@hoox-sh/pynets
v0.10.0
Published
PyneTS — TypeScript / Bun Pine Script library (parse, unparse, interpret, stream). Python pynescript Runtime remains the source of truth.
Maintainers
Readme
PyneTS
TypeScript / Bun library for Pine Script: parse, unparse, and interpret.
Same public names as Python pynescript: parse, unparse, Runtime.run.
Python pynescript.runtime is the source of truth. When semantics disagree, Python wins. This is not a TradingView platform certification.
Docs (hoox.sh): PyneTS · install · runtime · CLI · JS compile · parity · missing features · parity status · ecosystem
import { parse, unparse, Runtime } from "@hoox-sh/pynets";
const src = `//@version=5
indicator("sma")
plot(ta.sma(close, 3))
`;
const tree = parse(src);
unparse(tree);
const out = new Runtime("AAPL").run(src, [
{ close: 1 }, { close: 2 }, { close: 3 }, { close: 4 },
]);
// out.plots, out.series, out.count
// new Runtime("AAPL", { mode: "compile" }) — JS bar-loop emit (not Numba)
// mode: "auto" tries compile, then interpretInstall with Bun (published TypeScript source — no bundle needed):
bun add @hoox-sh/pynets
bunx pynets -- helpNode and browsers use the Bun-built ESM in dist/ (bun run build; npm pack runs it via prepack):
# Node 20+
npm install @hoox-sh/pynetsimport { parse, Runtime } from "@hoox-sh/pynets";<script type="module">
import { Runtime } from "https://esm.sh/@hoox-sh/pynets";
</script>The CLI still needs Bun on PATH. This is not a Cloudflare Worker package.
Where this repo wins
Most TypeScript Pine ports rewrite the language. PyneTS does not. It is the library-shaped port of PYNE: the same ANTLR grammar, the same ASDL field names, the same interpret contract.
| | Typical JS Pine runtime | PyneTS |
|---|---|---|
| Grammar | Hand-rolled or a forked copy | Shared PYNE .g4, TS ANTLR target — not forked here |
| AST | Custom / Zod (Identifier, Literal) | ASDL names (kind, lineno, col_offset) |
| Semantics | “Looks like Pine” | Python Runtime wins — every builtin is ported against pynescript |
| na | NaN, undefined, or thrown | null. na == na is true. Non-finite in/out is na |
| Lookback | Off-by-one or OOB crash | close[1] is previous; OOB / negative → na |
| Foreign data | Silently reuse the chart | request.security without data is na — we do not invent bars |
| Strategy | Events only, or a stats demo | Fills, ledger, strategy.risk.*, OCA |
| Matrix | Add/multiply if you are lucky | inv / pinv / eigen / rank / kron |
| Libraries | Unresolved import dies | In-process registry + soft stubs |
| Arrays | Push/get | Near-full array.* including search, percentiles, stdev |
| Host API | Ad-hoc | Runtime.run / stream / runProvider — same envelope as Python |
| CLI | None or a toy | check / format / run / dump / info — pipes stay JSON |
Family map — library vs edge host, same SoT:
| | pynescript | pyne-worker | pyne-agent-worker | PyneTS |
|---|---|---|---|---|
| Role | SoT library | Production Python Cloudflare Worker | NL authoring (Workers AI™) | TS / Bun library + CLI |
| Engine | interpret + Numba compile | Vendors pynescript.runtime | Writes source; optional /run validate | interpret + JS compile |
| Grammar | *.g4 | Same Python engine (not a fork) | — | same .g4, TS ANTLR target |
| How you call it | Runtime.run | POST /run | POST /v1/chat | Runtime.run (interpret / compile / auto) |
| Extra | LSP, Pro API | R2 OHLCV, 1m cron, alerts, trade-worker | AXIS plugin | Local TTY (check / format / run / dump) |
| Docs | hoox.sh/pyne/docs | pyne-worker | agent | pynets |
pyne-worker is the edge evaluate host of the Python engine. pyne-agent-worker writes scripts. PyneTS is the TypeScript library you import and the CLI you run on a laptop. They share the PYNE contract; this repo is not a Worker.
This repository is the standalone @hoox-sh/pynets checkout. PYNE consumes it only as the pynets/ git submodule — never copy sources back into hoox-sh/pyne.
Runtime contract
These are the rules that make plots match Python. Other ports usually get them wrong.
naisnull. A non-finite number in or out becomesna.na == nais true. Any other comparison involvingnais false.- Series lookback:
x[0]current,x[1]previous, out of range →na. - TA is one sample per bar, state keyed by call-site (Python incremental kernels).
- Foreign / HTF
request.securitywithout a feed →na. The chart series is never silently reused as another symbol. - v3/v4 bare aliases (
sma,ema,rsi, …) resolve when Python does.
const out = new Runtime("TEST").run(
`indicator("t")
plot(close[1])
plot(na == na ? 1 : 0)`,
[{ close: 10 }, { close: 20 }],
);
// plots[0] last bar = 10 (previous close)
// plots[1] = 1What interpret covers
Wired through one dispatcher (src/runtime/interpret.ts). Not a second engine.
Language — var / varip / :=, tuples, if / for / while / switch, UDFs, type / enum, import, library().
ta.* — SMA/EMA/RMA/WMA/VWMA/HMA/ALMA/DEMA/TEMA/KAMA/SWMA, RSI, ATR, Supertrend, Keltner, BB/BBW, MACD, Stoch, CCI, WPR, DMI/ADX, SAR, MFI, CMO, TSI, COG, stdev/dev/variance, highest/lowest, crossover/crossunder, volume (OBV, PVT, AD, WAD, NVI/PVI, III, WVAD), pivots, barssince, valuewhen, percentiles, and the v3/v4 bare names. Also uo, rci, dpo, kst, stochrsi, donchian, ichimoku, bb_pct, emv, fractal, atr_stop, zigzag, the Python bar-mode detectors (hammer, gap_detector, macd_signal, acceleration_factor, stoch_smooth, engulfing, inside_bar_pattern, double_top_bottom, voi, bid_ask_imbalance, rsi_oversold_overbought, rsi_divergence, cumulative_delta, volume_momentum, volume_profile_high / low, volume_thrust), and the statistical / risk analytics waves: sma_weighted, apo, atr_normalized, beta, r_squared, comovement, skewness, kurtosis, volume_weighted_momentum, parkinson, garman_klass, mean_reversion_score, market_condition, order_flow_imbalance, liquidity_score, fear_greed_index, crowd_sentiment, smart_money_flow, trend_confirmation_score, max_loss_level, profit_lock_level, position_sizing, kelly_criterion, risk_reward_ratio, risk_reward_asymmetry, probability_of_movement, trend_strength, breakeven_level, market_structure_pivot, position_sizing_score, trailing_exit_level, optimal_entry_zone, mean_reversion_entry, multi_timeframe_signal, market_timing_index, regime_adaptive_signal, strategy_score, breakout_detection, ema_cross_signal, momentum_filter, volatility_regime, economic_impact_score, contrarian_signal, drawdown_recovery_level, pivot_point_levels, volatility_regime_score, correlation_filter, advanced_breakout_detector, pullback_bounce_level, divergence_detector, momentum_divergence, spread_analysis, employment_cycle_indicator, gdp_growth_proxy, inflation_proxy_indicator, expected_value, gamma_levels, intelligent_strategy_synthesizer, signal_confluence. Compile emits the same names. All 941 PYNE-registered builtin names resolve — see PARITY.md for the structural aliases and remaining edge semantics.
math.* / str.* / color.* / input.* / log.* — the Python set, including nz, iff, fixnan, timestamp, weekofyear, time_tradingday.
array.* — new/from, get/set, push/pop/shift/unshift, slice/copy/concat, sort/search (binary + leftmost/rightmost), aggregates (avg/min/max/sum/median/mode/range/stdev/variance/covariance), percentiles, standardize, sort_indices.
matrix.* — shape ops, predicates, mult / inv / pinv / pow / kron / rank / eigenvalues / eigenvectors, plus row/col sum / avg / min / max / mode, fill, reverse, copy, stdev, variance.
strategy.* — entry/exit/order, pending limit/stop, commission/slippage/pyramiding, open/closed trade accessors, strategy.risk.*, OCA cancel/reduce. Market orders placed on bar N fill at the open of bar N+1 (PYNE 0.6.29 reference-Pine flow), and trade entry_time / exit_time use bar timestamps. strategy.cash is free cash and still selects cash sizing for default_qty_type.
Drawings / alerts — line / label / box / table / polyline / linefill getters and setters, *.all, *.copy, chart.point.*; alert / alertcondition with Python frequency tokens (alert.freq_*). plot.style_* tokens. timenow is the dataset last-bar time on every bar.
request.* — same-symbol security passthrough; foreign / HTF without a feed is na. request.footprint is Python’s mock object (footprint.* / volume_row.*); request.seed seeds that mock.
Run scripts
import { Runtime } from "@hoox-sh/pynets";
const rt = new Runtime("AAPL", {
timeframe: "5",
broker: { commission: 0.001, slippage: 0, pyramiding: 0 },
});
const result = rt.run(source, bars);
// result.plots last values, plot order
// result.series titled series (plot, strategy fields, …)
// result.plot_data { title: { data: [{ value, time? }] } }
// result.events strategy events
// result.fills fill tape
// result.drawings line/label/box/table
// result.logs log.info / warning / error
// result.error set on parse/runtime failure (does not throw)Push-driven re-eval:
const stream = rt.stream(source);
stream.on("bar", (out) => { /* one result per push */ });
stream.push({ close: 1 });
stream.push({ close: 2 });
// Re-tick the forming last bar (does not advance barCount):
// barstate.isrealtime is 1, ishistory 0; once/TA/broker state recomputes.
stream.updateLastBar({ open: 2, high: 3, low: 1, close: 2.5 });
stream.appendBars([{ close: 3 }, { close: 4 }]);
stream.close(); // confirm the last bar (realtimeLastBar: false)Runtime.run(src, bars, { realtimeLastBar: true }) exposes the same flag for a
single call. Multi-tick realtime windows work too (Python realtime_ticks /
realtime_bars / realtime_from_bar):
// Visit bars 12.. as realtime, 3 ticks per bar: intermediate ticks roll back
// var / TA / broker / plot state (varip persists, plots capture the final tick).
rt.run(src, bars, { realtimeFromBar: 12, realtimeTicks: 3 });
rt.run(src, bars, { realtimeBars: 5, realtimeTicks: 2 });Bars you actually have
Hosts feed OHLCV. PyneTS will not invent a foreign market.
import { MemoryProvider, StaticMapProvider, JsonBarProvider } from "@hoox-sh/pynets";
await rt.runProvider(source, new MemoryProvider(bars), { limit: 500 });
// In-process symbol map (tests, fixtures, your own cache)
const map = new StaticMapProvider({
"AAPL": bars,
"AAPL:60": htfBars,
});
// Generic JSON GET — inject fetch, no vendor URLs baked in
const http = new JsonBarProvider({
url: ({ symbol, timeframe, limit }) => `https://you.example/ohlcv?s=${symbol}&tf=${timeframe}&n=${limit}`,
fetch: globalThis.fetch,
});Libraries
rt.registerLibrarySource(
"User",
"MathLib",
1,
`//@version=5
library("MathLib")
export double(x) => x * 2`,
);
rt.run(
`indicator("t")
import User/MathLib/1 as ml
plot(ml.double(close))`,
bars,
);Unresolved remote imports bind a soft stub (known helpers such as index_2d_to_1d still work). Register a source when you want real exports.
CLI
Rich TTY (PYNE volt palette). Pipes and CI stay plain: check prints ok, run / info print JSON.
bunx pynets -- help
bunx pynets -- check script.pine
bunx pynets -- format script.pine
bunx pynets -- run script.pine --bars 20
bunx pynets -- run strat.pine --commission 0.001 --slippage 1 --pyramiding 0
bunx pynets -- dump script.pine --rich --full
bunx pynets -- infoPut --rich / --plain after the command so bun run does not swallow them. --plain wins over leftover FORCE_COLOR. Exit codes: 0 ok, 1 syntax/runtime, 2 usage.
run prints a sparkline + last-value row per plot, a strategy events table when present, and drawings/alerts when present. --plain is machine JSON.
| Flag | Effect |
| --- | --- |
| --bars N | Synthetic bar count for run (default 20, max 100000) |
| --commission N | Broker commission fraction (0.001 = 0.1%) |
| --slippage N | Slippage in price units |
| --pyramiding N | Max same-direction adds |
| --json | Machine-readable run / info |
| --full | Full AST dump (no 80-line cap on --rich) |
| --plain | Disable styling |
| --rich | Force panels / sparklines |
Public API
import {
parse, unparse, dump, tokenize, PinescriptSyntaxError,
Runtime, RuntimeStream,
PineArray, PineMap, PineMatrix,
MemoryProvider, StaticMapProvider, JsonBarProvider,
LibraryRegistry,
} from "@hoox-sh/pynets";src/index.ts is the stable barrel. Generated ANTLR, parser internals, and CLI helpers are not re-exported.
Development
Agent brief: AGENTS.md · contribute: CONTRIBUTING.md · changes: CHANGELOG.md · parity status: PARITY.md
git clone https://github.com/hoox-sh/pynets.git
cd pynets
bun install
bun test
bun run typecheckParity tests run against the local PYNE checkout ($PYNESCRIPT_ROOT, default /home/jango/Git/pynescript) by putting $PYNESCRIPT_ROOT/src on PYTHONPATH; they skip cleanly when PYNE is absent. The current snapshot is verified against PYNE 0.6.32.
Generated files under src/generated/ are committed so bun test does not require Java. Local development is Bun only — do not add npm/yarn/pnpm lockfiles.
Grammar (do not fork)
bun run generate needs the shared PYNE .g4 files. They are not vendored here.
Resolution (scripts/generate-antlr.ts): PYNETS_GRAMMAR → PYNESCRIPT_ROOT → parent PYNE checkout → sibling ../pynescript or ../pyne.
# PYNE cloned next to this repo
bun run generate
# or
PYNESCRIPT_ROOT=/path/to/pyne bun run generateJava is required only to regenerate. The ANTLR jar is cached in .tools/ (gitignored).
Inside PYNE
git clone --recurse-submodules https://github.com/hoox-sh/pyne.git
cd pyne/pynets
bun install && bun testAfter a plain clone: git submodule update --init --recursive.
Non-goals
Numba nopython, Worker packaging, ASDL codegen. Compile is JS emit (Python object-mode analog), not a JIT.
