イントラデイ・出来高スイング戦略
この戦略は、当日または前日の出来高によって定義されたスイング領域に価格が入ったときにトレードします。
詳細
- エントリー条件:
- ロング: 価格が上方スイング領域に進入する。
- ショート: 価格が下方スイング領域に進入する。
- ロング/ショート: 両方向。
- エグジット条件:
- 逆シグナル。
- ストップ: いいえ。
- デフォルト値:
RegionMustClose= trueCandleType= 1 minute
- フィルター:
- カテゴリ: ブレイクアウト
- 方向: 両方
- インジケーター: 出来高
- ストップ: いいえ
- 複雑さ: 中
- 時間軸: イントラデイ
- 季節性: いいえ
- ニューラルネットワーク: いいえ
- ダイバージェンス: いいえ
- リスクレベル: 中
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Intraday volume swings strategy.
/// A swing high forms when three consecutive candles make higher highs on rising volume, a swing low when three make lower lows
/// on rising volume. While the run continues the swing region spans the high and low of its candles. Once it ends, the most
/// extreme region of the day is kept, and the previous day's regions stay active as well. Price pushing up into a high swing
/// region (from the current or previous day) goes long, price pushing down into a low swing region goes short, reversing an
/// opposite position. With RegionMustClose the candle has to close inside the region, otherwise touching it is enough.
/// </summary>
public class IntradayVolumeSwingsStrategy : Strategy
{
private readonly StrategyParam<bool> _regionMustClose;
private readonly StrategyParam<DataType> _candleType;
private DateTime _currentDay;
private int _barCount;
private decimal _prevClose;
private decimal _high1;
private decimal _high2;
private decimal _low1;
private decimal _low2;
private decimal _volume1;
private bool _lowBar1;
private bool _lowBar2;
private bool _highBar1;
private bool _highBar2;
private bool _prevSwingLow;
private bool _prevSwingHigh;
private decimal? _runLowTop;
private decimal? _runLowBottom;
private decimal? _runHighTop;
private decimal? _runHighBottom;
private decimal? _dayLowTop;
private decimal? _dayLowBottom;
private decimal? _dayHighTop;
private decimal? _dayHighBottom;
private decimal? _prevDayLowTop;
private decimal? _prevDayLowBottom;
private decimal? _prevDayHighTop;
private decimal? _prevDayHighBottom;
/// <summary>
/// Require the candle to close inside the region to trigger.
/// </summary>
public bool RegionMustClose
{
get => _regionMustClose.Value;
set => _regionMustClose.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public IntradayVolumeSwingsStrategy()
{
_regionMustClose = Param(nameof(RegionMustClose), true)
.SetDisplay("Region Must Close", "Candle must close inside the region to trigger", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).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();
ResetState();
}
private void ResetState()
{
_currentDay = default;
_barCount = 0;
_prevClose = _high1 = _high2 = _low1 = _low2 = _volume1 = 0m;
_lowBar1 = _lowBar2 = _highBar1 = _highBar2 = false;
_prevSwingLow = _prevSwingHigh = false;
_runLowTop = _runLowBottom = _runHighTop = _runHighBottom = null;
_dayLowTop = _dayLowBottom = _dayHighTop = _dayHighBottom = null;
_prevDayLowTop = _prevDayLowBottom = _prevDayHighTop = _prevDayHighBottom = null;
}
/// <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;
if (_currentDay != day)
{
if (_currentDay != default)
{
_prevDayLowTop = _dayLowTop;
_prevDayLowBottom = _dayLowBottom;
_prevDayHighTop = _dayHighTop;
_prevDayHighBottom = _dayHighBottom;
}
_currentDay = day;
_dayLowTop = _dayLowBottom = _dayHighTop = _dayHighBottom = null;
}
var hasHistory = _barCount > 0;
var risingVolume = hasHistory && candle.TotalVolume > _volume1;
var lowBar = risingVolume && candle.LowPrice < _low1;
var highBar = risingVolume && candle.HighPrice > _high1;
var swingLow = lowBar && _lowBar1 && _lowBar2;
var swingHigh = highBar && _highBar1 && _highBar2;
var top3 = Math.Max(candle.HighPrice, Math.Max(_high1, _high2));
var bottom3 = Math.Min(candle.LowPrice, Math.Min(_low1, _low2));
if (swingLow)
{
_runLowTop = _prevSwingLow && _runLowTop is decimal t ? Math.Max(t, candle.HighPrice) : top3;
_runLowBottom = _prevSwingLow && _runLowBottom is decimal b ? Math.Min(b, candle.LowPrice) : bottom3;
}
else if (_prevSwingLow && _runLowTop is decimal lowTop && _runLowBottom is decimal lowBottom)
{
// Keep the lowest swing low region of the day.
if (_dayLowBottom is not decimal dayBottom || lowBottom < dayBottom)
{
_dayLowTop = lowTop;
_dayLowBottom = lowBottom;
}
_runLowTop = _runLowBottom = null;
}
if (swingHigh)
{
_runHighTop = _prevSwingHigh && _runHighTop is decimal t ? Math.Max(t, candle.HighPrice) : top3;
_runHighBottom = _prevSwingHigh && _runHighBottom is decimal b ? Math.Min(b, candle.LowPrice) : bottom3;
}
else if (_prevSwingHigh && _runHighTop is decimal highTop && _runHighBottom is decimal highBottom)
{
// Keep the highest swing high region of the day.
if (_dayHighTop is not decimal dayTop || highTop > dayTop)
{
_dayHighTop = highTop;
_dayHighBottom = highBottom;
}
_runHighTop = _runHighBottom = null;
}
if (hasHistory && IsFormedAndOnlineAndAllowTrading())
{
var goLong = EntersHighRegion(candle, _dayHighBottom) || EntersHighRegion(candle, _prevDayHighBottom);
var goShort = EntersLowRegion(candle, _dayLowTop) || EntersLowRegion(candle, _prevDayLowTop);
if (goLong && !goShort && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (goShort && !goLong && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
}
_barCount++;
_prevClose = candle.ClosePrice;
_volume1 = candle.TotalVolume;
_high2 = _high1;
_high1 = candle.HighPrice;
_low2 = _low1;
_low1 = candle.LowPrice;
_lowBar2 = _lowBar1;
_lowBar1 = lowBar;
_highBar2 = _highBar1;
_highBar1 = highBar;
_prevSwingLow = swingLow;
_prevSwingHigh = swingHigh;
}
// Price comes from below the region bottom and reaches it.
private bool EntersHighRegion(ICandleMessage candle, decimal? bottom)
{
if (bottom is not decimal level || _prevClose >= level)
return false;
return RegionMustClose ? candle.ClosePrice >= level : candle.HighPrice >= level;
}
// Price comes from above the region top and reaches it.
private bool EntersLowRegion(ICandleMessage candle, decimal? top)
{
if (top is not decimal level || _prevClose <= level)
return false;
return RegionMustClose ? candle.ClosePrice <= level : candle.LowPrice <= level;
}
}
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 intraday_volume_swings_strategy(Strategy):
"""
Intraday volume swings strategy.
A swing high forms when three consecutive candles make higher highs on rising volume, a swing low when three make lower lows
on rising volume. While the run continues the swing region spans the high and low of its candles. Once it ends, the most
extreme region of the day is kept, and the previous day's regions stay active as well. Price pushing up into a high swing
region (from the current or previous day) goes long, price pushing down into a low swing region goes short, reversing an
opposite position. With RegionMustClose the candle has to close inside the region, otherwise touching it is enough.
"""
def __init__(self):
super(intraday_volume_swings_strategy, self).__init__()
self._region_must_close = self.Param("RegionMustClose", True).SetDisplay("Region Must Close", "Candle must close inside the region to trigger", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))).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._current_day = None
self._bar_count = 0
self._prev_close = 0.0
self._high1 = 0.0
self._high2 = 0.0
self._low1 = 0.0
self._low2 = 0.0
self._volume1 = 0.0
self._low_bar1 = False
self._low_bar2 = False
self._high_bar1 = False
self._high_bar2 = False
self._prev_swing_low = False
self._prev_swing_high = False
self._run_low_top = None
self._run_low_bottom = None
self._run_high_top = None
self._run_high_bottom = None
self._day_low_top = None
self._day_low_bottom = None
self._day_high_top = None
self._day_high_bottom = None
self._prev_day_low_top = None
self._prev_day_low_bottom = None
self._prev_day_high_top = None
self._prev_day_high_bottom = None
def OnReseted(self):
super(intraday_volume_swings_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(intraday_volume_swings_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
high = float(candle.HighPrice)
low = float(candle.LowPrice)
close = float(candle.ClosePrice)
volume = float(candle.TotalVolume)
day = candle.OpenTime.Date
if self._current_day != day:
if self._current_day is not None:
self._prev_day_low_top = self._day_low_top
self._prev_day_low_bottom = self._day_low_bottom
self._prev_day_high_top = self._day_high_top
self._prev_day_high_bottom = self._day_high_bottom
self._current_day = day
self._day_low_top = None
self._day_low_bottom = None
self._day_high_top = None
self._day_high_bottom = None
has_history = self._bar_count > 0
rising_volume = has_history and volume > self._volume1
low_bar = rising_volume and low < self._low1
high_bar = rising_volume and high > self._high1
swing_low = low_bar and self._low_bar1 and self._low_bar2
swing_high = high_bar and self._high_bar1 and self._high_bar2
top3 = max(high, self._high1, self._high2)
bottom3 = min(low, self._low1, self._low2)
if swing_low:
self._run_low_top = max(self._run_low_top, high) if self._prev_swing_low and self._run_low_top is not None else top3
self._run_low_bottom = min(self._run_low_bottom, low) if self._prev_swing_low and self._run_low_bottom is not None else bottom3
elif self._prev_swing_low and self._run_low_top is not None and self._run_low_bottom is not None:
# Keep the lowest swing low region of the day.
if self._day_low_bottom is None or self._run_low_bottom < self._day_low_bottom:
self._day_low_top = self._run_low_top
self._day_low_bottom = self._run_low_bottom
self._run_low_top = None
self._run_low_bottom = None
if swing_high:
self._run_high_top = max(self._run_high_top, high) if self._prev_swing_high and self._run_high_top is not None else top3
self._run_high_bottom = min(self._run_high_bottom, low) if self._prev_swing_high and self._run_high_bottom is not None else bottom3
elif self._prev_swing_high and self._run_high_top is not None and self._run_high_bottom is not None:
# Keep the highest swing high region of the day.
if self._day_high_top is None or self._run_high_top > self._day_high_top:
self._day_high_top = self._run_high_top
self._day_high_bottom = self._run_high_bottom
self._run_high_top = None
self._run_high_bottom = None
if has_history and self.IsFormedAndOnlineAndAllowTrading():
go_long = self._enters_high_region(high, close, self._day_high_bottom) or self._enters_high_region(high, close, self._prev_day_high_bottom)
go_short = self._enters_low_region(low, close, self._day_low_top) or self._enters_low_region(low, close, self._prev_day_low_top)
if go_long and not go_short and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif go_short and not go_long and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
self._bar_count += 1
self._prev_close = close
self._volume1 = volume
self._high2 = self._high1
self._high1 = high
self._low2 = self._low1
self._low1 = low
self._low_bar2 = self._low_bar1
self._low_bar1 = low_bar
self._high_bar2 = self._high_bar1
self._high_bar1 = high_bar
self._prev_swing_low = swing_low
self._prev_swing_high = swing_high
# Price comes from below the region bottom and reaches it.
def _enters_high_region(self, high, close, bottom):
if bottom is None or self._prev_close >= bottom:
return False
return close >= bottom if self._region_must_close.Value else high >= bottom
# Price comes from above the region top and reaches it.
def _enters_low_region(self, low, close, top):
if top is None or self._prev_close <= top:
return False
return close <= top if self._region_must_close.Value else low <= top
def CreateClone(self):
return intraday_volume_swings_strategy()