Открыть на GitHub

Стратегия CHO Smoothed EA

Обзор

Стратегия воспроизводит логику оригинального советника «CHO Smoothed EA». На каждой закрывшейся свече рассчитывается осциллятор Chaikin, для сигнальной линии используется настраиваемое скользящее среднее. Дополнительные фильтры позволяют ограничить торговлю по времени, выбрать направление сделок и проверять сигналы относительно нулевой линии. После подтверждения сигнала стратегия открывает позицию по рынку и сопровождает её фиксированными дистанциями в пунктах для стоп-лосса, тейк-профита и трейлинг-стопа.

Логика торговли

  • Значения осциллятора Chaikin рассчитываются по завершённым свечам с заданными быстрым и медленным периодами.
  • Скользящее среднее создаёт сглаженную сигнальную линию; его период и тип можно изменять.
  • Покупка выполняется при пересечении осциллятора выше сигнальной линии, продажа — при обратном пересечении. Сигналы можно инвертировать.
  • При включённой проверке по нулевой линии для покупок оба значения должны быть ниже нуля, для продаж — выше нуля.
  • Можно автоматически закрывать встречные позиции перед входом или, наоборот, ждать пока позиция станет нулевой. Также доступен режим «только одна позиция».
  • Торговлю можно ограничить дневным временным окном; поддерживаются интервалы, переходящие через полночь.
  • После входа значения в пунктах переводятся в ценовые уровни с учётом шага цены инструмента, далее свечи контролируются на предмет срабатывания стоп-лосса, тейк-профита и трейлинг-стопа.

Управление рисками

  • Стоп-лосс и тейк-профит рассчитываются от цены входа как количество пунктов, умноженное на шаг цены.
  • Трейлинг-стоп активируется после прохождения ценой заданного минимального хода и затем следует за ценой на установленном расстоянии.
  • При достижении любого защитного уровня позиция закрывается рыночной заявкой, а все контрольные уровни сбрасываются.

Параметры

  • Candle Type — таймфрейм свечей для расчётов.
  • Fast Period / Slow Period — быстрый и медленный периоды осциллятора Chaikin.
  • Signal MA Period / Signal MA Type — параметры сглаживающего скользящего среднего.
  • Use Zero Level — требование нахождения показаний по соответствующую сторону нуля.
  • Trade Mode — разрешённое направление торговли (только покупка, только продажа или обе стороны).
  • Reverse Signals — инвертирование сигналов.
  • Close Opposite — закрывать ли встречные позиции перед входом.
  • Only One Position — запрещать ли открытие новой позиции при уже открытой.
  • Use Time Control / Start Time / End Time — включение и настройки торгового окна.
  • Stop Loss (pts) — расстояние стоп-лосса в пунктах.
  • Take Profit (pts) — расстояние тейк-профита в пунктах.
  • Trailing Stop (pts) — дистанция трейлинг-стопа в пунктах.
  • Trailing Step (pts) — минимальный ход цены в пунктах для переноса трейлинг-стопа.

Дополнительные рекомендации

  • Перед запуском задайте свойство Volume, чтобы выбрать объём сделки.
  • Поскольку используются рыночные заявки, учитывайте ликвидность и возможное проскальзывание.
  • Если время начала и окончания торгового окна совпадают, стратегия не будет совершать сделки — это соответствует поведению исходного советника.
using System;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Trades CCI oscillator zero-line crossovers with signal MA smoothing.
/// Originally based on Chaikin Oscillator concept, adapted to use CCI.
/// </summary>
public class ChoSmoothedEaStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _cciPeriod;
	private readonly StrategyParam<int> _maPeriod;

	private CommodityChannelIndex _cci;
	private readonly Queue<decimal> _cciHistory = new();
	private decimal? _prevCci;
	private decimal? _prevSignal;

	/// <summary>
	/// Initializes a new instance of the <see cref="ChoSmoothedEaStrategy"/> class.
	/// </summary>
	public ChoSmoothedEaStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe for signal calculations", "General");

		_cciPeriod = Param(nameof(CciPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("CCI Period", "Period for CCI oscillator", "Indicator");

		_maPeriod = Param(nameof(MaPeriod), 9)
			.SetGreaterThanZero()
			.SetDisplay("Signal MA Period", "Period of smoothing moving average on CCI", "Indicator");
	}

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

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

	/// <summary>
	/// Period of the smoothing moving average.
	/// </summary>
	public int MaPeriod
	{
		get => _maPeriod.Value;
		set => _maPeriod.Value = value;
	}

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

		_prevCci = null;
		_prevSignal = null;
		_cciHistory.Clear();

		_cci = new CommodityChannelIndex { Length = CciPeriod };

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

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

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

		_cciHistory.Enqueue(cciValue);
		while (_cciHistory.Count > MaPeriod)
			_cciHistory.Dequeue();

		if (!_cci.IsFormed)
			return;

		if (_cciHistory.Count < MaPeriod)
		{
			_prevCci = cciValue;
			return;
		}

		// Calculate signal line (SMA of CCI)
		var sum = 0m;
		var history = _cciHistory.ToArray();
		foreach (var v in history)
			sum += v;
		var signalValue = sum / history.Length;

		if (_prevCci is null || _prevSignal is null)
		{
			_prevCci = cciValue;
			_prevSignal = signalValue;
			return;
		}

		var crossUp = _prevCci <= _prevSignal && cciValue > signalValue;
		var crossDown = _prevCci >= _prevSignal && cciValue < signalValue;

		var volume = Volume;
		if (volume <= 0)
			volume = 1;

		var minSpread = 25m;

		if (crossUp && Math.Abs(cciValue - signalValue) >= minSpread)
		{
			if (Position <= 0)
				BuyMarket(Position < 0 ? Math.Abs(Position) + volume : volume);
		}
		else if (crossDown && Math.Abs(cciValue - signalValue) >= minSpread)
		{
			if (Position >= 0)
				SellMarket(Position > 0 ? Math.Abs(Position) + volume : volume);
		}

		_prevCci = cciValue;
		_prevSignal = signalValue;
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		_prevCci = null;
		_prevSignal = null;
		_cci = null;
		_cciHistory.Clear();

		base.OnReseted();
	}
}