HedgeTerminalAPIを利用して MetaTrader 5 で双方向トレードとポジションヘッジを行う - パート2

Vasiliy Sokolov | 25 12月, 2015

目次


はじめに

本稿は『HedgeTerminalパネルを利用して MetaTrader 5 で双方向トレードとポジションヘッジを行う-パート1』の続編です。パート2では Expert Advisors とその他 MQL5 プログラムのHedgeTerminalAPIライブラリとの統合についてお話します。本稿を読んでライブラリとの連携方法を学習してください。快適でシンプルなトレード環境で作業と同時に双方向トレード Expert Advisors の作成をするのに役立ちます。

ライブラリの解説以外にも本稿は非同期的トレードとマルチスレッドプログラミングについても触れています。 その解説は本稿の第3節、第4節にあります。本稿は双方向トレードに関心がなくても非同期的、マルチスレッドプログラミングに関して何か新しい情報を見つけたいトレーダーにとって有用なものとなるでしょう。

以下に提供している資料は MQL5 プログラム言語を理解している経験豊かなアルゴリズムトレーダー向けです。MQL5 言語をご存じない場合は、シンプルな図や図面でライブラリおよびHedgeTerminalパネルの一般原則を説明している本稿のパート1をご一読ください。


第1章 Expert Advisors のHedgeTerminalおよびそのパネルとの連携


1.1. HedgeTerminalAPI のインストールライブラリの初期起動

HedgeTerminal API のインストール手順は HT ビジュアルパネルの手順とは異なります。それはライブラリが MetaTrader 5 内で単独で実行できないためです。その代わり、ライブラリから HedgeTerminalInstall() 関数を呼ぶために特別な Expert Advisor を作成する必要があります。この関数は HedgeTerminalAPI で利用可能な関数を記述する特別なヘッダファイル Prototypes.mqh を設定します。

ライブラリは 3 段階の手順でコンピュータにインストールされます

ステップ1 お手持ちのコンピュータに HedgeTerminalAPI ライブラリをダウンロードします。お手持ちのターミナルに関連するライブラリの場所は以下です。 \MQL5\Experts\Market\HedgeTerminalApi.ex5

ステップ2 標準テンプレートを用いて MQL5 ウィザード内に新しい Expert Advisor を作成します。MQL5 ウィザードは次のソースコードを生成します。

//+------------------------------------------------------------------+
//|                                   InstallHedgeTerminalExpert.mq5 |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2015, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
  }

ステップ3 作成した Expert Advisor から、関数ひとつ-OnInit()と、HedgeTerminalApi ライブラリによってエクスポートされる特殊なインストーラ関数 HedgeTerminalInstall() を記述するエクスポート命令が必要です。この関数を OnInit() 関数内ですぐに実行します。黄色でマークされたソースコードがこの処理を行います。

//+------------------------------------------------------------------+
//|                                   InstallHedgeTerminalExpert.mq5 |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2015, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"

#import "HedgeTerminalAPI.ex5"
   void HedgeTerminalInstall(void);
#import

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   HedgeTerminalInstall();
   ExpertRemove();   
//---
   return(INIT_SUCCEEDED);
  }

ステップ4 今後の処理はライブラリを購入したかどうかによります。購入済みであれば、チャート上で直接リアルタイムで Expert Advisor を実行することができます。これは HedgeTerminal のプロダクツライン全体の標準インストーラを起動します。記事『HedgeTerminalパネルを利用して MetaTrader 5 で双方向トレードとポジションヘッジを行う-パート1』の2.1項および 2.2 項に記載のある指示に従えばこのステップは簡単に完了することができます。インストールウィザードはヘッダファイルとお手持ちのコンピュータの検証 Expert Advisor を含みすべて必要なファイルをインストールします。

ライブラリ未購入で検証のみ希望される場合は、リアルタイムでの EA 処理はできませんが、ストラテジーテスタで EA を実行することで API の検証が可能です。この場合、インストーラは実行されません。検証モードでは HedgeTermianalAPI は単一ユーザーモードで動作するため、通常モードでインストールされるファイルは必要ありません。ということはその他何も構成する必要はないということです。

EA 検証が終わり次第、ターミナルの共有フォルダ内にフォルダ \HedgeTerminal が作成されます。MetaTrader ターミナルの共有ディレクトリへの通常のパスは以下です。c:\Users\<Username>\AppData\Roaming\MetaQuotes\Terminal\Common\Files\HedgeTerminal\。ここで<Username> とはみなさんの現在のコンピュータアカウント名を言います。\HedgeTerminal フォルダにはすでにファイル \MQL5\Include\Prototypes.mqh および \MQL5\Experts\Chaos2.mq5 があります。ターミナルの同じディレクトリ内にこの2件のファイルを次の要領でコピーします。ファイルPrototypes.mqh\MetaTrader5\MQL5\Includeへ、そしてファイル Chaos2.mq5\MetaTrader5\MQL5\Expertsへ。

ファイル Prototypes.mqh は HedgeTerminalAPI ライブラリからエクスポートされた関数の記述を持つヘッダファイルです。その目的と記述はそこのコメントにあります。

ファイル Chaos2.mq5 には 『Chaos II EA 例による SendTradeRequest 関数と HedgeTradeRequest ストラクチャ』節に説明のあるサンプル EA が入っています。このように HedgeTerminalAPI の動作の仕方、HedgeTerminal の仮想化技術を利用して Expert Advisor を作成する方法を視覚的に確認することができます。

コピーされたファイルはお手持ちの EA に対して利用可能です。よってライブラリの使用を開始するには Expert Advisor のソースコードにあるヘッダファイルをインクルードするだけです。以下が一例です。

#include <Prototypes.mqh>
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   int transTotal = TransactionsTotal();
   printf((string)transTotal);
  }

たとえば、上記のコードはアクティブなポジションの合計数量を取得し MetaTrader 5 ターミナルの『エキスパート』タブにその数量を表示します。

HedgeTerminal は実際関数のひとつが最初に呼びだされるとき初期化されることを知ることは重要です。この初期化は遅延と呼ばれます。よって関数のひとつの最初の呼び出しには長い時間がかかる可能性があります。初回実行中に迅速な応答を求める場合には、事前に HT を初期化する必要があります。たとえば OnInit() ブロック内でTransactionTotal() 関数を呼びだすことができます。

遅延初期化では Expert Advisor の明示的な初期化を省略することができます。これは HedgeTerminal との連携を大幅にシンプル化し、事前構成を不要にします。


1.2. Expert Advisors のHedgeTerminalパネルへの統合

リアルタイムで実行可能な HedgeTerminal ビジュアルパネルとフル装備のライブラリバージョンを所有しているなら、お手持ちの Expert Advisors をパネルに統合し、行われるすべてのトレード処理がパネルに表示されるようにできます。一般的に統合は表示されません。HedgeTermianalAPI 関数を使用するなら、ロボットによる処理は自動的にパネルに表示されます。ただし行われるトランザクションのすべてに EA 名を指定することで可視性を拡げることができます。それはファイル Settings.xml 内の以下の行でコメントを解除することで行えます。

<Column ID="Magic" Name="Magic" Width="100"/>

このタグはセクション <Active-Position> および <History-Position>にあります。

これでコメントが消去されタグが処理に含まれました。パネルの再起動後、アクティブポジション、履歴ポジションの表に新規の列『マジック』が表示されます。この列には Expert Advisor のマジックナンバーがあり、それにポジションが属しています。

マジックナンバーの代わりに EA 名を表示したい場合は、エイリアスファイル ExpertAliases.xml に名前を追加します。たとえば、EA のマジックナンバーが 123847 で、その名前を "ExPro 1.1" のように表示したければ、以下のタグをファイルに追加します。

<Expert Magic="123847" Name="ExPro 1.1"></Expert>

これが正しく行われると、適切な行にマジックの代わりに EA 名が表示されます。

図1 マジックに代わり EA 名表示

図1 マジックに代わり EA 名表示

パネルと Expert Advisors はリアルタイムで通信することに注意が必要です。これは、パネルで直接 EA のポジションをクローズすると、EA は次に TransactionsTotal() 関数が呼ばれるときこのことを知るということです。また逆に Expert Advisor がポジションをクローズしたあとすぐにアクティブなポジションタブからそれが消えるのです。


1.3. HedgeTerminalAPI 処理の一般原則

双方向ポジションに加え、HedgeTerminal は未決注文、ディール、ブローカーの処理など他のトレードタイプにも動作します。HedgeTerminal はこういったタイプすべてを単一の トランザクショングループとして扱います。ディール、未決注文、双方向ポジションはすべてトランザクションです。ただトランザクションは単独で存在することはありません。オブジェクト指向プログラミングに関しては、トランザクションは抽象ベースクラスとして提示され、そこからディールや双方向ポジションなど可能なトレードエンティティがすべて継承されるのです。この点においてHedgeTerminalAPI の関数はすべて数種類にグループ分けすることができます。

  1. トランザクション検索および選択関数関数の一般的なシグネチャーとその動作はほとんど完全に MetaTrader 4 の関数 OrderSend() および OrderSelect() に一致します。
  2. 選択したトランザクションのプロパティを取得するための関数トランザクションはすべて特定のプロパティセットを持ち、プロパティ選択のための特定の関数を持ちます。関数の一般的なシグネチャーとその動作方法は、ポジション、ディール、オーダーへのアクセス方法において MetaTrader 5 のシステム関数(OrderGetDouble() または HistoryDealGetInteger() のような)に類似してしています。
  3. HedgeTerminalAPI はトレード関数をひとつだけ使用します。それは SendTradeRequest() です。この関数により双方向ポジションやその一部をクローズすることができます。おなじ関数がストップロス、テイクプロフィット、送信コメントの変更に使用されます。この関数処理は MetaTrader 5 における OrderSend() に似ています。
  4. 一般的エラーを取得する関数 GetHedgeError()、HedgeTerminal のトレードアクションの詳細分析を行う関数 TotalActionsTask() および GetActionResult()。またエラー検出にも使用されます。MetaTrader 4 または MetaTrader 5 には類似体はありません。

ほとんどすべての関数を処理することは MetaTrader 4 と MetaTrader 5 のシステム関数を使用することに似ています。通常、関数入力はある識別子(列挙値)で、関数はそれに対応する値を返します。

各関数に対しては指定の列挙が利用できます。以下は共通の呼び出しシグネチャーです。

<value> = Function(<identifier>);

ユニークなポジション識別子を取得する例を考察します。これは MetaTrader 5 における行記述です。

ulong id = PositionGetInteger(POSITION_IDENTIFIER);

HedgeTerminal では、双方向ポジションのユニークな識別子受け取りは以下のようになります。

ulong id = HedgePositionGetInteger(HEDGE_POSITION_ENTRY_ORDER_ID)

関数の一般原則は同様です。列挙タイプが異なるだけです。


1.4. トランザクションの選択

トランザクションの選択はトランザクションリスト内を検索することです。それは MetaTrader 4 でオーダーを検索するのと類似しています。ただ MetaTrader 4 ではオーダーのみ検索されるのに対し、HedgeTerminal では、未決注文やポジションヘッジなどなんでもトランザクションとして検索可能です。そのためトランザクションはすべてまず TransactionSelect() 関数を用いて検索され、それからそのタイプは TransactionType() 関数によって特定されます。

これまで2種類のトランザクションリストが利用可能となっています。それはアクティブなトランザクションリストと履歴上のトランザクションリストです。使用するリストは ENUM_MODE_TRADES モディファイアを基に決められます。これは MetaTrader 4 の MODE_TRADES モディファイアに似ています。

以下がトランザクション検索と選択のアルゴリズムです。

1: for(int i=TransactionsTotal(MODE_TRADES)-1; i>=0; i--)
2:     {
3:      if(!TransactionSelect(i,SELECT_BY_POS,MODE_TRADES))continue;
4:      if(TransactionType()!=TRANS_HEDGE_POSITION)continue;
5:      if(HedgePositionGetInteger(HEDGE_POSITION_MAGIC) != Magic)continue;
6:      if(HedgePositionGetString(HEDGE_POSITION_SYMBOL) != Symbol())continue;
7:      if(HedgePositionGetInteger(HEDGE_POSITION_STATE) == POSITION_STATE_FROZEN)continue;
8:      ulong id = HedgePositionGetInteger(HEDGE_POSITION_ENTRY_ORDER_ID)
9:     }

コードはサイクル for 内のアクティブなトランザクションリストをループします。(行1)トランザクションを進める前に TransactionSelect() を用いてトランザクションを選択します(行3)。このトランザクションからは双方向ポジションのみ選択されます(行4)。ポジションのマジックナンバーとシンボルが現 EA の実行対象マジックナンバーとシンボルに一致しなければ、HT は次のポジションに移動します(行5、6)。それからユニークなポジション識別子を決定します(行8)。

行7には特に注意が必要です。選択されたポジションは変更の可能性について確認される必要があります。ポジションがすでに変更プロセスにあれば、プロパティの一つを取得できたとしても現在のスレッドでは変更できません。ポジションがロックされていれば、解除されプロパティにアクセスできるまで待つか変更を再試行するのが賢明です。プロパティ HEDGE_POSITION_STATE はポジションの変更が可能かどうか調査します。

POSITION_STATE_FROZEN モディファイアはポジションが『凍結』されており、変更できないことを意味します。POSITION_STATE_ACTIVE モディファイアはポジションがアクティブで変更可能なことを表します。これらモディファイアは列挙 ENUM_HEDGE_POSITION_STATE にリストアップされており、その列挙は適切な セクションにドキュメントとして入っています。

履歴上のトランザクションを検索する必要がある場合は、関数TransactionTotal() および TransactionSelect() 内のモディファイア MODE_TRADESMODE_HISTORYと置き換えられます。

HedgeTerminal でのトランザクションは別のトランザクションにネスト化されます。これはネスト化が行われない MetaTrader 5 のコンセプトとは大きく異なります。たとえば HedgeTerminal での履歴上の双方向ポジションは2件のオーダーで構成されています。そのそれぞれには任意のディールのセットがあります。ネスト化は以下のように表されます。

図2 ネスト化されたトランザクション

図2 ネスト化されたトランザクション

トランザクションのネスト化は HedgeTerminal のビジュアルパネルで明確に確認されます。

下のスクリーンショットは MagicEx 1.3 のポジション詳細を表示しています。

図3 HedgeTerminal パネル内のネスト化されたトランザクション

図3 HedgeTerminal パネル内のネスト化されたトランザクション

双方向ポジションの特定オーダーまたはディールのプロパティにアクセスすることが可能です。

そのためには次を行います。

  1. 履歴上のトランザクションを選択し、それが双方向ポジションであることを確認します。
  2. HedgeOrderSelect() を用いてこのポジションのオーダーのひとつを選択します。
  3. 選択したオーダーのプロパティのひとつを取得します。たとえばそこにあるディール番号です。
  4. ディールをすべて検索し、そのオーダーに属するディールのひとつを選択します。
  5. 必要なディールプロパティを取得します。

トランザクションが選択されたあと、その指定プロパティはトランザクションに対して利用可能となることに注意します。たとえば、トランザクションがあるオーダーであった場合、 HedgeOrderSelect()によって選択したのち、それに対するディール番号(HedgeOrderGetInter(HEDGE_ORDER_DEALS_TOTAL) または加重平均エントリー価格(HedgeDealGetDouble(HEDGE_DEAL_PRICE_EXECUTED)を検索することができます。

ディール No.1197610 の価格を検索します。それはスクリーンショットで赤でマークされている部分です。このディールは MagicEx 1.3 EA の双方向ポジションの一部です。

以下のコードによって EA はそのポジションとこのディールにアクセスすることができます。

#include <Prototypes.mqh>

ulong Magic=5760655; // MagicEx 1.3.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+ 
void OnTick()
  {
   for(int i=TransactionsTotal(MODE_HISTORY)-1; i>=0; i--)
    {
      if(!TransactionSelect(i,SELECT_BY_POS,MODE_HISTORY))continue;        // Select transaction #i;
      if(TransactionType()!=TRANS_HEDGE_POSITION)continue;                 // If transaction is not position - continue;
      if(HedgePositionGetInteger(HEDGE_POSITION_MAGIC) != Magic)continue;  // If position is not main - continue;
      ulong id = HedgePositionGetInteger(HEDGE_POSITION_ENTRY_ORDER_ID);   // Get id for closed order;
      if(id!=5917888)continue;                                             // If id of position != 5917888 - continue;
      printf("1: -> Select position #"+(string)id);                        // Print position id;
      if(!HedgeOrderSelect(ORDER_SELECTED_CLOSED))continue;                // Select closed order or continue;    
      ulong order_id = HedgeOrderGetInteger(HEDGE_ORDER_ID);               // Get id closed order;
      printf("2: ----> Select order #" + (string)order_id);                // Print id closed order;
      int deals_total = (int)HedgeOrderGetInteger(HEDGE_ORDER_DEALS_TOTAL);// Get deals total in selected order;
      for(int deal_index = deals_total-1; deal_index >= 0; deal_index--)   // Search deal #1197610...
        {
         if(!HedgeDealSelect(deal_index))continue;                         // Select deal by index or continue;
         ulong deal_id = HedgeDealGetInteger(HEDGE_DEAL_ID);               // Get id for current deal;
         if(deal_id != 1197610)continue;                                   // Select deal #1197610;
         double price = HedgeDealGetDouble(HEDGE_DEAL_PRICE_EXECUTED);     // Get price executed;
         printf("3: --------> Select deal #"+(string)deal_id+              // Print price excecuted;
              ". Executed price = "+DoubleToString(price,0));
        }
     }
  }

コード実行後、以下のエントリーが MetaTrader 5 タブのエキスパートタブに作成されます。

2014.10.21 14:46:37.545 MagicEx1.3 (VTBR-12.14,D1)      3: --------> Select deal #1197610. Executed price = 4735
2014.10.21 14:46:37.545 MagicEx1.3 (VTBR-12.14,D1)      2: ----> Select order #6389111
2014.10.21 14:46:37.545 MagicEx1.3 (VTBR-12.14,D1)      1: -> Select position #5917888

The EA はまずポジションNo.5917888を選択し、それからそのポジション内のオーダーNo.6389111 を選択します。オーダーが選択されると EA はディールNo.1197610の検索を始めます。ディールが見つかると、EA は実行価格を取得し、その価格をジャーナルに追加します。


1.5. GetHedgeError() を用いてエラーコードを取得する方法

HedgeTerminal 環境の作業中エラーや不足の状況が発生する可能性があります。そういった場合、エラー取得と関数分析を行います。

もっともシンプルなエラー発生は TransactionSelect() 関数を用いてトランザクションを選択するのを忘れる場合です。この場合TransactionType() 関数はモディファイア TRANS_NOT_DEFINED を返します。

問題がどこにあるのか知るためには最終エラーのモディファイアを取得する必要がります。モディファイアにより今トランザクションが選択されていることを知らされるのです。これを行うのが以下のコードです。

for(int i=TransactionsTotal(MODE_HISTORY)-1; i>=0; i--)
  {
   //if(!TransactionSelect(i,SELECT_BY_POS,MODE_HISTORY))continue;        // forgot to select;
   ENUM_TRANS_TYPE type = TransactionType();
   if(type == TRANS_NOT_DEFINED)
   {
      ENUM_HEDGE_ERR error = GetHedgeError();
      printf("Error, transaction type not defined. Reason: " + EnumToString(error));
   }
  }

以下は結果メッセージです。

エラー、トランザクションタイプが定義されていません。理由:HEDGE_ERR_TRANS_NOTSELECTED

エラー ID はタイプ取得前にトランザクション選択を忘れていたことを示します。

可能性のあるエラーはすべて ENUM_HEDGE_ERR ストラクチャにリストアップされています。


1.6. TotalActionsTask() および GetActionResult() を用いたトレーディングの詳細分析とエラー特定

HedgeTerminal 環境での作業過程で発生するエラー以外に、 SendTradeRequest() 呼び出しの結果としてトレードエラーが発生する可能性があります。このタイプのエラーは対処がもっと困難です。SendTradeRequest() によって処理される1件のタスクが複数のトレード処理(サブタスク)を持つこともあります。たとえば、ストップロスレベルによって保護されているアクティブなポジション内で送信コメントを変更するためにはトレードアクションを2つ作成する必要があります。

  1. ストップレベルを表す保留中のストップオーダーをキャンセルする。
  2. 新しいコメントを持つ新たな保留ストップオーダーを前のオーダーの位置に入れる。

新規のストップオーダーが実行されると、そのコメントがポジション終了コメントとして表示されます。これは正しい方法です。

ただこのタスクは部分的に実行される可能性があります。未決注文が正常にキャンセルされ、新規発注は何らかの理由で失敗するとします。この場合、ポジションはストップロスレベルを持たないままとなります。このエラー処理のためには、EA は特別なタスクログを呼び、そこで失敗したサブタスクを検索する必要があります。

これは2種類の関数を用いて行われます。それは、このタスク内のトレードアクション(サブタスク)の合計数を返す TotalActionsTask() 関数、およびサブ他クスのインデックスを受け取りそのタイプと実行結果を返す GetActionResult() 関数です。トレーディング処理はすべて標準的な MetaTrader 5 ツールを用いて行われるため、処理結果はトレードサーバーの戻りコードに対応しています。

通常、失敗理由の検索アルゴリズムは以下のようなものです。

  1. TotalActionsTask() を使用するタスク内のサブタスク合計数を取得
  2. for ループ内のサブタスクすべてを検索各サブタスクタイプとその結果決定

注文の実行価格が現在価格レベルに近すぎて新コメントを持つストップオーダーが出せなかったとします。

以下のコード例では EA がこの失敗の理由を見つける方法を示しています。

#include <Prototypes.mqh> 

ulong Magic=5760655; // MagicEx 1.3.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+ 
void OnTick()
  {
//detect active position
   for(int i=TransactionsTotal(MODE_HISTORY)-1; i>=0; i--)
     {
      if(!TransactionSelect(i,SELECT_BY_POS,MODE_HISTORY))continue;
      ENUM_TRANS_TYPE type=TransactionType();
      if(type==TRANS_NOT_DEFINED)
        {
         ENUM_HEDGE_ERR error=GetHedgeError();
         printf("Error, transaction not defined. Reason: "+EnumToString(error));
        }
      if(TransactionType()!=TRANS_HEDGE_POSITION)continue;
      if(HedgePositionGetInteger(HEDGE_POSITION_MAGIC) != Magic)continue;
      if(HedgePositionGetString(HEDGE_POSITION_SYMBOL) != Symbol())continue;
      HedgeTradeRequest request;
      request.action=REQUEST_MODIFY_COMMENT;
      request.exit_comment="My new comment";
      if(!SendTradeRequest(request)) // Is error?
        {
         for(uint action=0; action < TotalActionsTask(); action++)
           {
            ENUM_TARGET_TYPE typeAction;
            int retcode=0;
            GetActionResult(action, typeAction, retcode);
            printf("Action#" + (string)action + ": " + EnumToString(type) +(string)retcode);
           }
        }
     }
  }

コード実行後次のメッセージが表示されます。

Action #0 TARGET_DELETE_PENDING_ORDER 10009 (TRADE_RETCODE_PLACED)
Action #1 TARGET_SET_PENDING_ORDER 10015 (TRADE_RETCODE_INVALID_PRICE)

番号をトレードサーバーの戻りコードの標準モディファイアと比較することで、未決注文が正常に消去されたが新規発注に失敗したことがわかります。トレードサーバーはエラー 10015 (不正な価格)を返しました。これは現在価格がストップレベルに近すぎることを意味します。

これを知ると EA がストップレベルの制御をします。そのために EA は同じSendTradeRequest() 関数を用いてこのポジションをクローズします。


1.7. トレードタスク実行状態の追跡

トレードタスクにはそれぞれ連続して処理されるサブタスクがいくつかあります。

非同期モードでは1件のタスクはコードを複数回渡す中で処理されます。タスクが『凍結』される場合もあり得ます。そのためタスク実行に対する EA の制御が必要となります。HEDGE_POSITION_TASK_STATUS モディファイアを伴って HedgePositionGetInteger() 関数を呼ぶとき、それは現在のポジションタスク状態を持つ ENUM_TASK_STATUS タイプの列挙を返します。

たとえばポジションをクローズするためにオーダー送信をしたのち何かがおかしいと、ポジションがクローズされないことを受け、タスク状態を取得する必要があります。

以下の例は非同期の Expert Advisor がポジションに対するタスク状態を分析するために実行するコードを示しています。

ENUM_TASK_STATUS status=HedgePositionGetInteger(HEDGE_POSITION_TASK_STATUS);
switch(status)
  {
   case TASK_STATUS_COMPLETE:
      printf("Task complete!");
      break;
   case TASK_STATUS_EXECUTING:
      printf("Task executing. Waiting...");
      Sleep(200);
      break;
   case TASK_STATUS_FAILED:
      printf("Filed executing task. Print logs...");
      for(int i=0; i<TotalActionsTask(); i++)
        {
         ENUM_TARGET_TYPE type;
         uint retcode;
         GetActionResult(i,type,retcode);
         printf("#"+i+" "+EnumToString(type)+" "+retcode);
        }
      break;
   case TASK_STATUS_WAITING:
      printf("task will soon start.");
      break;
  }

複雑なタスクを実行するには複数の反復が必要であることに注意ください。

非同期モードでは、トレード環境でシグナルを変更する到来するイベントが新しい反復を始めます。そのため反復はすべてディレイなく順番に行われ、それにつづいてトレードさーばーからの回答が受け取られます。タスク実行は同期モードでは異なります。

同期メソッドは同期処理エミュレータを使用します。それによりユーザーは単一パスでのタスク複合も行います。エミュレータは時間の遅れを利用します。たとえば、サブタスクの開始を実行した後、エミュレータは EA に対して実行スレッドを返しません。その代りトレード環境が変わることを期待してしばらくの間待ちます。そのあとトレード環境を再び読みます。サブタスクが正常に完了していることがわかれば、次のサブタスクを開始します。

このプロセスは、待ち時間を取るためいくぶん全体的なパフォーマンスを下げます。しかし複雑なタスクの実行を1度の関数呼び出しで行われるひじょうにシンプルな連続処理に変えるのです。それにより同期モードではタスク実行ログの分析はほとんどまったく必要なくなります。


1.8. 双方向ポジションの変更と終了方法

双方向ポジションは SendTradeRequest() 関数を用いて変更およびクローズされます。アクティブなポジションに適用されるオプションは3つだけです。

  1. ポジションは完全にまたは部分的にクローズされる
  2. ポジション、ストップロス、テイクプロフィットは変更可能である
  3. ポジションの送信コメントは変更可能である

履歴上のポジションは変更できません。 MetaTrader 5 の OrderSend() 関数同様、SendTradeRequest() はトレーディングストラクチャ HedgeTraderRequest形式のプリコンパイルされたクエリを使用します。SendTradeRequest() 関数および HedgeTraderRequest ストラクチャに関するより詳しいドキュメンテーションをご一読ください。ポジションの変更とクローズの例が Chaos II EA の項目で参照可能です。


1.9. Expert Advisor からのHedgeTerminalプロパティ設定方法

HedgeTerminal には頻度のリフレッシュ、サーバーからの応答を待つ秒数その他のプロパティセットがあります。

そのプロパティはすべて Settings.xmlで定義されています。EA がリアルタイムで実行しているとき、ライブラリはファイルからプロパティを読み出し適切な内部パラメータを設定します。EA がチャート上で検証される際、Settings.xml は使用されません。ただしなんらかの状況ではチャート上で実行しているかストラテジーテスタで実行しているかに関わらず、個別に EA のプロパティを変更する必要があるかもしれません。

これは特殊な関数セットHedgePropertySet…によって行われます。 現バージョンはこのセットから1件のプロトタイプのみ機能しています。

enum ENUM_HEDGE_PROP_INTEGER
{
   HEDGE_PROP_TIMEOUT,
};

bool HedgePropertySetInteger(ENUM_HEDGE_PROP_INTEGER property, int value)

たとえばサーバーの応答を待つのにライブラリにタイムアウトを設定するには以下を書きます。

bool res = HedgePropertySetInteger(HEDGE_PROP_TIMEOUT, 30);

サーバー応答が非同期リクエストを送信したのち30秒以内に受け取られなければ、ロックされたポジションは解放されます。


1.10. 処理の同期モードと非同期モード

完全に非同期的にトレード処理を行うHedgeTerminal とその API

ただしこのモードには of EAのより複雑なロジックが必要です。この複雑さを隠すには HedgeTerminalAPI が同期処理の特殊なエミュレータをインクルードします。それによって従来の同期方法で作成された EA が HedgeTerminalAPIの非同期アルゴリズムと通信できるようにします。. この連携は双方向ポジション変更時と終了時に SendTradeRequest() によって行われます。この関数により同期、非同期どちらのトレードタスクも実行可能となります。デフォルトではトレードアクションはすべて同期的処理エミュレータによって同期的に実行されます。ただ、トレードリクエスト(HedgeTradeRequest ストラクチャ)がはっきりと指定のフラグ、asynch_mode = 真を持つ場合、トレードタスクは非同期モードで行われます。

非同期モードではタスクはメインのスレッドから独立して行われます。非同期 EA と HedgeTerminal の非同期ある語リズム間の連携の実装はまだ完了していません。

同期エミュレータはひじょうにシンプルです。それはサブタスクを連続的に開始し、それから MetaTrader 5 内のトレード環境が変わるまでしばらく待ちます。エミュレータはこれら変更を分析し、現タスクの状態を判断します。タスク実行が正常に行われると、エミュレータは次のタスクに進みます。

同期的エミュレータはトレードオーダー実行に軽いディレイを起こします。これは MetaTrader 5 内のトレード環境が実行済みのトレード処理を反映するのに少し時間がかかるためです。環境にアクセスする必要性は HedgeTermianlAPI が同期的スレッドエミュレーションモードでは OnTradeTransaction() ハンドラにやってくるイベントにアクセスできないことに主に関連しています。

非同期スレッド間の連携の問題は、エミユレーションによる非同期スレッドと同期スレッド間の問題同様複雑すぎて明白な解決法はありません。


1.11. スクリプト例による双方向ポジションプロパティの管理

下のスクリプトでは TransactionSelect() 関数がアクティブなトランザクションのリスト上で利用可能なトランザクションすべてを検索します。

各トランザクションはそのリストから選択されます。トランザクションがポジションであれば、そのプロパティのいくつかにアクセスし画面表示します。ポジションプロパティ以外にも、ポジション内のオーダーとディールのプロパティも表示されます。オーダーとディールはそれぞれ最初に HedgeOrderSelect() および HedgeDealSelect() によって選択されます。

ポジションのプロパティすべて、そのオーダーとディールはコンバインされ、システム関数 printf によって1行に表示されます。

//+------------------------------------------------------------------+
//|                                           sample_using_htapi.mq5 |
//|         Copyright 2014, Vasiliy Sokolov, Russia, St.-Petersburg. |
//|                              https://login.mql5.com/ru/users/c-4 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2014, Vasiliy Sokolov."
#property link      "https://login.mql5.com/ru/users/c-4"
#property version   "1.00"

// Include prototypes function of HedgeTerminalAPI library.
#include <Prototypes.mqh> 

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+ 
void OnStart()
  {
   // Search all transaction in list transaction...
   for(int i=TransactionsTotal(); i>=0; i--)
     {
      if(!TransactionSelect(i,SELECT_BY_POS,MODE_TRADES))                           // Selecting from active transactions
        {
         ENUM_HEDGE_ERR error=GetHedgeError();                                      // Get reason if selecting has failed
         printf("Error selecting transaction # "+(string)i+". Reason: "+            // Print reason
                EnumToString(error));
         ResetHedgeError();                                                         // Reset error
         continue;                                                                  // Go to next transaction
        }
      // Only for hedge positions
      if(TransactionType()==TRANS_HEDGE_POSITION) 
        {
         // --- Position captions --- //
         ENUM_TRANS_DIRECTION direction=(ENUM_TRANS_DIRECTION)                      // Get direction caption
                              HedgePositionGetInteger(HEDGE_POSITION_DIRECTION);
         double price_entry = HedgeOrderGetDouble(HEDGE_ORDER_PRICE_EXECUTED);      // Get volume of positions
         string symbol = HedgePositionGetString(HEDGE_POSITION_SYMBOL);             // Get symbol of position
         // --- Order captions --- //
         if(!HedgeOrderSelect(ORDER_SELECTED_INIT))continue;                        // Selecting init order in position
         double slippage = HedgeOrderGetDouble(HEDGE_ORDER_SLIPPAGE);               // Get some slippage was
         uint deals_total = (uint)HedgeOrderGetInteger(HEDGE_ORDER_DEALS_TOTAL);    // Get deals total
         // --- Deals captions --- //
         double commissions=0.0;
         ulong deal_id=0;
         //Search all deals in list deals...
         for(uint d_index=0; d_index<deals_total; d_index++)                        
           {
            if(!HedgeDealSelect(d_index))continue;                                  // Selecting deal by its index
            deal_id = HedgeDealGetInteger(HEDGE_DEAL_ID);                           // Get deal id
            commissions += HedgeDealGetDouble(HEDGE_DEAL_COMMISSION);               // Count commissions
           }
         int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
         printf("Position #" + (string)i + ": DIR " + EnumToString(direction) +     // Print result line
         "; PRICE ENTRY " + DoubleToString(price_entry, digits) + 
         "; INIT SLIPPAGE " + DoubleToString(slippage, 2) + "; LAST DEAL ID " +
         (string)deal_id + "; COMMISSIONS SUM " + DoubleToString(commissions, 2));
        }
     }
  }


1.12. Chaos II EA 例による SendTradeRequest() 関数と HedgeTradeRequest ストラクチャ

例として Bill Williams 氏の著書 Trading Chaos. Second Editionで提案されているトレード戦略に基づく売買ロボットを作成します。

氏の提案にすべて従うわけではなく、アリゲータ インディケータとあといくつかの条件を省略してスキームをシンプル化します。この戦略を選択するのはいくつかの考察によります。主ものはこの戦略が複合ポジション維持戦略を持つことです。時としてポジションの一部をクローズし、ストップロスをとんとんにもっていくことが必要になります。

とんとんにする際、ストップレベルは価格に従っていく必要があります。2番目は、この戦略がよく知られており、それ用に開発されたインディケータが標準の MetaTrader 5 配布パックに入っていることです。ルールを少しばかり変更しシンプル化して、当初の目的:HedgeTerminalAPIライブラリとの EA の連携例を提示すること、を妨げる Expert Advisor の複雑なロジックを避けます。The EA のロジックは HedgeTerminalAPI のトレード関数のほとんどを使用します。これはライブラリにとってはよい検証になります。

反転バーを開始します。ブルの反転バーはその上部3分の1の価格に近いバーで、その最低価格は最終の N バーに対してもっとも低いものです。ベアの反転バーはその下部3分の1の価格に近いバーで、その最高価格は最終の N バーに対してもっとも高いものです。N は無作為に選ばれるパラメータで、Expert Advisor 起動中に設定することができます。これは古典的戦略 "Chaos 2"とは異なります。

反転バーが決まると、未決注文が2件出されます。ブルバーに対しては、注文はその高値の上に設定され、ベアバーに対しては安値のちょうど下に設定されます。こういった2件の注文が OldPending バー中に設定されなければ、シグナルは不要とみなされ、注文は取り消されます。OldPending および N の値はチャート上に EA が起動される前にユーザーによって設定されます。

注文は実行され2件の双方向ポジションに変わります。EA はそれらの間でそれぞれ"No. 1" および "No. 2"のコメント内の番号によって区別します。これはあまり洗練されたソリューションではありませんが、デモにはそれで十分です。オーダーが出されると、反転バーの高値(ベアバーに対し)または安値(ブルバーの場合)にストップロスが設定されます。

最初のポジションはきつい目標になります。そのテイクプロフィットは、実行の場合ポジション収益が発動するストップロスの絶対損益に等しくなるよう設定されます。たとえば、価格1.0000でストップロスが 0.9000 レベルでロングポジションがオープンする場合、テイクプロフィットレベルは 1.0000 + (1.0000 - 0.9000) = 1.1000となります。EA はストップロスかテイクプロフィットでポジションを終了します。

2番目のポジションは長期のポジションです。そのストップロスは価格に従っていきます。ストップは新規に形成されるビル・ウィリアムズのフラクタルについて移動します。ロングポジションに対してはストップは低いフラクタルにしたがって移動し、上側のフラクタルはショートポジションに対して利用されます。EA はストップロスでのみポジションを終了します。

以下の図はこの戦略の解説です。

図4 価格チャートにおける Chaos 2 EA の双方向ポジション表現

図4 価格チャートにおける Chaos 2 EA の双方向ポジション表現

反転バーは赤枠で囲まれています。このチャートの N 期間は2です。この戦略にはもっともチャンスな時が選択されます。ショートポジションはブルーの点線で、ロングポジションはグリーンで表示されています。ご覧のように、比較的シンプルな戦略でもロングポジションとショートポジションは同時に存在します。2014年1月5日~8日の期間に注目します。

これは AUDCAD ダウントレンドにとってのターニングポイントです。1月4日、シグナルはブル反転バーから受け取られました。1月5日ロングポジションが2個オープンされました。同時にまだストップがトレンドに従っている3個のショートポジション(赤の点線)があります。その後1月7日、ショートポジションに対してストップがかかり、マーケットにはロングポジションだけが残っています。

変化をネットポジションで監視するのは難しいかもしれません。ネットボリュームは EA が実際に保持しているポジション数を考慮しないためです。HedgeTerminal により、現ネットポジションにかかわらずこういったチャートを取得し類似の戦略を作成し、EA がその個別ポジションを監視することが可能になります。

以下はこの戦略の実装コードです。

私は故意にオブジェクト指向プログラミングを行わず、初心者向けのコードを適用しました。

//+------------------------------------------------------------------+
//|                                                       Chaos2.mq5 |
//|     Copyright 2014, Vasiliy Sokolov specially for HedgeTerminal. |
//|                                          St.-Petersburg, Russia. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2014, Vasiliy Sokolov."
#property link      "https://login.mql5.com/ru/users/c-4"
#property version   "1.00"

//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include <Prototypes.mqh>           // Include prototypes function of HedgeTerminalAPI library

//+------------------------------------------------------------------+
//| Input parameters.                                                |
//+------------------------------------------------------------------+
input uint N=2;                     // Period of extermum/minimum
input uint OldPending=3;            // Old pending

//+------------------------------------------------------------------+
//| Private variables of expert advisor.                             |
//+------------------------------------------------------------------+
ulong Magic = 2314;                 // Magic number of expert
datetime lastTime = 0;              // Remembered last time for function DetectNewBar
int hFractals = INVALID_HANDLE;     // Handle of indicator 'Fractals'. See: 'https://www.mql5.com/ja/docs/indicators/ifractals'
//+------------------------------------------------------------------+
//| Type of bar by Bill Wiallams strategy.                           |
//+------------------------------------------------------------------+
enum ENUM_BAR_TYPE
  {
   BAR_TYPE_ORDINARY,               // Ordinary bar. 
   BAR_TYPE_BEARISH,                // This bar close in the upper third and it's minimum is lowest at N period
   BAR_TYPE_BULLISH,                // This bar close in the lower third and it's maximum is highest at N period
  };
//+------------------------------------------------------------------+
//| Type of Extremum.                                                |
//+------------------------------------------------------------------+
enum ENUM_TYPE_EXTREMUM
  {
   TYPE_EXTREMUM_HIGHEST,           // Extremum from highest prices
   TYPE_EXTREMUM_LOWEST             // Extremum from lowest prices
  };
//+------------------------------------------------------------------+
//| Type of position.                                                |
//+------------------------------------------------------------------+
enum ENUM_ENTRY_TYPE
  {
   ENTRY_BUY1,                      // Buy position with short target
   ENTRY_BUY2,                      // Buy position with long target
   ENTRY_SELL1,                     // Sell position with short target
   ENTRY_SELL2,                     // Sell position with long target
   ENTRY_BAD_COMMENT                // My position, but wrong comment
  };
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Create indicator 'Fractals' ---//
   hFractals=iFractals(Symbol(),NULL);
   if(hFractals==INVALID_HANDLE)
      printf("Warning! Indicator 'Fractals' not does not create. Reason: "+
             (string)GetLastError());
//--- Corection magic by timeframe ---//
   int minPeriod=PeriodSeconds()/60;
   string strMagic=(string)Magic+(string)minPeriod;
   Magic=StringToInteger(strMagic);
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Delete indicator 'Fractals' ---//
   if(hFractals!=INVALID_HANDLE)
      IndicatorRelease(hFractals);
//---
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- Run logic only open new bar. ---//
   int totals=SupportPositions();
   if(NewBarDetect()==true)
     {
      MqlRates rates[];
      CopyRates(Symbol(),NULL,1,1,rates);
      MqlRates prevBar=rates[0];
      //--- Set new pendings order ---//
      double closeRate=GetCloseRate(prevBar);
      if(closeRate<=30 && BarIsExtremum(1,N,TYPE_EXTREMUM_HIGHEST))
        {
         DeleteOldPendingOrders(0);
         SetNewPendingOrder(1,BAR_TYPE_BEARISH);
        }
      else if(closeRate>=70 && BarIsExtremum(1,N,TYPE_EXTREMUM_LOWEST))
        {
         DeleteOldPendingOrders(0);
         SetNewPendingOrder(1,BAR_TYPE_BULLISH);
        }
      DeleteOldPendingOrders(OldPending);
     }
//---
  }
//+------------------------------------------------------------------+
//| Analyze open positions and modify it if needed.                  |
//+------------------------------------------------------------------+
int SupportPositions()
  {
//---
   int count=0;
   //--- Analize active positions... ---//
   for(int i=0; i<TransactionsTotal(); i++) // Get total positions.
     {
      //--- Select main active positions ---//
      if(!TransactionSelect(i, SELECT_BY_POS, MODE_TRADES))continue;             // Select active transactions
      if(TransactionType() != TRANS_HEDGE_POSITION)continue;                     // Select hedge positions only
      if(HedgePositionGetInteger(HEDGE_POSITION_MAGIC) != Magic)                 // Select main positions by magic
      if(HedgePositionGetInteger(HEDGE_POSITION_STATE) == POSITION_STATE_FROZEN) // If position is frozen - continue
         continue;                                                               // Let's try to get access to positions later
      count++;
      //--- What position do we choose?... ---//
      ENUM_ENTRY_TYPE type=IdentifySelectPosition();
      bool modify=false;
      double sl = 0.0;
      double tp = 0.0;
      switch(type)
        {
         case ENTRY_BUY1:
         case ENTRY_SELL1:
           {
            //--- Check sl, tp levels and modify it if need. ---//
            double currentStop=HedgePositionGetDouble(HEDGE_POSITION_SL);
            sl=GetStopLossLevel();
            if(!DoubleEquals(sl,currentStop))
               modify=true;
            tp=GetTakeProfitLevel();
            double ask = SymbolInfoDouble(Symbol(), SYMBOL_ASK);
            double bid = SymbolInfoDouble(Symbol(), SYMBOL_BID);
            //--- Close by take-profit if price more tp level
            bool isBuyTp=tp<bid && !DoubleEquals(tp,0.0) && type==ENTRY_BUY1;
            bool isSellTp=tp>ask && type==ENTRY_SELL1;
            if(isBuyTp || isSellTp)
              {
               HedgeTradeRequest request;
               request.action=REQUEST_CLOSE_POSITION;
               request.exit_comment="Close by TP from expert";
               request.close_type=CLOSE_AS_TAKE_PROFIT;
               if(!SendTradeRequest(request))
                 {
                  ENUM_HEDGE_ERR error=GetHedgeError();
                  string logs=error==HEDGE_ERR_TASK_FAILED ? ". Print logs..." : "";
                  printf("Close position by tp failed. Reason: "+EnumToString(error)+" "+logs);
                  if(error==HEDGE_ERR_TASK_FAILED)
                     PrintTaskLog();
                  ResetHedgeError();
                 }
               else break;
              }
            double currentTakeProfit=HedgePositionGetDouble(HEDGE_POSITION_TP);
            if(!DoubleEquals(tp,currentTakeProfit))
               modify=true;
            break;
           }
         case ENTRY_BUY2:
           {
            //--- Check sl level and set modify flag. ---//
            sl=GetStopLossLevel();
            double currentStop=HedgePositionGetDouble(HEDGE_POSITION_SL);
            if(sl>currentStop)
               modify=true;
            break;
           }
         case ENTRY_SELL2:
           {
            //--- Check sl level and set modify flag. ---//
            sl=GetStopLossLevel();
            double currentStop=HedgePositionGetDouble(HEDGE_POSITION_SL);
            bool usingSL=HedgePositionGetInteger(HEDGE_POSITION_USING_SL);
            if(sl<currentStop || !usingSL)
               modify=true;
            break;
           }
        }
      //--- if  need modify sl, tp levels - modify it. ---//
      if(modify)
        {
         HedgeTradeRequest request;
         request.action=REQUEST_MODIFY_SLTP;
         request.sl = sl;
         request.tp = tp;
         if(type==ENTRY_BUY1 || type==ENTRY_SELL1)
            request.exit_comment="Exit by T/P level";
         else
            request.exit_comment="Exit by trailing S/L";
         if(!SendTradeRequest(request))
           {
            ENUM_HEDGE_ERR error=GetHedgeError();
            string logs=error==HEDGE_ERR_TASK_FAILED ? ". Print logs..." : "";
            printf("Modify stop-loss or take-profit failed. Reason: "+EnumToString(error)+" "+logs);
            if(error==HEDGE_ERR_TASK_FAILED)
               PrintTaskLog();
            ResetHedgeError();
           }
         else break;
        }
     }
   return count;
//---
  }
//+------------------------------------------------------------------+
//| Return stop-loss level for selected position.                    |
//| RESULT                                                           |
//|   Stop-loss level                                                |
//+------------------------------------------------------------------+
double GetStopLossLevel()
  {
//---
   double point=SymbolInfoDouble(Symbol(),SYMBOL_TRADE_TICK_SIZE)*3;
   double fractals[];
   double sl=0.0;
   MqlRates ReversalBar;

   if(!LoadReversalBar(ReversalBar))
     {
      printf("Reversal bar load failed.");
      return sl;
     }
   //--- What position do we choose?... ---//
   switch(IdentifySelectPosition())
     {
      case ENTRY_SELL2:
        {
         if(HedgePositionGetInteger(HEDGE_POSITION_USING_SL))
           {
            sl=NormalizeDouble(HedgePositionGetDouble(HEDGE_POSITION_SL),Digits());
            CopyBuffer(hFractals,UPPER_LINE,ReversalBar.time,TimeCurrent(),fractals);
            for(int i=ArraySize(fractals)-4; i>=0; i--)
              {
               if(DoubleEquals(fractals[i],DBL_MAX))continue;
               if(DoubleEquals(fractals[i],sl))continue;
               if(fractals[i]<sl)
                 {
                  double price= SymbolInfoDouble(Symbol(),SYMBOL_ASK);
                  int ifreeze =(int)SymbolInfoInteger(Symbol(),SYMBOL_TRADE_FREEZE_LEVEL);
                  double freeze=SymbolInfoDouble(Symbol(),SYMBOL_TRADE_TICK_SIZE)*ifreeze;
                  if(fractals[i]>price+freeze)
                     sl=NormalizeDouble(fractals[i]+point,Digits());
                 }
              }
            break;
           }
        }
      case ENTRY_SELL1:
         sl=ReversalBar.high+point;
         break;
      case ENTRY_BUY2:
         if(HedgePositionGetInteger(HEDGE_POSITION_USING_SL))
           {
            sl=NormalizeDouble(HedgePositionGetDouble(HEDGE_POSITION_SL),Digits());
            CopyBuffer(hFractals,LOWER_LINE,ReversalBar.time,TimeCurrent(),fractals);
            for(int i=ArraySize(fractals)-4; i>=0; i--)
              {
               if(DoubleEquals(fractals[i],DBL_MAX))continue;
               if(DoubleEquals(fractals[i],sl))continue;
               if(fractals[i]>sl)
                 {
                  double price= SymbolInfoDouble(Symbol(),SYMBOL_BID);
                  int ifreeze =(int)SymbolInfoInteger(Symbol(),SYMBOL_TRADE_FREEZE_LEVEL);
                  double freeze=SymbolInfoDouble(Symbol(),SYMBOL_TRADE_TICK_SIZE)*ifreeze;
                  if(fractals[i]<price-freeze)
                     sl=NormalizeDouble(fractals[i]-point,Digits());
                 }
              }
            break;
           }
      case ENTRY_BUY1:
         sl=ReversalBar.low-point;
     }
   sl=NormalizeDouble(sl,Digits());
   return sl;
//---
  }
//+------------------------------------------------------------------+
//| Return Take-Profit level for selected position.                  |
//| RESULT                                                           |
//|   Take-profit level                                              |
//+------------------------------------------------------------------+
double GetTakeProfitLevel()
  {
//---
   double point=SymbolInfoDouble(Symbol(),SYMBOL_TRADE_TICK_SIZE)*3;
   ENUM_ENTRY_TYPE type=IdentifySelectPosition();
   double tp=0.0;
   if(type==ENTRY_BUY1 || type==ENTRY_SELL1)
     {
      if(!HedgePositionGetInteger(HEDGE_POSITION_USING_SL))
         return tp;
      double sl=HedgePositionGetDouble(HEDGE_POSITION_SL);
      double openPrice=HedgePositionGetDouble(HEDGE_POSITION_PRICE_OPEN);
      double deltaStopLoss=MathAbs(NormalizeDouble(openPrice-sl,Digits()));
      if(type==ENTRY_BUY1)
         tp=openPrice+deltaStopLoss;
      if(type==ENTRY_SELL1)
         tp=openPrice-deltaStopLoss;
      return tp;
     }
   else
      return 0.0;
//---
  }
//+------------------------------------------------------------------+
//| Identify what position type is select.                           |
//| RESULT                                                           |
//|   Return type position. See ENUM_ENTRY_TYPE                      |
//+------------------------------------------------------------------+
ENUM_ENTRY_TYPE IdentifySelectPosition()
  {
//---   
   string comment=HedgePositionGetString(HEDGE_POSITION_ENTRY_COMMENT);
   int pos=StringLen(comment)-2;
   string subStr=StringSubstr(comment,pos);
   ENUM_TRANS_DIRECTION posDir=(ENUM_TRANS_DIRECTION)HedgePositionGetInteger(HEDGE_POSITION_DIRECTION);
   if(subStr=="#0")
     {
      if(posDir==TRANS_LONG)
         return ENTRY_BUY1;
      if(posDir==TRANS_SHORT)
         return ENTRY_SELL1;
     }
   else if(subStr=="#1")
     {
      if(posDir==TRANS_LONG)
         return ENTRY_BUY2;
      if(posDir==TRANS_SHORT)
         return ENTRY_SELL2;
     }
   return ENTRY_BAD_COMMENT;
//---
  }
//+------------------------------------------------------------------+
//| Set pending orders under or over bar by index_bar.               |
//| INPUT PARAMETERS                                                 |
//|   index_bar - index of bar.                                      |
//|   barType - type of bar. See enum ENUM_BAR_TYPE.                 |
//| RESULT                                                           |
//|   True if new order successfully set, othewise false.            | 
//+------------------------------------------------------------------+
bool SetNewPendingOrder(int index_bar,ENUM_BAR_TYPE barType)
  {
//---
   MqlRates rates[1];
   CopyRates(Symbol(),NULL,index_bar,1,rates);
   MqlTradeRequest request={0};
   request.volume=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN);
   double vol=request.volume;
   request.symbol = Symbol();
   request.action = TRADE_ACTION_PENDING;
   request.type_filling=ORDER_FILLING_FOK;
   request.type_time=ORDER_TIME_GTC;
   request.magic=Magic;
   double point=SymbolInfoDouble(Symbol(),SYMBOL_TRADE_TICK_SIZE)*3;
   string comment="";
   if(barType==BAR_TYPE_BEARISH)
     {
      request.price=rates[0].low-point;
      comment="Entry sell by bearish bar";
      request.type=ORDER_TYPE_SELL_STOP;
     }
   else if(barType==BAR_TYPE_BULLISH)
     {
      request.price=rates[0].high+point;
      comment="Entry buy by bullish bar";
      request.type=ORDER_TYPE_BUY_STOP;
     }
   MqlTradeResult result={0};
//--- Send pending order twice...
   for(int i=0; i<2; i++)
     {
      request.comment=comment+" #"+(string)i;       // Detect order by comment;
      if(!OrderSend(request,result))
        {
         printf("Trade error #"+(string)result.retcode+" "+
                result.comment);
         return false;
        }
     }
   return true;
//---
  }
//+------------------------------------------------------------------+
//| Delete old pending orders. If pending order set older that       |
//| n_bars ago pending orders will be removed.                       |
//| INPUT PARAMETERS                                                 |
//|   period - count bar.                                            |
//+------------------------------------------------------------------+
void DeleteOldPendingOrders(int n_bars)
  {
//---
   for(int i=0; i<OrdersTotal(); i++)
     {
      ulong ticket = OrderGetTicket(i);            // Get ticket of order by index.
      if(!OrderSelect(ticket))                     // Continue if not selected.
         continue;
      if(Magic!=OrderGetInteger(ORDER_MAGIC))      // Continue if magic is not main.
         continue;
      if(OrderGetString(ORDER_SYMBOL)!=Symbol())   // Continue if symbol is not main.
         continue;
      //--- Count time elipsed ---//
      datetime timeSetup=(datetime)OrderGetInteger(ORDER_TIME_SETUP);
      int secElapsed=(int)(TimeCurrent()-timeSetup);
      //--- delete old pending order ---//
      if(secElapsed>=PeriodSeconds() *n_bars)
        {
         MqlTradeRequest request={0};
         MqlTradeResult result={0};
         request.action= TRADE_ACTION_REMOVE;
         request.order = ticket;
         if(!OrderSend(request,result))
            printf("Delete pending order failed. Reason #"+(string)result.retcode+" "+result.comment);
        }
     }
//---
  }
//+------------------------------------------------------------------+
//| Detect new bar.                                                  |
//+------------------------------------------------------------------+
bool NewBarDetect(void)
  {
//---
   datetime timeArray[1];
   CopyTime(Symbol(),NULL,0,1,timeArray);
   if(lastTime!=timeArray[0])
     {
      lastTime=timeArray[0];
      return true;
     }
   return false;
//---
  }
//+------------------------------------------------------------------+
//| Get close rate. Type bar defined in trade chaos strategy         |
//| and equal enum 'ENUM_TYPE_BAR'.                                  |
//| INPUT PARAMETERS                                                 |
//|   index - index of bars series. for example:                     |
//|   '0' - is current bar. 1 - previous bar.                        |
//| RESULT                                                           |
//|   Type of ENUM_TYPE_BAR.                                         | 
//+------------------------------------------------------------------+
double GetCloseRate(const MqlRates &bar)
  {
//---
   double highLowDelta = bar.high-bar.low;      // Calculate diaposon bar.
   double lowCloseDelta = bar.close - bar.low;  // Calculate Close - Low delta.
   double percentClose=0.0;
   if(!DoubleEquals(lowCloseDelta, 0.0))                    // Division by zero protected.   
      percentClose = lowCloseDelta/highLowDelta*100.0;      // Calculate percent 'lowCloseDelta' of 'highLowDelta'.
   return percentClose;
//---
  }
//+------------------------------------------------------------------+
//| If bar by index is extremum - return true, otherwise             |
//| return false.                                                    |
//| INPUT PARAMETERS                                                 |
//|   index - index of bar.                                          |
//|   period - Number of bars prior to the extremum.                 |
//|   type - Type of extremum. See ENUM_TYPE_EXTREMUM TYPE enum.     |
//| RESULT                                                           |
//|   True - if bar is extremum, otherwise false.                    | 
//+------------------------------------------------------------------+
bool BarIsExtremum(const int index,const int period,ENUM_TYPE_EXTREMUM type)
  {
//--- Copy rates --- //
   MqlRates rates[];
   ArraySetAsSeries(rates,true);
   CopyRates(Symbol(),NULL,index,N+1,rates);
//--- Search extremum --- //
   for(int i=1; i<ArraySize(rates); i++)
     {
      //--- Reset comment if you want include volume analize. ---//
      //if(rates[0].tick_volume<rates[i].tick_volume)
      //   return false;
      if(type==TYPE_EXTREMUM_HIGHEST && 
         rates[0].high<rates[i].high)
         return false;
      if(type==TYPE_EXTREMUM_LOWEST && 
         rates[0].low>rates[i].low)
         return false;
     }
   return true;
//---
  }
//+------------------------------------------------------------------+
//| Print current error and reset it.                                |
//+------------------------------------------------------------------+  
void PrintTaskLog()
  {
//---
   uint totals=(uint)HedgePositionGetInteger(HEDGE_POSITION_ACTIONS_TOTAL);
   for(uint i = 0; i<totals; i++)
     {
      uint retcode=0;
      ENUM_TARGET_TYPE type;
      GetActionResult(i,type,retcode);
      printf("---> Action #"+(string)i+"; "+EnumToString(type)+"; RETCODE: "+(string)retcode);
     }
//---
  }
//+------------------------------------------------------------------+
//| Load reversal bar. The current position must be selected.        |
//| OUTPUT PARAMETERS                                                |
//|   bar - MqlRates bar.
//+------------------------------------------------------------------+  
bool LoadReversalBar(MqlRates &bar)
  {
//---
   datetime time=(datetime)(HedgePositionGetInteger(HEDGE_POSITION_ENTRY_TIME_SETUP_MSC)/1000+1);
   MqlRates rates[];
   ArraySetAsSeries(rates,true);
   CopyRates(Symbol(),NULL,time,2,rates);
   int size=ArraySize(rates);
   if(size==0)return false;
   bar=rates[size-1];
   return true;
//---   
  }
//+------------------------------------------------------------------+
//| Compares two double numbers.                                     |
//| RESULT                                                           |
//|   True if two double numbers equal, otherwise false.             |
//+------------------------------------------------------------------+
bool DoubleEquals(const double a,const double b)
  {
//---
   return(fabs(a-b)<=16*DBL_EPSILON*fmax(fabs(a),fabs(b)));
//---
  }

以下はこのコードの動作方法の簡単な解説です。EA はティックごとに呼ばれます。それは BarIsExtremum() 関数によって前回バーを分析します。:ベアかブルか、未決注文を2件出しているか(関数 SetNewPendingOrder())。アクティブ化されると、未決注文はポジションに変換されます。それから EA はポジションに対しストップロスおよびテイクプロフィットを設定します。

残念ながら、これらレベルは未決注文と一緒には設定することができません。というのもまだ実ポジションがないからです。レベルは SupportPositions() によって設定されます。正しく処理するためには、テイクプロフィットが設定されるポジションと、フラクタルに従っていくポジションを知る必要があります。このポジション定義は IdentifySelectPosition() 関数によって行われます。それは開始ポジションコメントを分析し、それにストリング "No.1"があればきびしい目標がそのポジションに設定され、"No. 2"があれば、トレーリングストップが適用されます。

オープンした双方向ポジションを変更、またはそれをクローズするには特殊なトレードリクエストが作成され、実行のため SendTradeRequest() 関数に送られます。

...
if(modify)
  {
   HedgeTradeRequest request;
   request.action=REQUEST_MODIFY_SLTP;
   request.sl = sl;
   request.tp = tp;
   if(type==ENTRY_BUY1 || type==ENTRY_SELL1)
      request.exit_comment="Exit by T/P level";
   else
      request.exit_comment="Exit by trailing S/L";
   if(!SendTradeRequest(request))
     {
      ENUM_HEDGE_ERR error=GetHedgeError();
      string logs=error==HEDGE_ERR_TASK_FAILED ? ". Print logs..." : "";
      printf("Modify stop-loss or take-profit failed. Reason: "+EnumToString(error)+" "+logs);
      if(error==HEDGE_ERR_TASK_FAILED)
         PrintTaskLog();
      ResetHedgeError();
     }
   else break;
  }
...

エラー処理に注意します。

失敗を送信し、関数がを返すと、GetHedgeError() 関数により最終エラーコードを取得する必要があります。トレードオーダーの実行が開始されない場合もあります。ポジションが事前に選択されていなければ、クエリーが正しく作成されず、実行は不可能となります。

オーダーが実行されないと、実装ログを分析する意味はなく、エラーコードを取得するだけで十分です。

ただクエリーがただしいのになんらかの理由で注文が実行されないと、エラー HEDGE_ERR_TASK_FAILED が返されます。この場合にはログをくまなく検索して実行ログを分析する必要があります。これは特殊関数 PrintTaskLog() によって行われます。

//+------------------------------------------------------------------+
//| Print current error and reset it.                                |
//+------------------------------------------------------------------+  
void PrintTaskLog()
  {
//---
   uint totals=(uint)HedgePositionGetInteger(HEDGE_POSITION_ACTIONS_TOTAL);
   for(uint i = 0; i<totals; i++)
     {
      uint retcode=0;
      ENUM_TARGET_TYPE type;
      GetActionResult(i,type,retcode);
      printf("---> Action #"+(string)i+"; "+EnumToString(type)+"; RETCODE: "+(string)retcode);
     }
//---
  }

こういったメッセージにより失敗理由が特定でき、修正することができます。

それでは Chaos2 EA の表示とリアルタイムでの HedgeTerminal 内ポジションについて解説します。EA は M1 チャートで実行しています。

図5 HedgeTerminal パネルにおける Chaos 2 EA の双方向ポジション表現

図5 HedgeTerminal パネルにおける Chaos 2 EA の双方向ポジション表現

見てのとおり、1件の EA の双方向ポジションですら完璧に共存することが可能です。


1.13. ブローカーによる『重複シンボル』と仮想化

MetaTrader 5 が発表されたとき、ブローカーの中にはいわゆる重複シンボルを提供するものもありました。そのクオートは元のインスツルメントに等しいが一般に接尾辞 "_m""_1"などがついています。それらはトレーダーが実質的に同じシンボルについて双方向ポジションを持つことができるように取り入れられました。

ただそのようなシンボルはロボットを利用するアルゴリズムトレーダーにとってはほとんど役に立たないものです。それはこういうことです。HedgeTerminalAPI ライブラリを使わずに "Chaos II" EA を書く必要があるとします。代わりになにか重複シンボルを持つとします。それをどうやって行うのでしょうか?売りの処理すべてが単一のインスツルメントについてオープンされるとします。たとえば EURUSDのようなインスツルメントです。そして買いがすべて別のインスツルメント、たとえば EURUSD_m1に対してオープンされるとします。

しかしポジションがオープンするときシンボルの一つがすでに別のロボットやトレーダーによってトレードされてしまっていたらどうなるのでしょうか?そういうシンボルが常に空いていたとしても、同時に服すポジションを同じ方向に持つこのロボットに対して問題は解決されないでしょう。

上のスクリーンショットは売りポジションを3個示しています。そしてまだポジションがある可能性があります。ポジションは異なる保護的ストップレベルを持ちます。それが1個のネットポジションにまとめられない理由です。解決法は新しい重複シンボルに対して新規ポジションをオープンすることです。ただそのようなシンボルが十分にないかもしれません。なぜなら1件のロボットは6とおりの重複インスツルメントを必要とするからです(各トレード方向につき3とおり)。2つのロボットが異なるタイムフレームで実行されると、12個のシンボルが必要となります。

そんなに多くの重複シンボルを提供するブローカーはありません。ただしそのようなシンボルの量に制限がなく、つねに空いているとしても、アルゴリズムの複雑な分解が必要となります。ロボットは重複とそのポジションを検索して利用可能なシンボルをくまなく調べます。これは解決するよりも多くの問題を作ることとなります。

重複シンボルについてさらに難しいことがあります。ここにそれを利用することで持ち上がるその他の問題の簡単なリストがあります。

重複シンボルは基本的にブローカー側での仮想化です。HedgeTerminal はクライアント側で仮想化を行います。

どちらの場合もそのような仮想化を利用します。それはトレーダーの義務の実表現を変えるものです。仮想化によって1ポジションが2ポジションに変わります。クライアント側でこれが起こっても問題ではありません。クライアントは好きなように表現することができるからです。が、ブローカー側で仮想化を行うと、規制およびライセンス団体が提供される情報がどのように実際の情報に関連しているのか疑問を持つ可能性があります。次にむつかしいのはこれには1個に2つの API が必要になることです。:ネットモードで使用する関数とモディファイア1セットと双方向モードにもう1セットです。

アルゴリズムトレーダーの多くはトレードを1ポジションにバインドする独自の方法を見出してきました。そういう方法の多くはうまく作用しており、そういう方法について述べた記事があります。ただしポジションの仮想化は見かけ以上に複雑な手順です。HedgeTerminalではポジションの仮想化に関連するアルゴリズムは約 20,000 行のソースコードを取ります。その上、HedgeTerminal は基本関数しか実装しません。みなさんの EA で双方向ポジションだけのためにこれに近い量のコードを作成することはリソースを消費しすぎることとなります。


第2章 HedgeTerminal API マニュアル


2.1. トランザクション選択関数

関数 TransactionsTotal()

関数はトランザクションのリスト上でトランザクションのトータル数を返します。これは利用可能なトランザクションをすべて検索するための基本関数です。(本稿のセクション1.4 および 1.11 参照)。

int TransactionsTotal(ENUM_MODE_TRADES pool = MODE_TRADES);

パラメータ

戻り値

関数はトランザクションのリスト上でトランザクションのトータル数を返します。

関数 TransactionType()

この関数は選択済みトランザクションタイプを返します。

ENUM_TRANS_TYPE TransactionType(void);

戻り値

戻り値タイプ値 ENUM_TRANS_TYPE の値の一つです。

使用例

本稿のセクション 1.11 『スクリプト例による双方向ポジションプロパティの管理』 にある関数使用例を参照します。

関数 TransactionSelect()

この関数は今後の操作をするトランザクションを選択します。この関数はそのインデックスまたはトランザクションリストのユニークな識別子によってトランザクションを選択します。

bool TransactionSelect(int index,
     ENUM_MODE_SELECT select = SELECT_BY_POS,
     ENUM_MODE_TRADES pool=MODE_TRADES
     );

パラメータ

戻り値

トランザクションが正常に選択されると真、それ以外は偽を返します。エラー情報詳細取得には GetHedgeError()を呼びます。

使用例

本稿のセクション 1.11 『スクリプト例による双方向ポジションプロパティの管理』 にある関数使用例を参照します。

注意

トランザクションがそのインデックスに基づき選択されるなら、処理の複雑さは O(1) に対応します。トランザクションがそのユニークな識別子を基に選択される場合、処理の複雑さは漸近的に O(log2(n))に向かいます。

関数 HedgeOrderSelect()

この関数は双方向ポジションに含まれるオーダーの一つを選択します。双方向ポジションは、必要なオーダーを持ち、TransactionSelect ()によって事前に選択される必要があります。

bool HedgeOrderSelect(ENUM_HEDGE_ORDER_SELECTED_TYPE type);

パラメータ

戻り値

オーダーが正常に選択されると真、それ以外は偽を返します。エラー情報詳細取得には GetHedgeError()を呼びます。

使用例

本稿のセクション 1.11 『スクリプト例による双方向ポジションプロパティの管理』 にある関数使用例を参照します。

関数 HedgeDealSelect()

この関数はオーダーを実行するディールの一つを選択します。一部が選択済みディールであるオーダーは HedgeOrderSelect() 関数によって事前に選択される必要があります。

bool HedgeDealSelect(int index);

パラメータ

戻り値

ディールが正常に選択されると真、それ以外は偽を返します。エラー情報詳細取得には GetHedgeError()を呼びます。

使用例

本稿のセクション 1.11 『スクリプト例による双方向ポジションプロパティの管理』 にある関数使用例を参照します。


2.2. 選択したトランザクションのプロパティを取得するための関数

関数 HedgePositionGetInteger()

この関数は選択済みの双方向ポジションのプロトタイプを返します。プロトタイプはリクエストされるプロパティタイプに応じて intlongdatetimebool などのタイプです。双方向ポジションは TransactionSelect() 関数によって事前に選択される必要があります。

ulong HedgePositionGetInteger(ENUM_HEDGE_POSITION_PROP_INTEGER property);

パラメータ

戻り値

ulong タイプの値値のさらなる使用のためには、そのタイプはリクエストされるプロパティタイプに対して 明示的キャスト である必要があります。

使用例

本稿のセクション 1.11 『スクリプト例による双方向ポジションプロパティの管理』 にある関数使用例を参照します。

関数 HedgePositionGetDouble()

この関数は選択済みの双方向ポジションのプロトタイプを返します。戻り値プロパティタイプは doubleです。プロパティタイプは列挙 ENUM_HEDGE_POSITION_PROP_DOUBLE によって指定されます。双方向ポジションはTransactionSelect()によって事前に選択される必要があります。

ulong HedgePositionGetDouble(ENUM_HEDGE_POSITION_PROP_DOUBLE property);

パラメータ

戻り値

double タイプの値

使用例

本稿のセクション 1.11 『スクリプト例による双方向ポジションプロパティの管理』 にある関数使用例を参照します。

関数 HedgePositionGetString()

この関数は選択済みの双方向ポジションのプロトタイプを返します。プロパティは string タイプです。プロパティタイプは列挙 ENUM_HEDGE_POSITION_PROP_STRING によって指定されます。双方向ポジションはTransactionSelect()によって事前に選択される必要があります。

ulong HedgePositionGetString(ENUM_HEDGE_POSITION_PROP_STRING property);

パラメータ

戻り値

string タイプの値

使用例

本稿のセクション 1.11 『スクリプト例による双方向ポジションプロパティの管理』 にある関数使用例を参照します。

関数 HedgeOrderGetInteger()

この関数は選択済みオーダーのプロトタイプを返します。それは双方向ポジションの一部です。プロパティタイプは intlongdatetimeまたは boolです。プロパティタイプは列挙 ENUM_HEDGE_ORDER_PROP_INTEGER によって指定されます。オーダーはHedgeOrderSelect() 関数によって事前に選択される必要があります。

ulong HedgeOrderGetInteger(ENUM_HEDGE_ORDER_PROP_INTEGER property);

パラメータ

戻り値

ulong タイプの値値のさらなる使用のためには、そのタイプはリクエストされるプロパティタイプに対して 明示的キャスト である必要があります。

使用例

本稿のセクション 1.11 『スクリプト例による双方向ポジションプロパティの管理』 にある関数使用例を参照します。

関数 HedgeOrderGetDouble()

この関数は選択済みオーダーのプロトタイプを返します。それは双方向ポジションの一部です。リクエストされるプロパティは double タイプです。プロパティタイプは ENUM_HEDGE_ORDER_PROP_DOUBLE 列挙により指定されます。オーダーは HedgeOrderSelect() 関数によって事前選択される必要があります。

double HedgeOrderGetDouble(ENUM_HEDGE_ORDER_PROP_DOUBLE property);

パラメータ

戻り値

double タイプの値

使用例

本稿のセクション 1.11 『スクリプト例による双方向ポジションプロパティの管理』 にある関数使用例を参照します。

関数 HedgeDealGetInteger()

この関数は選択済みオーダーのプロトタイプを返します。それは実行済みオーダーの一部です。プロパティタイプは intlongdatetimeまたは boolです。プロパティタイプは列挙 ENUM_HEDGE_DEAL_PROP_INTEGER によって指定されます。ディールはHedgeDealSelect() 関数によって事前に選択される必要があります。

ulong HedgeOrderGetInteger(ENUM_HEDGE_DEAL_PROP_INTEGER property);

パラメータ

戻り値

ulong タイプの値値を後に使用するには、そのタイプがリクエストされるプロパティタイプに対して明示的にキャストされる必要があります。

使用例

本稿のセクション 1.11 『スクリプト例による双方向ポジションプロパティの管理』 にある関数使用例を参照します。

関数 HedgeDealGetDouble()

この関数は選択済みオーダーのプロトタイプを返します。それは実行済みオーダーの一部です。プロパティは double タイプです。プロパティタイプは列挙 a2>ENUM_HEDGE_DEAL_PROP_DOUBLE によって指定されます。ディールはHedgeDealSelect() 関数によって事前に選択される必要があります。

ulong HedgeOrderGetDouble(ENUM_HEDGE_DEAL_PROP_DOUBLE property);

パラメータ

戻り値

double タイプの値

使用例

本稿のセクション 1.11 『スクリプト例による双方向ポジションプロパティの管理』 にある関数使用例を参照します。


2.3. Expert Advisors からHedgeTerminalプロパティを設定し取得する関数

関数 HedgePropertySetInteger()

この関数は HedgeTerminal プロパティの一つを設定します。プロパティタイプは intlongdatetimeまたは boolです。プロパティタイプは列挙 ENUM_HEDGE_PROP_INTEGER によって指定されます。

bool HedgePropertySetInteger(ENUM_HEDGE_PROP_INTEGER property, long value);

パラメータ

戻り値

bool タイプの値プロパティが正常に設定されるとこの関数は真を返し、それ以外は偽を返します。

使用例

例ではこの関数は非同期リクエストを送信する間のポジションロック時間設定に使用されています。サーバー応答が非同期リクエスト送信後30秒以内に受け取られなければ、ブロックされたポジションは解除されます。

void SetTimeOut()
  {
   bool res=HedgePropertySetInteger(HEDGE_PROP_TIMEOUT,30);
   if(res)
      printf("The property is set successfully");
   else
      printf("Property is not set");
  }

関数 HedgePropertyGetInteger()

この関数は HedgeTerminal プロパティの一つを取得します。プロパティタイプは intlongdatetimeまたは boolです。プロパティタイプは列挙 ENUM_HEDGE_PROP_INTEGER によって指定されます。

long HedgePropertyGetInteger(ENUM_HEDGE_PROP_INTEGER property);

パラメータ

戻り値

long タイプの値

使用例

この関数は非同期リクエストを送信する間のポジションブロック時間を受け取り、ターミナルにそれを表示します。

void GetTimeOut()
  {
   int seconds=HedgePropertyGetInteger(HEDGE_PROP_TIMEOUT);
   printf("Timeout is "+(string) seconds);
  }


2.4. エラーコードを取得し処理する関数

関数 GetHedgeError()

この関数はエラーの識別子を返します。それは最後のアクションから取得されたものです。エラー識別子は列挙 ENUM_HEDGE_ERR に対応します。

ENUM_HEDGE_ERR GetHedgeError(void);

戻り値

ポジション ID。値は ENUM_HEDGE_ERR 列挙タイプのいずれかです。

注意

呼出し後、GetHedgeError() 関数はエラー IDをリセットしません。エラー IDをリセットには ResetHedgeError() 関数を使います。

使用例

本稿のセクション 1.11 『スクリプト例による双方向ポジションプロパティの管理』 にある関数使用例を参照します。

関数 ResetHedgeError()

この関数は最後に受け取ったエラーの識別子をリセットします。呼出し後 GetHedgeError() によって返される識別子 ENUM_HEDGE_ERRHEDGE_ERR_NOT_ERRORと等しくなります。

void ResetHedgeError(void);

使用例

本稿のセクション 1.11 『スクリプト例による双方向ポジションプロパティの管理』 にある関数使用例を参照します。

関数 TotalActionsTask()

HedgePositionSelect() 関数によってポジションが選択されると、それは SendTradeRequest() 関数によって変更することができます。たとえば、ポジションのクローズ、または送信コメントが変更できます。この変更は特殊なトレードタスクによって行われます。各タスクは複数のトレード処理(サブタスク)で構成されます。タスクは失敗することもあります。この場合、タスクに含まれるサブタスクすべての結果を分析し、どの類のサブタスクが失敗したか確認する必要があります。

TotalActionTask() 関数は選択されたポジションに対して実行された最後のトレードタスクに含まれるサブタスク数を返します。サブタスクの合計数を知ると、インデックスですべてのサブタスクを検索し、GetActionResult() 関数によって実行結果を分析し、それによって失敗の状況を明らかにすることができるのです。

uint TotalActionsTask(void);

戻り値

タスク内サブタスクの合計数を返します。

使用例

本稿のセクション 1.6『TotalActionsTask() および GetActionResult() を用いたトレーディングの詳細分析とエラー特定』で使用例を確認してください。

関数 GetActionResult()

このかんすうはタスク内サブタスクのインデックスを取り込みます( TotalActionTask()参照)。参照パラメータによりサブタスクタイプと実行結果を返します。サブタスクタイプは列挙 ENUM_TARGET_TYPE によって定義されます。サブタスクの実行結果は MetaTrader 5 のトレードサーバー戻り値コードに対応しています。

void GetActionResult(uint index, ENUM_TARGET_TYPE& target_type, uint& retcode);

パラメータ

使用例

本稿のセクション 1.6『TotalActionsTask() および GetActionResult() を用いたトレーディングの詳細分析とエラー特定』で使用例を確認してください。


2.5. トレーディング

関数 SendTradeRequest()

この関数は HedgeTerminalAPIの双方向ポジションを変更するリクエストを送信します。実行結果は3つのアクションの1つです。

  1. ポジションまたはそのボリュームの一部をクローズ
  2. ストップロスレベルとテイクプロフィットレベルの変更
  3. 送信コメントの変更

アクションタイプとそのパラメータは、パラメータとして参照によって渡される HedgeTradeRequest ストラクチャ内で指定されます。関数呼び出し前に双方向ポジションが TransactionSelect() 関数によって事前選択される必要があります。

bool SendTradeRequest(HedgeTradeRequest& request);

パラメータ

[in] request -双方向ポジションを変更するリクエストのストラクチャストラクチャの記述と HedgeTradeRequest ストラクチャの記述にあるフィールドの説明を確認してください。

戻り値

ポジション変更リクエストが正常に実行されれば真を返します。それ以外は偽を返します。リクエスト実行が失敗した場合は、関数 TotalActionsTask() および GetActionResult() を使って失敗とその理由を見つけます。

注意

リクエスト送信の非同期モードでは、タスクは正常に設定され開始した場合、戻りフラグは真となります。ただしタスクが正常にスタートする場合でも、実行は保証されないことを忘れてはいけません。よってこのフラグは非同期モードでタスク完了を制御するためには使用できません。同期モードではタスクは単一スレッドでスタートし実行されるため、同期モードではこのトレードリクエストの実行をフラグによって制御することができます。

トレードリクエストストラクチャ HedgeTradeRequest()

HedgeTerminal の双方向ポジションは SendTradeRequest() 関数を呼びだすことでクローズされ変更されます。この関数ではトレードリクエストが引数として使用されます。リクエストは特殊な定義済みストラクチャ HedgeTradeRequest によって表されます。これは選択されたポジションのクローズまたは変更に必要なフィールドすべてを持ちます。

struct HedgeTradeRequest
  {
   ENUM_REQUEST_TYPE action;             // type of action
   double            volume;             // volume of position
   ENUM_CLOSE_TYPE   close_type;         // Marker of closing order
   double            sl;                 // stop-loss level
   double            tp;                 // take-profit level
   string            exit_comment;       // outgoing comment
   uint              retcode;            // last retcode in executed operation
   bool              asynch_mode;        // true if the closure is performed asynchronously, otherwise false
   ulong             deviation;          // deviation in step price
                     HedgeTradeRequest() // default params
     {
      action=REQUEST_CLOSE_POSITION;
      asynch_mode=false;
      volume=0.0;
      sl = 0.0;
      tp = 0.0;
      retcode=0;
      deviation=3;
     }
  };

フィールド説明

フィールド 説明
action ポジションによって要求されるアクションタイプ値は列挙 ENUM_REQUEST_TYPE 値の任意の値です。
volume クローズするボリューム現アクティブポジションのボリュームよりも小さいものです。ボリュームがゼロの場合、アクティブポジションは完全にクローズされています。
sl アクティブポジションに対して設定されるストップロスレベル
tp アクティブポジションに対して設定されるテイクプロフィットレベル
exit_comment アクティブポジションについての送信コメント
retcode 最後に実行された処理の結果コード
asynch_mode リクエスト送信に非同期モードが使用されると真、それ以外は偽
deviation 使用価格からの最大偏差


2.6. トランザクション選択関数と連携するための列挙

ENUM_TRANS_TYPE

未決注文、双方向ポジションを含む分析に利用可能なトランザクションすべてはアクティブおよび履歴トランザクションリストにあります。

列挙 ENUM_TRANS_TYPE が選択されるトランザクションそれぞれのタイプを持ちます。この列挙は TransactionType() 関数によって返されます。以下は列挙フィールドとその説明です。

フィールド 説明
TRANS_NOT_DEFINED トランザクションが TransactionSelect() 関数によって選択された、またはそのタイプは未定義です。
TRANS_HEDGE_POSITION トランザクションは双方向ポジションです。
TRANS_BROKERAGE_DEAL トランザクションはブローカーのディール(アカウント処理)です。たとえばアカウントへの資金追加や修正です。
TRANS_PENDING_ORDER トランザクションは未決注文です。
TRANS_SWAP_POS トランザクションはネットポジションにチャージされるスワップです。


ENUM_MODE_SELECT

この列挙は TransactionSelect() 関数内に設定される インデックスパラメータタイプを定義します。

フィールド 説明
SELECT_BY_POS インデックスパラメータはリスト上のトランザクションのインデックスを渡すのに使用されます。
SELECT_BY_TICKET チケット番号はインデックスパラメータ内で渡されます。


ENUM_MODE_TRADES

この列挙は TransactionSelect()によってトランザクションが選択されるデータソースを定義します。

フィールド 説明
MODE_TRADES このトランザクションはアクティブなトランザクションから選択されます。
MODE_HISTORY このトランザクションは履歴トランザクションから選択されます。


2.7. トランザクションプロパティを取得する関数と連携するための列挙

列挙 ENUM_TRANS_DIRECTION

トランザクションはそれぞれ、それがディールでも双方向ポジションでも、マーケット方向を持ちます。

このマーケット方向は列挙 ENUM_TRANS_DIRECTION によって決められます。以下はそのフィールドと説明です。

フィールド 説明
TRANS_NDEF トランザクションの方向は未定義です。たとえばアカウントにおけるブローカーのトランザクションはマーケット方向を持たず、このモディファイアがついています。
TRANS_LONG トランザクション(オーダーまたは双方向ポジション)が「買い」トランザクションであることを示します。
TRANS_SHORT トランザクション(オーダーまたは双方向ポジション)が「売り」トランザクションであることを示します。

列挙 ENUM_HEDGE_POSITION_STATUS

この列挙には双方向ポジションの状態が入っています。

フィールド 説明
HEDGE_POSITION_ACTIVE アクティブポジションアクティブポジションは HedgeTerminal パネルの「アクティブ」タブに表示されます。
HEDGE_POSITION_HISTORY 履歴ポジションアクティブポジションは HedgeTerminal パネルの「履歴」タブに表示されます。

列挙 ENUM_HEDGE_POSITION_STATE

この列挙には双方向ポジションの状態があります。

フィールド 説明
POSITION_STATE_ACTIVE 選択されたポジションはアクティブで HedgeTradeRequest によって変更可能です。
POSITION_STATE_FROZEN 選択されたポジションはロックされており、変更不可です。このモディファイアが受け取られるとポジションが解除されるまで待つ必要があります。

列挙 ENUM_HEDGE_POSITION_PROP_INTEGER

この列挙はHedgePositionGetInteger()によって返されるプロパティタイプを設定します。

フィールド 説明
HEDGE_POSITION_ENTRY_TIME_SETUP_MSC ミリ秒で表示される1970年1月1日から双方向ポジションを開始するオーダーが出された時までの時間。
HEDGE_POSITION_ENTRY_TIME_EXECUTED_MSC ミリ秒で表示される1970年1月1日から双方向ポジションを開始するオーダーが実行された(ポジションがオープンした時刻)時までの時間。
HEDGE_POSITION_EXIT_TIME_SETUP_MSC ミリ秒で表示される1970年1月1日から双方向ポジションをクローズするオーダーが出された時までの時間。
HEDGE_POSITION_EXIT_TIME_EXECUTED_MSC ミリ秒で表示される1970年1月1日から双方向ポジションをクローズするオーダーが実行された(ポジションがオープンした時刻)時までの時間。
HEDGE_POSITION_TYPE 双方向ポジションタイプ開始オーダーと同じタイプです。システム列挙 ENUM_MODE_TRADES の値の一つを持ちます。
HEDGE_POSITION_DIRECTION ポジション方向列挙 ENUM_TRANS_DIRECTIONによって定義されます。
HEDGE_POSITION_MAGIC 選択されたポジションが属する Expert Advisor のマジックナンバーです。ゼロ値はポジションがマニュアルでオープンしたことを示します。
HEDGE_POSITION_CLOSE_TYPE ポジションをクローズするオーダーのマーケットENUM_CLOSE_TYPEによって定義されます。
HEDGE_POSITION_ID ポジション ID。開始オーダーと同じ識別子です。
HEDGE_POSITION_ENTRY_ORDER_ID 開始オーダーの識別子
HEDGE_POSITION_EXIT_ORDER_ID 履歴ポジションに対してオーダーをクローズする識別子
HEDGE_POSITION_STATUS ポジション状態ENUM_HEDGE_POSITION_STATUSによって定義されます。
HEDGE_POSITION_STATE ポジション動静ENUM_HEDGE_POSITION_STATEによって定義されます。
HEDGE_POSITION_USING_SL ストップロス使用のフラグストップロスが使用される場合、 HedgePositionGetInteger() 関数が真を返し、それ以外は偽を返します。
HEDGE_POSITION_USING_TP 使用されるテイクプロフィットレベルのフラグストップロスが使用される場合、HedgePositionGetInteger()が真を返し、それ以外は偽を返します。
HEDGE_POSITION_TASK_STATUS 選択されたポジションに対して行われているタスクの状態ポジションは変更中です。このモディファイアはこのポジションの変更情報追跡に使用されます。ポジション状態は ENUM_TASK_STATUSによって決められます。
HEDGE_POSITION_ACTIONS_TOTAL このポジション変更開始をしたサブタスクのトータル数を返します。

列挙 ENUM_HEDGE_POSITION_PROP_DOUBLE

この列挙は HedgePositionGetDouble() 関数によって返されるプロパティタイプを設定します。

フィールド 説明
HEDGE_POSITION_VOLUME 双方向ポジションボリューム
HEDGE_POSITION_PRICE_OPEN ポジションの荷重平均始値
HEDGE_POSITION_PRICE_CLOSED ポジションの荷重平均終値
HEDGE_POSITION_PRICE_CURRENT アクティブポジションの現在価格履歴ポジションに対しては、このモディファイアはポジションの終値を返します。
HEDGE_POSITION_SL ストップロスレベルストップロスが使用されなければゼロ
HEDGE_POSITION_TP テイクプロフィットレベルテイクプロフィットが使用されなければゼロ
HEDGE_POSITION_COMMISSION ポジションに対して支払われるコミッション額
HEDGE_POSITION_SLIPPAGE ポイント表示のスリッページ
HEDGE_POSITION_PROFIT_CURRENCY ポジションの収益または損失値はデポジット通貨で指定されます。
HEDGE_POSITION_PROFIT_POINTS ポジションの収益または損失値はポジションの金融シンボルのポイント表示で指定されます。

注意

スリッページ HEDGE_POSITION_SLIPPAGE は最良のポジションエントリーディールと平均の加重エントリー価格との差をポイントで計算されます。

列挙 ENUM_HEDGE_POSITION_PROP_STRING

この列挙はHedgePositionGetString() 関数によって返されるプロパティタイプを設定します。

フィールド 説明
HEDGE_POSITION_SYMBOL 現ポジションのシンボル
HEDGE_POSITION_ENTRY_COMMENT ポジションの着信コメント
HEDGE_POSITION_EXIT_COMMENT ポジションの送信コメント

列挙 ENUM_HEDGE_ORDER_STATUS

列挙はオーダータイプを持ちます。

フィールド 説明
HEDGE_ORDER_PENDING オーダーは保留でMetaTrader 5の「トレード」タブにあります。
HEDGE_ORDER_HISTORY オーダーは履歴で MetaTrader 5のオーダー履歴にあります。

列挙 ENUM_HEDGE_ORDER_SELECTED_TYPE

この列挙は関数 HedgeOrderSelect()によって選択されるオーダータイプを定義します。

フィールド
ORDER_SELECTED_INIT オーダーは双方向ポジションを開始します。
ORDER_SELECTED_CLOSED オーダーは双方向ポジションをクローズします。
ORDER_SELECTED_SL オーダーはストップロスレベルの役割をします。

列挙 ENUM_HEDGE_ORDER_PROP_INTEGER

この列挙は HedgePositionGetInteger() 関数によって返されるプロパティタイプを設定します。

フィールド 説明
HEDGE_ORDER_ID ユニークなオーダー識別子
HEDGE_ORDER_STATUS オーダー状態値は列挙ENUM_HEDGE_ORDER_STATUS 値の一つです。
HEDGE_ORDER_DEALS_TOTAL オーダーを満たしているディールのトータル数未決注文に対する値はゼロです。
HEDGE_ORDER_TIME_SETUP_MSC 1970年1月1日から未決注文発注時までのミリ秒表示時間。
HEDGE_ORDER_TIME_EXECUTED_MSC 1970年1月1日から実行済みオーダー実行時までのミリ秒表示時間
HEDGE_ORDER_TIME_CANCELED_MSC 1970年1月1日から実行済みオーダー取り消し時までのミリ秒表示時間

注意

オーダー実行時刻 HEDGE_ORDER_TIME_EXECUTED_MSC は直近ディール時刻に等しくなります。

列挙 ENUM_HEDGE_ORDER_PROP_DOUBLE

この列挙は HedgeOrderGetDouble() 関数によって返されるプロパティタイプを設定します。

フィールド 説明
HEDGE_ORDER_VOLUME_SETUP オーダー内で指定されるオーダーボリューム
HEDGE_ORDER_VOLUME_EXECUTED オーダーの実行ボリュームオーダーが保留の場合、実行済みボリュームはゼロとなります。
HEDGE_ORDER_VOLUME_REJECTED 実行できなかったオーダーボリューム初期ボリュームと実行済みボリュームの差に等しくなります。
HEDGE_ORDER_PRICE_SETUP 発注価格
HEDGE_ORDER_PRICE_EXECUTED オーダーの加重平均実行価格
HEDGE_ORDER_COMMISSION オーダーの実行に対してブローカーに支払われるコミッション額デポジット通貨で指定されます。
HEDGE_ORDER_SLIPPAGE オーダースリッページ

注意

スリッページ <s0>HEDGE_POSITION_SLIPPAGE</s0> は最良の実行済みディールとオーダーの平均の加重エントリー価格との差がポイントで計算されます。

列挙 ENUM_HEDGE_DEAL_PROP_INTEGER

この列挙はHedgeDealGetInteger()によって返されるプロパティタイプを設定します。

フィールド 説明
HEDGE_DEAL_ID ユニークなディール識別子
HEDGE_DEAL_TIME_EXECUTED_MSC 1970年1月1日からディール実行時までのミリ秒表示時間。


列挙 ENUM_HEDGE_DEAL_PROP_DOUBLE

この列挙はHedgeDealGetDouble()によって返されるプロパティタイプを設定します。

フィールド 説明
HEDGE_DEAL_VOLUME_EXECUTED ディールのボリューム
HEDGE_DEAL_PRICE_EXECUTED ディール実行価格
HEDGE_DEAL_COMMISSION ディールの実行に対してブローカーに支払われるコミッション額デポジット通貨で指定されます。


2.8. HedgeTerminalプロパティを設定し取得するための列挙

列挙 ENUM_HEDGE_PROP_INTEGER

この列挙は取得を希望するまたはHedgeTerminal に設定したいプロパティタイプを設定します。

フィールド 説明
HEDGE_PROP_TIMEOUT 変更中ポジションが解除されるまえにサーバーからの応答を HedgeTerminal が待つ間の秒表示時間


2.9. エラーコード処理関数と連携するための列挙

列挙 ENUM_TASK_STATUS

どの双方向ポジションも変更中の可能性があります。ポジションはトレードタスク中変更されます。

実行中のトレードタスクはいずれも ENUM_TASK_STATUSで定義される実行状態を持ちます。以下はそのフィールドと説明です。

フィールド 説明
TASK_STATUS_WAITING 現タスクなし、またはタスクは待機中です。
TASK_STATUS_EXECUTING トレードタスクは現在実行中です。
TASK_STATUS_COMPLETE ポジションに対するトレードタスクは正常に完了しました。
TASK_STATUS_FAILED ポジションに対するトレードタスクは失敗しました。

列挙 ENUM_HEDGE_ERR

この列挙は GetHedgeError()によって返されるエラー ID を持ちます。

フィールド 説明
HEDGE_ERR_NOT_ERROR エラーはありません。
HEDGE_ERR_TASK_FAILED 選択されたポジションに対するタスクは失敗しました。
HEDGE_ERR_TRANS_NOTFIND トランザクションが見つかりません。
HEDGE_ERR_WRONG_INDEX 不正なインデックスです。
HEDGE_ERR_WRONG_VOLUME 不正なボリュームです。
HEDGE_ERR_TRANS_NOTSELECTED トランザクションは TransactionSelect()によって事前選択されていません。
HEDGE_ERR_WRONG_PARAMETER 渡されたパラメータの1つが不正です。
HEDGE_ERR_POS_FROZEN 双方向ポジションは現在変更中で新しい変更はできません。ポジション解除までお待ちください。
HEDGE_ERR_POS_NO_CHANGES トレードリクエストに変更はありません。

列挙 ENUM_TARGET_TYPE

この列挙は関数 GetActionResult()によって選択されるタスクタイプを定義します。

フィールド 説明
TARGET_NDEF サブタスクが定義されていません。
TARGET_CREATE_TASK サブタスクは現在作成中です。このタイプは HedgeTerminalAPIの内部ロジックで使用されます。
TARGET_DELETE_PENDING_ORDER 未決注文を削除しています。
TARGET_SET_PENDING_ORDER 未決注文を出しています。
TARGET_MODIFY_PENDING_ORDER 未決注文価格を変更しています。
TARGET_TRADE_BY_MARKET トレード処理を行っています。


2.10. エラーコード処理関数と連携するための列挙

列挙 ENUM_REQUEST_TYPE

この列挙は双方向ポジションに適用される HedgeTerminal の処理を説明します。

フィールド 説明
REQUEST_CLOSE_POSITION ポジションをクローズします。HedgeTradeRequest スト楽cひゃのボリュームフィールドに現在のボリュームを下回るボリュームがあれば、ポジションの一部のみクローズされます。この場合、クローズされるポジションの一部はボリュームフィールドの値に対応しています。
REQUEST_MODIFY_SLTP 既存のストップロスレベルとテイクプロフィットレベルを設定または変更します。
REQUEST_MODIFY_COMMENT アクティブポジションの送信コメントを変更します。

列挙 ENUM_CLOSE_TYPE

この列挙はオーダーが双方向ポジションをクローズするための特殊なマーカーを定義します。マーカーはポジションクローズの理由を指示します。その理由というのは次のうちのひとつです。

フィールド 説明
CLOSE_AS_MARKET ポジションはマーケットによってクローズされたことを示します。ストップロスレベルとテイクプロフィットレベルは設定されていない、または到達していません。
CLOSE_AS_STOP_LOSS ポジションはストップロスレベルに到達したためクローズされることを示します。
CLOSE_AS_TAKE_PROFIT ポジションはテイクプロフィットレベルに到達したためクローズされることを示します。


第3章非同期的トレーディングの基本

非同期処理のテーマは複雑で別に詳細な記事が必要です。ただ HedgeTerminal は積極的に非同期処理を使用するため、このタイプのリクエスト提出を扱うExpert Advisors を作成する原則について簡単に説明するのは妥当です。. またこのテーマに関する資料はほとんどありません。


3.1. 同期的トレードオーダー送信の手配とスキーム

MetaTrader 5 はトレードリクエストをサーバーに送信する関数を2種類備えています。

OrderSend() 関数はそこに書き込まれた MqlTradeRequest ストラクチャとしてリクエストを受け取り、ストラクチャが正確である基礎検証を行います。基礎検証が正常に行われると、サーバーにリクエストを送信し、結果を待ち、それから MqlTradeResult ストラクチャによって結果をカスタムスレッドに返し、フラグを返します。基礎検証が失敗すれば、、この関数はマイナスの値を返します。

リクエストが検証されなかった理由も MqlTradeResult に含まれます。

以下は OrderSend() 関数を用いたカスタム MQL5 プログラムのスレッドの実行を図示しています。

図6 同期トレードリクエストの作成と送信スキーム

図6 非同期トレードリクエストの作成と送信スキーム

スキームからわかるように、MQL5 プログラムのスレッドをサーバーにリクエストを送信し為替のトレード処理を実行する共通システムのスレッドから分離することはできません。

OrderSend()の完了後、トレードリクエストの実結果を分析することができるのはこのためです。カスタムスレッドは赤の矢印でマークされています。とれはほとんど瞬時に実行されます。時間のほとんどは為替トレード処理を行うために費やされます。2件のスレッドが連動しているため、OrderSend() 関数の開始から終了までかなりの時間を遣います。トレード処理が単一スレッドで実行されるため MQL5 プログラムのロジックは連続的なのです。


3.2. 非同期的トレードオーダー送信の手配とスキーム

OrderSendAsync() 関数は異なります。OrderSend()のように、それはトレードリクエストMqlTradeRequest を受け付け、その結果を示すフラグを返します。

ただし最初の例と違ってこの関数はトレードリクエストがサーバーによって実行されるのを待たず、トレードリクエスト値の基礎検証モジュールからのみ取得する値(ターミナル内の基礎検証)を返します。OrderSendAsync() 関数を用いたときのカスタムスレッド実行手順を以下に図示しています。

図7 非同期トレードリクエストの作成と送信スキーム

図7 非同期トレードリクエストの作成と送信スキーム

トレードリクエストが正常に検証されると、それはメインのスレッドと並列してトレードサーバーに送信されます。ネットワーク経由でトレードリクエストと為替の実行を渡すのには最初のケースのようにいくらか時間がかかります。ですがカスタムスレッドはOrderSendAsync() 関数からほとんど即時の結果を取得します。

上図は OrderSendAsync() が実際にトレードサーバーによって実行される新規の並列スレッドを形成し、その実行結果が関数 OnTradeTransaction() または OnTrade() に入ることを示しています。これら関数は新たなカスタムスレッドを開始します。トレードリクエストの送信結果はこの新しいスレッド内で処理されることとなります。このことは Expert Advisorのロジックを大幅に複雑化します。というのも非同期オーダー送信によってリクエスト作成とその確認を単一スレッド内で行うのは不可能だからです。たとえば OnTick()内でリクエストを送信し確認するコードを連続して入れることはできません。

それを説明するためにシンプルな検証 EA を書きます。

//+------------------------------------------------------------------+
//|                                                    AsynchExp.mq5 |
//|                           Copyright 2014, Vasiliy Sokolov (C-4). |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2014, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
input bool UsingAsynchMode=true;
bool sendFlag=false;
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   if(sendFlag)return;
   printf("Formation of order and send to the server...");
   MqlTradeRequest request={0};
   request.magic=12345;
   request.symbol = Symbol();
   request.volume = 0.1;
   request.type=ORDER_TYPE_BUY;
   request.comment= "asynch test";
   request.action = TRADE_ACTION_DEAL;
   request.type_filling=ORDER_FILLING_FOK;
   MqlTradeResult result;
   uint tiks= GetTickCount();
   bool res = false;
   if(UsingAsynchMode)
      res=OrderSendAsync(request,result);
   else
      res=OrderSend(request,result);
   uint delta=GetTickCount()-tiks;
   if(OrderSendAsync(request,result))
     {
      printf("The order has been successfully"+
             "sent to the server.");
     }
  else
     {
     printf("The order is not shipped."+
             " Reason: "+(string)result.retcode);
     }
   printf("Time to send a trade request: "+(string)delta);
   sendFlag=true;
//---
  }

EA が UsingAsynchMode = 偽 で開始することで動作するのを確認します。

EA はロングの 0.1 ロットポジションをオープンします。トレードリクエストはOrderSend() 関数によって同期的に処理されます。以下がそのサンプルログです。

2014.11.06 17:49:28.442 AsynchExp (AUDCAD,H1)   Time to send a trade request: 94
2014.11.06 17:49:28.442 AsynchExp (AUDCAD,H1)   The order has been successfullysent to the server.
2014.11.06 17:49:28.345 AsynchExp (AUDCAD,H1)   Formation of order and send to the server...

トレードリクエストは94ミリ秒以内に完了しました。この時間は、基礎検証に渡されるリクエストがサーバーに送信され書き込まれたということを教えてくれます。

ここでトランザクションボリュームを可能な最大値 DBL_MAXに変更して EA のコードを書きかえます。

request.volume = DBL_MAX;

この値は明らかに実際の範囲から外れています。このリクエストを同期モードで実行します。

2014.11.06 17:54:15.373 AsynchExp (AUDCAD,H1)   Time to send a trade request: 0
2014.11.06 17:54:15.373 AsynchExp (AUDCAD,H1)   The order is not shipped. Reason: 10014
2014.11.06 17:54:15.373 AsynchExp (AUDCAD,H1)   Formation of order and send to the server...

リクエスト送信は失敗しました。失敗の理由はエラー 10014(無効なリクエストボリューム)です。 リクエストは基礎検証中に失敗し、サーバーに送信されてすらいません。それはリクエスト実行時間が 0 ミリ秒なことから明らかです。

リクエストを再び変更します。今回は十分大きなボリュームですが極端な値ではなく – 15 ロットです。EA が検証される$1,000 のアカウントに対してそれは大きすぎます。そのようなポジションはこのアカウントではオープンすることができません。

OrderSend() が何を返すか見ます。

2014.11.06 17:59:22.643 AsynchExp (AUDCAD,H1)   Time to send a trade request: 78
2014.11.06 17:59:22.643 AsynchExp (AUDCAD,H1)   The order is not shipped. Reason: 10019
2014.11.06 17:59:22.550 AsynchExp (AUDCAD,H1)   Formation of order and send to the server...

今回のエラーは前回とは異なり 10019 (真としてのリクエストの実行には不十分な資金です)です。リウエスト実行時間は今回 79 ミリ秒であることに注意します。リクエストはサーバーに送信され、サーバーがエラーを返したということです。

こんどは同じリクエストを OrderSendAsync() 関数によって15 ロットでリクエストします。OrderSend()のとき同様、ポジションはオープンされません。それでもログを分析します。

2014.11.06 18:03:58.106 AsynchExp (AUDCAD,H1)   Time to send a trade request: 0
2014.11.06 18:03:58.106 AsynchExp (AUDCAD,H1)   The order has been successfully sent to the server.
2014.11.06 18:03:58.104 AsynchExp (AUDCAD,H1)   Formation of order and send to the server...

ログによるとエラーはなし、です!エラー10019 はトレードサーバーによって検出されるので、非同期オーダー送信モードの現スレッドには適用されません。戻り値はリクエストが基礎検証を通過したことを示すに過ぎません。実際のエラー 10019を得るには、OnTradeTransaction() システム関数の新たなカスタムスレッドで結果を分析する必要があります。その関数はわれわれの EA に追加が必要です。

void  OnTradeTransaction(const MqlTradeTransaction    &trans,
                         const MqlTradeRequest        &request,
                         const MqlTradeResult         &result)
  {
   uint delta = GetTickCount() - tiks;
   printf("Server answer: " + (string)result.retcode + "; Time: " + (string)delta);
  }

EA を再び実行しログを確認します。

2014.11.06 18:17:00.943 AsynchExp (AUDCAD,H1)   Server answer: 10019; Time: 94
2014.11.06 18:17:00.854 AsynchExp (AUDCAD,H1)   Time to send a trade request: 0
2014.11.06 18:17:00.854 AsynchExp (AUDCAD,H1)   The order has been successfully sent to the server.
2014.11.06 18:17:00.851 AsynchExp (AUDCAD,H1)   Formation of order and send to the server...

エラー 10019 が受け取られましたが送信後即座にではありませんでした。それは OnTradeTransaction()で実行中の新しいカスタムスレッド内で受け取られたのです。


3.3. 同期オーダー実行スピード

トレーダーは非同期リクエストの実行スピードはゼロに近いという誤った信じ込みをしています。

それは OrderSendAsync()をよく見ることで断ち切られます。それは通常1ミリ秒以下で完了しています。現実には上記に示されるようにトレードトランザクションの実際の実行時間はサーバーからの応答が関数 OnTradeTransaction() またはOnTrade().の内部で受け取られる時に計測されます。この計測は実スピードを示します。それは1オーダーに対する同期モード実行スピードに等しいものです。実行時間についての実際のメリットはトランザクションのグループを送信するときにわかります。複数リクエストを送信する必要がある場合には、少なくとも3件のトランザクションがあります。

複数注文を出す際、リクエストを送信するにあたっての制約に配慮することを思い出してください。

MetaTrader 5 ビルド1010 以上では制限は 64 トランザクションで、そのうちの4トランザクションがユーザー用に取り置かれ、残りは Expert Advisorsに対して使用可です。この制限は初心者トレーダーをプログラムの深刻なエラーから守ること、またトレードサーバー上のスパムのロードを減らすことを目的としたものです。

これは同時に、たとえばループ内で適切なトレードリクエストと SendOrderAsync() を呼ぶことで60トレードまで送信することができることを意味しています。60トランザクションすべて送信した後、トランザクションバッファは満杯となります。そしてサーバーによってトランザクションの1件が処理されたことをサーバーが確認するのを待つ必要があります。

処理後、トランザクションバッファ内のトランザクション用の場所が解放され、新規トレードリクエストを取り入れることができるようになります。バッファが満杯になると、新規トランザクション用スペースがゆっくりと解放されます。それは、トレードサーバーは各トランザクションを処理するのに時間が必要で、処理開始を通知する TradeTransaction() イベントがネットワークを通じて渡され、これがいっそうの遅延を招くためでます。

よってリクエスト送信に必要とされる時間はリクエスト数の増加に比べ直線的に増加します。以下のテーブルは非同期モードでの推定オーダー送信比率を示しています。検証は複数回行われ、表示比率は平均値です。

リクエスト数 時間、ミリ秒単位
50 50
100 180
200 2100
500 9000
1000 23000

リクエスト数が60未満のときには、スクリプトはサーバー応答を待ちません。それが時間数値が小さい理由です。それはおよそ1件のリクエストを送信するのにかかる時間に等しくなっています。事実、おおよその実際の実行時間を取得するにはテーブルで指定されているリクエスト出し時間にリクエスト実行平均時間を加えます。


第4章 Meta Trader 5 IDE におけるマルチスレッドプログラミングの基本

MQL5 プログラマーはスレッドをMQLプログラムから直接制御できないことを解っています。この制約は初心者プログラマーには役立つものです。というのもスレッドの使用はプログラムのアルゴリズムをかなり複雑にするからです。ただ、状況によっては2個以上の EA が互いに通信しあいます。たとえばグローバルデータを作成し読む必要があります。

HedgeTerminal はそういった EA のひとつです。他の Expert Advisorのアクションについて HedgeTerminalAPI ライブラリを用いて各 EA に情報を伝えるためには HT は ActivePositions.xml ファイルを読み出し書き込むマルチスレッドによってデータ交換を手配します。このソリューションは非自明で MQL プログラマーはほとんど使用しません。というわけで、われわれは HedgeTerminal に類似したアルゴリズムを持つマルチスレッド EA を作成します。これはマルチスレッドプログラミングをよりよく理解するのに役立ち、それにより HedgeTerminal の動作の仕方をよりよく理解できるのです。


4.1. クオートコレクタ UnitedExchangeQuotes によるマルチスレッドプログラミング

具体例によってマルチスレッドプログラミングの基本を学習していくことにします。異なる提供者(ブローカー)からのクオートコレクタを書くのです。

この考え方は次のようなものです。同一インスツルメントに対してクオートを提供するブローカーが6~7あるとします。当然、異なるブローカーからのクオートは多少異なることでしょう。この違いを分析することでアービトラージ戦略への道が開けるのです。その上、クオート変動比較は最良の提供者、最低の提供者を特定するのに役立ちます。たとえばあるブローカーがより良い価格を提供している場合、トレードを行うのにこのブローカーを選ぶものです。結果の実用的な価値を求める代わりに結果を達成できるメカニズムを記述するにすぎません。

本章の終わりで書くことになる EA のスクリーンショットがあります。

図8 クオートコレクター UnitedExhangesQuotes の記述

図8 クオートコレクター UnitedExhangesQuotes の記述

Expert Advisor は4列と無限の行で構成されるシンプルな表で結果を表示しています。

各行はシンボルクオートを提供するブローカーを表示します(今回の場合は EURUSD です)。「買い」と「売り」はブローカーの最善の需要と供給です。スクリーンショットは価格が若干違って表示しています。現ブローカーと別のブローカーの提供価格の差は D-ASK (Delta Ask) 列に表示されています。同様に要求価格の差は D-BID (Delta Bid) 列に表示されています。たとえば、スクリーンショットが撮られたとき、最善の「買い」は "Alpari Limited"によって提供され、最も高い価格は "Bank VTB 24"のものでした。

MQL プログラムは他の MetaTrader ターミナル環境にはアクセスできません。すなわち、プログラムがあるターミナルで実行されていると、それは別のターミナルからのデータを受け取ることはできないのです。ただし MQL プログラムはすべて MetaTrader ターミナルの共有ディレクトリ内のファイルによってやりとりすることができあmす。任意のプログラムが、たとえば現クオートなどの情報をファイルに書く場合、別のターミナルからMQL プログラムがそれを読むことができます。MQL には外部 DLL なしには他の方法がありません。よってわれわれはこの方法を使用することにします。

もっとも難しいのはそのアクセスを作成することです。一方で EA は別の提供者からのクオートを読む必要があり、もう一方では同じファイルにその提供者のクオートを書き込む必要があります。もう一つ問題はクオートを読むとき、別の EA がこのファイルに新しいクオートを書き込んでいる可能性があることです。そのような並列した動作の結果は予測できません。良くてこれに続いてクラッシュとプログラムの中断が起こり、最悪の場合、これはクオート表示に関連する奇妙な微妙なエラーが時折出現することにつながります。

こういったエラーを除外する、あるいは最低こういったエラーが起こる可能性を最小限に留めるには、はっきりしたプランを作成します。

まず、情報はすべて XML 形式で格納されるようにします。この形式は使いにくい ini ファイルを置き換えました。XML によりクラスのような複雑なデータストラクチャ内にノードを柔軟に展開することができます。次に読み出しと書き込みの一般的アルゴリズムを決めます。基本処理が2とおりあります。データの読み出しとデータの書き込みです。MQL プログラムが読んだり書いたりする際、このファイルには別のどんなプログラムもアクセスできません。それによりあるプログラムがデータを読み、次のプログラムがそれを変更する状況を排除します。このため、データへのアクセスが常にできるとは限りません。

XML アクセスアルゴリズムおよびこのファイルからのクオートすべてのデータを持つ特殊なクラス CQuoteList を作成します。

このクラスの関数の一つは TryGetHandle() で、これはファイルへのアクセスを試行し正常にアクセスした場合はハンドルを返します。以下はその関数の実装です。

int CQuoteList::TryGetHandle(void)
{
   int attempts = 10;
   int handle = INVALID_HANDLE;
   // We try to open 'attemps' times
   for(att = 0; att < attempts; att++)
   {
      handle = FileOpen("Quotes.xml", FILE_WRITE|FILE_READ|FILE_BIN|FILE_COMMON);
      if(handle == INVALID_HANDLE)
      {
         Sleep(15);
         continue;
      }
      break;
   }
   return handle;
}

それは読み出し/書き込み融合モードでファイルを開く試みを複数回行います。試行のデフォルト数は10回です。

試行が失敗すれば、この関数は15ミリ秒フリーズし、ファイルオープンを再試行します。このように10回まで行います。

ファイルが開かれると、LoadQuotes() 関数にそのハンドルが渡されます。 この関数と CQuoteList クラスの完全リストは本稿添付にあります。ですからここでは関数内処理シーケンスについてのみお話します。

  1. TryGetHandle() がファイルを開き読み出し、書き込みを行います。
  2. XML Parser ライブラリによって XML ドキュメントが EA にアップロードされます。
  3. アップロードされた XML ドキュメントを基に、必要な情報を格納する新しいクオートの配列が形成されます。
  4. 作成された配列には現 EA に属するクオートが入っています。その値は更新されます。
  5. クオート配列は XML ドキュメントに変換しなおされます。開いている XML ファイルの内容はこの XML ドキュメントと置き換えられます。
  6. クオートの XML ファイルは閉じられます。

LoadQuotes() 関数はすばらしい仕事をしますが、ほとんどの場合はそれは1ミリ秒以下しかかかりません。

さらに節約するデータ読み出しと更新は1つのブロックに結合されます。これは、読み出しと書き込み処理の間でファイルへのアクセスコントロールが失われないよう意図的に行われるものです。

クオートがロードされ、クラス内に入ると、MetaTrader 5のその他あらゆるデータ同様アクセス可能となります。これは CQuotesList クラスに実装されている特殊な MetaTrader 5 タイプのプログラムインターフェースによって行われます。

関数呼び出しとデータレンダーリングは OnTick() ブロック内で行われます。以下がその内容です。

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   if(!AccountInfoInteger(ACCOUNT_TRADE_EXPERT))
      return;
   if(!QuotesList.LoadQuotes())    
      return;   
   PrintQuote quote = {0};
   Panel.DrawAccess(QuotesList.CountAccess());
   double ask = SymbolInfoDouble(Symbol(), SYMBOL_ASK);
   double bid = SymbolInfoDouble(Symbol(), SYMBOL_BID);
   string brokerName = AccountInfoString(ACCOUNT_COMPANY);
   for(int i = 0; i < QuotesList.BrokersTotal(); i++)
   {
      if(!QuotesList.BrokerSelect(i))
         continue;
      if(!QuotesList.SymbolSelect(Symbol()))
         continue;
      quote.ask = QuotesList.QuoteInfoDouble(QUOTE_ASK);
      quote.bid = QuotesList.QuoteInfoDouble(QUOTE_BID);
      quote.delta_ask = ask - quote.ask;
      quote.delta_bid = quote.bid - bid;
      quote.broker_name = QuotesList.BrokerName();
      quote.index = i;
      Panel.DrawBroker(quote);
   }
  }

サンプルコードはそれ以上の変更なしに MetaTrader 4 および MetaTrader 5 で動作するのは注目に値します!

ターミナルの異なるバージョンでパネルの表示方法に表面的な多少の違いがあるだけです。これは間違いなくプラットフォーム間でのコード移植を促進する驚くべき事実です。

EA の動作は最高のダイナミクスで観察されます。以下のビデオは異なるアカウントにおける EA 動作をお目にかけます。


ファイルからの読み出し、ファイルへの書き込みは多大なメリットがありますが、デメリットもあります。

主なメリットは:

  1. 柔軟性 そのようなデータも格納しロードすることができます。クラス全体もです。
  2. 比較的高速 読み出しと書き込みのサイクル全体はほぼつねに1ミリ秒もかかりません。それは80~150ミリ秒、あるいはときとしてもっと長くかかる比較的遅いトレード処理と比べると優秀な長さです。
  3. DLL を呼ばない MQL5 言語の標準ツールを基にしている。

そのソリューションの主なデメリットはストレージシステムに与える深刻な負荷です。クオート1件にブローカーが2者いると、書き直し処理の数は比較的小さいものですが、クオートの重い流れと大きい数量のブローカー/シンボルがあると書き直し処理の数はひじょうに大きいものとなります。1時間以内でデモ EA は 90,000 以上の Quotes.xml ファイル書き直し処理を行いました。こういった統計は EA パネルの一番上に表示されています。: "I/O Rewrite" はファイル書き変えトータル数、"fps" は過去2度の書き換え処理間の比率、"Avrg" は1秒単位での書き換えの平均速度を表示しています。

SSD または HDD にファイルと格納すれば、処理はディスクのライフタイムに悪い影響を与えます。そのためそういったデータ交換には仮想 RAM ディスクの使用が適しています。

上記例とは異なり、HedgeTerminal はActivePositions.xmlを控えめに使用し、グローバルコンテキストからはアクセスできない重大なポジション変化のみ書きます。よってそれは上記の例よりは少ない読み出し/書き込み処理を行うため、RAM ディスクのような特殊な条件は必要ありません。


4.2. Expert Advisor 間のマルチスレッド連携の使用

独立した MQL プログラム間のリアルタイムの連携は複雑ですが、興味を引かれるテーマです。本稿ではそれについては簡単な説明しかしていませんが、別の記事で述べる価値があるものです。ほとんどの場合、 Expert Advisor 間のマルチスレッド連携は必要ありません。ただここにタスクとさまざまなプログラムのリストがあります。それにはそういった連携を行うことが必要です。


添付資料解説

本稿に添付のあるファイルについて簡潔に説明します。またコンパイル手順についてもお話します。

Prototypes.mqh は HedgeTerminalAPI ライブラリ関数についての説明があるファイルです。このファイルには HedgeTerminalAPI ライブラリからの関数解説とプロトタイプが入っています。それによりお手持ちの EA は、ライブラリ内でどんな関数とモディファイアが利用できるのか、また関数の呼び出し方法、関数がどんな値を返すのか知ることができます。

このファイルをファイル C:\Program Files\MetaTrader 5\MQL5\Includeに保存します。そこでは "C:\Program Files\MetaTrader 5\" がお手持ちの MetaTrader 5 ターミナルがインストールされるディレクトリ名です。ファイルが正しいディレクトリにコピーされると、みなさんの MQL プログラム内でそれを 参照 することができます。これは HedgeTerminalAPI ライブラリを使用する必要があるときはいつも行います。Prototypes.mqh ファイルを参照するには、コードに特別なファイルインクルードディレクトリを追加します。

#include <Prototypes.mqh>
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   //...
   // Here is the content of your program.
   //...
  }

上記例では、この命令は黄色でマークされ "#include <Ptototypes.mqh>"と名づけられています。上記のスクリプトはライブラリ関数を参照し、その機能性を利用することができます。

HedgeTerminalAPI ライブラリ作成過程では、プロトタイプのファイルには少し変更が加わることに注意してください。よくあることですが、ライブラリのバージョンアップに伴い、プロトタイプファイルを更新する必要が生じます。それは変更を記述することになるものです。ご理解の上、この不都合な要因を受け入れてください。いずれにせよ、プロトタイプファイルの最新バージョンはつねにライブラリ(インストール手順はセクション 1.1で説明されています)からマニュアルでインストールすることができます。または本稿の添付(添付資料には定期的な更新があります)からダウンロードすることができます。

Chaos2.mqh は Chaos2 EA のソースコードです。その処理についてはセクション 1.12『Chaos II EA 例による SendTradeRequest 関数と HedgeTradeRequest 関数例』にあります。コードを正常にコンパイルするには、関数プロトタイプのファイルを対応するディレクトリ \Include に HedgeTerminalAPI ライブラリを C:\Program Files\MetaTrader 5\MQL5\Market\hedgeterminalapi.ex5に保存します。ここで "C:\Program Files\MetaTrader 5\" は、お手持ちの MetaTrader 5 ターミナルがインストールされるディレクトリ(ターミナルデータフォルダ)名です。

UnitedExchangeQuotes ソースコード は、第4章『Meta Trader 5 IDE におけるマルチスレッドプログラミングの基本』で説明されているプロジェクトを持つ特別な zip 形式のアーカイブ(unitedexchangequotes.zip)です。この zip には以下のファイルがあります。

アーカイブ内のファイルはすべて相対パスを持ちます。たとえば、ファイル UnitedExchangeQuotes.mq5 はフォルダ \MQL5\Experts内にあります。これは、C:\Program Files\MetaTrader 5\MQL5\Experts\UnitedExchangeQuotes.mq5など、MetaTrader 5 ターミナルのデータフォルダの同じサブディレクトリに入れる必要があるということです。


おわりに

HedgeTerminal プログラムインターフェースとの連携についての詳細を考察してきました

このライブラリの原則は MetaTrader 4 API にひじょうに似ていることを示しました。Like in MetaTrader 4 API のようにトランザクション(MetaTrader 4 での『オーダー』の概念の類似体)を処理する前に、まず TransactionSelect() によってそれを選択します。Hedge Terminal でのトランザクションとは通常、双方向ポジションを言います。ポジションが選択されると、そのプロパティを取得、またはそれにトレード処理を行うことができます。たとえば、ストップロスレベルを設定したり、ポジションをクローズすることができるのです。この処理シーケンスは MetaTrader 4 でオーダーを処理するアルゴリズムにほとんどそっくりです。

双方向ポジションとそのプロパティの数に関する基礎情報に加え、HedgeTerminal は MetaTrader 5 では直接利用できず複雑な分析計算を必要とする値へアクセスできるようにしてくれます。たとえば、プロパティの1つをリクエストするだけで各双方向ポジションのスリッページ金額を確認することができます。選択したポジション内部でディール数を確認することもできます。そのような計算と要求されるディールの一致はすべてHedgeTerminal起動中『裏側』で行われます。トレードを行うExpert Advisor 何も計算する必要がないため、それは便利です。必要な情報はすべてすでに計算され、シンプルで直観的な API によって利用可能となっているのです。

HedgeTerminal API とパネルによる共通アルゴリズムの利用により統一されたデータ提示が可能です。よってみなさんは HedgeTerminal パネルから EA を制御することができ、一方 EA が施す変更はパネルですぐに表示されることとなるのです。