Esta estratégia transporta o consultor especialista MetaTrader 4 original BalanceDrawdownInMT4 para o StockSharp API de alto nível. O EA abre imediatamente uma única posição longa e mede continuamente a redução da conta em relação ao saldo máximo alcançado desde o início da sessão.
Lógica de negociação
Quando a estratégia é iniciada, ela chama StartProtection para armar níveis gerenciados de stop-loss e take-profit que imitam as entradas MQL expressas em faixas de preço.
Na primeira vela finalizada (período padrão: 1 minuto) a estratégia verifica se uma posição está aberta. Se não existir exposição, ele envia uma ordem de compra a mercado usando o Volume configurado.
Após cada vela finalizada, a métrica de rebaixamento é atualizada:
A estratégia rastreia o saldo máximo alcançado como StartBalance + PnL realizado.
O patrimônio atual é igual a StartBalance + PnL realizado + PnL não realizado, onde o PnL não realizado é derivado do último preço de fechamento da vela, do preço médio de entrada e do PriceStep/StepPrice do instrumento.
Drawdown é o declínio percentual do saldo máximo armazenado até o patrimônio atual. O valor é registrado com uma mensagem informativa a cada atualização.
O algoritmo nunca abre posições adicionais ou reverte. Uma vez estabelecida a posição inicial, ela permanece ativa até ser interrompida, o take-profit dispara ou o usuário intervém manualmente.
Parâmetros
Parâmetro
Padrão
Descrição
StartBalance
1000
Saldo da linha de base usado ao calcular o patrimônio máximo e a porcentagem de redução.
Volume
0.01
Volume líquido (em unidades do instrumento) da ordem inicial de compra no mercado.
StopLossPoints
300
Distância do preço de entrada até o stop de proteção, medida em faixas de preço. Um valor de 0 desativa a parada.
TakeProfitPoints
400
Distância do preço de entrada até a meta de proteção, medida em faixas de preço. Um valor de 0 desativa o destino.
CandleType
1m período de tempo
Período que orienta atualizações periódicas de saque e verificação inicial de entrada.
Notas de implementação
O contador de rebaixamento usa o PnL realizado da estratégia (PnL) combinado com o PnL não realizado estimado a partir de diferenças de preço, correspondendo à lógica de saldo corrente encontrada na versão MT4.
Se PriceStep ou StepPrice não estiver disponível para o título, o cálculo de PnL não realizado retornará zero com segurança, evitando erros de divisão por zero.
Volume é validado para garantir um valor positivo antes da negociação inicial; caso contrário, um aviso será registrado e a estratégia permanecerá estável.
DrawdownPercent expõe a leitura de redução mais recente para que outros módulos (painel, controladores de risco) possam extrair o valor programaticamente.
Dicas de uso
Defina StartBalance para o saldo real da conta (ou o saldo no início da sessão de negociação) para obter estatísticas de rebaixamento significativas.
Mantenha as velas padrão de 1 minuto para atualizações oportunas ou escolha um tipo de vela sintética mais rápida se precisar de precisão próxima ao tick.
Como esta estratégia mantém intencionalmente uma única posição longa, combine-a com controles de risco manuais ou automação externa se precisar entrar novamente após um stop ou alvo ser atingido.
Sempre teste em um simulador para confirmar se o corretor fornece PriceStep e StepPrice para que a conversão de PnL não realizada corresponda às expectativas.
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>
/// Replicates the BalanceDrawdownInMT4 expert advisor: opens a single long position and tracks drawdown from the peak balance.
/// </summary>
public class BalanceDrawdownInMt4Strategy : Strategy
{
private readonly StrategyParam<decimal> _startBalance;
private readonly StrategyParam<decimal> _stopLossPoints;
private readonly StrategyParam<decimal> _takeProfitPoints;
private readonly StrategyParam<int> _entryCooldownDays;
private readonly StrategyParam<DataType> _candleType;
private decimal _maxBalance;
private decimal _lastDrawdown;
private decimal _lastPrice;
private DateTime _lastEntryDate;
/// <summary>
/// Balance used as the baseline for drawdown calculations.
/// </summary>
public decimal StartBalance
{
get => _startBalance.Value;
set => _startBalance.Value = value;
}
/// <summary>
/// Stop-loss distance expressed in price points.
/// </summary>
public decimal StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Take-profit distance expressed in price points.
/// </summary>
public decimal TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Minimum number of days between new entries.
/// </summary>
public int EntryCooldownDays
{
get => _entryCooldownDays.Value;
set => _entryCooldownDays.Value = value;
}
/// <summary>
/// Timeframe used to trigger periodic drawdown updates.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Current drawdown percentage relative to the peak balance.
/// </summary>
public decimal DrawdownPercent => _lastDrawdown;
/// <summary>
/// Initializes a new instance of the strategy.
/// </summary>
public BalanceDrawdownInMt4Strategy()
{
_startBalance = Param(nameof(StartBalance), 1000m)
.SetDisplay("Start Balance", "Initial balance for drawdown measurement.", "Risk")
;
_stopLossPoints = Param(nameof(StopLossPoints), 300m)
.SetDisplay("Stop-Loss (points)", "Distance from entry price to the protective stop.", "Risk")
;
_takeProfitPoints = Param(nameof(TakeProfitPoints), 400m)
.SetDisplay("Take-Profit (points)", "Distance from entry price to the profit target.", "Risk")
;
_entryCooldownDays = Param(nameof(EntryCooldownDays), 5)
.SetGreaterThanZero()
.SetDisplay("Entry Cooldown Days", "Minimum number of days between new long entries.", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Timeframe that drives drawdown monitoring.", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_maxBalance = 0m;
_lastDrawdown = 0m;
_lastPrice = 0m;
_lastEntryDate = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
StartProtection(
stopLoss: new Unit(StopLossPoints * GetPriceStep(), UnitTypes.Absolute),
takeProfit: new Unit(TakeProfitPoints * GetPriceStep(), UnitTypes.Absolute));
_maxBalance = StartBalance;
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
_lastPrice = candle.ClosePrice;
EnsurePosition(candle.CloseTime);
UpdateDrawdown();
}
private void EnsurePosition(DateTime candleDate)
{
if (Position != 0m)
return;
if (_lastEntryDate != default && (candleDate.Date - _lastEntryDate.Date).TotalDays < EntryCooldownDays)
return;
if (Volume <= 0m)
{
LogWarning("Volume parameter must be positive to open the initial trade.");
return;
}
BuyMarket(Volume);
_lastEntryDate = candleDate.Date;
}
private void UpdateDrawdown()
{
var balanceWithoutFloating = StartBalance + PnL;
if (balanceWithoutFloating > _maxBalance)
_maxBalance = balanceWithoutFloating;
if (_maxBalance <= 0m)
{
_lastDrawdown = 0m;
return;
}
var unrealized = CalculateUnrealizedPnL(_lastPrice);
var currentBalance = balanceWithoutFloating + unrealized;
var drawdown = (_maxBalance - currentBalance) / _maxBalance * 100m;
_lastDrawdown = drawdown > 0m ? drawdown : 0m;
LogInfo($"Current drawdown: {_lastDrawdown:F2}%.");
}
private decimal CalculateUnrealizedPnL(decimal price)
{
if (Position == 0m)
return 0m;
var step = Security?.PriceStep ?? 0m;
var stepPrice = GetSecurityValue<decimal?>(Level1Fields.StepPrice) ?? 0m;
if (step <= 0m || stepPrice <= 0m)
return 0m;
var priceDiff = price - _lastPrice;
var points = priceDiff / step;
return points * stepPrice * Position;
}
private decimal GetPriceStep()
{
var step = Security?.PriceStep ?? 0m;
return step > 0m ? step : 0.0001m;
}
}
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, Unit, UnitTypes
from StockSharp.Algo.Strategies import Strategy
class balance_drawdown_in_mt4_strategy(Strategy):
def __init__(self):
super(balance_drawdown_in_mt4_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._start_balance = self.Param("StartBalance", 1000.0)
self._stop_loss_points = self.Param("StopLossPoints", 300.0)
self._take_profit_points = self.Param("TakeProfitPoints", 400.0)
self._entry_cooldown_days = self.Param("EntryCooldownDays", 5)
self._max_balance = 0.0
self._last_entry_date = None
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def StartBalance(self):
return self._start_balance.Value
@StartBalance.setter
def StartBalance(self, value):
self._start_balance.Value = value
@property
def StopLossPoints(self):
return self._stop_loss_points.Value
@StopLossPoints.setter
def StopLossPoints(self, value):
self._stop_loss_points.Value = value
@property
def TakeProfitPoints(self):
return self._take_profit_points.Value
@TakeProfitPoints.setter
def TakeProfitPoints(self, value):
self._take_profit_points.Value = value
@property
def EntryCooldownDays(self):
return self._entry_cooldown_days.Value
@EntryCooldownDays.setter
def EntryCooldownDays(self, value):
self._entry_cooldown_days.Value = value
def OnReseted(self):
super(balance_drawdown_in_mt4_strategy, self).OnReseted()
self._max_balance = 0.0
self._last_entry_date = None
def OnStarted2(self, time):
super(balance_drawdown_in_mt4_strategy, self).OnStarted2(time)
self._max_balance = float(self.StartBalance)
self._last_entry_date = None
self.StartProtection(
takeProfit=Unit(2, UnitTypes.Percent),
stopLoss=Unit(1, UnitTypes.Percent))
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._process_candle).Start()
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
candle_date = candle.CloseTime.Date
# Ensure position (long-only, with cooldown)
if self.Position == 0:
if self._last_entry_date is not None:
days_diff = (candle_date - self._last_entry_date).TotalDays
if days_diff < self.EntryCooldownDays:
return
self.BuyMarket()
self._last_entry_date = candle_date
def CreateClone(self):
return balance_drawdown_in_mt4_strategy()