English Русский 中文 Español Deutsch Português
preview
MQL5取引ツールキット(第1回):ポジション管理EX5ライブラリ

MQL5取引ツールキット(第1回):ポジション管理EX5ライブラリ

MetaTrader 5 | 22 7月 2024, 10:44
382 0
Wanateki Solutions LTD
Kelvin Muturi Muigua

はじめに

ソフトウェア開発者として、しばしば独自のコードライブラリやツールキットを作成することが便利で効率的なことがあります。さまざまなMQL5開発プロジェクトで必要となる一般的なタスクのコードを何度も書き直す必要がなくなり、時間の節約になります。この連載では、一般的なMQL5開発プロジェクトで反復タスクを実行するMQL5取引ライブラリを作成します。

この最初の記事では、開発者ライブラリとは何か、なぜ重要なのか、そしてMQL5で作成できるさまざまなタイプのコードライブラリについて説明します。次に、実際のプロジェクトでコードライブラリをどのように使用できるかを理解するための実践的な例として、さまざまなポジション操作を処理するMQL5関数のライブラリを作成します。


MQL5のコードライブラリとは?

MQL5コードライブラリは、MetaTrader 5プラットフォーム用のEA、カスタム指標、スクリプト、またはサービスの開発プロセスを効率的にスピードアップするために使用できる、事前に記述されたコード関数(ex5)またはダイナミックリンクライブラリ(DLL)です。 

コードライブラリを整備士の道具箱とご想像ください。整備士の道具箱に、特定の作業のためのさまざまな工具(スパナ、ドライバーなど)が入っているように、コードライブラリにも、同じような役割を果たす関数があらかじめ書き込まれています。各関数は、ポジションの開閉や変更、プッシュ通知の送信、データベース管理など、レンチがボルトを締めたり、ドライバーがネジを回したりするように、プログラム内の特定のタスクに対応します。


MQL5のコードライブラリの種類

MQL5開発者がコードライブラリやツールキットを構築するにはいくつかの選択肢があります。

  • 関数ライブラリ(ex5):これらのライブラリは手続き的なコーディングスタイルを実装し、特定のタスクのためにあらかじめ書かれた関数のコレクションを提供します。また、コードのカプセル化というセキュリティ上の利点もあります。それぞれが特定の仕事のために設計された、個別の道具だとお考えください。
  • サードパーティのC++ DLL:DLL (Dynamic Linked Libraries)としてあらかじめ書かれたC++ライブラリを統合することができます。これにより、外部関数を活用できるようになり、MQL5の機能が拡張されます。

MetaTrader 5では、ツールキットを拡大する方法も追加されています。

  • .NET ライブラリ:MetaEditorは、「スマート」な関数インポートによって.NETライブラリとのシームレスな統合を提供し、カスタムラッパーを不要にします。
  • Python言語モジュール:新しくサポートされたPython言語モジュールにより、MQL5プロジェクトでPythonの機能を活用できます。

C++に慣れていれば、MQL5プロジェクトに簡単に統合できるカスタムDLLを作成できます。Microsoft Visual Studioなどのツールを使用して、C++ソースコードファイル(CPPとH)を開発し、DLLにコンパイルして、MetaEditorにインポートしてMQL5コードで使用することができます。

MQL5ライブラリに似た他のコードリソース

  • クラス/インクルード(*.mqh):MQL5のインクルードファイルはオブジェクト指向プログラミングを利用し、データや機能をカプセル化したビルド済みのクラスを提供します。関数/メソッドとデータ構造を組み合わせた、より複雑なツールだとご想像ください。要するに、クラスや構造体をエクスポートしてMQL5ライブラリ(ex5)を作成することはできませんが、MQL5ライブラリ関数内でクラスや構造体へのポインタや参照を使用することはできます。


ex5 MQL5ライブラリを作成または使用する必要がある理由

MQL5プロジェクト用に独自のコードライブラリを作成することで、開発プロセスをより効率的にすることができます。これらのライブラリは.ex5ファイルとして保存され、特定のタスクのために最適化された関数で満たされた個人的な道具箱のような役割を果たします。

容易な再利用性とモジュラー設計

新しいプロジェクトを始めるたびに一般的な関数を書き直す必要がないため、時間を節約できます。.ex5ライブラリを使えば、一度コードを書いてから、最高のパフォーマンスが得られるように最適化し、関数をエクスポートして、どんなプロジェクトにも簡単にインポートすることができます。このモジュラーアプローチは、コア機能とプロジェクト固有のロジックを分離することで、コードをすっきりと整理します。ライブラリの各部分は、強力な取引システムを作成するためのビルディングブロックとなります。

カプセル化によるセキュアな関数

MQL5ライブラリを作成することで、ソースコードを隠したまま関数を共有することができます。カプセル化することで、コードの詳細が安全でユーザーから見えないようにする一方で、機能のための明確なインターフェイスを提供することができます。他の開発者がライブラリ関数をプロジェクトにインポートできるように、エクスポートされた関数定義の明確な文書化とともに.ex5ライブラリファイルを共有するだけです。.ex5ライブラリファイルは、ソースコードとエクスポートされた関数がどのようにコード化されているかを効果的に隠し、メインプロジェクトのコード内に安全かつカプセル化されたワークスペースを維持します。

簡単なアップグレードと長期的なメリット

新しい言語機能が登場したり、古い機能が非推奨になったりしても、.ex5ライブラリを使えば簡単にコードを更新できます。ライブラリのコードを更新し、再デプロイして、それを使用しているすべてのプロジェクトを再コンパイルするだけで、自動的に変更が反映されます。特に大規模なプロジェクトでは、時間と労力を大幅に節約できます。ライブラリはコードベースの中央システムとして機能し、1つの更新や変更が関連するすべてのプロジェクトに影響するため、長期的には作業を効率化できます。


MQL5でex5ライブラリを作成するには?

すべての.ex5ライブラリのファイルは.mq5ソースコードファイルです。先頭に#property libraryディレクティブが追加されており、それに続いて、特別なキーワードexportを使用してエクスポート可能として指定された1つ以上の関数があります。.mq5ライブラリソースコードファイルは、コンパイル後に.ex5ライブラリファイルに変換され、ソースコードを安全にカプセル化または隠蔽し、他のMQL5プロジェクトでインポートして使用できるようにします。

新しいMQL5ライブラリの作成は、MetaEditor IDEを使えば簡単です。以下の手順に従って、ポジション管理機能を含む新しいライブラリソースコードファイル(.mq5)を作成し、後で.ex5ライブラリにコンパイルします。

手順1MetaEditor IDEを開き、[新規]メニューアイテムボタンを使用してMQL Wizardを起動します。

Mql5ウィザード新規ライブラリファイル

手順2:[ライブラリ]オプションを選択し、[次へ]をクリックします。

Mql5ウィザード新規ライブラリファイル

手順3ライブラリファイルの一般的なプロパティセクションで、新しいライブラリのフォルダと名前「Libraries\Toolkit\PositionsManager」を入力します。[終了]をクリックするとライブラリが新規生成されます。

Mql5ウィザード新規ライブラリファイル一般プロパティ

MQL5ライブラリは通常、拡張子が.mq5のファイルに保存されます。このファイルには、さまざまな特定のタスクのために書かれたさまざまな関数のソースコードが含まれています。コードライブラリは、デフォルトではMetaTrader 5のインストールディレクトリ内のMQL5Librariesフォルダに格納されています。MetaEditorのNavigatorパネルを使用してLibrariesフォルダにアクセスするのが簡単な方法です。

MetaEditorナビゲータパネルのMQL5デフォルトライブラリフォルダ

新しく作成した空白のMQL5ライブラリPositionsManager.mq5ファイルには、ヘッドプロパティディレクティブとコメントアウトされた関数が含まれています。続行する前に、新しいファイルを保存することを忘れないでください。新しく生成されたライブラリファイルはこんな感じだ:

//+------------------------------------------------------------------+
//|                                             PositionsManager.mq5 |
//|                          Copyright 2024, Wanateki Solutions Ltd. |
//|                                         https://www.wanateki.com |
//+------------------------------------------------------------------+
#property library
#property copyright "Copyright 2024, Wanateki Solutions Ltd."
#property link      "https://www.wanateki.com"
#property version   "1.00"
//+------------------------------------------------------------------+
//| My function                                                      |
//+------------------------------------------------------------------+
// int MyCalculator(int value,int value2) export
//   {
//    return(value+value2);
//   }
//+------------------------------------------------------------------+

MQL5ライブラリソースコードファイルの構成要素

MQL5ライブラリのソースコードファイル(.mq5)は、主に2つのコンポーネントで構成されています。

1.#property libraryダイレクティブ:この#propertyダイレクティブは、ライブラリ.mq5ソースコードファイルの先頭に追加する必要があります。ライブラリプロパティは、指定されたファイルがライブラリであることをコンパイラに知らせます。これは、.mq5ライブラリのコンパイル結果である.ex5ファイルのコンパイル済みライブラリのヘッダーに保存されます。

#property library

2. エクスポートされた関数:MQL5ライブラリの中心はエクスポートされた関数です。これらはライブラリの主要な構成要素であり、ライブラリによって実行されるすべての力仕事を担当します。エクスポートされたMQL5関数は、通常のMQL5関数と似ていますが、exportポスト修飾子を使用して宣言され、これにより、コンパイル後に他のMQL5プログラムでインポートして使用することができます。export修飾子は、指定された関数を、このライブラリファイルによってエクスポートされるex5関数の表に追加するようにコンパイラに指示します。export修飾子で宣言された関数だけが、他のmql5プログラムからアクセス可能で、検出可能です。ここでは特別な#importディレクティブでインポートされた後に呼び出すことができます。

//+------------------------------------------------------------------+
//| Example of an exported function with the export postmodifier         |
//+------------------------------------------------------------------+
int ExportedFunction(int a, int b) export
  {
   return(a + b);
  }
//+------------------------------------------------------------------+

MQL5ライブラリは、エキスパートアドバイザー、カスタム指標、スクリプトのような標準的なイベント処理を直接実行することは期待されていませんし、要求されていません。つまり、OnInit()OnDeinit()OnTick()のような標準的な関数を持ちません。


ポジション管理用MQL5関数ライブラリ

ポジション管理は、開発中のすべてのエキスパートアドバイザーにとって基本的なタスクです。これらの重要な操作は、アルゴリズム取引システムの中核をなします。繰り返しのコーディングを避けるために、MQL5の開発者はライブラリを使用して効率的にポジションを管理する必要があります。これにより、開発者はエキスパートアドバイザーごとに同じポジション管理コードを書き直す必要がなくなります。

このセクションでは、新しく作成したPositionsManager.mq5ファイルにコードを追加し、MQL5を使用してポジション管理ライブラリを作成します。このライブラリは、すべてのポジション関連業務を処理します。EAのコードにインポートすることで、ポジションの実行と管理を効率的におこない、コードベースをすっきりと整理することができます。


グローバル変数


MQL5における取引リクエスト取引は、特別な定義済み構造体MqlTradeRequestで表されます。この構造体には、取引操作の実行に必要なすべてのフィールドが含まれており、すべての必要な注文リクエストデータが単一のデータ構造内にパッケージされていることを保証します。取引リクエストが処理されると、結果は別の定義済み構造体MqlTradeResultに保存されます。MqlTradeResultは、取引リクエストが成功したかどうかや、取引の執行に関する関連データなど、取引リクエストの結果に関する詳細情報を提供する役割を果たします。

この2つの特殊なデータ構造体は、ほとんどの関数で使用することになるので、まずグローバル変数として宣言し、ライブラリ全体で使えるようにしておきましょう。

//---Global variables
//-------------------------------
//-- Trade operations request and result data structures
MqlTradeRequest tradeRequest;
MqlTradeResult  tradeResult;
//-------------------------------


ポジションエラー管理関数


ライブラリの最初の関数はエラー管理関数です。MQL5でポジションをオープン、変更、クローズする際、しばしばさまざまな種類のエラーが発生し、操作を中断することや、ポジション管理リクエストを取引サーバーに再送信することが必要になります。適切なアクションを監視し、推奨するための専用関数を作成することは、適切にコーディングされ、最適化されたライブラリを持つために必要なことです。

エラー処理関数を作成する前に、遭遇する可能性のあるさまざまなMQL5ポジション管理エラーコードを理解する必要があります。以下の表は、さまざまなポジションを管理する際に検出し、克服する必要がある、返された取引サーバーとランタイムエラーコードのいくつかを示しています。 エラーと警告の全コードはMQL5のドキュメントにあります。

コード コード定数  詳細
アクション コードの種類 
10004  TRADE_RETCODE_REQUOTE リクオート 注文リクエストを再度送信します。 取引サーバーのリターンコード(RETCODE)
10008  TRADE_RETCODE_PLACED 注文 なし。操作が成功しました。 取引サーバーのリターンコード(RETCODE)
10009  TRADE_RETCODE_DONE リクエスト完了 なし。操作が完了しました。
取引サーバーのリターンコード(RETCODE)
10013  TRADE_RETCODE_INVALID 無効なリクエスト 注文初期化リクエストの再送信を停止し、注文の詳細を更新します。 取引サーバーのリターンコード(RETCODE)
10014  TRADE_RETCODE_INVALID_VOLUME リクエストのボリュームが無効 注文初期化リクエストの再送信を停止し、注文の詳細を更新します。 取引サーバーのリターンコード(RETCODE)
10016  TRADE_RETCODE_INVALID_STOPS リクエスト内の無効なストップ 注文初期化リクエストの再送信を停止し、注文の詳細を更新します。 取引サーバーのリターンコード(RETCODE)
10017  TRADE_RETCODE_TRADE_DISABLED 取引は無効 すべての取引操作を終了し、注文初期化リクエストの再送信を停止します。 取引サーバーのリターンコード(RETCODE)
10018  TRADE_RETCODE_MARKET_CLOSED 市場が閉まっている 注文初期化リクエストの再送を停止します。 取引サーバーのリターンコード(RETCODE)
10019  TRADE_RETCODE_NO_MONEY 要請を完了するための十分な資金がない 注文初期化リクエストの再送信を停止し、注文の詳細を更新します。 取引サーバーのリターンコード(RETCODE)
10026  TRADE_RETCODE_SERVER_DISABLES_AT サーバーによる自動売買の無効化 取引はサーバーによって許可されていません。注文初期化リクエストの再送を停止します。 取引サーバーのリターンコード(RETCODE)
10027  TRADE_RETCODE_CLIENT_DISABLES_AT クライアント端末で自動売買が無効 クライアント端末がEA取引を無効にしています。注文初期化リクエストの再送を停止します。 取引サーバーのリターンコード(RETCODE)
10034  TRADE_RETCODE_LIMIT_VOLUME 銘柄の注文とポジションの量が上限に達した 注文初期化リクエストの再送を停止します。 取引サーバーのリターンコード(RETCODE)
10011  TRADE_RETCODE_ERROR リクエスト処理エラー 注文初期化リクエストを再送し続けます。 取引サーバーのリターンコード(RETCODE)
10012  TRADE_RETCODE_TIMEOUT タイムアウトによってキャンセルされたリクエスト 数ミリ秒実行を一時停止し、注文初期化リクエストを再送し続けます。 取引サーバーのリターンコード(RETCODE)
10015  TRADE_RETCODE_INVALID_PRICE リクエストの価格が無効 注文入力価格を更新し、注文初期化リクエストを再送信します。 取引サーバーのリターンコード(RETCODE)
10020  TRADE_RETCODE_PRICE_CHANGED 価格変更 注文入力価格を更新し、注文初期化リクエストを再送信します。 取引サーバーのリターンコード(RETCODE)
10021  TRADE_RETCODE_PRICE_OFF リクエストを処理するための見積もりはありません。 数ミリ秒実行を一時停止し、注文初期化リクエストを再送します。 取引サーバーのリターンコード(RETCODE)
10024  TRADE_RETCODE_TOO_MANY_REQUESTS 頻繁すぎるリクエスト 数秒間実行を一時停止し、注文初期化リクエストを再送します。 取引サーバーのリターンコード(RETCODE)
 10031  TRADE_RETCODE_CONNECTION 取引サーバーとの接続がない 数ミリ秒実行を一時停止し、注文初期化リクエストを再度送信します。 取引サーバーのリターンコード(RETCODE)
 0  ERR_SUCCESS 操作が正常に完了 リクエストの再送を停止します。注文は正常に送信されました。   ランタイムエラーコード
 4752  ERR_TRADE_DISABLED EAによる取引禁止 注文初期化リクエストの再送を停止します。 ランタイムエラーコード
 4753  ERR_TRADE_POSITION_NOT_FOUND ポジションが見つからない 取引操作リクエストの再送信を停止します。
ランタイムエラーコード
 4754  ERR_TRADE_ORDER_NOT_FOUND 注文が見つからない 注文リクエストの再送を停止します。
ランタイムエラーコード
 4755  ERR_TRADE_DEAL_NOT_FOUND 取引が見つからない 注文リクエストの再送を停止します。
ランタイムエラーコード


上記のエラーを処理する最初の関数をライブラリに作ってみましょう。ErrorAdvisor()と名付けられたエラー処理関数はブール型で、遭遇したエラータイプに応じてTrueまたはFalseを返します。データ処理を助けるために2つの引数を取ります。

  1. callingFunc (string)ErrorAdvisor()を呼び出す関数の名前または識別子が格納されます
  2. symbol (string):作業中のアセットの銘柄名が格納されます
  3. tradeServerErrorCode (integer):発生したエラーのタイプが格納されます

エラーが回復可能で重大でない場合、ErrorAdvisor()関数はTrueを返します。これは、注文がまだ実行されていないこと、注文リクエストを再送する必要があることを呼び出し関数に示します。ErrorAdvisor()Falseを返した場合、注文がすでに正常に実行されたか回復不可能な重大なエラーが発生したため、呼び出し関数がそれ以上の注文リクエストの送信を停止する必要があることを意味します。

関数がライブラリに属し、他のMQL5プログラムで使用されることを示すために、関数を開く中括弧の前にexport修飾子を置くことを忘れないでください。

//------------------------------------------------------------------+
// ErrorAdvisor(): Error analysis and processing function.          |
// Returns true if order opening failed and order can be re-sent    |
// Returns false if the error is critical and can not be executed   |
//------------------------------------------------------------------+
bool ErrorAdvisor(string callingFunc, string symbol, int tradeServerErrorCode)  export
  {
//-- place the function body here
  }

まず、現在の実行時エラーを格納する整数変数を宣言し、初期化することから始めましょう。整数にruntimeErrorCodeという名前を付け、GetLastError()関数を呼び出して最新のランタイムエラーを保存します。 この変数を使用して、2つ目の入れ子になったスイッチ演算子で、遭遇する可能性のある実行時エラーを処理します。

//-- save the current runtime error code
   int runtimeErrorCode = GetLastError();

ネストされたswitch演算子を使用して、取引サーバーのリターンエラー(retcode)とランタイムエラーをスキャンして処理します。これは、エラーの種類を素早く特定し、EAのログにユーザーのための説明を表示し、呼び出し関数に処理方法を指示できるので便利です。エラーが2つのカテゴリに分類されていることにお気づきでしょう。

  1. 注文の完了または不実行を示すエラーFalseを返し、注文リクエストの送信を停止するよう呼び出し関数に指示します。
  2. 不完全な注文を示すエラーTrueを返し、注文リクエストを再送するよう呼び出し関数に指示します。

最初のスイッチ演算子は取引サーバーのリターンコードを処理し、2番目の入れ子になったスイッチ演算子はランタイムエラーコードを処理します。 このアプローチでは、エラーコードや警告のたびに逐次確認することを避け、関数のコードを最小限に抑えることができます。

では、最初のswitch文をコーディングして、tradeServerErrorCodeを調べ、取引サーバーが報告したエラーの種類を確認しましょう。

switch(tradeServerErrorCode)//-- check for trade server errors
     {
        //--- Cases to scan different retcodes/server return codes
     }

switch文の中に、取引サーバーから返されるさまざまなエラーコードのケースを追加します。そのいくつかを紹介しましょう。

Requote(コード10004):ユーザーが注文を開こうとしてから価格が変更されたことを意味します。この場合、ログにメッセージを表示し、Sleep()関数を使用して数ミリ秒待ち、その後、注文のオープンを再試行するよう呼び出し関数に指示します。

case 10004:
    Print(symbol, " - ", callingFunc, " ->(TradeServer_Code: ", tradeServerErrorCode, ") Requote!");
    Sleep(10);
    return(true);    //--- Exit the function and retry opening the order again

注文に成功しました(コード10008):このコードが返された場合、すべてがうまくいき、注文がおこなわれたことになります。注文が成功したというメッセージをログに出力し、注文を開こうとするのを止めるように呼び出し関数に指示することができます。

case 10008:
    Print(symbol, " - ", callingFunc, " ->(TradeServer_Code: ", tradeServerErrorCode, ") Order placed!");
    return(false);    //--- success - order placed ok. exit function

他の取引サーバーエラー(コード10009、10011、10012など)についても同様のケースを追加し、EAのログにメッセージを出力し、必要であれば少し待ち、再度取引リクエストを送信するかどうかを呼び出し関数に指示するという同じロジックに従います。

取引サーバーエラー用のswitch文で一致するものが見つからない場合、そのエラーはランタイムエラーである可能性があり、GetLastError()関数によって返された現在のランタイムエラーをスキャンする別のswitch文を作成することによってのみ見つけることができることになります。この課題に取り組むために、先のswitch文のdefaultセクションで入れ子になったswitch文を使用して、先に保存したruntimeErrorCodeの値を調べることにします。

default:
  switch(runtimeErrorCode)//-- check for runtime errors
    //-- Add cases for different runtime errors here
  }
}

取引サーバーエラーでおこなったのと同じプロセスを繰り返し、異なるランタイムエラーコードのケースを追加します。

エラーなし(コード0):プログラム的にはすべてがうまく行っています。エラーがなかったというメッセージをログに出力し、呼び出し関数に試行を中止するよう指示すればいいのです。

case 0:
    Print(symbol, " - ", callingFunc, " ->(Runtime_Code: ", runtimeErrorCode, ") The operation completed successfully!");
    ResetLastError(); //--- reset error cache
    return(false);    //--- Exit the function and stop trying to open order

他のランタイムエラー(コード4752、4753、4754など)についても同様のケースを追加し、EAのエラーログにメッセージを出力し、呼び出し関数に取引リクエストを停止または続行するよう指示するという同じロジックに従います。

注文の実行プロセスに影響を与える可能性のある最も重要なエラーコードのみを考慮し、現存する可能性のあるすべてのエラーコードをスキャンまたは処理していないため、現在のコードでは処理または考慮されていないエラーに遭遇する可能性があります。この場合、未知の(その他の)エラーが発生したことを示すメッセージをログに出力し、サーバーのリターンコードエラーと発生したランタイムエラーを指定し、注文を開こうとするのを止めるように呼び出し関数に伝えます。

default: //--- All other error codes
    Print(symbol, " - ", callingFunc, " *OTHER* Error occurred \r\nTrade Server RetCode: ", tradeServerErrorCode, ", Runtime Error Code = ", runtimeErrorCode);
    ResetLastError(); //--- reset error cache
    return(false);    //--- Exit the function and stop trying to open order
    break;

以下は、すべてのコードセグメントが完成したエラー管理関数ErrorAdvisor()です。

bool ErrorAdvisor(string callingFunc, string symbol, int tradeServerErrorCode)  export
  {
//-- save the current runtime error code
   int runtimeErrorCode = GetLastError();

   switch(tradeServerErrorCode)//-- check for trade server errors
     {
      case 10004:
         Print(symbol, " - ", callingFunc, " ->(TradeServer_Code: ", tradeServerErrorCode, ") Requote!");
         Sleep(10);
         return(true);    //--- Exit the function and retry opening the order again
      case 10008:
         Print(symbol, " - ", callingFunc, " ->(TradeServer_Code: ", tradeServerErrorCode, ") Order placed!");
         return(false);    //--- success - order placed ok. exit function

      case 10009:
         Print(symbol, " - ", callingFunc, " ->(TradeServer_Code: ", tradeServerErrorCode, ") Request completed!");
         return(false);    //--- success - order placed ok. exit function

      case 10011:
         Print(symbol, " - ", callingFunc, " ->(TradeServer_Code: ", tradeServerErrorCode, ") Request processing error!");
         Sleep(10);
         return(true);    //--- Exit the function and retry opening the order again

      case 10012:
         Print(symbol, " - ", callingFunc, " ->(TradeServer_Code: ", tradeServerErrorCode, ") Request canceled by timeout!");
         Sleep(100);
         return(true);    //--- Exit the function and retry opening the order again

      case 10015:
         Print(symbol, " - ", callingFunc, " ->(TradeServer_Code: ", tradeServerErrorCode, ") Invalid price in the request!");
         Sleep(10);
         return(true);    //--- Exit the function and retry opening the order again

      case 10020:
         Print(symbol, " - ", callingFunc, " ->(TradeServer_Code: ", tradeServerErrorCode, ") Prices changed!");
         Sleep(10);
         return(true);    //--- Exit the function and retry opening the order again

      case 10021:
         Print(symbol, " - ", callingFunc, " ->(TradeServer_Code: ", tradeServerErrorCode, ") There are no quotes to process the request!");
         Sleep(100);
         return(true);    //--- Exit the function and retry opening the order again

      case 10024:
         Print(symbol, " - ", callingFunc, " ->(TradeServer_Code: ", tradeServerErrorCode, ") Too frequent requests!");
         Sleep(1000);
         return(true);    //--- Exit the function and retry opening the order again

      case 10031:
         Print(symbol, " - ", callingFunc, " ->(TradeServer_Code: ", tradeServerErrorCode, ") No connection with the trade server!");
         Sleep(100);
         return(true);    //--- Exit the function and retry opening the order again

      default:
         switch(runtimeErrorCode)//-- check for runtime errors
            case 0:
               Print(symbol, " - ", callingFunc, " ->(Runtime_Code: ", runtimeErrorCode, ") The operation completed successfully!");
               ResetLastError(); //--- reset error cache
               return(false);    //--- Exit the function and stop trying to open order

            case 4752:
               Print(symbol, " - ", callingFunc, " ->(Runtime_Code: ", runtimeErrorCode, ") Trading by Expert Advisors prohibited!");
               ResetLastError(); //--- reset error cache
               return(false);    //--- Exit the function and stop trying to open order

            case 4753:
               Print(symbol, " - ", callingFunc, " ->(Runtime_Code: ", runtimeErrorCode, ") Position not found!");
               ResetLastError(); //--- reset error cache
               return(false);    //--- Exit the function and stop trying to open order

            case 4754:
               Print(symbol, " - ", callingFunc, " ->(Runtime_Code: ", runtimeErrorCode, ") Order not found!");
               ResetLastError(); //--- reset error cache
               return(false);    //--- Exit the function and stop trying to open order

            case 4755:
               Print(symbol, " - ", callingFunc, " ->(Runtime_Code: ", runtimeErrorCode, ") Deal not found!");
               ResetLastError(); //--- reset error cache
               return(false);    //--- Exit the function and stop trying to open order

            default: //--- All other error codes
               Print(symbol, " - ", callingFunc, " *OTHER* Error occurred \r\nTrade Server RetCode: ", tradeServerErrorCode, ", Runtime Error Code = ", runtimeErrorCode);
               ResetLastError(); //--- reset error cache
               return(false);    //--- Exit the function and stop trying to open order
               break;
           }
     }
  }


取引許可関数


この関数は、取引端末で現在取引が許可されているかどうかを確認します。ユーザー、取引サーバー、ブローカーからの承認を考慮します。この関数は、ポジション操作や注文リクエストが取引サーバーに送信される前に呼び出されます。

関数にTradingIsAllowed()という名前を付け、戻り値の型をブール型とします。取引が許可され有効になっている場合、ブールTrueを返し、自動売買取引が無効または禁止されている場合、ブールFalseを返します。パラメータや引数を持たず、以下のコードセグメントを含みます。

//+-----------------------------------------------------------------------+
//| TradingIsAllowed() verifies whether auto-trading is currently allowed |                                                                 |
//+-----------------------------------------------------------------------+
bool TradingIsAllowed() export
  {
   if(
      !IsStopped() &&
      MQLInfoInteger(MQL_TRADE_ALLOWED) && TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) &&
      AccountInfoInteger(ACCOUNT_TRADE_ALLOWED) && AccountInfoInteger(ACCOUNT_TRADE_EXPERT)
   )
     {
      return(true);//-- trading is allowed, exit and return true
     }
   return(false);//-- trading is not allowed, exit and return false
  }


注文詳細ログ/出力関数


これは、さまざまな取引操作やポジションオープンリクエストのプロパティをログに記録し、出力するシンプルな関数です。EAユーザーがMetaTrader 5 EAログタブを通じてEAの操作状況を更新する簡単な方法を提供します。この関数はエクスポートされていないため、明示的に呼び出したり実行したりする他のエクスポートされた関数を通じてのみアクセス可能であることにお気づきでしょう。PrintOrderDetails()と名付け、データを返さないvoid型関数とし、入力パラメータまたは引数として文字列変数を1つ取ることにします。

//+-----------------------------------------------------------------------+
//| PrintOrderDetails() prints the order details for the EA log           |
//+-----------------------------------------------------------------------+
void PrintOrderDetails(string header)
  {
   string orderDescription;
//-- Print the order details
   orderDescription += "_______________________________________________________________________________________\r\n";
   orderDescription += "--> "  + tradeRequest.symbol + " " + EnumToString(tradeRequest.type) + " " + header +
                       " <--\r\n";
   orderDescription += "Order ticket: " + (string)tradeRequest.order + "\r\n";
   orderDescription += "Volume: " + StringFormat("%G", tradeRequest.volume) + "\r\n";
   orderDescription += "Price: " + StringFormat("%G", tradeRequest.price) + "\r\n";
   orderDescription += "Stop Loss: " + StringFormat("%G", tradeRequest.sl) + "\r\n";
   orderDescription += "Take Profit: " + StringFormat("%G", tradeRequest.tp) + "\r\n";
   orderDescription += "Comment: " + tradeRequest.comment + "\r\n";
   orderDescription += "Magic Number: " + StringFormat("%d", tradeRequest.magic) + "\r\n";
   orderDescription += "Order filling: " + EnumToString(tradeRequest.type_filling)+ "\r\n";
   orderDescription += "Deviation points: " + StringFormat("%G", tradeRequest.deviation) + "\r\n";
   orderDescription += "RETCODE: " + (string)(tradeResult.retcode) + "\r\n";
   orderDescription += "Runtime Code: " + (string)(GetLastError()) + "\r\n";
   orderDescription += "---";
   Print(orderDescription);
  }


ポジションオープニング関数


この関数を2つのカテゴリに分類します。

  1. OpenBuyPositions()関数:このエクスポート可能な機能は、その名の通り、新規の買いポジションを建てる役割を担います。
  2. OpenSellPositions()関数:このエクスポート可能な機能は、その名の通り、新規の売りポジションを建てることのみを担います。

OpenBuyPositios()関数

この関数はbool型で、指示通りに新規買いポジションを建てられた場合はtrueをを、建てられなかった場合はfalseを返します。パラメータ(引数)は6つです。

  1. ulong magicNumber:EAのマジックナンバーを保存し、ポジションの変更や終了を容易にします。
  2. string symbol:リクエストが実行される銘柄またはアセットの名前を保存します。
  3. double lotSize:建てる買いポジションの数量を格納します。
  4. int sl:買いポジションのストップロス値をポイント/pipsで保存します。
  5. int tp:買いポジションのテイクプロフィット値をポイント/pipsで格納します。
  6. string positionComment:買いポジションのコメントを保存または格納するために使用します。

まずは関数定義のコーディングから始めましょう。このライブラリを実装する他のMQL5プロジェクトで使用するために、この関数をエクスポート可能にするようコンパイラに指示するために、関数を開く中括弧の前にexport post修飾子を置いていることに注目してください。

bool OpenBuyPosition(ulong magicNumber, string symbol, double lotSize, int sl, int tp, string positionComment) export
  {
        //--- Function body
  }

注文を出す前に、EAの取引が許可されているかどうかを確認しましょう。これは、先ほど作成したTradingIsAllowed()関数を呼び出すことでおこないます。TradingIsAllowed()関数は、自動売買が有効になっているかどうかを確認するために、様々な設定や権限をスキャンします。

TradingIsAllowed()falseを返した場合、それは取引が無効であり、EAが注文を出せないことを意味します。この場合、この関数からもすぐにfalseを返し、新しい買い注文を出さずに終了します。

//-- first check if the ea is allowed to trade
if(!TradingIsAllowed())
  {
   return(false); //--- algo trading is disabled, exit function
  }

次のステップでは、以前の取引で残っていたデータを消去して、注文リクエストの準備をします。そのために、ファイルの最初に作成した2つのグローバル取引データ構造体(tradeRequesttradeResult)に対してZeroMemory()関数を使用してみましょう。これらはそれぞれ、出したい買い注文の詳細と、取引サーバーから返された結果を保存します。

//-- reset the the tradeRequest and tradeResult values by zeroing them
   ZeroMemory(tradeRequest);
   ZeroMemory(tradeResult);

では、買いポジションを建てるためのパラメータをtradeRequestデータ構造体変数に設定してみましょう。

  • tradeRequest.type:買い注文を示すにはORDER_TYPE_BUYに設定
  • tradeRequest.action:新規ポジションを建てるにはTRADE_ACTION_DEALに設定
  • tradeRequest.magic:引数として与えられたmagicNumberを代入(EAによって開かれた注文を識別するのに役立つ)
  • tradeRequest.symbol:引数として与えられた銘柄を割り当て、取引する通貨ペアを指定
  • tradeRequest.tptradeRequest.sl:テイクプロフィット(TP)とストップロス(SL)のレベルは後で扱うので、今は0に設定
  • tradeRequest.comment:引数として指定されたpositionCommentを代入し、注文にテキストコメントを追加するために使用
  • tradeRequest.deviation:取引中の銘柄について、現在のスプレッドの2倍までの乖離を許容するように設定(これにより、プラットフォームは、注文のリクオートを制限し、一致する注文価格を見つけることができる)

//-- initialize the parameters to open a buy position
   tradeRequest.type = ORDER_TYPE_BUY;
   tradeRequest.action = TRADE_ACTION_DEAL;
   tradeRequest.magic = magicNumber;
   tradeRequest.symbol = symbol;
   tradeRequest.tp = 0;
   tradeRequest.sl = 0;
   tradeRequest.comment = positionComment;
   tradeRequest.deviation = SymbolInfoInteger(symbol, SYMBOL_SPREAD) * 2;

注文量やロットサイズの正規化は非常に重要なステップです。lotSize変数の引数またはパラメータを調整して、選択した銘柄の許容範囲内に収まるようにします。どのように調整するかは次の通りです。

//-- set and moderate the lot size or volume
   lotSize = MathMax(lotSize, SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN));  //-- Verify that volume is not less than allowed minimum
   lotSize = MathMin(lotSize, SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX));  //-- Verify that volume is not more than allowed maximum
   lotSize = MathFloor(lotSize / SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP)) * SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP); //-- Round down to nearest volume step
   tradeRequest.volume = lotSize;

次に、ResetLastError()関数を使用して、プラットフォームから以前のランタイムエラーコードを消去します。これで、後でErrorAdvisor()関数から正確なエラーコードを得ることを保証します。

//--- Reset error cache so that we get an accurate runtime error code in the ErrorAdvisor function
   ResetLastError();

ここで、最大100回まで(最大101回の反復)発注を試すことができるループに入ります。このループは、一時的な市場の変動やその他の理由で注文が最初のトライでオープンできなかった場合のフェイルセーフとして機能します。

for(int loop = 0; loop <= 100; loop++) //-- try opening the order untill it is successful (100 max tries)
   {
    //--- Place the order request code to open a new buy position
   }

forループの最初のタスクは、注文価格のリクオートがあった場合に、各反復で注文開始価格を更新することです。SymbolInfoDouble(symbol, SYMBOL_ASK)を使用して、銘柄の現在の提示価格を取得し、tradeRequest.priceに代入します。これにより、注文リクエストが最新の市場価格を反映したものとなります。

//--- update order opening price on each iteration
   tradeRequest.price = SymbolInfoDouble(symbol, SYMBOL_ASK);

次に、各反復でテイクプロフィット値とストップロス値を更新し、更新されたエントリ価格と一致させるとともに、それらの値が必要な精度要件に適合するように正規化し、それらをtradeRequest.tptradeRequest.tpデータ構造体に割り当てます。

//-- set the take profit and stop loss on each iteration
   if(tp > 0)
     {
      tradeRequest.tp = NormalizeDouble(tradeRequest.price + (tp * _Point), _Digits);
     }
   if(sl > 0)
     {
      tradeRequest.sl = NormalizeDouble(tradeRequest.price - (sl * _Point), _Digits);
     }

次に、取引サーバーに注文を送信し、約定させます。このタスクでは、準備したtradeRequestと空のtradeResult変数を引数またはパラメータとして渡し、OrderSend()関数を使用します。この関数は、tradeRequestに格納された仕様に基づいて注文を出そうとします。成功またはエラーコードを含む結果は、実行完了後にtradeResult変数に格納されます。

OrderSend()関数に配置したif文は、注文リクエストが成功したかどうかをチェックし、確認することができます。OrderSend()trueを返した場合は、注文リクエストが正常に送信されたことを意味し、falseを返した場合は、リクエストが失敗したことを意味します。

次に、先にコード化した関数PrintOrderDetails()を「Sent OK」というメッセージとともに呼び出し、この情報をEAのログに記録します。

また、tradeResult.retcodeを確認し、注文が正常に執行されたことを確認します。OpenBuyPosition()関数からtrueを返して成功を示し、breakを使用してループを完全に終了します。OrderSend()がfalseを返す場合(注文リクエストが失敗したことを意味する)、問題が発生したことを意味します。PrintOrderDetails()を「Sending Failed」というメッセージで呼び出し、この情報をログに記録します。また、エラーメッセージを表示して遭遇したさまざまなエラーコードを強調し、OpenBuyPosition()関数からfalseを返して失敗を示し、breakを使用してループを終了します。

//--- send order to the trade server
      if(OrderSend(tradeRequest, tradeResult))
        {
         //-- Print the order details
         PrintOrderDetails("Sent OK");

         //-- Confirm order execution
         if(tradeResult.retcode == 10008 || tradeResult.retcode == 10009)
           {
            Print(__FUNCTION__, ": CONFIRMED: Successfully openend a ", symbol, " BUY POSITION #", tradeResult.order, ", Price: ", tradeResult.price);
            PrintFormat("retcode=%u  deal=%I64u  order=%I64u", tradeResult.retcode, tradeResult.deal, tradeResult.order);
            Print("_______________________________________________________________________________________");
            return(true); //-- exit the function
            break; //--- success - order placed ok. exit the for loop
           }
        }
      else //-- Order request failed
        {
         //-- Print the order details
         PrintOrderDetails("Sending Failed");

         //-- order not sent or critical error found
         if(!ErrorAdvisor(__FUNCTION__, symbol, tradeResult.retcode) || IsStopped())
           {
            Print(__FUNCTION__, ": ", symbol, " ERROR opening a BUY POSITION at: ", tradeRequest.price, ", Lot\\Vol: ", tradeRequest.volume);
            Print("_______________________________________________________________________________________");
            return(false); //-- exit the function
            break; //-- exit the for loop
           }
        }

以下は、OpenBuyPosition()のすべてのコードセグメントとその適切なシーケンスです。

//-------------------------------------------------------------------+
// OpenBuyPosition(): Function to open a new buy entry order.        |
//+------------------------------------------------------------------+
bool OpenBuyPosition(ulong magicNumber, string symbol, double lotSize, int sl, int tp, string positionComment) export
  {
//-- first check if the ea is allowed to trade
   if(!TradingIsAllowed())
     {
      return(false); //--- algo trading is disabled, exit function
     }

//-- reset the the tradeRequest and tradeResult values by zeroing them
   ZeroMemory(tradeRequest);
   ZeroMemory(tradeResult);

//-- initialize the parameters to open a buy position
   tradeRequest.type = ORDER_TYPE_BUY;
   tradeRequest.action = TRADE_ACTION_DEAL;
   tradeRequest.magic = magicNumber;
   tradeRequest.symbol = symbol;
   tradeRequest.tp = 0;
   tradeRequest.sl = 0;
   tradeRequest.comment = positionComment;
   tradeRequest.deviation = SymbolInfoInteger(symbol, SYMBOL_SPREAD) * 2;

//-- set and moderate the lot size or volume
   lotSize = MathMax(lotSize, SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN));  //-- Verify that volume is not less than allowed minimum
   lotSize = MathMin(lotSize, SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX));  //-- Verify that volume is not more than allowed maximum
   lotSize = MathFloor(lotSize / SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP)) * SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP); //-- Round down to nearest volume step
   tradeRequest.volume = lotSize;

//--- Reset error cache so that we get an accurate runtime error code in the ErrorAdvisor function
   ResetLastError();

   for(int loop = 0; loop <= 100; loop++) //-- try opening the order untill it is successful (100 max tries)
     {
      //--- update order opening price on each iteration
      tradeRequest.price = SymbolInfoDouble(symbol, SYMBOL_ASK);

      //-- set the take profit and stop loss on each iteration
      if(tp > 0)
        {
         tradeRequest.tp = NormalizeDouble(tradeRequest.price + (tp * _Point), _Digits);
        }
      if(sl > 0)
        {
         tradeRequest.sl = NormalizeDouble(tradeRequest.price - (sl * _Point), _Digits);
        }

      //--- send order to the trade server
      if(OrderSend(tradeRequest, tradeResult))
        {
         //-- Print the order details
         PrintOrderDetails("Sent OK");

         //-- Confirm order execution
         if(tradeResult.retcode == 10008 || tradeResult.retcode == 10009)
           {
            Print(__FUNCTION__, ": CONFIRMED: Successfully openend a ", symbol, " BUY POSITION #", tradeResult.order, ", Price: ", tradeResult.price);
            PrintFormat("retcode=%u  deal=%I64u  order=%I64u", tradeResult.retcode, tradeResult.deal, tradeResult.order);
            Print("_______________________________________________________________________________________");
            return(true); //-- exit the function
            break; //--- success - order placed ok. exit the for loop
           }
        }
      else //-- Order request failed
        {
         //-- Print the order details
         PrintOrderDetails("Sending Failed");

         //-- order not sent or critical error found
         if(!ErrorAdvisor(__FUNCTION__, symbol, tradeResult.retcode) || IsStopped())
           {
            Print(__FUNCTION__, ": ", symbol, " ERROR opening a BUY POSITION at: ", tradeRequest.price, ", Lot\\Vol: ", tradeRequest.volume);
            Print("_______________________________________________________________________________________");
            return(false); //-- exit the function
            break; //-- exit the for loop
           }
        }
     }
   return(false);
  }


OpenSellPositios()関数

この関数は、上記でコーディングを終えたOpenBuyPosition()関数と非常によく似ており、処理される注文のタイプなどいくつかの違いはありますが、同じ手順に従っています。OpenSellPosition()関数は、新規の売りポジションを建てるためのものです。失敗した場合に複数回試行するforループが含まれており、有効な取引リクエストパラメータが提供される限り、成功率は大幅に向上します。以下はOpenSellPosition()関数のコードです。

//-------------------------------------------------------------------+
// OpenSellPosition(): Function to open a new sell entry order.      |
//+------------------------------------------------------------------+
bool OpenSellPosition(ulong magicNumber, string symbol, double lotSize, int sl, int tp, string positionComment) export
  {
//-- first check if the ea is allowed to trade
   if(!TradingIsAllowed())
     {
      return(false); //--- algo trading is disabled, exit function
     }

//-- reset the the tradeRequest and tradeResult values by zeroing them
   ZeroMemory(tradeRequest);
   ZeroMemory(tradeResult);

//-- initialize the parameters to open a sell position
   tradeRequest.type = ORDER_TYPE_SELL;
   tradeRequest.action = TRADE_ACTION_DEAL;
   tradeRequest.magic = magicNumber;
   tradeRequest.symbol = symbol;
   tradeRequest.tp = 0;
   tradeRequest.sl = 0;
   tradeRequest.comment = positionComment;
   tradeRequest.deviation = SymbolInfoInteger(symbol, SYMBOL_SPREAD) * 2;

//-- set and moderate the lot size or volume
   lotSize = MathMax(lotSize, SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN));  //-- Verify that volume is not less than allowed minimum
   lotSize = MathMin(lotSize, SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX));  //-- Verify that volume is not more than allowed maximum
   lotSize = MathFloor(lotSize / SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP)) * SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP); //-- Round down to nearest volume step
   tradeRequest.volume = lotSize;

   ResetLastError(); //--- reset error cache so that we get an accurate runtime error code in the ErrorAdvisor function

   for(int loop = 0; loop <= 100; loop++) //-- try opening the order (101 max) times untill it is successful
     {
      //--- update order opening price on each iteration
      tradeRequest.price = SymbolInfoDouble(symbol, SYMBOL_BID);

      //-- set the take profit and stop loss on each iteration
      if(tp > 0)
        {
         tradeRequest.tp = NormalizeDouble(tradeRequest.price - (tp * _Point), _Digits);
        }
      if(sl > 0)
        {
         tradeRequest.sl = NormalizeDouble(tradeRequest.price + (sl * _Point), _Digits);
        }

      //--- send order to the trade server
      if(OrderSend(tradeRequest, tradeResult))
        {
         //-- Print the order details
         PrintOrderDetails("Sent OK");

         //-- Confirm order execution
         if(tradeResult.retcode == 10008 || tradeResult.retcode == 10009)
           {
            Print("CONFIRMED: Successfully openend a ", symbol, " SELL POSITION #", tradeResult.order, ", Price: ", tradeResult.price);
            PrintFormat("retcode=%u  deal=%I64u  order=%I64u", tradeResult.retcode, tradeResult.deal, tradeResult.order);
            Print("_______________________________________________________________________________________");
            return(true); //-- exit function
            break; //--- success - order placed ok. exit for loop
           }
        }
      else  //-- Order request failed
        {
         //-- Print the order details
         PrintOrderDetails("Sending Failed");

         //-- order not sent or critical error found
         if(!ErrorAdvisor(__FUNCTION__, symbol, tradeResult.retcode) || IsStopped())
           {
            Print(symbol, " ERROR opening a SELL POSITION at: ", tradeRequest.price, ", Lot\\Vol: ", tradeRequest.volume);
            Print("_______________________________________________________________________________________");
            return(false); //-- exit function
            break; //-- exit for loop
           }
        }
     }
   return(false);
  }


ポジションのストップロスとテイクプロフィットの変更関数


ライブラリの次の関数はSetSlTpByTicket()と呼ばれ、ポジションのチケットをフィルタリングのメカニズムとして使用して、既存のポジションのストップロス(SL)とテイクプロフィット(TP)のレベルを変更する役割を果たします。ポジションのチケット番号、希望するSL(pips(ポイント))、希望するTP(pips(ポイント))を引数として取り、取引サーバー上でポジションのSLとTPの更新を試みます。この関数はブール値(trueまたはfalse)を返します。ストップロスとテイクプロフィットレベルがポジションに対して正常に変更された場合はtrueを返し、ストップロスとテイクプロフィットレベルを正常に設定または変更できなかった場合はfalseを返します。

以下は、SetSlTpByTicket()関数の引数またはパラメータの内訳です。

  1. ulong positionTicket:これから修正するポジションの一意な識別子
  2. int sl:始値からのpips(ポイント)単位で希望するストップロスのレベル
  3. int tp:始値からの希望するテイクプロフィットレベルをpips(ポイント)で表したもの

関数の定義でexport post修飾子を使い、ライブラリから外部へアクセスできるようにすることを忘れないでください。

bool SetSlTpByTicket(ulong positionTicket, int sl, int tp) export
  {
//-- Function body
  }

上記の他の関数と同様に、まずTradingIsAllowed()関数を使用して、EAの取引が許可されているかどうかを確認します。取引が無効の場合、関数は終了し、falseを返します。

//-- first check if the EA is allowed to trade
   if(!TradingIsAllowed())
     {
      return(false); //--- algo trading is disabled, exit function
     }

positionTicket引数を使用して指定されたポジションを選択する前に、まず、後でErrorAdvisor()関数のための正確なアクション可能なエラー応答を取得するために、ランタイムエラーコードシステム変数をリセットします。選択が成功すると、ポジションが選択されたことを示すメッセージが表示され、ポジションのプロパティにアクセスするための許可が与えられます。選択に失敗した場合は、GetLastError()で取得したエラーコードとともにエラーメッセージが出力されます。その後、関数はfalseを返して終了します。

//--- Confirm and select the position using the provided positionTicket
   ResetLastError(); //--- Reset error cache incase of ticket selection errors
   if(PositionSelectByTicket(positionTicket))
     {
      //---Position selected
      Print("\r\n_______________________________________________________________________________________");
      Print(__FUNCTION__, ": Position with ticket:", positionTicket, " selected and ready to set SLTP.");
     }
   else
     {
      Print("\r\n_______________________________________________________________________________________");
      Print(__FUNCTION__, ": Selecting position with ticket:", positionTicket, " failed. ERROR: ", GetLastError());
      return(false); //-- Exit the function
     }

ポジションを選択したら、そのポジションに関するいくつかの詳細を収集し、保存する必要があります。この情報は、計算や後で参照するために使用します。

//-- create variables to store the calculated tp and sl prices to send to the trade server
   double tpPrice = 0.0, slPrice = 0.0;
   double newTpPrice = 0.0, newSlPrice = 0.0;

//--- Position ticket selected, save the position properties
   string positionSymbol = PositionGetString(POSITION_SYMBOL);
   double entryPrice = PositionGetDouble(POSITION_PRICE_OPEN);
   double volume = PositionGetDouble(POSITION_VOLUME);
   double currentPositionSlPrice = PositionGetDouble(POSITION_SL);
   double currentPositionTpPrice = PositionGetDouble(POSITION_TP);
   ENUM_POSITION_TYPE positionType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);

銘柄固有の情報も必要です。

//-- Get some information about the positions symbol
   int symbolDigits = (int)SymbolInfoInteger(positionSymbol, SYMBOL_DIGITS); //-- Number of symbol decimal places
   int symbolStopLevel = (int)SymbolInfoInteger(positionSymbol, SYMBOL_TRADE_STOPS_LEVEL);
   double symbolPoint = SymbolInfoDouble(positionSymbol, SYMBOL_POINT);
   double positionPriceCurrent = PositionGetDouble(POSITION_PRICE_CURRENT);
   int spread = (int)SymbolInfoInteger(positionSymbol, SYMBOL_SPREAD);

では、ポジションの始値、希望するSL/TP(pips(ポイント)単位)、ポジションタイプ(買いまたは売り)に基づいて、新しいストップロス(SL)とテイクプロフィット(TP)の価格を計算してみましょう。検証する前に、この初期計算を保存しておきます。

//--Save the non-validated tp and sl prices
   if(positionType == POSITION_TYPE_BUY) //-- Calculate and store the non-validated sl and tp prices
     {
      newSlPrice = entryPrice - (sl * symbolPoint);
      newTpPrice = entryPrice + (tp * symbolPoint);
     }
   else  //-- SELL POSITION
     {
      newSlPrice = entryPrice + (sl * symbolPoint);
      newTpPrice = entryPrice - (tp * symbolPoint);
     }

次に、先ほど集めたポジションの詳細を出力します。

//-- Print position properties before modification
   string positionProperties = "--> "  + positionSymbol + " " + EnumToString(positionType) + " SLTP Modification Details" +
   " <--\r\n";
   positionProperties += "------------------------------------------------------------\r\n";
   positionProperties += "Ticket: " + (string)positionTicket + "\r\n";
   positionProperties += "Volume: " + StringFormat("%G", volume) + "\r\n";
   positionProperties += "Price Open: " + StringFormat("%G", entryPrice) + "\r\n";
   positionProperties += "Current SL: " + StringFormat("%G", currentPositionSlPrice) + "   -> New Proposed SL: " + (string)newSlPrice + "\r\n";
   positionProperties += "Current TP: " + StringFormat("%G", currentPositionTpPrice) + "   -> New Proposed TP: " + (string)newTpPrice + "\r\n";
   positionProperties += "Comment: " + PositionGetString(POSITION_COMMENT) + "\r\n";
   positionProperties += "Magic Number: " + (string)PositionGetInteger(POSITION_MAGIC) + "\r\n";
   positionProperties += "---";
   Print(positionProperties);

ライブラリユーザがSLとTPに対して提供した値は、OrderSend()関数で直接使用できないかもしれません。続行する前に、その値を簡単に検証する必要があります。

//-- validate the sl and tp to a proper double that can be used in the OrderSend() function
   if(sl == 0)
     {
      slPrice = 0.0;
     }
   if(tp == 0)
     {
      tpPrice = 0.0;
     }

さて、先に保存した銘柄の詳細に基づいて、より複雑な検証をおこなう必要があります。検証ロジックを2つのグループ、1つは買いポジション用、もう1つは売りポジション用に分けます。SLとTPの検証は、銘柄の現在価格、銘柄のストップレベルの最小制限、銘柄のスプレッドに基づいておこなわれます。

指定されたTPまたはSL価格が無効で、必要な範囲外であることが判明した場合、元のTPまたはSL価格が保持され、修正に失敗した理由を説明するメッセージが出力されます。SLとTPの値の検証を終えたら、参考のために、確認された値と検証された値を記録するために、別の概要を出力します。

//--- Check if the sl and tp are valid in relation to the current price and set the tpPrice
   if(positionType == POSITION_TYPE_BUY)
     {
      //-- calculate the new sl and tp prices
      newTpPrice = 0.0;
      newSlPrice = 0.0;
      if(tp > 0)
        {
         newTpPrice = entryPrice + (tp * symbolPoint);
        }
      if(sl > 0)
        {
         newSlPrice = entryPrice - (sl * symbolPoint);
        }

      //-- save the new sl and tp prices incase they don't change afte validation below
      tpPrice = newTpPrice;
      slPrice = newSlPrice;

      if( //-- Check if specified TP is valid
         tp > 0 &&
         (
            newTpPrice <= entryPrice + (spread * symbolPoint) ||
            newTpPrice <= positionPriceCurrent ||
            (
               newTpPrice - entryPrice < symbolStopLevel * symbolPoint ||
               (positionPriceCurrent > entryPrice && newTpPrice - positionPriceCurrent < symbolStopLevel * symbolPoint)
            )
         )
      )
        {
         //-- Specified TP price is invalid, don't modify the TP
         Print(
            "Specified proposed ", positionSymbol,
            " TP Price at ", newTpPrice,
            " is invalid since current ", positionSymbol, " price is at ", positionPriceCurrent,
            "\r\nCurrent TP at ", StringFormat("%G", currentPositionTpPrice), " will not be changed!"
         );
         tpPrice = currentPositionTpPrice;
        }

      if( //-- Check if specified SL price is valid
         sl > 0 &&
         (
            newSlPrice >= positionPriceCurrent ||
            entryPrice - newSlPrice < symbolStopLevel * symbolPoint ||
            positionPriceCurrent - newSlPrice < symbolStopLevel * symbolPoint
         )
      )
        {
         //-- Specified SL price is invalid, don't modify the SL
         Print(
            "Specified proposed ", positionSymbol,
            " SL Price at ", newSlPrice,
            " is invalid since current ", positionSymbol, " price is at ", positionPriceCurrent,
            "\r\nCurrent SL at ", StringFormat("%G", currentPositionSlPrice), " will not be changed!"
         );
         slPrice = currentPositionSlPrice;
        }
     }
   if(positionType == POSITION_TYPE_SELL)
     {
      //-- calculate the new sl and tp prices
      newTpPrice = 0.0;
      newSlPrice = 0.0;
      if(tp > 0)
        {
         newTpPrice = entryPrice - (tp * symbolPoint);
        }
      if(sl > 0)
        {
         newSlPrice = entryPrice + (sl * symbolPoint);
        }

      //-- save the new sl and tp prices incase they don't change afte validation below
      tpPrice = newTpPrice;
      slPrice = newSlPrice;

      if( //-- Check if specified TP price is valid
         tp > 0 &&
         (
            newTpPrice >= entryPrice - (spread * symbolPoint) ||
            newTpPrice >= positionPriceCurrent ||
            (
               entryPrice - newTpPrice < symbolStopLevel * symbolPoint ||
               (positionPriceCurrent < entryPrice && positionPriceCurrent - newTpPrice < symbolStopLevel * symbolPoint)
            )
         )
      )
        {
         //-- Specified TP price is invalid, don't modify the TP
         Print(
            "Specified proposed ", positionSymbol,
            " TP Price at ", newTpPrice,
            " is invalid since current ", positionSymbol, " price is at ", positionPriceCurrent,
            "\r\nCurrent TP at ", StringFormat("%G", currentPositionTpPrice), " will not be changed!"
         );
         tpPrice = currentPositionTpPrice;
        }

      if( //-- Check if specified SL price is valid
         sl > 0 &&
         (
            newSlPrice <= positionPriceCurrent ||
            newSlPrice - entryPrice < symbolStopLevel * symbolPoint ||
            newSlPrice - positionPriceCurrent < symbolStopLevel * symbolPoint
         )
      )
        {
         //-- Specified SL price is invalid, don't modify the SL
         Print(
            "Specified proposed ", positionSymbol,
            " SL Price at ", newSlPrice,
            " is invalid since current ", positionSymbol, " price is at ", positionPriceCurrent,
            "\r\nCurrent SL at ", StringFormat("%G", currentPositionSlPrice), " will not be changed!"
         );
         slPrice = currentPositionSlPrice;
        }
     }

//-- Print verified position properties before modification
   positionProperties = "---\r\n";
   positionProperties += "--> Validated and Confirmed SL and TP: <--\r\n";
   positionProperties += "Price Open: " + StringFormat("%G", entryPrice) + ", Price Current: " + StringFormat("%G", positionPriceCurrent) + "\r\n";
   positionProperties += "Current SL: " + StringFormat("%G", currentPositionSlPrice) + "   -> New SL: " + (string)slPrice + "\r\n";
   positionProperties += "Current TP: " + StringFormat("%G", currentPositionTpPrice) + "   -> New TP: " + (string)tpPrice + "\r\n";
   Print(positionProperties);

これでSLとTPの値が検証できたので、次は取引サーバーにリクエストを送り、値を変更します。ZeroMemory()関数を使用して、tradeRequest およびtradeResult構造体をクリアし、以前の操作から残存データがないことを確認します。次に、tradeRequest構造体を以下の情報で初期化します。

  • action:ストップロスとテイクプロフィットを変更する場合はTRADE_ACTION_SLTPに設定
  • position:作業中のポジションを指定するためにpositionTicketに設定
  • symbol:このポジションの銘柄を識別するためにpositionSymbolに設定
  • sl:有効なストップロス値を含むslPriceに設定
  • tp:有効なテイクプロフィット値を含むtpPriceに設定

次にResetLastError()関数を呼び出して、内部に保存されている以前のエラーコードをクリアします。これにより、注文送信プロセスで正確なエラーコードを取得することができます。

//-- reset the the tradeRequest and tradeResult values by zeroing them
   ZeroMemory(tradeRequest);
   ZeroMemory(tradeResult);

取引サーバーに注文を送信する準備が整いました。しかし、経験上、一時的なネットワークの問題やサーバーの過負荷により、注文の実行が失敗することがあります。つまり、再試行を伴う注文の送信をスマートに処理する方法を見つける必要があります。これを解決するために、101回(loop <= 100)まで反復するforループを使用します。この再試行メカニズムは、注文実行中に起こりうる一時的なエラーに対処するのに役立ちます。

forループの内部では、OrderSend()を使用して、tradeRequestに含まれる注文リクエストを送信し、結果をtradeResultに格納します。OrderSend()がtrueを返した場合、SLおよびTP価格が正常に変更され、注文リクエストが問題なく完了したことを示します。

また、tradeResult.retcodeで、このポジションのSL/TP変更が成功したことを示す特定のコード(10008または10009)を確認することで、最終確認をおこないます。コードが一致すれば、ポジションチケット、銘柄、リターンコードなどの詳細が記載された確認メッセージが出力されます。次に、return(true)を使用して関数を正常に終了させます。break文がループを抜けるのは、不必要な繰り返しを避けるためにforループを抜けることを完全に確認するためです。OrderSend()falseを返すか、retcodeが成功コードと一致しない場合は、エラーを示します。

//-- initialize the parameters to set the sltp
   tradeRequest.action = TRADE_ACTION_SLTP; //-- Trade operation type for setting sl and tp
   tradeRequest.position = positionTicket;
   tradeRequest.symbol = positionSymbol;
   tradeRequest.sl = slPrice;
   tradeRequest.tp = tpPrice;

   ResetLastError(); //--- reset error cache so that we get an accurate runtime error code in the ErrorAdvisor function

   for(int loop = 0; loop <= 100; loop++) //-- try modifying the sl and tp 101 times untill the request is successful
     {
      //--- send order to the trade server
      if(OrderSend(tradeRequest, tradeResult))
        {
         //-- Confirm order execution
         if(tradeResult.retcode == 10008 || tradeResult.retcode == 10009)
           {
            PrintFormat("Successfully modified SLTP for #%I64d %s %s", positionTicket, positionSymbol, EnumToString(positionType));
            PrintFormat("retcode=%u  runtime_code=%u", tradeResult.retcode, GetLastError());
            Print("_______________________________________________________________________________________\r\n\r\n");
            return(true); //-- exit function
            break; //--- success - order placed ok. exit for loop
           }
        }
      else  //-- Order request failed
        {
         //-- order not sent or critical error found
         if(!ErrorAdvisor(__FUNCTION__, positionSymbol, tradeResult.retcode) || IsStopped())
           {
            PrintFormat("ERROR modified SLTP for #%I64d %s %s", positionTicket, positionSymbol, EnumToString(positionType));
            Print("_______________________________________________________________________________________\r\n\r\n");
            return(false); //-- exit function
            break; //-- exit for loop
           }
        }
     }

以下は、SetSlTpByTicket()関数のすべてのコードセグメントとその適切なシーケンスです。関数が以下のコードのすべての構成要素を持っていることを確認してください。

bool SetSlTpByTicket(ulong positionTicket, int sl, int tp) export
  {
//-- first check if the EA is allowed to trade
   if(!TradingIsAllowed())
     {
      return(false); //--- algo trading is disabled, exit function
     }

//--- Confirm and select the position using the provided positionTicket
   ResetLastError(); //--- Reset error cache incase of ticket selection errors
   if(PositionSelectByTicket(positionTicket))
     {
      //---Position selected
      Print("\r\n_______________________________________________________________________________________");
      Print(__FUNCTION__, ": Position with ticket:", positionTicket, " selected and ready to set SLTP.");
     }
   else
     {
      Print("\r\n_______________________________________________________________________________________");
      Print(__FUNCTION__, ": Selecting position with ticket:", positionTicket, " failed. ERROR: ", GetLastError());
      return(false); //-- Exit the function
     }

//-- create variables to store the calculated tp and sl prices to send to the trade server
   double tpPrice = 0.0, slPrice = 0.0;
   double newTpPrice = 0.0, newSlPrice = 0.0;

//--- Position ticket selected, save the position properties
   string positionSymbol = PositionGetString(POSITION_SYMBOL);
   double entryPrice = PositionGetDouble(POSITION_PRICE_OPEN);
   double volume = PositionGetDouble(POSITION_VOLUME);
   double currentPositionSlPrice = PositionGetDouble(POSITION_SL);
   double currentPositionTpPrice = PositionGetDouble(POSITION_TP);
   ENUM_POSITION_TYPE positionType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);

//-- Get some information about the positions symbol
   int symbolDigits = (int)SymbolInfoInteger(positionSymbol, SYMBOL_DIGITS); //-- Number of symbol decimal places
   int symbolStopLevel = (int)SymbolInfoInteger(positionSymbol, SYMBOL_TRADE_STOPS_LEVEL);
   double symbolPoint = SymbolInfoDouble(positionSymbol, SYMBOL_POINT);
   double positionPriceCurrent = PositionGetDouble(POSITION_PRICE_CURRENT);
   int spread = (int)SymbolInfoInteger(positionSymbol, SYMBOL_SPREAD);

//--Save the non-validated tp and sl prices
   if(positionType == POSITION_TYPE_BUY) //-- Calculate and store the non-validated sl and tp prices
     {
      newSlPrice = entryPrice - (sl * symbolPoint);
      newTpPrice = entryPrice + (tp * symbolPoint);
     }
   else  //-- SELL POSITION
     {
      newSlPrice = entryPrice + (sl * symbolPoint);
      newTpPrice = entryPrice - (tp * symbolPoint);
     }

//-- Print position properties before modification
   string positionProperties = "--> "  + positionSymbol + " " + EnumToString(positionType) + " SLTP Modification Details" +
   " <--\r\n";
   positionProperties += "------------------------------------------------------------\r\n";
   positionProperties += "Ticket: " + (string)positionTicket + "\r\n";
   positionProperties += "Volume: " + StringFormat("%G", volume) + "\r\n";
   positionProperties += "Price Open: " + StringFormat("%G", entryPrice) + "\r\n";
   positionProperties += "Current SL: " + StringFormat("%G", currentPositionSlPrice) + "   -> New Proposed SL: " + (string)newSlPrice + "\r\n";
   positionProperties += "Current TP: " + StringFormat("%G", currentPositionTpPrice) + "   -> New Proposed TP: " + (string)newTpPrice + "\r\n";
   positionProperties += "Comment: " + PositionGetString(POSITION_COMMENT) + "\r\n";
   positionProperties += "Magic Number: " + (string)PositionGetInteger(POSITION_MAGIC) + "\r\n";
   positionProperties += "---";
   Print(positionProperties);

//-- validate the sl and tp to a proper double that can be used in the OrderSend() function
   if(sl == 0)
     {
      slPrice = 0.0;
     }
   if(tp == 0)
     {
      tpPrice = 0.0;
     }

//--- Check if the sl and tp are valid in relation to the current price and set the tpPrice
   if(positionType == POSITION_TYPE_BUY)
     {
      //-- calculate the new sl and tp prices
      newTpPrice = 0.0;
      newSlPrice = 0.0;
      if(tp > 0)
        {
         newTpPrice = entryPrice + (tp * symbolPoint);
        }
      if(sl > 0)
        {
         newSlPrice = entryPrice - (sl * symbolPoint);
        }

      //-- save the new sl and tp prices incase they don't change afte validation below
      tpPrice = newTpPrice;
      slPrice = newSlPrice;

      if( //-- Check if specified TP is valid
         tp > 0 &&
         (
            newTpPrice <= entryPrice + (spread * symbolPoint) ||
            newTpPrice <= positionPriceCurrent ||
            (
               newTpPrice - entryPrice < symbolStopLevel * symbolPoint ||
               (positionPriceCurrent > entryPrice && newTpPrice - positionPriceCurrent < symbolStopLevel * symbolPoint)
            )
         )
      )
        {
         //-- Specified TP price is invalid, don't modify the TP
         Print(
            "Specified proposed ", positionSymbol,
            " TP Price at ", newTpPrice,
            " is invalid since current ", positionSymbol, " price is at ", positionPriceCurrent,
            "\r\nCurrent TP at ", StringFormat("%G", currentPositionTpPrice), " will not be changed!"
         );
         tpPrice = currentPositionTpPrice;
        }

      if( //-- Check if specified SL price is valid
         sl > 0 &&
         (
            newSlPrice >= positionPriceCurrent ||
            entryPrice - newSlPrice < symbolStopLevel * symbolPoint ||
            positionPriceCurrent - newSlPrice < symbolStopLevel * symbolPoint
         )
      )
        {
         //-- Specified SL price is invalid, don't modify the SL
         Print(
            "Specified proposed ", positionSymbol,
            " SL Price at ", newSlPrice,
            " is invalid since current ", positionSymbol, " price is at ", positionPriceCurrent,
            "\r\nCurrent SL at ", StringFormat("%G", currentPositionSlPrice), " will not be changed!"
         );
         slPrice = currentPositionSlPrice;
        }
     }
   if(positionType == POSITION_TYPE_SELL)
     {
      //-- calculate the new sl and tp prices
      newTpPrice = 0.0;
      newSlPrice = 0.0;
      if(tp > 0)
        {
         newTpPrice = entryPrice - (tp * symbolPoint);
        }
      if(sl > 0)
        {
         newSlPrice = entryPrice + (sl * symbolPoint);
        }

      //-- save the new sl and tp prices incase they don't change afte validation below
      tpPrice = newTpPrice;
      slPrice = newSlPrice;

      if( //-- Check if specified TP price is valid
         tp > 0 &&
         (
            newTpPrice >= entryPrice - (spread * symbolPoint) ||
            newTpPrice >= positionPriceCurrent ||
            (
               entryPrice - newTpPrice < symbolStopLevel * symbolPoint ||
               (positionPriceCurrent < entryPrice && positionPriceCurrent - newTpPrice < symbolStopLevel * symbolPoint)
            )
         )
      )
        {
         //-- Specified TP price is invalid, don't modify the TP
         Print(
            "Specified proposed ", positionSymbol,
            " TP Price at ", newTpPrice,
            " is invalid since current ", positionSymbol, " price is at ", positionPriceCurrent,
            "\r\nCurrent TP at ", StringFormat("%G", currentPositionTpPrice), " will not be changed!"
         );
         tpPrice = currentPositionTpPrice;
        }

      if( //-- Check if specified SL price is valid
         sl > 0 &&
         (
            newSlPrice <= positionPriceCurrent ||
            newSlPrice - entryPrice < symbolStopLevel * symbolPoint ||
            newSlPrice - positionPriceCurrent < symbolStopLevel * symbolPoint
         )
      )
        {
         //-- Specified SL price is invalid, don't modify the SL
         Print(
            "Specified proposed ", positionSymbol,
            " SL Price at ", newSlPrice,
            " is invalid since current ", positionSymbol, " price is at ", positionPriceCurrent,
            "\r\nCurrent SL at ", StringFormat("%G", currentPositionSlPrice), " will not be changed!"
         );
         slPrice = currentPositionSlPrice;
        }
     }

//-- Print verified position properties before modification
   positionProperties = "---\r\n";
   positionProperties += "--> Validated and Confirmed SL and TP: <--\r\n";
   positionProperties += "Price Open: " + StringFormat("%G", entryPrice) + ", Price Current: " + StringFormat("%G", positionPriceCurrent) + "\r\n";
   positionProperties += "Current SL: " + StringFormat("%G", currentPositionSlPrice) + "   -> New SL: " + (string)slPrice + "\r\n";
   positionProperties += "Current TP: " + StringFormat("%G", currentPositionTpPrice) + "   -> New TP: " + (string)tpPrice + "\r\n";
   Print(positionProperties);

//-- reset the the tradeRequest and tradeResult values by zeroing them
   ZeroMemory(tradeRequest);
   ZeroMemory(tradeResult);

//-- initialize the parameters to set the sltp
   tradeRequest.action = TRADE_ACTION_SLTP; //-- Trade operation type for setting sl and tp
   tradeRequest.position = positionTicket;
   tradeRequest.symbol = positionSymbol;
   tradeRequest.sl = slPrice;
   tradeRequest.tp = tpPrice;

   ResetLastError(); //--- reset error cache so that we get an accurate runtime error code in the ErrorAdvisor function

   for(int loop = 0; loop <= 100; loop++) //-- try modifying the sl and tp 101 times untill the request is successful
     {
      //--- send order to the trade server
      if(OrderSend(tradeRequest, tradeResult))
        {
         //-- Confirm order execution
         if(tradeResult.retcode == 10008 || tradeResult.retcode == 10009)
           {
            PrintFormat("Successfully modified SLTP for #%I64d %s %s", positionTicket, positionSymbol, EnumToString(positionType));
            PrintFormat("retcode=%u  runtime_code=%u", tradeResult.retcode, GetLastError());
            Print("_______________________________________________________________________________________\r\n\r\n");
            return(true); //-- exit function
            break; //--- success - order placed ok. exit for loop
           }
        }
      else  //-- Order request failed
        {
         //-- order not sent or critical error found
         if(!ErrorAdvisor(__FUNCTION__, positionSymbol, tradeResult.retcode) || IsStopped())
           {
            PrintFormat("ERROR modified SLTP for #%I64d %s %s", positionTicket, positionSymbol, EnumToString(positionType));
            Print("_______________________________________________________________________________________\r\n\r\n");
            return(false); //-- exit function
            break; //-- exit for loop
           }
        }
     }
   return(false);
  }


ポジションクローズ関数


この関数はClosePositionByTicket()と呼ばれ、チケット番号に基づいてポジションを効果的にクローズできるよう、構造化されたアプローチを使用します。引数にポジションのチケット番号を取ります。取引が許可されているかどうかを確認し、提供されたチケットを使用してポジションを選択し、そのプロパティを取得してシュル直し、取引リクエストを準備し、発生したエラーを処理しながらポジションを決済しようとします。

まず、関数を定義し、ブール値(trueまたはfalse)を返し、引数として1つのパラメータを取ることを指定します。

bool ClosePositionByTicket(ulong positionTicket) export
  {
//--- Function body
  }

次に、EAの取引が許可されているかどうかを確認します。

//-- first check if the EA is allowed to trade
   if(!TradingIsAllowed())
     {
      return(false); //--- algo trading is disabled, exit function
     }

次に、ResetLastError()関数を使用して以前のエラーをリセットし、提供されたチケット番号を使用してポジションを選択します。ポジションが選択された場合は、選択を確認するメッセージを表示し、選択が失敗した場合は、エラーメッセージを表示し、falseを返して関数を終了します。

//--- Confirm and select the position using the provided positionTicket
   ResetLastError(); //--- Reset error cache incase of ticket selection errors
   if(PositionSelectByTicket(positionTicket))
     {
      //---Position selected
      Print("...........................................................................................");
      Print(__FUNCTION__, ": Position with ticket:", positionTicket, " selected and ready to be closed.");
     }
   else
     {
      Print("...........................................................................................");
      Print(__FUNCTION__, ": Selecting position with ticket:", positionTicket, " failed. ERROR: ", GetLastError());
      return(false); //-- Exit the function
     }

ポジションが選択されたら、そのプロパティを保存して出力します。

//--- Position ticket selected, save the position properties
   string positionSymbol = PositionGetString(POSITION_SYMBOL);
   double positionVolume = PositionGetDouble(POSITION_VOLUME);
   ENUM_POSITION_TYPE positionType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);

//-- Print position properties before closing it
   string positionProperties;
   positionProperties += "-- "  + positionSymbol + " " + EnumToString(positionType) + " Details" +
   " -------------------------------------------------------------\r\n";
   positionProperties += "Ticket: " + (string)positionTicket + "\r\n";
   positionProperties += "Volume: " + StringFormat("%G", PositionGetDouble(POSITION_VOLUME)) + "\r\n";
   positionProperties += "Price Open: " + StringFormat("%G", PositionGetDouble(POSITION_PRICE_OPEN)) + "\r\n";
   positionProperties += "SL: " + StringFormat("%G", PositionGetDouble(POSITION_SL)) + "\r\n";
   positionProperties += "TP: " + StringFormat("%G", PositionGetDouble(POSITION_TP)) + "\r\n";
   positionProperties += "Comment: " + PositionGetString(POSITION_COMMENT) + "\r\n";
   positionProperties += "Magic Number: " + (string)PositionGetInteger(POSITION_MAGIC) + "\r\n";
   positionProperties += "_______________________________________________________________________________________";
   Print(positionProperties);

次に、ZeroMemory()関数を使用してtradeRequesttradeResultデータ構造体の値をリセットし、以前のデータをクリアします。次に、取引終了操作を示す取引アクションをTRADE_ACTION_DEALに設定し、ポジションチケット、銘柄、出来高、価格偏差を設定して、ポジションをクローズするための取引リクエストパラメータを初期化します。

//-- reset the the tradeRequest and tradeResult values by zeroing them
   ZeroMemory(tradeRequest);
   ZeroMemory(tradeResult);

//-- initialize the trade reqiest parameters to close the position
   tradeRequest.action = TRADE_ACTION_DEAL; //-- Trade operation type for closing a position
   tradeRequest.position = positionTicket;
   tradeRequest.symbol = positionSymbol;
   tradeRequest.volume = positionVolume;
   tradeRequest.deviation = SymbolInfoInteger(positionSymbol, SYMBOL_SPREAD) * 2;

ポジションが買いか売りかに基づいて、ポジションの終値と注文タイプを決定する必要があります。

//--- Set the price and order type of the position being closed
   if(positionType == POSITION_TYPE_BUY)
     {
      tradeRequest.price = SymbolInfoDouble(positionSymbol, SYMBOL_BID);
      tradeRequest.type = ORDER_TYPE_SELL;
     }
   else//--- For sell type positions
     {
      tradeRequest.price = SymbolInfoDouble(positionSymbol, SYMBOL_ASK);
      tradeRequest.type = ORDER_TYPE_BUY;
     }

最後に、ResetLastError()関数を使用して以前のエラーをリセットし、取引リクエストを送信してポジションを決済しようとします。インターネット接続が弱い場合や重大でないエラーが発生した場合でもポジションがクローズされるように、forループを使用してポジションのクローズリクエストを複数回送信してみます。注文が正常に送信され、実行された場合(リターンコード10008または10009)、成功メッセージを表示し、trueを返します。注文が失敗した場合、エラーを処理するためにErrorAdvisor()関数を呼び出します。ErrorAdvisor()関数が重大エラーを示すか、EAが停止した場合、エラーメッセージを表示し、ポジション決済が失敗したことを示すfalseを返します。

ResetLastError(); //--- reset error cache so that we get an accurate runtime error code in the ErrorAdvisor function

   for(int loop = 0; loop <= 100; loop++) //-- try closing the position 101 times untill the request is successful
     {
      //--- send order to the trade server
      if(OrderSend(tradeRequest, tradeResult))
        {
         //-- Confirm order execution
         if(tradeResult.retcode == 10008 || tradeResult.retcode == 10009)
           {
            Print(__FUNCTION__, "_________________________________________________________________________");
            PrintFormat("Successfully closed position #%I64d %s %s", positionTicket, positionSymbol, EnumToString(positionType));
            PrintFormat("retcode=%u  runtime_code=%u", tradeResult.retcode, GetLastError());
            Print("_______________________________________________________________________________________");
            return(true); //-- exit function
            break; //--- success - order placed ok. exit for loop
           }
        }
      else  //-- position closing request failed
        {
         //-- order not sent or critical error found
         if(!ErrorAdvisor(__FUNCTION__, positionSymbol, tradeResult.retcode) || IsStopped())
           {
            Print(__FUNCTION__, "_________________________________________________________________________");
            PrintFormat("ERROR closing position #%I64d %s %s", positionTicket, positionSymbol, EnumToString(positionType));
            Print("_______________________________________________________________________________________");
            return(false); //-- exit function
            break; //-- exit for loop
           }
        }
     }

上記のすべてのコードセグメントを以下の順序で並べるようにしてください。以下は、ClosePositionByTicket()関数のコードセグメントを適切な順番で並べたものです。

bool ClosePositionByTicket(ulong positionTicket) export
  {
//-- first check if the EA is allowed to trade
   if(!TradingIsAllowed())
     {
      return(false); //--- algo trading is disabled, exit function
     }

//--- Confirm and select the position using the provided positionTicket
   ResetLastError(); //--- Reset error cache incase of ticket selection errors
   if(PositionSelectByTicket(positionTicket))
     {
      //---Position selected
      Print("...........................................................................................");
      Print(__FUNCTION__, ": Position with ticket:", positionTicket, " selected and ready to be closed.");
     }
   else
     {
      Print("...........................................................................................");
      Print(__FUNCTION__, ": Selecting position with ticket:", positionTicket, " failed. ERROR: ", GetLastError());
      return(false); //-- Exit the function
     }

//--- Position ticket selected, save the position properties
   string positionSymbol = PositionGetString(POSITION_SYMBOL);
   double positionVolume = PositionGetDouble(POSITION_VOLUME);
   ENUM_POSITION_TYPE positionType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);

//-- Print position properties before closing it
   string positionProperties;
   positionProperties += "-- "  + positionSymbol + " " + EnumToString(positionType) + " Details" +
   " -------------------------------------------------------------\r\n";
   positionProperties += "Ticket: " + (string)positionTicket + "\r\n";
   positionProperties += "Volume: " + StringFormat("%G", PositionGetDouble(POSITION_VOLUME)) + "\r\n";
   positionProperties += "Price Open: " + StringFormat("%G", PositionGetDouble(POSITION_PRICE_OPEN)) + "\r\n";
   positionProperties += "SL: " + StringFormat("%G", PositionGetDouble(POSITION_SL)) + "\r\n";
   positionProperties += "TP: " + StringFormat("%G", PositionGetDouble(POSITION_TP)) + "\r\n";
   positionProperties += "Comment: " + PositionGetString(POSITION_COMMENT) + "\r\n";
   positionProperties += "Magic Number: " + (string)PositionGetInteger(POSITION_MAGIC) + "\r\n";
   positionProperties += "_______________________________________________________________________________________";
   Print(positionProperties);

//-- reset the the tradeRequest and tradeResult values by zeroing them
   ZeroMemory(tradeRequest);
   ZeroMemory(tradeResult);

//-- initialize the trade reqiest parameters to close the position
   tradeRequest.action = TRADE_ACTION_DEAL; //-- Trade operation type for closing a position
   tradeRequest.position = positionTicket;
   tradeRequest.symbol = positionSymbol;
   tradeRequest.volume = positionVolume;
   tradeRequest.deviation = SymbolInfoInteger(positionSymbol, SYMBOL_SPREAD) * 2;

//--- Set the price and order type of the position being closed
   if(positionType == POSITION_TYPE_BUY)
     {
      tradeRequest.price = SymbolInfoDouble(positionSymbol, SYMBOL_BID);
      tradeRequest.type = ORDER_TYPE_SELL;
     }
   else//--- For sell type positions
     {
      tradeRequest.price = SymbolInfoDouble(positionSymbol, SYMBOL_ASK);
      tradeRequest.type = ORDER_TYPE_BUY;
     }

   ResetLastError(); //--- reset error cache so that we get an accurate runtime error code in the ErrorAdvisor function

   for(int loop = 0; loop <= 100; loop++) //-- try closing the position 101 times untill the request is successful
     {
      //--- send order to the trade server
      if(OrderSend(tradeRequest, tradeResult))
        {
         //-- Confirm order execution
         if(tradeResult.retcode == 10008 || tradeResult.retcode == 10009)
           {
            Print(__FUNCTION__, "_________________________________________________________________________");
            PrintFormat("Successfully closed position #%I64d %s %s", positionTicket, positionSymbol, EnumToString(positionType));
            PrintFormat("retcode=%u  runtime_code=%u", tradeResult.retcode, GetLastError());
            Print("_______________________________________________________________________________________");
            return(true); //-- exit function
            break; //--- success - order placed ok. exit for loop
           }
        }
      else  //-- position closing request failed
        {
         //-- order not sent or critical error found
         if(!ErrorAdvisor(__FUNCTION__, positionSymbol, tradeResult.retcode) || IsStopped())
           {
            Print(__FUNCTION__, "_________________________________________________________________________");
            PrintFormat("ERROR closing position #%I64d %s %s", positionTicket, positionSymbol, EnumToString(positionType));
            Print("_______________________________________________________________________________________");
            return(false); //-- exit function
            break; //-- exit for loop
           }
        }
     }
   return(false);
  }

ライブラリのPositionsManager.mq5ソースコードファイルを保存してコンパイルすると、ライブラリを保存したLibraries\Toolkit\フォルダに新しいPositionsManager.ex5ライブラリファイルが生成されます。


結論

ここまでで、MQL5 ex5ライブラリとその作成プロセスについてしっかりと理解していただけたと思います。次回は、様々なポジション管理タスクのための追加機能でポジション管理ライブラリを拡張し、様々な実践的な例でMQL5プロジェクトにex5ライブラリを実装する方法を示します。上記で作成したすべての関数を含むライブラリのソースコードPositionsManager.mq5を、この記事の下に添付しておきます。


MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/14822

添付されたファイル |
データサイエンスと機械学習(第23回):LightGBMとXGBoostが多くのAIモデルを凌駕する理由 データサイエンスと機械学習(第23回):LightGBMとXGBoostが多くのAIモデルを凌駕する理由
これらの高度な勾配ブースティング決定木(GBDT)技術は、優れた性能と柔軟性を提供し、金融モデリングやアルゴリズム取引に最適です。これらのツールを活用して取引戦略を最適化し、予測精度を高め、金融市場での競争力を高める方法を学びましょう。
独自のLLMをEAに統合する(第3部):CPUを使った独自のLLMの訓練 独自のLLMをEAに統合する(第3部):CPUを使った独自のLLMの訓練
今日の人工知能の急速な発展に伴い、言語モデル(LLM)は人工知能の重要な部分となっています。私たちは、強力なLLMをアルゴリズム取引に統合する方法を考える必要があります。ほとんどの人にとって、これらの強力なモデルをニーズに応じて微調整し、ローカルに展開して、アルゴリズム取引に適用することは困難です。本連載では、この目標を達成するために段階的なアプローチをとっていきます。
ビル・ウィリアムズ戦略:他の指標と予測の有無による比較 ビル・ウィリアムズ戦略:他の指標と予測の有無による比較
この記事では、ビル・ウィリアムズの有名な戦略の1つを取り上げ、それについて議論し、他の指標や予測を用いて戦略の改善を試みます。
古典的戦略の再構築:原油 古典的戦略の再構築:原油
この記事では、教師あり機械学習アルゴリズムを活用することで、古典的な原油取引戦略を強化することを目的として、原油取引戦略を再検討します。ブレント原油価格とWTI原油価格のスプレッドに基づいて、将来のブレント原油価格を予測する最小二乗モデルを構築します。目標は、将来のブレント価格変動の先行指標を特定することです。
OSZAR »