How to code risk-based lot sizes for multiple orders placed within a price range

 

Hey guys, I am a bit stuck and need help.


I am currently coding a semi-automatic MT4 EA for order placement and management. The idea is, that I insert these variables:

1) Order direction (buy/sell)

2) Start price (e.g. 2800)

3) End price (e.g. 2798)

4) SL (e.g. 2802)

5) TP1 (2796)

6) TP2 (2794)

7) TP3 (2792)

8) Risk percentage (e.g. 3%)

9) Number of orders (e.g. 30)

For the sake of this example, lets assume 100k USD account, so the max loss/risked amount is 3k USD. The max loss (and therefore lot size per order) shall be calculated based on actual account balance. Leverage and any other variable shall be fetched automatically per instrument (not per account). The EA must work on any broker and any instrument and calculate risk/lot size correctly based on the inputs stated above.


Now there are two scenarios:

1) Only Start price is set, so that all 30 orders shall be set at 2800, all with same SL and various TP1/2/3. All the orders shall be evenly sized (all the same size), respecting the max loss of 3k USD in total.

2) Both Start and End price are set, so that the EA shall distribute 30 orders evenly in the range between 2800 and 2798. All the orders shall be evenly sized (all the same size), respecting the max loss of 3k USD in total.


Here is the problem.

I have created the following code, but after attaching it to various instruments (XAUUSD, EURUSD, BTCUSD and USDJPY so far), the EA calculates incorrect lot sizes. I tried to iterate the code using AI in order to find the problem, but with no success. Does anyone of you have idea what am I doing wrong? Any ideas are highly appreciated.


//+------------------------------------------------------------------+
//| Struct: Stores Instrument-Specific Settings                     |
//+------------------------------------------------------------------+
struct InstrumentSettings {
    double riskPercentage;
    double minLot;
    double maxLot;
    double lotStep;
    double pipValue;
    double leverage;
};

//+------------------------------------------------------------------+
//| Get Instrument Settings Based on Symbol                         |
//+------------------------------------------------------------------+
InstrumentSettings GetInstrumentSettings(string symbol) {
    InstrumentSettings settings;

    settings.riskPercentage = RiskPercentage; // Set by user
    settings.minLot = MarketInfo(symbol, MODE_MINLOT);
    settings.maxLot = MarketInfo(symbol, MODE_MAXLOT);
    settings.lotStep = MarketInfo(symbol, MODE_LOTSTEP);
    settings.pipValue = MarketInfo(symbol, MODE_TICKVALUE);
    
    double contractSize = MarketInfo(symbol, MODE_LOTSIZE);
    double marginRequiredPerLot = MarketInfo(symbol, MODE_MARGINREQUIRED);
    double marketPrice = MarketInfo(symbol, MODE_BID);

    // Calculate Instrument-Specific Leverage
    if (marginRequiredPerLot > 0 && contractSize > 0) {
        settings.leverage = (contractSize * marketPrice) / marginRequiredPerLot;
    } else {
        settings.leverage = AccountLeverage();  // Fallback in case margin info is unavailable
    }

    // Forex Majors & Minors (10.0 pip value per lot)
    if (symbol == "EURUSD" || symbol == "GBPUSD" || symbol == "USDCHF" || symbol == "USDCAD" ||
        symbol == "AUDUSD" || symbol == "NZDUSD" || symbol == "EURGBP" || symbol == "EURCHF" ||
        symbol == "EURCAD" || symbol == "EURAUD" || symbol == "EURNZD" || symbol == "GBPCHF" ||
        symbol == "GBPCAD" || symbol == "GBPAUD" || symbol == "GBPNZD" || symbol == "AUDCHF" ||
        symbol == "AUDCAD" || symbol == "AUDNZD" || symbol == "NZDCHF" || symbol == "NZDCAD" ||
        symbol == "CADCHF") {
        settings.pipValue = 10.0;
    }

    // JPY Pairs (100.0 pip value per lot)
    if (symbol == "USDJPY" || symbol == "EURJPY" || symbol == "GBPJPY" || symbol == "AUDJPY" ||
        symbol == "NZDJPY" || symbol == "CADJPY" || symbol == "CHFJPY") {
        settings.pipValue = 100.0;
    }

    // Exotic Pairs (Lower risk, 10.0 pip value per lot)
    if (symbol == "USDTRY" || symbol == "EURTRY" || symbol == "USDZAR" || symbol == "USDHKD" ||
        symbol == "USDSGD" || symbol == "USDMXN" || symbol == "USDRUB" || symbol == "USDNOK" ||
        symbol == "USDSEK" || symbol == "USDDKK" || symbol == "EURSEK" || symbol == "EURDKK" ||
        symbol == "EURNOK" || symbol == "EURPLN" || symbol == "USDPLN") {
        settings.pipValue = 10.0;
        settings.riskPercentage = 1.5; // Lower risk for exotic pairs
    }

    // Stock Indices (Pip Value: 1.0 per lot)
    if (symbol == "US30" || symbol == "SPX500" || symbol == "NAS100" || symbol == "UK100" ||
        symbol == "DE40" || symbol == "FR40" || symbol == "JP225" || symbol == "ES35" ||
        symbol == "AU200" || symbol == "HK50" || symbol == "CHINA50") {
        settings.pipValue = 1.0;
        settings.riskPercentage = 2.5;
    }

    // Cryptocurrencies (Pip Value: 1.0 per lot, Lower risk due to volatility)
    if (symbol == "BTCUSD" || symbol == "ETHUSD" || symbol == "LTCUSD" || symbol == "XRPUSD" ||
        symbol == "ADAUSD" || symbol == "DOTUSD" || symbol == "DOGEUSD" || symbol == "BNBUSD" ||
        symbol == "SOLUSD" || symbol == "MATICUSD" || symbol == "SHIBUSD") {
        settings.pipValue = 1.0;
        settings.riskPercentage = 1.0; // Crypto is highly volatile
    }

    // Metals (Gold, Silver, Platinum, Palladium)
    if (symbol == "XAUUSD") { settings.pipValue = 1.0; settings.riskPercentage = 3.0; }
    if (symbol == "XAGUSD") { settings.pipValue = 50.0; settings.riskPercentage = 3.0; }
    if (symbol == "XPTUSD" || symbol == "XPDUSD") { settings.pipValue = 10.0; settings.riskPercentage = 3.0; }

    // Commodities (Oil, Gas)
    if (symbol == "WTI" || symbol == "BRENT" || symbol == "NGAS") {
        settings.pipValue = 1.0;
        settings.riskPercentage = 2.0;
    }

    return settings;
}

//+------------------------------------------------------------------+
//| Calculate Lot Size based on Risk Percentage                     |
//+------------------------------------------------------------------+
double CalculateLotSize(double entryPrice, double stopLoss, int numOrders) {
    if (numOrders < 1 || RiskPercentage <= 0 || stopLoss <= 0) return 0;

    InstrumentSettings settings = GetInstrumentSettings(Symbol());

    // Calculate total risk per trade
    double totalRiskAmount = AccountBalance() * (settings.riskPercentage / 100.0);

    // Divide risk among all orders
    double riskPerOrder = totalRiskAmount / numOrders;

    // Convert stop loss distance into pips
    double slDistancePips = MathAbs(entryPrice - stopLoss) / MarketInfo(Symbol(), MODE_POINT);  

    if (slDistancePips <= 0) return 0;

    // Ensure pip value is correctly fetched per instrument
    double pipValue = settings.pipValue;
    if (pipValue <= 0) {
        pipValue = MarketInfo(Symbol(), MODE_TICKVALUE) / MarketInfo(Symbol(), MODE_POINT);  // Fallback
    }

    // Scale pip value correction for JPY pairs
    if (StringFind(Symbol(), "JPY") > -1) {
        pipValue *= 10.0;  // JPY pairs have pip value 10x greater
    }

    // Corrected lot size formula
    double lotSize = riskPerOrder / (slDistancePips * pipValue);

    // Normalize lot size based on broker constraints
    lotSize = NormalizeLotSize(lotSize, settings.minLot, settings.maxLot, settings.lotStep);

    // Debugging Output
    Print("=== DEBUG: CalculateLotSize ===");
    Print("Symbol: ", Symbol());
    Print("Account Balance: ", AccountBalance());
    Print("Total Risk Allowed (", settings.riskPercentage, "%): ", totalRiskAmount);
    Print("Number of Trades: ", numOrders);
    Print("Risk Per Order: ", riskPerOrder);
    Print("Stop Loss Distance (Pips): ", slDistancePips);
    Print("Pip Value: ", pipValue);
    Print("Final Lot Size: ", lotSize);
    Print("==============================");

    return lotSize;
}

//+------------------------------------------------------------------+
//| Normalize Lot Size to Broker Constraints                        |
//+------------------------------------------------------------------+
double NormalizeLotSize(double lotSize, double minLot, double maxLot, double lotStep) {
    lotSize = MathMin(lotSize, maxLot);  // Ensure within max limit
    lotSize = MathMax(lotSize, minLot);  // Ensure above min limit
    lotSize = NormalizeDouble(lotSize - fmod(lotSize, lotStep), 2);  // Adjust to lot step
    return lotSize;
}

double CalculateStaticLotSize(double stopLoss, int numOrders) {
    if (stopLoss <= 0 || numOrders < 1) return 0;

    InstrumentSettings settings = GetInstrumentSettings(Symbol());

    double totalRiskAmount = AccountBalance() * settings.riskPercentage / 100.0;
    double pipValue = settings.pipValue;
    double stopLossDistance = MathAbs(StartLevel - stopLoss) * 100;  // Convert price difference to pips

    if (stopLossDistance <= 0) return 0;

    // Distribute max loss equally across all orders
    double lotSize = totalRiskAmount / (numOrders * stopLossDistance * pipValue);

    // Ensure the lot size meets broker constraints
    lotSize = NormalizeLotSize(lotSize, settings.minLot, settings.maxLot, settings.lotStep);

    return lotSize;
}
 
Your topic has been moved to the section: MQL4 and MetaTrader 4
Please consider which section is most appropriate — https://www.mql5.com/en/forum/172166/page6#comment_49114893