English Русский 中文 Español Deutsch Português
preview
自動で動くEAを作る(第12回):自動化(IV)

自動で動くEAを作る(第12回):自動化(IV)

MetaTrader 5トレーディング | 29 5月 2023, 10:54
382 0
Daniel Jose
Daniel Jose

はじめに

前回の「自動で動くEAを作る(その11):自動化(III)」稿では、プログラムに影響を与える可能性のある不具合や抜け穴を最小限に抑え、堅牢なシステムを構築する方法について検討しました。

たまに「50万分の1の確率で何かが起こる」などということがありますが、たとえ確率が低くても、可能性はあるわけです。このようなことが起こりうることを認識しているのですから、万が一の場合に備えて、被害や副作用を最小限に抑える手段を作っておくべきです。可能性が低いからと言って、起こるかもしれないことを無視して、修正したり何らかの方法で防いだりしないのはいかがなものかとおもいます。

EAを自動化する方法に関するこの小さな連載をご覧になっている方は、手動で使用するEAを作成することが非常に速く、簡単なことであることにお気づきだと思います。しかし、100%自動化されたEAの場合、そう簡単にはいきません。お気づきの方もいらっしゃるかもしれませんが、私は「監視の目を通さずに使える100%フールプルーフのシステム」という考えを示したわけではありません。実際、多くの人が自動EAに対して抱いている、「電源を入れれば、何をやっているのかよくわからないまま放置できる」という前提とは全く逆のことが、かなり明確になったと思っています。

EAを100%自動化するとなると、すべてが深刻で複雑になります。ランタイムエラーが必ず発生し、またリアルタイムで動作するシステムを作らなければならないため、なおさらです。この2つが重なると、システムに何らかの不具合が生じる可能性もあり、EAの運用を監督する側としては、非常に疲れる仕事となります。

ただし、プログラマーとしては、すべてが完璧に調和しているように見えても、潜在的な問題を発生させる可能性があるいくつかのポイントに常に目を向ける必要があります。存在しないかもしれない問題を探せということではありません。これは、注意深いプロフェッショナルが実際におこなうことです。一見すると欠点がないシステムの欠点を探すのです。

手動および半自動での使用(ブレークイーブンとトレーリングストップを使用)を目的とした、現在の開発段階における私たちのEAには、ブロックバスター(DCスーパーヴィラン)のような破壊的なバグはありません。しかし、100%自動で使うとなると、状況は一変し、潜在的に危険な不具合が発生する危険性があります。

前回の記事で、私はこの問題を提起し、この欠陥がどこにあり、どのような問題を引き起こす可能性があるのか、まだ100%EAを自動化することができないことについての理解は読者に任せました。欠陥がどこで、どういったきっかけでトリガーしたのかを把握することはできたのでしょうか。答えが「いいえ」でも大丈夫です。コードを見て手動や半自動で使うだけで、実際に欠陥に気づける人はそういません。ただし。コードを自動化しようとすると、深刻な問題が発生します。この欠陥は、確かに最も単純なものですが、100%自動化されたEAに必要とされる修正にはそれほど単純ではありません。

そこで、その内容を理解するために、物事をトピックごとに分けて考えてみましょう。一見どうでもいいようなことが大きな問題になっていることに気づきやすくなると思います。


問題の把握

問題は、EAが1日に取引できる最大数量の制限を設定したときから始まります。この1日最大数量と操作数量を混同しないでください。ここで私たちが主に関心を寄せているのは、1日の最大数量です。

わかりやすくするために、最小数量の100倍の数量があると仮定します。つまり、この数量に達するまで、EAは可能な限り多くの演算をおこなうことができるようになるのです。そこで、C_Managerクラスで追加された最後のルールは、数量がこの100を超えないようにすることです。

では、実際に何が起こるのか見てみましょう。そのために、取引をおこなうためのコードを解析していきます。

inline bool IsPossible(const bool IsPending)
                        {
                                if (!CtrlTimeIsPassed()) return false;
                                if ((m_StaticLeverage >= m_InfosManager.MaxLeverage) || (m_bAccountHedging && (m_Position.Ticket > 0))) return false;
                                if ((IsPending) && (m_TicketPending > 0)) return false;
                                if (m_StaticLeverage + m_InfosManager.Leverage > m_InfosManager.MaxLeverage)
                                {
                                        Print("Request denied, as it would violate the maximum volume allowed for the EA.");
                                        return false;
                                }
                                
                                return true;
                        }

上記のコードにより、数量を超えることはありません。でも、実際は何が起こっているのでしょうか。

例えば、トレーダーがロット倍率を最低必要量の3倍にしてEAを起動し、EAコードで定義された最大数量が100倍だとします(これはコードのコンパイル時におこなわれます)。これについては、以前の記事で説明しました。33回取引すると、EAは最低取引量の99倍に達するので、あと1回取引できることになります。しかし、上記のコードで強調されている行のため、トレーダーは最大限度額に到達するために1回だけ数量を変更する必要があります。さもないと、EAが操作を実行することができません。

アイデアは、EAが事前に指定されたパラメータよりも多くを失うことがないように、最大数量を制限することです。これは常に主要かつ最重要な関心事でなければなりません。EAが規定よりはるかに高い数量でポジションを開かなければ、損失は発生しますが、何とか抑えることができます。

読者はこのコードに欠陥がないと思われるかもしれません。その通り、このコードに欠陥はありません。これを利用した関数(以下)は、EAが取引する数量を指定した上限値までに制限することができます。

//+------------------------------------------------------------------+
                void CreateOrder(const ENUM_ORDER_TYPE type, const double Price)
                        {
                                if (!IsPossible(true)) return;
                                m_TicketPending = C_Orders::CreateOrder(type, Price, (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceStop), (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceTake), m_InfosManager.Leverage, m_InfosManager.IsDayTrade);
                        }
//+------------------------------------------------------------------+  
                void ToMarket(const ENUM_ORDER_TYPE type)
                        {
                                ulong tmp;
                                
                                if (!IsPossible(false)) return;
                                tmp = C_Orders::ToMarket(type, (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceStop), (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceTake), m_InfosManager.Leverage, m_InfosManager.IsDayTrade);
                                m_Position.Ticket = (m_bAccountHedging ? tmp : (m_Position.Ticket > 0 ? m_Position.Ticket : tmp));
                        }
//+------------------------------------------------------------------+

では、どこに間違いがあるのでしょうか。実は、そんなに簡単に理解できるものではありません。この問題を理解するためには、EAが注文を送信しないように動作している可能性があることを考える必要があります。なぜなら、C_ManagerクラスのこのコードはEAがトリガーされないようにするためです。問題は、異なる注文タイプを組み合わせたときに起こります。この時点で、ブロック潰しの問題のトリガーができることになります。このようなトリガーを制限し、回避する方法があります。その内容は以下の通りです。

  • 注文送信タイプまたはモデルを選択します。この場合、EAは成行注文または指値注文にのみ依存することになります。自動売買のモデルによっては、市場のみで運用することがより適切でより一般的である場合があるため、このようなソリューションが適していますが、この場合、取引システムの種類は限られてしまいます。しかし、ここでのアイディアは、どの注文タイプを使うかを気にすることなく、できるだけ多くのケースをカバーできるシステムを作る方法を示したいということです。
  • トリガーを回避するもう1つの方法は、何がすでにポジションとして入力され、何がポジションに変更を加えるのか(この場合、指値注文)を深く考慮することです。この解決策はより良い選択ですが、プログラミングが複雑になるという悪化要因があります。
  • もう 1 つの方法(これから実装する予定)は、すでに開発されているシステムのベースを使用する方法です。これにより、何がおこなわれているかを説明しますが、何ができるかについては想定しません。ただし、それでも何とか1日の全期間での取引量をEAに予測させるようにします。こうすることで、EAがサポートできる取引システムの種類を制限することなく、トリガーを持つことを避けることができます。

さて、注文の組み合わせで問題が起こるということを踏まえて、実際にどのようなことが起こるのかを理解しましょう。ロット倍率が3の例に戻ると、1日の上限が100回であるにもかかわらず、すでに33回の取引がおこなわれているため、EAが成行注文のみで動作していれば、すべてが完璧にコントロールされていることになります。しかし、何らかの理由でトレードサーバーに指値注文がある場合は、状況が異なります。この指値注文が最小数量にさらに3ユニット追加された場合、トリガーと同時にEAで許容される最大数量を超えたことになります。

100の制限を超えたこの2単位の数量は大したことないと想像できますが、そんな単純なものではありません。トレーダーがEAを50ロットで取引するように設定した場合を考えてみましょう。100を超える成行注文を2回実行することができます。成行注文を出した後、EAはポジション量を増やすために指値注文を出します。これで、数量100に到達したことになります。しかし、この注文はまだ未決のため、何らかの理由で実行されていません。ある瞬間に、EAは同じ50ロットの数量の別の成行注文を送ることができると判断します。

C_Managerクラスは、EAが100ユニットとなるため、1日の上限に達していることにすぐに気づきます。しかし、C_Managerクラスは、指値注文があることを認識しているにもかかわらず、それをどうすればいいのかがよくわかりません。この場合、サーバー上で注文が実行されると、EAは100ルールを超え、150の数量を持つことになります。問題がおわかりでしょうか。しばらく前に、EAがブックに多くの注文をぶら下げたり、特定の数量で多くのポジションを開くことを防ぐために、ロックをかけました。このブロックは、EAを自動化するトリガーが、このようなことが起こることを予見していなかったという単純な事実によって破られたのです。C_Managerクラスには、EAを保持し、定義された最大数量を超えないようにすることが想定されていましたが、指値注文と成行注文を組み合わせてしまったため、失敗してしまったのです。 

多くのプログラマーは、成行注文のみまたは指値注文のみを使用することでこの問題を回避していますが、これで問題がなくなるわけではありません。トリガーを避けるために、新しい自動EAはそれぞれ同じテストと分析モードを通過する必要があります。

上記のような問題は、手動でシステムを使用する場合にも見られますが、トレーダーがこの間違いを犯す確率はかなり低くなっています。トレーダーが悪いのであって、保護システムが悪いわけではないでしょう。しかし、100%自動化されたシステムにとって、これは全く受け入れがたいことです。 このような理由から、100%自動化されたEAを、たとえ自分でプログラムしたものであっても、監視なしに放置することは絶対に避けてください。優秀なプログラマーであっても、それを放置することは決してお勧めできません。

ナンセンスだと思うなら、次のことを考えてみてください。飛行機には自動操縦システムがあり、人の手を煩わせることなく離陸、航路の移動、着陸が可能ですが、それでも機内には必ず有資格者のパイロットがいて、飛行機を操作しています。なぜ、このようなことが起こるのでしょうか。産業界が自動操縦装置を開発するために巨額の資金を費やし、操縦するパイロットを訓練しなければならないようなことはしないでしょう。これでは、業界自体が自動操縦に頼らない限り、意味がありません。少し考えてみてください。


クラッシュを修正する

実に、エラーは表示するだけでは直らないのです。もう少し何かが必要です。ただし、失敗を知り、それがどのように引き起こされるのか、その結果を理解しているということは、実際に何らかの解決策を生み出そうとすることができるということです。

C_Managerクラスが数量に違反しないようにすることを信頼していることが、すべての違いです。この問題を解決するために、コードにいくつかの工夫が必要です。まず、システムに新しい変数を追加します。

                struct st01
                {
                        ulong   Ticket;
                        double  SL,
                                TP,
                                PriceOpen,
                                Gap;
                        bool    EnableBreakEven,
                                IsBuy;
                        uint    Leverage;
                }m_Position, m_Pending;
                ulong   m_TicketPending;

この新しい変数はある種の予測を生み出すことで、数量の問題を解決するのに役立ちます。その登場から、コードのもう一点が削除されました。

新しい変数の追加には一連の変化が続きますが、あくまで新しい部分を強調します。添付のコードで、すべての改造をより詳細にご覧になれます。まず、指値注文のデータを取得するルーチンを作成します。

inline void SetInfoPending(void)
                        {
                                ENUM_ORDER_TYPE eLocal = (ENUM_ORDER_TYPE) OrderGetInteger(ORDER_TYPE);
                                
                                m_Pending.Leverage = (uint)(OrderGetDouble(ORDER_VOLUME_CURRENT) / GetTerminalInfos().VolMinimal);
                                m_Pending.IsBuy = ((eLocal == ORDER_TYPE_BUY) || (eLocal == ORDER_TYPE_BUY_LIMIT) || (eLocal == ORDER_TYPE_BUY_STOP) || (eLocal == ORDER_TYPE_BUY_STOP_LIMIT));
                                m_Pending.PriceOpen = OrderGetDouble(ORDER_PRICE_OPEN);
                                m_Pending.SL = OrderGetDouble(ORDER_SL);
                                m_Pending.TP = OrderGetDouble(ORDER_TP);
                        }

ポジションデータを取得するルーチンとは異なります。大きく違うのは、買うか売るかを確認することです。これは、買い注文を示すタイプの可能性を確認することでおこなわれます 。残りは自明です。

あと1つ新しい関数が必要です。

                void UpdatePending(const ulong ticket)
                        {
                                if ((ticket == 0) || (ticket != m_Pending.Ticket) || (m_Pending.Ticket == 0)) return;
                                if (OrderSelect(m_Pending.Ticket)) SetInfoPending();
                        }

指値注文がサーバーから何らかの更新を受けたときにデータを更新し、その情報をEAに渡します。

EAが上記の呼び出しを実行できるようにするために、OnTradeTransactionハンドラに新しいイベントを追加する必要があります。

void OnTradeTransaction(const MqlTradeTransaction &trans, const MqlTradeRequest &request, const MqlTradeResult &result)
{
        switch (trans.type)
        {
                case TRADE_TRANSACTION_POSITION:
                        manager.UpdatePosition(trans.position);
                        break;
                case TRADE_TRANSACTION_ORDER_DELETE:
                        if (trans.order == trans.position) (*manager).PendingToPosition();
                        else (*manager).UpdatePosition(trans.position);
                        break;
                case TRADE_TRANSACTION_ORDER_UPDATE:
                        (*manager).UpdatePending(trans.order);
                        break;
                case TRADE_TRANSACTION_REQUEST: if ((request.symbol == _Symbol) && (result.retcode == TRADE_RETCODE_DONE) && (request.magic == def_MAGIC_NUMBER)) switch (request.action)
                        {
                                case TRADE_ACTION_DEAL:
                                        (*manager).UpdatePosition(request.order);
                                        break;
                                case TRADE_ACTION_SLTP:
                                        (*manager).UpdatePosition(trans.position);
                                        break;
                                case TRADE_ACTION_REMOVE:
                                        (*manager).EraseTicketPending(request.order);
                                        break;
                        }
                        break;
        }
}

上記の強調表示された行はC_Manager クラスを呼び出します

では、C_Managerクラスに戻り、数量問題の解決策を引き続き実装してみましょう。

より十分なセキュリティレベルに持っていけるようにするためには、本項で紹介するような補正システムを作る必要があります。気づいてから長い間無視していたエラーは、オープン数量の更新を担うものです。手動なら影響ありませんが、自動式では致命的です。このエラーを修正するには、次の行を追加する必要があります

inline void LoadPositionValid(void)
                        {
                                ulong value;
                                
                                for (int c0 = PositionsTotal() - 1; (c0 >= 0) && (_LastError == ERR_SUCCESS); c0--)
                                {
                                        if ((value = PositionGetTicket(c0)) == 0) continue;
                                        if (PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
                                        if (PositionGetInteger(POSITION_MAGIC) != GetMagicNumber()) continue;
                                        if ((m_bAccountHedging) && (m_TicketPending > 0))
                                        {
                                                C_Orders::ClosePosition(value);
                                                continue;
                                        }
                                        if (m_Position.Ticket > 0) SetUserError(ERR_Unknown); else
                                        {
                                                m_Position.Ticket = value;
                                                SetInfoPositions();
                                                m_StaticLeverage = m_Position.Leverage;
                                        }
                                }
                        }

全自動システムを設計するのは難しい課題です。手動や半自動では、上記の行がなくても、さしたる違いはありませんが、自動化されたシステムでは、どんなに小さな障害も大惨事になる可能性がなります。トレーダーがEAを監督していない場合や、EAが実際に何をしているのか知らない場合はなおさらです。これでは、間違いなく市場で損をすることになります。

次に、注文送信関数とマーケットリクエスト関数を変更します。呼び出し側に値を返すと同時に、何が起こっているのかを知らせることができる必要があります。コードは次のとおりです。

//+------------------------------------------------------------------+
                bool CreateOrder(const ENUM_ORDER_TYPE type, const double Price)
                        {
                                bool bRet = false;
                                
                                if (!IsPossible(true)) return bRet;
                                m_Pending.Ticket = C_Orders::CreateOrder(type, Price, (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceStop), (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceTake), m_InfosManager.Leverage, m_InfosManager.IsDayTrade);
                                if (m_Pending.Ticket > 0) bRet = OrderSelect(m_Pending.Ticket);
                                if (bRet) SetInfoPending();
                                
                                return bRet;
                        }
//+------------------------------------------------------------------+  
                bool ToMarket(const ENUM_ORDER_TYPE type)
                        {
                                ulong tmp;
                                bool bRet = false;
                                
                                if (!IsPossible(false)) return bRet;
                                tmp = C_Orders::ToMarket(type, (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceStop), (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceTake), m_InfosManager.Leverage, m_InfosManager.IsDayTrade);
                                m_Position.Ticket = (m_bAccountHedging ? tmp : (m_Position.Ticket > 0 ? m_Position.Ticket : tmp));
                                if (m_Position.Ticket > 0) bRet = PositionSelectByTicket(m_Position.Ticket);
                                if (!bRet) ZeroMemory(m_Position);
                                
                                return bRet;
                        }
//+------------------------------------------------------------------+

チケットがまだ有効かどうかを確認するためにテストを使っています。 ネッティング口座では、ポジションと同じ数量を送ることでポジションを決済することができるからです。この場合、ポジションが決済された場合、システムのセキュリティと信頼性を高めるために、そのポジションからデータを削除する必要があります。これは100%自動化されたEAの場合であり、手動EAではこのようなことは不要です。

次に、EAが送信する数量を知るための方法を追加する必要があります。現在のシステムでポジションを逆転させることは可能ですが、1回のリクエストではなく、2回の呼び出しというか、サーバーへのリクエスト送信が必要になります。現在は、ポジションを決済してから、新しいポジションを開くリクエストを送信する必要があります。オープン数量を知ることで、EAはどの数量を送信すべきかを知ることができます。

const uint GetVolumeInPosition(void) const
                        {
                                return m_Position.Leverage;
                        }

これは、上の簡単なコードで十分実装できますが、クラスのコードには実際にポジションを反転させる方法がありません。

これを実現するために、今回も注文送信関数を変更します。ただ、これは手動やまたは半自動のEAには必要ないものです。このような変更をおこなうのは、自動化されたEAを作成するために、これらの手段が必要であるためです。それに、EAを監修しているトレーダーが、EAが実際に何をしているのかを知るために、ところどころに何らかのメッセージを入れておくのも面白いとおもいます。デモコードでやっていないとはいえ、そのようなことをすることを真剣に考えるべきでしょう。というのも、私たちは盲目同然で、EAのおかしな挙動に気づくには、ただチャートを見ているだけでは不十分だからです。

これらすべての変更を実装した後、私たちが扱っている問題を実際に解決する重要なポイントに到達しました。EAが任意の数量をサーバーに送信できるようにする方法を追加しました。これは、C_ManagerクラスがEAへのアクセスを与える2回の呼び出しでおこなわれるだけです。こうして、EAがコードで示された以上の取引量を使用してしまう問題を解決しています。ここで、これから配置される、あるいは既に配置されているポジションをエントリまたはエグジットする可能性のある数量の予測を開始することにします。

新しい呼び出しコードは以下です。

//+------------------------------------------------------------------+
                bool CreateOrder(const ENUM_ORDER_TYPE type, const double Price, const uint LeverageArg = 0)
                        {
                                bool bRet = false;
                                
                                if (!IsPossible(type, (LeverageArg > 0 ? LeverageArg : m_InfosManager.Leverage))) return bRet;
                                m_Pending.Ticket = C_Orders::CreateOrder(type, Price, (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceStop), (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceTake), m_InfosManager.Leverage, m_InfosManager.IsDayTrade);
                                if (m_Pending.Ticket > 0) bRet = OrderSelect(m_Pending.Ticket);
                                if (bRet) SetInfoPending();
                                
                                return bRet;
                        }
//+------------------------------------------------------------------+  
                bool ToMarket(const ENUM_ORDER_TYPE type, const uint LeverageArg = 0)
                        {
                                ulong tmp;
                                bool bRet = false;
                                
                                if (!IsPossible(type, (LeverageArg > 0 ? LeverageArg : m_InfosManager.Leverage))) return bRet;
                                tmp = C_Orders::ToMarket(type, (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceStop), (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceTake), m_InfosManager.Leverage, m_InfosManager.IsDayTrade);
                                m_Position.Ticket = (m_bAccountHedging ? tmp : (m_Position.Ticket > 0 ? m_Position.Ticket : tmp));
                                if (m_Position.Ticket > 0) bRet = PositionSelectByTicket(m_Position.Ticket);
                                if (!bRet) ZeroMemory(m_Position);
                                
                                return bRet;
                        }
//+------------------------------------------------------------------+

このコードでは、その引数を追加しましたが、そのデフォルト値はゼロです。 関数宣言時に値を指定しなかった場合、コンストラクタ呼び出し時に指定された値が使用されます。

しかし、C_Managerクラスが何を実行するか、何を許可するかの分析は、指値注文と成行注文の実行要求の両方に共通しているので、このステップでは何も処理されません。EAがC_Managerによって有効になると予想される値を知るために、三項演算子を使って値を正しく入力する小さなテストをおこないます。では、テストを正しく設定するために、関数に何を追加する必要があるか見てみましょう。

inline bool IsPossible(const ENUM_ORDER_TYPE type, const uint Leverage)
                        {
                                int i0, i1;
                                
                                if (!CtrlTimeIsPassed()) return false;
                                if ((m_StaticLeverage >= m_InfosManager.MaxLeverage) || (m_bAccountHedging && (m_Position.Ticket > 0))) return false;
                                if ((m_Pending.Ticket > 0) || (Leverage > INT_MAX)) return false;
                                i0 = (int)(m_Position.Ticket == 0 ? 0 : m_Position.Leverage) * (m_Position.IsBuy ? 1 : -1);
                                i1 = i0 + ((int)(Leverage * (type == ORDER_TYPE_BUY ? 1 : -1)));
                                if (((i1 < i0) && (i1 >= 0) && (i0 > 0)) || ((i1 > i0) && (i1 <= 0) && (i0 < 0))) return true;
                                if ((m_StaticLeverage + MathAbs(i1)) > m_InfosManager.MaxLeverage)
                                {
                                        Print("Request denied, as it would violate the maximum volume allowed for the EA.");
                                        return false;
                                }
                                
                                return true;
                        }

リクエストが拒否されないようにするために、トリガーシステムのプログラミングで注意すべきことがあります。指値注文がある場合は、数量を変更する注文を実行する前に、その注文を削除する必要があります。さもないと、リクエストは拒否されます。なぜでしょうか。それは絶対におかしいと思います。本当に説明に値します。

その理由は、EAが指値注文を実行させるかどうかを実際に知る方法がないということです。しかし、オープン数量が変更され、システムがポジションを停止する方法として指値注文を使用する場合、数量を変更する前にこの注文を削除することが重要です。いずれにせよ、その注文は削除され、正しい数量を含む新しい注文がおこなわれるようになります。

つまり、C_Managerクラスがオープン数量を変更する際に、指値注文がないことを要求することは明らかです。もう1つの理由は、ロングポジションがあり、それを反転させる場合、オープン数量より大きな数量で注文を出すことによっておこなわれるということです。当初は逆指値注文であった指値注文が存在し続ける場合、注文が実行されたときに問題が発生する可能性があります。数量はさらに増えます。これが、C_Managerが数量を変更する際に、指値注文がないことを要求するもう1つの理由です。

数量を変更する際に、なぜ指値注文を削除する必要があるのか、これでお分かりいただけたかと思います。EAがサーバーにリクエストを送信しなくなるため、この行を削除しないでください。

ここで、かなり奇妙な計算と、それよりさらに奇妙なテストがあり、実際に計算を理解せずに理解しようとすると、とんでもない頭痛に見舞われることになります。そして最後には、一見すると意味がわからないテストがもう1つあります。

この完全な数学的狂気を誰もが実際に理解できるように、この瞬間を注意深く分析してみましょう。簡単に説明するために、いくつかの例を見てみましょう。例題を読むときは、十分注意して、強調表示されたコードをすべて理解できるようにしてください。

例1:

ポジションがない場合、i1値はLeverage変数に含まれる絶対値と等しくなります。これが最も単純なケースです。ポジションを開くか、オーダーブックに注文を出すかは問題ではありません。i1とEAの蓄積値の合計が指定された最大値より小さい場合、サーバーにリクエストを送信します。

例2:

数量Xのショートポジションがあり、指値注文がないとします。この状況では、いくつかの異なるシナリオを用意することができます。

  • EA が数量Yの売り注文を送信した場合、i1値はXとYの合計になります。この場合、ショートポジションが増加するため、注文が約定されない可能性があります。
  • EAが数量Yの買い注文を出し、それが数量Xより少ない場合、i1値はXとYの差に等しくなります。この場合、ショートポジションが減るので注文は許可されます。
  • EAの注文が数量Yの買いであり、YがXと等しい場合、i1はゼロになります。この場合、ショートポジションが決済されるため、注文は許可されます。
  • もしEAが数量Yの買い注文を出し、それが数量Xより大きい場合、i1値はXとYの差に等しくなります。この場合、ポジションがロングにするため、注文は許可されないかもしれません。

ロングポジションの場合も同様です。しかし、EAの要求も変更されるため、最終的にはi1変数で示される同じ動作をすることになります。なお、i1とEAで蓄積した値の合計が許容値以下かどうかを確認するテストでは、MathAbs関数を呼び出しています。場合によっては、負のi1が発生するが、テストを正しく実行するためには、正であることが必要だからです。

ただ、まだ最後の問題が残っています。ポジションを反転させると、取引量の更新が正しくおこなわれなくなります。これを解決するために、解析システムを少し変更する必要があります。この変更点を以下に示します。

inline int SetInfoPositions(void)
                        {
                                double v1, v2;
                                uint tmp = m_Position.Leverage;
                                bool tBuy = m_Position.IsBuy;
                                
                                m_Position.Leverage = (uint)(PositionGetDouble(POSITION_VOLUME) / GetTerminalInfos().VolMinimal);
                                m_Position.IsBuy = ((ENUM_POSITION_TYPE) PositionGetInteger(POSITION_TYPE)) == POSITION_TYPE_BUY;
                                m_Position.TP = PositionGetDouble(POSITION_TP);
                                v1 = m_Position.SL = PositionGetDouble(POSITION_SL);
                                v2 = m_Position.PriceOpen = PositionGetDouble(POSITION_PRICE_OPEN);
                                if (m_InfosManager.IsOrderFinish) if (m_Pending.Ticket > 0) v1 = m_Pending.PriceOpen;
                                m_Position.EnableBreakEven = (m_InfosManager.IsOrderFinish ? m_Pending.Ticket == 0 : m_Position.EnableBreakEven) || (m_Position.IsBuy ? (v1 < v2) : (v1 > v2));
                                m_Position.Gap = FinanceToPoints(m_Trigger, m_Position.Leverage);

                                return (int)(tBuy == m_Position.IsBuy ? m_Position.Leverage - tmp : m_Position.Leverage);
                        }

まず、更新前のシステムのポジションを保存します。その後、最終的にポジションの方向に変化があったかを確認するまでは、何も干渉しません。 アイディアは、ロングのままなのかショートにするのかを確認することです。逆の場合も同様です。変更があった場合は、オープン数量を返します。そうでない場合は、現在どの数量が開いているかを知るために、通常通り計算をおこないます。こうすることで、EAがすでに取引した数量を適切に把握し、更新することができます。


最終的な結論

自動化されたシステムで問題が起きないようにする必要があるため、現在のシステムはより複雑になっていますが、トレーダーがEAの動作を監視するためのターミナルに出力されるメッセージがほとんどないことにお気づきでしょうか。これはかなり重要なことです。

ただ、ここでは100%自動化されたシステムを作る方法を紹介しているだけなので、どの時点でEAの内部で何が起こっているのかを知るのが適切なのかはわかりません。人によって、重要な情報が違ったり、重要でなかったりします。ただし、何が起こるかを正確に知るために、コードを入手して何度もテストや調整をおこなわずに実際の口座に直接起動することは、どなたにもお勧めしません。

次回は、EAを自律的に動作させるための設定と起動方法を紹介します。記事が出るまでは、この記事で勉強してみてください。簡単な対策で解決できることもあれば、案外複雑な問題もあることを理解するようにしてください。

自動EAを作る究極の方法を紹介するつもりはありません。数ある可能性のある使い方の1つを示しただけです。また、自動売買EAを使うことで発生するリスクも紹介し、それを最小限に抑える、あるいは許容範囲内に抑える方法をお伝えしています。

手動または自動でEAを使おうとしている人のために、最後のヒントを1つ。数量が使用ルールに違反しているとして、システムが注文を送信できない場合、以下の値を変更するだけで大丈夫です。

int OnInit()
{
        string szInfo;
        
        manager = new C_Manager(def_MAGIC_NUMBER, user03, user02, user01, user04, user08, false, 10);
        mouse = new C_Mouse(user05, user06, user07, user03, user02, user01);
        for (ENUM_DAY_OF_WEEK c0 = SUNDAY; c0 <= SATURDAY; c0++)

初期値では10です。しかし、特に為替市場では、より大きな値を使用する必要があることが起こり得ます。通常は100を使う人が多いです。よって、10よりずっと大きな値を使うべきでしょう。例えば、通常の100の10倍の数量を使いたい場合は、1000の値を入力します。コードは次のようになります。

int OnInit()
{
        string szInfo;
        
        manager = new C_Manager(def_MAGIC_NUMBER, user03, user02, user01, user04, user08, false, 1000);
        mouse = new C_Mouse(user05, user06, user07, user03, user02, user01);
        for (ENUM_DAY_OF_WEEK c0 = SUNDAY; c0 <= SATURDAY; c0++)

こうすれば、EAが新規注文の送信をブロックする前に、指定した数量の注文を最大10回まで送信することができます。

下の動画では、現在のシステムの構成を見ることができます。



MetaQuotes Ltdによりポルトガル語から翻訳されました。
元の記事: https://www.mql5.com/pt/articles/11305

添付されたファイル |
MQL5の圏論(第4回):スパン、実験、合成 MQL5の圏論(第4回):スパン、実験、合成
圏論は数学の一分野であり、多様な広がりを見せていますが、MQL5コミュニティでは今のところ比較的知られていません。この連載では、その概念のいくつかを紹介して考察することで、トレーダーの戦略開発におけるこの注目すべき分野の利用を促進することを目的としたオープンなライブラリを確立することを目指しています。
自動で動くEAを作る(第11回):自動化(III) 自動で動くEAを作る(第11回):自動化(III)
自動化されたシステムは、適切なセキュリティなしでは成功しません。ただし、いくつかのことをよく理解していなければ、セキュリティは保証されません。この記事では、自動化されたシステムで最大のセキュリティを達成することがなぜそれほど難しいのかを探ります。
母集団最適化アルゴリズム:電磁気的アルゴリズム(ЕМ) 母集団最適化アルゴリズム:電磁気的アルゴリズム(ЕМ)
この記事では、様々な最適化問題において、電磁気的アルゴリズム(EM、electroMagnetism-like Algorithm)を使用する原理、方法、可能性について解説しています。EMアルゴリズムは、大量のデータや多次元関数を扱うことができる効率的な最適化ツールです。
データサイエンスと機械学習(第13回):主成分分析(PCA)で金融市場分析を改善する データサイエンスと機械学習(第13回):主成分分析(PCA)で金融市場分析を改善する
主成分分析(Principal component analysis、PCA)で金融市場分析に革命を起こしましょう。この強力な手法がどのようにデータの隠れたパターンを解き放ち、潜在的な市場動向を明らかにし、投資戦略を最適化するかをご覧ください。この記事では、PCAが複雑な金融データを分析するための新しいレンズをどのように提供できるかを探り、従来のアプローチでは見逃されていた洞察を明らかにします。金融市場データにPCAを適用することで競争力を高め、時代を先取りする方法をご覧ください。