在 GitHub 上查看
FT TIME BIGDOG 突破策略
概述
FT TIME BIGDOG 是从 MetaTrader 4 智能交易系统 FT_TIME_BIGDOG.mq4(目录 MQL/9259)移植到 StockSharp 的伦敦时段突破策略。
策略在设定的开始和结束小时之间统计价格区间,并在时间窗口结束后在区间上下方分别挂出买入/卖出止损单。
本移植版本保留原始逻辑,同时提供灵活的时间、范围和风险管理参数。
交易逻辑
- 每个交易日记录所有开盘时间位于 StartHour 与 StopHour(包含边界)之间的已完成蜡烛的最高价和最低价。
- 当 StopHour 的蜡烛收盘后,如果区间宽度小于 RangeLimitPoints,就会准备两个待定止损单:
- 在记录的最高价上方挂出 买入止损单。
- 在记录的最低价下方挂出 卖出止损单。
- 只有当当前市场价格距离触发价至少 OrderBufferPoints 时才会真正下单。优先使用 Level1 行情中的最佳买卖价,若不可用则采用最后一根蜡烛的收盘价。
- 每个止损单都会附带止损(位于区间另一侧)和按照 TakeProfitPoints 计算的止盈目标。
- 一旦某个方向的挂单成交,另一张挂单会被取消。持仓期间会在蜡烛收盘时检查价格是否触及止损或止盈,若满足条件则以市价平仓。
- 策略每天最多执行一次该流程,并在新交易日开始时重置内部状态。
参数
| 参数 |
默认值 |
说明 |
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 的时间窗口,跨越午夜的设置暂未实现。
使用建议
- 绑定目标交易品种,并确认数据源提供 Level1 行情,以便精确计算价格缓冲。
- 根据经纪商服务器时区调整开始与结束小时。
- 建议先在仿真环境测试,以验证点值换算和时间逻辑是否符合预期。
- 如需手动修改挂单,请先停止策略以避免状态不一致。
文件
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();
}
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Algo.Indicators import AverageTrueRange
class ft_time_bigdog_strategy(Strategy):
def __init__(self):
super(ft_time_bigdog_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe.", "General")
self._channel_length = self.Param("ChannelLength", 20) \
.SetDisplay("Channel Length", "Lookback for high/low channel.", "Indicators")
self._atr_length = self.Param("AtrLength", 14) \
.SetDisplay("ATR Length", "ATR period.", "Indicators")
self._entry_price = 0.0
self._highest = 0.0
self._lowest = 0.0
self._bar_count = 0
self._highs = []
self._lows = []
@property
def CandleType(self):
return self._candle_type.Value
@property
def ChannelLength(self):
return self._channel_length.Value
@property
def AtrLength(self):
return self._atr_length.Value
def OnStarted2(self, time):
super(ft_time_bigdog_strategy, self).OnStarted2(time)
self._entry_price = 0.0
self._bar_count = 0
self._highest = 0.0
self._lowest = 0.0
self._highs = [0.0] * 20
self._lows = [0.0] * 20
self._atr = AverageTrueRange()
self._atr.Length = self.AtrLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._atr, self.ProcessCandle).Start()
def ProcessCandle(self, candle, atr_val):
if candle.State != CandleStates.Finished:
return
av = float(atr_val)
length = min(self.ChannelLength, 20)
idx = self._bar_count % length
self._highs[idx] = float(candle.HighPrice)
self._lows[idx] = float(candle.LowPrice)
self._bar_count += 1
if self._bar_count < length or av <= 0:
return
high = max(self._highs[i] for i in range(length))
low = min(self._lows[i] for i in range(length))
prev_high = self._highest
prev_low = self._lowest
self._highest = high
self._lowest = low
if prev_high == 0 or prev_low == 0:
return
close = float(candle.ClosePrice)
if self.Position > 0:
if close >= self._entry_price + av * 3.0 or close <= self._entry_price - av * 2.0:
self.SellMarket()
self._entry_price = 0.0
elif self.Position < 0:
if close <= self._entry_price - av * 3.0 or close >= self._entry_price + av * 2.0:
self.BuyMarket()
self._entry_price = 0.0
if self.Position == 0:
if close > prev_high:
self._entry_price = close
self.BuyMarket()
elif close < prev_low:
self._entry_price = close
self.SellMarket()
def OnReseted(self):
super(ft_time_bigdog_strategy, self).OnReseted()
self._entry_price = 0.0
self._bar_count = 0
self._highest = 0.0
self._lowest = 0.0
self._highs = []
self._lows = []
def CreateClone(self):
return ft_time_bigdog_strategy()