Delta SMA 1-Year High Low 策略
Delta SMA 1-Year High Low 策略计算买卖量差值及其简单移动平均。当 delta SMA 处于极低水平后向上穿越零线时做多;当 delta SMA 先上穿年内高值的70%后又跌破该高值的60%时平仓。
细节
- 入场条件:delta SMA 低于一年最低值的70%并向上穿越零线。
- 多空方向:仅做多。
- 出场条件:delta SMA 在先上穿一年最高值的70%后跌破该最高值的60%。
- 止损:无。
- 默认值:
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()