2026年10月18日 星期日

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% 正確的程式碼. 

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

做完 Qwen 2.5 Coding 模型測試後, 本篇回頭給 Gemma 4 做進階的推理與程式測試. 本系列全部測試文章索引參考 :


先載入執行 Gemma 4 模型 :

C:\Users\tony1>ollama run gemma4     
>>> 


1. 進階推理能力測試 : 

同樣是用 3 個步驟從 100 顆球中找出較輕的異常球的推理策略 :

>>>  有 100 顆球,其中 1 顆重量不同,但不知道它比較重還是比較輕。只有一個沒有砝碼的天平,最多可以秤 3
...  次。請找出異常球,並判斷它是比較重還是比較輕。請完整說明策略。
Thinking...
Here's a thinking process that leads to the suggested solution:

1.  **Understand the Goal:** I have 100 balls, one is odd (either heavier or lighter than the
rest). I have a balance scale and a maximum of 3 weighings. I need to identify the odd ball
*and* determine if it is heavy or light.

2.  **Analyze the Constraints (The Power of 3 Weighings):**
    *   A balance scale has three outcomes: Left side is heavier (>), Right side is heavier
(<), or Equal (=).
    *   With $W$ weighings, the maximum number of outcomes is $3^W$.
    *   For 3 weighings, I can distinguish $3^3 = 27$ possible outcomes.
    *   In this problem, I don't just need to identify the ball; I also need to know *if* it's
heavy or light. If there are $N$ balls, there are $2N$ possibilities (Ball 1 Heavy, Ball 1
Light, Ball 2 Heavy, Ball 2 Light, ..., Ball N Heavy, Ball N Light).
    *   If $N=100$, there are $200$ possibilities.
    *   Since $3^3 = 27$, it is impossible to solve this problem for 100 balls in 3 weighings.

3.  **Re-evaluate the Problem Statement/Common Knowledge:**
    *   This is a classic puzzle, usually solved with 12 balls (or 13, sometimes).
    *   *If the problem insists on 100 balls and 3 weighings,* then the problem is
mathematically impossible *unless* there is hidden information (e.g., we know it's heavier, or
we only need to identify the ball, but not the deviation).
    *   *Assumption Check:* Is it possible the question meant 10 balls, 27 balls, or 24 balls?
    *   *Crucial Insight:* I must assume the user is asking the standard version of this
puzzle, and I must address the impossibility first.

4.  **Structuring the Answer (Addressing the Impossibility):**
    *   **Step 1: State the mathematical limit.** (Explain why 100 balls is too many for 3
weighings).
    *   **Step 2: Propose a solution for the maximum possible number of balls.** (The standard
answer is $N \le 3^W / 2$, or more precisely, $N \le (3^W - 3) / 2$ for the $N$ heavy/light
case, but generally, $N=12$ is the commonly cited limit).
    *   **Step 3: Solve the problem using the maximum *feasible* amount of balls (N=12) as an
example strategy.** (This shows the methodology the user is looking for).

5.  **Developing the Strategy (Using N=12 as the example):**
    *   Let $N=12$.
    *   Weighing 1: Divide into groups (4, 4, 4). Weigh Group A vs Group B.
        *   Case 1: A = B (Equal). Odd ball is in Group C (the 4 unweighed).
        *   Case 2: A > B (A is heavier). Odd ball is in A (heavy) or B (light) or C
(anything).
        *   Case 3: A < B (A is lighter). Odd ball is in A (light) or B (heavy) or C
(anything).
    *   *Refining the Strategy (Focusing on the N/3 split):*
        *   Divide $N$ into three groups: $G_1, G_2, G_3$.
        *   Weigh $G_1$ vs $G_2$.
        *   If $G_1 = G_2$: The odd ball is in $G_3$. (We know $G_1$ and $G_2$ are standard.)
        *   If $G_1 > G_2$: The odd ball is in $G_1$ (heavy) or $G_2$ (light).
        *   If $G_1 < G_2$: The odd ball is in $G_1$ (light) or $G_2$ (heavy).

6.  **Final Polish (Translating to Professional Chinese):** Ensure the explanation is clear,
methodical, and strongly addresses the initial impossible premise while providing the correct
puzzle methodology. (Self-Correction: Make sure to emphasize that the $N=12$ strategy is the
model, not a solution for $N=100$).
...done thinking.

這是一個著名的邏輯謎題。

**首先,我必須提醒您:根據數學原理,用天平最多秤 3 次,您無法確定 100 顆球中哪一顆是異常球,也無
法判斷它是比較重還是比較輕。**

### 數學原理說明:

*   天平每次稱重,有三種可能結果:左邊重 ($>$), 右邊重 ($<$), 或兩邊一樣 (=)。
*   如果最多秤 $W$ 次,則最大的結果組合數量是 $3^W$。
*   在您這個問題中,每顆球 $N$ 顆,異常球的可能情況總共有 $2N$ 種(因為每顆球都有「比較重」或「比
較輕」這兩種可能性)。
*   對於 $W=3$ 次秤重,您最多可以區分的可能性數量是 $3^3 = 27$ 種結果。
*   如果 $N=100$,則總共有 $2 \times 100 = 200$ 種可能性。
*   因為 $200 > 27$,所以用 3 次天平無法解決 100 顆球的問題。

---

### 解決此類謎題的標準策略(以 $N=12$ 顆球為例):

這個謎題在實際的競賽或邏輯測試中,通常設定的最大球數是 $N=12$ 顆。以下我將完整說明以 **12 顆球
**為例的解題步驟,因為這才能展現出標準的邏輯策略。

假設我們有 $N$ 顆球,將它們平均分為三組:$G_1, G_2, G_3$。

#### 步驟一:第一次秤重 (W1)

*   **操作:** 將 $G_1$ 稱在左邊, $G_2$ 稱在右邊。
*   **樣本:** 假設我們有 12 顆球,每組取 4 顆:$G_1$ vs $G_2$。
*   **可能結果:**

**A. 結果:天平左右兩邊平衡 ($G_1 = G_2$)**
*   **判斷:** 這說明 $G_1$ 和 $G_2$ 的 8 顆球都是正常的。
*   **異常球所在:** 異常球必然在未參與稱重的 $G_3$ 組 (4 顆球中)。
*   **剩餘可能性:** 我們知道 $G_1$ 和 $G_2$ 的 8 顆球是標準的(設為標準球 $S$)。我們現在只有
$G_3$ 中的 4 顆球需要測試。

**B. 結果:左邊較重 ($G_1 > G_2$)**
*   **判斷:** 異常球必定在 $G_1$ 組(是重球)或 $G_2$ 組(是輕球)。
*   **已知資訊:** $G_3$ 的 4 顆球是正常的 $S$ 球。

**C. 結果:左邊較輕 ($G_1 < G_2$)**
*   **判斷:** 異常球必定在 $G_1$ 組(是輕球)或 $G_2$ 組(是重球)。
*   **已知資訊:** $G_3$ 的 4 顆球是正常的 $S$ 球。

---

#### 步驟二:第二次秤重 (W2)

我們根據第一次秤重得到的結果,選擇最複雜的情況(例如結果 B:$G_1 > G_2$)進行說明。

**假設:我們處於情況 B ($G_1 > G_2$)。**
*   $G_1$ (4 顆):可能是重球。
*   $G_2$ (4 顆):可能是輕球。
*   $G_3$ (4 顆):確定是標準球 $S$。
*   **操作:** 重新排列,從 $G_1$ 中取 3 顆球 ($G_{1A}$),從 $G_2$ 中取 3 顆球 ($G_{2A}$)。我們將
 $G_{1A}$ 稱在左邊, $G_{2A}$ 稱在右邊。
*   **比較:** 左邊 ($G_{1A}$) vs 右邊 ($G_{2A}$)。

**B1. 結果:天平左右兩邊平衡 ($G_{1A} = G_{2A}$)**
*   **判斷:** $G_{1A}$ 和 $G_{2A}$ 的 6 顆球都是標準的。
*   **異常球所在:** 異常球必定在:
    1.  $G_1$ 中剩餘的 1 顆未測試的球 ($G_{1B}$),它可能是重球。
    2.  $G_2$ 中剩餘的 1 顆未測試的球 ($G_{2B}$),它可能是輕球。
    3.  $G_3$ 中未測試的 4 顆球 (我們必須重新考慮,這裡的分類需要更精確,最佳做法是:將 $G_1$ 中
剩下的 $G_{1B}$ 拿出來,與 $G_3$ 中任意 1 顆標準球 $S$ 進行比較)。
*   **最佳優化策略:** 將 $G_{1A}$(3顆)、$G_{2A}$(3顆)和 $G_3$ 中 1 顆球 $S$ 分別分成三組:
A(3), B(3), C(1)。
    *   W2:A vs B。

**我們採用更標準且精確的 $N=12$ 策略來接續:**

*   將 $G_1$ (4顆) 拆成 $G_{1A}$ (2顆) 和 $G_{1B}$ (2顆)。
*   將 $G_2$ (4顆) 拆成 $G_{2A}$ (2顆) 和 $G_{2B}$ (2顆)。
*   W2:左邊放 $G_{1A}$ + $G_{2A}$ (共 4 顆),右邊放 $G_{1B}$ + $S_2$ (共 4 顆,其中 $S_2$ 是
$G_3$ 中的標準球)。

**這是極其複雜且容易出錯的步驟,重點是展示邏輯結構。我們以最簡潔的思路說明剩餘的判斷:**

---

#### 步驟三:第三次秤重 (W3)

*   **目標:** 利用剩餘的幾顆球,結合第二次的資訊,確定異常球並判斷其狀態。
*   **執行:** 根據 W2 的結果,將剩餘的幾顆嫌疑球(例如剩餘的 2-3 顆球)再次進行比較。

### 結論與總結(關鍵心法)

請記住,這個謎題的核心不在於步驟的數目,而在於「資訊量」的遞進:

1.  **第一階段 (W1):** 篩選出一個高度嫌疑的子集 (例如從 12 顆縮減到 4 顆)。
2.  **第二階段 (W2):** 根據子集和標準球,進一步減少嫌疑範圍。
3.  **第三階段 (W3):** 最終區分出異常球,並根據此時的比較方向判斷它究竟是重還是輕。

**總結來說,雖然 100 顆球在數學上無法解決,但只要將球數降到 $\mathbf{N \le 12}$,就能用三次天平成功找出異常球並判斷它的重量狀態。**

同樣將回應交給 Gemini 審查, 結果摘要如下 : 

前半段的「理論極限判斷」完全正確且非常敏銳;但後半段嘗試給出「12 顆球具體解法」時,演算法細節徹底崩潰,出現了典型的「邏輯斷頭」。整體評估呈現「元認知(Meta-reasoning)及格,但細節推導與分支窮舉能力不足」的特徵。

Gemma 4 在思考鏈與第一段回答中展現了具備 "前提檢驗" 的能力,資訊論的下界計算精準,指出每次秤量只有 3 種結果(左傾、右傾、平衡),3 次秤量最多產生 3^3 = 27 種狀態。100 顆球且不知輕重共有 100×2=200 種可能,因 200>27 得出「三次秤量找出異常球在數學上絕對不可能」的結論。

但是後半段它主動轉移到「演示 12 顆球的標準解法」時,暴露出模型在維護「多變數狀態樹(State Tree)」時的混亂。第二次秤量(W2)邏輯完全錯亂,而且它在思考中發現自己的分組有漏洞時出現自暴自棄與「幻覺切換」現象,最後直接放棄推導,完全沒有給出一個具體可執行的 12 球策略。

可見 Gemma 4 在邊界認知(Knowing what it doesn't know)與數學常識檢驗上明顯強過 qwen2.5-coder,不會輕易被使用者設定的「不可能前提」牽著鼻子走;但在需要構建嚴密決策樹(Decision Tree)的分支演算法上,顯然本機端中小型模型依然普遍缺乏長步長(Long-horizon)的推導定力。


2. 進階程式能力測試 : 

用下列提示詞叫 Gemma 寫一個金融相關的 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
... - 處理空資料
... - 提供可直接執行的完整程式
... - 附一組測試資料
Thinking...
Here's a thinking process to arrive at the suggested Python solution:

1.  **Understand the Goal:** The request is to write a Python function `detect_drawdown(prices,
threshold=0.2)` that takes a list of daily closing prices and identifies all periods where the
price drops significantly from a historical peak (a "drawdown"). The function must use `pandas`
and return a structured result containing key metrics for each drawdown period.

2.  **Analyze the Input/Output:**
    *   **Input:** `prices` (list or pandas Series of daily closing prices), `threshold`
(float, default 0.2).
    *   **Output:** A structured format (e.g., list of dictionaries or a pandas DataFrame) for
each detected drawdown, including:
        *   `peak date`
        *   `trough date`
        *   `peak price`
        *   `trough price`
        *   `drawdown` (the percentage drop).

3.  **Core Logic - How to Calculate Drawdown:**
    *   Drawdown (DD) at any point $t$ is calculated relative to the historical maximum price
up to $t$.
    *   Formula: $\text{Drawdown}_t = \frac{\text{Peak Price} - \text{Current
Price}}{\text{Peak Price}}$
    *   In pandas terms, we need a Running Maximum (Cumulative Max).

4.  **Step-by-Step Implementation using Pandas:**

    *   **Data Preparation:** Convert the input list `prices` into a pandas Series, preferably
indexed by dates (or sequential numbers if dates are not provided).
    *   **Calculate Running Peak:** Use `Series.cummax()` to find the historical high for every
day.
    *   **Calculate Drawdown:** Calculate the percentage drawdown using the formula derived
above.
    *   **Identify Drawdown Periods:** A drawdown *event* starts when the current price drops
significantly from the running peak, and ends when the price recovers back to (or close to) the
running peak (or when the drawdown drops below the threshold).

5.  **Refining the Detection Logic (The Hard Part):**
    *   We don't just want the drawdown percentage; we want the *period* (peak date to trough
date).
    *   The detection needs to identify consecutive drops that exceed the threshold, followed
by the lowest point (the trough).
    *   *Strategy:*
        1. Calculate the Drawdown percentage (`dd`).
        2. Flag potential drawdowns: A drawdown exists when `dd` exceeds the `threshold`.
        3. We need to find the

結果竟然沒有輸出任何程式碼! 詢問 Gemini 原因, 原來是模型在思考鏈中直接把生成的 Token 配額吃滿了 (觸發 Context/Output Token Limit ), 導致它還在構思階段就被 Ollama 強制截斷. 因為模型在構思這段演算法時遇到了如何在 pandas 裡定義 "波段的起點與谷底" 的問題, 它在思考鏈中反覆在逐行迭代與向量化分組 (Vectorized Filtering) 之間糾結, 導致思考內容過度膨脹, 最終被 Ollama 截斷. 

我用下列指令放寬 Ollama 的輸出長度 (num_predict) 還是一樣沒有輸出程式碼 : 

>>> /set parameter num_predict 8192   

看來 Gemma4 雖然具有多模態能力, 但程式能力受限於思考鍊會塞爆 token 配額, 顯然是不可用. 

UltraViewer 遠端桌面在 CMD 與 PS 視窗無法反白複製問題

今天在鄉下用 Ultraviewer 連線高雄家的 MSI 主機測試 qwen2.5-coder:14b 模型時, 發現不管是 CMD 還是 PS 視窗都無法用滑鼠拖曳反白方式複製視窗內容, 但改用 Anydesk 就可以, 我詢問 ChatGPT 可能原因, 它認為是 Ultarviewer 與目前 CMD/PS 所使用的 Windows Terminal 終端機主機在相容性上出問題所致, Windows 傳統上使用的預設終端機是 Windows 主控台主機 (Windows Console Host), 但在 Windows 11 22H2 更新後, Windows Terminal 就成為預設的終端機主機, 亮點之一是它可以在視窗中開多個終端機分頁. 

可以在 CMD/PS 視窗標題列按滑鼠右鍵, 點選 "設定" 來檢視 : 




在 "啟動/預設終端應用程式" 右邊預設是 "讓 Windows 決定" :




點開選單會出現三個選項 (預設是 "讓 Windows 決定", 它應該會選擇使用比較新的 Windows 終端機) : 




如果將其改為第二個選項 "Windows 主控台主機" 後儲存設定, 再次從遠端用 Ultraviewer 連線桌面, 就可以順利在 CMD/PS 視窗用滑鼠反白後按 Ctrl+C 複製視窗內容了 :




以上測試證明這是 Ultraviewer 本身與 Windows Terminal 之間的相容性問題, 新版 Ultraviewer 或許已解決此問題, 但我懶得更新版本, 改用 Anydesk 即可. 

如果要改回 "讓 Windows 決定", 在 CMD/PS 視窗標題列按滑鼠右鍵, 點選 "內容" : 



然後切到 "終端機" 頁籤, 將右下角的 "預設終端應用程式" 改為 "讓 Windows 決定" 按 "確定" 即可 : 




Windows 預設會選用 Windows 終端機. 

Ollama 學習筆記 : Qwen2.5-Coder 模型測試 (qwen2.5-coder:14b)

最近偶然得知有一個開源的 OpenCode 程式代理, 可以搭配 Ollama 在本地打造類似 Claude Code 的 AI Coding Agent 功能, 看搭配甚麼模型, 據估計效能大約可達 Claude Code 的 65%~85% 功力 (真的嗎 ?), 足敷中小型專案使用, 參考 : 


本系列全部測試文章索引參考 :
適合在我的 RTX3060 12GB VRAM 顯卡上跑的開源 Coding 模型是 Qwen2.5-Coder-14B, 其大小為 9GB, 可以完全載入顯卡內, 12GB VRAM 扣除 9 GB 模型後, 還剩下約 2.5~3 GB 空間可供 KV Cache 運算, 這讓 Ollama 可以穩穩開出 16k~32k 的 num_ctx, 足夠讓 OpenCode 一次讀取數個 Python 模組並產出完整的 patch, 而不會引發 CUDA OOM 當機, 這剛好位於硬體極限, 上下文長度與 Agent 任務穩定度的最佳平衡點, 推論速度仍能維持在健康的每秒 25~35 Tokens, 足以勝任真實專案開發的推理深度. 

在安裝 OpenCode 之前, 我想先對 Qwen2.5-Coder-14B 模型做個測試, 看看其回應速度與評估其  Coding 能力. 此模型可從 Ollama 網站下載 :




下載 & 載入模型之指令如下 :

ollama run qwen2.5-coder:14b  

注意, 後面指定 :14b 是必要的, 如上面 Qwen 2.5 Coder 模型列表所示, 如果沒有指定模型大小, 預設會下載 latest 的 7b 模型. 

C:\Users\tony1>ollama run qwen2.5-coder:14b    
pulling manifest
pulling ac9bc7a69dab: 100% ▕██████████████████ ▏ 9.0 GB/9.0 GB   40 MB/s      0s
pulling 0578f229f23a: 100% ▕██████████████████▏  488 B
verifying sha256 digest
writing manifest
success


1. 基本測試 : 

先做一些基本的測試 : 

>>> 你是誰?   
我是Qwen,由阿里云开发的超大规模语言模型。我被设计用来回答各种问题、提供信息和与用户进行对话。有什么我可以帮助你的吗
?
>>> 請一律使用繁體中文與我交談
好的,請問有什麼我可以幫助您的嗎?

>>> 請簡介量子糾纏
量子糾纏是量子力學中一個非常有趣且重要的現象,它描述了兩個或多個量子粒子之間的特殊關聯,使得這些
粒子的狀態無法分開來描述。即使這些粒子相隔很遠,一個粒子的狀態改變會立即影響到另一個粒子的狀態,
這種影響是即時的,不受距離的限制。

量子糾纏是量子通信、量子計算和量子機器學習等領域中研究的基礎。例如,量子密鑰分發利用量子糾纏來實
現安全的密鑰分發,即使在第三方試圖竊取密鑰的情況下,也能夠被檢測到。此外,量子計算機也利用量子糾
纏來實現並行計算,從而解決傳統計算機無法高效處理的問題。

量子糾纏的現象和應用是量子力學研究的熱門領域,對於未來的科技發展具有重要的意義。

測試數理邏輯 : 

>>> 3.10 與 3.8 這兩個數值哪一個比較大?   
3.10 比 3.8 大。

顯然是不經大腦的反射式答案, 請它細思量 : 

>>> 請思考推理一下再回答
當然,我們可以一步一步地來比較這兩個數值:

1. 首先比較整數部分:3.10 和 3.8 的整數部分都是 3,所以需要進一步比較小數部分。
2. 接著比較小數部分的第一位:3.10 的小數部分第一位是 1,而 3.8 的小數部分第一位是 8。
3. 顯然,1 小於 8,因此 3.10 小於 3.8。

所以,3.8 比 3.10 大。

這下就正確了.  


2. 基本程式能力測試 : 

接下來測試基本的程式能力 : 

>>> 請用 Python 寫一個基本的 GUI 四則運算計算器程式
當然可以!下面是一個使用 Python 和 Tkinter 庫編寫的基本四則運算計算器程式的範例:

```python
import tkinter as tk

class Calculator:
    def __init__(self, root):
        self.root = root
        self.root.title("Calculator")

        self.display = tk.Entry(root, width=40, borderwidth=5)
        self.display.grid(row=0, column=0, columnspan=4, padx=10, pady=10)

        buttons = [
            ('7', 1, 0), ('8', 1, 1), ('9', 1, 2), ('/', 1, 3),
            ('4', 2, 0), ('5', 2, 1), ('6', 2, 2), ('*', 2, 3),
            ('1', 3, 0), ('2', 3, 1), ('3', 3, 2), ('-', 3, 3),
            ('0', 4, 0), ('.', 4, 1), ('=', 4, 2), ('+', 4, 3)
        ]

        for (text, row, col) in buttons:
            button = tk.Button(root, text=text, width=10, height=3, command=lambda t=text:
self.click_button(t))
            button.grid(row=row, column=col, sticky="nsew")

        for i in range(4):
            root.grid_columnconfigure(i, weight=1)
            root.grid_rowconfigure(i+1, weight=1)

    def click_button(self, text):
        if text == '=':
            try:
                result = eval(self.display.get())
                self.display.delete(0, tk.END)
                self.display.insert(0, str(result))
            except Exception as e:
                self.display.delete(0, tk.END)
                self.display.insert(0, "Error")
        else:
            self.display.insert(tk.END, text)

if __name__ == "__main__":
    root = tk.Tk()
    calculator = Calculator(root)
    root.mainloop()
```

這個程式碼創建了一個簡單的四則運算計算器,包含數字按鈕、運算符號按鈕和等號按鈕。當用戶點擊按鈕時
,計算器會將輸入的數字和運算符號顯示在輸入框中,當用戶點擊等號時,計算器會計算並顯示結果。如果計
算過程中出現錯誤,計算器會顯示 "Error"。

執行此程式功能正常 : 





叫它加入 C 與 CE 按鈕 : 

>>> 請加上 C 或 CE 清除輸入框的按鈕   
當然可以!我們可以在計算器中添加一個 "C" 或 "CE" 按鈕,用於清除輸入框中的內容。下面是修改後的程式
碼:

```python
import tkinter as tk

class Calculator:
    def __init__(self, root):
        self.root = root
        self.root.title("Calculator")

        self.display = tk.Entry(root, width=40, borderwidth=5)
        self.display.grid(row=0, column=0, columnspan=4, padx=10, pady=10)

        buttons = [
            ('7', 1, 0), ('8', 1, 1), ('9', 1, 2), ('/', 1, 3),
            ('4', 2, 0), ('5', 2, 1), ('6', 2, 2), ('*', 2, 3),
            ('1', 3, 0), ('2', 3, 1), ('3', 3, 2), ('-', 3, 3),
            ('0', 4, 0), ('.', 4, 1), ('=', 4, 2), ('+', 4, 3),
            ('C', 5, 0), ('CE', 5, 1)  # 添加 C 和 CE 按鈕
        ]

        for (text, row, col) in buttons:
            button = tk.Button(root, text=text, width=10, height=3, command=lambda t=text:
self.click_button(t))
            button.grid(row=row, column=col, sticky="nsew")

        for i in range(4):
            root.grid_columnconfigure(i, weight=1)
            root.grid_rowconfigure(i+1, weight=1)

    def click_button(self, text):
        if text == '=':
            try:
                result = eval(self.display.get())
                self.display.delete(0, tk.END)
                self.display.insert(0, str(result))
            except Exception as e:
                self.display.delete(0, tk.END)
                self.display.insert(0, "Error")
        elif text == 'C' or text == 'CE':
            self.display.delete(0, tk.END)  # 清除輸入框中的內容
        else:
            self.display.insert(tk.END, text)

if __name__ == "__main__":
    root = tk.Tk()
    calculator = Calculator(root)
    root.mainloop()
```

現在,計算器中新增了 "C" 和 "CE" 按鈕。點擊 "C" 或 "CE" 按鈕時,計算器會清除輸入框中的內容。

執行結果功能正常 :




再叫它添加一個倒退鈕 : 

>>> 可以再加一個倒退刪除鍵嗎?  
當然可以!我們可以在計算器中添加一個倒退刪除鍵,用於刪除輸入框中的最後一個字符。下面是修改後的程式碼:

```python
import tkinter as tk

class Calculator:
    def __init__(self, root):
        self.root = root
        self.root.title("Calculator")

        self.display = tk.Entry(root, width=40, borderwidth=5)
        self.display.grid(row=0, column=0, columnspan=4, padx=10, pady=10)

        buttons = [
            ('7', 1, 0), ('8', 1, 1), ('9', 1, 2), ('/', 1, 3),
            ('4', 2, 0), ('5', 2, 1), ('6', 2, 2), ('*', 2, 3),
            ('1', 3, 0), ('2', 3, 1), ('3', 3, 2), ('-', 3, 3),
            ('0', 4, 0), ('.', 4, 1), ('=', 4, 2), ('+', 4, 3),
            ('C', 5, 0), ('CE', 5, 1), ('<', 5, 2)  # 添加倒退刪除鍵
        ]

        for (text, row, col) in buttons:
            button = tk.Button(root, text=text, width=10, height=3, command=lambda t=text:
self.click_button(t))
            button.grid(row=row, column=col, sticky="nsew")

        for i in range(4):
            root.grid_columnconfigure(i, weight=1)
            root.grid_rowconfigure(i+1, weight=1)

    def click_button(self, text):
        if text == '=':
            try:
                result = eval(self.display.get())
                self.display.delete(0, tk.END)
                self.display.insert(0, str(result))
            except Exception as e:
                self.display.delete(0, tk.END)
                self.display.insert(0, "Error")
        elif text == 'C':
            self.display.delete(0, tk.END)  # 清除輸入框中的內容
        elif text == 'CE':
            self.display.delete(0, tk.END)  # 清除輸入框中的內容
        elif text == '<':
            current_text = self.display.get()
            if current_text:
                self.display.delete(len(current_text) - 1)  # 刪除最後一個字符
        else:
            self.display.insert(tk.END, text)

if __name__ == "__main__":
    root = tk.Tk()
    calculator = Calculator(root)
    root.mainloop()
```

現在,計算器中新增了倒退刪除鍵("<")。點擊倒退刪除鍵時,計算器會刪除輸入框中的最後一個字符。

結果 OK : 




看來簡單的程式似乎難不倒它, 模型回應速度也很快, 幾乎是秒回. 


3. 進階推理測試 : 

接下來考驗它進階的推理能力 :

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

要解決這個問題,我們可以使用三步驟的策略來找出異常球,並判斷它是比較重還是比較輕。以下是詳細的步
驟:

### 步驟 1: 第一次秤量
1. 將 100 顆球分成三組:33 顆、33 顆和 34 顆。
2. 將兩組各 33 顆球放到天平的兩端。

#### 情況分析:
- 如果天平平衡,異常球在剩下的 34 顆球中。
- 如果天平不平衡,異常球在天平上較重或較輕的那一端的 33 顆球中。

### 步驟 2: 第二次秤量
1. 如果第一次天平平衡,從剩下的 34 顆球中取出 11 顆球,分成三組:11 顆、11 顆和 12 顆。
2. 將兩組各 11 顆球放到天平的兩端。

#### 情況分析:
- 如果天平平衡,異常球在剩下的 12 顆球中。
- 如果天平不平衡,異常球在天平上較重或較輕的那一端的 11 顆球中。

### 步驟 3: 第三次秤量
1. 如果第二次天平平衡,從剩下的 12 顆球中取出 4 顆球,分成三組:4 顆、4 顆和 4 顆。
2. 將兩組各 4 顆球放到天平的兩端。

#### 情況分析:
- 如果天平平衡,異常球在剩下的 4 顆球中。
- 如果天平不平衡,異常球在天平上較重或較輕的那一端的 4 顆球中。

### 步驟 4: 第四步(可選)
1. 如果第三次天平平衡,從剩下的 4 顆球中取出 2 顆球,分成三組:2 顆、2 顆和 0 顆。
2. 將兩組各 2 顆球放到天平的兩端。

#### 情況分析:
- 如果天平平衡,異常球在剩下的 2 顆球中。
- 如果天平不平衡,異常球在天平上較重或較輕的那一端的 2 顆球中。

### 步驟 5: 確定異常球
1. 如果第三次天平不平衡,從天平上較重或較輕的那一端的 4 顆球中取出 2 顆球,分成三組:1 顆、1 顆和
 2 顆。
2. 將兩組各 1 顆球放到天平的兩端。

#### 情況分析:
- 如果天平平衡,異常球在剩下的 2 顆球中。
- 如果天平不平衡,異常球在天平上較重或較輕的那一端的 1 顆球中。

### 確定異常球的重量
1. 如果在最後一次秤量中,天平平衡,異常球在剩下的 2 顆球中。取出這兩颗球,其中一顆放到天平上,另
一顆作為標準。如果天平平衡,則剩下的球是異常球;如果天平不平衡,則另一顆球是異常球。
2. 如果在最後一次秤量中,天平不平衡,異常球在天平上較重或較輕的那一端的 1 顆球中,即為異常球。

這樣,我們就可以在最多三次秤量內找出異常球,並判斷它是比較重還是比較輕。

可見 qwen2.5-coder:14b 的基本程式能力雖然還不錯, 但它的推理能力不足, 上面的回應完全錯誤, 因為在數學理論上, 100 顆球秤 3 次找出異常球是不可能的 (至少需要 5 次秤重). 參考之前 測試 OpenClaw 時, 對 GPT 5.6 Luna 的推理測試 : 



4. 進階程式能力測試 : 

接下來真正的大魔王來了, 下面是一個金融相關任務 : 

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

好的,下面是一個使用 pandas 的 Python 函式 `detect_drawdown`,它可以找出所有從歷史高點回撤超過指
定閾值的區段,並返回每個回撤的峰值日期、谷值日期、峰值價格、谷值價格和回撤百分比。

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

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'])

    # 轉換為 pandas Series
    prices = pd.Series(prices, name='price')

    # 計算歷史高點
    rolling_max = prices.cummax()

    # 計算回撤百分比
    drawdown = (prices - rolling_max) / rolling_max

    # 找出回撤超過阈值的點
    drawdown_mask = drawdown < -threshold

    # 找出峰值和谷值的索引
    peak_indices = drawdown_mask[drawdown_mask].index
    trough_indices = []

    for peak_idx in peak_indices:
        # 從峰值後面的資料中找出谷值
        trough_idx = drawdown.iloc[peak_idx+1:].idxmin()
        trough_indices.append(trough_idx)

    # 轉換索引為日期
    peak_dates = prices.index[peak_indices]
    trough_dates = prices.index[trough_indices]
    peak_prices = prices.loc[peak_indices]
    trough_prices = prices.loc[trough_indices]
    drawdown_values = drawdown.loc[peak_indices]

    # 創建結果 DataFrame
    result = pd.DataFrame({
        'peak_date': peak_dates,
        'trough_date': trough_dates,
        'peak_price': peak_prices,
        'trough_price': trough_prices,
        'drawdown': drawdown_values
    })

    return result

# 測試資料
prices = [100, 98, 95, 102, 101, 90, 88, 85, 92, 91, 80, 78, 75, 82, 81, 70, 68, 65, 72, 71]
prices = pd.Series(prices, index=pd.date_range(start='2023-01-01', periods=len(prices),
freq='D'))

# 執行函式並打印結果
result = detect_drawdown(prices, threshold=0.2)
print(result)
```

這段程式碼定義了一個 `detect_drawdown` 函式,它接受一個包含每日收盤價的 pandas Series 和一個回撤
閾值(預設為 0.2)。函式會返回一個包含每個回撤區段峰值日期、谷值日期、峰值價格、谷值價格和回撤百
分比的 DataFrame。

### 測試資料
測試資料是一個包含 20 個收盤價的 pandas Series,索引為從 2023-01-01 開始的日期範圍。這個範例中,
有一些從歷史高點回撤超過 20% 的區段。

### 結果
執行 `detect_drawdown(prices, threshold=0.2)` 後,會得到一個包含所有回撤區段的 DataFrame,其中每
一行代表一個回撤區段,包含峰值日期、谷值日期、峰值價格、谷值價格和回撤百分比。

希望這段程式碼能夠幫助你找出所有的回撤區段!

我把程式碼提交給 Gemini 審查, 結果發現模型欠缺高階 Python 程式技巧, 且對金融方面知識不足, 導致程式碼存在致命缺陷無法執行 : 

>>> 你這段程式碼存在嚴重的執行期錯誤(Runtime Bug)與金融邏輯錯誤,無法正常運作。模型對 pandas API 的
... 基礎語法具備基本認識,並寫出了基本的輸入保護與測試資料架構,但在金融指標定義、pandas 索引定位與區
... 段分割(Segmentation)上出現了致命缺陷。
... 1. 執行期報錯:idxmin() 在全空切片上引發例外
... 2. 定義混淆:誤把「回撤超標點」當成「歷史高點 Peak」
... 3. 缺乏「區段聚合(Grouping)」:重複輸出點而非區段
... 請檢查問題在哪裡, 並提出新版程式碼.

回應如下 : 

非常感謝你的指正,你提到的問題非常關鍵。讓我們一步步來解決這些問題:

1. **執行期報錯**:`idxmin()` 在全空切片上引發例外。
2. **定義混淆**:誤把「回撤超標點」當成「歷史高點 Peak」。
3. **缺乏「區段聚合(Grouping)」**:重複輸出點而非區段。

我們將修正這些問題,並提供一個新的版本。

### 步驟 1: 確保輸入無空值
首先,我們需要確保輸入的 `prices` 無空值,因為 `cummax` 和 `idxmin` 在空值上會引發問題。

### 步驟 2: 正確識別峰值和谷值
峰值應該是從當前日期之前的歷史高點,而谷值是回撤超過閾值後的最低點。

### 步驟 3: 區段聚合
我們需要確保只返回每個回撤區段的起始和結束點,而不是每個點。

### 步驟 4: 處理空資料
如果 `prices` 為空,則返回一個空的 DataFrame。

### 步驟 5: 修正執行期錯誤
確保在計算峰值和谷值時,不會在空切片上調用 `idxmin`。

### 新版程式碼

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

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 = prices.dropna()

    if len(prices) == 0:
        return pd.DataFrame(columns=['peak_date', 'trough_date', 'peak_price', 'trough_price',
'drawdown'])

    # 計算歷史高點
    rolling_max = prices.cummax()

    # 計算回撤百分比
    drawdown = (prices - rolling_max) / rolling_max

    # 找出回撤超過閾值的點
    drawdown_mask = drawdown < -threshold

    # 找出峰值和谷值的索引
    peak_indices = []
    trough_indices = []

    # 記錄上一個峰值的索引
    last_peak_idx = None

    for idx in range(len(prices)):
        if drawdown_mask[idx]:
            if last_peak_idx is None or prices[idx] > prices[last_peak_idx]:
                last_peak_idx = idx
        else:
            if last_peak_idx is not None:
                # 找出谷值
                trough_idx = drawdown.iloc[last_peak_idx+1:idx+1].idxmin()
                peak_indices.append(last_peak_idx)
                trough_indices.append(trough_idx)
                last_peak_idx = None

    # 轉換索引為日期
    peak_dates = prices.index[peak_indices]
    trough_dates = prices.index[trough_indices]
    peak_prices = prices.loc[peak_indices]
    trough_prices = prices.loc[trough_indices]
    drawdown_values = drawdown.loc[peak_indices]

    # 創建結果 DataFrame
    result = pd.DataFrame({
        'peak_date': peak_dates,
        'trough_date': trough_dates,
        'peak_price': peak_prices,
        'trough_price': trough_prices,
        'drawdown': drawdown_values
    })

    return result

# 測試資料
prices = [100, 98, 95, 102, 101, 90, 88, 85, 92, 91, 80, 78, 75, 82, 81, 70, 68, 65, 72, 71]
prices = pd.Series(prices, index=pd.date_range(start='2023-01-01', periods=len(prices),
freq='D'))

# 執行函式並打印結果
result = detect_drawdown(prices, threshold=0.2)
print(result)
```

### 說明
1. **移除空值**:`prices = prices.dropna()` 確保在計算時沒有空值。
2. **峰值和谷值的識別**:使用一個迴圈來遍歷 `prices`,並記錄峰值和谷值的索引。
3. **區段聚合**:確保只返回每個回撤區段的起始和結束點。
4. **執行期錯誤處理**:確保在計算峰值和谷值時,不會在空切片上調用 `idxmin`。

### 測試結果
執行新版程式碼後,應該能夠正確找出所有的回撤區段,並返回每個回撤區段的起始和結束點。

希望這段新版程式碼能夠解決你的問題!

再次審查結果更糟, 不僅沒有改對, 反而引入了更離譜的邏輯與執行時期錯誤. 

總評如下 :

qwen2.5-coder:14b 在這種時序狀態機 (State Machine)/區間聚合 (Segmentation) 的邏輯上看似有反思能力, 能完美覆述所指出的問題且講得頭頭是道, 但缺乏真正的程式模擬運算能力, 它在 prompt 裡承諾會修好, 但在生成程式碼時只是把原來的 for...in 換成了 for idx in range(...) 的傳統 C 語言思維, 既沒搞懂 pandas 的標籤/位置索引差異, 也沒有在腦中虛擬執行過變數生命週期. 它並沒有改對, 只是從寫法 A 的錯誤換成了更複雜但依然錯得離譜的寫法 B 而已. 

總之, qwen2.5-coder:14b 模型大概只能應付小規模單檔重構, 單元測試補全, Docstring, 與純語法翻譯而已, 其內部注意力機制無法維持長鏈條的變數生命週期, 欠缺領域語意 (Domain Semantics) 能力, 很容易寫出語法合法但邏輯顛倒的程式碼 (例如在暴跌日找 Peak). 唉, 看來 qwen2.5-coder:14b 只能應付學校的作業而已. 


2026-10-17 補充 :

我使用 Gemini 建議的高約束 Prompt (明確虛擬碼 + 向量化約束 + 禁令清單) :

你是一位嚴謹的量化工程師。請為我實作 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 代碼與一組可直接運行的測試範例。

得到如下 100% 正確的程式碼 : 

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'])

    # 轉為 pd.Series,執行 dropna()
    prices = pd.Series(prices).dropna()

    # 向量化波段分組(Episode Segmentation)
    cummax = prices.cummax()
    drawdown = (prices - cummax) / cummax
    episodes = (prices == cummax).cumsum()

    # 波段聚合計算(GroupBy)
    results = []
    for name, group in prices.groupby(episodes):
        peak_date = group.index[0]
        peak_price = group.iloc[0]
        group_dd = drawdown[group.index]
        min_dd = group_dd.min()

        if min_dd <= -threshold:
            trough_date = group_dd.idxmin()
            trough_price = group.loc[trough_date]
            results.append([peak_date, trough_date, peak_price, trough_price, min_dd])

    # 回傳格式
    return pd.DataFrame(results, columns=['peak_date', 'trough_date', 'peak_price',
'trough_price', 'drawdown'])

# 測試範例
if __name__ == "__main__":
    prices = [100, 98, 95, 102, 101, 97, 96, 99, 98, 97, 96, 95, 94, 93, 92, 91, 90, 89, 88,
87, 86, 85, 84, 83, 82, 81, 80, 79, 78, 77, 76, 75, 74, 73, 72, 71, 70, 69, 68, 67, 66, 65, 64,
63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 40,
39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16,
15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
    df = detect_drawdown(prices, threshold=0.5)
    print(df)

這段程式碼完全正確, 不僅邏輯嚴謹且能直接通過標準執行與邊界檢驗. 相較於最初由它自由發揮時寫出的 idxmin() 空切片例外與崩潰, 這次 Qwen 2.5 Coder 14B 展現了身為頂級語法執行單元的完整水準. 

但這表示小白無法只靠簡單的任務提示完成專案, 因為撰寫這樣的提示詞需要對 Python 生態系有較深入的了解, 且須具備領域知識. 能寫出這種提示詞的人必然具備如下知識 :
  • 在金融領域上, 回撤波段是用創新高來定義邊界而非任意價格下跌.
  • 在 Pandas 的向量化操作中, is_new_high.cumsum() 是最優雅且無 Bug 的波段分組方式. 
  • idxmin() 在切片為空時會崩潰, Series 在 DatetimeIndex 下用整數下標查找會拋出 KeyError.
這表示本機小模型在自動化工作流中的最佳調用方式是不要讓它自己設計演算法架構, 而是由架構師定義好虛擬碼規格後交由它執行細節實現與語法生成, 這無疑是加重了開發者負擔.