aether-quant 0.3.0__tar.gz → 0.4.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 (240) hide show
  1. {aether_quant-0.3.0 → aether_quant-0.4.0}/.gitignore +5 -0
  2. {aether_quant-0.3.0 → aether_quant-0.4.0}/PKG-INFO +1 -1
  3. {aether_quant-0.3.0 → aether_quant-0.4.0}/README.md +168 -5
  4. {aether_quant-0.3.0 → aether_quant-0.4.0}/aether_quant.egg-info/PKG-INFO +1 -1
  5. {aether_quant-0.3.0 → aether_quant-0.4.0}/aether_quant.egg-info/SOURCES.txt +3 -0
  6. {aether_quant-0.3.0 → aether_quant-0.4.0}/aether_quant.egg-info/scm_file_list.json +3 -0
  7. aether_quant-0.4.0/aether_quant.egg-info/scm_version.json +8 -0
  8. {aether_quant-0.3.0 → aether_quant-0.4.0}/aether_quant.egg-info/top_level.txt +1 -0
  9. {aether_quant-0.3.0 → aether_quant-0.4.0}/aq_cli.py +58 -5
  10. {aether_quant-0.3.0 → aether_quant-0.4.0}/data_pipeline/README.md +27 -0
  11. aether_quant-0.4.0/data_pipeline/fetch.py +134 -0
  12. {aether_quant-0.3.0 → aether_quant-0.4.0}/development/Changelog.md +159 -0
  13. {aether_quant-0.3.0 → aether_quant-0.4.0}/development/Problems.md +104 -0
  14. aether_quant-0.4.0/development/backtest_equity_chart.png +0 -0
  15. aether_quant-0.4.0/development/logo.png +0 -0
  16. {aether_quant-0.3.0 → aether_quant-0.4.0}/development/v2_architecture.md +3 -3
  17. {aether_quant-0.3.0 → aether_quant-0.4.0}/generate_backtest_report.py +59 -18
  18. {aether_quant-0.3.0 → aether_quant-0.4.0}/main.py +71 -31
  19. {aether_quant-0.3.0 → aether_quant-0.4.0}/pyproject.toml +3 -2
  20. {aether_quant-0.3.0 → aether_quant-0.4.0}/tests/test_aq_cli.py +99 -1
  21. aether_quant-0.4.0/tests/test_fetch.py +231 -0
  22. {aether_quant-0.3.0 → aether_quant-0.4.0}/tests/test_generate_backtest_report.py +70 -5
  23. {aether_quant-0.3.0 → aether_quant-0.4.0}/tests/test_lean_backtest_ml_coverage.py +8 -6
  24. {aether_quant-0.3.0 → aether_quant-0.4.0}/tests/test_train_pipeline.py +66 -0
  25. {aether_quant-0.3.0 → aether_quant-0.4.0}/train.py +20 -1
  26. aether_quant-0.3.0/aether_quant.egg-info/scm_version.json +0 -8
  27. aether_quant-0.3.0/development/backtest_equity_chart.png +0 -0
  28. {aether_quant-0.3.0 → aether_quant-0.4.0}/.dockerignore +0 -0
  29. {aether_quant-0.3.0 → aether_quant-0.4.0}/.env.compose.example +0 -0
  30. {aether_quant-0.3.0 → aether_quant-0.4.0}/.env.live.example +0 -0
  31. {aether_quant-0.3.0 → aether_quant-0.4.0}/.gitattributes +0 -0
  32. {aether_quant-0.3.0 → aether_quant-0.4.0}/.github/workflows/ci.yml +0 -0
  33. {aether_quant-0.3.0 → aether_quant-0.4.0}/.github/workflows/release.yml +0 -0
  34. {aether_quant-0.3.0 → aether_quant-0.4.0}/Dockerfile +0 -0
  35. {aether_quant-0.3.0 → aether_quant-0.4.0}/Dockerfile.retraining_worker +0 -0
  36. {aether_quant-0.3.0 → aether_quant-0.4.0}/Dockerfile.workers +0 -0
  37. {aether_quant-0.3.0 → aether_quant-0.4.0}/aether_quant.egg-info/dependency_links.txt +0 -0
  38. {aether_quant-0.3.0 → aether_quant-0.4.0}/aether_quant.egg-info/entry_points.txt +0 -0
  39. {aether_quant-0.3.0 → aether_quant-0.4.0}/analyzer/README.md +0 -0
  40. {aether_quant-0.3.0 → aether_quant-0.4.0}/analyzer/__init__.py +0 -0
  41. {aether_quant-0.3.0 → aether_quant-0.4.0}/analyzer/market_analyzer.py +0 -0
  42. {aether_quant-0.3.0 → aether_quant-0.4.0}/backtests/.gitkeep +0 -0
  43. {aether_quant-0.3.0 → aether_quant-0.4.0}/data/alternative/estimize/consensus/aapl/.keep +0 -0
  44. {aether_quant-0.3.0 → aether_quant-0.4.0}/data/alternative/estimize/estimate/.keep +0 -0
  45. {aether_quant-0.3.0 → aether_quant-0.4.0}/data/alternative/estimize/release/.keep +0 -0
  46. {aether_quant-0.3.0 → aether_quant-0.4.0}/data/alternative/sec/aapl/.keep +0 -0
  47. {aether_quant-0.3.0 → aether_quant-0.4.0}/data/alternative/trading-economics/.keep +0 -0
  48. {aether_quant-0.3.0 → aether_quant-0.4.0}/data/alternative/trading-economics/calendar/.keep +0 -0
  49. {aether_quant-0.3.0 → aether_quant-0.4.0}/data/alternative/trading-economics/earnings/.keep +0 -0
  50. {aether_quant-0.3.0 → aether_quant-0.4.0}/data/alternative/trading-economics/indicator/.keep +0 -0
  51. {aether_quant-0.3.0 → aether_quant-0.4.0}/data_pipeline/__init__.py +0 -0
  52. {aether_quant-0.3.0 → aether_quant-0.4.0}/data_pipeline/v2_manifest.py +0 -0
  53. {aether_quant-0.3.0 → aether_quant-0.4.0}/data_pipeline/yfinance_backfill.py +0 -0
  54. {aether_quant-0.3.0 → aether_quant-0.4.0}/development/README.md +0 -0
  55. {aether_quant-0.3.0 → aether_quant-0.4.0}/development/infrastructure.md +0 -0
  56. {aether_quant-0.3.0 → aether_quant-0.4.0}/docker-compose.yml +0 -0
  57. {aether_quant-0.3.0 → aether_quant-0.4.0}/execution/README.md +0 -0
  58. {aether_quant-0.3.0 → aether_quant-0.4.0}/execution/__init__.py +0 -0
  59. {aether_quant-0.3.0 → aether_quant-0.4.0}/execution/config_cache.py +0 -0
  60. {aether_quant-0.3.0 → aether_quant-0.4.0}/execution/live_credentials.py +0 -0
  61. {aether_quant-0.3.0 → aether_quant-0.4.0}/execution/live_credentials_io.py +0 -0
  62. {aether_quant-0.3.0 → aether_quant-0.4.0}/execution/order_gate.py +0 -0
  63. {aether_quant-0.3.0 → aether_quant-0.4.0}/execution/paper_readiness.py +0 -0
  64. {aether_quant-0.3.0 → aether_quant-0.4.0}/execution/paper_readiness_io.py +0 -0
  65. {aether_quant-0.3.0 → aether_quant-0.4.0}/execution/paper_readiness_report.py +0 -0
  66. {aether_quant-0.3.0 → aether_quant-0.4.0}/execution/runtime_config_io.py +0 -0
  67. {aether_quant-0.3.0 → aether_quant-0.4.0}/experience/README.md +0 -0
  68. {aether_quant-0.3.0 → aether_quant-0.4.0}/experience/__init__.py +0 -0
  69. {aether_quant-0.3.0 → aether_quant-0.4.0}/experience/observation_metrics.py +0 -0
  70. {aether_quant-0.3.0 → aether_quant-0.4.0}/experience/postgres_worker.py +0 -0
  71. {aether_quant-0.3.0 → aether_quant-0.4.0}/experience/redis_queue.py +0 -0
  72. {aether_quant-0.3.0 → aether_quant-0.4.0}/experience/simulated_portfolio.py +0 -0
  73. {aether_quant-0.3.0 → aether_quant-0.4.0}/experts/README.md +0 -0
  74. {aether_quant-0.3.0 → aether_quant-0.4.0}/experts/__init__.py +0 -0
  75. {aether_quant-0.3.0 → aether_quant-0.4.0}/experts/expert_datasets.py +0 -0
  76. {aether_quant-0.3.0 → aether_quant-0.4.0}/inference/README.md +0 -0
  77. {aether_quant-0.3.0 → aether_quant-0.4.0}/inference/__init__.py +0 -0
  78. {aether_quant-0.3.0 → aether_quant-0.4.0}/inference/exported_model.py +0 -0
  79. {aether_quant-0.3.0 → aether_quant-0.4.0}/liquidity/README.md +0 -0
  80. {aether_quant-0.3.0 → aether_quant-0.4.0}/liquidity/__init__.py +0 -0
  81. {aether_quant-0.3.0 → aether_quant-0.4.0}/liquidity/market_liquidity.py +0 -0
  82. {aether_quant-0.3.0 → aether_quant-0.4.0}/ml/README.md +0 -0
  83. {aether_quant-0.3.0 → aether_quant-0.4.0}/moe/README.md +0 -0
  84. {aether_quant-0.3.0 → aether_quant-0.4.0}/moe/__init__.py +0 -0
  85. {aether_quant-0.3.0 → aether_quant-0.4.0}/moe/gating.py +0 -0
  86. {aether_quant-0.3.0 → aether_quant-0.4.0}/monitoring/README.md +0 -0
  87. {aether_quant-0.3.0 → aether_quant-0.4.0}/monitoring/__init__.py +0 -0
  88. {aether_quant-0.3.0 → aether_quant-0.4.0}/monitoring/api_server.py +0 -0
  89. {aether_quant-0.3.0 → aether_quant-0.4.0}/monitoring/neural_network_state.py +0 -0
  90. {aether_quant-0.3.0 → aether_quant-0.4.0}/notifications/README.md +0 -0
  91. {aether_quant-0.3.0 → aether_quant-0.4.0}/notifications/__init__.py +0 -0
  92. {aether_quant-0.3.0 → aether_quant-0.4.0}/notifications/postgres_telegram.py +0 -0
  93. {aether_quant-0.3.0 → aether_quant-0.4.0}/notifications/telegram_alerts.py +0 -0
  94. {aether_quant-0.3.0 → aether_quant-0.4.0}/notifications/telegram_client.py +0 -0
  95. {aether_quant-0.3.0 → aether_quant-0.4.0}/notifications/telegram_worker.py +0 -0
  96. {aether_quant-0.3.0 → aether_quant-0.4.0}/performance/README.md +0 -0
  97. {aether_quant-0.3.0 → aether_quant-0.4.0}/performance/__init__.py +0 -0
  98. {aether_quant-0.3.0 → aether_quant-0.4.0}/performance/postgres_triggers.py +0 -0
  99. {aether_quant-0.3.0 → aether_quant-0.4.0}/performance/trigger_worker.py +0 -0
  100. {aether_quant-0.3.0 → aether_quant-0.4.0}/performance/triggers.py +0 -0
  101. {aether_quant-0.3.0 → aether_quant-0.4.0}/regime/README.md +0 -0
  102. {aether_quant-0.3.0 → aether_quant-0.4.0}/regime/__init__.py +0 -0
  103. {aether_quant-0.3.0 → aether_quant-0.4.0}/regime/market_regime.py +0 -0
  104. {aether_quant-0.3.0 → aether_quant-0.4.0}/requirements/README.md +0 -0
  105. {aether_quant-0.3.0 → aether_quant-0.4.0}/requirements/requirements-dev.txt +0 -0
  106. {aether_quant-0.3.0 → aether_quant-0.4.0}/requirements/requirements-retraining-worker.txt +0 -0
  107. {aether_quant-0.3.0 → aether_quant-0.4.0}/requirements/requirements-runtime.txt +0 -0
  108. {aether_quant-0.3.0 → aether_quant-0.4.0}/requirements/requirements-workers.txt +0 -0
  109. {aether_quant-0.3.0 → aether_quant-0.4.0}/requirements/requirements.txt +0 -0
  110. {aether_quant-0.3.0 → aether_quant-0.4.0}/retraining/README.md +0 -0
  111. {aether_quant-0.3.0 → aether_quant-0.4.0}/retraining/__init__.py +0 -0
  112. {aether_quant-0.3.0 → aether_quant-0.4.0}/retraining/artifacts.py +0 -0
  113. {aether_quant-0.3.0 → aether_quant-0.4.0}/retraining/backtest_gate.py +0 -0
  114. {aether_quant-0.3.0 → aether_quant-0.4.0}/retraining/lean_backtest.py +0 -0
  115. {aether_quant-0.3.0 → aether_quant-0.4.0}/retraining/orchestrator.py +0 -0
  116. {aether_quant-0.3.0 → aether_quant-0.4.0}/retraining/planning.py +0 -0
  117. {aether_quant-0.3.0 → aether_quant-0.4.0}/retraining/postgres_registry.py +0 -0
  118. {aether_quant-0.3.0 → aether_quant-0.4.0}/retraining/status_export.py +0 -0
  119. {aether_quant-0.3.0 → aether_quant-0.4.0}/retraining/validation_gate.py +0 -0
  120. {aether_quant-0.3.0 → aether_quant-0.4.0}/retraining/vault_client.py +0 -0
  121. {aether_quant-0.3.0 → aether_quant-0.4.0}/retraining/vault_commands.py +0 -0
  122. {aether_quant-0.3.0 → aether_quant-0.4.0}/retraining/worker.py +0 -0
  123. {aether_quant-0.3.0 → aether_quant-0.4.0}/risk/README.md +0 -0
  124. {aether_quant-0.3.0 → aether_quant-0.4.0}/risk/__init__.py +0 -0
  125. {aether_quant-0.3.0 → aether_quant-0.4.0}/risk/manual_override.py +0 -0
  126. {aether_quant-0.3.0 → aether_quant-0.4.0}/risk/position_sizing.py +0 -0
  127. {aether_quant-0.3.0 → aether_quant-0.4.0}/risk_controls.py +0 -0
  128. {aether_quant-0.3.0 → aether_quant-0.4.0}/setup.cfg +0 -0
  129. {aether_quant-0.3.0 → aether_quant-0.4.0}/tests/README.md +0 -0
  130. {aether_quant-0.3.0 → aether_quant-0.4.0}/tests/test_backtest_gate.py +0 -0
  131. {aether_quant-0.3.0 → aether_quant-0.4.0}/tests/test_config_cache.py +0 -0
  132. {aether_quant-0.3.0 → aether_quant-0.4.0}/tests/test_experience_queue.py +0 -0
  133. {aether_quant-0.3.0 → aether_quant-0.4.0}/tests/test_expert_datasets.py +0 -0
  134. {aether_quant-0.3.0 → aether_quant-0.4.0}/tests/test_expert_models.py +0 -0
  135. {aether_quant-0.3.0 → aether_quant-0.4.0}/tests/test_exported_model.py +0 -0
  136. {aether_quant-0.3.0 → aether_quant-0.4.0}/tests/test_gating_network.py +0 -0
  137. {aether_quant-0.3.0 → aether_quant-0.4.0}/tests/test_lean_backtest.py +0 -0
  138. {aether_quant-0.3.0 → aether_quant-0.4.0}/tests/test_learned_topology.py +0 -0
  139. {aether_quant-0.3.0 → aether_quant-0.4.0}/tests/test_live_credentials.py +0 -0
  140. {aether_quant-0.3.0 → aether_quant-0.4.0}/tests/test_live_credentials_io.py +0 -0
  141. {aether_quant-0.3.0 → aether_quant-0.4.0}/tests/test_manual_override.py +0 -0
  142. {aether_quant-0.3.0 → aether_quant-0.4.0}/tests/test_market_analyzer.py +0 -0
  143. {aether_quant-0.3.0 → aether_quant-0.4.0}/tests/test_market_liquidity.py +0 -0
  144. {aether_quant-0.3.0 → aether_quant-0.4.0}/tests/test_market_regime.py +0 -0
  145. {aether_quant-0.3.0 → aether_quant-0.4.0}/tests/test_market_topology.py +0 -0
  146. {aether_quant-0.3.0 → aether_quant-0.4.0}/tests/test_neural_network_state.py +0 -0
  147. {aether_quant-0.3.0 → aether_quant-0.4.0}/tests/test_observation_metrics.py +0 -0
  148. {aether_quant-0.3.0 → aether_quant-0.4.0}/tests/test_order_gate.py +0 -0
  149. {aether_quant-0.3.0 → aether_quant-0.4.0}/tests/test_paper_readiness.py +0 -0
  150. {aether_quant-0.3.0 → aether_quant-0.4.0}/tests/test_paper_readiness_io.py +0 -0
  151. {aether_quant-0.3.0 → aether_quant-0.4.0}/tests/test_paper_readiness_report.py +0 -0
  152. {aether_quant-0.3.0 → aether_quant-0.4.0}/tests/test_position_sizing.py +0 -0
  153. {aether_quant-0.3.0 → aether_quant-0.4.0}/tests/test_postgres_telegram.py +0 -0
  154. {aether_quant-0.3.0 → aether_quant-0.4.0}/tests/test_postgres_triggers.py +0 -0
  155. {aether_quant-0.3.0 → aether_quant-0.4.0}/tests/test_postgres_worker.py +0 -0
  156. {aether_quant-0.3.0 → aether_quant-0.4.0}/tests/test_retraining_artifacts.py +0 -0
  157. {aether_quant-0.3.0 → aether_quant-0.4.0}/tests/test_retraining_orchestrator.py +0 -0
  158. {aether_quant-0.3.0 → aether_quant-0.4.0}/tests/test_retraining_planning.py +0 -0
  159. {aether_quant-0.3.0 → aether_quant-0.4.0}/tests/test_retraining_postgres_registry.py +0 -0
  160. {aether_quant-0.3.0 → aether_quant-0.4.0}/tests/test_retraining_worker.py +0 -0
  161. {aether_quant-0.3.0 → aether_quant-0.4.0}/tests/test_risk_controls.py +0 -0
  162. {aether_quant-0.3.0 → aether_quant-0.4.0}/tests/test_runtime_config_io.py +0 -0
  163. {aether_quant-0.3.0 → aether_quant-0.4.0}/tests/test_simulated_portfolio.py +0 -0
  164. {aether_quant-0.3.0 → aether_quant-0.4.0}/tests/test_status_export.py +0 -0
  165. {aether_quant-0.3.0 → aether_quant-0.4.0}/tests/test_telegram_alerts.py +0 -0
  166. {aether_quant-0.3.0 → aether_quant-0.4.0}/tests/test_telegram_client.py +0 -0
  167. {aether_quant-0.3.0 → aether_quant-0.4.0}/tests/test_telegram_worker.py +0 -0
  168. {aether_quant-0.3.0 → aether_quant-0.4.0}/tests/test_train_topology.py +0 -0
  169. {aether_quant-0.3.0 → aether_quant-0.4.0}/tests/test_trigger_worker.py +0 -0
  170. {aether_quant-0.3.0 → aether_quant-0.4.0}/tests/test_triggers.py +0 -0
  171. {aether_quant-0.3.0 → aether_quant-0.4.0}/tests/test_v2_pipeline_manifest.py +0 -0
  172. {aether_quant-0.3.0 → aether_quant-0.4.0}/tests/test_validation_gate.py +0 -0
  173. {aether_quant-0.3.0 → aether_quant-0.4.0}/tests/test_vault_client.py +0 -0
  174. {aether_quant-0.3.0 → aether_quant-0.4.0}/tests/test_vault_commands.py +0 -0
  175. {aether_quant-0.3.0 → aether_quant-0.4.0}/tests/test_yfinance_backfill.py +0 -0
  176. {aether_quant-0.3.0 → aether_quant-0.4.0}/topology/README.md +0 -0
  177. {aether_quant-0.3.0 → aether_quant-0.4.0}/topology/__init__.py +0 -0
  178. {aether_quant-0.3.0 → aether_quant-0.4.0}/topology/learned_topology.py +0 -0
  179. {aether_quant-0.3.0 → aether_quant-0.4.0}/topology/market_topology.py +0 -0
  180. {aether_quant-0.3.0 → aether_quant-0.4.0}/train_topology.py +0 -0
  181. {aether_quant-0.3.0 → aether_quant-0.4.0}/visualization/README.md +0 -0
  182. {aether_quant-0.3.0 → aether_quant-0.4.0}/webui/.gitignore +0 -0
  183. {aether_quant-0.3.0 → aether_quant-0.4.0}/webui/.oxlintrc.json +0 -0
  184. {aether_quant-0.3.0 → aether_quant-0.4.0}/webui/README.md +0 -0
  185. {aether_quant-0.3.0 → aether_quant-0.4.0}/webui/index.html +0 -0
  186. {aether_quant-0.3.0 → aether_quant-0.4.0}/webui/overview_v2_12.png +0 -0
  187. {aether_quant-0.3.0 → aether_quant-0.4.0}/webui/package-lock.json +0 -0
  188. {aether_quant-0.3.0 → aether_quant-0.4.0}/webui/package.json +0 -0
  189. {aether_quant-0.3.0 → aether_quant-0.4.0}/webui/public/favicon.svg +0 -0
  190. {aether_quant-0.3.0 → aether_quant-0.4.0}/webui/public/icons.svg +0 -0
  191. {aether_quant-0.3.0 → aether_quant-0.4.0}/webui/risk_v2_12.png +0 -0
  192. {aether_quant-0.3.0 → aether_quant-0.4.0}/webui/src/App.tsx +0 -0
  193. {aether_quant-0.3.0 → aether_quant-0.4.0}/webui/src/api/client.ts +0 -0
  194. {aether_quant-0.3.0 → aether_quant-0.4.0}/webui/src/api/hooks.ts +0 -0
  195. {aether_quant-0.3.0 → aether_quant-0.4.0}/webui/src/components/heatmap/AssetHeatmap.tsx +0 -0
  196. {aether_quant-0.3.0 → aether_quant-0.4.0}/webui/src/components/layout/AppShell.tsx +0 -0
  197. {aether_quant-0.3.0 → aether_quant-0.4.0}/webui/src/components/layout/Panel.tsx +0 -0
  198. {aether_quant-0.3.0 → aether_quant-0.4.0}/webui/src/components/monitoring/CountTable.tsx +0 -0
  199. {aether_quant-0.3.0 → aether_quant-0.4.0}/webui/src/components/monitoring/MonitoringFeeds.tsx +0 -0
  200. {aether_quant-0.3.0 → aether_quant-0.4.0}/webui/src/components/monitoring/ObservationPanel.tsx +0 -0
  201. {aether_quant-0.3.0 → aether_quant-0.4.0}/webui/src/components/monitoring/PaperReadinessPanel.tsx +0 -0
  202. {aether_quant-0.3.0 → aether_quant-0.4.0}/webui/src/components/monitoring/PerformanceTriggersPanel.tsx +0 -0
  203. {aether_quant-0.3.0 → aether_quant-0.4.0}/webui/src/components/monitoring/RawStateViewer.tsx +0 -0
  204. {aether_quant-0.3.0 → aether_quant-0.4.0}/webui/src/components/monitoring/RetrainingStatusPanel.tsx +0 -0
  205. {aether_quant-0.3.0 → aether_quant-0.4.0}/webui/src/components/neuralnet/NeuralNetworkScene3D.tsx +0 -0
  206. {aether_quant-0.3.0 → aether_quant-0.4.0}/webui/src/components/neuralnet/NeuralNetworkStatsPanel.tsx +0 -0
  207. {aether_quant-0.3.0 → aether_quant-0.4.0}/webui/src/components/risk/AssetSizingTable.tsx +0 -0
  208. {aether_quant-0.3.0 → aether_quant-0.4.0}/webui/src/components/risk/LiquidityTable.tsx +0 -0
  209. {aether_quant-0.3.0 → aether_quant-0.4.0}/webui/src/components/risk/RiskBar.tsx +0 -0
  210. {aether_quant-0.3.0 → aether_quant-0.4.0}/webui/src/components/risk/RiskCore.tsx +0 -0
  211. {aether_quant-0.3.0 → aether_quant-0.4.0}/webui/src/components/risk/StrategyRiskCards.tsx +0 -0
  212. {aether_quant-0.3.0 → aether_quant-0.4.0}/webui/src/components/scene3d/Scene3D.tsx +0 -0
  213. {aether_quant-0.3.0 → aether_quant-0.4.0}/webui/src/components/scorecards/Scorecards.tsx +0 -0
  214. {aether_quant-0.3.0 → aether_quant-0.4.0}/webui/src/components/signals/Badge.tsx +0 -0
  215. {aether_quant-0.3.0 → aether_quant-0.4.0}/webui/src/components/signals/PositionsList.tsx +0 -0
  216. {aether_quant-0.3.0 → aether_quant-0.4.0}/webui/src/components/signals/SignalBoard.tsx +0 -0
  217. {aether_quant-0.3.0 → aether_quant-0.4.0}/webui/src/components/topology/ClusterList.tsx +0 -0
  218. {aether_quant-0.3.0 → aether_quant-0.4.0}/webui/src/components/topology/TopologyLearningPanel.tsx +0 -0
  219. {aether_quant-0.3.0 → aether_quant-0.4.0}/webui/src/components/topology/TopologyScene3D.tsx +0 -0
  220. {aether_quant-0.3.0 → aether_quant-0.4.0}/webui/src/components/tracing/AssetPerformancePanel.tsx +0 -0
  221. {aether_quant-0.3.0 → aether_quant-0.4.0}/webui/src/components/tracing/BacktestEquityPanel.tsx +0 -0
  222. {aether_quant-0.3.0 → aether_quant-0.4.0}/webui/src/components/tracing/DivergingBarChart.tsx +0 -0
  223. {aether_quant-0.3.0 → aether_quant-0.4.0}/webui/src/components/tracing/LineChart.tsx +0 -0
  224. {aether_quant-0.3.0 → aether_quant-0.4.0}/webui/src/components/tracing/MetricsSnapshotPanel.tsx +0 -0
  225. {aether_quant-0.3.0 → aether_quant-0.4.0}/webui/src/components/tracing/ObservationEquityPanel.tsx +0 -0
  226. {aether_quant-0.3.0 → aether_quant-0.4.0}/webui/src/index.css +0 -0
  227. {aether_quant-0.3.0 → aether_quant-0.4.0}/webui/src/lib/downsample.ts +0 -0
  228. {aether_quant-0.3.0 → aether_quant-0.4.0}/webui/src/lib/format.ts +0 -0
  229. {aether_quant-0.3.0 → aether_quant-0.4.0}/webui/src/main.tsx +0 -0
  230. {aether_quant-0.3.0 → aether_quant-0.4.0}/webui/src/pages/NeuralNetworkPage.tsx +0 -0
  231. {aether_quant-0.3.0 → aether_quant-0.4.0}/webui/src/pages/Overview.tsx +0 -0
  232. {aether_quant-0.3.0 → aether_quant-0.4.0}/webui/src/pages/RiskPage.tsx +0 -0
  233. {aether_quant-0.3.0 → aether_quant-0.4.0}/webui/src/pages/TopologyPage.tsx +0 -0
  234. {aether_quant-0.3.0 → aether_quant-0.4.0}/webui/src/pages/TracingPage.tsx +0 -0
  235. {aether_quant-0.3.0 → aether_quant-0.4.0}/webui/src/types/state.ts +0 -0
  236. {aether_quant-0.3.0 → aether_quant-0.4.0}/webui/src/types/tracing.ts +0 -0
  237. {aether_quant-0.3.0 → aether_quant-0.4.0}/webui/tsconfig.app.json +0 -0
  238. {aether_quant-0.3.0 → aether_quant-0.4.0}/webui/tsconfig.json +0 -0
  239. {aether_quant-0.3.0 → aether_quant-0.4.0}/webui/tsconfig.node.json +0 -0
  240. {aether_quant-0.3.0 → aether_quant-0.4.0}/webui/vite.config.ts +0 -0
@@ -94,3 +94,8 @@ Aether-quant-Obsidian-Vault/
94
94
  # Webui (React/Vite) build artifacts and dependencies
95
95
  webui/node_modules/
96
96
  webui/dist/
97
+
98
+ # Matplotlib font cache, redirected here by main.py so it persists across
99
+ # Lean CLI's ephemeral per-backtest Docker containers instead of rebuilding
100
+ # every run (see Problems.md)
101
+ .matplotlib_cache/
@@ -1,5 +1,5 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aether-quant
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
4
  Summary: Aether Quant convenience CLI
5
5
  Requires-Python: >=3.10
@@ -35,6 +35,7 @@ window and hope it generalizes.
35
35
  - [Getting Started](#getting-started)
36
36
  - [Requirements](#requirements)
37
37
  - [Architecture](#architecture)
38
+ - [Universe Size](#universe-size)
38
39
  - [Project Structure](#project-structure)
39
40
  - [Module Documentation](#module-documentation)
40
41
  - [Development Documentation](#development-documentation)
@@ -202,6 +203,105 @@ and more), the module map, and an honest analysis of what would need to
202
203
  change for this to become a genuinely low-latency/HFT system — see
203
204
  **[`development/v2_architecture.md`](development/v2_architecture.md)**.
204
205
 
206
+ ## Universe Size
207
+
208
+ The trading universe currently spans **20 assets** — 15 equities and 5
209
+ crypto pairs — defined in `config.json`'s `phase1.universe.assets` and
210
+ shared across training, validation, and backtesting
211
+ (`phase1.universe.common_window`: `2014-12-01` to `2021-03-31`).
212
+
213
+ | Ticker | Type | Role |
214
+ |---|---|---|
215
+ | AAPL | Equity | Trading |
216
+ | SPY | Equity | Trading |
217
+ | QQQ | Equity | Trading |
218
+ | IWM | Equity | Trading |
219
+ | EEM | Equity | Trading |
220
+ | BAC | Equity | Trading |
221
+ | IBM | Equity | Trading |
222
+ | AIG | Equity | Trading |
223
+ | BNO | Equity | Trading |
224
+ | FB | Equity | Trading |
225
+ | GOOG | Equity | Trading |
226
+ | GOOGL | Equity | Trading |
227
+ | USO | Equity | Trading |
228
+ | WM | Equity | Trading |
229
+ | AAA | Equity | Observation-only (thin history) |
230
+ | BTCUSD | Crypto | Trading |
231
+ | ETHUSD | Crypto | Observation-only (thin history) |
232
+ | LTCUSD | Crypto | Trading |
233
+ | XRPUSD | Crypto | Observation-only (thin history) |
234
+ | ADAUSD | Crypto | Observation-only (thin history) |
235
+
236
+ "Observation-only" assets (Phase 9's `asset_quality` gate) are still fed
237
+ through the full model/expert/topology pipeline every bar and visible on
238
+ the dashboard, but are never sized into real positions — their real
239
+ history is too short relative to the training window to be trusted for
240
+ trading decisions (see [`development/Changelog.md`](development/Changelog.md)
241
+ for the exact row-count thresholds). This is re-evaluated automatically
242
+ every time `train.py` rebuilds the dataset, so an asset can move between
243
+ these two roles as more history accumulates.
244
+
245
+ ```mermaid
246
+ flowchart TD
247
+ DNN(("Baseline DNN<br/>+ MoE Experts<br/>bullish / bearish /<br/>sideways / volatility"))
248
+
249
+ subgraph Equities["Equities (15)"]
250
+ AAPL["AAPL"]
251
+ SPY["SPY"]
252
+ QQQ["QQQ"]
253
+ IWM["IWM"]
254
+ EEM["EEM"]
255
+ BAC["BAC"]
256
+ IBM["IBM"]
257
+ AIG["AIG"]
258
+ BNO["BNO"]
259
+ FB["FB"]
260
+ GOOG["GOOG"]
261
+ GOOGL["GOOGL"]
262
+ USO["USO"]
263
+ WM["WM"]
264
+ AAA["AAA"]
265
+ end
266
+
267
+ subgraph Crypto["Crypto (5)"]
268
+ BTCUSD["BTCUSD"]
269
+ ETHUSD["ETHUSD"]
270
+ LTCUSD["LTCUSD"]
271
+ XRPUSD["XRPUSD"]
272
+ ADAUSD["ADAUSD"]
273
+ end
274
+
275
+ AAPL --- DNN
276
+ SPY --- DNN
277
+ QQQ --- DNN
278
+ IWM --- DNN
279
+ EEM --- DNN
280
+ BAC --- DNN
281
+ IBM --- DNN
282
+ AIG --- DNN
283
+ BNO --- DNN
284
+ FB --- DNN
285
+ GOOG --- DNN
286
+ GOOGL --- DNN
287
+ USO --- DNN
288
+ WM --- DNN
289
+ AAA --- DNN
290
+ BTCUSD --- DNN
291
+ ETHUSD --- DNN
292
+ LTCUSD --- DNN
293
+ XRPUSD --- DNN
294
+ ADAUSD --- DNN
295
+
296
+ classDef hub fill:#1A1A1A,stroke:#FF8C00,color:#FF8C00,stroke-width:2px;
297
+ classDef trading fill:#FF8C00,stroke:#1A1A1A,color:#1A1A1A,stroke-width:1px;
298
+ classDef observation fill:#3A3A3A,stroke:#FF8C00,color:#FF8C00,stroke-width:1px,stroke-dasharray: 4 2;
299
+
300
+ class DNN hub;
301
+ class AAPL,SPY,QQQ,IWM,EEM,BAC,IBM,AIG,BNO,FB,GOOG,GOOGL,USO,WM,BTCUSD,LTCUSD trading;
302
+ class AAA,ETHUSD,XRPUSD,ADAUSD observation;
303
+ ```
304
+
205
305
  ## Project Structure
206
306
 
207
307
  ```text
@@ -291,16 +391,77 @@ and how it's wired in — this table is the index.
291
391
 
292
392
  | Metric | Value |
293
393
  |---|---|
294
- | Backtest window | 2014-12-01 to 2016-07-06 |
295
- | Last updated | 2026-07-05 15:37 UTC (auto-generated by `aq backtest`) |
394
+ | Backtest window | 2014-12-01 to 2018-08-15 |
395
+ | Sharpe Ratio | -0.765 |
396
+ | Net Profit | -7.955% |
397
+ | Compounding Annual Return | -2.212% |
398
+ | Drawdown | 12.100% |
399
+ | Total Orders | 15 |
400
+ | Win Rate | 0% |
401
+ | Last updated | 2026-07-05 16:59 UTC (auto-generated by `aq backtest`) |
296
402
  <!-- AQ:BACKTEST_END -->
297
403
 
404
+ <details>
405
+ <summary><strong>Full Lean statistics</strong> (Sharpe, Sortino, Alpha/Beta, fees, capacity, and everything else Lean reports)</summary>
406
+
407
+ <!-- AQ:BACKTEST_FULL_STATS_START -->
408
+ | Metric | Value |
409
+ |---|---|
410
+ | Total Orders | 15 |
411
+ | Average Win | 0% |
412
+ | Average Loss | -1.64% |
413
+ | Compounding Annual Return | -2.212% |
414
+ | Drawdown | 12.100% |
415
+ | Expectancy | -1 |
416
+ | Start Equity | 100000 |
417
+ | End Equity | 92045.02 |
418
+ | Net Profit | -7.955% |
419
+ | Sharpe Ratio | -0.765 |
420
+ | Sortino Ratio | -0.531 |
421
+ | Probabilistic Sharpe Ratio | 0.052% |
422
+ | Loss Rate | 100% |
423
+ | Win Rate | 0% |
424
+ | Profit-Loss Ratio | 0 |
425
+ | Alpha | -0.043 |
426
+ | Beta | 0.224 |
427
+ | Annual Standard Deviation | 0.036 |
428
+ | Annual Variance | 0.001 |
429
+ | Information Ratio | -1.103 |
430
+ | Tracking Error | 0.087 |
431
+ | Treynor Ratio | -0.123 |
432
+ | Total Fees | $14.51 |
433
+ | Estimated Strategy Capacity | $310000000.00 |
434
+ | Lowest Capacity Asset | IBM R735QTJ8XC9X |
435
+ | Portfolio Turnover | 0.06% |
436
+ | Drawdown Recovery | 57 |
437
+ <!-- AQ:BACKTEST_FULL_STATS_END -->
438
+
439
+ </details>
440
+
298
441
  This section is regenerated automatically every time you run `aq backtest`
299
442
  (see [`generate_backtest_report.py`](generate_backtest_report.py)) — it
300
443
  always reflects your most recent successful Lean backtest, reading directly
301
444
  from Lean's own result JSON (strategy equity curve, its native SPY benchmark
302
445
  series, and the full statistics block), so it never goes stale as long as
303
- you keep backtesting.
446
+ you keep backtesting. Both the chart image and every statistic above are
447
+ overwritten on each run — there is no manual step and nothing here can go
448
+ stale relative to your last `aq backtest`.
449
+
450
+ **What this backtest does *not* prove:** a bare `lean backtest .` run
451
+ exercises the full inference stack (baseline model, all 4 experts, MoE
452
+ gating, regime, topology, liquidity) every bar, but it does **not**
453
+ exercise the controlled retraining loop — the "learning while trading"
454
+ half of this system's thesis. That loop is a decoupled, asynchronous
455
+ pipeline (`main.py` → Redis → experience-worker → Postgres →
456
+ performance-trigger-worker → retraining-worker), and a bare backtest run
457
+ outside the Docker Compose network can't even reach Redis — events are
458
+ dropped with a warning, so nothing reaches Postgres, no performance
459
+ trigger can fire, and no retraining ever runs (see
460
+ `development/infrastructure.md`). Exercising retraining for real requires
461
+ the full Compose stack up (`docker compose up -d redis postgres
462
+ experience-worker performance-trigger-worker retraining-worker`) with the
463
+ backtest run inside that network, plus an actual trigger condition being
464
+ met during the run.
304
465
 
305
466
  ## Test Suite
306
467
 
@@ -344,11 +505,12 @@ aq docker up [--lean|--all]
344
505
  aq docker build
345
506
  aq retrain <plan|train|validate|backtest|commit|promote|rollback|status> [...]
346
507
  aq trade-lock --on|--off|--auto|--status
508
+ aq fetch <crypto|stock> --ticker <TICKER> --start <YYYY-MM-DD> --end <YYYY-MM-DD> [--apply]
347
509
  aq status
348
510
  ```
349
511
 
350
- Every command except `aq trade-lock` is a thin `subprocess` wrapper around a
351
- command already documented elsewhere in this README:
512
+ Every command except `aq trade-lock` and `aq fetch` is a thin `subprocess`
513
+ wrapper around a command already documented elsewhere in this README:
352
514
 
353
515
  - **`aq train`** — runs `train.py`: builds the dataset and trains the baseline + expert models.
354
516
  - **`aq test`** — runs the pytest suite and refreshes this README's test badge.
@@ -360,6 +522,7 @@ command already documented elsewhere in this README:
360
522
  - **`aq docker build`** — rebuilds the `aether-quant` app image.
361
523
  - **`aq retrain <stage>`** — dispatches to `python -m retraining.orchestrator <stage> ...` for a single manual pipeline stage.
362
524
  - **`aq trade-lock --on|--off|--auto|--status`** — manually overrides `main.py`'s sticky total-drawdown trade lock (see `development/v2_architecture.md`'s Manual Trade-Lock Override Contract). `--off` deliberately clears an otherwise-permanent lock; `--auto` returns to fully automatic behavior.
525
+ - **`aq fetch <crypto|stock> --ticker <TICKER> --start <YYYY-MM-DD> --end <YYYY-MM-DD> [--apply]`** — fetches historical OHLCV from Yahoo Finance for a ticker that isn't in `config.json` yet, formats it into Lean's zip/CSV convention, and writes it to the right spot under `data/` (`data/crypto/coinbase/daily/<ticker>_trade.zip` or `data/equity/usa/daily/<ticker>.zip`). On `--apply`, it also appends a new asset block to `config.json`'s `phase1.universe.assets[]` — no manual editing needed. Dry run by default (no `--apply`): reports what would happen, writes nothing. Never runs `train.py` itself — once applied, run `python train.py --dataset-only` (then `python train.py` when ready) yourself to actually train on the new ticker. `crypto`/`stock` today; a `derivative` asset class is planned for V3.
363
526
  - **`aq status`** — shows `git status`.
364
527
 
365
528
  ## Release Process
@@ -1,5 +1,5 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aether-quant
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
4
  Summary: Aether Quant convenience CLI
5
5
  Requires-Python: >=3.10
@@ -38,6 +38,7 @@ data/alternative/trading-economics/earnings/.keep
38
38
  data/alternative/trading-economics/indicator/.keep
39
39
  data_pipeline/README.md
40
40
  data_pipeline/__init__.py
41
+ data_pipeline/fetch.py
41
42
  data_pipeline/v2_manifest.py
42
43
  data_pipeline/yfinance_backfill.py
43
44
  development/Changelog.md
@@ -45,6 +46,7 @@ development/Problems.md
45
46
  development/README.md
46
47
  development/backtest_equity_chart.png
47
48
  development/infrastructure.md
49
+ development/logo.png
48
50
  development/v2_architecture.md
49
51
  execution/README.md
50
52
  execution/__init__.py
@@ -124,6 +126,7 @@ tests/test_experience_queue.py
124
126
  tests/test_expert_datasets.py
125
127
  tests/test_expert_models.py
126
128
  tests/test_exported_model.py
129
+ tests/test_fetch.py
127
130
  tests/test_gating_network.py
128
131
  tests/test_generate_backtest_report.py
129
132
  tests/test_lean_backtest.py
@@ -43,6 +43,7 @@
43
43
  "data_pipeline/README.md",
44
44
  "data_pipeline/__init__.py",
45
45
  "data_pipeline/v2_manifest.py",
46
+ "data_pipeline/fetch.py",
46
47
  "data_pipeline/yfinance_backfill.py",
47
48
  "visualization/README.md",
48
49
  "inference/README.md",
@@ -152,6 +153,7 @@
152
153
  "webui/src/types/state.ts",
153
154
  "webui/public/favicon.svg",
154
155
  "webui/public/icons.svg",
156
+ "development/logo.png",
155
157
  "development/backtest_equity_chart.png",
156
158
  "development/README.md",
157
159
  "development/Problems.md",
@@ -209,6 +211,7 @@
209
211
  "tests/test_train_pipeline.py",
210
212
  "tests/test_experience_queue.py",
211
213
  "tests/test_vault_commands.py",
214
+ "tests/test_fetch.py",
212
215
  "data/alternative/sec/aapl/.keep",
213
216
  "data/alternative/trading-economics/.keep",
214
217
  "data/alternative/trading-economics/calendar/.keep",
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "0.4.0",
3
+ "distance": 0,
4
+ "node": "gae5c0bcd1f1cd83ef424ead2d3ced059c756a449",
5
+ "dirty": false,
6
+ "branch": "HEAD",
7
+ "node_date": "2026-07-06"
8
+ }
@@ -1,4 +1,5 @@
1
1
  aq_cli
2
+ data_pipeline
2
3
  execution
3
4
  generate_backtest_report
4
5
  risk
@@ -3,11 +3,12 @@
3
3
  Matches this codebase's existing CLI convention exactly (see
4
4
  `retraining/orchestrator.py`'s `argparse` + `subparsers.add_parser(...)`
5
5
  shape) - a single-file dispatcher, not a framework. Every subcommand other
6
- than `trade-lock` is a thin `subprocess.run(...)` wrapper around a command
7
- that already exists and is already documented elsewhere (README.md,
8
- development/infrastructure.md) - no logic is reimplemented here, this file
9
- only saves typing. `trade-lock` is the one exception: it calls
10
- `risk/manual_override.py` directly, no subprocess.
6
+ than `trade-lock` and `fetch` is a thin `subprocess.run(...)` wrapper around
7
+ a command that already exists and is already documented elsewhere
8
+ (README.md, development/infrastructure.md) - no logic is reimplemented
9
+ here, this file only saves typing. `trade-lock` and `fetch` are the two
10
+ exceptions: they call `risk/manual_override.py` and
11
+ `data_pipeline/fetch.py` directly, in-process, no subprocess.
11
12
 
12
13
  Deliberately scoped for v1 - wraps the commands already in daily use, not
13
14
  every command mentioned anywhere in the project. Designed to be extended
@@ -31,9 +32,11 @@ import subprocess
31
32
  import sys
32
33
  import time
33
34
  import urllib.request
35
+ from datetime import date
34
36
  from importlib.metadata import version as installed_version
35
37
  from pathlib import Path
36
38
 
39
+ from data_pipeline.fetch import ASSET_CLASSES, fetch_adhoc_asset
37
40
  from risk.manual_override import read_manual_trade_lock_override, write_manual_trade_lock_override
38
41
 
39
42
  ROOT_DIR = Path(__file__).resolve().parent
@@ -94,6 +97,18 @@ def _parse_simple_version(value: str) -> tuple[int, ...] | None:
94
97
  return None
95
98
 
96
99
 
100
+ def _iso_date(value: str) -> str:
101
+ """Validates --start/--end as strict ISO 8601 YYYY-MM-DD, matching the
102
+ convention used everywhere else in this repo (config.json,
103
+ yfinance_backfill.py) - rejects other formats (e.g. DD.MM.YYYY) with a
104
+ clear error instead of a confusing downstream yfinance failure."""
105
+ try:
106
+ date.fromisoformat(value)
107
+ except ValueError as exc:
108
+ raise argparse.ArgumentTypeError(f"invalid date {value!r} - expected ISO 8601 YYYY-MM-DD") from exc
109
+ return value
110
+
111
+
97
112
  def _read_update_cache() -> dict:
98
113
  if not UPDATE_CACHE_PATH.exists():
99
114
  return {}
@@ -340,6 +355,30 @@ def cmd_trade_lock(args: argparse.Namespace) -> int:
340
355
  return 0
341
356
 
342
357
 
358
+ def cmd_fetch(args: argparse.Namespace) -> int:
359
+ report = fetch_adhoc_asset(args.asset_class, args.ticker, args.start, args.end, apply=args.apply)
360
+ label = "APPLY" if args.apply else "DRY RUN"
361
+ print(f"{label} — {report['ticker']} ({report['yahoo_symbol']}): {report['action']}, rows_fetched={report['rows_fetched']}")
362
+ if report["suggested_available_from"]:
363
+ print(f" date range fetched: {report['suggested_available_from']} .. {report['suggested_available_to']}")
364
+ print(f" data_path: {report['data_path']}")
365
+
366
+ if report["config_status"] == "added":
367
+ print(f" config.json: added a new {report['ticker']} asset block to phase1.universe.assets[]")
368
+ elif report["config_status"] == "already_exists":
369
+ print(
370
+ f" config.json: {report['ticker']} is already configured - left untouched. "
371
+ "Use data_pipeline/yfinance_backfill.py to extend an existing asset's date range instead."
372
+ )
373
+
374
+ if not args.apply:
375
+ print("\nDry run only — nothing was written. Re-run with --apply to write the zip file and update config.json.")
376
+ elif report["action"] == "written":
377
+ print("\nReady to prepare training: run `python train.py --dataset-only` to confirm this ticker's asset quality, then `python train.py` when ready.")
378
+
379
+ return 1 if report["action"] == "no_data_returned" else 0
380
+
381
+
343
382
  def cmd_status(_args: argparse.Namespace) -> int:
344
383
  return _run(["git", "status"])
345
384
 
@@ -409,6 +448,20 @@ def build_parser() -> argparse.ArgumentParser:
409
448
  trade_lock_group.add_argument("--status", dest="status", action="store_true", help="Print the current override state")
410
449
  trade_lock_parser.set_defaults(func=cmd_trade_lock)
411
450
 
451
+ fetch_parser = subparsers.add_parser(
452
+ "fetch", help="Ad-hoc fetch of historical OHLCV from Yahoo Finance for a ticker not yet in config.json"
453
+ )
454
+ fetch_parser.add_argument(
455
+ "asset_class", choices=list(ASSET_CLASSES), help="Asset class (picks the Lean data_path/market convention)"
456
+ )
457
+ fetch_parser.add_argument("--ticker", required=True, help="Internal ticker, e.g. AAPL or BTCUSD")
458
+ fetch_parser.add_argument("--start", required=True, type=_iso_date, help="Start date, ISO 8601 YYYY-MM-DD")
459
+ fetch_parser.add_argument("--end", required=True, type=_iso_date, help="End date, ISO 8601 YYYY-MM-DD")
460
+ fetch_parser.add_argument(
461
+ "--apply", action="store_true", help="Actually write the zip file and update config.json (default: dry run, report only)"
462
+ )
463
+ fetch_parser.set_defaults(func=cmd_fetch)
464
+
412
465
  status_parser = subparsers.add_parser("status", help="Show git status")
413
466
  status_parser.set_defaults(func=cmd_status)
414
467
 
@@ -35,3 +35,30 @@ Usage: `python -m data_pipeline.yfinance_backfill [--tickers ETHUSD LTCUSD] [--a
35
35
  `yfinance` is a dev-only dependency (`requirements/requirements-dev.txt`),
36
36
  never in `requirements.txt`/`requirements-runtime.txt`.
37
37
 
38
+ ## Ad-hoc ticker fetch (`aq fetch`)
39
+
40
+ `fetch.py` is a narrower sibling to `yfinance_backfill.py` above, with a
41
+ different scope and a deliberately different `config.json` policy:
42
+
43
+ | | `yfinance_backfill.py` | `fetch.py` (backs `aq fetch`) |
44
+ |---|---|---|
45
+ | Scope | Fills gaps in **already-configured** assets | Fetches **any** ticker, including ones with no `config.json` entry at all |
46
+ | Date range | Auto-detected gap vs. the asset's `"backfill"` block | Explicit `--start`/`--end` |
47
+ | `config.json` | **Never** edited — only prints suggested values | **Writes** a new asset block on `--apply`, for a ticker that isn't already configured |
48
+
49
+ The differing `config.json` policy is deliberate, not an inconsistency:
50
+ `yfinance_backfill.py`'s rule exists to stop it from silently widening an
51
+ *existing*, already-trained asset's date range without a human decision.
52
+ `fetch.py` exists specifically to *add a brand-new ticker* to the universe
53
+ on purpose — auto-writing `config.json` there is the intended effect, not
54
+ an accidental side effect. If a ticker fetched via `aq fetch` already has a
55
+ `config.json` entry, `fetch.py` leaves it untouched and points at
56
+ `yfinance_backfill.py` instead, since extending an existing asset's range
57
+ is that script's job, not this one's.
58
+
59
+ Reuses `yfinance_backfill.py`'s pure functions (`fetch_yahoo_ohlcv`,
60
+ `scale_for_lean`, `write_lean_zip`) unchanged — no duplicated logic. Same
61
+ dry-run-by-default/`--apply` safety convention. Never runs `train.py`
62
+ itself; see the root [`README.md`](../README.md#cli-reference) for the
63
+ full `aq fetch` usage.
64
+
@@ -0,0 +1,134 @@
1
+ """Ad-hoc historical OHLCV fetch for tickers not (yet) in config.json.
2
+
3
+ Distinct responsibility from yfinance_backfill.py: that module fills gaps in
4
+ already-configured assets via a config.json "backfill" block, and
5
+ deliberately never touches config.json (widening an *existing*, already
6
+ -trained asset's date range is a decision a human should make explicitly).
7
+ This module fetches an explicit date range for *any* ticker, including ones
8
+ with no config.json entry at all, and — on --apply — *does* add a new asset
9
+ block to config.json, because adding a brand-new ticker to the universe is
10
+ this module's entire purpose, not an accidental side effect.
11
+
12
+ Reuses yfinance_backfill.py's config.json-independent pure functions
13
+ (fetch_yahoo_ohlcv, scale_for_lean, write_lean_zip) - no duplicated logic.
14
+
15
+ Never runs `train.py` itself - preparing a ticker for training (fetching
16
+ its data, formatting it for Lean, wiring it into config.json's universe) is
17
+ this module's job; actually training on it stays a deliberate, separate
18
+ step (`python train.py`).
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import json
24
+ from pathlib import Path
25
+ from typing import Callable
26
+
27
+ from data_pipeline.yfinance_backfill import fetch_yahoo_ohlcv, scale_for_lean, write_lean_zip
28
+
29
+ ROOT = Path(__file__).resolve().parents[1]
30
+ CONFIG_PATH = ROOT / "config.json"
31
+
32
+
33
+ def _crypto_yahoo_symbol(ticker: str) -> str:
34
+ """BTCUSD -> BTC-USD, DOGEUSD -> DOGE-USD: strip a trailing USD and join
35
+ with a hyphen, matching Yahoo Finance's crypto pair naming convention."""
36
+ base = ticker[:-3] if ticker.upper().endswith("USD") else ticker
37
+ return f"{base}-USD"
38
+
39
+
40
+ # One entry per asset class - adding "derivative" in V3 is one new entry
41
+ # here (plus adding "derivative" to the CLI's `choices=`), not a redesign.
42
+ ASSET_CLASS_CONFIG = {
43
+ "crypto": {
44
+ "security_type": "crypto",
45
+ "market": "coinbase",
46
+ "data_path_fn": lambda ticker: ROOT / "data" / "crypto" / "coinbase" / "daily" / f"{ticker.lower()}_trade.zip",
47
+ "yahoo_symbol_fn": _crypto_yahoo_symbol,
48
+ },
49
+ "stock": {
50
+ "security_type": "equity",
51
+ "market": "usa",
52
+ "data_path_fn": lambda ticker: ROOT / "data" / "equity" / "usa" / "daily" / f"{ticker.lower()}.zip",
53
+ "yahoo_symbol_fn": lambda ticker: ticker,
54
+ },
55
+ }
56
+ ASSET_CLASSES = tuple(ASSET_CLASS_CONFIG.keys())
57
+
58
+
59
+ def add_asset_to_config(config_path: Path, asset_block: dict) -> str:
60
+ """Appends asset_block to config.json's phase1.universe.assets[] unless
61
+ a block with the same ticker already exists there. Returns "added" or
62
+ "already_exists". Preserves the file's existing exact formatting
63
+ (4-space indent, trailing newline)."""
64
+ config = json.loads(config_path.read_text(encoding="utf-8"))
65
+ assets = config["phase1"]["universe"]["assets"]
66
+
67
+ if any(existing["ticker"] == asset_block["ticker"] for existing in assets):
68
+ return "already_exists"
69
+
70
+ assets.append(asset_block)
71
+ config_path.write_text(json.dumps(config, indent=4) + "\n", encoding="utf-8")
72
+ return "added"
73
+
74
+
75
+ def fetch_adhoc_asset(
76
+ asset_class: str,
77
+ ticker: str,
78
+ start: str,
79
+ end: str,
80
+ *,
81
+ apply: bool,
82
+ fetch_fn: Callable = fetch_yahoo_ohlcv,
83
+ config_path: Path = CONFIG_PATH,
84
+ ) -> dict:
85
+ """apply=False (default): dry run - fetches but writes nothing (no zip,
86
+ no config.json edit). apply=True: writes the Lean zip via
87
+ write_lean_zip(), then adds a new config.json asset block if this
88
+ ticker isn't already configured. fetch_fn is the injection point for
89
+ tests (never real yfinance in tests), matching
90
+ yfinance_backfill.run_backfill()'s exact convention."""
91
+ ticker = ticker.upper()
92
+ class_config = ASSET_CLASS_CONFIG[asset_class]
93
+ yahoo_symbol = class_config["yahoo_symbol_fn"](ticker)
94
+ output_zip = class_config["data_path_fn"](ticker)
95
+ security_type = class_config["security_type"]
96
+
97
+ rows = fetch_fn(yahoo_symbol, start, end)
98
+ scaled_rows = scale_for_lean(rows, security_type)
99
+
100
+ dates = [row["date"] for row in rows]
101
+ suggested_from = min(dates) if dates else None
102
+ suggested_to = max(dates) if dates else None
103
+
104
+ config_status = "not_attempted"
105
+ if not scaled_rows:
106
+ action = "no_data_returned"
107
+ elif apply:
108
+ write_lean_zip(output_zip, ticker, scaled_rows, merge_with_existing=True)
109
+ action = "written"
110
+ config_status = add_asset_to_config(
111
+ config_path,
112
+ {
113
+ "ticker": ticker,
114
+ "security_type": security_type,
115
+ "market": class_config["market"],
116
+ "data_path": output_zip.relative_to(ROOT).as_posix(),
117
+ "available_from": suggested_from.isoformat(),
118
+ "available_to": suggested_to.isoformat(),
119
+ },
120
+ )
121
+ else:
122
+ action = "dry_run"
123
+
124
+ return {
125
+ "ticker": ticker,
126
+ "asset_class": asset_class,
127
+ "yahoo_symbol": yahoo_symbol,
128
+ "data_path": str(output_zip),
129
+ "action": action,
130
+ "rows_fetched": len(rows),
131
+ "suggested_available_from": suggested_from.isoformat() if suggested_from else None,
132
+ "suggested_available_to": suggested_to.isoformat() if suggested_to else None,
133
+ "config_status": config_status,
134
+ }