Architecture
IndicaView is assembled from an open-source stack rather than hand-rolled. The project contributes the workspace, the optimizer, the analysis surface and the orchestration. It does not contain a charting engine or a simulation engine.
Two runtimes, one set of strategies
| Track | Entry point | Engine |
|---|---|---|
| Interactive (browser) | app/index.html | Vela + Vela PineTS |
| Autonomous (terminal) | python orchestrator/sweep.py | pinets-cli subprocess |
| Prop-firm | python propfirm/evaluate.py | LuxAlgo Monte Carlo CLI |
Three engines behind one strategy file
The three tracks are not variants of the same runtime. One runs a browser library inside the page, one drives a Python subprocess from the terminal, one calls a Monte Carlo CLI. Each has its own entry point and its own invocation style, and the engines are not interchangeable with one another.
What they do not each have is their own strategy. app/strategies/
is the single crossing point, and because it is the only thing the three share, a change
of engine never requires a change of strategy file. The strategy you backtest in the
browser is the file the sweep reads.
Repository layout
app/ the browser application
index.html the shell — topbar, chart, editor, panels
app.js bootstrap: chart, editor, run, trades
idb_bar_cache.js persistent + throttled candle cache
responsive.css breakpoints, phone layout, hit-area tokens
tb-select.css themed select triggers
auth.css sign-in and account-area styling
share-card.css result-card presentation
js/auth/ optional Supabase sign-in and My Account
js/share/ client-side result-card export
js/optimizer/ the optimizer (engine, workers, UI, algorithms)
js/optimizer.bundle.js the concatenated build of the above
table-pins.js measures real column widths for pinned columns
overflow-menu.js collapses button groups into a kebab
enhance-select.js upgrades native selects into themed controls
market-bar.js responsive market controls and the phone sheet
strategies/ the .pine sources
vendor/ vendored browser bundles (git-ignored)
__smoke_*.cjs browser test suites
pinets_cli/ Python wrapper around the AGPL CLI
orchestrator/ sweep, hyperopt, rank, report, derive_r_multiples
propfirm/ evaluate, firms, sweep, rank, report
tests/ pytest suites + offline fixtures
examples/ ready-to-run YAML configs
scripts/ vendor copy, installer, bundler
docs-site/ this documentation siteThe browser application
A static page. There is no bundler framework, no server-side application logic and
no API of our own; IndicaView serves the page and the browser does the rest. The build
step copies prebuilt vendor bundles into app/vendor/ and concatenates the optimizer
modules into a single bundle.
Vendored dependencies
| Bundle | Package | Licence |
|---|---|---|
vela.global.js | @luxalgo/vela | Apache-2.0 + attribution |
vela-pinets.global.js | @luxalgo/vela-pinets | AGPL-3.0 |
monaco/ and vs/ | monaco-editor | MIT |
echarts.min.js | echarts | Apache-2.0 |
app/vendor/ is git-ignored and regenerated by the postinstall hook. It is never
committed, and it is never served from a reference directory.
The vendor directory is a build product, not source
Nothing under app/vendor/ is authored here. The build step copies
prebuilt bundles in, and the postinstall hook regenerates the whole
directory — which is the whole reason it is git-ignored, never committed, and never served
from a reference directory.
The same build step does a second and quite different job: it concatenates the
optimizer modules from app/js/optimizer/ into the single
app/js/optimizer.bundle.js. Third-party bundles are copied in whole; the
project’s own modules are joined together. Both land under the same tree, and neither is
treated as interchangeable with the other. Note also that the table above lists
monaco/ and vs/ as two directories for one package, and that
three of the four entries are Apache-2.0 or MIT while
vela-pinets.global.js is AGPL.
Themes
The application is dark-first. Theme is expressed through CSS custom properties, and
switching it re-themes the chart, the editor and every panel from one source — the editor
swaps its syntax token set rather than maintaining a second copy of the palette. A
trad:theme-change event fans the change out to components that redraw on theme change.
One palette, swapped rather than duplicated
There is no second copy of the palette in the tree. The editor is the case worth naming: it swaps its syntax token set to match the active theme rather than carrying a palette of its own, which is what stops the two drifting apart over time.
Fan-out is explicit rather than ambient. A trad:theme-change event reaches
the components that redraw on theme change, and that is how the chart and every panel
follow one switch from one source instead of each polling the theme.
The optimizer
A Web Worker pool evaluating trials, with seven interchangeable algorithms behind a small interface. The UI never blocks: configuration, results and analysis all stay responsive while trials complete.
What separates the optimizer's three layers
Three layers, held apart for three different reasons:
- Algorithms — seven of them, interchangeable because each sits behind the same small interface. The loop does not know which one it is driving.
- Workers — the pool that evaluates trials. This is the layer that buys responsiveness: configuration, results and analysis all stay live while trials complete.
- UI — configuration and results, sitting above the pool and never inside it.
In the repository these are separate modules under app/js/optimizer/ —
engine, workers, UI, algorithms — and the build step concatenates them into
js/optimizer.bundle.js. So the layering is a source structure, not a load-time
one: the separation that actually costs something at runtime is the worker boundary.
See Optimizer for the algorithms, the worker-pool sizing model and the metrics.
The Python track
Scripts rather than a service. Each is a single-purpose entry point that reads a YAML config,
calls out to a CLI across a process boundary, and writes artifacts under results/<run-id>/.
sweep.py ──► rank.py ──► report.py
│
└─► derive_r_multiples.py ──► propfirm/evaluate.pyThe AGPL tooling stays on the other side of that process boundary — see Architecture decisions.
State
No database, no server. Browser state is localStorage for preferences and IndexedDB for
caches and saved runs; CLI state is files on disk. See
Storage & persistence.
Data flow
Binance public klines
│
├─► Vela BinanceProvider ──► idb_bar_cache ──► chart
│ (IDB, throttled)
├─► BacktestAdapter ──► PineTS ──► metrics ──► results / analysis
│
└─► pinets_cli/dataset.py ──► Parquet cache ──► sweep / hyperoptEvery arrow crosses a process, package or storage boundary that the project controls, which is what makes each of them individually testable.
The boundary each arrow actually crosses
The three paths are not the same kind of crossing, and treating them as one is how the diagram gets misread.
- Package and storage. Vela’s
BinanceProviderhands bars toidb_bar_cache, which keeps them in IndexedDB and throttles on the way. The chart reads out of the cache, not off the wire. - Package, inside one runtime.
BacktestAdapterruns the.pinethrough PineTS and returns metrics. No process is crossed and no file is read; this one happens entirely in the browser. - Process and file.
pinets_cli/dataset.pyfetches and caches to Parquet, andsweepandhyperoptread from there. This is the only path that leaves the browser, and the only one with a process boundary in it.
Different boundary types, but each one is a boundary the project owns — which is the actual claim. A crossing you do not control is a crossing you cannot test from one side.
What the project does not do
- No live order execution. Nothing contacts a broker.
- No server-side application backend. The browser application is a static page: IndicaView serves it, and all computation happens in the page or in Web Workers it spawns. There is no server-side state to deploy, secure or scale. The only network service involved is optional Supabase sign-in for a profile, and — when usage metering is switched on — a credit balance on that same account.
- No multi-asset portfolio aggregation. Deferred.
- No homegrown charting or simulation. Those are LuxAlgo’s, and the boundary is deliberate.
See Limitations for the verification boundaries.