Открыть на GitHub

Стратегия X Bug

Обзор

X Bug — это стратегия пересечения скользящих средних, перенесённая с советника MQL4. Она вычисляет две простые скользящие средние по медианной цене свечи и открывает позицию, когда быстрая средняя пересекает медленную. Реализация сохраняет ключевые особенности оригинала: разворот сигналов, принудительное закрытие позиции при противоположном сигнале и управление защитными заявками в пипсах.

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

  1. Подписка на выбранный тип свечей (по умолчанию минутные) и расчёт двух простых скользящих средних — быстрой и медленной. Параметры сдвига учитываются при получении значений индикаторов.
  2. Бычий сигнал фиксируется, когда текущее значение быстрой средней находится выше медленной, а два бара назад — ниже. Медвежий сигнал определяется зеркально.
  3. Параметр ReverseSignals позволяет инвертировать направление сделок и торговать против направления пересечения.
  4. При активированном CloseOnSignal стратегия немедленно закрывает противоположную позицию перед открытием новой.
  5. Входы совершаются рыночными ордерами: покупка при бычьем пересечении и продажа при медвежьем. Стратегия не накапливает однонаправленные позиции, торгует только при отсутствии открытых сделок или при совпадении направления.

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

  • StopLossPips — фиксирует защитный стоп в пипсах. При котировках с 5 или 3 знаками после запятой величина пипса автоматически корректируется по шагу цены инструмента.
  • TakeProfitPips — задаёт расстояние до тейк-профита в пипсах.
  • TrailingStopPips — при включённом UseTrailingStop активирует трейлинг с заданным расстоянием. Шаг трейлинга равен самому расстоянию, что соответствует логике советника.
  • Все защитные ордера запускаются через StartProtection с рыночным исполнением для точного соответствия MQL-версии.

Параметры

Параметр Описание Значение по умолчанию
OrderVolume Базовый объём сделки. 0.1
StopLossPips Стоп-лосс в пипсах (0 отключает). 70
TakeProfitPips Тейк-профит в пипсах (0 отключает). 5000
UseTrailingStop Включает/выключает трейлинг. true
TrailingStopPips Дистанция трейлинга в пипсах. 90
FastPeriod Период быстрой скользящей средней. 1
FastShift Сдвиг быстрой средней в барах. 0
SlowPeriod Период медленной скользящей средней. 14
SlowShift Сдвиг медленной средней в барах. 10
CloseOnSignal Закрывать противоположную позицию при новом сигнале. true
ReverseSignals Инвертировать направление сигналов. false
AppliedPrice Тип цены свечи для расчёта индикаторов. Median
CandleType Тип свечей для сигналов. Таймфрейм 1 минута

Примечания

  • Значение пипса вычисляется как шаг цены, умноженный на 10 для инструментов с 5 или 3 десятичными знаками, что повторяет оригинальный советник.
  • Версия на Python не предоставляется; в каталоге находится только реализация на C#.
  • Чтобы отключить стопы или трейлинг, установите соответствующие параметры в 0.
namespace StockSharp.Samples.Strategies;

using System;
using System.Collections.Generic;

using Ecng.Common;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

/// <summary>
/// Moving average crossover strategy converted from the MQL4 expert "X bug".
/// Uses fast and slow SMA crossover for signal generation.
/// </summary>
public class XBugStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<bool> _reverseSignals;
	private readonly StrategyParam<DataType> _candleType;

	private SimpleMovingAverage _slowMa;
	private decimal? _prevFast;
	private decimal? _prevSlow;
	private int _cooldown;
	private int _candleCount;

	public XBugStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 5)
			.SetGreaterThanZero()
			.SetDisplay("Fast MA period", "Length of the fast moving average.", "Indicators");

		_slowPeriod = Param(nameof(SlowPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("Slow MA period", "Length of the slow moving average.", "Indicators");

		_reverseSignals = Param(nameof(ReverseSignals), false)
			.SetDisplay("Reverse signals", "Invert buy and sell directions.", "Trading");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle type", "Primary timeframe used for signals.", "General");
	}

	public int FastPeriod
	{
		get => _fastPeriod.Value;
		set => _fastPeriod.Value = value;
	}

	public int SlowPeriod
	{
		get => _slowPeriod.Value;
		set => _slowPeriod.Value = value;
	}

	public bool ReverseSignals
	{
		get => _reverseSignals.Value;
		set => _reverseSignals.Value = value;
	}

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
		=> [(Security, CandleType)];

	protected override void OnReseted()
	{
		base.OnReseted();
		_slowMa = default;
		_prevFast = null;
		_prevSlow = null;
		_cooldown = 0;
		_candleCount = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		Volume = 0.001m;
		_prevFast = null;
		_prevSlow = null;
		_cooldown = 0;
		_candleCount = 0;

		_slowMa = new SimpleMovingAverage { Length = SlowPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(_slowMa, ProcessCandle)
			.Start();
	}

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

		if (!_slowMa.IsFormed)
			return;

		_candleCount++;
		if (_cooldown > 0)
		{
			_cooldown--;
			return;
		}

		// Use close price as the "fast" value (period=1 effectively)
		var fastValue = candle.ClosePrice;

		if (_prevFast is null || _prevSlow is null)
		{
			_prevFast = fastValue;
			_prevSlow = slowValue;
			return;
		}

		var signal = 0;
		if (fastValue > slowValue && _prevFast.Value <= _prevSlow.Value)
			signal = 1;
		else if (fastValue < slowValue && _prevFast.Value >= _prevSlow.Value)
			signal = -1;

		_prevFast = fastValue;
		_prevSlow = slowValue;

		if (signal == 0)
			return;

		if (ReverseSignals)
			signal = -signal;

		if (signal > 0 && Position <= 0)
		{
			BuyMarket();
			_cooldown = 100;
		}
		else if (signal < 0 && Position >= 0)
		{
			SellMarket();
			_cooldown = 100;
		}
	}
}