Ver en GitHub

Estrategia de Tendencia Chande Kroll

Estrategia que utiliza el stop Chande Kroll con un filtro de tendencia SMA. Se abre una posición larga cuando el cierre cruza por encima del stop inferior y está por encima de la SMA. La posición se cierra cuando el cierre cae por debajo del stop superior. El tamaño de la posición se basa en el cierre mínimo en 1560 barras y el multiplicador de riesgo.

Detalles

  • Criterios de entrada:
    • Largo: previous close <= previous low stop && Close > low stop && Close > SMA
  • Largo/Corto: Solo largos
  • Criterios de salida:
    • Largo: Close < high stop
  • Stops: Stop Chande Kroll (extremos Donchian ± ATR)
  • Valores predeterminados:
    • CalcMode = CalcMode.Exponential
    • RiskMultiplier = 5m
    • AtrPeriod = 10
    • AtrMultiplier = 3m
    • StopLength = 21
    • SmaLength = 21
    • CandleType = TimeSpan.FromHours(1).TimeFrame()
  • Filtros:
    • Categoría: Tendencia
    • Dirección: Solo largos
    • Indicadores: ATR, Donchian, SMA, Lowest
    • Stops: Sí
    • Complejidad: Principiante
    • Marco temporal: Medio plazo
    • Estacionalidad: No
    • Redes neuronales: No
    • Divergencia: No
    • Nivel de riesgo: Medio
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>
/// Chande Kroll Trend strategy.
/// The high stop is the highest high of the last StopLength candles minus AtrMultiplier times ATR(AtrPeriod), the low stop the lowest
/// low plus the same distance. Long only: buys when the close crosses above the low stop while above SMA(SmaLength), and closes the
/// long when the close falls below the high stop. The order size is RiskMultiplier percent of the capital divided by the lowest close
/// of the last 1560 candles; in Exponential mode the capital is the current equity, in Linear mode the starting capital.
/// </summary>
public class ChandeKrollTrendStrategy : Strategy
{
	/// <summary>
	/// Position sizing mode.
	/// </summary>
	public enum CalcModes
	{
		/// <summary>
		/// Size from the starting capital.
		/// </summary>
		Linear,

		/// <summary>
		/// Size from the current equity.
		/// </summary>
		Exponential,
	}

	private const int _sizingLookback = 1560;

	private readonly StrategyParam<CalcModes> _calcMode;
	private readonly StrategyParam<decimal> _riskMultiplier;
	private readonly StrategyParam<int> _atrPeriod;
	private readonly StrategyParam<decimal> _atrMultiplier;
	private readonly StrategyParam<int> _stopLength;
	private readonly StrategyParam<int> _smaLength;
	private readonly StrategyParam<DataType> _candleType;

	private Lowest _lowestClose;
	private decimal? _prevClose;
	private decimal? _prevLowStop;
	private decimal _initialCapital;

	/// <summary>
	/// Position sizing mode.
	/// </summary>
	public CalcModes CalcMode
	{
		get => _calcMode.Value;
		set => _calcMode.Value = value;
	}

	/// <summary>
	/// Percent of the capital committed to a position.
	/// </summary>
	public decimal RiskMultiplier
	{
		get => _riskMultiplier.Value;
		set => _riskMultiplier.Value = value;
	}

	/// <summary>
	/// ATR period.
	/// </summary>
	public int AtrPeriod
	{
		get => _atrPeriod.Value;
		set => _atrPeriod.Value = value;
	}

	/// <summary>
	/// ATR multiplier of the stops.
	/// </summary>
	public decimal AtrMultiplier
	{
		get => _atrMultiplier.Value;
		set => _atrMultiplier.Value = value;
	}

	/// <summary>
	/// Candles of the Donchian extremes.
	/// </summary>
	public int StopLength
	{
		get => _stopLength.Value;
		set => _stopLength.Value = value;
	}

	/// <summary>
	/// Period of the trend SMA.
	/// </summary>
	public int SmaLength
	{
		get => _smaLength.Value;
		set => _smaLength.Value = value;
	}

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

	/// <summary>
	/// Constructor.
	/// </summary>
	public ChandeKrollTrendStrategy()
	{
		_calcMode = Param(nameof(CalcMode), CalcModes.Exponential)
			.SetDisplay("Calc Mode", "Position sizing mode", "Sizing");

		_riskMultiplier = Param(nameof(RiskMultiplier), 5m)
			.SetGreaterThanZero()
			.SetDisplay("Risk Multiplier", "Percent of the capital committed to a position", "Sizing");

		_atrPeriod = Param(nameof(AtrPeriod), 10)
			.SetGreaterThanZero()
			.SetDisplay("ATR Period", "ATR period", "Stops");

		_atrMultiplier = Param(nameof(AtrMultiplier), 3m)
			.SetGreaterThanZero()
			.SetDisplay("ATR Multiplier", "ATR multiplier of the stops", "Stops");

		_stopLength = Param(nameof(StopLength), 21)
			.SetGreaterThanZero()
			.SetDisplay("Stop Length", "Candles of the Donchian extremes", "Stops");

		_smaLength = Param(nameof(SmaLength), 21)
			.SetGreaterThanZero()
			.SetDisplay("SMA Length", "Period of the trend SMA", "Trend");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles to use", "General");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_lowestClose = null;
		_prevClose = null;
		_prevLowStop = null;
		_initialCapital = 0m;
	}

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

		_prevClose = null;
		_prevLowStop = null;
		_initialCapital = Portfolio?.BeginValue is decimal begin && begin > 0 ? begin : Portfolio?.CurrentValue ?? 0m;
		_lowestClose = new Lowest { Length = _sizingLookback };

		var atr = new AverageTrueRange { Length = AtrPeriod };
		var highest = new Highest { Length = StopLength };
		var lowest = new Lowest { Length = StopLength };
		var sma = new SimpleMovingAverage { Length = SmaLength };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.BindEx(atr, highest, lowest, sma, ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, sma);
			DrawOwnTrades(area);
		}
	}

	private decimal GetOrderVolume(decimal lowestClose)
	{
		var capital = CalcMode == CalcModes.Exponential ? _initialCapital + PnL : _initialCapital;

		if (capital <= 0 || lowestClose <= 0)
			return Volume;

		var volume = capital * RiskMultiplier / 100m / lowestClose;
		var step = Security?.VolumeStep ?? 1m;

		if (step > 0)
			volume = Math.Floor(volume / step) * step;

		return volume > 0 ? volume : Volume;
	}

	private void ProcessCandle(ICandleMessage candle, IIndicatorValue atrValue, IIndicatorValue highestValue, IIndicatorValue lowestValue, IIndicatorValue smaValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		var close = candle.ClosePrice;

		// The sizing low uses whatever history is available until the full lookback is reached.
		var lowestClose = _lowestClose.Process(new DecimalIndicatorValue(_lowestClose, close, candle.OpenTime) { IsFinal = true }).GetValue<decimal>();

		if (!atrValue.IsFormed || !highestValue.IsFormed || !lowestValue.IsFormed)
			return;

		var distance = AtrMultiplier * atrValue.GetValue<decimal>();
		var highStop = highestValue.GetValue<decimal>() - distance;
		var lowStop = lowestValue.GetValue<decimal>() + distance;

		var prevClose = _prevClose;
		var prevLowStop = _prevLowStop;
		_prevClose = close;
		_prevLowStop = lowStop;

		if (!smaValue.IsFormed || prevClose is not decimal pc || prevLowStop is not decimal pl)
			return;

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		if (Position > 0)
		{
			if (close < highStop)
				SellMarket(Position);

			return;
		}

		if (Position == 0 && pc <= pl && close > lowStop && close > smaValue.GetValue<decimal>())
			BuyMarket(GetOrderVolume(lowestClose));
	}
}