English Русский 中文 Español Deutsch Português
preview
自動で動くEAを作る(第03回):新しい関数

自動で動くEAを作る(第03回):新しい関数

MetaTrader 5トレーディング | 2 3月 2023, 10:03
1 066 0
Daniel Jose
Daniel Jose

はじめに

前回の「自動で動くEAを作る(第02回):コードを始める」稿では、自動EAで使用するオーダーシステムの開発に着手しましたが、必要な関数のうち1つしか作っていません。

通常、オーダーシステムが完全に自動化されたモードで動作するためには、さらにいくつかのものが必要です。それに、同じ資産で異なる設定で動作する複数のEAを使用することを好む人もいます。

これは、ネッティングポジション(両建)会計システムを使用している口座には推奨されません。理由は、取引サーバーがいわゆる平均ポジション価格を作成するため、一方のEAが売ろうとし、もう一方のEAが買おうとする事態が発生する可能性があるためです。明らかに、これによって両方のEAが消滅し、短時間で預金を失うことになります。ヘッジ口座ではこのようなことはありません。このような口座では、一方のEAが売り、もう一方のEAが買うことができます。1つの注文が他の注文をキャンセルすることはありません。

EAと同じ資産を取引する人もいますが、これはヘッジ口座でおこなわなければなりません。疑いがある場合は、同じ資産を取引する2つのEAを実行することや(少なくとも同じ証券会社で取引するもの)、自動化されたEAが実行されている間に同じ資産を取引することは絶対に避けてください。

警告したところで、あとは必要な関数をEAに追加すれば、9割以上のケースをカバーできます。


新しい関数が必要な理由

通常、自動売買EAが取引をおこなう場合、基本的には相場の出入りをおこないます。つまり、EAがオーダーブックに注文を出すことはほとんどありません。しかし、オーダーブックに注文を出すことが必要になる場合もあります。このような注文を出す手続きは、最も困難なものの1つです。そのため、前回はそのような関数の実装に特化して解説しました。しかし、自動化されたEAにはこれだけでは不十分です。なぜなら、ほとんどの場合、市場にエントリし、市場からエグジットすることになるからです。そのため、少なくとも2つの関数を追加する必要があります。

  • 成行価格で売買するための注文を出す
  • 注文価格を変更できるようにする

この2つの関数と、前回の記事で紹介した関数だけが、自動売買EAに本当に必要なものです。では、なぜこの2つの関数だけを追加する必要があるのか、その理由を説明しましょう。EAがポジションを建てるとき、それが売りであれ買いであれ、多くの場合、トレーダーがあらかじめ決めた市場価格と数量でおこなうことになります。

ポジションを決済する必要がある場合、2つの方法があります。1つは市場価格で、反対方向と同じ数量の取引をおこなうことです。しかし、これがポジション決済に至ることはあまりないでしょう。実は、これはネッティング口座でないと機能しません。ヘッジ口座では、この操作ではポジションは決済されません。この場合、既存のポジションを決済するための明確な注文が必要です。

ヘッジ口座で同じ出来高で反対方向の取引を開始すると、価格を固定することができますが、実際にはポジションが決済されるわけではありません。価格ロックは利益も損失ももたらしません。そのため、EAにポジションを決済する関数を追加することになりますが、そのためには3つ目の関数を実装する必要があります。注:ネッティング口座では、同じ数量で反対方向の成行注文を出すだけで、ポジションは決済されます。

注文価格を変更できるポジションが必要です。ある運用モデルでは、EAは市場のポジションを開き、すぐにストップロス指値注文を送信します。この注文はオーダーブックに追加され、ポジションが決済されるまでずっとそこに残ります。この場合、EAは実際にクローズオーダーや他のタイプのリクエストを送信しません。それは単に、クローズオーダーが常にアクティブであるように、ブック内の注文を管理します。

これはネッティング口座ではうまくいきますが、ヘッジ口座ではこのシステムは期待通りには動きません。この場合、オーダーブックの注文は、取引の終了方法についてすでに説明したとおりのことをするだけです。さて、話を戻します。オーダーブックで注文を管理するのと同じ手順で、OCO (one cancel the other)注文のテイクプロフィットやストップロスのレベルを移動することもできます。

通常、自動売買のEAではOCO注文は使用せず、単に成行注文とオーダーブックの注文で動作します。しかし、ヘッジ口座の場合、この仕組みはOCO注文でおこなうことができ、ストップロスレベルのみが設定されます。あるいは、プログラマーが望めば、単に市場にエントリし、EAに何らかの方法で市場を監視させることも可能です。あるポイントまたは価格レベルに達すると、EAはクローズオーダーを送信します。

同じことをするにも(ここでは決済)、いろいろなやり方があるということを示したかっただけなのです。ポジションを開くのは最も簡単な作業です。決済は、次のような点を考慮しなければならないので、厄介な部分です。

  • 注文が「ジャンプ」するような、ボラティリティの高い瞬間の可能性(OCO注文の場合):注文のSTOP LIMITを除くオーダーブックの注文は、希望するポイント外でトリガーされることはあっても、決してジャンプすることはありません
  • 接続障害:EAがサーバーにしばらくアクセスできないことがある場合
  • 流動性の問題で、注文が執行されるのを待っていても、実際に執行されるには十分な量がないことがあります
  • そして最悪なのは、EAがランダムに注文を実行し始める可能性があることです

自動売買EAを作成する際には、これらの点をすべて考慮に入れ、観察する必要があります。他にも、プログラマーによってはEAの作動時間を追加するなどのポイントがあります。これに関しては、正直に言いますが、完全に無駄です。その方法は別の記事で紹介されてていますが、私は推奨していません。

次のように考えてみてください。取引の仕方がわからず、これをおこなうために自動化されたEAを持っており、一定のスケジュールを設定するとします。これで安心して、他のことができると思ったら、大間違いです。たとえ自動化されたものであっても、EAを、決して監視されないまま放置しないでください。絶対にです。動作中は、ご自分または信頼できる人がそばにいて、その動作を観察してください。

EAを放置して実行させると、トラブルの元となります。EAを開始・終了するためのスケジュール設定メソッドやトリガーを追加することは、自動売買EAを使用する際に最も愚かな行為です。やめてください。EAを使いたいのであれば、EAの電源を入れ、そばで見ていることです。離れるときは、電源を切ってから離れます。EAを監視のない状態で放置して勝手に取引させることは絶対に避けてください。非常に残念な結果になる可能性がありますので、やめてください。

必要な関数の実装 

成行注文を実行する手順は、指値注文を出す手順と非常によく似ているので、一般的な手順を作成することができます。操作固有のフィールドのみがローカルに入力されます。この関数で共通の入力をします。

inline void CommonData(const ENUM_ORDER_TYPE type, const double Price, const double FinanceStop, const double FinanceTake, const uint Leverage, const bool IsDayTrade)
                        {
                                double Desloc;
                                
                                ZeroMemory(m_TradeRequest);
				m_TradeRequest.magic		= m_Infos.MagicNumber;
                                m_TradeRequest.symbol           = _Symbol;
                                m_TradeRequest.volume           = NormalizeDouble(m_Infos.VolMinimal + (m_Infos.VolStep * (Leverage - 1)), m_Infos.nDigits);
                                m_TradeRequest.price            = NormalizeDouble(Price, m_Infos.nDigits);
                                Desloc = FinanceToPoints(FinanceStop, Leverage);
                                m_TradeRequest.sl               = NormalizeDouble(Desloc == 0 ? 0 : Price + (Desloc * (type == ORDER_TYPE_BUY ? -1 : 1)), m_Infos.nDigits);
                                Desloc = FinanceToPoints(FinanceTake, Leverage);
                                m_TradeRequest.tp               = NormalizeDouble(Desloc == 0 ? 0 : Price + (Desloc * (type == ORDER_TYPE_BUY ? 1 : -1)), m_Infos.nDigits);
                                m_TradeRequest.type_time        = (IsDayTrade ? ORDER_TIME_DAY : ORDER_TIME_GTC);
                                m_TradeRequest.stoplimit        = 0;
                                m_TradeRequest.expiration       = 0;
                                m_TradeRequest.type_filling     = ORDER_FILLING_RETURN;
                                m_TradeRequest.deviation        = 1000;
                                m_TradeRequest.comment          = "Order Generated by Experts Advisor.";
                        }

この共通関数にあるものはすべて、前回の記事で考察した成行注文を作成する関数にもあったものであることに注意してください。ただし、以前は存在しなかった、ヘッジ口座で作業している場合や作成した注文のみを観察するEAを作成しようとする場合に非常に有用になるかもしれない小さな何かを追加しました。いわゆるマジックナンバーと呼ばれるものです。通常、私はこの番号を使いませんが、使う場合には準備が必要です。

それでは、指値注文を送信する役割を担うこの新しい関数について説明します。

                ulong CreateOrder(const ENUM_ORDER_TYPE type, const double Price, const double FinanceStop, const double FinanceTake, const uint Leverage, const bool IsDayTrade)
                        {
                                double  bid, ask, Desloc;                               
                                
                                Price = AdjustPrice(Price);
                                bid = SymbolInfoDouble(_Symbol, (m_Infos.PlotLast ? SYMBOL_LAST : SYMBOL_BID));
                                ask = (m_Infos.PlotLast ? bid : SymbolInfoDouble(_Symbol, SYMBOL_ASK));
                                CommonData(type, AdjustPrice(Price), FinanceStop, FinanceTake, Leverage, IsDayTrade);
                                m_TradeRequest.action   = TRADE_ACTION_PENDING;
                                m_TradeRequest.type     = (type == ORDER_TYPE_BUY ? (ask >= Price ? ORDER_TYPE_BUY_LIMIT : ORDER_TYPE_BUY_STOP) : 
                                                                                    (bid < Price ? ORDER_TYPE_SELL_LIMIT : ORDER_TYPE_SELL_STOP));                              
                                ZeroMemory(m_TradeRequest);
                                m_TradeRequest.action           = TRADE_ACTION_PENDING;
                                m_TradeRequest.symbol           = _Symbol;
                                m_TradeRequest.volume           = NormalizeDouble(m_Infos.VolMinimal + (m_Infos.VolStep * (Leverage - 1)), m_Infos.nDigits);
                                m_TradeRequest.type             = (type == ORDER_TYPE_BUY ? (ask >= Price ? ORDER_TYPE_BUY_LIMIT : ORDER_TYPE_BUY_STOP) : 
                                                                                            (bid < Price ? ORDER_TYPE_SELL_LIMIT : ORDER_TYPE_SELL_STOP));
                                m_TradeRequest.price            = NormalizeDouble(Price, m_Infos.nDigits);
                                Desloc = FinanceToPoints(FinanceStop, Leverage);
                                m_TradeRequest.sl               = NormalizeDouble(Desloc == 0 ? 0 : Price + (Desloc * (type == ORDER_TYPE_BUY ? -1 : 1)), m_Infos.nDigits);
                                Desloc = FinanceToPoints(FinanceTake, Leverage);
                                m_TradeRequest.tp               = NormalizeDouble(Desloc == 0 ? 0 : Price + (Desloc * (type == ORDER_TYPE_BUY ? 1 : -1)), m_Infos.nDigits);
                                m_TradeRequest.type_time        = (IsDayTrade ? ORDER_TIME_DAY : ORDER_TIME_GTC);
                                m_TradeRequest.type_filling     = ORDER_FILLING_RETURN;
                                m_TradeRequest.deviation        = 1000;
                                m_TradeRequest.comment          = "Order Generated by Experts Advisor.";
                                
                                return (((type == ORDER_TYPE_BUY) || (type == ORDER_TYPE_SELL)) ? ToServer() : 0);
                        };

これらのフィールドは共通関数で記入されるので、クロスした部分はすべてコードから削除されています。本当に必要なのは、この2つの値を調整することで、オーダーシステムは前回の記事で示したように、指値注文を作成するようになります。

では、実際にどのようなプログラムを組めば、市場価格で約定要求を出すことができるオーダーシステムを作ることができるのかを見てみましょう。必要なコードは以下の通りです。

                ulong ToMarket(const ENUM_ORDER_TYPE type, const double FinanceStop, const double FinanceTake, const uint Leverage, const bool IsDayTrade)
                        {
                                CommonData(type, SymbolInfoDouble(_Symbol, (type == ORDER_TYPE_BUY ? SYMBOL_ASK : SYMBOL_BID)), FinanceStop, FinanceTake, Leverage, IsDayTrade);
                                m_TradeRequest.action   = TRADE_ACTION_DEAL;
                                m_TradeRequest.type     = type;

                                return (((type == ORDER_TYPE_BUY) || (type == ORDER_TYPE_SELL)) ? ToServer() : 0);
                        };

この簡単さをご覧ください。指値注文と比較して変更する必要があるのは、たったこの2点だけです。このように、リクエストの種類を変えるだけで、サーバーは常に互換性のあるデータを受信できることが保証されます。

このように、戻り値の分析も、指値注文や成行注文の実行手続きの呼び出し方も、ほぼ同じ操作でおこなえます。プロシージャを呼び出す人にとっての唯一の違いは、成行注文が実行されるときは、クラスで正しく値が記入されるので、価格を提供する必要がないが、指値注文では、価格を指定する必要があるということです。それ以外はすべて同じ操作になります。

では、システムで何が変わったのかを見てみましょう。マジックナンバーとして使用する値を追加したので、この値を受け取るクラスを作成します。これは、クラスのコンストラクタでおこなうべきです。現在、コンストラクタはこのようになっています。

                C_Orders(const ulong magic = 0)
                        {
                                m_Infos.MagicNumber     = magic;
                                m_Infos.nDigits         = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
                                m_Infos.VolMinimal      = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
                                m_Infos.VolStep         = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
                                m_Infos.PointPerTick    = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
                                m_Infos.ValuePerPoint   = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
                                m_Infos.AdjustToTrade   = m_Infos.PointPerTick / m_Infos.ValuePerPoint;
                                m_Infos.PlotLast        = (SymbolInfoInteger(_Symbol, SYMBOL_CHART_MODE) == SYMBOL_CHART_MODE_LAST);
                        };

上のコードで何が起こっているか見てみましょう。このようにデフォルト値を宣言すると、クラス生成時にそれを知らせる必要がないので、このコンストラクタはデフォルトに(引数の型を受け取らないように)しています。

ただし、クラスのユーザー(すなわちプログラマー)に、クラスが作成される段階でどの値を使用すべきかを知らせるようにしたい場合は、パラメータの値を削除します。コンパイラがコードを生成しようとすると、何かが欠けていることに気づき、どの値を使うか聞いてきますが、これはクラスに別のコンストラクタを含まれていない場合にのみ機能します。これは、多くの人が理解できない細かいことですが、なぜ値を指定しなければならないときとそうでないときがあるのでしょうか。

このように、プログラミングはとても面白いものなのです。多くの場合、私たちが実際におこなっているのは、コーディングやテストすべきことがそれほど多くならないように、最小限の労力でソリューションを作ろうとすることです。ただし、まだC_Ordersのクラスは完成していません。ヘッジとネッティングの口座の違いから、まだ1つの必須関数と1つのオプション関数が必要ですが、これはまだ作成する予定です。前に進みましょう。

                bool ModifyPricePoints(const ulong ticket, const double Price, const double PriceStop, const double PriceTake)
                        {
                                ZeroMemory(m_TradeRequest);
                                m_TradeRequest.symbol   = _Symbol;
                                if (OrderSelect(ticket))
                                {
                                        m_TradeRequest.action   = (Price > 0 ? TRADE_ACTION_MODIFY : TRADE_ACTION_REMOVE);
                                        m_TradeRequest.order    = ticket;
                                        if (Price > 0)
                                        {
                                                m_TradeRequest.price      = NormalizeDouble(AdjustPrice(Price), m_Infos.nDigits);
                                                m_TradeRequest.sl         = NormalizeDouble(AdjustPrice(PriceStop), m_Infos.nDigits);
                                                m_TradeRequest.tp         = NormalizeDouble(AdjustPrice(PriceTake), m_Infos.nDigits);
                                                m_TradeRequest.type_time  = (ENUM_ORDER_TYPE_TIME)OrderGetInteger(ORDER_TYPE_TIME) ;
                                                m_TradeRequest.expiration = 0;
                                        }
                                }else if (PositionSelectByTicket(ticket))
                                {
                                        m_TradeRequest.action   = TRADE_ACTION_SLTP;
                                        m_TradeRequest.position = ticket;
                                        m_TradeRequest.tp       = NormalizeDouble(AdjustPrice(PriceTake), m_Infos.nDigits);
                                        m_TradeRequest.sl       = NormalizeDouble(AdjustPrice(PriceStop), m_Infos.nDigits);
                                }else return false;
                                ToServer();
                                
                                return (_LastError == ERR_SUCCESS);
                        };

上記のプロシージャは非常に重要で、これから見る次のプロシージャよりもさらに重要です。なぜなら、このプロシージャは、指値注文の場合はオーダーブックのポジション、ポジションの場合はリミットなど、価格のポジションを操作する役割を担っているからです。上記の関数は非常に強力で、注文やポジションの制限を作成したり削除したりすることができます。その仕組みを理解するために、中のコードを解析してみましょう。これにより、この関数の正しい使い方を理解することができます。

よりシンプルでわかりやすい説明にするために、すべてを部分に分けて説明しますので、説明に迷わないように注意してください。

まず初めに、次のことを理解しましょう。オーダーブックへの指値注文であれ、成行注文であれ、注文を出すと、戻り値を得ることができます。リクエストにエラーが発生しなかった場合、この値は非NULLとなります。ただし、この値を無視してはいけません。注文またはポジション作成関数によって返される値は、注文またはポジションのチケットを表すため、細心の注意を払って保存する必要があります。

このチケットはパスのようなもので、取引サーバーにある注文やポジションを操作できるなど、いくつかの可能性を与えてくれます。つまり、成行注文を出すときや指値注文を出そうとしたときに得られる値や、この手順をおこなう関数が返す値は、基本的に(ゼロと異なる場合)EAがサーバーと通信するためのパスとして機能します。チケットを使うことで、価格を操作する能力を手に入れることができるのです。

各注文やポジションには固有のチケットがあるので、この番号を大切にして、ランダムに作成しようとしないでください。値がわからない、または紛失してしまった場合は取得する方法があります。ただし、この方法にはEAの時間がかかるので、なくさないように注意してください。

まず、ある注文があり、この注文の指値(テイクプロフィットまたはストップロス)を削除または変更したい場合を考えてみましょう。注文とポジションを混同しないでください。「注文」は、将来的に可能になるポジションです。通常、注文はオーダーブックにあり、ポジションは実際に注文が満たされたときです。.この場合、注文チケットと新しい価格の値を提供することになります。これらは価格の値になっており、金銭的価値 (売買に関連する金銭的価値) を示すものではないことに注意してください。ここで期待しているのは、チャートにあるような額面です。したがって、ここでランダムに操作することはできません。さもなければリクエストが却下されます。

これで、事実上、ほとんどすべてのストップロスとテイクプロフィットの値を注文に入れることができるようになりましたが、実際はそうとは言い切れません。買い注文の場合、ストップロス値はポジションの始値より大きくできず、テイクプロフィット値は小さくできません。これをしようとすると、サーバーはエラーを返します。

ここでよく注意していただきたいのは、注文におけるストップロスとテイクプロフィットの値はこれらの条件を満たしていなければならないということです。しかし、ポジションの場合、買いポジションのストップロス値は始値よりも高くすることができ、その場合、ストップロスはストッププロフィットとなり、つまりストップロス注文がヒットすれば、すでにある程度の利益があることになります。これについては後で詳しく説明します。とりあえず、買い注文の場合、ストップロスは建値より下に、売り注文の場合は上になるように覚えておいてください。ポジションのストップロスはどこにでも設定できます。

上記は、注文やポジションのストップレベルのみに関するものです。しかし、上の関数を見てみると、注文である限り、ポジションの始値を操作することもできることに気づきます。重要な詳細は、このとき、ストップロスとテイクプロフィットを一緒に移動させる必要があるということです。これをしないと、ある時点で、サーバーは建値変更リクエストを拒否することになります。

この部分を理解するために、これらの場合を確認するための小さなEAプログラムを作ってみましょう。EAファイルを新規作成し、この開いているファイルに以下のコードをコピー&ペーストしてください。その後、EAをコンパイルし、チャート上で起動させます。それから説明を見ます。

#property copyright "Daniel Jose"
#property description "This one is an automatic Expert Advisor"
#property description "for demonstration. To understand how to"
#property description "develop yours in order to use a particular"
#property description "operational, see the articles where there"
#property description "is an explanation of how to proceed."
#property version   "1.03"
#property link      "https://www.mql5.com/pt/articles/11226"
//+------------------------------------------------------------------+
#include <Generic Auto Trader\C_Orders.mqh>
//+------------------------------------------------------------------+
C_Orders *orders;
//+------------------------------------------------------------------+
input int       user01   = 1;           //Lot value
input int       user02   = 100;         //Take Profit
input int       user03   = 75;          //Stop Loss
input bool      user04   = true;        //Day Trade ?
input double    user05   = 84.00;       //Entry price...
//+------------------------------------------------------------------+
int OnInit()
{
        orders = new C_Orders(1234456789);
        
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
        delete orders;
}
//+------------------------------------------------------------------+
void OnTick()
{
}
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
#define KEY_UP                  38
#define KEY_DOWN                40
#define KEY_NUM_1               97
#define KEY_NUM_2               98
#define KEY_NUM_3               99
#define KEY_NUM_7               103
#define KEY_NUM_8               104
#define KEY_NUM_9               105

        static ulong sticket = 0;
        int key = (int)lparam;
        
        switch (id)
        {
                case CHARTEVENT_KEYDOWN:
                        switch (key)
                        {
                                case KEY_UP:
                                        if (sticket == 0)
                                                sticket = (*orders).CreateOrder(ORDER_TYPE_BUY, user05, user03, user02, user01, user04);
                                        break;
                                case KEY_DOWN:
                                        if (sticket == 0)
                                                sticket = (*orders).CreateOrder(ORDER_TYPE_SELL, user05, user03, user02, user01, user04);
                                        break;
                                case KEY_NUM_1:
                                case KEY_NUM_7:
                                        if (sticket > 0) ModifyStop(key == KEY_NUM_7, sticket);
                                        break;
                                case KEY_NUM_2:
                                case KEY_NUM_8:
                                        if (sticket > 0) ModifyPrice(key == KEY_NUM_8, sticket);
                                        break;
                                case KEY_NUM_3:
                                case KEY_NUM_9:
                                        if (sticket > 0) ModifyTake(key == KEY_NUM_9, sticket);
                                        break;
                        }
                        break;
        }
}
//+------------------------------------------------------------------+
void ModifyPrice(bool IsUp, const ulong ticket)
{
        double p, s, t;
        
        if (!OrderSelect(ticket)) return;
        p = OrderGetDouble(ORDER_PRICE_OPEN) + (SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE) * (IsUp ? 1 : -1));
        s = OrderGetDouble(ORDER_SL);
        t = OrderGetDouble(ORDER_TP);
        (*orders).ModifyPricePoints(ticket, p, s, t);
}
//+------------------------------------------------------------------+
void ModifyTake(bool IsUp, const ulong ticket)
{
        double p, s, t;
        
        if (!OrderSelect(ticket)) return;
        p = OrderGetDouble(ORDER_PRICE_OPEN);
        s = OrderGetDouble(ORDER_SL);
        t = OrderGetDouble(ORDER_TP) + (SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE) * (IsUp ? 1 : -1));
        (*orders).ModifyPricePoints(ticket, p, s, t);
}
//+------------------------------------------------------------------+
void ModifyStop(bool IsUp, const ulong ticket)
{
        double p, s, t;
        
        if (!OrderSelect(ticket)) return;
        p = OrderGetDouble(ORDER_PRICE_OPEN);
        s = OrderGetDouble(ORDER_SL) + (SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE) * (IsUp ? 1 : -1));
        t = OrderGetDouble(ORDER_TP);
        (*orders).ModifyPricePoints(ticket, p, s, t);
}
//+------------------------------------------------------------------+

コードは一見複雑そうですが、ご安心ください。起こりうることを1つ示すだけで大丈夫です。今後に生かすためにも、理解しておく必要があります。

コード自体は前回の記事で見たものと非常によく似ていますが、ここではそれ以上のことができます。テイクプロフィット、ストップロス、建値の値を変更することで、注文を操作することができるのです。このコードで唯一不便なのは、一度チャート上に注文を出すと、それを簡単に削除して別の注文を出すことができないことです。チャート上に注文を残したままでも問題ありませんが、このEAを使って指値注文を作成する場合(現時点では指値注文に対してのみ機能します)、物理キーボードの右側にある数字キーボードを使って、注文が入る価格帯を変更することができます。

これは、このイベントハンドラを使っておこなわれます。なお、それぞれのキーは、ストップロスを増やす、注文価格を下げるなど、1つのことに使用されます。ツールボックスの[取引]タブで結果を見ながらやってください。多くのことを学ぶことができます。

このコードをプラットフォームで起動することに自信がない場合は(全く無害ですが、それでも注意する必要があります)、以下のビデオでコードが実際に何をおこなうかをご覧ください。



上記コードのデモ

ストップロスやテイクプロフィットを動かすと正しい動きになりますが、建値を動かすとストップロスやテイクプロフィットは止まったままであることが分かります。なぜこうなるのでしょうか。なぜなら、取引サーバーの場合、実際には注文を動かしているのであり、それはおそらく他のオープン取引のストップである可能性があるからです。

冒頭で、未決済の取引を決済する1つの方法として、オーダーブックに注文を出してスムーズに移動させるという話をしたのを覚えていらっしゃるでしょうか。これは、まさに、この段階で起こるべきことなのです。つまり、サーバーにとっては、移動させるべき価格は、その価格情報しか提供されていません。OCO注文を全体として扱っているわけではありません。OCO注文は、異なる価格帯で表示されます。いずれかのリミットに達すると、サーバーはポジションが開いている価格を削除するイベントを送信します。関連するチケットがシステムから削除されるため、テイクプロフィットおよびストップロスの両方の注文が存在しなくなります。

この場合、ストップロスとテイクプロフィットを注文価格と一緒に移動させる必要があります。これを実装するために、上記のコードに以下の変更を加えてみましょう。

void ModifyPrice(bool IsUp, const ulong ticket)
{
        double p, s, t;
        
        if (!OrderSelect(ticket)) return;
        p = OrderGetDouble(ORDER_PRICE_OPEN) + (SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE) * (IsUp ? 1 : -1));
        s = OrderGetDouble(ORDER_SL) + (SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE) * (IsUp ? 1 : -1));
        t = OrderGetDouble(ORDER_TP) + (SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE) * (IsUp ? 1 : -1));
        (*orders).ModifyPricePoints(ticket, p, s, t);
}

さて、建値が動くと同時に、テイクプロフィットとストップロスの価格も、建値からの距離を常に同じに保ちながら動くことになります。

ポジションについても同様に機能しますが、ポジションの建値を移動させることはできません。同じ関数でテイクプロフィットとストップロスを移動させますが、若干の違いがあります。

                bool ModifyPricePoints(const ulong ticket, const double Price, const double PriceStop, const double PriceTake)
                        {
                                ZeroMemory(m_TradeRequest);
                                m_TradeRequest.symbol   = _Symbol;
                                if (OrderSelect(ticket))
                                {
// ... Code to move orders...
                                }else if (PositionSelectByTicket(ticket))
                                {
                                        m_TradeRequest.action   = TRADE_ACTION_SLTP;
                                        m_TradeRequest.position = ticket;
                                        m_TradeRequest.tp       = NormalizeDouble(AdjustPrice(PriceTake), m_Infos.nDigits);
                                        m_TradeRequest.sl       = NormalizeDouble(AdjustPrice(PriceStop), m_Infos.nDigits);
                                }else return false;
                                ToServer();
                                
                                return (_LastError == ERR_SUCCESS);
                        };

上記のコードを使って、ブレイクイーブンまたはトレーリングストップを実装します。あとは動きを始めるトリガーとなる値、これがブレイクイーブン値であることを確認するだけです。トリガーされたら、ポジションの建値を確定してストップロス価格として置き、ポジション価格を変更して、その結果がブレイクイーブンとなります。

トレーリングストップも全く同じように機能しますが、この場合、ある価格距離か何かでトリガーがかかるとレベルが動きます。このとき、ストップロスに使用する新しい値を取り、上記の関数を呼び出します。これはとてもシンプルなことです。

ブレイクイーブンとトレーリングストップのトリガーについては、後ほど自動で動作するEAのためにこれらのトリガーを開発する方法を紹介するときに検討します。マニアの方は、これらのレベルについて、すでにいくつかのアイデアを思いついていらっしゃるかもしれません。そうなら素晴らしいです。あなたは正しい道を歩んでいます。

さて、価格変更の手続きに戻りますが、まだ触れていないことがあります。どのように、そしてなぜそこにあるのかを知ることが重要です。簡単に説明するために、以下のコードに注目してみましょう。

                bool ModifyPricePoints(const ulong ticket, const double Price, const double PriceStop, const double PriceTake)
                        {
                                ZeroMemory(m_TradeRequest);
                                m_TradeRequest.symbol   = _Symbol;
                                if (OrderSelect(ticket))
                                {
                                        m_TradeRequest.action = (Price > 0 ? TRADE_ACTION_MODIFY : TRADE_ACTION_REMOVE);
                                        m_TradeRequest.order  = ticket;
                                        if (Price > 0)
                                        {
                                                m_TradeRequest.price      = NormalizeDouble(AdjustPrice(Price), m_Infos.nDigits);
                                                m_TradeRequest.sl         = NormalizeDouble(AdjustPrice(PriceStop), m_Infos.nDigits);
                                                m_TradeRequest.tp         = NormalizeDouble(AdjustPrice(PriceTake), m_Infos.nDigits);
                                                m_TradeRequest.type_time  = (ENUM_ORDER_TYPE_TIME)OrderGetInteger(ORDER_TYPE_TIME) ;
                                                m_TradeRequest.expiration = 0;
                                        }
                                }else if (PositionSelectByTicket(ticket))
                                {
// Code for working with positions ...
                                }else return false;
                                ToServer();
                                
                                return (_LastError == ERR_SUCCESS);
                        };

注文を取り消したり決済したりすると、オーダーブックから削除する必要がある場合があります。また、実行中に、EAがゼロに等しい価格を生成するようなことをしてしまう危険性があります。これは、特に自動売買EAを使っている場合は、よくあることです。その場合、EAは注文を送信し、注文価格は修正されますが、エラーのため、この価格はゼロとして送信されます。

このような場合、取引サーバーは注文を拒否しますが、EAはそこに立ちはだかり、建値がゼロでなければならないと、ほとんど狂ったように主張することがあります。これを何とかしないと、ループしてしまい、ライブ口座では非常に不愉快になります。EAがサーバーに受け入れられずに、しつこく居座るのを避けるために、次のようなアイデアを盛り込みました。EAがゼロに等しいポジション開始価格を送信した場合、注文はサーバーによって閉じられる必要があります。このコードがまさにそれです

取引サーバーに、注文を決済し、オーダーブックから削除することを通知します。そうなれば、その注文はなくなり、そのことが通知させれます。ここではそのためのコードは載せていません。この種のものには、EAが何かを主張するのを防ぐだけでなく、他にも同じように便利な使い方があるからです。これは、単純にオーダーブックから注文を削除するために使用することができます。

でも、まだ記事は完成していません。最後にもう1つ、オプションではありますが、場合によっては有効な手順があります。さて、せっかくオーダーシステムの仕組みのブラックボックスを開けているのだから、もう1つ、以下の関数を見てみましょう。

                bool ClosePosition(const ulong ticket, const uint partial = 0)
                        {
                                double v1 = partial * m_Infos.VolMinimal, Vol;
                                bool IsBuy;
                                
                                if (!PositionSelectByTicket(ticket)) return false;
                                IsBuy = PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY;
                                Vol = PositionGetDouble(POSITION_VOLUME);
                                ZeroMemory(m_TradeRequest);
                                m_TradeRequest.action    = TRADE_ACTION_DEAL;
                                m_TradeRequest.type      = (IsBuy ? ORDER_TYPE_SELL : ORDER_TYPE_BUY);
                                m_TradeRequest.price     = SymbolInfoDouble(_Symbol, (IsBuy ? SYMBOL_BID : SYMBOL_ASK));
                                m_TradeRequest.position  = ticket;
                                m_TradeRequest.symbol    = _Symbol;
                                m_TradeRequest.volume    = ((v1 == 0) || (v1 > Vol) ? Vol : v1);
                                m_TradeRequest.deviation = 1000;
                                ToServer();
                                
                                return (_LastError == ERR_SUCCESS);
                        };

この関数で、多くの人が大きな夢や想像を膨らませ、星を見ることができるのです。この関数を見ると、ポジションを決済するためのものだと思うはずです。白昼夢を見る人はいないとおもいます。

読者の皆さん、落ち着いてください。関数の名を見た時点で先入観を持ってしまうことがまだお分かりでないかもしれませんが、もう少し深く、コードを分析し、なぜ多くの人が夢を見ているのかを理解しましょう。このプロシージャには計算があるのですが、なぜでしょうか。これは、部分決済ができるようにするためです。これがどのように実装されているのかを理解しましょう。ボリューム300のポジションがあるとします。最小取引可能数量が100であれば、100、200、300でエグジットすることができます。

しかし、そのためには、デフォルトでゼロである値を通知する必要があります。つまり、ポジションが完全に決済されることを関数に伝えますが、これはデフォルトのままにしておく場合にのみ起こります。ただ、細かいことを言えば、デフォルトのままではいけません。ボリューム値、すなわち決済されるロット数を指定します。最小ロット数量が100で300なら、数量は3xということです。部分決済する場合は、1または2を指定します。0、3または3より大きい値を指定すると、ポジションは全部決済されます

しかし、いくつかの選択肢はあります。例えば、B3(BolsadoBrasil)の場合、自社株は100単位で取引されるが、1株から取引できる端株市場があります。この場合、EAを分数モードで動作させていると、同じ例の300の値が1から299になり、それでもポジションは完全に決済されず、開いたまま残ります。

これでご理解いただけたでしょうか。具体的な手順は、市場やトレーダーの利益によって異なります。ヘッジ口座で作業する場合、上記の機能は絶対に必要です。これがなければ、ポジションは単に蓄積され、すでに決済できたはずのものを分析するために、EAから時間とリソースを奪うことになります。

記事の最後に、オーダーシステムに関するすべての質問をカバーするために、次の制限を取り除くためにEAコードがどのようにあるべきかを見てみましょう。これらの問題を解決するためには、EAのコードを少し変更する必要がありますが、これをすることで、もう、いろいろなことが楽しくできるようになります。おそらくこれで、プログラミングの知識がなくても、比較的簡単なコードで何ができるのか、少しはわかっていただけたのではないでしょうか。

このEAを部分的に修正するためには、チャートイベントの処理を担当するコードを変更する必要があります。新しいコードは以下の通りです。

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
#define KEY_UP                  38
#define KEY_DOWN                40
#define KEY_NUM_1               97
#define KEY_NUM_2               98
#define KEY_NUM_3               99
#define KEY_NUM_7               103
#define KEY_NUM_8               104
#define KEY_NUM_9               105

        static ulong sticket = 0;
        ulong ul0;
        int key = (int)lparam;
        
        switch (id)
        {
                case CHARTEVENT_KEYDOWN:
                        switch (key)
                        {
                                case KEY_UP:
                                        if (sticket == 0)
                                                sticket = (*orders).CreateOrder(ORDER_TYPE_BUY, user05, user03, user02, user01, user04);
                                        ul0 = (*orders).CreateOrder(ORDER_TYPE_BUY, user05, user03, user02, user01, user04);
                                        sticket = (ul0 > 0 ? ul0 : sticket);
                                        break;
                                case KEY_DOWN:
                                        if (sticket == 0)
                                                sticket = (*orders).CreateOrder(ORDER_TYPE_SELL, user05, user03, user02, user01, user04);
                                        ul0 = (*orders).CreateOrder(ORDER_TYPE_SELL, user05, user03, user02, user01, user04);
                                        sticket = (ul0 > 0 ? ul0 : sticket);
                                        break;
                                case KEY_NUM_1:
                                case KEY_NUM_7:
                                        if (sticket > 0) ModifyStop(key == KEY_NUM_7, sticket);
                                        break;
                                case KEY_NUM_2:
                                case KEY_NUM_8:
                                        if (sticket > 0) ModifyPrice(key == KEY_NUM_8, sticket);
                                        break;
                                case KEY_NUM_3:
                                case KEY_NUM_9:
                                        if (sticket > 0) ModifyTake(key == KEY_NUM_9, sticket);
                                        break;
                        }
                        break;
        }
}

消した部分を削除し、新しい変数を追加して注文クラスが返すチケットの値を受け取るようにしました。値が0以外の場合、新しいチケットは静的変数に保存され、代わりに新しい値が書き込まれるまで、その値が保存されます。これでもう、いろいろと管理できるようになりますし、誤って注文が約定してしまっても、新規注文を出すときに値を上書きしていなければ、注文の限度額を管理することができます。

さて、追加作業として、(次の記事の前に)本当にオーダーシステムの操作方法を学んだかどうかをテストするために、チケット値を使用して、オーダーシステムでマーケットポジションを開き、ポジションの限界を管理できるようにしてみてください。説明を読まずにやってください。そうすることで、自分が解説についていけるかどうかを把握することができます。

では、価格を変更するコードを見てみましょう。初めのコードは同じです。注文がポジションに変換された場合は省略すできます。テイクプロフィットを考えてみましょう。

void ModifyTake(bool IsUp, const ulong ticket)
{
        double p = 0, s, t;
        
        if (!OrderSelect(ticket)) return;
        if (OrderSelect(ticket))
        {
                p = OrderGetDouble(ORDER_PRICE_OPEN);
                s = OrderGetDouble(ORDER_SL);
                t = OrderGetDouble(ORDER_TP) + (SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE) * (IsUp ? 1 : -1));
        }else if (PositionSelectByTicket(ticket))
        {
                s = PositionGetDouble(POSITION_SL);
                t = PositionGetDouble(POSITION_TP) + (SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE) * (IsUp ? 1 : -1));
        }else return;
        (*orders).ModifyPricePoints(ticket, p, s, t);
}

クロスしたコードはもはや存在しないので、新しいコードを使用してポジションのテイクプロフィット値を管理することができます。以下に示すストップロスコードも同様です。

void ModifyStop(bool IsUp, const ulong ticket)
{
        double p = 0, s, t;
        
        if (!OrderSelect(ticket)) return;
        if (OrderSelect(ticket))
        {
                p = OrderGetDouble(ORDER_PRICE_OPEN);
                s = OrderGetDouble(ORDER_SL) + (SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE) * (IsUp ? 1 : -1));
                t = OrderGetDouble(ORDER_TP);
        }else if (PositionSelectByTicket(ticket))
        {
                s = PositionGetDouble(POSITION_SL) + (SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE) * (IsUp ? 1 : -1));
                t = PositionGetDouble(POSITION_TP);
        }else return;
        (*orders).ModifyPricePoints(ticket, p, s, t);
}

このEAを学習ツールとして、特にデモ口座で最大限に活用し、この最初の3稿にあることをよく探求してください。次回は、EAを初期化していくつかの情報を取得する方法と、この初期化に関する問題点、そして考えられる解決策を紹介します。ただし、これらの質問はオーダーシステムの一部ではありません。EAを自動化するために本当に必要なものはすべて実装しています。


結論

最初の3つの記事で見てきたように、完全な自動化EAを作るにはまだまだ遠い道のりがあります。多くの場合、ここで紹介されている内容を無視したり、知らなかったりすることが多いですが、これは危険です。自動システムについては、まだ始まったばかりで、正しい知識を持たずに使用することの危険性が指摘されています。

これまでに検討したコードは、すべて添付ファイルに掲載しています。勉強して、仕組みを学んでください。次回の記事でお会いできることを楽しみにしています。

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

添付されたファイル |
母集団最適化アルゴリズム:コウモリアルゴリズム(BA) 母集団最適化アルゴリズム:コウモリアルゴリズム(BA)
今回は、滑らかな関数に対して良好な収束性を示すコウモリアルゴリズム(BA)について考えてみることにします。
自動で動くEAを作る(第02回):コードを始める 自動で動くEAを作る(第02回):コードを始める
今日は、自動モードでシンプルかつ安全に動作するエキスパートアドバイザー(EA)を作成する方法を紹介します。前回は、自動売買をおこなうEAの作成に進む前に、誰もが理解しておくべき最初のステップについて説明しました。概念と構造が検討されました。
母集団最適化アルゴリズム:侵入雑草最適化(IWO) 母集団最適化アルゴリズム:侵入雑草最適化(IWO)
雑草がさまざまな条件で生き残る驚くべき能力は、強力な最適化アルゴリズムのアイデアになっています。IWO(Invasive Weed Optimization)は、以前にレビューされたものの中で最高のアルゴリズムの1つです。
自動で動くEAを作る(第01回):概念と構造 自動で動くEAを作る(第01回):概念と構造
今日は、自動モードでシンプルかつ安全に動作するエキスパートアドバイザー(EA)を作成する方法を紹介します。