Skip to Content
Analysis

Analysis

Analysis is a read-only surface. It renders runs that already exist — it never spawns a worker and never executes Pine. That constraint is what lets it stay fast, and it is enforced, not aspirational.

Screenshot pending

awaiting screenshots/analysis-overview.png in public/

Analysis Overview. Reads an existing run — it never starts a worker or a backtest.

How it opens

The chart, the Optimizer and Analysis all stay mounted. Analysis replaces the popup actions rather than adding a layer on top:

  • Open analysis → the Overview for the current run.
  • Open in Snipe → the exact trial you had selected, addressed by its original trial index — not merely the run it came from.

The main Vela chart and the Pine editor are untouched throughout.

Layout

A 224 px sidebar lists the sections. The sidebar becomes a drawer at a max-width: 1023px media query, with full keyboard containment: focus enters it, Tab and Shift+Tab are trapped inside, the background is made inert, Escape closes it, and focus returns to the trigger.

Sections

SectionWhat it shows
OverviewThe run at a glance — objective, progress, best trial
TrialsEvery trial, sortable, filterable, paginated
Parameter AnalysisHow each parameter relates to the objective
Performance and RiskReturn against drawdown, trade by trade
Trial ComparisonTwo trials side by side
SnipeA focused single-trial view
Run DetailsThe captured configuration, seed, source and search space

Screenshot pending

awaiting screenshots/analysis-trials.png in public/

Trials. Selection, filters, sorting and pagination are remembered per job.

State that survives a reload

This is the part that makes the surface usable rather than a novelty.

Per job (reset when you open a different job):

  • selected section
  • filters, sorting, pagination
  • focus and scroll position

Per run:

  • display preferences, stored under Astra-prefixed localStorage keys

Cross-surface:

  • trial selection is tracked by the original trial index, so sorting or applying a filter that hides the selected row does not lose your selection
  • CSV export respects the filters you have applied; JSON export always contains the full run; Share card renders a PNG of the run from the same snapshot

Linking to a run

Analysis links resolve against IndexedDB records and need no migration. A link naming a job or trial that no longer exists resolves to an explicit empty state rather than an error — and malformed trial links stay unavailable even after saved snapshots refresh.

Legacy optimizer-detached.html URLs redirect in the same tab, preserving the validated job and trial identifiers so old bookmarks keep working.

Charts

Apache ECharts 6.1.0 is pinned exactly and vendored locally. It backs:

  • stepped best-objective progression
  • Snipe equity and comparison equity
  • parameter and trade distributions
  • parameter importance bars
  • the performance/risk scatter
  • the monthly-performance heatmap

Parallel coordinates are the one deliberate exception — a specialised SVG renderer is retained rather than approximated with a general charting library.

Why ECharts and not ApexCharts

This was a deliberate swap, not a preference.

Licensing. ApexCharts requires an OEM / Redistribution licence once embedded in a third-party product. ECharts is Apache-2.0, which fits the project’s licence posture — see Architecture decisions.

Build shape. The full ECharts bundle is vendored, not the smaller common build, because the common bundle lacks the calendar-heatmap series the monthly-performance panel needs. The Apache-2.0 licence text and the NOTICE file are both vendored alongside it.

Two ECharts-specific requirements the code must respect

Both were learned the hard way, and both produce a blank chart rather than an error.

1. Explicit resize. ECharts does not reflow when its container changes size. Every chart is observed with a ResizeObserver and must call chart.resize() on change. Without it a chart is correct at one width and clipped or stretched at every other.

2. Deferred initialisation. Initialising a chart while its container is hidden — a tab that is not selected, a panel that is still collapsed — gives a 0×0 canvas, and the chart silently renders nothing. Initialisation is therefore deferred until the slot is actually visible.

Three chart footguns found during the ECharts port

These were caught by pixel-diffing a real rendered canvas, not by inspecting the option object — which is the transferable lesson.

  • A series override that replaces rather than merges. Passing series: [{ lineStyle, areaStyle }] replaces the whole series array and silently dropped 40 drawdown points. Styling now merges through a per-index channel.
  • A step that was not a step. step is a series-level ECharts option, not a lineStyle property. Placed on lineStyle it is a no-op, and the best-so-far curve renders as a plain interpolated line. Verified by pixel count: series-level step 3360 painted pixels, plain 3696, lineStyle-level step 3696 — zero pixels different from plain.
  • HTML in a canvas. Heatmap cell labels were emitted as HTML, which a canvas renderer does not parse. They are now ECharts rich text ({val|137.5}). Relatedly, a numeric formatter left bound to a now-categorical year axis rendered every year as an em dash.

Three further details worth knowing, because they affect how you read the output:

  • Equity uses recorded millisecond timestamps where they exist. When they do not, the chart falls back to bar index and labels that fallback explicitly rather than letting you assume a time axis.
  • Weighted trade bins keep their recorded counts. A bin is not normalised away.
  • Missing values stay missing. A metric that was never recorded renders as unavailable, not as zero. Scatter points retain their original trial indices so a point is still traceable to its row in the table.

The search-space metadata is normalised into the shape the presentation layer expects without mutating the persisted record — the stored job stays exactly as it was written.

Load params

Load params applies a trial’s values back to the optimizer configuration through the existing configuration controller, so active-run protection still applies: you cannot retune a run that is currently executing.

Last updated on