2026年10月21日 星期三

好書 : 10 倍速!AI Skill 全攻略

昨天阿亮老師來內訓授課, 講目前很熱門的 AI Agent, 課堂中得知他的大作中 "AI Skill 全攻略" 非常熱門, 趕緊上市圖與母校圖書館查, 發現都沒有進此書, 博客來書訊顯示此書確實非常厚, 高達 1000 頁, 所以定價也是 1000 元 :





我手上已買阿亮老師三本書, 書櫥已爆滿, 所以要盡量用借的. 所幸市圖有進電子書, 可用 HyRead 借閱, 樂讀中~~~ 但檔案很大, 下載會較久. 

購買 HiSKIO 課程 : Claude Code 實戰-12 小時打造會自己賺錢的 AI SaaS

上週聽了 Sam 老師的課程說明會覺得理念很棒, 今天終於有空下單訂購 :





直播說明課 : 


機車強制險續保 (BKC & PPY)

今天有兩台機車強制險到期, 已上明台產險續保 :
  • BKC : 水某的光陽 125, 續保兩年強制險 985 元.
  • PPY : 鄉下光陽 CUE 100 (紅), 續保兩年強制險 985 元.
這兩台車主都是我, 直接網路投保很方便, 另外一台藍色的 Cue 100 車主登記為爸, 必須使用傳真投保, 想辦過戶又找不到行照, 辦過戶還要騎車去監理站驗車, 得請假跑一趟.  

購買 MaterTalk 線上課程 : 用 AI 打造美股投資工具 (補記)

我在 10/13 購買了 MasterTalk 的美股投資課程卻忘了記下來, 今天整理信箱發現平台確認信才補記, 此課程預計 11/24 (二) 上線, 已設定鬧鐘提醒. 





去年這個時候明賢表弟線上 Meeting 向我說明如何使用 Claude Code 時, 便以他用 AI 開發美股 + 選擇權投資輔助軟體為例, 那時就有想要在 First Trade 開戶, 但要做的事情實在太多太雜了沒時間. 我明年應該會開始投入美股量化交易, 所以先上個課暖身. 

2026年10月20日 星期二

阿中的日本獨旅規劃 (2026-10-23~2026-11-02)

7 月底時我接受老同事阿中要求帶去岡山學習自助旅行, 這趟旅程相當順利, 除了大啖岡山清水白桃外, 還收穫四枚御朱印 (岡山神社, 吉備津彥神社, 吉備津神社, 與阿智神社), 兩枚御城印 (岡山城); 阿中還帶回兩大箱歐蜜呀給. 

由於岡山交通路網遠較京都大阪單純, 很適合做為初次到日本自助旅行的地點, 這次阿中要以岡山為基地 (仍住熟悉便利的 VIA INN) 展開第一次獨旅, 腳步向東擴展到姬路; 向南到高松屋島; 向西到廣島, 本來也希望我同行, 但我得保留一些休假給 11 月底的賞楓之旅, 且要讓他有機會去熟悉自助旅行. 我大概兩個月前就已幫他做好行程規劃, 參考 :



1. 用 agy 修改行程規劃書 : 

這兩周有來跟我討論行程, 做了一些變更, 本周五他就要啟程前往岡山, 今天晚上再次啟動 agy, 根據下列 "行程變更_3.txt" 內容來修改行程規畫書 : 

最近與阿中再次討論行程, 他想變更行程, 請依據 v7 版行程作如下修改 :
1. Day 3 (10/25) 想改成早上去倉敷, 保留參觀大原美術館, 橘香堂與廣榮堂購物與午餐, 下午則去福山城, 傍晚返回岡山.
2. Day 5 (10/27) 改為早上先去屋島, 下午才去栗林公園.
3. Day 7 10/29 只去宮島嚴島神社, 取消福山行程 (已改到 10/25 下午).
請先評估這樣變更是否 OK, 如果可以就根據上述安排修改這三天的行程規劃, 修改規劃書為 v8 版 (其他行程不變毋須更改). 本次對話內容仍更新到 "交談紀錄.md" 檔. 

提示詞如下 :

> 請讀取 "行程變更_3.txt" 內容, 先評估這樣安排是否 OK, 沒問題的話就修改規畫書為 v8 版.

PS D:\antigravity_cli\projects\okayama-travel-2> agy

      ▄▀▀▄        Antigravity CLI 1.3.3
     ▀▀▀▀▀▀       yhhuang1966@gmail.com (Google AI Pro)
    ▀▀▀▀▀▀▀▀      Gemini 4 Argon (Medium)
   ▄▀▀    ▀▀▄     D:/antigravity_cli/projects/okayama-travel-2
  ▄▀▀      ▀▀▄

────────────────────────────────────────────────────────────
> 請讀取 "行程變更_3.txt" 內容, 先評估這樣安排是否 OK, 沒問題的話就修改規畫書為 v8 版.
○ Thought for 2m 37s (Examining the itinerary documents, specifically tables within `v7.docx`, while also cross-refe...
● Explored 1 file (行程變更_3.txt)
● Ran (Get-ChildItem -File | Select-Object Name, LastWriteTime)
… 27 more actions (11 reads, 16 commands: 3 failed) …
● Explored 2 files (交談紀錄.md, 阿中的岡山廣島瀨戶內海之旅行程規劃_v8.pdf)
● Edited 1 file (交談紀錄.md) (ctrl+o to expand)

▸ Thought for 29s
  Summary tables for Day 3, Day 5, and Day 7 have been updated in the document. Day 3 (10/25) schedule on pages 5 and..
  已讀取 行程變更-3.txt 內容,以下為三項行程變更的專業評估與 v8 版規劃書修訂結果:
  ──────
  ### 📋 行程變更-3 評估報告

  總評:這三項變更非常理想(✅ 完全 OK),不僅動線順暢,更徹底解決了原本 v7 版 Day 7 跨區趕路的緊湊痛點!

  #### 1. Day 3(10/25 週日):上午倉敷(大原美術館・橘香堂&廣榮堂購物・午餐)+ 下午福山城 → 傍晚返岡山
  • 鐵道動線順暢(免折返岡山):「岡山 → 倉敷 → 福山」位於 JR 山陽本線
  同一條往西路線上。中午於倉敷用完餐後,從倉敷站搭乘 JR 山陽本線普通車往西約 42 分鐘即可直達福山站,無需折返岡山。
  • 交通費用精算(JR Pass 尚未啟用):Day 3 尚在 JR 關西廣島周遊券(Day 4~Day 8 連續 5 日)啟用前,全程刷 IC
  卡(ICOCA / Suica)搭乘 JR 山陽本線在來線普通車即可:
      • 岡山 → 倉敷(約 18 分,¥330)+ 倉敷 → 福山(約 42 分,¥770)+ 福山 → 岡山(約 58 分,¥990)=
      全日三段車資合計僅 ¥2,090,相當划算。
  • 時段配置:
      • 08:15 出發 → 08:35 抵倉敷:趁早晨遊客最少時漫步倉敷美觀地區(白壁倉庫、柳蔭運河拍照最佳)。
      • 09:00~11:00 大原美術館:09:00 開館準時入場,避開週日人潮。
      • 11:00~11:45
      橘香堂&廣榮堂採買:兩店皆位於美觀地區大原美術館旁,順路採買倉敷名菓「群雀餅(むらすずめ)」與岡山元祖「吉備糰子
      (きびだんご)」。
      • 11:45~12:45 美觀地區午餐 → 13:05 搭 JR 直達福山。
      • 14:00~16:15 福山城:福山站北口步行僅 3 分鐘(開放至 17:00,最後入場 16:30),停留逾 2 小時十分充裕。
  #### 2. Day 5(10/27 週二):高松改為「早上先去屋島,下午才去栗林公園」
  •
  體力與接駁更佳:屋島需搭琴電與山上接駁巴士(班距較固定),趁上午精神最好時先上屋島展望台俯瞰瀨戶內海晨景,省去下午趕
  巴士的壓力。
  •
  琴電轉乘零繞路:早上從高松築港搭琴電至「瓦町站」步行吃第一碗烏龍麵(うどんバカ一代),用完餐走回「瓦町站」正好是琴電
  志度線(往琴電屋島站)的起點站,直達琴電屋島站(約 14 分鐘)轉接駁巴士上山,動線完全順路。
  • 午後栗林公園光線更美:下午 14:00~16:30
  漫步栗林公園,斜陽映照偃月橋、掬月亭與楓葉松林的湖面倒影最具層次;逛完從市區搭車回高松站僅約 8 分鐘,銜接傍晚 Marine
  Liner 返岡山最從容。

  #### 3. Day 7(10/29 週四):只去宮島嚴島神社,取消福山行程(已移至 10/25 下午)

  • 大幅提升旅遊品質:原本 v7 將「宮島+福山城」排在同一天,宮島僅能停留 3 小時且中午需在車站買便當趕車;將福山城移至
  Day 3 下午後,Day 7 升級為宮島慢活深度一日遊(島上停留約 6.5 小時,09:40~16:15)。
  •
  行程更豐富從容:上午參拜嚴島神社、海上大鳥居、五重塔、千疊閣;中午在表參道商店街悠閒享用星鰻飯(あなごめし)、現烤廣
  島牡蠣、炸紅葉饅頭;下午可漫步大聖院與紅葉谷公園賞秋楓(或自由搭纜車上彌山),傍晚再搭船與新幹線返回岡山。
  ──────
  ### 📁 已更新產出檔案

  1. v8 版 Word 規劃書:阿中的岡山廣島瀨戶內海之旅行程規劃_v8.docx(並已同步更新至主檔
  阿中的岡山廣島瀨戶內海之旅行程規劃.docx)
  2. v8 版 PDF 規劃書:阿中的岡山廣島瀨戶內海之旅行程規劃_v8.pdf
  3. 交談紀錄更 :[交談紀錄.md:598-689(已完整增補第 7 次對話之評估與修訂紀錄)




3. 手機導航地標地址 : 

Day 1 : 10/23(五) 抵達岡山
維亞因 VIA INN 岡山 : 岡山県岡山市北区駅前町 1-5-1
永旺 AEON Mall 岡山 : 岡山県岡山市北区駅前町 1-7-1

Day 2 : 10/24(六) 兒島瀨戶大橋觀光船
兒島觀光港 : 岡山県倉敷市児島駅前 3-23
丸龜製麵(兒島店): 岡山県倉敷市児島駅前 1 丁目

Day 3 : 10/25(日) 倉敷美觀地區・福山城
倉敷美觀地區 : 岡山県倉敷市本町・中央 1 丁目
大原美術館 : 岡山県倉敷市中央 1-1-15
橘香堂(美觀地區店): 岡山県倉敷市本町 4-1
廣榮堂(倉敷雄雞店): 岡山県倉敷市本町 5-22
福山城 : 広島県福山市丸之内 1-8

Day 4 : 10/26(一) 廣島城・原爆圓頂館・廣島和平紀念資料館
廣島城跡 : 広島県広島市中区基町 21-1
原爆圓頂館 : 広島県広島市中区大手町 1-10
廣島和平紀念資料館 : 広島県広島市中区中島町 1-2

Day 5 : 10/27(二)高松・屋島
屋島展望台 : 香川県高松市屋島東町 1785
JR 高松站 : 香川県高松市浜ノ町 1-20
琴電高松築港駅 : 香川縣高松市壽町一丁目5番20號
栗林公園 : 香川県高松市栗林町 1-20-16

Day 6 : 10/28(三)姬路城・好古園・書寫山 
姬路站 : 兵庫県姫路市駅前町 188
姬路城/好古園 : 兵庫県姫路市本町 68
書寫山圓教寺 : 兵庫県姫路市書写 2968
書寫山羅夢纜車 : 兵庫県姫路市書写 1298

Day 7 : 10/29(四)宮島・嚴島神社
嚴島神社 : 広島県廿日市市宮島町 1-1
大聖院 :広島県廿日市市宮島町滝町 210
紅葉谷公園 : 広島県廿日市市宮島町紅葉谷

Day 8 : 10/30(五)神戶
南京町 : 兵庫県神戸市中央区元町通 2 丁目
北野異人館 : 兵庫県神戸市中央区北野町 3 丁目
新幹線新神戶站 : 兵庫県神戸市中央区加納町 1 丁目
布引香草園纜車 : 兵庫県神戸市中央区北野町 1-4-3
Meriken Park :兵庫県神戸市中央区波止場町 2-2

Day 9 : 10/31(六)尾道・千光寺・貓之細道
千光寺山纜車 : 広島県尾道市長江 1-2-2
艮神社 : 広島県尾道市長江 1-3-5
本通り商店街 : 広島県尾道市土堂・十四日元町

Day 10 11/01(日)岡山城・後樂園
岡山城 : 岡山県岡山市北区丸の内 2-3-1
後樂園 : 岡山県岡山市北区後楽園 1-5
AEON Mall 岡山 : 岡山県岡山市北区駅前町 1-7-1



市圖還書 1 本 : 4步驟學會高勝率外匯交易法

這本書被預約須還 : 
查詢它館還有在館書, 立馬網路借書. 

Ollama 學習筆記 : Meta-prompt 通用模板優化

在前一篇測試中我們將需求前置轉譯整合到 meta-prompt 的通用模板中, 本篇旨在使用它來產生網格交易的高約束提示詞與 Python 程式碼, 檢驗模板的通用性與程式的正確性, 進而優化通用模板. 

本系列全部測試文章索引參考 :


將任務與功能 "動態網格交易" 填入通用模板的 "用戶原始需求" 欄裡 : 

我是 Python 演算法開發者。
【用戶原始需求】:【動態網格交易】
【架構師前置任務(自動補齊規格)】:
在生成 Prompt 前,請先依量化狀態機工程規範,主動將上述需求擴充為:
1. 具體函式簽名:包含 df: pd.DataFrame、外部觸發訊號欄位(如 entry_col: str = "entry_signal")及合理預設參數。
2. 精準功能描述:明確定義「外部觸發進場條件」、「持倉期間狀態更新」與「離場機制」,嚴禁遺漏外部觸發訊號。
【硬性範疇限制】:每次提問僅限單一策略。嚴禁合併多個不同策略,嚴禁設計通用分發引擎(禁止 mode 參數)。
請注意:現在先不要寫最終的 Python 實作代碼。
請扮演資深系統架構師,梳理該策略的邏輯,並輸出一份『專門餵給本機 14B 代碼模型(Qwen 2.5 Coder 14B)執行的極簡結構化 Prompt』。
為了防止本機小模型產生「陣列長度不匹配廣播崩潰」、「偽向量化偷換數學算式」、「時序穿透」、「分支漏賦值導致狀態中斷」或「注意力倒置漏給實作」,產出的 Prompt 必須嚴格遵守以下 5 項架構設計原則:
1. 運算範式與數值計算契約(Numerical & Alignment Guard):
   - 【純無副作用】:函式開頭必須強制執行 `df = df.copy()`,嚴禁修改傳入的原始物件;所有計算出的核心特徵與狀態欄位必須完整回填輸出。
   - 【時序差分對齊】:凡涉及前一期資訊(t-1 lag)的向量化運算,必須明確給出切片對齊語法(當期切片 [1:] 與前期切片 [:-1] 長度對齊,第 0 筆獨立初始化),嚴禁不同長度陣列直接相減引發廣播崩潰。
   - 【拒絕偽向量化】:明確區分有限滑動視窗與遞迴平滑(如 EMA、Wilder 等依賴前一期數值之濾波)。凡具備遞迴相依特徵,指示直接以純數值迴圈計算,嚴禁用 np.convolve 或滑動平均冒充。
   - 【0-based 視窗邊界對齊】:凡涉及長度為 W 之視窗統計或初始暖機計算,首個有效聚合值必須精確對齊至視窗末端索引 W - 1,後續時序推進嚴格自索引 W 起算,嚴禁因 0-based 索引混淆導致邊界偏移(Off-by-one)。
   - 【異常值傳播契約】:若時序中間出現缺失值(NaN),當期指標計算設為 NaN 即可;當後續數值恢復有效時,直接沿用最近一期有效狀態值延續遞迴平滑,嚴禁指示小模型設計複雜的「中斷後重新累積暖機」邏輯。
2. 迴圈拓撲與單一 Pass 限制(Single-Pass FSM Topology):
   - 明確區分「可全域預先向量化特徵」與「路徑依賴狀態機」。
   - 【單一迴圈紅線】:若存在狀態轉移依賴,所有相互關聯的事件、進出場判定與動態軌跡更新,必須嚴格封裝在唯一一個 `for t in range(n)` 迴圈內,嚴禁拆成多個獨立迴圈預算事件。
3. 依賴閉環與變數綁定(Dependency Closure & Memory Scaffold):
   - 【依賴閉環原則】:輸入 Schema 中定義的所有決策驅動欄位,在迴圈前必須轉為 1D NumPy 陣列;且必須在狀態機迴圈的 `if` 條件分支中被顯式引用,嚴禁輸入欄位被閒置或遺忘。
   - 所有輸出欄位,在進入迴圈前於記憶體預先宣告配置好 1D NumPy 陣列(禁止在迴圈內對 DataFrame 逐列賦值)。
4. 純量狀態機與統一提交架構(Scalar Register & Unified Commit):
- 【純量變數隔離】:迴圈外部初始化純量狀態暫存器(如 curr_pos=0, curr_peak=np.nan, curr_stop=np.nan)。迴圈內部各條件分支「僅允許修改純量變數」,嚴禁在 if-elif 分支內部直接對 position[t] 或 stop[t] 賦值。
- 【單向因果流動】:迴圈首行固定執行 prev_pos, prev_peak, prev_stop = curr_pos, curr_peak, curr_stop 凍結前值以防時序穿透。
- 【統一提交出口】:迴圈末端必須且只能以無條件寫入,將所有當前純量狀態提交至對應陣列(如 `state_arr[t] = curr_state`),禁止在分支內有條件落盤,杜絕因分支漏賦值導致狀態靜默遺失。
5. 極限邊界防禦與 Negative Constraints:
   - 空資料、長度不足時安全回傳帶有完整目標 Schema 的 DataFrame,嚴禁索引越界。
   - 數據中斷(NaN/None)時明確規定狀態凍結或沿用前值,嚴禁使用全域 fillna(0) 或重新累積暖機破壞時序。
   - 嚴格禁止使用 iterrows、itertuples、未定義欄位預先存取及拿回傳表比對原始物件。
   - 產出的 Prompt 篇幅控制在 600~800 字內,要求小模型依序輸出「完整函式」與「狀態邏輯 pytest 驗證(無須手算小數)」。

然後將上面的 Meta-prompt 貼給 ChatGPT, 生成如下的高約束 prompt : 

# 任務:實作單一動態網格交易函式

你是 Python 量化策略工程師。只實作 `dynamic_grid(df: pd.DataFrame, entry_col: str = "entry_signal", grid_pct: float = 0.02, lot_size: float = 1.0, max_positions: int = 5) -> pd.DataFrame`,禁止 mode 參數、多策略或通用分發引擎。

## 1. 策略規格
使用 `Close` 與外部 `entry_col`。只有空倉且訊號明確為 True 時,才以當期 Close 啟動網格;無訊號不得進場。以啟動價為基準,每格固定間距 `grid_pct`。持倉期間,Close 下穿下一買入格且持倉未達上限時,增加一個 `lot_size`;Close 上穿下一賣出格且持倉大於零時,減少一個 `lot_size`。每根 K 線最多執行一筆網格交易;買賣格依實際交易方向逐格更新,避免同一根 K 線連續多格成交。持倉降至零時結束週期並清除網格;不得同根重新啟動。此函式輸出交易訊號,不假設實際成交滑價或手續費。

## 2. 數值與對齊
函式首行執行 `df = df.copy()`,保留原欄位,新增 `Grid_Level, Position, Entry_Event, Buy_Event, Sell_Event, Exit_Event`。先驗證 `Close`、`entry_col` 與參數,再處理空資料。`grid_pct`、`lot_size` 必須為正有限數;`max_positions` 必須為正整數且不可為 bool。所有決策欄位轉為一維 NumPy 陣列,所有輸出陣列在迴圈前預配置。若需前期差分,使用 `[1:]` 與 `[:-1]` 對齊,第 0 筆獨立處理。禁止用向量化近似替代路徑依賴狀態。

## 3. FSM 與因果
所有進場、買賣格判斷、持倉數量、網格更新及事件均放在唯一一個 `for t in range(n)` 狀態機迴圈。迴圈首行凍結 `prev_pos, prev_grid = curr_pos, curr_grid`。分支只修改純量暫存器與事件旗標,禁止分支內直接寫輸出陣列;迴圈末端無條件提交全部輸出。當期決策只使用當期 Close、當期外部訊號與前期已確認狀態,禁止使用未來資料。

## 4. 缺失值與邊界
缺失 Close 或訊號不明確時不觸發交易,沿用前期狀態;禁止全域 `fillna(0)`。空資料、短資料安全回傳完整 Schema,不得索引越界。禁止 `iterrows`、`itertuples`、未驗證欄位存取及 DataFrame 逐列賦值。

## 5. 交付與測試
依序輸出完整函式及 pytest。測試涵蓋空資料、參數與欄位驗證、外部訊號控制、網格逐格更新、持倉上限、買賣事件互斥、清倉後不重進、缺失值狀態延續及原始 DataFrame 不變。以事件位置與狀態關係驗證,不必手算小數。不得宣稱未實際執行的測試已通過。

Gemini 對此提示詞給予 92 分高度評價, 表示這證明通用 Meta-Prompt 模板具備高度的可遷移性 (Transferability), 當策略從單向追蹤的 "ATR 移動停損" 切換為多階段加減碼的 "動態網格交易" 時, 架構師依然精準產出了符合五大原則的高約束規格書. 

從變數名稱 prev_pos, prev_grid = curr_pos, curr_grid 可知 ChatGPT 成功地生成網格交易語義的變數, 表示通用模板的可遷移性 (視任務不同產生適配之變數). 這份 Prompt 的結構已經非常嚴密, 但在網格業務邏輯的描述上稍微偏向口語, 可能會讓 14B 小模型在實作程式碼時會出現不同解讀, 摘要如下 : 
  • 下一買入格/下一賣出格的具體價格錨定基準 :
    14B 模型看到 "每格固定間距" 又看到 "買賣格依實際交易方向逐格更新", 可能會在狀態機中猶豫要記 "整數格數 Level (如 -1, -2)" 還是記 "上一筆成交價格 (float)".
  • 下穿/上穿的判斷方式 (純當期價格 vs 跨期穿透) :
    "Close 下穿下一買入格且持倉未達上限時,增加一個 `lot_size`;Close 上穿下一賣出格且持倉大於零時,減少一個 `lot_size`。" 這一段話存在潛在分歧, 14B 小模型可能會認為是單純比較當期收盤價; 也可能會認為是嚴格的穿越邏輯. 
整體來說, ChatGPT 的這份產出驗證了通用模板是成功的, 證明通用模板 Meta-Prompt 不是 ATR 的專用外殼, 而是真正具備跨策略能力的量化狀態機工程藍圖. 即使直接將這份 Prompt 丟給本機 qwen2.5-coder:14b, 它產出的代碼基本上不會有陣列越界或狀態遺失的語法級 Bug, 只需要留意它採用的網格價格更新公式是否符合預期的回測定義. 

我將 ChatGPT 生成的高約束提示詞直接丟給 Qwen 2.5 Coder 14B 生成如下程式碼 : 

import pandas as pd
import numpy as np

def dynamic_grid(df: pd.DataFrame, entry_col: str = "entry_signal", grid_pct: float = 0.02,
lot_size: float = 1.0, max_positions: int = 5) -> pd.DataFrame:
    df = df.copy()

    # Validate input
    required_cols = ['Close', entry_col]
    if not all(col in df.columns for col in required_cols):
        raise ValueError(f"Missing required columns: {', '.join(required_cols)}")

    if not pd.api.types.is_numeric_dtype(df['Close']):
        raise ValueError("Close must be a numeric type.")

    if not isinstance(grid_pct, (int, float)) or grid_pct <= 0:
        raise ValueError("grid_pct must be a positive float or integer.")

    if not isinstance(lot_size, (int, float)) or lot_size <= 0:
        raise ValueError("lot_size must be a positive float or integer.")

    if not isinstance(max_positions, int) or max_positions <= 0 or isinstance(max_positions,
bool):
        raise ValueError("max_positions must be a positive integer.")

    n = len(df)
    close = df['Close'].values
    entry_signal = df[entry_col].values.astype(bool)

    grid_level = np.full(n, np.nan)
    position = np.zeros(n, dtype=int)
    entry_event = np.zeros(n, dtype=bool)
    buy_event = np.zeros(n, dtype=bool)
    sell_event = np.zeros(n, dtype=bool)
    exit_event = np.zeros(n, dtype=bool)

    curr_pos = 0
    curr_grid = np.nan

    for t in range(n):
        prev_pos = curr_pos
        prev_grid = curr_grid

        valid_close = np.isfinite(close[t])
        valid_entry = entry_signal[t]

        if prev_pos == 0 and valid_entry and valid_close:
            curr_grid = close[t]
            curr_pos = 1
            entry_event[t] = True
        elif valid_close:
            if curr_pos < max_positions and close[t] < prev_grid * (1 - grid_pct):
                curr_pos += 1
                buy_event[t] = True
            elif curr_pos > 0 and close[t] > prev_grid * (1 + grid_pct):
                curr_pos -= 1
                sell_event[t] = True

        if curr_pos == 0 and prev_pos > 0:
            exit_event[t] = True

        grid_level[t] = curr_grid

    df['Grid_Level'] = grid_level
    df['Position'] = position
    df['Entry_Event'] = entry_event
    df['Buy_Event'] = buy_event
    df['Sell_Event'] = sell_event
    df['Exit_Event'] = exit_event

    return df

def test_dynamic_grid():
    data = {
        'Close': [100, 102, 103, 98, 95, 97, 100, 105, 110, 115, 118],
        'entry_signal': [True, False, False, False, False, False, False, False, False, False,
False]
    }
    df = pd.DataFrame(data)
    result = dynamic_grid(df)

    # Check columns
    required_cols = ['Close', 'entry_signal', 'Grid_Level', 'Position', 'Entry_Event',
'Buy_Event', 'Sell_Event', 'Exit_Event']
    assert all(col in result.columns for col in required_cols)

    # Check Grid_Level values
    expected_grid_level = [100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0,
100.0, 100.0]
    np.testing.assert_array_equal(result['Grid_Level'], expected_grid_level)

    # Check Position values
    expected_position = [1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]
    np.testing.assert_array_equal(result['Position'], expected_position)

    # Check Entry_Event values
    expected_entry_event = [True, False, False, False, False, False, False, False, False,
False, False]
    np.testing.assert_array_equal(result['Entry_Event'], expected_entry_event)

    # Check Buy_Event values
    expected_buy_event = [False, False, False, False, False, False, False, False, False, False,
False]
    np.testing.assert_array_equal(result['Buy_Event'], expected_buy_event)

    # Check Sell_Event values
    expected_sell_event = [False, False, False, False, False, False, False, False, False,
False, False]
    np.testing.assert_array_equal(result['Sell_Event'], expected_sell_event)

    # Check Exit_Event values
    expected_exit_event = [False, False, False, True, False, False, False, False, False, False,
False]
    np.testing.assert_array_equal(result['Exit_Event'], expected_exit_event)

    # Check that the original DataFrame is not modified
    assert 'Grid_Level' not in df.columns

def test_empty_data():
    df = pd.DataFrame(columns=['Close', 'entry_signal'])
    result = dynamic_grid(df)
    required_cols = ['Close', 'entry_signal', 'Grid_Level', 'Position', 'Entry_Event',
'Buy_Event', 'Sell_Event', 'Exit_Event']
    assert all(col in result.columns for col in required_cols)

def test_missing_data():
    data = {
        'Close': [100, np.nan, 103, 98, np.nan, 97, 100, np.nan, 110, 115, 118],
        'entry_signal': [True, False, False, False, False, False, False, False, False, False,
False]
    }
    df = pd.DataFrame(data)
    result = dynamic_grid(df)
    assert 'Grid_Level' in result.columns
    assert 'Position' in result.columns
    assert 'Entry_Event' in result.columns
    assert 'Buy_Event' in result.columns
    assert 'Sell_Event' in result.columns
    assert 'Exit_Event' in result.columns

提交給 Gemini 審查, 它認為 Meta-Prompt 通用模板的五大原則發揮了巨大威力, 完全遵守依序輸出協議; 測試的不可變性也防護成功; 時序與型別防禦也毫無死角. 不過, 此程式碼存在三個硬傷與邏輯缺陷, 摘要如下 : 
  1. Qwen 在迴圈末端 (第 61 行) 漏寫了 position[t] = curr_pos, 後果是輸出 DataFrame 中的 Position 欄位會永遠全為 0, 一執行 pytest 就會直接斷言失敗.
  2. 網格水位 (第 52~58 行) 未動態更新 (curr_grid 永遠凍結在初始進場價), 提示詞規格中寫 "買賣格依實際交易方向逐格更新", 但在觸發加減碼時 Qwen 完全沒有更新 curr_grid (例如加碼後應該更新為當前成交價或新格線價), 後果是後果:curr_grid 從頭到尾永遠等於第一天的進場價 close[0]. 
  3. 單位計算忽略了 lot_size, 函式參數傳入了 lot_size: float = 1.0, 在進場時它寫了 curr_pos = 1, 加碼時寫 curr_pos += 1, 這是錯誤的, 應該寫 curr_pos += lot_size 才對. 
從以上實測可得到以下兩點結論 :
  • Meta-Prompt 成功讓小模型擺脫幻覺與測試倒置 :
    Qwen 寫出的防呆, 型別, 複製與測試完全是一線水準, 架構師的約束極為成功.
  • 小模型對 "全部提交" 仍存在漏抄風險 :
    Prompt 雖然寫了 "迴圈末端無條件提交全部輸出", 但如果 ChatGPT 在生成 FSM 骨架時沒有把最後那幾行賦值代碼具體列出來 (例如此例的網格交易沒寫出 grid_level[t] = curr_grid; position[t] = curr_pos), 14B 模型就容易以為只要寫了一行 grid_level[t] 就可以交差了.
從此例可知, 我們的 Meta-Prompt 通用模板需要進一步優化,要求架構師將純量暫存器的統一提交出口寫成明確的賦值語句清單而非概括性字眼 (因為對 14B 小模型而言, "全部輸出" 是個高度抽象的集合名詞容易漏抄), 這樣才能封死小模型漏賦值問題. 

主要修正之處為通用模板第 4 條約束中的 "統一提交出口", 要禁止架構師使用概括描述, 強制要求它在規格書中把 "統一提交的所有賦值代碼行" 以具體語法逐行列出, 修正如下 : 

- 【統一提交出口(顯式代碼清單)】:產出的 Prompt 嚴禁使用「提交所有狀態」等概括性文字。架構師必須在骨架尾端,根據該策略宣告的所有狀態純量,具體逐行寫出無條件提交的賦值代碼(格式為 arr_A[t] = curr_A、arr_B[t] = curr_B),禁止在分支內有條件落盤,杜絕狀態漏賦值。

優化後的通用模板如下 :

我是 Python 演算法開發者。
【用戶原始需求】:【在此填入任何口語需求,例如:ATR 移動停損 / 海龜突破 / 雙均線交叉 / 動態網格交易】
【架構師前置任務(自動補齊規格)】:
在生成 Prompt 前,請先依量化狀態機工程規範,主動將上述需求擴充為:
1. 具體函式簽名:包含 df: pd.DataFrame、外部觸發訊號欄位(如 entry_col: str = "entry_signal")及合理預設參數。
2. 精準功能描述:明確定義「外部觸發進場條件」、「持倉期間狀態更新」與「離場機制」,嚴禁遺漏外部觸發訊號。
【硬性範疇限制】:每次提問僅限單一策略。嚴禁合併多個不同策略,嚴禁設計通用分發引擎(禁止 mode 參數)。
請注意:現在先不要寫最終的 Python 實作代碼。
請扮演資深系統架構師,梳理該策略的邏輯,並輸出一份『專門餵給本機 14B 代碼模型(Qwen 2.5 Coder 14B)執行的極簡結構化 Prompt』。
為了防止本機小模型產生「陣列長度不匹配廣播崩潰」、「偽向量化偷換數學算式」、「時序穿透」、「分支漏賦值導致狀態中斷」或「注意力倒置漏給實作」,產出的 Prompt 必須嚴格遵守以下 5 項架構設計原則:
1. 運算範式與數值計算契約(Numerical & Alignment Guard):
   - 【純無副作用】:函式開頭必須強制執行 `df = df.copy()`,嚴禁修改傳入的原始物件;所有計算出的核心特徵與狀態欄位必須完整回填輸出。
   - 【時序差分對齊】:凡涉及前一期資訊(t-1 lag)的向量化運算,必須明確給出切片對齊語法(當期切片 [1:] 與前期切片 [:-1] 長度對齊,第 0 筆獨立初始化),嚴禁不同長度陣列直接相減引發廣播崩潰。
   - 【拒絕偽向量化】:明確區分有限滑動視窗與遞迴平滑(如 EMA、Wilder 等依賴前一期數值之濾波)。凡具備遞迴相依特徵,指示直接以純數值迴圈計算,嚴禁用 np.convolve 或滑動平均冒充。
   - 【0-based 視窗邊界對齊】:凡涉及長度為 W 之視窗統計或初始暖機計算,首個有效聚合值必須精確對齊至視窗末端索引 W - 1,後續時序推進嚴格自索引 W 起算,嚴禁因 0-based 索引混淆導致邊界偏移(Off-by-one)。
   - 【異常值傳播契約】:若時序中間出現缺失值(NaN),當期指標計算設為 NaN 即可;當後續數值恢復有效時,直接沿用最近一期有效狀態值延續遞迴平滑,嚴禁指示小模型設計複雜的「中斷後重新累積暖機」邏輯。
2. 迴圈拓撲與單一 Pass 限制(Single-Pass FSM Topology):
   - 明確區分「可全域預先向量化特徵」與「路徑依賴狀態機」。
   - 【單一迴圈紅線】:若存在狀態轉移依賴,所有相互關聯的事件、進出場判定與動態軌跡更新,必須嚴格封裝在唯一一個 `for t in range(n)` 迴圈內,嚴禁拆成多個獨立迴圈預算事件。
3. 依賴閉環與變數綁定(Dependency Closure & Memory Scaffold):
   - 【依賴閉環原則】:輸入 Schema 中定義的所有決策驅動欄位,在迴圈前必須轉為 1D NumPy 陣列;且必須在狀態機迴圈的 `if` 條件分支中被顯式引用,嚴禁輸入欄位被閒置或遺忘。
   - 所有輸出欄位,在進入迴圈前於記憶體預先宣告配置好 1D NumPy 陣列(禁止在迴圈內對 DataFrame 逐列賦值)。
4. 純量狀態機與統一提交架構(Scalar Register & Unified Commit):
- 【純量變數隔離】:迴圈外部初始化純量狀態暫存器(如 curr_pos=0, curr_peak=np.nan, curr_stop=np.nan)。迴圈內部各條件分支「僅允許修改純量變數」,嚴禁在 if-elif 分支內部直接對 position[t] 或 stop[t] 賦值。
- 【單向因果流動】:迴圈首行固定執行 prev_pos, prev_peak, prev_stop = curr_pos, curr_peak, curr_stop 凍結前值以防時序穿透。
- 【統一提交出口(顯式代碼清單)】:產出的 Prompt 嚴禁使用「提交所有狀態」等概括性文字。架構師必須在骨架尾端,根據該策略宣告的所有狀態純量,具體逐行寫出無條件提交的賦值代碼(格式為 arr_A[t] = curr_A、arr_B[t] = curr_B),禁止在分支內有條件落盤,杜絕狀態漏賦值。
5. 極限邊界防禦與 Negative Constraints:
   - 空資料、長度不足時安全回傳帶有完整目標 Schema 的 DataFrame,嚴禁索引越界。
   - 數據中斷(NaN/None)時明確規定狀態凍結或沿用前值,嚴禁使用全域 fillna(0) 或重新累積暖機破壞時序。
   - 嚴格禁止使用 iterrows、itertuples、未定義欄位預先存取及拿回傳表比對原始物件。
   - 產出的 Prompt 篇幅控制在 600~800 字內,要求小模型依序輸出「完整函式」與「狀態邏輯 pytest 驗證(無須手算小數)」。

我用此模板叫 ChatGPT 生成動態網格交易的高約束提示詞如下 :

# 任務:動態網格交易函式

僅實作 `dynamic_grid(df: pd.DataFrame, entry_col: str = "entry_signal", grid_pct: float = 0.02, lot_size: float = 1.0, max_positions: int = 5) -> pd.DataFrame`。單一多頭固定百分比網格,禁止 mode、多策略及通用引擎。

## 1. 策略規格
必要欄位 `Close, entry_col`。僅空倉且外部訊號明確為 True 時,以當期 Close 啟動網格;無有效訊號不得進場。買入格與賣出格初始分別為啟動價下方與上方一格,間距為啟動價乘 `grid_pct`。持倉期間,Close 到達下一買入格且持倉未達 `max_positions` 時加一單位;Close 到達下一賣出格且持倉大於零時減一單位。每根 K 線最多成交一格,交易後按固定格距推進相應網格。持倉歸零即結束週期並清除網格;清倉當根不得重新進場。此為訊號模擬,不模擬滑價與手續費。

## 2. 數值契約
函式首行 `df = df.copy()`;保留原欄位,新增 `Grid_Level, Next_Buy, Next_Sell, Position, Entry_Event, Buy_Event, Sell_Event, Exit_Event`。驗證欄位及參數:`grid_pct`、`lot_size` 為正有限數;`max_positions` 為正整數且不可為 bool。所有決策欄位轉成一維 NumPy 陣列,所有輸出陣列預先配置。前期差分若使用向量化,必須以 `[1:]` 與 `[:-1]` 對齊,第 0 筆獨立處理;禁止偽向量化替代路徑依賴邏輯。空資料安全回傳完整 Schema。

## 3. 單一 FSM 與缺失值
唯一 `for t in range(n)` 迴圈處理進場、買賣、網格推進及事件。迴圈首行凍結 `prev_pos, prev_buy, prev_sell, prev_grid = curr_pos, curr_buy, curr_sell, curr_grid`。缺失 Close 或不明確訊號時凍結交易狀態,事件皆 False;禁止 `fillna(0)`。分支僅修改純量與事件旗標,不可直接寫輸出陣列。所有決策只使用當期資料及前期已確認狀態。禁止 `iterrows`、`itertuples`、未驗證欄位存取及 DataFrame 逐列賦值。

## 4. 狀態骨架與統一提交
迴圈外初始化 `curr_pos=0`、`curr_buy=np.nan`、`curr_sell=np.nan`、`curr_grid=np.nan`,事件旗標每根重設為 False。迴圈首行依上式凍結前值;分支只更新純量。迴圈尾端必須逐行無條件提交,禁止分支內落盤:

`position_arr[t] = curr_pos`
`next_buy_arr[t] = curr_buy`
`next_sell_arr[t] = curr_sell`
`grid_level_arr[t] = curr_grid`
`entry_event_arr[t] = curr_entry_event`
`buy_event_arr[t] = curr_buy_event`
`sell_event_arr[t] = curr_sell_event`
`exit_event_arr[t] = curr_exit_event`

## 5. 交付
依序輸出完整函式及 pytest。測試涵蓋原始資料不變、空資料、參數驗證、外部訊號控制、逐格買賣、持倉上限、事件互斥、清倉不重進、缺失值凍結與狀態延續。以事件與狀態關係驗證,不必手算小數;不得宣稱未實際執行的測試通過。

Gemini 對此高約束提示詞的評論是 : 工程水準已經收斂到無懈可擊的量產級境界, 所有在實測中遇到的隱性 Bug 與注意力遺忘問題在這份規格書中全數被精準封死. 通用模板中第 4 條加入的 "統一提交出口(顯式代碼清單)" 敘述發揮了決定性作用, 產出了清晰無歧義自帶代碼錨點的極致規格. 

將此高約束丟給 Qwen 生成程式碼 :

import pandas as pd
import numpy as np

def dynamic_grid(df: pd.DataFrame, entry_col: str = "entry_signal", grid_pct: float = 0.02,
lot_size: float = 1.0, max_positions: int = 5) -> pd.DataFrame:
    df = df.copy()

    # Validate input
    required_cols = ['Close', entry_col]
    if not all(col in df.columns for col in required_cols):
        raise ValueError(f"Missing required columns: {', '.join(required_cols)}")

    if not pd.api.types.is_numeric_dtype(df['Close']):
        raise ValueError("Close must be a numeric type.")

    if not isinstance(grid_pct, (int, float)) or grid_pct <= 0:
        raise ValueError("grid_pct must be a positive float or integer.")

    if not isinstance(lot_size, (int, float)) or lot_size <= 0:
        raise ValueError("lot_size must be a positive float or integer.")

    if not isinstance(max_positions, int) or max_positions <= 0 or isinstance(max_positions,
bool):
        raise ValueError("max_positions must be a positive integer.")

    n = len(df)
    close = df['Close'].values
    entry_signal = df[entry_col].values.astype(bool)

    grid_level_arr = np.full(n, np.nan)
    next_buy_arr = np.full(n, np.nan)
    next_sell_arr = np.full(n, np.nan)
    position_arr = np.zeros(n, dtype=int)
    entry_event_arr = np.zeros(n, dtype=bool)
    buy_event_arr = np.zeros(n, dtype=bool)
    sell_event_arr = np.zeros(n, dtype=bool)
    exit_event_arr = np.zeros(n, dtype=bool)

    curr_pos = 0
    curr_buy = np.nan
    curr_sell = np.nan
    curr_grid = np.nan

    for t in range(n):
        prev_pos = curr_pos
        prev_buy = curr_buy
        prev_sell = curr_sell
        prev_grid = curr_grid

        valid_close = np.isfinite(close[t])
        valid_entry = entry_signal[t]

        curr_entry_event = False
        curr_buy_event = False
        curr_sell_event = False
        curr_exit_event = False

        if prev_pos == 0 and valid_entry and valid_close:
            curr_grid = close[t]
            curr_buy = curr_grid * (1 - grid_pct)
            curr_sell = curr_grid * (1 + grid_pct)
            curr_pos = 1
            curr_entry_event = True
        elif valid_close:
            if curr_pos < max_positions and close[t] <= curr_buy:
                curr_pos += 1
                curr_buy = prev_buy * (1 - grid_pct)
                curr_sell = prev_sell * (1 - grid_pct)
                curr_buy_event = True
            elif curr_pos > 0 and close[t] >= curr_sell:
                curr_pos -= 1
                curr_buy = prev_buy * (1 + grid_pct)
                curr_sell = prev_sell * (1 + grid_pct)
                curr_sell_event = True

        if curr_pos == 0 and prev_pos > 0:
            curr_exit_event = True

        grid_level_arr[t] = curr_grid
        next_buy_arr[t] = curr_buy
        next_sell_arr[t] = curr_sell
        position_arr[t] = curr_pos
        entry_event_arr[t] = curr_entry_event
        buy_event_arr[t] = curr_buy_event
        sell_event_arr[t] = curr_sell_event
        exit_event_arr[t] = curr_exit_event

    df['Grid_Level'] = grid_level_arr
    df['Next_Buy'] = next_buy_arr
    df['Next_Sell'] = next_sell_arr
    df['Position'] = position_arr
    df['Entry_Event'] = entry_event_arr
    df['Buy_Event'] = buy_event_arr
    df['Sell_Event'] = sell_event_arr
    df['Exit_Event'] = exit_event_arr

    return df

def test_dynamic_grid():
    data = {
        'Close': [100, 102, 103, 98, 95, 97, 100, 101, 102, 103, 104],
        'entry_signal': [True, False, False, False, False, False, False, False, False, False,
False]
    }
    df = pd.DataFrame(data)
    result = dynamic_grid(df)
    assert 'Grid_Level' in result.columns
    assert 'Next_Buy' in result.columns
    assert 'Next_Sell' in result.columns
    assert 'Position' in result.columns
    assert 'Entry_Event' in result.columns
    assert 'Buy_Event' in result.columns
    assert 'Sell_Event' in result.columns
    assert 'Exit_Event' in result.columns

def test_empty_data():
    df = pd.DataFrame(columns=['Close', 'entry_signal'])
    result = dynamic_grid(df)
    assert 'Grid_Level' in result.columns
    assert 'Next_Buy' in result.columns
    assert 'Next_Sell' in result.columns
    assert 'Position' in result.columns
    assert 'Entry_Event' in result.columns
    assert 'Buy_Event' in result.columns
    assert 'Sell_Event' in result.columns
    assert 'Exit_Event' in result.columns

def test_parameter_validation():
    data = {
        'Close': [100, 102, 103, 98, 95, 97, 100, 101, 102, 103, 104],
        'entry_signal': [True, False, False, False, False, False, False, False, False, False,
False]
    }
    df = pd.DataFrame(data)
    try:
        dynamic_grid(df, grid_pct=-0.02)
    except ValueError as e:
        assert str(e) == "grid_pct must be a positive float or integer."

    try:
        dynamic_grid(df, lot_size=-1.0)
    except ValueError as e:
        assert str(e) == "lot_size must be a positive float or integer."

    try:
        dynamic_grid(df, max_positions=-5)
    except ValueError as e:
        assert str(e) == "max_positions must be a positive integer."

def test_missing_data():
    data = {
        'Close': [100, np.nan, 103, 98, np.nan, 97, 100, 101, 102, 103, 104],
        'entry_signal': [True, False, False, False, False, False, False, False, False, False,
False]
    }
    df = pd.DataFrame(data)
    result = dynamic_grid(df)
    assert 'Grid_Level' in result.columns
    assert 'Next_Buy' in result.columns
    assert 'Next_Sell' in result.columns
    assert 'Position' in result.columns
    assert 'Entry_Event' in result.columns
    assert 'Buy_Event' in result.columns
    assert 'Sell_Event' in result.columns
    assert 'Exit_Event' in result.columns

def test_no_entry_no_position():
    data = {
        'Close': [100, 102, 103, 98, 95, 97, 100, 101, 102, 103, 104],
        'entry_signal': [False, False, False, False, False, False, False, False, False, False,
False]
    }
    df = pd.DataFrame(data)
    result = dynamic_grid(df)
    assert all(result['Position'] == 0)
    assert all(result['Grid_Level'].isna())
    assert all(result['Next_Buy'].isna())
    assert all(result['Next_Sell'].isna())
    assert all(result['Entry_Event'] == False)
    assert all(result['Buy_Event'] == False)
    assert all(result['Sell_Event'] == False)
    assert all(result['Exit_Event'] == False)

def test_entry_and_trading():
    data = {
        'Close': [100, 102, 103, 98, 95, 97, 100, 101, 102, 103, 104],
        'entry_signal': [True, False, False, False, False, False, False, False, False, False,
False]
    }
    df = pd.DataFrame(data)
    result = dynamic_grid(df)
    assert result['Entry_Event'][0] == True
    assert result['Position'][0] == 1
    assert result['Grid_Level'][0] == 100
    assert result['Next_Buy'][0] == 98
    assert result['Next_Sell'][0] == 102
    assert all(result['Buy_Event'][1:5] == False)
    assert result['Buy_Event'][5] == True
    assert result['Position'][5] == 2
    assert result['Next_Buy'][5] == 96
    assert result['Next_Sell'][5] == 100
    assert all(result['Sell_Event'][6:] == False)
    assert result['Sell_Event'][9] == True
    assert result['Position'][9] == 1
    assert result['Next_Buy'][9] == 98
    assert result['Next_Sell'][9] == 102
    assert result['Exit_Event'][10] == True
    assert result['Position'][10] == 0

def test_max_positions():
    data = {
        'Close': [100, 102, 103, 98, 95, 97, 100, 101, 102, 103, 104],
        'entry_signal': [True, False, False, False, False, False, False, False, False, False,
False]
    }
    df = pd.DataFrame(data)
    result = dynamic_grid(df, max_positions=2)
    assert result['Entry_Event'][0] == True
    assert result['Position'][0] == 1
    assert result['Grid_Level'][0] == 100
    assert result['Next_Buy'][0] == 98
    assert result['Next_Sell'][0] == 102
    assert all(result['Buy_Event'][1:5] == False)
    assert result['Buy_Event'][5] == False
    assert result['Position'][5] == 1
    assert result['Next_Buy'][5] == 98
    assert result['Next_Sell'][5] == 102
    assert all(result['Sell_Event'][6:] == False)
    assert result['Exit_Event'][10] == True
    assert result['Position'][10] == 0

def test_no_reentry():
    data = {
        'Close': [100, 102, 103, 98, 95, 97, 100, 101, 102, 103, 104],
        'entry_signal': [True, False, False, False, False, True, False, False, False, False,
False]
    }
    df = pd.DataFrame(data)
    result = dynamic_grid(df)
    assert result['Entry_Event'][0] == True
    assert result['Position'][0] == 1
    assert result['Grid_Level'][0] == 100
    assert result['Next_Buy'][0] == 98
    assert result['Next_Sell'][0] == 102
    assert all(result['Buy_Event'][1:5] == False)
    assert result['Buy_Event'][5] == False
    assert result['Position'][5] == 1
    assert result['Next_Buy'][5] == 98
    assert result['Next_Sell'][5] == 102
    assert all(result['Sell_Event'][6:] == False)
    assert result['Exit_Event'][10] == True
    assert result['Position'][10] == 0

Gemini 對此新程式碼的審查結果認為, 這次 Qwen 生成的代碼證實 "顯式代碼清單" 帶來的威力非常明顯, 上次把 position 丟掉的致命 Bug 被直接徹底修正, 8 個輸出陣列全部在迴圈末端精確落盤, 並且附帶了完整的輸入驗證與大量 pytest 單元測試. 成果可以歸納為如下三點 : 
  1. 結構零漏洞 : 沒有任何陣列長度不符, 沒有未來函數, 沒有漏回填欄位. 
  2. 小模型順利執行 : Qwen 14B 完美依樣畫葫蘆完成了複雜的 8 個陣列統一提交. 
  3. 模板已經定型 : 整套 Meta-Prompt 模板不需要再做任何修改, 它已經是一套非常可靠, 能讓本機 14B 模型產出工業級狀態機程式碼的 "指令生成模具".
不過, 此程式碼在業務邏輯方面存在一些缺陷, 例如清倉時未重置網格水位 (curr_pos == 0 時未清除狀態) (第 65~73 行), Prompt 規格明確要求 "持倉歸零即結束週期並清除網格; 清倉當根不得重新進場", 但 Qwen 顯然沒有遵行; 其次是加減碼的網格推進公式有複利累積偏差: 最後是測試中存在手算假資料, 這是 Qwen 的老毛病, 雖然規格有寫 "不必手算小數", 但它在寫測試時還是手癢寫了一堆 hardcode 數值, 跑 pytest 時浮點數的四捨五入就會噴錯. 

這三個瑕疵與通用模板及高約束提示詞無關, 而是撞上了 14B 參數量級小模型的注意力容量與多步邏輯演繹物理極限. 

2026年10月19日 星期一

Ollama 學習筆記 : 需求前置轉譯 (Spec Intake)

在前一篇測試中, 我們利用通用模板 (meta-prompt) 讓 ChatGPT/Gemini/Claude 這種具備足夠推理能力與領域知識的雲端模型幫我們生成高約束 prompt, 再餵給本地小模型 (Qwen 2.5 Coder 14B) 來生成 Python 程式碼, 這樣就可以零 API 成本產出接近 100% 正確的 Python 代碼. 

但是小模型畢竟還是有它的物理邊界, 如果提示詞的任務/功能描述不夠具體, 或者沒有清楚說明輸入是甚麼, 14B 的小模型仍然可能生成有 bug 的程式. 但這對程式小白或領域知識不足的開發者而言, 要寫出妥適的函式名稱與功能描述是個高門檻, 本篇旨在展示如何以需求前置編譯的方法, 利用 ChatGPT/Gemini/Claude 來幫我們自動生成工程級的函式名稱與功能描述. 

本系列全部測試文章索引參考 :


小白開發者通常只具備業務直覺 (例如 "我想做 ATR 停損的程式"), 但缺乏工程與訊號因果的精確定義. 小白想破頭也無法在提示詞的功能描述中填寫嚴格且詳盡的規格, 這就非常容易因為遺漏關鍵的觸發條件而使小模型生成有 bug 的程式碼. 

理想的做法是讓 AI (ChatGPT/Gemini/Claude) 在進入通用 Meta-Prompt 之前, 先擔任 "需求轉譯編譯器 (Spec Intake Assistant)", 開發者只需輸入大白話的功能需求, AI 就會自動補齊外部觸發訊號, 依賴關係, 參數與標準函式名稱, 產出可以直接貼進 Meta-Prompt 的標準任務宣告. 這個 "需求前置轉譯" 的提示詞模板如下 :

你是 Python 量化系統分析師。小白用戶只會提供非常口語的交易想法。
你的任務是將口語需求轉換為符合量化狀態機規範的「標準任務規格宣告」,特別注意補齊「外部觸發訊號依賴」,杜絕模型天天自動進場。
請依以下格式輸出,不要有多餘廢話:
名稱:[標準 snake_case 函式簽名,包含 DataFrame、明確的進場觸發欄位 entry_col 與合理預設參數]
功能:[一句話精準定義:由誰觸發進場、如何維護路徑依賴狀態、依何種規則離場]
---
用戶口語需求:【此處貼上小白的一句話,例如:ATR 移動停損】

小白開發者只要在最後一行的 "用戶口語需求" 後面填入想做的功能, AI 就會轉譯出具備技術規格要求的函式名稱與功能描述, 例如我用 "ATR 移動停損" 做為需求叫 ChatGPT 轉譯, 生成如下的函式名稱與功能描述 :

名稱:atr_trailing_stop(df: pd.DataFrame, entry_col: str = "Entry_Signal", period: int = 14, multiplier: float = 3.0) -> pd.DataFrame
功能:僅由 entry_col 指定的外部訊號觸發進場,以 Wilder ATR 維護路徑依賴的累積最高價與不回撤停損線,當收盤價跌破前一期已確認的停損線時離場;無有效進場訊號時保持空倉。

將其填入通用模板開頭的名稱與功能佔位符 :

我是 Python 演算法開發者。我需要實作一個函式:
名稱:atr_trailing_stop(df: pd.DataFrame, entry_col: str = "Entry_Signal", period: int = 14, multiplier: float = 3.0) -> pd.DataFrame。
功能:僅由 entry_col 指定的外部訊號觸發進場,以 Wilder ATR 維護路徑依賴的累積最高價與不回撤停損線,當收盤價跌破前一期已確認的停損線時離場;無有效進場訊號時保持空倉。
【嚴格限制】:僅限實作此單一函式,嚴禁設計多模式切換引擎。
請注意:現在先不要寫最終的 Python 實作代碼。
請扮演資深系統架構師,梳理邏輯,並輸出一份『專門餵給本機 14B 代碼模型(Qwen 2.5 Coder 14B)執行的極簡結構化 Prompt』。
為了防止本機小模型產生「陣列長度不匹配廣播崩潰」、「偽向量化偷換數學算式」、「時序穿透」或「遺漏輸入條件」,產出的 Prompt 必須嚴格遵守以下 5 項架構設計原則:
1. 運算範式與數值計算契約(Numerical & Alignment Guard):
   - 【純無副作用】:函式開頭必須強制執行 `df = df.copy()`,嚴禁修改傳入的原始物件;所有計算出的核心特徵與狀態欄位必須完整回填輸出。
   - 【時序差分對齊】:凡涉及前一期資訊(t-1 lag)的向量化運算,必須明確給出切片對齊語法(當期切片 [1:] 與前期切片 [:-1] 長度對齊,第 0 筆獨立初始化),嚴禁不同長度陣列直接相減引發廣播崩潰。
   - 【拒絕偽向量化】:明確區分有限滑動視窗與遞迴平滑(如 EMA、Wilder 等依賴前一期數值之濾波)。凡具備遞迴相依特徵,指示直接以純數值迴圈計算,嚴禁用 np.convolve 或滑動平均冒充。
  - 【0-based 視窗邊界對齊】:凡涉及長度為 $W$ 之視窗統計或初始暖機計算,首個有效聚合值必須精確對齊至視窗末端索引 $W - 1$,後續時序推進嚴格自索引 $W$ 起算,嚴禁因 0-based 索引混淆導致邊界偏移(Off-by-one)。
2. 迴圈拓撲與單一 Pass 限制(Single-Pass FSM Topology):
   - 明確區分「可全域預先向量化特徵」與「路徑依賴狀態機」。
   - 【單一迴圈紅線】:若存在狀態轉移依賴,所有相互關聯的事件、進出場判定與動態軌跡更新,必須嚴格封裝在唯一一個 `for t in range(n)` 迴圈內,嚴禁拆成多個獨立迴圈預算事件。
3. 依賴閉環與變數綁定(Dependency Closure & Memory Scaffold):
   - 【依賴閉環原則】:輸入 Schema 中定義的所有決策驅動欄位,在迴圈前必須轉為 1D NumPy 陣列;且必須在狀態機迴圈的 `if` 條件分支中被顯式引用,嚴禁輸入欄位被閒置或遺忘。
   - 所有輸出欄位,在進入迴圈前於記憶體預先宣告配置好 1D NumPy 陣列(禁止在迴圈內對 DataFrame 逐列賦值)。
4. 純量狀態機與統一提交架構(Scalar Register & Unified Commit):
- 【純量變數隔離】:迴圈外部初始化純量狀態暫存器(如 curr_pos=0, curr_peak=np.nan, curr_stop=np.nan)。迴圈內部各條件分支「僅允許修改純量變數」,嚴禁在 if-elif 分支內部直接對 position[t] 或 stop[t] 賦值。
- 【單向因果流動】:迴圈首行固定執行 prev_pos, prev_peak, prev_stop = curr_pos, curr_peak, curr_stop 凍結前值以防時序穿透。
- 【統一提交出口】:迴圈末端必須且只能以無條件寫入,將所有當前純量狀態提交至對應陣列(如 `state_arr[t] = curr_state`),禁止在分支內有條件落盤,杜絕因分支漏賦值導致狀態靜默遺失。
5. 極限邊界防禦與 Negative Constraints:
   - 空資料、長度不足時安全回傳帶有完整目標 Schema 的空 DataFrame。
   - 數據中斷(NaN/None)時明確規定狀態凍結或沿用規則,不可使用全域 fillna(0) 破壞缺失值語意。
   - 嚴格禁止使用 iterrows、itertuples、未定義欄位預先存取。
   - 產出的 Prompt 篇幅控制在 600~800 字內,要求小模型輸出完整代碼與 pytest 驗證案例。

我將此 meta-prompt 貼給 ChatGPT 後, 生成如下的高約束提示詞 : 

# 任務:實作單一 ATR Trailing Stop

你是資深 Python 量化工程師。實作唯一函式 `atr_trailing_stop(df: pd.DataFrame, entry_col: str = "Entry_Signal", period: int = 14, multiplier: float = 3.0) -> pd.DataFrame`。禁止多模式引擎。

## 1. 數值與對齊契約
- 函式首行執行 `df = df.copy()`;保留原欄位,新增並完整回填 `TR, ATR, Peak, Stop, Position, Entry_Event, Exit_Event`。
- 必要欄位為 `High, Low, Close, entry_col`。先驗證欄位,再處理空資料;數值欄位須為數值型別。`period` 必須是正整數且不可為 bool;`multiplier` 必須為正有限數。
- TR:首筆 `High-Low`;其餘為 `max(High-Low, abs(High-prev_close), abs(Low-prev_close))`。差分使用 `[1:]` 與 `[:-1]` 對齊,第 0 筆獨立處理;必要輸入缺失時 TR=NaN。
- ATR 嚴格使用 Wilder 遞迴:首個連續 `period` 筆有效 TR 的算術平均對齊索引 `period-1`;後續公式為 `(prev_ATR*(period-1)+TR[t])/period`。禁止以 `np.convolve`、rolling mean 或滑動平均替代。
- TR 缺失時當期 ATR=NaN;後續 TR 恢復有效時,若已有有效 ATR,沿用最近有效 ATR 作遞迴基值;若尚無有效 ATR,繼續累積暖機資料,直到取得完整有效視窗。不得全域 `fillna(0)`。

## 2. 迴圈拓撲
TR 可向量化;ATR 遞迴可使用獨立數值迴圈。所有進出場、事件、Peak、Stop 與 Position 更新必須集中於唯一一個 `for t in range(n)` 狀態機迴圈,禁止拆分事件迴圈。迴圈前將所有決策欄位轉為一維 NumPy 陣列,並預配置全部輸出陣列。

## 3. 狀態機與因果
- 初始空倉 `Position=0`;僅明確為 True 的外部訊號可進場,不得自動進場。進場需有效 Close、High、ATR;初始 `Peak=High`、`Stop=Peak-multiplier*ATR`。
- 持倉期間 Peak 為累積最高 High;Stop 單調不下降:`max(prev_stop, Peak-multiplier*ATR)`。
- 先用前一期已確認停損線判斷離場:`Close[t] <= prev_stop`。離場當根禁止重新進場。
- 缺少必要決策資料時凍結持倉、Peak、Stop,事件皆為 False;ATR 缺失時不得用無效數值更新停損。
- 迴圈首行凍結 `prev_pos, prev_peak, prev_stop = curr_pos, curr_peak, curr_stop`。分支僅修改純量狀態及事件旗標,不得直接寫輸出陣列;迴圈末端無條件提交所有狀態與事件陣列。

## 4. 邊界與輸出
空資料、短資料均不得越界,回傳含完整輸出欄位的 DataFrame。禁止 `iterrows`、`itertuples`、未驗證欄位存取、DataFrame 逐列賦值及修改輸入物件。

## 5. 測試與交付
依序輸出完整函式,再輸出 pytest 測試。測試涵蓋原始資料不變、空資料、短資料、欄位與參數驗證、外部訊號控制、進出場時序、停損單調性、缺失值凍結與恢復。以狀態關係及事件位置驗證,無須手算小數。預期結果必須符合規格,不可為配合實作而修改;未實際執行測試時不得宣稱通過。

Gemini 對這份提示詞給予高度評價, 之前在多輪迭代中揪出的 14B 小模型死穴, 例如天天自動進場, 陣列切片長度不匹配, 偽向量化偷換算法, 多迴圈拆分, 以及多模式分發器等致命缺失在這一版中全數被鎖死, 展現了極高的工程完成度. 這份規格書毫無贅字, 條理清晰, 邊界防禦紮實, 篇幅恰好落在 600 字左右, 且各項限制已經精準收斂到生產級 (Production-ready) 的高水準, 是品質很高的高約束 Prompt. 

其實上面的 "需求前置轉譯 (Spec Intake)" 程序可以合併到通用模板 mata-prompt 裡面, 這樣就能一氣呵成, 不需要兩道程序. 這個二合一的通用模板如下 : 

我是 Python 演算法開發者。
【用戶原始需求】:【在此填入任何口語需求,例如:ATR 移動停損 / 海龜突破 / 雙均線交叉 / 動態網格交易】
【架構師前置任務(自動補齊規格)】:
在生成 Prompt 前,請先依量化狀態機工程規範,主動將上述需求擴充為:
1. 具體函式簽名:包含 df: pd.DataFrame、外部觸發訊號欄位(如 entry_col: str = "entry_signal")及合理預設參數。
2. 精準功能描述:明確定義「外部觸發進場條件」、「持倉期間狀態更新」與「離場機制」,嚴禁遺漏外部觸發訊號。
【硬性範疇限制】:每次提問僅限單一策略。嚴禁合併多個不同策略,嚴禁設計通用分發引擎(禁止 mode 參數)。
請注意:現在先不要寫最終的 Python 實作代碼。
請扮演資深系統架構師,梳理該策略的邏輯,並輸出一份『專門餵給本機 14B 代碼模型(Qwen 2.5 Coder 14B)執行的極簡結構化 Prompt』。
為了防止本機小模型產生「陣列長度不匹配廣播崩潰」、「偽向量化偷換數學算式」、「時序穿透」、「分支漏賦值導致狀態中斷」或「注意力倒置漏給實作」,產出的 Prompt 必須嚴格遵守以下 5 項架構設計原則:
1. 運算範式與數值計算契約(Numerical & Alignment Guard):
   - 【純無副作用】:函式開頭必須強制執行 `df = df.copy()`,嚴禁修改傳入的原始物件;所有計算出的核心特徵與狀態欄位必須完整回填輸出。
   - 【時序差分對齊】:凡涉及前一期資訊(t-1 lag)的向量化運算,必須明確給出切片對齊語法(當期切片 [1:] 與前期切片 [:-1] 長度對齊,第 0 筆獨立初始化),嚴禁不同長度陣列直接相減引發廣播崩潰。
   - 【拒絕偽向量化】:明確區分有限滑動視窗與遞迴平滑(如 EMA、Wilder 等依賴前一期數值之濾波)。凡具備遞迴相依特徵,指示直接以純數值迴圈計算,嚴禁用 np.convolve 或滑動平均冒充。
   - 【0-based 視窗邊界對齊】:凡涉及長度為 W 之視窗統計或初始暖機計算,首個有效聚合值必須精確對齊至視窗末端索引 W - 1,後續時序推進嚴格自索引 W 起算,嚴禁因 0-based 索引混淆導致邊界偏移(Off-by-one)。
   - 【異常值傳播契約】:若時序中間出現缺失值(NaN),當期指標計算設為 NaN 即可;當後續數值恢復有效時,直接沿用最近一期有效狀態值延續遞迴平滑,嚴禁指示小模型設計複雜的「中斷後重新累積暖機」邏輯。
2. 迴圈拓撲與單一 Pass 限制(Single-Pass FSM Topology):
   - 明確區分「可全域預先向量化特徵」與「路徑依賴狀態機」。
   - 【單一迴圈紅線】:若存在狀態轉移依賴,所有相互關聯的事件、進出場判定與動態軌跡更新,必須嚴格封裝在唯一一個 `for t in range(n)` 迴圈內,嚴禁拆成多個獨立迴圈預算事件。
3. 依賴閉環與變數綁定(Dependency Closure & Memory Scaffold):
   - 【依賴閉環原則】:輸入 Schema 中定義的所有決策驅動欄位,在迴圈前必須轉為 1D NumPy 陣列;且必須在狀態機迴圈的 `if` 條件分支中被顯式引用,嚴禁輸入欄位被閒置或遺忘。
   - 所有輸出欄位,在進入迴圈前於記憶體預先宣告配置好 1D NumPy 陣列(禁止在迴圈內對 DataFrame 逐列賦值)。
4. 純量狀態機與統一提交架構(Scalar Register & Unified Commit):
- 【純量變數隔離】:迴圈外部初始化純量狀態暫存器(如 curr_pos=0, curr_peak=np.nan, curr_stop=np.nan)。迴圈內部各條件分支「僅允許修改純量變數」,嚴禁在 if-elif 分支內部直接對 position[t] 或 stop[t] 賦值。
- 【單向因果流動】:迴圈首行固定執行 prev_pos, prev_peak, prev_stop = curr_pos, curr_peak, curr_stop 凍結前值以防時序穿透。
- 【統一提交出口】:迴圈末端必須且只能以無條件寫入,將所有當前純量狀態提交至對應陣列(如 `state_arr[t] = curr_state`),禁止在分支內有條件落盤,杜絕因分支漏賦值導致狀態靜默遺失。
5. 極限邊界防禦與 Negative Constraints:
   - 空資料、長度不足時安全回傳帶有完整目標 Schema 的 DataFrame,嚴禁索引越界。
   - 數據中斷(NaN/None)時明確規定狀態凍結或沿用前值,嚴禁使用全域 fillna(0) 或重新累積暖機破壞時序。
   - 嚴格禁止使用 iterrows、itertuples、未定義欄位預先存取及拿回傳表比對原始物件。
   - 產出的 Prompt 篇幅控制在 600~800 字內,要求小模型依序輸出「完整函式」與「狀態邏輯 pytest 驗證(無須手算小數)」。

利用這份整合了需求前置轉譯功能的通用模板, 即可一鍵生成高約束提示詞. 

蝦皮購買 Type-C 公轉 USB3.0A 母線 x 3

上週四開 RAV4 去高都做免費滿月保養時, 順便問技師 RAV4 可讀取 USB 隨身碟的 Type C 插槽位置, 原來是在中央置物箱裡面, 雖然上週買了 Type-C 公轉 USB3.0A 轉接頭ㄝ但我覺得插上隨身碟後有點長, 拿東西時容易被碰到, 所以今天再次上蝦皮買公對母轉接線, 我買最短的 (線長 15 公分) 的三條 :





明華智取免運 $259 元.  

2026年10月18日 星期日

2026 年第 40 周記事

哇, 2026 已飛走 40 周, 離新年剩下不到一季了! 今年以來我自認都努力向學 (都花甲之年了呵呵), 收穫很多, 在 AI 學習上前進不少, 倒是物聯網部分停滯不前, 因為時間實在不夠用 (那前陣子還沉溺在臉書 AI 漫劇?), 希望年底前可以把 OpenClaw 與 Ollama 學完.

向蘋果商店買的 Mac Mini 10/12 已如期到貨, 但還沒時間把機子裝起來測試, 下周五老同事阿中要前往日本獨旅, 這幾天要把他的行程再順一遍; 加上每周四晚上有 Karen 老師的 Vibe Coding 課要上 (到月底), 估計要 11 月才有空把機器架起來.  

周三對面張家伙房松盛伯母出山, 我改在週二下班回鄉下, 週三公祭完約 08:20 再出發去高雄上班, 剛好可在 09:30 前進辦公室毋須請假 (但要 18:30 下班). 周四早上把 RAV4 開回高都做滿月保養檢驗, 加裝了底盤保護金屬板 (扣掉將到期點數 500 元刷 2800), 完成後剛好 09:00, 但會計說點數只要留 3000 點, 其餘可先兌換以免過期, 所以耽擱了一下請半小時 10:00 才進辦公室, 連續兩天都最後關燈. 

本周觀察到部落格每日到訪數異常攀高, 從每天數千~兩萬來到每天 65 萬之譜, 這是以前約半年的總到訪數. 進一步查看 Blogger 統計來源, 發現大都來自巴西與阿根廷, 原先猜測可能是為了建置 AI 訓練資料由爬蟲發動搜尋所致, 但查看來源裝置居然是來自 iPhone, 這我就不太理解了 :






因廚房水龍頭越漏越嚴重, 上週問小舅上回來修菜園抽水馬達的水電工是否有空, 打電話才知出國要到這周日才有空. 為此我改成周六去市場買菜, 今日早上八點就來了, 先修廚房水龍頭 : 




果然有好工具三兩下就搞定. 接著到頂樓把裂開在滴水的自來水進水管截斷更換 : 




最後幫我把車庫的 LG 洗衣機移到室內的浴室走道 : 




一個早上就把一年來的三個水電難題都搞定了, 真順.