Sizing & backtest¶
ML position-sizing for short-premium index option strategies (NIFTY / SENSEX) — naked or hedged.
Our proprietary model scores each option leg's loss-risk at entry and turns it into a position size — it down-sizes the risky legs and keeps the clean ones at your base size, trading every leg. It's a sizing overlay, not a filter — a lower drawdown at the same average exposure, never extra margin. Two horizons (below). Validated out-of-sample on a rolling-quarter walk-forward; see the case study.
The flow: run a backtest once on the /backtest page in the app to validate the edge and get your
strategy's weight_norm, then size live with that norm — per leg via /sizing/leg-size
for naked strategies, or the whole structure via /sizing/structure for hedged ones.
All endpoints authenticate with X-API-Key: tlyt_… — your TradLyt API key (see Authentication).
Two models (horizon)¶
Pick the horizon that matches how long you hold:
horizon |
when | model |
|---|---|---|
intraday (default) |
same-day, square off by close | intraday model |
positional |
overnight / multi-day holds | positional model (underlying-driven, with a live daily-history lookup) |
The sizing runs in derisk mode: it down-sizes the risky legs and never sizes above your base — so you
add no extra margin and your average exposure is unchanged. The win is a lower drawdown and a higher
risk-adjusted return on the same capital — loss-reduction, not leverage.
Naked vs hedged — use the right endpoint. A naked strategy (straddle / strangle) is sized leg-by-leg with
/sizing/leg-size— there's no hedge ratio to protect and the CE/PE tilt is itself edge. A hedged structure (iron-fly / condor / credit spread) must go through/sizing/structure, which scales the whole trade as one unit so the buy:sell hedge ratio is preserved — the protective wings are never stripped.
Live leg sizing (naked)¶
For each leg you're about to place, get the lots to use. Pass the weight_norm from your backtest.
POST /api/v1/sizing/leg-size
Request body¶
| Field | Type | Description |
|---|---|---|
underlying |
enum |
NIFTY / SENSEX. Index only. |
option_type |
enum |
CE / PE. |
direction |
enum |
BUY / SELL. Defaults to SELL. |
strike |
float |
Strike price. |
expiry_date |
string |
YYYY-MM-DD. |
base_lots |
integer |
Your base size in lots. Built for 10+ lots; sizing engages at ≥ 5 (below that it runs flat). |
weight_norm |
float |
From your backtest's calibrated_weight_norm. Pass this so weights average ≈ 1 for your strategy. |
horizon |
enum |
intraday (default) or positional — selects the model. |
mode |
enum |
derisk (default) — the sizing mode. |
entry_time |
string |
ISO-8601 IST decision moment. Optional; defaults to now. |
Example request¶
{
"underlying": "SENSEX", "option_type": "CE", "direction": "SELL",
"strike": 74100, "expiry_date": "2026-06-12",
"base_lots": 10, "weight_norm": 0.497,
"horizon": "intraday", "mode": "derisk"
}
Example response¶
{
"recommended_lots": 8,
"risk": 0.63,
"weight": 0.79,
"weight_raw": 0.79,
"norm": 0.497,
"norm_source": "strategy",
"mode": "derisk",
"horizon": "intraday",
"sizing_active": true,
"method": "ml",
"iv": 12.49,
"delta": 0.534,
"theta_pct": -12.95
}
Response fields¶
| Field | Type | Description |
|---|---|---|
recommended_lots |
integer |
The answer — the lots to trade for this leg (floored at 1; every leg is kept). |
risk |
float (0-1) |
The model's loss-risk score for this leg — higher = riskier. null when it ran flat. |
weight |
float |
The final sizing multiplier on your base lots (after the mode cap). Averages ≈ 1 across a calibrated strategy — allocation, not leverage. |
weight_raw |
float |
The multiplier before the mode cap. Equals weight unless the cap bit (e.g. clean leg hits the mode ceiling). |
norm |
float |
The normalization scale actually used — your weight_norm, or the training fallback. |
norm_source |
enum |
"strategy" (you passed weight_norm — good) / "training" (fallback, may mis-size) / "none" (ran flat). |
mode |
enum |
The sizing mode applied — derisk. |
horizon |
enum |
The model used — intraday / positional. |
sizing_active |
bool |
false when base_lots < 3 or data was unavailable → ran flat (recommended_lots = base_lots). |
method |
enum |
"ml" (scored on live data) or "rule_based" (flat fallback — model/market data unavailable, never blocks you). |
iv |
float |
Implied volatility (%) of this contract at entry. |
delta |
float |
The leg's delta at entry. |
theta_pct |
float |
Daily theta as a % of premium — the leg's time decay. |
Structure sizing (hedged)¶
Send all legs of a hedged structure in one call. We score each leg, derive a single structure weight from the short (sold) legs' conviction, and apply it to every leg — so the buy:sell ratio you designed is the ratio you trade. Use this for iron-fly, iron-condor, and credit/debit spreads. (Naked structures can be sent here too; they fall back to per-leg sizing.)
POST /api/v1/sizing/structure
Request body¶
| Field | Type | Description |
|---|---|---|
underlying |
enum |
NIFTY / SENSEX. All legs share it. |
legs |
array |
The structure's legs (see below). Send every leg you intend to trade. |
base_lots |
integer |
Default base size for legs that don't set their own. Built for 10+ lots; engages at ≥ 5. |
weight_norm |
float |
The strategy's calibrated_weight_norm from its backtest. |
horizon |
enum |
intraday (default) or positional. |
mode |
enum |
derisk (default) — the sizing mode. |
entry_time |
string |
ISO-8601 IST decision moment. Optional; defaults to now. |
Each leg object:
| Field | Type | Description |
|---|---|---|
option_type |
enum |
CE / PE. |
direction |
enum |
BUY / SELL. A structure with any BUY leg is treated as hedged. |
strike |
float |
Strike price. |
expiry_date |
string |
YYYY-MM-DD. |
base_lots |
integer |
Optional — this leg's intended lots; falls back to the request-level base_lots. |
Example request — NIFTY iron-fly¶
{
"underlying": "NIFTY", "base_lots": 10,
"horizon": "intraday", "mode": "derisk", "weight_norm": 0.471,
"legs": [
{"option_type": "PE", "direction": "BUY", "strike": 23150, "expiry_date": "2026-06-16"},
{"option_type": "PE", "direction": "SELL", "strike": 23350, "expiry_date": "2026-06-16"},
{"option_type": "CE", "direction": "SELL", "strike": 23350, "expiry_date": "2026-06-16"},
{"option_type": "CE", "direction": "BUY", "strike": 23550, "expiry_date": "2026-06-16"}
]
}
Example response¶
{
"legs": [
{"option_type": "PE", "direction": "BUY", "strike": 23150, "risk": 0.46, "weight": 0.77, "base_lots": 10, "recommended_lots": 8},
{"option_type": "PE", "direction": "SELL", "strike": 23350, "risk": 0.40, "weight": 0.77, "base_lots": 10, "recommended_lots": 8},
{"option_type": "CE", "direction": "SELL", "strike": 23350, "risk": 0.40, "weight": 0.77, "base_lots": 10, "recommended_lots": 8},
{"option_type": "CE", "direction": "BUY", "strike": 23550, "risk": 0.50, "weight": 0.77, "base_lots": 10, "recommended_lots": 8}
],
"hedged": true,
"sizing_mode": "structure-level",
"structure_weight": 0.77,
"hedge_ratio": {"designed_sell_buy": 1.0, "sized_sell_buy": 1.0},
"mode": "derisk",
"horizon": "intraday",
"norm": 0.471,
"norm_source": "strategy",
"method": "ml"
}
All four legs come back at 8 lots — one structure weight, ratio preserved (designed 1.0 → sized 1.0).
Response fields¶
| Field | Type | Description |
|---|---|---|
legs[] |
array |
One object per leg, in request order: {underlying, option_type, direction, strike, risk, weight, base_lots, recommended_lots}. |
hedged |
bool |
true if the structure has any BUY (hedge) leg. |
sizing_mode |
enum |
"structure-level" (hedged → one weight applied to all legs) or "per-leg" (naked → each leg sized on its own). |
structure_weight |
float |
(hedged) The single weight applied to every leg, taken from the short legs' conviction. |
hedge_ratio |
object |
{designed_sell_buy, sized_sell_buy} — confirm the ratio is preserved; the two values should match. |
norm, norm_source, mode, horizon, method |
— | As in /sizing/leg-size. |
Send the structure as one call
Sizing each hedge leg separately on /sizing/leg-size drifts the buy:sell ratio (the short-trained model
mis-scores the BUY wings, and independent weights diverge) — which can strip your protection. /sizing/structure
exists precisely to keep the ratio intact. If any leg can't be priced (expired contract, market closed,
token lapsed) the whole structure runs flat — your ratio is never altered.
Backtest¶
To validate a strategy and get its weight_norm, run a backtest in the app — upload your AlgoTest CSV on the
/backtest page and pick the Hold (intraday or positional). You get a flat-vs-sized (De-risk) comparison,
the rolling-quarter walk-forward verdict, the calibrated weight_norm to use live, the capital you'll need, and a
downloadable per-leg tape. See the
case study for worked examples.
The verdict is a rolling-quarter walk-forward: sizing must beat flat in a majority of quarters and not be worse risk-adjusted. If it doesn't, the tool honestly says "run flat."
Keep your weight_norm fresh
weight_norm reflects the volatility regime your backtest covered. As regimes shift, the model's risk
distribution moves and the norm drifts — a stale norm can over-size in a vol spike. Re-run your backtest
periodically (about quarterly, or sooner after a clear volatility shift) to refresh it. (Automatic drift
monitoring is on the way, so you'll be told when.)
Glossary¶
Every term used by the sizing endpoints and the backtest, in one place.
| Term | Meaning |
|---|---|
risk |
The model's 0–1 loss-risk score for a leg at entry — higher means a larger adverse excursion is expected. It is not a market-direction call. |
weight |
The multiplier on your base lots: (1 − risk)² / norm, then capped by the mode. ≈ 1 on average for a calibrated strategy. |
weight_norm (a.k.a. norm) |
The per-strategy calibration constant from your backtest. It normalizes weights so they average ≈ 1 — making the model re-distribute size rather than blindly lever up. Pass it on every live call. |
norm_source |
Which norm was used: strategy (you passed weight_norm — correct), training (generic fallback — can systematically over/under-size), none (ran flat). |
mode |
The sizing mode — derisk: down-size the risky legs only, never above your base (no extra margin, lowest drawdown). |
horizon |
Which model scores the leg: intraday (same-day) or positional (multi-day, underlying-driven). Match it to your hold. |
sizing_active |
false ⇒ the call ran flat (base < 3 lots, or data unavailable) and recommended_lots = base_lots. |
method |
ml (scored on live market data) vs rule_based (flat fallback when the model or market data is unavailable — the API never blocks your order). |
structure_weight |
For a hedged structure, the single weight applied to every leg, derived from the short legs' conviction — this is what preserves the hedge ratio. |
hedge_ratio |
designed_sell_buy (the ratio you sent) vs sized_sell_buy (after sizing). They should match. |
| FLAT (backtest) | Your strategy at a constant base size — the baseline we compare against. |
| ML-SIZED (backtest) | The same strategy with our per-leg weights applied. |
| Rolling-quarter walk-forward (backtest) | The verdict method: re-judge sized-vs-flat each calendar quarter out-of-sample. Sizing must win a majority of quarters and not be worse risk-adjusted. |
| Sharpe (backtest) | Return per unit of volatility — higher is a smoother equity curve. |
| MaxDD (backtest) | Maximum peak-to-trough drawdown over the period. |
total_return_pct / roi_pct_yr (backtest) |
Return over the full backtest span, and that figure annualized. |
Notes¶
- Scope. Sizes short-premium strategies — naked straddles / strangles and hedged structures
(iron-fly / condor / spreads), intraday or positional. Hedged trades go through
/sizing/structureand are sized as one unit so the hedge ratio is preserved. Tightly-capped structures (e.g. narrow credit spreads) and long / option-buying strategies return "run flat" — don't enable sizing there. - De-risk sizing.
derisk(default) only down-sizes the risky legs and never sizes above your base → no extra margin and the lowest drawdown, at the same average exposure. Loss-reduction, not leverage. - Entry-time only. The model scores a leg's entry features and predicts its excursion from there. It sizes the order you're about to place; it never re-sizes a position you already hold. An adjustment is just a new entry, sized fresh.
- Internal fields. Raw responses may briefly carry debug-only keys (model name, training label, clip band). They are not part of the contract and may vanish — build only on the fields documented above.
- Gross of charges. Backtest P&L is gross — we do not net brokerage / STT / slippage. A ~1% round-trip
cost roughly halves the edge, so your live net will be lower. (
costs_basis: "gross".) - Returns are for the whole backtest period.
total_return_pctis the return over the full span;roi_pct_yris that annualized. The result also carries ayearlyP&L breakdown and theperiod. - Lot sizes are handled per underlying (NIFTY 65, SENSEX 20 — current).