English Русский 中文 Español Deutsch Português
preview
リスク管理(第1回):リスク管理クラス構築の基礎

リスク管理(第1回):リスク管理クラス構築の基礎

MetaTrader 5 |
62 0
Niquel Mendoza
Niquel Mendoza



はじめに 

本記事では、取引におけるリスク管理の意味と、それがなぜ自動売買において不可欠であるのかを解説します。まずは基本的な概念から始め、適切なリスク管理が金融市場における成功と失敗を分ける重要な要素であることを理解するための土台を築きます。 

その後、ロットサイズ、最大損失、期待利益などの主要な要素を制御できる、完全なリスク管理システムを実装したMQL5クラスを段階的に構築していきます。


リスク管理とは何か 

リスク管理は、あらゆる取引戦略における基本的な柱です。その主な目的は、保有中のポジションを監視および制御し、日次、週次、または全体の損失など、トレーダーが設定した制限を超えないようにすることです。

さらに、リスク管理はユーザーのルールや好みに基づいて、各取引における適切なロットサイズを決定します。これにより、資金を保護するだけでなく、定義されたリスクプロファイルに沿った取引をおこなうことで、戦略のパフォーマンスを最適化できます。

要するに、優れたリスク管理は壊滅的な損失の可能性を低減するだけでなく、賢明な金融判断を下すための規律ある枠組みを提供します。


自動取引における重要性  

リスク管理は、自動売買において極めて重要な役割を果たします。過剰な取引や不要なリスクへの過度なエクスポージャーといった高コストなミスを防ぐ制御システムとして機能するからです。意思決定が完全に自動化される売買ボットの環境では、適切なリスク管理によって、戦略が規律正しく効率的に実行されることが保証されます。

これは、日次、週次、または総損失に厳しい制限が課される資金提供チャレンジのような場面では特に重要です。リスク管理によってこれらの境界を正確に設定でき、合格と失敗を分ける決定的な要因となる場合もあります。結果として、ユーザーの資金を保護し、競争の激しい環境においてパフォーマンスを最適化できます。

さらに、明確な制限を設けることで、過剰な取引や不釣り合いなリスクを回避し、ボットをより戦略的に運用できるようになります。ロットサイズの自動計算や、取引ごとの損失制限をおこなうことで、リスク管理は資金を守るだけでなく、ボットが管理され安全なパラメータ内で動作しているという安心感をトレーダーに提供します。


リスク管理の主要概念 

コーディングを開始する前に、リスク管理に関わる主な変数と概念を理解しておくことが重要です。これらの概念は、ユーザーの資金を保護し、制御された運用を実現する効果的なシステムの基盤となります。以下では、それぞれの要素を詳しく解説します。

1. 最大日次損失

これは、ボットが1日(24時間)の間に許容される最大損失額を指します。この制限に達すると、通常はすべてのポジションが決済され、翌日まで取引活動が停止されます。この概念は、連続した損失取引によって資金が大きく損なわれるのを防ぐのに役立ちます。

2. 最大週次損失

日次損失制限と同様の考え方ですが、1週間という期間に適用されます。この閾値を超えると、ボットは翌週の開始まで取引を停止します。このパラメータは、より長い期間にわたる大きな損失を防ぐために最適です。

3. 最大総損失

これは、到達した時点で特別な回復戦略を発動させる絶対的な損失上限を表します。この回復戦略には、ロットサイズを縮小し、より慎重な取引をおこなうことで、失われた資金を段階的に回復する方法が含まれる場合があります。この概念は、口座全体のリスクを管理するのに役立ちます。

4. 取引あたりの最大損失

単一の取引で許容される最大損失額を定義します。この値は非常に重要で、ユーザーが受け入れ可能なリスク水準に基づいて、各取引の最適なロットサイズを自動的に計算することを可能にします。

5. 日次、週次、および総利益

これらは、異なる期間における累積利益を記録する変数です。これらの指標は、自動売買ロボットのパフォーマンスを評価し、得られた結果に応じて戦略を調整するために有用です。


インクルードファイルの作成と計画の説明 

このセクションでは、インクルードファイルのコーディングを開始します。

1. MetaTraderプラットフォームの上部にある[IDE]ボタンをクリックします。

 IDE-1

2. MetaEditorの左上隅で、[ファイル]タブをクリックし、[新規作成]を選択します。次のウィンドウが表示されます。

IDE-2

 3. [インクルード(.mgh)]を選択し、[次へ]をクリックします。

 IDE-3

4. 名前と作成者を指定してインクルードファイルを設定します。

IDE-4

これでファイルの作成は完了です。これはあくまで始まりにすぎません。次に、リスク管理システムがどのように動作するのか、その設計プランを確認していきましょう。

以下は、リスク管理の仕組みを示した図です。

Map-1

セクション
説明   実行頻度
1. 計算用変数の設定 この初期フェーズ(一度のみ実行)では、損失および利益を計算するために必要なすべての変数を設定します。

主な作業内容は以下のとおりです。
  • エキスパートアドバイザー(EA)から特定の取引を識別するためのマジックナンバーを定義します。
  • 特に資金提供口座やプロップファーム口座での取引において重要となる初期残高を設定します。
  • リスクの割合を宣言し、損失を金額ベースで計算するか、残高または資本に対する割合で計算するかを選択します。
割合ベースの方法を選択した場合、その割合を適用する基準値(例:総残高、エクイティ、総利益、余剰証拠金)を指定する必要があります。
EAの初期設定時または設定変更時に一度実行
2.
損失と利益の計算
このフェーズでは、口座における現在の損失と利益の状態を計算します。これには以下が含まれます。
  • 累積された総損失の計算
  • 日次、週次、または取引ごとの利益の記録
  • 前のセクションで設定した制限値と累積損失の比較
この処理は、ユーザーのニーズに応じて定期的に実行されます。
実行頻度:設定に応じて、取引開始時日次、または週次に実行
3.
リアルタイム検証
実運用中、EAはすべてのティックごとに現在の損失が定義された制限を超えていないかを継続的にチェックします。

いずれかの損失変数が閾値を超えた場合、さらなる損失を防ぐために、EAは即座にすべてのポジションを決済します。
毎ティック(リアルタイム処理)

以上を踏まえて、最初の関数の作成に進みます。


ロットサイズ計算用関数の作成 

クラス本体を開発する前に、まずは適切なロットサイズを計算するための関数を作成する必要があります。

理想的なロットサイズの計算

理想のロットサイズを決定するには、まず総ロットを計算する必要があります。これは、口座が売買できる最大取引量を表します。この計算は、1ロットを建てるために必要な証拠金(口座通貨建て)に基づいておこなわれます。この値が分かれば、口座の余剰証拠金を必要証拠金で割り、その結果を丸めることで、口座で許可される最大ロットサイズを取得できます。

前提条件

計算をおこなう前に、対象となる銘柄における1ロットあたりの必要証拠金を特定する必要があります。この例では、銘柄としてゴールド(XAU/USD)を使用しますが、同じ手順は他の金融商品にも適用できます。

主な目的は、口座残高や利用可能な証拠金に応じて動的に適応する、効率的なロット計算のための確固たる基盤を構築することです。

 MARGIN-1

図に示されているように、ゴールドを1ロット買うために必要なおおよその初期証拠金は1,326USDです。したがって、許可される最大ロットを計算するには、口座の利用可能な余剰証拠金を1ロットあたりの必要証拠金で割るだけです。この関係は次のように表せます。

MARGIN-2

余剰証拠金

  • 新しい取引をおこなうために使用できる口座内の利用可能資金を表します。MetaTraderでは次のように計算されます。

MARGIN-3

あらゆる注文タイプにおける価格の計算
最大ロットサイズの計算方法が分かったところで、次のステップはこのロジックをコードとして実装することです。しかし、その前に、注文がどの価格で約定されるのかを決定する必要があります。そのために、注文タイプに応じた価格を計算して返すPriceByOrderTypeという関数を作成します。この関数は、注文タイプに基づいて適切な価格を算出します。

double PriceByOrderType(const string symbol, const ENUM_ORDER_TYPE order_type, double DEVIATION = 100, double STOP_LIMIT = 50)

入力パラメータ

  1. symbol:注文を実行する取引銘柄(例:EURUSD)
  2. order_type:ENUM_ORDER_TYPE列挙型に基づく注文のタイプ
  3. DEVIATION:許容される価格乖離幅(ポイント)
  4. STOP_LIMIT:STOP_LIMITタイプの注文に使用される距離(ポイント)

手順1:必要な変数の作成

最初に、銘柄の小数点桁数、ポイント値、現在のBidおよびAsk価格を格納するための変数を宣言します。これらはすべてMqlTick構造体内で管理されます。

int     digits=0; 
double  point=0; 
MqlTick tick={}; 

手順2:変数への値の代入

ここでは、組み込み関数を使用して、銘柄情報(小数点桁数、ポイント値、現在価格など)を取得します。

まず、SYMBOL_POINTの値を取得します。
ResetLastError(); 
if(!SymbolInfoDouble(symbol, SYMBOL_POINT, point)) 
  { 
   Print("SymbolInfoDouble() failed. Error ", GetLastError()); 
   return 0; 
  } 

次に、SYMBOL_DIGITSの値を取得します。

long value=0; 
if(!SymbolInfoInteger(symbol, SYMBOL_DIGITS, value)) 
  { 
   Print("SymbolInfoInteger() failed. Error ", GetLastError()); 
   return 0; 
  } 
digits=(int)value; 

続いて、現在の銘柄価格(BidおよびAsk)を取得します。

if(!SymbolInfoTick(symbol, tick)) 
  { 
   Print("SymbolInfoTick() failed. Error ", GetLastError()); 
   return 0; 
  } 

手順3: 注文タイプに基づく価格の計算

注文タイプに応じて、switch構文を使用し、適切な価格を返します。

switch(order_type) 
  { 
   case ORDER_TYPE_BUY              :  return(tick.ask); 
   case ORDER_TYPE_SELL             :  return(tick.bid); 
   case ORDER_TYPE_BUY_LIMIT        :  return(NormalizeDouble(tick.ask - DEVIATION * point, digits)); 
   case ORDER_TYPE_SELL_LIMIT       :  return(NormalizeDouble(tick.bid + DEVIATION * point, digits)); 
   case ORDER_TYPE_BUY_STOP         :  return(NormalizeDouble(tick.ask + DEVIATION * point, digits)); 
   case ORDER_TYPE_SELL_STOP        :  return(NormalizeDouble(tick.bid - DEVIATION * point, digits)); 
   case ORDER_TYPE_BUY_STOP_LIMIT   :  return(NormalizeDouble(tick.ask + DEVIATION * point - STOP_LIMIT * point, digits)); 
   case ORDER_TYPE_SELL_STOP_LIMIT  :  return(NormalizeDouble(tick.bid - DEVIATION * point + STOP_LIMIT * point, digits)); 
   default                          :  return(0); 
  } 

最終的な関数の実装

double PriceByOrderType(const string symbol, const ENUM_ORDER_TYPE order_type, double DEVIATION = 100, double STOP_LIMIT = 50) 
  {
   int     digits=0; 
   double  point=0; 
   MqlTick tick={}; 

//--- we get the Point value of the symbol
   ResetLastError(); 
   if(!SymbolInfoDouble(symbol, SYMBOL_POINT, point)) 
     { 
      Print("SymbolInfoDouble() failed. Error ", GetLastError()); 
      return 0; 
     } 

//--- we get the Digits value of the symbol
   long value=0; 
   if(!SymbolInfoInteger(symbol, SYMBOL_DIGITS, value)) 
     { 
      Print("SymbolInfoInteger() failed. Error ", GetLastError()); 
      return 0; 
     } 
   digits=(int)value; 

//--- we get the latest prices of the symbol
   if(!SymbolInfoTick(symbol, tick)) 
     { 
      Print("SymbolInfoTick() failed. Error ", GetLastError()); 
      return 0; 
     } 

//--- Depending on the type of order, we return the price
   switch(order_type) 
     { 
      case ORDER_TYPE_BUY              :  return(tick.ask); 
      case ORDER_TYPE_SELL             :  return(tick.bid); 
      case ORDER_TYPE_BUY_LIMIT        :  return(NormalizeDouble(tick.ask - DEVIATION * point, digits)); 
      case ORDER_TYPE_SELL_LIMIT       :  return(NormalizeDouble(tick.bid + DEVIATION * point, digits)); 
      case ORDER_TYPE_BUY_STOP         :  return(NormalizeDouble(tick.ask + DEVIATION * point, digits)); 
      case ORDER_TYPE_SELL_STOP        :  return(NormalizeDouble(tick.bid - DEVIATION * point, digits)); 
      case ORDER_TYPE_BUY_STOP_LIMIT   :  return(NormalizeDouble(tick.ask + DEVIATION * point - STOP_LIMIT * point, digits)); 
      case ORDER_TYPE_SELL_STOP_LIMIT  :  return(NormalizeDouble(tick.bid - DEVIATION * point + STOP_LIMIT * point, digits)); 
      default                          :  return(0); 
     } 
  } 

さらに、注文タイプから対応する成行注文タイプを取得するための関数も必要になります。

ENUM_ORDER_TYPE MarketOrderByOrderType(ENUM_ORDER_TYPE type) 
  { 
   switch(type) 
     { 
      case ORDER_TYPE_BUY  : case ORDER_TYPE_BUY_LIMIT  : case ORDER_TYPE_BUY_STOP  : case ORDER_TYPE_BUY_STOP_LIMIT  : 
        return(ORDER_TYPE_BUY); 
      case ORDER_TYPE_SELL : case ORDER_TYPE_SELL_LIMIT : case ORDER_TYPE_SELL_STOP : case ORDER_TYPE_SELL_STOP_LIMIT : 
        return(ORDER_TYPE_SELL); 
     } 
   return(WRONG_VALUE); 
  }

最大ロットサイズの計算
GetMaxLote関数は、利用可能な余剰証拠金と指定された注文タイプに基づいて、開くことができる最大ロットサイズを計算します。これは、取引がブローカーの設定した証拠金要件に従うことを保証する、重要なリスク管理ツールです。

1. 関数パラメータの作成

関数は以下のパラメータを受け取ります。

double GetMaxLote(ENUM_ORDER_TYPE type, double DEVIATION = 100, double STOP_LIMIT = 50)
  • type:ORDER_TYPE_BUYやORDER_TYPE_SELLなど、注文タイプを定義します。このパラメータは、価格および証拠金を正しく計算するために重要です。
  • DEVIATION:指値・逆指値注文で許容される価格乖離幅をポイント単位で指定します。デフォルト値は100です。
  • STOP_LIMIT:STOP_LIMITタイプの注文に使用される距離をポイント単位で指定します。デフォルト値は50です。

2. 必要な変数の初期化

計算に使用するため、double型の変数を4つとORDER_TYPE列挙型の変数を1つ宣言します。

   //--- Set variables
   double VOLUME = 1.0; //Initial volume size
   ENUM_ORDER_TYPE new_type = MarketOrderByOrderType(type); 
   double price = PriceByOrderType(_Symbol, type, DEVIATION, STOP_LIMIT); // Price for the given order type
   double volume_step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP); // Volume step for the symbol
   double margin = EMPTY_VALUE; // Required margin, initialized as empty

3.  ロットあたりの必要証拠金の計算

OrderCalcMargin関数を使用して、現在の市場条件下で1ロットを建てるために必要な証拠金を求めます。計算に失敗した場合はエラーメッセージを表示し、関数は0を返します。

ResetLastError(); 
if (!OrderCalcMargin(new_type, _Symbol, VOLUME, price, margin)) 
  { 
   Print("OrderCalcMargin() failed. Error ", GetLastError()); 
   return 0; // Exit the function if margin calculation fails
  } 

4. 最大ロットサイズの計算

前述の計算式を使用して、最大ロットサイズを算出します。具体的には、余剰証拠金を必要証拠金で割り、許容されるボリュームステップに応じて正規化し、誤差を避けるために切り捨てます。

double result = MathFloor((AccountInfoDouble(ACCOUNT_MARGIN_FREE) / margin) / volume_step) * volume_step; 

5.  結果を返す

最後に、計算された最大ロットサイズを返します。

return result; // Return the maximum lot size

完全な関数

double GetMaxLote(ENUM_ORDER_TYPE type, double DEVIATION = 100, double STOP_LIMIT = 50) 
  { 
   //--- Set variables
   double VOLUME = 1.0; // Initial volume size
   ENUM_ORDER_TYPE new_type = MarketOrderByOrderType(type); 
   double price = PriceByOrderType(_Symbol, type, DEVIATION, STOP_LIMIT); // Price for the given order type
   double volume_step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);    // Volume step for the symbol
   double margin = EMPTY_VALUE; // Required margin, initialized as empty
  
   //--- Get margin for one lot
   ResetLastError(); 
   if (!OrderCalcMargin(new_type, _Symbol, VOLUME, price, margin)) 
     { 
      Print("OrderCalcMargin() failed. Error ", GetLastError()); 
      return 0; // Exit the function if margin calculation fails
     }
   //--- Calculate the maximum lot size
   double result = MathFloor((AccountInfoDouble(ACCOUNT_MARGIN_FREE) / margin) / volume_step) * volume_step; 
   return result; // Return the maximum lot size
  }


利益計算用関数の作成 

最大ロットサイズを求める関数の作成が完了したら、次は特定の日付から現在時刻までの利益を計算する関数を作成します。これは非常に重要です。なぜなら、各ティックを評価するときに、最大損失の変数が超過しているかどうかを判定する必要があるからです。そのため、利益データを格納する変数を使用します。たとえば、最大日次損失を超えているかどうかを確認する場合、現在のエクイティに加えて、累積日次利益を知る必要があります。

現在の利益の計算は、注文および取引履歴にアクセスするために設計された関数を使用しておこなわれます。これにより、定義された期間における利益と損失の正確かつ最新の情報を取得できます。

関数の詳細な説明

1. 変数の初期化とエラーリセット

double total_net_profit = 0.0; // Initialize the total net profit
ResetLastError(); // Reset any previous errors
  • total_net_profit:累積純利益を格納する変数を0.0で初期化します。
  • ResetLastError:実行を開始する前に、コード内の以前のエラーをリセットします。

開始日(start_date)の確認

if((start_date > 0 || start_date != D'1971.01.01 00:00'))

指定されたstart_dateが有効かどうかをチェックします(無効なデフォルト日付1971.01.01やゼロ日付でないかを確認)。有効な場合のみ、取引履歴の選択処理に進みます。

3. 取引履歴の選択

if(!HistorySelect(start_date, TimeCurrent())) 
{
   Print("Error when selecting orders: ", _LastError); 
   return 0.0; // Exit if unable to select the history
}
  • HistorySelect:HistorySelect(start_date, TimeCurrent())を使用して、開始日から現在時刻までの取引履歴を選択します。
  • 選択に失敗した場合はエラーメッセージを表示し、関数は0.0を返します。

4. 取引件数の取得

int total_deals = HistoryDealsTotal(); // Get the total number of deals in history

  • HistoryDealsTotal:取引履歴の総件数を返します。これにより、各取引を順番に処理することが可能です。

5. 全取引の反復処理

for(int i = 0; i < total_deals; i++)
{
   ulong deal_ticket = HistoryDealGetTicket(i); // Retrieve the deal ticket
  • forループを使用して、履歴内のすべての取引を順番に処理します。
  • HistoryDealGetTicket:取引の詳細にアクセスするために必要な、位置iにある一意の取引チケットを取得します。

6. 「バランス」操作の除外

if(HistoryDealGetInteger(deal_ticket, DEAL_TYPE) == DEAL_TYPE_BALANCE) continue;

入金、出金、調整などのバランス操作はスキップし、実際の取引のみを処理します。

7.取引の詳細を取得

ENUM_DEAL_ENTRY deal_entry = (ENUM_DEAL_ENTRY)HistoryDealGetInteger(deal_ticket, DEAL_ENTRY); // Get deal entry type
long deal_close_time_long = HistoryDealGetInteger(deal_ticket, DEAL_TIME);                    // Get deal close time (as long)
datetime deal_close_time = (datetime)deal_close_time_long;                                    // Explicit conversion to datetime
ulong position_id = HistoryDealGetInteger(deal_ticket, DEAL_POSITION_ID);                     // Get the position ID
  • deal_entry:取引がエントリーであったかエグジットであったかを定義します(操作が開始取引であったか終了取引であったかを判断するために使用されます)。
  • deal_close_time:便宜上、日付時刻に変換された取引の終了時刻を表します。
  • position_id:取引に関連付けられたポジションのID。マジックナンバーの検証に役立ちます。

8. 日付および取引タイプによるフィルタ

if(deal_close_time >= start_date && (deal_entry == DEAL_ENTRY_OUT || deal_entry == DEAL_ENTRY_IN))

取引の決済時刻がstart_date以上であり、かつ有効な新規建てまたは決済取引であることを確認します。

9. マジックナンバーおよび含めるタイプによるフィルタ

if((HistoryDealGetInteger(deal_ticket, DEAL_MAGIC) == specific_magic || specific_magic == GetMagic(position_id)) 
   || include_all_magic == true)

  • HistoryDealGetInteger:取引のマジックナンバーを入手します。
  • 取引マジックナンバーが指定されたspecific_magicと一致する場合、またはすべての取引を含めることが許可されている場合(include_all_magic == true)、取引の純利益が計算されます。

10. 取引純利益の計算

double deal_profit = HistoryDealGetDouble(deal_ticket, DEAL_PROFIT);         // Retrieve profit from the deal
double deal_commission = HistoryDealGetDouble(deal_ticket, DEAL_COMMISSION); // Retrieve commission
double deal_swap = HistoryDealGetDouble(deal_ticket, DEAL_SWAP);             // Retrieve swap fees
                  
double deal_net_profit = deal_profit + deal_commission + deal_swap;          // Calculate net profit for the deal
total_net_profit += deal_net_profit;                                         // Add to the total net profit
  • deal_profit:取引の利益
  • deal_commission:取引手数料
  • deal_swap:スワップ(利息・オーバーナイト手数料)

取引の純利益はこれらの合計として算出され、total_net_profitに加算されます。

11. 総純利益を返す

return NormalizeDouble(total_net_profit, 2); // Return the total net profit rounded to 2 decimals

最後に、NormalizeDoubleで小数点2桁に丸めたtotal_net_profitを返します。これにより、値が正しくフォーマットされ、今後の処理でも問題なく使用できるようになります。

完全な関数

double GetNetProfitSince(bool include_all_magic, ulong specific_magic, datetime start_date)
{
   double total_net_profit = 0.0; // Initialize the total net profit
   ResetLastError();              // Reset any previous errors

   // Check if the start date is valid
   if((start_date > 0 || start_date != D'1971.01.01 00:00'))
   {   
      // Select the order history from the given start date to the current time
      if(!HistorySelect(start_date, TimeCurrent())) 
      {
         Print("Error when selecting orders: ", _LastError); 
         return 0.0; // Exit if unable to select the history
      }

      int total_deals = HistoryDealsTotal(); // Get the total number of deals in history
  
      // Iterate through all deals
      for(int i = 0; i < total_deals; i++)
      {
         ulong deal_ticket = HistoryDealGetTicket(i); // Retrieve the deal ticket

         // Skip balance-type deals
         if(HistoryDealGetInteger(deal_ticket, DEAL_TYPE) == DEAL_TYPE_BALANCE) continue;            

         ENUM_DEAL_ENTRY deal_entry = (ENUM_DEAL_ENTRY)HistoryDealGetInteger(deal_ticket, DEAL_ENTRY); // Get deal entry type
         long deal_close_time_long = HistoryDealGetInteger(deal_ticket, DEAL_TIME);                    // Get deal close time (as long)
         datetime deal_close_time = (datetime)deal_close_time_long;                                    // Explicit conversion to datetime
         ulong position_id = HistoryDealGetInteger(deal_ticket, DEAL_POSITION_ID);                     // Get the position ID

         // Check if the deal is within the specified date range and is a valid entry/exit deal
         if(deal_close_time >= start_date && (deal_entry == DEAL_ENTRY_OUT || deal_entry == DEAL_ENTRY_IN))
         {             
            // Check if the deal matches the specified magic number or if all deals are to be included
            if((HistoryDealGetInteger(deal_ticket, DEAL_MAGIC) == specific_magic || specific_magic == GetMagic(position_id)) 
               || include_all_magic == true)
            {
               double deal_profit = HistoryDealGetDouble(deal_ticket, DEAL_PROFIT);         // Retrieve profit from the deal
               double deal_commission = HistoryDealGetDouble(deal_ticket, DEAL_COMMISSION); // Retrieve commission
               double deal_swap = HistoryDealGetDouble(deal_ticket, DEAL_SWAP);             // Retrieve swap fees
               
               double deal_net_profit = deal_profit + deal_commission + deal_swap; // Calculate net profit for the deal
               total_net_profit += deal_net_profit; // Add to the total net profit
            }
         }
      }
   }
     
   return NormalizeDouble(total_net_profit, 2); // Return the total net profit rounded to 2 decimals
}

注文のマジックナンバーを取得するための追加関数

ulong GetMagic(const ulong ticket)
{
HistoryOrderSelect(ticket);
return HistoryOrderGetInteger(ticket,ORDER_MAGIC); 
} 


簡単なスクリプトとインクルードファイルを使った実践的なテスト 

現在の銘柄に対して絶対距離をポイント単位に変換する関数を作成します。この変換は取引において基本的なもので、ポイントは価格レベル、ストップロス、目標値を計算するための標準単位です。

数式
ポイントで距離を計算する公式はシンプルです。

EXTRA-1

ここで

  • distは変換したい絶対距離
  • pointSizeは金融商品の1ポイントのサイズ(例:EUR/USDでは0.0001)

コードでの公式表現
MQL5でこの公式を実装するには、次の手順に従います。

  1. ポイントサイズpointSizeを取得します。

    SymbolInfoDouble関数を使用して現在の銘柄のポイントサイズを取得します。パラメータ「_Symbol」は現在稼働中の銘柄を表し、SYMBOL_POINT はそのポイントサイズを取得します。

  2. double pointSize = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
    
  3. 距離をポイントのサイズで割り、整数に変換します。

    距離distをポイントサイズpointSizeで割ってポイント数を計算します。ポイント数は整数である必要があるので、intへの型キャストを使って整数に変換します。

  4. return (int)(dist / pointSize);
    

完全な関数
以下は最終形の関数です。

int DistanceToPoint(double dist)
{
  double pointSize = SymbolInfoDouble(_Symbol, SYMBOL_POINT); // Get the point size for the current symbol
  return (int)(dist / pointSize); // Calculate and return the distance in points 
}

本記事で学んだ概念を実践するために、次に2つのスクリプトを作成します。

次に、重要な2つの関数を開発します。1つ目は、取引あたりのリスクに基づいて理想的なロットサイズを計算する関数、2つ目は、ロットサイズと1取引あたりのリスクに基づいてシンボルの理想的なストップロスをポイント単位で計算する関数です。  

関数:取引ごとのリスクに基づく理想的なロットの計算
GetIdealLot関数は、取引ごとに許容される最大損失とストップロス距離StopLossを考慮して理想的なロットサイズnlotを計算します。これにより、すべての取引がユーザー定義のリスク制限に準拠します。

void GetIdealLot(
    double& nlot,                     // Calculated ideal lot
    double glot,                      // Gross Lot (max lot accorsing to the balance)
    double max_risk_per_operation,    // Maximum allowed risk per trade (in account currency)
    double& new_risk_per_operation,   // Calculated risk for the adjusted lot (in account currency)
    long StopLoss                     // Stop Loss distance (in points)
)

パラメータの説明

  1. nlot:関数によって調整された理想的なロットサイズです。
  2. glot:利用可能な全資金を使った場合の総(最大)ロットサイズです。
  3. max_risk_per_operation:取引あたりの最大許容リスク(口座通貨)です。
  4. new_risk_per_operation:計算されたロットnlotに基づく調整後の実際のリスクです。価格がストップロスに達した場合に失われる金額を表します。
  5. StopLoss:ストップロスの距離(ポイント)です。

1. 初期確認

まず、StopLossの値が0より大きいことを確認します。無効なストップロスではリスク計算ができません。

if(StopLoss <= 0)
{
    Print("[ERROR SL] Stop Loss distance is less than or equal to zero, now correct the stoploss distance: ", StopLoss);
    nlot = 0.0; 
    return;   
}

2. 変数初期化

以下の変数を後で使用するために初期化します。

  • spread:現在の銘柄のスプレッド
  • tick_value:ティックごとの価値。最小価格変動が口座通貨でどの程度の価値かを示します
  • step:ロットサイズの最小増分
new_risk_per_operation = 0;  // Initialize the new risk
long spread = (long)SymbolInfoInteger(_Symbol, SYMBOL_SPREAD);
double tick_value = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
double step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);

3. 現在のリスク計算(rpo)

現在の取引ごとのリスクrpoは次の公式で計算されます。

RISK-1

コードでは

double rpo = (glot * (spread + 1 + (StopLoss * tick_value)));

4. 最大リスクの確認

現在のリスクrpoが取引ごとの最大許容リスクmax_risk_per_operationを超えているか評価します。

ケース1:リスクが最大値を超える場合

  • ロットサイズを最大許容リスクに比例して調整します。
  • 調整後のロットを許容される最小増分stepに切り捨てます。
  • 調整後ロットに対応する新しいリスクを計算します。
if(rpo > max_risk_per_operation)
{
    double new_lot = (max_risk_per_operation / rpo) * glot;
    new_lot = MathFloor(new_lot / step) * step;
    new_risk_per_operation = new_lot * (spread + 1 + (StopLoss * tick_value));
    nlot = new_lot; 
}

ケース2:許容範囲内のリスク

  • 現在の値を維持します。
else
{
    new_risk_per_operation = rpo; // Current risk
    nlot = glot;                  // Gross lot
}

最後に、取引ごとの最大許容損失とユーザー指定のロットサイズに基づいてストップロスを計算する関数を作成します。

long GetSL(const ENUM_ORDER_TYPE type , double risk_per_operation , double lot) 
{
 long spread = (long)SymbolInfoInteger(_Symbol, SYMBOL_SPREAD);
 double tick_value = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
 double result = ((risk_per_operation/lot)-spread-1)/tick_value;
 
return long(MathRound(result));
}  

パラメータの説明

  1. type:注文タイプ(買いまたは売り)。この関数では直接使用されません。
  2. risk_per_operation:取引ごとの最大許容損失(口座通貨)です。
  3. lot:ユーザー指定のロットサイズです。

ステップごとのロジック

1. 基本公式

取引ごとのリスクrpoを計算する基本公式は以下の通りです。

RISK-1

この関数では、ストップロスを分離し、RPO、ロットサイズ、その他の関連要因に基づいてその値を計算します。

2. ストップロスの分離

  • 式の両辺をロットサイズで割ります。

RISK-2

  • 両辺からスプレッドと1を減算します。

RISK-3

  • tick_valueで割ってStopLossを特定します。

RISK-4

コードでの実装
上記の公式は、そのまま関数本体内で計算に翻訳されます。

double result = ((risk_per_operation / lot) - spread - 1) / tick_value;
  • risk_per_operation / lot:ロット単位あたりのリスクを計算します
  • - spread - 1:スプレッドと追加マージンを差し引きます
  • / tick_value:ティック値で割ることで結果をポイントに変換します

結果は丸められ、longにキャストされ、必要な形式に合わせます。

return long(MathRound(result));

最後に、取引ごとに定義されたリスクに応じて理想的なロットサイズと理想的なストップロスを計算する2つのスクリプトを作成します。両方のスクリプトは、口座残高とユーザー定義パラメータに基づき、シンプルながら効率的なロジックで自動計算をおこないます。

最初のスクリプト:理想的なロットの計算
このスクリプトは、取引ごとのリスク率、ポイント単位で定義されたストップロス、注文タイプに基づいて理想的なロットを計算します。

  1. スクリプトのプロパティ

    • #property strict:コードが厳密なコンパイルルールに従うことを保証します。
    • #property script_show_inputs:ユーザーがグラフィカルインターフェースからパラメータを入力できるようにします。
  2. 入力パラメータ

input double percentage_risk_per_operation = 1.0; //Risk per operation in %
input long   sl = 600; //Stops Loss in points
input ENUM_ORDER_TYPE Order_Type = ORDER_TYPE_BUY; //Order Type

取引ごとのリスク計算

指定したパーセンテージに基づき、取引ごとに許容される口座通貨単位でのリスクを計算します。

double risk_per_operation = ((percentage_risk_per_operation/100.0) * AccountInfoDouble(ACCOUNT_BALANCE));

理想ロット計算関数の呼び出し

GetIdealLot(new_lot, GetMaxLote(Order_Type), risk_per_operation, new_risk_per_operation, sl);

ユーザーへのメッセージ:計算結果(理想ロット、調整後リスクなど)はコンソールとチャートに表示され、確認が容易です。

//+------------------------------------------------------------------+
//|                             Get Lot By Risk Per Trade and SL.mq5 |
//|                                                        Your name |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+ 
#property copyright "Your name"
#property link      "https://www.mql5.com"
#property version   "1.00"
#property strict
#property script_show_inputs

input double percentage_risk_per_operation = 1.0; // Risk per operation in %
input long   sl = 600; // Stop Loss in points
input ENUM_ORDER_TYPE Order_Type = ORDER_TYPE_BUY; // Order Type

#include <Risk Management.mqh>

//+------------------------------------------------------------------+
//| Main script function                                             |
//+------------------------------------------------------------------+
void OnStart()
  {
   // Calculate the maximum allowable risk per operation in account currency
   double risk_per_operation = ((percentage_risk_per_operation / 100.0) * AccountInfoDouble(ACCOUNT_BALANCE));
   
   // Print input and calculated risk details
   Print("Risk Per operation: ", risk_per_operation);
   Print("SL in points: ", sl);
   Print("Order type: ", EnumToString(Order_Type));
   
   double new_lot;
   double new_risk_per_operation;
   
   // Calculate the ideal lot size
   GetIdealLot(new_lot, GetMaxLote(Order_Type), risk_per_operation, new_risk_per_operation, sl);
   
   // Check if the lot size is valid
   if (new_lot <= 0)
     {
      Print("The stop loss is too large or the risk per operation is low. Increase the risk or decrease the stop loss.");
     }
   else
     {
      // Display calculated values
      Print("Ideal Lot: ", new_lot);
      Print("Maximum loss with SL: ", sl, " | Lot: ", new_lot, " is: ", new_risk_per_operation);
      Comment("Ideal Lot: ", new_lot);
     }
   
   Sleep(1000);
   Comment(" ");
  }
//+------------------------------------------------------------------+

2番目のスクリプト:理想的なストップロスの計算
このスクリプトは、ユーザー指定のロットサイズと取引ごとの最大リスクに基づいて、ポイント単位でストップロスを計算します。

input double percentage_risk_per_operation = 1.0; //Risk per operation in %
input double Lot = 0.01; //lot
input ENUM_ORDER_TYPE Order_Type = ORDER_TYPE_BUY; //Order Type

理想的なストップロスの計算:GetSL関数を使用してストップロスを算出します。

long new_sl = GetSL(Order_Type, risk_per_operation, Lot);

結果の確認:計算結果が無効(new_slが0以下)の場合はユーザーに通知されます。

//+------------------------------------------------------------------+
//|                         Get Sl by risk per operation and lot.mq5 |
//|                                                        Your name |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Your name"
#property link      "https://www.mql5.com"
#property version   "1.00"
#property strict
#property script_show_inputs

input double percentage_risk_per_operation = 1.0; // Risk per operation in %
input double Lot = 0.01; // Lot size
input ENUM_ORDER_TYPE Order_Type = ORDER_TYPE_BUY; // Order Type

#include <Risk Management.mqh>

//+------------------------------------------------------------------+
//| Main script function                                             |
//+------------------------------------------------------------------+
void OnStart()
  {
   // Calculate the maximum allowable risk per operation in account currency
   double risk_per_operation = ((percentage_risk_per_operation / 100.0) * AccountInfoDouble(ACCOUNT_BALANCE));
   
   // Print input and calculated risk details
   Print("Risk Per operation: ", risk_per_operation);
   Print("Lot size: ", Lot);
   Print("Order type: ", EnumToString(Order_Type));
   
   // Calculate the ideal stop loss
   long new_sl = GetSL(Order_Type, risk_per_operation, Lot);
   
   // Check if the SL is valid
   if (new_sl <= 0)
     {
      Print("The lot size is too high or the risk per operation is too low. Increase the risk or decrease the lot size.");
     }
   else
     {
      // Display calculated values
      Print("For lot: ", Lot, ", and risk: ", risk_per_operation, ", the ideal SL is: ", new_sl);
      Comment("Ideal SL: ", new_sl);
     }
   
   Sleep(1000);
   Comment(" ");
  }
//+------------------------------------------------------------------+

このスクリプトを使用して、指定した取引ごとのリスクに基づき理想ロットサイズを取得します。ここでは、XAU/USD(ゴールド)を対象にテストします。

 SCRIPT-RISK-1

例えば、ストップロスを200ポイント、取引ごとのリスクを口座残高の1.0%、注文タイプをORDER_TYPE_BUYに設定すると、結果は次の通りです。

 SCRIPT-RISK-2

[エキスパート]タブに表示される結果は、ロットサイズが0.01、ストップロスが200 pips、取引あたりのリスクが3.81(口座残高の1%)に相当します。



結論 

連載第一回が完了しました。今回は、リスク管理クラスで使用される基本的な関数の開発に焦点を当てました。これらの関数は、利益データを取得したり、追加の計算をおこなったりするために不可欠です。次回は、これまで学んだ内容をグラフィカルインターフェースに統合する方法を、MQL5のコントロールライブラリを使用して探求します。

MetaQuotes Ltdによりスペイン語から翻訳されました。
元の記事: https://www.mql5.com/es/articles/16820

初心者からエキスパートへ:パラメータ制御ユーティリティ 初心者からエキスパートへ:パラメータ制御ユーティリティ
従来のEAやインジケーターの入力プロパティを、リアルタイムで操作可能なオンチャートのコントロールインターフェースへと変換することを想像してみてください。本記事は、これまでに取り組んできたMarket Periods Synchronizerインジケーターでの基礎的な成果を土台とし、上位足(HTF)の市場構造を可視化し、管理する手法を大きく進化させるものです。ここでは、その概念を完全にインタラクティブなユーティリティへと昇華させ、動的な操作性と強化されたマルチタイムフレーム(MTF)のプライスアクションの可視化を、チャート上に直接統合したダッシュボードとして実装します。この革新的なアプローチが、トレーダーとツールの関わり方をどのように変えていくのか、一緒に見ていきましょう。
取引におけるニューラルネットワーク:金融市場向けマルチモーダルツール拡張エージェント(FinAgent) 取引におけるニューラルネットワーク:金融市場向けマルチモーダルツール拡張エージェント(FinAgent)
FinAgentを紹介します。FinAgentは、マーケットの動向や過去の取引パターンを反映するさまざまなタイプのデータを分析できるマルチモーダル金融取引エージェントのフレームワークです。
取引におけるニューラルネットワーク:概念強化を備えたマルチエージェントシステム(FinCon) 取引におけるニューラルネットワーク:概念強化を備えたマルチエージェントシステム(FinCon)
FinConフレームワークは、大規模言語モデル(LLM)をベースにしたマルチエージェントシステムです。概念的言語強化を活用して意思決定とリスク管理を改善し、さまざまな金融タスクで効果的に機能するよう設計されています。
取引におけるニューラルネットワーク:金融市場向けマルチモーダルツール拡張エージェント(最終部) 取引におけるニューラルネットワーク:金融市場向けマルチモーダルツール拡張エージェント(最終部)
マルチモーダル市場の動向データと過去の取引パターンを分析するために設計されたマルチモーダル金融取引エージェント「FinAgent」のアルゴリズム開発を続けます。