DE40 : Capital Management with a Dynamic Basket TP – Implementation

Viewing 4 posts - 1 through 4 (of 4 total)
  • Author
    Posts
  • #265385 quote
    JohnScherJohnScher
    Participant
    Veteran

    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:

    • every new position has the same fixed size
    • as long as only one position is open, it is treated as a standalone trade
    • as soon as a second position is added, a basket is created
    • from that moment on, a fixed capital profit target is defined for the whole basket
    • example: EUR 1,000 virtual capital with a 5% basket target = EUR 50 target profit
    • the basket profit target remains fixed
    • the common TP price is recalculated after every new entry
    • all open basket positions share the same common target price
    • if another position is added, the TP of the whole basket changes accordingly
    • the complete basket is closed when the predefined capital target is reached
    • position size is never increased

    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:

    • maximum floating drawdown
    • maximum number of simultaneously open positions
    • longest basket duration
    • maximum exposure / capital requirement
    • behaviour during long bear markets
    • behaviour in different market regimes

    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;

    }

    #265386 quote
    JohnScherJohnScher
    Participant
    Veteran

    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

    #265387 quote
    Iván GonzálezIván González
    Moderator
    Legend

    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.

    JohnScher thanked this post
    #265587 quote
    JohnScherJohnScher
    Participant
    Veteran

    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.

Viewing 4 posts - 1 through 4 (of 4 total)
  • You must be logged in to reply to this topic.
ProRealAI ProRealAI New

Stuck on this ProBuilder code?

Describe what this topic is trying to build, in plain English, and ProRealAI writes the ProRealTime™ indicator, screener or system for you.

Available in 7 languages
Try ProRealAI

DE40 : Capital Management with a Dynamic Basket TP – Implementation


ProOrder: Automated Strategies & Backtesting

New Reply
Author
author-avatar
JohnScher @johnscher Participant
Summary

This topic contains 3 replies,
has 2 voices, and was last updated by JohnScherJohnScher
17 hours, 45 minutes ago.

Topic Details
Forum: ProOrder: Automated Strategies & Backtesting
Language: English
Started: 09/29/2026
Status: Active
Attachments: No files
ProRealCode ProRealCode
Loading...