Стратегия Knux Martingale
Стратегия мартингейла, увеличивающая объём сделки после убыточной позиции. Входы фильтруются индикатором Average Directional Index (ADX), чтобы торговать только в трендовых условиях. Бычья свеча открывает длинную позицию, медвежья свеча открывает короткую.
Подробности
- Условия входа:
- ADX > 25
- Длинная:
Close > Open - Короткая:
Close < Open
- Long/Short: Оба
- Условия выхода: Стоп‑лосс или тейк‑профит
- Стопы: Да
- Параметры по умолчанию:
AdxPeriod= 14LotsMultiplier= 1.5mStopLoss= 150mTakeProfit= 50mCandleType= TimeSpan.FromMinutes(5).TimeFrame()
- Фильтры:
- Категория: Следование тренду, Мартингейл
- Направление: Оба
- Индикаторы: AverageDirectionalIndex
- Стопы: Абсолютные
- Сложность: Средняя
- Таймфрейм: Внутридневной
- Сезонность: Нет
- Нейросети: Нет
- Дивергенция: Нет
- Уровень риска: Высокий
using System;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Martingale strategy increasing volume after losses and filtered by ADX.
/// </summary>
public class KnuxMartingaleStrategy : Strategy
{
private readonly StrategyParam<int> _adxPeriod;
private readonly StrategyParam<decimal> _lotsMultiplier;
private readonly StrategyParam<decimal> _stopLoss;
private readonly StrategyParam<decimal> _takeProfit;
private readonly StrategyParam<decimal> _trendThreshold;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<DataType> _candleType;
private decimal _currentVolume;
private decimal _prevSma;
private bool _hasPrevSma;
private int _barsSinceExit;
/// <summary>
/// ADX period used for trend strength.
/// </summary>
public int AdxPeriod
{
get => _adxPeriod.Value;
set => _adxPeriod.Value = value;
}
/// <summary>
/// Volume multiplier applied after a losing trade.
/// </summary>
public decimal LotsMultiplier
{
get => _lotsMultiplier.Value;
set => _lotsMultiplier.Value = value;
}
/// <summary>
/// Stop loss in absolute price units.
/// </summary>
public decimal StopLoss
{
get => _stopLoss.Value;
set => _stopLoss.Value = value;
}
/// <summary>
/// Take profit in absolute price units.
/// </summary>
public decimal TakeProfit
{
get => _takeProfit.Value;
set => _takeProfit.Value = value;
}
/// <summary>
/// Minimum normalized distance between price and trend average.
/// </summary>
public decimal TrendThreshold
{
get => _trendThreshold.Value;
set => _trendThreshold.Value = value;
}
/// <summary>
/// Cooldown after a flat position before a new entry.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Candle type for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of the strategy.
/// </summary>
public KnuxMartingaleStrategy()
{
_adxPeriod = Param(nameof(AdxPeriod), 14)
.SetDisplay("ADX Period", "Period for ADX filter", "Indicators");
_lotsMultiplier = Param(nameof(LotsMultiplier), 1.5m)
.SetDisplay("Lots Multiplier", "Multiplier for losing trades", "Risk");
_stopLoss = Param(nameof(StopLoss), 150m)
.SetDisplay("Stop Loss", "Absolute stop loss in price units", "Risk");
_takeProfit = Param(nameof(TakeProfit), 300m)
.SetDisplay("Take Profit", "Absolute take profit in price units", "Risk");
_trendThreshold = Param(nameof(TrendThreshold), 0.008m)
.SetDisplay("Trend Threshold", "Minimum distance from trend average", "Indicators");
_cooldownBars = Param(nameof(CooldownBars), 3)
.SetDisplay("Cooldown Bars", "Bars to wait after a completed position", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Time frame for strategy", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_currentVolume = Volume;
_prevSma = 0m;
_hasPrevSma = false;
_barsSinceExit = CooldownBars;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_currentVolume = Volume;
var sma = new SimpleMovingAverage { Length = AdxPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(sma, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, sma);
DrawOwnTrades(area);
}
StartProtection(
takeProfit: new Unit(TakeProfit, UnitTypes.Absolute),
stopLoss: new Unit(StopLoss, UnitTypes.Absolute));
}
private void ProcessCandle(ICandleMessage candle, decimal smaValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (_barsSinceExit < CooldownBars)
_barsSinceExit++;
if (!_hasPrevSma)
{
_prevSma = smaValue;
_hasPrevSma = true;
return;
}
var distance = smaValue == 0m ? 0m : Math.Abs(candle.ClosePrice - smaValue) / smaValue;
var isTrendUp = candle.ClosePrice > smaValue && smaValue > _prevSma;
var isTrendDown = candle.ClosePrice < smaValue && smaValue < _prevSma;
if (distance < TrendThreshold)
{
_prevSma = smaValue;
return;
}
if (_barsSinceExit < CooldownBars && Position == 0)
{
_prevSma = smaValue;
return;
}
var volume = Math.Max(Volume, _currentVolume);
if (isTrendUp && candle.ClosePrice > candle.OpenPrice && Position <= 0)
{
BuyMarket(volume);
}
else if (isTrendDown && candle.ClosePrice < candle.OpenPrice && Position >= 0)
{
SellMarket(volume);
}
_prevSma = smaValue;
}
/// <inheritdoc />
protected override void OnOwnTradeReceived(MyTrade myTrade)
{
base.OnOwnTradeReceived(myTrade);
if (Position != 0)
return;
if (myTrade.PnL < 0)
{
_currentVolume *= LotsMultiplier;
}
else
{
_currentVolume = Volume;
}
_barsSinceExit = 0;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, Unit, UnitTypes, CandleStates
from StockSharp.Algo.Indicators import SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class knux_martingale_strategy(Strategy):
def __init__(self):
super(knux_martingale_strategy, self).__init__()
self._adx_period = self.Param("AdxPeriod", 14) \
.SetDisplay("ADX Period", "Period for ADX filter", "Indicators")
self._lots_multiplier = self.Param("LotsMultiplier", 1.5) \
.SetDisplay("Lots Multiplier", "Multiplier for losing trades", "Risk")
self._stop_loss = self.Param("StopLoss", 150.0) \
.SetDisplay("Stop Loss", "Absolute stop loss in price units", "Risk")
self._take_profit = self.Param("TakeProfit", 300.0) \
.SetDisplay("Take Profit", "Absolute take profit in price units", "Risk")
self._trend_threshold = self.Param("TrendThreshold", 0.008) \
.SetDisplay("Trend Threshold", "Minimum distance from trend average", "Indicators")
self._cooldown_bars = self.Param("CooldownBars", 3) \
.SetDisplay("Cooldown Bars", "Bars to wait after a completed position", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Time frame for strategy", "General")
self._current_volume = 0.0
self._prev_sma = 0.0
self._has_prev_sma = False
self._bars_since_exit = 0
@property
def AdxPeriod(self):
return self._adx_period.Value
@AdxPeriod.setter
def AdxPeriod(self, value):
self._adx_period.Value = value
@property
def LotsMultiplier(self):
return self._lots_multiplier.Value
@LotsMultiplier.setter
def LotsMultiplier(self, value):
self._lots_multiplier.Value = value
@property
def StopLoss(self):
return self._stop_loss.Value
@StopLoss.setter
def StopLoss(self, value):
self._stop_loss.Value = value
@property
def TakeProfit(self):
return self._take_profit.Value
@TakeProfit.setter
def TakeProfit(self, value):
self._take_profit.Value = value
@property
def TrendThreshold(self):
return self._trend_threshold.Value
@TrendThreshold.setter
def TrendThreshold(self, value):
self._trend_threshold.Value = value
@property
def CooldownBars(self):
return self._cooldown_bars.Value
@CooldownBars.setter
def CooldownBars(self, value):
self._cooldown_bars.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnStarted2(self, time):
super(knux_martingale_strategy, self).OnStarted2(time)
self._current_volume = float(self.Volume)
sma = SimpleMovingAverage()
sma.Length = self.AdxPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription \
.Bind(sma, self.ProcessCandle) \
.Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, sma)
self.DrawOwnTrades(area)
self.StartProtection(
takeProfit=Unit(self.TakeProfit, UnitTypes.Absolute),
stopLoss=Unit(self.StopLoss, UnitTypes.Absolute))
def ProcessCandle(self, candle, sma_value):
if candle.State != CandleStates.Finished:
return
sma_val = float(sma_value)
if self._bars_since_exit < self.CooldownBars:
self._bars_since_exit += 1
if not self._has_prev_sma:
self._prev_sma = sma_val
self._has_prev_sma = True
return
close = float(candle.ClosePrice)
distance = abs(close - sma_val) / sma_val if sma_val != 0.0 else 0.0
is_trend_up = close > sma_val and sma_val > self._prev_sma
is_trend_down = close < sma_val and sma_val < self._prev_sma
if distance < float(self.TrendThreshold):
self._prev_sma = sma_val
return
if self._bars_since_exit < self.CooldownBars and self.Position == 0:
self._prev_sma = sma_val
return
volume = max(float(self.Volume), self._current_volume)
pos = self.Position
if is_trend_up and float(candle.ClosePrice) > float(candle.OpenPrice) and pos <= 0:
self.BuyMarket(volume)
elif is_trend_down and float(candle.ClosePrice) < float(candle.OpenPrice) and pos >= 0:
self.SellMarket(volume)
self._prev_sma = sma_val
def OnOwnTradeReceived(self, my_trade):
super(knux_martingale_strategy, self).OnOwnTradeReceived(my_trade)
if self.Position != 0:
return
if my_trade.PnL < 0:
self._current_volume *= float(self.LotsMultiplier)
else:
self._current_volume = float(self.Volume)
self._bars_since_exit = 0
def OnReseted(self):
super(knux_martingale_strategy, self).OnReseted()
self._current_volume = float(self.Volume)
self._prev_sma = 0.0
self._has_prev_sma = False
self._bars_since_exit = self.CooldownBars
def CreateClone(self):
return knux_martingale_strategy()