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 洗衣機移到室內的浴室走道 : 




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

Ollama 學習筆記 : 打造 Bug-free 量化狀態機的 Meta-Prompt 防禦架構

這個周末花了大半時間對 Qwen 2.5 Coder, Gemma 4, Phi 4, 與 Deepseek Coder V2 這四個 14B 以下輕量模型進行進階推理與程式能力考驗, 本篇旨在總結測試結果, 並對如何馴服 14B 本地小模型, 用高約束提示詞來防止小模型生成有 Bug 的程式碼, 提出一個結合本地與雲端模型打造低成本的軟體專案開發方案. 

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


測試結果摘要如下表 : 


 模型名稱  架構/參數量  邏輯推理能力  程式能力
(弱約束 Prompt)
 程式能力
(強約束 Prompt)
 Qwen 2.5 Coder 14B  稠密 14B  ❌ 陷入陷阱,硬湊 5 步三分法,最後產生幻覺  ❌ 語法齊全但有 Runtime Bug(idxmin 空切片崩潰、索引混淆)  🌟 完美(100 分)
 精準落實向量化與防禦邊界,代碼零 Bug
 Gemma 4  具備推論思維鏈
(Thinking)8B
 🌟 唯一識破悖論
 以資訊論 3^3=27 < 200 精準證明數學不可能
 ⚠️ 思考死循環,Token 吃滿後無輸出中斷  ✔️ 有效
 但需強制壓制思考長度並調大 num_predict
 Phi-4 14B  稠密 14B
(微軟合成資料)
 ❌ 算術混亂(11 × 4 = 33),主觀假設輕重,次數超標  ❌ 邏輯互斥死代碼(在 > 0.2 的子集內找 <= 0.2,輸出永為空)  ✔️ 可用
 能被修正,但在極端邊界上仍偶有小瑕疵
 DeepSeek-Coder-V2-Lite  混合專家 (MoE)
 16B (活化約 2.4B)
 ❌ 算術全面失真(憑空多出球),分支失控爆 Token 斷頭  ❌ 偽造輸出結果,文案說明與實際執行邏輯  ❌不推薦用於複雜邏輯活化參數量太小,注意力衰減嚴重,多輪約束下容易顧此失彼。


從測試結果來看, Qwen 2.5 Coder 14B 在使用本地模型進行軟體開發表現上拔得頭籌, 其次是 Phi 4, 但條件是開發者必須嫻熟 Python 技術棧與領域知識才有能力寫出具有強約束性的提示詞, 這表示想用本地 14B 模型進行 Vibe coding 幾乎是不可能. 

解決辦法是先以聊天方式與具備足夠推理能力與領域常識的雲端模型 (Claude, ChatGPT, 與 Gemini 等) 討論, 以其為架構師讓它產出可被本機模型精確執行的規格說明書 (即高約術性 Spec Prompt), 然後再餵給本地 Qwen 2.5 Coder 14B 模型, 以零 API 成本產出 100% 正確的 Python 代碼. 這是最佳的降本增效策略, 因為如果把專案全丟給雲端 API (例如 Claude Code), 當專案一大,  上下文變長, 每次修改檔案都要反覆讀取整個專案, API 費用會以驚人的速度飆升! 

然而採用先雲端聊架構, 再用本機吃規格的降本策略則能極度省 Token 成本, 我們不需要把整個專案的大量檔案丟給雲端, 只需要開一個網頁視窗, 使用免費額度的 ChatGPT/Gemini/Claude, 用 1 到 2 輪對話 (消耗不到 1000 個 tokens), 用純文字把需求講清楚, 它就會幫我們把狀態機數學公式與高約束規格推導出來, 把產出的高約束 Prompt 直接複製到本地命令列餵給 Ollama 的 Qwen 2.5 Coder 14B, 本機模型就能像吃了定心丸一樣, 在 RTX 3060 上以每秒 40 tokens 的極速, 零 API 成本產出 100% 正確的 Python 代碼. 

以下是萬用架構師的提示詞模板 (元提示詞, meta-prompt) :

我是 Python 演算法開發者。我需要實作一個函式:
名稱:[填入單一函式名稱例如:atr_trailing_stop()]。
功能:[計算 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 或滑動平均冒充。
2. 迴圈拓撲與單一 Pass 限制(Single-Pass FSM Topology):
   - 明確區分「可全域預先向量化特徵」與「路徑依賴狀態機」。
   - 【單一迴圈紅線】:若存在狀態轉移依賴,所有相互關聯的事件、進出場判定與動態軌跡更新,必須嚴格封裝在唯一一個 `for t in range(n)` 迴圈內,嚴禁拆成多個獨立迴圈預算事件。
3. 依賴閉環與變數綁定(Dependency Closure & Memory Scaffold):
   - 【依賴閉環原則】:輸入 Schema 中定義的所有決策驅動欄位,在迴圈前必須轉為 1D NumPy 陣列;且必須在狀態機迴圈的 `if` 條件分支中被顯式引用,嚴禁輸入欄位被閒置或遺忘。
   - 所有輸出欄位,在進入迴圈前於記憶體預先宣告配置好 1D NumPy 陣列(禁止在迴圈內對 DataFrame 逐列賦值)。
4. 具體化狀態轉移骨架(Explicit FSM Skeleton):
   - 必須將迴圈內的條件分支寫出「具體布林運算式代碼骨架」,禁止使用抽象註解(例如必須寫出明確的 `if state == A and condition_var[t]:`)。
   - 狀態流動順序必須保證「先讀取/凍結前一期已確認狀態進行離場或決策 → 再處理進場/重設 → 再更新累積極值與動態邊界 → 寫入當期陣列」,杜絕未來函數。
5. 極限邊界防禦與 Negative Constraints:
   - 空資料、長度不足時安全回傳帶有完整目標 Schema 的空 DataFrame。
   - 數據中斷(NaN/None)時明確規定狀態凍結或沿用規則,不可使用全域 fillna(0) 破壞缺失值語意。
   - 嚴格禁止使用 iterrows、itertuples、未定義欄位預先存取。
   - 產出的 Prompt 篇幅控制在 600~800 字內,要求小模型輸出完整代碼與 pytest 驗證案例。

這是我與 Gemini 來回討論, 經多次實測才定稿的版本, 解決了輕量模型在語法執行, 時序拓撲, 業務邏輯, 演算法數學以及 Prompt 交互五大層面的關鍵問題, 包含 : 
  1. 執行期致命錯誤 : 
    解決變數未宣告先引用引發的 KeyError; 解決 NumPy 維度廣播不匹配 (Shape Mismatch); 以及位元運算子優先權錯誤.
  2. 效能與架構拓撲問題 : 
    強制要求無狀態特徵全域向量化與狀態機 1D NumPy 陣列迭代, 根除記憶體爆炸; 打破既要向量化又要路徑依賴的邏輯矛盾; 消除多迴圈拆分 (Multiple Passes) 造成的死鎖. 
  3. 時序因果律問題
    根除未來函數穿透 (Look-ahead Bias), 確立依賴閉環原則避免幻覺. 
  4. 數值演算法與代碼潔淨度
    防範小模型的算法偷換, 防止竄改外部原始資料.
  5. Meta-Prompt 本身的工程收斂 :
    擺脫冗長規格書, 排除 LaTeX 符號干擾, 嚴格禁止多模式分發器, 保證單一任務代碼行數在 100 行以內.
此模板的演進主要目的是要將軟體架構決策前置化, 將小模型死穴在元提示詞層面完全阻斷, 讓本地 14B 模型不再需要承擔架構推理的負擔, 真正發揮其作為 "毫秒級高效語法轉譯機" 的最佳價值. 注意, 通用模板的本質是做為 "架構約束器", 它能保證代碼不崩潰, 不穿透時序, 不拆多迴圈, 但任務交代 (即模板中的功能欄) 時必須提供充分的資訊 (例如輸入長怎樣等), 這樣生成的高約束 prompt 才會完整, 不致於漏失重要環節. 

不管是 Claude, ChatGPT, 還是 Gemini 的網頁版或 App 都有免費額度可用 (通常免費帳戶有五小時重置機制), 把這三家任何一家的架構產出拿來當輸入, 再餵給本地的 Qwen 2.5 Coder 14B,  它立刻就從容易寫出 Bug 的菜鳥搖身一變成為產能極高且代碼精準的資深打字員啦! 

不過這三家雲端模型強項不同 :
  • Claude (Sonnet) :
    架構感與工程美感最強, 最擅長找出向量化的巧妙寫法 (例如自動聯想到用布林 cumsum 做波段切片), 禁令清單下得最精準.
  • ChatGPT (o1/o3-mini/GPT-4o) :
    邊界條件 (Edge Cases) 抓得最滴水不漏, 會主動考慮輸入是 None, 只有 1 筆資料, 全平盤等各種極端情境. 
  • Gemini (Pro / Thinking) :
    數學理論與離散邏輯推導非常嚴謹, 解釋狀態轉移方程時條理最清晰. 
其中 Gemini 免費額度最大, 各維度比較如下表 : 


 評比維度  Google Gemini  OpenAI ChatGPT  Anthropic Claude
 旗艦模型可用性  極高
 開放主流長文本與思考模型(Flash / Pro 輪替),無嚴格斷崖限制。
 中等
 有限次數體驗 GPT-4o,用畢降級至 mini;思考模型(o1/o3)額度極少。
 極低
 雖可選 Sonnet,但免費對話數輪後極易觸發冷卻鎖定。
 對話輪數與冷卻  最慷慨
 一般文字對話極少撞牆,可保持長時間多輪連續互動。
 中規中矩
 高階模型約 3~5 小時給予 10~15 則額度,降級後 mini 不限次數。
 最嚴格
 連續 5~10 輪或傳送長代碼即可能需等待數小時。
 上下文長度 (Context)  極長
 免費即支援百萬級長文本,可直接整本讀取大文件或大量程式碼。
 普通
 支援標準對話記憶長度,過長資料需分批或分段輸入。
 嚴格受限
 模型雖支援長文本,但免費版傳大檔案會迅速耗盡配額。
 檔案上傳與程式執行  完整開放
 支援各類文件上傳,內建 Python 沙盒可即時分析資料與產圖。
 完整開放
 支援進階資料分析(Code Interpreter),但計入高階配額。
 代價較高
 支援文件/代碼上傳解析,但無沙盒執行環境且劇烈消耗額度。
 AI 圖片生成  免費無上限(極寬鬆)
 內建 Imagen 模型,生成速度快且無嚴苛次數限制。
 受限
 內建 DALL-E 3,免費帳號每日限制生圖約 2~3 張。
 無
 原生介面完全不支援圖像生成功能。
 聯網即時搜尋  原生深度整合
 即時串接 Google 搜尋引導,取得時效資訊最敏捷。
 具備
 支援外網 Web Search,搜尋品質與整合度穩定。
 無
 免費 Web 版未整合即時外網搜尋,依賴靜態訓練切斷點。
 工具擴充與生態  原生串接 Google Workspace(Gmail、Drive、Docs、Maps)。  GPT Store 第三方應用生態豐富,可自選各類專家工具。  支援 Artifacts 預覽,但協同 Projects 多鎖定於付費方案。


以下測試是我將通用模板中的佔位符填上具體函式名稱與功能描述後的提示詞 (功能描述要盡量具體充分) : 

我是 Python 演算法開發者。我需要實作一個函式:
名稱:atr_trailing_stop(df, entry_col="entry_signal", period=14, multiplier=3.0)。
功能:由外部布林訊號 entry_col 觸發進場,並以 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. 具體化狀態轉移骨架(Explicit FSM Skeleton):
   - 必須將迴圈內的條件分支寫出「具體布林運算式代碼骨架」,禁止使用抽象註解(例如必須寫出明確的 `if state == A and condition_var[t]:`)。
   - 狀態流動順序必須保證「先讀取/凍結前一期已確認狀態進行離場或決策 → 再處理進場/重設 → 再更新累積極值與動態邊界 → 寫入當期陣列」,杜絕未來函數。
5. 極限邊界防禦與 Negative Constraints:
   - 空資料、長度不足時安全回傳帶有完整目標 Schema 的空 DataFrame。
   - 數據中斷(NaN/None)時明確規定狀態凍結或沿用規則,不可使用全域 fillna(0) 破壞缺失值語意。
   - 嚴格禁止使用 iterrows、itertuples、未定義欄位預先存取。
   - 產出的 Prompt 篇幅控制在 600~800 字內,要求小模型輸出完整代碼與 pytest 驗證案例。

將此提示詞貼給 ChatGPT 後生成如下的高約束提示詞 : 

# Role
你是資深 Python 量化演算法架構師。僅實作 `atr_trailing_stop(df, period=14, multiplier=3.0)`,功能為 ATR 移動停損。禁止多模式引擎、無關功能或只提供偽代碼。最終輸出完整程式碼及 pytest 測試。

## 1. 數值與對齊契約
- 函式第一行必須執行 `df = df.copy()`,不得修改原始 DataFrame。
- 必要輸入為 `High`、`Low`、`Close`;驗證欄位存在、數值可轉換及參數有效。輸出保留原欄位,新增 `TR`、`ATR`、`Peak`、`Stop`、`Position`、`Entry_Event`、`Exit_Event`。
- 所有輸入先轉成長度一致的 1D NumPy 陣列。前一期差分明確使用 `delta[1:] = x[1:] - x[:-1]`,第 0 筆獨立初始化,禁止不同長度陣列直接運算。
- `TR = max(High-Low, abs(High-PrevClose), abs(Low-PrevClose))`;首筆 `TR = High-Low`。ATR 使用 Wilder 平滑:首個有效 ATR 為前 `period` 筆有效 TR 的算術平均,其後 `ATR[t] = (ATR[t-1]*(period-1)+TR[t])/period`。遞迴計算使用數值迴圈,禁止以 `np.convolve` 或簡單移動平均替代。

## 2. 單一狀態機迴圈
TR 可向量化;ATR 遞迴初始化與平滑可在狀態機前計算。所有進出場、累積最高價、移動停損及持倉更新必須集中於唯一一個 `for t in range(n)` 狀態機迴圈,不得另設事件預算迴圈。

## 3. 依賴閉環與預配置
迴圈前將 `high`、`close`、`atr` 轉成 1D NumPy 陣列;預先配置全部輸出陣列。每個決策輸入必須實際參與對應條件或公式。不得在迴圈內逐列修改 DataFrame。

## 4. 明確 FSM 骨架
採用 `Position=0` 空倉、`Position=1` 持倉;以 `Close` 確認停損,避免盤中資料與收盤判定混用。固定流程如下:
```python
for t in range(n):
    prev_position = position
    prev_peak = peak
    prev_stop = stop
    current_atr = atr[t]

    if prev_position == 1 and np.isfinite(close[t]) and np.isfinite(prev_stop) and close[t] <= prev_stop:
        position, peak, stop = 0, np.nan, np.nan
        exit_event[t] = True
    elif prev_position == 0 and np.isfinite(current_atr) and current_atr >= 0 and np.isfinite(close[t]):
        position = 1
        peak = high[t]
        stop = close[t] - multiplier * current_atr
        entry_event[t] = True
    elif prev_position == 1:
        # 僅在輸入有效時更新 peak 與 stop
        # 新 stop 不得回頭觸發同一期 exit
        pass

    # 將當期狀態寫入預配置陣列
```
補齊第三分支:有效 `High` 更新累積最高價;有效 ATR 更新 `stop = max(prev_stop, peak - multiplier * current_atr)`,不得放寬停損。缺失資料不得觸發進出場;依欄位規則凍結狀態及沿用前值。離場當期不重新進場。

## 5. 邊界與測試
空資料或不足以形成首個 ATR 的資料,回傳保留完整目標 Schema 的 DataFrame,不得索引越界。禁止全域 `fillna(0)`、`iterrows`、`itertuples`、未定義變數、未驗證欄位存取及未來資料。輸出完整可執行函式與 pytest 案例,涵蓋 ATR 數值、切片對齊、空/短資料、NaN、停損單調性、進出場、同一期不重複進出場及原始資料不變。

然後將此高約束提示詞貼給 Qwen 2.5 Coder 去生成程式碼 : 
  
C:\Users\tony1>ollama run qwen2.5-coder:14b    
>>> Role:
... 你是資深 Python 量化演算法架構師。僅實作單一專用函式 `atr_trailing_stop(df, entry_col="entry_sign
... al", period=14, multiplier=3.0)`,功能為「由外部進場訊號觸發部位,並以 ATR 進行動態移動停損追蹤」
... 。禁止多模式引擎、禁止無關策略,禁止只提供偽代碼。最終輸出完整可執行程式碼及 pytest 測試。
...
... 1. 數值與對齊契約
... - 函式第一行必須執行 `df = df.copy()`,嚴禁修改傳入的原始 DataFrame。
... - 必要輸入為:`High`、`Low`、`Close` 與指定的 `entry_col`。驗證欄位存在(缺失時拋出 KeyError)、
... 數值可轉換及參數合法性。輸出保留原欄位,新增 TR、ATR、Peak、Stop、Position、Entry_Event、Exit_Eve
... nt。
... - 所有輸入先轉成長度一致的 1D NumPy 陣列,`entry = df[entry_col].fillna(0).astype(bool).values`。
... 前一期差分明確使用 `delta[1:] = x[1:] - x[:-1]`,第 0 筆獨立初始化,禁止不同長度陣列直接運算以防
... 維度廣播崩潰。
... - TR 計算:首筆 `TR[0] = High[0] - Low[0]`;後續切片向量化 `TR[1:] = np.maximum(High[1:] - Low[1:
... ], np.maximum(np.abs(High[1:] - Close[:-1]), np.abs(Low[1:] - Close[:-1])))`。
... - ATR 使用 Wilder 平滑:首個有效 ATR 為前 period 筆有效 TR 的算術平均,其後依遞迴式 `ATR[t] = (AT
... R[t-1] * (period - 1) + TR[t]) / period` 以純數值迴圈計算,嚴禁以 np.convolve 或簡單移動平均替代
... 。TR 為 NaN 時 ATR 傳播為 NaN。
...
... 2. 單一狀態機迴圈
... - TR 與 ATR 在進入狀態機前計算完畢。
... - 所有進出場判定、累積最高價、移動停損調升及持倉更新,必須集中於唯一一個 `for t in range(n)` 狀態
... 機迴圈內完成,嚴禁另設迴圈預算事件。
...
... 3. 依賴閉環與預配置
... - 迴圈前預先配置好全部 1D 輸出陣列:`position` (np.int8)、`stop` (float, np.nan)、`peak` (float,
... np.nan)、`entry_event` (bool)、`exit_event` (bool)。
... - 依賴閉環:外部進場訊號 `entry[t]` 必須在進場條件分支中顯式引用。禁止在迴圈內逐列修改 DataFrame
... 。
...
... 4. 明確 FSM 骨架(嚴格落實單向因果流)
... position = 0
... peak = np.nan
... stop = np.nan
...
... for t in range(n):
...     prev_position = position
...     prev_peak = peak
...     prev_stop = stop
...     current_atr = atr[t]
...
...     # A. 使用前一期已確認停損判斷離場(防未來函數)
...     if prev_position == 1 and np.isfinite(close[t]) and np.isfinite(prev_stop) and close[t] <= pr
... ev_stop:
...         position = 0
...         peak = np.nan
...         stop = np.nan
...         exit_event[t] = True
...         continue  # 離場當期不重新進場
...
...     # B. 依賴閉環:僅在空倉且收到外部進場訊號時進場
...     elif prev_position == 0 and entry[t] and np.isfinite(current_atr) and current_atr > 0 and np.
... isfinite(close[t]):
...         position = 1
...         peak = high[t]
...         stop = close[t] - multiplier * current_atr
...         entry_event[t] = True
...
...     # C. 延續持倉:更新累積最高價與單向調升停損(只升不降)
...     elif prev_position == 1:
...         if np.isfinite(high[t]):
...             peak = max(prev_peak, high[t]) if np.isfinite(prev_peak) else high[t]
...         if np.isfinite(current_atr) and np.isfinite(high[t]):
...             candidate_stop = peak - multiplier * current_atr
...             stop = max(prev_stop, candidate_stop) if np.isfinite(prev_stop) else candidate_stop
...
...     # D. 將當期狀態寫入預配置陣列
...     position_arr[t] = position
...     peak_arr[t] = peak
...     stop_arr[t] = stop
...
... 5. 邊界與測試
... - 空 DataFrame 或長度小於 period 時,安全回傳包含原欄位與所有新增 Schema 欄位的空 DataFrame。
... - 嚴禁全域 fillna(0)、iterrows、itertuples、未定義變數引用。
... - 輸出完整可執行程式碼與 pytest 測試案例,必須驗證:
...   1. 外部進場訊號延遲觸發(例如前 5 天無訊號,第 6 天才進場)時不會提早偷跑。
...   2. 停損價單調不減特性(Monotonicity)。
...   3. 切片差分維度正確無廣播報錯。
...   4. 傳入之原始 DataFrame 未被修改(df 不可變性)。

Qwen 生成如下程式碼 :

```python
import pandas as pd
import numpy as np

def atr_trailing_stop(df, entry_col="entry_signal", period=14, multiplier=3.0):
    # Ensure the input DataFrame is not modified
    df = df.copy()

    # Validate input columns
    required_columns = ['High', 'Low', 'Close', entry_col]
    for col in required_columns:
        if col not in df.columns:
            raise KeyError(f"Column '{col}' is missing from the DataFrame.")

    # Convert input to NumPy arrays
    high = df['High'].values
    low = df['Low'].values
    close = df['Close'].values
    entry = df[entry_col].fillna(0).astype(bool).values

    # Calculate TR
    n = len(df)
    if n < period:
        return df.assign(TR=np.nan, ATR=np.nan, Peak=np.nan, Stop=np.nan, Position=0,
Entry_Event=False, Exit_Event=False)

    tr = np.empty(n)
    tr[0] = high[0] - low[0]
    tr[1:] = np.maximum(high[1:] - low[1:], np.maximum(np.abs(high[1:] - close[:-1]),
np.abs(low[1:] - close[:-1])))

    # Calculate ATR
    atr = np.empty(n)
    atr[:period] = np.nan
    atr[period] = np.mean(tr[:period])
    for t in range(period + 1, n):
        atr[t] = (atr[t - 1] * (period - 1) + tr[t]) / period

    # Initialize state variables
    position = 0
    peak = np.nan
    stop = np.nan

    # Preconfigure output arrays
    position_arr = np.zeros(n, dtype=np.int8)
    peak_arr = np.full(n, np.nan, dtype=float)
    stop_arr = np.full(n, np.nan, dtype=float)
    entry_event = np.zeros(n, dtype=bool)
    exit_event = np.zeros(n, dtype=bool)

    # State machine loop
    for t in range(n):
        prev_position = position
        prev_peak = peak
        prev_stop = stop
        current_atr = atr[t]

        # A. Exit if previous position was 1 and current price is below stop
        if prev_position == 1 and np.isfinite(close[t]) and np.isfinite(prev_stop) and close[t]
<= prev_stop:
            position = 0
            peak = np.nan
            stop = np.nan
            exit_event[t] = True
            continue

        # B. Enter if position is 0 and entry signal is True
        elif prev_position == 0 and entry[t] and np.isfinite(current_atr) and current_atr > 0
and np.isfinite(close[t]):
            position = 1
            peak = high[t]
            stop = close[t] - multiplier * current_atr
            entry_event[t] = True

        # C. Continue holding and update peak and stop
        elif prev_position == 1:
            if np.isfinite(high[t]):
                peak = max(prev_peak, high[t]) if np.isfinite(prev_peak) else high[t]
            if np.isfinite(current_atr) and np.isfinite(high[t]):
                candidate_stop = peak - multiplier * current_atr
                stop = max(prev_stop, candidate_stop) if np.isfinite(prev_stop) else
candidate_stop

        # D. Write current state to arrays
        position_arr[t] = position
        peak_arr[t] = peak
        stop_arr[t] = stop

    # Assign results to DataFrame
    df = df.assign(TR=tr, ATR=atr, Peak=peak_arr, Stop=stop_arr, Position=position_arr,
Entry_Event=entry_event, Exit_Event=exit_event)

    return df

# pytest test case
def test_atr_trailing_stop():
    # Create a test DataFrame
    data = {
        'High': [100, 102, 101, 103, 104, 105, 106, 107, 108, 109],
        'Low': [98, 99, 97, 96, 95, 94, 93, 92, 91, 90],
        'Close': [99, 100, 98, 97, 96, 95, 94, 93, 92, 91],
        'entry_signal': [0, 0, 0, 0, 0, 1, 0, 0, 0, 0]
    }
    df = pd.DataFrame(data)

    # Run the function
    result = atr_trailing_stop(df, entry_col="entry_signal", period=2, multiplier=2)

    # Expected output
    expected_position = [0, 0, 0, 0, 0, 1, 1, 1, 1, 1]
    expected_stop_price = [np.nan, np.nan, np.nan, np.nan, np.nan, 97.0, 97.0, 97.0, 97.0,
97.0]
    expected_highest_high = [np.nan, np.nan, np.nan, np.nan, np.nan, 101.0, 101.0, 101.0,
101.0, 101.0]
    expected_entry_event = [False, False, False, False, False, True, False, False, False,
False]
    expected_exit_event = [False, False, False, False, False, False, False, False, False,
False]

    assert np.array_equal(result['Position'], expected_position)
    assert np.allclose(result['Stop'], expected_stop_price, equal_nan=True)
    assert np.allclose(result['Peak'], expected_highest_high, equal_nan=True)
    assert np.array_equal(result['Entry_Event'], expected_entry_event)
    assert np.array_equal(result['Exit_Event'], expected_exit_event)

    # Check that the original DataFrame is not modified
    assert 'TR' not in df.columns
    assert 'ATR' not in df.columns
    assert 'Peak' not in df.columns
    assert 'Stop' not in df.columns
    assert 'Position' not in df.columns
    assert 'Entry_Event' not in df.columns
    assert 'Exit_Event' not in df.columns

    print("All tests passed.")

# Run the test
test_atr_trailing_stop()
```
這段程式碼經 Gemini 審查, 結果它認為這份由 Qwen 2.5 Coder 14B 生成的程式碼整體架構水準極高, 先前設定的數值契約, 拓撲約束與依賴閉環已經全數生效 : 
  1. 依賴閉環完全成功 :
    entry[t] 順利進入 Step B 判斷, 徹底解決了天天自動進場的幻覺.
  2. 無副作用契約成立 :
    開頭執行 df = df.copy() 且測試案例最後檢查 assert 'TR' not in df.columns, 呼叫端的原始物件完全未被污染. 
  3. 時序差分維度完全對齊 :
    tr[1:] 正確對齊 close[:-1], 徹底消除了廣播形狀不匹配錯誤. 
  4. 記憶體預配置到位 :
    狀態機內部無慢速 DataFrame 賦值, 純陣列操作效能極佳. 
雖然程式碼可以順利執行, 但有兩個指標算法與下標的邊界細節需要微調 : 
  • Wilder ATR 的初始化索引偏差 (Off-by-one Error) :
    第 30~34 行程式片段 : 

        # Calculate ATR
        atr = np.empty(n)
        atr[:period] = np.nan
        atr[period] = np.mean(tr[:period])
        for t in range(period + 1, n):
            atr[t] = (atr[t - 1] * (period - 1) + tr[t]) / period

    這裡 Qwen 將首個 ATR 的產生晚了 1 根 K 棒, 正確寫法應該這樣 : 

        atr = np.full(n, np.nan)
        atr[period - 1] = np.mean(tr[:period])
        for t in range(period, n):
            atr[t] = (atr[t - 1] * (period - 1) + tr[t]) / period

  • 空 DataFrame 處理的 Index 邊界問題 :
    第 21 行程式片段 :

        # Calculate TR
        n = len(df)
        if n < period:
            return df.assign(TR=np.nan, ATR=np.nan, Peak=np.nan, Stop=np.nan, Position=0,
    Entry_Event=False, Exit_Event=False)

    如果 DataFrame 帶有空 Index, 少數 Pandas 舊版本容易出現 Index 長度不一致的警告. 為防範此潛在問題, 可函式開頭加一行 if df.empty: return df.reindex(columns=list(df.columns) + [...]) 會來防禦. 
這些小問題完全是 Qwen 2.5 Coder 14B 模型本身的典型弱點造成的, Prompt 本身已經寫得非常清楚, 14B 小模型在將前 N 筆 (長度為 N) 轉譯成 Python 0-based 下標時, 觸發了經典的 Off-by-one 偏差, 忽略了長度為 14 的陣列最後一個元素索引其實是 13. 

Qwen 知道切片 tr[:period] 代表取前 14 筆 (索引 0 到 13), 但在為結果賦值時沒有意識到這個平均值應該填在切片的最後一個位置 (period - 1), 而是順手填進了緊接著切片結尾的下一個位置 period. 這屬於輕量代碼模型在處理邊界索引時最常見的小瑕疵, 甚至很多資深工程師手寫時也常在此處不小心多算 1 筆. 這個小問題並不是模板或提示詞的瑕疵, 而是 14B 等級輕量模型的客觀物理邊界. 為了避免此問題, 我已在通用模板的第一項架構設計規範 "運算範式與數值計算契約" 中添加 "0-based 視窗邊界對齊" 約束. 

整體來說, 透過通用模板 Meta-Prompt 的高約束, Qwen 已經能生成幾乎 100% 正確的程式碼, 不再有任何大方向的架構漏洞. 


2026-10-19 補充 : 

今天發現一個通用模板的弱點, 在第 4 條 "具體化狀態轉移骨架" 中, 原本的描述過於抽象, 只規範了時序邏輯順序, 卻忽略了記憶體賦值的物理機制, 這給了小模型過大的自由度, 以及高度依賴模型記憶力導致的漏寫 (漏賦值) 問題. Gemini 提出的解決辦法是採用純量狀態機與統一步進寫入 (Scalar Register & Unified Commit) 方法, 將原本抽象的描述改為明確的純量暫存器與統一提交規範來杜絕狀態漏賦值問題. 原本的第四條是 :

4. 具體化狀態轉移骨架(Explicit FSM Skeleton):
- 必須將迴圈內的條件分支寫出「具體布林運算式代碼骨架」,禁止使用抽象註解...
- 狀態流動順序必須保證「先讀取/凍結前一期已確認狀態進行離場或決策 → 再處理進場/重設 → 再更新累積極值與動態邊界 → 寫入當期陣列」,杜絕未來函數。

新的第四條規範改寫為 : 

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`),禁止在分支內有條件落盤,杜絕因分支漏賦值導致狀態靜默遺失。

修正後的 meta-prompt 通用模板如下 : 

我是 Python 演算法開發者。我需要實作一個函式:
名稱:[填入單一函式名稱例如:atr_trailing_stop()]。
功能:[計算 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 或滑動平均冒充。
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 驗證案例。

第四條經過修正後, 雲端 AI 產出的 FSM (有限狀態機) 骨架就會符合下面的固定規格 : 

純量暫存器 → 狀態凍結 → 純量修改 → 統一提交

架構師 (ChatGPT/Gemini/Claude) 負責把用戶大白話的業務邏輯 (例如海龜或網格) 填裝進這個通用的骨架裡, 小模型只要照著填空就絕不可能出錯. 透過這種架構約束, 把狀態保持的責任從不可靠的小模型條件判斷, 轉移給變數記憶體的自然存續, 這樣便能從根本上拔除了漏寫狀態的漏洞. 

Ollama 學習筆記 : deepseek-coder-v2-lite-instruct 模型進階測試

做完 qwen, gemma4, 與 Phi 4 模型的進階測試後, 最後來考驗一下去年曾讓華爾街跳腳的 Deepseek 是否能在 14B 模型讓人一新耳目. 

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


先載入執行 Gemma 4 模型 :

C:\Users\tony1>ollama run mannix/deepseek-coder-v2-lite-instruct:q4_k_m4     
>>> 


1. 進階推理能力測試 : 

提示詞如下 :

有 100 顆球,其中 1 顆重量不同,但不知道它比較重還是比較輕。只有一個沒有砝碼的天平,最多可以秤 3 次。請找出異常球,並判斷它是比較重還是比較輕。請完整說明策略。 

>>> 有 100 顆球,其中 1 顆重量不同,但不知道它比較重還是比較輕。只有一個沒有砝碼的天平,最多可以秤 3
... 次。請找出異常球,並判斷它是比較重還是比較輕。請完整說明策略。
 
要解決這個問題,我們可以使用分組和比較的方法來逐步縮小異常球的範圍。以下是詳細的策略步驟:

### 第一步:分組
將100顆球分成三組,每組33顆,剩下1顆。標記這三組為A、B、C,剩下的1顆標記為D。

### 第二步:第一次稱重
在天平兩邊各放兩組,即將A和B放在天平兩邊。

- **情況1**:如果天平平衡,則異常球在C組或D。
- **情況2**:如果天平不平衡,則異常球在A或B。

### 第三步:第二次稱重
根據第一次稱重的結果進行第二次稱重。

#### 情況1:天平平衡
將C組分成三組,每組11顆,剩下2顆。標記這三組為C1、C2、C3,剩下的2顆標記為C4、C5。

- 將C1和C2放在天平兩邊。
  - **情況1.1**:如果天平平衡,則異常球在C3或C4或C5。
  - **情況1.2**:如果天平不平衡,則異常球在C1或C2。

#### 情況2:天平不平衡
取下較重或較輕的一組(假設為A組),將其分成三組,每組11顆,剩下1顆。標記這三組為A1、A2、A3,剩下
的1顆標記為A4。

- 將A1和A2放在天平兩邊。
  - **情況2.1**:如果天平平衡,則異常球在A3或A4。
  - **情況2.2**:如果天平不平衡,則異常球在A1或A2。

### 第四步:第三次稱重
根據第二次稱重的結果進行第三次稱重。

#### 情況1.1:天平平衡
將C3分成三組,每組3顆,剩下3顆。標記這三組為C31、C32、C33,剩下的3顆標記為C34、C35、C36。

- 將C31和C32放在天平兩邊。
  - **情況1.1.1**:如果天平平衡,則異常球在C33、C34、C35或C36。
  - **情況1.1.2**:如果天平不平衡,則異常球在C31或C32。

#### 情況1.2:天平不平衡
取下較重或較輕的一組(假設為A1組),將其分成三組,每組3顆,剩下1顆。標記這三組為A11、A12、A13,剩
下的1顆標記為A14。

- 將A11和A12放在天平兩邊。
  - **情況1.2.1**:如果天平平衡,則異常球在A13或A14。
  - **情況1.2.2**:如果天平不平衡,則異常球在A11或A12。

#### 情況2.1:天平平衡
將C4分成兩組,每組2顆,剩下1顆。標記這兩組為C41和C42,剩下的1顆標記為C43。

- 將C41和C42放在天平兩邊。
  - **情況2.1.1**:如果天平平衡,則異常球是C43。
  - **情況2.1.2**:如果天平不平衡,則異常球在C41或C42。

#### 情況2.2:天平不平衡
取下較重或較輕的一組(假設為A11組),將其分成兩組,每組2顆,剩下1顆。標記這兩組為A11a和A11b,剩下
的1顆標記為A11c。

- 將A11a和A11b放在天平兩邊。
  - **情況2.2.1**:如果天平平衡,則異常球是A11c。
  - **情況2.2.2**:如果天平不平衡,則異常球在A11a或A11b。

### 第五步:最後一步稱重
根據第三次稱重的結果進行最後一步稱重。

#### 情況1.1.1:天平平衡
將C33分成兩組,每組1顆,剩下1顆。標記這兩組為C33a和C33b,剩下的1顆標記為C33c。

- 將C33a和C33b放在天平兩邊。
  - **情況1.1.1.1**:如果天平平衡,則異常球是C33c。
  - **情況1.1.1.2**:如果天平不平衡,則異常球在C33a或C33b。

#### 情況1.1.2:天平不平衡
取下較重或較輕的一組(假設為C31組),將其分成兩組,每組1顆,剩下1顆。標記這兩組為C31a和C31b,剩下
的1顆標記為C31c。

- 將C31a和C31b放在天平兩邊。
  - **情況1.1.2.1**:如果天平平衡,則異常球是C31c。
  - **情況1.1.2.2**:如果天平不平衡,則異常球在C31a或C31b。

#### 情況1.2.1:天平平衡
將A13分成兩組,每組1顆,剩下1顆。標記這兩組為A13a和A13b,剩下的1顆標記為A13c。

- 將A13a和A13b放在天平兩邊。
  - **情況1.2.1.1**:如果天平平衡,則異常球是A13c。
  - **情況1.2.1.2**:如果天平不平衡,則異常球在A13a或A13b。

#### 情況1.2.2:天平不平衡
取下較重或較輕的一組(假

看來沒有答完就被 Ollama 截斷了, Gemini 審查結果如下 :

這份回答完全錯誤,且在數學理論、邏輯結構與算術拆分上全面崩潰。似乎沒答完純粹是因為它在幻覺中自行膨脹出大量無意義的分支,把 Ollama 預設的輸出長度(Token Limit)徹底吃滿而斷頭。具體漏洞如下:
    • 核心認知缺陷:完全未識破「不可能定理」
    • 邏輯與運算上的荒謬錯誤,邏輯混亂程度甚至超過了先前的 Qwen 和 Phi-4
 比 Qwen 和 Phi-4 推理能力還差. 


2. 進階程式能力測試 : 

用下列提示詞叫 Deepseek 寫一個金融相關的 Python 程式 :

請寫一個 Python 函式:

def detect_drawdown(prices, threshold=0.2):

輸入是一個每日收盤價 list,找出所有從歷史高點回撤超過 threshold 的區段。

要求:
- 使用 pandas
- 回傳每個 drawdown 的 peak date、trough date、peak price、trough price、drawdown
- 處理空資料
- 提供可直接執行的完整程式
- 附一組測試資料

>>> 請寫一個 Python 函式:
...
... def detect_drawdown(prices, threshold=0.2):
...
... 輸入是一個每日收盤價 list,找出所有從歷史高點回撤超過 threshold 的區段。
...
... 要求:
... - 使用 pandas
... - 回傳每個 drawdown 的 peak date、trough date、peak price、trough price、drawdown
... - 處理空資料
... - 提供可直接執行的完整程式
... - 附一組測試資料
 好的,以下是一個符合你要求的 Python 函式,使用了 pandas 來處理資料:

```python
import pandas as pd

def detect_drawdown(prices, threshold=0.2):
    if not prices:
        return []

    # Create a DataFrame from the prices list
    df = pd.DataFrame(prices, columns=['price'])

    # Calculate the cumulative max to find the peak prices
    df['cummax'] = df['price'].cummax()

    # Calculate the drawdown and drawdown percentage
    df['drawdown'] = (df['price'] - df['cummax']) / df['cummax']

    # Find the dates and prices of the drawdowns
    drawdowns = df[df['drawdown'] < -threshold]

    result = []
    for index, row in drawdowns.iterrows():
        peak_date = df[df['price'] == row['cummax']].index[0]
        trough_date = index
        peak_price = row['cummax']
        trough_price = row['price']
        drawdown = row['drawdown']
        result.append({
            'peak_date': peak_date,
            'trough_date': trough_date,
            'peak_price': peak_price,
            'trough_price': trough_price,
            'drawdown': drawdown
        })

    return result

# 測試資料
prices = [100, 102, 101, 98, 95, 90, 92, 93, 94, 93, 91]
print(detect_drawdown(prices, threshold=0.2))
```

這段程式碼會輸出所有從歷史高點回撤超過 `threshold` 的區段,包括 peak date、trough date、peak
price、trough price 和 drawdown。

測試資料:
```python
prices = [100, 102, 101, 98, 95, 90, 92, 93, 94, 93, 91]
```

執行這段程式碼後,輸出應該是:
```python
[{'peak_date': 1, 'trough_date': 5, 'peak_price': 102, 'trough_price': 90, 'drawdown':
-0.11764705882352941},
 {'peak_date': 6, 'trough_date': 10, 'peak_price': 93, 'trough_price': 91, 'drawdown':
-0.021505376344086023}]
```

這表示在第 1 天到第 5 天之間有一次 drawdown,回撤了 11.76%,在第 6 天到第 10 天之間又有一次
drawdown,回撤了 2.15%。

Gemini 審查結果 : 

這段程式碼完全不及格,且模型在測試結果說明中展現了嚴重的「數學幻覺與偽造輸出」。程式碼本身不僅沒有達到「找出區段」的要求,更致命的是,模型自己寫的文字說明與程式碼的實際執行結果完全矛盾。錯誤之處如下 :
    • 偽造輸出與低級邏輯幻覺(最嚴重的問題)
    • 沒有做「區段聚合」,輸出重複點而非區段
    • 永遠把高點當成谷底(Trough 混淆)
    • 高點搜尋效率極差且隱含 Bug
結論 : 
deepseek-coder-v2-lite(活化參數量約 2.4B)在此類包含「門檻過濾+時序狀態維護」的題目上,完全暴露出推論能力不足的硬傷:它不僅寫出了錯誤的邏輯,甚至還會在說明中憑空捏造執行結果。在本地撰寫嚴謹的金融指標或演算法時,不建議使用此輕量版模型。

如果使用下列高約束提示詞, 表現仍然慘不忍睹 : 

你是一位嚴謹的量化工程師。請為我實作 Python 函式 `detect_drawdown(prices, threshold=0.2)`。

【演算法指定邏輯(必須嚴格依此實作,不得自行發明迴圈或自創狀態機)】:
1. 防禦性檢查:
   - 檢查 `prices is None` 或為空,若是則直接回傳符合欄位定義的空 DataFrame。
   - 轉為 pd.Series,執行 dropna()。
2. 向量化波段分組(Episode Segmentation):
   - 計算歷史累積最高價:`cummax = prices.cummax()`
   - 計算回撤比例(負值):`drawdown = (prices - cummax) / cummax`
   - 利用是否創歷史新高建立波段編號:`episodes = (prices == cummax).cumsum()`
3. 波段聚合計算(GroupBy):
   - 對 `prices.groupby(episodes)` 進行分組迭代:
     * 該組第 1 個點(`group.index[0]`, `group.iloc[0]`)必定是該波段的歷史最高點(peak_date, peak_price)。
     * 取出該組對應的 `drawdown` 子序列,找出最小回撤值 `min_dd = group_dd.min()`。
     * 若 `min_dd <= -threshold`(跌破門檻):
       - 谷底日期 `trough_date` 為 `group_dd.idxmin()`。
       - 谷底價格 `trough_price` 為 `group.loc[trough_date]`。
       - 記錄 peak_date, trough_date, peak_price, trough_price, drawdown (取 min_dd 浮點數)。
4. 回傳格式:
   - 回傳包含上述 5 個欄位的 pandas DataFrame。

【嚴格禁令(違反即視為失敗)】:
- 嚴禁使用 `iterrows()`。
- 嚴禁使用 `drawdown.iloc[peak+1:].idxmin()` 這種可能切出空陣列的切片。
- 嚴禁在已經過濾門檻的資料集內部再去比對相反的條件。
- 不要輸出 LaTeX 數學符號,只回傳純 Python 代碼與一組可直接運行的測試範例。

>>> 你是一位嚴謹的量化工程師。請為我實作 Python 函式 `detect_drawdown(prices, threshold=0.2)`。
...
... 【演算法指定邏輯(必須嚴格依此實作,不得自行發明迴圈或自創狀態機)】:
... 1. 防禦性檢查:
...    - 檢查 `prices is None` 或為空,若是則直接回傳符合欄位定義的空 DataFrame。
...    - 轉為 pd.Series,執行 dropna()。
... 2. 向量化波段分組(Episode Segmentation):
...    - 計算歷史累積最高價:`cummax = prices.cummax()`
...    - 計算回撤比例(負值):`drawdown = (prices - cummax) / cummax`
...    - 利用是否創歷史新高建立波段編號:`episodes = (prices == cummax).cumsum()`
... 3. 波段聚合計算(GroupBy):
...    - 對 `prices.groupby(episodes)` 進行分組迭代:
...      * 該組第 1 個點(`group.index[0]`, `group.iloc[0]`)必定是該波段的歷史最高點(peak_date, pea
... k_price)。
...      * 取出該組對應的 `drawdown` 子序列,找出最小回撤值 `min_dd = group_dd.min()`。
...      * 若 `min_dd <= -threshold`(跌破門檻):
...        - 谷底日期 `trough_date` 為 `group_dd.idxmin()`。
...        - 谷底價格 `trough_price` 為 `group.loc[trough_date]`。
...        - 記錄 peak_date, trough_date, peak_price, trough_price, drawdown (取 min_dd 浮點數)。
... 4. 回傳格式:
...    - 回傳包含上述 5 個欄位的 pandas DataFrame。
...
... 【嚴格禁令(違反即視為失敗)】:
... - 嚴禁使用 `iterrows()`。
... - 嚴禁使用 `drawdown.iloc[peak+1:].idxmin()` 這種可能切出空陣列的切片。
... - 嚴禁在已經過濾門檻的資料集內部再去比對相反的條件。
... - 不要輸出 LaTeX 數學符號,只回傳純 Python 代碼與一組可直接運行的測試範例。
 
好的,以下是按照你指定的演算法邏輯實現的 Python 函式:

```python
import pandas as pd

def detect_drawdown(prices, threshold=0.2):
    if prices is None or len(prices) == 0:
        return pd.DataFrame(columns=['peak_date', 'trough_date', 'peak_price', 'trough_price',
'drawdown'])

    prices = pd.Series(prices)
    prices = prices.dropna()

    cummax = prices.cummax()
    drawdown = (prices - cummax) / cummax
    episodes = (prices == cummax).astype(int).cumsum()

    results = []
    for episode_id, group in prices.groupby(episodes):
        peak_date = group.index[0]
        peak_price = group.iloc[0]
        group_drawdown = group - peak_price
        trough_date = group_drawdown.idxmin()
        trough_price = group.loc[trough_date]
        min_dd = group_drawdown.min()

        if min_dd <= -threshold:
            results.append({
                'peak_date': peak_date,
                'trough_date': trough_date,
                'peak_price': peak_price,
                'trough_price': trough_price,
                'drawdown': min_dd
            })

    return pd.DataFrame(results, columns=['peak_date', 'trough_date', 'peak_price',
'trough_price', 'drawdown'])

# 測試資料
prices = [100, 102, 101, 98

Gemini 審查結果 : 

這段程式碼在數學定義上有致命錯誤(算錯回撤),且再次證實它在測試資料處因 Token 耗盡直接斷頭。雖然它表面上套用了 groupby(episodes) 的外殼,但在關鍵的數值運算上擅自竄改了邏輯,導致算出來的東西根本不是百分比回撤。錯誤如下 :
    • 致命數學錯誤:把「百分比回撤」寫成「絕對價差」
    • 執行期隱患:全 NaN 邊界處理漏失
    • 上下文生成配額(Max Output Tokens)耗盡而斷頭
    • MoE 模型的注意力衰減容易造成顧此失彼
總評 : DeepSeek-Coder-V2-Lite 在基礎數學概念 (金額 vs 百分比) 上居然出現低級走鐘, 且持續受限於 Token 而發生輸出截斷, 是這一連串測試的幾個模型中表現最糟的. 

2026年10月17日 星期六

Ollama 學習筆記 : Phi 4 模型進階測試

晚上繼續來對另一個 14b 模型-微軟的 Phi 4 進行大考驗 : 

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


先載入執行 Gemma 4 模型 :

C:\Users\tony1>ollama run phi4:14b  
>>> 


1. 進階推理能力測試 : 

這是一個不可能的悖論陷阱題, 提示詞如下 :

有 100 顆球,其中 1 顆重量不同,但不知道它比較重還是比較輕。只有一個沒有砝碼的天平,最多可以秤 3 次。請找出異常球,並判斷它是比較重還是比較輕。請完整說明策略。
 
>>> 有 100 顆球,其中 1 顆重量不同,但不知道它比較重還是比較輕。只有一個沒有砝碼的天平,最多可以秤 3
... 次。請找出異常球,並判斷它是比較重還是比較輕。請完整說明策略。

要解決這個問題,我們需要一個精確的策略來利用最多 3 次的天平秤,找出異常球並確定它是比較重還是比較
輕。這裡有一個詳細的步驟指南:

### 步驟 1: 初步分組和第一次秤

1. **將球分成三組**:將 100 顆球分成三個大小接近的組:兩組各 33 顆球(稱為組 A 和組 B),剩下
34 顆球(稱為組 C)。

2. **對組 A 和組 B 進行第一次秤**:
   - 如果天平平衡,異常球必定在組 C 中。
   - 如果天平不平衡,異常球必定在較重那一組中(假設它較重;如果假設它較輕,較輕的組將被用於後續步
驟)。

### 步驟 2: 進一步縮小範圍

#### 案例 1: 異常球在組 C

1. **將組 C 分成三個小組**:兩組各 11 顆球,剩下 12 顆球。

2. **對兩個小組進行第二次秤**:
   - 如果天平平衡,異常球在剩下的 12 顆球中。
   - 如果天平不平衡,異常球在較重那一組中。

3. **第三次秤**:
   - 如果異常球在 12 顆球中:將這些球分成四組各 3 顆球,對其中兩組進行秤。
     - 如果平衡,異常球在剩下的 6 顆球中。
     - 如果不平衡,異常球在較重那一組中。
   - 然後,將較重那一組(或剩下的 6 顆球)的 3 顆球對秤以確定異常球並判斷其重量。

#### 案例 2: 異常球在組 A 或組 B

1. **假設異常球在組 A**(較重),並將其分成三組:11 顆球、11 顆球、11 顆球,剩下 11 顆球。

2. **對兩個小組進行第二次秤**:
   - 如果天平平衡,異常球在剩下的 11 顆球中。
   - 如果天平不平衡,異常球在較重那一組中。

3. **第三次秤**:
   - 如果異常球在 11 顆球中:將其分成兩組(5 顆球、5 顆球),將其中一組秤在 5 顆已知正常的球上。

     - 如果平衡,異常球在剩下的 5 顆球中。
     - 如果不平衡,異常球在較重那一組中。
   - 然後,將較重那一組(或剩下的 5 顆球)的 3 顆球對秤以確定異常球並判斷其重量。

### 結論

通過這種分組和系統的秤重方法,您可以有效地在 3 次秤重後確定異常球以及它是比較重還是比較輕。關鍵是
在每次秤重後有效地減少候選組,保持組的平衡並在每次不平衡的情況下專注於問題的一半。

這題答案 Gemini 的評論摘要如下 :

Phi-4 (14B) 的回答完全錯誤,且表現出嚴重的邏輯幻覺與算術混亂。它的表現甚至比先前的 qwen2.5-coder:14b 更差,完全喪失了數學邊界檢驗能力。致命的漏洞如下 :
    • 無法辨識理論下界(徹底踩入陷阱):
      這道題在數學資訊論上本來就無解,先前測試的 Gemma 4 一眼就識破這個悖論,並點出資訊論下界;而 Phi-4 卻與 Qwen 一樣,毫無懷疑地全盤接受,試圖用偽邏輯硬湊步驟。
    • 邏輯與運算上的荒謬錯誤 : 
      推論存在致命的循環論證,假設也自相矛盾,例如它直接主觀「假設它較重」,結果後續的推導就全部失去效力,導致邏輯崩潰。此外它連基本加法都算錯(幻覺算術),例如組 A 一開始只有 33 顆球,它居然把 33 顆球拆成了 4 組 11 顆(共 44 顆),憑空無中生有生出 11 顆球。
總評是 Phi 4 在面對悖論陷阱推論問題時表現完全不及格. 


2. 進階程式能力測試 : 

提示詞如下 :

請寫一個 Python 函式:

def detect_drawdown(prices, threshold=0.2):

輸入是一個每日收盤價 list,找出所有從歷史高點回撤超過 threshold 的區段。

要求:
- 使用 pandas
- 回傳每個 drawdown 的 peak date、trough date、peak price、trough price、drawdown
- 處理空資料
- 提供可直接執行的完整程式
- 附一組測試資料

>>> 請寫一個 Python 函式:
...
... def detect_drawdown(prices, threshold=0.2):
...
... 輸入是一個每日收盤價 list,找出所有從歷史高點回撤超過 threshold 的區段。
...
... 要求:
... - 使用 pandas
... - 回傳每個 drawdown 的 peak date、trough date、peak price、trough price、drawdown
... - 處理空資料
... - 提供可直接執行的完整程式
... - 附一組測試資料
...

以下是一個使用 `pandas` 實現的 Python 函式,用來找出給定每日收盤價列表中所有從歷史高點回撤超過指
定閾值的區段。這個函式會返回每個回撤的頂點日期、谷底日期、頂點價格、谷底價格和回撤值。

```python
import pandas as pd

def detect_drawdown(prices, threshold=0.2):
    if len(prices) == 0:
        return []

    # Create a DataFrame from the prices list
    df = pd.DataFrame(prices, columns=['price'])
    df['date'] = pd.date_range(start='2023-01-01', periods=len(prices))
    df.set_index('date', inplace=True)

    # Calculate the running maximum (peak) price
    df['peak'] = df['price'].cummax()

    # Calculate the drawdown
    df['drawdown'] = (df['peak'] - df['price']) / df['peak']

    # Find the points where the drawdown exceeds the threshold
    drawdown_events = df[df['drawdown'] > threshold].copy()

    # Identify the peak date, trough date, peak price, trough price, and drawdown value
    results = []
    last_peak = None
    for index, row in drawdown_events.iterrows():
        if last_peak is None or row['price'] < drawdown_events.loc[last_peak]['price']:
            # Update the last peak if a new peak is found
            last_peak = index

        if row['drawdown'] <= threshold:
            # If the drawdown goes below the threshold, record the drawdown event
            peak_price = df.loc[:last_peak]['price'].max()
            peak_date = df.loc[:last_peak]['price'].idxmax()
            trough_price = row['price']
            trough_date = index
            drawdown_value = row['drawdown']
            results.append({
                'peak_date': peak_date,
                'trough_date': trough_date,
                'peak_price': peak_price,
                'trough_price': trough_price,
                'drawdown': drawdown_value
            })
            last_peak = None

    return results

# Test data
test_prices = [100, 105, 102, 110, 108, 95, 98, 120, 115, 113, 90, 92, 130, 125, 118]

# Running the function with the test data
drawdowns = detect_drawdown(test_prices, threshold=0.2)
for drawdown in drawdowns:
    print(drawdown)
```

### 說明

1. **數據處理**:
   - 使用 `pandas` 的 DataFrame 將價格列表轉化為表格格式,並添加日期索引。
   - `cummax()` 方法計算從頭到當前行的最大價格,以確定頂點價格。
   - 計算回撤為 `(peak - current price) / peak`。

2. **識別回撤事件**:
   - 遍歷所有超過閾值的回撤事件,找出頂點和谷底。
   - 當回撤值小於或等於閾值時,記錄該回撤事件。

3. **測試數據**:
   - 提供一組測試數據以展示函式的使用。

該函式將返回所有符合條件的回撤事件,包括其開始和結束日期、頂點價格和谷底價格,以及回撤值。

雖然有產出程式碼且可執行, 但執行後完全無法輸出任何結果, 程式存在邏輯上的根本矛盾 (死邏輯), Gemini 的審查結果摘要如下 :

此程式存在致命且荒謬的互斥邏輯(Dead Code),迴圈中的條件式 if row['drawdown'] <= threshold 永遠不會成真,因為一個數值不可能同時「大於 0.2」又「小於等於 0.2」,導致輸出永遠是空串列 []。其次,它誤把谷底(Trough)當成高點(Peak),它把比當前價格更低(row['price'] < ...)的點指派給 last_peak,變成一路找更低的價格,找出來的明明是波段谷底(Trough),它卻將變數命名為 last_peak,再次印證此模型完全沒有「起跌高點」與「回撤谷底」的語意心智模型。最後是空值的處理存在邊界隱患,題目要求「處理空資料」,但如果使用者傳入 None,len(None) 會直接噴出 TypeError。

總評 : Phi 4 輸出的程式程式結構最工整, 排版乾淨, 註解完整, 有自建測試資料, 甚至知道利用 cummax() 來算高點, 也可執行不噴錯, 但寫出的卻是百分之百的死代碼. 主要是它在進入狀態控制與條件判斷時喪失了對集合交集的基本感知. 綜合前面對 Qwen-2.5 Coding, 與 Gemma4 的測試結果可知, 14B 級別的中小型模型在處理包含狀態轉移, 波段切分, 極值關聯的演算法時, 尚無法獨立完成具備嚴密邏輯的完整程式碼交付. 

截至目前為止的測試讓我覺得 14B 模型似乎無法勝任需要複雜演算法的任務, 我詢問 Gemini 是否有方法可讓 14B 模型產出正確可用的程式碼, 答案是肯定的 : 

要讓 14B 等級的小模型寫出 100% 正確的複雜時序演算法,關鍵在於「剝奪它的架構決策權」。小模型之所以會崩潰,是因為「自由發揮」時它必須同時處理金融語意、狀態機邏輯、邊界保護與 pandas 索引。解決方案是採用「演算法虛擬碼+向量化骨架約束+禁令清單(Negative Constraints)」的三位一體提示詞工程。

它建議將提示詞修改為 : 

你是一位嚴謹的量化工程師。請為我實作 Python 函式 `detect_drawdown(prices, threshold=0.2)`。

【演算法指定邏輯(必須嚴格依此實作,不得自行發明迴圈或自創狀態機)】:
1. 防禦性檢查:
   - 檢查 `prices is None` 或為空,若是則直接回傳符合欄位定義的空 DataFrame。
   - 轉為 pd.Series,執行 dropna()。
2. 向量化波段分組(Episode Segmentation):
   - 計算歷史累積最高價:`cummax = prices.cummax()`
   - 計算回撤比例(負值):`drawdown = (prices - cummax) / cummax`
   - 利用是否創歷史新高建立波段編號:`episodes = (prices == cummax).cumsum()`
3. 波段聚合計算(GroupBy):
   - 對 `prices.groupby(episodes)` 進行分組迭代:
     * 該組第 1 個點(`group.index[0]`, `group.iloc[0]`)必定是該波段的歷史最高點(peak_date, peak_price)。
     * 取出該組對應的 `drawdown` 子序列,找出最小回撤值 `min_dd = group_dd.min()`。
     * 若 `min_dd <= -threshold`(跌破門檻):
       - 谷底日期 `trough_date` 為 `group_dd.idxmin()`。
       - 谷底價格 `trough_price` 為 `group.loc[trough_date]`。
       - 記錄 peak_date, trough_date, peak_price, trough_price, drawdown (取 min_dd 浮點數)。
4. 回傳格式:
   - 回傳包含上述 5 個欄位的 pandas DataFrame。

【嚴格禁令(違反即視為失敗)】:
- 嚴禁使用 `iterrows()`。
- 嚴禁使用 `drawdown.iloc[peak+1:].idxmin()` 這種可能切出空陣列的切片。
- 嚴禁在已經過濾門檻的資料集內部再去比對相反的條件。
- 不要輸出 LaTeX 數學符號,只回傳純 Python 代碼與一組可直接運行的測試範例。

輸出結果為 : 

以下是根據你提供的演算法指定邏輯,實現的 `detect_drawdown` 函式。這個函式會檢查輸入的價格序列,計
算歷史最高價,找出回撤區段並檢查是否超過指定閾值。最後,它會以 DataFrame 形式返回符合條件的回撤事
件。

```python
import pandas as pd

def detect_drawdown(prices, threshold=0.2):
    # Step 1: Defensive checks
    if prices is None or len(prices) == 0:
        return pd.DataFrame(columns=['peak_date', 'trough_date', 'peak_price', 'trough_price',
'drawdown'])

    # Convert to pd.Series and drop NaNs
    prices = pd.Series(prices).dropna()

    # Step 2: Episode Segmentation
    cummax = prices.cummax()
    drawdown = (prices - cummax) / cummax
    episodes = (prices == cummax).cumsum()

    # Step 3: GroupBy to calculate episodes
    results = []

    # Group by episodes
    for episode, group in prices.groupby(episodes):
        group_dd = drawdown.loc[group.index]

        # Calculate peak and trough for the episode
        peak_date = group.index[0]
        peak_price = group.iloc[0]
        min_dd = group_dd.min()

        if min_dd <= -threshold:
            trough_date = group_dd.idxmin()
            trough_price = group.loc[trough_date]
            drawdown_value = min_dd

            # Record the result
            results.append({
                'peak_date': peak_date,
                'trough_date': trough_date,
                'peak_price': peak_price,
                'trough_price': trough_price,
                'drawdown': drawdown_value
            })

    # Step 4: Return as DataFrame
    return pd.DataFrame(results)

# Test data
test_prices = [100, 105, 102, 110, 108, 95, 98, 120, 115, 113, 90, 92, 130, 125, 118]

# Running the function with the test data
drawdowns = detect_drawdown(test_prices, threshold=0.2)
print(drawdowns)
```

### 測試結果說明

這段程式碼根據你的要求,將會處理價格序列,並計算出從歷史高點回撤超過閾值的事件。測試數據
`test_prices` 可以幫助檢查是否正確地識別和回報了這些回撤事件。

- **防禦性檢查**:確保輸入有效,並移除任何可能的 `NaN` 值。
- **波段分組**:使用歷史最高價和波段編號來分組和計算回撤。
- **波段聚合計算**:為每個波段計算回撤,並檢查是否超過閾值。
- **結果回傳**:以 DataFrame 的形式返回所有符合條件的回撤事件。

這樣的實現確保了每一步都遵循你的演算法指定邏輯,而且不使用被禁止的函式或操作。

經 Gemini 審查, 上面的程式碼完全正確且實測執行結果精準無誤, 這份輸出展現了當 14B 模型被鎖定架構與虛擬碼後所能達到的最高執行品質ㄝ, 可見當提示詞將演算法複雜度降解為語法翻譯題時, 14B 模型的高速代碼生成能力才能真正轉化為可靠的生產力. 

這表示在 RTX 3060 本機運行的 14B 模型時, 如果讓模型獨立構思程式架構, 容易在邊界條件, 時序狀態機與索引對齊上踩坑. 但只要架構師在 Prompt 中給定精確的狀態劃分公式 (例如 cumsum() 函式) 與負面約束, 它也能寫出完全不輸頂級雲端模型的乾淨且 100% 正確的程式碼.