价格行为分析工具箱开发(第三部分):分析大师 EA·进阶篇
🤖

价格行为分析工具箱开发(第三部分):分析大师 EA·进阶篇

(2/3)·脚本只能写一次指标的痛点,用EA每两小时刷新的架构彻底绕开

含代码示例 第 2/3 篇
很多交易者装了分析脚本就以为盘面诊断完事了,其实脚本跑完即终止,两小时后市场结构早变了它还停在旧数据。把一次性脚本升级成常驻EA,指标才会跟着Tick持续刷新,你看到的支撑阻力才是活的。

◍ 隔两小时刷新前日轨道与账户快照

在 MT5 EA 里,用 TimeCurrent() 减去上次更新时间判断是否跨过 2*3600 秒(即 2 小时),是低频刷新前日数据的常见闸门。跨过阈值才重抓前一日 OHLCV,能避免每 tick 都调 iTime/iOpen 这类历史函数拖慢回测与实盘。 下面这段逻辑直接取 PERIOD_D1 的 1 号柱(昨日的完整日线):开盘、收盘、最高、最低、成交量一并拿到,同时取 0 号柱的当日成交量。支撑与阻力用昨日波幅乘 0.382 向外推——支撑在昨低下方、阻力在昨高上方,属于简化版斐波那契外延。 账户端用 AccountInfoDouble 抓余额与净值,点差则手动用 SYMBOL_ASK 减 SYMBOL_BID 算,不依赖内置点差字段。最小最大手数从 SYMBOL_VOLUME_MIN/MAX 读,仓位计算函数再拿支撑阻力去估手数。 波动端走 iATR 拿 H1 周期句柄,CopyBuffer 取最新一根 ATR 值进 atrBuffer[0];句柄无效就跳过。外汇与贵金属杠杆高,ATR 只是波动刻度,实际止损间距仍要按账户承受度压。

MQL5 / C++
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);
   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
        }

「把前日数据打包成盘面浮层」

这段逻辑干的事很直接:在每次刷新周期里,把前日 OHLCV、当日成交量、账户权益、点差和 ATR 波动率拼成一条多行字符串,再用 Print 写进日志、用 Comment 钉在图表左上角。实盘里你开 MT5 加载这类 EA,就能在图表上实时看到「Prev Day High / Low」和「Possible Lot Size」这类字段,不用切窗口翻终端。 代码先释放 atrHandle 避免句柄泄漏,再用 TimeToString(TimeCurrent(), TIME_DATE|TIME_MINUTES) 拿到「年月日 时分」格式的刷新时间,拼进输出串末尾。随后 ObjectsDeleteAll(0) 清掉旧对象,调用 DrawContinuousTrendLine 重画前日高、低两条延伸线——这是价格行为者盯突破最常用的参照线。 方向判断单独拆成 AnalyzeMarketDirection:close>open 归 Bullish,close<open 归 Bearish,持平则 Neutral。注意它只看收官价与开盘价,不含影线,所以震荡日容易判 Neutral,你拿去辅助判势时得结合支撑阻力一起看。外汇与贵金属杠杆高,这类信号只作概率参考,别当成单边依据。

MQL5 / C++
IndicatorRelease(atrHandle); class=class="str">"cmt">// Release the indicator handle
}
class=class="str">"cmt">// Create the output class="type">class="kw">string including the last update time
class="type">class="kw">string lastUpdateStr = TimeToString(TimeCurrent(), TIME_DATE | TIME_MINUTES);
class="type">class="kw">string infoStr = StringFormat("Prev 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",
                              previousDayOpen, previousDayClose, previousDayHigh, previousDayLow,
                              previousDayVolume, currentDayVolume, marketDirection,
                              support, resistance, accountBalance, accountEquity, marketSpread,
                              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">// 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";
     }

把影线压力、左延长线与仓位算在一起

在方向判断里补一句上下文,比单纯输出「涨/跌」更有用。若某根 K 线的高点同时高于开盘与收盘,就给方向字符串追加 bullish pressure;若低点同时低于开盘与收盘,则追加 bearish pressure。这样后续日志或面板能直接看到「当前倾向伴随买压还是卖压」。 画左侧无限延长的趋势线,用 OBJ_TREND 配合 OBJPROP_XSIZE=0 即可。代码里把起点推到 TimeCurrent()-720*3600,也就是回看 30 天(720 小时)的位置,终点落在当前时间同一价格,视觉上是一条水平参考带;名字里带 High 染红、否则染蓝,线宽设 2。

仓位计算走风险反推:止损距离 =支撑-阻力× StopLossMultiplier,风险金额 = 账户余额 × RiskPercentage/100。手数 = 风险金额 ÷ (止损距离/点值),再用 NormalizeDouble 修到两位小数,并和 SYMBOL_VOLUME_MIN 比较兜底。外汇与贵金属杠杆高,止损距离设错会直接把 lotSize 放大到不可承受,建议先打印 stopLossDistance 与 lotSize 到 Experts 日志核对。
MQL5 / C++
  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
   ObjectSetInteger(class="num">0, name, OBJPROP_XSIZE, class="num">0); class=class="str">"cmt">// Set this class="kw">property to extend the line infinitely to the left
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Calculate the lot size based on risk management                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
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=class="str">"cmt">// Get the tick size for the current symbol
   class="type">class="kw">double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
class=class="str">"cmt">// Calculate the lot size based on the stop loss and tick size
   class="type">class="kw">double lotSize = riskAmount / (stopLossDistance / tickSize); class=class="str">"cmt">// Adjusted for the correct pip size
   lotSize = NormalizeDouble(lotSize, class="num">2); class=class="str">"cmt">// Normalize the lot size to two decimal places
class=class="str">"cmt">// Ensure lot size is above minimum lot size allowed by broker
   class="type">class="kw">double minLotSize = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
   if(lotSize < minLotSize)
      lotSize = minLotSize;
   class="kw">return lotSize;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+

◍ EA 指标采集与绘图的逻辑骨架

这套 EA 把可配置项放在 input 区:风险百分比默认 1.0、止损乘数 1.0、ATR 周期 14,都是直接改文件头就能调的硬参数。全局变量存了前一日 OHLC、成交量以及算出来的支撑阻力,OnInit 只做一件事——把 lastUpdateTime 清零,OnDeinit 则调 ObjectsDeleteAll(0) 把图表上画过的线全清掉,避免换周期时残留对象。 OnTick 本身极薄,每次报价只调 UpdateMetrics()。真正干活的地方限制为「距上次更新满 2 小时(2*3600 秒)才执行」,用 TimeCurrent()-lastUpdateTime 判断,这在盯欧美盘时能把不必要的计算压下去,MT5 终端负载倾向更平稳。 数据抓取全用 iTime/iOpen/iClose/iHigh/iLow/iVolume,前一日取 shift=1,当日成交量取 shift=0。支撑阻力不是用枢轴点,而是以前日振幅的 0.382 向外扩:support = 前低 - 振幅*0.382,resistance = 前高 + 振幅*0.382,属于斐波那契外延的变体。 市场方向交给 AnalyzeMarketDirection,吃前日开收高低四个价,返回看涨/看跌/中性;ATR 用 14 周期衡量波动,账户余额净值点差也一并取了辅助决策。最后 infoStr 拼好指标经 Print 丢进日志,旧趋势线删掉后按前高红线、前低蓝线重画并回推 24 小时。手数由 CalculateLotSize 按风险百分比和止损距离算,并夹在经纪商允许范围内——外汇和贵金属杠杆高,实盘前务必在策略测试器用历史数据验一遍手数边界。

MQL5 / C++
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">// 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="type">int OnInit()
{
    lastUpdateTime = class="num">0; class=class="str">"cmt">// Set the initial update time
    class="kw">return INIT_SUCCEEDED;
}
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="type">void OnTick()
{
    UpdateMetrics(); class=class="str">"cmt">// Call to the function that fetches and displays the metrics
}
class="type">void UpdateMetrics()
{
    if (TimeCurrent() - lastUpdateTime >= class="num">2 * class="num">3600) class=class="str">"cmt">// Check if class="num">2 hours have passed since last update
    {
        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;
        resistance = previousDayHigh + (previousDayHigh - previousDayLow) * class="num">0.382;
        
        class=class="str">"cmt">// Determine market direction
        class="type">class="kw">string marketDirection = AnalyzeMarketDirection(previousDayOpen, previousDayClose, previousDayHigh, previousDayLow);

「把账户与波动数据塞进面板输出」

在支撑阻力算完之后,这段逻辑负责把下单前该看的风控变量一次性抓出来。先调 AccountInfoDouble 拿余额和净值,再用 SymbolInfoDouble 取当前品种的买卖价,两者相减就是实时点差——外汇和贵金属点差随流动性跳动,高频时段可能从 1.2 点扩到 4 点以上,属于高风险品种的典型特征。 波动端直接用 iATR 挂一个 H1 周期的 ATR 句柄,CopyBuffer 取最新一根的数值后马上 IndicatorRelease 释放,避免句柄泄漏。ATR_Period 若设 14,欧美对常见值在 0.0008~0.0015 之间,黄金往往破 10 美元,参数不动就先跑一遍看自己品种的量级。 最后用 StringFormat 把前日 OHLCV、方向、支撑阻力、账户数据、点差、ATR 和算出的 Possible Lot Size 拼成多行字符串,TimeToString(TimeCurrent()) 打上更新时间。你把这串贴进 Comment() 或自建面板,盯盘时不用切窗口就能核对仓位是否超风控。

MQL5 / C++
  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);
  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 market volatility using ATR
  class="type">int atrHandle = iATR(NULL, PERIOD_H1, ATR_Period);
  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[];
    if (CopyBuffer(atrHandle, class="num">0, class="num">0, class="num">1, atrBuffer) > class="num">0)
    {
      atrValue = atrBuffer[class="num">0];
    }
    IndicatorRelease(atrHandle); class=class="str">"cmt">// Release the indicator handle
  }
  
  class=class="str">"cmt">// Create the output class="type">class="kw">string including the last update time
  class="type">class="kw">string lastUpdateStr = TimeToString(TimeCurrent(), TIME_DATE | TIME_MINUTES);
  class="type">class="kw">string infoStr = StringFormat("Prev 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",
                                                    previousDayOpen, previousDayClose, previousDayHigh, previousDayLow,
                                                    previousDayVolume, currentDayVolume, marketDirection,
交给小布盯盘看盘口
这些诊断小布盯盘的AIGC已内置,打开对应品种页即可看到类似的持续更新指标,把重复劳动交给小布,你专注决策。

常见问题

脚本执行完即退出无法持续更新,EA通过OnTimer每两小时重算前日开盘收盘成交量及支撑阻力,盘面信息保持实时。
可以,把脚本中计算价位和预测的函数移植进EA的自定义模块,配合事件处理函数就能保留原功能并增加常驻能力。
管理多品种或复杂算法时CPU内存占用会高于脚本,但两小时级刷新频率对多数单机环境压力可控。
能,小布盯盘品种页内置了类EA的滚动指标更新,不需自己写MQL5也可获得同等持续视野。
自动化诊断仅辅助判断,外汇贵金属杠杆高、跳空频繁,信号倾向参考而非执行依据,实盘前需回测。