Skip to content

Commit 796ed19

Browse files
luisleo526claude
andcommitted
docs: dedicated trading-metrics reference page; link from README + report schema
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 1bd075c commit 796ed19

4 files changed

Lines changed: 102 additions & 1 deletion

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ bash tutorial/run.sh # MACD backtest end-to-end
162162
| 📖 **[cdocs.pineforge.dev](https://cdocs.pineforge.dev)** | Full C ABI reference, lifecycle, report schema, configuration knobs, magnifier, FFI bindings, ABI stability contract |
163163
| 🚀 **[Getting Started](https://cdocs.pineforge.dev/getting_started.html)** | 60-second build + install + smoke test |
164164
| 🧪 **[Tutorial: MACD on BTC/USDT](https://cdocs.pineforge.dev/tutorial_macd.html)** | End-to-end annotated walkthrough |
165+
| 📊 **[Trading metrics reference](https://cdocs.pineforge.dev/metrics.html)** | Every report metric (ABI v2): definitions, units, NaN rules, TV / quant-library validation status |
165166
| 🔌 **[FFI from Python](https://cdocs.pineforge.dev/ffi_python.html)** | Complete `ctypes` mirror — paste-ready |
166167
| 🦀 **[Calling from Rust](https://cdocs.pineforge.dev/examples_rust.html)** | Idiomatic `libloading` wrapper |
167168
| 🧰 **[CMake integration](https://cdocs.pineforge.dev/integration_cmake.html)** | `find_package(PineForge)` recipe |

docs/Doxyfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ INPUT = ../include/pineforge/pineforge.h \
9191
pages/integration-cmake.md \
9292
pages/lifecycle.md \
9393
pages/report-schema.md \
94+
pages/metrics.md \
9495
pages/configuration.md \
9596
pages/magnifier.md \
9697
pages/timeframes.md \

docs/pages/metrics.md

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# Trading metrics reference {#metrics}
2+
3+
@tableofcontents
4+
5+
Every backtest report (`pf_report_t::metrics`, ABI v2) carries a computed
6+
trading-metrics suite plus the raw per-script-bar equity curve. This page is
7+
the complete metric list with definitions, units, NaN rules, and validation
8+
status. The per-field doxygen in `<pineforge/pineforge.h>` is the canonical
9+
contract; this page is the readable index of it.
10+
11+
@see @ref report_schema for where the blocks sit inside `pf_report_t`.
12+
13+
## Blocks
14+
15+
| Block | Type | Scope |
16+
| --- | --- | --- |
17+
| `metrics.all` | #pf_trade_stats_t | All closed trades |
18+
| `metrics.longs` | #pf_trade_stats_t | Long trades only |
19+
| `metrics.shorts` | #pf_trade_stats_t | Short trades only |
20+
| `metrics.equity` | #pf_equity_stats_t | Equity-curve stats (all-trades only, like TradingView) |
21+
| `equity_curve[]` | #pf_equity_point_t | One point per script bar: `{time_ms, equity, open_profit}` |
22+
23+
## Trade statistics (per block: All / Long / Short)
24+
25+
Conventions: loss-side currency fields are **positive magnitudes**; percent
26+
fields are on the 0–100 scale; per-trade `%` derives from
27+
`pf_trade_t::pnl_pct` = **net return-on-cost** (`net pnl / (entry_price ×
28+
qty × pointvalue) × 100`, TradingView-arbitrated).
29+
30+
| Field | Definition | Undefined → NaN when |
31+
| --- | --- | --- |
32+
| `num_trades` | Closed trades in the block ||
33+
| `num_wins` / `num_losses` | Trades with `pnl > 0` / `pnl < 0` ||
34+
| `num_even` | Trades with `pnl == 0.0` exactly; break both streaks, excluded from win/loss averages ||
35+
| `percent_profitable` | `100 · num_wins / num_trades` | no trades |
36+
| `net_profit` | Σ pnl (account currency, net of commission) ||
37+
| `net_profit_pct` | `net_profit / initial_capital · 100` | capital ≤ 0 |
38+
| `gross_profit` (+`_pct`) | Σ winning pnl (pct vs initial capital) | capital ≤ 0 (pct) |
39+
| `gross_loss` (+`_pct`) | Σ \|losing pnl\| — positive magnitude | capital ≤ 0 (pct) |
40+
| `profit_factor` | `gross_profit / gross_loss` | zero gross loss |
41+
| `avg_trade` (+`_pct`) | `net_profit / num_trades`; pct = mean `pnl_pct` | no trades |
42+
| `avg_win` (+`_pct`) | `gross_profit / num_wins`; pct = mean win `pnl_pct` | no wins |
43+
| `avg_loss` (+`_pct`) | `gross_loss / num_losses` (positive); pct = mean negated loss `pnl_pct` | no losses |
44+
| `ratio_avg_win_avg_loss` | `avg_win / avg_loss` | either side empty |
45+
| `largest_win` (+`_pct`) | Largest win in **currency**; `_pct` = **independent max** of per-trade % (TV convention — may come from a different trade) | no wins |
46+
| `largest_loss` (+`_pct`) | Same, loss side, positive magnitudes | no losses |
47+
| `commission_paid` | Σ `pf_trade_t::commission` (captured at close from actual deductions) ||
48+
| `expectancy` | `(num_wins/n)·avg_win − (num_losses/n)·avg_loss`, currency per trade | no trades |
49+
| `max_consecutive_wins` / `_losses` | Longest run; even trades reset both ||
50+
| `avg_bars_in_trade` / `_wins` / `_losses` | Mean of `exit_bar − entry_bar + 1` (**inclusive** of the entry bar, TV convention), script bars | empty set |
51+
52+
## Equity statistics (`metrics.equity`, all-trades only)
53+
54+
The equity curve samples `initial_capital + net_profit + open_profit` at
55+
every **script-bar close**, timestamped with the script-bar **open** time
56+
(magnifier-invariant).
57+
58+
| Field | Definition | Undefined → NaN when |
59+
| --- | --- | --- |
60+
| `max_equity_drawdown` (+`_pct`) | Peak-to-trough equity drop, positive currency; pct vs the peak in effect | — (0 when flat) |
61+
| `max_equity_runup` (+`_pct`) | Trough-to-peak rise, **trough resets on each new equity peak** (mirrors the engine's intra-run extremes); pct vs that trough ||
62+
| `buy_hold_return` (+`_pct`) | `initial_capital · (last_close/first_open − 1)` | first open non-finite or ≤ 0 |
63+
| `sharpe_tv` / `sortino_tv` | Month-end-resampled equity simple returns (chart tz, open-time bucketing), risk-free 2 %/yr (2/12 per month), annualized ×√12. Sharpe: sample (N−1) stddev. Sortino: population downside deviation vs the monthly risk-free | < 2 monthly returns, or zero deviation |
64+
| `sharpe_bar` / `sortino_bar` | Same construction over per-script-bar returns, annualized by **observed bar density** (`bars/yr = (len−1)/calendar span`) — not a fixed calendar formula | < 2 returns, or zero deviation |
65+
| `cagr` | `100 · ((final_equity/initial_capital)^(1/years) − 1)`, calendar span | span ≤ 0, or either side ≤ 0 |
66+
| `calmar` | `cagr / max_equity_drawdown_pct` (both percent → dimensionless) | zero drawdown |
67+
| `recovery_factor` | `net_profit / max_equity_drawdown` | zero drawdown |
68+
| `time_in_market_pct` | `100 ·` script bars with an open position at close `/ total script bars` | empty curve |
69+
| `open_pl` | Mark-to-market open profit at the final bar ||
70+
71+
@note Metrics are only meaningful when `strategy_get_last_error()` returns
72+
an empty string: `run()` captures exceptions, so a failed run yields a
73+
truncated curve and metrics over the truncated prefix.
74+
75+
## Validation status
76+
77+
| Surface | Validated against | Result |
78+
| --- | --- | --- |
79+
| Trade statistics (counts, PF, percent bases, averages, largest-%, bars) | Real TradingView Strategy Tester export (`composite-4emarsi-integration-01`, 336 trades, All/Long/Short panels) | Match within TV 2-dp rounding; three TV conventions arbitrated and adopted (net return-on-cost `pnl_pct`, independent largest-%, inclusive bar counts) |
80+
| Equity statistics (max DD ±%, Sharpe/Sortino both variants, CAGR, Calmar, recovery) | quantstats 0.0.81 + empyrical-reloaded 0.5.12 (`scripts/crossvalidate_metrics.py`) | ≤ 1e-11 relative on two corpus strategies; residual deltas proven to be library conventions |
81+
| Closed-form unit oracles | `tests/test_metrics.cpp` (e.g. monthly Sharpe 19/20, Sortino 114/61 exact rationals) | Bit-level |
82+
83+
Known open deltas: TradingView's "Max run-up (close-to-close)" uses a
84+
different run-up definition than the engine's trough-reset semantics; TV's
85+
own Sharpe/Sortino/max-DD panel values have not yet been exported for
86+
comparison (engine values are library-validated meanwhile). TV-only fields
87+
not computed: outliers, average run-up/duration, intrabar excursion
88+
variants.
89+
90+
## Consuming from Python
91+
92+
```python
93+
report.metrics.all.profit_factor # ctypes mirror, see FFI page
94+
report.metrics.equity.sharpe_tv
95+
curve = report.equity_curve[:report.equity_curve_len]
96+
```
97+
98+
Mirror classes and the mandatory `pf_abi_version()` guard: @ref ffi_python.

docs/pages/report-schema.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,8 @@ formatting, no per-bar branch.
163163
## Metrics (ABI v2)
164164

165165
`pf_report_t::metrics` is a `pf_metrics_t` — four embedded blocks
166-
computed at report time:
166+
computed at report time. The complete per-metric reference (definitions,
167+
units, NaN rules, validation status) lives on @ref metrics.
167168

168169
| Block | Type | Scope |
169170
| --- | --- | --- |

0 commit comments

Comments
 (0)