楽になりエラーが少なくてすむように EA コードを短くする方法
はじめに
インディケータであろうとグラフィカルな描画であろうと、テクニカル分析を基にしたトレーディングシステムは数多く存在します。そしてそれらは重要な特性を持っています。というのは、トレーディングの方向におけるそのようなシステムの対称性です。この特性により、そのシステムにおけるトレードシグナルとトレード注文を出す力学は、一般的に方向に対する相対として表わされます。
以下に述べるシンプルな方法により、そのような対称的なシステムを基にして Expert Advisor のコード長を大幅に減らすためこの特性を効率的に利用することができます。この方法を使用するExpert Advisor はトレードシグナルを検出し、ロングポジション、ショートポジション両方に対してトレード注文を生成するために同じコードを利用します。
対称システムに基づく Expert Advisor を作成するのは一般的な方法で、まずは一方向でのトレードシグナルの生成と処理をコード化し、そしてそのコードをコピーしてもう一方向用に改良するのです。この場合、エラーをひじょうに起こしやすく、そしてそのようなエラーを見つけるのはひじょうにむつかしいのです。よってExpert Advisor のロジックで潜在的なエラーを減らすことは、考察される方法のもう一つの利点です。
1. Expert Advisor の埋め込みのトレード方向に関してのインバリアント
検討中のコンセプトはトレード方向に基づきます。この方向はロング(買いシグナルと注文)、ショート(売りシグナルと注文)のどちらかです。われわれの目標はコードが現在のトレード方向に関して不変であるように Expert Advisor を書くことです。テキストが長くならないよう、トレード方向に向かってのみ不変であることを考慮し、これをコード インバリアントと呼びます。
このために、関数または変数を入れます。その値は常に現トレードの方向を可能性ある2つの値で示します。
コードにおけるこの変数の表記法はひじょうに重要な部分です。bool タイプがこういった目的に適するように思えますが、少しばかり違う表記法を使う方がより効率的であると思われます。それは整数タイプです。トレード方向自体は以下のようにコード化されます。
- ロングのトレード方向:+1
- ショートのトレード方向:-1
理論的なものに比べこの表記法の利点は、従来の方法で用いられている条件分岐を行わずExpert Advisor のコード内で多様な計算や確認をを効果的に行うのに使用できることです。
2. 従来のコードからインバリアント コードに変更する方法例
いくつかの例を用いてこの内容を明確にします。ただし、のちに繰り返し使用することになる補助関数をいくつか考察することから始めます。
int sign( double v ) { if( v < 0 ) return( -1 ); return( 1 ); } double iif( bool condition, double ifTrue, double ifFalse ) { if( condition ) return( ifTrue ); return( ifFalse ); } string iifStr( bool condition, string ifTrue, string ifFalse ) { if( condition ) return( ifTrue ); return( ifFalse ); } int orderDirection() { return( 1 - 2 * ( OrderType() % 2 ) ); }
sign() 関数の目的は明らかです。それは引数の正の値に対して1を返し、負の値に対して -1 を返します。
関数 iif() は C 言語の演算子 "condition ?ifTrue : ifFalse" と同じです。それは、大幅に簡素化するインバリアントな Expert Advisor をより簡潔で典型的なものにすることができます。それはdouble タイプの引数を取ります。そのためこのタイプと int および datetime タイプの両方の値に使用可能です。文字列との同様の作業に対しては、stringタイプの値を取る完全に類似体の関数 iifStr() が必要となります。
関数 orderDirection() は、トレード方向をどのように表記するかについてのわれわれの取り決めに従って、現トレード注文の方向(すなわち関数 OrderSelect() によって選択されたもの)を返します。
そのようなトレード方向のコード化へのインバリアント法により、どのように Expert Advisor のコードを簡素化できるか、具体的な例で検討します。
2.1 例 1 トレーリングストップ実行の変換
一般的なコード:
if( OrderType() == OP_BUY ) { bool modified = OrderModify( OrderTicket(), OrderOpenPrice(), Bid - Point * TrailingStop, OrderTakeProfit(), OrderExpiration() ); int error = GetLastError(); if( !modified && error != ERR_NO_RESULT ) { Print( "Failed to modify order " + OrderTicket() + ", error code: " + error ); } } else { modified = OrderModify( OrderTicket(), OrderOpenPrice(), Ask + Point * TrailingStop, OrderTakeProfit(), OrderExpiration() ); error = GetLastError(); if( !modified && error != ERR_NO_RESULT ) { Print( "Failed to modify order " + OrderTicket() + ", error code: " + error ); } }
インバリアント コード:
double closePrice = iif( orderDirection() > 0, Bid, Ask ); bool modified = OrderModify( OrderTicket(), OrderOpenPrice(), closePrice - orderDirection() * Point * TrailingStop, OrderTakeProfit(), OrderExpiration() ); int error = GetLastError(); if( !modified && error != ERR_NO_RESULT ) { Print( "Failed to modify order " + OrderTicket() + ", error code: " + error ); }
まとめ:
- 重い条件分岐をなんとか避けることができました。
- 最初の2つの関数の代わりに1つだけ文字列呼び出し関数 OrderModify() を使いました。
- そして、アプリケーション(2)として、エラー処理のためにコードを短くしました。
ストップレベルを計算する演算式ですぐにトレード注文方向を利用するため、OrderModify() の呼出しは一度のみ行ったことに留意ください。トレード方向の論理表現を用いる場合には、それは不可能でしょう。
基本的に、経験ある Expert Advisor のプログラマーであれば、従来の方法によって OrderModify() を一度だけ呼び出すことができると思います。ただし、われわれの場合は、これはまったく当然のことで、それ以上のステップは必要ありません。
2.2 例 2:トレードシグナル方向の変換
例として、2 つの移動平均システム内でのトレードシグナル検出を考察します。
double slowMA = iMA( Symbol(), Period(), SlowMovingPeriod, 0, MODE_SMA, PRICE_CLOSE, 0 ); double fastMA = iMA( Symbol(), Period(), FastMovingPeriod, 0, MODE_SMA, PRICE_CLOSE, 0 ); if( fastMA > slowMA + Threshold * Point ) { // open a long position int ticket = OrderSend( Symbol(), OP_BUY, Lots, Ask, Slippage, 0, 0 ); if( ticket == -1 ) { Print( "Failed to open BUY order, error code: " + GetLastError() ); } } else if( fastMA < slowMA - Threshold * Point ) { // open a short position ticket = OrderSend( Symbol(), OP_SELL, Lots, Bid, Slippage, 0, 0 ); if( ticket == -1 ) { Print( "Failed to open SELL order, error code: " + GetLastError() ); } }
ここでトレード方向に関してコードインバリアントを作成します。
double slowMA = iMA( Symbol(), Period(), SlowMovingPeriod, 0, MODE_SMA, PRICE_CLOSE, 0 ); double fastMA = iMA( Symbol(), Period(), FastMovingPeriod, 0, MODE_SMA, PRICE_CLOSE, 0 ); if( MathAbs( fastMA - slowMA ) > Threshold * Point ) { // open a position int tradeDirection = sign( fastMA - slowMA ); int ticket = OrderSend( Symbol(), iif( tradeDirection > 0, OP_BUY, OP_SELL ), Lots, iif( tradeDirection > 0, Ask, Bid ), Slippage, 0, 0 ); if( ticket == -1 ) { Print( "Failed to open " + iifStr( tradeDirection > 0, "BUY", "SELL" ) + " order, error code: " + GetLastError() ); } }
コードがより簡潔になったのが断然明らかだと思います。そして当然、2つのエラー チェックは1つになりました。
上記の例はひじょうにシンプルではあるものの、検討している方法の主なメリットはひじょうにわかりやすくなっています。より複雑なケースでは、従来の方法と検討中の方法の差はもっと大きくなることもあります。標準 Expert Advisor の MACD サンプルでそれを確認します。
3. MACD サンプルを単純化する方法
記事が長くならないように、ここではこの Expert Advisor のコード全体については考察しません。考察するコンセプトで変更するコード領域に行きます。
この EA のフルコードは MetaTrader 4 配布セットに入っています。またみなさんの便宜のため、その単純化バージョン(MACD Sample-2.mq4)とともに本稿にも添付しています(ファイル MACD Sample.mq4 )。
トレードシグナル検出のために書かれたブロックから始めます。以下がその初期コードです。
// check for long position (BUY) possibility if(MacdCurrent<0 && MacdCurrent>SignalCurrent && MacdPrevious<SignalPrevious && MathAbs(MacdCurrent)>(MACDOpenLevel*Point) && MaCurrent>MaPrevious) { 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("BUY order opened : ",OrderOpenPrice()); } else Print("Error opening BUY order : ",GetLastError()); return(0); } // check for short position (SELL) possibility if(MacdCurrent>0 && MacdCurrent<SignalCurrent && MacdPrevious>SignalPrevious && MacdCurrent>(MACDOpenLevel*Point) && MaCurrent<MaPrevious) { ticket=OrderSend(Symbol(),OP_SELL,Lots,Bid,3,0,Bid-TakeProfit*Point, "macd sample",16384,0,Red); if(ticket>0) { if(OrderSelect(ticket,SELECT_BY_TICKET,MODE_TRADES)) Print("SELL order opened : ",OrderOpenPrice()); } else Print("Error opening SELL order : ",GetLastError()); return(0); }
上記の方法によって、売りシグナルと買いシグナル両方に対して同じ方法でコードを書き直します。
int tradeDirection = -sign( MacdCurrent ); // check if we can enter the market if( MacdCurrent * tradeDirection < 0 && ( MacdCurrent - SignalCurrent ) * tradeDirection > 0 && ( MacdPrevious - SignalPrevious ) * tradeDirection < 0 && MathAbs(MacdCurrent)>(MACDOpenLevel*Point) && ( MaCurrent - MaPrevious ) * tradeDirection > 0 ) { int orderType = iif( tradeDirection > 0, OP_BUY, OP_SELL ); string orderTypeName = iifStr( tradeDirection > 0, "BUY", "SELL" ); double openPrice = iif( tradeDirection > 0, Ask, Bid ); color c = iif( tradeDirection > 0, Green, Red ); ticket = OrderSend( Symbol(), orderType, Lots, openPrice, 3 , 0, openPrice + tradeDirection * TakeProfit * Point, "macd sample", 16384, 0, c ); if(ticket>0) { if(OrderSelect(ticket,SELECT_BY_TICKET,MODE_TRADES)) Print( orderTypeName + " order opened : ", OrderOpenPrice() ); } else Print("Error opening " + orderTypeName + " order : ",GetLastError()); return(0); }
ここで、オープンしているポジションをクローズし、トレーリングストップを処理するブロックに行きます。前のようにその最初のバージョンをまず調べます。
if(OrderType()==OP_BUY) // long position is opened { // should it be closed? if(MacdCurrent>0 && MacdCurrent<SignalCurrent && MacdPrevious>SignalPrevious && MacdCurrent>(MACDCloseLevel*Point)) { OrderClose(OrderTicket(),OrderLots(),Bid,3,Violet); // close position return(0); // exit } // check for trailing stop if(TrailingStop>0) { if(Bid-OrderOpenPrice()>Point*TrailingStop) { if(OrderStopLoss()<Bid-Point*TrailingStop) { OrderModify(OrderTicket(),OrderOpenPrice(),Bid-Point*TrailingStop, OrderTakeProfit(),0,Green); return(0); } } } } else // go to short position { // should it be closed? if(MacdCurrent<0 && MacdCurrent>SignalCurrent && MacdPrevious<SignalPrevious && MathAbs(MacdCurrent)>(MACDCloseLevel*Point)) { OrderClose(OrderTicket(),OrderLots(),Ask,3,Violet); // close position return(0); // exit } // check for trailing stop if(TrailingStop>0) { if((OrderOpenPrice()-Ask)>(Point*TrailingStop)) { if((OrderStopLoss()>(Ask+Point*TrailingStop)) || (OrderStopLoss()==0)) { OrderModify(OrderTicket(),OrderOpenPrice(),Ask+Point*TrailingStop, OrderTakeProfit(),0,Red); return(0); } } } }
このコードを、トレード方向に関してインバリアントなコードに変換します。
tradeDirection = orderDirection(); double closePrice = iif( tradeDirection > 0, Bid, Ask ); c = iif( tradeDirection > 0, Green, Red ); // should it be closed? if( MacdCurrent * tradeDirection > 0 && ( MacdCurrent - SignalCurrent ) * tradeDirection < 0 && ( MacdPrevious - SignalPrevious ) * tradeDirection > 0 && MathAbs( MacdCurrent ) > ( MACDCloseLevel * Point ) ) { OrderClose(OrderTicket(),OrderLots(), closePrice, 3,Violet); // close position return(0); // exit } // check for trailing stop if(TrailingStop>0) { if( ( closePrice - OrderOpenPrice() ) * tradeDirection > Point * TrailingStop ) { if( OrderStopLoss() == 0 || ( OrderStopLoss() - ( closePrice - tradeDirection * Point * TrailingStop ) ) * tradeDirection < 0 ) { OrderModify( OrderTicket(), OrderOpenPrice(), closePrice - tradeDirection * Point * TrailingStop, OrderTakeProfit(), 0, c ); return(0); } } }
この EA の最初のバージョンはトレーリングストップを処理するとき、ショートポジションに対してのみ条件 OrderStopLoss() == 0 を確認することにご注意ください。これは、たとえば初期ストップレベルを設定しなかった場合(たとえば市場価格に近すぎることにより)、状況を処理するのに必要なことなのです。
この条件がロングポジションい対して確認されないことは、コピー&ペーストでそのような対称的な Expert Advisor を書くにあたり非常に一般的なエラーとみなされます。
このエラーは、改善後のコードでは双方向に対し自動で修正されることに留意ください。また、このエラーがインバリアント コードを書かせるとしたら、エラーはロングポジション、ショートポジション両方を処理するときに発生するだろうということにも注意が必要です。これは検証中にそれを検出する確率を高めるであろうことは言うまでもありません。
まあ、それについては以上です。同一データ、同一設定で両方の Expert Advisor を検証するなら、それらは全く同じであることがわかるでしょう。ですが、単純化バージョンはずっと簡潔で維持しやすいものです。
4. 『最初から』対称的 Expert Advisor を書くことの薦め
これまで、Expert Advisor の従来コードをインバリアントなコードに変更する方法の可能性について考察しました。上記の原則に基づいて『最初から』売買ロボットを作成することはもっと効果的です。
一見、トレード方向に関して条件やインバリアントな表現を考え出すには、一定のスキルと経験が必要なので、簡単なことには思えません。ですが、いくらか練習を積めば、このスタイルでコードを書くことそれだけは簡単になります。
より効果的な方法でスタートするのに役立つような推奨事項をいくらか提供しようと思います。
- 何かコード領域を作成するときは、まずロングのトレード方向を処理します。ほとんどの場合、インバリアント コードを合成するともっと簡単です。というのも、このトレード方向は値 +1 によって表され、インバリアントな関係を書き、分析するとき、あまりすることはないのです。
- ロング方向でスタートしたら、まずトレード方向に影響を与える変数/関数なしで条件を書くようにします。式が正しいことを確認して、そこにトレード方向を追加します。いくらか経験を積んだら、このステップ除算なしで継続できます。
- ロングのトレード方向拘らないようにします。ショートの方向に対する条件を述べる方が効果的なこともあるのです。
- 算術計算ができる場面では、条件分岐や関数 iif() の使用は避けるようにします。
最後の節については、条件分岐なしではできない状況があることを付け加えようと思います。ただし、そのような状況をプールするようにして、特定の EA に依存しないトレード方向のヘルパー関数を分離するためにそれらを使用します。こういった関数は、前述の関数 sign()、iif()、orderDirection() 同様、みなさんの Expert Advisor すべてでのちに使用することになる特殊ライブラリに対して使用されます。
もう一度、すべてを明確にするために以下の課題について考察します。
「トレード注文では、ストップレベルがロングポジションに対する前回バーの最小レベルで、ショートポジションに対する前回バーの最大レベルでなければならない。」
コードでは以下のように表現します。
double stopLevel = iif( tradeDirection > 0, Low[ 1 ], High[ 1 ] );
簡単で明確に思えますが、このシンプルな構造でさえ、繰り返し使えるように小さい単純な関数に貯めておくことができる、また貯めておく必要があります。
条件演算子を汎用的なヘルパー関数に入れることで、それを避けます。
double barPeakPrice( int barIndex, int peakDirection ) { return( iif( peakDirection > 0, High[ barIndex ], Low[ barIndex ] ) ); }
ストップレベルの計算は以下のように書きます。
double stopLevel = barPeakPrice( 1, -tradeDirection );
差が歴然としているように思えても、第一印象に引きずられないようにします。このバリアントには以下のような大きい利点があります。
- インバリアントな形式で目的を明示的に表現する。
- 適切なスタイルで EA のコードを書くことを促す。
- 読みやすく、のちの作成を簡単にする。
これはほんの一例です。実際、Expert Advisor のコードの数多くの標準エレメントが同様の形式で表現可能です。みなさんもご自身でそれができるようになります。ご自分のコードを分析し、この方法で改良することを強くお薦めします。
5. なぜすべてこうなのか?
上述のコンセプトには以下のような重大なメリットがあると私は思います。
- 機能性を失うことなくソースコードを減らし、その後、トレーディングシステム開発と調整の際、かかる時間が削減される。
- 潜在的エラー数を削減する。
- 既存のエラー検出の可能性を高める。
- さらなる Expert Advisor の変更を簡素化する(ロング、ショートシグナルおよびポジションへの変更が自動でおこなわれる)。
デメリットは1つしか見当たりませんでした。それは、このコンセプトは、初期段階で理解と学習にわずかな困難がある、ということです。ですが、このデメリットは上記でリストアップしたメリットで補って余りあるものです。そのうえ、これは単純に時間といくばくかの経験の問題にすぎません。また、インバリアント コードの作成は当然で簡単なものとなります。
おわりに
MQL4 で Expert Advisor のコードを書く上記の方法は、トレード方向の概念の利用と効果的な表記に基づくものです。それにより、従来の方法により書かれた Expert Advisor ではふつうに見られる実質上同一コード領域の一部が重複するのを避けることができます。説明している方法を利用することで、それによるすべてのメリットを備え、ソースコードのボリュームを基本的に削減することにつながります。
例は、初心者の方、売買システムの作成経験が多少ある方がお望みならご自身の既存のコードを念入りに作成するのに役立つよう提供されています。これら例は私の推奨事項で実装されていますが、トレード方向に関して、簡潔なコードをインバリアントとして書くのに役立つものです。
考察されている例は、読み易く、問題が理解しやすいようにかなりシンプルなものです。ですが、説明されている方法は、トレンドライン、チャンネル、アンドリューズピッチフォーク、エリオット波動など、その他市場分析の中、高度なテクニカル分析に応用される、より複雑なシステムを実行するのに問題なく利用されています。
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/1491
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索