A Estratégia de Emboscada cerca continuamente o mercado com um par de ordens buy-stop e sell-stop. As ordens pendentes são colocadas
a uma indentação configurável acima do melhor ask e abaixo do melhor bid, com uma substituição dinâmica que impõe uma distância
mínima baseada no spread atual. Sempre que um lado é acionado, a estratégia reconstrói imediatamente ambas as ordens para que o
mercado permaneça "em emboscada" de ambas as direções. Um disjuntor simples baseado em patrimônio pode nivelar as posições assim que
um alvo de lucro diário ou limite de perda é atingido.
Esta implementação em C# replica o comportamento do especialista original do MetaTrader 5 de Zuzabush. Opera puramente com cotações
de Nível 1 e não requer velas ou indicadores. Cada decisão é impulsionada por mudanças em tempo real do bid/ask, portanto a estratégia
é mais adequada para instrumentos líquidos com spreads apertados.
Lógica de trading
Recepção de dados de mercado
A estratégia assina atualizações de Nível 1 e rastreia o último melhor bid e best ask.
Os cálculos param até que ambos os lados do livro de ordens estejam disponíveis e a estratégia tenha permissão para negociar.
Salvaguardas de patrimônio
O PnL realizado (PnL) e o componente não realizado derivado do bid/ask atual e PositionPrice são somados.
Se o patrimônio combinado exceder EquityTakeProfit, ou cair abaixo de -EquityStopLoss, a posição líquida atual é nivelada
com uma ordem a mercado. As ordens pendentes são deixadas intactas, correspondendo ao comportamento original do especialista.
Colocação de ordens pendentes
O spread em unidades de preço é comparado com MaxSpreadPoints. Se o spread for muito amplo, nenhuma nova ordem é colocada.
Caso contrário, uma distância é calculada como max(IndentationPoints * step, spread * 3). Esse valor replica a lógica MT5 de
respeitar a indentação do usuário ou impor três spreads quando o StopsLevel do corretor é zero.
Uma ordem buy-stop é colocada em ask + distância e uma sell-stop em bid - distância. Os preços são normalizados para o
tick mais próximo. Apenas uma ordem ativa por lado é permitida; ordens obsoletas são limpas quando seu estado muda para
Done, Failed ou Canceled.
Trailing de ordens pendentes
Quando TrailingStopPoints é maior que zero, a estratégia recalcula periodicamente (não mais frequentemente que Pause) a
distância de stop usando max((TrailingStopPoints + TrailingStepPoints) * step, spread * 3) e re-registra as ordens se a
mudança exceder meio tick.
O trailing mantém as ordens próximas ao mercado enquanto ainda respeita a distância mínima que evita o acionamento prematuro.
O resultado final é um motor de rompimento tipo grid que está constantemente esperando que o preço se mova decisivamente em qualquer direção.
Parâmetros
Parâmetro
Descrição
IndentationPoints
Distância base em pontos entre o mercado e cada ordem stop pendente.
MaxSpreadPoints
Spread máximo permitido (em pontos). As ordens são suspensas enquanto o spread for mais amplo.
TrailingStopPoints
Distância base de trailing em pontos aplicada a ordens pendentes existentes. Definir como zero para desativar o trailing.
TrailingStepPoints
Buffer adicional adicionado acima da distância base de trailing.
Pause
Tempo mínimo entre dois recálculos de trailing. O padrão espelha a pausa de um segundo do especialista MT5.
EquityTakeProfit
Lucro de patrimônio em moeda da conta que aciona um nivelamento imediato de posição.
EquityStopLoss
Queda de patrimônio permitida antes que a posição aberta seja fechada.
Volume
Tamanho da ordem herdado da classe base Strategy. Usar o mínimo do corretor para imitar o padrão MT5.
Todos os offsets de preço são convertidos de pontos para unidades de preço reais usando Security.PriceStep. Se o instrumento não
expõe um passo de preço, um valor de fallback de 1 é usado.
Notas práticas
Como a estratégia trabalha apenas com ordens stop, nenhuma vela ou indicador é necessário. Pode ser executada durante backtests que
não fornecem velas históricas desde que os dados de Nível 1 estejam disponíveis.
Os corretores que impõem um StopsLevel não nulo devem configurar IndentationPoints para que a diferença de preço resultante
satisfaça a regra da bolsa. A rede de segurança de triplo spread atua como guarda secundária.
O filtro de patrimônio é intencionalmente leve e não cancela ordens pendentes. Isso espelha o comportamento original de Emboscada,
permitindo novas negociações após o evento de nivelamento sem intervenção manual.
O deslizamento e a tolerância de preenchimento de ordens são controlados pelo corretor ou simulador conectado. Ajustar Volume e
valores de parâmetros para corresponder à volatilidade do instrumento.
Esta documentação fornece intencionalmente o nível máximo de detalhe para que tanto traders discricionários quanto algorítmicos possam
entender a conversão e personalizar a estratégia para seu local de execução.
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()