Volume Spike Trend 戦略
Volume Spike Trendは取引出来高の急増を監視します。現在の出来高が直近の平均を設定した乗数倍超えると、強い市場参加を示します。
テストでは年平均リターン約175%を示しています。株式市場での運用に最も適しています。
出来高がスパイクし価格が移動平均より上にある場合は買い、出来高がスパイクし価格が平均を下回っている場合は売り建てます。出来高が平均を下回るかストップロスに達した時点で決済します。
この手法は活動の急増によって動く相場を捉えることを目指しています。
詳細
- エントリー条件: 出来高の変化が平均の
VolumeSpikeMultiplier倍を超える。 - ロング/ショート: 両方向。
- エグジット条件: 出来高が平均を下回るまたはストップ。
- ストップ: あり。
- デフォルト値:
MAPeriod= 20VolAvgPeriod= 20VolumeSpikeMultiplier= 2.0mCandleType= TimeSpan.FromMinutes(5)
- フィルター:
- カテゴリ: ブレイクアウト
- 方向: 両方
- インジケーター: 出来高, MA
- ストップ: はい
- 複雑さ: 基本
- 時間軸: イントラデイ
- 季節性: いいえ
- ニューラルネットワーク: いいえ
- ダイバージェンス: いいえ
- リスクレベル: 中
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 strict current-volume spikes above a rolling volume SMA in price/SMA direction.
/// Fully exits below the volume mean or through actual-fill percent protection.
/// </summary>
public class VolumeSpikeStrategy : Strategy
{
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<int> _volAvgPeriod;
private readonly StrategyParam<decimal> _volumeSpikeMultiplier;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<DataType> _candleType;
private SimpleMovingAverage _volumeAverage;
private Order _pendingOrder;
public int MAPeriod { get => _maPeriod.Value; set => _maPeriod.Value = value; }
public int VolAvgPeriod { get => _volAvgPeriod.Value; set => _volAvgPeriod.Value = value; }
public decimal VolumeSpikeMultiplier { get => _volumeSpikeMultiplier.Value; set => _volumeSpikeMultiplier.Value = value; }
public decimal StopLossPercent { get => _stopLossPercent.Value; set => _stopLossPercent.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public VolumeSpikeStrategy()
{
_maPeriod = Param(nameof(MAPeriod), 20).SetGreaterThanZero()
.SetDisplay("MA Period", "Period for Moving Average calculation", "Indicators")
.SetOptimize(10, 50, 10);
_volAvgPeriod = Param(nameof(VolAvgPeriod), 20).SetGreaterThanZero()
.SetDisplay("Volume Average Period", "Current-inclusive rolling TotalVolume SMA length", "Indicators");
_volumeSpikeMultiplier = Param(nameof(VolumeSpikeMultiplier), 2m).SetGreaterThanZero()
.SetDisplay("Volume Spike Multiplier", "Current volume must strictly exceed volume SMA times multiplier", "Entry")
.SetOptimize(1.5m, 3m, 0.5m);
_stopLossPercent = Param(nameof(StopLossPercent), 2m).SetNotNegative()
.SetDisplay("Stop Loss (%)", "Actual-fill percent stop; zero disables it.", "Protection");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
OrderRegistering += order => _pendingOrder = order;
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType), (Security, DataType.Level1)];
protected override void OnReseted()
{
base.OnReseted();
_volumeAverage = null;
_pendingOrder = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_pendingOrder = null;
StartProtection(new Unit(), new Unit(StopLossPercent, UnitTypes.Percent), useMarketOrders: true, isLocalStop: true);
foreach (var field in new[] { Level1Fields.BestBidPrice, Level1Fields.BestAskPrice })
{
var quotes = new Subscription(DataType.Level1, Security);
quotes.MarketData.BuildField = field;
SubscribeLevel1(quotes).Bind(ObserveProtectionQuote).Start();
}
_volumeAverage = new SimpleMovingAverage { Length = VolAvgPeriod, Name = "Volume average" };
Indicators.Add(_volumeAverage);
var ma = new SimpleMovingAverage { Length = MAPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.BindEx(ma, ProcessCandle, false).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ma);
DrawOwnTrades(area);
}
}
private void ObserveProtectionQuote(Level1ChangeMessage quote)
{
// Native protection runs before this callback, including between finished candles.
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue maValue)
{
if (candle.State != CandleStates.Finished)
return;
// Feed actual volume, not Close or the preceding candle, including during price-SMA warmup.
var volumeValue = _volumeAverage.Process(new DecimalIndicatorValue(_volumeAverage, candle.TotalVolume, candle.OpenTime) { IsFinal = true });
if (!maValue.Indicator.IsFormed || !_volumeAverage.IsFormed || !IsFormedAndOnlineAndAllowTrading())
return;
var average = volumeValue.GetValue<decimal>();
if (_pendingOrder is not null && _pendingOrder.State is not (OrderStates.Done or OrderStates.Failed))
return;
if (Position != 0m && candle.TotalVolume < average)
{
if (Position > 0m) SellMarket(Position);
else BuyMarket(Math.Abs(Position));
}
else if (Position == 0m && average > 0m && candle.TotalVolume > average * VolumeSpikeMultiplier)
{
var mean = maValue.GetValue<decimal>();
if (candle.ClosePrice > mean) BuyMarket(Volume);
else if (candle.ClosePrice < mean) SellMarket(Volume);
}
}
}
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, Math
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes, OrderStates, Level1Fields
from StockSharp.BusinessEntities import Subscription
from StockSharp.Algo.Indicators import SimpleMovingAverage, DecimalIndicatorValue
from StockSharp.Algo.Strategies import Strategy
class volume_spike_strategy(Strategy):
"""
Trades strict volume spikes above a rolling volume SMA in price/SMA direction.
Fully exits below the volume mean or through actual-fill percent protection.
"""
def __init__(self):
super(volume_spike_strategy, self).__init__()
self._ma_period = self.Param("MAPeriod", 20).SetGreaterThanZero().SetDisplay("MA Period", "Period for Moving Average calculation", "Indicators")
self._vol_avg_period = self.Param("VolAvgPeriod", 20).SetGreaterThanZero().SetDisplay("Volume Average Period", "Current-inclusive rolling TotalVolume SMA length", "Indicators")
self._volume_spike_multiplier = self.Param("VolumeSpikeMultiplier", 2.0).SetGreaterThanZero().SetDisplay("Volume Spike Multiplier", "Current volume must strictly exceed volume SMA times multiplier", "Entry")
self._stop_loss_percent = self.Param("StopLossPercent", 2.0).SetNotNegative().SetDisplay("Stop Loss (%)", "Actual-fill percent stop; zero disables it.", "Protection")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._volume_average = None
self._pending_order = None
self.OrderRegistering += self._track_pending
@property
def candle_type(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
return [(self.Security, self.candle_type), (self.Security, DataType.Level1)]
def _track_pending(self, order):
self._pending_order = order
def OnReseted(self):
super(volume_spike_strategy, self).OnReseted()
self._volume_average = None
self._pending_order = None
def OnStarted2(self, time):
super(volume_spike_strategy, self).OnStarted2(time)
self._pending_order = None
self.StartProtection(Unit(), Unit(Decimal(self._stop_loss_percent.Value), UnitTypes.Percent), useMarketOrders=True, isLocalStop=True)
for field in (Level1Fields.BestBidPrice, Level1Fields.BestAskPrice):
quotes = Subscription(DataType.Level1, self.Security)
quotes.MarketData.BuildField = field
self.SubscribeLevel1(quotes).Bind(self._observe_protection_quote).Start()
self._volume_average = SimpleMovingAverage()
self._volume_average.Length = self._vol_avg_period.Value
self._volume_average.Name = "Volume average"
self.Indicators.Add(self._volume_average)
ma = SimpleMovingAverage()
ma.Length = self._ma_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(ma, self._process_candle, False).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ma)
self.DrawOwnTrades(area)
def _observe_protection_quote(self, quote):
# Native protection runs before this callback, including between finished candles.
pass
def _process_candle(self, candle, ma_value):
if candle.State != CandleStates.Finished:
return
# Feed current actual volume, including during price-SMA warmup.
volume_input = DecimalIndicatorValue(self._volume_average, candle.TotalVolume, candle.OpenTime)
volume_input.IsFinal = True
volume_value = self._volume_average.Process(volume_input)
if not ma_value.Indicator.IsFormed or not self._volume_average.IsFormed or not self.IsFormedAndOnlineAndAllowTrading():
return
average = volume_value.GetValue[Decimal](None)
if self._pending_order is not None and self._pending_order.State not in (OrderStates.Done, OrderStates.Failed):
return
if self.Position != 0 and candle.TotalVolume < average:
if self.Position > 0:
self.SellMarket(self.Position)
else:
self.BuyMarket(Math.Abs(self.Position))
elif self.Position == 0 and average > 0 and candle.TotalVolume > average * Decimal(self._volume_spike_multiplier.Value):
mean = ma_value.GetValue[Decimal](None)
if candle.ClosePrice > mean:
self.BuyMarket(self.Volume)
elif candle.ClosePrice < mean:
self.SellMarket(self.Volume)
def CreateClone(self):
return volume_spike_strategy()