在 GitHub 上查看

RSI Donchian Strategy

本策略结合RSI与唐奇安通道,寻找动量极端并配合通道突破。当RSI低于30且价格突破上轨时做多;当RSI高于70且价格跌破下轨时做空。价格回到中轨时离场。

测试表明年均收益约为 82%,该策略在股票市场表现最佳。

适合喜欢在耗尽走势后反向交易但又依赖突破水平的主动交易者。止损用于防止动量未能及时回撤时的风险。

细节

  • 入场条件:
    • 多头: RSI < 30 && Price > Donchian Upper
    • 空头: RSI > 70 && Price < Donchian Lower
  • 多/空: 双向
  • 离场条件:
    • 多头: 收盘价跌破唐奇安中轨
    • 空头: 收盘价升破唐奇安中轨
  • 止损: 百分比止损
  • 默认值:
    • RsiPeriod = 14
    • DonchianPeriod = 20
    • StopLossPercent = 2m
    • CandleType = TimeSpan.FromMinutes(15)
  • 过滤器:
    • 类别: Mixed
    • 方向: 双向
    • 指标: RSI, Donchian Channel
    • 止损: 是
    • 复杂度: 中等
    • 时间框架: 日内
    • 季节性: 否
    • 神经网络: 否
    • 背离: 否
    • 风险等级: 中等
using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;

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

namespace StockSharp.Samples.Strategies;
	
/// <summary>
/// Strategy based on RSI and Donchian Channel indicators.
/// Enters long when RSI is below 30 (oversold) and price breaks above Donchian high.
/// Enters short when RSI is above 70 (overbought) and price breaks below Donchian low.
/// Uses middle line of Donchian Channel for exit signals.
/// </summary>
public class RsiDonchianStrategy : Strategy
{
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<int> _donchianPeriod;
	private readonly StrategyParam<int> _cooldownBars;
	private readonly StrategyParam<decimal> _stopLossPercent;
	private readonly StrategyParam<DataType> _candleType;
	
	private RelativeStrengthIndex _rsi;
	private Highest _highestHigh;
	private Lowest _lowestLow;
	
	private decimal _previousRsi;
	private decimal _donchianHigh;
	private decimal _donchianLow;
	private decimal _donchianMiddle;
	private decimal _currentRsi;
	private int _cooldown;

	/// <summary>
	/// RSI period parameter.
	/// </summary>
	public int RsiPeriod
	{
		get => _rsiPeriod.Value;
		set => _rsiPeriod.Value = value;
	}
	
	/// <summary>
	/// Donchian Channel period parameter.
	/// </summary>
	public int DonchianPeriod
	{
		get => _donchianPeriod.Value;
		set => _donchianPeriod.Value = value;
	}
	
	/// <summary>
	/// Bars to wait between trades.
	/// </summary>
	public int CooldownBars
	{
		get => _cooldownBars.Value;
		set => _cooldownBars.Value = value;
	}

	/// <summary>
	/// Stop-loss percentage parameter.
	/// </summary>
	public decimal StopLossPercent
	{
		get => _stopLossPercent.Value;
		set => _stopLossPercent.Value = value;
	}
	
	/// <summary>
	/// Candle type parameter.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}
	
	/// <summary>
	/// Constructor.
	/// </summary>
	public RsiDonchianStrategy()
	{
		_rsiPeriod = Param(nameof(RsiPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("RSI Period", "Period for RSI calculation", "Indicators")
			
			.SetOptimize(10, 20, 2);
			
		_donchianPeriod = Param(nameof(DonchianPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("Donchian Period", "Period for Donchian Channel calculation", "Indicators")
			
			.SetOptimize(10, 30, 5);

		_cooldownBars = Param(nameof(CooldownBars), 80)
			.SetRange(1, 200)
			.SetDisplay("Cooldown Bars", "Bars between trades", "General");
			
		_stopLossPercent = Param(nameof(StopLossPercent), 2m)
			.SetGreaterThanZero()
			.SetDisplay("Stop-loss %", "Stop-loss as percentage of entry price", "Risk Management")
			
			.SetOptimize(1m, 3m, 0.5m);
			
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles to use", "General");
	}
	
	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}
	
	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_rsi = null;
		_highestHigh = null;
		_lowestLow = null;
		_previousRsi = 0;
		_donchianHigh = 0;
		_donchianLow = 0;
		_donchianMiddle = 0;
		_currentRsi = 0;
		_cooldown = 0;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

// Initialize indicators
		_rsi = new RelativeStrengthIndex
	{
Length = RsiPeriod
	};
		
		_highestHigh = new Highest
		{
			Length = DonchianPeriod
		};
		
		_lowestLow = new Lowest
		{
			Length = DonchianPeriod
		};
		
		// Create candles subscription
		var subscription = SubscribeCandles(CandleType);
		
		// Bind indicators
		subscription
			.Bind(_rsi, _highestHigh, _lowestLow, ProcessIndicators)
			.Start();

		// Setup chart if available
		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, _rsi);
			DrawOwnTrades(area);
		}
	}
	
	private void ProcessIndicators(ICandleMessage candle, decimal rsiValue, decimal highestValue, decimal lowestValue)
	{
		// Skip unfinished candles
		if (candle.State != CandleStates.Finished)
			return;
		
		// Save previous RSI value
		_previousRsi = _currentRsi;
		
		// Get current RSI value
		_currentRsi = rsiValue;

		// Update Donchian high value
		_donchianHigh = highestValue;

		// Update Donchian low value
		_donchianLow = lowestValue;

		// Calculate Donchian middle line
		_donchianMiddle = (_donchianHigh + _donchianLow) / 2;

		// Process trading logic after all indicators are updated
		ProcessTradingLogic(candle);
	}

	private void ProcessTradingLogic(ICandleMessage candle)
	{
		// Skip if not all indicators are initialized
		if (_donchianHigh == 0 || _donchianLow == 0 || _currentRsi == 0)
			return;
		
		// Trading signals
		bool isRsiOversold = _currentRsi < 30;
		bool isRsiOverbought = _currentRsi > 70;
		bool isAtLowerBand = candle.ClosePrice <= _donchianLow * 1.001m;
		bool isAtUpperBand = candle.ClosePrice >= _donchianHigh * 0.999m;
		if (_cooldown > 0)
			_cooldown--;
		
		// Long signal: RSI < 30 (oversold) and price near Donchian low
		if (_cooldown == 0 && isRsiOversold && isAtLowerBand)
		{
			if (Position <= 0)
			{
				BuyMarket();
				_cooldown = CooldownBars;
			}
		}
		// Short signal: RSI > 70 (overbought) and price near Donchian high
		else if (_cooldown == 0 && isRsiOverbought && isAtUpperBand)
		{
			if (Position >= 0)
			{
				SellMarket();
				_cooldown = CooldownBars;
			}
		}
		// Exit signals based on Donchian middle line
		else if ((Position > 0 && candle.ClosePrice < _donchianMiddle) || 
				(Position < 0 && candle.ClosePrice > _donchianMiddle))
		{
			if (Position > 0)
			{
				SellMarket();
				_cooldown = CooldownBars;
			}
			else if (Position < 0)
			{
				BuyMarket();
				_cooldown = CooldownBars;
			}
		}
	}
}