オンチャートUIを使用したリスクベースの取引執行EA(第2回):インタラクティブ性とロジックの追加
はじめに
連載第1回では、リスクベースの取引ツールのための静的なオンチャートコントロールパネルの設計に焦点を当てました。目的は、注文タイプや建値などの取引パラメータをチャート上から直接入力できる、整理された分かりやすいユーザーインターフェースを作成することでした。その段階では、インターフェースは視覚的な要素のみで構成されており、ユーザーの操作に応答することはできませんでした。
この第2回では、このインターフェースをインタラクティブで実用的なものにしていきます。主な目的は、グラフィックコンポーネントを実際のロジックと結び付けることです。これにより、ユーザーが[Calculate Lot]ボタンをクリックすると、プログラムが入力フィールドの値を取得し、設定されたリスクに基づいて適切なロットサイズを計算し、その結果を即座にチャート上に表示できるようになります。同様に、[Send Order]ボタンがクリックされると、計算されたロットサイズおよびユーザー定義のパラメータを用いて、EAが取引を実行します。
この記事を読み終える頃には、ユーザー操作に反応し、リアルタイムで計算を行う完全に機能するオンチャート型のロットサイズ計算機が完成しているはずです。この実践的なステップにより、静的なパネルがインテリジェントな取引アシスタントへと進化し、取引計画時の精度とコントロール性が向上します。
インタラクティブ機能への準備
連載第1回では、コントロールパネルは静的なものでした。ボタンや入力フィールドを表示することはできましたが、ユーザーがそれらをクリックしても反応することはできませんでした。本パートでは、ボタンクリックなどのユーザー操作を処理できるようにし、パネルをインタラクティブ化します。
MQL5では、OnChartEventと呼ばれる特別なイベント処理関数によってインタラクティブ性が実現されます。この関数は、チャートオブジェクト上でのマウスクリックやキー入力、あるいはプログラムによって生成されたカスタムイベントなど、特定のチャートイベントが発生するたびにターミナルによって自動的に呼び出されます。
以下は、ソースコード内ですでに定義されているこの関数の構造です。
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int32_t id, const long &lparam, const double &dparam, const string &sparam) { }
この関数には4つのパラメータがあります。
- id:発生したイベントの種類を識別します。たとえば、CHARTEVENT_OBJECT_CLICKはチャートオブジェクトがクリックされたことを表します。
- lparam:イベントの種類に応じた追加データを保持します。たとえば、マウスのX座標やキーコードなどです。
- dparam:通常はイベントに関連する数値データを保持します。たとえば、マウスのY座標などです。
- sparam:文字列パラメータで、イベントに関与したオブジェクト名が含まれることが多いです。どのボタンやラベルがクリックされたかを特定するために使用します。
それでは、この関数内でクリックイベントを検出してみましょう。今回は、グラフィックコンポーネント上でのユーザークリックのみを対象とするため、CHARTEVENT_OBJECT_CLICK イベントに注目します。関数本体に次の条件文を追加します。
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int32_t id, const long &lparam, const double &dparam, const string &sparam) { //--- Handle click events if(id == CHARTEVENT_OBJECT_CLICK){ } }
これにより、ユーザーがチャート上のオブジェクトをクリックした場合にのみ、プログラムが反応するようになります。ここからは、sparamパラメータを使用して、どのオブジェクトがクリックされたのかを判別し、それに応じた処理を実装していきます。
ユーザー操作の取得と処理
最初に処理したい操作は、コントロールパネル右上のXアイコンがクリックされたときにGUIを閉じることです。

これを実現するために、DESTROY_GUIという新しい関数を定義します。この関数は、ユーザーインターフェースを構成するすべてのオブジェクトを削除し、チャートを更新します。
関数の定義は次のとおりです。
//+------------------------------------------------------------------+ //| Function to destroy the main GUI | //+------------------------------------------------------------------+ void DESTROY_GUI(){ ObjectsDeleteAll(0, SmartRiskTrader); ObjectDelete(0, BTN_CLOSE_GUI); ObjectDelete(0, BTN_ORDER_TYPES); ObjectDelete(0, FIELD_ENTRY_PRICE); ObjectDelete(0, FIELD_STOP_LOSS); ObjectDelete(0, FIELD_TAKE_PROFIT); ObjectDelete(0, RISK); ObjectDelete(0, BTN_CALC_LOT); ObjectDelete(0, BTN_SEND_ORDER); ObjectDelete(0, RESULTS_TEXT); ObjectsDeleteAll(0, "ORDER_TYPE_GROUP"); ChartRedraw(); }
この関数は、GUIが閉じられた際にすべてのグラフィックコンポーネントをチャート上から削除し、クリーンな状態に戻すことを保証します。ChartRedraw関数の呼び出しにより、変更内容が即座にチャートへ反映されます。
次に、ユーザーが実際にXアイコンをクリックしたことを検出する必要があります。これはOnChartEvent関数内でおこないます。イベントがオブジェクトクリックであり、クリックされたオブジェクト名がBTN_CLOSE_GUIと一致するかを確認します。一致した場合、DESTROY_GUI関数を呼び出してインターフェースを削除します。
以下のように実装します。
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int32_t id, const long &lparam, const double &dparam, const string &sparam) { //--- Handle click events if(id == CHARTEVENT_OBJECT_CLICK){ //--- To close GUI if(sparam == BTN_CLOSE_GUI){ DESTROY_GUI(); } } }
コンパイルして実行すると、XアイコンをクリックするだけでGUI全体が即座にチャート上から削除されます。標準的なアプリケーションのように、クリーンで応答性の高いUIになります。
コントロールパネルを閉じた後に再度開けなければ、実用的とは言えません。そのため、チャート左下に小さな[Open GUI]ボタンを表示します。このボタンをクリックするとメインパネルが再表示されます。

まず、後で参照できるように、ボタンを識別するためのマクロを定義します。
//+------------------------------------------------------------------+ //| Macros | //+------------------------------------------------------------------+ ... #define BTN_GUI_OPEN "BTN_GUI_OPEN"
CREATE_GUI_OPEN_BUTTON関数を定義します。この関数は、チャート上に[Open GUI]ボタンを描画し、チャートの左下隅にきれいに配置します。
//+------------------------------------------------------------------+ //| Function to create the GUI open button | //+------------------------------------------------------------------+ void CREATE_GUI_OPEN_BUTTON(){ CREATE_OBJ_BUTTON(BTN_GUI_OPEN, 20, 60, 120, 40, "Open GUI", clrWhite, 12, 2, clrDarkGreen, clrBlack); ObjectSetInteger(0, BTN_GUI_OPEN, OBJPROP_CORNER, CORNER_LEFT_LOWER); ChartRedraw(); }
ObjectSetInteger関数の呼び出しにより、ボタンの位置がチャート左下に固定されます。また、ChartRedraw関数によって、ボタンが即座に表示され、遅延なく画面上に反映されます。
このボタンは、ユーザーがXアイコンをクリックしてGUIを閉じた直後に表示する必要があります。そのため、イベント処理のロジック内で、Xアイコンがクリックされた際にCREATE_GUI_OPEN_BUTTON関数を呼び出します。
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int32_t id, const long &lparam, const double &dparam, const string &sparam) { //--- Handle click events if(id == CHARTEVENT_OBJECT_CLICK){ //--- To close GUI if(sparam == BTN_CLOSE_GUI){ ... CREATE_GUI_OPEN_BUTTON(); } } }
作成したボタンに機能を持たせる必要があります。ボタンがクリックされたとき、メインコントロールパネルを再表示する必要があります。これを実現するために、イベント処理関数に小さなロジックを追加します。
アイデアはシンプルです。[Open GUI]ボタンがクリックされたら、まずチャートからボタン自体を削除して重複を防ぎます。その後、既存のCREATE_GUI関数を呼び出して、メインGUIを再生成します。更新後のイベント処理関数は次のとおりです。
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int32_t id, const long &lparam, const double &dparam, const string &sparam) { //--- Handle click events if(id == CHARTEVENT_OBJECT_CLICK){ ... //--- To open GUI if(sparam == BTN_GUI_OPEN){ DESTROY_GUI_OPEN_BUTTON(); CREATE_GUI(); } } }
ここで、DESTROY_GUI_OPEN_BUTTON関数は、メインパネルが再び作成される前に[Open GUI]ボタンを削除する役割を持っています。
//+------------------------------------------------------------------+ //| Function to destroy the GUI open button | //+------------------------------------------------------------------+ void DESTROY_GUI_OPEN_BUTTON (){ ObjectDelete(0, BTN_GUI_OPEN); ChartRedraw(); }
これにより、チャート上のオブジェクトが重複することなく、常にクリーンな状態を保つことができます。
このコードをコンパイルしてテストすると、[Open GUI]ボタンをクリックするだけで、メインのコントロールパネルが元通りに表示されることが確認できます。このシンプルな機能により、インターフェースはよりインタラクティブで使いやすくなります。
次に、注文タイプのドロップダウンを自動化します。このドロップダウンは、トレーダーがボタンをクリックしたときに表示され、再度クリックすると非表示になるように機能させます。この機能を実現するため、まずドロップダウンリストが不要になったときに削除する関数を定義します。
//+------------------------------------------------------------------+ //| Function to destroy the order type dropdown | //+------------------------------------------------------------------+ void DESTROY_ORDER_TYPE_DROPDOWN(){ ObjectsDeleteAll(0, "ORDER_TYPE_GROUP"); ChartRedraw(); }
この関数はORDER_TYPE_GROUPというグループに属するすべてのチャートオブジェクトを削除します。ドロップダウンを構築する際に作成されたすべてのオブジェクトは、このグループに割り当てられています。削除後にChartRedraw関数を呼び出すことで、変更内容が即座にチャート上に反映されます。
次に、ドロップダウンを表示するタイミングと非表示にするタイミングを制御する必要があります。

これを実現するには、OnChartEvent関数を使用してチャート上でのユーザー操作を監視します。この関数を修正して、[Order Types]ボタンがクリックされたときに、ドロップダウンを表示または非表示に切り替えるロジックを追加します。
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int32_t id, const long &lparam, const double &dparam, const string &sparam) { //--- Handle click events if(id == CHARTEVENT_OBJECT_CLICK){ ... //--- To handle order types dropdown if(sparam == BTN_ORDER_TYPES){ bool state = ObjectGetInteger(0, BTN_ORDER_TYPES, OBJPROP_STATE); if(state == true) { CREATE_ORDER_TYPE_DROPDOWN(); } if(state == false){ DESTROY_ORDER_TYPE_DROPDOWN(); } } } }
ここで、OBJPROP_STATEプロパティを使用してボタンが押されているか(true)いないか(false)を判定します。ボタンが押されていればCREATE_ORDER_TYPE_DROPDOWN関数を呼び出してドロップダウンを表示し、押されていなければDESTROY_ORDER_TYPE_DROPDOWN関数を呼び出して非表示にします。この単純な仕組みにより、トグル機能が実現され、トレーダーはボタンをクリックすることでオーダータイプのリストを簡単に表示と非表示を切り替えることができます。
連載第1回では、建値、ストップロス、テイクプロフィットの各フィールドにハードコーディングされた値を表示する静的GUIを作成しました。設計段階では有用でしたが、実際の取引環境では理想的ではありません。リアルタイム取引では価格が常に変動しており、これらのフィールドは現在の市場状況を反映した値を表示する必要があります。
これを実現するために、まず2つのヘルパー関数を作成します。1つは現在の銘柄のAsk価格を取得するための関数で、もう1つはBid価格を取得するための関数です。これらの関数を使うことで、コード内のどこからでもライブ価格を簡単に参照でき、同じロジックを繰り返す必要がなくなります。
//+------------------------------------------------------------------+ //| Function to get Ask Price | //+------------------------------------------------------------------+ double Ask(){ double askPrc = SymbolInfoDouble(_Symbol, SYMBOL_ASK); return NormalizeDouble(askPrc, _Digits); } //+------------------------------------------------------------------+ //| Function to get Bid Price | //+------------------------------------------------------------------+ double Bid(){ double bidPrc = SymbolInfoDouble(_Symbol, SYMBOL_BID); return NormalizeDouble(bidPrc, _Digits); }
両方の関数は、組み込み関数SymbolInfoDoubleを使用して、現在のチャートの銘柄の価格データを取得します。SYMBOL_ASKとSYMBOL_BIDの定数は、それぞれ取得する価格の種類(Ask価格またはBid価格)を指定します。その後、NormalizeDouble関数を使用して、銘柄の小数点桁数(事前定義変数 _Digitsで表される)に合わせて値を丸めます。 この2つの関数を使うことで、コード内のどこからでもAskやBidの価格を簡単に参照でき、常に正確で最新の価格を取得できます。
次に、CREATE_GUI関数を修正して、建値、ストップロス、テイクプロフィットの各フィールドが固定のテキスト値ではなく、現在のAsk価格に基づいた値を表示するようにします。元々は、これらのフィールドはハードコーディングされた数値で作成されていました。
//+------------------------------------------------------------------+ //| Function to render the main GUI | //+------------------------------------------------------------------+ void CREATE_GUI(){ ... //--- Entry Price CREATE_OBJ_LABEL(GenerateUniqueName(SmartRiskTrader), 40, 125, "Entry Price: ", C'20, 20, 20'); CREATE_OBJ_EDIT(FIELD_ENTRY_PRICE, 140, 125, 100, 25, "1.14030", C'20, 20, 20', 12, clrWhiteSmoke, clrDarkBlue); //--- Stop Loss CREATE_OBJ_LABEL(GenerateUniqueName(SmartRiskTrader), 40, 160, "Stop Loss: ", C'20, 20, 20'); CREATE_OBJ_EDIT(FIELD_STOP_LOSS, 140, 160, 100, 25, "1.13302", C'20, 20, 20', 12, clrWhiteSmoke, clrDarkBlue); //--- Take Profit CREATE_OBJ_LABEL(GenerateUniqueName(SmartRiskTrader), 40, 195, "Take Profit: ", C'20, 20, 20'); CREATE_OBJ_EDIT(FIELD_TAKE_PROFIT, 140, 195, 100, 25, "1.16302", C'20, 20, 20', 12, clrWhiteSmoke, clrDarkBlue); ... }
見た目上は問題ありませんでしたが、表示される値は実際に見ている金融商品の市場価格を反映していませんでした。インターフェースをより実用的にするために、これらの固定値をライブの価格データを使った式に置き換えます。以下がその更新後の該当部分です。
//+------------------------------------------------------------------+ //| Function to render the main GUI | //+------------------------------------------------------------------+ void CREATE_GUI(){ ... //--- Entry Price CREATE_OBJ_LABEL(GenerateUniqueName(SmartRiskTrader), 40, 125, "Entry Price: ", C'20, 20, 20'); CREATE_OBJ_EDIT(FIELD_ENTRY_PRICE, 140, 125, 100, 25, DoubleToString(Ask(), Digits()), C'20, 20, 20', 12, clrWhiteSmoke, clrDarkBlue); //--- Stop Loss CREATE_OBJ_LABEL(GenerateUniqueName(SmartRiskTrader), 40, 160, "Stop Loss: ", C'20, 20, 20'); CREATE_OBJ_EDIT(FIELD_STOP_LOSS, 140, 160, 100, 25, DoubleToString(Ask() - (200 * Point()), Digits()), C'20, 20, 20', 12, clrWhiteSmoke, clrDarkBlue); //--- Take Profit CREATE_OBJ_LABEL(GenerateUniqueName(SmartRiskTrader), 40, 195, "Take Profit: ", C'20, 20, 20'); CREATE_OBJ_EDIT(FIELD_TAKE_PROFIT, 140, 195, 100, 25, DoubleToString(Ask() + (400 * Point()), Digits()), C'20, 20, 20', 12, clrWhiteSmoke, clrDarkBlue); ... }
これを詳しく見てみましょう:
- 建値フィールドは、DoubleToString(Ask(), Digits())を使って現在のAsk価格を直接表示します。これにより、金融商品の価格スケールに合わせた正確で見やすい価格文字列が得られます。
- ストップロスフィールドはAsk価格から200ポイント引いた値で計算され、テイクプロフィットフィールドはAsk価格に400ポイント加えた値に設定されます。これにより、トレーダーは手動で値を入力することなく、論理的な取引レベルの目安を得ることができます。
- Point関数は、オフセット値が通貨ペア、株式CFD、商品など、銘柄ごとの価格形式に応じて自動的に調整されるようにします。
このようにすることで、GUIは取引対象の金融商品に自動的に適応します。インターフェースが表示された瞬間から、トレーダーは現実的なエントリーおよびエグジットレベルを確認でき、計算機としてより実用的で直感的に使用可能になります。
これまでに、ユーザーが[Order Type]ボタンをクリックすると表示され、再度クリックすると非表示になるドロップダウンメニューを構築しました。しかし、このドロップダウン自体はまだ何も機能しておらず、注文タイプを選択してもインターフェースは更新されません。このセクションでは、そのインタラクションを実装します。トレーダーが特定の注文タイプ(たとえば[Market Buy])をクリックしたときに、メインボタンが選択した注文タイプを表示するように更新され、ドロップダウンが自動的に閉じ、建値、ストップロス、テイクプロフィットのフィールドが適切な初期値で埋められるようにします。
以下は、ユーザーが[Market Buy]を選択したときにこれを実現するコードです。
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int32_t id, const long &lparam, const double &dparam, const string &sparam) { //--- Handle click events if(id == CHARTEVENT_OBJECT_CLICK){ ... //--- To select the market buy order if(sparam == "ORDER_TYPE_GROUP_MARKET_BUY"){ bool state = ObjectGetInteger(0, BTN_ORDER_TYPES, OBJPROP_STATE); string text = ObjectGetString (0, "ORDER_TYPE_GROUP_MARKET_BUY", OBJPROP_TEXT); ObjectSetString(0, BTN_ORDER_TYPES, OBJPROP_TEXT, text); DESTROY_ORDER_TYPE_DROPDOWN(); if(state == true){ ObjectSetInteger(0, BTN_ORDER_TYPES, OBJPROP_STATE, false); } //--- Set reasonable values to start with ObjectSetString(0, FIELD_ENTRY_PRICE, OBJPROP_TEXT, DoubleToString(Ask(), Digits())); ObjectSetString(0, FIELD_STOP_LOSS, OBJPROP_TEXT, DoubleToString(Ask() - (200 * Point()), Digits())); ObjectSetString(0, FIELD_TAKE_PROFIT, OBJPROP_TEXT, DoubleToString(Ask() + (400 * Point()), Digits())); ChartRedraw(); } } }
ここからは順を追って見ていきます。
bool state = ObjectGetInteger(0, BTN_ORDER_TYPES, OBJPROP_STATE); string text = ObjectGetString (0, "ORDER_TYPE_GROUP_MARKET_BUY", OBJPROP_TEXT);
ここでは、2つの処理をおこなっています。
- 注文タイプボタンの現在の状態(押されているかどうか)を確認する。
- クリックされたドロップダウン項目のテキストを取得する(この場合は「Market Buy」)。
ObjectSetString(0, BTN_ORDER_TYPES, OBJPROP_TEXT, text);
この行は、メインの注文タイプボタンのラベルを更新し、[Market Buy]と表示されるようにします。ユーザーに即座にフィードバックが提供され、選択した注文タイプがメインボタンに表示されることがわかります。
DESTROY_ORDER_TYPE_DROPDOWN();
選択がおこなわれた後、先に定義したDESTROY_ORDER_TYPE_DROPDOWN関数を呼び出し、チャート上のドロップダウンオブジェクトをすべて削除します。
次に、メインボタンが再度「押されていない」見た目になるように、状態をリセットします。
if(state == true){ ObjectSetInteger(0, BTN_ORDER_TYPES, OBJPROP_STATE, false); }
その後、主要な入力フィールドに実用的な初期値を自動で設定します。
ObjectSetString(0, FIELD_ENTRY_PRICE, OBJPROP_TEXT, DoubleToString(Ask(), Digits())); ObjectSetString(0, FIELD_STOP_LOSS, OBJPROP_TEXT, DoubleToString(Ask() - (200 * Point()), Digits())); ObjectSetString(0, FIELD_TAKE_PROFIT, OBJPROP_TEXT, DoubleToString(Ask() + (400 * Point()), Digits()));
各行の役割は以下のとおりです。
- 建値:現在のAsk価格に設定します。成行買い注文が実行される価格です。
- ストップロス:Ask価格より200ポイント低い位置に設定します。論理的な安全マージンとして機能します。
- テイクプロフィット:Ask価格より400ポイント高い位置に設定します。リスクリワードレシオが有利になるようにしています。
最後に、
ChartRedraw();
を呼び出します。これにより、チャートが即座に更新され、オブジェクトやテキストの最新状態が画面に反映されます。
これで、ドロップダウン内のすべての注文タイプを機能させたい場合、 トレーダーが注文タイプを選択したときに自動で以下の3つの処理がおこなわれます。
- 選択した注文タイプのテキストが[Order Type]ボタンに表示される。
- ドロップダウンが閉じ、ボタンが通常状態に戻る。
- 建値、ストップロス、テイクプロフィットフィールドが、その注文タイプに適した合理的な値で更新される。
たとえば、トレーダーが[Market Sell]を選択した場合、EAは以下の処理をおこないます。
- メインの注文タイプボタンを[Market Sell]と表示するよう更新する。
- ドロップダウンを閉じ、ボタン状態を復元する。
- 建値、ストップロス、テイクプロフィットを、売り注文に適した値で自動的に入力する。
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int32_t id, const long &lparam, const double &dparam, const string &sparam) { //--- Handle click events if(id == CHARTEVENT_OBJECT_CLICK){ ... //--- To select the market sell order if(sparam == "ORDER_TYPE_GROUP_MARKET_SELL"){ bool state = ObjectGetInteger(0, BTN_ORDER_TYPES, OBJPROP_STATE); string text = ObjectGetString (0, "ORDER_TYPE_GROUP_MARKET_SELL", OBJPROP_TEXT); ObjectSetString(0, BTN_ORDER_TYPES, OBJPROP_TEXT, text); DESTROY_ORDER_TYPE_DROPDOWN(); if(state == true){ ObjectSetInteger(0, BTN_ORDER_TYPES, OBJPROP_STATE, false); } //--- Set reasonable values to start with ObjectSetString(0, FIELD_ENTRY_PRICE, OBJPROP_TEXT, DoubleToString(Bid(), Digits())); ObjectSetString(0, FIELD_STOP_LOSS, OBJPROP_TEXT, DoubleToString(Bid() + (200 * Point()), Digits())); ObjectSetString(0, FIELD_TAKE_PROFIT, OBJPROP_TEXT, DoubleToString(Bid() - (400 * Point()), Digits())); ChartRedraw(); } } }
これは成行売り注文なので、注文はBid価格で実行されます。ストップロスは建値より上(200ポイント高い位置)に設定し、テイクプロフィットは建値より下(400ポイント低い位置)に設定します。これにより、典型的な売り注文における実用的な価格設定が自動的におこなわれます。
指値買いの場合も基本的なロジックは同じですが、価格は現在の市場価格より下に設定される指値注文に合わせて調整されます。
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int32_t id, const long &lparam, const double &dparam, const string &sparam) { //--- Handle click events if(id == CHARTEVENT_OBJECT_CLICK){ ... //--- To select the buy limit order if(sparam == "ORDER_TYPE_GROUP_BUY_LIMIT"){ bool state = ObjectGetInteger(0, BTN_ORDER_TYPES, OBJPROP_STATE); string text = ObjectGetString (0, "ORDER_TYPE_GROUP_BUY_LIMIT", OBJPROP_TEXT); ObjectSetString(0, BTN_ORDER_TYPES, OBJPROP_TEXT, text); DESTROY_ORDER_TYPE_DROPDOWN(); if(state == true){ ObjectSetInteger(0, BTN_ORDER_TYPES, OBJPROP_STATE, false); } //--- Set reasonable values to start with ObjectSetString(0, FIELD_ENTRY_PRICE, OBJPROP_TEXT, DoubleToString(Ask() - (200 * Point()), Digits())); ObjectSetString(0, FIELD_STOP_LOSS, OBJPROP_TEXT, DoubleToString(Ask() - (400 * Point()), Digits())); ObjectSetString(0, FIELD_TAKE_PROFIT, OBJPROP_TEXT, DoubleToString(Ask() + (200 * Point()), Digits())); ChartRedraw(); } } }
つまり、建値は現在のAsk価格より低く設定され、ストップロスはさらに低い位置に、テイクプロフィットはAsk価格より高い位置に設定されます。
指値売りは指値買いの反対で、現在のBid価格より上に設定します。
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int32_t id, const long &lparam, const double &dparam, const string &sparam) { //--- Handle click events if(id == CHARTEVENT_OBJECT_CLICK){ ... //--- To select the sell limit order if(sparam == "ORDER_TYPE_GROUP_SELL_LIMIT"){ bool state = ObjectGetInteger(0, BTN_ORDER_TYPES, OBJPROP_STATE); string text = ObjectGetString (0, "ORDER_TYPE_GROUP_SELL_LIMIT", OBJPROP_TEXT); ObjectSetString(0, BTN_ORDER_TYPES, OBJPROP_TEXT, text); DESTROY_ORDER_TYPE_DROPDOWN(); if(state == true){ ObjectSetInteger(0, BTN_ORDER_TYPES, OBJPROP_STATE, false); } //--- Set reasonable values to start with ObjectSetString(0, FIELD_ENTRY_PRICE, OBJPROP_TEXT, DoubleToString(Bid() + (200 * Point()), Digits())); ObjectSetString(0, FIELD_STOP_LOSS, OBJPROP_TEXT, DoubleToString(Bid() + (400 * Point()), Digits())); ObjectSetString(0, FIELD_TAKE_PROFIT, OBJPROP_TEXT, DoubleToString(Bid() - (200 * Point()), Digits())); ChartRedraw(); } } }
建値は現在の市場レベルより上に設定されます。ストップロスはさらに高くなり、テイクプロフィットは低くなります。これは、指値売り注文の一般的な構成と一致します。
価格が引き続き上昇すると予想して、市場価格を上回る逆指値買い注文が出されます。
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int32_t id, const long &lparam, const double &dparam, const string &sparam) { //--- Handle click events if(id == CHARTEVENT_OBJECT_CLICK){ ... //--- To select the buy stop order if(sparam == "ORDER_TYPE_GROUP_BUY_STOP"){ bool state = ObjectGetInteger(0, BTN_ORDER_TYPES, OBJPROP_STATE); string text = ObjectGetString (0, "ORDER_TYPE_GROUP_BUY_STOP", OBJPROP_TEXT); ObjectSetString(0, BTN_ORDER_TYPES, OBJPROP_TEXT, text); DESTROY_ORDER_TYPE_DROPDOWN(); if(state == true){ ObjectSetInteger(0, BTN_ORDER_TYPES, OBJPROP_STATE, false); } //--- Set reasonable values to start with ObjectSetString(0, FIELD_ENTRY_PRICE, OBJPROP_TEXT, DoubleToString(Ask() + (200 * Point()), Digits())); ObjectSetString(0, FIELD_STOP_LOSS, OBJPROP_TEXT, DoubleToString(Ask(), Digits())); ObjectSetString(0, FIELD_TAKE_PROFIT, OBJPROP_TEXT, DoubleToString(Ask() + (600 * Point()), Digits())); ChartRedraw(); } } }
ここでの逆指値買い注文の建値は、現在のAsk価格よりやや上に置かれます。ストップロスは現在のAsk価格付近に設定され、テイクプロフィットは上方に置かれ、価格上昇の可能性を狙います。
最後に、逆指値売り注文は、価格がさらに下落することを見越して現在の価格より下に置かれます。
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int32_t id, const long &lparam, const double &dparam, const string &sparam) { //--- Handle click events if(id == CHARTEVENT_OBJECT_CLICK){ ... //--- To select the sell stop order if(sparam == "ORDER_TYPE_GROUP_SELL_STOP"){ bool state = ObjectGetInteger(0, BTN_ORDER_TYPES, OBJPROP_STATE); string text = ObjectGetString (0, "ORDER_TYPE_GROUP_SELL_STOP", OBJPROP_TEXT); ObjectSetString(0, BTN_ORDER_TYPES, OBJPROP_TEXT, text); DESTROY_ORDER_TYPE_DROPDOWN(); if(state == true){ ObjectSetInteger(0, BTN_ORDER_TYPES, OBJPROP_STATE, false); } //--- Set reasonable values to start with ObjectSetString(0, FIELD_ENTRY_PRICE, OBJPROP_TEXT, DoubleToString(Bid() - (200 * Point()), Digits())); ObjectSetString(0, FIELD_STOP_LOSS, OBJPROP_TEXT, DoubleToString(Bid(), Digits())); ObjectSetString(0, FIELD_TAKE_PROFIT, OBJPROP_TEXT, DoubleToString(Bid() - (600 * Point()), Digits())); ChartRedraw(); } } }
この注文は下方向へのブレイクアウトを想定しています。建値はBid価格より下に置かれ、ストップロスは上方に、テイクプロフィットはさらに下方に設定され、下落トレンドの継続による利益確保を狙います。
この時点で、ドロップダウン内のすべての注文タイプはインタラクティブになり、期待通りに動作します。トレーダーが任意の注文タイプをクリックすると、インターフェースは即座に反応します。
- 選択した注文タイプがメインの注文タイプボタンに表示される
- ドロップダウンが消える
- デフォルトの建値、ストップロス、テイクプロフィットが自動的に入力される
これにより、トレーダーは各注文タイプを直感的に確認でき、毎回手動で値を入力する必要のない、動的でユーザーフレンドリーな取引インターフェースが構築されます。
次に、ボタンにインタラクティブ機能を追加します。これにより、ユーザーが実行コントロールをクリックしたときに、EAがロットサイズを計算して表示したり、実際にサーバーに注文を送信したりできるようになります。ただし、ボタンを接続する前に、いくつかのサポート要素が必要です。これらは、コードに取引機能を追加し、ユニークなトレードIDを管理し、特定の注文タイプを開くための小さな補助ルーチンを提供します。
まず、標準取引ライブラリをインクルードします。この行は、ファイル上部のマクロの下に配置します。
//+------------------------------------------------------------------+ //| Standard Libraries | //+------------------------------------------------------------------+ #include <Trade\Trade.mqh>
このライブラリはCTradeクラスと、すぐに使える取引メソッドを提供します。このライブラリを使用することで、取引コードをシンプルかつ信頼性の高いものに保つことができます。
次に、EA用のマジックナンバーをユーザー入力として宣言します。この入力は、先ほどのインクルード部分の後に配置します。
//+------------------------------------------------------------------+ //| User input variables | //+------------------------------------------------------------------+ input group "Information" input ulong magicNumber = 254700680002;
このマジックナンバーは、このEAによって作成されたトレードを一意に識別します。ユーザー入力として設定することで、ソースコードを編集せずに変更できるようになります。
次に、いくつかのグローバル変数を定義します。これらはユーザー入力セクションのすぐ下に配置します。
//+------------------------------------------------------------------+ //| Global variables | //+------------------------------------------------------------------+ CTrade Trade; double accountBalance; double contractSize;
Tradeオブジェクトは、成行注文や指値・逆指値注文を簡単に発注するメソッドを提供します。他の2つの変数は、後で使用する可能性のある口座情報や契約サイズ情報を格納するためのプレースホルダーです。
次に、OnInit内でTradeオブジェクトを初期化し、EAのマジックナンバーを設定します。OnInitに以下の行を追加します。
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { ... //--- Set Expert Magic Number Trade.SetExpertMagicNumber(magicNumber); return(INIT_SUCCEEDED); }
続いて、各注文タイプで注文をおこなうための小さなヘルパー関数を追加します。これらのヘルパー関数は、1回の取引呼び出しをラップし、成功した場合はtrue、失敗した場合はfalseを返します。このようにラップすることで、メインの処理フローを読みやすく、保守しやすく保つことができます。
たとえば、成行買い用のヘルパー関数は、指定されたロット数、エントリー価格、ストップロス、テイクプロフィットを使ってTrade.Buyメソッドを呼び出します。呼び出しが失敗した場合は、エラーコードとトレード結果情報を出力してfalseを返します。成功した場合はtrueを返します。
//+------------------------------------------------------------------+ //| Function to open a market buy position | //+------------------------------------------------------------------+ bool OPEN_MARKET_BUY(double entryPrice, double stopLoss, double takeProfit, double lotSize){ if(!Trade.Buy(lotSize, _Symbol, entryPrice, stopLoss, takeProfit)){ Print("Error while executing a market buy order: ", GetLastError()); Print(Trade.ResultRetcode()); Print(Trade.ResultComment()); return false; } return true; }
成行売り用のヘルパーはTrade.Sellを使用して同じことをおこないます。エラーも同様に処理し、呼び出し元にブール値の結果を返します。
//+------------------------------------------------------------------+ //| Function to open a market sell position | //+------------------------------------------------------------------+ bool OPEN_MARKET_SELL(double entryPrice, double stopLoss, double takeProfit, double lotSize){ if(!Trade.Sell(lotSize, _Symbol, entryPrice, stopLoss, takeProfit)){ Print("Error while executing a market sell order: ", GetLastError()); Print(Trade.ResultRetcode()); Print(Trade.ResultComment()); return false; } return true; }
指値・逆指値注文用のヘルパーも追加します。指値買い用のヘルパーはTrade.BuyLimitを使用し、指値売り用のヘルパーはTrade.SellLimitを使用します。両方のヘルパー関数は、建値、ストップロス、テイクプロフィット、ロットサイズを引数として受け取ります。注文が失敗した場合はエラーを出力し、falseを返します。
//+------------------------------------------------------------------+ //| Function to open a buy limit order | //+------------------------------------------------------------------+ bool OPEN_BUY_LIMIT(double entryPrice, double stopLoss, double takeProfit, double lotSize){ if(!Trade.BuyLimit(lotSize, entryPrice, _Symbol, stopLoss, takeProfit)){ Print("Error while executing a buy limit order: ", GetLastError()); Print(Trade.ResultRetcode()); Print(Trade.ResultComment()); return false; } return true; } //+------------------------------------------------------------------+ //| Function to open a sell limit order | //+------------------------------------------------------------------+ bool OPEN_SELL_LIMIT(double entryPrice, double stopLoss, double takeProfit, double lotSize){ if(!Trade.SellLimit(lotSize, entryPrice, _Symbol, stopLoss, takeProfit)){ Print("Error while executing a sell limit order: ", GetLastError()); Print(Trade.ResultRetcode()); Print(Trade.ResultComment()); return false; } return true; }
最後に、Trade.BuyStopとTrade.SellStopを使用する逆指値買い注文と逆指値売り注文用のヘルパーを追加します。各ヘルパー関数は、注文を試み、失敗した場合は、診断情報(エラーコードやコメント)を出力し、成功した場合はtrue、失敗した場合はfalseを返すというパターンで動作します。
//+------------------------------------------------------------------+ //| Function to open a buy stop order | //+------------------------------------------------------------------+ bool OPEN_BUY_STOP(double entryPrice, double stopLoss, double takeProfit, double lotSize){ if(!Trade.BuyStop(lotSize, entryPrice, _Symbol, stopLoss, takeProfit)){ Print("Error while executing a buy stop order: ", GetLastError()); Print(Trade.ResultRetcode()); Print(Trade.ResultComment()); return false; } return true; } //+------------------------------------------------------------------+ //| Function to open a sell stop order | //+------------------------------------------------------------------+ bool OPEN_SELL_STOP(double entryPrice, double stopLoss, double takeProfit, double lotSize){ if(!Trade.SellStop(lotSize, entryPrice, _Symbol, stopLoss, takeProfit)){ Print("Error while executing a sell stop order: ", GetLastError()); Print(Trade.ResultRetcode()); Print(Trade.ResultComment()); return false; } return true; }
この統一されたパターンにより、逆指値注文も簡単に扱えます。後で、ユーザーが選択した注文タイプに応じて、適切なヘルパー関数を呼び出すだけで済みます。ヘルパー関数が出力するエラーメッセージによって、証拠金不足や無効なパラメータなどの原因を診断できます。
次に、[Calculate Lot]ボタンと[Send Order]ボタンにインタラクティブ機能を追加します。これにより、ユーザーがこれらのボタンをクリックしたときに、EAが適切に反応するようになります。

これらの2つのボタンは、私たちのグラフィカルインターフェースの最終段階を表しています。1つ目のボタンは、定義されたリスク割合に基づいて正しいロットサイズを計算します。2つ目のボタンは、選択された注文タイプをマーケットやサーバーに送信します。これを実現するためには、各ボタンがクリックされたかどうかを検知し、適切な処理を実行する必要があります。
まず、OnChartEvent関数を拡張します。この関数は、ユーザーがチャート上で操作するたびに自動的に呼び出されます。ここでは、特にCHARTEVENT_OBJECT_CLICKを扱います。これは、ユーザーがボタンをクリックしたときに発生するイベントです。イベント内で、どのオブジェクト(ボタン)が押されたかを確認するために、オブジェクト名をチェックします。
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int32_t id, const long &lparam, const double &dparam, const string &sparam) { //--- Handle click events if(id == CHARTEVENT_OBJECT_CLICK){ ... //--- When the lot calculation button is clicked if(sparam == BTN_CALC_LOT) { } //--- When the send order button is clicked if(sparam == BTN_SEND_ORDER){ } } }
オブジェクト名がBTN_CALC_LOTと一致する場合、それはユーザーが[Calculate Lot]ボタンをクリックしたことを意味します。同様に、BTN_SEND_ORDERと一致する場合は[Send Order]ボタンがクリックされたことになります。この段階では、イベントはクリックを検知するだけで、実際の処理はまだおこないません。各アクションを正しく処理するために、ロット計算用と注文実行用の2つのヘルパー関数を定義します。
ロットサイズ計算の処理
//+------------------------------------------------------------------+ //| Function to handles lot calculation process | //+------------------------------------------------------------------+ void HandleLotCalculation(){ string order_type = ObjectGetString(0, BTN_ORDER_TYPES, OBJPROP_TEXT); string entry_price = ObjectGetString(0, FIELD_ENTRY_PRICE, OBJPROP_TEXT); string stop_loss = ObjectGetString(0, FIELD_STOP_LOSS, OBJPROP_TEXT); string take_profit = ObjectGetString(0, FIELD_TAKE_PROFIT, OBJPROP_TEXT); string riskPercent = ObjectGetString(0, RISK, OBJPROP_TEXT); accountBalance = AccountInfoDouble(ACCOUNT_BALANCE); contractSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_CONTRACT_SIZE); if(order_type == "Select Order Type"){ ObjectSetString(0, RESULTS_TEXT, OBJPROP_TEXT, "Please Select Order Type!"); ChartRedraw(); return; } if(order_type == "Market Buy "){ double stopDistance = Ask() - StringToDouble(stop_loss); double riskPercentValue = StringToDouble(riskPercent); double amountAtRisk = (riskPercentValue / 100.0) * accountBalance; double lotSize = amountAtRisk / (contractSize * stopDistance); lotSize = lotSize = NormalizeDouble(lotSize, 2); ObjectSetString(0, RESULTS_TEXT, OBJPROP_TEXT, "Result: Lot Size = " + DoubleToString(lotSize, 2)); ChartRedraw(); } if(order_type == "Market Sell"){ double stopDistance = StringToDouble(stop_loss) - Bid(); double riskPercentValue = StringToDouble(riskPercent); double amountAtRisk = (riskPercentValue / 100.0) * accountBalance; double lotSize = amountAtRisk / (contractSize * stopDistance); lotSize = lotSize = NormalizeDouble(lotSize, 2); ObjectSetString(0, RESULTS_TEXT, OBJPROP_TEXT, "Result: Lot Size = " + DoubleToString(lotSize, 2)); ChartRedraw(); } if(order_type == "Buy Limit "){ double stopDistance = StringToDouble(entry_price) - StringToDouble(stop_loss); double riskPercentValue = StringToDouble(riskPercent); double amountAtRisk = (riskPercentValue / 100.0) * accountBalance; double lotSize = amountAtRisk / (contractSize * stopDistance); lotSize = lotSize = NormalizeDouble(lotSize, 2); ObjectSetString(0, RESULTS_TEXT, OBJPROP_TEXT, "Result: Lot Size = " + DoubleToString(lotSize, 2)); ChartRedraw(); } if(order_type == "Sell Limit "){ double stopDistance = StringToDouble(stop_loss) - StringToDouble(entry_price); double riskPercentValue = StringToDouble(riskPercent); double amountAtRisk = (riskPercentValue / 100.0) * accountBalance; double lotSize = amountAtRisk / (contractSize * stopDistance); lotSize = lotSize = NormalizeDouble(lotSize, 2); ObjectSetString(0, RESULTS_TEXT, OBJPROP_TEXT, "Result: Lot Size = " + DoubleToString(lotSize, 2)); ChartRedraw(); } if(order_type == "Buy Stop "){ double stopDistance = StringToDouble(entry_price) - StringToDouble(stop_loss); double riskPercentValue = StringToDouble(riskPercent); double amountAtRisk = (riskPercentValue / 100.0) * accountBalance; double lotSize = amountAtRisk / (contractSize * stopDistance); lotSize = lotSize = NormalizeDouble(lotSize, 2); ObjectSetString(0, RESULTS_TEXT, OBJPROP_TEXT, "Result: Lot Size = " + DoubleToString(lotSize, 2)); ChartRedraw(); } if(order_type == "Sell Stop "){ double stopDistance = StringToDouble(stop_loss) - StringToDouble(entry_price); double riskPercentValue = StringToDouble(riskPercent); double amountAtRisk = (riskPercentValue / 100.0) * accountBalance; double lotSize = amountAtRisk / (contractSize * stopDistance); lotSize = lotSize = NormalizeDouble(lotSize, 2); ObjectSetString(0, RESULTS_TEXT, OBJPROP_TEXT, "Result: Lot Size = " + DoubleToString(lotSize, 2)); ChartRedraw(); } }
HandleLotCalculation関数は、正しいロットサイズの計算に関わるすべての処理を管理します。この関数が呼び出されると、まずGUI上のすべての関連データ(注文タイプ、建値、ストップロス、テイクプロフィット、リスク割合)を取得します。さらに、現在の口座残高と取引対象の契約サイズも取得します。
最初のチェックでは、ユーザーが注文タイプを選択しているか確認します。選択されていない場合、チャート上に親切なメッセージを表示し、注文タイプを選択するように促します。これにより、不要な計算を防止します。
有効な注文タイプが確認されると、関数は注文が成行買い、成行売り、指値買い、指値売り、逆指値買い、逆指値売りのいずれであるかに応じてロットサイズを異なる方法で計算します。それぞれのケースで、建値とストップロスの差(ストップ距離)を計算し、価格ベースのリスクを測定します。
次に、リスク額を「口座残高 * リスク割合」として計算します。このリスク額を、「契約サイズ * ストップ距離」で割ることで、定義されたリスクに基づく正しいロットサイズを算出します。結果は小数点以下2桁に丸められ、GUIの結果表示欄にきれいに表示されます。
これにより、トレーダーはたとえば口座残高の2%や3%をリスクにさらす場合に必要なロット数を、手計算なしで瞬時に確認できます。
注文実行処理
//+------------------------------------------------------------------+ //| Function to handles order execution process. | //+------------------------------------------------------------------+ void HandleOrderExecution(){ string order_type = ObjectGetString(0, BTN_ORDER_TYPES, OBJPROP_TEXT); string entry_price = ObjectGetString(0, FIELD_ENTRY_PRICE, OBJPROP_TEXT); string stop_loss = ObjectGetString(0, FIELD_STOP_LOSS, OBJPROP_TEXT); string take_profit = ObjectGetString(0, FIELD_TAKE_PROFIT, OBJPROP_TEXT); string riskPercent = ObjectGetString(0, RISK, OBJPROP_TEXT); accountBalance = AccountInfoDouble(ACCOUNT_BALANCE); contractSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_CONTRACT_SIZE); if(order_type == "Select Order Type"){ ObjectSetString(0, RESULTS_TEXT, OBJPROP_TEXT, "Please Select Order Type!"); ChartRedraw(); return; } if(order_type == "Market Buy "){ double stopDistance = Ask() - StringToDouble(stop_loss); double riskPercentValue = StringToDouble(riskPercent); double amountAtRisk = (riskPercentValue / 100.0) * accountBalance; double lotSize = amountAtRisk / (contractSize * stopDistance); lotSize = lotSize = NormalizeDouble(lotSize, 2); ObjectSetString(0, RESULTS_TEXT, OBJPROP_TEXT, "Result: Lot Size = " + DoubleToString(lotSize, 2)); OPEN_MARKET_BUY(Ask(), StringToDouble(stop_loss), StringToDouble(take_profit), lotSize); ChartRedraw(); } if(order_type == "Market Sell"){ double stopDistance = StringToDouble(stop_loss) - Bid(); double riskPercentValue = StringToDouble(riskPercent); double amountAtRisk = (riskPercentValue / 100.0) * accountBalance; double lotSize = amountAtRisk / (contractSize * stopDistance); lotSize = lotSize = NormalizeDouble(lotSize, 2); ObjectSetString(0, RESULTS_TEXT, OBJPROP_TEXT, "Result: Lot Size = " + DoubleToString(lotSize, 2)); OPEN_MARKET_SELL(Bid(), StringToDouble(stop_loss), StringToDouble(take_profit), lotSize); ChartRedraw(); } if(order_type == "Buy Limit "){ double stopDistance = StringToDouble(entry_price) - StringToDouble(stop_loss); double riskPercentValue = StringToDouble(riskPercent); double amountAtRisk = (riskPercentValue / 100.0) * accountBalance; double lotSize = amountAtRisk / (contractSize * stopDistance); lotSize = lotSize = NormalizeDouble(lotSize, 2); ObjectSetString(0, RESULTS_TEXT, OBJPROP_TEXT, "Result: Lot Size = " + DoubleToString(lotSize, 2)); OPEN_BUY_LIMIT(StringToDouble(entry_price), StringToDouble(stop_loss), StringToDouble(take_profit), lotSize); ChartRedraw(); } if(order_type == "Sell Limit "){ double stopDistance = StringToDouble(stop_loss) - StringToDouble(entry_price); double riskPercentValue = StringToDouble(riskPercent); double amountAtRisk = (riskPercentValue / 100.0) * accountBalance; double lotSize = amountAtRisk / (contractSize * stopDistance); lotSize = lotSize = NormalizeDouble(lotSize, 2); ObjectSetString(0, RESULTS_TEXT, OBJPROP_TEXT, "Result: Lot Size = " + DoubleToString(lotSize, 2)); OPEN_SELL_LIMIT(StringToDouble(entry_price), StringToDouble(stop_loss), StringToDouble(take_profit), lotSize); ChartRedraw(); } if(order_type == "Buy Stop "){ double stopDistance = StringToDouble(entry_price) - StringToDouble(stop_loss); double riskPercentValue = StringToDouble(riskPercent); double amountAtRisk = (riskPercentValue / 100.0) * accountBalance; double lotSize = amountAtRisk / (contractSize * stopDistance); lotSize = lotSize = NormalizeDouble(lotSize, 2); ObjectSetString(0, RESULTS_TEXT, OBJPROP_TEXT, "Result: Lot Size = " + DoubleToString(lotSize, 2)); OPEN_BUY_STOP(StringToDouble(entry_price), StringToDouble(stop_loss), StringToDouble(take_profit), lotSize); ChartRedraw(); } if(order_type == "Sell Stop "){ double stopDistance = StringToDouble(stop_loss) - StringToDouble(entry_price); double riskPercentValue = StringToDouble(riskPercent); double amountAtRisk = (riskPercentValue / 100.0) * accountBalance; double lotSize = amountAtRisk / (contractSize * stopDistance); lotSize = lotSize = NormalizeDouble(lotSize, 2); ObjectSetString(0, RESULTS_TEXT, OBJPROP_TEXT, "Result: Lot Size = " + DoubleToString(lotSize, 2)); OPEN_SELL_STOP(StringToDouble(entry_price), StringToDouble(stop_loss), StringToDouble(take_profit), lotSize); ChartRedraw(); } }
2つ目のヘルパー関数HandleOrderExecutionは、前の関数と同じ手順を踏みますが、さらに一歩進んで実際の注文を実行します。関数が注文タイプを確認し、正しいロットサイズを計算すると、事前に定義した該当の注文実行関数(OPEN_MARKET_BUYやOPEN_SELL_LIMITなど)を呼び出します。
ロット計算関数と同様に、まず注文タイプが選択されているかを確認します。選択されていない場合は、ユーザーに注文タイプを選択するようチャート上でメッセージが表示されます。その後、ストップ距離、リスク金額、最終ロットサイズを同じ方法で計算します。違いは、ロットサイズが決定された後、適切なヘルパー関数を使って注文を取引サーバーに送信する点です。
各ヘルパー関数は対応する注文タイプを処理し、価格が無効であったり、証拠金不足の場合などのエラーが発生した際には、[エキスパート]タブに診断情報を出力します。注文が実行されると、チャートは結果と使用したロットサイズを表示するように更新されます。
最後に、これら2つの関数はOnChartEvent関数内で呼び出されます。ユーザーがボタンをクリックすると、イベントハンドラはどのボタンが押されたかを判定し、それに応じたヘルパー関数を呼び出します。
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int32_t id, const long &lparam, const double &dparam, const string &sparam) { //--- Handle click events if(id == CHARTEVENT_OBJECT_CLICK){ ... //--- When the lot calculation button is clicked if(sparam == BTN_CALC_LOT) { HandleLotCalculation(); } //--- When the send order button is clicked if(sparam == BTN_SEND_ORDER){ HandleOrderExecution(); } } }
これにより、インターフェースは完全にインタラクティブになります。[Calculate Lot]ボタンが押されると、EAは瞬時に適切なロットサイズを計算して表示します。[Send Order]ボタンが押されると、計算済みのパラメータを使って適切な取引が実行されます。
このアプローチにより、GUIは単に見た目が整っているだけでなく、機能的で実用的なものになります。トレーダーは、コマンドを入力したり、別のパネルに切り替えたりすることなく、チャート上で直接リスクベースの取引管理をおこなえるようになります。
結論
これで第2回が完了し、プロジェクトは一応の完成となります。本記事では、GUIを静的なものから完全にインタラクティブで機能的なものへと進化させました。すべてのボタン、ドロップダウン、入力フィールドを実際の取引アクションと接続し、EAがリスクに基づくロットサイズを計算し、チャート上から直接取引を実行できるようにしました。
この過程で、視覚的に魅力的なインターフェースを設計する方法だけでなく、OnChartEventを用いてユーザーの操作に知的に反応させる方法も学びました。また、取引実行ロジックをクリーンでモジュール化された形で構造化するためのヘルパー関数の作り方も理解できました。これはプロ仕様のEAに欠かせない基盤です。
この知識を応用すれば、ここで作ったGUIをさらに高度で機能豊富なものに拡張できます。高度な注文パネルから、完全な取引管理ダッシュボードまで、応用範囲は無限です。基盤はすでに整っており、あとはあなたの創造力次第です。
最後にボーナスとして、このプロジェクトの完全なソースコードを添付しました。自由に確認、改変、応用して、自分の取引アイデアに合わせたツールを作ることができます。個人取引用であれ、クライアント用であれ、このプロジェクトはMQL5上で完全にインタラクティブかつリスク管理に対応した取引インターフェースを開発するための強力な出発点となります。
これで、チャート上のコントロールパネルを用いたリスクベースの取引配置EAの構築についての連載は終了です。すぐに使える実用的なツールであり、MQL5 GUI開発をマスターするための大きな一歩となります。
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/20159
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
取引戦略の開発:トリプルサイン平均回帰法
MQL5での取引戦略の自動化(第39回):信頼区間とダッシュボードを備えた統計的平均回帰
MQL5取引ツール(第10回):視覚的なレベルとパフォーマンス指標を備えた戦略追跡システムの構築
古典的な戦略を再構築する(第18回):ローソク足パターンの探索
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索