ml4t-data 0.2.0__tar.gz → 0.2.1__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 (349) hide show
  1. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/PKG-INFO +2 -2
  2. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/README.md +1 -1
  3. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/pyproject.toml +2 -0
  4. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/_version.py +2 -2
  5. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/config/models.py +2 -0
  6. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/macro/downloader.py +10 -1
  7. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/providers/AGENTS.md +1 -0
  8. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/providers/__init__.py +12 -0
  9. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/providers/base.py +122 -7
  10. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/providers/fama_french.py +6 -5
  11. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/providers/finnhub.py +18 -6
  12. ml4t_data-0.2.1/src/ml4t/data/providers/forecastex.py +438 -0
  13. ml4t_data-0.2.1/src/ml4t/data/providers/kalshi.py +1634 -0
  14. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/providers/polygon.py +22 -1
  15. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/providers/polymarket.py +740 -6
  16. ml4t_data-0.2.1/src/ml4t/data/providers/polymarket_us.py +431 -0
  17. ml4t_data-0.2.1/src/ml4t/data/providers/prediction_markets.py +136 -0
  18. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/providers/registry.py +16 -0
  19. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/update_manager.py +3 -2
  20. ml4t_data-0.2.1/tests/integration/test_forecastex.py +34 -0
  21. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/integration/test_kalshi.py +66 -0
  22. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/integration/test_polymarket.py +57 -0
  23. ml4t_data-0.2.1/tests/integration/test_polymarket_us.py +35 -0
  24. ml4t_data-0.2.1/tests/providers/conftest.py +43 -0
  25. ml4t_data-0.2.1/tests/providers/fixtures/polymarket/clob_prices_history.json +106 -0
  26. ml4t_data-0.2.1/tests/providers/fixtures/polymarket/data_api_trades.json +413 -0
  27. ml4t_data-0.2.1/tests/providers/fixtures/polymarket/gamma_keyset_active.json +198 -0
  28. ml4t_data-0.2.1/tests/providers/fixtures/polymarket/gamma_keyset_closed.json +1224 -0
  29. ml4t_data-0.2.1/tests/providers/test_forecastex_provider.py +295 -0
  30. ml4t_data-0.2.1/tests/providers/test_kalshi_history.py +819 -0
  31. ml4t_data-0.2.1/tests/providers/test_polymarket_history.py +408 -0
  32. ml4t_data-0.2.1/tests/providers/test_polymarket_us_provider.py +323 -0
  33. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_fama_french_provider.py +23 -0
  34. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_finnhub_provider.py +15 -0
  35. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_macro_downloader.py +2 -1
  36. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_polygon_provider.py +24 -0
  37. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_release_pipeline.py +107 -1
  38. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_release_workflows.py +4 -0
  39. ml4t_data-0.2.1/tests/test_source_reference_contract.py +99 -0
  40. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_update_manager.py +18 -0
  41. ml4t_data-0.2.0/src/ml4t/data/providers/kalshi.py +0 -832
  42. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/.gitignore +0 -0
  43. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/LICENSE +0 -0
  44. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/scripts/verify_distribution.py +0 -0
  45. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/scripts/verify_provider_contract_runs.py +0 -0
  46. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/AGENTS.md +0 -0
  47. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/__init__.py +0 -0
  48. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/adjustments/__init__.py +0 -0
  49. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/adjustments/core.py +0 -0
  50. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/anomaly/__init__.py +0 -0
  51. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/anomaly/base.py +0 -0
  52. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/anomaly/config.py +0 -0
  53. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/anomaly/detectors.py +0 -0
  54. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/anomaly/manager.py +0 -0
  55. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/assets/__init__.py +0 -0
  56. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/assets/asset_class.py +0 -0
  57. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/assets/contracts.py +0 -0
  58. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/assets/data/__init__.py +0 -0
  59. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/assets/data/crypto_top100.json +0 -0
  60. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/assets/data/forex_majors.json +0 -0
  61. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/assets/data/nasdaq100.json +0 -0
  62. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/assets/data/sp500.json +0 -0
  63. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/assets/schemas.py +0 -0
  64. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/assets/validation.py +0 -0
  65. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/calendar/__init__.py +0 -0
  66. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/calendar/crypto.py +0 -0
  67. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/cli/__init__.py +0 -0
  68. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/cli/batch.py +0 -0
  69. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/cli/config.py +0 -0
  70. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/cli/core.py +0 -0
  71. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/cli/cot.py +0 -0
  72. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/cli/futures.py +0 -0
  73. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/cli/utils.py +0 -0
  74. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/cli_interface.py +0 -0
  75. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/config/__init__.py +0 -0
  76. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/config/_serialization.py +0 -0
  77. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/config/loader.py +0 -0
  78. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/config/validator.py +0 -0
  79. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/core/__init__.py +0 -0
  80. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/core/config.py +0 -0
  81. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/core/exceptions.py +0 -0
  82. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/core/models.py +0 -0
  83. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/core/schemas.py +0 -0
  84. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/cot/__init__.py +0 -0
  85. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/cot/fetcher.py +0 -0
  86. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/cot/workflow.py +0 -0
  87. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/crypto/__init__.py +0 -0
  88. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/crypto/downloader.py +0 -0
  89. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/data_manager.py +0 -0
  90. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/etfs/__init__.py +0 -0
  91. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/etfs/downloader.py +0 -0
  92. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/export/__init__.py +0 -0
  93. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/export/formats/__init__.py +0 -0
  94. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/export/formats/base.py +0 -0
  95. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/export/formats/csv.py +0 -0
  96. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/export/formats/excel.py +0 -0
  97. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/export/formats/json.py +0 -0
  98. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/export/manager.py +0 -0
  99. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/futures/AGENTS.md +0 -0
  100. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/futures/__init__.py +0 -0
  101. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/futures/adjustment.py +0 -0
  102. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/futures/book_downloader.py +0 -0
  103. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/futures/config.py +0 -0
  104. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/futures/continuous.py +0 -0
  105. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/futures/continuous_downloader.py +0 -0
  106. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/futures/databento_parser.py +0 -0
  107. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/futures/definitions.py +0 -0
  108. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/futures/downloader.py +0 -0
  109. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/futures/individual_downloader.py +0 -0
  110. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/futures/parser.py +0 -0
  111. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/futures/roll.py +0 -0
  112. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/futures/schema.py +0 -0
  113. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/macro/__init__.py +0 -0
  114. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/managers/__init__.py +0 -0
  115. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/managers/async_batch.py +0 -0
  116. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/managers/batch_manager.py +0 -0
  117. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/managers/bulk_manager.py +0 -0
  118. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/managers/config_manager.py +0 -0
  119. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/managers/fetch_manager.py +0 -0
  120. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/managers/metadata_manager.py +0 -0
  121. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/managers/provider_manager.py +0 -0
  122. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/managers/storage_manager.py +0 -0
  123. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/provider_updater.py +0 -0
  124. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/providers/alpaca.py +0 -0
  125. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/providers/aqr.py +0 -0
  126. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/providers/async_base.py +0 -0
  127. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/providers/binance.py +0 -0
  128. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/providers/binance_public.py +0 -0
  129. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/providers/coingecko.py +0 -0
  130. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/providers/cryptocompare.py +0 -0
  131. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/providers/databento.py +0 -0
  132. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/providers/eodhd.py +0 -0
  133. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/providers/fred.py +0 -0
  134. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/providers/fundamentals.py +0 -0
  135. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/providers/fxmacrodata.py +0 -0
  136. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/providers/learned_synthetic.py +0 -0
  137. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/providers/mixins/__init__.py +0 -0
  138. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/providers/mixins/async_session.py +0 -0
  139. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/providers/mixins/circuit_breaker.py +0 -0
  140. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/providers/mixins/rate_limit.py +0 -0
  141. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/providers/mixins/retry.py +0 -0
  142. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/providers/mixins/session.py +0 -0
  143. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/providers/mixins/validation.py +0 -0
  144. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/providers/mock.py +0 -0
  145. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/providers/nasdaq_itch.py +0 -0
  146. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/providers/oanda.py +0 -0
  147. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/providers/okx.py +0 -0
  148. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/providers/protocols.py +0 -0
  149. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/providers/synthetic.py +0 -0
  150. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/providers/tiingo.py +0 -0
  151. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/providers/twelve_data.py +0 -0
  152. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/providers/wiki_prices.py +0 -0
  153. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/providers/yahoo.py +0 -0
  154. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/py.typed +0 -0
  155. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/security/__init__.py +0 -0
  156. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/security/path_validator.py +0 -0
  157. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/sessions/__init__.py +0 -0
  158. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/sessions/assigner.py +0 -0
  159. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/sessions/completer.py +0 -0
  160. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/storage/AGENTS.md +0 -0
  161. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/storage/__init__.py +0 -0
  162. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/storage/async_base.py +0 -0
  163. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/storage/backend.py +0 -0
  164. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/storage/chunked.py +0 -0
  165. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/storage/config.py +0 -0
  166. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/storage/data_profile.py +0 -0
  167. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/storage/flat.py +0 -0
  168. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/storage/hive.py +0 -0
  169. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/storage/keys.py +0 -0
  170. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/storage/legacy_migration.py +0 -0
  171. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/storage/metadata_tracker.py +0 -0
  172. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/storage/migration.py +0 -0
  173. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/storage/protocols.py +0 -0
  174. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/synthetic/__init__.py +0 -0
  175. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/synthetic/ohlcv_utils.py +0 -0
  176. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/synthetic/registry.py +0 -0
  177. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/universe.py +0 -0
  178. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/utils/__init__.py +0 -0
  179. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/utils/async_rate_limit.py +0 -0
  180. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/utils/conversion.py +0 -0
  181. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/utils/format.py +0 -0
  182. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/utils/gap_optimizer.py +0 -0
  183. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/utils/gaps.py +0 -0
  184. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/utils/global_rate_limit.py +0 -0
  185. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/utils/locking.py +0 -0
  186. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/utils/rate_limit.py +0 -0
  187. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/utils/retry.py +0 -0
  188. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/validation/__init__.py +0 -0
  189. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/validation/base.py +0 -0
  190. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/validation/cross_validation.py +0 -0
  191. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/validation/ohlcv.py +0 -0
  192. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/validation/report.py +0 -0
  193. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/src/ml4t/data/validation/rules.py +0 -0
  194. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/__init__.py +0 -0
  195. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/anomaly/__init__.py +0 -0
  196. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/anomaly/test_base.py +0 -0
  197. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/anomaly/test_config.py +0 -0
  198. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/anomaly/test_detectors.py +0 -0
  199. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/anomaly/test_manager.py +0 -0
  200. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/assets/__init__.py +0 -0
  201. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/assets/test_asset_class.py +0 -0
  202. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/assets/test_contracts.py +0 -0
  203. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/assets/test_schemas.py +0 -0
  204. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/assets/test_validation.py +0 -0
  205. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/conftest.py +0 -0
  206. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/core/__init__.py +0 -0
  207. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/core/test_schemas.py +0 -0
  208. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/cot/__init__.py +0 -0
  209. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/cot/test_fetcher.py +0 -0
  210. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/cot/test_workflow.py +0 -0
  211. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/futures/test_adjustment.py +0 -0
  212. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/futures/test_book_downloader.py +0 -0
  213. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/futures/test_continuous.py +0 -0
  214. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/futures/test_databento_parser.py +0 -0
  215. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/futures/test_downloader.py +0 -0
  216. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/futures/test_parser.py +0 -0
  217. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/futures/test_roll.py +0 -0
  218. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/integration/conftest.py +0 -0
  219. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/integration/test_alpaca.py +0 -0
  220. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/integration/test_binance_public.py +0 -0
  221. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/integration/test_coingecko.py +0 -0
  222. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/integration/test_cryptocompare.py +0 -0
  223. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/integration/test_eodhd.py +0 -0
  224. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/integration/test_factor_providers.py +0 -0
  225. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/integration/test_finnhub.py +0 -0
  226. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/integration/test_fred.py +0 -0
  227. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/integration/test_fxmacrodata.py +0 -0
  228. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/integration/test_oanda.py +0 -0
  229. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/integration/test_polygon.py +0 -0
  230. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/integration/test_real_api_integration.py +0 -0
  231. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/integration/test_release_provider_contracts.py +0 -0
  232. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/integration/test_tiingo.py +0 -0
  233. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/integration/test_twelve_data.py +0 -0
  234. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/integration/test_wiki_prices.py +0 -0
  235. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/integration/test_yahoo.py +0 -0
  236. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/providers/test_nasdaq_itch.py +0 -0
  237. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/storage/test_data_profile.py +0 -0
  238. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/storage/test_keys.py +0 -0
  239. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/storage/test_legacy_migration.py +0 -0
  240. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/storage/test_metadata_tracker.py +0 -0
  241. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/storage/test_migration.py +0 -0
  242. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/synthetic/__init__.py +0 -0
  243. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/synthetic/test_learned_synthetic.py +0 -0
  244. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/synthetic/test_registry.py +0 -0
  245. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_adjustments_core.py +0 -0
  246. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_alpaca_provider.py +0 -0
  247. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_anomaly/__init__.py +0 -0
  248. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_anomaly/test_detectors.py +0 -0
  249. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_anomaly/test_manager.py +0 -0
  250. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_aqr_provider.py +0 -0
  251. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_aqr_provider_edge_cases.py +0 -0
  252. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_async_providers.py +0 -0
  253. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_async_providers_extended.py +0 -0
  254. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_async_rate_limit.py +0 -0
  255. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_base_provider_enhanced.py +0 -0
  256. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_batch_load.py +0 -0
  257. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_batch_load_integration.py +0 -0
  258. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_batch_load_performance.py +0 -0
  259. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_binance.py +0 -0
  260. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_binance_public_edge_cases.py +0 -0
  261. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_binance_public_provider.py +0 -0
  262. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_chunked_storage.py +0 -0
  263. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_cli_batch.py +0 -0
  264. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_cli_interface.py +0 -0
  265. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_coingecko_provider.py +0 -0
  266. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_config.py +0 -0
  267. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_config_basics.py +0 -0
  268. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_config_integration.py +0 -0
  269. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_config_loader.py +0 -0
  270. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_config_models.py +0 -0
  271. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_config_validator.py +0 -0
  272. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_configuration_authority.py +0 -0
  273. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_conversion.py +0 -0
  274. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_core_abstractions.py +0 -0
  275. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_core_models.py +0 -0
  276. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_cot.py +0 -0
  277. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_crypto_assets.py +0 -0
  278. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_crypto_downloader.py +0 -0
  279. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_cryptocompare.py +0 -0
  280. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_cryptocompare_provider.py +0 -0
  281. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_data_manager.py +0 -0
  282. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_data_manager_storage.py +0 -0
  283. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_data_manager_unit.py +0 -0
  284. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_data_manager_universe.py +0 -0
  285. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_databento_acceptance.py +0 -0
  286. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_databento_provider.py +0 -0
  287. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_distribution_verification.py +0 -0
  288. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_eodhd_provider.py +0 -0
  289. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_etf_downloader.py +0 -0
  290. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_exceptions.py +0 -0
  291. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_export.py +0 -0
  292. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_fred_unit.py +0 -0
  293. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_fundamentals_providers.py +0 -0
  294. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_fxmacrodata_provider.py +0 -0
  295. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_gap_optimizer.py +0 -0
  296. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_gaps.py +0 -0
  297. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_global_rate_limit.py +0 -0
  298. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_global_rate_limiting.py +0 -0
  299. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_hive_storage.py +0 -0
  300. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_import.py +0 -0
  301. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_incremental_update_system.py +0 -0
  302. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_kalshi_unit.py +0 -0
  303. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_locking.py +0 -0
  304. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_metadata_tracker.py +0 -0
  305. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_mock_provider.py +0 -0
  306. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_mock_provider_enhanced.py +0 -0
  307. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_multi_asset.py +0 -0
  308. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_oanda_provider.py +0 -0
  309. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_offline_network_policy.py +0 -0
  310. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_okx_provider.py +0 -0
  311. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_package_metadata.py +0 -0
  312. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_polymarket_unit.py +0 -0
  313. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_provider_consistency.py +0 -0
  314. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_provider_contract_verification.py +0 -0
  315. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_provider_registration.py +0 -0
  316. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_provider_registry_contract.py +0 -0
  317. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_provider_updater.py +0 -0
  318. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_rate_limit.py +0 -0
  319. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_regenerate_combined.py +0 -0
  320. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_retry.py +0 -0
  321. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_security.py +0 -0
  322. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_sessions.py +0 -0
  323. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_simple_utils.py +0 -0
  324. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_storage.py +0 -0
  325. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_storage_backends.py +0 -0
  326. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_storage_config_authority.py +0 -0
  327. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_storage_migration.py +0 -0
  328. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_storage_paths.py +0 -0
  329. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_synthetic_provider.py +0 -0
  330. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_tiingo_provider.py +0 -0
  331. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_twelve_data_provider.py +0 -0
  332. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_universe.py +0 -0
  333. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_utils_gaps.py +0 -0
  334. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_validation.py +0 -0
  335. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_validation_report.py +0 -0
  336. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_validation_rules.py +0 -0
  337. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_validation_simple.py +0 -0
  338. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_validation_unit.py +0 -0
  339. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_wiki_prices_provider.py +0 -0
  340. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_yahoo_empty_download_reporting.py +0 -0
  341. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_yahoo_provider.py +0 -0
  342. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_yahoo_provider_edge_cases.py +0 -0
  343. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/test_yahoo_provider_unit.py +0 -0
  344. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/utils/test_format.py +0 -0
  345. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/utils/test_format_integration.py +0 -0
  346. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/validation/test_cross_validation.py +0 -0
  347. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/validation/test_ohlcv_validator.py +0 -0
  348. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/validation/test_report.py +0 -0
  349. {ml4t_data-0.2.0 → ml4t_data-0.2.1}/tests/validation/test_rules.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ml4t-data
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: Market data acquisition, storage, and update workflows for machine learning for trading.
5
5
  Project-URL: Homepage, https://www.ml4trading.io/
6
6
  Project-URL: Documentation, https://www.ml4trading.io/docs/data/
@@ -179,7 +179,7 @@ uv run pre-commit install
179
179
  uv run ruff check src/ tests/
180
180
  uv run ruff format --check src/ tests/
181
181
  uv run ty check
182
- uv run pytest tests/ -q
182
+ uv run pytest tests/ -q -ra --cov=ml4t.data --cov-report=term-missing
183
183
  uv run mkdocs build --strict
184
184
  uv build
185
185
  ```
@@ -70,7 +70,7 @@ uv run pre-commit install
70
70
  uv run ruff check src/ tests/
71
71
  uv run ruff format --check src/ tests/
72
72
  uv run ty check
73
- uv run pytest tests/ -q
73
+ uv run pytest tests/ -q -ra --cov=ml4t.data --cov-report=term-missing
74
74
  uv run mkdocs build --strict
75
75
  uv build
76
76
  ```
@@ -301,6 +301,8 @@ omit = [
301
301
  ]
302
302
 
303
303
  [tool.coverage.report]
304
+ fail_under = 85
305
+ show_missing = true
304
306
  exclude_lines = [
305
307
  "pragma: no cover",
306
308
  "def __repr__",
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '0.2.0'
22
- __version_tuple__ = version_tuple = (0, 2, 0)
21
+ __version__ = version = '0.2.1'
22
+ __version_tuple__ = version_tuple = (0, 2, 1)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -55,6 +55,8 @@ class ProviderType(StrEnum):
55
55
  FAMA_FRENCH = "fama_french"
56
56
  KALSHI = "kalshi"
57
57
  POLYMARKET = "polymarket"
58
+ POLYMARKET_US = "polymarket_us"
59
+ FORECASTEX = "forecastex"
58
60
  COINGECKO = "coingecko"
59
61
  BINANCE = "binance"
60
62
  BINANCE_PUBLIC = "binance_public"
@@ -30,6 +30,7 @@ from __future__ import annotations
30
30
 
31
31
  import os
32
32
  from dataclasses import dataclass, field
33
+ from datetime import datetime, timedelta
33
34
  from pathlib import Path
34
35
  from typing import Any
35
36
 
@@ -269,10 +270,18 @@ class MacroDataManager:
269
270
 
270
271
  ticker = yf_mapping[series_id]
271
272
  try:
273
+ # yfinance treats end as exclusive. Config dates are inclusive.
274
+ end = self.config.end
275
+ try:
276
+ end = (datetime.strptime(end, "%Y-%m-%d") + timedelta(days=1)).strftime(
277
+ "%Y-%m-%d"
278
+ )
279
+ except ValueError:
280
+ pass
272
281
  df = yf.download(
273
282
  ticker,
274
283
  start=self.config.start,
275
- end=self.config.end,
284
+ end=end,
276
285
  progress=False,
277
286
  auto_adjust=True,
278
287
  threads=False,
@@ -12,6 +12,7 @@ service limitations are documented in `docs/providers/`.
12
12
  | `protocols.py` | Structural contracts used by callers and type checking |
13
13
  | `registry.py` | Provider discovery, capabilities, aliases, and factory metadata |
14
14
  | `mixins/` | Shared HTTP-session and rate-limit behavior |
15
+ | `prediction_markets.py` | Shared resolution-outcome vocabulary and trade schema for event-contract adapters |
15
16
  | `synthetic.py`, `mock.py` | Deterministic offline and testing providers |
16
17
  | Other top-level modules | One external or packaged data source per adapter |
17
18
 
@@ -74,6 +74,16 @@ try:
74
74
  except ImportError:
75
75
  PolymarketProvider = None # type: ignore
76
76
 
77
+ try:
78
+ from ml4t.data.providers.polymarket_us import PolymarketUSProvider
79
+ except ImportError:
80
+ PolymarketUSProvider = None # type: ignore
81
+
82
+ try:
83
+ from ml4t.data.providers.forecastex import ForecastExProvider
84
+ except ImportError:
85
+ ForecastExProvider = None # type: ignore
86
+
77
87
  # Crypto providers
78
88
  from ml4t.data.providers.coingecko import CoinGeckoProvider
79
89
 
@@ -151,6 +161,8 @@ __all__ = [
151
161
  # Prediction market providers
152
162
  "KalshiProvider",
153
163
  "PolymarketProvider",
164
+ "PolymarketUSProvider",
165
+ "ForecastExProvider",
154
166
  # Crypto providers
155
167
  "CoinGeckoProvider",
156
168
  "BinanceProvider",
@@ -31,10 +31,12 @@ Example (composing mixins):
31
31
 
32
32
  from __future__ import annotations
33
33
 
34
+ import math
34
35
  import os
35
36
  from abc import ABC, abstractmethod
36
37
  from typing import Any, ClassVar
37
38
 
39
+ import httpx
38
40
  import polars as pl
39
41
  import structlog
40
42
  from tenacity import (
@@ -45,7 +47,12 @@ from tenacity import (
45
47
  wait_exponential,
46
48
  )
47
49
 
48
- from ml4t.data.core.exceptions import NetworkError
50
+ from ml4t.data.core.exceptions import (
51
+ DataValidationError,
52
+ NetworkError,
53
+ RateLimitError,
54
+ SymbolNotFoundError,
55
+ )
49
56
  from ml4t.data.providers.mixins.circuit_breaker import CircuitBreaker, CircuitBreakerMixin
50
57
  from ml4t.data.providers.mixins.rate_limit import RateLimitMixin
51
58
  from ml4t.data.providers.mixins.session import SessionMixin
@@ -73,6 +80,29 @@ def _provider_error_is_retryable(error: BaseException) -> bool:
73
80
  return isinstance(error, NetworkError) and error.retryable
74
81
 
75
82
 
83
+ # Shared retry policy: three attempts, exponential backoff, provider ``retry_after`` honored.
84
+ provider_retry = retry(
85
+ stop=stop_after_attempt(3),
86
+ wait=_provider_retry_wait,
87
+ retry=retry_if_exception(_provider_error_is_retryable),
88
+ reraise=True,
89
+ )
90
+
91
+
92
+ def retry_after_seconds(response: httpx.Response) -> float | None:
93
+ """Return a non-negative ``Retry-After`` delay in seconds, or None when absent or invalid."""
94
+ value = response.headers.get("Retry-After")
95
+ if value is None:
96
+ return None
97
+ try:
98
+ delay = float(value)
99
+ except ValueError:
100
+ return None
101
+ if not math.isfinite(delay):
102
+ return None
103
+ return max(delay, 0.0)
104
+
105
+
76
106
  # Re-export for backward compatibility
77
107
  __all__ = [
78
108
  "BaseProvider",
@@ -81,6 +111,8 @@ __all__ = [
81
111
  "circuit_breaker",
82
112
  "OHLCVProvider",
83
113
  "ProviderCapabilities",
114
+ "provider_retry",
115
+ "retry_after_seconds",
84
116
  ]
85
117
 
86
118
 
@@ -196,12 +228,7 @@ class BaseProvider(
196
228
  rate_limit=self.DEFAULT_RATE_LIMIT,
197
229
  )
198
230
 
199
- @retry(
200
- stop=stop_after_attempt(3),
201
- wait=_provider_retry_wait,
202
- retry=retry_if_exception(_provider_error_is_retryable),
203
- reraise=True,
204
- )
231
+ @provider_retry
205
232
  def fetch_ohlcv(
206
233
  self,
207
234
  symbol: str,
@@ -271,6 +298,94 @@ class BaseProvider(
271
298
 
272
299
  return validated_data
273
300
 
301
+ def _classify_error_response(self, response: httpx.Response) -> Exception | None:
302
+ """Return a provider-specific exception for an error response, or None for the default.
303
+
304
+ Override to mark a service-specific failure as transient (for example an edge-layer
305
+ rejection). The default classification in ``_request`` applies when this returns None.
306
+ """
307
+ return None
308
+
309
+ @provider_retry
310
+ def _request(
311
+ self,
312
+ url: str,
313
+ *,
314
+ resource: str,
315
+ params: dict[str, Any] | None = None,
316
+ headers: dict[str, str] | None = None,
317
+ ) -> httpx.Response:
318
+ """Rate-limited GET that maps transport and HTTP failures to shared exceptions.
319
+
320
+ Transient failures (transport errors, HTTP 429 and 5xx) raise retryable errors and are
321
+ retried by the shared ``provider_retry`` policy, honoring ``Retry-After``.
322
+
323
+ Args:
324
+ url: Absolute request URL.
325
+ resource: Human-readable name of the requested resource, used in errors.
326
+ params: Query parameters.
327
+ headers: Request headers.
328
+
329
+ Returns:
330
+ The successful (2xx) response.
331
+
332
+ Raises:
333
+ RateLimitError: HTTP 429.
334
+ SymbolNotFoundError: HTTP 404; ``symbol`` is ``resource``.
335
+ NetworkError: Transport failure, HTTP 5xx (retryable) or other HTTP errors.
336
+ """
337
+ self._acquire_rate_limit()
338
+ try:
339
+ response = self.session.get(url, params=params, headers=headers)
340
+ except httpx.HTTPError as err:
341
+ raise NetworkError(
342
+ provider=self.name, message=f"Request failed for {resource}: {err}"
343
+ ) from err
344
+
345
+ if 200 <= response.status_code < 300:
346
+ return response
347
+ custom = self._classify_error_response(response)
348
+ if custom is not None:
349
+ raise custom
350
+ if response.status_code == 429:
351
+ raise RateLimitError(provider=self.name, retry_after=retry_after_seconds(response))
352
+ if response.status_code == 404:
353
+ raise SymbolNotFoundError(provider=self.name, symbol=resource)
354
+ message = f"HTTP {response.status_code} for {resource}: {response.text[:200]}"
355
+ raise NetworkError(
356
+ provider=self.name,
357
+ message=message,
358
+ retryable=response.status_code >= 500,
359
+ )
360
+
361
+ def _request_json(
362
+ self,
363
+ url: str,
364
+ *,
365
+ resource: str,
366
+ params: dict[str, Any] | None = None,
367
+ headers: dict[str, str] | None = None,
368
+ ) -> dict[str, Any]:
369
+ """Run ``_request`` and parse a JSON object body.
370
+
371
+ Raises:
372
+ DataValidationError: The body is not valid JSON or not a JSON object.
373
+ """
374
+ response = self._request(url, resource=resource, params=params, headers=headers)
375
+ try:
376
+ payload = response.json()
377
+ except ValueError as err:
378
+ raise DataValidationError(
379
+ provider=self.name,
380
+ message=f"Malformed JSON response for {resource}",
381
+ ) from err
382
+ if not isinstance(payload, dict):
383
+ raise DataValidationError(
384
+ provider=self.name,
385
+ message=f"Expected a JSON object for {resource}, got {type(payload).__name__}",
386
+ )
387
+ return payload
388
+
274
389
  def _create_empty_dataframe(self) -> pl.DataFrame:
275
390
  """Create an empty DataFrame with canonical OHLCV schema.
276
391
 
@@ -41,6 +41,7 @@ from __future__ import annotations
41
41
 
42
42
  import io
43
43
  import zipfile
44
+ from calendar import monthrange
44
45
  from datetime import datetime
45
46
  from pathlib import Path
46
47
  from typing import Any, ClassVar, Literal
@@ -730,11 +731,11 @@ class FamaFrenchProvider(BaseProvider):
730
731
  )
731
732
  df = df.filter(pl.col("timestamp") >= start_date)
732
733
  if end:
733
- end_date = (
734
- datetime.strptime(end[:10], "%Y-%m-%d").date()
735
- if len(end) >= 10
736
- else datetime.strptime(end + "-28", "%Y-%m-%d").date()
737
- )
734
+ if len(end) >= 10:
735
+ end_date = datetime.strptime(end[:10], "%Y-%m-%d").date()
736
+ else:
737
+ year, month = (int(part) for part in end.split("-", 1))
738
+ end_date = datetime(year, month, monthrange(year, month)[1]).date()
738
739
  df = df.filter(pl.col("timestamp") <= end_date)
739
740
 
740
741
  self.logger.info(
@@ -4,7 +4,7 @@ Endpoint availability, request quotas, and historical depth depend on the accoun
4
4
  """
5
5
 
6
6
  import os
7
- from datetime import datetime
7
+ from datetime import UTC, datetime
8
8
  from typing import Any, ClassVar
9
9
 
10
10
  import polars as pl
@@ -34,6 +34,19 @@ from ml4t.data.providers.fundamentals import (
34
34
  logger = structlog.get_logger()
35
35
 
36
36
 
37
+ def _utc_unix_timestamp(value: str, *, inclusive_date: bool = False) -> int:
38
+ """Parse a date or datetime as a UTC unix timestamp.
39
+
40
+ A date-only end bound covers that UTC calendar day. An explicit datetime
41
+ is used as given, with naive values treated as UTC.
42
+ """
43
+ parsed = datetime.fromisoformat(value)
44
+ parsed = parsed.replace(tzinfo=UTC) if parsed.tzinfo is None else parsed.astimezone(UTC)
45
+ if inclusive_date and "T" not in value and len(value) == 10:
46
+ parsed = parsed.replace(hour=23, minute=59, second=59)
47
+ return int(parsed.timestamp())
48
+
49
+
37
50
  class FinnhubProvider(BaseProvider):
38
51
  """Fetch Finnhub market data through synchronous provider methods.
39
52
 
@@ -196,12 +209,11 @@ class FinnhubProvider(BaseProvider):
196
209
  value=frequency,
197
210
  )
198
211
 
199
- # Convert dates to unix timestamps
212
+ # Convert dates to unix timestamps. Date-only bounds are UTC calendar
213
+ # days, with the end date inclusive through 23:59:59.
200
214
  try:
201
- start_dt = datetime.fromisoformat(start)
202
- end_dt = datetime.fromisoformat(end)
203
- start_ts = int(start_dt.timestamp())
204
- end_ts = int(end_dt.timestamp())
215
+ start_ts = _utc_unix_timestamp(start)
216
+ end_ts = _utc_unix_timestamp(end, inclusive_date=True)
205
217
  except ValueError as err:
206
218
  raise DataValidationError(
207
219
  provider="finnhub",