Overnight Gap используется при сильных гэпах на открытии по сравнению с предыдущим закрытием, вызванных новостями или послеторговой активностью.
Крупные гэпы часто частично закрываются, когда участники осмысливают движение.
Тестирование показывает среднегодичную доходность около 124%. Стратегию лучше запускать на рынке Форекс.
Стратегия торгует против чрезмерных разрывов, входя вскоре после открытия в противоположную сторону и закрываясь до конца сессии.
Стопы ставятся на процентную величину за пределами экстремумов гэпа, чтобы управлять риском, если движение продолжится.
Детали
Критерий входа: сигнал индикатора
Длинная/короткая сторона: обе
Критерий выхода: стоп-лосс или противоположный сигнал
Стопы: да, процентные
Значения по умолчанию:
CandleType = 15 минут
StopLossPercent = 2
MinGapPercent = 0.01
Рынок торгуется круглосуточно, поэтому сессия — это сутки по UTC. Гэп — расстояние от последнего закрытия предыдущего дня до первого открытия дня. Гэп не меньше MinGapPercent отыгрывается против по закрытию первой свечи; стоп ставится на StopLossPercent за максимумом (шорт) или минимумом (лонг) этой свечи и проверяется по закрытиям свечей, а позиция закрывается на последней свече дня.
Фильтры:
Категория: Gap
Направление: обе
Индикаторы: Gap
Стопы: да
Сложность: средняя
Таймфрейм: внутридневной
Сезонность: нет
Нейросети: нет
Дивергенция: нет
Уровень риска: средний
using System;
using System.Collections.Generic;
using System.Linq;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Overnight Gap strategy.
/// The market trades around the clock, so the session is the UTC day.
/// The gap is the distance between the previous UTC day's last close and the new day's first open. A gap of at least MinGapPercent
/// is faded at the close of the day's first candle; the stop lies StopLossPercent beyond the first candle's extreme in the gap's direction,
/// and the position closes at the day's last candle.
/// </summary>
public class OvernightGapStrategy : Strategy
{
private readonly StrategyParam<decimal> _minGapPercent;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<DataType> _candleType;
private DateTime? _day;
private decimal? _prevClose;
private decimal _stopPrice;
/// <summary>
/// Smallest gap to fade, in percent.
/// </summary>
public decimal MinGapPercent
{
get => _minGapPercent.Value;
set => _minGapPercent.Value = value;
}
/// <summary>
/// Distance of the stop beyond the first candle's extreme, in percent.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public OvernightGapStrategy()
{
_minGapPercent = Param(nameof(MinGapPercent), 0.01m)
.SetGreaterThanZero()
.SetDisplay("Min Gap %", "Smallest gap to fade, in percent", "Session");
_stopLossPercent = Param(nameof(StopLossPercent), 2m)
.SetNotNegative()
.SetDisplay("Stop Loss %", "Distance of the stop beyond the first candle's extreme, in percent", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_day = null;
_prevClose = null;
_stopPrice = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_day = null;
_prevClose = null;
_stopPrice = default;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ClosePosition()
{
if (Position > 0)
SellMarket(Position);
else if (Position < 0)
BuyMarket(-Position);
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var day = candle.OpenTime.Date;
var firstOfDay = _day != day;
if (firstOfDay)
{
_day = day;
}
var frame = CandleType.Arg is TimeSpan tf ? tf : TimeSpan.Zero;
var lastOfDay = (candle.OpenTime + frame).Date != day;
var prevClose = _prevClose;
_prevClose = candle.ClosePrice;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var close = candle.ClosePrice;
if (Position != 0)
{
if (lastOfDay || (Position > 0 ? close <= _stopPrice : close >= _stopPrice))
ClosePosition();
return;
}
if (!firstOfDay || prevClose is not decimal last || last <= 0)
return;
var gap = (candle.OpenPrice - last) / last * 100m;
if (gap >= MinGapPercent)
{
SellMarket(Volume);
_stopPrice = candle.HighPrice * (1 + StopLossPercent / 100m);
}
else if (-gap >= MinGapPercent)
{
BuyMarket(Volume);
_stopPrice = candle.LowPrice * (1 - StopLossPercent / 100m);
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes, Level1Fields
from StockSharp.BusinessEntities import Subscription
from StockSharp.Algo.Strategies import Strategy
class overnight_gap_strategy(Strategy):
"""
Overnight Gap strategy.
The market trades around the clock, so the session is the UTC day.
The gap is the distance between the previous UTC day's last close and the new day's first open. A gap of at least MinGapPercent
is faded at the close of the day's first candle; the stop lies StopLossPercent beyond the first candle's extreme in the gap's direction,
and the position closes at the day's last candle.
"""
def __init__(self):
super(overnight_gap_strategy, self).__init__()
self._min_gap_percent = self.Param("MinGapPercent", 0.01).SetGreaterThanZero().SetDisplay("Min Gap %", "Smallest gap to fade, in percent", "Session")
self._stop_loss_percent = self.Param("StopLossPercent", 2.0).SetNotNegative().SetDisplay("Stop Loss %", "Distance of the stop beyond the first candle's extreme, in percent", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def _reset_state(self):
self._day = None
self._prev_close = None
self._stop_price = Decimal(0)
def OnReseted(self):
super(overnight_gap_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(overnight_gap_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 _close_position(self):
if self.Position > 0:
self.SellMarket(self.Position)
elif self.Position < 0:
self.BuyMarket(-self.Position)
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
day = candle.OpenTime.Date
first_of_day = self._day is None or self._day != day
if first_of_day:
self._day = day
frame = self.candle_type.Arg
last_of_day = (candle.OpenTime + frame).Date != day
prev_close = self._prev_close
self._prev_close = candle.ClosePrice
if not self.IsFormedAndOnlineAndAllowTrading():
return
close = candle.ClosePrice
if self.Position != 0:
stopped = close <= self._stop_price if self.Position > 0 else close >= self._stop_price
if last_of_day or stopped:
self._close_position()
return
if not first_of_day or prev_close is None or prev_close <= 0:
return
gap = (candle.OpenPrice - prev_close) / prev_close * Decimal(100)
min_gap = Decimal(self._min_gap_percent.Value)
percent = Decimal(self._stop_loss_percent.Value) / Decimal(100)
if gap >= min_gap:
self.SellMarket(self.Volume)
self._stop_price = candle.HighPrice * (Decimal(1) + percent)
elif -gap >= min_gap:
self.BuyMarket(self.Volume)
self._stop_price = candle.LowPrice * (Decimal(1) - percent)
def CreateClone(self):
return overnight_gap_strategy()