Ver en GitHub

Estrategia CCI VWAP

El enfoque CCI VWAP intenta capturar reversiones intradía cuando el momentum y el precio se alejan del precio promedio ponderado por volumen. Al observar el Índice de Canal de Materias Primas junto con el nivel VWAP, el sistema mide la fuerza de los movimientos recientes en relación con un punto de referencia de valor justo.

Las pruebas indican un rendimiento anual promedio de aproximadamente 70%. Funciona mejor en el mercado de acciones.

Una configuración de compra surge cuando el CCI cae por debajo de -100 y el mercado cotiza por debajo del VWAP, señalando que la presión vendedora puede estar agotada. Un corto ocurre cuando el CCI sube por encima de +100 con el precio sobre el VWAP, destacando un rally extendido vulnerable a una corrección. Las posiciones se cierran una vez que el precio recupera el VWAP en dirección opuesta.

Esta estrategia está diseñada para traders intradía que prefieren operar en los extremos pero aun así confían en niveles objetivos para las salidas. El stop-loss definido ayuda a gestionar el riesgo si el momentum no revierte rápidamente a la media.

Detalles

  • Criterios de entrada:
    • Largo: CCI < -100 && Price < VWAP (oversold below VWAP)
    • Corto: CCI > 100 && Price > VWAP (overbought above VWAP)
  • Largo/Corto: Ambos lados.
  • Criterios de salida:
    • Largo: Salir del largo cuando el precio suba por encima del VWAP
    • Corto: Salir del corto cuando el precio caiga por debajo del VWAP
  • Stops: Sí.
  • Valores predeterminados:
    • CciPeriod = 20
    • StopLossPercent = 2m
    • CandleType = TimeSpan.FromMinutes(5)
  • Filtros:
    • Categoría: Mixto
    • Dirección: Ambos
    • Indicadores: CCI VWAP
    • Stops: Sí
    • Complejidad: Intermedio
    • Marco temporal: Intradía
    • Estacionalidad: No
    • Redes neuronales: No
    • Divergencia: No
    • Nivel de riesgo: Medio
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 that uses CCI and VWAP indicators to identify oversold and overbought conditions.
/// Enters long when CCI is below -100 and price is below VWAP.
/// Enters short when CCI is above 100 and price is above VWAP.
/// </summary>
public class CciVwapStrategy : Strategy
{
	private readonly StrategyParam<int> _cciPeriod;
	private readonly StrategyParam<int> _cooldownBars;
	private readonly StrategyParam<decimal> _stopLossPercent;
	private readonly StrategyParam<DataType> _candleType;
	
	private CommodityChannelIndex _cci;
	private int _cooldown;
	private DateTime _vwapDate;
	private decimal _vwapCumPv;
	private decimal _vwapCumVol;
	
	/// <summary>
	/// CCI period parameter.
	/// </summary>
	public int CciPeriod
	{
		get => _cciPeriod.Value;
		set => _cciPeriod.Value = value;
	}
	
	/// <summary>
	/// Bars to wait between trades.
	/// </summary>
	public int CooldownBars
	{
		get => _cooldownBars.Value;
		set => _cooldownBars.Value = value;
	}

	/// <summary>
	/// Stop-loss percentage parameter.
	/// </summary>
	public decimal StopLossPercent
	{
		get => _stopLossPercent.Value;
		set => _stopLossPercent.Value = value;
	}
	
	/// <summary>
	/// Candle type parameter.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}
	
	/// <summary>
	/// Strategy constructor.
	/// </summary>
	public CciVwapStrategy()
	{
		_cciPeriod = Param(nameof(CciPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("CCI period", "CCI indicator period", "Indicators")
			
			.SetOptimize(10, 30, 5);

		_cooldownBars = Param(nameof(CooldownBars), 60)
			.SetRange(1, 200)
			.SetDisplay("Cooldown Bars", "Bars between trades", "General");
			
		_stopLossPercent = Param(nameof(StopLossPercent), 2m)
			.SetGreaterThanZero()
			.SetDisplay("Stop-loss %", "Stop-loss as percentage of entry price", "Risk Management")
			
			.SetOptimize(1m, 3m, 0.5m);
			
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).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();

		_cci = null;
		_cooldown = 0;
		_vwapDate = default;
		_vwapCumPv = 0m;
		_vwapCumVol = 0m;
	}

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

		// Initialize CCI indicator
		_cci = new CommodityChannelIndex
		{
			Length = CciPeriod
		};

		var dummyEma = new ExponentialMovingAverage { Length = 10 };

		// Bind CCI to candle subscription
		var candlesSubscription = SubscribeCandles(CandleType)
			.Bind(_cci, dummyEma, ProcessCandle)
			.Start();
		
		// Setup chart if available
		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, candlesSubscription);
			DrawIndicator(area, _cci);
			DrawOwnTrades(area);
		}
	}
	
	private void ProcessCandle(ICandleMessage candle, decimal cci, decimal dummyValue)
	{
		// Skip unfinished candles
		if (candle.State != CandleStates.Finished)
			return;
			
		var date = candle.ServerTime.Date;
		if (_vwapDate != date)
		{
			_vwapDate = date;
			_vwapCumPv = 0m;
			_vwapCumVol = 0m;
		}

		_vwapCumPv += candle.ClosePrice * candle.TotalVolume;
		_vwapCumVol += candle.TotalVolume;
		if (_vwapCumVol <= 0m)
			return;

		var currentVwap = _vwapCumPv / _vwapCumVol;
		if (currentVwap == 0)
			return;

		if (_cooldown > 0)
			_cooldown--;
			
		// Long signal: CCI below -100 and price below VWAP
		if (_cooldown == 0 && cci < -100 && candle.ClosePrice < currentVwap && Position <= 0)
		{
			BuyMarket();
			_cooldown = CooldownBars;
		}
		else if (_cooldown == 0 && cci > 100 && candle.ClosePrice > currentVwap && Position >= 0)
		{
			SellMarket();
			_cooldown = CooldownBars;
		}
		else if (Position > 0 && candle.ClosePrice > currentVwap)
		{
			SellMarket();
			_cooldown = CooldownBars;
		}
		else if (Position < 0 && candle.ClosePrice < currentVwap)
		{
			BuyMarket();
			_cooldown = CooldownBars;
		}
	}
}