在 GitHub 上查看
Bollinger RSI MA 策略
概述
Bollinger RSI MA 策略将 MetaTrader 专家顾问 BolRSIMAs 迁移到 StockSharp 的高级 API。策略结合布林带突破、RSI 滤波
以及高周期指数移动平均线(EMA)来寻找顺势回调的交易机会。原策略的自动手数功能得以保留:启用后系统会根据
data 账户权益、布林止损距离以及合约规模自动换算下单手数。
交易逻辑
- 订阅主级别 K 线(默认 1 小时),在同一时间框计算布林带和 RSI。
- 订阅日线级别 K 线,将收盘价送入 200 周期 EMA,以复现原 EA 使用的高周期趋势过滤器。
- 当最新 K 线收盘价跌破下轨、RSI 低于超卖阈值且收盘价仍位于日线 EMA 之上时触发做多信号;若收盘价突破上轨、
RSI 超过超买阈值且价格位于日线 EMA 之下,则触发做空信号。
- 仅在没有持仓时开新仓。入场后即记录基于布林带的保护位:多单止损为
下轨 - StopLossOffset,止盈为中轨;
空单止损为 上轨 + StopLossOffset,止盈同样设在中轨。
- 每根 K 线收盘时检查最高价/最低价是否触及保护位,若命中则立即平仓,以模拟原 EA 在下单时附加的止损/止盈。
参数
| 名称 |
默认值 |
说明 |
CandleType |
1 小时 K 线 |
布林带与 RSI 所使用的主级别时间框。 |
DailyCandleType |
日线 |
为 EMA 提供数据的高周期时间框。 |
BollingerPeriod |
20 |
布林带计算周期。 |
BollingerDeviation |
2 |
布林带宽度系数。 |
RsiPeriod |
13 |
RSI 平滑周期。 |
RsiUpperLevel |
70 |
做空所需的超买阈值。 |
RsiLowerLevel |
30 |
做多所需的超卖阈值。 |
MaPeriod |
200 |
高周期 EMA 长度。 |
StopLossOffset |
0.0238 |
止损距布林带的额外缓冲。 |
UseAutoLot |
true |
是否启用风险比例自动手数。 |
RiskPerTrade |
0.05 |
自动手数下每笔交易的权益占比。 |
FixedVolume |
0.1 |
关闭自动手数时的下单手数。 |
资金管理
- 当
UseAutoLot 为 true 时,手数按照 (equity * RiskPerTrade) / (StopLossOffset * price * contractSize) 计算,并根据
交易所的最小/最大手数与步长进行修正,复现 MetaTrader 中的 autolot 逻辑。
- 若无法获取权益或价格信息,策略会回退到
FixedVolume,同时仍遵守交易所的体积限制。
与原 EA 的差异
- StockSharp 通过比较 K 线最高价/最低价来模拟止损和止盈触发,不再依赖服务器端的挂单,但结果与原 EA 保持一致。
- EMA 滤波完全基于 StockSharp 的多周期订阅,无需调用 MetaTrader 的日线句柄接口。
- 风险控制会参考证券的
MinVolume、MaxVolume、VolumeStep 设置,避免因手数不合规而被交易所拒单。
使用建议
- 针对不同报价精度的品种可调整
StopLossOffset,以保持约 2.38% 的布林带缓冲距离。
- 加密货币等非 T+1 市场如需不同的高周期,可修改
DailyCandleType,确保 EMA 反映正确的趋势周期。
- 若希望在到达中轨后继续跟踪趋势,可配合外部的移动止损模块使用。
namespace StockSharp.Samples.Strategies;
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;
/// <summary>
/// Bollinger + RSI + MA strategy.
/// Buys when price at lower BB and RSI oversold, sells at upper BB and RSI overbought.
/// </summary>
public class BollingerRsiMaStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<int> _bbPeriod;
private readonly StrategyParam<decimal> _bandPercent;
private readonly StrategyParam<int> _signalCooldownCandles;
private int _candlesSinceTrade;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int RsiPeriod { get => _rsiPeriod.Value; set => _rsiPeriod.Value = value; }
public int BbPeriod { get => _bbPeriod.Value; set => _bbPeriod.Value = value; }
public decimal BandPercent { get => _bandPercent.Value; set => _bandPercent.Value = value; }
public int SignalCooldownCandles { get => _signalCooldownCandles.Value; set => _signalCooldownCandles.Value = value; }
public BollingerRsiMaStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
_rsiPeriod = Param(nameof(RsiPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Period", "RSI period", "Indicators");
_bbPeriod = Param(nameof(BbPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("BB Period", "Bollinger Bands period", "Indicators");
_bandPercent = Param(nameof(BandPercent), 0.01m)
.SetGreaterThanZero()
.SetDisplay("Band Percent", "MA percentage band width", "Signals");
_signalCooldownCandles = Param(nameof(SignalCooldownCandles), 6)
.SetGreaterThanZero()
.SetDisplay("Signal Cooldown", "Bars to wait between trades", "Trading");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_candlesSinceTrade = SignalCooldownCandles;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_candlesSinceTrade = SignalCooldownCandles;
var rsi = new RelativeStrengthIndex { Length = RsiPeriod };
var ma = new SimpleMovingAverage { Length = BbPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(rsi, ma, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal rsiValue, decimal maValue)
{
if (candle.State != CandleStates.Finished) return;
if (_candlesSinceTrade < SignalCooldownCandles)
_candlesSinceTrade++;
var close = candle.ClosePrice;
var upper = maValue * (1 + BandPercent);
var lower = maValue * (1 - BandPercent);
// Mean reversion: buy at lower band, sell at upper band
if (close < lower && rsiValue < 35 && Position <= 0 && _candlesSinceTrade >= SignalCooldownCandles)
{
BuyMarket();
_candlesSinceTrade = 0;
}
else if (close > upper && rsiValue > 65 && Position >= 0 && _candlesSinceTrade >= SignalCooldownCandles)
{
SellMarket();
_candlesSinceTrade = 0;
}
}
}
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.Indicators import RelativeStrengthIndex, SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class bollinger_rsi_ma_strategy(Strategy):
def __init__(self):
super(bollinger_rsi_ma_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30)))
self._rsi_period = self.Param("RsiPeriod", 14)
self._bb_period = self.Param("BbPeriod", 20)
self._band_percent = self.Param("BandPercent", 0.01)
self._signal_cooldown_candles = self.Param("SignalCooldownCandles", 6)
self._candles_since_trade = 6
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def RsiPeriod(self):
return self._rsi_period.Value
@RsiPeriod.setter
def RsiPeriod(self, value):
self._rsi_period.Value = value
@property
def BbPeriod(self):
return self._bb_period.Value
@BbPeriod.setter
def BbPeriod(self, value):
self._bb_period.Value = value
@property
def BandPercent(self):
return self._band_percent.Value
@BandPercent.setter
def BandPercent(self, value):
self._band_percent.Value = value
@property
def SignalCooldownCandles(self):
return self._signal_cooldown_candles.Value
@SignalCooldownCandles.setter
def SignalCooldownCandles(self, value):
self._signal_cooldown_candles.Value = value
def OnReseted(self):
super(bollinger_rsi_ma_strategy, self).OnReseted()
self._candles_since_trade = self.SignalCooldownCandles
def OnStarted2(self, time):
super(bollinger_rsi_ma_strategy, self).OnStarted2(time)
self._candles_since_trade = self.SignalCooldownCandles
rsi = RelativeStrengthIndex()
rsi.Length = self.RsiPeriod
ma = SimpleMovingAverage()
ma.Length = self.BbPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(rsi, ma, self._process_candle).Start()
def _process_candle(self, candle, rsi_value, ma_value):
if candle.State != CandleStates.Finished:
return
if self._candles_since_trade < self.SignalCooldownCandles:
self._candles_since_trade += 1
close = float(candle.ClosePrice)
rsi_val = float(rsi_value)
ma_val = float(ma_value)
upper = ma_val * (1.0 + self.BandPercent)
lower = ma_val * (1.0 - self.BandPercent)
if close < lower and rsi_val < 35 and self.Position <= 0 and self._candles_since_trade >= self.SignalCooldownCandles:
self.BuyMarket()
self._candles_since_trade = 0
elif close > upper and rsi_val > 65 and self.Position >= 0 and self._candles_since_trade >= self.SignalCooldownCandles:
self.SellMarket()
self._candles_since_trade = 0
def CreateClone(self):
return bollinger_rsi_ma_strategy()