期貨量化交易軟件:交易機器人的虛假觸發(fā)保護
概論
本文討論了多種方式來增加交易機器人的操作穩(wěn)定性, 譬如消減可能的重復觸發(fā) (抖動): 既可以分別使用入場和離場算法,也可將它們連結。
問題的本質
若是當前蠟燭條幅度過高, 并且在交易機器人里沒有提供預防抖動措施, 則虛假觸發(fā)問題在暴跌暴漲行情中尤為突出。它會導致在當前蠟燭條上連續(xù)重復開單、平倉。
依據(jù)行情的特殊算法以及交易機器人的開發(fā)者所設置的參數(shù), 其結算后果是變化的。在所有情況下, 交易者的點差開銷會隨著抖動期間觸發(fā)數(shù)量而成比例增加。
在本文中, 我不會涉及金融工具 (技術和基本面特征) 分析的話題, 這能夠影響智能交易程序操作的穩(wěn)定性, 并有助于避免散射 (這是一個單獨的話題 — 我是脈沖均衡理論及其應用系統(tǒng)的作者)。在此, 赫茲期貨量化重點關注那些軟件手段, 而非直接依賴金融市場分析的方法。
所以, 赫茲期貨量化來著手解決問題。作為一個示例, 我將使用來自 МetaТrader 4 客戶端標準集里提供的 "MACD 樣本" EA。
如圖例所示 EURUSD 價格在當年的十月份第二天飆升 (М15 時間幀, "MACD 樣本" EA 省缺設置), 它可直觀解釋散射問題:

編輯切換為居中
屏幕截圖清楚顯示在單根蠟燭條里有 8 個連串的觸發(fā) (買進入場)。它們之中只有 1 個是正確的 (按照正常的行情邏輯條件), 其余 7 個是散射。
在這種特殊情況下虛假觸發(fā)背后的原因是:
省缺設置的止盈數(shù)值太小 (離場算法), 所以每筆持倉被很快平倉;
還由于雙重入場, "MACD 樣本" EA 的入場算法在前一單平倉之后被觸發(fā), 即使在此跟蠟燭條上已經(jīng)數(shù)次入場。
赫茲期貨量化已經(jīng)同意, 過濾行情波動的事項不是考慮的目地 (因為每位交易者有自己的入場和離場算法), 所以為了解決問題, 我們考慮以下更普遍的因素:
時間 (蠟燭條長度),
出發(fā)數(shù)量 (計數(shù)器),
運動幅度 (蠟燭條范圍)。

編輯切換為居中
入場算法里的解決方案
最簡單同時也是最可靠地固定入場點的方法是通過時間因素, 其原因有以下幾點:
觸發(fā)數(shù)量計數(shù)器要在程序里循環(huán)創(chuàng)建, 不僅算法復雜, 而且降低智能交易程序的速度。
幅度和相關的價位控制可以重復, 因為蠟燭條逆轉時返回的價格令價位的標準參差不齊。
時間是不可逆的, 僅在一個方向 (增加) 移動, 所以, 它最準確, 甚至是通過一次性觸發(fā)解決問題或消除散射的準則。
這種方式, 主要因素是入場算法的觸發(fā)時刻, 更加具體的是, 開倉所需的訂單觸發(fā)時刻 (OrderSend), 因為這兩個時刻也許不相符, 如果在算法里有一些特別的開單延遲。
因此, 赫茲期貨量化要記住開倉的時刻 (當前時間)。但如何在入場算法里使用這個參數(shù), 以便在指定的蠟燭條上禁止隨后的重復入場?我們無法預先知道這一時刻 (其絕對值), 所以我們不能在入場算法里預先輸入它。算法應考慮 (包括) 一些通常的條件來解決在蠟燭條上的首次入場, 且無需計算觸發(fā)即可禁止在蠟燭條上的后續(xù)入場 (我們之前拒絕的帶計數(shù)器的選項)。
此解決方案是相當簡單的。首先, 我將會編寫一些帶注釋的代碼, 然后將會澄清更多細節(jié)。這是一段輔助代碼 (以黃色加亮), 需要放置于交易 EA 的算法里 (參看 MACD_Sample_plus1.mq4):
//+------------------------------------------------------------------+ //| ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?MACD Sample.mq4 | //| ? ? ? ? ? ? ? ? ? ? ? ? ? ?版權所有 2005-2014, MetaQuotes 軟件公司| //| ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.mql4.com | //+------------------------------------------------------------------+ #property copyright ? "2005-2014, MetaQuotes 軟件公司" #property link ? ? ? ?"https://www.mql4.com" input double TakeProfit ? ?=50; input double Lots ? ? ? ? ?=0.1; input double TrailingStop ?=30; input double MACDOpenLevel =3; input double MACDCloseLevel=2; input int ? ?MATrendPeriod =26; //--- 輸入新變量 (此時間幀內一根柱線的秒數(shù), 對于 М15 等于 60 с х 15 = 900 с) datetime Time_open=900; //--- 輸入新變量 (柱線開盤時間, 首次入場) datetime Time_bar = 0; //+------------------------------------------------------------------+ //| ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?| //+------------------------------------------------------------------+ void OnTick(void) ?{ ? double MacdCurrent,MacdPrevious; ? double SignalCurrent,SignalPrevious; ? double MaCurrent,MaPrevious; ? int ? ?cnt,ticket,total; //--- // 初始數(shù)據(jù)檢查 // 它對于確保程序能在正常圖表上工作十分重要 // 而且用戶在設置外部變量時不可出錯 // (Lots, StopLoss, TakeProfit, // TrailingStop), 在我們的例子中, 我們檢查止盈 // 在圖表上是否小于 100 根柱線 //--- ? if(Bars<100) ? ? { ? ? ?Print("柱線數(shù)小于 100"); ? ? ?return; ? ? } ? if(TakeProfit<10) ? ? { ? ? ?Print("TakeProfit 小于 10"); ? ? ?return; ? ? } //--- 為了簡化代碼, 并加速存取, 數(shù)據(jù)被放到內部變量 ? MacdCurrent=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_MAIN,0); ? MacdPrevious=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_MAIN,1); ? SignalCurrent=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_SIGNAL,0); ? SignalPrevious=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_SIGNAL,1); ? MaCurrent=iMA(NULL,0,MATrendPeriod,0,MODE_EMA,PRICE_CLOSE,0); ? MaPrevious=iMA(NULL,0,MATrendPeriod,0,MODE_EMA,PRICE_CLOSE,1); ? total=OrdersTotal(); ? if(total<1) ? ? { ? ? ?//--- 未識別出已開訂單 ? ? ?if(AccountFreeMargin()<(1000*Lots)) ? ? ? ?{ ? ? ? ? Print("我們沒有資金。Free Margin = ",AccountFreeMargin()); ? ? ? ? return; ? ? ? ?} ? ? ?//--- 檢查多頭倉位 (買入) 的可行性 ? ? ? ? ? ?//--- 輸入新字符串 (若新柱線開盤, 刪除重復入場禁止標志) ? ? ?if( (TimeCurrent() - Time_bar) > 900 ) Time_open = 900; ? ? ? ? ? ?if(MacdCurrent<0 && MacdCurrent>SignalCurrent && MacdPrevious<SignalPrevious && ? ? ? ? MathAbs(MacdCurrent)>(MACDOpenLevel*Point) && MaCurrent>MaPrevious && ? ? ? ? (TimeCurrent()-Time[0])<Time_open) //輸入新字符串至入場算法 (僅執(zhí)行一次, 此蠟燭條上的條件以后不能完成) ? ? ? ?{ ? ? ? ? ticket=OrderSend(Symbol(),OP_BUY,Lots,Ask,3,0,Ask+TakeProfit*Point,"macd sample",16384,0,Green); ? ? ? ? if(ticket>0) ? ? ? ? ? { ? ? ? ? ? ?if(OrderSelect(ticket,SELECT_BY_TICKET,MODE_TRADES)) ? ? ? ? ? ? { ? ? ? ? ? ? ?Print("買入訂單已開 : ",OrderOpenPrice()); ? ? ? ? ? ? ?Time_open = TimeCurrent()-Time[0]; //輸入新字符串 (保存入場時的柱線開盤時間到離場時刻的間隔) ? ? ? ? ? ? ?Time_bar = Time[0]; //輸入新字符串 (記住柱線開盤時間已有首次入場) ? ? ? ? ? ? } ? ? ? ? ? } ? ? ? ? else ? ? ? ? ? ?Print("買入開單出錯 : ",GetLastError()); ? ? ? ? return; ? ? ? ?}
閱讀更多:
替代絕對時間 (入場時可), 赫茲期貨量化使用相對時間 —自當前蠟燭條開盤時刻至入場時刻的時間缺口。此數(shù)值與預先設定的進行比較, 較大時間值 (整根蠟燭條的長度), 允許觸發(fā)首次入場。在開倉時刻, 我們修改 (降低) Time_open 變量的數(shù)值, 寫入自蠟燭條開盤到實際收盤時刻之間的缺口值。并且由于在隨后的任意時刻, 數(shù)值 (TimeCurrent() - Time[0]) 將會超出我們寫入的入場點數(shù)值, 則 (TimeCurrent() - Time[0]) < Time_open 條件仍將是不可能的, 即通過阻塞此蠟燭條上隨后的入場來達成。
這樣, 無需任何入場數(shù)量計數(shù)器, 以及分析價格變動的幅度, 我們就解決了虛假觸發(fā)的問題。
以下是 EA 的初始入場算法經(jīng)過簡單改進后的結果 ("MACD Sample_plus1"):

編輯切換為居中
赫茲期貨量化看到, 在一根蠟燭條上只有一次入場, 不存在任何虛假觸發(fā), 且散射完全消除。省缺設置全部保存, 所以很顯然, 這個問題在不改變 EA 設置的協(xié)助下得以解決。
現(xiàn)在入場的散射問題得以解決, 赫茲期貨量化將改進入場算法以便排除快速平倉時可能的散射, 在這種特殊情況下增加盈利 (脈沖很不錯, 快速離場, 早發(fā))。
離場算法里的解決方案
由于最初的問題涉及如何消除交易機器人的散射可能性, 而非增加盈利, 那么在此話題里我將不會考慮分析動態(tài)金融工具的相關問題, 并通過固定選擇的參數(shù)限制我自己, 這種動態(tài)不予考慮。
之前, 我們已經(jīng)使用了一個安全性參數(shù)和時間因素, 赫茲期貨量化將用它再次嚴格規(guī)范依照時間平倉的時刻, 具體而言, 緊隨蠟燭條開盤的關鍵點 (入場之后)。在離場算法中的這一時刻, 我們將顯示為:
if(!OrderSelect(cnt,SELECT_BY_POS,MODE_TRADES)) ? ? ? ? continue; ? ? ?if(OrderType()<=OP_SELL && ? // 檢查已開倉位 ? ? ? ? OrderSymbol()==Symbol()) ?// 檢查品種 ? ? ? ?{ ? ? ? ? //--- 已開多頭倉位 ? ? ? ? if(OrderType()==OP_BUY) ? ? ? ? ? { ? ? ? ? ? ?//--- 應平倉否? ? ? ? ? ? ?if(/* MacdCurrent>0 && MacdCurrent<SignalCurrent && MacdPrevious>SignalPrevious && // 刪除 MACD 離場觸發(fā)代碼, 不去干擾平倉的新條件 (看之后) ? ? ? ? ? ? ? MacdCurrent>(MACDCloseLevel*Point) && ? ? ? ? ? ? */ ? ? ? ? ? ? ? Bid > OrderOpenPrice() && ?// enter new string - optional (price in a positive area in regards to the entry level) ? ? ? ? ? ? ? TimeCurrent() == Time[0] ) // 輸入新字符串 (立場算法的簡單實現(xiàn): 離場限制在當前蠟燭條的開盤時刻) ? ? ? ? ? ? ?{ ? ? ? ? ? ? ? //--- 平倉退出 ? ? ? ? ? ? ? if(!OrderClose(OrderTicket(),OrderLots(),Bid,3,Violet)) ? ? ? ? ? ? ? ? ?Print("平倉錯誤 ",GetLastError()); ? ? ? ? ? ? ? return; ? ? ? ? ? ? ?}