
トレードロボットのプロトタイプ
はじめに
どのトレーディングシステムのライフサイクルもポジションの開始と終了に集約される。これは疑いのない事実である。しかし、ことアルゴリズムの実現になると、プログラマーの数だけの見解があると言っても過言ではない。誰もが同じ問題を彼自身の方法で解くことができるが、しかし同じ最終結果に至る。
何年にもわたるプログラムつくりの実践を通じて、いくつかのエキスパートの論理と構造を構築するアプローチが試みられてきた。今では全てのコードで使われている明快なパターンのテンプレートを確立したと主張できる。
このアプローチは100%普遍的とはいえない、しかしあなたのエキスパート論理の設計の方法を変えるかもしれない。このケースはあなたがエキスパートを使いたいオーダーに使う能力のレベルにはない。全ての論点はトレーディングモデルの作成の原理である。
1. トレーディングシステム設計の原理とイベントソースのタイプ
主として使われているアルゴリズム設計への基本的アプローチは1つのポジションの開始から終了までをたどることである。これは線形なアプローチである。そしてコードに変更を加えたいと望むなら、非常に多くの条件が沸き起こりコードに新しい解析の枝が蓄積するので、それはしばしば大変な混乱に陥る。
トレーディングロボットのモデルへの最良の 解決は"条件に対応すること"である。基本的原理は、このエキスパートの条件とそのポジションとオーダーがいかにして起こったかを解析することではなく、それらを今処理するべき内容である。この基本原理はトレードの管理を根本的に変えつつありコードの開発を簡単化する。
そのことをさらに詳細に考察する。
1.1. 「条件に対応」の原理
既に述べたように、エキスパートは現在の状況がいかにして形成されたかを知る必要はない。それをどうするべきかをその環境 (パラメーターの値、保存された オーダーのプロパティなど) に従って知る必要がある。
この原理はエキスパートがどのループ (特にチック(目盛)からチック) にも存在すると言う事実に直接関連し、前のチックでオーダーに何が起こったかについて気にするべきではない。 従って、マージンのオーダーにイベント駆動型のアプローチを採るべきである。 すなわち、エキスパートがその状態を保存している現在のチック上でということで、それは次のチックの判断の開始点である。
例えば、エキスパートの全ての 保留オーダーを除去すべきであり、そしてインジケーター の解析と新しいオーダーを続けるのみである。我々が見てきたコード例は "(true)の間はループを {除去するようにし}" あるいは少し和らげて " (k < 1000)ならば { k++のループを取り除く}"のである。 変形もスキップし、除去コマンドをエラー解析もなしで呼ぶ。
この方法は線形であり、無期限の時間エキスパートを非動作に置く。
従って、エキスパートをループさせないことがより正しく、オーダーを除去するためにオーダーを保存し、新しいチック毎にこのオーダーがチェックされ、一方保留のオーダーは除去を試みる。このケースでは、エキスパートは、状態パラメーターを読む間、その瞬間にオーダーを消去すべきであることを知っている。そして、それはそれらを除去しようと試みる。もしトレーディングにエラーが起こると、エキスパートは単純にそれ以降の解析を止め次のループに備えて働く。
1.2. 設計の第2の原則は、考慮される ポジションの方向 (買い/売り) 、通貨とチャートからの可能な限りの抽象化である。エキスパートファンクションの全てはそのような方法で組まれるべきであり、方向やシンボルが解析されるのはまれなケースで、本当に避けられない場合 (例えば、ポジションをオープンするのに特定することを避ける別の選択があるにも関わらず、価格の望ましい伸びを考慮するような場合)だけである。 常にそのような「低レベルの」設計を避けるようにする。これでコードそしてファンクションを書くプロセスを最低2回減らす。そしてそれらを「取引-独立」にする。
この原則の実装は、オーダーのタイプ、シンボルのパラメーターとそれに絡んでマクロファンクションで計算されるパラメーターの明示的な解析を置き換えることである。次の論説でこの実装の詳細をカバーする。
1.3. 第3の原則は – アルゴリズムを論理的な単位 (独立なモジュール) に分割すること)である。
実用的には、最良のアプローチはエキスパート操作を個別のファンクションに分離することである。エキスパートの全アルゴリズムを1つのファンクションに書くことは難しいことに同意するだろう。またそれは後に続く解析と編集を複雑にしてしまう。 そこで、MQL5ではそうはしない、これは環境をほぼ完全に制御できるようになっている。
そこで、論理部分 (例えば、オーダーの開始、追跡、終了) は環境パラメーターの解析とイベントについてもお互いに分離されて実装されるべきである。このアプローチで、エキスパートは設計が柔軟になる。既存のものに触れることなく新しい独立したモジュールを追加することができ、メインのコードを変えることなく既存のモジュールを無効にすることができる。
エキスパートシステムのイベントソースは次のものである:
1. インジケーター。 1つの例はインジケーターラインの値の解析、それらの交差、結合、など。また、インジケーターは:現在時刻、インターネットから得られたデータ、などであるかもしれない。多くの場合、インジケーターイベントはオーダーの開始と終了のシグナルに使われる。それらの調整は多くはないが (通常はストップロスの追跡あるいはインジケーターの保留のオーダー)。
例えば、インジケーターの実用的な実装はエキスパートを呼び,速い、もしくは、遅いMAの交差を解析しさらに交差の向きによってポジションの更なる開始とすることがある。
2. 既存のオーダー、ポジションとその状態例えば、現在の損益の額。ポジションの有り無し、あるいは保留のオーダー、終了したポジションの利得、など。これらのイベントの実用的な実装はずっと広くさらに多様であり、インジケーターのイベントに対するよりもっと多くの関連するオプションがある。
トレードイベントにのみ基づくエキスパートの最も簡単な例は、既存のポジションの平均を埋めそしてそれを希望する利益に出力するものである。すなわち、可能なポジション上の損失の存在は新しい平均化のオーダーをするイベントとなる。
あるいは、例えば、ストップロスの追跡。このファンクションは前のストップロスから特定の数のポイントの間価格が利得に動くイベントをチェックする。その結果、エキスパートは価格によって3つのストップロスを引く。
3. 外部イベント このようなイベントは純粋なエキスパートシステムでは起こらないが、一般に、判断をするために考慮されるべきである。これにはオーダー、ポジション、トレードエラーの処理、チャートイベントの処理 (オブジェクトの移動/作成/消去、ボタンを押す、など) の調整を含む。一般に、これらは来歴で検証できず、エキスパートが働いたときにのみ起こるイベントである。
このようなエキスパートの目立った例はグラフィカルなトレード制御を持ったトレード情報システムである。
2. CExpertAdvisor を基にしたクラス - エキスパートコンストラクター
トレードエキスパートの働きは何か?MQL-プログラムの一般的なやり取りの方式は下のダイヤグラムに示されている。
図 1. MQL-プログラム要素のやり取りの一般的な方式
この方式に見るように、まず作業ループ (これはチックあるいはタイマー信号) への入り口がある。この最初のブロックの段階で、このチックは処理なしでフィルターされることがある。これは、 エキスパートがチックごとに働く必要がない(新しいバーでのみでよい)、あるいはエキスパートが単に働くことを許されない時に行われる。
そしてプログラムは第2ブロックに進む、オーダーとポジションを扱うモジュール、そしてそれからやっとイベント処理ブロックがモジュールから呼び出される。各モジュールは関連するイベントのみ問い合わせることができる。
この方式は直接論理方式と呼ばれ、それはまず何をエキスパートがするのか (何のイベント処理モジュールが使われるのか) を決め、そしてその後でのみいかにそしてなぜそれを行うのか (イベントシグナルを得る) を実装するからである。
直接論理は我々の世界の認識と普遍的な論理と整合性がある。結局、人は最初に概念を固め、そしてそれを総括し、それからそれらの関係を分類し同定する。
エキスパートの設計もこの観点で例外ではない。まず、何をエキスパートはするべきなのか (ポジションを開けそして閉め、保護的ストップをかける) を宣言し、そしてそれからそれはどのイベントでいかにそれを行うべきかと特定する。しかし、とにかく逆はない:それはシグナルを受け取りそしてどこでいかにそれを処理するか考えること。これは逆の論理であり、用いないほうが良い、結果として大変な数の条件分枝を持つ厄介なコードを得ることになる。
ここに逆と直接論理の例をしめす開始/終了をRSIシグナルで起す。
- 逆論理 ではエキスパートはインジケーターの値を得ることから始め、そしてシグナルの方向とそのポジションでなすべきことをチェックし、買いを開始し、売りを終了し、あるいは逆に売りを開き、買いを終了する。すなわち、開始点はシグナルを得て解析することである。
- 直接論理 では何事も逆である。エキスパー-トは2つのモジュールポジションの開始と終了を持ち、それは単純にこれらのモジュールを実行する条件をチェックする。すなわち、開始モジュールに入ったのち、エキスパートはインジケーターの値を受け取りそしてそれが開始のシグナルかどうかをチェックする。そして、終了オーダーに入った後にエキスパートはそれがポジションを閉じる信号かどうかをチェックする。すなわち、入り口がなく、システム状態の解析のモジュールが独立に働いている (設計の第1原則)。
エキスパートを複雑にしたければ、第2の変形を用いれば第1のものより容易である。新しいイベントモジュールを作成するのはもうたくさんである。
そして第1の変形では信号処理の構造を更新しなければならない、あるいは別のファンクションとして貼り付けなければならない。
このアプローチをよりよく理解するために、ここに異なった作業方式が4つの異なったエキスパートの文脈としてある。
図 2. エキスパート実装の例
a). エキスパート、あるインジケーターのシグナルにのみ基づく。それはシグナルが変化したときに、ポジションを開始と終了を行うことができる。例 - MAエキスパート.
b). グラフィカルなトレード制御のあるエキスパート
c). インジケーターに基づくエキスパート、しかしストップロスと操作時間の追跡を追加する。例 - MAインジケーターによるトレンドでポジションを開始する。
d). インジケーターなしのエキスパート、ポジションの平均化付き新しいバーを開けたときに一度だけポジションのパラメーターを検証する。例 - 平均化エキスパート
3. エキスパートクラスの実装
上記に述べた全ての規則と用件を用いて、今後全てのエキスパートの基礎となるであろうクラスを作成する。
CExpertAdvisorクラスに入れるべき最小限の機能は次のようである:
1. 初期化
- 登録インジケーター
- パラメーターの初期値を設定する
- 要求されるシンボルと時間枠に合わせる
2. シグナルを得るファンクション
- 許される動作時間 (トレード周期)
- ポジションの開始/終了あるいはオーダーのためのシグナル
- フィルターの決定 (トレンド、時刻、など)
- スタート/ストップタイマー
3. サービスファンクション
- 開始の価格、SLとTPのレベル、オーダーの額をを計算
- トレード要求の送信 (開始、閉鎖、修正)
4. トレードモジュール
- シグナルとフィルターの処理
- ポジションとオーダーの制御
- エキスパートファンクションで働く:OnTrade(), OnTimer(), OnTester(), OnChartEvent()。
5. 初期化戻し
- 出力メッセージ、レポート
- チャートのクリア、インジケーターを降ろす
このクラスの全ての関数は3つのグループに分割される。入れ子になった一般方式とその説明が以下に示される。
図 3. エキスパートの入れ子ファンクションの方式
1. マクロファンクション
この小さな関数グループはオーダーのタイプ、シンボルのパラメーターとオーダーに設定される価格の値を取り扱うための基礎となる。これらのマクロファンクションは設計の第2原則 - 抽象化 - を提供する。これらはエキスパートで使われるシンボルの文脈で働く。
タイプを変換するマクロ ファンクションは市場の方向 - 買いか売りか - に応じて働く。従って、自身の定数を作成しないために、既存のもの ORDER_TYPE_BUY と ORDER_TYPE_SELLを用いる。ここにマクロ使用の例とその働きの結果の例を示す。
//--- Type conversion macro long BaseType(long dir); // returns the base type of order for specified direction long ReversType(long dir); // returns the reverse type of order for specified direction long StopType(long dir); // returns the stop-order type for specified direction long LimitType(long dir); // returns the limit-order type for specified direction //--- Normalization macro double BasePrice(long dir); // returns Bid/Ask price for specified direction double ReversPrice(long dir); // returns Bid/Ask price for reverse direction long dir,newdir; dir=ORDER_TYPE_BUY; newdir=ReversType(dir); // newdir=ORDER_TYPE_SELL newdir=StopType(dir); // newdir=ORDER_TYPE_BUY_STOP newdir=LimitType(dir); // newdir=ORDER_TYPE_BUY_LIMIT newdir=BaseType(newdir); // newdir=ORDER_TYPE_BUY double price; price=BasePrice(dir); // price=Ask price=ReversPrice(dir); // price=Bid
エキスパートを開発するときにマクロは処理の方向を指定せずそしてより簡潔なコードの作成を助ける。
2. サービスファンクション
これらの関数はオーダーとポジションを扱うよう設計されている。マクロファンクションのようにこれらも低レベルである。便宜上、これらは2つのカテゴリーに分割される;ファンクションと実行ファンクション。これらは1種類の行動のみ行い、いかなるイベントの解析も行わない。これらは上位エキスパートハンドラーからの命令を実行する。
情報ファンクションの例: 現在の保留オーダーの最高開始価格を見つける;ポジションが如何に終了されたか調べる - 利得あるいは損失;エキスパートのオーダーの数とチケットのリストを得る、など。
実行ファンクションの例: 特定のオーダーを終了する;ストップロスを特定のポジションで修正する、など。
このグループは最大。これはある種の機能性であり、エキスパートの全ルーチンワークがこれらに基づいている。非常に多数のこれらファンクションの例を https://www.mql5.com/ru/forum/107476のフォーラムで見つけることができる。このMQL5 標準ライブラリ は既にオーダーとポジションを発する仕事の部分 を引き受けるクラス、特にCTradeを含んでいる。
しかしあなたのどのタスクも新しい実装を作成するか既存のものを少し修正する必要がある。
3. イベント処理モジュール
このファンクションのグループは最初の2つのグループの上の高レベルの上位構造である。上に述べたように、これらはあなたのエキスパートを構築するためのそのまますぐ使えるブロックである。一般に、それらはMQLのイベント処理 ファンクションに含まれている: OnStart(), OnTick(), OnTimer(), OnTrade(), OnChartEvent()。このグループはそれほど多くなく、その内容はいろいろなタスクに調節できる。しかし基本的に何も変化しない。
モジュールでは、同じモジュールが買いでも売りでも呼び出されるように、全てが抽象的であるべきである (第2設計原則)。 これはもちろんマクロの助けで達成される。.
それでは実装に進もう
。
1. 初期化と、定義n
class CExpertAdvisor { protected: bool m_bInit; // flag of correct initialization ulong m_magic; // magic number of expert string m_smb; // symbol, on which expert works ENUM_TIMEFRAMES m_tf; // working timeframe CSymbolInfo m_smbinf; // symbol parameters int m_timer; // time for timer public: double m_pnt; // consider 5/3 digit quotes for stops CTrade m_trade; // object to execute trade orders string m_inf; // comment string for information about expert's work
これがエキスパートファンクションが働くために最低限要求されるパラメーターのセットである。
m_smb と m_tfパラメーターは特にエキスパートプロパティに置かれ、エキスパートにどの通貨、そしてどの期間とするかを伝える。 例えば、もしm_smb = "USDJPY"と割り当てると、エキスパートはそのシンボルで走っているかに関わらず、そのシンボルで働く。もしtf = PERIOD_H1とセットすると、インジケーターの全てのシグナルと解析はチャートで行われる。
さらにクラスメソッドがある。最初のメソッドは初期化とエキスパートの初期化外し(ディイニシャライゼーション)である。
public: //--- Initialization void CExpertAdvisor(); // constructor void ~CExpertAdvisor(); // destructor virtual bool Init(long magic,string smb,ENUM_TIMEFRAMES tf); // initialization
基本クラスのコンストラクターとデストラクタは何も行わない。
Init() メソッドはエキスパートパラメータの初期化をシンボル、時間枠とマジック番号で行う。
//------------------------------------------------------------------ CExpertAdvisor void CExpertAdvisor::CExpertAdvisor() { m_bInit=false; } //------------------------------------------------------------------ ~CExpertAdvisor void CExpertAdvisor::~CExpertAdvisor() { } //------------------------------------------------------------------ Init bool CExpertAdvisor::Init(long magic,string smb,ENUM_TIMEFRAMES tf) { m_magic=magic; m_smb=smb; m_tf=tf; // set initializing parameters m_smbinf.Name(m_smb); // initialize symbol m_pnt=m_smbinf.Point(); // calculate multiplier for 5/3 digit quote if(m_smbinf.Digits()==5 || m_smbinf.Digits()==3) m_pnt*=10; m_trade.SetExpertMagicNumber(m_magic); // set magic number for expert m_bInit=true; return(true); // trade allowed }
2. シグナルを得るファンクション
これらのファンクションは市場とインジケーターを解析する。
bool CheckNewBar(); // check for new bar bool CheckTime(datetime start,datetime end); // check allowed trade time virtual long CheckSignal(bool bEntry); // check signal virtual bool CheckFilter(long dir); // check filter for direction
最初の2つのファンクションは特定の場合に使用され、そしてこのクラスのさらに子クラスに使われる。
//------------------------------------------------------------------ CheckNewBar bool CExpertAdvisor::CheckNewBar() // function of checking new bar { MqlRates rt[2]; if(CopyRates(m_smb,m_tf,0,2,rt)!=2) // copy bar { Print("CopyRates of ",m_smb," failed, no history"); return(false); } if(rt[1].tick_volume>1) return(false); // check volume return(true); } //--------------------------------------------------------------- CheckTime bool CExpertAdvisor::CheckTime(datetime start,datetime end) { datetime dt=TimeCurrent(); // current time if(start<end) if(dt>=start && dt<end) return(true); // check if we are in the range if(start>=end) if(dt>=start|| dt<end) return(true); return(false); }
後の2者は常にそれらのインジケーターに依存する。全てのケースでこれらのファンクションを設定することは単に不可能である。
重要な点として、CheckSignal() と CheckFilter() のシグナルファンクションはどのインジケーター、そして、いかなる組み合わせでも完全に解析できることを理解することは重要である。すなわち、これらのシグナルが引き続き含まれるトレードモジュールはソースから独立している。
これによって一旦エキスパートを書けばそれをテンプレートとして同様な原理で働くほかのエキスパートに使うことができる。 解析されるインジケーターを変更するか、あるいは新しいフィルタリング条件を加えるとよい。
3. サービスファンクション
既に述べたように、このグループのファンクションは最も数が多い。この論説で説明する実用的なタスクのためには4つのファンクションを実装すれば十分である。
double CountLotByRisk(int dist,double risk,double lot); // calculate lot by size of risk ulong DealOpen(long dir,double lot,int SL,int TP); // execute deal with specified parameter ulong GetDealByOrder(ulong order); // get deal ticket by order ticket double CountProfitByDeal(ulong ticket); // calculate profit by deal ticket
//------------------------------------------------------------------ CountLotByRisk double CExpertAdvisor::CountLotByRisk(int dist,double risk,double lot) // calculate lot by size of risk { if(dist==0 || risk==0) return(lot); m_smbinf.Refresh(); return(NormalLot(AccountInfoDouble(ACCOUNT_BALANCE)*risk/(dist*10*m_smbinf.TickValue()))); } //------------------------------------------------------------------ DealOpen ulong CExpertAdvisor::DealOpen(long dir,double lot,int SL,int TP) { double op,sl,tp,apr,StopLvl; // determine price parameters m_smbinf.RefreshRates(); m_smbinf.Refresh(); StopLvl = m_smbinf.StopsLevel()*m_smbinf.Point(); // remember stop level apr = ReversPrice(dir); op = BasePrice(dir); // open price sl = NormalSL(dir, op, apr, SL, StopLvl); // stop loss tp = NormalTP(dir, op, apr, TP, StopLvl); // take profit // open position m_trade.PositionOpen(m_smb,(ENUM_ORDER_TYPE)dir,lot,op,sl,tp); ulong order = m_trade.ResultOrder(); if(order<=0) return(0); // order ticket return(GetDealByOrder(order)); // return deal ticket } //------------------------------------------------------------------ GetDealByOrder ulong CExpertAdvisor::GetDealByOrder(ulong order) // get deal ticket by order ticket { PositionSelect(m_smb); HistorySelectByPosition(PositionGetInteger(POSITION_IDENTIFIER)); uint total=HistoryDealsTotal(); for(uint i=0; i<total; i++) { ulong deal=HistoryDealGetTicket(i); if(order==HistoryDealGetInteger(deal,DEAL_ORDER)) return(deal); // remember deal ticket } return(0); } //------------------------------------------------------------------ CountProfit double CExpertAdvisor::CountProfitByDeal(ulong ticket) // position profit by deal ticket { CDealInfo deal; deal.Ticket(ticket); // deal ticket HistorySelect(deal.Time(),TimeCurrent()); // select all deals after this uint total = HistoryDealsTotal(); long pos_id = deal.PositionId(); // get position id double prof = 0; for(uint i=0; i<total; i++) // find all deals with this id { ticket = HistoryDealGetTicket(i); if(HistoryDealGetInteger(ticket,DEAL_POSITION_ID)!=pos_id) continue; prof += HistoryDealGetDouble(ticket,DEAL_PROFIT); // summarize profit } return(prof); // return profit }
4. トレードモジュール
最後に、このグループのファンクションはサービス ファンクションとマクロを用いてトレーディングの全プロセスを結びつけ、シグナルとイベントを処理する。トレード操作の論理的要素は少なく、それらは特定の目的に依存している。しかし、ほとんど全てのエキスパートに存在する共通の概念を認めることができる。
virtual bool Main(); // main module controlling trade process virtual void OpenPosition(long dir); // module of opening position virtual void CheckPosition(long dir); // check position and open additional ones virtual void ClosePosition(long dir); // close position virtual void BEPosition(long dir,int BE); // moving Stop Loss to break-even virtual void TrailingPosition(long dir,int TS); // trailing position of Stop Loss virtual void OpenPending(long dir); // module of opening pending orders virtual void CheckPending(long dir); // work with current orders and open additional ones virtual void TrailingPending(long dir); // move pending orders virtual void DeletePending(long dir); // delete pending orders
これらのファンクションの特定の実装を以下の例で考察する。
新しいファンクションを加えることは難しいことではなく、それは正しいアプローチを選択し、エキスパートの構造を構成したからである。 この方式をきちんと使えば、設計は最小の努力と時間で済み、コードは1年後でも読みやすいはずである。
もちろん、エキスパートはこれらに限定されない。CExpertAdvisor クラスには、もっとも必要なメソッドのみを宣言している。 子クラスに新しいハンドラーを加え、既存のものを改造し、自身のモジュールを拡張し、1つのライブラリを作成できる。このようなライブラリをもっていれば、すぐ使える「ターンキー」 エキスパートの開発はは30分から2日でできる。
4. CExpertAdvisor クラスを用いた例
4.1. インジケーターシグナルに基づいた仕事の例最初の例として、最も簡単な仕事から始めよう - CExpertAdvisor クラスを用いたMovingAverage エキスパートアドバイザー (MetaTrader 5の基本的な例) 。ほんの少し複雑にしよう。
アルゴリズム:
a) ポジション開始の条件
- もし価格が MA を下から上にまたいだなら、買いのポジションを開始する。
- もし価格が MA を上から下ににまたいだなら、売りのポジションを開始する。
- SL を(ロス停止), TP を(益を採る)とセットする。
- ポジションのロットはRisk パラメーター (ストップロスが起こったらどれだけがデポジットから失われるか) で計算される。
b) 閉鎖ポジションの条件
- もし価格が MA を上から下にまたいだなら、売りのポジションを終了する。
- もし価格が MA を上から下にまたいだなら、買いのポジションを終了する。
c) 限界
- エキスパートの働きを毎日HourStart から HourEndまでとする。
- エキスパートは新しいバー上でのみトレード操作を行う。
d) ポジションのサポート
- 単純な追跡をTS の距離で停止する。
このエキスパートのために CExpertAdvisor クラスの7つのファンクションを必要とする。
- シグナルファンクション - CheckSignal()
- チックフィルター - CheckNewBar()
- 時間フィルター - CheckTime()
- ポジション開始のサービスファンクション - DealOpen()
- 3つの仕事モジュール - OpenPosition(), ClosePosition(), TrailingPosition()
この仕事を特別に解くには CheckSignal() ファンクションとモジュールは子クラスで定義される必要がある。また、インジケーターの初期化を加える必要がある。
//+------------------------------------------------------------------+ //| Moving Averages.mq5 | //| Copyright 2010, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2010, MetaQuotes Software Corp." #property link "https://www.mql5.com" #property version "1.00" #include "ExpertAdvisor.mqh" input double Risk = 0.1; // Risk input int SL = 100; // Stop Loss distance input int TP = 100; // Take Profit distance input int TS = 30; // Trailing Stop distance input int pMA = 12; // Moving Average period input int HourStart = 7; // Hour of trade start input int HourEnd = 20; // Hour of trade end //--- class CMyEA : public CExpertAdvisor { protected: double m_risk; // size of risk int m_sl; // Stop Loss int m_tp; // Take Profit int m_ts; // Trailing Stop int m_pMA; // MA period int m_hourStart; // Hour of trade start int m_hourEnd; // Hour of trade end int m_hma; // MA indicator public: void CMyEA(); void ~CMyEA(); virtual bool Init(string smb,ENUM_TIMEFRAMES tf); // initialization virtual bool Main(); // main function virtual void OpenPosition(long dir); // open position on signal virtual void ClosePosition(long dir); // close position on signal virtual long CheckSignal(bool bEntry); // check signal }; //------------------------------------------------------------------ CMyEA void CMyEA::CMyEA() { } //----------------------------------------------------------------- ~CMyEA void CMyEA::~CMyEA() { IndicatorRelease(m_hma); // delete MA indicator } //------------------------------------------------------------------ Init bool CMyEA::Init(string smb,ENUM_TIMEFRAMES tf) { if(!CExpertAdvisor::Init(0,smb,tf)) return(false); // initialize parent class m_risk=Risk; m_tp=TP; m_sl=SL; m_ts=TS; m_pMA=pMA; // copy parameters m_hourStart=HourStart; m_hourEnd=HourEnd; m_hma=iMA(m_smb,m_tf,m_pMA,0,MODE_SMA,PRICE_CLOSE); // create MA indicator if(m_hma==INVALID_HANDLE) return(false); // if there is an error, then exit m_bInit=true; return(true); // trade allowed } //------------------------------------------------------------------ Main bool CMyEA::Main() // main function { if(!CExpertAdvisor::Main()) return(false); // call function of parent class if(Bars(m_smb,m_tf)<=m_pMA) return(false); // if there are insufficient number of bars if(!CheckNewBar()) return(true); // check new bar // check each direction long dir; dir=ORDER_TYPE_BUY; OpenPosition(dir); ClosePosition(dir); TrailingPosition(dir,m_ts); dir=ORDER_TYPE_SELL; OpenPosition(dir); ClosePosition(dir); TrailingPosition(dir,m_ts); return(true); } //------------------------------------------------------------------ OpenPos void CMyEA::OpenPosition(long dir) { if(PositionSelect(m_smb)) return; // if there is an order, then exit if(!CheckTime(StringToTime(IntegerToString(m_hourStart)+":00"), StringToTime(IntegerToString(m_hourEnd)+":00"))) return; if(dir!=CheckSignal(true)) return; // if there is no signal for current direction double lot=CountLotByRisk(m_sl,m_risk,0); if(lot<=0) return; // if lot is not defined then exit DealOpen(dir,lot,m_sl,m_tp); // open position } //------------------------------------------------------------------ ClosePos void CMyEA::ClosePosition(long dir) { if(!PositionSelect(m_smb)) return; // if there is no position, then exit if(!CheckTime(StringToTime(IntegerToString(m_hourStart)+":00"), StringToTime(IntegerToString(m_hourEnd)+":00"))) { m_trade.PositionClose(m_smb); return; } // if it's not time for trade, then close orders if(dir!=PositionGetInteger(POSITION_TYPE)) return; // if position of unchecked direction if(dir!=CheckSignal(false)) return; // if the close signal didn't match the current position m_trade.PositionClose(m_smb,1); // close position } //------------------------------------------------------------------ CheckSignal long CMyEA::CheckSignal(bool bEntry) { MqlRates rt[2]; if(CopyRates(m_smb,m_tf,0,2,rt)!=2) { Print("CopyRates ",m_smb," history is not loaded"); return(WRONG_VALUE); } double ma[1]; if(CopyBuffer(m_hma,0,0,1,ma)!=1) { Print("CopyBuffer MA - no data"); return(WRONG_VALUE); } if(rt[0].open<ma[0] && rt[0].close>ma[0]) return(bEntry ? ORDER_TYPE_BUY:ORDER_TYPE_SELL); // condition for buy if(rt[0].open>ma[0] && rt[0].close<ma[0]) return(bEntry ? ORDER_TYPE_SELL:ORDER_TYPE_BUY); // condition for sell return(WRONG_VALUE); // if there is no signal } CMyEA ea; // class instance //------------------------------------------------------------------ OnInit int OnInit() { ea.Init(Symbol(),Period()); // initialize expert return(0); } //------------------------------------------------------------------ OnDeinit void OnDeinit(const int reason) { } //------------------------------------------------------------------ OnTick void OnTick() { ea.Main(); // process incoming tick }
Main() ファンクションの構造を構文解析しよう。 便宜上これを2つの部分に分割する。
最初の部分では親のファンクションが呼び出される。このファンクションはエキスパートの仕事にグローバルに影響する可能性あるパラメーターを処理する。これらはエキスパートのためにトレードの余裕をチェックし来歴データを検証するのを含む。
第2の部分では市場のイベントが直接的に処理される。
CheckNewBar() フィルターが試される - 新しいバーをチェックする。トレードの2つの方向のためのモジュールが一つづつ呼び出される。
モジュールにおいて全てがかなり抽象的に組織されている (第2設計原則)。シンボルのプロパティに直接のアドレスはない。そして3つのモジュール - OpenPosition(), ClosePosition() と TrailingPosition() - は外部からそれらに来るパラメーターにのみ頼る。これはこれらのモジュールを買いのためと、売りのための両方のオーダーの検証のために呼ぶことを可能にする。
4.2. CExpertAdvisorを使用する例 - インジケーターなしのエキスパート、ポジションの状態と結果を解析する。
デモのためロス後のロット増加でのポジション逆転のみでトレードするシステムを使おう。
a) 初期オーダーをする
- エキスパートがスタートすると初期ロットで買いの最初のポジションを開始する。
b) 引き続きポジションを開始する
- もし前のポジションが利得で終了していると、初期ロットで同じ方向でポジションを開始する。
- もし前のポジションが損失で閉鎖していると、反対の方向でより大きなロットでポジションを開始する。
このエキスパートのために CExpertAdvisor クラスの3つのファンクションを必要とする:
- ポジション開始
- ディールチケットで終了ポジションの利益額を得る - CountProfitByDeal()
- 仕事モジュール - OpenPosition(), CheckPosition()
エキスパートはどのインジケーターも解析しないが、ディールの結果だけがあり、生産性を最適化するためにOnTrade() イベントを用いる。すなわち、最初の初期オーダーを買いのオーダーをしたエキスパートでは、引き続く全てのオーダーをこのポジションの終了後にのみ行う。従って初期オーダーを OnTick() でそして引き続く全ての仕事は OnTrade()で行う。.
Init() ファンクションが、通常通り、単にクラスのパラメーターをエキスパートの外部パラメーターで初期化する。
OpenPosition() モジュールが初期ポジションを開始しそして m_first フラグによってブロックされる。
CheckPosition()モジュールがさらにポジションの反転を制御する。
これらのモジュールはエキスパートのそれぞれのファンクション(OnTick() と OnTrade())から呼び出される。
//+------------------------------------------------------------------+ //| eMarti.mq5 | //| Copyright Copyright 2010, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2010, MetaQuotes Software Corp." #property link "https://www.mql5.com" #property version "1.00" #include "ExpertAdvisor.mqh" #include <Trade\DealInfo.mqh> input double Lots = 0.1; // Lot input double LotKoef = 2; // lot multiplier for loss input int Dist = 60; // distance to Stop Loss and Take Profit //--- class CMartiEA : public CExpertAdvisor { protected: double m_lots; // Lot double m_lotkoef; // lot multiplier for loss int m_dist; // distance to Stop Loss and Take Profit CDealInfo m_deal; // last deal bool m_first; // flag of opening the first position public: void CMartiEA() { } void ~CMartiEA() { } virtual bool Init(string smb,ENUM_TIMEFRAMES tf); // initialization virtual void OpenPosition(); virtual void CheckPosition(); }; //------------------------------------------------------------------ Init bool CMartiEA::Init(string smb,ENUM_TIMEFRAMES tf) { if(!CExpertAdvisor::Init(0,smb,tf)) return(false); // initialize parent class m_lots=Lots; m_lotkoef=LotKoef; m_dist=Dist; // copy parameters m_deal.Ticket(0); m_first=true; m_bInit=true; return(true); // trade allowed } //------------------------------------------------------------------ OnTrade void CMartiEA::OpenPosition() { if(!CExpertAdvisor::Main()) return; // call parent function if(!m_first) return; // if already opened initial position ulong deal=DealOpen(ORDER_TYPE_BUY,m_lots,m_dist,m_dist); // open initial position if(deal>0) { m_deal.Ticket(deal); m_first=false; } // if position exists } //------------------------------------------------------------------ OnTrade void CMartiEA::CheckPosition() { if(!CExpertAdvisor::Main()) return; // call parent function if(m_first) return; // if not yet placed initial position if(PositionSelect(m_smb)) return; // if position exists // check profit of previous position double lot=m_lots; // initial lot long dir=m_deal.Type(); // previous direction if(CountProfitByDeal(m_deal.Ticket())<0) // if there was loss { lot=NormalLot(m_lotkoef*m_deal.Volume()); // increase lot dir=ReversType(m_deal.Type()); // reverse position } ulong deal=DealOpen(dir,lot,m_dist,m_dist); // open position if(deal>0) m_deal.Ticket(deal); // remember ticket } CMartiEA ea; // class instance //------------------------------------------------------------------ OnInit int OnInit() { ea.Init(Symbol(),Period()); // initialize expert return(0); } //------------------------------------------------------------------ OnDeinit void OnDeinit(const int reason) { } //------------------------------------------------------------------ OnTick void OnTick() { ea.OpenPosition(); // process tick - open first order } //------------------------------------------------------------------ OnTrade void OnTrade() { ea.CheckPosition(); // process trade event }
5. イベントの処理
この論説では2つのイベントを処理する例を紹介した - NewTick と Trade: それらはそれぞれ OnTick() と OnTrade() ファンクション, で演じられている。たいていの場合、これら2つのイベントは継続的に使われる。
エキスパートには、イベントを処理する4つのファンクションがある。
- OnChartEvent はイベントの大きなグループを処理する。それはグラフィックオブジェクト、キーボード、マウスとカスタムなイベントを使用する際である。例えば、ファンクションは対話式エキスパートあるいはオーダーのグラフィカル管理の原理に基づいて作られたエキスパートを作成するために使われる。あるいはMQLプログラムのパラメーターのアクティブなコントロールを作成する。一般に、このファンクションはエキスパートの外部イベントを処理するのに使われる。
- OnTimer はシステムのタイマーのイベントが処理されたときに呼び出される。この場合MQLプログラムは、インジケーターの値を計算するためにその環境を定期的に解析するよう要求するとき、またシグナルの外部ソースを連続的に参照する必要があるときに使われる。大雑把に言えば、 OnTimer() ファンクションは、代替案であり、最良の置き換え方法である。while(true) { /* perform analysis */; Sleep(1000); }.
すなわちエキスパートはスタートするのに、終わりなくループして待つ必要はなく、 OnTick() から OnTimer() を呼び出すよう変更すればよい。 - OnBookEvent は、市場深度がその状態を変えるイベントを処理する。このイベントは外部によるものでもよく、そして、そのタスクに従って処理を行う。
- OnTester は、与えられた日にちの範囲でエキスパートをテストした後に呼び出され、テストにより生成されたエキスパートのスクリーニングを実行するOnDeinit() ファンクションが呼び出される前、カスタム最大パラメーターで本来的な最適化を用いた際に、呼び出される。
どのイベントもその組み合わせも常に使用者の特定のタスクの解決に使うのに妥当なものであることを忘れてはいけない。
最後に
これで分かるように、エキスパートを書くことは、正しい方式をもっていれば、そう時間のかかるものではない。MQL5のイベントを処理する新しい可能性によって、トレーディング処理を管理するより柔軟な構造を使える。しかしこれらの材料はトレーディングのアルゴリズムを正しく準備したときにのみ真に強力なツールとなるのである。
この論説はその作成における3つの主な原則 - イベント対応、抽象性、モジュラリティについて説明した。エキスパートをこの「3つの枕」に基づいて作成すれば、トレードを簡単にすることができる。
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/132





- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索