Dieser statistische Ansatz sucht nach kurzfristigen Extremen im Preis im Verhältnis zu seinem jüngsten Durchschnitt. Die Strategie verwendet einen gleitenden Durchschnitt zur Definition des fairen Werts und misst die Abweichung von diesem Mittelwert durch eine Standardabweichungsberechnung.
Tests zeigen eine durchschnittliche Jahresrendite von etwa 85%. Sie funktioniert am besten auf dem Kryptomarkt.
Trades werden geöffnet, wenn der Preis eine festgelegte Distanz vom Durchschnitt schiebt. Ein Einbruch unter das untere Band löst einen Long-Einstieg aus, der eine Erholung in Richtung des Mittelwerts antizipiert, während eine Rally über das obere Band einen Short veranlasst. Sobald der Preis den gleitenden Durchschnitt wieder berührt, wird eine offene Position geschlossen.
Die Methode spricht Trader mit einem konträren Stil an, die klar definierte Einstiegs- und Ausstiegszonen wünschen. Da sie auf volatilitätsbasierten Bändern basiert, passt sie sich ruhigeren oder aktiveren Märkten an, während Verluste durch einen festen Stop-Loss unter Kontrolle bleiben.
Details
Einstiegskriterien:
Long: Price < MA - k*StdDev (below lower band)
Short: Price > MA + k*StdDev (above upper band)
Long/Short: Beide Seiten.
Ausstiegskriterien:
Long: Ausstieg, wenn der Preis über den gleitenden Durchschnitt kreuzt
Short: Ausstieg, wenn der Preis unter den gleitenden Durchschnitt kreuzt
Stops: Ja.
Standardwerte:
MovingAveragePeriod = 20
DeviationMultiplier = 2.0m
StopLossPercent = 2m
CandleType = TimeSpan.FromMinutes(5)
Filter:
Kategorie: Mean Reversion
Richtung: Beide
Indikatoren: Mean Reversion
Stops: Ja
Komplexität: Mittel
Zeitrahmen: Intraday
Saisonalität: Nein
Neuronale Netze: Nein
Divergenz: Nein
Risikolevel: Mittel
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>
/// Statistical Mean Reversion strategy.
/// Enters long when price falls below the mean by a specified number of standard deviations.
/// Enters short when price rises above the mean by a specified number of standard deviations.
/// Exits positions when price returns to the mean.
/// </summary>
public class MeanReversionStrategy : Strategy
{
private readonly StrategyParam<int> _movingAveragePeriod;
private readonly StrategyParam<decimal> _deviationMultiplier;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<DataType> _candleType;
private SimpleMovingAverage _ma;
private StandardDeviation _stdDev;
private bool _wasBelowLower;
private bool _wasAboveUpper;
private int _cooldown;
/// <summary>
/// Moving average period parameter.
/// </summary>
public int MovingAveragePeriod
{
get => _movingAveragePeriod.Value;
set => _movingAveragePeriod.Value = value;
}
/// <summary>
/// Standard deviation multiplier parameter.
/// </summary>
public decimal DeviationMultiplier
{
get => _deviationMultiplier.Value;
set => _deviationMultiplier.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 MeanReversionStrategy()
{
_movingAveragePeriod = Param(nameof(MovingAveragePeriod), 20)
.SetGreaterThanZero()
.SetDisplay("MA Period", "Period for moving average calculation", "Indicators")
.SetOptimize(10, 50, 5);
_deviationMultiplier = Param(nameof(DeviationMultiplier), 2.0m)
.SetGreaterThanZero()
.SetDisplay("Deviation Multiplier", "Standard deviation multiplier for entry signals", "Indicators")
.SetOptimize(1.5m, 3.0m, 0.5m);
_cooldownBars = Param(nameof(CooldownBars), 50)
.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();
_ma = null;
_stdDev = null;
_wasBelowLower = false;
_wasAboveUpper = false;
_cooldown = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Initialize indicators
_ma = new() { Length = MovingAveragePeriod };
_stdDev = new() { Length = MovingAveragePeriod };
// Create candles subscription
var subscription = SubscribeCandles(CandleType);
// Bind indicators to subscription
subscription
.Bind(_ma, _stdDev, ProcessCandle)
.Start();
// Setup chart if available
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _ma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal maValue, decimal stdDevValue)
{
// Skip unfinished candles
if (candle.State != CandleStates.Finished)
return;
// Skip if strategy is not ready to trade
// Calculate upper and lower bands based on mean and standard deviation
decimal upperBand = maValue + (stdDevValue * DeviationMultiplier);
decimal lowerBand = maValue - (stdDevValue * DeviationMultiplier);
var isBelowLower = candle.ClosePrice < lowerBand;
var isAboveUpper = candle.ClosePrice > upperBand;
var crossedBelowLower = !_wasBelowLower && isBelowLower;
var crossedAboveUpper = !_wasAboveUpper && isAboveUpper;
_wasBelowLower = isBelowLower;
_wasAboveUpper = isAboveUpper;
if (_cooldown > 0)
_cooldown--;
// Trading logic
if (_cooldown == 0 && isBelowLower)
{
// Long signal: Price below lower band (mean - k*stdDev)
if (Position <= 0)
{
BuyMarket();
_cooldown = CooldownBars;
}
}
else if (_cooldown == 0 && isAboveUpper)
{
// Short signal: Price above upper band (mean + k*stdDev)
if (Position >= 0)
{
SellMarket();
_cooldown = CooldownBars;
}
}
else if ((Position > 0 && candle.ClosePrice > maValue) ||
(Position < 0 && candle.ClosePrice < maValue))
{
// Exit signals: Price returned to the mean
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
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import SimpleMovingAverage, StandardDeviation
from StockSharp.Algo.Strategies import Strategy
class mean_reversion_strategy(Strategy):
"""
Statistical Mean Reversion: enters when price deviates from mean by k*stddev, exits at mean.
"""
def __init__(self):
super(mean_reversion_strategy, self).__init__()
self._ma_period = self.Param("MovingAveragePeriod", 20).SetDisplay("MA Period", "SMA period", "Indicators")
self._dev_mult = self.Param("DeviationMultiplier", 2.0).SetDisplay("Dev Mult", "Stddev multiplier", "Indicators")
self._cooldown_bars = self.Param("CooldownBars", 50).SetDisplay("Cooldown Bars", "Bars between trades", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Timeframe", "General")
self._was_below_lower = False
self._was_above_upper = False
self._cooldown = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(mean_reversion_strategy, self).OnReseted()
self._was_below_lower = False
self._was_above_upper = False
self._cooldown = 0
def OnStarted2(self, time):
super(mean_reversion_strategy, self).OnStarted2(time)
ma = SimpleMovingAverage()
ma.Length = self._ma_period.Value
std_dev = StandardDeviation()
std_dev.Length = self._ma_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(ma, std_dev, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ma)
self.DrawOwnTrades(area)
def _process_candle(self, candle, ma_val, std_val):
if candle.State != CandleStates.Finished:
return
ma = float(ma_val)
std = float(std_val)
close = float(candle.ClosePrice)
dm = self._dev_mult.Value
upper = ma + std * dm
lower = ma - std * dm
is_below = close < lower
is_above = close > upper
self._was_below_lower = is_below
self._was_above_upper = is_above
if self._cooldown > 0:
self._cooldown -= 1
if self._cooldown == 0 and is_below:
if self.Position <= 0:
self.BuyMarket()
self._cooldown = self._cooldown_bars.Value
elif self._cooldown == 0 and is_above:
if self.Position >= 0:
self.SellMarket()
self._cooldown = self._cooldown_bars.Value
elif self.Position > 0 and close > ma:
self.SellMarket()
self._cooldown = self._cooldown_bars.Value
elif self.Position < 0 and close < ma:
self.BuyMarket()
self._cooldown = self._cooldown_bars.Value
def CreateClone(self):
return mean_reversion_strategy()