Join our fan page
- Views:
- 32
- Rating:
- Published:
-
Need a robot or indicator based on this code? Order it on Freelance Go to Freelance
Why I wrote it
My scalper trades two setups. A bullish order block and the bearish mirror of it. The Strategy Tester gave me one profit factor for both of them. I spent two weeks tuning inputs that applied to each setup equally.
Then I split the trades by setup type. On USDCAD M1 the bullish side had caused 88 percent of the loss. The bearish side was close to flat. The number I needed had been sitting in my own trade history the whole time. I just had no way to read it.
This class reads it.
What it gives you
You register your setup types once at startup. Each one gets an id back. That id is added to your magic number base so every order carries its own setup tag from the moment it is sent.
After that the ledger answers three questions for each setup. How many wins and losses. What the net result was after swap and commission. And whether the win rate has earned any trust yet.
That last question is the one I built this for. Three wins from three trades is a 100 percent win rate. It is also what a coin that lands heads 44 percent of the time manages about once in twelve attempts. So the class prints a Wilson score lower bound beside the raw rate and checks it against the breakeven win rate your reward ratio demands. At 1:2 that bar sits at 33.3 percent. A setup only reads as trusted when even its pessimistic rate clears the bar. Early on everything reads "not yet trusted". That is the honest answer when you have twenty trades.
Using it
#include <CSetupLedger.mqh> CSetupLedger ledger; int SETUP_BULL, SETUP_BEAR; int OnInit() { ledger.Init(552001); // your magic base SETUP_BULL = ledger.Register("Bullish OB"); // id 0 -> magic 552001 SETUP_BEAR = ledger.Register("Bearish OB"); // id 1 -> magic 552002 ledger.RebuildFromHistory(); // read the record off the account Print(ledger.Report(2.0)); // 2.0 = reward-to-risk ratio return INIT_SUCCEEDED; }
Stamp the setup onto the trade before the order goes out.
trade.SetExpertMagicNumber((ulong)ledger.MagicFor(SETUP_BULL)); Fold the position in once it closes.
double net = 0.0; if(ledger.ProcessClosedPosition(posId, setupId, net)) Print(ledger.Report(2.0));
Report() returns one block of text. It works as a Print() and as a Comment() . Here is a real run of mine on USDCAD M1.
Per-setup ledger (breakeven win rate at 1:2.0 = 33.3%) --------------------------------------------------------------------- Bullish OB 235/529 raw 30.8% wilson 27.6% net -5819.94 not yet trusted Bearish OB 191/393 raw 32.7% wilson 29.0% net -811.42 not yet trusted
The same report passed to Comment() sits in the chart corner while the EA runs. The box on the bar is the entry label. It records what that setup's record was before the trade was taken. Here the panel reads 4 wins and 6 losses. Raw rate 40 percent. Wilson lower bound 16.8 percent. So on ten trades the verdict still reads not yet trusted, which is the whole point of the column.
The counts are wins over losses. So that is 764 trades on the bullish setup and 584 on the bearish one. Both nets add back to the tester's own net loss to the cent. Run that reconciliation on your own output before you trust any ledger including this one. If the per-setup trades do not sum to the tester's trade count then positions are being miscounted and every percentage above that line is worthless.
Three traps it handles for you
Hand written versions of this usually get these wrong. I got two of them wrong myself before this class existed.
Deals are not trades. MT5 history is a list of deals. A position closed in three parts produces three OUT deals. Loop over deals and count each profitable one as a win and a single winning trade becomes three wins. The ledger aggregates by DEAL_POSITION_ID so a partial close still counts once.
DEAL_PROFIT is not the result. Swap and commission sit in their own fields. On a swing EA that gap is cosmetic. On a scalper it decides whether the setup made money at all. A trade that gained 3 USD of price movement and paid 4 USD in commission is a loss. The ledger sums profit plus swap plus commission.
POSITION_TICKET is not POSITION_IDENTIFIER . HistorySelectByPosition() wants the identifier. The two hold the same value on a netting account most of the time so the bug stays invisible until a hedging account or a partial close pulls them apart. Then the selection comes back empty and says nothing.
The one rule you must not break
Registration order is a data format. The setup id lives nowhere except inside the magic numbers of trades that have already closed. Insert a new setup in the middle of the list next month and every historical trade of the setup that used to hold that id silently becomes a trade of the new one. Nothing throws an error. The numbers are simply wrong from then on.
New setups go at the end of the list. If you retire a setup then leave its Register() call where it is so the id stays taken. A short comment above the registration block saying this earns its keep.
What it will not do
It keeps one record per position. An EA that stacks a second entry into the same netting position will credit the whole thing to the first setup. If yours does that it needs a per-deal model instead.
Sixteen setups is the ceiling. Raise SETUP_LEDGER_MAX if you need more. Sixteen setups on one account also means sixteen small samples and the Wilson column will tell you so.
A net result of exactly zero counts as a loss. Scratch trades are rare and treating them as wins is the more flattering error.
ProcessClosedPosition() replaces the global history selection because it calls HistorySelectByPosition() . Do not call it in the middle of your own history loop without reselecting afterwards.
And it measures your exits rather than your idea. A setup that reads direction correctly but places its stop badly scores as a bad setup. For deciding what to trade tomorrow that is arguably the right answer. It is not the same as judging whether the idea was sound.
Requirements
MetaTrader 5. No dependencies beyond the standard library. Drop the file in MQL5/Include/ and include it. Written and tested on build 6190.
One last thing. A ledger built on a single in sample backtest measures that backtest. It does not validate a strategy. Out of sample testing on a period you never tuned on is a separate job and this class does not replace it.
— Ali Rajput
Position sizing from risk, using the broker's own tick value and volume step
Position size from a risk amount, computed from the symbol's real tick value, tick size and volume step rather than an assumed pip value. It always rounds volume down, and when your risk is smaller than the minimum lot it says so and reports what that lot actually costs. No terminal state, so the sizing logic is testable offline - a 41-assertion test script is included.
Heiken Ashi ATR Blend
Heiken Ashi ATR Blend combines smoothed Heiken Ashi candles, ATR-based volatility filtering, and fast/slow EMA trend confirmation to identify potential trend-change signals.
RegimeRouter — trend/range classifier with a per-regime win rate ledger
Classifies the market with ADX, the Hurst exponent and lag-1 autocorrelation, routes each bar to a breakout module or a mean-reversion module, and keeps a separate win rate ledger for each regime with a Wilson 95% lower bound, so you can see which half of it is actually working.
MACD Signals
Indicator edition for new platform.