在 GitHub 上查看

Grr AL Breakout 策略

概述

Grr AL Breakout 是 MetaTrader 专家顾问 grr-al.mq5 的移植版本。策略在每根新 K 线开始时记录开盘价,并等待市场向上或向下偏离该价格达到设定的点数。当价格波动超过阈值时,当前 K 线只触发一次交易,并且可以反转已有头寸。

在 StockSharp 中实现时,策略使用高级的蜡烛订阅 API,而不是原始 EA 的定时器。新的蜡烛快照提供初始参考价,后续的同一根蜡烛更新则提供最新的收盘价作为实时行情。这样可以在不处理低级事件的情况下,复刻原策略基于 tick 的突破检测。

交易逻辑

  1. 确定锚点价格:当出现新蜡烛时,保存其开盘价(如果尚未提供,则使用首次可用的收盘价),并重置该蜡烛的触发标记。
  2. 突破判定:只要当前蜡烛尚未触发交易,就会比较最新收盘价与锚点之间的差距。价格上涨超过 DeltaPoints(转换为价格后)则开空,价格下跌超过同样距离则开多。
  3. 每根蜡烛仅一次交易:一旦突破信号执行,本蜡烛中不再下单,与原始 EA 中的 br 标记保持一致。
  4. 风险控制:开仓后立即附加可选的止损和止盈。如果订单只是减少反向持仓,则不会附加保护单,以避免在平仓时仍保留挂单。
  5. 仓位管理:既可使用固定手数,也可按经纪商提供的最大手数乘以 RiskFraction 对成交量进行限制。

参数说明

  • Volume:基础下单手数,对应 MQL 版本中的 BASELOT
  • RiskFraction:0 到 1 之间的比例。如果大于 0,会将订单手数限制为“最大允许手数 × 该比例”与 Volume 之间的较小值。
  • DeltaPoints:触发交易所需的点数距离,对应原代码中的 DELTA
  • StopLossPoints:止损距离(点)。设置为 0 表示不使用止损(类似 SL = 0)。
  • TakeProfitPoints:止盈距离(点)。设置为 0 表示不使用止盈(类似 TP = 0)。
  • CandleType:StockSharp 的蜡烛类型参数,用于指定策略运行的时间周期,默认是 5 分钟。

与 MQL 版本的差异

  • 原策略依赖 1 秒定时器和 tick 事件;移植版本通过蜡烛订阅自动获得实时数据,不需要手动调度定时器。
  • 高级 API 不区分买卖价,因此使用蜡烛收盘价作为下单参考。止损和止盈仍以点数方式换算成价格。
  • 原版风险管理基于保证金计算;此处改为简单的最大仓位比例,更加通用且与经纪商实现无关。
  • StockSharp 使用净头寸模式,反向下单会自动平仓或反转,与 MetaTrader 5 的净值账户模式类似。

使用方法

  1. 在 Designer、Runner 或自定义的 StockSharp 应用中选择证券和投资组合。
  2. 配置蜡烛周期、突破距离、止损/止盈及交易手数等参数。
  3. 启动策略后,系统会自动订阅所需蜡烛数据,并在满足条件时发送市价单。

原始文件

  • MetaTrader 5 专家顾问:MQL/244/grr-al.mq5
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;

public class GrrAlBreakoutStrategy : 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;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public GrrAlBreakoutStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).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");
	}

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

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	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;

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

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

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}