Skip to content

Sizing from the record ​

Every trade here has a stop and a target, and every finished trade is written up from the venue's own fills. That makes two things possible that a trading agent usually cannot do honestly: measure what the desk's trades actually return, in the same units every time, and let that measurement lower the risk the next trade takes. No model is involved. The arithmetic below is all of it, and tra sizing prints it for your desk.

It is off until you turn it on, and once on it can only lower the risk you declared in [controls].max_risk_per_trade_bps, never raise it.

toml
[sizing]
kelly_fraction = 0.25            # a quarter of the Kelly fraction; turning this on is what enables it
# min_round_trips = 30           # finished trades before the record decides anything
# lookback_round_trips = 100     # only the newest trades count
# simulate_trades = 100          # how far ahead the drawdown is simulated, 10 to 300
# simulate_paths = 2000          # 100 to 5000, so a cycle waits seconds at most
# seed = 20261021                # the same seed gives the same figures on any machine
# max_drawdown_probability = 0.05

The unit: what the trade planned to lose ​

When a cycle writes an intent, it also writes loss_at_stop_bps: the distance from the price to the stop plus the round trip it pays, in basis points of the notional. That is what the trade loses if the stop is hit as priced. When the trade finishes, its net result is divided by that figure and written on the round trip as r. A stop hit as priced is about -1. A target twice as far as the stop is about +2 less the cost. A time exit lands wherever it lands.

r is price and fees, like net; funding is recorded beside it as the venue reports it, because venues sign it differently.

Reading results in planned losses makes trades of different sizes, instruments and stop distances comparable, and it is the unit the risk limit is already in: risking 50 bps at the stop and getting r = -1 costs the account 50 bps.

Kelly from the record ​

From the newest lookback_round_trips trades with an r, three figures: the share that won (p), the share that lost (q), and the average win and loss in planned losses (W and L). A trade that closed flat counts in neither. The fraction of equity to lose on a losing trade that maximises long-run growth is then:

f* = (p * W - q * L) / ((p + q) * W * L)

With every trade a win or a loss this is the familiar p / L - q / W. As arithmetic only: half the trades winning 2 and half losing 1 gives f* = (0.5 * 2 - 0.5 * 1) / 2 = 0.25, a quarter of equity at the stop. Nobody should bet that, and the record does not ask you to:

  • Only kelly_fraction of it is applied. p, W and L are estimates from a few dozen trades, and full Kelly on an overstated edge loses accounts. A quarter is a common choice; 0.25 of 0.25 is 625 bps.
  • It is a ceiling. The risk a cycle uses is the smaller of this and your declared max_risk_per_trade_bps. With 50 bps declared, the ceiling above changes nothing.
  • Too few trades decide nothing. Below min_round_trips the declared risk stands, and the cycle records how many trades the record holds.
  • No edge refuses. With enough trades, an f* at or below zero means the record supports no bet, and the cycle refuses new entries with the figures behind it. The same when none of the trades won. A person decides what happens next; lowering or removing [sizing] is a configuration change.

Beside f*, the record also gives the win rate it supports at 95% confidence (the lower Wilson bound), so you can see how much of the win rate is the sample.

The drawdown ahead: a seeded Monte Carlo ​

Kelly is about the average. The path matters too, and [controls].max_drawdown_bps is where the desk stops taking entries. So the cycle also asks: at this risk per trade, how likely is the desk to fall through the room it has left under that limit in the next simulate_trades trades?

It answers by resampling. It draws simulate_trades results, with replacement, from the same trades the Kelly figures came from, compounds them at the risk being tested, and repeats that simulate_paths times. The share of paths whose drawdown reaches the room left is the chance it reports. If the Kelly ceiling keeps that chance within max_drawdown_probability, it stands; otherwise the risk is lowered, by bisection to a hundredth of a basis point, to the largest value that does. The room is your limit minus the drawdown the account already has from its highest equity, read at the start of the cycle, in whole basis points rounded down.

It is repeatable to the digit:

  • the draws come from a generator seeded with seed, and the same draws are reused at every risk level tried;
  • the draws are integers taken from the generator's own numbers, so nothing depends on how a Python version implements random choice;
  • percentiles are nearest-rank in integer arithmetic.

The same record, settings and seed give the same answer on any machine, and the cycle writes the seed, the paths, the risk and the figures into its record, so anyone can run it again. Each answer is also kept in memory, so a desk asking the same question every cycle does not simulate it again.

What it is not: a forecast. Resampling assumes the next trades look like the last ones. It is a check on how hard the recent record could push the account at a given risk, which is the question a drawdown limit is there to answer.

Excursions, measured by the system ​

Every round trip that closes in venue mode now also carries how far it went for and against the position before it closed, read from the venue's own candles over the hold. The finest candles that cover the hold in at most 1000 bars are used, one minute for most holds.

FieldWhat it is
mfe_bpsthe best the position stood at, in bps of the entry, positive when it was in front
mae_bpsthe worst, negative when it was under water
bars_to_mfe, bars_to_maeon which bar each happened
mae_of_stopthe worst as a share of the stop distance: 0.9 is a stop that nearly fired
mfe_of_takethe best as a share of the target distance: over 1 is a target that was reached
timeframe, completethe candles read, and whether they covered the whole hold

The first and the last bar can include prices from just before the entry and just after the exit, which is why the finest candles are used. The quant auditor and the risk manager read these figures; they no longer have to infer the shape of a trade from its result.

Where it shows ​

WhereWhat
the cycle record, sizing in cycle.jsonthe rule, the measured figures, f*, the ceiling, the simulation and why
intents.jsonlloss_at_stop_bps on every intent
round_trips.jsonlloss_at_stop_bps, r, stop, take and excursion on every finished trade
tra sizingthe same calculation the cycle makes, from the records, without changing anything

Trades written before this version have no loss_at_stop_bps, so they are not counted: the record starts with the first trade that knows what it planned to lose.

Released under the MIT License.