在 GitHub 上查看
EMA RSI 波动自适应均线交叉策略
本策略是 MetaTrader 专家顾问 EA_MARSI_1-02 的直接移植版本。它监听两条 EMA_RSI_VA 指标线的交叉情况 —— 该指
标由 Integer 编写,利用 RSI 推动 EMA 的自适应周期。每当慢线与快线交叉,策略都会立即翻转净头寸,以完全再现原始
EA 的“信号即反手”逻辑,并遵循 StockSharp 的下单规范。
指标工作方式
原始 MQL 套件包含自定义指标 EMA_RSI_VA。它在价格上构建 EMA,并根据 RSI 偏离 50 的幅度动态调整平滑周期。本移
植版本实现了 EmaRsiVolatilityAdaptiveIndicator 类来还原该公式:
- 在选定的
AppliedPrice 价格源上计算周期为 RSIPeriod 的 RSI。
- 计算 RSI 与 50 的距离
|RSI - 50| + 1,作为波动度代理。
- 构造自适应系数
multi = (5 + 100 / RSIPeriod) / (0.06 + 0.92 * dist + 0.02 * dist^2)。
- 使用该系数乘以基础 EMA 周期,得到动态周期
pdsx。
- 以平滑系数
2 / (pdsx + 1) 执行标准 EMA 递推,并使用所选的价格类型作为输入。
当 RSI 远离 50 时,动态周期缩短,指标反应更快;RSI 平稳时,周期拉长以抑制噪声。慢线与快线均支持
StockSharp.Messages.AppliedPrice 的全部价格模式。
交易规则
- 信号判断
- 做空/卖出: 前一根 K 线的慢线 < 快线,且当前慢线 ≥ 快线。
- 做多/买入: 前一根 K 线的慢线 > 快线,且当前慢线 ≤ 快线。
- 执行细节
- 只处理已完成的蜡烛(
CandleStates.Finished)。
- 触发信号时提交一笔市价单,其数量可同时平掉原有仓位并建立新的反向仓位。
- 体量会自动匹配
Security.MinVolume、Security.VolumeStep 与 Security.MaxVolume 等交易所限制。
- 翻转
- 通过一次
SellMarket 或 BuyMarket 调整净头寸穿过零点,从而模拟 EA 在出现反向信号时立即反手的行为。
风险管理
TakeProfitPoints 与 StopLossPoints 对应 EA 中的 TP/SL(以价格点表示)。任一参数大于零时会启动 StockSharp 的保护
管理器,使用绝对价差和 useMarketOrders = true,模拟原程序中不断修改止损/止盈的逻辑。
UseBalanceMultiplier 实现了 use_Multpl。启用后实际下单量变为
Volume * PortfolioEquity / MaxDrawdown,随后再按交易所要求进行归一化。
- 依旧调用
StartProtection(),方便外部模块挂接跟踪止损或保本逻辑。
参数
| 参数 |
默认值 |
说明 |
Volume |
0.1 |
启动时的基础下单手数,未应用资金乘数前的数值。 |
TakeProfitPoints |
0 |
止盈距离(点);0 表示不启用。 |
StopLossPoints |
0 |
止损距离(点);0 表示不启用。 |
UseBalanceMultiplier |
false |
是否启用资金规模乘数,等价于 EA 的 use_Multpl。 |
MaxDrawdown |
10000 |
资金乘数的分母,对应 EA 的 Max_drawdown。 |
SlowRsiPeriod |
310 |
慢线 RSI 周期。 |
SlowEmaPeriod |
40 |
慢线的基础 EMA 周期。 |
SlowAppliedPrice |
Close |
慢线使用的价格类型。 |
FastRsiPeriod |
200 |
快线 RSI 周期。 |
FastEmaPeriod |
50 |
快线的基础 EMA 周期。 |
FastAppliedPrice |
Close |
快线使用的价格类型。 |
CandleType |
TimeFrame(1m) |
进行计算的蜡烛序列。 |
实现说明
- 采用 StockSharp 高层 API(
SubscribeCandles().Bind(...))完成订阅与指标绑定,无需手动遍历历史缓存。
- 仅使用已完成的蜡烛,行为与 MQL 中
CopyBuffer(..., 1, 2, ...) 的读法一致。
- 订单量通过
Security.MinVolume、Security.VolumeStep、Security.MaxVolume 进行归一化,避免提交无效委托。
- 根据要求暂不提供 Python 版本,目录内只有 C# 实现和多语言文档。
该移植版本忠实重现原始 EA 的交易逻辑,同时提供更符合 StockSharp 生态(Designer、Runner 等)的参数界面与风控选项。
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// EMA RSI VA Crossover: Fast/slow EMA crossover with RSI volatility filter.
/// </summary>
public class EmaRsiVaCrossStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastEmaLength;
private readonly StrategyParam<int> _slowEmaLength;
private readonly StrategyParam<int> _rsiLength;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _entryPrice;
public EmaRsiVaCrossStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_fastEmaLength = Param(nameof(FastEmaLength), 10)
.SetDisplay("Fast EMA", "Fast EMA period.", "Indicators");
_slowEmaLength = Param(nameof(SlowEmaLength), 40)
.SetDisplay("Slow EMA", "Slow EMA period.", "Indicators");
_rsiLength = Param(nameof(RsiLength), 14)
.SetDisplay("RSI Length", "RSI period.", "Indicators");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int FastEmaLength
{
get => _fastEmaLength.Value;
set => _fastEmaLength.Value = value;
}
public int SlowEmaLength
{
get => _slowEmaLength.Value;
set => _slowEmaLength.Value = value;
}
public int RsiLength
{
get => _rsiLength.Value;
set => _rsiLength.Value = value;
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevFast = 0;
_prevSlow = 0;
_entryPrice = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevFast = 0;
_prevSlow = 0;
_entryPrice = 0;
var fastEma = new ExponentialMovingAverage { Length = FastEmaLength };
var slowEma = new ExponentialMovingAverage { Length = SlowEmaLength };
var rsi = new RelativeStrengthIndex { Length = RsiLength };
var atr = new AverageTrueRange { Length = 14 };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(fastEma, slowEma, rsi, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, fastEma);
DrawIndicator(area, slowEma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal fastVal, decimal slowVal, decimal rsiVal, decimal atrVal)
{
if (candle.State != CandleStates.Finished)
return;
if (_prevFast == 0 || _prevSlow == 0 || atrVal <= 0)
{
_prevFast = fastVal;
_prevSlow = slowVal;
return;
}
var close = candle.ClosePrice;
if (Position > 0)
{
if (close >= _entryPrice + atrVal * 3m || close <= _entryPrice - atrVal * 2m || (fastVal < slowVal && _prevFast >= _prevSlow))
{
SellMarket();
_entryPrice = 0;
}
}
else if (Position < 0)
{
if (close <= _entryPrice - atrVal * 3m || close >= _entryPrice + atrVal * 2m || (fastVal > slowVal && _prevFast <= _prevSlow))
{
BuyMarket();
_entryPrice = 0;
}
}
if (Position == 0)
{
if (fastVal > slowVal && _prevFast <= _prevSlow && rsiVal > 40 && rsiVal < 70)
{
_entryPrice = close;
BuyMarket();
}
else if (fastVal < slowVal && _prevFast >= _prevSlow && rsiVal > 30 && rsiVal < 60)
{
_entryPrice = close;
SellMarket();
}
}
_prevFast = fastVal;
_prevSlow = slowVal;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Algo.Indicators import ExponentialMovingAverage, RelativeStrengthIndex, AverageTrueRange
class ema_rsi_va_cross_strategy(Strategy):
def __init__(self):
super(ema_rsi_va_cross_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe.", "General")
self._fast_ema_length = self.Param("FastEmaLength", 10) \
.SetDisplay("Fast EMA", "Fast EMA period.", "Indicators")
self._slow_ema_length = self.Param("SlowEmaLength", 40) \
.SetDisplay("Slow EMA", "Slow EMA period.", "Indicators")
self._rsi_length = self.Param("RsiLength", 14) \
.SetDisplay("RSI Length", "RSI period.", "Indicators")
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@property
def FastEmaLength(self):
return self._fast_ema_length.Value
@property
def SlowEmaLength(self):
return self._slow_ema_length.Value
@property
def RsiLength(self):
return self._rsi_length.Value
def OnStarted2(self, time):
super(ema_rsi_va_cross_strategy, self).OnStarted2(time)
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._fast_ema = ExponentialMovingAverage()
self._fast_ema.Length = self.FastEmaLength
self._slow_ema = ExponentialMovingAverage()
self._slow_ema.Length = self.SlowEmaLength
self._rsi = RelativeStrengthIndex()
self._rsi.Length = self.RsiLength
self._atr = AverageTrueRange()
self._atr.Length = 14
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._fast_ema, self._slow_ema, self._rsi, self._atr, self.ProcessCandle).Start()
def ProcessCandle(self, candle, fast_val, slow_val, rsi_val, atr_val):
if candle.State != CandleStates.Finished:
return
fv = float(fast_val)
sv = float(slow_val)
rv = float(rsi_val)
av = float(atr_val)
if self._prev_fast == 0 or self._prev_slow == 0 or av <= 0:
self._prev_fast = fv
self._prev_slow = sv
return
close = float(candle.ClosePrice)
if self.Position > 0:
if close >= self._entry_price + av * 3.0 or close <= self._entry_price - av * 2.0 or (fv < sv and self._prev_fast >= self._prev_slow):
self.SellMarket()
self._entry_price = 0.0
elif self.Position < 0:
if close <= self._entry_price - av * 3.0 or close >= self._entry_price + av * 2.0 or (fv > sv and self._prev_fast <= self._prev_slow):
self.BuyMarket()
self._entry_price = 0.0
if self.Position == 0:
if fv > sv and self._prev_fast <= self._prev_slow and rv > 40 and rv < 70:
self._entry_price = close
self.BuyMarket()
elif fv < sv and self._prev_fast >= self._prev_slow and rv > 30 and rv < 60:
self._entry_price = close
self.SellMarket()
self._prev_fast = fv
self._prev_slow = sv
def OnReseted(self):
super(ema_rsi_va_cross_strategy, self).OnReseted()
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
def CreateClone(self):
return ema_rsi_va_cross_strategy()