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
@@ -0,0 +1,85 @@
1
+ """TDX downloader factory declarations owned by the provider package."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Callable
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+ DownloaderAdapterFactory = Callable[..., Any]
10
+ RuntimeSourceServerMaxFactory = Callable[..., int]
11
+ DownloaderProfileDeclarations = Callable[[type[Any], type[Any]], dict[str, Any]]
12
+ TdxDownloaderDeclarations = tuple[
13
+ DownloaderProfileDeclarations,
14
+ dict[str, DownloaderAdapterFactory],
15
+ dict[str, RuntimeSourceServerMaxFactory],
16
+ ]
17
+
18
+
19
+ def tdx_downloader_profile_declarations(
20
+ concurrency_profile_cls: type[Any],
21
+ downloader_profile_cls: type[Any],
22
+ ) -> dict[str, Any]:
23
+ """Return downloader profile declarations owned by TDX."""
24
+
25
+ from .downloader_profiles import tdx_downloader_profiles
26
+
27
+ return tdx_downloader_profiles(concurrency_profile_cls, downloader_profile_cls)
28
+
29
+
30
+ def tdx_downloader_adapter_factories() -> dict[str, DownloaderAdapterFactory]:
31
+ """Return downloader adapter factories owned by TDX."""
32
+
33
+ return {"tdx": _tdx_download_adapter}
34
+
35
+
36
+ def tdx_runtime_source_server_max_factories() -> dict[str, RuntimeSourceServerMaxFactory]:
37
+ """Return runtime source-server cap factories owned by TDX."""
38
+
39
+ return {"tdx": _tdx_runtime_source_server_max}
40
+
41
+
42
+ def tdx_downloader_declarations() -> TdxDownloaderDeclarations:
43
+ """Return all downloader declarations owned by TDX."""
44
+
45
+ return (
46
+ tdx_downloader_profile_declarations,
47
+ tdx_downloader_adapter_factories(),
48
+ tdx_runtime_source_server_max_factories(),
49
+ )
50
+
51
+
52
+ def _tdx_download_adapter(
53
+ *,
54
+ interface_name: str,
55
+ source_server_count: int,
56
+ pool_size: int,
57
+ data_root: str | Path | None = None,
58
+ progress_callback: Callable[..., None] | None = None,
59
+ ) -> tuple[Any, Any]:
60
+ from .downloader import create_tdx_download_adapter
61
+
62
+ return create_tdx_download_adapter(
63
+ interface_name=interface_name,
64
+ source_server_count=source_server_count,
65
+ pool_size=pool_size,
66
+ data_root=data_root,
67
+ progress_callback=progress_callback,
68
+ )
69
+
70
+
71
+ def _tdx_runtime_source_server_max(
72
+ interface_name: str,
73
+ *,
74
+ configured_max: int,
75
+ source_server_count_editable: bool,
76
+ data_root: str | Path | None = None,
77
+ ) -> int:
78
+ from .downloader import tdx_runtime_source_server_max
79
+
80
+ return tdx_runtime_source_server_max(
81
+ interface_name,
82
+ configured_max=configured_max,
83
+ source_server_count_editable=source_server_count_editable,
84
+ data_root=data_root,
85
+ )
@@ -0,0 +1,141 @@
1
+ """Execution helpers shared by the TDX request adapter."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Sequence
6
+ from time import monotonic
7
+ from typing import Any, Callable
8
+
9
+
10
+ def client_pool_size(client: Any) -> int:
11
+ transport = getattr(client, "transport", None)
12
+ value = getattr(transport, "pool_size", getattr(client, "pool_size", 1))
13
+ try:
14
+ return max(1, int(value or 1))
15
+ except (TypeError, ValueError):
16
+ return 1
17
+
18
+
19
+ def chunked(values: Sequence[Any], size: int) -> list[list[Any]]:
20
+ size = max(1, int(size))
21
+ return [list(values[index : index + size]) for index in range(0, len(values), size)]
22
+
23
+
24
+ def tdx_client_meta(client: Any) -> dict[str, Any]:
25
+ transport = getattr(client, "transport", None)
26
+ hosts = tuple(getattr(transport, "hosts", None) or getattr(client, "hosts", None) or ())
27
+ connected_hosts = getattr(transport, "connected_hosts", None)
28
+ connected_host = getattr(transport, "connected_host", None)
29
+ pool_size = getattr(transport, "pool_size", getattr(client, "pool_size", None))
30
+ heartbeat_interval = getattr(
31
+ transport,
32
+ "heartbeat_interval",
33
+ getattr(client, "heartbeat_interval", None),
34
+ )
35
+ return {
36
+ "tdx_connected_host": connected_host,
37
+ "tdx_connected_hosts": tuple(connected_hosts) if connected_hosts is not None else None,
38
+ "tdx_configured_host_count": len(hosts),
39
+ "tdx_configured_hosts_sample": hosts[:5],
40
+ "tdx_pool_size": pool_size,
41
+ "tdx_heartbeat_interval": heartbeat_interval,
42
+ }
43
+
44
+
45
+ def finish_request_result(
46
+ *,
47
+ client: Any,
48
+ result: Any,
49
+ client_meta: Callable[[Any], dict[str, Any]] = tdx_client_meta,
50
+ ) -> tuple[list[dict[str, Any]], dict[str, Any]]:
51
+ return result.rows, {
52
+ **client_meta(client),
53
+ **result.meta,
54
+ }
55
+
56
+
57
+ def apply_asset_type_meta(
58
+ rows: list[dict[str, Any]],
59
+ meta: dict[str, Any],
60
+ *,
61
+ asset_type: str,
62
+ ) -> tuple[list[dict[str, Any]], dict[str, Any]]:
63
+ return rows, {
64
+ **meta,
65
+ "tdx_asset_type": asset_type,
66
+ }
67
+
68
+
69
+ def emit_price_limit_progress(
70
+ progress_callback: Callable[..., None] | None,
71
+ *,
72
+ completed: int,
73
+ total: int,
74
+ started_at: float,
75
+ ) -> None:
76
+ if total <= 0:
77
+ return
78
+ fraction = min(1.0, max(0.0, completed / total))
79
+ percent = 20 + int(fraction * 48)
80
+ elapsed_ms = max(0.0, (monotonic() - started_at) * 1000)
81
+ eta_ms = None
82
+ if completed > 0 and completed < total:
83
+ eta_ms = int((elapsed_ms / completed) * (total - completed))
84
+ emit_source_progress(
85
+ progress_callback,
86
+ percent,
87
+ f"计算涨跌停价格 {completed}/{total} 只",
88
+ progress_current=completed,
89
+ progress_total=total,
90
+ progress_unit="只",
91
+ eta_ms=eta_ms,
92
+ )
93
+
94
+
95
+ def emit_batch_progress(
96
+ progress_callback: Callable[..., None] | None,
97
+ *,
98
+ completed: int,
99
+ total: int,
100
+ unit: str,
101
+ started_at: float,
102
+ percent_start: int,
103
+ percent_span: int,
104
+ label: str,
105
+ ) -> None:
106
+ if total <= 0:
107
+ return
108
+ safe_completed = min(total, max(0, completed))
109
+ fraction = min(1.0, max(0.0, safe_completed / total))
110
+ percent = percent_start + int(fraction * percent_span)
111
+ elapsed_ms = max(0.0, (monotonic() - started_at) * 1000)
112
+ eta_ms = None
113
+ if safe_completed > 0 and safe_completed < total:
114
+ eta_ms = int((elapsed_ms / safe_completed) * (total - safe_completed))
115
+ emit_source_progress(
116
+ progress_callback,
117
+ percent,
118
+ f"{label} {safe_completed}/{total} {unit}",
119
+ progress_current=safe_completed,
120
+ progress_total=total,
121
+ progress_unit=unit,
122
+ eta_ms=eta_ms,
123
+ )
124
+
125
+
126
+ def emit_source_progress(
127
+ progress_callback: Callable[..., None] | None,
128
+ percent: int,
129
+ message: str,
130
+ **details: Any,
131
+ ) -> None:
132
+ if progress_callback is None:
133
+ return
134
+ try:
135
+ progress_callback(percent, message, **details)
136
+ except TypeError:
137
+ progress_callback(percent, message)
138
+
139
+
140
+ def elapsed_monotonic_ms(started_at: float) -> int:
141
+ return max(0, int((monotonic() - started_at) * 1000))
@@ -0,0 +1,414 @@
1
+ """Execution helpers for TDX F10 batch requests."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Callable, Mapping, Sequence
6
+ from concurrent.futures import ThreadPoolExecutor, as_completed
7
+ from dataclasses import dataclass
8
+ from time import monotonic
9
+ from typing import TYPE_CHECKING, Any
10
+
11
+ if TYPE_CHECKING:
12
+ from .tdx_f10_models import F10InterfaceSpec
13
+
14
+
15
+ @dataclass(frozen=True)
16
+ class F10DispatchResult:
17
+ rows: list[dict[str, Any]]
18
+ requested_code_count: int
19
+ worker_count: int
20
+ meta: dict[str, Any]
21
+
22
+
23
+ @dataclass(frozen=True)
24
+ class TopicRowsLookupResult:
25
+ rows: dict[str, list[dict[str, Any]]]
26
+ meta: dict[str, Any]
27
+
28
+
29
+ def __getattr__(name: str) -> Any:
30
+ if name == "request_f10_interface":
31
+ from .f10_request import request_f10_interface as value
32
+
33
+ globals()[name] = value
34
+ return value
35
+ if name == "TdxTqlexClient":
36
+ from .tqlex import TdxTqlexClient as value
37
+
38
+ globals()[name] = value
39
+ return value
40
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
41
+
42
+
43
+ def _request_f10_interface(client: Any, spec: Any, params: Mapping[str, Any]) -> list[dict[str, Any]]:
44
+ request_func = globals().get("request_f10_interface")
45
+ if request_func is None:
46
+ request_func = __getattr__("request_f10_interface")
47
+ return request_func(client, spec, params)
48
+
49
+
50
+ def _create_default_tqlex_client() -> Any:
51
+ client_class = globals().get("TdxTqlexClient")
52
+ if client_class is None:
53
+ client_class = __getattr__("TdxTqlexClient")
54
+ return client_class()
55
+
56
+
57
+ def request_f10_dispatch_result(
58
+ params: Mapping[str, Any],
59
+ *,
60
+ interface_name: str,
61
+ spec: Any,
62
+ requested_codes: Sequence[str],
63
+ configured_worker_count: int,
64
+ request_one: Callable[[Mapping[str, Any]], list[dict[str, Any]]],
65
+ ) -> F10DispatchResult:
66
+ result = request_f10_rows(
67
+ params,
68
+ requested_codes,
69
+ configured_worker_count=configured_worker_count,
70
+ request_one=request_one,
71
+ )
72
+ return F10DispatchResult(
73
+ rows=result.rows,
74
+ requested_code_count=result.requested_code_count,
75
+ worker_count=result.worker_count,
76
+ meta={
77
+ "tdx_protocol_family": "7615",
78
+ "tdx_requested_interface": interface_name,
79
+ "tdx_returned_count": len(result.rows),
80
+ "tdx_requested_code_count": result.requested_code_count,
81
+ "tdx_f10_workers": result.worker_count,
82
+ "tdx_f10_category": spec.category,
83
+ "tdx_source_evaluation": spec.evaluation,
84
+ },
85
+ )
86
+
87
+
88
+ def stock_f10_request_result(
89
+ interface_name: str,
90
+ params: Mapping[str, Any],
91
+ *,
92
+ specs: Mapping[str, Any],
93
+ existing_client: Any | None,
94
+ create_client: Callable[[], Any],
95
+ requested_codes: Callable[[Any], Sequence[str]],
96
+ option_workers: Callable[[Mapping[str, Any], int], int],
97
+ options: Mapping[str, Any],
98
+ request_interface: Callable[[Any, Any, Mapping[str, Any]], list[dict[str, Any]]],
99
+ ) -> F10DispatchResult:
100
+ spec = specs[interface_name]
101
+ client = existing_client if existing_client is not None and hasattr(existing_client, "request") else create_client()
102
+ requested = requested_codes(params.get("code")) if any(param.name == "code" for param in spec.params) else []
103
+ configured_worker_count = option_workers(options, max(1, len(requested))) if "f10_workers" in options else 1
104
+ return request_f10_dispatch_result(
105
+ params,
106
+ interface_name=interface_name,
107
+ spec=spec,
108
+ requested_codes=requested,
109
+ configured_worker_count=configured_worker_count,
110
+ request_one=lambda per_code_params: request_interface(client, spec, per_code_params),
111
+ )
112
+
113
+
114
+ def default_stock_f10_request_result(
115
+ interface_name: str,
116
+ params: Mapping[str, Any],
117
+ *,
118
+ specs: Mapping[str, Any],
119
+ existing_client: Any | None,
120
+ requested_codes: Callable[[Any], Sequence[str]],
121
+ option_workers: Callable[[Mapping[str, Any], int], int],
122
+ options: Mapping[str, Any],
123
+ ) -> F10DispatchResult:
124
+ return stock_f10_request_result(
125
+ interface_name,
126
+ params,
127
+ specs=specs,
128
+ existing_client=existing_client,
129
+ create_client=_create_default_tqlex_client,
130
+ requested_codes=requested_codes,
131
+ option_workers=option_workers,
132
+ options=options,
133
+ request_interface=_request_f10_interface,
134
+ )
135
+
136
+
137
+ def request_f10_many_with_default_interface(
138
+ client: Any,
139
+ spec: F10InterfaceSpec,
140
+ params: Mapping[str, Any],
141
+ requested_codes: Sequence[str],
142
+ *,
143
+ worker_count: int,
144
+ ) -> list[dict[str, Any]]:
145
+ return request_f10_many_by_code(
146
+ params,
147
+ requested_codes,
148
+ worker_count=worker_count,
149
+ request_one=lambda per_code_params: _request_f10_interface(client, spec, per_code_params),
150
+ )
151
+
152
+
153
+ def request_f10_rows(
154
+ params: Mapping[str, Any],
155
+ requested_codes: Sequence[str],
156
+ *,
157
+ configured_worker_count: int,
158
+ request_one: Callable[[Mapping[str, Any]], list[dict[str, Any]]],
159
+ ) -> F10DispatchResult:
160
+ if len(requested_codes) > 1:
161
+ rows = request_f10_many_by_code(
162
+ params,
163
+ requested_codes,
164
+ worker_count=configured_worker_count,
165
+ request_one=request_one,
166
+ )
167
+ return F10DispatchResult(
168
+ rows=rows,
169
+ requested_code_count=len(requested_codes),
170
+ worker_count=min(configured_worker_count, len(requested_codes)),
171
+ meta={},
172
+ )
173
+
174
+ rows = request_one(params)
175
+ return F10DispatchResult(
176
+ rows=rows,
177
+ requested_code_count=len(requested_codes) or 1,
178
+ worker_count=1,
179
+ meta={},
180
+ )
181
+
182
+
183
+ def request_f10_many_by_code(
184
+ params: Mapping[str, Any],
185
+ requested_codes: Sequence[str],
186
+ *,
187
+ worker_count: int,
188
+ request_one: Callable[[Mapping[str, Any]], list[dict[str, Any]]],
189
+ ) -> list[dict[str, Any]]:
190
+ indexed_codes = list(enumerate(requested_codes))
191
+ if worker_count <= 1:
192
+ rows: list[dict[str, Any]] = []
193
+ for _index, code in indexed_codes:
194
+ per_code_params = dict(params)
195
+ per_code_params["code"] = code
196
+ rows.extend(request_one(per_code_params))
197
+ return rows
198
+
199
+ def request_indexed(index: int, code: str) -> tuple[int, list[dict[str, Any]]]:
200
+ per_code_params = dict(params)
201
+ per_code_params["code"] = code
202
+ return index, request_one(per_code_params)
203
+
204
+ rows_by_index: dict[int, list[dict[str, Any]]] = {}
205
+ resolved_worker_count = max(1, min(worker_count, len(indexed_codes)))
206
+ with ThreadPoolExecutor(
207
+ max_workers=resolved_worker_count,
208
+ thread_name_prefix="axdata-tdx-f10",
209
+ ) as executor:
210
+ futures = [executor.submit(request_indexed, index, code) for index, code in indexed_codes]
211
+ for future in as_completed(futures):
212
+ index, code_rows = future.result()
213
+ rows_by_index[index] = code_rows
214
+ rows = []
215
+ for index, _code in indexed_codes:
216
+ rows.extend(rows_by_index.get(index, []))
217
+ return rows
218
+
219
+
220
+ def request_topic_rows_with_refill(
221
+ instrument_ids: Sequence[str],
222
+ *,
223
+ request_one: Callable[[str], list[dict[str, Any]]],
224
+ topic_worker_count: int,
225
+ refill_rounds: int,
226
+ refill_worker_count: int,
227
+ progress_callback: Callable[..., None] | None = None,
228
+ progress_start: int | None = None,
229
+ progress_span: int | None = None,
230
+ ) -> tuple[dict[str, list[dict[str, Any]]], dict[str, Any]]:
231
+ topic_rows: dict[str, list[dict[str, Any]]] = {}
232
+ unique_instrument_ids = [
233
+ instrument_id
234
+ for instrument_id in dict.fromkeys(str(value or "") for value in instrument_ids)
235
+ if instrument_id
236
+ ]
237
+ total = len(unique_instrument_ids)
238
+ progress_started_at = monotonic()
239
+
240
+ def mark_done(instrument_id: str, rows: list[dict[str, Any]], completed: int) -> None:
241
+ topic_rows[instrument_id] = rows
242
+ if progress_start is not None and progress_span is not None:
243
+ _emit_topic_progress(
244
+ progress_callback,
245
+ completed=completed,
246
+ total=total,
247
+ started_at=progress_started_at,
248
+ percent_start=progress_start,
249
+ percent_span=progress_span,
250
+ )
251
+
252
+ def fetch_many(
253
+ requested_instrument_ids: Sequence[str],
254
+ *,
255
+ worker_count: int,
256
+ completed_offset: int,
257
+ ) -> int:
258
+ requested = list(requested_instrument_ids)
259
+ if not requested:
260
+ return completed_offset
261
+ completed = completed_offset
262
+ resolved_worker_count = max(1, min(worker_count, len(requested)))
263
+ if resolved_worker_count <= 1:
264
+ for instrument_id in requested:
265
+ rows = request_one(instrument_id)
266
+ completed += 1
267
+ mark_done(instrument_id, rows, completed)
268
+ return completed
269
+ with ThreadPoolExecutor(
270
+ max_workers=resolved_worker_count,
271
+ thread_name_prefix="axdata-tdx-topic",
272
+ ) as executor:
273
+ futures = {executor.submit(request_one, instrument_id): instrument_id for instrument_id in requested}
274
+ for future in as_completed(futures):
275
+ instrument_id = futures[future]
276
+ rows = future.result()
277
+ completed += 1
278
+ mark_done(instrument_id, rows, completed)
279
+ return completed
280
+
281
+ completed = fetch_many(
282
+ unique_instrument_ids,
283
+ worker_count=topic_worker_count,
284
+ completed_offset=0,
285
+ )
286
+ refill_requested_count = 0
287
+ refill_round_count = 0
288
+ for _round in range(refill_rounds):
289
+ missing_instrument_ids = [
290
+ instrument_id
291
+ for instrument_id in unique_instrument_ids
292
+ if not topic_rows.get(instrument_id)
293
+ ]
294
+ if not missing_instrument_ids:
295
+ break
296
+ refill_round_count += 1
297
+ refill_requested_count += len(missing_instrument_ids)
298
+ completed = fetch_many(
299
+ missing_instrument_ids,
300
+ worker_count=refill_worker_count,
301
+ completed_offset=completed,
302
+ )
303
+
304
+ meta = {
305
+ "tdx_f10_topic_workers": topic_worker_count,
306
+ "tdx_f10_topic_refill_workers": max(1, min(refill_worker_count, int(total or 1))),
307
+ "tdx_f10_topic_refill_rounds": refill_round_count,
308
+ "tdx_f10_topic_refill_configured_rounds": refill_rounds,
309
+ "tdx_f10_topic_refill_requested_count": refill_requested_count,
310
+ "tdx_f10_topic_missing_stock_count": sum(
311
+ 1 for instrument_id in unique_instrument_ids if not topic_rows.get(instrument_id)
312
+ ),
313
+ }
314
+ return topic_rows, meta
315
+
316
+
317
+ def request_topic_rows_by_instrument_id(
318
+ instrument_ids: Sequence[str],
319
+ *,
320
+ topic_type: str,
321
+ request_f10: Callable[[str, Mapping[str, Any]], list[dict[str, Any]]],
322
+ topic_worker_count: int,
323
+ refill_rounds: int,
324
+ refill_worker_count: int,
325
+ request_error: type[BaseException],
326
+ progress_callback: Callable[..., None] | None = None,
327
+ progress_start: int | None = None,
328
+ progress_span: int | None = None,
329
+ ) -> tuple[dict[str, list[dict[str, Any]]], dict[str, Any]]:
330
+ unique_instrument_ids = [
331
+ instrument_id
332
+ for instrument_id in dict.fromkeys(str(value or "") for value in instrument_ids)
333
+ if instrument_id
334
+ ]
335
+
336
+ def request_one(instrument_id: str) -> list[dict[str, Any]]:
337
+ try:
338
+ return request_f10(
339
+ "stock_topic_exposure_tdx",
340
+ {"code": instrument_id, "topic_type": topic_type},
341
+ )
342
+ except request_error:
343
+ return []
344
+
345
+ return request_topic_rows_with_refill(
346
+ unique_instrument_ids,
347
+ request_one=request_one,
348
+ topic_worker_count=topic_worker_count,
349
+ refill_rounds=refill_rounds,
350
+ refill_worker_count=refill_worker_count,
351
+ progress_callback=progress_callback,
352
+ progress_start=progress_start,
353
+ progress_span=progress_span,
354
+ )
355
+
356
+
357
+ def request_topic_rows_lookup_result(
358
+ instrument_ids: Sequence[str],
359
+ *,
360
+ topic_type: str,
361
+ request_f10: Callable[[str, Mapping[str, Any]], list[dict[str, Any]]],
362
+ topic_worker_count: Callable[[int], int],
363
+ refill_rounds: int,
364
+ refill_worker_count: int,
365
+ request_error: type[BaseException],
366
+ progress_callback: Callable[..., None] | None = None,
367
+ progress_start: int | None = None,
368
+ progress_span: int | None = None,
369
+ ) -> TopicRowsLookupResult:
370
+ total = len([value for value in dict.fromkeys(str(value or "") for value in instrument_ids) if value])
371
+ rows, meta = request_topic_rows_by_instrument_id(
372
+ instrument_ids,
373
+ topic_type=topic_type,
374
+ request_f10=request_f10,
375
+ topic_worker_count=topic_worker_count(total),
376
+ refill_rounds=refill_rounds,
377
+ refill_worker_count=refill_worker_count,
378
+ request_error=request_error,
379
+ progress_callback=progress_callback,
380
+ progress_start=progress_start,
381
+ progress_span=progress_span,
382
+ )
383
+ return TopicRowsLookupResult(rows=rows, meta=meta)
384
+
385
+
386
+ def _emit_topic_progress(
387
+ progress_callback: Callable[..., None] | None,
388
+ *,
389
+ completed: int,
390
+ total: int,
391
+ started_at: float,
392
+ percent_start: int,
393
+ percent_span: int,
394
+ ) -> None:
395
+ if progress_callback is None or total <= 0:
396
+ return
397
+ safe_completed = min(total, max(0, completed))
398
+ fraction = min(1.0, max(0.0, safe_completed / total))
399
+ percent = percent_start + int(fraction * percent_span)
400
+ elapsed_ms = max(0.0, (monotonic() - started_at) * 1000)
401
+ eta_ms = None
402
+ if safe_completed > 0 and safe_completed < total:
403
+ eta_ms = int((elapsed_ms / safe_completed) * (total - safe_completed))
404
+ try:
405
+ progress_callback(
406
+ percent,
407
+ f"查询题材 {safe_completed}/{total} 只",
408
+ progress_current=safe_completed,
409
+ progress_total=total,
410
+ progress_unit="只",
411
+ eta_ms=eta_ms,
412
+ )
413
+ except TypeError:
414
+ progress_callback(percent, f"查询题材 {safe_completed}/{total} 只")