在 GitHub 上查看

Time Bomb 策略

Time Bomb 策略还原了同名的 MetaTrader 智能交易系统:当价格在极短时间内突进指定点数时,它只开一笔仓位。 策略持续跟踪最新的买一与卖一价格,计算最新报价与参考价之间跨越的点数。如果价格在给定的时间窗口内快速 达到设定距离,就按照突破方向发送市价单,并以点数形式即时计算隐藏的止损与止盈水平。

实现严格遵循原版的模块逻辑:若已有持仓则不会重复开仓。触发信号后或观察窗口到期后参考价会重置,因此每 次波动最多只产生一笔多头或空头交易。止损和止盈完全在策略内部维护,因为 StockSharp 在发送市价单时不会 自动挂出保护单。

细节

  • 入场条件
    • 多头:最新卖一价相对于参考价至少上涨 BuyPipsInTime 点,并且该涨幅在 BuyTimeToWait 秒内完成。 满足条件后按 BuyVolume 的数量买入。
    • 空头:最新买一价相对于参考价至少下跌 SellPipsInTime 点,并且该跌幅在 SellTimeToWait 秒内完成。 满足条件后按 SellVolume 的数量卖出。
  • 多空方向:支持双向交易,但任意时刻只允许存在一笔仓位。
  • 离场条件
    • 多头:当买一价触及预先计算的止损或止盈价位时平仓。
    • 空头:当卖一价触及止损,或买一价触及止盈时平仓。
  • 止损:使用隐藏式保护止损/止盈,距离以点数指定,并根据当前品种的最小报价步长换算成价格。
  • 默认参数
    • SellPipsInTime = 5 点,SellTimeToWait = 10 秒,SellVolume = 0.01 手。
    • SellStopLossPips = 20 点,SellTakeProfitPips = 20 点。
    • BuyPipsInTime = 5 点,BuyTimeToWait = 10 秒,BuyVolume = 0.01 手。
    • BuyStopLossPips = 20 点,BuyTakeProfitPips = 20 点。
  • 过滤标签
    • 分类:突破 / 动量。
    • 方向:对称(多头与空头)。
    • 指标:仅使用原始价格波动,无其他指标。
    • 止损:是(各方向固定点数)。
    • 复杂度:低——单一突破检测与简单状态机。
    • 时间框架:日内,依赖逐笔行情,每秒至少检查一次。
    • 季节性:否。
    • 神经网络:否。
    • 背离:否。
    • 风险等级:取决于所设定的点数距离;默认值在主要外汇对上属于中等风险。

参数

名称 说明
SellPipsInTime 触发做空前所需的最小下跌点数。
SellTimeToWait 允许价格完成下跌的时间(秒)。
SellVolume 空头信号使用的交易手数。
SellStopLossPips 空头止损距离(点)。
SellTakeProfitPips 空头止盈距离(点)。
BuyPipsInTime 触发做多前所需的最小上涨点数。
BuyTimeToWait 允许价格完成上涨的时间(秒)。
BuyVolume 多头信号使用的交易手数。
BuyStopLossPips 多头止损距离(点)。
BuyTakeProfitPips 多头止盈距离(点)。

说明

  • 策略依赖最佳买卖价,请确保数据源能提供准确的 Level1 报价。
  • 若将任一距离或时间窗口设为零,相应信号会被禁用,因为参考价会被立即重置而不会触发交易。
  • 由于保护价位在策略内部管理,若发生断线可能导致仓位缺乏硬性止损。实盘运行时建议叠加外部风控措施。
namespace StockSharp.Samples.Strategies;

using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;

/// <summary>
/// Time Bomb strategy: Momentum zero-line crossover.
/// Buys when momentum crosses above zero, sells when momentum crosses below zero.
/// </summary>
public class TimeBombStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _period;
	private readonly StrategyParam<int> _signalCooldownCandles;

	private decimal _prevMom;
	private int _candlesSinceTrade;
	private bool _hasPrev;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int Period { get => _period.Value; set => _period.Value = value; }
	public int SignalCooldownCandles { get => _signalCooldownCandles.Value; set => _signalCooldownCandles.Value = value; }

	public TimeBombStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
		_period = Param(nameof(Period), 10)
			.SetGreaterThanZero()
			.SetDisplay("Period", "Momentum period", "Indicators");
		_signalCooldownCandles = Param(nameof(SignalCooldownCandles), 4)
			.SetGreaterThanZero()
			.SetDisplay("Signal Cooldown", "Bars to wait between trades", "Trading");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevMom = 0;
		_candlesSinceTrade = SignalCooldownCandles;
		_hasPrev = false;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_prevMom = 0;
		_candlesSinceTrade = SignalCooldownCandles;
		_hasPrev = false;
		var mom = new Momentum { Length = Period };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(mom, ProcessCandle).Start();
	}

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

		if (_candlesSinceTrade < SignalCooldownCandles)
			_candlesSinceTrade++;

		if (_hasPrev)
		{
			if (_prevMom <= 0 && momValue > 0 && Position <= 0 && _candlesSinceTrade >= SignalCooldownCandles)
			{
				BuyMarket();
				_candlesSinceTrade = 0;
			}
			else if (_prevMom >= 0 && momValue < 0 && Position >= 0 && _candlesSinceTrade >= SignalCooldownCandles)
			{
				SellMarket();
				_candlesSinceTrade = 0;
			}
		}

		_prevMom = momValue;
		_hasPrev = true;
	}
}