Linear Mean Reversion Strategy
Linear Mean Reversion Strategy 利用价格相对于均值的 z 分数进行均值回归交易,并使用固定点数止损。
细节
- 数据:价格K线。
- 入场条件:
- 多头:z-score < -EntryThreshold。
- 空头:z-score > EntryThreshold。
- 出场条件:z-score 回到零附近(多头 z-score > -ExitThreshold,空头 z-score < ExitThreshold)。
- 止损:固定点数止损。
- 默认值:
HalfLife= 14Scale= 1EntryThreshold= 2ExitThreshold= 0.2StopLossPoints= 50
- 过滤器:
- 类别:均值回归
- 方向:多头 & 空头
- 指标:SMA, StandardDeviation
- 止损:是
- 复杂度:低
- 风险等级:中等
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Linear mean reversion strategy based on price z-score.
/// Buys when price is below the lower z-score threshold and sells when above the upper threshold.
/// Exits when z-score moves back toward zero or a fixed stop loss is hit.
/// </summary>
public class LinearMeanReversionStrategy : Strategy
{
private readonly StrategyParam<int> _halfLife;
private readonly StrategyParam<decimal> _scale;
private readonly StrategyParam<decimal> _entryThreshold;
private readonly StrategyParam<decimal> _exitThreshold;
private readonly StrategyParam<decimal> _stopLossPoints;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<DataType> _candleType;
private decimal _entryPrice;
private int _barsFromTrade;
/// <summary>
/// Lookback window for moving average and standard deviation.
/// </summary>
public int HalfLife { get => _halfLife.Value; set => _halfLife.Value = value; }
/// <summary>
/// Position scaling factor.
/// </summary>
public decimal Scale { get => _scale.Value; set => _scale.Value = value; }
/// <summary>
/// Z-score threshold for entries.
/// </summary>
public decimal EntryThreshold { get => _entryThreshold.Value; set => _entryThreshold.Value = value; }
/// <summary>
/// Z-score threshold for exits.
/// </summary>
public decimal ExitThreshold { get => _exitThreshold.Value; set => _exitThreshold.Value = value; }
/// <summary>
/// Fixed stop loss in price points.
/// </summary>
public decimal StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
/// <summary>
/// Minimum bars between trade actions.
/// </summary>
public int CooldownBars { get => _cooldownBars.Value; set => _cooldownBars.Value = value; }
/// <summary>
/// Type of candles used.
/// </summary>
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
/// <summary>
/// Constructor.
/// </summary>
public LinearMeanReversionStrategy()
{
_halfLife = Param(nameof(HalfLife), 30)
.SetGreaterThanZero()
.SetDisplay("Half-Life", "Lookback window for mean and deviation", "General")
.SetOptimize(20, 60, 5);
_scale = Param(nameof(Scale), 1m)
.SetGreaterThanZero()
.SetDisplay("Scale", "Position scaling factor", "General")
.SetOptimize(1m, 3m, 1m);
_entryThreshold = Param(nameof(EntryThreshold), 2.2m)
.SetGreaterThanZero()
.SetDisplay("Entry Threshold", "Z-score entry threshold", "Parameters")
.SetOptimize(1.5m, 3.5m, 0.2m);
_exitThreshold = Param(nameof(ExitThreshold), 0.5m)
.SetGreaterThanZero()
.SetDisplay("Exit Threshold", "Z-score exit threshold", "Parameters")
.SetOptimize(0.3m, 1.2m, 0.1m);
_stopLossPoints = Param(nameof(StopLossPoints), 50m)
.SetGreaterThanZero()
.SetDisplay("Stop Loss Points", "Fixed stop loss in price points", "Risk Management")
.SetOptimize(20m, 100m, 10m);
_cooldownBars = Param(nameof(CooldownBars), 12)
.SetGreaterThanZero()
.SetDisplay("Cooldown Bars", "Minimum bars between trade actions", "Risk Management")
.SetOptimize(5, 30, 1);
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(10).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_entryPrice = 0;
_barsFromTrade = int.MaxValue;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var sma = new SMA { Length = HalfLife };
var std = new StandardDeviation { Length = HalfLife };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(sma, std, ProcessCandle)
.Start();
StartProtection(null, null);
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, sma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal mean, decimal deviation)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (deviation == 0)
return;
var zscore = (candle.ClosePrice - mean) / deviation;
var volume = Volume * Scale;
_barsFromTrade++;
if (_barsFromTrade < CooldownBars)
return;
if (volume <= 0)
return;
if (Position == 0)
{
if (zscore < -EntryThreshold)
{
BuyMarket(volume);
_entryPrice = candle.ClosePrice;
_barsFromTrade = 0;
}
else if (zscore > EntryThreshold)
{
SellMarket(volume);
_entryPrice = candle.ClosePrice;
_barsFromTrade = 0;
}
return;
}
if (Position > 0)
{
var stopPrice = _entryPrice - StopLossPoints;
if (candle.ClosePrice <= stopPrice || zscore >= -ExitThreshold)
{
SellMarket(Math.Abs(Position));
_entryPrice = 0;
_barsFromTrade = 0;
}
return;
}
if (Position < 0)
{
var stopPrice = _entryPrice + StopLossPoints;
if (candle.ClosePrice >= stopPrice || zscore <= ExitThreshold)
{
BuyMarket(Math.Abs(Position));
_entryPrice = 0;
_barsFromTrade = 0;
}
}
}
}
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 SimpleMovingAverage, StandardDeviation
from StockSharp.Algo.Strategies import Strategy
class linear_mean_reversion_strategy(Strategy):
"""
Linear mean reversion based on price z-score.
Buys when z-score below entry threshold, sells above. Exits near zero.
"""
def __init__(self):
super(linear_mean_reversion_strategy, self).__init__()
self._half_life = self.Param("HalfLife", 30) \
.SetDisplay("Half-Life", "Lookback window", "General")
self._scale = self.Param("Scale", 1.0) \
.SetDisplay("Scale", "Position scaling factor", "General")
self._entry_threshold = self.Param("EntryThreshold", 2.2) \
.SetDisplay("Entry Threshold", "Z-score entry threshold", "Parameters")
self._exit_threshold = self.Param("ExitThreshold", 0.5) \
.SetDisplay("Exit Threshold", "Z-score exit threshold", "Parameters")
self._stop_loss_points = self.Param("StopLossPoints", 50.0) \
.SetDisplay("Stop Loss Points", "Fixed stop loss in price points", "Risk Management")
self._cooldown_bars = self.Param("CooldownBars", 12) \
.SetDisplay("Cooldown Bars", "Min bars between trade actions", "Risk Management")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(10))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._entry_price = 0.0
self._bars_from_trade = 999999
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(linear_mean_reversion_strategy, self).OnReseted()
self._entry_price = 0.0
self._bars_from_trade = 999999
def OnStarted2(self, time):
super(linear_mean_reversion_strategy, self).OnStarted2(time)
sma = SimpleMovingAverage()
sma.Length = self._half_life.Value
std = StandardDeviation()
std.Length = self._half_life.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(sma, std, self._process_candle).Start()
self.StartProtection(None, None)
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, sma)
self.DrawOwnTrades(area)
def _process_candle(self, candle, mean_val, dev_val):
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
mean = float(mean_val)
deviation = float(dev_val)
if deviation == 0:
return
close = float(candle.ClosePrice)
zscore = (close - mean) / deviation
volume = self.Volume * self._scale.Value
self._bars_from_trade += 1
if self._bars_from_trade < self._cooldown_bars.Value:
return
if volume <= 0:
return
entry_th = self._entry_threshold.Value
exit_th = self._exit_threshold.Value
sl_pts = self._stop_loss_points.Value
if self.Position == 0:
if zscore < -entry_th:
self.BuyMarket(volume)
self._entry_price = close
self._bars_from_trade = 0
elif zscore > entry_th:
self.SellMarket(volume)
self._entry_price = close
self._bars_from_trade = 0
return
if self.Position > 0:
stop = self._entry_price - sl_pts
if close <= stop or zscore >= -exit_th:
self.SellMarket(abs(self.Position))
self._entry_price = 0.0
self._bars_from_trade = 0
return
if self.Position < 0:
stop = self._entry_price + sl_pts
if close >= stop or zscore <= exit_th:
self.BuyMarket(abs(self.Position))
self._entry_price = 0.0
self._bars_from_trade = 0
def CreateClone(self):
return linear_mean_reversion_strategy()