价格行为分析工具包开发(第 17 部分):TrendLoom EA 工具(基础篇)
TrendLoom EA 工具的设计起点
TrendLoom EA 是价格行为分析工具包的第 17 个扩展模块,运行于 MetaTrader 5 环境,定位为趋势识别型的自动化交易辅助工具。它在 2025 年 11 月 11 日发布,原文页面记录浏览量为 1 019、互动标记 3 次,说明该工具尚处于早期被验证阶段。 从结构看,该模块原文依次包含策略概览、MQL5 实现、代码与回测结果四块。对读者而言,最有用的切入点是先把策略概览里的趋势判定逻辑吃透,再对照 MQL5 代码看信号如何落地,而不是一上来就挂实盘。 外汇与贵金属市场杠杆高、滑点随机,任何趋势类 EA 都只是概率优势,不存在稳定捕获行情的保证。建议先在 MT5 策略测试器用历史数据跑一遍,确认信号触发频率与自身风险承受匹配,再考虑模拟盘验证。
「别被小周期骗了进场」
做趋势确认时,很多人会同时盯 M1、M5、M15 或者 H1、H4、W1 来交叉验证信号可靠性。但实操里常踩坑:在 M5 上看到卖出信号进了场,切到 H4 才发现大周期是买入趋势,单子一进去就被周期矛盾洗出去。 外汇与贵金属市场高杠杆、高波动,这种跨周期打架的情况极易造成无效止损。 TrendLoom EA 的思路是直接塞给你一个七按钮面板,每个按钮绑定一种交易风格,并同时拉取三个时间周期用移动平均线算信号,输出买 / 卖 / 中性结论。不用手动切图,面板检测到相关信号会持续刷新,等于把多周期确认压缩成一次点击。 开 MT5 装个这类面板 EA,先把其中一个按钮的三个周期设成你常看的 M5+H1+H4,跑两天看它给的中性信号能不能帮你管住手。
◍ 面板七键怎么给出买卖信号
TrendLoom EA 把交易逻辑做成了带七个按钮的图形面板,每个按钮锁定一组时间周期组合:超短线是 M1/M5/M15,剥头皮日内用 M5/M15/H1,波段看 M15/H1/H4,趋势交易走 H1/H4/D1,多周期确认取 H1/H4/W1,短剥+中趋是 M5/H1/D1,长趋势用 H1/D1/W1。按下任一按钮,EA 只在已收盘的 K 线上动手,不碰当前未成形那根。 具体算法分两步。先取三个周期最后一根完整 K 线的收盘价,各自算一条 50 周期 SMA 作基准;价格上穿 SMA 记 +1,下破记 -1,相等记 0。再把三个数值相加:和 ≥2 出“买入”,≤-2 出“卖出”,其余归“中性”。 这套规则意味着至少要两个周期同向站上或跌破均线才触发单边信号,单一周期反转会被第三个周期中和。外汇与贵金属杠杆高、滑点大,信号只是概率倾向,实盘前务必在 MT5 策略测试器用历史数据跑一遍验证。
TrendLoom 面板的代码骨架怎么搭
打开 MT5 的 MetaEditor,EA 最上方先用 #property strict 强制严格编译,能挡掉一批低级类型错误。随后通过 #include <Controls/Dialog.mqh> 等把对话框、按钮、标签、面板的现成类拉进来;图表上的动态文字则靠 ChartObjects/ChartObjectsTxtControls.mqh 提供,不用自己重写 UI 底层。
输入参数直接决定面板几何与配色:PanelX、PanelY 是左上角坐标(默认都是 10 像素),PanelWidth 设 250,btnWidth 设 220。改这几个数就能把面板挪到图表任意角落,外汇和贵金属图表波动快,建议把 PanelY 留足空间避免遮 K 线。
按钮名和文字塞进数组统一管理,以后加币种切换按钮只改数组不改布局逻辑。宏定义集中放面板标题和分析标签名,拼写错误率会降下来。
核心是两个辅助函数:GetSMA 用 iMA 建句柄取缓冲再释放;AnalyzeTimeframes 抓三个周期收盘价与 SMA,价格高于 SMA 记 +1、低于记 -1,总和 ≥2 出“买入”倾向、≤-2 出“卖出”倾向,否则中性。shift 参数锁最后一根已完成 K 线,多周期叠加可削弱短暂噪音,但外汇高杠杆下信号仍可能失效,需自验。
生命周期里 OnInit 用输入参数建面板,失败就返初始化错误;OnChartEvent 把图表事件甩给面板处理器保响应。代码层已经把界面和分析解耦,复制下去调 SMA 周期就能跑。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| TrendLoom EA.mq5 | class=class="str">"cmt">//| Copyright class="num">2025, MetaQuotes Ltd. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2025, MetaQuotes Ltd." class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class="macro">#class="kw">property strict class="macro">#include <Controls/Dialog.mqh> class="macro">#include <Controls/Button.mqh> class="macro">#include <Controls/Label.mqh> class="macro">#include <Controls/Panel.mqh> class="macro">#include <ChartObjects/ChartObjectsTxtControls.mqh> class=class="str">"cmt">// Adjusted include path(singular folder) class="macro">#ifndef ALIGN_LEFT class="macro">#define ALIGN_LEFT class="num">0 class="macro">#endif class="macro">#ifndef ALIGN_CENTER class="macro">#define ALIGN_CENTER class="num">1 class="macro">#endif class="macro">#ifndef ALIGN_RIGHT class="macro">#define ALIGN_RIGHT class="num">2 class="macro">#endif class="macro">#define clrSilver 0xC0C0C0 class=class="str">"cmt">//---- Input parameters ----------------------------------------------- input class="type">int PanelX = class="num">10; input class="type">int PanelY = class="num">10; input class="type">int PanelWidth = class="num">250; input class="type">int btnWidth = class="num">220;
「面板参数与按钮矩阵的硬编码约定」
这套趋势面板的视觉与交互骨架,靠一组 input 宏直接钉死:按钮高度 30 像素、间距 5 像素,背景用 clrDimGray,头部与文字走蓝紫(clrBlueViolet),分析结论用亮绿(clrLime)。在 MT5 里改这几个值,面板密度和辨识度会立刻变化,不需要动逻辑层。 按钮不是动态生成的,而是写死 7 个分析模式:从 btnShortTerm(M1/M5/M15)到 btnLongTerm(H1/D1/W1),对应短线、剥头皮、波段、趋势、多周期确认、短剥皮中趋势、长线七类视角。buttonTexts 数组里用 \n 做了两行标签,实盘加载后按钮上会直接显示周期组合,省去交易者自己切图表的动作。 CreateTrendPanel 里先调 CAppDialog::Create 建对话框,再用 m_lblHeader 做标题条:坐标 (10,10),宽比面板少 20、高 30,字号 14、字体 Segoe UI,最后用 ObjectSetInteger 把 OBJPROP_ALIGN 设成 ALIGN_CENTER。若任一步返回 false,终端会 Print 对应失败信息——开 MT5 加载时盯一眼 Experts 日志就能定位是对话框还是标签没建出来。 OnEvent 用 sparam 比对按钮名,例如 sparam=="btnShortTerm" 即触发短周期分析。这种字符串硬匹配在 7 个按钮内够用,按钮数若扩到两位数建议改哈希或枚举,否则后期维护容易漏改。
input class="type">int btnHeight = class="num">30; input class="type">int btnSpacing = class="num">5; input class="type">class="kw">color PanelBackgroundColor = clrDimGray; input class="type">class="kw">color PanelHeaderColor = clrBlueViolet; input class="type">class="kw">color ButtonBgColor = clrBlack; input class="type">class="kw">color ButtonTextColor = clrBlueViolet; input class="type">class="kw">color AnalysisTextColor = clrLime; class=class="str">"cmt">//---- Button Names and Texts(class="num">7 analysis options) -------------------- class="type">class="kw">string buttonNames[class="num">7] = { "btnShortTerm", "btnScalping", "btnSwing", "btnTrend", "btnMTFTrend", "btnShortScalper", "btnLongTerm" }; class="type">class="kw">string buttonTexts[class="num">7] = { "Short Term Focus\n(M1, M5, M15)", "Scalping/Intraday\n(M5, M15, H1)", "Swing Trading\n(M15, H1, H4)", "Trend Trading\n(H1, H4, D1)", "MTF Trend Confirm\n(H1, H4, W1)", "Short Scalper/Mid Trend\n(M5, H1, D1)", "Long Term Trend\n(H1, D1, W1)" }; class=class="str">"cmt">// Global object names for panel header and analysis label class="macro">#define PANEL_BG "PanelBG" class="macro">#define PANEL_HEADER "PanelHeader" class="macro">#define ANALYSIS_LABEL "AnalysisResult" class=class="str">"cmt">//--- Helper function declarations class="type">class="kw">double GetSMA(class="type">class="kw">string symbol, ENUM_TIMEFRAMES timeframe, class="type">int period, class="type">int shift); class="type">class="kw">string AnalyzeTimeframes(ENUM_TIMEFRAMES tf1, ENUM_TIMEFRAMES tf2, ENUM_TIMEFRAMES tf3); class="type">bool CreateTrendPanel(const class="type">long chart, const class="type">class="kw">string name, const class="type">int x1, const class="type">int y1, const class="type">int x2, const class="type">int y2) { if(!CAppDialog::Create(chart, name, class="num">0, x1, y1, x2, y2)) { Print("Failed to create TrendLoom dialog."); class="kw">return false; } if(!m_lblHeader.Create(class="num">0, "TrendLoomHeader", class="num">0, class="num">10, class="num">10, x2 - x1 - class="num">20, class="num">30)) { Print("Failed to create header label."); class="kw">return false; } m_lblHeader.Text("TrendLoom EA"); m_lblHeader.Color(PanelHeaderColor); m_lblHeader.FontSize(class="num">14); m_lblHeader.Font("Segoe UI"); Add(m_lblHeader); if(!ObjectSetInteger(0L, m_lblHeader.Name(), OBJPROP_ALIGN, (class="type">long)ALIGN_CENTER)) Print("Failed to set header alignment"); class="type">bool OnEvent(const class="type">int id, const class="type">long &lparam, const class="type">class="kw">double &dparam, const class="type">class="kw">string &sparam) { if(sparam == "btnShortTerm") {
◍ 把多周期按钮接进 SMA 信号判定
面板按钮的回调里,短周期按钮取 M1/M5/M15 三周期做 AnalyzeTimeframes,刷单按钮则取 M5/M15/H1。两者都把返回字符串拼成提示语,写进标签并弹窗报警,函数最终 return true 表示已处理。 AnalyzeTimeframes 内部固定用 50 周期 SMA、shift=1 取上一根已完成蜡烛。三个周期各自比收盘价与 SMA:价在线上记 1,线下记 -1,相等记 0;三者加总 sum≥2 输出 BUY,≤-2 输出 SELL,其余情况走默认分支。 GetSMA 每次用 iMA 建句柄、CopyBuffer 取 1 根数据后立即 IndicatorRelease 释放,避免面板反复点击堆积句柄。外汇与贵金属杠杆高,这类多周期共振仅提示概率倾向,实盘前请在 MT5 策略测试器用至少 3 个月 Tick 数据回测验证信号分布。
class="type">class="kw">string res = AnalyzeTimeframes(PERIOD_M1, PERIOD_M5, PERIOD_M15); class="type">class="kw">string out = "Short Term Focus: " + res; UpdateResults(out); Alert(out); class="kw">return true; } else if(sparam == "btnScalping") { class="type">class="kw">string res = AnalyzeTimeframes(PERIOD_M5, PERIOD_M15, PERIOD_H1); class="type">class="kw">string out = "Scalping/Intraday: " + res; UpdateResults(out); Alert(out); class="kw">return true; } class=class="str">"cmt">// Additional conditions for other buttons class="kw">return false; } class="type">void UpdateResults(const class="type">class="kw">string &result) { m_lblResults.Text("Analysis Result: " + result); ChartRedraw(); } class="type">class="kw">double GetSMA(class="type">class="kw">string symbol, ENUM_TIMEFRAMES timeframe, class="type">int period, class="type">int shift) { class="type">int handle = iMA(symbol, timeframe, period, class="num">0, MODE_SMA, PRICE_CLOSE); if(handle == INVALID_HANDLE) { Print("Failed to create iMA handle for timeframe ", timeframe); class="kw">return class="num">0.0; } class="type">class="kw">double sma[]; if(CopyBuffer(handle, class="num">0, shift, class="num">1, sma) <= class="num">0) { Print("Failed to copy buffer for timeframe ", timeframe); IndicatorRelease(handle); class="kw">return class="num">0.0; } class="type">class="kw">double result = sma[class="num">0]; IndicatorRelease(handle); class="kw">return result; } class="type">class="kw">string AnalyzeTimeframes(ENUM_TIMEFRAMES tf1, ENUM_TIMEFRAMES tf2, ENUM_TIMEFRAMES tf3) { class="type">int period = class="num">50; class="type">int shift = class="num">1; class=class="str">"cmt">// last completed candle class="type">class="kw">double price1 = iClose(_Symbol, tf1, shift); class="type">class="kw">double sma1 = GetSMA(_Symbol, tf1, period, shift); class="type">int signal1 = (price1 > sma1) ? class="num">1 : (price1 < sma1 ? -class="num">1 : class="num">0); class="type">class="kw">double price2 = iClose(_Symbol, tf2, shift); class="type">class="kw">double sma2 = GetSMA(_Symbol, tf2, period, shift); class="type">int signal2 = (price2 > sma2) ? class="num">1 : (price2 < sma2 ? -class="num">1 : class="num">0); class="type">class="kw">double price3 = iClose(_Symbol, tf3, shift); class="type">class="kw">double sma3 = GetSMA(_Symbol, tf3, period, shift); class="type">int signal3 = (price3 > sma3) ? class="num">1 : (price3 < sma3 ? -class="num">1 : class="num">0); class="type">int sum = signal1 + signal2 + signal3; if(sum >= class="num">2) class="kw">return "BUY"; else if(sum <= -class="num">2) class="kw">return "SELL"; else