ledgr is an event-sourced systematic trading research framework for R.
Use it when you want a backtest result to be more than a temporary object in an R session. ledgr starts from sealed market-data snapshots, runs strategies through an experiment boundary, records event-sourced results, and lets you reopen the evidence later.
sealed snapshot -> experiment -> run -> event ledger -> results
The setup is not overhead. The setup is the audit trail.
ledgr is research software, not investment advice. Backtests and audit trails are evidence tools; they do not predict future returns or provide compliance guarantees. See DISCLAIMER.md.
if (!requireNamespace("pak", quietly = TRUE)) install.packages("pak")
pak::pak("blechturm/ledgr")library(ledgr)
library(dplyr)
data("ledgr_demo_bars", package = "ledgr")Start with the package-owned demo bars. Real research should seal your own market data, but the demo data keeps this first run local and deterministic.
bars <- ledgr_demo_bars |>
filter(
instrument_id %in% c("DEMO_01", "DEMO_02"),
between(ts_utc, ledgr_utc("2019-01-01"), ledgr_utc("2019-06-30"))
)
bars |>
slice_head(n = 4)
#> # A tibble: 4 x 7
#> ts_utc instrument_id open high low close volume
#> <dttm> <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 2019-01-01 00:00:00 DEMO_01 89.7 91.8 89.7 91.5 468600
#> 2 2019-01-02 00:00:00 DEMO_01 91.5 91.6 91.0 91.3 438315
#> 3 2019-01-03 00:00:00 DEMO_01 91.3 92.1 89.6 90.5 576390
#> 4 2019-01-04 00:00:00 DEMO_01 90.7 91.1 89.5 89.8 458921Seal the bars, declare the strategy boundary, and run one parameter set.
snapshot <- ledgr_snapshot_from_df(
bars,
snapshot_id = "readme_demo"
)
features <- ledgr_feature_map(
fast = ledgr_ind_sma(ledgr_param("fast_n")),
slow = ledgr_ind_sma(ledgr_param("slow_n"))
)
exp <- ledgr_experiment(
snapshot = snapshot,
strategy = ledgr_demo_sma_crossover_strategy(),
features = features,
opening = ledgr_opening(cash = 10000),
cost_model = ledgr_cost_zero()
)
bt <- ledgr_run(
exp,
feature_params = list(fast_n = 10L, slow_n = 40L),
params = list(qty = 10, threshold = 0),
run_id = "readme_sma_crossover"
)
summary(bt)
#> ledgr Backtest Summary
#> ======================
#>
#> Performance Metrics:
#> Total Return: 1.07%
#> Annualized Return: 2.11%
#> Max Drawdown: -0.76%
#>
#> Risk Metrics:
#> Risk-Free Rate: 0.00% annual
#> Annualization: 252 periods/year (US equity daily)
#> Volatility (annual): 1.56%
#> Sharpe Ratio: 1.349
#>
#> Trade Statistics:
#> Total Trades: 2
#> Win Rate: 100.00%
#> Avg Trade: $53.41
#>
#> Exposure:
#> Time in Market: 59.69%The result views are derived from recorded events. The ledger is the source of truth; trades, equity, and metrics are views over that evidence.
ledgr_results(bt, what = "trades")
#> # A tibble: 2 x 9
#> event_seq ts_utc instrument_id side qty price fee realized_pnl action
#> <int> <date> <chr> <chr> <dbl> <dbl> <dbl> <dbl> <chr>
#> 1 3 2019-04-23 DEMO_01 SELL 10 102. 0 27.4 CLOSE
#> 2 4 2019-06-13 DEMO_02 SELL 10 76.5 0 79.4 CLOSE
head(ledgr_results(bt, what = "equity"), 3)
#> # A tibble: 3 x 6
#> ts_utc equity cash positions_value running_max drawdown
#> <date> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 2019-01-01 10000 10000 0 10000 0
#> 2 2019-01-02 10000 10000 0 10000 0
#> 3 2019-01-03 10000 10000 0 10000 0
head(ledgr_results(bt, what = "returns"), 3)
#> # A tibble: 3 x 3
#> ts_utc equity period_return
#> <date> <dbl> <dbl>
#> 1 2019-01-01 10000 NA
#> 2 2019-01-02 10000 0
#> 3 2019-01-03 10000 0Stored strategy provenance is inspectable without rerunning or
evaluating the strategy source. Use trust = FALSE for source and
metadata inspection.
stored_strategy <- ledgr_run_strategy(snapshot, "readme_sma_crossover", trust = FALSE)
list(
reproducibility_level = stored_strategy$reproducibility_level,
hash_verified = stored_strategy$hash_verified,
strategy_params = stored_strategy$strategy_params
)
#> $reproducibility_level
#> [1] "tier_1"
#>
#> $hash_verified
#> [1] TRUE
#>
#> $strategy_params
#> $strategy_params$qty
#> [1] 10
#>
#> $strategy_params$threshold
#> [1] 0Hash verification proves stored-text identity, not code safety. Use
trust = TRUE only when you already trust the store and intentionally
want to recover a function object.
| Question | Article |
|---|---|
| I want the full research loop: snapshot, sweep, promotion, reopen. | Research Workflow |
| I want the shortest runnable path through the package. | Quickstart |
| I want to write strategies correctly. | Strategy Development |
| I want feature maps, indicators, and active aliases. | Indicators |
| I want exploratory sweeps and candidate promotion. | Sweeps |
| I want PBO/CSCV, MinTRL, DSR, and effective-trial diagnostics. | Selection Integrity |
| I want cost and target-risk policy boundaries. | Risk And Cost |
| I want walk-forward evaluation. | Walk-Forward |
| I want sealed snapshots, durable stores, backup, and reopen. | Experiment Store |
| I want hashes, provenance tiers, and limits of recovery. | Reproducibility |
| I want fills, trades, equity, metrics, and metric context. | Metrics And Accounting |
Start with the pkgdown site for the full article set: https://blechturm.github.io/ledgr/.
Installed package help remains available from R:
help(package = "ledgr")
vignette(package = "ledgr")ledgr connects to the R finance ecosystem through adapters. The core is
narrow by design:
data -> pulse -> decision -> fill -> ledger event -> portfolio state.
Everything outside that sequence, such as data vendors, indicators,
charting, and analytics, can be provided by packages that already do
those things well.
| ledgr owns | Other packages can own |
|---|---|
| sealed snapshots and hashes | market-data acquisition |
| pulse construction and no-lookahead contexts | indicator calculations through adapters |
| target validation, target-risk transforms, fills, and ledger events | charting and visualization |
| run identity, provenance, and result reconstruction | downstream analytics and reporting |
This posture is deliberate. If you want an all-in-one charting or array-backtesting package, ledgr may not be the shortest path. Choose ledgr when you want the audit trail and adapter boundary to be explicit.
The current ledgr research API is experiment-first. It includes memory-backed
exploratory sweep support, compact saved sweeps with optional retained return
series, classed target-risk transforms, optional parallel candidate dispatch,
canonical single-run returns, retained-return panel projections, evidence-only
selection-integrity diagnostics, and a scoped
compiled_accounting_model = "spot_fifo" opt-in for memory-backed spot-asset
FIFO sweeps. Canonical R execution remains the default.
The compiled opt-in is not durable ledgr_run() integration, not a non-spot
accounting model, and not a general compiled fold core. The target-risk layer
is a target-vector transformation layer; it is not affordability enforcement,
portfolio optimization, margin, shorting or borrow policy, liquidity/capacity
modeling, OMS lifecycle behavior, or broker-grade risk control. The
selection-integrity diagnostics do not choose or promote candidates and do not
prove future profitability. ledgr does not ship ledgr_tune(),
business-objective filtering, purging/embargo/CPCV, benchmark-relative
diagnostics, broker adapters, paper trading, or live trading. Those are
separate roadmap items with different state and safety requirements.
ledgr_run() returns a live handle. The run artifacts are already
durable when the run finishes. Most result inspection opens and closes
its own read connection; explicit close(bt) is resource cleanup for
long sessions, explicit opens, and lazy result cursors.
ledgr is not yet on CRAN. Until the first CRAN release, stored artifacts, database schemas, config hashes, provenance formats, and experimental APIs may change without backward compatibility or a deprecation cycle. Treat pre-CRAN ledgr as a research/development package and expect to rerun experiments after upgrading. Once ledgr is released on CRAN, the project will define an explicit compatibility and deprecation policy.