API reference
The backtest of a screener filter set
/track-recordKey requiredThe backtest of a screener filter set
Returns the record of the past trades of one family (or all five) whose facts at entry matched every filter: the TradingPal screener's Track record panel for the same filters, computed from the same stored past-trade ledger. filters takes the same predicate list as /scan. Filters that describe today rather than the trade date cannot cut past trades and come back under filters.left_out. edge=on counts only the trades the Edge Filter kept (tiers S to C). Under minimum_trades matched trades, track_record is null and note says why. unfiltered is the same family's record without the filters, for comparison. Needs a key on an account with Premium+.
Authentication
Needs a key: send Authorization: Bearer tp_live_… (see Authentication). Counted against the key's limits of 60 requests a minute and 10,000 a day.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
family | query | string (enum) | no | Keep one family: rising_wedge, falling_wedge, bullish_pennant, bearish_pennant, triangle. Omit for all five. Anything else answers 400 bad_request. One of rising_wedge, falling_wedge, bullish_pennant, bearish_pennant, triangle. |
filters | query | string | no | The screener's own filters, URL-encoded: a JSON array of predicates, ANDed together; a kind may repeat (for example two moving-average lengths). Numeric kinds take op (<, <=, >, >=, =) and value, or op between with value and upper (inclusive), choice and text kinds take value, boolean kinds take value true or false, and the moving-average, performance and high/low kinds also take period. For example filters=[{"kind":"price","op":">","value":10},{"kind":"rsi","op":"<","value":40},{"kind":"asset_type","value":"stock"}]. Every predicate must match; a setup with no evidence for a kind never matches. The kinds, with what each takes and means, are the x-filters list on this operation (the reference page shows it as a table). A malformed list answers 400 bad_request with the reason. |
edge | query | string (enum) | no | on: only the past trades the Edge Filter kept (tiers S to C); off: every trade. One of off, on. |
X-TradingPal-Client | header | string | no | Optional name of the agent or script making the call. It is recorded in the account's usage log so the user can see which client made which calls; the User-Agent is recorded when it is absent. |
Request
curl "https://api.tradingpal.io/api/v1/track-record?family=value&filters=value&edge=value" \
-H "Authorization: Bearer $TRADINGPAL_API_KEY"Response
JSON. The fields this route adds are below; every response also carries the envelope fields (api_version, as_of_session, data_version, source, disclaimer).
| Field | Type | Description |
|---|---|---|
family | string · nullable | The family asked for (rising_wedge, falling_wedge, bullish_pennant, bearish_pennant, triangle); null for all five. |
label | string | The family's display name, or All patterns. |
edge | object | The Edge Filter side the record was cut from. |
edge.requested | string (enum) | off or on, as asked. One of off, on. |
edge.applied | boolean | False when on was asked of a ledger that seals no Edge Filter tiers; the record is then every trade's. |
filters | object | How the filters were used. |
filters.applied | array of object | The predicates that cut the past trades, canonical. |
filters.left_out | array of object | Predicates the record ignores, each with its reason: they describe today (market cap, security type, exchange, sector, country, phase, distance, the model's estimates) or the ledger holds no values for them. |
matched_trades | integer | Past trades that matched every applied filter. |
too_few | boolean | True under minimum_trades matched trades; track_record is then null. |
minimum_trades | integer | The fewest matched trades a record is given for. |
track_record | Track record | The record of the matched trades (scope filtered). Null when too_few. |
unfiltered | Track record | The same family's record without the filters, on the same Edge Filter side, for comparison (scope unfiltered). |
note | string · nullable | Why no win rate is given, when too_few. |
Example response
{
"api_version": "v1",
"data_version": "patterns-D-2026-11-12",
"family": "falling_wedge",
"label": "Falling wedge",
"edge": {
"requested": "off",
"applied": true
},
"filters": {
"applied": [
{
"kind": "min_rr",
"op": ">=",
"value": 2
}
],
"left_out": [
{
"kind": "market_cap",
"op": ">",
"value": 1000000000,
"reason": "describes today, not the trade date, so it cannot cut past trades"
}
]
},
"matched_trades": 1287,
"too_few": false,
"minimum_trades": 30,
"track_record": {
"scope": "filtered",
"family": "falling_wedge",
"symbol": null,
"win_rate_pct": 54,
"sample_size": 1287,
"wins": 695,
"losses": 592,
"avg_win_pct": 8.1,
"avg_loss_pct": -4.2,
"median_win_pct": 6.3,
"avg_trade_pct": 2.4,
"avg_r": 0.41,
"expected_r": 0.41,
"profit_factor": 1.61,
"avg_hold_bars": 11.2,
"backtest_range": [
"2006-09-25",
"2026-09-25"
],
"measured": "Every detector-confirmed breakout … open trades are excluded.",
"summary": "Falling wedge: 1,287 resolved trades from 2006-09-25 to 2026-09-25; 54% reached the target, average win +8.1%, average loss -4.2%, median win +6.3%."
},
"unfiltered": {
"scope": "unfiltered",
"family": "falling_wedge",
"symbol": null,
"win_rate_pct": 54,
"sample_size": 1287,
"wins": 695,
"losses": 592,
"avg_win_pct": 8.1,
"avg_loss_pct": -4.2,
"median_win_pct": 6.3,
"avg_trade_pct": 2.4,
"avg_r": 0.41,
"expected_r": 0.41,
"profit_factor": 1.61,
"avg_hold_bars": 11.2,
"backtest_range": [
"2006-09-25",
"2026-09-25"
],
"measured": "Every detector-confirmed breakout … open trades are excluded.",
"summary": "Falling wedge: 1,287 resolved trades from 2006-09-25 to 2026-09-25; 54% reached the target, average win +8.1%, average loss -4.2%, median win +6.3%."
},
"note": null
}Errors
Errors are JSON with the Error shape; the fix field says what to do.
| Status | Code | Meaning | Fix |
|---|---|---|---|
| 401 | missing_api_key | The request had no Authorization: Bearer tp_live_… header and the route needs one. | Send the header on every keyed call. Only the demo, families and universe routes answer without it. |
| 401 | invalid_api_key | The key is not one we know, or it has been revoked. | Create a new key on the API Keys page and replace it wherever the old one was stored. |
| 403 | forbidden | The account behind the key is not on Premium+. API and MCP access requires Premium+ subscription at $30/month; Premium alone does not unlock the API. | Get Premium+ ($30/month, all of the benefits in Premium plus API and MCP access) from the Quickstart, where a first subscription is $0 for 1 month, or switch an existing Premium subscription to it there, then retry with the same key. |
| 429 | rate_limited | The key passed 60 requests in a minute or 10,000 in a day, or a keyless caller passed 30 a minute from one IP. | Wait details.retry_after_seconds, then retry. Cache the nightly answers; they do not change until the next session. |
| 400 | bad_request | A query parameter is malformed: limit or offset is not an integer, outcome is not all, resolved or open, a scan filter (family, direction, status, max_distance_pct) is not one of its allowed values, or filters is not a valid predicate list (the message names the predicate). | The message names the parameter. Correct it and retry. |