日内交易:拉里·康纳斯(Larry Connors)RSI2均值回归策略·进阶篇
(2/3)· 从日线到30分钟,用代码把康纳斯的RSI2逻辑搬进标普500差价合约盘口
平仓与开仓的触发逻辑
这段代码片段把趋势过滤和 RSI 超买超卖拧在了一起:持多单时若收盘价跌破快线均线、或上穿慢线均线,倾向直接平掉;持空单对称处理。 开仓侧只认「无持仓 + 价格站上均线 + RSI 低于上限」做多,或「无持仓 + 价格跌破均线 + RSI 高于下限」做空,不追单。外汇与贵金属波动剧烈,这种条件单也可能在滑点下失效,实盘前务必在 MT5 策略测试器跑一遍。 止损计算用市价乘系数:卖单 sl = bid*(1+slp),买单 sl = ask*(1-slp),再用 NormalizeDouble 对齐品种小数位。把 slp 设成 0.005 代表 50 点止损(以 1.000 报价计),调它就能压住单笔回撤。
if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL&&(lastClose<ma_fast[class="num">0]||lastClose>ma[class="num">0])))trade.PositionClose(pos); } if(rsi[class="num">0]<Max&&NotInPosition&&lastClose>ma[class="num">0])executeBuy(); if(rsi[class="num">0]>Min&&NotInPosition&&lastClose<ma[class="num">0])executeSell(); } } class="type">void executeSell() { class="type">class="kw">double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); bid = NormalizeDouble(bid,_Digits); class="type">class="kw">double sl = bid*(class="num">1+slp); sl = NormalizeDouble(sl, _Digits); trade.Sell(lot,_Symbol,bid,sl); } class="type">void executeBuy() { class="type">class="kw">double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); ask = NormalizeDouble(ask,_Digits); class="type">class="kw">double sl = ask*(class="num">1-slp); sl = NormalizeDouble(sl, _Digits); trade.Buy(lot,_Symbol,ask,sl); }
◍ RSI2连续三根极端值才动手
把传统 RSI2 的阈值拉高,再强制要求连续三根 K 线都撞进极端区,信号质量会比单根触发干净得多。逻辑上,长期趋势品种若连吃三根急跌 K 线,市场更可能已进入恐慌抛售段,短期反转的概率会抬升。 做多需过去三根 RSI2 全小于 10,且收盘价在 200 周期均线上方;做空则三根全大于 90 且收盘在均线下方。离场不拖泥带水:价格突破前前一根 K 线高低点就平,实测这种反转 K 常伴随对前一根的穿透,能锁住 2~3 根 K 线的小幅利润。 US500 的 M30 上,2024.1.1–2025.3.1 回测显示交易频率比普通 RSI2 版本低,因连续三根极端值不易凑齐;多头胜率不算亮眼,主要吃亏在 2024 牛市里只啃到微小反弹。外汇和贵金属用这套,杠杆下 0.15% 止损间距极窄,高风险,建议先开 MT5 用策略测试器跑一遍再谈实盘。 代码里 handleRsi 周期为 2,CopyBuffer 从偏移 1 取 3 根,正好对应“过去三根”;平仓判断直接用 lastClose 与 lastlastHigh/Low 及 ma[0] 比,逻辑和上面规则一一对应。
class=class="str">"cmt">//US500 M30 class="macro">#include <Trade/Trade.mqh> CTrade trade; input class="type">int Magic = class="num">0; input class="type">class="kw">double lot = class="num">0.1; class="type">int barsTotal = class="num">0; class="type">int handleMa; class="type">int handleRsi; const class="type">int Max = class="num">10; const class="type">int Min = class="num">90; const class="type">int MaPeriods = class="num">200; const class="type">class="kw">double slp = class="num">0.0015; class="type">int OnInit() { trade.SetExpertMagicNumber(Magic); handleMa =iMA(_Symbol,PERIOD_CURRENT,MaPeriods,class="num">0,MODE_SMA,PRICE_CLOSE); handleRsi = iRSI(_Symbol,PERIOD_CURRENT,class="num">2,PRICE_CLOSE); class="kw">return(INIT_SUCCEEDED); } class="type">void OnDeinit(const class="type">int reason) { } class="type">void OnTick() { class="type">int bars = iBars(_Symbol,PERIOD_CURRENT); if (barsTotal!= bars){ barsTotal = bars; class="type">bool NotInPosition = true; class="type">class="kw">double ma[]; class="type">class="kw">double rsi[]; CopyBuffer(handleMa,BASE_LINE,class="num">1,class="num">1,ma); CopyBuffer(handleRsi,class="num">0,class="num">1,class="num">3,rsi); class="type">class="kw">double lastClose = iClose(_Symbol, PERIOD_CURRENT, class="num">1); class="type">class="kw">double lastlastHigh = iHigh(_Symbol,PERIOD_CURRENT,class="num">2); class="type">class="kw">double lastlastLow = iLow(_Symbol,PERIOD_CURRENT,class="num">2); for(class="type">int i = PositionsTotal()-class="num">1; i>=class="num">0; i--){ class="type">ulong pos = PositionGetTicket(i); class="type">class="kw">string symboll = PositionGetSymbol(i); if(PositionGetInteger(POSITION_MAGIC) == Magic&&symboll== _Symbol){ NotInPosition = false; if((PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY&&(lastClose>lastlastHigh||lastClose<ma[class="num">0])) ||(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL&&(lastClose<lastlastLow||lastClose>ma[class="num">0])))trade.PositionClose(pos); }}
「RSI 三连低位配合均线的进出场触发」
这段逻辑把 RSI 连续三期受压和价格相对均线的位置捆绑成触发条件。买侧要求 rsi[0]、rsi[1]、rsi[2] 同时低于 Max,且当前无持仓、最后收盘价站在 ma[0] 上方,才调用 executeBuy;卖侧镜像处理,三期限 RSI 高于 Min 且收盘价跌破均线才 executeSell。 进场函数的止损用比例参数 slp 直接算:卖单 sl = bid*(1+slp),买单 sl = ask*(1-slp),再用 NormalizeDouble 按品种小数位对齐。这样写省去手动算点值,但 slp 若设 0.005 在 XAUUSD 上约对应 5 美金止损,外汇贵金属波动大,实盘前需在 MT5 策略测试器按品种校准。 别把 RSI 阈值当死规矩 Max/Min 和 slp 都是外部量,回测里 RSI 三连低于 30 常出现在震荡假突破后,配合均线过滤能砍掉部分逆势单,但趋势市里可能连续踏空。把代码贴进 EA 跑 2023 年 EURUSD M15,观察被 NotInPosition 拦掉的重叠信号占比,再决定要不要放宽三周期限制。
if(rsi[class="num">0]<Max&&rsi[class="num">1]<Max&&rsi[class="num">2]<Max&&NotInPosition&&lastClose>ma[class="num">0])executeBuy(); if(rsi[class="num">0]>Min&&rsi[class="num">1]>Min&&rsi[class="num">2]>Min&&NotInPosition&&lastClose<ma[class="num">0])executeSell(); } } class="type">void executeSell() { class="type">class="kw">double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); bid = NormalizeDouble(bid,_Digits); class="type">class="kw">double sl = bid*(class="num">1+slp); sl = NormalizeDouble(sl, _Digits); trade.Sell(lot,_Symbol,bid,sl); } class="type">void executeBuy() { class="type">class="kw">double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); ask = NormalizeDouble(ask,_Digits); class="type">class="kw">double sl = ask*(class="num">1-slp); sl = NormalizeDouble(sl, _Digits); trade.Buy(lot,_Symbol,ask,sl); }
RSI2双向阈值与50均线过滤的实战写法
把2周期RSI压到5和95做进出场阈值,比传统30/70更极端,赌的是价格短期过度偏离后回头。离场用RSI2穿70平多、穿30平空,趋势过滤从康纳斯的200均线改成50均线——做空本就吃短期突变,50均线对转折更敏感。 这套在US500的M30上跑过:2024.1.1至2025.3.1回测,胜率比前两种均值回归写法高,但权益曲线回撤浮动区间明显更大。根子是肥尾风险,2周期RSI仍被平滑、滞后于价格,方向骤反时只有硬止损能兜住,原文给的是市价1%间距止损,实盘外汇/贵金属波动更野,这间距可能偏窄,需自测。 别把正态当圣经 均值回归策略最怕极端波动比正态预期频繁得多,价格不回头反而继续扯,亏损会超模型。US500回测里绿线回撤放大就是这现象,真上MT5跑之前先把止损宽度和手数压住。 代码里OnTick只在新bar触发,用PositionsTotal扫同Magic同品种持仓,无仓才开。开多要rsi[0]<5且上根收盘>50均线,开空反之。直接把下面片段丢进MT5策略测试器,品种切US500、周期M30就能复现信号逻辑。
class=class="str">"cmt">//US500 M30 class="macro">#include <Trade/Trade.mqh> CTrade trade; input class="type">int Magic = class="num">0; input class="type">class="kw">double lot = class="num">0.1; const class="type">int Max = class="num">5; const class="type">int Min = class="num">95; const class="type">int MaPeriods = class="num">50; const class="type">class="kw">double slp = class="num">0.01; class="type">int barsTotal = class="num">0; class="type">int handleMa; class="type">int handleRsi; class="type">int OnInit() { trade.SetExpertMagicNumber(Magic); handleMa =iMA(_Symbol,PERIOD_CURRENT,MaPeriods,class="num">0,MODE_SMA,PRICE_CLOSE); handleRsi = iRSI(_Symbol,PERIOD_CURRENT,class="num">2,PRICE_CLOSE); class="kw">return(INIT_SUCCEEDED); } class="type">void OnDeinit(const class="type">int reason) { } class="type">void OnTick() { class="type">int bars = iBars(_Symbol,PERIOD_CURRENT); if (barsTotal!= bars){ barsTotal = bars; class="type">bool NotInPosition = true; class="type">class="kw">double ma[]; class="type">class="kw">double rsi[]; CopyBuffer(handleMa,BASE_LINE,class="num">1,class="num">1,ma); CopyBuffer(handleRsi,class="num">0,class="num">1,class="num">1,rsi); class="type">class="kw">double lastClose = iClose(_Symbol, PERIOD_CURRENT, class="num">1); for(class="type">int i = PositionsTotal()-class="num">1; i>=class="num">0; i--){ class="type">ulong pos = PositionGetTicket(i); class="type">class="kw">string symboll = PositionGetSymbol(i); if(PositionGetInteger(POSITION_MAGIC) == Magic&&symboll== _Symbol){ NotInPosition = false; if((PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY&&rsi[class="num">0]>class="num">70) ||(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL&&rsi[class="num">0]<class="num">30))trade.PositionClose(pos); }} if(rsi[class="num">0]<Max&&NotInPosition&&lastClose>ma[class="num">0])executeBuy(); if(rsi[class="num">0]>Min&&NotInPosition&&lastClose<ma[class="num">0])executeSell(); } }