110 lines
5.4 KiB
MQL5
110 lines
5.4 KiB
MQL5
//+------------------------------------------------------------------+
| |||
//| WarriorMoney.mqh |
| |||
//| AnimateDread |
| |||
//| |
| |||
//| Position sizing: the standard library's shape, with the one hole |
| |||
//| in it closed. |
| |||
//| |
| |||
//| 🛑 THE HOLE. MQL5's own CMoneyFixedRisk ends with |
| |||
//| |
| |||
//| if(lot < minvol) lot = minvol; |
| |||
//| |
| |||
//| so when the risk budget buys LESS than one minimum lot, it trades |
| |||
//| a minimum lot anyway. The requested risk is silently exceeded, |
| |||
//| by an arbitrary factor that is largest exactly when the stop is |
| |||
//| widest - and nothing in the log says so. On an index with a 0.10 |
| |||
//| minimum that can be several times the intended risk. |
| |||
//| |
| |||
//| Here a trade that cannot be sized within its risk is REFUSED. A |
| |||
//| skipped trade is a missing row in the results; an oversized one |
| |||
//| is a wrong number in them, and only the second kind is dangerous. |
| |||
//| This repo closed the same hole once before, in 77e8080. |
| |||
//| |
| |||
//| NO RISK BUDGET, NO LATCH, NO PERSISTED STATE. CRiskBudget writes |
| |||
//| a permanent halt to disk that survives between backtests, so one |
| |||
//| breaching run silently truncates every later run on that symbol. |
| |||
//| Account protection belongs in a veto filter that starts fresh |
| |||
//| each pass, not in the lot-size calculation. |
| |||
//+------------------------------------------------------------------+
| |||
#ifndef WARRIOR_SIMPLE_MONEY_MQH
| |||
#define WARRIOR_SIMPLE_MONEY_MQH
| |||
| |||
#include <Expert\ExpertMoney.mqh>
| |||
| |||
class CWarriorMoney : public CExpertMoney
| |||
{
| |||
protected:
| |||
bool m_fixedLot; // true = trade m_lots flat, ignore m_percent
| |||
double m_lots;
| |||
int m_refused; // how many entries could not be sized - reported at deinit
| |||
| |||
double Size(ENUM_ORDER_TYPE type, double price, double sl);
| |||
| |||
public:
| |||
CWarriorMoney(void) : m_fixedLot(false), m_lots(0.01), m_refused(0) {}
| |||
~CWarriorMoney(void) {}
| |||
| |||
void FixedLot(const double lots) { m_fixedLot = true; m_lots = lots; }
| |||
void RiskPercent(const double p) { m_fixedLot = false; m_percent = p; }
| |||
int Refused(void) const { return m_refused; }
| |||
| |||
virtual double CheckOpenLong(double price, double sl) override
| |||
{ return Size(ORDER_TYPE_BUY, price, sl); }
| |||
virtual double CheckOpenShort(double price, double sl) override
| |||
{ return Size(ORDER_TYPE_SELL, price, sl); }
| |||
};
| |||
//+------------------------------------------------------------------+
| |||
double CWarriorMoney::Size(ENUM_ORDER_TYPE type, double price, double sl)
| |||
{
| |||
if(m_symbol == NULL)
| |||
return 0.0;
| |||
const double minvol = m_symbol.LotsMin();
| |||
const double maxvol = m_symbol.LotsMax();
| |||
const double step = m_symbol.LotsStep();
| |||
| |||
if(m_fixedLot)
| |||
{
| |||
double lot = m_lots;
| |||
if(lot < minvol) lot = minvol;
| |||
if(lot > maxvol) lot = maxvol;
| |||
return lot;
| |||
}
| |||
//--- NO STOP MEANS NO RISK UNIT, so there is nothing to size against. The stdlib answers this
| |||
//--- case with "the largest lot the margin allows", which is the opposite of a risk rule.
| |||
if(sl == 0.0 || price <= 0.0)
| |||
{
| |||
m_refused++;
| |||
return 0.0;
| |||
}
| |||
//--- Loss per one lot between entry and stop, asked of the broker rather than derived from the
| |||
//--- point value - it is the only source that knows this symbol's contract and currency.
| |||
const double lossPerLot = -m_account.OrderProfitCheck(m_symbol.Name(), type, 1.0, price, sl);
| |||
if(lossPerLot <= 0.0)
| |||
{
| |||
//--- An inverted or zero-distance stop. Refuse: dividing by it produces either a negative lot
| |||
//--- or an enormous one, and both have reached live accounts in this repo's history.
| |||
m_refused++;
| |||
return 0.0;
| |||
}
| |||
const double riskMoney = m_account.Balance() * m_percent / 100.0;
| |||
double lot = (step > 0.0) ? MathFloor(riskMoney / lossPerLot / step) * step
| |||
: riskMoney / lossPerLot;
| |||
if(lot < minvol)
| |||
{
| |||
//--- THE REFUSAL. Rounding up to the minimum here is what the stdlib does and what this class
| |||
//--- exists not to do. Said out loud, throttled to once per 50, because a silent skip is how
| |||
//--- "the EA barely traded" becomes a mystery instead of a message.
| |||
m_refused++;
| |||
if(m_refused % 50 == 1)
| |||
PrintFormat("CWarriorMoney: refused - %.2f%% of %.2f is %.2f, and one minimum lot (%.2f)"
| |||
" would risk %.2f. Widen the risk, tighten the stop, or accept fewer trades;"
| |||
" trading a minimum lot here would exceed the stated risk. (%d so far)",
| |||
m_percent, m_account.Balance(), riskMoney, minvol, minvol * lossPerLot,
| |||
m_refused);
| |||
return 0.0;
| |||
}
| |||
if(lot > maxvol)
| |||
lot = maxvol;
| |||
return lot;
| |||
}
| |||
#endif // WARRIOR_SIMPLE_MONEY_MQH
|