Стратегия Z-Score Buy Sell
Стратегия использует Z-score для определения экстремальных отклонений от скользящей средней. Позиции открываются при превышении пороговых значений Z-score, а период ожидания предотвращает повторные сигналы.
Подробности
- Условия входа:
- Шорт при z-score >
ZThresholdи завершённом ожидании продажи. - Лонг при z-score < -
ZThresholdи завершённом ожидании покупки.
- Шорт при z-score >
- Направление: Лонг и шорт.
- Условия выхода:
- Обратный сигнал.
- Стопы: Нет.
- Значения по умолчанию:
RollingWindow= 80ZThreshold= 2.8CoolDown= 5CandleType= TimeSpan.FromMinutes(1)
- Фильтры:
- Категория: Средняя реверсия
- Направление: Оба
- Индикаторы: SMA, StandardDeviation, Z-Score
- Стопы: Нет
- Сложность: Базовая
- Таймфрейм: Любой
- Сезонность: Нет
- Нейросети: Нет
- Дивергенция: Нет
- Уровень риска: Средний
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>
/// Z-Score mean reversion strategy with cooldown between signals.
/// </summary>
public class ZScoreBuySellStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _rollingWindow;
private readonly StrategyParam<decimal> _zThreshold;
private readonly StrategyParam<int> _coolDown;
private int _buyCooldownCounter;
private int _sellCooldownCounter;
/// <summary>
/// Candle type for strategy calculation.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Period for moving average and standard deviation.
/// </summary>
public int RollingWindow
{
get => _rollingWindow.Value;
set => _rollingWindow.Value = value;
}
/// <summary>
/// Absolute z-score level to trigger trades.
/// </summary>
public decimal ZThreshold
{
get => _zThreshold.Value;
set => _zThreshold.Value = value;
}
/// <summary>
/// Bars to wait after trade.
/// </summary>
public int CoolDown
{
get => _coolDown.Value;
set => _coolDown.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="ZScoreBuySellStrategy"/>.
/// </summary>
public ZScoreBuySellStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
_rollingWindow = Param(nameof(RollingWindow), 300)
.SetGreaterThanZero()
.SetDisplay("Rolling Window", "Lookback period", "Parameters")
.SetOptimize(20, 150, 10);
_zThreshold = Param(nameof(ZThreshold), 2.8m)
.SetGreaterThanZero()
.SetDisplay("Z Threshold", "Z-score trigger level", "Parameters")
.SetOptimize(1m, 5m, 0.5m);
_coolDown = Param(nameof(CoolDown), 50)
.SetGreaterThanZero()
.SetDisplay("Cool Down", "Bars to wait after trade", "Parameters");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_buyCooldownCounter = _sellCooldownCounter = CoolDown;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var sma = new SimpleMovingAverage { Length = RollingWindow };
var stdDev = new StandardDeviation { Length = RollingWindow };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(sma, stdDev, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, sma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal smaValue, decimal stdDevValue)
{
if (candle.State != CandleStates.Finished)
return;
if (stdDevValue == 0)
return;
var zScore = (candle.ClosePrice - smaValue) / stdDevValue;
if (zScore > ZThreshold)
{
if (_sellCooldownCounter >= CoolDown)
{
if (Position >= 0)
SellMarket();
_sellCooldownCounter = 0;
_buyCooldownCounter = CoolDown;
}
else
{
_sellCooldownCounter++;
}
}
else if (zScore < -ZThreshold)
{
if (_buyCooldownCounter >= CoolDown)
{
if (Position <= 0)
BuyMarket();
_sellCooldownCounter = CoolDown;
_buyCooldownCounter = 0;
}
else
{
_buyCooldownCounter++;
}
}
}
}
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 z_score_buy_sell_strategy(Strategy):
def __init__(self):
super(z_score_buy_sell_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._rolling_window = self.Param("RollingWindow", 300) \
.SetGreaterThanZero() \
.SetDisplay("Rolling Window", "Lookback period", "Parameters")
self._z_threshold = self.Param("ZThreshold", 2.8) \
.SetGreaterThanZero() \
.SetDisplay("Z Threshold", "Z-score trigger level", "Parameters")
self._cool_down = self.Param("CoolDown", 50) \
.SetGreaterThanZero() \
.SetDisplay("Cool Down", "Bars to wait after trade", "Parameters")
self._buy_cooldown_counter = 0
self._sell_cooldown_counter = 0
@property
def candle_type(self):
return self._candle_type.Value
@candle_type.setter
def candle_type(self, value):
self._candle_type.Value = value
def OnReseted(self):
super(z_score_buy_sell_strategy, self).OnReseted()
cd = self._cool_down.Value
self._buy_cooldown_counter = cd
self._sell_cooldown_counter = cd
def OnStarted2(self, time):
super(z_score_buy_sell_strategy, self).OnStarted2(time)
sma = SimpleMovingAverage()
sma.Length = self._rolling_window.Value
std_dev = StandardDeviation()
std_dev.Length = self._rolling_window.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(sma, std_dev, self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, sma)
self.DrawOwnTrades(area)
def OnProcess(self, candle, sma_val, std_dev_val):
if candle.State != CandleStates.Finished:
return
std_v = float(std_dev_val)
if std_v == 0:
return
close = float(candle.ClosePrice)
sma_v = float(sma_val)
z_score = (close - sma_v) / std_v
z_thresh = float(self._z_threshold.Value)
cd = self._cool_down.Value
if z_score > z_thresh:
if self._sell_cooldown_counter >= cd:
if self.Position >= 0:
self.SellMarket()
self._sell_cooldown_counter = 0
self._buy_cooldown_counter = cd
else:
self._sell_cooldown_counter += 1
elif z_score < -z_thresh:
if self._buy_cooldown_counter >= cd:
if self.Position <= 0:
self.BuyMarket()
self._sell_cooldown_counter = cd
self._buy_cooldown_counter = 0
else:
self._buy_cooldown_counter += 1
def CreateClone(self):
return z_score_buy_sell_strategy()