
MQL5 エキスパートアドバイザーから、GSMモデムを使用する
はじめに
現在、トレーディングのアカウントを監視する手段がたくさんあります:モバイルターミナルは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.dll をLibrariesフォルダーにコピーし、インクルードフォルダーのComPort.mqh, modem.mqhとsms.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リクエストのパラメーター
図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メッセージはレスポンスにて送信されます。
図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メッセージをモデムに送る場合、図表に表示されます。
図11. ターミナルウィンドウに表示される新規SMSメッセージ
すべての新規SMSメッセージは以下の形式にて「Expert Advisors」タブに表示されます:<index_in_modem_memory>text_of_the_message:
図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. つなげてみましょう;
図13. Siemens M55の接続
図14. Siemens M55の初期化, "Expert Advisors" タブ
必要なパラメーターすべてが取得されているとわかります。唯一の問題は、USSDリクエストから取得するデータです。Siemens M55 は、USSDリクエストを扱うAT commandをサポートしていません。それとは別に、その機能性は現在のモデムのものと同じくらいよく、エキスパートアドバイザーを扱うために使用されます。
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/797





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