IU Gap Fill Strategy
IU Gap Fill Strategy enters trades when the price gaps away from the previous session close and then fills that gap. A long position opens after a gap up that dips below the prior close and closes back above it. A short position opens after a gap down that rallies above the prior close and closes back below. An ATR-based trailing stop manages exits.
Details
- Data: Candles from a user-defined timeframe.
- Entry Criteria:
- Long: Gap up of at least
GapPercentand price crosses above the previous session close. - Short: Gap down of at least
GapPercentand price crosses below the previous session close.
- Long: Gap up of at least
- Exit Criteria: ATR trailing stop.
- Stops: ATR
AtrLength*AtrFactortrailing level. - Default Values:
CandleType= 1mGapPercent= 0.2AtrLength= 14AtrFactor= 2
- Filters:
- Category: Gap
- Direction: Long & Short
- Indicators: ATR
- Complexity: Low
- Risk level: Medium
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Trades when a session gap of a given size is filled.
/// </summary>
public class IUGapFillStrategy : Strategy
{
private readonly StrategyParam<decimal> _gapPercent;
private readonly StrategyParam<int> _atrLength;
private readonly StrategyParam<decimal> _atrFactor;
private readonly StrategyParam<int> _cooldownDays;
private readonly StrategyParam<DataType> _candleType;
private DateTime _currentDay;
private decimal _lastSessionClose;
private decimal _prevDayClose;
private bool _gapUp;
private bool _gapDown;
private bool _validGap;
private bool _isFirstBar;
private DateTime _entryDay;
private DateTime _nextEntryDate;
/// <summary>
/// Percentage difference for a valid gap.
/// </summary>
public decimal GapPercent
{
get => _gapPercent.Value;
set => _gapPercent.Value = value;
}
/// <summary>
/// ATR period.
/// </summary>
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
/// <summary>
/// ATR multiplier for trailing stop.
/// </summary>
public decimal AtrFactor
{
get => _atrFactor.Value;
set => _atrFactor.Value = value;
}
/// <summary>
/// Minimum number of days between entries.
/// </summary>
public int CooldownDays
{
get => _cooldownDays.Value;
set => _cooldownDays.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="IUGapFillStrategy"/> class.
/// </summary>
public IUGapFillStrategy()
{
_gapPercent = Param(nameof(GapPercent), 0.01m)
.SetDisplay("Gap %", "Minimum percentage gap.", "General");
_atrLength = Param(nameof(AtrLength), 14)
.SetDisplay("ATR Length", "ATR calculation period.", "ATR");
_atrFactor = Param(nameof(AtrFactor), 2m)
.SetDisplay("ATR Factor", "ATR multiplier.", "ATR");
_cooldownDays = Param(nameof(CooldownDays), 1)
.SetDisplay("Cooldown Days", "Minimum days between entries.", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use.", "General");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_currentDay = default;
_lastSessionClose = 0m;
_prevDayClose = 0m;
_gapUp = false;
_gapDown = false;
_validGap = false;
_isFirstBar = false;
_entryDay = default;
_nextEntryDate = DateTime.MinValue;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_currentDay = default;
_lastSessionClose = 0m;
_prevDayClose = 0m;
_gapUp = false;
_gapDown = false;
_validGap = false;
_isFirstBar = false;
_entryDay = default;
_nextEntryDate = DateTime.MinValue;
var dummyEma1 = new ExponentialMovingAverage { Length = 10 };
var dummyEma2 = new ExponentialMovingAverage { Length = 20 };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(dummyEma1, dummyEma2, ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal d1, decimal d2)
{
if (candle.State != CandleStates.Finished)
return;
var day = candle.OpenTime.Date;
if (_currentDay != day)
{
_prevDayClose = _lastSessionClose;
_currentDay = day;
if (Position > 0)
SellMarket();
else if (Position < 0)
BuyMarket();
if (_prevDayClose > 0)
{
_gapUp = candle.OpenPrice > _prevDayClose;
_gapDown = candle.OpenPrice < _prevDayClose;
_validGap = Math.Abs(_prevDayClose - candle.OpenPrice) >= candle.OpenPrice * GapPercent / 100m;
}
_isFirstBar = true;
}
_lastSessionClose = candle.ClosePrice;
if (_isFirstBar)
{
_isFirstBar = false;
}
else if (_validGap && Position == 0 && day >= _nextEntryDate)
{
// Gap fill logic: price returns to previous close level
if (_gapUp && candle.ClosePrice <= _prevDayClose)
{
BuyMarket();
_entryDay = day;
_nextEntryDate = day.AddDays(CooldownDays);
}
else if (_gapDown && candle.ClosePrice >= _prevDayClose)
{
SellMarket();
_entryDay = day;
_nextEntryDate = day.AddDays(CooldownDays);
}
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class iu_gap_fill_strategy(Strategy):
def __init__(self):
super(iu_gap_fill_strategy, self).__init__()
self._gap_percent = self.Param("GapPercent", 0.01) \
.SetDisplay("Gap %", "Minimum percentage gap", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._current_day = None
self._last_session_close = 0.0
self._prev_day_close = 0.0
self._gap_up = False
self._gap_down = False
self._valid_gap = False
self._is_first_bar = False
@property
def candle_type(self):
return self._candle_type.Value
@candle_type.setter
def candle_type(self, value):
self._candle_type.Value = value
def OnReseted(self):
super(iu_gap_fill_strategy, self).OnReseted()
self._current_day = None
self._last_session_close = 0.0
self._prev_day_close = 0.0
self._gap_up = False
self._gap_down = False
self._valid_gap = False
self._is_first_bar = False
def OnStarted2(self, time):
super(iu_gap_fill_strategy, self).OnStarted2(time)
ema1 = ExponentialMovingAverage()
ema1.Length = 10
ema2 = ExponentialMovingAverage()
ema2.Length = 20
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(ema1, ema2, self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def OnProcess(self, candle, d1, d2):
if candle.State != CandleStates.Finished:
return
day = candle.OpenTime.Date
close = float(candle.ClosePrice)
open_p = float(candle.OpenPrice)
if self._current_day is None or self._current_day != day:
self._prev_day_close = self._last_session_close
self._current_day = day
if self.Position > 0:
self.SellMarket()
elif self.Position < 0:
self.BuyMarket()
if self._prev_day_close > 0:
self._gap_up = open_p > self._prev_day_close
self._gap_down = open_p < self._prev_day_close
gap_pct = float(self._gap_percent.Value)
self._valid_gap = abs(self._prev_day_close - open_p) >= open_p * gap_pct / 100.0
self._is_first_bar = True
self._last_session_close = close
if self._is_first_bar:
self._is_first_bar = False
elif self._valid_gap and self.Position == 0:
if self._gap_up and close <= self._prev_day_close:
self.BuyMarket()
elif self._gap_down and close >= self._prev_day_close:
self.SellMarket()
def CreateClone(self):
return iu_gap_fill_strategy()