Overnight Gap Strategy
Overnight Gap plays the open when price gaps significantly from the prior close due to news or after-hours activity. Large gaps often retrace partially as traders digest the move.
Testing indicates an average annual return of about 124%. It performs best in the forex market.
The strategy fades excessive gaps, entering in the opposite direction shortly after the open and closing before the session ends.
Stops are based on a percentage beyond the gap extremes to manage risk if the move continues.
Details
- Entry Criteria: indicator signal
- Long/Short: Both
- Exit Criteria: stop-loss or opposite signal
- Stops: Yes, percent based
- Default Values:
CandleType= 15 minuteStopLossPercent= 2MinGapPercent= 0.01 The market trades around the clock, so the session is the UTC day. The gap is the distance from the previous day's last close to the day's first open. A gap of at least MinGapPercent is faded at the close of the first candle; the stop lies StopLossPercent beyond that candle's high (short) or low (long) and is checked on candle closes, and the position closes at the day's last candle.
- Filters:
- Category: Gap
- Direction: Both
- Indicators: Gap
- Stops: Yes
- Complexity: Intermediate
- Timeframe: Intraday
- Seasonality: No
- Neural networks: No
- Divergence: No
- Risk level: Medium
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()