区域方法·进阶篇
(2/3)·传统RSI四信号决策太繁琐?这一篇用代码把“50线后区域”直接变成开仓逻辑
「RSI 区域指标的双缓冲骨架怎么搭」
在 RSI 震荡指标之上叠加区域视角,最直观的做法是把同一组数据用两种画法呈现:一个缓冲区走 DRAW_HISTOGRAM(柱状),一个走 DRAW_LINE(线),用来区分 RSI 穿越 50 中轴的上/下区域。原文实现的 v1.00 版本实际声明了 3 个双精度数组,其中两个是指标缓冲区,第三个是额外缓冲区,配合标准的 iRSI 句柄取数。 头部先写清描述,例如「显示RSI超过/低于50线的指标」,再用 #property indicator_buffers 3 与 indicator_plots 2 锁定结构;画图类型分别指定为 DRAW_HISTOGRAM 和 DRAW_LINE,颜色都给 clrGray。输入参数只留一个 ExtRSIPeriod=13,也就是 RSI 周期默认 13,读者开 MT5 后改这个数就能直接看不同灵敏度的区域厚度。 OnInit() 里先把数组用 SetIndexBuffer 绑到 0、1 号缓冲区,再对数组做 ArraySetAsSeries 让最右元素索引为 0,符合时间序列习惯;随后用 iRSI 拿到句柄,填好 short_name。这样指标骨架就立住了,复制下面代码到 MetaEditor 存成 .mq5 即可编译验证。 把取数逻辑单独抽成 FillArrayFromBuffer() 是值得借鉴的拆分:OnCalculate() 只算要拷贝多少根 K 线的数值(values_to_copy),再调函数用 CopyBuffer 灌进缓冲区。注意 OnCalculate 传入的 time[]/close[] 等数组若未设时间序列,最右索引最大,数学计算时别搞反方向。
class="macro">#class="kw">property version "class="num">1.00" class="macro">#class="kw">property description "显示RSI超过/低于50线的指标" class="macro">#class="kw">property indicator_separate_window class="macro">#class="kw">property indicator_separate_window class="macro">#class="kw">property indicator_buffers class="num">3 class="macro">#class="kw">property indicator_plots class="num">2 class="macro">#class="kw">property indicator_type1 DRAW_HISTOGRAM class="macro">#class="kw">property indicator_type2 DRAW_LINE class="macro">#class="kw">property indicator_color1 clrGray class="macro">#class="kw">property indicator_color2 clrGray class=class="str">"cmt">//--- 输入参数 input class="type">int ExtRSIPeriod=class="num">13; class=class="str">"cmt">//--- 输入参数 input class="type">int ExtRSIPeriod=class="num">13; class=class="str">"cmt">//---- 缓冲区 class="type">class="kw">double ExtMapBuffer1[]; class="type">class="kw">double ExtMapBuffer2[]; class="type">class="kw">double ExtMapBuffer3[]; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 自定义指标初始化函数 | class="type">class="kw">double ExtMapBuffer3[]; class=class="str">"cmt">//--- 用于保存 iRSI 指标句柄的变量 class="type">int handle; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 自定义指标初始化函数 | class="type">int handle; class=class="str">"cmt">//--- 用于保存的变量 class="type">class="kw">string name=Symbol(); class=class="str">"cmt">//--- 图表上指标的名称 class="type">class="kw">string short_name; class=class="str">"cmt">//--- 我们将会记录相对强弱指数指标值得数量 class="type">int bars_calculated=class="num">0; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 自定义指标初始化函数 | class="type">int OnInit() { class=class="str">"cmt">//--- 指标缓冲区映射 SetIndexBuffer(class="num">0,ExtMapBuffer1,INDICATOR_DATA); SetIndexBuffer(class="num">1,ExtMapBuffer2,INDICATOR_DATA);
RSI区域指标的缓冲区与句柄初始化
在自定义 MT5 指标里,把外部 RSI 数据接进来前,得先把三个缓冲区按顺序绑好:索引 1 用 INDICATOR_DATA 画主线,索引 2 用 INDICATOR_CALCULATIONS 做中间计算。ExtMapBuffer1~3 全部 ArraySetAsSeries(true),时间轴才和 K 线右对齐,否则历史数据会倒着填。 精度别偷懒,IndicatorSetInteger(INDICATOR_DIGITS,2) 直接锁两位小数,黄金和欧美报价跳动肉眼更顺。外汇和贵金属杠杆高,显示错一位可能误导进出场,初始化阶段就把Digits定死更稳。 真正拉数据靠 iRSI 句柄:handle=iRSI(name,0,ExtRSIPeriod,PRICE_CLOSE)。若返回 INVALID_HANDLE,立刻 PrintFormat 把错误编号甩进日志并返回 INIT_FAILED,指标不加载好过乱画。 FillArrayFromBuffer 这个函数负责把句柄里的 RSI 值搬进本地数组。CopyBuffer(ind_handle,0,0,amount,rsi_buffer) 复制量小于 0 就 ResetLastError 后报错退出,返回 false 让主循环知道这根 K 线还没算完。
SetIndexBuffer(class="num">2,ExtMapBuffer3,INDICATOR_CALCULATIONS); class=class="str">"cmt">//--- class="kw">return(INIT_SUCCEEDED); SetIndexBuffer(class="num">1,ExtMapBuffer2,INDICATOR_DATA); SetIndexBuffer(class="num">2,ExtMapBuffer3,INDICATOR_CALCULATIONS); ArraySetAsSeries(ExtMapBuffer1,true); ArraySetAsSeries(ExtMapBuffer2,true); ArraySetAsSeries(ExtMapBuffer3,true); class=class="str">"cmt">//--- class="kw">return(INIT_SUCCEEDED); ArraySetAsSeries(ExtMapBuffer3,true); class=class="str">"cmt">//--- 设置精度 IndicatorSetInteger(INDICATOR_DIGITS,class="num">2); class=class="str">"cmt">//--- class="kw">return(INIT_SUCCEEDED); ArraySetAsSeries(ExtMapBuffer2,true); ArraySetAsSeries(ExtMapBuffer3,true); class=class="str">"cmt">//--- 设置精度 IndicatorSetInteger(INDICATOR_DIGITS,class="num">2); handle=iRSI(name,class="num">0,ExtRSIPeriod,PRICE_CLOSE); class=class="str">"cmt">//--- 如果句柄没有创建成功 if(handle==INVALID_HANDLE) { class=class="str">"cmt">//--- 通知失败消息并输出错误编号 PrintFormat("在交易品种 %s/%s 上创建iRSI指标句柄失败, 错误编号 %d", name, EnumToString(PERIOD_CURRENT), GetLastError()); class=class="str">"cmt">//--- 指标提前退出 class="kw">return(INIT_FAILED); } class=class="str">"cmt">//--- 在交易品种/时段图表上显示计算出来的RSI区域指标 short_name=StringFormat("RSIArea(%d)",ExtRSIPeriod); IndicatorSetString(INDICATOR_SHORTNAME,short_name); class=class="str">"cmt">//--- 指标正常初始化 class="kw">return(INIT_SUCCEEDED); class=class="str">"cmt">//--- 返回prev_calculated的值以便下一次调用 class="kw">return(rates_total); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 使用 iRSI 指标值填充指标缓冲区 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool FillArrayFromBuffer(class="type">class="kw">double &rsi_buffer[],class=class="str">"cmt">// 用于相对强弱指数值的指标缓冲区 class="type">int ind_handle, class=class="str">"cmt">// iRSI 指标的句柄 class="type">int amount class=class="str">"cmt">// 复制数值的数量 ) { class=class="str">"cmt">//--- 重设错误代码 ResetLastError(); class=class="str">"cmt">//--- 使用索引为0开始的指标缓冲区值填充iRSIBuffer数组 if(CopyBuffer(ind_handle,class="num">0,class="num">0,amount,rsi_buffer)<class="num">0) { class=class="str">"cmt">//--- 如果复制失败输出错误编号 PrintFormat("从iRSI指标复制数据失败, 错误编号 %d",GetLastError()); class=class="str">"cmt">//--- 使用零结果退出 - 这是意味着指标还没有计算 class="kw">return(false); }
◍ RSI 缓冲区的增量拷贝逻辑
在自定义指标里接 iRSI 时,最忌每次 OnCalculate 都把全量重算一遍。MT5 给的 prev_calculated 就是用来省算力的:首次或品种历史变了才整段拷,否则只补最新一根柱。 判断条件写得很直白——prev_calculated==0(首次)、calculated!=bars_calculated(RSI 端算出的总根数变了)、或 rates_total>prev_calculated+1(价格历史被插柱)。满足任一就走全量分支:values_to_copy 取 calculated 与 rates_total 的较小值,避免数组越界。 非首次且历史平稳时,只补 (rates_total-prev_calculated)+1 根。注意那个 +1,它保证当前未闭合的实时柱也被刷进缓冲区,不然图表最右一根会滞后一次 tick。 拷完之后用 FillArrayFromBuffer 把 RSI 值灌进 ExtMapBuffer3,失败直接 return(0) 退出,不污染后续绘制。最后一行 ExtMapBuffer1[i]=ExtMapBuffer2[i]=ExtMapBuffer3[i]-50.0 把 RSI 从 0~100 平移到 -50~50,零轴就是经典中线。外汇与贵金属波动跳点大,这种偏移只为看图直观,不改变指标概率属性,实盘仍属高风险。
class=class="str">"cmt">//--- 从iRSI指标复制数值的数量 class="type">int values_to_copy; class=class="str">"cmt">//--- 确定指标中已经计算的数值数量 class="type">int calculated=BarsCalculated(handle); if(calculated<=class="num">0) { PrintFormat("BarsCalculated() 返回 %d, 错误编号 %d",calculated,GetLastError()); class="kw">return(class="num">0); } class=class="str">"cmt">//--- 如果这是第一次计算指标值或者iRSI指标值数量改变 class=class="str">"cmt">//---或者有需要计算两个以上柱的指标值 (意思是价格历史有变) if(prev_calculated==class="num">0 || calculated!=bars_calculated || rates_total>prev_calculated+class="num">1) { class=class="str">"cmt">//--- 如果iRSIBuffer数组大于对于某交易品种/时段的iRSI指标值的数量,则不复制任何内容 class=class="str">"cmt">//--- 否则,我们复制少于指标缓冲区大小的部分 if(calculated>rates_total) values_to_copy=rates_total; else values_to_copy=calculated; } else { class=class="str">"cmt">//--- 这里的意思是指标不是第一次计算,而是从上次调用OnCalculate()开始的 class=class="str">"cmt">//--- 要多计算一个柱 values_to_copy=(rates_total-prev_calculated)+class="num">1; } class=class="str">"cmt">//--- 使用iRSI指标的值填充数组 class=class="str">"cmt">//--- 如果 FillArrayFromBuffer 返回 false, 意思是信息还没有准备好,就要退出运行 if(!FillArrayFromBuffer(ExtMapBuffer3,handle,values_to_copy)) class="kw">return(class="num">0); class=class="str">"cmt">//--- for(class="type">int i=class="num">0;i<values_to_copy;i++) { ExtMapBuffer1[i]=ExtMapBuffer2[i]=ExtMapBuffer3[i]-class="num">50.0; } class=class="str">"cmt">//--- 记住相对强弱指数指标中数值的数量 bars_calculated=calculated; class=class="str">"cmt">//--- 返回prev_calculated的值以便下一次调用 class="kw">return(rates_total);
「用 CTrade 搭一个 RSI 区域 EA 的骨架」
做 EA 时建议把它丢进独立目录(比如 MyExp),新建向导里所有选项都别勾,先拿到一份 RSIAreaExpert_v1_Step1.mq5 的草稿。开头先把描述写进通用页,顺手引入标准库 CTrade 并声明 my_trade 对象,后面下单全靠它。 头部还要挂三个全局变量:handle 存 iRSI 句柄、RSIArea 存算出来的区域值、RSIOpen 做辅助。输入参数里塞一个 period(默认 PERIOD_M15),纯粹是为了在策略测试器里能切不同周期跑,比如图 8 里就能覆盖较大时段范围做验证。 OnInit() 只干一件事:用 Symbol() 配合 period 调 iRSI 拿到句柄赋给 handle,改到这一步对应 RSIAreaExpert_v1_Step2.mq5。RSIAreaFunc() 第一块先把 limit 写死成 100,也就是每次 CopyBuffer 只拷最近 100 个 RSI 值进 arr_rsi[],对应 Step3 文件。 想确认 CopyBuffer 的 0 号索引是不是最右柱,就开个 Step3_check 临时版:在 OnTick 里调函数后把 arr_rsi[0] 和末元素打印出来。编译后挂 M15 图,RSI 设周期 13、收盘价,EA 一挂上图 9 就显示 arr_rsi[0] 正是最右柱数值。外汇和贵金属杠杆高,这类验证单只用于理清机制,不等于实盘稳。 RSIAreaFunc 后续把 result 设为最右柱减 50,再从索引 1 扫到 limit-1 找穿越零线位置存进 RSIAreaShift。OnTick 里先取 rsi 和 last_tick,已有仓位时按 RSIArea 与 AreaCondition 的大小决定加买或加卖;平仓则看 RSIOpen 与 RSICurrent 是否同侧于 50 线,且局部极值回滚达 4%(如 50 上方空单,RSI 触 40 弹回 44 即平)。完整版见 RSIAreaExpert_v1.mq5。
class="macro">#class="kw">property version "class="num">1.00" class="macro">#class="kw">property description "EA trades on \"Method areas\"" class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| EA 初始化函数 | class="macro">#include <Trade\Trade.mqh> class=class="str">"cmt">//--- 全局变量 CTrade my_trade; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| EA 初始化函数 | class=class="str">"cmt">//--- 全局变量 CTrade my_trade; class="type">int handle; class=class="str">"cmt">// 用于保存iRSI指标句柄的变量 class="type">class="kw">double RSIArea; class=class="str">"cmt">// 计算的区域 class="type">class="kw">double RSIOpen; class=class="str">"cmt">// 辅助变量 class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| EA 初始化函数 | class="type">class="kw">double RSIOpen; class=class="str">"cmt">// 辅助变量 class=class="str">"cmt">//--- 输入参数 input class="type">int ExtRSIPeriod=class="num">13; class=class="str">"cmt">// RSI周期数 input class="type">int AreaCondition=class="num">300; class=class="str">"cmt">// 区域 input ENUM_TIMEFRAMES period=PERIOD_M15; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| EA 初始化函数 | class="type">int OnInit() { class=class="str">"cmt">//--- handle=iRSI(Symbol(),period,ExtRSIPeriod,PRICE_CLOSE);
RSI 区域取值的句柄与缓冲陷阱
指标初始化时若 iRSI 句柄返回 INVALID_HANDLE,必须立刻 return(INIT_FAILED),否则后续 CopyBuffer 全是在空句柄上瞎跑。很多 EA 在切换周期或重连后静默失效,根因就在这段没拦住。 下面这段 RSIAreaFunc 里有个硬限制:limit 取 Bars 减去 ExtRSIPeriod,且上限锁死 100。意味着你最多只回溯 100 根 K 线的 RSI 数组,想算 200 根以上的区域面积得改这个数,但改大前要确认品种历史数据已下载,否则 CopyBuffer 返回 -1 直接吐“没有数据”并 return(0)。 代码里用 ArraySetAsSeries(arr_rsi,true) 把数组设为时间序列,arr_rsi[0] 就是最新柱。最后 result=arr_rsi[0]-50.0 把最新 RSI 对 50 中轴做偏移,正数为偏多动能、负数为偏空,外汇与贵金属波动大,这种偏移只反映概率倾向,不代表方向必变。 OnTick 里用 static int RSIAreaShift=0 传引用进函数,目前没在内部改写它,属于预留钩子。你开 MT5 把这段贴进自定义指标,用 Comment 把 arr_rsi[0] 和 arr_rsi[limit-1] 打印到图表左上,就能直观看到 RSI 区域首尾差值。
if(handle==INVALID_HANDLE) { PrintFormat("在交易品种 %s/%s 上创建iRSI指标句柄失败, 错误编号 %d", Symbol(), EnumToString(period), GetLastError()); class="kw">return(INIT_FAILED); } class="kw">return(INIT_SUCCEEDED); } class="type">void OnTick() { } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 区域计算 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double RSIAreaFunc(class="type">int &RSIAreaShift,class="type">int BeginShift) { class="type">int shift,limit; class="type">class="kw">double rsivalue,result; limit=Bars(Symbol(),period)-ExtRSIPeriod; if(limit>class="num">100) limit=class="num">100; class="type">class="kw">double arr_rsi[]; ArrayResize(arr_rsi,limit); ArraySetAsSeries(arr_rsi,true); if(CopyBuffer(handle,class="num">0,class="num">0,limit,arr_rsi)==-class="num">1) { Print("从iRSI CopyBuffer 失败,没有数据。"); class="kw">return(class="num">0); } class="kw">return(result); } class="type">void OnTick() { class="kw">static class="type">int RSIAreaShift=class="num">0; RSIAreaFunc(RSIAreaShift,class="num">0); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 区域计算 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double RSIAreaFunc(class="type">int &RSIAreaShift,class="type">int BeginShift) if(CopyBuffer(handle,class="num">0,class="num">0,limit,arr_rsi)==-class="num">1) { Print("从iRSI CopyBuffer 失败,没有数据。"); class="kw">return(class="num">0); } Comment("arr_rsi[",limit-class="num">1,"]=",DoubleToString(arr_rsi[limit-class="num">1],class="num">2), "; arr_rsi[class="num">0]=",DoubleToString(arr_rsi[class="num">0],class="num">2)); class="kw">return(result); } if(CopyBuffer(handle,class="num">0,class="num">0,limit,arr_rsi)==-class="num">1) { Print("从iRSI CopyBuffer 失败,没有数据。"); class="kw">return(class="num">0); } result=arr_rsi[class="num">0]-class="num">50.0; class=class="str">"cmt">// 索引为0的柱的值