Developing a Replay System (Part 36): Making Adjustments (II)
はじめに
前回の「リプレイシステムの開発(第35回):調整」稿では 、マーケットリプレイサービスを簡単に管理できるEAを作りました。この記事での修正によってユーザーエクスペリエンスは向上しましたが、リプレイ/シミュレーションシステムに関連するすべてを完全に再設計したわけではありません。
ユーザーエクスペリエンスが向上したとはいえ、まだ小さな問題があります。この記事では、(理論的には)比較的単純であるにもかかわらず、正しく解くのがかなり難しいことが判明した問題を解きます。そして、MetaTrader 5に関する知識を広げ、MQL5言語をより詳細に使用する方法を学びます。
市場タイプの表示
チャート上に配置されたEAは、検出された口座タイプを通知します。これは、EAがどのように行動すべきかを知るために重要です。非常にうまく機能するものの、システムがリアル口座またはデモ口座のチャート上で実行されると、システムはリプレイ/シミュレーションシステムを制御するものを使用せず、資産が属する口座タイプではなくプラットフォームが操作している口座タイプを報告します。この問題は小さいとはいえ、私たちに不便を強います。
解決するのは複雑でも難しくもなく、とても簡単なことだと思われるかもしれません。リプレイ/シミュレーションシステムが、どの口座タイプが正しいかを何らかの方法で教えてくれるようにしましょう。もちろん、これは使用する資産によって異なります。実際、このアイデアはシンプルですが、実践してみると、これはまた別の話になるでしょう。実際のところ、リプレイ/シミュレーションシステムに、どの口座タイプを使用するべきかを教えてもらうのは、そう簡単なことではありません。しかし幸いなことに、MetaTrader 5プラットフォームは、実際の使用に十分かつ妥当な解決策を実装する機会を提供してくれます。
しかし、無謀なことはしません。特定の口座タイプで特定のことを防ぐために、特定の方法で解決策を実装します。この情報は、発注システムを構築する際に重要な情報となります。まず、何の話をしているのか考えてみましょう。リプレイ/シミュレーションシステムは、さまざまな市場の資産を使用できるようになります。つまり、NETTINGまたはHEDGINGの口座タイプを意味する資産を使用することができます。
注:EXCHANGEタイプはNETTINGとよく似ているので、実際にはこのEXCHANGEタイプは使用しません。NETTINGとみなします。
リプレイ/シミュレーションシステムのユーザーはその資産がどのようなタイプの市場、より具体的にはどのようなタイプの口座を使用しているかを知っているので、ユーザーがリプレイ/シミュレーションシステムにそれを指示する機能を追加することができます。取引銘柄の設定ファイルに新しいコマンドを追加するだけなので、これが最も簡単な部分です。しかし、これでは情報が本当に必要な場所で使用できる保証はありません。では、リプレイ/シミュレーションシステムのどこで使用されているのでしょうか。この情報はEA、すなわちC_ManagerクラスとC_Ordersクラスで使用されます。そこでは、この具体的な情報が積極的に活用されます。今はそうでなくても、もっとグローバルに、一般的に考える必要があります。以前、連載「自動で動くEAを作る」に見たものを使用したいかもしれません。
その連載では、EAは使用されている口座タイプを知る必要がありました。まさに同じ理由で、リプレイ/モデリングシステムがこのことをEAにも通知できるようにする必要がありました。そうでなければ、連載で説明した機能は失われ、私たちのシステムに移行することはできません。さて、特定の資産が使用している口座タイプをリプレイ/モデリングサービスに伝える方法については、すでにアイデアがあります。しかし問題は、この情報をどうやってEAに渡すかです。
ここで本当に立ち止まって考える必要があります。コードを見れば、口座タイプがわかります。ご覧ください。
C_Manager(C_Terminal *arg1, C_Study *arg2, color cPrice, color cStop, color cTake, const ulong magic, const double FinanceStop, const double FinanceTake, uint Leverage, bool IsDayTrade) :C_ControlOfTime(arg1, magic) { string szInfo = "HEDGING"; Terminal = arg1; Study = arg2; if (CheckPointer(Terminal) == POINTER_INVALID) SetUserError(C_Terminal::ERR_PointerInvalid); if (CheckPointer(Study) == POINTER_INVALID) SetUserError(C_Terminal::ERR_PointerInvalid); if (_LastError != ERR_SUCCESS) return; m_Infos.FinanceStop = FinanceStop; m_Infos.FinanceTake = FinanceTake; m_Infos.Leverage = Leverage; m_Infos.IsDayTrade = IsDayTrade; m_Infos.AccountHedging = false; m_Objects.corPrice = cPrice; m_Objects.corStop = cStop; m_Objects.corTake = cTake; m_Objects.bCreate = false; switch ((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE)) { case ACCOUNT_MARGIN_MODE_RETAIL_HEDGING: m_Infos.AccountHedging = true; break; case ACCOUNT_MARGIN_MODE_RETAIL_NETTING: szInfo = "NETTING"; break; case ACCOUNT_MARGIN_MODE_EXCHANGE : szInfo = "EXCHANGE"; break; } Print("Detected Account ", szInfo); }
したがって、EAは使用されている口座タイプを判断することができます。しかし、ここで問題があります。それはAccountInfoInteger関数です。関数そのものに問題があるわけではありません。問題は、リプレイ/シミュレーションサービスを使用する場合、AccountInfoInteger関数の結果が取引サーバー上の口座タイプに関する情報になるということです。言い換えれば、NETTINGサーバーに接続している場合、リプレイ/シミュレーション資産がHEDGINGであっても、NETTING口座で実行することになります。
これが問題なのです。次はアイデアです。EAにリプレイ/シミュレーション資産の設定ファイルを読み込むように指示することもできます。ある意味、1つのファイルだけを使用するのであれば、これは適切なことでしょう。そこで、この口座タイプの情報を渡すよう、リプレイ/シミュレーションサービスに依頼することができます。そうすればEAは正しいタイプを知ることができます。そう、その通りです。ただし、ここでちょっとしたポイントがあります。C/C++とは異なり、MQL5には特定のコード構築タイプがありません。プログラム間で情報をやり取りするために、ある種のコーディングを使用することがまったく不可能というわけではありません。それが可能であることは、すでに何度も見てきました。しかし、問題は、情報が8バイトのブロックに収められなければならないことです。これにより、端末のグローバル変数を使用して、プログラム間で情報を受け渡すことができます。MQL5を使用するのだということを忘れないでください。他にも方法はありますが、ここではMQL5のプラットフォームと言語の機能を使いたいのです。
問題に戻りましょう。長い間使用されてきた方法を使えばいいのです。このために、InterProcess.mqhファイルを使用して必要な通信をおこないました。しかし、このファイルには1つ問題があります。この問題を理解するために、そのコードを見てみましょう。
#property copyright "Daniel Jose" //+------------------------------------------------------------------+ #define def_SymbolReplay "RePlay" #define def_GlobalVariableReplay def_SymbolReplay + "_Infos" #define def_GlobalVariableIdGraphics def_SymbolReplay + "_ID" #define def_GlobalVariableServerTime def_SymbolReplay + "_Time" #define def_MaxPosSlider 400 #define def_ShortName "Market_" + def_SymbolReplay //+------------------------------------------------------------------+ union u_Interprocess { union u_0 { double df_Value; // Value of the terminal global variable... ulong IdGraphic; // Contains the Graph ID of the asset... }u_Value; struct st_0 { bool isPlay; // Indicates whether we are in Play or Pause mode... bool isWait; // Tells the user to wait... ushort iPosShift; // Value between 0 and 400... }s_Infos; datetime ServerTime; }; //+------------------------------------------------------------------+
問題はこれらのブール値です。各ブール値が1ビットに収まるC/C++とは異なり、MQL5では各ブール値が8ビットを占めます。もしかしたら、この本当の意味がおわかりでないないかもしれませんが、必要なものを保存するのに1ビットしか必要ないときに整数バイトが使用されています。この事実を知らないことが問題を引き起こします。ushortは情報を送信するのに2バイト必要であることを覚えておいてください。st_0構造体は、予想した3バイトではなく、実際には4バイトを消費します。このst_0構造体にさらに4つ、ブーリアンだけを4つ追加すると、使用できるバイト数は8バイトが限界となります。
このようなことがあると、プログラミングが少し複雑になるので、すべてをMQL5の中だけでやろうという考えは、見かけよりも少し複雑になります。物事がより複雑になったり、ブーリアンモードでより多くのデータを渡す必要がある場合は、この構造体を根本的に変えなければならないでしょう。これは実施上の悪夢ですが、これまでは同じ構造体を維持することができています。MQL5言語の開発者が、少なくともブール型の場合、C/C++と同じプログラミングモードを使用できるようにしてくれればいいですね。こうすることで、各変数が使用するビット数を決定することができ、変数の整理、分離、グループ分けはコンパイラがすべてやってくれるので、そのような目標を達成するために低レベルのプログラミングをする必要がなくなります。唯一の違いがあります。MQL5の開発者がこの作業をおこなうのであれば、アセンブリコードや機械語に非常に近いものを使用して整理し、構築することが可能になるため、結果はより効率的なものになるだろうということです。私たち開発者だけでこの作業をするならば、コードの効率はそれほど良くなく、もっと多くの作業が必要になるでしょう。
そこで、MQL5の改善のヒントを紹介しましょう。一見些細なことですが、この言語でのプログラミングには非常に役立ちます。
手元にあるものだけで作業できるので、今すぐコードに戻りましょう。Interprocess.mqhファイルの新しいコードを以下に示します。
#property copyright "Daniel Jose" //+------------------------------------------------------------------+ #define def_SymbolReplay "RePlay" #define def_GlobalVariableReplay def_SymbolReplay + "_Infos" #define def_GlobalVariableIdGraphics def_SymbolReplay + "_ID" #define def_GlobalVariableServerTime def_SymbolReplay + "_Time" #define def_MaxPosSlider 400 #define def_ShortName "Market_" + def_SymbolReplay //+------------------------------------------------------------------+ union u_Interprocess { union u_0 { double df_Value; // Value of the terminal global variable... ulong IdGraphic; // Contains the Graph ID of the asset... }u_Value; struct st_0 { bool isPlay; // Indicates whether we are in Play or Pause mode... bool isWait; // Tells the user to wait... bool isHedging; // If true we are in a Hedging account, if false the account is Netting... bool isSync; // If true indicates that the service is synchronized... ushort iPosShift; // Value between 0 and 400... }s_Infos; datetime ServerTime; }; //+------------------------------------------------------------------+
ここでやったことは、2つの新しいブーリアン変数を追加しただけであることにご注目ください。これで当面の問題は解決しますが、8バイトの制限にかなり近づいてしまいます。ここでは4ビットに対応する4つのブール数しか使用していませんが、各ブール数が8ビットを占めるということは、st_0構造体のサイズが予想された3バイトではなく、6バイトであることを意味します。
重要な注意事項:もしMQL5言語がブール構造を定義するために同様のC/C++モデリングを使用した場合に同じコードがどのようになるかを知らない方のために言っておくと、コードを読んだり書いたりするのはまったく問題ありません。同じコードで6バイトかかるものが3バイトになります。
union u_Interprocess { union u_0 { double df_Value; // Value of the terminal global variable... ulong IdGraphic; // Contains the Graph ID of the asset... }u_Value; struct st_0 { char isPlay : 1; // Indicates whether we are in Play or Pause mode... char isWait : 1; // Tells the user to wait... char isHedging : 1; // If true we are in a Hedging account, if false the account is Netting... char isSync : 1; // If true indicates that the service is synchronized... ushort iPosShift; // Value between 0 and 400... }s_Infos; datetime ServerTime; };
MQL5言語コンパイラは、少なくとも執筆時点では上記のコードを理解できませんが、ここでは各宣言で1ビットしか使わないことをコンパイラに伝えています。より制御された方法でビットにアクセスし、コンパイル定義ディレクティブではなくラベルを使用してこれを実行できるように、変数を適切に設定するのはプログラマーではなくコンパイラの仕事です。
ネイティブのMQL5でもこの構成は可能ですが、コンパイルディレクティブを使用してこれをおこなうのはエラーや失敗の原因になるとともに、コードのメンテナンスも非常に難しくなります。言語が私たちに特定のことを許さないと思わないように、この点を明確にしておきましょう。
でも、世界がレモンをくれるなら、レモネードを作りましょう。そして、世界が私たちの期待通りでないことに文句を言うのはやめましょう。Interprocess.mqhファイルにこのような変更を加えたので、次のポイントに進むことができます。C_ConfigService.mqhを修正します。以下のコードから始めて、いくつかの変更を加えます。
private : enum eWhatExec {eTickReplay, eBarToTick, eTickToBar, eBarPrev}; enum eTranscriptionDefine {Transcription_INFO, Transcription_DEFINE}; struct st001 { C_Array *pTicksToReplay, *pBarsToTicks, *pTicksToBars, *pBarsToPrev; int Line; }m_GlPrivate; string m_szPath; bool m_AccountHedging;
C_ConfigServiceクラスにprivate変数を追加しました。そのため、次に何を処理するかを覚えておくことができます。まず、この変数を初期化しなければなりません。これはクラスのコンストラクタでおこなわれます。
C_ConfigService() :m_szPath(NULL), m_ModelLoading(1), m_AccountHedging(false) { }
メモリとして機能する変数が必要なのは、すでに設定されたデータを受け取るグローバルターミナル変数を作成するために、コントロール指標が必要だからです。しかし、このグローバル変数が表示されるのは、テンプレートがコントロール指標をチャートにロードするときだけです。そして、チャートが表示されるのは、リプレイ/シミュレーションサービスがシステム全体の設定を終えてからです。ユーザーが指定した設定値がどこかに保存されていなければ、EAに口座タイプを伝えることはできません。ただし、注意深くプログラミングすれば、リプレイ/シミュレーションサービスで設定した口座タイプをEAに渡すことができます。そうすることで、作業の仕方を知ることができますが、自動EAを作成する方法に関する知識を使用することが前提です。
この変数はprivateであり、C_Replayクラスはグローバルターミナル変数を設定するので、口座タイプ変数にアクセスするためのC_Replayクラスメソッドが必要です。これは、以下のコードでおこなわれます。
inline const bool TypeAccountIsHedging(void) const { return m_AccountHedging; }
これは値を返します。次に、ユーザーが指定した設定をコードに取り込んで使用できるようにする必要があります。そのためには、キャプチャシステムに新しいコードを追加します。以下をご覧ください。
inline bool Configs(const string szInfo) { const string szList[] = { "PATH", "POINTSPERTICK", "VALUEPERPOINTS", "VOLUMEMINIMAL", "LOADMODEL", "ACCOUNT" }; string szRet[]; char cWho; if (StringSplit(szInfo, '=', szRet) == 2) { StringTrimRight(szRet[0]); StringTrimLeft(szRet[1]); for (cWho = 0; cWho < ArraySize(szList); cWho++) if (szList[cWho] == szRet[0]) break; switch (cWho) { case 0: m_szPath = szRet[1]; return true; case 1: CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, StringToDouble(szRet[1])); return true; case 2: CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, StringToDouble(szRet[1])); return true; case 3: CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, StringToDouble(szRet[1])); return true; case 4: m_ModelLoading = StringInit(szRet[1]); m_ModelLoading = ((m_ModelLoading < 1) && (m_ModelLoading > 4) ? 1 : m_ModelLoading); return true; case 5: if (szRet[1] == "HEDGING") m_AccountHedging = true; else if (szRet[1] == "NETTING") m_AccountHedging = false; else { Print("Entered account type is not invalid."); return false; } return true; } Print("Variable >>", szRet[0], "<< not defined."); }else Print("Configuration definition >>", szInfo, "<< invalidates."); return false; }
この新しいコードによって、どの口座を使用するべきかを知ることができます。とても簡単で、同時に機能的です。さて、構成ファイルは
[Config] Path = Forex\EURUSD PointsPerTick = 0.00001 ValuePerPoints = 1.0 VolumeMinimal = 0.01 Account = HEDGING
または
[Config] Path = Petrobras PN PointsPerTick = 0.01 ValuePerPoints = 1.0 VolumeMinimal = 100.0 Account = NETTING
のようになるでしょう。レプリケーション/シミュレーションサービスで使用できる様々な資産がありますが、ユーザーは使用する口座タイプを決めることができます。とてもシンプルでわかりやすいです。こうすることで、どのようなタイプの市場にも分析を広げることができます。私たちが知っている、あるいは私たちが開発したどんな方法でも使用することができます。C_ConfigServiceクラスに関する作業はすべて終了したので、次にC_Replayクラスにこのデータを設定させるメソッドを見てみましょう。これを実現し、ユーザーが指定した構成をリプレイ/シミュレーションシステム全体から見えるようにするために、それほど多くの作業は必要ありません。必要なことはすべて次のコードに示されています。
bool ViewReplay(ENUM_TIMEFRAMES arg1) { #define macroError(A) { Print(A); return false; } u_Interprocess info; if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE) == 0) macroError("Asset configuration is not complete, it remains to declare the size of the ticket."); if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE) == 0) macroError("Asset configuration is not complete, need to declare the ticket value."); if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP) == 0) macroError("Asset configuration not complete, need to declare the minimum volume."); if (m_IdReplay == -1) return false; if ((m_IdReplay = ChartFirst()) > 0) do { if (ChartSymbol(m_IdReplay) == def_SymbolReplay) { ChartClose(m_IdReplay); ChartRedraw(); } }while ((m_IdReplay = ChartNext(m_IdReplay)) > 0); Print("Waiting for [Market Replay] indicator permission to start replay ..."); info.ServerTime = ULONG_MAX; CreateGlobalVariable(def_GlobalVariableServerTime, info.u_Value.df_Value); info.u_Value.IdGraphic = m_IdReplay = ChartOpen(def_SymbolReplay, arg1); ChartApplyTemplate(m_IdReplay, "Market Replay.tpl"); CreateGlobalVariable(def_GlobalVariableIdGraphics, info.u_Value.df_Value); while ((!GlobalVariableCheck(def_GlobalVariableReplay)) && (!_StopFlag) && (ChartSymbol(m_IdReplay) != "")) Sleep(750); while ((!GlobalVariableGet(def_GlobalVariableReplay, info.u_Value.df_Value)) && (!_StopFlag) && (ChartSymbol(m_IdReplay) != "")) Sleep(750); info.s_Infos.isHedging = TypeAccountIsHedging(); info.s_Infos.isSync = true; GlobalVariableSet(def_GlobalVariableReplay, info.u_Value.df_Value); return ((!_StopFlag) && (ChartSymbol(m_IdReplay) != "")); #undef macroError }
コードから1行削除しました。何を扱っているかを知るために、C_Replayクラスが、コントロール指標によって配置された変数の値を読み込む必要があるからです。仮定することは禁物です。将来的には、コントロール指標が作動し始めたら、サービスに値を渡さなければならないかもしれません。この後、ユーザーによって指定された値を読み取り、システムが同期していることを報告します。その後、MetaTrader 5に値を送信し、グローバルターミナル変数を通じてデータを利用できるようにします。これでリプレイ/シミュレーションサービスに関する部分は完了ですが、まだコードを書いていません。次に、リプレイ/シミュレーションがユーザーによってどのように設定されたかを知ることができるように、EAを修正する必要があります。繰り返しになりますが、関連する説明はすべて別のトピックで考えることにします。
EAに口座タイプを認識させる
EAにユーザーカスタム口座タイプを認識させる方法を理解するためには、システム全体の仕組みを理解する必要があります。初期化プロセスがどのようにおこなわれるかを簡単に説明した図01を見てみましょう。
図01:リプレイ/シミュレーションシステムの初期化プロセス
なぜこの数字を理解することが重要なのでしょうか。C_Terminalクラスはコントロール指標とEAの共通クラスだからです。これを理解しなければ、MetaTrader 5でシステムがユーザー設定に従うことができる方法と理由を理解することができません。しかも、この初期化を理解していないと、リプレイ/シミュレーションプロセスを設定するファイルを編集して、バカなことをやってしまう可能性があります。それでシステム全体の動作がおかしくなります。そのため、実際の市場に近い経験を得ることができる代わりに、リプレイ/シミュレーションのプロセスを通じて学んだことを実際に使用する際に、どのように行動すべきかという誤った印象を得る可能性があります。よって、最初の数字が何であるかを理解することは義務です。
- すべての青い矢印は、初期化の第一段階を示しています。これは、ユーザーとしてMetaTrader 5でサービスを起動した瞬間に起こります。
- この後、サービスがチャートを作成し、テンプレートを使用してEAとコントロール指標を初期化するときに実行される第2段階に進みます。これは黄色の矢印で示されています。
- 緑色の矢印で示された次のステップは、コントロール指標を使用してグローバルターミナル変数を作成し、この変数をリプレイ/シミュレーションサービスに設定することです。この時点で、C_Terminalクラスはコントロール指標にデータを送信していますが、現在EAによって初期化されているC_Managerクラスには送信していないことがわかります。
- 最後のステップ(紫色の矢印)は、リプレイ/シミュレーションがどのように機能するかを指定するファイルで、ユーザーによって提供されたデータからの設定です。EAは、使用されている口座タイプを知るために、この情報を必要とします。
難しく思えるかもしれませんが、C_Terminalクラスがコントロール指標に作業させるように細心の注意を払わなければなりません。同時に、C_Managerクラスが適切に設定される前にEAに何もさせてはなりません。なぜ、管理はC_Managerクラスではなく、C_Terminalクラスでおこなうべきだと言うのでしょうか。C_Managerクラスでおこなう方が簡単かもしれません。これは可能です。その理由は、単純にどのクラスに物事を格納するかを選択するためではなく、本当の理由はC_Ordersクラスにあります。コントロールシステムがC_Managerクラスにある場合、C_Ordersクラスは必要なデータにアクセスできません。コントロールをC_Terminalクラスに置いたので、後で別のクラスを使用して注文を管理する場合でも、必要なデータにアクセスすることができます。図01に示すように、適当に選んで実施することはできません。それがすべて正しくおこなわれていることを確認する必要があります。さもないと、起動直後にシステム全体が故障する可能性があります。理解するのは難しいし、ショックを受けるかもしれませんが、プログラミングが十分に厳密におこなわれなければ、システムは失敗します。では、どのようなシステムになっているのか見てみましょう。次のコードから始めましょう。
class C_Terminal { protected: enum eErrUser {ERR_Unknown, ERR_PointerInvalid}; enum eEvents {ev_Update}; //+------------------------------------------------------------------+ struct st_Terminal { ENUM_SYMBOL_CHART_MODE ChartMode; ENUM_ACCOUNT_MARGIN_MODE TypeAccount; long ID; string szSymbol; int Width, Height, nDigits; double PointPerTick, ValuePerPoint, VolumeMinimal, AdjustToTrade; }; //+------------------------------------------------------------------+ private : st_Terminal m_Infos; struct mem { long Show_Descr, Show_Date; bool AccountLock; }m_Mem;
この変数は、この記事ですでに実装されているメソッドを使用して、口座タイプへのアクセスを提供します。この情報を得るためにクラスに新しいコードを追加する必要はありません。しかし、もう1つの変数もあります。これはLOCKの役割を果たしますが、その理由と使い方はすぐに理解できるでしょう。クラスのコンストラクタを見てみましょう。
C_Terminal() { m_Infos.ID = ChartID(); m_Mem.AccountLock = false; CurrentSymbol(); m_Mem.Show_Descr = ChartGetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR); m_Mem.Show_Date = ChartGetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE); ChartSetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR, false); ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_DELETE, 0, true); ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, 0, true); ChartSetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE, false); m_Infos.nDigits = (int) SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_DIGITS); m_Infos.Width = (int)ChartGetInteger(m_Infos.ID, CHART_WIDTH_IN_PIXELS); m_Infos.Height = (int)ChartGetInteger(m_Infos.ID, CHART_HEIGHT_IN_PIXELS); m_Infos.PointPerTick = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_SIZE); m_Infos.ValuePerPoint = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_VALUE); m_Infos.VolumeMinimal = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_VOLUME_STEP); m_Infos.AdjustToTrade = m_Infos.ValuePerPoint / m_Infos.PointPerTick; m_Infos.ChartMode = (ENUM_SYMBOL_CHART_MODE) SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_CHART_MODE); if(m_Infos.szSymbol != def_SymbolReplay) SetTypeAccount((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE)); ResetLastError(); }
ここでは、C_Terminalクラスに口座タイプの値がまだ実装されていないことを示すために、lock変数を初期化していますが、対策はとります。これはとても重要です。資産が再生資産でないことをシステムが検出したら、C_Terminalクラスは口座データを初期化する必要があります。再生資産である場合、データは今初期化されるのではなく、後のC_Managerクラスで初期化されます。これはすぐに非常に重要になるため、理解する必要があります。C_Managerクラスは、資産が再生資産である場合にデータを初期化します。資産が再生資産でない場合は、この段階で初期化されます。次に、次のメソッド呼び出しがあります。
inline void SetTypeAccount(const ENUM_ACCOUNT_MARGIN_MODE arg) { if (m_Mem.AccountLock) return; else m_Mem.AccountLock = true; m_Infos.TypeAccount = (arg == ACCOUNT_MARGIN_MODE_RETAIL_HEDGING ? arg : ACCOUNT_MARGIN_MODE_RETAIL_NETTING); }
このメソッドは非常に重要です。これがなければ、使用されている口座タイプは不明となります。この場合、この型か別の型が使用されていると仮定することになるからです。そして、一度特定の口座タイプで始めたら、変数の値を変えないことが重要です。このロックを使用するのはそのためです。ロック変数がfalseに設定されている場合のみ、メソッドに渡された引数に基づいてその値を初期化することができます。この後、コードの実行が終わるまで、変数は変更できません。もう1つの詳細は、NETTINGタイプとHEDGINGタイプに限定するということです。EXCHANGEがNETTINGタイプと同じ動作をするからです。私たちは実際の市場に非常に近い取引システムを使用しているので、NETTINGタイプとHEDGINGタイプに限定することに問題はないと思います。これでC_Terminalクラスのコード部分は完成です。では、C_Managerクラスのどこを変更する必要があるのか見てみましょう。このC_Managerクラスでは、コンストラクタのコードを変更するだけでよかったのです。これで次のようになります。
C_Manager(C_Terminal *arg1, C_Study *arg2, color cPrice, color cStop, color cTake, const ulong magic, const double FinanceStop, const double FinanceTake, uint Leverage, bool IsDayTrade) :C_ControlOfTime(arg1, magic) { string szInfo = "HEDGING"; u_Interprocess info; Terminal = arg1; Study = arg2; if (CheckPointer(Terminal) == POINTER_INVALID) SetUserError(C_Terminal::ERR_PointerInvalid); if (CheckPointer(Study) == POINTER_INVALID) SetUserError(C_Terminal::ERR_PointerInvalid); if (_LastError != ERR_SUCCESS) return; m_Infos.FinanceStop = FinanceStop; m_Infos.FinanceTake = FinanceTake; m_Infos.Leverage = Leverage; m_Infos.IsDayTrade = IsDayTrade; m_Infos.AccountHedging = false; m_Objects.corPrice = cPrice; m_Objects.corStop = cStop; m_Objects.corTake = cTake; m_Objects.bCreate = false; if (def_InfoTerminal.szSymbol == def_SymbolReplay) { do { while ((!GlobalVariableGet(def_GlobalVariableReplay, info.u_Value.df_Value)) && (!_StopFlag)) Sleep(750); }while ((!info.s_Infos.isSync) && (!_StopFlag)); def_AcessTerminal.SetTypeAccount(info.s_Infos.isHedging ? ACCOUNT_MARGIN_MODE_RETAIL_HEDGING : ACCOUNT_MARGIN_MODE_RETAIL_NETTING); }; switch (def_InfoTerminal.TypeAccount) { case ACCOUNT_MARGIN_MODE_RETAIL_HEDGING: m_Infos.AccountHedging = true; break; case ACCOUNT_MARGIN_MODE_RETAIL_NETTING: szInfo = "NETTING"; break; } Print("Detected Account ", szInfo); }
何も変わっていないと思うかもしれません。もっと悪いことに、何が起こっているのか理解できないかもしれません。ここでは、ある条件下で無限ループに陥る可能性のあるループがあるからです。ただし、コードの大部分は同じままですが、小さな違いが1つあり、まさにこの点にあります。前回までは、ここですべての口座タイプを確認しました。2つだけ残します。そして、口座タイプを知るための情報は、C_Terminalクラスに作られた変数にあります。しかし、コードの本当に新しい部分を詳しく見てみましょう。使用されている資産がリプレイ/シミュレーションシステム内のものと同じであると判断された時点で開始されます。確認が通れば、外側のループと、この最初のループの中に入れ子になったもう1つのループがある、二重ループになります。
このネストされたループでは、コントロール指標がグローバルターミナル変数を作成するのを待ち、リプレイ/シミュレーションサービスがそれを設定できるようにします。ユーザーによってプログラムが終了されるか、コントロール指標がグローバル終端変数を作成すると、このネストされたループを抜けて外側のループに入ります。1つは、リプレイ/シミュレーションサービスがグローバルなターミナル変数を設定した場合、もう1つは、ユーザーがプログラムを終了した場合です。これら2つの状況以外では、ループは終了せず、ネストされたループに再び入ります。すべてがうまくいき、外側のループが終了すれば、口座タイプの値がC_Terminalクラスに送られます。
結論
この記事では、些細でありながら将来多くの頭痛の種を引き起こす可能性のある問題を解決しました。これらの変更の重要性、そして最も重要なこととして、MQL5を使用してどのように問題を解決したかを理解していただけたと思います。添付ファイルには、ソースコードと、リプレイ/シミュレーションで使用するための更新されたファイルが含まれています。すでにこのサービスを使用している場合は、まだ分析に使用できないとしても、適切な口座タイプを使用するように、リプレイ/シミュレーションサービスの設定を受け持つファイルも更新してください。そうしないと、デフォルトのHEDGING口座タイプが使用されます。
将来の問題につながる可能性があるので、必ず更新をお願いします。
MetaQuotes Ltdによりポルトガル語から翻訳されました。
元の記事: https://www.mql5.com/pt/articles/11510
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索