在 GitHub 上查看

X Bug 策略

概述

X Bug 策略 源自同名的 MQL4 智能交易系统,是一套基于移动平均线交叉的趋势跟随方案。策略使用蜡烛图的中价来计算快、慢两条简单移动平均线,当快线向上或向下穿越慢线时,按照交叉方向建立仓位。本实现保留了原版 EA 的核心特性,包括信号反向、反向信号时自动平仓以及以点(pip)为单位的风控设置。

交易逻辑

  1. 订阅设定的蜡烛类型(默认 1 分钟),计算两条简单移动平均线,并应用参数中的指标位移。
  2. 当当前快线值高于慢线、且前两个柱子的快线值低于慢线时认定为看多交叉;反之则为看空交叉。
  3. 启用 ReverseSignals 参数时,策略会反向执行交易信号。
  4. 若启用 CloseOnSignal,在新的反向信号出现时会立即平掉当前反向持仓,然后再执行新交易。
  5. 仅在仓位为空或方向一致时开仓,避免同方向叠加仓位。

风险控制

  • StopLossPips:设置以点为单位的止损距离。对于五位或三位小数报价,会根据品种的最小报价单位自动换算。
  • TakeProfitPips:设置以点为单位的止盈目标。
  • TrailingStopPips:启用 UseTrailingStop 时,在持仓盈利后按照该距离启动移动止损,移动步长与距离相同,以模拟原始 EA 的行为。
  • 所有保护性订单通过 StartProtection 管理,并以市价执行,确保与 MQL4 版本一致。

参数

参数 说明 默认值
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 分钟时间框架

备注

  • 对于 5 位或 3 位小数报价,策略会将价格步长乘以 10 以获得 1 个点的大小,从而与原版 EA 保持一致。
  • 本目录仅提供 C# 实现,暂不包含 Python 版本。
  • 将任意点距参数设为 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;
		}
	}
}