MetaTrader4を使ってチャネルブレイクアウトBOTを作成するためにカスタムインジケーターを作成→EA本体も作成[MetaTrader4 MQL]

ニッケルメッキです。
最近はめっきり仮想通貨界隈から外為や指数などへ移民が進んでおりTLが過疎るばかりですね。

そのため、botterとしても外為へ移民してみたいと思い。懐かしのMetaTrader4でチャネルブレイクアウトBOTを作成するためのカスタムインジケーターを作成してみました。

チャネルブレイクアウトは「大きく動きそうだな~」という時に起動しておき、予想通り大きく同意付いた際には利益を教授しやすい戦略になります。
裁量のお供にも用意しておくと便利かと思います。

インジケーターの内容としては、指定期間の最高値と最安値を上限、下限として線が引かれるだけのとてもシンプルなものになります。

基本的にMQLをたいして理解していませんが、MetaQuotes社のサンプルコードを見本にコメントなどを入れてみました。間違ってるかもしれません。
需要があるかわかりませんが、何かの参考になれば幸いです。

以下コード

//------------------------------------------------------------------
//                                              ChannelBreakout.mq4 
//                                   Copyright 2018, nickel-plated. 
//------------------------------------------------------------------

// インジケータープロパティーの設定
#property copyright   "Copyright 2018, nickel-plated."  // コピーライト
#property link        "https://twitter.com/team_hiyatoi?lang=ja"  // 作者のリンク
#property description "ChannelBreakout"  // インジケーターの名称
#property strict  // エラーなど厳密に取得したい場合は指定(または日本語の使用など)

#property indicator_chart_window  // チャートウィンドウへ表示
#property indicator_buffers 2  // 表示するインジケーターの本数(バッファ数)
#property indicator_color1 LightSeaGreen  // インジケーターの色
#property indicator_color2 LightSeaGreen

// パラメータに使用する変数の定義(ユーザーで変更可能な変数)
input int BandsPeriod = 18;  // 最高値、最安値を求めるバーの本数

// バッファの定義
double ExtUpperBuffer[];  // 浮動小数点を使用
double ExtLowerBuffer[];

//------------------------------------------------------------------
// 初期化
//------------------------------------------------------------------

int OnInit(void){  // 初期化
    IndicatorBuffers(2);  // バッファの確保
    IndicatorDigits(Digits);  // インジケーターの小数点桁数を指定(Digits=表示中Symbolの小数点桁数)
    
    // 上限
    SetIndexStyle(0, DRAW_LINE);  // 線形
    SetIndexBuffer(0, ExtUpperBuffer);  // 使用するバッファ
    SetIndexLabel(0, "Bands Upper");  // インジケーターの説明

    // 下限
    SetIndexStyle(1, DRAW_LINE);
    SetIndexBuffer(1, ExtLowerBuffer);
    SetIndexLabel(1, "Bands Lower");

    // パラメータのエラー処理、0の場合は処理を行わずターミナルへエラー表示
    if(BandsPeriod <=0 ){
        Print("BandsPeriodは0以上を指定してください。");  // ターミナルへ表示
        return(INIT_FAILED);  // エラーを返却
    }
    
    //描画開始位置の指定
    SetIndexDrawBegin(0, BandsPeriod);
    SetIndexDrawBegin(1, BandsPeriod);

    return(INIT_SUCCEEDED);  // エラーが無ければ初期化完了
}

//------------------------------------------------------------------
// チャネルブレイクアウトに使用するインジケーター部分
//------------------------------------------------------------------

// OnCalculate(カスタムインジケーター本体、引数は所定のもの)
int OnCalculate(const int rates_total,  // バーの総数
                const int prev_calculated,  // 計算済みのバー数
                const datetime &time[],  // バーのタイムスタンプ
                const double &open[],  // 始値
                const double &high[],  // 高値
                const double &low[],  // 安値
                const double &close[],  // 終値
                const long &tick_volume[],  // 1ティックの出来高(あるいは値動き)
                const long &volume[],  // 出来高(あるいは値動き)
                const int &spread[]){  // スプレッド

    // 変数の定義
    int i;  // カウンタ
    int pos;
    
    // 足の本数がインジケーターに必要な本数を下回っている場合は処理しない
    if(rates_total <= BandsPeriod || BandsPeriod <= 0){
        return(0);
    }

    // 時系列配列の定義
    ArraySetAsSeries(ExtUpperBuffer, false);
    ArraySetAsSeries(ExtLowerBuffer, false);
    ArraySetAsSeries(high, false);
    ArraySetAsSeries(low, false);

    // インジケーターバッファの初期化0埋め
    if(prev_calculated<1){
        for(i = 0; i < BandsPeriod; i++){
            ExtUpperBuffer[i] = EMPTY_VALUE;
            ExtLowerBuffer[i] = EMPTY_VALUE;
        }
    }

    // ループに使用するカウンタをセット
    if(prev_calculated > 1)
        pos = prev_calculated - 1;  // 配列が[0]から始まる為、処理済みバー数よりカウンタを1除算する、たぶん
    else
        pos = 0;

    // メインループ(指定期間の最高値、最安値の計算)
    for(i = pos; i < rates_total && !IsStopped(); i++){  // バーの総数に達するか強制終了までループ
        ExtUpperBuffer[i] = Highest_Func(i, high, BandsPeriod);
        ExtLowerBuffer[i] = Lowest_Func(i, low, BandsPeriod);
    }

   return(rates_total);
}

//------------------------------------------------------------------
// 関数の定義 最高値及び最安値を求める
//------------------------------------------------------------------

// 引数の定義
double Highest_Func(int position,
                    const double &price[],
                    int period){  // 関数名(引数の定義(整数、定数浮動小数点参照渡し、整数))

    // 変数の定義
    double Highest_dTmp = 0.0;

    // 現在値が指定バー数より大きければ処理
    if(position >= period){

        // メインループ(最高値の計算)
        for(int i = 0; i < period; i++){
            if(Highest_dTmp < price[position - i]){
                Highest_dTmp = price[position - i];
            }
        }
    }
    // 結果を返値に指定
    return(Highest_dTmp);
}

double Lowest_Func(int position,
                   const double &price[],
                   int period){

    double Lowest_dTmp = 0.0;
    if(position >= period){
        for(int i = 0; i < period; i++){
            if(Lowest_dTmp == 0.0){
                Lowest_dTmp = price[position - i];
            }
            if(Lowest_dTmp > price[position - i]){
                Lowest_dTmp = price[position - i];
            }
        }
    }
    return(Lowest_dTmp);
}

//------------------------------------------------------------------

次回は実際にエキスパートアドバイザー(仮想通貨でいうところのBOT)を作っていきたいとは思っています。
以下エキスパートアドバイザー(実際の運用に必要な細かな実装はおこなってません、コードの参考までとして実運用はしないでください)

//------------------------------------------------------------------
//                                    Channel Breakout Strategy.mq4 
//                                   Copyright 2018, nickel-plated. 
//------------------------------------------------------------------

//MT4ドキュメント https://docs.mql4.com/

// インジケータープロパティーの設定
#property copyright   "Copyright 2018, nickel-plated."  // コピーライト
#property description "Channel Breakout Strategy"  // ストラテジーの名前

#define MAGICMA  20181028  // マジックナンバーEAごとに一意の値を割り振ること

// ユーザー入力可能なパラメーターの設定
input double Lots          = 0.1;  // ロット数
input int    ChannelPeriod = 18;  // チャネルを計算する際の期間

//------------------------------------------------------------------
// メインループ チャネルブレイクアウトEA本体                                  
//------------------------------------------------------------------
void Main(){

    //変数の宣言
    double upBound;  // チャネル上限
    double downBound;  // チャネル下限
    int    orderCnt;  // オーダー数
    int    i;  // インデックス(ループ用カウンタ)

    // バーの始まりでのみ処理
    if(Volume[0]>1){
        return;
    }

    upBound   = iCustom(NULL, 0, "ChannelBreakout", ChannelPeriod, 0, 1);
    downBound = iCustom(NULL, 0, "ChannelBreakout", ChannelPeriod, 1, 1);

    // 注文数を取得(約定済みのポジションも含む)
    orderCnt = OrdersTotal();

    // 注文(約定済みのポジション含む)が存在する場合の処理
    if(orderCnt > 0){
        // オーダーを評価
        for(i = 0; i < orderCnt; i++){
            // 未約定、未決済のオーダーを選択
            // マジックナンバー、カレント通貨ペアとオーダー中の通貨ペアが一致しない場合は処理しない
            if(OrderSelect(i,SELECT_BY_POS,MODE_TRADES) == false){
                break;
            }
            if(OrderMagicNumber() != MAGICMA || OrderSymbol() != Symbol()){
                continue;
            }
            // 買い建玉が存在する場合で且つ、終値が下限を下回った場合は決済、ドテンする
            if(OrderType() == OP_BUY && downBound > Close[0]){
                // 決済注文
                if(!OrderClose(OrderTicket(), OrderLots(), Bid, 3, White)){
                    Print("注文エラー ", GetLastError());
                // 注文が正常に決済完了した場合はドテン売買
                } else {
                    SendOrder(OP_SELL);
                }
                break;
            // 売り建玉が存在する場合の処理
            } else if(OrderType() == OP_SELL && upBound < Close[0]){
                if(!OrderClose(OrderTicket(), OrderLots(), Ask, 3, White)){
                    Print("注文エラー ", GetLastError());
                } else {
                    SendOrder(OP_BUY);
                }
                break;
            }
        }
    // 注文が存在しない場合の処理(新規注文)
    } else {
        if(upBound < Close[0]){
            SendOrder(OP_BUY);
        } else if(downBound > Close[0]){
            SendOrder(OP_SELL);
        }
    }
}
  
//------------------------------------------------------------------
// 1ティック毎のイベントループ
//------------------------------------------------------------------

void OnTick(){
    // バーの本数がチャネル算出期間以下、またはEAの取引許可が無い場合処理しない
    if(Bars < ChannelPeriod || IsTradeAllowed() == false){
        return;
    }
    // メインファンクション呼び出し
    Main();
}

//------------------------------------------------------------------
// その他関数
//------------------------------------------------------------------

// 注文送信
bool SendOrder(int side){
    int    ticket;  // チケット、オーダー番号
    double price;  // 発注価格
    string message;  // オーダーに関するコメント
    color  arrowClr;  // チャート上に表示される注文の色(矢印の色)

    //売買によって、価格、メッセージの代入
    if(side == OP_BUY){
        price = Ask;
        message = "ChBrk Buy";
        arrowClr = Blue;
    } else if(side == OP_SELL){
        price = Bid;
        message = "ChBrk Sell";
        arrowClr = Red;
    } else {
        Print("売/買を指定して下さい");
        return false;
    }
    // 発注
    ticket = OrderSend(Symbol(), side, Lots, price, 3, 0, 0, message, MAGICMA, 0, arrowClr);
    if(ticket < 0){
        Print("注文エラー ", GetLastError());
        return false;
    } else {
        Print("注文完了");
        return true;
    }
}

//------------------------------------------------------------------

この記事が気に入ったら、サポートをしてみませんか?気軽にクリエイターを支援できます。

20

ニッケルメッキ

相場好きです!それだけ。
コメントを投稿するには、 ログイン または 会員登録 をする必要があります。