aria-code 4.1.3__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.
- agents/__init__.py +32 -0
- agents/base.py +190 -0
- agents/deep/__init__.py +37 -0
- agents/deep/calibration_loop.py +144 -0
- agents/deep/critic.py +125 -0
- agents/deep/deepen.py +193 -0
- agents/deep/models.py +149 -0
- agents/deep/pipeline.py +164 -0
- agents/deep/quant_fusion.py +192 -0
- agents/deep/themes.py +95 -0
- agents/deep/tiers.py +106 -0
- agents/financial/__init__.py +10 -0
- agents/financial/catalyst.py +279 -0
- agents/financial/debate.py +145 -0
- agents/financial/earnings.py +303 -0
- agents/financial/fundamental.py +159 -0
- agents/financial/macro.py +99 -0
- agents/financial/news.py +207 -0
- agents/financial/risk.py +132 -0
- agents/financial/sector.py +279 -0
- agents/financial/synthesis.py +274 -0
- agents/financial/technical.py +258 -0
- agents/portfolio_agent.py +333 -0
- agents/realty/__init__.py +62 -0
- agents/realty/asset_diagnosis.py +150 -0
- agents/realty/business_match.py +165 -0
- agents/realty/cashflow_verify.py +208 -0
- agents/realty/contract_rules.py +209 -0
- agents/realty/energy_anomaly.py +188 -0
- agents/realty/exit_settlement.py +207 -0
- agents/realty/fulfillment_risk.py +205 -0
- agents/realty/ops_optimize.py +159 -0
- agents/realty/revenue_share.py +214 -0
- agents/registry.py +144 -0
- agents/sports/__init__.py +0 -0
- agents/sports/football_agent.py +169 -0
- agents/team.py +289 -0
- aliyun_data_client.py +660 -0
- apps/README.md +12 -0
- apps/__init__.py +2 -0
- apps/channels/README.md +15 -0
- apps/cli/README.md +13 -0
- apps/cli/__init__.py +2 -0
- apps/cli/bootstrap.py +99 -0
- apps/cli/codegen_paths.py +29 -0
- apps/cli/commands/__init__.py +16 -0
- apps/cli/commands/analysis_cmds.py +288 -0
- apps/cli/commands/backtest_cmds.py +1887 -0
- apps/cli/commands/broker_cmds.py +1154 -0
- apps/cli/commands/business_workflow_cmds.py +289 -0
- apps/cli/commands/catalog.py +84 -0
- apps/cli/commands/data_cmds.py +405 -0
- apps/cli/commands/diagnostic_cmds.py +179 -0
- apps/cli/commands/diagnostic_ops_cmds.py +696 -0
- apps/cli/commands/finance_render.py +12 -0
- apps/cli/commands/market.py +399 -0
- apps/cli/commands/market_cmds.py +1276 -0
- apps/cli/commands/market_context.py +425 -0
- apps/cli/commands/market_render.py +7 -0
- apps/cli/commands/model_cmds.py +1579 -0
- apps/cli/commands/ops_cmds.py +668 -0
- apps/cli/commands/portfolio_cmds.py +962 -0
- apps/cli/commands/report.py +377 -0
- apps/cli/commands/scaffold_templates.py +617 -0
- apps/cli/commands/session_cmds.py +179 -0
- apps/cli/commands/session_ux_cmds.py +280 -0
- apps/cli/commands/team.py +588 -0
- apps/cli/commands/team_render.py +8 -0
- apps/cli/commands/ui_cmds.py +358 -0
- apps/cli/commands/workflow_cmds.py +279 -0
- apps/cli/commands/workspace_cmds.py +1414 -0
- apps/cli/config_paths.py +70 -0
- apps/cli/config_store.py +61 -0
- apps/cli/deterministic.py +122 -0
- apps/cli/direct.py +48 -0
- apps/cli/github_app_auth.py +135 -0
- apps/cli/handlers/__init__.py +11 -0
- apps/cli/handlers/broker_handlers.py +122 -0
- apps/cli/handlers/chart_handlers.py +1309 -0
- apps/cli/handlers/market_handlers.py +2509 -0
- apps/cli/handlers/realty_handlers.py +114 -0
- apps/cli/handlers/strategy_advice.py +82 -0
- apps/cli/hooks.py +180 -0
- apps/cli/i18n.py +284 -0
- apps/cli/intent.py +136 -0
- apps/cli/intent_router.py +217 -0
- apps/cli/lifecycle_hooks.py +48 -0
- apps/cli/main.py +29 -0
- apps/cli/market_metadata.py +135 -0
- apps/cli/market_universe.py +265 -0
- apps/cli/message_processing.py +257 -0
- apps/cli/plan_mode.py +139 -0
- apps/cli/plotly_html.py +15 -0
- apps/cli/prediction_feedback.py +202 -0
- apps/cli/preflight.py +497 -0
- apps/cli/project_aria.py +60 -0
- apps/cli/prompts/__init__.py +0 -0
- apps/cli/prompts/coding.py +658 -0
- apps/cli/prompts/system_prompts.py +531 -0
- apps/cli/prompts/ui.py +434 -0
- apps/cli/providers/__init__.py +1 -0
- apps/cli/providers/base.py +271 -0
- apps/cli/providers/chat_routing.py +80 -0
- apps/cli/providers/llm/__init__.py +1 -0
- apps/cli/providers/llm/ollama_stream.py +1170 -0
- apps/cli/providers/llm/sse_stream.py +216 -0
- apps/cli/providers/runtime_bridge.py +185 -0
- apps/cli/runtime_consumer.py +489 -0
- apps/cli/session_export.py +87 -0
- apps/cli/session_jsonl.py +207 -0
- apps/cli/session_store.py +112 -0
- apps/cli/todo_tracker.py +190 -0
- apps/cli/tools/__init__.py +40 -0
- apps/cli/tools/context.py +46 -0
- apps/cli/tools/file_tools.py +112 -0
- apps/cli/tools/market_tools.py +549 -0
- apps/cli/tools/notebook_tools.py +111 -0
- apps/cli/tools/system_tools.py +669 -0
- apps/cli/tools/write_tools.py +715 -0
- apps/cli/tradingview_bridge.py +434 -0
- apps/cli/update_check.py +152 -0
- apps/cli/utils/__init__.py +0 -0
- apps/cli/utils/market_detect.py +1578 -0
- apps/daemon/README.md +14 -0
- apps/vscode/README.md +115 -0
- apps/vscode/package.json +70 -0
- aria_cli.py +11636 -0
- aria_code-4.1.3.dist-info/METADATA +952 -0
- aria_code-4.1.3.dist-info/RECORD +284 -0
- aria_code-4.1.3.dist-info/WHEEL +5 -0
- aria_code-4.1.3.dist-info/entry_points.txt +2 -0
- aria_code-4.1.3.dist-info/licenses/LICENSE +121 -0
- aria_code-4.1.3.dist-info/top_level.txt +50 -0
- aria_daemon.py +1295 -0
- aria_feishu_bot.py +1359 -0
- aria_relay_client.py +182 -0
- aria_relay_server.py +405 -0
- aria_telegram_bot.py +202 -0
- ariarc.py +328 -0
- artifacts.py +491 -0
- backtest_report.py +472 -0
- brokers/__init__.py +72 -0
- brokers/base.py +207 -0
- brokers/capabilities.py +264 -0
- brokers/cn/__init__.py +10 -0
- brokers/cn/easytrader_broker.py +193 -0
- brokers/cn/futu_broker.py +194 -0
- brokers/cn/longbridge_broker.py +190 -0
- brokers/cn/tiger_broker.py +196 -0
- brokers/cn/xtquant_broker.py +175 -0
- brokers/config.py +364 -0
- brokers/intl/__init__.py +5 -0
- brokers/intl/alpaca_broker.py +183 -0
- brokers/intl/ibkr_broker.py +215 -0
- brokers/intl/webull_broker.py +156 -0
- brokers/paper_broker.py +259 -0
- brokers/planning.py +296 -0
- brokers/registry.py +181 -0
- brokers/trading.py +237 -0
- change_store.py +127 -0
- command_safety.py +19 -0
- computer_use_tools.py +504 -0
- dashboard_generator.py +578 -0
- data_analysis_tools.py +808 -0
- data_cleaner.py +483 -0
- data_service.py +481 -0
- datasources/__init__.py +23 -0
- datasources/base.py +166 -0
- datasources/router.py +221 -0
- datasources/sources/__init__.py +15 -0
- datasources/sources/akshare_source.py +269 -0
- datasources/sources/alpha_vantage_source.py +202 -0
- datasources/sources/edgar_source.py +218 -0
- datasources/sources/finnhub_source.py +197 -0
- datasources/sources/fred_source.py +219 -0
- datasources/sources/tushare_source.py +141 -0
- datasources/sources/web_scraper_source.py +278 -0
- datasources/sources/world_bank_source.py +205 -0
- datasources/sources/yfinance_source.py +152 -0
- demo_player.py +204 -0
- doctor.py +508 -0
- file_analysis_tools.py +734 -0
- finance_formulas.py +389 -0
- football_data_client.py +1670 -0
- intent_classifier.py +358 -0
- local_finance_tools.py +3221 -0
- local_llm_provider.py +552 -0
- macro_tools.py +368 -0
- market_data_client.py +1899 -0
- mcp_client.py +506 -0
- memory_manager.py +245 -0
- model_capability.py +416 -0
- notification_tools.py +248 -0
- packages/__init__.py +23 -0
- packages/aria_agents/__init__.py +5 -0
- packages/aria_agents/manifest.py +69 -0
- packages/aria_core/__init__.py +34 -0
- packages/aria_core/architecture.py +192 -0
- packages/aria_core/export.py +124 -0
- packages/aria_core/manifest.py +65 -0
- packages/aria_infra/__init__.py +15 -0
- packages/aria_infra/arthera.py +52 -0
- packages/aria_infra/doctor.py +246 -0
- packages/aria_infra/product.py +37 -0
- packages/aria_mcp/__init__.py +25 -0
- packages/aria_mcp/bridge.py +38 -0
- packages/aria_mcp/config.py +97 -0
- packages/aria_mcp/tools.py +61 -0
- packages/aria_sdk/__init__.py +19 -0
- packages/aria_sdk/client.py +396 -0
- packages/aria_sdk/providers.py +70 -0
- packages/aria_sdk/streaming.py +73 -0
- packages/aria_sdk/types.py +86 -0
- packages/aria_services/__init__.py +55 -0
- packages/aria_services/context.py +258 -0
- packages/aria_services/data.py +11 -0
- packages/aria_services/provider_health.py +189 -0
- packages/aria_services/registry.py +213 -0
- packages/aria_services/usage.py +138 -0
- packages/aria_skills/__init__.py +5 -0
- packages/aria_skills/registry.py +59 -0
- packages/aria_tools/__init__.py +5 -0
- packages/aria_tools/registry.py +128 -0
- packages/quant_engine/__init__.py +6 -0
- packages/quant_engine/sports/__init__.py +72 -0
- packages/quant_engine/sports/calibrator.py +353 -0
- packages/quant_engine/sports/dixon_coles.py +234 -0
- packages/quant_engine/sports/elo.py +299 -0
- packages/quant_engine/sports/form.py +188 -0
- packages/quant_engine/sports/h2h.py +195 -0
- packages/quant_engine/sports/ml_model.py +354 -0
- packages/quant_engine/sports/predictor.py +311 -0
- packages/quant_engine/sports/tracker.py +664 -0
- packages/quant_engine/stochastic/__init__.py +27 -0
- packages/quant_engine/stochastic/gbm_enhanced.py +195 -0
- packages/quant_engine/stochastic/ito_calculus.py +477 -0
- packages/quant_engine/stochastic/kelly_criterion.py +181 -0
- packages/quant_engine/stochastic/monte_carlo_advanced.py +95 -0
- packages/quant_engine/stochastic/options_pricing.py +573 -0
- packages/quant_engine/stochastic/stochastic_processes.py +90 -0
- plan_utils.py +194 -0
- plugin_loader.py +328 -0
- portfolio_ledger.py +262 -0
- privacy/__init__.py +5 -0
- privacy/feedback.py +123 -0
- project_tools.py +525 -0
- providers/__init__.py +30 -0
- providers/llm/__init__.py +19 -0
- providers/llm/anthropic.py +184 -0
- providers/llm/base.py +139 -0
- providers/llm/ollama.py +128 -0
- providers/llm/openai_compat.py +282 -0
- providers/llm/registry.py +358 -0
- realty_data_tools.py +659 -0
- report_generator.py +1314 -0
- runtime/__init__.py +103 -0
- runtime/agent_loop.py +1183 -0
- runtime/approval.py +51 -0
- runtime/events.py +102 -0
- runtime/gateway.py +128 -0
- runtime/lsp.py +346 -0
- runtime/subagent.py +258 -0
- runtime/tool_executor.py +104 -0
- runtime/tool_policy.py +106 -0
- safety/__init__.py +21 -0
- safety/permissions.py +275 -0
- setup_wizard.py +653 -0
- strategy_vault.py +420 -0
- ui/__init__.py +100 -0
- ui/banner.py +310 -0
- ui/completer.py +391 -0
- ui/console.py +271 -0
- ui/image_render.py +243 -0
- ui/input_box.py +376 -0
- ui/picker.py +195 -0
- ui/render/__init__.py +11 -0
- ui/render/finance.py +1480 -0
- ui/render/market.py +225 -0
- ui/render/output.py +681 -0
- ui/render/team.py +346 -0
- ui/robot.py +235 -0
- workspace/__init__.py +6 -0
- workspace/files.py +170 -0
- workspace/verify.py +113 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"""
|
|
2
|
+
brokers/cn/xtquant_broker.py — 迅投 XTQuant 适配器
|
|
3
|
+
====================================================
|
|
4
|
+
支持券商:中信证券、华鑫证券、浙商证券(通过迅投QMT平台)
|
|
5
|
+
|
|
6
|
+
安装:pip install xtquant
|
|
7
|
+
文档:https://dict.thinktrader.net/nativeApi/
|
|
8
|
+
|
|
9
|
+
配置示例::
|
|
10
|
+
|
|
11
|
+
{
|
|
12
|
+
"id": "xt_main",
|
|
13
|
+
"type": "xtquant",
|
|
14
|
+
"label": "中信主账户",
|
|
15
|
+
"account_id": "1234567890",
|
|
16
|
+
"path": "C:\\国金QMT交易端模拟\\userdata_mini"
|
|
17
|
+
}
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
from typing import Any, Dict, List
|
|
23
|
+
|
|
24
|
+
from ..base import BrokerBase, AccountInfo, Position, Order, OrderResult
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class XTQuantBroker(BrokerBase):
|
|
28
|
+
broker_type = "xtquant"
|
|
29
|
+
broker_name = "迅投 XTQuant"
|
|
30
|
+
market = "CN"
|
|
31
|
+
|
|
32
|
+
def __init__(self, broker_id: str, config: Dict[str, Any]):
|
|
33
|
+
super().__init__(broker_id, config)
|
|
34
|
+
self._account_id = config.get("account_id", "")
|
|
35
|
+
self._path = config.get("path", "")
|
|
36
|
+
self._xt_trader = None
|
|
37
|
+
|
|
38
|
+
def connect(self) -> bool:
|
|
39
|
+
try:
|
|
40
|
+
import os, sys
|
|
41
|
+
with open(os.devnull, "w") as _null:
|
|
42
|
+
_old, sys.stdout = sys.stdout, _null
|
|
43
|
+
try:
|
|
44
|
+
from xtquant.xttrader import XtQuantTrader
|
|
45
|
+
from xtquant import xtdata
|
|
46
|
+
finally:
|
|
47
|
+
sys.stdout = _old
|
|
48
|
+
path = self._path or "."
|
|
49
|
+
trader = XtQuantTrader(path, int(self.config.get("session_id", 1)))
|
|
50
|
+
conn = trader.connect()
|
|
51
|
+
if conn != 0:
|
|
52
|
+
raise RuntimeError(f"XTQuant 连接失败: code={conn}")
|
|
53
|
+
trader.subscribe_position(self._account_id)
|
|
54
|
+
trader.subscribe_order(self._account_id)
|
|
55
|
+
trader.subscribe_trade(self._account_id)
|
|
56
|
+
self._xt_trader = trader
|
|
57
|
+
self._connected = True
|
|
58
|
+
return True
|
|
59
|
+
except ImportError:
|
|
60
|
+
raise ImportError("xtquant 未安装。请运行: pip install xtquant")
|
|
61
|
+
except Exception as e:
|
|
62
|
+
self._connected = False
|
|
63
|
+
raise RuntimeError(f"XTQuant 连接失败: {e}") from e
|
|
64
|
+
|
|
65
|
+
def disconnect(self) -> None:
|
|
66
|
+
if self._xt_trader:
|
|
67
|
+
try:
|
|
68
|
+
self._xt_trader.disconnect()
|
|
69
|
+
except Exception:
|
|
70
|
+
pass
|
|
71
|
+
self._xt_trader = None
|
|
72
|
+
self._connected = False
|
|
73
|
+
|
|
74
|
+
def account_info(self) -> AccountInfo:
|
|
75
|
+
self._require_connected()
|
|
76
|
+
acct = self._xt_trader.query_stock_asset(self._account_id)
|
|
77
|
+
if not acct:
|
|
78
|
+
raise RuntimeError("XTQuant 查询账户资金失败")
|
|
79
|
+
# XTQuant StockAsset fields
|
|
80
|
+
total = float(getattr(acct, "total_asset", 0))
|
|
81
|
+
cash = float(getattr(acct, "cash", 0))
|
|
82
|
+
frozen = float(getattr(acct, "frozen_cash", 0))
|
|
83
|
+
mv = float(getattr(acct, "market_value", 0))
|
|
84
|
+
return AccountInfo(
|
|
85
|
+
broker_id=self.broker_id,
|
|
86
|
+
broker_type=self.broker_type,
|
|
87
|
+
label=self.label,
|
|
88
|
+
account_id=self._account_id,
|
|
89
|
+
currency="CNY",
|
|
90
|
+
total_assets=total,
|
|
91
|
+
cash=cash,
|
|
92
|
+
market_value=mv,
|
|
93
|
+
frozen=frozen,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
def positions(self) -> List[Position]:
|
|
97
|
+
self._require_connected()
|
|
98
|
+
raw = self._xt_trader.query_stock_positions(self._account_id)
|
|
99
|
+
result = []
|
|
100
|
+
for p in (raw or []):
|
|
101
|
+
cost = float(getattr(p, "open_price", 0))
|
|
102
|
+
price = float(getattr(p, "market_price", 0))
|
|
103
|
+
qty = float(getattr(p, "volume", 0))
|
|
104
|
+
avail = float(getattr(p, "can_use_volume",0))
|
|
105
|
+
mv = float(getattr(p, "market_value", 0))
|
|
106
|
+
pnl = float(getattr(p, "open_pnl", 0))
|
|
107
|
+
sym = str(getattr(p, "stock_code", ""))
|
|
108
|
+
name = str(getattr(p, "stock_name", ""))
|
|
109
|
+
pnl_pct = (pnl / (mv - pnl) * 100) if (mv - pnl) > 0 else 0.0
|
|
110
|
+
result.append(Position(
|
|
111
|
+
symbol=sym, name=name,
|
|
112
|
+
quantity=qty, available_qty=avail,
|
|
113
|
+
cost_price=cost, current_price=price,
|
|
114
|
+
market_value=mv, pnl=pnl, pnl_pct=pnl_pct,
|
|
115
|
+
currency="CNY", market="a_share",
|
|
116
|
+
))
|
|
117
|
+
return result
|
|
118
|
+
|
|
119
|
+
def orders(self, status: str = "all", limit: int = 50) -> List[Order]:
|
|
120
|
+
self._require_connected()
|
|
121
|
+
raw = self._xt_trader.query_stock_orders(self._account_id)
|
|
122
|
+
result = []
|
|
123
|
+
for o in (raw or [])[:limit]:
|
|
124
|
+
raw_status = int(getattr(o, "order_status", 0))
|
|
125
|
+
mapped = _xt_order_status(raw_status)
|
|
126
|
+
if status != "all" and mapped != status:
|
|
127
|
+
continue
|
|
128
|
+
side = "buy" if int(getattr(o, "order_type", 23)) in (23, 90) else "sell"
|
|
129
|
+
result.append(Order(
|
|
130
|
+
order_id=str(getattr(o, "order_id", "")),
|
|
131
|
+
symbol=str(getattr(o, "stock_code", "")),
|
|
132
|
+
name=str(getattr(o, "stock_name", "")),
|
|
133
|
+
side=side,
|
|
134
|
+
order_type="limit",
|
|
135
|
+
quantity=float(getattr(o, "order_volume", 0)),
|
|
136
|
+
filled_qty=float(getattr(o, "traded_volume", 0)),
|
|
137
|
+
price=float(getattr(o, "price", 0)),
|
|
138
|
+
avg_price=float(getattr(o, "traded_price", 0)),
|
|
139
|
+
status=mapped,
|
|
140
|
+
created_at=str(getattr(o, "order_time", "")),
|
|
141
|
+
currency="CNY",
|
|
142
|
+
))
|
|
143
|
+
return result
|
|
144
|
+
|
|
145
|
+
def place_order(self, symbol: str, side: str, quantity: float,
|
|
146
|
+
order_type: str = "limit", price: float = 0.0, **kwargs) -> OrderResult:
|
|
147
|
+
self._require_connected()
|
|
148
|
+
from xtquant.xttype import StockOrderType, StockPriceType
|
|
149
|
+
xt_type = 23 if side == "buy" else 24 # XTQuant buy/sell constants
|
|
150
|
+
price_tp = StockPriceType.LATEST if order_type == "market" else StockPriceType.FIX
|
|
151
|
+
oid = self._xt_trader.order_stock(
|
|
152
|
+
self._account_id, symbol, xt_type, int(quantity), price_tp, price,
|
|
153
|
+
strategy_name="aria_code", order_remark="",
|
|
154
|
+
)
|
|
155
|
+
if oid == -1:
|
|
156
|
+
return OrderResult(success=False, message="XTQuant 下单失败(返回 -1)", broker_id=self.broker_id)
|
|
157
|
+
return OrderResult(success=True, order_id=str(oid), broker_id=self.broker_id)
|
|
158
|
+
|
|
159
|
+
def cancel_order(self, order_id: str) -> bool:
|
|
160
|
+
self._require_connected()
|
|
161
|
+
ret = self._xt_trader.cancel_order_stock(self._account_id, int(order_id))
|
|
162
|
+
return ret == 0
|
|
163
|
+
|
|
164
|
+
def _require_connected(self):
|
|
165
|
+
if not self._connected or not self._xt_trader:
|
|
166
|
+
raise RuntimeError("XTQuant 未连接,请先调用 connect()")
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _xt_order_status(code: int) -> str:
|
|
170
|
+
_MAP = {
|
|
171
|
+
48: "open", 49: "open", 50: "open",
|
|
172
|
+
51: "filled", 52: "partial",
|
|
173
|
+
53: "cancelled", 54: "cancelled", 55: "cancelled",
|
|
174
|
+
}
|
|
175
|
+
return _MAP.get(code, "open")
|
brokers/config.py
ADDED
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
"""
|
|
2
|
+
brokers/config.py — 券商配置加载与管理
|
|
3
|
+
========================================
|
|
4
|
+
配置文件位置:~/.aria-code/brokers.json(或由 ARIA_HOME 环境变量覆盖)
|
|
5
|
+
|
|
6
|
+
示例配置::
|
|
7
|
+
|
|
8
|
+
{
|
|
9
|
+
"brokers": [
|
|
10
|
+
{
|
|
11
|
+
"id": "xt_main",
|
|
12
|
+
"type": "xtquant",
|
|
13
|
+
"label": "中信主账户",
|
|
14
|
+
"account_id": "1234567890",
|
|
15
|
+
"default": true
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"id": "ibkr_us",
|
|
19
|
+
"type": "ibkr",
|
|
20
|
+
"label": "盈透美股",
|
|
21
|
+
"host": "127.0.0.1",
|
|
22
|
+
"port": 7496,
|
|
23
|
+
"client_id": 1
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"id": "alpaca_paper",
|
|
27
|
+
"type": "alpaca",
|
|
28
|
+
"label": "Alpaca 模拟盘",
|
|
29
|
+
"api_key": "PKxxx",
|
|
30
|
+
"api_secret": "xxx",
|
|
31
|
+
"paper": true
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"id": "futu_hk",
|
|
35
|
+
"type": "futu",
|
|
36
|
+
"label": "富途港股",
|
|
37
|
+
"host": "127.0.0.1",
|
|
38
|
+
"port": 11111,
|
|
39
|
+
"market": "HK"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"id": "tiger_us",
|
|
43
|
+
"type": "tiger",
|
|
44
|
+
"label": "老虎美股",
|
|
45
|
+
"tiger_id": "xxx",
|
|
46
|
+
"private_key_path": "~/.aria-code/tiger_rsa.pem",
|
|
47
|
+
"account": "xxx"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"id": "longbridge_cn",
|
|
51
|
+
"type": "longbridge",
|
|
52
|
+
"label": "长桥A股",
|
|
53
|
+
"app_key": "xxx",
|
|
54
|
+
"app_secret": "xxx",
|
|
55
|
+
"access_token": "xxx"
|
|
56
|
+
}
|
|
57
|
+
]
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
支持的 type 值:
|
|
61
|
+
paper 本地仿盘账户(无真实券商连接)
|
|
62
|
+
xtquant 迅投(中信、华鑫、浙商等)
|
|
63
|
+
easytrader easytrader(同花顺、通达信、华泰、国君)
|
|
64
|
+
futu 富途牛牛 OpenAPI(港股/美股/A股)
|
|
65
|
+
tiger 老虎证券 OpenAPI(美股/港股/A股)
|
|
66
|
+
longbridge 长桥证券 OpenAPI(港股/美股/A股)
|
|
67
|
+
ibkr Interactive Brokers TWS/Gateway
|
|
68
|
+
alpaca Alpaca Markets(美股,支持模拟盘)
|
|
69
|
+
webull Webull(美股)
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
from __future__ import annotations
|
|
73
|
+
|
|
74
|
+
import json
|
|
75
|
+
import os
|
|
76
|
+
from pathlib import Path
|
|
77
|
+
from typing import Any, Dict, List, Optional
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _resolve_aria_home() -> Path:
|
|
81
|
+
"""Resolve the Aria config directory (same logic as aria_cli.py).
|
|
82
|
+
|
|
83
|
+
Priority:
|
|
84
|
+
1. ARIA_HOME env var (explicit override)
|
|
85
|
+
2. ~/.arthera — legacy path, kept for backward compat if it exists
|
|
86
|
+
3. ~/.aria-code — default for fresh installs
|
|
87
|
+
"""
|
|
88
|
+
if "ARIA_HOME" in os.environ:
|
|
89
|
+
return Path(os.environ["ARIA_HOME"]).expanduser()
|
|
90
|
+
legacy = Path.home() / ".arthera"
|
|
91
|
+
if legacy.exists():
|
|
92
|
+
return legacy
|
|
93
|
+
return Path.home() / ".aria-code"
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
BROKERS_CONFIG_PATH = _resolve_aria_home() / "brokers.json"
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
# ── 读写工具 ───────────────────────────────────────────────────────────────────
|
|
100
|
+
|
|
101
|
+
def load_config() -> Dict[str, Any]:
|
|
102
|
+
"""加载 brokers.json,文件不存在时返回空 {"brokers": []}。"""
|
|
103
|
+
if not BROKERS_CONFIG_PATH.exists():
|
|
104
|
+
return {"brokers": []}
|
|
105
|
+
try:
|
|
106
|
+
with open(BROKERS_CONFIG_PATH, encoding="utf-8") as f:
|
|
107
|
+
return json.load(f)
|
|
108
|
+
except Exception:
|
|
109
|
+
return {"brokers": []}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def save_config(cfg: Dict[str, Any]) -> None:
|
|
113
|
+
"""保存配置到 brokers.json,自动创建目录。"""
|
|
114
|
+
BROKERS_CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
115
|
+
with open(BROKERS_CONFIG_PATH, "w", encoding="utf-8") as f:
|
|
116
|
+
json.dump(cfg, f, ensure_ascii=False, indent=2)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def list_broker_configs() -> List[Dict[str, Any]]:
|
|
120
|
+
"""返回所有已配置的券商列表。"""
|
|
121
|
+
return load_config().get("brokers", [])
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def get_broker_config(broker_id: str) -> Optional[Dict[str, Any]]:
|
|
125
|
+
"""根据 id 查找某个券商配置。"""
|
|
126
|
+
for b in list_broker_configs():
|
|
127
|
+
if b.get("id") == broker_id:
|
|
128
|
+
return b
|
|
129
|
+
return None
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def get_default_broker_config() -> Optional[Dict[str, Any]]:
|
|
133
|
+
"""返回标记了 default=true 的券商,或第一个。"""
|
|
134
|
+
brokers = list_broker_configs()
|
|
135
|
+
if not brokers:
|
|
136
|
+
return None
|
|
137
|
+
for b in brokers:
|
|
138
|
+
if b.get("default"):
|
|
139
|
+
return b
|
|
140
|
+
return brokers[0]
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def add_broker_config(cfg: Dict[str, Any]) -> None:
|
|
144
|
+
"""新增一条券商配置(id 重复时覆盖)。"""
|
|
145
|
+
data = load_config()
|
|
146
|
+
brokers = data.get("brokers", [])
|
|
147
|
+
brokers = [b for b in brokers if b.get("id") != cfg.get("id")]
|
|
148
|
+
brokers.append(cfg)
|
|
149
|
+
data["brokers"] = brokers
|
|
150
|
+
save_config(data)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def remove_broker_config(broker_id: str) -> bool:
|
|
154
|
+
"""删除某个券商配置,返回 True 表示找到并删除。"""
|
|
155
|
+
data = load_config()
|
|
156
|
+
before = len(data.get("brokers", []))
|
|
157
|
+
data["brokers"] = [b for b in data.get("brokers", []) if b.get("id") != broker_id]
|
|
158
|
+
if len(data["brokers"]) < before:
|
|
159
|
+
save_config(data)
|
|
160
|
+
return True
|
|
161
|
+
return False
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def set_default_broker(broker_id: str) -> bool:
|
|
165
|
+
"""设置默认券商。"""
|
|
166
|
+
data = load_config()
|
|
167
|
+
found = False
|
|
168
|
+
for b in data.get("brokers", []):
|
|
169
|
+
if b.get("id") == broker_id:
|
|
170
|
+
b["default"] = True
|
|
171
|
+
found = True
|
|
172
|
+
else:
|
|
173
|
+
b.pop("default", None)
|
|
174
|
+
if found:
|
|
175
|
+
save_config(data)
|
|
176
|
+
return found
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
# ── 配置校验 ──────────────────────────────────────────────────────────────────
|
|
180
|
+
|
|
181
|
+
# 每种 type 必需的字段
|
|
182
|
+
_REQUIRED_FIELDS: Dict[str, List[str]] = {
|
|
183
|
+
"paper": [],
|
|
184
|
+
"xtquant": ["account_id"],
|
|
185
|
+
"easytrader": ["broker_name"],
|
|
186
|
+
"futu": ["host", "port"], # FutuOpenD must be running locally
|
|
187
|
+
"tiger": ["tiger_id", "private_key_path", "account"],
|
|
188
|
+
"longbridge": ["app_key", "app_secret", "access_token"],
|
|
189
|
+
"ibkr": ["host", "port"], # TWS/Gateway host:port required
|
|
190
|
+
"alpaca": ["api_key", "api_secret"],
|
|
191
|
+
"webull": ["username", "password"], # read-only, still needs credentials
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
_TYPE_LABELS: Dict[str, str] = {
|
|
195
|
+
"paper": "Aria 本地仿盘账户",
|
|
196
|
+
"xtquant": "迅投 XTQuant(中信/华鑫/浙商等)",
|
|
197
|
+
"easytrader": "EasyTrader(同花顺/通达信/华泰/国君)",
|
|
198
|
+
"futu": "富途牛牛 OpenAPI",
|
|
199
|
+
"tiger": "老虎证券 OpenAPI",
|
|
200
|
+
"longbridge": "长桥证券 OpenAPI",
|
|
201
|
+
"ibkr": "Interactive Brokers TWS/Gateway",
|
|
202
|
+
"alpaca": "Alpaca Markets(美股 / 模拟盘)",
|
|
203
|
+
"webull": "Webull(美股)",
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
_RECOMMENDED_FIELDS: Dict[str, List[str]] = {
|
|
208
|
+
"paper": ["starting_cash", "currency"],
|
|
209
|
+
"futu": ["market"],
|
|
210
|
+
"ibkr": ["client_id"],
|
|
211
|
+
"alpaca": ["paper"],
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def validate_broker_config(cfg: Dict[str, Any]) -> List[str]:
|
|
216
|
+
"""校验券商配置,返回错误列表(空列表=通过)。
|
|
217
|
+
|
|
218
|
+
Warnings (prefixed with '⚠ ') are non-fatal but worth showing.
|
|
219
|
+
"""
|
|
220
|
+
errors: List[str] = []
|
|
221
|
+
if not cfg.get("id"):
|
|
222
|
+
errors.append("缺少 'id' 字段")
|
|
223
|
+
if not cfg.get("label"):
|
|
224
|
+
errors.append("⚠ 建议设置 'label' 字段(账户显示名称)")
|
|
225
|
+
broker_type = cfg.get("type", "")
|
|
226
|
+
if not broker_type:
|
|
227
|
+
errors.append("缺少 'type' 字段")
|
|
228
|
+
elif broker_type not in _REQUIRED_FIELDS:
|
|
229
|
+
errors.append(f"不支持的 type: {broker_type!r} (支持: {', '.join(_REQUIRED_FIELDS)})")
|
|
230
|
+
else:
|
|
231
|
+
for req in _REQUIRED_FIELDS[broker_type]:
|
|
232
|
+
if not cfg.get(req):
|
|
233
|
+
errors.append(f"{broker_type} 缺少必需字段: '{req}'")
|
|
234
|
+
for rec in _RECOMMENDED_FIELDS.get(broker_type, []):
|
|
235
|
+
if not cfg.get(rec):
|
|
236
|
+
errors.append(f"⚠ {broker_type} 建议设置 '{rec}' 字段")
|
|
237
|
+
return errors
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def supported_broker_types() -> Dict[str, str]:
|
|
241
|
+
"""返回所有支持的券商类型 {type: 描述}。"""
|
|
242
|
+
return dict(_TYPE_LABELS)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
# ── 生成配置模板 ──────────────────────────────────────────────────────────────
|
|
246
|
+
|
|
247
|
+
_TEMPLATES: Dict[str, Dict[str, Any]] = {
|
|
248
|
+
"paper": {
|
|
249
|
+
"id": "paper_main",
|
|
250
|
+
"type": "paper",
|
|
251
|
+
"label": "Aria 仿盘账户",
|
|
252
|
+
"mode": "paper",
|
|
253
|
+
"starting_cash": 100000,
|
|
254
|
+
"currency": "USD",
|
|
255
|
+
"default": True,
|
|
256
|
+
"_comment": "本地仿盘账户,不连接真实券商;订单写入 ~/.arthera/paper_ledger.json"
|
|
257
|
+
},
|
|
258
|
+
"xtquant": {
|
|
259
|
+
"id": "xt_main",
|
|
260
|
+
"type": "xtquant",
|
|
261
|
+
"label": "中信主账户",
|
|
262
|
+
"mode": "read_only",
|
|
263
|
+
"allow_live_trade": False,
|
|
264
|
+
"require_confirm": True,
|
|
265
|
+
"account_id": "YOUR_ACCOUNT_ID",
|
|
266
|
+
"default": True,
|
|
267
|
+
"_comment": "需安装 xtquant: pip install xtquant (仅 Windows/Linux)"
|
|
268
|
+
},
|
|
269
|
+
"easytrader": {
|
|
270
|
+
"id": "ht_main",
|
|
271
|
+
"type": "easytrader",
|
|
272
|
+
"label": "华泰账户",
|
|
273
|
+
"mode": "read_only",
|
|
274
|
+
"allow_live_trade": False,
|
|
275
|
+
"require_confirm": True,
|
|
276
|
+
"broker_name": "huatai",
|
|
277
|
+
"exe_path": "C:\\华泰证券\\xiadan.exe",
|
|
278
|
+
"_comment": "broker_name 可选: huatai/guojun/ths/tdx/yh/zszq/xq"
|
|
279
|
+
},
|
|
280
|
+
"futu": {
|
|
281
|
+
"id": "futu_hk",
|
|
282
|
+
"type": "futu",
|
|
283
|
+
"label": "富途港股",
|
|
284
|
+
"mode": "read_only",
|
|
285
|
+
"allow_live_trade": False,
|
|
286
|
+
"require_confirm": True,
|
|
287
|
+
"host": "127.0.0.1",
|
|
288
|
+
"port": 11111,
|
|
289
|
+
"market": "HK",
|
|
290
|
+
"_comment": "需安装 futu-api: pip install futu-api 并启动富途 OpenD"
|
|
291
|
+
},
|
|
292
|
+
"tiger": {
|
|
293
|
+
"id": "tiger_us",
|
|
294
|
+
"type": "tiger",
|
|
295
|
+
"label": "老虎美股",
|
|
296
|
+
"mode": "read_only",
|
|
297
|
+
"allow_live_trade": False,
|
|
298
|
+
"require_confirm": True,
|
|
299
|
+
"tiger_id": "YOUR_TIGER_ID",
|
|
300
|
+
"private_key_path": "~/.aria-code/tiger_rsa.pem",
|
|
301
|
+
"account": "YOUR_ACCOUNT",
|
|
302
|
+
"_comment": "需安装 tigeropen: pip install tigeropen"
|
|
303
|
+
},
|
|
304
|
+
"longbridge": {
|
|
305
|
+
"id": "lb_cn",
|
|
306
|
+
"type": "longbridge",
|
|
307
|
+
"label": "长桥A股",
|
|
308
|
+
"mode": "read_only",
|
|
309
|
+
"allow_live_trade": False,
|
|
310
|
+
"require_confirm": True,
|
|
311
|
+
"app_key": "YOUR_APP_KEY",
|
|
312
|
+
"app_secret": "YOUR_APP_SECRET",
|
|
313
|
+
"access_token": "YOUR_ACCESS_TOKEN",
|
|
314
|
+
"_comment": "需安装 longbridge: pip install longbridge"
|
|
315
|
+
},
|
|
316
|
+
"ibkr": {
|
|
317
|
+
"id": "ibkr_us",
|
|
318
|
+
"type": "ibkr",
|
|
319
|
+
"label": "盈透美股",
|
|
320
|
+
"mode": "read_only",
|
|
321
|
+
"allow_live_trade": False,
|
|
322
|
+
"require_confirm": True,
|
|
323
|
+
"host": "127.0.0.1",
|
|
324
|
+
"port": 7496,
|
|
325
|
+
"client_id": 1,
|
|
326
|
+
"_comment": "需安装 ib_insync: pip install ib_insync 并启动 TWS/Gateway"
|
|
327
|
+
},
|
|
328
|
+
"alpaca": {
|
|
329
|
+
"id": "alpaca_paper",
|
|
330
|
+
"type": "alpaca",
|
|
331
|
+
"label": "Alpaca 模拟盘",
|
|
332
|
+
"mode": "paper",
|
|
333
|
+
"allow_live_trade": False,
|
|
334
|
+
"require_confirm": True,
|
|
335
|
+
"api_key": "YOUR_API_KEY",
|
|
336
|
+
"api_secret": "YOUR_API_SECRET",
|
|
337
|
+
"paper": True,
|
|
338
|
+
"_comment": "需安装 alpaca-py: pip install alpaca-py"
|
|
339
|
+
},
|
|
340
|
+
"webull": {
|
|
341
|
+
"id": "webull_us",
|
|
342
|
+
"type": "webull",
|
|
343
|
+
"label": "Webull 美股",
|
|
344
|
+
"mode": "read_only",
|
|
345
|
+
"allow_live_trade": False,
|
|
346
|
+
"require_confirm": True,
|
|
347
|
+
"username": "YOUR_EMAIL_OR_PHONE",
|
|
348
|
+
"password": "YOUR_PASSWORD",
|
|
349
|
+
"device_id": "",
|
|
350
|
+
"_comment": "需安装 webull: pip install webull"
|
|
351
|
+
},
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def get_config_template(broker_type: str) -> Optional[Dict[str, Any]]:
|
|
356
|
+
"""返回指定券商类型的配置模板。"""
|
|
357
|
+
import copy
|
|
358
|
+
tmpl = _TEMPLATES.get(broker_type)
|
|
359
|
+
return copy.deepcopy(tmpl) if tmpl else None
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def print_all_templates() -> str:
|
|
363
|
+
"""返回所有配置模板的 JSON 字符串(用于 /broker init 命令)。"""
|
|
364
|
+
return json.dumps({"brokers": list(_TEMPLATES.values())}, ensure_ascii=False, indent=2)
|