FXオートトレードプログラムを開発する会(3)

前回のリンク
次回のリンク

今回はMT5のサンプルプログラムである”Moving Average.mq5″の中の”TradeSizeOptimized”関数の内容を解読しました.

TradeSizeOptimized

double TradeSizeOptimized(void)
  {
   double price=0.0;
   double margin=0.0;
//--- select lot size
   if(!SymbolInfoDouble(_Symbol,SYMBOL_ASK,price))
      return(0.0);
   if(!OrderCalcMargin(ORDER_TYPE_BUY,_Symbol,1.0,price,margin))
      return(0.0);
   if(margin<=0.0)
      return(0.0);

   double lot=NormalizeDouble(AccountInfoDouble(ACCOUNT_MARGIN_FREE)*MaximumRisk/margin,2);
//--- calculate number of losses orders without a break
   if(DecreaseFactor>0)
     {
      //--- select history for access
      HistorySelect(0,TimeCurrent());
      //---
      int    orders=HistoryDealsTotal();  // total history deals
      int    losses=0;                    // number of losses orders without a break

      for(int i=orders-1;i>=0;i--)
        {
         ulong ticket=HistoryDealGetTicket(i);
         if(ticket==0)
           {
            Print("HistoryDealGetTicket failed, no trade history");
            break;
           }
         //--- check symbol
         if(HistoryDealGetString(ticket,DEAL_SYMBOL)!=_Symbol)
            continue;
         //--- check Expert Magic number
         if(HistoryDealGetInteger(ticket,DEAL_MAGIC)!=MA_MAGIC)
            continue;
         //--- check profit
         double profit=HistoryDealGetDouble(ticket,DEAL_PROFIT);
         if(profit>0.0)
            break;
         if(profit<0.0)
            losses++;
        }
      //---
      if(losses>1)
         lot=NormalizeDouble(lot-lot*losses/DecreaseFactor,1);
     }
//--- normalize and check limits
   double stepvol=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP);
   lot=stepvol*NormalizeDouble(lot/stepvol,0);

   double minvol=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);
   if(lot<minvol)
      lot=minvol;

   double maxvol=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX);
   if(lot>maxvol)
      lot=maxvol;
//--- return trading volume
   return(lot);
  }
スポンサーリンク

概要

TradeSizeOptimizedは取引の際に最適な取引lot量Vを決定する関数で基本的には

$$V=余剰証拠金 \times MaximumRisk/1lotの取引に必要な証拠金$$

で決定します.

さらに過去2回以上連続で損をしている場合は上記の式求めた取引lot量Vを以下の式で減らした値V’で取引するようになっています.

$$V’=V-V \times 連続で損している回数/DecreaseFactor$$

このように連続で損しているときには取引量を減らすことでリスクを回避しているようです.

ここでMaximumRiskやDecreaseFactorは自分で決定する数字でサンプルプログラム内ではそれぞれ0.02と3になっています.

TradeSizeOptimizedのフローチャート.簡略化のため,少し例外処理を省略しています.
スポンサーリンク

詳細説明

double TradeSizeOptimized(void)
  {
//---省略
//--- return trading volume
   return(lot);
  }

TradeSizeOptimized関数は上記のような形をしていて,最終的にはdouble型でトレードのために適切なlot量を返す関数です.

関数の詳細な処理の解説

   double price=0.0;
   double margin=0.0;

上記は変数の初期化(宣言)を行う部分です.priceやmarginはこの後使用する変数で買値や取引に必要な証拠金の量(マージン)が入ります.

//--- select lot size
   if(!SymbolInfoDouble(_Symbol,SYMBOL_ASK,price))
      return(0.0);
   if(!OrderCalcMargin(ORDER_TYPE_BUY,_Symbol,1.0,price,margin))
      return(0.0);
   if(margin<=0.0)
      return(0.0);

この部分では上記の3つの条件に一致した場合return(0.0)が実行され取引lot量が0,つまり取引が行われないようになっています.

一つ目のif文ではSymbolInfoDouble関数がエラーを吐いた場合にlot量0を返します.

SymbolInfoDouble関数は以下のように入力したシンボル(銘柄名)の指定されたプロパティを最後の引数に入力した変数に返します.

//SymbolInfoDouble関数の引数
bool  SymbolInfoDouble(
  string                  name,      // シンボル
  ENUM_SYMBOL_INFO_DOUBLE prop_id,   // プロパティの識別子
  double&                  double_var  // プロパティ値を受け取る変数
  );

今回の場合は取引銘柄_Symbolの買値(SYMBOL_ASKなので)がpriceに入力するという意味になっていて,できなかった場合にif文の中のreturn(0.0)に進むようになっています.

二つ目のif文の中身では取引に必要な証拠金の量marginを取得しています.ここでもmarginの量が取得できなかったときにIf文の中に入りlot量0を返すようになっています.

//OrderCalcMargin関数の引数
bool  OrderCalcMargin(
  ENUM_ORDER_TYPE      action,          // 注文の種類
  string                symbol,          // 銘柄名
  double                volume,          // ボリューム
  double                price,            // 始値
  double&               margin           // 証拠金取得に使用される変数
  );

今回の場合は成り行き買い注文(ORDER_TYPE_BUY)で銘柄名(_Symbol)の1ボリュームで始値が先ほど取得した買値priceの時に必要な証拠金の量をmarginに入れています.

最後のif文では先ほど取得した必要な証拠金の量が0より小さいときreturn(0.0)を返すようになっています.

   double lot=NormalizeDouble(AccountInfoDouble(ACCOUNT_MARGIN_FREE)*MaximumRisk/margin,2);

この行ではAccountInfoDouble(ACCOUNT_MARGIN_FREE)で預金通貨での口座の余剰証拠金の量を取得し,それにグローバル変数として宣言したリスク率(MaximumRisk)をかけて,1lotの取引に必要なmarginの量で割った数を取引するlot量としています.

NormalizeDouble(取引lot量,2)では有効数字の丸めを行っていて小数点以下2に丸めています.これは通貨の最小取引単位pipsがその通貨の100分の1であるからだとおもいます.

//--- calculate number of losses orders without a break
   if(DecreaseFactor>0)
     {
      //連続で損失を出した回数をlossesに入れる部分(省略)
      //---
      if(losses>1)
         lot=NormalizeDouble(lot-lot*losses/DecreaseFactor,1);
     }

ここでは何回連続で取引で損を出しているかを計算して,連続で損しているときほど取引量を減らすようにしています.

まず初めのif文の条件である(DecreaseFacter>0)を満たさなければこのブロックは何も実行されません.DecreaseFacterは連続で損した回数に対してどのくらい取引量を減らすかを決める変数で,この値が負だったりすると逆に取引量を増やすことになってしまうからです.DecreaseFacterはグローバル変数として宣言されています.

最後の部分は連続して損失を出した取引の回数(losses)が2回以上の時にif文の条件が満たされ,lossesの大きさに応じて取引量が小さくなるようになっています.例えばDecreaseFacter=3なら3回連続で取引で損失を出しているときに取引量が最小になるようになっています.

以下では省略した部分を見ていきます.

//--- select history for access
      HistorySelect(0,TimeCurrent());
      //---
      int    orders=HistoryDealsTotal();  // total history deals
      int    losses=0;                    // number of losses orders without a break

HistorySelect(0,TimeCurrent());では0から現在までの約定と注文履歴を取得しています.

//HistorySelect関数の引数
bool  HistorySelect(
  datetime  from_date,    // 開始日
  datetime  to_date        // 終了日
  );

ここで取得した履歴からHistoryDealsTotal()などの関数で必要な情報を取得することができます.

ここではまずHistoryDealsTotal()で変数ordersに過去の約定回数を保存しています.

そして,一番最新の約定(orders-1番目)からprofit利益が初めて出るところまでループを回し,連続で損を出した回数をカウントしています.

      for(int i=orders-1;i>=0;i--)
        {
         ulong ticket=HistoryDealGetTicket(i);
         if(ticket==0)
           {
            Print("HistoryDealGetTicket failed, no trade history");
            break;
           }
         //--- check symbol
         if(HistoryDealGetString(ticket,DEAL_SYMBOL)!=_Symbol)
            continue;
         //--- check Expert Magic number
         if(HistoryDealGetInteger(ticket,DEAL_MAGIC)!=MA_MAGIC)
            continue;
         //--- check profit
         double profit=HistoryDealGetDouble(ticket,DEAL_PROFIT);
         if(profit>0.0)
            break;
         if(profit<0.0)
            losses++;
        }

具体的にはHistoryDealGetTicket(i)で過去に約定したチケットの情報を取得し,double profit=HistoryDealGetDouble(ticket,DEAL_PROFIT); 約定によって得られた損益を取得しています.

その前の3つのif文はチケットが取得できたかどうか,チケットのシンボルが同じかどうか,チケットのマジックナンバーが同じかどうか(このプログラムで取引したチケットかどうか)を確認しています.後半の2つはcontinueを使用することでそのあとの処理を飛ばし次のチケットへ移動させています.

if(losses>1)
   lot=NormalizeDouble(lot-lot*losses/DecreaseFactor,1);
//--- normalize and check limits
   double stepvol=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP);
   lot=stepvol*NormalizeDouble(lot/stepvol,0);

   double minvol=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);
   if(lot<minvol)
      lot=minvol;

   double maxvol=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX);
   if(lot>maxvol)
      lot=maxvol;

この最後のブロックではstepvol=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP);で約定実行のための最小限のボリューム変化のステップを取得し,lot量がその整数倍になるように規格化しています.

その後,SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN)で約定に最小ボリューム,SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX)で最大ボリュームを取得し,lot量がこの範囲から出ている場合はこの範囲に入る値に修正しています.

このような処理をしてTradeSizeOptimized関数は適切なlot量を返り値として返しています.

次回のリンクはこちら

コメント

タイトルとURLをコピーしました