Стратегия Gap Filling
Стратегия Gap Filling ищет ценовые разрывы между сессиями и торгует их заполнение. По умолчанию она открывает позицию против направления гэпа, ожидая возврата цены к уровню предыдущего дня. При включённом параметре Invert торгует в направлении гэпа и использует этот уровень как стоп.
Подробности
- Данные: свечи цены.
- Условия входа:
- Лонг: новая сессия и разрыв вниз (или вверх при
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()