Live on Chalk · Point-in-time backtests

Point-in-time backtests.
On Chalk.

This live demo shows how you can backtest results that hold up in live trading, with Chalk. Tune a strategy and see how much the leak inflates returns, against a benchmark.

Compare a valid backtest with one that saw the future

Both curves use the same synthetic prices and strategy. Their information timing differs.

Try this experiment · expected results

  1. 1Read the recorded comparison

    Keep the default strategy and compare the solid point-in-time curve with the dashed date-joined curve.

    Expected: The dashed curve uses that day’s close before trading and can overstate performance.

  2. 2Inspect one rebalance

    Compare the inputs available at 09:30 with the later closing values in the timing example.

    Expected: The row-level evidence explains the gap; changing strategy settings does not remove the timing requirement.

Strategy

Hold the 8 tickers with the highest total return over the lookback. Long only, equal weight, from 40 synthetic tickers.

Lookback
Rebalance

Checking the live backend…

Growth of $1, after costs

Recorded run · 2026-10-15
$1$1.5$2202320242025
2022-07-04 → 2025-12-31Point in time$1.46Leaky (date join)$2.19Equal-weight benchmark$1.64
Weekly · 3 months · 5 bpsTotalCAGRSharpeMax DDTurnover/yrCosts
Point in time46%11.3%0.63−41%26.1×4.6%
Leaky (date join)119%24.9%1.21−34%27.5×4.8%
Benchmark64%15.0%0.85−34%––
What the leak did

Reading each day's close before trading made momentum look better by 13.6 percentage points a year and moved its Sharpe from 0.63 to 1.21. The leaky run beats the benchmark; the real one does not.

The leaky signal already knows how the day it trades on will close, so it buys what is about to rise. The more often you rebalance, the more those few hours are worth: try daily.

Point in time · dataset c60987c3 · 36,520 rows in 28.6s · 0 rows read a close that had not printed
Date join · dataset af579e84 · 36,520 rows in 27.3s · 36,520 rows read a close that had not printed

The 40 tickers and their prices are synthetic, generated from a fixed seed with planted trend, choppy and sell-off regimes. Nothing here is market data or investment advice. The features, offline queries and portfolio math are real and run on Chalk.

What one rebalance saw

TKR01 at the 09:30 rebalance on Tuesday, 5 March 2024. Both rows are real output from the demo's offline queries; only the input time differs.

Point in time
Mon 4 Mar, 16:00
$50.52
17.5 hours before the decision. Every window ends here.
Date join
Tue 5 Mar, 16:00
$49.75
Six and a half hours after the trade. The signal already knows the day fell 1.5%.

Built on Chalk

Time travel for every signal, the compute to trade on it, and a record of each run.

  • One definition, research to production

    The signal you backtest is the feature your live strategy queries. No second pipeline to rebuild the windows, and no drift between the two.

  • Windows as of any moment

    30, 91 and 182-day windows end at each row's input time, to the second, so a rebalance only ever sees closes that had printed.

  • Checkable, row by row

    Output the time of the last close each signal read next to its input time. In the recorded run, zero point-in-time rows read the future.

  • Backtests are datasets

    Each offline query is saved as a named dataset with its own id, so a backtest can be re-read, compared and audited later.

Deploy your own

Backtest your own signals

Define bars with the time each one became known, write signals as windowed aggregations that filter on chalk_now, and run one offline query with an input time per rebalance.

To use your own data, swap the synthetic resolver for a SQL resolver over your bars table. The signals, the query and the portfolio code do not change.

# The signals, on a branch of your environment
cd chalk_project
chalk apply --branch strategy-backtest

# Both offline queries and every backtest; writes results/summary.json
cd ..
./record_run.py --branch strategy-backtest

# The live re-run API, as a Chalk scaling group with a workload identity
./deploy.py --branch strategy-backtest
Implementation details

How the example works

  • Stamp bars when they print

    Each daily bar carries the moment its close printed, 16:00 New York, not the trading date it belongs to. A 09:30 decision cannot see it.

  • Signals filter on chalk_now

    Momentum, rolling volatility and the mean-reversion z-score are windowed aggregations over bars with ts <= chalk_now. The same definitions serve live trading.

  • One offline query per backtest

    Every ticker at every rebalance is one input row, with its input time set to the decision. The recorded run reads 36,520 rows in under 30 seconds.

  • Trade it with costs

    A Chalk scaling group turns signals into weights, trades at the open, charges basis points on turnover and reports the equity curve, Sharpe and drawdown.

More capabilities

  • Compute next to the data

    The portfolio step runs in a Chalk scaling group that authenticates with its workload identity. No client secret in the container.

  • Numbers you can trade

    The leaky backtest of the default strategy claimed 24.9% a year. Point in time, it made 11.3%, behind the benchmark's 15.0%.

Trust the Sharpe your backtest reports

Signals on bars, quotes, filings or alternative data, at any frequency. Talk to an engineer about computing them point in time, for research and production alike.