投資でプログラミングを活用したい!MT4のサンプルコードを勉強した話

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

FX投資の自動売買プログラムは無料で作れるのは知っていますか?

私はトレーダーと一緒に自動売買プログラムづくりにチャレンジしています。

(参考:FX投資の自動売買プログラムを作るきっかけ

ただ、プログラミング言語MQL4は初体験なので勉強しながら進めています。

今回はサンプルコードを使って、自動売買プログラムの書き方を解説します。

FX投資の自動売買プログラムは素人でも無料で作れます!

FX投資の自動売買プログラムは素人でも無料で作れます!

私はXMTradingのMT4をダウンロードして、自動売買プログラムを書いています。

プログラミング言語はMQL4です。

MQL4は、MT4で自動売買したり独自の分析をしたりするための言語です。

C言語に似ていると言われています。

私はC言語も書いたことないので、サンプルプログラムを真似しながら勉強を始めました。

今回はサンプルプログラムの内容を一つずつ解説します。

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

//+------------------------------------------------------------------+
//| Moving Average.mq4 |
//| Copyright 2005-2014, MetaQuotes Software Corp. |
//| http://www.mql4.com |
//+------------------------------------------------------------------+

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

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

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

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

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

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

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

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

property copyright   "2005-2014, MetaQuotes Software Corp."
#property link        "http://www.mql4.com"
#property description "Moving Average sample expert advisor"

#define MAGICMA  20131111

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

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

#property 識別子 値

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

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

FX投資の自動売買プログラムが稼働したときの様子

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

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

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

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

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

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

//--- Inputs
input double Lots =0.1;
input double MaximumRisk =0.02;
input double DecreaseFactor=3;
input int MovingPeriod =12;
input int MovingShift =6;

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

FX投資の自動売買プログラムで初期値を入力する画面

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

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

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

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

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

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

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

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

//+------------------------------------------------------------------+
//| Calculate open positions                                         |
//+------------------------------------------------------------------+
int CalculateCurrentOrders(string symbol)
  {
   int buys=0,sells=0;
//---
   for(int i=0;i<OrdersTotal();i++)
     {
      if(OrderSelect(i,SELECT_BY_POS,MODE_TRADES)==false) break;
      if(OrderSymbol()==Symbol() && OrderMagicNumber()==MAGICMA)
        {
         if(OrderType()==OP_BUY)  buys++;
         if(OrderType()==OP_SELL) sells++;
        }
     }
//--- return orders volume
   if(buys>0) 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をマイナスの値にして返します。

自動売買プログラム5:適切なロットサイズを計算しよう

//+------------------------------------------------------------------+
//| Calculate optimal lot size                                       |
//+------------------------------------------------------------------+
double LotsOptimized()
  {
   double lot=Lots;
   int    orders=HistoryTotal();     // history orders total
   int    losses=0;                               // number of losses orders without a break
//--- select lot size
   lot=NormalizeDouble(AccountFreeMargin()*MaximumRisk/1000.0,1);
//--- calcuulate number of losses orders without a break
   if(DecreaseFactor>0)
     {
      for(int i=orders-1;i>=0;i--)
        {
         if(OrderSelect(i,SELECT_BY_POS,MODE_HISTORY)==false)
           {
            Print("Error in history!");
            break;
           }
         if(OrderSymbol()!=Symbol() || OrderType()>OP_SELL)
            continue;
         //---
         if(OrderProfit()>0) break;
         if(OrderProfit()<0) losses++;
        }
      if(losses>1)
         lot=NormalizeDouble(lot-lot*losses/DecreaseFactor,1);
     }
//--- return lot size
   if(lot<0.1) lot=0.1;
   return(lot);
  }

ロットサイズとは、取引単位のことです。

実際のトレードではロット数ごとに外貨を購入したり売却したりします。

つまりロット数を小さくするほど、少額でトレードができます。

最小のロット数はFX会社により異なります。

また、同じロット数でもトレードする金額は通貨ペアやレートによって変わります。

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

HistoryTotal()は、OrdersHistoryTotal()の昔の関数です。

決済済み注文の数を返します。

double NormalizeDouble(
    double value,     // 浮動小数点数
    int digits               // 丸める小数点以下の桁数
    );

浮動小数点数を指定した精度で丸めます

コードではvalueにAccountFreeMargin()*MaximumRisk/1000.0が入ります。

AccountFreeMargin()はアカウントの余剰証拠金です。

つまり注文できる金額にMaximumRiskをかけ、1000.0で割っています。

さらにdigitsは1なので、小数点第1位までで丸めます。

これをlotとして、1注文あたりのロットサイズとします。

DecreaseFactorは、負けトレード回数によってロット数を下げる減少係数です。

負けトレードで損失が大きくなると、ロットを減らす処理が入ります。

負けトレードの数はlosessに入ります。

losessは整数値で、かつ1より大きいときにロット数を減らします。

if(losses>1)
lot=NormalizeDouble(lot-lot*losses/DecreaseFactor,1);

//--- return lot size
if(lot<0.1) lot=0.1;
return(lot);

ただし、lotは最小値が0.1となるようにしています。

自動売買プログラム6:注文を出そう

//+------------------------------------------------------------------+
//| Check for open order conditions                                  |
//+------------------------------------------------------------------+
void CheckForOpen()
  {
   double ma;
   int    res;
//--- go trading only for first tiks of new bar
   if(Volume[0]>1) return;
//--- get Moving Average 
   ma=iMA(NULL,0,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE,0);
//--- sell conditions
   if(Open[1]>ma && Close[1]<ma)
     {
      res=OrderSend(Symbol(),OP_SELL,LotsOptimized(),Bid,3,0,0,"",MAGICMA,0,Red);
      return;
     }
//--- buy conditions
   if(Open[1]<ma && Close[1]>ma)
     {
      res=OrderSend(Symbol(),OP_BUY,LotsOptimized(),Ask,3,0,0,"",MAGICMA,0,Blue);
      return;
     }
//---
  }

void型は関数が戻り値を返さない事を示すのに用いられます。

つまり関数内で処理が完結します。

また、関数の引数が無い時にも使用します。

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,0);

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

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

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

計算には終値を使い、バーの位置は0(現在値)とします。

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        //チャート上に表示するオブジェクトの色
    );

売り注文の場合、コードはこのようになっています。

res=OrderSend(Symbol(),OP_SELL,LotsOptimized(),Bid,3,0,0,"",MAGICMA,0,Red);

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

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

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

損切り、利食いはなし、コメントもなし

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

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

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

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

自動売買プログラム7:注文を決済しよう

//+------------------------------------------------------------------+
//| Check for close order conditions                                 |
//+------------------------------------------------------------------+
void CheckForClose()
  {
   double ma;
//--- go trading only for first tiks of new bar
   if(Volume[0]>1) return;
//--- get Moving Average 
   ma=iMA(NULL,0,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE,0);
//---
   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,3,White))
               Print("OrderClose error ",GetLastError());
           }
         break;
        }
      if(OrderType()==OP_SELL)
        {
         if(Open[1]<ma && Close[1]>ma)
           {
            if(!OrderClose(OrderTicket(),OrderLots(),Ask,3,White))
               Print("OrderClose error ",GetLastError());
           }
         break;
        }
     }
//---
  }

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

bool OrderClose(
    int ticket,                      //決済する注文のチケット番号(各注文に付される整数値)
    double lots,                 //ロットサイズ
    double price,              //注文価格
    int slippage,                 //許容スリッページ数
    color arrow_color     //チャート上に表示するオブジェクトの色
    );

OrderCloseは注文を決済する関数です。

正常に決済された場合は「true」が返され、正常に決済されなかった場合は「false」が返されます。

OrderClose(OrderTicket(),OrderLots(),Ask,3,White)

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

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

Askは買値、許容スリッページは3pips、チャート上には白色で表示されます。

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

つまり決済が失敗したときにPrintでエラー内容が表示されます。

自動売買プログラム8:ティックが動くたびにプログラムを動かそう

//+------------------------------------------------------------------+
//| 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();

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

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

以上がサンプルコードの内容でした。

次回はトレーダーから依頼を受けて作成したプログラムのコードを紹介したいと思います。

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

セットもあります。

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

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

コメントを残す

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

CAPTCHA