Your privacy choices

Sentimentor uses optional analytics cookies to understand visits and improve the product. Essential cookies remain enabled either way.

Privacy policy
Back
Documentation

PineNode

Overview

PineNode is Sentimentor's strategy and custom-indicator DSL (Domain-Specific Language — a small language purpose-built for chart studies and trading strategies) — a JavaScript-based replacement for TradingView's Pine Script designed around three things TradingView's Pine doesn't do well:

  • Deterministic execution. No calc_on_every_tick trap — strategies always evaluate at bar close in both backtest and live.
  • Real debugging. Set breakpoints in the source, step bar-by-bar, watch arbitrary expressions, see the captured console.
  • Safety analysis. A static linter that flags repaint, lookahead, alert-payload, and order-flow traps with plain-English fixes.

You write strategies and custom trading indicators in TypeScript-flavored JavaScript using a small, fixed API (pn.onBar(...), indicators like new SMA(20), and an order surface of buy() / sell()). Sentimentor-exclusive data — dark pool flow, unusual options activity, aggregated sentiment — is available via thesentimentor namespace inside the per-bar callback.

Note
pn (short for "pine node") and strategy are interchangeable names for the same builder. Examples in these docs use pn because it's shorter; older strategies that use strategy.param(...) /strategy.onBar(...) continue to work unchanged.
Tip
If you're coming from Trading View's Pine Script, jump straight to Migrating from Pine Script — most v5 strategies transpile automatically; the doc just covers the gaps.

Quickstart

The smallest useful, robustness-compatible strategy — an OHLCV-only SMA cross:

pinenode
const fastPeriod = pn.param('Fast Period', 20, 5, 50, 5); const slowPeriod = pn.param('Slow Period', 50, 20, 200, 10); const smaFast = new SMA(fastPeriod); const smaSlow = new SMA(slowPeriod); strategy .name('SMA Cross') .onBar(({ series, buy, sell, crossOver, crossUnder }) => { smaFast.next(series); smaSlow.next(series); if (crossOver(smaFast, smaSlow)) buy('SMA bullish cross'); if (crossUnder(smaFast, smaSlow)) { sell('SMA death cross'); } });

Paste that into the PineNode editor and click Validate & backtest or Exploratory backtest. You'll see a backtest with equity curve, trade list, and aggregate stats. Click Debug to step through bar-by-bar, or Analyze to run safety lint.

Strategy anatomy

Every PineNode strategy follows the same shape: indicators declared at the top, then a fluentstrategy builder chain that ends with .onBar(callback). Top-level code runs once at compile time; the onBar callback runs once per bar.

pn.name(label)

Labels the strategy in reports, the debugger header, and saved-strategy lists. Optional but recommended — the default is "Untitled Strategy".

pn.name('Mean Reversion v2');

pn.onBar(callback)

The per-bar callback is the heart of the strategy. It receives a freshcontext argument on every bar containing the series, order helpers, math utilities, and the Sentimentor data namespace.

pn.onBar((ctx) => {
  // ctx.series, ctx.buy, ctx.sell, ctx.crossOver, ctx.crossUnder,
  // ctx.highest, ctx.lowest, ctx.sentimentor
});

Most strategies destructure the arguments inline for readability:

pn.onBar(({ series, buy, sell, crossOver, sentimentor }) => {
  /* ... */
});
Heads up
The callback runs once per closed bar. There is nobarstate.isconfirmed equivalent because there's nothing to guard against — PineNode never re-evaluates mid-bar. This is the single biggest semantic difference vs Pine.

Callback arguments

NameTypeDescription
seriesBarSeriesThe bar history. Use series.close(), series.length, etc.
buy(reason: string) ⇒ voidLong entry. No-op if already long.
sell(reason: string) ⇒ voidCloses a long or opens a short.
crossOver(a, b) ⇒ booleanTrue when a moved above b on the latest bar.
crossUnder(a, b) ⇒ booleanTrue when a moved below b on the latest bar.
highest(field, n) ⇒ numberHighest value of a field over the last n bars.
lowest(field, n) ⇒ numberLowest value of a field over the last n bars.
sentimentorobjectSentimentor-exclusive data. See below.

Series API

The series object passed to onBar is a Pine-style indexed history of bars seen so far. series.close(0) is the just-closed bar; series.close(1)is the bar before it, and so on.

OHLCV accessors

series.close(0)   // last closed bar's close
series.open(0)
series.high(0)
series.low(0)
series.volume(0)
series.time(0)    // ms since epoch

// History — the argument is "bars back from the current"
series.close(1)   // previous bar's close
series.high(3)    // 3 bars ago

// Bare value getter (returns the full Bar object)
const bar = series.get(0); // { open, high, low, close, volume, time, symbol, timeframe }

series.length

Number of closed bars seen so far. Use it to guard against the warm-up problem: indicators with a period of N can't produce meaningful values untilseries.length >= N.

pn.onBar(({ series }) => {
  if (series.length < 50) return;        // warm-up
  // ... safe to read history here
});

Reading history

For slices of consecutive history, indicators do this for you. If you genuinely need the raw array, use series.slice(n) which returns the last n closes (oldest-first):

const last10 = series.slice(10);
// last10 = [close-9, close-8, ..., close-0]

Indicators

Indicators are stateful objects. You create one at the top level of the strategy file, then call .next(series) on every bar inside onBar. Read the current value via .value. Multi-output indicators (MACD, Bollinger Bands, Stochastic, ADX) return objects instead of scalars.

Heads up
Don't instantiate indicators inside onBar. A newnew SMA(20) per bar would never accumulate any history — every reading would equal the current close. The safety analyzer flags this as pn/repaint/indicator-in-onbar.

SMA — Simple Moving Average

const sma = new SMA(20);
pn.onBar(({ series }) => {
  sma.next(series);
  console.log('SMA(20) =', sma.value);
});

Constructor: new SMA(period). Returns number | null (null during warm-up).

EMA — Exponential Moving Average

const ema = new EMA(50);
pn.onBar(({ series }) => {
  ema.next(series);
  if (series.close(0) > ema.value) buy('above EMA');
});

Constructor: new EMA(period).

RSI — Relative Strength Index

Returns a 0–100 oscillator. Default period is 14, matching Pine's default.

const rsi = new RSI(14);
pn.onBar(({ series, buy, sell }) => {
  rsi.next(series);
  if (rsi.value < 30) buy('RSI oversold');
  if (rsi.value > 70) sell('RSI overbought');
});

Constructor: new RSI(period = 14).

MACD

const macd = new MACD(12, 26, 9);
pn.onBar(({ series }) => {
  macd.next(series);
  const { macd: macdLine, signal, histogram } = macd.value;
});

Constructor: new MACD(fast = 12, slow = 26, signal = 9).

Returns { macd, signal, histogram }.

Bollinger Bands

const bb = new BollingerBands(20, 2);
pn.onBar(({ series, buy }) => {
  bb.next(series);
  if (series.close(0) < bb.value.lower) buy('lower band touch');
});

Constructor: new BollingerBands(period = 20, stdDevMultiplier = 2).

Returns { middle, upper, lower }.

ATR — Average True Range

Volatility — useful for stop placement and position sizing.

const atr = new ATR(14);
pn.onBar(({ series, buy }) => {
  atr.next(series);
  const stopDist = atr.value * 2;
  // ...
});

Constructor: new ATR(period = 14).

Stochastic

const stoch = new Stochastic(14, 3);
pn.onBar(({ series }) => {
  stoch.next(series);
  const { k, d } = stoch.value;
});

Constructor: new Stochastic(kPeriod = 14, dPeriod = 3).

Returns { k, d }.

ADX — Average Directional Index

const adx = new ADX(14);
pn.onBar(({ series }) => {
  adx.next(series);
  const { adx: trendStrength, plusDI, minusDI } = adx.value;
  if (trendStrength > 25 && plusDI > minusDI) buy('strong uptrend');
});

Constructor: new ADX(period = 14).

Returns { adx, plusDI, minusDI }.

OBV — On-Balance Volume

const obv = new OBV();
pn.onBar(({ series }) => { obv.next(series); });

Constructor: new OBV(). No period.

VWAP

const vwap = new VWAP();
pn.onBar(({ series, buy }) => {
  vwap.next(series);
  if (series.close(0) > vwap.value) buy('above VWAP');
});

Constructor: new VWAP(). Session reset is automatic.

Williams %R

Inverted oscillator — readings near -100 are oversold, near 0 are overbought.

const wr = new WilliamsR(14);
pn.onBar(({ series, buy }) => {
  wr.next(series);
  if (wr.value < -80) buy('Williams oversold');
});

Constructor: new WilliamsR(period = 14).

ROC — Rate of Change

const roc = new ROC(12);
pn.onBar(({ series }) => { roc.next(series); });

Constructor: new ROC(period = 12).

CCI — Commodity Channel Index

const cci = new CCI(20);
pn.onBar(({ series, buy }) => {
  cci.next(series);
  if (cci.value < -100) buy('CCI oversold');
});

Constructor: new CCI(period = 20).

Pine-compatible rolling calculations

The deterministic importers can emit the additional constructors used by Pine's moving-average, dispersion, range and momentum helpers: WMA,RMA, VWMA, HMA, ALMA,SWMA, StandardDeviation, Variance,MeanDeviation, Highest, Lowest,RollingSum, CumulativeSum, Change,Momentum, LinearRegression, TrueRangeand MFI.

const cumulative = new CumulativeSum();
pn.onBar(({ series }) => {
  cumulative.update(series.close(0) - series.open(0));
  // cumulative.get(0) matches imported ta.cum(close - open)
});

Use .update(value) for an arbitrary scalar source and.next(series) for calculations that require the real OHLCV bar.

Custom trading indicators

PineNode custom indicators are editable chart studies. They read the current chart's OHLCV series, update calculations once per bar and emit one or more visible plots. You can write the source directly, convert a supported TradingView Pine Script indicator or ask Augur to generate a draft from a plain-language description. In every path, the code stays visible and must run successfully before it is added to the chart.

Note
An indicator plots research context. A strategy can place simulated orders and produce a backtest. The custom-indicator runtime rejectsbuy(), sell(), short() and cover(). Strategy Tester likewise accepts strategy() sources only and directsindicator()/study() sources here instead of presenting their chart-only statements as strategy conversion failures.

Create an indicator in the chart

  1. Open a symbol in the chart.
  2. Select Create Custom Indicator in the chart header, or Custom Indicators from the right rail.
  3. Start from the PineNode template, import a TradingView Pine Script indicator, or select Ask Augur and describe the study.
  4. Select Run Custom Indicator to compile it against the current symbol and timeframe.
  5. Review the plots and inputs, then select Add to chart.

Augur output is a starting point, not a hidden signal. Review its formula, parameter bounds, warm-up behavior and plot placement before adding or saving it.

On desktop, Custom Indicators opens as a wide resizable chart panel. Use the action rail to close it, open this documentation or switch between the normal and maximized layouts without leaving the active symbol.

Import a TradingView Pine Script indicator

Select Import from TV Pine Script at the top of Custom Indicators. The import panel uses the same workflow as Strategy Tester: paste Pine v2-v6 source, load an example, upload a .pine file, clear the editor or selectConvert. Line and character counts, warnings and unsupported-capability counts are shown before conversion.

Conversion runs locally and deterministically; it does not send the script to Augur. A successful conversion replaces the PineNode editor draft while keeping the source visible for review. Run the converted indicator before adding or saving it.

Note
The importer currently converts indicator()/study(), numeric and boolean inputs, legacy and namespaced technical-analysis calls, SMA, EMA, WMA, VWMA, RMA, HMA, ALMA, SWMA, RSI, ATR, ADX, OBV, VWAP, MFI, Williams %R, ROC, CCI, standard deviation, variance, mean deviation, rolling high/low/sum, cumulative sum, change, momentum, stochastic, linear regression, true range, MACD, Bollinger Bands,math.avg(), plot(),hline() and plotcandle(). Because PineNode's custom plot store is scalar, an imported plotcandle() is represented by a close-price line and the conversion report flags candle bodies, wicks and per-bar colors for manual migration. It preserves the script's overlay setting and maps common colors and line, area or histogram styles. Complex drawing objects, alerts, multi-timeframe requests and unsupported ta.*calls require manual review or migration.
Note
Every technical-analysis class advertised by either deterministic importer is checked against the browser execution registry by the PineNode test suite. This prevents a conversion from being marked supported while emitting a missing runtime constructor. Strategy execution in the verification worker mirrors the same class registry.
Heads up
Drawing-heavy Pine indicators can rely on user-defined types, methods, labels, boxes and hundreds of chart objects that are not equivalent to a numeric PineNode plot. The importer recognizes their plotcandle() output and creates a runnable close-price proxy instead of reporting "no plot found". For a plotcandle-only source, discarded drawing dependencies are summarized as one quantified degraded-output warning rather than red compile blockers. The warning remains the source of truth: omitted drawing semantics are not recreated by the proxy. If the source also emits a native scalar plot(), dependencies that the importer cannot preserve remain listed as unsupported because they may affect that plot.

Declare the study with pn.indicator()

Call pn.indicator(name, options) once, then chain.onBar(callback). Set overlay: true for a price-chart overlay or overlay: false for a separate indicator pane.

pinenode
const fastLength = pn.param('Fast Length', 20, 2, 200, 1); const showSlow = pn.boolParam('Show Slow', true); const fast = new EMA(fastLength); const slow = new EMA(50); pn .indicator('EMA Research Overlay', { overlay: true }) .onBar(({ series, plot }) => { fast.next(series); slow.next(series); plot('Fast EMA', fast.get(0), { color: '#22d3ee', lineWidth: 2, }); if (showSlow) { plot('Slow EMA', slow.get(0), { color: '#f472b6', lineWidth: 2, }); } });

The callback receives the same historical series accessors used by a strategy plus plot(). The runtime includes the imported rolling and smoothing libraries listed above as well as SMA, EMA, RSI, MACD, Bollinger Bands, ATR, Stochastic, ADX, OBV, VWAP, Williams %R, ROC, CCI and cumulative sum.

Expose configurable inputs

pn.param(name, default, min, max, step?) declares a bounded numeric input.pn.boolParam(name, default) declares a switch. After a successful run, the builder shows both kinds of inputs and reruns the indicator when a value changes.

const length = pn.param('Length', 14, 2, 100, 1);
const showSignal = pn.boolParam('Show signal line', true);
Tip
Give inputs stable names. Saved parameter values are keyed by the normalized name, so renaming an input creates a new saved setting.

Emit plots and choose their panes

plot(name, value, options?) records one numeric value for the current bar. Reuse the same plot name on every bar. A non-finite value creates a gap during warm-up or when a condition intentionally hides the plot.

plot('Momentum', value, {
  color: '#a78bfa',
  lineWidth: 2,
  renderType: 'histogram', // 'line' | 'histogram' | 'area'
  pane: 'sub',             // omit for the main chart
  independentScale: true,
  scaleGroup: 'momentum',
});

The indicator-level overlay option supplies the default placement. A plot can override it with pane. Use independentScale or a shared scaleGroup when multiple plots should not inherit the price scale.

The chart's Indicators panel can also override placement for selected studies. With Overlay on main chart checked, plots share the price chart; uncheck it to give each active indicator—including a saved custom indicator—its own pane.

Run, add and save

  • Run Custom Indicator validates the source and previews the emitted plots against the current chart data.
  • Add to chart keeps the indicator live while you inspect or change the chart.
  • Save opens a review dialog for the name and optional description, then stores the source and selected inputs privately.
  • Use the pencil beside a saved indicator to edit its details, or Load in Editor to inspect and change its source.

Signed-in users save to their private account library. Signed-out users save on the current device and can sign in later when they want account synchronization. Saving does not publish the indicator or expose its source to other users.

First-release limits and safety

  • The runtime evaluates only the current chart symbol and timeframe; multi-timeframe requests are not available yet.
  • Inputs are numeric or boolean. Text, symbol and timeframe inputs are not available in this release.
  • Browser and network APIs, imports, dynamic evaluation and strategy order calls are rejected.
  • The runtime executes PineNode JavaScript DSL. The Pine Script importer converts its supported subset and reports syntax that still needs manual migration.
  • Historical output depends on the loaded bars and data quality. Generated or custom indicators are research tools, not financial advice.

Helpers — cross, highest, lowest

The callback args include four utility functions for common patterns. They work on raw numbers and on indicator instances (which carry their own .value / previous value), so you can pass either:

crossOver(smaFast, smaSlow)         // indicator vs indicator
crossOver(series.close(0), 100)     // value vs constant
crossUnder(rsi, 30)                 // indicator vs constant

highest('high', 20)                 // 20-bar high
lowest('close', 50)                 // 50-bar closing low
Tip
crossOver uses Pine's definition — true on the bar where amoved from below to above b, not on every bar where a > b. To check ongoing condition, just compare directly.

Orders — buy / sell

The order surface is intentionally minimal. buy(reason) opens a long; if a long position is already open, the call is a no-op (the runtime does notpyramid in v1). sell(reason) closes a long or opens a short.

buy('SMA cross + dark pool confirms');
sell('SMA death cross');

The reason string is logged with the trade and surfaced in the journal, backtest report, and live execution feed. Keep it stable (describe the rule, don't embed live prices) — the safety analyzer flags reason strings that includeseries.close() as a mutable-payload risk.

Heads up
Position size is set by the run config (default 10% of equity per trade), not by the order call. Stops and take-profits are configured per backtest run from the UI; per-order stops are not currently available as per-order PineNode instructions and have no published release date.

Risk management & guardrails

Beyond a strategy's own buy/sell logic, the engine applies rule-based risk guardrails per backtest run. In the editor a🛡 Risk guardrails toggle (default ON) applies them; the AI refine loop applies them automatically to every generated strategy.

  • trendFilterPeriod — trend-regime filter: longs are suppressed unless the close is above the SMA of this many bars (shorts unless below it). Keeps a strategy from fighting the primary trend. Default preset: 200.
  • trailingStopPercent — exit when price retraces this % from the best price reached since entry. Lets winners run while capping give-back. Default preset: 20.
  • stopLossPercent — hard stop at this % loss. Preset floor: 15.
  • positionSizePercent — % of capital deployed per trade.

From the CLI, layer them onto any backtest:

pn backtest --strategy strategy.pinenode --ticker NVDA \
  --trend-filter 200 --trailing-stop 20 --stop-loss 15 --position-size 100
Tip
Guardrails are a risk brake, not an alpha source. They protect capital in sustained downtrends and cut drawdown, but can't beat buy-and-hold in a strong uptrend on their own — the strategy's entry/exit logic drives participation.

Backtest metrics

Every report benchmarks the strategy against simply holding the asset over the same window — the only fair comparison:

  • buyHoldReturnPercent — return of buying at the first bar and holding to the last.
  • alphaPercent — strategy return minus buy-and-hold, in points. Positive = beat holding.
  • upsideCapturePercent — in an uptrend, the fraction of the buy-and-hold move the strategy captured (the participation metric). 0 when the asset was flat/down, where downside protection — not capture — is the objective.
  • Plus sharpeRatio, maxDrawdownPercent, winRate,profitFactor, and expectancy.
Tip
Alpha vs buy-and-hold is the number that matters. A +8% strategy only beats the market if holding returned less. Win rate on its own is a vanity metric — a high-win-rate strategy that cuts winners short can still lose badly to just holding.

Discovery, rolling validation, final lockbox, and forward evidence

PineNode uses four distinct stages because each answers a different question. The same protocol applies to manually written, imported, workflow-created, and Augur-created code: origin does not change how executable rules should be tested.

  1. Discovery: find and initially screen a potentially interesting hypothesis. Experimentation is allowed here, and Sentimentor records each server validation attempt.
  2. Rolling validation: three later chronological folds test whether the frozen rules survive multiple unseen periods rather than one favorable split.
  3. Embargo: excluded bars between discovery, validation, and the final lockbox reduce leakage when signals, features, or holding periods overlap. The default is five bars; an appropriate gap depends on the strategy horizon.
  4. Final lockbox: the validation endpoint does not return the newest bars or their metrics to the validation workflow or Augur until the exact configuration passes the earlier stages. It opens once. Passing and failing results both consume it.
  5. Forward: after a pass, simulated runners or optional Alpaca Paper runners collect genuinely new evidence. Forward results are not part of the historical backtest and should be compared with the frozen baseline.

Backtest accuracy depends on its assumptions. Record the data provider, bar interval, lookback, position sizing and transaction costs. Inspect commission, slippage and fill assumptions when comparing results. Missing bars, price adjustments or inputs unavailable at the time of a historical bar can change the outcome. A modeled backtest is not a record of broker fills; compare it with separately labeled forward simulation or paper trading evidence.

Published post-research signals are available in the public Forward Signal Track Record. It retains open trades, wins, losses and execution errors and labels scheduled bar-close simulation separately from Alpaca Paper fills. The public view does not expose user, runner, account or order identifiers.

Note
What is frozen: strategy source, parameters, ticker/universe, timeframe, feature definitions, feature inputs, data provider and snapshot, entry/exit rules, position sizing, transaction costs, engine version, and—when supplied—the hash and model provenance of the AI generation input are captured in a fingerprinted manifest. If any executable input changes, the previous pass is invalid. A revised strategy cannot open the same final period again; that research project needs at least 40 genuinely newer bars before another final lockbox can be evaluated.
Note
Scope of the lock: historical prices may be visible on Sentimentor charts or third-party services, so the platform cannot prove that a human has never seen the calendar period. The lockbox is a server-enforced control against automated evaluation, repeated hypothesis selection, and silent reuse inside this research workflow. Researchers should avoid manually tuning rules to the reserved dates.
Tip
Validation is provenance-neutral. The Validate & backtest action tests the exact code currently in the editor without silently changing it. Use the separate Improve with Augur action when you explicitly want AI-assisted revisions. An exploratory backtest remains available for quick research, but it is labeled as such and cannot be sent to Alpaca Paper. Editing the source, changing its parameters, or applying an in-sample optimizer result invalidates the prior pass and requires a new validation run. Live activation verifies the pass against the server-owned lockbox row and rejects stale, forged, ticker-mismatched, or configuration-mismatched records.

The final lockbox report also includes a 2,000-path trade-bootstrap Monte Carlo downside estimate and Deflated Sharpe confidence based on server-recorded trials. These answer different questions: Monte Carlo measures outcome fragility conditional on the strategy; the lockbox tests whether the research process selected something that survives untouched data. Neither guarantees future profitability, and unrecorded experimentation elsewhere cannot be inferred by the trial adjustment.

Heads up
Strategies that read sentimentor.* research inputs can run exploratory tests, but they cannot receive a robustness pass until historical point-in-time versions of those features are available. Reusing today's sentiment or research snapshot across older bars would leak future information. Research Evals are a separate system for sealing and later scoring research claims; they do not replace PineNode strategy validation.

If discovery fails, validation and the lockbox stay sealed. If rolling validation fails, the lockbox also stays sealed. If the lockbox fails, the failure remains in the record and is not auto-loaded as validated. If all stages pass, the exact frozen version may proceed to forward paper monitoring, where drift from the historical baseline becomes new evidence.

Params + the optimizer

pn.param(name, default, min, max, step?) declares an integer or float knob that the optimizer can sweep. Inside a strategy, the call returns the current value — at compile time it returns the default; during an optimization run, it returns whatever value the optimizer chose.

const fastPeriod = pn.param('Fast Period', 20, 5, 50, 5);
const slowPeriod = pn.param('Slow Period', 50, 20, 200, 10);

const smaFast = new SMA(fastPeriod);
const smaSlow = new SMA(slowPeriod);

Params show up in the strategy's right-hand sidebar as adjustable inputs. The optimizer takes the same min/max/step and ranks the searched combinations. The result panel discloses the full trial count. Because the ranking uses the same bars, every optimizer winner is exploratory and must complete the full robustness protocol before live use.

Sentimentor data

The single feature Pine Script can't express. Inside onBar,sentimentor is an object with four sub-namespaces that surface Sentimentor's proprietary data feeds:

PathShapeWhat it tells you
sentimentor.darkPool{ ratio, divergenceScore, blockTradeCount }Dark-pool buy/sell ratio + recent block trades.
sentimentor.optionsFlow{ putCallRatio, unusualScore, sentiment, sweepCount }Options sweep + unusual activity for this bar.
sentimentor.sentiment{ score, category }Aggregated 0-10 sentiment with a human-readable category.
sentimentor.signals{ overallSignal, confidence }Sentimentor's blended overall signal for the symbol.

All four can be null when no data is available for the current bar. Always guard:

if (crossOver(smaFast, smaSlow)) {
  const dp = sentimentor.darkPool;
  if (!dp || dp.ratio > 0.5) buy('cross + dark pool agrees');
}
Note
How the data is sourced. The backtest engine (and the debugger) fetch a single Sentimentor snapshot for the symbol at the start of the run and feed it to every onBar call. That means the same dark-pool / options-flow / sentiment values are visible on every bar of the run rather than reconstructed per-historical-bar — the API doesn't expose true historical sentimentor data. The snapshot still lets your strategy read live values and condition entries on them; just keep in mind that comparing "sentiment on bar 100" to "sentiment on bar 200" from the same backtest will always be equal until per-bar historical data ships.

Editor + Intellisense

The PineNode code editor pairs JavaScript syntax highlighting with a custom completion source, hover provider, and snippet engine — so authoring a strategy feels closer to writing TypeScript in VS Code than poking at a Pine Script editor on tradingview.com. Everything below is on by default.

Autocomplete

Start typing inside the editor and a dropdown surfaces matches across three layered sources:

  • Indicator constructors — SMA, EMA,RSI, MACD, BollingerBands, ATR,Stochastic, ADX, OBV, VWAP,WilliamsR, ROC, CCI, plus the rolling averages, dispersion, range and momentum constructors used by the Pine importer. Each entry shows the signature in the detail column so you don't have to remember argument order.
  • Strategy and indicator builder methods — pn.name,pn.indicator, pn.onBar, pn.param andpn.boolParam. (strategy is also accepted as a legacy alias.)
  • Callback args — when the cursor is inside theonBar body, the destructured args (series,bar, barIndex, buy, sell,enterLong, enterShort,crossOver, crossUnder, plot,sentimentor) bubble to the top of the list. Outside onBar, the dropdown deprioritises them so you don't see runtime-only values when declaring top-level constants.

Hit Ctrl-Space (or ⌘-Space) anywhere to force the dropdown open even when the prefix is empty — useful when you want to browse the surface without remembering the first character.

Hover docs

Hover any DSL identifier and a tooltip shows the same documentation that powers the completion dropdown — a one-line summary plus, where useful, the function signature. The tooltip distinguishes:

  • DSL primitives — new SMA(20) shows"Simple Moving Average" + signature.
  • User declarations — local const /let / function bindings show the line they were declared on plus, during a debug session, their current value at the paused bar.
  • Safety findings — any line the safety analyzer has a finding for surfaces the headline + explanation + suggested fix in the same tooltip surface, so you don't have to flip to the Safety Report to see what was flagged.
Tip
Inside an active debug session, the hover-inspect tooltip adds an Add to watch button so you can promote whatever you're hovering into the right-rail Watch panel without re-typing the name.

Snippet expansion

Several completions expand to a multi-cursor snippet template you can tab through. The big ones:

pn.onBar  → pn.onBar(({ series, buy, sell, crossOver, crossUnder }) => {
            ${1}
          });

pn.param  → pn.param('${label}', ${def}, ${min}, ${max}, ${step});

buy       → buy('${reason}');
sell      → sell('${reason}');

crossOver → crossOver(${1:a}, ${2:b})

Tab advances to the next placeholder; Esc drops out of the snippet so you can keep editing.

User identifiers

The editor parses your code as you type and pulls out every const,let, var, and function declaration. Those get merged into the completion dropdown so a variable you defined two lines ago auto-completes alongside the DSL surface, with no editor restart and no type annotations required.

Same goes for indicator instances: as soon as you write const ema20 = new EMA(20);, typing ema insideonBar offers ema20 with the right colour-coded variable icon.

Debugging

PineNode ships an in-browser debugger Pine Script doesn't have. Everything below is available without installing anything — open the PineNode tab in the chart topbar.

Inline squiggles

As you type, the editor underlines structural issues (an indicator declared but never called .next(), a condition that fires neither buy norsell) and safety problems (repaint, lookahead, alert payload, etc.). Hover a squiggle for the title, explanation, and a fix.

Breakpoint gutter

The narrow column to the left of the line numbers is the breakpoint gutter — click a line to toggle a breakpoint. If you click without an active debug session, PineNode offers to start one and add the breakpoint in one step.

Tip
The breakpoint only fires on statements inside the onBarcallback — top-level code runs once at compile time. The gutter shows a red dot for an active breakpoint and a yellow ▶ arrow on whatever line execution is paused at.

Debug session

Click Debug below the editor. PineNode compiles the strategy, fetches bars for the configured ticker/timeframe, and pauses at bar 0. The transport controls:

  • Step — advance exactly one bar.
  • Continue — run forward until the next breakpoint (line, bar-index, or condition) or end of data.
  • Restart — reset to bar 0 with the same bars and breakpoints.

When paused, the right panel shows the current bar's OHLC, the running equity, signals emitted this bar, locals captured via debug.watch(name, value), and any safety findings that touch the paused line.

Watch expressions

Add an expression to the Watches panel and it's evaluated after every bar against(series, bar, barIndex, locals):

series.close(0) - series.close(1)
smaFast.value > smaSlow.value
locals.dpConfirm

Inside your strategy, call debug.watch('name', value) to surface any local value into the locals snapshot:

pn.onBar(({ series, buy, sentimentor }) => {
  const dpConfirm = sentimentor.darkPool?.ratio > 0.5;
  debug.watch('dpConfirm', dpConfirm);
  if (dpConfirm) buy('dp');
});

Plain-English errors

When your strategy throws an error during compilation or while running, PineNode rewrites the raw underlying message into something you can act on. Examples:

Raw  : ReferenceError: sma502 is not defined
Pine : "sma502" hasn't been declared
       — Check spelling against your params/indicators, or declare with
         const at the top level.

Raw  : TypeError: Cannot read properties of undefined (reading 'close')
Pine : series is missing on the first bar
       — Guard with: if (series.length < N) return;

The rewriter covers ~20 common patterns including Pine leftovers (ta.sma,strategy.entry), missing instantiations, indicator misuse, and JS syntax errors.

Parser and runtime diagnostics include the PineNode editor location asLine N:M. For example, an unresolved rsi call is reported at the identifier's exact line and column rather than as a location-freersi is not defined message.

Safety analyzer

Click Analyze below the editor to run PineNode's safety lint — static analysis of failure modes that make backtests look clean while live trading bleeds. Five rule families:

Repaint

Logic that reads values that change after the fact. Most common: instantiating an indicator inside onBar (which restarts the rolling window every bar), or reading series.close(N) without verifying series.lengthis at least N+1.

Lookahead

Logic that effectively peeks into the future. Pine's lookahead_onrequest.security calls are the canonical example. In PineNode, since there's no security-call surface, this family mostly catches Pine leftovers in imported code.

Barstate

Orders or alerts that fire unconditionally every bar. Usually a missingif: the user wrote buy('…') at the top level ofonBar instead of inside a condition.

In imported Pine, this check applies to runtime alert() calls.alertcondition() is a declaration users can select when creating an alert, so it is not reported as though it fired by itself.

Alert payloads

Three rules in this family today:

  • Mutable reason — buy() / sell() /alert() reason strings that embed series.close() or other mutable OHLC / sentimentor values. The string captures the value at fire time, so live and backtest reasons disagree even when the logic is identical.
  • Raw OHLC in alert payload — alert() messages that read series.close(0) (current-bar value) directly. Fine for closed-bar backtests today, but any future real-time / streaming mode will re-fire the alert mid-bar with a different number than the chart shows.
  • Pine equivalents — when importing Pine, the same family fires on alert("… " + str.tostring(close)) as well as barealert(...) calls that reference close / high/ low / open without a barstate.isconfirmedguard.

The fix in every case is to either reference the previous (closed) bar viaseries.close(1) / close[1], or to wrap the side-effect in a bar-close guard.

Strategy / orders

Order-flow traps. Two buy() calls without an interveningsell() — the second is silently dropped. Pine'scalc_on_every_tick=true — backtest and live diverge. Pyramiding without an explicit limit.

Tip
Findings are dismissible per session. Saving a strategy with unsafe findings active shows a confirm dialog that points you back at the Analyze modal — the gate is a prompt, not a hard block.

Ask Augur

Augur is Sentimentor's in-app AI assistant. PineNode exposes two entry points into Augur — one anchored on the strategy as a whole and one anchored on a specific safety finding — so you can improve a complete strategy or address an exact warning without leaving the editor.

Editor button

Next to Import from TV Pine Script in the editor toolbar, the Ask Augur button offers Improve the strategy and Suggest backtest config, which open a draft with the current strategy source. It also offers Compare recorded versions and Follow recorded outcomes: read-only reviews of retained records, not the unsaved editor source. Review and submit the draft yourself; choosing a menu item does not run a test or start monitoring.

Augur lists your records before comparing selected versions or reviewing existing outcomes. See strategy history and published-history integrity for provenance, missing-fill handling and public-visibility limits.

Note
Finding-specific Fix the logic and Explain this warning actions remain in the Safety Report because they need the exact rule and line as context.

Per-finding dropdown

Every safety finding card in the Analyze modal has its own Ask Augur button on the right side of the rule row. That dropdown shows the two finding-anchored intents: Fix the logic and Explain this warning. Clicking either packages the finding (rule id, line, severity, headline, explanation, suggested fix) alongside the strategy source into the seeded prompt — so the agent sees the exact warning the analyzer surfaced and doesn't have to guess which line you're asking about.

Warning at line 24 (unsafe): Indicator "smaFast" is instantiated inside onBar
Rule id: pn/repaint/indicator-in-onbar
Analyzer says: Indicators that hold a rolling window...
Suggested fix: Move the new SMA(...) outside the onBar callback...

The four intents

IntentSurfaceWhat Augur does
Fix the logicFinding cardDecides whether the warning is real, then rewrites the strategy with the minimum change that resolves it. If it's a false positive, explains why so you can dismiss it.
Explain this warningFinding cardNo rewrite — just an explanation of whether the warning applies given the surrounding logic.
Improve the strategyEditor toolbarOpen-ended refactor for clarity, signal quality, robustness, or performance. Keeps the spirit of the strategy intact.
Suggest backtest configEditor toolbarRecommends timeframe, lookback window, stop-loss %, take-profit %, position sizing, trade direction — with brief justification.

The seeded prompt always includes the full strategy source inside a fenced```javascript block so Augur has the actual code to reason about, not a summary. You can review and edit the prompt before sending — the chat input is pre-populated, not auto-submitted.

Tip
Augur runs on the same chat surface as the rest of the agent (Sentimentor's Market Chat). When the agent emits a runnable PineNode strategy in its reply, you can click Open in Strategy Tester to load the editable code. In Strategy Tester, choose Validate & backtest for the full robustness protocol or Exploratory backtest for a quick research run — closing the loop from question → suggested fix → loaded code in two clicks.

CLI

For headless backtests, robustness validation, safety analysis, debugging, batch optimization, scripted research, and paper/forward runners, PineNode ships a Node CLI distributed as the npm package @sentimentor/pinenode-cli. The CLI binary is called pn (short for "pine node"). Since v1.1.0 every functional verb runs directly under the top-level command (pn backtest / pn fetch / pn optimize / pn push / pn pull / pn deploy), so the first token after pn always names the operation you're running rather than a file path. The older pn run <verb> form from 1.0.x is still accepted for backwards compatibility. v1.3.1 adds protocol validation, local optimization, Augur research/refinement, strategy and runner lifecycle commands, public proof, custom indicators, safety analysis, debugging, templates, parameter overrides, and futures routing. Asset-class flags (--crypto / --forex / --indices / --futures) letpn fetch, pn backtest --ticker …, and pn optimize can target any market Sentimentor supports — see the Market selection section below. Strategy files ride on --strategy <file>, which defaults to ./strategy.pinenode when omitted. The repository is on GitHub at sentimentor-ai/pinenode-cli — PRs and issues welcome.

Install

Requires Node 18 or newer. Install globally with npm:

bash
npm install -g @sentimentor/pinenode-cli@1.3.1 # Verify the install pn version # → 1.3.1 pn help

The shipped binary is fully self-contained — the PineNode engine, transpiler, and safety analyzer are all bundled in. No additional Sentimentor checkout required.

If pn version reports something different from what npm view @sentimentor/pinenode-cli version shows, npm probably served a cached tarball. Force it:

bash
npm cache clean --force npm install -g @sentimentor/pinenode-cli@1.3.1 which pn pn version

Auth — generating + using a token

The CLI is included with every Sentimentor subscription. Trial and free accounts get local backtests only against bars files you supply — a try-before-you-buy affordance. Pro unlocks historical-data fetch and Sentimentor data reads (sentiment, dark-pool, options flow). Elite adds the optimizer, cloud library (push/pull), and validated Alpaca Paper deployment; Trader includes the same CLI paper-deployment capability. Live brokerage support is planned for December 2026, with additional brokers to follow, and is not enabled. The token you generate inherits your current plan's capabilities automatically — no separate billing on the CLI side.

Generate one from /account/cli-tokens:

  1. Run pn login — your browser opens the management page.
  2. Click Generate token. The plaintext is shown once — copy it immediately.
  3. Paste it at the CLI prompt. The token gets stored in ~/.config/sentimentor/credentials.json (mode 0600).
bash
# Interactive — opens the browser, prompts for the pasted token pn login # Non-interactive (CI) — skip the prompt, pass the token directly pn login --token pn_live_•••••••••••••••••••• # Check what's saved + force a refresh of the capability cache pn whoami pn whoami --refresh # Revoke the current token server-side + wipe the local file pn logout
Tip
You can issue multiple tokens — one per machine is convenient. Each token shows up in the management UI with a fingerprint (hostname, platform, last-used timestamp) so you can revoke just the one from a lost laptop without disturbing your other machines.

Backtest — pn backtest

Local backtest against a bars file you supply — available on every plan, including Trial / Free. The strategy file is passed via --strategy so the sub-verb slot names the operation, not a file path.

Tip
--strategy accepts three shapes: omit it and the CLI reads ./strategy.pinenode from the current directory; pass a path to a local file and it's read verbatim; pass a cloud strategy name or UUID (e.g. --strategy "SMA Cross + Sentimentor") and the CLI pulls the source from your cloud library before running — no explicit pn pull needed. Name matches are case-insensitive and must be unique; ambiguous matches print the offending UUIDs so you can pin the exact one. Local paths work on every plan; resolving a cloud strategy name or UUID requires the cloud-library capability available on Elite and Trader.

Need a bars file to test with? Download sample-bars.json — 121 daily AAPL candles (~5 months), ready to drop into your working directory as bars.json and use in the examples below.

bash
# Short form — reads ./strategy.pinenode pn backtest --bars bars.json # Explicit local path pn backtest --strategy other-strategy.pinenode --bars bars.json # Cloud strategy by NAME — pulled + backtested in one step (Elite+) pn backtest --strategy "SMA Cross + Sentimentor" --bars bars.json # Cloud strategy by UUID — unambiguous when names clash (Elite+) pn backtest --strategy be538db3-a925-4712-be9d-baf915dd9a70 --bars bars.json # CSV bars + non-default capital pn backtest --bars bars.csv --capital 50000 # Machine-readable single JSON object (good for CI assertions) pn backtest --bars bars.json --json # Every braille chart at once — equivalent to combining all --chart-* pn backtest --bars bars.json --rich

Selective chart output. The --chart-* flags render individual sections of the rich report so you can compose exactly the view you need. Combine as many as you want; each flag is additive.

bash
# Just the equity + drawdown line charts pn backtest --bars bars.json --chart-line # Just the colored P/L distribution histogram pn backtest --bars bars.json --chart-bar # Just the price scatter with entry/exit markers (▲ ▼ ● ○) pn backtest --bars bars.json --chart-scatter # Just the top-drawdowns table pn backtest --bars bars.json --chart-table # Any combination is additive — line + histogram is a common pair pn backtest --bars bars.json --chart-line --chart-bar

--chart-line covers both the equity curve and the drawdown curve because they share an x-axis and are read together. Every chart falls back cleanly on terminals without UTF-8 support.

Fetch bars and run in one shot — Pro, Elite, or Trader:

bash
# Uses ./strategy.pinenode by default; --strategy overrides pn backtest --ticker NVDA --days 365 --timeframe 1d # Same, plus inject Sentimentor's dark-pool / options-flow / # sentiment inputs into the strategy before the bar loop runs pn backtest --ticker NVDA --sentimentor

Fetch data — pn fetch

Pull historical OHLCV bars to a file, to a braille chart, or to stdout — Pro, Elite, or Trader. Three output modes, picked by which flag you set:

bash
# --out <file> — write bars to disk (JSON or CSV by extension) pn fetch --ticker AAPL --days 730 --timeframe 1d --out aapl.json pn fetch --ticker NVDA --days 90 --timeframe 15m --out nvda.csv # --chart-* — render bars as a braille chart to stdout pn fetch --ticker AAPL --days 730 --chart-line pn fetch --ticker AAPL --days 730 --chart-line --chart-bar --chart-table pn fetch --ticker AAPL --days 90 --timeframe 1h --chart-scatter # (no flag) — dump bars as JSON to stdout (pipeable) pn fetch --ticker AAPL --days 30 | jq '.[0]' # peek at bar 0 pn fetch --ticker AAPL --days 30 > aapl.json # tee to a file

Chart flags on fetch mean: --chart-line is a connected close-price curve, --chart-bar is a per-bar volume histogram, --chart-scatter is a close-price dot per bar, and --chart-table is a summary of first / last / min / max / mean / stdev / % change. Combine any set for a composed view.

Market selection — --crypto / --forex / --indices / --futures

Expanded in v1.3.1. pn fetch, pn backtest --ticker …, and pn optimize target the stocks market by default — which also covers ETFs like SPY / QQQ / GLD since Polygon exposes those through the equities feed. Pick a different asset class with one of four mutually-exclusive flags:

FlagAsset classSymbol format
(none)Stocks & ETFsAAPL, NVDA, SPY, QQQ
--cryptoCryptocurrencyBTCUSD, ETHUSD, SOLUSD
--forexForex pairsEURUSD, USDJPY, GBPUSD
--indicesMarket indicesSPX, NDX, DJI, VIX
--futuresFutures contractsESZ6, NQZ6, CLX6

Symbols are always in the bare form — the CLI adds Polygon's X: / C: / I: prefix behind the scenes based on the market flag. Passing more than one market flag is a validation error.

bash
# Stocks (default — no flag needed) pn fetch --ticker AAPL --days 30 --out aapl.json pn backtest --ticker NVDA --days 365 --strategy sma-cross.pinenode pn optimize --ticker TSLA --days 90 # ETFs — same route as stocks pn fetch --ticker SPY --days 30 --out spy.json pn fetch --ticker QQQ --days 30 --chart-line # Crypto pn fetch --ticker BTCUSD --crypto --days 30 --out btc.json pn fetch --ticker ETHUSD --crypto --days 30 | jq '.[0]' pn backtest --ticker SOLUSD --crypto --days 90 --strategy momentum.pinenode pn optimize --ticker BTCUSD --crypto --days 180 # Forex pn fetch --ticker EURUSD --forex --days 30 --out eur.json pn fetch --ticker USDJPY --forex --days 90 --timeframe 1h --chart-line pn backtest --ticker GBPUSD --forex --days 365 --strategy trend.pinenode # Indices pn fetch --ticker SPX --indices --days 30 --out spx.json pn fetch --ticker VIX --indices --days 90 --chart-line pn optimize --ticker NDX --indices --days 365

When a fetch on the default stocks market returns zero bars, the CLI prints a hint suggesting the crypto / forex / indices flag — a common mistake is running pn fetch --ticker BTCUSD (no flag) and getting nothing back because BTCUSD isn't a stock.

Optimize — pn optimize

Submit the strategy's pn.param() declarations to Sentimentor's bundled deterministic optimizer — Elite or Trader. It evaluates up to 2,000 combinations locally and ranks valid results by Sharpe, then return. The command accepts the same --crypto / --forex / --indicesmarket flags as fetch — see Market selection.

bash
# Use the param() bounds declared in the strategy itself # (--strategy defaults to ./strategy.pinenode when omitted) pn optimize --ticker NVDA --days 365 # Crypto — grid-search a momentum strategy on ETHUSD pn optimize --ticker ETHUSD --crypto --days 180 # Override the search space with a grid spec file pn optimize --ticker NVDA --grid grid.json --json

grid.json shape:

{
  "Fast Period": { "min": 5,  "max": 50,  "step": 5  },
  "Slow Period": { "min": 20, "max": 200, "step": 10 }
}

Validation, diagnostics, Augur, indicators, runners, and proof — v1.3.1

Version 1.3.1 exposes the web PineNode research lifecycle as headless commands. Validation uses the same server-owned discovery → purged rolling validation → one-time final lockbox protocol; later stages remain sealed when an earlier stage fails, and each failed rule is printed.

bash
pn validate --strategy strategy.pinenode --ticker SPY --days 3650 --embargo 5 pn analyze --strategy strategy.pinenode pn debug --strategy strategy.pinenode --bars bars.json --break-bars 20,50 --watch "bar.close" pn backtest --bars bars.json --params params.json pn convert strategy.pine --out strategy.pinenode # After reviewing a Degraded/Unsupported report: pn convert strategy.pine --out strategy.pinenode --allow-degraded pn indicator convert study.pine --out study.pinenode pn indicator run study.pinenode --bars bars.json --out plots.json pn library list pn augur --prompt "Research a daily SPY trend hypothesis" --ticker SPY pn augur --prompt "Reduce fragility" --strategy strategy.pinenode --out refined.pinenode pn strategies rename <id> --name "SPY Trend v5" pn forward --strategy <cloud-id> --ticker SPY pn runners list pn runners history <runner-id> pn runners pause <runner-id> --strategy <cloud-id> pn verify publish --strategy strategy.pinenode --ticker SPY pn ledger --ticker SPY --passed-only
Tip
Augur output is always an editable draft. validate tests the exact submitted code and never silently rewrites manual, imported, or generated artifacts. A successful validation of a cloud strategy stores its server-issued protocol so simulated or Alpaca Paper activation can verify the frozen code, parameters, risk settings, universe, ticker, and timeframe.

Cloud library — pn push / pull

Round-trip strategies through your Sentimentor cloud library — Elite or Trader:

bash
# Push the local file to your cloud library (defaults to # ./strategy.pinenode; --strategy overrides) pn push --name "NVDA EMA Cross v3" # List your saved strategies pn pull # Pull a specific strategy by id, write it to disk pn pull <strategy-id> --out my-strategy.pinenode

Alpaca Paper deployment

Heads up
Paper execution only. Sentimentor does not route live orders and has no live-mode fallback. Connect a user-owned Alpaca Paper account in Live Runner History, then activate a saved strategy that has passed discovery, rolling validation, and its one-time final lockbox.

PineNode CLI Alpaca Paper deployment is available on Elite and Trader. Connect the paper account under Profile → Broker Connections, then use the CLI to activate an eligible saved strategy. Web-app Alpaca Paper deployment is available on every plan, including Trial. Credentials stay encrypted on the server and are never returned to the browser or CLI. Broker-backed runners are limited to validated, long-only US-equity strategies, completed bars, market orders, day time-in-force, and the configured risk limits.

CLI deployment accepts a cloud strategy name or ID, never arbitrary local source. The server reconstructs the frozen parameters and verifies the saved code, ticker, timeframe, validated universe, configuration fingerprint, and consumed final-lockbox record before activating the runner:

bash
pn deploy --strategy "SPY Trend v4" --ticker SPY --paper \ --max-order-notional 1000 \ --max-orders-per-day 10 \ --daily-loss-limit 250 # Pause future scans. This does not cancel accepted orders or close positions. pn deploy stop <runner-id>

Code, parameters, initial capital, sizing, stops, direction, ticker, and timeframe remain frozen by validation. The three optional CLI limits above are operational guards, not research inputs. Local files and configuration-changing flags are rejected with instructions to save and revalidate first. --live is not available; live brokerage support is planned for December 2026, with additional broker integrations to follow. Orders use deterministic client IDs so a retry cannot intentionally submit the same completed-bar signal twice. Sentimentor reconciles accepted, partial, filled, rejected, expired, and canceled order states; repeated broker failures pause the runner. Disconnecting Alpaca pauses paper-backed runners and does not close positions or cancel pending orders.

See the dedicated Alpaca Paper setup, safeguards, monitoring and troubleshooting guide before activating a broker-backed runner.

Bars file format

Both --bars file.json and --bars file.csv are accepted.

JSON — array of OHLCV rows:

[
  { "time": 1709480400000, "open": 174.32, "high": 175.10, "low": 173.95, "close": 174.88, "volume": 1240500 },
  ...
]

CSV — header row required, column order free:

time,open,high,low,close,volume
2024-03-04T14:30:00Z,174.32,175.10,173.95,174.88,1240500
2024-03-04T14:31:00Z,174.88,175.05,174.40,174.61, 920100

time accepts Unix milliseconds, an integer string, or any ISO-8601 datetime — the CLI normalizes them all to ms.

Exit codes

Distinct exit codes for shell-script-friendly branching:

CodeMeaning
0Success
1Usage error or bad input
2Strategy failed to load (transpile or compile error)
3Backtest runtime failure, robustness protocol did not pass, or a requested Augur strategy artifact was absent
4Upgrade required — your plan doesn't include this command
5Not logged in — run pn login
6API error from auth.sentimentor.ai or data.sentimentor.ai
99Unexpected CLI bug — please file an issue on GitHub

Plan matrix

Sentimentor has three paid plans — Pro, Elite, and Trader — plus a Trial / Free state for users inside the 7-day trial window, after trial expiry, or with a cancelled / past-due subscription. The CLI ships with every plan; the gating rule is: Trial / Free can run local backtests against files you supply, Pro adds historical-data fetch + Sentimentor data reads, Elite adds the optimizer, cloud library, and Alpaca Paper deployment; Trader includes the same CLI paper capability. Live brokerage deployment is planned for December 2026, with additional brokers to follow, and is not currently available.

CommandTrial / FreeProEliteTrader
login / logout / whoami / help✓✓✓✓
pn backtest --bars <file> (local)✓✓✓✓
pn backtest --ticker / pn fetch (historical data)—✓✓✓
pn backtest --sentimentor (sentiment / flow / dark-pool)—✓✓✓
pn optimize——✓✓
pn push / pn pull (cloud library)——✓✓
pn deploy --paper (validated cloud strategy)——✓✓
pn deploy --live (planned December 2026)————

When the CLI tries to run a command your current plan doesn't cover, it exits with code 4 and a colored "Upgrade required" message that links straight to billing.

Migrating from Pine Script

Click Import from TV Pine Script in the PineNode editor, paste your Pine v2-v6 source, and select Convert. The deterministic transpiler handles the compatibility surface below and identifies code that needs manual review before backtesting. TradingView's current language is Pine Script v6; conversion is not a claim of full compiler or broker-emulator parity.

Note
Conversion contract: a clean conversion means the emitted PineNode program compiled using supported syntax. It does not mean every TradingView execution detail is equivalent. Order types, fill timing, multi-timeframe data and stateful Pine language constructs must also pass the compatibility report before results are compared.

Every import receives one of three persistent grades. Exact means no known semantic difference was detected within PineNode's closed-bar runtime. Degradedmeans the output can run but one or more documented behaviors differ. Unsupportedmeans the output is partial and requires manual rewriting. Non-exact imports require an explicit acknowledgement, and the grade, Pine version and issue counts are embedded at the top of the saved PineNode source so the conversion report travels with the strategy.

Direct mappings

Pine v2-v6PineNode
ta.sma(close, n)new SMA(n)
ta.ema(close, n)new EMA(n)
ta.wma / ta.vwma / ta.rmanew WMA / VWMA / RMA
ta.hma / ta.alma / ta.swmanew HMA / ALMA / SWMA
ta.rsi(close, n)new RSI(n)
ta.macd(close, f, s, sig)new MACD(f, s, sig)
ta.bb(close, n, m)new BollingerBands(n, m)
ta.atr(n)new ATR(n)
ta.stoch(...)new Stochastic(...)
ta.cci(close, n)new CCI(n)
ta.stdev / ta.variance / ta.devnew StandardDeviation / Variance / MeanDeviation
ta.sum / ta.cum / ta.change / ta.momnew RollingSum / CumulativeSum / Change / Momentum
ta.linreg / ta.tr / ta.mfinew LinearRegression / TrueRange / MFI
ta.adx(...)new ADX(...)added in 2026-06
ta.obv()new OBV()added in 2026-06
ta.vwap(close)new VWAP()added in 2026-06
ta.wpr / ta.willrnew WilliamsR(n)added in 2026-06
ta.roc(close, n)new ROC(n)added in 2026-06
ta.crossover(a, b)crossOver(a, b)
ta.crossunder(a, b)crossUnder(a, b)
ta.highest(source, n) / ta.lowest(source, n)new Highest(n) / Lowest(n)
strategy.entry('L', strategy.long)enterLong('L')automatically reverses a short
strategy.entry('S', strategy.short)enterShort('S')automatically reverses a long
strategy.entry(..., when=condition)conditional enterLong / enterShortpreserves the entry gate
strategy.close('L')sell / cover by open side
strategy.exit('X', ...)close-based sell / cover checkspending OCO and intrabar fills not modeled
math.abs / max / min / round / floor / ceilMath.abs / max / min / round / floor / ceil
math.pow / sqrt / log / log10 / expMath.pow / sqrt / log / log10 / expadded in 2026-06
math.sin / cos / tan / atan / atan2 / asin / acosJavaScript Math equivalentsincludes sinh, cosh, tanh, log2 and degree/radian conversion
math.avg(a, b, ...)(a + b + ...) / countvariadic
barstate.isconfirmedtrueonBar fires once per closed bar
barstate.isnewtrue
barstate.islastfalse + conversion warningstreaming callback does not know the final dataset bar
barstate.isfirstbarIndex === 1
barstate.ishistory / isrealtimetrue / false
input.int(20, 'Fast')pn.param('Fast', 20, ...)
input.bool(true, 'Use stop')pn.boolParam('Use stop', true)
input(true, title='Use stop')pn.boolParam('Use stop', true)legacy v2-v4 form
input.string('SMA', 'MA type')const maType = 'SMA';
var float x = na / x := valuetop-level let x / per-bar assignmentpersists across closed bars
x[1], x[2], ...prior completed scalar valuesincludes variables updated with :=
barssince(condition)stateful per-bar counterreturns na until the first match
helper(a) => expressionconst helper = (a) => expressionsingle-expression helpers
time / timestamp(y, m, d, h, min)bar.time / Date.UTC(...)date-window filters
tr / max(a, b) / min(a, b)true-range expression / Math.max / Math.minlegacy v2-v4 globals
numeric naNumber.NaNpreserves false comparisons and plot gaps
na(value) / nz(value, replacement)Number.isNaN / null-safe fallback
array.new<float>(n, init)new Array(n).fill(init)and array.new<int / bool / string>
array.push / pop / shift / unshiftarr.push / pop / shift / unshift
array.get(arr, i) / set(arr, i, v)arr[i] / (arr[i] = v)
array.size(arr)arr.length
array.sum / avg / max / minreduce / Math.max / Math.min
array.median / stdevinline helperO(n log n) sort for median
array.includes / indexof / slice / copy / sort / reverse / concat / join / fillnative Array equivalents
array.first / last / insert / remove / range / variance / standardize / covariance / binary_searchnative Array or deterministic inline helpers
map.new<K, V>()new Map()
map.put / get / contains / removem.set / get / has / delete
map.size / clear / keys / values / copym.size / clear / Array.from(m.keys()) / Array.from(m.values()) / new Map(m)
matrix.new<float>(rows, cols, init)Array.from(...).fill(...)nested arrays
matrix.get(m, r, c) / set(m, r, c, v)m[r][c] / (m[r][c] = v)
matrix.rows / columnsm.length / m[0]?.length
matrix.row / col / transpose / tracenested-array equivalents
str.tostring(x, n)Number(x).toFixed(n)single-arg form → String(x)
str.tonumber(s)Number(s)
str.length / contains / startswith / endswiths.length / includes / startsWith / endsWith
str.upper / lower / trim / split / repeat / substrings.toUpperCase / toLowerCase / trim / split / repeat / substring
str.pos / reverse / capitalizeindexOf / reverse / case transform
str.replace / replace_alls.replace / s.split().join()
str.format('{0} {1}', a, b)positional templatebest-effort
log.info / warning / errorconsole.log / warn / errorrouted into the PineNode console
alertcondition(cond, title, msg)if (cond) alert(title + ' — ' + msg);declaration becomes per-bar emit

Loudly degraded (with explicit warning)

request.security(symbol, tf, expr) is rewritten to the chart-timeframe value of expr and a warning is added to the conversion report. PineNode runs on a single timeframe today — the multi-timeframe dimension is dropped, but the rest of the strategy continues to parse and run instead of silently breaking on a missing variable. Review every flagged line manually.

Price-triggered strategy.entry() calls are currently converted to bar-close market entries. stop, limit, OCA settings andstrategy.cancel() are reported because PineNode does not yet maintain a TradingView-compatible pending-order book. Quantity, commission, slippage,calc_on_order_fills and next-tick fill assumptions also require explicit backtest configuration and result comparison.

Silently skipped (chart-only visualization)

plot (including assigned handles such as p1 = plot(...)), plotshape, plotchar, plotarrow,plotcandle, plotbar, bgcolor, barcolor, hline,fill, label.*, line.*, box.*,polyline.*, table.*, color.*. These render to the TradingView chart — there's nothing to render against in a headless backtest, so the transpiler drops them rather than emitting code that would throw. (PineNode has its own per-bar plot(name, value) for indicator lines if you want to visualize values on the chart.)

Flagged as unsupported

Multi-statement user-defined functions, user-defined types and methods, libraries/imports, varipintrabar persistence, loops, switch, nested indentation-sensitive scopes, arbitrary tuple destructuring, dynamic requests and complex multi-armelse if chains. The transpiler emits a line-specific note instead of pretending those semantics were preserved.

Strategy order semantics

Pine's default pyramiding value is one. That limits additional same-direction entries while a trade in that direction remains open; it does not drop an opposite-direction strategy.entry(). An opposite entry reverses the open position automatically. PineNode's convertedenterLong()/enterShort() helpers preserve that reversal, and the analyzer only warns about repeated same-direction entries at the active limit.

Tip
After conversion, click Analyze to run the safety lint on the PineNode output — Pine's implicit safety assumptions don't carry over, and the analyzer catches the most common conversion mistakes.

Recipes

Three small examples showing the API in context.

RSI mean-reversion with ATR stops

const rsiLength = pn.param('RSI Length', 14, 7, 30, 1);
const oversold  = pn.param('Oversold',  30,  20, 40, 1);
const overbought = pn.param('Overbought', 70, 60, 80, 1);

const rsi = new RSI(rsiLength);
const atr = new ATR(14);

strategy
  .name('RSI Mean Reversion')
  .onBar(({ series, buy, sell }) => {
    if (series.length < rsiLength + 1) return;
    rsi.next(series);
    atr.next(series);
    if (rsi.value < oversold)   buy('rsi oversold');
    if (rsi.value > overbought) sell('rsi overbought');
  });

Bollinger Bands breakout

const bb = new BollingerBands(20, 2);
strategy
  .name('BB Breakout')
  .onBar(({ series, buy, sell }) => {
    bb.next(series);
    if (!bb.value) return;
    if (series.close(0) > bb.value.upper) buy('break above upper');
    if (series.close(0) < bb.value.lower) sell('break below lower');
  });

Sentimentor confluence — only buy when 3 signals agree

const sma = new SMA(50);
strategy
  .name('Confluence Long')
  .onBar(({ series, buy, sentimentor }) => {
    sma.next(series);
    const aboveSma   = series.close(0) > sma.value;
    const dpAgrees   = sentimentor.darkPool?.ratio > 0.55;
    const sentBull   = (sentimentor.sentiment?.score ?? 5) > 6.5;
    if (aboveSma && dpAgrees && sentBull) buy('3-way confluence');
  });

FAQ

Why JavaScript and not Python?

The strategy runs in your browser and on our serverless backtest workers. JS compiles once, runs everywhere, and the indicator library doesn't need a separate transpilation step. Python in the browser via Pyodide is a 6 MB runtime cost for roughly the same expressiveness.

Can I add my own indicators?

Yes. Open Custom Indicators from the chart's right rail, write the source or generate an editable draft with Augur, select Run, then add it to the chart or save it privately. See the complete custom-indicator API and workflow.

Does PineNode support multi-timeframe?

Not yet — every strategy runs at the chart's native timeframe. Importedrequest.security() calls are graded Degraded and use the chart-timeframe expression only after explicit acknowledgement. Native multi-timeframe support requires a point-in-time, no-lookahead data contract and does not currently have a published release date.

Can I deploy a strategy to a broker?

Only to Alpaca Paper today. Live brokerage support is planned for December 2026, with additional broker integrations to follow; other brokers are not supported today. A strategy must pass discovery, rolling validation, and the final lockbox for the exact ticker, timeframe, parameters, sizing, and cost configuration before it can become a broker-backed runner. Activation currently can happen in the PineNode web interface or through pn deploy --paperusing a saved cloud strategy. Both paths enforce the same server-owned verification.

Alpaca authorization uses OAuth with the paper environment forced at both connection and execution time. Paper runners are long-only US equities and submit market/day orders after a signal bar is complete. The separate journal importer also accepts Alpaca Paper history only.

Why does the analyzer flag X?

Each finding has a rule id (e.g. pn/repaint/series-length-not-checked) and a fix string in the Analyze modal. If a finding is genuinely a false positive, dismiss it — dismissals are remembered for the session.

Need something not covered here? Use the in-app feedback widget or drop us a note from the support menu in the header.