Open source · by AlgoBeam
Run strategy script everywhere.
AlgoBeamTS is our open-source transpiler and runtime — the bridge between AlgoBeamScript and the JavaScript ecosystem. Develop an indicator once, then deploy it on your own servers, in a browser tab, or inside the AlgoBeam platform, without rewriting a line or reconciling a number.
npm install algobeam-tsalgobeam/algobeam-ts · 38 kB gzipped · Node, browser, edge and CLI
If it runs JavaScript, it runs AlgoBeamTS
The compiler emits plain, readable ES modules with no native addons and no runtime dependencies — so the deployment question is answered by wherever you already run code.
Node target
The default target
Install the package, point it at a provider and load bars. The same class drives one-off scripts, cron jobs and long-lived services.
- Node 20+, Bun and Deno
- ESM and CommonJS builds
- No native addons to compile
// npm install algobeam-tsimport { AlgoBeamTS, Provider, indicators } from 'algobeam-ts' const algobeam = new AlgoBeamTS(Provider.Binance, 'BTCUSDT', '1h', 300)await algobeam.load() const rsi = await algobeam.run(indicators.rsi({ length: 14 }))const ema = await algobeam.run(indicators.ema({ length: 50 })) console.log('rsi(14)', rsi.last.toFixed(2))console.log('regime ', ema.rising ? 'expansion' : 'contraction')Point it at any market
A provider is one function that returns bars, oldest first. Use the exchange and vendor feeds we maintain in-tree, wrap your own REST API in about twenty lines, or replay a directory of CSV exports while the market is shut.
- Exchange, broker and vendor feeds maintained in the repo
- Any REST or websocket source becomes a provider in one function
- Timeframe resolution, gap filling and warm-up are handled for you
import { readFile } from 'node:fs/promises'
import { AlgoBeamTS, defineProvider, type Bar, type BarRequest } from 'algobeam-ts'
// A provider is anything that can return bars, oldest first.
export const CsvProvider = defineProvider({
id: 'local-csv',
name: 'Local CSV',
timeframes: ['1m', '5m', '15m', '1h', '4h', '1D'],
async fetchBars({ symbol, timeframe, limit }: BarRequest): Promise<Bar[]> {
const path = './data/' + symbol + '-' + timeframe + '.csv'
const raw = await readFile(path, 'utf8')
// time,open,high,low,close,volume
const bars = raw
.trim()
.split('\n')
.slice(1)
.map((line): Bar => {
const [time, open, high, low, close, volume] = line.split(',')
return {
time: Math.floor(new Date(time).getTime() / 1000),
open: Number(open),
high: Number(high),
low: Number(low),
close: Number(close),
volume: Number(volume),
}
})
.filter((bar) => Number.isFinite(bar.close))
return bars.slice(-limit)
},
})
const algobeam = new AlgoBeamTS(CsvProvider, 'BTCUSDT', '1h', 720)
const bars = await algobeam.load()
console.log('replayed', bars.length, 'bars from disk')Write it once in AlgoBeamScript
AlgoBeamScript is a small declarative language for series maths: typed inputs, a ta namespace of technical functions, and one obvious way to draw. It has no threads, no file system and no network — which is exactly why the same file is safe in a browser tab and on a production box.
- Series-aware arithmetic — you never loop over bars by hand
- Around 180 unit-tested functions in the ta namespace
- Compiles to readable JavaScript you can commit to your repo
//@version=2
indicator("Momentum Ribbon", overlay = true, precision = 2)
// ---- inputs --------------------------------------------------------
len = input.int(14, "RSI length", minval = 2, maxval = 200)
fast = input.int(21, "Fast EMA", minval = 2)
slow = input.int(55, "Slow EMA", minval = 5)
span = input.int(8, "Weighted window", minval = 2, maxval = 100)
level = input.float(55.0, "Bull threshold", step = 0.5)
src = input.source(close, "Source")
// ---- series --------------------------------------------------------
series r = ta.rsi(src, len)
series emaF = ta.ema(src, fast)
series emaS = ta.ema(src, slow)
series slope = (emaF - emaF[3]) / 3
// A linearly weighted average written out longhand. The accumulator is
// declared with = before the loop and updated with := inside it; it is a
// plain local rather than a var, so it starts again from zero every bar.
weighted = 0.0
for i = 0 to span - 1
weighted := weighted + (span - i) * src[i]
series pull = weighted / (span * (span + 1) / 2)
bull = r > level and emaF > emaS and slope > 0
bear = r < 100 - level and emaF < emaS and slope < 0
// ---- output --------------------------------------------------------
plot(emaF, title = "Fast", width = 2,
color = bull ? color.bull : bear ? color.bear : color.fg_subtle)
plot(emaS, title = "Slow", width = 1, color = color.fg_subtle)
plot(pull, title = "Weighted", width = 1, color = color.accent)
fill(emaF, emaS, color = color.fade(bull ? color.bull : color.bear, 88))
plot(r, title = "RSI", pane = "lower", color = color.accent, width = 2)
hline(level, "Bull", pane = "lower", style = line.dashed)
hline(100 - level, "Bear", pane = "lower", style = line.dashed)
alert.when(ta.crossover(r, level), "Ribbon flipped bullish on {symbol} at {close}")Backtest it in the same runtime
Feed the runtime history and it replays your strategy bar by bar with commission, slippage and a fill model you choose. The ledger comes out of the same evaluator that draws the chart, so there is no research-to-production drift to explain away.
- Commission, slippage and next-open or same-close fills
- Per-trade ledger you can export to CSV or JSON
- The runtime routes no orders
import { AlgoBeamTS, Provider, readScript } from 'algobeam-ts'
const algobeam = new AlgoBeamTS(Provider.Binance, 'ETHUSDT', '4h', 5_000)
const source = await readScript('./strategies/ribbon-breakout.algo')
const report = await algobeam.backtest(source, {
capital: 25_000,
positionSize: { type: 'percent', value: 15 },
commissionBps: 5,
slippageBps: 2,
fillModel: 'next-open',
from: '2024-01-01',
to: '2026-08-27',
})
const { stats, trades } = report
console.table({
netProfitPct: stats.netProfitPct.toFixed(2),
maxDrawdownPct: stats.maxDrawdownPct.toFixed(2),
winRatePct: stats.winRatePct.toFixed(1),
profitFactor: stats.profitFactor.toFixed(2),
sharpe: stats.sharpe.toFixed(2),
trades: trades.length,
})
// These numbers assume the fill model above and do not account for
// funding, borrow cost or partial fills in thin books.
await report.writeCsv('./out/ribbon-breakout-equity.csv')Stream it live
Warm the buffer from history, then push live bars into the same series objects. Indicators update incrementally instead of recomputing the window, and unclosed bars arrive flagged as provisional so nothing downstream acts on a candle that is still moving.
- Incremental updates — constant work per bar, not per window
- Provisional values are marked, never silently treated as final
- Reconnect with backoff and jitter ships in the box
import { AlgoBeamTS, Provider, indicators, type Bar } from 'algobeam-ts'
const algobeam = new AlgoBeamTS(Provider.Binance, 'SOLUSDT', '1m', 500)
await algobeam.load() // warm the buffer so indicators are not cold-started
const rsi = algobeam.series(indicators.rsi({ length: 14 }))
const stream = algobeam.stream({
transport: 'websocket',
reconnect: { retries: 8, backoffMs: 750, jitter: true },
})
stream.on('open', () => console.log('[algobeam] socket open'))
stream.on('bar', (bar: Bar, meta: { closed: boolean }) => {
const value = rsi.push(bar, { provisional: !meta.closed })
if (!meta.closed) return
const stamp = new Date(bar.time * 1000).toISOString()
console.log(`${bar.symbol} ${stamp} close=${bar.close} rsi=${value.toFixed(1)}`)
if (value > 70) console.warn(`[algobeam] ${bar.symbol} stretched at ${value.toFixed(1)}`)
})
stream.on('error', (error: unknown) => console.error('[algobeam] stream error', error))
stream.on('close', ({ code }: { code: number }) => console.warn('[algobeam] closed', code))
await stream.connect()
process.on('SIGINT', () => void stream.disconnect())Ship it behind your own API
Compile once at boot, cache by symbol and timeframe, and hand plots and alerts to whatever sits downstream — your dashboard, your order router, your desk chat. It is an ordinary Node process, so you deploy it the way you deploy everything else.
- Compile at boot, evaluate per request, cache what repeats
- Plots and alerts serialise to plain JSON — no proprietary envelope
- No account, no API key, no outbound call to us
import express from 'express'
import { AlgoBeamTS, Provider, compile, readScript } from 'algobeam-ts'
const app = express()
const script = compile(await readScript('./scripts/momentum-ribbon.algo'))
const cache = new Map<string, { at: number; body: unknown }>()
const TTL_MS = 15_000
app.get('/api/indicator/:symbol', async (req, res) => {
const symbol = req.params.symbol.toUpperCase()
const timeframe = typeof req.query.tf === 'string' ? req.query.tf : '1h'
const key = symbol + ':' + timeframe
const hit = cache.get(key)
if (hit && performance.now() - hit.at < TTL_MS) {
res.setHeader('x-algobeam-cache', 'hit')
return res.json(hit.body)
}
try {
const algobeam = new AlgoBeamTS(Provider.Binance, symbol, timeframe, 300)
const result = await algobeam.run(script)
const body = {
symbol,
timeframe,
bars: result.bars.length,
plots: result.plots,
alerts: result.alerts,
}
cache.set(key, { at: performance.now(), body })
res.json(body)
} catch (error: unknown) {
res.status(502).json({ error: 'provider unavailable', detail: String(error) })
}
})
app.listen(8787, () => console.log('algobeam indicator service listening on :8787'))Boring guarantees, on purpose
Open source is only useful if it keeps behaving after you have built on top of it. These are the promises we hold the runtime to, release after release.
MIT licensed
The compiler, the runtime and the CLI are all MIT. Fork it, vendor it, or ship it inside a commercial product without starting a licence conversation with us.
Zero lock-in
Nothing calls home. No API key, no telemetry, no AlgoBeam account is required to compile a script or evaluate it against your own data.
Byte-identical results
The platform chart and your runtime share one evaluator, so the value we plot is the value your server prints — to the last decimal, on the same bar.
Tree-shakeable
Every indicator is its own ES module export. A bundle that only reaches for RSI and EMA ships about 9 kB gzipped instead of the whole standard library.
Types from your script
Build emits a .d.ts beside the JavaScript, so your inputs, plots and alert payloads are typed at the call site rather than described in a comment.
Works offline
Point the runtime at a folder of CSV exports and the whole toolchain — compile, replay, backtest, report — runs with the network unplugged.
Three very different desks, one file format
The runtime was extracted from the platform because our own engineers wanted it. These are the people who have found it useful since.
Indie developers
One package, one laptop
You already have an idea and an editor. Install the package, point it at an exchange feed or a CSV export, and you have a research loop that costs nothing to run and nothing to keep.
Quant teams
One artefact across the wall
Analysts prototype on the platform; engineers run the identical file in the production stack. What crosses the boundary is source code with a test suite, not a screenshot and a spreadsheet.
Platform builders
Embed it in your product
Drop the runtime into your own charting surface or broker app. MIT means you can distribute it to your customers, and nothing phones home from their machines.
The roadmap is a public issue tracker
Every indicator, provider and compiler pass lives in the open. Bug reports get a reproduction request, not a form letter, and a merged provider ships in the next minor release.
- 0
- Stars
- 0
- Forks
- 0
- Open issues
- 0
- Contributors
Add a Kraken spot provider to the in-tree set
good first issue
ta.vwap should reset on the session boundary
bug
Document the provisional-bar flag in the streaming guide
docs
Ready when you are
Install it, read the source, break it.
AlgoBeamTS takes one command and no account. Compile a script, replay a decade of bars against your own data, and open an issue the moment something behaves oddly.
- No card required
- Cancel anytime
- 14-day Ultimate trial