Gap Filling 策略
Gap Filling 策略在新交易日开盘时寻找价格跳空。出现跳空时,策略默认反向开仓并期望价格回补到前一日水平;若启用 Invert 参数,则顺势交易并在跳空水平设置止损。
细节
- 数据: 价格K线。
- 入场条件:
- 多头: 新交易日出现向下跳空(
Invert时为向上跳空)。 - 空头: 新交易日出现向上跳空(
Invert时为向下跳空)。
- 多头: 新交易日出现向下跳空(
- 出场条件:
- 价格回补跳空(止盈),或在
Invert时触及跳空水平的止损。
- 价格回补跳空(止盈),或在
- 止损: 使用前一日价格作为目标或止损。
- 默认参数:
CandleType= 1 分钟Invert= falseCloseWhen= NewSession
- 过滤器:
- 类别: 跳空交易
- 方向: 多 & 空
- 指标: 无
- 复杂度: 简单
- 风险级别: 中等
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 filling strategy.
/// A session is a UTC calendar day. When its first candle opens away from the previous session's last close, the strategy fades the gap
/// (short an up gap, long a down gap) with the previous close as the profit target. With Invert it trades in the gap direction instead
/// and the previous close becomes the stop. With CloseWhen set to NewSession an open position is also closed when the next session
/// starts; with GapLevel it stays open until the target or stop is reached.
/// </summary>
public class GapFillingStrategy : Strategy
{
/// <summary>
/// When a position is closed besides the gap level.
/// </summary>
public enum CloseModes
{
/// <summary>
/// Close at the start of the next session or at the gap level.
/// </summary>
NewSession,
/// <summary>
/// Close only at the gap level.
/// </summary>
GapLevel,
}
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<bool> _invert;
private readonly StrategyParam<CloseModes> _closeWhen;
private DateTime? _sessionDay;
private decimal? _lastClose;
private decimal _gapLevel;
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Trade in the gap direction with a stop at the gap level instead of fading it.
/// </summary>
public bool Invert
{
get => _invert.Value;
set => _invert.Value = value;
}
/// <summary>
/// When the position is closed besides the gap level.
/// </summary>
public CloseModes CloseWhen
{
get => _closeWhen.Value;
set => _closeWhen.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public GapFillingStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_invert = Param(nameof(Invert), false)
.SetDisplay("Invert", "Trade in the gap direction with a stop at the gap level", "Trading");
_closeWhen = Param(nameof(CloseWhen), CloseModes.NewSession)
.SetDisplay("Close When", "When the position is closed besides the gap level", "Trading");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
ResetState();
}
private void ResetState()
{
_sessionDay = null;
_lastClose = null;
_gapLevel = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetState();
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var day = candle.OpenTime.Date;
var newSession = _sessionDay is DateTime current && current != day;
var previousClose = _lastClose;
_sessionDay = day;
_lastClose = candle.ClosePrice;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (newSession)
{
if (Position != 0 && CloseWhen != CloseModes.NewSession)
return;
var target = 0m;
if (previousClose is decimal gapLevel && candle.OpenPrice != gapLevel)
{
_gapLevel = gapLevel;
var gapUp = candle.OpenPrice > gapLevel;
target = gapUp != Invert ? -Volume : Volume;
}
// One order both closes the previous session's position and opens the new one.
var diff = target - Position;
if (diff > 0)
BuyMarket(diff);
else if (diff < 0)
SellMarket(-diff);
return;
}
if (Position == 0 || _gapLevel <= 0)
return;
// A faded gap targets the gap level; an inverted trade is stopped there.
if (Position > 0)
{
var hit = Invert ? candle.LowPrice <= _gapLevel : candle.HighPrice >= _gapLevel;
if (hit)
ClosePosition();
}
else
{
var hit = Invert ? candle.HighPrice >= _gapLevel : candle.LowPrice <= _gapLevel;
if (hit)
ClosePosition();
}
}
private void ClosePosition()
{
if (Position > 0)
SellMarket(Position);
else if (Position < 0)
BuyMarket(Math.Abs(Position));
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class CloseModes:
"""When a position is closed besides the gap level."""
NewSession = 0
GapLevel = 1
class gap_filling_strategy(Strategy):
"""
Gap filling strategy.
A session is a UTC calendar day. When its first candle opens away from the previous session's last close, the strategy fades the gap
(short an up gap, long a down gap) with the previous close as the profit target. With Invert it trades in the gap direction instead
and the previous close becomes the stop. With CloseWhen set to NewSession an open position is also closed when the next session
starts; with GapLevel it stays open until the target or stop is reached.
"""
def __init__(self):
super(gap_filling_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._invert = self.Param("Invert", False).SetDisplay("Invert", "Trade in the gap direction with a stop at the gap level", "Trading")
self._close_when = self.Param("CloseWhen", CloseModes.NewSession).SetDisplay("Close When", "When the position is closed besides the gap level", "Trading")
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def _reset_state(self):
self._session_day = None
self._last_close = None
self._gap_level = None
def OnReseted(self):
super(gap_filling_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(gap_filling_strategy, self).OnStarted2(time)
self._reset_state()
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
day = candle.OpenTime.Date
new_session = self._session_day is not None and self._session_day != day
previous_close = self._last_close
self._session_day = day
self._last_close = candle.ClosePrice
if not self.IsFormedAndOnlineAndAllowTrading():
return
invert = self._invert.Value
if new_session:
if self.Position != 0 and self._close_when.Value != CloseModes.NewSession:
return
target = 0
if previous_close is not None and candle.OpenPrice != previous_close:
self._gap_level = previous_close
gap_up = candle.OpenPrice > previous_close
target = -self.Volume if gap_up != invert else self.Volume
# One order both closes the previous session's position and opens the new one.
diff = target - self.Position
if diff > 0:
self.BuyMarket(diff)
elif diff < 0:
self.SellMarket(-diff)
return
if self.Position == 0 or self._gap_level is None:
return
# A faded gap targets the gap level; an inverted trade is stopped there.
if self.Position > 0:
hit = candle.LowPrice <= self._gap_level if invert else candle.HighPrice >= self._gap_level
if hit:
self.SellMarket(self.Position)
else:
hit = candle.HighPrice >= self._gap_level if invert else candle.LowPrice <= self._gap_level
if hit:
self.BuyMarket(abs(self.Position))
def CreateClone(self):
return gap_filling_strategy()