forked from mnbvc188199/Warrior_EA
- Introduced `FeatureScale.mqh` with `FeatSquash` function for stateless feature scaling. - Added `RegimeMath.mqh` class for regime arithmetic, including efficiency and variance calculations. - Documented the Mind trading logic in `MIND.md`, detailing the trading process and modes. - Created `VOLNORM_PLAN.md` and `VOLNORM_RESULTS.md` for tick-volume normalization testing. - Implemented `read_book.py` for analyzing trade book data and correlations. - Developed `volnorm.py` for testing tick-volume normalization with new and old methods.
121 lines
6.3 KiB
MQL5
121 lines
6.3 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. |
| |
//| |
| |
//| SIZED ON EQUITY, NOT BALANCE (2026-09-23). A prop account is |
| |
//| judged on equity, and with several charts sharing one account the |
| |
//| balance ignores what the other charts' open positions have |
| |
//| already lost. The reference dip EA sized on equity; a flat single |
| |
//| chart sees no difference. |
| |
//| |
| |
//| 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 m_scale; // multiple of the base risk for the NEXT entry, 0 < scale <= 1
| |
| |
double Size(ENUM_ORDER_TYPE type, double price, double sl);
| |
| |
public:
| |
CWarriorMoney(void) : m_fixedLot(false), m_lots(0.01), m_refused(0), m_scale(1.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; }
| |
//--- The Mind's hook. It may only take risk OFF (clamped to (0, 1]); a fixed-lot book has no risk
| |
//--- unit to scale and ignores it. Set per entry and reset by its owner - never persisted.
| |
void RiskScale(const double s) { m_scale = MathMax(0.05, MathMin(1.0, s)); }
| |
double RiskScale(void) const { return m_scale; }
| |
| |
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.Equity() * m_percent / 100.0 * m_scale;
| |
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.Equity(), riskMoney, minvol, minvol * lossPerLot,
| |
m_refused);
| |
return 0.0;
| |
}
| |
if(lot > maxvol)
| |
lot = maxvol;
| |
return lot;
| |
}
| |
#endif // WARRIOR_SIMPLE_MONEY_MQH
|