Strategie Volatility Contraction Pattern
Die VCP-Strategie sucht nach einer Abfolge sich verengender Preisspannen. Mit jeder Kontraktion baut sich Energie für einen Ausbruch auf. Das System misst die Spannenbreite und wartet auf einen Ausbruch über das höchste Hoch oder unter das niedrigste Tief.
Tests zeigen eine durchschnittliche jährliche Rendite von etwa 166%. Am besten funktioniert es im Aktienmarkt.
Sobald eine Kontraktion beobachtet wird, löst ein Ausbruch über die jüngsten Extrempunkte hinaus einen Trade in dieser Richtung aus. Das Kreuzen des Preises mit dem gleitenden Durchschnitt wird zur Steuerung von Ausstiegen verwendet.
Dieser Ansatz zielt darauf ab, explosive Bewegungen nach einem Volatilitätssqueeze zu erfassen.
Details
- Einstiegskriterien: Kontraktion der Spanne und anschließender Ausbruch über das jüngste Hoch/Tief.
- Long/Short: Beide Richtungen.
- Ausstiegskriterien: Preis kreuzt MA oder Stop.
- Stops: Ja.
- Standardwerte:
MAPeriod= 20LookbackPeriod= 20CandleType= TimeSpan.FromMinutes(5)
- Filter:
- Kategorie: Ausbruch
- Richtung: Beide
- Indikatoren: Range, MA
- Stops: Ja
- Komplexität: Mittel
- Zeitrahmen: Intraday
- Saisonalität: Nein
- Neuronale Netze: Nein
- Divergenz: Nein
- Risikolevel: Mittel
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>
/// Volume Contraction Pattern (VCP) strategy.
/// Looks for narrowing volatility (ATR declining) and breakouts above/below MA bands.
/// </summary>
public class VcpStrategy : Strategy
{
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<decimal> _atrMultiplier;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _cooldownBars;
private decimal _prevAtr;
private int _contractionCount;
private int _cooldown;
/// <summary>
/// MA Period.
/// </summary>
public int MAPeriod
{
get => _maPeriod.Value;
set => _maPeriod.Value = value;
}
/// <summary>
/// ATR Period.
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// ATR multiplier for breakout band.
/// </summary>
public decimal AtrMultiplier
{
get => _atrMultiplier.Value;
set => _atrMultiplier.Value = value;
}
/// <summary>
/// Candle type for strategy calculation.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Cooldown bars between trades.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Initialize the VCP strategy.
/// </summary>
public VcpStrategy()
{
_maPeriod = Param(nameof(MAPeriod), 20)
.SetDisplay("MA Period", "Period for Moving Average calculation", "Indicators")
.SetOptimize(10, 50, 10);
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetDisplay("ATR Period", "Period for ATR calculation", "Indicators")
.SetOptimize(7, 21, 7);
_atrMultiplier = Param(nameof(AtrMultiplier), 2.0m)
.SetDisplay("ATR Multiplier", "ATR multiplier for breakout band", "Entry")
.SetOptimize(1.0m, 3.0m, 0.5m);
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_cooldownBars = Param(nameof(CooldownBars), 500)
.SetRange(1, 1000)
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevAtr = default;
_contractionCount = default;
_cooldown = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevAtr = 0;
_contractionCount = 0;
_cooldown = 0;
var ma = new SimpleMovingAverage { Length = MAPeriod };
var atr = new AverageTrueRange { Length = AtrPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ma, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal maValue, decimal atrValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (_prevAtr == 0)
{
_prevAtr = atrValue;
return;
}
// Track contraction
if (atrValue < _prevAtr)
_contractionCount++;
else
_contractionCount = 0;
if (_cooldown > 0)
{
_cooldown--;
_prevAtr = atrValue;
return;
}
// After 3+ contracting bars, look for breakout
var isContracted = _contractionCount >= 3;
var upperBand = maValue + atrValue * AtrMultiplier;
var lowerBand = maValue - atrValue * AtrMultiplier;
if (Position == 0 && isContracted)
{
if (candle.ClosePrice > upperBand)
{
BuyMarket();
_cooldown = CooldownBars;
_contractionCount = 0;
}
else if (candle.ClosePrice < lowerBand)
{
SellMarket();
_cooldown = CooldownBars;
_contractionCount = 0;
}
}
else if (Position > 0 && candle.ClosePrice < maValue)
{
SellMarket();
_cooldown = CooldownBars;
}
else if (Position < 0 && candle.ClosePrice > maValue)
{
BuyMarket();
_cooldown = CooldownBars;
}
_prevAtr = atrValue;
}
}
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 SimpleMovingAverage, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class vcp_strategy(Strategy):
"""
Volume Contraction Pattern (VCP) strategy.
Looks for narrowing volatility (ATR declining) and breakouts above/below MA bands.
"""
def __init__(self):
super(vcp_strategy, self).__init__()
self._ma_period = self.Param("MAPeriod", 20).SetDisplay("MA Period", "Period for Moving Average calculation", "Indicators")
self._atr_period = self.Param("AtrPeriod", 14).SetDisplay("ATR Period", "Period for ATR calculation", "Indicators")
self._atr_multiplier = self.Param("AtrMultiplier", 2.0).SetDisplay("ATR Multiplier", "ATR multiplier for breakout band", "Entry")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._cooldown_bars = self.Param("CooldownBars", 500).SetDisplay("Cooldown Bars", "Bars to wait between trades", "General")
self._prev_atr = 0.0
self._contraction_count = 0
self._cooldown = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(vcp_strategy, self).OnReseted()
self._prev_atr = 0.0
self._contraction_count = 0
self._cooldown = 0
def OnStarted2(self, time):
super(vcp_strategy, self).OnStarted2(time)
self._prev_atr = 0.0
self._contraction_count = 0
self._cooldown = 0
ma = SimpleMovingAverage()
ma.Length = self._ma_period.Value
atr = AverageTrueRange()
atr.Length = self._atr_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(ma, atr, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ma)
self.DrawOwnTrades(area)
def _process_candle(self, candle, ma_val, atr_val):
if candle.State != CandleStates.Finished:
return
av = float(atr_val)
if self._prev_atr == 0:
self._prev_atr = av
return
# Track contraction
if av < self._prev_atr:
self._contraction_count += 1
else:
self._contraction_count = 0
if self._cooldown > 0:
self._cooldown -= 1
self._prev_atr = av
return
mv = float(ma_val)
mult = float(self._atr_multiplier.Value)
close = float(candle.ClosePrice)
cd = self._cooldown_bars.Value
is_contracted = self._contraction_count >= 3
upper_band = mv + av * mult
lower_band = mv - av * mult
if self.Position == 0 and is_contracted:
if close > upper_band:
self.BuyMarket()
self._cooldown = cd
self._contraction_count = 0
elif close < lower_band:
self.SellMarket()
self._cooldown = cd
self._contraction_count = 0
elif self.Position > 0 and close < mv:
self.SellMarket()
self._cooldown = cd
elif self.Position < 0 and close > mv:
self.BuyMarket()
self._cooldown = cd
self._prev_atr = av
def CreateClone(self):
return vcp_strategy()