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.
- {aether_quant-0.4.0 → aether_quant-0.5.0}/Dockerfile.retraining_worker +3 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/PKG-INFO +1 -1
- {aether_quant-0.4.0 → aether_quant-0.5.0}/README.md +48 -37
- {aether_quant-0.4.0 → aether_quant-0.5.0}/aether_quant.egg-info/PKG-INFO +1 -1
- {aether_quant-0.4.0 → aether_quant-0.5.0}/aether_quant.egg-info/SOURCES.txt +8 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/aether_quant.egg-info/scm_file_list.json +8 -0
- aether_quant-0.5.0/aether_quant.egg-info/scm_version.json +8 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/analyzer/README.md +11 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/aq_cli.py +41 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/development/Changelog.md +186 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/development/Problems.md +103 -12
- aether_quant-0.5.0/development/backtest_equity_chart.png +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/development/v2_architecture.md +20 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/main.py +68 -2
- aether_quant-0.5.0/ml/gating_feature_schema.json +30 -0
- aether_quant-0.5.0/ml/gating_model.json +570 -0
- aether_quant-0.5.0/ml/gating_training_metrics.json +190 -0
- aether_quant-0.5.0/ml/versions/gating-only-26218402-34d9-4a0a-add8-ee5d50dd0459/gating_feature_schema.json +30 -0
- aether_quant-0.5.0/ml/versions/gating-only-26218402-34d9-4a0a-add8-ee5d50dd0459/gating_model.json +570 -0
- aether_quant-0.5.0/ml/versions/gating-only-26218402-34d9-4a0a-add8-ee5d50dd0459/gating_training_metrics.json +190 -0
- aether_quant-0.5.0/moe/README.md +61 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/moe/__init__.py +4 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/moe/gating.py +101 -36
- {aether_quant-0.4.0 → aether_quant-0.5.0}/monitoring/README.md +1 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/monitoring/neural_network_state.py +41 -22
- {aether_quant-0.4.0 → aether_quant-0.5.0}/retraining/__init__.py +4 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/retraining/artifacts.py +23 -6
- {aether_quant-0.4.0 → aether_quant-0.5.0}/retraining/orchestrator.py +54 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/retraining/worker.py +6 -0
- aether_quant-0.5.0/risk/README.md +50 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/risk/position_sizing.py +52 -1
- {aether_quant-0.4.0 → aether_quant-0.5.0}/risk_controls.py +14 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_aq_cli.py +50 -0
- aether_quant-0.5.0/tests/test_gating_network.py +235 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_neural_network_state.py +34 -5
- aether_quant-0.5.0/tests/test_position_sizing.py +123 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_retraining_artifacts.py +66 -1
- {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_retraining_orchestrator.py +49 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_retraining_worker.py +20 -12
- {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_risk_controls.py +19 -0
- aether_quant-0.5.0/tests/test_train_gating.py +179 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/topology/README.md +7 -0
- aether_quant-0.5.0/train_gating.py +436 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/README.md +13 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/components/neuralnet/NeuralNetworkScene3D.tsx +5 -3
- {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/types/state.ts +2 -2
- aether_quant-0.4.0/aether_quant.egg-info/scm_version.json +0 -8
- aether_quant-0.4.0/development/backtest_equity_chart.png +0 -0
- aether_quant-0.4.0/moe/README.md +0 -19
- aether_quant-0.4.0/risk/README.md +0 -19
- aether_quant-0.4.0/tests/test_gating_network.py +0 -97
- aether_quant-0.4.0/tests/test_position_sizing.py +0 -55
- {aether_quant-0.4.0 → aether_quant-0.5.0}/.dockerignore +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/.env.compose.example +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/.env.live.example +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/.gitattributes +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/.github/workflows/ci.yml +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/.github/workflows/release.yml +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/.gitignore +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/Dockerfile +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/Dockerfile.workers +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/aether_quant.egg-info/dependency_links.txt +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/aether_quant.egg-info/entry_points.txt +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/aether_quant.egg-info/top_level.txt +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/analyzer/__init__.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/analyzer/market_analyzer.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/backtests/.gitkeep +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/data/alternative/estimize/consensus/aapl/.keep +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/data/alternative/estimize/estimate/.keep +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/data/alternative/estimize/release/.keep +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/data/alternative/sec/aapl/.keep +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/data/alternative/trading-economics/.keep +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/data/alternative/trading-economics/calendar/.keep +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/data/alternative/trading-economics/earnings/.keep +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/data/alternative/trading-economics/indicator/.keep +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/data_pipeline/README.md +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/data_pipeline/__init__.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/data_pipeline/fetch.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/data_pipeline/v2_manifest.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/data_pipeline/yfinance_backfill.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/development/README.md +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/development/infrastructure.md +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/development/logo.png +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/docker-compose.yml +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/execution/README.md +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/execution/__init__.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/execution/config_cache.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/execution/live_credentials.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/execution/live_credentials_io.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/execution/order_gate.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/execution/paper_readiness.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/execution/paper_readiness_io.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/execution/paper_readiness_report.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/execution/runtime_config_io.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/experience/README.md +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/experience/__init__.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/experience/observation_metrics.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/experience/postgres_worker.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/experience/redis_queue.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/experience/simulated_portfolio.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/experts/README.md +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/experts/__init__.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/experts/expert_datasets.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/generate_backtest_report.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/inference/README.md +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/inference/__init__.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/inference/exported_model.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/liquidity/README.md +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/liquidity/__init__.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/liquidity/market_liquidity.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/ml/README.md +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/monitoring/__init__.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/monitoring/api_server.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/notifications/README.md +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/notifications/__init__.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/notifications/postgres_telegram.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/notifications/telegram_alerts.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/notifications/telegram_client.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/notifications/telegram_worker.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/performance/README.md +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/performance/__init__.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/performance/postgres_triggers.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/performance/trigger_worker.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/performance/triggers.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/pyproject.toml +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/regime/README.md +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/regime/__init__.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/regime/market_regime.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/requirements/README.md +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/requirements/requirements-dev.txt +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/requirements/requirements-retraining-worker.txt +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/requirements/requirements-runtime.txt +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/requirements/requirements-workers.txt +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/requirements/requirements.txt +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/retraining/README.md +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/retraining/backtest_gate.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/retraining/lean_backtest.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/retraining/planning.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/retraining/postgres_registry.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/retraining/status_export.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/retraining/validation_gate.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/retraining/vault_client.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/retraining/vault_commands.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/risk/__init__.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/risk/manual_override.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/setup.cfg +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/README.md +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_backtest_gate.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_config_cache.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_experience_queue.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_expert_datasets.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_expert_models.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_exported_model.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_fetch.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_generate_backtest_report.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_lean_backtest.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_lean_backtest_ml_coverage.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_learned_topology.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_live_credentials.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_live_credentials_io.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_manual_override.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_market_analyzer.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_market_liquidity.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_market_regime.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_market_topology.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_observation_metrics.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_order_gate.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_paper_readiness.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_paper_readiness_io.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_paper_readiness_report.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_postgres_telegram.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_postgres_triggers.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_postgres_worker.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_retraining_planning.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_retraining_postgres_registry.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_runtime_config_io.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_simulated_portfolio.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_status_export.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_telegram_alerts.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_telegram_client.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_telegram_worker.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_train_pipeline.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_train_topology.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_trigger_worker.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_triggers.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_v2_pipeline_manifest.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_validation_gate.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_vault_client.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_vault_commands.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/tests/test_yfinance_backfill.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/topology/__init__.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/topology/learned_topology.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/topology/market_topology.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/train.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/train_topology.py +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/visualization/README.md +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/.gitignore +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/.oxlintrc.json +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/index.html +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/overview_v2_12.png +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/package-lock.json +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/package.json +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/public/favicon.svg +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/public/icons.svg +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/risk_v2_12.png +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/App.tsx +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/api/client.ts +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/api/hooks.ts +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/components/heatmap/AssetHeatmap.tsx +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/components/layout/AppShell.tsx +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/components/layout/Panel.tsx +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/components/monitoring/CountTable.tsx +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/components/monitoring/MonitoringFeeds.tsx +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/components/monitoring/ObservationPanel.tsx +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/components/monitoring/PaperReadinessPanel.tsx +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/components/monitoring/PerformanceTriggersPanel.tsx +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/components/monitoring/RawStateViewer.tsx +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/components/monitoring/RetrainingStatusPanel.tsx +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/components/neuralnet/NeuralNetworkStatsPanel.tsx +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/components/risk/AssetSizingTable.tsx +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/components/risk/LiquidityTable.tsx +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/components/risk/RiskBar.tsx +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/components/risk/RiskCore.tsx +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/components/risk/StrategyRiskCards.tsx +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/components/scene3d/Scene3D.tsx +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/components/scorecards/Scorecards.tsx +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/components/signals/Badge.tsx +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/components/signals/PositionsList.tsx +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/components/signals/SignalBoard.tsx +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/components/topology/ClusterList.tsx +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/components/topology/TopologyLearningPanel.tsx +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/components/topology/TopologyScene3D.tsx +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/components/tracing/AssetPerformancePanel.tsx +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/components/tracing/BacktestEquityPanel.tsx +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/components/tracing/DivergingBarChart.tsx +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/components/tracing/LineChart.tsx +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/components/tracing/MetricsSnapshotPanel.tsx +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/components/tracing/ObservationEquityPanel.tsx +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/index.css +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/lib/downsample.ts +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/lib/format.ts +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/main.tsx +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/pages/NeuralNetworkPage.tsx +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/pages/Overview.tsx +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/pages/RiskPage.tsx +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/pages/TopologyPage.tsx +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/pages/TracingPage.tsx +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/src/types/tracing.ts +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/tsconfig.app.json +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/tsconfig.json +0 -0
- {aether_quant-0.4.0 → aether_quant-0.5.0}/webui/tsconfig.node.json +0 -0
- {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"]
|
|
@@ -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-
|
|
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 |
|
|
395
|
-
| Sharpe Ratio | -0.
|
|
396
|
-
| Net Profit | -
|
|
397
|
-
| Compounding Annual Return | -2.
|
|
398
|
-
| Drawdown | 12.
|
|
399
|
-
| Total Orders |
|
|
400
|
-
| Win Rate |
|
|
401
|
-
| Last updated | 2026-07-
|
|
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 |
|
|
411
|
-
| Average Win | 0% |
|
|
412
|
-
| Average Loss | -1.
|
|
413
|
-
| Compounding Annual Return | -2.
|
|
414
|
-
| Drawdown | 12.
|
|
415
|
-
| Expectancy | -
|
|
416
|
-
| Start Equity | 100000 |
|
|
417
|
-
| End Equity |
|
|
418
|
-
| Net Profit | -
|
|
419
|
-
| Sharpe Ratio | -0.
|
|
420
|
-
| Sortino Ratio | -0.
|
|
421
|
-
| Probabilistic Sharpe Ratio | 0.
|
|
422
|
-
| Loss Rate |
|
|
423
|
-
| Win Rate |
|
|
424
|
-
| Profit-Loss Ratio | 0 |
|
|
425
|
-
| Alpha | -0.
|
|
426
|
-
| Beta | 0.
|
|
427
|
-
| Annual Standard Deviation | 0.
|
|
428
|
-
| Annual Variance | 0.
|
|
429
|
-
| Information Ratio | -
|
|
430
|
-
| Tracking Error | 0.
|
|
431
|
-
| Treynor Ratio | -0.
|
|
432
|
-
| Total Fees | $
|
|
433
|
-
| Estimated Strategy Capacity | $
|
|
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.
|
|
436
|
-
| Drawdown Recovery |
|
|
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`.
|
|
@@ -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
|
]
|
|
@@ -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:**
|
|
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
|
|
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
|
|
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
|
-
|
|
462
|
-
|
|
463
|
-
assumption
|
|
464
|
-
|
|
465
|
-
**
|
|
466
|
-
backtest-mode experience events out of Postgres
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
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.
|
|
Binary file
|