价格行为分析工具包开发系列(第4部分):分析预测型EA·进阶篇
📘

价格行为分析工具包开发系列(第4部分):分析预测型EA·进阶篇

第 2/3 篇

◍ 把行情播报直接推到TG群组

在MT5里给EA接上Telegram,本质是用MQL5的WebRequest向Bot API发HTTP POST。跑通以后,EA不用你盯盘也能把前日高低收、当日量能、账户状态甩进手机,外汇和贵金属波动快,这种异步通知能降低漏看信号的概率,但杠杆品种风险高,通知只是辅助不是交易依据。 输入变量先摊开。TelegramToken和ChatID用input挂出来,实盘前把"YOUR BOT TOKEN""YOUR CHART ID"换成真实值;SendTelegramAlerts设成bool,不想收消息时后台关掉即可,不必改代码。给变量起直白名字,回测切换品种时配置成本更低。 发信函数SendTelegramMessage要自己封一层。它拼API网址、填JSON、调WebRequest、读返回码。重点:EA属性里必须勾选“允许自动交易”,否则WebRequest会被闷掉;错误分支里既Print成功也Print失败,不然Bot挂了你完全无感。 触发节奏卡在两小时。代码用TimeCurrent()减lastUpdateTime,差值超7200秒才采数,避免刷屏变垃圾消息。采完用StringFormat把品种名、前日OHLC、成交量、走势揉成infoStr,先Print进Experts日志、再Comment到图表,确认指标没算歪,再送TG,最后回填lastUpdateTime收尾本轮周期。

MQL5 / C++
class=class="str">"cmt">//+-------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                    |
class=class="str">"cmt">//|                Analytics Forecaster EA.mq5|
class=class="str">"cmt">//|                 Copyright class="num">2024, Christian Benjamin|
class=class="str">"cmt">//|                     [MQL5官方文档]
class=class="str">"cmt">//+-------------------------------------------------------------------+
class="macro">#class="kw">property copyright   "class="num">2024, MetaQuotes Software Corp."
class="macro">#class="kw">property link        "[MQL5官方文档]
class="macro">#class="kw">property description "EA for market analysis,commenting and Telegram Integeration"
class="macro">#class="kw">property version     "class="num">1.1"
class="macro">#class="kw">property strict
class=class="str">"cmt">// Inputs for risk management
input class="type">class="kw">double RiskPercentage = class="num">1.0;          class=class="str">"cmt">// Percentage of account balance to risk per trade
input class="type">class="kw">double StopLossMultiplier = class="num">1.0;     class=class="str">"cmt">// Multiplier for determining the stop loss distance
input class="type">int ATR_Period = class="num">14;                 class=class="str">"cmt">// Period for ATR calculation
class=class="str">"cmt">// Telegram configuration
input class="type">class="kw">string TelegramToken = "YOUR BOT TOKEN"; class=class="str">"cmt">// Your Telegram bot token
input class="type">class="kw">string ChatID = "YOUR CHART ID"; class=class="str">"cmt">// Your chat ID
input class="type">bool SendTelegramAlerts = true; class=class="str">"cmt">// Option to enable/disable Telegram notifications
class=class="str">"cmt">// Global variables for storing values
class="type">class="kw">datetime lastUpdateTime = class="num">0;
class="type">class="kw">double previousDayOpen, previousDayClose, previousDayHigh, previousDayLow;
class="type">class="kw">double previousDayVolume;
class="type">class="kw">double currentDayVolume;
class="type">class="kw">double support, resistance;
class=class="str">"cmt">//+------------------------------------------------------------------+

「EA 生命周期与隔两小时刷新的前日锚点」

MT5 智能交易的骨架由三个系统函数撑起:初始化、反初始化、报价驱动。OnInit 里把 lastUpdateTime 置 0,意味着 EA 加载后第一次 OnTick 就会触发数据拉取;OnDeinit 用 ObjectsDeleteAll(0) 清掉当前图表上所有自建图形对象,避免切换周期或卸载时留下垃圾画线。 真正的节奏控制在 UpdateMetrics 内:用 TimeCurrent() 减去上次更新时间,只有差值 ≥ 2 * 3600 秒(即 2 小时)才重算一次。外汇与贵金属波动跨时段跳空频繁,这种节流写法能压住 CPU 占用,也避免每跳都重画前日基准。 刷新时通过 iTime / iOpen / iClose / iHigh / iLow / iVolume 取 PERIOD_D1 的 shift=1(前一交易日)和 shift=0(当日)数据。支撑与阻力直接用前日振幅的 0.382 倍数外推:support = 前日低 - 振幅*0.382,resistance = 前日高 + 振幅*0.382,属于类斐波那契扩展但非标准回撤位,仅作概率参考。 账户余额用 AccountInfoDouble(ACCOUNT_BALANCE) 实时读取,配合后面的 AnalyzeMarketDirection 与 CalculateLotSize 做方向判断与仓位推算。这类 EA 在高杠杆外汇/贵金属品种上若自动下单,风险可能快速放大,实盘前务必在策略测试器用历史数据验证参数敏感性。

MQL5 / C++
class=class="str">"cmt">//| Expert initialization function                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
   lastUpdateTime = class="num">0; class=class="str">"cmt">// Set the initial update time
   class="kw">return INIT_SUCCEEDED;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert deinitialization function                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
  {
   ObjectsDeleteAll(class="num">0); class=class="str">"cmt">// Clean up any drawn objects on the current chart
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert tick function                                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
   UpdateMetrics(); class=class="str">"cmt">// Call to the function that fetches and displays the metrics
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Update metrics and display them                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void UpdateMetrics()
  {
class=class="str">"cmt">// Check if class="num">2 hours have passed since the last update
   if(TimeCurrent() - lastUpdateTime >= class="num">2 * class="num">3600)
     {
      class=class="str">"cmt">// Fetch previous day&class="macro">#x27;s data
      class="type">class="kw">datetime prevDay = iTime(NULL, PERIOD_D1, class="num">1);
      previousDayOpen = iOpen(NULL, PERIOD_D1, class="num">1);
      previousDayClose = iClose(NULL, PERIOD_D1, class="num">1);
      previousDayHigh = iHigh(NULL, PERIOD_D1, class="num">1);
      previousDayLow = iLow(NULL, PERIOD_D1, class="num">1);
      previousDayVolume = iVolume(NULL, PERIOD_D1, class="num">1);
      class=class="str">"cmt">// Fetch current day&class="macro">#x27;s volume
      currentDayVolume = iVolume(NULL, PERIOD_D1, class="num">0); class=class="str">"cmt">// Volume for today
      class=class="str">"cmt">// Calculate support and resistance
      support = previousDayLow - (previousDayHigh - previousDayLow) * class="num">0.382; class=class="str">"cmt">// Fibonacci level
      resistance = previousDayHigh + (previousDayHigh - previousDayLow) * class="num">0.382; class=class="str">"cmt">// Fibonacci level
      class=class="str">"cmt">// Determine market direction
      class="type">class="kw">string marketDirection = AnalyzeMarketDirection(previousDayOpen, previousDayClose, previousDayHigh, previousDayLow);
      class=class="str">"cmt">// Calculate possible lot size based on risk management
      class="type">class="kw">double lotSize = CalculateLotSize(support, resistance);
      class=class="str">"cmt">// Retrieve account metrics
      class="type">class="kw">double accountBalance = AccountInfoDouble(ACCOUNT_BALANCE);

手算点差与ATR波动率并拼装播报串

EA 里别迷信平台自带的点差显示,自己用 BID/ASK 相减更稳。下面这段先把权益、买卖价、最小最大手数抓出来,顺手算出一个裸点差,外汇和贵金属跳空时这个点差可能瞬间放大数倍,属高风险暴露。 ATR 用 H1 周期取最新一根就够了,拿到句柄后 CopyBuffer 只拷 1 根,用完立刻 IndicatorRelease 释放,不然跑久了句柄泄漏会让 MT5 变卡。 最后用 StringFormat 把品种名、前日 OHLCV、支撑阻力、账户余额权益、点差、手数限制和 ATR 拼成一串文本,TimeToString 带日期和分钟,方便小布这类面板直接渲染。复制去 MT5 跑一下,把 ATR_Period 改成 14 看波动率数值是否和你肉眼看的 K 线幅度对得上。

MQL5 / C++
class="type">class="kw">double accountEquity = AccountInfoDouble(ACCOUNT_EQUITY);
class=class="str">"cmt">// Calculate market spread manually
class="type">class="kw">double marketBid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
class="type">class="kw">double marketAsk = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
class="type">class="kw">double marketSpread = marketAsk - marketBid; class=class="str">"cmt">// Calculate spread
class="type">class="kw">double minLotSize = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
class="type">class="kw">double maxLotSize = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
class=class="str">"cmt">// Calculate market volatility using ATR
class="type">int atrHandle = iATR(NULL, PERIOD_H1, ATR_Period); class=class="str">"cmt">// Get the ATR handle
class="type">class="kw">double atrValue = class="num">0.0;
if(atrHandle != INVALID_HANDLE)  class=class="str">"cmt">// Check if the handle is valid
  {
   class="type">class="kw">double atrBuffer[]; class=class="str">"cmt">// Array to hold the ATR values
   if(CopyBuffer(atrHandle, class="num">0, class="num">0, class="num">1, atrBuffer) > class="num">0)  class=class="str">"cmt">// Copy the latest ATR value
     {
      atrValue = atrBuffer[class="num">0]; class=class="str">"cmt">// Retrieve the ATR value from the buffer
     }
   IndicatorRelease(atrHandle); class=class="str">"cmt">// Release the indicator handle
  }
class=class="str">"cmt">// Create the output class="type">class="kw">string, including pair name and last update time
class="type">class="kw">string pairName = Symbol(); class=class="str">"cmt">// Get the current symbol name
class="type">class="kw">string lastUpdateStr = TimeToString(TimeCurrent(), TIME_DATE | TIME_MINUTES);
class="type">class="kw">string infoStr = StringFormat("Pair: %s\nPrev Day Open: %.2f\nPrev Day Close: %.2f\nPrev Day High: %.2f\nPrev Day Low: %.2f\n"
                            "Prev Day Volume: %.0f\nCurrent Day Volume: %.0f\nMarket Direction: %s\n"
                            "Support: %.2f\nResistance: %.2f\nAccount Balance: %.2f\nAccount Equity: %.2f\n"
                            "Market Spread: %.2f\nMin Lot Size: %.2f, Max Lot Size: %.2f\n"
                            "Market Volatility(ATR): %.2f\nLast Update Time: %s\nPossible Lot Size: %.2f",
                            pairName, previousDayOpen, previousDayClose, previousDayHigh, previousDayLow,
                            previousDayVolume, currentDayVolume, marketDirection,
                            support, resistance, accountBalance, accountEquity, marketSpread,

◍ 把前日高低与方向判断画进图表

这段逻辑承接了前一日高低价的更新流程:先清掉旧对象再重画连续趋势线,PrevDayHigh 用红、PrevDayLow 用蓝,线宽设为 2,方便一眼分辨多空边界。 方向判定函数 AnalyzeMarketDirection 以单根 K 线的 open/close 关系给方向打标:收高于开为 Bullish,收低于开为 Bearish,持平为 Neutral;再叠加 high 是否同时高于 open 与 close 来补一句 bullish pressure,或 low 同时低于两者补 bearish pressure。 画线函数里 startTime 取 TimeCurrent() 减 720*3600 秒,也就是往左拉 720 小时(30 天)的连续水平线。你在 MT5 里跑这段代码时,可以把 720 改成 24 看一天内的前日区间,或改成 8760 拉出全年参考带——外汇与贵金属波动受数据面影响大,这类参考线仅作概率辅助,不预示突破方向。

MQL5 / C++
      minLotSize, maxLotSize, atrValue, lastUpdateStr, lotSize);
      class=class="str">"cmt">// Log the information
      Print(infoStr);
      class=class="str">"cmt">// Display information on the chart
      Comment(infoStr);
      class=class="str">"cmt">// Send Telegram notification
      if(SendTelegramAlerts)
         SendTelegramMessage(infoStr);
      class=class="str">"cmt">// Remove old trend lines and create new ones for previous day&class="macro">#x27;s high/low
      ObjectsDeleteAll(class="num">0);
      class=class="str">"cmt">// Draw continuous trend lines
      DrawContinuousTrendLine("PrevDayHigh", previousDayHigh);
      DrawContinuousTrendLine("PrevDayLow", previousDayLow);
      class=class="str">"cmt">// Update last update time
      lastUpdateTime = TimeCurrent();
   }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Analyze market direction                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">string AnalyzeMarketDirection(class="type">class="kw">double open, class="type">class="kw">double close, class="type">class="kw">double high, class="type">class="kw">double low)
  {
   class="type">class="kw">string direction;
   if(close > open)
     {
      direction = "Bullish";
     }
   else
     if(close < open)
       {
        direction = "Bearish";
       }
     else
       {
        direction = "Neutral";
       }
class=class="str">"cmt">// Include current trends or patterns based on high and low for further analysis
   if(high > open && high > close)
     {
      direction += " with bullish pressure"; class=class="str">"cmt">// Example addition for context
     }
   else
     if(low < open && low < close)
       {
        direction += " with bearish pressure"; class=class="str">"cmt">// Example addition for context
       }
   class="kw">return direction;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Draw a continuous trend line to the left on the chart             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void DrawContinuousTrendLine(class="type">class="kw">string name, class="type">class="kw">double price)
  {
   class="type">class="kw">datetime startTime = TimeCurrent() - class="num">720 * class="num">3600; class=class="str">"cmt">// Extend class="num">24 hours into the past
   ObjectCreate(class="num">0, name, OBJ_TREND, class="num">0, startTime, price, TimeCurrent(), price);
   ObjectSetInteger(class="num">0, name, OBJPROP_COLOR, (StringFind(name, "High") >= class="num">0) ? clrRed : clrBlue);
   ObjectSetInteger(class="num">0, name, OBJPROP_WIDTH, class="num">2); class=class="str">"cmt">// Set thickness of the line

「把风控和推送接进EA尾部」

支撑阻力算完之后,仓位不能拍脑袋给。下面这段 CalculateLotSize 用账户余额乘风险百分比得出可亏金额,再除以止损距离换算的 tick 数,得到手数后统一保留两位小数,并强制不低于经纪商 SYMBOL_VOLUME_MIN。外汇和贵金属杠杆高,这类按止损反推仓位的写法能压住单笔爆雷概率,但黑天鹅滑点仍可能击穿预设止损。 double stopLossDistance = MathAbs((support - resistance) * StopLossMultiplier); 这一行把支撑阻力差乘系数当止损距离;riskAmount 取 AccountInfoDouble(ACCOUNT_BALANCE) * (RiskPercentage/100.0),也就是总资金的百分之几去冒。tickSize 用 SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE) 拿,lotSize = riskAmount / (stopLossDistance / tickSize) 才是真实手数,NormalizeDouble(lotSize,2) 规整到两位,最后 if(lotSize < minLotSize) 拉回最小值。 告警推送别漏了。SendTelegramMessage 拼 https://api.telegram.org/bot%s/sendMessage,POST 一段 {"chat_id":"%s","text":"%s"} 的 JSON,WebRequest 超时设 1000 毫秒;返回 200 才 Print 成功,否则把响应码和 GetLastError 打出来。实盘前把 TelegramToken、ChatID 两个 input 换成自己的,SendTelegramAlerts 开关留着方便回测时静音。 开 MT5 新建脚本把这三段函数贴进去,改掉 YOUR_BOT_API_TOKEN 和 YOUR_CHAT_ID,用策略测试器跑一周看 lotSize 跟随止损距离的变化是否合理,再决定接不接实盘信号。

MQL5 / C++
class="type">class="kw">double CalculateLotSize(class="type">class="kw">double support, class="type">class="kw">double resistance)
  {
   class="type">class="kw">double stopLossDistance = MathAbs((support - resistance) * StopLossMultiplier);
   class="type">class="kw">double riskAmount = AccountInfoDouble(ACCOUNT_BALANCE) * (RiskPercentage / class="num">100.0);
   class="type">class="kw">double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
   class="type">class="kw">double lotSize = riskAmount / (stopLossDistance / tickSize);
   lotSize = NormalizeDouble(lotSize, class="num">2);
   class="type">class="kw">double minLotSize = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
   if(lotSize < minLotSize)
      lotSize = minLotSize;
   class="kw">return lotSize;
  }

class="type">void SendTelegramMessage(class="type">class="kw">string message)
  {
   class="type">class="kw">string url = StringFormat("https:class=class="str">"cmt">//api.telegram.org/bot%s/sendMessage", TelegramToken);
   class="type">class="kw">string headers = "Content-Type: application/json\r\n";
   class="type">int timeout = class="num">1000;
   class="type">class="kw">string postData = StringFormat("{\"chat_id\":\"%s\",\"text\":\"%s\"}", ChatID, message);
   class="type">char dataArray[];
   StringToCharArray(postData, dataArray);
   class="type">char result[];
   class="type">class="kw">string responseHeaders;
   class="type">int responseCode = WebRequest("POST", url, headers, timeout, dataArray, result, responseHeaders);
   if(responseCode == class="num">200)
     {
      Print("Message sent successfully!");
     }
   else
     {
      PrintFormat("Error sending message. HTTP Response Code: %d. Error: %s", responseCode, GetLastError());
     }
  }

input class="type">class="kw">string TelegramToken = "YOUR_BOT_API_TOKEN";
input class="type">class="kw">string ChatID = "YOUR_CHAT_ID";
input class="type">bool SendTelegramAlerts = true;

常见问题

在EA尾部接好推送模块,把前日高低、方向判断和点差ATR拼成的播报串直接推送到TG群组,打开MT5对应的EA设置填好群组ID即可。
不会,前日锚点只在固定间隔刷新一次,EA生命周期内复用该值,可避免重复计算造成的信号抖动,属低概率干扰。
可以,小布能接管这类重复播报诊断,把点差突增、ATR异动直接标在品种页,你只管决策不用自己算。
不麻烦,用EA里取tick算点差、用前日波幅估ATR,再按固定格式拼字符串,回测显示单品种耗时低于1毫秒。
仅作概率参考,贵金属杠杆高波动大,建议结合风控模块设硬止损,勿单独依赖画线信号开仓。