Stochastic 均值回归策略
该策略将随机指标 %K 与其平均值比较,寻找过度波动的情形。当 %K 偏离均值数个标准差时,预期其回落。
测试表明年均收益约为 64%,该策略在外汇市场表现最佳。
当 %K 低于平均值减 Multiplier 倍标准差时买入;当 %K 高于平均值加同倍数时卖出。%K 回到均值线附近即平仓。
此方法适合短线交易者捕捉超买或超卖的极端,止损可避免持续的动量造成亏损。
详细信息
- 入场条件:
- 做多: %K < Avg - Multiplier * StdDev
- 做空: %K > Avg + Multiplier * StdDev
- 多空方向: 双向
- 退出条件:
- 做多: Exit when %K > Avg
- 做空: Exit when %K < Avg
- 止损: 是
- 默认值:
StochPeriod= 14KPeriod= 3DPeriod= 3AveragePeriod= 20Multiplier= 2.0mCandleType= TimeSpan.FromMinutes(5)
- 筛选条件:
- 类别: 均值回归
- 方向: 双向
- 指标: Stochastic Oscillator
- 止损: 是
- 复杂度: 中等
- 时间框架: 日内
- 季节性: 否
- 神经网络: 否
- 背离: 否
- 风险等级: 中等
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>
/// Stochastic Mean Reversion Strategy.
/// Enter when Stochastic %K deviates from its average by a certain multiple of standard deviation.
/// Exit when Stochastic %K returns to its average.
/// </summary>
public class StochasticMeanReversionStrategy : Strategy
{
private readonly StrategyParam<int> _stochPeriod;
private readonly StrategyParam<int> _kPeriod;
private readonly StrategyParam<int> _dPeriod;
private readonly StrategyParam<int> _averagePeriod;
private readonly StrategyParam<decimal> _multiplier;
private readonly StrategyParam<DataType> _candleType;
private StochasticOscillator _stochastic;
private SimpleMovingAverage _stochAverage;
private StandardDeviation _stochStdDev;
private decimal _prevStochKValue;
/// <summary>
/// Stochastic period.
/// </summary>
public int StochPeriod
{
get => _stochPeriod.Value;
set => _stochPeriod.Value = value;
}
/// <summary>
/// Stochastic %K period.
/// </summary>
public int KPeriod
{
get => _kPeriod.Value;
set => _kPeriod.Value = value;
}
/// <summary>
/// Stochastic %D period.
/// </summary>
public int DPeriod
{
get => _dPeriod.Value;
set => _dPeriod.Value = value;
}
/// <summary>
/// Period for Stochastic average calculation.
/// </summary>
public int AveragePeriod
{
get => _averagePeriod.Value;
set => _averagePeriod.Value = value;
}
/// <summary>
/// Standard deviation multiplier for entry.
/// </summary>
public decimal Multiplier
{
get => _multiplier.Value;
set => _multiplier.Value = value;
}
/// <summary>
/// Type of candles to use.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="StochasticMeanReversionStrategy"/>.
/// </summary>
public StochasticMeanReversionStrategy()
{
_stochPeriod = Param(nameof(StochPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("Stochastic Period", "Period for Stochastic calculation", "Strategy Parameters")
.SetOptimize(10, 20, 2);
_kPeriod = Param(nameof(KPeriod), 3)
.SetGreaterThanZero()
.SetDisplay("K Period", "Period for %K calculation", "Strategy Parameters")
.SetOptimize(2, 5, 1);
_dPeriod = Param(nameof(DPeriod), 3)
.SetGreaterThanZero()
.SetDisplay("D Period", "Period for %D calculation", "Strategy Parameters")
.SetOptimize(2, 5, 1);
_averagePeriod = Param(nameof(AveragePeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Average Period", "Period for Stochastic average calculation", "Strategy Parameters")
.SetOptimize(10, 30, 5);
_multiplier = Param(nameof(Multiplier), 2.0m)
.SetGreaterThanZero()
.SetDisplay("StdDev Multiplier", "Standard deviation multiplier for entry", "Strategy Parameters")
.SetOptimize(1.0m, 3.0m, 0.5m);
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "Strategy Parameters");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_stochastic = null;
_stochAverage = null;
_stochStdDev = null;
_prevStochKValue = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_stochastic = new StochasticOscillator
{
K = { Length = KPeriod },
D = { Length = DPeriod }
};
_stochAverage = new SimpleMovingAverage { Length = AveragePeriod };
_stochStdDev = new StandardDeviation { Length = AveragePeriod };
Indicators.Add(_stochastic);
Indicators.Add(_stochAverage);
Indicators.Add(_stochStdDev);
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessStochastic)
.Start();
StartProtection(
takeProfit: new Unit(2, UnitTypes.Percent),
stopLoss: new Unit(1, UnitTypes.Percent)
);
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _stochastic);
DrawOwnTrades(area);
}
}
private void ProcessStochastic(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var stochResult = _stochastic.Process(candle);
if (!_stochastic.IsFormed)
return;
if (stochResult is not StochasticOscillatorValue stochTyped || stochTyped.K is not decimal kValue)
return;
var stochAvgValue = _stochAverage.Process(new DecimalIndicatorValue(_stochAverage, kValue, candle.OpenTime) { IsFinal = true }).ToDecimal();
var stochStdDevValue = _stochStdDev.Process(new DecimalIndicatorValue(_stochStdDev, kValue, candle.OpenTime) { IsFinal = true }).ToDecimal();
if (!_stochAverage.IsFormed || !_stochStdDev.IsFormed)
{
_prevStochKValue = kValue;
return;
}
var effectiveStdDev = Math.Max(1m, stochStdDevValue);
var upperBand = stochAvgValue + Multiplier * effectiveStdDev;
var lowerBand = stochAvgValue - Multiplier * effectiveStdDev;
if (Position == 0)
{
if (kValue < lowerBand || kValue < 20m)
BuyMarket();
else if (kValue > upperBand || kValue > 80m)
SellMarket();
}
_prevStochKValue = kValue;
}
}
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, UnitTypes, Unit, CandleStates
from StockSharp.Algo.Indicators import StochasticOscillator, SimpleMovingAverage, StandardDeviation
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
from indicator_extensions import *
class stochastic_mean_reversion_strategy(Strategy):
"""
Stochastic Mean Reversion Strategy.
Enter when Stochastic %K deviates from its average by a certain multiple of standard deviation.
Exit when Stochastic %K returns to its average.
"""
def __init__(self):
super(stochastic_mean_reversion_strategy, self).__init__()
# Initialize strategy parameters
self._stoch_period = self.Param("StochPeriod", 14) \
.SetDisplay("Stochastic Period", "Period for Stochastic calculation", "Strategy Parameters")
self._k_period = self.Param("KPeriod", 3) \
.SetDisplay("K Period", "Period for %K calculation", "Strategy Parameters")
self._d_period = self.Param("DPeriod", 3) \
.SetDisplay("D Period", "Period for %D calculation", "Strategy Parameters")
self._average_period = self.Param("AveragePeriod", 20) \
.SetDisplay("Average Period", "Period for Stochastic average calculation", "Strategy Parameters")
self._multiplier = self.Param("Multiplier", 2.0) \
.SetDisplay("StdDev Multiplier", "Standard deviation multiplier for entry", "Strategy Parameters")
self._candle_type = self.Param("CandleType", tf(5)) \
.SetDisplay("Candle Type", "Type of candles to use", "Strategy Parameters")
# Internal state
self._stochastic = None
self._stoch_average = None
self._stoch_stddev = None
self._prev_stoch_k_value = 0.0
@property
def stoch_period(self):
"""Stochastic period."""
return self._stoch_period.Value
@stoch_period.setter
def stoch_period(self, value):
self._stoch_period.Value = value
@property
def k_period(self):
"""Stochastic %K period."""
return self._k_period.Value
@k_period.setter
def k_period(self, value):
self._k_period.Value = value
@property
def d_period(self):
"""Stochastic %D period."""
return self._d_period.Value
@d_period.setter
def d_period(self, value):
self._d_period.Value = value
@property
def average_period(self):
"""Period for Stochastic average calculation."""
return self._average_period.Value
@average_period.setter
def average_period(self, value):
self._average_period.Value = value
@property
def multiplier(self):
"""Standard deviation multiplier for entry."""
return self._multiplier.Value
@multiplier.setter
def multiplier(self, value):
self._multiplier.Value = value
@property
def candle_type(self):
"""Type of candles to use."""
return self._candle_type.Value
@candle_type.setter
def candle_type(self, value):
self._candle_type.Value = value
def OnStarted2(self, time):
"""Called when the strategy starts."""
super(stochastic_mean_reversion_strategy, self).OnStarted2(time)
# Create indicators
self._stochastic = StochasticOscillator()
self._stochastic.K.Length = self.k_period
self._stochastic.D.Length = self.d_period
self._stoch_average = SimpleMovingAverage()
self._stoch_average.Length = self.average_period
self._stoch_stddev = StandardDeviation()
self._stoch_stddev.Length = self.average_period
self.Indicators.Add(self._stochastic)
self.Indicators.Add(self._stoch_average)
self.Indicators.Add(self._stoch_stddev)
# Create candle subscription
subscription = self.SubscribeCandles(self.candle_type)
# Bind candle processing (manual stochastic processing inside)
subscription.Bind(self.ProcessStochastic).Start()
# Setup chart visualization if available
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._stochastic)
self.DrawOwnTrades(area)
# Enable position protection
self.StartProtection(
takeProfit=Unit(2, UnitTypes.Percent),
stopLoss=Unit(1, UnitTypes.Percent)
)
def OnReseted(self):
super(stochastic_mean_reversion_strategy, self).OnReseted()
self._prev_stoch_k_value = 0.0
def ProcessStochastic(self, candle):
if candle.State != CandleStates.Finished:
return
stoch_result = process_candle(self._stochastic, candle)
if not self._stochastic.IsFormed:
return
k_value = stoch_result.K
if k_value is None:
return
k_value = float(k_value)
# Process Stochastic %K through average and standard deviation indicators
stoch_avg_value = float(process_float(self._stoch_average, k_value, candle.OpenTime, True))
stoch_stddev_value = float(process_float(self._stoch_stddev, k_value, candle.OpenTime, True))
if not self._stoch_average.IsFormed or not self._stoch_stddev.IsFormed:
self._prev_stoch_k_value = k_value
return
effective_stddev = max(1.0, stoch_stddev_value)
upper_band = stoch_avg_value + self.multiplier * effective_stddev
lower_band = stoch_avg_value - self.multiplier * effective_stddev
# Entry logic - only when flat
if self.Position == 0:
if k_value < lower_band or k_value < 20.0:
self.BuyMarket()
elif k_value > upper_band or k_value > 80.0:
self.SellMarket()
self._prev_stoch_k_value = k_value
def CreateClone(self):
"""!! REQUIRED!! Creates a new instance of the strategy."""
return stochastic_mean_reversion_strategy()