Delta SMA 1年高値安値戦略
Delta SMA 1年高値安値戦略は、出来高デルタ(買い出来高マイナス売り出来高)とその単純移動平均を計算します。デルタSMAが非常に低い状態からゼロを上抜けたときにロングを建てます。以前に1年高値の70%を上抜けた後、デルタSMAが1年高値の60%を下回ったときにポジションを決済します。
詳細
- エントリー条件: デルタSMAが1年安値の70%を下回った後、ゼロを上抜ける。
- ロング/ショート: ロングのみ。
- エグジット条件: デルタSMAが70%を超えた後、1年高値の60%を下回る。
- ストップ: なし。
- デフォルト値:
DeltaSmaLength = 14CandleType = TimeSpan.FromMinutes(5).TimeFrame()
- フィルター:
- カテゴリ: 出来高
- 方向: ロング
- インジケーター: 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()