Открыть на GitHub

Стратегия Exp Cronex AO

Стратегия переносит советник MetaTrader Exp_CronexAO на высокоуровневый API StockSharp. Оригинальный робот торгует пересечения между двумя линиями индикатора Cronex Awesome Oscillator (AO). Версия для StockSharp подписывается на настраиваемую серию свечей, рассчитывает AO, дважды сглаживает его скользящими средними, чтобы восстановить линии Cronex, и открывает либо закрывает позиции при пересечении быстрой и медленной линий.

Логика работы

  1. Рассчитать Awesome Oscillator по выбранным свечам.
  2. Выполнить двойное сглаживание простыми скользящими средними: первое сглаживание формирует «быструю» линию Cronex, второе — сигнальную линию.
  3. Проанализировать SignalBar завершённых свечей назад и сравнить значения обеих линий на этой свече и на предыдущей.
  4. Покупка выполняется, когда быстрая линия находится выше медленной и сделала восходящее пересечение на анализируемой свече. Стратегия по необходимости закрывает короткую позицию и, если разрешено, открывает длинную рыночную сделку.
  5. Продажа происходит при зеркальных условиях: быстрая линия должна быть ниже медленной и пересечь её вниз на указанной свече. В этом случае стратегия может закрыть длинную позицию и, при разрешении, открыть короткую.
  6. При каждом входе рассчитываются уровни стоп-лосса и тейк-профита в пунктах инструмента и привязываются к итоговой позиции.

Стратегия поддерживает только одну чистую позицию. При смене направления объём заявки объединяет количество, необходимое для закрытия противоположной позиции, и объём нового входа — аналогично поведению неттингового счёта MT5.

Параметры

Параметр Описание
CandleType Тип свечей для расчёта Cronex AO. По умолчанию используется таймфрейм 8 часов.
FastPeriod Длина первого сглаживания Awesome Oscillator.
SlowPeriod Длина второго сглаживания быстрой линии.
SignalBar Количество завершённых свечей назад, на которых должен произойти сигнал пересечения. Дополнительно проверяется следующая свеча для подтверждения.
BuyOpenEnabled / SellOpenEnabled Разрешение на открытие длинных или коротких позиций.
BuyCloseEnabled / SellCloseEnabled Разрешение закрывать позиции при появлении противоположного сигнала.
TakeProfit Уровень тейк-профита в пунктах, устанавливается после каждого входа, если больше нуля.
StopLoss Уровень стоп-лосса в пунктах, устанавливается после каждого входа, если больше нуля.

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

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

Отличия от версии MetaTrader

  • В реализации StockSharp оба этапа сглаживания Cronex выполняются простыми скользящими средними. В оригинальном классе XMA доступно больше методов, однако настройка по умолчанию соответствует здесь реализованной SMA.
  • Не перенесены функции управления капиталом и проскальзыванием из библиотеки TradeAlgorithms. Размер позиции задаётся стандартным свойством Volume.
  • Сделки выполняются в режиме неттинга StockSharp. При развороте направления отправляется единый рыночный ордер, который одновременно закрывает старую позицию и открывает новую, что соответствует логике MT5.
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 ExpCronexAOStrategy : 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 ExpCronexAOStrategy()
	{
		_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;
	}
}