ProRealCode - Trading & Coding with ProRealTime™
Hello everyone,
I would like to reproduce an existing MT5 trading logic in ProRealTime / ProBacktest, mainly in order to test it on the longest possible historical DE40 data series available in PRT.
The focus is not on optimizing the entry logic, but on the capital management.
The basic idea is:
In short:
Capital target fixed – TP price dynamic.
This is not intended as a martingale system and not as a classic fixed-distance grid. The position size remains constant.
Example:
With two open positions, the common TP should be calculated so that both positions together produce the predefined basket profit.
If a third position is added, the common TP should be recalculated so that all three positions together still reach the same fixed capital target.
The main purpose of the backtest is to evaluate the capital-management behaviour over a long DE40 history.
The statistics I am especially interested in are:
I will add the existing MT5 code below. It should be understood as a reference implementation of the intended logic and does not need to be translated line by line.
If SET TARGET %PROFIT is not suitable for cumulative positions, the common basket target price would need to be calculated explicitly and the complete position closed at that price.
The underlying entry logic is simple and is not the main point of this discussion.
Any help with a clean ProRealTime / ProBacktest implementation of this capital-management logic would be appreciated.
MT5 code follows here:
(Please note: the MT5 code is provided only as a reference for the trading and capital-management logic. I am not asking for a review of the EA’s MT5-specific robustness, retry handling, logging, persistence, or broker execution details.)
//+——————————————————————+
//| D209_1HL_D522_D1HighLow_Basket.mq5 |
//| https://www.dkatle.com |
//| Copyright dkatle 2026 |
//+——————————————————————+
#property strict
#property copyright “dkatle 2026”
#property link “https://www.dkatle.com”
#property version “6.4”
#include <Trade/Trade.mqh>
#include <Trade/PositionInfo.mqh>
CTrade trade;
CPositionInfo posInfo;
// — Input Parameter —
input string TradeComment = “D209 1HL D522 D1HiLo Basket”;
input long Magic = 2090522063
input group “Kapital & Basket Einstellungen”
input double InitialVirtualCapital = 1000.0;
input double BasketTPPercent = 5.0;
input bool CumulateOrders = true;
input group “Handels Parameter”
input double FixedLotSize = 0.1;
input double StopLossPercent = 0.0;
input double TakeProfitPercent = 2.5;
input group “Einstiegs-Zeitpunkt (Slot)”
input string InpHolidays = “01.01, 01.05, 03.10, 24.12, 25.12, 26.12, 31.12”;
input int BlockMonth1 = 0;
input int BlockMonth2 = 0;
input int BlockMonth3 = 0;
input int BlockMonth4 = 0;
input bool UseBerlinOffset = false;
input int BerlinOffsetHours = +1;
input bool InpTradeMonday = false;
input bool InpTradeTuesday = true;
input bool InpTradeWednesday = false;
input bool InpTradeThursday = false;
input bool InpTradeFriday = false;
input int InpEntryHour = 09;
input int InpEntryMinute = 00;
input group “Ausstiegs-Zeitpunkt (Slot & Wochentag für Einzelläufer)”
input bool InpExitEnabled = false;
input bool InpExitMonday = false;
input bool InpExitTuesday = true;
input bool InpExitWednesday = false;
input bool InpExitThursday = false;
input bool InpExitFriday = true;
input int InpExitHour = 22;
input int InpExitMinute = 00;
input group “Robustheit / Retry”
input int EntryWindowSeconds = 60;
input int RetryIntervalMs = 500;
input int CloseRetryIntervalHours = 1;
input int MaxSpreadPoints = 100;
input int MaxSlippagePoints = 50;
// — Globale Variablen —
string lblStart, lblCurr, lblTarget, logFileName;
datetime lastProcessedEntrySlot = 0;
ulong lastEntryAttemptMs = 0;
bool basketActive = false;
double basketLockedTarget = 0.0;
bool targetCloseActive = false;
double targetCloseLockedTarget = 0.0;
// — Hilfs-Namen für globale Variablen im Terminal —
string GV_TargetName(ulong ticket) { return “D113_Exit_” + (string)Magic + “_” + (string)ticket + “_Target”; }
string GV_NextName(ulong ticket) { return “D113_Exit_” + (string)Magic + “_” + (string)ticket + “_Next”; }
string GV_LastEntrySlotName() { return “D113_LastEntry_” + (string)Magic + “_” + _Symbol; }
string GV_BasketTargetName() { return “D113_BasketTarget_” + (string)Magic + “_” + _Symbol; }
string GV_TargetCloseName() { return “D113_BasketClose_” + (string)Magic + “_” + _Symbol; }
//+——————————————————————+
//| Hilfsfunktionen |
//+——————————————————————+
int GetOpenPositionsCount()
{
int count = 0;
for(int i = PositionsTotal() – 1; i >= 0; i–)
{
if(posInfo.SelectByIndex(i) && posInfo.Magic() == Magic && posInfo.Symbol() == _Symbol)
count++;
}
return count;
}
bool IsTradeRetcodeAccepted()
{
uint rc = trade.ResultRetcode();
return (rc == TRADE_RETCODE_DONE ||
rc == TRADE_RETCODE_DONE_PARTIAL ||
rc == TRADE_RETCODE_PLACED);
}
string TradeResultText()
{
return “Retcode=” + (string)trade.ResultRetcode() +
” (” + trade.ResultRetcodeDescription() + “)” +
“, Order=” + (string)trade.ResultOrder() +
“, Deal=” + (string)trade.ResultDeal();
}
bool ClosePositionVerified(ulong ticket)
{
if(!PositionSelectByTicket(ticket))
return true;
trade.SetDeviationInPoints(MaxSlippagePoints);
ResetLastError();
bool requestOk = trade.PositionClose(ticket);
bool accepted = requestOk && IsTradeRetcodeAccepted();
bool stillOpen = PositionSelectByTicket(ticket);
if(accepted && !stillOpen)
return true;
Print(“D308 CLOSE nicht vollständig ausgeführt. Ticket=”, ticket,
“, RequestOK=”, requestOk,
“, “, TradeResultText(),
“, LastError=”, GetLastError());
return false;
}
void MarkEntrySlotProcessed(datetime slotStart)
{
lastProcessedEntrySlot = slotStart;
GlobalVariableSet(GV_LastEntrySlotName(), (double)slotStart);
}
void ActivateBasket(double target)
{
basketActive = true;
basketLockedTarget = target;
GlobalVariableSet(GV_BasketTargetName(), target);
}
void DeactivateBasket()
{
basketActive = false;
basketLockedTarget = 0.0;
string gv = GV_BasketTargetName();
if(GlobalVariableCheck(gv))
GlobalVariableDel(gv);
}
void ActivateTargetClose(double target)
{
targetCloseActive = true;
targetCloseLockedTarget = target;
GlobalVariableSet(GV_TargetCloseName(), target);
}
void DeactivateTargetClose()
{
targetCloseActive = false;
targetCloseLockedTarget = 0.0;
string gv = GV_TargetCloseName();
if(GlobalVariableCheck(gv))
GlobalVariableDel(gv);
}
string FormatGermanNumber(double value)
{
string s = DoubleToString(value, 2);
StringReplace(s, “.”, “,”);
int commaPos = StringFind(s, “,”);
if(commaPos > 3)
{
for(int i = commaPos – 3; i > 0; i -= 3)
s = StringSubstr(s, 0, i) + “.” + StringSubstr(s, i);
}
return s;
}
void UpdateDisplay()
{
double realizedProfit = 0;
double floatingProfit = 0;
datetime startDate = 0;
CalculateCapitalMetrics(realizedProfit, floatingProfit, startDate);
double securedCapital = InitialVirtualCapital + realizedProfit;
double virtualCapital = securedCapital + floatingProfit;
double nextTarget = CalculateNextTarget(securedCapital);
int openCount = GetOpenPositionsCount();
string dateStr = (startDate > 0) ? TimeToString(startDate, TIME_DATE) : “Kein Trade”;
string txtStart = “Start Capital (” + dateStr + “) : ” + FormatGermanNumber(InitialVirtualCapital) + ” EUR”;
string txtCurr = “Virtual Capital (Equity) : ” + FormatGermanNumber(virtualCapital) + ” EUR”;
string txtTarget = “Next Capital Target (+ ” + DoubleToString(BasketTPPercent, 1) + “%) : ” + FormatGermanNumber(nextTarget) + ” EUR”;
if(basketActive)
txtTarget = “Basket Target LOCKED (+ ” + DoubleToString(BasketTPPercent, 1) + “%) : ” + FormatGermanNumber(basketLockedTarget) + ” EUR”;
if(targetCloseActive)
{
string mode = basketActive ? “BASKET” : “SINGLE”;
txtTarget = mode + ” TARGET CLOSE ACTIVE : ” + FormatGermanNumber(targetCloseLockedTarget) + ” EUR”;
}
else if(openCount == 1 && !basketActive)
{
txtTarget = “Single Capital Target (+ ” + DoubleToString(BasketTPPercent, 1) + “%) : ” + FormatGermanNumber(nextTarget) + ” EUR”;
}
CreateOrUpdateLabel(lblStart, txtStart, 35);
CreateOrUpdateLabel(lblCurr, txtCurr, 55);
CreateOrUpdateLabel(lblTarget, txtTarget, 75);
ChartRedraw();
}
void CreateOrUpdateLabel(string name, string text, int yDist)
{
if(ObjectFind(0, name) < 0)
{
ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0);
ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_RIGHT_UPPER);
ObjectSetInteger(0, name, OBJPROP_ANCHOR, ANCHOR_RIGHT_UPPER);
ObjectSetInteger(0, name, OBJPROP_XDISTANCE, 15);
ObjectSetInteger(0, name, OBJPROP_COLOR, clrBlack);
ObjectSetInteger(0, name, OBJPROP_FONTSIZE, 8);
ObjectSetString(0, name, OBJPROP_FONT, “Arial”);
}
ObjectSetInteger(0, name, OBJPROP_YDISTANCE, yDist);
ObjectSetString(0, name, OBJPROP_TEXT, text);
}
//+——————————————————————+
//| Daten- und Log-Funktionen |
//+——————————————————————+
string SanitizeFileName(string s)
{
string bad = “\/:*?”<>|”;
for(int i = 0; i < StringLen(bad); i++)
StringReplace(s, StringSubstr(bad, i, 1), “_”);
StringReplace(s, ” “, “_”);
return s;
}
void LogTradeEvent(string line)
{
if(StringLen(logFileName) == 0) return;
int handle = FileOpen(logFileName, FILE_READ|FILE_WRITE|FILE_CSV|FILE_ANSI);
if(handle == INVALID_HANDLE)
{
handle = FileOpen(logFileName, FILE_WRITE|FILE_CSV|FILE_ANSI);
if(handle == INVALID_HANDLE) return;
FileWrite(handle, “Time;Magic;Event;Symbol;Lots;Price;SL;TP;Comment;Info;Spread;Swap”);
}
FileSeek(handle, 0, SEEK_END);
FileWrite(handle, line);
FileClose(handle);
}
//+——————————————————————+
//| Handels-Kernfunktionen |
//+——————————————————————+
double RoundLotFixed(double lotsInput)
{
double step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
if(step <= 0) step = 0.1;
double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
if(minLot <= 0) minLot = 0.01;
double r = MathRound(lotsInput / step) * step;
return NormalizeDouble(MathMax(r, minLot), 2);
}
bool TryBuyWithFallback(double lotsInput, double price, double sl, double tp, string comment)
{
ulong nowMs = GetTickCount64();
int retryMs = RetryIntervalMs;
if(retryMs < 0) retryMs = 0;
if(lastEntryAttemptMs > 0 && (nowMs – lastEntryAttemptMs) < (ulong)retryMs)
return false;
lastEntryAttemptMs = nowMs;
double tryLots = RoundLotFixed(lotsInput);
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
if(ask <= 0 || bid <= 0)
return false;
double spreadPoints = (ask – bid) / _Point;
if(spreadPoints > MaxSpreadPoints)
return false;
trade.SetDeviationInPoints(MaxSlippagePoints);
ResetLastError();
bool requestOk = trade.Buy(tryLots, _Symbol, ask, sl, tp, comment);
if(requestOk && IsTradeRetcodeAccepted())
{
LogTradeEvent(TimeToString(TimeCurrent(), TIME_DATE|TIME_SECONDS) + “;” +
(string)Magic + “;ORDER_OK;” + _Symbol + “;” +
DoubleToString(tryLots, 2) + “;” +
DoubleToString(ask, _Digits) + “;” +
DoubleToString(sl, _Digits) + “;” +
DoubleToString(tp, _Digits) + “;” +
TradeComment + “;” + TradeResultText() + “;” +
DoubleToString(spreadPoints, 1) + “;0”);
return true;
}
Print(“D308 BUY fehlgeschlagen/abgelehnt. RequestOK=”, requestOk,
“, “, TradeResultText(),
“, LastError=”, GetLastError());
return false;
}
//+——————————————————————+
//| Filter-Funktionen |
//+——————————————————————+
bool IsHoliday(MqlDateTime &dt)
{
string date_str = StringFormat(“%02d.%02d”, dt.day, dt.mon);
if(StringFind(InpHolidays, date_str) >= 0) return true;
return false;
}
bool IsMonthBlocked(int m)
{
if(m == BlockMonth1 || m == BlockMonth2 || m == BlockMonth3) return true;
if(BlockMonth4 != 0 && m == BlockMonth4) return true;
return false;
}
bool IsWeekdayAllowed(int w)
{
if(w == 1 && InpTradeMonday) return true;
if(w == 2 && InpTradeTuesday) return true;
if(w == 3 && InpTradeWednesday) return true;
if(w == 4 && InpTradeThursday) return true;
if(w == 5 && InpTradeFriday) return true;
return false;
}
bool IsExitWeekdayAllowed(int w)
{
if(w == 1 && InpExitMonday) return true;
if(w == 2 && InpExitTuesday) return true;
if(w == 3 && InpExitWednesday) return true;
if(w == 4 && InpExitThursday) return true;
if(w == 5 && InpExitFriday) return true;
return false;
}
//+——————————————————————+
//| Zeit-Management für den Ausstieg des Einzelläufers |
//+——————————————————————+
datetime CalculateNextExit(datetime fromTime)
{
MqlDateTime dt;
TimeToStruct(fromTime, dt);
dt.hour = InpExitHour;
dt.min = InpExitMinute;
dt.sec = 0;
datetime target = StructToTime(dt);
while(target <= fromTime || !IsExitWeekdayAllowed(dt.day_of_week))
{
target += 86400;
TimeToStruct(target, dt);
dt.hour = InpExitHour;
dt.min = InpExitMinute;
dt.sec = 0;
target = StructToTime(dt);
}
return target;
}
void RegisterAllOpenPositionsTargets()
{
if(!InpExitEnabled) return;
for(int i = PositionsTotal() – 1; i >= 0; i–)
{
if(posInfo.SelectByIndex(i) && posInfo.Magic() == Magic && posInfo.Symbol() == _Symbol)
{
ulong ticket = posInfo.Ticket();
if(!GlobalVariableCheck(GV_TargetName(ticket)))
{
datetime target = CalculateNextExit((datetime)posInfo.Time());
GlobalVariableSet(GV_TargetName(ticket), (double)target);
GlobalVariableSet(GV_NextName(ticket), (double)target);
}
}
}
}
//+——————————————————————+
//| Zeit-Ausstieg |
//+——————————————————————+
void CheckAndClosePositions()
{
if(!InpExitEnabled) return;
if(basketActive || targetCloseActive) return;
if(GetOpenPositionsCount() != 1) return;
for(int i = PositionsTotal() – 1; i >= 0; i–)
{
if(posInfo.SelectByIndex(i) && posInfo.Magic() == Magic && posInfo.Symbol() == _Symbol)
{
ulong ticket = posInfo.Ticket();
string gvT = GV_TargetName(ticket);
string gvN = GV_NextName(ticket);
if(!GlobalVariableCheck(gvN)) continue;
if(TimeCurrent() < (datetime)GlobalVariableGet(gvN)) continue;
double currentProfit = posInfo.Profit() + posInfo.Swap() + posInfo.Commission();
if(currentProfit <= 0.0) continue;
if(ClosePositionVerified(ticket))
{
if(GlobalVariableCheck(gvT)) GlobalVariableDel(gvT);
if(GlobalVariableCheck(gvN)) GlobalVariableDel(gvN);
LogTradeEvent(TimeToString(TimeCurrent(), TIME_DATE|TIME_SECONDS) + “;” +
(string)Magic + “;SINGLE_EXIT_SLOT_PROFIT;” + _Symbol +
“;0;0;0;0;” + TradeComment +
“;ClosedInProfit=” + DoubleToString(currentProfit, 2) + “;0;0”);
UpdateDisplay();
}
else
{
GlobalVariableSet(gvN, (double)(TimeCurrent() + CloseRetryIntervalHours * 3600));
}
}
}
}
//+——————————————————————+
//| Kapital- & Basket-Berechnungen |
//+——————————————————————+
void CalculateCapitalMetrics(double &realizedProfit, double &floatingProfit, datetime &startDate)
{
realizedProfit = 0;
floatingProfit = 0;
startDate = 0;
if(HistorySelect(0, TimeCurrent() + 86400))
{
int totalDeals = HistoryDealsTotal();
for(int i = 0; i < totalDeals; i++)
{
ulong ticket = HistoryDealGetTicket(i);
if(ticket > 0)
{
long dealMagic = HistoryDealGetInteger(ticket, DEAL_MAGIC);
string dealSymbol = HistoryDealGetString(ticket, DEAL_SYMBOL);
if(dealMagic == Magic && dealSymbol == _Symbol)
{
datetime dealTime = (datetime)HistoryDealGetInteger(ticket, DEAL_TIME);
if(startDate == 0 || dealTime < startDate)
startDate = dealTime;
long dealEntry = HistoryDealGetInteger(ticket, DEAL_ENTRY);
if(dealEntry == DEAL_ENTRY_OUT || dealEntry == DEAL_ENTRY_INOUT)
{
realizedProfit += HistoryDealGetDouble(ticket, DEAL_PROFIT)
+ HistoryDealGetDouble(ticket, DEAL_SWAP)
+ HistoryDealGetDouble(ticket, DEAL_COMMISSION);
}
}
}
}
}
int openTotal = PositionsTotal();
for(int i = 0; i < openTotal; i++)
{
if(posInfo.SelectByIndex(i) && posInfo.Magic() == Magic && posInfo.Symbol() == _Symbol)
{
floatingProfit += posInfo.Profit() + posInfo.Swap() + posInfo.Commission();
}
}
}
double CalculateNextTarget(double securedCapital)
{
if(BasketTPPercent <= 0.0)
return DBL_MAX;
double target = InitialVirtualCapital;
double multiplier = 1.0 + (BasketTPPercent / 100.0);
while(securedCapital >= target * multiplier)
target = target * multiplier;
return NormalizeDouble(target * multiplier, 2);
}
//+——————————————————————+
//| Kapitalziel / Basket-State-Machine |
//+——————————————————————+
void CheckAndCloseBasket()
{
double realizedProfit = 0;
double floatingProfit = 0;
datetime startDate = 0;
CalculateCapitalMetrics(realizedProfit, floatingProfit, startDate);
double securedCapital = InitialVirtualCapital + realizedProfit;
double virtualCapital = securedCapital + floatingProfit;
double dynamicTarget = CalculateNextTarget(securedCapital);
int openCount = GetOpenPositionsCount();
if(openCount == 0)
{
bool hadBasket = basketActive;
bool hadTargetClose = targetCloseActive;
double completedTarget = targetCloseActive ? targetCloseLockedTarget : basketLockedTarget;
if(targetCloseActive)
DeactivateTargetClose();
if(basketActive)
DeactivateBasket();
if(hadTargetClose)
{
LogTradeEvent(TimeToString(TimeCurrent(), TIME_DATE|TIME_SECONDS) + “;” +
(string)Magic + “;CAPITAL_TARGET_CLOSE_SUCCESS;” + _Symbol +
“;0;0;0;0;” + TradeComment +
“;AllPositionsClosed;Mode=” + (hadBasket ? “BASKET” : “SINGLE”) +
“,LockedTarget=” + DoubleToString(completedTarget, 2) + “;0;0”);
}
if(hadBasket || hadTargetClose)
UpdateDisplay();
return;
}
if(!basketActive && openCount >= 2)
{
ActivateBasket(dynamicTarget);
LogTradeEvent(TimeToString(TimeCurrent(), TIME_DATE|TIME_SECONDS) + “;” +
(string)Magic + “;BASKET_ACTIVATED;” + _Symbol +
“;0;0;0;0;” + TradeComment +
“;OpenPositions=” + (string)openCount +
“,LockedTarget=” + DoubleToString(basketLockedTarget, 2) +
“,VirtualCap=” + DoubleToString(virtualCapital, 2) + “;0;0”);
UpdateDisplay();
}
double triggerTarget = basketActive ? basketLockedTarget : dynamicTarget;
if(basketActive && triggerTarget <= 0.0)
{
basketLockedTarget = dynamicTarget;
triggerTarget = dynamicTarget;
GlobalVariableSet(GV_BasketTargetName(), basketLockedTarget);
}
if(!targetCloseActive && virtualCapital >= triggerTarget)
{
ActivateTargetClose(triggerTarget);
string eventName = basketActive ? “BASKET_TARGET_TRIGGER” : “SINGLE_CAPITAL_TARGET_TRIGGER”;
LogTradeEvent(TimeToString(TimeCurrent(), TIME_DATE|TIME_SECONDS) + “;” +
(string)Magic + “;” + eventName + “;” + _Symbol +
“;0;0;0;0;” + TradeComment +
“;OpenPositions=” + (string)openCount +
“,VirtualCap=” + DoubleToString(virtualCapital, 2) +
“,Target=” + DoubleToString(triggerTarget, 2) + “;0;0”);
UpdateDisplay();
}
if(!targetCloseActive)
return;
int closedCount = 0;
for(int i = PositionsTotal() – 1; i >= 0; i–)
{
if(posInfo.SelectByIndex(i) && posInfo.Magic() == Magic && posInfo.Symbol() == _Symbol)
{
ulong ticket = posInfo.Ticket();
if(ClosePositionVerified(ticket))
{
string gvT = GV_TargetName(ticket);
string gvN = GV_NextName(ticket);
if(GlobalVariableCheck(gvT)) GlobalVariableDel(gvT);
if(GlobalVariableCheck(gvN)) GlobalVariableDel(gvN);
closedCount++;
}
}
}
int remaining = GetOpenPositionsCount();
if(remaining == 0)
{
bool wasBasket = basketActive;
double completedTarget = targetCloseLockedTarget;
DeactivateTargetClose();
if(basketActive)
DeactivateBasket();
LogTradeEvent(TimeToString(TimeCurrent(), TIME_DATE|TIME_SECONDS) + “;” +
(string)Magic + “;CAPITAL_TARGET_CLOSE_SUCCESS;” + _Symbol +
“;0;0;0;0;” + TradeComment +
“;AllPositionsClosed;Mode=” + (wasBasket ? “BASKET” : “SINGLE”) +
“,LockedTarget=” + DoubleToString(completedTarget, 2) +
“,VirtualCapAtTrigger=” + DoubleToString(virtualCapital, 2) + “;0;0”);
UpdateDisplay();
}
else if(closedCount > 0)
{
LogTradeEvent(TimeToString(TimeCurrent(), TIME_DATE|TIME_SECONDS) + “;” +
(string)Magic + “;CAPITAL_TARGET_CLOSE_PROGRESS;” + _Symbol +
“;0;0;0;0;” + TradeComment +
“;ClosedNow=” + (string)closedCount +
“,Remaining=” + (string)remaining +
“,Mode=” + (basketActive ? “BASKET” : “SINGLE”) +
“,LockedTarget=” + DoubleToString(targetCloseLockedTarget, 2) + “;0;0”);
UpdateDisplay();
}
}
//+——————————————————————+
//| Init / Deinit |
//+——————————————————————+
int OnInit()
{
if(InitialVirtualCapital <= 0.0 ||
BasketTPPercent <= 0.0 ||
FixedLotSize <= 0.0 ||
EntryWindowSeconds <= 0 ||
RetryIntervalMs < 0 ||
MaxSpreadPoints < 0 ||
MaxSlippagePoints < 0)
{
Print(“D308: Ungültige Eingabeparameter. EA wird nicht gestartet.”);
return(INIT_PARAMETERS_INCORRECT);
}
trade.SetExpertMagicNumber(Magic);
trade.SetDeviationInPoints(MaxSlippagePoints);
lblStart = “D113_Lbl_Start_” + (string)Magic;
lblCurr = “D113_Lbl_Curr_” + (string)Magic;
lblTarget = “D113_Lbl_Target_” + (string)Magic;
logFileName = “D113_Log_” + _Symbol + “_” + SanitizeFileName(TradeComment) + “_” + (string)Magic + “.csv”;
string gvEntry = GV_LastEntrySlotName();
if(GlobalVariableCheck(gvEntry))
lastProcessedEntrySlot = (datetime)GlobalVariableGet(gvEntry);
string gvBasket = GV_BasketTargetName();
if(GlobalVariableCheck(gvBasket))
{
basketLockedTarget = GlobalVariableGet(gvBasket);
basketActive = (basketLockedTarget > 0.0);
}
string gvClose = GV_TargetCloseName();
if(GlobalVariableCheck(gvClose))
{
targetCloseLockedTarget = GlobalVariableGet(gvClose);
targetCloseActive = (targetCloseLockedTarget > 0.0);
}
int openCountInit = GetOpenPositionsCount();
if(!basketActive && openCountInit >= 2)
{
double rp = 0.0, fp = 0.0;
datetime sd = 0;
CalculateCapitalMetrics(rp, fp, sd);
double secured = InitialVirtualCapital + rp;
ActivateBasket(CalculateNextTarget(secured));
}
if(openCountInit == 0)
{
if(targetCloseActive) DeactivateTargetClose();
if(basketActive) DeactivateBasket();
}
RegisterAllOpenPositionsTargets();
UpdateDisplay();
return(INIT_SUCCEEDED);
}
void OnDeinit(const int reason)
{
ObjectDelete(0, lblStart);
ObjectDelete(0, lblCurr);
ObjectDelete(0, lblTarget);
}
//+——————————————————————+
//| OnTick Logic |
//+——————————————————————+
void OnTick()
{
CheckAndCloseBasket();
if(targetCloseActive)
return;
CheckAndClosePositions();
MqlDateTime t;
datetime evalTime = TimeCurrent();
if(UseBerlinOffset)
evalTime += BerlinOffsetHours * 3600;
TimeToStruct(evalTime, t);
if(IsHoliday(t)) return;
if(IsMonthBlocked(t.mon)) return;
if(!IsWeekdayAllowed(t.day_of_week)) return;
MqlDateTime slot = t;
slot.hour = InpEntryHour;
slot.min = InpEntryMinute;
slot.sec = 0;
datetime slotStart = StructToTime(slot);
long diff = (long)evalTime – (long)slotStart;
if(diff >= 0 && diff <= EntryWindowSeconds && lastProcessedEntrySlot != slotStart)
{
int shift = 1;
MqlDateTime dtPrev;
TimeToStruct(iTime(_Symbol, PERIOD_D1, 1), dtPrev);
if(dtPrev.day_of_week == 0)
shift = 2;
if(iLow(_Symbol, PERIOD_D1, 0) < iLow(_Symbol, PERIOD_D1, shift) ||
iHigh(_Symbol, PERIOD_D1, 0) < iHigh(_Symbol, PERIOD_D1, shift))
{
if(CumulateOrders || !PositionSelectByMagic(Magic))
{
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double sl = 0.0;
double tp = 0.0;
if(StopLossPercent > 0.0)
sl = NormalizeDouble(ask * (1.0 – StopLossPercent / 100.0), _Digits);
if(TakeProfitPercent > 0.0)
tp = NormalizeDouble(ask * (1.0 + TakeProfitPercent / 100.0), _Digits);
if(TryBuyWithFallback(FixedLotSize, ask, sl, tp, TradeComment))
{
RegisterAllOpenPositionsTargets();
MarkEntrySlotProcessed(slotStart);
UpdateDisplay();
}
}
}
else
{
MarkEntrySlotProcessed(slotStart);
}
}
}
bool PositionSelectByMagic(long mg)
{
for(int i = PositionsTotal() – 1; i >= 0; i–)
{
if(posInfo.SelectByIndex(i) && posInfo.Magic() == mg && posInfo.Symbol() == _Symbol)
return true;
}
return false;
}
A quick clarification: The description above (fixed euro target per basket) is the governing factor, not the capital ladder in the MT5 code. Additionally, an individual trade initially retains its own take-profit (TP) at +2.5%. Once a second position is added, this individual TP is cancelled, and all positions share a common TP level.
If you like, i ll post the modified mt5-code here
Good morning. Here is a suggestion.
In the last few lines of code, you can see the variables that are set to appear in the bottom section. You can use // to hide them, or remove it to display them.
// ============================================================
// DE40 basket: fixed-size entries, common take-profit price
// "Capital target fixed, TP price dynamic"
// ProBacktest / ProOrder - long only - intraday chart
// ============================================================
DEFPARAM CumulateOrders = True
// --- Capital and basket ---
virtualCapital = 1000 // reference capital, in the account currency
basketTPPercent = 5 // basket target = virtualCapital x basketTPPercent / 100 (50 with these values)
positionSize = 1 // contracts per entry, never increased
cumulateEntries = 1 // 1 = add a position on every signal, 0 = only when flat
// --- Standalone trade (only while a single position is open) ---
takeProfitPercent = 2.5 // own TP of the single position, 0 = off
stopLossPercent = 0 // own SL of the single position, 0 = off
// --- Entry slot (chart time zone) ---
// The bar that CLOSES at entryTime sends the order, which is filled
// at the open of the next bar, i.e. at entryTime.
entryTime = 090000
tradeMon = 0
tradeTue = 1
tradeWed = 0
tradeThu = 0
tradeFri = 0
blockMonth1 = 0 // months without new entries (1-12, 0 = none)
blockMonth2 = 0
blockMonth3 = 0
blockMonth4 = 0
// --- Time exit of the standalone trade ---
// From the first exit slot after the entry, the single position is
// closed as soon as it is in profit. It never applies to a basket.
exitEnabled = 0
exitTime = 220000 // must fall inside the instrument's session
exitMon = 0
exitTue = 1
exitWed = 0
exitThu = 0
exitFri = 1
// --- Costs and capital requirement ---
financingRate = 0 // annual overnight financing on the notional, in % (0 = off)
marginPercent = 5 // margin as % of the notional (5 = leverage 20:1)
ONCE lastSlotDate = 0
ONCE openFin = 0
ONCE closedFin = 0
ONCE cycleDays = 0
ONCE timeExitArmed = 0
ONCE entryDate = 0
ONCE entryClock = 0
ONCE maxPositions = 0
ONCE maxFloatLoss = 0
ONCE maxCycleDays = 0
ONCE maxNotional = 0
ONCE maxCapNeeded = 0
basketTarget = virtualCapital * basketTPPercent / 100
moneyPerPoint = pointvalue / pipsize // money for a 1.0 price move, 1 contract
// ------------------------------------------------------------
// Position carried over from the previous session
// ------------------------------------------------------------
IF IntradayBarIndex = 0 AND OnMarket[1] THEN
cycleDays = cycleDays + 1
IF financingRate > 0 THEN
nights = 1
IF OpenDayOfWeek = 1 THEN
nights = 3 // weekend
ENDIF
openFin = openFin + ABS(COUNTOFPOSITION[1]) * close[1] * moneyPerPoint * financingRate / 100 / 365 * nights
ENDIF
ENDIF
// ------------------------------------------------------------
// Position state and statistics
// ------------------------------------------------------------
posQty = ABS(COUNTOFPOSITION)
nPos = ROUND(posQty / positionSize)
avgPrice = POSITIONPRICE
IF OnMarket THEN
floatNow = (close - avgPrice) * posQty * moneyPerPoint - openFin
floatWorst = (low - avgPrice) * posQty * moneyPerPoint - openFin
notional = posQty * close * moneyPerPoint
ELSE
floatNow = 0
floatWorst = 0
notional = 0
ENDIF
maxPositions = MAX(maxPositions, nPos)
maxFloatLoss = MIN(maxFloatLoss, floatWorst)
maxCycleDays = MAX(maxCycleDays, cycleDays)
maxNotional = MAX(maxNotional, notional)
maxCapNeeded = MAX(maxCapNeeded, notional * marginPercent / 100 - MIN(floatWorst, 0))
// A closed cycle books its financing cost and resets its state
IF NOT OnMarket THEN
closedFin = closedFin + openFin
openFin = 0
cycleDays = 0
timeExitArmed = 0
ENDIF
// Moment the first position of the cycle was filled
IF OnMarket AND NOT OnMarket[1] THEN
entryDate = Date
entryClock = opentime
ENDIF
virtualEquity = virtualCapital + STRATEGYPROFIT - closedFin + floatNow
// ------------------------------------------------------------
// Time exit of the standalone trade: arm it at the first exit
// slot that comes after the entry
// ------------------------------------------------------------
IF exitEnabled = 1 AND nPos = 1 AND timeExitArmed = 0 THEN
exitDayOK = (OpenDayOfWeek = 1 AND exitMon = 1) OR (OpenDayOfWeek = 2 AND exitTue = 1) OR (OpenDayOfWeek = 3 AND exitWed = 1) OR (OpenDayOfWeek = 4 AND exitThu = 1) OR (OpenDayOfWeek = 5 AND exitFri = 1)
IF exitDayOK AND time >= exitTime AND (Date > entryDate OR entryClock < exitTime) THEN
timeExitArmed = 1
ENDIF
ENDIF
// ------------------------------------------------------------
// Exits. Pending orders only live for one bar, so they are
// sent again at every bar close.
// ------------------------------------------------------------
tpPrice = 0
IF OnMarket THEN
IF nPos >= 2 THEN
// Basket: one common TP so that all positions together earn
// basketTarget. avgPrice and posQty change with every new entry,
// so the TP price moves while the money target stays fixed.
tpPrice = avgPrice + (basketTarget + openFin) / (posQty * moneyPerPoint)
ELSE
// Standalone trade: its own TP, optional time exit and SL
IF takeProfitPercent > 0 THEN
tpPrice = avgPrice * (1 + takeProfitPercent / 100)
ENDIF
IF timeExitArmed = 1 THEN
// one point above break-even (financing included)
bePrice = avgPrice + openFin / (posQty * moneyPerPoint) + pipsize
IF tpPrice = 0 OR bePrice < tpPrice THEN
tpPrice = bePrice
ENDIF
ENDIF
IF stopLossPercent > 0 THEN
SELL AT avgPrice * (1 - stopLossPercent / 100) STOP
ENDIF
ENDIF
IF tpPrice > 0 THEN
SELL AT tpPrice LIMIT
ENDIF
ENDIF
// ------------------------------------------------------------
// Entry: once per day, on the first bar that closes at or after
// entryTime
// ------------------------------------------------------------
IF time >= entryTime AND Date <> lastSlotDate THEN
lastSlotDate = Date
dayOK = (OpenDayOfWeek = 1 AND tradeMon = 1) OR (OpenDayOfWeek = 2 AND tradeTue = 1) OR (OpenDayOfWeek = 3 AND tradeWed = 1) OR (OpenDayOfWeek = 4 AND tradeThu = 1) OR (OpenDayOfWeek = 5 AND tradeFri = 1)
mmdd = OpenMonth * 100 + OpenDay
isHoliday = (mmdd = 101) OR (mmdd = 501) OR (mmdd = 1003) OR (mmdd = 1224) OR (mmdd = 1225) OR (mmdd = 1226) OR (mmdd = 1231)
monthOK = OpenMonth <> blockMonth1 AND OpenMonth <> blockMonth2 AND OpenMonth <> blockMonth3 AND OpenMonth <> blockMonth4
// Signal: today has not traded above yesterday's high so far,
// or it has already traded below yesterday's low
entrySetup = DLow(0) < DLow(1) OR DHigh(0) < DHigh(1)
IF dayOK AND monthOK AND NOT isHoliday AND entrySetup THEN
IF cumulateEntries = 1 OR NOT OnMarket THEN
BUY positionSize CONTRACTS AT MARKET
ENDIF
ENDIF
ENDIF
// ------------------------------------------------------------
// Chart. The price lines follow the close while there is nothing
// to show. The max... series only grow, so their value on the
// last bar is the maximum over the whole backtest.
// ------------------------------------------------------------
IF tpPrice > 0 THEN
tpLine = tpPrice
ELSE
tpLine = close
ENDIF
IF OnMarket THEN
avgLine = avgPrice
ELSE
avgLine = close
ENDIF
GRAPHONPRICE tpLine AS "take-profit price" coloured("green")
GRAPHONPRICE avgLine AS "average entry price"coloured("blue")
//GRAPH virtualEquity AS "virtual equity"
//GRAPH nPos AS "open positions"
//GRAPH maxPositions AS "max open positions"
GRAPH maxFloatLoss AS "max floating loss"
//GRAPH maxCycleDays AS "longest cycle (trading days)"
//GRAPH maxNotional AS "max exposure (notional)"
GRAPH maxCapNeeded AS "max capital needed (margin + floating loss)"
** I’ve moved the thread to the English forum.
Due to a slight weakness, I’m back here for the first time today since I posted my enquiry. Just one second after seeing your reply – without having analysed it in detail – I’d like to thank you from the bottom of my heart.
Describe what this topic is trying to build, in plain English, and ProRealAI writes the ProRealTime™ indicator, screener or system for you.
DE40 : Capital Management with a Dynamic Basket TP – Implementation
This topic contains 3 replies,
has 2 voices, and was last updated by JohnScher
17 hours, 45 minutes ago.
| Forum: | ProOrder: Automated Strategies & Backtesting |
| Language: | English |
| Started: | 09/29/2026 |
| Status: | Active |
| Attachments: | No files |
The information collected on this form is stored in a computer file by ProRealCode to create and access your ProRealCode profile. This data is kept in a secure database for the duration of the member's membership. They will be kept as long as you use our services and will be automatically deleted after 3 years of inactivity. Your personal data is used to create your private profile on ProRealCode. This data is maintained by SAS ProRealCode, 407 rue Freycinet, 59151 Arleux, France. If you subscribe to our newsletters, your email address is provided to our service provider "MailChimp" located in the United States, with whom we have signed a confidentiality agreement. This company is also compliant with the EU/Swiss Privacy Shield, and the GDPR. For any request for correction or deletion concerning your data, you can directly contact the ProRealCode team by email at privacy@prorealcode.com If you would like to lodge a complaint regarding the use of your personal data, you can contact your data protection supervisory authority.