iT邦幫忙

2026 iThome 鐵人賽

DAY 24
0
Software Development

量化交易入門:從 K 線到可組合的交易策略引擎系列 第 25

Day 24:一次該下多少錢?從固定比例到凱利公式的部位管理

  • 分享至 

  • xImage
  •  

同一個訊號,四種下注方式

昨天處理的是「這張單送不送得出去」。今天處理它前面那個問題:這張單該下多少錢。

Day 19 到 Day 22 的四天回測裡,這個問題有一個沒被檢查過的答案:滿倉。部位是 +1 或 0,也就是「有訊號就把全部資金押上去」。那四天的所有數字都建立在這個預設值上,而它從來沒有被當成一個選項討論過。

今天把它變成一個選項。而選項之間的差距比預期大得多——同一個策略、同一組 232 筆交易,滿倉的總報酬是 −63.54%,固定四分之一倉是 −21.35%。

交易概念補課:部位、槓桿、爆倉

部位(position) 這個詞在 Day 16 出現過,那時候它只有方向。今天它有大小。

大小的單位是總資金的比例:押 25% 表示一萬鎂的帳戶這一筆下 2,500 鎂。用比例而不是金額,是因為比例會跟著帳戶一起變——賺了之後下得多、虧了之後下得少,而後者是它最有價值的性質。

槓桿(leverage) 是把比例推過 100%:借錢下注,押的錢比帳戶裡的多。現貨市場借不到,所以本系列的主線碰不到它;合約市場可以,而它同時放大獲利與虧損。

爆倉(liquidation) 是槓桿的極限:虧損吃掉保證金,交易所強制平倉,剩下的錢歸零。這件事在現貨不會發生,但它的溫和版本在現貨每天都在發生——帳戶跌到一半的時候,多數人已經關掉機器人了,而那跟爆倉在結果上沒有差別。所以今天量的「破產機率」,定義是「權益跌到起始資金的一半以下」,不是「歸零」。

凱利公式(Kelly criterion) 稍後單獨講,它需要先有前面這些詞。

四種下注方式,以及沒有做成積木的那一種

從最單純的往下走。

固定金額:每一筆都下 1,000 鎂。它是加法的——賺錢時不放大、虧錢時不縮小。

沒有被做成一個積木,而理由值得寫出來,因為它是一個誠實的限制而不是遺漏:在權重制的回測裡,「固定金額」會是一條隨著權益反向變化的權重序列(帳戶越大、1,000 鎂佔的比例越小),而那個形狀需要知道當下的權益。當下的權益是路徑相依的東西,而 Day 19 選的是向量化回測——它一次算完整段,沒有「當下」這個概念。要表達固定金額得先有一個逐筆模擬的引擎。

固定比例:每一筆都押 25%。它是乘法的,所以它有一個很好的性質——破產在數學上變得不可能。連虧十次之後帳戶還剩 5.6%,永遠不會歸零。

# quantbot/domain/sizing/fixed_fraction_sizer.py
class FixedFractionSizer:
    """每一筆都押總資金的固定比例。實作 domain 的 PositionSizer。

    它跟「固定金額」的差別是複利:固定金額(每次都下 1000 鎂)在賺錢時不會放大、
    在虧錢時不會縮小,所以它的權益曲線是加法的;固定比例是乘法的,賺了之後下得
    多、虧了之後下得少,而那個自動縮小是它最有價值的性質——**它讓破產在數學上
    變得不可能**(押 25% 的話,連虧十次還剩 5.6%)。
    """

波動率目標:先決定「這一筆最多輸多少」,再用停損距離反推部位。這是 Day 12 那個 ATR 留下來的伏筆:

# quantbot/domain/sizing/volatility_target_sizer.py
    """先決定「這一筆最多輸多少」,再用停損距離反推部位。實作 PositionSizer。

    它是這四個積木裡唯一會隨市場改變下注大小的一個,而它的算式只有一行:

        權重 = 每筆風險預算 ÷ 停損距離(以價格比例表示)

    停損距離用 Day 12 的 ATR 決定,這是那一天留下來的伏筆:ATR 是價格單位的
    波動度量,除以收盤價就變成比例。停損放在 2 個 ATR 之外、每筆願意輸 1% 的話,
    波動小的時候(ATR 是價格的 0.25%)停損距離 0.5%,權重 200%(會被上限夾到
    25%);波動大的時候(ATR 是價格的 2%)停損距離 4%,權重 25%。

    **同一個風險預算,在不同的市況下對應完全不同的部位大小。** 這是「風險平價」
    的雛形,也是它跟固定比例最大的差別:固定比例在暴漲暴跌的那幾天押的錢跟
    在無風無雨的那幾天一樣多。
    """

停損距離用 ATR 而不是固定百分比,是因為固定百分比兩頭都不對:低波動期會被頻繁掃到(價格正常呼吸就碰到停損),高波動期又形同沒有停損。

這個積木有一個必須明講的行為:ATR 算不出來的那幾根不下注。

# quantbot/domain/sizing/volatility_target_sizer.py
    """NEVER 在 ATR 是 NaN 的那幾根硬給一個權重。暖機期的 ATR 沒有定義,
    而「算不出波動就不下注」是唯一安全的選項,用預設值代替會在最沒有資訊的
    時候下最大的注。
    """

ATR 為 0 也算「算不出來」而不是「風險為零」。後者會讓權重變成無限大,而那個除以零在測試裡是一條斷言。

凱利公式,以及它那個我們永遠滿足不了的前提

凱利公式回答的是:知道勝率與賠率的情況下,押多少能讓長期複利成長率最大。

f* = p − (1 − p) / b

p 是勝率,b 是賠率(平均獲利除以平均虧損)。勝率 60%、賠率 2 的話,f* = 0.6 − 0.4 / 2 = 0.4,也就是押四成。

它最大化的是長期複利成長率,不是期望報酬。這個差別是凱利存在的全部理由:最大化期望報酬的答案永遠是「全押」,而全押在有限次數內破產的機率趨近 1。

然後是那個前提:

# quantbot/domain/sizing/fractional_kelly_sizer.py
    """凱利公式的一個分數。實作 domain 的 PositionSizer。

    凱利給的是「最大化長期複利成長率」的下注比例,而它有一個致命的前提:
    **它假設我們知道真實的勝率與賠率。** 我們不知道。手上有的是一段歷史算出來的
    估計值,而那個估計值的誤差在凱利的算式裡會被放大——高估勝率會讓建議的比例
    暴增,而下注過量的懲罰不對稱:押到最佳比例的兩倍,長期成長率會變成負的,
    即使那個策略本身是賺錢的。

    所以實務上一律用**分數凱利**(1/4 到 1/2)。乘上 0.25 之後,就算真實的最佳
    比例只有估計值的一半,我們押的還是低於最佳值——那一側的懲罰溫和得多。
    """

「懲罰不對稱」是分數凱利的整個論證。押不足的代價是成長慢一點;押過量的代價是成長率變成負的。兩邊都不確定的時候,往押不足那一側偏是唯一合理的選擇。

除了乘上一個分數,這個積木還處理兩件事,而它們在實務上比公式本身重要:

樣本不足時退回一個保守的固定比例。 Day 22 那張表上,均值回歸的勝率 53.6% 與賠率 0.64 是 28 筆交易算出來的。28 筆的勝率信賴區間大概有正負 18 個百分點,而那個區間解出來的凱利比例可以從 0 跨到 40%。所以樣本低於門檻時不用凱利。

完全沒有統計時也一樣退回固定比例。 第一次上線的策略手上什麼都沒有,而那正是最需要一個明確行為的時刻。

歷史統計刻意不從回測報告自動生出來:

# quantbot/domain/values/trade_statistics.py
    """一段歷史交易的勝率、賠率與樣本數。凱利公式的三個輸入。

    這個值刻意不從回測報告自動生出來(沒有 from_report 之類的建構子)。
    理由是「哪一段歷史算出來的統計,可以拿去決定未來的下注比例」是一個要人判斷的
    問題——樣本外的、走過完整多空循環的、還是剛好那三個月的?一個方便的建構子會
    讓這個判斷消失。
    """

積木的介面,兩條刻意收很窄的契約

四種下注方式後面是同一個 Protocol,形狀跟 Day 15 的 Feature、Day 16 的 Condition 一樣:

# quantbot/domain/interfaces/position_sizer.py
class PositionSizer(Protocol):
    """一次該下多少:吃一份 SizingContext,回一條與它等長的權重序列。

    契約有兩條,兩條都是刻意收得很窄:

    - **回傳的權重 NEVER 決定方向。** 方向來自引擎(Day 16),積木只決定大小。
      實作照 context.directions 的符號走,一個把方向反過來的積木會讓「策略說做多
      但下注模組決定做空」變成合法的程式碼。
    - **回傳值不必自己遵守風險上限。** 上限由 RiskLimits 在之後統一套用,
      所以積木可以誠實地說出它算出來的數字(凱利算出 3.4 倍就寫 3.4 倍),
      而被夾掉的那一段會出現在報告裡。積木自己夾的話那個資訊就消失了。
    """

第二條有一個具體的好處,而它出現在實測的表上:波動率目標在最極端的那一根要求 310.3% 的權重。如果它自己夾成 100%,報告上只會看到 100%,而「這個積木在某些市況下想押三倍」這個資訊就不見了。被夾掉的比例越高,代表那個積木的估計越不可靠,或者風險預算設得太小。

上限放在積木之上,而且它自己會執行

這是今天最重要的設計決定。部位計算是可插拔的積木,所以它是使用者可以自由換的東西;上限不是。

# quantbot/domain/values/risk_limits.py
@dataclass(frozen=True)
class RiskLimits:
    """硬性上限。**它在部位計算之後套用,而且 NEVER 由策略設定檔覆寫。**

    這個「之後」是整個 Day 24 最重要的設計決定。部位計算是可插拔的積木,
    所以它是使用者可以自由換的東西;而上限不是——一個算出「這一筆下 300% 資金」
    的積木(凱利在勝率被高估時很容易算出這種數字)如果能自己決定要不要遵守上限,
    那上限就只是一個建議。

    落地方式是型別上的:`sizing.yaml` 裡沒有任何欄位對得到這三個值,而多寫一個
    `maximum_weight:` 會被 FeatureParameters.ensure_used() 當成拼錯的參數直接
    報錯——因為沒有任何 builder 會去取它。這條規則因此不靠人記得。
    """

    maximum_weight_per_trade: float = 0.25
    maximum_total_weight: float = 1.0
    daily_loss_halt_fraction: float = 0.05

最後那段是這個設計最省事的部分:規則不需要一段程式碼去守。Day 15 為了抓拼錯的參數名寫的 ensure_used(),剛好也擋掉了「在設定檔裡偷改風險上限」——因為兩件事在型別上是同一件事:一個沒有任何 builder 會去讀的鍵。

測試把它釘住:

# tests/domain/sizing/test_position_sizer_registry.py
def test_the_risk_ceiling_cannot_be_overridden_from_the_configuration():
    """這是「上限不由設定檔覆寫」的落地方式:沒有 builder 會取這個鍵,
    於是 ensure_used() 把它當成拼錯的參數擋下來。"""
    with pytest.raises(ValueError, match="maximum_weight_per_trade"):
        PositionSizerRegistry().build(
            SizingSpecification(
                kind="fixed_fraction", parameters={"maximum_weight_per_trade": 3.0}
            )
        )

夾取本身有一個容易寫錯的地方:

# quantbot/domain/values/risk_limits.py
    def clamp(self, weights: pd.Series) -> pd.Series:
        """把權重夾進上限,方向保留。

        用絕對值夾再乘回符號,而不是 `clip(-ceiling, ceiling)`:後者在權重是
        NaN 的時候會留下 NaN,而 NaN 乘進報酬序列會讓整段權益曲線從那一根起
        全部是 NaN——一個「算不出部位」的錯誤會表現成「回測沒有結果」,
        而那看起來像資料問題,不像部位計算問題。

        算不出部位的那幾根一律當空手。NEVER 用「上次的權重」補,那是憑空
        生出一個沒有依據的部位。
        """

接進回測只換一條序列

Day 19 的回測引擎一行都不用改,而那是 Day 16 把部位定義成權重而不是金額換到的東西:

# quantbot/domain/services/position_sizing_service.py
class PositionSizingService:
    """把引擎給的方向序列(±1/0)換成帶大小的權重序列,並強制套上風險上限。

    **第一,回測引擎一行都不用改。** Day 19 的 BacktestService 吃的是一條權重
    序列,然後把它乘上價格報酬——它從來沒有假設那條序列只有 0 與 ±1。所以部位
    大小接進去的方式是「換掉那條序列」,不是「在回測裡加一段部位邏輯」。

    **第二,上限一定會被套用。** 這個 service 是唯一一條產生「可以拿去回測的
    權重序列」的路徑,而它在 return 之前一定會呼叫 limits.clamp()。積木自己不夾、
    也夾不到——它拿不到 RiskLimits。
    """

成本也自動跟著對:BacktestService 用換手量(|position[t] − position[t-1]|)計價,所以押 25% 的那一筆手續費也是滿倉的四分之一。這件事不需要為部位大小另寫一段。

蒙地卡羅:打亂順序,不是重新抽樣

有了不同的下注比例,接下來的問題是「哪一種比較容易活下來」。這要看的不是一條權益曲線,是一群。

# quantbot/domain/services/equity_simulation_service.py
class EquitySimulationService:
    """把同一組交易的順序打亂幾百次,看不同下注比例的權益曲線分布長什麼樣。

    **為什麼打亂順序而不是重新抽樣。** 重抽(bootstrap)會同時改變「哪些交易發生
    了」,於是結果的變化裡混了兩個來源:下注比例的影響,與抽樣的影響。打亂順序
    只改一個變數——每一條路徑用的是**完全相同的那幾筆交易**,只是先後不同。

    這帶來一個很好的性質,而它也是 Day 22 那句「回撤是路徑性質」的直接示範:
    在複利之下,同一組報酬不管什麼順序,**最終權益完全相同**(乘法可交換),
    但**最大回撤與破產機率會變**。
    """

那個性質有一條測試守著,而它同時是 Day 22 那句話的證明:

# tests/domain/services/test_equity_simulation_service.py
def test_shuffling_never_changes_the_final_equity():
    """複利之下乘法可交換,所以總報酬在所有路徑上是同一個數字。

    這正是 Day 22 那句「回撤是路徑性質」的另一半:換順序不變的是報酬,
    會變的是路徑。
    """
    paths = SERVICE.paths(trades(), weight=1.0, seed=1, path_count=50)
    finals = paths.iloc[:, -1]
    assert finals.std() == pytest.approx(0.0, abs=1e-12)

所以這份模擬的輸出要這樣讀:總報酬那一欄在所有路徑上是同一個數字,而風險那幾欄不是。 一千條路徑一次算完(path_count × trade_count 的矩陣),「NEVER 用 for loop 遍歷 K 線」在這裡的對應是「NEVER 用 for loop 跑模擬路徑」。

實測:三個策略、四種下注方式

跑的是 Day 17 到 Day 18 那三份設定,BTC/USDT 現貨 1 小時 K 線,資料來自 Day 07 的 TimescaleDB。上限先放寬到 100%,讓四種下注方式的差別看得出來。試驗次數 1(沒有搜尋,四種下注方式是事先寫好的四份設定)。

趨勢跟隨(訊號 232 筆):

下注方式 交易 平均權重 要求上限 被夾 總報酬 最大回撤 破產機率
滿倉 232 100.0% 100.0% 0.0% −63.54% −66.55% 100.0%
固定 25% 232 25.0% 25.0% 0.0% −21.35% −23.07% 0.0%
波動率目標 1% 232 83.3% 310.3% 19.2% −52.84% −54.88% 100.0%
四分之一凱利 0 0.0% 0.0% 0.0% 0.00% 0.00% 0.0%

均值回歸(訊號 28 筆):

下注方式 交易 平均權重 要求上限 被夾 總報酬 最大回撤 破產機率
滿倉 28 100.0% 100.0% 0.0% −2.45% −5.64% 0.0%
固定 25% 28 25.0% 25.0% 0.0% −0.59% −1.42% 0.0%
波動率目標 1% 28 84.4% 188.4% 0.4% −3.80% −5.43% 0.0%
四分之一凱利 28 5.0% 5.0% 0.0% −0.12% −0.29% 0.0%

動能爆發(訊號 190 筆):

下注方式 交易 平均權重 要求上限 被夾 總報酬 最大回撤 破產機率
滿倉 190 100.0% 100.0% 0.0% −42.97% −44.85% 0.1%
固定 25% 190 25.0% 25.0% 0.0% −12.84% −13.57% 0.0%
波動率目標 1% 190 73.4% 215.3% 0.9% −34.87% −35.63% 0.0%
四分之一凱利 0 0.0% 0.0% 0.0% 0.00% 0.00% 0.0%

先講一件必須誠實講的:這三個策略在 Day 22 就已經是賠錢的,而下注比例改變不了方向。 押少一點只是賠少一點,那不是一個發現。所以這三張表要看的不是總報酬那一欄,是後面兩欄。

凱利對兩個策略給出的答案是「不要下注」。 趨勢跟隨與動能爆發的期望值都是負的(Day 22 算過:0.238 × 1.86 − 0.762 = −0.32),而凱利對負期望的最佳解就是 0。那兩列的交易筆數是 0 筆,不是 232 筆。

這件事在實作上撞了一次。原本那份比較報告的 DTO 有一條建構時的檢查:「所有列的交易筆數必須相同」,理由聽起來很直觀——換下注方式不改變訊號。第一次實跑就打掉了它:

ValueError: 所有列必須來自同一組訊號,實得交易筆數 [0, 232]

「押 0」在部位序列上跟「不進場」是同一件事,所以下注方式確實可以改變交易筆數,而那正是它在做的判斷。檢查因此改成 bar_count 相同(同一段資料),而交易筆數變成一個要看的欄位——訊號 232 筆、實際下 0 筆的那個差距是這份報告裡最值得看的一格。

均值回歸那一列走的是退路,不是公式。 它的 5.0% 是 fallback_fraction,因為 28 筆交易低於 100 筆的門檻。這一列跟前面兩列不是同一種東西:它不是「凱利算出來 5%」,是「凱利拒絕回答,所以用了一個保守的預設值」。報告上兩者長得一樣,這也是為什麼積木的 describe() 要把門檻寫出來。

破產機率那一欄有一個 0.1%。 動能爆發滿倉的總報酬是 −42.97%,期末權益 57%,看起來離「跌到一半」還有距離。而一千條打亂順序的路徑裡有一條在中途跌破了 50%——同一組交易,只是先後不同。那 0.1% 就是「回撤是路徑性質」最小的一個實例。

趨勢跟隨滿倉的 100% 則是另一回事:它的期末權益只有 36%,任何順序都會在某個時點跌破一半。這個 100% 不是模擬的結論,是總報酬的必然結果,讀的時候要分清楚。

上限一收緊,四種下注方式就變成同一種

把單筆上限從 100% 收回預設的 25% 再跑一次趨勢跟隨:

下注方式 交易 平均權重 要求上限 被夾 總報酬 最大回撤
滿倉 232 25.0% 100.0% 48.4% −21.35% −23.07%
固定 25% 232 25.0% 25.0% 0.0% −21.35% −23.07%
波動率目標 1% 232 25.0% 310.3% 48.4% −21.31% −23.03%

三列幾乎是同一列。上限成了主導,而三種下注方式的差別被壓掉了。

這件事有兩種讀法,而兩種都對。從風控的角度,這正是硬性上限該有的效果:不管上面那一層算出什麼,曝險就是不超過那個數字。從研究的角度,它是一個警訊——在這組參數下,「比較四種下注方式」這件事本身沒有意義,因為被比較的東西有 48.4% 的時間是同一個值。

所以要比較下注方式,上限必須鬆到讓它們有差別;而要上線,上限必須緊到讓最壞的積木也不會出事。這是兩個不同的設定,不該用同一份。

視覺化:一群權益曲線

# quantbot/infrastructure/charting/plotly_sizing_distribution_renderer.py
class PlotlySizingDistributionRenderer:
    """同一組交易、不同下注比例,幾百條打亂順序的權益曲線。

    這張圖要說的事只有一件,而它在數字表上很難看出來:**同一個總報酬,不同的
    下注比例會給出完全不同的路徑分布。** 押得重的那一格線會散得很開、下緣會探
    得很低;押得輕的那一格是一束細線。

    橫軸是交易筆數而不是時間,因為每一條路徑的交易順序都被打亂了,時間軸在這裡
    沒有意義。這件事要在軸標題上講清楚,否則讀者會把它讀成一條真的權益曲線。
    """

四格並排,每一格上緣標它的破產機率,虛線標破產線。所有格共用同一條縱軸,否則「散得很開」這件事會被各自的縮放抹掉。

順帶:三份策略設定各多了一行

波動率目標需要 atr_14,而 Day 17 到 18 的三份設定沒有宣告它。所以第一次跑的結果是這個:

ValueError: 部位計算 volatility_target_0.01 需要欄位 ['atr_14'],但特徵表裡沒有。
把對應的特徵加進策略設定的 features 清單。

這是 Day 17 那道特徵對帳的同一種檢查,只是換了一個提問方向——那時候問「條件引用的欄位有沒有宣告」,現在問「部位計算需要的欄位有沒有宣告」。兩者都在啟動時就失敗,而不是跑完一輪回測才發現某一列全是 NaN。

修法是三份設定各加一行。但加一個特徵會改變特徵表的暖機期,所以前幾天的數字有可能跟著變,而那件事要驗過才能說:

Day 19 原本   K 線 13,823 根,交易 232 筆,總報酬 −63.54%
加了 atr 之後  K 線 13,823 根,交易 232 筆,總報酬 −63.54%

一樣。原因是 ATR 的暖機期是 14 根,而這份設定裡最長的是 EMA 26;暖機期取最長的那一個,所以加一個更短的不會改變表的長度。另外兩份設定裡最長的是活躍度的 168 根,更是遠遠蓋過 ATR。三份都驗過,交易筆數與曝險與 Day 22 那張表完全一致。

今日交付物

quantbot/
├── domain/
│   ├── values/            TradeStatistics(凱利), RiskLimits, SizingContext,
│   │                      SizingSpecification, SizedPositions
│   ├── sizing/            今天新開的資料夾
│   │   ├── full_notional_sizer.py       Day 19-22 的隱含行為,現在是一個選項
│   │   ├── fixed_fraction_sizer.py
│   │   ├── volatility_target_sizer.py   回收 Day 12 的 ATR
│   │   ├── fractional_kelly_sizer.py
│   │   └── position_sizer_registry.py
│   ├── interfaces/        PositionSizer, PositionSizerBuilder
│   ├── services/          position_sizing_service.py
│   │                      equity_simulation_service.py
│   └── dto/               ruin_risk_report.py, sizing_comparison_report.py
├── application/analyze_sizing_application.py     今天
├── infrastructure/
│   ├── configuration/sizing.yaml + 載入器        今天
│   ├── reporting/text_sizing_comparison_report_renderer.py   今天
│   └── charting/plotly_sizing_distribution_renderer.py       今天
├── entrypoints/sizing_command.py                 今天
└── tests/                 7 個新測試檔案,共 55 條

先把資料補到最新

破產機率是從交易明細算出來的,所以資料短一段結論就會變。跑之前先把管線補完:

uv sync
docker compose -f docker/docker-compose.yml up -d
uv run python -m quantbot.entrypoints.ingest_pipeline_command

跑起來

# 上限放寬,看四種下注方式的差別
uv run python -m quantbot.entrypoints.sizing_command \
    --strategy trend_ema_rsi --timeframe 1h \
    --start 2025-01-01 --end 2026-08-01 --max-weight 1.0

# 上限收回 25%,看上限主導的樣子
uv run python -m quantbot.entrypoints.sizing_command \
    --strategy trend_ema_rsi --timeframe 1h \
    --start 2025-01-01 --end 2026-08-01

換一種下注方式只要改 sizing.yaml 的一行:

# quantbot/infrastructure/configuration/sizing.yaml
sizings:
  - kind: volatility_target
    label: 波動率目標(每筆風險 1%)
    risk_fraction: 0.01
    atr_column: atr_14
    stop_multiple: 2.0

risk_fraction 調成 0.02 再跑一次,會看到平均權重與破產機率一起往上走。這份設定裡寫不進風險上限——試著加一行 maximum_weight_per_trade: 3.0,載入時就會失敗。

驗收標準

八項全過才算完成:

  1. uv run pytest 全綠(547 passed)。
  2. 沒有任何積木能翻轉方向,有測試把四個積木一起掃過一遍。
  3. 波動率目標在波動翻倍時押一半,有測試釘住具體數字。
  4. ATR 是 NaN 或 0 時不下注,而不是給一個預設權重。
  5. 上限在積木之後套用,而且積木拿不到它:凱利要求 90% 以上、實際只拿到 25%,有測試釘住。
  6. 在設定檔裡寫風險上限會失敗,有測試釘住。
  7. 打亂順序不改變最終權益,但改變回撤,有測試釘住——這是 Day 22 那句話的證明。
  8. uv run mypy quantbotuv run lint-imports 全過(275 檔,3 條契約)。

第 6 項是今天最值得留著的一條,因為它是一條不需要任何人記得的規則。

免責聲明:本文為程式與資料工程的技術分享,所有數字皆為歷史資料上的觀察與教學範例,不構成投資建議。文中三個策略在真實成本下都不賺錢,下注比例只改變賠的幅度;這段期間 BTC 下跌 33.71%,所有數字都要放在那個背景下讀。

明天

現在機器人會算訊號、會決定下多少、送單也會自己重試與對帳。它還缺一件事:沒有人知道它在做什麼。

明天處理無人值守系統的第一原則——沒有消息不等於好消息。設計三個層級的通知:心跳(每天一次「我還活著」)、事件(有交易發生)、告警(出事了,需要人介入)。告警疲勞是真的,所以設計時要克制:哪些事真的需要半夜叫人起來(連續虧損超過閾值、部位與交易所不一致),哪些不用(單筆停損)。

順帶把 Telegram Bot 做成雙向的:不只推訊息出去,也收指令進來——查目前部位、暫停交易、緊急平倉。那是這套系統唯一的一道人為閘門。

Reference


上一篇
Day 23:凌晨三點 API 斷線、機器人卡死?用防禦性設計讓它自己活下來
下一篇
Day 25:機器人在雲端跑,怎麼知道它還活著?用 Telegram 建立回報與告警
系列文
量化交易入門:從 K 線到可組合的交易策略引擎26
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言