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

一からの取引エキスパートアドバイザーの開発(第11部):両建て注文システム

MetaTrader 5トレーディング | 22 7月 2022, 08:59
1 271 0
Daniel Jose
Daniel Jose

はじめに

先物契約は、トレーダーを極度に苦しめる資産タイプです。なにがそんなに難しいのでしょうか。金融商品の契約が満了すると、新しい契約が作成され、それが取引されるようになります。実際、契約の満了時には、分析を終了し、すべてをテンプレートとして保存し、このテンプレートを新しい契約にインポートして分析を続行する必要があります。この種の資産を取引する人にはよくあることですが、先物契約にもある程度の履歴があり、この履歴を使用して継続的に分析することができます。

プロのトレーダーは特定の過去の情報を分析するのが好きであるため、2番目のチャートが必要です。さて、適切なツールを使用すれば、2番目のチャートを作成する必要はありません。そのようなツールの1つが両建て注文システムです。


計画

この連載の最初の記事では、このタイプの注文についてはすでに説明しましたが、実装には至りませんでした。その記事では、MetaTrader 5プラットフォームで動作する完全なシステムを立ち上げていたので、他のことに焦点を当てていました。この記事では、この機能を実装する方法を示します。

この機能を作成する理由を理解するには、次の2つの画像をご覧ください。

           

左の画像は典型的な先物契約です。この場合、チャートからわかるように、数日前に開始されたMINI DOLLAR FUTUREです。右のチャートは同じ契約を示していますが、実際に期限切れの契約の値を表す追加データが含まれているため、履歴チャートになっています。右のチャートは、古い支持抵抗レベルを分析するのにより適しています。ただし、取引する必要がある場合は問題が発生します。これを以下に示します。

          

ご覧のとおり、取引される銘柄はCHART TRADEで指定されています。履歴を使用しても、CHART TRADEには注文を送信できると表示されます。これはツールバーから確認できます。左の画像では、チャートには現在の契約に対して作成された注文がありますが、右の画像では、注文はメッセージボックスにのみ表示されていてチャートには何も表示されません。

単なる表示の問題だと思われるかもしれませんが、実は、すべてがはるかに複雑です。この記事で対処するのはこの問題です。

重要:ここでは、履歴データを作業に使用するルールを作成する方法を説明します。ここでの場合、これらのルールは、ブラジル取引所(B3)で取引されるミニドル(WDO)とミニインデックス(WIN)の操作に焦点を当てます。正しく理解することで、世界中のあらゆる取引所からのあらゆる種類の先物契約にルールを適合させることができます。

システムは特定の資産に限定されておらず、コードの適切な部分を適応させることがすべてです。これが正しくおこなわれればEA(エキスパートアドバイザー)が誕生し、資産契約が満了に近づいているかどうか、そして次の契約がどうなるかについて心配する必要がなくなります。EAは必要に応じて契約を正しいものに置き換えることによってこれをおこないます。


ゲームのルールを理解する方法

WDO(ミニドル)、WIN(ミニインデックス)、DOL(ドル先物)、IND(インデックス先物)先物契約は、満期と契約仕様に関する非常に具体的なルールに従います。まず、契約の有効期限を確認する方法を見てみましょう。


強調表示された情報に注意してください。青は契約の有効期限を示し、赤は契約が存在しなくなって取引されなくなる日付を示します。これを知っていることは非常に重要です。

契約期間は契約自体で指定されていますが、名前が指定されていません。幸いなことに、名前は市場全体で使用されている厳格なルールに基づいて簡単に見つけることができます。ドル先物および指数先物契約の場合、次のようになります。

最初の3文字は契約タイプを示します。

コード 契約
WIN ミニボベスパ指数先物契約 
IND ボベスパ指数先物契約
WDO ミニドル先物契約
DOL ドル先物契約

このコードの後には、契約の有効期限(月)を示す文字が続きます。

有効期限(月) WDOとDOLを表す文字 WINとINDを表す文字 
1月 F
 
2月  G  G
3月  H  
4月  J  J
5月  K  
6月  M  M
7月  N  
8月  Q  Q
9月  U  
10月  V  V
11月  X  
12月  Z  Z

これらの後に、契約の有効期限(年)を表す2桁の数字が続きます。たとえば、2022年4月に満了するドル先物契約は「DOLJ22」として示されます。これは5月が始まるまで取引できる契約です。5月が始まると、契約が失効します。WINとINDではルールが若干異なるため、契約は実際には指定された月の15日に最も近い水曜日に失効します。ルールはより複雑ですが、EAはこれを管理し、常に正しい契約を提供できます。


実装

EAには、ルールを受け取るために必要なポイントがすでにあります。ここでは、注文送信システムに関する設定を実装する必要があります。では、さっそく仕事に取りかかりましょう。まず、C_Terminalクラスオブジェクトに次のコードを追加します。

void CurrentSymbol(void)
{
        MqlDateTime mdt1;
        string sz0, sz1;
        datetime dt = TimeLocal();
            
        sz0 = StringSubstr(m_Infos.szSymbol = _Symbol, 0, 3);                           
        if ((sz0 != "WDO") && (sz0 != "DOL") && (sz0 != "WIN") && (sz0 != "IND")) return;
        sz1 = ((sz0 == "WDO") || (sz0 == "DOL") ? "FGHJKMNQUVXZ" : "GJMQVZ");
        TimeToStruct(TimeLocal(), mdt1);
        for (int i0 = 0, i1 = mdt1.year - 2000;;)
        {
                m_Infos.szSymbol = StringFormat("%s%s%d", sz0, StringSubstr(sz1, i0, 1), i1);
                if (i0 < StringLen(sz1)) i0++; else
                {
                        i0 = 0;
                        i1++;
                }
                if (macroGetDate(dt) < macroGetDate(SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_EXPIRATION_TIME))) break;
        }
}

このコードでは、上記のルールを使用してアセット名を生成します。常に現在の契約を使用するために、強調表示された行に示されているチェックを実装します。つまり、アセットはプラットフォームに対して有効である必要があり、EAは生成された名前を使用します。他の先物契約を使用する場合は、名前が場合ごとに異なる可能性があるため、名前が正しく生成されるように前のコードを調整する必要があります。ただし、コードはリンクされている資産だけに限定されるものではなく、正しいルールを使用している限り、あらゆる種類の先物契約を反映するために使用できます。

次は注文の詳細が記載される部分です。この開発段階でシステムを使用すると、次の動作が見られます。


言い換えれば、両建て注文モードはすでに存在しますが、まだ完全には実装されていません。チャート上に注文が表示されていません。水平線を使用して注文を示す必要があるため、実装は多くの人が想像しているほど難しくはありません。ただし、これですべてではありません。両建て注文を使用すると、MetaTrader 5によって提供されるいくつかの機能が失われます。したがって、注文システムが安全で安定して確実に機能するように、不足しているロジックを実装する必要があります。そうしないと、両建て注文を使用したときに問題が発生する可能性があります。

この観点から見ると、それほど単純ではないようにみえます。実際、MetaTraderプラットフォームが元々備えているすべてのロジックを作成する必要があるため、これは単純ではありません。最初にすべきことは、内部のMetaTraderシステムについては忘れることです。両建て注文システムを使い始めた瞬間から、それを利用することはできません。

今後ルールを決定するのは注文チケットです。しかし、いくつかの悪影響が出ます。最もマイナスなものの1つは、チャートに何件の注文が出されているかわからないことです。その数を制限することは、トレーダーにとって間違いなく不快です。したがって、通常完全なMetaTraderロジックでおこなわれるのと同じように、トレーダーがシステムを使用できるようにするためには、何かをおこなう必要があります。これがまず解決する問題です。


C_HLineTradeクラス

この問題を解決するために、新しいクラス「C_HLineTrade」を作成します。これは、MetaTrader5によって提供されるチャートに注文を表示するシステムに置き換わるものです。クラス宣言から始めます。

class C_HLineTrade
{
#define def_NameHLineTrade "*HLTSMD*"
        protected:
                enum eHLineTrade {HL_PRICE, HL_STOP, HL_TAKE};
        private :
                color   m_corPrice,
                        m_corStop,
                        m_corTake;
                string  m_SelectObj;

ここではいくつかのことが定義されています。これらはコードで頻繁に使用されます。したがって、今後の変更には十分ご注意ください。実際、多くの変更があります。次に、クラスのコンストラクタとデストラクタを宣言します。
C_HLineTrade() : m_SelectObj("")
{
        ChartSetInteger(Terminal.Get_ID(), CHART_SHOW_TRADE_LEVELS, false);
        RemoveAllsLines();
};
//+------------------------------------------------------------------+  
~C_HLineTrade()
{
        RemoveAllsLines();
        ChartSetInteger(Terminal.Get_ID(), CHART_SHOW_TRADE_LEVELS, true);
};

コンストラクタは元の線が表示されないようにし、デストラクタは元の線をチャートに戻します。どちらの関数にも次のような共通の機能があります。

void RemoveAllsLines(void)
{
        string sz0;
        int i0 = StringLen(def_NameHLineTrade);
                                
        for (int c0 = ObjectsTotal(Terminal.Get_ID(), -1, -1); c0 >= 0; c0--)
        {
                sz0 = ObjectName(Terminal.Get_ID(), c0, -1, -1);
                if (StringSubstr(sz0, 0, i0) == def_NameHLineTrade) ObjectDelete(Terminal.Get_ID(), sz0);
        }
}

強調表示された行は、オブジェクト(この場合は水平線)がクラスで使用されるオブジェクトの1つであるかどうかを確認します。そうである場合は、オブジェクトを削除します。オブジェクトの数はわかりませんが、システムはオブジェクトごとにチェックし、クラスによって作成されたすべてのものをクリーンアップしようとします。このクラスで次に推奨される関数を以下に示します。

inline void SetLineOrder(ulong ticket, double price, eHLineTrade hl, bool select)
{
        string sz0 = def_NameHLineTrade + (string)hl + (string)ticket, sz1;
                                
        if (price <= 0)
        {
                ObjectDelete(Terminal.Get_ID(), sz0);
                return;
        }
        if (!ObjectGetString(Terminal.Get_ID(), sz0, OBJPROP_TOOLTIP, 0, sz1))
        {
                ObjectCreate(Terminal.Get_ID(), sz0, OBJ_HLINE, 0, 0, 0);
                ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_COLOR, (hl == HL_PRICE ? m_corPrice : (hl == HL_STOP ? m_corStop : m_corTake)));
                ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_WIDTH, 1);
                ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_STYLE, STYLE_DASHDOT);
                ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_SELECTABLE, select);
                ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_SELECTED, false);
                ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_BACK, true);
                ObjectSetString(Terminal.Get_ID(), sz0, OBJPROP_TOOLTIP, (string)ticket + " "+StringSubstr(EnumToString(hl), 3, 10));
        }
        ObjectSetDouble(Terminal.Get_ID(), sz0, OBJPROP_PRICE, price);
}
この関数の場合、作成されるオブジェクトの数や、呼び出された時点でオブジェクトが存在するかどうかは関係ありません。線が作成され、適切な場所に配置されます。この作成された線は、MetaTraderで最初に使用された線を置き換えます。

ここでの目的は、それを美しくするのではなく、機能的にすることです。線が作成時に選択されないのはそのためです。この動作は必要に応じて変更できます。ただし、私はMetaTrader5メッセージングシステムを使用してラインを配置しています。それらを移動できるようにするには、これを明示的に示す必要があります。どの線が調整されているかを示すために、別の関数があります。

inline void Select(const string &sparam)
{
        int i0 = StringLen(def_NameHLineTrade);
                                
        if (m_SelectObj != "") ObjectSetInteger(Terminal.Get_ID(), m_SelectObj, OBJPROP_SELECTED, false);
        m_SelectObj = "";
        if (StringSubstr(sparam, 0, i0) == def_NameHLineTrade)
        {
                if (ObjectGetInteger(Terminal.Get_ID(), sparam, OBJPROP_SELECTABLE))
                {
                        ObjectSetInteger(Terminal.Get_ID(), sparam, OBJPROP_SELECTED, true);
                        m_SelectObj = sparam;
                };
        }
}
この関数は、線の選択を実装します。別の線が選択されている場合は、前の選択がキャンセルされます。すべてが簡単です。この関数は、クラスによって実際に処理される線のみを操作します。言及する価値のあるこのクラスの別の関数は次のとおりです。
bool GetNewInfosOrder(const string &sparam, ulong &ticket, double &price, eHLineTrade &hl)
{
        int i0 = StringLen(def_NameHLineTrade);
                                
        if (StringSubstr(sparam, 0, i0) == def_NameHLineTrade)
        {
                hl = (eHLineTrade) StringToInteger(StringSubstr(sparam, i0, 1));
                ticket = (ulong)StringToInteger(StringSubstr(sparam, i0 + 1, StringLen(sparam)));
                price = ObjectGetDouble(Terminal.Get_ID(), sparam, OBJPROP_PRICE);
                return true;
        }
        return false;
}
この関数は、おそらくこのクラスで最も重要です。チャート上にある線の数がわからないため、ユーザーが操作している線を知る必要がありますが、この関数はまさにそれを実行し、どの線が操作されているかをシステムに通知します。

ただし、これはやらなければならないことのほんの一部にすぎません。システムはまだ機能しているとは言えません。次の手順に進みましょう。注文のルーティングを担当するC_Routerクラスの関数を追加および変更します。このクラスは、C_HLineTradeクラスで作成した機能を継承します。次のコードを参照してください。

#include "C_HLineTrade.mqh"
//+------------------------------------------------------------------+
class C_Router : public C_HLineTrade



新しいC_Routerクラス

ソースのC_Routerクラスには、注文を1つだけ持つことができるという制限がありました。この制限を解除するために、C_Routerクラスに重要な変更を加える必要があります。

最初の変更はクラス更新関数にあります。これは次のようになります。

void UpdatePosition(void)
{
        static int memPositions = 0, memOrder = 0;
        ulong ul;
        int p, o;
                                
        p = PositionsTotal() - 1;
        o = OrdersTotal() - 1;
        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;
        };
        for(int i0 = p; i0 >= 0; i0--) if(PositionGetSymbol(i0) == Terminal.GetSymbol())
        {
                ul = PositionGetInteger(POSITION_TICKET);
                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);
        }
};

この関数は以前は1つのポジションについてのみデータを収集し、それを観察用に保存していましたが、チャート上に完全にすべてのポジションと未決注文を表示するようになります。それは間違いなくMetaTraderによって提供されるシステムの代わりになります。これらは重要であるため、それがどのように機能するかを理解することが重要です。失敗すると、両建て注文システム全体に影響を与えるからです。実際の口座で取引する前に、デモ口座でこのシステムをテストしてみましょう。このようなシステムは、すべてが正常に機能することが確実になるまで適切にテストする必要があります。まず、動作がMetaTrader 5のとは少し異なるため、システムを構成する必要があります。

強調表示された行を見て、正直にお答えください。それらが実際に何をしているのかは明確でしょうか。これら2行がここにある理由は、この記事の後半でC_OrderViewクラスについて説明するときに明らかになります。それがないと、コードは非常に不安定になり、奇妙に動作するということです。残りのコードは非常に単純で、C_HLineTradeクラスオブジェクトを介して各線を作成します。この場合、選択できない線は1つだけです。これは以下のコードに示すように簡単に示されます。

SetLineOrder(ul, PositionGetDouble(POSITION_PRICE_OPEN), HL_PRICE, false);

言い換えれば、このシステムは非常にシンプルでわかりやすいものになっています。この関数は、OnTradeイベント中にEAによって呼び出されます。

C_TemplateChart Chart;

// ... Expert Advisor code ...

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

// ... The rest of the Expert Advisor code ...

強調表示されたコードによって、画面上のコマンドの更新が有効になります。これにはC_TemplateChartチャートを使用しています。これは、システム内のクラスの構造が変更されたためです。以下に新しい構造を示します。

この構造によって、EA内でのメッセージの方向付けられたフローが可能になります。メッセージフローが特定のクラスにどのように入るのか疑問がある場合は、このクラス継承グラフをご確認ください。publicと見なされる唯一のクラスはC_Terminalオブジェクトクラスです。他のすべてのクラスはクラス間の継承によって処理され、このシステムでは変数は絶対にpublicではありません。

さて、システムは単一の注文だけを分析するわけではないので、何か他のものを理解する必要があります:操作の結果を理解する方法はなんでしょうか。なぜそれが重要なのでしょうか。ポジションが1つしかない場合、システムはすべてを簡単に理解できますが、ポジションの数が増えると、何が起こっているのかを把握する必要があります。この情報を提供する関数は次のとおりです。

void OnTick()
{
        Chart.DispatchMessage(CHARTEVENT_CHART_CHANGE, 0, Chart.CheckPosition(), C_Chart_IDE::szMsgIDE[C_Chart_IDE::eRESULT]);
}

変更はあまりありません。強調表示された関数コードを見てください。

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

この関数は、強調表示された3つの部分で構成されています。黄色の部分はポジションの結果を通知し、緑色の部分はボラティリティが高いためにストップロスを逃した場合にポジションをチェックします。この場合、ポジションをできるだけはやく決済する必要があります。したがって、この関数は、特定の資産に対して1つのポジションがある場合を除いて、単一のポジションの結果を返すことはありません。

両建て注文モデルを使用するときにシステムが機能し続けるのに役立つ他の関数もあります。以下のコードでご覧ください。

bool ModifyOrderPendent(const ulong Ticket, const double Price, const double Take, const double Stop, const bool DayTrade = true)
{
        if (Ticket == 0) return false;
        ZeroMemory(TradeRequest);
        ZeroMemory(TradeResult);
        TradeRequest.action     = TRADE_ACTION_MODIFY;
        TradeRequest.order      = Ticket;
        TradeRequest.price      = NormalizeDouble(Price, Terminal.GetDigits());
        TradeRequest.sl         = NormalizeDouble(Stop, Terminal.GetDigits());
        TradeRequest.tp         = NormalizeDouble(Take, Terminal.GetDigits());
        TradeRequest.type_time  = (DayTrade ? ORDER_TIME_DAY : ORDER_TIME_GTC);
        TradeRequest.expiration = 0;
        return OrderSend(TradeRequest, TradeResult);
};
//+------------------------------------------------------------------+
bool ModifyPosition(const ulong Ticket, const double Take, const double Stop)
{
        ZeroMemory(TradeRequest);
        ZeroMemory(TradeResult);
        if (!PositionSelectByTicket(Ticket)) return false;
        TradeRequest.action     = TRADE_ACTION_SLTP;
        TradeRequest.position   = Ticket;
        TradeRequest.symbol     = PositionGetString(POSITION_SYMBOL);
        TradeRequest.tp         = NormalizeDouble(Take, Terminal.GetDigits());
        TradeRequest.sl         = NormalizeDouble(Stop, Terminal.GetDigits());
        return OrderSend(TradeRequest, TradeResult);
};
最初の1つはまだ開いている注文の変更を担当し、もう1つは開いたポジションを変更します。同じように見えますが、そうではありません。システムにはもう1つの重要な機能があります。
bool RemoveOrderPendent(ulong Ticket)
{
        ZeroMemory(TradeRequest);
        ZeroMemory(TradeResult);
        TradeRequest.action     = TRADE_ACTION_REMOVE;
        TradeRequest.order      = Ticket;       
        return OrderSend(TradeRequest, TradeResult);
};

この最後の関数でC_Routerクラスの検討は完成します。MetaTraderで通常サポートされている機能をカバーする基本的なシステムを実装しました。両建て注文システムのため、このサポートを信頼することはできないからです。ただし、システムはまだ完成していません。システムを実際に機能させるためには、他に何かを追加する必要があります。現時点で注文があると以下のようになります。これは、次の手順を完了するために必要です。



上の画像をよくご覧ください。メッセージボックスには、未決済注文とその資産が表示されます。取引されている資産はCHART TRADEで示されます。メッセージボックスに表示されているのと同じ資産であることに注意してください。それでは、チャートに表示されている資産を確認しましょう。名前はチャートウィンドウのヘッダーで確認できますが、完全に異なります。それはチャート上の資産ではありませんが、ミニインデックスの履歴です。つまり、MetaTrader 5内部システムを使用せず、この記事で説明する両建て注文システムを使用するため、あるのは注文の場所を表示できる機能だけです。両建て注文システムを介して操作できる完全に機能するシステムが必要なため、これでは十分ではありません。よって、他の何かが必要です。注文の移動に関連するイベントについては、別のクラスで実装されます。


C_OrderViewクラスの新機能

C_OrderViewオブジェクトクラスではいくつかのことを実行できますが、未処理または未決済注文のデータはまだ処理できません。ただし、メッセージングシステムを追加すると、それを使用する可能性が高くなります。今のところクラスに追加するのはこれだけです。完全な関数コードを以下に示します。

void DispatchMessage(int id, long lparam, double dparam, string sparam)
{
        ulong           ticket;
        double          price, pp, pt, ps;
        eHLineTrade     hl;
        
        switch (id)
        {
                case CHARTEVENT_MOUSE_MOVE:
                        MoveTo((int)lparam, (int)dparam, (uint)sparam);
                        break;
                case CHARTEVENT_OBJECT_DELETE:
                        if (GetNewInfosOrder(sparam, ticket, price, hl))
                        {
                                if (OrderSelect(ticket))
                                {
                                        switch (hl)
                                        {
                                                case HL_PRICE:
                                                        RemoveOrderPendent(ticket);
                                                        break;
                                                case HL_STOP:
                                                        ModifyOrderPendent(ticket, OrderGetDouble(ORDER_PRICE_OPEN), OrderGetDouble(ORDER_TP), 0);
                                                        break;
                                                case HL_TAKE:
                                                        ModifyOrderPendent(ticket, OrderGetDouble(ORDER_PRICE_OPEN), 0, OrderGetDouble(ORDER_SL));
                                                        break;
                                        }
                                }else if (PositionSelectByTicket(ticket))
                                {
                                        switch (hl)
                                        {
                                                case HL_PRICE:
                                                        ClosePosition(ticket);
                                                        break;
                                                case HL_STOP:
                                                        ModifyPosition(ticket, OrderGetDouble(ORDER_TP), 0);
                                                        break;
                                                case HL_TAKE:
                                                        ModifyPosition(ticket, 0, OrderGetDouble(ORDER_SL));
                                                        break;
                                        }
                                }
                        }
                        break;
                case CHARTEVENT_OBJECT_CLICK:
                        C_HLineTrade::Select(sparam);
                        break;
                case CHARTEVENT_OBJECT_DRAG:
                        if (GetNewInfosOrder(sparam, ticket, price, hl))
                        {
                                price = AdjustPrice(price);
                                if (OrderSelect(ticket)) switch(hl)
                                {
                                        case HL_PRICE:
                                                pp = price - OrderGetDouble(ORDER_PRICE_OPEN);
                                                pt = OrderGetDouble(ORDER_TP);
                                                ps = OrderGetDouble(ORDER_SL);
                                                if (!ModifyOrderPendent(ticket, price, (pt > 0 ? pt + pp : 0), (ps > 0 ? ps + pp : 0))) UpdatePosition();
                                                break;
                                        case HL_STOP:
                                                if (!ModifyOrderPendent(ticket, OrderGetDouble(ORDER_PRICE_OPEN), OrderGetDouble(ORDER_TP), price)) UpdatePosition();
                                                break;
                                        case HL_TAKE:
                                                if (!ModifyOrderPendent(ticket, OrderGetDouble(ORDER_PRICE_OPEN), price, OrderGetDouble(ORDER_SL))) UpdatePosition();
                                                break;
                                }
                                if (PositionSelectByTicket(ticket)) switch (hl)
                                {
                                        case HL_PRICE:
                                                UpdatePosition();
                                                break;
                                        case HL_STOP:
                                                ModifyPosition(ticket, PositionGetDouble(POSITION_TP), price);
                                                break;
                                        case HL_TAKE:
                                                ModifyPosition(ticket, price, PositionGetDouble(POSITION_SL));
                                                break;
                                }
                        };
                break;
        }
}

このコードで、両建て注文システムが完成します。機能が向上し、両建て注文システムなしで可能だったのとほぼ同じことができるようになりました。全体的に、関数は非常に明確なはずですが、あまり一般的ではないイベントタイプ(CHARTEVENT_OBJECT_DELETE)を持ちます。ユーザーによる線の削除はチャートと注文システムに反映されるため、チャートから線を削除するときは十分に注意する必要があります。次のアニメーションに示すように、注文はそのまま変わらないため、チャートからEAを削除するときに心配する必要はありません。


ただし、EAがチャート上にある場合にチャートから線を削除するとき(特にオブジェクトのリストに隠されている線を削除するとき)は、細心の注意を払う必要があります。それ以外の場合、両建て注文システムによって作成された線を削除したときに注文システムで何が起こるかを以下に示します。

次に、システムのデモを終了するために、価格ラインをドラッグしたときに注文がどうなるかを見てみましょう。ドラッグした線を選択する必要があります。選択されていない場合、移動することはできないので、注意してください。チャート上で線が放されると価格が変更しますが、それまでは価格は以前と同じ位置に留まります。


線が選択されているかどうかわからない場合は、選択されたコードを変更してみましょう。以下では変更点が強調表示されています。この変更は、添付のバージョンですでに実装されています。

inline void Select(const string &sparam)
{
        int i0 = StringLen(def_NameHLineTrade);
                
        if (m_SelectObj != "")
        {
                ObjectSetInteger(Terminal.Get_ID(), m_SelectObj, OBJPROP_SELECTED, false);
                ObjectSetInteger(Terminal.Get_ID(), m_SelectObj, OBJPROP_WIDTH, 1);
        }
        m_SelectObj = "";
        if (StringSubstr(sparam, 0, i0) == def_NameHLineTrade)
        {
                if (ObjectGetInteger(Terminal.Get_ID(), sparam, OBJPROP_SELECTABLE))
                {
                        ObjectSetInteger(Terminal.Get_ID(), sparam, OBJPROP_SELECTED, true);
                        ObjectSetInteger(Terminal.Get_ID(), sparam, OBJPROP_WIDTH, 2);
                        m_SelectObj = sparam;
                };
        }
}

次の図にこのコード変更の結果を示します。


結論

ここではMetaTraderで両建て注文システムを作成する方法を示しました。このシステムが、この知識を利用するすべての人に役立つことを願っています。ただし、次の点にご注意ください。このシステムを使用してライブ口座で取引を開始する前に、さまざまな市場シナリオで可能な限り徹底的にテストする必要があります。このシステムはMetaTraderプラットフォームに実装されていますが、エラー処理の面ではプラットフォーム側のサポートはほとんどないので、万が一発生した場合は、大きな損失を出さないように迅速に対応する必要があります。しかし、さまざまなシナリオでテストすることで、移動、コンピューターが処理できる注文の最大数、分析システムの最大許容スプレッド、未決済注文の許容ボラティリティレベルといった、問題が発生する場所を見つけることができます。未処理の注文と分析する情報の数が多いほど、何か悪いことが起こる可能性が高くなります。これは、各注文がシステムが受け取るすべてのティックで分析されているために、同時に多数の注文が開かれている場合に問題になる可能性があるためです。

デモ口座で多くのシナリオでテストするまで、このシステムを信頼しないことをお勧めします。コードは完璧に見えても、エラー分析がありません。

現時点でのすべてのEAのコードを添付しています。


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

添付されたファイル |
EA.zip (12013.17 KB)
データサイエンスと機械学習(第02回):ロジスティック回帰 データサイエンスと機械学習(第02回):ロジスティック回帰
データ分類は、アルゴトレーダーとプログラマーにとって非常に重要なものです。この記事では、「はい」と「いいえ」、上と下、買いと売りを識別するのに役立つ可能性のある分類ロジスティックアルゴリズムの1つに焦点を当てます。
MACDによる取引システムの設計方法を学ぶ MACDによる取引システムの設計方法を学ぶ
今回は、このシリーズの新しいツール、MACD(Moving Average Convergence Divergence、移動平均収束発散)に基づいた取引システムの設計方法について学びます。
ストキャスティクスによる取引システムの設計方法を学ぶ ストキャスティクスによる取引システムの設計方法を学ぶ
この記事では、学習シリーズを継続します。今回は、基本的な知識の新しいブロックを構築するために、最も人気があり、便利な指標の1つであるストキャスティックスオシレータ指標を使用して取引システムを設計する方法を学びます。
CCIによる取引システムの設計方法を学ぶ CCIによる取引システムの設計方法を学ぶ
今回は、取引システムの設計方法を学ぶ連載の新しい記事として、CCI(商品チャンネル指数、Commodities Channel Index)を紹介し、その詳細を説明し、この指標に基づいた取引システムの作り方を紹介します。