手動取引のリスクマネージャー
内容
- はじめに
- 機能の定義
- 入力パラメータとクラスコンストラクタ
- リスク制限期間との連携
- 制限の使用を制御する
- クラスイベントハンドラ
- 1日の目標利益を制御する仕組み
- EA構造における監視開始メソッドの定義
- 最終的な実装とクラス拡張の可能性
- 使用例
- 結論
はじめに
皆さん、こんにちは。今回は、リスク管理の方法論について話を続けます。前回の「複数の商品を同時に取引する際のリスクバランス」稿では、リスクの基本的な概念についてお話ししました。今回は、安全な取引のための基本的なリスクマネージャークラスをゼロから実装していきます。また、取引システムのリスクを制限することが、取引戦略の有効性にどのような影響を与えるかも見ていきます。
リスクマネージャーは私の最初のクラスで、プログラミングの基礎を学んだ直後の2019年に書いたものです。その時、私は自分の経験から、トレーダーの心理状態が取引の有効性、特に取引の意思決定の「一貫性」と「公平性」に大きく影響することを理解しました。ギャンブル、感情的な取引、できるだけ早く損失を補おうとすることによって膨らんだリスクは、テストで非常に良い結果を示した効果的な取引戦略を使用していたとしても、口座を枯渇させる可能性があります。
この記事の目的は、リスクマネジャーを使用したリスク制御がその有効性と信頼性を高めることを示すことです。このテーゼを確認するために、手動取引用のシンプルな基本リスクマネージャークラスをゼロから作成し、非常にシンプルなフラクタルブレイクアウト戦略を使用してテストします。
機能の定義
手動取引に特化したアルゴリズムを実装する際、1日、1週間、1ヶ月の時間的リスク制限の管理のみを実装します。実際の損失額がユーザーによって設定された限度額に達するかそれを超えると、EAは自動的にすべてのポジションをクローズし、それ以上の取引が不可能であることをユーザーに通知しなければなりません。ここで注意しなければならないのは、この情報は純粋に「助言的なもの」であり、チャートの左下隅にあるコメント欄にEAとともに表示されるということです。これは、手動取引に特化したリスクマネージャーを作成しているためで、「どうしても必要な場合」には、ユーザーはいつでもこのEAをチャートから削除して取引を継続することができます。しかし、これを実行することは本当にお勧めしません。なぜなら、市場が自分に不利な方向に動いた場合、手動取引で何が間違っていたのかを正確に把握しようとするよりも、翌日に取引に戻って大きな損失を回避する方がよいからです。このクラスをアルゴリズム取引に組み込む場合は、限度に達したときの注文送信の制限を実装し、できればこのクラスをEA構造に直接組み込む必要があります。これについてはもう少し詳しくお話しします。
入力パラメータとクラスコンストラクタ
期間によるリスク制御と日次利益率の達成基準のみを実施することに決めました。そのために、ユーザーが各期間の預金のパーセンテージとしてリスク値を手動で入力できるように、メモリクラス修飾子input持つdouble型の変数をいくつか導入し、また利益を確定するための目標日次利益パーセンテージも導入します。目標日次利益の制御を示すために、トレーダーが各エントリを個別に検討し、選択した商品間に相関関係がないことを確信している場合にこの機能を有効/無効にする機能として、bool型の追加変数を導入します。このタイプのスイッチ変数は「フラグ」とも呼ばれます。次のコードをグローバルレベルで宣言してみましょう。便宜上、以前はgroupキーワードを使用して名前付きブロックに「ラップ」していました。
input group "RiskManagerBaseClass" input double inp_riskperday = 1; // risk per day as a percentage of deposit input double inp_riskperweek = 3; // risk per week input double inp_riskpermonth = 9; // risk per month input double inp_plandayprofit = 3; // target daily profit input bool dayProfitControl = true; // whether to close positions after reaching daily profit
宣言された変数は、以下のロジックに従ってデフォルト値で初期化されます。このクラスは日中取引に最適ですが、中期取引や投資にも使用できるので、日次リスクから始めることにします。もちろん、中期的な取引や投資家としての取引では、日中リスクを制御する意味はないので、日次リスクと週次リスクに同じ値を設定できます。さらに、長期投資のみをおこなう場合は、すべての限度を毎月のドローダウンに等しく設定することができます。ここでは、日中取引のデフォルトパラメータのロジックについて見ていきます。
1日のリスクを預金の1%とすることに決めました。1日の限度額を超えた場合は、明日まで端末を閉鎖します。次に、1週間の制限を以下のように定義します。通常、1週間の取引日は5日で、3日連続で損失が出た場合は、次の週の初めまで取引を停止します。単純に、今週は相場を理解していなかったか、何かが変わった可能性が高く、このまま取引を続ければ、この期間に大きな損失を積み上げてしまい、次の週を犠牲にしてもカバーできなくなるからです。同様のロジックが、日中取引で月間の指値を設定する場合にも適用されます。1ヶ月のうち3週間が不採算週であった場合、4週目の取引はおこなわない方がよいという条件を受け入れます。将来の期間を犠牲にして利回り曲線を「改善」するには、多くの時間がかかるからです。また、別の月に大きな損失を出して投資家を「脅かす」こともしたくありません。
お使いの取引システムの特性を考慮し、1日のリスクに基づいて1日の目標利益の大きさを設定します。ここで考慮すべきことは次の通りです。まず、相関性のある商品を取引しているかどうか、取引システムがエントリシグナルを出す頻度、個々の取引についてストップロスとテイクプロフィットの割合を固定して取引しているかどうか、預金の大きさなどです。ストップロスなし、リスクマネージャーなしの取引はまったく推奨しないことを言っておきます。この場合、預金を失うのは時間の問題です。そのため、各取引に個別にストップを設定するか、リスクマネージャーを使用して期間ごとにリスクを制限します。現在のデフォルトパラメータの例では、日次リスクに対する日次利益の条件を1から3に設定しています。また、これらのパラメータは、ストップロスとテイクプロフィットの比率(1~3)を通じて、各取引のリスクプロフィット可能性を設定することと並行して使用するのがよいでしょう(テイクプロフィットはストップロスより大きい)。
制限の構造は次のように描くことができます。
図1:制限の構造
次に、classキーワードを使用して、カスタムデータ型 RiskManagerBaseを宣言します。入力パラメータは、カスタムRiskManagerBaseクラス内に格納する必要があります。入力パラメータはパーセンテージで測定され、制限は預金通貨で追跡されるため、カスタムクラスにprotectedアクセス修飾子付きのdouble型の対応するフィールドをいくつか入力する必要があります。
protected: double riskperday, // risk per day as a percentage of deposit riskperweek, // risk per week as a percentage of deposit riskpermonth, // risk per month as a percentage of deposit plandayprofit // target daily profit as a percentage of deposit ; double RiskPerDay, // risk per day in currency RiskPerWeek, // risk per week in currency RiskPerMonth, // risk per month in currency StartBalance, // account balance at the EA start time, in currency StartEquity, // account equity at the limit update time, in currency PlanDayEquity, // target account equity value per day, in currency PlanDayProfit // target daily profit, in currency ; double CurrentEquity, // current equity value CurrentBallance; // current balance
入力されたパラメータに基づいて、預金通貨の期間ごとのリスク限度を計算する便宜のために、アクセス修飾子をprotected にして、クラス内でRefreshLimits()メソッドを宣言します。このメソッドをクラス外で次のように説明しましょう。将来的に、得られたデータの正しさを確認する機能を備えたメソッドを拡張する必要がある場合に備えて、bool型の戻り値の型を提供する予定です。とりあえず、このメソッドを次のような形で説明します。
//+------------------------------------------------------------------+ //| RefreshLimits | //+------------------------------------------------------------------+ bool RiskManagerBase::RefreshLimits(void) { CurrentEquity = NormalizeDouble(AccountInfoDouble(ACCOUNT_EQUITY),2); // request current equity value CurrentBallance = NormalizeDouble(AccountInfoDouble(ACCOUNT_BALANCE),2); // request current balance StartBalance = NormalizeDouble(AccountInfoDouble(ACCOUNT_BALANCE),2); // set start balance StartEquity = NormalizeDouble(AccountInfoDouble(ACCOUNT_EQUITY),2); // request current equity value PlanDayProfit = NormalizeDouble(StartEquity * plandayprofit/100,2); // target daily profit, in currency PlanDayEquity = NormalizeDouble(StartEquity + PlanDayProfit/100,2); // target equity, in currency RiskPerDay = NormalizeDouble(StartEquity * riskperday/100,2); // risk per day in currency RiskPerWeek = NormalizeDouble(StartEquity * riskperweek/100,2); // risk per week in currency RiskPerMonth = NormalizeDouble(StartEquity * riskpermonth/100,2); // risk per month in currency return(true); }
便利な方法は、期間を変更する際に限度値を再計算する必要があるときや、クラスのコンストラクタを呼び出す際にフィールドの値を最初に変更するときに、コード内でこのメソッドを毎回呼び出すことです。フィールドの開始値を初期化するために、クラスのコンストラクタに以下のコードを書きます。
//+------------------------------------------------------------------+ //| RiskManagerBase | //+------------------------------------------------------------------+ RiskManagerBase::RiskManagerBase() { riskperday = inp_riskperday; // set the value for the internal variable riskperweek = inp_riskperweek; // set the value for the internal variable riskpermonth = inp_riskpermonth; // set the value for the internal variable plandayprofit = inp_plandayprofit; // set the value for the internal variable RefreshLimits(); // update limits }
入力パラメータのロジックとクラスの開始データ状態を決めたら、次は限度計算の実装に移ります。
リスク制限期間との連携
リスク制限期間と連動させるには、protectedアクセスタイプを持つ追加の変数が必要です。まず、設定されたリスク制限に到達した際のデータを格納するbool型変数の形式で各期間の独自のフラグと、すべての制限が同時に利用可能な場合にのみ取引を続行できる可能性を通知するメイン フラグを宣言します。これは、1ヶ月の限度をすでに超えているにもかかわらず、1日の限度が残っているために取引が許可されているといった状況を避けるために必要なことです。これにより、次の時間帯までに限度に達した場合ごとに、取引が制限されます。毎日の利益と新しい取引日の開始を制御するために、同じ型の変数も必要です。さらに、各期間(日、週、月)の実際の損益情報を格納するために、double型のフィールドを追加します。さらに、取引業務におけるスワップと手数料については、個別の価値を提供します。
bool RiskTradePermission; // general variable - whether opening of new trades is allowed bool RiskDayPermission; // flag prohibiting trading if daily limit is reached bool RiskWeekPermission; // flag to prohibit trading if daily limit is reached bool RiskMonthPermission; // flag to prohibit trading if monthly limit is reached bool DayProfitArrive; // variable to control if daily target profit is achieved bool NewTradeDay; // variable for a new trading day //--- actual limits double DayorderLoss; // accumulated daily loss double DayorderProfit; // accumulated daily profit double WeekorderLoss; // accumulated weekly loss double WeekorderProfit; // accumulated weekly profit double MonthorderLoss; // accumulated monthly loss double MonthorderProfit; // accumulated monthly profit double MonthOrderSwap; // monthly swap double MonthOrderCommis; // monthly commission
将来的に意思決定ツールから発生する損失と、異なるブローカーの手数料およびスワップ要件に関連する損失を分離できるようにするため、特に手数料およびスワップから発生する費用を対応する期間の損失に含めていません。さて、クラスの対応するフィールドを宣言したので、次は制限の使用を制御しましょう。
制限の使用を制御する
限度の実際の使用を制御するためには、新しい期間の開始に関するイベントと、取引操作の完了に関するイベントを処理する必要があります。実際に使用された限度を正しく追跡するために、クラスのprotectedアクセス領域で内部メソッドForOnTrade()を発表します。
まず、現在時刻と日、週、月の開始時刻を表す変数をメソッドに用意する必要があります。このような目的のために、MqlDateTime形式のstruct構造型の特別な定義済みデータ型を使用します。さっそく次のような形で、現在の終了時刻で初期化します。
MqlDateTime local, start_day, start_week, start_month; // create structure to filter dates TimeLocal(local); // fill in initially TimeLocal(start_day); // fill in initially TimeLocal(start_week); // fill in initially TimeLocal(start_month); // fill in initially
現在の時刻を最初に初期化するには、定義済み関数TimeCurrent()ではなくTimeLocal()を使用することに注意してください。これは、後者はローカル時間を使用する一方、前者はブローカーから受信した最後のティックから時間を取得するため、異なるブローカー間のタイムゾーンの違いにより制限が不正確に計算される可能性があるためです。次に、各期間の開始時刻をリセットして、それぞれの開始日の値を取得する必要があります。そのためには、構造体のpublicフィールドに以下のようにアクセスします。
//--- reset to have the report from the beginning of the period start_day.sec = 0; // from the day beginning start_day.min = 0; // from the day beginning start_day.hour = 0; // from the day beginning start_week.sec = 0; // from the week beginning start_week.min = 0; // from the week beginning start_week.hour = 0; // from the week beginning start_month.sec = 0; // from the month beginning start_month.min = 0; // from the month beginning start_month.hour = 0; // from the month beginning
週と月のデータを正しく取得するには、週と月の始まりを見つけるロジックを定義する必要があります。月の場合、月はすべて1日から始まることがわかっているので、すべてが単純です。1週間を扱うのは少し複雑です。特定の報告ポイントがなく、日付も毎回変わるからです。ここでは、MqlDateTime構造体の特別なday_of_weekフィールドを使用することができます。現在の日付から、ゼロから始まる週番号を取得できるので、次のように簡単に現在の週の開始日を知ることができます。
//--- determining the beginning of the week int dif; // day of week difference variable if(start_week.day_of_week==0) // if this is the first day of the week { dif = 0; // then reset } else { dif = start_week.day_of_week-1; // if not the first, then calculate the difference start_week.day -= dif; // subtract the difference at the beginning of the week from the number of the day } //---month start_month.day = 1; // everything is simple with the month
現在時点を基準とした各期間の正確な開始日がわかったので、口座で実行された取引の履歴データの要求に進むことができます。最初に、クローズされた注文を考慮するために必要な変数を宣言し、選択された各期間について取引の財務結果が収集される変数の値をリセットする必要があります。
//--- uint total = 0; // number of selected trades ulong ticket = 0; // order number long type; // order type double profit = 0, // order profit commis = 0, // order commission swap = 0; // order swap DayorderLoss = 0; // daily loss without commission DayorderProfit = 0; // daily profit WeekorderLoss = 0; // weekly loss without commission WeekorderProfit = 0; // weekly profit MonthorderLoss = 0; // monthly loss without commission MonthorderProfit = 0; // monthly profit MonthOrderCommis = 0; // monthly commission MonthOrderSwap = 0; // monthly swap
定義済みの端末関数HistorySelect()を使用して、決済済み注文の履歴データを要求します。この関数のパラメータには、各期間について、先ほど受け取った日付が使用されます。そのためには、MqlDateTime変数の型を、パラメータHistorySelect()関数で要求される型(datetime)に変更する必要があります。そのために、定義済みの端末関数StructToTime() を使用します。取引データについても同様に、必要な期間の始期と終期を必要な値に置き換えて要求します。
HistorySelect()関数を呼び出すたびに、定義済みの端末関数HistoryDealsTotal()を使用して選択された注文の数を取得し、この値をローカル変数totalに入れる必要があります。クローズ済み取引の数を取得した後、for演算子でループを構成し、定義済みの端末関数HistoryDealGetTicket()で各注文の件数を要求します。これにより、各注文のデータにアクセスできるようになります。事前定義された端末関数HistoryDealGetDouble()およびHistoryDealGetInteger()を使用して各注文のデータにアクセスし、以前に受信した注文番号を渡します。ENUM_DEAL_PROPERTY_INTEGER列挙体およびENUM_DEAL_PROPERTY_DOUBLE列挙体から、対応する取引プロパティ識別子を指定する必要があります。また、ENUM_DEAL_TYPE列挙体からDEAL_TYPE_BUYとDEAL_TYPE_SELLの値を確認し、残高取引やボーナス発生などの他の口座操作をフィルタリングすることにより、取引操作からの取引のみを考慮する場合はブール選択演算子によるフィルタを追加する必要があります。というわけで、データを選択するコードは次のようになります。
//--- now select data by --==DAY==-- HistorySelect(StructToTime(start_day),StructToTime(local)); // select required history //--- check total = HistoryDealsTotal(); // number number of selected deals ticket = 0; // order number profit = 0; // order profit commis = 0; // order commission swap = 0; // order swap //--- for all deals for(uint i=0; i<total; i++) // loop through all selected orders { //--- try to get deals ticket if((ticket=HistoryDealGetTicket(i))>0) // get the number of each in order { //--- get deals properties profit = HistoryDealGetDouble(ticket,DEAL_PROFIT); // get data on financial results commis = HistoryDealGetDouble(ticket,DEAL_COMMISSION); // get data on commission swap = HistoryDealGetDouble(ticket,DEAL_SWAP); // get swap data type = HistoryDealGetInteger(ticket,DEAL_TYPE); // get data on operation type if(type == DEAL_TYPE_BUY || type == DEAL_TYPE_SELL) // if the deal is form a trading operatoin { if(profit>0) // if financial result of current order is greater than 0, { DayorderProfit += profit; // add to profit } else { DayorderLoss += MathAbs(profit); // if loss, add up } } } } //--- now select data by --==WEEK==-- HistorySelect(StructToTime(start_week),StructToTime(local)); // select the required history //--- check total = HistoryDealsTotal(); // number number of selected deals ticket = 0; // order number profit = 0; // order profit commis = 0; // order commission swap = 0; // order swap //--- for all deals for(uint i=0; i<total; i++) // loop through all selected orders { //--- try to get deals ticket if((ticket=HistoryDealGetTicket(i))>0) // get the number of each in order { //--- get deals properties profit = HistoryDealGetDouble(ticket,DEAL_PROFIT); // get data on financial results commis = HistoryDealGetDouble(ticket,DEAL_COMMISSION); // get data on commission swap = HistoryDealGetDouble(ticket,DEAL_SWAP); // get swap data type = HistoryDealGetInteger(ticket,DEAL_TYPE); // get data on operation type if(type == DEAL_TYPE_BUY || type == DEAL_TYPE_SELL) // if the deal is form a trading operatoin { if(profit>0) // if financial result of current order is greater than 0, { WeekorderProfit += profit; // add to profit } else { WeekorderLoss += MathAbs(profit); // if loss, add up } } } } //--- now select data by --==MONTH==-- HistorySelect(StructToTime(start_month),StructToTime(local)); // select the required history //--- check total = HistoryDealsTotal(); // number number of selected deals ticket = 0; // order number profit = 0; // order profit commis = 0; // order commission swap = 0; // order swap //--- for all deals for(uint i=0; i<total; i++) // loop through all selected orders { //--- try to get deals ticket if((ticket=HistoryDealGetTicket(i))>0) // get the number of each in order { //--- get deals properties profit = HistoryDealGetDouble(ticket,DEAL_PROFIT); // get data on financial results commis = HistoryDealGetDouble(ticket,DEAL_COMMISSION); // get data on commission swap = HistoryDealGetDouble(ticket,DEAL_SWAP); // get swap data type = HistoryDealGetInteger(ticket,DEAL_TYPE); // get data on operation type MonthOrderSwap += swap; // sum up swaps MonthOrderCommis += commis; // sum up commissions if(type == DEAL_TYPE_BUY || type == DEAL_TYPE_SELL) // if the deal is form a trading operatoin { if(profit>0) // if financial result of current order is greater than 0, { MonthorderProfit += profit; // add to profit } else { MonthorderLoss += MathAbs(profit); // if loss, sum up } } } }
上記のメソッドは、現在の制限使用値を更新する必要があるたびに呼び出すことができます。この関数を呼び出すだけでなく、実際の制限の値を更新することもできます。このメソッドのポイントは制限を更新することなので、TradeやTradeTransactionのような現在の注文の変更に関連するイベントが発生したときや、NewTickイベントで新しいティックが出現したときに実行することができます。これらのメソッドは非常にリソース効率が良いので、ティックごとに実際の制限を更新します。次に、動的キャンセルと取引解決に関連するイベントを処理するために必要なイベントハンドラを実装してみましょう。
クラスイベントハンドラ
イベントを処理するために、ContoEvents()クラスの内部メソッドをprotectedアクセスレベルで定義します。そのために、同じアクセスレベルを持つ補助フィールドを追加宣言します。取引許可フラグを変更するために必要な、新しい取引期間の開始時刻を即座に追跡できるようにするには、最後に記録した期間と現在の期間の値を保存する必要があります。このような目的のために、datetimeデータ型で宣言された単純な配列を使用して、対応する期間の値を格納することができます。
//--- additional auxiliary arrays datetime Periods_old[3]; // 0-day,1-week,2-mn datetime Periods_new[3]; // 0-day,1-week,2-mn
1次元目は日、2次元目には週、3次元目には月の値を格納します。管理期間をさらに拡張する必要がある場合は、これらの配列を静的にではなく、動的に宣言することができますが、ここでは3つの期間しか扱いません。では、クラスのコンストラクタに、これらの配列変数の一次初期化を以下のように追加してみましょう。
Periods_new[0] = iTime(_Symbol, PERIOD_D1, 1); // initialize the current day with the previous period Periods_new[1] = iTime(_Symbol, PERIOD_W1, 1); // initialize the current week with the previous period Periods_new[2] = iTime(_Symbol, PERIOD_MN1, 1); // initialize the current month with the previous period
対応する各期間を、定義済みの終端関数iTime()を使用して初期化します。パラメータには、現在の期間の前の期間からENUM_TIMEFRAMESの対応する期間を渡します。Periods_old[]配列はあえて初期化していません。この場合、コンストラクタとContoEvents()メソッドを呼び出した後、新しい取引期間の開始イベントがトリガーされ、取引開始のためのすべてのフラグがオープンされ、制限が残っていない場合にのみコードによってクローズされるようにします。そうしないと、再初期化したときにクラスが正しく動作しない可能性があります。もし現在の期間が前の期間と等しくなければ、それは新しい対応する期間が始まったことを意味し、フラグの値を変更することで限度をリセットし、取引を許可することができます。また、各期間ごとに、先に説明したRefreshLimits()メソッドを呼び出して、入力限度を再計算します。
//+------------------------------------------------------------------+ //| ContoEvents | //+------------------------------------------------------------------+ void RiskManagerBase::ContoEvents() { // check the start of a new trading day NewTradeDay = false; // variable for new trading day set to false Periods_old[0] = Periods_new[0]; // copy to old, new Periods_new[0] = iTime(_Symbol, PERIOD_D1, 0); // update new for day if(Periods_new[0]!=Periods_old[0]) // if do not match, it's a new day { Print(__FUNCTION__+" line"+IntegerToString(__LINE__)+", New trade day!"); // inform NewTradeDay = true; // variable to true DayProfitArrive = false; // reset flag of reaching target profit after a new day started RiskDayPermission = true; // allow opening new positions RefreshLimits(); // update limits DayorderLoss = 0; // reset daily financial result DayorderProfit = 0; // reset daily financial result } // check the start of a new trading week Periods_old[1] = Periods_new[1]; // copy data to old period Periods_new[1] = iTime(_Symbol, PERIOD_W1, 0); // fill new period for week if(Periods_new[1]!= Periods_old[1]) // if periods do not match, it's a new week { Print(__FUNCTION__+" line"+IntegerToString(__LINE__)+", New trade week!"); // inform RiskWeekPermission = true; // allow opening new positions RefreshLimits(); // update limits WeekorderLoss = 0; // reset weekly losses WeekorderProfit = 0; // reset weekly profits } // check the start of a new trading month Periods_old[2] = Periods_new[2]; // copy the period to the old one Periods_new[2] = iTime(_Symbol, PERIOD_MN1, 0); // update new period for month if(Periods_new[2]!= Periods_old[2]) // if do not match, it's a new month { Print(__FUNCTION__+" line"+IntegerToString(__LINE__)+", New trade Month!"); // inform RiskMonthPermission = true; // allow opening new positions RefreshLimits(); // update limits MonthorderLoss = 0; // reset the month's loss MonthorderProfit = 0; // reset the month's profit } // set the permission to open new positions true only if everything is true // set to true if(RiskDayPermission == true && // if there is a daily limit available RiskWeekPermission == true && // if there is a weekly limit available RiskMonthPermission == true // if there is a monthly limit available ) // { RiskTradePermission=true; // if all are allowed, trading is allowed } // set to false if at least one of them is false if(RiskDayPermission == false || // no daily limit available RiskWeekPermission == false || // or no weekly limit available RiskMonthPermission == false || // or no monthly limit available DayProfitArrive == true // or target profit is reached ) // then { RiskTradePermission=false; // prohibit trading } }
また、このメソッドでは、新規ポジションを建てる可能性を示すフラグのメイン変数であるRiskTradePermissionのデータの状態に対する制御を追加しました。論理的な選択演算子によって、すべての権限がtrueの場合のみ、この変数を通して権限を有効にし、少なくとも1つのフラグが取引を許可していない場合は無効にします。この変数は、すでに作成されているアルゴリズムEAにこのクラスを統合する場合に非常に便利です。ゲッターを介してこの変数を受け取り、注文を出すための条件をコードに挿入するだけです。私たちの場合、これは単に自由な取引制限がないことをユーザーに知らせ始めるためのフラグとして機能します。さて、このクラスでは、指定した損失が達成されたときにリスクを制御する方法を「学んだ」ので、次は目標利益の達成を制御する機能の実装に移りましょう。
1日の目標利益を制御する仕組み
これまでの記事で、目標利益に対する制御を開始するためのフラグと、口座の預金額に対するその値を決定するための入力変数を宣言しました。目標利益の達成を制御する当クラスのロジックによれば、すべてのポジションの合計利益が目標値に達した場合、すべての未決済ポジションはクローズされます。口座のすべてのポジションをクローズするには、クラスで内部メソッド AllOrdersClose() をpublicアクセスレベルで宣言します。このメソッドを機能させるには、ポジションのデータを受け取り、自動的に決済注文を送信する必要があります。
この機能を独自に実装して時間を浪費しないために、端末の既製の内部クラスを使用することにします。ポジションを扱うには内部標準の端末クラスCPositionInfoを使用し、ポジションをクローズするにはCTradeクラスを使用します。この2つのクラスの変数も、デフォルトのコンストラクタでポインタを使わずに、protectedアクセスレベルで次のように宣言してみましょう。
CTrade r_trade; // instance CPositionInfo r_position; // instance
これらのオブジェクトを今必要な機能の枠組みの中で扱う場合、追加で設定する必要はないので、クラスのコンストラクタに書くことはありません。以下は、宣言されたクラスを使用したこのメソッドの実装です。
//+------------------------------------------------------------------+ //| AllOrdersClose | //+------------------------------------------------------------------+ bool RiskManagerBase::AllOrdersClose() // closing market positions { ulong ticket = 0; // order ticket string symb; for(int i = PositionsTotal(); i>=0; i--) // loop through open positoins { if(r_position.SelectByIndex(i)) // if a position selected { ticket = r_position.Ticket(); // remember position ticket if(!r_trade.PositionClose(ticket)) // close by ticket { Print(__FUNCTION__+". Error close order. "+IntegerToString(ticket)); // if not, inform return(false); // return false } else { Print(__FUNCTION__+". Order close success. "+IntegerToString(ticket)); // if not, inform continue; // if everything is ok, continue } } } return(true); // return true }
目標利益が達成されたときと、制限に達したときの両方で、説明したメソッドを呼び出します。また、注文決済を送信する際にエラーを処理する必要がある場合に備えて、bool値も返します。目標利益が達成されたかどうかを制御する機能を提供するために、イベント処理メソッドContoEvents()を、すでに上で説明したコードの直後に、以下のコードで補足します。
//--- daily if(dayProfitControl) // check if functionality is enabled by the user { if(CurrentEquity >= (StartEquity+PlanDayProfit)) // if equity exceeds or equals start + target profit, { DayProfitArrive = true; // set flag that target profit is reached Print(__FUNCTION__+", PlanDayProfit has been arrived."); // inform about the event Print(__FUNCTION__+", CurrentEquity = "+DoubleToString(CurrentEquity)+ ", StartEquity = "+DoubleToString(StartEquity)+ ", PlanDayProfit = "+DoubleToString(PlanDayProfit)); AllOrdersClose(); // close all open orders StartEquity = CurrentEquity; // rewrite starting equity value //--- send a push notification ResetLastError(); // reset the last error if(!SendNotification("The planned profitability for the day has been achieved. Equity: "+DoubleToString(CurrentEquity)))// notification { Print(__FUNCTION__+IntegerToString(__LINE__)+", Error of sending notification: "+IntegerToString(GetLastError()));// if not, print } } }
このメソッドには、このイベントが発生したことを通知するためにユーザーにプッシュ通知を送信することが含まれます。そのために、定義済みの端末関数SendNotificationを使用します。このクラスの最低限必要な機能を完成させるには、publicアクセスを持つクラスメソッドをもうひとつ組み立てるだけです。このメソッドは、リスクマネージャーがEAの構造に接続されたときに呼び出されます。
EA構造における監視開始メソッドの定義
リスクマネージャークラスのインスタンスからEA構造に監視機能を追加するために、ContoMonitor() publicメソッドを宣言します。このメソッドでは、以前に宣言されたすべてのイベント処理メソッドを収集し、また、実際に使用された制限値を、入力パラメータでユーザーが承認した値と比較する機能で補足します。このメソッドをpublicアクセスレベルで宣言し、クラスの外部に次のように記述してみましょう。
//+------------------------------------------------------------------+ //| ContoMonitor | //+------------------------------------------------------------------+ void RiskManagerBase::ContoMonitor() // monitoring { ForOnTrade(); // update at each tick ContoEvents(); // event block //--- double currentProfit = AccountInfoDouble(ACCOUNT_PROFIT); if((MathAbs(DayorderLoss)+MathAbs(currentProfit) >= RiskPerDay && // if equity is less than or equal to the start balance minus the daily risk currentProfit<0 && // profit below zero RiskDayPermission==true) // day trading is allowed || // OR (RiskDayPermission==true && // day trading is allowed MathAbs(DayorderLoss) >= RiskPerDay) // loss exceed daily risk ) { Print(__FUNCTION__+", EquityControl, "+"ACCOUNT_PROFIT = " +DoubleToString(currentProfit));// notify Print(__FUNCTION__+", EquityControl, "+"RiskPerDay = " +DoubleToString(RiskPerDay)); // notify Print(__FUNCTION__+", EquityControl, "+"DayorderLoss = " +DoubleToString(DayorderLoss)); // notify RiskDayPermission=false; // prohibit opening new orders during the day AllOrdersClose(); // close all open positions } // check if there is a WEEK limit available for opening a new position if there are no open ones if( MathAbs(WeekorderLoss)>=RiskPerWeek && // if weekly loss is greater than or equal to the weekly risk RiskWeekPermission==true) // and we traded { RiskWeekPermission=false; // prohibit opening of new orders during the day AllOrdersClose(); // close all open positions Print(__FUNCTION__+", EquityControl, "+"WeekorderLoss = "+DoubleToString(WeekorderLoss)); // notify Print(__FUNCTION__+", EquityControl, "+"RiskPerWeek = "+DoubleToString(RiskPerWeek)); // notify } // check if there is a MONTH limit available for opening a new position if there are no open ones if( MathAbs(MonthorderLoss)>=RiskPerMonth && // if monthly loss is greater than or equal to the monthly risk RiskMonthPermission==true) // we traded { RiskMonthPermission=false; // prohibit opening of new orders during the day AllOrdersClose(); // close all open positions Print(__FUNCTION__+", EquityControl, "+"MonthorderLoss = "+DoubleToString(MonthorderLoss)); // notify Print(__FUNCTION__+", EquityControl, "+"RiskPerMonth = "+DoubleToString(RiskPerMonth)); // notify } }
このメソッドの動作ロジックは非常にシンプルです。月または週の実際の損失限度がユーザーによって設定されたものを超えた場合、指定された期間の取引フラグが禁止に設定され、それに応じて取引が禁止されます。唯一の違いは日次の限度で、そこではポジションの存在も制御する必要があります。このため、論理演算子ORを通じて、ポジションからの現在の利益の制御も追加します。リスクの限度に達したら、ポジションをクローズするメソッドを呼び出し、このイベントに関するログを表示します。
この段階で、クラスを完全に完成させるには、ユーザーが現在の制限を制御するためのメソッドを追加するだけです。最もシンプルで便利な方法は、標準の定義済み端末関数Comment()を使用して必要な情報を表示することです。この関数を使用するには、チャートに表示する情報を含む文字列型のパラメータを渡す必要があります。私たちのクラスからこれらの値を取得するために、publicアクセスレベルでMessage()メソッドを宣言します。これは、ユーザーが必要とするすべての変数に関する収集されたデータを含む文字列 データを返します。
//+------------------------------------------------------------------+ //| Message | //+------------------------------------------------------------------+ string RiskManagerBase::Message(void) { string msg; // message msg += "\n"+" ----------Risk-Manager---------- "; // common //--- msg += "\n"+"RiskTradePer = "+(string)RiskTradePermission; // final trade permission msg += "\n"+"RiskDayPer = "+(string)RiskDayPermission; // daily risk available msg += "\n"+"RiskWeekPer = "+(string)RiskWeekPermission; // weekly risk available msg += "\n"+"RiskMonthPer = "+(string)RiskMonthPermission; // monthly risk available //---limits and inputs msg += "\n"+" -------------------------------- "; // msg += "\n"+"RiskPerDay = "+DoubleToString(RiskPerDay,2); // daily risk in usd msg += "\n"+"RiskPerWeek = "+DoubleToString(RiskPerWeek,2); // weekly risk in usd msg += "\n"+"RiskPerMonth = "+DoubleToString(RiskPerMonth,2); // monthly risk usd //--- current profits and losses for periods msg += "\n"+" -------------------------------- "; // msg += "\n"+"DayLoss = "+DoubleToString(DayorderLoss,2); // daily loss msg += "\n"+"DayProfit = "+DoubleToString(DayorderProfit,2); // daily profit msg += "\n"+"WeekLoss = "+DoubleToString(WeekorderLoss,2); // weekly loss msg += "\n"+"WeekProfit = "+DoubleToString(WeekorderProfit,2); // weekly profit msg += "\n"+"MonthLoss = "+DoubleToString(MonthorderLoss,2); // monthly loss msg += "\n"+"MonthProfit = "+DoubleToString(MonthorderProfit,2); // monthly profit msg += "\n"+"MonthCommis = "+DoubleToString(MonthOrderCommis,2); // monthly commissions msg += "\n"+"MonthSwap = "+DoubleToString(MonthOrderSwap,2); // monthly swaps //--- for current monitoring if(dayProfitControl) // if control daily profit { msg += "\n"+" ---------dayProfitControl-------- "; // msg += "\n"+"DayProfitArrive = "+(string)DayProfitArrive; // daily profit achieved msg += "\n"+"StartBallance = "+DoubleToString(StartBalance,2); // starting balance msg += "\n"+"PlanDayProfit = "+DoubleToString(PlanDayProfit,2); // target profit msg += "\n"+"PlanDayEquity = "+DoubleToString(PlanDayEquity,2); // target equity } return(msg); // return value }
このメソッドで作成されたユーザーのメッセージは次のようになります。
図2:データ出力形式
このメソッドは、端末でグラフィックを扱うための要素を追加することによって、変更または補足することができます。しかし、このように使用することで、ユーザーにクラスから判断を下すのに十分なデータを提供することができるからです。必要であれば、将来この形式を改良し、グラフィックの面でより美しくすることもできます。ここで、個別取引戦略を使用する際に、このクラスを拡張する可能性について説明しましょう。
最終的な実装とクラス拡張の可能性
先に述べたように、ここで説明した機能は必要最低限であり、ほとんどすべての取引戦略にとって最も普遍的なものです。一日のリスクを制御し、預金の損失を防ぐことができます。この部分では、このクラスを拡大する可能性をさらにいくつか見ていきます。
- 売りストップロスで取引する場合、スプレッドのサイズを制御
- ポジションのスリッページを制御
- 目標月利益を制御
最初の点については、売りのストップロスでの取引を使用する取引システムのための追加機能を実装することができます。SpreadMonitor(int intSL)メソッドを宣言することができます。このメソッドは、テクニカルまたは計算されたストップロスをパラメータとして受け取り、現在のスプレッドレベルと比較します。このメソッドでは、スプレッドがストップロスに対してユーザーが決めた割合で大きく広がった場合、注文を出すことを禁止し、スプレッドによるストップロスでポジションを決済する高いリスクを回避します。
オープン時のスリッページを制御するには、2番目のポイントに従って、SlippageCheck()メソッドを宣言します。このメソッドは、ブローカーが、取引リスクが期待値を上回ったために、提示された価格と大きく異なる価格で取引を開始した場合、個々の取引をクローズします。これにより、ストップロスがトリガーされた場合でも、1回のエントリごとにハイリスク取引をして統計を台無しにすることがなくなります。また、ストップロスとテイクプロフィットの比率を固定して取引する場合、この比率はスリッページによって悪化するため、後で大きな損失を被るよりは、小さな損失でポジションを決済した方がよいです。
日次利益を制御するロジックと同様に、月次目標利益を制御するための対応するメソッドを実装することが可能です。このメソッドは、長期的な戦略を取引する際に利用できます。説明したクラスは、手動による日中取引で使用するために必要なすべての機能をすでに備えており、取引EAの最終的な実装に統合することができます。これは、手動取引の開始と同時に、商品チャート上で起動する必要があります。
プロジェクトの最終的な組み立てには、#includeプリプロセッサー指令を使用したクラスの接続が含まれます。
#include <RiskManagerBase.mqh>
次に、グローバルレベルでリスクマネージャーオブジェクトのポインタを宣言します。
RiskManagerBase *RMB;
EAを初期化する際、手動でオブジェクトのメモリを確保し、起動前に準備します。
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { RMB = new RiskManagerBase(); //--- return(INIT_SUCCEEDED); }
チャートからEAを取り除く際には、メモリリークを避けるためにオブジェクトからメモリをクリアする必要があります。そのために、EAのOnDeinit関数に次のように記述します。
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- delete RMB; }
また、必要であれば、同じイベントでComment(" ")メソッドを呼び出し、そこに空の文字列を渡すことで、EAが銘柄チャートから削除されたときにチャートのコメントが消去されるようにすることもできます。
銘柄の新しいティックを受け取ったら、クラスのメイン監視メソッドを呼び出します。
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { RMB.ContoMonitor(); Comment(RMB.Message()); } //+------------------------------------------------------------------+
これでリスクマネジャーを組み込んだEAの組み立ては完了し、使用する準備が完全に整いました(ファイルManualRiskManager.mq5)。いくつかの使用例をテストするために、現在のコードにちょっとした追加をおこない、手動取引のプロセスをシミュレートします。
使用例
リスクマネージャーを使用した場合と使用しなかった場合の手動取引のプロセスを視覚化するためには、手動取引をモデル化する追加コードが必要になります。この記事では、取引戦略の選択については触れないので、コードで完全な取引機能は実装しません。その代わりに、日足チャートから視覚的にエントリを取得し、EAに既製のデータを追加します。非常に単純な戦略を使用して取引判断をおこない、この戦略の最終的な財務結果を、リスク制御の有無という唯一の違いで見ていきます。
エントリの例として、USDJPYの商品について、2ヶ月間にわたるフラクタルレベルのブレイクアウトを使用したシンプルな戦略を使用します。この戦略が、リスク制御の有無にかかわらず、どのように機能するか見てみましょう。手動エントリのシグナルは以下のようになります。
図3:テスト戦略によるエントリ
この戦略をモデル化するために、あらゆる手動戦略のための普遍的なユニットテストとして、小さな追加項目を書いてみましょう。このテストでは、エントリのロジックを実装せずに、準備完了信号を戦略に読み込みます。そのためにはまず、フラクタルベースのエントリを格納する追加の構造体structを宣言する必要があります。
//+------------------------------------------------------------------+ //| TradeInputs | //+------------------------------------------------------------------+ struct TradeInputs { string symbol; // symbol ENUM_POSITION_TYPE direction; // direction double price; // price datetime tradedate; // date bool done; // trigger flag };
取引シグナルのモデリングを担当する主要クラスは TradeModel です。このクラスのコンストラクタは、シグナル入力パラメータを持つコンテナを受け取り、メインのProcessing()メソッドは、入力値に基づいてエントリポイントの時刻が来たかどうかを毎ティック監視します。日中取引をシミュレートしているので、一日の終わりには、リスクマネージャークラスで先に宣言したAllOrdersClose()メソッドを使用してすべてのポジションを削除します。これが補助クラスです。
//+------------------------------------------------------------------+ //| TradeModel | //+------------------------------------------------------------------+ class TradeModel { protected: CTrade *cTrade; // to trade TradeInputs container[]; // container of entries int size; // container size public: TradeModel(const TradeInputs &inputs[]); ~TradeModel(void); void Processing(); // main modeling method };
便利な発注を可能にするため、必要な機能をすべて備えた標準端末クラスCTradeを使用します。これで補助クラスの開発にかかる時間を短縮できます。クラスインスタンスを生成するときに入力パラメータを渡すために、エントリコンテナの入力パラメータを1つ持つコンストラクタを定義します。
//+------------------------------------------------------------------+ //| TradeModel | //+------------------------------------------------------------------+ TradeModel::TradeModel(const TradeInputs &inputs[]) { size = ArraySize(inputs); // get container size ArrayResize(container, size); // resize for(int i=0; i<size; i++) // loop through inputs { container[i] = inputs[i]; // copy to internal } //--- trade class cTrade=new CTrade(); // create trade instance if(CheckPointer(cTrade)==POINTER_INVALID) // if instance not created, { Print(__FUNCTION__+IntegerToString(__LINE__)+" Error creating object!"); // notify } cTrade.SetTypeFillingBySymbol(Symbol()); // fill type for the symbol cTrade.SetDeviationInPoints(1000); // deviation cTrade.SetExpertMagicNumber(123); // magic number cTrade.SetAsyncMode(false); // asynchronous method }
コンストラクタでは、入力パラメータのコンテナーを希望する値で初期化し、そのサイズを記憶させ、必要な設定でCTradeクラスのオブジェクトを作成します。ここでのパラメータのほとんどは、ユニットテストを作成する目的には影響しないので、ユーザーが設定することはありません。
TradeModel クラスのデストラクタはCTradeオブジェクトを削除するだけです。
//+------------------------------------------------------------------+ //| ~TradeModel | //+------------------------------------------------------------------+ TradeModel::~TradeModel(void) { if(CheckPointer(cTrade)!=POINTER_INVALID) // if there is an instance, { delete cTrade; // delete } }
それでは、プロジェクト全体の構造の中で、クラスの操作のための主要な処理メソッドを実装していきましょう。図3に従って注文を出すロジックを実装してみましょう。
//+------------------------------------------------------------------+ //| Processing | //+------------------------------------------------------------------+ void TradeModel::Processing(void) { datetime timeCurr = TimeCurrent(); // request current time double bid = SymbolInfoDouble(Symbol(),SYMBOL_BID); // take bid double ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK); // take ask for(int i=0; i<size; i++) // loop through inputs { if(container[i].done==false && // if we haven't traded yet AND container[i].tradedate <= timeCurr) // date is correct { switch(container[i].direction) // check trade direction { //--- case POSITION_TYPE_BUY: // if Buy, if(container[i].price >= ask) // check if price has reached and { if(cTrade.Buy(0.1)) // by the same lot { container[i].done = true; // if time has passed, put a flag Print("Buy has been done"); // notify } else // if hasn't passed, { Print("Error: buy"); // notify } } break; // complete the case //--- case POSITION_TYPE_SELL: // if Sell if(container[i].price <= bid) // check if price has reached and { if(cTrade.Sell(0.1)) // sell the same lot { container[i].done = true; // if time has passed, put a flag Print("Sell has been done"); // notify } else // if hasn't passed, { Print("Error: sell"); // notify } } break; // complete the case //--- default: Print("Wrong inputs"); // notify return; break; } } } }
この方法の理屈はいたってシンプルです。モデリング時間が到来したコンテナ内に未処理のエントリがあれば、図3でマークされたフラクタル方向と価格に従って、これらの注文を発注します。この機能はリスクマネージャーをテストするのに十分なので、メインプロジェクトに統合することができます。
まず、テストクラスとESコードを次のように接続してみましょう。
#include <TradeModel.mqh>
OnInit()関数の中で、TradeInputsデータ配列構造体のインスタンスを作成し、この配列をTradeModelクラスのコンストラクタに渡して初期化します。
//--- TradeInputs modelInputs[] = { {"USDJPYz", POSITION_TYPE_SELL, 146.636, D'2024-01-31',false}, {"USDJPYz", POSITION_TYPE_BUY, 148.794, D'2024-02-05',false}, {"USDJPYz", POSITION_TYPE_BUY, 148.882, D'2024-02-08',false}, {"USDJPYz", POSITION_TYPE_SELL, 149.672, D'2024-02-08',false} }; //--- tModel = new TradeModel(modelInputs);
DeInit()関数でtModelオブジェクトのメモリをクリアすることを忘れないでください。主な機能はOnTick()関数の中で実行され、以下のコードで補足されます。
tModel.Processing(); // place orders MqlDateTime time_curr; // current time structure TimeCurrent(time_curr); // request current time if(time_curr.hour >= 23) // if end of day { RMB.AllOrdersClose(); // close all positions }
同じ戦略でリスク制御クラスを使用した場合と使わなかった場合の結果を比較してみましょう。ユニットテストファイルManualRiskManager(UniTest1)をリスク制御メソッドなしで実行してみましょう。期間2024年1月~3月の戦略の結果は次のようになります。
図4:リスクマネージャーを使わずにデータをテストする
その結果、次のようなパラメータを持つこの戦略に対して、数学的に正の期待値が得られます。
# | パラメータ名 | パラメータ値 |
---|---|---|
1 | EA | ManualRiskManager(UniTest1) |
2 | 銘柄 | USDJPY |
3 | チャートの時間枠 | М15 |
4 | 時間 | 2024.01.01 - 2024.03.18 |
5 | フォワードテスト | いいえ |
6 | 遅延 | 遅延なし、完璧なパフォーマンス |
7 | シミュレーション | 全ティック |
8 | 初期投資額 | USD 10,000 |
9 | レバレッジ | 1:100 |
表1:ストラテジーテスターの入力パラメータ
では、ユニットテストManualRiskManager(UniTest2)を実行しましょう。ここでは、リスクマネージャークラスを以下の入力パラメータで使用します。
入力パラメータ名 | 変数値 |
---|---|
inp_riskperday | 0.25 |
inp_riskperweek | 0.75 |
inp_riskpermonth | 2.25 |
inp_plandayprofit | 0.78 |
dayProfitControl | true |
表2:リスクマネジャーの入力パラメータ
入力パラメータを生成するためのロジックは、第3回で入力パラメータの構造を設計する際に上述したロジックと同様です。利益曲線はこのようになります。
図5:リスクマネージャーを使用したデータのテスト
2つのケースのテスト結果の要約を以下の表に示します。
# | 値 | リスクマネージャーなし | リスクマネージャーあり | 変化 |
---|---|---|---|---|
1 | 総収益 | 41.1 | 144.48 | +103.38 |
2 | 残高ドローダウン最大値 | 0.74% | 0.25% | 3倍削減 |
3 | エクイティドローダウン最大値 | 1.13% | 0.58% | 2倍に減少 |
4 | 期待損益 | 10.28 | 36.12 | 3倍以上の成長 |
5 | シャープレシオ | 0.12 | 0.67 | 5倍の成長 |
6 | 勝ち取引(%) | 75% | 75% | - |
7 | 平均収益取引 | 38.52 | 56.65 | 50%の成長 |
8 | 平均損失 | -74.47 | -25.46 | 3倍削減 |
9 | 平均リスクリターン | 0.52 | 2.23 | 4倍の成長 |
表3:リスクマネージャーを使用した場合と使用しなかった場合の財務結果の比較
単体テストの結果から、リスクマネージャークラスによるリスク制御の利用は、リスクを制限し、固定リスクに対する各取引の利益を確定することで、同じ単純な戦略を使用した取引の効率を大幅に高めたと結論づけることができます。これにより、残高のドローダウンを3倍、エクイティ残高を2倍に減らすことができました。同戦略の期待ペイオフは3倍以上、シャープレシオは5倍以上増加しました。平均利益取引は50%増加し、平均不採算取引は3倍減少したため、口座の平均リスクリターンをほぼ目標値である1対3にすることができました。下の表は、プールから個々の取引について財務結果を詳細に比較したものです。
日付 | 銘柄 | 方向 | ロット | リスクマネージャーなし | リスクマネージャーあり | 変化 |
---|---|---|---|---|---|---|
2024.01.31 | USDJPY | 買 | 0.1 | 25.75 | 78 | + 52.25 |
2024.02.05 | USDJPY | 売 | 0.1 | 13.19 | 13.19 | - |
2024.02.08 | USDJPY | 売 | 0.1 | 76.63 | 78.75 | + 2.12 |
2024.02.08 | USDJPY | 買 | 0.1 | -74.47 | -25.46 | + 49.01 |
合計 | - | - | - | 41.10 | 144.48 | + 103.38 |
表4:リスクマネージャーの有無による約定比較
結論
本稿で提示されたテーゼに基づき、以下の結論が導き出されます。手動取引でもリスクマネージャーを使用することで、収益性の高い戦略を含め、戦略の有効性を大幅に高めることができます。負ける戦略の場合、リスクマネージャーを利用することで、預託金を確保し、損失を抑えることができます。冒頭で述べたように、私たちは心理的な要因を軽減しようとしています。すぐに損失を取り戻そうとして、リスクマネージャーをオフにすべきではありません。限度が完了するのを待ち、感情的にならずに取引を再開した方がいいです。リスクマネジャーが取引を禁止している時間を利用して、取引戦略を分析し、損失の原因と今後の回避方法を理解します。
この記事を最後まで読んでくださった皆さん、ありがとうございました。この記事によって、少なくとも1人の預金が完全に失われることから救われることを心から願っています。この場合、私の努力は無駄ではなかったと考えることにします。特にこのクラスを純粋にアルゴリズム的な EA に適応できる新しい記事を開始すべきかどうかについて、皆さんのコメントやDMをお待ちしています。ご意見、ご感想は大歓迎です。よろしくお願いします。
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/14340
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索