2025-05-30 16:35:54 +02:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| MoneyFixedLot.mqh |
|
|
|
|
|
//| Copyright 2000-2023, MetaQuotes Ltd. |
|
|
|
|
|
//| https://www.mql5.com |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
#include "..\Expert\ExpertMoneyCustom.mqh"
|
fix: four risk-layer holes a funded account would eventually find
1. The expectancy stop was stone dead at shipped defaults. Its only feed -
RecordTradeResult inside CTradeJournalManager::Update() - ran solely under
UseDatabaseRanking, which ships false, so the da54639 halt was armed
(ExpectancyMinTrades=40) and never received a single closed trade. A risk
rule must not be a side effect of an analytics toggle: the journal gains
InitTrackingOnly(), Update() runs unconditionally from OnTick and skips
only the DB insert when no DB was initialized.
2. Below-minimum lots were silently bumped UP to SYMBOL_VOLUME_MIN by
TCNormalizeVolume - correct for a user-entered fixed lot, but in the
risk-sizing path it turned a budget-capped 0.05 into 0.10 on min-0.10/
step-0.01 symbols: double the intended risk, after CapRiskAmount already
clamped, exactly the routine-stop-out-breaches-the-daily-limit scenario
the budget exists to close. CMoneyRiskBase now refuses the trade when the
risk-derived lot is below the broker minimum.
3. All trading was async fire-and-forget (SetAsyncMode(true)) with no
OnTradeTransaction handler and no retry: server retcodes were never
observed. Fail-safe for entries, not for closes - a silently rejected
close rode the position until the next bar (or next day for the timed
close window). Now synchronous, matching the risk-budget flatten's own
already-synchronous CTrade; on an H1 EA the latency is irrelevant.
4. FIXED_LOT bypassed the budget entirely (no CapRiskAmount, no
OpenRiskAtStops) - pre-halt it could commit more than the remaining daily
allowance. A fixed lot cannot be scaled, so the rule is binary: its
loss-to-stop fits the remaining allowance whole or the trade is refused;
unpriceable risk (no SL) is refused while the budget is enabled.
Compile: 0 errors, 0 warnings.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 18:14:26 -04:00
|
|
|
#include "..\Variables\RiskBudget.mqh"
|
2025-05-30 16:35:54 +02:00
|
|
|
// wizard description start
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Description of the class |
|
|
|
|
|
//| Title=Trading with fixed trade volume |
|
|
|
|
|
//| Type=Money |
|
|
|
|
|
//| Name=FixLot |
|
|
|
|
|
//| Class=CMoneyFixedLot |
|
|
|
|
|
//| Page= |
|
|
|
|
|
//| Parameter=Percent,double,10.0,Percent |
|
|
|
|
|
//| Parameter=Lots,double,0.1,Fixed volume |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
// wizard description end
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Class CMoneyFixedLot. |
|
|
|
|
|
//| Purpose: Class of money management with fixed lot. |
|
|
|
|
|
//| Derives from class CExpertMoney. |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
class CMoneyFixedLot : public CExpertMoneyCustom
|
|
|
|
|
{
|
|
|
|
|
protected:
|
|
|
|
|
//--- input parameters
|
|
|
|
|
double m_lots;
|
2026-07-26 23:08:32 -04:00
|
|
|
//--- declared ahead of the inline CheckOpenLong/Short bodies below that call it
|
fix: four risk-layer holes a funded account would eventually find
1. The expectancy stop was stone dead at shipped defaults. Its only feed -
RecordTradeResult inside CTradeJournalManager::Update() - ran solely under
UseDatabaseRanking, which ships false, so the da54639 halt was armed
(ExpectancyMinTrades=40) and never received a single closed trade. A risk
rule must not be a side effect of an analytics toggle: the journal gains
InitTrackingOnly(), Update() runs unconditionally from OnTick and skips
only the DB insert when no DB was initialized.
2. Below-minimum lots were silently bumped UP to SYMBOL_VOLUME_MIN by
TCNormalizeVolume - correct for a user-entered fixed lot, but in the
risk-sizing path it turned a budget-capped 0.05 into 0.10 on min-0.10/
step-0.01 symbols: double the intended risk, after CapRiskAmount already
clamped, exactly the routine-stop-out-breaches-the-daily-limit scenario
the budget exists to close. CMoneyRiskBase now refuses the trade when the
risk-derived lot is below the broker minimum.
3. All trading was async fire-and-forget (SetAsyncMode(true)) with no
OnTradeTransaction handler and no retry: server retcodes were never
observed. Fail-safe for entries, not for closes - a silently rejected
close rode the position until the next bar (or next day for the timed
close window). Now synchronous, matching the risk-budget flatten's own
already-synchronous CTrade; on an H1 EA the latency is irrelevant.
4. FIXED_LOT bypassed the budget entirely (no CapRiskAmount, no
OpenRiskAtStops) - pre-halt it could commit more than the remaining daily
allowance. A fixed lot cannot be scaled, so the rule is binary: its
loss-to-stop fits the remaining allowance whole or the trade is refused;
unpriceable risk (no SL) is refused while the budget is enabled.
Compile: 0 errors, 0 warnings.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 18:14:26 -04:00
|
|
|
double CheckedLot(ENUM_ORDER_TYPE type, double price, double sl);
|
2025-05-30 16:35:54 +02:00
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
//---
|
|
|
|
|
void Lots(double lots) { m_lots=lots; }
|
|
|
|
|
virtual bool ValidationSettings(void);
|
2026-07-26 23:08:32 -04:00
|
|
|
//--- The fixed lot from the input is a REQUEST, not a guarantee: it still has to clear the
|
|
|
|
|
//--- broker's volume grid, the per-symbol SYMBOL_VOLUME_LIMIT and free margin before it can be
|
|
|
|
|
//--- sent (article 2555 #2/#3/#5). Returning m_lots raw - as this class used to - meant a
|
|
|
|
|
//--- Money_FixLot_Lots that a small/loaded account cannot margin was handed straight to
|
|
|
|
|
//--- OrderSend() and rejected server-side with "not enough money" every single signal.
|
fix: four risk-layer holes a funded account would eventually find
1. The expectancy stop was stone dead at shipped defaults. Its only feed -
RecordTradeResult inside CTradeJournalManager::Update() - ran solely under
UseDatabaseRanking, which ships false, so the da54639 halt was armed
(ExpectancyMinTrades=40) and never received a single closed trade. A risk
rule must not be a side effect of an analytics toggle: the journal gains
InitTrackingOnly(), Update() runs unconditionally from OnTick and skips
only the DB insert when no DB was initialized.
2. Below-minimum lots were silently bumped UP to SYMBOL_VOLUME_MIN by
TCNormalizeVolume - correct for a user-entered fixed lot, but in the
risk-sizing path it turned a budget-capped 0.05 into 0.10 on min-0.10/
step-0.01 symbols: double the intended risk, after CapRiskAmount already
clamped, exactly the routine-stop-out-breaches-the-daily-limit scenario
the budget exists to close. CMoneyRiskBase now refuses the trade when the
risk-derived lot is below the broker minimum.
3. All trading was async fire-and-forget (SetAsyncMode(true)) with no
OnTradeTransaction handler and no retry: server retcodes were never
observed. Fail-safe for entries, not for closes - a silently rejected
close rode the position until the next bar (or next day for the timed
close window). Now synchronous, matching the risk-budget flatten's own
already-synchronous CTrade; on an H1 EA the latency is irrelevant.
4. FIXED_LOT bypassed the budget entirely (no CapRiskAmount, no
OpenRiskAtStops) - pre-halt it could commit more than the remaining daily
allowance. A fixed lot cannot be scaled, so the rule is binary: its
loss-to-stop fits the remaining allowance whole or the trade is refused;
unpriceable risk (no SL) is refused while the budget is enabled.
Compile: 0 errors, 0 warnings.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 18:14:26 -04:00
|
|
|
virtual double CheckOpenLong(double price,double sl) { return(CheckedLot(ORDER_TYPE_BUY, price, sl)); }
|
|
|
|
|
virtual double CheckOpenShort(double price,double sl) { return(CheckedLot(ORDER_TYPE_SELL, price, sl)); }
|
2025-05-30 16:35:54 +02:00
|
|
|
};
|
|
|
|
|
//+------------------------------------------------------------------+
|
2026-07-26 23:08:32 -04:00
|
|
|
//| Run the configured fixed lot through the shared pre-OrderSend |
|
|
|
|
|
//| volume gate; 0.0 means "no legal volume right now", which |
|
|
|
|
|
//| CExpert::OpenLong/OpenShort treat as "do not trade this tick". |
|
|
|
|
|
//+------------------------------------------------------------------+
|
fix: four risk-layer holes a funded account would eventually find
1. The expectancy stop was stone dead at shipped defaults. Its only feed -
RecordTradeResult inside CTradeJournalManager::Update() - ran solely under
UseDatabaseRanking, which ships false, so the da54639 halt was armed
(ExpectancyMinTrades=40) and never received a single closed trade. A risk
rule must not be a side effect of an analytics toggle: the journal gains
InitTrackingOnly(), Update() runs unconditionally from OnTick and skips
only the DB insert when no DB was initialized.
2. Below-minimum lots were silently bumped UP to SYMBOL_VOLUME_MIN by
TCNormalizeVolume - correct for a user-entered fixed lot, but in the
risk-sizing path it turned a budget-capped 0.05 into 0.10 on min-0.10/
step-0.01 symbols: double the intended risk, after CapRiskAmount already
clamped, exactly the routine-stop-out-breaches-the-daily-limit scenario
the budget exists to close. CMoneyRiskBase now refuses the trade when the
risk-derived lot is below the broker minimum.
3. All trading was async fire-and-forget (SetAsyncMode(true)) with no
OnTradeTransaction handler and no retry: server retcodes were never
observed. Fail-safe for entries, not for closes - a silently rejected
close rode the position until the next bar (or next day for the timed
close window). Now synchronous, matching the risk-budget flatten's own
already-synchronous CTrade; on an H1 EA the latency is irrelevant.
4. FIXED_LOT bypassed the budget entirely (no CapRiskAmount, no
OpenRiskAtStops) - pre-halt it could commit more than the remaining daily
allowance. A fixed lot cannot be scaled, so the rule is binary: its
loss-to-stop fits the remaining allowance whole or the trade is refused;
unpriceable risk (no SL) is refused while the budget is enabled.
Compile: 0 errors, 0 warnings.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 18:14:26 -04:00
|
|
|
double CMoneyFixedLot::CheckedLot(ENUM_ORDER_TYPE type, double price, double sl)
|
2026-07-26 23:08:32 -04:00
|
|
|
{
|
|
|
|
|
double lot = m_lots;
|
fix: four risk-layer holes a funded account would eventually find
1. The expectancy stop was stone dead at shipped defaults. Its only feed -
RecordTradeResult inside CTradeJournalManager::Update() - ran solely under
UseDatabaseRanking, which ships false, so the da54639 halt was armed
(ExpectancyMinTrades=40) and never received a single closed trade. A risk
rule must not be a side effect of an analytics toggle: the journal gains
InitTrackingOnly(), Update() runs unconditionally from OnTick and skips
only the DB insert when no DB was initialized.
2. Below-minimum lots were silently bumped UP to SYMBOL_VOLUME_MIN by
TCNormalizeVolume - correct for a user-entered fixed lot, but in the
risk-sizing path it turned a budget-capped 0.05 into 0.10 on min-0.10/
step-0.01 symbols: double the intended risk, after CapRiskAmount already
clamped, exactly the routine-stop-out-breaches-the-daily-limit scenario
the budget exists to close. CMoneyRiskBase now refuses the trade when the
risk-derived lot is below the broker minimum.
3. All trading was async fire-and-forget (SetAsyncMode(true)) with no
OnTradeTransaction handler and no retry: server retcodes were never
observed. Fail-safe for entries, not for closes - a silently rejected
close rode the position until the next bar (or next day for the timed
close window). Now synchronous, matching the risk-budget flatten's own
already-synchronous CTrade; on an H1 EA the latency is irrelevant.
4. FIXED_LOT bypassed the budget entirely (no CapRiskAmount, no
OpenRiskAtStops) - pre-halt it could commit more than the remaining daily
allowance. A fixed lot cannot be scaled, so the rule is binary: its
loss-to-stop fits the remaining allowance whole or the trade is refused;
unpriceable risk (no SL) is refused while the budget is enabled.
Compile: 0 errors, 0 warnings.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 18:14:26 -04:00
|
|
|
//--- THE RISK BUDGET GOVERNS FIXED LOTS TOO (2026-08-11). This class used to bypass CapRiskAmount()
|
|
|
|
|
//--- and OpenRiskAtStops() entirely, so pre-halt a fixed lot could commit more than the remaining
|
|
|
|
|
//--- daily allowance with nothing shrinking or refusing it - the "enforced in the sizing path"
|
|
|
|
|
//--- guarantee only held for FIXED_RISK/INTELLIGENT. A fixed lot cannot be scaled (it is fixed by
|
|
|
|
|
//--- request), so the rule is binary: its loss-to-stop either fits the remaining allowance WHOLE,
|
|
|
|
|
//--- or the trade is refused. CapRiskAmount() handles disabled (returns the amount unchanged),
|
|
|
|
|
//--- halted (0) and partial-allowance (reduced) states - any reduction at all means "does not fit".
|
|
|
|
|
if(g_riskBudget.Enabled())
|
|
|
|
|
{
|
|
|
|
|
if(sl == 0.0 || price == 0.0)
|
|
|
|
|
{
|
|
|
|
|
//--- No stop (or no quote) means the risk is unpriceable, and the budget cannot govern what
|
|
|
|
|
//--- it cannot price - refuse rather than open unbounded risk on a budget-governed account.
|
|
|
|
|
PrintFormat("%s: rejected - fixed lot %.2f has no priceable risk (price=%.5f sl=%.5f) while "
|
|
|
|
|
"the risk budget is enabled", __FUNCTION__, lot, price, sl);
|
|
|
|
|
return(0.0);
|
|
|
|
|
}
|
|
|
|
|
double loss = -m_account.OrderProfitCheck(MoneySymbol(), type, lot, price, sl);
|
|
|
|
|
if(loss > 0.0)
|
|
|
|
|
{
|
|
|
|
|
double allowed = g_riskBudget.CapRiskAmount(loss);
|
|
|
|
|
if(allowed < loss)
|
|
|
|
|
{
|
|
|
|
|
if(allowed > 0.0)
|
|
|
|
|
PrintFormat("%s: rejected - fixed lot %.2f risks %.2f but only %.2f of the budget "
|
|
|
|
|
"remains; a fixed lot is not scaled down, it stands aside",
|
|
|
|
|
__FUNCTION__, lot, loss, allowed);
|
|
|
|
|
return(0.0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-07-26 23:08:32 -04:00
|
|
|
string description;
|
|
|
|
|
if(!ValidateLotForTrade(MoneySymbol(), lot, type, description))
|
|
|
|
|
return(0.0);
|
|
|
|
|
return(lot);
|
|
|
|
|
}
|
|
|
|
|
//+------------------------------------------------------------------+
|
2025-05-30 16:35:54 +02:00
|
|
|
//| Validation settings protected data. |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool CMoneyFixedLot::ValidationSettings(void)
|
|
|
|
|
{
|
|
|
|
|
if(!CExpertMoney::ValidationSettings())
|
|
|
|
|
return(false);
|
|
|
|
|
//--- initial data checks
|
|
|
|
|
if(m_lots<m_symbol.LotsMin() || m_lots>m_symbol.LotsMax())
|
|
|
|
|
{
|
|
|
|
|
printf(__FUNCTION__+": lots amount must be in the range from %f to %f",m_symbol.LotsMin(),m_symbol.LotsMax());
|
|
|
|
|
return(false);
|
|
|
|
|
}
|
2026-07-26 23:08:32 -04:00
|
|
|
//--- article 2555 #10: LotsStep() is 0 until the broker syncs SYMBOL_VOLUME_STEP, and the
|
|
|
|
|
//--- multiple-of-step test below divides by it twice
|
|
|
|
|
if(m_symbol.LotsStep()<=0.0)
|
|
|
|
|
{
|
|
|
|
|
printf(__FUNCTION__+": SYMBOL_VOLUME_STEP for %s is not available yet (%f)",m_symbol.Name(),m_symbol.LotsStep());
|
|
|
|
|
return(false);
|
|
|
|
|
}
|
2025-05-30 16:35:54 +02:00
|
|
|
if(MathAbs(m_lots/m_symbol.LotsStep()-MathRound(m_lots/m_symbol.LotsStep()))>1.0E-10)
|
|
|
|
|
{
|
|
|
|
|
printf(__FUNCTION__+": lots amount is not corresponding with lot step %f",m_symbol.LotsStep());
|
|
|
|
|
return(false);
|
|
|
|
|
}
|
|
|
|
|
//--- ok
|
|
|
|
|
return(true);
|
|
|
|
|
}
|
|
|
|
|
//+------------------------------------------------------------------+
|