2026年10月20日 星期二

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 參數量級小模型的注意力容量與多步邏輯演繹物理極限. 

沒有留言 :