axdata 0.1.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.
Files changed (427) hide show
  1. axdata/__init__.py +16 -0
  2. axdata/cache.py +19 -0
  3. axdata/client.py +1223 -0
  4. axdata-0.1.0.dist-info/METADATA +238 -0
  5. axdata-0.1.0.dist-info/RECORD +427 -0
  6. axdata-0.1.0.dist-info/WHEEL +5 -0
  7. axdata-0.1.0.dist-info/entry_points.txt +8 -0
  8. axdata-0.1.0.dist-info/licenses/LICENSE +162 -0
  9. axdata-0.1.0.dist-info/top_level.txt +6 -0
  10. axdata_core/__init__.py +221 -0
  11. axdata_core/_tdx_wire/__init__.py +35 -0
  12. axdata_core/_tdx_wire/_shim.py +140 -0
  13. axdata_core/_tdx_wire/api/__init__.py +51 -0
  14. axdata_core/_tdx_wire/api/auction.py +24 -0
  15. axdata_core/_tdx_wire/api/bars.py +24 -0
  16. axdata_core/_tdx_wire/api/base.py +26 -0
  17. axdata_core/_tdx_wire/api/codes.py +25 -0
  18. axdata_core/_tdx_wire/api/corporate.py +24 -0
  19. axdata_core/_tdx_wire/api/finance.py +24 -0
  20. axdata_core/_tdx_wire/api/intraday.py +24 -0
  21. axdata_core/_tdx_wire/api/quotes.py +26 -0
  22. axdata_core/_tdx_wire/api/resources.py +25 -0
  23. axdata_core/_tdx_wire/api/session.py +24 -0
  24. axdata_core/_tdx_wire/api/trades.py +24 -0
  25. axdata_core/_tdx_wire/client.py +33 -0
  26. axdata_core/_tdx_wire/exceptions.py +27 -0
  27. axdata_core/_tdx_wire/hosts.py +106 -0
  28. axdata_core/_tdx_wire/models/__init__.py +93 -0
  29. axdata_core/_tdx_wire/models/auction.py +26 -0
  30. axdata_core/_tdx_wire/models/corporate.py +27 -0
  31. axdata_core/_tdx_wire/models/finance.py +26 -0
  32. axdata_core/_tdx_wire/models/intraday.py +31 -0
  33. axdata_core/_tdx_wire/models/kline.py +27 -0
  34. axdata_core/_tdx_wire/models/quote.py +32 -0
  35. axdata_core/_tdx_wire/models/resource.py +24 -0
  36. axdata_core/_tdx_wire/models/security.py +24 -0
  37. axdata_core/_tdx_wire/models/session.py +27 -0
  38. axdata_core/_tdx_wire/models/subchart.py +25 -0
  39. axdata_core/_tdx_wire/models/trade.py +28 -0
  40. axdata_core/_tdx_wire/protocol/__init__.py +49 -0
  41. axdata_core/_tdx_wire/protocol/commands/__init__.py +43 -0
  42. axdata_core/_tdx_wire/protocol/commands/auction.py +110 -0
  43. axdata_core/_tdx_wire/protocol/commands/codec.py +43 -0
  44. axdata_core/_tdx_wire/protocol/commands/corporate.py +98 -0
  45. axdata_core/_tdx_wire/protocol/commands/finance.py +96 -0
  46. axdata_core/_tdx_wire/protocol/commands/intraday.py +122 -0
  47. axdata_core/_tdx_wire/protocol/commands/klines.py +102 -0
  48. axdata_core/_tdx_wire/protocol/commands/lookup.py +36 -0
  49. axdata_core/_tdx_wire/protocol/commands/price_limits.py +89 -0
  50. axdata_core/_tdx_wire/protocol/commands/quotes.py +102 -0
  51. axdata_core/_tdx_wire/protocol/commands/registry.py +36 -0
  52. axdata_core/_tdx_wire/protocol/commands/resources.py +99 -0
  53. axdata_core/_tdx_wire/protocol/commands/security.py +103 -0
  54. axdata_core/_tdx_wire/protocol/commands/session.py +82 -0
  55. axdata_core/_tdx_wire/protocol/commands/subchart.py +97 -0
  56. axdata_core/_tdx_wire/protocol/commands/trades.py +107 -0
  57. axdata_core/_tdx_wire/protocol/constants.py +124 -0
  58. axdata_core/_tdx_wire/protocol/frame.py +68 -0
  59. axdata_core/_tdx_wire/protocol/unit.py +85 -0
  60. axdata_core/_tdx_wire/py.typed +1 -0
  61. axdata_core/_tdx_wire/transport/__init__.py +34 -0
  62. axdata_core/_tdx_wire/transport/base.py +25 -0
  63. axdata_core/_tdx_wire/transport/memory.py +24 -0
  64. axdata_core/_tdx_wire/transport/pool.py +32 -0
  65. axdata_core/_tdx_wire/transport/socket.py +39 -0
  66. axdata_core/adapters/__init__.py +0 -0
  67. axdata_core/adapters/cls/__init__.py +17 -0
  68. axdata_core/adapters/cls/provider_bridge.py +23 -0
  69. axdata_core/adapters/cls/request.py +632 -0
  70. axdata_core/adapters/cls/source_adapter_registry.py +31 -0
  71. axdata_core/adapters/cninfo/__init__.py +17 -0
  72. axdata_core/adapters/cninfo/provider_bridge.py +25 -0
  73. axdata_core/adapters/cninfo/request.py +2425 -0
  74. axdata_core/adapters/cninfo/source_adapter_registry.py +42 -0
  75. axdata_core/adapters/eastmoney/__init__.py +17 -0
  76. axdata_core/adapters/eastmoney/provider_bridge.py +25 -0
  77. axdata_core/adapters/eastmoney/request.py +1128 -0
  78. axdata_core/adapters/eastmoney/source_adapter_registry.py +42 -0
  79. axdata_core/adapters/exchange/__init__.py +17 -0
  80. axdata_core/adapters/exchange/provider_bridge.py +25 -0
  81. axdata_core/adapters/exchange/request.py +1214 -0
  82. axdata_core/adapters/exchange/source_adapter_registry.py +42 -0
  83. axdata_core/adapters/kph/__init__.py +17 -0
  84. axdata_core/adapters/kph/provider_bridge.py +23 -0
  85. axdata_core/adapters/kph/request.py +509 -0
  86. axdata_core/adapters/kph/source_adapter_registry.py +31 -0
  87. axdata_core/adapters/sina/__init__.py +17 -0
  88. axdata_core/adapters/sina/provider_bridge.py +25 -0
  89. axdata_core/adapters/sina/request.py +5155 -0
  90. axdata_core/adapters/sina/source_adapter_registry.py +42 -0
  91. axdata_core/adapters/tdx/__init__.py +32 -0
  92. axdata_core/adapters/tdx/adjustment.py +50 -0
  93. axdata_core/adapters/tdx/adjustment_fetch.py +51 -0
  94. axdata_core/adapters/tdx/auction_fetch.py +51 -0
  95. axdata_core/adapters/tdx/client_factory.py +110 -0
  96. axdata_core/adapters/tdx/code_fetch.py +62 -0
  97. axdata_core/adapters/tdx/codes.py +54 -0
  98. axdata_core/adapters/tdx/derived_rows.py +69 -0
  99. axdata_core/adapters/tdx/downloader.py +193 -0
  100. axdata_core/adapters/tdx/downloader_interface_sets.py +50 -0
  101. axdata_core/adapters/tdx/downloader_profiles.py +43 -0
  102. axdata_core/adapters/tdx/downloader_registry.py +115 -0
  103. axdata_core/adapters/tdx/execution_utils.py +55 -0
  104. axdata_core/adapters/tdx/f10_executor.py +57 -0
  105. axdata_core/adapters/tdx/f10_normalize.py +61 -0
  106. axdata_core/adapters/tdx/f10_params.py +57 -0
  107. axdata_core/adapters/tdx/f10_postprocess.py +67 -0
  108. axdata_core/adapters/tdx/f10_render.py +48 -0
  109. axdata_core/adapters/tdx/f10_request.py +62 -0
  110. axdata_core/adapters/tdx/finance_fetch.py +61 -0
  111. axdata_core/adapters/tdx/finance_maps.py +54 -0
  112. axdata_core/adapters/tdx/finance_normalize.py +57 -0
  113. axdata_core/adapters/tdx/host_config.py +115 -0
  114. axdata_core/adapters/tdx/interface_sets.py +50 -0
  115. axdata_core/adapters/tdx/intraday_fetch.py +68 -0
  116. axdata_core/adapters/tdx/kline_helpers.py +58 -0
  117. axdata_core/adapters/tdx/limit_ladder_fetch.py +60 -0
  118. axdata_core/adapters/tdx/limit_ladder_topics.py +53 -0
  119. axdata_core/adapters/tdx/normalize_utils.py +75 -0
  120. axdata_core/adapters/tdx/options.py +61 -0
  121. axdata_core/adapters/tdx/price_limit_calendar.py +49 -0
  122. axdata_core/adapters/tdx/price_limit_fetch.py +53 -0
  123. axdata_core/adapters/tdx/price_limit_history.py +50 -0
  124. axdata_core/adapters/tdx/price_limits.py +58 -0
  125. axdata_core/adapters/tdx/provider_bridge.py +68 -0
  126. axdata_core/adapters/tdx/quote_fetch.py +61 -0
  127. axdata_core/adapters/tdx/quote_identity.py +49 -0
  128. axdata_core/adapters/tdx/rank_fetch.py +55 -0
  129. axdata_core/adapters/tdx/rank_params.py +52 -0
  130. axdata_core/adapters/tdx/realtime_refresh.py +45 -0
  131. axdata_core/adapters/tdx/request.py +11 -0
  132. axdata_core/adapters/tdx/request_adapter_runtime.py +41 -0
  133. axdata_core/adapters/tdx/request_client.py +51 -0
  134. axdata_core/adapters/tdx/request_compat.py +89 -0
  135. axdata_core/adapters/tdx/request_dispatch.py +50 -0
  136. axdata_core/adapters/tdx/request_filters.py +60 -0
  137. axdata_core/adapters/tdx/request_host_config.py +110 -0
  138. axdata_core/adapters/tdx/request_limits.py +59 -0
  139. axdata_core/adapters/tdx/request_methods.py +41 -0
  140. axdata_core/adapters/tdx/request_params.py +62 -0
  141. axdata_core/adapters/tdx/request_seams.py +41 -0
  142. axdata_core/adapters/tdx/resources/__init__.py +0 -0
  143. axdata_core/adapters/tdx/resources/finance_maps/__init__.py +0 -0
  144. axdata_core/adapters/tdx/resources/finance_maps/incon.dat +3702 -0
  145. axdata_core/adapters/tdx/resources/finance_maps/tdxhy.cfg +5620 -0
  146. axdata_core/adapters/tdx/resources/finance_maps/tdxzs.cfg +605 -0
  147. axdata_core/adapters/tdx/security_codes.py +54 -0
  148. axdata_core/adapters/tdx/series_history.py +57 -0
  149. axdata_core/adapters/tdx/server_cache.py +58 -0
  150. axdata_core/adapters/tdx/snapshot_normalize.py +56 -0
  151. axdata_core/adapters/tdx/source_adapter_registry.py +69 -0
  152. axdata_core/adapters/tdx/source_execution.py +44 -0
  153. axdata_core/adapters/tdx/source_execution_registry.py +70 -0
  154. axdata_core/adapters/tdx/stats_cache.py +184 -0
  155. axdata_core/adapters/tdx/stats_models.py +159 -0
  156. axdata_core/adapters/tdx/stats_resource.py +304 -0
  157. axdata_core/adapters/tdx/status_fetch.py +55 -0
  158. axdata_core/adapters/tdx/time_series_normalize.py +67 -0
  159. axdata_core/adapters/tdx/tqlex.py +50 -0
  160. axdata_core/adapters/tdx/wire_requests.py +60 -0
  161. axdata_core/adapters/tdx_ext/__init__.py +66 -0
  162. axdata_core/adapters/tdx_ext/client.py +688 -0
  163. axdata_core/adapters/tdx_ext/exceptions.py +51 -0
  164. axdata_core/adapters/tdx_ext/host_config.py +40 -0
  165. axdata_core/adapters/tdx_ext/interface_sets.py +65 -0
  166. axdata_core/adapters/tdx_ext/local_cache.py +649 -0
  167. axdata_core/adapters/tdx_ext/models.py +96 -0
  168. axdata_core/adapters/tdx_ext/options.py +36 -0
  169. axdata_core/adapters/tdx_ext/pool.py +153 -0
  170. axdata_core/adapters/tdx_ext/provider_bridge.py +35 -0
  171. axdata_core/adapters/tdx_ext/request.py +498 -0
  172. axdata_core/adapters/tdx_ext/request_execution.py +118 -0
  173. axdata_core/adapters/tdx_ext/request_instruments.py +221 -0
  174. axdata_core/adapters/tdx_ext/request_normalize.py +356 -0
  175. axdata_core/adapters/tdx_ext/request_params.py +157 -0
  176. axdata_core/adapters/tdx_ext/request_series.py +98 -0
  177. axdata_core/adapters/tdx_ext/server_cache.py +13 -0
  178. axdata_core/adapters/tdx_ext/servers.py +35 -0
  179. axdata_core/adapters/tdx_ext/source_adapter_registry.py +42 -0
  180. axdata_core/adapters/tdx_ext/source_execution.py +19 -0
  181. axdata_core/adapters/tdx_ext/source_execution_registry.py +43 -0
  182. axdata_core/adapters/tencent/__init__.py +17 -0
  183. axdata_core/adapters/tencent/provider_bridge.py +25 -0
  184. axdata_core/adapters/tencent/request.py +729 -0
  185. axdata_core/adapters/tencent/source_adapter_registry.py +42 -0
  186. axdata_core/axp.py +1843 -0
  187. axdata_core/builtin_providers.py +586 -0
  188. axdata_core/builtin_source_declarations.py +156 -0
  189. axdata_core/cli.py +1818 -0
  190. axdata_core/collector_registry.py +227 -0
  191. axdata_core/collector_runner.py +1016 -0
  192. axdata_core/collector_scheduler.py +3155 -0
  193. axdata_core/collector_templates.py +339 -0
  194. axdata_core/data_browser.py +1696 -0
  195. axdata_core/diagnostics.py +734 -0
  196. axdata_core/downloader_engine.py +1480 -0
  197. axdata_core/downloader_registry.py +62 -0
  198. axdata_core/downloaders.py +1619 -0
  199. axdata_core/legacy_provider_adapter.py +68 -0
  200. axdata_core/paths.py +51 -0
  201. axdata_core/plugin_config.py +272 -0
  202. axdata_core/plugin_status.py +419 -0
  203. axdata_core/plugins.py +1363 -0
  204. axdata_core/provider_catalog.py +462 -0
  205. axdata_core/provider_registry.py +833 -0
  206. axdata_core/quality.py +86 -0
  207. axdata_core/query.py +242 -0
  208. axdata_core/resources/__init__.py +2 -0
  209. axdata_core/resources/tdx_extended_servers.json +22 -0
  210. axdata_core/resources/tdx_quote_servers.json +49 -0
  211. axdata_core/sample_collectors.py +42 -0
  212. axdata_core/schema.py +385 -0
  213. axdata_core/source_adapter_factory.py +95 -0
  214. axdata_core/source_adapter_options.py +18 -0
  215. axdata_core/source_errors.py +43 -0
  216. axdata_core/source_execution_options.py +37 -0
  217. axdata_core/source_execution_registry.py +27 -0
  218. axdata_core/source_projection.py +21 -0
  219. axdata_core/source_request.py +441 -0
  220. axdata_core/source_session.py +355 -0
  221. axdata_core/sources/__init__.py +40 -0
  222. axdata_core/sources/base.py +112 -0
  223. axdata_core/sources/catalog.py +60 -0
  224. axdata_core/sources/cls/__init__.py +5 -0
  225. axdata_core/sources/cls/catalog.py +231 -0
  226. axdata_core/sources/cninfo/__init__.py +5 -0
  227. axdata_core/sources/cninfo/catalog.py +1994 -0
  228. axdata_core/sources/display_docs.py +139 -0
  229. axdata_core/sources/eastmoney/__init__.py +5 -0
  230. axdata_core/sources/eastmoney/catalog.py +509 -0
  231. axdata_core/sources/exchange/__init__.py +5 -0
  232. axdata_core/sources/exchange/catalog.py +361 -0
  233. axdata_core/sources/kph/__init__.py +5 -0
  234. axdata_core/sources/kph/catalog.py +255 -0
  235. axdata_core/sources/sina/__init__.py +5 -0
  236. axdata_core/sources/sina/catalog.py +2951 -0
  237. axdata_core/sources/tdx/__init__.py +5 -0
  238. axdata_core/sources/tdx/catalog.py +4546 -0
  239. axdata_core/sources/tdx_ext/__init__.py +5 -0
  240. axdata_core/sources/tdx_ext/catalog.py +1518 -0
  241. axdata_core/sources/tencent/__init__.py +5 -0
  242. axdata_core/sources/tencent/catalog.py +370 -0
  243. axdata_core/storage.py +186 -0
  244. axdata_core/tdx_f10_catalog.py +52 -0
  245. axdata_core/tdx_f10_models.py +50 -0
  246. axdata_core/tdx_f10_names.py +47 -0
  247. axdata_core/tdx_f10_specs.py +56 -0
  248. axdata_core/tdx_plugin_required.py +43 -0
  249. axdata_core/tdx_server_config.py +65 -0
  250. axdata_core/trade_calendar_cache.py +397 -0
  251. axdata_source_cninfo/__init__.py +15 -0
  252. axdata_source_cninfo/adapter.py +31 -0
  253. axdata_source_cninfo/axdata-provider.json +384 -0
  254. axdata_source_cninfo/catalog.py +209 -0
  255. axdata_source_cninfo/metadata.py +8 -0
  256. axdata_source_cninfo/provider.py +41 -0
  257. axdata_source_tdx/__init__.py +15 -0
  258. axdata_source_tdx/_tdx_wire/__init__.py +26 -0
  259. axdata_source_tdx/_tdx_wire/_binary.py +218 -0
  260. axdata_source_tdx/_tdx_wire/_code_utils.py +42 -0
  261. axdata_source_tdx/_tdx_wire/_command_codec.py +186 -0
  262. axdata_source_tdx/_tdx_wire/_command_codes.py +74 -0
  263. axdata_source_tdx/_tdx_wire/_command_defaults.py +15 -0
  264. axdata_source_tdx/_tdx_wire/_command_dispatch.py +96 -0
  265. axdata_source_tdx/_tdx_wire/_command_layouts.py +96 -0
  266. axdata_source_tdx/_tdx_wire/_command_lookup.py +25 -0
  267. axdata_source_tdx/_tdx_wire/_command_metadata.py +32 -0
  268. axdata_source_tdx/_tdx_wire/_command_registry.py +61 -0
  269. axdata_source_tdx/_tdx_wire/_connection_defaults.py +13 -0
  270. axdata_source_tdx/_tdx_wire/_host_defaults.py +51 -0
  271. axdata_source_tdx/_tdx_wire/_host_probe.py +81 -0
  272. axdata_source_tdx/_tdx_wire/_host_resource.py +70 -0
  273. axdata_source_tdx/_tdx_wire/_host_utils.py +31 -0
  274. axdata_source_tdx/_tdx_wire/_market.py +66 -0
  275. axdata_source_tdx/_tdx_wire/_request_defaults.py +11 -0
  276. axdata_source_tdx/_tdx_wire/_security_classification.py +61 -0
  277. axdata_source_tdx/_tdx_wire/_time_utils.py +7 -0
  278. axdata_source_tdx/_tdx_wire/api/__init__.py +45 -0
  279. axdata_source_tdx/_tdx_wire/api/auction.py +25 -0
  280. axdata_source_tdx/_tdx_wire/api/bars.py +31 -0
  281. axdata_source_tdx/_tdx_wire/api/base.py +39 -0
  282. axdata_source_tdx/_tdx_wire/api/codes.py +25 -0
  283. axdata_source_tdx/_tdx_wire/api/corporate.py +11 -0
  284. axdata_source_tdx/_tdx_wire/api/finance.py +10 -0
  285. axdata_source_tdx/_tdx_wire/api/intraday.py +38 -0
  286. axdata_source_tdx/_tdx_wire/api/quotes.py +78 -0
  287. axdata_source_tdx/_tdx_wire/api/resources.py +34 -0
  288. axdata_source_tdx/_tdx_wire/api/session.py +16 -0
  289. axdata_source_tdx/_tdx_wire/api/trades.py +34 -0
  290. axdata_source_tdx/_tdx_wire/client.py +470 -0
  291. axdata_source_tdx/_tdx_wire/exceptions.py +25 -0
  292. axdata_source_tdx/_tdx_wire/hosts.py +27 -0
  293. axdata_source_tdx/_tdx_wire/models/__init__.py +87 -0
  294. axdata_source_tdx/_tdx_wire/models/auction.py +58 -0
  295. axdata_source_tdx/_tdx_wire/models/corporate.py +59 -0
  296. axdata_source_tdx/_tdx_wire/models/finance.py +80 -0
  297. axdata_source_tdx/_tdx_wire/models/intraday.py +112 -0
  298. axdata_source_tdx/_tdx_wire/models/kline.py +58 -0
  299. axdata_source_tdx/_tdx_wire/models/quote.py +262 -0
  300. axdata_source_tdx/_tdx_wire/models/resource.py +18 -0
  301. axdata_source_tdx/_tdx_wire/models/security.py +28 -0
  302. axdata_source_tdx/_tdx_wire/models/session.py +30 -0
  303. axdata_source_tdx/_tdx_wire/models/subchart.py +39 -0
  304. axdata_source_tdx/_tdx_wire/models/trade.py +46 -0
  305. axdata_source_tdx/_tdx_wire/protocol/__init__.py +43 -0
  306. axdata_source_tdx/_tdx_wire/protocol/_frame_constants.py +5 -0
  307. axdata_source_tdx/_tdx_wire/protocol/commands/__init__.py +37 -0
  308. axdata_source_tdx/_tdx_wire/protocol/commands/auction.py +165 -0
  309. axdata_source_tdx/_tdx_wire/protocol/commands/codec.py +37 -0
  310. axdata_source_tdx/_tdx_wire/protocol/commands/corporate.py +163 -0
  311. axdata_source_tdx/_tdx_wire/protocol/commands/finance.py +180 -0
  312. axdata_source_tdx/_tdx_wire/protocol/commands/intraday.py +382 -0
  313. axdata_source_tdx/_tdx_wire/protocol/commands/klines.py +396 -0
  314. axdata_source_tdx/_tdx_wire/protocol/commands/lookup.py +31 -0
  315. axdata_source_tdx/_tdx_wire/protocol/commands/price_limits.py +110 -0
  316. axdata_source_tdx/_tdx_wire/protocol/commands/quotes.py +804 -0
  317. axdata_source_tdx/_tdx_wire/protocol/commands/registry.py +30 -0
  318. axdata_source_tdx/_tdx_wire/protocol/commands/resources.py +107 -0
  319. axdata_source_tdx/_tdx_wire/protocol/commands/security.py +163 -0
  320. axdata_source_tdx/_tdx_wire/protocol/commands/session.py +129 -0
  321. axdata_source_tdx/_tdx_wire/protocol/commands/subchart.py +182 -0
  322. axdata_source_tdx/_tdx_wire/protocol/commands/trades.py +275 -0
  323. axdata_source_tdx/_tdx_wire/protocol/constants.py +70 -0
  324. axdata_source_tdx/_tdx_wire/protocol/frame.py +141 -0
  325. axdata_source_tdx/_tdx_wire/protocol/unit.py +98 -0
  326. axdata_source_tdx/_tdx_wire/py.typed +1 -0
  327. axdata_source_tdx/_tdx_wire/transport/__init__.py +28 -0
  328. axdata_source_tdx/_tdx_wire/transport/base.py +21 -0
  329. axdata_source_tdx/_tdx_wire/transport/memory.py +42 -0
  330. axdata_source_tdx/_tdx_wire/transport/pool.py +148 -0
  331. axdata_source_tdx/_tdx_wire/transport/socket.py +373 -0
  332. axdata_source_tdx/adapter.py +31 -0
  333. axdata_source_tdx/adjustment.py +163 -0
  334. axdata_source_tdx/adjustment_fetch.py +148 -0
  335. axdata_source_tdx/auction_fetch.py +162 -0
  336. axdata_source_tdx/axdata-provider.json +23680 -0
  337. axdata_source_tdx/catalog.py +39 -0
  338. axdata_source_tdx/client_factory.py +61 -0
  339. axdata_source_tdx/code_fetch.py +579 -0
  340. axdata_source_tdx/codes.py +69 -0
  341. axdata_source_tdx/collectors.py +525 -0
  342. axdata_source_tdx/derived_rows.py +500 -0
  343. axdata_source_tdx/downloader.py +139 -0
  344. axdata_source_tdx/downloader_interface_sets.py +29 -0
  345. axdata_source_tdx/downloader_profiles.py +731 -0
  346. axdata_source_tdx/downloader_registry.py +85 -0
  347. axdata_source_tdx/execution_utils.py +141 -0
  348. axdata_source_tdx/f10_executor.py +414 -0
  349. axdata_source_tdx/f10_normalize.py +169 -0
  350. axdata_source_tdx/f10_params.py +221 -0
  351. axdata_source_tdx/f10_postprocess.py +405 -0
  352. axdata_source_tdx/f10_render.py +29 -0
  353. axdata_source_tdx/f10_request.py +365 -0
  354. axdata_source_tdx/finance_fetch.py +514 -0
  355. axdata_source_tdx/finance_maps.py +377 -0
  356. axdata_source_tdx/finance_normalize.py +273 -0
  357. axdata_source_tdx/host_config.py +51 -0
  358. axdata_source_tdx/interface_sets.py +180 -0
  359. axdata_source_tdx/intraday_fetch.py +600 -0
  360. axdata_source_tdx/kline_helpers.py +333 -0
  361. axdata_source_tdx/limit_ladder_fetch.py +614 -0
  362. axdata_source_tdx/limit_ladder_topics.py +280 -0
  363. axdata_source_tdx/metadata.py +8 -0
  364. axdata_source_tdx/normalize_utils.py +264 -0
  365. axdata_source_tdx/options.py +195 -0
  366. axdata_source_tdx/price_limit_calendar.py +106 -0
  367. axdata_source_tdx/price_limit_fetch.py +264 -0
  368. axdata_source_tdx/price_limit_history.py +100 -0
  369. axdata_source_tdx/price_limits.py +142 -0
  370. axdata_source_tdx/provider.py +44 -0
  371. axdata_source_tdx/provider_bridge.py +43 -0
  372. axdata_source_tdx/quote_fetch.py +485 -0
  373. axdata_source_tdx/quote_identity.py +50 -0
  374. axdata_source_tdx/rank_fetch.py +316 -0
  375. axdata_source_tdx/rank_params.py +181 -0
  376. axdata_source_tdx/realtime_refresh.py +51 -0
  377. axdata_source_tdx/request_adapter.py +533 -0
  378. axdata_source_tdx/request_adapter_runtime.py +302 -0
  379. axdata_source_tdx/request_client.py +98 -0
  380. axdata_source_tdx/request_compat.py +432 -0
  381. axdata_source_tdx/request_dispatch.py +138 -0
  382. axdata_source_tdx/request_entrypoints.py +146 -0
  383. axdata_source_tdx/request_filters.py +154 -0
  384. axdata_source_tdx/request_host_config.py +103 -0
  385. axdata_source_tdx/request_limits.py +15 -0
  386. axdata_source_tdx/request_methods.py +1910 -0
  387. axdata_source_tdx/request_params.py +225 -0
  388. axdata_source_tdx/request_seams.py +534 -0
  389. axdata_source_tdx/resources/__init__.py +0 -0
  390. axdata_source_tdx/resources/finance_maps/__init__.py +1 -0
  391. axdata_source_tdx/resources/finance_maps/incon.dat +3702 -0
  392. axdata_source_tdx/resources/finance_maps/tdxhy.cfg +5620 -0
  393. axdata_source_tdx/resources/finance_maps/tdxzs.cfg +605 -0
  394. axdata_source_tdx/resources/tdx_extended_servers.json +22 -0
  395. axdata_source_tdx/resources/tdx_quote_servers.json +49 -0
  396. axdata_source_tdx/security_codes.py +127 -0
  397. axdata_source_tdx/series_history.py +348 -0
  398. axdata_source_tdx/server_cache.py +21 -0
  399. axdata_source_tdx/snapshot_normalize.py +232 -0
  400. axdata_source_tdx/source_adapter_registry.py +42 -0
  401. axdata_source_tdx/source_execution.py +22 -0
  402. axdata_source_tdx/source_execution_registry.py +43 -0
  403. axdata_source_tdx/stats_cache.py +150 -0
  404. axdata_source_tdx/stats_models.py +161 -0
  405. axdata_source_tdx/stats_resource.py +242 -0
  406. axdata_source_tdx/status_fetch.py +209 -0
  407. axdata_source_tdx/tdx_f10_catalog.py +957 -0
  408. axdata_source_tdx/tdx_f10_models.py +47 -0
  409. axdata_source_tdx/tdx_f10_names.py +48 -0
  410. axdata_source_tdx/tdx_f10_specs.py +1142 -0
  411. axdata_source_tdx/tdx_server_config.py +389 -0
  412. axdata_source_tdx/time_series_normalize.py +370 -0
  413. axdata_source_tdx/tqlex.py +138 -0
  414. axdata_source_tdx/wire.py +32 -0
  415. axdata_source_tdx/wire_requests.py +190 -0
  416. axdata_source_tdx_ext/__init__.py +15 -0
  417. axdata_source_tdx_ext/adapter.py +31 -0
  418. axdata_source_tdx_ext/axdata-provider.json +9424 -0
  419. axdata_source_tdx_ext/catalog.py +33 -0
  420. axdata_source_tdx_ext/metadata.py +8 -0
  421. axdata_source_tdx_ext/provider.py +44 -0
  422. axdata_source_tencent/__init__.py +15 -0
  423. axdata_source_tencent/adapter.py +31 -0
  424. axdata_source_tencent/axdata-provider.json +291 -0
  425. axdata_source_tencent/catalog.py +190 -0
  426. axdata_source_tencent/metadata.py +8 -0
  427. axdata_source_tencent/provider.py +41 -0
axdata/client.py ADDED
@@ -0,0 +1,1223 @@
1
+ """Python client for AxData local and remote data access."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import json
7
+ import time
8
+ from collections.abc import Iterator, Mapping
9
+ from datetime import datetime, timezone
10
+ from pathlib import Path
11
+ from types import SimpleNamespace
12
+ from typing import Any
13
+ from urllib.parse import urlencode, urlparse, urlunparse
14
+ from uuid import uuid4
15
+
16
+ import requests
17
+
18
+
19
+ DEFAULT_API_BASE = "http://127.0.0.1:8666"
20
+ BackendMode = str
21
+ DEFAULT_LIMIT = 1000
22
+ LOCAL_STREAM_NAME = "stock_quote_refresh_tdx"
23
+ LOCAL_STREAM_DEFAULT_INTERVAL_MS = 3000
24
+ LOCAL_STREAM_MIN_INTERVAL_MS = 500
25
+ LOCAL_STREAM_MAX_CODES = 100
26
+
27
+
28
+ class AxDataError(RuntimeError):
29
+ """Raised when AxData returns an invalid or failed response."""
30
+
31
+
32
+ class AxDataClient:
33
+ """User-facing AxData client.
34
+
35
+ The default backend is local: it reads the current machine's AxData data
36
+ directory through axdata_core and does not require an API service. Pass
37
+ api_base, set AXDATA_API_BASE, or use mode="api" to read a LAN/server AxData
38
+ service through HTTP.
39
+ """
40
+
41
+ def __init__(
42
+ self,
43
+ token: str | None = None,
44
+ api_base: str | None = None,
45
+ data_root: str | Path | None = None,
46
+ data_dir: str | Path | None = None,
47
+ data_home: str | Path | None = None,
48
+ mode: BackendMode = "auto",
49
+ backend: BackendMode | None = None,
50
+ session: requests.Session | None = None,
51
+ timeout: float = 30.0,
52
+ ) -> None:
53
+ if backend is not None and mode != "auto":
54
+ raise ValueError("Use either mode or backend, not both.")
55
+ if backend is None and mode == "auto" and api_base is None:
56
+ backend = os.getenv("AXDATA_BACKEND")
57
+ if backend is not None:
58
+ mode = backend
59
+ if mode not in {"auto", "local", "api"}:
60
+ raise ValueError("backend/mode must be one of: auto, local, api")
61
+
62
+ self.token = token if token is not None else os.getenv("AXDATA_TOKEN")
63
+ env_api_base = os.getenv("AXDATA_API_BASE")
64
+ resolved_api_base = api_base or env_api_base
65
+ if mode == "api" and not resolved_api_base:
66
+ resolved_api_base = DEFAULT_API_BASE
67
+
68
+ if mode == "auto":
69
+ self.mode = "api" if resolved_api_base else "local"
70
+ else:
71
+ self.mode = mode
72
+ self.backend = self.mode
73
+
74
+ self.api_base = resolved_api_base.rstrip("/") if resolved_api_base else None
75
+ self.data_root = self._resolve_data_root(
76
+ data_root=data_root,
77
+ data_dir=data_dir,
78
+ data_home=data_home,
79
+ )
80
+ self._http_session = session or requests.Session()
81
+ self.timeout = timeout
82
+
83
+ @classmethod
84
+ def local(
85
+ cls,
86
+ *,
87
+ data_root: str | Path | None = None,
88
+ data_dir: str | Path | None = None,
89
+ data_home: str | Path | None = None,
90
+ ) -> "AxDataClient":
91
+ """Create a client that reads the current machine's AxData data directory."""
92
+
93
+ return cls(mode="local", data_root=data_root, data_dir=data_dir, data_home=data_home)
94
+
95
+ @classmethod
96
+ def api(
97
+ cls,
98
+ api_base: str = DEFAULT_API_BASE,
99
+ *,
100
+ token: str | None = None,
101
+ session: requests.Session | None = None,
102
+ timeout: float = 30.0,
103
+ ) -> "AxDataClient":
104
+ """Create a client that reads a local/LAN/server AxData API service."""
105
+
106
+ return cls(
107
+ token=token,
108
+ api_base=api_base,
109
+ mode="api",
110
+ session=session,
111
+ timeout=timeout,
112
+ )
113
+
114
+ def query(
115
+ self,
116
+ api_name: str,
117
+ fields: str | list[str] | tuple[str, ...] | None = None,
118
+ **params: Any,
119
+ ):
120
+ """Query an AxData table and return records as a pandas.DataFrame."""
121
+
122
+ if not api_name:
123
+ raise ValueError("api_name is required")
124
+
125
+ if self.mode == "local":
126
+ return self._query_local(api_name, fields=fields, **params)
127
+ return self._query_api(api_name, fields=fields, **params)
128
+
129
+ def call(
130
+ self,
131
+ interface: str,
132
+ fields: str | list[str] | tuple[str, ...] | None = None,
133
+ options: Mapping[str, Any] | None = None,
134
+ **params: Any,
135
+ ):
136
+ """Call a provider-style source interface and return a pandas.DataFrame."""
137
+
138
+ if not interface:
139
+ raise ValueError("interface is required")
140
+
141
+ if self.mode == "local":
142
+ return self._call_local(interface, fields=fields, options=options, **params)
143
+ return self._call_api(interface, fields=fields, options=options, **params)
144
+
145
+ def download(
146
+ self,
147
+ interface: str,
148
+ fields: str | list[str] | tuple[str, ...] | None = None,
149
+ output_root: str | Path | None = None,
150
+ output_dir: str | Path | None = None,
151
+ formats: str | list[str] | tuple[str, ...] | None = None,
152
+ collect_mode: str | None = None,
153
+ connection_mode: str | None = None,
154
+ connection_count: int | None = None,
155
+ source_server_count: int | None = None,
156
+ connections_per_server: int | None = None,
157
+ max_concurrent_tasks: int | None = None,
158
+ batch_size: int | None = None,
159
+ request_interval_ms: int | None = None,
160
+ retry_count: int | None = None,
161
+ timeout_ms: int | None = None,
162
+ **params: Any,
163
+ ) -> SimpleNamespace:
164
+ """Run a configured downloader and return job metadata."""
165
+
166
+ if not interface:
167
+ raise ValueError("interface is required")
168
+
169
+ if self.mode == "local":
170
+ return self._download_local(
171
+ interface,
172
+ fields=fields,
173
+ output_root=output_root,
174
+ output_dir=output_dir,
175
+ formats=formats,
176
+ collect_mode=collect_mode,
177
+ connection_mode=connection_mode,
178
+ connection_count=connection_count,
179
+ source_server_count=source_server_count,
180
+ connections_per_server=connections_per_server,
181
+ max_concurrent_tasks=max_concurrent_tasks,
182
+ batch_size=batch_size,
183
+ request_interval_ms=request_interval_ms,
184
+ retry_count=retry_count,
185
+ timeout_ms=timeout_ms,
186
+ **params,
187
+ )
188
+ return self._download_api(
189
+ interface,
190
+ fields=fields,
191
+ output_root=output_root,
192
+ output_dir=output_dir,
193
+ formats=formats,
194
+ collect_mode=collect_mode,
195
+ connection_mode=connection_mode,
196
+ connection_count=connection_count,
197
+ source_server_count=source_server_count,
198
+ connections_per_server=connections_per_server,
199
+ max_concurrent_tasks=max_concurrent_tasks,
200
+ batch_size=batch_size,
201
+ request_interval_ms=request_interval_ms,
202
+ retry_count=retry_count,
203
+ timeout_ms=timeout_ms,
204
+ **params,
205
+ )
206
+
207
+ def stream(
208
+ self,
209
+ stream: str,
210
+ fields: str | list[str] | tuple[str, ...] | None = None,
211
+ **params: Any,
212
+ ) -> "AxDataStream | LocalAxDataStream":
213
+ """Open an AxData realtime stream.
214
+
215
+ Local clients run supported streams in-process without requiring the
216
+ AxData API service. API clients keep using the `/v1/stream/{stream}`
217
+ WebSocket route.
218
+ """
219
+
220
+ if not stream:
221
+ raise ValueError("stream is required")
222
+ stream_params = {
223
+ str(key): self._normalize_param_value(value)
224
+ for key, value in params.items()
225
+ if value is not None
226
+ }
227
+ selected_fields = self._normalize_fields(fields) if fields is not None else None
228
+ if selected_fields is not None:
229
+ stream_params["fields"] = selected_fields
230
+ if self.mode == "local":
231
+ return LocalAxDataStream(
232
+ stream=stream,
233
+ params=stream_params,
234
+ data_root=self.data_root,
235
+ timeout=self.timeout,
236
+ )
237
+ return AxDataStream(
238
+ api_base=self.api_base or DEFAULT_API_BASE,
239
+ stream=stream,
240
+ params=stream_params,
241
+ token=self.token,
242
+ timeout=self.timeout,
243
+ )
244
+
245
+ def session(
246
+ self,
247
+ source: str,
248
+ options: Mapping[str, Any] | None = None,
249
+ **source_options: Any,
250
+ ) -> "AxDataLocalSession":
251
+ """Open a local high-frequency source session for TDX/TDX_EXT."""
252
+
253
+ if not source:
254
+ raise ValueError("source is required")
255
+ if self.mode != "local":
256
+ raise AxDataError(
257
+ "client.session(...) is local-only. Create AxDataClient() without api_base "
258
+ "to reuse local TDX/TDX_EXT connections; API clients should use call(...) or stream(...)."
259
+ )
260
+ resolved_options = self._normalize_options(options or {})
261
+ resolved_options.update(
262
+ {
263
+ str(key): self._normalize_param_value(value)
264
+ for key, value in source_options.items()
265
+ if value is not None
266
+ }
267
+ )
268
+ return AxDataLocalSession(
269
+ client=self,
270
+ source=source,
271
+ options=resolved_options,
272
+ )
273
+
274
+ def stock_quote_refresh_tdx(
275
+ self,
276
+ fields: str | list[str] | tuple[str, ...] | None = None,
277
+ **params: Any,
278
+ ) -> "AxDataStream":
279
+ """Subscribe to TDX stock quote refresh events."""
280
+
281
+ return self.stream("stock_quote_refresh_tdx", fields=fields, **params)
282
+
283
+ def request_interface(
284
+ self,
285
+ interface: str,
286
+ fields: str | list[str] | tuple[str, ...] | None = None,
287
+ options: Mapping[str, Any] | None = None,
288
+ **params: Any,
289
+ ):
290
+ """Compatibility helper for :meth:`call`."""
291
+
292
+ return self.call(interface, fields=fields, options=options, **params)
293
+
294
+ def _query_api(
295
+ self,
296
+ api_name: str,
297
+ fields: str | list[str] | tuple[str, ...] | None = None,
298
+ **params: Any,
299
+ ):
300
+ if not self.api_base:
301
+ raise AxDataError("api_base is required for API mode")
302
+
303
+ query_parts = self._split_query_params(params)
304
+ payload: dict[str, Any] = {
305
+ "table": api_name,
306
+ "params": query_parts["params"],
307
+ }
308
+ if fields is not None:
309
+ payload["fields"] = self._normalize_fields(fields)
310
+ if query_parts["filters"]:
311
+ payload["filters"] = query_parts["filters"]
312
+ if query_parts["start_date"] is not None:
313
+ payload["start_date"] = query_parts["start_date"]
314
+ if query_parts["end_date"] is not None:
315
+ payload["end_date"] = query_parts["end_date"]
316
+ if query_parts["limit"] is not None:
317
+ payload["limit"] = query_parts["limit"]
318
+
319
+ response = self._http_session.post(
320
+ f"{self.api_base}/v1/query",
321
+ json=payload,
322
+ headers=self._headers(),
323
+ timeout=self.timeout,
324
+ )
325
+ self._raise_for_status(response)
326
+ data = response.json()
327
+ records = self._extract_records(data)
328
+ return self._to_dataframe(records)
329
+
330
+ def _call_api(
331
+ self,
332
+ interface: str,
333
+ fields: str | list[str] | tuple[str, ...] | None = None,
334
+ options: Mapping[str, Any] | None = None,
335
+ **params: Any,
336
+ ):
337
+ if not self.api_base:
338
+ raise AxDataError("api_base is required for API mode")
339
+
340
+ payload: dict[str, Any] = {
341
+ "params": {
342
+ str(key): self._normalize_param_value(value)
343
+ for key, value in params.items()
344
+ if value is not None
345
+ },
346
+ }
347
+ if fields is not None:
348
+ payload["fields"] = self._normalize_fields(fields)
349
+ if options:
350
+ payload["options"] = self._normalize_options(options)
351
+
352
+ response = self._http_session.post(
353
+ f"{self.api_base}/v1/request/{interface}",
354
+ json=payload,
355
+ headers=self._headers(),
356
+ timeout=self.timeout,
357
+ )
358
+ self._raise_for_status(response)
359
+ data = response.json()
360
+ records = self._extract_records(data)
361
+ return self._to_dataframe(records)
362
+
363
+ def _download_api(
364
+ self,
365
+ interface: str,
366
+ fields: str | list[str] | tuple[str, ...] | None = None,
367
+ output_root: str | Path | None = None,
368
+ output_dir: str | Path | None = None,
369
+ formats: str | list[str] | tuple[str, ...] | None = None,
370
+ collect_mode: str | None = None,
371
+ connection_mode: str | None = None,
372
+ connection_count: int | None = None,
373
+ source_server_count: int | None = None,
374
+ connections_per_server: int | None = None,
375
+ max_concurrent_tasks: int | None = None,
376
+ batch_size: int | None = None,
377
+ request_interval_ms: int | None = None,
378
+ retry_count: int | None = None,
379
+ timeout_ms: int | None = None,
380
+ **params: Any,
381
+ ) -> SimpleNamespace:
382
+ if not self.api_base:
383
+ raise AxDataError("api_base is required for API mode")
384
+
385
+ payload: dict[str, Any] = {
386
+ "params": {
387
+ str(key): self._normalize_param_value(value)
388
+ for key, value in params.items()
389
+ if value is not None
390
+ },
391
+ }
392
+ if fields is not None:
393
+ payload["fields"] = self._normalize_fields(fields)
394
+ if output_root is not None:
395
+ payload["output_root"] = str(output_root)
396
+ if output_dir is not None:
397
+ payload["output_dir"] = str(output_dir)
398
+ if formats is not None:
399
+ payload["formats"] = self._normalize_fields(formats)
400
+ if collect_mode is not None:
401
+ payload["collect_mode"] = collect_mode
402
+ if connection_mode is not None:
403
+ payload["connection_mode"] = connection_mode
404
+ if connection_count is not None:
405
+ payload["connection_count"] = int(connection_count)
406
+ if source_server_count is not None:
407
+ payload["source_server_count"] = int(source_server_count)
408
+ if connections_per_server is not None:
409
+ payload["connections_per_server"] = int(connections_per_server)
410
+ if max_concurrent_tasks is not None:
411
+ payload["max_concurrent_tasks"] = int(max_concurrent_tasks)
412
+ if batch_size is not None:
413
+ payload["batch_size"] = int(batch_size)
414
+ if request_interval_ms is not None:
415
+ payload["request_interval_ms"] = int(request_interval_ms)
416
+ if retry_count is not None:
417
+ payload["retry_count"] = int(retry_count)
418
+ if timeout_ms is not None:
419
+ payload["timeout_ms"] = int(timeout_ms)
420
+
421
+ response = self._http_session.post(
422
+ f"{self.api_base}/v1/download/{interface}",
423
+ json=payload,
424
+ headers=self._headers(),
425
+ timeout=self.timeout,
426
+ )
427
+ self._raise_for_status(response)
428
+ data = response.json()
429
+ result = self._extract_data_object(data)
430
+ return SimpleNamespace(**result)
431
+
432
+ def _query_local(
433
+ self,
434
+ table: str,
435
+ fields: str | list[str] | tuple[str, ...] | None = None,
436
+ **params: Any,
437
+ ):
438
+ try:
439
+ from axdata_core import query_table
440
+ except ImportError as exc:
441
+ raise ImportError(
442
+ "AxData local mode requires axdata_core and DuckDB. Install the workspace "
443
+ "or use `pip install axdata[local]`."
444
+ ) from exc
445
+
446
+ query_parts = self._split_query_params(params)
447
+ filters = dict(query_parts["filters"])
448
+ filters.update(query_parts["params"])
449
+ selected_fields = self._normalize_fields(fields) if fields is not None else None
450
+ return query_table(
451
+ table,
452
+ root=self.data_root,
453
+ filters=filters,
454
+ fields=selected_fields,
455
+ start_date=query_parts["start_date"],
456
+ end_date=query_parts["end_date"],
457
+ limit=query_parts["limit"],
458
+ )
459
+
460
+ def _call_local(
461
+ self,
462
+ interface: str,
463
+ fields: str | list[str] | tuple[str, ...] | None = None,
464
+ options: Mapping[str, Any] | None = None,
465
+ **params: Any,
466
+ ):
467
+ try:
468
+ from axdata_core import request_interface
469
+ except ImportError as exc:
470
+ raise ImportError(
471
+ "AxData local source requests require axdata_core. Install the workspace "
472
+ "or use API mode with api_base."
473
+ ) from exc
474
+
475
+ request_params = {
476
+ str(key): self._normalize_param_value(value)
477
+ for key, value in params.items()
478
+ if value is not None
479
+ }
480
+ selected_fields = self._normalize_fields(fields) if fields is not None else None
481
+ request_kwargs = {
482
+ "params": request_params,
483
+ "fields": selected_fields,
484
+ "persist": False,
485
+ "data_root": self.data_root,
486
+ }
487
+ if options:
488
+ request_kwargs["options"] = self._normalize_options(options)
489
+ result = request_interface(interface, **request_kwargs)
490
+ return self._to_dataframe(result.records)
491
+
492
+ def _download_local(
493
+ self,
494
+ interface: str,
495
+ fields: str | list[str] | tuple[str, ...] | None = None,
496
+ output_root: str | Path | None = None,
497
+ output_dir: str | Path | None = None,
498
+ formats: str | list[str] | tuple[str, ...] | None = None,
499
+ collect_mode: str | None = None,
500
+ connection_mode: str | None = None,
501
+ connection_count: int | None = None,
502
+ source_server_count: int | None = None,
503
+ connections_per_server: int | None = None,
504
+ max_concurrent_tasks: int | None = None,
505
+ batch_size: int | None = None,
506
+ request_interval_ms: int | None = None,
507
+ retry_count: int | None = None,
508
+ timeout_ms: int | None = None,
509
+ **params: Any,
510
+ ) -> SimpleNamespace:
511
+ try:
512
+ from axdata_core import run_downloader
513
+ except ImportError as exc:
514
+ raise ImportError(
515
+ "AxData local downloads require axdata_core. Install the workspace "
516
+ "or use API mode with api_base."
517
+ ) from exc
518
+
519
+ request_params = {
520
+ str(key): self._normalize_param_value(value)
521
+ for key, value in params.items()
522
+ if value is not None
523
+ }
524
+ selected_fields = self._normalize_fields(fields) if fields is not None else None
525
+ result = run_downloader(
526
+ interface,
527
+ params=request_params,
528
+ fields=selected_fields,
529
+ data_root=self.data_root,
530
+ output_root=output_root,
531
+ output_dir=output_dir,
532
+ formats=self._normalize_fields(formats) if formats is not None else None,
533
+ collect_mode=collect_mode,
534
+ connection_mode=connection_mode,
535
+ connection_count=connection_count,
536
+ source_server_count=source_server_count,
537
+ connections_per_server=connections_per_server,
538
+ max_concurrent_tasks=max_concurrent_tasks,
539
+ batch_size=batch_size,
540
+ request_interval_ms=request_interval_ms,
541
+ retry_count=retry_count,
542
+ timeout_ms=timeout_ms,
543
+ )
544
+ return SimpleNamespace(**result)
545
+
546
+ def daily(
547
+ self,
548
+ fields: str | list[str] | tuple[str, ...] | None = None,
549
+ **params: Any,
550
+ ):
551
+ """Query daily market data."""
552
+
553
+ return self.query("daily", fields=fields, **params)
554
+
555
+ def adj_factor(
556
+ self,
557
+ fields: str | list[str] | tuple[str, ...] | None = None,
558
+ **params: Any,
559
+ ):
560
+ """Query daily adjustment factors."""
561
+
562
+ return self.query("adj_factor", fields=fields, **params)
563
+
564
+ def trade_cal(
565
+ self,
566
+ fields: str | list[str] | tuple[str, ...] | None = None,
567
+ **params: Any,
568
+ ):
569
+ """Query the trading calendar."""
570
+
571
+ return self.query("trade_cal", fields=fields, **params)
572
+
573
+ def stock_basic(
574
+ self,
575
+ fields: str | list[str] | tuple[str, ...] | None = None,
576
+ **params: Any,
577
+ ):
578
+ """Query stock basic information.
579
+
580
+ Compatibility alias for :meth:`stock_basic_exchange`.
581
+ """
582
+
583
+ return self.stock_basic_exchange(fields=fields, **params)
584
+
585
+ def stock_basic_exchange(
586
+ self,
587
+ fields: str | list[str] | tuple[str, ...] | None = None,
588
+ **params: Any,
589
+ ):
590
+ """Query stock list using the official exchange interface口径."""
591
+
592
+ return self.query("stock_basic_exchange", fields=fields, **params)
593
+
594
+ @staticmethod
595
+ def _resolve_data_root(
596
+ *,
597
+ data_root: str | Path | None = None,
598
+ data_dir: str | Path | None = None,
599
+ data_home: str | Path | None = None,
600
+ ) -> Path:
601
+ if data_root is not None and data_dir is not None:
602
+ raise ValueError("Use either data_root or data_dir, not both.")
603
+ if data_root is not None:
604
+ return Path(data_root).expanduser().resolve()
605
+ if data_dir is not None:
606
+ return Path(data_dir).expanduser().resolve()
607
+
608
+ env_data_root = os.getenv("AXDATA_DATA_DIR")
609
+ if env_data_root:
610
+ return Path(env_data_root).expanduser().resolve()
611
+
612
+ resolved_data_home = data_home or os.getenv("AXDATA_DATA_HOME")
613
+ if resolved_data_home:
614
+ return (Path(resolved_data_home).expanduser().resolve() / "data")
615
+
616
+ return (Path.cwd() / "data").resolve()
617
+
618
+ @staticmethod
619
+ def _normalize_date_text(value: Any) -> Any:
620
+ if isinstance(value, str) and len(value) == 10 and value[4] == "-" and value[7] == "-":
621
+ return value.replace("-", "")
622
+ return value
623
+
624
+ @classmethod
625
+ def _split_query_params(cls, params: Mapping[str, Any]) -> dict[str, Any]:
626
+ query_params = dict(params)
627
+ explicit_filters = query_params.pop("filters", None)
628
+ start_date = cls._normalize_date_text(
629
+ query_params.pop("start_date", None) or query_params.pop("start", None)
630
+ )
631
+ end_date = cls._normalize_date_text(
632
+ query_params.pop("end_date", None) or query_params.pop("end", None)
633
+ )
634
+ limit = cls._normalize_limit(query_params.pop("limit", DEFAULT_LIMIT))
635
+ filters = {
636
+ str(key): cls._normalize_param_value(value)
637
+ for key, value in dict(explicit_filters or {}).items()
638
+ if value is not None
639
+ }
640
+ params = {
641
+ str(key): cls._normalize_param_value(value)
642
+ for key, value in query_params.items()
643
+ if value is not None
644
+ }
645
+ return {
646
+ "params": params,
647
+ "filters": filters,
648
+ "start_date": start_date,
649
+ "end_date": end_date,
650
+ "limit": limit,
651
+ }
652
+
653
+ @classmethod
654
+ def _normalize_param_value(cls, value: Any) -> Any:
655
+ if isinstance(value, str):
656
+ return cls._normalize_date_text(value)
657
+ if isinstance(value, tuple):
658
+ return tuple(cls._normalize_param_value(item) for item in value)
659
+ if isinstance(value, list):
660
+ return [cls._normalize_param_value(item) for item in value]
661
+ if isinstance(value, set):
662
+ return [cls._normalize_param_value(item) for item in value]
663
+ if isinstance(value, frozenset):
664
+ return [cls._normalize_param_value(item) for item in value]
665
+ return value
666
+
667
+ @classmethod
668
+ def _normalize_options(cls, options: Mapping[str, Any]) -> dict[str, Any]:
669
+ return {
670
+ str(key): cls._normalize_param_value(value)
671
+ for key, value in dict(options or {}).items()
672
+ if value is not None
673
+ }
674
+
675
+ @staticmethod
676
+ def _normalize_limit(value: Any) -> int | None:
677
+ if value is None:
678
+ return None
679
+ return int(value)
680
+
681
+ def _headers(self) -> dict[str, str] | None:
682
+ if not self.token:
683
+ return None
684
+ return {"Authorization": f"Bearer {self.token}"}
685
+
686
+ @staticmethod
687
+ def _raise_for_status(response: Any) -> None:
688
+ status_code = getattr(response, "status_code", 200)
689
+ if status_code is not None and int(status_code) >= 400:
690
+ try:
691
+ payload = response.json()
692
+ except Exception:
693
+ payload = None
694
+ if isinstance(payload, Mapping):
695
+ error = payload.get("error")
696
+ if isinstance(error, Mapping) and error.get("message"):
697
+ raise AxDataError(str(error["message"]))
698
+ if payload.get("message") or payload.get("msg"):
699
+ raise AxDataError(str(payload.get("message") or payload.get("msg")))
700
+ response.raise_for_status()
701
+
702
+ @staticmethod
703
+ def _normalize_fields(fields: str | list[str] | tuple[str, ...]) -> list[str]:
704
+ if isinstance(fields, str):
705
+ return [field.strip() for field in fields.split(",") if field.strip()]
706
+ return [str(field).strip() for field in fields if str(field).strip()]
707
+
708
+ @staticmethod
709
+ def _extract_records(data: Any) -> list[Mapping[str, Any]]:
710
+ if not isinstance(data, Mapping):
711
+ raise AxDataError("AxData response must be a JSON object")
712
+
713
+ if data.get("success") is False:
714
+ error = data.get("error")
715
+ if isinstance(error, Mapping):
716
+ message = error.get("message") or error.get("code")
717
+ else:
718
+ message = data.get("message") or data.get("msg")
719
+ message = message or "AxData query failed"
720
+ raise AxDataError(str(message))
721
+
722
+ if isinstance(data.get("data"), list):
723
+ records = data["data"]
724
+ elif "records" in data:
725
+ records = data["records"]
726
+ elif isinstance(data.get("data"), Mapping) and "records" in data["data"]:
727
+ records = data["data"]["records"]
728
+ else:
729
+ raise AxDataError("AxData response is missing a data record list")
730
+
731
+ if records is None:
732
+ return []
733
+ if not isinstance(records, list):
734
+ raise AxDataError("AxData response 'records' must be a list")
735
+
736
+ return records
737
+
738
+ @staticmethod
739
+ def _extract_data_object(data: Any) -> dict[str, Any]:
740
+ if not isinstance(data, Mapping):
741
+ raise AxDataError("AxData response must be a JSON object")
742
+
743
+ if data.get("success") is False:
744
+ error = data.get("error")
745
+ if isinstance(error, Mapping):
746
+ message = error.get("message") or error.get("code")
747
+ else:
748
+ message = data.get("message") or data.get("msg")
749
+ raise AxDataError(str(message or "AxData request failed"))
750
+
751
+ payload = data.get("data")
752
+ if not isinstance(payload, Mapping):
753
+ raise AxDataError("AxData response is missing a data object")
754
+ return dict(payload)
755
+
756
+ @staticmethod
757
+ def _to_dataframe(records: list[Mapping[str, Any]]):
758
+ try:
759
+ import pandas as pd
760
+ except ImportError as exc:
761
+ raise ImportError(
762
+ "AxData SDK requires pandas to convert API records to a DataFrame. "
763
+ "Install it with `pip install pandas` or `pip install axdata[pandas]`."
764
+ ) from exc
765
+
766
+ return pd.DataFrame.from_records(records)
767
+
768
+
769
+ class AxDataStream:
770
+ """Synchronous iterator wrapper for AxData WebSocket streams."""
771
+
772
+ def __init__(
773
+ self,
774
+ *,
775
+ api_base: str,
776
+ stream: str,
777
+ params: Mapping[str, Any],
778
+ token: str | None,
779
+ timeout: float,
780
+ ) -> None:
781
+ self.api_base = api_base.rstrip("/")
782
+ self.stream = stream
783
+ self.params = dict(params)
784
+ self.token = token
785
+ self.timeout = timeout
786
+ self._ws = None
787
+
788
+ def __enter__(self) -> "AxDataStream":
789
+ self.open()
790
+ return self
791
+
792
+ def __exit__(self, exc_type, exc, tb) -> None:
793
+ self.close()
794
+
795
+ def __iter__(self) -> Iterator[SimpleNamespace]:
796
+ return self
797
+
798
+ def __next__(self) -> SimpleNamespace:
799
+ if self._ws is None:
800
+ self.open()
801
+ assert self._ws is not None
802
+ raw = self._ws.recv()
803
+ if raw in (None, ""):
804
+ raise StopIteration
805
+ payload = json.loads(raw)
806
+ if isinstance(payload, Mapping) and payload.get("type") == "error":
807
+ error = payload.get("error")
808
+ if isinstance(error, Mapping) and error.get("message"):
809
+ raise AxDataError(str(error["message"]))
810
+ return SimpleNamespace(**payload)
811
+
812
+ @property
813
+ def url(self) -> str:
814
+ parsed = urlparse(self.api_base)
815
+ scheme = "wss" if parsed.scheme == "https" else "ws"
816
+ query = parsed.query
817
+ if self.token:
818
+ token_query = urlencode({"token": self.token})
819
+ query = f"{query}&{token_query}" if query else token_query
820
+ return urlunparse((scheme, parsed.netloc, f"/v1/stream/{self.stream}", "", query, ""))
821
+
822
+ def open(self) -> None:
823
+ if self._ws is not None:
824
+ return
825
+ try:
826
+ import websocket
827
+ except ImportError as exc:
828
+ raise ImportError(
829
+ "AxData realtime streams require websocket-client. Install axdata with "
830
+ "`pip install axdata` from this workspace or install `websocket-client`."
831
+ ) from exc
832
+
833
+ headers = []
834
+ if self.token:
835
+ headers.append(f"Authorization: Bearer {self.token}")
836
+ self._ws = websocket.create_connection(
837
+ self.url,
838
+ timeout=self.timeout,
839
+ header=headers or None,
840
+ )
841
+ self._ws.send(
842
+ json.dumps(
843
+ {
844
+ "op": "subscribe",
845
+ "stream": self.stream,
846
+ "params": self.params,
847
+ }
848
+ )
849
+ )
850
+
851
+ def close(self) -> None:
852
+ if self._ws is None:
853
+ return
854
+ try:
855
+ self._ws.close()
856
+ finally:
857
+ self._ws = None
858
+
859
+
860
+ class AxDataLocalSession:
861
+ """SDK wrapper around an in-process local source session."""
862
+
863
+ def __init__(
864
+ self,
865
+ *,
866
+ client: AxDataClient,
867
+ source: str,
868
+ options: Mapping[str, Any],
869
+ ) -> None:
870
+ self._client = client
871
+ self.source = source
872
+ self.options = dict(options)
873
+ self._session = None
874
+
875
+ def __enter__(self) -> "AxDataLocalSession":
876
+ self.open()
877
+ return self
878
+
879
+ def __exit__(self, exc_type, exc, tb) -> None:
880
+ self.close()
881
+
882
+ def open(self) -> None:
883
+ if self._session is not None:
884
+ return
885
+ try:
886
+ from axdata_core import create_source_session
887
+ except ImportError as exc:
888
+ raise ImportError(
889
+ "AxData local source sessions require axdata_core. Install the workspace "
890
+ "or use API mode with api_base."
891
+ ) from exc
892
+ try:
893
+ session = create_source_session(
894
+ source=self.source,
895
+ data_root=self._client.data_root,
896
+ options=self.options,
897
+ )
898
+ session.open()
899
+ except Exception as exc:
900
+ raise AxDataError(f"Failed to open local source session for {self.source!r}: {exc}") from exc
901
+ self._session = session
902
+
903
+ def close(self) -> None:
904
+ if self._session is None:
905
+ return
906
+ try:
907
+ self._session.close()
908
+ finally:
909
+ self._session = None
910
+
911
+ def call(
912
+ self,
913
+ interface: str,
914
+ fields: str | list[str] | tuple[str, ...] | None = None,
915
+ options: Mapping[str, Any] | None = None,
916
+ **params: Any,
917
+ ):
918
+ """Call a source request interface through the session-owned adapter."""
919
+
920
+ if not interface:
921
+ raise ValueError("interface is required")
922
+ if self._session is None:
923
+ self.open()
924
+ assert self._session is not None
925
+ request_params = {
926
+ str(key): self._client._normalize_param_value(value)
927
+ for key, value in params.items()
928
+ if value is not None
929
+ }
930
+ selected_fields = self._client._normalize_fields(fields) if fields is not None else None
931
+ request_options = self._client._normalize_options(options) if options else None
932
+ try:
933
+ result = self._session.call(
934
+ interface,
935
+ params=request_params,
936
+ fields=selected_fields,
937
+ options=request_options,
938
+ )
939
+ except AxDataError:
940
+ raise
941
+ except Exception as exc:
942
+ raise AxDataError(f"Local source session call {interface!r} failed: {exc}") from exc
943
+ return self._client._to_dataframe(result.records)
944
+
945
+
946
+ class LocalAxDataStream:
947
+ """Synchronous iterator for local in-process TDX streams."""
948
+
949
+ def __init__(
950
+ self,
951
+ *,
952
+ stream: str,
953
+ params: Mapping[str, Any],
954
+ data_root: Path,
955
+ timeout: float,
956
+ ) -> None:
957
+ self.stream = stream
958
+ self.params = dict(params)
959
+ self.data_root = data_root
960
+ self.timeout = timeout
961
+ self._source_session = None
962
+ self._subscription_id: str | None = None
963
+ self._code: list[str] = []
964
+ self._fields: list[str] | None = None
965
+ self._interval_ms = LOCAL_STREAM_DEFAULT_INTERVAL_MS
966
+ self._initial_snapshot = True
967
+ self._pending: list[SimpleNamespace] = []
968
+ self._next_step = "closed"
969
+ self._refresh_cursors: dict[str, int] = {}
970
+
971
+ def __enter__(self) -> "LocalAxDataStream":
972
+ self.open()
973
+ return self
974
+
975
+ def __exit__(self, exc_type, exc, tb) -> None:
976
+ self.close()
977
+
978
+ def __iter__(self) -> Iterator[SimpleNamespace]:
979
+ return self
980
+
981
+ def __next__(self) -> SimpleNamespace:
982
+ if self._source_session is None:
983
+ self.open()
984
+ if self._pending:
985
+ return self._pending.pop(0)
986
+ if self._next_step == "snapshot":
987
+ self._next_step = "update"
988
+ return self._snapshot_event()
989
+ if self._next_step == "update":
990
+ time.sleep(self._interval_ms / 1000.0)
991
+ return self._update_event()
992
+ raise StopIteration
993
+
994
+ def open(self) -> None:
995
+ if self._source_session is not None:
996
+ return
997
+ if self.stream != LOCAL_STREAM_NAME:
998
+ raise AxDataError(
999
+ f"Local streams currently support only {LOCAL_STREAM_NAME!r}; got {self.stream!r}. "
1000
+ "Pass api_base to AxDataClient for remote WebSocket streams."
1001
+ )
1002
+ code = _normalize_stream_codes(self.params.get("code"))
1003
+ if not code:
1004
+ raise AxDataError("code must include at least one security code for local TDX stream.")
1005
+ if len(code) > LOCAL_STREAM_MAX_CODES:
1006
+ raise AxDataError(f"Local TDX stream supports at most {LOCAL_STREAM_MAX_CODES} codes per subscription.")
1007
+ fields = self.params.get("fields")
1008
+ selected_fields = AxDataClient._normalize_fields(fields) if fields is not None else None
1009
+ interval_ms = _normalize_stream_interval_ms(self.params.get("interval_ms"))
1010
+ initial_snapshot = _normalize_stream_bool(
1011
+ self.params.get("initial_snapshot", self.params.get("snapshot")),
1012
+ True,
1013
+ )
1014
+ try:
1015
+ from axdata_core import create_source_session
1016
+ except ImportError as exc:
1017
+ raise ImportError(
1018
+ "AxData local streams require axdata_core and the TDX source plugin. "
1019
+ "Install the workspace or create AxDataClient(api_base=...) for remote streams."
1020
+ ) from exc
1021
+
1022
+ session_options = _local_stream_session_options(self.params)
1023
+ try:
1024
+ source_session = create_source_session(
1025
+ source="tdx",
1026
+ data_root=self.data_root,
1027
+ options=session_options,
1028
+ )
1029
+ source_session.open()
1030
+ except Exception as exc:
1031
+ raise AxDataError(f"Failed to open local TDX stream session: {exc}") from exc
1032
+
1033
+ self._source_session = source_session
1034
+ self._subscription_id = f"sub_{uuid4().hex[:12]}"
1035
+ self._code = code
1036
+ self._fields = selected_fields
1037
+ self._interval_ms = interval_ms
1038
+ self._initial_snapshot = initial_snapshot
1039
+ self._next_step = "snapshot" if initial_snapshot else "update"
1040
+ self._pending.append(
1041
+ self._event(
1042
+ "subscribed",
1043
+ {
1044
+ "code": code,
1045
+ "fields": selected_fields,
1046
+ "interval_ms": interval_ms,
1047
+ "initial_snapshot": initial_snapshot,
1048
+ },
1049
+ )
1050
+ )
1051
+
1052
+ def close(self) -> None:
1053
+ if self._source_session is None:
1054
+ return
1055
+ try:
1056
+ self._source_session.close()
1057
+ finally:
1058
+ self._source_session = None
1059
+ self._subscription_id = None
1060
+ self._pending.clear()
1061
+ self._refresh_cursors.clear()
1062
+ self._next_step = "closed"
1063
+
1064
+ def _snapshot_event(self) -> SimpleNamespace:
1065
+ assert self._source_session is not None
1066
+ try:
1067
+ result = self._source_session.call(
1068
+ "stock_realtime_snapshot_tdx",
1069
+ params={"code": self._code},
1070
+ fields=self._fields,
1071
+ )
1072
+ except Exception as exc:
1073
+ raise AxDataError(f"Local TDX stream snapshot failed: {exc}") from exc
1074
+ return self._event("snapshot", list(result.records))
1075
+
1076
+ def _update_event(self) -> SimpleNamespace:
1077
+ assert self._source_session is not None
1078
+ try:
1079
+ from axdata_core.adapters.tdx.realtime_refresh import request_realtime_refresh_rows
1080
+
1081
+ rows = request_realtime_refresh_rows(
1082
+ code=self._code,
1083
+ fields=self._fields,
1084
+ cursors=self._refresh_cursors,
1085
+ include_internal=True,
1086
+ client=getattr(self._source_session, "tdx_client", None),
1087
+ )
1088
+ except Exception as exc:
1089
+ raise AxDataError(f"Local TDX stream update failed: {exc}") from exc
1090
+ _update_refresh_cursors(self._refresh_cursors, rows)
1091
+ return self._event("update", _strip_internal_fields(rows))
1092
+
1093
+ def _event(self, event_type: str, data: Any) -> SimpleNamespace:
1094
+ return SimpleNamespace(
1095
+ type=event_type,
1096
+ stream=self.stream,
1097
+ subscription_id=self._subscription_id,
1098
+ server_time=_stream_time(),
1099
+ data=data,
1100
+ )
1101
+
1102
+
1103
+ def _stream_time() -> str:
1104
+ return datetime.now(timezone.utc).astimezone().isoformat(timespec="seconds")
1105
+
1106
+
1107
+ def _normalize_stream_codes(value: Any) -> list[str]:
1108
+ if value is None:
1109
+ return []
1110
+ if isinstance(value, str):
1111
+ return [item.strip() for item in value.split(",") if item.strip()]
1112
+ if isinstance(value, (list, tuple, set, frozenset)):
1113
+ return [str(item).strip() for item in value if str(item).strip()]
1114
+ return [str(value).strip()] if str(value).strip() else []
1115
+
1116
+
1117
+ def _normalize_stream_interval_ms(value: Any) -> int:
1118
+ if value is None:
1119
+ return LOCAL_STREAM_DEFAULT_INTERVAL_MS
1120
+ try:
1121
+ parsed = int(value)
1122
+ except (TypeError, ValueError):
1123
+ return LOCAL_STREAM_DEFAULT_INTERVAL_MS
1124
+ return max(LOCAL_STREAM_MIN_INTERVAL_MS, parsed)
1125
+
1126
+
1127
+ def _normalize_stream_bool(value: Any, default: bool) -> bool:
1128
+ if value is None:
1129
+ return default
1130
+ if isinstance(value, bool):
1131
+ return value
1132
+ if isinstance(value, str):
1133
+ normalized = value.strip().lower()
1134
+ if normalized in {"1", "true", "yes", "on"}:
1135
+ return True
1136
+ if normalized in {"0", "false", "no", "off"}:
1137
+ return False
1138
+ return bool(value)
1139
+
1140
+
1141
+ def _local_stream_session_options(params: Mapping[str, Any]) -> dict[str, Any]:
1142
+ option_keys = {
1143
+ "connections_per_server",
1144
+ "hosts",
1145
+ "pool_size",
1146
+ "source_server_count",
1147
+ }
1148
+ return {
1149
+ key: value
1150
+ for key, value in params.items()
1151
+ if key in option_keys and value is not None
1152
+ }
1153
+
1154
+
1155
+ def _update_refresh_cursors(cursors: dict[str, int], rows: list[dict[str, Any]]) -> None:
1156
+ for row in rows:
1157
+ instrument_id = row.get("_tdx_instrument_id") or row.get("instrument_id")
1158
+ update_time_raw = row.get("_tdx_update_time_raw")
1159
+ if not instrument_id or update_time_raw in (None, ""):
1160
+ continue
1161
+ try:
1162
+ cursors[str(instrument_id).upper()] = int(update_time_raw)
1163
+ except (TypeError, ValueError):
1164
+ continue
1165
+
1166
+
1167
+ def _strip_internal_fields(rows: list[dict[str, Any]]) -> list[dict[str, Any]]:
1168
+ return [
1169
+ {key: value for key, value in dict(row).items() if not str(key).startswith("_tdx_")}
1170
+ for row in rows
1171
+ ]
1172
+
1173
+
1174
+ def pro_api(
1175
+ token: str | None = None,
1176
+ api_base: str | None = None,
1177
+ data_root: str | Path | None = None,
1178
+ data_dir: str | Path | None = None,
1179
+ data_home: str | Path | None = None,
1180
+ mode: BackendMode = "auto",
1181
+ backend: BackendMode | None = None,
1182
+ ) -> AxDataClient:
1183
+ """Create an AxData client.
1184
+
1185
+ If api_base or AXDATA_API_BASE is set, the client reads that AxData API
1186
+ service. Otherwise it reads the local AxData data directory directly.
1187
+ """
1188
+
1189
+ return AxDataClient(
1190
+ token=token,
1191
+ api_base=api_base,
1192
+ data_root=data_root,
1193
+ data_dir=data_dir,
1194
+ data_home=data_home,
1195
+ mode=mode,
1196
+ backend=backend,
1197
+ )
1198
+
1199
+
1200
+ def connect(
1201
+ *,
1202
+ api_base: str | None = None,
1203
+ data_root: str | Path | None = None,
1204
+ data_dir: str | Path | None = None,
1205
+ data_home: str | Path | None = None,
1206
+ token: str | None = None,
1207
+ mode: BackendMode = "auto",
1208
+ backend: BackendMode | None = None,
1209
+ ) -> AxDataClient:
1210
+ """Connect to local AxData data or a remote AxData API service."""
1211
+
1212
+ return AxDataClient(
1213
+ token=token,
1214
+ api_base=api_base,
1215
+ data_root=data_root,
1216
+ data_dir=data_dir,
1217
+ data_home=data_home,
1218
+ mode=mode,
1219
+ backend=backend,
1220
+ )
1221
+
1222
+
1223
+ Client = AxDataClient