English Русский 中文 Español Deutsch 日本語 Português 한국어 Français Italiano
Bir MQL5 Expert Advisor'dan GSM Modem ile Çalışma

Bir MQL5 Expert Advisor'dan GSM Modem ile Çalışma

MetaTrader 5Entegrasyon | 14 Ocak 2022, 10:34
112 0
Serhii Shevchuk
Serhii Shevchuk

Giriş

Şu anda bir alım satım hesabının rahat bir şekilde uzaktan izlenmesi için makul sayıda araç var: mobil terminaller, push bildirimleri, ICQ ile çalışma. Ama hepsi internet bağlantısı gerektiriyor. Bu makale, mobil İnternet olmadığında bile çağrılar ve metin mesajları yoluyla alım satım terminalinizle iletişim halinde kalmanızı sağlayacak bir Expert Advisor oluşturma sürecini açıklamaktadır. Ek olarak, bu Expert Advisor, alım satım sunucusuyla olan bağlantının koptuğunu veya yeniden kurulduğunu size bildirebilecektir.

Bu amaçla, hemen hemen her GSM modem ve modem işlevine sahip çoğu telefon iş görecektir. Örnek olarak, bu modem türünün en yaygın kullanılan cihazlarından biri olduğu için Huawei E1550'i seçtim. Ayrıca, makalenin sonunda, modemi eski bir cep telefonu Siemens M55 (2003'te piyasaya sürüldü) ile değiştirmeye çalışacağız ve ne olacağını göreceğiz.

Ama önce, bir bayt verinin Expert Advisor'dan modeme nasıl gönderileceğine dair birkaç kelam.


1. COM Portu ile Çalışma

Modemi bilgisayarınıza bağladıktan ve gerekli tüm sürücüleri yükledikten sonra sistemde sanal bir COM portu görebileceksiniz. Modem ile gelecekteki tüm işlemler bu port üzerinden gerçekleştirilir. Sonuç olarak, modem ile veri alışverişi yapmak için önce COM portuna erişim sağlamalısınız.

Aygıt yöneticisinde görüntülenen modem

Şek. 1. Huawei modem COM3 bağlantı noktasına bağlı

Burada, İnternette serbestçe dağıtılan TrComPort.dll DLL kitaplığına ihtiyacımız olacak kaynak dosyalarla birlikte. COM portunu yapılandırmak, durumunu sorgulamak ve ayrıca veri almak ve göndermek için kullanılacaktır. Bunu yapmak için aşağıdaki işlevleri kullanacağız:

#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

İletilen veri türlerinin MQL5 ile uyumluluk için biraz değiştirilmesi gerekiyordu.

TrComPortParameters yapısı aşağıdaki gibidir:

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
};

Çoğu cihaz aşağıdaki ayarlarla çalışır: 8 veri biti, eşlik kontrolü yok, 1 stop biti. Bu nedenle, tüm COM port parametrelerinden yalnızca Expert Advisor'ın parametrelerine COM port numarasını ve veri hızını eklemek mantıklıdır:

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

COM port başlatma işlevi aşağıdaki gibi olacaktır:

//+------------------------------------------------------------------+
//| 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);
  }

Başarılı başlatma durumunda, PortID değişkeni, açık COM portunun tanımlayıcısını depolayacaktır.

Burada tanımlayıcıların sıfırdan numaralandırıldığına dikkat edilmelidir, bu nedenle COM3 port tanımlayıcısı 2'ye eşit olacaktır. Artık port açık olduğuna göre modem ile veri alışverişi yapabiliriz. Bu arada, sadece bir modemle değil. Expert Advisor'dan COM portuna erişim, lehimlemede iyi olanlar için büyük yaratıcılık fırsatları sunar: Expert Advisor'ı belirli döviz çiftlerinin özsermaye veya piyasa fiyatlarını göstermesi için bir LED'e veya hareketli bir metin ekranına bağlayabilirsiniz.

TrComPortGetQueue işlevi, COM bağlantı noktası alıcı ve vericisinin kuyruğundaki verilerin ayrıntılarını almak için kullanılacaktır:

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
   );

Bir hata durumunda, hata kodunun negatif değerini verir. Hata kodlarının ayrıntılı açıklaması arşivde TrComPort.dll kitaplığı kaynak kodlarıyla mevcuttur.

İşlev, alıcı arabelleğinde sıfır olmayan sayıda veri verirse, bunların okunması gerekir. Bunun için TrComPortReadArray işlevini kullanıyoruz:

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)
   );

Bir hata durumunda, hata kodunun negatif değerini verir. Veri baytlarının sayısı, TrComPortGetQueue işlevi tarafından verilen değere karşılık gelmelidir.

Varsayılan zaman aşımını kullanmak için (COM port başlatmada ayarlanır) -1 değerini iletmeniz gerekir.

COM portuna veri iletmek için TrComPortWriteArray işlevini kullanırız:

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)
   );

Uygulama örneği. "Merhaba dünya!" mesajına cevap olarak, "İyi günler!" göndermeliyiz.

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");
   }
}

Port kapatma işlevine özel dikkat gösterilmelidir:

int TrComPortClose(
   int portid         // Port identifier
   );  

Bu işlev, Expert Advisor başlatmadan kaldırma sürecinde her zaman mevcut olmalıdır. Çoğu durumda, açık bırakılan port ancak sistem yeniden başlatıldıktan sonra tekrar kullanılabilir hale gelir. Aslında, modemi kapatıp açmak bile yardımcı olmayabilir.


2. AT Komutları ve Modemle Çalışma

Bir modemle çalışma, АТ komutları kullanılarak düzenlenir. Bir bilgisayardan mobil İnternet kullanmış olanlarınız, kabaca aşağıdaki gibi görünen "modem başlatma dizesi" denen şeyi hatırlıyor olmalıdır: AT+CGDCONT=1,"IP","internet". Bu AT komutlarından biridir. Hemen hemen hepsi AT öneki ile başlar ve 0x0d (satır başı) ile biter.

İstenen işlevselliğin uygulanması için gereken minimum AT komut setini kullanacağız. Bu, komut setinin çeşitli cihazlarla uyumluluğunu sağlama çabasını azaltacaktır.

İşleyicimiz tarafından modemle çalışmak için kullanılan AT komutlarının listesi aşağıdadır:

 Komut Açıklama
  ATE1                                    
  Yankıyı etkinleştir
  AT+CGMI
  Üreticinin adını al
  AT+CGMM
  Cihazın modelini al
  AT^SCKS
  SIM kart durumunu al
  AT^SYSINFO
  Sistem bilgileri
  AT+CREG
  Ağ kayıt durumunu al
  AT+COPS
  Mevcut mobil operatörün adını al
  AT+CMGF
  Metin/PDU modları arasında geçiş yap
  AT+CLIP
  Arama hattı tanımlamasını etkinleştir
  AT+CPAS
  Modem durumunu al
  AT+CSQ
  Sinyal kalitesi al
  AT+CUSD
  USSD isteği gönder
  AT+CALM
  Sessiz modu etkinleştir (telefonlar için geçerlidir)
  AT+CBC
  Pil durumunu al (telefonlar için geçerlidir)
  AT+CSCA
  SMS servis merkezi numarasını al
  AT+CMGL
  SMS mesajlarının listesini al
  AT+CPMS
  SMS mesajları için hafıza seç
  AT+CMGD
  SMS mesajını hafızadan sil
  AT+CMGR
  Hafızadan SMS mesajını oku
  AT+CHUP
  Gelen aramayı reddet
  AT+CMGS
  SMS mesajı gönder


AT komutlarıyla çalışmanın inceliklerini açıklayarak konudan ayrılmayacağım. Teknik forumlarda konuyla ilgili birçok bilgi var. Ayrıca, her şey zaten uygulandı ve bir modem ile çalışabilen bir Expert Advisor oluşturmak için tek ihtiyacımız olan bir başlık dosyası eklemek ve hazır işlev ve yapıları kullanmaya başlamak. Detaylandıracağım konu da bu olacak.


2.1. İşlevler

COM port başlatma:

bool InitComPort();

Verilen değer: başarıyla başlatılırsa doğru, aksi takdirde yanlıştır. Modem başlatılmadan önce OnInit() işlevinden çağrılır.

COM port sıfırlama:

void DeinitComPort();

Dönüş değeri: hiçbiri. OnDeinit() işlevinden çağrılır.

Modem başlatma:

void InitModem();

Dönüş değeri: hiçbiri. COM portunun başarılı bir şekilde başlatılmasının ardından OnInit() işlevinden çağrılır.

Modem olayı işleyicisi:

void ModemTimerProc();

Dönüş değeri: hiçbiri. 1 saniyelik aralıklarla OnTimer() işlevinden çağrılır.

Modem hafızasından indekse göre SMS mesajı okuma:

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
   );

Verilen değer: başarıyla okunursa doğru, aksi takdirde yanlıştır.

Modem hafızasından indekse göre SMS mesajı silme:

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

Dönüştürülen değer: başarıyla silinirse - doğru, aksi takdirde - yanlış.

Bağlantı kalitesi indeksinin bir dizeye dönüştürülmesi:

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

Verilen değer: bir dize, örneğin "-55 dBm".

SMS mesajı gönderme:

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
   );

Dönüştürülen değer: başarıyla gönderilirse - doğru, aksi takdirde - yanlış. SMS mesajları yalnızca Latin karakterleri kullanılarak yazıldığında gönderilebilir. Kiril karakterleri yalnızca gelen SMS mesajları için desteklenir. flash=true ayarlanırsa bir hızlı mesaj gönderilir.


2.2. Olaylar (modem işleyicisi tarafından çağrılan işlevler)

Modem durum yapısındaki verilerin güncellenmesi:

void ModemChState();

Geçilen parametreler: yok. Bu işlev modem işleyicisi tarafından arandığında, modem yapısındaki verilerin güncellendiğini gösterir (yapının açıklaması aşağıda verilecektir).

Gelen çağrı:

void IncomingCall(
   string number          // Caller number
   );

Geçilen parametreler: arayan numarası. Bu işlev modem işleyicisi tarafından arandığında, 'numara' numarasından gelen aramanın kabul edildiğini ve reddedildiğini gösterir.

Yeni gelen SMS mesajı:

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

Geçilen parametreler: SMS mesaj yapısı (yapının açıklaması aşağıda verilecektir). Bu işlev modem işleyicisi tarafından arandığında, modem belleğinde bir veya daha fazla yeni okunmamış SMS mesajı olduğunu gösterir. Okunmamış mesaj sayısı birden fazlaysa, en son mesaj bu işleve iletilecektir.

SMS hafızası dolu:

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

Geçilen parametreler: modem belleğindeki mesaj sayısı. Bu işlev modem işleyicisi tarafından arandığında, SMS belleğinin dolu olduğunu ve bellek serbest bırakılıncaya kadar modemin yeni mesajları kabul etmeyeceğini gösterir.


2.3. Modem parametrelerinin durum yapısı

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;

Bu yapı yalnızca modem etkinlik işleyicisi tarafından doldurulur ve diğer işlevler tarafından yalnızca okuma için kullanılmalıdır.

Yapı öğelerinin açıklaması aşağıdadır:

 ÖğeAçıklama
  modem.init_ok
  Modemin başarıyla başlatıldığını gösteren bir gösterge.
  Başlatma tamamlandıktan sonra false başlangıç değeri true olur.
  modem.manufacturer
  Modem üreticisi, örneğin "huawei".
  Başlangıç değeri "n/a"dır.
  modem.device
  Modem modeli, ör. "E1550"
  Başlangıç değeri "n/a"dır.
  modem.sim_stat
  Sim kart durumu. Aşağıdaki değerleri alabilir:
  -1 - veri yok
   0 - kart eksik, bloke veya bozuk
   1 - kart mevcut
  modem.net_reg
  Ağ kayıt durumu. Aşağıdaki değerleri alabilir:
  -1 - veri yok
   0 - kayıtlı değil
   1 - kayıtlı
   2 - arıyor
   3 - yasaklandı
   4 - tanımsız durum
   5 - dolaşımda kayıtlı
  modem.status
  Modem durumu. Aşağıdaki değerleri alabilir:
  -1 - başlatma
   0 - hazır
   1 - hata
   2 - hata
   3 - gelen çağrı
   4 - aktif çağrı
  modem.op
  Mevcut mobil operatör.
  Operatör adına (örn. "MTS UKR"),
  veya uluslararası operatör koduna (örn. "25501") eşit olabilir.
 Başlangıç değeri "n/a"dır.
  modem.rssi
  Sinyal kalitesi endeksi. Aşağıdaki değerleri alabilir:
  -1 - veri yok
   0 - sinyal -113 dBm veya daha düşük
   1 - sinyal -111 dBm
   2...30 - sinyal -109...-53 dBm
  31 - sinyal -51 dBm veya daha yüksek
  99 - veri yok
  Bir dizgeye dönüştürmek için rssi_to_str() işlevini kullanın.
  modem.sms_sca
  SMS servis merkezi numarası. SIM kart hafızasında bulunur.
  Giden bir SMS mesajı oluşturmak için gereklidir.
  Nadir durumlarda, numara SIM kart belleğine kaydedilmezse Expert Advisor'ın
  giriş parametrelerinde belirtilen numara ile değiştirilecektir.
  modem.bat_stat
  Modem pil durumu (yalnızca telefonlar için geçerlidir).
  Aşağıdaki değerleri alabilir:
  -1 - veri yok
   0 - cihaz pil gücüyle çalışır
   1 - pil mevcut ancak cihaz pille çalışmıyor
   2 - pil yok
   3 - hata
  modem.bat_charge
  Yüzde olarak pil şarjı.
  0'dan 100'e kadar değer alabilir.
  modem.bal
  Mobil hesap bakiyesi. Değer, ilgili USSD talebine
  verilen operatör yanıtından elde edilir.
  Başlangıç değeri (başlatmadan önce): -10000.
  modem.exp_date
  Cep telefonu numarası son kullanma tarihi. Değer, ilgili USSD talebine
  verilen operatör yanıtından elde edilir.
  Başlangıç değeri "n/a"dır.
  modem.sms_free
  Mevcut paket SMS sayısı. Kullanılan SMS paketinin başlangıç
  numarası ile sayacı arasındaki fark olarak hesaplanır.
  modem.sms_free_cnt
  Kullanılan paket SMS sayacı. Değer, ilgili USSD talebine
  verilen operatör yanıtından elde edilir. Başlangıç değeri -1'dir.
  modem.sms_mem_size
  Modem SMS bellek boyutu.
  modem.sms_mem_used
  Kullanılan modem SMS belleği.
  modem.incoming
  Son arayanın numarası.
  Başlangıç değeri "n/a"dır.


2.4. SMS mesajı yapısı

//+------------------------------------------------------------------+
//| 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 merkezi zaman etiketi, göndericiden belirli bir mesajın SMS merkezine ulaştığı saattir. Zaman etiketi yapısı aşağıdaki gibidir:

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

Saat dilimi 15 dakikalık aralıklarla ifade edilir. Yani 8 değeri GMT+02:00'ye karşılık gelir.

Alınan SMS mesajının metni, Kiril karakterlerinin yanı sıra Latince kullanılarak da yazılabilir. Alınan mesajlarda 7 bit ve UCS2 kodlamaları desteklenir. Uzun mesajların birleştirilmesi uygulanamamaktadır (bu işlemin kısa komutlar için tasarlandığı gözükmektedir).

SMS mesajları yalnızca Latin karakterleri kullanılarak yazıldığında gönderilebilir. Maksimum mesaj uzunluğu 158 karakterdir. Daha uzun bir mesaj olması durumunda, belirtilen sayıyı aşan karakterler olmadan gönderilecektir.


3. Uzman Danışman Geliştirme

Başlangıç için, TrComPort.dll dosyasını Kütüphaneler klasörüne kopyalamanız ve ComPort.mqh, modem.mqh ve sms.mqh dosyasını Dahili klasörüne yerleştirmeniz gereklidir.

Ardından Sihirbazı kullanarak yeni bir Expert Advisor oluşturuyoruz ve modemle çalışmak için gereken minimum değeri ekliyoruz. Yani:

modem.mqh: ekleyin

#include <modem.mqh>

Giriş parametreleri ekleyin:

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

Modem işleyicisi tarafından çağrılan işlevler:

//+------------------------------------------------------------------+
//| 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;
   }
}

COM bağlantı noktası ve modem başlatma, 1 saniyelik aralıklarla ayarlanmış bir zamanlayıcı ile birlikte OnInit() işlevine eklenmelidir:

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() işlevinde modem işleyicisini aramamız gerekiyor:

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

OnDeinit() işlevinde COM port sıfırlamayı çağırmak gerekir:

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

Kodu derliyoruz ve görüyoruz: 0 hata(lar).

Şimdi Expert Advisor'ı çalıştırın ancak DLL içe aktarımına izin vermeyi ve modemle ilişkili COM bağlantı noktasını seçmeyi unutmayın. "Expert Advisors" sekmesinde aşağıdaki mesajları görebilmeniz gerekir:

İlk çalıştırma

Şek. 2. Başarılı bir çalışmanın ardından Expert Advisor'ın mesajları

Aynı mesajları alıyorsanız, modeminizin (telefonunuzun) bu Expert Advisor ile çalışmaya uygun olduğu anlamına gelir. Bu durumda ilerlemeye devam ediyoruz.

Modem parametrelerinin görselleştirilmesi için bir tablo çizelim. Terminal penceresinin sol üst köşesine, OHLC satırının altına yerleştirilecektir. Tabloda kullanılacak metin yazı tipi tek aralıklı olacaktır, örn. "Kurye Yeni".

//+------------------------------------------------------------------+
//| 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);
  }

Tablodaki verileri yenilemek için RefreshTab() işlevini kullanacağız:

//+------------------------------------------------------------------+
//| 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() işlevi tabloyu siler:

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

OnInit() ve OnDeinit() olay işleyicilerine ve ayrıca ModemChState() işlevine, tablo ile çalışması için işlevler ekleyelim:

//+------------------------------------------------------------------+
//| 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();
  }

Ayrıca, IncomingCall() işlevine tablodaki son gelen aramanın numarasını yenileme fırsatını ekleriz:

//+------------------------------------------------------------------+
//| 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);   
} 

Şimdi kodu derleyin ve Expert Advisor'ı çalıştırın. Aşağıdaki raporu terminal penceresinde görebilmeniz gereklidir:

Modem durumu parametreleri

Şek. 3. Modem parametreleri

Modeme çağrı yapmayı deneyin. Çağrı reddedilecek ve numaranız "Gelen" satırında görünecektir.


4. USSD İstekleriyle Çalışmak

Zamanında yükleme yapılmayan bir mobil hesap, en uygunsuz anda Expert Advisor'ın işleyişini bozabilir. Bu nedenle, hesap bakiyesini kontrol eden işlev en önemlilerinden biridir. Mobil hesap bakiyesini kontrol etmek için genellikle USSD isteklerini kullanırız. Ayrıca, mevcut paket SMS sayısı hakkında bilgi almak için USSD isteklerini kullanacağız.

İstek oluşturmaya ve alınan yanıtları işlemeye yönelik veriler, giriş parametrelerinde bulunur:

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

İstek numarası belirtilmezse istek işleme alınmayacaktır. Alternatif olarak, istek, modemin başlatılmasından hemen sonra gönderilir ve belirtilen süre sonunda tekrar tekrar gönderilir. Ayrıca, modeminiz (telefonunuz) ilgili BT komutunu desteklemiyorsa (eski cep telefonu modellerini ilgilendirir) istek işleme alınmayacaktır. 

Bakiye isteğini takiben operatörünüzden aşağıdaki yanıtı aldığınızı varsayın:

7.13 UAH, 22.05.2014 tarihinde sona eriyor. Telefon Planı - Süper MTS 3D Null 25.

İşleyicinin yanıtı doğru bir şekilde tanımlamasını sağlamak için bakiye son eki "UAH" olarak ayarlanmalı ve numara son kullanma tarihinin öneki "sona eriyor" olmalıdır.

Expert Advisor'ın çok sık SMS göndermesi beklendiği için operatörünüzden bir SMS paketi, yani cüzi bir ücret karşılığında belirli sayıda SMS aldığınız bir hizmet satın almanız iyi olur. Bu durumda kaç adet SMS paketinin kaldığını bilmek çok faydalı olabilir. Bu, bir USSD isteği kullanılarak da yapılabilir. Operatör genellikle mevcut SMS sayısı yerine kullanılan SMS sayısı ile yanıt verir.

Operatörünüzden aşağıdaki yanıtı aldığınızı varsayın:

Bakiye: Bugün içinde 69 dakikalık yerel çağrı. Bugün kullanılan: 0 SMS ve 0 MB.

Bu durumda SMS sayaç eki "SMS" olarak ayarlanır ve günlük limit SMS paketi hüküm ve koşullarına göre belirlenir. Örneğin size günde 30 mesaj veriliyorsa ve istek 10 değerini verdiyse 30-10=20 SMS'iniz var demektir. Bu numara, işleyici tarafından modem durum yapısının uygun elemanına yerleştirilecektir.

DİKKAT! USSD istek numaralarında çok dikkatli olun! Yanlış bir istek göndermenin istenmeyen sonuçları olabilir, örneğin bazı istenmeyen ücretli hizmetlerin etkinleştirilmesi!

Expert Advisor'ımızın USSD istekleri ile çalışmaya başlaması için ilgili giriş parametrelerini belirtmemiz yeterlidir.

Örneğin Ukraynalı mobil operatör MTS Ukraine için parametreler aşağıdaki gibi olacaktır:

Kullanılabilir bakiye isteğinin parametreleri

Şek. 4. Kullanılabilir bakiye için USSD isteği parametreleri

Kullanılabilir paket SMS sayısı isteğinin parametreleri

Şek. 5. Mevcut SMS paketi sayısı için USSD isteği parametreleri

Mobil operatörünüzle ilgili değerleri ayarlayın. Bundan sonra, mobil hesabınızdaki mevcut bakiye ve mevcut SMS sayısı, modem durumu tablosunda görüntülenecektir:

Kullanılabilir Bakiye

Şek. 6. USSD yanıtlarından elde edilen parametreler

Bu yazıyı yazarken, mobil operatörüm numara son kullanma tarihi yerine Noel reklamı gönderiyordu. Sonuç olarak, işleyici tarih değerini alamadı, bu nedenle "Son kullanma tarihi" satırında "yok" ifadesini görebiliriz. Lütfen tüm operatör yanıtlarının "Expert Advisor'lar" sekmesinde görüntülendiğini unutmayın.

Operatör yanıtları

Şek. 7. "Expert Advisor'lar" sekmesinde görüntülenen operatör yanıtları


5. SMS Mesajı Gönderme

Mevcut kârı, öz sermayeyi ve açık pozisyon sayısını belirten SMS mesajları göndermek gibi faydalı işlevler eklemeye başlayacağız. Gönderme, gelen bir çağrı ile başlatılacaktır.

Böyle bir yanıt kesinlikle yalnızca yönetici numarası olması durumunda beklenir, bu nedenle başka bir giriş parametremiz olacak:

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

Numara, numaradan önceki "+" dahil olmak üzere uluslararası formatta belirtilecektir.

Gelen çağrı işleyiciye numara kontrolü, SMS metin oluşturma ve gönderme eklenmelidir:

//+------------------------------------------------------------------+
//| 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);   
}  

Şimdi, yöneticinin inp_admin_number numarasından modeme bir çağrı olursa, yanıt olarak bir SMS mesajı gönderilir:

Gelen bir çağrıya yanıt olarak SMS

Şek. 8. Yöneticinin telefon numarasından gelen çağrıya cevaben Expert Advisor tarafından gönderilen SMS mesajı

Burada, mevcut kar ve öz sermaye değerlerini, açık pozisyon sayısını ve mobil hesap bakiyesini görebiliriz.


6. Alım Satım Sunucusu ile Bağlantıyı İzleme

Alım satım sunucusuyla bağlantının kesilmesi ve yeniden kurulması durumunda bildirimleri ekleyelim. Bu amaçla, alım satım sunucu bağlantısını TERMINAL_CONNECTED özellik tanımlayıcısıyla TerminalInfoInteger() kullanarak her 10 saniyede bir kontrol edeceğiz.

Kısa süreli bağlantı kayıplarını filtrelemek için giriş parametreleri listesine eklenmesi gereken histerezis kullanacağız:

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

6 değeri, 6*10=60 saniyeden fazla bağlantı olmaması durumunda bağlantının kopmuş sayılacağı anlamına gelir. Benzer şekilde, 60 saniyeden daha uzun süre kullanılabilir durumdaysa bağlantı yeniden kurulmuş olarak kabul edilecektir. İlk kaydedilen bağlantı eksikliğinin yerel saati, bağlantı kaybının zamanı olarak kabul edilirken, bağlantının kullanıma sunulduğu ilk yerel saat, kurtarma zamanı olarak kabul edilecektir.

Bunu uygulamak için OnTimer() işlevine aşağıdaki kodu ekliyoruz:

   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 yapısı aşağıdaki gibidir:

//+------------------------------------------------------------------+
//| 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 mesajının metninde, alım satım sunucusuyla bağlantının kesildiği (veya yeniden kurulduğu) zamanı ve ayrıca bağlantının mevcut olduğu (veya mevcut olmadığı) süreyi, bağlantının kesildiği ve kurulduğu zaman arasındaki fark olarak hesaplayacağız. Zaman farkını saniyeden gün:saat:dk'ye dönüştürmek için dTimeToString() işlevini ekleyeceğiz:

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);
}

Expert Advisor her çalıştırıldığında, Expert Advisor'ın kurulan bağlantı ile ilgili bir metin mesajı göndermemesini sağlamak için, cm yapı öğelerine, sanki bağlantı kurulmuş gibi değerleri ayarlayan bir conn_mon_init() işlevi ekliyoruz. Bu durumda, Expert Advisor'ın çalıştırıldığı yerel saatte bağlantı kurulmuş kabul edilecektir. Bu işlev OnInit() işlevinden çağrılmalıdır.

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;
} 

Şimdi Expert Advisor'ı derleyin ve çalıştırın. Ardından bilgisayarınızın İnternet bağlantısını kesmeyi deneyin. 60 (aşağı yukarı 10 saniye değişebilir) saniye içinde sunucuyla bağlantının kesildiğini söyleyen bir mesaj alacaksınız. İnternete tekrar bağlanın. 60 saniye içinde, yeniden kurulan bağlantı hakkında toplam bağlantının kesilme süresini belirten bir mesaj alacaksınız:

Bağlantının kesilmesiyle ilgili mesaj Bağlantının yeniden kurulmasıyla ilgili mesaj

Şek. 9. Sunucuyla bağlantının kesildiğini ve yeniden kurulduğunu bildiren kısa mesajlar


7. Pozisyon Açılış ve Kapanış Raporlarının Gönderilmesi

Pozisyonların açılış ve kapanışlarını izlemek için OnTradeTransaction() işlevine aşağıdaki kodu ekleyelim:

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;
         }
      }
   }    
}

burada ps, POS_STR yapılarının arabelleğidir:

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];

Kısa bir süre içinde birden fazla pozisyonun kapatılması (veya açılması) durumunda arabellek gereklidir. Bir pozisyon açıldığında veya kapatıldığında, yatırım geçmişe eklendikten sonra gerekli tüm parametreleri alıyoruz ve new_event bayrağını ayarlıyoruz.

new_event bayraklarını izlemek ve SMS raporları oluşturmak için OnTimer() işlevine eklenecek kod aşağıdadır:

   //--- 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);
   }

Şimdi Expert Advisor'ı derleyin ve çalıştırın. 0,14 lot büyüklüğünde AUDCAD almaya çalışalım. Expert Advisor aşağıdaki SMS mesajını gönderecektir: "0,14 AUDCAD satın al, fiyat=0.96538, giriş: in". Kısa bir süre sonra pozisyonu kapatıyoruz ve pozisyonun kapanmasıyla ilgili aşağıdaki metin mesajını alıyoruz:

Pozisyon açılışı ile ilgili mesaj Pozisyon kapanışı ile ilgili mesaj

Şek. 10. Pozisyon açılış (girdi: in) ve kapanış (girdi: out) ile ilgili metin mesajları


8. Açık Pozisyon Yönetimi için Gelen SMS Mesajlarının İşlenmesi

Expert Advisor'ımız şimdiye kadar sadece yöneticinin telefon numarasına mesaj göndermiştir. Şimdi ona SMS komutlarını almayı ve yürütmeyi öğretelim. Bu, örneğin tüm veya bazı açık pozisyonların kapatılmasında faydalı olabilir. Bildiğimiz gibi, pozisyonunuzu zamanında kapatmak gibisi yoktur.

Ancak öncelikle SMS mesajlarının doğru alındığından emin olmalıyız. Bunu yapmak için, son alınan mesajın görüntüsünü IncomingSMS() işlevine ekleriz:

//+------------------------------------------------------------------+
//| 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);  
}

Şimdi modeme bir SMS mesajı gönderirsek, tabloda görüntülenecektir:

Yeni SMS mesajı

Şek. 11. Terminal penceresinde görüntülendiği şekliyle gelen SMS mesajı

Lütfen tüm gelen SMS mesajlarının «"Expert Advisor" sekmesinde aşağıdaki biçimde görüntülendiğini unutmayın:<index_in_modem_memory> text_of_the_message:

"Expert Advisor'lar" sekmesinde görüntülenen SMS mesajı

Şek. 12. "Expert Advisor'lar" sekmesinde görüntülenen gelen SMS mesajı

"kapat" kelimesi, yatırımları kapatmak için bir komut olarak kullanılacaktır. Bunu boşluk ve parametre kapatılması gereken pozisyonun sembolü veya tüm pozisyonları kapatmanız gerekirse "tümü" takip etmelidir. Büyük veya küçük harf olması önemli değil zira mesaj metnini işlemeden önce StringToUpper() işlevini kullanıyoruz. Mesajı analiz ederken, gönderenin telefon numarasının yöneticinin ayarlanan numarasıyla eşleştiğinden emin olun.

Ayrıca, SMS mesajının ciddi bir gecikmeyle (operatör tarafındaki teknik aksaklıklar vb. nedeniyle) alındığı durumlar olabileceği unutulmamalıdır. Bu gibi durumlarda mesajda gelen komutu dikkate alamazsınız çünkü piyasa durumu değişmiş olabilir. Bunun ışığında, başka bir giriş parametresi sunuyoruz:

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

600 değeri, teslim edilmesi 600 saniyeden (10 dakika) uzun süren komutların yok sayılacağını gösterir. Lütfen örnekte kullanılan teslimat süresini kontrol etme yönteminin, SMS hizmet merkezinin ve Expert Advisor'ın çalıştığı cihazın aynı saat diliminde yer aldığını kastettiğini unutmayın.

SMS komutlarını işlemek için IncomingSMS() işlevine aşağıdaki kodu ekleyelim:

   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 mesajı yöneticiden teslim edildiyse, süresi dolmamıştır ve bir komutu temsil ediyordur ("Kapat" anahtar kelimesini içerir), parametresini ClosePositions() işlevi tarafından işlenmek üzere göndeririz:

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);
}

Bu işlev, komutta alınan parametre (sembol) açısından herhangi bir açık pozisyonla eşleşip eşleşmediğini kontrol eder. Bu koşulu sağlayan pozisyonlar, OrderClose() işlevi kullanılarak kapatılır:

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);
}

Talimatların başarılı bir şekilde işlenmesinden sonra, pozisyon değişikliklerini izleme işlevi bir SMS bildirimi oluşturacak ve gönderecektir.


9. Modem Belleğinden Mesaj Silme

Lütfen gelen SMS mesajlarının, modem işleyicisi tarafından kendi kendine silinmediğini unutmayın. Bu nedenle, SMS belleği zamanla dolduğunda, işleyici SMSMemoryFull() işlevini arayacak ve ona modem belleğindeki mevcut mesaj sayısını iletecektir. Hepsini silebilir veya seçerek silebilirsiniz. Bellek boşalıncaya kadar modem yeni mesajları kabul etmeyecektir.

//+------------------------------------------------------------------+
//| 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;   
   }
}

Ayrıca SMS mesajlarını işlendikten hemen sonra silebilirsiniz. IncomingSMS() işlevi modem işleyicisi tarafından arandığında, INCOMING_SMS_STR yapısı, modem belleğindeki mesaj indeksini geçirir, bu da mesajın işlemden hemen sonra silinmesine izin verir. DelSMSbyIndex() işlevi:


Sonuç

Bu makale, alım satım terminalini uzaktan izlemek için bir GSM modem kullanan Expert Advisor'ın geliştirilmesini ele almaktadır. Açık pozisyonlar, cari kar ve diğer veriler hakkında SMS bildirimleri ile bilgi alma yöntemlerini inceledik. Ayrıca SMS komutlarını kullanarak açık pozisyon yönetimi için temel fonksiyonları da uyguladık. Verilen örnekte İngilizce komutlar bulunmaktadır ancak Rusça komutları da aynı derecede iyi kullanabilirsiniz (telefonunuzda farklı klavye düzenleri arasında geçiş yaparak zaman kaybetmemek için).

Son olarak, 10 yılı aşkın bir süre önce piyasaya sürülen eski bir cep telefonuyla uğraşırken Expert Advisor'ımızın davranışını kontrol edelim. Cihaz - Siemens M55. Bağlayalım:

Siemens M55 parametreleriSiemens M55

Şek. 13. Siemens M55'in bağlanması

Siemens M55, "Expert Advisor'lar" sekmesi

Şek. 14. Siemens M55, "Expert Advisor'lar" sekmesinin başarıyla başlatılması

Gerekli tüm parametrelerin elde edildiğini görebilirsiniz. Tek sorun USSD isteklerinden aldığımız veriler. Mesele şu ki, Siemens M55, USSD istekleriyle çalışmak için AT komutunu desteklemiyor. Bunun dışında işlevselliği, günümüz modemlerininki kadar iyidir, bu nedenle Expert Advisor'ımızla çalışmak için kullanılabilir.


MetaQuotes Ltd tarafından Rusçadan çevrilmiştir.
Orijinal makale: https://www.mql5.com/ru/articles/797

Ekli dosyalar |
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)

Bu yazarın diğer makaleleri

MetaTrader 5'te sürekli vadeli işlem sözleşmeleri MetaTrader 5'te sürekli vadeli işlem sözleşmeleri
Vadeli işlem sözleşmelerinin kısa ömürleri teknik analizlerini karmaşık hale getirir. Kısa grafikleri teknik olarak analiz etmek zordur. Örneğin UX-9.13 Ukrayna Hisse stok endeksinin günlük grafiğindeki çubuk sayısı 100'den fazladır. Bu nedenle, yatırımcı sentetik uzun pozisyonlu vadeli işlem sözleşmeleri oluşturur. Bu makale, MetaTrader 5 terminalinde farklı tarihlere sahip vadeli işlem sözleşmelerinin nasıl birleştirileceğini açıklamaktadır.
Renko grafiği için gösterge Renko grafiği için gösterge
Makale, Renko grafiğinin bir örneğini ve bunun bir gösterge olarak MQL5'teki uygulamasını açıklamaktadır. Bu göstergenin modifikasyonları, onu klasik bir grafikten ayırır. Hem gösterge penceresinde hem de ana grafikte oluşturulabilir. Üstelik ZigZag göstergesi var. Grafik uygulamasının birkaç örneğini bulabilirsiniz.
Gecikmesiz Dijital Filtreler Oluşturma Gecikmesiz Dijital Filtreler Oluşturma
Makale, akış verilerinde yararlı bir sinyal (eğilim) belirleme yaklaşımlarından birini açıklamaktadır. Piyasa kotasyonlarına uygulanan küçük filtreleme (düzgünleştirme) testleri, son çubuklarda yeniden çizilmeyen gecikmesiz dijital filtreler (göstergeler) oluşturma potansiyelini gösterir.
MQL5 Yemek Kitabı - Çok Para Birimli Expert Advisor ve MQL5'te Bekleyen Talimatlarla Çalışma MQL5 Yemek Kitabı - Çok Para Birimli Expert Advisor ve MQL5'te Bekleyen Talimatlarla Çalışma
Bu sefer, bekleyen Satın Al Durdur ve Sat Durdur talimatlarıyla çalışmayı temel alan bir alım satım algoritmasına sahip çok para birimli bir Expert Advisor oluşturacağız. Bu makale şu konuları ele almaktadır: belirli bir zaman aralığında alım satım yapma, bekleyen talimatları verme/değiştirme/silme, son pozisyonun Kar Al veya Zarar Durdur'da kapatılıp kapatılmadığını kontrol etme ve her bir sembol için yatırım geçmişinin kontrolü.