RSI Donchian Strategy
本策略结合RSI与唐奇安通道,寻找动量极端并配合通道突破。当RSI低于30且价格突破上轨时做多;当RSI高于70且价格跌破下轨时做空。价格回到中轨时离场。
测试表明年均收益约为 82%,该策略在股票市场表现最佳。
适合喜欢在耗尽走势后反向交易但又依赖突破水平的主动交易者。止损用于防止动量未能及时回撤时的风险。
细节
- 入场条件:
- 多头:
RSI < 30 && Price > Donchian Upper - 空头:
RSI > 70 && Price < Donchian Lower
- 多头:
- 多/空: 双向
- 离场条件:
- 多头: 收盘价跌破唐奇安中轨
- 空头: 收盘价升破唐奇安中轨
- 止损: 百分比止损
- 默认值:
RsiPeriod= 14DonchianPeriod= 20StopLossPercent= 2mCandleType= 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;
}
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import RelativeStrengthIndex, Highest, Lowest
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
class rsi_donchian_strategy(Strategy):
"""
Strategy based on RSI and Donchian Channel indicators.
"""
def __init__(self):
super(rsi_donchian_strategy, self).__init__()
self._rsi_period = self.Param("RsiPeriod", 14) \
.SetGreaterThanZero() \
.SetDisplay("RSI Period", "Period for RSI calculation", "Indicators")
self._donchian_period = self.Param("DonchianPeriod", 20) \
.SetGreaterThanZero() \
.SetDisplay("Donchian Period", "Period for Donchian Channel calculation", "Indicators")
self._cooldown_bars = self.Param("CooldownBars", 80) \
.SetRange(1, 200) \
.SetDisplay("Cooldown Bars", "Bars between trades", "General")
self._stop_loss_percent = self.Param("StopLossPercent", 2.0) \
.SetGreaterThanZero() \
.SetDisplay("Stop-loss %", "Stop-loss as percentage of entry price", "Risk Management")
self._candle_type = self.Param("CandleType", tf(5)) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._previous_rsi = 0.0
self._donchian_high = 0.0
self._donchian_low = 0.0
self._donchian_middle = 0.0
self._current_rsi = 0.0
self._cooldown = 0
@property
def CandleType(self):
return self._candle_type.Value
def OnReseted(self):
super(rsi_donchian_strategy, self).OnReseted()
self._previous_rsi = 0.0
self._donchian_high = 0.0
self._donchian_low = 0.0
self._donchian_middle = 0.0
self._current_rsi = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(rsi_donchian_strategy, self).OnStarted2(time)
self._previous_rsi = 0.0
self._donchian_high = 0.0
self._donchian_low = 0.0
self._donchian_middle = 0.0
self._current_rsi = 0.0
self._cooldown = 0
rsi = RelativeStrengthIndex()
rsi.Length = self._rsi_period.Value
highest = Highest()
highest.Length = self._donchian_period.Value
lowest = Lowest()
lowest.Length = self._donchian_period.Value
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(rsi, highest, lowest, self.ProcessIndicators).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, rsi)
self.DrawOwnTrades(area)
def ProcessIndicators(self, candle, rsi_value, highest_value, lowest_value):
if candle.State != CandleStates.Finished:
return
self._previous_rsi = self._current_rsi
self._current_rsi = float(rsi_value)
self._donchian_high = float(highest_value)
self._donchian_low = float(lowest_value)
self._donchian_middle = (self._donchian_high + self._donchian_low) / 2.0
if self._donchian_high == 0 or self._donchian_low == 0 or self._current_rsi == 0:
return
price = float(candle.ClosePrice)
is_rsi_oversold = self._current_rsi < 30
is_rsi_overbought = self._current_rsi > 70
is_at_lower = price <= self._donchian_low * 1.001
is_at_upper = price >= self._donchian_high * 0.999
if self._cooldown > 0:
self._cooldown -= 1
cooldown_val = int(self._cooldown_bars.Value)
if self._cooldown == 0 and is_rsi_oversold and is_at_lower:
if self.Position <= 0:
self.BuyMarket()
self._cooldown = cooldown_val
elif self._cooldown == 0 and is_rsi_overbought and is_at_upper:
if self.Position >= 0:
self.SellMarket()
self._cooldown = cooldown_val
elif (self.Position > 0 and price < self._donchian_middle) or \
(self.Position < 0 and price > self._donchian_middle):
if self.Position > 0:
self.SellMarket()
self._cooldown = cooldown_val
elif self.Position < 0:
self.BuyMarket()
self._cooldown = cooldown_val
def CreateClone(self):
return rsi_donchian_strategy()