English Русский 中文 Español Deutsch Português 한국어 Français Italiano Türkçe
preview
一からの取引エキスパートアドバイザーの開発(第18部):新規受注システム(I)

一からの取引エキスパートアドバイザーの開発(第18部):新規受注システム(I)

MetaTrader 5トレーディング | 1 9月 2022, 12:34
546 0
Daniel Jose
Daniel Jose

はじめに

本連載の第1稿「一からの取引エキスパートアドバイザーの開発」以降、このEAは、同じチャート上注文システムモデルを維持しながら様々な変更と改良を受けてきました。このEAはとてもシンプルで機能的ですが、実際の取引には適さない場面も少なくありません。

もちろん、元のシステムに何かを追加して未決注文に関する情報を得られるようにすることは可能ですが、コードがわかりづらくなって、最終的には改善プロセスが悪夢と化してしまいます。コードが大きくなり複雑になりすぎると、自分なりの方法を持っていても時間が経つにつれて失われていきます。

よって、使用する注文モデルからして全く違うシステムを作る必要があります。同時に、トレーダーが理解しやすく、かつ安全に作業するために必要な情報をすべて提供する必要があります。


重要事項

この記事で説明するシステムは、ACCOUNT_MARGIN_MODE_RETAIL_NETTING(ネッティング口座)用に設計されており、銘柄ごとに1つのポジションのみを持つことが可能です。 ACCOUNT_MARGIN_MODE_RETAIL_HEDGING(ヘッジ口座)を使用している場合、お互いに干渉しないポジションを必要なだけ持つことができるため、この記事がEAに提供できるものはありません。最終的なコードからすべての修正を元に戻して削除してください。

ACCOUNT_MARGIN_MODE_RETAIL_HEDGINGモードが最もよく使われるのは、EAを自動的に実行して自分の参加なしでポジションをオープンおよびクローズすると同時に、手動で取引を継続したい場合です。さらに、EAが取引する資産と同じ資産を取引する場合です。ヘッジを使えば、EAのアクティビティは自分の取引に影響を与えないので、独立したポジションを持つことができます。

このため、追加または修正されるコード部分をすべてハイライト表示することにしました。すべての変更と追加をゆっくりと徐々に実装していくので、コードから何かを削除する必要がある場合、関連するコード部分を容易に見つけることができます。

修正は削除できるのですが、システムをチェックしている部分があります。このエキスパートアドバイザー(EA)は特定のモデルをチェックしてそれに応じて調整するため、ここで検討する変更を保存しても、どちらのタイプの取引口座(NETTINGとHEDGING)でも使用することができます。


1.0.計画

実際、システムに追加され、適切な価格に達して出された注文がどうなるかを理解することが先決です。多くのトレーダーは、このことを知らない(というより、このことについて考えたことがない)ため、この考えを理解するためのテストをおこなっておらず、適切かつ信頼できるシステムを実装していません。

実際に何が起こるかを理解するために、簡単な例で分析してみましょう。ある資産のポジション(仮に買い)を1ロットのイニシャルボリュームで持っているとします。その後、少し高い値段で新たに2ロットの買い注文を出すとします。今のところ、特に問題はありません。ただ、この2ロットを買ったとたんに何かが起きて、問題が起こります。

この2ロットを買うと3ロットを持つことになりますが、システムは初値を更新し、平均値に設定します。さて、これは明確に思え、誰もが理解していることです。しかし、新しいポジションのストップロスやテイクプロフィットのレベルはどうなるのでしょうか。

多くのトレーダーはその答えを知りませんが、考えた方がいいと思います。すべての取引でOCO注文システム(One Cancel The Other)を使用している場合、総取引量の一部でポジションをオープンまたはクローズするたびに、興味深いことに気づくことができます。

クロス注文についての記事で、MetaTrader 5の標準システムを使わずに、チャート上に直接テイクプロフィットやストップロスを配置する方法を紹介しました。実際、この方法はMetaTrader 5システムとほとんど同じです。なぜなら、その機能はプラットフォームの機能に非常に近く、しかし適切な比率を持つものだからです。ただし、テストしてみたところ、未決済のOCO注文と未決注文がある場合、価格が注文で指定した値に達したため、OCO注文も取引システムにキャプチャされることがわかりました。新しい平均価格に加え、テイクプロフィットとストップロスの値も、最後にキャプチャされたOCO注文で指定されたものに変更されています。設定によっては、取引システムが新しいテイクプロフィットとストップロスを報告した後、EAがすぐにそれを決済します。

これは、EAに実装された以下のチェックにより発生します。

inline double CheckPosition(void)
{
        double Res = 0, last, sl;
        ulong ticket;
                                
        last = SymbolInfoDouble(Terminal.GetSymbol(), SYMBOL_LAST);
        for (int i0 = PositionsTotal() - 1; i0 >= 0; i0--) if (PositionGetSymbol(i0) == Terminal.GetSymbol())
        {
                ticket = PositionGetInteger(POSITION_TICKET);
                Res += PositionGetDouble(POSITION_PROFIT);
                sl = PositionGetDouble(POSITION_SL);
                if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
                {
                        if (last < sl) ClosePosition(ticket);
                }else
                {
                        if ((last > sl) && (sl > 0)) ClosePosition(ticket);
                }
        }
        return Res;
};

ハイライト表示された行はこのチェックをおこない、価格がテイクプロフィット値とストップロス値で形成されるチャネルから外れた場合に注文を決済じます。

EAの故障、取引システムの問題や不具合の話ではないので、これを知っておくことは重要です。本当の問題は、ほとんどの場合、私たちは何が起こっているのかに十分な注意を払わず、この事実から目をそらすことができなくなるまで、この種の問題を無視するということです。

価格を平均化しない方法で取引すれば、このようなことは起こりませんが、平均価格が適切であり、必要でさえある操作も非常に広い範囲に存在します。そのため、このような場合、上記の詳細を知らずにシステムを動かしていると、以前にポジションのOCO注文を適宜変更していたとしても、思ったより早くポジションを決済してしまうことがあります。OCO未決注文がキャプチャされるとすぐに、しきい値(しきい値について話すときはいつも、前回のテイクプロフィットとストップロスを参照)は、最近キャプチャされたOCO未決注文で指定されたものに置き換えられます。

これを修正または回避する方法があります。少なくとも、すでにポジションがあるときは、OCO注文を使用しないということです。システムに渡されるその他の注文は、テイクプロフィットやストップロスを設定しない単純なタイプでなければならないということです。

基本的にはそれだけです。しかし、EAがチャート上にあるのは、私たちをサポートして楽にしてくれるためです。後で使えないのであれば、EAをプログラムしても意味がありません。


2.0.新しいシステムの実装

システムを実装して、私たちをサポートしてエラーを回避する手助けをするという期待通りにEAを動作させるためには、コードを少し変更する必要があります。

これらの変更はそれほど難しくはありませんが、望まないタイミングでOCO注文が発生して操作が本当に混乱するようなリスクをなくすことができます。

以下の変更から始めましょう。


2.0.1.C_Routerクラスの修正

C_Routerクラスは、注文を解析して送信する役割を担っています。そこにprivate変数を追加してみましょう。EAで取引する資産にポジションが見つかった場合、この変数に関連情報が格納されます。EAがポジションがあるかどうかを知りたがるたびに、そのことを教えてくれます。

実装は以下のコードに示されていますが、このコードはさらに記事の中で変更される予定です。実際にどのように修正したのかは、順を追って説明したいと思います。

//+------------------------------------------------------------------+
inline bool ExistPosition(void) const { return m_bContainsPosition; }
//+------------------------------------------------------------------+
void UpdatePosition(void)
{
        static int memPositions = 0, memOrder = 0;
        ulong ul;
        int p, o;
                                
        p = PositionsTotal();
        o = OrdersTotal();
        if ((memPositions != p) || (memOrder != o))
        {
                ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false);
                RemoveAllsLines();
                ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);
                memOrder = o;
                memPositions = p;
                m_bContainsPosition = false;
        };
        for(int i0 = p; i0 >= 0; i0--) if(PositionGetSymbol(i0) == Terminal.GetSymbol())
        {
                ul = PositionGetInteger(POSITION_TICKET);
                m_bContainsPosition = true;
                SetLineOrder(ul, PositionGetDouble(POSITION_PRICE_OPEN), HL_PRICE, false);
                SetLineOrder(ul, PositionGetDouble(POSITION_TP), HL_TAKE, true);
                SetLineOrder(ul, PositionGetDouble(POSITION_SL), HL_STOP, true);
        }
        for (int i0 = o; i0 >= 0; i0--) if ((ul = OrderGetTicket(i0)) > 0) if (OrderGetString(ORDER_SYMBOL) == Terminal.GetSymbol())
        {
                SetLineOrder(ul, OrderGetDouble(ORDER_PRICE_OPEN), HL_PRICE, true);
                SetLineOrder(ul, OrderGetDouble(ORDER_TP), HL_TAKE, true);
                SetLineOrder(ul, OrderGetDouble(ORDER_SL), HL_STOP, true);
        }
};
//+------------------------------------------------------------------+


ハイライト表示された行は、EAコードの特定の場所に後で追加されるすべてのチェックを実行するために必要な追加部分を示しています。

ある意味、C_Routerクラスだけですべてのチェックと調整を実装することも可能ですが、これでは十分とは言えません。これについては、後ほど説明します。とりあえず、修正を続けましょう。上記のチェックを作成した後、新しく追加された変数を適切に初期化するためのコンストラクタを追加しましょう。

C_Router() : m_bContainsPosition(false) {}


未決注文を出す関数を次のように編集します。

ulong CreateOrderPendent(const bool IsBuy, const double Volume, const double Price, const double Take, const double Stop, const bool DayTrade = true)
{
        double last = SymbolInfoDouble(Terminal.GetSymbol(), SYMBOL_LAST);
                                
        ZeroMemory(TradeRequest);
        ZeroMemory(TradeResult);
        TradeRequest.action             = TRADE_ACTION_PENDING;
        TradeRequest.symbol             = Terminal.GetSymbol();
        TradeRequest.volume             = Volume;
        TradeRequest.type               = (IsBuy ? (last >= Price ? ORDER_TYPE_BUY_LIMIT : ORDER_TYPE_BUY_STOP) : (last < Price ? ORDER_TYPE_SELL_LIMIT : ORDER_TYPE_SELL_STOP));
        TradeRequest.price              = NormalizeDouble(Price, Terminal.GetDigits());
        TradeRequest.sl                 = NormalizeDouble((m_bContainsPosition ? 0 : Stop), Terminal.GetDigits());
        TradeRequest.tp                 = NormalizeDouble((m_bContainsPosition ? 0 : Take), Terminal.GetDigits());
        TradeRequest.type_time          = (DayTrade ? ORDER_TIME_DAY : ORDER_TIME_GTC);
        TradeRequest.stoplimit          = 0;
        TradeRequest.expiration         = 0;
        TradeRequest.type_filling       = ORDER_FILLING_RETURN;
        TradeRequest.deviation          = 1000;
        TradeRequest.comment            = "Order Generated by Experts Advisor.";
        if (!Send()) return 0;
                                                                
        return TradeResult.order;
};


ハイライト表示された部分が実装された変更点です。

では、更新されたコードに戻り、新たな修正を加えてみましょう。OnTrade関数によって呼び出され、注文が変更されるたびに呼び出されることを覚えておいてください。これは、以下のコードで見ることができます。

void UpdatePosition(void)
{
        static int memPositions = 0, memOrder = 0;
        ulong ul;
        int p, o;
                                
        p = PositionsTotal();
        o = OrdersTotal();
        if ((memPositions != p) || (memOrder != o))
        {
                ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false);
                RemoveAllsLines();
                ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);
                memOrder = o;
                memPositions = p;
                m_bContainsPosition = false;
        };
        for(int i0 = p; i0 >= 0; i0--) if(PositionGetSymbol(i0) == Terminal.GetSymbol())
        {
                ul = PositionGetInteger(POSITION_TICKET);
                m_bContainsPosition = true;
                SetLineOrder(ul, PositionGetDouble(POSITION_PRICE_OPEN), HL_PRICE, false);
                SetLineOrder(ul, PositionGetDouble(POSITION_TP), HL_TAKE, true);
                SetLineOrder(ul, PositionGetDouble(POSITION_SL), HL_STOP, true);
        }
        for (int i0 = o; i0 >= 0; i0--) if ((ul = OrderGetTicket(i0)) > 0) if (OrderGetString(ORDER_SYMBOL) == Terminal.GetSymbol())
        {
                if (m_bContainsPosition)
                {
                        ModifyOrderPendent(ul, OrderGetDouble(ORDER_PRICE_OPEN), 0, 0);
                        (OrderSelect(ul) ? 0 : 0);
                }
                SetLineOrder(ul, OrderGetDouble(ORDER_PRICE_OPEN), HL_PRICE, true);
                SetLineOrder(ul, OrderGetDouble(ORDER_TP), HL_TAKE, true);
                SetLineOrder(ul, OrderGetDouble(ORDER_SL), HL_STOP, true);
        }
};

さて、すでにポジションがあるときに、ユーザーが単純な未決注文をOCO未決注文に変換しないようにする必要があります。これは、ユーザーがツールボックスを開いて、テイクプロフィットやストップロスを編集しようとした場合です。ユーザーがこれを実行しようとすると、取引サーバーがOnTrade関数で知らせてくれるので、EAはすぐにその変更を知り、ユーザーがおこなった変更を取り消してシステムの信頼性を確保することができるのです。

しかし、もう1つ修正しなければならないことがあります。それは、成行注文に関することです。この変更は、チェックに関連する修正を必要としないため、非常に単純です。新しい関数のコードを以下に示します。

ulong ExecuteOrderInMarket(const bool IsBuy, const double Volume, const double Price, const double Take, const double Stop, const bool DayTrade = true)
{
        ZeroMemory(TradeRequest);
        ZeroMemory(TradeResult);
        TradeRequest.action             = TRADE_ACTION_DEAL;
        TradeRequest.symbol             = Terminal.GetSymbol();
        TradeRequest.volume             = Volume;
        TradeRequest.type               = (IsBuy ? ORDER_TYPE_BUY : ORDER_TYPE_SELL);
        TradeRequest.price              = NormalizeDouble(Price, Terminal.GetDigits());
        TradeRequest.sl                 = NormalizeDouble((m_bContainsPosition ? 0 : Stop), Terminal.GetDigits());
        TradeRequest.tp                 = NormalizeDouble((m_bContainsPosition ? 0 : Take), Terminal.GetDigits());
        TradeRequest.type_time          = (DayTrade ? ORDER_TIME_DAY : ORDER_TIME_GTC);
        TradeRequest.stoplimit          = 0;
        TradeRequest.expiration         = 0;
        TradeRequest.type_filling       = ORDER_FILLING_RETURN;
        TradeRequest.deviation          = 1000;
        TradeRequest.comment            = "[ Order Market ] Generated by Experts Advisor.";
        if (!Send()) return 0;
                                                
        return TradeResult.order;
};


奇妙に思えるかもしれませんが、これらの変更は十分なレベルのセキュリティ(少なくとも許容レベル)をすでに提供しており、EAが取引している同じ資産のポジションがすでにある場合に、OCO注文、指値注文、成行注文を見逃さないようにします。このように、実際に注文を出す仕組みはEAが担当することになります。

まあ、あまりにも美しく、素晴らしすぎる話です。これですでに安全性が確保されていると思われるかもしれませんが、そうとも言い切れません。これらの修正により、OCO注文が未決のままになったり、ポジションを持っているときにマーケットに入らないことが保証されています。しかし、これらの修正には致命的な欠陥があり、適切に修正されないと、この欠陥が大きな頭痛の種となり、途方もない損失を出して、口座が空になったり、証拠金不足のために証券会社によってポジションがクローズされたりすることがあります。

未決注文がポジションの制限内にあるかどうかのチェックはありません。現状では、ポジションの制限外にOCO未決注文を追加すると、EAがその注文をOCOタイプとして認めないため、非常に危険な状態になっています。言い換えれば、この注文は、テイクプロフィットもストップロスもない注文になるので、ポジションをクローズしてこの未決注文を入力するときは、できるだけ早くこれらのレベルを調整する必要があります。これを忘れると、指値のないポジションを持つことになる危険性があります。

レベルを設定するには、メッセージウィンドウを開き、注文を開き、レベル値を編集する必要があります。この問題は近日中に修正される予定です。では、現在の問題を解決しましょう。

未決注文でのEAの動作方法を変更する必要があります。ユーザーがレベルなしで注文を作成したい場合、EAはそれを通常のものとして扱いますが、ユーザーが指値注文を作成してしまうと、EAはそれに応じて注文を調整する必要があります。注文がポジションの外にある場合は指値を設定し、注文がポジション内にある場合は指値注文の指値を削除します。


2.0.2.限度内の作業

まず、検証用の限度を作成します。これをするのはとても簡単です。ただし、より多くのケースに拡張される可能性のある2つのケースが考えられるので、細かい配慮が必要です。何をすべきか理解するためには、この2つのケースで十分です。


最初のケースは上記の通りです。限度(この場合の限度は勾配のある領域で、つまり買いか売りかのポジションを持ち、上限を設定している)の外側で未決注文を持つことになります。価格がこの限度に達するかそれを超えると、ポジションは決済されます。この場合、未決注文はユーザーによってOCO注文として設定され、EAはユーザーによって設定された注文方法(単純な注文またはOCO注文)を受け入れなければなりません。このケースでは、EAは干渉してはいけません。

2つ目のケースは以下の通りです。ここでは、未決注文は、ポジションによって制限された領域内にあります。この場合、EAはユーザーによって設定される可能性のある指値を削除する必要があります。


指値がどこまで行っても、買いでも売りでも、この領域に注文が入ると、EAは指値を未決注文から削除しなければなりません。しかし、この領域を離れたら、EAは注文をユーザーによって設定された方法のままに必要があります。

これを定義した後、以下のようにいくつかの変数を作成する必要があります。

class C_Router : public C_HLineTrade
{
        protected:
                MqlTradeRequest TradeRequest;
                MqlTradeResult  TradeResult;
        private  :
                bool            m_bContainsPosition;
                struct st00
                {
                        double  TakeProfit,
                                StopLoss;
                        bool    IsBuy;
                }m_Limits;

// ... Rest of the code

これで、OnTrade注文のトリガーイベントが発生した段階で、制限を確認することができるようになりました。そこで、もう一度、C_Routerクラスのupdate関数を修正します。

// Rest of the code....

//+------------------------------------------------------------------+
#define macro_MAX(A, B) (A > B ? A : B)
#define macro_MIN(A, B) (A < B ? A : B)
void UpdatePosition(void)
{
        static int      memPositions = 0, memOrder = 0;
        ulong           ul;
        int             p, o;
        double          price;
        bool            bTest;
                                
        p = PositionsTotal();
        o = OrdersTotal();
        if ((memPositions != p) || (memOrder != o))
        {
                ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false);
                RemoveAllsLines();
                ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);
                memOrder = o;
                memPositions = p;
                m_bContainsPosition = false;
                m_Limits.StopLoss = -1;
                m_Limits.TakeProfit = -1;
                m_Limits.IsBuy = false;
        };
        for(int i0 = p; i0 >= 0; i0--) if(PositionGetSymbol(i0) == Terminal.GetSymbol())
        {
                ul = PositionGetInteger(POSITION_TICKET);
                m_bContainsPosition = true;
                SetLineOrder(ul, PositionGetDouble(POSITION_PRICE_OPEN), HL_PRICE, false);
                SetLineOrder(ul, m_Limits.TakeProfit = PositionGetDouble(POSITION_TP), HL_TAKE, true);
                SetLineOrder(ul, m_Limits.StopLoss = PositionGetDouble(POSITION_SL), HL_STOP, true);
                m_Limits.IsBuy = PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY;
        }
        for (int i0 = o; i0 >= 0; i0--) if ((ul = OrderGetTicket(i0)) > 0) if (OrderGetString(ORDER_SYMBOL) == Terminal.GetSymbol())
        {
                price = OrderGetDouble(ORDER_PRICE_OPEN);
                if ((m_Limits.StopLoss == -1) && (m_Limits.TakeProfit == -1)) bTest = false; else
                {
                        bTest = ((!m_Limits.IsBuy) && (m_Limits.StopLoss > price));
                        bTest = (bTest ? bTest : (m_Limits.IsBuy) && (m_Limits.StopLoss < price));
                        bTest = (bTest ? bTest : ((macro_MAX(m_Limits.TakeProfit, m_Limits.StopLoss) > price) && (macro_MIN(m_Limits.TakeProfit, m_Limits.StopLoss) < price)));
                }
                if ((m_bContainsPosition) && (bTest))
                {
                        ModifyOrderPendent(ul, price, 0, 0);
                        (OrderSelect(ul) ? 0 : 0);
                }
                SetLineOrder(ul, price, HL_PRICE, true);
                SetLineOrder(ul, OrderGetDouble(ORDER_TP), HL_TAKE, true);
                SetLineOrder(ul, OrderGetDouble(ORDER_SL), HL_STOP, true);
        }
};
#undef macro_MAX
#undef macro_MIN
//+------------------------------------------------------------------+

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

このクラスは、未決のポジションが、未決OCO注文を持つことができない領域内にある場合と、その領域外にある場合を区別して処理するようになります。この関数は、注文システムの状態が変化するたびに呼び出されます。まず最初に、変数を適切に初期化します。

m_Limits.StopLoss = -1;
m_Limits.TakeProfit = -1;
m_Limits.IsBuy = false;


これが終わったら、ポジションがあるかどうかを確認します。これはいつでもできます。ポジションを持つとすぐに、未決OCO注文を持つことができない領域を区切ります。これはこの時点で達成されます。

SetLineOrder(ul, m_Limits.TakeProfit = PositionGetDouble(POSITION_TP), HL_TAKE, true);
SetLineOrder(ul, m_Limits.StopLoss = PositionGetDouble(POSITION_SL), HL_STOP, true);
m_Limits.IsBuy = PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY;


これで、各未決注文が制限領域内にあるかどうかを確認することができます。重要な注意点:ここで、注文が買いか売りかを知る必要があります。なぜなら、テイクプロフィットではなく、ストップロスを持つかもしれないからです。この場合はポジションの種類を知ることが必要です。これを理解するために、以下の図をご覧ください。

 

売りポジションの場合、ストップロスは上限を指定します...

 

買いポジションの場合、ストップロスは下限となります。

つまり、未決注文は、上限以上出されればOCOタイプになる場合もあります。それ以外の場合は、下限以下に出されます。しかし、以下のように、未決注文がOCOタイプである場合もあり得ます。

 

限度外の未決注文は典型的なケースです。

これを確認するために、以下のフラグメントを使用します。

price = OrderGetDouble(ORDER_PRICE_OPEN);
if ((m_Limits.StopLoss == -1) && (m_Limits.TakeProfit == -1)) bTest = false; else
{
        bTest = ((!m_Limits.IsBuy) && (m_Limits.StopLoss > price));
        bTest = (bTest ? bTest : (m_Limits.IsBuy) && (m_Limits.StopLoss < price));
        bTest = (bTest ? bTest : ((macro_MAX(m_Limits.TakeProfit, m_Limits.StopLoss) > price) && (macro_MIN(m_Limits.TakeProfit, m_Limits.StopLoss) < price)));
}

ポジションがあるかどうかが確認されます。ポジションがない場合、EAはユーザーが指定した注文設定を尊重する必要があります。ポジションが見つかった場合、以下の順序でチェックがおこなわれます。

  1. ショートであれば、未決注文を出す価格は、ポジションのストップロス値より高くなければなりません。
  2. ロングであれば、未決注文を出す価格は、ポジションのストップロス値より低くなければなりません。
  3. システムがまだOCOタイプとして注文を受け付けようとしている場合、最後にもう一度、注文がポジションから外れているかどうかを確認するテストをおこないます。

これができれば、未決注文をユーザーが設定した方法で残すか残さないかを確認でき、続行できますが、次のステップに進む前に、最後にもう1つ付け加えなければならないことがあります。実は、冒頭で述べた最後のチェックですが、これは以下の断片にあります。

void UpdatePosition(void)
{

// ... Internal code...

        for(int i0 = p; i0 >= 0; i0--) if(PositionGetSymbol(i0) == Terminal.GetSymbol())
        {
                ul = PositionGetInteger(POSITION_TICKET);
                m_bContainsPosition = true;
                SetLineOrder(ul, PositionGetDouble(POSITION_PRICE_OPEN), HL_PRICE, false);
                SetLineOrder(ul, m_Limits.TakeProfit = PositionGetDouble(POSITION_TP), HL_TAKE, true);
                SetLineOrder(ul, m_Limits.StopLoss = PositionGetDouble(POSITION_SL), HL_STOP, true);
                m_Limits.IsBuy = PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY;
        }
        if (AccountInfoInteger(ACCOUNT_TRADE_MODE) == ACCOUNT_MARGIN_MODE_RETAIL_HEDGING)
                m_bContainsPosition = false;
        for (int i0 = o; i0 >= 0; i0--) if ((ul = OrderGetTicket(i0)) > 0) if (OrderGetString(ORDER_SYMBOL) == Terminal.GetSymbol())
        {
                price = OrderGetDouble(ORDER_PRICE_OPEN);
                if (m_bContainsPosition)
                {
                        if ((m_Limits.StopLoss == -1) && (m_Limits.TakeProfit == -1)) bTest = false; else
                        {
                                bTest = ((!m_Limits.IsBuy) && (m_Limits.StopLoss > price));
                                bTest = (bTest ? bTest : (m_Limits.IsBuy) && (m_Limits.StopLoss < price));
                                bTest = (bTest ? bTest : ((macro_MAX(m_Limits.TakeProfit, m_Limits.StopLoss) > price) && (macro_MIN(m_Limits.TakeProfit, m_Limits.StopLoss) < price)));
                        }
                        if (bTest)
                        {
                                ModifyOrderPendent(ul, price, 0, 0);
                                (OrderSelect(ul) ? 0 : 0);
                        }
                }
                SetLineOrder(ul, price, HL_PRICE, true);
                SetLineOrder(ul, OrderGetDouble(ORDER_TP), HL_TAKE, true);
                SetLineOrder(ul, OrderGetDouble(ORDER_SL), HL_STOP, true);
        }
};


ハイライト表示された部分では、口座の種類がHEDGINGであるかどうかを確認します。HEDGINGであれば、仮に変数が制限を扱う必要があると示したとしても、この時点でその必要がないことが示されます。したがって、EAは発生しうる制限を無視し、ユーザーによって設定された方法で注文を扱います。これは非常に簡単なチェックですがシステム全体が正しく機能するためにはこの段階でおこなう必要があります。


2.1.0.ポジショニングシステムの調整

C_Routerオブジェクトクラスの調整により、ほとんどの問題は解決されましたが、システムはまだ十分だとは言えません。マウスによる位置決めのシステムを修正するという課題もあり、これも同様に重要なステップです。C_OrderViewクラスに存在するシステムがどのように動作すべきかを定義しなければならないので、これはいくつかのことを意味します。

実際にどのように操作したいかについての大きな問題は、未決注文がポジションの制限を離れるときにC_OrderViewクラスで制限を作成するかどうかです。

毎回そうしたくなるものですが、この判断には考慮しなければならないことがあります。でも、切り裂きジャックが言うように、部分ごとにやりましょう。基本的に、C_OrderViewクラスで実際におこなわなければならない変更は、以下のコードに示すものだけです。


inline void MoveTo(uint Key)
{
        static double local = 0;
        datetime dt;
        bool    bEClick, bKeyBuy, bKeySell, bCheck;
        double  take = 0, stop = 0, price;
                                
        bEClick  = (Key & 0x01) == 0x01;    //Left click
        bKeyBuy  = (Key & 0x04) == 0x04;    //SHIFT pressed
        bKeySell = (Key & 0x08) == 0x08;    //CTRL pressed                          
        Mouse.GetPositionDP(dt, price);
        if (bKeyBuy != bKeySell)
        {
                Mouse.Hide();
                bCheck = CheckLimits(price);
        } else Mouse.Show();
        ObjectMove(Terminal.Get_ID(), m_Infos.szHLinePrice, 0, 0, price = (bKeyBuy != bKeySell ? price : 0));
        ObjectMove(Terminal.Get_ID(), m_Infos.szHLineTake, 0, 0, take = (bCheck ? 0 : price + (m_Infos.TakeProfit * (bKeyBuy ? 1 : -1))));
        ObjectMove(Terminal.Get_ID(), m_Infos.szHLineStop, 0, 0, stop = (bCheck ? 0 : price + (m_Infos.StopLoss * (bKeyBuy ? -1 : 1))));
        if((bEClick) && (bKeyBuy != bKeySell) && (local == 0)) CreateOrderPendent(bKeyBuy, m_Infos.Volume, local = price, take, stop, m_Infos.IsDayTrade);
        local = (local != price ? 0 : local);                           
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szHLinePrice, OBJPROP_COLOR, (bKeyBuy != bKeySell ? m_Infos.cPrice : clrNONE));
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szHLineTake, OBJPROP_COLOR, (take > 0 ? m_Infos.cTake : clrNONE));
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szHLineStop, OBJPROP_COLOR, (stop > 0 ? m_Infos.cStop : clrNONE));
};

それだけですか?はい、それだけでいいのです。残りのロジックはすべてC_Routerクラス内にあります。修正されていないものは、MetaTrader 5独自のメッセージングシステムによって実行されます。なぜなら、注文のリスト(未決またはポジション)に変更があった場合、OnTradeルーチンが呼び出されるからです。このとき、C_Routerクラス内の更新ルーチンが起動し、必要な調整がおこなわれます。しかし、このルーチンの中に登場するコードがあり、それがどこにあるのか探すのが困難かもしれません。実際には、C_Routerクラスの中にあり、以下のようになります。

#define macro_MAX(A, B) (A > B ? A : B)
#define macro_MIN(A, B) (A < B ? A : B)
inline bool CheckLimits(const double price)
{
        bool bTest = false;
                                
        if ((!m_bContainsPosition) || ((m_Limits.StopLoss == -1) && (m_Limits.TakeProfit == -1))) return bTest;
        bTest = ((macro_MAX(m_Limits.TakeProfit, m_Limits.StopLoss) > price) && (macro_MIN(m_Limits.TakeProfit, m_Limits.StopLoss) < price));
        if (m_Limits.TakeProfit == 0)
        {
                bTest = (bTest ? bTest : (!m_Limits.IsBuy) && (m_Limits.StopLoss > price));
                bTest = (bTest ? bTest : (m_Limits.IsBuy) && (m_Limits.StopLoss < price));
        }
        return bTest;
};
#undef macro_MAX
#undef macro_MIN


このコードは、C_Routerクラスの更新関数内にあったものです。そこから削除され、呼び出しで置き換えられています。


2.2.0.制限するかしないか、それが問題である

仕事はほぼ終わりましたが、最後の問題が残っています。これが今のところ最も難しい問題でしょう。ここまでの内容を追って理解された方は、システムは非常にうまく機能しているが、OCOとして設定された未決注文がポジションの制限に入るたびに、その注文に設定されていた指値が失われていることにお気づきかもしれません。これは常に起こります。

ただし、万が一、トレーダーがポジションの制限を変更したり、OCOであった注文が単純な注文になった場合、その制限を越えても単純な注文のままとなります。つまり、潜在的な問題があるのです。

もう1つの重要な詳細は、EAをどのように進めていくかということです。その資産に対して単純な注文が出されたことをトレーダーに知らせるべきでしょうか。それとも、単純に注文に制限を設けてOCOにすべきなのでしょうか。

この質問は、EAが本当に取引に役立つことを望んでいるのであれば、非常に重要です。EAが警告を発し、何が起こったかを知らせてくれるのは良いことですが、資産にある場合、ボラティリティの高い時に、それは我々が何が起こっているかを認識する前に、大きな被害を引き起こす可能性があり、それが緩んでそこに滞在しないように、EAが自動的に注文にいくつかの制限を作成することも良いです。

そこで、この問題を解決するために、システムに最後の修正をおこなったのですが、上で説明したように、実際にどう対処するかは真剣に考える必要があります。以下は、私が考えた解決策を実装する方法です。

まず、トレーダーがEAにどの手続きを実行するかを知らせることができる新しい変数を追加しました。以下に示します。

// ... Code ...

input group "Chart Trader"
input int       user20   = 1;              //Leverage factor
input int       user21   = 100;            //Take Profit (financial)
input int       user22   = 75;             //Stop Loss (financial)
input color     user23   = clrBlue;        //Price line color
input color     user24   = clrForestGreen; //Take Profit line color
input color     user25   = clrFireBrick;   //Stop Loss line color
input bool      user26   = true;           //Day Trade?
input bool      user27   = true;           //Always set loose order limits

// ... Rest of the code...

void OnTrade()
{
        Chart.DispatchMessage(CHARTEVENT_CHART_CHANGE, 0, OrderView.UpdateRoof(), C_Chart_IDE::szMsgIDE[C_Chart_IDE::eROOF_DIARY]);
        OrderView.UpdatePosition(user27);
}

// ... Rest of the code...

次に、C_Routerクラスに戻って、3つの新しい関数を追加する必要があります。以下でご覧ください。

//+------------------------------------------------------------------+
void SetFinance(const int Contracts, const int Take, const int Stop)
{
        m_Limits.Contract = Contracts;
        m_Limits.FinanceTake = Take;
        m_Limits.FinanceStop = Stop;
}
//+------------------------------------------------------------------+
inline double GetDisplacementTake(const bool IsBuy, const double Vol) const
{
        return (Terminal.AdjustPrice(m_Limits.FinanceTake * (Vol / m_Limits.Contract) * Terminal.GetAdjustToTrade() / Vol) * (IsBuy ? 1 : -1));
}
//+------------------------------------------------------------------+
inline double GetDisplacementStop(const bool IsBuy, const double Vol) const
{
        return (Terminal.AdjustPrice(m_Limits.FinanceStop * (Vol / m_Limits.Contract) * Terminal.GetAdjustToTrade() / Vol) * (IsBuy ? -1 : 1));
}
//+------------------------------------------------------------------+

このコードは、次の画像で見ることができるように、Chart Traderで通知される値を維持しますが、OCO未決注文でリミットとして使用すべき値も比例して修正されます。

つまり、未決注文がトリガーされたときにEAが最小限のOCO注文を構成できるように、使用する値を取得する場所が既に決まっているのです。ただし、お察しの通り、C_Routerクラスの更新コードに新たな変更を加える必要があります。その変更内容は以下の通りです。

void UpdatePosition(bool bAdjust)
{
        static int      memPositions = 0, memOrder = 0;
        ulong           ul;
        int             p, o;
        long            info;
        double          price, stop, take, vol;
        bool            bIsBuy, bTest;
                        
        p = PositionsTotal();
        o = OrdersTotal();
        if ((memPositions != p) || (memOrder != o))
        {
                ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false);
                RemoveAllsLines();
                ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);
                memOrder = o;
                memPositions = p;
                m_bContainsPosition = false;
                m_Limits.StopLoss = -1;
                m_Limits.TakeProfit = -1;
                m_Limits.IsBuy = false;
        };
        for(int i0 = p; i0 >= 0; i0--) if(PositionGetSymbol(i0) == Terminal.GetSymbol())
        {
                ul = PositionGetInteger(POSITION_TICKET);
                m_bContainsPosition = true;
                SetLineOrder(ul, PositionGetDouble(POSITION_PRICE_OPEN), HL_PRICE, false);
                SetLineOrder(ul, take = PositionGetDouble(POSITION_TP), HL_TAKE, true);
                SetLineOrder(ul, stop = PositionGetDouble(POSITION_SL), HL_STOP, true);
                m_Limits.IsBuy = PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY;
                m_Limits.TakeProfit = (m_Limits.TakeProfit < 0 ? take : (m_Limits.IsBuy ? (m_Limits.TakeProfit > take ? m_Limits.TakeProfit : take) : (take > m_Limits.TakeProfit ? m_Limits.TakeProfit : take)));
                m_Limits.StopLoss = (m_Limits.StopLoss < 0 ? stop : (m_Limits.IsBuy ? (m_Limits.StopLoss < stop ? m_Limits.StopLoss : stop) : (stop < m_Limits.StopLoss ? m_Limits.StopLoss : stop)));
        }
        if ((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_TRADE_MODE) == ACCOUNT_MARGIN_MODE_RETAIL_HEDGING)
                m_bContainsPosition = false;
        for (int i0 = o; i0 >= 0; i0--) if ((ul = OrderGetTicket(i0)) > 0) if (OrderGetString(ORDER_SYMBOL) == Terminal.GetSymbol())
        {
                price = OrderGetDouble(ORDER_PRICE_OPEN);
                take = OrderGetDouble(ORDER_TP);
                stop = OrderGetDouble(ORDER_SL);
		bTest = CheckLimits(price);
                if ((take == 0) && (stop == 0) && (bAdjust) && (!bTest))
                {
                        info = OrderGetInteger(ORDER_TYPE);
                        vol = OrderGetDouble(ORDER_VOLUME_CURRENT);
                        bIsBuy = ((info == ORDER_TYPE_BUY_LIMIT) || (info == ORDER_TYPE_BUY_STOP) || (info == ORDER_TYPE_BUY_STOP_LIMIT) || (info == ORDER_TYPE_BUY));
                        take = price + GetDisplacementTake(bIsBuy, vol);
                        stop = price + GetDisplacementStop(bIsBuy, vol);
                        ModifyOrderPendent(ul, price, take, stop);
                }
                if ((take != 0) && (stop != 0) && (bTest))
                        ModifyOrderPendent(ul, price, take = 0, stop = 0);
                SetLineOrder(ul, price, HL_PRICE, true);
                SetLineOrder(ul, take, HL_TAKE, true);
                SetLineOrder(ul, stop, HL_STOP, true);
        }
};

ハイライト表示された行は、注文がフリーかどうか、EAがそれに介入すべきかどうかをチェックします。EAが介入した場合、Chart Traderで提示された金融価値をもとに、未決注文数量に応じた計算がおこなわれます。そして、収集した情報に基づいて計算された限度額が、単純な未決注文に適用されます。EAがラインを作成し、リミットが設定されることを知らせ、単純な未決注文をOCO未決注文に変更します。


結論

システムをテストし、その口座がヘッジとして認識されるかどうかを確認しようと試みたにもかかわらず、現段階では成功していません。MetaTrader 5プラットフォームで口座がヘッジだと報告された場合でも、EA は常に口座がネッティングモードであると報告しました。そのため、注意が必要です。思い通りに動くのですが、ヘッジ口座でもネッティング口座のように未決注文が調整されてしまいます...。

ビデオでは、上記のすべてがはっきりわかります。お分かりのように、このシステムは使っていてとても面白いです。




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

添付されたファイル |
データサイエンスと機械学習(第04回):現在の株式市場の暴落を予測する データサイエンスと機械学習(第04回):現在の株式市場の暴落を予測する
今回は、米国経済のファンダメンタルズに基づいて、私たちのロジスティックモデルを使って株式市場の暴落の予測を試みます。NETFLIXとAPPLEが私たちが注目する銘柄です、2019年と2020年の過去の市場の暴落を使って、モデルが現在の破滅と暗雲でどのように機能するか見てみましょう。
DoEasy - コントロール(第7部):テキストラベルコントロール DoEasy - コントロール(第7部):テキストラベルコントロール
今回の記事では、WinFormsテキストラベルコントロールオブジェクトのクラスを作成します。このようなオブジェクトはコンテナをどこにでも配置できますが、独自の機能はMS Visual Studioテキストラベルの機能を繰り返します。表示されるテキストのフォントパラメータは設定できます。
機械学習を使いこなすには 機械学習を使いこなすには
アルゴリズム取引に関するトレーダーの知識の向上に役立つ資料を集めたので、チェックしてみてください。単純なアルゴリズムの時代は過ぎ去りつつあり、機械学習技術やニューラルネットワークを使用せずに成功することは難しくなっています。
ビデオ:MetaTrader5とMQL5での簡単な自動売買の設定方法 ビデオ:MetaTrader5とMQL5での簡単な自動売買の設定方法
このビデオコースでは、MetaTrader 5をダウンロード、インストールして自動売買のために設定する方法を学びます。また、チャートの設定や自動売買のオプションの調整方法についても学びます。最初のバックテストをおこないます。このコースの終わりには、画面の前に座らなくても、24時間365日自動的に取引できるエキスパートアドバイザー(EA)をインポートする方法が分かります。