Стратегия Delta SMA 1-Year High Low
Стратегия Delta SMA 1-Year High Low вычисляет дельту объёма (покупки минус продажи) и её простое скользящее среднее. Длинная позиция открывается, когда SMA дельты была очень низкой и пересекает ноль снизу вверх. Позиция закрывается, когда SMA дельты падает ниже 60% своего годового максимума после предыдущего пересечения 70% этого максимума.
Детали
- Условия входа: SMA дельты была ниже 70% годового минимума и пересекает ноль вверх.
- Длинные/короткие: только long.
- Условия выхода: SMA дельты падает ниже 60% годового максимума после пересечения 70%.
- Стопы: нет.
- Значения по умолчанию:
DeltaSmaLength = 14CandleType = TimeSpan.FromMinutes(5).TimeFrame()
- Фильтры:
- Категория: Volume
- Направление: Лонг
- Индикаторы: SMA, Highest, Lowest
- Стопы: Нет
- Сложность: Средняя
- Таймфрейм: Дневной
- Сезонность: Нет
- Нейросети: Нет
- Дивергенция: Нет
- Уровень риска: Средний
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>
/// Delta SMA 1-year high/low strategy.
/// The candle volume delta is buy minus sell volume (the candle volume signed by its direction when the split is unknown),
/// smoothed by an SMA. Its high and low are tracked over the last year of available history.
/// After the delta SMA has been below 70% of that low, a cross above zero opens a long.
/// After the delta SMA has risen above 70% of the high, a drop below 60% of the high closes the long.
/// </summary>
public class DeltaSma1YearHighLowStrategy : Strategy
{
private static readonly TimeSpan _window = TimeSpan.FromDays(365);
private readonly StrategyParam<int> _deltaSmaLength;
private readonly StrategyParam<DataType> _candleType;
private SimpleMovingAverage _deltaSma;
private readonly LinkedList<(DateTime time, decimal value)> _maxQueue = new();
private readonly LinkedList<(DateTime time, decimal value)> _minQueue = new();
private decimal? _prevDeltaSma;
private bool _wasLow;
private bool _wasHigh;
/// <summary>
/// SMA length of the volume delta.
/// </summary>
public int DeltaSmaLength
{
get => _deltaSmaLength.Value;
set => _deltaSmaLength.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public DeltaSma1YearHighLowStrategy()
{
_deltaSmaLength = Param(nameof(DeltaSmaLength), 14)
.SetGreaterThanZero()
.SetDisplay("Delta SMA Length", "SMA length of the volume delta", "Indicators");
_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();
ResetState();
}
private void ResetState()
{
_maxQueue.Clear();
_minQueue.Clear();
_prevDeltaSma = null;
_wasLow = false;
_wasHigh = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetState();
_deltaSma = new SimpleMovingAverage { Length = DeltaSmaLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var delta = candle.BuyVolume is decimal buy && candle.SellVolume is decimal sell
? buy - sell
: candle.ClosePrice > candle.OpenPrice ? candle.TotalVolume
: candle.ClosePrice < candle.OpenPrice ? -candle.TotalVolume
: 0m;
var smaValue = _deltaSma.Process(delta, candle.ServerTime, true);
if (!smaValue.IsFormed)
return;
var deltaSma = smaValue.GetValue<decimal>();
var time = candle.OpenTime;
Push(_maxQueue, time, deltaSma, true);
Push(_minQueue, time, deltaSma, false);
var yearHigh = _maxQueue.First.Value.value;
var yearLow = _minQueue.First.Value.value;
var prev = _prevDeltaSma;
_prevDeltaSma = deltaSma;
if (deltaSma < yearLow * 0.7m)
_wasLow = true;
if (Position > 0 && deltaSma > yearHigh * 0.7m)
_wasHigh = true;
if (prev is not decimal prevDeltaSma)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (Position == 0 && _wasLow && prevDeltaSma <= 0m && deltaSma > 0m)
{
BuyMarket();
_wasLow = false;
_wasHigh = false;
}
else if (Position > 0 && _wasHigh && deltaSma < yearHigh * 0.6m)
{
SellMarket(Position);
_wasHigh = false;
}
}
private static void Push(LinkedList<(DateTime time, decimal value)> queue, DateTime time, decimal value, bool isMax)
{
// Monotonic queue: the front always holds the extreme of the window.
while (queue.Last != null && (isMax ? queue.Last.Value.value <= value : queue.Last.Value.value >= value))
queue.RemoveLast();
queue.AddLast((time, value));
while (queue.First != null && queue.First.Value.time <= time - _window)
queue.RemoveFirst();
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from collections import deque
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
WINDOW_TICKS = TimeSpan.FromDays(365).Ticks
class delta_sma_1_year_high_low_strategy(Strategy):
"""
Delta SMA 1-year high/low strategy.
The candle volume delta is buy minus sell volume (the candle volume signed by its direction when the split is unknown),
smoothed by an SMA. Its high and low are tracked over the last year of available history.
After the delta SMA has been below 70% of that low, a cross above zero opens a long.
After the delta SMA has risen above 70% of the high, a drop below 60% of the high closes the long.
"""
def __init__(self):
super(delta_sma_1_year_high_low_strategy, self).__init__()
self._delta_sma_length = self.Param("DeltaSmaLength", 14) \
.SetGreaterThanZero() \
.SetDisplay("Delta SMA Length", "SMA length of the volume delta", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._delta_sma = None
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
return [(self.Security, self.candle_type)]
def _reset_state(self):
self._max_queue = deque()
self._min_queue = deque()
self._prev_delta_sma = None
self._was_low = False
self._was_high = False
def OnReseted(self):
super(delta_sma_1_year_high_low_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(delta_sma_1_year_high_low_strategy, self).OnStarted2(time)
self._reset_state()
self._delta_sma = SimpleMovingAverage()
self._delta_sma.Length = self._delta_sma_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
volume = float(candle.TotalVolume)
if candle.BuyVolume is not None and candle.SellVolume is not None:
delta = float(candle.BuyVolume) - float(candle.SellVolume)
elif candle.ClosePrice > candle.OpenPrice:
delta = volume
elif candle.ClosePrice < candle.OpenPrice:
delta = -volume
else:
delta = 0.0
sma_value = process_float(self._delta_sma, delta, candle.ServerTime, True)
if not sma_value.IsFormed:
return
delta_sma = float(sma_value.GetValue[Decimal](None))
ticks = candle.OpenTime.Ticks
self._push(self._max_queue, ticks, delta_sma, True)
self._push(self._min_queue, ticks, delta_sma, False)
year_high = self._max_queue[0][1]
year_low = self._min_queue[0][1]
prev = self._prev_delta_sma
self._prev_delta_sma = delta_sma
if delta_sma < year_low * 0.7:
self._was_low = True
if self.Position > 0 and delta_sma > year_high * 0.7:
self._was_high = True
if prev is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
if self.Position == 0 and self._was_low and prev <= 0 and delta_sma > 0:
self.BuyMarket()
self._was_low = False
self._was_high = False
elif self.Position > 0 and self._was_high and delta_sma < year_high * 0.6:
self.SellMarket(self.Position)
self._was_high = False
@staticmethod
def _push(queue, ticks, value, is_max):
# Monotonic queue: the front always holds the extreme of the window.
while queue and (queue[-1][1] <= value if is_max else queue[-1][1] >= value):
queue.pop()
queue.append((ticks, value))
while queue and queue[0][0] <= ticks - WINDOW_TICKS:
queue.popleft()
def CreateClone(self):
return delta_sma_1_year_high_low_strategy()