在 GitHub 上查看

早鸟区间突破策略

概述

早鸟区间突破策略是 MetaTrader 4 专家顾问 earlyBird1 的 C# 版本。策略在交易日开盘前统计指定时段的最高价和最低价,使用 14 周期 RSI(基于开盘价)判断做多或做空方向,并在常规交易时段开启后捕捉首次突破。该移植版本完整保留了原始 EA 的每日单向一次交易限制、基于波动率的跟踪止损以及收盘平仓逻辑。

策略逻辑

区间构建

  • 时间窗口 – 区间在 Range Start HourRange End Hour 之间计算(会按照夏令时/冬令时规则调整时差)。所有与该时间段有交集的 K 线都会扩展区间高低点。
  • 入场缓冲 – 在区间高点上方与低点下方各叠加一个可配置的点差,复刻 MT4 脚本中 ±2/Fakt 的突破缓冲处理。
  • 每日重置 – 每个新交易日第一根已完成的 K 线会重置区间、入场触发价以及当日交易计数。

方向过滤

  • 基于开盘价的 RSI – RSI 指标以蜡烛开盘价为输入,对应 MT4 中 iRSI(..., PRICE_OPEN) 的实现。
  • 方向选择 – 当 RSI 大于 50 时,仅启用多头触发;当 RSI 小于等于 50 时,仅启用空头触发。这样确保每根 K 线只会激活一个方向,与原 EA 保持一致。

入场规则

  • 交易时段 – 只有在工作日且处于 Session StartSession End 之间、并且区间已经计算完成后,策略才允许入场。
  • 每日单次限制 – 一旦当日已经开过一笔多单或空单,对应方向当天不再重复入场,完全复刻 MT4 中的历史成交计数逻辑。
  • 对冲开关 – 启用 Allow Hedging 后,策略会在下单时自动加量以平掉相反持仓并立即翻向;禁用时若仍持仓则跳过入场,直到仓位归零。

出场规则

  • 固定止盈止损 – 止盈和止损距离以点数表示。实际止盈距离会被止损距离和区间宽度共同约束,复现 MQL 程序中的 MathMin 判定。
  • 波动率跟踪 – 当当前 K 线实体大于 16 根历史 K 线平均波动乘以 Trailing Risk,且浮盈达到 Trailing Trigger 点时,止损上调到当前价减去原始止损距离,止盈同步收紧为触发距离的一半,与原 EA 的 OrderModify 行为一致。
  • 日内收盘 – 到达 Closing Hour 后,盈利仓位立即平仓;浮亏仓位将止盈价调整到开仓价,等待价格回到保本位离场,呼应 MT4 的日内收盘处理。

参数

  • Auto Trading – 自动交易开关。
  • Allow Hedging – 允许在已有仓位的情况下翻向交易。
  • Trade Direction – 方向限制:0 为双向、1 为仅做多、2 为仅做空。
  • Volume – 市价单下单量。
  • Take Profit (pips) – 止盈距离上限,最终距离受止损与区间宽度限制。
  • Stop Loss (pips) – 固定止损距离。
  • Trailing Trigger (pips) – 启用跟踪逻辑所需的最低浮盈。
  • Trailing Risk – 乘以 16 根历史波动的系数,用于判断是否进入高波动模式。
  • Entry Buffer (pips) – 计算突破价时附加的点差缓冲。
  • Session Start Hour / Minute – 可交易时段的起始时间(DST 调整前的图表时间)。
  • Session End Hour – 可交易时段的结束时间。
  • Closing Hour – 日内强制收盘时间。
  • Range Start Hour / Range End Hour – 用于构建突破区间的时间范围。
  • Summer Time Start / Winter Time Start – 夏令时与冬令时切换所在的年内日数,用于复刻原脚本的 Sommerzeit/Winterzeit 逻辑。
  • RSI Length – RSI 周期数(默认 14)。
  • Candle Type – 主驱动时间周期(默认 15 分钟 K 线)。

其他说明

  • 点值按当前价格大小自动切换(价格 ≥ 10 取 0.01,否则取 0.0001),与 MT4 脚本中的 Fakt 计算完全一致。
  • 跟踪所用的平均波动长度为 16 根已完成 K 线,不包含当前 K 线,与原策略一致。
  • StockSharp 使用净头寸模型,若开启对冲,策略会通过加减仓的方式模拟双向持仓效果。
  • 本仓库仅提供 C# 版本,不包含 Python 实现。
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Early Bird Range Breakout: N-bar high/low breakout with EMA filter and ATR stops.
/// </summary>
public class EarlyBirdRangeBreakoutStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _emaLength;
	private readonly StrategyParam<int> _atrLength;

	private decimal _prevHigh;
	private decimal _prevLow;
	private decimal _entryPrice;

	public EarlyBirdRangeBreakoutStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(8).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe.", "General");
		_emaLength = Param(nameof(EmaLength), 20)
			.SetDisplay("EMA Length", "Trend filter.", "Indicators");
		_atrLength = Param(nameof(AtrLength), 14)
			.SetDisplay("ATR Length", "ATR period.", "Indicators");
	}

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int EmaLength { get => _emaLength.Value; set => _emaLength.Value = value; }
	public int AtrLength { get => _atrLength.Value; set => _atrLength.Value = value; }

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_prevHigh = 0; _prevLow = 0; _entryPrice = 0;
	}

		protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_prevHigh = 0; _prevLow = 0; _entryPrice = 0;
		var ema = new ExponentialMovingAverage { Length = EmaLength };
		var atr = new AverageTrueRange { Length = AtrLength };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(ema, atr, ProcessCandle).Start();
		var area = CreateChartArea();
		if (area != null) { DrawCandles(area, subscription); DrawIndicator(area, ema); DrawOwnTrades(area); }
	}

	private void ProcessCandle(ICandleMessage candle, decimal emaVal, decimal atrVal)
	{
		if (candle.State != CandleStates.Finished) return;
		var close = candle.ClosePrice;
		if (_prevHigh == 0 || atrVal <= 0) { _prevHigh = candle.HighPrice; _prevLow = candle.LowPrice; return; }

		if (Position > 0)
		{
			if (close <= _entryPrice - atrVal * 1.5m || close >= _entryPrice + atrVal * 2.5m) { SellMarket(); _entryPrice = 0; }
		}
		else if (Position < 0)
		{
			if (close >= _entryPrice + atrVal * 1.5m || close <= _entryPrice - atrVal * 2.5m) { BuyMarket(); _entryPrice = 0; }
		}

		if (Position == 0)
		{
			if (close > _prevHigh && close > emaVal) { _entryPrice = close; BuyMarket(); }
			else if (close < _prevLow && close < emaVal) { _entryPrice = close; SellMarket(); }
		}
		_prevHigh = candle.HighPrice; _prevLow = candle.LowPrice;
	}
}