Открыть на GitHub

Стратегия Volume EA

Обзор

Стратегия торгует на всплесках объёма и индикаторе CCI. Сделки открываются в начале нового часа, когда объём предыдущей свечи превышает объём позапрошлой в Factor раз. CCI используется для фильтрации сигналов в заданных диапазонах.

Правила

  • Одновременно открыта только одна позиция.
  • В начале каждого часа:
    • Покупка, если:
      • Предыдущая свеча бычья.
      • Предыдущий объём > позапрошлого × Factor.
      • CCI между CciLevel1 и CciLevel2.
    • Продажа, если:
      • Предыдущая свеча медвежья.
      • Предыдущий объём > позапрошлого × Factor.
      • CCI между CciLevel4 и CciLevel3.
  • Прибыль защищается трейлинг-стопом TrailingStop в шагах цены.
  • Все позиции закрываются, когда час равен 23.

Параметры

  • Factor – порог множителя объёма.
  • TrailingStop – расстояние трейлинг-стопа в шагах цены.
  • CciLevel1 / CciLevel2 – границы CCI для покупок.
  • CciLevel3 / CciLevel4 – границы CCI для продаж.
  • CandleType – таймфрейм свечей для расчётов.
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;
	}
}