Accepted under the repository owner-approved DCO exception for this maintenance batch; the original author commit is preserved without adding an attestation on their behalf.
The x-ray took the sample standard deviation of the losing bars only, which measures how much the losses differ from each other. A book that loses the same amount on every losing bar reported zero downside deviation, and a single loss reported none. #1693 and #1727 already moved the Sortino in calc_metrics and the options engine to the zero-target root mean square over every return period; this is the remaining call site.
Signed-off-by: Rudy Celekli <rudy@gradiahq.com>
redact_text() and the structured-payload key matcher missed several common
credential shapes that carry no key label the existing patterns can see:
- bare GitHub fine-grained (github_pat_), GitLab (glpat-), npm (npm_) and
Hugging Face (hf_) tokens
- PEM / PGP private-key blocks in free text (whole block redacted; an
unterminated block is redacted to the end of the text)
- Cookie / Set-Cookie header values
- bare Stripe (sk_/rk_ live/test), Google API (AIza) and AWS ASIA keys
- cookie and private_key field names in structured payloads
The token shapes are added to the shared token alternation, the private-key
and cookie patterns run first in redact_text, and the field markers extend
_SENSITIVE_ARG_MARKERS. Each family keeps its own test file.
Consolidates #1866, #1867, #1868, #1869 and #1870.
Signed-off-by: Rudy Celekli <rudy@gradiahq.com>
The elliott-wave engine and the harmonic fallback detector find swing points
with a centred rolling window (rolling(2w+1, center=True)), so a pivot at bar t
is only known once bar t+w has closed. Both engines stamped the signal on bar t,
i.e. w bars of future prices were already baked into it, and the zigzag/merge
step then ran over the whole series, so a later, more extreme pivot could
silently replace an earlier one.
Detect the swings once, then replay them in time order: at each pivot only the
pivots confirmed so far are zigzagged/merged, patterns are matched on that
prefix, and the signal is stamped on the confirmation bar (pivot + swing_window).
Truncating the history after bar t no longer changes any signal at or before t.
Signed-off-by: Rudy Celekli <rudy@gradiahq.com>
The Trades tab rendered trade_log, which the runs API caps at the first
500 rows of trades.csv. Trades were shown oldest first, and a run with
more than 500 trades lost its most recent ones in the table and in the
CSV download.
The same response already carries every row in artifacts_trades_csv, and
the strategy dashboard reads it with a trade_log fallback. Move that
fallback into runTrades() in lib/runReports.ts and use it for the Trades
tab, its CSV download and the dashboard.
The Trades tab now sorts newest first. Clicking the time header flips
the order; aria-sort reports the direction.
Signed-off-by: alex <alexwan02@163.com>
_detect_market had no pattern for the documented USD-M perpetual spelling
(BASE-USDT-PERP, parsed and documented by ccxt_loader._parse_ccxt_symbol
and binance_loader), so BTC-USDT-PERP fell through _MARKET_PATTERNS to the
"a_share" default. A leveraged perpetual then ran under China A-share
rules: 100-share board-lot rounding turned every computed position size
into a zero_size rejection, every rebalance was dropped, and the backtest
reported trade_count 0 with no surfaced error.
Add the pattern next to the other USDT pairs with the loader's own base
alphabet ([A-Z0-9]+, so numeric bases like 1000PEPE-USDT-PERP are covered)
and pin it with parameterized regressions.
Closes#1863
Only the USDT spellings were separator-aware in the market classifier, so
BTC-USDC, ETH/USDC and BTC-BUSD matched no pattern and fell through to the
a_share default (T+1, no shorting, CNY), while the unseparated BTCUSDC and
correlation.infer_market already treated them as crypto.
Signed-off-by: Rudy Celekli <rudy@gradiahq.com>
USDT-pair patterns used [A-Z]+ for the base asset, so 1INCH-USDT,
1INCH/USDT, 1INCHUSDT, 1000PEPE-USDT and 1000SHIBUSDT matched no pattern
and fell through to the a_share default (T+1, no shorting, CNY, tushare).
Require at least one letter in the base so digit-only A-share/HK codes
are still never treated as crypto.
Signed-off-by: Rudy Celekli <rudy@gradiahq.com>
Loading a session fetched GET /runs/{run_id} one message at a time for
every assistant turn without stored metrics, and each run card without
an equity curve fetched it again on mount. Most turns are plain
replies, and backtest cards pulled the full chart payload just to draw
a sparkline.
History loading now requests GET /runs/{run_id}/preview for all those
turns at once, deduplicated by run id, and awaits them in message
order. Live completion and RunCompleteCard use the same endpoint.
lib/runCards.ts turns a preview into card fields for both the history
and live paths; a failed preview still shows the fallback card, as a
failed run fetch did before. report_worthy comes from the backend, so
the client-side rule in lib/runReports.ts and its test helper are
removed.
Signed-off-by: alex <alexwan02@163.com>
The chat UI fetched the full GET /runs/{run_id} for every assistant
turn in a loaded session, only to read metrics and the equity curve
and to decide whether the turn deserves a run card. Every turn owns a
run directory, so this ran for plain replies too, and for backtests it
pulled tens of megabytes of chart payload.
GET /runs/{run_id}/preview returns just metrics, the first 1000 equity
points (same shape as the full response) and report_worthy. The
response is built and encoded in the threadpool. is_report_worthy
decides from the artifact file names and run_card.json, so a plain
reply costs one directory listing and no file reads.
src/api/run_artifacts.py holds the metrics parsing and equity
projection shared by the detail and preview responses. Both routes
read files through ui_services.load_json_file and load_csv_records,
which replace the private copies in runs_routes; load_csv_records
gains a limit that stops reading early. The unused api_server
re-exports of those copies are dropped. GET /runs/{run_id} now
declares its schema through `responses`, since it returns pre-rendered
bytes that FastAPI does not re-validate; the OpenAPI document is
unchanged.
On 142 local runs the preview matches the full response's metrics and
equity curve, and report_worthy matches the previous client-side rule
everywhere.
Signed-off-by: alex <alexwan02@163.com>
GET /runs/{run_id} built its response synchronously inside an async
handler and returned the model, so both the build and FastAPI's JSON
encoding ran on the event loop. A full payload for a multi-symbol
backtest is ~70MB and stalled every other request for about a second.
Build and encode inside run_in_threadpool and return the rendered
bytes. The chart_payload/chart_symbol variants move into the same
call. Response bytes are unchanged: sha256 matches the previous
implementation on six real runs, full and summary variants.
The encoder itself is a single native call that holds the GIL, so a
~200ms stall per full payload remains; shrinking that needs a smaller
default payload and is left out of this change.
Signed-off-by: alex <alexwan02@163.com>