Стратегия TSI DeMarker
Стратегия рассчитывает индикатор True Strength Index на основе осциллятора DeMarker. Длинная позиция открывается, когда TSI пересекает снизу вверх свою сигнальную скользящую среднюю. Короткая позиция открывается, когда TSI пересекает сверху вниз сигнальную линию.
Подход сочетает анализ импульса и зон перекупленности/перепроданности.
Детали
- Условия входа:
- Long:
TSI пересекает сигнал снизу - Short:
TSI пересекает сигнал сверху
- Long:
- Длинные/Короткие: Оба направления
- Условия выхода: Противоположный сигнал
- Стопы: Нет
- Значения по умолчанию:
CandleType= TimeSpan.FromHours(8).TimeFrame()DemarkerPeriod= 25ShortLength= 5LongLength= 8SignalLength= 20
- Фильтры:
- Категория: Пересечение осцилляторов
- Направление: Оба
- Индикаторы: TSI, DeMarker
- Стопы: Нет
- Сложность: Средняя
- Таймфрейм: Среднесрочный
- Сезонность: Нет
- Нейросети: Нет
- Дивергенция: Нет
- Уровень риска: Средний
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>
/// True Strength Index crossover strategy filtered by DeMarker.
/// </summary>
public class TSIDeMarkerStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _demarkerPeriod;
private readonly StrategyParam<decimal> _tsiSpread;
private readonly StrategyParam<decimal> _longDeMarkerLimit;
private readonly StrategyParam<decimal> _shortDeMarkerLimit;
private readonly StrategyParam<int> _cooldownBars;
private decimal? _prevTsi;
private decimal? _prevSignal;
private int _cooldownRemaining;
/// <summary>
/// Candle type for processing.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Period for DeMarker indicator.
/// </summary>
public int DemarkerPeriod
{
get => _demarkerPeriod.Value;
set => _demarkerPeriod.Value = value;
}
/// <summary>
/// Minimum absolute spread between TSI and its signal line.
/// </summary>
public decimal TsiSpread
{
get => _tsiSpread.Value;
set => _tsiSpread.Value = value;
}
/// <summary>
/// Maximum DeMarker value allowed for long entries.
/// </summary>
public decimal LongDeMarkerLimit
{
get => _longDeMarkerLimit.Value;
set => _longDeMarkerLimit.Value = value;
}
/// <summary>
/// Minimum DeMarker value allowed for short entries.
/// </summary>
public decimal ShortDeMarkerLimit
{
get => _shortDeMarkerLimit.Value;
set => _shortDeMarkerLimit.Value = value;
}
/// <summary>
/// Number of completed candles to wait after a position change.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="TSIDeMarkerStrategy"/> class.
/// </summary>
public TSIDeMarkerStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for strategy", "General");
_demarkerPeriod = Param(nameof(DemarkerPeriod), 14)
.SetDisplay("DeMarker Period", "Period for DeMarker", "Indicators");
_tsiSpread = Param(nameof(TsiSpread), 2m)
.SetDisplay("TSI Spread", "Minimum spread between TSI and its signal line", "Filters");
_longDeMarkerLimit = Param(nameof(LongDeMarkerLimit), 0.55m)
.SetDisplay("Long DeMarker", "Maximum DeMarker for long entries", "Filters");
_shortDeMarkerLimit = Param(nameof(ShortDeMarkerLimit), 0.45m)
.SetDisplay("Short DeMarker", "Minimum DeMarker for short entries", "Filters");
_cooldownBars = Param(nameof(CooldownBars), 6)
.SetDisplay("Cooldown Bars", "Completed candles to wait after a position change", "Trading");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevTsi = null;
_prevSignal = null;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var tsi = new TrueStrengthIndex();
var demarker = new DeMarker { Length = DemarkerPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(tsi, demarker, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, tsi);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue tsiValue, IIndicatorValue demarkerValue)
{
if (candle.State != CandleStates.Finished || !tsiValue.IsFinal || !demarkerValue.IsFinal)
return;
if (_cooldownRemaining > 0)
_cooldownRemaining--;
var tsiPair = (ITrueStrengthIndexValue)tsiValue;
if (tsiPair.Tsi is not decimal tsi || tsiPair.Signal is not decimal signal)
return;
var demarker = demarkerValue.ToDecimal();
if (_prevTsi is not decimal prevTsi || _prevSignal is not decimal prevSignal)
{
_prevTsi = tsi;
_prevSignal = signal;
return;
}
var crossUp = prevTsi <= prevSignal && tsi > signal && Math.Abs(tsi - signal) >= TsiSpread;
var crossDown = prevTsi >= prevSignal && tsi < signal && Math.Abs(tsi - signal) >= TsiSpread;
if (_cooldownRemaining == 0)
{
if (crossUp && demarker <= LongDeMarkerLimit && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
_cooldownRemaining = CooldownBars;
}
else if (crossDown && demarker >= ShortDeMarkerLimit && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
_cooldownRemaining = CooldownBars;
}
}
_prevTsi = tsi;
_prevSignal = signal;
}
}
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, CandleStates
from StockSharp.Algo.Indicators import TrueStrengthIndex, DeMarker
from StockSharp.Algo.Strategies import Strategy
class tsi_de_marker_strategy(Strategy):
def __init__(self):
super(tsi_de_marker_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe for strategy", "General")
self._demarker_period = self.Param("DemarkerPeriod", 14) \
.SetDisplay("DeMarker Period", "Period for DeMarker", "Indicators")
self._tsi_spread = self.Param("TsiSpread", 2.0) \
.SetDisplay("TSI Spread", "Minimum spread between TSI and its signal line", "Filters")
self._long_demarker_limit = self.Param("LongDeMarkerLimit", 0.55) \
.SetDisplay("Long DeMarker", "Maximum DeMarker for long entries", "Filters")
self._short_demarker_limit = self.Param("ShortDeMarkerLimit", 0.45) \
.SetDisplay("Short DeMarker", "Minimum DeMarker for short entries", "Filters")
self._cooldown_bars = self.Param("CooldownBars", 6) \
.SetDisplay("Cooldown Bars", "Completed candles to wait after a position change", "Trading")
self._prev_tsi = None
self._prev_signal = None
self._cooldown_remaining = 0
@property
def candle_type(self):
return self._candle_type.Value
@property
def demarker_period(self):
return self._demarker_period.Value
@property
def tsi_spread(self):
return self._tsi_spread.Value
@property
def long_demarker_limit(self):
return self._long_demarker_limit.Value
@property
def short_demarker_limit(self):
return self._short_demarker_limit.Value
@property
def cooldown_bars(self):
return self._cooldown_bars.Value
def OnReseted(self):
super(tsi_de_marker_strategy, self).OnReseted()
self._prev_tsi = None
self._prev_signal = None
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(tsi_de_marker_strategy, self).OnStarted2(time)
tsi = TrueStrengthIndex()
demarker = DeMarker()
demarker.Length = self.demarker_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(tsi, demarker, self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, tsi)
self.DrawOwnTrades(area)
def process_candle(self, candle, tsi_value, demarker_value):
if candle.State != CandleStates.Finished or not tsi_value.IsFinal or not demarker_value.IsFinal:
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
tsi_pair = tsi_value
tsi_val = tsi_pair.Tsi
signal_val = tsi_pair.Signal
if tsi_val is None or signal_val is None:
return
tsi_val = float(tsi_val)
signal_val = float(signal_val)
demarker = float(demarker_value)
if self._prev_tsi is None or self._prev_signal is None:
self._prev_tsi = tsi_val
self._prev_signal = signal_val
return
tsi_spread_val = float(self.tsi_spread)
cross_up = self._prev_tsi <= self._prev_signal and tsi_val > signal_val and abs(tsi_val - signal_val) >= tsi_spread_val
cross_down = self._prev_tsi >= self._prev_signal and tsi_val < signal_val and abs(tsi_val - signal_val) >= tsi_spread_val
if self._cooldown_remaining == 0:
if cross_up and demarker <= float(self.long_demarker_limit) and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._cooldown_remaining = self.cooldown_bars
elif cross_down and demarker >= float(self.short_demarker_limit) and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._cooldown_remaining = self.cooldown_bars
self._prev_tsi = tsi_val
self._prev_signal = signal_val
def CreateClone(self):
return tsi_de_marker_strategy()