介绍
2016 年 4 月 12 日, 在旧金山举办的 F8 会议期间, Facebook 宣布将 Bot API 集成到即时通信软件里。同日发布了 Telegram Bot 平台的重大更新。版本 2.0 有令人惊喜的功能。看样子, 曾经在 ICQ 时代流行的 bots 正在经历卷土重来。开发 bots 进入了新阶段, 完善的功能, 开放编程的接口, 以及多媒体支持。基本上, 当您查找、查看或购买物品时, 它们拥有令人难以割舍的所有条件。
本文是采用 MQL5 逐步创建用于 Telegram 的 bots 手册。那么, 什么是 "bot"?bot ("robot" 的缩写) 是 Telegram 内的特殊帐户, 用于交换消息。Bots 的操作位于您的客户端, 并且通过部分 Bot API 的特殊命令与 Telegram 服务器进行交互。在我们着手创建 bot 之前, 请下载 Telegram 并登录。注册会与电话号码连接, 但您也可以通过 @username 搜索。现在是时候来认识所有 bots 的文件夹。
注册新的 bot
一个特殊的 bot @BotFather 负责注册并设置 bots。我们将通过搜索引擎来搜索它。将它添加到联系人列表后, 我们将使用 /start 命令开始与之通信。作为响应, 它会发给您所有可用命令的列表, 如图例. 1 所示。
.
图例.1. 命令 @BotFather 的列表
利用 /newbot 命令我们开始注册新的 bot。我们需要紧跟两个名字。第一个是 bot 的名字, 可用您的母语设定。第二个是拉丁语的用户名, 结尾是 "bot" 后缀。作为结果, 我们得到一个令牌 – bot 通过 API 进行操作的识别符。注册的例子如图例. 2 所示。
图例.2. 新 bot 注册
若您愿意, 一些参数可以省略。我建议保持设置用于内联模式。否则, 我们的 bots 将不能与之工作。我建议仅进行一些装饰性设置:- /setcommands – 支持的设置命令列表。当在聊天窗口键入 "/" 符号时, 此列表将会作为工具提示显示给用户。
- /setuserpic – 设置资料图片。若没有它的话, bot 的表现力就不够。
- /setdescription – 当机器人添加到即时通信软件时的致辞。通常情况下, 用几句话来描述 bot 的目的。
于是, 新 bot 注册完成。现在让我们来讨论它可以使用的模式。
bots 的操作模式
Telegram 有三种方案用于 bots 和用户之间的交互。第一种 - 私聊。每位用户利用 bot 单独与他人通信, 如图例. 3 所示, 通过产生请求和接收响应。
图例.3. bot 和私聊。
用户发送消息至 bot。它们保存在服务器上不超过 24 小时, 然后即被删除。bot 有足够的时间发送这些消息并响应它们。这是我们的 bots 将要操作的主要模式。
第二种模式涉及群聊。在此情况下, 发自群内任意成员的消息可以被全群所见 (图例. 4)。
图例.4. 群聊模式的 bot。
利用相关 bots, 您可以使用 /setjoingroups 命令让它们加入群。如果 bot 已经被加入群, 则使用 /setprivacy 命令您可以设置选项, 即可接收所有消息, 或开始字符为 "/" 标记的那些。说实话, 我只设想到一个这种模式下 bot 的作用 - 用于后续分析的消息统计数据。
第三种模式重点在于信道操作。Telegram 信道是为大量听众传输消息的帐户, 它可支持无限数量的订阅者。信道的重要特征是它不支持用户留言, 就像新闻提要 (单向连接)。只有信道的管理员才能创建消息 (图例. 5)。
图例.5. Bot 作为通道管理员。
Bots 也可以添加到管理员列表。这可令信道作为理想的提供交易信号的工具。稍后我们将编写一个简单的 bot 来发布标准 MACD 指标的信号。新的公共信道可以通过即时通信软件的 "新信道" 菜单创建。不要忘记将您的 bot 添加到信道的管理员列表。它是通过信道的属性窗口来实现。所有的准备工作已经完成, 让我们继续进行编程。
处理消息流
当撰写本文时, 我有一个目标就是创建一个类来承担例行消息处理, 并可以集中精力于 bot 的逻辑。作为结果, 类 CCustomBot 实现了所写操作的最小功能。
使用 WebRequst 函数 POST 请求可与服务器进行通信。每个命令有其自己的 URL:
https://api.telegram.org/bot< TOKEN >/ METHOD_NAME
此处 TOKEN 是注册 bot 的令牌; METHOD_NAME — 支持的方法列表。
自服务器的响应抵达时是 JSON 格式, 所以需要一款优秀的 JSON 解析器。我应用了一款本地化的解析器 JSON 序列化和逆序列化。我十分感谢 Alexey (sergeev) 为此所做的贡献。此外, 还应用了显示参数的面板。类 CComment 取自代码库, 它十分适合这个任务。类的公共方法名称借助 API 的文档来达到普适性。在类中我们已管理的方法列表如下:
- GetMe
- GetUpdates
- GendMessage
- SendPhoto
- SendChatAction
为了理解如何使用这些方法, 我们将进一步深入编程。
GetMe
由于以上所有函数每次请求期间要发送令牌, 则 GetMe 函数检查其可信度。最好是在 EA 开始时执行检查, 并且在出故障的情况下通知用户。
int GetMe()
|
|
返回值 | 错误代码 |
如果成功, GetMe 返回 0, 并且您可以通过 Name() 方法找出 bot 的用户名。此名字并非用于操作。不过, 出于提示目的, 它将显示在面板上。像是 telegram.me/<botname> 的地址可以使用即时通信软件的网页版, 且作为您的 bot 的广告链接。EA 在 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";//令牌 CCustomBot bot; int getme_result; //+------------------------------------------------------------------+ //| OnInit | //+------------------------------------------------------------------+ int OnInit() { //--- 设置令牌 bot.Token(InpToken); //--- 检查令牌 getme_result=bot.GetMe(); //--- 返回定时器 EventSetTimer(3); OnTimer(); //--- 结束 return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| OnDeinit | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { Comment(""); } //+------------------------------------------------------------------+ //| OnTimer | //+------------------------------------------------------------------+ void OnTimer() { //--- 显示错误消息并退出 if(getme_result!=0) { Comment("错误: ",GetErrorDescription(getme_result)); return; } //--- 显示机器人名字 Comment("机器人名字: ",bot.Name()); //---{ 在此插入您的代码 } } //+------------------------------------------------------------------+
GetUpdates
主函数 GetUpdates 从服务器上读取一个消息数组。它需要通过定时器来调用。定时器的更新周期不应低于一秒钟, 以避免服务器过载。
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 命令至 bot。要点是保存这种信息, 并在以后作出回应。聊天号码 chat[id] 是独有的标识符。同一用户使用各种不同设备与 bot 通信会有不同的聊天标识符。此参数适合作为构建聊天列表的独有关键字。当操作时, bot 将会累加聊天数组, 并依次更新最后收到的消息。如果我们已经做出了回应, 则此消息已被处理, 我们可以为它设置结束标志。聊天类型也是已知的。它可以是私聊, 或群聊。
为了编写您自己的机器人, 只需要简单地继承 CCustomBot 类, 并重写类中的 ProcessMessage 虚函数, 实现操作逻辑。一个成熟的 bot, 依据 Telegram 文档, 需要知道如何响应两个命令: "/start" 和 "/help"。让我们编写第一个 bot 来响应它们。
//+------------------------------------------------------------------+ //| Telegram_GetUpdates.mq5 | //| 版权所有 2014, MetaQuotes 软件公司| //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "版权所有 2014, MetaQuotes 软件公司" #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(!chat.m_new_one.done) { chat.m_new_one.done=true; string text=chat.m_new_one.message_text; //--- 开始 if(text=="/start") SendMessage(chat.m_id,"你好, 世界!我是 bot。\xF680"); //--- 帮助 if(text=="/help") SendMessage(chat.m_id,"我的命令列表: \n/start-开始和我聊天 \n/help-获取帮助"); } } } }; //--- input string InpToken="177791741:AAH0yB3YV7ywm80af_-AGqb7hzTR_Ud9DhQ";//令牌 //--- CMyBot bot; int getme_result; //+------------------------------------------------------------------+ //| OnInit | //+------------------------------------------------------------------+ int OnInit() { //--- 设置令牌 bot.Token(InpToken); //--- 检查令牌 getme_result=bot.GetMe(); //--- 运行定时器 EventSetTimer(3); OnTimer(); //--- 结束 return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| OnDeinit | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { Comment(""); } //+------------------------------------------------------------------+ //| OnTimer | //+------------------------------------------------------------------+ void OnTimer() { //--- 显示错误消息并退出 if(getme_result!=0) { Comment("错误: ",GetErrorDescription(getme_result)); return; } //--- 显示 bot 名称 Comment("Bot 名称: ",bot.Name()); //--- 读取消息 bot.GetUpdates(); //--- 处理消息 bot.ProcessMessages(); } //+------------------------------------------------------------------+获得的结果示于图例 6。
图例.6. 拥有最小命令集的 bot。
利用键盘操作
为了便于用户与 bot 之间的交互通信, 开发者产生的想法是使用 "键盘"。当每次聊天发送消息时, 可以显示一个具有预选按键集合的 "键盘"。当按下按键y, 用户发送一个其上指示的消息。此种方式, 令 bot 和一名用户之间的交互显著简化。
这个类有三个函数用于键盘操作。第一个函数创建键盘的对象。
string ReplyKeyboardMarkup(const string keyboard, const bool resize, const bool one_time) |
|
keyboard |
本地按键集合的字符串 |
resize | 改变键盘大小的许可 |
one_time | 仅显示一次键盘在按键之后键盘消失。 |
返回值 | 当 SendMessage 发送消息时, 字符串 (JSON 对象) 需要作为 reply_markup 参数传递。 |
第二个函数隐藏键盘。
string ReplyKeyboardHide() |
|
返回值 | 当 SendMessage 发送消息时, 字符串 (JSON 对象) 需要作为 reply_markup 参数传递。 |
第三个函数可以发送一条小面板, 其类型表示 bot 期望能从您这里得到文本表单的应答 (不显示键盘)。
string ForceReply() |
|
返回值 | 当 SendMessage 发送消息时, 字符串 (JSON 对象) 需要作为 reply_markup 参数传递。 |
现在我们来分析这些函数如何使用。
SendMessage
键盘不能显示或自行隐身。这个动作伴随一条消息发送。函数 SendMessage 发送聊天消息如下所示:
int SendMessage(const long chat_id, const string text, const string reply_markup=NULL) |
|
chat_id |
聊天号码 |
text | 消息文本 |
reply markup | 键盘 (JSON 对象) |
返回值 | 错误代码 |
在此情况下键盘是选项。我们可以从我们的 MQL 程序里发送简单的文本消息。我的看法是, 此函数比原生的 SendNotification 更有趣。首先, 我们可以更频繁地发送消息 (大概每秒钟一条)。第二, 它支持 HTML 格式。此外, 发送表情的能力是一桩红利。
Тelegram 所支持的表情符号可 在此 查看。如您所见, 主要的表情代码范围在 1F300 – 1F700。它们的位数已经超出了 MQL5 可接受的双字节字符串范围。如果您删除高位仅保留两位, 则所得范围 (F300 – F700) 落入 (E000— F8FF) 区域, 而此范围在 Unicode 表里当前保留。如此一来, 没有什么可以阻止我们使用低两位发送表情符号。带有经典 U+1F642 代码表情的消息看上去如下:
string text="Have a nice day.\xF642";//message text with Emoji U+1F642
这也是公平的, 事实上文本才是关键。没有什么可以阻止我们从按键上使用表情符号。让我们来编写事件处理器显示三个按键的例程。
//+------------------------------------------------------------------+ //| Telegram_SendMessage.mq5 | //| 版权所有 2014, MetaQuotes 软件公司| //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "版权所有 2014, MetaQuotes 软件公司" #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]="按钮 #1"; m_button[1]="按钮 #2"; m_button[2]="按钮 #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 或 help 命令 if(text=="/start" || text=="/help") bot.SendMessage(chat.m_id,"点击按钮",bot.ReplyKeyboardMarkup(GetKeyboard(),false,false)); //--- 点击事件 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";//令牌 CMyBot bot; int getme_result; //+------------------------------------------------------------------+ //| OnInit | //+------------------------------------------------------------------+ int OnInit() { //--- 设置令牌 bot.Token(InpToken); //--- 检查令牌 getme_result=bot.GetMe(); //--- 运行定时器 EventSetTimer(1); OnTimer(); //--- 结束 return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| OnDeinit | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { Comment(""); } //+------------------------------------------------------------------+ //| OnTimer | //+------------------------------------------------------------------+ void OnTimer() { //--- 显示错误消息并退出 if(getme_result!=0) { Comment("错误: ",GetErrorDescription(getme_result)); return; } //--- 显示 bot 名称 Comment("Bot 名称: ",bot.Name()); //--- 读取消息 bot.GetUpdates(); //--- 处理消息 bot.ProcessMessages(); } //+------------------------------------------------------------------+
结果就是我们得到一条带键盘的消息, 如图例. 7。
图例.7. 带键盘的消息。
//+------------------------------------------------------------------+ //| Telegram_SendMessage.mq5 | //| 版权所有 2014, MetaQuotes 软件公司| //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "版权所有 2014, MetaQuotes 软件公司" #property link "https://www.mql5.com" #property version "1.00" #property strict #include <Telegram.mqh> #define MUTE_TEXT "静音" #define UNMUTE_TEXT "非静音" #define LOCK_TEXT "锁定" #define UNLOCK_TEXT "解锁" #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]="单选按钮 #1"; m_radio_button[1]="单选按钮 #2"; m_radio_button[2]="单选按钮 #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; //--- 开始 if(text=="/start" || text=="/help") { bot.SendMessage(chat.m_id,"点击按钮",bot.ReplyKeyboardMarkup(GetKeyboard(),false,false)); } //--- 点击单选按钮 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)); } } //--- 解锁 if(text==LOCK_CODE+" "+LOCK_TEXT) { m_lock_state=false; bot.SendMessage(chat.m_id,UNLOCK_TEXT,bot.ReplyKeyboardMarkup(GetKeyboard(),false,false)); } //--- 锁定 if(text==UNLOCK_CODE+" "+UNLOCK_TEXT) { m_lock_state=true; bot.SendMessage(chat.m_id,LOCK_TEXT,bot.ReplyKeyboardMarkup(GetKeyboard(),false,false)); } //--- 非静音 if(text==MUTE_CODE+" "+MUTE_TEXT) { m_mute_state=false; bot.SendMessage(chat.m_id,UNMUTE_TEXT,bot.ReplyKeyboardMarkup(GetKeyboard(),false,false)); } //--- 静音 if(text==UNMUTE_CODE+" "+UNMUTE_TEXT) { m_mute_state=true; bot.SendMessage(chat.m_id,MUTE_TEXT,bot.ReplyKeyboardMarkup(GetKeyboard(),false,false)); } } } } };
结果是我们得到以下窗口 (图例 8)。
图例.8. 单选按钮和多选按钮控件
我们可以从这里看出, 在此使用的表情符号提供一个更好的视觉设置。除了这些控件, 我们将能够轻松实现每个子菜单导航的层叠菜单。一切都取决于您将要提出并决定实现的功能。
当我们决定在信道里发布消息的情况下, 此时有第二个选项 - SendMessage。
int SendMessage(const string channel_name, const string text) |
|
channel_name |
信道名称如 @name |
text | 消息文本。支持 HTML 标签。 |
返回值 | 错误代码 |
此函数的结果显示如下图例 9。
多媒体操作
Bots 可以交换照片, 声频和视频文件, 还有语音消息, 贴纸和位置坐标。在撰写本文之时, 具有交换联系人数据并邀请他人参加会议功能的 Bot API 2.0 已经发布。从已提供的完整清单中, 只有交换照片选项与我们相关。
SendPhoto
该类已经实现了两种发送照片的应用方式。
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 | 上载到服务器的照片标识符 |
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,"屏幕截图"); if(result==0) Print("照片 ID: ",photo_id); else Print("错误: ",GetErrorDescription(result));
我相信您会遇到这种情况: 需要将一张照片发送给多个用户, 或将同一照片发送多次。在此情况下, 更合理的情形是载照片只上一次, 当重发照片时, 利用 SendPhoto 函数的第二种选项, 并应用 photo_id 识别符:
int SendPhoto(const long chat_id, const string photo_id, const string caption=NULL) |
|
chat_id | 聊天号码 |
photo_id | 上载到服务器的照片标识符 |
caption | 照片下面的签名文本 |
SendChartAction
想象一下, 您处理一条用户的回应, 并几乎准备好提供结果。由于创建回应需要花费几秒钟, 此时应当客气地通知用户您已经在处理。这就是使用事件的目的。例如, 在将产生的图表截图发送给用户期间, 您可以提示 "发送照片" 事件。这是通过 SendChatAction 实现的。
int SendChatAction(const long chat_id, const ENUM_CHAT_ACTION actiona) |
|
chat_id | 聊天号码 |
action | 事件标识符 |
Bots 例程
第一个 bot 是 Telegram_Bot_EA, 可以获取关于账户余额、报价和图表截图的信息。在这部视频里演示了它是如何运作的。
第二个 bot 是 Telegram_Search_EA, 发送搜索结果至 MQL5.com。您也许会好奇地观看下面的视频来看看它是如何实际工作的。
第三个 bot 是 Telegram_Signal_EA, 在信道里发布来自 MACD 标准指标的信号。我认为很容易就能将 MACD 改为您喜爱的指标, 并用此代码为您的目的服务。
//+------------------------------------------------------------------+ //| Telegram_Signal_EA_v1.mq4 | //| 版权所有 2014, MetaQuotes 软件公司| //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "版权所有 2014, MetaQuotes 软件公司" #property link "https://www.mql5.com" #property version "1.00" //+------------------------------------------------------------------+ //| 包含 | //+------------------------------------------------------------------+ #include <Telegram.mqh> //--- 输入参数 input string InpChannelName="@forexsignalchannel";//信道名称 input string InpToken="177791741:AAH0yB3YV7ywm80af_-AGqb7hzTR_Ud9DhQ";//令牌 //--- 全局变量 CCustomBot bot; int macd_handle; datetime time_signal=0; //+------------------------------------------------------------------+ //| 智能交易程序初始化函数 | //+------------------------------------------------------------------+ int OnInit() { time_signal=0; //--- 设置令牌 bot.Token(InpToken); //--- 获得指标句柄 macd_handle=iMACD(NULL,0,12,26,9,PRICE_CLOSE); if(macd_handle==INVALID_HANDLE) return(INIT_FAILED); //--- 结束 return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| 智能交易程序分时函数 | //+------------------------------------------------------------------+ void OnTick() { //--- 获得时间 datetime time[1]; if(CopyTime(NULL,0,0,1,time)!=1) return; //--- 在每根柱线上检查信号 if(time_signal!=time[0]) { //--- 第一次计算 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]; //--- 发送买入信号 if(macd[1]>signal[1] && macd[0]<=signal[0]) { string msg=StringFormat("名称: MACD 信号\n品种: %s\n时间帧: %s\n类型: 买入\n价格: %s\n时间: %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("错误: ",GetErrorDescription(res)); } //--- 发送卖出信号 if(macd[1]<signal[1] && macd[0]>=signal[0]) { string msg=StringFormat("名称: MACD 信号\n品种: %s\n时间帧: %s\n类型: 卖出\n价格: %s\n时间: %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("错误: ",GetErrorDescription(res)); } } } //+------------------------------------------------------------------+
结果就是您将收到一条消息, 如图例. 9。
图例.9. MACD 指标信号。
结论
那些希望将它们的 bot 连接到 Yandex.AppMetrika 基地进行分析的用户, 也许可以使用 Botan 源。该服务的思路是: 用户接收的消息发送给它们, 并请求诸如分段、跟踪、队列分析等指标。没有必要退出即时通信软件, 因为统计数据将通过挂载到图表上的特殊 bot 发送, 更详细的报告将在网站上提供。
我希望这篇文章能启发您在交易中应用 Telegram。我的目标并非要覆盖所有的细节, 因为它们已经在 Bot API 的文档里提供。本文所附的代码适用于这两个交易平台 — MetaTrader 4 和 MetaTrader 5。