在 GitHub 上查看

FT TIME BIGDOG 突破策略

概述

FT TIME BIGDOG 是从 MetaTrader 4 智能交易系统 FT_TIME_BIGDOG.mq4(目录 MQL/9259)移植到 StockSharp 的伦敦时段突破策略。 策略在设定的开始和结束小时之间统计价格区间,并在时间窗口结束后在区间上下方分别挂出买入/卖出止损单。 本移植版本保留原始逻辑,同时提供灵活的时间、范围和风险管理参数。

交易逻辑

  1. 每个交易日记录所有开盘时间位于 StartHourStopHour(包含边界)之间的已完成蜡烛的最高价和最低价。
  2. StopHour 的蜡烛收盘后,如果区间宽度小于 RangeLimitPoints,就会准备两个待定止损单:
    • 在记录的最高价上方挂出 买入止损单
    • 在记录的最低价下方挂出 卖出止损单
  3. 只有当当前市场价格距离触发价至少 OrderBufferPoints 时才会真正下单。优先使用 Level1 行情中的最佳买卖价,若不可用则采用最后一根蜡烛的收盘价。
  4. 每个止损单都会附带止损(位于区间另一侧)和按照 TakeProfitPoints 计算的止盈目标。
  5. 一旦某个方向的挂单成交,另一张挂单会被取消。持仓期间会在蜡烛收盘时检查价格是否触及止损或止盈,若满足条件则以市价平仓。
  6. 策略每天最多执行一次该流程,并在新交易日开始时重置内部状态。

参数

参数 默认值 说明
StartHour 14 累积区间的起始小时(0–23)。
StopHour 16 允许挂出止损单的结束小时,必须 ≥ StartHour
RangeLimitPoints 50 区间最大宽度(以经纪商点数表示,点数 × PointMultiplier)。超出该值则不下单。
TakeProfitPoints 50 成交仓位的止盈距离(经纪商点数)。
OrderBufferPoints 20 市价与挂单价格之间的最小距离,防止过于接近当前价格。
PointMultiplier 1 点值倍数,用于处理五位报价;五位外汇品种建议设置为 10。
Volume 0.1 两个止损单的下单数量。
CandleType 1 小时 用于统计区间和生成信号的蜡烛类型。

风险与仓位管理

  • 多头仓位的止损设为区间最低价,空头仓位的止损设为区间最高价。
  • 止盈价基于触发价加减 TakeProfitPoints 与价格步长计算。
  • 所有风控在蜡烛收盘时触发,盘中瞬时突破止损/止盈可能导致延迟平仓。

与原版 EA 的差异

  • 原始 MT4 版本基于逐笔报价运行,本移植依赖已完成蜡烛与 Level1 行情,蜡烛内部的行为可能略有差异。
  • 点值换算使用 Security.PriceStep × PointMultiplier,实盘前请确认价格步长配置正确。
  • 仅支持 StartHour <= StopHour 的时间窗口,跨越午夜的设置暂未实现。

使用建议

  1. 绑定目标交易品种,并确认数据源提供 Level1 行情,以便精确计算价格缓冲。
  2. 根据经纪商服务器时区调整开始与结束小时。
  3. 建议先在仿真环境测试,以验证点值换算和时间逻辑是否符合预期。
  4. 如需手动修改挂单,请先停止策略以避免状态不一致。

文件

  • CS/FtTimeBigdogStrategy.cs —— StockSharp 实现,包含详细英文注释。
  • MQL/9259/FT_TIME_BIGDOG.mq4 —— 原始 MetaTrader EA 源码。
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// FT Time Bigdog: Range breakout using N-bar high/low channel with ATR stops.
/// </summary>
public class FtTimeBigdogStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _channelLength;
	private readonly StrategyParam<int> _atrLength;

	private decimal _entryPrice;
	private decimal _highest;
	private decimal _lowest;
	private int _barCount;
	private readonly decimal[] _highs = new decimal[20];
	private readonly decimal[] _lows = new decimal[20];

	public FtTimeBigdogStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe.", "General");

		_channelLength = Param(nameof(ChannelLength), 20)
			.SetDisplay("Channel Length", "Lookback for high/low channel.", "Indicators");

		_atrLength = Param(nameof(AtrLength), 14)
			.SetDisplay("ATR Length", "ATR period.", "Indicators");
	}

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

	public int ChannelLength
	{
		get => _channelLength.Value;
		set => _channelLength.Value = value;
	}

	public int AtrLength
	{
		get => _atrLength.Value;
		set => _atrLength.Value = value;
	}

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

		_entryPrice = 0;
		_barCount = 0;
		_highest = 0;
		_lowest = 0;
		Array.Clear(_highs, 0, _highs.Length);
		Array.Clear(_lows, 0, _lows.Length);
	}

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

		_entryPrice = 0;
		_barCount = 0;
		_highest = 0;
		_lowest = 0;
		Array.Clear(_highs, 0, _highs.Length);
		Array.Clear(_lows, 0, _lows.Length);

		var atr = new AverageTrueRange { Length = AtrLength };

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

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawOwnTrades(area);
		}
	}

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

		var len = Math.Min(ChannelLength, _highs.Length);
		var idx = _barCount % len;
		_highs[idx] = candle.HighPrice;
		_lows[idx] = candle.LowPrice;
		_barCount++;

		if (_barCount < len || atrVal <= 0)
			return;

		var high = decimal.MinValue;
		var low = decimal.MaxValue;
		for (var i = 0; i < len; i++)
		{
			if (_highs[i] > high) high = _highs[i];
			if (_lows[i] < low) low = _lows[i];
		}

		var prevHigh = _highest;
		var prevLow = _lowest;
		_highest = high;
		_lowest = low;

		if (prevHigh == 0 || prevLow == 0)
			return;

		var close = candle.ClosePrice;

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

		if (Position == 0)
		{
			if (close > prevHigh)
			{
				_entryPrice = close;
				BuyMarket();
			}
			else if (close < prevLow)
			{
				_entryPrice = close;
				SellMarket();
			}
		}
	}
}