在 GitHub 上查看

Binario 3 策略

概述

本策略为 MetaTrader 4 专家顾问“Binario_3”(位于 MQL/7658/Binario_3.mq4)的 StockSharp 版本。原始 EA 使用两条以最高价和最低价计算的 144 周期指数移动平均线来形成一条自适应通道,并在价格向上或向下突破该通道时交易。它会在通道外侧布置买入和卖出止损挂单,同时通过止损、止盈和可选的移动止损来管理风险。

移植后的 StockSharp 策略保持了相同的交易规则,但完全基于高级 API 实现:

  1. 订阅所选蜡烛序列,在每根蜡烛收盘时更新两条 EMA 包络线。
  2. 当最新收盘价仍位于通道内部时,根据 EMA 值加上偏移量放置买入止损单和卖出止损单。
  3. 记录每个挂单对应的止损和止盈水平,在挂单被触发形成持仓后立即应用。
  4. 订阅 Level-1 行情,监控最新买一/卖一价格;一旦触碰记录的止损、止盈或移动止损距离,即平掉持仓。
  5. 如果价格离开通道或已经出现相反方向的持仓,则撤销对应的挂单,与 MQL 程序中的清理逻辑保持一致。

参数

名称 默认值 说明
TakeProfit 850 计算止盈价格时加到突破方向上的附加点数。
TrailingStop 850 移动止损距离(点)。设为 0 即关闭移动止损。
PipDifference 25 在 EMA 通道外侧布置挂单时使用的偏移量。
Lots 0.1 当无法根据资金推导仓位时使用的基础手数。
MaximumRisk 10 继承自原始 EA 的风险系数。策略按照 max(Lots, Balance * MaximumRisk / 50000) 估算手数。
EmaPeriod 144 计算高/低价 EMA 的周期。
CandleType 1 小时 驱动指标计算和订单生成的蜡烛类型。

所有以“点”为单位的参数都会乘以品种的 PriceStep 得到实际价格距离。如果合约没有提供价格步长,则回退为 1

交易逻辑

  1. 指标计算:使用两条 ExponentialMovingAverage 分别处理蜡烛的最高价和最低价,只有在两条均线都形成后才允许发出信号。
  2. 挂单布置:当收盘价位于通道内部时,生成两种挂单:
    • 买入止损:EMA(High) + 点差 + PipDifference × 步长。
    • 卖出止损:EMA(Low) − PipDifference × 步长。 生成挂单的同时会保存其对应的止损和止盈水平。
  3. 持仓管理:一旦挂单被触发并形成持仓,策略会撤销对向挂单,并采用之前记录的止损/止盈。通过 Level-1 行情判断价格是否触及止损、止盈或移动止损距离 (TrailingStop × 步长)。
  4. 移动止损:多头时,当浮盈超过设定距离后,移动止损跟随买一价上移;空头时,移动止损跟随卖一价下移,只朝有利方向调整,复现 MetaTrader 中的移动止损行为。
  5. 挂单清理:若最新收盘价突破了 EMA 通道,立即撤销两侧挂单,避免在通道外意外成交。

与 MQL 版本的差异

  • 原始 EA 通过 OrderModify 修改服务器端止损单;在 StockSharp 版本中,使用 Level-1 行情监控触发条件,并在达到阈值时调用 ClosePosition() 市价平仓。
  • 移动止损完全在策略内部模拟,因为高级 API 不支持交易所原生的跟踪指令。
  • 仓位大小优先使用投资组合余额(Portfolio.CurrentValuePortfolio.BeginValue),若无法获取余额则回退为固定的 Lots 参数。
  • 在注册挂单前,会将价格对齐到标的的价格步长,以满足交易所的价格精度要求。

使用建议

  • 运行策略时务必订阅 Level-1 行情,以便及时更新买卖价并执行止损/移动止损逻辑。
  • 策略仅在蜡烛收盘后决策,选择的时间框架越大,反应节奏越慢。
  • TrailingStop 设为 0 可关闭移动止损,此时只保留固定止损和止盈。
using System;
using System.Collections.Generic;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Binario 3 strategy - fast/slow EMA crossover with momentum confirmation.
/// Buys on bullish EMA crossover, sells on bearish EMA crossover.
/// </summary>
public class Binario3Strategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _prevFast;
	private decimal _prevSlow;
	private bool _hasPrev;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public Binario3Strategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 8)
			.SetDisplay("Fast EMA", "Fast EMA period", "Indicators");

		_slowPeriod = Param(nameof(SlowPeriod), 21)
			.SetDisplay("Slow EMA", "Slow EMA period", "Indicators");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];
	protected override void OnReseted() { base.OnReseted(); _prevFast = 0m; _prevSlow = 0m; _hasPrev = false; }

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

		_hasPrev = false;

		var fast = new ExponentialMovingAverage { Length = FastPeriod };
		var slow = new ExponentialMovingAverage { Length = SlowPeriod };

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

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

		if (!_hasPrev)
		{
			_prevFast = fast;
			_prevSlow = slow;
			_hasPrev = true;
			return;
		}

		var crossUp = _prevFast <= _prevSlow && fast > slow;
		var crossDown = _prevFast >= _prevSlow && fast < slow;

		if (crossUp && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();
			BuyMarket();
		}
		else if (crossDown && Position >= 0)
		{
			if (Position > 0)
				SellMarket();
			SellMarket();
		}

		_prevFast = fast;
		_prevSlow = slow;
	}
}