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,500 @@
1
+ """Derived TDX request row builders."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Mapping, Sequence
6
+ from datetime import datetime
7
+ from typing import Any
8
+
9
+ from .codes import MARKET_TO_EXCHANGE, MARKET_TO_ID, tdx_code_to_instrument_id
10
+ from .normalize_utils import (
11
+ auction_open_volume_hand,
12
+ bar_trade_date,
13
+ get_value,
14
+ limit_board_text,
15
+ market_value,
16
+ open_volume_ratio,
17
+ optional_int,
18
+ round_optional_float,
19
+ safe_ratio,
20
+ safe_ratio_pct,
21
+ tenk_shares_to_lots,
22
+ tenk_to_unit,
23
+ tenk_yuan,
24
+ )
25
+ from .price_limits import (
26
+ limit_ladder_status,
27
+ positive_number,
28
+ price_limit_name_flag,
29
+ price_limit_ratio_from_rule,
30
+ price_limit_rule,
31
+ rule_price_limits,
32
+ st_type_from_name,
33
+ )
34
+ from .request_filters import board_matches
35
+ from .security_codes import board_from_tdx_code
36
+ from .snapshot_normalize import (
37
+ normalize_realtime_snapshot_row,
38
+ quote_level_at,
39
+ quote_tdx_code,
40
+ )
41
+ from axdata_core.source_errors import SourceRequestValidationError
42
+
43
+
44
+ def normalize_auction_indicator_row(
45
+ quote: Any,
46
+ *,
47
+ stat_row: Any | None,
48
+ stat2_row: Any | None,
49
+ recent_daily_bars: Sequence[Any],
50
+ finance_row: Mapping[str, Any] | None,
51
+ ) -> dict[str, Any]:
52
+ snapshot = normalize_realtime_snapshot_row(quote)
53
+ open_price = snapshot.get("open")
54
+ last_price = snapshot.get("last_price")
55
+ open_amount = snapshot.get("open_amount")
56
+ amount = snapshot.get("amount")
57
+ locked_amount = snapshot.get("locked_amount")
58
+ open_volume_hand_value = auction_open_volume_hand(open_amount, open_price)
59
+ free_float_shares = tenk_to_unit(get_value(stat_row, "free_float_shares_10k"))
60
+ free_float_market_value = market_value(free_float_shares, last_price)
61
+ float_shares = get_value(finance_row, "float_share") if finance_row else None
62
+ float_market_value = market_value(float_shares, last_price)
63
+ prev_amount = tenk_yuan(get_value(stat2_row, "prev_amount_10k"))
64
+ prev_seal_amount = tenk_yuan(get_value(stat2_row, "prev_seal_amount_10k"))
65
+ prev2_seal_amount = tenk_yuan(get_value(stat2_row, "prev2_seal_amount_10k"))
66
+ prev_open_amount = tenk_yuan(get_value(stat2_row, "prev_open_amount_10k"))
67
+ prev_open_volume_hand = round_optional_float(get_value(stat2_row, "prev_open_volume_hand"))
68
+ limit_stat_days = optional_int(get_value(stat_row, "limit_stat_days"))
69
+ limit_up_count = optional_int(get_value(stat_row, "limit_up_count_in_stat_days"))
70
+ return {
71
+ "instrument_id": snapshot.get("instrument_id"),
72
+ "symbol": snapshot.get("symbol"),
73
+ "tdx_code": str(snapshot.get("tdx_code") or "").lower(),
74
+ "exchange": snapshot.get("exchange"),
75
+ "stats_date": get_value(stat2_row, "stats_date") or get_value(stat_row, "stats_date"),
76
+ "open_price": open_price,
77
+ "pre_close": snapshot.get("pre_close"),
78
+ "open_change_pct": snapshot.get("open_change_pct"),
79
+ "open_amount": open_amount,
80
+ "open_volume_hand": open_volume_hand_value,
81
+ "open_volume_ratio": open_volume_ratio(open_volume_hand_value, recent_daily_bars),
82
+ "open_turnover_z": safe_ratio_pct(open_volume_hand_value, tenk_shares_to_lots(get_value(stat_row, "free_float_shares_10k"))),
83
+ "open_prev_amount_ratio": safe_ratio_pct(open_amount, prev_amount),
84
+ "auction_prev_volume_ratio": safe_ratio(open_volume_hand_value, prev_open_volume_hand),
85
+ "opening_rush": snapshot.get("opening_rush"),
86
+ "open_prev_seal_ratio": safe_ratio_pct(open_amount, prev_seal_amount),
87
+ "prev_amount": prev_amount,
88
+ "prev_seal_amount": prev_seal_amount,
89
+ "prev2_seal_amount": prev2_seal_amount,
90
+ "prev_open_volume_hand": prev_open_volume_hand,
91
+ "prev_open_amount": prev_open_amount,
92
+ "float_shares": round_optional_float(float_shares),
93
+ "float_market_value": float_market_value,
94
+ "free_float_shares": free_float_shares,
95
+ "free_float_market_value": free_float_market_value,
96
+ "seal_amount": locked_amount,
97
+ "seal_to_amount_ratio": safe_ratio(locked_amount, amount),
98
+ "seal_to_float_ratio": safe_ratio_pct(locked_amount, free_float_market_value),
99
+ "seal_prev_ratio": safe_ratio(locked_amount, prev_seal_amount),
100
+ "limit_stat_days": limit_stat_days,
101
+ "limit_up_count_in_stat_days": limit_up_count,
102
+ "limit_board_text": limit_board_text(limit_stat_days, limit_up_count),
103
+ "limit_up_streak_days": optional_int(get_value(stat_row, "limit_up_streak_days")),
104
+ "year_limit_up_days": optional_int(get_value(stat_row, "year_limit_up_days")),
105
+ }
106
+
107
+
108
+ def normalize_daily_share_row(
109
+ tdx_code: str,
110
+ *,
111
+ stats_date: Any,
112
+ stat_row: Any | None,
113
+ finance_row: Mapping[str, Any] | None,
114
+ ) -> dict[str, Any]:
115
+ instrument_id = tdx_code_to_instrument_id(tdx_code)
116
+ symbol = instrument_id.split(".", 1)[0]
117
+ total_share = get_value(finance_row, "total_share") if finance_row else None
118
+ float_share = get_value(finance_row, "float_share") if finance_row else None
119
+ free_float_share = tenk_to_unit(get_value(stat_row, "free_float_shares_10k"))
120
+ row_stats_date = get_value(stat_row, "stats_date") or stats_date
121
+ row_stats_date = str(row_stats_date) if row_stats_date not in (None, "") else None
122
+ return {
123
+ "trade_date": row_stats_date,
124
+ "instrument_id": instrument_id,
125
+ "symbol": symbol,
126
+ "tdx_code": tdx_code,
127
+ "exchange": MARKET_TO_EXCHANGE.get(tdx_code[:2], tdx_code[:2].upper()),
128
+ "total_share": round_optional_float(total_share),
129
+ "float_share": round_optional_float(float_share),
130
+ "free_float_share_z": free_float_share,
131
+ "finance_updated_date": get_value(finance_row, "updated_date") if finance_row else None,
132
+ "share_source": daily_share_source(total_share, float_share, free_float_share),
133
+ }
134
+
135
+
136
+ def normalize_daily_price_limit_row(
137
+ tdx_code: str,
138
+ *,
139
+ target_trade_date: Any,
140
+ base: Any,
141
+ name: str | None,
142
+ ) -> dict[str, Any]:
143
+ base_bar = getattr(base, "bar", None)
144
+ pre_close = round_optional_float(get_value(base_bar, "close")) if base_bar is not None else None
145
+ pre_close_trade_date = bar_trade_date(base_bar) if base_bar is not None else None
146
+ return normalize_daily_price_limit_from_pre_close(
147
+ tdx_code,
148
+ target_trade_date=target_trade_date,
149
+ pre_close_trade_date=pre_close_trade_date,
150
+ pre_close=pre_close,
151
+ pre_close_source="tdx_daily_kline" if pre_close_trade_date else None,
152
+ name=name,
153
+ )
154
+
155
+
156
+ def normalize_daily_price_limit_snapshot_row(
157
+ tdx_code: str,
158
+ *,
159
+ target_trade_date: Any,
160
+ pre_close_trade_date: Any,
161
+ snapshot: Mapping[str, Any] | None,
162
+ snapshot_base_field: str,
163
+ name: str | None,
164
+ ) -> dict[str, Any]:
165
+ if snapshot_base_field == "last_price":
166
+ pre_close = snapshot.get("last_price") if snapshot is not None else None
167
+ else:
168
+ pre_close = snapshot.get("pre_close") if snapshot is not None else None
169
+ return normalize_daily_price_limit_from_pre_close(
170
+ tdx_code,
171
+ target_trade_date=target_trade_date,
172
+ pre_close_trade_date=pre_close_trade_date,
173
+ pre_close=pre_close,
174
+ pre_close_source="tdx_realtime_snapshot" if snapshot is not None else None,
175
+ name=name,
176
+ )
177
+
178
+
179
+ def normalize_daily_price_limit_from_pre_close(
180
+ tdx_code: str,
181
+ *,
182
+ target_trade_date: Any,
183
+ pre_close_trade_date: Any,
184
+ pre_close: Any,
185
+ pre_close_source: str | None,
186
+ name: str | None,
187
+ ) -> dict[str, Any]:
188
+ instrument_id = tdx_code_to_instrument_id(tdx_code)
189
+ symbol = instrument_id.split(".", 1)[0]
190
+ exchange = MARKET_TO_EXCHANGE.get(tdx_code[:2], tdx_code[:2].upper())
191
+ normalized_pre_close = round_optional_float(pre_close)
192
+ normalized_pre_close_trade_date = str(pre_close_trade_date) if pre_close_trade_date not in (None, "") else None
193
+ name_flag = price_limit_name_flag(name)
194
+ limit_ratio = price_limit_ratio_from_rule(tdx_code, name_flag)
195
+ limit_rule = price_limit_rule(tdx_code, name_flag)
196
+ limit_status = "normal"
197
+ if limit_ratio is None:
198
+ limit_up_price = None
199
+ limit_down_price = None
200
+ limit_status = "no_price_limit"
201
+ elif normalized_pre_close in (None, ""):
202
+ limit_up_price = None
203
+ limit_down_price = None
204
+ limit_status = "missing_pre_close"
205
+ else:
206
+ limit_up_price, limit_down_price = rule_price_limits(normalized_pre_close, limit_ratio)
207
+
208
+ return {
209
+ "trade_date": str(target_trade_date) if target_trade_date not in (None, "") else None,
210
+ "instrument_id": instrument_id,
211
+ "symbol": symbol,
212
+ "tdx_code": tdx_code,
213
+ "exchange": exchange,
214
+ "name": name,
215
+ "name_flag": name_flag,
216
+ "pre_close_trade_date": normalized_pre_close_trade_date,
217
+ "pre_close": normalized_pre_close,
218
+ "pre_close_source": pre_close_source if normalized_pre_close not in (None, "") else None,
219
+ "limit_up_price": limit_up_price,
220
+ "limit_down_price": limit_down_price,
221
+ "limit_ratio_pct": limit_ratio,
222
+ "limit_rule": limit_rule,
223
+ "limit_status": limit_status,
224
+ }
225
+
226
+
227
+ def normalize_limit_ladder_row(
228
+ snapshot: Mapping[str, Any],
229
+ *,
230
+ stats: Any,
231
+ name: str | None,
232
+ boards: set[str] | None,
233
+ include_touched: bool,
234
+ ) -> dict[str, Any] | None:
235
+ tdx_code = str(snapshot.get("tdx_code") or "").lower()
236
+ if not tdx_code:
237
+ return None
238
+ board = board_from_tdx_code(tdx_code)
239
+ if not board_matches(board, boards):
240
+ return None
241
+ name_flag = price_limit_name_flag(name)
242
+ if name_flag in {"ST", "*ST"} or st_type_from_name(name):
243
+ return None
244
+ limit_ratio = price_limit_ratio_from_rule(tdx_code, name_flag)
245
+ if limit_ratio is None:
246
+ return None
247
+ limit_up_price, _limit_down_price = rule_price_limits(snapshot.get("pre_close"), limit_ratio)
248
+ last_price = snapshot.get("last_price")
249
+ pre_close = snapshot.get("pre_close")
250
+ if not positive_number(last_price) or not positive_number(pre_close) or not positive_number(limit_up_price):
251
+ return None
252
+ status = limit_ladder_status(snapshot, limit_up_price)
253
+ if status == "none" or (status == "touched" and not include_touched):
254
+ return None
255
+ market_id = MARKET_TO_ID.get(tdx_code[:2], 0)
256
+ stat_row, _stat2_row = stats.row(market_id, tdx_code[2:])
257
+ ladder_level = limit_ladder_level(stat_row, stats.stats_date)
258
+ recent_days = optional_int(get_value(stat_row, "limit_stat_days"))
259
+ recent_count = optional_int(get_value(stat_row, "limit_up_count_in_stat_days"))
260
+ if status == "sealed":
261
+ recent_days, recent_count = today_limit_board_window(recent_days, recent_count, stats.stats_date)
262
+ amount = snapshot.get("amount")
263
+ seal_amount = snapshot.get("locked_amount") if status == "sealed" else None
264
+ free_float_shares = tenk_to_unit(get_value(stat_row, "free_float_shares_10k"))
265
+ free_float_market_value = market_value(free_float_shares, last_price)
266
+ return {
267
+ "ladder_level": ladder_level if status == "sealed" else None,
268
+ "limit_board_text": limit_board_text(recent_days, recent_count),
269
+ "trade_date": limit_ladder_trade_date(stats.stats_date),
270
+ "stats_date": stats.stats_date,
271
+ "rank": snapshot.get("rank"),
272
+ "limit_status": status,
273
+ "instrument_id": snapshot.get("instrument_id"),
274
+ "symbol": snapshot.get("symbol"),
275
+ "tdx_code": tdx_code,
276
+ "exchange": snapshot.get("exchange"),
277
+ "name": name,
278
+ "last_price": last_price,
279
+ "pre_close": snapshot.get("pre_close"),
280
+ "limit_up_price": limit_up_price,
281
+ "change_pct": snapshot.get("change_pct"),
282
+ "high": snapshot.get("high"),
283
+ "amount": amount,
284
+ "seal_amount": seal_amount,
285
+ "free_float_market_value": free_float_market_value,
286
+ "seal_to_amount_ratio": safe_ratio(seal_amount, amount),
287
+ "limit_stat_days": recent_days,
288
+ "limit_up_count_in_stat_days": recent_count,
289
+ "limit_up_streak_days": optional_int(get_value(stat_row, "limit_up_streak_days")),
290
+ "year_limit_up_days": limit_ladder_year_limit_up_days(stat_row, status, stats.stats_date),
291
+ "primary_theme": None,
292
+ "secondary_themes": None,
293
+ "top_theme_names": None,
294
+ "top_themes": [],
295
+ "themes": [],
296
+ "theme_count": 0,
297
+ "theme_strength_score": None,
298
+ "same_theme_limit_up_count": None,
299
+ "same_theme_highest_board": None,
300
+ "same_theme_lianban_count": None,
301
+ }
302
+
303
+
304
+ def daily_share_source(total_share: Any, float_share: Any, free_float_share: Any) -> str:
305
+ sources = []
306
+ if total_share not in (None, "") or float_share not in (None, ""):
307
+ sources.append("finance_snapshot")
308
+ if free_float_share not in (None, ""):
309
+ sources.append("tdxstat")
310
+ return "+".join(sources) if sources else "empty"
311
+
312
+
313
+ def limit_ladder_rank_page_below_threshold(rows: Sequence[Mapping[str, Any]], threshold: float) -> bool:
314
+ if not rows:
315
+ return True
316
+ return all(not numeric_at_or_above(row.get("change_pct"), threshold) for row in rows)
317
+
318
+
319
+ def limit_ladder_candidate(row: Mapping[str, Any], *, include_touched: bool) -> bool:
320
+ change_pct = row.get("change_pct")
321
+ high_change_pct = row.get("high_change_pct")
322
+ threshold = 4.5 if include_touched else 8.5
323
+ for value in (change_pct, high_change_pct):
324
+ try:
325
+ if value is not None and float(value) >= threshold:
326
+ return True
327
+ except (TypeError, ValueError):
328
+ continue
329
+ return False
330
+
331
+
332
+ def limit_ladder_needs_name_lookup(
333
+ snapshot: Mapping[str, Any],
334
+ *,
335
+ boards: set[str] | None,
336
+ include_touched: bool,
337
+ ) -> bool:
338
+ tdx_code = str(snapshot.get("tdx_code") or "").lower()
339
+ if not tdx_code:
340
+ return False
341
+ if not board_matches(board_from_tdx_code(tdx_code), boards):
342
+ return False
343
+ if not limit_ladder_candidate(snapshot, include_touched=include_touched):
344
+ return False
345
+ limit_ratio = price_limit_ratio_from_rule(tdx_code, None)
346
+ if limit_ratio is None:
347
+ return False
348
+ limit_up_price, _limit_down_price = rule_price_limits(snapshot.get("pre_close"), limit_ratio)
349
+ if not positive_number(snapshot.get("last_price")) or not positive_number(snapshot.get("pre_close")):
350
+ return False
351
+ if not positive_number(limit_up_price):
352
+ return False
353
+ status = limit_ladder_status(snapshot, limit_up_price)
354
+ return status != "none" and (include_touched or status != "touched")
355
+
356
+
357
+ def numeric_at_or_above(value: Any, threshold: float) -> bool:
358
+ try:
359
+ return value is not None and float(value) >= threshold
360
+ except (TypeError, ValueError):
361
+ return False
362
+
363
+
364
+ _LIMIT_LADDER_PUBLIC_FIELDS = (
365
+ "trade_date",
366
+ "ladder_level",
367
+ "limit_board_text",
368
+ "instrument_id",
369
+ "name",
370
+ "last_price",
371
+ "change_pct",
372
+ "limit_status",
373
+ "amount",
374
+ "seal_amount",
375
+ "seal_to_amount_ratio",
376
+ "free_float_market_value",
377
+ "primary_theme",
378
+ "secondary_themes",
379
+ "year_limit_up_days",
380
+ "symbol",
381
+ "exchange",
382
+ "pre_close",
383
+ "limit_up_price",
384
+ )
385
+
386
+
387
+ def limit_ladder_public_row(row: Mapping[str, Any]) -> dict[str, Any]:
388
+ return {field: row.get(field) for field in _LIMIT_LADDER_PUBLIC_FIELDS}
389
+
390
+
391
+ def limit_ladder_trade_date(stats_date: Any) -> str:
392
+ return default_daily_price_limit_trade_date(stats_date)
393
+
394
+
395
+ def stats_date_is_today(stats_date: Any) -> bool:
396
+ stats_text = str(stats_date or "").strip()
397
+ return bool(stats_text) and stats_text == datetime.now().strftime("%Y%m%d")
398
+
399
+
400
+ def limit_ladder_level(stat_row: Any | None, stats_date: Any) -> int:
401
+ prior_streak = optional_int(get_value(stat_row, "limit_up_streak_days")) or 0
402
+ if stats_date_is_today(stats_date):
403
+ return max(1, prior_streak)
404
+ return max(1, prior_streak + 1)
405
+
406
+
407
+ def today_limit_board_window(days: int | None, count: int | None, stats_date: Any) -> tuple[int | None, int | None]:
408
+ if stats_date_is_today(stats_date):
409
+ return days, count
410
+ next_days = (days or 0) + 1
411
+ next_count = (count or 0) + 1
412
+ return next_days if next_days > 0 else None, next_count if next_count > 0 else None
413
+
414
+
415
+ def limit_ladder_year_limit_up_days(stat_row: Any | None, status: str, stats_date: Any) -> int | None:
416
+ value = optional_int(get_value(stat_row, "year_limit_up_days"))
417
+ if value is None:
418
+ return None
419
+ if status == "sealed" and not stats_date_is_today(stats_date):
420
+ return value + 1
421
+ return value
422
+
423
+
424
+ def limit_ladder_topic_type(value: Any) -> str:
425
+ text = str(value or "theme").strip().lower()
426
+ if text in {"theme", "主题", "题材"}:
427
+ return "theme"
428
+ if text in {"sector", "板块"}:
429
+ return "sector"
430
+ raise SourceRequestValidationError("topic_type must be theme or sector")
431
+
432
+
433
+ def limit_ladder_count_param(value: Any) -> int | None:
434
+ if value in (None, ""):
435
+ return None
436
+ if isinstance(value, str) and value.strip().lower() == "all":
437
+ return None
438
+ try:
439
+ parsed = int(value)
440
+ except (TypeError, ValueError) as exc:
441
+ raise SourceRequestValidationError("count must be an integer or 'all'") from exc
442
+ if parsed < 1:
443
+ raise SourceRequestValidationError("count must be >= 1")
444
+ if parsed > 500:
445
+ raise SourceRequestValidationError("count must be <= 500")
446
+ return parsed
447
+
448
+
449
+ def normalize_order_book_rows(quote: Any) -> list[dict[str, Any]]:
450
+ tdx_code = quote_tdx_code(quote)
451
+ instrument_id = tdx_code_to_instrument_id(tdx_code)
452
+ symbol = instrument_id.split(".", 1)[0]
453
+ exchange = MARKET_TO_EXCHANGE.get(tdx_code[:2], str(get_value(quote, "exchange") or "").upper())
454
+ bid_levels = list(get_value(quote, "bid_levels", ()) or ())
455
+ ask_levels = list(get_value(quote, "ask_levels", ()) or ())
456
+
457
+ rows: list[dict[str, Any]] = []
458
+ for index in range(5):
459
+ bid_level = quote_level_at(bid_levels, index)
460
+ ask_level = quote_level_at(ask_levels, index)
461
+ rows.append(
462
+ {
463
+ "instrument_id": instrument_id,
464
+ "symbol": symbol,
465
+ "tdx_code": tdx_code,
466
+ "exchange": exchange,
467
+ "level": index + 1,
468
+ "bid_price": round_optional_float(get_value(bid_level, "price")) if bid_level is not None else None,
469
+ "bid_volume": optional_int(get_value(bid_level, "volume")) if bid_level is not None else None,
470
+ "ask_price": round_optional_float(get_value(ask_level, "price")) if ask_level is not None else None,
471
+ "ask_volume": optional_int(get_value(ask_level, "volume")) if ask_level is not None else None,
472
+ }
473
+ )
474
+ return rows
475
+
476
+
477
+ def normalize_adj_factor_row(tdx_code: str, trade_date: str, adj_factor: float) -> dict[str, Any]:
478
+ instrument_id = tdx_code_to_instrument_id(tdx_code)
479
+ return {
480
+ "instrument_id": instrument_id,
481
+ "ts_code": instrument_id,
482
+ "symbol": instrument_id.split(".", 1)[0],
483
+ "tdx_code": tdx_code,
484
+ "exchange": MARKET_TO_EXCHANGE.get(tdx_code[:2], tdx_code[:2].upper()),
485
+ "trade_date": trade_date,
486
+ "adj_factor": round(float(adj_factor), 10),
487
+ }
488
+
489
+
490
+ def default_daily_price_limit_trade_date(stats_date: Any) -> str | None:
491
+ stats_text = str(stats_date or "").strip()
492
+ today_text = datetime.now().strftime("%Y%m%d")
493
+ weekday = datetime.now().weekday()
494
+ if weekday < 5 and (not stats_text or today_text > stats_text):
495
+ return today_text
496
+ return stats_text or today_text
497
+
498
+
499
+ def before_daily_close_buffer(value: datetime) -> bool:
500
+ return value.hour < 15 or (value.hour == 15 and value.minute < 30)
@@ -0,0 +1,139 @@
1
+ """TDX downloader adapter factory 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, Protocol
8
+
9
+ from .downloader_interface_sets import (
10
+ DOWNLOADER_F10_TOPIC_PREFILL_INTERFACES,
11
+ DOWNLOADER_PARALLEL_SUSPENSION_INTERFACES,
12
+ DOWNLOADER_RUNTIME_SOURCE_SERVER_MAX_INTERFACES,
13
+ DOWNLOADER_STATS_RESOURCE_INTERFACES,
14
+ )
15
+ from .server_cache import tdx_server_cache_root, tdx_stats_cache_root
16
+
17
+
18
+ class _SourceRequestAdapterLike(Protocol):
19
+ source: str
20
+
21
+ def supports(self, interface_name: str) -> bool: ...
22
+
23
+ def request(self, interface_name: str, params: dict[str, Any]) -> list[dict[str, Any]]: ...
24
+
25
+
26
+ def create_tdx_client(**kwargs: Any) -> Any:
27
+ """Create a TDX client through the regular factory lazily."""
28
+
29
+ from .client_factory import create_tdx_client as create_client
30
+
31
+ return create_client(**kwargs)
32
+
33
+
34
+ def effective_host_strings(kind: str, *, cache_root: str | None = None) -> list[str]:
35
+ """Resolve configured TDX quote hosts lazily."""
36
+
37
+ from .host_config import effective_host_strings as resolve_hosts
38
+
39
+ return list(resolve_hosts(kind, cache_root=cache_root))
40
+
41
+
42
+ def create_tdx_download_adapter(
43
+ *,
44
+ interface_name: str,
45
+ source_server_count: int,
46
+ pool_size: int,
47
+ data_root: str | Path | None = None,
48
+ progress_callback: Callable[..., None] | None = None,
49
+ ) -> tuple[_SourceRequestAdapterLike, Any]:
50
+ """Create the long-connection adapter used by TDX downloader runs."""
51
+
52
+ server_cache_root = tdx_server_cache_root(data_root)
53
+ stats_cache_root = tdx_stats_cache_root(data_root)
54
+ hosts = effective_host_strings("quote", cache_root=server_cache_root)[: max(1, source_server_count)]
55
+ client = create_tdx_client(
56
+ hosts=hosts or None,
57
+ pool_size=max(1, pool_size),
58
+ heartbeat_interval=None,
59
+ )
60
+ adapter_cls = _tdx_request_adapter_class()
61
+ return (
62
+ adapter_cls(
63
+ client=client,
64
+ progress_callback=progress_callback,
65
+ use_parallel_suspension_quotes=interface_name in DOWNLOADER_PARALLEL_SUSPENSION_INTERFACES,
66
+ options=tdx_adapter_options(
67
+ interface_name=interface_name,
68
+ pool_size=pool_size,
69
+ server_cache_root=server_cache_root,
70
+ stats_cache_root=stats_cache_root,
71
+ ),
72
+ ),
73
+ client,
74
+ )
75
+
76
+
77
+ def tdx_adapter_options(
78
+ *,
79
+ interface_name: str,
80
+ pool_size: int,
81
+ server_cache_root: str | None = None,
82
+ stats_cache_root: str | None = None,
83
+ ) -> dict[str, Any] | None:
84
+ """Return request adapter options for a TDX downloader interface."""
85
+
86
+ options: dict[str, Any] = {}
87
+ if server_cache_root is not None:
88
+ options["server_cache_root"] = server_cache_root
89
+ if stats_cache_root is not None and interface_name in DOWNLOADER_STATS_RESOURCE_INTERFACES:
90
+ options["stats_cache_root"] = stats_cache_root
91
+ if interface_name in DOWNLOADER_F10_TOPIC_PREFILL_INTERFACES:
92
+ options.update(
93
+ {
94
+ "f10_topic_workers": 6,
95
+ "f10_topic_refill_workers": 6,
96
+ "f10_topic_refill_rounds": 1,
97
+ }
98
+ )
99
+ return options or None
100
+
101
+
102
+ def tdx_runtime_source_server_max(
103
+ interface_name: str,
104
+ *,
105
+ configured_max: int,
106
+ source_server_count_editable: bool,
107
+ data_root: str | Path | None = None,
108
+ ) -> int:
109
+ """Return the runtime source-server cap for TDX downloader concurrency."""
110
+
111
+ resolved_configured_max = max(1, int(configured_max))
112
+ if (
113
+ interface_name not in DOWNLOADER_RUNTIME_SOURCE_SERVER_MAX_INTERFACES
114
+ or not source_server_count_editable
115
+ ):
116
+ return resolved_configured_max
117
+ try:
118
+ available_count = len(effective_host_strings("quote", cache_root=tdx_server_cache_root(data_root)))
119
+ except Exception:
120
+ available_count = 0
121
+ if available_count <= 0:
122
+ return resolved_configured_max
123
+ return max(1, min(resolved_configured_max, available_count))
124
+
125
+
126
+ def _tdx_request_adapter_class() -> Any:
127
+ adapter_cls = globals().get("TdxRequestAdapter")
128
+ if adapter_cls is not None:
129
+ return adapter_cls
130
+ from .request_adapter import TdxRequestAdapter as loaded_adapter_cls
131
+
132
+ globals()["TdxRequestAdapter"] = loaded_adapter_cls
133
+ return loaded_adapter_cls
134
+
135
+
136
+ def __getattr__(name: str) -> Any:
137
+ if name == "TdxRequestAdapter":
138
+ return _tdx_request_adapter_class()
139
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
@@ -0,0 +1,29 @@
1
+ """TDX downloader interface groups used by adapter factory rules."""
2
+
3
+ from __future__ import annotations
4
+
5
+
6
+ DOWNLOADER_PARALLEL_SUSPENSION_INTERFACES = frozenset({"stock_suspensions_tdx"})
7
+
8
+ DOWNLOADER_STATS_RESOURCE_INTERFACES = frozenset(
9
+ {
10
+ "stock_daily_share_tdx",
11
+ "stock_limit_ladder_tdx",
12
+ "stock_theme_strength_rank_tdx",
13
+ "stock_shortline_indicators_tdx",
14
+ }
15
+ )
16
+
17
+ DOWNLOADER_F10_TOPIC_PREFILL_INTERFACES = frozenset(
18
+ {
19
+ "stock_limit_ladder_tdx",
20
+ "stock_theme_strength_rank_tdx",
21
+ }
22
+ )
23
+
24
+ DOWNLOADER_RUNTIME_SOURCE_SERVER_MAX_INTERFACES = frozenset(
25
+ {
26
+ "stock_daily_share_tdx",
27
+ "stock_daily_price_limit_tdx",
28
+ }
29
+ )