Full-stack UI framework for visualizing cryptocurrency trading signals, backtests, and real-time market data. Combines a Node.js backend server with a React dashboard - all in one package.

Interactive dashboard for backtest-kit with signal visualization, candle charts, risk analysis, and notification management. Built with React 18, Material-UI, and Lightweight Charts.
π Backtest Kit Docs | π GitHub
@backtest-kit/ui provides both backend API and frontend dashboard:
| Component | Description |
|---|---|
serve() |
Start HTTP server with REST API endpoints |
getRouter() |
Get expressjs-compatible router for custom middleware integration |
npm install @backtest-kit/ui backtest-kit ccxt
import { serve } from '@backtest-kit/ui';
// Start the UI server
serve('0.0.0.0', 60050);
// Dashboard available at http://localhost:60050
import { setLogger } from '@backtest-kit/ui';
setLogger({
log: (msg) => console.log(`[UI] ${msg}`),
warn: (msg) => console.warn(`[UI] ${msg}`),
error: (msg) => console.error(`[UI] ${msg}`),
});
The Revenue metrics on the dashboard are calculated in dollar terms by summing the pnlCost field from all closed signals within each time window.
revenue[window] = Ξ£ signal.pnl.pnlCost (for all closed signals in that window)
pnlCost is computed by the backend (toProfitLossDto) as:
pnlCost = (pnlPercentage / 100) Γ pnlEntries
| Field | Source | Description |
|---|---|---|
pnl.pnlCost |
IStorageSignalRow |
Absolute P&L in USD β the only value summed for revenue |
pnl.pnlPercentage |
IStorageSignalRow |
Percentage P&L (accounts for DCA-weighted entry price, slippage, and fees) |
pnl.pnlEntries |
IStorageSignalRow |
Total invested capital in USD β sum of all entry costs (Ξ£ entry.cost) |
Example (1 DCA entry at $100, position closed +5%):
| DCA entries | pnlEntries |
pnlPercentage |
pnlCost |
|---|---|---|---|
| 1 | $100 | 5 % | +$5.00 |
| 2 | $200 | 5 % | +$10.00 |
| 3 | $300 | 5 % | +$15.00 |
The anchor point depends on execution mode:
updatedAt across all closed signals (time windows are relative to the end of the run)Date.now() (wall-clock time)| Window | Range |
|---|---|
| Today | >= startOf(anchorDay) |
| Yesterday | [anchorDay β 1d, anchorDay) |
| 7 days | >= anchorDay β 7d |
| 31 days | >= anchorDay β 31d |
Revenue and signal count are tracked separately for each window and aggregated across all symbols on the Dashboard.
When multiple DCA entries exist, the effective open price is a cost-weighted harmonic mean:
effectivePrice = Ξ£cost / Ξ£(cost / price)
This is the correct formula for fixed-dollar entries (not simple average), because buying $100 worth at different prices gives different coin quantities.
Each partial stores a costBasisAtClose snapshot β the running dollar cost-basis before that partial fired. This avoids replaying the full entry history on every call.
Cost-basis replay:
for each partial[i]:
closedDollar += (percent[i] / 100) Γ costBasisAtClose[i]
remainingCostBasis = costBasisAtClose[i] Γ (1 - percent[i] / 100)
# DCA entries added AFTER the last partial are appended:
remainingCostBasis += Ξ£ entry.cost for entries[lastEntryCount..]
totalClosedPercent = closedDollar / totalInvested Γ 100
Effective price through partials is computed iteratively so that a partial sell does not change the entry price of the remaining coins:
# partial[0]:
effPrice = costBasisAtClose[0] / Ξ£(cost/price for entries[0..cnt[0]])
# partial[j]:
remainingCB = prev.costBasisAtClose Γ (1 - prev.percent / 100)
oldCoins = remainingCB / effPrice β coins still held
newCoins = Ξ£(cost/price for DCA entries between j-1 and j)
effPrice = (remainingCB + newCost) / (oldCoins + newCoins)
Without partials:
priceOpenSlip = effectivePrice Γ (1 Β± slippage)
priceCloseSlip = priceClose Γ (1 β slippage)
pnlPercentage = (priceCloseSlip - priceOpenSlip) / priceOpenSlip Γ 100
fee = CC_PERCENT_FEE Γ (1 + priceCloseSlip / priceOpenSlip)
pnlPercentage -= fee
With partials β dollar-weighted sum:
weight[i] = (percent[i] / 100 Γ costBasisAtClose[i]) / totalInvested
totalWeightedPnl = Ξ£ weight[i] Γ pnl[i] # each partial at its own effectivePrice
+ remainingWeight Γ pnlRemaining # rest closed at final priceClose
fee = CC_PERCENT_FEE # open (once)
+ Ξ£ CC_PERCENT_FEE Γ weight[i] Γ (closeSlip[i] / openSlip[i]) # per partial
+ CC_PERCENT_FEE Γ remainingWeight Γ (closeSlip / openSlip) # final close
pnlPercentage = totalWeightedPnl - fee
pnlCost = pnlPercentage / 100 Γ totalInvested
| Field | Description |
|---|---|
totalInvested |
Ξ£ entry.cost (or CC_POSITION_ENTRY_COST if no _entry) |
weight[i] |
Real dollar share of each partial relative to totalInvested |
effectivePrice at partial i |
Computed via iterative costBasisAtClose replay up to partials[i] |
priceOpen in result |
getEffectivePriceOpen(signal) β DCA-weighted harmonic mean across all entries |
The frontend provides specialized views for different trading events:
| View | Description |
|---|---|
| Signal Opened | Entry details with chart visualization |
| Signal Closed | Exit details with PnL analysis |
| Signal Scheduled | Pending orders awaiting activation |
| Signal Cancelled | Cancelled orders with reasons |
| Risk Rejection | Signals rejected by risk management |
| Partial Profit/Loss | Partial position closures |
| Trailing Stop/Take | Trailing adjustments visualization |
| Breakeven | Breakeven level adjustments |
Each view includes:
Instead of building custom dashboards:
Without backtest-kit
// β Without @backtest-kit/ui
// Build your own React app
// Implement chart components
// Create signal visualization
// Handle notifications
// Write API endpoints
// ... weeks of development
With backtest-kit
// β
With @backtest-kit/ui
import { serve } from '@backtest-kit/ui';
serve(); // Full dashboard ready!
Benefits:
Fork/PR on GitHub.
MIT Β© tripolskypetr