反转策略
概述
反转策略是一种均值回归系统,结合布林带与相对强弱指数(RSI)来识别行情的超买与超卖状态。当价格位于布林带外缘并且 RSI 再次回到阈值以内时,策略判断走势可能耗尽,尝试逆势进场,并通过固定的带宽止损与止盈控制风险收益。
交易逻辑
- 订阅所配置的K线数据(默认 5 分钟K线)。
- 基于设定的周期和标准差倍数计算布林带。
- 基于设定的周期计算 RSI。
- 记录上一根完成K线,用于检测跨越信号:
- 做多条件:上一根收盘价低于上一根下轨且 RSI 低于超卖阈值,本根收盘价重新站上当前下轨,同时 RSI 上穿超卖阈值。
- 做空条件:上一根收盘价高于上一根上轨且 RSI 高于超买阈值,本根收盘价重新跌破当前上轨,同时 RSI 下穿超买阈值。
- 做多时以市价买入,止损设置在入场价下方一个标准差,止盈设置在入场价上方两个标准差。
- 做空时以市价卖出,止损设置在入场价上方一个标准差,止盈设置在入场价下方两个标准差。
- 持仓管理:
- 多头仓位若触及上轨、触发止损或达到止盈则立即平仓。
- 空头仓位若触及下轨、触发止损或达到止盈则立即平仓。
参数
| 名称 | 说明 | 默认值 |
|---|---|---|
CandleType |
K线订阅的时间框架。 | 5 分钟 |
BollingerPeriod |
布林带均线与标准差的计算周期。 | 20 |
BollingerWidth |
布林带使用的标准差倍数。 | 2.0 |
RsiPeriod |
计算 RSI 所使用的周期。 | 14 |
RsiOverbought |
判断超买并触发做空条件的 RSI 阈值。 | 70 |
RsiOversold |
判断超卖并触发做多条件的 RSI 阈值。 | 30 |
所有参数均可在 StockSharp Designer 或 Runner 中进行优化。调节 RSI 阈值可以改变信号的激进程度,调整布林带宽度可决定价格需要偏离均值的幅度。
使用说明
- 策略采用 StockSharp 高级 API,实现自动订阅K线并绑定指标。
- 交易通过
BuyMarket与SellMarket市价单完成,止损与止盈在代码内部管理,不会在市场中挂出限价单。 - 默认设置适用于日内级别的反转交易,如需用于更高时间框架,可调整
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();
}
}
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 ExponentialMovingAverage, RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
class reverse_strategy(Strategy):
def __init__(self):
super(reverse_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30)))
self._bollinger_period = self.Param("BollingerPeriod", 20)
self._bollinger_width = self.Param("BollingerWidth", 1.0)
self._rsi_period = self.Param("RsiPeriod", 14)
self._rsi_overbought = self.Param("RsiOverbought", 70.0)
self._rsi_oversold = self.Param("RsiOversold", 30.0)
self._prev_close = 0.0
self._prev_rsi = 0.0
self._prev_lower = 0.0
self._prev_upper = 0.0
self._has_prev = False
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def BollingerPeriod(self):
return self._bollinger_period.Value
@BollingerPeriod.setter
def BollingerPeriod(self, value):
self._bollinger_period.Value = value
@property
def BollingerWidth(self):
return self._bollinger_width.Value
@BollingerWidth.setter
def BollingerWidth(self, value):
self._bollinger_width.Value = value
@property
def RsiPeriod(self):
return self._rsi_period.Value
@RsiPeriod.setter
def RsiPeriod(self, value):
self._rsi_period.Value = value
@property
def RsiOverbought(self):
return self._rsi_overbought.Value
@RsiOverbought.setter
def RsiOverbought(self, value):
self._rsi_overbought.Value = value
@property
def RsiOversold(self):
return self._rsi_oversold.Value
@RsiOversold.setter
def RsiOversold(self, value):
self._rsi_oversold.Value = value
def OnReseted(self):
super(reverse_strategy, self).OnReseted()
self._prev_close = 0.0
self._prev_rsi = 0.0
self._prev_lower = 0.0
self._prev_upper = 0.0
self._has_prev = False
def OnStarted2(self, time):
super(reverse_strategy, self).OnStarted2(time)
self._prev_close = 0.0
self._prev_rsi = 0.0
self._prev_lower = 0.0
self._prev_upper = 0.0
self._has_prev = False
ema = ExponentialMovingAverage()
ema.Length = self.BollingerPeriod
rsi = RelativeStrengthIndex()
rsi.Length = self.RsiPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(ema, rsi, self._process_candle).Start()
def _process_candle(self, candle, ema_value, rsi_value):
if candle.State != CandleStates.Finished:
return
ema_val = float(ema_value)
rsi_val = float(rsi_value)
close = float(candle.ClosePrice)
band_offset = ema_val * (float(self.BollingerWidth) / 100.0)
upper_band = ema_val + band_offset
lower_band = ema_val - band_offset
if not self._has_prev:
self._prev_close = close
self._prev_rsi = rsi_val
self._prev_lower = lower_band
self._prev_upper = upper_band
self._has_prev = True
return
# Long: price crosses up from below lower band + RSI was oversold
long_signal = (self._prev_close < self._prev_lower and close >= lower_band and
self._prev_rsi < float(self.RsiOversold))
# Short: price crosses down from above upper band + RSI was overbought
short_signal = (self._prev_close > self._prev_upper and close <= upper_band and
self._prev_rsi > float(self.RsiOverbought))
if long_signal:
if self.Position <= 0:
self.BuyMarket()
elif short_signal:
if self.Position >= 0:
self.SellMarket()
# Exit long at upper band
if self.Position > 0 and close >= upper_band:
self.SellMarket()
# Exit short at lower band
if self.Position < 0 and close <= lower_band:
self.BuyMarket()
self._prev_close = close
self._prev_rsi = rsi_val
self._prev_lower = lower_band
self._prev_upper = upper_band
def CreateClone(self):
return reverse_strategy()