{"_id":"@calculator53295/backtest-engine","name":"@calculator53295/backtest-engine","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@calculator53295/backtest-engine","version":"0.1.0","description":"Pure TypeScript backtest and retirement-simulation engine with zero runtime dependencies: daily TWR/IRR backtesting, Monte Carlo retirement simulation, and a historical cohort engine with nine withdrawal strategies.","license":"MIT","author":{"name":"Calculator5329"},"repository":{"type":"git","url":"git+https://github.com/Calculator5329/finance-kit.git","directory":"packages/backtest-engine"},"homepage":"https://github.com/Calculator5329/finance-kit/tree/main/packages/backtest-engine#readme","bugs":{"url":"https://github.com/Calculator5329/finance-kit/issues"},"type":"module","sideEffects":false,"main":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./retirement":{"types":"./dist/retirement/index.d.ts","import":"./dist/retirement/index.js"}},"publishConfig":{"access":"public"},"scripts":{"build":"tsc --build","typecheck":"tsc -p tsconfig.test.json"},"dependencies":{},"keywords":["backtest","twr","irr","monte-carlo","retirement","withdrawal-strategies","portfolio"],"_id":"@calculator53295/backtest-engine@0.1.0","_integrity":"sha512-50KkjFBS4rw+GU9cr+iIsLh8G/qLE2xISaFZAG6DR8IUsM0p8ypXtnYP6QP2H0pvwagfl731owax8UDR4KZuCA==","_resolved":"/home/ethan/.cache/tmp/backtest-release-audit-final/calculator53295-backtest-engine-0.1.0.tgz","_from":"file:/home/ethan/.cache/tmp/backtest-release-audit-final/calculator53295-backtest-engine-0.1.0.tgz","_nodeVersion":"24.18.0","_npmVersion":"11.16.0","dist":{"integrity":"sha512-50KkjFBS4rw+GU9cr+iIsLh8G/qLE2xISaFZAG6DR8IUsM0p8ypXtnYP6QP2H0pvwagfl731owax8UDR4KZuCA==","shasum":"1e2df732aacf51bd0464aa8718aaeb3d9dd328fa","tarball":"https://registry.npmjs.org/@calculator53295/backtest-engine/-/backtest-engine-0.1.0.tgz","fileCount":87,"unpackedSize":231641,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCICsrRnrzy2Wq8Svq/tzANe7qPYlGQXOrJiIYMGA2/ox6AiEA3b2yrkFTaAiZk0wPhjWadvCxsok3SA0PRdl47Uggcl0="}]},"_npmUser":{"name":"calculator53295","email":"5329548871.eg@gmail.com"},"directories":{},"maintainers":[{"name":"calculator53295","email":"5329548871.eg@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/backtest-engine_0.1.0_1783891081695_0.19853262634630076"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-12T21:18:01.571Z","0.1.0":"2026-07-12T21:18:01.820Z","modified":"2026-07-12T21:18:02.047Z"},"maintainers":[{"name":"calculator53295","email":"5329548871.eg@gmail.com"}],"description":"Pure TypeScript backtest and retirement-simulation engine with zero runtime dependencies: daily TWR/IRR backtesting, Monte Carlo retirement simulation, and a historical cohort engine with nine withdrawal strategies.","homepage":"https://github.com/Calculator5329/finance-kit/tree/main/packages/backtest-engine#readme","keywords":["backtest","twr","irr","monte-carlo","retirement","withdrawal-strategies","portfolio"],"repository":{"type":"git","url":"git+https://github.com/Calculator5329/finance-kit.git","directory":"packages/backtest-engine"},"author":{"name":"Calculator5329"},"bugs":{"url":"https://github.com/Calculator5329/finance-kit/issues"},"license":"MIT","readme":"# @calculator53295/backtest-engine\n\nA **pure-TypeScript, zero-runtime-dependency** engine for portfolio backtesting and\nretirement simulation. Everything ships as plain functions over plain data — no\nnetwork, no filesystem, no globals — so the same inputs produce the same numbers\nin a browser, a worker, or Node.\n\nThree surfaces in one package:\n\n- **Daily backtest engine** — `runBacktest` walks a portfolio over the\n  intersection of its tickers' trading days, handling dividends (reinvested or\n  accrued as cash), splits, monthly contributions, and rebalancing. It reports a\n  time-weighted-return index (flows stripped out), a money-weighted **IRR**, plus\n  drawdown, volatility, annual/monthly/rolling returns, and per-holding\n  breakdowns. A two-asset efficient-frontier helper (`twoAssetFrontier`,\n  `minVarianceIndex`, `maxSharpeIndex`) rounds it out.\n- **Monte Carlo retirement simulator** — `runHistoricalSequence`,\n  `runBootstrap`, and `maxSafeWithdrawal` run withdrawal plans over historical\n  or block-bootstrapped real-return sequences and summarize success rate, balance\n  and income percentiles, and pay-cut risk. Everything is in **real\n  (inflation-adjusted)** dollars.\n- **Historical cohort retirement engine** (`retirement.*`) — a bucketed\n  stocks/bonds/cash simulator with glide paths, income and extra-withdrawal\n  streams, fees, and a **registry of nine withdrawal strategies** (constant\n  dollar, percent-of-portfolio, 1/N, VPW, Guyton-Klinger, CAPE-based, 95%-rule,\n  endowment, Vanguard dynamic — plus legacy adapters). `runAllCohorts` sweeps\n  every historical start date; `solveSwr` back-solves the safe withdrawal rate.\n\nA namespaced **canonical** module (`canonical.*`) provides a forward-compatible\ntype vocabulary and pure adapters that reconcile the two engine dialects lifted\ninto this repo, so consumers can share one vocabulary as the surfaces converge.\n\n## How this engine is tested\n\nThis package exists to be trusted by three production apps, so its behavior is\npinned by tests at three levels — hand-computed synthetic fixtures for exact\ncorrectness, real-market regression bands for realism, and a golden parity\nfixture that locks results to the app the code was extracted from. The engine\nitself carries **no personal financial data**: only synthetic tuples and public\nmarket/Shiller data are used as fixtures.\n\n| Fixture class | What it pins | Concrete examples (from the test suite) |\n| --- | --- | --- |\n| **Hand-computed synthetic fixtures** | Exact arithmetic of the daily engine — every value is derived by hand in the test, no tolerance beyond float epsilon. | A flat/linear price path `100 → 110 → 99` on a single asset must yield portfolio values `10 000 → 11 000 → 9 900` and `totalReturn = −0.01` (`engine/__tests__/backtest.test.ts`). A `$2` dividend where unadjusted close drops `100 → 99` yields `10 100` reinvested and `10 100` with `$200` accrued cash when not. A 2:1 split (`100 → 51`, real `+2%`) must neutralize to `10 200`. Two-asset 60/40 monthly rebalancing lands on `11 024`; TWR strips a `$1 000` monthly contribution so the index matches the no-contribution run exactly. |\n| **Real-data regression bands** | The engine reproduces known market history within approximate ranges (vendor adjustment methods differ slightly). Runs against gitignored Tiingo data; auto-skips when absent. | SPY 1994–2023 CAGR ∈ (9%, 11%); GFC max drawdown ∈ (−58%, −50%) troughing in **2009-03** with a non-null recovery; 2008 annual return ∈ (−40%, −34%) (`≈ −37%`); AAPL 2020 **4:1 split** continuity — no daily move outside (0.85×, 1.15×); a 60/40 SPY/BND mix shows lower volatility and shallower drawdown than pure SPY and clamps its range to BND's 2007 inception (`engine/__tests__/realdata.test.ts`). |\n| **Golden Shiller cohort parity** | The retirement cohort engine matches the app it was ported from, bit-for-bit. | The first 48 rows of retirement-sim's public `shiller.csv` (1871-01…1874-12) run through `runAllCohorts` must reproduce captured summary quantiles (real and nominal end balances) to 10 decimal places, with `count = 3`, `successRate = 1`, start dates `1871/1872/1873-01`, worst cohort `1872-01`, best `1871-01`, and every year-1 real withdrawal `≈ $4 000` (`retirement/__tests__/golden-cohort.test.ts`). |\n\n## Consumers\n\nOne engine, three production apps by the same author — extracting the shared math\nhere means simulation results are identical everywhere the same inputs appear,\nwith no per-app drift:\n\n- **retirement-sim** — the cohort retirement engine and withdrawal-strategy\n  registry were ported from it.\n- **Fathom** stock-analysis suite — the daily backtest and Monte Carlo engines\n  were lifted verbatim from its fixture-guarded `src/engine` + `src/montecarlo`.\n- **finance-master** — shares the same engine.\n\n## Quickstart\n\n### Daily backtest — `runBacktest`\n\n```ts\nimport { runBacktest } from \"@calculator53295/backtest-engine\";\n\nconst spy /* : TickerSeries */ = {\n  ticker: \"SPY\",\n  records: [\n    { date: \"2020-01-02\", close: 100, adjClose: 100, divCash: 0, splitFactor: 1 },\n    { date: \"2020-01-03\", close: 110, adjClose: 110, divCash: 0, splitFactor: 1 },\n    { date: \"2020-01-06\", close: 99, adjClose: 99, divCash: 0, splitFactor: 1 },\n  ],\n};\n\nconst result = runBacktest(\n  [spy],\n  { name: \"All SPY\", allocations: [{ ticker: \"SPY\", weight: 100 }] },\n  { initialAmount: 10_000, monthlyContribution: 0, rebalance: \"none\", reinvestDividends: true },\n);\n\nresult.values;         // portfolio value per trading day\nresult.metrics.cagr;   // + totalReturn, irr, volatility, drawdown, annualReturns…\nresult.twrIndex;       // flow-stripped time-weighted-return index\n```\n\n### Retirement Monte Carlo — `runHistoricalSequence` / `maxSafeWithdrawal`\n\n```ts\nimport {\n  runHistoricalSequence,\n  maxSafeWithdrawal,\n  type RealReturnSeries,\n  type SimParams,\n} from \"@calculator53295/backtest-engine\";\n\n// Real (inflation-adjusted) monthly total returns, oldest first.\nconst series: RealReturnSeries = { dates: [\"1994-01\", /* … */], returns: [0.003, /* … */] };\n\nconst params: Omit<SimParams, \"withdrawalRate\"> = {\n  initialBalance: 1_000_000,\n  strategy: \"fixedReal\",   // or \"fixedPercent\" | \"vpw\" | \"guardrails\"\n  horizonYears: 30,\n  feeRate: 0.001,\n};\n\nconst sim = runHistoricalSequence(series, { ...params, withdrawalRate: 0.04 });\nsim.successRate;              // fraction of historical cohorts that survived\nsim.percentiles.p50;          // median balance path, today's dollars\n\n// Or back-solve the highest rate meeting a 95% success target:\nconst swr = maxSafeWithdrawal(series, params, 0.95);\n```\n\n### Historical cohort engine — `retirement.simulate`\n\n```ts\nimport { retirement } from \"@calculator53295/backtest-engine\";\n// or: import { simulate, runAllCohorts } from \"@calculator53295/backtest-engine/retirement\";\n\n// [date, spReturn, bondReturn, cashReturn, cpi] → HistoricalRow[]\nconst rows: retirement.HistoricalRow[] = [\n  { date: \"1871-01\", spReturn: 0.0184, bondReturn: 0.0042, cashReturn: 0.0011, cpi: 12.4641 },\n  // …monthly public Shiller / asset-class rows…\n];\n\nconst trial = retirement.simulate(\n  {\n    startDate: \"1871-01\",\n    horizonYears: 30,\n    stepFreq: \"monthly\",\n    initialBalance: 100_000,\n    initialAllocation: { stocks: 0.6, bonds: 0.3, cash: 0.1 },\n    strategy: { kind: \"constant-dollar\", annualAmount: 40_000, inflationAdjusted: true, sourcing: \"proportional\" },\n    rebalance: \"yearly\",\n  },\n  rows,\n);\n\ntrial.endBalanceReal;           // ending balance, today's dollars\ntrial.ranOutAtPeriod;           // null if the plan survived\n\n// Sweep every start date in the dataset:\nconst { summary } = retirement.runAllCohorts({ /* config without startDate */ }, rows);\nsummary.successRate;\n```\n\nThe cohort engine takes an injectable random source (`RandomSource`) for its\nsampling helpers (e.g. `pickRandomStart`), so simulations stay deterministic and\ntestable — pass a seeded RNG like the exported `mulberry32`.\n\n## API stability\n\nThis package is **0.x**: minor releases may include breaking changes to the two\nflat engine dialects while the surfaces converge. The **`canonical`** module is\nthe forward-compatible vocabulary — prefer it for long-lived integrations. The\nexported `VERSION` constant tracks the package version.\n\n## License\n\nMIT\n","readmeFilename":"README.md","_rev":"1-739337f9df6d51ab37275e70c8ab7e48"}