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.
Files changed (284) hide show
  1. agents/__init__.py +32 -0
  2. agents/base.py +190 -0
  3. agents/deep/__init__.py +37 -0
  4. agents/deep/calibration_loop.py +144 -0
  5. agents/deep/critic.py +125 -0
  6. agents/deep/deepen.py +193 -0
  7. agents/deep/models.py +149 -0
  8. agents/deep/pipeline.py +164 -0
  9. agents/deep/quant_fusion.py +192 -0
  10. agents/deep/themes.py +95 -0
  11. agents/deep/tiers.py +106 -0
  12. agents/financial/__init__.py +10 -0
  13. agents/financial/catalyst.py +279 -0
  14. agents/financial/debate.py +145 -0
  15. agents/financial/earnings.py +303 -0
  16. agents/financial/fundamental.py +159 -0
  17. agents/financial/macro.py +99 -0
  18. agents/financial/news.py +207 -0
  19. agents/financial/risk.py +132 -0
  20. agents/financial/sector.py +279 -0
  21. agents/financial/synthesis.py +274 -0
  22. agents/financial/technical.py +258 -0
  23. agents/portfolio_agent.py +333 -0
  24. agents/realty/__init__.py +62 -0
  25. agents/realty/asset_diagnosis.py +150 -0
  26. agents/realty/business_match.py +165 -0
  27. agents/realty/cashflow_verify.py +208 -0
  28. agents/realty/contract_rules.py +209 -0
  29. agents/realty/energy_anomaly.py +188 -0
  30. agents/realty/exit_settlement.py +207 -0
  31. agents/realty/fulfillment_risk.py +205 -0
  32. agents/realty/ops_optimize.py +159 -0
  33. agents/realty/revenue_share.py +214 -0
  34. agents/registry.py +144 -0
  35. agents/sports/__init__.py +0 -0
  36. agents/sports/football_agent.py +169 -0
  37. agents/team.py +289 -0
  38. aliyun_data_client.py +660 -0
  39. apps/README.md +12 -0
  40. apps/__init__.py +2 -0
  41. apps/channels/README.md +15 -0
  42. apps/cli/README.md +13 -0
  43. apps/cli/__init__.py +2 -0
  44. apps/cli/bootstrap.py +99 -0
  45. apps/cli/codegen_paths.py +29 -0
  46. apps/cli/commands/__init__.py +16 -0
  47. apps/cli/commands/analysis_cmds.py +288 -0
  48. apps/cli/commands/backtest_cmds.py +1887 -0
  49. apps/cli/commands/broker_cmds.py +1154 -0
  50. apps/cli/commands/business_workflow_cmds.py +289 -0
  51. apps/cli/commands/catalog.py +84 -0
  52. apps/cli/commands/data_cmds.py +405 -0
  53. apps/cli/commands/diagnostic_cmds.py +179 -0
  54. apps/cli/commands/diagnostic_ops_cmds.py +696 -0
  55. apps/cli/commands/finance_render.py +12 -0
  56. apps/cli/commands/market.py +399 -0
  57. apps/cli/commands/market_cmds.py +1276 -0
  58. apps/cli/commands/market_context.py +425 -0
  59. apps/cli/commands/market_render.py +7 -0
  60. apps/cli/commands/model_cmds.py +1579 -0
  61. apps/cli/commands/ops_cmds.py +668 -0
  62. apps/cli/commands/portfolio_cmds.py +962 -0
  63. apps/cli/commands/report.py +377 -0
  64. apps/cli/commands/scaffold_templates.py +617 -0
  65. apps/cli/commands/session_cmds.py +179 -0
  66. apps/cli/commands/session_ux_cmds.py +280 -0
  67. apps/cli/commands/team.py +588 -0
  68. apps/cli/commands/team_render.py +8 -0
  69. apps/cli/commands/ui_cmds.py +358 -0
  70. apps/cli/commands/workflow_cmds.py +279 -0
  71. apps/cli/commands/workspace_cmds.py +1414 -0
  72. apps/cli/config_paths.py +70 -0
  73. apps/cli/config_store.py +61 -0
  74. apps/cli/deterministic.py +122 -0
  75. apps/cli/direct.py +48 -0
  76. apps/cli/github_app_auth.py +135 -0
  77. apps/cli/handlers/__init__.py +11 -0
  78. apps/cli/handlers/broker_handlers.py +122 -0
  79. apps/cli/handlers/chart_handlers.py +1309 -0
  80. apps/cli/handlers/market_handlers.py +2509 -0
  81. apps/cli/handlers/realty_handlers.py +114 -0
  82. apps/cli/handlers/strategy_advice.py +82 -0
  83. apps/cli/hooks.py +180 -0
  84. apps/cli/i18n.py +284 -0
  85. apps/cli/intent.py +136 -0
  86. apps/cli/intent_router.py +217 -0
  87. apps/cli/lifecycle_hooks.py +48 -0
  88. apps/cli/main.py +29 -0
  89. apps/cli/market_metadata.py +135 -0
  90. apps/cli/market_universe.py +265 -0
  91. apps/cli/message_processing.py +257 -0
  92. apps/cli/plan_mode.py +139 -0
  93. apps/cli/plotly_html.py +15 -0
  94. apps/cli/prediction_feedback.py +202 -0
  95. apps/cli/preflight.py +497 -0
  96. apps/cli/project_aria.py +60 -0
  97. apps/cli/prompts/__init__.py +0 -0
  98. apps/cli/prompts/coding.py +658 -0
  99. apps/cli/prompts/system_prompts.py +531 -0
  100. apps/cli/prompts/ui.py +434 -0
  101. apps/cli/providers/__init__.py +1 -0
  102. apps/cli/providers/base.py +271 -0
  103. apps/cli/providers/chat_routing.py +80 -0
  104. apps/cli/providers/llm/__init__.py +1 -0
  105. apps/cli/providers/llm/ollama_stream.py +1170 -0
  106. apps/cli/providers/llm/sse_stream.py +216 -0
  107. apps/cli/providers/runtime_bridge.py +185 -0
  108. apps/cli/runtime_consumer.py +489 -0
  109. apps/cli/session_export.py +87 -0
  110. apps/cli/session_jsonl.py +207 -0
  111. apps/cli/session_store.py +112 -0
  112. apps/cli/todo_tracker.py +190 -0
  113. apps/cli/tools/__init__.py +40 -0
  114. apps/cli/tools/context.py +46 -0
  115. apps/cli/tools/file_tools.py +112 -0
  116. apps/cli/tools/market_tools.py +549 -0
  117. apps/cli/tools/notebook_tools.py +111 -0
  118. apps/cli/tools/system_tools.py +669 -0
  119. apps/cli/tools/write_tools.py +715 -0
  120. apps/cli/tradingview_bridge.py +434 -0
  121. apps/cli/update_check.py +152 -0
  122. apps/cli/utils/__init__.py +0 -0
  123. apps/cli/utils/market_detect.py +1578 -0
  124. apps/daemon/README.md +14 -0
  125. apps/vscode/README.md +115 -0
  126. apps/vscode/package.json +70 -0
  127. aria_cli.py +11636 -0
  128. aria_code-4.1.3.dist-info/METADATA +952 -0
  129. aria_code-4.1.3.dist-info/RECORD +284 -0
  130. aria_code-4.1.3.dist-info/WHEEL +5 -0
  131. aria_code-4.1.3.dist-info/entry_points.txt +2 -0
  132. aria_code-4.1.3.dist-info/licenses/LICENSE +121 -0
  133. aria_code-4.1.3.dist-info/top_level.txt +50 -0
  134. aria_daemon.py +1295 -0
  135. aria_feishu_bot.py +1359 -0
  136. aria_relay_client.py +182 -0
  137. aria_relay_server.py +405 -0
  138. aria_telegram_bot.py +202 -0
  139. ariarc.py +328 -0
  140. artifacts.py +491 -0
  141. backtest_report.py +472 -0
  142. brokers/__init__.py +72 -0
  143. brokers/base.py +207 -0
  144. brokers/capabilities.py +264 -0
  145. brokers/cn/__init__.py +10 -0
  146. brokers/cn/easytrader_broker.py +193 -0
  147. brokers/cn/futu_broker.py +194 -0
  148. brokers/cn/longbridge_broker.py +190 -0
  149. brokers/cn/tiger_broker.py +196 -0
  150. brokers/cn/xtquant_broker.py +175 -0
  151. brokers/config.py +364 -0
  152. brokers/intl/__init__.py +5 -0
  153. brokers/intl/alpaca_broker.py +183 -0
  154. brokers/intl/ibkr_broker.py +215 -0
  155. brokers/intl/webull_broker.py +156 -0
  156. brokers/paper_broker.py +259 -0
  157. brokers/planning.py +296 -0
  158. brokers/registry.py +181 -0
  159. brokers/trading.py +237 -0
  160. change_store.py +127 -0
  161. command_safety.py +19 -0
  162. computer_use_tools.py +504 -0
  163. dashboard_generator.py +578 -0
  164. data_analysis_tools.py +808 -0
  165. data_cleaner.py +483 -0
  166. data_service.py +481 -0
  167. datasources/__init__.py +23 -0
  168. datasources/base.py +166 -0
  169. datasources/router.py +221 -0
  170. datasources/sources/__init__.py +15 -0
  171. datasources/sources/akshare_source.py +269 -0
  172. datasources/sources/alpha_vantage_source.py +202 -0
  173. datasources/sources/edgar_source.py +218 -0
  174. datasources/sources/finnhub_source.py +197 -0
  175. datasources/sources/fred_source.py +219 -0
  176. datasources/sources/tushare_source.py +141 -0
  177. datasources/sources/web_scraper_source.py +278 -0
  178. datasources/sources/world_bank_source.py +205 -0
  179. datasources/sources/yfinance_source.py +152 -0
  180. demo_player.py +204 -0
  181. doctor.py +508 -0
  182. file_analysis_tools.py +734 -0
  183. finance_formulas.py +389 -0
  184. football_data_client.py +1670 -0
  185. intent_classifier.py +358 -0
  186. local_finance_tools.py +3221 -0
  187. local_llm_provider.py +552 -0
  188. macro_tools.py +368 -0
  189. market_data_client.py +1899 -0
  190. mcp_client.py +506 -0
  191. memory_manager.py +245 -0
  192. model_capability.py +416 -0
  193. notification_tools.py +248 -0
  194. packages/__init__.py +23 -0
  195. packages/aria_agents/__init__.py +5 -0
  196. packages/aria_agents/manifest.py +69 -0
  197. packages/aria_core/__init__.py +34 -0
  198. packages/aria_core/architecture.py +192 -0
  199. packages/aria_core/export.py +124 -0
  200. packages/aria_core/manifest.py +65 -0
  201. packages/aria_infra/__init__.py +15 -0
  202. packages/aria_infra/arthera.py +52 -0
  203. packages/aria_infra/doctor.py +246 -0
  204. packages/aria_infra/product.py +37 -0
  205. packages/aria_mcp/__init__.py +25 -0
  206. packages/aria_mcp/bridge.py +38 -0
  207. packages/aria_mcp/config.py +97 -0
  208. packages/aria_mcp/tools.py +61 -0
  209. packages/aria_sdk/__init__.py +19 -0
  210. packages/aria_sdk/client.py +396 -0
  211. packages/aria_sdk/providers.py +70 -0
  212. packages/aria_sdk/streaming.py +73 -0
  213. packages/aria_sdk/types.py +86 -0
  214. packages/aria_services/__init__.py +55 -0
  215. packages/aria_services/context.py +258 -0
  216. packages/aria_services/data.py +11 -0
  217. packages/aria_services/provider_health.py +189 -0
  218. packages/aria_services/registry.py +213 -0
  219. packages/aria_services/usage.py +138 -0
  220. packages/aria_skills/__init__.py +5 -0
  221. packages/aria_skills/registry.py +59 -0
  222. packages/aria_tools/__init__.py +5 -0
  223. packages/aria_tools/registry.py +128 -0
  224. packages/quant_engine/__init__.py +6 -0
  225. packages/quant_engine/sports/__init__.py +72 -0
  226. packages/quant_engine/sports/calibrator.py +353 -0
  227. packages/quant_engine/sports/dixon_coles.py +234 -0
  228. packages/quant_engine/sports/elo.py +299 -0
  229. packages/quant_engine/sports/form.py +188 -0
  230. packages/quant_engine/sports/h2h.py +195 -0
  231. packages/quant_engine/sports/ml_model.py +354 -0
  232. packages/quant_engine/sports/predictor.py +311 -0
  233. packages/quant_engine/sports/tracker.py +664 -0
  234. packages/quant_engine/stochastic/__init__.py +27 -0
  235. packages/quant_engine/stochastic/gbm_enhanced.py +195 -0
  236. packages/quant_engine/stochastic/ito_calculus.py +477 -0
  237. packages/quant_engine/stochastic/kelly_criterion.py +181 -0
  238. packages/quant_engine/stochastic/monte_carlo_advanced.py +95 -0
  239. packages/quant_engine/stochastic/options_pricing.py +573 -0
  240. packages/quant_engine/stochastic/stochastic_processes.py +90 -0
  241. plan_utils.py +194 -0
  242. plugin_loader.py +328 -0
  243. portfolio_ledger.py +262 -0
  244. privacy/__init__.py +5 -0
  245. privacy/feedback.py +123 -0
  246. project_tools.py +525 -0
  247. providers/__init__.py +30 -0
  248. providers/llm/__init__.py +19 -0
  249. providers/llm/anthropic.py +184 -0
  250. providers/llm/base.py +139 -0
  251. providers/llm/ollama.py +128 -0
  252. providers/llm/openai_compat.py +282 -0
  253. providers/llm/registry.py +358 -0
  254. realty_data_tools.py +659 -0
  255. report_generator.py +1314 -0
  256. runtime/__init__.py +103 -0
  257. runtime/agent_loop.py +1183 -0
  258. runtime/approval.py +51 -0
  259. runtime/events.py +102 -0
  260. runtime/gateway.py +128 -0
  261. runtime/lsp.py +346 -0
  262. runtime/subagent.py +258 -0
  263. runtime/tool_executor.py +104 -0
  264. runtime/tool_policy.py +106 -0
  265. safety/__init__.py +21 -0
  266. safety/permissions.py +275 -0
  267. setup_wizard.py +653 -0
  268. strategy_vault.py +420 -0
  269. ui/__init__.py +100 -0
  270. ui/banner.py +310 -0
  271. ui/completer.py +391 -0
  272. ui/console.py +271 -0
  273. ui/image_render.py +243 -0
  274. ui/input_box.py +376 -0
  275. ui/picker.py +195 -0
  276. ui/render/__init__.py +11 -0
  277. ui/render/finance.py +1480 -0
  278. ui/render/market.py +225 -0
  279. ui/render/output.py +681 -0
  280. ui/render/team.py +346 -0
  281. ui/robot.py +235 -0
  282. workspace/__init__.py +6 -0
  283. workspace/files.py +170 -0
  284. workspace/verify.py +113 -0
@@ -0,0 +1,194 @@
1
+ """
2
+ brokers/cn/futu_broker.py — 富途牛牛 OpenAPI 适配器
3
+ =====================================================
4
+ 支持市场:港股、美股、A股(通过 OpenD 网关)
5
+
6
+ 安装:pip install futu-api
7
+ 文档:https://openapi.futunn.com/futu-api-doc/
8
+
9
+ 配置示例::
10
+
11
+ {
12
+ "id": "futu_hk",
13
+ "type": "futu",
14
+ "label": "富途港股",
15
+ "host": "127.0.0.1",
16
+ "port": 11111,
17
+ "market": "HK",
18
+ "trd_env": "REAL"
19
+ }
20
+
21
+ 注意:需在本机启动 Futu OpenD 进程。
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ from typing import Any, Dict, List
27
+
28
+ from ..base import BrokerBase, AccountInfo, Position, Order, OrderResult
29
+
30
+
31
+ class FutuBroker(BrokerBase):
32
+ broker_type = "futu"
33
+ broker_name = "富途牛牛"
34
+ market = "HK"
35
+
36
+ def __init__(self, broker_id: str, config: Dict[str, Any]):
37
+ super().__init__(broker_id, config)
38
+ self._host = config.get("host", "127.0.0.1")
39
+ self._port = int(config.get("port", 11111))
40
+ self._market = config.get("market", "HK").upper()
41
+ self._trd_env = config.get("trd_env", "REAL").upper()
42
+ self._quote_ctx = None
43
+ self._trd_ctx = None
44
+ self.market = self._market
45
+
46
+ def connect(self) -> bool:
47
+ try:
48
+ import futu as ft
49
+ except ImportError:
50
+ raise ImportError("futu-api 未安装。请运行: pip install futu-api 并启动 Futu OpenD")
51
+
52
+ try:
53
+ self._quote_ctx = ft.OpenQuoteContext(host=self._host, port=self._port)
54
+ trd_market = {
55
+ "HK": ft.TrdMarket.HK,
56
+ "US": ft.TrdMarket.US,
57
+ "CN": ft.TrdMarket.CN,
58
+ "A": ft.TrdMarket.CN,
59
+ }.get(self._market, ft.TrdMarket.HK)
60
+ trd_env = ft.TrdEnv.REAL if self._trd_env == "REAL" else ft.TrdEnv.SIMULATE
61
+ self._trd_ctx = ft.OpenSecTradeContext(
62
+ filter_trdmarket=trd_market,
63
+ host=self._host, port=self._port,
64
+ security_firm=ft.SecurityFirm.FUTUINC,
65
+ )
66
+ self._ft = ft
67
+ self._trd_env_val = trd_env
68
+ self._connected = True
69
+ return True
70
+ except Exception as e:
71
+ raise RuntimeError(f"富途 OpenAPI 连接失败: {e}") from e
72
+
73
+ def disconnect(self) -> None:
74
+ for ctx in (self._quote_ctx, self._trd_ctx):
75
+ try:
76
+ if ctx:
77
+ ctx.close()
78
+ except Exception:
79
+ pass
80
+ self._connected = False
81
+
82
+ def account_info(self) -> AccountInfo:
83
+ self._require_connected()
84
+ ret, data = self._trd_ctx.accinfo_query(trd_env=self._trd_env_val)
85
+ if ret != self._ft.RET_OK:
86
+ raise RuntimeError(f"富途账户查询失败: {data}")
87
+ row = data.iloc[0]
88
+ currency = "HKD" if self._market == "HK" else "USD" if self._market == "US" else "CNY"
89
+ return AccountInfo(
90
+ broker_id=self.broker_id,
91
+ broker_type=self.broker_type,
92
+ label=self.label,
93
+ account_id=str(row.get("acc_id", "")),
94
+ currency=currency,
95
+ total_assets=float(row.get("total_assets", 0)),
96
+ cash=float(row.get("cash", 0)),
97
+ market_value=float(row.get("market_val", 0)),
98
+ frozen=float(row.get("frozen_cash", 0)),
99
+ pnl_today=float(row.get("today_profit", 0)),
100
+ pnl_pct=float(row.get("today_profit_ratio", 0)) * 100,
101
+ )
102
+
103
+ def positions(self) -> List[Position]:
104
+ self._require_connected()
105
+ ret, data = self._trd_ctx.position_list_query(trd_env=self._trd_env_val)
106
+ if ret != self._ft.RET_OK:
107
+ raise RuntimeError(f"富途持仓查询失败: {data}")
108
+ result = []
109
+ currency = "HKD" if self._market == "HK" else "USD" if self._market == "US" else "CNY"
110
+ for _, row in data.iterrows():
111
+ sym = str(row.get("code", ""))
112
+ name = str(row.get("stock_name", ""))
113
+ qty = float(row.get("qty", 0))
114
+ avail= float(row.get("can_sell_qty", 0))
115
+ cost = float(row.get("cost_price", 0))
116
+ price= float(row.get("price", 0))
117
+ mv = float(row.get("market_val", 0))
118
+ pnl = float(row.get("unrealized_pl", 0))
119
+ pnl_pct = float(row.get("unrealized_pl_ratio", 0)) * 100
120
+ result.append(Position(
121
+ symbol=sym, name=name,
122
+ quantity=qty, available_qty=avail,
123
+ cost_price=cost, current_price=price,
124
+ market_value=mv, pnl=pnl, pnl_pct=pnl_pct,
125
+ currency=currency, market=self._market.lower(),
126
+ ))
127
+ return result
128
+
129
+ def orders(self, status: str = "all", limit: int = 50) -> List[Order]:
130
+ self._require_connected()
131
+ ret, data = self._trd_ctx.order_list_query(trd_env=self._trd_env_val)
132
+ if ret != self._ft.RET_OK:
133
+ raise RuntimeError(f"富途订单查询失败: {data}")
134
+ result = []
135
+ for _, row in data.head(limit).iterrows():
136
+ side = "buy" if "BUY" in str(row.get("trd_side", "")).upper() else "sell"
137
+ raw_st = str(row.get("order_status", ""))
138
+ mapped = _futu_order_status(raw_st)
139
+ if status != "all" and mapped != status:
140
+ continue
141
+ result.append(Order(
142
+ order_id=str(row.get("order_id", "")),
143
+ symbol=str(row.get("code", "")),
144
+ name=str(row.get("stock_name", "")),
145
+ side=side,
146
+ order_type=str(row.get("order_type", "")).lower(),
147
+ quantity=float(row.get("qty", 0)),
148
+ filled_qty=float(row.get("dealt_qty", 0)),
149
+ price=float(row.get("price", 0)),
150
+ avg_price=float(row.get("dealt_avg_price", 0)),
151
+ status=mapped,
152
+ created_at=str(row.get("create_time", "")),
153
+ ))
154
+ return result
155
+
156
+ def place_order(self, symbol: str, side: str, quantity: float,
157
+ order_type: str = "limit", price: float = 0.0, **kwargs) -> OrderResult:
158
+ self._require_connected()
159
+ ft = self._ft
160
+ trd_side = ft.TrdSide.BUY if side == "buy" else ft.TrdSide.SELL
161
+ ot = ft.OrderType.NORMAL if order_type == "limit" else ft.OrderType.MARKET
162
+ ret, data = self._trd_ctx.place_order(
163
+ price=price, qty=quantity, code=symbol,
164
+ trd_side=trd_side, order_type=ot,
165
+ trd_env=self._trd_env_val,
166
+ )
167
+ if ret != ft.RET_OK:
168
+ return OrderResult(success=False, message=str(data), broker_id=self.broker_id)
169
+ oid = str(data.iloc[0].get("order_id", ""))
170
+ return OrderResult(success=True, order_id=oid, broker_id=self.broker_id)
171
+
172
+ def cancel_order(self, order_id: str) -> bool:
173
+ self._require_connected()
174
+ ret, _ = self._trd_ctx.modify_order(
175
+ modify_order_op=self._ft.ModifyOrderOp.CANCEL,
176
+ order_id=int(order_id), qty=0, price=0,
177
+ trd_env=self._trd_env_val,
178
+ )
179
+ return ret == self._ft.RET_OK
180
+
181
+ def _require_connected(self):
182
+ if not self._connected or not self._trd_ctx:
183
+ raise RuntimeError("富途 OpenAPI 未连接,请先调用 connect()")
184
+
185
+
186
+ def _futu_order_status(raw: str) -> str:
187
+ raw = raw.upper()
188
+ if "FILLED_ALL" in raw or "已全部成交" in raw:
189
+ return "filled"
190
+ if "FILLED_PART" in raw or "部分" in raw:
191
+ return "partial"
192
+ if "CANCELLED" in raw or "已撤" in raw:
193
+ return "cancelled"
194
+ return "open"
@@ -0,0 +1,190 @@
1
+ """
2
+ brokers/cn/longbridge_broker.py — 长桥证券 OpenAPI 适配器
3
+ ===========================================================
4
+ 支持市场:港股、美股、A股
5
+
6
+ 安装:pip install longbridge
7
+ 文档:https://open.longportapp.com/
8
+
9
+ 配置示例::
10
+
11
+ {
12
+ "id": "lb_main",
13
+ "type": "longbridge",
14
+ "label": "长桥主账户",
15
+ "app_key": "YOUR_APP_KEY",
16
+ "app_secret": "YOUR_APP_SECRET",
17
+ "access_token": "YOUR_ACCESS_TOKEN"
18
+ }
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ from typing import Any, Dict, List
24
+
25
+ from ..base import BrokerBase, AccountInfo, Position, Order, OrderResult
26
+
27
+
28
+ class LongbridgeBroker(BrokerBase):
29
+ broker_type = "longbridge"
30
+ broker_name = "长桥证券"
31
+ market = "GLOBAL"
32
+
33
+ def __init__(self, broker_id: str, config: Dict[str, Any]):
34
+ super().__init__(broker_id, config)
35
+ self._app_key = config.get("app_key", "")
36
+ self._app_secret = config.get("app_secret", "")
37
+ self._access_token = config.get("access_token", "")
38
+ self._trade_ctx = None
39
+ self._quote_ctx = None
40
+
41
+ def connect(self) -> bool:
42
+ try:
43
+ from longbridge.openapi import TradeContext, QuoteContext, Config
44
+ except ImportError:
45
+ raise ImportError("longbridge 未安装。请运行: pip install longbridge")
46
+
47
+ try:
48
+ cfg = Config(
49
+ app_key=self._app_key,
50
+ app_secret=self._app_secret,
51
+ access_token=self._access_token,
52
+ )
53
+ self._trade_ctx = TradeContext(cfg)
54
+ self._quote_ctx = QuoteContext(cfg)
55
+ # 测试连接
56
+ self._trade_ctx.account_balance()
57
+ self._connected = True
58
+ return True
59
+ except Exception as e:
60
+ raise RuntimeError(f"长桥证券连接失败: {e}") from e
61
+
62
+ def account_info(self) -> AccountInfo:
63
+ self._require_connected()
64
+ try:
65
+ resp = self._trade_ctx.account_balance()
66
+ bal = resp[0] if resp else None
67
+ if not bal:
68
+ raise RuntimeError("长桥账户余额为空")
69
+ currency = str(getattr(bal, "currency", "HKD"))
70
+ total = float(getattr(bal, "total_cash", 0))
71
+ cash = float(getattr(bal, "available_cash", 0))
72
+ mv = float(getattr(bal, "market_value", 0))
73
+ pnl = float(getattr(bal, "unrealized_pnl", 0))
74
+ except Exception as e:
75
+ raise RuntimeError(f"长桥账户查询失败: {e}") from e
76
+
77
+ return AccountInfo(
78
+ broker_id=self.broker_id,
79
+ broker_type=self.broker_type,
80
+ label=self.label,
81
+ account_id=self.config.get("account_id", self._app_key[-6:]),
82
+ currency=currency,
83
+ total_assets=total + mv,
84
+ cash=cash,
85
+ market_value=mv,
86
+ pnl_total=pnl,
87
+ )
88
+
89
+ def positions(self) -> List[Position]:
90
+ self._require_connected()
91
+ try:
92
+ raw = self._trade_ctx.stock_positions()
93
+ except Exception as e:
94
+ raise RuntimeError(f"长桥持仓查询失败: {e}") from e
95
+
96
+ result = []
97
+ for ch in getattr(raw, "channels", []):
98
+ for p in getattr(ch, "positions", []):
99
+ sym = str(getattr(p, "symbol", ""))
100
+ name = str(getattr(p, "symbol_name", ""))
101
+ qty = float(getattr(p, "quantity", 0))
102
+ avail = float(getattr(p, "available_quantity", qty))
103
+ cost = float(getattr(p, "cost_price", 0))
104
+ price = float(getattr(p, "current_price", 0))
105
+ mv = float(getattr(p, "market_value", 0))
106
+ pnl = float(getattr(p, "unrealized_pnl", 0))
107
+ pnl_pct = float(getattr(p, "unrealized_pnl_ratio", 0))
108
+ currency = str(getattr(p, "currency", "HKD"))
109
+ result.append(Position(
110
+ symbol=sym, name=name,
111
+ quantity=qty, available_qty=avail,
112
+ cost_price=cost, current_price=price,
113
+ market_value=mv, pnl=pnl, pnl_pct=pnl_pct * 100,
114
+ currency=currency,
115
+ ))
116
+ return result
117
+
118
+ def orders(self, status: str = "all", limit: int = 50) -> List[Order]:
119
+ self._require_connected()
120
+ try:
121
+ from longbridge.openapi import OrderStatus as LBOrderStatus
122
+ raw = self._trade_ctx.today_orders()
123
+ except Exception as e:
124
+ raise RuntimeError(f"长桥订单查询失败: {e}") from e
125
+
126
+ result = []
127
+ for o in list(raw or [])[:limit]:
128
+ side = "buy" if "BUY" in str(getattr(o, "side", "")).upper() else "sell"
129
+ mapped = _lb_order_status(str(getattr(o, "status", "")))
130
+ if status != "all" and mapped != status:
131
+ continue
132
+ result.append(Order(
133
+ order_id=str(getattr(o, "order_id", "")),
134
+ symbol=str(getattr(o, "symbol", "")),
135
+ name=str(getattr(o, "stock_name", "")),
136
+ side=side,
137
+ order_type=str(getattr(o, "order_type", "")).lower(),
138
+ quantity=float(getattr(o, "quantity", 0)),
139
+ filled_qty=float(getattr(o, "executed_quantity", 0)),
140
+ price=float(getattr(o, "price", 0)),
141
+ avg_price=float(getattr(o, "executed_price", 0)),
142
+ status=mapped,
143
+ created_at=str(getattr(o, "submitted_at", "")),
144
+ currency=str(getattr(o, "currency", "HKD")),
145
+ ))
146
+ return result
147
+
148
+ def place_order(self, symbol: str, side: str, quantity: float,
149
+ order_type: str = "limit", price: float = 0.0, **kwargs) -> OrderResult:
150
+ self._require_connected()
151
+ try:
152
+ from longbridge.openapi import OrderSide, OrderType, TimeInForceType
153
+ from decimal import Decimal
154
+ lb_side = OrderSide.Buy if side == "buy" else OrderSide.Sell
155
+ lb_type = OrderType.MO if order_type == "market" else OrderType.LO
156
+ resp = self._trade_ctx.submit_order(
157
+ symbol=symbol,
158
+ order_type=lb_type,
159
+ side=lb_side,
160
+ submitted_quantity=int(quantity),
161
+ time_in_force=TimeInForceType.Day,
162
+ submitted_price=Decimal(str(price)) if price else None,
163
+ )
164
+ oid = str(getattr(resp, "order_id", ""))
165
+ return OrderResult(success=True, order_id=oid, broker_id=self.broker_id)
166
+ except Exception as e:
167
+ return OrderResult(success=False, message=str(e), broker_id=self.broker_id)
168
+
169
+ def cancel_order(self, order_id: str) -> bool:
170
+ self._require_connected()
171
+ try:
172
+ self._trade_ctx.cancel_order(order_id)
173
+ return True
174
+ except Exception:
175
+ return False
176
+
177
+ def _require_connected(self):
178
+ if not self._connected:
179
+ raise RuntimeError("长桥证券未连接,请先调用 connect()")
180
+
181
+
182
+ def _lb_order_status(raw: str) -> str:
183
+ raw = raw.upper()
184
+ if "FILLED" in raw and "PARTIAL" not in raw:
185
+ return "filled"
186
+ if "PARTIAL" in raw:
187
+ return "partial"
188
+ if any(x in raw for x in ("CANCELLED", "EXPIRED", "REJECTED")):
189
+ return "cancelled"
190
+ return "open"
@@ -0,0 +1,196 @@
1
+ """
2
+ brokers/cn/tiger_broker.py — 老虎证券 OpenAPI 适配器
3
+ ======================================================
4
+ 支持市场:美股、港股、A股
5
+
6
+ 安装:pip install tigeropen
7
+ 文档:https://quant.tigerfintech.com/
8
+
9
+ 配置示例::
10
+
11
+ {
12
+ "id": "tiger_us",
13
+ "type": "tiger",
14
+ "label": "老虎美股",
15
+ "tiger_id": "YOUR_TIGER_ID",
16
+ "private_key_path": "~/.arthera/tiger_rsa.pem",
17
+ "account": "YOUR_ACCOUNT_ID",
18
+ "sandbox": false
19
+ }
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ from typing import Any, Dict, List
25
+
26
+ from ..base import BrokerBase, AccountInfo, Position, Order, OrderResult
27
+
28
+
29
+ class TigerBroker(BrokerBase):
30
+ broker_type = "tiger"
31
+ broker_name = "老虎证券"
32
+ market = "US"
33
+
34
+ def __init__(self, broker_id: str, config: Dict[str, Any]):
35
+ super().__init__(broker_id, config)
36
+ self._tiger_id = config.get("tiger_id", "")
37
+ self._private_key_path = config.get("private_key_path", "")
38
+ self._account = config.get("account", "")
39
+ self._sandbox = bool(config.get("sandbox", False))
40
+ self._client_config = None
41
+ self._trade_client = None
42
+ self._quote_client = None
43
+
44
+ def connect(self) -> bool:
45
+ try:
46
+ from tigeropen.tiger_open_config import TigerOpenClientConfig
47
+ from tigeropen.common.consts import TigerApiConstants
48
+ from tigeropen.trade.trade_client import TradeClient
49
+ from tigeropen.quote.quote_client import QuoteClient
50
+ except ImportError:
51
+ raise ImportError("tigeropen 未安装。请运行: pip install tigeropen")
52
+
53
+ import os
54
+ pk_path = os.path.expanduser(self._private_key_path)
55
+ if not os.path.exists(pk_path):
56
+ raise FileNotFoundError(f"老虎证券私钥文件不存在: {pk_path}")
57
+
58
+ try:
59
+ cfg = TigerOpenClientConfig(sandbox_debug=self._sandbox)
60
+ cfg.tiger_id = self._tiger_id
61
+ cfg.private_key = open(pk_path).read()
62
+ cfg.account = self._account
63
+ self._client_config = cfg
64
+ self._trade_client = TradeClient(cfg)
65
+ self._quote_client = QuoteClient(cfg)
66
+ # 测试连接
67
+ self._trade_client.get_managed_accounts()
68
+ self._connected = True
69
+ return True
70
+ except Exception as e:
71
+ raise RuntimeError(f"老虎证券连接失败: {e}") from e
72
+
73
+ def account_info(self) -> AccountInfo:
74
+ self._require_connected()
75
+ try:
76
+ assets = self._trade_client.get_assets(account=self._account)
77
+ a = assets[0] if assets else None
78
+ if not a:
79
+ raise RuntimeError("老虎证券返回空账户数据")
80
+ return AccountInfo(
81
+ broker_id=self.broker_id,
82
+ broker_type=self.broker_type,
83
+ label=self.label,
84
+ account_id=self._account,
85
+ currency=str(getattr(a, "currency", "USD")),
86
+ total_assets=float(getattr(a, "net_liquidation", 0)),
87
+ cash=float(getattr(a, "cash_balance", 0)),
88
+ market_value=float(getattr(a, "gross_position_value", 0)),
89
+ pnl_today=float(getattr(a, "realized_pnl", 0)),
90
+ pnl_total=float(getattr(a, "unrealized_pnl", 0)),
91
+ )
92
+ except Exception as e:
93
+ raise RuntimeError(f"老虎证券账户查询失败: {e}") from e
94
+
95
+ def positions(self) -> List[Position]:
96
+ self._require_connected()
97
+ try:
98
+ raw = self._trade_client.get_positions(account=self._account)
99
+ except Exception as e:
100
+ raise RuntimeError(f"老虎证券持仓查询失败: {e}") from e
101
+
102
+ result = []
103
+ for p in (raw or []):
104
+ sym = str(getattr(p, "contract", {}).symbol if hasattr(p, "contract") else getattr(p, "symbol", ""))
105
+ qty = float(getattr(p, "quantity", 0))
106
+ avail = float(getattr(p, "available_qty", qty))
107
+ cost = float(getattr(p, "average_cost", 0))
108
+ price = float(getattr(p, "market_price", 0))
109
+ mv = float(getattr(p, "position_value", 0))
110
+ pnl = float(getattr(p, "unrealized_pnl", 0))
111
+ pnl_pct = float(getattr(p, "unrealized_pnl_ratio", 0)) * 100
112
+ currency = str(getattr(p, "currency", "USD"))
113
+ result.append(Position(
114
+ symbol=sym, name=sym,
115
+ quantity=qty, available_qty=avail,
116
+ cost_price=cost, current_price=price,
117
+ market_value=mv, pnl=pnl, pnl_pct=pnl_pct,
118
+ currency=currency, market="us",
119
+ ))
120
+ return result
121
+
122
+ def orders(self, status: str = "all", limit: int = 50) -> List[Order]:
123
+ self._require_connected()
124
+ try:
125
+ from tigeropen.common.consts import OrderStatus
126
+ raw = self._trade_client.get_orders(account=self._account, limit=limit)
127
+ except Exception as e:
128
+ raise RuntimeError(f"老虎证券订单查询失败: {e}") from e
129
+
130
+ result = []
131
+ for o in (raw or []):
132
+ side = "buy" if str(getattr(o, "action", "")).upper() == "BUY" else "sell"
133
+ mapped = _tiger_order_status(str(getattr(o, "status", "")))
134
+ if status != "all" and mapped != status:
135
+ continue
136
+ contract = getattr(o, "contract", None)
137
+ sym = str(contract.symbol if contract else getattr(o, "symbol", ""))
138
+ result.append(Order(
139
+ order_id=str(getattr(o, "id", getattr(o, "order_id", ""))),
140
+ symbol=sym, name=sym,
141
+ side=side,
142
+ order_type=str(getattr(o, "order_type", "limit")).lower(),
143
+ quantity=float(getattr(o, "quantity", 0)),
144
+ filled_qty=float(getattr(o, "filled", 0)),
145
+ price=float(getattr(o, "limit_price", 0)),
146
+ avg_price=float(getattr(o, "avg_fill_price", 0)),
147
+ status=mapped,
148
+ created_at=str(getattr(o, "order_time", "")),
149
+ currency=str(getattr(o, "currency", "USD")),
150
+ ))
151
+ return result
152
+
153
+ def place_order(self, symbol: str, side: str, quantity: float,
154
+ order_type: str = "limit", price: float = 0.0, **kwargs) -> OrderResult:
155
+ self._require_connected()
156
+ try:
157
+ from tigeropen.trade.domain.order import market_order, limit_order
158
+ from tigeropen.common.consts import Currency, Market
159
+
160
+ if order_type == "market":
161
+ order = market_order(
162
+ account=self._account, symbol=symbol,
163
+ action=side.upper(), quantity=int(quantity),
164
+ )
165
+ else:
166
+ order = limit_order(
167
+ account=self._account, symbol=symbol,
168
+ action=side.upper(), quantity=int(quantity), limit_price=price,
169
+ )
170
+ ret = self._trade_client.place_order(order)
171
+ return OrderResult(success=True, order_id=str(ret), broker_id=self.broker_id)
172
+ except Exception as e:
173
+ return OrderResult(success=False, message=str(e), broker_id=self.broker_id)
174
+
175
+ def cancel_order(self, order_id: str) -> bool:
176
+ self._require_connected()
177
+ try:
178
+ self._trade_client.cancel_order(account=self._account, id=int(order_id))
179
+ return True
180
+ except Exception:
181
+ return False
182
+
183
+ def _require_connected(self):
184
+ if not self._connected:
185
+ raise RuntimeError("老虎证券未连接,请先调用 connect()")
186
+
187
+
188
+ def _tiger_order_status(raw: str) -> str:
189
+ raw = raw.upper()
190
+ if raw in ("FILLED",):
191
+ return "filled"
192
+ if raw in ("PARTIALLY_FILLED",):
193
+ return "partial"
194
+ if raw in ("CANCELLED", "EXPIRED", "REJECTED"):
195
+ return "cancelled"
196
+ return "open"