Стратегия Multi Indicator Swing
Свинг-стратегия, объединяющая Parabolic SAR, SuperTrend, ADX и подтверждение по объёмной дельте.
Детали
- Условия входа: Все включённые индикаторы дают сигнал в одном направлении.
- Лонг/Шорт: Оба направления.
- Условия выхода: Противоположный сигнал или достижение стоп-лосса/тейк-профита.
- Стопы: Процентные уровни по желанию.
- Параметры по умолчанию:
CandleType= TimeSpan.FromMinutes(2)PsarStart= 0.02mPsarIncrement= 0.02mPsarMaximum= 0.2mAtrPeriod= 10AtrMultiplier= 3mAdxLength= 14AdxThreshold= 25mDeltaLength= 14DeltaSmooth= 3DeltaThreshold= 0.5mStopLossPercent= 2mTakeProfitPercent= 4m
- Фильтры:
- Категория: Trend
- Направление: Both
- Индикаторы: PSAR, SuperTrend, ADX, Volume
- Стопы: Yes
- Сложность: Intermediate
- Таймфрейм: Intraday (2m)
- Сезонность: No
- Нейросети: No
- Дивергенция: No
- Уровень риска: Medium
using System;
using System.Collections.Generic;
using System.Linq;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Multi indicator swing strategy.
/// A long needs the close above Parabolic SAR, an up-trending SuperTrend, ADX above AdxThreshold with +DI above -DI, and a volume
/// delta ratio above DeltaThreshold; a short needs all of the mirrored conditions. An opposite signal reverses the position and
/// percent stop loss / take profit levels protect it.
/// </summary>
public class MultiIndicatorSwingStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _psarStart;
private readonly StrategyParam<decimal> _psarIncrement;
private readonly StrategyParam<decimal> _psarMaximum;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<decimal> _atrMultiplier;
private readonly StrategyParam<int> _adxLength;
private readonly StrategyParam<decimal> _adxThreshold;
private readonly StrategyParam<int> _deltaLength;
private readonly StrategyParam<int> _deltaSmooth;
private readonly StrategyParam<decimal> _deltaThreshold;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<decimal> _takeProfitPercent;
private readonly Queue<decimal> _deltas = new();
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Parabolic SAR start acceleration.
/// </summary>
public decimal PsarStart
{
get => _psarStart.Value;
set => _psarStart.Value = value;
}
/// <summary>
/// Parabolic SAR acceleration increment.
/// </summary>
public decimal PsarIncrement
{
get => _psarIncrement.Value;
set => _psarIncrement.Value = value;
}
/// <summary>
/// Parabolic SAR maximum acceleration.
/// </summary>
public decimal PsarMaximum
{
get => _psarMaximum.Value;
set => _psarMaximum.Value = value;
}
/// <summary>
/// SuperTrend ATR period.
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// SuperTrend ATR multiplier.
/// </summary>
public decimal AtrMultiplier
{
get => _atrMultiplier.Value;
set => _atrMultiplier.Value = value;
}
/// <summary>
/// ADX period.
/// </summary>
public int AdxLength
{
get => _adxLength.Value;
set => _adxLength.Value = value;
}
/// <summary>
/// Minimum ADX for a trend.
/// </summary>
public decimal AdxThreshold
{
get => _adxThreshold.Value;
set => _adxThreshold.Value = value;
}
/// <summary>
/// Candles over which the typical absolute volume delta is averaged.
/// </summary>
public int DeltaLength
{
get => _deltaLength.Value;
set => _deltaLength.Value = value;
}
/// <summary>
/// Candles over which the volume delta is smoothed.
/// </summary>
public int DeltaSmooth
{
get => _deltaSmooth.Value;
set => _deltaSmooth.Value = value;
}
/// <summary>
/// Minimum normalized volume delta that confirms a direction.
/// </summary>
public decimal DeltaThreshold
{
get => _deltaThreshold.Value;
set => _deltaThreshold.Value = value;
}
/// <summary>
/// Stop loss percentage.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Take profit percentage.
/// </summary>
public decimal TakeProfitPercent
{
get => _takeProfitPercent.Value;
set => _takeProfitPercent.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public MultiIndicatorSwingStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(2).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_psarStart = Param(nameof(PsarStart), 0.02m)
.SetGreaterThanZero()
.SetDisplay("PSAR Start", "Parabolic SAR start acceleration", "PSAR");
_psarIncrement = Param(nameof(PsarIncrement), 0.02m)
.SetGreaterThanZero()
.SetDisplay("PSAR Increment", "Parabolic SAR acceleration increment", "PSAR");
_psarMaximum = Param(nameof(PsarMaximum), 0.2m)
.SetGreaterThanZero()
.SetDisplay("PSAR Maximum", "Parabolic SAR maximum acceleration", "PSAR");
_atrPeriod = Param(nameof(AtrPeriod), 10)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "SuperTrend ATR period", "SuperTrend");
_atrMultiplier = Param(nameof(AtrMultiplier), 3m)
.SetGreaterThanZero()
.SetDisplay("ATR Multiplier", "SuperTrend ATR multiplier", "SuperTrend");
_adxLength = Param(nameof(AdxLength), 14)
.SetGreaterThanZero()
.SetDisplay("ADX Length", "ADX period", "ADX");
_adxThreshold = Param(nameof(AdxThreshold), 25m)
.SetNotNegative()
.SetDisplay("ADX Threshold", "Minimum ADX for a trend", "ADX");
_deltaLength = Param(nameof(DeltaLength), 14)
.SetGreaterThanZero()
.SetDisplay("Delta Length", "Candles for the typical absolute volume delta", "Volume Delta");
_deltaSmooth = Param(nameof(DeltaSmooth), 3)
.SetGreaterThanZero()
.SetDisplay("Delta Smooth", "Candles for smoothing the volume delta", "Volume Delta");
_deltaThreshold = Param(nameof(DeltaThreshold), 0.5m)
.SetNotNegative()
.SetDisplay("Delta Threshold", "Minimum normalized volume delta", "Volume Delta");
_stopLossPercent = Param(nameof(StopLossPercent), 2m)
.SetNotNegative()
.SetDisplay("Stop Loss %", "Stop loss percentage", "Risk");
_takeProfitPercent = Param(nameof(TakeProfitPercent), 4m)
.SetNotNegative()
.SetDisplay("Take Profit %", "Take profit percentage", "Risk");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_deltas.Clear();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_deltas.Clear();
var psar = new ParabolicSar
{
Acceleration = PsarStart,
AccelerationStep = PsarIncrement,
AccelerationMax = PsarMaximum,
};
var supertrend = new SuperTrend { Length = AtrPeriod, Multiplier = AtrMultiplier };
var adx = new AverageDirectionalIndex { Length = AdxLength };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(psar, supertrend, adx, ProcessCandle)
.Start();
StartProtection(
takeProfit: TakeProfitPercent > 0 ? new Unit(TakeProfitPercent, UnitTypes.Percent) : new Unit(),
stopLoss: StopLossPercent > 0 ? new Unit(StopLossPercent, UnitTypes.Percent) : new Unit(),
useMarketOrders: true);
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, psar);
DrawIndicator(area, supertrend);
DrawOwnTrades(area);
var oscillators = CreateChartArea();
if (oscillators != null)
DrawIndicator(oscillators, adx);
}
}
private decimal? UpdateDeltaRatio(ICandleMessage candle)
{
// Volume delta estimated from where the candle closed inside its range.
var range = candle.HighPrice - candle.LowPrice;
var delta = range > 0 ? candle.TotalVolume * (candle.ClosePrice - candle.OpenPrice) / range : 0m;
_deltas.Enqueue(delta);
var capacity = Math.Max(DeltaLength, DeltaSmooth);
while (_deltas.Count > capacity)
_deltas.Dequeue();
if (_deltas.Count < capacity)
return null;
var smoothed = _deltas.Skip(_deltas.Count - DeltaSmooth).Average();
var typical = _deltas.Skip(_deltas.Count - DeltaLength).Average(Math.Abs);
// The smoothed delta is expressed in units of the typical absolute delta.
return typical > 0 ? smoothed / typical : 0m;
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue psarValue, IIndicatorValue supertrendValue, IIndicatorValue adxValue)
{
if (candle.State != CandleStates.Finished)
return;
var deltaRatio = UpdateDeltaRatio(candle);
if (!psarValue.IsFormed || !supertrendValue.IsFormed || !adxValue.IsFormed || deltaRatio is not decimal ratio)
return;
if (supertrendValue is not SuperTrendIndicatorValue st)
return;
if (adxValue is not AverageDirectionalIndexValue adxData
|| adxData.MovingAverage is not decimal adx || adxData.Dx.Plus is not decimal plusDi || adxData.Dx.Minus is not decimal minusDi)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var sar = psarValue.GetValue<decimal>();
var close = candle.ClosePrice;
var strongTrend = adx > AdxThreshold;
var longSignal = close > sar && st.IsUpTrend && strongTrend && plusDi > minusDi && ratio > DeltaThreshold;
var shortSignal = close < sar && !st.IsUpTrend && strongTrend && minusDi > plusDi && ratio < -DeltaThreshold;
if (longSignal && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (shortSignal && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
}
}
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 ParabolicSar, SuperTrend, AverageDirectionalIndex
from StockSharp.Algo.Strategies import Strategy
class multi_indicator_swing_strategy(Strategy):
"""
Multi indicator swing strategy.
A long needs the close above Parabolic SAR, an up-trending SuperTrend, ADX above AdxThreshold with +DI above -DI, and a volume
delta ratio above DeltaThreshold; a short needs all of the mirrored conditions. An opposite signal reverses the position and
percent stop loss / take profit levels protect it.
"""
def __init__(self):
super(multi_indicator_swing_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(2))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._psar_start = self.Param("PsarStart", 0.02).SetGreaterThanZero().SetDisplay("PSAR Start", "Parabolic SAR start acceleration", "PSAR")
self._psar_increment = self.Param("PsarIncrement", 0.02).SetGreaterThanZero().SetDisplay("PSAR Increment", "Parabolic SAR acceleration increment", "PSAR")
self._psar_maximum = self.Param("PsarMaximum", 0.2).SetGreaterThanZero().SetDisplay("PSAR Maximum", "Parabolic SAR maximum acceleration", "PSAR")
self._atr_period = self.Param("AtrPeriod", 10).SetGreaterThanZero().SetDisplay("ATR Period", "SuperTrend ATR period", "SuperTrend")
self._atr_multiplier = self.Param("AtrMultiplier", 3.0).SetGreaterThanZero().SetDisplay("ATR Multiplier", "SuperTrend ATR multiplier", "SuperTrend")
self._adx_length = self.Param("AdxLength", 14).SetGreaterThanZero().SetDisplay("ADX Length", "ADX period", "ADX")
self._adx_threshold = self.Param("AdxThreshold", 25.0).SetNotNegative().SetDisplay("ADX Threshold", "Minimum ADX for a trend", "ADX")
self._delta_length = self.Param("DeltaLength", 14).SetGreaterThanZero().SetDisplay("Delta Length", "Candles for the typical absolute volume delta", "Volume Delta")
self._delta_smooth = self.Param("DeltaSmooth", 3).SetGreaterThanZero().SetDisplay("Delta Smooth", "Candles for smoothing the volume delta", "Volume Delta")
self._delta_threshold = self.Param("DeltaThreshold", 0.5).SetNotNegative().SetDisplay("Delta Threshold", "Minimum normalized volume delta", "Volume Delta")
self._stop_loss_percent = self.Param("StopLossPercent", 2.0).SetNotNegative().SetDisplay("Stop Loss %", "Stop loss percentage", "Risk")
self._take_profit_percent = self.Param("TakeProfitPercent", 4.0).SetNotNegative().SetDisplay("Take Profit %", "Take profit percentage", "Risk")
self._deltas = []
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(multi_indicator_swing_strategy, self).OnReseted()
self._deltas = []
def OnStarted2(self, time):
super(multi_indicator_swing_strategy, self).OnStarted2(time)
self._deltas = []
psar = ParabolicSar()
psar.Acceleration = Decimal(self._psar_start.Value)
psar.AccelerationStep = Decimal(self._psar_increment.Value)
psar.AccelerationMax = Decimal(self._psar_maximum.Value)
supertrend = SuperTrend()
supertrend.Length = self._atr_period.Value
supertrend.Multiplier = Decimal(self._atr_multiplier.Value)
adx = AverageDirectionalIndex()
adx.Length = self._adx_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(psar, supertrend, adx, self._process_candle).Start()
tp = float(self._take_profit_percent.Value)
sl = float(self._stop_loss_percent.Value)
self.StartProtection(
Unit(Decimal(tp), UnitTypes.Percent) if tp > 0 else Unit(),
Unit(Decimal(sl), UnitTypes.Percent) if sl > 0 else Unit(),
useMarketOrders=True)
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, psar)
self.DrawIndicator(area, supertrend)
self.DrawOwnTrades(area)
oscillators = self.CreateChartArea()
if oscillators is not None:
self.DrawIndicator(oscillators, adx)
def _update_delta_ratio(self, candle):
# Volume delta estimated from where the candle closed inside its range.
rng = candle.HighPrice - candle.LowPrice
if rng > Decimal(0):
delta = candle.TotalVolume * (candle.ClosePrice - candle.OpenPrice) / rng
else:
delta = Decimal(0)
length = self._delta_length.Value
smooth = self._delta_smooth.Value
capacity = max(length, smooth)
self._deltas.append(delta)
while len(self._deltas) > capacity:
self._deltas.pop(0)
if len(self._deltas) < capacity:
return None
smoothed = sum(self._deltas[-smooth:], Decimal(0)) / Decimal(smooth)
typical = sum((abs(d) for d in self._deltas[-length:]), Decimal(0)) / Decimal(length)
# The smoothed delta is expressed in units of the typical absolute delta.
return smoothed / typical if typical > Decimal(0) else Decimal(0)
def _process_candle(self, candle, psar_value, supertrend_value, adx_value):
if candle.State != CandleStates.Finished:
return
ratio = self._update_delta_ratio(candle)
if not psar_value.IsFormed or not supertrend_value.IsFormed or not adx_value.IsFormed or ratio is None:
return
if adx_value.MovingAverage is None or adx_value.Dx.Plus is None or adx_value.Dx.Minus is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
sar = psar_value.GetValue[Decimal](None)
adx = adx_value.MovingAverage
plus_di = adx_value.Dx.Plus
minus_di = adx_value.Dx.Minus
is_up_trend = supertrend_value.IsUpTrend
close = candle.ClosePrice
threshold = Decimal(self._delta_threshold.Value)
strong_trend = adx > Decimal(self._adx_threshold.Value)
long_signal = close > sar and is_up_trend and strong_trend and plus_di > minus_di and ratio > threshold
short_signal = close < sar and not is_up_trend and strong_trend and minus_di > plus_di and ratio < -threshold
if long_signal and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif short_signal and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
def CreateClone(self):
return multi_indicator_swing_strategy()