Adaptive Squeeze-Momentum-Strategie
Die adaptive Squeeze-Momentum-Strategie erkennt Volatilitätskontraktionen, wenn Bollinger Bänder innerhalb der Keltner Kanäle liegen, und wartet auf einen Ausbruch mit starkem Momentum. Die Momentumstärke wird anhand eines auf der Standardabweichung basierenden Schwellenwerts bewertet. Optionale RSI- und EMA-Trendfilter verfeinern die Einstiege. ATR kann zur Festlegung dynamischer Stop-Loss- und Take-Profit-Niveaus verwendet werden, und Positionen werden nach einer zeitbasierten Halteperiode geschlossen.
Details
- Einstiegskriterien:
- Squeeze löst sich (Bollinger Bänder außerhalb der Keltner Kanäle).
- Long: Momentum > dynamischer Schwellenwert, RSI kreuzt über Überverkauft, Trend-EMA steigt (optional).
- Short: Momentum < -dynamischer Schwellenwert, RSI kreuzt unter Überkauft, Trend-EMA fällt (optional).
- Long/Short: Beide Seiten.
- Ausstiegskriterien:
- Entgegengesetztes Signal, ATR-basierter Stop-Loss/Take-Profit oder zeitbasierter Ausstieg.
- Stops: Optionaler ATR Stop-Loss und Take-Profit.
- Standardwerte:
BollingerPeriod= 20BollingerMultiplier= 2.0KeltnerPeriod= 20KeltnerMultiplier= 1.5MomentumLength= 12TrendMaLength= 50UseAtrStops= TrueAtrMultiplierSl= 1.5AtrMultiplierTp= 2.5AtrLength= 14MinVolatility= 0.5HoldingPeriodMultiplier= 1.5UseTrendFilter= TrueUseRsiFilter= TrueRsiLength= 14RsiOversold= 40RsiOverbought= 60MomentumMultiplier= 1.5AllowLong= TrueAllowShort= True
- Filter:
- Kategorie: Volatilitätsausbruch
- Richtung: Beide
- Indikatoren: Bollinger Bands, Keltner Channels, Momentum, RSI, EMA, ATR
- Stops: Optional
- Komplexität: Hoch
- Zeitrahmen: Beliebig
- Saisonalität: Nein
- Neuronale Netze: Nein
- Divergenz: Nein
- Risikolevel: Mittel
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>
/// Adaptive Squeeze Momentum strategy.
/// Trades only when the squeeze is released (Bollinger Bands outside the Keltner Channel) and ATR is at least MinVolatility percent
/// of price. A long needs momentum above MomentumMultiplier standard deviations of momentum, RSI above RsiOversold and a
/// rising trend EMA; a short mirrors this with RSI below RsiOverbought and a falling EMA (both filters optional). The
/// opposite signal reverses; ATR stop-loss and take-profit levels and a holding period of HoldingPeriodMultiplier * MomentumLength
/// bars close positions.
/// </summary>
public class AdaptiveSqueezeMomentumStrategy : Strategy
{
private readonly StrategyParam<int> _bollingerPeriod;
private readonly StrategyParam<decimal> _bollingerMultiplier;
private readonly StrategyParam<int> _keltnerPeriod;
private readonly StrategyParam<decimal> _keltnerMultiplier;
private readonly StrategyParam<int> _momentumLength;
private readonly StrategyParam<int> _trendMaLength;
private readonly StrategyParam<bool> _useAtrStops;
private readonly StrategyParam<decimal> _atrMultiplierSl;
private readonly StrategyParam<decimal> _atrMultiplierTp;
private readonly StrategyParam<int> _atrLength;
private readonly StrategyParam<decimal> _minVolatility;
private readonly StrategyParam<decimal> _holdingPeriodMultiplier;
private readonly StrategyParam<bool> _useTrendFilter;
private readonly StrategyParam<bool> _useRsiFilter;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<decimal> _rsiOversold;
private readonly StrategyParam<decimal> _rsiOverbought;
private readonly StrategyParam<decimal> _momentumMultiplier;
private readonly StrategyParam<bool> _allowLong;
private readonly StrategyParam<bool> _allowShort;
private readonly StrategyParam<DataType> _candleType;
private StandardDeviation _momentumStdDev;
private decimal? _prevEma;
private decimal? _stopPrice;
private decimal? _takePrice;
private int _barsInPosition;
/// <summary>
/// Bollinger Bands period.
/// </summary>
public int BollingerPeriod
{
get => _bollingerPeriod.Value;
set => _bollingerPeriod.Value = value;
}
/// <summary>
/// Bollinger Bands standard deviation multiplier.
/// </summary>
public decimal BollingerMultiplier
{
get => _bollingerMultiplier.Value;
set => _bollingerMultiplier.Value = value;
}
/// <summary>
/// Keltner Channel period.
/// </summary>
public int KeltnerPeriod
{
get => _keltnerPeriod.Value;
set => _keltnerPeriod.Value = value;
}
/// <summary>
/// Keltner Channel ATR multiplier.
/// </summary>
public decimal KeltnerMultiplier
{
get => _keltnerMultiplier.Value;
set => _keltnerMultiplier.Value = value;
}
/// <summary>
/// Momentum period, also the window of its standard deviation.
/// </summary>
public int MomentumLength
{
get => _momentumLength.Value;
set => _momentumLength.Value = value;
}
/// <summary>
/// Period of the trend EMA.
/// </summary>
public int TrendMaLength
{
get => _trendMaLength.Value;
set => _trendMaLength.Value = value;
}
/// <summary>
/// Use ATR stop-loss and take-profit.
/// </summary>
public bool UseAtrStops
{
get => _useAtrStops.Value;
set => _useAtrStops.Value = value;
}
/// <summary>
/// ATR multiplier of the stop-loss.
/// </summary>
public decimal AtrMultiplierSl
{
get => _atrMultiplierSl.Value;
set => _atrMultiplierSl.Value = value;
}
/// <summary>
/// ATR multiplier of the take-profit.
/// </summary>
public decimal AtrMultiplierTp
{
get => _atrMultiplierTp.Value;
set => _atrMultiplierTp.Value = value;
}
/// <summary>
/// ATR period.
/// </summary>
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
/// <summary>
/// Minimum ATR as a percent of the close required to trade.
/// </summary>
public decimal MinVolatility
{
get => _minVolatility.Value;
set => _minVolatility.Value = value;
}
/// <summary>
/// Holding period in multiples of MomentumLength bars.
/// </summary>
public decimal HoldingPeriodMultiplier
{
get => _holdingPeriodMultiplier.Value;
set => _holdingPeriodMultiplier.Value = value;
}
/// <summary>
/// Require the trend EMA to slope in the trade direction.
/// </summary>
public bool UseTrendFilter
{
get => _useTrendFilter.Value;
set => _useTrendFilter.Value = value;
}
/// <summary>
/// Require RSI on the trade side of its level.
/// </summary>
public bool UseRsiFilter
{
get => _useRsiFilter.Value;
set => _useRsiFilter.Value = value;
}
/// <summary>
/// RSI period.
/// </summary>
public int RsiLength
{
get => _rsiLength.Value;
set => _rsiLength.Value = value;
}
/// <summary>
/// RSI level a long must be above.
/// </summary>
public decimal RsiOversold
{
get => _rsiOversold.Value;
set => _rsiOversold.Value = value;
}
/// <summary>
/// RSI level a short must be below.
/// </summary>
public decimal RsiOverbought
{
get => _rsiOverbought.Value;
set => _rsiOverbought.Value = value;
}
/// <summary>
/// Standard deviations of momentum that make the dynamic threshold.
/// </summary>
public decimal MomentumMultiplier
{
get => _momentumMultiplier.Value;
set => _momentumMultiplier.Value = value;
}
/// <summary>
/// Allow long trades.
/// </summary>
public bool AllowLong
{
get => _allowLong.Value;
set => _allowLong.Value = value;
}
/// <summary>
/// Allow short trades.
/// </summary>
public bool AllowShort
{
get => _allowShort.Value;
set => _allowShort.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public AdaptiveSqueezeMomentumStrategy()
{
_bollingerPeriod = Param(nameof(BollingerPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Bollinger Period", "Bollinger Bands period", "Squeeze");
_bollingerMultiplier = Param(nameof(BollingerMultiplier), 2.0m)
.SetGreaterThanZero()
.SetDisplay("Bollinger Multiplier", "Bollinger Bands standard deviation multiplier", "Squeeze");
_keltnerPeriod = Param(nameof(KeltnerPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Keltner Period", "Keltner Channel period", "Squeeze");
_keltnerMultiplier = Param(nameof(KeltnerMultiplier), 1.5m)
.SetGreaterThanZero()
.SetDisplay("Keltner Multiplier", "Keltner Channel ATR multiplier", "Squeeze");
_momentumLength = Param(nameof(MomentumLength), 12)
.SetGreaterThanZero()
.SetDisplay("Momentum Length", "Momentum period, also the window of its standard deviation", "Momentum");
_trendMaLength = Param(nameof(TrendMaLength), 50)
.SetGreaterThanZero()
.SetDisplay("Trend MA Length", "Period of the trend EMA", "Filters");
_useAtrStops = Param(nameof(UseAtrStops), true)
.SetDisplay("Use ATR Stops", "Use ATR stop-loss and take-profit", "Risk");
_atrMultiplierSl = Param(nameof(AtrMultiplierSl), 1.5m)
.SetNotNegative()
.SetDisplay("ATR Multiplier SL", "ATR multiplier of the stop-loss", "Risk");
_atrMultiplierTp = Param(nameof(AtrMultiplierTp), 2.5m)
.SetNotNegative()
.SetDisplay("ATR Multiplier TP", "ATR multiplier of the take-profit", "Risk");
_atrLength = Param(nameof(AtrLength), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Length", "ATR period", "Risk");
_minVolatility = Param(nameof(MinVolatility), 0.5m)
.SetNotNegative()
.SetDisplay("Min Volatility %", "Minimum ATR as a percent of the close required to trade", "Filters");
_holdingPeriodMultiplier = Param(nameof(HoldingPeriodMultiplier), 1.5m)
.SetNotNegative()
.SetDisplay("Holding Period Multiplier", "Holding period in multiples of MomentumLength bars", "Risk");
_useTrendFilter = Param(nameof(UseTrendFilter), true)
.SetDisplay("Use Trend Filter", "Require the trend EMA to slope in the trade direction", "Filters");
_useRsiFilter = Param(nameof(UseRsiFilter), true)
.SetDisplay("Use RSI Filter", "Require RSI on the trade side of its level", "Filters");
_rsiLength = Param(nameof(RsiLength), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Length", "RSI period", "Filters");
_rsiOversold = Param(nameof(RsiOversold), 40m)
.SetDisplay("RSI Oversold", "RSI level a long must be above", "Filters");
_rsiOverbought = Param(nameof(RsiOverbought), 60m)
.SetDisplay("RSI Overbought", "RSI level a short must be below", "Filters");
_momentumMultiplier = Param(nameof(MomentumMultiplier), 1.5m)
.SetNotNegative()
.SetDisplay("Momentum Multiplier", "Standard deviations of momentum that make the dynamic threshold", "Momentum");
_allowLong = Param(nameof(AllowLong), true)
.SetDisplay("Allow Long", "Allow long trades", "General");
_allowShort = Param(nameof(AllowShort), true)
.SetDisplay("Allow Short", "Allow short trades", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).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();
_momentumStdDev = null;
ResetState();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetState();
var bollinger = new BollingerBands { Length = BollingerPeriod, Width = BollingerMultiplier };
var keltner = new KeltnerChannels { Length = KeltnerPeriod, Multiplier = KeltnerMultiplier };
var momentum = new Momentum { Length = MomentumLength };
var trendEma = new ExponentialMovingAverage { Length = TrendMaLength };
var atr = new AverageTrueRange { Length = AtrLength };
var rsi = new RelativeStrengthIndex { Length = RsiLength };
_momentumStdDev = new StandardDeviation { Length = MomentumLength };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx([bollinger, keltner, momentum, trendEma, atr, rsi], ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, bollinger);
DrawIndicator(area, keltner);
DrawIndicator(area, trendEma);
DrawOwnTrades(area);
}
}
private void ResetState()
{
_prevEma = null;
_stopPrice = null;
_takePrice = null;
_barsInPosition = 0;
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue[] values)
{
if (candle.State != CandleStates.Finished)
return;
var momentumValue = values[2];
decimal? threshold = null;
if (momentumValue.IsFormed)
{
var stdDev = _momentumStdDev.Process(momentumValue.ToDecimal(), candle.ServerTime, true);
if (_momentumStdDev.IsFormed)
threshold = stdDev.ToDecimal() * MomentumMultiplier;
}
foreach (var value in values)
{
if (!value.IsFormed)
return;
}
if (values[0] is not BollingerBandsValue { UpBand: decimal bbUpper, LowBand: decimal bbLower }
|| values[1] is not KeltnerChannelsValue { Upper: decimal kcUpper, Lower: decimal kcLower })
return;
var momentum = momentumValue.ToDecimal();
var ema = values[3].ToDecimal();
var atr = values[4].ToDecimal();
var rsi = values[5].ToDecimal();
var prevEma = _prevEma;
_prevEma = ema;
if (threshold is not decimal momentumThreshold || prevEma is not decimal pEma)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var close = candle.ClosePrice;
if (Position != 0 && ManagePosition(candle))
return;
var squeezeReleased = bbUpper > kcUpper && bbLower < kcLower;
var volatile_ = close > 0 && atr / close * 100m >= MinVolatility;
var longSignal = AllowLong && squeezeReleased && volatile_ && momentum > momentumThreshold
&& (!UseRsiFilter || rsi > RsiOversold)
&& (!UseTrendFilter || ema > pEma);
var shortSignal = AllowShort && squeezeReleased && volatile_ && momentum < -momentumThreshold
&& (!UseRsiFilter || rsi < RsiOverbought)
&& (!UseTrendFilter || ema < pEma);
if (longSignal && Position <= 0)
{
BuyMarket(Volume + Math.Abs(Position));
SetLevels(close, atr, true);
}
else if (shortSignal && Position >= 0)
{
SellMarket(Volume + Math.Abs(Position));
SetLevels(close, atr, false);
}
}
private bool ManagePosition(ICandleMessage candle)
{
_barsInPosition++;
var hitStop = Position > 0
? (_stopPrice is decimal ls && candle.LowPrice <= ls) || (_takePrice is decimal lt && candle.HighPrice >= lt)
: (_stopPrice is decimal ss && candle.HighPrice >= ss) || (_takePrice is decimal st && candle.LowPrice <= st);
var holdingBars = (int)Math.Round(MomentumLength * HoldingPeriodMultiplier);
var expired = holdingBars > 0 && _barsInPosition >= holdingBars;
if (!hitStop && !expired)
return false;
if (Position > 0)
SellMarket(Position);
else
BuyMarket(-Position);
_stopPrice = null;
_takePrice = null;
_barsInPosition = 0;
return true;
}
private void SetLevels(decimal price, decimal atr, bool isLong)
{
_barsInPosition = 0;
if (!UseAtrStops)
{
_stopPrice = null;
_takePrice = null;
return;
}
var stop = atr * AtrMultiplierSl;
var take = atr * AtrMultiplierTp;
_stopPrice = stop > 0 ? (isLong ? price - stop : price + stop) : null;
_takePrice = take > 0 ? (isLong ? price + take : price - take) : null;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
from System import TimeSpan, Decimal, Array
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import (IIndicator, BollingerBands, KeltnerChannels, Momentum, ExponentialMovingAverage,
AverageTrueRange, RelativeStrengthIndex, StandardDeviation)
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class adaptive_squeeze_momentum_strategy(Strategy):
"""
Adaptive Squeeze Momentum strategy.
Trades only when the squeeze is released (Bollinger Bands outside the Keltner Channel) and ATR is at least MinVolatility percent
of price. A long needs momentum above MomentumMultiplier standard deviations of momentum, RSI above RsiOversold and a
rising trend EMA; a short mirrors this with RSI below RsiOverbought and a falling EMA (both filters optional). The
opposite signal reverses; ATR stop-loss and take-profit levels and a holding period of HoldingPeriodMultiplier * MomentumLength
bars close positions.
"""
def __init__(self):
super(adaptive_squeeze_momentum_strategy, self).__init__()
self._bollinger_period = self.Param("BollingerPeriod", 20).SetGreaterThanZero().SetDisplay("Bollinger Period", "Bollinger Bands period", "Squeeze")
self._bollinger_multiplier = self.Param("BollingerMultiplier", 2.0).SetGreaterThanZero().SetDisplay("Bollinger Multiplier", "Bollinger Bands standard deviation multiplier", "Squeeze")
self._keltner_period = self.Param("KeltnerPeriod", 20).SetGreaterThanZero().SetDisplay("Keltner Period", "Keltner Channel period", "Squeeze")
self._keltner_multiplier = self.Param("KeltnerMultiplier", 1.5).SetGreaterThanZero().SetDisplay("Keltner Multiplier", "Keltner Channel ATR multiplier", "Squeeze")
self._momentum_length = self.Param("MomentumLength", 12).SetGreaterThanZero().SetDisplay("Momentum Length", "Momentum period, also the window of its standard deviation", "Momentum")
self._trend_ma_length = self.Param("TrendMaLength", 50).SetGreaterThanZero().SetDisplay("Trend MA Length", "Period of the trend EMA", "Filters")
self._use_atr_stops = self.Param("UseAtrStops", True).SetDisplay("Use ATR Stops", "Use ATR stop-loss and take-profit", "Risk")
self._atr_multiplier_sl = self.Param("AtrMultiplierSl", 1.5).SetNotNegative().SetDisplay("ATR Multiplier SL", "ATR multiplier of the stop-loss", "Risk")
self._atr_multiplier_tp = self.Param("AtrMultiplierTp", 2.5).SetNotNegative().SetDisplay("ATR Multiplier TP", "ATR multiplier of the take-profit", "Risk")
self._atr_length = self.Param("AtrLength", 14).SetGreaterThanZero().SetDisplay("ATR Length", "ATR period", "Risk")
self._min_volatility = self.Param("MinVolatility", 0.5).SetNotNegative().SetDisplay("Min Volatility %", "Minimum ATR as a percent of the close required to trade", "Filters")
self._holding_period_multiplier = self.Param("HoldingPeriodMultiplier", 1.5).SetNotNegative().SetDisplay("Holding Period Multiplier", "Holding period in multiples of MomentumLength bars", "Risk")
self._use_trend_filter = self.Param("UseTrendFilter", True).SetDisplay("Use Trend Filter", "Require the trend EMA to slope in the trade direction", "Filters")
self._use_rsi_filter = self.Param("UseRsiFilter", True).SetDisplay("Use RSI Filter", "Require RSI on the trade side of its level", "Filters")
self._rsi_length = self.Param("RsiLength", 14).SetGreaterThanZero().SetDisplay("RSI Length", "RSI period", "Filters")
self._rsi_oversold = self.Param("RsiOversold", 40.0).SetDisplay("RSI Oversold", "RSI level a long must be above", "Filters")
self._rsi_overbought = self.Param("RsiOverbought", 60.0).SetDisplay("RSI Overbought", "RSI level a short must be below", "Filters")
self._momentum_multiplier = self.Param("MomentumMultiplier", 1.5).SetNotNegative().SetDisplay("Momentum Multiplier", "Standard deviations of momentum that make the dynamic threshold", "Momentum")
self._allow_long = self.Param("AllowLong", True).SetDisplay("Allow Long", "Allow long trades", "General")
self._allow_short = self.Param("AllowShort", True).SetDisplay("Allow Short", "Allow short trades", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._momentum_std_dev = None
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def _reset_state(self):
self._prev_ema = None
self._stop_price = None
self._take_price = None
self._bars_in_position = 0
def OnReseted(self):
super(adaptive_squeeze_momentum_strategy, self).OnReseted()
self._momentum_std_dev = None
self._reset_state()
def OnStarted2(self, time):
super(adaptive_squeeze_momentum_strategy, self).OnStarted2(time)
self._reset_state()
bollinger = BollingerBands()
bollinger.Length = self._bollinger_period.Value
bollinger.Width = Decimal(self._bollinger_multiplier.Value)
keltner = KeltnerChannels()
keltner.Length = self._keltner_period.Value
keltner.Multiplier = Decimal(self._keltner_multiplier.Value)
momentum = Momentum()
momentum.Length = self._momentum_length.Value
trend_ema = ExponentialMovingAverage()
trend_ema.Length = self._trend_ma_length.Value
atr = AverageTrueRange()
atr.Length = self._atr_length.Value
rsi = RelativeStrengthIndex()
rsi.Length = self._rsi_length.Value
self._momentum_std_dev = StandardDeviation()
self._momentum_std_dev.Length = self._momentum_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(Array[IIndicator]([bollinger, keltner, momentum, trend_ema, atr, rsi]), self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, bollinger)
self.DrawIndicator(area, keltner)
self.DrawIndicator(area, trend_ema)
self.DrawOwnTrades(area)
def _process_candle(self, candle, values):
if candle.State != CandleStates.Finished:
return
momentum_value = values[2]
threshold = None
if momentum_value.IsFormed:
std_dev = process_value(self._momentum_std_dev, momentum_value.GetValue[Decimal](None), candle.ServerTime, True)
if self._momentum_std_dev.IsFormed:
threshold = std_dev.GetValue[Decimal](None) * Decimal(self._momentum_multiplier.Value)
for value in values:
if not value.IsFormed:
return
bb = values[0]
kc = values[1]
if bb.UpBand is None or bb.LowBand is None or kc.Upper is None or kc.Lower is None:
return
momentum = momentum_value.GetValue[Decimal](None)
ema = values[3].GetValue[Decimal](None)
atr = values[4].GetValue[Decimal](None)
rsi = values[5].GetValue[Decimal](None)
prev_ema = self._prev_ema
self._prev_ema = ema
if threshold is None or prev_ema is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
close = candle.ClosePrice
if self.Position != 0 and self._manage_position(candle):
return
squeeze_released = bb.UpBand > kc.Upper and bb.LowBand < kc.Lower
volatile = close > 0 and atr / close * Decimal(100) >= Decimal(self._min_volatility.Value)
use_rsi = self._use_rsi_filter.Value
use_trend = self._use_trend_filter.Value
long_signal = (self._allow_long.Value and squeeze_released and volatile and momentum > threshold
and (not use_rsi or rsi > Decimal(self._rsi_oversold.Value))
and (not use_trend or ema > prev_ema))
short_signal = (self._allow_short.Value and squeeze_released and volatile and momentum < -threshold
and (not use_rsi or rsi < Decimal(self._rsi_overbought.Value))
and (not use_trend or ema < prev_ema))
if long_signal and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
self._set_levels(close, atr, True)
elif short_signal and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
self._set_levels(close, atr, False)
def _manage_position(self, candle):
self._bars_in_position += 1
if self.Position > 0:
hit_stop = ((self._stop_price is not None and candle.LowPrice <= self._stop_price)
or (self._take_price is not None and candle.HighPrice >= self._take_price))
else:
hit_stop = ((self._stop_price is not None and candle.HighPrice >= self._stop_price)
or (self._take_price is not None and candle.LowPrice <= self._take_price))
holding_bars = int(round(self._momentum_length.Value * float(self._holding_period_multiplier.Value)))
expired = holding_bars > 0 and self._bars_in_position >= holding_bars
if not hit_stop and not expired:
return False
if self.Position > 0:
self.SellMarket(self.Position)
else:
self.BuyMarket(-self.Position)
self._stop_price = None
self._take_price = None
self._bars_in_position = 0
return True
def _set_levels(self, price, atr, is_long):
self._bars_in_position = 0
if not self._use_atr_stops.Value:
self._stop_price = None
self._take_price = None
return
stop = atr * Decimal(self._atr_multiplier_sl.Value)
take = atr * Decimal(self._atr_multiplier_tp.Value)
self._stop_price = (price - stop if is_long else price + stop) if stop > 0 else None
self._take_price = (price + take if is_long else price - take) if take > 0 else None
def CreateClone(self):
return adaptive_squeeze_momentum_strategy()