Providers
Built-in data adapters, the provider interface, and writing your own from a CSV or a database.
A provider is the runtime abstraction over market data. Built-in adapters cover the major crypto venues, a delayed equities and ETF feed, and a synthetic provider that generates deterministic bars from a seed — the last of which is invaluable in tests, because a test that depends on live prices is a test that fails on a quiet Sunday.
The interface is small on purpose. A provider declares an id, a display name, the timeframes it supports, and implements fetchBars, which receives a symbol, timeframe, limit and optional from and to, and returns bars oldest first. Optionally it can implement subscribe for live updates and resolveSymbol for search. Nothing else is required.
Because the interface is that small, a provider over your own storage is a short function. A CSV-backed provider reads a file and maps rows to bars. A database-backed one runs a query. An in-house tick store aggregates on the way out. The runtime does not care where bars come from, only that they are ordered, non-overlapping and complete.
Two rules matter more than the rest. Return bars in ascending time order with no duplicates, because indicators assume it and will silently produce nonsense otherwise. And return exactly the requested timeframe rather than something close to it — resampling belongs inside the provider, where you know how the venue defines a session boundary, not in the caller.
Providers can be composed. registerProvider adds one to the global registry so the CLI can address it by id; withCache wraps any provider in a time-to-live cache; and withFallback chains two so a primary outage rolls over to a secondary without the script noticing.
Example
A custom provider backed by CSV files
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')Copy it, change one input, and run it again — the numbers are deterministic, so a difference in the output is always a difference you made.