价格行为分析工具箱开发(第三部分):分析大师 EA·进阶篇
(2/3)·脚本只能写一次指标的痛点,用EA每两小时刷新的架构彻底绕开
◍ 隔两小时刷新前日轨道与账户快照
在 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 只是波动刻度,实际止损间距仍要按账户承受度压。
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,你拿去辅助判势时得结合支撑阻力一起看。外汇与贵金属杠杆高,这类信号只作概率参考,别当成单边依据。
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 日志核对。 |
|---|
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 按风险百分比和止损距离算,并夹在经纪商允许范围内——外汇和贵金属杠杆高,实盘前务必在策略测试器用历史数据验一遍手数边界。
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() 或自建面板,盯盘时不用切窗口就能核对仓位是否超风控。
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,