aria-code 0.52.0__py3-none-any.whl
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.
- aria_code/__init__.py +42 -0
- aria_code/_version.py +20 -0
- aria_code/adk_apps/__init__.py +1 -0
- aria_code/adk_apps/aria_code_review/__init__.py +1 -0
- aria_code/adk_apps/aria_code_review/agent.py +32 -0
- aria_code/adk_apps/aria_research/__init__.py +1 -0
- aria_code/adk_apps/aria_research/agent.py +31 -0
- aria_code/agents/__init__.py +34 -0
- aria_code/agents/base.py +318 -0
- aria_code/agents/code_review.py +200 -0
- aria_code/agents/deep/__init__.py +37 -0
- aria_code/agents/deep/calibration_loop.py +144 -0
- aria_code/agents/deep/critic.py +125 -0
- aria_code/agents/deep/deepen.py +193 -0
- aria_code/agents/deep/models.py +149 -0
- aria_code/agents/deep/pipeline.py +164 -0
- aria_code/agents/deep/quant_fusion.py +192 -0
- aria_code/agents/deep/themes.py +95 -0
- aria_code/agents/deep/tiers.py +106 -0
- aria_code/agents/engineering/__init__.py +14 -0
- aria_code/agents/engineering/coder.py +222 -0
- aria_code/agents/engineering/tester.py +145 -0
- aria_code/agents/financial/__init__.py +21 -0
- aria_code/agents/financial/cashflow_burn.py +119 -0
- aria_code/agents/financial/catalyst.py +279 -0
- aria_code/agents/financial/corporate_finance.py +175 -0
- aria_code/agents/financial/debate.py +224 -0
- aria_code/agents/financial/earnings.py +303 -0
- aria_code/agents/financial/fundamental.py +191 -0
- aria_code/agents/financial/macro.py +99 -0
- aria_code/agents/financial/news.py +207 -0
- aria_code/agents/financial/risk.py +165 -0
- aria_code/agents/financial/sector.py +279 -0
- aria_code/agents/financial/strategist.py +121 -0
- aria_code/agents/financial/stripe_revenue.py +127 -0
- aria_code/agents/financial/synthesis.py +274 -0
- aria_code/agents/financial/technical.py +258 -0
- aria_code/agents/orchestrator.py +44 -0
- aria_code/agents/portfolio_agent.py +363 -0
- aria_code/agents/realty/__init__.py +62 -0
- aria_code/agents/realty/asset_diagnosis.py +152 -0
- aria_code/agents/realty/business_match.py +166 -0
- aria_code/agents/realty/cashflow_verify.py +209 -0
- aria_code/agents/realty/contract_rules.py +210 -0
- aria_code/agents/realty/energy_anomaly.py +189 -0
- aria_code/agents/realty/exit_settlement.py +208 -0
- aria_code/agents/realty/fulfillment_risk.py +206 -0
- aria_code/agents/realty/ops_optimize.py +160 -0
- aria_code/agents/realty/revenue_share.py +215 -0
- aria_code/agents/registry.py +158 -0
- aria_code/agents/signal_scheme.py +111 -0
- aria_code/agents/sports/__init__.py +0 -0
- aria_code/agents/sports/football_agent.py +197 -0
- aria_code/agents/supervisor.py +113 -0
- aria_code/agents/team.py +492 -0
- aria_code/agents/warehouse/__init__.py +25 -0
- aria_code/agents/warehouse/contracts.py +26 -0
- aria_code/agents/warehouse/dashboard.py +155 -0
- aria_code/agents/warehouse/fulfillment_leadtime.py +95 -0
- aria_code/agents/warehouse/inbound_exceptions.py +25 -0
- aria_code/agents/warehouse/inventory_health.py +24 -0
- aria_code/agents/warehouse/local_run.py +71 -0
- aria_code/agents/warehouse/local_snapshot.py +132 -0
- aria_code/agents/warehouse/logistics_cost.py +84 -0
- aria_code/agents/warehouse/logistics_sync.py +21 -0
- aria_code/agents/warehouse/workflow.py +31 -0
- aria_code/aliyun_data_client.py +4 -0
- aria_code/apps/README.md +12 -0
- aria_code/apps/__init__.py +2 -0
- aria_code/apps/channels/README.md +15 -0
- aria_code/apps/channels/__init__.py +17 -0
- aria_code/apps/channels/intake.py +92 -0
- aria_code/apps/channels/registry.py +95 -0
- aria_code/apps/channels/tradingview.py +94 -0
- aria_code/apps/cli/README.md +13 -0
- aria_code/apps/cli/__init__.py +2 -0
- aria_code/apps/cli/_optional.py +120 -0
- aria_code/apps/cli/bootstrap.py +145 -0
- aria_code/apps/cli/broker_render.py +206 -0
- aria_code/apps/cli/codegen_paths.py +29 -0
- aria_code/apps/cli/commands/__init__.py +16 -0
- aria_code/apps/cli/commands/_business_render.py +78 -0
- aria_code/apps/cli/commands/_ui.py +71 -0
- aria_code/apps/cli/commands/analysis_cmds.py +369 -0
- aria_code/apps/cli/commands/ashare_prediction_cmds.py +327 -0
- aria_code/apps/cli/commands/auth_cmds.py +239 -0
- aria_code/apps/cli/commands/backtest_cmds.py +2543 -0
- aria_code/apps/cli/commands/broker_cmds.py +1234 -0
- aria_code/apps/cli/commands/business_workflow_cmds.py +319 -0
- aria_code/apps/cli/commands/canvas_cmds.py +77 -0
- aria_code/apps/cli/commands/catalog.py +87 -0
- aria_code/apps/cli/commands/core_cmds.py +2520 -0
- aria_code/apps/cli/commands/data_cmds.py +457 -0
- aria_code/apps/cli/commands/diagnostic_cmds.py +230 -0
- aria_code/apps/cli/commands/diagnostic_ops_cmds.py +813 -0
- aria_code/apps/cli/commands/file_cmds.py +312 -0
- aria_code/apps/cli/commands/finance_service_cmds.py +60 -0
- aria_code/apps/cli/commands/fx_commodity_cmds.py +123 -0
- aria_code/apps/cli/commands/market.py +434 -0
- aria_code/apps/cli/commands/market_cmds.py +1381 -0
- aria_code/apps/cli/commands/market_context.py +425 -0
- aria_code/apps/cli/commands/market_render.py +7 -0
- aria_code/apps/cli/commands/model_cmds.py +2153 -0
- aria_code/apps/cli/commands/ops_cmds.py +721 -0
- aria_code/apps/cli/commands/orchestrator_cmds.py +179 -0
- aria_code/apps/cli/commands/pdf_export_cmds.py +125 -0
- aria_code/apps/cli/commands/portfolio_cmds.py +1278 -0
- aria_code/apps/cli/commands/report.py +437 -0
- aria_code/apps/cli/commands/scaffold_templates.py +617 -0
- aria_code/apps/cli/commands/session_cmds.py +236 -0
- aria_code/apps/cli/commands/session_ux_cmds.py +359 -0
- aria_code/apps/cli/commands/team.py +669 -0
- aria_code/apps/cli/commands/team_render.py +8 -0
- aria_code/apps/cli/commands/ui_cmds.py +401 -0
- aria_code/apps/cli/commands/warehouse_cmds.py +79 -0
- aria_code/apps/cli/commands/workflow_cmds.py +480 -0
- aria_code/apps/cli/commands/workspace_cmds.py +1494 -0
- aria_code/apps/cli/config_paths.py +73 -0
- aria_code/apps/cli/config_store.py +78 -0
- aria_code/apps/cli/constants.py +88 -0
- aria_code/apps/cli/context.py +34 -0
- aria_code/apps/cli/deterministic.py +191 -0
- aria_code/apps/cli/direct.py +48 -0
- aria_code/apps/cli/finance_service_catalog.py +111 -0
- aria_code/apps/cli/football_reports.py +231 -0
- aria_code/apps/cli/github_app_auth.py +135 -0
- aria_code/apps/cli/google_login.py +196 -0
- aria_code/apps/cli/handlers/__init__.py +11 -0
- aria_code/apps/cli/handlers/broker_handlers.py +122 -0
- aria_code/apps/cli/handlers/chart_handlers.py +1309 -0
- aria_code/apps/cli/handlers/market_handlers.py +2513 -0
- aria_code/apps/cli/handlers/realty_handlers.py +114 -0
- aria_code/apps/cli/handlers/sports_handlers.py +22 -0
- aria_code/apps/cli/handlers/strategy_advice.py +82 -0
- aria_code/apps/cli/helpers.py +302 -0
- aria_code/apps/cli/hooks.py +180 -0
- aria_code/apps/cli/i18n.py +297 -0
- aria_code/apps/cli/intent.py +136 -0
- aria_code/apps/cli/intent_router.py +276 -0
- aria_code/apps/cli/intent_signals.py +344 -0
- aria_code/apps/cli/lifecycle_hooks.py +48 -0
- aria_code/apps/cli/local_tool_schemas.py +536 -0
- aria_code/apps/cli/main.py +29 -0
- aria_code/apps/cli/market_metadata.py +135 -0
- aria_code/apps/cli/market_universe.py +487 -0
- aria_code/apps/cli/message_processing.py +310 -0
- aria_code/apps/cli/model_catalog.py +620 -0
- aria_code/apps/cli/pdf_report.py +977 -0
- aria_code/apps/cli/plan_mode.py +139 -0
- aria_code/apps/cli/plotly_html.py +15 -0
- aria_code/apps/cli/prediction_feedback.py +202 -0
- aria_code/apps/cli/preflight.py +502 -0
- aria_code/apps/cli/project_aria.py +60 -0
- aria_code/apps/cli/prompt_assembly.py +59 -0
- aria_code/apps/cli/prompts/__init__.py +0 -0
- aria_code/apps/cli/prompts/coding.py +53 -0
- aria_code/apps/cli/prompts/select.py +142 -0
- aria_code/apps/cli/prompts/system_prompts.py +552 -0
- aria_code/apps/cli/prompts/ui.py +436 -0
- aria_code/apps/cli/provider_endpoints.py +237 -0
- aria_code/apps/cli/provider_keys.py +73 -0
- aria_code/apps/cli/providers/__init__.py +1 -0
- aria_code/apps/cli/providers/base.py +521 -0
- aria_code/apps/cli/providers/chat_routing.py +128 -0
- aria_code/apps/cli/providers/collaboration.py +98 -0
- aria_code/apps/cli/providers/llm/__init__.py +1 -0
- aria_code/apps/cli/providers/llm/offline_responses.py +68 -0
- aria_code/apps/cli/providers/llm/ollama_stream.py +1594 -0
- aria_code/apps/cli/providers/llm/response_cache.py +60 -0
- aria_code/apps/cli/providers/llm/sse_stream.py +257 -0
- aria_code/apps/cli/providers/runtime_bridge.py +353 -0
- aria_code/apps/cli/providers/vertexai_stream.py +316 -0
- aria_code/apps/cli/runtime_consumer.py +657 -0
- aria_code/apps/cli/sandbox.py +32 -0
- aria_code/apps/cli/session_export.py +120 -0
- aria_code/apps/cli/session_jsonl.py +208 -0
- aria_code/apps/cli/session_store.py +112 -0
- aria_code/apps/cli/skills_catalog.py +273 -0
- aria_code/apps/cli/todo_tracker.py +190 -0
- aria_code/apps/cli/tool_executor.py +823 -0
- aria_code/apps/cli/tool_registry.py +34 -0
- aria_code/apps/cli/tool_scope.py +106 -0
- aria_code/apps/cli/tools/__init__.py +40 -0
- aria_code/apps/cli/tools/context.py +46 -0
- aria_code/apps/cli/tools/file_tools.py +180 -0
- aria_code/apps/cli/tools/market_tools.py +620 -0
- aria_code/apps/cli/tools/notebook_tools.py +111 -0
- aria_code/apps/cli/tools/system_tools.py +726 -0
- aria_code/apps/cli/tools/write_tools.py +845 -0
- aria_code/apps/cli/tradingview_bridge.py +529 -0
- aria_code/apps/cli/turn_planning.py +137 -0
- aria_code/apps/cli/update_check.py +153 -0
- aria_code/apps/cli/utils/__init__.py +0 -0
- aria_code/apps/cli/utils/market_detect.py +1597 -0
- aria_code/apps/daemon/README.md +14 -0
- aria_code/apps/vscode/README.md +115 -0
- aria_code/apps/vscode/package.json +70 -0
- aria_code/aria_cli.py +6743 -0
- aria_code/aria_daemon.py +1398 -0
- aria_code/aria_feishu_bot.py +1423 -0
- aria_code/aria_mcp_server.py +7 -0
- aria_code/aria_relay_client.py +4 -0
- aria_code/aria_relay_server.py +490 -0
- aria_code/aria_telegram_bot.py +202 -0
- aria_code/ariarc.py +385 -0
- aria_code/artifacts.py +581 -0
- aria_code/backtest_engine.py +4 -0
- aria_code/backtest_report.py +4 -0
- aria_code/brokers/__init__.py +78 -0
- aria_code/brokers/automation.py +193 -0
- aria_code/brokers/base.py +231 -0
- aria_code/brokers/capabilities.py +264 -0
- aria_code/brokers/cn/__init__.py +10 -0
- aria_code/brokers/cn/easytrader_broker.py +193 -0
- aria_code/brokers/cn/futu_broker.py +194 -0
- aria_code/brokers/cn/longbridge_broker.py +190 -0
- aria_code/brokers/cn/tiger_broker.py +196 -0
- aria_code/brokers/cn/xtquant_broker.py +225 -0
- aria_code/brokers/config.py +414 -0
- aria_code/brokers/intl/__init__.py +5 -0
- aria_code/brokers/intl/alpaca_broker.py +183 -0
- aria_code/brokers/intl/ibkr_broker.py +215 -0
- aria_code/brokers/intl/trading212_broker.py +183 -0
- aria_code/brokers/intl/webull_broker.py +156 -0
- aria_code/brokers/paper_broker.py +266 -0
- aria_code/brokers/planning.py +296 -0
- aria_code/brokers/registry.py +213 -0
- aria_code/brokers/trading.py +267 -0
- aria_code/canva_client.py +4 -0
- aria_code/change_store.py +127 -0
- aria_code/clients/__init__.py +0 -0
- aria_code/clients/aliyun_data_client.py +660 -0
- aria_code/clients/aria_relay_client.py +182 -0
- aria_code/clients/canva_client.py +284 -0
- aria_code/clients/figma_client.py +100 -0
- aria_code/clients/football_data_client.py +1671 -0
- aria_code/clients/kling_video_client.py +182 -0
- aria_code/clients/market_data_client.py +1976 -0
- aria_code/clients/openai_image_client.py +242 -0
- aria_code/clients/runway_video_client.py +157 -0
- aria_code/clients/warehouse_erp_client.py +105 -0
- aria_code/cloud_memory.py +181 -0
- aria_code/command_safety.py +19 -0
- aria_code/computer_use_tools.py +4 -0
- aria_code/dashboard_generator.py +579 -0
- aria_code/data_analysis_tools.py +4 -0
- aria_code/data_cleaner.py +483 -0
- aria_code/data_service.py +513 -0
- aria_code/datasources/__init__.py +23 -0
- aria_code/datasources/base.py +166 -0
- aria_code/datasources/router.py +253 -0
- aria_code/datasources/sources/__init__.py +15 -0
- aria_code/datasources/sources/akshare_source.py +269 -0
- aria_code/datasources/sources/alpha_vantage_source.py +203 -0
- aria_code/datasources/sources/edgar_source.py +219 -0
- aria_code/datasources/sources/finnhub_source.py +198 -0
- aria_code/datasources/sources/fred_source.py +223 -0
- aria_code/datasources/sources/tushare_source.py +143 -0
- aria_code/datasources/sources/web_scraper_source.py +278 -0
- aria_code/datasources/sources/world_bank_source.py +206 -0
- aria_code/datasources/sources/yfinance_source.py +152 -0
- aria_code/demo_player.py +207 -0
- aria_code/doctor.py +813 -0
- aria_code/domain/__init__.py +0 -0
- aria_code/domain/backtest_engine.py +354 -0
- aria_code/domain/backtest_report.py +472 -0
- aria_code/domain/finance_formulas.py +389 -0
- aria_code/domain/portfolio_ledger.py +307 -0
- aria_code/domain/strategy_vault.py +421 -0
- aria_code/evals/__init__.py +37 -0
- aria_code/evals/harness.py +598 -0
- aria_code/evals/runner.py +238 -0
- aria_code/external_agent_runner.py +115 -0
- aria_code/figma_client.py +4 -0
- aria_code/file_analysis_tools.py +4 -0
- aria_code/finance_formulas.py +4 -0
- aria_code/football_data_client.py +4 -0
- aria_code/image_gen_tools.py +4 -0
- aria_code/intent_classifier.py +386 -0
- aria_code/kling_video_client.py +4 -0
- aria_code/licensing.py +151 -0
- aria_code/local_finance_tools.py +4 -0
- aria_code/local_image_provider.py +4 -0
- aria_code/local_llm_provider.py +4 -0
- aria_code/macro_tools.py +4 -0
- aria_code/markdown_pdf.py +289 -0
- aria_code/market_data_client.py +4 -0
- aria_code/mcp_client.py +639 -0
- aria_code/memory_manager.py +246 -0
- aria_code/model_capability.py +486 -0
- aria_code/notification_tools.py +4 -0
- aria_code/openai_image_client.py +4 -0
- aria_code/packages/__init__.py +23 -0
- aria_code/packages/adk_bridge/__init__.py +11 -0
- aria_code/packages/adk_bridge/code_review_tools.py +49 -0
- aria_code/packages/adk_bridge/market_tools.py +125 -0
- aria_code/packages/aria_agents/__init__.py +5 -0
- aria_code/packages/aria_agents/manifest.py +78 -0
- aria_code/packages/aria_core/__init__.py +34 -0
- aria_code/packages/aria_core/architecture.py +269 -0
- aria_code/packages/aria_core/export.py +147 -0
- aria_code/packages/aria_core/manifest.py +65 -0
- aria_code/packages/aria_core/paths.py +57 -0
- aria_code/packages/aria_core/secure_file.py +68 -0
- aria_code/packages/aria_infra/__init__.py +15 -0
- aria_code/packages/aria_infra/arthera.py +52 -0
- aria_code/packages/aria_infra/doctor.py +246 -0
- aria_code/packages/aria_infra/product.py +40 -0
- aria_code/packages/aria_mcp/__init__.py +25 -0
- aria_code/packages/aria_mcp/bridge.py +201 -0
- aria_code/packages/aria_mcp/circuit.py +82 -0
- aria_code/packages/aria_mcp/config.py +111 -0
- aria_code/packages/aria_mcp/installer.py +95 -0
- aria_code/packages/aria_mcp/server.py +1550 -0
- aria_code/packages/aria_mcp/tools.py +81 -0
- aria_code/packages/aria_sdk/__init__.py +19 -0
- aria_code/packages/aria_sdk/client.py +396 -0
- aria_code/packages/aria_sdk/providers.py +70 -0
- aria_code/packages/aria_sdk/streaming.py +79 -0
- aria_code/packages/aria_sdk/types.py +86 -0
- aria_code/packages/aria_services/__init__.py +92 -0
- aria_code/packages/aria_services/cache.py +60 -0
- aria_code/packages/aria_services/context.py +356 -0
- aria_code/packages/aria_services/context_checkpoints.py +157 -0
- aria_code/packages/aria_services/data.py +11 -0
- aria_code/packages/aria_services/provider_health.py +268 -0
- aria_code/packages/aria_services/references.py +247 -0
- aria_code/packages/aria_services/registry.py +249 -0
- aria_code/packages/aria_services/research_protocol.py +57 -0
- aria_code/packages/aria_services/research_quality.py +186 -0
- aria_code/packages/aria_services/settings.py +157 -0
- aria_code/packages/aria_services/usage.py +151 -0
- aria_code/packages/aria_skills/__init__.py +46 -0
- aria_code/packages/aria_skills/catalog.py +149 -0
- aria_code/packages/aria_skills/loader.py +455 -0
- aria_code/packages/aria_skills/registry.py +84 -0
- aria_code/packages/aria_tools/__init__.py +5 -0
- aria_code/packages/aria_tools/financial/__init__.py +8 -0
- aria_code/packages/aria_tools/financial/_paths.py +130 -0
- aria_code/packages/aria_tools/financial/compliance.py +31 -0
- aria_code/packages/aria_tools/financial/factors.py +33 -0
- aria_code/packages/aria_tools/financial/risk_tools.py +33 -0
- aria_code/packages/aria_tools/financial/strategy.py +31 -0
- aria_code/packages/aria_tools/registry.py +128 -0
- aria_code/packages/quant_engine/__init__.py +34 -0
- aria_code/packages/quant_engine/_namespace.py +62 -0
- aria_code/packages/quant_engine/backtest/__init__.py +14 -0
- aria_code/packages/quant_engine/backtest/core.py +53 -0
- aria_code/packages/quant_engine/backtest/engine.py +96 -0
- aria_code/packages/quant_engine/portfolio/__init__.py +13 -0
- aria_code/packages/quant_engine/portfolio/optimizer.py +85 -0
- aria_code/packages/quant_engine/sports/__init__.py +80 -0
- aria_code/packages/quant_engine/sports/calibrator.py +354 -0
- aria_code/packages/quant_engine/sports/dixon_coles.py +234 -0
- aria_code/packages/quant_engine/sports/elo.py +300 -0
- aria_code/packages/quant_engine/sports/form.py +188 -0
- aria_code/packages/quant_engine/sports/h2h.py +195 -0
- aria_code/packages/quant_engine/sports/ml_model.py +377 -0
- aria_code/packages/quant_engine/sports/predictor.py +312 -0
- aria_code/packages/quant_engine/sports/tracker.py +665 -0
- aria_code/packages/quant_engine/stochastic/__init__.py +35 -0
- aria_code/packages/quant_engine/stochastic/gbm_enhanced.py +195 -0
- aria_code/packages/quant_engine/stochastic/ito_calculus.py +477 -0
- aria_code/packages/quant_engine/stochastic/kelly_criterion.py +181 -0
- aria_code/packages/quant_engine/stochastic/monte_carlo_advanced.py +95 -0
- aria_code/packages/quant_engine/stochastic/options_pricing.py +576 -0
- aria_code/packages/quant_engine/stochastic/stochastic_processes.py +90 -0
- aria_code/packs/__init__.py +85 -0
- aria_code/packs/base.py +168 -0
- aria_code/packs/finance/__init__.py +250 -0
- aria_code/packs/logistics/__init__.py +169 -0
- aria_code/packs/payments/__init__.py +139 -0
- aria_code/packs/realty/__init__.py +190 -0
- aria_code/packs/registry.py +202 -0
- aria_code/packs/rules.py +90 -0
- aria_code/plan_utils.py +194 -0
- aria_code/plugin_loader.py +328 -0
- aria_code/portfolio_ledger.py +4 -0
- aria_code/preview_server.py +449 -0
- aria_code/privacy/__init__.py +5 -0
- aria_code/privacy/feedback.py +123 -0
- aria_code/project_review.py +278 -0
- aria_code/project_review_api.py +235 -0
- aria_code/project_review_server.py +18 -0
- aria_code/project_tools.py +4 -0
- aria_code/providers/__init__.py +30 -0
- aria_code/providers/llm/__init__.py +19 -0
- aria_code/providers/llm/anthropic.py +184 -0
- aria_code/providers/llm/autoconfig.py +213 -0
- aria_code/providers/llm/base.py +139 -0
- aria_code/providers/llm/custom.py +158 -0
- aria_code/providers/llm/ollama.py +129 -0
- aria_code/providers/llm/openai_compat.py +313 -0
- aria_code/providers/llm/registry.py +444 -0
- aria_code/providers/local_image_provider.py +202 -0
- aria_code/providers/local_llm_provider.py +589 -0
- aria_code/realty_data_tools.py +4 -0
- aria_code/report_exporters.py +150 -0
- aria_code/report_generator.py +1764 -0
- aria_code/runtime/__init__.py +169 -0
- aria_code/runtime/acceptance.py +481 -0
- aria_code/runtime/agent_loop.py +1520 -0
- aria_code/runtime/approval.py +57 -0
- aria_code/runtime/budget.py +232 -0
- aria_code/runtime/checkpoints.py +432 -0
- aria_code/runtime/crash_report.py +174 -0
- aria_code/runtime/events.py +113 -0
- aria_code/runtime/gateway.py +150 -0
- aria_code/runtime/lsp.py +484 -0
- aria_code/runtime/orchestrator.py +55 -0
- aria_code/runtime/repo_map.py +700 -0
- aria_code/runtime/run_state.py +127 -0
- aria_code/runtime/run_store.py +450 -0
- aria_code/runtime/self_healing.py +422 -0
- aria_code/runtime/subagent.py +547 -0
- aria_code/runtime/task_ledger.py +86 -0
- aria_code/runtime/task_orchestrator.py +90 -0
- aria_code/runtime/tool_executor.py +217 -0
- aria_code/runtime/tool_policy.py +107 -0
- aria_code/runtime/worktrees.py +215 -0
- aria_code/runway_video_client.py +4 -0
- aria_code/safety/__init__.py +23 -0
- aria_code/safety/permissions.py +306 -0
- aria_code/safety/service.py +102 -0
- aria_code/setup_wizard.py +673 -0
- aria_code/spreadsheet_tools.py +4 -0
- aria_code/strategy_vault.py +4 -0
- aria_code/tools/__init__.py +0 -0
- aria_code/tools/artifact_tools.py +252 -0
- aria_code/tools/broker_tools.py +92 -0
- aria_code/tools/build_quant_engine.py +97 -0
- aria_code/tools/code_audit_tools.py +192 -0
- aria_code/tools/computer_use_tools.py +504 -0
- aria_code/tools/data_analysis_tools.py +809 -0
- aria_code/tools/enterprise_finance_tools.py +156 -0
- aria_code/tools/extended_tools.py +240 -0
- aria_code/tools/file_analysis_tools.py +885 -0
- aria_code/tools/image_gen_tools.py +114 -0
- aria_code/tools/local_finance_tools.py +3417 -0
- aria_code/tools/logistics_tools.py +135 -0
- aria_code/tools/macro_tools.py +369 -0
- aria_code/tools/notification_tools.py +249 -0
- aria_code/tools/project_tools.py +525 -0
- aria_code/tools/realty_data_tools.py +659 -0
- aria_code/tools/spreadsheet_tools.py +388 -0
- aria_code/tools/stripe_tools.py +115 -0
- aria_code/ui/__init__.py +105 -0
- aria_code/ui/banner.py +508 -0
- aria_code/ui/completer.py +501 -0
- aria_code/ui/console.py +281 -0
- aria_code/ui/image_render.py +243 -0
- aria_code/ui/input_box.py +488 -0
- aria_code/ui/picker.py +201 -0
- aria_code/ui/render/__init__.py +11 -0
- aria_code/ui/render/finance.py +1705 -0
- aria_code/ui/render/market.py +225 -0
- aria_code/ui/render/output.py +775 -0
- aria_code/ui/render/responsive.py +75 -0
- aria_code/ui/render/team.py +354 -0
- aria_code/ui/robot.py +235 -0
- aria_code/ui/startup_dashboard.py +109 -0
- aria_code/utils/vscode_uri.py +37 -0
- aria_code/video_analysis.py +147 -0
- aria_code/video_editor.py +271 -0
- aria_code/workspace/__init__.py +6 -0
- aria_code/workspace/files.py +193 -0
- aria_code/workspace/verify.py +113 -0
- aria_code/workspace_changes.py +199 -0
- aria_code/workspace_context.py +233 -0
- aria_code/workspace_service.py +196 -0
- aria_code/workspace_tools.py +150 -0
- aria_code-0.52.0.dist-info/METADATA +237 -0
- aria_code-0.52.0.dist-info/RECORD +478 -0
- aria_code-0.52.0.dist-info/WHEEL +5 -0
- aria_code-0.52.0.dist-info/entry_points.txt +3 -0
- aria_code-0.52.0.dist-info/licenses/LICENSE +201 -0
- aria_code-0.52.0.dist-info/licenses/NOTICE +49 -0
- aria_code-0.52.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
"""
|
|
2
|
+
agents/financial/technical.py — 技术分析 Agent
|
|
3
|
+
===============================================
|
|
4
|
+
分析 K线形态、均线结构、MACD/RSI、布林带、关键价位。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
from typing import Any, Dict, List
|
|
11
|
+
|
|
12
|
+
from ..base import BaseAgent, AgentResult
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class TechnicalAgent(BaseAgent):
|
|
18
|
+
|
|
19
|
+
name = "technical"
|
|
20
|
+
description = "技术分析:图形形态、动量指标、关键价位"
|
|
21
|
+
|
|
22
|
+
_SYSTEM = (
|
|
23
|
+
"You are a quantitative technical analyst. Analyze price action, "
|
|
24
|
+
"chart patterns, momentum indicators (RSI, MACD), moving averages, "
|
|
25
|
+
"and Bollinger Bands. Provide concise, data-driven insights. "
|
|
26
|
+
"Conclude with a clear directional bias: BULLISH / NEUTRAL / BEARISH."
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
async def fetch_data(self, symbol: str) -> Dict[str, Any]:
|
|
30
|
+
data = await super().fetch_data(symbol)
|
|
31
|
+
if self.data:
|
|
32
|
+
try:
|
|
33
|
+
h = self.data.history(symbol, days=120)
|
|
34
|
+
if h and h.data is not None:
|
|
35
|
+
df = h.data
|
|
36
|
+
data["history"] = _compute_indicators(df)
|
|
37
|
+
except Exception as e:
|
|
38
|
+
logger.debug(f"[technical] fetch history {symbol}: {e}")
|
|
39
|
+
return data
|
|
40
|
+
|
|
41
|
+
async def analyze(self, symbol: str, data: Dict[str, Any]) -> AgentResult:
|
|
42
|
+
quote = data.get("quote", {})
|
|
43
|
+
history = data.get("history", {})
|
|
44
|
+
price = quote.get("price", 0)
|
|
45
|
+
|
|
46
|
+
# 提取指标用于 prompt
|
|
47
|
+
indicators = _format_indicators(history)
|
|
48
|
+
pattern = history.get("pattern", "无特殊形态")
|
|
49
|
+
|
|
50
|
+
prompt = (
|
|
51
|
+
f"Stock: {symbol} Current Price: {price}\n"
|
|
52
|
+
f"Technical Indicators:\n{indicators}\n"
|
|
53
|
+
f"Pattern: {pattern}\n\n"
|
|
54
|
+
"Provide: 1) Trend assessment 2) Key support/resistance levels "
|
|
55
|
+
"3) Signal strength 4) Short-term outlook (3-10 days) "
|
|
56
|
+
"5) Conclusion: BULLISH / NEUTRAL / BEARISH"
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
analysis = await self._call_llm(self._SYSTEM, prompt, max_tokens=600, quote=quote)
|
|
60
|
+
if not analysis:
|
|
61
|
+
analysis = _template_analysis(symbol, price, history)
|
|
62
|
+
|
|
63
|
+
signal = _extract_signal(analysis, history)
|
|
64
|
+
confidence = history.get("signal_strength", 0.5)
|
|
65
|
+
key_points = _extract_key_points(history, price)
|
|
66
|
+
|
|
67
|
+
return AgentResult(
|
|
68
|
+
agent=self.name, symbol=symbol,
|
|
69
|
+
analysis=analysis, confidence=confidence,
|
|
70
|
+
signal=signal, key_points=key_points,
|
|
71
|
+
data_used={"price": price, "indicators": history},
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
# ── 技术指标计算 ──────────────────────────────────────────────────────────────
|
|
76
|
+
|
|
77
|
+
def _compute_indicators(df) -> Dict[str, Any]:
|
|
78
|
+
"""从 OHLCV DataFrame 计算常用指标"""
|
|
79
|
+
try:
|
|
80
|
+
import numpy as np
|
|
81
|
+
close = df["close"].values if "close" in df.columns else df.iloc[:, -1].values
|
|
82
|
+
|
|
83
|
+
# MA
|
|
84
|
+
ma5 = float(np.mean(close[-5:])) if len(close) >= 5 else 0
|
|
85
|
+
ma20 = float(np.mean(close[-20:])) if len(close) >= 20 else 0
|
|
86
|
+
ma60 = float(np.mean(close[-60:])) if len(close) >= 60 else 0
|
|
87
|
+
|
|
88
|
+
# RSI(14)
|
|
89
|
+
delta = np.diff(close[-15:])
|
|
90
|
+
gains = np.where(delta > 0, delta, 0)
|
|
91
|
+
losses= np.where(delta < 0, -delta, 0)
|
|
92
|
+
rs = np.mean(gains[-14:]) / (np.mean(losses[-14:]) + 1e-9)
|
|
93
|
+
rsi = round(100 - 100 / (1 + rs), 1)
|
|
94
|
+
|
|
95
|
+
# MACD
|
|
96
|
+
ema12 = _ema(close, 12)
|
|
97
|
+
ema26 = _ema(close, 26)
|
|
98
|
+
macd = ema12 - ema26
|
|
99
|
+
signal= _ema_arr(macd[-50:] if len(macd) >= 50 else macd, 9)
|
|
100
|
+
macd_val = round(float(macd[-1]), 4)
|
|
101
|
+
signal_val= round(float(signal[-1]), 4)
|
|
102
|
+
hist_val = round(macd_val - signal_val, 4)
|
|
103
|
+
|
|
104
|
+
# 布林带
|
|
105
|
+
std20 = float(np.std(close[-20:])) if len(close) >= 20 else 0
|
|
106
|
+
bb_up = round(ma20 + 2 * std20, 2)
|
|
107
|
+
bb_lo = round(ma20 - 2 * std20, 2)
|
|
108
|
+
|
|
109
|
+
# 信号强度
|
|
110
|
+
price = float(close[-1])
|
|
111
|
+
ma_bull = price > ma5 > ma20
|
|
112
|
+
strength = 0.5
|
|
113
|
+
if ma_bull and macd_val > signal_val:
|
|
114
|
+
strength = 0.75
|
|
115
|
+
elif not ma_bull and macd_val < signal_val:
|
|
116
|
+
strength = 0.25
|
|
117
|
+
|
|
118
|
+
# 形态检测(简单)
|
|
119
|
+
pattern = _detect_simple_pattern(close)
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
"price": round(price, 2),
|
|
123
|
+
"ma5": round(ma5, 2),
|
|
124
|
+
"ma20": round(ma20, 2),
|
|
125
|
+
"ma60": round(ma60, 2),
|
|
126
|
+
"rsi": rsi,
|
|
127
|
+
"macd": macd_val,
|
|
128
|
+
"macd_signal": signal_val,
|
|
129
|
+
"macd_hist": hist_val,
|
|
130
|
+
"bb_upper": bb_up,
|
|
131
|
+
"bb_lower": bb_lo,
|
|
132
|
+
"signal_strength": strength,
|
|
133
|
+
"pattern": pattern,
|
|
134
|
+
}
|
|
135
|
+
except Exception as e:
|
|
136
|
+
logger.debug(f"compute_indicators 失败: {e}")
|
|
137
|
+
return {}
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _ema(arr, period):
|
|
141
|
+
import numpy as np
|
|
142
|
+
k = 2 / (period + 1)
|
|
143
|
+
ema = np.zeros(len(arr))
|
|
144
|
+
ema[0] = arr[0]
|
|
145
|
+
for i in range(1, len(arr)):
|
|
146
|
+
ema[i] = arr[i] * k + ema[i-1] * (1 - k)
|
|
147
|
+
return ema
|
|
148
|
+
|
|
149
|
+
def _ema_arr(arr, period):
|
|
150
|
+
return _ema(arr, period)
|
|
151
|
+
|
|
152
|
+
def _detect_simple_pattern(close) -> str:
|
|
153
|
+
if len(close) < 3:
|
|
154
|
+
return "数据不足"
|
|
155
|
+
o, h, c = close[-3], close[-2], close[-1]
|
|
156
|
+
if c > o and c > h and (c - h) > abs(c - o) * 2:
|
|
157
|
+
return "锤子线"
|
|
158
|
+
if c > o and o < close[-4] and c > close[-4]:
|
|
159
|
+
return "阳线吞噬"
|
|
160
|
+
if abs(c - o) / (max(close[-3:]) - min(close[-3:]) + 1e-9) < 0.1:
|
|
161
|
+
return "十字星"
|
|
162
|
+
return "无特殊形态"
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _format_indicators(history: Dict) -> str:
|
|
166
|
+
if not history:
|
|
167
|
+
return " (无指标数据)"
|
|
168
|
+
return (
|
|
169
|
+
f" MA5={history.get('ma5',0):.2f} MA20={history.get('ma20',0):.2f} "
|
|
170
|
+
f"MA60={history.get('ma60',0):.2f}\n"
|
|
171
|
+
f" RSI={history.get('rsi',50):.1f} "
|
|
172
|
+
f"MACD={history.get('macd',0):.4f} Signal={history.get('macd_signal',0):.4f}\n"
|
|
173
|
+
f" BB Upper={history.get('bb_upper',0):.2f} BB Lower={history.get('bb_lower',0):.2f}"
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _extract_signal(analysis: str, history: Dict) -> str:
|
|
178
|
+
import re as _re
|
|
179
|
+
text = analysis.upper()
|
|
180
|
+
|
|
181
|
+
# Unambiguous strong signals always win
|
|
182
|
+
if "STRONG_BUY" in text or "强烈买入" in text:
|
|
183
|
+
return "STRONG_BUY"
|
|
184
|
+
if "STRONG_SELL" in text or "强烈卖出" in text:
|
|
185
|
+
return "STRONG_SELL"
|
|
186
|
+
|
|
187
|
+
# The LLM prompt asks the model to conclude with BULLISH/NEUTRAL/BEARISH.
|
|
188
|
+
# Use the LAST occurrence — it's the conclusion, not mid-text context like
|
|
189
|
+
# "a potential BULLISH reversal" appearing before "currently BEARISH".
|
|
190
|
+
matches = _re.findall(r'\b(BULLISH|BEARISH|NEUTRAL)\b', text)
|
|
191
|
+
if matches:
|
|
192
|
+
last = matches[-1]
|
|
193
|
+
if last == "BULLISH":
|
|
194
|
+
return "BUY"
|
|
195
|
+
if last == "BEARISH":
|
|
196
|
+
return "SELL"
|
|
197
|
+
# NEUTRAL → fall through to quantitative check below
|
|
198
|
+
|
|
199
|
+
# Explicit Chinese directional signals
|
|
200
|
+
if "看多" in analysis:
|
|
201
|
+
return "BUY"
|
|
202
|
+
if "看空" in analysis:
|
|
203
|
+
return "SELL"
|
|
204
|
+
|
|
205
|
+
# Quantitative fallback: score RSI + MACD crossover
|
|
206
|
+
rsi = history.get("rsi", 50)
|
|
207
|
+
macd = history.get("macd", 0)
|
|
208
|
+
msig = history.get("macd_signal", 0)
|
|
209
|
+
score = 0
|
|
210
|
+
if rsi < 30: score += 2 # oversold — strong mean-reversion signal
|
|
211
|
+
elif rsi < 45: score -= 1
|
|
212
|
+
elif rsi > 70: score -= 2 # overbought
|
|
213
|
+
elif rsi > 55: score += 1
|
|
214
|
+
if macd > msig: score += 1
|
|
215
|
+
elif macd < msig: score -= 1
|
|
216
|
+
if score >= 2: return "BUY"
|
|
217
|
+
if score <= -2: return "SELL"
|
|
218
|
+
return "HOLD"
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def _extract_key_points(history: Dict, price: float) -> List[str]:
|
|
222
|
+
points = []
|
|
223
|
+
rsi = history.get("rsi", 50)
|
|
224
|
+
ma20 = history.get("ma20", 0)
|
|
225
|
+
ma60 = history.get("ma60", 0)
|
|
226
|
+
if rsi < 35:
|
|
227
|
+
points.append(f"RSI超卖({rsi:.0f}),有反弹机会")
|
|
228
|
+
elif rsi > 70:
|
|
229
|
+
points.append(f"RSI超买({rsi:.0f}),注意回调风险")
|
|
230
|
+
if ma20 > 0:
|
|
231
|
+
diff_pct = (price - ma20) / ma20 * 100
|
|
232
|
+
points.append(f"距MA20 {diff_pct:+.1f}%")
|
|
233
|
+
macd = history.get("macd", 0)
|
|
234
|
+
sig = history.get("macd_signal", 0)
|
|
235
|
+
if macd > sig and history.get("macd_hist", 0) > 0:
|
|
236
|
+
points.append("MACD金叉,多头动能")
|
|
237
|
+
elif macd < sig:
|
|
238
|
+
points.append("MACD死叉,空头压力")
|
|
239
|
+
pattern = history.get("pattern", "")
|
|
240
|
+
if pattern and pattern != "无特殊形态":
|
|
241
|
+
points.append(f"K线形态: {pattern}")
|
|
242
|
+
return points
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def _template_analysis(symbol: str, price: float, history: Dict) -> str:
|
|
246
|
+
rsi = history.get("rsi", 50)
|
|
247
|
+
ma20 = history.get("ma20", 0)
|
|
248
|
+
macd = history.get("macd", 0)
|
|
249
|
+
sig = history.get("macd_signal", 0)
|
|
250
|
+
trend = "上升趋势" if price > ma20 else "下降趋势"
|
|
251
|
+
momentum = "偏多" if macd > sig else "偏空"
|
|
252
|
+
return (
|
|
253
|
+
f"{symbol} 技术面分析(模板):\n"
|
|
254
|
+
f"• 当前价格 {price},处于 {trend}(MA20={ma20:.2f})\n"
|
|
255
|
+
f"• RSI={rsi:.0f},{'超卖' if rsi<35 else '超买' if rsi>70 else '正常区间'}\n"
|
|
256
|
+
f"• MACD 动能{momentum}\n"
|
|
257
|
+
f"• 整体技术面{'BULLISH' if momentum=='偏多' and trend=='上升趋势' else 'NEUTRAL'}"
|
|
258
|
+
)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
import json
|
|
3
|
+
from .base import BaseAgent
|
|
4
|
+
from .registry import get_registry
|
|
5
|
+
|
|
6
|
+
class OrchestratorAgent(BaseAgent):
|
|
7
|
+
name = "orchestrator"
|
|
8
|
+
description = "动态分析用户意图,从注册中心挑选最合适的专业智能体进行流转和编排。"
|
|
9
|
+
|
|
10
|
+
def get_prompt(self, context: Any) -> str:
|
|
11
|
+
registry = get_registry()
|
|
12
|
+
agents = registry.list()
|
|
13
|
+
# Remove orchestrator itself from the list to avoid recursive loop
|
|
14
|
+
agents = [a for a in agents if a["name"] != "orchestrator"]
|
|
15
|
+
|
|
16
|
+
agents_info = "\n".join([f"- {a['name']}: {a['description']}" for a in agents])
|
|
17
|
+
|
|
18
|
+
prompt = (
|
|
19
|
+
"你是一个高级架构师智能体 (Orchestrator)。\n"
|
|
20
|
+
"你的任务是分析用户的请求,并从以下可用的专业智能体库中,挑选出最合适的一个或多个智能体来解决问题。\n"
|
|
21
|
+
"可用智能体库:\n"
|
|
22
|
+
f"{agents_info}\n\n"
|
|
23
|
+
"请直接输出 JSON 格式的结果,包含两项:\n"
|
|
24
|
+
"1. 'plan': 简要的一句话解释为什么选择这些智能体。\n"
|
|
25
|
+
"2. 'agents': 一个字符串列表,包含需要调用的智能体名称(最多选 3 个)。\n"
|
|
26
|
+
"注意: 必须且只能输出合法的 JSON,不要有任何代码块包裹或前言后语。\n"
|
|
27
|
+
)
|
|
28
|
+
return prompt
|
|
29
|
+
|
|
30
|
+
def get_tools(self) -> list:
|
|
31
|
+
return []
|
|
32
|
+
|
|
33
|
+
async def execute(self, provider, model_name: str, **kwargs) -> Any:
|
|
34
|
+
context = kwargs.get("context", "")
|
|
35
|
+
# Because Orchestrator doesn't have its own tools, it just calls the LLM with a basic generate_text
|
|
36
|
+
# We need a simple non-streaming call or accumulate the stream.
|
|
37
|
+
from aria_cli import send_message
|
|
38
|
+
prompt = self.get_prompt(context)
|
|
39
|
+
|
|
40
|
+
# Or we can just use the provider directly if we have a simple function.
|
|
41
|
+
# But BaseAgent usually operates via `AgentTeam` which handles the loops.
|
|
42
|
+
# Since Orchestrator is special, it should probably be used by the CLI directly.
|
|
43
|
+
pass
|
|
44
|
+
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
"""
|
|
2
|
+
agents/portfolio_agent.py — 组合级分析 Agent
|
|
3
|
+
=============================================
|
|
4
|
+
跨标的分析:相关性矩阵、波动率、集中度风险、再平衡建议。
|
|
5
|
+
数据源:yfinance 1年日线(免费,无需 key)
|
|
6
|
+
触发:/portfolio analyze [symbols…]
|
|
7
|
+
/portfolio rebalance [symbols…]
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import logging
|
|
13
|
+
from datetime import datetime
|
|
14
|
+
from typing import Any, Dict, List, Optional
|
|
15
|
+
|
|
16
|
+
from .base import BaseAgent, AgentResult
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class PortfolioAgent(BaseAgent):
|
|
22
|
+
|
|
23
|
+
name = "portfolio"
|
|
24
|
+
description = "组合级分析 — 相关性、分散度、整体风险、再平衡建议"
|
|
25
|
+
|
|
26
|
+
_SYSTEM = (
|
|
27
|
+
"You are a portfolio risk analyst. Given price history and statistics for "
|
|
28
|
+
"a portfolio of stocks, your job is to:\n"
|
|
29
|
+
"1. Evaluate overall portfolio risk (Low / Medium / High)\n"
|
|
30
|
+
"2. Identify diversification gaps — highly correlated pairs, sector crowding\n"
|
|
31
|
+
"3. Flag concentration risk if any single position dominates\n"
|
|
32
|
+
"4. Give 2-3 actionable rebalancing suggestions (be specific: which stocks to "
|
|
33
|
+
"trim / add / replace and why)\n"
|
|
34
|
+
"5. End with a one-line verdict: HEALTHY / NEEDS_ATTENTION / HIGH_RISK\n"
|
|
35
|
+
"Be concrete. Avoid boilerplate."
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
# ── BaseAgent compatibility ───────────────────────────────────────────────
|
|
39
|
+
|
|
40
|
+
async def fetch_data(self, symbol: str) -> Dict[str, Any]:
|
|
41
|
+
symbols = [s.strip().upper() for s in symbol.split(",") if s.strip()]
|
|
42
|
+
return await self.fetch_portfolio_data(symbols)
|
|
43
|
+
|
|
44
|
+
async def analyze(self, symbol: str, data: Dict[str, Any]) -> AgentResult:
|
|
45
|
+
symbols = data.get("symbols") or [s.strip().upper() for s in symbol.split(",") if s.strip()]
|
|
46
|
+
return await self.analyze_portfolio(symbols, data)
|
|
47
|
+
|
|
48
|
+
# ── Multi-symbol interface ────────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
async def run_portfolio(
|
|
51
|
+
self, symbols: List[str], weights: Optional[Dict[str, float]] = None
|
|
52
|
+
) -> AgentResult:
|
|
53
|
+
"""Primary entry point for multi-symbol analysis.
|
|
54
|
+
|
|
55
|
+
weights: optional {symbol: dollar_cost_basis} from the user's real
|
|
56
|
+
holdings (portfolio_ledger.PortfolioLedger.get_positions()). Without
|
|
57
|
+
it, every position is silently treated as equal-weight — which used
|
|
58
|
+
to be the ONLY mode this agent supported, so a user who actually
|
|
59
|
+
holds 80% NVDA and 20% everything else got a risk verdict computed
|
|
60
|
+
on a fictional 1/n-weighted portfolio instead of their real exposure.
|
|
61
|
+
"""
|
|
62
|
+
data = await self.fetch_portfolio_data(symbols, weights=weights)
|
|
63
|
+
return await self.analyze_portfolio(symbols, data)
|
|
64
|
+
|
|
65
|
+
async def fetch_portfolio_data(
|
|
66
|
+
self, symbols: List[str], weights: Optional[Dict[str, float]] = None
|
|
67
|
+
) -> Dict[str, Any]:
|
|
68
|
+
data: Dict[str, Any] = {"symbols": symbols}
|
|
69
|
+
if len(symbols) < 2:
|
|
70
|
+
return data
|
|
71
|
+
|
|
72
|
+
price_series: Dict[str, Any] = {}
|
|
73
|
+
latest_prices: Dict[str, float] = {}
|
|
74
|
+
sector_map: Dict[str, str] = {}
|
|
75
|
+
|
|
76
|
+
for sym in symbols:
|
|
77
|
+
try:
|
|
78
|
+
import yfinance as yf
|
|
79
|
+
ticker = yf.Ticker(sym)
|
|
80
|
+
hist = ticker.history(period="1y")
|
|
81
|
+
if not hist.empty and len(hist) > 20:
|
|
82
|
+
price_series[sym] = hist["Close"]
|
|
83
|
+
latest_prices[sym] = float(hist["Close"].iloc[-1])
|
|
84
|
+
# Try to get sector info
|
|
85
|
+
try:
|
|
86
|
+
info = ticker.info or {}
|
|
87
|
+
sector = info.get("sector") or info.get("industry", "")
|
|
88
|
+
if sector:
|
|
89
|
+
sector_map[sym] = sector
|
|
90
|
+
except Exception:
|
|
91
|
+
pass
|
|
92
|
+
except Exception as e:
|
|
93
|
+
logger.debug("[portfolio] yfinance %s: %s", sym, e)
|
|
94
|
+
|
|
95
|
+
if len(price_series) < 2:
|
|
96
|
+
data["error"] = "insufficient_data"
|
|
97
|
+
return data
|
|
98
|
+
|
|
99
|
+
try:
|
|
100
|
+
import pandas as pd
|
|
101
|
+
import numpy as np
|
|
102
|
+
|
|
103
|
+
df = pd.DataFrame(price_series).dropna()
|
|
104
|
+
returns = df.pct_change().dropna()
|
|
105
|
+
|
|
106
|
+
corr = returns.corr()
|
|
107
|
+
ann_returns = (returns.mean() * 252).round(4)
|
|
108
|
+
ann_vols = (returns.std() * np.sqrt(252)).round(4)
|
|
109
|
+
cov_matrix = returns.cov() * 252
|
|
110
|
+
valid_syms = list(df.columns)
|
|
111
|
+
n = len(valid_syms)
|
|
112
|
+
if weights and any(weights.get(s, 0) > 0 for s in valid_syms):
|
|
113
|
+
raw = np.array([max(weights.get(s, 0.0), 0.0) for s in valid_syms])
|
|
114
|
+
w_vec = raw / raw.sum() if raw.sum() > 0 else np.ones(n) / n
|
|
115
|
+
weight_source = "ledger"
|
|
116
|
+
else:
|
|
117
|
+
w_vec = np.ones(n) / n
|
|
118
|
+
weight_source = "equal"
|
|
119
|
+
port_vol = float(np.sqrt(w_vec @ cov_matrix.values @ w_vec))
|
|
120
|
+
position_weights = {s: round(float(w_vec[i]), 4) for i, s in enumerate(valid_syms)}
|
|
121
|
+
|
|
122
|
+
# High correlation pairs (|r| > 0.70)
|
|
123
|
+
high_corr: List[Dict] = []
|
|
124
|
+
for i in range(len(valid_syms)):
|
|
125
|
+
for j in range(i + 1, len(valid_syms)):
|
|
126
|
+
c = float(corr.iloc[i, j])
|
|
127
|
+
if abs(c) > 0.70:
|
|
128
|
+
high_corr.append({
|
|
129
|
+
"sym1": valid_syms[i],
|
|
130
|
+
"sym2": valid_syms[j],
|
|
131
|
+
"corr": round(c, 3),
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
# Diversification ratio: weighted avg individual vol / portfolio vol.
|
|
135
|
+
# Was np.mean() (unweighted) despite the comment already claiming
|
|
136
|
+
# "weighted" — with real ledger weights now available, actually weight it.
|
|
137
|
+
avg_vol = float(sum(w_vec[i] * float(ann_vols.get(s, 0)) for i, s in enumerate(valid_syms)))
|
|
138
|
+
div_ratio = round(avg_vol / port_vol, 2) if port_vol > 0 else 1.0
|
|
139
|
+
|
|
140
|
+
# 52-week return per symbol
|
|
141
|
+
returns_1y: Dict[str, float] = {}
|
|
142
|
+
for sym in valid_syms:
|
|
143
|
+
first = float(df[sym].iloc[0])
|
|
144
|
+
last = float(df[sym].iloc[-1])
|
|
145
|
+
if first > 0:
|
|
146
|
+
returns_1y[sym] = round((last - first) / first, 4)
|
|
147
|
+
|
|
148
|
+
# Best/worst performer
|
|
149
|
+
sorted_ret = sorted(returns_1y.items(), key=lambda x: x[1], reverse=True)
|
|
150
|
+
|
|
151
|
+
data.update({
|
|
152
|
+
"valid_symbols": valid_syms,
|
|
153
|
+
"latest_prices": latest_prices,
|
|
154
|
+
"ann_returns": {s: float(v) for s, v in ann_returns.items()},
|
|
155
|
+
"ann_vols": {s: float(v) for s, v in ann_vols.items()},
|
|
156
|
+
"port_vol_ann": round(port_vol, 4),
|
|
157
|
+
"div_ratio": div_ratio,
|
|
158
|
+
"corr_matrix": corr.round(3).to_dict(),
|
|
159
|
+
"high_corr": high_corr,
|
|
160
|
+
"returns_1y": returns_1y,
|
|
161
|
+
"best_performer": sorted_ret[0] if sorted_ret else None,
|
|
162
|
+
"worst_performer": sorted_ret[-1] if sorted_ret else None,
|
|
163
|
+
"sector_map": sector_map,
|
|
164
|
+
"position_weights": position_weights,
|
|
165
|
+
"weight_source": weight_source,
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
except ImportError:
|
|
169
|
+
data["error"] = "pandas/numpy not available"
|
|
170
|
+
except Exception as e:
|
|
171
|
+
logger.warning("[portfolio] stats calculation: %s", e)
|
|
172
|
+
data["error"] = str(e)
|
|
173
|
+
|
|
174
|
+
return data
|
|
175
|
+
|
|
176
|
+
async def analyze_portfolio(
|
|
177
|
+
self, symbols: List[str], data: Dict[str, Any]
|
|
178
|
+
) -> AgentResult:
|
|
179
|
+
if len(symbols) < 2:
|
|
180
|
+
return AgentResult(
|
|
181
|
+
agent=self.name, symbol=",".join(symbols),
|
|
182
|
+
analysis="组合分析需要至少 2 个标的。",
|
|
183
|
+
confidence=0.0, signal="HOLD",
|
|
184
|
+
key_points=["标的数量不足"],
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
if data.get("error") == "insufficient_data":
|
|
188
|
+
return AgentResult(
|
|
189
|
+
agent=self.name, symbol=",".join(symbols),
|
|
190
|
+
analysis="无法获取足够的历史价格数据进行组合分析。",
|
|
191
|
+
confidence=0.3, signal="HOLD",
|
|
192
|
+
key_points=["历史数据不足(需要 >20 个交易日)"],
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
valid_syms = data.get("valid_symbols", symbols)
|
|
196
|
+
port_block = _format_portfolio_stats(data)
|
|
197
|
+
weight_desc = ("your actual position sizes" if data.get("weight_source") == "ledger"
|
|
198
|
+
else "equal weight — no real position sizes available")
|
|
199
|
+
|
|
200
|
+
prompt = (
|
|
201
|
+
f"Portfolio: {', '.join(valid_syms)} ({len(valid_syms)} positions, weighted by {weight_desc})\n\n"
|
|
202
|
+
f"{port_block}\n\n"
|
|
203
|
+
"Analyze this portfolio:\n"
|
|
204
|
+
"1. Overall risk level (Low / Medium / High)\n"
|
|
205
|
+
"2. Diversification quality — any dangerous correlations or sector crowding?\n"
|
|
206
|
+
"3. Top 2-3 concerns (be specific about which symbols)\n"
|
|
207
|
+
"4. Concrete rebalancing suggestions\n"
|
|
208
|
+
"5. End with: HEALTHY / NEEDS_ATTENTION / HIGH_RISK"
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
analysis = await self._call_llm(self._SYSTEM, prompt, max_tokens=650)
|
|
212
|
+
if not analysis:
|
|
213
|
+
analysis = _template_analysis(valid_syms, data)
|
|
214
|
+
|
|
215
|
+
verdict = _extract_verdict(analysis)
|
|
216
|
+
signal = _verdict_to_signal(verdict)
|
|
217
|
+
confidence = _estimate_confidence(data)
|
|
218
|
+
key_points = _build_key_points(data, verdict)
|
|
219
|
+
|
|
220
|
+
return AgentResult(
|
|
221
|
+
agent=self.name,
|
|
222
|
+
symbol=",".join(valid_syms),
|
|
223
|
+
analysis=analysis,
|
|
224
|
+
confidence=confidence,
|
|
225
|
+
signal=signal,
|
|
226
|
+
key_points=key_points,
|
|
227
|
+
data_used={
|
|
228
|
+
"n": len(valid_syms),
|
|
229
|
+
"port_vol_ann": data.get("port_vol_ann"),
|
|
230
|
+
"div_ratio": data.get("div_ratio"),
|
|
231
|
+
"high_corr_count": len(data.get("high_corr", [])),
|
|
232
|
+
},
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
# ── Helpers ───────────────────────────────────────────────────────────────────
|
|
237
|
+
|
|
238
|
+
def _format_portfolio_stats(d: Dict) -> str:
|
|
239
|
+
lines = []
|
|
240
|
+
|
|
241
|
+
# Returns & volatility table
|
|
242
|
+
vols = d.get("ann_vols", {})
|
|
243
|
+
rets = d.get("ann_returns", {})
|
|
244
|
+
rets_1y = d.get("returns_1y", {})
|
|
245
|
+
pweights = d.get("position_weights", {})
|
|
246
|
+
if vols:
|
|
247
|
+
lines.append("Symbol │ Weight │ Ann.Vol │ Ann.Ret │ 1Y Return")
|
|
248
|
+
lines.append("─" * 52)
|
|
249
|
+
for sym in d.get("valid_symbols", list(vols)):
|
|
250
|
+
v = vols.get(sym, 0)
|
|
251
|
+
r = rets.get(sym, 0)
|
|
252
|
+
y = rets_1y.get(sym, 0)
|
|
253
|
+
w = pweights.get(sym, 0)
|
|
254
|
+
lines.append(
|
|
255
|
+
f"{sym:<6} │ {w*100:>5.1f}% │ {v*100:>6.1f}% │ {r*100:>6.1f}% │ {y*100:>+7.1f}%"
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
port_vol = d.get("port_vol_ann", 0)
|
|
259
|
+
div_r = d.get("div_ratio", 1)
|
|
260
|
+
if port_vol:
|
|
261
|
+
lines.append(f"\nPortfolio Ann.Vol: {port_vol*100:.1f}%")
|
|
262
|
+
lines.append(f"Diversification Ratio: {div_r:.2f}x "
|
|
263
|
+
f"({'good' if div_r >= 1.3 else 'poor'})")
|
|
264
|
+
|
|
265
|
+
high_corr = d.get("high_corr", [])
|
|
266
|
+
if high_corr:
|
|
267
|
+
lines.append("\nHigh-Correlation Pairs (|r|>0.70):")
|
|
268
|
+
for p in high_corr[:5]:
|
|
269
|
+
lines.append(f" {p['sym1']} ↔ {p['sym2']}: {p['corr']:+.2f}")
|
|
270
|
+
|
|
271
|
+
sectors = d.get("sector_map", {})
|
|
272
|
+
if sectors:
|
|
273
|
+
from collections import Counter
|
|
274
|
+
sector_counts = Counter(sectors.values())
|
|
275
|
+
dominant = [(s, c) for s, c in sector_counts.items() if c >= 2]
|
|
276
|
+
if dominant:
|
|
277
|
+
lines.append("\nSector Concentration:")
|
|
278
|
+
for sec, cnt in dominant:
|
|
279
|
+
lines.append(f" {sec}: {cnt} positions")
|
|
280
|
+
|
|
281
|
+
best = d.get("best_performer")
|
|
282
|
+
worst = d.get("worst_performer")
|
|
283
|
+
if best and worst:
|
|
284
|
+
lines.append(f"\nBest 1Y: {best[0]} ({best[1]*100:+.1f}%)")
|
|
285
|
+
lines.append(f"Worst 1Y: {worst[0]} ({worst[1]*100:+.1f}%)")
|
|
286
|
+
|
|
287
|
+
return "\n".join(lines)
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def _extract_verdict(analysis: str) -> str:
|
|
291
|
+
text = analysis.upper()
|
|
292
|
+
if "HIGH_RISK" in text or "HIGH RISK" in text:
|
|
293
|
+
return "HIGH_RISK"
|
|
294
|
+
if "NEEDS_ATTENTION" in text or "NEEDS ATTENTION" in text:
|
|
295
|
+
return "NEEDS_ATTENTION"
|
|
296
|
+
if "HEALTHY" in text:
|
|
297
|
+
return "HEALTHY"
|
|
298
|
+
return "NEEDS_ATTENTION"
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def _verdict_to_signal(verdict: str) -> str:
|
|
302
|
+
return {"HEALTHY": "HOLD", "NEEDS_ATTENTION": "SELL", "HIGH_RISK": "SELL"}.get(verdict, "HOLD")
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def _estimate_confidence(d: Dict) -> float:
|
|
306
|
+
base = 0.60
|
|
307
|
+
n = len(d.get("valid_symbols", []))
|
|
308
|
+
if n >= 5:
|
|
309
|
+
base += 0.05
|
|
310
|
+
if d.get("div_ratio", 1) < 1.1:
|
|
311
|
+
base += 0.05
|
|
312
|
+
return min(round(base, 2), 0.75)
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def _build_key_points(d: Dict, verdict: str) -> List[str]:
|
|
316
|
+
pts = []
|
|
317
|
+
n = len(d.get("valid_symbols", []))
|
|
318
|
+
if d.get("weight_source") == "ledger":
|
|
319
|
+
pts.append(f"{n} 个标的,按真实持仓成本加权")
|
|
320
|
+
else:
|
|
321
|
+
pts.append(f"{n} 个标的,等权配置(无真实持仓数据,非实际风险暴露)")
|
|
322
|
+
|
|
323
|
+
port_vol = d.get("port_vol_ann", 0)
|
|
324
|
+
if port_vol:
|
|
325
|
+
risk_lv = "高风险" if port_vol > 0.30 else ("中等" if port_vol > 0.18 else "低风险")
|
|
326
|
+
pts.append(f"组合年化波动 {port_vol*100:.1f}%({risk_lv})")
|
|
327
|
+
|
|
328
|
+
div_r = d.get("div_ratio", 1)
|
|
329
|
+
if div_r < 1.1:
|
|
330
|
+
pts.append(f"分散度不足(ratio {div_r:.2f}x,标的相关性高)")
|
|
331
|
+
else:
|
|
332
|
+
pts.append(f"分散度合理(ratio {div_r:.2f}x)")
|
|
333
|
+
|
|
334
|
+
high_corr = d.get("high_corr", [])
|
|
335
|
+
if high_corr:
|
|
336
|
+
top = high_corr[0]
|
|
337
|
+
pts.append(f"最高相关对: {top['sym1']}↔{top['sym2']} ({top['corr']:+.2f})")
|
|
338
|
+
|
|
339
|
+
pts.append(f"整体评级: {verdict}")
|
|
340
|
+
return pts[:6]
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def _template_analysis(symbols: List[str], d: Dict) -> str:
|
|
344
|
+
high_corr = d.get("high_corr", [])
|
|
345
|
+
port_vol = d.get("port_vol_ann", 0)
|
|
346
|
+
div_r = d.get("div_ratio", 1)
|
|
347
|
+
|
|
348
|
+
verdict = "NEEDS_ATTENTION"
|
|
349
|
+
if not high_corr and port_vol < 0.20 and div_r >= 1.2:
|
|
350
|
+
verdict = "HEALTHY"
|
|
351
|
+
elif len(high_corr) >= 3 or port_vol > 0.30:
|
|
352
|
+
verdict = "HIGH_RISK"
|
|
353
|
+
|
|
354
|
+
lines = [f"{','.join(symbols)} 组合分析报告(模板)"]
|
|
355
|
+
lines.append(f"年化波动率: {port_vol*100:.1f}%")
|
|
356
|
+
lines.append(f"分散度系数: {div_r:.2f}x")
|
|
357
|
+
if high_corr:
|
|
358
|
+
lines.append(f"高相关对 {len(high_corr)} 组,最高: "
|
|
359
|
+
f"{high_corr[0]['sym1']}↔{high_corr[0]['sym2']} {high_corr[0]['corr']:+.2f}")
|
|
360
|
+
else:
|
|
361
|
+
lines.append("无显著高相关对,分散良好。")
|
|
362
|
+
lines.append(f"\n{verdict}")
|
|
363
|
+
return "\n".join(lines)
|