在 GitHub 上查看

反转策略

概述

反转策略是一种均值回归系统,结合布林带与相对强弱指数(RSI)来识别行情的超买与超卖状态。当价格位于布林带外缘并且 RSI 再次回到阈值以内时,策略判断走势可能耗尽,尝试逆势进场,并通过固定的带宽止损与止盈控制风险收益。

交易逻辑

  1. 订阅所配置的K线数据(默认 5 分钟K线)。
  2. 基于设定的周期和标准差倍数计算布林带。
  3. 基于设定的周期计算 RSI。
  4. 记录上一根完成K线,用于检测跨越信号:
    • 做多条件:上一根收盘价低于上一根下轨且 RSI 低于超卖阈值,本根收盘价重新站上当前下轨,同时 RSI 上穿超卖阈值。
    • 做空条件:上一根收盘价高于上一根上轨且 RSI 高于超买阈值,本根收盘价重新跌破当前上轨,同时 RSI 下穿超买阈值。
  5. 做多时以市价买入,止损设置在入场价下方一个标准差,止盈设置在入场价上方两个标准差。
  6. 做空时以市价卖出,止损设置在入场价上方一个标准差,止盈设置在入场价下方两个标准差。
  7. 持仓管理:
    • 多头仓位若触及上轨、触发止损或达到止盈则立即平仓。
    • 空头仓位若触及下轨、触发止损或达到止盈则立即平仓。

参数

名称 说明 默认值
CandleType K线订阅的时间框架。 5 分钟
BollingerPeriod 布林带均线与标准差的计算周期。 20
BollingerWidth 布林带使用的标准差倍数。 2.0
RsiPeriod 计算 RSI 所使用的周期。 14
RsiOverbought 判断超买并触发做空条件的 RSI 阈值。 70
RsiOversold 判断超卖并触发做多条件的 RSI 阈值。 30

所有参数均可在 StockSharp Designer 或 Runner 中进行优化。调节 RSI 阈值可以改变信号的激进程度,调整布林带宽度可决定价格需要偏离均值的幅度。

使用说明

  • 策略采用 StockSharp 高级 API,实现自动订阅K线并绑定指标。
  • 交易通过 BuyMarketSellMarket 市价单完成,止损与止盈在代码内部管理,不会在市场中挂出限价单。
  • 默认设置适用于日内级别的反转交易,如需用于更高时间框架,可调整 CandleType 参数。
  • 实盘部署时,可视需要叠加趋势、波动率或交易时段等过滤条件以提升稳定性。
using System;
using Ecng.Common;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Simplified from "Reverse" MetaTrader expert.
/// Uses Bollinger Band touches with RSI confirmation for mean-reversion entries.
/// Enters long when price crosses above lower band with RSI oversold,
/// enters short when price crosses below upper band with RSI overbought.
/// </summary>
public class ReverseStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _bollingerPeriod;
	private readonly StrategyParam<decimal> _bollingerWidth;
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<decimal> _rsiOverbought;
	private readonly StrategyParam<decimal> _rsiOversold;

	private ExponentialMovingAverage _ema;
	private RelativeStrengthIndex _rsi;

	private decimal _prevClose;
	private decimal _prevRsi;
	private decimal _prevLower;
	private decimal _prevUpper;
	private bool _hasPrev;

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public int BollingerPeriod
	{
		get => _bollingerPeriod.Value;
		set => _bollingerPeriod.Value = value;
	}

	public decimal BollingerWidth
	{
		get => _bollingerWidth.Value;
		set => _bollingerWidth.Value = value;
	}

	public int RsiPeriod
	{
		get => _rsiPeriod.Value;
		set => _rsiPeriod.Value = value;
	}

	public decimal RsiOverbought
	{
		get => _rsiOverbought.Value;
		set => _rsiOverbought.Value = value;
	}

	public decimal RsiOversold
	{
		get => _rsiOversold.Value;
		set => _rsiOversold.Value = value;
	}

	public ReverseStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe for signals", "General");

		_bollingerPeriod = Param(nameof(BollingerPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("Bollinger Period", "MA length for Bollinger Bands", "Indicators");

		_bollingerWidth = Param(nameof(BollingerWidth), 1m)
			.SetGreaterThanZero()
			.SetDisplay("Bollinger Width", "Standard deviation multiplier", "Indicators");

		_rsiPeriod = Param(nameof(RsiPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("RSI Period", "RSI period", "Indicators");

		_rsiOverbought = Param(nameof(RsiOverbought), 70m)
			.SetDisplay("RSI Overbought", "Upper threshold for short signals", "Signals");

		_rsiOversold = Param(nameof(RsiOversold), 30m)
			.SetDisplay("RSI Oversold", "Lower threshold for long signals", "Signals");
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_ema = new ExponentialMovingAverage { Length = BollingerPeriod };
		_rsi = new RelativeStrengthIndex { Length = RsiPeriod };
		_hasPrev = false;

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(_ema, _rsi, ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, _ema);
			DrawIndicator(area, _rsi);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle, decimal emaValue, decimal rsiValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!_ema.IsFormed || !_rsi.IsFormed)
			return;

		var close = candle.ClosePrice;
		var bandOffset = emaValue * (BollingerWidth / 100m);
		var upperBand = emaValue + bandOffset;
		var lowerBand = emaValue - bandOffset;

		if (!_hasPrev)
		{
			_prevClose = close;
			_prevRsi = rsiValue;
			_prevLower = lowerBand;
			_prevUpper = upperBand;
			_hasPrev = true;
			return;
		}

		var volume = Volume;
		if (volume <= 0)
			volume = 1;

		// Long: price crosses up from below lower band + RSI was oversold
		var longSignal = _prevClose < _prevLower && close >= lowerBand && _prevRsi < RsiOversold;
		// Short: price crosses down from above upper band + RSI was overbought
		var shortSignal = _prevClose > _prevUpper && close <= upperBand && _prevRsi > RsiOverbought;

		if (longSignal)
		{
			if (Position <= 0)
				BuyMarket(Position < 0 ? Math.Abs(Position) + volume : volume);
		}
		else if (shortSignal)
		{
			if (Position >= 0)
				SellMarket(Position > 0 ? Math.Abs(Position) + volume : volume);
		}

		// Exit long at upper band
		if (Position > 0 && close >= upperBand)
			SellMarket(Position);

		// Exit short at lower band
		if (Position < 0 && close <= lowerBand)
			BuyMarket(Math.Abs(Position));

		_prevClose = close;
		_prevRsi = rsiValue;
		_prevLower = lowerBand;
		_prevUpper = upperBand;
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		_ema = null;
		_rsi = null;
		_prevClose = 0;
		_prevRsi = 0;
		_prevLower = 0;
		_prevUpper = 0;
		_hasPrev = false;

		base.OnReseted();
	}
}