FOMO Kernel · 用哲學鏡片復盤你的交易
把一份交易紀錄,變成一張「逼你下次只改一件事」的復盤卡。 機械層(Python)負責抓大放小——只挑最大的行為漏洞;哲學鏡片負責找動機——問出那筆交易背後你不願承認的原因。
何時用
用戶想復盤自己的交易、想知道「我反覆犯的錯是什麼」、丟給你一份券商 CSV / 對帳單、或直接說 /fomo-kernel。沒有資料時,請他提供券商 CSV / 對帳單(截圖也行,Step 0 讀得懂),並同時給「試駕」選項(見下節);只想看靜態長相 → README 的範例卡。
🧪 試駕模式(沒資料也能體驗流程;三個防護缺一不可)
用戶沒資料或想先體驗 → 用 AskUserQuestion 給兩選項:「提供我的 CSV / 先用內建假資料試駕一遍」。選試駕 → 拿 mock/mock_trades.csv 走完整四步流程,但:
- 狀態一律不落盤:
TR_STATE_OUT指到臨時目錄(如mktemp -d下);~/.trade-coach/的 log.jsonl / theses.jsonl / profile.md 一個字都不寫——假資料的承諾進了教練記憶,下次真復盤的對帳基準就是髒的。收尾改成一句講解:「真實使用時,這條規矩會存進你本機的教練記憶,下週回來先對帳」。 - Step 2 照問,但標明是演練:動機問題照走 AskUserQuestion——試駕就是要讓他體驗「我的答案會改變卡」這個差異化;但問句裡標明「示範資料,隨便選一個,看卡怎麼跟著變」,不逼他為不是他的交易編動機(#53 的尷尬就消了)。
- 卡標示範:卡頭標「示範 · 假資料,非真實成績」;α/β 附一句「示範資料失真,別當真」——失真警告是呈現層(你)的責任,引擎對任何輸入一致、沒有 demo 分支(#89)。
卡尾必收一句引導:「想復盤自己的交易 → /fomo-kernel your.csv」。
🔒 隱私第一(每次都要遵守)
- 用戶的交易 CSV 全程留在他本機。你只在他的環境裡跑
engine/trade_recap.py,不上傳、不複製到別處、不寫進任何雲端。 - 不要把用戶的交易內容寫進記憶、不要外傳給任何人(包括 skill 作者)。
- 誠實邊界(隱私話術別過度承諾):資料不上傳後端、不落地儲存到別處、作者永遠拿不到;但你(Claude)為了復盤必須讀 CSV/JSON,交易內容自然進你的 context —— 這跟用戶平常用 Claude 一樣,不是「完全不經過任何伺服器」。README / 卡上的隱私話術照這個精度寫,別講成「絕對不離開你的電腦」。
- 要回給作者的只有一件事:「這張卡有沒有用」的文字反饋(用戶自願)——不含任何交易明細。
- 用戶沒給資料時,請他提供或走試駕模式(內建假資料、不落盤);絕不要主動去翻他機器上的真實對帳單。
🌐 Output language (apply every time)
Everything the user sees — your dialogue, the AskUserQuestion options, and the final card — must be in one resolved output language. Do not hardcode a language. Resolve it per session, first match wins:
- Explicit request this session — the user says "give it to me in English" / "用中文" / passes
lang=en. - Saved preference —
output_lang:in~/.trade-coach/profile.md, if present. - Conversation language — the language the user is speaking to you in right now. This is the default; follow it, don't impose a language.
- Fallback — Traditional Chinese (
zh-TW).
Once resolved:
- Run the whole flow (dialogue, questions, card) in that language. Lens files (
rubric/*.lens.json) currently carry Traditional-Chinese quotes/prompts — translate them faithfully on the fly into the resolved language when you write the card. - Pass it to the engine each run as
TR_LANG=<code>(e.g.TR_LANG=en) for forward-compatibility. The engine does not consume it yet — its own printed CLI card and lens strings stay Chinese for now; full engine/lens localization keyed onTR_LANG(a strings table) is tracked separately as internationalization work. This still governs what the user sees today, because the card is one you write frombuild_card_data()'s structured JSON, not the engine's printed card. - Persist it: on first run, or whenever the user switches, write
output_lang: <code>into~/.trade-coach/profile.md(alongside the profile principles) so the next session resolves to their preference at step 2.
The Traditional-Chinese phrasings, question templates, and card examples throughout the rest of this SKILL are illustrative of intent, not literal strings to copy — express their meaning in the resolved language.
工作流程(四步)
分工原則:engine 做純算(確定性),Claude 做世界知識(格式 / 分類 / 動機)。 需要認得世界的事都交給 Claude,engine 不 hardcode。
開場 · 讀本機狀態 + 偵測這次要處理什麼(weekly loop 入口)
投資不是復盤一次就結束。 這個 skill 是一條每週迴圈:匯入 CSV → 偵測新交易 / 新倉 → 只問缺的動機 → 寫本週 review → 出卡。目標是取代你每週的交易紀錄,而不是每次重算同一個洞。動 CSV 前先讀本機狀態(都在 ~/.trade-coach/,純本機、不外傳):
mkdir -p ~/.trade-coach
cat ~/.trade-coach/log.jsonl 2>/dev/null # 每行一次 review session(薄 metric + 承諾);空 = 第一次
cat ~/.trade-coach/theses.jsonl 2>/dev/null # 每行一筆 thesis event(append-only,不覆蓋);持股動機庫
cat ~/.trade-coach/profile.md 2>/dev/null # 你的交易目標 + 3 條個人原則(復盤對照基準);空 = 第一次幫你建
路由(讀完上面兩檔 + 跑完 Step 1 engine 後判定):
- log 空 → 初診:跑完整 Step 0→4,收尾寫第一筆 session + 為值得問的持倉建 thesis。
- log 非空 → 對帳(每週迴圈),依序:
- 偵測新交易 = engine state
date_end與 log 最後一筆date_end之間的交易(本週新動作,復盤重點;不再從頭講舊帳)。 - 偵測缺 thesis 的持倉 = engine state
holdings.positions每個cycle_id比對theses.jsonl。新建倉(新 cycle_id)或從沒寫過 thesis 的持倉 = 缺。 - 先對帳(Step 2.5):上次
commitment的 metric 新舊值 + 上次每筆 active thesis 的exit_trigger有沒有觸發。 - 補缺的 thesis(Step 2):缺 thesis 的持倉由 AI 猜(標
inferred、零提問),只對「行為矛盾、金額最大的 1 檔」問一句;已有 thesis 的不碰(除非 trigger 觸發)。
- 偵測新交易 = engine state
兩個狀態檔都是用戶自己的本機教練記憶,永不外傳、不回作者(隱私第一)。
log.jsonl存聚合 metric + 承諾(max_pos_pct=0.48、「虧損不加碼」);theses.jsonl存 per-position「為什麼持有 + 什麼條件算錯」。append-only:修正 thesis = 補一筆新 event(帶revises指回舊的),不蓋舊的 —— 才能跨期看你當初怎麼想、後來怎麼變(蓋掉 = 跨期對帳失效 + 鼓勵事後合理化)。
Step 0 · 把任意券商格式變成引擎吃得下的(用讀檔者自己的 Claude)
用戶的 CSV 可能來自任何券商、欄位名各異,甚至是一張對帳單截圖。不要寫死 parser——你(Claude)直接讀它,轉成標準欄位存暫存 CSV:Symbol,Action(BUY|SELL),Quantity,Price,TradeDate(YYYY-MM-DD),RecordType(填 Trade)。這步用的是用戶自己的 Claude 額度,零後端成本,且天生吃得下所有券商——不必為每家券商寫轉換器。
Step 0.5 · 生成 driver map(讓冷門股不失準)
引擎 sector 表只認常見股,冷門股會變「未分類」→ 分散維失真。你(Claude)對用戶實際持倉用世界知識分類:每檔 → [sector, thematic],thematic=1 表示跟別檔同屬一個跨產業主題(AI capex / 減重藥 / 太空…)。寫成 JSON {"PLTR":["軟體雲",1],"CEG":["核電",1],"XOM":["能源",0]},用環境變數餵進去:TR_DRIVER_MAP=/path/driver_map.json python3 engine/trade_recap.py <csv>。
Step 1 · 跑引擎,抓大放小
SKILL 走 JSON 模式拿結構化資料,Step 3 你自己寫卡 —— 不要照搬 engine 預設輸出(那是 README quickstart 用的乾淨人話卡 / fallback,不是 SKILL 規格那張定論卡):
mkdir -p ~/.trade-coach
TR_JSON=1 TR_STATE_OUT=~/.trade-coach/last_state.json python3 engine/trade_recap.py <標準化後的CSV>
# TR_JSON=1 → stdout 純 JSON(build_card_data,給你在 Step 3 寫敘事卡用);meta 走 stderr
# TR_STATE_OUT → 寫一份薄 state(對帳用),跟 TR_JSON 平行,可同時設
# 都不設 → 印預設人話卡(README quickstart 用)
# TR_DEBUG=1 → 在預設輸出補回 5 維 severity raw 表(開發/驗證用,絕不上卡)
🔧 引擎報
ModuleNotFoundError(如 pandas / yfinance):依賴多半裝在 venv / pyenv 的另一個 python 裡。找到裝了依賴的直譯器路徑重跑一次即可,常見是 repo 根的.venv/bin/python3(README 安裝節的 venv 三行裝出來的)——把上面指令的python3換成那個路徑;別急著全域 pip(新 macOS 會被 PEP 668 擋)。 引擎吃標準欄位(Symbol / Action(BUY|SELL) / Quantity / Price / TradeDate),TR_JSON=1吐的結構含:
top_holes:已選好的 top 1–2 機械洞 + 對應鏡片 quote(融入敘事,別當結語)。candidate_rules:2–3 條候選規矩(卡上列候選,Step 3.5 讓用戶挑/改一條,別只給第一條;引擎只給一條時就用那條)。thesis_questions:per-ticker 持股假設問句 — 這是給 Step 2 對話用的,絕不准印在卡上(SKILL 鐵律:確認在出卡之前)。alpha_beta_breakdown/payoff_attribution/ticker_diagnosis:完整數字,你拿去組敘事。dims_raw:5 維行為診斷(每維 severity 0–1)— 別整張攤出來,用「一句人話」帶過非 headline 的維度(SKILL 鐵律:不放 5 維小數表)。- alpha/beta:贏大盤多少、其中多少只是「膽子大(高 beta)」、真本事(Jensen's α)剩多少。
excess_split把「贏大盤」機械拆成 押對賽道(allocation)+ 板塊內選股(selection),兩項相加恆等於贏大盤 pp——這兩個數是會計恆等式、不需統計顯著,永遠可講;alpha_stat給 α 的 95% 區間 / t 值 / 分級(顯著與否),語氣照它走。 - 結構化 state(
TR_STATE_OUT):給對帳用的薄 JSON,讀這幾個欄位 ——headline_dim/headline_metric:這次最大的洞 +(key, value)。commitment:{rule, metric_key, metric_value, goal}= 引擎的機械預設承諾(下次只改這一件 + 追蹤哪個 metric)。Step 2 動機問完可能推翻它(實例:engine 給「別加碼」,用戶答「計畫內定投」→ 改盯ai_pct)→ 收尾要存卡上最終那條,不是這個預設。對帳比metric_key,別比 headline(規矩維 ≠ headline 維才不對錯帳)。metrics:全 metric 快照(max_pos_pct / avgdown_count / ai_pct / max_sector_pct / top3_pct / payoff / beta / alpha_ann …),對帳時拿承諾的metric_key反查新值(集中度承諾就追ai_pct)。alpha_ann/alpha_t/alpha_credible:α 永遠有數,語氣看統計。alpha_credible=true(樣本 ≥1 年且 |t|≥1.96)才可用「真本事」語氣(顯著的負 α 也是可講的定論);false→ 數字照講但必帶不確定性:「α 年化 +X%,但 95% 區間 −Y%~+Z%——統計上還分不出是本事還是運氣」。卡在哪要講清楚,引alpha_beta_breakdown.alpha_stat.gate.reason:sample_short=不到 1 年 → 才是樣本不足;not_significant=區間太寬 → 常見原因是持倉集中、個股雜訊大(這條跟『最大的洞=集中度』是同一件事,要串起來講——但這是工具的侷限,不是他沒本事)。贏大盤幾 pp 必配拆帳:押對賽道 vs 板塊內選股(excess_split),coverage<1時補一句「X 檔無板塊對照、按大盤計」。insufficient_data:true(round-trip<3 或交易跨度<~84 日曆日≈60 交易日)→ 只做體檢、不硬出 commitment(見開場/收尾)。
抓大放小鐵律:只看引擎排在最前面的 1–2 個洞,其餘忽略。不要把 5 維全攤給用戶——那就變成另一份報表了。引擎已經幫你收斂,你不要再展開。
Step 2 · 出卡前的對話確認(持股假設 + 動機)——這層才是鏡片,不可省
流程鐵律:確認在出卡之前,不在卡上。 機械算得出「你做了什麼」(what),算不出「你為什麼這樣做」(why)。所以先在對話裡問完所有需要你定性的問題、拿到答案,Step 3 才出最終卡——卡是確認後的定論,不是帶問號的待辦。別把問題做成卡上的按鈕(那是把 Step 2/3 混在一起)。
問法鐵律(#55):動機/定性問題一律用 AskUserQuestion 工具問,不要寫成文字段落等用戶打字。 每題二選一(選項裡把兩個動機都寫成人話)+ 用戶可跳過,一次最多 2–3 題,5 秒可點完。自由打字 = 摩擦:用戶會直接略過 Step 2,卡就只能標「待確認」半成品,教練迴圈斷在第一環。只有執行環境沒有 AskUserQuestion 工具(非 Claude Code 的 agent)才退回對話問。
消重鐵律:答過的不重問(每週被問同一題 = 教練失憶,用戶會走)。 engine 只讀 CSV,不讀記憶——thesis_questions 每次都會對同一批標的重新生成,消重是你(Claude)的責任:問任何一題之前,先比對 theses.jsonl(Step 2.5 重建出的 active thesis)與 log.jsonl 最近一筆的動機定性。同一 cycle_id 用戶已答過(thesis maturity=testable、或上次卡已標凹單/逢低定論)→ 這題不再問,直接引用舊答案入卡(「上次你說 MSTR 是凹單」);要更新認知走 Step 2.5 對帳的「順手改」,不是重新問卷。只有三種情況同一標的可以再問:① 新 cycle(清倉後重建倉)② 行為顯著變了(上次答逢低、這次又深虧加碼 N 次 → 對帳語氣問「還是當初那個理由嗎」,引用他上次的答案)③ 用戶上次跳過(inferred 不算答過,但也只在它仍是「金額最大 + 行為矛盾」時才重問一次)。
(a) 持股假設:逢低加碼 vs 凹單(標的層挑出來的) —— 引擎 ticker_diagnosis 對「金額大 + 虧損中狂加碼」的標的生成 thesis_q。機械分不出逢低/凹單,因為差別在加碼當下 thesis 還在不在(= why,算不出),所以挑出來問你:
- 還在虧的(如 MSTR 加 26 次還虧):「你還相信當初的理由,還是不想認賠在凹單?」
- 賺回來的(如 GOOG 加 9 次現賺):「計劃內核心倉,還是套牢後才合理化、剛好漲回?」 → 答「凹單/合理化」→ 卡標凹單;答「逢低/計劃內」→ 移除警告、標逢低。機械挑誰問,你的答案定性。
(b) 動機(鏡片) —— 從引擎的洞,對應最該問的交易,用下面的鏡片動機單元問。讀 rubric/vincent-yu.md 拿原話,讓問題真的是「這套哲學會問的」,不是泛泛而問。
鏡片動機單元 → 交易訊號 → 問句模板:
| 引擎抓到的洞 | 鏡片單元(去 rubric 看原話) | 問用戶的二選一(舉例,要換成他的真實 ticker/數字) |
|---|---|---|
| 虧損中加碼攤平 | A2 試探≠加碼、G 不想認賠 | 「PLTR 你從 24 一路加到 15,是因為你知道了一個進場時不知道的新利多,還是不想認賠、想攤低成本等回本?」 |
| winner 賣太早 | D1 時間軸、G1 焦慮驅動 | 「你賣掉賺錢的有 71% 後來繼續漲。那些賣出是thesis 到價了,還是賺了怕回吐、落袋為安?」 |
| 部位梭哈 | B1 賠率、A1 信念是光譜上的sizing | 「PLTR 佔你 48%。這個 size 是算過最壞情況能承受,還是就是很看好、直接重壓?」 |
| 集中在同一 driver | B2 driver 不同才算分散 | 「你 X 檔 Y% 是 AI。你當初覺得這樣算分散,還是刻意押這個賽道?」→ 答案決定標題,見下規則 |
| 把 beta 當 alpha | E2 拆解你承擔什麼風險 | 「你贏大盤 +80pp,但 β=1.8。這些報酬你算自己選股的本事,還是敢押高波動 AI換來的?」 |
| 連勝後加大 sizing | G2 連勝是該檢查的警報 | 「這筆加大,是有獨立的新理由,還是最近都對、覺得手感正順?」 |
規則:
- 一次最多問 2–3 個(抓大放小,別審問)。每個都是二選一,5 秒可答。
- 用戶選哪個都不要說教——這是鏡子,不是審判。他選「不想認賠」就接「好,那這就是下面那條規矩要擋的事」。
- 答案要改標題,不是只補在『看動機』那行——這是 Step 2 的全部意義。最常踩雷的是集中度:用戶答「刻意押賽道 / 知道集中」→ 那個洞絕不准叫「假分散」(他沒在騙自己,你問了他還罵他=自相矛盾)。改框成「你選的集中押注」,打的點變兩個:① 它讓你的選股本事測不出來(就是 α 判不出的原因,串起來講)② 集中回檔風險——有沒有減碼/停損線。答「以為分散」→ 才用「假分散」。凹單/逢低、梭哈同理:答案怎麼說,標題就怎麼標。
- 用戶若略過不答,就只用機械洞出卡,不強逼。
(c) 建立 / 更新 thesis(AI 猜為主、問為輔 —— 取代週記錄的核心)
鐵律:降摩擦 + 克制。 這產品的命是「不變成你想逃的重系統」。thesis 絕不逼用戶坐下來填 —— 由你(Claude)從交易行為 + ticker 世界知識猜,預設落盤標
inferred,用戶不爽再改。讓用戶冷啟動就有完整 thesis 庫、零填寫成本。
主路徑:AI 推測,不問用戶。 對每個缺 thesis 的持倉,用 engine 行為訊號(ticker_diagnosis 的 定投/凹單/押太重/紀律持有 + 加碼次數 + cur_ret + 持有天數)+ ticker 是什麼公司 / 賽道,猜一筆 thesis:
- why:從行為猜(規律加碼 + 長抱 + 獲利 → 「定投型核心倉」;疑似凹單 → 「攤平等回本(待確認)」)。
- 三 trigger:從 ticker 類別猜常見的 —— 成長股 → 營收 / 用戶增速失速;週期股 → 週期反轉;AI 概念 → capex 轉弱。
reduce從當前 sizing 猜(已超標 → 該檔減碼線)。 - 每條標來源
(推測自:規律加碼+長抱),讓用戶一眼看出是猜的、好校正。 - maturity =
inferred,全部直接落盤、零提問。
只在一種情況問用戶一句(抓大放小,別審問):
- 行為矛盾、金額最大的那 1 檔(疑似凹單 / 深虧還加碼)—— 機械分不出「逢低 vs 凹單」,差別只在 why(算不出)。問一句(同樣走 AskUserQuestion,三個選項直接給他點):「{ticker} 加碼 N 次還虧 X%,我猜是不想認賠(凹單)—— 對 / 有新理由(逢低)/ 跳過」。
- 一次最多問 1 檔;其他全用猜的,不打擾。用戶跳過 → 留
inferred,不追問。
校正走「對帳時順手改」,不是「坐下來填」:對帳(Step 2.5)呈現猜的 thesis + trigger 觸發,用戶看到猜歪的順手改一條 → 該 thesis 升 testable(用戶確認過)。明說「投機跟風沒 thesis」→ 標 draft。thesis 越用越準,但從不逼填。
鐵律不變:exit_trigger(看錯了,事實)≠ stop(跌多少賣,價格)。 猜的時候 exit 也猜「thesis 失效的事實」,不是猜停損價。寧可 inferred 也不要假的 testable。
Step 2.5 · 對帳上次的 thesis 與承諾(只在對帳模式 / log 非空)
先重建「目前有效的 thesis」(append-only 讀取必做,否則 active 名單會爆掉):theses.jsonl 是 append-only,同一 thesis 有多筆 revision。讀取時按 thesis_id 建 event log,每個 cycle 只取 latest 未被 supersede 的:
- 後出現的
revises: <舊 id>→ 把舊 id 標 superseded、排除。 - cycle 已清倉(該
cycle_id不在 engineholdings.positions)→ 該 thesis 標 closed、不進對帳(歷史保留)。 - 結果 = 每個 active cycle 恰一筆有效 thesis。
出新卡先回看上次:
- 承諾 metric:上次
commitment.metric_key舊值 → 這次 engine state 新值(「上次說壓到 20%,當時 51% → 現在 48%:在降、沒達標」)。 - trigger 檢查 —— 只查三類,別逐檔掃(逐檔掃 = 把復盤變研究任務 = 回到高級拖延):
- 只查:本週有交易的 ticker + 上次承諾關聯的 ticker + 最大風險 1 檔。其餘 active thesis 標「本週未檢查」。外部新聞 / 基本面查是 opt-in(用戶說要才查,不每週必跑)。
- 對這幾檔看 trigger 觸發,措辭依 maturity 分(最關鍵 —— 別把 AI 猜的當你的承諾):
testable(你確認過的) → 才用定論:exit_trigger觸發 = 🔴「你定的『{exit}』發生了 —— thesis broken,該走」。inferred(AI 猜的) → 只能用問句,絕不說「該走」:🟡「我猜的失效條件『{exit}』似乎發生了 —— 這符合你當初買的邏輯嗎?符合 → 考慮出場;不符 → 順手改成你真正的 exit」。inferred一律帶[⚠️ AI 猜測待校正]標。
review_trigger觸發 → 提示重看,不催賣。
- 對帳完才講本週新洞(headline)。只收斂一個洞 + 一條規矩,別把每筆 thesis 攤成報表。
Step 3 · 出一張卡(收斂鐵律)——拿到 Step 2 答案後才出
🚦 出卡前 self-check(沒過一律不准出卡):
- engine 用
TR_JSON=1跑過了嗎? 拿到的是build_card_data()結構化 JSON,不是預設那張人話卡。 - Step 2 對話完成了嗎? —
thesis_questions至少對「金額最大 + 行為矛盾」的 1 檔問過 + 拿到答案;主要動機鏡片(對應 headline_dim 的)問過 1 句。沒問完就出卡 = 退化成「engine + 套版」,失去 SKILL 的價值。 - 你打算自己用敘事寫卡,不是照搬 JSON 欄位? 把 JSON 當資料源,自己組句子,不要列
〔X〕內容的 dashboard 拼接。
三項都過了,才讀 card-spec.md,照裡面的規格出卡——卡的結構、禁止清單、private/public 兩種卡與 redact 規則、敘事鐵律、處方層全在那份檔裡,這裡不重複。 Step 2 還沒問完,不要提前打開它:在那之前,你唯一的目標是把動機問完、拿到答案。
Step 3.5 · 規矩收斂:讓用戶挑一條,存進記憶(不可省——這是下次對帳的入口)
卡上列的 2–3 條候選規矩不是結局。出完卡立刻用 AskUserQuestion(選項 = 各候選 + 「這週不承諾」,Other 可改寫)問一句:「選一條當下週對帳的承諾?會存進本機 log,下次開場第一句就對它:說到有沒有做到。」(#56:你不准代選,他點了哪條才存哪條。)
- 選項標籤 = 規矩短語,description 寫「下週看哪個數 + 現在的值」——一律人話,內部 metric key 不准出現在任何用戶看得到的文字裡(真人反饋:「追蹤 max_pos_pct,本週基線 42%」= 拗口)。✅「下週就看:最大單注佔比,現在 42%」/ ❌「追蹤 max_pos_pct,基線 42%」。用戶要能 5 秒選完。
- metric_key 對映(log 存內部名,顯示用人話):單一標的佔比 →
max_pos_pct(人話「最大單注佔比」);虧損加碼 →avgdown_count(「虧損加碼次數」);賽道集中 →ai_pct(「同賽道佔比」);板塊 →max_sector_pct(「最大板塊佔比」);盈虧比 →payoff。對帳比 metric,不比 headline(規矩維 ≠ headline 維才不對錯帳)。 - 用戶挑完 → 立刻走下面收尾腳本落盤,
FINAL_RULE/METRIC_KEY填他選(或改寫)的那條。 insufficient_data=true時的分工:機械預設 commitment 照舊不出(引擎已設 null,別把缺資料的猜測當承諾);但用戶自己選的規矩照存——行為承諾是他的意志,跟樣本夠不夠無關;樣本不足影響的只是 metric 基線的解讀。落盤時標source: "user_chosen"+baseline_note: "short-sample baseline",下次對帳措辭看方向(在降/沒動/變糟),不判達標。- 用戶選「這週不承諾」/ 跳過 → 收尾
FINAL_RULE填SKIP:log 照存本週 metrics(供趨勢對帳),commitment=null,下週不拿規矩對他。
Step 4 · 收一句反饋(驗證用)
出完卡,問一句:「這張卡,有戳中你嗎?還是哪裡不對?」 把這句反饋(純文字、不含交易明細)記下來給作者——這是這個 skill 唯一要回收的東西,用來驗證「這面鏡片產出的卡對別人有沒有用」。
鏡片的定位:普世機械 + 一套可換的哲學
- 判分的 5 維算法是普世行為金融(Odean 的處置效應、beta 歸因)——這層誰來都一樣,跟用哪套哲學無關。
- 鏡片不可替代的地方在 Step 2 找動機:用什麼框架解讀「你為什麼攤平、為什麼賣太早」,以及 Step 3 那句原話。換一套哲學,問法與原話就不同——這才是鏡片的價值,不是貼個名字。
- 預設鏡片是「存活紀律派」:來自一位投資人公開文章的原則蒸餾(
rubric/vincent-yu.md逐條標出處),屬引用非轉載、非經本人背書。 - 鏡片是可換層:換一套哲學 = 換
rubric/*.lens.json,engine 程式碼一律不動;同一架構可掛多套哲學。 - 對外定位:research / coaching support,不構成投資建議。
狀態迴圈(記憶 + 持續):對帳 + 收尾
「投資不是復盤一次就結束。」第二張卡的價值在進度——上次那條規矩守了沒,不是再照出同一個「分散」(機械洞會收斂、會重複)。這靠開場讀、收尾寫的本機狀態 ~/.trade-coach/log.jsonl 撐起來。
對帳(log 非空時,卡開頭先做):
- 讀 log 最後一行的
commitment = {rule, metric_key, metric_value}。 - 這次引擎 state 的
metrics[commitment.metric_key]= 新值。 - 卡第一句就對帳:
上次說要{rule 白話},當時{metric 人話}={舊值} → 現在 {新值}:{在降/沒動/變糟}{達標沒}(例:「上次說逢低加碼要有頂,當時最大單注 42% → 現在 31%:在降」)。用戶的數字、白話、metric key 內部名不上卡(人話對映見 Step 3.5)。commitment 帶source:"user_chosen"→ 措辭用「你上次自己選的規矩」(這是他的承諾,不是系統派的);帶baseline_note:"short-sample baseline"→ 只講方向(在降/沒動/變糟),不判達標。 - 再講新一輪的洞(headline_dim)——若跟上次同維,直說「這條還沒過關,先別開新戰場」;若是新維,才開新洞。永遠只收斂一個洞 + 一條規矩。
規矩承諾:用戶主動選,你不准代選(#56)。 挑規矩的互動走 Step 3.5(AskUserQuestion:候選各一 + 「這週不承諾」,Other 可改寫)。用戶沒點選之前,任何規矩都不准寫進 log —— 承諾是下週對帳的錨點,錨不是他自己下的,對帳時他只會一頭霧水、迴圈失效。選「這週不承諾」→ 下面 FINAL_RULE 填 SKIP(照存本週 metrics 供趨勢對帳,但 commitment 為空、下週不拿規矩對他)。
收尾(出完卡 + Step 3.5 用戶挑完規矩 + Step 4 收完反饋,append 一行):
# 把這次的薄狀態接到 log(只存聚合 metric + 規矩,不存任何交易)。
# ⚠️ commitment 要存【用戶在 Step 3.5 親選的那條】(#56),不是引擎機械預設、更不是你代選的 ——
# Step 2 動機問完常推翻引擎預設。實例:engine 預設「虧損別加碼」,但用戶答「NVDA 是計畫內定投」
# → 用戶改挑「盯集中度 ai_pct」那條。
# 兩格填用戶挑的規矩 + 對應 state.metrics 鍵;用戶選「這週不承諾」→ FINAL_RULE 填 SKIP。
# gate 規則:SKIP 一律不存 commitment;insufficient_data 只擋「engine 預設 fallback」,不擋用戶明選
# (行為承諾跟樣本無關,只是基線標 short-sample、下次對帳看方向不判達標)。
FINAL_RULE="AI 暴險封頂 70%:要加 AI 新倉先問新賽道還是同一注往上疊"
METRIC_KEY="ai_pct"
python3 - "$FINAL_RULE" "$METRIC_KEY" <<'PY'
import json, os, sys, pathlib
st = json.load(open(os.path.expanduser("~/.trade-coach/last_state.json")))
dflt = st.get("commitment") or {} # engine 機械預設(fallback)
arg1 = sys.argv[1] if len(sys.argv) > 1 else ""
skip = (arg1 == "SKIP") # 用戶明確「這週不承諾」(#56):不硬塞錨點
user_chose = (not skip) and bool(arg1.strip()) # 填了非 SKIP = Step 3.5 用戶親選
rule = ("" if skip else arg1) or dflt.get("rule")
mk = (sys.argv[2] if len(sys.argv) > 2 else "") or dflt.get("metric_key")
commitment = None
if not skip and rule and mk and (user_chose or not st["insufficient_data"]): # SKIP 一律不存;樣本不足只擋機械預設
commitment = {"rule": rule, "metric_key": mk,
"metric_value": st["metrics"].get(mk), "goal": "down",
"source": "user_chosen" if user_chose else "engine_default"}
if user_chose and st["insufficient_data"]:
commitment["baseline_note"] = "short-sample baseline" # 下次對帳看方向,不判達標
entry = {"date_end": st["date_end"], "headline_dim": st["headline_dim"],
"commitment": commitment, # 對帳對的是這條(用戶選定版,非機械版)
"metrics_snapshot": {k: st["metrics"].get(k)
for k in ("ai_pct","max_pos_pct","avgdown_count","avgdown_breach")}}
p = pathlib.Path(os.path.expanduser("~/.trade-coach/log.jsonl"))
with p.open("a", encoding="utf-8") as f: f.write(json.dumps(entry, ensure_ascii=False) + "\n")
print("appended commitment:", json.dumps(commitment, ensure_ascii=False))
PY
收尾 part 2 · 把本週建立 / 更新的 thesis append 到 theses.jsonl(append-only):
python3 - <<'PY'
import json, os, pathlib
# Claude 把本週「新建倉 / 缺 thesis / trigger 觸發後更新」的 thesis 填 theses[]。
# thesis 是對話 articulate 出來的(engine 不碰);append-only:更新用 revises 指回舊 thesis_id,不蓋舊的。
session_date = "YYYY-MM-DD" # 本次 review 日(= engine state 的 date_end)
theses = [
# ⚠️ cycle_id 必須【照抄】engine state holdings.positions[ticker].cycle_id(3 段格式 ticker#開倉日#序號,
# 如 "NVDA#2024-01-12#1"),別自己拼 2 段 —— 格式不符 → 對帳(開場 §偵測缺 thesis)永遠匹配不上 →
# 每週把已寫過 thesis 的持倉當「缺 thesis」重問,記憶迴圈失效。
# {"ticker":"NVDA","cycle_id":"NVDA#2024-01-12#1",
# "why":"一句:為什麼持有",
# "triggers":{"review":"什麼消息/數字該重看","reduce":"什麼情況減碼","exit":"什麼代表看錯(非股價跌)"},
# "maturity":"inferred", # inferred(AI 猜,預設)| testable(用戶確認過)| draft(投機跟風沒 thesis)
# "stop":"", "target_size":"20%",
# "revises": None}, # 更新既有 thesis 才填舊 thesis_id
]
import time; _sid = int(time.time()) # session 戳:防同日多次 review 撞 id
p = pathlib.Path(os.path.expanduser("~/.trade-coach/theses.jsonl"))
with p.open("a", encoding="utf-8") as f:
for i, t in enumerate(theses):
t.setdefault("status", "active"); t["session_date"] = session_date
t["thesis_id"] = f"{t['ticker']}-{session_date}-{_sid}-{i}"
f.write(json.dumps(t, ensure_ascii=False) + "\n")
print(f"appended {len(theses)} thesis events")
PY
theses.jsonl是 append-only 動機庫:只追加、不改不刪。清倉不刪 thesis(留著當歷史);下次同 ticker 重建倉 = 新cycle_id= 新 thesis。Step 2.5 對帳讀每筆 active thesis 的 trigger 檢查觸發。隱私同 log:純本機、不外傳、不回作者。
收尾 part 3 · 個人 profile(只第一次建,當復盤對照基準):~/.trade-coach/profile.md 不存在 → 第一次從交易行為猜 3 條個人原則寫進去(同 inference-first:不逼填,用戶可改):持有風格(長抱 / 短打)、集中度傾向、紀律缺口(出場 / 加碼)。例:1. 長期持有型(中位 X 天) 2. 易重押單一賽道(AI X%) 3. 弱點在出場擇時(賣完常續漲)。之後每週對帳順帶一句「這批交易符合你定的原則嗎」,用戶要改直接改檔。
第一次樣本不足(insufficient_data=true):round-trip<3 或交易跨度<~84 日曆日(≈60 交易日),引擎已把 commitment 設成 null。機械層只做體檢、不硬出規矩(否則下次把缺資料的猜測當成已確認的承諾來對帳)。但 Step 3.5 照走:用戶自己挑的規矩照存(source:"user_chosen" + baseline_note,見收尾腳本 gate)——體檢卡也要留下記憶入口,否則第二週還是初診。卡收尾講一句「資料還太短,基線先存個底,累積多幾筆 round-trip 後對帳才看達標」;用戶跳過不選 → log append(commitment=null),下次來就接得上。
驗收這套有沒有真的「記憶」:
engine/test_state_loop.py把一份 CSV 按時間切兩段,累積跑「初診→對帳」,驗第二張卡有沒有真的對帳第一張承諾的那一維(而非重新初診)。改完 engine 或這段流程都先跑它。