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,529 @@
|
|
|
1
|
+
"""TradingView symbol mapping helpers.
|
|
2
|
+
|
|
3
|
+
TradingView is an optional chart/alert surface. These helpers only translate
|
|
4
|
+
Aria's canonical market symbols into TradingView URLs; they do not fetch or
|
|
5
|
+
trust TradingView data for analysis.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import hashlib
|
|
10
|
+
import hmac
|
|
11
|
+
import json
|
|
12
|
+
import os
|
|
13
|
+
import sqlite3
|
|
14
|
+
import time
|
|
15
|
+
import uuid
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Any
|
|
18
|
+
from urllib.parse import quote
|
|
19
|
+
|
|
20
|
+
from aria_code.artifacts import slugify_topic, user_generated_dir
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# ── Webhook security ──────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
def _expected_webhook_secret() -> str:
|
|
26
|
+
return str(os.getenv("ARIA_WEBHOOK_SECRET", "") or "").strip()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _alert_passphrase(raw: dict[str, Any]) -> str:
|
|
30
|
+
"""Pull the shared secret a TradingView alert body may carry.
|
|
31
|
+
|
|
32
|
+
TradingView cannot send custom HMAC headers, so the documented way to secure
|
|
33
|
+
its webhooks is a passphrase embedded in the JSON body. Accept the common
|
|
34
|
+
field names.
|
|
35
|
+
"""
|
|
36
|
+
for key in ("passphrase", "secret", "token", "key", "webhook_secret"):
|
|
37
|
+
val = raw.get(key)
|
|
38
|
+
if val:
|
|
39
|
+
return str(val).strip()
|
|
40
|
+
return ""
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def verify_webhook_secret(raw: dict[str, Any]) -> bool:
|
|
44
|
+
"""True if alerts are allowed through.
|
|
45
|
+
|
|
46
|
+
When ARIA_WEBHOOK_SECRET is unset, verification is disabled (open) — the
|
|
47
|
+
deployment is assumed to be localhost-only. When set, the alert body MUST
|
|
48
|
+
carry a matching passphrase (constant-time compare) or it is rejected.
|
|
49
|
+
"""
|
|
50
|
+
expected = _expected_webhook_secret()
|
|
51
|
+
if not expected:
|
|
52
|
+
return True
|
|
53
|
+
provided = _alert_passphrase(raw)
|
|
54
|
+
if not provided:
|
|
55
|
+
return False
|
|
56
|
+
return hmac.compare_digest(provided, expected)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def verify_webhook_hmac(raw_body: bytes | str, signature: str, secret: str | None = None) -> bool:
|
|
60
|
+
"""Constant-time HMAC-SHA256 check for a fronting proxy/signer.
|
|
61
|
+
|
|
62
|
+
For deployments that put a signer in front of the daemon (e.g. a serverless
|
|
63
|
+
relay that can compute HMAC, unlike TradingView itself). Compares the hex
|
|
64
|
+
digest of ``raw_body`` against ``signature`` (optional ``sha256=`` prefix).
|
|
65
|
+
"""
|
|
66
|
+
key = (secret if secret is not None else _expected_webhook_secret())
|
|
67
|
+
if not key:
|
|
68
|
+
return False
|
|
69
|
+
if isinstance(raw_body, str):
|
|
70
|
+
raw_body = raw_body.encode("utf-8")
|
|
71
|
+
sig = str(signature or "").strip()
|
|
72
|
+
if sig.startswith("sha256="):
|
|
73
|
+
sig = sig[7:]
|
|
74
|
+
digest = hmac.new(key.encode("utf-8"), raw_body, hashlib.sha256).hexdigest()
|
|
75
|
+
return hmac.compare_digest(digest, sig)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _alert_dedup_key(alert: dict[str, Any]) -> str:
|
|
79
|
+
"""Stable key for collapsing duplicate alerts (TV retries the same bar)."""
|
|
80
|
+
basis = "|".join(str(alert.get(k) or "") for k in ("symbol", "action", "time", "price"))
|
|
81
|
+
return hashlib.sha1(basis.encode("utf-8")).hexdigest()[:16]
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
_INDEX_SYMBOLS = {
|
|
85
|
+
"^GSPC": "SP:SPX",
|
|
86
|
+
"^IXIC": "NASDAQ:IXIC",
|
|
87
|
+
"^DJI": "DJ:DJI",
|
|
88
|
+
"^RUT": "RUSSELL:RUT",
|
|
89
|
+
"^VIX": "CBOE:VIX",
|
|
90
|
+
"^HSI": "HKEX:HSI",
|
|
91
|
+
"^HSTECH": "HKEX:HSTECH",
|
|
92
|
+
"^N225": "TVC:NI225",
|
|
93
|
+
"^FTSE": "TVC:UKX",
|
|
94
|
+
"^GDAXI": "XETR:DAX",
|
|
95
|
+
"^FCHI": "EURONEXT:PX1",
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
_FUTURES_SYMBOLS = {
|
|
99
|
+
"GC=F": "COMEX:GC1!",
|
|
100
|
+
"SI=F": "COMEX:SI1!",
|
|
101
|
+
"CL=F": "NYMEX:CL1!",
|
|
102
|
+
"BZ=F": "NYMEX:BRN1!",
|
|
103
|
+
"HG=F": "COMEX:HG1!",
|
|
104
|
+
"NG=F": "NYMEX:NG1!",
|
|
105
|
+
"ZC=F": "CBOT:ZC1!",
|
|
106
|
+
"ZS=F": "CBOT:ZS1!",
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
_FX_SYMBOLS = {
|
|
110
|
+
"CNY=X": "FX_IDC:USDCNY",
|
|
111
|
+
"EURUSD=X": "FX:EURUSD",
|
|
112
|
+
"GBPUSD=X": "FX:GBPUSD",
|
|
113
|
+
"JPY=X": "FX:USDJPY",
|
|
114
|
+
"DX-Y.NYB": "TVC:DXY",
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def tradingview_symbol(symbol: str) -> str:
|
|
119
|
+
"""Map an Aria canonical symbol to a TradingView symbol."""
|
|
120
|
+
s = (symbol or "").strip().upper()
|
|
121
|
+
if not s:
|
|
122
|
+
return ""
|
|
123
|
+
if s in _INDEX_SYMBOLS:
|
|
124
|
+
return _INDEX_SYMBOLS[s]
|
|
125
|
+
if s in _FUTURES_SYMBOLS:
|
|
126
|
+
return _FUTURES_SYMBOLS[s]
|
|
127
|
+
if s in _FX_SYMBOLS:
|
|
128
|
+
return _FX_SYMBOLS[s]
|
|
129
|
+
if s.endswith("-USD"):
|
|
130
|
+
return f"BINANCE:{s[:-4]}USDT"
|
|
131
|
+
if s.endswith(".HK"):
|
|
132
|
+
digits = "".join(ch for ch in s[:-3] if ch.isdigit()).lstrip("0") or s[:-3]
|
|
133
|
+
return f"HKEX:{digits}"
|
|
134
|
+
if s.endswith(".SS") or (s.isdigit() and len(s) == 6 and s.startswith(("6", "9"))):
|
|
135
|
+
return f"SSE:{s[:6]}"
|
|
136
|
+
if s.endswith(".SZ") or (s.isdigit() and len(s) == 6):
|
|
137
|
+
return f"SZSE:{s[:6]}"
|
|
138
|
+
if "." in s:
|
|
139
|
+
base, suffix = s.rsplit(".", 1)
|
|
140
|
+
exchange = {
|
|
141
|
+
"DE": "XETR",
|
|
142
|
+
"PA": "EURONEXT",
|
|
143
|
+
"AS": "EURONEXT",
|
|
144
|
+
"MI": "MIL",
|
|
145
|
+
"MC": "BME",
|
|
146
|
+
"L": "LSE",
|
|
147
|
+
"TO": "TSX",
|
|
148
|
+
}.get(suffix, suffix)
|
|
149
|
+
return f"{exchange}:{base}"
|
|
150
|
+
return f"NASDAQ:{s}"
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def tradingview_url(symbol: str, *, interval: str | None = None) -> str:
|
|
154
|
+
tv_symbol = tradingview_symbol(symbol)
|
|
155
|
+
if not tv_symbol:
|
|
156
|
+
return ""
|
|
157
|
+
url = f"https://www.tradingview.com/chart/?symbol={quote(tv_symbol, safe='')}"
|
|
158
|
+
if interval:
|
|
159
|
+
url += f"&interval={quote(str(interval), safe='')}"
|
|
160
|
+
return url
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def parse_tradingview_alert(payload: dict[str, Any] | str) -> dict[str, Any]:
|
|
164
|
+
"""Normalize a TradingView webhook payload.
|
|
165
|
+
|
|
166
|
+
TradingView alert bodies are user-defined, so accept common JSON fields and
|
|
167
|
+
a compact text fallback such as "NVDA buy".
|
|
168
|
+
"""
|
|
169
|
+
if isinstance(payload, str):
|
|
170
|
+
raw = payload.strip()
|
|
171
|
+
try:
|
|
172
|
+
payload = json.loads(raw)
|
|
173
|
+
except Exception:
|
|
174
|
+
parts = raw.replace(",", " ").split()
|
|
175
|
+
payload = {
|
|
176
|
+
"symbol": parts[0] if parts else "",
|
|
177
|
+
"action": parts[1] if len(parts) > 1 else "",
|
|
178
|
+
"message": raw,
|
|
179
|
+
}
|
|
180
|
+
data = dict(payload or {})
|
|
181
|
+
raw_symbol = (
|
|
182
|
+
data.get("symbol")
|
|
183
|
+
or data.get("ticker")
|
|
184
|
+
or data.get("tv_symbol")
|
|
185
|
+
or data.get("syminfo.ticker")
|
|
186
|
+
or data.get("syminfo.tickerid")
|
|
187
|
+
or data.get("s")
|
|
188
|
+
or ""
|
|
189
|
+
)
|
|
190
|
+
strategy = data.get("strategy") if isinstance(data.get("strategy"), dict) else {}
|
|
191
|
+
action = (
|
|
192
|
+
data.get("action")
|
|
193
|
+
or data.get("side")
|
|
194
|
+
or data.get("signal")
|
|
195
|
+
or data.get("order_action")
|
|
196
|
+
or data.get("strategy.order.action")
|
|
197
|
+
or strategy.get("order_action")
|
|
198
|
+
or ""
|
|
199
|
+
)
|
|
200
|
+
symbol = normalize_tradingview_alert_symbol(str(raw_symbol))
|
|
201
|
+
action_norm = str(action or "").strip().upper()
|
|
202
|
+
if action_norm in {"LONG", "BUY", "B"}:
|
|
203
|
+
action_norm = "BUY"
|
|
204
|
+
elif action_norm in {"SHORT", "SELL", "S"}:
|
|
205
|
+
action_norm = "SELL"
|
|
206
|
+
elif action_norm in {"EXIT", "CLOSE", "FLAT"}:
|
|
207
|
+
action_norm = "EXIT"
|
|
208
|
+
elif not action_norm:
|
|
209
|
+
action_norm = "ALERT"
|
|
210
|
+
return {
|
|
211
|
+
"symbol": symbol,
|
|
212
|
+
"action": action_norm,
|
|
213
|
+
"price": data.get("price") or data.get("close") or data.get("last") or data.get("strategy.order.price"),
|
|
214
|
+
"time": data.get("time") or data.get("timestamp") or data.get("t"),
|
|
215
|
+
"message": data.get("message") or data.get("alert_message") or "",
|
|
216
|
+
"channels": data.get("channels"),
|
|
217
|
+
"raw": data,
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def _as_float_or_none(value: Any) -> float | None:
|
|
222
|
+
try:
|
|
223
|
+
if value in (None, ""):
|
|
224
|
+
return None
|
|
225
|
+
out = float(value)
|
|
226
|
+
return out if out == out else None
|
|
227
|
+
except Exception:
|
|
228
|
+
return None
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def _first_present(raw: dict[str, Any], names: tuple[str, ...]) -> Any:
|
|
232
|
+
for name in names:
|
|
233
|
+
if name in raw and raw.get(name) not in (None, ""):
|
|
234
|
+
return raw.get(name)
|
|
235
|
+
return None
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _strategy_field(raw: dict[str, Any], names: tuple[str, ...]) -> Any:
|
|
239
|
+
strategy = raw.get("strategy")
|
|
240
|
+
if not isinstance(strategy, dict):
|
|
241
|
+
return None
|
|
242
|
+
return _first_present(strategy, names)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def _current_position_quantity(broker: Any, symbol: str) -> float:
|
|
246
|
+
sym = str(symbol or "").upper()
|
|
247
|
+
try:
|
|
248
|
+
for pos in broker.positions() or []:
|
|
249
|
+
if str(getattr(pos, "symbol", "") or "").upper() == sym:
|
|
250
|
+
return float(getattr(pos, "quantity", 0.0) or 0.0)
|
|
251
|
+
except Exception:
|
|
252
|
+
pass
|
|
253
|
+
return 0.0
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def _ensure_tradingview_broker(broker_id: str | None = None) -> Any:
|
|
257
|
+
"""Connect a broker for TradingView alert previews.
|
|
258
|
+
|
|
259
|
+
If no broker is configured, create a local paper account. This keeps
|
|
260
|
+
webhooks useful while never defaulting to live execution.
|
|
261
|
+
"""
|
|
262
|
+
from brokers.config import add_broker_config, get_broker_config, get_default_broker_config, set_default_broker
|
|
263
|
+
from brokers.registry import BrokerRegistry
|
|
264
|
+
|
|
265
|
+
selected_id = str(broker_id or "").strip()
|
|
266
|
+
if not selected_id:
|
|
267
|
+
selected_id = str((get_default_broker_config() or {}).get("id") or "")
|
|
268
|
+
|
|
269
|
+
if not selected_id:
|
|
270
|
+
selected_id = "paper_main"
|
|
271
|
+
if not get_broker_config(selected_id):
|
|
272
|
+
add_broker_config({
|
|
273
|
+
"id": selected_id,
|
|
274
|
+
"type": "paper",
|
|
275
|
+
"label": "Aria TradingView 仿盘",
|
|
276
|
+
"mode": "paper",
|
|
277
|
+
"starting_cash": 100000,
|
|
278
|
+
"currency": "USD",
|
|
279
|
+
"default": True,
|
|
280
|
+
})
|
|
281
|
+
set_default_broker(selected_id)
|
|
282
|
+
|
|
283
|
+
registry = BrokerRegistry()
|
|
284
|
+
return registry.connect(selected_id)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def build_tradingview_order_preview(
|
|
288
|
+
payload: dict[str, Any] | str,
|
|
289
|
+
*,
|
|
290
|
+
broker: Any | None = None,
|
|
291
|
+
broker_id: str | None = None,
|
|
292
|
+
) -> dict[str, Any]:
|
|
293
|
+
"""Turn a TradingView alert into an Aria trade preview.
|
|
294
|
+
|
|
295
|
+
The function never executes an order. It only creates a `preview_id` through
|
|
296
|
+
the broker trading service, so live trading still requires manual
|
|
297
|
+
confirmation through `/trade confirm <preview_id>` or `broker_order`.
|
|
298
|
+
"""
|
|
299
|
+
alert = parse_tradingview_alert(payload)
|
|
300
|
+
symbol = str(alert.get("symbol") or "").upper()
|
|
301
|
+
action = str(alert.get("action") or "ALERT").upper()
|
|
302
|
+
raw = dict(alert.get("raw") or {})
|
|
303
|
+
if not symbol:
|
|
304
|
+
return {"success": False, "error": "symbol is required", "alert": alert}
|
|
305
|
+
if action not in {"BUY", "SELL", "EXIT"}:
|
|
306
|
+
return {
|
|
307
|
+
"success": True,
|
|
308
|
+
"trade_preview_created": False,
|
|
309
|
+
"reason": "non_trade_alert",
|
|
310
|
+
"alert": alert,
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
selected_broker_id = broker_id or raw.get("broker_id") or raw.get("account_id")
|
|
314
|
+
if broker is None:
|
|
315
|
+
try:
|
|
316
|
+
broker = _ensure_tradingview_broker(str(selected_broker_id or "") or None)
|
|
317
|
+
except Exception as exc:
|
|
318
|
+
return {"success": False, "error": f"broker connect failed: {exc}", "alert": alert}
|
|
319
|
+
|
|
320
|
+
qty = _as_float_or_none(
|
|
321
|
+
_first_present(raw, (
|
|
322
|
+
"quantity",
|
|
323
|
+
"qty",
|
|
324
|
+
"shares",
|
|
325
|
+
"contracts",
|
|
326
|
+
"order_size",
|
|
327
|
+
"strategy.order.contracts",
|
|
328
|
+
"strategy.position_size",
|
|
329
|
+
))
|
|
330
|
+
or _strategy_field(raw, ("order_contracts", "position_size"))
|
|
331
|
+
)
|
|
332
|
+
target_weight = _as_float_or_none(_first_present(raw, ("target_weight", "weight", "target")))
|
|
333
|
+
price = _as_float_or_none(alert.get("price"))
|
|
334
|
+
order_type = str(raw.get("order_type") or raw.get("type") or "limit").lower()
|
|
335
|
+
if order_type not in {"limit", "market"}:
|
|
336
|
+
order_type = "limit"
|
|
337
|
+
|
|
338
|
+
side = "buy" if action == "BUY" else "sell"
|
|
339
|
+
if side == "sell" and qty is None:
|
|
340
|
+
qty = _current_position_quantity(broker, symbol)
|
|
341
|
+
if qty <= 0:
|
|
342
|
+
return {
|
|
343
|
+
"success": True,
|
|
344
|
+
"trade_preview_created": False,
|
|
345
|
+
"reason": "no_position_to_exit" if action == "EXIT" else "missing_quantity",
|
|
346
|
+
"alert": alert,
|
|
347
|
+
"broker_id": getattr(broker, "broker_id", ""),
|
|
348
|
+
"broker_label": getattr(broker, "label", ""),
|
|
349
|
+
}
|
|
350
|
+
if side == "buy" and qty is None and target_weight is None:
|
|
351
|
+
return {
|
|
352
|
+
"success": True,
|
|
353
|
+
"trade_preview_created": False,
|
|
354
|
+
"reason": "missing_quantity",
|
|
355
|
+
"alert": alert,
|
|
356
|
+
"hint": "TradingView BUY alerts need quantity/qty or target_weight to create an order preview.",
|
|
357
|
+
"broker_id": getattr(broker, "broker_id", ""),
|
|
358
|
+
"broker_label": getattr(broker, "label", ""),
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
from brokers import OrderIntent, build_order_preview
|
|
362
|
+
|
|
363
|
+
preview = build_order_preview(
|
|
364
|
+
broker,
|
|
365
|
+
OrderIntent(
|
|
366
|
+
symbol=symbol,
|
|
367
|
+
side=side,
|
|
368
|
+
quantity=qty,
|
|
369
|
+
price=price,
|
|
370
|
+
order_type=order_type,
|
|
371
|
+
target_weight=target_weight,
|
|
372
|
+
source="tradingview_alert",
|
|
373
|
+
user_message=str(alert.get("message") or ""),
|
|
374
|
+
metadata={
|
|
375
|
+
"tradingview_action": action,
|
|
376
|
+
"tradingview_time": alert.get("time"),
|
|
377
|
+
},
|
|
378
|
+
),
|
|
379
|
+
)
|
|
380
|
+
return {
|
|
381
|
+
"success": True,
|
|
382
|
+
"trade_preview_created": True,
|
|
383
|
+
"alert": alert,
|
|
384
|
+
"preview_id": preview.get("preview_id"),
|
|
385
|
+
"trade_preview": preview,
|
|
386
|
+
"can_execute": preview.get("can_execute"),
|
|
387
|
+
"mode": preview.get("mode"),
|
|
388
|
+
"broker_id": preview.get("broker_id"),
|
|
389
|
+
"broker_label": preview.get("broker_label"),
|
|
390
|
+
"execution_blockers": preview.get("execution_blockers") or [],
|
|
391
|
+
"confirm_command": f"/trade confirm {preview.get('preview_id')}",
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def normalize_tradingview_alert_symbol(symbol: str) -> str:
|
|
396
|
+
"""Convert common TradingView symbols back to Aria/yfinance-style symbols."""
|
|
397
|
+
raw = str(symbol or "").strip().upper()
|
|
398
|
+
if not raw:
|
|
399
|
+
return ""
|
|
400
|
+
if ":" in raw:
|
|
401
|
+
exchange, ticker = raw.split(":", 1)
|
|
402
|
+
ticker = ticker.strip()
|
|
403
|
+
if exchange in {"NASDAQ", "NYSE", "AMEX"}:
|
|
404
|
+
return ticker
|
|
405
|
+
if exchange == "HKEX":
|
|
406
|
+
return ticker.zfill(4) + ".HK"
|
|
407
|
+
if exchange == "SSE":
|
|
408
|
+
return ticker.zfill(6)
|
|
409
|
+
if exchange == "SZSE":
|
|
410
|
+
return ticker.zfill(6)
|
|
411
|
+
if exchange in {"BINANCE", "BYBIT", "OKX"} and ticker.endswith("USDT"):
|
|
412
|
+
return ticker[:-4] + "-USD"
|
|
413
|
+
if exchange in {"COMEX", "NYMEX", "CBOT"} and ticker.endswith("1!"):
|
|
414
|
+
reverse = {value: key for key, value in _FUTURES_SYMBOLS.items()}
|
|
415
|
+
return reverse.get(raw, ticker)
|
|
416
|
+
if exchange == "FX":
|
|
417
|
+
reverse = {value: key for key, value in _FX_SYMBOLS.items()}
|
|
418
|
+
return reverse.get(raw, ticker + "=X")
|
|
419
|
+
return ticker
|
|
420
|
+
if raw.endswith("USDT"):
|
|
421
|
+
return raw[:-4] + "-USD"
|
|
422
|
+
return raw
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
def enqueue_tradingview_alert(
|
|
426
|
+
payload: dict[str, Any] | str,
|
|
427
|
+
*,
|
|
428
|
+
db_path: str | Path | None = None,
|
|
429
|
+
dedup_window_seconds: int = 90,
|
|
430
|
+
) -> dict[str, Any]:
|
|
431
|
+
"""Queue a TradingView alert for the daemon webhook executor.
|
|
432
|
+
|
|
433
|
+
Security & integrity:
|
|
434
|
+
* If ARIA_WEBHOOK_SECRET is set, the alert body must carry a matching
|
|
435
|
+
passphrase, else it is rejected (prevents injected buy/sell alerts).
|
|
436
|
+
* Duplicate alerts (TradingView retries the same bar) are collapsed within
|
|
437
|
+
``dedup_window_seconds`` so they can't create duplicate order drafts.
|
|
438
|
+
"""
|
|
439
|
+
alert = parse_tradingview_alert(payload)
|
|
440
|
+
if not alert["symbol"]:
|
|
441
|
+
return {"success": False, "error": "symbol is required", "alert": alert}
|
|
442
|
+
|
|
443
|
+
if not verify_webhook_secret(alert.get("raw") or {}):
|
|
444
|
+
return {"success": False, "error": "unauthorized: missing/invalid webhook secret",
|
|
445
|
+
"rejected": True, "alert": {"symbol": alert.get("symbol"), "action": alert.get("action")}}
|
|
446
|
+
|
|
447
|
+
dedup_key = _alert_dedup_key(alert)
|
|
448
|
+
path = Path(db_path).expanduser() if db_path else Path.home() / ".aria" / "daemon.db"
|
|
449
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
450
|
+
job_id = "tv_" + uuid.uuid4().hex[:12]
|
|
451
|
+
with sqlite3.connect(path) as conn:
|
|
452
|
+
conn.execute(
|
|
453
|
+
"""
|
|
454
|
+
CREATE TABLE IF NOT EXISTS webhook_jobs (
|
|
455
|
+
id TEXT PRIMARY KEY,
|
|
456
|
+
command TEXT NOT NULL,
|
|
457
|
+
payload TEXT DEFAULT '{}',
|
|
458
|
+
source TEXT DEFAULT 'external',
|
|
459
|
+
status TEXT DEFAULT 'pending',
|
|
460
|
+
result TEXT,
|
|
461
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
462
|
+
started_at TEXT,
|
|
463
|
+
done_at TEXT,
|
|
464
|
+
dedup_key TEXT
|
|
465
|
+
)
|
|
466
|
+
"""
|
|
467
|
+
)
|
|
468
|
+
# Older DBs may predate the dedup_key column — add it on the fly.
|
|
469
|
+
cols = {row[1] for row in conn.execute("PRAGMA table_info(webhook_jobs)")}
|
|
470
|
+
if "dedup_key" not in cols:
|
|
471
|
+
conn.execute("ALTER TABLE webhook_jobs ADD COLUMN dedup_key TEXT")
|
|
472
|
+
existing = conn.execute(
|
|
473
|
+
"SELECT id FROM webhook_jobs WHERE dedup_key = ? "
|
|
474
|
+
"AND created_at >= datetime('now', ?) ORDER BY created_at DESC LIMIT 1",
|
|
475
|
+
(dedup_key, f"-{int(dedup_window_seconds)} seconds"),
|
|
476
|
+
).fetchone()
|
|
477
|
+
if existing:
|
|
478
|
+
return {"success": True, "job_id": existing[0], "deduped": True, "alert": alert}
|
|
479
|
+
conn.execute(
|
|
480
|
+
"INSERT INTO webhook_jobs(id, command, payload, source, status, dedup_key) "
|
|
481
|
+
"VALUES (?, ?, ?, ?, 'pending', ?)",
|
|
482
|
+
(job_id, "tradingview_alert", json.dumps(alert, ensure_ascii=False), "tradingview", dedup_key),
|
|
483
|
+
)
|
|
484
|
+
conn.commit()
|
|
485
|
+
return {"success": True, "job_id": job_id, "alert": alert}
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
def generate_pine_strategy(symbol: str, *, name: str | None = None) -> str:
|
|
489
|
+
"""Generate a TradingView Pine Script strategy template."""
|
|
490
|
+
sym = str(symbol or "SYMBOL").strip().upper()
|
|
491
|
+
title = name or f"Aria {sym} EMA RSI Strategy"
|
|
492
|
+
return f"""//@version=5
|
|
493
|
+
strategy("{title}", overlay=true, initial_capital=100000, commission_type=strategy.commission.percent, commission_value=0.05)
|
|
494
|
+
|
|
495
|
+
fastLen = input.int(20, "Fast EMA", minval=1)
|
|
496
|
+
slowLen = input.int(60, "Slow EMA", minval=1)
|
|
497
|
+
rsiLen = input.int(14, "RSI Length", minval=1)
|
|
498
|
+
rsiBuy = input.float(55, "RSI buy threshold")
|
|
499
|
+
rsiSell = input.float(45, "RSI sell threshold")
|
|
500
|
+
|
|
501
|
+
fast = ta.ema(close, fastLen)
|
|
502
|
+
slow = ta.ema(close, slowLen)
|
|
503
|
+
rsi = ta.rsi(close, rsiLen)
|
|
504
|
+
|
|
505
|
+
longCondition = ta.crossover(fast, slow) and rsi > rsiBuy
|
|
506
|
+
exitCondition = ta.crossunder(fast, slow) or rsi < rsiSell
|
|
507
|
+
|
|
508
|
+
if longCondition
|
|
509
|
+
strategy.entry("Aria Long", strategy.long)
|
|
510
|
+
|
|
511
|
+
if exitCondition
|
|
512
|
+
strategy.close("Aria Long")
|
|
513
|
+
|
|
514
|
+
plot(fast, "Fast EMA", color=color.teal)
|
|
515
|
+
plot(slow, "Slow EMA", color=color.orange)
|
|
516
|
+
alertcondition(longCondition, "Aria BUY {sym}", "{{\\"symbol\\":\\"{sym}\\",\\"action\\":\\"BUY\\",\\"quantity\\":1,\\"price\\":{{{{close}}}}}}")
|
|
517
|
+
alertcondition(exitCondition, "Aria EXIT {sym}", "{{\\"symbol\\":\\"{sym}\\",\\"action\\":\\"EXIT\\",\\"price\\":{{{{close}}}}}}")
|
|
518
|
+
"""
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
def export_pine_strategy(symbol: str, *, name: str | None = None, output_dir: str | Path | None = None) -> Path:
|
|
522
|
+
"""Write a Pine Script strategy file and return its path."""
|
|
523
|
+
sym = str(symbol or "SYMBOL").strip().upper()
|
|
524
|
+
directory = Path(output_dir).expanduser() if output_dir else user_generated_dir()
|
|
525
|
+
directory.mkdir(parents=True, exist_ok=True)
|
|
526
|
+
fname = f"{int(time.time())}_{slugify_topic(sym, 'symbol')}_strategy.pine"
|
|
527
|
+
path = directory / fname
|
|
528
|
+
path.write_text(generate_pine_strategy(sym, name=name), encoding="utf-8")
|
|
529
|
+
return path
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""Per-turn planning DECISIONS for send_message (pure, testable).
|
|
2
|
+
|
|
3
|
+
Extracted from ``aria_cli.send_message`` — the first slice of the file's
|
|
4
|
+
1300+ line stateful method to be pulled out. This module holds only the
|
|
5
|
+
*decision* logic (round budget, whether to trigger AI decomposition), which
|
|
6
|
+
depends solely on the user's message text and has no I/O, no ``self`` state,
|
|
7
|
+
and no side effects. It is safe to unit-test in isolation and safe to extract
|
|
8
|
+
without any behavior change, unlike the surrounding streaming/tool-loop code.
|
|
9
|
+
|
|
10
|
+
Mirrors send_message's inline logic exactly:
|
|
11
|
+
• task complexity → message > 120 chars, or contains a multi-step keyword
|
|
12
|
+
• round budget → complex tasks get a larger soft/hard round budget
|
|
13
|
+
• decomposition → long (>150 char), complex, non-slash-command messages
|
|
14
|
+
trigger an upfront AI decomposition pass
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
TASK_COMPLEXITY_LENGTH_THRESHOLD = 120
|
|
20
|
+
|
|
21
|
+
TASK_COMPLEXITY_KEYWORDS = (
|
|
22
|
+
"然后", "接着", "最后", "步骤", "并且", "同时",
|
|
23
|
+
"and then", "step", "finally", "after that", "next",
|
|
24
|
+
"完整", "全面", "详细", "系统", "comprehensive", "complete",
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
DECOMP_THRESHOLD = 150 # chars
|
|
28
|
+
|
|
29
|
+
SOFT_ROUND_BUDGET_SIMPLE = 16
|
|
30
|
+
SOFT_ROUND_BUDGET_COMPLEX = 30
|
|
31
|
+
HARD_ROUND_EXTENSION_SIMPLE = 10
|
|
32
|
+
HARD_ROUND_EXTENSION_COMPLEX = 20
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def is_complex_task(message: str) -> bool:
|
|
36
|
+
"""A message is 'complex' if it's long or names a multi-step structure."""
|
|
37
|
+
return (
|
|
38
|
+
len(message) > TASK_COMPLEXITY_LENGTH_THRESHOLD
|
|
39
|
+
or any(kw in message for kw in TASK_COMPLEXITY_KEYWORDS)
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def round_budget_for(is_complex: bool) -> tuple[int, int]:
|
|
44
|
+
"""-> (max_rounds, hard_max_rounds). Complex tasks get a larger budget
|
|
45
|
+
on both the soft limit (where we start checking for real progress) and
|
|
46
|
+
the hard extension (how far a still-progressing task can run past it)."""
|
|
47
|
+
max_rounds = SOFT_ROUND_BUDGET_COMPLEX if is_complex else SOFT_ROUND_BUDGET_SIMPLE
|
|
48
|
+
hard_max_rounds = max_rounds + (
|
|
49
|
+
HARD_ROUND_EXTENSION_COMPLEX if is_complex else HARD_ROUND_EXTENSION_SIMPLE
|
|
50
|
+
)
|
|
51
|
+
return max_rounds, hard_max_rounds
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def should_decompose(message: str, is_complex: bool) -> bool:
|
|
55
|
+
"""Trigger an upfront AI decomposition pass for long, complex, non-slash
|
|
56
|
+
messages. Slash commands (``/...``) and bang commands (``!...``) are never
|
|
57
|
+
decomposed — they're already a single deterministic action."""
|
|
58
|
+
return (
|
|
59
|
+
len(message) > DECOMP_THRESHOLD
|
|
60
|
+
and is_complex
|
|
61
|
+
and not any(message.startswith(p) for p in ("/", "!"))
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
# ── post-turn context pressure ───────────────────────────────────────────────
|
|
66
|
+
# send_message had three separate places that decide whether history is too hot
|
|
67
|
+
# to carry into the next turn, and they disagreed. The usage-driven one (real
|
|
68
|
+
# provider prompt_tokens) and the pre-turn one both honour auto_compact_context
|
|
69
|
+
# and auto_compact_threshold; the post-turn char-estimate one honoured neither
|
|
70
|
+
# and hardcoded 90% compact / 70% warn. So `/config set
|
|
71
|
+
# auto_compact_context=false` did not turn auto-compaction off — /status said
|
|
72
|
+
# "auto compact off" while the third path still compacted — and a threshold of
|
|
73
|
+
# 0.95 was capped at the hidden 0.90.
|
|
74
|
+
#
|
|
75
|
+
# Warning is deliberately NOT gated on the toggle. Turning auto-compaction off
|
|
76
|
+
# means "do not rewrite my history", not "stop telling me how full it is" —
|
|
77
|
+
# and it is precisely the user who disabled compaction who needs to be told to
|
|
78
|
+
# run /compact.
|
|
79
|
+
|
|
80
|
+
CONTEXT_WARN_FRACTION = 0.70
|
|
81
|
+
CHARS_PER_TOKEN_ESTIMATE = 3
|
|
82
|
+
DEFAULT_COMPACT_THRESHOLD = 0.78
|
|
83
|
+
MIN_COMPACT_THRESHOLD = 0.50
|
|
84
|
+
MAX_COMPACT_THRESHOLD = 0.95
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def estimate_context_tokens(conversation: list) -> int:
|
|
88
|
+
"""Rough token count for a conversation, by characters."""
|
|
89
|
+
return sum(
|
|
90
|
+
len(m.get("content", "") or "") for m in (conversation or [])
|
|
91
|
+
) // CHARS_PER_TOKEN_ESTIMATE
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def clamp_compact_threshold(value) -> float:
|
|
95
|
+
"""Config threshold as a fraction, clamped to the range /config accepts."""
|
|
96
|
+
try:
|
|
97
|
+
threshold = float(value)
|
|
98
|
+
except (TypeError, ValueError):
|
|
99
|
+
return DEFAULT_COMPACT_THRESHOLD
|
|
100
|
+
return max(MIN_COMPACT_THRESHOLD, min(MAX_COMPACT_THRESHOLD, threshold))
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def post_turn_context_decision(
|
|
104
|
+
used_tokens: int,
|
|
105
|
+
max_tokens: int,
|
|
106
|
+
*,
|
|
107
|
+
auto_compact_enabled: bool = True,
|
|
108
|
+
threshold=DEFAULT_COMPACT_THRESHOLD,
|
|
109
|
+
already_compacted: bool = False,
|
|
110
|
+
) -> dict:
|
|
111
|
+
"""-> {"fill_pct", "should_compact", "should_warn"}.
|
|
112
|
+
|
|
113
|
+
``already_compacted`` is the usage-driven path having fired this turn; it
|
|
114
|
+
suppresses both outcomes so one turn never compacts twice or warns about a
|
|
115
|
+
figure it just fixed.
|
|
116
|
+
"""
|
|
117
|
+
if max_tokens <= 0:
|
|
118
|
+
return {"fill_pct": 0, "should_compact": False, "should_warn": False}
|
|
119
|
+
|
|
120
|
+
fill_pct = min(100, int(used_tokens / max_tokens * 100))
|
|
121
|
+
limit_pct = clamp_compact_threshold(threshold) * 100
|
|
122
|
+
|
|
123
|
+
should_compact = (
|
|
124
|
+
bool(auto_compact_enabled)
|
|
125
|
+
and not already_compacted
|
|
126
|
+
and fill_pct >= limit_pct
|
|
127
|
+
)
|
|
128
|
+
should_warn = (
|
|
129
|
+
not should_compact
|
|
130
|
+
and not already_compacted
|
|
131
|
+
and fill_pct >= CONTEXT_WARN_FRACTION * 100
|
|
132
|
+
)
|
|
133
|
+
return {
|
|
134
|
+
"fill_pct": fill_pct,
|
|
135
|
+
"should_compact": should_compact,
|
|
136
|
+
"should_warn": should_warn,
|
|
137
|
+
}
|