自動で動くEAを作る(第06回):口座の種類(I)

Daniel Jose | 30 3月, 2023

はじめに

前回の「自動で動くEAを作る(第05回):手動トリガー(II)」稿では 、堅牢性と信頼性の高い、かなりシンプルなEAを開発しました。このEAは、FXや株式銘柄など、あらゆる資産の取引に利用できます。自動化はしておらず、完全に手動で制御されます。

このEAは、現状ではどんな状況でも機能しますが、まだ自動化には至っていません。まだ、いくつかの点で工夫が必要です。ブレークイーブンやトレーリングストップを追加する前にやるべきことがあります。これらの仕組みを追加するのが早過ぎると、後で取り今朝なければならないことが出てくるからです。そこで、少し違った道筋で、まずは汎用EAの作成を考えてみることにします。


C_Managerクラスの誕生

C_Managerクラスは、EAと受注システムの間の分離層となります。同時に、このクラスはEAの自動化を促進し、EAがいくつかのことを自動的におこなうことができるようにします。

それでは、クラス作りがどのように始まるのかを見ていきましょう。その初期コードは以下の通りです。

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#include "C_Orders.mqh"
//+------------------------------------------------------------------+
class C_Manager : private C_Orders
{
        private :
                struct st00
                {
                        double  FinanceStop,
                                FinanceTake;
                        uint    Leverage;
                        bool    IsDayTrade;
                }m_InfosManager;
        public  :
//+------------------------------------------------------------------+
                C_Manager(const ulong magic, double FinanceStop, double FinanceTake, uint Leverage, bool IsDayTrade)
                        :C_Orders(magic)
                        {
                                m_InfosManager.FinanceStop = FinanceStop;
                                m_InfosManager.FinanceTake = FinanceTake;
                                m_InfosManager.Leverage    = Leverage;
                                m_InfosManager.IsDayTrade  = IsDayTrade;
                        }
//+------------------------------------------------------------------+
                ~C_Manager() { }
//+------------------------------------------------------------------+
                void CreateOrder(const ENUM_ORDER_TYPE type, const double Price)
                        {
                                C_Orders::CreateOrder(type, Price, m_InfosManager.FinanceStop, m_InfosManager.FinanceTake, m_InfosManager.Leverage, m_InfosManager.IsDayTrade);
                        }
//+------------------------------------------------------------------+  
                void ToMarket(const ENUM_ORDER_TYPE type)
                        {
                                C_Orders::ToMarket(type, m_InfosManager.FinanceStop, m_InfosManager.FinanceTake, m_InfosManager.Leverage, m_InfosManager.IsDayTrade);
                        }
//+------------------------------------------------------------------+
};

上のコードにあるのは、これから構築するものの基本構造です。なお、EAでは、注文を送信する際に、一部の情報を提供する必要がなくなります。すべてはこのC_Managerクラスで管理されることになります。実際には、コンストラクタの呼び出しで、注文を作成したり、マーケットポジションを開くための注文を送信したりするために必要なすべての値を渡します

C_ManagerクラスはC_Ordersクラスを継承するが、この継承はprivateであることに注目してください。なぜでしょうか。その理由は、セキュリティと信頼性の向上です。このクラスを「シンジケーター」の一種としてここに配置する場合、EAと注文送信を担当するクラスとの間の唯一のコミュニケーションポイントになるようにします。

C_Managerは注文システムへのアクセスを制御し、注文やポジションの送信、クローズ、修正をおこなうことができるため、EAに注文システムにアクセスするための何らかの手段を与えることにします。しかし、このアクセスは限定的なものになります。 次は、EAが注文システムにアクセスするために使用できる2つの初期関数です。ご覧のように、これらはC_Ordersクラスのものよりもはるかに制限されているが、より安全なものです。

ここで実装しているもののレベルを理解するために、前回のEAのコードと今回のEAのコードを比較してみましょう。C_Managerクラスのみ作成しました。EAに存在する2つの関数で何が起こったかを見てみましょう。

int OnInit()
{
        manager = new C_Orders(def_MAGIC_NUMBER);
        manager = new C_Manager(def_MAGIC_NUMBER, user03, user02, user01, user04);
        mouse = new C_Mouse(user05, user06, user07, user03, user02, user01);

        return INIT_SUCCEEDED;
}

従来のコードは削除され、新しいものに変更されましたが、パラメータの数が多くなっています。しかし、これはあくまでも些細なことです。主なものは(私見ですが、これによってすべてがよりリスキーになります)、以下の通りです。

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        uint    BtnStatus;
        double  Price;
        static double mem = 0;
        
        (*mouse).DispatchMessage(id, lparam, dparam, sparam);
        (*mouse).GetStatus(Price, BtnStatus);
        if (TerminalInfoInteger(TERMINAL_KEYSTATE_CONTROL))
        {
                if (TerminalInfoInteger(TERMINAL_KEYSTATE_UP))  (*manager).ToMarket(ORDER_TYPE_BUY, user03, user02, user01, user04);
                if (TerminalInfoInteger(TERMINAL_KEYSTATE_DOWN))(*manager).ToMarket(ORDER_TYPE_SELL, user03, user02, user01, user04);
                if (TerminalInfoInteger(TERMINAL_KEYSTATE_UP))  (*manager).ToMarket(ORDER_TYPE_BUY);
                if (TerminalInfoInteger(TERMINAL_KEYSTATE_DOWN))(*manager).ToMarket(ORDER_TYPE_SELL);
        }
        if ((def_SHIFT_Press(BtnStatus) != def_CTRL_Press(BtnStatus)) && def_BtnLeftClick(BtnStatus))
        {
                if (mem == 0) (*manager).CreateOrder((def_SHIFT_Press(BtnStatus) ? ORDER_TYPE_BUY : ORDER_TYPE_SELL), mem = Price, user03, user02, user01, user04);
                if (mem == 0) (*manager).CreateOrder((def_SHIFT_Press(BtnStatus) ? ORDER_TYPE_BUY : ORDER_TYPE_SELL), mem = Price);
        }else mem = 0;
}

単に削除しただけの部分もあります。ひいては、新しいコードがよりシンプルになったことがお分かりいただけると思います。 しかし、それだけではありません。「管理者」作業をおこなうためにクラスを使用することで、EAがクラスの初期化時に定義されたパラメータを正確に使用することが保証されます。このため、誤った情報や無効な情報を呼び出しに入れる危険性はありません。EAとC_Ordersの通信の仲介役となるC_Managerクラスに、すべてが集約されているのです。これにより、EAコードのセキュリティレベルや信頼性が大幅に向上します。


ネッティング口座、取引所口座、ヘッジ口座....それが問題です

多くの人がこの事実を無視するか気づかないですが、ここには深刻な問題があります。EAがうまく機能する場合としない場合があるのは、口座の種類のせいです。トレーダーやMetaTrader 5プラットフォームのユーザーのほとんどは、市場に3種類の口座があることを知りません。しかし、完全に自動化されたモードで動作するEAを開発したい場合、この知識は非常に重要です。

この連載では、2種類の口座について説明します。ネッティングとヘッジです。理由は簡単で、ネッティング口座は取引所口座と同じようにEAで機能するためです。

たとえEAにブレークイーブンやトレーリングストップなどの簡単な自動化があったとしても、それがネッティング口座で動作しているという事実が、ヘッジ口座で動作するものとは全く異なる動作をさせるのです。その理由は、取引サーバの仕組みにあります。ネッティング口座では、ポジションを増減させると、取引サーバが平均価格を作成します。

ヘッジ口座用のサーバでは違います。すべてのポジションが別々に扱われるので、同じ資産のショートポジションとポジションを同時に持つことができます。ネッティング口座では絶対に起こりえないことです。同じロットで反対のポジションを開こうとすると、サーバはそのポジションを決済します。

このため、EAがネッティング口座用なのかヘッジ口座用なのかによって、動作原理が全く異なるため、把握する必要があります。ただし、これは自動化されたEAや、ある程度の自動化レベルを持つEAにしか適用されず、手動EAでは関係ありません。

そのため、プログラミングや使い勝手においての問題なしでは、どんなレベルの自動化もおこなうことはできません。

ここでは、少し標準化する必要があります。つまり、EAが口座の種類にかかわらず標準的な方法で動作するようにする必要があるのです。たしかに、これではEAの能力が低下してしまいますが、自動化されたEAは、自由度が高いはずです。EAをうまく動作させるために拘束させるのが一番です。少しでも逸脱すれば、禁止されるか、少なくとも何らかの罰を受けなければなりません。

標準化する方法としては、EAから見てヘッジ口座がネッティング口座と同じような働きをするようにすることです。これは難しく複雑に見えるかもしれませんが、ここで本当に望んでいるのは、EAが1つのポジションと1つの指値注文だけを持つことを可能にすることです、言い換えれば、EAは非常に制限され、他のことをすることができなくなることです。

そこで、C_Managerクラスに以下のコードを追加します。

class C_Manager : private C_Orders
{
        private :
                struct st00
                {
                        double  FinanceStop,
                                FinanceTake;
                        uint    Leverage;
                        bool    IsDayTrade;
                }m_InfosManager;
//---
                struct st01
                {
                        ulong   Ticket;
                        double  SL,
                                TP,
                                PriceOpen,
                                Gap;
                        bool    EnableBreakEven,
                                IsBuy;
                        int     Leverage;
                }m_Position;
                ulong           m_TicketPending;
                bool            m_bAccountHedging;
		double		m_Trigger;


この構造体では、ポジションで作業するために必要なものをすべて作成します。 ブレークイーブンやトレーリングストップなど、第一段階の自動化に関連するものがすでに用意されています。指値注文は、チケットを使用してよりシンプルな方法で保存されることになりますが、 将来的にもっとデータが必要になれば、それを実装することも可能です。今はこれで十分でしょう。また、ヘッジとネッティングのどちらの口座を使用しているかを示す別の変数があります。ある瞬間に特に役立つことでしょう。いつものように、この段階では使用しないが、後でブレークイーブンとトレーリングストップのトリガーを作成するときに必要となる別の変数が追加されています

このように物事を規準化させていきます。その後、以下のようにクラスのコンストラクタを変更することができます。

                C_Manager(const ulong magic, double FinanceStop, double FinanceTake, uint Leverage, bool IsDayTrade, double Trigger)
                        :C_Orders(magic),
                        m_bAccountHedging(false),
                        m_TicketPending(0),
                        m_Trigger(Trigger)
                        {
                                string szInfo;
                                
                                ZeroMemory(m_Position);
                                m_InfosManager.FinanceStop = FinanceStop;
                                m_InfosManager.FinanceTake = FinanceTake;
                                m_InfosManager.Leverage    = Leverage;
                                m_InfosManager.IsDayTrade  = IsDayTrade;
                                switch ((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE))
                                {
                                        case ACCOUNT_MARGIN_MODE_RETAIL_HEDGING:
                                                m_bAccountHedging = true;
                                                szInfo = "HEDGING";
                                                break;
                                        case ACCOUNT_MARGIN_MODE_RETAIL_NETTING:
                                                szInfo = "NETTING";
                                                break;
                                        case ACCOUNT_MARGIN_MODE_EXCHANGE:
                                                szInfo = "EXCHANGE";
                                                break;
                                }
                                Print("Detected Account ", szInfo);
                        }

プログラミングの経験がない方のために、少しずつコードを提示していきます。ここでやっていることを皆さんに理解してもらいたいので、あまり退屈でなければいいのですが。以下はその解説です。これらの行は、コンストラクタのコード実行を開始する前に、これらの変数を初期化したいことをコンパイラに知らせます。 変数が作成されると、コンパイラは通常、その変数に0の値を割り当てます。

これらの行では、変数を作成する際に、コンパイラに変数の値を伝えています。この時点で、構造体の中身をすべてリセットします。こうすることで、より少ないコードで、より速い結果を得ることができるのです。ここでは、ヘッジ口座で作業することになることに注目しています。ある時点でこの情報が必要になった場合、それを伝えるための変数が用意されています。すでにここで、どの口座の種類が見つかったかをターミナルで知らせます。 これは、ユーザーが種類を知らない場合に備えてのことです。

これらの手順を見る前に、EAが複数のポジション(ヘッジ口座)または複数の指値注文を見つけた場合はどうなるかを考えてみてください。どうなるのでしょうか。この場合、EAは複数のポジションと1つの注文で動作することができないので、エラーが発生します。これを処理するために、コード内に次のような列挙を作成しましょう。

class C_Manager : private C_Orders
{
        enum eErrUser {ERR_Unknown};
        private :

// ... The rest of the code...

};

ここでは、新しいエラーコードを追加するのが簡単なように列挙型を使用することにします。 そのためには、新しい名前を指定するだけで、コンパイラがコードに応じた値を生成してくれる一方、不注意で重複した値が作られる心配はありません。なお、列挙はprivateコード部分より前にあるので、publicになります。しかし、クラスの外でアクセスするためには、どの列挙が正しいかをコンパイラに知らせるための小さな工夫が必要になります。これは、特定のクラスに関連する列挙を使用したい場合に特に有効です。それでは、チャート上に残っている可能性のあるもの、EAが作業を開始する前に復元しなければならないものを読み込む手順を見てみましょう。最初のものは次のとおりです。

inline void LoadOrderValid(void)
                        {
                                ulong value;
                                
                                for (int c0 = OrdersTotal() - 1; (c0 >= 0) && (_LastError == ERR_SUCCESS); c0--)
                                {
                                        if ((value = OrderGetTicket(c0)) == 0) continue;
                                        if (OrderGetString(ORDER_SYMBOL) != _Symbol) continue;
                                        if (OrderGetInteger(ORDER_MAGIC) != GetMagicNumber()) continue;
                                        if (m_TicketPending > 0) SetUserError(ERR_Unknown); else m_TicketPending = value;
                                }
                        }

このコードがどのように機能するのか、なぜこのような変わった形になるのかを見てみましょう。ここでは、ループを使って、オーダーブックにあるすべての指値注文を読み込んみますOrdersTotal関数は、注文が存在する場合、0より大きい値を返します。インデックス付けは常に0から始まります。これはC/C++に由来します。ループが終了するには 2 つの条件があります。1つ目は、変数c0の値が0より小さいこと、2つ目は、_LastErrorERR_SUCESSと異なる形で、EAで何らかの障害が発生したことを示していることです。

こうしてループに入り、変数c0で示されるインデックスを持つ最初の注文を取得すると、OrderGetTicketはチケット値または0を返します。0ならループに戻り、今度は変数c0から1を減算します。

OrderGetTicketは注文値を読み込み、それらは区別されないので、EAが特定の注文についてだけ知ることができるように、すべてをフィルタリングする必要があります。最初に使用するフィルタは資産名です。このために、注文の中の資産とEAが実行されている資産を比較します。 異なる場合は、注文を無視し、あればまた別のものを取得します。

オーダーブックキャブには手動で発注した注文や他のEAが発注した注文があるため、次のフィルタはマジックナンバーです。各EAが持つべきマジックナンバーから、その注文が私たちのEAによるものであるかどうかを調べることができます。マジックナンバーがEAで使用するものと異なる場合、注文を無視し、初めに戻って、新しい注文を探す必要があります。

ここで岐路に到達します。 EAが、何らかの理由でチャートから削除される前に発注した注文を見つけた場合(理由については後述)、そのメモリ、つまり指値注文チケットを示す変数には、0以外の値が格納されていることになります。そして、2つ目の注文が発生した場合はエラーになります。この関数は、エラーが発生したことを示すために、列挙型を使用します。

ここでは一般的な値であるERR_Unknownを使用していますが、エラーを指定する値を作成し、_LastErrorの値で表示されるようにします。SetUserError関数は、変数_LastErrorにエラー値を設定する役割を果たします。 ただし、すべてが問題なく、注文チケットを含む変数が0に設定されている場合、すべてのフィルタの後に見つかった注文の値は、今後の使用のためにm_TicketPending変数に保存されることになります。ここで、この手順の解説は終わりです。次に、ポジションを検索する役割を担う手順について考えてみましょう。以下がそのコードです。

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_Position.Ticket > 0) SetUserError(ERR_Unknown); else
					{
						m_Position.Ticket = value;
						SetInfoPositions();
					}
                                }
                        }

前のコードについて述べたことは、このコードにもすべて当てはまります。前は注文を操作したが今度はポジションを操作していることが唯一の違いです。ロジックは同じですが、最新のポジション情報を格納し、修正し、処理する必要のあるSetInfoPositionsを呼び出すまでです。そのために、以下のコードを使用します。

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);
                                m_Position.EnableBreakEven = (m_Position.IsBuy ? (v1 < v2) : (v1 > v2));
                                m_Position.Gap = FinanceToPoints(m_Trigger, m_Position.Leverage);

                                return m_Position.Leverage - tmp;
                        }

このコードは、最新のポジションデータを扱う際に、特に興味深いものとなっています。ただし、これを呼び出す前に、PositionGetTicketPositionSelectPositionGetSymbolPositionSelectByTicketのいずれかの呼び出してポジションデータを更新しておくことが必要なので、ご注意ください。一般的に、ここでは必要に応じてすべてのものを初期化したり、設定したりします。このコードを別に配置しなければならなかったのは、必要なときにいつでもポジションデータを更新するために、他のポイントでもこのコードを使用できるようにするためです。

基本的にはこれで終わりですが、今度はEAを完全に正しく初期化できるように、クラスのコンストラクタを新たに修正する必要があります。あとは、上に示したような呼び出しを追加するだけです。そうすると、コンストラクタの最終的なコードは次のようになります。

                C_Manager(const ulong magic, double FinanceStop, double FinanceTake, uint Leverage, bool IsDayTrade, double Trigger)
                        :C_Orders(magic),
                        m_bAccountHedging(false),
                        m_TicketPending(0),
                        m_Trigger(Trigger)
                        {
                                string szInfo;
                                
                                ResetLastError();
                                ZeroMemory(m_Position);
                                m_InfosManager.FinanceStop = FinanceStop;
                                m_InfosManager.FinanceTake = FinanceTake;
                                m_InfosManager.Leverage    = Leverage;
                                m_InfosManager.IsDayTrade  = IsDayTrade;
                                switch ((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE))
                                {
                                        case ACCOUNT_MARGIN_MODE_RETAIL_HEDGING:
                                                m_bAccountHedging = true;
                                                szInfo = "HEDGING";
                                                break;
                                        case ACCOUNT_MARGIN_MODE_RETAIL_NETTING:
                                                szInfo = "NETTING";
                                                break;
                                        case ACCOUNT_MARGIN_MODE_EXCHANGE:
                                                szInfo = "EXCHANGE";
                                                break;
                                }
                                Print("Detected Account ", szInfo);
                                LoadPositionValid();
                                LoadOrderValid();
                                if (_LastError == ERR_SUCCESS)
                                {
                                        szInfo = "Successful upload...";
                                        szInfo += StringFormat("%s", (m_Position.Ticket > 0 ? "\nTicket Position: " + (string)m_Position.Ticket : ""));
                                        szInfo += StringFormat("%s", (m_TicketPending > 0 ? "\nTicket Order: " + (string)m_TicketPending : ""));
                                        Print(szInfo);
                                }
                        }

この2本の線は、EAがポジションと指値注文を読み込むようにします。ここで、コンストラクタが値を返せないことに注目してください。返したらエラーです。コンストラクタで何か問題が発生したことを、他のコードに伝える方法が必要です。

あらゆるプログラミングシステムはそのような手段を備えている、あるいは私たちに作らせます。MQL5では、このために_LastError変数を使用するという、非常に実用的な方法が用意されています。初期化時に問題がなければ、ターミナルに関連するメッセージが表示されますポジションが検出された場合、EAがどのポジションチケットを観察するのかを示すメッセージも表示されます注文が見つかった場合、EAが見つけた指値注文チケットについて知らせるメッセージも表示されます

LastErrorの値は、EAがある時点でオフラインになったかどうかを確認する方法として使用されます。エラーリストにメッセージの種類を増やすことで、実際に何が起こったのかをより正確に把握できるようにすると面白いかもしれません。


ヘッジ口座における自動EAの問題点

特にプログラミングを学び始めた方にとっては、すべてが美しく素晴らしいものに見えるかもしれませんが、ここでは、自動EAでより高い堅牢性を実現するために開発を続けます。ヘッジ口座で使用した場合、システムに潜在的な問題が残っています。EAがサーバに注文やリクエストを送信するためのコードに進む前なのにです。問題は、ネッティング口座のように、新規成行注文の入力や指値注文の執行によってポジションが変更されるとサーバが平均価格を作成するのとは異なり、ヘッジ口座では、単に、サーバから来るコントロールはそれほど多くないという点にあります。

ヘッジ口座の問題点は、ポジションがあって指値注文が執行されても、ポジションが直接変更されることがないことです。指値注文が実行されると、新しいポジションが開設されることがあり、実際にそうなります。この新しいポジションは、価格を固定することができるので、利益も損失もないのですが、全体のポジションを増やすこともできます。注文が実行されると同時に発生します。

この委細は、ヘッジ口座に存在するもので、別の対策を講じざるを得ません。ポジションが開いている場合や、指値注文がすでにオーダーブックにある場合に、EAが市場に注文を送信するのを防ぐことができます。これは、私が示しているコードに基づいて、簡単におこなうことができますが、問題は、初期化中にEAがヘッジ口座のポジションと指値注文を見つける可能性があることです。上で説明したように、これはネッティング口座では問題ではありません。

この場合、EAはどうすればいいのでしょうか。覚えていらっしゃるように、EAを制御するC_Managerクラスは、2つのポジションや2つの指値注文を持つことを許可しません。この場合、指値注文を削除するか、ポジションを決済する必要があります。自動EAでこのような事態を許してはならないので、何らかの方法で、何とかしなければなりません。自動売買EAは、同時に2つ以上のポジション、または同時に2つ以上の指値注文で動作してはなりません。手動EAでは事情が異なります。

そのため、ポジションを決済するか、指値注文を削除するかのどちらの対策をとるべきかを判断する必要があります。ポジションを決済したい場合、C_Ordersクラスにはすでにそのための手順が用意されています。しかし、指値注文を削除する必要がある場合、C_Ordersクラスには適切なプロシージャがないため、そのための方法を実装する必要があるのです。この時点から、指値注文を削除するシステムを有効にしていきましょう。そのために、新しいコードを追加していきます。

class C_Orders : protected C_Terminal
{
        protected:
//+------------------------------------------------------------------+
inline const ulong GetMagicNumber(void) const { return m_MagicNumber; }
//+------------------------------------------------------------------+
                void RemoveOrderPendent(const ulong ticket)
                        {
                                ZeroMemory(m_TradeRequest);
                                m_TradeRequest.action   = TRADE_ACTION_REMOVE;
                                m_TradeRequest.order    = ticket;
                                ToServer();
                        };

// ... The rest of the class code 

}

コード内のいくつかの詳細に注目してください。1つ目は、EAでC_Ordersクラスを直接使おうとしても、先に説明した理由により、このコードにアクセスすることができないことです。2つ目は、指値注文の削除には使われるが、ポジションの決済や指値注文の修正には使われないことです。

つまり、このコードはC_Ordersクラスですでに実装されているのです。C_Managerに戻って、自動EAがヘッジ口座で実行されていて、すでにポジションを持っている場合に、指値注文を持つことを防ぐシステムを実装することができます。しかし、ポジションを決済し、指値注文を維持したいのであれば、コードを変更するだけで希望通りの動作になります。唯一起こりえないことは、ヘッジ口座で稼働している自動売買EAが、ポジションと指値注文の両方を持つことです。これは許されることではありません。

重要:ヘッジ口座であれば、同じ資産で複数のEAを稼働させることが可能です。この場合、一方のEAがポジションを持ち、もう一方のEAが指値注文を持っていても、両EAの動作に何ら影響を与えることはありません。この場合、それらは独立した存在となります。そのため、同じ資産に対して、複数のポジションや複数の指値注文がある場合があります。このような状況は、1つのEAではありえません。また、これは自動EAにのみ関係するものです。これを理解して覚えておくことは極めて重要なので、何度も繰り返します。

コンストラクタのコードで、最初にポジションをキャプチャし、その後で注文をキャプチャしていることにお気づきかもしれません。これにより、必要に応じて注文を削除することが容易になります。しかし、ポジションを決済して注文を残したい場合は、コンストラクタでこれを反転させ、最初に注文を取り込み、次にポジションを取り込むようにすればよいのです。その後、ニーズがあればポジションが決済されます。検討中のケースでどうするか、見てみましょう。ポジションをキャプチャし、必要に応じて、見つかった指値注文を削除します。そのためのコードを以下に示します。

inline void LoadOrderValid(void)
                        {
                                ulong value;
                                
                                for (int c0 = OrdersTotal() - 1; (c0 >= 0) && (_LastError == ERR_SUCCESS); c0--)
                                {
                                        if ((value = OrderGetTicket(c0)) == 0) continue;
                                        if (OrderGetString(ORDER_SYMBOL) != _Symbol) continue;
                                        if (OrderGetInteger(ORDER_MAGIC) != GetMagicNumber()) continue;
                                        if ((m_bAccountHedging) && (m_Position.Ticket > 0))
                                        {
                                                RemoveOrderPendent(value);
                                                continue;
                                        }
                                        if (m_TicketPending > 0) SetUserError(ERR_Unknown); else m_TicketPending = value;
                                }
                        }

必要な変更は、強調表示されたコードを追加することです。指値注文を削除するのは簡単ですが、ここでは注文を削除するだけであることに注目してください。ポジションがあり、口座の種類がヘッジの場合、指値注文が削除される事態が発生します。しかし、ネッティング口座を持っていたり、ポジションがない場合は、このコードは実行されないので、EAをスムーズに動作させることができます。

ポジションを決済して指値注文を維持したい場合もあるので、この場合、どのようなコードにすればよいかを見てみましょう。指値注文の読み込みコードは変更する必要はなく、上記のコードを使用しますが、いくつかの変更が必要です。1番目は、ポジションを読み込むプロシージャに次のコードを追加することです。

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))
                                        {
                                                ClosePosition(value);
                                                continue;
                                        }
                                        if (m_Position.Ticket > 0) SetUserError(ERR_Unknown); else
					{
						m_Position.Ticket = value;
						SetInfoPositions();
					}
                                }
                        }

強調表示されたコードを追加することで、指値注文を維持したまま、ポジションを決済することができます。しかし、ここには1つの詳細があります。ヘッジ口座の指値注文を維持し、ポジションを決済するためには、コンストラクタのコード内の1項目を以下のように変更する必要があります。

                C_Manager(const ulong magic, double FinanceStop, double FinanceTake, uint Leverage, bool IsDayTrade, double Trigger)
                        :C_Orders(magic),
                        m_bAccountHedging(false),
                        m_TicketPending(0),
                        m_Trigger(Trigger)
                        {
                                string szInfo;
                                
                                ResetLastError();
                                ZeroMemory(m_Position);
                                m_InfosManager.FinanceStop = FinanceStop;
                                m_InfosManager.FinanceTake = FinanceTake;
                                m_InfosManager.Leverage    = Leverage;
                                m_InfosManager.IsDayTrade  = IsDayTrade;
                                switch ((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE))
                                {
                                        case ACCOUNT_MARGIN_MODE_RETAIL_HEDGING:
                                                m_bAccountHedging = true;
                                                szInfo = "HEDGING";
                                                break;
                                        case ACCOUNT_MARGIN_MODE_RETAIL_NETTING:
                                                szInfo = "NETTING";
                                                break;
                                        case ACCOUNT_MARGIN_MODE_EXCHANGE:
                                                szInfo = "EXCHANGE";
                                                break;
                                }
                                Print("Detected Account ", szInfo);
                                LoadOrderValid();
                                LoadPositionValid();
                                if (_LastError == ERR_SUCCESS)
                                {
                                        szInfo = "Successful upload...";
                                        szInfo += StringFormat("%s", (m_Position.Ticket > 0 ? "\nTicket Position: " + (string)m_Position.Ticket + "\n" : ""));
                                        szInfo += StringFormat("%s", (m_TicketPending > 0 ? "\nTicket Order: " + (string)m_TicketPending : ""));
                                        Print(szInfo);
                                }
                        }

変更に気づかれていないかもしれませんが、前のセクションの終わりにあるのコードと比較すると、強調表示された部分が異なっていることがおわかりかと思います。 この場合、以前のバージョンでは注文を削除していたのに、ポジションは決済されます。これがプログラミングの醍醐味です。時には、シンプルな詳細が大きな違いになることもあります。ここでは、コードの実行順序を変えただけですが、結果は全く異なります。

理論上、これまで考えてきたコードには何の問題もなく、完璧に動作するはずです。ただし、これは理論上の話です。取引サーバがエラーを報告するのは、送信されるリクエストに何か問題があるためではなく、起こりうる何らかの相互作用のためであることが判明する場合があります。_LastError変数には、何らかの失敗を示す値が格納されます。

致命的でないため許容できる失敗もあれば、無視できない失敗もあります。この違いを理解してこの考えを受け入れるなら、特定のコード部分にResetLastErrorを呼び出すことで、何らかのエラーが発生したためにEAがチャートから投げ出されるのを防ぐことができます。このエラーは、ほとんどの場合、EAが原因ではなく、EAと取引サーバ間の誤った対話によって発生します。

この初期段階では、これらの呼び出しをどこで追加できるかは紹介しません。このようにすることで、ある時点で無差別にこれらの呼び出しをおこなったり、_LastError変数に含まれる値を無視したりする誘惑に駆られないようにしています。 これでは、強力で堅牢、かつ信頼性の高い自動化EAを構築するというテーマが崩れてしまいます。


結論

今回は、EAを自動化するためには、常に安全・安定・堅牢な方法を考える必要があることを示すために、ごく基本的なことを紹介しました。自動で動くEAのプログラミングは、経験の浅い人でも問題なくできるものではなく、プログラマーに細心の注意が必要な非常に難しい作業です。

次回は、自動化されたEAを手に入れるために、さらに必要な実装を考えてみます。チャートに載せても大丈夫なように安全にする方法を検討します。苦労して手に入れた資産に損害を与えることのないよう、常に十分な注意と正しい対策を持って行動しなければなりません。