Открыть на GitHub

Стратегия Color Schaff TRIX Trend Cycle

Стратегия реализует осциллятор Schaff Trend Cycle, рассчитанный на основе TRIX и MACD. Осциллятор выявляет циклические изменения тренда и генерирует сигналы при пересечении заданных уровней.

Принцип работы

  1. Вычисляются два осциллятора TRIX с разными периодами для построения серии MACD.
  2. Значения MACD проходят двойное стохастическое преобразование, формируя Schaff Trend Cycle (STC).
  3. Длинная позиция открывается при пересечении STC выше верхнего уровня, короткая – при пересечении ниже нижнего уровня.
  4. Текущие позиции закрываются при обратном пересечении.

Параметры

  • Fast TRIX – период быстрого TRIX.
  • Slow TRIX – период медленного TRIX.
  • Cycle – период для расчёта стохастики.
  • High Level / Low Level – верхняя и нижняя границы STC.
  • Stop Loss % / Take Profit % – параметры управления рисками в процентах.
  • Buy/Sell Open/Close – разрешение на открытие и закрытие соответствующих позиций.

Примечания

Стратегия использует свечи выбранного таймфрейма (по умолчанию 4 часа) и выставляет рыночные ордера. Включена защита с параметрами стоп-лосса и тейк-профита. Обработка индикаторов выполняется через высокоуровневый API с автоматической привязкой.

Используйте стратегию в учебных целях и тщательно тестируйте перед реальной торговлей.

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;

using StockSharp.Algo;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Strategy based on Schaff Trend Cycle calculated over TRIX MACD.
/// Buys when the oscillator crosses above the high level and sells when it
/// crosses below the low level.
/// </summary>
public class ColorSchaffTrixTrendCycleStrategy : Strategy {
	private readonly StrategyParam<int> _fastTrixLength;
	private readonly StrategyParam<int> _slowTrixLength;
	private readonly StrategyParam<int> _cycle;
	private readonly StrategyParam<int> _highLevel;
	private readonly StrategyParam<int> _lowLevel;
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<decimal> _stopLoss;
	private readonly StrategyParam<decimal> _takeProfit;
	private readonly StrategyParam<bool> _buyOpen;
	private readonly StrategyParam<bool> _sellOpen;
	private readonly StrategyParam<bool> _buyClose;
	private readonly StrategyParam<bool> _sellClose;
	private readonly StrategyParam<decimal> _factor;
	private readonly StrategyParam<int> _signalCooldownBars;

	private SchaffTrixTrendCycle _stc = null!;
	private decimal? _prevStc;
	private int _cooldownRemaining;

	/// <summary>
	/// Fast TRIX length.
	/// </summary>
	public int FastTrixLength {
	get => _fastTrixLength.Value;
	set => _fastTrixLength.Value = value;
	}

	/// <summary>
	/// Slow TRIX length.
	/// </summary>
	public int SlowTrixLength {
	get => _slowTrixLength.Value;
	set => _slowTrixLength.Value = value;
	}

	/// <summary>
	/// Cycle length used in stochastic calculations.
	/// </summary>
	public int Cycle {
	get => _cycle.Value;
	set => _cycle.Value = value;
	}

	/// <summary>
	/// Upper threshold for oscillator.
	/// </summary>
	public int HighLevel {
	get => _highLevel.Value;
	set => _highLevel.Value = value;
	}

	/// <summary>
	/// Lower threshold for oscillator.
	/// </summary>
	public int LowLevel {
	get => _lowLevel.Value;
	set => _lowLevel.Value = value;
	}

	/// <summary>
	/// Candle type for indicator calculation.
	/// </summary>
	public DataType CandleType {
	get => _candleType.Value;
	set => _candleType.Value = value;
	}

	/// <summary>
	/// Stop loss value in percent.
	/// </summary>
	public decimal StopLoss {
	get => _stopLoss.Value;
	set => _stopLoss.Value = value;
	}

	/// <summary>
	/// Take profit value in percent.
	/// </summary>
	public decimal TakeProfit {
	get => _takeProfit.Value;
	set => _takeProfit.Value = value;
	}

	/// <summary>
	/// Allow opening long positions.
	/// </summary>
	public bool BuyOpen {
	get => _buyOpen.Value;
	set => _buyOpen.Value = value;
	}

	/// <summary>
	/// Allow opening short positions.
	/// </summary>
	public bool SellOpen {
	get => _sellOpen.Value;
	set => _sellOpen.Value = value;
	}

	/// <summary>
	/// Allow closing long positions.
	/// </summary>
	public bool BuyClose {
	get => _buyClose.Value;
	set => _buyClose.Value = value;
	}

	/// <summary>
	/// Allow closing short positions.
	/// </summary>
	public bool SellClose {
	get => _sellClose.Value;
	set => _sellClose.Value = value;
	}

	/// <summary>
	/// Smoothing factor used in the Schaff Trend Cycle calculations.
	/// </summary>
	public decimal Factor {
	get => _factor.Value;
	set => _factor.Value = value;
	}

	/// <summary>
	/// Bars to wait between reversals.
	/// </summary>
	public int SignalCooldownBars {
	get => _signalCooldownBars.Value;
	set => _signalCooldownBars.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of <see
	/// cref="ColorSchaffTrixTrendCycleStrategy"/>.
	/// </summary>
	public ColorSchaffTrixTrendCycleStrategy() {
	_fastTrixLength =
		Param(nameof(FastTrixLength), 5)
		.SetGreaterThanZero()
		.SetDisplay("Fast TRIX", "Fast TRIX length", "Indicator")
		.SetOptimize(3, 15, 2);

	_slowTrixLength =
		Param(nameof(SlowTrixLength), 12)
		.SetGreaterThanZero()
		.SetDisplay("Slow TRIX", "Slow TRIX length", "Indicator")
		.SetOptimize(8, 30, 3);

	_cycle = Param(nameof(Cycle), 10)
			 .SetGreaterThanZero()
			 .SetDisplay("Cycle", "Cycle length", "Indicator")
			 
			 .SetOptimize(5, 20, 1);

	_highLevel =
		Param(nameof(HighLevel), 20)
		.SetDisplay("High Level", "Upper threshold", "Indicator");

	_lowLevel =
		Param(nameof(LowLevel), -20)
		.SetDisplay("Low Level", "Lower threshold", "Indicator");

		_candleType =
		Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
		.SetDisplay("Candle Type", "Timeframe for candles", "General");

	_stopLoss =
		Param(nameof(StopLoss), 1m)
		.SetDisplay("Stop Loss %", "Stop loss percentage", "Risk");

	_takeProfit =
		Param(nameof(TakeProfit), 2m)
		.SetDisplay("Take Profit %", "Take profit percentage", "Risk");

	_buyOpen = Param(nameof(BuyOpen), true)
			   .SetDisplay("Buy Open", "Allow buy entries", "Trading");

	_sellOpen =
		Param(nameof(SellOpen), true)
		.SetDisplay("Sell Open", "Allow sell entries", "Trading");

	_buyClose =
		Param(nameof(BuyClose), true)
		.SetDisplay("Buy Close", "Allow closing long", "Trading");

	_sellClose =
		Param(nameof(SellClose), true)
		.SetDisplay("Sell Close", "Allow closing short", "Trading");

	_factor =
		Param(nameof(Factor), 0.5m)
		.SetDisplay("Factor", "Smoothing factor for STC calculations", "Indicator");

		_signalCooldownBars = Param(nameof(SignalCooldownBars), 8)
			.SetGreaterThanZero()
			.SetDisplay("Signal Cooldown", "Bars to wait between reversals", "Trading");
	}

	/// <inheritdoc />
	protected override void OnReseted() {
	base.OnReseted();
	_prevStc = null;
	_cooldownRemaining = 0;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time) {
	base.OnStarted2(time);

	_stc = new SchaffTrixTrendCycle { FastLength = FastTrixLength,
					  SlowLength = SlowTrixLength,
					  Cycle = Cycle,
					  Factor = Factor };
	_prevStc = null;
	_cooldownRemaining = 0;

	var subscription = SubscribeCandles(CandleType);

	subscription.Bind(_stc, ProcessStc).Start();

	StartProtection(stopLoss: new Unit(StopLoss, UnitTypes.Percent),
			takeProfit: new Unit(TakeProfit, UnitTypes.Percent));
	}

	private void ProcessStc(ICandleMessage candle, decimal stc) {
	if (candle.State != CandleStates.Finished)
		return;

	if (_cooldownRemaining > 0)
		_cooldownRemaining--;

	if (_prevStc is null) {
		_prevStc = stc;
		return;
	}

	var prev = _prevStc.Value;

	var crossedUp = stc > HighLevel && prev <= HighLevel;
	var crossedDown = stc < LowLevel && prev >= LowLevel;
	var longExit = BuyClose && Position > 0 && stc < 0m;
	var shortExit = SellClose && Position < 0 && stc > 0m;

	if (longExit) {
		SellMarket(Position);
		_cooldownRemaining = SignalCooldownBars;
	} else if (shortExit) {
		BuyMarket(Math.Abs(Position));
		_cooldownRemaining = SignalCooldownBars;
	} else if (_cooldownRemaining == 0 && crossedUp && BuyOpen && Position <= 0) {
		BuyMarket(Volume + Math.Abs(Position));
		_cooldownRemaining = SignalCooldownBars;
	} else if (_cooldownRemaining == 0 && crossedDown && SellOpen && Position >= 0) {
		SellMarket(Volume + Math.Abs(Position));
		_cooldownRemaining = SignalCooldownBars;
	}

	_prevStc = stc;
	}

	/// <summary>
	/// Schaff Trend Cycle indicator based on TRIX MACD.
	/// </summary>
	private sealed class SchaffTrixTrendCycle : BaseIndicator {
	public int FastLength { get; set; }
	public int SlowLength { get; set; }
	public int Cycle { get; set; }
	public decimal Factor { get; set; } = 0.5m;

	private Trix _fast;
	private Trix _slow;
	private Highest _macdHigh;
	private Lowest _macdLow;
	private Highest _stHigh;
	private Lowest _stLow;
	private decimal _stPrev;
	private decimal _stcPrev;
	private bool _stPass;
	private bool _stcPass;
	private bool _inited;

	public override int NumValuesToInitialize => 1;

	protected override bool CalcIsFormed() => _inited && _slow.IsFormed && _stcPass;

	private void EnsureInit()
	{
		if (_inited) return;
		_fast = new Trix(FastLength);
		_slow = new Trix(SlowLength);
		_macdHigh = new Highest { Length = Cycle };
		_macdLow = new Lowest { Length = Cycle };
		_stHigh = new Highest { Length = Cycle };
		_stLow = new Lowest { Length = Cycle };
		_inited = true;
	}

	protected override IIndicatorValue OnProcess(IIndicatorValue input) {
		EnsureInit();

		var t = input.Time;

		var fast = _fast.Process(input).ToDecimal();
		var slow = _slow.Process(input).ToDecimal();

		var macd = fast - slow;

		var macdHigh = _macdHigh.Process(new DecimalIndicatorValue(_macdHigh, macd, t) { IsFinal = input.IsFinal }).ToDecimal();
		var macdLow = _macdLow.Process(new DecimalIndicatorValue(_macdLow, macd, t) { IsFinal = input.IsFinal }).ToDecimal();

		decimal st;
		if (macdHigh - macdLow != 0)
		st = (macd - macdLow) / (macdHigh - macdLow) * 100m;
		else
		st = _stPrev;

		if (_stPass)
		st = Factor * (st - _stPrev) + _stPrev;

		_stPrev = st;
		_stPass = true;

		var stHigh = _stHigh.Process(new DecimalIndicatorValue(_stHigh, st, t) { IsFinal = input.IsFinal }).ToDecimal();
		var stLow = _stLow.Process(new DecimalIndicatorValue(_stLow, st, t) { IsFinal = input.IsFinal }).ToDecimal();

		decimal stc;
		if (stHigh - stLow != 0)
		stc = (st - stLow) / (stHigh - stLow) * 200m - 100m;
		else
		stc = _stcPrev;

		if (_stcPass)
		stc = Factor * (stc - _stcPrev) + _stcPrev;

		_stcPrev = stc;
		_stcPass = true;

		return new DecimalIndicatorValue(this, stc, t) { IsFinal = input.IsFinal };
	}

	public override void Reset() {
		_fast?.Reset();
		_slow?.Reset();
		_macdHigh?.Reset();
		_macdLow?.Reset();
		_stHigh?.Reset();
		_stLow?.Reset();
		_stPrev = 0m;
		_stcPrev = 0m;
		_stPass = false;
		_stcPass = false;
		_inited = false;
		base.Reset();
	}
	}

	/// <summary>
	/// TRIX oscillator implementation.
	/// </summary>
	private sealed class Trix : BaseIndicator {
	private readonly ExponentialMovingAverage _ema1;
	private readonly ExponentialMovingAverage _ema2;
	private readonly ExponentialMovingAverage _ema3;
	private decimal? _prev;

	public Trix(int length)
	{
		_ema1 = new ExponentialMovingAverage { Length = length };
		_ema2 = new ExponentialMovingAverage { Length = length };
		_ema3 = new ExponentialMovingAverage { Length = length };
	}

	public override int NumValuesToInitialize => 1;

	protected override bool CalcIsFormed() => _ema3.IsFormed && _prev != null;

	protected override IIndicatorValue OnProcess(IIndicatorValue input) {
		var ema1 = _ema1.Process(input);
		var ema2 = _ema2.Process(ema1);
		var ema3 = _ema3.Process(ema2);

		var value = ema3.ToDecimal();
		decimal trix = 0m;

		if (_prev != null && _prev != 0)
		trix = (value - _prev.Value) / _prev.Value;

		_prev = value;

		return new DecimalIndicatorValue(this, trix, input.Time) { IsFinal = input.IsFinal };
	}

	public override void Reset() {
		_ema1.Reset();
		_ema2.Reset();
		_ema3.Reset();
		_prev = null;
		base.Reset();
	}
	}
}