A TypeScript framework for backtesting and live trading strategies on multi-asset, crypto, forex or DEX (peer-to-peer marketplace) with crash-safe persistence, signal validation, and AI optimization.
Build reliable trading systems: backtest on historical data, deploy live bots with recovery, and optimize strategies using LLMs like Ollama.
π API Reference | π Quick Start | π° Article
- π Production-Ready: Seamless switch between backtest/live modes; identical code across environments.
- πΎ Crash-Safe: Atomic persistence recovers states after crashes, preventing duplicates or losses.
- β Validation: Checks signals for TP/SL logic, risk/reward ratios, and portfolio limits.
- π Efficient Execution: Streaming architecture for large datasets; VWAP pricing for realism.
- π€ AI Integration: LLM-powered strategy generation (Optimizer) with multi-timeframe analysis.
- π Reports & Metrics: Auto Markdown reports with PNL, Sharpe Ratio, win rate, and more.
- π‘οΈ Risk Management: Custom rules for position limits, time windows, and multi-strategy coordination.
- π Pluggable: Custom data sources (CCXT), persistence (file/Redis), and sizing calculators.
- π§ͺ Tested: 300+ unit/integration tests for validation, recovery, and events.
- π Self hosted: Zero dependency on third-party node_modules or platforms; run entirely in your own environment.
- Market/Limit entries
- TP/SL/OCO exits
- Grid with auto-cancel on unmet conditions
Talk is cheap. Let me show you the code
Link to π the demo app π
npm install backtest-kit ccxt ollama uuidimport { setLogger, setConfig } from 'backtest-kit';
// Enable logging
setLogger({
log: console.log,
debug: console.debug,
info: console.info,
warn: console.warn,
});
// Global config (optional)
setConfig({
CC_PERCENT_SLIPPAGE: 0.1, // % slippage
CC_PERCENT_FEE: 0.1, // % fee
CC_SCHEDULE_AWAIT_MINUTES: 120, // Pending signal timeout
});import ccxt from 'ccxt';
import { addExchange, addStrategy, addFrame, addRisk } from 'backtest-kit';
// Exchange (data source)
addExchange({
exchangeName: 'binance',
getCandles: async (symbol, interval, since, limit) => {
const exchange = new ccxt.binance();
const ohlcv = await exchange.fetchOHLCV(symbol, interval, since.getTime(), limit);
return ohlcv.map(([timestamp, open, high, low, close, volume]) => ({ timestamp, open, high, low, close, volume }));
},
formatPrice: (symbol, price) => price.toFixed(2),
formatQuantity: (symbol, quantity) => quantity.toFixed(8),
});
// Risk profile
addRisk({
riskName: 'demo',
validations: [
// TP at least 1%
({ pendingSignal, currentPrice }) => {
const { priceOpen = currentPrice, priceTakeProfit, position } = pendingSignal;
const tpDistance = position === 'long' ? ((priceTakeProfit - priceOpen) / priceOpen) * 100 : ((priceOpen - priceTakeProfit) / priceOpen) * 100;
if (tpDistance < 1) throw new Error(`TP too close: ${tpDistance.toFixed(2)}%`);
},
// R/R at least 2:1
({ pendingSignal, currentPrice }) => {
const { priceOpen = currentPrice, priceTakeProfit, priceStopLoss, position } = pendingSignal;
const reward = position === 'long' ? priceTakeProfit - priceOpen : priceOpen - priceTakeProfit;
const risk = position === 'long' ? priceOpen - priceStopLoss : priceStopLoss - priceOpen;
if (reward / risk < 2) throw new Error('Poor R/R ratio');
},
],
});
// Time frame
addFrame({
frameName: '1d-test',
interval: '1m',
startDate: new Date('2025-12-01'),
endDate: new Date('2025-12-02'),
});import { v4 as uuid } from 'uuid';
import { addStrategy, dumpSignal, getCandles } from 'backtest-kit';
import { json } from './utils/json.mjs'; // LLM wrapper
import { getMessages } from './utils/messages.mjs'; // Market data prep
addStrategy({
strategyName: 'llm-strategy',
interval: '5m',
riskName: 'demo',
getSignal: async (symbol) => {
const candles1h = await getCandles(symbol, "1h", 24);
const candles15m = await getCandles(symbol, "15m", 48);
const candles5m = await getCandles(symbol, "5m", 60);
const candles1m = await getCandles(symbol, "1m", 60);
const messages = await getMessages(symbol, {
candles1h,
candles15m,
candles5m,
candles1m,
}); // Calculate indicators / Fetch news
const resultId = uuid();
const signal = await json(messages); // LLM generates signal
await dumpSignal(resultId, messages, signal); // Log
return { ...signal, id: resultId };
},
});import { Backtest, listenSignalBacktest, listenDoneBacktest } from 'backtest-kit';
Backtest.background('BTCUSDT', {
strategyName: 'llm-strategy',
exchangeName: 'binance',
frameName: '1d-test',
});
listenSignalBacktest((event) => console.log(event));
listenDoneBacktest(async (event) => {
await Backtest.dump(event.symbol, event.strategyName); // Generate report
});import { Live, listenSignalLive } from 'backtest-kit';
Live.background('BTCUSDT', {
strategyName: 'llm-strategy',
exchangeName: 'binance', // Use API keys in .env
});
listenSignalLive((event) => console.log(event));- Use
listenRisk,listenError,listenPartialProfit/Lossfor alerts. - Dump reports:
Backtest.dump(),Live.dump().
Customize via setConfig():
CC_SCHEDULE_AWAIT_MINUTES: Pending timeout (default: 120).CC_AVG_PRICE_CANDLES_COUNT: VWAP candles (default: 5).
Backtest Kit is not a data-processing library - it is a time execution engine. Think of the engine as an async stream of time, where your strategy is evaluated step by step.
backtest-kit uses Node.js AsyncLocalStorage to automatically provide
temporal time context to your strategies.
getCandles()always returns data UP TO the current backtest timestamp usingasync_hooks- Multi-timeframe data is automatically synchronized
- Impossible to introduce look-ahead bias
- Same code works in both backtest and live modes
Backtest Kit exposes the same runtime in two equivalent forms. Both approaches use the same engine and guarantees - only the consumption model differs.
Suitable for production bots, monitoring, and long-running processes.
Backtest.background('BTCUSDT', config);
listenSignalBacktest(event => { /* handle signals */ });
listenDoneBacktest(event => { /* finalize / dump report */ });Suitable for research, scripting, testing, and LLM agents.
for await (const event of Backtest.run('BTCUSDT', config)) {
// signal | trade | progress | done
}Open-source QuantConnect without the vendor lock-in
Unlike cloud-based platforms, backtest-kit runs entirely in your environment. You own the entire stack from data ingestion to live execution. In addition to Ollama, you can use neural-trader in getSignal function or any other third party library
- No C# required - pure TypeScript/JavaScript
- Self-hosted - your code, your data, your infrastructure
- No platform fees or hidden costs
- Full control over execution and data sources
- GUI for visualization and monitoring
For language models: Read extended description in ./LLMs.md
300+ tests cover validation, recovery, reports, and events.
Fork/PR on GitHub.
MIT Β© tripolskypetr
