Auf GitHub ansehen

CCI Automated

CCI Automated is a reversal strategy that reacts to Commodity Channel Index (CCI) threshold crossings. It goes long when CCI rises above −80 after dipping below −90, and goes short when CCI falls below 80 after exceeding 90. The system duplicates trades up to a user-defined limit, manages risk with fixed take-profit and stop-loss levels, and trails profits with a configurable trailing stop.

The approach aims to catch early momentum shifts after oversold or overbought conditions. By stacking multiple positions and moving the stop as price advances, it attempts to capitalize on sustained reversals while capping downside risk.

Details

  • Entry Criteria: CCI crosses above -80 after being below -90 for longs; crosses below 80 after being above 90 for shorts.
  • Long/Short: Both directions.
  • Exit Criteria: Stop loss, take profit, or trailing stop.
  • Stops: Yes.
  • Default Values:
    • CciPeriod = 9
    • TradesDuplicator = 3
    • Volume = 0.03
    • StopLoss = 50
    • TakeProfit = 200
    • TrailingStop = 50
    • CandleType = TimeSpan.FromMinutes(5)
  • Filters:
    • Category: Mean Reversion
    • Direction: Both
    • Indicators: CCI
    • Stops: Yes
    • Complexity: Basic
    • Timeframe: Intraday (5m)
    • Seasonality: No
    • Neural Networks: No
    • Divergence: No
    • Risk Level: Medium
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 trades based on CCI crossing specific thresholds.
/// </summary>
public class CciAutomatedStrategy : Strategy
{
	private readonly StrategyParam<int> _cciPeriod;
	private readonly StrategyParam<int> _tradesDuplicator;
	private readonly StrategyParam<decimal> _stopLoss;
	private readonly StrategyParam<decimal> _takeProfit;
	private readonly StrategyParam<decimal> _trailingStop;
	private readonly StrategyParam<DataType> _candleType;

	private decimal? _prevCci;
	private decimal? _trailPrice;

	/// <summary>
	/// CCI calculation period.
	/// </summary>
	public int CciPeriod
	{
		get => _cciPeriod.Value;
		set => _cciPeriod.Value = value;
	}

	/// <summary>
	/// Maximum number of duplicated trades.
	/// </summary>
	public int TradesDuplicator
	{
		get => _tradesDuplicator.Value;
		set => _tradesDuplicator.Value = value;
	}


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

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

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

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

	/// <summary>
	/// Initializes a new instance of the <see cref="CciAutomatedStrategy" /> class.
	/// </summary>
	public CciAutomatedStrategy()
	{
		_cciPeriod = Param(nameof(CciPeriod), 9)
			.SetRange(5, 50)
			.SetDisplay("CCI Period", "CCI indicator length", "Indicators")
			;

		_tradesDuplicator = Param(nameof(TradesDuplicator), 3)
			.SetRange(1, 10)
			.SetDisplay("Trades Duplicator", "Maximum number of concurrent trades", "General")
			;


		_stopLoss = Param(nameof(StopLoss), 50m)
			.SetRange(10m, 200m)
			.SetDisplay("Stop Loss", "Stop loss in price units", "Risk")
			;

		_takeProfit = Param(nameof(TakeProfit), 200m)
			.SetRange(10m, 500m)
			.SetDisplay("Take Profit", "Take profit in price units", "Risk")
			;

		_trailingStop = Param(nameof(TrailingStop), 50m)
			.SetRange(10m, 200m)
			.SetDisplay("Trailing Stop", "Trailing stop in price units", "Risk")
			;

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevCci = null;
		_trailPrice = null;
	}

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

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

		var cci = new CommodityChannelIndex { Length = CciPeriod };

		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;

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		var maxVolume = TradesDuplicator * Volume;

		if (_prevCci is decimal prev)
		{
			if (prev < -90m && cciValue > -80m && Position + Volume <= maxVolume)
			{
				BuyMarket();
				_trailPrice = candle.ClosePrice - TrailingStop;
			}
			else if (prev > 90m && cciValue < 80m && Position - Volume >= -maxVolume)
			{
				SellMarket();
				_trailPrice = candle.ClosePrice + TrailingStop;
			}
		}

		if (Position > 0)
		{
			var candidate = candle.ClosePrice - TrailingStop;
			if (_trailPrice is null || candidate > _trailPrice)
				_trailPrice = candidate;
			if (_trailPrice is decimal tp && candle.ClosePrice <= tp)
			{
				SellMarket();
				_trailPrice = null;
			}
		}
		else if (Position < 0)
		{
			var candidate = candle.ClosePrice + TrailingStop;
			if (_trailPrice is null || candidate < _trailPrice)
				_trailPrice = candidate;
			if (_trailPrice is decimal tp && candle.ClosePrice >= tp)
			{
				BuyMarket();
				_trailPrice = null;
			}
		}

		_prevCci = cciValue;
	}

	/// <inheritdoc />
	protected override void OnPositionReceived(Position position)
	{
		base.OnPositionReceived(position);

		if (Position == 0)
			_trailPrice = null;
	}
}