"打造你自己的套利機器人"課程第 12 課。這也為世界 3 收尾。你的機器人已經有掃描器、數量計算器和現實檢驗。今天要讓它可以安全運作。
最直接的做法
最直接的開始方式:把金鑰放進腳本,在函式裡硬編碼所有數字,手續費 0.001、數量 1 等,然後以真錢執行來 "看看會發生什麼事"。它會一直正常,直到第一次意外出現。接著三個問題都沒有好答案:我改了哪個數字?我虧了多少?它為什麼持續交易?
本課以三個工具逐一解決這三點。
工具 1:模擬交易
模擬交易是指整個機器人以即時價格運作、做出決策,並把訂單送到一個只記錄原本會發生什麼事的虛擬帳戶。資金不會移動。你會知道機器人如何運作,每個錯誤都不用花錢。
讓它有用的關鍵是:虛擬帳戶和真實交易所用戶端有相同的方法,這裡叫作 trade。機器人不知道正在和哪一個互動。之後切換到真實用戶端時,其他任何東西都不用改。
模擬帳戶也會稍微說謊,第 11 課已展示過:它會以你傳入的委託簿價格成交,沒有延遲,也沒有其他機器人。把它的利潤看作上限。真實結果會更低。
工具 2:設定檔
所有可能需要更改的數字都放在一個位置,於啟動時讀取。下面的程式碼把它寫成程式內的 JSON 文字,因此可以直接執行。在你的機器人中,它會是一個 config.json 檔案。
- 不碰邏輯也能修改數字。
- 可以在每次執行旁儲存設定,之後知道哪套設定得到哪個結果。
- 數字錯誤只要在一行中找出。
金鑰和機密絕不放進版本庫中的設定檔。請放在環境變數。
工具 3:風險限額
風險限額是一條規則,當某件事看來不對時會停止機器人,不管機器人相信什麼。四種基本限額:
- 單筆最大數量。要求 1000 枚而不是 1 枚的錯誤會在此處被攔住。
- 最小 edge:計畫利潤低於安全邊際時不交易,原因見第 11 課。
- 一個期間內的最多交易次數。失控迴圈會撞上它。
- 每日最大損失。當損失越過限額時,機器人停止,只有人可以重新啟動。它就是緊急停止開關。
程式碼
import json
CONFIG = json.loads("""{
"fee": 0.001,
"min_edge": 0.05,
"max_size": 1.0,
"max_trades": 20,
"max_daily_loss": 0.50
}""")
def walk(levels, amount):
money, left = 0.0, amount
for price, size in levels:
take = min(size, left)
money += take * price
left -= take
if left <= 0:
break
return money, amount - left
def plan_profit(asks, bids, q, fee):
"""Profit after fees for size q, or None if a book is too thin."""
paid, got_a = walk(asks, q)
received, got_b = walk(bids, q)
if got_a < q or got_b < q:
return None
return received * (1 - fee) - paid * (1 + fee)
class PaperAccount:
"""A fake account. A real exchange client would have the same trade() method."""
def __init__(self):
self.pnl, self.trades = 0.0, 0
def trade(self, asks, bids, q, fee):
profit = plan_profit(asks, bids, q, fee)
self.pnl += profit
self.trades += 1
return profit
class Guard:
def __init__(self, cfg):
self.cfg, self.stopped = cfg, False
def check(self, account, size, edge):
if self.stopped:
return "stopped"
if account.pnl <= -self.cfg["max_daily_loss"]:
self.stopped = True
return "daily loss limit"
if account.trades >= self.cfg["max_trades"]:
return "trade limit"
if size > self.cfg["max_size"]:
return "size above limit"
if edge is None or edge < self.cfg["min_edge"]:
return "edge below minimum"
return "ok"
asks = [(100.00, 2), (100.20, 3)]
bids = [(100.30, 1), (99.90, 5)]
account, guard = PaperAccount(), Guard(CONFIG)
for size in [1.0, 1.0, 1.5, 0.2]:
edge = plan_profit(asks, bids, size, CONFIG["fee"])
verdict = guard.check(account, size, edge)
profit = account.trade(asks, bids, size, CONFIG["fee"]) if verdict == "ok" else None
print(f"size {size:<4} edge {edge:+.4f} -> {verdict:20} profit {profit if profit is None else round(profit, 4)}")
print("pnl", round(account.pnl, 4), "trades", account.trades)
print("loss streak:")
account, guard = PaperAccount(), Guard(CONFIG)
for i in range(6):
account.pnl -= 0.20
print(i, round(account.pnl, 2), guard.check(account, 1.0, 0.1))
輸出:
size 1.0 edge +0.0997 -> ok profit 0.0997
size 1.0 edge +0.0997 -> ok profit 0.0997
size 1.5 edge -0.0502 -> size above limit profit None
size 0.2 edge +0.0199 -> edge below minimum profit None
pnl 0.1994 trades 2
loss streak:
0 -0.2 ok
1 -0.4 ok
2 -0.6 daily loss limit
3 -0.8 stopped
4 -1.0 stopped
5 -1.2 stopped
看看輸出。
- 前兩筆交易通過所有檢查並被記錄:每筆 0.0997,合計 0.1994。可以手動核算 0.0997,參見第 10 課。
- 數量 1.5 被數量限額阻止,它也會虧錢:-0.0502。
- 數量 0.2 的 edge 為正,0.0199,但低於最小 edge 0.05,因此機器人跳過它。微小利潤不值得承擔第 11 課裡的風險。
- 在連續虧損中,機器人每步損失 0.20。保護機制在第三步觸發,此時累計為 -0.6。限額是 0.50,損失多出它 0.10,因為檢查在每筆交易之前執行,只看到已經產生的損失。限額可以止損,但不能阻止最後一筆交易。把它設得低於你實際承受得起的損失。
- 在 "daily loss limit" 之後,保護機制會永遠回答 "stopped"。只有人工重新啟動可以清除它。這是刻意設計的。
開始真實交易之前
合理的順序,規則是只有前一步已經變得乏味時,才往下一步走:
- 至少一週的模擬交易,保留日誌。把它聲稱的利潤和第 11 課的估算相比。
- 真實交易所、極小數量,交易所的最低值,幾美元的損失限額。目標是測試連線、訂單和手續費,不是獲利。
- 只有真實結果持續與模擬結果一致時,才慢慢提高數量。
如果真實結果遠差於模擬結果,差異就是你的真實成本:延遲、失敗的一腿、其他機器人。提高數量前,找出是哪一項。
讓 AI 代理撰寫並測試它
模糊的請求:
為我的機器人加入安全措施。
有檢查的請求:
撰寫
safe.py,包含第 12 課的plan_profit、PaperAccount和Guard,從config.json讀取設定(fee 0.001, min_edge 0.05, max_size 1.0, max_trades 20, max_daily_loss 0.50)。以 asks [(100.00, 2), (100.20, 3)] 和 bids [(100.30, 1), (99.90, 5)] 測試:數量 1.0、1.0、1.5、0.2 必須得到 ok、ok、size above limit、edge below minimum,最終 pnl 必須為 0.1994。測試 2:每步損失 0.20 時,必須在第 2 步印出 "daily loss limit",之後印出 "stopped"。只從環境變數讀取 API 金鑰,絕不印出它們。不得連線到任何交易所。
接受代理的成果前,先用計算機核算 pnl 0.1994。
你的練習
- 執行程式碼並與上面的輸出比較。
- 把設定移到真正的
config.json檔案並用json.load讀取。將min_edge改為 0.10,看看哪些交易被跳過。 - 修改保護機制,讓它在每筆交易後檢查損失,以便它在第一筆越過限額的交易後停止。超出的部分消失了嗎?不完全是:最後一筆交易仍然發生。
- 新增 "最大未平倉部位" 限額:如果機器人在一次失敗的一腿後同時持有超過 2 枚幣,它就停止。
下一步
世界 3 已完成:你擁有一個完整的機器人,它能發現機會、計算數量、模擬並保護。世界 4 讓它更快、範圍更廣:第 13 課從速度開始,解釋 Python 為什麼變慢、可以怎麼處理,以及 Rust 如何加入其中。