价格行为分析工具包开发系列(第4部分):分析预测型EA·进阶篇
◍ 把行情播报直接推到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收尾本轮周期。
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 在高杠杆外汇/贵金属品种上若自动下单,风险可能快速放大,实盘前务必在策略测试器用历史数据验证参数敏感性。
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 线幅度对得上。
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 拉出全年参考带——外汇与贵金属波动受数据面影响大,这类参考线仅作概率辅助,不预示突破方向。
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 跟随止损距离的变化是否合理,再决定接不接实盘信号。
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;