V0 Changelog#
0.61.1 (2026-07-28)#
Fixed#
The summary sheet’s statistics table now shows one column per configured benchmark (labeled by ticker) instead of a single
Benchmarkcolumn, so a multi-benchmark run ("SPY,QQQ") compares the portfolio against every benchmark side by side — matching thebenchmark_comparisonsheet. Portfolio-relative metrics (Alpha, Information Ratio, Tracking Error) remainN/Ain the benchmark columns, and a single-benchmark run is unchanged except that the column is now labeled with the ticker.The stats table keeps exactly two empty columns of separation from the annual-returns table; the gap is now computed from the stats-table width so it stays correct as benchmark columns are added.
The portfolio-info block (Start Date / End Date / Years) is placed below the stats table instead of at a fixed row 18. It previously overlapped the last two metric rows (
Max Drawdown Date,Alpha), hiding them; both rows are now visible, and an extra benchmark column no longer collides with the info block.
0.61.0 (2026-07-28)#
Added#
Multiple benchmarks.
benchmark_file_namenow accepts a comma-separated list (e.g."SPY,QQQ,IWM"); the first entry is the primary (comparison) benchmark used for Alpha / Information Ratio / Tracking Error, and every listed benchmark is built and carried through the pipeline. NewConfiguration.get_benchmark_file_names()/get_primary_benchmark()(backed by the module-levelsplit_benchmark_file_names) andDataPipelineResult.get_benchmarks()expose the full set additively, while the singularbenchmarkremains the primary for backward compatibility. A single ticker behaves exactly as before.New performance metrics. PnL (
End Balance − Start Balance), Information Ratio (mean(active) / std(active) × √252) and Tracking Error (annualized sample std of the active return), computed against the primary benchmark and surfaced in both the Excel report and the dashboard.Multi-benchmark Excel report. A dedicated
benchmark_comparisonmatrix sheet (metric × [Portfolio, benchmarks…]); the performance/drawdown chart draws one value line and one drawdown fill per benchmark; theportfolio_bench_total_valuesheet gains a<ticker>_Value/<ticker>_Returnscolumn pair per benchmark; and the annual-returns table carries one column set per benchmark.Multi-benchmark dashboard. The performance chart, the statistics table (now a metric × [Portfolio, benchmarks…] matrix), the drawdown tab (one fill per benchmark) and the annual-returns tab (one column set per benchmark) all show every configured benchmark. A single benchmark renders exactly as before.
Multi-benchmark batch runs. A comma-separated
benchmark_file_nameinglobal_defaultsor a per-portfolio override no longer breaks a batch:SharedDataPoolloads and builds every benchmark, and the ticker-collection pass expands each one. The per-portfolio report labels use the primary benchmark.
Changed#
~44× faster ``run()`` on the bundled 110-ticker × 2836-day dataset (≈17 s → ≈0.4 s), verified bit-exact against the previous engine via the side-by-side A/B test (rebalancing outputs —
Commission_df,Shares_df,orders_df,Total_commissions— identical; daily valuation within float tolerancertol=1e-9, atol=1e-6):New numpy-backed
PyArrowBacktesterFastpre-converts the three price tables to densefloat64matrices once in__init__, turning each per-ticker/day price lookup into an O(1) array index instead of a PyArrow filter-and-extract (Opt#1).run()now loops over rebalances and computes each constant-state window’s daily snapshots with a single matrix operation, cutting the Python loop from ~2500 days to ~40 rebalances (Opt#2).ExecutionBroker.execute_rebalance_vectorizedcomputes every target ticker’s diff vectorized and only iterates the tickers that actually trade, reusing the existing commission/slippage models (bit-exact).main(),MultiPortfolioBacktesterandBacktestVariationRunnerinstantiate the fast engine in production; the originalPyArrowBacktesterand the A/B test are kept as the regression baseline.
Fixed#
Annual-returns table (Excel + dashboard): it showed a single, mislabeled
"SPY,QQQ_return"column set — the rawbenchmark_file_namewas used as the column prefix and only the primary benchmark was merged — and carried a junkyear_numbercolumn. It now shows one correctly-labeled column set per benchmark (per-year CAGR and alpha computed exactly as before) and dropsyear_number; single-benchmark output is bit-identical.The
benchmark_comparisonmatrix sheet now formats its values by metric (currency / percentage / ratio), matching the summary statistics table, instead of writing raw numbers. The generic data-sheet writer had derived each cell’s format from the column name, which on this sheet is a ticker that matches no metric.
0.60.0 (2026-07-27)#
Fixed#
Daily portfolio valuation now uses the configured
mark_to_market_price_columninstead of the execution price.PyArrowBacktester._record_daily_statuswas passing_get_execution_price_for_tickerto bothcalculate_portfolio_valueandcalculate_weights, so the mark-to-market price table was loaded and filtered on every trading day but never read — themark_to_market_price_columnsetting had no effect on any result. A new_get_mark_to_market_price_for_tickergetter is now used for the daily snapshot. Trade execution, commission sizing, and the available-capital calculation continue to use the execution price, soCommission_df,Shares_df,orders_df, andTotal_commissionsare bit-exact unchanged. This changes the numerical results of existing backtests —Total_Portfolio_Value,Portfolio_Value,Daily_Weights,Returns, and all derived metrics (Sharpe ratio, max drawdown, annualized return,final_total_portfolio_value) shift whenever the mark-to-market column differs from the execution column (e.g. a dividend-and-split-adjusted series vs a split-only-adjusted one).
Changed#
Performance and memory, with no change to results (verified bit-exact against the pre-change engine on the bundled dataset —
Register_df,Daily_Weights,Commission_df,Shares_df,orders_df, andTotal_commissionsall identical):Removed the per-date
_all_prices_cachefromPyArrowBacktester. The main loop visits each trading day exactly once, so the cache never registered a hit; it only accumulated one filtered copy of all three price tables per day, holding ~64 MB of PyArrow buffers alive for the entire run on a 110-ticker × 2836-day backtest (peak memory dropped ~22%)._get_all_prices_for_datenow builds the day’s tables fresh and lets them be freed once the day is processed.PortfolioStateManager.calculate_weightsaccepts an optional pre-computedtotal_value._record_daily_statusnow passes the value it already computed for the daily snapshot instead of havingcalculate_weightsrecompute the full portfolio valuation a second time per day.
0.59.0 (2026-06-03)#
Added#
Basis-point commission model for crypto and other notional-based fee schedules. New
commission_modelselector (per_share|basis_points) andcommission_basis_pointsparameters in the ExcelGeneralsheet (and in batchglobal_defaults/ template / portfolio overrides), validated by the newBasisPointsvalue object (range 0–1000 bps) andCommissionModelTypeenum. The basis-points model prices against the real trade notional via the trade price (1 bp = 0.01%, so 100 = 1%). The default remainsper_share, preserving existing behavior. Model selection is dispatched by the newbuild_commission_model()factory.
Changed#
Breaking:
CommissionModelInterface.calculate(shares)is nowcalculate(context), receiving a newCommissionContext(shares + price) value object. All built-in commission models were updated; the unusedPercentageCommission.calculate_with_price()method andnotional_per_shareparameter were removed. Custom external commission models must update theircalculatesignature accordingly.Excel
parameters_format_versionbumped to0.57.0. Configuration files using an older format version are rejected with an “outdated format” message (runkaxanuk.backtest_engine update excelto get the latest template).
Documentation#
Removed obsolete references to
execution_pricefrom the End User Manual (configuration.rst,excel_workflow.rst,running_from_python.rst). The field was removed in v0.57.0 and is now rejected byBatchConfigParsersince v0.58.5; users following the manual no longer see it as a valid parameter.use_cases.rstnow points to the publicKaxaNuk/Backtest-Engine-Use-Casesrepository as the single source of executable examples, mirroring the pattern used by the Data Curator project.end_user_manual/index.rstexpanded with an Excel-vs-Python “Choose your workflow” table that helps a new reader pick the right execution path.Main
index.rstgained a “What’s new in 0.58.x” section with a TL;DR of breaking changes and headline fixes.
0.58.6 (2026-05-19)#
Fixed#
generate_comprehensive_excel_reportnow restores thedateindex column on theportfolio_bench_total_valuesheet.pd.concat([register_df, benchmark[...]], axis=1)was silently droppingindex.namebecause the two source DataFrames carried different names (register_df.index.name == "date"vsbenchmark.index.name == "date_column"fromStandardField.DATE.value). Without a name,include_index = dataframe.index.name is not NoneevaluatedFalseand the writer skipped both the header and every date cell, leaving the sheet with only the value columns. The concat result is now explicitly renamed to"date", mirroring the other data sheets.
0.58.5 (2026-05-13)#
Changed#
BatchConfigParser.parse_dict()/parse_file()now rejectexecution_priceinglobal_defaultswith aBatchConfigErrorpointing touser_column_trade_execution_price. The key was silently ignored since v0.57.0 (whenexecution_pricewas removed from the Excel configuration and order execution was wired toStandardField.TRADE_EXECUTION_PRICEexclusively), which let users believe they were configuring the execution price via the batch dict/YAML when in fact onlyuser_column_trade_execution_pricewas being read. Surfacing the error closes that hidden mismatch and makes the batch path consistent with the Excel path.
0.58.4 (2026-05-13)#
Changed#
MultiPortfolioRunner.run()now logs the empty-batch failure vialogger.error(...)before raisingNoPortfoliosDiscoveredError. The error message appears as a single[ERROR]line in the configured logger format (matching the surrounding[INFO] Discovered 0 portfolio files ...line) instead of only surfacing as a raw Python traceback when callers invoke the runner directly without a top-leveltry/except BacktestErrorwrapper like the one inbacktest_engine.main().
0.58.3 (2026-05-13)#
Added#
NoPortfoliosDiscoveredErrorexception (subclass ofBatchConfigError→ConfigurationError) exported fromkaxanuk.backtest_engine.exceptions.
Fixed#
MultiPortfolioRunner.run()no longer crashes with an opaqueIndexError: list index out of rangeinside_build_reference_configurationwhenportfolio_directoryis configured but resolves to zero portfolios (empty directory, missing directory, or no files matching the expected format). It now raisesNoPortfoliosDiscoveredErrorwith a message that includes the scanned directory and the expected format.
0.58.2 (2026-05-12)#
Fixed#
MultiPortfolioBacktester.generate_individual_reportsnow sets the benchmarkDatetimeIndexfrom the canonicalStandardField.DATEcolumn (currently"date_column") instead of the literal string"date"that does not exist in the schema. The old lookup silently fell through and calledpd.to_datetimeon the integer RangeIndex (0, 1, 2, …), producing timestamps near 1970-01-01 that propagated to every annual-returns row, the Max Drawdown Date of the benchmark, and the embedded chart. Annual returns and Max Drawdown Date for the benchmark now align with the portfolio’s date range. Same fix applied to theregister_dflookup for symmetry.
0.58.1 (2026-05-12)#
Fixed#
portfolio_drawdown_viznow picks thedate_columnfrom the benchmark DataFrame when present, instead of relying on its index. The previous 0.58.0 fix coerced the index to datetime, but when the benchmark comes from apa.Table.to_pandas()the index is a plain RangeIndex (0, 1, 2, …) andpd.to_datetimeinterpreted those integers as nanoseconds-since-epoch, leaving every point near 1970-01-01 — the visible vertical line in the embedded Excel charts.
0.58.0 (2026-05-12)#
Breaking Changes#
Renamed environment variable
KNPC_API_KEY_KAXANUK→KNBE_API_KEY_KAXANUK. Update yourConfig/.envfile accordingly.
Added#
arrow_to_pandas_for_analysis()helper inkaxanuk.backtest_engine.modules.type_converters. Converts apyarrow.Tableto apandas.DataFramewith analysis-friendly dtypes:decimal128/decimal256columns are coerced tofloat64anddate32/timestampcolumns todatetime64[ns]. Use it when feeding a backtest’s PyArrow result into matplotlib, numpy or scikit-learn.
Fixed#
portfolio_drawdown_vizno longer renders a spurious vertical line at 1970-01-01 when the caller passes a benchmark DataFrame that originated from apa.Table.to_pandas()(object dtype withdatetime.dateindex anddecimal.Decimalvalues). The function now coerces the index and the plotted columns defensively before drawing.
Documentation#
Full Sphinx documentation overhaul: real runtime dependencies in
docs/requirements.txt(no moreMagicMockin signatures), newBacktest Componentssection coveringcost_models,enums,execution,interfacesandorchestratorssubpackages, enriched docstrings across thebacktestpackage and the data/portfolio/market pipelines, CLI page rebuilt with intro + summary table + common workflows, andSee Alsocross-references from load methods to the user guide. Build now finishes with 0 warnings.Fixed two malformed
.. deprecated::directives inportfolio_configuration_service.pyso the rendered notice reads “Deprecated since version 0.40.0: Use load_from_file() instead.” instead of the previous garbled output.load_portfolio_from_nameandload_portfolio_from_weightsare excluded from the Sphinx API reference (still callable from Python for backwards compatibility).
0.57.0 (2026-04-15)#
Breaking Changes#
Removed ``execution_price`` Excel configuration field. Order execution now always uses the column named in
trade_execution_price_column(internally mapped toStandardField.TRADE_EXECUTION_PRICE). To migrate, runkaxanuk.backtest_engine update exceland delete theexecution_pricerow from your existingConfig/backtest_engine_parameters.xlsxif you updated manually.Renamed environment variable
KAXANUK_LICENSE_KEY→KNPC_API_KEY_KAXANUK. Update yourConfig/.envfile accordingly.``MarketDataBundlePyarrow.get_execution_table()`` signature changed. The method no longer accepts an
execution_priceparameter; it returns the TRADE_EXECUTION_PRICE table directly.Excel template format version bumped from
0.54.0to0.56.0. The engine enforces this check at startup — outdated Excels must be regenerated viakaxanuk.backtest_engine update excel.Minimum rebalance dates relaxed from 2 → 1. Users relying on the previous validation for error detection should revisit their portfolio files.
Added#
__parameters_format_version__constant exported fromkaxanuk.backtest_enginefor tracking Excel schema version independently of the package version.Explicit version-compatibility check at Excel load time: raises
ConfigurationHandlerErrorwith a “run update excel” hint when the Excel format is outdated, and logs a warning when the Excel is newer than the engine supports.Support for buy-and-hold portfolios: a single rebalance date is now valid.
Validation preventing any price column (
commission_price_column,trade_execution_price_column,mark_to_market_price_column) from being set to the same value asuser_column_date.packagingadded as an explicit dependency for semantic version comparison.
Changed#
__version__bumped to0.57.0,__parameters_format_version__bumped to0.56.0.CLI module (
services/cli.py) aligned stylistically with sibling packages: added-> Nonereturn annotations, periods to docstrings, removed dead commented code.dependenciesinpyproject.tomlsorted alphabetically (case-insensitive).
Fixed#
Duplicate log output when
ExcelConfiguratorwas instantiated in a session that already had a root logger configured (logger.propagate = Falseand handler-deduplication guard added).
0.55.0 (2026-04-01)#
Added#
``load_config_env()`` helper that loads
Config/.envinto the process environment, eliminating the need to manuallyset KNPC_API_KEY_KAXANUK=…in the terminal before every runConfig/.envtemplate generated automatically byinit excelwith a placeholder for the license keypython-dotenvdependency for.envfile loadingload_config_envexported at the top-level package namespace (from kaxanuk.backtest_engine import load_config_env)
Changed#
Template
__main__.pynow callsload_config_env()before any license-validated codeLicense error message updated to recommend
Config/.envas the primary configuration methodREADME examples updated to include
load_config_env()call
0.54.0 (2026-03-31)#
Added#
Runtime license validation (phone-home) to protect against unauthorized use
license_validatorservice with device fingerprinting, local cache (24h TTL), and 7-day offline grace periodLicense validation at all entry points:
__main__.py,main(), andPyArrowBacktester.run()Custom exception hierarchy:
LicenseError,LicenseNotFoundError,LicenseValidationError,LicenseServerErrorHMAC-signed local cache file (
~/.kaxanuk_cache) to prevent tamperingIn-process validation flag to avoid redundant server calls within the same session
httpxdependency for license server communication
Changed#
.gitignoreupdated to exclude.kaxanuk_licenseand.kaxanuk_cachefiles
0.52.0 (2026-03-04)#
Added#
Batch backtesting capability for running multiple portfolios in a single execution
BatchRunnerorchestrator withSharedDataPoolfor efficient market data reuse across portfoliosBatchConfigParserfor dict/YAML-based batch configurationDirectory scanning mode to auto-discover portfolio files
Comparative Excel report ranking portfolios by configurable metric
Individual Excel reports per portfolio with:
Full performance metrics
Drawdown analysis
Annual returns breakdown
Benchmark statistics computation in batch processing
Alpha calculation in batch path
Changed#
PyArrow Decimal-to-float coercion at numpy math operation boundaries for compatibility
0.51.0 (2026-02-18)#
Added#
BacktestSuiteclass withadd_variation()method to define configuration overrides per variation“auto” date derivation from portfolio rebalancing dates
Comparative Excel reports with:
Summary metrics across all variations
Cumulative returns comparison
Drawdown comparison charts
Fixed#
Alpha metric calculation bug
CAGR (Compound Annual Growth Rate) metric calculation bug
0.50.0 (2026-01-16)#
Added#
New PyArrow-based backtesting engine (backtest_2) with component-oriented design:
Explicit PortfolioStateManager, ExecutionBroker, and ReportingEngine components
Pluggable commission and slippage models via interfaces
Immutable BacktestReport dataclass as structured result container
Clear separation between configuration (inputs) and runtime state
Ability to run multiple backtest cases (different parameters) with only one data pipeline
Fixed#
Summary card alpha now uses the same calculation as annual returns table, ensuring consistent alpha display across dashboards
0.49.0 (2025-11-25)#
Added#
StandardFieldenum for type-safe field names (VWAP, ADJUSTED_VWAP, ADJUSTED_CLOSE, DATE)Long-short portfolio support with
allow_shortparameter in PortfolioEntityPortfolioEntity.from_dict()class method for creating portfolios from dictionaries
Changed#
Portfolio validation now supports both long-only and long-short strategies
README documentation updated
Removed#
Unnecessary configuration entity fields
0.48.0 (2025-10-30)#
Added#
Annual returns table to Excel report
Annual returns analysis to dashboard
Alpha metric to annual returns table (both dashboard and Excel report)
Fixed#
Performance metrics formatting
Dashboard alpha metric format display
0.47.0 (2025-10-15)#
Added#
Helper functions module (
input_handlers_helpers) for common data processing operationsInteractive dropdown menu in portfolio weights evolution chart to filter tickers
Comprehensive docstrings to almost all codes
Changed#
ExcelPortfolioInputHandler now returns
pa.Tableinstead ofpd.DataFrameDashboard pie chart now displays Top 10 assets alphabetically with “Others” category for remaining positions
Benchmark builder now uses
ADJ_CLOSE_COLUMN_NAMEconstant instead of hardcoded stringsExcel portfolio validates first column must be ‘Ticker’
0.46.0 (2025-10-06)#
Added#
MissingBenchmarkexception and benchmark validation before processingBenchmark availability validation (
_validate_benchmark_availability())
Changed#
Backtest engine optimized (converted class parameters to Python float for better performance)
Portfolio transformer now includes all ticker weights (not just >0)
Increased decimal precision for financial calculations
Fixed#
Portfolio calculation bugs
_convert_df_to_python()now properly handles None/NaN values (converts to 0.0)
0.45.0 (2025-10-02)#
Added#
Execution price selection option (customizable execution price for backtesting)
safe_decimal()function for PyArrow Decimal conversions_convert_df_to_python()method for converting PyArrow scalars to Python values
Changed#
Migrated data handling from Pandas to PyArrow
Build system changed from
pdm-backendtohatchling
Fixed#
Input handler and Entities bugs
Removed#
Unnecessary project directories and files
0.44.0 (2025-08-27)#
Added#
Project templates for easier setup (
__main__.pytemplate for CLI init).gitkeepfiles for empty directories
Changed#
Rebalancing logic improvements
CLI interface updates and improvements
pdm.locknow excluded from version control
Fixed#
CLI bugs
0.43.0 (2025-08-12)#
Added#
Benchmark name displayed in plot legends
Sortino ratio calculation with robust downside deviation methodology
Changed#
Dashboard color scheme updated to company brand colors
Interactive pie chart now updates based on selected date
Fixed#
Functions for CVaR and VaR calculations with robust historical and parametric Gaussian methods (including Cornish-Fisher modification) were fixed
0.42.0 (2025-07-29)#
Added#
Broker interfaces implementation
Config factory as alternative to Excel configuration
PyArrow-based portfolio entity
Custom exceptions for better error handling
Mixed date format support
New ‘portfolio’ Excel report sheet added
0.41.0 (2025-06-17)#
Added#
Portfolio weights evolution plot
Custom market data column names support
Changed#
Market data builder updated to support custom column names
Fixed#
Annualized return calculation
CAGR calculation
Alpha metric calculation
0.40.1 (2025-06-04)#
Fixed#
OS Error on cli
init scriptbecause of entry script template missing from wheel data
0.40.0 (2025-06-04)#
Changed#
First public release, now on PyPI