Analytics (0.8)

@tradecanvas/analytics ships a headless, bar-by-bar strategy backtester with portfolio tracking and a summary risk-metrics calculator. Pair it with replay mode to visualize trades on the chart.

Live backtest — SMA(10/30) cross

365 days of synthetic price data, $10k initial cash, 0.05% commission, 0.03% slippage.

Running backtest…

↑ Live SMA(10/30) cross-over backtest on 365 days of deterministic synthetic data. Scrub the slider or press Play to step through the equity curve bar-by-bar.

Backtester

Execution model:

  • Strategy fn runs at close of each bar.
  • Orders placed on bar N fill on bar N+1 — market at open; limit/stop when the bar trades through the trigger price.
  • Equity is marked to close of every bar.
import { Backtester, FixedCommission, PercentSlippage } from '@tradecanvas/analytics'

const bt = new Backtester({
  initialCash: 10_000,
  commission: new FixedCommission(2),
  slippage: new PercentSlippage(0.0005),
  allowShort: true,
})

const result = bt.run(historicalBars, (ctx) => {
  if (!ctx.position) {
    ctx.placeOrder({ side: 'long', type: 'market', quantity: 1 })
  } else if (ctx.bar.close > ctx.position.averagePrice * 1.02) {
    ctx.close()
  }
})

console.log(result.metrics.sharpe, result.metrics.maxDrawdownPct)

StrategyContext

Field / methodDescription
barCurrent bar.
indexIndex of bar in the input series.
historyBars up to and including bar.
positionCurrent position or null.
cashAvailable cash.
equityCash + mark-to-market position value.
placeOrder(order)Queue order for next bar.
close(tag?)Market-close current position.
cancel(orderId)Cancel a pending order.

Commission & slippage models

  • FixedCommission(perTrade)
  • PercentCommission(rate) — fraction of notional, e.g. 0.001 = 10 bps.
  • PerShareCommission(perShare, minimum?)
  • PercentSlippage(rate) — adverse fraction of price.
  • RangeBasedSlippage(factor) — proportional to the bar's range.

Portfolio

Tracks cash, one net position, realized P&L, and the equity curve.

const portfolio = new Portfolio({ initialCash: 10_000 })
portfolio.applyFill({ ... })
portfolio.mark(time, price)         // record equity point

portfolio.getPosition()             // → { side, quantity, averagePrice, ... } | null
portfolio.getTrades()               // → closed trades
portfolio.getEquityCurve()          // → equity points
portfolio.equity(price)             // mark-to-market

Risk metrics

import { computeRiskMetrics } from '@tradecanvas/analytics'

const m = computeRiskMetrics(initialCash, equityCurve, trades, {
  periodsPerYear: 252,    // optional; auto-detected from timestamps
  riskFreeRate: 0.03,
})

m.totalReturnPct
m.cagr
m.sharpe
m.sortino
m.calmar
m.maxDrawdownPct
m.winRate
m.profitFactor
m.expectancy

Pair the result's equityCurve with EquityCurveRenderer to visualize backtests.