Estrategia Delta SMA Máximo-Mínimo de 1 Año
La estrategia Delta SMA Máximo-Mínimo de 1 Año calcula el delta de volumen (volumen comprador menos vendedor) y su media móvil simple. Entra en largo cuando el delta SMA estuvo muy bajo y cruza por encima de cero. La posición se cierra cuando el delta SMA cae por debajo del 60% de su máximo de 1 año después de haber cruzado previamente el 70% de ese máximo.
Detalles
- Criterios de entrada: El delta SMA estaba por debajo del 70% de su mínimo de 1 año y cruza por encima de cero.
- Largo/Corto: Solo largos.
- Criterios de salida: El delta SMA cae por debajo del 60% de su máximo de 1 año después de cruzar el 70%.
- Stops: No.
- Valores predeterminados:
DeltaSmaLength = 14CandleType = TimeSpan.FromMinutes(5).TimeFrame()
- Filtros:
- Categoría: Volumen
- Dirección: Largo
- Indicadores: SMA, Highest, Lowest
- Stops: No
- Complejidad: Intermedio
- Marco temporal: Diario
- Estacionalidad: No
- Redes neuronales: No
- Divergencia: No
- Nivel de riesgo: Medio
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()