Стратегия Momentum Keltner Stochastic Combo
Стратегия объединяет сравнение момента цены со стохастиком на базе каналов Кельтнера.
Размер позиции динамически увеличивается в зависимости от эквити и защищается фиксированным стоп-лоссом.
Детали
- Критерии входа:
- Лонг:
Momentum > 0иKeltnerStoch < Threshold - Шорт:
Momentum < 0иKeltnerStoch > Threshold
- Лонг:
- Лонг/Шорт: Оба
- Критерии выхода:
- Лонг:
KeltnerStoch > Threshold - Шорт:
KeltnerStoch < Threshold
- Лонг:
- Стопы: фиксированный
SlPointsот цены входа - Значения по умолчанию:
MomLength= 7KeltnerLength= 9KeltnerMultiplier= 0.5Threshold= 99AtrLength= 20SlPoints= 1185EnableScaling= trueBaseContracts= 1InitialCapital= 30000EquityStep= 150000MaxContracts= 15CandleType= TimeSpan.FromMinutes(5).TimeFrame()
- Фильтры:
- Категория: Следование тренду
- Направление: Оба
- Индикаторы: Momentum, EMA, ATR
- Стопы: Да
- Сложность: Средняя
- Таймфрейм: Среднесрочный
- Сезонность: Нет
- Нейросети: Нет
- Дивергенция: Нет
- Уровень риска: Средний
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy combining momentum and Keltner stochastic.
/// </summary>
public class MomentumKeltnerStochasticComboStrategy : Strategy
{
private readonly StrategyParam<int> _momLength;
private readonly StrategyParam<int> _keltnerLength;
private readonly StrategyParam<decimal> _keltnerMultiplier;
private readonly StrategyParam<decimal> _threshold;
private readonly StrategyParam<int> _atrLength;
private readonly StrategyParam<decimal> _slPoints;
private readonly StrategyParam<int> _signalCooldownBars;
private readonly StrategyParam<bool> _enableScaling;
private readonly StrategyParam<int> _baseContracts;
private readonly StrategyParam<decimal> _initialCapital;
private readonly StrategyParam<decimal> _equityStep;
private readonly StrategyParam<int> _maxContracts;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevMomentum;
private bool _hasPrevMomentum;
private int _barsFromSignal;
public int MomLength
{
get => _momLength.Value;
set => _momLength.Value = value;
}
public int KeltnerLength
{
get => _keltnerLength.Value;
set => _keltnerLength.Value = value;
}
public decimal KeltnerMultiplier
{
get => _keltnerMultiplier.Value;
set => _keltnerMultiplier.Value = value;
}
public decimal Threshold
{
get => _threshold.Value;
set => _threshold.Value = value;
}
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
public decimal SlPoints
{
get => _slPoints.Value;
set => _slPoints.Value = value;
}
public int SignalCooldownBars
{
get => _signalCooldownBars.Value;
set => _signalCooldownBars.Value = value;
}
public bool EnableScaling
{
get => _enableScaling.Value;
set => _enableScaling.Value = value;
}
public int BaseContracts
{
get => _baseContracts.Value;
set => _baseContracts.Value = value;
}
public decimal InitialCapital
{
get => _initialCapital.Value;
set => _initialCapital.Value = value;
}
public decimal EquityStep
{
get => _equityStep.Value;
set => _equityStep.Value = value;
}
public int MaxContracts
{
get => _maxContracts.Value;
set => _maxContracts.Value = value;
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public MomentumKeltnerStochasticComboStrategy()
{
_momLength = Param(nameof(MomLength), 7)
.SetGreaterThanZero()
.SetDisplay("Momentum Lookback", "Momentum lookback length", "Indicators")
.SetOptimize(5, 15, 1);
_keltnerLength = Param(nameof(KeltnerLength), 9)
.SetGreaterThanZero()
.SetDisplay("Keltner EMA Length", "EMA length for Keltner basis", "Indicators")
.SetOptimize(5, 20, 1);
_keltnerMultiplier = Param(nameof(KeltnerMultiplier), 0.5m)
.SetGreaterThanZero()
.SetDisplay("Keltner Mult", "Keltner multiplier", "Indicators")
.SetOptimize(0.5m, 2m, 0.1m);
_threshold = Param(nameof(Threshold), 10m)
.SetRange(0m, 100m)
.SetDisplay("Stochastic Threshold", "Threshold for Keltner stochastic", "Indicators")
.SetOptimize(50m, 100m, 5m);
_atrLength = Param(nameof(AtrLength), 20)
.SetGreaterThanZero()
.SetDisplay("ATR Length", "ATR length for Keltner", "Indicators")
.SetOptimize(10, 30, 1);
_slPoints = Param(nameof(SlPoints), 1185m)
.SetGreaterThanZero()
.SetDisplay("Stop Loss Points", "Stop loss in price points", "Risk Management")
.SetOptimize(500m, 2000m, 100m);
_enableScaling = Param(nameof(EnableScaling), false)
.SetDisplay("Enable Dynamic Contracts", "Use equity based position sizing", "Money Management");
_baseContracts = Param(nameof(BaseContracts), 1)
.SetGreaterThanZero()
.SetDisplay("Base Contracts", "Initial contract size", "Money Management");
_initialCapital = Param(nameof(InitialCapital), 30000m)
.SetGreaterThanZero()
.SetDisplay("Initial Capital", "Starting capital", "Money Management");
_equityStep = Param(nameof(EquityStep), 150000m)
.SetGreaterThanZero()
.SetDisplay("Equity Step", "Equity step for contract change", "Money Management");
_maxContracts = Param(nameof(MaxContracts), 15)
.SetGreaterThanZero()
.SetDisplay("Max Contracts", "Maximum contracts allowed", "Money Management");
_signalCooldownBars = Param(nameof(SignalCooldownBars), 24)
.SetGreaterThanZero()
.SetDisplay("Signal Cooldown Bars", "Minimum bars between entries", "Risk Management");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Type of candles for calculations", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevMomentum = 0m;
_hasPrevMomentum = false;
_barsFromSignal = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevMomentum = 0m;
_hasPrevMomentum = false;
_barsFromSignal = SignalCooldownBars;
var ema = new EMA { Length = KeltnerLength };
var atr = new AverageTrueRange { Length = AtrLength };
var momentum = new Momentum { Length = MomLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ema, atr, momentum, ProcessCandle)
.Start();
StartProtection(
takeProfit: new Unit(0, UnitTypes.Absolute),
stopLoss: new Unit(SlPoints, UnitTypes.Absolute)
);
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ema);
DrawIndicator(area, atr);
DrawIndicator(area, momentum);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal emaValue, decimal atrValue, decimal momentumValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var upper = emaValue + KeltnerMultiplier * atrValue;
var lower = emaValue - KeltnerMultiplier * atrValue;
var denominator = upper - lower;
var keltnerStoch = denominator != 0m ? 100m * (candle.ClosePrice - lower) / denominator : 50m;
var momentumCrossUp = _hasPrevMomentum && _prevMomentum <= 0m && momentumValue > 0m;
var momentumCrossDown = _hasPrevMomentum && _prevMomentum >= 0m && momentumValue < 0m;
var longCondition = momentumCrossUp && keltnerStoch <= Threshold;
var shortCondition = momentumCrossDown && keltnerStoch >= (100m - Threshold);
var contractSize = BaseContracts;
if (EnableScaling)
{
var profitLoss = PnL;
var contractIncrease = (int)Math.Floor(Math.Max(profitLoss, 0m) / EquityStep);
var contractDecrease = (int)Math.Floor(Math.Abs(Math.Min(profitLoss, 0m)) / EquityStep);
contractSize = Math.Max(BaseContracts + contractIncrease - contractDecrease, BaseContracts);
}
if (contractSize > MaxContracts)
contractSize = MaxContracts;
_barsFromSignal++;
if (_barsFromSignal >= SignalCooldownBars && longCondition && Position <= 0)
{
var volume = contractSize + Math.Abs(Position);
BuyMarket(volume);
_barsFromSignal = 0;
}
else if (_barsFromSignal >= SignalCooldownBars && shortCondition && Position >= 0)
{
var volume = contractSize + Math.Abs(Position);
SellMarket(volume);
_barsFromSignal = 0;
}
_prevMomentum = momentumValue;
_hasPrevMomentum = true;
}
}
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, Unit, UnitTypes
from StockSharp.Algo.Indicators import ExponentialMovingAverage, AverageTrueRange, Momentum
from StockSharp.Algo.Strategies import Strategy
class momentum_keltner_stochastic_combo_strategy(Strategy):
def __init__(self):
super(momentum_keltner_stochastic_combo_strategy, self).__init__()
self._mom_length = self.Param("MomLength", 7) \
.SetGreaterThanZero() \
.SetDisplay("Momentum Lookback", "Momentum lookback length", "Indicators")
self._keltner_length = self.Param("KeltnerLength", 9) \
.SetGreaterThanZero() \
.SetDisplay("Keltner EMA Length", "EMA length for Keltner basis", "Indicators")
self._keltner_multiplier = self.Param("KeltnerMultiplier", 0.5) \
.SetGreaterThanZero() \
.SetDisplay("Keltner Mult", "Keltner multiplier", "Indicators")
self._threshold = self.Param("Threshold", 10.0) \
.SetDisplay("Stochastic Threshold", "Threshold for Keltner stochastic", "Indicators")
self._atr_length = self.Param("AtrLength", 20) \
.SetGreaterThanZero() \
.SetDisplay("ATR Length", "ATR length for Keltner", "Indicators")
self._sl_points = self.Param("SlPoints", 1185.0) \
.SetGreaterThanZero() \
.SetDisplay("Stop Loss Points", "Stop loss in price points", "Risk Management")
self._signal_cooldown_bars = self.Param("SignalCooldownBars", 24) \
.SetGreaterThanZero() \
.SetDisplay("Signal Cooldown Bars", "Minimum bars between entries", "Risk Management")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))) \
.SetDisplay("Candle Type", "Type of candles for calculations", "General")
self._prev_momentum = 0.0
self._has_prev_momentum = False
self._bars_from_signal = 0
@property
def candle_type(self):
return self._candle_type.Value
@candle_type.setter
def candle_type(self, value):
self._candle_type.Value = value
def OnReseted(self):
super(momentum_keltner_stochastic_combo_strategy, self).OnReseted()
self._prev_momentum = 0.0
self._has_prev_momentum = False
self._bars_from_signal = 0
def OnStarted2(self, time):
super(momentum_keltner_stochastic_combo_strategy, self).OnStarted2(time)
self._prev_momentum = 0.0
self._has_prev_momentum = False
self._bars_from_signal = self._signal_cooldown_bars.Value
self._ema = ExponentialMovingAverage()
self._ema.Length = self._keltner_length.Value
self._atr = AverageTrueRange()
self._atr.Length = self._atr_length.Value
self._momentum = Momentum()
self._momentum.Length = self._mom_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._ema, self._atr, self._momentum, self.OnProcess).Start()
self.StartProtection(
Unit(0, UnitTypes.Absolute),
Unit(self._sl_points.Value, UnitTypes.Absolute)
)
def OnProcess(self, candle, ema_value, atr_value, momentum_value):
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
ev = float(ema_value)
av = float(atr_value)
mv = float(momentum_value)
km = float(self._keltner_multiplier.Value)
upper = ev + km * av
lower = ev - km * av
denom = upper - lower
close = float(candle.ClosePrice)
keltner_stoch = 100.0 * (close - lower) / denom if denom != 0.0 else 50.0
momentum_cross_up = self._has_prev_momentum and self._prev_momentum <= 0.0 and mv > 0.0
momentum_cross_down = self._has_prev_momentum and self._prev_momentum >= 0.0 and mv < 0.0
thr = float(self._threshold.Value)
long_condition = momentum_cross_up and keltner_stoch <= thr
short_condition = momentum_cross_down and keltner_stoch >= (100.0 - thr)
self._bars_from_signal += 1
cd = self._signal_cooldown_bars.Value
if self._bars_from_signal >= cd and long_condition and self.Position <= 0:
self.BuyMarket()
self._bars_from_signal = 0
elif self._bars_from_signal >= cd and short_condition and self.Position >= 0:
self.SellMarket()
self._bars_from_signal = 0
self._prev_momentum = mv
self._has_prev_momentum = True
def CreateClone(self):
return momentum_keltner_stochastic_combo_strategy()