aether-quant 0.5.0__tar.gz → 0.6.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.5.0 → aether_quant-0.6.0}/.gitignore +0 -2
- {aether_quant-0.5.0 → aether_quant-0.6.0}/PKG-INFO +1 -1
- {aether_quant-0.5.0 → aether_quant-0.6.0}/README.md +18 -2
- {aether_quant-0.5.0 → aether_quant-0.6.0}/aether_quant.egg-info/PKG-INFO +1 -1
- {aether_quant-0.5.0 → aether_quant-0.6.0}/aether_quant.egg-info/SOURCES.txt +2 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/aether_quant.egg-info/scm_file_list.json +2 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/aether_quant.egg-info/scm_version.json +2 -2
- {aether_quant-0.5.0 → aether_quant-0.6.0}/analyzer/README.md +26 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/analyzer/__init__.py +2 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/analyzer/market_analyzer.py +81 -2
- {aether_quant-0.5.0 → aether_quant-0.6.0}/aq_cli.py +151 -1
- aether_quant-0.6.0/config.json +543 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/development/Changelog.md +76 -0
- aether_quant-0.6.0/lean.json +649 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/main.py +2 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/tests/test_aq_cli.py +234 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/tests/test_market_analyzer.py +118 -1
- {aether_quant-0.5.0 → aether_quant-0.6.0}/.dockerignore +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/.env.compose.example +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/.env.live.example +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/.gitattributes +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/.github/workflows/ci.yml +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/.github/workflows/release.yml +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/Dockerfile +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/Dockerfile.retraining_worker +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/Dockerfile.workers +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/aether_quant.egg-info/dependency_links.txt +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/aether_quant.egg-info/entry_points.txt +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/aether_quant.egg-info/top_level.txt +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/backtests/.gitkeep +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/data/alternative/estimize/consensus/aapl/.keep +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/data/alternative/estimize/estimate/.keep +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/data/alternative/estimize/release/.keep +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/data/alternative/sec/aapl/.keep +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/data/alternative/trading-economics/.keep +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/data/alternative/trading-economics/calendar/.keep +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/data/alternative/trading-economics/earnings/.keep +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/data/alternative/trading-economics/indicator/.keep +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/data_pipeline/README.md +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/data_pipeline/__init__.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/data_pipeline/fetch.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/data_pipeline/v2_manifest.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/data_pipeline/yfinance_backfill.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/development/Problems.md +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/development/README.md +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/development/backtest_equity_chart.png +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/development/infrastructure.md +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/development/logo.png +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/development/v2_architecture.md +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/docker-compose.yml +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/execution/README.md +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/execution/__init__.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/execution/config_cache.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/execution/live_credentials.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/execution/live_credentials_io.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/execution/order_gate.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/execution/paper_readiness.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/execution/paper_readiness_io.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/execution/paper_readiness_report.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/execution/runtime_config_io.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/experience/README.md +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/experience/__init__.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/experience/observation_metrics.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/experience/postgres_worker.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/experience/redis_queue.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/experience/simulated_portfolio.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/experts/README.md +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/experts/__init__.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/experts/expert_datasets.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/generate_backtest_report.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/inference/README.md +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/inference/__init__.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/inference/exported_model.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/liquidity/README.md +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/liquidity/__init__.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/liquidity/market_liquidity.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/ml/README.md +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/ml/gating_feature_schema.json +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/ml/gating_model.json +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/ml/gating_training_metrics.json +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/ml/versions/gating-only-26218402-34d9-4a0a-add8-ee5d50dd0459/gating_feature_schema.json +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/ml/versions/gating-only-26218402-34d9-4a0a-add8-ee5d50dd0459/gating_model.json +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/ml/versions/gating-only-26218402-34d9-4a0a-add8-ee5d50dd0459/gating_training_metrics.json +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/moe/README.md +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/moe/__init__.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/moe/gating.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/monitoring/README.md +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/monitoring/__init__.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/monitoring/api_server.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/monitoring/neural_network_state.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/notifications/README.md +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/notifications/__init__.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/notifications/postgres_telegram.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/notifications/telegram_alerts.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/notifications/telegram_client.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/notifications/telegram_worker.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/performance/README.md +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/performance/__init__.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/performance/postgres_triggers.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/performance/trigger_worker.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/performance/triggers.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/pyproject.toml +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/regime/README.md +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/regime/__init__.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/regime/market_regime.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/requirements/README.md +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/requirements/requirements-dev.txt +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/requirements/requirements-retraining-worker.txt +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/requirements/requirements-runtime.txt +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/requirements/requirements-workers.txt +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/requirements/requirements.txt +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/retraining/README.md +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/retraining/__init__.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/retraining/artifacts.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/retraining/backtest_gate.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/retraining/lean_backtest.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/retraining/orchestrator.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/retraining/planning.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/retraining/postgres_registry.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/retraining/status_export.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/retraining/validation_gate.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/retraining/vault_client.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/retraining/vault_commands.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/retraining/worker.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/risk/README.md +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/risk/__init__.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/risk/manual_override.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/risk/position_sizing.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/risk_controls.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/setup.cfg +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/tests/README.md +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/tests/test_backtest_gate.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/tests/test_config_cache.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/tests/test_experience_queue.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/tests/test_expert_datasets.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/tests/test_expert_models.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/tests/test_exported_model.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/tests/test_fetch.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/tests/test_gating_network.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/tests/test_generate_backtest_report.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/tests/test_lean_backtest.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/tests/test_lean_backtest_ml_coverage.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/tests/test_learned_topology.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/tests/test_live_credentials.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/tests/test_live_credentials_io.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/tests/test_manual_override.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/tests/test_market_liquidity.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/tests/test_market_regime.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/tests/test_market_topology.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/tests/test_neural_network_state.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/tests/test_observation_metrics.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/tests/test_order_gate.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/tests/test_paper_readiness.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/tests/test_paper_readiness_io.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/tests/test_paper_readiness_report.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/tests/test_position_sizing.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/tests/test_postgres_telegram.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/tests/test_postgres_triggers.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/tests/test_postgres_worker.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/tests/test_retraining_artifacts.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/tests/test_retraining_orchestrator.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/tests/test_retraining_planning.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/tests/test_retraining_postgres_registry.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/tests/test_retraining_worker.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/tests/test_risk_controls.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/tests/test_runtime_config_io.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/tests/test_simulated_portfolio.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/tests/test_status_export.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/tests/test_telegram_alerts.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/tests/test_telegram_client.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/tests/test_telegram_worker.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/tests/test_train_gating.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/tests/test_train_pipeline.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/tests/test_train_topology.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/tests/test_trigger_worker.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/tests/test_triggers.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/tests/test_v2_pipeline_manifest.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/tests/test_validation_gate.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/tests/test_vault_client.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/tests/test_vault_commands.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/tests/test_yfinance_backfill.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/topology/README.md +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/topology/__init__.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/topology/learned_topology.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/topology/market_topology.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/train.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/train_gating.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/train_topology.py +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/visualization/README.md +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/webui/.gitignore +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/webui/.oxlintrc.json +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/webui/README.md +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/webui/index.html +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/webui/overview_v2_12.png +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/webui/package-lock.json +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/webui/package.json +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/webui/public/favicon.svg +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/webui/public/icons.svg +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/webui/risk_v2_12.png +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/webui/src/App.tsx +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/webui/src/api/client.ts +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/webui/src/api/hooks.ts +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/webui/src/components/heatmap/AssetHeatmap.tsx +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/webui/src/components/layout/AppShell.tsx +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/webui/src/components/layout/Panel.tsx +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/webui/src/components/monitoring/CountTable.tsx +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/webui/src/components/monitoring/MonitoringFeeds.tsx +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/webui/src/components/monitoring/ObservationPanel.tsx +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/webui/src/components/monitoring/PaperReadinessPanel.tsx +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/webui/src/components/monitoring/PerformanceTriggersPanel.tsx +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/webui/src/components/monitoring/RawStateViewer.tsx +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/webui/src/components/monitoring/RetrainingStatusPanel.tsx +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/webui/src/components/neuralnet/NeuralNetworkScene3D.tsx +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/webui/src/components/neuralnet/NeuralNetworkStatsPanel.tsx +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/webui/src/components/risk/AssetSizingTable.tsx +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/webui/src/components/risk/LiquidityTable.tsx +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/webui/src/components/risk/RiskBar.tsx +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/webui/src/components/risk/RiskCore.tsx +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/webui/src/components/risk/StrategyRiskCards.tsx +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/webui/src/components/scene3d/Scene3D.tsx +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/webui/src/components/scorecards/Scorecards.tsx +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/webui/src/components/signals/Badge.tsx +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/webui/src/components/signals/PositionsList.tsx +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/webui/src/components/signals/SignalBoard.tsx +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/webui/src/components/topology/ClusterList.tsx +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/webui/src/components/topology/TopologyLearningPanel.tsx +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/webui/src/components/topology/TopologyScene3D.tsx +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/webui/src/components/tracing/AssetPerformancePanel.tsx +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/webui/src/components/tracing/BacktestEquityPanel.tsx +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/webui/src/components/tracing/DivergingBarChart.tsx +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/webui/src/components/tracing/LineChart.tsx +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/webui/src/components/tracing/MetricsSnapshotPanel.tsx +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/webui/src/components/tracing/ObservationEquityPanel.tsx +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/webui/src/index.css +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/webui/src/lib/downsample.ts +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/webui/src/lib/format.ts +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/webui/src/main.tsx +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/webui/src/pages/NeuralNetworkPage.tsx +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/webui/src/pages/Overview.tsx +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/webui/src/pages/RiskPage.tsx +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/webui/src/pages/TopologyPage.tsx +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/webui/src/pages/TracingPage.tsx +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/webui/src/types/state.ts +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/webui/src/types/tracing.ts +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/webui/tsconfig.app.json +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/webui/tsconfig.json +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/webui/tsconfig.node.json +0 -0
- {aether_quant-0.5.0 → aether_quant-0.6.0}/webui/vite.config.ts +0 -0
|
@@ -10,11 +10,23 @@
|
|
|
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-611%2F611%20passing-brightgreen?style=flat-square&labelColor=1A1A1A" alt="611 of 611 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>
|
|
17
17
|
|
|
18
|
+
<p align="center">
|
|
19
|
+
<img src="https://img.shields.io/badge/PyTorch-4B5563?style=flat-square&labelColor=1A1A1A&logo=pytorch&logoColor=white" alt="PyTorch">
|
|
20
|
+
<img src="https://img.shields.io/badge/scikit--learn-4B5563?style=flat-square&labelColor=1A1A1A&logo=scikitlearn&logoColor=white" alt="scikit-learn">
|
|
21
|
+
<img src="https://img.shields.io/badge/QuantConnect%20Lean-4B5563?style=flat-square&labelColor=1A1A1A" alt="QuantConnect Lean">
|
|
22
|
+
<img src="https://img.shields.io/badge/FastAPI-4B5563?style=flat-square&labelColor=1A1A1A&logo=fastapi&logoColor=white" alt="FastAPI">
|
|
23
|
+
<img src="https://img.shields.io/badge/React-4B5563?style=flat-square&labelColor=1A1A1A&logo=react&logoColor=white" alt="React">
|
|
24
|
+
<img src="https://img.shields.io/badge/TypeScript-4B5563?style=flat-square&labelColor=1A1A1A&logo=typescript&logoColor=white" alt="TypeScript">
|
|
25
|
+
<img src="https://img.shields.io/badge/Redis-4B5563?style=flat-square&labelColor=1A1A1A&logo=redis&logoColor=white" alt="Redis">
|
|
26
|
+
<img src="https://img.shields.io/badge/PostgreSQL-4B5563?style=flat-square&labelColor=1A1A1A&logo=postgresql&logoColor=white" alt="PostgreSQL">
|
|
27
|
+
<img src="https://img.shields.io/badge/GitHub%20Actions-4B5563?style=flat-square&labelColor=1A1A1A&logo=githubactions&logoColor=white" alt="GitHub Actions">
|
|
28
|
+
</p>
|
|
29
|
+
|
|
18
30
|
Aether Quant is not a single static strategy — it's a **dynamic system**: a
|
|
19
31
|
Mixture-of-Experts ensemble (bullish/bearish/sideways/volatility specialists)
|
|
20
32
|
routed by a learned gating network, a market-regime detector, a 3D market
|
|
@@ -514,6 +526,8 @@ aq api
|
|
|
514
526
|
aq webui
|
|
515
527
|
aq docker up [--lean|--all]
|
|
516
528
|
aq docker build
|
|
529
|
+
aq config [get <dotted.key>|set <dotted.key> <value>|keys [<dotted.prefix>]]
|
|
530
|
+
aq lean [get <dotted.key>|set <dotted.key> <value>|keys [<dotted.prefix>]]
|
|
517
531
|
aq retrain <plan|train|validate|backtest|commit|promote|rollback|status> [...]
|
|
518
532
|
aq trade-lock --on|--off|--auto|--status
|
|
519
533
|
aq fetch <crypto|stock> --ticker <TICKER> --start <YYYY-MM-DD> --end <YYYY-MM-DD> [--apply]
|
|
@@ -531,6 +545,8 @@ wrapper around a command already documented elsewhere in this README:
|
|
|
531
545
|
- **`aq webui`** — starts the webui dev server (`npm run dev`).
|
|
532
546
|
- **`aq docker up [--lean|--all]`** — starts local infrastructure (default: Redis + PostgreSQL only).
|
|
533
547
|
- **`aq docker build`** — rebuilds the `aether-quant` app image.
|
|
548
|
+
- **`aq config`** — reads or edits `config.json` directly, no manual file editing needed. Bare `aq config` pretty-prints the whole file; `aq config keys [<dotted.prefix>]` lists every leaf key path (handy for finding the right key in a deeply nested file); `aq config get <dotted.key>` prints one value (scalar, or a whole nested section as JSON); `aq config set <dotted.key> <value>` writes it — the value is parsed as JSON first (so `true`/`123`/`0.5`/`["a","b"]` become their real types automatically), falling back to a plain string otherwise. Every `set` backs up the previous file to `config.json.bak` first and prints old → new so a mistake is immediately visible; changing a value's type (e.g. bool → string) prints a warning but still writes it, since this command intentionally gives full access to every key, not just a safe subset.
|
|
549
|
+
- **`aq lean`** — the exact same `get`/`set`/`keys` tool as `aq config`, just pointed at `lean.json` (the QuantConnect Lean CLI's own config file — broker credentials, environments, data providers) instead. `aq lean set ib-trading-mode live`, `aq lean keys environments.live-paper`, etc.
|
|
534
550
|
- **`aq retrain <stage>`** — dispatches to `python -m retraining.orchestrator <stage> ...` for a single manual pipeline stage.
|
|
535
551
|
- **`aq trade-lock --on|--off|--auto|--status`** — manually overrides `main.py`'s sticky total-drawdown trade lock (see `development/v2_architecture.md`'s Manual Trade-Lock Override Contract). `--off` deliberately clears an otherwise-permanent lock; `--auto` returns to fully automatic behavior.
|
|
536
552
|
- **`aq fetch <crypto|stock> --ticker <TICKER> --start <YYYY-MM-DD> --end <YYYY-MM-DD> [--apply]`** — fetches historical OHLCV from Yahoo Finance for a ticker that isn't in `config.json` yet, formats it into Lean's zip/CSV convention, and writes it to the right spot under `data/` (`data/crypto/coinbase/daily/<ticker>_trade.zip` or `data/equity/usa/daily/<ticker>.zip`). On `--apply`, it also appends a new asset block to `config.json`'s `phase1.universe.assets[]` — no manual editing needed. Dry run by default (no `--apply`): reports what would happen, writes nothing. Never runs `train.py` itself — once applied, run `python train.py --dataset-only` (then `python train.py` when ready) yourself to actually train on the new ticker. `crypto`/`stock` today; a `derivative` asset class is planned for V3.
|
|
@@ -706,5 +722,5 @@ architecture docs already identify:
|
|
|
706
722
|
---
|
|
707
723
|
|
|
708
724
|
<div align="center">
|
|
709
|
-
<sub>
|
|
725
|
+
<sub>Aether Quant</sub>
|
|
710
726
|
</div>
|
|
@@ -53,3 +53,29 @@ risk engine, and before action categorization / Lean order placement.
|
|
|
53
53
|
actions are written into the per-asset `signal_payload` for
|
|
54
54
|
dashboard/Grafana visibility regardless of whether a real order is
|
|
55
55
|
placed.
|
|
56
|
+
- **Real composite scoring (additive, config-gated).** Until now, every
|
|
57
|
+
priority tier here checked exactly one raw field in isolation against a
|
|
58
|
+
fixed threshold (confidence alone, `topology_risk` alone,
|
|
59
|
+
`liquidity_action` alone) — pure if/elif routing, no aggregation. New
|
|
60
|
+
`compute_signal_quality_score(confidence, regime_confidence, topology,
|
|
61
|
+
liquidity)` computes a real bounded `[0,1]` composite — a small,
|
|
62
|
+
hand-tuned weighted blend of raw model confidence (0.45), regime
|
|
63
|
+
confidence (0.20), topology peer-support (0.20, penalized when
|
|
64
|
+
`topology_risk` is `isolated`/`elevated`), and liquidity friction (0.15,
|
|
65
|
+
penalized by `participation_rate`) — mirroring `moe/gating.py`'s
|
|
66
|
+
`_quality_multiplier`/`_performance_score` style: real math over
|
|
67
|
+
already-available fields, not a trained model. `MarketAnalysisDecision`
|
|
68
|
+
gains `signal_quality_score`/`signal_quality_breakdown` fields that are
|
|
69
|
+
**always** computed and populated on every decision, regardless of any
|
|
70
|
+
flag — visible in `visualization/state.json` immediately for dashboard
|
|
71
|
+
use. It only ever changes routing when
|
|
72
|
+
`phase_v2.market_analyzer.use_composite_signal_score` is explicitly
|
|
73
|
+
`true` (default `false`) — in that case it replaces raw `confidence`
|
|
74
|
+
with the composite score in the `trade` gate (priority 7) and the
|
|
75
|
+
`simulate`-vs-`observe` split (priority 8) only. The hard safety-override
|
|
76
|
+
tiers (trade-lock, `risk_off` regime, elevated topology, liquidity
|
|
77
|
+
blocked — priorities 1-6) are **never** affected by this flag, regardless
|
|
78
|
+
of its value — they stay simple categorical gates on purpose, same
|
|
79
|
+
reasoning as the topology-elevated/isolated rules above. Default `false`
|
|
80
|
+
means output is byte-identical to pre-this-change behavior everywhere
|
|
81
|
+
the flag isn't explicitly turned on.
|
|
@@ -4,10 +4,12 @@ from .market_analyzer import (
|
|
|
4
4
|
ACTIONS,
|
|
5
5
|
MarketAnalysisDecision,
|
|
6
6
|
build_market_analysis_decision,
|
|
7
|
+
compute_signal_quality_score,
|
|
7
8
|
)
|
|
8
9
|
|
|
9
10
|
__all__ = [
|
|
10
11
|
"ACTIONS",
|
|
11
12
|
"MarketAnalysisDecision",
|
|
12
13
|
"build_market_analysis_decision",
|
|
14
|
+
"compute_signal_quality_score",
|
|
13
15
|
]
|
|
@@ -4,7 +4,7 @@ action."""
|
|
|
4
4
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
|
-
from dataclasses import asdict, dataclass
|
|
7
|
+
from dataclasses import asdict, dataclass, field
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
ACTIONS = ("observe", "simulate", "trade", "reduce_risk", "retrain_candidate")
|
|
@@ -21,11 +21,57 @@ class MarketAnalysisDecision:
|
|
|
21
21
|
topology_considered: bool
|
|
22
22
|
liquidity_considered: bool
|
|
23
23
|
reasons: list[str]
|
|
24
|
+
signal_quality_score: float = 0.0
|
|
25
|
+
signal_quality_breakdown: dict = field(default_factory=dict)
|
|
24
26
|
|
|
25
27
|
def to_dict(self) -> dict:
|
|
26
28
|
return asdict(self)
|
|
27
29
|
|
|
28
30
|
|
|
31
|
+
def _clamp01(value: float) -> float:
|
|
32
|
+
return max(0.0, min(1.0, value))
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def compute_signal_quality_score(
|
|
36
|
+
confidence: float,
|
|
37
|
+
regime_confidence: float,
|
|
38
|
+
topology: dict,
|
|
39
|
+
liquidity: dict,
|
|
40
|
+
) -> tuple[float, dict]:
|
|
41
|
+
"""Bounded [0,1] composite of raw model confidence, regime confidence,
|
|
42
|
+
topology peer-support and liquidity friction. Always computed and
|
|
43
|
+
exposed (signal_quality_score/signal_quality_breakdown) for dashboard
|
|
44
|
+
visibility; only influences routing in build_market_analysis_decision()
|
|
45
|
+
when use_composite_signal_score=True - see analyzer/README.md. Small
|
|
46
|
+
hand-tuned weights, mirroring moe/gating.py's
|
|
47
|
+
_quality_multiplier/_performance_score style - not a trained model."""
|
|
48
|
+
confidence_component = _clamp01(float(confidence))
|
|
49
|
+
regime_component = _clamp01(float(regime_confidence))
|
|
50
|
+
|
|
51
|
+
correlation_strength = _clamp01(float(topology.get("correlation_strength", 0.0) or 0.0))
|
|
52
|
+
topology_penalty = {"isolated": 0.3, "elevated": 0.6}.get(str(topology.get("topology_risk", "unknown")), 1.0)
|
|
53
|
+
topology_component = correlation_strength * topology_penalty if topology else regime_component
|
|
54
|
+
|
|
55
|
+
participation_rate = _clamp01(float(liquidity.get("participation_rate", 0.0) or 0.0))
|
|
56
|
+
liquidity_component = _clamp01(1.0 - participation_rate) if liquidity else 1.0
|
|
57
|
+
|
|
58
|
+
weights = {"confidence": 0.45, "regime": 0.20, "topology": 0.20, "liquidity": 0.15}
|
|
59
|
+
score = (
|
|
60
|
+
weights["confidence"] * confidence_component
|
|
61
|
+
+ weights["regime"] * regime_component
|
|
62
|
+
+ weights["topology"] * topology_component
|
|
63
|
+
+ weights["liquidity"] * liquidity_component
|
|
64
|
+
)
|
|
65
|
+
breakdown = {
|
|
66
|
+
"confidence_component": confidence_component,
|
|
67
|
+
"regime_component": regime_component,
|
|
68
|
+
"topology_component": topology_component,
|
|
69
|
+
"liquidity_component": liquidity_component,
|
|
70
|
+
"weights": dict(weights),
|
|
71
|
+
}
|
|
72
|
+
return _clamp01(score), breakdown
|
|
73
|
+
|
|
74
|
+
|
|
29
75
|
def build_market_analysis_decision(
|
|
30
76
|
signal_name: str,
|
|
31
77
|
confidence: float,
|
|
@@ -41,6 +87,7 @@ def build_market_analysis_decision(
|
|
|
41
87
|
min_confidence_to_trade: float = 0.12,
|
|
42
88
|
retrain_min_regime_confidence: float = 0.20,
|
|
43
89
|
low_regime_confidence_threshold: float = 0.35,
|
|
90
|
+
use_composite_signal_score: bool = False,
|
|
44
91
|
) -> MarketAnalysisDecision:
|
|
45
92
|
reasons: list[str] = []
|
|
46
93
|
decision_source = str(gating.get("decision_source", "unknown"))
|
|
@@ -57,6 +104,17 @@ def build_market_analysis_decision(
|
|
|
57
104
|
else:
|
|
58
105
|
reasons.append("topology_absent_v2_11_pending")
|
|
59
106
|
|
|
107
|
+
# Always computed and exposed for dashboard visibility, regardless of
|
|
108
|
+
# use_composite_signal_score - see compute_signal_quality_score()'s
|
|
109
|
+
# docstring and analyzer/README.md. trade_metric is what priorities 7/8
|
|
110
|
+
# actually gate on below: the composite score when the flag is on,
|
|
111
|
+
# otherwise raw confidence - byte-identical to pre-flag behavior when
|
|
112
|
+
# use_composite_signal_score=False (the default).
|
|
113
|
+
signal_quality_score, signal_quality_breakdown = compute_signal_quality_score(
|
|
114
|
+
confidence, regime_confidence, topology, liquidity
|
|
115
|
+
)
|
|
116
|
+
trade_metric = signal_quality_score if use_composite_signal_score else confidence
|
|
117
|
+
|
|
60
118
|
# Priority 1: reduce_risk - portfolio-level risk lock always wins.
|
|
61
119
|
if trade_lock_active:
|
|
62
120
|
reasons.append(trade_lock_reason or "risk_lock_active")
|
|
@@ -70,6 +128,8 @@ def build_market_analysis_decision(
|
|
|
70
128
|
topology_considered=topology_considered,
|
|
71
129
|
liquidity_considered=liquidity_considered,
|
|
72
130
|
reasons=reasons,
|
|
131
|
+
signal_quality_score=signal_quality_score,
|
|
132
|
+
signal_quality_breakdown=signal_quality_breakdown,
|
|
73
133
|
)
|
|
74
134
|
|
|
75
135
|
# Priority 2: reduce_risk - asset-level risk regime override even
|
|
@@ -86,6 +146,8 @@ def build_market_analysis_decision(
|
|
|
86
146
|
topology_considered=topology_considered,
|
|
87
147
|
liquidity_considered=liquidity_considered,
|
|
88
148
|
reasons=reasons,
|
|
149
|
+
signal_quality_score=signal_quality_score,
|
|
150
|
+
signal_quality_breakdown=signal_quality_breakdown,
|
|
89
151
|
)
|
|
90
152
|
|
|
91
153
|
# Priority 3: reduce_risk - elevated cross-sectional volatility pressure
|
|
@@ -106,6 +168,8 @@ def build_market_analysis_decision(
|
|
|
106
168
|
topology_considered=topology_considered,
|
|
107
169
|
liquidity_considered=liquidity_considered,
|
|
108
170
|
reasons=reasons,
|
|
171
|
+
signal_quality_score=signal_quality_score,
|
|
172
|
+
signal_quality_breakdown=signal_quality_breakdown,
|
|
109
173
|
)
|
|
110
174
|
|
|
111
175
|
# Priority 4: retrain_candidate - zero experts contributing AND the
|
|
@@ -125,6 +189,8 @@ def build_market_analysis_decision(
|
|
|
125
189
|
topology_considered=topology_considered,
|
|
126
190
|
liquidity_considered=liquidity_considered,
|
|
127
191
|
reasons=reasons,
|
|
192
|
+
signal_quality_score=signal_quality_score,
|
|
193
|
+
signal_quality_breakdown=signal_quality_breakdown,
|
|
128
194
|
)
|
|
129
195
|
|
|
130
196
|
# Priority 5: simulate - liquidity blocked (zero volume or DDV below
|
|
@@ -142,6 +208,8 @@ def build_market_analysis_decision(
|
|
|
142
208
|
topology_considered=topology_considered,
|
|
143
209
|
liquidity_considered=liquidity_considered,
|
|
144
210
|
reasons=reasons,
|
|
211
|
+
signal_quality_score=signal_quality_score,
|
|
212
|
+
signal_quality_breakdown=signal_quality_breakdown,
|
|
145
213
|
)
|
|
146
214
|
|
|
147
215
|
# Priority 6: simulate - thin market; participation rate would be
|
|
@@ -158,15 +226,20 @@ def build_market_analysis_decision(
|
|
|
158
226
|
topology_considered=topology_considered,
|
|
159
227
|
liquidity_considered=liquidity_considered,
|
|
160
228
|
reasons=reasons,
|
|
229
|
+
signal_quality_score=signal_quality_score,
|
|
230
|
+
signal_quality_breakdown=signal_quality_breakdown,
|
|
161
231
|
)
|
|
162
232
|
|
|
163
233
|
# Priority 7: trade - only for trading-eligible assets with an actionable
|
|
164
234
|
# directional signal, sufficient confidence, no topology isolation, and
|
|
165
235
|
# no liquidity block (redundant guard — tiers 5/6 already caught those).
|
|
236
|
+
# Gates on trade_metric, not raw confidence directly - see trade_metric's
|
|
237
|
+
# definition above (identical to confidence unless
|
|
238
|
+
# use_composite_signal_score=True).
|
|
166
239
|
if (
|
|
167
240
|
trading_eligible
|
|
168
241
|
and signal_name in {"buy", "sell"}
|
|
169
|
-
and
|
|
242
|
+
and trade_metric >= min_confidence_to_trade
|
|
170
243
|
and topology_risk != "isolated"
|
|
171
244
|
and liquidity_action not in {"block", "simulate_instead"}
|
|
172
245
|
):
|
|
@@ -181,6 +254,8 @@ def build_market_analysis_decision(
|
|
|
181
254
|
topology_considered=topology_considered,
|
|
182
255
|
liquidity_considered=liquidity_considered,
|
|
183
256
|
reasons=reasons,
|
|
257
|
+
signal_quality_score=signal_quality_score,
|
|
258
|
+
signal_quality_breakdown=signal_quality_breakdown,
|
|
184
259
|
)
|
|
185
260
|
|
|
186
261
|
# Priority 8: simulate vs observe.
|
|
@@ -201,6 +276,8 @@ def build_market_analysis_decision(
|
|
|
201
276
|
topology_considered=topology_considered,
|
|
202
277
|
liquidity_considered=liquidity_considered,
|
|
203
278
|
reasons=reasons,
|
|
279
|
+
signal_quality_score=signal_quality_score,
|
|
280
|
+
signal_quality_breakdown=signal_quality_breakdown,
|
|
204
281
|
)
|
|
205
282
|
|
|
206
283
|
reasons.append("no_actionable_edge")
|
|
@@ -214,4 +291,6 @@ def build_market_analysis_decision(
|
|
|
214
291
|
topology_considered=topology_considered,
|
|
215
292
|
liquidity_considered=liquidity_considered,
|
|
216
293
|
reasons=reasons,
|
|
294
|
+
signal_quality_score=signal_quality_score,
|
|
295
|
+
signal_quality_breakdown=signal_quality_breakdown,
|
|
217
296
|
)
|
|
@@ -33,6 +33,7 @@ import sys
|
|
|
33
33
|
import time
|
|
34
34
|
import urllib.request
|
|
35
35
|
import uuid
|
|
36
|
+
from collections.abc import Iterator
|
|
36
37
|
from datetime import date
|
|
37
38
|
from importlib.metadata import version as installed_version
|
|
38
39
|
from pathlib import Path
|
|
@@ -42,6 +43,7 @@ from risk.manual_override import read_manual_trade_lock_override, write_manual_t
|
|
|
42
43
|
|
|
43
44
|
ROOT_DIR = Path(__file__).resolve().parent
|
|
44
45
|
CONFIG_PATH = ROOT_DIR / "config.json"
|
|
46
|
+
LEAN_JSON_PATH = ROOT_DIR / "lean.json"
|
|
45
47
|
WEBUI_DIR = ROOT_DIR / "webui"
|
|
46
48
|
README_PATH = ROOT_DIR / "README.md"
|
|
47
49
|
|
|
@@ -393,6 +395,115 @@ def cmd_trade_lock(args: argparse.Namespace) -> int:
|
|
|
393
395
|
return 0
|
|
394
396
|
|
|
395
397
|
|
|
398
|
+
class ConfigPathError(Exception):
|
|
399
|
+
"""Raised by _get_config_value/_set_config_value for a bad dotted path."""
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def _get_config_value(config: dict, dotted_path: str) -> object:
|
|
403
|
+
node = config
|
|
404
|
+
walked: list[str] = []
|
|
405
|
+
for segment in dotted_path.split("."):
|
|
406
|
+
walked.append(segment)
|
|
407
|
+
if not isinstance(node, dict) or segment not in node:
|
|
408
|
+
raise ConfigPathError(f"no such config key: {'.'.join(walked)!r}")
|
|
409
|
+
node = node[segment]
|
|
410
|
+
return node
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def _coerce_config_value(raw: str) -> object:
|
|
414
|
+
"""JSON-first parsing so true/false/123/0.5/[...]/{...} all become their
|
|
415
|
+
real type automatically; anything that isn't valid JSON on its own
|
|
416
|
+
(e.g. a bare word) is kept as a plain string."""
|
|
417
|
+
try:
|
|
418
|
+
return json.loads(raw)
|
|
419
|
+
except json.JSONDecodeError:
|
|
420
|
+
return raw
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def _set_config_value(config: dict, dotted_path: str, raw_value: str) -> tuple[object, object, bool]:
|
|
424
|
+
"""Mutates `config` in place. Returns (old_value, new_value, type_changed) -
|
|
425
|
+
deliberately does not refuse to overwrite a list/dict: the caller wants
|
|
426
|
+
full read/write access to every key, not just scalars. Safety instead
|
|
427
|
+
comes from always reporting old -> new (and a type-change warning) to
|
|
428
|
+
the caller, plus the automatic config.json.bak snapshot cmd_config()
|
|
429
|
+
writes before every set."""
|
|
430
|
+
*parents, leaf = dotted_path.split(".")
|
|
431
|
+
node = config
|
|
432
|
+
for segment in parents:
|
|
433
|
+
if not isinstance(node, dict) or segment not in node:
|
|
434
|
+
raise ConfigPathError(f"no such config key: {dotted_path!r}")
|
|
435
|
+
node = node[segment]
|
|
436
|
+
if not isinstance(node, dict) or leaf not in node:
|
|
437
|
+
raise ConfigPathError(f"no such config key: {dotted_path!r}")
|
|
438
|
+
old_value = node[leaf]
|
|
439
|
+
new_value = _coerce_config_value(raw_value)
|
|
440
|
+
node[leaf] = new_value
|
|
441
|
+
return old_value, new_value, type(old_value) is not type(new_value)
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
def _iter_leaf_paths(node: object, prefix: str = "") -> Iterator[str]:
|
|
445
|
+
"""Recursively yields every dot-joined leaf path under `node`. A "leaf"
|
|
446
|
+
is any non-dict value (or an empty dict) - list-valued keys show up as
|
|
447
|
+
one leaf, never expanded per-element."""
|
|
448
|
+
if isinstance(node, dict) and node:
|
|
449
|
+
for key, value in node.items():
|
|
450
|
+
yield from _iter_leaf_paths(value, f"{prefix}.{key}" if prefix else key)
|
|
451
|
+
else:
|
|
452
|
+
yield prefix
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
def _dispatch_json_config_command(args: argparse.Namespace, json_path: Path, command_attr: str) -> int:
|
|
456
|
+
"""Shared dispatch for `aq config`/`aq lean` - both are the same
|
|
457
|
+
dump/get/set/keys tool over a single flat JSON file, just pointed at a
|
|
458
|
+
different path. See cmd_config()/cmd_lean()."""
|
|
459
|
+
data = json.loads(json_path.read_text(encoding="utf-8"))
|
|
460
|
+
command = getattr(args, command_attr, None)
|
|
461
|
+
|
|
462
|
+
try:
|
|
463
|
+
if command is None:
|
|
464
|
+
print(json.dumps(data, indent=2))
|
|
465
|
+
return 0
|
|
466
|
+
|
|
467
|
+
if command == "get":
|
|
468
|
+
value = _get_config_value(data, args.dotted_path)
|
|
469
|
+
print(value if isinstance(value, str) else json.dumps(value, indent=2))
|
|
470
|
+
return 0
|
|
471
|
+
|
|
472
|
+
if command == "keys":
|
|
473
|
+
root = _get_config_value(data, args.dotted_prefix) if args.dotted_prefix else data
|
|
474
|
+
for path in _iter_leaf_paths(root, args.dotted_prefix or ""):
|
|
475
|
+
print(path)
|
|
476
|
+
return 0
|
|
477
|
+
|
|
478
|
+
if command == "set":
|
|
479
|
+
shutil.copy2(json_path, json_path.with_suffix(".json.bak"))
|
|
480
|
+
old_value, new_value, type_changed = _set_config_value(data, args.dotted_path, args.value)
|
|
481
|
+
json_path.write_text(json.dumps(data, indent=4) + "\n", encoding="utf-8")
|
|
482
|
+
print(f"{args.dotted_path}: {old_value!r} -> {new_value!r}")
|
|
483
|
+
if type_changed:
|
|
484
|
+
print(
|
|
485
|
+
f"WARNING: type changed from {type(old_value).__name__} to {type(new_value).__name__} "
|
|
486
|
+
f"for {args.dotted_path}",
|
|
487
|
+
file=sys.stderr,
|
|
488
|
+
)
|
|
489
|
+
return 0
|
|
490
|
+
except ConfigPathError as error:
|
|
491
|
+
print(f"error: {error}", file=sys.stderr)
|
|
492
|
+
return 1
|
|
493
|
+
|
|
494
|
+
return 1
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
def cmd_config(args: argparse.Namespace) -> int:
|
|
498
|
+
return _dispatch_json_config_command(args, CONFIG_PATH, "config_command")
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
def cmd_lean(args: argparse.Namespace) -> int:
|
|
502
|
+
return _dispatch_json_config_command(args, LEAN_JSON_PATH, "lean_command")
|
|
503
|
+
|
|
504
|
+
return 1
|
|
505
|
+
|
|
506
|
+
|
|
396
507
|
def cmd_fetch(args: argparse.Namespace) -> int:
|
|
397
508
|
report = fetch_adhoc_asset(args.asset_class, args.ticker, args.start, args.end, apply=args.apply)
|
|
398
509
|
label = "APPLY" if args.apply else "DRY RUN"
|
|
@@ -464,12 +575,51 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
464
575
|
docker_build_parser = docker_subparsers.add_parser("build", help="Rebuild the aether-quant app image")
|
|
465
576
|
docker_build_parser.set_defaults(func=cmd_docker_build)
|
|
466
577
|
|
|
578
|
+
config_parser = subparsers.add_parser("config", help="Show or edit config.json")
|
|
579
|
+
config_parser.set_defaults(func=cmd_config)
|
|
580
|
+
config_subparsers = config_parser.add_subparsers(dest="config_command")
|
|
581
|
+
|
|
582
|
+
config_get_parser = config_subparsers.add_parser("get", help="Print a config.json value")
|
|
583
|
+
config_get_parser.add_argument("dotted_path")
|
|
584
|
+
|
|
585
|
+
config_keys_parser = config_subparsers.add_parser("keys", help="List leaf key paths (optionally scoped to a prefix)")
|
|
586
|
+
config_keys_parser.add_argument("dotted_prefix", nargs="?", default=None)
|
|
587
|
+
|
|
588
|
+
config_set_parser = config_subparsers.add_parser("set", help="Set a config.json value (JSON-parsed, string fallback)")
|
|
589
|
+
config_set_parser.add_argument("dotted_path")
|
|
590
|
+
config_set_parser.add_argument("value")
|
|
591
|
+
|
|
592
|
+
lean_parser = subparsers.add_parser("lean", help="Show or edit lean.json (same shape as `aq config`)")
|
|
593
|
+
lean_parser.set_defaults(func=cmd_lean)
|
|
594
|
+
lean_subparsers = lean_parser.add_subparsers(dest="lean_command")
|
|
595
|
+
|
|
596
|
+
lean_get_parser = lean_subparsers.add_parser("get", help="Print a lean.json value")
|
|
597
|
+
lean_get_parser.add_argument("dotted_path")
|
|
598
|
+
|
|
599
|
+
lean_keys_parser = lean_subparsers.add_parser("keys", help="List leaf key paths (optionally scoped to a prefix)")
|
|
600
|
+
lean_keys_parser.add_argument("dotted_prefix", nargs="?", default=None)
|
|
601
|
+
|
|
602
|
+
lean_set_parser = lean_subparsers.add_parser("set", help="Set a lean.json value (JSON-parsed, string fallback)")
|
|
603
|
+
lean_set_parser.add_argument("dotted_path")
|
|
604
|
+
lean_set_parser.add_argument("value")
|
|
605
|
+
|
|
467
606
|
retrain_parser = subparsers.add_parser(
|
|
468
607
|
"retrain", help="Thin dispatcher to python -m retraining.orchestrator <stage> ..."
|
|
469
608
|
)
|
|
470
609
|
retrain_parser.add_argument(
|
|
471
610
|
"stage",
|
|
472
|
-
choices=[
|
|
611
|
+
choices=[
|
|
612
|
+
"plan",
|
|
613
|
+
"train",
|
|
614
|
+
"train_topology",
|
|
615
|
+
"train_gating",
|
|
616
|
+
"validate",
|
|
617
|
+
"backtest",
|
|
618
|
+
"commit",
|
|
619
|
+
"promote",
|
|
620
|
+
"rollback",
|
|
621
|
+
"status",
|
|
622
|
+
],
|
|
473
623
|
)
|
|
474
624
|
retrain_parser.add_argument("retrain_args", nargs=argparse.REMAINDER, help="Passed through verbatim, e.g. --version-id <uuid>")
|
|
475
625
|
retrain_parser.set_defaults(func=cmd_retrain)
|