投資でプログラミングを活用したい!始値と終値の開きを使った自動売買プログラムを作った話

こんにちは、ウチダです。

寝てても儲かるプログラムができたら素敵ですよね。

最近はトレーダーと一緒に自動売買プログラム作りに励んでいます。

今回はトレード戦略の1つとして、始値と終値の開きを使った自動売買プログラムを作りました。

ここでは、MQL4のプログラミングコードを紹介します。

(参考:自動売買プログラムの作り方の基礎はこちら

始値と終値の開きを使った自動売買プログラムを作ろう!

MT4の自動売買プログラムで注文した結果

自動売買プログラムは、事前に設定した条件で自動的に注文と決済をしてくれます。

すごく簡単に儲かりそうなイメージもありますが、実はそんなに単純ではありません。

FX投資では上がり相場、下がり相場だけでなく、レンジ相場というものがあります。

レンジ相場ではレートの上がり下がりが細かく発生します。

すると、売買条件によって注文がたくさん発生してしまい、損することが多いのです。

そこで発想したのが、始値と終値の開きを使う作戦です。

レンジ相場では開きが小さいため、この作戦ではレンジ相場を回避できると考えました。

さらに開きが大きくなると相場も大きく動く傾向にあるため、売買利益も出しやすいのではないか・・!

そんな期待を込めて自動売買プログラムを作成しました。

今回はそのプログラムのコードを紹介します。

自動売買プログラム1:どんなプログラムなのか説明しよう

//+------------------------------------------------------------------+
//| uchy_strategy1.mq4 |
//| Copyright 2021, uchida. |
//| http://tsumawosettoku20200808.com |
//+------------------------------------------------------------------+

まずはプログラムがどんなもので、誰が作ったものなのか記載します。

//から始まる行はコメントです。

//以降は行末までをコメントとみなし、プログラムとは関係ありません。

行頭に//があればその行がすべてコメントになります。

途中に//があれば、そこから後だけがコメントになります。

これは、人がコードを見たときに内容を分かりやすくするために記載します。

今回のコードではファイル名、著作権、サイトのURLを記載しています。

自動売買プログラム2:プログラム全体の設定をしよう

#property copyright   "2021, uchida"
#property link        "http://tsumawosettoku20200808.com"
#property description "Buy : SMA & a gap between Open and Close"

#define MAGICMA  2021042401

次にプログラム全体に関わる設定を行います。

#propertyは、例えばインジケーターの色や数を設定します。

#property 識別子 値

コードでは著作権とリンクを表すだけで、自動売買プログラムとは関係ありません。

#property descriptionは自動売買プログラムを起動させたときに表示される説明文です。

自動売買プログラムを稼働させたときのメッセージ

#defineは値を定義するコードです。

コードではMAGICMAを2021042401という値に定義しています。

数学の代入のようなもの。

MAGICMAはマジックナンバーとも呼ばれ、複数の自動売買プログラムを動かしたときに識別するための番号です。

(参考:自動売買プログラムになぜマジックナンバーが必要なの?

自動売買プログラム3:指標などの入力値を設定しよう

//--- Inputs
input double Lots          =0.1;
input int    MovingPeriod  =10;
input int    MovingShift   =0;

extern double TakeProfit   = 10; //利食い
extern double StopLoss     = 30; //損切り
extern int    Slippage     =10; 
extern string Comments     = "EA has traded";

int           Adjusted_Slippage =0;
double        _point       = Point; // Pips入力値用の変数

inputは、自動売買プログラムを起動させたときに値を入力できるようにします。

自動売買プログラムを稼働させたときの初期設定

doubleは小数点以下の小さな値を定義します。

Lotsは自分で決めた変数の名前です。

高校数学のx=0.1のxと同じようなもの。

MovingPeriod、MovingShiftも自分で決めた変数ですが、前にintがついています。

intは整数型の値を入れるのに使います。

externは外部変数として宣言する方法です。

externをつけて宣言された外部変数は、指標をチャートに挿入する時、あるいは挿入後でも設定値を変更できます。

例えば自動売買プログラムを稼働しているときでも、損切りや利食いの条件を変えられるのです。

あと、MQL4ではコードの後に;をつけます。

pythonとは違うところで、これでよくエラーを出しちゃいました。

自動売買プログラム4:スリッページを調整しよう

//スリッページの調整------------------------------------------------------
int AdjustSlippage(string Currency, int Slippage_Pips)
{
    int Symbol_Digits = MarketInfo(Currency, MODE_DIGITS);

    int Calculated_Slippage = 0;

    if(Symbol_Digits == 2 || Symbol_Digits ==4)
    {
        Calculated_Slippage = Slippage_Pips;    
    }
    else if(Symbol_Digits == 3 || Symbol_Digits == 5)
    {
        Calculated_Slippage = Slippage_Pips * 10;
    }

    return(Calculated_Slippage);
}

自動売買プログラムはどのFX業者で使用されるか分かりません。

FX業者によって提示レートが異なることがあるため、調整するためにスリッページの関数を用意しています。

(参考:【MQL4学習】便利で使えるAdjustSlippage()関数とは?

double MarketInfo(
    string symbol, //マーケット情報を取得したい通貨ペア名を指定
    int type //取得したいマーケット情報を指定。MODE_DIGITSは提示レートの小数点以下の桁数。
    );

CurrencyはAdjustSlippage(引数)の第一引数(string Currency)から来ています。

Currencyの内容は後のコードで記載しますが、引数にはSymbol()を入れます。

Symbol()は自動売買プログラムを稼働させている通貨ペア名です。

int Symbol_Digitsには提示レートの桁数が入りますが、桁数が偶数の時と奇数の時で処理を変えています。

提示レートが3桁又は5桁の業者において、提示レートが2桁又は4桁の業者と同様のpips単位で許容スリッページ数の設定を行うためです。

なので提示レートが3桁又は5桁の場合は、Slippage_Pipsを10倍にしています。

Slippage_pipsはAdjustSlippage(引数)の第二引数(int Slippage_Pips)から来ています。

後のコードで記載しますが、引数にはSlippageを入れます。

自動売買プログラム5:今のポジションを確認しよう

//+------------------------------------------------------------------+
//| Calculate open positions |
//+------------------------------------------------------------------+
int CalculateCurrentOrders(string symbol)
{
    int buys=0,sells=0;
    //---
    for(int i=0;i0) return(buys);
    else return(-sells);
}

自動売買プログラムを開始する前に、注文が入っていないか確認します。

int CalculateCurrentOrders(){コード}は関数です。

以降のプログラムで、関数を呼び出せば、{コード}をまとめて実行できます。

CalculateCurrentOrdersは自分でつけた名前なので、なんでもOKです。

stringはテキストの文字型を定義するのに使用します。

string symbolは、symbolにテキストの文字型データを入れると宣言しています。

int buys=0,sells=0では整数型データとして0をそれぞれ代入しています。

for(変数の初期値; 繰り返す範囲;変数の増やし方){コード}は繰り返し処理です。

for内の条件を満たす間、{コード}を実行します。

この場合はi=0からi<OrdersTotal()まで、iを1ずつ増やしながら繰り返し処理します。

OrdersTotal()はエントリー中の注文と保留中注文の総数を整数型で返します。

つまりエントリー中の注文および保留中の注文がある場合、{コード}を実行します。

OrderSelect()は注文の種類を調べます。

bool OrderSelect(
    int i, // i番目の注文をチェックの対象とするための指定
    int SELECT_BY_POS, // トレーディングプールのインデックスをiに指定します。
    int MODE_TRADES // トレーディングプールを使用
    );

(参考:MQL4のOrderSelectを正しく理解する

注文状態にエラーがなければtrue、エラーがあればfalseを返します。

if(OrderSelect(i,SELECT_BY_POS,MODE_TRADES)==false) break;

falseの場合、breakを実行します。

breakはforループの処理から抜けます。

trueならforループ内のコードを続けます。

次に選択した注文の通貨ペアとマジックナンバーを確認します。

if(OrderSymbol()==Symbol() && OrderMagicNumber()==MAGICMA)

OrderSymbol()はi番目の注文の通貨ペア名を取得します。

Symbol()と異なれば、{コード}をスキップします。

OrderMagicNumber()でi番目の注文のマジックナンバーを取得します。

こちらも、MAGICMAと異なれば{コード}をスキップします。

if(OrderType()==OP_BUY) buys++;
if(OrderType()==OP_SELL) sells++;

OrderType()でi番目の注文タイプを取得します。

OP_BUYは成行買い注文、OP_SELLは成行売り注文を意味します。

i番目の注文とOP_BUYが一致したら、buysを1増やします。

i番目の注文とOP_SELLが一致したら、sellsを1増やします。

forループ処理を終えたら、returnで関数の戻り値を返します。

//--- return orders volume
if(buys>0) return(buys);
else return(-sells);

buysが0より大きいなら、buysを返します。

buysが0ならsellsをマイナスの値にして返します。

自動売買プログラム6:注文する関数を用意しよう

//+------------------------------------------------------------------+
//| Check for open order conditions                                  |
//+------------------------------------------------------------------+
int CheckForOpen()
  {
     int    res;
    double sl = 0, tp = 0;
    //--- go trading only for first tiks of new bar
   if(Volume[0]>1) return(0);
    //--- get Moving Average 
   double ma=iMA(NULL,0,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE,1);
   double ma2=iMA(NULL,0,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE,2);
   double ma3=iMA(NULL,0,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE,3);
   double ma4=iMA(NULL,0,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE,4);

   double delta = MathAbs(Open[1] - Close[1]);

    //--- buy conditions
   if(Close[4] > ma4 && Open[4] < ma4 && Open[3] > ma3 &&  Close[1] > ma && delta > 10* _point)
     {
          //if (StopLoss > 0) sl = Ask - StopLoss * _point;
          //if (TakeProfit > 0) tp = Ask + TakeProfit * _point;
          res=OrderSend(Symbol(),OP_BUY,Lots,Ask,Adjusted_Slippage,sl,tp,Comments,MAGICMA,0,Blue);
          return(0);
     }
    //---
  return(0);
}

CheckForOpen()は関数で、自分で決めた名前です。

Volume[i]は出来高を表します。

Volume[0]が現在のバーの出来高、Volume[1]が1本前のバーの出来高、Volume[2]が2本前のバーの出来高……を表します。

Volume[0]>1というのは新しい足にティック数がある場合を指します。

この時はreturnで戻っているので新しい足にティック数がある場合はトレードしない。

つまり、新しい足ができた直後トレードするようになっています。

double iMA(
    string symbol, //通貨ペア名
    int timeframe, //移動平均線の値を計算する時間軸
    int ma_period, //移動平均線の値を計算する期間
    int ma_shift, //移動平均線の表示を右方向にシフト(ずらす)するバーの個数
    int ma_method, //計算する移動平均線の種類
    int applied_price, //移動平均線の値の計算に使用する価格データ
    int shift //移動平均線の値を取得したいバーの位置
    );

iMA()関数は移動平均線の関数です。

ma=iMA(NULL,0,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE,1);

通貨ペアは指定なし(自動売買プログラムを適用した通貨ペアになる)

時間軸も指定なし(自動売買プログラムを適用した時間軸になる)

期間は12、シフトは6、計算方法は単純移動平均です。

計算には終値を使い、バーの位置は1〜4本前としました。

MathAbsは、指定した値の絶対値を取得するために使用します。

double MathAbs(
    double value //絶対値を取得したい値を指定
    );

valueにはOpen[1] – Close[1]、つまり始値と終値の差を入れています。

これが取引戦略の肝なんです。

if条件によって注文を出す条件を設定しています。

そして、Open[i]は始値を格納しているdouble型の配列です。

Open[0]が現在のバーの始値、Open[1]が1本前のバーの始値、Open[2]が2本前のバーの始値……を表します。

Close[i]は終値を格納しているdouble型の配列です。

Close[0]が現在のバーの終値、Close[1]が1本前のバーの終値、Close[2]が2本前のバーの終値……を表します。

int OrderSend(
    string symbol, //通貨ペア名
    int cmd, //注文方法
    double volume, //ロットサイズ
    double price, //注文価格
    int slippage, //許容スリッページ数
    double stoploss, //損切り価格
    double takeprofit, //利益確定価格
    string comment, //注文に対するコメント
    int magic, //マジックナンバー
    datetime expiration, //注文の有効期限
    color arrow_color //チャート上に表示するオブジェクトの色
    );

買い注文のコードはこのようになっています。

//--- buy conditions
if(Close[4] > ma4 && Open[4] < ma4 && Open[3] > ma3 && Close[1] > ma && delta > 10* _point)
{
    //if (StopLoss > 0) sl = Ask - StopLoss * _point;
    //if (TakeProfit > 0) tp = Ask + TakeProfit * _point;
    res=OrderSend(Symbol(),OP_BUY,Lots,Ask,Adjusted_Slippage,sl,tp,Comments,MAGICMA,0,Blue);
    return(0);
}

通貨ペアは指定なし(自動売買プログラムを適用した通貨ペアになる)

注文方法は成行売り注文、ロット数は関数LotsOptimized()で計算した値

注文価格はBid(売値)、許容スリッページは3

slは損切り、tpは利食いの条件です。

今回は//でコメントにしており、設定されていません。

コメントは”EA has traded”

マジックナンバーはMAGICMA、注文の有効期限はなし、チャート上には青色で表示します。

許容スリッページとは注文レートと約定レートの差です。

自分が意図しないレートで注文しないように、許容スリッページを決めています。

(参考:スリッページとは何ですか?

もし売り注文をする際は、こちらに書き換えます。

//--- sell conditions
if(Close[4] < ma4 && Open[4] > ma4 && Open[3] < ma3 && Close[1] < ma && delta > 10* _point)
{
    //if (StopLoss > 0) sl = Bid + StopLoss * _point;
    //if (TakeProfit > 0) tp = Bid - TakeProfit * _point;
    res=OrderSend(Symbol(),OP_SELL,Lots,Bid,Adjusted_Slippage,sl,tp,Comments,MAGICMA,0,Red);
    return(0);
}

自動売買プログラム7:決済する関数を用意しよう

//+------------------------------------------------------------------+
//| Check for close order conditions                                 |
//+------------------------------------------------------------------+
int CheckForClose()
 {
     double ma;
    //--- go trading only for first tiks of new bar
    if(Volume[0]>1) return(0);
    //--- get Moving Average 
   ma=iMA(NULL,0,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE,1);
    //---
   for(int i=0;i<OrdersTotal();i++)
     {
          if(OrderSelect(i,SELECT_BY_POS,MODE_TRADES)==false) break;
          if(OrderMagicNumber()!=MAGICMA || OrderSymbol()!=Symbol()) continue;
          //--- check order type 
          if(OrderType()==OP_BUY)
          {
          if(Open[1]>ma && Close[1]<ma)
          {
             if(!OrderClose(OrderTicket(),OrderLots(),Bid,Adjusted_Slippage,White))
             Print("OrderClose error ",GetLastError());
          }
          break;
          }
         //if(OrderType()==OP_SELL)
        //{
             //if(Open[1]<ma && Close[1]>ma)
             //{
             //if(!OrderClose(OrderTicket(),OrderLots(),Ask,Adjusted_Slippage,White))
             //Print("OrderClose error ",GetLastError());
        //}
        //break;
        //}
     }
     return(0);
}

OrdersTotal()は、現に保有しているポジションと、待機注文の合計数を取得するために使用します。

つまり何も注文していない時、待機注文がないなら0を返します。

このとき、i<OrdersTotal()を満たさないので注文は決済されません。

ポジションや待機注文があればi<OrdersTotal()が満たされ、実行されます。

OrderSelect()は注文の種類を調べます。

bool OrderSelect(
    int i, // i番目の注文をチェックの対象とするための指定
    int SELECT_BY_POS, // トレーディングプールのインデックスをiに指定します。
    int MODE_TRADES // トレーディングプールを使用
    );

(参考:MQL4のOrderSelectを正しく理解する

注文状態にエラーがなければtrue、エラーがあればfalseを返します。

if(OrderSelect(i,SELECT_BY_POS,MODE_TRADES)==false) break;

falseの場合、breakを実行します。

breakはforループの処理から抜けます。

trueならforループ内のコードを続けます。

if(OrderMagicNumber()!=MAGICMA || OrderSymbol()!=Symbol()) continue;

OrderMagicNumber()は、選択した注文のマジックナンバーを取得するために使用します。

プログラムが勝手に別の自動売買プログラムの注文を決済したら大変ですよね。

なのでマジックナンバーを先に確認します。

OrderSymbol()はi番目の注文の通貨ペア名を取得します。

Symbol()と異なれば、continueを実行します。

continue処理は最も近くにある外側のwhile,do while,forのループ処理の始りに戻ります。

つまり以降の処理をスキップします。決済しません。

//--- check order type
if(OrderType()==OP_BUY)
{
    if(Open[1]>ma && Close[1]<ma)
    {
        if(!OrderClose(OrderTicket(),OrderLots(),Bid,Adjusted_Slippage,White))
        Print("OrderClose error ",GetLastError());
    }
    break;
}

OrderSelectで選択した注文が買いの場合、決済を行います。

決済条件は移動平均値に対する始値と終値の関係で決めます。

bool OrderClose(
    int ticket, //決済する注文のチケット番号
    double lots, //決済するロットサイズ
    double price, //決済価格を指定
    int slippage, //許容スリッページ数を指定
    color arrow_color //チャート上のポジションを決済した位置に表示するオブジェクトの色
    );

OrderTicket()は、OrderSelectで選択した注文のチケット番号を取得します。

OrderLots()も、OrderSelectで選択した注文の取引ロット数を取得します。

価格はBid(売値)、スリッページはAdjusted_Slippageで計算した値、色は白にします。

if(!条件){コード}は、条件を満たさないときにコードが実行されます。

今回はGetLastError()を使っており、直近に発生したエラーのエラーコードを取得します。

エラーコードの戻り値はこちら

(参考:【MQL4プログラミング勉強】GetLastError()関数とは?

自動売買プログラム8:初期化関数を設定しよう

int init()
{
    Adjusted_Slippage = AdjustSlippage(Symbol(), Slippage);
    return(0);
}

int init(){コード}は、インジケーターを稼働させたときの初期設定をします。

{コード}が初期設定の内容になります。

今回はAdjustSlippage関数で、許容スリッページを調整しています。

自動売買プログラム9:ティックが更新されるたびにプログラムを動かそう

//+------------------------------------------------------------------+
//| OnTick function                                                  |
//+------------------------------------------------------------------+
void OnTick()
  {
    //--- check for history and trading
   if(Bars<100 || IsTradeAllowed()==false)
   return;
   
    //--- calculate open orders by current symbol
   if(CalculateCurrentOrders(Symbol())==0) CheckForOpen();
   else                                    CheckForClose();
    //---
}

OnTick()はティックが動くたびに{コード}が動きます。

これがないと最初の単発でプログラムが止まってしまいます。

継続してプログラムを動かすには、OnTick()が必要です。

Barsは、「チャートウィンドウ」に表示されているバーの本数を格納しているint型の変数です。

バーが100本未満の場合、注文は出されません。

IsTradeAllowed()は、プログラムによる自動売買が許可されてるかどうかをチェックします。

許可されていない場合、falseが返され、注文は出されません。

if(CalculateCurrentOrders(Symbol())==0) CheckForOpen();
else CheckForClose();

上記の関数が呼び出され、注文や決済などが実行されます。

これをずっと繰り返すことで自動売買が行われます。

以上が自動売買プログラムの内容でした。

最後にストラテジーテスターで検証した結果を紹介します。

自動売買プログラムの実力はいかに!?ストラテジーテスターで検証しよう

自動売買プログラムをストラテジーテスターで検証する

MT4では過去のデータを使って、自動売買プログラムの実力を検証できます。

今回はJPY-USDペア、H1(1時間足)、2017年1月1日〜2021年7月1日までの期間で検証しました。

結果がこちら

ストラテジーテスターの資産曲線
自動売買プログラムのストラテジーテスターの結果レポート

テストの結果、−11%の成績なので、かなり損しています。。

トレード条件は妙案だと思いましたが・・・残念。

あとはチャートを表示して取引戦略を変えたり

損切り、利食いの条件を追加したり

通貨ペアや時間足を変えてみたりして改善していきます。

もしあなたも自動売買プログラムを勉強するなら、こちらの本がおすすめです。

セットもあります。

ネットは1つのコードを調べるのに便利ですが、まとまった理論などは本のほうが勉強しやすいですね。

ここまで読んでくださりありがとうございました!

1995年生まれ、佐賀県出身。 高専を卒業して大手化学メーカーの研究職に就職するが、投資で1200万円作ったので好きなことをやろうと決め、5年で退職する。 現在は保険代理店に勤めながらお金から自由になる仕組みづくりに挑戦している。 趣味は国内旅行。「コロナが明けたら旅行に行こう」が妻との合言葉。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

CAPTCHA