Volume Weighted Price Breakout 戦略
この戦略は移動平均と出来高加重移動平均(VWMA)を組み合わせています。価格がVWMAを上回って推移している場合は買い方が優勢であることを示します。価格がVWMAを反対側からクロスした時点でブレイクアウトが発生します。
テストでは年平均リターン約40%を示しています。暗号資産市場での運用に最も適しています。
トレードはVWMAの方向に沿って行われ、単純移動平均を上位トレンドフィルターとして使用します。価格が移動平均に対して反転した時点で決済します。
出来高に裏付けられたブレイクアウトを捉えることが目的です。
詳細
- エントリー条件: 価格がVWMAの上または下にあり、MAの確認がある。
- ロング/ショート: 両方向。
- エグジット条件: 価格が反対方向にMAをクロスまたはストップ。
- ストップ: あり。
- デフォルト値:
MAPeriod= 20VWAPPeriod= 20CandleType= TimeSpan.FromMinutes(5)
- フィルター:
- カテゴリ: ブレイクアウト
- 方向: 両方
- インジケーター: VWMA, 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>
/// Enters on an actual Close/VWMA cross confirmed by the Close/SMA side.
/// Exits the full remaining position on an adverse Close/SMA cross or actual-fill protection.
/// </summary>
public class VolumeWeightedPriceBreakoutStrategy : Strategy
{
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<int> _vwapPeriod;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _stopLossPercent;
private SimpleMovingAverage _ma;
private VolumeWeightedMovingAverage _vwma;
private decimal? _previousClose;
private decimal? _previousMa;
private decimal? _previousVwma;
private Order _pendingOrder;
public int MAPeriod { get => _maPeriod.Value; set => _maPeriod.Value = value; }
// This historical parameter name controls a rolling VWMA, not session VWAP.
public int VWAPPeriod { get => _vwapPeriod.Value; set => _vwapPeriod.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public decimal StopLossPercent { get => _stopLossPercent.Value; set => _stopLossPercent.Value = value; }
public VolumeWeightedPriceBreakoutStrategy()
{
_maPeriod = Param(nameof(MAPeriod), 20).SetGreaterThanZero()
.SetDisplay("MA Period", "Current-inclusive Close SMA length", "Indicators")
.SetOptimize(10, 50, 10);
_vwapPeriod = Param(nameof(VWAPPeriod), 20).SetGreaterThanZero()
.SetDisplay("VWAP Period", "Current-inclusive rolling Close/volume VWMA length", "Indicators")
.SetOptimize(10, 30, 5);
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_stopLossPercent = Param(nameof(StopLossPercent), 2m).SetNotNegative()
.SetDisplay("Stop Loss (%)", "Actual-fill percent stop; zero disables it.", "Protection");
OrderRegistering += order => _pendingOrder = order;
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType), (Security, DataType.Level1)];
protected override void OnReseted()
{
base.OnReseted();
ClearSignalState();
}
private void ClearSignalState()
{
_ma = null;
_vwma = null;
_previousClose = null;
_previousMa = null;
_previousVwma = null;
_pendingOrder = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ClearSignalState();
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();
}
_ma = new SimpleMovingAverage { Length = MAPeriod };
_vwma = new VolumeWeightedMovingAverage { Length = VWAPPeriod };
Indicators.Add(_ma);
Indicators.Add(_vwma);
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _ma);
DrawIndicator(area, _vwma);
DrawOwnTrades(area);
}
}
private void ObserveProtectionQuote(Level1ChangeMessage quote)
{
// Native protection runs before this callback, including between finished candles.
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var maValue = _ma.Process(candle);
var vwmaValue = _vwma.Process(candle);
decimal? ma = !maValue.IsEmpty && _ma.IsFormed ? maValue.GetValue<decimal>() : null;
decimal? vwma = !vwmaValue.IsEmpty && _vwma.IsFormed ? vwmaValue.GetValue<decimal>() : null;
var close = candle.ClosePrice;
var maUp = _previousClose is decimal priorMaUpClose && _previousMa is decimal priorMaUp &&
ma is decimal currentMaUp && priorMaUpClose <= priorMaUp && close > currentMaUp;
var maDown = _previousClose is decimal priorMaDownClose && _previousMa is decimal priorMaDown &&
ma is decimal currentMaDown && priorMaDownClose >= priorMaDown && close < currentMaDown;
var vwmaUp = _previousClose is decimal priorVwmaUpClose && _previousVwma is decimal priorVwmaUp &&
vwma is decimal currentVwmaUp && priorVwmaUpClose <= priorVwmaUp && close > currentVwmaUp;
var vwmaDown = _previousClose is decimal priorVwmaDownClose && _previousVwma is decimal priorVwmaDown &&
vwma is decimal currentVwmaDown && priorVwmaDownClose >= priorVwmaDown && close < currentVwmaDown;
// Raw close and each independently formed mean advance even while trading is disabled
// or an order is pending. Undefined VWMA does not bridge a later cross.
_previousClose = close;
_previousMa = ma;
_previousVwma = vwma;
if (!IsFormedAndOnlineAndAllowTrading() ||
_pendingOrder is not null && _pendingOrder.State is not (OrderStates.Done or OrderStates.Failed))
return;
if (Position > 0m && maDown) SellMarket(Position);
else if (Position < 0m && maUp) BuyMarket(Math.Abs(Position));
else if (Position == 0m && ma is decimal confirmed)
{
if (vwmaUp && close > confirmed) BuyMarket(Volume);
else if (vwmaDown && close < confirmed) 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, VolumeWeightedMovingAverage, CandleIndicatorValue
from StockSharp.Algo.Strategies import Strategy
class volume_weighted_price_breakout_strategy(Strategy):
"""
Enters on an actual Close/VWMA cross confirmed by the Close/SMA side.
Exits the full remaining position on an adverse Close/SMA cross or actual-fill protection.
"""
def __init__(self):
super(volume_weighted_price_breakout_strategy, self).__init__()
self._ma_period = self.Param("MAPeriod", 20).SetGreaterThanZero().SetDisplay("MA Period", "Current-inclusive Close SMA length", "Indicators")
# The legacy name controls rolling VWMA, not session VWAP.
self._vwap_period = self.Param("VWAPPeriod", 20).SetGreaterThanZero().SetDisplay("VWAP Period", "Current-inclusive rolling Close/volume VWMA length", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._stop_loss_percent = self.Param("StopLossPercent", 2.0).SetNotNegative().SetDisplay("Stop Loss (%)", "Actual-fill percent stop; zero disables it.", "Protection")
self._ma = None
self._vwma = None
self._previous_close = None
self._previous_ma = None
self._previous_vwma = 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 _clear_signal_state(self):
self._ma = None
self._vwma = None
self._previous_close = None
self._previous_ma = None
self._previous_vwma = None
self._pending_order = None
def OnReseted(self):
super(volume_weighted_price_breakout_strategy, self).OnReseted()
self._clear_signal_state()
def OnStarted2(self, time):
super(volume_weighted_price_breakout_strategy, self).OnStarted2(time)
self._clear_signal_state()
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._ma = SimpleMovingAverage()
self._ma.Length = self._ma_period.Value
self._vwma = VolumeWeightedMovingAverage()
self._vwma.Length = self._vwap_period.Value
self.Indicators.Add(self._ma)
self.Indicators.Add(self._vwma)
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.DrawIndicator(area, self._ma)
self.DrawIndicator(area, self._vwma)
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):
if candle.State != CandleStates.Finished:
return
ma_value = self._ma.Process(CandleIndicatorValue(self._ma, candle))
vwma_value = self._vwma.Process(CandleIndicatorValue(self._vwma, candle))
ma = ma_value.GetValue[Decimal](None) if not ma_value.IsEmpty and self._ma.IsFormed else None
vwma = vwma_value.GetValue[Decimal](None) if not vwma_value.IsEmpty and self._vwma.IsFormed else None
close = candle.ClosePrice
ma_up = self._previous_close is not None and self._previous_ma is not None and ma is not None and self._previous_close <= self._previous_ma and close > ma
ma_down = self._previous_close is not None and self._previous_ma is not None and ma is not None and self._previous_close >= self._previous_ma and close < ma
vwma_up = self._previous_close is not None and self._previous_vwma is not None and vwma is not None and self._previous_close <= self._previous_vwma and close > vwma
vwma_down = self._previous_close is not None and self._previous_vwma is not None and vwma is not None and self._previous_close >= self._previous_vwma and close < vwma
# Update raw close and independent indicator readiness even while disabled or pending.
# An undefined VWMA window cannot bridge a later crossing.
self._previous_close = close
self._previous_ma = ma
self._previous_vwma = vwma
if not self.IsFormedAndOnlineAndAllowTrading():
return
if self._pending_order is not None and self._pending_order.State not in (OrderStates.Done, OrderStates.Failed):
return
if self.Position > 0 and ma_down:
self.SellMarket(self.Position)
elif self.Position < 0 and ma_up:
self.BuyMarket(Math.Abs(self.Position))
elif self.Position == 0 and ma is not None:
if vwma_up and close > ma:
self.BuyMarket(self.Volume)
elif vwma_down and close < ma:
self.SellMarket(self.Volume)
def CreateClone(self):
return volume_weighted_price_breakout_strategy()