IndicatorSmith docs
Everything you need to install the local engine, bring your Pine scripts across and connect your own data. The docs track the current release; version-specific notes are marked.
Install
IndicatorSmith ships as a single installer per platform that contains the UI and the local engine. There is no separate Python to install — the engine bundles its own runtime.
- Download: downloads open with early access. See the download page.
- Run it. On first launch the engine starts on
127.0.0.1:8741and the UI opens automatically. Nothing listens on external interfaces unless you configure it. - Enter your licence, which you receive with early access, or continue in guest mode.
This manual describes the planned release. IndicatorSmith is not on sale yet.
Your first chart
Without any data provider configured, IndicatorSmith uses the free Yahoo Finance daily feed. Type a symbol in the toolbar (BTC-USD, SPY, GC=F), pick a timeframe, and press / to add an indicator. Intraday timeframes require a provider key — see BYOK.
| Shortcut | Action |
|---|---|
| / | Indicator search |
| Ctrl + 1..9 | Switch layout |
| Alt + T | Trendline tool |
| Alt + R | Toggle bar replay |
| Ctrl + P | Open Pine converter |
| Ctrl + Shift + A | New alert on crosshair price |
| ` | Toggle the engine terminal panel |
Workspace folder
All user state lives in one folder you can back up, sync or version with git:
~/IndicatorSmith/workspace/ ├── layouts/ *.layout.json (panes, symbols, indicators, links) ├── drawings/ <symbol>.json (per symbol, all timeframes) ├── indicators/ *.py (converted + hand-written, hot-reloaded) ├── pine/ *.pine (your originals, kept for reference) ├── alerts.sqlite (definitions + fired history) └── cache/ parquet/ (historical bars per provider/symbol/tf)
Windows: %USERPROFILE%\IndicatorSmith\workspace. Change the location in Settings → Workspace; the engine moves the folder for you.
Local engine
The engine is a Python service (bundled runtime) that owns data connectors, the indicator runtime, the Pine converter, alerts and the quick backtester. The UI talks to it over a local WebSocket. One engine can serve several IndicatorSmith windows at once — for example the desktop UI and a second monitor layout.
| Component | Default | Notes |
|---|---|---|
| Bind address | 127.0.0.1:8741 | Never exposed externally by default |
| Indicator runtime | NumPy vectorised, optional Numba | Stateful scripts run as compiled loops |
| Alert scheduler | Evaluates on bar close + on tick | Continues while the UI is closed if the engine runs as a service |
| Update check | Daily, version number only | Disable in Settings → Updates |
Headless / VPS
Run the engine on a VPS so alerts keep evaluating 24/7, and connect the desktop UI to it over an SSH tunnel. Do not expose port 8741 to the internet.
The engine-only Docker image opens with early access, together with the other downloads. Connect the desktop UI to it over an SSH tunnel; never expose port 8741 to the internet.
Configuration
engine.toml in the workspace root. Common keys:
[engine] bind = "127.0.0.1:8741" threads = 4 [data] default_provider = "binance" cache_max_gb = 8 [alerts] telegram_bot_token_ref = "keychain:indicatorsmith/telegram" # never the token itself webhook_timeout_s = 5 [converter] strict_v6_booleans = true lookahead_requires_ack = true
Pine converter — supported versions
The converter parses the //@version= tag and applies that version's semantics. Scripts without a tag are treated as v4 with a warning.
| Pine version | Status | Coverage | Notes |
|---|---|---|---|
| v6 | full | ~1,900 functions | Strict booleans, dynamic requests, ta.vwap tuple return, new strategy defaults |
| v5 | full | ~1,850 functions | Namespaced ta.*, request.*, libraries via import |
| v4 | full, legacy | ~1,400 functions | Un-namespaced functions mapped to v5 equivalents first |
| v3 and earlier | best effort | — | Converted through the v4 path; review the report carefully |
Indicators, strategies and libraries are supported. Library import user/lib/1 statements are resolved from your pine/ folder; if the library is not present the converter asks for the file.
Pine converter — unsupported functions
The following are reported per line as unsupported. The rest of the script still converts; unsupported lines become commented # TODO(pine) markers so nothing is silently dropped.
| Pine function / feature | Status | Workaround |
|---|---|---|
request.seed() | unsupported | Import the GitHub dataset as CSV via the data connector |
polyline.* | unsupported | Use line.new segments; the converter can auto-split simple polylines with --polyline-split |
label.new() | unsupported | No equivalent object yet. The line is marked ✕ on both sides and the caption is dropped; a plotshape on the same condition still plots and values are unaffected |
table.cell_set_* styling | partial | Text and background colour convert; borders, tooltips and merge do not |
runtime.error() formatting | partial | Raises ForgeRuntimeError with the plain message |
varip on historical bars | approximated | Treated as var; live behaviour reproduced tick-by-tick |
request.financials() | provider-dependent | Requires a fundamentals-capable provider key (Polygon.io, IBKR) |
syminfo.* for non-BYOK symbols | partial | Fields resolved from your provider; unknown fields return na |
Pine Screener / request.* in screener context | unsupported | IndicatorSmith has no screener; run the indicator per symbol in a layout instead |
request.security lookahead
request.security(..., lookahead=barmerge.lookahead_on) returns the value of the higher-timeframe bar that contains the current bar — including its close, which has not happened yet. Strategies using it show impossible results in backtests and fail live.The converter's rules:
lookahead_off(Pine default) →lookahead=False. The engine returns the last completed higher-timeframe bar. This matches live behaviour.lookahead_onwith a[1]offset on the expression (the common "non-repainting" idiom) → converted tolookahead=Falsewithout the offset, which is equivalent and clearer. Noted in the report.lookahead_onwithout offset →lookahead=True, line flagged repainting. Withlookahead_requires_ack = true(default) the script cannot be added to your library until you acknowledge the flag.gaps=barmerge.gaps_on→naon bars without a new higher-timeframe value;gaps_off→ forward-filled.
# emitted code
htf_sma = request_security(bars, "D", lambda b: ta.sma(b.close, 200),
lookahead=False, gaps=False) # Pine: lookahead_off, gaps_off
Verifying a conversion
- Export ≥ 500 bars of plotted values as CSV from the platform that runs your Pine script.
- In the converter, open Regression → select the CSV. Warm-up bars (longest lookback + 5) are excluded.
- Tolerances: 1e-9 for pure arithmetic, 1e-6 for EMA/RMA/VWAP-style accumulations. Any bar outside tolerance is listed with both values.
- For strategies, compare trade count with the Strategy Tester on the same range. A different count means a signal-timing difference; investigate before looking at P&L.
The agent — what it does
IndicatorSmith ships with one assistant, reachable from the same spark icon in every view header (chart toolbar, side panel, dock) and from C anywhere. Opened from a header it already knows which view you are on, what is selected and how the controls are set. It is a porting engineer: its job is to get you off another platform and keep the library working.
It does not only describe. Every reply ends with a line stating what it just did — “Converted rsi_divergence.pine, proved it bar for bar, and queued the rest of the library.” — and if it could not do something it says so instead of pretending.
setup in the terminal to bring the question back.Standing instructions
A standing instruction is stated once and then runs on the workspace clock, one cycle at a time, without further input. Three ship with v1.14.2. Each one states its cost before it starts.
| Instruction | What it does each cycle | What it costs you |
|---|---|---|
Port my Pine libraryagent convert | Converts one script from the queue, then proves it bar for bar against the Pine reference on the visible window | It will not guess at a construct it does not support — you get a marked line and a dropped decoration rather than a silent approximation |
Watch these symbolsagent watch | Checks BTCUSD, ETHUSD and SPX on the 4h; speaks on an EMA 20/50 cross only after it has held 3 bars, separated to 0.25 × ATR, with RSI on the right side of 50 | A rule strict enough to be worth reading is quiet most of the time — expect far more “nothing to report” than signals |
Keep testing this ideaagent research | Walks the parameter neighbourhood around the current rule with costs charged, and reports whether the result is distinguishable from noise | Most ideas fail this, and it says so rather than finding something |
Set one from the radar icon on the rail, from the command palette (Ctrl+K → “Set a standing instruction”), or by asking the assistant in words.
The activity log
Every autonomous cycle writes one entry: what it did, why, and the number it produced. The log is visible, scrollable and never summarised away — including the cycles where it decided there was nothing worth saying, which carry the number that fell short so you can judge the threshold instead of trusting it.
rsi_divergence.pine → rsi_divergence.py 13 lines clean, 1 noted, 2 unsupported — then proved the converted lines bar for bar against the Pine reference 0 of 306 bars differ · 0 signals off Queue finished rsi_divergence.pine uses label.new(), which has no equivalent here — I marked the line and dropped the caption rather than substitute something that looks similar. 2 files carried a note where the wording changed but the behaviour did not. 4 scripts · 44 clean lines · 2 unsupported · 0 differing bars in total Checked BTCUSD, ETHUSD, SPX at bar 521 — said nothing SPX crossed up and separated to 0.37 × ATR, but RSI is 76.0 — on the wrong side of 50 for that direction, so the cross and the momentum disagree and I stayed quiet SPX RSI 76.0
The panel header also carries a standing summary: how many cycles have run, how many times it spoke, how many things it changed in your workspace, and what it wants a decision on.
What it refuses
The refusals are not soft. They are stated before anything else and asking again does not move them.
- Leverage multiples. “No. I do not size with leverage multiples and I will not model one … That is the ceiling, not a starting point I negotiate up from.”
- Sizing above the guards. Quarter Kelly (0.25) on the measured edge, capped at 7.5 % of equity per asset, fees 4 bps, slippage 20 bps, and a −20 % drawdown halt drawn on the drawdown panel. Type
guardsin the terminal to print them. - A statistic from too small a sample. With fewer than five trades in the window it will not compute a t-statistic: “A number from that few is a decimal place pretending to be evidence, and handing it to you would be worse than saying I do not know.”
- An alert the engine cannot evaluate. Conditions are built from what is actually plotted; it will not arm a rule it cannot score.
- A construct it cannot convert. The line is marked ✕ in the gutter on both sides and the decoration is dropped, rather than substituted with a lookalike.
It never places an order, never reads your provider keys, and gives no personal investment advice — everything it prints is educational model output on the data in front of you.
Where its numbers come from
Every figure the assistant quotes is produced by the same functions the panels use, so the assistant and the screen cannot disagree. If it cannot compute something, it names the panel that can.
The conversion proof is the clearest case. The Equivalence tab runs the Pine reference and the converted Python over the same bars and counts every difference above floating-point tolerance. On rsi_divergence.py, BTCUSD 1D: 306 of 306 bars agree, max Δ 0, and the same 6 signals fire on the same bars. A hand port of the same script that uses an EMA instead of Wilder smoothing differs on all 306 bars, by up to 21.93 RSI points, fires 9 signals instead of 6 with 3 of them on the wrong bar, and leaves 13 bars with no value at all where Pine has one. Every differing bar is listed with both values; nothing is hidden.
Pause, undo and stop
Four controls sit in the standing-instruction panel, one click each:
- Pause / Resume. Pausing stops the clock immediately and freezes the log exactly where it is. Resuming continues from the next cycle. Also
agent pause/agent resume. - Undo last. Reverses the most recent change the agent made in your workspace — an armed alert, a plotted indicator, a loaded layout — and marks that entry in the log as undone. The button's label names what will be undone. Also
agent undo. - Change. Reopens the instruction picker so you can state a different one; the old log stays.
- Stop. Ends the instruction. Nothing further runs on your behalf until you set a new one. Also
agent stop.
agent log prints the most recent entries into the terminal if you would rather read them there.
Built-in indicators
140+ indicators ship with the engine, grouped as below. Every built-in is a Python file you can open, copy and modify.
| Group | Examples | Count |
|---|---|---|
| Trend | EMA, SMA, WMA, HMA, DEMA/TEMA, KAMA, Supertrend, Ichimoku, Parabolic SAR, ADX | 28 |
| Momentum | RSI, Stochastic, StochRSI, MACD, CCI, Williams %R, ROC, TSI, Awesome Oscillator | 24 |
| Volatility | Bollinger, Keltner, Donchian, ATR, Historical volatility, Chaikin volatility | 16 |
| Volume | VWAP (session/anchored/bands), OBV, MFI, CMF, Volume profile, Footprint, Delta | 22 |
| Structure | Pivots, Swing highs/lows, Market structure (BOS/CHoCH), Fair value gaps, Order blocks (basic) | 18 |
| Statistics | Linear regression channel, Z-score, Correlation, Beta, Hurst exponent | 14 |
| Session / time | Session ranges, Opening range, Day/week separators, Time-of-day heatmap | 12 |
| Converted | Anything you bring from Pine | ∞ |
Writing your own
Custom indicators use the same forge API the converter emits. Drop a file into indicators/; it appears in the indicator search immediately.
from forge import indicator, ta, plot
@indicator("Volume-weighted RSI", overlay=False)
def vw_rsi(bars, length: int = 14):
vw = (bars.close * bars.volume) / ta.sma(bars.volume, length)
r = ta.rsi(vw, length)
plot(r, "VW-RSI", color="#8ab4ff")
plot(70, "OB", color="#f23645", style="dashed")
plot(30, "OS", color="#22c55e", style="dashed")
return {"vw_rsi": r}
Adding a data provider (BYOK)
Settings → Data → Add provider. Create a read-only API key at your exchange or broker; IndicatorSmith never needs trade or withdrawal permissions. Keys are stored in your OS keychain and referenced by name in engine.toml.
| Provider | Markets | Intraday | Key type | Cost |
|---|---|---|---|---|
| Binance | Spot, USD-M perps | 1s+ | Read-only | Free |
| Bybit | Spot, perps | 1m+ | Read-only | Free |
| Deribit | Options, perps | 1m+ | Read-only | Free |
| Hyperliquid | Perps | 1m+ | Public (no key) / wallet read | Free |
| Coinbase | Spot | 1m+ | Read-only | Free |
| Alpaca | US equities | 1m+ (IEX) | Paper/read key | Free tier |
| Polygon.io | US equities, options, FX | 1s+ | API key | Your Polygon plan |
| Interactive Brokers | Global | 1s+ | TWS/Gateway read-only | Your IBKR data subscriptions |
| Yahoo Finance | Almost everything | Daily only | None | Free |
| CSV import | Anything | Any | None | Free |
Local cache
Historical bars are cached as Parquet under cache/parquet/<provider>/<symbol>/<tf>/. First load pulls from the provider; subsequent loads are instant and offline. The cache is capped by cache_max_gb and evicted least-recently-used. Right-click a chart → Refresh from provider to force a re-pull.
Activation & devices
A licence includes 3 device activations. Activation sends your licence key and a device fingerprint (hash of hardware IDs) to the licence server; no file names, scripts or symbols are transmitted. Manage devices in the account portal — deactivate a retired machine to free a slot. Offline machines can be activated with a one-time file exchange (Settings → Licence → Offline activation).
Update contract
Your licence is perpetual and includes the first 12 months of feature releases. The optional update contract (€79/year) extends that: every feature release published while it is active is yours. When it lapses, the engine records the last eligible version; you can keep installing that version and its point releases indefinitely. Renewing later jumps you to the current release — there is no back-payment. Full explanation on the pricing page; release history in the changelog.
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| UI opens but says "engine unreachable" | Port 8741 in use or firewall blocked loopback | Change bind in engine.toml; allow loopback in the firewall |
| Intraday timeframes greyed out | No provider key configured | Add a provider under Settings → Data |
| Converted indicator differs from the Pine reference on early bars | Warm-up period | Expected; the regression test excludes warm-up. Persisting differences → check the seeding note in the report |
| Strategy backtests too well | Lookahead in request.security | See the lookahead note |
| "Licence: device limit reached" | 3 activations in use | Deactivate a device in the account portal |
| Alerts stop when the laptop sleeps | Engine runs in-process | Install the engine as a service, or run it on a VPS |
Logs & support
Engine logs: workspace/logs/engine.log (rotated daily). UI logs: Help → Open log folder. For support, attach the log and the converter report if relevant. Update-contract holders get a one-business-day response; everyone else is answered in order. Nothing in your logs includes script contents unless you enable debug_dump_scripts, which is off by default.