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

自動で動くEAを作る(第11回):自動化(III)

MetaTrader 5トレーディング | 25 5月 2023, 15:14
682 0
Daniel Jose
Daniel Jose

はじめに

前回の「自動で動くEAを作る(第10回):自動化(II)」稿では、EAの運用スケジュール管理を追加する方法を検討しました。EAシステム全体が自律性を優先するように構築されていますが、完全に自動化されたEAを取得する最終段階に進む前に、コードにいくつかの小さな変更を加える必要があります。

自動化段階では、既存のコードのいかなる部分も修正、作成、変更してはなりません。トレーダーとEAの間の相互作用があったポイントを削除し、代わりにある種の自動トリガーを追加する必要があるだけです。古いコードをさらに変更することは避けるべきです。自動化機能を実装するときにそのような変更が必要であった場合、コードの計画が不十分であったことになり、次の特性を持つシステムを実現するには、すべての計画をやり直す必要があります。

  • 堅牢:システムには、コードのどの部分の整合性にも違反する可能性のある主要なエラーがない
  • 信頼:多くの潜在的に危険な状況にさらされても失敗することなく機能する
  • 安定:常にうまく動作し、説明なしにクラッシュしない
  • スケーラブル:多くのプログラミングを必要とせずにスムーズに拡張できる
  • カプセル化:使用に本当に必要な関数だけが、コードが作成されている場所の外から見える
  • 高速:コードの準備が不十分なためにモデルが遅くならない

これらの機能の一部は、オブジェクト指向プログラミングモデルを参照しています。実際、このモデルは、優れたレベルのセキュリティを必要とするプログラムの作成に最も適しています。このため、この連載の最初から、すべてのプログラミングがクラスの使用、つまりオブジェクト指向プログラミングの適用に焦点を当てていることにはお気付きだと思います。このタイプのプログラミングは、最初はわかりにくく、習得が難しいように思えるかもしれませんが、実際に努力してコードをクラスとして作成することを学べば、本当に多くの恩恵を受けることができます。

私がこう言うのは、意欲的なプロのプログラマーにコードを作成する方法を示すためにです。コードが保持される3つのフォルダの操作に慣れなければいけません。プログラムがどれほど複雑であろうと単純であろうと、常に3つのステップで作業する必要があります。

  1. 開発フォルダにある最初の段階では、すべてのコードを作成、変更、テストします。新しい機能や変更は、このフォルダに存在するコードでのみおこなう必要があります。
  2. コードをビルドしてテストした後、作業用の2番目のフォルダに移動します。このフォルダのコードにはまだエラーが含まれている可能性がありますが、変更しないでください。このフォルダにいる間にコードを編集する必要がある場合は、開発フォルダに戻します。1つの詳細:発見された欠陥を修正するためにコードを編集する場合、他のより極端な変更をおこなわずに、この作業フォルダのコードをその中に保持し、適切な修正を受け取ることができます。
  3. 最後に、使用した後、新しい変更を加えずにさまざまな状況でコードを数回変更した場合は、それを3番目の最後のstableフォルダに移動します。このフォルダに存在するコードに欠陥がないことはすでに証明されています。これは、設計されたタスクに対して非常に便利で効率的です。このフォルダには新しいコードを絶対に追加しないでください。

この方法をしばらく適用すると、最終的に関数と手順の非常に興味深いデータベースが作成され、非常に迅速かつ安全にプログラミングできるようになります。この種のことは、特に金融市場など、市場に常に存在するリスクに適していないコードの使用に誰も本当に興味を持っていないようなアクティビティにおいて高く評価されています.私たちは常に実行時エラーが評価されず、すべてがリアルタイムという最悪のシナリオで発生する領域で作業しているため、コードはいつでも予期しないイベントに耐えることができなければなりません。

これはすべて、図01に示されている次のポイントに到達するために述べられています。

図01

図01:手動制御システム

多くの人は、自分が何をプログラミングまたは作成しているのかを完全には理解していません。これは、彼らの多くがシステム内で何が起こっているのかを本当に理解しておらず、トレーダーがやりたいことをプラットフォームが正確に提供する必要があると考えてしまうためです。ただし、図01を見ると、プラットフォームはトレーダーが望むものを提供することに重点を置いてはならないことがわかります。代わりに、トレーダーとやり取りする方法を提供すると同時に、取引サーバーと安定して迅速かつ効率的にやり取りする方法を提供する必要があります。同時に、プラットフォームは運用を維持し、トレーダーとの対話に使用される要素をサポートする必要があります。

どの矢印もポイントを超えていないことに注意してください。これは、これが手動取引システムであることを示しています。また、EAはプラットフォームによって提供され、その逆ではなく、トレーダーがEAと直接通信しないことに注意してください。奇妙に思えるかもしれませんが、実際にはトレーダーはプラットフォームを介してEAにアクセスし、プラットフォームはトレーダーが作成または実行するイベントを送信してEAを制御します。それに応じて、EAはプラットフォームに要求を送信します。これは取引サーバーに送信する必要があります。サーバーが応答すると、それらの応答がプラットフォームに返され、EAに転送されます。EAは、サーバーの応答を分析および処理した後、プラットフォームに情報を送信して、何が起こっているか、またはトレーダーによっておこなわれた要求の結果が何であるかをトレーダーに示すことができます。

多くの人は、そのようなことに気づいていません。EAで不具合が発生しても、プラットフォームに問題はありません。問題はEAにありますが、経験の浅いトレーダーは、プラットフォームが望んだことをしていないと誤って非難する可能性があります。

プログラマーとしてプラットフォームの開発と保守に関与していない場合は、プラットフォームの動作に影響を与えようとするべきではありません。コードがプラットフォームの要件に適切に対応していることを確認してください。

最後に次の質問に行き着きます。手動EAを作成し、しばらく使用してから自動化するのはなぜでしょうか。その理由はまさに「コードを実際にテストし、必要なものを正確に作成する方法を作成し、それ以上でもそれ以下でもない」ということです。

コントロールポイントおよび注文システムとして使用される1行のコードに影響を与えずにシステムを適切に自動化するには、いくつかの追加と小さな変更をおこなう必要があります。というわけで、前回までに作成したコードをworkingフォルダに、前回までに作成したコードをstableフォルダに、今回の記事で紹介したコードをdevelopmentフォルダに移動します。このようにして、開発プロセスが拡張および進化し、コーディングが大幅に高速化されます。何か問題が発生した場合は、すべてが問題なく機能していた2つのバージョンにいつでも戻ることができます。


変更の実装

実際に最初におこなうことは、タイミングシステムを変更することです。この変更は、次のコードに示されています。

virtual const bool CtrlTimeIsPassed(void) final
                        {
                                datetime dt;
                                MqlDateTime mdt;
                                
                                TimeToStruct(TimeLocal(), mdt);
                                TimeCurrent(mdt);
                                dt = (mdt.hour * 3600) + (mdt.min * 60);
                                return ((m_InfoCtrl[mdt.day_of_week].Init <= dt) && (m_InfoCtrl[mdt.day_of_week].End >= dt));
                        }

削除された行は、強調表示された行に置き換えられました。なぜこれらの変更をおこなったのでしょうか。理由は2つあります。まず、2つの呼び出しを1つに置き換えます。削除された行では、最初に時間を取得するための呼び出しがあり、次にそれを構造体に変換するための別の呼び出しがありました。2つ目の理由は、図02に示すように、TimeLocalが実際には、気配値表示要素に表示される時刻ではなく、コンピューターの時刻を返すことです。

図02

図02:前回の更新でサーバーから提供された時間

NTPサーバー(公式の時刻を最新に保つサーバー)を介して同期されている場合、コンピューターの時刻を使用しても問題になりません。ただし、ほとんどの場合、そのようなサーバーは使用されません。したがって、時間制御システムにより、EAが早期に開始または終了する場合があります。この変更は、この不便さを回避するために必要でした。

変更したのは、コードを根本的に変更するためではなく、トレーダーが期待するより高い安定性を提供するためにです。EAが予想よりも早く開始して終了すると、トレーダーはプラットフォームまたはコードにエラーがあると考えることがあります。しかし、これは実際には、コンピューターと取引サーバー間の時刻同期の欠如によって引き起こされます。取引サーバーが公式時刻を維持するためにNTPサーバーを使用している可能性が高い一方、作業用のコンピューターがこのサーバーを使用していない可能性があります。

次の変更が注文システムに実装されました。

                ulong ToServer(void)
                        {
                                MqlTradeCheckResult     TradeCheck;
                                MqlTradeResult          TradeResult;
                                bool bTmp;
                                
                                ResetLastError();
                                ZeroMemory(TradeCheck);
                                ZeroMemory(TradeResult);
                                bTmp = OrderCheck(m_TradeRequest, TradeCheck);
                                if (_LastError == ERR_SUCCESS) bTmp = OrderSend(m_TradeRequest, TradeResult);
                                if (_LastError != ERR_SUCCESS) MessageBox(StringFormat("Error Number: %d", GetLastError()), "Order System", MB_OK);
                                if (_LastError != ERR_SUCCESS) PrintFormat("Order System - Error Number: %d", _LastError);
                                return (_LastError == ERR_SUCCESS ? TradeResult.order : 0);
                        }

あまり苦労せずにEAを自動化できるようにシステムを本来の目的で使用できるようにするためにも、これは必要な変更です。実際、削除された文字列が強調表示された文字列に置き換えられたのは、コードが高速化または安定化されたからではなく、エラーの発生を適切に処理する必要があったためです。自動化されたEAがある場合、以前の記事で説明したように、いくつかのタイプの障害は無視できます。

問題は、場合によってはエラーがコードによって正しく処理され、ボックスが必要ないのにもかかわらず、削除された行が常にエラーを通知するメッセージボックスを起動することです。そのような場合は、トレーダーが適切な行動を取ることができるように、ターミナルにメッセージを出力するだけです。

完全に自動化されたEAは、トレーダーが決定を下すのを待ちません。これにはしばらく時間がかかる可能性がありますが、どんな問題が発生したかを報告せずに他のことをするわけにはいきません。ここでも、アジリティを向上させるためにコードが変更されました。変更によって引き起こされた可能性のある障害を探すために、システムをより集中的にテストしなければならないような主要な変更はありませんでした。

ただし、上記の変更とは異なり、次に来る変更はシステムの動作に影響を与えるため、詳細にテストする必要があります。


自動化への道を開く

次の変更により、完全に自動化されたシステムを効果的に作成できるようになります。これらの変更がなければ、次の記事では、すでにテスト済みのEAを自律型のEAに変える方法を説明することになります(読者が、すべてが実際にどのように機能するかを理解するために必要なすべてのテストを実行していることを願っています)。必要な変更を実装するには、いくつかのものを削除(より適切に言えば変更)し、他のものを追加する必要があります。変更を始めましょう。変更される内容は、以下のコードで説明されています。

//+------------------------------------------------------------------+
#include "C_ControlOfTime.mqh"
//+------------------------------------------------------------------+
#define def_MAX_LEVERAGE                10
#define def_ORDER_FINISH                false
//+------------------------------------------------------------------+
class C_Manager : public C_ControlOfTime

これら2つの定義は存在しなくなります。その代わりに2つの新しい変数が表示されます。これらはトレーダーが変更することはできませんが、プログラマーが定義することができます。なぜそのような変更をおこなうのでしょうか。定義を変数に置き換えると、速度が少し低下します。数マシンサイクルであっても、変数にアクセスするよりも定数値にアクセスする方がかなり高速であるため、実際にはパフォーマンスがわずかに低下します。しかしその見返りとして、クラスの再利用を得ることができます。これについては、次の記事で詳しく説明します。信じてください、使いやすさと移植性の点での違いは、パフォーマンスのわずかな損失を補います。したがって、上記の2行は次のように置き換えられます。

class C_Manager : public C_ControlOfTime
{
        enum eErrUser {ERR_Unknown, ERR_Excommunicate};
        private :
                struct st00
                {
                        double  FinanceStop,
                                FinanceTake;
                        uint    Leverage,
                                MaxLeverage;
                        bool    IsDayTrade,
                                IsOrderFinish;                                          
                }m_InfosManager;

コードを扱うときはご注意ください。初期化された場所以外でこれら2つの変数の値を変更しないでください。これをおこなわないように十分注意してください。次のコードに示すように、これらが初期化される場所はクラスコンストラクタ内にあります。

                C_Manager(const ulong magic, double FinanceStop, double FinanceTake, uint Leverage, bool IsDayTrade, double Trigger, const bool IsOrderFinish, const uint MaxLeverage)
                        :C_ControlOfTime(magic),
                        m_bAccountHedging(false),
                        m_TicketPending(0),
                        m_Trigger(Trigger)
                        {
                                string szInfo;
                                
                                ResetLastError();
                                ZeroMemory(m_Position);
                                m_InfosManager.IsOrderFinish    = IsOrderFinish;
                                m_InfosManager.MaxLeverage      = MaxLeverage;
                                m_InfosManager.FinanceStop      = FinanceStop;
                                m_InfosManager.FinanceTake      = FinanceTake;
                                m_InfosManager.Leverage         = Leverage;
                                m_InfosManager.IsDayTrade       = IsDayTrade;

コンストラクタは、変数を初期化する2つの新しい引数を受け取ります。その後、定義がインスタンス化されたポイントを変更します。これらの変更は、次の点でおこなわれます。

inline int SetInfoPositions(void)
                        {
                                double v1, v2;
                                int tmp = m_Position.Leverage;
                                
                                m_Position.Leverage = (int)(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 (def_ORDER_FINISH) if (m_TicketPending > 0) if (OrderSelect(m_TicketPending)) v1 = OrderGetDouble(ORDER_PRICE_OPEN);
                                m_Position.EnableBreakEven = (def_ORDER_FINISH ? m_TicketPending == 0 : m_Position.EnableBreakEven) || (m_Position.IsBuy ? (v1 < v2) : (v1 > v2));
                                if (m_InfosManager.IsOrderFinish) if (m_TicketPending > 0) if (OrderSelect(m_TicketPending)) v1 = OrderGetDouble(ORDER_PRICE_OPEN);
                                m_Position.EnableBreakEven = (m_InfosManager.IsOrderFinish ? m_TicketPending == 0 : m_Position.EnableBreakEven) || (m_Position.IsBuy ? (v1 < v2) : (v1 > v2));
                                m_Position.Gap = FinanceToPoints(m_Trigger, m_Position.Leverage);

                                return m_Position.Leverage - tmp;
                        }

inline void TriggerBreakeven(void)
                        {
                                double price;
                                
                                if (PositionSelectByTicket(m_Position.Ticket))
                                        if (PositionGetDouble(POSITION_PROFIT) >= m_Trigger)
                                        {
                                                price = m_Position.PriceOpen + (GetTerminalInfos().PointPerTick * (m_Position.IsBuy ? 1 : -1));
                                                if (def_ORDER_FINISH)
                                                if (m_InfosManager.IsOrderFinish)
                                                {
                                                        if (m_TicketPending > 0) m_Position.EnableBreakEven = !ModifyPricePoints(m_TicketPending, price, 0, 0);
                                                }else m_Position.EnableBreakEven = !ModifyPricePoints(m_Position.Ticket, m_Position.PriceOpen, price, m_Position.TP);
                                        }
                        }

                void CreateOrder(const ENUM_ORDER_TYPE type, const double Price)
                        {
                                if (!CtrlTimeIsPassed()) return;
                                if ((m_StaticLeverage >= def_MAX_LEVERAGE) || (m_TicketPending > 0) || (m_bAccountHedging && (m_Position.Ticket > 0))) return;
                                if ((m_StaticLeverage >= m_InfosManager.MaxLeverage) || (m_TicketPending > 0) || (m_bAccountHedging && (m_Position.Ticket > 0))) return;
                                m_TicketPending = C_Orders::CreateOrder(type, Price, (def_ORDER_FINISH ? 0 : m_InfosManager.FinanceStop), (def_ORDER_FINISH ? 0 : m_InfosManager.FinanceTake), m_InfosManager.Leverage, m_InfosManager.IsDayTrade);
                                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 (!CtrlTimeIsPassed()) return;
                                if ((m_StaticLeverage >= def_MAX_LEVERAGE) || (m_bAccountHedging && (m_Position.Ticket > 0))) return;
                                if ((m_StaticLeverage >= m_InfosManager.MaxLeverage) || (m_bAccountHedging && (m_Position.Ticket > 0))) return;
                                tmp = C_Orders::ToMarket(type, (def_ORDER_FINISH ? 0 : m_InfosManager.FinanceStop), (def_ORDER_FINISH ? 0 : m_InfosManager.FinanceTake), m_InfosManager.Leverage, m_InfosManager.IsDayTrade);
                                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));
                        }

                void PendingToPosition(void)
                        {
                                ResetLastError();
                                if ((m_bAccountHedging) && (m_Position.Ticket > 0))
                                {
                                        if (def_ORDER_FINISH)
                                        if (m_InfosManager.IsOrderFinish)
                                        {
// ... The rest of the code ...

                void UpdatePosition(const ulong ticket)
                        {
                                int ret;
                                double price;
                                
                                if ((ticket == 0) || (ticket != m_Position.Ticket) || (m_Position.Ticket == 0)) return;
                                if (PositionSelectByTicket(m_Position.Ticket))
                                {
                                        ret = SetInfoPositions();
                                        if (def_ORDER_FINISH)
                                        if (m_InfosManager.IsOrderFinish)
                                        {
                                                price = m_Position.PriceOpen + (FinanceToPoints(m_InfosManager.FinanceStop, m_Position.Leverage) * (m_Position.IsBuy ? -1 : 1));
                                                if (m_TicketPending > 0) if (OrderSelect(m_TicketPending))
                                                {
                                                        price = OrderGetDouble(ORDER_PRICE_OPEN);
                                                        C_Orders::RemoveOrderPendent(m_TicketPending);
                                                }
                                                if (m_Position.Ticket > 0)      m_TicketPending = C_Orders::CreateOrder(m_Position.IsBuy ? ORDER_TYPE_SELL : ORDER_TYPE_BUY, price, 0, 0, m_Position.Leverage, m_InfosManager.IsDayTrade);
                                        }
                                        m_StaticLeverage += (ret > 0 ? ret : 0);
                                }else
                                {
                                        ZeroMemory(m_Position);
                                        if ((def_ORDER_FINISH) && (m_TicketPending > 0))
                                        if ((m_InfosManager.IsOrderFinish) && (m_TicketPending > 0))
                                        {
                                                RemoveOrderPendent(m_TicketPending);
                                                m_TicketPending = 0;
                                        }
                                }
                                ResetLastError();
                        }

inline void TriggerTrailingStop(void)
                        {
                                double price, v1;
                                
                                if ((m_Position.Ticket == 0) || (def_ORDER_FINISH ? m_TicketPending == 0 : m_Position.SL == 0)) return;
                                if ((m_Position.Ticket == 0) || (m_InfosManager.IsOrderFinish ? m_TicketPending == 0 : m_Position.SL == 0)) return;
                                if (m_Position.EnableBreakEven) TriggerBreakeven(); else
                                {
                                        price = SymbolInfoDouble(_Symbol, (GetTerminalInfos().ChartMode == SYMBOL_CHART_MODE_LAST ? SYMBOL_LAST : (m_Position.IsBuy ? SYMBOL_ASK : SYMBOL_BID)));
                                        v1 = m_Position.SL;
                                        if (def_ORDER_FINISH)
                                        if (m_InfosManager.IsOrderFinish)
                                                if (OrderSelect(m_TicketPending)) v1 = OrderGetDouble(ORDER_PRICE_OPEN);
                                        if (v1 > 0) if (MathAbs(price - v1) >= (m_Position.Gap * 2)) 
                                        {
                                                price = v1 + (m_Position.Gap * (m_Position.IsBuy ? 1 : -1));
                                                if (def_ORDER_FINISH)
                                                if (m_InfosManager.IsOrderFinish)
                                                        ModifyPricePoints(m_TicketPending, price, 0, 0);
                                                else    ModifyPricePoints(m_Position.Ticket, m_Position.PriceOpen, price, m_Position.TP);
                                        }
                                }
                        }

削除されたすべての部分は、強調表示されたフラグメントに置き換えられています。このようにして、念願のクラスの再利用を促進するだけでなく、使いやすさの面でも改善することができました。これはこの記事では完全に明確ではありませんが、次の記事ではこれがどのようにおこなわれるかを説明します。

すでに加えた変更に加えて、注文送信システムへの自動化システムのアクセスを最大化し、それによってクラスの再利用を増やすために、いくつかの追加をおこなう必要があります。まず次の関数を追加します。

inline void ClosePosition(void)
	{
		if (m_Position.Ticket > 0)
		{
			C_Orders::ClosePosition(m_Position.Ticket);
			ZeroMemory(m_Position.Ticket);
		}                               
	}

この関数は一部のオペレーティングモデルで必要になるため、C_Managerクラスコードに含める必要があります。ただし、この関数を追加した後、コードをコンパイルしようとすると、コンパイラはいくつかの警告を生成します。以下の図03を参照してください。

図03

図03:コンパイラの警告

無視できる(ただし、まったくお勧めしません)コンパイラの警告とは異なり、図03の警告がプログラムに有害で、生成されたコードが正しく動作しない可能性があります。

理想的には、コンパイラがそのような警告を生成したことに気付いた場合は、警告を生成したエラーを修正するように努めるべきです。非常に簡単に解決できる場合もあれば、もう少し複雑な場合もあります。これは、変換中にデータの一部が失われるタイプの変更である可能性があります。いずれにせよ、コードがコンパイルされている場合でも、コンパイラがそのような警告を生成した理由を調べることは常に重要です。

コンパイラが警告するということは、コンパイラがプログラミング内容を理解するのに苦労していて、コードで何かがうまくいっていないということです。理解できなければ、コンパイラーは完全に信頼できるコードを生成できません。

一部のプログラミングプラットフォームではコンパイラの警告を無効にすることができますが、個人的にはこれをおこなうことはお勧めしません。完全に信頼できるコードが必要な場合は、すべての警告を有効にしておくことをお勧めします。時間が経つにつれて、標準のプラットフォーム設定をそのままにしておくことが、より信頼性の高いコードを確保するための最良の方法であることに気付くでしょう。

上記の警告を解決するには、2つのオプションがあります。1つ目は、現在C_Ordersクラスに存在する関数を参照しているClosePosition関数の呼び出しを、C_Managerクラスに追加された新しい関数に置き換えることです。C_Managerに存在する呼び出しを確認するため、これが最適なオプションです。2番目のオプションは、呼び出しがC_Ordersクラスを参照することをコンパイラに伝えることです。

新しく作成した呼び出しを使用するようにコードを変更します。警告を生成する問題点が解決され、コンパイラは私たちが何をしようとしているのかを理解するようになります。

                ~C_Manager()
                        {
                                if (_LastError == (ERR_USER_ERROR_FIRST + ERR_Excommunicate))
                                {
                                        if (m_TicketPending > 0) RemoveOrderPendent(m_TicketPending);
                                        if (m_Position.Ticket > 0) ClosePosition(m_Position.Ticket);
                                        ClosePosition();
                                        Print("EA was kicked off the chart for making a serious mistake.");
                                }
                        }

これをデストラクタで解決するのが最も簡単な方法ですが、以下に示すように少し難しい部分があります。

                void PendingToPosition(void)
                        {
                                ResetLastError();
                                if ((m_bAccountHedging) && (m_Position.Ticket > 0))
                                {
                                        if (m_InfosManager.IsOrderFinish)
                                        {
                                                if (PositionSelectByTicket(m_Position.Ticket)) ClosePosition(m_Position.Ticket);
                                                ClosePosition();
                                                if (PositionSelectByTicket(m_TicketPending)) C_Orders::ClosePosition(m_TicketPending); else RemoveOrderPendent(m_TicketPending);
                                                ZeroMemory(m_Position.Ticket);
                                                m_TicketPending = 0;
                                                ResetLastError();
                                        }else   SetUserError(ERR_Unknown);
                                }else m_Position.Ticket = (m_Position.Ticket == 0 ? m_TicketPending : m_Position.Ticket);
                                m_TicketPending = 0;
                                if (_LastError != ERR_SUCCESS) UpdatePosition(m_Position.Ticket);
                                CheckToleranceLevel();
                        }

何行かを削除し、ポジションを決済するための呼び出しを追加しました。 未決注文が何らかの理由でポジションになった場合、元のコードでおこなったようにポジションを削除する必要がありますが、そのポジションはC_Managerクラスによってまだキャプチャされていません。この場合、強調表示されたコードに示すように、呼び出しがC_Ordersクラスを参照することをコンパイラに伝えます

以下は、必要なもう1つの変更です。

inline void EraseTicketPending(const ulong ticket)
                        {
                                if ((m_TicketPending == ticket) && (m_TicketPending > 0))
                                {
                                        if (PositionSelectByTicket(m_TicketPending)) C_Orders::ClosePosition(m_TicketPending); 
                                        else RemoveOrderPendent(m_TicketPending);
                                        m_TicketPending = 0;
                                }
                                ResetLastError();
                                m_TicketPending = (ticket == m_TicketPending ? 0 : m_TicketPending);
                        }

元のコードであった取り消し線付きのコードは、もう少し複雑なものに置き換えられましたが、未決注文を削除したり、未決注文がポジションになっている場合は削除したりすることができるようになりました。以前は、MetaTrader 5プラットフォームから通知されたイベントに応答しただけで、未決注文チケットとして示される値がゼロに設定され、新しい未決注文を送信できるようになっていましたが、現在はそれ以上のことをおこなっています。完全に自動化されたシステムではそのような機能が必要なためです。

これらの小さな変更を実装することで、システム全体が恩恵を受け、コードの再利用とテストが増加します。


最終段階前の最終改善

最終段階に移る前に、コードの再利用を増やすために改善を実装できます。最初の改善点は、次のコードに示されています。

                void UpdatePosition(const ulong ticket)
                        {
                                int ret;
                                double price;
                                
                                if ((ticket == 0) || (ticket != m_Position.Ticket) || (m_Position.Ticket == 0)) return;
                                if (PositionSelectByTicket(m_Position.Ticket))
                                {
                                        ret = SetInfoPositions();
                                        if (m_InfosManager.IsOrderFinish)
                                        {
                                                price = m_Position.PriceOpen + (FinanceToPoints(m_InfosManager.FinanceStop, m_Position.Leverage) * (m_Position.IsBuy ? -1 : 1));
                                                if (m_TicketPending > 0) if (OrderSelect(m_TicketPending))
                                                {
                                                        price = OrderGetDouble(ORDER_PRICE_OPEN);
                                                        C_Orders::RemoveOrderPendent(m_TicketPending);
                                                        EraseTicketPending(m_TicketPending);
                                                }
                                                if (m_Position.Ticket > 0)      m_TicketPending = C_Orders::CreateOrder(m_Position.IsBuy ? ORDER_TYPE_SELL : ORDER_TYPE_BUY, price, 0, 0, m_Position.Leverage, m_InfosManager.IsDayTrade);
                                        }
                                        m_StaticLeverage += (ret > 0 ? ret : 0);
                                }else
                                {
                                        ZeroMemory(m_Position);
                                        if ((m_InfosManager.IsOrderFinish) && (m_TicketPending > 0))
                                        {
                                                RemoveOrderPendent(m_TicketPending);
                                                m_TicketPending = 0;
                                        }
                                        if (m_InfosManager.IsOrderFinish) EraseTicketPending(m_TicketPending);
                                }
                                ResetLastError();
                        }

これらの改善の恩恵を受けることができるもう1つのポイントは、次のコードにあります。

                ~C_Manager()
                        {
                                if (_LastError == (ERR_USER_ERROR_FIRST + ERR_Excommunicate))
                                {
                                        if (m_TicketPending > 0) RemoveOrderPendent(m_TicketPending);
                                        EraseTicketPending(m_TicketPending);
                                        ClosePosition();
                                        Print("EA was kicked off the chart for making a serious mistake.");
                                }
                        }

最後に、コードの再利用のメリットもある最後のポイントです。

                void PendingToPosition(void)
                        {
                                ResetLastError();
                                if ((m_bAccountHedging) && (m_Position.Ticket > 0))
                                {
                                        if (m_InfosManager.IsOrderFinish)
                                        {
                                                ClosePosition();
                                                EraseTicketPending(m_TicketPending);
                                                if (PositionSelectByTicket(m_TicketPending)) C_Orders::ClosePosition(m_TicketPending); else RemoveOrderPendent(m_TicketPending);
                                                m_TicketPending = 0;
                                                ResetLastError();
                                        }else   SetUserError(ERR_Unknown);
                                }else m_Position.Ticket = (m_Position.Ticket == 0 ? m_TicketPending : m_Position.Ticket);
                                m_TicketPending = 0;
                                if (_LastError != ERR_SUCCESS) UpdatePosition(m_Position.Ticket);
                                CheckToleranceLevel();
                        }

改善と変更の問題を完了するために、事細やかな点があります。EAは、たとえば、最小ボリュームの10倍の最大確定ボリュームを持つことができます。しかし、トレーダーがボリュームを構成しているときに、3回目の操作を実行するときに3倍のレバレッジ値を示した場合、EAは許可された最大ボリュームに非常に近くなります。次に、4番目の要求を送信すると、最大許容ボリュームを超えます。

これは小さな欠陥のように思えるかもしれません。多くの人は、危険はほとんどないと考えます。ある意味では、5番目の注文は受け入れられないので、私はこの考えに同意します。ただし、プログラミング時に定義された表示ボリュームは10倍でした。したがって、4番目の注文が受け入れられたとき、EAはボリュームを12回実行し、構成された最大ボリュームの2倍を超えます。これは、トレーダーが3倍のレバレッジを設定しているためですが、9倍のレバレッジを指定していたらどうなるでしょうか。この場合、トレーダーはEAが1つの取引のみを実行することを期待します。

したがって、EAが2回目の取引を開始し、最大ボリュームを8倍に超えたことを確認したユーザーの驚きを想像してみてください。心臓発作を起こすほどかもしれません。

おわかりのように、潜在的な失敗はほとんどありませんが、特に自動化されたEAの場合、これは認めるべきではない失敗です。トレーダーなら確認して同じレベルのレバレッジで別のエントリを作成しないため、手動EAではこれは問題になりません。いずれにせよ、そのような場合にはある種のEAブロックを提供する必要があります。これは次の記事に行く前に実装するべきです。後になってこのようなことを心配したくはありません。

これを修正するには、以下に示すように数行のコードを追加します。

//+------------------------------------------------------------------+
                void CreateOrder(const ENUM_ORDER_TYPE type, const double Price)
                        {                               
                                if (!CtrlTimeIsPassed()) return;
                                if ((m_StaticLeverage >= m_InfosManager.MaxLeverage) || (m_TicketPending > 0) || (m_bAccountHedging && (m_Position.Ticket > 0))) return;
                                if (m_StaticLeverage + m_InfosManager.Leverage > m_InfosManager.MaxLeverage)
                                {
                                        Print("Request denied, as it would violate the maximum volume allowed for the EA.");
                                        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 (!CtrlTimeIsPassed()) return;
                                if ((m_StaticLeverage >= m_InfosManager.MaxLeverage) || (m_bAccountHedging && (m_Position.Ticket > 0))) return;
                                if (m_StaticLeverage + m_InfosManager.Leverage > m_InfosManager.MaxLeverage)
                                {
                                        Print("Request denied, as it would violate the maximum volume allowed for the EA.");
                                        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));
                        }
//+------------------------------------------------------------------+

緑色で示されたコードは、上記で説明したイベントを回避します。ただし、強調表示されたコードをよくご覧ください。まったく同じだということに気がつかれたでしょうか。ここでできることは2つあります。マクロを作成してすべてのコードを配置するか、関数を作成してすべての共通コードを置換または蓄積することです。。

これらの記事の多くの読者はプログラミングの旅を始めたばかりで、経験や知識があまりないかもしれません。よって、すべてを単純明快にする関数を作成することにしました。この新しい関数をコードのprivate部分に配置します。残りのEAコードはその存在を知る必要はありません。新しい関数は次のとおりです。

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;
	}

ここで何が起こっているのかを理解しましょう。コードを送信するために繰り返されたすべてのコード行が、上記のコードにまとめられています。ただし、指値注文と成行注文の送信にはほとんど違いがなく、この違いはこの特定のポイントによって決まります。したがって、未決注文か成行注文かを確認する必要があります。これら2種類の注文を区別するために、すべてのコードを1つに結合できる引数を使用します注文を送信できない場合はfalseが返されます注文を送信できる場合はtrueが返されます

新しい関数のコードを以下に示します。

//+------------------------------------------------------------------+
                void CreateOrder(const ENUM_ORDER_TYPE type, const double Price)
                        {
                                if (!IsPossible(true)) return;
                                if (!CtrlTimeIsPassed()) return;
                                if ((m_StaticLeverage >= m_InfosManager.MaxLeverage) || (m_TicketPending > 0) || (m_bAccountHedging && (m_Position.Ticket > 0))) return;
                                if (m_StaticLeverage + m_InfosManager.Leverage > m_InfosManager.MaxLeverage)
                                {
                                        Print("Request denied, as it would violate the maximum volume allowed for the EA.");
                                        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;
                                if (!CtrlTimeIsPassed()) return;
                                if ((m_StaticLeverage >= m_InfosManager.MaxLeverage) || (m_bAccountHedging && (m_Position.Ticket > 0))) return;
                                if (m_StaticLeverage + m_InfosManager.Leverage > m_InfosManager.MaxLeverage)
                                {
                                        Print("Request denied, as it would violate the maximum volume allowed for the EA.");
                                        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が自動的に機能することを熱望しているはずです。しかし、本当に、完全に自動化されたEAに侵害が含まれていたり、動作に失敗してほしくはないものです。手動システムではそれほど深刻ではない欠陥が見逃されることさえありますが、完全に自動化されたシステムの場合、これは許容できません。

このトピックは説明が難しいので、次の記事で取り上げます。現状のコードを以下に添付します。次の記事をお読みになる前に、ここでちょっとした課題を残しておきます。EAにはまだ弱点があって、安全に自動化することができません。見つかりましたか。ヒントは、それがC_ManagerクラスがEAの作業を分析する方法にあるということです。


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

添付されたファイル |
自動で動くEAを作る(第12回):自動化(IV) 自動で動くEAを作る(第12回):自動化(IV)
自動化されたシステムをシンプルだと思う方はおそらく、それを作るために必要なことを十分に理解していないのでしょう。今回は、多くのエキスパートアドバイザー(EA)を死に至らしめる問題点についてお話します。この問題を解決するために、無差別に注文をトリガーすることが考えられます。
データサイエンスと機械学習(第13回):主成分分析(PCA)で金融市場分析を改善する データサイエンスと機械学習(第13回):主成分分析(PCA)で金融市場分析を改善する
主成分分析(Principal component analysis、PCA)で金融市場分析に革命を起こしましょう。この強力な手法がどのようにデータの隠れたパターンを解き放ち、潜在的な市場動向を明らかにし、投資戦略を最適化するかをご覧ください。この記事では、PCAが複雑な金融データを分析するための新しいレンズをどのように提供できるかを探り、従来のアプローチでは見逃されていた洞察を明らかにします。金融市場データにPCAを適用することで競争力を高め、時代を先取りする方法をご覧ください。
MQL5の圏論(第4回):スパン、実験、合成 MQL5の圏論(第4回):スパン、実験、合成
圏論は数学の一分野であり、多様な広がりを見せていますが、MQL5コミュニティでは今のところ比較的知られていません。この連載では、その概念のいくつかを紹介して考察することで、トレーダーの戦略開発におけるこの注目すべき分野の利用を促進することを目的としたオープンなライブラリを確立することを目指しています。
データサイエンスと機械学習(第12回):自己学習型ニューラルネットワークは株式市場を凌駕することができるのか? データサイエンスと機械学習(第12回):自己学習型ニューラルネットワークは株式市場を凌駕することができるのか?
常に株式市場を予測しようとするのにお疲れでないでしょうか。より多くの情報に基づいた投資判断をするための水晶玉があったらとお思いでしょうか。自己学習型ニューラルネットワークは、あなたが探していたソリューションかもしれません。この記事では、これらの強力なアルゴリズムが、株式市場を凌駕する「波に乗る」のに役立つのかどうかを探ります。膨大な量のデータを分析し、パターンを特定することで、自己訓練されたニューラルネットワークは、しばしば人間のトレーダーよりも精度の高い予測をおこなうことができます。この最先端のテクノロジーを使って、利益を最大化し、よりスマートな投資判断をおこなう方法をご紹介します。