aether-quant 0.4.0__tar.gz → 0.5.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 (252) hide show
  1. {aether_quant-0.4.0 → aether_quant-0.5.0}/Dockerfile.retraining_worker +3 -0
  2. {aether_quant-0.4.0 → aether_quant-0.5.0}/PKG-INFO +1 -1
  3. {aether_quant-0.4.0 → aether_quant-0.5.0}/README.md +48 -37
  4. {aether_quant-0.4.0 → aether_quant-0.5.0}/aether_quant.egg-info/PKG-INFO +1 -1
  5. {aether_quant-0.4.0 → aether_quant-0.5.0}/aether_quant.egg-info/SOURCES.txt +8 -0
  6. {aether_quant-0.4.0 → aether_quant-0.5.0}/aether_quant.egg-info/scm_file_list.json +8 -0
  7. aether_quant-0.5.0/aether_quant.egg-info/scm_version.json +8 -0
  8. {aether_quant-0.4.0 → aether_quant-0.5.0}/analyzer/README.md +11 -0
  9. {aether_quant-0.4.0 → aether_quant-0.5.0}/aq_cli.py +41 -0
  10. {aether_quant-0.4.0 → aether_quant-0.5.0}/development/Changelog.md +186 -0
  11. {aether_quant-0.4.0 → aether_quant-0.5.0}/development/Problems.md +103 -12
  12. aether_quant-0.5.0/development/backtest_equity_chart.png +0 -0
  13. {aether_quant-0.4.0 → aether_quant-0.5.0}/development/v2_architecture.md +20 -0
  14. {aether_quant-0.4.0 → aether_quant-0.5.0}/main.py +68 -2
  15. aether_quant-0.5.0/ml/gating_feature_schema.json +30 -0
  16. aether_quant-0.5.0/ml/gating_model.json +570 -0
  17. aether_quant-0.5.0/ml/gating_training_metrics.json +190 -0
  18. aether_quant-0.5.0/ml/versions/gating-only-26218402-34d9-4a0a-add8-ee5d50dd0459/gating_feature_schema.json +30 -0
  19. aether_quant-0.5.0/ml/versions/gating-only-26218402-34d9-4a0a-add8-ee5d50dd0459/gating_model.json +570 -0
  20. aether_quant-0.5.0/ml/versions/gating-only-26218402-34d9-4a0a-add8-ee5d50dd0459/gating_training_metrics.json +190 -0
  21. aether_quant-0.5.0/moe/README.md +61 -0
  22. {aether_quant-0.4.0 → aether_quant-0.5.0}/moe/__init__.py +4 -0
  23. {aether_quant-0.4.0 → aether_quant-0.5.0}/moe/gating.py +101 -36
  24. {aether_quant-0.4.0 → aether_quant-0.5.0}/monitoring/README.md +1 -0
  25. {aether_quant-0.4.0 → aether_quant-0.5.0}/monitoring/neural_network_state.py +41 -22
  26. {aether_quant-0.4.0 → aether_quant-0.5.0}/retraining/__init__.py +4 -0
  27. {aether_quant-0.4.0 → aether_quant-0.5.0}/retraining/artifacts.py +23 -6
  28. {aether_quant-0.4.0 → aether_quant-0.5.0}/retraining/orchestrator.py +54 -0
  29. {aether_quant-0.4.0 → aether_quant-0.5.0}/retraining/worker.py +6 -0
  30. aether_quant-0.5.0/risk/README.md +50 -0
  31. {aether_quant-0.4.0 → aether_quant-0.5.0}/risk/position_sizing.py +52 -1
  32. {aether_quant-0.4.0 → aether_quant-0.5.0}/risk_controls.py +14 -0
  33. {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_aq_cli.py +50 -0
  34. aether_quant-0.5.0/tests/test_gating_network.py +235 -0
  35. {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_neural_network_state.py +34 -5
  36. aether_quant-0.5.0/tests/test_position_sizing.py +123 -0
  37. {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_retraining_artifacts.py +66 -1
  38. {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_retraining_orchestrator.py +49 -0
  39. {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_retraining_worker.py +20 -12
  40. {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_risk_controls.py +19 -0
  41. aether_quant-0.5.0/tests/test_train_gating.py +179 -0
  42. {aether_quant-0.4.0 → aether_quant-0.5.0}/topology/README.md +7 -0
  43. aether_quant-0.5.0/train_gating.py +436 -0
  44. {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/README.md +13 -0
  45. {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/components/neuralnet/NeuralNetworkScene3D.tsx +5 -3
  46. {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/types/state.ts +2 -2
  47. aether_quant-0.4.0/aether_quant.egg-info/scm_version.json +0 -8
  48. aether_quant-0.4.0/development/backtest_equity_chart.png +0 -0
  49. aether_quant-0.4.0/moe/README.md +0 -19
  50. aether_quant-0.4.0/risk/README.md +0 -19
  51. aether_quant-0.4.0/tests/test_gating_network.py +0 -97
  52. aether_quant-0.4.0/tests/test_position_sizing.py +0 -55
  53. {aether_quant-0.4.0 → aether_quant-0.5.0}/.dockerignore +0 -0
  54. {aether_quant-0.4.0 → aether_quant-0.5.0}/.env.compose.example +0 -0
  55. {aether_quant-0.4.0 → aether_quant-0.5.0}/.env.live.example +0 -0
  56. {aether_quant-0.4.0 → aether_quant-0.5.0}/.gitattributes +0 -0
  57. {aether_quant-0.4.0 → aether_quant-0.5.0}/.github/workflows/ci.yml +0 -0
  58. {aether_quant-0.4.0 → aether_quant-0.5.0}/.github/workflows/release.yml +0 -0
  59. {aether_quant-0.4.0 → aether_quant-0.5.0}/.gitignore +0 -0
  60. {aether_quant-0.4.0 → aether_quant-0.5.0}/Dockerfile +0 -0
  61. {aether_quant-0.4.0 → aether_quant-0.5.0}/Dockerfile.workers +0 -0
  62. {aether_quant-0.4.0 → aether_quant-0.5.0}/aether_quant.egg-info/dependency_links.txt +0 -0
  63. {aether_quant-0.4.0 → aether_quant-0.5.0}/aether_quant.egg-info/entry_points.txt +0 -0
  64. {aether_quant-0.4.0 → aether_quant-0.5.0}/aether_quant.egg-info/top_level.txt +0 -0
  65. {aether_quant-0.4.0 → aether_quant-0.5.0}/analyzer/__init__.py +0 -0
  66. {aether_quant-0.4.0 → aether_quant-0.5.0}/analyzer/market_analyzer.py +0 -0
  67. {aether_quant-0.4.0 → aether_quant-0.5.0}/backtests/.gitkeep +0 -0
  68. {aether_quant-0.4.0 → aether_quant-0.5.0}/data/alternative/estimize/consensus/aapl/.keep +0 -0
  69. {aether_quant-0.4.0 → aether_quant-0.5.0}/data/alternative/estimize/estimate/.keep +0 -0
  70. {aether_quant-0.4.0 → aether_quant-0.5.0}/data/alternative/estimize/release/.keep +0 -0
  71. {aether_quant-0.4.0 → aether_quant-0.5.0}/data/alternative/sec/aapl/.keep +0 -0
  72. {aether_quant-0.4.0 → aether_quant-0.5.0}/data/alternative/trading-economics/.keep +0 -0
  73. {aether_quant-0.4.0 → aether_quant-0.5.0}/data/alternative/trading-economics/calendar/.keep +0 -0
  74. {aether_quant-0.4.0 → aether_quant-0.5.0}/data/alternative/trading-economics/earnings/.keep +0 -0
  75. {aether_quant-0.4.0 → aether_quant-0.5.0}/data/alternative/trading-economics/indicator/.keep +0 -0
  76. {aether_quant-0.4.0 → aether_quant-0.5.0}/data_pipeline/README.md +0 -0
  77. {aether_quant-0.4.0 → aether_quant-0.5.0}/data_pipeline/__init__.py +0 -0
  78. {aether_quant-0.4.0 → aether_quant-0.5.0}/data_pipeline/fetch.py +0 -0
  79. {aether_quant-0.4.0 → aether_quant-0.5.0}/data_pipeline/v2_manifest.py +0 -0
  80. {aether_quant-0.4.0 → aether_quant-0.5.0}/data_pipeline/yfinance_backfill.py +0 -0
  81. {aether_quant-0.4.0 → aether_quant-0.5.0}/development/README.md +0 -0
  82. {aether_quant-0.4.0 → aether_quant-0.5.0}/development/infrastructure.md +0 -0
  83. {aether_quant-0.4.0 → aether_quant-0.5.0}/development/logo.png +0 -0
  84. {aether_quant-0.4.0 → aether_quant-0.5.0}/docker-compose.yml +0 -0
  85. {aether_quant-0.4.0 → aether_quant-0.5.0}/execution/README.md +0 -0
  86. {aether_quant-0.4.0 → aether_quant-0.5.0}/execution/__init__.py +0 -0
  87. {aether_quant-0.4.0 → aether_quant-0.5.0}/execution/config_cache.py +0 -0
  88. {aether_quant-0.4.0 → aether_quant-0.5.0}/execution/live_credentials.py +0 -0
  89. {aether_quant-0.4.0 → aether_quant-0.5.0}/execution/live_credentials_io.py +0 -0
  90. {aether_quant-0.4.0 → aether_quant-0.5.0}/execution/order_gate.py +0 -0
  91. {aether_quant-0.4.0 → aether_quant-0.5.0}/execution/paper_readiness.py +0 -0
  92. {aether_quant-0.4.0 → aether_quant-0.5.0}/execution/paper_readiness_io.py +0 -0
  93. {aether_quant-0.4.0 → aether_quant-0.5.0}/execution/paper_readiness_report.py +0 -0
  94. {aether_quant-0.4.0 → aether_quant-0.5.0}/execution/runtime_config_io.py +0 -0
  95. {aether_quant-0.4.0 → aether_quant-0.5.0}/experience/README.md +0 -0
  96. {aether_quant-0.4.0 → aether_quant-0.5.0}/experience/__init__.py +0 -0
  97. {aether_quant-0.4.0 → aether_quant-0.5.0}/experience/observation_metrics.py +0 -0
  98. {aether_quant-0.4.0 → aether_quant-0.5.0}/experience/postgres_worker.py +0 -0
  99. {aether_quant-0.4.0 → aether_quant-0.5.0}/experience/redis_queue.py +0 -0
  100. {aether_quant-0.4.0 → aether_quant-0.5.0}/experience/simulated_portfolio.py +0 -0
  101. {aether_quant-0.4.0 → aether_quant-0.5.0}/experts/README.md +0 -0
  102. {aether_quant-0.4.0 → aether_quant-0.5.0}/experts/__init__.py +0 -0
  103. {aether_quant-0.4.0 → aether_quant-0.5.0}/experts/expert_datasets.py +0 -0
  104. {aether_quant-0.4.0 → aether_quant-0.5.0}/generate_backtest_report.py +0 -0
  105. {aether_quant-0.4.0 → aether_quant-0.5.0}/inference/README.md +0 -0
  106. {aether_quant-0.4.0 → aether_quant-0.5.0}/inference/__init__.py +0 -0
  107. {aether_quant-0.4.0 → aether_quant-0.5.0}/inference/exported_model.py +0 -0
  108. {aether_quant-0.4.0 → aether_quant-0.5.0}/liquidity/README.md +0 -0
  109. {aether_quant-0.4.0 → aether_quant-0.5.0}/liquidity/__init__.py +0 -0
  110. {aether_quant-0.4.0 → aether_quant-0.5.0}/liquidity/market_liquidity.py +0 -0
  111. {aether_quant-0.4.0 → aether_quant-0.5.0}/ml/README.md +0 -0
  112. {aether_quant-0.4.0 → aether_quant-0.5.0}/monitoring/__init__.py +0 -0
  113. {aether_quant-0.4.0 → aether_quant-0.5.0}/monitoring/api_server.py +0 -0
  114. {aether_quant-0.4.0 → aether_quant-0.5.0}/notifications/README.md +0 -0
  115. {aether_quant-0.4.0 → aether_quant-0.5.0}/notifications/__init__.py +0 -0
  116. {aether_quant-0.4.0 → aether_quant-0.5.0}/notifications/postgres_telegram.py +0 -0
  117. {aether_quant-0.4.0 → aether_quant-0.5.0}/notifications/telegram_alerts.py +0 -0
  118. {aether_quant-0.4.0 → aether_quant-0.5.0}/notifications/telegram_client.py +0 -0
  119. {aether_quant-0.4.0 → aether_quant-0.5.0}/notifications/telegram_worker.py +0 -0
  120. {aether_quant-0.4.0 → aether_quant-0.5.0}/performance/README.md +0 -0
  121. {aether_quant-0.4.0 → aether_quant-0.5.0}/performance/__init__.py +0 -0
  122. {aether_quant-0.4.0 → aether_quant-0.5.0}/performance/postgres_triggers.py +0 -0
  123. {aether_quant-0.4.0 → aether_quant-0.5.0}/performance/trigger_worker.py +0 -0
  124. {aether_quant-0.4.0 → aether_quant-0.5.0}/performance/triggers.py +0 -0
  125. {aether_quant-0.4.0 → aether_quant-0.5.0}/pyproject.toml +0 -0
  126. {aether_quant-0.4.0 → aether_quant-0.5.0}/regime/README.md +0 -0
  127. {aether_quant-0.4.0 → aether_quant-0.5.0}/regime/__init__.py +0 -0
  128. {aether_quant-0.4.0 → aether_quant-0.5.0}/regime/market_regime.py +0 -0
  129. {aether_quant-0.4.0 → aether_quant-0.5.0}/requirements/README.md +0 -0
  130. {aether_quant-0.4.0 → aether_quant-0.5.0}/requirements/requirements-dev.txt +0 -0
  131. {aether_quant-0.4.0 → aether_quant-0.5.0}/requirements/requirements-retraining-worker.txt +0 -0
  132. {aether_quant-0.4.0 → aether_quant-0.5.0}/requirements/requirements-runtime.txt +0 -0
  133. {aether_quant-0.4.0 → aether_quant-0.5.0}/requirements/requirements-workers.txt +0 -0
  134. {aether_quant-0.4.0 → aether_quant-0.5.0}/requirements/requirements.txt +0 -0
  135. {aether_quant-0.4.0 → aether_quant-0.5.0}/retraining/README.md +0 -0
  136. {aether_quant-0.4.0 → aether_quant-0.5.0}/retraining/backtest_gate.py +0 -0
  137. {aether_quant-0.4.0 → aether_quant-0.5.0}/retraining/lean_backtest.py +0 -0
  138. {aether_quant-0.4.0 → aether_quant-0.5.0}/retraining/planning.py +0 -0
  139. {aether_quant-0.4.0 → aether_quant-0.5.0}/retraining/postgres_registry.py +0 -0
  140. {aether_quant-0.4.0 → aether_quant-0.5.0}/retraining/status_export.py +0 -0
  141. {aether_quant-0.4.0 → aether_quant-0.5.0}/retraining/validation_gate.py +0 -0
  142. {aether_quant-0.4.0 → aether_quant-0.5.0}/retraining/vault_client.py +0 -0
  143. {aether_quant-0.4.0 → aether_quant-0.5.0}/retraining/vault_commands.py +0 -0
  144. {aether_quant-0.4.0 → aether_quant-0.5.0}/risk/__init__.py +0 -0
  145. {aether_quant-0.4.0 → aether_quant-0.5.0}/risk/manual_override.py +0 -0
  146. {aether_quant-0.4.0 → aether_quant-0.5.0}/setup.cfg +0 -0
  147. {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/README.md +0 -0
  148. {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_backtest_gate.py +0 -0
  149. {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_config_cache.py +0 -0
  150. {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_experience_queue.py +0 -0
  151. {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_expert_datasets.py +0 -0
  152. {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_expert_models.py +0 -0
  153. {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_exported_model.py +0 -0
  154. {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_fetch.py +0 -0
  155. {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_generate_backtest_report.py +0 -0
  156. {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_lean_backtest.py +0 -0
  157. {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_lean_backtest_ml_coverage.py +0 -0
  158. {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_learned_topology.py +0 -0
  159. {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_live_credentials.py +0 -0
  160. {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_live_credentials_io.py +0 -0
  161. {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_manual_override.py +0 -0
  162. {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_market_analyzer.py +0 -0
  163. {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_market_liquidity.py +0 -0
  164. {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_market_regime.py +0 -0
  165. {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_market_topology.py +0 -0
  166. {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_observation_metrics.py +0 -0
  167. {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_order_gate.py +0 -0
  168. {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_paper_readiness.py +0 -0
  169. {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_paper_readiness_io.py +0 -0
  170. {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_paper_readiness_report.py +0 -0
  171. {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_postgres_telegram.py +0 -0
  172. {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_postgres_triggers.py +0 -0
  173. {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_postgres_worker.py +0 -0
  174. {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_retraining_planning.py +0 -0
  175. {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_retraining_postgres_registry.py +0 -0
  176. {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_runtime_config_io.py +0 -0
  177. {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_simulated_portfolio.py +0 -0
  178. {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_status_export.py +0 -0
  179. {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_telegram_alerts.py +0 -0
  180. {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_telegram_client.py +0 -0
  181. {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_telegram_worker.py +0 -0
  182. {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_train_pipeline.py +0 -0
  183. {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_train_topology.py +0 -0
  184. {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_trigger_worker.py +0 -0
  185. {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_triggers.py +0 -0
  186. {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_v2_pipeline_manifest.py +0 -0
  187. {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_validation_gate.py +0 -0
  188. {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_vault_client.py +0 -0
  189. {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_vault_commands.py +0 -0
  190. {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_yfinance_backfill.py +0 -0
  191. {aether_quant-0.4.0 → aether_quant-0.5.0}/topology/__init__.py +0 -0
  192. {aether_quant-0.4.0 → aether_quant-0.5.0}/topology/learned_topology.py +0 -0
  193. {aether_quant-0.4.0 → aether_quant-0.5.0}/topology/market_topology.py +0 -0
  194. {aether_quant-0.4.0 → aether_quant-0.5.0}/train.py +0 -0
  195. {aether_quant-0.4.0 → aether_quant-0.5.0}/train_topology.py +0 -0
  196. {aether_quant-0.4.0 → aether_quant-0.5.0}/visualization/README.md +0 -0
  197. {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/.gitignore +0 -0
  198. {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/.oxlintrc.json +0 -0
  199. {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/index.html +0 -0
  200. {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/overview_v2_12.png +0 -0
  201. {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/package-lock.json +0 -0
  202. {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/package.json +0 -0
  203. {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/public/favicon.svg +0 -0
  204. {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/public/icons.svg +0 -0
  205. {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/risk_v2_12.png +0 -0
  206. {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/App.tsx +0 -0
  207. {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/api/client.ts +0 -0
  208. {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/api/hooks.ts +0 -0
  209. {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/components/heatmap/AssetHeatmap.tsx +0 -0
  210. {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/components/layout/AppShell.tsx +0 -0
  211. {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/components/layout/Panel.tsx +0 -0
  212. {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/components/monitoring/CountTable.tsx +0 -0
  213. {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/components/monitoring/MonitoringFeeds.tsx +0 -0
  214. {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/components/monitoring/ObservationPanel.tsx +0 -0
  215. {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/components/monitoring/PaperReadinessPanel.tsx +0 -0
  216. {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/components/monitoring/PerformanceTriggersPanel.tsx +0 -0
  217. {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/components/monitoring/RawStateViewer.tsx +0 -0
  218. {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/components/monitoring/RetrainingStatusPanel.tsx +0 -0
  219. {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/components/neuralnet/NeuralNetworkStatsPanel.tsx +0 -0
  220. {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/components/risk/AssetSizingTable.tsx +0 -0
  221. {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/components/risk/LiquidityTable.tsx +0 -0
  222. {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/components/risk/RiskBar.tsx +0 -0
  223. {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/components/risk/RiskCore.tsx +0 -0
  224. {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/components/risk/StrategyRiskCards.tsx +0 -0
  225. {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/components/scene3d/Scene3D.tsx +0 -0
  226. {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/components/scorecards/Scorecards.tsx +0 -0
  227. {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/components/signals/Badge.tsx +0 -0
  228. {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/components/signals/PositionsList.tsx +0 -0
  229. {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/components/signals/SignalBoard.tsx +0 -0
  230. {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/components/topology/ClusterList.tsx +0 -0
  231. {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/components/topology/TopologyLearningPanel.tsx +0 -0
  232. {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/components/topology/TopologyScene3D.tsx +0 -0
  233. {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/components/tracing/AssetPerformancePanel.tsx +0 -0
  234. {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/components/tracing/BacktestEquityPanel.tsx +0 -0
  235. {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/components/tracing/DivergingBarChart.tsx +0 -0
  236. {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/components/tracing/LineChart.tsx +0 -0
  237. {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/components/tracing/MetricsSnapshotPanel.tsx +0 -0
  238. {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/components/tracing/ObservationEquityPanel.tsx +0 -0
  239. {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/index.css +0 -0
  240. {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/lib/downsample.ts +0 -0
  241. {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/lib/format.ts +0 -0
  242. {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/main.tsx +0 -0
  243. {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/pages/NeuralNetworkPage.tsx +0 -0
  244. {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/pages/Overview.tsx +0 -0
  245. {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/pages/RiskPage.tsx +0 -0
  246. {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/pages/TopologyPage.tsx +0 -0
  247. {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/pages/TracingPage.tsx +0 -0
  248. {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/types/tracing.ts +0 -0
  249. {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/tsconfig.app.json +0 -0
  250. {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/tsconfig.json +0 -0
  251. {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/tsconfig.node.json +0 -0
  252. {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/vite.config.ts +0 -0
@@ -8,7 +8,10 @@ COPY performance/ ./performance/
8
8
  COPY regime/ ./regime/
9
9
  COPY experts/ ./experts/
10
10
  COPY topology/ ./topology/
11
+ COPY moe/ ./moe/
12
+ COPY inference/ ./inference/
11
13
  COPY retraining/ ./retraining/
12
14
  COPY train.py .
13
15
  COPY train_topology.py .
16
+ COPY train_gating.py .
14
17
  CMD ["python", "-m", "retraining.worker"]
@@ -1,5 +1,5 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aether-quant
3
- Version: 0.4.0
3
+ Version: 0.5.0
4
4
  Summary: Aether Quant convenience CLI
5
5
  Requires-Python: >=3.10
@@ -10,7 +10,7 @@
10
10
 
11
11
  <p align="center">
12
12
  <img src="https://img.shields.io/badge/python-3.10%2B-FF8C00?style=flat-square&labelColor=1A1A1A&logo=python&logoColor=white" alt="Python 3.10+">
13
- <!-- AQ:TEST_BADGE_START --><img src="https://img.shields.io/badge/tests-528%2F528%20passing-brightgreen?style=flat-square&labelColor=1A1A1A" alt="528 of 528 tests passing"><!-- AQ:TEST_BADGE_END -->
13
+ <!-- AQ:TEST_BADGE_START --><img src="https://img.shields.io/badge/tests-583%2F583%20passing-brightgreen?style=flat-square&labelColor=1A1A1A" alt="583 of 583 tests passing"><!-- AQ:TEST_BADGE_END -->
14
14
  <img src="https://img.shields.io/pypi/v/aether-quant?style=flat-square&labelColor=1A1A1A&color=FF8C00" alt="PyPI version">
15
15
  <img src="https://img.shields.io/badge/docker-ghcr.io%2Faether--quant-2496ED?style=flat-square&labelColor=1A1A1A&logo=docker&logoColor=white" alt="Docker image on GHCR">
16
16
  </p>
@@ -391,14 +391,14 @@ and how it's wired in — this table is the index.
391
391
 
392
392
  | Metric | Value |
393
393
  |---|---|
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`) |
394
+ | Backtest window | 2018-04-01 to 2021-04-02 |
395
+ | Sharpe Ratio | -0.71 |
396
+ | Net Profit | -8.069% |
397
+ | Compounding Annual Return | -2.761% |
398
+ | Drawdown | 12.500% |
399
+ | Total Orders | 27 |
400
+ | Win Rate | 17% |
401
+ | Last updated | 2026-07-06 17:20 UTC (auto-generated by `aq backtest`) |
402
402
  <!-- AQ:BACKTEST_END -->
403
403
 
404
404
  <details>
@@ -407,33 +407,33 @@ and how it's wired in — this table is the index.
407
407
  <!-- AQ:BACKTEST_FULL_STATS_START -->
408
408
  | Metric | Value |
409
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 |
410
+ | Total Orders | 27 |
411
+ | Average Win | 0.18% |
412
+ | Average Loss | -1.70% |
413
+ | Compounding Annual Return | -2.761% |
414
+ | Drawdown | 12.500% |
415
+ | Expectancy | -0.816 |
416
+ | Start Equity | 100000.00 |
417
+ | End Equity | 91930.60 |
418
+ | Net Profit | -8.069% |
419
+ | Sharpe Ratio | -0.71 |
420
+ | Sortino Ratio | -0.471 |
421
+ | Probabilistic Sharpe Ratio | 0.171% |
422
+ | Loss Rate | 83% |
423
+ | Win Rate | 17% |
424
+ | Profit-Loss Ratio | 0.11 |
425
+ | Alpha | -0.058 |
426
+ | Beta | 0.195 |
427
+ | Annual Standard Deviation | 0.051 |
428
+ | Annual Variance | 0.003 |
429
+ | Information Ratio | -0.972 |
430
+ | Tracking Error | 0.155 |
431
+ | Treynor Ratio | -0.184 |
432
+ | Total Fees | $12.00 |
433
+ | Estimated Strategy Capacity | $210000000.00 |
434
434
  | Lowest Capacity Asset | IBM R735QTJ8XC9X |
435
- | Portfolio Turnover | 0.06% |
436
- | Drawdown Recovery | 57 |
435
+ | Portfolio Turnover | 0.07% |
436
+ | Drawdown Recovery | 503 |
437
437
  <!-- AQ:BACKTEST_FULL_STATS_END -->
438
438
 
439
439
  </details>
@@ -463,6 +463,17 @@ experience-worker performance-trigger-worker retraining-worker`) with the
463
463
  backtest run inside that network, plus an actual trigger condition being
464
464
  met during the run.
465
465
 
466
+ **If `phase_v2.backtest.bypass_safety_gates` is `true`:** this backtest
467
+ also does not represent live/paper-deployable behavior. That flag (default
468
+ `false`) disables the sticky total-drawdown lock and the regime detector's
469
+ drawdown-driven `risk_off` override — both real, designed safety behavior
470
+ in live/paper mode — purely to generate enough trade volume for
471
+ statistically meaningful backtest metrics (see `development/Problems.md`
472
+ #18). A backtest run with this flag set shows the underlying model's
473
+ signal quality across more market conditions, not what this system would
474
+ have actually done if deployed — in live/paper mode, both gates would have
475
+ genuinely halted trading exactly as designed.
476
+
466
477
  ## Test Suite
467
478
 
468
479
  507 tests, one file per source module, run via:
@@ -495,7 +506,7 @@ pip install -e .
495
506
  Either way, `aq --help` gives the full command list:
496
507
 
497
508
  ```text
498
- aq train [--dataset-only|--init-only|--experts-only]
509
+ aq train [--dataset-only|--init-only|--experts-only|--gating-only]
499
510
  aq test
500
511
  aq backtest
501
512
  aq report <backtest-folder> <result-id>
@@ -512,7 +523,7 @@ aq status
512
523
  Every command except `aq trade-lock` and `aq fetch` is a thin `subprocess`
513
524
  wrapper around a command already documented elsewhere in this README:
514
525
 
515
- - **`aq train`** — runs `train.py`: builds the dataset and trains the baseline + expert models.
526
+ - **`aq train`** — runs `train.py`: builds the dataset and trains the baseline + expert models. `--gating-only` trains just the learned gating blend (`train_gating.py`) and installs it straight into active `ml/`, mirroring what `--experts-only` already does for the expert models — see `moe/README.md`.
516
527
  - **`aq test`** — runs the pytest suite and refreshes this README's test badge.
517
528
  - **`aq backtest`** — runs `lean backtest .` and refreshes this README's [Backtest Results](#backtest-results) section.
518
529
  - **`aq report <backtest-folder> <result-id>`** — generates Lean's own HTML backtest report (trade blotter, standard Lean charts) at `backtests/<backtest-folder>/report.html`.
@@ -1,5 +1,5 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aether-quant
3
- Version: 0.4.0
3
+ Version: 0.5.0
4
4
  Summary: Aether Quant convenience CLI
5
5
  Requires-Python: >=3.10
@@ -14,6 +14,7 @@ main.py
14
14
  pyproject.toml
15
15
  risk_controls.py
16
16
  train.py
17
+ train_gating.py
17
18
  train_topology.py
18
19
  .github/workflows/ci.yml
19
20
  .github/workflows/release.yml
@@ -74,6 +75,12 @@ liquidity/README.md
74
75
  liquidity/__init__.py
75
76
  liquidity/market_liquidity.py
76
77
  ml/README.md
78
+ ml/gating_feature_schema.json
79
+ ml/gating_model.json
80
+ ml/gating_training_metrics.json
81
+ ml/versions/gating-only-26218402-34d9-4a0a-add8-ee5d50dd0459/gating_feature_schema.json
82
+ ml/versions/gating-only-26218402-34d9-4a0a-add8-ee5d50dd0459/gating_model.json
83
+ ml/versions/gating-only-26218402-34d9-4a0a-add8-ee5d50dd0459/gating_training_metrics.json
77
84
  moe/README.md
78
85
  moe/__init__.py
79
86
  moe/gating.py
@@ -161,6 +168,7 @@ tests/test_status_export.py
161
168
  tests/test_telegram_alerts.py
162
169
  tests/test_telegram_client.py
163
170
  tests/test_telegram_worker.py
171
+ tests/test_train_gating.py
164
172
  tests/test_train_pipeline.py
165
173
  tests/test_train_topology.py
166
174
  tests/test_trigger_worker.py
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "files": [
3
3
  "README.md",
4
+ "train_gating.py",
4
5
  "Dockerfile.workers",
5
6
  "aq_cli.py",
6
7
  "Dockerfile",
@@ -211,6 +212,7 @@
211
212
  "tests/test_train_pipeline.py",
212
213
  "tests/test_experience_queue.py",
213
214
  "tests/test_vault_commands.py",
215
+ "tests/test_train_gating.py",
214
216
  "tests/test_fetch.py",
215
217
  "data/alternative/sec/aapl/.keep",
216
218
  "data/alternative/trading-economics/.keep",
@@ -227,6 +229,12 @@
227
229
  "requirements/requirements-workers.txt",
228
230
  "requirements/requirements-retraining-worker.txt",
229
231
  "ml/README.md",
232
+ "ml/gating_model.json",
233
+ "ml/gating_feature_schema.json",
234
+ "ml/gating_training_metrics.json",
235
+ "ml/versions/gating-only-26218402-34d9-4a0a-add8-ee5d50dd0459/gating_model.json",
236
+ "ml/versions/gating-only-26218402-34d9-4a0a-add8-ee5d50dd0459/gating_feature_schema.json",
237
+ "ml/versions/gating-only-26218402-34d9-4a0a-add8-ee5d50dd0459/gating_training_metrics.json",
230
238
  ".github/workflows/release.yml",
231
239
  ".github/workflows/ci.yml"
232
240
  ]
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "0.5.0",
3
+ "distance": 0,
4
+ "node": "gb230bc437a96905925c7aa521e7006da67cfb98c",
5
+ "dirty": false,
6
+ "branch": "HEAD",
7
+ "node_date": "2026-07-07"
8
+ }
@@ -38,6 +38,17 @@ risk engine, and before action categorization / Lean order placement.
38
38
  explicitly ruled out of scope for that phase as a materially riskier
39
39
  change than what its safety rule ("confidence/uncertainty only, never a
40
40
  randomized decision") allows.
41
+ - **Follow-up:** the learned topology overlay's confidence/disagreement
42
+ scores now *do* reach a real trade — but through position sizing
43
+ (`risk/position_sizing.py::topology_sizing_multiplier()`), not this
44
+ module. That integration point was chosen specifically because sizing
45
+ is already a continuous, shrink-only multiplier applied after this
46
+ analyzer has categorized the action, so it changes only *how large* an
47
+ already-approved trade is, never *whether* one happens — preserving
48
+ exactly the "confidence/uncertainty only, never a randomized decision"
49
+ rule above. This module's own `trade`/`simulate`/`observe`/
50
+ `reduce_risk` categorization remains fully deterministic and
51
+ unaffected. See `risk/README.md` for details.
41
52
  - `main.py` only calls `_apply_signal` when `action == "trade"`. All five
42
53
  actions are written into the per-asset `signal_payload` for
43
54
  dashboard/Grafana visibility regardless of whether a real order is
@@ -32,6 +32,7 @@ import subprocess
32
32
  import sys
33
33
  import time
34
34
  import urllib.request
35
+ import uuid
35
36
  from datetime import date
36
37
  from importlib.metadata import version as installed_version
37
38
  from pathlib import Path
@@ -176,6 +177,8 @@ def check_for_update() -> None:
176
177
 
177
178
 
178
179
  def cmd_train(args: argparse.Namespace) -> int:
180
+ if args.gating_only:
181
+ return _train_gating_only()
179
182
  cmd = [sys.executable, "train.py"]
180
183
  if args.dataset_only:
181
184
  cmd.append("--dataset-only")
@@ -186,6 +189,41 @@ def cmd_train(args: argparse.Namespace) -> int:
186
189
  return _run(cmd)
187
190
 
188
191
 
192
+ def _train_gating_only() -> int:
193
+ """`aq train --gating-only`: trains the learned gating blend
194
+ (train_gating.py) and installs it straight into active ml/, mirroring
195
+ what `train.py --experts-only` already does for the expert models.
196
+
197
+ train_gating.py always writes to ml/versions/<version_id>/ (same
198
+ versioned-candidate convention every other trainer in this project
199
+ uses), so this generates a throwaway version-id, runs the trainer,
200
+ then copies the 3 resulting artifacts into active ml/ - the same
201
+ manual promotion-simulation step already documented for verifying this
202
+ trainer, skipping the full retraining/validate/backtest/commit/promote
203
+ pipeline since this is an ad-hoc, user-triggered run, not a scheduled
204
+ candidate."""
205
+ version_id = f"gating-only-{uuid.uuid4()}"
206
+ returncode = _run([sys.executable, "train_gating.py", "--version-id", version_id])
207
+ if returncode != 0:
208
+ return returncode
209
+
210
+ version_dir = ROOT_DIR / "ml" / "versions" / version_id
211
+ artifact_names = ("gating_model.json", "gating_feature_schema.json", "gating_training_metrics.json")
212
+ if any(not (version_dir / name).exists() for name in artifact_names):
213
+ print(
214
+ "aq train --gating-only: train_gating.py exited 0 but skipped writing artifacts "
215
+ "(likely insufficient validation/backtest rows) - active ml/ left unchanged.",
216
+ file=sys.stderr,
217
+ )
218
+ return 0
219
+
220
+ ml_dir = ROOT_DIR / "ml"
221
+ for name in artifact_names:
222
+ shutil.copy2(version_dir / name, ml_dir / name)
223
+ print(f"aq train --gating-only: copied {', '.join(artifact_names)} into active ml/.")
224
+ return 0
225
+
226
+
189
227
  def _update_readme_test_badge(passed: int, failed: int) -> None:
190
228
  """Atomically rewrites the shields.io test-count badge between the
191
229
  AQ:TEST_BADGE markers in README.md, so it never drifts from the real
@@ -392,6 +430,9 @@ def build_parser() -> argparse.ArgumentParser:
392
430
  train_group.add_argument("--dataset-only", action="store_true", help="Build dataset/scaler/manifest only")
393
431
  train_group.add_argument("--init-only", action="store_true", help="Refresh the data inventory only")
394
432
  train_group.add_argument("--experts-only", action="store_true", help="Train the 4 expert models only")
433
+ train_group.add_argument(
434
+ "--gating-only", action="store_true", help="Train the learned gating blend only (wraps python train_gating.py)"
435
+ )
395
436
  train_parser.set_defaults(func=cmd_train)
396
437
 
397
438
  test_parser = subparsers.add_parser("test", help="Run the test suite (wraps pytest tests/)")
@@ -854,3 +854,189 @@ Finance, formats into Lean's zip/CSV convention, writes it to the correct
854
854
  rejected by argparse (V3 not implemented yet). Test artifacts removed
855
855
  from `config.json`/`data/` after verification — not part of the real
856
856
  20-asset universe.
857
+
858
+ ## Trade-frequency tuning — statistical/diagnostic backtest mode
859
+
860
+ A real 3-year, 20-asset backtest this session produced only 12 filled
861
+ trades: 5 entries, 1 opportunistic exit, then a 5-symbol mass liquidation
862
+ on 2020-03-23 that froze the algorithm for the remaining 374 days of the
863
+ window. Two parallel research passes tracing the full per-bar decision
864
+ pipeline found the suppression compounds across several independent gates,
865
+ plus two structural traps where a gate that fires once effectively never
866
+ clears for the rest of a run — the portfolio sits flat in cash and its own
867
+ drawdown-from-peak calculation can never recover without trading.
868
+
869
+ - **New opt-in flag, `phase_v2.backtest.bypass_safety_gates`** (default
870
+ `false`) — deliberately a standalone key, not a repurposing of
871
+ `aq trade-lock`'s existing `--on`/`--off`/`--auto` override (which keeps
872
+ its separately-documented meaning completely unchanged in every runtime
873
+ mode). New pure helper
874
+ `risk_controls.py::is_backtest_safety_bypass_active(runtime_mode, bypass_flag)`
875
+ returns `True` only when `runtime_mode == "backtest"` **and** the flag is
876
+ explicitly `true` — any non-backtest mode always returns `False`
877
+ regardless of the flag, so live/paper safety behavior is completely
878
+ untouched by this change.
879
+ - **Bypasses the sticky total-drawdown lock** (`main.py::_refresh_risk_state()`,
880
+ the `trade_lock_reason != "total_drawdown_limit_breached"` exclusion) and
881
+ **the regime detector's `risk_off` drawdown branch**
882
+ (`main.py::_build_regime_payload()`, passes `float("inf")` instead of
883
+ `regime_risk_off_drawdown_threshold` when the bypass is active) — the
884
+ regime override was found to be an equally significant, earlier-firing
885
+ (8% vs. the lock's 12%) version of the same structural trap, independent
886
+ of the lock. Only these two specific mechanisms are affected; the
887
+ bearish-trend+high-vol and composite risk-score branches of regime
888
+ classification, and every liquidity/topology/cooldown/exposure gate,
889
+ stay fully active either way.
890
+ - **Explicitly scoped to statistical/model-quality evaluation, not a
891
+ live-representative equity curve** — in live/paper mode both gates are
892
+ real, designed behavior that would have actually frozen trading on
893
+ 2020-03-23. This is a deliberate, accepted tradeoff for generating enough
894
+ trade volume to get meaningful backtest metrics and exercise
895
+ performance-trigger thresholds (`trade_count_interval=100`,
896
+ `validation_gate.min_trade_count=30`) that never fire at ~12 trades —
897
+ not a claim about deployable behavior.
898
+ - **Config-only threshold loosening** (confirmed test-safe: no existing
899
+ test loads the real `config.json` for any of these keys):
900
+ `phase6.risk.min_confidence_to_trade` 0.12→0.05,
901
+ `phase5.backtest.buy_threshold_offset`/`sell_threshold_offset` 0.08→0.04
902
+ each, `phase6.risk.trade_cooldown_bars` 3→1,
903
+ `phase_v2.liquidity.thin_participation_threshold` 0.002→0.01,
904
+ `phase_v2.liquidity.blocked_participation_threshold` 0.05→0.10,
905
+ `phase9.portfolio.max_active_positions` 5→10,
906
+ `phase9.portfolio.max_crypto_exposure` 0.25→0.35.
907
+ - **`phase9.asset_quality.min_training_rows` 100→50**: unlocked ETHUSD,
908
+ XRPUSD, and ADAUSD as `training_eligible`/`trading_eligible` (they failed
909
+ only this one threshold — 52-54 actual training rows, since these coins
910
+ weren't listed on Yahoo until deep into the 2014-2017 training window;
911
+ they already comfortably cleared `min_total_feature_rows` and
912
+ `min_backtest_rows`) — confirmed via `train.py --dataset-only`: 19 of 20
913
+ assets now `training_eligible`, only `AAA` remains observation-only.
914
+ `AAA` was deliberately excluded from this fix — its usable data starts
915
+ 2020-09-09, entirely after both the training and validation windows end,
916
+ so no threshold value can fix it; it would need real pre-2018 backfilled
917
+ history that doesn't exist for whatever instrument `AAA` actually is.
918
+ - **Deliberately out of scope:** real short-selling (confirmed
919
+ `phase5.backtest.strategy_mode` is read in exactly one place in the whole
920
+ repo, `train.py`'s report-metadata code, and is never branched on —
921
+ `main.py::_apply_signal`'s `sell` branch only ever calls `self.Liquidate()`,
922
+ there is no code path anywhere that opens a short position; enabling real
923
+ shorting would be a materially bigger, riskier change than everything
924
+ above and isn't needed to raise trade count via long/flat cycling alone).
925
+ Also left alone: the topology "elevated" volatility threshold
926
+ (`topology/market_topology.py`'s `ELEVATED_VOLATILITY_THRESHOLD = 0.45`,
927
+ hardcoded, not in `config.json`) — a smaller contributor than the two
928
+ structural traps, left as a secondary lever for later if needed.
929
+ - New tests: `tests/test_risk_controls.py` (4 new cases for
930
+ `is_backtest_safety_bypass_active`).
931
+ - Stopping point: the real Lean backtest with `bypass_safety_gates: true`
932
+ to confirm the actual resulting trade count against the ~200 target was
933
+ left for the user to run manually, per this session's established
934
+ preference — the exact starting-point threshold values above may need
935
+ one iteration based on that real count.
936
+
937
+ ## Real learned gating weights + learned topology wired into position sizing
938
+
939
+ Closes two gaps this project's own V3 completeness assessment had flagged
940
+ as deferred, not rejected: `moe/gating.py`'s gating network was still
941
+ hand-tuned arithmetic dressed as a decision layer, and the learned
942
+ probabilistic topology overlay (V2-17.5) computed real per-symbol
943
+ confidence/uncertainty every bar but never reached an actual trade
944
+ decision — only the dashboard and the offline retrain-trigger pipeline.
945
+
946
+ **Learned topology → position sizing** (`risk/position_sizing.py`): new
947
+ pure function `topology_sizing_multiplier(topology_source,
948
+ topology_confidence, topology_disagreement, min_topology_multiplier=0.5,
949
+ max_topology_multiplier=1.0)` — a strict no-op (`1.0`) unless
950
+ `topology_source == "learned"`, otherwise a bounded, continuous,
951
+ **shrink-only** multiplier (`min + (max-min) * confidence * (1-disagreement)`,
952
+ never above `1.0`). Composes into the existing `volatility_multiplier ×
953
+ confidence_multiplier` chain in `build_dynamic_position_sizing()` as a
954
+ third factor — this changes only *how large* an already-approved trade is,
955
+ never *whether* it happens, so it never touches the analyzer's
956
+ `trade`/`simulate`/`observe`/`reduce_risk` decision itself (see
957
+ `analyzer/README.md`'s documented reason that path stays deterministic).
958
+ Wired through `main.py::_build_dynamic_sizing_payload()`. New config keys:
959
+ `phase_v2.dynamic_risk.topology_sizing_enabled` (default `true`),
960
+ `min_topology_multiplier` (`0.5`), `max_topology_multiplier` (`1.0`).
961
+
962
+ **Real learned gating weights** (`moe/gating.py`): additive, optional,
963
+ always-falls-back — the existing hand-written
964
+ quality-multiplier×performance-score×regime-alignment blend is still
965
+ computed first and unchanged; a trained model, if present, only
966
+ *overrides* the final probability. New `GATING_MODEL_FEATURE_KEYS`
967
+ (26-dim: each expert's probability/quality/performance/regime-alignment ×4,
968
+ baseline probability, one-hot trend/volatility/risk regime — already
969
+ bounded `[0,1]`/one-hot, so no scaler needed) and
970
+ `build_gating_model_features()`. `build_gating_decision()` gains optional
971
+ `gating_model`/`gating_feature_schema` params; on success `decision_source`
972
+ becomes `"learned_gating"` (new value, confirmed inert everywhere else in
973
+ the repo); any failure/missing model silently falls back to the hardcoded
974
+ blend, mirroring `topology/learned_topology.py`'s per-node fallback
975
+ isolation. Wired via new `main.py::_load_gating_model()`, gated by
976
+ `phase_v2.gating_network.learned_model_enabled` (default `true`).
977
+
978
+ **New offline trainer, `train_gating.py`** (sibling of `train_topology.py`,
979
+ same never-runs-in-Lean, exits-0-not-1-on-insufficient-data contract).
980
+ Trains the blend on the dataset's `validation` split (avoids
981
+ stacking-circularity: `train` already fit baseline+experts, `validation`
982
+ is the right held-out-from-fitting size to become this model's *own*
983
+ training data) replayed through the exported baseline+expert models via
984
+ the existing `inference/exported_model.py::run_exported_model()`
985
+ interpreter — zero new inference code. Evaluates once, at the end, on the
986
+ `backtest` split (never touched by any fitting anywhere in the pipeline).
987
+ Model is a small `AetherNet(26 → [16] → 1)`, deliberately restricted to
988
+ `relu`/`layernorm` (never `gelu`/`silu`/`batchnorm1d`, which
989
+ `run_exported_model()` cannot interpret). Regime reconstruction uses
990
+ `portfolio_drawdown=0.0`/`average_correlation=0.0` (runtime-only state not
991
+ recoverable offline — an honest, documented simplification; the two most-
992
+ used regime keys, `trend_regime`/`volatility_regime`, are unaffected).
993
+ Smoke-tested end-to-end against the real dataset (1,304 validation rows,
994
+ 16,184 backtest rows) — writes a valid, runtime-interpretable model in
995
+ ~90s. New config block `phase_v2.retraining.gating_training`.
996
+
997
+ **Retraining pipeline wiring**: `retraining/artifacts.py` gained
998
+ `OPTIONAL_GATING_FILES` (3 files, same best-effort contract as
999
+ `OPTIONAL_TOPOLOGY_FILES` — never required, never blocks candidate
1000
+ promotion) plus `check_gating_artifacts()`; `retraining/orchestrator.py`
1001
+ gained `train_gating()` (mirrors `train_topology()` line-for-line) plus a
1002
+ `train_gating` CLI subparser; `retraining/worker.py` calls it right after
1003
+ `train_topology()` in `run_once()`. `config.json`'s
1004
+ `promotion.active_artifact_files` extended with the 3 new files.
1005
+
1006
+ **New `aq train --gating-only` flag**: since `train_gating.py` always
1007
+ writes to `ml/versions/<id>/` (the versioned-candidate convention every
1008
+ trainer here uses), this generates a throwaway version-id, runs the
1009
+ trainer, then copies the 3 resulting artifacts straight into active `ml/`
1010
+ — the same manual promotion-simulation step already used to verify this
1011
+ trainer, mirroring how `train.py --experts-only` already writes directly
1012
+ to active paths without a promotion gate.
1013
+
1014
+ **Neural-network webui tab now shows the gating network too**
1015
+ (`monitoring/neural_network_state.py`): it was previously in the page's
1016
+ hardcoded `excluded` list with a "deterministic, no weight matrix" reason
1017
+ that this change makes stale. Now reads `ml/gating_model.json` exactly
1018
+ like the baseline/experts (same optional, degrades-to-`not_trained`
1019
+ contract) and reports a `"learned"` quality-status badge once a model
1020
+ exists. `webui/src/components/neuralnet/NeuralNetworkScene3D.tsx`'s
1021
+ previously-hardcoded 5-network render order gained `'gating'` (violet),
1022
+ since the 3D scene only ever drew networks named in that list regardless
1023
+ of what the backend returned.
1024
+
1025
+ - New tests: `tests/test_position_sizing.py` (+7), `tests/test_gating_network.py`
1026
+ (+5), `tests/test_train_gating.py` (8, pure-function style mirroring
1027
+ `tests/test_train_topology.py`), `tests/test_retraining_artifacts.py`/
1028
+ `test_retraining_orchestrator.py`/`test_retraining_worker.py` (extended
1029
+ for the new gating stage), `tests/test_aq_cli.py` (+3 for
1030
+ `--gating-only`), `tests/test_neural_network_state.py` (+2 new, 2
1031
+ rewritten to match gating no longer being excluded). Full suite: 581
1032
+ passed.
1033
+ - Docs: `development/Problems.md` #14 (Redis push in backtest mode) marked
1034
+ resolved — the project owner confirmed no downstream process reads
1035
+ backtest-mode experience events from Postgres, so the open question this
1036
+ entry tracked is answered; `experience/redis_queue.py::push()` itself is
1037
+ deliberately left unchanged, since performance was never the blocker.
1038
+ - Stopping point: `python train_gating.py` / `aq train --gating-only` was
1039
+ smoke-tested against the real dataset but its resulting model was **not**
1040
+ installed into active `ml/` or promoted through the retraining pipeline —
1041
+ per this session's established preference, the user runs the real
1042
+ training/backtest themselves.
@@ -440,7 +440,7 @@ the cache silently breaking hot-reload.
440
440
  ---
441
441
 
442
442
  ### 14. Redis push in backtest mode — deliberately left unoptimized
443
- **Severity:** n/a · **Status:** 🟠 `open` (deferred by design)
443
+ **Severity:** n/a · **Status:** 🟢 `resolved` (confirmed no-op, no code change needed)
444
444
 
445
445
  `experience/redis_queue.py::push()` does a synchronous, blocking `XADD` per
446
446
  symbol per bar, plus one more at every session rollover (which, per #13, is
@@ -450,23 +450,28 @@ have `self.runtime_mode`/`self._experience_mode` available — and would save
450
450
  real per-bar network I/O during backtests, which never need live
451
451
  experience-stream delivery.
452
452
 
453
- **Why this stays open on purpose:** `development/v2_architecture.md`'s
453
+ **Why this stayed open initially:** `development/v2_architecture.md`'s
454
454
  Redis Experience Queue section documents `"backtest"` as one of four
455
455
  normal, expected mode values flowing into Redis, and
456
456
  `tests/test_experience_queue.py`'s default fixture treats `mode="backtest"`
457
- as the canonical case, not an exclusion. There is at least one plausible
457
+ as the canonical case, not an exclusion. There was at least one plausible
458
458
  downstream dependency on backtest-mode Redis events reaching Postgres via
459
459
  `experience-worker` — debugging via the observation dashboard against a
460
460
  backtest run, or later analysis of a backtest's persisted events — that
461
- has not been confirmed one way or the other. Skipping the push would
462
- contradict documented, tested behavior on the strength of an unconfirmed
463
- assumption, so it is recorded here instead of implemented.
464
-
465
- **Next step, when revisited:** confirm whether anything actually reads
466
- backtest-mode experience events out of Postgres after the fact; if nothing
467
- does, gate `push()` on `runtime_mode != "backtest"` and update
468
- `v2_architecture.md`'s Redis Experience Queue section and
469
- `test_experience_queue.py`'s default fixture accordingly.
461
+ had not been confirmed one way or the other. Skipping the push would have
462
+ contradicted documented, tested behavior on the strength of an unconfirmed
463
+ assumption.
464
+
465
+ **Resolution:** the project owner personally confirmed no downstream
466
+ process reads backtest-mode experience events out of Postgres. Since the
467
+ open question this entry was tracking is now answered, it's marked
468
+ resolved on that basis alone — `experience/redis_queue.py::push()` itself
469
+ is intentionally left unchanged (still pushes in backtest mode), since the
470
+ performance cost was never the blocker, only the unconfirmed dependency
471
+ was. A future optimization pass may still gate `push()` on
472
+ `runtime_mode != "backtest"` if the per-bar I/O ever becomes a real
473
+ bottleneck, but that is now a pure performance nice-to-have, not a
474
+ correctness fix blocked on missing information.
470
475
 
471
476
  ---
472
477
 
@@ -571,3 +576,89 @@ runs: the first (cold cache) still showed the "building the font cache"
571
576
  message and took ~82 seconds just to import; the second (warm cache) showed
572
577
  no such message and imported in ~58 seconds, with zero isolator timeout.
573
578
  `.matplotlib_cache/` added to `.gitignore` (generated cache, not committed).
579
+
580
+ ---
581
+
582
+ ### 18. Two structural "never recovers" traps suppressed real backtest trade count to 12 over 3 years
583
+ **Severity:** 5/10 · **Status:** 🟢 `addressed` (opt-in statistical bypass, default behavior unchanged)
584
+
585
+ Found investigating why a real 3-year, 20-asset backtest produced only 12
586
+ filled trades: a 5-symbol mass liquidation on 2020-03-23 (portfolio total
587
+ drawdown crossed `phase6.risk.max_total_drawdown_pct`, 12%) froze the
588
+ algorithm for the remaining 374 days of the window — no trades at all. The
589
+ sticky lock (`main.py::_refresh_risk_state()`'s daily auto-clear
590
+ deliberately excludes `"total_drawdown_limit_breached"`, by design, for
591
+ live capital preservation) is only half the story: `peak_equity` is a
592
+ running max that never decreases, so once liquidated to flat cash, the
593
+ drawdown-from-peak percentage can never recover on its own — the lock
594
+ cannot ever clear again regardless of the sticky-exclusion logic, purely
595
+ because the underlying number stays permanently breached. A second,
596
+ independent, earlier-firing (8% vs. 12%) version of the identical trap was
597
+ found in `regime/market_regime.py::classify_risk_regime()`'s `risk_off`
598
+ drawdown branch (`phase_v2.regime_detection.risk_off_drawdown_threshold`),
599
+ fed from the same portfolio-wide drawdown number — this alone would
600
+ suppress the whole universe's signals even if the 12% lock were fixed in
601
+ isolation.
602
+
603
+ **Fix:** new opt-in flag `phase_v2.backtest.bypass_safety_gates` (default
604
+ `false`) and pure helper
605
+ `risk_controls.py::is_backtest_safety_bypass_active(runtime_mode, bypass_flag)`
606
+ — `True` only when `runtime_mode == "backtest"` and the flag is explicitly
607
+ `true`; any non-backtest mode always returns `False` regardless of the
608
+ flag. When active, bypasses only these two specific mechanisms (the sticky
609
+ lock's exclusion, and the regime override's drawdown branch specifically —
610
+ passing `float("inf")` in place of the configured threshold); every other
611
+ gate (liquidity, topology, cooldown, exposure caps, the bearish-trend+
612
+ high-vol and composite-score regime branches) stays fully active. Live/paper
613
+ mode, and a backtest with the flag left at its default `false`, are
614
+ completely unaffected.
615
+
616
+ **Deliberately not wired into `aq trade-lock`:** `--on`/`--off`/`--auto`
617
+ already has a separately-documented meaning (`--auto` = "return to fully
618
+ automatic \[original, safety-preserving\] behavior") that this must not
619
+ collide with or repurpose — a dedicated config key keeps both mechanisms
620
+ independently meaningful in every runtime mode.
621
+
622
+ **Explicitly not a claim about live-representative behavior:** in live/paper
623
+ mode both gates are real, designed behavior that would have actually frozen
624
+ trading on 2020-03-23. This flag is scoped to statistical/model-quality
625
+ backtesting only (enough trade volume for meaningful metrics and to
626
+ exercise `performance_triggers.trade_count_interval=100`/
627
+ `retraining.validation_gate.min_trade_count=30`, neither of which ever
628
+ fires at ~12 trades) — never to be read as "what would happen if deployed."
629
+
630
+ ---
631
+
632
+ ### 19. Neural-network webui tab's gating exclusion went stale the moment gating became learnable
633
+ **Severity:** 2/10 · **Status:** 🟢 `fixed`
634
+
635
+ `monitoring/neural_network_state.py`'s `EXCLUDED_NON_NETWORKS` listed
636
+ `moe/gating.py`'s gating network with the reason "deterministic rule-based
637
+ combiner ... no learned weight matrix," and `/neural-network`'s 3D scene
638
+ (`webui/src/components/neuralnet/NeuralNetworkScene3D.tsx`) hardcoded its
639
+ render order to exactly 5 network names, silently dropping anything else
640
+ even if the backend did return it. Both became inaccurate the moment
641
+ `moe/gating.py` gained an optional learned model (this session, same
642
+ Phase E work as entry above) — the gating network now genuinely can have
643
+ a learned weight matrix (`ml/gating_model.json`), but the webui had no way
644
+ to show it even after one existed, and the "no learned weight matrix"
645
+ claim was simply wrong.
646
+
647
+ **Fix:** removed `gating_network` from `EXCLUDED_NON_NETWORKS` (only
648
+ `learned_topology`'s KMeans centroids remain excluded — genuinely not a
649
+ layered network); `build_neural_network_state()` now reads
650
+ `ml/gating_model.json` through the exact same generic
651
+ `_build_network_summary()` path already used for the baseline and the 4
652
+ experts, degrading to `status="not_trained"` when no gating model exists
653
+ yet (same graceful-optional contract as everything else in this module).
654
+ Added `'gating'` to `NeuralNetworkScene3D.tsx`'s `NETWORK_ORDER` array so
655
+ it actually renders in the 3D scene once returned by the backend.
656
+
657
+ **Why this matters beyond gating specifically:** `NETWORK_ORDER` is a
658
+ silent filter — any future network the backend starts reporting will not
659
+ appear in the 3D scene (though it will still appear in the stats panel's
660
+ list, which iterates the array directly) unless someone remembers to add
661
+ its name here too. Left as-is rather than making it dynamic, since the
662
+ 3D layout intentionally controls left-to-right ordering for readability;
663
+ noted here so the next network added to `build_neural_network_state()`
664
+ doesn't quietly repeat this gap.