自分で考えた自動売買を設計するためには手順があります。
人それぞれ色々やり方はあると思いますが、私はこんな手順で考えます。
売買ルールを決めよう
まずは自分が作りたいシステムの売買ルールを整理しましょう。箇条書きで構わないの思いつくまま書き出してみましょう。設計ではこれを要件定義とよびます。ゴールをはっきりさせることが目的です。
この段階では実現可能かどうかはあまり考えずにアイデアを書き出すことが大事です。
例題 決済タイミングに別のテクニカルをつかう
たとえば、RSIでエントリーしたあとの決済判断にはボリンジャーバンドを使うとしましょう。
箇条書きでいいのでルールを書き出します。
例えばこんな感じです。
- RSIが75以上で売り
- RSIが25以下で買い
- 買いポジションがあるときにボリンジャーバンドの+2σを超えたら決済
- 売りポジションがあるときにボリンジャーバンドの-2σを下回ったら決済
状態遷移について
今回はサンプルと同じ状態遷移ですので、設計を再利用できます。
イベントを抽出してみよう
イベントは状態が遷移するきっかけとなるトリガーです。今回の場合ならボリンジャーバンドと交差した時などです。
- 約定
- 売りシグナル(RSI >= 75)
- 買いシグナル(RSI <= 25)
- 買いポジ決済シグナル(ボリンジャーバンド+2σ超え)
- 売りポジ決済シグナル(ボリンジャーバンド-2σ超え)
状態遷移表を作成しよう
状態遷移表は状態とイベントを表にして、次の状態を書き込んだ表です。言葉だと難しいですね。論より証拠。例題の場合は以下のような表になります。
初期状態 | 買い注文中 | 買いポジあり | 売り注文中 | 売りポジあり | 決済中 | |
---|---|---|---|---|---|---|
約定 | – | →買いポジあり | – | →売りポジあり | – | →初期状態 |
売りシグナル |
売り注文を発行 →売り注文中 |
– | – | – | – | – |
買いシグナル |
買い注文を発行 →買い注文中 |
– | – | – | – | – |
買いポジ決済 | – | – | 決済→買いポジ決済中 | – | – | – |
売りポジ決済 | – | – | – | – | 決済→売りポジ決済中 | – |
-は無視(何もしない)です。状態を列、イベントを行に割り当て、各状態でイベントが来たときの動作と次の状態を書き込んだのが上の表です。
状態遷移表からプログラムを作ろう
ここまでで、設計は完成しました。ここからは実際に動くプログラムを作成しましょう。
サンプルプログラムと違うのは決済用のイベントが増えたことです。状態基底クラスに決済用の売買イベントを2つ追加します。
// 状態基底クラス class CStatus { private: int m_status; protected: static int g_ticket; static int ticket() { return g_ticket; } void ticket(int tk) { g_ticket = tk; } public: CStatus(int st) { m_status = st; } virtual int sell_signal() { return status(); } virtual int buy_signal() { return status(); } virtual int close_sell_signal() { return status(); } virtual int close_buy_signal() { return status(); } virtual int check() { return status(); } int status() { return m_status; } };
次に買ポジあり、売りポジあり状態を修正します。サンプルでは売りシグナル、買いシグナルで決済していましたが、今回はそれぞれ別イベントになります。
class buy_pos : public CStatus { public: buy_pos() : CStatus(2) {} virtual int close_sell_signal() { // パラメータが変わってる時を考慮して、ロット数を取得 double _lots = lots; if ( OrderSelect( ticket(), SELECT_BY_TICKET ) ) { _lots = OrderLots(); } int result = OrderClose( ticket(), _lots, Bid, 30, Goldenrod ); int ErrCode = GetLastError(); if (result) { return 6; } else { return status(); } } virtual int check() { // オーダー選択 if ( OrderSelect( ticket(), SELECT_BY_TICKET ) ) { if (OrderCloseTime() != 0) { // キャンセルされた ticket(-1); return 1; } } else { ticket(-1); return 1; } return status(); } }; class sell_pos : public CStatus { public: sell_pos() : CStatus(3) {} virtual int close_buy_signal() { // パラメータが変わってる時を考慮して、ロット数を取得 double _lots = lots; if ( OrderSelect( ticket(), SELECT_BY_TICKET ) ) { _lots = OrderLots(); } int result = OrderClose( ticket(), _lots, Ask, 30, Goldenrod ); int ErrCode = GetLastError(); if (result) { return 6; } else { return status(); } } virtual int check() { // オーダー選択 if ( OrderSelect( ticket(), SELECT_BY_TICKET ) ) { if (OrderCloseTime() != 0) { // キャンセルされた ticket(-1); return 1; } } else { ticket(-1); return 1; } return status(); } };
最後にボリンジャーバンドでの決済判定処理を追加します。
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- int next = status; next = current.check(); if (next != status) { Print("ポジションチェックで状態変化:", status, "→", next); status = next; } current = StatusArray[status]; // 売りシグナルチェック if ( is_sell_signal() ) { next = current.sell_signal(); } // 買いシグナルチェック if ( is_buy_signal() ) { next = current.buy_signal(); } if (next != status) { Print("シグナルで状態変化:", status, "→", next); status = next; } current = StatusArray[status]; // 売りシグナルチェック if ( is_close_sell_signal() ) { next = current.close_sell_signal(); } // 買いシグナルチェック if ( is_close_buy_signal() ) { next = current.close_buy_signal(); } if (next != status) { Print("シグナルで状態変化:", status, "→", next); status = next; } current = StatusArray[status]; } //+------------------------------------------------------------------+ // 売シグナル判定関数です。if文の関数を他のテクニカルインジケータ―関数に変えてみてください bool is_sell_signal() { bool ret = false; if ((iRSI(NULL, 0, 14, PRICE_CLOSE, 0)) > sell_threshold) { ret = true; } return ret; } // 買シグナル判定関数です。if文の関数を他のテクニカルインジケータ―関数に変えてみてください bool is_buy_signal() { bool ret = false; if ((iRSI(NULL, 0, 14, PRICE_CLOSE, 0)) < buy_threshold) { ret = true; } return ret; } // 売シグナル判定関数です。if文の関数を他のテクニカルインジケータ―関数に変えてみてください bool is_close_sell_signal() { bool ret = false; if ((iBands(NULL, 0, 20, 2.0, 0, PRICE_CLOSE, MODE_UPPER, 0)) < (Bid)) { ret = true; } return ret; } // 買シグナル判定関数です。if文の関数を他のテクニカルインジケータ―関数に変えてみてください bool is_close_buy_signal() { bool ret = false; if ((iBands(NULL, 0, 20, 2., 0, PRICE_CLOSE, MODE_LOWER, 0)) > (Ask)) { ret = true; } return ret; }
取引状態の処理はほとんど同じなので、変更箇所がとても少ないのがわかるでしょうか?
イベントを追加する場合、OnTick関数でのイベント処理の優先順位を考慮が必要な場合があります。今回は売り買いのシグナルが同時に成立しないことを前提にしていますが、違う場合は注意してください。一つのtickで複数回状態遷移が発生しないように気をつけましょう。
完成したソースがこちら
//+------------------------------------------------------------------+ //| rsi_iband.mq4 | //| castanet | //| https://fx.castanet.tokyo | //+------------------------------------------------------------------+ #property copyright "castanet" #property link "https://fx.castanet.tokyo" #property version "1.00" #property strict //--- input parameters input double sell_threshold=75; input double buy_threshold=25; input double set_lots=2; input int stop_pips=1000; input int limit_pips=200; input int stop_enable=0; input int limit_enable=0; input int magic_number = 10415518; double lots = set_lots / 10.0; // 状態基底クラス class CStatus { private: int m_status; protected: static int g_ticket; static int ticket() { return g_ticket; } void ticket(int tk) { g_ticket = tk; } public: CStatus(int st) { m_status = st; } virtual int sell_signal() { return status(); } virtual int buy_signal() { return status(); } virtual int close_sell_signal() { return status(); } virtual int close_buy_signal() { return status(); } virtual int check() { return status(); } int status() { return m_status; } }; int CStatus::g_ticket = -1; class init_pos : public CStatus { public: init_pos() : CStatus(1) {} virtual int sell_signal() { double stop_level = 0; if (stop_enable) { stop_level = NormalizeDouble(Bid + stop_pips * Point, Digits); } double limit_level = 0; if (limit_enable) { limit_level = NormalizeDouble(Bid - limit_pips * Point, Digits); } Print("Bid=", Bid, "Stop=", stop_level, "Limit=", limit_level); int result = OrderSend( Symbol(), OP_SELL, lots, Bid, 30, stop_level, limit_level, NULL, magic_number, 0, Red ); int ErrCode = GetLastError(); if (ErrCode == 0) { ticket(result); return 5; } else { Print( "ErrCode=" + (string)ErrCode ); return status(); } } virtual int buy_signal() { double stop_level = 0; if (stop_enable) { stop_level = NormalizeDouble(Ask - stop_pips * Point, Digits); } double limit_level = 0; if (limit_enable) { limit_level = NormalizeDouble(Ask + limit_pips * Point, Digits); } Print("Ask=", Ask, "Stop=", stop_level); int result = OrderSend( Symbol(), OP_BUY, lots, Ask, 30, stop_level, limit_level, NULL, magic_number, 0, Blue ); int ErrCode = GetLastError(); if (ErrCode == 0) { ticket(result); return 4; } else { Print( "ErrCode=" + (string)ErrCode ); return status(); } } virtual int check() { // オーダー情報取得 for ( int i = 0; i < OrdersTotal(); i++ ){ // オーダー選択 if ( OrderSelect( i, SELECT_BY_POS, MODE_TRADES ) ) { // オーダー確認 if ( OrderMagicNumber() != magic_number || OrderSymbol() != Symbol() ) { continue; } // オーダーあり ticket(OrderTicket()); if ( OrderType() == OP_BUY ) { return 2; } else if ( OrderType() == OP_SELL ) { return 3; } else { // とりあえず、決済中にしておく return 6; } } } return status(); } }; class buy_pos : public CStatus { public: buy_pos() : CStatus(2) {} virtual int close_sell_signal() { // パラメータが変わってる時を考慮して、ロット数を取得 double _lots = lots; if ( OrderSelect( ticket(), SELECT_BY_TICKET ) ) { _lots = OrderLots(); } int result = OrderClose( ticket(), _lots, Bid, 30, Goldenrod ); int ErrCode = GetLastError(); if (result) { return 6; } else { return status(); } } virtual int check() { // オーダー選択 if ( OrderSelect( ticket(), SELECT_BY_TICKET ) ) { if (OrderCloseTime() != 0) { // キャンセルされた ticket(-1); return 1; } } else { ticket(-1); return 1; } return status(); } }; class sell_pos : public CStatus { public: sell_pos() : CStatus(3) {} virtual int close_buy_signal() { // パラメータが変わってる時を考慮して、ロット数を取得 double _lots = lots; if ( OrderSelect( ticket(), SELECT_BY_TICKET ) ) { _lots = OrderLots(); } int result = OrderClose( ticket(), _lots, Ask, 30, Goldenrod ); int ErrCode = GetLastError(); if (result) { return 6; } else { return status(); } } virtual int check() { // オーダー選択 if ( OrderSelect( ticket(), SELECT_BY_TICKET ) ) { if (OrderCloseTime() != 0) { // キャンセルされた ticket(-1); return 1; } } else { ticket(-1); return 1; } return status(); } }; class buy_order : public CStatus { public: buy_order() : CStatus(4) {} virtual int check() { // オーダー選択 if ( OrderSelect( ticket(), SELECT_BY_TICKET ) ) { if (OrderCloseTime() != 0) { // キャンセルされた ticket(-1); return 1; } if (OrderType() == OP_BUY) { return 2; } } else { ticket(-1); return 1; } return status(); } }; class sell_order : public CStatus { public: sell_order() : CStatus(5) {} virtual int check() { // オーダー選択 if ( OrderSelect( ticket(), SELECT_BY_TICKET ) ) { if (OrderCloseTime() != 0) { // キャンセルされた ticket(-1); return 1; } if (OrderType() == OP_SELL) { return 3; } } else { ticket(-1); return 1; } return status(); } }; class close_order : public CStatus { public: close_order() : CStatus(6) {} virtual int check() { // オーダー選択 if ( OrderSelect( ticket(), SELECT_BY_TICKET ) ) { if (OrderCloseTime() != 0) { ticket(-1); return 1; } } else { ticket(-1); return 1; } return status(); } }; CStatus *current; CStatus *StatusArray[7]; int status = 1; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- StatusArray[1] = new init_pos(); StatusArray[2] = new buy_pos(); StatusArray[3] = new sell_pos(); StatusArray[4] = new buy_order(); StatusArray[5] = new sell_order(); StatusArray[6] = new close_order(); status = 1; current = StatusArray[1]; //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- for (int i = 1; i < 7; i++) { delete StatusArray[i]; } } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- int next = status; next = current.check(); if (next != status) { Print("ポジションチェックで状態変化:", status, "→", next); status = next; } current = StatusArray[status]; // 売りシグナルチェック if ( is_sell_signal() ) { next = current.sell_signal(); } // 買いシグナルチェック if ( is_buy_signal() ) { next = current.buy_signal(); } if (next != status) { Print("シグナルで状態変化:", status, "→", next); status = next; } current = StatusArray[status]; // 売りシグナルチェック if ( is_close_sell_signal() ) { next = current.close_sell_signal(); } // 買いシグナルチェック if ( is_close_buy_signal() ) { next = current.close_buy_signal(); } if (next != status) { Print("シグナルで状態変化:", status, "→", next); status = next; } current = StatusArray[status]; } //+------------------------------------------------------------------+ // 売シグナル判定関数です。if文の関数を他のテクニカルインジケータ―関数に変えてみてください bool is_sell_signal() { bool ret = false; if ((iRSI(NULL, 0, 14, PRICE_CLOSE, 0)) > sell_threshold) { ret = true; } return ret; } // 買シグナル判定関数です。if文の関数を他のテクニカルインジケータ―関数に変えてみてください bool is_buy_signal() { bool ret = false; if ((iRSI(NULL, 0, 14, PRICE_CLOSE, 0)) < buy_threshold) { ret = true; } return ret; } // 売シグナル判定関数です。if文の関数を他のテクニカルインジケータ―関数に変えてみてください bool is_close_sell_signal() { bool ret = false; if ((iBands(NULL, 0, 20, 2.0, 0, PRICE_CLOSE, MODE_UPPER, 0)) < (Bid)) { ret = true; } return ret; } // 買シグナル判定関数です。if文の関数を他のテクニカルインジケータ―関数に変えてみてください bool is_close_buy_signal() { bool ret = false; if ((iBands(NULL, 0, 20, 2., 0, PRICE_CLOSE, MODE_LOWER, 0)) > (Ask)) { ret = true; } return ret; }