Backtest Kit is a production-ready TypeScript framework for algorithmic trading that solves the fundamental challenges of building reliable trading systems. Unlike research-focused libraries, it provides battle-tested solutions for the real-world problems that emerge when moving from backtesting to live trading.
This documentation explains the core concepts, design decisions, and problem-solving approaches that make Backtest Kit suitable for production trading systems.
Every trading position follows a strict lifecycle: idle → opened → active → closed. This state machine is enforced through TypeScript discriminated unions, making invalid states unrepresentable at compile time.
Problem Solved: Traditional trading systems often suffer from state corruption where positions exist in ambiguous states (partially opened, unclear if closed, etc.). Backtest Kit eliminates this class of bugs entirely.
Key Benefits:
- Impossible to access closed position data
- Impossible to modify opened position as if it were active
- Type-safe access to position properties based on current state
- Automatic validation of state transitions
The framework uses Node.js AsyncLocalStorage to maintain temporal context throughout the entire execution stack. Every function call automatically knows:
- Current simulation timestamp (backtest) or real time (live)
- Trading symbol being processed
- Strategy and exchange configuration
- Whether running in backtest or live mode
Problem Solved: Manual timestamp passing creates error-prone code where developers accidentally use wrong time references, leading to look-ahead bias or incorrect calculations.
Key Benefits:
- Zero boilerplate for time context
- Impossible to accidentally use future data
- Same code works identically in backtest and live modes
- Automatic synchronization across multiple timeframes
All state mutations are written atomically to disk before being considered complete. If the process crashes mid-operation, the system recovers to the last consistent state on restart.
Problem Solved: Trading systems that crash during position updates often leave corrupted state, causing duplicate trades, missed exits, or phantom positions.
Key Solved Scenarios:
- Process killed during order placement → position state unchanged
- Network failure during exchange API call → automatic retry on next tick
- Power outage during state save → recovery from last atomic write
- Out of memory error → graceful shutdown with state preserved
Signals are created through the getSignal function and undergo comprehensive validation before being accepted:
Validation Checks:
- Take-profit and stop-loss prices are positive
- TP/SL relationship is correct (TP > entry for long, TP < entry for short)
- Risk/reward ratio meets minimum thresholds
- Timestamps are valid and not in the future
- Signal doesn't violate interval throttling rules
Problem Solved: Invalid signals cause undefined behavior, incorrect PnL calculations, and potential financial losses. Validation catches these issues before they reach the execution layer.
Signals progress through well-defined states:
- Idle: No active signal, strategy is monitoring
- Scheduled: Signal created but waiting for entry price
- Pending: Entry conditions met, waiting for execution
- Opened: Position established, monitoring for exit
- Active: Position in profit/loss, trailing stops active
- Closed: Position exited (take-profit, stop-loss, manual, or timeout)
Each state has specific allowed operations and accessible data. Attempting invalid operations (like modifying a closed position) results in compile-time errors.
Signals can be cancelled before execution due to:
- User intervention
- Risk management rejection
- Timeout (configurable wait period)
- Conflicting signal generation
Cancelled signals are tracked separately from closed positions, allowing analysis of signal quality and rejection patterns.
The framework supports sophisticated DCA strategies with automatic entry tracking:
Features:
- Multiple entry levels with individual cost basis
- Harmonic mean calculation for effective entry price
- Automatic rejection of averaging into winning positions (configurable)
- Partial close history affects future DCA calculations
Problem Solved: Manual DCA tracking requires complex bookkeeping. The framework automatically maintains entry history, calculates blended cost basis, and prevents common mistakes like averaging up.
Key Mechanics:
- Each DCA entry records price, cost, and timestamp
- Effective price recalculates after each entry
- Partial closes adjust cost basis proportionally
- Entry overlap detection prevents duplicate entries at similar prices
Positions can be partially closed at profit or loss milestones:
Capabilities:
- Close percentage of position at specific profit levels (10%, 20%, 30%, etc.)
- Close percentage at loss levels to limit exposure
- Track remaining cost basis after partial closes
- Calculate PnL for each partial independently
Problem Solved: All-or-nothing exits miss opportunities to lock in gains or limit losses. Partial closes allow fine-grained position management while maintaining exposure.
Advanced Features:
- Partial close history preserved for analysis
- Remaining position continues with adjusted cost basis
- PnL calculations account for all partials and remaining position
- Overlap detection prevents duplicate partials at similar prices
Dynamic adjustment of exit levels based on price movement:
Trailing Stop:
- Stop-loss moves in favorable direction only
- Configurable distance from current price
- Automatic adjustment on each tick
- Prevents stop from moving backward
Trailing Take:
- Take-profit adjusts as price moves favorably
- Locks in gains while allowing upside
- Configurable trailing percentage
- Independent from initial take-profit
Problem Solved: Static exit levels either exit too early (missing trends) or too late (giving back gains). Trailing exits adapt to market conditions automatically.
Automatic adjustment of stop-loss to entry price once position reaches profitability threshold:
Mechanism:
- Calculates threshold to cover fees and slippage
- Moves stop-loss to entry price when threshold reached
- Prevents winning trade from becoming loser
- One-time adjustment per signal
Problem Solved: Traders often move stop-loss to breakeven manually, missing the optimal moment or forgetting entirely. Automation ensures protection is applied consistently.
Risk validation operates across all strategies and symbols simultaneously:
Capabilities:
- Track total number of open positions across portfolio
- Enforce maximum concurrent position limits
- Custom validation functions for complex rules
- Rejection tracking with detailed reasons
Problem Solved: Individual strategy risk checks miss portfolio-level exposure. A trader might have 10 strategies each risking 10%, creating 100% total exposure unintentionally.
Key Features:
- Atomic check-and-reserve prevents race conditions
- Multiple strategies can share same risk profile
- Rejection events logged for analysis
- Configurable callbacks for rejection handling
Developers define validation functions that receive:
- Pending signal details (entry, TP, SL, direction)
- Current market price
- Active position count
- Portfolio state
Common Validations:
- Minimum risk/reward ratio
- Maximum position size
- Time-of-day restrictions
- Correlation checks between symbols
- Volatility filters
Problem Solved: Hard-coded risk rules don't adapt to strategy-specific requirements. Custom validators allow arbitrary logic while maintaining framework guarantees.
When a signal violates risk rules:
- Signal is rejected before execution
- Rejection reason logged with full context
- Strategy continues operating normally
- Rejection statistics tracked for analysis
Problem Solved: Silent risk violations lead to unexpected exposure. Explicit rejection with logging provides visibility into risk management effectiveness.
The framework enforces strict temporal boundaries to prevent accidental use of future data:
Mechanisms:
- All candle requests aligned to interval boundaries
- Pending (incomplete) candles excluded from results
- Timestamp validation on every data access
- AsyncLocalStorage provides automatic context
Problem Solved: Look-ahead bias is the most common cause of unrealistic backtest results. Manual timestamp management inevitably leads to errors.
Guarantees:
- Impossible to request data beyond current simulation time
- Pending candles never included in calculations
- Multi-timeframe data automatically synchronized
- Same code produces identical results in backtest and live
All timestamps aligned down to interval boundaries:
Example:
- 15-minute interval
- Request at 00:17 → aligned to 00:15
- Request at 00:44 → aligned to 00:30
Problem Solved: Misaligned timestamps cause off-by-one errors, duplicate data, or missing candles. Automatic alignment ensures consistency.
Key Rules:
- First candle timestamp equals aligned start time
- Exactly requested number of candles returned
- Sequential timestamps with fixed interval
- Last candle timestamp calculated deterministically
Similar temporal protection for order book and aggregated trades:
Order Book:
- Configurable time offset window
- Aligned to offset boundaries
- Adapter receives time range for historical queries
Aggregated Trades:
- Always aligned to 1-minute boundaries
- Pagination for large requests
- Automatic slicing to requested limit
Problem Solved: Order book data without temporal context leads to using future snapshots. Time windows ensure only historical data accessible.
Default persistence uses atomic file operations:
Mechanism:
- Write to temporary file
- Rename to final location (atomic on most filesystems)
- Verify write completion
- Recover from incomplete writes on restart
Problem Solved: Partial writes during crashes corrupt state files, making recovery impossible. Atomic writes ensure all-or-nothing semantics.
Framework supports multiple storage backends through adapter pattern:
Built-in Adapters:
- File-based (default, zero configuration)
- In-memory (testing, fast iteration)
- MongoDB (production, scalable)
- Redis (caching, O(1) lookups)
Problem Solved: Different deployment scenarios require different storage strategies. Hard-coded storage limits flexibility and scalability.
Adapter Capabilities:
- 15 separate persistence contracts (signals, candles, logs, etc.)
- Each adapter implements all contracts
- Zero strategy code changes when switching backends
- Automatic adapter selection based on mode
Production-grade storage with advanced features:
MongoDB Features:
- Unique compound indexes prevent duplicates
- Atomic upserts with read-after-write consistency
- Queryable signal history
- Survives process restarts
Redis Features:
- O(1) lookups via context-key to ID mapping
- Cache miss fallback to MongoDB
- Automatic cache invalidation on writes
- Eliminates B-tree scans for repeated reads
Problem Solved: File-based storage becomes bottleneck at scale. Database backends provide durability, queryability, and performance for production deployments.
Certain records use soft delete instead of physical deletion:
Applies To:
- Measure data (LLM responses, API results)
- Interval markers (processed time periods)
- Memory entries (agent context)
Mechanism:
- Set
removed: trueflag - Listing operations filter on
removed: false - Physical file preserved for audit trail
Problem Solved: Physical deletion loses audit trail and can cause race conditions. Soft delete preserves history while hiding from active use.
Every broker operation follows transactional pattern:
Sequence:
- Framework prepares commit payload
- Broker adapter executes exchange operation
- If success → internal state updated
- If failure → state unchanged, retry on next tick
Problem Solved: Exchange failures during position updates cause state desynchronization. Transactional commits ensure framework state matches exchange state.
When broker adapter throws exception:
Behavior:
- Internal state mutation skipped
- Position remains in previous state
- Error logged with full context
- Automatic retry on next tick
Problem Solved: Partial state updates leave system in inconsistent state. Rollback ensures atomicity of position changes.
Example Scenarios:
- Exchange API timeout → retry automatically
- Insufficient margin → reject and log
- Network failure → recover when connection restored
- Exchange rejection → state unchanged, strategy continues
Failed commits automatically retried:
Mechanism:
- Exception caught by framework
- State rollback completed
- Same commit attempted on next tick
- Continues until success or strategy cancelled
Problem Solved: Transient exchange failures shouldn't require manual intervention. Automatic retry handles temporary issues transparently.
Guarantees:
- No duplicate commits (state unchanged until success)
- No lost commits (retry continues indefinitely)
- Strategy can cancel pending commit
- Full audit trail of attempts
Broker adapters can reject operations:
Rejection Scenarios:
- Insufficient balance
- Invalid order parameters
- Exchange-specific restrictions
- Rate limiting
Handling:
- Adapter throws exception with reason
- Framework rolls back state
- Error surfaced to user interface
- Strategy decides whether to retry or cancel
Problem Solved: Silent exchange rejections cause confusion about position state. Explicit rejection with error messages provides clarity.
Automatic generation of human-readable reports:
Report Types:
- Backtest summary (total PnL, win rate, Sharpe ratio)
- Signal lifecycle (all state transitions)
- Risk rejections (all blocked signals)
- Partial closes (all profit/loss milestones)
- Maximum drawdown (all peak-to-trough events)
Problem Solved: Manual report generation is time-consuming and error-prone. Automatic reports provide instant visibility into strategy performance.
Features:
- Customizable columns
- Configurable output format (file or JSONL)
- Organized by symbol, strategy, exchange
- Searchable and filterable
Machine-readable logs for programmatic analysis:
Capabilities:
- Incremental writes (one event per line)
- Metadata included (symbol, strategy, timestamp)
- Searchable by multiple criteria
- Append-only (no overwrites)
Problem Solved: Binary log formats require custom parsers. JSONL provides human-readable, tool-friendly format for analysis.
Use Cases:
- Post-trade analysis
- Strategy optimization
- Anomaly detection
- Compliance auditing
Comprehensive calculation of trading metrics:
Metrics Calculated:
- Total PnL (absolute and percentage)
- Win rate and loss rate
- Average win and loss size
- Sharpe ratio (risk-adjusted returns)
- Sortino ratio (downside risk-adjusted)
- Maximum drawdown (peak-to-trough decline)
- Recovery factor (profit / max drawdown)
- Expectancy (average profit per trade)
Problem Solved: Manual metric calculation is error-prone and incomplete. Built-in calculations ensure accuracy and consistency.
Advanced Features:
- Pooled metrics across multiple symbols
- Time-weighted returns
- Consecutive win/loss streaks
- Buyer/seller pressure analysis
- Trend classification with confidence
Portfolio-wide performance visualization:
Features:
- Performance by symbol
- Cross-strategy comparison
- Risk-adjusted metrics
- Time-period analysis
Problem Solved: Tabular reports don't reveal patterns across symbols. Heatmaps provide instant visual identification of strengths and weaknesses.
Persistent storage for LLM context and reasoning:
Capabilities:
- Store conversation history per signal
- Search memory using BM25 algorithm
- List all memories up to timestamp
- Soft delete for audit trail
Problem Solved: LLM context lost between sessions prevents learning and continuity. Memory adapters preserve reasoning across restarts.
Use Cases:
- Store LLM reasoning for each trade
- Retrieve relevant past decisions
- Build knowledge base over time
- Debug LLM decision-making
Temporary storage for calculations and intermediate results:
Features:
- Scoped to symbol, strategy, exchange, timeframe
- Automatic cleanup on signal close
- Persistent across process restarts
- Prevents look-ahead bias
Problem Solved: Complex calculations repeated on every tick waste resources. Session storage caches results while maintaining temporal correctness.
Example Scenarios:
- Cache indicator calculations
- Store LLM inference results
- Maintain running statistics
- Track signal-specific metrics
Complete preservation of LLM interactions:
Capabilities:
- Store full conversation history
- Include system prompts and tool calls
- Attach to specific trading signals
- Searchable and exportable
Problem Solved: LLM decisions are opaque without full context. Complete conversation dumps enable debugging and improvement.
Features:
- Messages with roles (system, user, assistant, tool)
- Reasoning content preserved
- Tool call details included
- Images and attachments supported
Unified interface for multiple LLM providers:
Supported Providers:
- OpenAI, Claude, DeepSeek, Grok
- Mistral, Perplexity, Cohere, Alibaba
- Hugging Face, Ollama (local)
Features:
- Automatic API key rotation
- Structured output enforcement
- Trading-specific prompts
- Fallback chains
Problem Solved: Different LLM providers have incompatible APIs. Unified interface allows easy switching and comparison.
Zero-boilerplate execution of strategies:
Modes:
--backtest: Historical simulation--paper: Live prices, no real orders--live: Real trading with exchange--walker: A/B strategy comparison
Features:
- Automatic candle caching
- Web dashboard integration
- Telegram notifications
- Graceful shutdown on SIGINT
Problem Solved: Manual setup for each mode requires significant boilerplate. CLI provides one-command execution with all infrastructure handled.
Real-time monitoring and manual control:
Pages:
- Main overview (all strategies and symbols)
- Signal status (pending, active, closed)
- Manual control (open, close, average, breakeven)
- Pine Script editor (interactive development)
Problem Solved: Console-only monitoring lacks visibility. Web dashboard provides real-time insights and manual intervention capabilities.
Features:
- Live signal updates
- Multi-timeframe charts
- Risk rejection tracking
- Position management buttons
Formatted alerts for trading events:
Notifications:
- Signal opened/closed
- Partial profit/loss
- Risk rejections
- Breakeven reached
- Trailing adjustments
Problem Solved: Missing trading events due to lack of monitoring. Telegram alerts ensure immediate awareness of important events.
Features:
- Price charts included
- Customizable templates
- Filter by event type
- HTML formatting
Containerized deployment with automatic restarts:
Capabilities:
- Pre-configured docker-compose
- Environment variable configuration
- Automatic container restart
- Zero-downtime updates
Problem Solved: Manual deployment and process management is error-prone. Docker provides consistent, reliable execution environment.
Features:
- Separate containers for app, MongoDB, Redis
- Configurable via environment variables
- Health checks and restart policies
- Log aggregation
Systematic comparison of multiple strategies:
Workflow:
- Define multiple strategy variants
- Run all on same historical data
- Calculate metrics for each
- Rank by optimization criterion
- Generate comparison report
Problem Solved: Manual strategy comparison is time-consuming and inconsistent. Walker automates the entire process with statistical rigor.
Features:
- Configurable optimization metric (Sharpe, PnL, etc.)
- Progress tracking during execution
- Markdown comparison reports
- JSON export for further analysis
Periodic and fire-once job execution:
Capabilities:
- Periodic jobs (every hour, daily, etc.)
- Fire-once jobs (single execution)
- Symbol-specific or global scope
- Coordination across parallel backtests
Problem Solved: Manual scheduling leads to missed executions or duplicate runs. Cron ensures reliable, coordinated job execution.
Features:
- Virtual time alignment (backtest mode)
- Automatic retry on failure
- Generation tracking for re-registrations
- Mutex semantics for parallel execution
Example Use Cases:
- Fetch funding rates hourly
- Parse Telegram signals every 15 minutes
- Warm cache on startup
- Daily portfolio rebalancing
Run TradingView indicators directly in Node.js:
Features:
- Pine Script v5/v6 compatibility
- 60+ built-in indicators
- File or code string input
- Plot extraction to signals
Problem Solved: Rewriting Pine Script indicators in JavaScript is time-consuming. Direct execution preserves existing work.
Capabilities:
- Native TradingView syntax
- Automatic indicator calculation
- Flexible signal mapping
- Cached execution for performance
Synchronized data across multiple intervals:
Mechanism:
- Automatic alignment of all timeframes
- Consistent temporal boundaries
- No look-ahead bias possible
- Same code for all timeframes
Problem Solved: Manual multi-timeframe synchronization is error-prone. Automatic alignment ensures consistency.
Supported Intervals:
- 1m, 3m, 5m, 15m, 30m
- 1h, 2h, 4h, 6h, 8h
- 12h, 1d, 3d, 1w, 1M
Memory-efficient streaming for large datasets:
Benefits:
- No array accumulation
- Early termination support
- Constant memory usage
- Backpressure handling
Problem Solved: Loading entire history into memory causes out-of-memory errors. Streaming allows processing unlimited data.
Use Cases:
- Year-long backtests on 1-minute data
- Multi-symbol parallel execution
- Real-time signal processing
- Large portfolio analysis
Cache expensive calculations automatically:
Capabilities:
- Client instances cached by schema name
- Validation results memoized
- Connection reuse
- Time-based cache invalidation
Problem Solved: Repeated calculations waste CPU cycles. Memoization eliminates redundant work.
Features:
- Automatic cache key generation
- Configurable TTL
- Manual cache clearing
- Memory-efficient storage
Memory-efficient method definitions:
Mechanism:
- Methods defined on prototype, not instances
- Shared across all instances of class
- Reduced memory footprint
- Faster instantiation
Problem Solved: Arrow functions in constructors create new function per instance. Prototype methods share single function across all instances.
Impact:
- 50-70% memory reduction for large object collections
- Faster garbage collection
- Better CPU cache utilization
Prevent memory leaks from unbounded event accumulation:
Mechanism:
- Maximum queue size enforced
- Oldest events dropped when full
- Backpressure signals to producers
- Graceful degradation under load
Problem Solved: Unbounded queues grow indefinitely during high event rates, causing out-of-memory crashes. Bounded queues maintain stability.
Configuration:
- Configurable maximum size
- Drop policy (oldest, newest, etc.)
- Monitoring of queue depth
- Alerts on threshold breach
Strict separation of concerns:
Client Layer:
- Pure business logic
- No dependency injection
- Prototype methods for efficiency
- Direct data manipulation
Service Layer:
- Dependency injection container
- Organized by responsibility
- Schema, validation, connection services
- Global wrappers for public API
Persistence Layer:
- Atomic file operations
- Pluggable adapters
- Crash-safe writes
- Recovery mechanisms
Event Layer:
- Subject-based emitters
- Queued async processing
- Filter predicates
- Once listeners
Problem Solved: Monolithic architecture makes testing and modification difficult. Layered architecture enables independent development and testing.
Custom DI container with Symbol-based tokens:
Features:
- Type-safe injection
- Lazy instantiation
- Scope management
- Test-friendly mocking
Problem Solved: Hard-coded dependencies prevent testing and flexibility. DI enables easy substitution and testing.
Benefits:
- Easy mocking for tests
- Flexible configuration
- Clear dependency graph
- Reduced coupling
Nested contexts using scoped services:
Contexts:
- ExecutionContext (symbol, timestamp, mode)
- MethodContext (strategy, exchange, frame)
- Automatic propagation through async calls
Problem Solved: Manual context passing creates verbose, error-prone code. Automatic propagation ensures correct context everywhere.
Mechanism:
- AsyncLocalStorage for execution context
- DI scopes for method context
- Automatic inheritance
- Isolation between parallel executions
Centralized configuration management:
Registries:
- Strategy schemas
- Exchange schemas
- Frame schemas
- Risk schemas
- Sizing schemas
Problem Solved: Scattered configuration makes validation and discovery difficult. Centralized registries provide single source of truth.
Features:
- Type-safe registration
- Shallow validation on add
- Runtime existence checks
- Memoized validation results
775+ unit and integration tests:
Test Categories:
- Exchange helper functions
- Event listener system
- Signal validation logic
- PnL calculation accuracy
- Signal lifecycle verification
- Strategy callbacks
- Report generation
Problem Solved: Untested trading systems contain hidden bugs that cause financial losses. Comprehensive testing ensures reliability.
Testing Patterns:
- Unique names per test (prevent cross-contamination)
- Mock candle generators
- Async coordination utilities
- Background execution with event detection
Runtime validation with memoization:
Validations:
- Strategy existence and configuration
- Exchange schema correctness
- Frame definition validity
- Risk profile completeness
- Sizing method parameters
Problem Solved: Invalid configurations cause runtime errors or incorrect behavior. Validation catches issues before execution.
Features:
- Shallow validation on registration
- Deep validation before execution
- Memoized results (performance)
- Clear error messages
Graceful degradation on failures:
Strategies:
- ActionProxy wraps all handlers
- Errors logged, execution continues
- Rollback on state mutations
- Automatic retry on transient failures
Problem Solved: Unhandled exceptions crash trading systems. Graceful error handling maintains operation through failures.
Mechanisms:
- Try-catch at every boundary
- Error events for monitoring
- State rollback on failure
- Retry with backoff
Quick iteration on individual strategies:
Workflow:
- Create strategy file
- Run backtest with CLI
- Analyze reports
- Iterate on logic
- Deploy to paper trading
- Move to live when validated
Problem Solved: Complex setup slows development. Simple workflow enables rapid iteration.
Manage multiple strategies in single repository:
Structure:
- Shared packages (brokers, signals, utilities)
- Individual strategy directories
- Isolated resources per strategy
- Shared infrastructure (MongoDB, Redis)
Problem Solved: Separate repositories for each strategy create maintenance burden. Monorepo enables code sharing and consistent tooling.
Features:
- Automatic import aliases
- Per-strategy environment variables
- Isolated dump directories
- Shared broker adapters
Run same strategy across multiple symbols simultaneously:
Capabilities:
- Single Node.js process
- Shared event loop
- Common infrastructure
- Independent state per symbol
Problem Solved: Separate processes for each symbol waste resources. Parallel execution maximizes hardware utilization.
Performance:
- ~6,300× real-time aggregate speed
- ~703× per-symbol replay speed
- ~103 events/second in hot loops
- Constant memory usage
Reliable live trading with monitoring:
Components:
- Docker containers for isolation
- MongoDB for persistence
- Redis for caching
- Web dashboard for monitoring
- Telegram for alerts
Problem Solved: Ad-hoc deployments are unreliable. Structured deployment ensures stability and observability.
Features:
- Automatic restart on failure
- Health checks and monitoring
- Log aggregation
- Zero-downtime updates
Typical signal generation workflow:
- Fetch multi-timeframe data
- Calculate indicators
- Apply entry logic
- Validate signal parameters
- Return signal or null
Best Practices:
- Use framework data fetching (prevents look-ahead bias)
- Validate all parameters before return
- Include descriptive notes
- Handle errors gracefully
Common position management patterns:
Partial Profit:
- Close percentage at profit milestones
- Lock in gains while maintaining exposure
- Adjust remaining cost basis
DCA Entry:
- Add to position on favorable moves
- Track multiple entry levels
- Calculate blended entry price
Trailing Exit:
- Adjust stop/take as price moves
- Lock in gains dynamically
- Prevent giving back profits
Typical risk management setup:
- Define risk profile with validations
- Attach profile to strategy
- Framework checks before every signal
- Rejections logged automatically
- Statistics available for analysis
Common Validations:
- Minimum risk/reward ratio
- Maximum position size
- Time-of-day restrictions
- Volatility filters
import {
addStrategySchema,
listenError,
listenActivePing,
Log,
Position,
commitClosePending,
getPositionPnlPercent,
getPositionEntryOverlap,
getPositionEntries,
commitAverageBuy,
} from "backtest-kit";
import { errorData, getErrorMessage, str } from "functools-kit";
const HARD_STOP = 25.0;
const TARGET_PROFIT = 3;
const LADDER_STEP_COST = 100;
const LADDER_UPPER_STEP = 5;
const LADDER_LOWER_STEP = 1;
const LADDER_MAX_STEPS = 10;
addStrategySchema({
strategyName: "apr_2026_strategy",
getSignal: async (symbol, when, currentPrice) => {
return {
position: "long",
...Position.moonbag({
position: "long",
currentPrice,
percentStopLoss: HARD_STOP,
}),
minuteEstimatedTime: Infinity,
cost: LADDER_STEP_COST,
};
},
});
listenActivePing(async ({ symbol, currentPrice }) => {
const { length: steps } = await getPositionEntries(symbol);
if (steps >= LADDER_MAX_STEPS) {
return;
}
const hasOverlap = await getPositionEntryOverlap(symbol, currentPrice, {
upperPercent: LADDER_UPPER_STEP,
lowerPercent: LADDER_LOWER_STEP,
});
if (hasOverlap) {
return;
}
await commitAverageBuy(symbol, LADDER_STEP_COST);
});
listenActivePing(async ({ symbol, data, timestamp }) => {
console.log(new Date(timestamp));
const currentProfit = await getPositionPnlPercent(symbol);
if (currentProfit < TARGET_PROFIT) {
return;
}
Log.info("position closed due to the target pnl reached", {
symbol,
data,
});
await commitClosePending(symbol, {
id: "unknown",
note: str.newline(
"# Closed by target pnl",
),
});
});
listenError((error) => {
console.log(error);
Log.debug("error", {
error: errorData(error),
message: getErrorMessage(error),
});
});A common critique of trading frameworks is their steep learning curve and the ease with which a developer can introduce catastrophic bugs. Backtest Kit is intentionally designed with a "fool-proof" API surface. It restricts dangerous primitives and forces the developer into the "pit of success," making it practically impossible to shoot yourself in the foot.
In traditional frameworks, you must manually pass the currentDate or timestamp through every function call. If you miss a parameter, you accidentally leak future data into your indicators, ruining the backtest.
Backtest Kit uses Node.js AsyncLocalStorage to create an ambient execution context. When you request candles or order book data, the engine automatically knows exactly what "now" is. You cannot accidentally query future data because the engine physically blocks it at the adapter level.
Trading signals have a strict lifecycle: Idle → Scheduled → Pending → Opened → Active → Closed.
Using TypeScript's Discriminated Unions, the framework makes invalid states unrepresentable. You cannot accidentally call a closePosition method on a signal that is already closed, nor can you modify the entry price of an active trade. The compiler enforces the lifecycle, eliminating entire classes of runtime state-corruption bugs.
Manually implementing DCA is a mathematical nightmare. Developers often accidentally "average up" (buying at a higher price than the current effective entry), which destroys the position's profitability. Backtest Kit's DCA engine uses a harmonic mean calculation and physically rejects any commitAverageBuy call that would worsen the effective entry price.
In live trading, if the exchange rejects an order (e.g., insufficient margin, rate limit), your internal bot state and the exchange state become desynchronized. Usually, developers write complex try/catch blocks to manually roll back variables.
In Backtest Kit, the broker adapter intercepts every mutation before the internal state changes. If the exchange throws an error, the framework automatically rolls back the internal state and retries on the next tick. You never have to write rollback logic.
Before a signal even reaches the execution engine, it passes through a rigorous validation pipeline. The framework checks if Take-Profit (TP) and Stop-Loss (SL) prices are logically sound, if the Risk/Reward ratio meets your criteria, and if the signal violates any interval throttling rules. Invalid signals are rejected silently or logged, preventing them from crashing the strategy.
Most trading frameworks force you to choose between research speed and production reliability. Backtest Kit bridges this gap by providing a unified engine that handles everything from historical simulation to live execution. Here is how it stacks up against the industry standards.
The Paradigm Shift: From Fragile Scripts to Type-Safe Systems Backtrader is the grandfather of Python backtesting, but it relies heavily on dynamic typing and global state. This makes it notoriously easy to introduce look-ahead bias or state corruption, as the framework rarely stops you from making logical errors.
- The Backtest Kit Advantage: Backtest Kit enforces a strict, type-safe state machine (
idle → opened → active → closed) using TypeScript's discriminated unions. The compiler physically prevents you from accessing closed position data or modifying an active trade as if it were closed. Furthermore, Backtest Kit's ambient temporal context automatically blocks look-ahead bias at the adapter level.
The Paradigm Shift: From Static Simulation to Live Execution VectorBT is incredibly fast for matrix-based, single-pass historical simulations. However, it is fundamentally a research tool. It lacks a built-in execution engine for live trading, meaning you have to build a completely separate, custom system to actually deploy your strategy to an exchange.
- The Backtest Kit Advantage: Backtest Kit is not just a simulator; it is a unified execution engine. The exact same code that runs a historical backtest can be deployed to live trading with a single configuration change. Its crash-safe persistence, atomic file writes, and transactional broker commits ensure that your live bot can recover from network failures or process crashes without losing track of open positions.
The Paradigm Shift: From Platform Lock-in to a Modern, Open Stack MetaTrader is a closed ecosystem. MQL5 is a legacy language, and your strategies are trapped inside a Windows-centric desktop application. Integrating with modern data sources, AI models, or custom REST/WebSocket APIs requires painful workarounds and external bridges.
- The Backtest Kit Advantage: Backtest Kit is built on a modern, open-source stack (Node.js/TypeScript). You own the entire infrastructure. You can seamlessly integrate local LLMs (like Ollama), fetch data from any custom API, or deploy your bot in a Docker container on a Linux server. There is no vendor lock-in, no platform fees, and no reliance on a proprietary desktop GUI.
The Paradigm Shift: From Cloud Dependency to Self-Hosted Sovereignty QuantConnect (and its open-source Lean engine) is powerful, but it heavily pushes you toward their cloud infrastructure and data ecosystem. C# adds compilation overhead, and debugging complex multi-timeframe strategies in a web IDE can be frustrating.
- The Backtest Kit Advantage: Backtest Kit is 100% self-hosted and has zero dependencies on third-party cloud platforms. Your code, your data, and your execution environment stay entirely on your own machines. The TypeScript ecosystem allows for rapid iteration without compilation steps, and the built-in Web UI, CLI, and Telegram notifications provide a superior, modern developer experience for debugging and monitoring live bots.
The Paradigm Shift: From Simple Grids to Complex Position Management Freqtrade is an excellent open-source crypto trading bot, but its strategy logic is relatively flat. It excels at simple entry/exit signals but struggles with complex, multi-layered position management.
- The Backtest Kit Advantage: Backtest Kit treats position management as a first-class citizen. It natively supports complex Dollar-Cost Averaging (DCA) ladders, partial profit/loss taking, trailing stops, and breakeven protection—all with mathematically accurate cost-basis tracking. If your strategy requires dynamically adjusting a position over time rather than just "buying and holding until a stop-loss hits," Backtest Kit provides the enterprise-grade math to handle it flawlessly.
If you are building a quick research prototype or running a simple moving-average crossover in Python, tools like VectorBT or Backtrader are hard to beat for raw speed.
But the moment you need to deploy a strategy to production, manage complex position sizing, integrate AI agents, or guarantee that a network outage won't desync your bot from the exchange, Backtest Kit provides the architectural guardrails that legacy frameworks simply do not have. It is the bridge between a Jupyter Notebook experiment and a resilient, commercial-grade trading desk.
Look-Ahead Bias:
- Symptom: Unrealistic backtest results
- Cause: Using future data accidentally
- Solution: Use framework data fetching functions
State Desynchronization:
- Symptom: Framework state doesn't match exchange
- Cause: Exchange failure during state update
- Solution: Use transactional broker commits
Memory Leaks:
- Symptom: Increasing memory usage over time
- Cause: Unbounded event accumulation
- Solution: Use bounded queues, dispose resources
Performance Degradation:
- Symptom: Slow execution over time
- Cause: Repeated expensive calculations
- Solution: Use memoization, cache results
Enable Verbose Logging:
- Set log level to debug
- Review all state transitions
- Check validation errors
Use Web Dashboard:
- Monitor signals in real-time
- Inspect position details
- Trigger manual operations
Analyze Reports:
- Review Markdown reports
- Check JSONL logs
- Examine heatmap visualizations
Key Differences:
- Strict state machine (no ambiguous states)
- Automatic temporal context (no manual timestamps)
- Transactional commits (no partial updates)
- Comprehensive validation (no invalid signals)
Migration Steps:
- Identify signal generation logic
- Map to
getSignalfunction - Configure risk management
- Set up broker adapter
- Test in paper mode
- Deploy to live
Benefits:
- Eliminate state corruption bugs
- Prevent look-ahead bias
- Ensure exchange synchronization
- Improve code maintainability
Building AI-powered trading strategies typically requires integrating with multiple LLM providers, each with its own API format, authentication method, and response structure. Developers end up writing boilerplate code to handle OpenAI's format, then Claude's format, then DeepSeek's format — and managing API key rotation, structured output enforcement, and error handling across all of them.
The Backtest Kit Solution: @backtest-kit/ollama provides a universal inference adapter that abstracts away provider differences, letting you switch between 10+ LLM providers with a single line of code while maintaining type-safe, structured output.
Instead of learning and maintaining separate SDKs for each provider, you use a unified Higher-Order Function (HOF) API. Wrap your signal generation function with deepseek(), claude(), gpt5(), or ollama(), and the adapter handles provider-specific formatting, authentication, and error handling automatically.
Supported Providers:
- OpenAI (GPT-5, GPT-4o)
- Anthropic Claude
- DeepSeek
- Grok (xAI)
- Mistral
- Perplexity
- Cohere
- Alibaba (Qwen)
- Hugging Face
- Ollama (local models)
- GLM-4 (Zhipu AI)
Problem Solved: Provider lock-in and API fragmentation. You can test your strategy with DeepSeek during development, switch to Claude for production, and fall back to local Ollama if cloud APIs are unavailable — all without changing your strategy code.
Trading signals require specific fields: position direction, entry price, take-profit, stop-loss, risk notes. Raw LLM outputs are unpredictable — they might return markdown, JSON with missing fields, or hallucinated values.
The adapter integrates with agent-swarm-kit to enforce Zod schemas or JSON schema validation. You define the expected structure once, and the adapter guarantees every LLM response conforms to it before reaching your strategy logic.
Key Features:
- Zod schema validation with automatic retry on malformed output
- Custom validation rules (e.g., "stop-loss must be below entry for LONG")
- Trading-specific field descriptions injected into prompts
- Type-safe TypeScript interfaces generated from schemas
Problem Solved: Unstructured LLM outputs crash strategies or generate invalid trades. Schema enforcement ensures every signal is valid before execution.
Cloud LLM APIs have rate limits, and single API keys can exhaust quotas during intensive backtesting or live trading. The adapter supports automatic token rotation — pass an array of API keys, and the system rotates through them to distribute load.
For production resilience, you can configure fallback chains: if DeepSeek is unavailable, automatically retry with Claude; if Claude fails, fall back to local Ollama. This ensures your strategy never stops generating signals due to provider outages.
Problem Solved: Rate limiting and provider downtime halt trading. Token rotation and fallback chains maintain continuous operation.
Prompt engineering is iterative — you need to tweak system prompts, add context, and test variations. The adapter loads prompts from .cjs modules in your config/prompt/ directory, making them easy to edit without touching strategy code.
To avoid redundant API calls during backtesting (where the same candle data might be analyzed multiple times), the adapter memoizes prompt modules using functools-kit. Identical inputs return cached responses, dramatically reducing API costs and latency.
Problem Solved: Prompt management scattered across strategy files, and redundant API calls during backtesting. Centralized prompt modules with memoization solve both issues.
Unlike generic LLM wrappers, @backtest-kit/ollama is designed for trading. System prompts automatically receive context about the current symbol, strategy name, exchange, timeframe, and whether you're in backtest or live mode. This allows your prompts to adapt dynamically without manual string interpolation.
Problem Solved: Generic LLM adapters don't understand trading context. Trading-specific prompts produce more relevant signals and risk assessments.
Most trading frameworks provide toy examples — moving average crossovers on daily data that work in tutorials but fail in production. Backtest Kit includes real-world strategy examples that demonstrate advanced position management, multi-timeframe analysis, AI integration, and alternative signal sources. Each example is fully documented with backtest results, Sharpe ratios, and implementation details.
Signal Source: TensorFlow feed-forward neural network trained on normalized candle patterns
How It Works:
- Every 8 hours, the strategy fetches 58 candles (50 for training, 8 for prediction)
- A neural network (8→6→4→1 architecture) is trained on normalized data, where each candle's close is mapped to [0,1] representing its position within the high-low range
- Every 15 minutes, if the current price is below the predicted price, a LONG position is opened with a 1% hard stop
- Positions exit via trailing take-profit: when profit retraces 1% from its peak (e.g., position hits +3%, closes at +2%)
Results: +18.26% PNL, Sharpe Ratio 0.31
Key Techniques Demonstrated:
- Real-time model training within strategy execution
- Normalization for stable neural network inputs
- Trailing take-profit for dynamic exits
- Integration with TensorFlow.js in Node.js
Problem Solved: Static technical indicators fail to adapt to changing market regimes. Neural networks can learn non-linear patterns, but integrating them into a trading system requires careful handling of training data, prediction timing, and position management.
Signal Source: TradingView Pine Script indicator executed via @backtest-kit/pinets
How It Works:
- Every hour, the strategy runs
btc_dec2025_range.pineon 1h candles - The Pine Script extracts: Bollinger Bands, range boundaries, signal direction (±1), ranging flag, and volume spike detection
- A signal fires on
signal === 1(LONG) orsignal === -1(SHORT), but only if the price hasn't already moved past the signal close and the market isn't ranging - Each position uses a fixed ±2% bracket (TP and SL), no DCA, no trailing
Results: +2.40% PNL, Sharpe Ratio 0.06
Key Techniques Demonstrated:
- Running TradingView Pine Script directly in Node.js
- Extracting multiple outputs from a single Pine indicator
- Filtering signals based on market state (ranging vs. trending)
- Fixed bracket exits for mean-reversion strategies
Problem Solved: Traders have thousands of hours invested in Pine Script indicators. Rewriting them in JavaScript is time-consuming and error-prone. Direct Pine Script execution preserves existing work while enabling backtesting and live deployment.
Signal Source: Real Telegram channel signals (Crypto Yoda), inverted
How It Works:
- Signals are loaded from
assets/entry.jsonl— 11 real posts from a Telegram channel, exported verbatim - On each candle,
getSignalchecks ifpublishedAtmatches the current minute and whetherclosePricefalls insideentry.from..entry.to - The strategy enters counter-trend (inverts the channel's direction) with trailing take-profit and no fixed TP
- Stop-loss is set to -0.5%
Results: +8.58% PNL, Sharpe Ratio 1.14 (highest in the example set)
Key Techniques Demonstrated:
- Exploiting predictable signal sources (channels with algorithmic posting)
- Counter-trend entry based on liquidity harvesting
- Trailing take-profit for maximizing gains
- Loading external signal data into backtest
Problem Solved: Many Telegram channels publish signals with poor risk-reward ratios and algorithmic timing. By reverse-engineering the algorithm and entering counter-trend, you can harvest the liquidity created by followers blindly copying the signals.
Signal Source: LLM analysis of live crypto/macro news via Tavily + Ollama
How It Works:
- Every 4–8 hours, a Tavily search fetches the latest Bitcoin and macro headlines
- The raw news text is passed to a local Ollama model, which returns one of
bullish,bearish, orwait getSignalopens a LONG onbullish, SHORT onbearish, and skips onwait- A conflicting forecast while a position is open triggers
commitClosePending(sentiment flip) - Positions exit on trailing take-profit (1% drawdown from peak) or stop-loss (1% from entry)
Results: +16.99% PNL, Sharpe Ratio 0.25 (during a month where BTC fell -16.4%)
Key Techniques Demonstrated:
- Integrating LLM inference into signal generation
- Using external news APIs (Tavily) for real-time data
- Sentiment-based position flipping
- Trailing exits for trending markets
Problem Solved: Technical indicators lag price action. News sentiment can provide leading signals, but parsing news manually is impractical. LLMs can analyze headlines in real-time and generate directional bias, enabling news-driven strategies.
Signal Source: Fixed SHORT signal with dynamic Dollar-Cost Averaging
How It Works:
getSignalopens a SHORT on every new pending signal viaPosition.moonbagwith a 25% hard stop and $100 cost- While active,
commitAverageBuyfires on each ping if the current price moves outside a ±1–5% band around the last entry and fewer than 10 rungs have been added - The position closes as soon as blended portfolio PNL reaches +0.5% via
commitClosePending
Results: +37.83% PNL, Sharpe Ratio 0.35
Key Techniques Demonstrated:
- Dynamic DCA ladder with overlap detection
- Blended cost basis tracking across multiple entries
- Target-based exit (close at specific PNL threshold)
- Short-biased mean-reversion strategy
Problem Solved: Single-entry SHORT positions in volatile markets often get stopped out before the reversal. DCA ladders allow you to average into the position as price spikes, lowering the blended entry and increasing the probability of hitting a small profit target on the reversal.
Signal Source: Fixed LONG signal with dynamic Dollar-Cost Averaging
How It Works:
- Same mechanics as SHORT version but LONG-biased with a 3% profit target
- Deployed 2.4 entries per trade on average
- Achieved +67.85% PNL on deployed capital with improved percentage drawdown (-2.59% vs -3.99% without DCA)
Results: +67.85% PNL, Sharpe Ratio 0.12
Key Techniques Demonstrated:
- Long-biased DCA in trending bull markets
- Comparison of DCA vs. single-entry performance
- Drawdown analysis with and without averaging
Problem Solved: In trending markets, single entries miss opportunities to add to winning positions. DCA ladders allow you to scale into trends while maintaining a favorable risk-reward profile.
Signal Source: Python-based EMA crossover executed via WebAssembly (WASI)
How It Works:
- Every 8 hours,
Cache.fnruns the Python indicator (strategy.py) on 8h candles to calculate EMA(9) and EMA(21) - A signal fires based on EMA crossover and 4h range midpoint confirmation: if EMA(9) > EMA(21), open LONG; otherwise SELL
- Each signal opens a $100 bracket position via
Position.bracketwith ±2% take-profit and stop-loss - The strategy deployed $3,300 across 33 trades (all LONG), achieving +$5.52 (+0.17%) with a 63.6% win rate
Results: +5.52% PNL, Sharpe Ratio 0.09
Key Techniques Demonstrated:
- Running Python indicators in Node.js via WebAssembly
- Multi-timeframe confirmation (8h EMA + 4h range)
- Bracket orders for fixed risk-reward
- Integration with Python ecosystem from TypeScript
Problem Solved: Python has a rich ecosystem of technical analysis libraries (pandas, ta-lib, scikit-learn), but integrating them into Node.js trading systems is difficult. WebAssembly execution allows you to run Python code directly in your TypeScript strategies without rewriting logic.
Signal Source: Prediction market probability shifts from Polymarket
How It Works:
loadPolySignalsreadsassets/polymarket-backtest-result.jsononce viasingleshot, aggregates to one signal per day (max|dprob|across all crypto-prices markets), and stripsentryPrice/exitPrice(future-data fields)getSignalpicks the most recent signal withtimestamp ≤ whenand rejects it if older than 1h or|dprob| < 0.10- Positive Δprob → LONG, negative → SHORT
- Entry via
Position.moonbagat market with a 1% hard stop and no fixed TP listenActivePingcloses on 1% trailing drawdown from peak profit, or on the 24h timeout
Results: 10 trades, 70% WR, Sharpe +0.065 — three SL hits (one per LONG on the April top) nearly cancel seven trailing-take SHORT wins on the recovery slope
Key Techniques Demonstrated:
- Using prediction markets as leading indicators
- Aggregating signals across multiple markets
- Time-based signal expiration
- Trailing exits for asymmetric risk-reward
Problem Solved: Prediction markets aggregate crowd wisdom about future events. Sharp shifts in probability can reflect retail sentiment flow that precedes spot price movement. By tracking Δprob (change in probability), you can exploit this leading indicator without look-ahead bias.
Each strategy example showcases a different aspect of Backtest Kit's capabilities:
Advanced Position Management:
- DCA ladders with overlap detection and blended cost basis
- Partial profit/loss with dynamic cost adjustment
- Trailing take-profit and stop-loss
- Breakeven protection
Multi-Source Signal Generation:
- Technical indicators (EMA, Bollinger Bands, RSI)
- Neural networks and machine learning
- LLM-powered sentiment analysis
- External data sources (Telegram, Polymarket, news APIs)
- Pine Script indicators from TradingView
Realistic Backtesting:
- Look-ahead bias prevention
- Accurate PNL with fees and slippage
- Multi-timeframe synchronization
- Crash-safe persistence
Production Deployment:
- Same code for backtest and live
- Transactional broker commits
- Risk management and validation
- Event-driven monitoring
These examples are not toy demonstrations — they are production-quality strategies that have been backtested on real historical data with documented results. You can clone the repository, run the backtests, and see the exact performance metrics for yourself.
Evaluating a multi-asset portfolio requires more than just averaging the performance of individual symbols. In earlier versions, the framework calculated a "Portfolio Sharpe" as a trade-count-weighted average of per-symbol Sharpes. Mathematically, this is flawed — a true Markowitz Portfolio Sharpe requires a cross-symbol correlation matrix and capital allocation weights, neither of which the framework tracks. Averaging ratios often creates a false sense of security, artificially inflating the metric if one symbol has a high Sharpe while another has a negative one.
Starting from v10.2.0, the framework replaces the flawed average with a statistically rigorous Pooled Sharpe Ratio.
How It Works: Instead of averaging individual ratios, the engine pools all per-trade returns across every symbol in the portfolio into a single, unified sample. The Sharpe Ratio is then computed on this pooled distribution.
Why It Matters:
- Statistical Honesty: It accurately reflects the risk-adjusted returns of the portfolio as a whole, correctly penalizing strategies that have high variance across different assets.
- No False Positives: Prevents the illusion of a "good" portfolio Sharpe that is mathematically unjustified.
- Transparent Labeling: The Markdown header label was explicitly changed from
Portfolio SharpetoPooled Sharpe, and the report legend includes a disclaimer about Markowitz semantics, ensuring you never mistake it for a covariance-based optimization metric.
This change is part of a massive overhaul of the statistical suite. To ensure every metric is battle-tested and mathematically sound, the entire analytics layer was rebuilt against canonical definitions and verified against an independent reference implementation (an 84-file measure testbed):
- Sample Standard Deviation: Switched to Bessel's correction (
N-1) for unbiased variance estimation, preventing the underestimation of risk in small sample sizes. - Compounded Equity-Curve: Max Drawdown, Calmar, and Recovery Factor now use a compounded equity curve rather than additive arithmetic sums, preventing the double-counting of percentage returns.
- Geometric Annualization: Expected Yearly Returns now use geometric compounding instead of arithmetic multiplication, correctly accounting for volatility drag (the mathematical reality that a 50% loss requires a 100% gain to recover).
- Canonical Sortino: Implements the textbook Sortino (1991) formula with downside deviation over the total sample size (
N_total), properly penalizing strategies with frequent small losses instead of just looking at the worst days. - Float-Artifact Guards: Identical-return series produce a standard deviation of
≈1e-17due to floating-point math. Without a guard,Sharpe = Avg / 1e-17produces astronomical magnitudes. The engine now uses anSTDDEV_EPSILONguard to returnN/Ainstead of publishing a fake Sharpe of10,000,000.
Problem Solved: Publishing astronomically high or mathematically impossible Sharpe/Sortino ratios due to tiny sample sizes, float-artifact standard deviations, or incorrect arithmetic averaging. The new engine includes strict gating thresholds (e.g., minimum 10 signals, minimum 14 calendar days) to return N/A instead of misleading numbers, ensuring that the metrics you base your financial decisions on are mathematically bulletproof.
Running multiple symbols sequentially or spawning a separate Node.js process for each ticker creates massive IPC overhead and wastes CPU cycles. Furthermore, coordinating shared state (like global risk limits or candle caches) across separate processes is an architectural nightmare.
The Solution: Backtest Kit natively supports running dozens of symbols concurrently within a single Node.js process using Backtest.background() and Live.background(). All parallel contexts share the same V8 event loop, MongoDB connection pool, and Redis cache, eliminating IPC overhead while maintaining strict state isolation per symbol.
How It Works:
- In-Memory Activity Registry (
Lookup): Tracks every in-flight backtest and live activity. The engine knows exactly how many workloads are running concurrently and manages their lifecycle. - Cooperative Event-Loop Hand-Off (
Candle.spinLock): When fetching candles, the active backtest yields the event loop to peer workloads. This ensures multiple parallel calls progress in a fair, round-robin fashion instead of letting the first started symbol monopolize the CPU until completion. - Shared Infrastructure: Parallel runs share the same Mongo/Redis adapters, meaning candle caches and global risk state are instantly synchronized across all symbols without cross-process syncing.
Performance Metrics:
- ~6,300× Real-Time Aggregate: Processing 9 symbols simultaneously on a commodity laptop (i5-13420H) achieves ~703× per-symbol replay speed.
- ~103 Events/Second: The hot loop (
listenActivePing → commitAverageBuy) sustains high throughput in a single Node process.
Use Cases:
- Portfolio-Wide Backtesting: Test a strategy across the entire top-50 crypto market cap simultaneously.
- Live Multi-Asset Desks: Run a single live bot that monitors and trades 20+ pairs, sharing a global risk profile to prevent over-leveraging the total account.
- Walker A/B Testing: Evaluate multiple strategy variants across different symbols in parallel.
Backtest Kit includes a powerful built-in web interface that provides real-time visualization of transaction state graphs — transforming abstract trading logic into an interactive, visual debugging experience.
The web interface renders the complete signal lifecycle state machine as an interactive graph, showing exactly where each transaction sits in its journey from idle → scheduled → opened → active → closed.
What You See:
- Node-based graph where each signal is a visual node
- Color-coded states: Green for profitable positions, red for losses, yellow for pending, blue for active
- Real-time transitions: Watch signals flow through states as market conditions change
- State transition history: Click any node to see the complete audit trail of state changes
Example Visualization:
[idle] ──signal generated──> [scheduled] ──price hit──> [opened]
│
├──> [active] ──TP hit──> [closed: profit]
│
└──> [active] ──SL hit──> [closed: loss]
1. State Inspection Panel Click any transaction node to reveal:
- Current position size and entry price
- Real-time PnL with fees/slippage calculated
- Distance to take-profit and stop-loss levels
- DCA entry history (if applicable)
- Partial close execution log
- Timestamp of last state transition
2. Filter & Search
- Filter by symbol (BTCUSDT, ETHUSDT, etc.)
- Filter by strategy name
- Filter by state (show only
activepositions, or onlyclosedfor post-analysis) - Search by signal ID or transaction hash
3. Time-Travel Debugging
- Scrub through historical backtest timeline
- Watch the state graph evolve candle-by-candle
- Pause at any moment to inspect exact state
- Replay specific signals to understand decision logic
4. Multi-Symbol Portfolio View
- Aggregate view showing all symbols simultaneously
- Heatmap overlay showing which symbols are generating the most signals
- Correlation matrix showing how signals cluster across assets
- Portfolio-level risk exposure visualization
The interface includes a live metrics panel showing:
- Active Positions: Count and total capital deployed
- Pending Signals: Waiting for price activation
- Closed Today: Win rate and total PnL
- Risk Exposure: Percentage of portfolio at risk
- Signal Velocity: Signals per hour across all strategies
- Export graph as PNG/SVG for presentations or documentation
- Export state history as JSON for offline analysis
- Share permalink to specific backtest moment (includes timestamp and symbol filters)
- Embed mode for embedding the live graph in internal dashboards via iframe
# Start backtest with UI
npx @backtest-kit/cli --backtest ./strategy.ts --ui
# Interface available at:
http://localhost:60050The web interface requires zero configuration — it auto-discovers registered strategies, exchanges, and symbols, presenting them in an intuitive navigation menu.
The Pain Point:
For quantitative developers, Python is the undisputed king of technical analysis, boasting massive ecosystems like Pandas, TA-Lib, and Scikit-learn. In contrast, the native TypeScript ecosystem for financial indicators has historically been sparse. While Backtest Kit offers native Pine Script integration, developers deeply entrenched in Python might view the lack of native TA-Lib bindings as a critical bottleneck, fearing they would have to rewrite complex mathematical models and custom indicators from scratch.
The Solution: Backtest Kit solves this not by forcing you to abandon your existing tools, but by providing a multi-layered integration strategy that brings the entire Python and TradingView ecosystem directly into your Node.js runtime, without breaking the type-safe, crash-safe architecture.
1. Native Pine Script Execution (@backtest-kit/pinets)
Instead of rewriting indicators in JavaScript, you can run native TradingView Pine Script (v5/v6) directly inside Node.js. This instantly grants you access to thousands of community-built indicators, custom strategies, and complex plotting logic. The framework handles the execution context, ensuring that Pine Script calculations are perfectly synchronized with the backtest timeline and strictly protected against look-ahead bias.
2. Built-in Multi-Timeframe Analysis (@backtest-kit/signals)
For standard technical analysis, the framework ships with a dedicated signals package featuring 50+ pre-computed indicators (RSI, MACD, Bollinger Bands, Stochastic, ADX, ATR, CCI, Fibonacci, Support/Resistance). Crucially, it handles multi-timeframe synchronization (1m, 15m, 30m, 1h) automatically, generating structured Markdown reports optimized for LLM context injection.
3. Python via WebAssembly (WASI)
When you absolutely must use a Python-specific library (like ta-lib, pandas, or custom scikit-learn models), Backtest Kit supports executing Python code directly within your TypeScript strategy via WebAssembly (WASI). This allows you to compile your existing Python indicator scripts into WASM and run them natively in the Node.js event loop. You get the exact mathematical output of Python without the IPC overhead, subprocess management, or serialization bottlenecks of traditional bridges.
4. Zero-Dependency Quant Math Ports
For advanced quantitative modeling, the ecosystem includes native TypeScript ports of complex financial mathematics (inspired by vectorbt). This includes modules for conditional variance modeling (garch), order-flow intensity and regime breaks (volume-anomaly), and coordinated speculation detection (pump-anomaly). These run natively in TypeScript, fully typed, and integrated directly with the Exchange schema.
5. High-Performance Custom Indicators via Cache
If you need to write custom indicators in TypeScript, the framework provides intelligent memoization and time-based caching (Cache.fn and Cache.file). Expensive calculations are automatically cached per candle-interval boundary, ensuring that your custom logic runs at O(1) speed on subsequent ticks without consuming excessive memory or triggering garbage collection pauses.
The Bottom Line: You do not need to choose between the rich indicator ecosystem of Python/TradingView and the production-grade reliability of TypeScript. Backtest Kit acts as a universal bridge, allowing you to leverage Pine Script, Python WASM, and native quant ports seamlessly, all while maintaining strict temporal alignment and type safety.
Backtest Kit solves the fundamental challenges of building reliable trading systems through:
- Type-safe state machines that eliminate state corruption
- Automatic temporal context that prevents look-ahead bias
- Transactional commits that ensure exchange synchronization
- Comprehensive validation that catches errors before execution
- Pluggable architecture that adapts to different deployment scenarios
- Production-grade persistence that survives crashes and restarts
The framework is battle-tested through 775+ tests, real-world trading deployments, and continuous iteration based on user feedback. It provides the reliability and correctness required for production trading while maintaining the flexibility needed for strategy research and development.
Whether building a single strategy or a multi-symbol trading desk, Backtest Kit provides the tools and guarantees needed to move from research to production with confidence.