一からの取引エキスパートアドバイザーの開発(第19部):新規受注システム(II)
はじめに
前回の「一からの取引エキスパートアドバイザーの開発(第18回)」では、口座運用の違うNETTING口座とHEDGING口座で異なる取引ができるシステムを目指し、受注システムの修正、変更、調整をおこないました。NETTINGタイプでは、システムは平均価格を生成し、1つの資産のためのポジションは1つだけです。HEDGING口座では、複数のポジションを持つことができ、それぞれのポジションには個別の限度額が設定されています。同じ資産を同時に買い、売ることができます。これはHEDGING口座でのみ可能です。これは、オプション取引を理解するための基礎となるものです。
ただし、今は受注システムを完全にビジュアル化することで、メッセージボックスをなくし、メッセージボックスなしで各ポジションにどんな値があるのかを分析することができるようにします。それは、新しい受注システムを見るだけでできます。これによって、一度にいくつもの調整ができるようになります。また、EAがリアルタイムで関連情報を表示し、余分な計算を必要としないので、OCOポジションや未決のOCO注文の利損の上限を簡単に知ることができます。
これは実装の最初の部分ですが、ゼロから始めるわけではありません。取引している資産のチャートにさらにオブジェクトやイベントを追加して、既存のシステムを修正するのです。
1.0.計画
ここで使うシステムの企画は特に難しいものではなく、チャート上で注文を表現するシステムだけを変えて、既存のシステムを改造するものです。これが主要なアイディアでとてもシンプルに思えますが、実際には、MetaTrader 5プラットフォームがすべての難しい作業をおこなうようにデータを操作してえモデル化するため、多くの創造性が必要とされます。
データのモデル化にはいくつかの方法があり、それぞれに長所と短所があります。
- 最初の方法は、リストを使うことです。サイクリックシングル、サイクリックダブル、あるいはハッシュシステムでも大丈夫です。いずれの方法を用いる場合も、システムの導入が容易であるというメリットがありますが、データ操作ができなくなる、あるいは注文数が制限されるというデメリットがあります。さらに、この場合、リストを保存するためだけにいろいろなロジックを追加で作成する必要があります。
- 2つ目の方法は、クラスの配列を作成し、そのクラスは新しく作成されたすべてのオブジェクトを格納して維持することです。この場合、配列はリストのように動作しますが、リストを使用する場合にコーディングしなければならないいくつかの事柄をMQL5が既にサポートしているため、書かなければならないコードはより少なくなります。ただし、イベントハンドリングなど別の問題が発生するので、この状況ではかなり難しいでしょう。
- 3つ目の方法は、ここで使用する方法です。MQL5で作成したコードを強制的に動的オブジェクトに対応させます。これは非現実的に思えますが、使用するデータのモデリングを正しくおこなえば、MQL5言語によって、画面上のオブジェクト数に制限のないシステムを作ることができます。さらに、すべてのオブジェクトがイベントを生成し、受信できるようになります。そして、それらの個別性にもかかわらず、プラットフォームは、それらがすべてリストまたは配列のインデックスにあるかのようにリンクされていると見なします。
実装するのが難しいと思われる方は、C_HLineTradeクラスの以下のコード部分をご覧ください。
inline void SetLineOrder(ulong ticket, double price, eHLineTrade hl, bool select) { string sz0 = def_NameHLineTrade + (string)hl + (string)ticket, sz1; ObjectCreate(Terminal.Get_ID(), sz0, OBJ_HLINE, 0, 0, 0); //... The rest of the code....
ハイライトされた部分は、まさに水平線を好きなだけ作ることができ、それらが完全に独立した方法でイベントを受け取ることを示しています。あとは、各行が持つ名前は一意であるため、その名前に基づいたイベントを実装すればよいだけです。残りはMetaTrader 5プラットフォームが引き受けます。結果は次のようになります。
一見理想的に思えますが、このモデリングだけでは、本当に必要な結果は得られません。アイデアは実現可能ですが、現在EAで利用できるデータモデリングでは、1つの名前を元に無制限にオブジェクトを持つことができないので、理想的とは言えません。かなり深いコードの修正を伴う変更をおこなう必要があります。
これからこの新しいデータモデリング手法の実装に入りますが、できるだけ安定的に動作し続けるように、コード全体を安定的に維持しつつ、そのために必要な部分のみを変更することにします。すべての作業はMetaTrader 5プラットフォームによって実行されるので、プラットフォームがモデリングをどのように理解すべきかを示すだけです。
2.0.実装
最初の修正は、C_HLineTradeを新しいC_ObjectsTradeクラスに変更することです。この新しいクラスは、私たちが必要としているもの、つまり無制限の数のオブジェクトをリンクさせる方法をサポートできます。
まず、以下のコードで元の定義を見てみましょう。
class C_ObjectsTrade { //+------------------------------------------------------------------+ #define def_NameObjectsTrade "SMD_OT" #define def_SeparatorInfo '*' #define def_IndicatorTicket0 1 //+------------------------------------------------------------------+ protected: enum eIndicatorTrade {IT_NULL, IT_STOP= 65, IT_TAKE, IT_PRICE}; //+------------------------------------------------------------------+ // ... The rest of the class code
ここには、これから実装する初期ベースがあります。将来的には拡張していく予定ですが、今はシステムを変更したり新しいデータモデリングをしたりしながらも、システムが安定した状態を維持できるようにしたいと考えています。
protected宣言の中にも、次のような関数があります。
inline double GetLimitsTake(void) const { return m_Limits.TakeProfit; } //+------------------------------------------------------------------+ inline double GetLimitsStop(void) const { return m_Limits.StopLoss; } //+------------------------------------------------------------------+ inline bool GetLimitsIsBuy(void) const { return m_Limits.IsBuy; } //+------------------------------------------------------------------+ inline void SetLimits(double take, double stop, bool isbuy) { m_Limits.IsBuy = isbuy; m_Limits.TakeProfit = (m_Limits.TakeProfit < 0 ? take : (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 : (isbuy ? (m_Limits.StopLoss < stop ? m_Limits.StopLoss : stop) : (stop < m_Limits.StopLoss ? m_Limits.StopLoss : stop))); } //+------------------------------------------------------------------+ inline int GetBaseFinanceLeveRange(void) const { return m_BaseFinance.Leverange; } //+------------------------------------------------------------------+ inline int GetBaseFinanceIsDayTrade(void) const { return m_BaseFinance.IsDayTrade; } //+------------------------------------------------------------------+ inline int GetBaseFinanceTakeProfit(void) const { return m_BaseFinance.FinanceTake; } //+------------------------------------------------------------------+ inline int GetBaseFinanceStopLoss(void) const { return m_BaseFinance.FinanceStop; }
現在は、将来的に実装する別の方式のセキュリティ対策として、これらの関数を提供しているに過ぎません。データや実行される解析は別の場所で実装できるとしても、一部はできるだけ継承の連鎖の低いところに置いておくのがよいでしょう。たとえ戻り値が派生クラスでしか使われないとしても、これを直接許可することはしたくはありません。派生クラスがこのC_ObjectsTradeオブジェクトクラスの内部にある値にアクセスできるようにはしたくありません。オブジェクトクラスのカプセル化の考え方が崩れ、将来、派生クラスが基本クラスの値を変更する際に、手続き呼び出しを通じて関連する変更をおこなわなければ、修正またはバグフィックスが困難になるためです。
呼び出しの重複をできるだけ少なくするため、すべての関数はインラインで宣言しています。実行ファイルのサイズは若干大きくなりますが、より安全なシステムを実現することができます。
次はprivate宣言です。
//+------------------------------------------------------------------+ private : string m_SelectObj; struct st00 { double TakeProfit, StopLoss; bool IsBuy; }m_Limits; struct st01 { int FinanceTake, FinanceStop, Leverange; bool IsDayTrade; }m_BaseFinance; //+------------------------------------------------------------------+ string MountName(ulong ticket, eIndicatorTrade it) { return StringFormat("%s%c%c%c%d", def_NameObjectsTrade, def_SeparatorInfo, (char)it, def_SeparatorInfo, ticket); } //+------------------------------------------------------------------+
最も重要な部分はオブジェクトの名前をモデリングすることになり、ハイライトされています。まだ使える基本的なものはシステムに残します。まずモデリングを作り、修正することで、システムを安定させるためです。次に、新しいオブジェクトを追加していきますが、これは非常に簡単に、素早くおこなうことができます。さらに、すでに達成した安定性を維持できます。
ここで紹介した以外にも多くの変更が加えられていますが、ここでは新機能と、以前のコードと比較してかなりの変更があった点のみを取り上げます。
1つ目の関数を以下に示します。
inline string CreateIndicatorTrade(ulong ticket, eIndicatorTrade it, bool select) { string sz0 = MountName(ticket, it); ObjectCreate(Terminal.Get_ID(), sz0, OBJ_HLINE, 0, 0, 0); ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_COLOR, (it == IT_PRICE ? clrBlue : (it == IT_STOP ? clrFireBrick : clrForestGreen))); 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(it), 3, 10)); return sz0; }
今のところ、横線を作成するだけです。名前を生成するコードに注意してください。また、色はユーザではなく、コードによって内部的に定義されることにも注意してください。
次に、同じ関数をオーバーロードすると、以下のようになります。
inline string CreateIndicatorTrade(ulong ticket, double price, eIndicatorTrade it, bool select) { if (price <= 0) { RemoveIndicatorTrade(ticket, it); return NULL; } string sz0 = CreateIndicatorTrade(ticket, it, select); ObjectMove(Terminal.Get_ID(), sz0, 0, 0, price); return sz0; }
この2つの関数は同じように見えて、実は違うものなので混同しないようにしてください。オーバーロードはよくおこなわれ、単純な関数を作り、そこに新しいパラメータを追加して、ある種のモデリングを蓄積していくものです。オーバーロードで実装しないと、同じコードを繰り返すようになることがあります。何かを宣言するのを忘れてしまうことがあるので、これは危険で、あまり実用的でもありません。そこで、関数をオーバーロードして、複数回呼び出すのではなく、1回だけ呼び出すようにします。
ここで1つ、この第2部で強調されている部分について触れておきましょう。ここで作成する必要はなく、別の場所でも大丈夫ですが、ご覧の通り、価格ゼロのオブジェクトを作成しようとすると、実はそれは破壊されなければなりません。
実際にこの現象が起こる瞬間を見るには、以下のコードをご覧ください。
class C_Router : public C_ObjectsTrade { // ... Internal class code .... void UpdatePosition(int iAdjust = -1) { // ... Internal function code ... for(int i0 = p; i0 >= 0; i0--) if(PositionGetSymbol(i0) == Terminal.GetSymbol()) { ul = PositionGetInteger(POSITION_TICKET); m_bContainsPosition = true; CreateIndicatorTrade(ul, PositionGetDouble(POSITION_PRICE_OPEN), IT_PRICE, false); CreateIndicatorTrade(ul, take = PositionGetDouble(POSITION_TP), IT_TAKE, true); CreateIndicatorTrade(ul, stop = PositionGetDouble(POSITION_SL), IT_STOP, true); // ... The rest of the code...
EAはOnTradeイベントを受信するたびに上記の関数を実行し、選択したポイントに指標を作成しようとしますが、ユーザーがリミットを解除するとゼロになります。そのため、呼び出されると、メモリ内に無駄なオブジェクトがないように、実際にチャートから指標が削除されます。そのため、作成の瞬間にチェックが入るので、ある時点で利得がありますが、
オーバーロードについては、実際のコードでどのように使われるかを十分に理解していない人もいるため、まだ問題が残っています。これを理解するために、以下の2つのコード部分をご覧ください。
class C_OrderView : public C_Router { private : //+------------------------------------------------------------------+ public : //+------------------------------------------------------------------+ void InitBaseFinance(int nContracts, int FinanceTake, int FinanceStop, bool b1) { SetBaseFinance(nContracts, FinanceTake, FinanceStop, b1); CreateIndicatorTrade(def_IndicatorTicket0, IT_PRICE, false); CreateIndicatorTrade(def_IndicatorTicket0, IT_TAKE, false); CreateIndicatorTrade(def_IndicatorTicket0, IT_STOP, false); } //+------------------------------------------------------------------+ // ... Rest of the code... class C_Router : public C_ObjectsTrade { // ... Class code ... void UpdatePosition(int iAdjust = -1) { // ... Function code .... for(int i0 = p; i0 >= 0; i0--) if(PositionGetSymbol(i0) == Terminal.GetSymbol()) { ul = PositionGetInteger(POSITION_TICKET); m_bContainsPosition = true; CreateIndicatorTrade(ul, PositionGetDouble(POSITION_PRICE_OPEN), IT_PRICE, false); // ... The rest of the code...
どちらの場合も、使用されている関数の名前は同じです。また、両者は同 C_ObjectsTradeクラスに属しています。しかし、この場合でもコンパイラは両者を区別することができます。これは、パラメータの数が異なるためです。よく見ると、「price」パラメータが追加されているだけの違いですが、他にもいくつかありそうです。ご覧のように、オーバーロードされたバージョンの1つに存在するすべてのコードを1回の呼び出しでコピーする方がはるかに簡単です。最終的にはよりクリーンなコードになり、メンテナンスが容易になります。
さて、C_ObjectsTradeクラスに戻りましょう。次に理解しなければならないのは、次のような関数です。
bool GetInfosOrder(const string &sparam, ulong &ticket, double &price, eIndicatorTrade &it) { string szRet[]; char szInfo[]; if (StringSplit(sparam, def_SeparatorInfo, szRet) < 2) return false; if (szRet[0] != def_NameObjectsTrade) return false; StringToCharArray(szRet[1], szInfo); it = (eIndicatorTrade)szInfo[0]; ticket = (ulong) StringToInteger(szRet[2]); price = ObjectGetDouble(Terminal.Get_ID(), sparam, OBJPROP_PRICE); return true; }
実は、これは新しいシステム全体の心臓で、精神であり、肉体でもあるのです。一見シンプルですが、EA全体が新しいモデリングシステムの要求通りに機能するために必要不可欠な仕事をしています。
ハイライトされたコード、特にStringSplit関数に注目してください。MQL5に存在していなければ、コーディングする必要があったでしょう。幸いMQL5に搭載されているので、この関数をフルに活用します。これは、オブジェクトの名前を必要なデータに分解します。オブジェクト名が作成されるとき、それは非常に特殊な方法でモデル化されます。このため、このコーディングモデルを元に戻すことができますので、StringSplitはStringFormat関数がおこなうことを元に戻します。
残りの関数ではオブジェクト名に存在するデータを取得し、後でそれをテストして使用できるようにします。つまり、MetaTrader 5でデータが生成され、私たちはそれを分解して何が起こったかを知り、MetaTrader 5にどのような手順を踏むべきかを指示します。目的は、MetaTrader 5を機能させることです。一からモデルを作るのではなく、インターフェイスやEAを一からモデリングしています。したがって、外部の解決策を探すのではなく、MetaTrader 5が提供するサポートの恩恵を受けるべきでしょう。
以下のコードでは、上でおこなったことと非常によく似たことをおこないます。
inline void RemoveAllsIndicatorTrade(bool bFull) { string sz0, szRet[]; int i0 = StringLen(def_NameObjectsTrade); ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false); 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_NameObjectsTrade) { if (!bFull) { StringSplit(sz0, def_SeparatorInfo, szRet); if (StringToInteger(szRet[2]) == def_IndicatorTicket0) continue; } }else continue; ObjectDelete(Terminal.Get_ID(), sz0); } ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true); }
決済されるポジションにしても削除されるリミットレベルにしても、チャートからラインを削除するたびに、EAがチャートから削除されるときと同じように、対応するオブジェクトを削除しなければなりません。オブジェクトを削除する必要がありますが、絶対に必要でない限り削除してはならない一連のラインもあります。これはTicket0で、極端に必要でない限り削除してはなりません。削除を回避するために、ハイライトされたコードを使用します。これがないと、このTicket0を毎回新しく作成する必要があります。このチケットは、後で説明する別のコード部分で非常に重要だからです。
それ以外のときは、特定のものを削除する必要があります。このために、以下に示すような別の削除関数を使用します。
inline void RemoveIndicatorTrade(ulong ticket, eIndicatorTrade it = IT_NULL) { ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false); if ((it != NULL) && (it != IT_PRICE)) ObjectDelete(Terminal.Get_ID(), MountName(ticket, it)); else { ObjectDelete(Terminal.Get_ID(), MountName(ticket, IT_PRICE)); ObjectDelete(Terminal.Get_ID(), MountName(ticket, IT_TAKE)); ObjectDelete(Terminal.Get_ID(), MountName(ticket, IT_STOP)); } ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true); }
新しいルーチンは、以下で見ることができます。
inline void PositionAxlePrice(double price, ulong ticket, eIndicatorTrade it, int FinanceTake, int FinanceStop, int Leverange, bool isBuy) { double ad = Terminal.GetAdjustToTrade() / (Leverange * Terminal.GetVolumeMinimal()); ObjectMove(Terminal.Get_ID(), MountName(ticket, it), 0, 0, price); if (it == IT_PRICE) { ObjectMove(Terminal.Get_ID(), MountName(ticket, IT_TAKE), 0, 0, price + Terminal.AdjustPrice(FinanceTake * (isBuy ? ad : (-ad)))); ObjectMove(Terminal.Get_ID(), MountName(ticket, IT_STOP), 0, 0, price + Terminal.AdjustPrice(FinanceStop * (isBuy ? (-ad) : ad))); } }
価格軸上にオブジェクトが配置されますが、様々な理由ですぐに消滅してしまうので、あまり執着しないようにしましょう。その中で、この連載の別の記事で取り上げたものがあります。単一チャート上の複数指標(第05部):MetaTrader 5をRAD(I)システムに変換する.この記事では、位置決めに直交座標を使えるオブジェクトを表にしていますが、この座標はXとYです。価格と時間の座標は、便利な場合もありますが、必ずしも便利ではありません。画面上のある地点に配置しなければならない要素を配置したい場合、価格と時間の座標を使って開発する方が早いでしょうが、XとY系に比べてはるかに作業がしにくくなります。
次回は変更しますが、今回はこれまでのシステムに代わるものを作ることが目的です。
次に、C_ObjectsTradeクラスの最後の重要な関数です。次のコードに示します。
inline double GetDisplacement(const bool IsBuy, const double Vol, eIndicatorTrade it) const { int i0 = (it == IT_TAKE ? m_BaseFinance.FinanceTake : m_BaseFinance.FinanceStop), i1 = (it == IT_TAKE ? (IsBuy ? 1 : -1) : (IsBuy ? -1 : 1)); return (Terminal.AdjustPrice(i0 * (Vol / m_BaseFinance.Leverange) * Terminal.GetAdjustToTrade() / Vol) * i1); }
Chart Traderで指定した値を、発注待ちの注文や成行で開くポジションに変換する関数です。
これらの変更はすべて、C_HLineTrade関数をC_ObjectsTradeに変換するために実装されたものです。ただし、この変更には別の変更も必要でした。例えば、同じく大きく変更されたクラスとしてC_ViewOrderがあります。このクラスの一部は存在意義がないため単に消滅し、残った機能は変更されています。特に注目すべき関数は以下の通りです。
1番目は、Chart Traderから来るデータを初期化するための関数です。
void InitBaseFinance(int nContracts, int FinanceTake, int FinanceStop, bool b1) { SetBaseFinance(nContracts, FinanceTake, FinanceStop, b1); CreateIndicatorTrade(def_IndicatorTicket0, IT_PRICE, false); CreateIndicatorTrade(def_IndicatorTicket0, IT_TAKE, false); CreateIndicatorTrade(def_IndicatorTicket0, IT_STOP, false); }
ハイライトされている部分が実際にTicket0が作成される部分です。このチケットは、マウスとキーボードを使用して指値注文を発注するために使用します。SHIFTで買い、CTRLで売ります。従来は、この時点でラインを作成し、そのラインで注文のポジションを示していました。発注する注文と同じように、未決注文や未決済のポジションも表示されるようになりました。常にシステムをチェックするということです。例えるなら、車を組み立てるときに、実際に使うときにブレーキがどのように動くかをずっとチェックしているようなものです。
コードが長くなることの大きな問題は、関数を作っても、それが実際に使われる時にしか動作しているかどうかが分からないことですが、常にチェックされるようになったわけです。たとえすべての関数を使わなくても、さまざまな場所でコードが再利用されているため、常にチェックされている状態です。
この記事で最後に紹介するルーチンは以下の通りです。指値注文を出します。以前の記事の同じ関数と比較すると、非常にコンパクトになっています。
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; //Let mouse button click bKeyBuy = (Key & 0x04) == 0x04; //Pressed SHIFT bKeySell = (Key & 0x08) == 0x08; //Pressed CTRL Mouse.GetPositionDP(dt, price); if (bKeyBuy != bKeySell) { Mouse.Hide(); bCheck = CheckLimits(price); } else Mouse.Show(); PositionAxlePrice((bKeyBuy != bKeySell ? price : 0), def_IndicatorTicket0, IT_PRICE, (bCheck ? 0 : GetBaseFinanceTakeProfit()), (bCheck ? 0 : GetBaseFinanceStopLoss()), GetBaseFinanceLeveRange(), bKeyBuy); if((bEClick) && (bKeyBuy != bKeySell) && (local == 0)) CreateOrderPendent(bKeyBuy, local = price); local = (local != price ? 0 : local); }
なぜなら、今度はシステムに新しいルールが入るので、関数が「痩せて」コンパクトになったからです。
結論
次回の記事で使用する変更点を紹介しました。これらはすべて、よりシンプルにするためと、場合によって異なる可能性があるものを示すためのものです。誰もが運用に役立つEAのプログラミングを追って学ぶべきだというのが私の考えなので、完成されたすぐに使えるシステムを提示するだけではありません。解決すべき問題があることを示し、開発中に発生する問題や課題を解決するために歩んできた道筋を示したいと思います。この点をご理解いただければと思います。システムを作ってすぐに使える形で提示するのであれば、売った方がいいのですが、これは私の本意ではありません..。
MetaQuotes Ltdによりポルトガル語から翻訳されました。
元の記事: https://www.mql5.com/pt/articles/10474
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索