オーバーナイトギャップ戦略
オーバーナイトギャップは、ニュースやアフターアワーの動きにより、価格が前日終値から大きくギャップして寄り付いたときに機能します。 大きなギャップはトレーダーが動きを消化する過程で、しばしば部分的に埋め戻されます。
テストでは年平均リターンが約124%であることが示されています。外国為替市場で最も効果的です。
この戦略は過大なギャップに逆張りし、寄り付き直後に逆方向にエントリーして、セッション終了前に決済します。
動きが継続した場合のリスク管理のため、ストップはギャップの極値から一定割合を超えた水準に設定します。
詳細
- エントリー条件: インジケーターシグナル
- ロング/ショート: 両方
- エグジット条件: ストップロスまたは逆シグナル
- ストップ: はい、パーセントベース
- デフォルト値:
CandleType= 15 minuteStopLossPercent= 2MinGapPercent= 0.01 市場は24時間取引のため、セッションはUTCの1日とします。 ギャップは前日の最後の終値から当日の最初の始値までの距離です。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()