
MQL5 と MQL4 でのシンボル選択とナビゲーションユーティリティの開発
イントロダクション
経験豊富なトレーダーが認識している事実として、トレードにおいて最も時間のかかるものはポジションを開いたり追跡したりするのではなく、シンボルを選択してインプットポイントを探すことというものがあります。
もちろん、1-2 シンボルのみで機能する場合、大きな問題ではありません。 しかし、トレードツールキットが株式の数百と外国為替シンボルの数十で構成されている場合, 適切なエントリポイントを見つけるために数時間かかる場合があります。
この記事では、株式の検索を簡素化するEAを開発します。 このEAは次の3つの方法で役に立ちます。
- 条件を満たす株式を事前にフィルタすることです。
- 生成されたストックリストのナビゲーションを簡素化する必要があります。
- 決定を下すために必要な追加データを表示することです。
初期EAテンプレート
最初は MQL5 でEAを開発します。 しかし、多くのブローカーはまだ MetaTrader5 アカウントを提供していないため、EAを再開発して、記事の最後にある MetaTrader4 でも動作するようにします。
では、MQL5 ウィザードによって生成されるものと異なるテンプレートを準備してみましょう:
//+------------------------------------------------------------------+ //| _finder.mq5 | //| Copyright 2018, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Klymenko Roman (needtome@icloud.com)" #property link "https://logmy.net" #property version "1.00" #property strict //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //---タイマーの作成 EventSetTimer(1); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { EventKillTimer(); } //+------------------------------------------------------------------+ //| Timer function | //+------------------------------------------------------------------+ void OnTimer() { } //+------------------------------------------------------------------+ void OnChartEvent(const int id, //イベント ID const long& lparam, //long 型のイベントパラメータ const double& dparam, //double 型のイベントパラメータ const string& sparam) //文字列型のイベントパラメータ { }
このテンプレートでは、EAを作成するときにタイマーを登録します。 タイマーは毎秒アクティブになります。 OnTimer標準関数が毎秒1回呼び出されることを意味します。
MQL5 ウィザードによって生成される典型的なテンプレートとは異なる唯一の文字列は#property strictです。 この文字列は、EAが MetaTrader4 で正しく動作するために必要です。 MetaTrader5 に大きな影響を及ぼさないため、事前にテンプレートに追加します。
以下の標準関数を適用します。
- OnInit: チャート上で条件を満たすトレード商品のボタンを表示します。
- OnDeinit: タイマーとEAによって作成されたすべてのグラフィカルオブジェクトを削除します。
- OnTimer: タイマーは、EAによって作成されたチャート上のグラフィカルオブジェクトのクリックを決定するために使用します。
- OnChartEvent: チャート上に作成されたグラフィカルオブジェクトをクリックすると、EAが起動します。
条件を満たすシンボルのリストは、CArrayString 型オブジェクトに格納されます。 したがって、EAへのオブジェクト記述に MQH file をインクルードしてください。
#include <Arrays\ArrayString.mqh>
チャートを操作するには、CChart 型オブジェクトも必要です。 その定義も含めてみましょう。
#include <Charts\Chart.mqh>
すべては、文字列の#propertyブロックの後に、任意の関数の外側に、テンプレートの先頭に行われます。
次に、EAによって作成されたすべてのボタンの幅と高さを決定する必要があります。 値を、文字列の#includeブロックの後に指定する定数に設定します。
#define BTN_HEIGHT (20) #define BTN_WIDTH (100)
インプット
このEAはインプットによって管理されます。 この記事の後半で実装する関数をすぐに定義できるように、見てみましょう。
sinput string delimeter_01=""; //---フィルタ設定--- input bool noSYMBmarketWath=true; //マーケットウォッチにない場合は非表示にする input bool noSYMBwithPOS=true; //ポジションがある場合は非表示にする input ValueOfSpread hide_SPREAD=spread_b1; //スプレッドの場合に隠す input uint hide_PRICE_HIGH=0; //価格が高い場合は非表示にする input uint hide_PRICE_LOW=0; //価格が低い場合は非表示にする input bool hideProhibites=true; //トレードが無効な場合は非表示 input bool hideClosed=true; //相場が閉じている場合は非表示 input StartHour hide_HOURS=hour_any; //開いている時間があるかどうかを表示する input double hideATRcents=0.00; //ATR がドルで設定された値より小さい場合は非表示にします sinput string delimeter_02=""; //チャートの設定------ input bool addInfoWatch=false; //マーケットウォッチにチャートを追加する input bool viewCandle=true; //オープンローソク足チャート input bool viewVolumes=true; //ティックボリュームの表示 input bool showInfoSymbol=true; //移動方向を表示 input bool showNameSymbol=true; //シンボル名の表示
2つのインプットにカスタム型があることがすぐにわかります。 したがって、インプットの前に型の定義を追加してみましょう。 どちらのカスタム型も列挙です。
ValueOfSpread 列挙体は、EAによって表示されるシンボルのスプレッド値に関する可能な条件を定義します。
enum ValueOfSpread { spread_no,//No spread_b05,// > 0.05% spread_b1,// > 0.1% spread_b15,// > 0.15% spread_l15,// < 0.15% spread_l1,// < 0.1% };
価格の 0.1% を超えるスプレッドは増加していると考えられます。 したがって、デフォルトでは 0.1% 未満のスプレッドを持つシンボルのみが表示されます。 したがって、スプレッドパラメータの場合の非表示の値は> 0.1%です。 しかし、ブローカーによって提供されるそのようなシンボルのリストが小さすぎる場合は、このパラメータに別の値を選択することができます。
StartHour 列挙体には、相場の一部が開かれる主な期間の一覧があります。
enum StartHour { hour_any, //いつでも hour_9am, //午前9時 hour_10am,//午前10時 hour_4pm, //午後4時 hour_0am, //真夜中 };
9 am(またはその他の値) は、正確に指定された時刻に開くシンボルのみが表示されることを意味するものではありません。 代わりに、この時間 (たとえば、9:05) で開くシンボルが表示されることを意味します。
したがって、4 pm時は、16:30 で開かれた米国株式相場の株式のみが表示されることを意味します。
10 amは主にロシアとヨーロッパの株式相場の株式に関連します。
9 amはインデックスの開始時間です。
最後に,真夜中は、クロックラウンド動作するので、外国為替相場のオープン時間です。
グローバル変数
標準関数の内容の操作を開始する前に、可視性スコープがEAである一連の変数を宣言するだけです。 インプットの後に追加してみましょう:
//EAによって作成されたすべてのグラフィカルオブジェクトの名前に追加される接頭辞: string exprefix="finder"; //条件に適合するシンボルの配列: CArrayString arrPanel1; //arrPanel1 配列内の現在のシンボルのインデックス: int panel1val; //EAによって作成されたチャートを格納する配列です (現時点ではチャートは1つしかありません)。 CChart charts[]; //EAによって作成されたチャートへのポインタを格納する配列です (現時点ではポインタは1つしかありません)。 long curChartID[];
コメントは、なぜ変数が必要なのかを明確にする必要があります。
すべての準備が完了しました。 EAの開発を開始する準備が整いました。 しかし、まず、結果を見てみましょう。
//+------------------------------------------------------------------+ //| _finder.mq5 | //| Copyright 2018, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Klymenko Roman (needtome@icloud.com)" #property link "https://logmy.net" #property version "1.00" #property strict #include <Arrays\ArrayString.mqh> #include <Charts\Chart.mqh> #define BTN_HEIGHT (20) #define BTN_WIDTH (100) enum ValueOfSpread { spread_no,//No spread_b05,//> 0.05% spread_b1,//> 0.1% spread_b15,//> 0.15% spread_l15,// spread_l1,// }; enum StartHour { hour_any,//いつでも hour_9am,//午前9時 hour_10am,//午前10時 hour_4pm,//午後4時 hour_0am,//真夜中 }; input bool noSYMBmarketWath=true; //気配表示パネルに存在しないシンボルを隠す input bool noSYMBwithPOS=true; //ポジションを持つシンボルの非表示 input ValueOfSpread hide_SPREAD=spread_b1; //スプレッドでシンボルを非表示にする input uint hide_PRICE_HIGH=0; //価格が高い場合にシンボルを非表示にする (0-非表示) input uint hide_PRICE_LOW=0; //価格が低い場合にシンボルを非表示にする (0-非表示) input bool hideProhibites=true; //トレードに使用できないシンボルの非表示 input StartHour hide_HOURS=hour_any; //[シンボルを開く] input bool viewCandle=true; //オープンローソク足チャート //EAによって作成されたすべてのグラフィカルオブジェクトの名前に追加される接頭辞: string exprefix="finder"; //条件に適合するシンボルの配列: CArrayString arrPanel1; //arrPanel1 配列内の現在のシンボルのインデックス: int panel1val; //EAによって作成されたチャートを格納する配列です (現時点ではチャートは1つしかありません)。 CChart charts[]; //EAによって作成されたチャートへのポインタを格納する配列です (現時点ではポインタは1つしかありません)。 long curChartID[]; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //---タイマーの作成 EventSetTimer(1); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { if(reason!=REASON_CHARTCHANGE){ ObjectsDeleteAll(0, exprefix); } EventKillTimer(); } //+------------------------------------------------------------------+ //| Timer function | //+------------------------------------------------------------------+ void OnTimer() { } void OnChartEvent(const int id, //イベント ID const long& lparam, //long 型のイベントパラメータ const double& dparam, //double 型のイベントパラメータ const string& sparam) //文字列型のイベントパラメータ { }
シンボルフィルタ関数
チャート上で条件を満たすシンボルのボタンを表示する関数から始めます。 この関数にstart_symbolsという名前をつけましょう。 この関数は、 OnInit関数の内部で呼び出されます。 結果として、 OnInit関数は最終的な形式を取ります。
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { start_symbols(); //---タイマーの作成 EventSetTimer(1); //--- return(INIT_SUCCEEDED); }
OnInitの内部ですべてを実装できる場合、なぜ別の関数が必要なのでしょうか? それは簡単です。 EAを起動するときだけでなく、 Rキーを押したときにもこの関数を呼び出します。 したがって、チャートからEAを削除して再度開くことなく、文字のリストを更新することができます。
スプレッドは絶えず変化しているため、シンボルリストを頻繁に更新する必要があります。 また、特定のシンボル上のオープンポジションの存在も変化します。 したがって、以前に起動したEAを再度使用する前に、(R を押して) シンボルリストを更新して、現在のデータを表示することを忘れないでください。
start_symbols関数を見てみましょう。 また、他の関数を起動するためのラッパーとしても機能します。
void start_symbols(){ //リスト内の現在のシンボルのインデックスをゼロ (配列内の最初のシンボル) に設定します。 panel1val=0; //シンボルリストを準備します。 prepare_symbols(); //チャートから以前に作成したシンボルボタンを削除します。 ObjectsDeleteAll(0, exprefix); //シンボルリストの表示: show_symbols(); //変更を表示するには、チャートを更新します。 ChartRedraw(0); }
prepare_symbolsとshow_symbolsの2つのカスタム関数に出会いました。 最初のものは、条件に適合するシンボルの配列を形成します。 2番目のものは、EAが実行されているチャート上のシンボルのボタンを表示します。
チャート上のボタンの表示は簡単です。 まず、ボタンを表示するために使用する X 座標と Y 軸を見つけて、他のボタンと重ならないようにします。 次に、表示する必要があります。
void show_symbols(){ //X および Y 座標を定義するための変数を初期化します。 int btn_left=0; int btn_line=1; int btn_right=(int) ChartGetInteger(0, CHART_WIDTH_IN_PIXELS)-77; //配列内の各シンボルのチャート上にボタンを表示します。 //ボタンにシンボル名を書く for( int i=0; i<arrPanel1.Total(); i++ ){ if( btn_left>btn_right-BTN_WIDTH ){ btn_line++; btn_left=0; } ObjectCreate(0, exprefix+"btn"+(string) i, OBJ_BUTTON, 0, 0, 0); ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_XDISTANCE,btn_left); ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_YDISTANCE,BTN_HEIGHT*btn_line); ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_XSIZE,BTN_WIDTH); ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_YSIZE,BTN_HEIGHT); ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_FONTSIZE,8); ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_COLOR,clrBlack); ObjectSetString(0,exprefix+"btn"+(string) i,OBJPROP_TEXT,arrPanel1.At(i)); ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_SELECTABLE,false); btn_left+=BTN_WIDTH; } }
結果として、条件を満たすシンボルがチャートに表示されます。
次に、シンボルの選択に使用する条件 ( prepare_symbols関数) の形成に焦点を当てましょう。 まず、リストにすべてのシンボルを追加してみましょう。
void prepare_symbols(){ //シンボル名を一時的に格納するための変数 string name; //直近のシンボルのクオートを格納するための変数 MqlTick lastme; //値が含まれている場合は、シンボル配列をリセットします。 arrPanel1.Resize(0); //一時的な tmpSymbols 配列を形成する //使用可能なすべてのシンボルが含まれます CArrayString tmpSymbols; for( int i=0; i<SymbolsTotal(noSYMBmarketWath); i++ ){ tmpSymbols.Add(SymbolName(i, noSYMBmarketWath)); } //条件はここでチェックされます //シンボルがシンボルリストに含まれる //条件に適合する場合 for( int i=0; i<tmpSymbols.Total(); i++ ){ name=tmpSymbols[i]; //シンボル名から余分なスペースを削除 //↑(どこから来たのか確かに知らないので) StringTrimLeft(name); StringTrimRight(name); if( !StringLen(name) ){ continue; } //シンボルの主要なフィルタは上で更に行われる //... ... //シンボルがすべての条件に適合する場合は、リストに追加します。 arrPanel1.Add(name); } }
最初に、すべてのシンボルを一時配列に配置します。 最初のフィルタリングは、気配値表示パネルのインプットに存在しないシンボルを隠すことによって、この時点ですでに発生します。
一時配列にシンボルを配置する必要はありません。 代わりに、メインリストに必要なシンボルを配置することができます。 しかし、この場合、リストに表示されるシンボルのみを追加するインプットを追加する必要がある場合などに、コードを再記述する必要があります。 つまり、ブローカーによって提供されるすべてのシンボルを取得する代わりに、必要なオーダーでカスタムシンボルを使用する必要があります。
同じ理由から、シンボル名は最初に、一時的な配列からすべてのシンボルを列挙するループ内のスペースからクリアされます。 上記のインプットを実装する場合は、カスタムインプットをフィルタリングせずに実行することはできません。
次に、取得したシンボルを、インプットに基づいて並べ替えます。 以下に示すコードブロックは、 prepare_symbols関数のコメント文字列 (リストにシンボルを追加するループ内) でさらに実行されます。
ポジションを持つシンボルを非表示:
//ポジションを持つシンボルの非表示 bool isskip=false; if( noSYMBwithPOS ){ //すべてのオープンポジションの一覧を表示します int cntMyPos=PositionsTotal(); for(int ti=cntMyPos-1; ti>=0; ti--){ //現在のシンボルのポジションがある場合はスキップ if(PositionGetSymbol(ti) == name ){ isskip=true; break; } } if(!isskip){ int cntMyPosO=OrdersTotal(); if(cntMyPosO>0){ for(int ti=cntMyPosO-1; ti>=0; ti--){ ulong orderTicket=OrderGetTicket(ti); if( OrderGetString(ORDER_SYMBOL) == name ){ isskip=true; break; } } } } }
まず、シンボルにポジションがあるかどうかを確認します。 ポジションがない場合は、指値オーダーがあるかどうかを確認します。 オープンポジションまたは指値オーダーが存在する場合は、シンボルをスキップします。
スプレッドでシンボルを非表示にする:
//シンボルの現在の価格の値を使用するインプットが有効な場合、 //現在の値を取得します if(hide_PRICE_HIGH>0 || hide_PRICE_LOW>0 || hide_SPREAD>0 ){ SymbolInfoTick(name, lastme); if( lastme.bid==0 ){ Alert("Failed to get BID value. Some filtration functions may not work."); } } if(hide_SPREAD>0 && lastme.bid>0){ switch(hide_SPREAD){ //現在のスプレッドが価格の 0.05% を超えている場合は、シンボルをスキップします。 case spread_b05: if( ((SymbolInfoInteger(name, SYMBOL_SPREAD)*SymbolInfoDouble(name, SYMBOL_POINT))/lastme.bid)*100 > 0.05 ){ isskip=true; } break; //現在のスプレッドが価格の 0.1% を超えている場合は、シンボルをスキップします。 case spread_b1: if( ((SymbolInfoInteger(name, SYMBOL_SPREAD)*SymbolInfoDouble(name, SYMBOL_POINT))/lastme.bid)*100 > 0.1 ){ isskip=true; } break; //現在のスプレッドが価格の 0.15% を超えている場合は、シンボルをスキップします。 case spread_b15: if( ((SymbolInfoInteger(name, SYMBOL_SPREAD)*SymbolInfoDouble(name, SYMBOL_POINT))/lastme.bid)*100 > 0.15 ){ isskip=true; } break; //現在のスプレッドが価格の 0.15% 以下である場合は、シンボルをスキップします。 case spread_l15: if( ((SymbolInfoInteger(name, SYMBOL_SPREAD)*SymbolInfoDouble(name, SYMBOL_POINT))/lastme.bid)*100 < 0.15 ){ isskip=true; } break; //現在のスプレッドが価格の 0.1% 未満の場合は、シンボルをスキップします。 case spread_l1: if( ((SymbolInfoInteger(name, SYMBOL_SPREAD)*SymbolInfoDouble(name, SYMBOL_POINT))/lastme.bid)*100 < 0.1 ){ isskip=true; } break; } } if(isskip){ continue; }
スプレッドが小さいほど、より良いです。 この観点から、価格の 0.05% 未満であるスプレッドを有するシンボルで機能するのが最善です。 すべてのブローカーは、このような良い条件を提供していません, 特に株式相場でのトレードでは顕著です。
Hide symbols with the higher price (0 - do not hide):
//より高い価格でシンボルを非表示にする (0-非表示にしない) if(hide_PRICE_HIGH>0 && lastme.bid>0 && lastme.bid>hide_PRICE_HIGH){ continue; }
Hide symbols with the lower price (0 - do not hide):
if(hide_PRICE_LOW>0 && lastme.bid>0 && lastme.bid<hide_PRICE_LOW){ continue; }
Hide symbols unavailable for trading:
if(hideProhibites){ //シンボルの最小ポジションボリュームが0の場合はスキップ if( SymbolInfoDouble(name, SYMBOL_VOLUME_MIN)==0 ) continue; //シンボルの開始ポジションが無効になっている場合はスキップ if(SymbolInfoInteger(name, SYMBOL_TRADE_MODE)==SYMBOL_TRADE_MODE_DISABLED || SymbolInfoInteger(name, SYMBOL_TRADE_MODE)==SYMBOL_TRADE_MODE_CLOSEONLY ){ continue; } }
もちろん、インプットから任意の条件なしでトレードが無効になっているシンボルを隠すことができます。 ただし、このようなシンボルをリストにインクルードすることもできます。 このため、このインプットを追加します。
指定した時間にのみシンボルを開く:
//curDay 変数で現在の日付を取得する MqlDateTime curDay; TimeCurrent(curDay); MqlDateTime curDayFrom; datetime dfrom; datetime dto; //相場開放時間に制限がある場合 //現在のストックのオープン時間を取得することができました。 if( hide_HOURS!=hour_any && SymbolInfoSessionTrade(name, (ENUM_DAY_OF_WEEK) curDay.day_of_week, 0, dfrom, dto)){ TimeToStruct(dfrom, curDayFrom); if(hide_HOURS==hour_9am && curDayFrom.hour != 9){ continue; } if(hide_HOURS==hour_10am && curDayFrom.hour != 10){ continue; } if(hide_HOURS==hour_4pm && curDayFrom.hour != 16){ continue; } if(hide_HOURS==hour_0am && curDayFrom.hour != 0){ continue; } }
相場が閉じている場合は非表示. 日曜日にEAを起動する場合、多くの場合株式相場を分析する意図ではありません。 ほとんどの場合、TA25 インデックスや仮想通貨など、日曜日に利用可能なシンボルを選択する必要があります。 このインプットパラメータを使用すると、行うことができます。
もちろん、別のインプットを導入するのではなく、今日トレードされたシンボルを表示することだけが可能です。 しかし、日曜日であり、まだ適切な株式などを選択して、次のトレード日の準備をしたい場合はどうなるでしょうか? この関数をインプットパラメータとして実装してみましょう。
相場が今日開かれるかどうかを定義するには、 SymbolInfoSessionTrade関数が必要です。 falseが返された場合、このシンボルは現在トレードできません。 関数を2回呼び出すことを避けるためには、次のように開くシンボルのみを示すコードを再記述する必要があります。
MqlDateTime curDay; TimeCurrent(curDay); MqlDateTime curDayFrom; datetime dfrom; datetime dto; bool sessionData=SymbolInfoSessionTrade(name, (ENUM_DAY_OF_WEEK) curDay.day_of_week, 0, dfrom, dto); //相場が今日閉じている場合は、シンボルを非表示にします if( hideClosed && !sessionData ){ continue; } //[開くシンボルのみを表示] //現在の日付を curDay 変数に取得する //相場開放時間に制限がある場合、 //現在のストックのオープン時間を取得することができました。 if( hide_HOURS!=hour_any && sessionData){ TimeToStruct(dfrom, curDayFrom); if(hide_HOURS==hour_9am && curDayFrom.hour != 9){ continue; } if(hide_HOURS==hour_10am && curDayFrom.hour != 10){ continue; } if(hide_HOURS==hour_4pm && curDayFrom.hour != 16){ continue; } if(hide_HOURS==hour_0am && curDayFrom.hour != 0){ continue; } }
ATR がドルで設定された値より小さい場合は非表示. 日中トレードし、価格が少なくとも50-90 セントに移動するのを待つ場合は、統計的に1日あたり30セント以下に移動するシンボルを必要とする可能性は低いです。 このパラメータを使用すると、価格の日の移動に必要な最小サイズを指定して、このようなシンボルを並べ替えることができます。
//ATR がドルで設定された値より小さい場合は非表示にします if(hideATRcents>0){ MqlRates rates[]; ArraySetAsSeries(rates, true); double atr; if(CopyRates(name, PERIOD_D1, 1, 5, rates)==5){ atr=0; for(int j=0; j<5; j++){ atr+=rates[j].high-rates[j].low; } atr/=5; if( atr>0 && atr<hideATRcents ){ continue; } } }
ほとんどのパラメータによって完全なフィルタリングに十分であると思います。 しかし、他のフィルタ条件が必要な場合は、 prepare_symbols関数ループ内にいつでも追加できます。
チャートを開く
すでに条件をフィッティングシンボルボタンを描画する方法を学んできました。 この時点でストップし、取得したシンボルのチャートを手動で開くことができます。 しかし、これでは不便です。 幸いにも, プロセスを簡素化し、ボタンをクリックすると、必要なチャートを開くことができます。
ボタンをクリックしたときにアクションが発生するようにするには、そのアクションをOnChartEvent MQL 言語標準関数で記述する必要があります。 この関数は、任意のチャートイベントをインターセプトします。 つまり、チャート上で起こっているあらゆるイベントで呼び出されます。
OnChartEvent関数には4つのインプットがあります。 最初のパラメータ (id) には、 OnChartEvent関数によって現在インターセプトされているイベントの id があります。 チャートボタンをクリックした後にOnChartEvent関数が正確に呼び出されたことを理解するには、パラメータ値を必要なものと比較します。
ボタンのクリックイベントには、 CHARTEVENT_OBJECT_CLICK ID があります。 したがって、チャート上のボタンクリックを処理するために、 OnChartEvent関数に次のコードを追加してみましょう。
void OnChartEvent(const int id, //イベント ID const long& lparam, //long 型のイベントパラメータ const double& dparam, //double 型のイベントパラメータ const string& sparam) //文字列型のイベントパラメータ { switch(id){ case CHARTEVENT_OBJECT_CLICK: //ボタンをクリックしたときに実行されるコード break; } }
チャートで押されたボタンを正確に理解するにはどうすればよいでしょうか。 OnChartEvent関数 (sparam) の2番目のパラメータが役に立ちます。 CHARTEVENT_OBJECT_CLICKイベントには、ユーザーがクリックしたボタン名があります。 この名前をEAによって生成されたものと比較するだけです。 このEAボタンの場合は、必要なシンボルチャートを開きます。 開いているシンボルの名前は、ボタンに書かれたテキストから取得します。 その結果、case CHARTEVENT_OBJECT_CLICK条件の内部に配置する必要がある次のコードがあります。
//ボタン名にすべてのグラフィカルオブジェクトに存在する行が含まれている場合 //EAによって作成され、その後... if( StringFind(sparam, exprefix+"btn")>=0 ){ //現在のボタンのインデックスを配置する //(シンボルリスト内の現在のシンボルのポジション) を panel1val 変数に string tmpme=sparam; StringReplace(tmpme, exprefix+"btn", ""); panel1val=(int) tmpme; //現在のシンボルチャートを開く showcharts(ObjectGetString(0,sparam,OBJPROP_TEXT)); }
選択したシンボルのチャートは、 showchartsカスタム関数を使用して開きます。 この関数は、必要なチャートだけでなく、さらに次のものも開きます。
- EAによって以前に開かれたチャートを閉じます。
- マーケットウォッチパネルにシンボルがない場合は、追加します。
- 必要に応じて、チャートをローソク足モードに切り替えます。
- チャートのスケールを変更します (この関数は、デフォルト値ではなくカスタムスケールを使用するだけなので、追加しました)。
void showcharts(string name){ //チャートが既に開いている場合は、閉じます。 closecharts(); //「マーケットウォッチ」パネルにシンボルがない場合は、を追加します。 //「チャートを気配値に追加」のインプットが「真」の場合 if( addInfoWatch ){ SymbolSelect(name, true); } //チャートを開き、curChartID 配列にチャート ID を配置します。 curChartID[ArrayResize(curChartID,ArraySize(curChartID)+1)-1]=charts[(uchar) ArrayResize(charts,ArraySize(charts)+1)-1].Open( name, PERIOD_D1 ); //「オープンローソク足チャート」のインプットが「真」の場合、 //チャートをローソク足モードに切り替える if(viewCandle){ ChartSetInteger( curChartID[ArraySize(curChartID)-1], CHART_MODE, CHART_CANDLES); } //「ティック・ボリュームを表示」のインプットが「真」の場合は、 //ティックボリュームの表示 if(viewVolumes){ ChartSetInteger( curChartID[ArraySize(curChartID)-1], CHART_SHOW_VOLUMES, CHART_VOLUME_TICK); } //チャートの縮尺を最も便利なものに変更する ChartSetInteger( curChartID[ArraySize(curChartID)-1], CHART_SCALE, 2); //すべての変更が実装されるまで、3分の1秒待ちます Sleep(333); //開いているチャートを更新してすべての変更を行う ChartRedraw(curChartID[ArraySize(curChartID)-1]); }closecharts関数は、EAによって以前に開かれたすべてのチャートを閉じます。 そのコードは簡単です。
void closecharts(){ //EAによって開かれたチャートの配列が空でない場合は、... if(ArraySize(charts)){ //すべてのチャートを一貫して閉じる for( int i=0; i<ArraySize(charts); i++ ){ charts[i].Close(); } //チャートの配列をクリアする ArrayFree(charts); } //EAによって開かれたチャートの id の配列が空でない場合は、クリアします if(ArraySize(curChartID)){ ArrayFree(curChartID); } }
追加のシンボル情報の表示
シンボルチャートを開くだけでなく、その上に補助的なデータを表示するだけでなく、(ブローカーは、一種の暗号であると思われるシンボル名を作る) と直近の日と時間のシンボル移動方向を示しても良いでしょう。
この情報は、グラフィカルオブジェクトを使用して表示できます。 ただし、よりシンプルなアプローチを使用します。 チャートのコメントに情報が表示されます。
これを行うには、次のコードを、 showcharts関数を呼び出す前に追加します。 Sleep関数。
//チャートに追加情報を表示する string msg=""; if(showNameSymbol){ StringAdd(msg, getmename_symbol(name)+"\r\n"); } if(showInfoSymbol){ StringAdd(msg, getmeinfo_symbol(name, false)+"\r\n"); } if( StringLen(msg)>0 ){ ChartSetString(curChartID[ArraySize(curChartID)-1], CHART_COMMENT, msg); }
showNameSymbolのインプットがtrueの場合は、シンボル名を持つ行を返すgetmename_symbol関数を呼び出します。 showInfoSymbolのインプットがtrueの場合は、シンボル名を持つ行を返すgetmeinfo_symbol関数を呼び出します。
string getmename_symbol(string symname){ return SymbolInfoString(symname, SYMBOL_DESCRIPTION); } string getmeinfo_symbol(string symname, bool show=true){ MqlRates rates2[]; ArraySetAsSeries(rates2, true); string msg=""; if(CopyRates(symname, PERIOD_D1, 0, 1, rates2)>0){ if(show){ StringAdd(msg, (string) symname+": "); } StringAdd(msg, "D1 "); if( rates2[0].close > rates2[0].open ){ StringAdd(msg, "+"+DoubleToString(((rates2[0].close-rates2[0].open)/rates2[0].close)*100, 2) +"%"); }else{ if( rates2[0].close < rates2[0].open ){ StringAdd(msg, "-"+DoubleToString(((rates2[0].open-rates2[0].close)/rates2[0].close)*100, 2) +"%"); }else{ StringAdd(msg, "0%"); } } } if(CopyRates(symname, PERIOD_H1, 0, 1, rates2)>0){ StringAdd(msg, ", H1 "); if( rates2[0].close > rates2[0].open ){ StringAdd(msg, "+"+DoubleToString(((rates2[0].close-rates2[0].open)/rates2[0].close)*100, 2)+"% (+"+DoubleToString(rates2[0].close-rates2[0].open, (int) SymbolInfoInteger(symname, SYMBOL_DIGITS))+" "+SymbolInfoString(symname, SYMBOL_CURRENCY_PROFIT)+")"); }else{ if( rates2[0].close < rates2[0].open ){ StringAdd(msg, "-"+DoubleToString(((rates2[0].open-rates2[0].close)/rates2[0].close)*100, 2)+"% (-"+DoubleToString(rates2[0].open-rates2[0].close, (int) SymbolInfoInteger(symname, SYMBOL_DIGITS))+" "+SymbolInfoString(symname, SYMBOL_CURRENCY_PROFIT)+")"); }else{ StringAdd(msg, "0%"); } } } return msg; }
結果として、新たに開かれたチャートに次の情報が表示されるようになります。
キーボードからEAを管理する
まだOnChartEvent関数では、キーを押して応答を追加してみましょう。
- R を押すと、条件に適合するシンボルのリストを更新します:
- X を押すと、チャートからEAを削除します。
CHARTEVENT_KEYDOWN ID を持つイベントは、キーの押下を処理します。 押されたキーのコードは、既に説明したsparamパラメータで渡します。 したがって、次の条件をswitch演算子に追加するだけです。
case CHARTEVENT_KEYDOWN: switch((int) sparam){ case 45: //X ExpertRemove(); break; case 19: //R start_symbols(); break; } break;
ご覧のように、R を押すと、先ほど作成したstart_symbols関数を呼び出すだけです。
チャートナビゲーションの追加
シンボルボタンを表示する方法だけでなく、ボタンをクリックしたときに必要なシンボルのチャートを開く方法についても学習しました。 しかし、まだここでは行われていません。 このユーティリティはまだ使用に不便です。 シンボルチャートを開いた後、手動で閉じて、次のチャートボタンをクリックする必要があります。 タスクを退屈にする次のシンボルに移動する必要があるたびに行う必要があります。 チャートを開くためのシンボルリストナビゲーションボタンを追加してみましょう。
次のチャートに移動し、前のチャートに移動してチャートを閉じるために、3つのボタンのみが追加されます。
これらを実装する方法を決定するのが残っています。 新しいチャートを作成するときに、 showcharts関数に直接ボタンを追加することができます。 しかし、ボタンの数は、将来的に増加する可能性があります。 ボタンやその他のグラフィカルオブジェクトを作成すると、望ましくないチャートを開くことが遅くなることがあります。
したがって、標準のOnTimer関数でボタンを作成します。 EAのチャートが開いているかどうか、チャートが開いているかどうか、ボタンがあるかどうかを定期的に確認します。 ボタンがない場合は、次のように作成します。
void OnTimer() { //開いているチャートの ID 配列に値が含まれている場合は、... uchar tmpCIDcnt=(uchar) ArraySize(curChartID); if(tmpCIDcnt>0 ){ //配列内の直近の ID が破損していない場合は、... if(curChartID[tmpCIDcnt-1]>0){ //ID を持つチャートにボタンがない場合は、作成します。 if(ObjectFind(curChartID[tmpCIDcnt-1], exprefix+"_p_btn_next")<0){ createBTNS(curChartID[tmpCIDcnt-1]); } } } }
チャートのボタンは、 createBTNSカスタム関数で作成されます。 そのコードは簡単です。
void createBTNS(long CID){ ObjectCreate(CID, exprefix+"_p_btn_prev", OBJ_BUTTON, 0, 0, 0); ObjectSetInteger(CID,exprefix+"_p_btn_prev",OBJPROP_XDISTANCE,110); ObjectSetInteger(CID,exprefix+"_p_btn_prev",OBJPROP_YDISTANCE,90); ObjectSetInteger(CID,exprefix+"_p_btn_prev",OBJPROP_XSIZE,BTN_WIDTH); ObjectSetInteger(CID,exprefix+"_p_btn_prev",OBJPROP_YSIZE,BTN_HEIGHT); ObjectSetInteger(CID,exprefix+"_p_btn_prev",OBJPROP_CORNER,CORNER_LEFT_LOWER); ObjectSetString(CID,exprefix+"_p_btn_prev",OBJPROP_TEXT,"Prev chart"); ObjectSetInteger(CID,exprefix+"_p_btn_prev",OBJPROP_SELECTABLE,false); ObjectCreate(CID, exprefix+"_p_btn_next", OBJ_BUTTON, 0, 0, 0); ObjectSetInteger(CID,exprefix+"_p_btn_next",OBJPROP_XDISTANCE,110); ObjectSetInteger(CID,exprefix+"_p_btn_next",OBJPROP_YDISTANCE,65); ObjectSetInteger(CID,exprefix+"_p_btn_next",OBJPROP_XSIZE,BTN_WIDTH); ObjectSetInteger(CID,exprefix+"_p_btn_next",OBJPROP_YSIZE,BTN_HEIGHT); ObjectSetInteger(CID,exprefix+"_p_btn_next",OBJPROP_CORNER,CORNER_LEFT_LOWER); ObjectSetString(CID,exprefix+"_p_btn_next",OBJPROP_TEXT,"Next chart"); ObjectSetInteger(CID,exprefix+"_p_btn_next",OBJPROP_SELECTABLE,false); ObjectCreate(CID, exprefix+"_p_btn_close", OBJ_BUTTON, 0, 0, 0); ObjectSetInteger(CID,exprefix+"_p_btn_close",OBJPROP_XDISTANCE,110); ObjectSetInteger(CID,exprefix+"_p_btn_close",OBJPROP_YDISTANCE,40); ObjectSetInteger(CID,exprefix+"_p_btn_close",OBJPROP_XSIZE,BTN_WIDTH); ObjectSetInteger(CID,exprefix+"_p_btn_close",OBJPROP_YSIZE,BTN_HEIGHT); ObjectSetInteger(CID,exprefix+"_p_btn_close",OBJPROP_CORNER,CORNER_LEFT_LOWER); ObjectSetString(CID,exprefix+"_p_btn_close",OBJPROP_TEXT,"Close chart"); ObjectSetInteger(CID,exprefix+"_p_btn_close",OBJPROP_SELECTABLE,false); //実装された変更を確認するためにチャートを更新する ChartRedraw(CID); }
結果として、新しいチャートは次の形式になります。
ボタン押下への応答の追加
これまでのところ、チャートに追加されたボタンは単なる装飾です。 押しても何も起こりません。 プレスに対応する方法について説明しましょう。
残念ながら、標準のOnChartEvent関数は、EAが起動されたチャートで発生したイベントだけに反応し、ボタンは新しいチャートに追加されるため、ここでは役に立ちません。
おそらく、これより便利な方法があります。 別のチャートで発生した変更に対応する方法は1つだけでしました。 OnTimer標準関数が含まれます。 チャートがボタンを備えている場合、いくつかが押されているかどうかを確認します。 yes の場合、必要なアクションが実行されます。 その結果、条件:
if(ObjectFind(curChartID[tmpCIDcnt-1], exprefix+"_p_btn_next")<0){ createBTNS(curChartID[tmpCIDcnt-1]); }
次のように書き換えられます。
if(ObjectFind(curChartID[tmpCIDcnt-1], exprefix+"_p_btn_next")<0){ createBTNS(curChartID[tmpCIDcnt-1]); }else{ if(ObjectGetInteger(curChartID[tmpCIDcnt-1],exprefix+"_p_btn_prev",OBJPROP_STATE)==true ){ prevchart(); return; } if(ObjectGetInteger(curChartID[tmpCIDcnt-1],exprefix+"_p_btn_next",OBJPROP_STATE)==true ){ nextchart(); return; } if(ObjectGetInteger(curChartID[tmpCIDcnt-1],exprefix+"_p_btn_close",OBJPROP_STATE)==true ){ closecharts(); return; } }
前のチャートボタンを押すと、 prevchart関数を呼び出します。 次のチャートボタンを押すと、 nextchart関数を呼び出します。 Close chartボタンを押したときに、上記のclosecharts関数を呼び出します。 prevchartとnextchart関数は似ています。
void nextchart(){ //シンボルリストが次のシンボルを備えている場合は、そのチャートを開きます。 //それ以外の場合は、現在のチャートを閉じる if(arrPanel1.Total()>(panel1val+1)){ panel1val++; showcharts(arrPanel1[panel1val]); }else{ closecharts(); } } void prevchart(){ //シンボルリストが前のシンボルを備えている場合は、そのチャートを開きます。 //それ以外の場合は、現在のチャートを閉じる if(arrPanel1.Total()>(panel1val-1) && (panel1val-1)>=0){ panel1val--; showcharts(arrPanel1[panel1val]); }else{ closecharts(); } }
結論
結論を出しましょう。 ご覧のように、全体的なコードの量は圧倒的ではありませんが、利点は明白です。 チャートを開いて、繰り返し閉じる必要がなくなりました。 代わりに、必要なボタンをクリックすることができ、すべてが行われます。
もちろん、EAを改善する方法は他にもあるかもしれません。 しかし、現在の形でも、株式の選択を大幅に簡素化する本格的なプロダクトです。
MQL4 へのユーティリティの移動
次に、ユーティリティを MQL4 に移動してみましょう。 驚いたことに、1つのコードブロックだけを書き直す必要があります。 これには約5分かかります。
まず、メタエディタ4で新しいEAを作成します。 その後、MQL5EAのソースコードを移動します。
EAをコンパイルします。 もちろん、試行はエラーで終了します。 しかし、結果として、修正するエラーのリストが得られます。 そのうちの3つだけがあります。
- ' PositionsTotal '-関数が定義されていません
- ' PositionGetSymbol '-関数が定義されていません
- ' OrderGetTicket '-関数が定義されていません
最初のエラーをダブルクリックして、適切なEA文字列に移動します。
' PositionsTotal '-関数が定義されていません。 このエラーは、 prepare_symbols関数コードの次のブロックで検出されます。
int cntMyPos=PositionsTotal(); for(int ti=cntMyPos-1; ti>=0; ti--){ //現在のシンボルにポジションがある場合はスキップ if(PositionGetSymbol(ti) == name ){ isskip=true; break; } } if(!isskip){ int cntMyPosO=OrdersTotal(); if(cntMyPosO>0){ for(int ti=cntMyPosO-1; ti>=0; ti--){ ulong orderTicket=OrderGetTicket(ti); if( OrderGetString(ORDER_SYMBOL) == name ){ isskip=true; break; } } } }
MQL4 と MQL5 言語の間の重要な差の1つは、ポジションとオーダーを処理することです。 したがって、EAが MetaTrader4 で正しく動作するように、コードブロックを次のように書き換える必要があります。
int cntMyPos=OrdersTotal(); for(int ti=cntMyPos-1; ti>=0; ti--){ if(OrderSelect(ti,SELECT_BY_POS,MODE_TRADES)==false) continue; if(OrderSymbol() == name ){ isskip=true; break; } }
MQL4 はポジションとオーダーを区別しないため、結果のコードははるかに小さくなります。
残りのエラーは、修正したコードブロックで発生したため、自動的に修正されます。
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/5348





- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索