課程「建立自己的套利機器人」第 11 課。機器人現在知道要交易什麼和交易多少。今天你要檢驗藏在這一切底下的假設:計畫會如同寫下的一樣發生。
正面假設
到目前為止,機器人算出一個計畫:在 A 以 100.00 買 1 枚幣,在 B 以 100.30 賣 1 枚幣,扣除手續費後利潤為 0.0997(第 10 課)。直接執行的做法,是假設兩張訂單會立刻以完全相同的價格成交。這是第一個模擬的寫法,也是第一批機器人在紙面上看起來很棒、實盤卻虧錢的原因。本課其餘部分會拆解這個假設。
會破壞計畫的五件事
- 延遲。 從你讀取價格到訂單抵達交易所的這段時間,會經過你的程式、網路、交易所佇列。數十或數百毫秒。價格會在這段時間移動。
- 兩條腿,兩個時刻。 你先買,再賣。第一筆成交後,第二筆的價格可能改變。第二腿完成前,你持有一枚幣並承擔價格風險。這稱為腿風險。
- 部分成交。 你要求 1 枚幣,卻只得到 0.6。另一腿必須縮小來配合,否則你會剩下一部分沒有避險。
- 某一腿失敗。 第二張訂單可能被拒絕:該交易所沒有餘額、碰到價格限制、交易所故障、你碰到速率限制。現在你持有原本不想持有的一枚幣,必須以市場給的任何價格賣出。
- 其他機器人。 你看到的價差對所有人都可見。更快的機器人會在你之前拿走委託簿最上方價位。等你的訂單抵達時,你依賴的價位已經不在。你主要會在價格朝不利方向移動時成交,這表示你贏得的交易比平均情況更差。
可執行的模擬
我們保留第 10 課的計畫,並加入兩種影響。第一,B 的買價會在賣出時隨機移動(以價格單位計、有選定分散程度的常態隨機數)。第二,賣出腿有一定機率失敗,而你要以 99.80 在市場拋售這枚幣。
import random
random.seed(11)
FEE = 0.001
ASK_A, BID_B = 100.00, 100.30 # 計畫:在 A 買 1 枚幣,在 B 賣 1 枚幣
def one_trade(move_sigma, fail_chance, dump_price):
paid = ASK_A * (1 + FEE)
if random.random() < fail_chance: # 賣出腿失敗:在市場拋售這枚幣
return dump_price * (1 - FEE) - paid
bid_now = BID_B + random.gauss(0, move_sigma) # 我們買入時,買價已移動
return bid_now * (1 - FEE) - paid
def run(move_sigma, fail_chance, n=10000):
r = [one_trade(move_sigma, fail_chance, 99.80) for _ in range(n)]
return sum(r) / n, sum(x < 0 for x in r) / n
print("plan profit", round(BID_B * (1 - FEE) - ASK_A * (1 + FEE), 4))
for sig, fail in [(0, 0), (0.05, 0), (0.2, 0), (0.2, 0.1), (0.2, 0.3)]:
m, l = run(sig, fail)
print(f"move {sig:<5} fail {fail:<4} avg profit {m:+.4f} losing trades {l*100:.0f}%")
輸出:
plan profit 0.0997
move 0 fail 0 avg profit +0.0997 losing trades 0%
move 0.05 fail 0 avg profit +0.0994 losing trades 2%
move 0.2 fail 0 avg profit +0.0994 losing trades 31%
move 0.2 fail 0.1 avg profit +0.0516 losing trades 38%
move 0.2 fail 0.3 avg profit -0.0512 losing trades 52%
逐行閱讀。
- 沒有移動,沒有失敗:計畫的結果,+0.0997。
- 隨機移動 0.05(利潤的一半):平均值幾乎不變,但每 100 筆交易已有 2 筆虧錢。沒有偏差的隨機雜訊會互相抵消。這是樂觀情況。
- 隨機移動 0.2:平均值相同,但每 100 筆交易有 31 筆虧錢。你的每筆交易利潤很小,因此即使看似無害的雜訊,也會讓三分之一的交易變成虧損。
- 第二腿有 10% 機率失敗:平均值減半,成為 +0.0516。一筆失敗交易約損失 0.40(你付 100.10,收到 99.70),會拿走四筆成功交易的利潤。
- 有 30% 失敗機率:平均值為負,−0.0512。這個策略會虧錢,儘管每個個別計畫看起來都有獲利。
手算檢查失敗成本:你付 100.00 × 1.001 = 100.10。你以 99.80 × 0.999 = 99.7002 拋售。虧損:0.3998。若失敗機率是 30%,預期利潤為 0.7 × 0.0997 − 0.3 × 0.3998 = −0.0501,接近模擬出的 −0.0512(差異是 10 000 次試驗產生的隨機雜訊)。
這個模擬故意保持簡單,並有一個往樂觀方向的重要缺陷:它讓價格在沒有偏差的情況下移動。在真實市場,你得到的成交不是公平樣本。價格往對你有利的方向移動時,其他機器人會在你之前拿走它。價格往不利方向移動時,你才成交。因此,真實結果比「move 0.2, fail 0」那一行更差。
機器人如何處理每個問題
- 延遲。 測量它。記錄每個步驟的時間。讓機器人靠近交易所(同一區域的伺服器),並先做最便宜的檢查。
- 腿風險。 選擇暴露時間較短的設計:在兩家交易所都持有餘額,同時賣出和買入,而不是在它們之間轉移幣。兩張訂單一起送出,不要一張接一張。
- 部分成交。 第一腿之後,讀取實際成交多少,並把第二腿數量設為該數量。
- 某一腿失敗。 為剩餘的幣準備計畫:以市價賣出、加上價格限制,並在結果中計入成本。如模擬所做,把失敗率加入利潤估計。
- 其他機器人。 留下安全邊際:只在預期利潤明顯大於零時交易,而不只是略高於零。要高多少,應由你自己的測量決定。
讓 AI 代理人寫它並測試
一個模糊的要求:
讓套利模擬更貼近真實。
一個內建檢查方式的要求:
依第 11 課程式碼撰寫 reality.py:one_trade(move_sigma, fail_chance, dump_price) 和 run(...)。使用 random.seed(11)。第一行輸出必須是 "plan profit 0.0997"。move 0.2、fail 0.3 的那一行必須顯示負的平均利潤。加入一個參數 min_edge:計畫利潤低於 min_edge 時跳過交易。為 min_edge 0.05 和 0.15 印出每筆計畫交易的平均利潤,並用兩句話解釋數字為什麼不同。不要改變計畫數字。
手算失敗成本(0.3998),並和代理人針對單筆失敗交易所印出的結果比較。
你的練習
- 執行程式碼,確認輸出和表格相符。
- 找出平均利潤剛好是零的失敗機率。手算:0.0997 × (1 − f) = 0.3998 × f,得到 f = 0.0997 / 0.4995,約為 0.20。用 fail 0.2 在程式中檢查。
- 把拋售價格從 99.80 改為 99.95。損益兩平的失敗機率如何改變?這說明快速且接近市場價格賣出剩餘幣的價值是什麼?
- 讓移動變成單向:從每個
bid_now減去 0.05(成交偏差)。再次執行並比較。這更接近真實市場。
接下來
你現在知道計畫只是開始,而失敗決定平均結果。第 12 課會建立保護措施:模擬交易(用即時價格執行完整機器人,但使用假錢)、為我們用過的每個數字建立設定檔,以及在一個糟糕小時造成傷害前停止機器人的風險限制。