Открыть на GitHub

Стратегия Bago EA

Стратегия воспроизводит эксперт MetaTrader «Bago EA». Торговля ведётся по тренду: сигналы подтверждаются пересечениями EMA и RSI, а туннель Vegas (EMA 144 и 169) фильтрует входы и задаёт уровни для трейлинга.

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

  1. Индикаторы
    • Две EMA с периодами FastPeriod и SlowPeriod, методом MaMethod и ценой MaAppliedPrice.
    • Vegas-туннель из EMA 144 и 169 (те же метод и цена) для определения положения цены.
    • RSI (RsiPeriod, RsiAppliedPrice).
    • Перевод пунктов в цену использует шаг PriceStep с поправкой на 3/5 знаков, как в оригинальном советнике.
  2. Машина состояний
    • Отслеживаются пересечения EMA и RSI относительно уровня 50. Каждое состояние активно CrossEffectiveBars свечей или до появления противоположного пересечения.
    • Флаги туннеля фиксируют переход цены с одной стороны Vegas-туннеля на другую.
  3. Условия входа
    • Покупка: активны пересечения EMA и RSI вверх и выполняется одно из условий:
      • Закрытие выше туннеля минимум на TunnelBandWidthPips, но не дальше TunnelSafeZonePips, свеча бычья.
      • Закрытие ниже туннеля на TunnelBandWidthPips, что трактуется как отбой снизу.
    • Продажа: зеркальные условия для пересечений вниз.
    • Торговля разрешена только в включённых сессиях: Лондон 07–16, Нью‑Йорк 12–21, Токио 00–08, либо любая свеча с закрытием после 23:00.
  4. Работа с ордерами
    • Открытие позиции объёмом TradeVolume. При появлении сигнала противоположные позиции закрываются перед реверсом.
    • Начальный стоп устанавливается на расстоянии StopLossPips от цены закрытия, с дополнительной поправкой StopLossToFiboPips при фиксации у туннеля.
  5. Трейлинг и частичные выходы
    • При движении цены стоп переносится:
      • Внутри туннеля — на уровень туннель ± (TrailingStepX + StopLossToFibo).
      • Вне туннеля — классический трейлинг на расстоянии TrailingStopPips от цены.
    • Частичные выходы: объём PartialClose1Volume фиксируется на TrailingStep1Pips, PartialClose2Volume — на TrailingStep2Pips.
    • Противоположное пересечение EMA или RSI полностью закрывает позицию.
  6. Стоп-заявки
    • Защитные ордера выставляются через SellStop/BuyStop и снимаются при закрытии позиции.

Параметры

Параметр Тип Значение по умолчанию Описание
TradeVolume decimal 3 Объём позиции (лоты).
StopLossPips decimal 30 Стартовый стоп-лосс в пунктах.
StopLossToFiboPips decimal 20 Дополнительный зазор при фиксации стопа у туннеля.
TrailingStopPips decimal 30 Расстояние трейлинг-стопа вне туннеля.
TrailingStep1Pips decimal 55 Первая ступень для частичного выхода и переноса стопа.
TrailingStep2Pips decimal 89 Вторая ступень.
TrailingStep3Pips decimal 144 Финальная ступень перед чистым трейлингом.
PartialClose1Volume decimal 1 Объём частичного выхода на первой ступени.
PartialClose2Volume decimal 1 Объём частичного выхода на второй ступени.
CrossEffectiveBars int 2 Количество свечей, в течение которых пересечение считается актуальным.
TunnelBandWidthPips decimal 5 Ширина нейтральной зоны вокруг туннеля.
TunnelSafeZonePips decimal 120 Максимальная дистанция от туннеля для входа по тренду.
EnableLondonSession bool true Разрешить торговлю в Лондонскую сессию.
EnableNewYorkSession bool true Разрешить торговлю в Нью‑Йоркскую сессию.
EnableTokyoSession bool false Разрешить торговлю в Токийскую сессию.
FastPeriod int 5 Период быстрой EMA.
SlowPeriod int 12 Период медленной EMA.
MaShift int 0 Горизонтальный сдвиг EMA.
MaMethod MovingAverageType Exponential Метод усреднения скользящих.
MaAppliedPrice AppliedPriceType Close Тип цены для EMA.
RsiPeriod int 21 Период RSI.
RsiAppliedPrice AppliedPriceType Close Тип цены для RSI.
CandleType DataType H1 Таймфрейм свечей.

Примечания

  • Машина состояний индикаторов обновляется даже вне торговых часов, как и в исходном советнике.
  • Защитные ордера управляются через высокоуровневый API, что соответствует вызовам PositionModify в MetaTrader.
  • Код следует требованиям репозитория: табуляция, комментарии на английском, без использования GetValue у индикаторов.
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>
/// Bago EA trend-following strategy using EMA crossover with RSI confirmation.
/// Buys when fast EMA crosses above slow EMA and RSI above 50.
/// Sells on reverse conditions.
/// </summary>
public class BagoEaStrategy : 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;

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

	/// <summary>
	/// Slow EMA 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="BagoEaStrategy"/> class.
	/// </summary>
	public BagoEaStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 12)
			.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");
	}

	/// <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 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;

		// 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;
			}
		}

		// EMA 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;
	}
}