MQL5言語でのTelegram用ボットの作成

Andriy Voitenko | 30 5月, 2016


概論

2016年4月12日にサンフランシスコで開催されたF8会議(*翻訳者注:Faceboоk開発者会議)で、Faceboоk社は自身のメッセンジャーボットの為のAPIの実装を発表しました。同日、Telegram Bot Platformの多くの更新がリリースされました。バージョン2.0の新機能は喜ばしいものでした。ICQ時代に人気だったのに、忘れ去られてしまったボットに関心が戻ってきているようです。発展のスパイラルの新しい方向性は、より良く考えられた機能やプログラミングの為のオープンインターフェイス、そしてマルチメディアサポートなどを与えるものとなっています。つまりは、あなたが何かを見つけたり、閲覧したり、購入したい時に、彼らが不可欠な存在となる為のものは全て揃ったということです。

この記事は、MQL5でのTelegram用ボットの作成マニュアルを意図して書かれています。ボットとは?ボット(『ロボット』の省略形)とは、Telegramの特別なアカウントで、プログラムによるメッセージ交換機能の為に設計されたものです。ボットはあなた(クライアント)側で動作し、Bot APIに含まれる特別なコマンドセットを介して、Telegramサーバと相互に作用します。ボット自体の作成に取り掛かる前に、Telegramをインストールし、このサービスにログインしてください。登録は電話番号に関連付けられ、その他には、あなたを検索で見つけることができるニックネーム(@username)を作成することもできます。ここで全ボットのフォルダをご紹介しましょう。

新しいボットの登録

ボットの登録と設定は、@BotFatherという特別なボットが行います。彼を検索で見つけましょう。連絡先リストに追加したら、/startコマンドを入力し、彼との通信を開始します。図1で見られるように、返事として彼は利用できるコマンドの全リストを送ってきます。


@BotFatherのコマンドリスト.

図1. @BotFatherのコマンドリスト

/newbotコマンドで、新しいボットの登録を開始しましょう。必ず2つの名前を考える必要があります。1つ目であるボット名(name)は、あなたの母国語で設定することができます。2つ目のボットユーザー名(username)は、ラテン文字で入力し最後に接尾辞“bot”をつけます。その結果、我々はAPIボットを使用する為のアクセスキーであるトークンを取得します。登録例は図2をご覧ください。


新しいボットの登録

図2 新しいボットの登録

希望に合わせていくつかのパラメータを変更することができます。inlineモードの設定には触れないことをお勧めします。私達のボットはこのモードで動作しません。以下のプロパティの設定を行うことをお勧めします。
  • /setcommands – サポートされているコマンドのリストのセット。このリストは、チャット画面に"/"を入力したときに、ツールチップとしてユーザーに表示されます。
  • /setuserpic – プロフィール写真の設定。写真があった方が見た目がいいですから。
  • /setdescription – メッセンジャーへボットを追加する時に、挨拶文として表示されるテキスト。通常、ここにボットの用途が数行のテキストで表示されます。

これで、ボットが登録されました。どのようなモードでボットを使用することができるかをご紹介しましょう。

ボットの動作モード

Telegramには、ユーザーとボットが相互作用する3つのパターンがあります。1つ目は、プライベートチャットです。各ユーザーは、図3で示されているように、お互いに独立した要求/応答モードでボットと通信します。


ボットとプライベートチャット

図3 ボットとプライベートチャット


ユーザーはボットにメッセージを送ります。これらのメッセージは、サーバーで24時間以上保存されることはなく、その後削除されます。ボットは、これらのメッセージを要求したり、それに応答する為の時間を持っています。これは、私達のボットが動作するメインモードです。

2つ目のモードは、グループチャットです。ここでは、グループのメンバーが送ったメッセージを、全員が見ることができます(図4)。

ボットとグループチャット

図4. グループチャットのボット。

/setjoingroupsコマンドを介して、ボットのグループへの参加を許可することができます。ボットがグループに追加された場合、/setprivacyコマンドで、ボットが全てのメッセージを受信するか、もしくは“/”のコマンド記号で始まるものだけを受信するかを設定できます。正直なところ、私はこのモード用には、指示を一つだけ考え付くことができました。それは、今後の分析の為のメッセージの統計の収集です。

3つ目のモードは、チャンネル上の動作です。Telegramのチャンネルとは、加入数に制限のない幅広いオーディエンスの為のメッセージをブロードキャストする為のアカウントです。チャンネルの重要な特徴は、メッセージフィードにユーザーによっていいねやコメントを残すことができない点にあります。フィードへのメッセージの作成は、チャンネルの管理者のみ行うことができます(図5)。


チャンネル管理者としてのボット

図5. チャンネルの管理者としてのボット

管理者のリストにもボットを追加することができます。これをすることで、チャンネルをトレードシグナルの配信の為の理想的なツールにすることができます。後ほど、標準的なMACDインディケータからのシグナルを知らせる、簡単なボットを作成します。新しいチャンネルは、メッセンジャーの“New Channel”メニューを介して作成することができます。あなたのボットをチャンネル管理者のリストに入れることを忘れないでください。これは、チャンネルのプロパティ画面を通じて行われます。これで準備作業は完了です。ここから、プログラミングに移ります。


メッセージフローの処理

この記事を書いていく中で、メッセージ処理のルーティーンを引き受け、ボット自体の動作ロジックに集中することができるクラスを作成する必要性が出てきました。その結果、動作の為の必要最低限の機能を実装する、CCustomBotクラスが作成されました。

サーバーとの通信は、WebRequst関数を使用し、POSTリクエストを介して行われます。各コマンドにはそれぞれURLがつきます。

https://api.telegram.org/bot< TOKEN >/ METHOD_NAME

TOKENの部分は、登録されたボットのトークン、METHOD_NAMEの部分は、サポートされているメソッドのリストです。

サーバーからの応答はJSONフォーマットで送信されるので、良いJSONパーサーが必要になりました。私はネイティブのJSON Serialization and Deserializationパーサーを適用しました。アレクセイ(セルゲーエフ)さんに、彼が行ってくれた仕事に対し、感謝したいと思います。また、いくつかのパラメータを表示する為のパネルを使用しました。CCommentクラスは、この課題に適合するものをCodebaseから引用しました。パブリックメソッドの名前を普遍的なものにする為に、クラスはBot APIのドキュメントからお借りしました。クラスで実装することができたメソッドのリストは以下の通りです。

これらの関数をどう使用するかを理解する為に、プログラミングを掘り下げていきましょう。


GetMe

全てのリクエストにトークンが送信される為、その信憑性をチェックするGetMe関数がまず実装されました。このチェックは、エキスパートアドバイザの起動時や、このことについてユーザーに通知することができなかった場合に行うことが望ましいです。

int GetMe()
 戻り値  エラーコード

成功した場合、GetMeは0を返し、Name()メソッドを介してボット名(username)を知ることができます。この名前は動作に関係しません。しかし、これはパネル上で情報として表示されます。telegram.me/<botname>といったこのようなアドレスによって、メッセンジャーのウェブバージョンを使用することができ、これがあなたのボットの宣伝の為のリンクとなります。OnInitでトークンのチェックを持つエキスパートアドバイザは次のようになります。

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

#include <Telegram.mqh>

input string InpToken="177791741:AAH0yB3YV7ywm80af_-AGqb7hzTR_Ud9DhQ";//Token

CCustomBot bot;
int getme_result;
//+------------------------------------------------------------------+
//|   OnInit                                                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- set token
   bot.Token(InpToken);
//--- check token
   getme_result=bot.GetMe();
//--- run timer
   EventSetTimer(3);
   OnTimer();
//--- done
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//|   OnDeinit                                                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   Comment("");
  }
//+------------------------------------------------------------------+
//|   OnTimer                                                        |
//+------------------------------------------------------------------+
void OnTimer()
  {
//--- show error message end exit
   if(getme_result!=0)
     {
      Comment("Error: ",GetErrorDescription(getme_result));
      return;
     }
//--- show bot name
   Comment("Bot name: ",bot.Name());

//---{ insert your code here }
  }
//+------------------------------------------------------------------+

GetUpdates関数はメッセージ配列を読み込み、サーバーに保存します。これは、必ずタイマーで呼び出す必要があります。タイマーの更新期間は、サーバーに負荷を与える為、1秒以下に設定することは望ましくありません。

int GetUpdate()
 戻り値  エラーコード

この関数に含まれているものを詳細に見ていきましょう。その呼び出し時に、ユーザーからの全ての未読メッセージの読み取りとパーシングが行われます。このようなメッセージの例は以下のようになります。

{ 
   "ok":true,
   "result":[ 
      { 
         "update_id":349778698,
         "message":{ 
            "message_id":2,
            "from":{ 
               "id":198289825,
               "first_name":"Andriy",
               "last_name":"Voitenko",
               "username":"avaticks"
            },
            "chat":{ 
               "id":198289825,
               "first_name":"Andriy",
               "last_name":"Voitenko",
               "username":"avaticks",
               "type":"private"
            },
            "date":1459775817,
            "text":"\/start"
         }
      }
   ]
}

avaticksというニックネームのユーザーが、ボットに/startというコマンドを送りました。目的はこのようなメッセージの保存と、今後のそれらへの対応です。この時の一意の識別子は、チャット番号のchat[id]です。様々なデバイスを介してボットと通信する同一ユーザーが、様々なチャットIDを持ちます。このパラメータは、チャットリスト構築の為の固有キーとして適しています。動作の過程で、ボットはチャットの配列を集め、その中から最後に送信されたメッセージをそれぞれ更新します。私達がこれに答えた場合、こういったメッセージは処理されたとみなされ、そのメッセージにはdoneフラグが付きます。チャットタイプもわかります。チャットタイプはプライベート(private)、もしくはグループ(group)となります。

自分のボット作成の為に、私達に必要なことはCCustomBotから継承し、自分のクラスで自分の動作ロジックを実装するProcessMessage仮想関数を再定義するだけです。Telegramドキュメントによると、完全なボットは、"/start"と"/help"の2つのコマンドに答えることができなけれければいけません。これらのコマンドに答える最初のボットを作成してみましょう。

//+------------------------------------------------------------------+
//|                                          Telegram_GetUpdates.mq5 |
//|                        Copyright 2014, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+

#property copyright "Copyright 2014, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property strict

#include <Telegram.mqh>
//+------------------------------------------------------------------+
//|   CMyBot                                                         |
//+------------------------------------------------------------------+
class CMyBot: public CCustomBot
  {
public:
   void ProcessMessages(void)
     {
      for(int i=0; i<m_chats.Total(); i++)
        {
         CCustomChat *chat=m_chats.GetNodeAtIndex(i);
         //--- if the message is not processed
         if(!chat.m_new_one.done)
           {
            chat.m_new_one.done=true;
            string text=chat.m_new_one.message_text;

            //--- start
            if(text=="/start")
               SendMessage(chat.m_id,"Hello, world! I am bot. \xF680");

            //--- help
            if(text=="/help")
               SendMessage(chat.m_id,"My commands list: \n/start-start chatting with me \n/help-get help");

           }
        }
     }
  };

//---
input string InpToken="177791741:AAH0yB3YV7ywm80af_-AGqb7hzTR_Ud9DhQ";//Token
//---
CMyBot bot;
int getme_result;
//+------------------------------------------------------------------+
//|   OnInit                                                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- set token
   bot.Token(InpToken);
//--- check token
   getme_result=bot.GetMe();
//--- run timer
   EventSetTimer(3);
   OnTimer();
//--- done
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//|   OnDeinit                                                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   Comment("");
  }
//+------------------------------------------------------------------+
//|   OnTimer                                                        |
//+------------------------------------------------------------------+
void OnTimer()
  {
//--- show error message end exit
   if(getme_result!=0)
     {
      Comment("Error: ",GetErrorDescription(getme_result));
      return;
     }
//--- show bot name
   Comment("Bot name: ",bot.Name());
//--- reading messages
   bot.GetUpdates();
//--- processing messages
   bot.ProcessMessages();
  }
//+------------------------------------------------------------------+
このボットの動作結果は図6になります。


コマンドの最小キットを備えるボット

図6. コマンドの最小キットを備えるボット

キーボードの使用

ユーザーとの双方向コミュニケーションの為に、ボットには『キーボード』が作成されました。メッセージ送信の際に、各チャットには事前に設定したキーの『キーボード』を表示することができます。キーを押し、ユーザーは指定されたテキストメッセージを送信します。このようにボットとユーザーの相互動作は、遥かに簡素化されています。

クラスにはキーボードを使う3つの関数を持っています。それらのうちの1つ目は、キーボードのオブジェクトを作成します。

string ReplyKeyboardMarkup(const string keyboard,
                           const bool resize,
                           const bool one_time)
 keyboard
 キーの配置場所を指定する文字列
 resize  キーボードサイズの変更許可
 one_time  キーボードが表示されるのは一度だけです。キーを押した後、キーボードは消えます。
 戻り値 SendMessageを使用したメッセージ送信時に、reply_markupパラメータとして引き渡す必要がある文字列(JSONオブジェクト)

2つ目の関数はキーボードを隠します。

string ReplyKeyboardHide()
 戻り値 SendMessageを使用したメッセージ送信時に、reply_markupパラメータとして引き渡す必要がある文字列(JSONオブジェクト)

3つ目の関数は、テキスト形式であなたからの返答をボットが待っているということを示す小さいパネルを送信することができます(キーボードは表示されません)。

string ForceReply()
 戻り値 SendMessageを使用したメッセージ送信時に、reply_markupパラメータとして引き渡す必要がある文字列(JSONオブジェクト)

ここで、これらの関数の使用方法を見ていきましょう。

SendMessage

キーボードは、表示または非表示にすることができます。アクションはメッセージと一緒に送信されます。チャットへメッセージを送信するSendMessage関数は次のようになります。

int SendMessage(const long chat_id,
                const string text,
                const string reply_markup=NULL)
 chat_id
 チャット番号
 text  メッセージテキスト
 reply markup  キーボード(JSONオブジェクト)
 戻り値  エラーコード

この場合、キーボードはオプションです。私達のMQLプログラムから、簡単なテキストメッセージを送信することができます。私の意見としては、この関数はネイティブのSendNotificationよりも面白いと思います。第一に、私達はより頻繁にメッセージを送信することができます(1秒に1回程)。第二に、HTML形式をサポートしています。また、重要な特典として、絵文字を送信することができる機能があります。

Тelegramがサポートする絵文字の多くは、 こちらで見ることができます。お分かりのように、絵文字のコードの多くは(1F300 – 1F700)の範囲内にあります。これらの能力はMQL5で採用されている、文字列の2バイトコード化の領域を超えています。2バイトの整数が残るようにする為に、上位の列を削除した場合、(F300 – F700)の領域は、Unicode表に私的利用の為に登録された(E000— F8FF)の範囲に入ります。このように、絵文字を送信するのに2以上のバイトを使用するのに妨げとなるものはありません。U+1F642のコードを持つ古典的なスマイルの絵文字のメッセージ列は、次のようになります。

string text="Have a nice day.\xF642";//U+1F642の絵文字を持つメッセージテキスト

これらは基本的にテキストである為、キーにとっては正当です。キーボード上で絵文字を使うのに妨げるものはありません。イベント処理を伴う3つのキーの反映の例を書いてみましょう。

//+------------------------------------------------------------------+
//|                                         Telegram_SendMessage.mq5 |
//|                        Copyright 2014, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+

#property copyright "Copyright 2014, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property strict

#include <Telegram.mqh>
//+------------------------------------------------------------------+
//|   CMyBot                                                         |
//+------------------------------------------------------------------+
class CMyBot: public CCustomBot
  {
private:
   string            m_button[3];
public:
   //+------------------------------------------------------------------+
   void CMyBot::CMyBot(void)
     {
      m_button[0]="Button #1";
      m_button[1]="Button #2";
      m_button[2]="Button #3";
     }

   //+------------------------------------------------------------------+
   string GetKeyboard()
     {
      return("[[\""+m_button[0]+"\"],[\""+m_button[1]+"\"],[\""+m_button[2]+"\"]]");
     }

   //+------------------------------------------------------------------+
   void ProcessMessages(void)
     {
      for(int i=0;i<m_chats.Total();i++)
        {
         CCustomChat *chat=m_chats.GetNodeAtIndex(i);
         if(!chat.m_new_one.done)
           {
            chat.m_new_one.done=true;
            string text=chat.m_new_one.message_text;

            //--- start or help commands
            if(text=="/start" || text=="/help")
               bot.SendMessage(chat.m_id,"Click on the buttons",bot.ReplyKeyboardMarkup(GetKeyboard(),false,false));

            //--- on click event
            int total=ArraySize(m_button);
            for(int k=0;k<total;k++)
              {
               if(text==m_button[k])
                  bot.SendMessage(chat.m_id,m_button[k],bot.ReplyKeyboardMarkup(GetKeyboard(),false,false));
              }
           }
        }
     }
  };

input string InpToken="177791741:AAH0yB3YV7ywm80af_-AGqb7hzTR_Ud9DhQ";//Token

CMyBot bot;
int getme_result;
//+------------------------------------------------------------------+
//|   OnInit                                                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- set token
   bot.Token(InpToken);
//--- check token
   getme_result=bot.GetMe();
//--- run timer
   EventSetTimer(1);
   OnTimer();
//--- done
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//|   OnDeinit                                                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   Comment("");
  }
//+------------------------------------------------------------------+
//|   OnTimer                                                        |
//+------------------------------------------------------------------+
void OnTimer()
  {
//--- show error message end exit
   if(getme_result!=0)
     {
      Comment("Error: ",GetErrorDescription(getme_result));
      return;
     }
//--- show bot name
   Comment("Bot name: ",bot.Name());
//--- reading messages
   bot.GetUpdates();
//--- processing messages
   bot.ProcessMessages();
  }
//+------------------------------------------------------------------+

結果として、私達は図7に表示されているような、キーボードを持つメッセージを取得します。

キーボードを持つメッセージ

図7. キーボードを持つメッセージ

ここで、制御要素のアナログ、RadioButtonとCheckBoxの実装をしてみましょう。例えば、3つのオプションから1つを選び、特定のオプションをオン/オフにする必要があるとします。変更は私達のクラスにのみ影響する為、残りのエキスパートアドバイザのコードは前の例のまま残ります。
//+------------------------------------------------------------------+
//|                                         Telegram_SendMessage.mq5 |
//|                        Copyright 2014, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+

#property copyright "Copyright 2014, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property strict

#include <Telegram.mqh>

#define MUTE_TEXT       "Mute"
#define UNMUTE_TEXT     "Unmute"

#define LOCK_TEXT       "Lock"
#define UNLOCK_TEXT     "Unlock"

#define RADIO_SELECT    "\xF518"
#define RADIO_EMPTY     "\x26AA"

#define MUTE_CODE       "\xF515"
#define UNMUTE_CODE     "\xF514"

#define LOCK_CODE       "\xF512"
#define UNLOCK_CODE     "\xF513"
//+------------------------------------------------------------------+
//|   CMyBot                                                         |
//+------------------------------------------------------------------+
class CMyBot: public CCustomBot
  {
private:
   string            m_radio_button[3];
   int               m_radio_index;
   bool              m_lock_state;
   bool              m_mute_state;

public:
   //+------------------------------------------------------------------+
   void CMyBot::CMyBot(void)
     {
      m_radio_button[0]="Radio Button #1";
      m_radio_button[1]="Radio Button #2";
      m_radio_button[2]="Radio Button #3";
      m_radio_index=0;
      m_lock_state=false;
      m_mute_state=true;
     }
   //+------------------------------------------------------------------+

   string GetKeyboard()
     {
      //---
      string radio_code[3]={RADIO_EMPTY,RADIO_EMPTY,RADIO_EMPTY};
      if(m_radio_index>=0 && m_radio_index<=2)
         radio_code[m_radio_index]=RADIO_SELECT;
      //---
      string mute_text=UNMUTE_TEXT;
      string mute_code=UNMUTE_CODE;
      if(m_mute_state)
        {
         mute_text=MUTE_TEXT;
         mute_code=MUTE_CODE;
        }
      //---
      string lock_text=UNLOCK_TEXT;
      string lock_code=UNLOCK_CODE;
      if(m_lock_state)
        {
         lock_text=LOCK_TEXT;
         lock_code=LOCK_CODE;
        }
      //---
      //Print(m_lock.GetKey());
      return(StringFormat("[[\"%s %s\"],[\"%s %s\"],[\"%s %s\"],[\"%s %s\",\"%s %s\"]]",
             radio_code[0],m_radio_button[0],
             radio_code[1],m_radio_button[1],
             radio_code[2],m_radio_button[2],
             lock_code,lock_text,
             mute_code,mute_text));
     }
   //+------------------------------------------------------------------+

   void ProcessMessages(void)
     {
      for(int i=0;i<m_chats.Total();i++)
        {
         CCustomChat *chat=m_chats.GetNodeAtIndex(i);
         if(!chat.m_new_one.done)
           {
            chat.m_new_one.done=true;
            string text=chat.m_new_one.message_text;

            //--- start
            if(text=="/start" || text=="/help")
              {
               bot.SendMessage(chat.m_id,"Click on the buttons",bot.ReplyKeyboardMarkup(GetKeyboard(),false,false));
              }

            //--- Click on a RadioButton
            int total=ArraySize(m_radio_button);
            for(int k=0;k<total;k++)
              {
               if(text==RADIO_EMPTY+" "+m_radio_button[k])
                 {
                  m_radio_index=k;
                  bot.SendMessage(chat.m_id,m_radio_button[k],bot.ReplyKeyboardMarkup(GetKeyboard(),false,false));
                 }
              }

            //--- Unlock
            if(text==LOCK_CODE+" "+LOCK_TEXT)
              {
               m_lock_state=false;
               bot.SendMessage(chat.m_id,UNLOCK_TEXT,bot.ReplyKeyboardMarkup(GetKeyboard(),false,false));
              }

            //--- Lock
            if(text==UNLOCK_CODE+" "+UNLOCK_TEXT)
              {
               m_lock_state=true;
               bot.SendMessage(chat.m_id,LOCK_TEXT,bot.ReplyKeyboardMarkup(GetKeyboard(),false,false));
              }

            //--- Unmute
            if(text==MUTE_CODE+" "+MUTE_TEXT)
              {
               m_mute_state=false;
               bot.SendMessage(chat.m_id,UNMUTE_TEXT,bot.ReplyKeyboardMarkup(GetKeyboard(),false,false));
              }

            //--- Mute
            if(text==UNMUTE_CODE+" "+UNMUTE_TEXT)
              {
               m_mute_state=true;
               bot.SendMessage(chat.m_id,MUTE_TEXT,bot.ReplyKeyboardMarkup(GetKeyboard(),false,false));
              }
           }
        }
     }
  };

結果として、このような画面になります(図8)。

図8. 制御要素 RadioButtonとCheckBox

ご覧のように、ここでは絵文字が設定をより分かりやすくしています。これらの制御要素の他にも、各サブメニューにナビゲーションをつけた段階的なメニューを実装することも簡単です。概して、全てはあなたが思いつき、実装したいと思う機能によります。

チャンネル上にメッセージを投稿したい場合、2つ目の選択肢としてSendMessageがあります。

int SendMessage(const string channel_name,
                const string text)
 channel_name
 @nameとしてのチャンネル名
 text  メッセージテキスト。HTMLタグをサポートしています。
 戻り値  エラーコード

この関数の動作結果は、以下に掲載している図9で見ることができます。

マルチメディアの使用

ボットは写真やオーディオ、ビデオファイルだけでなく、音声メッセージやスタンプ、現在位置情報を共有することができます。記事の執筆中に、連絡先と招待状を交換する機能が追加された、新しいバージョンBot API 2.0がリリースされました。全てのリストの中で、私達のボットに実用的なのは写真の交換のみです。

SendPhoto

クラスでは、2つの使用方法を持つ写真送信機能が実装されました。

int SendPhoto(const long   chat_id,
              const string local_path,
              string       &photo_id,
              const string caption=NULL,
              const bool   common_flag=false,
              const int    timeout=10000)
 chat_id  チャット番号
 local_path  <データフォルダ>\MQL5\Filesのファイルへのローカルパス
 photo_id  写真サーバーでロードされたID
 caption  写真の下のサブテキスト
 common_flag  \Terminal\Common\Filesの全てのクライアントターミナルのパブリックフォルダ内のファイルの場所のフラグ
 timeout  ミリ秒単位の操作タイムアウト

送信する写真のコード例:

CCustomBot bot;

string token = "208375865:AAFnuOjlZ3Wsdan6PAjeqqUtBybe0Di1or8";

bot.Token(token);

string photo_id;
int result=bot.SendPhoto(198289825,"EURUSD1.gif",photo_id,"screenshot");
if(result==0)
   Print("Photo ID: ",photo_id);
else
   Print("Error: ",GetErrorDescription(result));
複数のユーザーに写真を送ったり、同じ写真をもう一度送る必要にかられる場面があると思います。この場合、一度写真をダウンロードし、再送信にはSendPhoto関数の代わりにphoto_idを使用した方が合理的です。
int SendPhoto(const long chat_id,
              const string photo_id,
              const string caption=NULL)
 chat_id  チャット番号
 photo_id  写真サーバーでロードされたID
 caption  写真の下のサブテキスト

SendChartAction

ユーザーからの返事を処理し、すでにユーザーへ返事をする準備ができていると想像してみましょう。しかし、返事の作成には数秒を費やすことになります。あなたが今実行中であることを、ユーザーに知らせた方が良いでしょう。この為にイベントがあります。例えば、ユーザーに送信する為に、チャートのスクリーンショットを作成中である場合、あなたはユーザーに『写真の送信』イベントを送ることができます。これはSendChatActionを使って行われます。

int SendChatAction(const long chat_id,
                   const ENUM_CHAT_ACTION actiona)
 chat_id  チャット番号
 action  イベントID
上述の全ての関数は、以下でご紹介する3つのデモボットで実装されています。

ボットの例

1つ目のボットーTelegram_Bot_EAでは、口座状態や相場、チャートのスクリーンショットについての情報を得ることができます。ボットの動作については、このビデオでご覧ください。


2つ目のボットーTelegram_Search_EAは、MQL5.comのサイト内の検索結果を送信します。きっとこのボットの動作はあなたの興味を引くと思います。次のビデオをご覧ください。


3つ目のボットーTelegram_Signal_EAは、チャンネル上に標準インディケータMACDからのシグナルを表示します。MACDを自分の好きなインディケータに変え、自分用にこのコードを使用することは簡単だと思います。

//+------------------------------------------------------------------+
//|                                        Telegram_Signal_EA_v1.mq4 |
//|                        Copyright 2014, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+

#property copyright "Copyright 2014, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"

//+------------------------------------------------------------------+
//|   Includes                                                       |
//+------------------------------------------------------------------+
#include <Telegram.mqh>

//--- Input parameters
input string InpChannelName="@forexsignalchannel";//Channel Name
input string InpToken="177791741:AAH0yB3YV7ywm80af_-AGqb7hzTR_Ud9DhQ";//Token

//--- Global variables
CCustomBot bot;
int macd_handle;
datetime time_signal=0;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   time_signal=0;

//--- set token
   bot.Token(InpToken);

//--- get an indicator handle
   macd_handle=iMACD(NULL,0,12,26,9,PRICE_CLOSE);
   if(macd_handle==INVALID_HANDLE)
      return(INIT_FAILED);

//--- done
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {

//--- get time
   datetime time[1];
   if(CopyTime(NULL,0,0,1,time)!=1)
      return;

//--- check the signal on each bar
   if(time_signal!=time[0])
     {
      //--- first calc
      if(time_signal==0)
        {
         time_signal=time[0];
         return;
        }

      double macd[2]={0.0};
      double signal[2]={0.0};

      if(CopyBuffer(macd_handle,0,0,2,macd)!=2)
         return;
      if(CopyBuffer(macd_handle,1,0,2,signal)!=2)
         return;

      time_signal=time[0];

      //--- Send signal BUY
      if(macd[1]>signal[1] && 
         macd[0]<=signal[0])
        {
         string msg=StringFormat("Name: MACD Signal\nSymbol: %s\nTimeframe: %s\nType: Buy\nPrice: %s\nTime: %s",
                                 _Symbol,
                                 StringSubstr(EnumToString(_Period),7),
                                 DoubleToString(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits),
                                 TimeToString(time[0]));
         int res=bot.SendMessage(InpChannelName,msg);
         if(res!=0)
            Print("Error: ",GetErrorDescription(res));
        }

      //--- Send signal SELL
      if(macd[1]<signal[1] && 
         macd[0]>=signal[0])
        {
         string msg=StringFormat("Name: MACD Signal\nSymbol: %s\nTimeframe: %s\nType: Sell\nPrice: %s\nTime: %s",
                                 _Symbol,
                                 StringSubstr(EnumToString(_Period),7),
                                 DoubleToString(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits),
                                 TimeToString(time[0]));
         int res=bot.SendMessage(InpChannelName,msg);
         if(res!=0)
            Print("Error: ",GetErrorDescription(res));
        }
     }
  }
//+------------------------------------------------------------------+

その結果、図9に表示されているメッセージを受け取ります。

MACDインディケータのシグナル

図9. MACDインディケータのシグナル

まとめ

自分のボットの為にYandex.AppMetrikaのデータベースでアナリストに接続したい人は、Botanリソースを使用することができます。サービスの要旨は、ユーザーからあなたが受信するメッセージを彼らに送り、セグメントやトラッキング、コホート分析などの指標をリクエストすることができます。この時、メッセンジャーのエコシステムから出る必要はありません。というのは、統計は特別なボットによってチャートとして送られ、より詳細なレポートはサイトから利用することができるからです。

この記事がトレードにTelegramを使う動機となることを願っています。詳細はBot APIのドキュメントでしっかりと説明されているので、私は詳細を記述することを目的とはしませんでした。記事に添付されているコードは、MetaTrader 4およびMetaTrader 5の両方のプラットフォームでの動作するように適応させました。