English Русский 中文 Español Deutsch Português 한국어 Français Italiano Türkçe
MQL5 エキスパートアドバイザーから、GSMモデムを使用する

MQL5 エキスパートアドバイザーから、GSMモデムを使用する

MetaTrader 5統合 | 23 12月 2015, 13:46
669 0
Serhii Shevchuk
Serhii Shevchuk


はじめに

現在、トレーディングのアカウントを監視する手段がたくさんあります:モバイルターミナルはICQを用い、プッシュ通知を行います。しかし、すべてインターネットの接続を必要とします。この記事は、特に呼び出しやテキストメッセージはできるが、モバイルのインターネットを使用できないような時にトレーディングターミナルの情報を取得できるようになるエキスパートアドバイザーを作成するプロセスを紹介します。さらに、このエキスパートアドバイザーは、トレードサーバーとの再接続や、接続停止を知らせることができます。

モデム機能を持つ多くの電話と同様に、ほとんどのGSMモデムがこれを担います。最も幅広く使われているモデムであるため、Huawei E1550を例として選びました。さらに、この記事の最後に、古い携帯電話であるSiemens M55(2003年リリース)とそのモデムを取り替え、どうなるかを試してみます。

しかし、まずは、エキスパートアドバイザーからmドエムにバイトデータをどのように送信するかについて簡単に見ていきます。


1. COMポートを扱う

コンピューターにモデムを接続し、必要なドライバをすべてインストールすると、仮想COMポートをシステム上でご覧になれます。そのモデムでのすべての処理はこのポートを経由して実行されます。結果として、そのモデムとデータを交換するために、COMポートにまずアクセスする必要があります。

デバイスマネージャーに表示されるモデム

図1. Huaweiモデムは、COM3ポートに接続されています。

ここで、DLLライブラリのTrComPort.dllが必要です。それは、ソースファイルとともにインターネット上で無料で配られています。データの送受信だけでなく、COMポートの設定やその状態を参照するためにも用いられます。そのために、以下の関数を使用します;

#import "TrComPort.dll"
   int TrComPortOpen(int portnum);
   int TrComPortClose(int portid);   
   int TrComPortSetConfig(int portid, TrComPortParameters& parameters);
   int TrComPortGetConfig(int portid, TrComPortParameters& parameters);
   int TrComPortWriteArray(int portid, uchar& buffer[], uint length, int timeout);
   int TrComPortReadArray(int portid, uchar& buffer[], uint length, int timeout);   
   int TrComPortGetQueue(int portid, uint& input_queue, uint& output_queue);
#import

送信されたデータの種類は、MQL5と調和するようわずかに修正される必要があります。

TrComPortParametersストラクチャーは以下のとおりです;

struct TrComPortParameters
{
   uint   DesiredParams;
   int    BaudRate;           // Data rate
   int    DefaultTimeout;     // Default timeout (in milliseconds)
   uchar  ByteSize;           // Data size(4-8)
   uchar  StopBits;           // Number of stop bits
   uchar  CheckParity;        // Parity check(0-no,1-yes)
   uchar  Parity;             // Parity type
   uchar  RtsControl;         // Initial RTS state
   uchar  DtrControl;         // Initial DTR state
};

多くのデバイスが以下の設定で稼働します;8データビット、パリティチェック無し、1ストップビット。したがって、すべてのCOMポートパラメーターの中で、COMポートナンバーとデータレートを、エキスパートアドバイザーのパラメーターにのみ追加するのが妥当です。

input ComPortList    inp_com_port_index=COM3;   // Choosing the COM port
input BaudRateList   inp_com_baudrate=_9600bps; // Data rate

COMポート初期化関数は、以下の通りです;

//+------------------------------------------------------------------+
//| COM port initialization                                          |
//+------------------------------------------------------------------+
bool InitComPort()
  {
   rx_cnt=0;
   tx_cnt=0;
   tx_err=0;
//--- attempt to open the port
   PortID=TrComPortOpen(inp_com_port_index);
   if(PortID!=inp_com_port_index)
     {
      Print("Error when opening the COM port"+DoubleToString(inp_com_port_index+1,0));
      return(false);
     }
   else
     {
      Print("The COM port"+DoubleToString(inp_com_port_index+1,0)+" opened successfully");
      //--- request all parameters, so set all flags
      com_par.DesiredParams=tcpmpBaudRate|tcpmpDefaultTimeout|tcpmpByteSize|tcpmpStopBits|tcpmpCheckParity|tcpmpParity|tcpmpEnableRtsControl|tcpmpEnableDtrControl;
      //--- read current parameters 
      if(TrComPortGetConfig(PortID,com_par)==-1)
         ,bnreturn(false);//read error
      //
      com_par.ByteSize=8;                //8 bits
      com_par.Parity=0;                  //no parity check
      com_par.StopBits=0;                //1 stop bit
      com_par.DefaultTimeout=100;        //100 ms timeout
      com_par.BaudRate=inp_com_baudrate; //rate - from the parameters of the Expert Advisor
      //---
      if(TrComPortSetConfig(PortID,com_par)==-1)
         return(false);//write error
     }
   return(true);
  }

初期化に成功した場合、PortID変数は、オープンCOMポートの識別子を保存します。

識別子は、0から数字が割り振られので、COM3ポートの識別子は2になることに注意してください。そのポートがオープンになったので、モデムとデータを交換します。そして、モデムだけではありません。エキスパートアドバイザーからCOMポートへのアクセスは、半田付けが得意な人にはクリエィテイビティを発揮する良い機会になります:エキスパートアドバイザーをLEDか、移動テキストディスプレイに接続し、特定の通貨ペアの市場価格を示すようにします。

TrComPortGetQueue関数は、COMポートレシーバートランスミッターにてデータの詳細を取得するために使用されます。

int TrComPortGetQueue(
   int   portid,           // COM port identifier
   uint& input_queue,      // Number of bytes in the input buffer
   uint& output_queue      // Number of bytes in the output buffer
   );

エラーの場合、エラーコードの負の値を返します。エラーコードの詳細は、TrComPort.dllライブラリソースコードのあるアーカイブにてご覧になれます。

もしその関数がバッファーの受診にて非0データを返した場合、読み取られる必要があります。このために、TrComPortReadArray関数を使用します:

int TrComPortReadArray(
   int portid,             // Port identifier
   uchar& buffer[],        // Pointer to the buffer to read
   uint length,            // Number of data bytes
   int timeout             // Execution timeout (in ms)
   );

エラーの場合、エラーコードの負の値を返します。データバイト数は、TrComPortGetQueue関数によって返された値に一致しなければりません。

標準のタイムアウト(COMポートの初期化にて設定されます)を用いるために、-1の値を渡す必要があります。

COMポートにデータを送るためにTrComPortWriteArray関数を使用します。

int TrComPortWriteArray(
   int portid,             // Port identifier
   uchar& buffer[],        // Pointer to the initial buffer
   uint length,            // Number of data bytes
   int timeout             // Execution timeout (in ms)
   );

アプリケーション例「Hello World!」というメッセージの返信として、「Have a nice day」と送ります。

uchar rx_buf[1024];
uchar tx_buf[1024];
string rx_str;
int rxn, txn;
TrComPortGetQueue(PortID, rxn, txn);
if(rxn>0)
{  //--- data received in the receiving buffer
   //--- read
   TrComPortReadArray(PortID, rx_buf, rxn, -1);
   //--- convert to a string
   rx_str = CharArrayToString(rx_buf,0,rxn,CP_ACP);
   //--- check the received message (expected message "Hello world!"
   if(StringFind(rx_str,"Hello world!",0)!=-1)
   {//--- if we have a match, prepare the reply
      string tx_str = "Have a nice day!";
      int len = StringLen(tx_str);//get the length in characters
      //--- convert to uchar buffer
      StringToCharArray(tx_str, tx_buf, 0, len, CP_ACP);
      //--- send to the port
      if(TrComPortWriteArray(PortID, tx_buf, len, -1)<0) Print("Error when writing to the port");
   }
}

特別にポートクロージング関数に注意を払う必要があります。

int TrComPortClose(
   int portid         // Port identifier
   );  

この関数は、エキスパートアドバイザーのディイニシャライゼーションプロセスにて使用される必要があります。多くの場合、オープンにされているポートは、システムを再起動したのちに使用できるようになります。実際、モデムのオン・オフの切り替えは役に立ちません。


2. ATコマンドとモデムの取り扱い

АТコマンドを用いて、モデムの取り扱いを調整します。コンピューターからモバイルインターネットを用いたみなさんであれば、AT+CGDCONT=1,"IP","internet"というような、いわゆる「モバイル初期化String」をご存知のはずです。これがATコマンドの一つになります。それらはすべて接頭辞ATで始まり、0x0d (キャリッジリターン)で終わります。

望ましい機能を実装するために必要な最小のATコマンドを使用します。これは、様々なデバイスのコマンドとの調和を保証するための努力を削減します。

以下は、モデムを扱うためのハンドラにより用いられるATコマンドのリストです。

コマンド 詳細
ATE1 エコーを可能にする
AT+CGMI 製造者の名前を取得する
AT+CGMM デバイスのモデルを取得する
AT^SCKS SIMカードの状態を取得する
AT^SYSINFO システム情報を取得する
AT+CREG ネットワークレジストレーション状態を取得する
AT+COPS 現在のモバイルオペレーターの名前を取得する
AT+CMGF テキスト/PDUモードの間をスイッチする
AT+CLIP 発端末識別を可能にする
AT+CPAS モデムの状態を取得する
AT+CSQ シグナルクオリティを取得する
AT+CUSD USSDリクエストを送信する
AT+CALM サイレントモードをオンにする
AT+CBC バッテリーの状態を取得する
AT+CSCA SMSサービスセンターナンバーを取得する
AT+CMGL SMSメッセージリストを取得する
AT+CPMS SMSメッセージのためのメモリーを選択する
AT+CMGD SMSメッセージをメモリーから削除する
AT+CMGR メモリーからSMSメッセージを読み取る
AT+CHUP 着信拒否をする
AT+CMGS SMSメッセージを送る

ATコマンドの扱いに関する繊細な要素を紹介するというトピックから外れることはしmせん。技術フォーラムにて関連する情報がたくさんあります。さらに、すでにすべて実装されており、モデムを扱えるエキスパートアドバイザーを作成するためには、ヘッダーファイルを作成し、すでに出来上がっている関数とストラクチャーを用いて起動するだけで構いません。こちらについて紹介していきます。


2.1. 機能

COMポート初期化;

bool InitComPort();

返される値;もし初期化が成功していれば、true、さもなければ、falseです。モデムの初期化の前にOnInit()関数から呼ばれます。

COMポートディイニシャライゼーション:

void DeinitComPort();

返される値;NoneOnDeinit()関数から呼ばれます。

モデム初期化;

void InitModem();

返される値;NoneCOMポートの初期化に成功した後、OnInit()関数から呼ばれます。

モデムイベントハンドラ

void ModemTimerProc();

返される値;None1秒のインターバルにてOnTimer()関数から呼ばれます

モデムのメモリーからインデックスによりSMSメッセージを読み取る;

bool ReadSMSbyIndex(
   int index,             // SMS message index in the modem memory
   INCOMING_SMS_STR& sms  // Pointer to the structure where the message will be moved
   );

返される値;もし読み取りに成功すれば、true、さもなければ、falseです

モデムメモリーからインデックスによりSMSメッセージを削除する;

bool DelSMSbyIndex(
   int index              // SMS message index in the modem memory
   );

返される値;もし削除に成功すれば、true、さもなければ、falseです。

接続クオリティインデックスのString型への変換:

string rssi_to_str(
   int rssi               // Connection quality index, values 0..31, 99
   );

返される値: String型、 例えば、 "-55 dBm"です。

SMSメッセージの送信:

bool SendSMS(
   string da,      // Recipient's phone number in international format
   string text,    // Text of the message, Latin characters and numbers, maximum length - 158 characters
   bool flash      // Flash message flag
   );

返される値:もし送信に成功すれば、true、さもなければ、falseが返されます。SMSメッセージは、ラテン字で書かれた場合のみ送信されます。キリル文字は、SMS受信メッセージにのみサポートされています。flash=trueがセットされれば、フラッシュメッセージが送信されます。

2.2. イベント(モデムハンドラにより呼ばれる関数)

モデムステートストラクチャーのデータの更新

void ModemChState();

渡されるパラメーター;Noneこの関数がモデムハンドラに呼ばれれば、データがモデムストラクチャーにて更新されていることを示します。(ストラクチャーの詳細が以下に示されます)

着信電話:

void IncomingCall(
   string number          // Caller number
   );

渡されるパラメーター: 呼び出し者の数字この関数がモデムハンドラにより呼び出された時、その番号からの着信呼び出しが受信され、拒否されることを示します。

新規SMS受信メッセージ:

void IncomingSMS(
   INCOMING_SMS_STR& sms  // SMS message structure
   );

渡されるパラメーター: SMSメッセージストラクチャー (ストラクチャーの詳細は以下に示されています。). この関数がそのモデムハンドラにより呼び出され他時、モデムメモリー内で読まれていない新規SMSメッセージが一つ以上あることを示します。もし読まれていないメッセージの数が1より多ければ、最も最新のメッセージがこの関数に渡されます。

SMSメモリー満タン:

void SMSMemoryFull(
   int n                  // Number of SMS messages in the modem memory
   );

渡されるパラメーター:モデムメモリー内のメッセージ数この関数はモデムハンドラにより呼び出され、SMSメモリーが満タンで、そのモデムがメモリに空きが出るまで新着メッセージを受け取れないことを示します。

2.3. モデムパラメーターのステートストラクチャー

struct MODEM_STR
{
   bool     init_ok;          // The required minimum initialized
   //
   string   manufacturer;     // Manufacturer
   string   device;           // Model
   int      sim_stat;         // SIM status
   int      net_reg;          // Network registration state
   int      status;           // Modem status
   string   op;               // Operator
   int      rssi;             // Signal quality
   string   sms_sca;          // SMS center number
   int      bat_stat;         // Battery state
   int      bat_charge;       // Battery charge in percent (applicability depends on bat_stat)
   //
   double   bal;              // Mobile account balance
   string   exp_date;         // Mobile number expiration date
   int      sms_free;         // Package SMS available
   int      sms_free_cnt;     // Counter of package SMS used
   //
   int      sms_mem_size;     // SMS memory size
   int      sms_mem_used;     // Used SMS memory size
   //
   string   incoming;         // Caller number
};

MODEM_STR modem;

このストラクチャーは、もっぱらそのモデムイベントハンドラにより格納され、読み取りのみのため、その他の関数により用いられなければなりません。

以下は、ストラクチャーの詳細です。

要素 詳細
modem.init_ok そのモデムの初期化が成功したという印
falseという初期の値は、初期化が終了した後、trueに変わります。
modem.manufacturer モデム製造者, 例: "huawei".
初期の値は、"n/a"です.
modem.device モデムモデル、例: "E1550"
初期の値は、"n/a"です。
modem.sim_stat Simカードステータス以下の値を持ちます: -1 - データ無し 0 - カードがないか、ブロックされているか、故障中 1 - カード利用可能
modem.net_reg ネットワークレジストレーション状態以下の値を持ちます: -1 - データ無し 0 - レジスターされていない 1 - レジスターされている 2 - 検索する 3 - 禁止されている 4 - 状態未定義 5 - ローミングレジスター済み
modem.status モデムステータス以下の値を持ちます: -1 - 初期化 0 - 準備完了 1 - エラー 2 - エラー 3 - 着信電話 4 - アクティブコール
modem.op 現在のモバイルオペレーター
オペレーターの名前になるか(例: "MTS UKR"),
国際オペレーターコード(例; "25501")になります。
初期の値は "n/a"です
modem.rssi シグナルクオリティインデックス以下の値を持ちます: -1 - データ無し 0 - シグナル -113 dBmか、それ以下 1 - シグナル -111 dBm 2...30 - シグナル -109...-53 dBm 31 - シグナル -51 dBmか、それ以上 99 - データ無し Stringへの変換のために、rssi_to_str() 関数を使用してください
modem.sms_sca SMSサービスセンターナンバーSIMカードメモリーに含まれています。
SMSメッセージを生成するために必要です。
もしその番号がSIMカードメモリーに保存されていなければ、それは
エキスパートアドバイザーの入力パラメーターに明記されている番号と取り替えられます。
modem.bat_stat モデムバッテリーステータス (携帯電話のみ適用).
以下の値を持ちます。: -1 - データ無し 0 - そのデバイスはバッテリーパワーにより稼働します。 1 - そのバッテリーは利用できますが、デバイスのバッテリーは充電されていません 2 - バッテリーがありません 3 - エラー
modem.bat_charge パーセントでのバッテリーの変化
0から100までの値を持ちます
modem.bal モバイルアカウントバランス以下の
オペレータのレスポンスから関連のUSSDリクエストの値が取得されます。
初期値 (初期化以前): -10000.
modem.exp_date モバイルナンバー期限切れ日以下の
オペレータのレスポンスから関連のUSSDリクエストの値が取得されます。
初期値は、 "n/a"です。
modem.sms_free 利用できるパッケージSMS数以下の
初期値と使用されるSMSの数の差として計算されます。
modem.sms_free_cnt 使用されるパッケージSMSのカウンター以下の
オペレーターリスポンスから関連するUSSDリクエストまでの値が取得されます。初期値は-1です。
modem.sms_mem_size モデムSMSメモリーサイズ
modem.sms_mem_used 使用されるモデムSMSメモリー
modem.incoming 最後の着信者の番号
初期の値は、"n/a"です.

2.4. SMS メッセージストラクチャー

//+------------------------------------------------------------------+
//| SMS message structure                                            |
//+------------------------------------------------------------------+
struct INCOMING_SMS_STR
{
   int index;                //index in the modem memory
   string sca;               //sender's SMS center number
   string sender;            //sender's number
   INCOMING_CTST_STR scts;   //SMS center time label
   string text;              //text of the message
};

SMSセンタータイムラベルは、発信者からの特定の番号がSMSセンターにて受け取られた時間です。そのタイムラベルのストラクチャーは以下の通りです:

//+------------------------------------------------------------------+
//| Time label structure                                             |
//+------------------------------------------------------------------+
struct INCOMING_CTST_STR
{
   datetime time;            // time
   int gmt;                  // time zone
};

タイムゾーンは、15分のインターバルにて表されます。したがって、8の値はGMT+02:00に一致します。

受け取られたSMSメッセージのテキストは、ラテン文字かキリル文字で書かれます。7-bitとUCS2エンコーディングが受け取られたメッセージにおいてサポートされています。長いメッセージのマージングは、実装されていません。(この処理は短いコマンドのために設計されている観点により)

SMSメッセージは、もしラテン文字にて記載された場合のみ送信されます。最大のメッセージの長さは、158文字です。より長いメッセージの場合、特定の数を超えた文字なしで送信されます。


3. エキスパートアドバイザーを開発する

はじめに、TrComPort.dllLibrariesフォルダーにコピーし、インクルードフォルダーのComPort.mqh, modem.mqhsms.mqhに配置します。

Wizardを用いて、新しいエキスパートアドバイザーを作成し、モデムを扱うための最小値を追加します。以下がコードになります:

インクルードmodem.mqh:

#include <modem.mqh>

入力パラメーターを追加する;

input string         str00="COM port settings";
input ComPortList    inp_com_port_index=COM3;   // Selecting the COM port
input BaudRateList   inp_com_baudrate=_9600bps; // Data rate
//
input string         str01="Modem";
input int            inp_refr_period=3;         // Modem query period, sec
input int            inp_ussd_request_tout=20;  // Timeout for response to a USSD request, sec
input string         inp_sms_service_center=""; // SMS service center number
//
input string         str02="Balance";
input int            inp_refr_bal_period=12;    // Query period, hr
input string         inp_ussd_get_balance="";   // Balance USSD request
input string         inp_ussd_bal_suffix="";    // Balance suffix
input string         inp_ussd_exp_prefix="";    // Prefix of the number expiration date
//
input string         str03="Number of package SMS";
input int            inp_refr_smscnt_period=6;  // Query period, hr
input string         inp_ussd_get_sms_cnt="";   // USSD request for the package service status
input string         inp_ussd_sms_suffix="";    // SMS counter suffix
input int            inp_free_sms_daily=0;      // Daily SMS limit

モデムハンドラにより呼ばされる関数:

//+------------------------------------------------------------------+
//| Called when a new SMS message is received                        |
//+------------------------------------------------------------------+
void IncomingSMS(INCOMING_SMS_STR& sms)
{  
} 

//+------------------------------------------------------------------+
//| SMS memory is full                                               |
//+------------------------------------------------------------------+
void SMSMemoryFull(int n)
{
}

//+------------------------------------------------------------------+
//| Called upon receiving an incoming call                           |
//+------------------------------------------------------------------+
void IncomingCall(string number)
{ 
}  

//+------------------------------------------------------------------+
//| Called after updating data in the modem status structure         |
//+------------------------------------------------------------------+
void ModemChState()
{
   static bool init_ok = false;
   if(modem.init_ok==true && init_ok==false)
   {
      Print("Modem initialized successfully");      
      init_ok = true;
   }
}

1秒のインターバルでセットされたタイマー付きのモデム初期化とCOMポートがOnInit()関数に追加されなければなりません。

int OnInit()
{  //---COM port initialization
   if(InitComPort()==false)
   {
      Print("Error when initializing the COM"+DoubleToString(inp_com_port_index+1,0)+" port");
      return(INIT_FAILED);
   }      
   //--- modem initialization
   InitModem();
   //--- setting the timer
   EventSetTimer(1); //1 second interval
   //      
   return(INIT_SUCCEEDED);
}

OnTimer()関数にて、モデムハンドラを呼び出す必要があります。

void OnTimer()
{
//---
   ModemTimerProc();
}

OnDeinit()関数にてディイニシャライゼーションを呼び出す必要があります:

void OnDeinit(const int reason)
{
//--- destroy timer
   EventKillTimer();
   DeinitComPort();   
}

コードをコンパイルし、0エラー(s)を見ます。

それでは、エキスパートアドバイザーを作動させますが、DLLインポートを行い、モデムに関連したCOMポートを選択することを忘れないでください。"Expert Advisors" タブにて、以下のメッセージをご覧になれるはずです。:

最初の起動

図2. 起動性交後のエキスパートアドバイザーのメッセージ

同じメッセージを受け取ったのであれば、あなたのモデムがこのエキスパートアドバイザーを扱うことができるということです。この場合、次に進みます。

モデムパラメーターのビジュアル化のためのテーブルを描画します。OHLC線の下、ターミナルウィンドウの上左角に位置しています。テーブルにて使用されるテキストのフォントは、例えば、"Courier New"に固定されています。

//+------------------------------------------------------------------+
//| TextXY                                                           |
//+------------------------------------------------------------------+
void TextXY(string ObjName,string Text,int x,int y,color TextColor)
  {
//--- display the text string
   ObjectDelete(0,ObjName);
   ObjectCreate(0,ObjName,OBJ_LABEL,0,0,0,0,0);
   ObjectSetInteger(0,ObjName,OBJPROP_XDISTANCE,x);
   ObjectSetInteger(0,ObjName,OBJPROP_YDISTANCE,y);
   ObjectSetInteger(0,ObjName,OBJPROP_COLOR,TextColor);
   ObjectSetInteger(0,ObjName,OBJPROP_FONTSIZE,9);
   ObjectSetString(0,ObjName,OBJPROP_FONT,"Courier New");
   ObjectSetString(0,ObjName,OBJPROP_TEXT,Text);
  }
//+------------------------------------------------------------------+
//| Drawing the table of modem parameters                            |
//+------------------------------------------------------------------+
void DrawTab()
  {
   int   x=20, //horizontal indent
   y = 20,     //vertical indent
   dy = 15;    //step along the Y-axis
//--- draw the background
   ObjectDelete(0,"bgnd000");
   ObjectCreate(0,"bgnd000",OBJ_RECTANGLE_LABEL,0,0,0,0,0);
   ObjectSetInteger(0,"bgnd000",OBJPROP_XDISTANCE,x-10);
   ObjectSetInteger(0,"bgnd000",OBJPROP_YDISTANCE,y-5);
   ObjectSetInteger(0,"bgnd000",OBJPROP_XSIZE,270);
   ObjectSetInteger(0,"bgnd000",OBJPROP_YSIZE,420);
   ObjectSetInteger(0,"bgnd000",OBJPROP_BGCOLOR,clrBlack);
//--- port parameters
   TextXY("str0",  "Port:            ", x, y, clrWhite); y+=dy;
   TextXY("str1",  "Speed:           ", x, y, clrWhite); y+=dy;
   TextXY("str2",  "Rx:              ", x, y, clrWhite); y+=dy;
   TextXY("str3",  "Tx:              ", x, y, clrWhite); y+=dy;
   TextXY("str4",  "Err:             ", x, y, clrWhite); y+=(dy*3)/2;
//--- modem parameters
   TextXY("str5",  "Modem:           ", x, y, clrWhite); y+=dy;
   TextXY("str6",  "SIM:             ", x, y, clrWhite); y+=dy;
   TextXY("str7",  "NET:             ", x, y, clrWhite); y+=dy;
   TextXY("str8",  "Operator:        ", x, y, clrWhite); y+=dy;
   TextXY("str9",  "SMSC:            ", x, y, clrWhite); y+=dy;
   TextXY("str10", "RSSI:            ", x, y, clrWhite); y+=dy;
   TextXY("str11", "Bat:             ", x, y, clrWhite); y+=dy;
   TextXY("str12", "Modem status:    ", x, y, clrWhite); y+=(dy*3)/2;
//--- mobile account balance
   TextXY("str13", "Balance:         ", x, y, clrWhite); y+=dy;
   TextXY("str14", "Expiration date: ", x, y, clrWhite); y+=dy;
   TextXY("str15", "Free SMS:        ", x, y, clrWhite); y+=(dy*3)/2;
//--- number of the last incoming call
   TextXY("str16","Incoming:        ",x,y,clrWhite); y+=(dy*3)/2;
//--- parameters of the last received SMS message
   TextXY("str17", "SMS mem full:    ", x, y, clrWhite); y+=dy;
   TextXY("str18", "SMS number:      ", x, y, clrWhite); y+=dy;
   TextXY("str19", "SMS date/time:   ", x, y, clrWhite); y+=dy;
//--- text of the last received SMS message
   TextXY("str20", "                 ", x, y, clrGray); y+=dy;
   TextXY("str21", "                 ", x, y, clrGray); y+=dy;
   TextXY("str22", "                 ", x, y, clrGray); y+=dy;
   TextXY("str23", "                 ", x, y, clrGray); y+=dy;
   TextXY("str24", "                 ", x, y, clrGray); y+=dy;
//---
   ChartRedraw(0);
  }

テーブルのデータを更新するために、RefreshTab()関数を使用します:

//+------------------------------------------------------------------+
//| Refreshing values in the table                                   |
//+------------------------------------------------------------------+
void RefreshTab()
  {
   string str;
//--- COM port index:
   str="COM"+DoubleToString(PortID+1,0);
   ObjectSetString(0,"str0",OBJPROP_TEXT,"Port:            "+str);
//--- data rate:
   str=DoubleToString(inp_com_baudrate,0)+" bps";
   ObjectSetString(0,"str1",OBJPROP_TEXT,"Speed:           "+str);
//--- number of bytes received:
   str=DoubleToString(rx_cnt,0)+" bytes";
   ObjectSetString(0,"str2",OBJPROP_TEXT,"Rx:              "+str);
//--- number of bytes transmitted:
   str=DoubleToString(tx_cnt,0)+" bytes";
   ObjectSetString(0,"str3",OBJPROP_TEXT,"Tx:              "+str);
//--- number of port errors:
   str=DoubleToString(tx_err,0);
   ObjectSetString(0,"str4",OBJPROP_TEXT,"Err:             "+str);
//--- modem manufacturer and model:
   str=modem.manufacturer+" "+modem.device;
   ObjectSetString(0,"str5",OBJPROP_TEXT,"Modem:           "+str);
//--- SIM card status:
   string sim_stat_str[2]={"Error","Ok"};
   if(modem.sim_stat==-1)
      str="n/a";
   else
      str=sim_stat_str[modem.sim_stat];
   ObjectSetString(0,"str6",OBJPROP_TEXT,"SIM:             "+str);
//--- network registration:
   string net_reg_str[6]={"No","Ok","Search...","Restricted","Unknown","Roaming"};
   if(modem.net_reg==-1)
      str="n/a";
   else
      str=net_reg_str[modem.net_reg];
   ObjectSetString(0,"str7",OBJPROP_TEXT,"NET:             "+str);
//--- name of mobile operator:
   ObjectSetString(0,"str8",OBJPROP_TEXT,"Operator:        "+modem.op);
//--- SMS service center number
   ObjectSetString(0,"str9",OBJPROP_TEXT,"SMSC:            "+modem.sms_sca);
//--- signal level:
   if(modem.rssi==-1)
      str="n/a";
   else
      str=rssi_to_str(modem.rssi);
   ObjectSetString(0,"str10",OBJPROP_TEXT,"RSSI:            "+str);
//--- battery status (applicable to phones):
   string bat_stats_str[4]={"Ok, ","Ok, ","No","Err"};
   if(modem.bat_stat==-1)
      str="n/a";
   else
      str=bat_stats_str[modem.bat_stat];
   if(modem.bat_stat==0 || modem.bat_stat==1)
      str+=DoubleToString(modem.bat_charge,0)+"%";
   ObjectSetString(0,"str11",OBJPROP_TEXT,"Bat:             "+str);
//--- modem status:
   string modem_stat_str[5]={"Ready","Err","Err","Incoming call","Active call"};
   if(modem.status==-1)
      str="init...";
   else
     {
      if(modem.status>4 || modem.status<0)
         Print("Unknown modem status: "+DoubleToString(modem.status,0));
      else
         str=modem_stat_str[modem.status];
     }
   ObjectSetString(0,"str12",OBJPROP_TEXT,"Modem status:    "+str);
//--- mobile account balance:
   if(modem.bal==-10000)
      str="n/a";
   else
      str=DoubleToString(modem.bal,2)+" "+inp_ussd_bal_suffix;
   ObjectSetString(0,"str13",OBJPROP_TEXT,"Balance:         "+str);
//--- mobile number expiration date:
   ObjectSetString(0,"str14",OBJPROP_TEXT,"Expiration date: "+modem.exp_date);
//--- package SMS available:
   if(modem.sms_free<0)
      str="n/a";
   else
      str=DoubleToString(modem.sms_free,0);
   ObjectSetString(0,"str15",OBJPROP_TEXT,"Free SMS:        "+str);
//--- SMS memory full:
   if(sms_mem_full==true)
      str="Yes";
   else
      str="No";
   ObjectSetString(0,"str17",OBJPROP_TEXT,"SMS mem full:    "+str);
//---
   ChartRedraw(0);
  }

DelTab()関数はテーブルを削除します;

//+------------------------------------------------------------------+
//| Deleting the table                                               |
//+------------------------------------------------------------------+
void DelTab()
  {
   for(int i=0; i<25; i++)
      ObjectDelete(0,"str"+DoubleToString(i,0));
   ObjectDelete(0,"bgnd000");
  }

テーブルを扱うためにイベントハンドラOnInit()OnDeinit()ModemChState()に関数を追加しましょう:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- COM port initialization
   if(InitComPort()==false)
     {
      Print("Error when initializing the COM port"+DoubleToString(inp_com_port_index+1,0));
      return(INIT_FAILED);
     }
//---
   DrawTab();
//--- modem initialization
   InitModem();
//--- setting the timer 
   EventSetTimer(1);//1 second interval
//---
   RefreshTab();
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- destroy timer
   EventKillTimer();
   DeinitComPort();
   DelTab();
  }
//+------------------------------------------------------------------+
//| ModemChState                                                     |
//+------------------------------------------------------------------+
void ModemChState()
  {
   static bool init_ok=false;
//Print("Modem status changed");
   if(modem.init_ok==true && init_ok==false)
     {
      Print("Modem initialized successfully");
      init_ok=true;
     }
//---
   RefreshTab();
  }

さらに、IncomingCall()関数にテーブルの最後の着信番号を更新できる機能を追加します:

//+------------------------------------------------------------------+
//| Called upon receiving an incoming call                           |
//+------------------------------------------------------------------+
void IncomingCall(string number)
{    
   //--- update the number of the last incoming call:
   ObjectSetString(0, "str16",OBJPROP_TEXT, "Incoming:        "+number);   
}

コードをコンパイルし、エキスパートアドバイザーを稼働します。ターミナルウィンドウにて以下のレポートを見ることができるはずです。

モデムステータスパラメーター

図3. モデムパラメーター

モデムを呼び出してみてくださいその呼び出しは削除されず、あんたの番号が「着信」回線に表示されます。


4. USSDリクエストを扱う

使用可能時間が満たされていないモバイルアカウントがエキスパートアドバイザーの処理を妨害することもあります。したがって、そのアカウントの口座をチェックする関数が最も重要なものの一つにになります。. モバイルアカウントの口座をチェックするためにUSSDリクエストを使用します。さらに、使用できるパッケージの数に関する情報を取得するためにUSSdリクエストします。

リクエストを生成し、受け取られたレスポンスを処理するためのデータが入力パラメーターに保存されています;

input string         str02="=== Balance ======";
input int            inp_refr_bal_period=12;  //query period, hr
input string         inp_ussd_get_balance=""; //balance USSD request
input string         inp_ussd_bal_suffix="";  //balance suffix
input string         inp_ussd_exp_prefix="";  //prefix of the number expiration date
//
input string         str03="= Number of package SMS ==";
input int            inp_refr_smscnt_period=6;//query period, hr
input string         inp_ussd_get_sms_cnt=""; //USSD request for the package service status
input string         inp_ussd_sms_suffix="";  //SMS counter suffix
input int            inp_free_sms_daily=0;    //daily SMS limit

もしリクエスト番号が明記されていなければ、そのリクエストが処理されません。かわりに、そのリクエストはモデムの初期化後に送信され、繰り返して特定の期間の後に送信されます。さらに、そのリクエストはもしあなたのモデムが関連のITコマンドをサポートしていなければ、処理されません。(古い電話モデルが関連しています)

以下のバランスリクエストの後、オペレーターから以下のレスポンスを受け取ると想定してください:

7.13UAH,22.05.2014に失効電話プラン - Super MTS 3D Null 25.

レスポンスをハンドラが正確に特定すると保証するために、バランスの接尾辞は、"UAH"に設定され、番号失効期日の接頭辞が"expires on"である必要があります。

エキスパートアドバイザーは、SMSメッセージを送ることが期待されているので、オペレーターからSMSパッケージを購入し、少額でSMSメッセージを特定数取得できるサービスを使用できるようにする方が良いです。この場合、パッケージSMSが未だ使用できる回数を知れることはとても便利です。これは、USSDリクエストでも実行できます。オペレーターは普通利用できるSMSの代わりに使用されたSMSの数に応じます。

以下のレスポンスをオペレーターから受け取ったと想定してください:

バランス: 本日ローカル呼び出し69分本日の使用: 0 SMS、0 MB.

この場合、SMSカウンターの接尾辞は"SMS"と設定され、その日毎の制限は、SMSパッケージの取引条件に一致して設定されます。例えば、もし毎日特定の30テキストメッセージを与えられ、リクエストが10の値を返した場合、30-10=20のSMSがあるということです。この番号は、ハンドラにより適切なモデムステータスストラクチャー要素に配置されます。

注意! USSDリクエスト番号に注意してください!間違ったリクエストを送ると望ましくない結果、例えば、不必要な支払いサービスをオンにするなどに繋がります!

エキスパートアドバイザーがUSDリクエストを扱い始まるように、関連する入力パラメーターを明記する必要があります。

例えば、ウクライナのモバイルオペレーター、MTS Ukraineにおけるパラメーターは以下の通りです:

使用可能なバランスにおけるリクエストのパラメーター

図4. 使用可能なバランスにおけるUSSDリクエストのパラメーター

使用できるパッケージSMSの数におけるリクエストのパラメーター

図5. 使用できるパッケージSMSの数におけるUSSDリクエストのパラメーター

モバイルオペレーターに関連する値を設定するその後、モバイルアカウントの使用できるバランス雨と使用できるSMSの数は、モデムステータスの表に表示されます。

使用できるバランス

図6. USSDレスポンスから取得されるパラメーター

この記事執筆時、私のモバイルのオペレーターは、番号の期日日の代わりにクリスマス広告を送信していました。結果、そのハンドラは期日の値を取得できず、「Expriation」の行に「n_a」を見ることができます。すべてのオペレーターのレスポンスは、「Expert Advisor」タブにて表示されます。

オペレーターレスポンス

図7. 「Expert Advisotrs」タブに表示されるオペレーターレスポンス


5. SMSメッセージを送信する

役に立つ関数を追加していきます。例えば、現在の利益や資産、オープンポジション数を述べるSMSメッセージの送信です。送信は、着信呼び出しにより初期化されます。

そのようなレスポンスは、確実にアドミニストレーターの番号の場合のみ取得できます。したがって、別の入力パラメーターを作成します。

input string         inp_admin_number="+XXXXXXXXXXXX";//administrator's phone number

その番号は、国際フォーマットにて明記され、「+」が番号の前に含まれます。

その番号のチェックは、SMSテキスト生成や送信と同様、着信呼び出しハンドラに追加される必要があります。

//+------------------------------------------------------------------+
//| Called upon receiving an incoming call                           |
//+------------------------------------------------------------------+
void IncomingCall(string number)
{
   bool result;
   if(number==inp_admin_number)
   {
      Print("Administrator's phone number. Sending SMS.");
      //
      string mob_bal="";
      if(modem.bal!=-10000)//mobile account balance
         mob_bal = "\n(m.bal="+DoubleToString(modem.bal,2)+")";
      result = SendSMS(inp_admin_number, "Account: "+DoubleToString(AccountInfoInteger(ACCOUNT_LOGIN),0)   
               +"\nProfit: "+DoubleToString(AccountInfoDouble(ACCOUNT_PROFIT),2)
               +"\nEquity: "+DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY),2)
               +"\nPositions: "+DoubleToString(PositionsTotal(),0)
               +mob_bal
               , false);
      if(result==true)
         Print("SMS sent successfully");
      else
         Print("Error when sending SMS");               
   }   
   else
      Print("Unauthorized number ("+number+")"); 
   //--- update the number of the last incoming call:
   ObjectSetString(0, "str16",OBJPROP_TEXT, "Incoming:        "+number);   
}  

もしアドミニストレーターの番号inp_admin_numberからモデムに呼び出しがあれば、SMSメッセージはレスポンスにて送信されます。

着信呼び出しへのレスポンスでのSMS

図8. アドミニストレーターの番号から受け取られた呼び出しへのレスポンス時にの、エキスパートアドバイザーにより送信されるSMSメッセージ

オープンポジションの数やモバイルアカウントの残高と同様に、利益と資産の現在の値を見ることができます。


6. トレードーサーバーとの接続の監視

トレードサーバーとの接続の損失時と再接続時の通知を追加しましょう。このため、TERMINAL_CONNECTEDプロパティ識別子付きのTerminalInfoInteger()関数を用いて10秒毎にトレードサーバーの接続をチェックします。

短時間の接続ロスをフィルタリングするため、入力パラメーターのリストに追加される必要のあるヒステリシスを用います。

input int            inp_conn_hyst=6; //Hysteresis, х10 sec

6の値は、もし6x10=60秒間接続がない場合、接続がロスされたとみなされるという意味です。同様に、もし60秒以上の間使用できる場合、接続が再構築されたとみなされます。最初にレジスターされた接続損失のローカル時間が接続ロス時間とされ、接続できる状態になった最初のローカル時間がリカバリー時間とみなされます。

これを実装するため、OnTimer()関数を以下のコードに追加します:

static int s10 = 0;//pre-divider by 10 seconds
   static datetime conn_time;
   static datetime disconn_time;
   if(++s10>=10)
   {//--- once every 10 seconds
      s10 = 0;
      //
      if((bool)TerminalInfoInteger(TERMINAL_CONNECTED)==true)
      {
         if(cm.conn_cnt==0)             //first successful query in the sequence
            conn_time = TimeLocal();    //save the time
         if(cm.conn_cnt<inp_conn_hyst)
         {
            if(++cm.conn_cnt>=inp_conn_hyst)
            {//--- connection has been stabilized
               if(cm.connected == false)
               {//--- if there was a long-standing connection loss prior to that
                  cm.connected = true;
                  cm.new_state = true;
                  cm.conn_time = conn_time;
               }
            }
         }
         cm.disconn_cnt = 0;
      }
      else
      {
         if(cm.disconn_cnt==0)          //first unsuccessful query in the sequence
            disconn_time = TimeLocal(); //save the time
         if(cm.disconn_cnt<inp_conn_hyst)
         {
            if(++cm.disconn_cnt>=inp_conn_hyst)
            {//--- long-standing connection loss
               if(cm.connected == true)
               {//--- if the connection was stable prior to that
                  cm.connected = false;
                  cm.new_state = true;
                  cm.disconn_time = disconn_time;
               }
            }
         }
         cm.conn_cnt = 0;
      }
   }
   //
   if(cm.new_state == true)
   {//--- connection status changed
      if(cm.connected == true)
      {//--- connection is available
         string str = "Connected "+TimeToString(cm.conn_time,TIME_DATE|TIME_SECONDS);
         if(cm.disconn_time!=0)
            str+= ", offline: "+dTimeToString((ulong)(cm.conn_time-cm.disconn_time));
         Print(str);
         SendSMS(inp_admin_number, str, false);//sending message
      }
      else
      {//--- no connection
         string str = "Disconnected "+TimeToString(cm.disconn_time,TIME_DATE|TIME_SECONDS);
         if(cm.conn_time!=0)
            str+= ", online: "+dTimeToString((ulong)(cm.disconn_time-cm.conn_time));
         Print(str);
         SendSMS(inp_admin_number, str, false);//sending message
      }
      cm.new_state = false;
   }

cmストラクチャーは以下の通りです:

//+------------------------------------------------------------------+
//| Structure of monitoring connection with the terminal             |
//+------------------------------------------------------------------+
struct CONN_MON_STR
  {
   bool              new_state;    //flag of change in the connection status
   bool              connected;    //connection status
   int               conn_cnt;     //counter of successful connection queries
   int               disconn_cnt;  //counter of unsuccessful connection queries
   datetime          conn_time;    //time of established connection
   datetime          disconn_time; //time of lost connection
  };

CONN_MON_STR cm;//structure of connection monitoring

SMSメッセージのテキスト内に、トレードサーバーとの接続が失われた時間と、同様に、接続が構築された時間から接続ロス時間を引いた値が計算され、接続可能時間として記載されます。秒から、dd hh:mm:ssに変換するために、dTimeToString()関数に追加します。

string dTimeToString(ulong sec)
{
   string str;
   uint days = (uint)(sec/86400);
   if(days>0)
   {
      str+= DoubleToString(days,0)+" days, ";
      sec-= days*86400;
   }
   uint hour = (uint)(sec/3600);
   if(hour<10) str+= "0";
   str+= DoubleToString(hour,0)+":";
   sec-= hour*3600;
   uint min = (uint)(sec/60);
   if(min<10) str+= "0";
   str+= DoubleToString(min,0)+":";
   sec-= min*60;
   if(sec<10) str+= "0";
   str+= DoubleToString(sec,0);
   //
   return(str);
}

エキスパートが稼働されるたびに、エキスパートアドバイザーが構築された接続に関するテキストメッセージを送らないように、まるで接続がすでに構築されていたかのうようにcmストラクチャー要素に値をセットするconn_mon_init()関数を追加します。この場合、接続は、エキスパートアドバイザーを稼働時のローカル時刻にて構築されたとみなされます。この関数はOnInit() functionから呼ばれなければなりません。

void conn_mon_init()
{
   cm.connected = true;   
   cm.conn_cnt = inp_conn_hyst;
   cm.disconn_cnt = 0;
   cm.conn_time = TimeLocal();
   cm.new_state = false;
}

それでは、コンパイルし、エキスパートアドバイザーを稼働させましょう。それから、あなたのコンピューターのインターネットとの接続を切ってみてください。60秒にて、サーバーとの接続が失われたとのメッセージを受信できます。インターネットとの接続を再構築してみてください。60秒にて、非接続時間を述べ、接続の再構に関するメッセージを受信できます;築

接続損失のメッセージ 接続再構築のメッセージ

図9. サーバーとの接続の損失と再構築を通知するテキストメッセージ


7. ポジションのオープンとクローズに関するレポートの送信

ポジションのオープンとクローズを監視するため、OnTradeTransaction()関数に以下のコードを追加しましょう。:

void OnTradeTransaction(const MqlTradeTransaction& trans,
                        const MqlTradeRequest& request,
                        const MqlTradeResult& result)
{
//---
   if(trans.type==TRADE_TRANSACTION_DEAL_ADD)
   {
      if(trans.deal_type==DEAL_TYPE_BUY ||
         trans.deal_type==DEAL_TYPE_SELL)
      {
         int i;
         for(i=0;i<POS_BUF_LEN;i++)
         {
            if(ps[i].new_event==false)
               break;
         }   
         if(i<POS_BUF_LEN)
         {
            ps[i].new_event = true;
            ps[i].deal_type = trans.deal_type;
            ps[i].symbol = trans.symbol;
            ps[i].volume = trans.volume;
            ps[i].price = trans.price;
            ps[i].deal = trans.deal;
         }
      }
   }   
}

psは、POS_STRストラクチャーのバッファです:

struct POS_STR
{
   bool new_event;
   string symbol;
   ulong deal;
   ENUM_DEAL_TYPE deal_type; 
   double volume;
   double price;
};  

#define POS_BUF_LEN  3

POS_STR ps[POS_BUF_LEN];

そのバッファーは、短期間にてあるポジションがクローズされた場合、必要です。ポジションがオープンかクローズされた際、取引が履歴に追加されたのち、必要なパラメーターを取得し、new_event フラッグを設定します。

以下は、 new_eventフラッグを監視し、SMSレポートを生成するためのOnTimer()関数に追加されるコードです。

//--- processing of the opening/closing of positions
   string posstr="";
   for(int i=0;i<POS_BUF_LEN;i++)
   {
      if(ps[i].new_event==true)
      {
         string str;
         if(ps[i].deal_type==DEAL_TYPE_BUY)
            str+= "Buy ";
         else if(ps[i].deal_type==DEAL_TYPE_SELL)
            str+= "Sell ";
         str+= DoubleToString(ps[i].volume,2)+" "+ps[i].symbol;     
         int digits = (int)SymbolInfoInteger(ps[i].symbol,SYMBOL_DIGITS);
         str+= ", price="+DoubleToString(ps[i].price,digits);
         //
         long deal_entry;
         HistorySelect(TimeCurrent()-3600,TimeCurrent());//retrieve the history for the last hour
         if(HistoryDealGetInteger(ps[i].deal,DEAL_ENTRY,deal_entry)==true)
         {
            if(((ENUM_DEAL_ENTRY)deal_entry)==DEAL_ENTRY_IN)
               str+= ", entry: in";
            else if(((ENUM_DEAL_ENTRY)deal_entry)==DEAL_ENTRY_OUT)
            {
               str+= ", entry: out";
               double profit;
               if(HistoryDealGetDouble(ps[i].deal,DEAL_PROFIT,profit)==true)
               {
                  str+= ", profit = "+DoubleToString(profit,2);
               }
            }
         }           
         posstr+= str+"\r\n";
         ps[i].new_event=false;
      }
   }    
   if(posstr!="")
   {
      Print(posstr+"pos: "+DoubleToString(PositionsTotal(),0));
      SendSMS(inp_admin_number, posstr+"pos: "+DoubleToString(PositionsTotal(),0), false);
   }

それでは、コンパイルし、エキスパートアドバイザーを稼働させましょう。0.14ロットサイズのAUDCADを購入してみましょう。エキスパートアドバイザーは、以下のSMSメッセージを送信します:「Buy 0.14 AUDCAD, price=0.96538, entry: in」少し後に、ポジションをクローズし、ポジションのクローズに関する以下のテキストメッセージを取得します;

ポジションオープンのメッセージ ポジションクローズのメッセージ

図10. ポジションオープン(entry: in)とクローズ(entry: out)に関するテキストメッセージ



8. オープンポジションマネージメントにおける新規SMSメッセージの処理

今まで、エキスパートアドバイザーはアドミニストレーターの電話番号にのみメッセージを送信していました。SMSコマンドを受信し実行する方法を教えましょう。これは、すべて、もしくはいくつかのオープンポジションをクローズするのに役に立ちます。ご存知の通り、ポジションをクローズにするようなことはありません。

しかし、SMSメッセージが正しく受け取られるようにしなければなりません。このために、最後に受け取られたメッセージのディスプレィを IncomingSMS()関数に追加します:

//+------------------------------------------------------------------+
//| Called when a new SMS message is received                        |
//+------------------------------------------------------------------+
void IncomingSMS(INCOMING_SMS_STR& sms)
{
   string str, strtmp;
   //Number from which the last received SMS message was sent:
   ObjectSetString(0, "str18", OBJPROP_TEXT, "SMS number:      "+sms.sender);
   //Date and time of sending the last received SMS message:
   str = TimeToString(sms.scts.time,TIME_DATE|TIME_SECONDS);
   ObjectSetString(0, "str19", OBJPROP_TEXT, "SMS date/time:   "+str);
   //Text of the last received SMS message:
   strtmp = StringSubstr(sms.text, 0, 32); str = " ";
   if(strtmp!="") str = strtmp;    
   ObjectSetString(0, "str20", OBJPROP_TEXT, str);
   strtmp = StringSubstr(sms.text,32, 32); str = " ";
   if(strtmp!="") str = strtmp;    
   ObjectSetString(0, "str21", OBJPROP_TEXT, str);
   strtmp = StringSubstr(sms.text,64, 32); str = " ";
   if(strtmp!="") str = strtmp;    
   ObjectSetString(0, "str22", OBJPROP_TEXT, str);
   strtmp = StringSubstr(sms.text,96, 32); str = " ";
   if(strtmp!="") str = strtmp;    
   ObjectSetString(0, "str23", OBJPROP_TEXT, str);
   strtmp = StringSubstr(sms.text,128,32); str = " ";
   if(strtmp!="") str = strtmp;    
   ObjectSetString(0, "str24", OBJPROP_TEXT, str);  
}

SMSメッセージをモデムに送る場合、図表に表示されます。

新規SMSメッセージ

図11. ターミナルウィンドウに表示される新規SMSメッセージ

すべての新規SMSメッセージは以下の形式にて「Expert Advisors」タブに表示されます:<index_in_modem_memory>text_of_the_message:

「Expert Advisors」タブに表示されるSMSメッセージ

図12. 「Expert Advisors」タブに表示される新規SMSメッセージ

"close"という単語は、ディールをクローズするためのコマンドとして使用されます。すべてのポジションをクローズする場合、それはスペース、とパラメーター - クローズされる必要のあるポジションのシンボルか、"all"の後に続きます。そのケースは、メッセージのテキストを処理する前には重要でなく、StringToUpper()関数を使用します。そのメッセージを分析する際、送信者の電話番号が設定されたアドミニストレーターの番号にマッチするかチェックしてください。

さらに、SMSメッセージがかなり遅れて受信される場合があることに注意してください。(オペレーター側の技術的欠陥のためです)そのような場合、マーケットの状況が変わる可能性があるので、メッセージに受信されたコマンドは考慮しないでください。これを考慮して、別の入力パラメーターを紹介します。

input int            inp_sms_max_old=600; //SMS command expiration, sec

600の値は、600秒以上届けられるのにかかるコマンドが無視されるということを示します。例にて使用されている配達時間をチェックするメソッドは、SMSサービスセンターとエキスパートアドバイザーが稼働しているデバイスが同じタイムゾーンに位置することを示します。

SMSコマンドを処理するために IncomingSMS()関数に以下のコードを追加しましょう;

if(sms.sender==inp_admin_number)
   {
      Print("SMS from the administrator");
      datetime t = TimeLocal();
      //--- message expiration check
      if(t-sms.scts.time<=inp_sms_max_old)
      {//--- check if the message is a command
         string cmdstr = sms.text;
         StringToUpper(cmdstr);//convert everything to upper case
         int pos = StringFind(cmdstr, "CLOSE", 0);
         cmdstr = StringSubstr(cmdstr, pos+6, 6);
         if(pos>=0)
         {//--- command. send it for processing
            ClosePositions(cmdstr);            
         } 
      }
      else
         Print("The SMS command has expired");
   }  

もしそのSMSメッセージがアドミニストレーターから届けられれば、期限切れではなく、コマンドを表し("Close"というキーワードを含みます)、ClosePositions()関数により処理するためにパラメーターを送信します:

uint ClosePositions(string sstr)
{//--- close the specified positions
   bool all = false;
   if(StringFind(sstr, "ALL", 0)>=0)
      all = true;
   uint res = 0;
   for(int i=0;i<PositionsTotal();i++)
   {
      string symbol = PositionGetSymbol(i);
      if(all==true || sstr==symbol)
      {
         if(PositionSelect(symbol)==true)
         {
            long pos_type;
            double pos_vol;
            if(PositionGetInteger(POSITION_TYPE,pos_type)==true)
            {
               if(PositionGetDouble(POSITION_VOLUME,pos_vol)==true)
               {
                  if(OrderClose(symbol, (ENUM_POSITION_TYPE)pos_type, pos_vol)==true)
                     res|=0x01;
                  else
                     res|=0x02;   
               }
            }
         }
      }
   }
   return(res);
}

この関数は、オープンポジションがコマンドにて受け取られたパラメーターという観点において、マッチするかチェックします。OrderClose()関数を用いて、この条件を満たすポジションがクローズされます:

bool OrderClose(string symbol, ENUM_POSITION_TYPE pos_type, double vol)
{
   MqlTick last_tick;
   MqlTradeRequest request;
   MqlTradeResult result;
   double price = 0;
   //
   ZeroMemory(request);
   ZeroMemory(result);
   //
   if(SymbolInfoTick(Symbol(),last_tick))
   {
      price = last_tick.bid;
   }
   else
   {
      Print("Error when getting current prices");
      return(false);
   }
   //   
   if(pos_type==POSITION_TYPE_BUY)
   {//--- closing a BUY position - SELL
      request.type = ORDER_TYPE_SELL;
   }
   else if(pos_type==POSITION_TYPE_SELL)
   {//--- closing a SELL position - BUY
      request.type = ORDER_TYPE_BUY;
   }
   else
      return(false);
   //
   request.price = NormalizeDouble(price, _Digits);
   request.deviation = 20;
   request.action = TRADE_ACTION_DEAL;
   request.symbol = symbol;
   request.volume = NormalizeDouble(vol, 2);
   if(request.volume==0)
      return(false);
   request.type_filling = ORDER_FILLING_FOK;
   //
   if(OrderSend(request, result)==true)
   {
      if(result.retcode==TRADE_RETCODE_DONE || result.retcode==TRADE_RETCODE_DONE_PARTIAL)
      {
         Print("Order executed successfully");
         return(true);
      }
   }
   else
   {
      Print("Order parameter error: ", GetLastError(),", Trade server return code: ", result.retcode);     
      return(false);
   }      
   //
   return(false);
}

注文の処理が成功すれば、ポジションの変更を監視するための関数が生成され、SMS通知を送信します。


9. モデムメモリーからメッセージを削除する

モデムハンドラは新規SMSメッセージを削除しないことに注意してください。したがって、SMSメモリーが時間内に満タンになる時に、そのハンドラは、SMSMemoryFull()関数を呼び、モデムメモリの現在のメッセージ数を渡します。すべて削除するか、取捨選択することができます。そのモデムは、メモリが解放されるまで新しいメッセージを受け入れません。

//+------------------------------------------------------------------+
//| SMS memory is full                                               |
//+------------------------------------------------------------------+
void SMSMemoryFull(int n)
{
   sms_mem_full = true;
   for(int i=0; i<n; i++)
   {//delete all SMS messages
      if(DelSMSbyIndex(i)==false)
         break;
      else
         sms_mem_full = false;   
   }
}

処理されたのち、SMSメッセージを削除することができます。IncomingSMS()関数はモデムハンドラにより呼び出された際、INCOMING_SMS_STRストラクチャーは、モデムメモリのメッセージインデックスを渡します。それは、 DelSMSbyIndex()関数を用いて、処理の後すぐにメッセージを削除できるようにします;


結論

この記事は、GSMモデムを用いてトレーディングターミナルを監視できるエキスパートアドバイザーの開発を扱いました。SMS通知を用いた、オープンポジションや現在の利益、その他のデータに関する情報を取得におけるメソッドを見ました。また、SMSコマンドを用いてオープンポジション管理に関する基礎的な関数を実装しました。紹介した例は英語でのコマンドに関するものですが、ロシア語コマンドも同様に用いることができます(携帯電話でキーボードを変える時間を浪費しないために使えます)

最後に、10年前にリリースされた古い携帯電話を扱う際のエキスパートアドバイザーの動作をチェックしてみましょう。デバイス - Siemens M55. つなげてみましょう;

Siemens M55のパラメーターSiemens M55

図13. Siemens M55の接続

Siemens M55, "Expert Advisors" タブ

図14. Siemens M55の初期化, "Expert Advisors" タブ

必要なパラメーターすべてが取得されているとわかります。唯一の問題は、USSDリクエストから取得するデータです。Siemens M55 は、USSDリクエストを扱うAT commandをサポートしていません。それとは別に、その機能性は現在のモデムのものと同じくらいよく、エキスパートアドバイザーを扱うために使用されます。

MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/797

添付されたファイル |
trcomport.zip (30.99 KB)
modem.mqh (44.74 KB)
sms.mqh (9.46 KB)
gsminformer.mq5 (25.28 KB)
comport.mqh (5.18 KB)
MetaTrader 5の継続的な先物取引 MetaTrader 5の継続的な先物取引
先物取引の短期的なスパンは、テクニカル分析を複雑にします。短いチャートをテクニカル分析するのは難しいです。例えば、UX-9.13 Ukrainian Stockインデックス先物のディチャートにおけるバーの数は、100以上になります。したがって、トレーダーは総合的な長期の先物取引を作成します。この記事は、MetaTrader 5ターミナルにて、異なる日付の先物取引を組み合わせる方法を紹介します。
レンコチャートにおけるインジケーター レンコチャートにおけるインジケーター
この記事は、MQL5のレンコチャートとその実装の例を紹介します。このインジケーターの修正は、古典的なチャートとは異なります。インジケーターウィンドウ、メインチャート上の両方で構築できます。さらに、ジグザグインジケーターがあります。そのチャートの実装例をいくつかご確認ください。
ラグのないデジタルフィルターの作成 ラグのないデジタルフィルターの作成
本稿はストリームデータにおいて有用な信号(トレンド)を判断する方法の一つについて説明します。マーケットクオートに対して適用される小規模なフィルタリング(平滑化)テストが最終バーで再作成されないノンラギングデジタルフィルタ(インディケータ)作成の可能性を示しています。
MQL5クックブック - MQL5での未決注文の取り扱いとマルチカレンシーエキスパートアドバイザー MQL5クックブック - MQL5での未決注文の取り扱いとマルチカレンシーエキスパートアドバイザー
今回は、指値売り、逆指値売りなど未決注文に基づくトレーディングアルゴリズムを持つマルチカレンシーエキスパートアドバイザーを作成していきます。この記事は、以下を紹介します:特定の時間半位におけるトレーディング、未決注文の実行・修正・削除、最後のポジションが利取りや損切りにて閉じられたかのチェック、各シンボルにおける取引の履歴の管理などです。