Cumulative Delta Breakout
Cumulative Delta sums the difference between buy and sell volume. This strategy watches the running total and trades when it breaks above its highest value or below its lowest value within the lookback period.
Testing indicates an average annual return of about 49%. It performs best in the crypto market.
A break of cumulative delta often precedes price follow-through. The strategy closes trades when delta crosses back through zero or a stop-loss level.
Details
- Entry Criteria: Cumulative delta exceeds highest or lowest value in lookback.
- Long/Short: Both directions.
- Exit Criteria: Delta crosses zero or stop.
- Stops: Yes.
- Default Values:
LookbackPeriod= 20CandleType= TimeSpan.FromMinutes(5)
- Filters:
- Category: Breakout
- Direction: Both
- Indicators: Cumulative Delta
- Stops: Yes
- Complexity: Intermediate
- Timeframe: Intraday
- Seasonality: No
- Neural Networks: No
- Divergence: No
- Risk Level: Medium
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>
/// Cumulative Delta Breakout strategy.
/// Estimates delta from candle direction and volume.
/// Long: Cumulative delta rising and price above SMA.
/// Short: Cumulative delta falling and price below SMA.
/// </summary>
public class CumulativeDeltaBreakoutStrategy : Strategy
{
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _cooldownBars;
private decimal _cumulativeDelta;
private decimal _prevDelta;
private int _cooldown;
/// <summary>
/// MA Period.
/// </summary>
public int MAPeriod
{
get => _maPeriod.Value;
set => _maPeriod.Value = value;
}
/// <summary>
/// Candle type for strategy calculation.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Cooldown bars between trades.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Initialize <see cref="CumulativeDeltaBreakoutStrategy"/>.
/// </summary>
public CumulativeDeltaBreakoutStrategy()
{
_maPeriod = Param(nameof(MAPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("MA Period", "Period for SMA", "Indicators")
.SetOptimize(10, 50, 10);
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_cooldownBars = Param(nameof(CooldownBars), 500)
.SetRange(1, 1000)
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_cumulativeDelta = default;
_prevDelta = default;
_cooldown = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_cumulativeDelta = 0;
_prevDelta = 0;
_cooldown = 0;
var sma = new SimpleMovingAverage { Length = MAPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(sma, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, sma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal smaValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
// Estimate delta from candle: bullish candle adds volume, bearish subtracts
var delta = candle.ClosePrice >= candle.OpenPrice
? candle.TotalVolume
: -candle.TotalVolume;
_cumulativeDelta += delta;
if (_prevDelta == 0)
{
_prevDelta = _cumulativeDelta;
return;
}
if (_cooldown > 0)
{
_cooldown--;
_prevDelta = _cumulativeDelta;
return;
}
var deltaRising = _cumulativeDelta > _prevDelta;
if (Position == 0)
{
if (deltaRising && candle.ClosePrice > smaValue)
{
BuyMarket();
_cooldown = CooldownBars;
}
else if (!deltaRising && candle.ClosePrice < smaValue)
{
SellMarket();
_cooldown = CooldownBars;
}
}
else if (Position > 0 && !deltaRising)
{
SellMarket();
_cooldown = CooldownBars;
}
else if (Position < 0 && deltaRising)
{
BuyMarket();
_cooldown = CooldownBars;
}
_prevDelta = _cumulativeDelta;
}
}
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
from StockSharp.Algo.Strategies import Strategy
class cumulative_delta_breakout_strategy(Strategy):
"""
Cumulative Delta Breakout strategy.
Estimates delta from candle direction and volume.
Long: Cumulative delta rising and price above SMA.
Short: Cumulative delta falling and price below SMA.
"""
def __init__(self):
super(cumulative_delta_breakout_strategy, self).__init__()
self._ma_period = self.Param("MAPeriod", 20).SetDisplay("MA Period", "Period for SMA", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._cooldown_bars = self.Param("CooldownBars", 500).SetDisplay("Cooldown Bars", "Bars to wait between trades", "General")
self._cumulative_delta = 0.0
self._prev_delta = 0.0
self._cooldown = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(cumulative_delta_breakout_strategy, self).OnReseted()
self._cumulative_delta = 0.0
self._prev_delta = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(cumulative_delta_breakout_strategy, self).OnStarted2(time)
self._cumulative_delta = 0.0
self._prev_delta = 0.0
self._cooldown = 0
sma = SimpleMovingAverage()
sma.Length = self._ma_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(sma, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, sma)
self.DrawOwnTrades(area)
def _process_candle(self, candle, sma_val):
if candle.State != CandleStates.Finished:
return
# Estimate delta from candle: bullish adds volume, bearish subtracts
vol = float(candle.TotalVolume)
if float(candle.ClosePrice) >= float(candle.OpenPrice):
delta = vol
else:
delta = -vol
self._cumulative_delta += delta
if self._prev_delta == 0:
self._prev_delta = self._cumulative_delta
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_delta = self._cumulative_delta
return
delta_rising = self._cumulative_delta > self._prev_delta
close = float(candle.ClosePrice)
sv = float(sma_val)
cd = self._cooldown_bars.Value
if self.Position == 0:
if delta_rising and close > sv:
self.BuyMarket()
self._cooldown = cd
elif not delta_rising and close < sv:
self.SellMarket()
self._cooldown = cd
elif self.Position > 0 and not delta_rising:
self.SellMarket()
self._cooldown = cd
elif self.Position < 0 and delta_rising:
self.BuyMarket()
self._cooldown = cd
self._prev_delta = self._cumulative_delta
def CreateClone(self):
return cumulative_delta_breakout_strategy()