py-beacon-kit 0.2.0__tar.gz → 0.3.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.
Files changed (344) hide show
  1. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/.gitignore +3 -0
  2. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/CHANGELOG.md +52 -1
  3. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/PKG-INFO +1 -1
  4. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/__init__.py +1 -1
  5. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/analysis/attribution.py +22 -6
  6. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/backtest/__init__.py +2 -1
  7. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/backtest/engine.py +1 -2
  8. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/backtest/pricing.py +1 -2
  9. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/backtest/result.py +22 -31
  10. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/backtest/rules.py +7 -0
  11. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/changelog.py +17 -6
  12. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/data/__init__.py +1 -1
  13. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/data/adjustment.py +2 -2
  14. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/data/corporate_actions.py +18 -5
  15. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/data/fetcher.py +43 -6
  16. py_beacon_kit-0.3.0/src/beacon/data/prices.py +152 -0
  17. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/data/store.py +26 -0
  18. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/derivatives/pricing.py +6 -4
  19. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/derivatives/swaps.py +7 -3
  20. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/exceptions.py +10 -0
  21. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/expressions/catalogue.py +0 -7
  22. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/expressions/namespaces.py +11 -7
  23. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/expressions/resolve.py +61 -31
  24. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/expressions/stubs.py +0 -4
  25. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/expressions/validation.py +48 -17
  26. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/index/__init__.py +2 -1
  27. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/index/cache.py +10 -2
  28. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/index/calculation/calculator.py +9 -1
  29. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/index/calculation/market_values.py +12 -7
  30. py_beacon_kit-0.3.0/src/beacon/index/calculation/price_gaps.py +88 -0
  31. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/index/calculation/total_return.py +4 -3
  32. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/index/constructor.py +24 -0
  33. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/index/expression_rules.py +7 -3
  34. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/index/methodology.py +45 -5
  35. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/index/requirements.py +10 -0
  36. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/index/result.py +30 -0
  37. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/plot/accessors.py +26 -12
  38. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/portfolio/reporting.py +32 -16
  39. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/report/blocks.py +15 -1
  40. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/server/__main__.py +7 -2
  41. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/server/app.py +9 -3
  42. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/server/config.py +17 -7
  43. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/server/errors.py +6 -0
  44. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/server/jobs.py +62 -5
  45. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/server/methods.py +45 -4
  46. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/server/optimisation.py +26 -16
  47. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/server/risk.py +19 -16
  48. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/server/routers/beacon.py +19 -16
  49. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/server/routers/data.py +62 -15
  50. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/server/routers/derivatives.py +22 -3
  51. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/server/routers/jobs.py +6 -3
  52. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/server/routers/optimise.py +8 -1
  53. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/server/routers/reports.py +14 -1
  54. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/server/routers/risk.py +25 -1
  55. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/server/routers/synthetic.py +23 -2
  56. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/server/schemas.py +53 -3
  57. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/server/store_schemas.py +5 -3
  58. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/server/views.py +38 -14
  59. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/server/weights.py +31 -25
  60. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/testing/dataset.py +5 -2
  61. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/universe.py +7 -0
  62. py_beacon_kit-0.3.0/tests/baseline/test_annual_returns_dark.png +0 -0
  63. py_beacon_kit-0.3.0/tests/baseline/test_annual_returns_light.png +0 -0
  64. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_adjustment.py +18 -1
  65. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_api_contract.py +8 -3
  66. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_attribution.py +20 -0
  67. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_changelog.py +34 -2
  68. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_corporate_actions.py +11 -0
  69. py_beacon_kit-0.3.0/tests/test_currency_conversion.py +183 -0
  70. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_derivatives_pricing.py +10 -1
  71. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_derivatives_swaps.py +11 -0
  72. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_docs_style.py +32 -0
  73. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_expression_rules.py +30 -5
  74. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_expression_validation.py +55 -1
  75. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_fx_data.py +20 -2
  76. py_beacon_kit-0.3.0/tests/test_fx_pairs.py +67 -0
  77. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_index_calculator_integration.py +85 -1
  78. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_index_definition.py +13 -5
  79. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_index_schedule.py +10 -0
  80. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_job_persistence.py +73 -0
  81. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_plot.py +69 -0
  82. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_plot_images.py +2 -2
  83. py_beacon_kit-0.3.0/tests/test_reporting.py +69 -0
  84. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_server.py +10 -0
  85. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_server_cors.py +16 -0
  86. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_server_data_router.py +13 -2
  87. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_server_data_stores.py +67 -0
  88. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_server_derivatives.py +11 -0
  89. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_server_features.py +4 -1
  90. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_server_jobs.py +10 -0
  91. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_server_reports.py +13 -0
  92. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_server_synthetic.py +33 -0
  93. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_server_views.py +28 -1
  94. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_spec_conformance.py +47 -0
  95. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_total_return.py +20 -0
  96. py_beacon_kit-0.2.0/tests/baseline/test_annual_returns_dark.png +0 -0
  97. py_beacon_kit-0.2.0/tests/baseline/test_annual_returns_light.png +0 -0
  98. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/LICENSE.txt +0 -0
  99. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/README.md +0 -0
  100. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/examples/01_index_and_backtest.ipynb +0 -0
  101. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/examples/02_backtest_analysis.ipynb +0 -0
  102. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/examples/03_index_futures.ipynb +0 -0
  103. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/examples/04_optimised_index.ipynb +0 -0
  104. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/examples/05_optimised_backtest.ipynb +0 -0
  105. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/examples/README.md +0 -0
  106. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/examples/data/templates/equities_corp_actions_data.xlsx +0 -0
  107. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/examples/data/templates/equities_fundamental_data.xlsx +0 -0
  108. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/examples/data/templates/equities_market_data.xlsx +0 -0
  109. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/examples/data/templates/equities_reference_data.xlsx +0 -0
  110. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/examples/data/templates/fx_rates_data.xlsx +0 -0
  111. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/pyproject.toml +0 -0
  112. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/_optional.py +0 -0
  113. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/analysis/__init__.py +0 -0
  114. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/analysis/concentration.py +0 -0
  115. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/analysis/etf/__init__.py +0 -0
  116. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/analysis/etf/analytics.py +0 -0
  117. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/analysis/liquidity.py +0 -0
  118. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/analysis/relative.py +0 -0
  119. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/analysis/risk.py +0 -0
  120. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/asset/__init__.py +0 -0
  121. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/asset/base.py +0 -0
  122. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/asset/bond.py +0 -0
  123. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/asset/commodity.py +0 -0
  124. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/asset/equity.py +0 -0
  125. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/asset/view.py +0 -0
  126. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/backtest/asset_view.py +0 -0
  127. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/backtest/main.py +0 -0
  128. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/catalogue.py +0 -0
  129. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/data/base.py +0 -0
  130. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/data/features.py +0 -0
  131. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/data/free_float.py +0 -0
  132. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/data/fx.py +0 -0
  133. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/data/identifiers.py +0 -0
  134. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/data/importing.py +0 -0
  135. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/data/ingest.py +0 -0
  136. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/data/layout.py +0 -0
  137. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/data/loader.py +0 -0
  138. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/data/postgres.py +0 -0
  139. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/data/session.py +0 -0
  140. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/derivatives/__init__.py +0 -0
  141. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/derivatives/base.py +0 -0
  142. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/derivatives/curves.py +0 -0
  143. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/derivatives/forwards.py +0 -0
  144. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/derivatives/futures.py +0 -0
  145. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/derivatives/term_structure.py +0 -0
  146. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/environment/__init__.py +0 -0
  147. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/environment/config.py +0 -0
  148. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/expressions/__init__.py +0 -0
  149. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/expressions/core.py +0 -0
  150. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/fund/__init__.py +0 -0
  151. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/fund/base.py +0 -0
  152. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/fund/etf.py +0 -0
  153. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/index/asset_view.py +0 -0
  154. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/index/calculation/__init__.py +0 -0
  155. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/index/calculation/corporate_actions.py +0 -0
  156. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/index/calculation/deletions.py +0 -0
  157. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/index/calculation/selection.py +0 -0
  158. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/index/capping.py +0 -0
  159. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/index/chaining.py +0 -0
  160. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/index/context.py +0 -0
  161. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/index/derived.py +0 -0
  162. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/index/feature_rules.py +0 -0
  163. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/index/schedule.py +0 -0
  164. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/optimise/__init__.py +0 -0
  165. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/optimise/config.py +0 -0
  166. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/optimise/constraints.py +0 -0
  167. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/optimise/frontier.py +0 -0
  168. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/optimise/result.py +0 -0
  169. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/optimise/solver.py +0 -0
  170. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/plot/__init__.py +0 -0
  171. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/plot/base.py +0 -0
  172. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/plot/comparison.py +0 -0
  173. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/plot/style.py +0 -0
  174. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/portfolio/__init__.py +0 -0
  175. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/portfolio/asset_view.py +0 -0
  176. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/portfolio/base.py +0 -0
  177. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/portfolio/history.py +0 -0
  178. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/py.typed +0 -0
  179. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/report/__init__.py +0 -0
  180. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/report/pdf.py +0 -0
  181. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/risk/__init__.py +0 -0
  182. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/risk/contribution.py +0 -0
  183. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/risk/covariance.py +0 -0
  184. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/risk/factors.py +0 -0
  185. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/risk/model.py +0 -0
  186. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/server/__init__.py +0 -0
  187. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/server/active_data.py +0 -0
  188. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/server/backtests.py +0 -0
  189. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/server/benchmarks.py +0 -0
  190. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/server/constraints.py +0 -0
  191. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/server/data_stores.py +0 -0
  192. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/server/definitions.py +0 -0
  193. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/server/derivatives.py +0 -0
  194. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/server/documents.py +0 -0
  195. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/server/preview.py +0 -0
  196. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/server/reference.py +0 -0
  197. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/server/reports.py +0 -0
  198. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/server/routers/__init__.py +0 -0
  199. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/server/routers/coverage.py +0 -0
  200. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/server/routers/importing.py +0 -0
  201. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/server/routers/indices.py +0 -0
  202. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/server/routers/refresh.py +0 -0
  203. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/server/routers/stores.py +0 -0
  204. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/server/routers/universes.py +0 -0
  205. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/server/routers/watchlists.py +0 -0
  206. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/server/runs.py +0 -0
  207. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/server/security.py +0 -0
  208. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/server/serialisation.py +0 -0
  209. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/server/store.py +0 -0
  210. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/server/types.py +0 -0
  211. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/sources.py +0 -0
  212. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/synthetic/__init__.py +0 -0
  213. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/synthetic/__main__.py +0 -0
  214. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/synthetic/dataset.py +0 -0
  215. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/synthetic/extend.py +0 -0
  216. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/synthetic/features.py +0 -0
  217. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/synthetic/fx.py +0 -0
  218. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/synthetic/listings.py +0 -0
  219. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/synthetic/prices.py +0 -0
  220. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/synthetic/profiles.py +0 -0
  221. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/synthetic/regimes.py +0 -0
  222. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/synthetic/regions.py +0 -0
  223. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/synthetic/returns.py +0 -0
  224. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/synthetic/state.py +0 -0
  225. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/synthetic/universe.py +0 -0
  226. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/testing/__init__.py +0 -0
  227. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/testing/weights.py +0 -0
  228. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/tokens/__init__.py +0 -0
  229. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/src/beacon/tokens/colors.json +0 -0
  230. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/baseline/test_compare_dark.png +0 -0
  231. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/baseline/test_compare_light.png +0 -0
  232. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/baseline/test_contributions_dark.png +0 -0
  233. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/baseline/test_contributions_light.png +0 -0
  234. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/baseline/test_correlation_dark.png +0 -0
  235. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/baseline/test_correlation_light.png +0 -0
  236. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/baseline/test_exposures_dark.png +0 -0
  237. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/baseline/test_exposures_light.png +0 -0
  238. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/baseline/test_frontier_dark.png +0 -0
  239. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/baseline/test_frontier_light.png +0 -0
  240. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/baseline/test_level_dark.png +0 -0
  241. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/baseline/test_level_light.png +0 -0
  242. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/baseline/test_performance_dark.png +0 -0
  243. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/baseline/test_performance_light.png +0 -0
  244. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/baseline/test_weights_dark.png +0 -0
  245. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/baseline/test_weights_light.png +0 -0
  246. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/conftest.py +0 -0
  247. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/docstyle.py +0 -0
  248. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_asset.py +0 -0
  249. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_asset_view.py +0 -0
  250. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_backtest_engine.py +0 -0
  251. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_backtest_engine_integration.py +0 -0
  252. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_backtest_fills.py +0 -0
  253. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_backtest_main.py +0 -0
  254. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_backtest_modifier.py +0 -0
  255. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_backtest_pricing.py +0 -0
  256. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_backtest_result.py +0 -0
  257. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_benchmark.py +0 -0
  258. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_canonical_dataset_integration.py +0 -0
  259. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_capping.py +0 -0
  260. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_catalogue.py +0 -0
  261. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_chaining.py +0 -0
  262. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_concentration.py +0 -0
  263. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_constraint_catalogue.py +0 -0
  264. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_corporate_action_kind.py +0 -0
  265. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_coverage_fields.py +0 -0
  266. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_daily_weights.py +0 -0
  267. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_data_fetcher.py +0 -0
  268. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_data_import.py +0 -0
  269. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_data_store.py +0 -0
  270. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_data_tables.py +0 -0
  271. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_derivatives_base.py +0 -0
  272. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_derivatives_curves.py +0 -0
  273. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_derivatives_etf_future.py +0 -0
  274. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_derivatives_futures.py +0 -0
  275. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_derived_index.py +0 -0
  276. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_docs_examples.py +0 -0
  277. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_effective_lag.py +0 -0
  278. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_etf_tracking.py +0 -0
  279. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_examples.py +0 -0
  280. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_expression_namespaces.py +0 -0
  281. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_expression_schema.py +0 -0
  282. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_expressions.py +0 -0
  283. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_factors.py +0 -0
  284. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_feature_rules.py +0 -0
  285. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_features.py +0 -0
  286. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_free_float_backfill.py +0 -0
  287. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_frontier.py +0 -0
  288. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_fund.py +0 -0
  289. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_fx_routes.py +0 -0
  290. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_identifier_search.py +0 -0
  291. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_index_asset_view.py +0 -0
  292. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_index_batch_reads.py +0 -0
  293. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_index_cache.py +0 -0
  294. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_index_calculator.py +0 -0
  295. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_index_currency.py +0 -0
  296. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_index_eligibility.py +0 -0
  297. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_index_result.py +0 -0
  298. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_index_selection.py +0 -0
  299. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_index_weighting.py +0 -0
  300. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_ingest.py +0 -0
  301. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_integration.py +0 -0
  302. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_look_ahead.py +0 -0
  303. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_optimise.py +0 -0
  304. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_optional_dependencies.py +0 -0
  305. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_portfolio.py +0 -0
  306. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_portfolio_asset_view.py +0 -0
  307. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_postgres_store.py +0 -0
  308. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_properties.py +0 -0
  309. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_read_volume.py +0 -0
  310. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_readme.py +0 -0
  311. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_reference_currency.py +0 -0
  312. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_reference_dimensions.py +0 -0
  313. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_reference_profile.py +0 -0
  314. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_report.py +0 -0
  315. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_requirements.py +0 -0
  316. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_risk_contribution.py +0 -0
  317. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_risk_model.py +0 -0
  318. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_server_backtest.py +0 -0
  319. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_server_document_faults.py +0 -0
  320. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_server_import.py +0 -0
  321. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_server_indices.py +0 -0
  322. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_server_listing_discovery.py +0 -0
  323. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_server_optimise.py +0 -0
  324. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_server_optimised_indices.py +0 -0
  325. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_server_preview.py +0 -0
  326. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_server_preview_document.py +0 -0
  327. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_server_preview_optimised.py +0 -0
  328. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_server_reference_batch.py +0 -0
  329. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_server_refresh.py +0 -0
  330. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_server_risk.py +0 -0
  331. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_server_store.py +0 -0
  332. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_server_universes.py +0 -0
  333. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_server_weights_rows.py +0 -0
  334. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_session_agreement.py +0 -0
  335. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_splits.py +0 -0
  336. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_stale_prices.py +0 -0
  337. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_survivorship.py +0 -0
  338. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_synthetic.py +0 -0
  339. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_synthetic_extend.py +0 -0
  340. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_synthetic_features.py +0 -0
  341. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_testing_dataset.py +0 -0
  342. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_tokens.py +0 -0
  343. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_universe_batch_reads.py +0 -0
  344. {py_beacon_kit-0.2.0 → py_beacon_kit-0.3.0}/tests/test_universe_filters.py +0 -0
@@ -96,3 +96,6 @@ fuzz-server.log
96
96
  # `git add -A` swept it into a commit once; an ignore rule is cheaper than
97
97
  # remembering.
98
98
  us_tickers.csv
99
+
100
+ # Chart comparison results (pytest --mpl-results-path)
101
+ chart-results/
@@ -8,6 +8,56 @@ Before 1.0, a breaking change raises the middle number, as in 0.1 to 0.2.
8
8
 
9
9
  ## [Unreleased]
10
10
 
11
+ ## [0.3.0] - 2026-09-30
12
+
13
+ Money in one currency across multi-currency indices, prices carried over gaps, expressions that refuse fields the data does not have, and engine state that survives. Two breaking changes: `data.actions` is gone from expressions, and some requests the engine cannot answer as put answer 422 instead of 404.
14
+
15
+ ### Added
16
+
17
+ - `DataFetcher.fetch_prices` reads prices for several instruments at once, converted into one currency day by day under the dataset's FX policy.
18
+ - `IndexResult.price_gaps` and `beacon.index.PriceGap`: the days a held name had no bar and was valued at its last close. `PriceGap` is still importable from `beacon.backtest`.
19
+ - The asset view reports `currency`, which its returns, beta and tracking error are measured in (the index's), and `price_currency`, which its price series is in (the name's own).
20
+ - A feature import's response says whether the rows were `saved` into the served store, and gives the new `data_version`.
21
+ - `BacktestResult.get_annual_returns()`: calendar-year returns that compound to the whole run's return.
22
+ - `TotalReturnSwap` takes `underlying_type` (`INDEX`, the default, `ETF` or `EQUITY`). It was always `INDEX`.
23
+ - Each release from `GET /changelog` carries its `summary`, the prose between its heading and its first section.
24
+ - A risk model request takes an optional `currency`, and a risk model and an optimisation run report the currency they were measured in.
25
+
26
+ ### Changed
27
+
28
+ - Requests that cannot be answered as put answer 422 `INVALID_ARGUMENT` instead of 404 `DATA_NOT_FOUND`: an unknown price interval, an adjusted series without `CLOSE`, and an empty or oversized feature batch.
29
+ - An expression with a field the data does not have is refused with `ExpressionError` by `universe.where`, `ExpressionScreen` and an index's `ExpressionRule`, naming the field and suggesting close matches. Before, it selected nothing.
30
+ - `IndexDefinition` refuses an unknown calendar or rebalancing frequency when it is built, naming the valid values and suggesting close calendar codes. Before, the mistake surfaced only when rebalance dates were first computed.
31
+ - The API schema states more of what it accepts: `since` on `/changelog` is a dotted version, and `/beacon/compare` needs at least two `ids`, each a valid identifier. These are now refused as `VALIDATION_ERROR` by the schema check. The boolean fields of a synthetic-data or import request no longer accept `0`, `1` or strings.
32
+
33
+ ### Removed
34
+
35
+ - `data.actions` in expressions. No expression could read its fields, so every name was missing a value; it now raises `UnknownDatasetError`, and the field catalogue and `GET /data/fields` no longer list an `actions` namespace. Read corporate actions from the fetcher's `corporate_actions`.
36
+
37
+ ### Fixed
38
+
39
+ - Money is compared in one currency wherever names are compared or added up. `LiquidityRule`'s traded-value floor, the engine's risk models, optimisation runs, the weights pane (drift, risk contributions, active risk), attribution and the asset view all read each name's prices in its own currency, so a multi-currency index mixed yen with dollars and left exchange-rate returns and risk out. They now convert into the index's currency.
40
+ - An index values a held name with no bar on a session at its last close, as the backtest engine does, and lists the day in `IndexResult.price_gaps`. It used to value the name at zero, so the level dipped by the name's weight for the day and recovered when the bar returned.
41
+ - An expression's `market_cap` inside an index is in the index's currency, as `MarketCapRule`'s bounds are. Outside an index it stays in USD.
42
+ - `POST /data/features` now saves the rows into the served store when it is a folder, so they survive a restart, and changes `data_version` and sends a `data.freshness` event for `features`. Before, a client caching on `data_version` missed the change and a restart lost the rows.
43
+ - The engine always keeps the saved results its views read: the latest backtest of each index, every optimisation run and the latest estimate of each risk model. The limit of 50 saved jobs now applies only to the rest, so a run of loads, refreshes or renders no longer makes those views answer 404.
44
+ - `data.market.free_float` and `free_float_market_cap` in an expression carry the free float forward as far as the data's `free_float_backfill_days`, as every other read does, rather than 10 days.
45
+ - Attribution for a capped index no longer fails when its window starts after the index's first rebalance. The cap drag is measured over the same periods as the contributions.
46
+ - A cancelled dividend is no longer reinvested by a total-return index, counted in the trailing dividend and yield, or applied to adjusted closes; a cancelled split is no longer applied to adjusted closes either.
47
+ - The engine checks for a token before it loads any data, so a missing token is reported at once rather than after a large store has loaded.
48
+ - `http://127.0.0.1` and `http://[::1]` on any port are allowed origins, as `http://localhost` already was.
49
+ - A wrong or missing token on the event socket closes it with code 1008 and a reason, as intended, instead of refusing the handshake with an HTTP 403 a browser cannot read.
50
+ - `BacktestPlots.annual_returns` measures each year from the previous year's close, and the first from the initial capital. It measured each year from its own first close, so every year after the first lost its first day's return.
51
+ - A chart drawn before `beacon.plot.use()` gets the light colours on matplotlib's white background, not the dark mode's.
52
+ - `OptimisationPlots.frontier` starts the capital market line at the rate the frontier was traced at, unless another is passed, so the line is tangent.
53
+ - The Excel reports accept a `pathlib.Path` as well as a string, and the holdings report writes `valuation_date` into its sheet instead of only logging it.
54
+ - `futures_roll_return` uses 365-day years, like every other year fraction in the derivatives. It used 365.25, so its rates came out smaller than the rest by a factor of 365/365.25.
55
+ - The sample dataset in `beacon.testing.dataset` stores its GBPUSD rate in `RATE`, so `fx_pairs` and the coverage view list it. A pair stored without `RATE` still converts but now logs a warning that it is not listed.
56
+ - A report template with an unknown page setting answers 422 instead of 500, and so does a futures or swap price whose inputs overflow.
57
+ - A plain `OPTIONS` request answers 405 with an `Allow` header listing every method its path supports. It listed only one route's methods on a path with several, such as `/data/features`. CORS preflights are unchanged.
58
+ - The nightly API fuzz run passes again: it leaves out operations that start heavy work, and skips the check that a schema-valid request is accepted only where a rule spans fields or depends on the data.
59
+ - Generating or extending synthetic data from an isolated engine (`python -I`, as the Beacon app runs it) keeps the child process isolated too, so it cannot load packages from the user's own site-packages.
60
+
11
61
  ## [0.2.0] - 2026-09-26
12
62
 
13
63
  Choose where the engine's data comes from: named stores, synthetic data you can extend to today, CSV and Excel import, and read-only Postgres. Also documentation at pybeacon.dev, and fixes to index levels and backtests around splits and delistings.
@@ -78,7 +128,8 @@ The first release.
78
128
  - An index or backtest never uses a price, rate or free float dated after the day it is working on.
79
129
  - Requires Python 3.11 or later.
80
130
 
81
- [Unreleased]: https://github.com/karanbh01/py-beacon/compare/v0.2.0...HEAD
131
+ [Unreleased]: https://github.com/karanbh01/py-beacon/compare/v0.3.0...HEAD
132
+ [0.3.0]: https://github.com/karanbh01/py-beacon/compare/v0.2.0...v0.3.0
82
133
  [0.2.0]: https://github.com/karanbh01/py-beacon/compare/v0.1.1...v0.2.0
83
134
  [0.1.1]: https://github.com/karanbh01/py-beacon/compare/v0.1.0...v0.1.1
84
135
  [0.1.0]: https://github.com/karanbh01/py-beacon/releases/tag/v0.1.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: py-beacon-kit
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: End-to-end tool kit for index, ETF & Delta-1 derivatives development
5
5
  Project-URL: Homepage, https://pybeacon.dev
6
6
  Project-URL: Documentation, https://pybeacon.dev/library/
@@ -3,7 +3,7 @@
3
3
  py-beacon, an end-to-end toolkit for index, ETF, and Delta-1 derivatives
4
4
  development.
5
5
  """
6
- __version__ = "0.2.0"
6
+ __version__ = "0.3.0"
7
7
 
8
8
  from . import derivatives
9
9
  from .derivatives import (
@@ -350,7 +350,8 @@ def cost_drag(total_costs: float,
350
350
 
351
351
  def cap_drag(capped_weights: dict[pd.Timestamp, dict[str, float]],
352
352
  uncapped_weights: dict[pd.Timestamp, dict[str, float]],
353
- prices: pd.DataFrame) -> float:
353
+ prices: pd.DataFrame,
354
+ window: pd.Index | None = None) -> float:
354
355
  """What capping cost, or gained, over the window.
355
356
 
356
357
  The capped index's return minus the return of the same methodology left
@@ -365,27 +366,42 @@ def cap_drag(capped_weights: dict[pd.Timestamp, dict[str, float]],
365
366
  Args:
366
367
  capped_weights: Rebalance date -> capped weights.
367
368
  uncapped_weights: Rebalance date -> weights before capping.
368
- prices: Constituent prices over the window.
369
+ prices: Constituent prices, from the first rebalance on. Weights
370
+ drift from the rebalance before each date, so the prices have to
371
+ reach back to it even when *window* starts later.
372
+ window: The dates to measure over, as for :func:`attribute`: the
373
+ return of each date after the first. None measures over every
374
+ date in *prices*.
369
375
 
370
376
  Returns:
371
377
  float: Capped total return minus uncapped total return.
372
378
  """
373
379
  asset_returns = prices.pct_change()
374
380
 
375
- capped_return = _path_return(capped_weights, prices, asset_returns)
376
- uncapped_return = _path_return(uncapped_weights, prices, asset_returns)
381
+ capped_return = _path_return(capped_weights, prices, asset_returns, window)
382
+ uncapped_return = _path_return(uncapped_weights, prices, asset_returns,
383
+ window)
377
384
 
378
385
  return capped_return - uncapped_return
379
386
 
380
387
 
381
388
  def _path_return(snapshots: dict[pd.Timestamp, dict[str, float]],
382
389
  prices: pd.DataFrame,
383
- asset_returns: pd.DataFrame) -> float:
390
+ asset_returns: pd.DataFrame,
391
+ window: pd.Index | None = None) -> float:
384
392
  """Compounded return of an index following *snapshots*."""
393
+ # BN-258: drifted over all of *prices*, then cut to the window. Cutting
394
+ # the prices first left a rebalance before the window with no price to
395
+ # drift from, and the lookup raised KeyError.
385
396
  weights = drifted_weights(snapshots, prices)
397
+
398
+ if window is not None:
399
+ weights = weights.loc[weights.index.intersection(window)]
400
+
386
401
  assets = sorted(set(weights.columns) & set(asset_returns.columns))
387
402
 
388
- periods = (weights[assets].shift(1) * asset_returns[assets]).sum(axis=1)
403
+ periods = (weights[assets].shift(1)
404
+ * asset_returns[assets].reindex(weights.index)).sum(axis=1)
389
405
  periods = periods.loc[weights[assets].shift(1).dropna(how="all").index]
390
406
 
391
407
  return float((1.0 + periods).prod() - 1.0)
@@ -13,11 +13,12 @@ and tracking against the target index. `BacktestModifier` subclasses such as
13
13
  `DriftThresholdModifier` can skip a rebalance or adjust its trades, and
14
14
  `BacktestAssetView` gives one asset's story through a backtest.
15
15
  """
16
+ from ..index.result import PriceGap
16
17
  from ..portfolio.base import TradeInstruction
17
18
  from .asset_view import BacktestAssetView
18
19
  from .engine import BacktestEngine
19
20
  from .main import Backtest
20
- from .result import BacktestResult, PriceGap, RebalancePricing, UnfilledOrder
21
+ from .result import BacktestResult, RebalancePricing, UnfilledOrder
21
22
  from .rules import BacktestModifier, DriftThresholdModifier
22
23
 
23
24
  __all__ = [
@@ -9,7 +9,7 @@ import pandas as pd
9
9
  from ..data.corporate_actions import ratios_between
10
10
  from ..data.fetcher import DataFetcher
11
11
  from ..index.requirements import require_price_column
12
- from ..index.result import IndexResult
12
+ from ..index.result import IndexResult, PriceGap
13
13
  from ..index.schedule import SESSION_UNIT, sessions
14
14
  from ..portfolio.base import CASH_TOLERANCE as PORTFOLIO_CASH_TOLERANCE
15
15
 
@@ -22,7 +22,6 @@ from .result import (
22
22
  BacktestResult,
23
23
  Book,
24
24
  IndexBooks,
25
- PriceGap,
26
25
  RebalancePricing,
27
26
  UnfilledOrder,
28
27
  )
@@ -31,9 +31,8 @@ import pandas as pd
31
31
 
32
32
  from ..data.fetcher import DataFetcher
33
33
  from ..exceptions import CalculationError
34
- from ..index.result import IndexResult
34
+ from ..index.result import IndexResult, PriceGap
35
35
  from ..index.schedule import is_session
36
- from .result import PriceGap
37
36
 
38
37
  logger = logging.getLogger(__name__)
39
38
 
@@ -47,7 +47,7 @@ import pandas as pd
47
47
 
48
48
  from ..analysis.relative import RelativeMetrics, relative_metrics
49
49
  from ..data.fetcher import DataFetcher
50
- from ..index.result import IndexResult
50
+ from ..index.result import IndexResult, PriceGap
51
51
  from ..plot.base import PlotAccessor
52
52
  from ..portfolio.base import Portfolio
53
53
  from .asset_view import BacktestAssetView
@@ -78,36 +78,8 @@ class UnfilledOrder:
78
78
  shortfall_value: float
79
79
 
80
80
 
81
- # PriceGap and RebalancePricing are BN-183.
82
- @dataclass(frozen=True)
83
- class PriceGap:
84
- """A day a name should have traded on and had no bar.
85
-
86
- The engine prices from the last session on or before the date it is
87
- marking. Two different things can put it there, and only one of them is a
88
- fault: a day the index's **calendar says was closed** is a market that was
89
- shut, and the previous session's price is what the position was genuinely
90
- worth through it, so no gap is recorded, because nothing is missing. A day
91
- the calendar says was **open** is data that is missing something, and the
92
- price carried forward is a stale quote.
93
-
94
- Carrying it forward is standard practice and beats refusing: a backtest
95
- over five hundred names must not die because one of them had one bad day.
96
- Doing it silently is not: the mark is not what that day's market said, so
97
- it is published here rather than absorbed, the way `unfilled` publishes the
98
- legs a rebalance could not fill.
99
-
100
- Attributes:
101
- date: The simulated day whose bar was missing.
102
- asset_id: The name with no bar.
103
- priced_from: The session the carried price actually came from, always
104
- earlier than *date*.
105
- """
106
- date: pd.Timestamp
107
- asset_id: str
108
- priced_from: pd.Timestamp
109
-
110
-
81
+ # RebalancePricing is BN-183. PriceGap, also BN-183, moved to the index
82
+ # layer with BN-251, when the index started recording gaps too.
111
83
  @dataclass(frozen=True)
112
84
  class RebalancePricing:
113
85
  """What one rebalance priced from.
@@ -369,6 +341,25 @@ class BacktestResult:
369
341
 
370
342
  return returns.dropna()
371
343
 
344
+ def get_annual_returns(self) -> pd.Series:
345
+ """The portfolio's calendar-year returns.
346
+
347
+ Each year runs from the previous year's last close, and the first from
348
+ the initial capital, so the years compound to the whole run's return.
349
+ A partial first or last year covers only its days.
350
+
351
+ Returns:
352
+ pd.Series: Return by calendar year, indexed by the year as an int.
353
+ """
354
+ returns = self.get_returns()
355
+
356
+ if returns.empty:
357
+ return pd.Series(dtype=float)
358
+
359
+ yearly = (1.0 + returns).groupby(returns.index.year).prod() - 1.0
360
+
361
+ return yearly.astype(float)
362
+
372
363
  def get_tracking_error(self) -> float | None:
373
364
  """Calculate annualised tracking error against the tracked index.
374
365
 
@@ -14,6 +14,7 @@ import pandas as pd
14
14
  from ..data.fetcher import DataFetcher
15
15
  from ..expressions.core import Expression
16
16
  from ..expressions.resolve import resolve
17
+ from ..expressions.validation import require_valid
17
18
  from ..portfolio.base import Portfolio, TradeInstruction
18
19
 
19
20
  logger = logging.getLogger(__name__)
@@ -147,12 +148,18 @@ class ExpressionScreen(BacktestModifier):
147
148
  fetcher: Data to resolve against.
148
149
  on_missing: Whether a name with no value passes. Excluded by default,
149
150
  matching the index rules.
151
+
152
+ Raises:
153
+ ExpressionError: If the expression names a field the data does not
154
+ have.
150
155
  """
151
156
 
152
157
  def __init__(self,
153
158
  expression: "Expression",
154
159
  fetcher: "DataFetcher",
155
160
  on_missing: bool = False):
161
+ require_valid(expression, fetcher, "ExpressionScreen")
162
+
156
163
  self.expression = expression
157
164
  self.fetcher = fetcher
158
165
  self.on_missing = on_missing
@@ -17,7 +17,7 @@ the emphasis and code spans in them survive to whatever renders them.
17
17
  # BN-222. `CHANGELOG.md` at the repository root is the one file anybody
18
18
  # edits; the wheel's copy comes from `force-include` in pyproject.toml.
19
19
  import re
20
- from dataclasses import dataclass, field
20
+ from dataclasses import dataclass, field, replace
21
21
  from importlib import resources
22
22
  from pathlib import Path
23
23
 
@@ -43,21 +43,25 @@ class ChangelogSection:
43
43
  class ChangelogEntry:
44
44
  """One release: its version, its date, and what changed in it.
45
45
 
46
- `date` is None for the Unreleased entry, which has none.
46
+ `date` is None for the Unreleased entry, which has none. `summary` is the
47
+ prose between the release heading and its first section, its lines
48
+ joined with spaces, or None when there is none.
47
49
  """
48
50
  version: str
49
51
  date: str | None
50
52
  sections: list[ChangelogSection] = field(default_factory=list)
53
+ summary: str | None = None
51
54
 
52
55
 
53
56
  def parse(text: str) -> list[ChangelogEntry]:
54
57
  """Read a Keep a Changelog document into entries, newest first.
55
58
 
56
- Prose between a release heading and its first section (a note that
57
- nothing has been released yet, say) is not an item and is skipped. A
58
- list item that wraps onto indented lines is joined back into one string.
59
+ Prose between a release heading and its first section is the entry's
60
+ summary, not an item. A list item that wraps onto indented lines is
61
+ joined back into one string.
59
62
  """
60
63
  entries: list[ChangelogEntry] = []
64
+ prose: list[list[str]] = []
61
65
  section: ChangelogSection | None = None
62
66
 
63
67
  for line in text.splitlines():
@@ -66,6 +70,7 @@ def parse(text: str) -> list[ChangelogEntry]:
66
70
  if release is not None:
67
71
  entries.append(ChangelogEntry(version=release["version"],
68
72
  date=release["date"]))
73
+ prose.append([])
69
74
  section = None
70
75
  continue
71
76
 
@@ -79,9 +84,15 @@ def parse(text: str) -> list[ChangelogEntry]:
79
84
  entries[-1].sections.append(section)
80
85
  continue
81
86
 
87
+ # BN-274: the summary used to be skipped.
88
+ if section is None and line.strip():
89
+ prose[-1].append(line.strip())
90
+ continue
91
+
82
92
  section = _add_line(section, line)
83
93
 
84
- return entries
94
+ return [replace(entry, summary=" ".join(lines) or None)
95
+ for entry, lines in zip(entries, prose, strict=True)]
85
96
 
86
97
 
87
98
  def _add_line(section: ChangelogSection | None,
@@ -25,7 +25,7 @@ __all__ = [
25
25
  # anybody who guessed the other import at the right one. Without it the error
26
26
  # is "module 'beacon.data' has no attribute 'market'", which is true and tells
27
27
  # nobody what to do about it.
28
- _EXPRESSION_ROOTS = ("market", "reference", "actions")
28
+ _EXPRESSION_ROOTS = ("market", "reference")
29
29
 
30
30
 
31
31
  def __getattr__(name: str) -> Any:
@@ -47,7 +47,7 @@ import logging
47
47
  import numpy as np
48
48
  import pandas as pd
49
49
 
50
- from .corporate_actions import CASH_ACTIONS, RATIO_ACTIONS
50
+ from .corporate_actions import CASH_ACTIONS, RATIO_ACTIONS, without_cancelled
51
51
 
52
52
  logger = logging.getLogger(__name__)
53
53
 
@@ -90,7 +90,7 @@ def _factors(closes: pd.Series,
90
90
  if actions is None or actions.empty:
91
91
  return factors
92
92
 
93
- for _, action in actions.iterrows():
93
+ for _, action in without_cancelled(actions).iterrows():
94
94
  ex_date = pd.Timestamp(action["EX_DATE"])
95
95
  action_type = str(action["TYPE"])
96
96
 
@@ -123,6 +123,22 @@ def kind_of(action_type: str) -> Kind:
123
123
  return STRUCTURAL
124
124
 
125
125
 
126
+ def without_cancelled(actions: pd.DataFrame) -> pd.DataFrame:
127
+ """*actions* less any whose status is cancelled.
128
+
129
+ A cancelled action never happened, so nothing that applies actions (a
130
+ split to units held, a dividend to a total return, either to an adjusted
131
+ close) or sums them (a trailing dividend) may count it. Rows with no
132
+ status, or a frame with no status column, are kept.
133
+ """
134
+ # BN-261: the ratio schedule already did this; the dividend readers and
135
+ # the adjusted closes did not.
136
+ if actions.empty or STATUS_COLUMN not in actions.columns:
137
+ return actions
138
+
139
+ return actions[actions[STATUS_COLUMN].map(status_of) != CANCELLED]
140
+
141
+
126
142
  def status_of(value: object) -> Status | None:
127
143
  """Normalise a stored status, or None if it is not one this vocabulary has.
128
144
 
@@ -372,7 +388,7 @@ class CorporateActions:
372
388
  end = pd.Timestamp(as_of)
373
389
  start = end - TRAILING_YEAR
374
390
 
375
- subset = self.get(identifier, types=types)
391
+ subset = without_cancelled(self.get(identifier, types=types))
376
392
  if subset.empty:
377
393
  return subset
378
394
 
@@ -434,10 +450,7 @@ class CorporateActions:
434
450
  multiplier would erase or invert a position.
435
451
  """
436
452
  frame = self._df
437
- ratios = frame[frame["TYPE"].isin(RATIO_ACTIONS)]
438
-
439
- if STATUS_COLUMN in ratios.columns:
440
- ratios = ratios[ratios[STATUS_COLUMN].map(status_of) != CANCELLED]
453
+ ratios = without_cancelled(frame[frame["TYPE"].isin(RATIO_ACTIONS)])
441
454
 
442
455
  schedule: dict[pd.Timestamp, dict[str, float]] = {}
443
456
 
@@ -21,6 +21,7 @@ from .free_float import (
21
21
  carried_forward,
22
22
  validated_window,
23
23
  )
24
+ from .prices import prices_in
24
25
  from .session import SessionPanel
25
26
 
26
27
  logger = logging.getLogger(__name__)
@@ -269,6 +270,8 @@ class DataFetcher:
269
270
  self._fx_series: dict[tuple[str, str], pd.Series] = {}
270
271
  # How each cached pair was found: direct, inverse, or a cross (BN-235).
271
272
  self._fx_routes: dict[tuple[str, str], str | None] = {}
273
+ # Pairs already warned about being stored without RATE (BN-226).
274
+ self._unlisted_pairs: set[str] = set()
272
275
  # Each name's observed free floats, blanks dropped, read once when a
273
276
  # blank day first needs carrying over (BN-219). Cleared with the FX
274
277
  # series on a merge, for the same reason.
@@ -968,6 +971,26 @@ class DataFetcher:
968
971
  val = df[column].iloc[0]
969
972
  return float(val) if pd.notna(val) else None
970
973
 
974
+ def fetch_prices(self,
975
+ identifiers: list[str],
976
+ start_date: str | None = None,
977
+ end_date: str | None = None,
978
+ currency: str | None = None,
979
+ column: str = "CLOSE") -> pd.DataFrame:
980
+ """Prices by date and instrument, converted into *currency*.
981
+
982
+ The read to use whenever prices of several instruments are compared
983
+ or added up: each is converted from its own currency day by day under
984
+ this fetcher's FX policy. With ``currency=None`` nothing is converted.
985
+ See `beacon.data.prices`.
986
+
987
+ Raises:
988
+ CalculationError: If an instrument's currency has no rate into
989
+ *currency*.
990
+ """
991
+ return prices_in(self, identifiers, start_date, end_date, currency,
992
+ column)
993
+
971
994
  def fetch_fx_rates(self,
972
995
  from_currency: str,
973
996
  to_currency: str,
@@ -977,10 +1000,12 @@ class DataFetcher:
977
1000
  """Return the stored FX rate series converting *from_currency* into *to_currency*.
978
1001
 
979
1002
  The pair is looked up as a market-data identifier named
980
- ``f"{from_currency}{to_currency}"`` (upper-cased). The *column* field is
981
- used if present, otherwise the first data column. Returns an empty
982
- Series if that exact pair is not stored: this does not invert or cross
983
- rates. :meth:`fx_rate_on` and :meth:`fx_rates_on` do.
1003
+ ``f"{from_currency}{to_currency}"`` (upper-cased), and its *column*
1004
+ read. A pair should carry `RATE`, which is also what :attr:`fx_pairs`
1005
+ lists pairs by. One stored without it is read from its first data
1006
+ column, with a warning, and is not listed. Returns an empty Series if
1007
+ that exact pair is not stored: this does not invert or cross rates.
1008
+ :meth:`fx_rate_on` and :meth:`fx_rates_on` do.
984
1009
  """
985
1010
  pair = f"{from_currency}{to_currency}".upper()
986
1011
  if pair not in self._market.identifiers:
@@ -988,8 +1013,20 @@ class DataFetcher:
988
1013
  df = self._market.get(pair, start_date, end_date)
989
1014
  if df.empty:
990
1015
  return pd.Series(dtype=float)
991
- rate_col = column if column in df.columns else df.columns[0]
992
- return df[rate_col]
1016
+
1017
+ if column in df.columns and df[column].notna().any():
1018
+ return df[column]
1019
+
1020
+ # BN-226: kept for stores written without RATE, but said, because
1021
+ # such a pair converts and `fx_pairs` does not list it.
1022
+ fallback = next(name for name in df.columns if name != column)
1023
+ if pair not in self._unlisted_pairs:
1024
+ self._unlisted_pairs.add(pair)
1025
+ logger.warning("%s has no %s values, so it is read from %s and "
1026
+ "is not listed among the currency pairs. Store its "
1027
+ "rate in %s.", pair, column, fallback, column)
1028
+
1029
+ return df[fallback]
993
1030
 
994
1031
  def fx_route(self,
995
1032
  from_currency: str,