2026年10月20日 星期二

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

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

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


這兩周有來跟我討論行程, 做了一些變更, 本周五他就要啟程前往岡山, 今天晚上再次啟動 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 次對話之評估與修訂紀錄)


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




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

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