這裏主要講的是,中低頻交易策略的程序化實盤代碼實現。

策略

一個量化交易策略的誕生,一般是經由對市場的觀察瞭解,產生策略 idea,然後設計策略的具體細節,再回測多品種的數據,從而獲取、驗證具體參數數值的效果。如果效果可以,具有普適性,那麼下一步就是上實盤小資金測試。樣本外實盤可行,最終就可以加大資金並長期運行。

爲了防槓精,說明一下,當然也有其他方法產生策略,比如數據挖掘,挖呀挖呀挖因子。還有機器學習、強化學習這類自動生成因子。不過,這類黑盒方法,在策略回撤期很難堅持運行,因爲你不知道這些挖出來的因子,什麼時候失效,爲什麼失效。而做量化交易,最重要的一點就是要堅持實盤,行情不好,你可以縮倉,但是不能停機。沒有人能知道大行情什麼時候爆發。錯過了,你之前的回撤就是白瞎。

一個類比,策略的設計、回測,就相當於《孫子兵法》上說的“廟算”,“夫未戰而廟算勝者,得算多也;未戰而廟算不勝者,得算少也。多算勝,少算不勝,而況於無算乎!”

所以,交易策略在最初的設計上就得謀劃好,計算周密,不然還沒實盤其實就已經輸了。這一步是最難的,後面的實盤代碼雖然也不容易,但不過大部分只是繁瑣而已,多花花時間精力,總能做好的。實盤的關鍵還是在風控。

交易策略,或者叫交易規則,就是讓我們 “做正確的事”,具體的實盤落地,就是“正確地做事”。很多時候,知道正確的方向,遠遠要比走完路程要難。就如那個維修設備的段子,畫一條線收費一千塊。畫線本身只值 1 塊,但是知道在哪裏畫收費 999。

策略+實盤共同組成交易系統。關於交易系統的設計,可以參看之前這篇:完備交易系統的七要素。這裏的實盤其實就是交易系統的後面 4 步“進出盈損”的具體操作。前面 3 步,在策略設計完成時,就基本已經確定了。

但是,就算策略不錯,如果實盤執行不到位,那麼效果也會大打折扣,甚至導致停掉本來還不錯的潛力策略。研究出一個好策略是很不容易的事情,不要倒在執行這一關。好的想法,要最終穩妥落地纔行。

問題

1. 突然的行情

這種是有 bar 內信號的策略纔會遇到。bar 內就是沒有采用某根 k 線的關倉價或者開倉價,而是盤中的價格來觸發信號。

這裏以幾天前實盤遇到的一個問題爲例。一個交易市場的微觀例子。在幣圈挺普遍的。

下圖是前幾天(2023.7.10 17:21) BNB 永續合約的 10 秒 k 線(就是一根 bar 是 10 秒內的交易數據聚合出 OHLCV 的意思)。

可以看到,那根大陽線振幅 3.53%,成交了大概 3 千萬刀左右。10 秒內完成。在這次行情啓動前,走勢幾乎毫無波瀾。

再看下圖是當時的 1 秒 k 線,可以得到更多的細節。

基本上 4 秒左右就完全漲到位了。第 1 秒漲了大概2 個多點。這些,基本都是那些高頻策略(事件驅動,高頻趨勢,做市等)還有可能事先佈置的算法單之類的博弈結果。

再來看 BNB 的 4 小時 k 線。最大那根陽線就是當時消息出來的時間段。振幅 4.05%。

對比一下這幾個週期的 k 線就可以看出,這種消息驅動的波動(這次消息爲幣安新IEO,ARKM),一般都是瞬間完成的。4 小時的漲幅居然和 10 秒相差並不太多,而真正的啓動,也就 4 秒左右。這種戰場,是屬於那些武裝到牙齒的高頻策略的。但是,它也可能會影響到中低頻策略,造成更大的滑點。

這裏不研究高頻策略,對於中低頻,說明什麼問題呢?我想到的主要有兩點:

1. 有的趨勢爆發非常突然,而且是在很短時間內完成,所以不能隨便暫停策略。在幣圈的實盤要保證 24 小時在線,特別是有倉位的情況下,不能掉線。

2. 實盤的滑點有可能達到 2 個百分點級別。看上面的 1 秒 k 線,如果你的買入信號是以某個價格爲基準,這個價格又正好在大 k 線的下部出來,那麼不管怎樣,你進場後的價格大概都可能是滑出 2~3 個百分點。最差能到 4 個百分點,那些在針尖成交的買單就是。不過,如果你是賣單,那麼就會獲取超出預期的利潤。但是,根據墨菲法則,落下的麪包總是塗着黃油的一面着地。長期來看,大概率是滑點更多。

不過,好的是,這種情況發生的次數有限。只要策略沒問題,滑點大一點,只是盈利回吐而已,不影響長期正收益。就當多承受了一次震盪磨損。

緩解的一個辦法,就是採用 websocket 獲取最新價格,文檔說 250 毫秒更新一次,但是有的時候其實更新更快,這樣就有可能在第一時間反應了。websocket 的另外一個優點,是不佔用交易所 Restful API 的頻率限制。這個後面有詳細提到。

2. 整點行情

這種是很多趨勢策略會遇到,因爲大都是採用開關倉價格來計算進出場的信號。那麼整點一過,各自紛紛啓動。

整點時刻,特別是 8 的倍數,因爲無論你的策略是什麼週期,這三個點應該都遇到公約數時間了,可能大家的自動化策略都會有動作,高中低頻擠在一起產生各自的信號,互爲對手盤。

另外,這個時候,交易所還需要結算交割。雖然永續合約不交割,但是要計算交割資金費,可能還有其他操作。在 0/8/16 這三個整點之後的開始 6 秒鐘左右,幣安的 websocket 是不推送 k 線信息的,它停了!就問你怎麼辦?現實情況,很多時候就是沒有辦法。

更嚴重的是,行情會共振,特別是下跌行情,恐慌的傳染性更強烈。交易所的服務器更忙了,很多行情激烈的小幣直接就宕機,下單是下不進去的,全是 1001 錯誤。等你忙不迭地下單成功了,可能針尖上成交的那些單子就是你的。

所以,滑點真的是避免不了的,都是命,接受它吧,過分優化代碼也沒太大用處。就如前面說的,就當多止損了一次。

不過,也有一些小技巧分享。

可以在整點前 n 秒提前計算信號跑路,那個時候還沒開始擁擠,因爲可能大多數自動化策略都是等到整點 k 線關倉價出來之後,纔開始計算信號的。不過這樣操作帶來的問題就是,可能會產生錯誤信號,例如價格馬上又回去了,本來不該有信號的,false positive 發生了。這時就看你的取捨了,要不要這麼幹。不過,可以再加一定閾值,超過了才觸發,這樣防止短時間價格迴歸造成的誤發信號。滑點能減少一點是一點吧。

還有就是採用時間 offset 或者 shift k 線這類偏移信號的方法,不在整點產生信號,不跟別人捲到一起去,更好地避開擁擠。還有就是 TWAP 等等方式。

不過,這些都需要一些技巧,會加大實盤代碼的難度。

實盤

初級的中低頻 CTA 策略,一般來說實盤代碼非常簡單。一個品種只有一個策略,而且沒有加減倉,沒有交叉信息混淆在一起。那麼本地代碼可以不維護任何狀態,因爲交易所幫你記錄了一切。是否有倉位,保證金多少,單子是否成交等等。因爲是中低頻,需要的時候可以隨時查詢交易所的賬戶信息,而且這個是最即時準確的。免去了本地使用數據庫之類記錄交易狀態的麻煩。

這種簡單策略其實也是可行的,但是問題就是可能回撤會大一點,不夠分散,風險高。單品種可能一直沒行情,震盪回撤不見回頭,或者遇到極端行情,資金曲線上又會來一個大坑。嚴重點,扛不住就會被嚇得停策略,輕一點,交易體驗會差很多。注意,這些說的都是在合理倉位的條件下。不然倉位太輕,回撤是小了,但是收益也降低了,盈虧同源,機會錯過也可惜;太重,就是錯誤,遲早要完蛋的。

不過,建議新手可以先搞一個這種策略實盤跑起來。實踐才能快速提高自己的交易水平。但是注意一定要輕倉,然後槓桿要低一點,最好是不用槓槓,這樣可以堅持更長時間。

如果要做得更專業一點,降低單點風險,資金曲線更平滑,實盤的時候更少擔心(如前所述,合理倉位下,這點其實挺重要,因爲更容易堅持運行策略,等到風來)一般都是多品種多策略多參數的模式,然後還有加減倉。不一定是在一個策略上的加減倉,也可以是多個子策略實現加減倉的效果。不過,這樣的話,策略就會比較擁擠,如果還想盡量降低開平倉滑點,那麼代碼複雜度就會直線上升。

下面說說這種多品種多策略多參數模式帶來的難題。

難點

1. API 頻率限制

多策略的第一個難題就是 API 頻率限制。品種多了,策略多了,爲了獲得即時的價格、倉位、下單等信息,就得不停去訪問交易所。像前面講的,頻率低了,慢了,那麼獲得價格就可能不是最新的,滑點就可能大不少。

交易所的服務器資源有限,那麼就產生了 API 的訪問頻率限制。幣安的限制是每分鐘 2400,然後還有 10 秒的限制,然後還有其他所謂機器學習探測惡意行爲的模式,反正就是太頻繁肯定不行,至於怎麼不行,交易所說了算,這種你懂的,沒有定式。

雖然說是 2400,但是我用併發隨便測試了一下,如果是請求 k 線,一次就算是最短的 99 根(API 權重爲 1)那麼,一起請求 75 個幣左右就會被 ban 幾分鐘。所以,不能輕易去挑戰極限的。

違反了,就是收到 429, 418 錯誤,IP 被封禁幾分鐘到幾天,這個時候,如果你有倉位就慘了(下單,特別是出清已有倉位不受限制,但是你沒有價格信息了,除非還有 websocket)

這個問題的解決,就是使用 websocket 獲取數據,這樣交易所主動推過來,時間及時又不佔用 API。可以減少一些滑點。帶來的問題,就是你得再維護一套基於 websocket 的行情中心。實盤代碼難度上升了,變複雜了。要 24 小時不掉線,斷線了要及時自動重連等等。

2. 策略狀態

前面說了,策略多了,複雜了,就不得不記錄每個策略的狀態,不然交易所的倉位也不知道是哪個策略開的,每個開的多少之類。而且後面要覆盤的話,也得記錄更多數據。

這個時候就得引入數據庫了(當然,用文件記錄也行,一個意思)。

引入了數據庫,就帶來數據庫的那些問題。不過這和其他行業的軟件開發類似,沒有新意。特別是電商行業,因爲都是資金餘額,訂單管理這些類似的東西,沒有經驗的可以看看這類書籍文章。

要注意的就是,很多步驟要達到類似數據庫事務的原子化操作,就是一組操作要麼就是全部成功,一旦某一步失敗,就得什麼都不幹,回滾到最初狀態。

然後,因爲畢竟是真金白銀的交易,對於下單信息的數據庫操作,最好是採用 4 個隔離級別的最高級,串行化 Serializable,杜絕所有髒讀、不可重複讀、幻讀的可能性。也就是不採用任何併發,多線程,甚至異步去讀寫數據庫的關鍵信息,所有操作在一個線程內完成。實現操作的冥等。

不要輕易去挑戰併發編程。這種系統出了問題大概都無從下手,屬於軟件開發裏最難發現的 bug。能把這類活完成得乾淨利落的,都是 5 萬+月薪級別的程序員。

我能想到的不多的採用多線程或者異步(多線程和異步,建議採用多線程,因爲異步在 Python 裏就跟傳染病似的,一旦用了,一條調用鏈上都得用它纔行)確實有益處的地方,就是發送釘釘告警信號之類。因爲釘釘服務在中國,而幣圈交易所服務器又都在海外,你的代碼服務器應該佈置在海外,更靠近交易所,所以發送釘釘,有時候要等好幾秒才返回,有時候甚至阻塞十幾秒都有。

最後

其實很多事情做不到也不要緊。只要有接受大一點滑點的覺悟,也就是可能吐出差不多 1/5 甚至更多的利潤出來。重要的還是策略,收益正期望,能夠不停機一直運行纔是王道。

總之,實盤的第一要務是持續穩定執行策略的既定邏輯。滑點是交易的一部分,盡力減少就行了,只要不傷筋動骨。

一流策略,2、3 流實盤實現,問題不大。但是如果策略不過關,頂級實盤代碼也無濟於事,該虧也還是得虧。所以光是代碼寫得好,是做不好量化交易的。策略好,實盤代碼也健壯,當然是最好的。不過,在時間有限的情況下,要把主要精力放在策略上。

To be continued