Открыть на GitHub

Cronex AC

Стратегия Cronex AC переносит классического советника Cronex Acceleration/Deceleration (AC) на высокоуровневый API StockSharp. Она сглаживает осциллятор Accelerator (AC) двумя последовательными скользящими средними и реагирует на пересечение быстрой и медленной линий. Бычье пересечение открывает длинную позицию и закрывает короткую, медвежье — наоборот.

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

  1. Рассчитываются значения осциллятора Acceleration/Deceleration по выбранной серии свечей.
  2. Осциллятор последовательно сглаживается выбранным типом скользящей средней дважды: первое сглаживание формирует «быструю» линию, второе — сигнальную.
  3. Сигналы оцениваются на свече, задаваемой параметром SignalBar. Дополнительно берётся значение ещё на один бар глубже, чтобы подтвердить факт пересечения.
  4. Если быстрая линия поднимается выше сигнальной, стратегия (при разрешении) закрывает короткую позицию и открывает длинную.
  5. Если быстрая линия опускается ниже сигнальной, стратегия (при разрешении) закрывает длинную позицию и открывает короткую.
  6. Объём заявки равен параметру Volume плюс абсолютное значение текущей позиции, что позволяет разворачиваться одним рыночным ордером.

Логика повторяет MQL5‑советника: все решения принимаются только после закрытия свечи, а разрешения на открытие и закрытие позиций по направлениям задаются отдельно.

Параметры

Имя Тип По умолчанию Описание
SmoothingType CronexMovingAverageType Simple Тип скользящей средней, применяемой к осциллятору AC. Доступны Simple, Exponential, Smoothed, Weighted.
FastPeriod int 14 Период первого сглаживания (быстрая линия).
SlowPeriod int 25 Период второго сглаживания (сигнальная линия).
SignalBar int 1 Номер завершённой свечи, по которой считывается сигнал. Значение 1 соответствует оригиналу Cronex.
CandleType DataType TimeFrame(8h) Тип свечей для расчётов.
EnableLongEntry bool true Разрешить открытие длинных позиций после бычьего пересечения.
EnableShortEntry bool true Разрешить открытие коротких позиций после медвежьего пересечения.
EnableLongExit bool true Разрешить закрытие длинных позиций, когда быстрая линия падает ниже сигнальной.
EnableShortExit bool true Разрешить закрытие коротких позиций, когда быстрая линия поднимается выше сигнальной.
Volume decimal значение стратегии Объём сделок. К нему прибавляется абсолютное значение текущей позиции для разворотов одним ордером.

Отображение на графике

При наличии области графика стратегия рисует:

  • исходные свечи выбранного таймфрейма;
  • значения осциллятора Accelerator;
  • быструю и сигнальную линии после сглаживания;
  • собственные сделки стратегии для визуальной проверки.

Примечания

  • Обработка выполняется только по завершённым свечам (CandleStates.Finished), что исключает перерисовку сигналов.
  • Внутренние буферы хранят ровно столько значений, сколько нужно для смещения SignalBar, повторяя поведение MQL‑версии.
  • Функции управления рисками из оригинала (стоп‑лосс, тейк‑профит, допустимое проскальзывание) не реализованы и могут быть добавлены через встроенные механизмы StockSharp.
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;

public class CronexAcStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public CronexAcStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}