在 GitHub 上查看

Bollinger RSI MA 策略

概述

Bollinger RSI MA 策略将 MetaTrader 专家顾问 BolRSIMAs 迁移到 StockSharp 的高级 API。策略结合布林带突破、RSI 滤波 以及高周期指数移动平均线(EMA)来寻找顺势回调的交易机会。原策略的自动手数功能得以保留:启用后系统会根据 data 账户权益、布林止损距离以及合约规模自动换算下单手数。

交易逻辑

  1. 订阅主级别 K 线(默认 1 小时),在同一时间框计算布林带和 RSI。
  2. 订阅日线级别 K 线,将收盘价送入 200 周期 EMA,以复现原 EA 使用的高周期趋势过滤器。
  3. 当最新 K 线收盘价跌破下轨、RSI 低于超卖阈值且收盘价仍位于日线 EMA 之上时触发做多信号;若收盘价突破上轨、 RSI 超过超买阈值且价格位于日线 EMA 之下,则触发做空信号。
  4. 仅在没有持仓时开新仓。入场后即记录基于布林带的保护位:多单止损为 下轨 - StopLossOffset,止盈为中轨; 空单止损为 上轨 + StopLossOffset,止盈同样设在中轨。
  5. 每根 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 关闭自动手数时的下单手数。

资金管理

  • UseAutoLottrue 时,手数按照 (equity * RiskPerTrade) / (StopLossOffset * price * contractSize) 计算,并根据 交易所的最小/最大手数与步长进行修正,复现 MetaTrader 中的 autolot 逻辑。
  • 若无法获取权益或价格信息,策略会回退到 FixedVolume,同时仍遵守交易所的体积限制。

与原 EA 的差异

  • StockSharp 通过比较 K 线最高价/最低价来模拟止损和止盈触发,不再依赖服务器端的挂单,但结果与原 EA 保持一致。
  • EMA 滤波完全基于 StockSharp 的多周期订阅,无需调用 MetaTrader 的日线句柄接口。
  • 风险控制会参考证券的 MinVolumeMaxVolumeVolumeStep 设置,避免因手数不合规而被交易所拒单。

使用建议

  • 针对不同报价精度的品种可调整 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;
		}
	}
}