Пишем Twitter-клиент для MetaTrader 4 и MetaTrader 5 без использования DLL

8 сентября 2020, 10:04
Soewono Effendi
1
860

Введение

Твиттер — бесплатная платформа для публичного обмена сообщениями. Писать можно что угодно — от ценных финансовых советов до личных мнений известных и не очень людей. Однако в этой статье мы будем говорить не о содержании, а о средствах работы с твитами.

Прежде всего нужно зарегистрироваться, чтобы получить доступ к набору токенов, необходимых для работы с Tweeter API. Поначалу в этих токенах можно запутаться — их много, при этом у них похожие имена.

Вообще для использования Twitter API вам понадобятся следующие токены:

  • customer_token
  • customer_token_secret
  • access_token
  • access_token_secret

Примечание: понадобятся знания о работе с публичными и приватными ключами, используемыми для цифровой подписи.

Первые два токена, customer_token и customer_token_secret — это как раз публичный и приватный ключи для идентификации вашего твиттер-приложения. Попросту говоря, твиттер-приложение — это идентификация вашей службы, использующей Twitter API. 

Токены access_token и access_token_secret — это публичный и приватный ключи для идентификации вас как пользователя Твиттера. На языке Твиттера это называется user auth или user context. На основе этого токен API access_token может определить, кто к нему обращается.

Также есть еще один токен bearer_token, с помощью которого можно получить так называемый анонимный доступ с помощью Twitter API. Этот метод доступа называется app auth или app context. Невозможно получить доступ к некоторым Twitter API без user context. Подробнее об этом читайте в Справке Twitter API

Если вы умеете программировать на других языках, для большего понимания можете воспользоваться готовыми библиотеками Twitter. Они дают хорошее представление о деталях реализации, которые могут быть не так очевидны при работе только с документацией API.


Tweeter API и авторизация

Будем использовать указанные выше токены:

  • customer_token
  • customer_token_secret
  • access_token
  • access_token_secret

На YouTube достаточно много инструкций и обучающих материалов относительно того, как получить эти токены.

OAuth — это общепринятый и широко используемый стандарт для аутентификации и авторизации веб-API, который в том числе используется в Twitter API. Проще говоря, OAuth — это цифровая подпись, метод подписи цифрового контента, при которой любая попытка манипулирования контентом делает его недействительным.

Для правильной проверки содержимого и его подписи необходимо использовать определенные методы и точно соблюдать процесс создания этого содержимого.

Весь процесс отлично описан в документации к Twitter API.

Twitter API требует определенный метод кодирования запросов по HTTP. Этот метод будет описан в следующей главе.

Кодировка URL и сортировка параметров

Чтобы гарантировать корректность содержания, подписанного цифровой подписью, само содержание должно быть четко определено. Для этих целей при подписании цифровой подписью Twitter API (а точнее, метод OAuth) требует параметры HTTP POST и/или HTTP GET для прохождения обязательных шагов.

Крайне важно кодировать параметры HTTP POST / HTTP GET следующим образом:

  • Все символы необходимо кодировать в процентах (%XX), за исключением буквенно-цифровых (0-9, A-Z, a-z) и специальных символов ‘-‘, ‘.’, ‘_’, ‘~’.
  • Необходимо использовать заглавные буквы (0-9 ABCDEF) в шестнадцатеричном значении процентного кодирования.

О том, как корректно закодировать символы "%" и "+", читайте в Справке и дополнительной документации.

Также обратите внимание на сортировку указанных параметров из справочной документации:

  • В спецификации OAuth говорится о лексикографической сортировке, которая является алфавитной сортировкой по умолчанию для многих библиотек.
  • Если два ключа имеют один и тот же закодированный ключ, согласно спецификации OAuth необходимо продолжить сортировку на основе значения. Однако Твиттер не принимает в запросах API повторяющиеся ключи.

Простая реализация этого требования выглядит так:

string hex(int i)
  {
   static string h="0123456789ABCDEF";
   string ret="";
   int a = i % 16;
   int b = (i-a)/16;
   if(b>15)
      StringConcatenate(ret,ret,hex(b),StringSubstr(h,a,1));
   else
      StringConcatenate(ret,ret,StringSubstr(h,b,1),StringSubstr(h,a,1));
   return (ret);
  }

string URLEncode(string toCode)
  {
   int max=StringLen(toCode);

   string RetStr="";
   for(int i=0; i<max; i++)
     {
      string c = StringSubstr(toCode,i,1);
      ushort asc = StringGetCharacter(c, 0);

      if((asc >= '0' && asc <= '9')
         || (asc >= 'a' && asc <= 'z')
         || (asc >= 'A' && asc <= 'Z')
         || (asc == '-')
         || (asc == '.')
         || (asc == '_')
         || (asc == '~'))
         StringAdd(RetStr,c);
      else
        {
         StringConcatenate(RetStr,RetStr,"%",hex(asc));
        }
     }
   return (RetStr);
  }

string arrayEncode(string &array[][2])
  {
   string ret="";
   string key,val;
   int l=ArrayRange(array,0);
   for(int i=0; i<l; i++)
     {
      key = URLEncode(array[i,0]);
      val = URLEncode(array[i,1]);
      StringConcatenate(ret,ret,key,"=",val);
      if(i+1<l)
         StringConcatenate(ret,ret,"&");
     }
   return (ret);
  }

void sortParam(string&arr[][2])
  {
   string k1, k2;
   string v1, v2;
   int n = ArrayRange(arr,0);

// bubble sort
   int i, j;
   for(i = 0; i < n-1; i++)
     {
      // Last i elements are already in place
      for(j = 0; j < n-i-1; j++)
        {
         int x = j+1;
         k1 = arr[j][0];
         k2 = arr[x][0];
         if(k1 > k2)
           {
            // swap values
            v1 = arr[j][1];
            v2 = arr[x][1];
            arr[j][1] = v2;
            arr[x][1] = v1;
            // swap keys
            arr[j][0] = k2;
            arr[x][0] = k1;
           }
        }
     }
  }

void addParam(string key,string val,string&array[][2])
  {
   int x=ArrayRange(array,0);
   if(ArrayResize(array,x+1)>-1)
     {
      array[x][0]=key;
      array[x][1]=val;
     }
  }


Пример использования рассмотренных выше функций:

   string params[][2];

   addParam("oauth_callback", "oob", params);
   addParam("oauth_consumer_key", consumer_key, params);

   sortParam(params);

Обратите внимание: для упрощения кода сортировка здесь неполная, в ней не учитываются параметры одинаковых ключей. Код можно улучшить, если вы используете параметры с одинаковыми ключами. Например, можно добавить переключатели или флажки в html-форме.


О HMAC-SHA1 максимально просто

Еще одно неудобство при создании подписи OAuth — отсутствие встроенной поддержки HMAC-SHA1 в MQL. MQL-функция CryptEncode() поддерживает только алгоритм SHA1-HASH. Отсюда получаем флаг: CRYPT_HASH_SHA1.

Итак, давайте закодируем наш HMAC-SHA1 с помощью CryptEncode()


string hmac_sha1(string smsg, string skey, uchar &dstbuf[])
  {
// Описание HMAC:
// https://ru.wikipedia.org/wiki/HMAC
//
   uint n;
   uint BLOCKSIZE=64;
   uchar key[];
   uchar msg[];
   uchar i_s[];
   uchar o_s[];
   uchar i_sha1[];
   uchar keybuf[];
   uchar i_key_pad[];
   uchar o_key_pad[];
   string s = "";

   if((uint)StringLen(skey)>BLOCKSIZE)
     {
      uchar tmpkey[];
      StringToCharArray(skey,tmpkey,0,StringLen(skey));
      CryptEncode(CRYPT_HASH_SHA1, tmpkey, keybuf, key);
      n=(uint)ArraySize(key);
     }
   else
      n=(uint)StringToCharArray(skey,key,0,StringLen(skey));

   if(n<BLOCKSIZE)
     {
      ArrayResize(key,BLOCKSIZE);
      ArrayFill(key,n,BLOCKSIZE-n,0);
     }

   ArrayCopy(i_key_pad,key);
   for(uint i=0; i<BLOCKSIZE; i++)
      i_key_pad[i]=key[i]^(uchar)0x36;

   ArrayCopy(o_key_pad,key);
   for(uint i=0; i<BLOCKSIZE; i++)
      o_key_pad[i]=key[i]^(uchar)0x5c;

   n=(uint)StringToCharArray(smsg,msg,0,StringLen(smsg));
   ArrayResize(i_s,BLOCKSIZE+n);
   ArrayCopy(i_s,i_key_pad);
   ArrayCopy(i_s,msg,BLOCKSIZE);

   CryptEncode(CRYPT_HASH_SHA1, i_s, keybuf, i_sha1);
   ArrayResize(o_s,BLOCKSIZE+ArraySize(i_sha1));
   ArrayCopy(o_s,o_key_pad);
   ArrayCopy(o_s,i_sha1,BLOCKSIZE);
   CryptEncode(CRYPT_HASH_SHA1, o_s, keybuf, dstbuf);

   for(int i=0; i < ArraySize(dstbuf); i++)
      StringConcatenate(s, s, StringFormat("%02x",(dstbuf[i])));

   return s;
  }


Для проверки корректности результата давайте сравним с хешем, созданным по документации Twitter API:

   uchar hashbuf[];
   string base_string = "POST&https%3A%2F%2Fapi.twitter.com%2F1.1%2Fstatuses%2Fupdate.json&include_entities%3Dtrue%26oauth_consumer_key%3Dxvz1evFS4wEEPTGEFPHBog%26oauth_nonce%3DkYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1318622958%26oauth_token%3D370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb%26oauth_version%3D1.0%26status%3DHello%2520Ladies%2520%252B%2520Gentlemen%252C%2520a%2520signed%2520OAuth%2520request%2521";
   string signing_key = "kAcSOqF21Fu85e7zjz7ZN2U4ZRhfV3WpwPAoE3Z7kBw&LswwdoUaIvS8ltyTt5jkRh4J50vUPVVHtR2YPi5kE";  
   string hash = hmac_sha1(base_string, signing_key, hashbuf);

   Print(hash); // 842b5299887e88760212a056ac4ec2ee1626b549

   uchar not_use[];
   uchar base64buf[];
   CryptEncode(CRYPT_BASE64, hashbuf, not_use, base64buf);
   string base64 = CharArrayToString(base64buf);
   
   Print(base64); // hCtSmYh+iHYCEqBWrE7C7hYmtUk=


Решение через WebRequest

Функция WebRequest() позволяет обращаться к любому интерфейсу REST API через Интернет без использования внешнего DLL. Следующий код позволяет получить доступ к Twitter API с помощью WebRequest()


#define WEBREQUEST_TIMEOUT 5000
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
string SendRequest(string method, string url, string headers="", string params="", int timeout=WEBREQUEST_TIMEOUT)
  {
   char data[];
   char result[];
   string resp_headers;
   ResetLastError();
   StringToCharArray(params, data);
   ArrayResize(data, ArraySize(data)-1);
   int res = WebRequest(method, url, headers, timeout, data, result, resp_headers);
   if(res != -1)
     {
      string resp = CharArrayToString(result);
      if(verbose)
        {
         Print("***");
         Print("Data:");
         Print(CharArrayToString(data));
         Print("Resp Headers:");
         Print(resp_headers);
         Print("Resp:");
         Print("***");
         Print(resp);
        }
      return resp;
     }
   else
     {
      int err = GetLastError();
      PrintFormat("* WebRequest error: %d (%d)", res, err);
      if(verbose)
        {
         Print("***");
         Print("Data:");
         Print(CharArrayToString(data));
        }
      if (err == 4014)
      {
         string msg = "* PLEASE allow https://api.tweeter.com in WebRequest listed URL";
         Print(msg);
      }
     }
   return "";
  }

Обязательно ознакомьтесь с документацией по WebRequest().

Для использования функции WebRequest() следует добавить адреса серверов в список разрешенных URL во вкладке "Советники" окна "Настройки". Порт сервера выбирается автоматически на основе указанного протокола - 80 для "http://" и 443 для "https://".

Вспомогательные функции для создания Twitter REST API

Эти вспомогательные функции помогут при создании подписи Twitter API.

string getNonce()
  {
   const string alnum = "abcdef0123456789";
   char base[];
   StringToCharArray(alnum, base);
   int x, len = StringLen(alnum);
   char res[32];
   for(int i=0; i<32; i++)
     {
      x = MathRand() % len;
      res[i] = base[x];
     }
   return CharArrayToString(res);
  }

string getBase(string&params[][2], string url, string method="POST")
  {
   string s = method;
   StringAdd(s, "&");
   StringAdd(s, URLEncode(url));
   StringAdd(s, "&");
   bool first = true;
   int x=ArrayRange(params,0);
   for(int i=0; i<x; i++)
     {
      if(first)
         first = false;
      else
         StringAdd(s, "%26");  // URLEncode("&")
      StringAdd(s, URLEncode(params[i][0]));
      StringAdd(s, "%3D"); // URLEncode("=")
      StringAdd(s, URLEncode(params[i][1]));
     }
   return s;
  }

string getQuery(string&params[][2], string url = "")
  {
   string key;
   string s = url;
   string sep = "";
   if(StringLen(s) > 0)
     {
      if(StringFind(s, "?") < 0)
        {
         sep = "?";
        }
     }
   bool first = true;
   int x=ArrayRange(params,0);
   for(int i=0; i<x; i++)
     {
      key = params[i][0];
      if(StringFind(key, "oauth_")==0)
         continue;
      if(first)
        {
         first = false;
         StringAdd(s, sep);
        }
      else
         StringAdd(s, "&");
      StringAdd(s, params[i][0]);
      StringAdd(s, "=");
      StringAdd(s, params[i][1]);
     }
   return s;
  }

string getOauth(string&params[][2])
  {
   string key;
   string s = "OAuth ";
   bool first = true;
   int x=ArrayRange(params,0);
   for(int i=0; i<x; i++)
     {
      key = params[i][0];
      if(StringFind(key, "oauth_")!=0)
         continue;
      if(first)
         first = false;
      else
         StringAdd(s, ", ");
      StringAdd(s, URLEncode(key));
      StringAdd(s, "=\"");
      StringAdd(s, URLEncode(params[i][1]));
      StringAdd(s, "\"");
     }
   return s;
  }


Пример скрипта

Теперь можно отправить первый запрос Twitter API.

void verifyCredentials()
  {

   string _api_key = consumer_key;
   string _api_secret = consumer_secret;
   string _token = access_token;
   string _secret = access_secret;

   string url = "https://api.twitter.com/1.1/account/verify_credentials.json";
   
   string params[][2];
   addParam("oauth_consumer_key", _api_key, params);
   string oauth_nonce = getNonce();
   addParam("oauth_nonce", oauth_nonce, params);
   addParam("oauth_signature_method", "HMAC-SHA1", params);
   
   string oauth_timestamp = IntegerToString(TimeGMT());
   addParam("oauth_timestamp", oauth_timestamp, params);
   
   addParam("oauth_token", _token, params);
   addParam("oauth_version", "1.0", params);
   
   sortParam(params);
   
   string query = getQuery(params, url);
   string base = getBase(params, url, "GET");
   uchar buf[];
   string key = URLEncode(_api_secret);
   StringAdd(key, "&");
   StringAdd(key, URLEncode(_secret));
   
   uchar hashbuf[], base64buf[], nokey[];
   string hash = hmac_sha1(base, key, hashbuf);
   CryptEncode(CRYPT_BASE64, hashbuf, nokey, base64buf);
   string base64 = CharArrayToString(base64buf);

   addParam("oauth_signature", base64, params);
   sortParam(params);
   string o = getOauth(params);

   string headers = "Host:api.twitter.com\r\nContent-Encoding: identity\r\nConnection: close\r\n";
   StringAdd(headers, "Authorization: ");
   StringAdd(headers, o);
   StringAdd(headers, "\r\n\r\n");
   
   string resp = SendRequest("GET", query, headers);
   Print(resp);

   // если все в порядке, мы получим JSON-ответ
   // {"id":122,"id_str":"122","name":"xxx","screen_name":"xxx123","location":"","description":"", ... 
  }


Пример Твиттер-клиента на графике MetaTrader 5


На картинке ниже показан Твиттер-клиент, отображающий твиты из индонезийского новостного канала. В данный момент я готовлю к публикации следующую статью с дополнительной реализацией Twitter API.



Твиттер-клиент на графике MT5

Рис 1. Отображение твитов на графике


Следующий скриншот показывает публикацию твитов из терминала MetaTrader 5.



Твит из терминала MetaTrader 5
Рис.2. Твит, опубликованный из терминала MetaTrader 5 


Будущие улучшения


Описанный выше метод работает хорошо, но он охватывает далеко не все функции Twitter API. Это хорошая база для тех, кто хочет глубже изучить Tweeter API. В следующей статье мы рассмотрим, как делать публикации в Твиттере, включая скриншоты графиков.

Можно будет создать класс TwitterAPI или даже общий клиентский класс OAuth.

Примечание о трехногой авторизации и авторизации на основе PIN-кода


Также для работы с Твиттером можно использовать так называемую трехногую авторизацию и авторизацию по PIN-коду.

Однако это уже за рамками данной статьи. Если у вас возникнут какие-либо вопросы или помощь, обращайтесь ко мне.

Заключение

Twitter — это популярная платформа, которая позволяет любому человеку публиковать практически все, что угодно. Надеюсь, статьи и содержащийся в ней код окажутся полезными для MQL-сообщества и помогут понять OAuth при работе с Twitter API.

Жду ваших отзывов и предложений. Вы можете использовать любой код из этой статьи в своих бесплатных и/или коммерческих проектах.

Хочу поблагодарить пользователя  Grzegorz Korycki за библиотеку (SHA256, SHA384 и SHA512 + HMAC - библиотека для MetaTrader 4), которая вдохновила меня на создание функции HMAC-SHA1.

Пусть твиты приносят удовольствие и прибыль!



Перевод с английского произведен MetaQuotes Software Corp.
Оригинальная статья: https://www.mql5.com/en/articles/8270

Прикрепленные файлы |
tweeter_mql.mq5 (9.67 KB)
urlencode.mqh (3.55 KB)
IraSveta
IraSveta | 8 сен 2020 в 16:20
Твиттер полезная вещь. Спасибо
Торговля на форекс и ее базовая математика Торговля на форекс и ее базовая математика

Статья ставит целью максимально просто и быстро описать основные особенности торговли на форекс, поделиться простыми истинами с новичками. Ну и постараться ответить на наиболее волнующие вопросы в трейдерской среде, а также написать простенький индикатор.

Работа с таймсериями в библиотеке DoEasy (Часть 51): Составные мультипериодные мультисимвольные стандартные индикаторы Работа с таймсериями в библиотеке DoEasy (Часть 51): Составные мультипериодные мультисимвольные стандартные индикаторы

В статье завершим разработку объектов мультисимвольных мультипериодных стандартных индикаторов. На примере стандартного индикатора Ichimoku Kinko Hyo разберём создание сложносоставных пользовательских индикаторов, имеющих вспомогательные рисуемые буферы для отображения данных на графике.

Нейросети — это просто (Часть 4): Рекуррентные сети Нейросети — это просто (Часть 4): Рекуррентные сети

Продолжаем наше погружение в мир нейронных сетей. И в этой статье я предлагаю поговорить о рекуррентных нейронных сетях. Данный тип нейронных сетей предлагается для использования с временными рядами, коими и являются ценовые графики в торговой платформе MetaTrader 5.

Брутфорс подход к поиску закономерностей Брутфорс подход к поиску закономерностей

В данной статье мы будем искать закономерности на рынке, создавать советников на их основе и проверять, как долго эти закономерности сохраняют работоспособность и вообще, сохраняют ли они ее.