11 KiB
Trading risk analytics
Read-only tools for sizing, tail risk, and stress tests. They inspect the account and open positions — they never place, modify, or close orders. Pair them with TRADING_SAFETY.md when you move from analysis to execution.
| Tool | Answers |
|---|---|
trade_risk_analyze |
“How big can this trade be?” and “How much am I risking right now?” |
trade_var_cvar_calculate |
“What is my statistical worst-case loss over one bar?” |
trade_stress_test |
“What happens to open positions under these price shocks?” |
Dense terms: Fixed-fraction sizing · Kelly · VaR · CVaR / ES · Drawdown
Related: Sample trade · Advanced playbook · Guardrails · Barriers
trade_risk_analyze
Analyzes current open/pending risk and, when given a proposed entry and stop, sizes a new trade. Supports two sizing methods: fixed-fraction and Kelly.
# Current portfolio risk only
mtdata-cli trade_risk_analyze --json
# Size a new long: risk-based volume from entry + stop
mtdata-cli trade_risk_analyze EURUSD --direction long \
--entry 1.0850 --stop-loss 1.0800 --sizing '{"method":"fixed_fraction","risk_pct":1.0}' --json
| Parameter | Default | Description |
|---|---|---|
symbol |
— | Symbol for new-trade sizing; omit for portfolio-only risk. |
sizing |
— | JSON object selecting fixed_fraction with risk_pct, or kelly with its edge inputs and cap. Risk percentages use percentage points (1 means 1%) and must be in (0, 100]. |
strict_risk |
true |
Return suggested_volume=0.0 if the broker minimum lot would exceed the requested sizing risk. |
include_pending |
true |
Include contingent stop-loss risk from pending orders in portfolio totals. |
direction |
— | long/short (aliases accepted) for the proposed trade. |
entry |
— | Proposed entry price. With symbol+stop_loss but no entry, it is resolved from the latest tick (ask for long, bid for short, mid otherwise). A closed/stale quote is labeled last_available_tick_* and sizing_reference_only; refresh it before submission. |
stop_loss |
— | Proposed stop (alias sl). Required to compute risk-based volume. |
take_profit |
— | Optional target (alias tp) for reward/risk context. |
New-trade sizing uses an incremental candidate-risk policy: sizing.risk_pct
limits the proposed trade's stop risk as a percentage of account equity, and
account-wide margin stress remains a hard safety gate. Existing positions on other
symbols do not prevent sizing, but the returned sizing_risk_policy states that the
suggestion is not an aggregate portfolio stop-risk cap.
Candidate validation is independent of portfolio observation. A valid proposed
trade returns candidate_valid: true. If its direction, stop, or target is
invalid, the response retains the account and portfolio snapshot but returns
success: false, candidate_valid: false, a structured error_code, and
portfolio_snapshot_status: available; CLI callers receive a nonzero exit status.
Kelly sizing
Set sizing.method=kelly and supply edge statistics in the same JSON object:
mtdata-cli trade_risk_analyze EURUSD --direction long \
--entry 1.0850 --stop-loss 1.0800 \
--sizing '{"method":"kelly","win_rate":0.55,"avg_win":0.012,"avg_loss":0.010,"fraction_multiplier":0.5,"max_risk_pct":2.0}' --json
| Parameter | Default | Description |
|---|---|---|
sizing.win_rate |
— | Win probability as a fraction in [0, 1]. |
sizing.avg_win |
— | Average winning return normalized to a consistent stake or unit of risk (for example, an R-multiple). |
sizing.avg_loss |
— | Average losing return magnitude on the same normalized basis. |
sizing.fraction_multiplier |
0.5 |
Multiplier on the raw Kelly fraction (half-Kelly = 0.5). |
sizing.max_risk_pct |
2.0 |
Hard cap on account risk in percentage points (1 means 1%); must be in (0, 100]. |
The Kelly fraction is win_rate − (1 − win_rate) / (avg_win / |avg_loss|), then scaled
by sizing.fraction_multiplier and capped by sizing.max_risk_pct. On a non-positive edge the tool reports
status="kelly_no_edge" and a suggested volume of 0.0.
trade_journal_analyze reports avg_win and avg_loss in account currency. Those
raw PnL averages are not Kelly inputs because deal sizes and capital at risk can vary.
Normalize each historical outcome to a consistent stake or initial risk before
computing the average return metrics supplied here.
Journal side filters distinguish two directions: side=long|short selects the
economic position side of realized exits, while side=buy|sell selects the
broker fill direction. A sell fill can close a long and a buy fill can close a
short, so inspect the response's side_filter.dimension when automating cohorts.
Portfolio stop risk is the gross sum of each ticket's remaining loss from its
current MT5 mark to its stop. This measures equity at risk now; it does not reuse
the original entry-to-stop loss after unrealized P&L has changed. It is a
conservative path-risk measure, not a same-symbol net-exposure estimate; a path
can trigger both sides of a hedge sequentially. Pending-order stop risk is
reported separately as contingent and is included in the total only when
include_pending=true.
notional_value and portfolio notional fields are linearized account-currency
exposures derived from the broker's tick value and tick size. The per-position
contract_price_product diagnostic preserves raw volume × contract_size × price
with the explicit unit contract_size_times_price; it must not be compared with
account equity or summed across unlike instruments.
trade_get_open uses the same distinction on each position row.
notional_account is comparable with account balance/equity and names its
currency in notional_account_currency; notional_quote is the raw contract
price product in notional_quote_currency. If broker tick economics are not
available for a cross-currency conversion, notional_account is null and
notional_account_unavailable_reason explains why. Never sum notional_quote
across instruments with different quote currencies.
trade_var_cvar_calculate
Estimates one-bar Value at Risk (VaR) and Conditional VaR (CVaR, a.k.a. Expected Shortfall) for the current open positions — either the whole portfolio or a single symbol.
# Portfolio VaR/CVaR at 95% over one H1 bar
mtdata-cli trade_var_cvar_calculate --timeframe H1 --lookback 500 --confidence 0.95 --json
# Symbol-scoped, parametric/Gaussian, percentage returns
mtdata-cli trade_var_cvar_calculate EURUSD --method parametric \
--transform pct --lookback 300 --json
| Parameter | Default | Description |
|---|---|---|
symbol |
— | Restrict to one symbol's exposure; omit for the full portfolio. |
timeframe |
H1 |
Return interval and the one-bar holding period. |
lookback |
500 |
Historical bars used to build the return distribution. |
confidence |
0.95 |
Confidence fraction (0.95, 0.99); must satisfy 0 < confidence < 1. |
method |
historical |
historical (empirical tail) or parametric (Gaussian model). |
transform |
log_return |
Return transform: log_return or pct. |
min_observations |
50 |
Minimum aligned observations before estimating risk. |
Output includes var and cvar, position/exposure counts, and — by detail level —
per-position and per-symbol exposure breakdowns. With no open positions, --detail full
returns the legacy zero-filled arrays.
VaR/CVaR converts percentage price shocks to account-currency P&L with each
symbol's broker-provided tick value and tick size (pnl_model is
tick_value_linear_sensitivity). Positions without usable tick economics are rejected
rather than mixed into a portfolio in incompatible quote-currency units. The model is
linearized and does not include gaps, spread changes, swaps, or nonlinear payoff effects.
Every open position must have usable account-currency valuation and every
included symbol must have usable history. The tool returns
portfolio_var_incomplete instead of silently calculating a smaller portfolio
when any position lacks valuation inputs or any symbol history is unavailable.
trade_stress_test
Applies deterministic percentage price shocks to open positions and reports the P&L impact. Useful for "what if EURUSD drops 2% and everything else 3%?" scenarios.
# Per-symbol shocks
mtdata-cli trade_stress_test --shocks '{"EURUSD":-2.0,"GBPUSD":-1.5}' --json
# Wildcard shock applied to every position without an explicit entry
mtdata-cli trade_stress_test --shocks '{"*":-3.0}' --detail full --json
| Parameter | Default | Description |
|---|---|---|
shocks |
(required) | Per-symbol percentage shocks, e.g. {"EURUSD": -2.0}. Use "*" as a fallback for symbols without an explicit entry. |
include_unshocked |
false |
Include positions that received no shock (no exact match and no "*" fallback). |
detail |
compact |
full adds per-position diagnostics. |
Output is a list of items (one per evaluated position) with ticket, symbol,
side, volume, shock_pct, current_price, shocked_price, and pnl_impact, plus
totals: total_pnl_impact, positions_total/evaluated/shocked, and — when account
metadata is available — equity_before/equity_after/impact_pct.
Caveats
- All three tools read live MT5 state; results change as positions and quotes move.
- A
Noneposition/order response from MT5 is a failed snapshot, not an empty book. Snapshot-dependent analytics return a*_snapshot_unavailableerror; empty tuples/lists remain valid empty books. - VaR/CVaR assume the recent return distribution persists and use a single-bar holding period; they are not a guarantee of maximum loss.
- Stress shocks are deterministic and linear in price; they do not model spread widening, gaps, swaps, or correlation breaks.
- Kelly sizing is only as good as its inputs — estimate
win_rateand normalized average win/loss returns from a sufficient out-of-sample track record.
See also
- BARRIER_FUNCTIONS.md — TP/SL hit probabilities
- TRADING_SAFETY.md — Execution preview and guardrails
- GLOSSARY.md — VaR, CVaR, Kelly
- SAMPLE-TRADE-ADVANCED.md — Risk gates in a full workflow