Big Dog Range-Ausbruch-Strategie
Die Big Dog-Strategie sucht in der Londoner Morgensitzung nach einem engen Konsolidierungsfenster und handelt Ausbrüche aus dieser Box. Der ursprüngliche MQL Expert Advisor platzierte Stop-Orders, sobald der Preisbereich zwischen den angegebenen StartHour und StopHour innerhalb einer konfigurierbaren Punktanzahl blieb. Der StockSharp-Port behält die gleiche Idee bei und verwendet Market Orders beim Ausbruch, begleitet von dynamischen Stop-Loss- und Take-Profit-Levels, die von den Extremen der Konsolidierung abgeleitet werden.
Handelslogik
- Abgeschlossene Kerzen zwischen
StartHour (einschließlich) und StopHour (standardmäßig ausschließlich) sammeln, um den täglichen Bereich zu erstellen.
- Die Sitzung ignorieren, wenn die Differenz zwischen Sitzungshoch und -tief
MaxRangePoints überschreitet (konvertiert in Preiseinheiten mit der angepassten Punktgröße).
- Nach dem Schließen der Sitzung den Abstand zwischen dem aktuellen besten Ask/Bid und den Ausbruchslevels prüfen. Ein Setup wird nur aktiviert, wenn der Markt mindestens
DistancePoints vom Hoch (für Long-Einstiege) oder Tief (für Short-Einstiege) entfernt ist.
- Wenn der Preis auf einer nachfolgenden Kerze durch das vorbereitete Hoch oder Tief bricht, mit einer Market Order in Größe von
OrderVolume einsteigen (entgegengesetzte Positionen werden automatisch ausgeglichen).
- Sofort Ausstiege zuweisen:
- Long-Trades verwenden einen Stop-Loss beim aufgezeichneten Sitzungstief und einen Take-Profit
TakeProfitPoints über dem Einstiegslevel.
- Short-Trades verwenden einen Stop-Loss beim aufgezeichneten Sitzungshoch und einen Take-Profit
TakeProfitPoints unter dem Einstiegslevel.
- Bei jeder abgeschlossenen Kerze überwacht die Strategie das Hoch/Tief, um zu entscheiden, ob der Stop-Loss oder Take-Profit erreicht wurde, und schließt die Position entsprechend.
- Am Beginn eines neuen Handelstages werden alle gecachten Levels zurückgesetzt, um Restorders aus der vorherigen Sitzung zu verhindern.
Angepasste Punkte. Die Strategie konvertiert punktbasierte Eingaben in tatsächliche Preisabstände, indem sie diese mit dem Instrument-PriceStep multipliziert. Wenn das Wertpapier 3 oder 5 Dezimalstellen hat, wird der Wert zusätzlich um 10 skaliert, um die im ursprünglichen EA verwendete Pip-Logik nachzuahmen.
Parameter
| Parameter |
Beschreibung |
Standard |
StartHour |
Tagesstunde (0-23), zu der das Konsolidierungsfenster beginnt. |
14 |
StopHour |
Tagesstunde (0-23), zu der das Konsolidierungsfenster endet. |
16 |
MaxRangePoints |
Maximale Höhe der Sitzungsbox in angepassten Punkten. |
50 |
TakeProfitPoints |
Take-Profit-Abstand in angepassten Punkten vom Ausbruchspreis. |
50 |
DistancePoints |
Mindestabstand zwischen aktuellem Preis und Ausbruchslevel vor der Aktivierung von Orders. |
20 |
OrderVolume |
Volumen jedes Ausbruchsgeschäfts (auch auf Strategie-Volume angewendet). |
1 |
CandleType |
Kerzentyp zum Erstellen der Sitzungsbox. Einstündiger Zeitrahmen standardmäßig. |
1h |
Implementierungshinweise
- Die Strategie abonniert sowohl Kerzen als auch das Orderbuch. Die besten Bid/Ask-Werte werden zur Auswertung der Distanzfilter verwendet, mit Fallback auf den letzten Kerzenschluss, wenn keine Tiefe verfügbar ist.
- Einstiege werden mit Market Orders ausgeführt. Dies spiegelt das Verhalten der ursprünglichen ausstehenden Stop-Orders wider, während es innerhalb der High-Level-API bleibt.
- Stop-Loss- und Take-Profit-Entscheidungen werden bei Kerzenschlüssen basierend auf Intrabar-Hochs und -Tiefs getroffen, was die Schutzlevels der MQL-Version emuliert, ohne zusätzliche Child-Orders zu registrieren.
- Das tägliche Zustandsmanagement storniert aktive Orders und setzt gecachte Hochs/Tiefs zurück, wenn sich das Kalenderdatum ändert.
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Big Dog range breakout strategy that trades breakouts from a tight range built between configurable hours.
/// </summary>
public class BigDogStrategy : Strategy
{
private readonly StrategyParam<int> _startHour;
private readonly StrategyParam<int> _stopHour;
private readonly StrategyParam<decimal> _maxRangePoints;
private readonly StrategyParam<decimal> _takeProfitPoints;
private readonly StrategyParam<decimal> _distancePoints;
private readonly StrategyParam<decimal> _orderVolume;
private readonly StrategyParam<DataType> _candleType;
private decimal? _rangeHigh;
private decimal? _rangeLow;
private DateTime? _rangeDate;
private bool _longReady;
private bool _shortReady;
private decimal? _longStopPrice;
private decimal? _longTakeProfitPrice;
private decimal? _shortStopPrice;
private decimal? _shortTakeProfitPrice;
private decimal _longEntryPrice;
private decimal _shortEntryPrice;
private decimal? _bestBid;
private decimal? _bestAsk;
private decimal _adjustedPointSize;
/// <summary>
/// Hour (0-23) when the consolidation range calculation starts.
/// </summary>
public int StartHour
{
get => _startHour.Value;
set => _startHour.Value = value;
}
/// <summary>
/// Hour (0-23) when the consolidation range calculation stops.
/// </summary>
public int StopHour
{
get => _stopHour.Value;
set => _stopHour.Value = value;
}
/// <summary>
/// Maximum acceptable range height measured in adjusted points.
/// </summary>
public decimal MaxRangePoints
{
get => _maxRangePoints.Value;
set => _maxRangePoints.Value = value;
}
/// <summary>
/// Take-profit distance measured in adjusted points.
/// </summary>
public decimal TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Minimum distance required between the current price and breakout level, measured in adjusted points.
/// </summary>
public decimal DistancePoints
{
get => _distancePoints.Value;
set => _distancePoints.Value = value;
}
/// <summary>
/// Order volume that will be used for entries.
/// </summary>
public decimal OrderVolume
{
get => _orderVolume.Value;
set => _orderVolume.Value = value;
}
/// <summary>
/// Candle type used for range calculation and breakout detection.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="BigDogStrategy"/> class.
/// </summary>
public BigDogStrategy()
{
_startHour = Param(nameof(StartHour), 2)
.SetRange(0, 23)
.SetDisplay("Start Hour", "Hour to begin measuring the range", "Session");
_stopHour = Param(nameof(StopHour), 8)
.SetRange(0, 23)
.SetDisplay("Stop Hour", "Hour to stop measuring the range", "Session");
_maxRangePoints = Param(nameof(MaxRangePoints), 50000m)
.SetGreaterThanZero()
.SetDisplay("Max Range", "Maximum allowed height of the consolidation range (points)", "Trading");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 50m)
.SetGreaterThanZero()
.SetDisplay("Take Profit", "Take-profit distance in adjusted points", "Trading");
_distancePoints = Param(nameof(DistancePoints), 1m)
.SetGreaterThanZero()
.SetDisplay("Min Distance", "Minimum distance from current price to breakout level (points)", "Trading");
_orderVolume = Param(nameof(OrderVolume), 1m)
.SetGreaterThanZero()
.SetDisplay("Order Volume", "Volume used for each breakout order", "Trading");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Candles timeframe used for range detection", "Data");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_rangeHigh = null;
_rangeLow = null;
_rangeDate = null;
_longReady = false;
_shortReady = false;
_longStopPrice = null;
_longTakeProfitPrice = null;
_shortStopPrice = null;
_shortTakeProfitPrice = null;
_longEntryPrice = 0m;
_shortEntryPrice = 0m;
_bestBid = null;
_bestAsk = null;
_adjustedPointSize = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
Volume = OrderVolume;
_adjustedPointSize = CalculateAdjustedPointSize();
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 currentDate = candle.OpenTime.Date;
if (_rangeDate != currentDate)
{
ResetDailyState(currentDate);
}
UpdateRange(candle);
if (candle.OpenTime.Hour >= StopHour)
{
PrepareBreakoutLevels(candle);
}
ProcessEntries(candle);
ProcessRiskManagement(candle);
}
private void ResetDailyState(DateTime date)
{
_rangeDate = date;
_rangeHigh = null;
_rangeLow = null;
_longReady = false;
_shortReady = false;
_longStopPrice = null;
_longTakeProfitPrice = null;
_shortStopPrice = null;
_shortTakeProfitPrice = null;
}
private void UpdateRange(ICandleMessage candle)
{
var hour = candle.OpenTime.Hour;
if (hour < StartHour || hour >= StopHour)
return;
_rangeHigh = _rangeHigh.HasValue
? Math.Max(_rangeHigh.Value, candle.HighPrice)
: candle.HighPrice;
_rangeLow = _rangeLow.HasValue
? Math.Min(_rangeLow.Value, candle.LowPrice)
: candle.LowPrice;
}
private void PrepareBreakoutLevels(ICandleMessage candle)
{
if (!_rangeHigh.HasValue || !_rangeLow.HasValue)
return;
var rangeHeight = _rangeHigh.Value - _rangeLow.Value;
var maxRange = ConvertToPrice(MaxRangePoints);
if (rangeHeight >= maxRange)
{
// Reset pending plans when the range becomes too wide.
_longReady = false;
_shortReady = false;
return;
}
var minDistance = ConvertToPrice(DistancePoints);
var ask = _bestAsk ?? candle.ClosePrice;
var bid = _bestBid ?? candle.ClosePrice;
if (!_longReady && Position >= 0 && (_rangeHigh.Value - ask) > minDistance)
{
_longReady = true;
_longEntryPrice = _rangeHigh.Value;
_longStopPrice = _rangeLow.Value;
_longTakeProfitPrice = _rangeHigh.Value + ConvertToPrice(TakeProfitPoints);
}
if (!_shortReady && Position <= 0 && (bid - _rangeLow.Value) > minDistance)
{
_shortReady = true;
_shortEntryPrice = _rangeLow.Value;
_shortStopPrice = _rangeHigh.Value;
_shortTakeProfitPrice = _rangeLow.Value - ConvertToPrice(TakeProfitPoints);
}
}
private void ProcessEntries(ICandleMessage candle)
{
var volume = OrderVolume;
if (_longReady && candle.HighPrice >= _longEntryPrice && Position <= 0)
{
// Enter long on breakout of the session high.
BuyMarket();
_longReady = false;
_shortReady = false;
}
if (_shortReady && candle.LowPrice <= _shortEntryPrice && Position >= 0)
{
// Enter short on breakout of the session low.
SellMarket();
_shortReady = false;
_longReady = false;
}
}
private void ProcessRiskManagement(ICandleMessage candle)
{
if (Position > 0 && _longStopPrice.HasValue && _longTakeProfitPrice.HasValue)
{
// Close the long position if stop-loss or take-profit levels are touched.
if (candle.LowPrice <= _longStopPrice.Value)
{
SellMarket();
_longStopPrice = null;
_longTakeProfitPrice = null;
}
else if (candle.HighPrice >= _longTakeProfitPrice.Value)
{
SellMarket();
_longStopPrice = null;
_longTakeProfitPrice = null;
}
}
else if (Position < 0 && _shortStopPrice.HasValue && _shortTakeProfitPrice.HasValue)
{
// Close the short position if stop-loss or take-profit levels are touched.
if (candle.HighPrice >= _shortStopPrice.Value)
{
BuyMarket();
_shortStopPrice = null;
_shortTakeProfitPrice = null;
}
else if (candle.LowPrice <= _shortTakeProfitPrice.Value)
{
BuyMarket();
_shortStopPrice = null;
_shortTakeProfitPrice = null;
}
}
else
{
_longStopPrice = null;
_longTakeProfitPrice = null;
_shortStopPrice = null;
_shortTakeProfitPrice = null;
}
}
private decimal ConvertToPrice(decimal points)
{
return points * _adjustedPointSize;
}
private decimal CalculateAdjustedPointSize()
{
var step = Security?.PriceStep ?? 0m;
if (step <= 0m)
step = 1m;
var decimals = Security?.Decimals ?? 0;
var multiplier = decimals == 3 || decimals == 5 ? 10m : 1m;
return step * multiplier;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class big_dog_strategy(Strategy):
def __init__(self):
super(big_dog_strategy, self).__init__()
self._start_hour = self.Param("StartHour", 2) \
.SetDisplay("Start Hour", "Hour to begin measuring the range", "Session")
self._stop_hour = self.Param("StopHour", 8) \
.SetDisplay("Stop Hour", "Hour to stop measuring the range", "Session")
self._max_range_points = self.Param("MaxRangePoints", 50000.0) \
.SetDisplay("Max Range", "Maximum allowed height of consolidation range", "Trading")
self._take_profit_points = self.Param("TakeProfitPoints", 50.0) \
.SetDisplay("Take Profit", "Take-profit distance in adjusted points", "Trading")
self._distance_points = self.Param("DistancePoints", 1.0) \
.SetDisplay("Min Distance", "Minimum distance from price to breakout level", "Trading")
self._order_volume = self.Param("OrderVolume", 1.0) \
.SetDisplay("Order Volume", "Volume used for each breakout order", "Trading")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Candles timeframe used for range detection", "Data")
self._range_high = None
self._range_low = None
self._range_date = None
self._long_ready = False
self._short_ready = False
self._long_stop_price = None
self._long_tp_price = None
self._short_stop_price = None
self._short_tp_price = None
self._long_entry_price = 0.0
self._short_entry_price = 0.0
self._adjusted_point_size = 0.0
@property
def start_hour(self):
return self._start_hour.Value
@property
def stop_hour(self):
return self._stop_hour.Value
@property
def max_range_points(self):
return self._max_range_points.Value
@property
def take_profit_points(self):
return self._take_profit_points.Value
@property
def distance_points(self):
return self._distance_points.Value
@property
def order_volume(self):
return self._order_volume.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(big_dog_strategy, self).OnReseted()
self._range_high = None
self._range_low = None
self._range_date = None
self._long_ready = False
self._short_ready = False
self._long_stop_price = None
self._long_tp_price = None
self._short_stop_price = None
self._short_tp_price = None
self._long_entry_price = 0.0
self._short_entry_price = 0.0
self._adjusted_point_size = 0.0
def _calc_adjusted_point_size(self):
sec = self.Security
step = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None and sec.PriceStep > 0 else 1.0
decimals = sec.Decimals if sec is not None and sec.Decimals is not None else 0
multiplier = 10.0 if decimals == 3 or decimals == 5 else 1.0
return step * multiplier
def _convert_to_price(self, points):
return points * self._adjusted_point_size
def OnStarted2(self, time):
super(big_dog_strategy, self).OnStarted2(time)
self.Volume = self._order_volume.Value
self._adjusted_point_size = self._calc_adjusted_point_size()
subscription = self.SubscribeCandles(self._candle_type.Value)
subscription.Bind(self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _reset_daily(self, date):
self._range_date = date
self._range_high = None
self._range_low = None
self._long_ready = False
self._short_ready = False
self._long_stop_price = None
self._long_tp_price = None
self._short_stop_price = None
self._short_tp_price = None
def _update_range(self, candle):
hour = candle.OpenTime.Hour
if hour < self.start_hour or hour >= self.stop_hour:
return
h = float(candle.HighPrice)
l = float(candle.LowPrice)
self._range_high = max(self._range_high, h) if self._range_high is not None else h
self._range_low = min(self._range_low, l) if self._range_low is not None else l
def _prepare_breakout(self, candle):
if self._range_high is None or self._range_low is None:
return
range_height = self._range_high - self._range_low
max_range = self._convert_to_price(self.max_range_points)
if range_height >= max_range:
self._long_ready = False
self._short_ready = False
return
min_dist = self._convert_to_price(self.distance_points)
ask = float(candle.ClosePrice)
bid = float(candle.ClosePrice)
if not self._long_ready and self.Position >= 0 and (self._range_high - ask) > min_dist:
self._long_ready = True
self._long_entry_price = self._range_high
self._long_stop_price = self._range_low
self._long_tp_price = self._range_high + self._convert_to_price(self.take_profit_points)
if not self._short_ready and self.Position <= 0 and (bid - self._range_low) > min_dist:
self._short_ready = True
self._short_entry_price = self._range_low
self._short_stop_price = self._range_high
self._short_tp_price = self._range_low - self._convert_to_price(self.take_profit_points)
def _process_entries(self, candle):
if self._long_ready and float(candle.HighPrice) >= self._long_entry_price and self.Position <= 0:
self.BuyMarket()
self._long_ready = False
self._short_ready = False
if self._short_ready and float(candle.LowPrice) <= self._short_entry_price and self.Position >= 0:
self.SellMarket()
self._short_ready = False
self._long_ready = False
def _process_risk(self, candle):
if self.Position > 0 and self._long_stop_price is not None and self._long_tp_price is not None:
if float(candle.LowPrice) <= self._long_stop_price:
self.SellMarket()
self._long_stop_price = None
self._long_tp_price = None
elif float(candle.HighPrice) >= self._long_tp_price:
self.SellMarket()
self._long_stop_price = None
self._long_tp_price = None
elif self.Position < 0 and self._short_stop_price is not None and self._short_tp_price is not None:
if float(candle.HighPrice) >= self._short_stop_price:
self.BuyMarket()
self._short_stop_price = None
self._short_tp_price = None
elif float(candle.LowPrice) <= self._short_tp_price:
self.BuyMarket()
self._short_stop_price = None
self._short_tp_price = None
else:
self._long_stop_price = None
self._long_tp_price = None
self._short_stop_price = None
self._short_tp_price = None
def OnProcess(self, candle):
if candle.State != CandleStates.Finished:
return
current_date = candle.OpenTime.Date
if self._range_date != current_date:
self._reset_daily(current_date)
self._update_range(candle)
if candle.OpenTime.Hour >= self.stop_hour:
self._prepare_breakout(candle)
self._process_entries(candle)
self._process_risk(candle)
def CreateClone(self):
return big_dog_strategy()