固定ロットと複利ロットをバックテストで比較したいと思ったことはないでしょうか。

ただ、別々のEAを用意すると「条件が微妙に違う」という理由で、

結果の差が信用できなくなることがあります。

この記事では、入力パラメーター1つでロットの決め方だけを切り替えるEAを設計し、

2回のバックテスト結果をCSVログで比較する方法を解説します。

この記事で分かること
・固定ロットと複利ロットを同条件で比較するための設計の考え方
・ENUM入力パラメーターでロットの決め方だけを切り替える方法
・MathRoundで刻み値の倍数にロットを丸める計算の再利用
・CSVログで2回のテスト結果を並べて比較する方法

なぜ比較の「条件を揃える」ことが難しいのか

まず、よくある失敗パターンを確認します。

別々のEAで比較する方法の問題点

別々のEAで作ると、ロット計算以外の部分を

完全に同じに保つのが難しく、差の原因が分からなくなります。

1つのEAに両方の計算方法を入れる発想

そこで、1つのEAの中に両方の計算方法を用意し、

入力パラメーターで切り替える設計にします。

この記事で作るものの概要

LotMode ロットの決め方
LOT_MODE_FIXED 常に一定のロット数
LOT_MODE_COMPOUND 口座残高のリスク%から毎回計算

ENUMでロットモードを切り替える設計

切り替える部分の設計を見ていきます。

ENUM_LOT_MODEの定義

enum ENUM_LOT_MODE
  {
   LOT_MODE_FIXED,
   LOT_MODE_COMPOUND
  };

input ENUM_LOT_MODE InpLotMode = LOT_MODE_FIXED;

ENUM型にすると、MT4の設定画面でドロップダウンから選べます。

固定ロットの計算

double lots = InpFixedLot;

固定モードでは入力値をそのまま使います。

複利ロットの計算(既刊記事の再利用)

double riskAmount = AccountBalance() * InpRiskPercent / 100.0;
double lots = riskAmount / (slDistancePoints * tickValue);

口座残高にリスク%を掛けた金額を損切り幅で割って逆算します。

刻み値の倍数への丸め(前回記事の再利用)

計算したロット数はそのまま使うと不正になります。

MathRoundで刻み値の倍数に丸める

double lotStep = MarketInfo(Symbol(), MODE_LOTSTEP);
lots = MathRound(lots / lotStep) * lotStep;

既刊のMathRound/MathAbsの記事で解説した計算式を

そのまま再利用します。

最小・最大ロットの範囲チェック

double minLot = MarketInfo(Symbol(), MODE_MINLOT);
double maxLot = MarketInfo(Symbol(), MODE_MAXLOT);

if(lots < minLot) lots = minLot;
if(lots > maxLot) lots = maxLot;

複利モードで残高が少ないと最小ロットを下回ることがあるため必須です。

両モードで共通の損切り幅を使う

ロットの決め方だけを変え、損切り幅やエントリー条件は

両モードで同じロジックを使うことが最も重要なポイントです。

バックテスト結果をCSVログに残す

2回の結果を見比べるため、ファイルに記録します。

OnDeinitで最終結果を記録する

void OnDeinit(const int reason)
  {
   WriteComparisonLog();
  }

ストラテジーテスターの実行終了時にログ処理を呼び出します。

FileOpenで追記モードで開く

int handle = FileOpen(InpLogFileName, FILE_READ|FILE_WRITE|FILE_CSV|FILE_ANSI, ',');
FileSeek(handle, 0, SEEK_END);

既刊のFileOpen/FileWrite記事と同じ書き方で、

FileSeekで末尾に移動し追記できるようにします。

モード名・最終バランスを書き込む

FileWrite(handle, TimeToString(TimeCurrent()), EnumToString(InpLotMode),
          DoubleToString(AccountBalance(), 2));
FileClose(handle);

LotModeをEnumToStringで文字列化して記録するため、

2回分の結果が並んでもどちらのモードか一目で分かります。

実際に動くコード全体

移動平均線のクロスをエントリー条件にした比較検証用EAの全体コードです。

//+------------------------------------------------------------------+
//|  FixedVsCompoundLotCompare.mq4 - FX-EA System Project Creator     |
//+------------------------------------------------------------------+
#property strict

enum ENUM_LOT_MODE
  {
   LOT_MODE_FIXED,
   LOT_MODE_COMPOUND
  };

input ENUM_LOT_MODE InpLotMode      = LOT_MODE_FIXED;
input double        InpFixedLot     = 0.10;
input double        InpRiskPercent  = 2.0;
input int           InpFastMA       = 10;
input int           InpSlowMA       = 30;
input int           InpStopLossPts  = 300;
input int           InpMagicNumber  = 20261003;
input string        InpLogFileName  = "lot_mode_comparison_log.csv";

double CalcLots(double slDistancePoints)
  {
   double lotStep = MarketInfo(Symbol(), MODE_LOTSTEP);
   double minLot   = MarketInfo(Symbol(), MODE_MINLOT);
   double maxLot   = MarketInfo(Symbol(), MODE_MAXLOT);
   double tickValue = MarketInfo(Symbol(), MODE_TICKVALUE);

   double lots;
   if(InpLotMode == LOT_MODE_FIXED)
     {
      lots = InpFixedLot;
     }
   else
     {
      double riskAmount = AccountBalance() * InpRiskPercent / 100.0;
      lots = riskAmount / (slDistancePoints * tickValue);
     }

   lots = MathRound(lots / lotStep) * lotStep;

   if(lots < minLot) lots = minLot;
   if(lots > maxLot) lots = maxLot;

   return(lots);
  }

void OnTick()
  {
   for(int i = 0; i < OrdersTotal(); i++)
      if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES) && OrderMagicNumber() == InpMagicNumber)
         return;

   double fastMA = iMA(NULL, 0, InpFastMA, 0, MODE_SMA, PRICE_CLOSE, 0);
   double slowMA = iMA(NULL, 0, InpSlowMA, 0, MODE_SMA, PRICE_CLOSE, 0);
   double fastMAPrev = iMA(NULL, 0, InpFastMA, 0, MODE_SMA, PRICE_CLOSE, 1);
   double slowMAPrev = iMA(NULL, 0, InpSlowMA, 0, MODE_SMA, PRICE_CLOSE, 1);

   bool goldenCross = (fastMAPrev <= slowMAPrev && fastMA > slowMA);

   if(!goldenCross)
      return;

   double lots = CalcLots(InpStopLossPts);
   double sl = Ask - InpStopLossPts * Point;

   OrderSend(Symbol(), OP_BUY, lots, Ask, 3, sl, 0, "FixedVsCompound", InpMagicNumber, 0, clrBlue);
  }

void WriteComparisonLog()
  {
   int handle = FileOpen(InpLogFileName, FILE_READ|FILE_WRITE|FILE_CSV|FILE_ANSI, ',');
   if(handle == INVALID_HANDLE)
      return;

   FileSeek(handle, 0, SEEK_END);
   FileWrite(handle, TimeToString(TimeCurrent()), EnumToString(InpLotMode),
             DoubleToString(AccountBalance(), 2));
   FileClose(handle);
  }

void OnDeinit(const int reason)
  {
   WriteComparisonLog();
  }

MetaEditorでのコンパイル・実行手順

  1. MetaEditorで新規EA「FixedVsCompoundLotCompare」を作成し、上のコードを貼り付けてコンパイルする
  2. InpLotMode=LOT_MODE_FIXEDのままストラテジーテスターで1回目を実行する
  3. 同じ期間・ペアでInpLotMode=LOT_MODE_COMPOUNDに変え2回目を実行する
  4. MQL4/Filesフォルダのlot_mode_comparison_log.csvで2行を見比べる

よくあるエラーと注意点

実際に使うときにつまずきやすいポイントをまとめます。

テスト期間・通貨ペアを揃え忘れる

注意
・テスト期間や通貨ペアがズレると比較が無意味になります
・LotMode以外の設定は変えずに2回のテストを実行してください
・スプレッド設定も揃えると差の原因をロットだけに絞れます

MQL5のOnTesterに相当する機能がMT4にない点

MQL5のOnTester/OnTesterPassのような

結果自動取得のフックは、MT4には存在しません。

ログファイルを消さずに複数回比較する方法

実践アドバイス
・追記方式なので何回テストしても結果が積み重なります
・別の比較軸を試すときはログファイル名を変えて管理してください
・古い結果を消したいときだけファイルを手動で削除してください

よくある質問

固定ロットと複利ロットの比較設計について、よく検索される疑問に回答します。

このEAをそのまま実運用に使ってもいいですか?

このEAはあくまで比較検証用のサンプルです。

エントリー条件は単純なロジックのため、

実運用の前に自分の戦略に置き換えてください。

複利ロットの方が常に有利になりますか?

一概には言えません。

資金が増えるほどロットも増える分、

連敗時はロットが大きいまま損失が続くリスクもあります。

【MQL】固定ロットと複利ロットを同じ条件でバックテスト比較できるEAの設計についてわかりやすく解説してみた:まとめ

別々のEAで比較すると、条件のズレで結果が信用できなくなります。

ENUM入力でロット計算部分だけを切り替え、

CSVログに結果を記録すれば、同じ条件のまま公平に比較できます。

関連記事