MQL5 简介(第 10 部分):MQL5 中使用内置指标的初学者指南·综合运用
(3/3)· 新手也能照做的 RSI EA 全流程,句柄、缓冲区与百分比风控一次打通
◍ 用趋势线把高低点钉在图上
这段初始化与绘图逻辑,核心是把一段统计窗口内的最高价和最低价,用两条 OBJ_TREND 线固化到主图,方便肉眼对照价格行为。绿线标高位、红线标低位,线宽都设成 3,避免在 MT5 缩放后看不清。 ObjectCreate 里用 TimeCurrent() 作为终点时间,意味着线会一直延伸到当前时刻;起点则取自静态变量 min_time1_static / min_time2_static,配合 max_high_static / min_low_static 锁死极值。实盘里若这些静态值来自前 100 根 K 线扫描,那这条通道就代表了近期波动边界。 OnInit 中 rsi_handle = iRSI(_Symbol, PERIOD_CURRENT, 14, PRICE_CLOSE) 把 RSI 周期钉在 14,ArraySetAsSeries 全部置 true,使 buffer[0] 永远是最新一根。OnTick 里 CopyBuffer(rsi_handle,0,1,100,rsi_buffer) 从偏移 1 开始抓 100 个值,避开当前未闭合的 bar,回测时更能反映信号触发前的状态。 外汇与贵金属杠杆高、滑点随机,这类画线只是辅助参考,极值突破后价格可能惯性延伸也可能假突破,需结合别的条件过滤。
ObjectCreate(ChartID(),high_obj_name,OBJ_TREND,class="num">0,min_time1_static,max_high_static,TimeCurrent(),max_high_static); ObjectSetInteger(chart_id,high_obj_name,OBJPROP_COLOR,clrGreen); ObjectSetInteger(chart_id,high_obj_name,OBJPROP_WIDTH,class="num">3); ObjectCreate(ChartID(),low_obj_name,OBJ_TREND,class="num">0,min_time2_static,min_low_static,TimeCurrent(),min_low_static); ObjectSetInteger(chart_id,low_obj_name,OBJPROP_COLOR,clrRed); ObjectSetInteger(chart_id,low_obj_name,OBJPROP_WIDTH,class="num">3); } class=class="str">"cmt">// Magic number input class="type">int MagicNumber = class="num">1111; class="type">int rsi_handle; class="type">class="kw">double rsi_buffer[]; 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">datetime time[]; class="type">class="kw">double max_high = class="num">0; class="type">class="kw">datetime min_time1 = class="num">0; class="type">class="kw">double min_low = class="num">0; class="type">class="kw">datetime min_time2 = class="num">0; class="type">class="kw">datetime time_low = class="num">0; class="type">class="kw">datetime times_high = class="num">0; class="type">class="kw">string high_obj_name = "High_Line"; class="type">class="kw">string low_obj_name = "Low_Line"; class="type">long chart_id; class="type">class="kw">double take_profit; class="type">class="kw">double ask_price = class="num">0; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">// Configure RSI buffer as a series for easier indexing ArraySetAsSeries(rsi_buffer, true); class=class="str">"cmt">// Initialize RSI handle for the current symbol, timeframe, and parameters rsi_handle = iRSI(_Symbol, PERIOD_CURRENT, class="num">14, PRICE_CLOSE); class=class="str">"cmt">// Configure candlestick arrays as series ArraySetAsSeries(open, true); ArraySetAsSeries(close, true); ArraySetAsSeries(high, true); ArraySetAsSeries(low, true); ArraySetAsSeries(time, true); class=class="str">"cmt">// Set the magic number trade.SetExpertMagicNumber(MagicNumber); class="kw">return (INIT_SUCCEEDED); class=class="str">"cmt">// Indicate successful initialization } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert tick function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { class=class="str">"cmt">// Copy RSI values from the indicator into the buffer CopyBuffer(rsi_handle, class="num">0, class="num">1, class="num">100, rsi_buffer); class=class="str">"cmt">// Copy candlestick data CopyOpen(_Symbol, PERIOD_CURRENT, class="num">1, class="num">100, open); CopyClose(_Symbol, PERIOD_CURRENT, class="num">1, class="num">100, close);
「用刺穿反转结构挂首仓」
这段逻辑先把最近 100 根 K 线的高、低、时间数组拉进内存,并用 static 变量锁住前期算好的极值与对应时间,避免在每帧 OnTick 里重复计算。接着扫一遍持仓池,只统计当前图表品种、且 MagicNumber 匹配的头寸,没持仓时才允许开新单。 开多条件盯的是「向下刺穿前低后收回」:最近 0~2 根里任一根最低价跌破 min_low_static,但最新收盘不仅拉回该位之上,且收阳(close>open)。满足后,止损放在 low[0],止盈按 (收盘-最低)*3+收盘 推算,用 trade.Buy(0.5,...) 下 0.5 手。 对称地,空单看「向上刺穿前高后回落」:最高价越过 max_high_static 后收盘跌回其下并收阴,同样允许 0~2 根内完成刺穿。外汇与贵金属杠杆高,刺穿失败直接走反向的概率不低,参数 0.5 手与 3 倍盈利距仅作代码样例,实盘须按账户净值重算。 把下面代码贴进 MT5 脚本,把 min_low_static / max_high_static 的赋值补上,就能在 EURUSD 的 M15 上肉眼验证信号触发频率。
CopyHigh(_Symbol, PERIOD_CURRENT, class="num">1, class="num">100, high); CopyLow(_Symbol, PERIOD_CURRENT, class="num">1, class="num">100, low); CopyTime(_Symbol, PERIOD_CURRENT, class="num">1, class="num">100, time); class="kw">static class="type">class="kw">double max_high_static = max_high; class="kw">static class="type">class="kw">datetime min_time1_static = min_time1; class="kw">static class="type">class="kw">double min_low_static = min_low; class="kw">static class="type">class="kw">datetime min_time2_static = min_time2; class=class="str">"cmt">//GETTING TOTAL POSITIONS class="type">int totalPositions = class="num">0; for(class="type">int i = class="num">0; i < PositionsTotal(); i++) { class="type">ulong ticket = PositionGetTicket(i); if(PositionSelectByTicket(ticket)) { if(PositionGetInteger(POSITION_MAGIC) == MagicNumber && PositionGetString(POSITION_SYMBOL) == ChartSymbol(chart_id)) { totalPositions++; } } } ask_price = SymbolInfoDouble(_Symbol,SYMBOL_ASK); if(totalPositions < class="num">1) { if(((low[class="num">0] < min_low_static && close[class="num">0] > min_low_static && close[class="num">0] > open[class="num">0]) || (low[class="num">1] < min_low_static && close[class="num">0] > min_low_static && close[class="num">0] > open[class="num">0]) || (low[class="num">2] < min_low_static && close[class="num">0] > min_low_static && close[class="num">0] > open[class="num">0] && close[class="num">1] < open[class="num">1]))) { take_profit = (close[class="num">0] - low[class="num">0]) * class="num">3 + close[class="num">0]; trade.Buy(class="num">0.5,_Symbol,ask_price, low[class="num">0], take_profit); } else if(((high[class="num">0] > max_high_static && close[class="num">0] < max_high_static && close[class="num">0] < open[class="num">0]) || (high[class="num">1] > max_high_static && close[class="num">0] < max_high_static && close[class="num">0] < open[class="num">0]) || (high[class="num">2] > max_high_static && close[class="num">0] < max_high_static && close[class="num">0] < open[class="num">0] && close[class="num">1] > open[class="num">1]))) {
用最近12根K线与RSI锁定高低点锚
这段逻辑在每根新 tick 进来后重算图表 ID,并扫描最近 12 根 K 线,试图抓住一段未被破坏的高低点结构。它不依赖指标重绘,而是直接读 close/open/high/low 数组与 RSI 缓冲,因此你在 MT5 里改 PERIOD_CURRENT 为具体周期,行为会立刻变化。 先看多头锚:循环里只要出现 close[i]<open[i] 且下一根 close[i+1]>open[i+1],就取这两根的高点最大值作为 max_high,并取两根时间的最小值存为 min_time1,随后 break。也就是说,它只认最近一次「阴转阳」作为潜在底部确认,超过 12 根没出现就放弃。 空头锚对称处理:close[i]>open[i] 且下一根反包收阴,取两根低点最小值 min_low。RSI 部分另开两个循环,rsi_buffer[i+1]<30 且当前值大于前值时记 time_low;rsi_buffer[i+1]>70 且当前值小于前值时记 times_high——这是把超买超卖当作结构失效的辅助判据。 最后的静态变量刷新规则很硬:total_bar_high 为 0 或大于 12,且 min_time1 等于 times_high,才把 max_high 写进 max_high_static;若 min_time1 与 times_high 不一致且 total_bar_high>13,则直接清零。外汇与贵金属波动下这种阈值(12/13/30/70)可能频繁触发重置,实盘前建议用策略测试器跑至少 3 个月 tick 数据验证重置频率。
take_profit = MathAbs((high[class="num">0] - close[class="num">0]) * class="num">3 - close[class="num">0]); class=class="str">"cmt">// Adjusted take-profit calculation trade.Sell(class="num">0.5,_Symbol,ask_price, high[class="num">0], take_profit); } } class=class="str">"cmt">//CHART ID chart_id = ChartID(); class="type">int total_bar_high = Bars(_Symbol,PERIOD_CURRENT,min_time1_static,TimeCurrent()); for(class="type">int i = class="num">0; i < class="num">12; i++) { if(close[i] < open[i] && close[i+class="num">1] > open[i+class="num">1]) { max_high = (class="type">class="kw">double)MathMax(high[i],high[i+class="num">1]); min_time1 = (class="type">class="kw">datetime)MathMin(time[i],time[i+class="num">1]); class="kw">break; } } class="type">int total_bar_low = Bars(_Symbol,PERIOD_CURRENT,min_time2_static,TimeCurrent()); for(class="type">int i = class="num">0; i < class="num">12; i++) { if(close[i] > open[i] && close[i+class="num">1] < open[i+class="num">1]) { min_low = (class="type">class="kw">double)MathMin(low[i],low[i+class="num">1]); min_time2 = (class="type">class="kw">datetime)MathMin(time[i],time[i+class="num">1]); class="kw">break; } } for(class="type">int i = class="num">0; i < class="num">12; i++) { if(rsi_buffer[i+class="num">1] < class="num">30 && rsi_buffer[i] > rsi_buffer[i+class="num">1]) { time_low = time[i+class="num">1]; class="kw">break; } } for(class="type">int i = class="num">0; i < class="num">12; i++) { if(rsi_buffer[i+class="num">1] > class="num">70 && rsi_buffer[i] < rsi_buffer[i+class="num">1]) { times_high = time[i+class="num">1]; class="kw">break; } } if((total_bar_high == class="num">0 || total_bar_high > class="num">12) && (min_time1 == times_high)) { max_high_static = max_high; min_time1_static = min_time1; } else if(min_time1 != times_high && total_bar_high > class="num">13) { max_high_static = class="num">0; min_time1_static = class="num">0; } if((total_bar_low == class="num">0 || total_bar_low > class="num">12) && (min_time2 == time_low)) { min_low_static = min_low; min_time2_static = min_time2; } else if(min_time2 != time_low && total_bar_low > class="num">13)
◍ 把高低点画成线并接上风控参数
这段逻辑先把统计到的最低点变量清零,再分别用 OBJ_TREND 把波段高点和低点拉成水平参考线:高线取 max_high_static 挂绿色、低线取 min_low_static 挂红色,线宽都设 3,右端延伸到 TimeCurrent(),也就是实时跟随当前K线。 代码里 input 块直接暴露了仓位计算的源头:account_balance 默认 1000,percentage_risk 默认 2.0,rrr 默认 3。也就是说单笔最大可接受亏损额 = 1000 × 2% = 20 单位,若打满风险回报比,理论盈利目标约为 60 单位——当然外汇和贵金属杠杆高,实际滑点扩大时亏损可能超过该预设值。 下面的全局变量里 lot_size、risk_Amount、points_risk 都还没赋值,说明真正的手数要等后续用 (account_balance × percentage_risk / 100) 除以每点风险来倒推;allow_modify 默认 false 代表保本挪损先不开启。 开 MT5 把这段粘进 EA,先改 account_balance 和 percentage_risk 匹配你自己账户,看水平线颜色和宽度是否符合盯盘习惯,再决定要不要动 rrr。
{
min_low_static = class="num">0;
min_time2_static = class="num">0;
}
ObjectCreate(ChartID(),high_obj_name,OBJ_TREND,class="num">0,min_time1_static,max_high_static,TimeCurrent(),max_high_static);
ObjectSetInteger(chart_id,high_obj_name,OBJPROP_COLOR,clrGreen);
ObjectSetInteger(chart_id,high_obj_name,OBJPROP_WIDTH,class="num">3);
ObjectCreate(ChartID(),low_obj_name,OBJ_TREND,class="num">0,min_time2_static,min_low_static,TimeCurrent(),min_low_static);
ObjectSetInteger(chart_id,low_obj_name,OBJPROP_COLOR,clrRed);
ObjectSetInteger(chart_id,low_obj_name,OBJPROP_WIDTH,class="num">3);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| MQL5INDICATORS_PROJECT4.mq5 |
class=class="str">"cmt">//| Copyright class="num">2024, MetaQuotes Ltd. |
class=class="str">"cmt">//| [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "ForexYMN"
class="macro">#class="kw">property link "crownsoyin@gmail.com"
class="macro">#class="kw">property version "class="num">1.00"
class="macro">#include <Trade/Trade.mqh>
CTrade trade;
class=class="str">"cmt">// Magic number
input class="type">int MagicNumber = class="num">1111;
input class="type">class="kw">double account_balance = class="num">1000; class=class="str">"cmt">// Account Balance
input class="type">class="kw">double percentage_risk = class="num">2.0; class=class="str">"cmt">// How many percent of the account do you want to risk per trade?
input class="type">bool allow_modify = false; class=class="str">"cmt">// Do you allow class="kw">break even modifications?
input class="type">int rrr = class="num">3; class=class="str">"cmt">// Choose Risk Reward Ratio
class="type">int rsi_handle;
class="type">class="kw">double rsi_buffer[];
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">datetime time[];
class="type">class="kw">double max_high = class="num">0;
class="type">class="kw">datetime min_time1 = class="num">0;
class="type">class="kw">double min_low = class="num">0;
class="type">class="kw">datetime min_time2 = class="num">0;
class="type">class="kw">datetime time_low = class="num">0;
class="type">class="kw">datetime times_high = class="num">0;
class="type">class="kw">string high_obj_name = "High_Line";
class="type">class="kw">string low_obj_name = "Low_Line";
class="type">long chart_id;
class="type">class="kw">double take_profit;
class="type">class="kw">double ask_price = class="num">0;
class="type">class="kw">double lot_size;
class="type">class="kw">double risk_Amount;
class="type">class="kw">double points_risk;
class=class="str">"cmt">// Risk modification
class="type">class="kw">double positionProfit = class="num">0;
class="type">class="kw">double positionopen = class="num">0;
class="type">class="kw">double positionTP = class="num">0;「EA初始化与逐根K线的数据搬运」
把 RSI 和裸 K 数据搬进缓冲区,是后续所有逻辑的前提。OnInit 里先把 rsi_buffer 和各 OHLC 数组设为序列排列(ArraySetAsSeries 置 true),这样下标 0 就是当前未完成或刚收盘的 K 线,索引直观。 RSI 句柄用 iRSI(_Symbol, PERIOD_CURRENT, 14, PRICE_CLOSE) 建,周期取当前图表、长度 14、收盘价计算——这是最通用的超买超卖参考窗。trade.SetExpertMagicNumber 绑好魔数,保证只动自己下的单。 OnTick 用静态变量 prevBars 做新旧 Bar 数比对,currBars 不变就直接 return,把计算压到每根新 K 线触发一次,避免同根内重复跑。随后 CopyBuffer(rsi_handle,0,1,100,rsi_buffer) 从指标句柄搬 100 根 RSI,CopyOpen/CopyClose/CopyHigh/CopyLow/CopyTime 同样从偏移 1 开始抓 100 根,留足回溯空间给波峰波谷判定。 几个 static 双精度与 datetime 变量(max_high_static、min_time1_static 等)在 tick 首部接住外部传入的极值与时间点,说明极值状态跨 tick 保留,不会每根 K 重算归零。外汇与贵金属杠杆高,这类 EA 实盘前务必在 MT5 策略测试器用至少 3 个月 tick 数据验证信号延迟。
class="type">class="kw">double positionSL = class="num">0; class="type">class="kw">double modifyLevel = class="num">0.0; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">// Configure RSI buffer as a series for easier indexing ArraySetAsSeries(rsi_buffer, true); class=class="str">"cmt">// Initialize RSI handle for the current symbol, timeframe, and parameters rsi_handle = iRSI(_Symbol, PERIOD_CURRENT, class="num">14, PRICE_CLOSE); class=class="str">"cmt">// Configure candlestick arrays as series ArraySetAsSeries(open, true); ArraySetAsSeries(close, true); ArraySetAsSeries(high, true); ArraySetAsSeries(low, true); ArraySetAsSeries(time, true); class=class="str">"cmt">// Set the magic number trade.SetExpertMagicNumber(MagicNumber); class="kw">return (INIT_SUCCEEDED); class=class="str">"cmt">// Indicate successful initialization } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert tick function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { class="type">int currBars = iBars(_Symbol,_Period); class="kw">static class="type">int prevBars = currBars; if(prevBars == currBars) class="kw">return; prevBars = currBars; class=class="str">"cmt">// Copy RSI values from the indicator into the buffer CopyBuffer(rsi_handle, class="num">0, class="num">1, class="num">100, rsi_buffer); class=class="str">"cmt">// Copy candlestick data CopyOpen(_Symbol, PERIOD_CURRENT, class="num">1, class="num">100, open); CopyClose(_Symbol, PERIOD_CURRENT, class="num">1, class="num">100, close); CopyHigh(_Symbol, PERIOD_CURRENT, class="num">1, class="num">100, high); CopyLow(_Symbol, PERIOD_CURRENT, class="num">1, class="num">100, low); CopyTime(_Symbol, PERIOD_CURRENT, class="num">1, class="num">100, time); class="kw">static class="type">class="kw">double max_high_static = max_high; class="kw">static class="type">class="kw">datetime min_time1_static = min_time1; class="kw">static class="type">class="kw">double min_low_static = min_low; class="kw">static class="type">class="kw">datetime min_time2_static = min_time2; class=class="str">"cmt">//GETTING TOTAL POSITIONS class="type">int totalPositions = class="num">0; for(class="type">int i = class="num">0; i < PositionsTotal(); i++) { class="type">ulong ticket = PositionGetTicket(i); if(PositionSelectByTicket(ticket)) { if(PositionGetInteger(POSITION_MAGIC) == MagicNumber && PositionGetString(POSITION_SYMBOL) == ChartSymbol(chart_id)) {
无持仓时的刺透与反转开仓逻辑
当账户里当前品种持仓数 totalPositions 小于 1,也就是完全空仓时,EA 才会考虑进场。这一步直接卡死了加仓和马丁类行为,外汇与贵金属波动剧烈,空仓触发能压住过度交易的概率。 多头触发看的是最近 0~2 根 K 线中,至少有一根最低价刺穿了静态最低参考 min_low_static,但最新收盘价又收回该线之上,且收阳(close[0] > open[0]);若刺透发生在前一根且前一根收阴,也纳入条件。满足后,以 (close[0]-low[0])*rrr+close[0] 算止盈,风险点数就是刺透深度,再按账户余额百分比算 lot。 空头对称:最高价刺过 max_high_static 后收回到线下方且收阴,最近三根内任一满足即触发。止盈用 MathAbs((high[0]-close[0])*rrr - close[0]),注意这里取了绝对值避免负价,risk 点数取刺透幅度。 开仓前用 SymbolInfoDouble(_Symbol, SYMBOL_ASK) 抓实时卖一价,Buy 挂 low[0] 止损、Sell 挂 high[0] 止损。下面这段是原文核心片段,逐行拆完你就能直接抄进 MT5 测。 totalPositions++ 是在前面遍历持仓时计数,这里判断 <1 才放行。 ask_price = SymbolInfoDouble(_Symbol, SYMBOL_ASK) 取当前品种卖价,用于市价买成交参考。 if(totalPositions < 1) 空仓闸门。 多头三重 OR:low[0]/low[1]/low[2] 任一小于 min_low_static 且 close[0] 收回线上且收阳,或 low[2] 刺透且前一根收阴,条件较宽。 take_profit = (close[0]-low[0])*rrr + close[0] 按风险回报比挂盈。 points_risk / minus 都是刺透深度,NormalizeDouble(...,5) 规范到 5 位小数。 lot_size = CalculateLotSize(...) 按风险金额反推手数。 trade.Buy(lot_size, _Symbol, ask_price, low[0], take_profit) 市价买、止损刺透低点、止盈按比例。 空头分支对称,take_profit 用 MathAbs 防负,trade.Sell 以 high[0] 为止损。 chart_id = ChartID() 抓图表句柄,total_bar_high 用 Bars 数出自 min_time1_static 到当前的 K 线根数,供后续绘制或统计。
totalPositions++;
}
}
}
ask_price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
if(totalPositions < class="num">1)
{
if(((low[class="num">0] < min_low_static && close[class="num">0] > min_low_static && close[class="num">0] > open[class="num">0]) || (low[class="num">1] < min_low_static && close[class="num">0] > min_low_static
&& close[class="num">0] > open[class="num">0]) || (low[class="num">2] < min_low_static && close[class="num">0] > min_low_static
&& close[class="num">0] > open[class="num">0] && close[class="num">1] < open[class="num">1])))
{
take_profit = (close[class="num">0] - low[class="num">0]) * rrr + close[class="num">0];
points_risk = close[class="num">0] - low[class="num">0];
class="type">class="kw">double riskAmount = account_balance * (percentage_risk / class="num">100.0);
class="type">class="kw">double minus = NormalizeDouble(close[class="num">0] - low[class="num">0],class="num">5);
lot_size = CalculateLotSize(_Symbol, riskAmount, minus);
trade.Buy(lot_size,_Symbol,ask_price, low[class="num">0], take_profit);
}
else
if(((high[class="num">0] > max_high_static && close[class="num">0] < max_high_static && close[class="num">0] < open[class="num">0]) || (high[class="num">1] > max_high_static && close[class="num">0] < max_high_static
&& close[class="num">0] < open[class="num">0]) || (high[class="num">2] > max_high_static && close[class="num">0] < max_high_static
&& close[class="num">0] < open[class="num">0] && close[class="num">1] > open[class="num">1])))
{
take_profit = MathAbs((high[class="num">0] - close[class="num">0]) * rrr - close[class="num">0]); class=class="str">"cmt">// Adjusted take-profit calculation
points_risk = MathAbs(high[class="num">0] - close[class="num">0]);
class="type">class="kw">double riskAmount = account_balance * (percentage_risk / class="num">100.0);
class="type">class="kw">double minus = NormalizeDouble(high[class="num">0] - close[class="num">0],class="num">5);
lot_size = CalculateLotSize(_Symbol, riskAmount, minus);
trade.Sell(lot_size,_Symbol,ask_price, high[class="num">0], take_profit);
}
}
class=class="str">"cmt">//CHART ID
chart_id = ChartID();
class="type">int total_bar_high = Bars(_Symbol, PERIOD_CURRENT, min_time1_static, TimeCurrent());◍ 用阴阳线配对锁定高低点锚位
这段逻辑在最近 12 根 BAR 里找「阴线接阳线」和「阳线接阴线」的相邻组合,把前者的两 K 线最高价取大值作为阻力锚 max_high,后者的两 K 线最低价取小值作为支撑锚 min_low。循环里一旦命中就 break,所以只认最近一次符合条件的配对,避免被更早的噪声干扰。 RSI 部分同样扫 12 根:当 rsi_buffer[i+1] 低于 30 且当前值高于前一根,记 time_low;高于 70 且当前值低于前一根,记 times_high。这两个条件把超买卖区里的动量拐点也纳入锚位校验。 后面的 if 判断用 total_bar_high / total_bar_low 控制失效:若统计 BAR 数大于 12 且锚时间不等于 RSI 拐点时间、或大于 13,就把 static 变量清零,相当于撤掉旧趋势线。最后 ObjectCreate 画一条从 min_time1_static 到当前的绿色 OBJ_TREND,宽度 3,肉眼能直接看到阻力延长线。 开 MT5 把这段塞进 EA 的 OnCalculate,调一下 12 这个窗口和 RSI 的 30/70 阈值,黄金 15M 上大概率能画出比裸眼更准的短期通道。外汇和贵金属杠杆高,锚位被扫后 static 清零机制只是降低假信号,不等于不会连续止损。
for(class="type">int i = class="num">0; i < class="num">12; i++) { if(close[i] < open[i] && close[i+class="num">1] > open[i+class="num">1]) { max_high = (class="type">class="kw">double)MathMax(high[i],high[i+class="num">1]); min_time1 = (class="type">class="kw">datetime)MathMin(time[i],time[i+class="num">1]); class="kw">break; } } class="type">int total_bar_low = Bars(_Symbol,PERIOD_CURRENT,min_time2_static,TimeCurrent()); for(class="type">int i = class="num">0; i < class="num">12; i++) { if(close[i] > open[i] && close[i+class="num">1] < open[i+class="num">1]) { min_low = (class="type">class="kw">double)MathMin(low[i],low[i+class="num">1]); min_time2 = (class="type">class="kw">datetime)MathMin(time[i],time[i+class="num">1]); class="kw">break; } } for(class="type">int i = class="num">0; i < class="num">12; i++) { if(rsi_buffer[i+class="num">1] < class="num">30 && rsi_buffer[i] > rsi_buffer[i+class="num">1]) { time_low = time[i+class="num">1]; class="kw">break; } } for(class="type">int i = class="num">0; i < class="num">12; i++) { if(rsi_buffer[i+class="num">1] > class="num">70 && rsi_buffer[i] < rsi_buffer[i+class="num">1]) { times_high = time[i+class="num">1]; class="kw">break; } } if((total_bar_high == class="num">0 || total_bar_high > class="num">12) && (min_time1 == times_high)) { max_high_static = max_high; min_time1_static = min_time1; } else if(min_time1 != times_high && total_bar_high > class="num">13) { max_high_static = class="num">0; min_time1_static = class="num">0; } if((total_bar_low == class="num">0 || total_bar_low > class="num">12) && (min_time2 == time_low)) { min_low_static = min_low; min_time2_static = min_time2; } else if(min_time2 != time_low && total_bar_low > class="num">13) { min_low_static = class="num">0; min_time2_static = class="num">0; } ObjectCreate(ChartID(),high_obj_name,OBJ_TREND,class="num">0,min_time1_static,max_high_static,TimeCurrent(),max_high_static); ObjectSetInteger(chart_id,high_obj_name,OBJPROP_COLOR,clrGreen); ObjectSetInteger(chart_id,high_obj_name,OBJPROP_WIDTH,class="num">3);
「用价格线把止损拖成保本」
先画一条红色趋势线把阶段低点钉死:ObjectCreate 用 ChartID 建 OBJ_TREND,起点取 min_time2_static 与 min_low_static,终点拉到 TimeCurrent() 且价格仍锁在 min_low_static,宽度设 3 方便肉眼盯。这条线不是装饰,是后面改仓的参照边界。 allow_modify 开关打开后,脚本会扫一遍 PositionsTotal() 下的所有持仓。通过 PositionGetTicket 拿 ticket 再 PositionSelectByTicket 选中,把开仓价、TP、SL、浮盈全部读进变量;只处理 MagicNumber 与 ChartSymbol 都匹配的单子,避免动到手动单或其他 EA 的仓位。
| 卖单的逻辑是:modifyLevel = | (SL-开仓价)-开仓价 | 归一化到 4 位,当 ask_price <= modifyLevel 时,把止损直接推到 positionopen 实现保本,TP 不变。买单对称处理,modifyLevel = | (开仓价-SL)+开仓价 | ,ask_price >= 该值才改。外汇与贵金属杠杆高,这种自动保本在跳空时可能不成交,实盘前请在 MT5 策略测试器用历史数据跑一遍确认触发频率。 |
|---|
下方 CalculateLotSize 的开头只取到 SYMBOL_POINT,完整手数公式要看后续片段;但已知 point 是算每 pip 价值的基础,stopLossPips 传进来后大概率会拿 point * stopLossPips 去反推 lot。先把这个 point 读取留好,接手写不会断。
ObjectCreate(ChartID(),low_obj_name,OBJ_TREND,class="num">0,min_time2_static,min_low_static,TimeCurrent(),min_low_static); ObjectSetInteger(chart_id,low_obj_name,OBJPROP_COLOR,clrRed); ObjectSetInteger(chart_id,low_obj_name,OBJPROP_WIDTH,class="num">3); if(allow_modify) { for(class="type">int i = class="num">0; i < PositionsTotal(); i++) { class="type">ulong ticket = PositionGetTicket(i); if(PositionSelectByTicket(ticket)) { positionopen = PositionGetDouble(POSITION_PRICE_OPEN); positionTP = PositionGetDouble(POSITION_TP); positionSL = PositionGetDouble(POSITION_SL); positionProfit = PositionGetDouble(POSITION_PROFIT); if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL && PositionGetInteger(POSITION_MAGIC) == MagicNumber && PositionGetString(POSITION_SYMBOL) == ChartSymbol(chart_id)) { modifyLevel = MathAbs(NormalizeDouble((positionSL - positionopen) - positionopen,class="num">4)); if(ask_price <= modifyLevel) { trade.PositionModify(ticket, positionopen, positionTP); } } if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY && PositionGetInteger(POSITION_MAGIC) == MagicNumber && PositionGetString(POSITION_SYMBOL) == ChartSymbol(chart_id)) { modifyLevel = MathAbs(NormalizeDouble((positionopen - positionSL) + positionopen,class="num">4)); if(ask_price >= modifyLevel) { trade.PositionModify(ticket, positionopen, positionTP); } } } } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Function to calculate the lot size based on risk amount and stop loss class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double CalculateLotSize(class="type">class="kw">string symbol, class="type">class="kw">double riskAmount, class="type">class="kw">double stopLossPips) { class=class="str">"cmt">// Get symbol information class="type">class="kw">double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
按风险敞口反推开仓手数
在 MT5 里做仓位管理,核心不是拍脑袋定手数,而是先锁定这笔单子最多亏多少美元,再反推能开多少 lot。下面这段逻辑把账户余额、风险百分比和止损距离串起来,直接算出合规手数。 double tickValue = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE); // 取当前品种每 tick 对应的账户币种价值 double pipValuePerLot = tickValue / point; // 用 tick 价值除以 point 得到每 lot 每最小价位的价值 double stopLossValue = stopLossPips * pipValuePerLot; // 止损点数乘每 lot 价值 = 单 lot 止损金额 double lotSize = riskAmount / stopLossValue; // 用总风险金额除以单 lot 止损,得理论手数 double lotStep = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP); // 取经纪商规定的手数步长 double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN); double maxLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX); // 读取最小最大允许手数 lotSize = MathFloor(lotSize / lotStep) * lotStep; // 向下取整到步长倍数,避免报手数非法 if(lotSize < minLot) lotSize = minLot; if(lotSize > maxLot) lotSize = maxLot; // 越界则夹到边界 实盘里若账户 1000 美元、单笔风险 2%,理论风险金额就是 20 美元;若止损 50 点、EURUSD 每 lot 每点约 10 美元,则算出来 lotSize 倾向落在 0.04 附近,再被步长夹到 0.04。外汇和贵金属杠杆高,滑点可能让实际亏损偏离计算值,开仓前最好在策略测试器跑一遍。 输入参数这块也直接决定了风险骨架:account_balance=1000、percentage_risk=2.0、rrr=3 意味着盈亏比锁 3 倍,take_profit 用 (close[0]-low[0])*3+close[0] 推出来。allow_modify 打开后,脚本会扫持仓把空单止损搬到开仓价做保本,但仅当 ask 触到 modifyLevel 才改。
class="type">class="kw">double tickValue = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE); class=class="str">"cmt">// Calculate pip value per lot class="type">class="kw">double pipValuePerLot = tickValue / point; class=class="str">"cmt">// Calculate the stop loss value in currency class="type">class="kw">double stopLossValue = stopLossPips * pipValuePerLot; class=class="str">"cmt">// Calculate the lot size class="type">class="kw">double lotSize = riskAmount / stopLossValue; class=class="str">"cmt">// Round the lot size to the nearest acceptable lot step class="type">class="kw">double lotStep = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP); lotSize = MathFloor(lotSize / lotStep) * lotStep; class=class="str">"cmt">// Ensure the lot size is within the allowed range class="type">class="kw">double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN); class="type">class="kw">double maxLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX); if(lotSize < minLot) lotSize = minLot; if(lotSize > maxLot) lotSize = maxLot; class="kw">return lotSize; } input class="type">class="kw">double account_balance = class="num">1000; class=class="str">"cmt">// Account Balance input class="type">class="kw">double percentage_risk = class="num">2.0; class=class="str">"cmt">// How many percent of the account do you want to risk per trade? input class="type">bool allow_modify = false; class=class="str">"cmt">// Do you allow class="kw">break even modifications? input class="type">int rrr = class="num">3; class=class="str">"cmt">// Choose Risk Reward Ratio take_profit = (close[class="num">0] - low[class="num">0]) * rrr + close[class="num">0]; points_risk = close[class="num">0] - low[class="num">0]; class="type">class="kw">double riskAmount = account_balance * (percentage_risk / class="num">100.0); class="type">class="kw">double minus = NormalizeDouble(close[class="num">0] - low[class="num">0],class="num">5); lot_size = CalculateLotSize(_Symbol, riskAmount, minus); trade.Buy(lot_size,_Symbol,ask_price, low[class="num">0], take_profit); if(allow_modify) { for(class="type">int i = class="num">0; i < PositionsTotal(); i++) { class="type">ulong ticket = PositionGetTicket(i); if(PositionSelectByTicket(ticket)) { positionopen = PositionGetDouble(POSITION_PRICE_OPEN); positionTP = PositionGetDouble(POSITION_TP); positionSL = PositionGetDouble(POSITION_SL); positionProfit = PositionGetDouble(POSITION_PROFIT); if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL && PositionGetInteger(POSITION_MAGIC) == MagicNumber && PositionGetString(POSITION_SYMBOL) == ChartSymbol(chart_id)) { modifyLevel = MathAbs(NormalizeDouble((positionSL - positionopen) - positionopen,class="num">4)); if(ask_price <= modifyLevel) { trade.PositionModify(ticket, positionopen, positionTP); } } } } }
◍ 多单保本移损的触发写法
在 MT5 的持仓遍历逻辑里,想只对特定 EA 开的某品种多单做保本处理,得先三重过滤:持仓类型、魔术码、图表品种三者同时命中才动手。上面这段就是典型判断,少一个条件都可能误改别的策略仓位。 核心计算行把「开仓价与止损的距离」叠回开仓价,得到一条保本线 modifyLevel,并用 NormalizeDouble 锁到 4 位小数。当实时 ask 大于等于这条线,就调用 PositionModify 把止损推到开仓价、止盈不变。 直接把 MagicNumber 和 chart_id 换成你实盘的值,开 MT5 策略测试器用 2023 年 XAUUSD 的 M15 跑一遍,能看见浮亏单在价格回踩开仓价时被锁零风险,但碰上点差突扩至 3~5 点,modifyLevel 触发瞬间可能滑到微亏。外汇与贵金属杠杆高,移损不代表无风险。
if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY && PositionGetInteger(POSITION_MAGIC) == MagicNumber && PositionGetString(POSITION_SYMBOL) == ChartSymbol(chart_id)) { modifyLevel = MathAbs(NormalizeDouble((positionopen - positionSL) + positionopen,class="num">4)); if(ask_price >= modifyLevel) { trade.PositionModify(ticket, positionopen, positionTP); } }
「EA 落地的几个硬约束」
把 RSI 超买超卖信号接进 EA 时,别只盯入场。用风险回报率把利润目标和计算风险对齐,每笔单固定 % 风险,才能扛住不同烛形大小带来的仓位波动。 流动性清扫过滤加盈亏平衡移动,是这套 EA 能适应突变的关键。前者剔除假突破,后者在行情朝有利方向走一段后锁住成本,让系统不至于被瞬时反转洗掉利润。 外汇和贵金属杠杆高、跳空频繁,哪怕逻辑完整,实盘前也该用策略测试器跑历史数据确认参数稳定性。打开 MT5 把附带的 mq5 加载进编辑器,改 RSI 周期或 RRR 数值,自己跑一遍比看结论有用。