Die Hinterhalt-Strategie umgibt den Markt kontinuierlich mit einem Paar Buy-Stop- und Sell-Stop-Aufträgen. Die ausstehenden Aufträge werden
in einem konfigurierbaren Abstand über dem besten Ask und unter dem besten Bid platziert, mit einer dynamischen Überschreibung, die eine
Mindestdistanz basierend auf dem aktuellen Spread erzwingt. Sobald eine Seite ausgelöst wird, baut die Strategie sofort beide Aufträge
neu auf, sodass der Markt von beiden Richtungen "im Hinterhalt" bleibt. Ein einfacher eigenkapitalbasierter Schutzschalter kann
Positionen abflachen, sobald ein tägliches Gewinnziel oder Verlustziel erreicht ist.
Diese C#-Implementierung repliziert das Verhalten des ursprünglichen MetaTrader 5-Experten von Zuzabush. Sie arbeitet ausschließlich mit
Level-1-Notierungen und benötigt keine Kerzen oder Indikatoren. Jede Entscheidung wird durch Echtzeit-Bid/Ask-Änderungen getrieben,
daher ist die Strategie am besten für liquide Instrumente mit engen Spreads geeignet.
Handelslogik
Marktdatenaufnahme
Die Strategie abonniert Level-1-Updates und verfolgt den neuesten besten Bid und Best Ask.
Berechnungen werden gestoppt, bis beide Seiten des Orderbuchs verfügbar sind und die Strategie handeln darf.
Eigenkapital-Schutzmaßnahmen
Der realisierte PnL (PnL) und die unrealisierte Komponente aus dem aktuellen Bid/Ask und PositionPrice werden summiert.
Wenn das kombinierte Eigenkapital EquityTakeProfit überschreitet oder unter -EquityStopLoss fällt, wird die aktuelle
Nettoposition mit einem Marktauftrag abgeflacht. Ausstehende Aufträge bleiben intakt und entsprechen dem ursprünglichen
Expertenverhalten.
Platzierung ausstehender Aufträge
Der Spread in Preiseinheiten wird mit MaxSpreadPoints verglichen. Wenn der Spread zu breit ist, werden keine neuen Aufträge
platziert.
Andernfalls wird eine Distanz als max(IndentationPoints * step, spread * 3) berechnet. Dieser Wert repliziert die MT5-Logik,
entweder den Benutzereinzug zu respektieren oder drei Spreads zu erzwingen, wenn der Broker StopsLevel null ist.
Ein Buy-Stop-Auftrag wird bei ask + Distanz platziert und ein Sell-Stop bei bid - Distanz. Preise werden auf den nächsten
Tick normalisiert. Nur ein aktiver Auftrag pro Seite ist erlaubt; veraltete Aufträge werden bereinigt, wenn ihr Status auf
Done, Failed oder Canceled wechselt.
Trailing ausstehender Aufträge
Wenn TrailingStopPoints größer als null ist, berechnet die Strategie periodisch (nicht häufiger als Pause) die Stop-Distanz
mithilfe von max((TrailingStopPoints + TrailingStepPoints) * step, spread * 3) neu und registriert die Aufträge erneut, wenn
die Änderung einen halben Tick überschreitet.
Trailing hält die Aufträge nahe am Markt, während die Mindestdistanz eingehalten wird, die ein vorzeitiges Auslösen vermeidet.
Das Endergebnis ist eine gitterartige Ausbruchsmaschine, die ständig darauf wartet, dass der Preis in eine der Richtungen ausschlägt.
Parameter
Parameter
Beschreibung
IndentationPoints
Basisdistanz in Punkten zwischen dem Markt und jedem ausstehenden Stop-Auftrag.
MaxSpreadPoints
Maximaler zulässiger Spread (in Punkten). Aufträge werden ausgesetzt, während der Spread breiter ist.
TrailingStopPoints
Basis-Trailing-Distanz in Punkten für bestehende ausstehende Aufträge. Auf null setzen, um Trailing zu deaktivieren.
TrailingStepPoints
Zusätzlicher Puffer, der zur Basis-Trailing-Distanz hinzugefügt wird.
Pause
Mindestzeit zwischen zwei Trailing-Neuberechnungen. Der Standard entspricht der Ein-Sekunden-Pause des MT5-Experten.
EquityTakeProfit
Eigenkapitalgewinn in Kontowährung, der ein sofortiges Abflachen der Position auslöst.
EquityStopLoss
Zulässiger Eigenkapital-Drawdown, bevor die offene Position geschlossen wird.
Volume
Auftragsgröße aus der Strategy-Basisklasse. Das Broker-Minimum verwenden, um den MT5-Standard nachzuahmen.
Alle Preisabstände werden von Punkten in tatsächliche Preiseinheiten umgerechnet unter Verwendung von Security.PriceStep. Falls das
Instrument keinen Preisschritt bietet, wird ein Fallback-Wert von 1 verwendet.
Praktische Hinweise
Da die Strategie nur mit Stop-Aufträgen arbeitet, sind keine Kerzen oder Indikatoren erforderlich. Sie kann bei Backtests ausgeführt
werden, die keine historischen Kerzen liefern, solange Level-1-Daten verfügbar sind.
Broker, die einen Nicht-Null-StopsLevel erzwingen, sollten IndentationPoints so konfigurieren, dass der resultierende
Preisunterschied die Börsenpflicht erfüllt. Das Dreifach-Spread-Sicherheitsnetz dient als sekundäre Absicherung.
Der Eigenkapitalfilter ist absichtlich leicht berührend und storniert keine ausstehenden Aufträge. Dies spiegelt das ursprüngliche
Hinterhalt-Verhalten wider und ermöglicht neue Trades nach dem Abflach-Ereignis ohne manuelle Eingriffe.
Slippage und Auftragserfüllungstoleranz werden vom verbundenen Broker oder Simulator gesteuert. Volume und Parameterwerte anpassen,
um der Instrumentenvolatilität zu entsprechen.
Diese Dokumentation bietet absichtlich das maximale Detailniveau, damit sowohl diskretionäre als auch algorithmische Trader die
Konvertierung verstehen und die Strategie für ihren Ausführungsort anpassen können.
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>
/// Breakout strategy converted from the Ambush MQL5 expert.
/// Enters on breakouts above/below previous candle range with trailing stop management.
/// </summary>
public class AmbushStrategy : Strategy
{
private readonly StrategyParam<decimal> _indentationPoints;
private readonly StrategyParam<decimal> _trailingStopPoints;
private readonly StrategyParam<decimal> _trailingStepPoints;
private readonly StrategyParam<decimal> _equityTakeProfit;
private readonly StrategyParam<decimal> _equityStopLoss;
private readonly StrategyParam<DataType> _candleType;
private ICandleMessage _previousCandle;
private decimal _entryPrice;
private decimal? _stopPrice;
private decimal _priceStep;
/// <summary>
/// Distance from the market price for breakout detection, in points.
/// </summary>
public decimal IndentationPoints
{
get => _indentationPoints.Value;
set => _indentationPoints.Value = value;
}
/// <summary>
/// Trailing distance for stop orders, in points.
/// </summary>
public decimal TrailingStopPoints
{
get => _trailingStopPoints.Value;
set => _trailingStopPoints.Value = value;
}
/// <summary>
/// Trailing step added to the base trailing distance, in points.
/// </summary>
public decimal TrailingStepPoints
{
get => _trailingStepPoints.Value;
set => _trailingStepPoints.Value = value;
}
/// <summary>
/// Target equity profit that triggers position flattening.
/// </summary>
public decimal EquityTakeProfit
{
get => _equityTakeProfit.Value;
set => _equityTakeProfit.Value = value;
}
/// <summary>
/// Maximum equity drawdown allowed before flattening positions.
/// </summary>
public decimal EquityStopLoss
{
get => _equityStopLoss.Value;
set => _equityStopLoss.Value = value;
}
/// <summary>
/// Candle type used for breakout detection.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="AmbushStrategy"/> class.
/// </summary>
public AmbushStrategy()
{
_indentationPoints = Param(nameof(IndentationPoints), 10m)
.SetNotNegative()
.SetDisplay("Indentation (points)", "Distance from price for breakout", "Orders");
_trailingStopPoints = Param(nameof(TrailingStopPoints), 10m)
.SetNotNegative()
.SetDisplay("Trailing Stop (points)", "Base trailing distance", "Orders");
_trailingStepPoints = Param(nameof(TrailingStepPoints), 1m)
.SetNotNegative()
.SetDisplay("Trailing Step (points)", "Additional trailing offset", "Orders");
_equityTakeProfit = Param(nameof(EquityTakeProfit), 15m)
.SetNotNegative()
.SetDisplay("Equity Take Profit", "Flatten positions once this profit is reached", "Risk");
_equityStopLoss = Param(nameof(EquityStopLoss), 5m)
.SetNotNegative()
.SetDisplay("Equity Stop Loss", "Flatten positions after this loss", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(6).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for breakout detection", "General");
Volume = 1;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_previousCandle = null;
_entryPrice = 0m;
_stopPrice = null;
_priceStep = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_priceStep = Security?.PriceStep ?? 1m;
if (_priceStep <= 0m) _priceStep = 1m;
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;
// Check equity targets.
var pnl = PnL;
if (EquityTakeProfit > 0m && pnl >= EquityTakeProfit)
{
FlattenPosition();
_previousCandle = candle;
return;
}
if (EquityStopLoss > 0m && pnl <= -EquityStopLoss)
{
FlattenPosition();
_previousCandle = candle;
return;
}
// Check trailing stop.
if (Position > 0 && _stopPrice.HasValue && candle.LowPrice <= _stopPrice.Value)
{
SellMarket(Position);
ResetTargets();
}
else if (Position < 0 && _stopPrice.HasValue && candle.HighPrice >= _stopPrice.Value)
{
BuyMarket(Math.Abs(Position));
ResetTargets();
}
// Update trailing stop.
UpdateTrailing(candle);
// Entry logic - breakout above/below previous candle range.
if (Position == 0 && _previousCandle != null)
{
var indentation = IndentationPoints * _priceStep;
var buyLevel = _previousCandle.HighPrice + indentation;
var sellLevel = _previousCandle.LowPrice - indentation;
if (candle.HighPrice >= buyLevel)
{
BuyMarket(Volume);
_entryPrice = candle.ClosePrice;
var trailDist = (TrailingStopPoints + TrailingStepPoints) * _priceStep;
_stopPrice = trailDist > 0m ? _entryPrice - trailDist : null;
}
else if (candle.LowPrice <= sellLevel)
{
SellMarket(Volume);
_entryPrice = candle.ClosePrice;
var trailDist = (TrailingStopPoints + TrailingStepPoints) * _priceStep;
_stopPrice = trailDist > 0m ? _entryPrice + trailDist : null;
}
}
_previousCandle = candle;
}
private void UpdateTrailing(ICandleMessage candle)
{
if (TrailingStopPoints <= 0m)
return;
var trailDist = (TrailingStopPoints + TrailingStepPoints) * _priceStep;
if (trailDist <= 0m)
return;
if (Position > 0)
{
var newStop = candle.ClosePrice - trailDist;
if (!_stopPrice.HasValue || newStop > _stopPrice.Value)
_stopPrice = newStop;
}
else if (Position < 0)
{
var newStop = candle.ClosePrice + trailDist;
if (!_stopPrice.HasValue || newStop < _stopPrice.Value)
_stopPrice = newStop;
}
}
private void FlattenPosition()
{
if (Position > 0)
SellMarket(Position);
else if (Position < 0)
BuyMarket(Math.Abs(Position));
ResetTargets();
}
private void ResetTargets()
{
_entryPrice = 0m;
_stopPrice = null;
}
}
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 ambush_strategy(Strategy):
def __init__(self):
super(ambush_strategy, self).__init__()
self._indentation_points = self.Param("IndentationPoints", 10.0)
self._trailing_stop_points = self.Param("TrailingStopPoints", 10.0)
self._trailing_step_points = self.Param("TrailingStepPoints", 1.0)
self._equity_take_profit = self.Param("EquityTakeProfit", 15.0)
self._equity_stop_loss = self.Param("EquityStopLoss", 5.0)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(6)))
self.Volume = 1
self._previous_candle = None
self._entry_price = 0.0
self._stop_price = None
self._price_step = 1.0
@property
def IndentationPoints(self):
return self._indentation_points.Value
@property
def TrailingStopPoints(self):
return self._trailing_stop_points.Value
@property
def TrailingStepPoints(self):
return self._trailing_step_points.Value
@property
def EquityTakeProfit(self):
return self._equity_take_profit.Value
@property
def EquityStopLoss(self):
return self._equity_stop_loss.Value
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(ambush_strategy, self).OnStarted2(time)
sec = self.Security
self._price_step = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None and float(sec.PriceStep) > 0 else 1.0
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
pos = float(self.Position)
pnl = float(self.PnL)
if float(self.EquityTakeProfit) > 0 and pnl >= float(self.EquityTakeProfit):
self._flatten_position()
self._previous_candle = candle
return
if float(self.EquityStopLoss) > 0 and pnl <= -float(self.EquityStopLoss):
self._flatten_position()
self._previous_candle = candle
return
if pos > 0 and self._stop_price is not None and float(candle.LowPrice) <= self._stop_price:
self.SellMarket(pos)
self._reset_targets()
elif pos < 0 and self._stop_price is not None and float(candle.HighPrice) >= self._stop_price:
self.BuyMarket(abs(pos))
self._reset_targets()
self._update_trailing(candle)
pos = float(self.Position)
if pos == 0 and self._previous_candle is not None:
indentation = float(self.IndentationPoints) * self._price_step
buy_level = float(self._previous_candle.HighPrice) + indentation
sell_level = float(self._previous_candle.LowPrice) - indentation
if float(candle.HighPrice) >= buy_level:
self.BuyMarket(float(self.Volume))
self._entry_price = float(candle.ClosePrice)
trail_dist = (float(self.TrailingStopPoints) + float(self.TrailingStepPoints)) * self._price_step
self._stop_price = self._entry_price - trail_dist if trail_dist > 0 else None
elif float(candle.LowPrice) <= sell_level:
self.SellMarket(float(self.Volume))
self._entry_price = float(candle.ClosePrice)
trail_dist = (float(self.TrailingStopPoints) + float(self.TrailingStepPoints)) * self._price_step
self._stop_price = self._entry_price + trail_dist if trail_dist > 0 else None
self._previous_candle = candle
def _update_trailing(self, candle):
if float(self.TrailingStopPoints) <= 0:
return
trail_dist = (float(self.TrailingStopPoints) + float(self.TrailingStepPoints)) * self._price_step
if trail_dist <= 0:
return
pos = float(self.Position)
if pos > 0:
new_stop = float(candle.ClosePrice) - trail_dist
if self._stop_price is None or new_stop > self._stop_price:
self._stop_price = new_stop
elif pos < 0:
new_stop = float(candle.ClosePrice) + trail_dist
if self._stop_price is None or new_stop < self._stop_price:
self._stop_price = new_stop
def _flatten_position(self):
pos = float(self.Position)
if pos > 0:
self.SellMarket(pos)
elif pos < 0:
self.BuyMarket(abs(pos))
self._reset_targets()
def _reset_targets(self):
self._entry_price = 0.0
self._stop_price = None
def OnReseted(self):
super(ambush_strategy, self).OnReseted()
self._previous_candle = None
self._entry_price = 0.0
self._stop_price = None
self._price_step = 0.0
def CreateClone(self):
return ambush_strategy()