在 GitHub 上查看

Gaps 策略

该策略基于价格缺口的反转思路:当新 K 线的开盘价突破上一根 K 线的最高价或最低价一定的点数后,立即在 反方向建立仓位,并通过固定止损、固定止盈以及可选的分级跟踪止损来管理风险。

工作流程

  1. 在选定的时间框架上监控单个标的的收盘 K 线数据。
  2. 每当新 K 线生成时,对比最近两根 K 线的开盘价与上一根 K 线的高低点:
    • 如果新 K 线开盘价低于上一根最低价并超过 GapPips 点,判定出现向下跳空,买入做多,期待价格回补缺口;
    • 如果新 K 线开盘价高于上一根最高价并超过 GapPips 点,判定出现向上跳空,卖出做空,期待价格回落。
  3. 入场后策略自动进行持仓管理:
    • 止损价设在距离入场价 StopLossPips 点的位置(多单在下方,空单在上方);
    • 止盈价设在距离入场价 TakeProfitPips 点的位置(沿持仓方向);
    • 若开启跟踪止损,当价格累计向有利方向移动 TrailingStopPips + TrailingStepPips 点时, 将止损价锁定在距离最新极值 TrailingStopPips 点的位置,且只有当价格再次前进至少 TrailingStepPips 点时才会继续移动。
  4. 每根已完成 K 线都会根据最高价与最低价检查是否触发止损或止盈,确保盘中触价能够在下一根 K 线立即执行退出。
  5. 入场前会取消所有挂单,并在需要反手时通过一笔市价单同时平掉旧仓位并建立新方向仓位。

参数说明

  • OrderVolume = 0.1 —— 每次建仓的交易量(手数)。
  • StopLossPips = 50 —— 入场价与止损价之间的点数,设为 0 表示不使用止损。
  • TakeProfitPips = 50 —— 入场价与止盈价之间的点数,设为 0 表示不使用止盈。
  • TrailingStopPips = 5 —— 跟踪止损与最新极值之间的点数,设为 0 表示关闭跟踪功能。
  • TrailingStepPips = 5 —— 每次移动跟踪止损前所需的最小价格改变量(点)。
  • GapPips = 1 —— 触发交易所需的最小跳空幅度(点)。
  • CandleType = 1 小时时间框架 —— 用于监控缺口并执行风控的 K 线类型。

实现细节

  • 所有以点数表示的参数会根据标的的最小报价单位转换成绝对价格;对外汇市场的三位或五位小数报价会自动调 整以获得真实的“pip”尺寸。
  • TrailingStopPips 大于 0 时,TrailingStepPips 必须为正值,否则策略会在启动时抛出异常,避免与原始 MQL 版本逻辑不一致。
  • 策略仅在完整收盘的 K 线上进行信号与风控判断,符合 StockSharp 高阶 API 的使用规范。
  • 止损与止盈通过市价离场实现,不会在盘口中挂出额外的保护性订单。
  • 默认参数针对外汇品种设计;在波动率不同的市场中使用时,请根据需要调整点数与时间框架。
using System;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Gap trading strategy. Detects price gaps between candles and trades the gap fill.
/// </summary>
public class GapsStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<decimal> _gapPercent;

	private decimal? _prevClose;

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

	public decimal GapPercent
	{
		get => _gapPercent.Value;
		set => _gapPercent.Value = value;
	}

	public GapsStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe", "General");

		_gapPercent = Param(nameof(GapPercent), 0.05m)
			.SetDisplay("Gap Percent", "Minimum gap size as percentage", "Trading");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevClose = null;
	}

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

		_prevClose = null;

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

		StartProtection(
			takeProfit: new Unit(2, UnitTypes.Percent),
			stopLoss: new Unit(1, UnitTypes.Percent)
		);

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

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

		if (_prevClose == null)
		{
			_prevClose = candle.ClosePrice;
			return;
		}

		var prevClose = _prevClose.Value;
		var open = candle.OpenPrice;
		var close = candle.ClosePrice;
		_prevClose = close;

		if (prevClose == 0)
			return;

		var gapPct = (open - prevClose) / prevClose * 100;

		// Gap up detected - sell expecting gap fill
		if (gapPct > GapPercent && Position == 0)
		{
			SellMarket();
		}
		// Gap down detected - buy expecting gap fill
		else if (gapPct < -GapPercent && Position == 0)
		{
			BuyMarket();
		}
	}
}