Changelog
September 4, 2026 · View on GitHub
All notable changes to this project will be documented in this file.
The format is loosely based on Keep a Changelog, and this project adheres to Semantic Versioning.
[9.0.0a14] — 2026-09-04
Added
- Broker-native mirror stop-loss/take-profit safety net:
StopLossRule/TakeProfitRulegainmirror_on_exchange— whenTrue, a resting broker-nativeSTOPorder is placed on the exchange alongside the client-side tracked rule as soon as a trade opens, so the rule keeps working even if the bot is offline when the price crosses the trigger. The client-side check remains the primary mechanism; the mirror order is a safety net underneath it. Trade allocation/P&L accounting is deferred until the mirror order is actually observed to fill, so a merely-resting order never prematurely closes a trade. Whichever side fires first (mirror or client-side) cancels the other's resting order for that trade — including a sibling rule's mirror order, so a mirrored stop-loss and a mirrored take-profit on the same trade can never double-commit the same shares or leave one another orphaned on the exchange.OrderExecutorgains asupports_mirror_orderscapability flag (the local paper-trading executor opts out); the feature is a no-op in backtests. See the new "Broker-Native Mirror Stop-Loss / Take-Profit" page under Advanced Concepts in the docs.
Changed
trading_costsmoved offTradingStrategyontoPortfolioConfiguration/Study.execution_config— trading costs are now a property of the venue/scenario, not the strategy's signal logic, matching how they're actually resolved for live/paper trading and backtests.trading_symbolremoved fromTradingStrategy(symbolsis unaffected); all examples and docs updated accordingly.
[9.0.0a13] — 2026-09-01
Fixed
App.run_backtest()event-driven engine required a manually-registeredPortfolioConfiguration, unlike the vector engine: callingrun_backtest(..., study=study)withstudy.engines=[BacktestEngine.EVENT_DRIVEN]raisedOperationalException: No portfolios configuredeven whenstudy.universe(market + trading_symbol) andstudy.initial_capitalfully specified a portfolio — the exact sameStudyworked fine withBacktestEngine.VECTOR. The event-driven branch now builds a defaultPortfolioConfigurationfrom the Study the same way the vector branch already did, and raises a clearer, engine-specific error message if the Study still doesn't specify enough to build one.RunReport.orderssilently dropping still-open orders: orders were only included when created or updated within the current run's time window. A pending order thatcheck_pending_ordersre-fetched as identical (no field changes) issues no UPDATE, soupdated_atnever bumps and the order silently disappeared from every subsequent report until it finally changed.ordersnow also includes any order that is still pending, regardless of when it was last touched.
Added
load_backtests(): recursively loads every.obtfbundle and/or legacy backtest directory found anywhere under a directory tree (a singleos.walkpass, pruned as soon as a backtest is matched), for storage layouts that group backtests into nested subfolders (e.g. one per study/window).load_backtests_from_directory()gained a matchingrecursive=Falseparameter —load_backtests()is a thinrecursive=Trueconvenience wrapper around it.
[9.0.0a12] — 2026-08-28
Fixed
-
Downside-deviation-based metrics returning misleading zero/near-infinite values:
get_sortino_ratio,get_calmar_ratio, and theiranalyze_backtest_windowsequivalents now consistently returninfwhen there is no downside risk to divide by but the return is positive, and0.0otherwise — matching the convention already used byget_profit_factor/get_omega_ratio. Previously:get_downside_std_of_daily_returnsmeasured the dispersion among the losing periods rather than the shortfall over every period, so a strategy with similarly-sized losses could sendsortino_ratiotoward~1e14instead of a bounded, realistic value.- A run with no losing periods at all reported
sortino_ratio/calmar_ratioof a flat0.0, which the HTML report then painted as "Suboptimal", even though a profitable run with zero downside/drawdown is an excellent outcome, not a bad one.
Breaking:
sortino_ratioandcalmar_ratiovalues in reports generated before this release are not directly comparable to values generated after it.
Added
<MARKET>_OVERRIDE_INITIAL_BALANCE: closes the same deployment-override gap that<MARKET>_OVERRIDE_API_KEY/_SECRET_KEY/_PAPER_TRADING/_PAPER_TRADING_MODEalready covered —initial_balancepassed toadd_market()(including a value hardcoded in an entry script) is now replaced by this environment variable whenever it is set.
[9.0.0a11] — 2026-08-28
Fixed
- Missing
jinja2runtime dependency: the framework importsjinja2directly (HTML backtest report generation) but did not declare it as a dependency inpyproject.toml, causingModuleNotFoundError: No module named 'jinja2'in clean install environments (e.g. serverless/Lambda deployments) that don't already have it installed transitively.jinja2is now an explicit>=3.1.6runtime dependency.
[9.0.0a10] — 2026-08-28
Added
- Market-scoped deployment overrides for
add_market():<MARKET>_OVERRIDE_API_KEY,<MARKET>_OVERRIDE_SECRET_KEY,<MARKET>_OVERRIDE_PAPER_TRADING, and<MARKET>_OVERRIDE_PAPER_TRADING_MODEreplace the correspondingadd_market()argument whenever set, regardless of what was explicitly passed — intended for hosting platforms that need a guarantee that injected credentials/paper-mode settings take effect. The existing<MARKET>_API_KEYand<MARKET>_SECRET_KEYvariables keep their original, backward-compatible fallback behavior (only used when the argument isNone).marketandtrading_symbolarguments are never affected by either mechanism. GET /api/portfolios/order-costs: returns the effective order cost (fees and slippage) of the connected portfolios, supporting the same query filters as/api/portfolios.GET /api/portfolios/order-cost-specification: returns the order cost specification (the fee/slippage that would apply to a new order) for the connected portfolios.
Removed
/api/backtest-results/*endpoints and their schemas removed in favor of the portfolio order-cost endpoints above and the existing/api/orders,/api/trades, and/api/positionsendpoints, which already support backtest-scoped filtering.
Changed
TradeOrderEvaluator(and its backtest/default subclasses) andPaperTradingOrderExecutorupdated to support the new order cost overview/specification resolution used by the endpoints above.
Docs
- Corrected and restructured
Getting Started/portfolio-configuration.mdandGetting Started/credentials.mdto match currentadd_market()/PortfolioConfigurationbehavior:.envloading, credential/override precedence, initial balance semantics per trading mode, market-level fees/slippage, HEDGE position-mode support, and local paper-trading fill behavior.
[9.0.0a9] — 2026-08-27
Added
Schedule.every(interval, time_unit, anchor=None): interval schedules now fire on fixed, anchor-aligned wall-clock slots (default anchor: UNIX epoch UTC) instead oflast_run + step(). A manually/force-triggered run (request_immediate_run()) no longer shifts the next natural scheduled run — e.g. "every 2 hours" always fires at00:00,02:00, ... regardless of when the app started or a manual run happened.Schedule.is_due,next_run_after, anditer_run_times(backtest schedule generation) all derive from the same anchor-aligned slot logic, so live and backtest cadences agree.TIMEZONEapp config: alongside the existingDATETIME_FORMAT, lets logs display timestamps in a local IANA timezone (e.g."Europe/Amsterdam") via the newformat_datetime_utc()helper. Applied consistently to the algorithm-level "next run" log and the per-strategy startup log.- Event loop now logs the next scheduled run for the algorithm as a whole (the earliest next run across every registered strategy), not just per-strategy.
Fixed
- Windows CI flakiness from stale SQLite state leaking between tests:
teardown_sqlalchemy()now callsgc.collect()after disposing the engine, since ORM reference cycles could keep the underlyingsqlite3connection/file handle open afterclose_all_sessions()/engine.dispose(). Harmless on POSIX (unlinked-but-open files are still removable) but Windows' mandatory file locking then blockedshutil.rmtree()of the database directory in test teardown, leaking a contaminated database into whichever test ran next (mismatched portfolio/balance errors, missing data providers) — only reproducible on Windows runners. DATETIME_FORMATleaking into CCXT internals:CCXTOHLCVDataProvider.get_ohlcv()used to honor the app's user-facingDATETIME_FORMATconfig when building its own CCXT API request timestamps, which must stay in a fixed internal format — a custom display format (e.g."%d-%m-%Y %H:%M:%S") madeccxt.parse8601()silently returnNone, causing'<' not supported between instances of 'NoneType' and 'NoneType'.DATETIME_FORMATnow only affects logging/display; CCXT request serialization always uses the fixed internal format.AlgorithmRunner's background loop error log now includes the traceback (exc_info=True) instead of only the exception message, to aid diagnosing live/paper-trading crashes.
[9.0.0a8] — 2026-08-27
Added
RunReport.score_cards: a new top-level, flattened list of everyScoreCardrecorded viaTradingStrategy.record_score_card(...)this run — each entry carries its ownstrategy_id,symbol,summary, andentries, so a caller no longer has to dig throughreport["signals"][i]["score_cards"]to find them.
Fixed
RunReport.ordersmissed orders filled/updated after being created in a previous run: the report only included orders whosecreated_atfell inside the current run's window. An order placed in an earlier run that got filled (or otherwise changed status) during this run is now included too, matched oncreated_atorupdated_at.
[9.0.0a7] — 2026-08-27
Added
- REST API migrated from Flask to FastAPI:
create_app(web=True)now serves a FastAPI application (uvicorn) instead of Flask, with automatic Swagger docs at/docs. All controllers (portfolio, orders, positions, trades, algorithm, backtest results, run reports) ported toAPIRouter. CORS, error handling, and response serialization all updated accordingly. POST /api/algorithm/invoke: trigger an immediate, out-of-schedule strategy run on a live algorithm via the REST API, with thread-safe queueing (AlgorithmRunner.invoke_now).RunReport/App.get_last_run_report()/App.get_run_reports(): a first-class, persisted snapshot of what a run did — portfolios (now includingnet_size,realized,total_revenue,total_cost,total_net_gain,total_trade_volume), positions, orders, trades and per-tick signal outcomes, plus a newis_paperflag (true only when every configured portfolio is paper-traded). Generated automatically after every bounded run and after every live/paper iteration, not just once at process exit — including the web/live mode, which previously never produced one at all.ScoreCard/ScoreCardEntry: a portable, versioned explanation object attachable to aSignal(Signal.with_score_card(...)) or recorded independently of any signal via the newTradingStrategy.record_score_card(score_card, symbol=...), so a strategy can explain why no signal fired on a given tick, not just why one did. Flows automatically intoRunReport.signals.ExposureRule: a new portfolio-wide risk rule capping total invested value across every symbol combined (e.g. "never more than 80% invested"), enforced byApplyRiskBudgetPhasealongside the existing available-cash check. Complements the existing per-symbolPositionSize/ScalingRule.PositionSize/ScalingRuledefault-with-override: both now acceptsymbol=Noneas a default entry applied to every symbol that doesn't have its own symbol-specific entry — a symbol-specific entry always takes precedence.App.run(run_immediately_on_start=True): new flag controlling whether every strategy fires on its very first tick regardless of schedule (default, unchanged behaviour) or instead waits for its configured interval to elapse before the first run.PaperTradingMode.LOCAL/.BROKER/.AUTO: explicit control over whether paper trading always uses the framework's local, broker-agnostic simulator, always requires the broker's own sandbox/testnet, or prefers the sandbox and falls back to the local simulator.PositionMode.NETTING/.HEDGE: opt-in hedge position mode, allowing simultaneous long and short positions on the same symbol instead of netting them into one.- Extensive startup/runtime logging narration: strategy registration, data source and order-executor/portfolio-provider initialization counts, portfolio sync, next-scheduled-run times per strategy, per-tick signal/order counts, and the web API/Swagger URLs on startup.
Fixed
AlgorithmRunner.stop(persist=True): a plain process shutdown (e.g. Ctrl+C on aweb=Truerun) no longer persists a disabled control-file state — only an explicitPOST /api/algorithm/stop(orstop()withoutpersist=False) disables future runs. Previously every local Ctrl+C permanently disabled the nextpython your_script.pyinvocation.Portfolio.to_dict(): was silently missingnet_size,realized,total_revenue,total_cost,total_net_gain, andtotal_trade_volume— all real attributes on the model, just never serialized. Also fixedSQLPortfolio.__init__to accept them as optional kwargs so portfolio creation from a fullto_dict()payload doesn't raise.TradingStrategy.strategy_idclass attribute was silently ignored (always fell back to the class name), inconsistent with howalgorithm_idis resolved. Now matchesalgorithm_id's precedence: instance arg > class attribute > class name.- Two long-skipped
test_backtest_service.pytests turned out to be test-authoring bugs, not framework bugs (a wrong default value inmetadata.get('filtered_out', True), and a filter threshold below the fixture strategy's actual trade count) — fixed and un-skipped.
[9.0.0a6] — 2026-08-20
Added
App.validate(require_portfolio=True): new opt-in flag. When set toFalse,validate()only checks config,on_initializehooks, storage, and data source declarations — it skipsinitialize_services()/initialize_portfolios(), so it no longer requires a configured portfolio, market, or resolvable market credentials. Intended for sandboxes that validate a strategy's definition without a connected exchange (the default,True, is unchanged and still mirrorsrun()exactly).
[9.0.0a5] — 2026-08-20
Added
App.validate(): runs the same setuprun()performs before entering its live event loop (config,on_initializehooks, storage, algorithm resolution, data sources, services, portfolios) and then returns, without startingEventLoopService, the Flask thread, or executing any strategy iteration/order placement. Intended for tooling that has imported an entry module (e.g. one built viacreate_app()+add_strategy(...)+add_market(...)) to fail fast on configuration errors (missing portfolio, failingon_initializehook, etc.) before a realrun().
Fixed
CCXTOHLCVDataProvider.get_ohlcv: fetching a date range with zero returned candles (e.g. a gap before a symbol's listing date) crashed withpolars.exceptions.SchemaError: invalid series dtype: expected String, got null for series with name Datetimeinstead of returning an empty frame, because the OHLCVDataFramewas built with column names only and no dtypes, so Polars inferredNullfor an emptydatalist. An explicit schema is now passed so empty results are typed correctly.- Reduced unrelated console noise from expected-exception test paths (pipeline evaluation errors,
vector pipeline injection errors, trade-hook dispatch errors) by capturing their logged
tracebacks with
assertLogsinstead of letting them print during test runs.
[9.0.0a4] — 2026-08-19
Added
- Combined multi-strategy event-driven backtests (
app/app.py):run_backtest(algorithm=...)now runs every strategy on the givenAlgorithmTOGETHER, sharing one portfolio, in ONEBacktest— the backtest-mode equivalent of howapp.run()executes multiple strategies live. Raises a clearOperationalExceptionif it resolves to the vector engine (not supported for combined multi-strategy backtests yet). algorithms=(independent Algorithms) on bothrun_backtest/run_backtests: each Algorithm runs on its own portfolio, yielding its own Backtest — the Algorithm-level equivalent ofstrategies=. Only supported by the event-driven engine.strategy_idattribution onOrder/Trade: new DB column (auto-migrated via_apply_forward_only_migrations()) plus a newContext._attach_strategy_attribution()/EventLoopServicewiring that stampsstrategy_idon every order/trade created during a tick, so combined multi-strategy backtests (and live runs) can be broken down per strategy. Exposed on theOrderSerializer/TradeSerializer/BacktestRunOrderSerializer/BacktestRunTradeSerializer.ipythonoptional extra (pip install investing-algorithm-framework[notebook]) for the%%backtestJupyter magic — previously undeclared, see Fixed below.
Changed — Breaking: consolidated backtest API around Study
App.run_vector_backtest/App.run_vector_backtestsare removed.App.run_backtest/App.run_backtestsare now the only two backtest entry points; both acceptstrategy=/strategies=/algorithm=/algorithms=(exactly one) and a requiredstudy=Study(...), and auto-detect the engine (vector vs event-driven) from whether the strategy overridesgenerate_signal_series— unlessStudy(engines=[BacktestEngine.VECTOR/EVENT_DRIVEN])is set explicitly.- The old backward-compat
backtest_date_range=/backtest_date_ranges=/market=/trading_symbol=/initial_amount=kwargs are removed fromrun_backtest/run_backtests— useStudy(universe=Universe(market=..., trading_symbol=...), initial_capital=..., backtest_windows=[BacktestWindow(train_range=...)])instead.run_backtest/run_backtestsnow always returnList[Backtest](previously returned a singleBacktestwhen called with the oldbacktest_date_range=shim). window_part(which part of eachStudy.backtest_windowsentry to run) is now purely aStudyattribute (Study(window_part=...)) — the redundant call-time override parameter was removed from both methods.- Consolidated three separate post-run stamping helpers (
_apply_study_fields,_apply_universes,_apply_backtest_windows) into one_apply_study_to_backtests(backtests, study, ...)that takes theStudyobject directly and does a single re-save pass instead of three.
Changed — legacy strategy protocol removed
- The dead, never-called pre-v9.0
generate_buy_signals/generate_sell_signalsprotocol has been fully removed from the test suite, examples, and documentation. Strategies must implementgenerate_signals(event-driven) and/orgenerate_signal_series(vector) — seedocs/architecture/strategy/strategy.mdand thestrategies.mdguide for the current API.
Fixed
- Release-breaking bug:
import investing_algorithm_frameworkcrashed on a clean install without the (correctly optional, per 9.0.0a3)boto3/azure-*/ipythonpackages. The dependency-injector wiring step (container.wire(packages=["investing_algorithm_framework"]), run by everycreate_app()) auto-imports every submodule in the package to discover@injectusages, which forced eager top-level imports ofboto3(cli/deploy_to_aws_lambda.py), the Azure SDK (cli/deploy_to_azure_function.py), andIPython(notebook/magic.py, re-exported from the top-level__init__.py) regardless of whether those features were ever used. All three are now guarded withtry/except ImportError, with a clear error raised only at the point of actual use (the AWS Lambda / Azure Function deploy CLI commands) instead of at import time. Verified in a from-scratch venv with zero extras installed. - Docusaurus documentation (
vector-backtesting.md,backtesting.md,backtest-storage.md,backtest-reports.md,metrics.md,strategies.md,simple-example.md, and others) andexamples/scripts updated to match the consolidatedrun_backtest/run_backtests+StudyAPI and thegenerate_signals/generate_signal_seriesprotocol.
[9.0.0a3] — 2026-08-18
Added
- Web API: algorithm control and insights (
app/web/controllers/algorithm.py)GET /api/algorithm/status— run status plus the persisted enabled/disabled control state.POST /api/algorithm/start/POST /api/algorithm/stop(?wait=true,?reason=...) — start/stop the live event loop. The enabled/disabled flag is persisted to the resource directory (and pushed immediately via a configuredStateHandler), so stateless deployments (AWS Lambda, Azure Functions) honor it on their next scheduled invocation too. NewApp.start_algorithm()/App.stop_algorithm()/App.is_algorithm_enabled()/App.get_algorithm_control_state()expose the same control directly to serverless handler code, without needing Flask at all.GET /api/algorithm/insights— equity curve, drawdown series, max drawdown, win rate, Sharpe ratio, trade/portfolio counts, computed from live data.
- Web API:
GET /api/trades/GET /api/trades/count— trades previously had no REST endpoint at all, unlike orders/positions/portfolios. - Wired the new algorithm start/stop/status controls into the AWS Lambda and
Azure Function project scaffolds (
iaf init --type aws_lambda|azure_function).
Fixed
- Real bug in
EventLoopService.start()(app/eventloop.py): the live/ unbounded loop (noschedule, nonumber_of_iterations) ran exactly one iteration and returned instead of looping indefinitely as documented. Fixed by wrapping it in awhileloop gated on a new stop flag (request_stop()/reset_stop()), which is also what makes the new start/stop API actually work.
Changed — reduced default install footprint
- Removed
plotlyas a core dependency; all backtest-report charts now render viafinterion-charts(lighter, no bundled JS payload) instead. - Removed
jupyterfrom core dependencies (never imported at runtime; moved to thedevdependency group). - Removed the unused
Flask-Migratedependency (and its transitivealembic/Flask-SQLAlchemy/Mako). boto3and the Azure SDK packages (azure-storage-blob,azure-identity,azure-mgmt-*) are now optional extras (pip install investing-algorithm-framework[aws]/[azure]) instead of hard dependencies — they were previously imported eagerly at package-import time even for users who never touch cloud state storage.
[9.0.0a2] — 2026-08-10
Fixed
BacktestReportmulti-study bundles (app/reporting/backtest_report.py)BacktestReport/BacktestReport.openaccept a newstudy=argument (a study name orStudyinstance) to scope a report to a single study on a multi-study bundle. Previously, opening a report over a bundle with more than one study always raisedOperationalException: Backtest has N studies — pass study= to disambiguate, with no way to pass astudy=throughBacktestReportitself.- Without an explicit
study=, every study on a bundle is now rendered as its own strategy entry (labelled with the study name) instead of raising. - Progressively-pruned strategies (via a
window_filter_function) are now labelled "Pruned after<window>" in the Window Coverage panel, using thefiltered_out/filtered_out_at_date_rangemetadata the backtest service already persists on the bundle.
- Yearly returns in the HTML report (#597)
- Fixed a scale mismatch where
_build_run_data()'s yearly-returns series embedded raw decimal ratios (e.g.0.358) while the chart renders values as already-scaled percentages, showing+0.4%instead of+35.8%. - Fixed
get_yearly_returns()(services/metrics/returns.py) silently dropping a backtest's first calendar year:shift(1)has no prior year-end for the first row, so it wasNaNand removed bydropna(). The first year's return is now anchored to the initial portfolio snapshot when more than one snapshot exists in that year.
- Fixed a scale mismatch where
[9.0.0a1] — 2026-05-28
Headline
v9.0.0a1 makes the framework dual-engine native. Every Backtest
object now carries an independent vector and event slot — runs,
summary metrics, and on-disk layout are all engine-scoped. The
.iafbt bundle format is bumped to v3 with a nested envelope and
namespaced metric blobs (vector_runs/... / event_runs/...).
This release is backwards compatible at read time: v1, v2 bundles
and pre-v9.0 directory layouts still open. Writers always emit v3.
See docs/migration-v8-to-v9.md for the
upgrade path.
Added
Backtestdual-engine API (domain/backtesting/backtest.py)- Fields
vector_runs,vector_summary,event_runs,event_summaryreplace the singlebacktest_runs/backtest_summary. engines()returns engines with any populated slot (runs OR summary),get_runs(engine)/get_summary(engine)select per engine,get_all_backtest_runs()returns vector + event concat.index_rows(bundle_path=...)yields oneBacktestIndexRowper populated engine;index_row(...)is a singular shim.regenerate_summaries()rebuilds both engine summaries from current run lists in place.
- Fields
- Bundle format v3 (
domain/backtesting/bundle.py)- Nested
vector/eventslot dicts inside the envelope, each holding its ownrunslist andsummary. - Metric blobs are namespaced as
<engine>_runs/<i>/metrics/<field>.parquet. peek_bundle_format_version(path)peeks the 8-byte header (magicIAFB+ uint32 LE version) without decoding the body.
- Nested
- Merge-on-save in
save_bundle(..., merge=True)(default on). Engines absent from the in-memory backtest are restored from the on-disk envelope so writing one engine never erases the other. Atomic write:<target>.tmp.<pid>+ fsync +os.replace+ dir fsync. Setmerge=Falsefor legacy overwrite semantics. - Engine-scoped ranking & CLI
analysis.ranking.rank_results(..., engine="vector")filters backtests that lack the requested engine.iaf list --engine vector|eventandiaf rank --engine ...Click options.- SQLite index schema v3: composite primary key
(bundle_path, engine_type), automatic migration from v2 withCOALESCE(engine_type, 'vector')on NULL rows.
- HTML report per-engine pages (
app/reporting/backtest_report.py)- Dual-engine bundles render as two strategy entries with name
suffix
(vector)/(event); single-engine reports are unchanged.
- Dual-engine bundles render as two strategy entries with name
suffix
iaf migrate-bundles --to v3 <dir>CLI- In-place upgrade of v1/v2 bundles → v3 and legacy directory
layouts →
.iafbt. Usespeek_bundle_format_versionto skip bundles already at the target version cheaply. Supports--keep-source,--no-index,--dry-run,--workers.
- In-place upgrade of v1/v2 bundles → v3 and legacy directory
layouts →
Changed
BUNDLE_FORMAT_VERSION = 3. Writers emit v3 only; passingformat_version=1or2tosave_bundleraisesValueError.- Directory
Backtest.save()writes per-engine subdirectoriesvector_runs//event_runs/and per-engine summary filesvector_summary.json/event_summary.json.Backtest.open()reads both the new layout AND pre-v9.0runs/+summary.json(legacy data is routed into the vector slot). combine_backtests()now concatenates per engine (vector with vector, event with event).migrate_backtestsdirectory discovery accepts both legacyruns/and per-enginevector_runs//event_runs/layouts.
Compatibility shims (legacy callers)
Backtest.backtest_runs,Backtest.backtest_summary,Backtest.engine_typeremain as read/write properties that delegate to the vector slot.Backtest(..., backtest_runs=..., backtest_summary=..., engine_type=...)keyword arguments are accepted and routed viaengine="vector"default.
Removed
- v1 / v2 bundle writers. v9.0 bundles are written as v3 only.
- (Internal)
Backtest.scalar_summary()— useget_summary(engine)followed by.to_dict().
Migration
# Upgrade an entire results directory in place — bundles AND legacy dirs.
iaf migrate-bundles --to v3 ./backtest_results
See docs/migration-v8-to-v9.md for a
detailed walkthrough.