Warrior_EA/Money/MoneyRiskBase.mqh

128 lines
6.7 KiB
MQL5
Raw Permalink Normal View History

//+------------------------------------------------------------------+
//| MoneyRiskBase.mqh |
//| AnimateDread |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#include "..\Expert\ExpertMoneyCustom.mqh"
//+------------------------------------------------------------------+
//| Class CMoneyRiskBase. |
//| Shared risk-based lot-sizing core for every money-management |
//| strategy that sizes a trade off a fixed account-risk percentage |
//| (CMoneyFixedRisk, CMoneyIntelligent) - CalculatePotentialLoss()/ |
//| CheckOpenLong()/CheckOpenShort()/CalculateLotSize() used to be |
//| duplicated near-verbatim across both. AdjustRiskAmount()/ |
//| AdjustLotSize() are the two points a subclass can diverge at |
//| (CMoneyIntelligent overrides AdjustRiskAmount() for its Kelly- |
//| criterion, confidence-and-reward:risk-based sizing) - override |
//| only those, everything else here is shared as-is. |
//+------------------------------------------------------------------+
class CMoneyRiskBase : public CExpertMoneyCustom
{
public:
virtual double CheckOpenLong(double price, double sl);
virtual double CheckOpenShort(double price, double sl);
virtual double CheckClose(CPositionInfo *position) { return(0.0); }
protected:
double CalculatePotentialLoss(ENUM_ORDER_TYPE orderType, double price, double sl);
double CalculateLotSize(double loss);
//--- Hook for CMoneyIntelligent's Kelly-criterion risk% scaling (see its own
//--- AdjustRiskAmount() override for the rationale); default no-op keeps CMoneyFixedRisk's
//--- behavior exactly as it was before this base class existed.
virtual double AdjustRiskAmount(double riskAmount) { return riskAmount; }
//--- Reserved extension point for a future lot-size-level adjustment (e.g. equity-curve-
//--- based scaling); no current subclass overrides this - default is a no-op.
virtual double AdjustLotSize(double lot) { return lot; }
};
//+------------------------------------------------------------------+
//| Getting lot size for open long position. |
//+------------------------------------------------------------------+
double CMoneyRiskBase::CheckOpenLong(double price, double sl)
{
if(m_symbol == NULL)
return 0.0;
double loss = CalculatePotentialLoss(ORDER_TYPE_BUY, price, sl);
if(loss <= 0.0)
{
// loss<=0 means sl landed on the wrong side of price (upstream signal bug, stale/gapped
// quote, or a mid-tick price move) - reject the trade instead of silently opening at
// LotsMin(), which used to bypass Money_Risk_Percent entirely with no trace of why.
PrintFormat("%s: rejected - non-positive potential loss (%.5f) for price=%.5f sl=%.5f on %s",
__FUNCTION__, loss, price, sl, m_symbol.Name());
return 0.0;
}
double lot = AdjustLotSize(CalculateLotSize(loss));
if(lot <= 0.0 || !MathIsValidNumber(lot))
{
PrintFormat("%s: rejected - invalid computed lot size (%.5f) for loss=%.5f on %s", __FUNCTION__, lot, loss, m_symbol.Name());
return 0.0;
}
string description;
// Article 2555 #14/#3/#5/#2 in one gate: tradeable symbol, legal volume on the SYMBOL_VOLUME_STEP
// grid, inside SYMBOL_VOLUME_LIMIT for this direction, and covered by ACCOUNT_MARGIN_FREE. See
// CExpertMoneyCustom::ValidateLotForTrade() - it logs its own (throttled) rejection reason.
if(!ValidateLotForTrade(m_symbol.Name(), lot, ORDER_TYPE_BUY, description))
return 0.0;
return lot;
}
//+------------------------------------------------------------------+
//| Getting lot size for open short position. |
//+------------------------------------------------------------------+
double CMoneyRiskBase::CheckOpenShort(double price, double sl)
{
if(m_symbol == NULL)
return 0.0;
double loss = CalculatePotentialLoss(ORDER_TYPE_SELL, price, sl);
if(loss <= 0.0)
{
// See CheckOpenLong() for why this rejects instead of falling back to LotsMin().
PrintFormat("%s: rejected - non-positive potential loss (%.5f) for price=%.5f sl=%.5f on %s",
__FUNCTION__, loss, price, sl, m_symbol.Name());
return 0.0;
}
double lot = AdjustLotSize(CalculateLotSize(loss));
if(lot <= 0.0 || !MathIsValidNumber(lot))
{
PrintFormat("%s: rejected - invalid computed lot size (%.5f) for loss=%.5f on %s", __FUNCTION__, lot, loss, m_symbol.Name());
return 0.0;
}
string description;
// See CheckOpenLong() above - same article 2555 #14/#3/#5/#2 gate.
if(!ValidateLotForTrade(m_symbol.Name(), lot, ORDER_TYPE_SELL, description))
return 0.0;
return lot;
}
//+------------------------------------------------------------------+
//| Calculate potential loss |
//+------------------------------------------------------------------+
double CMoneyRiskBase::CalculatePotentialLoss(ENUM_ORDER_TYPE orderType, double price, double sl)
{
if(price == 0.0)
price = (orderType == ORDER_TYPE_BUY) ? m_symbol.Ask() : m_symbol.Bid();
if(price == 0.0)
{
// SymbolInfoDouble-backed Ask()/Bid() returns 0 when no quote is available yet
// (disconnected/freshly-selected symbol) - a 0 price makes OrderProfitCheck's result
// meaningless, so fail loudly instead of feeding it into the lot-size calculation.
PrintFormat("%s: no valid price available for %s (Ask/Bid returned 0)", __FUNCTION__, m_symbol.Name());
return 0.0;
}
return -m_account.OrderProfitCheck(m_symbol.Name(), orderType, 1.0, price, sl);
}
//+------------------------------------------------------------------+
//| Calculate the lot size based on potential loss and account balance|
//+------------------------------------------------------------------+
double CMoneyRiskBase::CalculateLotSize(double loss)
{
double riskAmount = AdjustRiskAmount(m_account.Balance() * m_percent / 100.0);
double stepvol = m_symbol.LotsStep();
if(stepvol <= 0.0)
{
// A 0 SYMBOL_VOLUME_STEP (not yet synced by the broker) would otherwise divide by zero and
// propagate inf/NaN downstream into volume/margin checks with no diagnostic.
PrintFormat("%s: rejected - LotsStep() returned %.5f for %s (broker volume data not ready?)", __FUNCTION__, stepvol, m_symbol.Name());
return 0.0;
}
return MathFloor(riskAmount / loss / stepvol) * stepvol;
}
//+------------------------------------------------------------------+