Skip to contents

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.

Install

if (!requireNamespace("pak", quietly = TRUE)) install.packages("pak")
pak::pak("blechturm/ledgr")
library(ledgr)
library(dplyr)

data("ledgr_demo_bars", package = "ledgr")

Run A Small Backtest

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 458921

Seal 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%

Inspect The Evidence

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             0

Stored 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] 0

Hash 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.

Where To Go Next

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")

Ecosystem

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.

Scope

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.

Pre-CRAN Compatibility

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.