py-beacon-kit 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- py_beacon_kit-0.1.0/.gitignore +98 -0
- py_beacon_kit-0.1.0/CHANGELOG.md +39 -0
- py_beacon_kit-0.1.0/LICENSE.txt +21 -0
- py_beacon_kit-0.1.0/PKG-INFO +503 -0
- py_beacon_kit-0.1.0/README.md +427 -0
- py_beacon_kit-0.1.0/examples/01_index_and_backtest.ipynb +526 -0
- py_beacon_kit-0.1.0/examples/02_backtest_analysis.ipynb +415 -0
- py_beacon_kit-0.1.0/examples/03_index_futures.ipynb +409 -0
- py_beacon_kit-0.1.0/examples/04_optimised_index.ipynb +420 -0
- py_beacon_kit-0.1.0/examples/05_optimised_backtest.ipynb +419 -0
- py_beacon_kit-0.1.0/examples/README.md +46 -0
- py_beacon_kit-0.1.0/examples/data/templates/equities_corp_actions_data.xlsx +0 -0
- py_beacon_kit-0.1.0/examples/data/templates/equities_fundamental_data.xlsx +0 -0
- py_beacon_kit-0.1.0/examples/data/templates/equities_market_data.xlsx +0 -0
- py_beacon_kit-0.1.0/examples/data/templates/equities_reference_data.xlsx +0 -0
- py_beacon_kit-0.1.0/examples/data/templates/fx_rates_data.xlsx +0 -0
- py_beacon_kit-0.1.0/pyproject.toml +238 -0
- py_beacon_kit-0.1.0/src/beacon/__init__.py +25 -0
- py_beacon_kit-0.1.0/src/beacon/_optional.py +58 -0
- py_beacon_kit-0.1.0/src/beacon/analysis/__init__.py +72 -0
- py_beacon_kit-0.1.0/src/beacon/analysis/attribution.py +433 -0
- py_beacon_kit-0.1.0/src/beacon/analysis/concentration.py +254 -0
- py_beacon_kit-0.1.0/src/beacon/analysis/etf/analytics.py +134 -0
- py_beacon_kit-0.1.0/src/beacon/analysis/liquidity.py +74 -0
- py_beacon_kit-0.1.0/src/beacon/analysis/relative.py +215 -0
- py_beacon_kit-0.1.0/src/beacon/analysis/risk.py +155 -0
- py_beacon_kit-0.1.0/src/beacon/asset/__init__.py +19 -0
- py_beacon_kit-0.1.0/src/beacon/asset/base.py +27 -0
- py_beacon_kit-0.1.0/src/beacon/asset/bond.py +28 -0
- py_beacon_kit-0.1.0/src/beacon/asset/commodity.py +25 -0
- py_beacon_kit-0.1.0/src/beacon/asset/equity.py +84 -0
- py_beacon_kit-0.1.0/src/beacon/asset/view.py +120 -0
- py_beacon_kit-0.1.0/src/beacon/backtest/__init__.py +26 -0
- py_beacon_kit-0.1.0/src/beacon/backtest/asset_view.py +270 -0
- py_beacon_kit-0.1.0/src/beacon/backtest/engine.py +758 -0
- py_beacon_kit-0.1.0/src/beacon/backtest/main.py +373 -0
- py_beacon_kit-0.1.0/src/beacon/backtest/pricing.py +452 -0
- py_beacon_kit-0.1.0/src/beacon/backtest/result.py +492 -0
- py_beacon_kit-0.1.0/src/beacon/backtest/rules.py +214 -0
- py_beacon_kit-0.1.0/src/beacon/catalogue.py +287 -0
- py_beacon_kit-0.1.0/src/beacon/changelog.py +154 -0
- py_beacon_kit-0.1.0/src/beacon/data/__init__.py +39 -0
- py_beacon_kit-0.1.0/src/beacon/data/adjustment.py +167 -0
- py_beacon_kit-0.1.0/src/beacon/data/base.py +454 -0
- py_beacon_kit-0.1.0/src/beacon/data/corporate_actions.py +407 -0
- py_beacon_kit-0.1.0/src/beacon/data/features.py +391 -0
- py_beacon_kit-0.1.0/src/beacon/data/fetcher.py +1303 -0
- py_beacon_kit-0.1.0/src/beacon/data/free_float.py +125 -0
- py_beacon_kit-0.1.0/src/beacon/data/identifiers.py +399 -0
- py_beacon_kit-0.1.0/src/beacon/data/ingest.py +334 -0
- py_beacon_kit-0.1.0/src/beacon/data/loader.py +36 -0
- py_beacon_kit-0.1.0/src/beacon/data/session.py +142 -0
- py_beacon_kit-0.1.0/src/beacon/data/store.py +378 -0
- py_beacon_kit-0.1.0/src/beacon/derivatives/__init__.py +34 -0
- py_beacon_kit-0.1.0/src/beacon/derivatives/base.py +146 -0
- py_beacon_kit-0.1.0/src/beacon/derivatives/curves.py +246 -0
- py_beacon_kit-0.1.0/src/beacon/derivatives/forwards.py +6 -0
- py_beacon_kit-0.1.0/src/beacon/derivatives/futures.py +228 -0
- py_beacon_kit-0.1.0/src/beacon/derivatives/pricing.py +199 -0
- py_beacon_kit-0.1.0/src/beacon/derivatives/swaps.py +263 -0
- py_beacon_kit-0.1.0/src/beacon/derivatives/term_structure.py +221 -0
- py_beacon_kit-0.1.0/src/beacon/environment/__init__.py +3 -0
- py_beacon_kit-0.1.0/src/beacon/environment/config.py +105 -0
- py_beacon_kit-0.1.0/src/beacon/exceptions.py +237 -0
- py_beacon_kit-0.1.0/src/beacon/expressions/__init__.py +43 -0
- py_beacon_kit-0.1.0/src/beacon/expressions/catalogue.py +92 -0
- py_beacon_kit-0.1.0/src/beacon/expressions/core.py +454 -0
- py_beacon_kit-0.1.0/src/beacon/expressions/namespaces.py +283 -0
- py_beacon_kit-0.1.0/src/beacon/expressions/resolve.py +344 -0
- py_beacon_kit-0.1.0/src/beacon/expressions/stubs.py +186 -0
- py_beacon_kit-0.1.0/src/beacon/expressions/validation.py +227 -0
- py_beacon_kit-0.1.0/src/beacon/fund/__init__.py +13 -0
- py_beacon_kit-0.1.0/src/beacon/fund/base.py +234 -0
- py_beacon_kit-0.1.0/src/beacon/fund/etf.py +146 -0
- py_beacon_kit-0.1.0/src/beacon/index/__init__.py +39 -0
- py_beacon_kit-0.1.0/src/beacon/index/asset_view.py +97 -0
- py_beacon_kit-0.1.0/src/beacon/index/cache.py +621 -0
- py_beacon_kit-0.1.0/src/beacon/index/calculation/__init__.py +23 -0
- py_beacon_kit-0.1.0/src/beacon/index/calculation/calculator.py +990 -0
- py_beacon_kit-0.1.0/src/beacon/index/calculation/corporate_actions.py +266 -0
- py_beacon_kit-0.1.0/src/beacon/index/calculation/deletions.py +150 -0
- py_beacon_kit-0.1.0/src/beacon/index/calculation/market_values.py +612 -0
- py_beacon_kit-0.1.0/src/beacon/index/calculation/selection.py +238 -0
- py_beacon_kit-0.1.0/src/beacon/index/calculation/total_return.py +258 -0
- py_beacon_kit-0.1.0/src/beacon/index/capping.py +200 -0
- py_beacon_kit-0.1.0/src/beacon/index/chaining.py +421 -0
- py_beacon_kit-0.1.0/src/beacon/index/constructor.py +218 -0
- py_beacon_kit-0.1.0/src/beacon/index/context.py +34 -0
- py_beacon_kit-0.1.0/src/beacon/index/derived.py +363 -0
- py_beacon_kit-0.1.0/src/beacon/index/expression_rules.py +159 -0
- py_beacon_kit-0.1.0/src/beacon/index/feature_rules.py +158 -0
- py_beacon_kit-0.1.0/src/beacon/index/methodology.py +842 -0
- py_beacon_kit-0.1.0/src/beacon/index/requirements.py +179 -0
- py_beacon_kit-0.1.0/src/beacon/index/result.py +276 -0
- py_beacon_kit-0.1.0/src/beacon/index/schedule.py +766 -0
- py_beacon_kit-0.1.0/src/beacon/optimise/__init__.py +84 -0
- py_beacon_kit-0.1.0/src/beacon/optimise/config.py +167 -0
- py_beacon_kit-0.1.0/src/beacon/optimise/constraints.py +612 -0
- py_beacon_kit-0.1.0/src/beacon/optimise/frontier.py +490 -0
- py_beacon_kit-0.1.0/src/beacon/optimise/result.py +170 -0
- py_beacon_kit-0.1.0/src/beacon/optimise/solver.py +691 -0
- py_beacon_kit-0.1.0/src/beacon/plot/__init__.py +61 -0
- py_beacon_kit-0.1.0/src/beacon/plot/accessors.py +527 -0
- py_beacon_kit-0.1.0/src/beacon/plot/base.py +75 -0
- py_beacon_kit-0.1.0/src/beacon/plot/comparison.py +158 -0
- py_beacon_kit-0.1.0/src/beacon/plot/style.py +243 -0
- py_beacon_kit-0.1.0/src/beacon/portfolio/__init__.py +16 -0
- py_beacon_kit-0.1.0/src/beacon/portfolio/asset_view.py +151 -0
- py_beacon_kit-0.1.0/src/beacon/portfolio/base.py +571 -0
- py_beacon_kit-0.1.0/src/beacon/portfolio/history.py +169 -0
- py_beacon_kit-0.1.0/src/beacon/portfolio/reporting.py +139 -0
- py_beacon_kit-0.1.0/src/beacon/py.typed +0 -0
- py_beacon_kit-0.1.0/src/beacon/report/__init__.py +50 -0
- py_beacon_kit-0.1.0/src/beacon/report/blocks.py +376 -0
- py_beacon_kit-0.1.0/src/beacon/report/pdf.py +500 -0
- py_beacon_kit-0.1.0/src/beacon/risk/__init__.py +81 -0
- py_beacon_kit-0.1.0/src/beacon/risk/contribution.py +234 -0
- py_beacon_kit-0.1.0/src/beacon/risk/covariance.py +381 -0
- py_beacon_kit-0.1.0/src/beacon/risk/factors.py +441 -0
- py_beacon_kit-0.1.0/src/beacon/risk/model.py +290 -0
- py_beacon_kit-0.1.0/src/beacon/server/__init__.py +42 -0
- py_beacon_kit-0.1.0/src/beacon/server/__main__.py +198 -0
- py_beacon_kit-0.1.0/src/beacon/server/app.py +263 -0
- py_beacon_kit-0.1.0/src/beacon/server/backtests.py +301 -0
- py_beacon_kit-0.1.0/src/beacon/server/benchmarks.py +138 -0
- py_beacon_kit-0.1.0/src/beacon/server/config.py +195 -0
- py_beacon_kit-0.1.0/src/beacon/server/constraints.py +304 -0
- py_beacon_kit-0.1.0/src/beacon/server/definitions.py +567 -0
- py_beacon_kit-0.1.0/src/beacon/server/derivatives.py +480 -0
- py_beacon_kit-0.1.0/src/beacon/server/documents.py +334 -0
- py_beacon_kit-0.1.0/src/beacon/server/errors.py +334 -0
- py_beacon_kit-0.1.0/src/beacon/server/jobs.py +537 -0
- py_beacon_kit-0.1.0/src/beacon/server/methods.py +84 -0
- py_beacon_kit-0.1.0/src/beacon/server/optimisation.py +373 -0
- py_beacon_kit-0.1.0/src/beacon/server/preview.py +347 -0
- py_beacon_kit-0.1.0/src/beacon/server/reference.py +602 -0
- py_beacon_kit-0.1.0/src/beacon/server/reports.py +235 -0
- py_beacon_kit-0.1.0/src/beacon/server/risk.py +150 -0
- py_beacon_kit-0.1.0/src/beacon/server/routers/__init__.py +35 -0
- py_beacon_kit-0.1.0/src/beacon/server/routers/beacon.py +317 -0
- py_beacon_kit-0.1.0/src/beacon/server/routers/coverage.py +410 -0
- py_beacon_kit-0.1.0/src/beacon/server/routers/data.py +726 -0
- py_beacon_kit-0.1.0/src/beacon/server/routers/derivatives.py +105 -0
- py_beacon_kit-0.1.0/src/beacon/server/routers/indices.py +633 -0
- py_beacon_kit-0.1.0/src/beacon/server/routers/jobs.py +182 -0
- py_beacon_kit-0.1.0/src/beacon/server/routers/optimise.py +242 -0
- py_beacon_kit-0.1.0/src/beacon/server/routers/reports.py +247 -0
- py_beacon_kit-0.1.0/src/beacon/server/routers/risk.py +150 -0
- py_beacon_kit-0.1.0/src/beacon/server/routers/universes.py +540 -0
- py_beacon_kit-0.1.0/src/beacon/server/routers/watchlists.py +92 -0
- py_beacon_kit-0.1.0/src/beacon/server/runs.py +67 -0
- py_beacon_kit-0.1.0/src/beacon/server/schemas.py +3571 -0
- py_beacon_kit-0.1.0/src/beacon/server/security.py +50 -0
- py_beacon_kit-0.1.0/src/beacon/server/serialisation.py +68 -0
- py_beacon_kit-0.1.0/src/beacon/server/store.py +302 -0
- py_beacon_kit-0.1.0/src/beacon/server/types.py +67 -0
- py_beacon_kit-0.1.0/src/beacon/server/views.py +331 -0
- py_beacon_kit-0.1.0/src/beacon/server/weights.py +382 -0
- py_beacon_kit-0.1.0/src/beacon/sources.py +124 -0
- py_beacon_kit-0.1.0/src/beacon/synthetic/__init__.py +48 -0
- py_beacon_kit-0.1.0/src/beacon/synthetic/__main__.py +297 -0
- py_beacon_kit-0.1.0/src/beacon/synthetic/dataset.py +236 -0
- py_beacon_kit-0.1.0/src/beacon/synthetic/features.py +352 -0
- py_beacon_kit-0.1.0/src/beacon/synthetic/fx.py +138 -0
- py_beacon_kit-0.1.0/src/beacon/synthetic/listings.py +261 -0
- py_beacon_kit-0.1.0/src/beacon/synthetic/prices.py +488 -0
- py_beacon_kit-0.1.0/src/beacon/synthetic/profiles.py +358 -0
- py_beacon_kit-0.1.0/src/beacon/synthetic/regimes.py +269 -0
- py_beacon_kit-0.1.0/src/beacon/synthetic/regions.py +217 -0
- py_beacon_kit-0.1.0/src/beacon/synthetic/returns.py +460 -0
- py_beacon_kit-0.1.0/src/beacon/synthetic/universe.py +344 -0
- py_beacon_kit-0.1.0/src/beacon/testing/__init__.py +70 -0
- py_beacon_kit-0.1.0/src/beacon/testing/dataset.py +310 -0
- py_beacon_kit-0.1.0/src/beacon/testing/weights.py +87 -0
- py_beacon_kit-0.1.0/src/beacon/tokens/__init__.py +210 -0
- py_beacon_kit-0.1.0/src/beacon/tokens/colors.json +298 -0
- py_beacon_kit-0.1.0/src/beacon/universe.py +181 -0
- py_beacon_kit-0.1.0/tests/baseline/test_annual_returns_dark.png +0 -0
- py_beacon_kit-0.1.0/tests/baseline/test_annual_returns_light.png +0 -0
- py_beacon_kit-0.1.0/tests/baseline/test_compare_dark.png +0 -0
- py_beacon_kit-0.1.0/tests/baseline/test_compare_light.png +0 -0
- py_beacon_kit-0.1.0/tests/baseline/test_contributions_dark.png +0 -0
- py_beacon_kit-0.1.0/tests/baseline/test_contributions_light.png +0 -0
- py_beacon_kit-0.1.0/tests/baseline/test_correlation_dark.png +0 -0
- py_beacon_kit-0.1.0/tests/baseline/test_correlation_light.png +0 -0
- py_beacon_kit-0.1.0/tests/baseline/test_exposures_dark.png +0 -0
- py_beacon_kit-0.1.0/tests/baseline/test_exposures_light.png +0 -0
- py_beacon_kit-0.1.0/tests/baseline/test_frontier_dark.png +0 -0
- py_beacon_kit-0.1.0/tests/baseline/test_frontier_light.png +0 -0
- py_beacon_kit-0.1.0/tests/baseline/test_level_dark.png +0 -0
- py_beacon_kit-0.1.0/tests/baseline/test_level_light.png +0 -0
- py_beacon_kit-0.1.0/tests/baseline/test_performance_dark.png +0 -0
- py_beacon_kit-0.1.0/tests/baseline/test_performance_light.png +0 -0
- py_beacon_kit-0.1.0/tests/baseline/test_weights_dark.png +0 -0
- py_beacon_kit-0.1.0/tests/baseline/test_weights_light.png +0 -0
- py_beacon_kit-0.1.0/tests/conftest.py +93 -0
- py_beacon_kit-0.1.0/tests/test_adjustment.py +251 -0
- py_beacon_kit-0.1.0/tests/test_api_contract.py +1015 -0
- py_beacon_kit-0.1.0/tests/test_asset.py +199 -0
- py_beacon_kit-0.1.0/tests/test_asset_view.py +151 -0
- py_beacon_kit-0.1.0/tests/test_attribution.py +355 -0
- py_beacon_kit-0.1.0/tests/test_backtest_engine.py +610 -0
- py_beacon_kit-0.1.0/tests/test_backtest_engine_integration.py +381 -0
- py_beacon_kit-0.1.0/tests/test_backtest_fills.py +233 -0
- py_beacon_kit-0.1.0/tests/test_backtest_main.py +501 -0
- py_beacon_kit-0.1.0/tests/test_backtest_modifier.py +200 -0
- py_beacon_kit-0.1.0/tests/test_backtest_pricing.py +596 -0
- py_beacon_kit-0.1.0/tests/test_backtest_result.py +667 -0
- py_beacon_kit-0.1.0/tests/test_benchmark.py +451 -0
- py_beacon_kit-0.1.0/tests/test_canonical_dataset_integration.py +233 -0
- py_beacon_kit-0.1.0/tests/test_capping.py +309 -0
- py_beacon_kit-0.1.0/tests/test_catalogue.py +437 -0
- py_beacon_kit-0.1.0/tests/test_chaining.py +332 -0
- py_beacon_kit-0.1.0/tests/test_changelog.py +152 -0
- py_beacon_kit-0.1.0/tests/test_concentration.py +252 -0
- py_beacon_kit-0.1.0/tests/test_constraint_catalogue.py +196 -0
- py_beacon_kit-0.1.0/tests/test_corporate_action_kind.py +264 -0
- py_beacon_kit-0.1.0/tests/test_corporate_actions.py +419 -0
- py_beacon_kit-0.1.0/tests/test_coverage_fields.py +317 -0
- py_beacon_kit-0.1.0/tests/test_daily_weights.py +414 -0
- py_beacon_kit-0.1.0/tests/test_data_fetcher.py +65 -0
- py_beacon_kit-0.1.0/tests/test_data_store.py +455 -0
- py_beacon_kit-0.1.0/tests/test_data_tables.py +328 -0
- py_beacon_kit-0.1.0/tests/test_derivatives_base.py +158 -0
- py_beacon_kit-0.1.0/tests/test_derivatives_curves.py +456 -0
- py_beacon_kit-0.1.0/tests/test_derivatives_etf_future.py +174 -0
- py_beacon_kit-0.1.0/tests/test_derivatives_futures.py +190 -0
- py_beacon_kit-0.1.0/tests/test_derivatives_pricing.py +218 -0
- py_beacon_kit-0.1.0/tests/test_derivatives_swaps.py +192 -0
- py_beacon_kit-0.1.0/tests/test_derived_index.py +376 -0
- py_beacon_kit-0.1.0/tests/test_effective_lag.py +257 -0
- py_beacon_kit-0.1.0/tests/test_etf_tracking.py +141 -0
- py_beacon_kit-0.1.0/tests/test_examples.py +191 -0
- py_beacon_kit-0.1.0/tests/test_expression_namespaces.py +207 -0
- py_beacon_kit-0.1.0/tests/test_expression_rules.py +412 -0
- py_beacon_kit-0.1.0/tests/test_expression_schema.py +461 -0
- py_beacon_kit-0.1.0/tests/test_expression_validation.py +392 -0
- py_beacon_kit-0.1.0/tests/test_expressions.py +318 -0
- py_beacon_kit-0.1.0/tests/test_factors.py +441 -0
- py_beacon_kit-0.1.0/tests/test_feature_rules.py +236 -0
- py_beacon_kit-0.1.0/tests/test_features.py +406 -0
- py_beacon_kit-0.1.0/tests/test_free_float_backfill.py +226 -0
- py_beacon_kit-0.1.0/tests/test_frontier.py +459 -0
- py_beacon_kit-0.1.0/tests/test_fund.py +429 -0
- py_beacon_kit-0.1.0/tests/test_fx_data.py +193 -0
- py_beacon_kit-0.1.0/tests/test_identifier_search.py +483 -0
- py_beacon_kit-0.1.0/tests/test_index_asset_view.py +168 -0
- py_beacon_kit-0.1.0/tests/test_index_batch_reads.py +400 -0
- py_beacon_kit-0.1.0/tests/test_index_cache.py +598 -0
- py_beacon_kit-0.1.0/tests/test_index_calculator.py +1098 -0
- py_beacon_kit-0.1.0/tests/test_index_calculator_integration.py +377 -0
- py_beacon_kit-0.1.0/tests/test_index_currency.py +772 -0
- py_beacon_kit-0.1.0/tests/test_index_definition.py +270 -0
- py_beacon_kit-0.1.0/tests/test_index_eligibility.py +348 -0
- py_beacon_kit-0.1.0/tests/test_index_result.py +178 -0
- py_beacon_kit-0.1.0/tests/test_index_schedule.py +1376 -0
- py_beacon_kit-0.1.0/tests/test_index_selection.py +339 -0
- py_beacon_kit-0.1.0/tests/test_index_weighting.py +410 -0
- py_beacon_kit-0.1.0/tests/test_ingest.py +698 -0
- py_beacon_kit-0.1.0/tests/test_integration.py +263 -0
- py_beacon_kit-0.1.0/tests/test_job_persistence.py +419 -0
- py_beacon_kit-0.1.0/tests/test_look_ahead.py +137 -0
- py_beacon_kit-0.1.0/tests/test_optimise.py +665 -0
- py_beacon_kit-0.1.0/tests/test_optional_dependencies.py +305 -0
- py_beacon_kit-0.1.0/tests/test_plot.py +858 -0
- py_beacon_kit-0.1.0/tests/test_plot_images.py +235 -0
- py_beacon_kit-0.1.0/tests/test_portfolio.py +700 -0
- py_beacon_kit-0.1.0/tests/test_portfolio_asset_view.py +245 -0
- py_beacon_kit-0.1.0/tests/test_properties.py +966 -0
- py_beacon_kit-0.1.0/tests/test_read_volume.py +353 -0
- py_beacon_kit-0.1.0/tests/test_reference_currency.py +331 -0
- py_beacon_kit-0.1.0/tests/test_reference_dimensions.py +300 -0
- py_beacon_kit-0.1.0/tests/test_reference_profile.py +343 -0
- py_beacon_kit-0.1.0/tests/test_report.py +442 -0
- py_beacon_kit-0.1.0/tests/test_requirements.py +227 -0
- py_beacon_kit-0.1.0/tests/test_risk_contribution.py +495 -0
- py_beacon_kit-0.1.0/tests/test_risk_model.py +533 -0
- py_beacon_kit-0.1.0/tests/test_server.py +896 -0
- py_beacon_kit-0.1.0/tests/test_server_backtest.py +813 -0
- py_beacon_kit-0.1.0/tests/test_server_cors.py +185 -0
- py_beacon_kit-0.1.0/tests/test_server_data_router.py +321 -0
- py_beacon_kit-0.1.0/tests/test_server_derivatives.py +646 -0
- py_beacon_kit-0.1.0/tests/test_server_document_faults.py +910 -0
- py_beacon_kit-0.1.0/tests/test_server_features.py +251 -0
- py_beacon_kit-0.1.0/tests/test_server_indices.py +542 -0
- py_beacon_kit-0.1.0/tests/test_server_jobs.py +539 -0
- py_beacon_kit-0.1.0/tests/test_server_listing_discovery.py +354 -0
- py_beacon_kit-0.1.0/tests/test_server_optimise.py +700 -0
- py_beacon_kit-0.1.0/tests/test_server_optimised_indices.py +847 -0
- py_beacon_kit-0.1.0/tests/test_server_preview.py +531 -0
- py_beacon_kit-0.1.0/tests/test_server_preview_document.py +269 -0
- py_beacon_kit-0.1.0/tests/test_server_preview_optimised.py +449 -0
- py_beacon_kit-0.1.0/tests/test_server_reference_batch.py +583 -0
- py_beacon_kit-0.1.0/tests/test_server_reports.py +410 -0
- py_beacon_kit-0.1.0/tests/test_server_risk.py +379 -0
- py_beacon_kit-0.1.0/tests/test_server_store.py +493 -0
- py_beacon_kit-0.1.0/tests/test_server_universes.py +443 -0
- py_beacon_kit-0.1.0/tests/test_server_views.py +591 -0
- py_beacon_kit-0.1.0/tests/test_server_weights_rows.py +334 -0
- py_beacon_kit-0.1.0/tests/test_session_agreement.py +195 -0
- py_beacon_kit-0.1.0/tests/test_spec_conformance.py +613 -0
- py_beacon_kit-0.1.0/tests/test_stale_prices.py +229 -0
- py_beacon_kit-0.1.0/tests/test_survivorship.py +413 -0
- py_beacon_kit-0.1.0/tests/test_synthetic.py +1031 -0
- py_beacon_kit-0.1.0/tests/test_synthetic_features.py +319 -0
- py_beacon_kit-0.1.0/tests/test_testing_dataset.py +283 -0
- py_beacon_kit-0.1.0/tests/test_tokens.py +399 -0
- py_beacon_kit-0.1.0/tests/test_total_return.py +553 -0
- py_beacon_kit-0.1.0/tests/test_universe_batch_reads.py +375 -0
- py_beacon_kit-0.1.0/tests/test_universe_filters.py +368 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
|
|
2
|
+
# Byte-compiled / optimized / DLL files
|
|
3
|
+
__pycache__/
|
|
4
|
+
*.py[cod]
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
bin/
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
eggs/
|
|
15
|
+
lib/
|
|
16
|
+
lib64/
|
|
17
|
+
parts/
|
|
18
|
+
sdist/
|
|
19
|
+
var/
|
|
20
|
+
*.egg-info/
|
|
21
|
+
.installed.cfg
|
|
22
|
+
*.egg
|
|
23
|
+
|
|
24
|
+
# Installer logs
|
|
25
|
+
pip-log.txt
|
|
26
|
+
pip-delete-this-directory.txt
|
|
27
|
+
|
|
28
|
+
# Unit test / coverage reports
|
|
29
|
+
.tox/
|
|
30
|
+
.coverage
|
|
31
|
+
.cache
|
|
32
|
+
nosetests.xml
|
|
33
|
+
coverage.xml
|
|
34
|
+
|
|
35
|
+
# Translations
|
|
36
|
+
*.mo
|
|
37
|
+
|
|
38
|
+
# Mr Developer
|
|
39
|
+
.mr.developer.cfg
|
|
40
|
+
.project
|
|
41
|
+
.pydevproject
|
|
42
|
+
|
|
43
|
+
# Rope
|
|
44
|
+
.ropeproject
|
|
45
|
+
|
|
46
|
+
# Django stuff:
|
|
47
|
+
*.log
|
|
48
|
+
*.pot
|
|
49
|
+
|
|
50
|
+
# Sphinx documentation
|
|
51
|
+
docs/_build/
|
|
52
|
+
|
|
53
|
+
# MkDocs build output
|
|
54
|
+
site/
|
|
55
|
+
|
|
56
|
+
/.env
|
|
57
|
+
|
|
58
|
+
#database
|
|
59
|
+
*.db
|
|
60
|
+
|
|
61
|
+
# AI
|
|
62
|
+
.roo/
|
|
63
|
+
/.roomodes
|
|
64
|
+
/.rooignore
|
|
65
|
+
.claude/
|
|
66
|
+
CLAUDE.md
|
|
67
|
+
|
|
68
|
+
/planning/
|
|
69
|
+
# Anchored to the repository root. Unanchored, `testing/` also matched
|
|
70
|
+
# src/beacon/testing — the shipped fixture dataset — and silently kept it out
|
|
71
|
+
# of a commit whose tests import it.
|
|
72
|
+
/testing/
|
|
73
|
+
openapi.json
|
|
74
|
+
|
|
75
|
+
# Generated at docs build time by scripts/build_gallery.py.
|
|
76
|
+
/docs/gallery/
|
|
77
|
+
/docs/gallery.md
|
|
78
|
+
|
|
79
|
+
# Charts written by examples/02_backtest_analysis.ipynb, regenerated on each run.
|
|
80
|
+
examples/output/
|
|
81
|
+
|
|
82
|
+
# Jupyter checkpoints. The notebooks themselves are committed without outputs;
|
|
83
|
+
# a test enforces it.
|
|
84
|
+
.ipynb_checkpoints/
|
|
85
|
+
|
|
86
|
+
# Written by scripts/fuzz_store.py for the nightly fuzz run, and by the server
|
|
87
|
+
# it starts. Both are regenerated on every run.
|
|
88
|
+
.fuzzstore/
|
|
89
|
+
fuzz-server.log
|
|
90
|
+
.fuzzserver.log
|
|
91
|
+
|
|
92
|
+
# Fuzz run artefacts (BN-131)
|
|
93
|
+
.schemathesis/
|
|
94
|
+
|
|
95
|
+
# Local scratch data that is not part of the codebase. Listed because a
|
|
96
|
+
# `git add -A` swept it into a commit once; an ignore rule is cheaper than
|
|
97
|
+
# remembering.
|
|
98
|
+
us_tickers.csv
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
What changed in each release of py-beacon. The newest release is first.
|
|
4
|
+
|
|
5
|
+
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
|
|
6
|
+
and the version numbers follow [Semantic Versioning](https://semver.org/).
|
|
7
|
+
Before 1.0, any release may change the API.
|
|
8
|
+
|
|
9
|
+
## [Unreleased]
|
|
10
|
+
|
|
11
|
+
## [0.1.0] - 2026-09-24
|
|
12
|
+
|
|
13
|
+
The first release.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- Build an index from rules and a weighting scheme: equal weight, market cap or free-float market cap, with optional weight caps.
|
|
18
|
+
- Calculate the index level on a real exchange calendar, as price, total return or net total return.
|
|
19
|
+
- Adjust the index for dividends, special dividends and delistings.
|
|
20
|
+
- Write selection rules as expressions, such as `data.market.market_cap > 1e9`, including rules on company fundamentals and other features.
|
|
21
|
+
- Backtest a portfolio that trades to the index weights, with transaction costs, drift thresholds and a benchmark.
|
|
22
|
+
- Read results with tracking error, attribution, risk, concentration and drift, and draw them as charts.
|
|
23
|
+
- Optimise an index under constraints (position limits, group limits, turnover, number of names), with the efficient frontier and factor exposures.
|
|
24
|
+
- Estimate risk models with shrinkage, and split risk and active risk by constituent.
|
|
25
|
+
- Price index futures, ETF futures and total return swaps, with carry, roll and a sensitivity grid.
|
|
26
|
+
- Hold names in several currencies. Prices are converted with FX rates.
|
|
27
|
+
- Load data from files, generate a realistic synthetic dataset, or download prices with yfinance. Data is kept in a local store that reports its coverage and age.
|
|
28
|
+
- Three settings, shown on `/health`: whether a missing FX rate carries forward, when a stale price drops a name, and how long a free float carries forward (90 days by default).
|
|
29
|
+
- A local API server for the Beacon desktop app, covering data, indices, universes, backtests, the optimiser, risk, derivatives and PDF reports. Long tasks run as jobs with live progress.
|
|
30
|
+
- This changelog, served at `GET /changelog` so the app can show what changed.
|
|
31
|
+
- Optional extras keep the core install small: `data`, `excel`, `pdf`, `optimise`, `plot` and `server`.
|
|
32
|
+
|
|
33
|
+
### Notes
|
|
34
|
+
|
|
35
|
+
- An index or backtest never uses a price, rate or free float dated after the day it is working on.
|
|
36
|
+
- Requires Python 3.11 or later.
|
|
37
|
+
|
|
38
|
+
[Unreleased]: https://github.com/karanbh01/py-beacon/compare/v0.1.0...HEAD
|
|
39
|
+
[0.1.0]: https://github.com/karanbh01/py-beacon/releases/tag/v0.1.0
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Karan Bhanot
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: py-beacon-kit
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: End-to-end tool kit for index, ETF & Delta-1 derivatives development
|
|
5
|
+
Project-URL: Homepage, https://github.com/karanbh01/py-beacon
|
|
6
|
+
Project-URL: Source, https://github.com/karanbh01/py-beacon
|
|
7
|
+
Project-URL: Issue Tracker, https://github.com/karanbh01/py-beacon/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/karanbh01/py-beacon/blob/main/CHANGELOG.md
|
|
9
|
+
Author-email: Karan Bhanot <karan.bhanot@outlook.com>
|
|
10
|
+
Maintainer-email: Karan Bhanot <karan.bhanot@outlook.com>
|
|
11
|
+
License-Expression: MIT
|
|
12
|
+
License-File: LICENSE.txt
|
|
13
|
+
Keywords: analysis,backtesting,data,delta-one,derivatives,etf,finance,futures,index,index investing,investment,passive,passive investing,portfolio,quant,quantitative,quantitative analysis,quantitative portfolio management,strategy,swaps,systematic investing
|
|
14
|
+
Classifier: Development Status :: 3 - Alpha
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Intended Audience :: Financial and Insurance Industry
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Office/Business :: Financial :: Investment
|
|
23
|
+
Requires-Python: >=3.11
|
|
24
|
+
Requires-Dist: exchange-calendars
|
|
25
|
+
Requires-Dist: numpy
|
|
26
|
+
Requires-Dist: pandas
|
|
27
|
+
Requires-Dist: pydantic>=2
|
|
28
|
+
Provides-Extra: data
|
|
29
|
+
Requires-Dist: yfinance; extra == 'data'
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: fastapi; extra == 'dev'
|
|
32
|
+
Requires-Dist: httpx; extra == 'dev'
|
|
33
|
+
Requires-Dist: hypothesis; extra == 'dev'
|
|
34
|
+
Requires-Dist: ipykernel; extra == 'dev'
|
|
35
|
+
Requires-Dist: matplotlib<3.11,>=3.10; extra == 'dev'
|
|
36
|
+
Requires-Dist: mypy; extra == 'dev'
|
|
37
|
+
Requires-Dist: nbclient; extra == 'dev'
|
|
38
|
+
Requires-Dist: nbformat; extra == 'dev'
|
|
39
|
+
Requires-Dist: orjson; extra == 'dev'
|
|
40
|
+
Requires-Dist: platformdirs; extra == 'dev'
|
|
41
|
+
Requires-Dist: pre-commit; extra == 'dev'
|
|
42
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
43
|
+
Requires-Dist: pytest-asyncio; extra == 'dev'
|
|
44
|
+
Requires-Dist: pytest-cov; extra == 'dev'
|
|
45
|
+
Requires-Dist: pytest-mpl; extra == 'dev'
|
|
46
|
+
Requires-Dist: pytest-timeout; extra == 'dev'
|
|
47
|
+
Requires-Dist: reportlab; extra == 'dev'
|
|
48
|
+
Requires-Dist: ruff; extra == 'dev'
|
|
49
|
+
Requires-Dist: scipy; extra == 'dev'
|
|
50
|
+
Requires-Dist: uvicorn[standard]; extra == 'dev'
|
|
51
|
+
Requires-Dist: websockets; extra == 'dev'
|
|
52
|
+
Provides-Extra: docs
|
|
53
|
+
Requires-Dist: matplotlib<3.11,>=3.10; extra == 'docs'
|
|
54
|
+
Requires-Dist: mkdocs-material; extra == 'docs'
|
|
55
|
+
Requires-Dist: mkdocstrings[python]; extra == 'docs'
|
|
56
|
+
Requires-Dist: scipy; extra == 'docs'
|
|
57
|
+
Provides-Extra: excel
|
|
58
|
+
Requires-Dist: openpyxl; extra == 'excel'
|
|
59
|
+
Provides-Extra: optimise
|
|
60
|
+
Requires-Dist: scipy; extra == 'optimise'
|
|
61
|
+
Provides-Extra: pdf
|
|
62
|
+
Requires-Dist: reportlab; extra == 'pdf'
|
|
63
|
+
Provides-Extra: plot
|
|
64
|
+
Requires-Dist: matplotlib<3.11,>=3.10; extra == 'plot'
|
|
65
|
+
Provides-Extra: plot-interactive
|
|
66
|
+
Requires-Dist: plotly; extra == 'plot-interactive'
|
|
67
|
+
Provides-Extra: server
|
|
68
|
+
Requires-Dist: fastapi; extra == 'server'
|
|
69
|
+
Requires-Dist: orjson; extra == 'server'
|
|
70
|
+
Requires-Dist: platformdirs; extra == 'server'
|
|
71
|
+
Requires-Dist: reportlab; extra == 'server'
|
|
72
|
+
Requires-Dist: scipy; extra == 'server'
|
|
73
|
+
Requires-Dist: uvicorn[standard]; extra == 'server'
|
|
74
|
+
Requires-Dist: websockets; extra == 'server'
|
|
75
|
+
Description-Content-Type: text/markdown
|
|
76
|
+
|
|
77
|
+
# Beacon
|
|
78
|
+
|
|
79
|
+
[](https://github.com/karanbh01/py-beacon/actions/workflows/ci.yml)
|
|
80
|
+
|
|
81
|
+

|
|
82
|
+
|
|
83
|
+
Beacon (***Be***t***a*** ***Con***structor) is a Python toolkit for end-to-end
|
|
84
|
+
index, ETF, and Delta-1 derivatives development — from defining an index
|
|
85
|
+
methodology, through calculating its historical levels, to backtesting a
|
|
86
|
+
tracking portfolio and analysing the result.
|
|
87
|
+
|
|
88
|
+
> Status: under active development.
|
|
89
|
+
|
|
90
|
+
## Architecture
|
|
91
|
+
|
|
92
|
+
Beacon is organised around a three-layer pipeline. Each layer has a single
|
|
93
|
+
responsibility and depends only on the layer(s) below it, which keeps the
|
|
94
|
+
methodology, the calculation, and the simulation cleanly separated.
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
┌──────────────────────────────────────────────┐
|
|
98
|
+
│ Methodology │
|
|
99
|
+
│ eligibility rules + weighting schemes │
|
|
100
|
+
│ (what belongs in the index and at what weight)│
|
|
101
|
+
└───────────────────────┬────────────────────────┘
|
|
102
|
+
│ defines
|
|
103
|
+
▼
|
|
104
|
+
┌──────────────────────────────────────────────┐
|
|
105
|
+
│ Calculator │
|
|
106
|
+
│ IndexCalculator.run() -> IndexResult │
|
|
107
|
+
│ (levels, divisor, constituent/weight history) │
|
|
108
|
+
└───────────────────────┬────────────────────────┘
|
|
109
|
+
│ target weights
|
|
110
|
+
▼
|
|
111
|
+
┌──────────────────────────────────────────────┐
|
|
112
|
+
│ Backtest │
|
|
113
|
+
│ BacktestEngine.run() -> BacktestResult │
|
|
114
|
+
│ (NAV, trades, tracking error vs. the index) │
|
|
115
|
+
└──────────────────────────────────────────────┘
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Funds (`IndexFund`, `ETF`) compose the Calculator and Backtest layers, and the
|
|
119
|
+
Derivatives layer prices instruments off the levels an `IndexResult` produces.
|
|
120
|
+
|
|
121
|
+
## Modules
|
|
122
|
+
|
|
123
|
+
- **`index`** — Index construction and calculation. `IndexDefinition` captures
|
|
124
|
+
the static rules (universe, currency, base date, rebalance frequency);
|
|
125
|
+
`methodology` provides the eligibility rules and weighting schemes (e.g.
|
|
126
|
+
`EqualWeighted`, `MarketCapWeighted`); `IndexCalculator` runs the day-by-day
|
|
127
|
+
calculation and returns an `IndexResult` with index levels, divisor history,
|
|
128
|
+
and constituent/weight snapshots.
|
|
129
|
+
- **`backtest`** — Portfolio simulation. `BacktestEngine` consumes a target
|
|
130
|
+
weight schedule (an `IndexResult` or a custom weight dict), simulates trading
|
|
131
|
+
with configurable transaction costs, and returns a `BacktestResult` exposing
|
|
132
|
+
NAV, cash and weight history, transactions, and tracking metrics.
|
|
133
|
+
- **`portfolio`** — The `Portfolio` accounting primitive: holdings, cash,
|
|
134
|
+
transactions, valuation and weights, plus Excel reporting helpers. It has no
|
|
135
|
+
dependency on assets or data sources — callers pass identifiers and prices.
|
|
136
|
+
- **`fund`** — Investable vehicles. `IndexFund` composes an `IndexCalculator`
|
|
137
|
+
and a `BacktestEngine` to track an index (with management-fee accrual); `ETF`
|
|
138
|
+
extends it with a ticker, creation-unit size, market-price simulation, and
|
|
139
|
+
tracking-performance analysis.
|
|
140
|
+
- **`derivatives`** — Delta-1 instruments referencing indices/ETFs/equities:
|
|
141
|
+
`IndexFuture`, `ETFFuture`, and `TotalReturnSwap`, built on a `DerivativeBase`
|
|
142
|
+
ABC, plus pure `pricing` functions (cost-of-carry, discrete-dividend forward,
|
|
143
|
+
implied repo, roll return, TRS breakeven spread).
|
|
144
|
+
- **`analysis`** — Performance and risk analytics, including ETF tracking
|
|
145
|
+
metrics (`analysis.etf`), attribution, and risk measures.
|
|
146
|
+
- **`data`** — Market and reference data access. `MarketData`/`ReferenceData`
|
|
147
|
+
wrap tabular sources and `DataFetcher` provides a unified query interface used
|
|
148
|
+
throughout the calculation and backtest layers. `data.store` persists a
|
|
149
|
+
fetcher to disk so a spawned server can find one at startup.
|
|
150
|
+
- **`synthetic`** — A generator for market-like data at demo scale: a factor
|
|
151
|
+
model with GJR-GARCH volatility and Student-t innovations, plus the reference
|
|
152
|
+
data, shares outstanding, free float and corporate actions that agree with
|
|
153
|
+
the prices it produces.
|
|
154
|
+
- **`environment`** — The `Environment` configuration object that centralises
|
|
155
|
+
run-level settings.
|
|
156
|
+
|
|
157
|
+
## Installation
|
|
158
|
+
|
|
159
|
+
Beacon needs Python 3.11 or later. Install it from PyPI:
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
pip install py-beacon-kit
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
The distribution is named `py-beacon-kit` (`py-beacon` is too close to an
|
|
166
|
+
existing PyPI project); the import package is `beacon`. The core installs
|
|
167
|
+
pandas, numpy, pydantic and exchange_calendars.
|
|
168
|
+
|
|
169
|
+
Everything beyond the core pipeline lives behind an extra, so a plain install
|
|
170
|
+
stays light:
|
|
171
|
+
|
|
172
|
+
| Extra | Installs | Needed for |
|
|
173
|
+
| --- | --- | --- |
|
|
174
|
+
| `data` | yfinance | Downloading market data |
|
|
175
|
+
| `excel` | openpyxl | `ReportGenerator` Excel output |
|
|
176
|
+
| `pdf` | reportlab | PDF reports |
|
|
177
|
+
| `optimise` | scipy | Portfolio optimisation |
|
|
178
|
+
| `plot` | matplotlib | Chart accessors on result objects |
|
|
179
|
+
| `plot-interactive` | plotly | Reserved for interactive charts; not used yet |
|
|
180
|
+
| `server` | fastapi, uvicorn, orjson, websockets, platformdirs, plus `optimise` and `pdf` | The local API server |
|
|
181
|
+
| `dev` | pytest, ruff, mypy, pre-commit, hypothesis | Contributing |
|
|
182
|
+
|
|
183
|
+
Install one with `pip install "py-beacon-kit[plot]"`, or several with
|
|
184
|
+
`pip install "py-beacon-kit[plot,data]"`. Using a feature without its extra raises
|
|
185
|
+
an error naming the extra to install.
|
|
186
|
+
|
|
187
|
+
To work on Beacon itself, clone the repository and install it in editable
|
|
188
|
+
mode with the development extra:
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
git clone https://github.com/karanbh01/py-beacon.git
|
|
192
|
+
cd py-beacon
|
|
193
|
+
python -m venv .venv && source .venv/bin/activate # Windows: .venv\Scripts\activate
|
|
194
|
+
pip install -e ".[dev]"
|
|
195
|
+
pytest
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Contributors should also install the git hooks once per clone:
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
pre-commit install # lint + whitespace checks on commit
|
|
202
|
+
pre-commit install --hook-type pre-push # strict type check on push
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
`ruff check` and `mypy` are the enforced gates. Code formatting is not
|
|
206
|
+
tool-enforced — signature layout follows a reviewed convention rather than a
|
|
207
|
+
formatter, so `ruff format` is deliberately not part of the hook set.
|
|
208
|
+
|
|
209
|
+
See [CONTRIBUTING.md](https://github.com/karanbh01/py-beacon/blob/main/CONTRIBUTING.md) for the conventions, the issue and
|
|
210
|
+
commit format, and the release process, and [CHANGELOG.md](https://github.com/karanbh01/py-beacon/blob/main/CHANGELOG.md) for
|
|
211
|
+
what has changed.
|
|
212
|
+
|
|
213
|
+
## Running the API server
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
pip install "py-beacon-kit[server]"
|
|
217
|
+
python -m beacon.server --port 0 --token dev
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
The process binds first and prints `BEACON_PORT=<n>` on stdout before serving,
|
|
221
|
+
so a parent process launching it with `--port 0` can read back the port the OS
|
|
222
|
+
chose. Every later stdout line is ordinary logging.
|
|
223
|
+
|
|
224
|
+
### Where the server gets its data
|
|
225
|
+
|
|
226
|
+
A data source is resolved at startup, in this order:
|
|
227
|
+
|
|
228
|
+
1. `--data <path>` — an explicit store directory
|
|
229
|
+
2. `$BEACON_DATA_PATH`
|
|
230
|
+
3. the app-data store, auto-loaded if one has been written there
|
|
231
|
+
4. nothing — the server starts data-less and the data endpoints report
|
|
232
|
+
`CONFIGURATION_ERROR` until a sync populates one
|
|
233
|
+
|
|
234
|
+
The branch that ran is logged immediately after the port announcement, so an
|
|
235
|
+
empty client is diagnosed by reading the log rather than by guessing. The first
|
|
236
|
+
two branches fail loudly: asking for a store that cannot be read stops startup,
|
|
237
|
+
because starting empty instead would disguise the mistake. Auto-load only
|
|
238
|
+
warns, so a corrupt store cannot leave the client unable to start the server
|
|
239
|
+
that would replace it.
|
|
240
|
+
|
|
241
|
+
### Which origins may call it
|
|
242
|
+
|
|
243
|
+
`localhost` on any port is always allowed, so a dev build needs no
|
|
244
|
+
configuration. Beyond that the defaults are `beacon://app` (the packaged
|
|
245
|
+
renderer's origin) and `app://`. To set them explicitly:
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
python -m beacon.server --cors-origin beacon://app --cors-origin app://custom
|
|
249
|
+
BEACON_CORS_ORIGINS="beacon://app,app://custom" python -m beacon.server
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
Explicit origins **replace** the defaults rather than adding to them — an
|
|
253
|
+
operator narrowing what may call the server should not find extras still
|
|
254
|
+
permitted. The allowed set is logged at startup, because a CORS failure
|
|
255
|
+
otherwise appears only in a browser console on the far side of the process
|
|
256
|
+
boundary.
|
|
257
|
+
|
|
258
|
+
### Price, total and net total return
|
|
259
|
+
|
|
260
|
+
An index accumulates returns one of three ways. `PRICE` ignores distributions
|
|
261
|
+
and is the default. `TOTAL_RETURN` reinvests each cash distribution across the
|
|
262
|
+
index by shrinking the divisor, and `NET_TOTAL_RETURN` does the same after a
|
|
263
|
+
flat `withholding_tax_rate`.
|
|
264
|
+
|
|
265
|
+
Reinvestment is a divisor adjustment rather than a purchase: buying more units
|
|
266
|
+
of whichever constituent paid would re-weight the index towards it and make the
|
|
267
|
+
composition depend on the return type, so a price and a total-return version of
|
|
268
|
+
one index would hold different things. Only actions whose `kind` is `cash`
|
|
269
|
+
reinvest — a split changes the share count and the price together and
|
|
270
|
+
distributes nothing.
|
|
271
|
+
|
|
272
|
+
### Rebalance schedules and trading calendars
|
|
273
|
+
|
|
274
|
+
An index carries a cadence (`MONTHLY`…`ANNUAL`) and a day rule —
|
|
275
|
+
`FIRST_BUSINESS_DAY`, `LAST_BUSINESS_DAY` or `THIRD_FRIDAY`. Naming a `calendar`
|
|
276
|
+
(an exchange MIC such as `XNYS`) backs the arithmetic with real holidays, and a
|
|
277
|
+
date landing on one rolls back to the previous session.
|
|
278
|
+
|
|
279
|
+
`GET /indices/{id}/schedule` returns the next rebalance and the days until it,
|
|
280
|
+
derived from the schedule and the calendar rather than stored — a stored date
|
|
281
|
+
would silently expire.
|
|
282
|
+
|
|
283
|
+
The calendar is **required** since BN-180 — on the wire and in
|
|
284
|
+
`IndexDefinition`, which has no default for it — and `exchange_calendars` is a
|
|
285
|
+
core dependency rather than an extra. It used to be optional, defaulting to
|
|
286
|
+
Monday to Friday, which scheduled rebalances on 1 January, 4 July and 25
|
|
287
|
+
December: days no exchange has a session for. There is deliberately no
|
|
288
|
+
constructor default either, since one would let a European index schedule
|
|
289
|
+
itself on New York's holidays without saying so. Definitions stored before this
|
|
290
|
+
were migrated to `XNYS`, because choosing for an index that already exists is
|
|
291
|
+
repair while choosing for a new one is a guess.
|
|
292
|
+
|
|
293
|
+
`GET /indices/calendars` publishes every accepted MIC with a display name, an
|
|
294
|
+
IANA timezone and a region derived from that timezone, so a client can group a
|
|
295
|
+
picker without hard-coding anything. The day rule still defaults to the first
|
|
296
|
+
business day of the month.
|
|
297
|
+
|
|
298
|
+
### Discovering what a methodology can contain
|
|
299
|
+
|
|
300
|
+
`GET /indices/rule-types` publishes the eligibility rules and weighting schemes
|
|
301
|
+
the library provides, with enough detail to render an editor: each parameter's
|
|
302
|
+
name, display type, whether it is required, its default, a label, its position
|
|
303
|
+
in the form, and any closed set of choices.
|
|
304
|
+
|
|
305
|
+
`GET /optimise/constraint-types` serves the optimiser's constraints in the same
|
|
306
|
+
shape under `specs`, so one client component can render both editors. Its
|
|
307
|
+
original `types` field is unchanged.
|
|
308
|
+
|
|
309
|
+
Both come from a registry the classes populate themselves
|
|
310
|
+
(`beacon.catalogue`). Names, types, defaults and required-ness are read from
|
|
311
|
+
the constructors, so they cannot drift from what the code accepts; only labels
|
|
312
|
+
and ordering are declared, because a signature cannot carry them. A rule class
|
|
313
|
+
that exists without a catalogue entry fails a completeness test — the symptom
|
|
314
|
+
otherwise is silent, since the rule still works and the editor simply never
|
|
315
|
+
offers it.
|
|
316
|
+
|
|
317
|
+
### Finding out which instruments exist
|
|
318
|
+
|
|
319
|
+
`GET /data/identifiers` answers "which identifiers do you have, and which match
|
|
320
|
+
what the user is typing" — search when given `q`, enumeration when not.
|
|
321
|
+
|
|
322
|
+
```
|
|
323
|
+
/data/identifiers?q=cmpa&limit=20
|
|
324
|
+
/data/identifiers?datasets=market # everything with prices
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
Each row carries `datasets`, which is what lets a client offer a
|
|
328
|
+
reference-only name in a reference view and mark it unavailable for prices,
|
|
329
|
+
rather than suggesting something the engine cannot then serve. `total` is the
|
|
330
|
+
match count before the limit, so a UI can say "showing 20 of 340".
|
|
331
|
+
|
|
332
|
+
Ranking is decided server-side and is part of the contract — exact identifier,
|
|
333
|
+
identifier prefix, name prefix, identifier substring, name substring,
|
|
334
|
+
alphabetical within each — because once `limit` is applied a client cannot
|
|
335
|
+
re-rank what it was not sent.
|
|
336
|
+
|
|
337
|
+
Served from an index built once and cached against a fingerprint of the
|
|
338
|
+
datasets' refresh times, so a sync invalidates it and nothing else does. A
|
|
339
|
+
server with no data returns `200` with an empty list rather than an error:
|
|
340
|
+
"nothing matches" and "this engine is misconfigured" are different statements.
|
|
341
|
+
|
|
342
|
+
### Looking up many instruments at once
|
|
343
|
+
|
|
344
|
+
`GET /data/reference` is the batch form of `/data/reference/{identifier}`:
|
|
345
|
+
|
|
346
|
+
```
|
|
347
|
+
/data/reference?identifiers=AAA,BBB,CCC&fields=NAME,SECTOR,adv_3m
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
Entries come back in the order the request named them, one per identifier, so
|
|
351
|
+
a table renders straight down the list. An unknown identifier is an entry with
|
|
352
|
+
`found: false` rather than a failed batch — one bad ticker in five hundred
|
|
353
|
+
should not lose the other 499. At most 1000 identifiers per call.
|
|
354
|
+
|
|
355
|
+
`fields` selects stored reference columns and may also name a derived field.
|
|
356
|
+
`adv_3m` is mean daily volume over the trailing three *calendar* months,
|
|
357
|
+
computed server-side from held prices; it is opt-in, because computing it means
|
|
358
|
+
slicing price history for every identifier in the batch.
|
|
359
|
+
|
|
360
|
+
### Generating data to serve
|
|
361
|
+
|
|
362
|
+
`beacon.synthetic` produces a universe at demo scale — thousands of anonymised
|
|
363
|
+
companies with years of history — and writes it straight to the location above:
|
|
364
|
+
|
|
365
|
+
```bash
|
|
366
|
+
python -m beacon.synthetic --seed 42 # 6,000 names, 10 years, ~19s
|
|
367
|
+
python -m beacon.server --port 0 --token dev # picks it up automatically
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
`--extended-universe` doubles the universe to 10,000 names and
|
|
371
|
+
`--long-history` reaches back past every crisis the generator models. See
|
|
372
|
+
[docs/serving-data.md](https://github.com/karanbh01/py-beacon/blob/main/docs/serving-data.md) for what each costs.
|
|
373
|
+
|
|
374
|
+
Prices reproduce the stylized facts of equity returns rather than being a
|
|
375
|
+
random walk: volatility clustering (GJR-GARCH), fat tails (Student-t
|
|
376
|
+
innovations), negative skew, and a market/sector factor structure that puts
|
|
377
|
+
average pairwise correlation near 0.39 with same-sector pairs above
|
|
378
|
+
cross-sector ones. Shares outstanding, free float, dividends and splits are
|
|
379
|
+
generated alongside the prices and agree with them — undoing the splits and
|
|
380
|
+
adding the dividends back recovers the return path exactly.
|
|
381
|
+
|
|
382
|
+
Nothing resembles a real company: names are `Company A` … and every ticker
|
|
383
|
+
carries a `CMP` prefix. The same seed and dates always produce the same store,
|
|
384
|
+
byte for byte.
|
|
385
|
+
|
|
386
|
+
It is importable too, which is what examples and integration tests use:
|
|
387
|
+
|
|
388
|
+
```python
|
|
389
|
+
from beacon.synthetic import SyntheticConfig, generate
|
|
390
|
+
|
|
391
|
+
dataset = generate(SyntheticConfig(assets=64, seed=1))
|
|
392
|
+
fetcher = dataset.fetcher()
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
This is not `beacon.testing.dataset`, which stays a tiny frozen fixture whose
|
|
396
|
+
exact values the chart baselines depend on.
|
|
397
|
+
|
|
398
|
+
### The store format
|
|
399
|
+
|
|
400
|
+
A store is a directory of gzipped CSV written by `beacon.data.store`:
|
|
401
|
+
|
|
402
|
+
```python
|
|
403
|
+
from pathlib import Path
|
|
404
|
+
from beacon.data import store
|
|
405
|
+
|
|
406
|
+
store.save(fetcher, Path("~/beacon-data").expanduser(), source="local")
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
`store.default_path()` is the app-data location branch 3 reads.
|
|
410
|
+
|
|
411
|
+
## Versioning
|
|
412
|
+
|
|
413
|
+
Beacon follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
414
|
+
While the major version is `0` the public API may change in any release — the
|
|
415
|
+
surface is still settling. Breaking changes are recorded under **Changed** or
|
|
416
|
+
**Removed** in the changelog. From 1.0 onward, a deprecated name keeps working
|
|
417
|
+
for at least one minor release with a `DeprecationWarning` naming its
|
|
418
|
+
replacement, and removals only land in a major release; the full policy is in
|
|
419
|
+
[CONTRIBUTING.md](https://github.com/karanbh01/py-beacon/blob/main/CONTRIBUTING.md#versioning-and-deprecation-policy).
|
|
420
|
+
|
|
421
|
+
## Quickstart
|
|
422
|
+
|
|
423
|
+
Define an index, calculate it, backtest a portfolio that tracks it, and view
|
|
424
|
+
the results. This snippet is fully self-contained (synthetic data, no external
|
|
425
|
+
dependencies) and copy-paste runnable:
|
|
426
|
+
|
|
427
|
+
```python
|
|
428
|
+
import logging
|
|
429
|
+
import pandas as pd
|
|
430
|
+
from beacon.index.constructor import IndexDefinition
|
|
431
|
+
from beacon.index.methodology import EqualWeighted
|
|
432
|
+
from beacon.index.calculation import IndexCalculator
|
|
433
|
+
from beacon.backtest.engine import BacktestEngine
|
|
434
|
+
from beacon.index.schedule import sessions
|
|
435
|
+
|
|
436
|
+
logging.getLogger("beacon").setLevel(logging.ERROR) # keep the demo output clean
|
|
437
|
+
|
|
438
|
+
# --- 1. Synthetic market data: two assets over ~3 months of sessions ---
|
|
439
|
+
ASSETS = ["AAA", "BBB"]
|
|
440
|
+
# The index's own sessions, which is what the calculator walks: XNYS is
|
|
441
|
+
# shut on three weekdays in this window, so a business-day range would
|
|
442
|
+
# hold days the index has no level on.
|
|
443
|
+
DAYS = sessions(pd.Timestamp("2024-01-02"), pd.Timestamp("2024-03-29"),
|
|
444
|
+
"XNYS")
|
|
445
|
+
|
|
446
|
+
def price(asset,
|
|
447
|
+
day):
|
|
448
|
+
frac = DAYS.get_loc(day) / (len(DAYS) - 1)
|
|
449
|
+
return (100 * 1.10 ** frac) if asset == "AAA" else (50 * 1.20 ** frac)
|
|
450
|
+
|
|
451
|
+
class QuickData:
|
|
452
|
+
"""Tiny in-memory provider satisfying the calculator + engine data APIs."""
|
|
453
|
+
def fetch_reference_data(self,
|
|
454
|
+
identifier,
|
|
455
|
+
date=None):
|
|
456
|
+
return pd.DataFrame(
|
|
457
|
+
{"NAME": [identifier], "CURRENCY": ["USD"], "EXCHANGE": ["NYSE"]},
|
|
458
|
+
index=pd.Index([identifier], name="IDENTIFIER"))
|
|
459
|
+
def fetch_market_data(self,
|
|
460
|
+
identifier,
|
|
461
|
+
start=None,
|
|
462
|
+
end=None,
|
|
463
|
+
columns=None):
|
|
464
|
+
p = price(identifier, pd.Timestamp(start))
|
|
465
|
+
return pd.DataFrame({"CLOSE": [p]}, index=pd.Index([pd.Timestamp(start)], name="DATE"))
|
|
466
|
+
def fetch_shares_outstanding(self,
|
|
467
|
+
ticker,
|
|
468
|
+
date):
|
|
469
|
+
return 1_000
|
|
470
|
+
def delisting_dates(self):
|
|
471
|
+
return {} # nothing in this universe stops being listed
|
|
472
|
+
|
|
473
|
+
data = QuickData()
|
|
474
|
+
|
|
475
|
+
# --- 2. Define the index: equal-weight, rebalanced monthly ---
|
|
476
|
+
definition = IndexDefinition(
|
|
477
|
+
index_id="DEMO", index_name="Demo Equal-Weight Index",
|
|
478
|
+
base_date="2024-01-02", base_value=1000.0, currency="USD",
|
|
479
|
+
eligibility_rules=[], weighting_scheme=EqualWeighted(),
|
|
480
|
+
rebalancing_frequency="MONTHLY", calendar="XNYS",
|
|
481
|
+
universe_identifiers=ASSETS,
|
|
482
|
+
)
|
|
483
|
+
|
|
484
|
+
# --- 3. Calculate the index ---
|
|
485
|
+
index_result = IndexCalculator(definition, data).run(end_date="2024-03-29")
|
|
486
|
+
print("Final index level:", round(index_result.index_levels.iloc[-1], 2))
|
|
487
|
+
|
|
488
|
+
# --- 4. Backtest a portfolio that tracks the index ---
|
|
489
|
+
backtest = BacktestEngine(
|
|
490
|
+
start_date="2024-01-02", end_date="2024-03-29",
|
|
491
|
+
initial_capital=1_000_000.0, data_provider=data,
|
|
492
|
+
index_result=index_result, calendar="XNYS",
|
|
493
|
+
).run()
|
|
494
|
+
|
|
495
|
+
# --- 5. View results ---
|
|
496
|
+
summary = backtest.summary()
|
|
497
|
+
print("Total return: ", round(summary["total_return"], 4))
|
|
498
|
+
print("Annualised return: ", round(summary["annualised_return"], 4))
|
|
499
|
+
print("Tracking error: ", round(summary["tracking_error"], 6))
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
For a derivatives walkthrough — pricing an `IndexFuture` off an `IndexResult` —
|
|
503
|
+
see [`examples/futures_pricing_example.py`](https://github.com/karanbh01/py-beacon/blob/main/examples/futures_pricing_example.py).
|