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

一からの取引エキスパートアドバイザーの開発(第25部):システムの堅牢性の提供(II)

MetaTrader 5 | 2 11月 2022, 08:51
301 0
Daniel Jose
Daniel Jose

はじめに

以前の「システムの堅牢性の提供(I)」稿では、システムの信頼性と堅牢性を高めるためにEAの一部を変更する方法を見ましたが、

これは、本稿でやろうとしていることの紹介にすぎませんでした。知っていたこと、計画したこと、望んでいたことをすべて忘れてください。ここで最も難しいのは、物事を分離できることです。連載を始めて以来、EAはほぼ常に進化してきました。いくつかのものを追加、変更、さらには削除しています。今回はこれまでやってきたことを極限まで突き詰めていきます。

見かけとは違い、大きな問題があります。適切に設計されたEAの内部にはいかなる種類の指標も含まれないということです。指示された注文の位置が尊重されることのみを観察し、保証します。完璧なEAは本質的に、価格が何をしているかについての真の洞察を提供するウィザードにすぎません。指標は視野になく、見るのはチャート上にあるポジションまたは注文だけです。

私がナンセンスなことを言っている、何を言っているのかわからない、と思われるかもしれませんが、Metatrader 5が異なるものに対して異なるクラスを提供する理由を考えたことはありますか。プラットフォームに指標、サービス、スクリプト、EAが1つのブロックではなく別々にあるのはなぜでしょうか。つまり...

これがポイントなのです。別々なものが別々なのは、別々に取り組んだ方がよいからです。

指標は何らかの一般的な目的に使用されます。全体的なパフォーマンスを損なわないように、指標の設計がよく考えられているのは良いことです。つまり、Metatrader 5プラットフォームではなく、他の指標に損害を与えるということです。これらは別のスレッドで実行されるため、非常に効率的にタスクを並行して実行できます。

サービスはさまざまな方法で役立ちます。たとえば、連載の「Web上のデータへのアクセス(II)」および「Web上のデータへのアクセス(III)」稿では、サービスを使用して非常に興味深い方法でデータにアクセスしました。実際、これをEAで直接おこなうこともできますが、他の記事で既に説明したように、これは最適な方法ではありません。

スクリプトは非常にユニークな方法で私たちを助けます。スクリプトは特定の時間だけ存在し、非常に具体的なことをおこない、その後チャートから消えるからです。または、時間枠などのチャート設定を変更するまで、そこにとどまることができます。

これは可能性を少し制限しますが、これはそのまま受け入れなければならないことの一つです。反対に、EAは、取引システムでの作業に特化しています。EAの取引システムの一部ではない機能やコードを追加することはできますが、これは高性能または高信頼性のシステムにはあまり適していません。その理由は、取引システムの一部ではないものはすべてEAに含めるべきではないためです。物事は適切な場所に配置し、正しく処理する必要があります。

したがって、信頼性を向上させるために最初にすべきことは、取引システムの一部ではないコードからすべてを完全に削除し、これらを指標などに変換することです。EAコードに残るのは、注文やポジションの管理、分析、処理を担当する部分だけです。他のものはすべて削除されます。

始めましょう。


2.0.実装

2.0.1.EAの背景を削除する

これはEAに損害を与えたり、問題を引き起こしたりすることはありませんが、画面を空白にして特定の項目だけを表示したいという人もいます。そこで、この部分をEAから削除して指標に変えます。実装するのは非常に簡単です。どのクラスにも触れずに、次のコードを作成します。

#property copyright "Daniel Jose"
#property indicator_chart_window
#property indicator_plots 0
//+------------------------------------------------------------------+
#include <NanoEA-SIMD\Auxiliar\C_Wallpaper.mqh>
//+------------------------------------------------------------------+
input string                    user10 = "Wallpaper_01";        //Used BitMap
input char                      user11 = 60;                    //Transparency (from 0 to 100)
input C_WallPaper::eTypeImage   user12 = C_WallPaper::IMAGEM;   //Background image type
//+------------------------------------------------------------------+
C_Terminal      Terminal;
C_WallPaper WallPaper;
//+------------------------------------------------------------------+
int OnInit()
{
        IndicatorSetString(INDICATOR_SHORTNAME, "WallPaper");
        Terminal.Init();
        WallPaper.Init(user10, user12, user11);

        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
{
        return rates_total;
}
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        switch (id)
        {
                case CHARTEVENT_CHART_CHANGE:
                        Terminal.Resize();
                        WallPaper.Resize();
        break;
        }
        ChartRedraw();
}
//+------------------------------------------------------------------+

ご覧のとおり、すべてが非常に自然でわかりやすいものです。EAからコードを単純に削除し、チャートに追加できる指標に変換しました。また、背景、透明度のレベル、チャートからの削除などの変更は、EAの動作には影響しません。

そして今、EAのパフォーマンス低下を実際に引き起こしているものを削除し始めます。これらは、時折、またはすべての価格変動で機能するため、EAの速度が低下し、チャート上の注文またはポジションで何が起こっているかを監視するという本来の仕事を実行できなくなることがあります。


2.0.2.Volume At Priceを指標に変換する

このようには見えないかもしれませんが、Volume At Priceシステムには時間がかかります。これはEAにとってしばしば重要になります。つまり、ボラティリティが高く、価格が大きな方向性もなく激しく変動する瞬間です。EAがそのタスクを完了するために利用可能なすべてのマシンサイクルを必要とするのは、このような時です。指標が作業を乗っ取ろうとしたために、良い機会を逃すのは気が動転するでしょう.EAからそれを削除し、以下のコードを作成して実際の指標に変えましょう。

#property copyright "Daniel Jose"
#property indicator_chart_window
#property indicator_plots 0
//+------------------------------------------------------------------+
#include <NanoEA-SIMD\Tape Reading\C_VolumeAtPrice.mqh>
//+------------------------------------------------------------------+
input color             user0   = clrBlack;                     //Bar color
input   char            user1   = 20;                                   //Transparency (from 0 to 100 )
input color     user2 = clrForestGreen; //Buying
input color     user3 = clrFireBrick;   //Selling
//+------------------------------------------------------------------+
C_Terminal                      Terminal;
C_VolumeAtPrice VolumeAtPrice;
//+------------------------------------------------------------------+
int OnInit()
{
        Terminal.Init();
        VolumeAtPrice.Init(user2, user3, user0, user1);
        EventSetTimer(1);
        
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
{
        return rates_total;
}
//+------------------------------------------------------------------+
void OnTimer()
{
        VolumeAtPrice.Update();
}
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        VolumeAtPrice.DispatchMessage(id, sparam);
        ChartRedraw();
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
        EventKillTimer();
}
//+------------------------------------------------------------------+

これは最も簡単な部分でした。EAからコードを削除し、指標に入れました。コードをEAに戻したい場合は、指標コードをコピーしてEAに戻すだけです。

簡単なことから始めましたが、事態はより複雑になっています。次に、EAからTimes & Tradeを削除します。


2.0.3.Times & Tradeを指標に変える

EAと指標の両方で使用できるコードを作成することを目指すのは、それほど単純ではありません。サブウィンドウで機能する指標なので、指標に変換するのは簡単に思えますが、サブウィンドウで動作するため、簡単ではありません。主な問題は、前の場合のようにすべてをおこなうと、指標ウィンドウに次の結果が表示されることです。

ユーザーが画面から指標を削除したい場合に混乱するため、このようなものを指標ウィンドウに配置することはお勧めできません。したがって、これは別の方法でおこなう必要があります。そして、このパスは非常に紛らわしいように見えるかもしれませんが、実際には単純な一連のディレクティブといくつかの編集であり、指標ウィンドウに次の結果が表示されます。

これはまさにユーザーが期待するものであり、上の図に見られるような混乱ではありません。

以下は、Times & Trade指標の完全なコードです。

#property copyright "Daniel Jose"
#property version   "1.00"
#property indicator_separate_window
#property indicator_plots 0
//+------------------------------------------------------------------+
#include <NanoEA-SIMD\Tape Reading\C_TimesAndTrade.mqh>
//+------------------------------------------------------------------+
C_Terminal        Terminal;
C_TimesAndTrade   TimesAndTrade;
//+------------------------------------------------------------------+
input int     user1 = 2;      //Scale
//+------------------------------------------------------------------+
bool isConnecting = false;
int SubWin;
//+------------------------------------------------------------------+
int OnInit()
{
        IndicatorSetString(INDICATOR_SHORTNAME, "Times & Trade");
        SubWin = ChartWindowFind();
        Terminal.Init();
        TimesAndTrade.Init(user1);
        EventSetTimer(1);
                
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
{
        if (isConnecting)
                TimesAndTrade.Update();
        return rates_total;
}
//+------------------------------------------------------------------+
void OnTimer()
{
        if (TimesAndTrade.Connect())
        {
                isConnecting = true;
                EventKillTimer();
        }
}
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        switch (id)
        {
                case CHARTEVENT_CHART_CHANGE:
                        Terminal.Resize();
                        TimesAndTrade.Resize();
        break;
        }
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
        EventKillTimer();
}
//+------------------------------------------------------------------+

このコードは、EAコードに存在しない強調表示された行を除いて、EAで使用されているものと似ています。では、何が問題なのでしょうか。それとも問題はないのでしょうか。実際には、いくつかの問題があります。コードはまったく同じではなく、指標やEAコードではなく、クラスコードで、違いがあります。しかし、違いを考える前に、次のことを考えてみましょう:。コンパイルするものとコンパイルしないものをコンパイラにどのように伝えるのでしょうか。おそらく、プログラミングするときは、これについてまったく心配する必要はありません。単にコードを作成し、気に入らないものがあれば削除するだけでしょう。

経験豊富なプログラマーにはルールがあります。明らかに機能しないものだけを削除し、それ以外の場合は、実際にコンパイルされていなくてもフラグメントを保持するということです。しかし、記述された関数が常に機能するようにしたい場合、線形のコードでこれをおこなうにはどうすればよいでしょうか。質問です。何をコンパイルし、何をコンパイルしないかをコンパイラに伝える方法をご存じですか。lご存じでなくても問題ありません。個人的に、私も始めたときはやり方がわかりませんでした。ただし、これは大いに役立ちます。やり方を見てみましょう。

一部の言語にはコンパイルディレクティブがあります。著者によってはプリプロセッサと呼ばれることもありますが、考え方は同じです。何をコンパイルするか、どのようにコンパイルするかをコンパイラに指示します。特定のことをテストできるようにコードを意図的に分離するために使用できる、非常に特殊なタイプのディレクティブがあります。条件付きコンパイルディレクティブです。適切に使用すると、同じコードをさまざまな方法でコンパイルできます。これはまさに、Times & Tradeの例でおこなわれていることです。条件付きコンパイルを何が(EAまたは指標)生成するかを選択します。このパラメータを定義した後、#defineディレクティブを作成し、条件付きディレクティブ#ifdef #else #endifを使用して、コードのコンパイル方法をコンパイラに通知します。

理解しにくいかもしれないので、仕組みを見てみましょう。

EAコードで、以下で強調表示されている行を定義して追加します。

#define def_INTEGRATION_WITH_EA
//+------------------------------------------------------------------+
#include <NanoEA-SIMD\Trade\Control\C_IndicatorTradeView.mqh>
#ifdef def_INTEGRATION_WITH_EA
        #include <NanoEA-SIMD\Auxiliar\C_Wallpaper.mqh>
        #include <NanoEA-SIMD\Tape Reading\C_VolumeAtPrice.mqh>
        #include <NanoEA-SIMD\Tape Reading\C_TimesAndTrade.mqh>
#endif
//+------------------------------------------------------------------+

次のことが起こります。MQHファイルのクラスでEAをコンパイルする場合は、EAで定義されている#ifdefine def_INTEGRATION_WIT_EAディレクティブを残します。これにより、EAには、取得して指標に挿入するすべてのクラスが含まれるようになります。指標を削除したい場合、コードを削除する必要はなく、定義をコメントするだけです。これは、ディレクティブが宣言されている行をコメント行に変換するだけでできます。コンパイラはディレクティブを認識せず、存在しないものとして与えられます。存在しないため、条件付きディレクティブ#ifdef def_INTEGRATION_WITH_EAが見つかるたびに完全に無視されますが、上記の例のそれと#endif部分の間のコードはコンパイルされません。

これが、C_TimesAndTradeクラスで実装するアイデアです。新しいクラスはこんな感じです。注意を引くポイントを1つだけ示します。

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#include <NanoEA-SIMD\Auxiliar\C_Canvas.mqh>
#ifdef def_INTEGRATION_WITH_EA

#include <NanoEA-SIMD\Auxiliar\C_FnSubWin.mqh>

class C_TimesAndTrade : private C_FnSubWin

#else

class C_TimesAndTrade

#endif
{
//+------------------------------------------------------------------+
#define def_SizeBuff 2048
#define macro_Limits(A) (A & 0xFF)
#define def_MaxInfos 257
#define def_ObjectName "TimesAndTrade"
//+------------------------------------------------------------------+
        private :
                string  m_szCustomSymbol;

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

}

コンパイルディレクティブを使用していない人にとっては、このコードは奇妙に見えるかもしれません。def_INTEGRATION_WITH_EAディレクティブはEAで宣言されています。その後、次のことが起こります。コンパイラがこのファイルからオブジェクトコードを生成する場合、次の関係が想定されます。コンパイルされるファイルがEAであり、宣言されたディレクティブがある場合、コンパイラは、条件付きディレクティブ#ifdef def_INTEGRATION_WITH_EA#elseの間にあるコードでオブジェクトコードを生成します。通常、このような場合は#elseディレクティブを使用します。ディレクティブdef_INTEGRATION_WITH_EAが定義されていない指標など、別のファイルがコンパイルされる場合、ディレクティブ#else#endifの間のすべてがコンパイルされます。それが仕組みです。

EAまたは指標をコンパイルするときは、これらの各テストと一般的な操作を理解するために、C_TimesAndTradeクラスのコード全体を調べます。MQL5コンパイラはすべての設定をおこない、2つの異なるファイルを維持する必要性に関連する時間と労力を節約します。


2.0.4.EAをより機敏にする

前述のように、EAは注文システムでのみ機能する必要があります。これまでのところ、それは現在指標となった機能を備えています。この理由は非常に個人的なものであり、EAがおこなうべき計算に関係するものです。ただし、この計算システムは変更され、別のメソッドに移行されました。このため、EAが注文を処理する代わりにおこなったいくつかのことによって、注文システムが損なわれていることに気付きました。最悪の問題はOnTickイベントにありました。

void OnTick()
{
        Chart.DispatchMessage(CHARTEVENT_CHART_CHANGE, 0, TradeView.SecureChannelPosition(), C_Chart_IDE::szMsgIDE[C_Chart_IDE::eRESULT]);
#ifdef def_INTEGRATION_WITH_EA
        TimesAndTrade.Update();
#endif 
}

ボラティリティが高い期間に取引をおこなわない人は、必要に応じて元の指標をすべて備えたEAを持つことができるように、このイベントには条件付きディレクティブが付いています。ただし、これが良い考えだと思う前に、Times & Tradeの更新機能がどのように機能するかを思い出してください。

inline void Update(void)
{
        MqlTick Tick[];
        MqlRates Rates[def_SizeBuff];
        int i0, p1, p2 = 0;
        int iflag;
        long lg1;
        static int nSwap = 0;
        static long lTime = 0;

        if (m_ConnectionStatus < 3) return;
        if ((i0 = CopyTicks(Terminal.GetFullSymbol(), Tick, COPY_TICKS_ALL, m_MemTickTime, def_SizeBuff)) > 0)
        {

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

        }
}

上記のコードは、C_TimesAndTradeクラスにあるupdate関数の一部です。問題は強調表示された部分にあります。実行されるたびに、特定の時点以降に作成されたすべての取引チケットを返すように要求がサーバーに送信されますが、これはそれほど問題ではありません。問題は、この呼び出しが他の2つのイベントと重なることがあるということです。

最初の最も明白なイベントは、多数の取引が発生する可能性があることです。これにより、OnTick関数は多数の呼び出しを受けます。C_TimesAndTradeクラスに存在する上記のコードを実行する必要があることに加えて、この関数は。C_IndicatorTradeViewクラスに存在するSecureChannelPosition関数を呼び出すという別の問題に対処します。これも小さな問題ですが、それだけではありません。ボラティリティが低いにもかかわらず2つのイベントが同時に発生することはすでに述べましたが、最初のイベントはこのイベントでした。

2つ目は、既に更新されているOnTimeイベント内にあり、次のようになります。

#ifdef def_INTEGRATION_WITH_EA
void OnTimer()
{
        VolumeAtPrice.Update();
        TimesAndTrade.Connect();
}
#endif 

設計された方法でEAを使用する場合、さらに多くのコードを受け取ることを考えると、イベントが一致するために問題が発生することがあります。これが発生すると、EAは注文システムに関係のないことを(たとえ1秒でも)実行し続けます。

C_TimesAndTradeにある関数とは異なり、この関数はC_VolumeAtPriceクラスに存在し、注文を管理するときにEAのパフォーマンスを実際に損なう可能性があります。これが起こる理由は次のとおりです。

inline virtual void Update(void)
{
        MqlTick Tick[];
        int i1, p1;

        if (macroCheckUsing == false) return;
        if ((i1 = CopyTicksRange(Terminal.GetSymbol(), Tick, COPY_TICKS_TRADE, m_Infos.memTimeTick)) > 0)
        {
                if (m_Infos.CountInfos == 0)
                {
                        macroSetInteger(OBJPROP_TIME, m_Infos.StartTime = macroRemoveSec(Tick[0].time));
                        m_Infos.FirstPrice = Tick[0].last;
                }                                               
                for (p1 = 0; (p1 < i1) && (Tick[p1].time_msc == m_Infos.memTimeTick); p1++);
                for (int c0 = p1; c0 < i1; c0++) SetMatrix(Tick[c0]);
                if (p1 == i1) return;
                m_Infos.memTimeTick = Tick[i1 - 1].time_msc;
                m_Infos.CurrentTime = macroRemoveSec(Tick[i1 - 1].time);
                Redraw();
        };      
};

理由は強調表示された部分にありますが、その中で最悪なのはREDRAWです。指定された値を超えるボリュームの受信ティックごとに、価格でのボリューム全体が画面から削除され、再計算されて元の場所に戻されるため、EAのパフォーマンスに大きな悪影響を及ぼします。これは約1秒ごとに発生します。これは他のことと一致する可能性があります。そのため、すべての指標がEAから削除されています。EAで直接使用できるように残しましたが、前述の理由から、そのままにすることはお勧めしません。

これらの変更が必要でしたが、もっと象徴的で、変更する必要がある別のものがあります。今回の変更は、OnTradeTransactionイベントに関するものです。このイベントの使用は、システムを可能な限り柔軟にする試みです。注文実行EAをプログラムする人の多くは、OnTradeイベントを使用して、どの注文がサーバー上にあるかどうか、またはどのポジションがまだ開いているかを確認します。このやり方が間違っていると言っているのではありません。何が起こっているかを私たちに知らせてくれるのがサーバーなので、あまり効率的ではないというだけです.ただし、OnTradeイベントの大きな問題は、不必要にチェックし続けなければならないという事実です。OnTradeTransactionイベントを使用すると、少なくとも動きの分析に関してより効率的なシステムが得られますが、これはここでの目的ではありません。誰もが自分の基準に最も適した方法を使用します。

このEAを開発するとき、ストレージ構造を使用しないことに決めたので、処理できる注文やポジションの数を制限しませんが、この事実は状況を非常に複雑にするため、OnTradeTransactionイベントを使用して見つけることができるOnTradeイベントの代替手段が必要になります。

このイベントは実装が非常に難しいため、おそらく多くの人が使用していませんが、私には選択の余地がありませんでした。機能するか機能しないかのどちらかです。さもなければ、事態は複雑になります。ただし、以前のバージョンでは、このイベントのコードは非常に非効率的でした。以下にそれを示します。

void OnTradeTransaction(const MqlTradeTransaction &trans, const MqlTradeRequest &request, const MqlTradeResult &result)
{
#define def_IsBuy(A) ((A == ORDER_TYPE_BUY_LIMIT) || (A == ORDER_TYPE_BUY_STOP) || (A == ORDER_TYPE_BUY_STOP_LIMIT) || (A == ORDER_TYPE_BUY))

        ulong ticket;
        
        if (trans.symbol == Terminal.GetSymbol()) switch (trans.type)
        {
                case TRADE_TRANSACTION_DEAL_ADD:
                case TRADE_TRANSACTION_ORDER_ADD:
                        ticket = trans.order;
                        ticket = (ticket == 0 ? trans.position : ticket);
                        TradeView.IndicatorInfosAdd(ticket);
                        TradeView.UpdateInfosIndicators(0, ticket, trans.price, trans.price_tp, trans.price_sl, trans.volume, (trans.position > 0 ? trans.deal_type == DEAL_TYPE_BUY : def_IsBuy(trans.order_type)));
                        break;
                case TRADE_TRANSACTION_ORDER_DELETE:
                         if (trans.order != trans.position) TradeView.RemoveIndicator(trans.order);
                         else TradeView.UpdateInfosIndicators(0, trans.position, trans.price, trans.price_tp, trans.price_sl, trans.volume, trans.deal_type == DEAL_TYPE_BUY);
                         if (!PositionSelectByTicket(trans.position)) TradeView.RemoveIndicator(trans.position);
                        break;
                case TRADE_TRANSACTION_ORDER_UPDATE:
                        TradeView.UpdateInfosIndicators(0, trans.order, trans.price, trans.price_tp, trans.price_sl, trans.volume, def_IsBuy(trans.order_type));
                        break;
                case TRADE_TRANSACTION_POSITION:
                        TradeView.UpdateInfosIndicators(0, trans.position, trans.price, trans.price_tp, trans.price_sl, trans.volume, trans.deal_type == DEAL_TYPE_BUY);
                        break;
        }
                
#undef def_IsBuy
}

上記のコードは機能しますが、控えめに言ってもよくないものです。上記のコードによって生成される無駄な呼び出しの数はばかげています。上記のコードを修正できない場合、安定性と信頼性の面でEAを改善することはできません。

このため、メッセージのパターンを見つけるためにデモ口座でいくつかのことをおこないましたが、これは実際には非常に困難です。パターンは見つかりませんでしたが、生成された無駄な呼び出しを回避し、コードを安定させ、信頼性を高め、同時に市場でいつでも取引できるほど柔軟にする何かを見つけました。もちろん、修正すべき小さなバグはまだいくつかありますが、コードは非常に優れています。

void OnTradeTransaction(const MqlTradeTransaction &trans, const MqlTradeRequest &request, const MqlTradeResult &result)
{
#define def_IsBuy(A) ((A == ORDER_TYPE_BUY_LIMIT) || (A == ORDER_TYPE_BUY_STOP) || (A == ORDER_TYPE_BUY_STOP_LIMIT) || (A == ORDER_TYPE_BUY))

        if (trans.type == TRADE_TRANSACTION_HISTORY_ADD) if (trans.symbol == Terminal.GetSymbol()) TradeView.RemoveIndicator(trans.position);
        if (trans.type == TRADE_TRANSACTION_REQUEST) if ((request.symbol == Terminal.GetSymbol()) && (result.retcode == TRADE_RETCODE_DONE)) switch (request.action)
        {
                case TRADE_ACTION_PENDING:
                        TradeView.IndicatorAdd(request.order);
                        break;
                case TRADE_ACTION_SLTP:
                        TradeView.UpdateIndicators(request.position, request.tp, request.sl, request.volume, def_IsBuy(request.type));
                        break;
                case TRADE_ACTION_DEAL:
                        TradeView.RemoveIndicator(request.position);
                        break;
                case TRADE_ACTION_REMOVE:
                        TradeView.RemoveIndicator(request.order);
                        break;
                case TRADE_ACTION_MODIFY:
                        TradeView.UpdateIndicators(request.order, request.tp, request.sl, request.volume, def_IsBuy(request.type));
                        break;
        }
                        
#undef def_IsBuy
}

何が起こっているのかをすぐに理解しようとしないで、この機能の美しさを楽しんでください。ほぼ完璧です。私がやったから言うわけではなく、堅牢性と機敏性が高いからです。

複雑に見えるかもしれませんが、このコードには2つのチェックがあります。何が起こっているのかをよりよく説明するために、それらは以下で強調表示されています。

if (trans.type == TRADE_TRANSACTION_HISTORY_ADD) if (trans.symbol == Terminal.GetSymbol()) TradeView.RemoveIndicator(trans.position);
if (trans.type == TRADE_TRANSACTION_REQUEST) if ((request.symbol == Terminal.GetSymbol()) && (result.retcode == TRADE_RETCODE_DONE)) switch (request.action)
{

//... inner code ...

}

緑色で強調表示された行は、履歴で取引が発生するたびに、その資産がEAによって観測された資産と同じかどうかを確認します。その場合、C_IndicatorTradeViewクラスは、チャートから指標を削除するコマンドを受け取ります。これは、注文がポジションになったときと、ポジションが決済されたときの2つの場合に発生する可能性があります。HEDGINGではなくNETTINGモードのみを使用することに注意してください。したがって、何が起こっても、指標はチャートから削除されます。

次のように尋ねることができます。ポジションが決済されている場合は問題ありません。しかし、注文がポジションになる場合はだめですか。いいえ。しかし、問題はエラー内ではなく、C_IndicatorTradeViewクラス内で解決されます。記事の次のセクションで検討します。

一方、赤い線は、C_IndicatorTradeViewクラスに転送された無駄なメッセージの量を極度に減らしています。これは、リクエストに対してサーバーから返されたレスポンスをチェックすることによっておこなわれるため、EAが追跡しているアセットと同じ名前でリクエストを発行して確認を得る必要があります。その場合にのみ、新しいラウンドのコールがC_IndicatorTradeViewクラスに送信されます。

このシステムについて私が言えることはこれだけですが、話はまだ終わっていません。やるべきことはたくさんありますが、これからはC_IndicatorTradeViewクラスだけに焦点を当てます。ここで、必要ないくつかの変更から始めます。


2.0.5.C_IndicatorTradeViewによって作成されるオブジェクトの数を減らす

一からの取引エキスパートアドバイザーの開発(第23回)の記事で、注文またはストップレベルをシフトするというかなり抽象的かつ非常に興味深い概念を紹介しました。概念は、位置のゴーストまたはシャドウを使用することでした。これらは実際の動きが発生するまで使用され、取引サーバーが認識するものを定義してチャートに表示します。このモデルには小さな問題があります。Metatrader 5によって管理されるオブジェクトを追加しますが、追加されたオブジェクトはほとんどの場合必要ないため、Metatrader 5は役に立たないものやめったに使用されないものでいっぱいのオブジェクトのリストを取得するということです。

しかし、EAが常にオブジェクトを作成したり、不要なオブジェクトをリストに保持したりすることは望ましくありません。これはEAのパフォーマンスを低下させるからです。Metatrader 5を使用して注文を管理するため、システム全体に干渉する無駄なオブジェクトを排除する必要があります。

ただし、非常に簡単な解決策があります。実はそれほど単純ではありません。C_IndicatorTradeViewクラスを改善するために、さらに変更を加える予定です。画面にゴーストを表示したままにして、非常に興味深く、ほとんど使用されていない方法を使用します。

楽しくて面白くなります。

まず、選択構造を変更します。次のようになります。

struct st00
{
        eIndicatorTrade it;
        bool            bIsBuy,
			bIsDayTrade;
        ulong           ticket;
        double          vol,
                        pr,
                        tp,
                        sl;
}m_Selection;

何が変わったのかを正確に説明することはしません。ご自分で分かると思います。ただし、この変更により、コーディングロジックのいくつかの部分が単純化されました。

したがって、ゴースト指標は独自のインデックスを持つようになります。

#define def_IndicatorGhost      2

これにより、モデリングという名前も変更されました。

#define macroMountName(ticket, it, ev) StringFormat("%s%c%llu%c%c%c%c", def_NameObjectsTrade, def_SeparatorInfo,\
                                                                        ticket, def_SeparatorInfo,              \
                                                                        (char)it, def_SeparatorInfo,            \
                                                                        (char)(ticket <= def_IndicatorGhost ? ev + 32 : ev))

小さなことのように思えますが、すぐに多くが変わります。それでは続けましょう。

これで価格ポジションマクロは常にストレートになり、重複がなくなります。コードは次のようになります。

#define macroSetLinePrice(ticket, it, price) ObjectSetDouble(Terminal.Get_ID(), macroMountName(ticket, it, EV_LINE), OBJPROP_PRICE, price)
#define macroGetLinePrice(ticket, it) ObjectGetDouble(Terminal.Get_ID(), macroMountName(ticket, it, EV_LINE), OBJPROP_PRICE)

これらの変更により、他に2つの関数を作成する必要がありました。1つ目は、指標自体を作成する関数の置き換えです。指標同士が実際に異なる理由を文字通り明らかにしました。以下でご覧ください。

#define macroCreateIndicator(A, B, C, D)        {                                                                       \       
                m_TradeLine.Create(ticket, sz0 = macroMountName(ticket, A, EV_LINE), C);                                \
                m_BackGround.Create(ticket, sz0 = macroMountName(ticket, A, EV_GROUND), B);                             \
                m_BackGround.Size(sz0, (A == IT_RESULT ? 84 : 92), (A == IT_RESULT ? 34 : 22));                         \
                m_EditInfo1.Create(ticket, sz0 = macroMountName(ticket, A, EV_EDIT), D, 0.0);                           \
                m_EditInfo1.Size(sz0, 60, 14);                                                                          \
                if (A != IT_RESULT)     {                                                                               \
                        m_BtnMove.Create(ticket, sz0 = macroMountName(ticket, A, EV_MOVE), "Wingdings", "u", 17, C);    \
                        m_BtnMove.Size(sz0, 21, 23);                                                                    \
                                        }else                   {                                                       \
                        m_EditInfo2.Create(ticket, sz0 = macroMountName(ticket, A, EV_PROFIT), clrNONE, 0.0);           \
                        m_EditInfo2.Size(sz0, 60, 14);  }                                                               \
                                                }

                void CreateIndicator(ulong ticket, eIndicatorTrade it)
                        {
                                string sz0;
                                
                                switch (it)
                                {
                                        case IT_TAKE    : macroCreateIndicator(it, clrForestGreen, clrDarkGreen, clrNONE); break;
                                        case IT_STOP    : macroCreateIndicator(it, clrFireBrick, clrMaroon, clrNONE); break;
                                        case IT_PENDING : macroCreateIndicator(it, clrCornflowerBlue, clrDarkGoldenrod, def_ColorVolumeEdit); break;
                                        case IT_RESULT  : macroCreateIndicator(it, clrDarkBlue, clrDarkBlue, def_ColorVolumeResult); break;
                                }
                                m_BtnClose.Create(ticket, macroMountName(ticket, it, EV_CLOSE), def_BtnClose);
                        }
#undef macroCreateIndicator

お気付きかもしれませんが、私はコードで前処理ディレクティブを使うのが大好きで、ほとんどいつも使っています。ただし、ご覧のとおり、指標を区別するのは非常に簡単になりました。指標に希望の色を付けたい場合は、このコードを変更してください。それらはすべてほぼ同一であるため、マクロを使用してすべて同じように機能させ、同じ要素を持たせることができます。これは究極のコードの再利用です。

これとよく似た名前の別の関数がありますが、それは何か違うことをします。それについては最後に詳しく説明します。

IndicatorAdd関数が変更されました—一部のフラグメントを削除しました。

inline void IndicatorAdd(ulong ticket)
                        {
                                char ret;
                                
                                if (ticket == def_IndicatorTicket0) ret = -1; else
                                {
                                        if (ObjectGetDouble(Terminal.Get_ID(), macroMountName(ticket, IT_PENDING, EV_LINE, false), OBJPROP_PRICE) != 0) return;
                                        if (ObjectGetDouble(Terminal.Get_ID(), macroMountName(ticket, IT_RESULT, EV_LINE, false), OBJPROP_PRICE) != 0) return;
                                        if ((ret = GetInfosTradeServer(ticket)) == 0) return;
                                }
                                switch (ret)
                                {
                                        case  1:
                                                CreateIndicatorTrade(ticket, IT_RESULT);
                                                PositionAxlePrice(ticket, IT_RESULT, m_InfoSelection.pr);
                                                break;
                                        case -1:
                                                CreateIndicatorTrade(ticket, IT_PENDING);
                                                PositionAxlePrice(ticket, IT_PENDING, m_InfoSelection.pr);
                                                break;
                                }
                                ChartRedraw();
                                UpdateIndicators(ticket, m_InfoSelection.tp, m_InfoSelection.sl, m_InfoSelection.vol, m_InfoSelection.bIsBuy);
				UpdateIndicators(ticket, m_Selection.tp, m_Selection.sl, m_Selection.vol, m_Selection.bIsBuy);
                        } 

削除されたフラグメントの1つが、強調表示されたフラグメントに置き換えられます。指値注文と0指標が作成されなくなるということでしょうか。それらはまだ作成されていますが、別の場所にあります。後1つの関数の登場です。

未決注文指標と指標0を作成する関数です。更新された指標コードは次のとおりです。

#define macroUpdate(A, B) { if (B > 0) {                                                                \
                if (b0 = (macroGetLinePrice(ticket, A) == 0 ? true : b0)) CreateIndicator(ticket, A);   \
                PositionAxlePrice(ticket, A, B);                                                        \
                SetTextValue(ticket, A, vol, (isBuy ? B - pr : pr - B));                                \
                                        } else RemoveIndicator(ticket, A); }
                                                                        
                void UpdateIndicators(ulong ticket, double tp, double sl, double vol, bool isBuy)
                        {
                                double pr;
                                bool b0 = false;
                                
                                if (ticket == def_IndicatorGhost) pr = m_Selection.pr; else
                                {
                                        pr = macroGetLinePrice(ticket, IT_RESULT);
                                        if ((pr == 0) && (macroGetLinePrice(ticket, IT_PENDING) == 0))
                                        {
                                                CreateIndicator(ticket, IT_PENDING);
                                                PositionAxlePrice(ticket, IT_PENDING, m_Selection.pr);
                                                ChartRedraw();
                                        }
                                        pr = (pr > 0 ? pr : macroGetLinePrice(ticket, IT_PENDING));
                                        SetTextValue(ticket, IT_PENDING, vol);
                                }
                                if (m_Selection.tp > 0) macroUpdate(IT_TAKE, tp);
                                if (m_Selection.sl > 0) macroUpdate(IT_STOP, sl);
                                if (b0) ChartRedraw();
                        }
#undef macroUpdate

関数には、コードで強調表示されている非常に興味深いチェックがあります。ゴースト指標の作成に役立つため、IndicatorAdd関数は指値注文指標と指標0を作成できなくなります。しかし、このチェックをおこなうだけでは、ゴースト指標を作成するのに十分ではありません。

DispatchMessage関数にはいくつかの詳細が含まれるようになりました。これらは小さな変更ですが、私たちの生活をずっと楽にしてくれます。変更された部分を示します。

void DispatchMessage(int id, long lparam, double dparam, string sparam)
{

// ... Code ....

        switch (id)
        {
                case CHARTEVENT_MOUSE_MOVE:

// ... Code ....
                        }else if ((!bMounting) && (bKeyBuy == bKeySell) && (m_Selection.ticket > def_IndicatorGhost))
                        {
                                if (bEClick) SetPriceSelection(price); else MoveSelection(price);
                        }
                        break;

// ... Code ...

                case CHARTEVENT_OBJECT_CLICK:
                        if (GetIndicatorInfos(sparam, ticket, it, ev)) switch (ev)
                        {
                                case EV_CLOSE:

// ... Code ...

                                        break;
                                case EV_MOVE:
                                        CreateGhostIndicator(ticket, it);
                                        break;
                        }
                break;
        }
}

CHARTEVENT_MOUSE_MOVEに修正箇所があります。このコードは、ゴーストを指標しているかどうかを確認します。ゴーストの場合、フラグメントはブロックされますが、そうでない場合、移動は可能です(指標自体が移動できる場合)。

指標の新しい位置をクリックするとすぐに、すべてのコンポーネントを含むゴーストがオブジェクトのリストから削除されます。明確かと思います。ここで強調表示された点に注意してください—それはCreateGhostndicator関数の呼び出しです。このコードについては、次のセクションで説明します。


2.0.6.CreateGhostIndicatorの仕組み

CreateGhostIndicator関数は奇妙に思えます。以下のコードを見てみましょう。

CreateGhostIndicator

#define macroSwapName(A, B) ObjectSetString(Terminal.Get_ID(), macroMountName(ticket, A, B), OBJPROP_NAME, macroMountName(def_IndicatorGhost, A, B));
                void CreateGhostIndicator(ulong ticket, eIndicatorTrade it)
                        {
                                if (GetInfosTradeServer(m_Selection.ticket = ticket) != 0)
                                {
                                        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false);
                                        macroSwapName(it, EV_LINE);
                                        macroSwapName(it, EV_GROUND);
                                        macroSwapName(it, EV_MOVE);
                                        macroSwapName(it, EV_EDIT);
                                        macroSwapName(it, EV_CLOSE);
                                        m_TradeLine.SetColor(macroMountName(def_IndicatorGhost, it, EV_LINE), def_IndicatorGhostColor);
                                        m_BackGround.SetColor(macroMountName(def_IndicatorGhost, it, EV_GROUND), def_IndicatorGhostColor);
                                        m_BtnMove.SetColor(macroMountName(def_IndicatorGhost, it, EV_MOVE), def_IndicatorGhostColor);
                                        ObjectDelete(Terminal.Get_ID(), macroMountName(def_IndicatorGhost, it, EV_CLOSE));
                                        m_TradeLine.SpotLight();
                                        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);
                                        m_Selection.it = it;
                                }else m_Selection.ticket = 0;
                        }
#undef macroSwapName

この関数で何も作成されないのは非常に興味深いことです。ただし、EAをコンパイルして実行すると、サーバー上で注文ステータスを示すゴーストが作成されます。これを理解するには、次のビデオをご覧ください。これは、システムが実際にどのように機能するかのデモンストレーションです。



ゴースト指標は実際にチャート上に作成されますが、これは実際にどのように発生するのでしょうか。コードのどこかで実際に指標を作成することなく、どうやって指標を作成できたのでしょうか。

これらはゴーストです。実際にそれらが作成されているのを見ることはありません。コードを読んで、「ここで私は発見しました...この時点でゴースト指標が作成されています...」おいうような行を見つけようとしても意味がありません。真実は、それらはすでにチャート上にあるが、注文またはポジションの操作を開始するまでどこにも表示されず、表示されるようになるということです。これはどのように可能なのでしょうか。

これを理解するために、EA実行スレッドを考えてみましょう。

EAの初期化後、次の実行スレッドが表示されます。

スレッド1

init_ea<<<<システム初期化スレッド

オレンジ色の領域はEAの一部で、緑色の領域はC_IndicatorTradeViewクラスの一部です。指標が作成されて画面に表示される前に何が起こるかを確認してください。黒い矢印は未決注文とポジションの一般的なパスです。青い矢印はポジションのパスで、紫色の矢印は未決注文が指標を作成するためにたどるパスを示しています。もちろん、関数内にはスレッドを何らかの方法で誘導するものがありますが、ここの図は、すべてが一般的にどのように機能するかを示すことを目的としています。

以前のスキームは、システムの起動時に一度だけ使用されます。未決注文をチャートに配置するたびに、2つの異なる実行スレッドがあります。最初のスレッドは、指標0の作成とチャートへの注文の配置を担当します。これを下図に示します。

スレッド2

   <<<<指標0初期化スレッド

チャートに表示される注文を実際に作成するのはクラスではないことに注意してください。そうしようとするだけです。すべてがうまくいけば、SetPriceSelection関数が正常に実行され、新しいスレッドが作成され、チャートに注文が表示されます。したがって、次のスレッドを取得します。取引サーバーが報告した場所で実際に注文を出すので、最初に指定した場所で実際に注文が終わるまで待っても意味がありません。ボラティリティにより、サーバーが指定した時点とは異なる時点で注文を約定する場合、EAはこれを修正し、注文を正しい場所に提示します。したがって、条件が取引モデルに適しているかどうかを分析するだけで済みます。

スレッド3

   <<<未決注文のスレッド

これは、チャート上で注文をおこなう部分です。ここでは、完全な注文について話しています。つまり、エントリポイント、テイクプロフィット、ストップロスがあります。しかし、テイクプロフィットやストップロスなど、指値注文の1つが注文から削除された場合、スレッドはどうなるでしょうか?これらのスレッドはこれには対応しません。実際、スレッドはここのものとはかなり異なりますが、要素はほとんど同じです。ボタンをクリックして指値注文の1つを決済する場合の流れを以下に示します。

奇妙に思えるかもしれません。

スレッド4

   <<<注文または逆指値レベルの削除

2つのスレッドが隣り合っています。紫色の矢印で示されたものが最初に実行されます。OnTradeTransactionイベントが実行されるとすぐに、サーバーからの応答がキャプチャされ、システムがトリガーされて画面から指標が削除されます。逆指値注文の削除とポジションまたは注文の決済の違いは1つだけです。これらの場合、SetPriceSelection関数は実行されませんが、OnTradeTransactionイベントフローは残ります。

これはすべて素晴らしいことですが、それでもゴーストがどのように現れるかという問題には答えていません。

ゴーストがどのように作成されるかを理解するために、実行スレッドがどのように発生するかを知る必要があります。EAが未決注文をどのように配置するか、または指標0の作成が実際にどのように発生するかです。このフローを上図に示します。実行スレッドを理解すると、ゴーストが理解しやすくなります。

最後に、ゴーストがどのように作成されるかを見てみましょう。関数CreateGhostIndicatorをもう一度見てください。何も作成せず、単にいくつかのデータを操作します。なぜでしょうか。オブジェクトを作成しようとすると、既存のオブジェクトにオーバーレイされ、その上にレンダリングされるためです。したがって、必要なオブジェクトは非表示になります。この問題には2つの解決策があります。1つ目は、他のすべてよりも劣るセットを作成することです。注文を表す他のオブジェクトの前に作成されます。ただし、この解決策には問題があります。役に立たないオブジェクトがたくさんあるということです。ただし、これを避けるためにコード全体を変更しています。2番目の解決策は、ゴーストを作成し、操作しているポインタを削除してから、再度作成することです。これらの解決策はどちらもあまり実用的ではなく、さらに、どちらも非常に高価です。

ドキュメントを調べているときに、私の注意を引く情報を見つけました。ObjectSetString関数を使用すると、一見すると意味をなさないオブジェクトプロパティ(OBJPROP_NAME)を操作できます。なぜこれが許可されているのか、私は興味をそそられました。意味がありません。オブジェクトが既に作成されている場合、その名前を変更する意味は何ですか。

ポイントはこれです。オブジェクトの名前を変更すると、古いオブジェクトは存在しなくなり、新しい名前が付けられます。名前を変更した後、オブジェクトは元のオブジェクトの代わりになるため、EAは問題なく元のオブジェクトを作成でき、ゴーストはグラフィックに副作用がなく、痕跡を残さずに表示および非表示にすることができます。削除する必要がある唯一のオブジェクトは、指標の[閉じる]ボタンです。これは次の行でおこなわれます。

ObjectDelete(Terminal.Get_ID(), macroMountName(def_IndicatorGhost, it, EV_CLOSE));

ここに細かな事実があります。ObjectSetString関数のドキュメントを見ると、その操作に関する警告があります。

グラフィカルオブジェクトの名前を変更すると、2つのイベントが同時に生成されます。これらのイベントは、EAまたは指標を使用して処理できます。OnChartEvent()関数:

  • 古い名前のオブジェクトを削除するイベント
  • 新しい名前でオブジェクトを作成するイベント

名前を変更しようとしているオブジェクトが、準備ができていない場合に表示されることを望まないため、これを考慮することが重要です。そのため、名前の変更の前後にもう1つ追加します。

ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false);

// ... Secure code...

ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);

コード内でオブジェクトの作成および削除イベントがトリガーされることはなくなります。これで、ゴーストが表示される完全なコードが得られ、正しい動作が得られます。

おそらく、指標の名前を変更するだけで、コードが実際にどのようにゴーストを作成するのかはまだ明らかではないでしょう。ここで終わりにします。少し手助けするために、ゴースト実行スレッドがどのように見えるかを示しておきます。下の画像をご覧ください。

スレッド5

  <<<<ゴーストスレッド

これはスレッド2のほぼ完全なクローンであるため、作成コードを実際に記述しなくても、ゴーストがどのように作成および破壊されるかを知ることができます。


結論

著者として、私はこの記事が非常に興味深く、刺激的でさえあると感じました。EAコードを大幅に変更する必要がありましたが、これはすべて良い方向に向かっています。信頼性をさらに高めるには、まだいくつかのことと手順を実行する必要がありますが、すでに実装されている変更は、システム全体に大きなメリットをもたらします。ここで、よく設計されたプログラムは、通常、実装されている特定の手順を実行するのだということを強調したいと思います。ドキュメンテーションを調べ、実行スレッドを分析し、システムのベンチマークをおこなって重要な瞬間に過負荷になるかどうかを確認し、何よりも落ち着いて、コードが実際のモンスターにならないようにするべきです。コードをフランケンシュタインのコピーに変えないようにすることは非常に重要です。これはコードをより良くするわけではなく、将来の改善、特に修正をより困難にするだけだからです。

連載の読者すべてに温かいハグを送ります。次の記事でお会いできることを楽しみにしています。



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

添付されたファイル |
データサイエンスと機械学習(第06回):勾配降下法 データサイエンスと機械学習(第06回):勾配降下法
勾配降下法は、ニューラルネットワークや多くの機械学習アルゴリズムの訓練において重要な役割を果たします。これは、その印象的な成果にもかかわらず、迅速でインテリジェントなアルゴリズムであり、多くのデータサイエンティストによっていまだに誤解されています。
ニューラルネットワークが簡単に(第21部):変分オートエンコーダ(Variational autoencoder、VAE) ニューラルネットワークが簡単に(第21部):変分オートエンコーダ(Variational autoencoder、VAE)
前回の記事で、オートエンコーダアルゴリズムについて学びました。他のアルゴリズム同様、このアルゴリズムには長所と短所があります。元の実装では、オートエンコーダは、訓練標本からオブジェクトを可能な限り分離するために使用されます。今回はその短所への対処法についてお話します。
ニューラルネットワークが簡単に(第22部):回帰モデルの教師なし学習 ニューラルネットワークが簡単に(第22部):回帰モデルの教師なし学習
モデルと教師なし学習アルゴリズムの研究を続けます。今回は、回帰モデルの学習に適用した場合のオートエンコーダの特徴について提案します。
DoEasy-コントロール(第12部):基本リストオブジェクト、ListBoxおよびButtonListBox WinFormsオブジェクト DoEasy-コントロール(第12部):基本リストオブジェクト、ListBoxおよびButtonListBox WinFormsオブジェクト
この記事では、WinFormsオブジェクトリストの基本オブジェクトと、2つの新しいオブジェクトを作成します。ListBoxとButtonListBoxです。