Открыть на GitHub

Стратегия Lego EA

Lego EA Strategy — порт оригинального эксперта Lego EA под StockSharp. Стратегия объединяет несколько фильтров (CCI, две скользящие средние, стохастик, Accelerator Oscillator, DeMarker и Awesome Oscillator), причем каждый фильтр можно включать отдельно для входов и выходов. Это позволяет либо полностью воспроизвести оригинальную конфигурацию, либо собирать «конструктор» из собственных блоков.

Параметры

  • Volume — базовый объём позиции при прибыльной предыдущей сделке.
  • LotMultiplier — множитель для объёма после убыточной сделки.
  • StopLossPips — расстояние стоп-лосса в пунктах (автоматически переводится в цену).
  • TakeProfitPips — расстояние тейк-профита в пунктах.
  • UseCciForEntry / UseCciForExit — использование фильтра CCI для открытия/закрытия.
  • UseMaForEntry / UseMaForExit — проверка пересечения быстрой и медленной скользящих.
  • UseStochasticForEntry / UseStochasticForExit — контроль стохастика относительно уровней.
  • UseAcceleratorForEntry / UseAcceleratorForExit — анализ последовательности значений Accelerator Oscillator.
  • UseDemarkerForEntry / UseDemarkerForExit — проверка индикатора DeMarker на выход за заданные уровни.
  • UseAwesomeForEntry / UseAwesomeForExit — подтверждение импульса через Awesome Oscillator.
  • CciPeriod — период расчёта CCI.
  • MaFastPeriod, MaSlowPeriod — периоды скользящих средних.
  • MaShift — горизонтальный сдвиг скользящих (количество завершённых баров назад).
  • MaMethod — тип сглаживания.
  • MaPrice — цена, подаваемая на обе скользящие.
  • StochasticKPeriod, StochasticDPeriod, StochasticSlow — параметры стохастика.
  • StochasticLevelUp, StochasticLevelDown — пороговые уровни стохастика.
  • DemarkerPeriod, DemarkerLevelUp, DemarkerLevelDown — настройки DeMarker.
  • CandleType — таймфрейм свечей, по которым строятся индикаторы.

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

  1. После формирования каждой свечи стратегия собирает значения всех выбранных индикаторов.
  2. Для согласованности с MetaTrader используется значение предыдущего закрытого бара (аналог iGetArray(...,1)).
  3. Вход в лонг возможен только при одновременном подтверждении всех включённых фильтров на покупку; для шорта требуются сигналы на продажу.
  4. Если позиции нет и условия выполнены — отправляется рыночная заявка. Объём равен базовому Volume либо последнему объёму, умноженному на LotMultiplier, если предыдущая сделка была убыточной.
  5. При наличии позиции фильтры выхода проверяются аналогичным образом. Закрытие выполняется, когда все активные фильтры дают встречный сигнал.
  6. StartProtection автоматически устанавливает стоп и тейк в абсолютных ценах с учётом шага цены инструмента.

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

  • При прибыли объём сбрасывается к базовому Volume.
  • При убытке следующий объём увеличивается в LotMultiplier раз, воспроизводя ступенчатый маневр исходного эксперта.
  • Минимальный/максимальный объём и шаг объёма берутся из свойств инструмента и контролируются перед отправкой заявки.

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

  • Источники цен индикаторов сопоставлены со стандартными типами StockSharp. CCI использует типичную цену, скользящие — выбранный MaPrice.
  • Расчёт ведётся строго по закрытым свечам, что исключает неполные значения.
  • Проверки freeze-level заменены на встроенный механизм StartProtection.
  • Учёт результата сделки происходит при полном закрытии позиции, что соответствует обработке события DEAL_ENTRY_OUT в MT5.

Рекомендации

  • Начните с базовой конфигурации (включены только скользящие), затем постепенно подключайте дополнительные фильтры.
  • Следите за нагрузкой на депозит при больших значениях LotMultiplier — серия убытков быстро увеличивает объём.
  • Используйте тестер стратегий StockSharp для оценки сочетаний фильтров на нужном инструменте.

Python-реализация пока отсутствует.

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>
/// Lego EA multi-indicator trend following strategy using SMA crossover.
/// Buys when fast SMA crosses above slow SMA, sells on reverse.
/// </summary>
public class LegoEaStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private SimpleMovingAverage _fast;
	private SimpleMovingAverage _slow;

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

	/// <summary>
	/// Fast SMA period.
	/// </summary>
	public int FastPeriod
	{
		get => _fastPeriod.Value;
		set => _fastPeriod.Value = value;
	}

	/// <summary>
	/// Slow SMA period.
	/// </summary>
	public int SlowPeriod
	{
		get => _slowPeriod.Value;
		set => _slowPeriod.Value = value;
	}

	/// <summary>
	/// Stop-loss distance in price steps.
	/// </summary>
	public int StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	/// <summary>
	/// Take-profit distance in price steps.
	/// </summary>
	public int TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the <see cref="LegoEaStrategy"/> class.
	/// </summary>
	public LegoEaStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("Fast Period", "Fast SMA period", "Indicator");

		_slowPeriod = Param(nameof(SlowPeriod), 67)
			.SetGreaterThanZero()
			.SetDisplay("Slow Period", "Slow SMA 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");
	}

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

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

		_fast = null;
		_slow = null;
		_prevFast = 0;
		_prevSlow = 0;
		_entryPrice = 0;
		_cooldown = 0;
	}

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

		_fast = new SimpleMovingAverage { Length = FastPeriod };
		_slow = new SimpleMovingAverage { 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;

		// Check SL/TP
		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step)
			{
				SellMarket();
				_entryPrice = 0;
				_cooldown = 80;
				_prevFast = fastValue;
				_prevSlow = slowValue;
				return;
			}

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

			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step)
			{
				BuyMarket();
				_entryPrice = 0;
				_cooldown = 80;
				_prevFast = fastValue;
				_prevSlow = slowValue;
				return;
			}
		}

		// SMA crossover
		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();

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

			SellMarket();
			_entryPrice = close;
			_cooldown = 80;
		}

		_prevFast = fastValue;
		_prevSlow = slowValue;
	}
}