Ausbruchskauf-Strategie für Preis und Volumen
Die Strategie steigt ein, wenn Preis und Volumen gleichzeitig über ihre jeweiligen Lookback-Hochs ausbrechen, während der Preis über dem Trend-SMA bleibt. Short-Trades werden ausgelöst, wenn der Preis unter das Lookback-Tief fällt, unter der gleichen Volumenbedingung und dem SMA-Filter. Positionen werden nach fünf aufeinanderfolgenden Schlusskursen auf der entgegengesetzten Seite des SMA geschlossen.
Details
- Einstiegskriterien:
- Long: Close > vorheriges höchstes Hoch && Volume > vorheriges höchstes Volumen && Close > SMA
- Short: Close < vorheriges niedrigstes Tief && Volume > vorheriges höchstes Volumen && Close < SMA
- Long/Short: Konfigurierbar
- Ausstiegskriterien:
- Trend: Fünf Schlusskurse jenseits des SMA
- Stops: Nein
- Standardwerte:
PriceBreakoutPeriod= 60VolumeBreakoutPeriod= 60TrendlineLength= 200OrderDirection= "Long"CandleType= TimeSpan.FromDays(1)
- Filter:
- Kategorie: Ausbruch
- Richtung: Konfigurierbar
- Indikatoren: Highest, SMA, Volume
- Stops: Nein
- Komplexität: Grundlegend
- Zeitrahmen: Beliebig
- Saisonalität: Nein
- Neuronale Netze: Nein
- Divergenz: Nein
- Risikolevel: Mittel
Preis- und Volumenextrema werden nur aus vorherigen abgeschlossenen Kerzen berechnet (ohne die Signalkerze). OrderDirection akzeptiert Long, Short oder Both, standardmäßig Long. Ein Schlusskurs auf dem SMA oder zurück auf die Positionsseite setzt den Zähler für fünf aufeinanderfolgende Schlusskurse zurück.
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>
/// Trades breakouts of the previous price and volume ranges in the direction of the trend SMA.
/// </summary>
public class PriceAndVolumeBreakoutBuyStrategy : Strategy
{
private readonly StrategyParam<int> _priceBreakoutPeriod;
private readonly StrategyParam<int> _volumeBreakoutPeriod;
private readonly StrategyParam<int> _trendlineLength;
private readonly StrategyParam<string> _orderDirection;
private readonly StrategyParam<DataType> _candleType;
private readonly Queue<(decimal high, decimal low, decimal volume)> _previousBars = new();
private int _oppositeCloses;
private bool _entryRequested;
private bool _exitRequested;
public int PriceBreakoutPeriod { get => _priceBreakoutPeriod.Value; set => _priceBreakoutPeriod.Value = value; }
public int VolumeBreakoutPeriod { get => _volumeBreakoutPeriod.Value; set => _volumeBreakoutPeriod.Value = value; }
public int TrendlineLength { get => _trendlineLength.Value; set => _trendlineLength.Value = value; }
public string OrderDirection { get => _orderDirection.Value; set => _orderDirection.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public PriceAndVolumeBreakoutBuyStrategy()
{
_priceBreakoutPeriod = Param(nameof(PriceBreakoutPeriod), 60)
.SetGreaterThanZero()
.SetDisplay("Price Breakout Period", "Number of preceding candles used for the price extreme.", "Breakout");
_volumeBreakoutPeriod = Param(nameof(VolumeBreakoutPeriod), 60)
.SetGreaterThanZero()
.SetDisplay("Volume Breakout Period", "Number of preceding candles used for maximum volume.", "Breakout");
_trendlineLength = Param(nameof(TrendlineLength), 200)
.SetGreaterThanZero()
.SetDisplay("Trendline Length", "Trend SMA period.", "Trend");
_orderDirection = Param(nameof(OrderDirection), "Long")
.SetDisplay("Order Direction", "Long, Short, or Both.", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromDays(1).TimeFrame())
.SetDisplay("Candle Type", "Candle type.", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_previousBars.Clear();
_oppositeCloses = 0;
_entryRequested = false;
_exitRequested = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
if (OrderDirection is not ("Long" or "Short" or "Both"))
throw new ArgumentOutOfRangeException(nameof(OrderDirection), OrderDirection, "Supported values are Long, Short and Both.");
var trend = new SimpleMovingAverage { Length = TrendlineLength };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(trend, (candle, average) => ProcessCandle(candle, average, trend)).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, trend);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal average, SimpleMovingAverage trend)
{
if (candle.State != CandleStates.Finished)
return;
// The current bar must never contribute to its own lookback extrema.
if (trend.IsFormed && _previousBars.Count >= Math.Max(PriceBreakoutPeriod, VolumeBreakoutPeriod))
{
if (Position != 0m)
{
_entryRequested = false;
if (!_exitRequested)
{
var opposite = Position > 0m ? candle.ClosePrice < average : candle.ClosePrice > average;
_oppositeCloses = opposite ? _oppositeCloses + 1 : 0;
if (_oppositeCloses == 5)
{
_exitRequested = true;
_oppositeCloses = 0;
if (Position > 0m)
SellMarket(Position);
else
BuyMarket(Math.Abs(Position));
}
}
}
else
{
_exitRequested = false;
_oppositeCloses = 0;
if (!_entryRequested)
{
var priceBars = _previousBars.TakeLast(PriceBreakoutPeriod).ToArray();
var maximumVolume = _previousBars.TakeLast(VolumeBreakoutPeriod).Max(x => x.volume);
var longSignal = candle.ClosePrice > priceBars.Max(x => x.high)
&& candle.TotalVolume > maximumVolume && candle.ClosePrice > average;
var shortSignal = candle.ClosePrice < priceBars.Min(x => x.low)
&& candle.TotalVolume > maximumVolume && candle.ClosePrice < average;
if (longSignal && (OrderDirection is "Long" or "Both"))
{
_entryRequested = true;
BuyMarket(Volume);
}
else if (shortSignal && (OrderDirection is "Short" or "Both"))
{
_entryRequested = true;
SellMarket(Volume);
}
}
}
}
_previousBars.Enqueue((candle.HighPrice, candle.LowPrice, candle.TotalVolume));
while (_previousBars.Count > Math.Max(PriceBreakoutPeriod, VolumeBreakoutPeriod))
_previousBars.Dequeue();
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import Math, TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class price_and_volume_breakout_buy_strategy(Strategy):
def __init__(self):
super(price_and_volume_breakout_buy_strategy, self).__init__()
self._price_breakout_period = self.Param("PriceBreakoutPeriod", 60) \
.SetGreaterThanZero() \
.SetDisplay("Price Breakout Period", "Number of preceding candles used for the price extreme.", "Breakout")
self._volume_breakout_period = self.Param("VolumeBreakoutPeriod", 60) \
.SetGreaterThanZero() \
.SetDisplay("Volume Breakout Period", "Number of preceding candles used for maximum volume.", "Breakout")
self._trendline_length = self.Param("TrendlineLength", 200) \
.SetGreaterThanZero() \
.SetDisplay("Trendline Length", "Trend SMA period.", "Trend")
self._order_direction = self.Param("OrderDirection", "Long") \
.SetDisplay("Order Direction", "Long, Short, or Both.", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromDays(1))) \
.SetDisplay("Candle Type", "Candle type.", "General")
self._previous_bars = []
self._opposite_closes = 0
self._entry_requested = False
self._exit_requested = False
def GetWorkingSecurities(self):
return [(self.Security, self._candle_type.Value)]
def OnReseted(self):
super(price_and_volume_breakout_buy_strategy, self).OnReseted()
self._previous_bars = []
self._opposite_closes = 0
self._entry_requested = False
self._exit_requested = False
def OnStarted2(self, time):
super(price_and_volume_breakout_buy_strategy, self).OnStarted2(time)
if self._order_direction.Value not in ("Long", "Short", "Both"):
raise ValueError("OrderDirection must be Long, Short, or Both")
self._trend = SimpleMovingAverage()
self._trend.Length = int(self._trendline_length.Value)
subscription = self.SubscribeCandles(self._candle_type.Value)
subscription.Bind(self._trend, self.on_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._trend)
self.DrawOwnTrades(area)
def on_candle(self, candle, average):
if candle.State != CandleStates.Finished:
return
price_period = int(self._price_breakout_period.Value)
volume_period = int(self._volume_breakout_period.Value)
window = max(price_period, volume_period)
# Evaluate using previous bricks/bars only, before adding the current one.
if self._trend.IsFormed and len(self._previous_bars) >= window:
close = float(candle.ClosePrice)
sma = float(average)
position = self.Position
if position != 0:
self._entry_requested = False
if not self._exit_requested:
opposite = close < sma if position > 0 else close > sma
self._opposite_closes = self._opposite_closes + 1 if opposite else 0
if self._opposite_closes == 5:
self._exit_requested = True
self._opposite_closes = 0
if position > 0:
self.SellMarket(position)
else:
self.BuyMarket(Math.Abs(position))
else:
self._exit_requested = False
self._opposite_closes = 0
if not self._entry_requested:
previous_price_bars = self._previous_bars[-price_period:]
previous_volume_bars = self._previous_bars[-volume_period:]
max_volume = max(bar[2] for bar in previous_volume_bars)
volume = float(candle.TotalVolume)
long_signal = (close > max(bar[0] for bar in previous_price_bars)
and volume > max_volume and close > sma)
short_signal = (close < min(bar[1] for bar in previous_price_bars)
and volume > max_volume and close < sma)
direction = self._order_direction.Value
if long_signal and direction in ("Long", "Both"):
self._entry_requested = True
self.BuyMarket(self.Volume)
elif short_signal and direction in ("Short", "Both"):
self._entry_requested = True
self.SellMarket(self.Volume)
self._previous_bars.append((float(candle.HighPrice),
float(candle.LowPrice),
float(candle.TotalVolume)))
if len(self._previous_bars) > window:
self._previous_bars.pop(0)
def CreateClone(self):
return price_and_volume_breakout_buy_strategy()