Ver en GitHub

Estrategia Volume EA

Descripción general

Esta estrategia opera basándose en picos de volumen y el Índice de Canal de Materias Primas (CCI). Abre posiciones al inicio de una nueva hora cuando el volumen de la vela anterior supera al de la vela previa por un factor configurable. Los valores del CCI deben caer dentro de bandas específicas para confirmar la señal.

Reglas

  • Solo hay una posición abierta a la vez.
  • Al comienzo de cada hora:
    • Entrada larga cuando:
      • La vela anterior es alcista.
      • Volumen anterior > volumen previo × Factor.
      • El CCI está entre CciLevel1 y CciLevel2.
    • Entrada corta cuando:
      • La vela anterior es bajista.
      • Volumen anterior > volumen previo × Factor.
      • El CCI está entre CciLevel4 y CciLevel3.
  • Un stop de seguimiento de TrailingStop pasos de precio protege las ganancias.
  • Todas las posiciones se cierran cuando la hora es igual a 23.

Parámetros

  • Factor – umbral multiplicador de volumen.
  • TrailingStop – distancia de seguimiento en pasos de precio.
  • CciLevel1 / CciLevel2 – límites del CCI para operaciones largas.
  • CciLevel3 / CciLevel4 – límites del CCI para operaciones cortas.
  • CandleType – marco temporal de velas utilizado para los cálculos.
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;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Strategy based on volume spikes and CCI ranges.
/// </summary>
public class VolumeEaStrategy : Strategy
{
	private readonly StrategyParam<decimal> _factor;
	private readonly StrategyParam<decimal> _trailingStop;
	private readonly StrategyParam<decimal> _cciLevel1;
	private readonly StrategyParam<decimal> _cciLevel2;
	private readonly StrategyParam<decimal> _cciLevel3;
	private readonly StrategyParam<decimal> _cciLevel4;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _prevVolume;
	private decimal _prevPrevVolume;
	private decimal _prevOpen;
	private decimal _prevClose;
	private decimal _longStop;
	private decimal _shortStop;

	/// <summary>
	/// Volume multiplier threshold.
	/// </summary>
	public decimal Factor
	{
		get => _factor.Value;
		set => _factor.Value = value;
	}

	/// <summary>
	/// Trailing stop distance in price steps.
	/// </summary>
	public decimal TrailingStop
	{
		get => _trailingStop.Value;
		set => _trailingStop.Value = value;
	}

	/// <summary>
	/// Lower CCI level for long trades.
	/// </summary>
	public decimal CciLevel1
	{
		get => _cciLevel1.Value;
		set => _cciLevel1.Value = value;
	}

	/// <summary>
	/// Upper CCI level for long trades.
	/// </summary>
	public decimal CciLevel2
	{
		get => _cciLevel2.Value;
		set => _cciLevel2.Value = value;
	}

	/// <summary>
	/// Upper CCI level for short trades.
	/// </summary>
	public decimal CciLevel3
	{
		get => _cciLevel3.Value;
		set => _cciLevel3.Value = value;
	}

	/// <summary>
	/// Lower CCI level for short trades.
	/// </summary>
	public decimal CciLevel4
	{
		get => _cciLevel4.Value;
		set => _cciLevel4.Value = value;
	}

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

	/// <summary>
	/// Initializes a new instance of the strategy.
	/// </summary>
	public VolumeEaStrategy()
	{
		_factor = Param(nameof(Factor), 1.55m)
			.SetGreaterThanZero()
			.SetDisplay("Factor", "Volume multiplier", "Trading");

		_trailingStop = Param(nameof(TrailingStop), 350m)
			.SetGreaterThanZero()
			.SetDisplay("Trailing Stop", "Trailing distance in steps", "Risk");

		_cciLevel1 = Param(nameof(CciLevel1), 50m)
			.SetDisplay("CCI Level1", "Lower CCI for buys", "Trading");

		_cciLevel2 = Param(nameof(CciLevel2), 190m)
			.SetDisplay("CCI Level2", "Upper CCI for buys", "Trading");

		_cciLevel3 = Param(nameof(CciLevel3), -50m)
			.SetDisplay("CCI Level3", "Upper CCI for sells", "Trading");

		_cciLevel4 = Param(nameof(CciLevel4), -190m)
			.SetDisplay("CCI Level4", "Lower CCI for sells", "Trading");

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

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevVolume = 0;
		_prevPrevVolume = 0;
		_prevOpen = 0;
		_prevClose = 0;
		_longStop = 0;
		_shortStop = 0;
	}

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

		var cci = new CommodityChannelIndex { Length = 14 };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(cci, ProcessCandle)
			.Start();

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

	}

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

		var currentVolume = candle.TotalVolume;

		{
			var trailDist = TrailingStop;
			var volumeOk = _prevVolume > _prevPrevVolume * Factor;

			if (volumeOk)
			{
				if (_prevClose > _prevOpen && cciValue > CciLevel1 && cciValue < CciLevel2 && Position <= 0)
				{
					if (Position < 0) BuyMarket();
					BuyMarket();
					_longStop = candle.ClosePrice - trailDist;
					_shortStop = 0m;
				}
				else if (_prevClose < _prevOpen && cciValue < CciLevel3 && cciValue > CciLevel4 && Position >= 0)
				{
					if (Position > 0) SellMarket();
					SellMarket();
					_shortStop = candle.ClosePrice + trailDist;
					_longStop = 0m;
				}
			}

			if (Position > 0)
			{
				var candidate = candle.ClosePrice - trailDist;
				if (candidate > _longStop)
					_longStop = candidate;

				if (candle.ClosePrice <= _longStop)
				{
					SellMarket();
					_longStop = 0m;
				}
			}
			else if (Position < 0)
			{
				var candidate = candle.ClosePrice + trailDist;
				if (_shortStop == 0m || candidate < _shortStop)
					_shortStop = candidate;

				if (candle.ClosePrice >= _shortStop)
				{
					BuyMarket();
					_shortStop = 0m;
				}
			}
		}

		_prevPrevVolume = _prevVolume;
		_prevVolume = currentVolume;
		_prevOpen = candle.OpenPrice;
		_prevClose = candle.ClosePrice;
	}
}