Bitcoin Liquidity Breakout Strategy
This strategy enters long positions when liquidity and volatility are high and the short-term trend is bullish. High liquidity is defined as volume above its moving average multiplied by a threshold. Volatility is confirmed when ATR exceeds its moving average.
Details
- Entry Criteria:
Volume > SMA(volume) * LiquidityThresholdPrice change (%) > PriceChangeThresholdFast SMA > Slow SMARSI < 65ATR > SMA(ATR,10)
- Long/Short: Long only.
- Exit Criteria: Fast SMA crossing below slow SMA or RSI > 70.
- Stops: Optional stop-loss and take-profit percentages.
- Default Values:
LiquidityThreshold= 1.3PriceChangeThreshold= 1.5VolatilityPeriod= 14LiquidityPeriod= 20FastMaPeriod= 9SlowMaPeriod= 21RsiPeriod= 14StopLossPercent= 0.5TakeProfitPercent= 7
- Filters:
- Category: Breakout
- Direction: Long
- Indicators: SMA, RSI, ATR
- Stops: Yes
- Complexity: Medium
- Timeframe: 1h
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>
/// Bitcoin Liquidity Breakout strategy.
/// Long only: buys when volume exceeds LiquidityThreshold times its SMA(LiquidityPeriod), the close has risen more than
/// PriceChangeThreshold percent from the previous close, SMA(FastMaPeriod) is above SMA(SlowMaPeriod), RSI(RsiPeriod) is below 65
/// and ATR(VolatilityPeriod) is above its 10-bar SMA. The long closes when the fast SMA crosses below the slow SMA or RSI rises
/// above 70; percent stop loss and take profit protect it (0 disables either).
/// </summary>
public class BitcoinLiquidityBreakoutStrategy : Strategy
{
private const decimal _rsiEntryMax = 65m;
private const decimal _rsiExit = 70m;
private const int _atrSmaLength = 10;
private readonly StrategyParam<decimal> _liquidityThreshold;
private readonly StrategyParam<decimal> _priceChangeThreshold;
private readonly StrategyParam<int> _volatilityPeriod;
private readonly StrategyParam<int> _liquidityPeriod;
private readonly StrategyParam<int> _fastMaPeriod;
private readonly StrategyParam<int> _slowMaPeriod;
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<decimal> _takeProfitPercent;
private readonly StrategyParam<DataType> _candleType;
private SimpleMovingAverage _volumeSma;
private SimpleMovingAverage _atrSma;
private decimal? _prevClose;
private decimal? _prevFast;
private decimal? _prevSlow;
/// <summary>
/// Multiple of the volume SMA that marks high liquidity.
/// </summary>
public decimal LiquidityThreshold { get => _liquidityThreshold.Value; set => _liquidityThreshold.Value = value; }
/// <summary>
/// Minimum close-to-close rise in percent.
/// </summary>
public decimal PriceChangeThreshold { get => _priceChangeThreshold.Value; set => _priceChangeThreshold.Value = value; }
/// <summary>
/// ATR period.
/// </summary>
public int VolatilityPeriod { get => _volatilityPeriod.Value; set => _volatilityPeriod.Value = value; }
/// <summary>
/// Volume SMA period.
/// </summary>
public int LiquidityPeriod { get => _liquidityPeriod.Value; set => _liquidityPeriod.Value = value; }
/// <summary>
/// Fast SMA period.
/// </summary>
public int FastMaPeriod { get => _fastMaPeriod.Value; set => _fastMaPeriod.Value = value; }
/// <summary>
/// Slow SMA period.
/// </summary>
public int SlowMaPeriod { get => _slowMaPeriod.Value; set => _slowMaPeriod.Value = value; }
/// <summary>
/// RSI period.
/// </summary>
public int RsiPeriod { get => _rsiPeriod.Value; set => _rsiPeriod.Value = value; }
/// <summary>
/// Stop loss in percent.
/// </summary>
public decimal StopLossPercent { get => _stopLossPercent.Value; set => _stopLossPercent.Value = value; }
/// <summary>
/// Take profit in percent.
/// </summary>
public decimal TakeProfitPercent { get => _takeProfitPercent.Value; set => _takeProfitPercent.Value = value; }
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
/// <summary>
/// Constructor.
/// </summary>
public BitcoinLiquidityBreakoutStrategy()
{
_liquidityThreshold = Param(nameof(LiquidityThreshold), 1.3m)
.SetGreaterThanZero()
.SetDisplay("Liquidity Threshold", "Multiple of the volume SMA that marks high liquidity", "Entry");
_priceChangeThreshold = Param(nameof(PriceChangeThreshold), 1.5m)
.SetDisplay("Price Change Threshold %", "Minimum close-to-close rise", "Entry");
_volatilityPeriod = Param(nameof(VolatilityPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("Volatility Period", "ATR period", "Indicators");
_liquidityPeriod = Param(nameof(LiquidityPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Liquidity Period", "Volume SMA period", "Indicators");
_fastMaPeriod = Param(nameof(FastMaPeriod), 9)
.SetGreaterThanZero()
.SetDisplay("Fast MA Period", "Fast SMA period", "Indicators");
_slowMaPeriod = Param(nameof(SlowMaPeriod), 21)
.SetGreaterThanZero()
.SetDisplay("Slow MA Period", "Slow SMA period", "Indicators");
_rsiPeriod = Param(nameof(RsiPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Period", "RSI period", "Indicators");
_stopLossPercent = Param(nameof(StopLossPercent), 0.5m)
.SetNotNegative()
.SetDisplay("Stop Loss %", "Stop loss in percent", "Risk");
_takeProfitPercent = Param(nameof(TakeProfitPercent), 7m)
.SetNotNegative()
.SetDisplay("Take Profit %", "Take profit in percent", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).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();
_volumeSma = null;
_atrSma = null;
_prevClose = null;
_prevFast = null;
_prevSlow = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevClose = null;
_prevFast = null;
_prevSlow = null;
_volumeSma = new SimpleMovingAverage { Length = LiquidityPeriod };
_atrSma = new SimpleMovingAverage { Length = _atrSmaLength };
var fast = new SimpleMovingAverage { Length = FastMaPeriod };
var slow = new SimpleMovingAverage { Length = SlowMaPeriod };
var rsi = new RelativeStrengthIndex { Length = RsiPeriod };
var atr = new AverageTrueRange { Length = VolatilityPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(fast, slow, rsi, atr, ProcessCandle)
.Start();
var take = TakeProfitPercent > 0 ? new Unit(TakeProfitPercent, UnitTypes.Percent) : new Unit();
var stop = StopLossPercent > 0 ? new Unit(StopLossPercent, UnitTypes.Percent) : new Unit();
StartProtection(take, stop, useMarketOrders: true);
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, fast);
DrawIndicator(area, slow);
DrawOwnTrades(area);
var oscillators = CreateChartArea();
if (oscillators != null)
DrawIndicator(oscillators, rsi);
}
}
private void ProcessCandle(ICandleMessage candle, decimal fast, decimal slow, decimal rsi, decimal atr)
{
if (candle.State != CandleStates.Finished)
return;
var volumeAverage = _volumeSma.Process(candle.TotalVolume, candle.ServerTime, true).ToDecimal();
var atrAverage = _atrSma.Process(atr, candle.ServerTime, true).ToDecimal();
var close = candle.ClosePrice;
var prevClose = _prevClose;
var prevFast = _prevFast;
var prevSlow = _prevSlow;
_prevClose = close;
_prevFast = fast;
_prevSlow = slow;
if (!_volumeSma.IsFormed || !_atrSma.IsFormed || prevClose is not decimal pc || pc <= 0)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (Position > 0)
{
var crossDown = prevFast is decimal pf && prevSlow is decimal ps && pf >= ps && fast < slow;
if (crossDown || rsi > _rsiExit)
SellMarket(Position);
return;
}
var priceChange = (close - pc) / pc * 100m;
if (Position == 0
&& candle.TotalVolume > volumeAverage * LiquidityThreshold
&& priceChange > PriceChangeThreshold
&& fast > slow
&& rsi < _rsiEntryMax
&& atr > atrAverage)
{
BuyMarket(Volume);
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import SimpleMovingAverage, RelativeStrengthIndex, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
RSI_ENTRY_MAX = 65
RSI_EXIT = 70
ATR_SMA_LENGTH = 10
class bitcoin_liquidity_breakout_strategy(Strategy):
"""
Bitcoin Liquidity Breakout strategy.
Long only: buys when volume exceeds LiquidityThreshold times its SMA(LiquidityPeriod), the close has risen more than
PriceChangeThreshold percent from the previous close, SMA(FastMaPeriod) is above SMA(SlowMaPeriod), RSI(RsiPeriod) is below 65
and ATR(VolatilityPeriod) is above its 10-bar SMA. The long closes when the fast SMA crosses below the slow SMA or RSI rises
above 70; percent stop loss and take profit protect it (0 disables either).
"""
def __init__(self):
super(bitcoin_liquidity_breakout_strategy, self).__init__()
self._liquidity_threshold = self.Param("LiquidityThreshold", 1.3).SetGreaterThanZero().SetDisplay("Liquidity Threshold", "Multiple of the volume SMA that marks high liquidity", "Entry")
self._price_change_threshold = self.Param("PriceChangeThreshold", 1.5).SetDisplay("Price Change Threshold %", "Minimum close-to-close rise", "Entry")
self._volatility_period = self.Param("VolatilityPeriod", 14).SetGreaterThanZero().SetDisplay("Volatility Period", "ATR period", "Indicators")
self._liquidity_period = self.Param("LiquidityPeriod", 20).SetGreaterThanZero().SetDisplay("Liquidity Period", "Volume SMA period", "Indicators")
self._fast_ma_period = self.Param("FastMaPeriod", 9).SetGreaterThanZero().SetDisplay("Fast MA Period", "Fast SMA period", "Indicators")
self._slow_ma_period = self.Param("SlowMaPeriod", 21).SetGreaterThanZero().SetDisplay("Slow MA Period", "Slow SMA period", "Indicators")
self._rsi_period = self.Param("RsiPeriod", 14).SetGreaterThanZero().SetDisplay("RSI Period", "RSI period", "Indicators")
self._stop_loss_percent = self.Param("StopLossPercent", 0.5).SetNotNegative().SetDisplay("Stop Loss %", "Stop loss in percent", "Risk")
self._take_profit_percent = self.Param("TakeProfitPercent", 7.0).SetNotNegative().SetDisplay("Take Profit %", "Take profit in percent", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def _reset_state(self):
self._volume_sma = None
self._atr_sma = None
self._prev_close = None
self._prev_fast = None
self._prev_slow = None
def OnReseted(self):
super(bitcoin_liquidity_breakout_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(bitcoin_liquidity_breakout_strategy, self).OnStarted2(time)
self._reset_state()
self._volume_sma = SimpleMovingAverage()
self._volume_sma.Length = self._liquidity_period.Value
self._atr_sma = SimpleMovingAverage()
self._atr_sma.Length = ATR_SMA_LENGTH
fast = SimpleMovingAverage()
fast.Length = self._fast_ma_period.Value
slow = SimpleMovingAverage()
slow.Length = self._slow_ma_period.Value
rsi = RelativeStrengthIndex()
rsi.Length = self._rsi_period.Value
atr = AverageTrueRange()
atr.Length = self._volatility_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(fast, slow, rsi, atr, self._process_candle).Start()
tp = self._take_profit_percent.Value
sl = self._stop_loss_percent.Value
take = Unit(Decimal(tp), UnitTypes.Percent) if tp > 0 else Unit()
stop = Unit(Decimal(sl), UnitTypes.Percent) if sl > 0 else Unit()
self.StartProtection(take, stop, useMarketOrders=True)
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, fast)
self.DrawIndicator(area, slow)
self.DrawOwnTrades(area)
oscillators = self.CreateChartArea()
if oscillators is not None:
self.DrawIndicator(oscillators, rsi)
def _process_candle(self, candle, fast, slow, rsi, atr):
if candle.State != CandleStates.Finished:
return
volume_average = process_float(self._volume_sma, candle.TotalVolume, candle.ServerTime, True).GetValue[Decimal](None)
atr_average = process_float(self._atr_sma, atr, candle.ServerTime, True).GetValue[Decimal](None)
close = candle.ClosePrice
prev_close = self._prev_close
prev_fast = self._prev_fast
prev_slow = self._prev_slow
self._prev_close = close
self._prev_fast = fast
self._prev_slow = slow
if not self._volume_sma.IsFormed or not self._atr_sma.IsFormed or prev_close is None or prev_close <= 0:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
if self.Position > 0:
cross_down = prev_fast is not None and prev_slow is not None and prev_fast >= prev_slow and fast < slow
if cross_down or rsi > RSI_EXIT:
self.SellMarket(self.Position)
return
price_change = (close - prev_close) / prev_close * Decimal(100)
if (self.Position == 0
and candle.TotalVolume > volume_average * Decimal(self._liquidity_threshold.Value)
and price_change > Decimal(self._price_change_threshold.Value)
and fast > slow
and rsi < RSI_ENTRY_MAX
and atr > atr_average):
self.BuyMarket(self.Volume)
def CreateClone(self):
return bitcoin_liquidity_breakout_strategy()