Ver en GitHub

Estrategia CHO With Flat

Esta estrategia opera basándose en el cruce del Chaikin Oscillator y su media móvil. Se utiliza un filtro de Bandas de Bollinger para evitar operar durante mercados planos.

Parámetros

  • Candle Type – marco temporal de las velas de entrada.
  • Fast Period – período rápido del Chaikin Oscillator.
  • Slow Period – período lento del Chaikin Oscillator.
  • MA Period – período de la media móvil aplicada al oscilador.
  • MA Type – tipo de media móvil para la línea de señal.
  • Bollinger Period – período de las Bandas de Bollinger.
  • Std Deviation – desviación estándar para las Bandas de Bollinger.
  • Flat Threshold – ancho mínimo de banda (en puntos) para considerar el mercado activo.

Lógica de trading

  1. Calcular el Chaikin Oscillator y su media móvil.
  2. Construir Bandas de Bollinger sobre el precio para detectar mercado plano.
  3. Omitir operaciones si el ancho de la banda de Bollinger está por debajo de Flat Threshold.
  4. Comprar cuando el oscilador cruza por debajo de su línea de señal.
  5. Vender cuando el oscilador cruza por encima de su línea de señal.

La dirección de la posición siempre sigue el último cruce mientras el filtro plano evita operar en condiciones de mercado lateral.

using System;
using System.Collections.Generic;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Chaikin-style oscillator strategy with flat filter.
/// Uses difference between fast and slow EMA as oscillator, EMA as signal line,
/// and Bollinger Bands to detect flat market.
/// </summary>
public class ChoWithFlatStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _signalPeriod;
	private readonly StrategyParam<int> _bollingerPeriod;
	private readonly StrategyParam<decimal> _stdDeviation;
	private readonly StrategyParam<decimal> _flatThreshold;

	private ExponentialMovingAverage _fastEma;
	private ExponentialMovingAverage _slowEma;
	private ExponentialMovingAverage _signalEma;
	private decimal _prevOsc;
	private decimal _prevSignal;
	private bool _isInitialized;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int SignalPeriod { get => _signalPeriod.Value; set => _signalPeriod.Value = value; }
	public int BollingerPeriod { get => _bollingerPeriod.Value; set => _bollingerPeriod.Value = value; }
	public decimal StdDeviation { get => _stdDeviation.Value; set => _stdDeviation.Value = value; }
	public decimal FlatThreshold { get => _flatThreshold.Value; set => _flatThreshold.Value = value; }

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

		_fastPeriod = Param(nameof(FastPeriod), 3)
			.SetDisplay("Fast Period", "Fast EMA period", "Indicator");

		_slowPeriod = Param(nameof(SlowPeriod), 10)
			.SetDisplay("Slow Period", "Slow EMA period", "Indicator");

		_signalPeriod = Param(nameof(SignalPeriod), 9)
			.SetDisplay("Signal Period", "Signal line EMA period", "Indicator");

		_bollingerPeriod = Param(nameof(BollingerPeriod), 20)
			.SetDisplay("Bollinger Period", "Period for Bollinger Bands", "Flat Filter");

		_stdDeviation = Param(nameof(StdDeviation), 2.0m)
			.SetDisplay("Std Deviation", "Deviation for Bollinger Bands", "Flat Filter");

		_flatThreshold = Param(nameof(FlatThreshold), 0.005m)
			.SetDisplay("Flat Threshold", "Minimum band width ratio to detect trending", "Flat Filter");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_fastEma = default;
		_slowEma = default;
		_signalEma = default;
		_prevOsc = 0;
		_prevSignal = 0;
		_isInitialized = false;
	}

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

		_fastEma = new ExponentialMovingAverage { Length = FastPeriod };
		_slowEma = new ExponentialMovingAverage { Length = SlowPeriod };
		_signalEma = new ExponentialMovingAverage { Length = SignalPeriod };

		Indicators.Add(_fastEma);
		Indicators.Add(_slowEma);
		Indicators.Add(_signalEma);

		var bollinger = new BollingerBands { Length = BollingerPeriod, Width = StdDeviation };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.BindEx(bollinger, ProcessCandle)
			.Start();

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

	private void ProcessCandle(ICandleMessage candle, IIndicatorValue bbValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		var bb = (BollingerBandsValue)bbValue;
		if (bb.UpBand is not decimal upperBand ||
			bb.LowBand is not decimal lowerBand ||
			bb.MovingAverage is not decimal middleBand)
			return;

		var fastResult = _fastEma.Process(candle.ClosePrice, candle.OpenTime, true);
		var slowResult = _slowEma.Process(candle.ClosePrice, candle.OpenTime, true);

		if (!fastResult.IsFormed || !slowResult.IsFormed)
			return;

		var oscValue = fastResult.ToDecimal() - slowResult.ToDecimal();

		var sigResult = _signalEma.Process(oscValue, candle.OpenTime, true);
		if (!sigResult.IsFormed)
			return;

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		var signalValue = sigResult.ToDecimal();

		if (!_isInitialized)
		{
			_prevOsc = oscValue;
			_prevSignal = signalValue;
			_isInitialized = true;
			return;
		}

		var bandWidth = upperBand - lowerBand;
		if (middleBand != 0 && (bandWidth / middleBand) < FlatThreshold)
		{
			_prevOsc = oscValue;
			_prevSignal = signalValue;
			return;
		}

		var wasAbove = _prevOsc > _prevSignal;
		var isAbove = oscValue > signalValue;

		if (!wasAbove && isAbove)
		{
			if (Position <= 0)
				BuyMarket();
		}
		else if (wasAbove && !isAbove)
		{
			if (Position >= 0)
				SellMarket();
		}

		_prevOsc = oscValue;
		_prevSignal = signalValue;
	}
}