Открыть на GitHub

Extreme EA (версия для StockSharp)

Extreme EA — это трендовый советник из экосистемы MetaTrader. Он совмещает две скользящие средние с фильтром Commodity Channel Index и динамическим управлением капиталом. Перенос на StockSharp использует высокоуровневый API, сохраняет торговую логику и делает все ключевые параметры доступными из интерфейса. Алгоритм работает только с закрытыми свечами и поддерживает многотаймфреймовый анализ благодаря отдельным подпискам на данные.

Описание алгоритма

  1. Трендовый фильтр. На таймфрейме MaCandleType вычисляются быстрая и медленная скользящие. Быстрая линия отслеживает локальный импульс, медленная оценивает направление тренда. Для точного повторения CopyBuffer из MQL стратегия хранит две последние точки медленной средней и одну актуальную точку быстрой.
  2. Фильтр импульса. Индикатор CCI рассчитывается на собственном таймфрейме CciCandleType и с выбранным типом цены. Последнее завершённое значение кэшируется и используется до появления новой свечи CCI, что соответствует механике буферов MetaTrader.
  3. Условия входа.
    • Длинная позиция открывается, когда медленная средняя растёт, быстрая растёт, а CCI опускается ниже нижнего уровня.
    • Короткая позиция открывается, когда медленная средняя снижается, быстрая снижается, а CCI превышает верхний уровень.
  4. Условия выхода.
    • Все покупки закрываются, если медленная средняя перестаёт расти.
    • Все продажи закрываются, если медленная средняя перестаёт падать.

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

  • MaximumRisk определяет расчётный объём сделки как долю от текущей стоимости портфеля. Если данные портфеля недоступны, используется значение Volume или минимально допустимый объём инструмента.
  • DecreaseFactor уменьшает объём после двух и более подряд убыточных сделок по формуле оригинального советника volume - volume * losses / DecreaseFactor.
  • HistoryDays ограничивает длительность учёта серии убытков. Если новая закрывающая сделка произошла позже указанного срока, счётчик сбрасывается перед пересчётом объёма.
  • MaxPositions задаёт максимум усреднений в одном направлении и блокирует новые входы, когда лимит достигнут.

Параметры

Имя Тип Значение по умолчанию Описание
MaximumRisk decimal 0.05 Доля капитала, используемая для расчёта объёма позиции.
DecreaseFactor decimal 6 Коэффициент уменьшения объёма после серии убытков (0 отключает механизм).
HistoryDays int 60 Максимальный возраст сделок, участвующих в подсчёте серии убытков.
MaxPositions int 3 Максимальное число одновременно открытых позиций в одном направлении.
FastMaPeriod int 15 Период быстрой скользящей средней.
SlowMaPeriod int 75 Период медленной скользящей средней.
CciPeriod int 12 Количество баров для расчёта CCI.
CciUpperLevel decimal 50 Верхний порог CCI для сигналов на продажу.
CciLowerLevel decimal -50 Нижний порог CCI для сигналов на покупку.
MaCandleType DataType 15m Таймфрейм скользящих средних и торговли.
CciCandleType DataType 30m Таймфрейм, на котором вычисляется CCI.
MaMethod MaMethod Exponential Метод сглаживания (Simple, Exponential, Smoothed, LinearWeighted).
MaPriceMode AppliedPriceMode Median Тип цены для скользящих средних.
CciPriceMode AppliedPriceMode Typical Тип цены для индикатора CCI.

Особенности реализации

  • Если таймфреймы совпадают, одна подписка на свечи одновременно обновляет и скользящие средние, и CCI. При различающихся таймфреймах создаётся вторая подписка только для CCI.
  • Внутренние поля хранят последние значения индикаторов, что позволяет проверять условия ma_slow_array[1], ma_slow_array[2] и ma_fast_array[0] без ручного доступа к буферам.
  • Объём заявок нормализуется по минимальному шагу, минимальному и максимальному объёму инструмента, чтобы снизить риск отклонённых сделок.
  • Блок управления риском фиксирует цену входа и выхода последней сделки и на этой основе пересчитывает серию убытков, заменяя вызовы HistoryDealGet из MQL.

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

  • Функции FreeMarginCheck, MarginCheck и HistorySelect заменены доступными показателями портфеля StockSharp и внутренним счётчиком убытков.
  • StockSharp использует модель чистой позиции, поэтому закрытие приводит к обнулению совокупного объёма в соответствующем направлении.
  • Журналирование оригинала удалено — используйте стандартные средства логирования 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;

/// <summary>
/// Extreme EA strategy using fast/slow EMA crossover with CCI filter.
/// Buys when both EMAs rising and CCI below lower level (oversold bounce).
/// Sells when both EMAs falling and CCI above upper level (overbought reversal).
/// </summary>
public class ExtremeEaStrategy : Strategy
{
	private readonly StrategyParam<int> _fastMaPeriod;
	private readonly StrategyParam<int> _slowMaPeriod;
	private readonly StrategyParam<int> _cciPeriod;
	private readonly StrategyParam<decimal> _cciUpperLevel;
	private readonly StrategyParam<decimal> _cciLowerLevel;

	private ExponentialMovingAverage _fastMa;
	private ExponentialMovingAverage _slowMa;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _prevFast2;
	private decimal _prevSlow2;
	private bool _hasPrev;

	/// <summary>
	/// Fast EMA period.
	/// </summary>
	public int FastMaPeriod
	{
		get => _fastMaPeriod.Value;
		set => _fastMaPeriod.Value = value;
	}

	/// <summary>
	/// Slow EMA period.
	/// </summary>
	public int SlowMaPeriod
	{
		get => _slowMaPeriod.Value;
		set => _slowMaPeriod.Value = value;
	}

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

	/// <summary>
	/// Upper CCI threshold for sell entries.
	/// </summary>
	public decimal CciUpperLevel
	{
		get => _cciUpperLevel.Value;
		set => _cciUpperLevel.Value = value;
	}

	/// <summary>
	/// Lower CCI threshold for buy entries.
	/// </summary>
	public decimal CciLowerLevel
	{
		get => _cciLowerLevel.Value;
		set => _cciLowerLevel.Value = value;
	}

	/// <summary>
	/// Initialize strategy parameters.
	/// </summary>
	public ExtremeEaStrategy()
	{
		_fastMaPeriod = Param(nameof(FastMaPeriod), 50)
			.SetGreaterThanZero()
			.SetDisplay("Fast MA", "Fast EMA period", "Indicator");

		_slowMaPeriod = Param(nameof(SlowMaPeriod), 200)
			.SetGreaterThanZero()
			.SetDisplay("Slow MA", "Slow EMA period", "Indicator");

		_cciPeriod = Param(nameof(CciPeriod), 12)
			.SetGreaterThanZero()
			.SetDisplay("CCI Period", "CCI lookback period", "Indicator");

		_cciUpperLevel = Param(nameof(CciUpperLevel), 50m)
			.SetDisplay("CCI Upper", "Upper CCI threshold for sell", "Levels");

		_cciLowerLevel = Param(nameof(CciLowerLevel), -50m)
			.SetDisplay("CCI Lower", "Lower CCI threshold for buy", "Levels");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_fastMa = null;
		_slowMa = null;
		_prevFast = 0;
		_prevSlow = 0;
		_prevFast2 = 0;
		_prevSlow2 = 0;
		_hasPrev = false;
	}

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

		_fastMa = new ExponentialMovingAverage { Length = FastMaPeriod };
		_slowMa = new ExponentialMovingAverage { Length = SlowMaPeriod };

		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fastMa, _slowMa, ProcessCandle);
		subscription.Start();
	}

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

		if (!_fastMa.IsFormed || !_slowMa.IsFormed)
		{
			_prevFast2 = _prevFast;
			_prevSlow2 = _prevSlow;
			_prevFast = fastValue;
			_prevSlow = slowValue;
			_hasPrev = true;
			return;
		}

		if (!_hasPrev)
		{
			_prevFast2 = _prevFast;
			_prevSlow2 = _prevSlow;
			_prevFast = fastValue;
			_prevSlow = slowValue;
			_hasPrev = true;
			return;
		}

		var slowIsRising = _prevSlow > _prevSlow2;
		var slowIsFalling = _prevSlow < _prevSlow2;
		var fastIsRising = fastValue > _prevFast;
		var fastIsFalling = fastValue < _prevFast;

		// Buy: fast crosses above slow
		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();

			BuyMarket();
		}
		// Sell: fast crosses below slow
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{
			if (Position > 0)
				SellMarket();

			SellMarket();
		}

		_prevFast2 = _prevFast;
		_prevSlow2 = _prevSlow;
		_prevFast = fastValue;
		_prevSlow = slowValue;
	}
}