English Deutsch
preview
制約付きCustom Maxを実装するための一般的な最適化定式化(GOF)

制約付きCustom Maxを実装するための一般的な最適化定式化(GOF)

MetaTrader 5 | 24 5月 2024, 10:41
68 0
better.trader every.day
better.trader every.day

はじめに:最適化の基本

最適化問題には問題の定式化と問題の解決の2段階があります。第1段階には、入力変数、目的関数、制約関数の3つの主要コンポーネントがあります。第2段階では、最適化アルゴリズムを用いて数値的に問題を解きます。

変数(x_1、x_2、x_3、, ...、x_n):  変数とは、目的関数を最大化するために変更できる「つまみ」です。整数型、実数型、ブール型など、さまざまな型の変数があります。エキスパートアドバイザー(EA)では、移動平均期間、エントリ時のTP/SLレシオ、ピップ単位でのSLなどの変数を使用できます。

目的関数(f_i (x)): 複数の目的関数がある場合、その問題は多目的最適化問題と呼ばれます。MetaTrader 5の最適化アルゴリズムでは1つの目的関数を想定しているため、複数の目的を考慮するには、それらを1つにまとめる必要があります。目的関数を組み合わせる最も簡単な方法は、目的関数の加重和を作ることです。開発者の何人かが乗算演算を使用してそれらを組み合わせることを提案しています。これはいくつかの状況で機能するかもしれませんが、私たちはより良いアプローチとして合計を好みます。本記事では、以下に説明するように、目的の「的を絞った加重平均」を用います。目的関数の例としては、残高、利益目標、勝率、年間リターン、純利益などがあります。

制約関数(g_i (x)): ユーザーが制限したい値を生成する関数です。境界は、 g_i(x) ≤ U_iのように上限にすることもできます。ここで、 g_i(x)は i_ 番目の制約、 U_i は上限です。同様に、下限の制約はg_i(x) ≥ L_i のようなもので、L_iが下限です。

MetaTrader 5の最適化アルゴリズムは、入力変数に課された制約(side constraintとも呼ばれる)(例:3 <= x_1 <= 4)のみを使用します他の制約タイプを使用することはできません。したがって、以下に示すように、追加制約g_i(x)の存在を、最終的な単一の目的関数F(x)に含める必要があります。制約関数g_i(x)の例としては、連敗回数の制限、シャープレシオ、勝率などがあります。


最適化アルゴリズム

一般的に、最適化アルゴリズムには大きく分けて2つのタイプがあります。最初のタイプは、最適化問題に関与するすべての関数の勾配の計算に基づく、より古典的なものです(これはアイザックニュートンの時代にさかのぼる)。2つ目のタイプは、より最近のもの(~1970年代)で、勾配情報をまったく使用しません。その中間に、前述の2つのアプローチを組み合わせたアルゴリズムがあるかもしれませんが、ここで取り上げる必要はありません。MetaTrader 5の端末設定タブにある[高速遺伝的アルゴリズム]と呼ばれるアルゴリズムは、2番目のタイプに属します。これにより、目的関数と制約関数の勾配計算を省略することができます。さらに、MetaTrader 5アルゴリズムの勾配なしの性質のおかげで、勾配ベースのアルゴリズムでは適切でなかった制約関数を考慮することができました。これについては後述します。

1つ重要な点は、[完全アルゴリズム(遅い)]と呼ばれるMetaTrader 5のアルゴリズムは、実際には最適化アルゴリズムではなく、side constraintの範囲内で、すべての入力変数の値のすべての可能な組み合わせを総当たり的に評価するということです。


複数の目的関数f_i(x)から構成される目的関数F(x)

このセクションでは、複数の目的関数を1つの目的関数Fに組み合わせることについて説明します。

「はじめに」で述べたように、複数の目的を組み合わせるために、ここでは合計を使用します。どうしてもお望みなら、ソースコードを変更して乗算の組み合わせをご使用ください。和算の組み合わせ式はこうなります。

目的関数の定式化

ここで 

  • x:n個の入力変数のベクトル
  • f_i(x):i番目の目的関数
  • W_i:i番目の重み
  • T_i:i番目の目的関数に対して望まれるi番目の目標

目標は、与えられた目的が合計の中で他の目的と比較できるように、正規化(分割)値として機能します。言い換えれば、重み付き和は「正規化された」目的関数のためのものです。この重みは、正規化された目的( f_i(x)/T_i )に乗じる重みとして機能し、どの正規化された目的が他よりも重要であるかを強調することができます。重みW_iは、W_i*f_i(x)/T_iの和をW_iの和で割ることによって凸化されます。

W_iの選択: 重みを凸化することで、ユーザーは重みの絶対値ではなく、相対値に注目することができます。例えば、年間リターン、リカバリーファクター、プロフィットファクターという3つの目的があるとします。重みW_1=10、W_2=5、W_3=1は、W_1=100、W_2=50、W_3=10と同じ効果を持ちます。つまり、正規化目的1(年間リターン/T_1)は正規化目的2(リカバリーファクター/T_2)の2倍、正規化目的3(プロフィットファクター/T_3)の10倍重要視されます。正でありユーザーの基準に基づく相対的な重要性を表す限り、その値に正誤はありません。


T_iの選択 目標T_iの選択は、完全な最適化をおこなう前に、数回のシミュレーションをおこなった後、慎重におこなわなければなりません。そうする理由は、各目的関数の範囲を推定し、正規化された関数(f_i(x)/T_i)が同等の大きさになるようにT_i値を設定することです。例えば、口座の初期残高が10,000で、EAアルゴリズムが最適化される前に最終残高を約20,000にするとします。プロフィットファクターは0.9です。f_1=残高、f_2=プロフィットファクターという2つの目的で最適化問題を設定した場合、目標の良い値はT_1=30k、T_2=2となります。これによって、正規化された両関数は同じオーダー(同程度の値)になります。一度、完全な最適化を実行すると、EAが非常に大きな最終残高と同様の利益係数を与えることに気づくかもしれません。この時点で、正規化された関数のオーダーが異なれば、T_i値を調整することができます。目標値も正の実数でなければなりません。ご自分の判断でおこなってください。このトピックの詳細については、GOFコードからの出力を取り上げる際に説明します。


制約の追加 

目的関数を個々の目的関数の加重和として定式化することは、ごく普通のことです。ここで、MetaTrader 5の高速遺伝学ベースのアルゴリズムが機能するように、最適化問題に制約関数を含める簡単な方法を紹介します。ここでは、ラグランジュの未定乗数法を修正したものを使用します。この方式では、単一の最適化目的からペナルティを差し引きます。  そのために、各制約の違反量を測定する新しいペナルティ関数を定義します。

ペナルティ

おわかりのように、P_i(x)はg_i(x)が違反したときに正になり、そうでないときは0になります。U_iまたはL_iが0の場合、0による除算を避けるため、それらを1に置き換えます。

ラグランジュ乗数法では、制約1つにつき乗数が1つで、その値が連立方程式の解となります。私たちの修正版では、すべての乗数は同じであり、一定の値k_pを持つと仮定します(以下の式を参照)。この単純化がここで有効なのは、最適化アルゴリズムが最適化問題のどの関数の勾配も計算する必要がないためです。


最終的な単一目的関数F(x)は次のようになります。


objfun

注:記号「:=」は代入を意味し、数学的定義ではありません。 

乗数k_pはラグランジュ乗数の役割を果たし、実行不可能な設計(少なくとも1つの制約に違反する設計)を強制的に非常に低い目的にするために使用されます。この目的値の低下により、MetaTrader 5の遺伝的アルゴリズムは、その設計を非常に低くランク付けすることになり、次世代の設計の再生に使用される可能性は低くなります。

乗数k_oはラグランジュ乗数法の一部ではありません。MetaTrader 5端末の最適化プロットのY軸のプラス側を拡大するために、実行可能な設計の目的関数を増加させるために使用されます。 

K_oとK_pの値は、Miscellaneousセクションで変更することができます。k_oとK_pの値は10の累乗(例:10、100、1000など)を推奨します。


MetaTrader 5端末入力タブのスクリーンショット

GOFには目的関数、制約関数、その他の3つの入力セクションがあります。

以下は目的関数のセクションです。後で示す18の可能な関数のうち、問題定式化に含めることができる目的は5つまでです。5つ以上の目的を追加することはコードに従って可能ですが、3つを超えると重みと目的の選択が難しくなります。18以上の可能な関数を追加することは、コードに従っておこなうことができます。

obj section

以下はハード制約関数セクションです。GOFに実装されている14の制約関数のうち、最適化定式化に追加できる制約は最大10個です。10個以上の制約を追加することは、コードに従っておこなうことができます。新しい制約を14のリストに追加することも、コードに従っておこなうことができます。

制約セクション

以下は、その他の最適化パラメータセクションです。このセクションの詳細については、次の段落で説明します。

misc


EAでGenericOptimizationFormulation.mqhを使用する

もし記事全体を読む忍耐力がなければ、まずこのコードをEAで使用する手順を提供するので、まずはそれで遊んでみてください。それから記事の残りの部分を読んでさらに洞察を深めてください。以下は、GenericOptimizationFormulation.mqhファイルの最初のコメントです。

/*

In order to use this file in your EA you must do the following:

- Edit the EA .mq5 file

- Insert the following two lines after the input variables and before OnInit()

     ulong gof_magic= an_ulong_value;

     #include <YOUR_INCLUDE_LOCATION\GenericOptimizationFormulation.mqh>

- Save and compile your EA file

If you get compiler's errors, make sure you did:

- replace an_ulong_value by the variable containing the magic number, or by the magic numeric value

- replace YOUR_INCLUDE_LOCATION by the folder name where your include files are

*/

上記の挿入が自明であることを願っています。MetaTrader 5の移動平均EAを使用した例を後で紹介します。では、ソースコードの説明を続けましょう。


ソースコード:GenericOptimizationFormulation.mqh

さて、目的関数と制約の公式を示したところで、コードの断片を示しながらmql5の実装について説明します。

 読者へのお願い

  • コード行数が7行を超えるソフトウェアにバグが存在する確率は0ではないと言われています。GOFは1000行以上あるので、確率は間違いなく0ではありません。読者の皆さんには、コードを改善するためにコメントでフィードバックを提供することを奨励します。
  • より良いコードの書き方を見つけたら、GOFを改善するためにお知らせください。
  • 独自の目的関数と制約を追加してコードを拡張してください。コメントでも共有してください。
ライブラリを含む

まず、統計と代数をおこなうためのライブラリをいくつか入れる必要があります。

#include <Math\Stat\Lognormal.mqh>
#include <Math\Stat\Uniform.mqh>
#include <Math\Alglib\alglib.mqh>

選択できる目的関数は以下の通りです。

enum gof_FunctionDefs {        // functions to build objective
   MAX_NONE=0,                 // 0] None
   MAX_AnnRetPct,              // 1] Annual Return %
   MAX_Balance,                // 2] Balance
   MAX_NetProfit,              // 3] Net Profit
   MAX_SharpeRatio,            // 4] Sharpe Ratio
   MAX_ExpPayOff,              // 5] Expected Payoff
   MAX_RecovFact,              // 6] Recovery Factor
   MAX_ProfFact,               // 7] Profit Factor
   MAX_LRcrr3,                 // 8] LRcrr^3
   MAX_NbrTradesPerWeek,       // 9] #Trades/week
   MAX_WinRatePct,             // 10] Win Rate %
   MAX_Rew2RiskRatio,          // 11] Reward/Risk(RRR=AvgWin/AvgLoss)
   MAX_OneOverLRstd,           // 12] 1/(LR std%)
   MAX_OneHoverWorstTradePct,  // 13] 100/(1+|WorstLoss/Init.Dep*100|)
   MAX_LR,                     // 14] LRslope*LRcorr/LRstd
   MAX_OneHoverEqtyMaxDDpct,   // 15] 100/(1+EqtyMaxDD%))
   MAX_StratEfficiency,        // 16] Seff=Profit/(TotalTrades*AvgLot)
   MAX_KellyCrit,              // 17] Kelly Criterion
   MAX_OneOverRoRApct          // 18] 1/Max(0.01,RoRA %)
};


最適化問題を定式化するために、最大5つを選択する18の可能な目的があります。ユーザーは、同じ実装パターンに従って、ソースコードにさらに関数を追加することができます。目的関数の名前は、以下に挙げるものを除いて、自明であることを意図しています。

  • LRcrr^3:線形回帰相関係数の3乗
  • 1/(LR std%):LR stdは線形回帰の標準偏差です。その逆数は、エクイティラインが直線にどれだけ近いかを示します。
  • 100/(1+|WorstLoss/Init.Dep*100|):最大の損失を初期入金で割ったものがパフォーマンスの低さを示します。その逆がパフォーマンスの高さの指標となります。
  • LRslope*LRcorr/LRstd:線形回帰からの3つの関数(傾き、相関係数、標準偏差)の乗法目的です。
  • Seff=Profit/(TotalTrades*AvgLot): 戦略の効率性を示す指標。利益が高く、取引回数が少なく、ロットサイズが小さい戦略が好まれます。
  • 1/Max(0.01,RoRA %):RoRAとは、口座を破滅させるリスクのことです。後述するモンテカルロシミュレーションを使って計算されます。


選択できるハード制約は以下の通りです。

enum gof_HardConstrains {
   hc_NONE=0,                     // 0] None
   hc_MaxAccountLoss_pct,         // 1] Account Loss % InitDep
   hc_maxAllowed_DDpct,           // 2] Equity DrawDown %
   hc_maxAllowednbrConLossTrades, // 3] Consecutive losing trades
   hc_minAllowedWin_pct,          // 4] Win Rate %
   hc_minAllowedNbrTradesPerWeek, // 5] # trades/week
   hc_minAllowedRecovFactor,      // 6] Recov Factor
   hc_minAllowedRRRFactor,        // 7] Reward/Risk ratio
   hc_minAllowedAnnualReturn_pct, // 8] Annual Return in %
   hc_minAllowedProfFactor,       // 9] Profit Factor
   hc_minAllowedSharpeFactor,     // 10] Sharpe Factor
   hc_minAllowedExpPayOff,        // 11] Expected PayOff
   hc_minAllowedMarginLevel,      // 12] Smallest Margin Level
   hc_maxAllowedTradeLoss,        // 13] Max Loss trade
   hc_maxAllowedRoRApct           // 14] Risk of Ruin(%)
};
enum gof_HardConstType {
   hc_GT=0, // >=     Greater or equal to
   hc_LT    // <=     Less or equal to
};
最適化問題を定式化するために、最大10を選択するために14の可能な制約があります。ユーザーは、同じ実装パターンに従って、ソースコードにさらに制約を追加することができます。制約関数の名前は自明であることを意図しています。制約には2種類あり、hc_GTは下限の制約、ht_LTは上限の制約です。これについては、使い方を紹介するときに詳しく説明します。



破滅のリスク オプション

enum gof_RoRaCapital {
   roraCustomPct=0,  // Custom % of Ini.Dep.
   roraCustomAmount, // Custom Capital amount
   roraIniDep        // Initial deposit
};

口座を破滅させるリスクを計算する場合、「口座」の資金を定義するには3つの方法があります。最初の選択肢は、初期入金額に対するパーセンテージです。2つ目の選択肢は、口座通貨で提供される固定資本金額です。3つ目は、パーセンテージが100%の場合の最初の選択肢の特殊なケースです。破滅のリスクについては、後ほどコードで説明します。

目的関数小数

前述したように、結果列の小数点2桁を使ってシミュレーションの追加情報を表示することもできます。選択肢は以下の通りです。

enum gof_objFuncDecimals {
   fr_winRate=0, // WinRate %
   fr_MCRoRA,    // MonteCarlo Sim Risk of Ruin Account %
   fr_LRcorr,    // LR correlation
   fr_ConLoss,   // Max # Consecutive losing Trades
   fr_NONE       // None
};

fr_winRate:シミュレーションの勝率をパーセンテージで表したものです。例えば、勝率が34%の場合、結果の目標は0.39となります。勝率が100%の場合、0.99と表示されます。

fr_MCRoRA:口座を破綻させるリスクをパーセンテージで示したものです。例えば、口座を破滅させるリスクが11%であれば、結果の目標は0.11となります。

fr_LRcorr:線形回帰相関係数です。例えば、係数が0.88の場合、結果の目的は0.88となります。

fr_ConLoss:連続負けトレードの最大数。例えば、数字が7の場合、結果の目的は0.07となります。99以上の場合は0.99と表示されます。

fr_NONE:小数点以下の情報を表示したくない場合に使用します。


目的関数

コードの次のセクションは、個々の目的関数の選択です(最大5)。以下は、最初の関数のみのスニペットと、その目標と重みです。

input group   "- Build Custom Objective to Maximize:"
sinput gof_FunctionDefs gof_Func1   = MAX_AnnRetPct;          // Select Objective Function to Maximize 1:
sinput double gof_Target1           = 200;                    // Target 1
sinput double gof_Weight1           = 1;                      // Weight 1


制約関数

input group   "- Hard Constraints:"
sinput bool   gof_IncludeHardConstraints     = true;//if false, all constraints are ignored

sinput gof_HardConstrains gof_HC_1=hc_minAllowedAnnualReturn_pct; // Select Constraint Function 1:
sinput gof_HardConstType gof_HCType_1=hc_GT; // Type 1
sinput double gof_HCBound_1=50; // Bound Value 1

gof_IncludeHardConstraints=falseを設定することで、すべてのハード制約をオフにするオプションがあります。次に、最初の制約の選択、そのタイプ、制約値があります。10個の制約はすべて同じ形式を使用しています。

その他の最適化パラメータ

input group   "------ Misc Optimization Params -----"
sinput gof_objFuncDecimals gof_fr                  = fr_winRate;     // Choose Result-column's decimals
sinput gof_RoRaCapital  gof_roraMaxCap             = roraCustomPct;  // Choose capital method for Risk of Ruin
sinput double           gof_RoraCustomValue        = 10;             // Custom Value for Risk of Ruin (if needed)
sinput bool             gof_drawSummary            = false;          // Draw summary on chart
sinput bool             gof_printSummary           = true;           // Print summary on journal
sinput bool             gof_discardLargestProfit   = false;          // Subtract Largest Profit from Netprofit
sinput bool             gof_discardLargestLoss     = false;          // Add Largest Loss to Net profit
sinput double           gof_PenaltyMultiplier      = 100;            // Multiplier for Penalties (k_p)
sinput double           gof_ObjMultiplier          = 100;            // Multiplier for Objectives (k_o)

上記のセクションでは次を選択します。

  • gof_fr:結果の列に小数で表示する量 gof_roraMaxCap:RoRA資本の計算方法
  • gof_RoraCustomValue:RoRAの資本金額または初期入金の割合。これは前の行での選択次第です。
  • gof_drawSummary:GOFレポート概要をチャート上に描画することもできます。
  • gof_printSummary:[操作ログ]タブでGOFレポート概要の出力を選択できます。
  • gof_discardLargestProfit:単一の大きな利益を好む戦略を阻止するために、純利益から最大の利益を差し引くことができます。
  • gof_discardLargestLoss:大きな損失を出す戦略を阻止するために、最大の損失を純利益に加えることもできます。 
  • gof_PenaltyMultiplier:先の目的関数の定義で示した乗数K_p
  • gof_ObjMultiplier:先の目的関数の定義で示した乗数K_o

その他セクションのデフォルト値でうまくいくはずです。

コードの次の行は、変数を定義し、MetaTrader 5のTesterStatistics()関数から値をフェッチします。その後、GOFのメインセクションがやってきます。

//------------ GOF ----------------------
// Printing and displaying results from the simulation
   GOFsummaryReport();

// calculate the single objective function
   double SingleObjective = calcObjFunc();

// calculate the total penalty from constraint violations
   if(gof_IncludeHardConstraints) gof_constraintTotalPenalty=calcContraintTotalPenalty(gof_displayContraintFlag);

// Compute customMaxCriterion
// gof_PenaltyMultiplier pushes infeasible designs to have low objective values
// gof_PenaltyMultiplier expand the positive side of the Y axis
   double customMaxCriterion=gof_constraintTotalPenalty>0?
                             SingleObjective-gof_PenaltyMultiplier*gof_constraintTotalPenalty:
                             gof_ObjMultiplier*SingleObjective;

// add additional simulation result as two decimal digits in the result column
   customMaxCriterion=AddDecimalsToCustomMax(customMaxCriterion);

// Printing and displaying more results from GOF
   FinishGOFsummaryReport(customMaxCriterion);

   return (NormalizeDouble(customMaxCriterion,2));

操作ログ上のコードでは以下がおこなわれます。 

  • GOFsummaryReport():[操作ログ]タブとチャートに表示されるGOF概要レポートを作成する
  • calcObjFunc():組み合わされた単一の目的関数を計算する
  • calcContraintTotalPenalty():制約違反による合計ペナルティを計算する
  • customMaxCriterion:「はじめに」で示したように、単一の目的から制約違反のペナルティの合計を引いたものとして計算される
  • AddDecimalsToCustomMax():customMaxCriterionの小数部の情報を追加する
  • FinishGOFsummaryReport():GOF概要レポートを終了して出力する


残りのコードは、「はじめに」で示した公式をそのまま実装したものです。議論する価値があるのは、破滅のリスクを計算する部分だけです。

モンテカルロシミュレーションによる破滅のリスク

破滅のリスクは、単純な計算式で計算することもできますが、単純な計算式では賢明な結果が得られなかったので、代わりにモンテカルロシミュレーションを使用することにしました。モンテカルロ法では、平均勝敗、勝敗の標準偏差、勝率、シミュレーションの取引回数が必要です。さらに、口座の破滅を決定づける資本を提供する必要があります。 

double MonteCarlo_RiskOfRuinAccount(double WinRatePct, double AvgWin, double AvgLoss, double limitLoss_money, int nTrades) {
// 10000 Montecarlo simulations, each with at least 100 trades.
// Ideally, if we had lots of trades in the history, we could use a Markov Chain transfer probability matrix
//  we are limiting the statistics to mean & stdev, without knowledge of a transfer probability information

   double posDealsMean,posDealsStd,negDealsMean,negDealsStd;
   CalcDealStatistics(gof_dealsEquity, posDealsMean,posDealsStd,negDealsMean,negDealsStd);

// seeding the random number generator
   MathSrand((int)TimeLocal()+1);

// ignore posDealsMean and negDealsMean. Use AvgWin and AvgLoss instead
   AvgLoss=MathAbs(AvgLoss);
   WinRatePct=MathMin(100,MathMax(0,WinRatePct));

// case when win rate is 100%:
   if((int)(WinRatePct*nTrades/100)>=nTrades) {
      WinRatePct=99;          // just to be a bit conservative if winrate=100%
      AvgLoss=AvgWin/2;       // a guessengineering value
      negDealsStd=posDealsStd;// a guessengineering value
   }

// Use log-normal distribution function. Mean and Std are estimated as:
   double win_lnMean =log(AvgWin*AvgWin/sqrt(AvgWin*AvgWin+posDealsStd*posDealsStd));
   double loss_lnMean=log(AvgLoss*AvgLoss/sqrt(AvgLoss*AvgLoss+negDealsStd*negDealsStd));
   double win_lnstd  =sqrt(log(1+(posDealsStd*posDealsStd)/(AvgWin*AvgWin)));
   double loss_lnstd =sqrt(log(1+(negDealsStd*negDealsStd)/(AvgLoss*AvgLoss)));

   double rand_Win[],rand_Loss[];
   double r[];

// limit amount of money that defines Ruin
   limitLoss_money=MathAbs(limitLoss_money);
   bool success;
   int ruinCount=0; // counter of ruins
   int successfulMCcounter=0;
   int nTradesPerSim=MathMax(100,nTrades);// at least 100 trades per sim
   int nMCsims=10000; // MC sims, each one with nTradesPerSim

   for(int iMC=0; iMC<nMCsims; iMC++) {
      success=MathRandomUniform(0,1,nTradesPerSim,r);

      // generate nTradesPerSim wins and losses for each simulation
      // use LogNormal distribution
      success&=MathQuantileLognormal(r,win_lnMean,win_lnstd,true,false,rand_Win);
      success&=MathQuantileLognormal(r,loss_lnMean,loss_lnstd,true,false,rand_Loss);
      if(!success)continue;
      successfulMCcounter++;
      //simulate nTradesPerSim
      double eqty=0; // start each simulation with zero equity
      for(int i=0; i<nTradesPerSim; i++) {

         // draw a random number in [0,1]
         double randNumber=(double)MathRand()/32767.;

         // select a win or a loss depending on the win rate and the random number
         // and add to the equity
         eqty+=randNumber*100 < WinRatePct?
               rand_Win[i]:
               -rand_Loss[i];

         // check if equity is below the limit (ruin)
         // count the number of times there is a ruin
         if(eqty<= -limitLoss_money) {
            ruinCount++;
            break;
         }
      }
   }
// compute risk of ruin as percentage
   double RiskOfRuinPct=(double)(ruinCount)/successfulMCcounter*100.;
   return(RiskOfRuinPct);
}

理解しやすいように、上のコードには多くのコメント行を挿入しました。CalcDealStatistics()という関数もソースコードに含まれており、ここで勝敗の標準偏差が計算されます。破滅リスク計算の主な前提は、取引の履歴が対数正規分布に従うことであり、分布のサンプルが勝ちと負けでそれぞれ正と負の値になるようにすることです。

理想的には、取引履歴に多くの取引があれば、取引履歴の「対数正規性」を仮定する代わりに、マルコフ連鎖移動確率行列を使用することができます。  取引履歴はせいぜい数百件程度であるため、マルコフ連鎖の遷移確率行列を精度よく構築するには十分な情報がありません。 

破滅のMCリスクを解釈する

モンテカルロシミュレーションの結果は、予測値としてではなく、資本を失うリスクの確率として解釈する必要があります。例えば、モンテカルロシミュレーションのリターン値が1%であれば、取引戦略で総資本が一掃する可能性(確率)は1%あります。リスクのある資本の1%を失うという意味ではありません


Custom Maxに小数点下を加える

これはやっかいなことです。もっと良い方法を見つけたら、ぜひ教えてください。小数点下の値が計算されると(コードではdecと命名)、目的(obj)は次のように修正されます。

  obj=obj>0?
       MathFloor(obj*gof_ObjPositiveScalefactor)+dec/100.:
       -(MathFloor(-obj*gof_ObjNegativeScalefactor)+dec/100.);

ご覧のように、objが正の値の場合、大きな数(gof_ObjPositiveScalefactor=1e6)が掛けられ、切り捨てられた後、値decが100で割られ、少数として加算されます。objの値が負であるとき(多くの制約に違反していることを意味する)、負の値のために縦軸を圧縮するために、objに別の数(gof_ObjPositiveScalefactor =1e3)が乗算されます。



MetaTrader 5移動平均EAでのGOFの実装

以下は、MetaTrader 5のMoving Averages.mq5 EAにGOFを実装する方法を示す例です。

//+------------------------------------------------------------------+
//|                                              Moving Averages.mq5 |
//|                             Copyright 2000-2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2000-2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"

#include <Trade\Trade.mqh>

input double MaximumRisk        = 0.02;    // Maximum Risk in percentage
input double DecreaseFactor     = 3;       // Descrease factor
input int    MovingPeriod       = 12;      // Moving Average period
input int    MovingShift        = 6;       // Moving Average shift
//---
int    ExtHandle=0;
bool   ExtHedging=false;
CTrade ExtTrade;

#define MA_MAGIC 1234501

// changes needed to use the GenericOptimizationFormulation (GOF):
ulong gof_magic= MA_MAGIC;
#include <GenericOptimizationFormulation.mqh>
// end of changes

それだけです。EAファイルに2行追加するだけです。

GOFを使用した最適化の例

例11つの目的、制約なし([設定]タブでMetaTrader 5 Max Balanceを使用しているかのように)


GOFによる最大残高設定


[入力]タブは以下の通りです。目的は1つだけなので、目標と重みはまったく関係ありません。  ハード制約セクションの最初のブール変数で、すべての制約がオフになっていることに注目してください。


最大入力


その他セクションは以下の通りです。

maxbalmisc


結果は以下の通りです。Result列が口座残高ではなく、勝率を小数で表した修正目標であることをご覧ください。

変数の最良の組み合わせ(9,9,3)は939.81の利益と41%の勝率を生み出すことがわかります。

maxbalresults


表の最適な組み合わせ(1行目)をシミュレーションした場合、[操作ログ]タブに出力されるGOF概要レポートは以下の通りです。


maxbalreport

GOF概要レポートについては、次の例で詳しく検討します。


例23つの目的と5つの制約

[設定]タブは以前と同じです。入力変数と範囲も同じです。GOF変数の[入力]タブを以下に示します。

目的

  • 目標50%、重み100%の年間リターン
  • 目標10、重み10のリカバリーファクター
  • 目標100%、重み10の勝率 

制約

  • 初期入金に対する口座損失の割合が10%以下であること
  • エクイティドローダウンの割合が10%以下であること
  • 連続負けトレード回数が5回以下であること
  • 勝率35%以上
  • 破滅のリスクが0.5%以下であること


ex2Inputs

破滅のリスクを計算するための資本金は、上記のその他セクションで示したように、初期入金の10%、または口座通貨の1000単位に設定されました。

最適化表の1行目にあるように、最適な設計は(10,6,6)となりました。

ex2results

オプティマイザは、最初の例と比較してより低い利益(805.64対839.81)の解決策を発見しましたが、この新しい設計は5つの制約条件をすべて満たし、3つの目的を合わせて最大化しています。


GOF概要レポート 

上の最適化表の最初の行をシミュレーションすると、下のGOF概要レポートが得られます。

ex2report

GOF概要には3つのセクションがあります。最初のセクションには、[BackTest]タブの数量がたくさんあります。[BackTest]タブにはない追加的な数量がいくつかあります。年率利益、テスト期間(年)、リワード/リスク比率(RRR)、平均および最大出来高、勝率および損失標準偏差、シミュレーション中に達成された最証拠金レベルです。

2番目のセクションは目的関数です。ここでは、各目的について、目的値、目標値、重み、貢献率の4つの値があります。貢献率は、単一の目的全体に対するその目的関数の貢献率です。この例では、年間リターンが95.1%、リカバリーファクターが1.4%、勝率が3.5%となり、合計で単一目的の100%となりました。目標と重みはこれらの貢献度に影響します。 

3番目のセクションは制約です。各制約に対してpassまたはFailのメッセージが表示され、制約の実際の値と制限された入力との比較も表示されます。

比較のために、例1の最初の設計(9, 9, 3)を例2と同じ最適化定式化で実行しました。以下は、このシミュレーションの概要です。ある制約に違反していることに注目してください。連続損失数は6であり、最適化定式化で与えられた境界値5より大きくなります。したがって、MaxBalance設計(9,9,3)がMultiObjective/Constrained設計(10,6,6)よりも利益が良いとしても、すべての制約を満たすのは(10,6,6)設計です。 

ex2-bal-compare


GenericOptimizationFormulation.mqhを使用する際の推奨事項

複数の目的と複数の制約を選択できる自由度の高さは、慎重に行使されるべきです。以下は一般的な推奨事項です。

  1. 目標は3つ以内とします。このコードでは最大5つの目標を設定できますが、最終的な結果に影響する目標と重みの選択は、目標が3つ以上になると難しくなります。
  2. 上限(U_i)と下限(L_i)を選択するときは、好みに基づいて制約を使用し、あまりきつく設定しないでください。境界が厳しすぎると、実現可能な入力変数の組み合わせが得られません。
  3. 与えられた制約に対してどのような境界値を与えるべきかわからない場合、制約を目的セクションに移動し、GOF概要レポートを検査することによって、それがどのように振る舞うか(大きさ、符号など)を見ることができます。
  4. より良いプロットが必要な場合、あるいはオプティマイザが期待した結果を出していないことがわかった場合は、k_oとk_pを調整してください。
  5. 覚えておいてほしいのは、最適設計(最適化表の一番上の行)は、必ずしも最も収益性の高い設計ではなく、個々の目的と制約の選択に基づいて最も高い目的関数を持つ設計であるということです。
  6. 最適化が完了した後、利益、回収係数、ドローダウン、期待ペイオフ、利益係数などの他の列で設計を並び替えることをお勧めします。各並び替えの一番上の行は、GOF概要レポートを確認するためのシミュレーションの実行を検討できる候補となり得ます。
  7. 目的と制約の良い選択は、例2で示されました。ご自分の実験の最初のポイントとして使用してください。


失敗

期待した結果が得られない最適化定式化を設定してしまうことがあります。このようなことが起こる理由をいくつか挙げてみましょう。

  1. 制約が厳しすぎると、MetaTrader 5の遺伝的最適化アルゴリズムは、正の目的関数に到達するまでに何世代もかかり、場合によっては到達できないこともあります。解決策は、制約を緩和することです。
  2. 制約が互いに対立しています。解決策は、制約が論理的に一貫していることを確認することです。
  3. 最適化におけるプロットは、Y軸が負の値に偏っています(つまり、負の側が正の側よりも多くのスペースを占めている)。解決策は、K_oを増やすか、K_pを減らすか、あるいはその両方です。
  4. 気に入った設計が、最適化テーブルの上位に表示されないことがあります。目標値と重みは最適化の目的に影響を与え、また、たった1つの制約違反が、表の中で目的を下方に追いやる可能性があることに留意してください。解決策は、目標と重みと制約条件を調整することによって、最適化問題を再定式化することです。

   

結論

この一般的な最適化定式化が皆様のお役に立てれば幸いです。これで、複数の目的と複数の制約条件を選択し、好みの最適化問題を設定できるようになりました。

MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/14365

添付されたファイル |
データサイエンスと機械学習(第21回):ニューラルネットワークと最適化アルゴリズムの解明 データサイエンスと機械学習(第21回):ニューラルネットワークと最適化アルゴリズムの解明
ニューラルネットワーク内部で使用される最適化アルゴリズムを解明しながら、ニューラルネットワークの核心に飛び込みます。この記事では、ニューラルネットワークの可能性を最大限に引き出し、モデルを精度と効率の新たな高みへと押し上げる重要なテクニックご紹介します。
MQL5入門(第6部):MQL5における配列関数の入門ガイド MQL5入門(第6部):MQL5における配列関数の入門ガイド
MQL5の旅の次の段階を始めましょう。この洞察に満ちて初心者に優しい記事では、残りの配列関数について調べ、複雑な概念を解明し、効率的な取引戦略を作成できるようにします。ArrayPrint、ArrayInsert、ArraySize、ArrayRange、ArrarRemove、ArraySwap、ArrayReverse、ArraySortについて説明します。アルゴリズム取引の専門知識を、これらの必要不可欠な配列関数で高めてください。一緒にMQL5マスターへの道を歩みましょう。
MQL5で自己最適化エキスパートアドバイザーを構築する MQL5で自己最適化エキスパートアドバイザーを構築する
どのような市場にも対応できる専門的なエキスパートアドバイザー(EA)を構築します。
Pythonを使用したEA用ディープラーニングONNXモデルの季節性フィルタと期間 Pythonを使用したEA用ディープラーニングONNXモデルの季節性フィルタと期間
Pythonでディープラーニングのモデルを作成する際、季節性から恩恵を受けることはできるのでしょうか。ONNXモデルのデータをフィルタすることでより良い結果が得られるのでしょうか。どの期間を使用するべきでしょうか。この記事では、これらすべてを取り上げます。