lib-x17-fintech 2.1.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (282) hide show
  1. lib_x17_fintech-2.1.3.dist-info/METADATA +633 -0
  2. lib_x17_fintech-2.1.3.dist-info/RECORD +282 -0
  3. lib_x17_fintech-2.1.3.dist-info/WHEEL +5 -0
  4. lib_x17_fintech-2.1.3.dist-info/licenses/LICENSE +1 -0
  5. lib_x17_fintech-2.1.3.dist-info/top_level.txt +1 -0
  6. xfintech/__init__.py +0 -0
  7. xfintech/connect/__init__.py +18 -0
  8. xfintech/connect/artifact/__init__.py +5 -0
  9. xfintech/connect/artifact/artifact.py +168 -0
  10. xfintech/connect/artifact/tests/__init__.py +3 -0
  11. xfintech/connect/artifact/tests/test_class_artifact_all.py +564 -0
  12. xfintech/connect/common/__init__.py +12 -0
  13. xfintech/connect/common/connect.py +49 -0
  14. xfintech/connect/common/connectref.py +119 -0
  15. xfintech/connect/common/error.py +62 -0
  16. xfintech/connect/common/tests/__init__.py +1 -0
  17. xfintech/connect/common/tests/test_class_connectlike_all.py +544 -0
  18. xfintech/connect/common/tests/test_class_connectref_all.py +586 -0
  19. xfintech/connect/common/tests/test_class_errors_all.py +524 -0
  20. xfintech/connect/instance/__init__.py +7 -0
  21. xfintech/connect/instance/macos.py +121 -0
  22. xfintech/connect/instance/s3.py +176 -0
  23. xfintech/connect/instance/tests/__init__.py +1 -0
  24. xfintech/connect/instance/tests/test_class_macosconnect_all.py +692 -0
  25. xfintech/connect/instance/tests/test_class_s3connect_all.py +603 -0
  26. xfintech/data/__init__.py +20 -0
  27. xfintech/data/common/__init__.py +15 -0
  28. xfintech/data/common/cache.py +186 -0
  29. xfintech/data/common/coolant.py +171 -0
  30. xfintech/data/common/metric.py +138 -0
  31. xfintech/data/common/paginate.py +132 -0
  32. xfintech/data/common/params.py +162 -0
  33. xfintech/data/common/retry.py +201 -0
  34. xfintech/data/common/tests/__init__.py +1 -0
  35. xfintech/data/common/tests/test_class_cache_all.py +681 -0
  36. xfintech/data/common/tests/test_class_coolant_all.py +534 -0
  37. xfintech/data/common/tests/test_class_metric_all.py +705 -0
  38. xfintech/data/common/tests/test_class_paginate_all.py +508 -0
  39. xfintech/data/common/tests/test_class_params_all.py +891 -0
  40. xfintech/data/common/tests/test_class_retry_all.py +714 -0
  41. xfintech/data/job/__init__.py +17 -0
  42. xfintech/data/job/errors.py +112 -0
  43. xfintech/data/job/house.py +156 -0
  44. xfintech/data/job/job.py +247 -0
  45. xfintech/data/job/joblike.py +47 -0
  46. xfintech/data/job/tests/__init__.py +1 -0
  47. xfintech/data/job/tests/test_class_errors_all.py +275 -0
  48. xfintech/data/job/tests/test_class_house_all.py +801 -0
  49. xfintech/data/job/tests/test_class_job_all.py +684 -0
  50. xfintech/data/job/tests/test_class_joblike_all.py +482 -0
  51. xfintech/data/relay/__init__.py +7 -0
  52. xfintech/data/relay/client.py +114 -0
  53. xfintech/data/relay/clientlike.py +45 -0
  54. xfintech/data/relay/tests/test_class_relayclient_all.py +484 -0
  55. xfintech/data/relay/tests/test_class_relayclientlike_all.py +500 -0
  56. xfintech/data/source/__init__.py +7 -0
  57. xfintech/data/source/baostock/__init__.py +21 -0
  58. xfintech/data/source/baostock/job/__init__.py +5 -0
  59. xfintech/data/source/baostock/job/job.py +217 -0
  60. xfintech/data/source/baostock/job/tests/__init__.py +0 -0
  61. xfintech/data/source/baostock/job/tests/test_class_baostockjob_all.py +547 -0
  62. xfintech/data/source/baostock/session/__init__.py +8 -0
  63. xfintech/data/source/baostock/session/relay.py +223 -0
  64. xfintech/data/source/baostock/session/session.py +241 -0
  65. xfintech/data/source/baostock/session/tests/__init__.py +0 -0
  66. xfintech/data/source/baostock/session/tests/test_class_relay_all.py +694 -0
  67. xfintech/data/source/baostock/session/tests/test_class_session_all.py +505 -0
  68. xfintech/data/source/baostock/stock/__init__.py +0 -0
  69. xfintech/data/source/baostock/stock/hs300stock/__init__.py +3 -0
  70. xfintech/data/source/baostock/stock/hs300stock/constant.py +49 -0
  71. xfintech/data/source/baostock/stock/hs300stock/hs300stock.py +133 -0
  72. xfintech/data/source/baostock/stock/hs300stock/tests/__init__.py +1 -0
  73. xfintech/data/source/baostock/stock/hs300stock/tests/test_class_hs300index_all.py +413 -0
  74. xfintech/data/source/baostock/stock/minuteline/__init__.py +19 -0
  75. xfintech/data/source/baostock/stock/minuteline/constant.py +89 -0
  76. xfintech/data/source/baostock/stock/minuteline/minuteline.py +163 -0
  77. xfintech/data/source/baostock/stock/minuteline/tests/__init__.py +0 -0
  78. xfintech/data/source/baostock/stock/minuteline/tests/test_class_minuteline_all.py +582 -0
  79. xfintech/data/source/baostock/stock/stock/__init__.py +19 -0
  80. xfintech/data/source/baostock/stock/stock/constant.py +55 -0
  81. xfintech/data/source/baostock/stock/stock/stock.py +149 -0
  82. xfintech/data/source/baostock/stock/stock/tests/__init__.py +0 -0
  83. xfintech/data/source/baostock/stock/stock/tests/test_class_stock_all.py +508 -0
  84. xfintech/data/source/baostock/stock/stockinfo/__init__.py +5 -0
  85. xfintech/data/source/baostock/stock/stockinfo/constant.py +66 -0
  86. xfintech/data/source/baostock/stock/stockinfo/stockinfo.py +176 -0
  87. xfintech/data/source/baostock/stock/stockinfo/tests/__init__.py +1 -0
  88. xfintech/data/source/baostock/stock/stockinfo/tests/test_class_stockinfo_all.py +617 -0
  89. xfintech/data/source/baostock/stock/sz50stock/__init__.py +3 -0
  90. xfintech/data/source/baostock/stock/sz50stock/constant.py +49 -0
  91. xfintech/data/source/baostock/stock/sz50stock/sz50stock.py +133 -0
  92. xfintech/data/source/baostock/stock/sz50stock/tests/__init__.py +1 -0
  93. xfintech/data/source/baostock/stock/sz50stock/tests/test_class_sz50stock_all.py +397 -0
  94. xfintech/data/source/baostock/stock/tradedate/__init__.py +19 -0
  95. xfintech/data/source/baostock/stock/tradedate/constant.py +72 -0
  96. xfintech/data/source/baostock/stock/tradedate/tests/__init__.py +0 -0
  97. xfintech/data/source/baostock/stock/tradedate/tests/test_class_tradedate_all.py +695 -0
  98. xfintech/data/source/baostock/stock/tradedate/tradedate.py +208 -0
  99. xfintech/data/source/baostock/stock/zz500stock/__init__.py +3 -0
  100. xfintech/data/source/baostock/stock/zz500stock/constant.py +55 -0
  101. xfintech/data/source/baostock/stock/zz500stock/tests/__init__.py +1 -0
  102. xfintech/data/source/baostock/stock/zz500stock/tests/test_class_zz500stock_all.py +421 -0
  103. xfintech/data/source/baostock/stock/zz500stock/zz500stock.py +133 -0
  104. xfintech/data/source/tushare/__init__.py +61 -0
  105. xfintech/data/source/tushare/job/__init__.py +5 -0
  106. xfintech/data/source/tushare/job/job.py +257 -0
  107. xfintech/data/source/tushare/job/tests/test_class_tusharejob_all.py +589 -0
  108. xfintech/data/source/tushare/session/__init__.py +5 -0
  109. xfintech/data/source/tushare/session/relay.py +231 -0
  110. xfintech/data/source/tushare/session/session.py +239 -0
  111. xfintech/data/source/tushare/session/tests/test_class_relay_all.py +719 -0
  112. xfintech/data/source/tushare/session/tests/test_class_session_all.py +705 -0
  113. xfintech/data/source/tushare/stock/__init__.py +55 -0
  114. xfintech/data/source/tushare/stock/adjfactor/__init__.py +19 -0
  115. xfintech/data/source/tushare/stock/adjfactor/adjfactor.py +150 -0
  116. xfintech/data/source/tushare/stock/adjfactor/constant.py +71 -0
  117. xfintech/data/source/tushare/stock/adjfactor/tests/__init__.py +0 -0
  118. xfintech/data/source/tushare/stock/adjfactor/tests/test_class_adjfactor_all.py +372 -0
  119. xfintech/data/source/tushare/stock/capflow/__init__.py +19 -0
  120. xfintech/data/source/tushare/stock/capflow/capflow.py +171 -0
  121. xfintech/data/source/tushare/stock/capflow/constant.py +105 -0
  122. xfintech/data/source/tushare/stock/capflow/tests/__init__.py +0 -0
  123. xfintech/data/source/tushare/stock/capflow/tests/test_class_capflow_all.py +589 -0
  124. xfintech/data/source/tushare/stock/capflowdc/__init__.py +19 -0
  125. xfintech/data/source/tushare/stock/capflowdc/capflowdc.py +167 -0
  126. xfintech/data/source/tushare/stock/capflowdc/constant.py +95 -0
  127. xfintech/data/source/tushare/stock/capflowdc/tests/__init__.py +0 -0
  128. xfintech/data/source/tushare/stock/capflowdc/tests/test_class_capflowdc_all.py +814 -0
  129. xfintech/data/source/tushare/stock/capflowths/__init__.py +19 -0
  130. xfintech/data/source/tushare/stock/capflowths/capflowths.py +173 -0
  131. xfintech/data/source/tushare/stock/capflowths/constant.py +92 -0
  132. xfintech/data/source/tushare/stock/capflowths/tests/__init__.py +0 -0
  133. xfintech/data/source/tushare/stock/capflowths/tests/test_class_capflowths_all.py +551 -0
  134. xfintech/data/source/tushare/stock/company/__init__.py +19 -0
  135. xfintech/data/source/tushare/stock/company/company.py +188 -0
  136. xfintech/data/source/tushare/stock/company/constant.py +92 -0
  137. xfintech/data/source/tushare/stock/company/tests/__init__.py +1 -0
  138. xfintech/data/source/tushare/stock/company/tests/test_class_company_all.py +829 -0
  139. xfintech/data/source/tushare/stock/companybusiness/__init__.py +21 -0
  140. xfintech/data/source/tushare/stock/companybusiness/companybusiness.py +183 -0
  141. xfintech/data/source/tushare/stock/companybusiness/constant.py +91 -0
  142. xfintech/data/source/tushare/stock/companybusiness/tests/__init__.py +0 -0
  143. xfintech/data/source/tushare/stock/companybusiness/tests/test_class_companybusiness_all.py +633 -0
  144. xfintech/data/source/tushare/stock/companycashflow/__init__.py +21 -0
  145. xfintech/data/source/tushare/stock/companycashflow/companycashflow.py +277 -0
  146. xfintech/data/source/tushare/stock/companycashflow/constant.py +293 -0
  147. xfintech/data/source/tushare/stock/companycashflow/tests/__init__.py +0 -0
  148. xfintech/data/source/tushare/stock/companycashflow/tests/test_class_companycashflow_all.py +619 -0
  149. xfintech/data/source/tushare/stock/companydebt/__init__.py +19 -0
  150. xfintech/data/source/tushare/stock/companydebt/companydebt.py +339 -0
  151. xfintech/data/source/tushare/stock/companydebt/constant.py +403 -0
  152. xfintech/data/source/tushare/stock/companydebt/tests/__init__.py +0 -0
  153. xfintech/data/source/tushare/stock/companydebt/tests/test_class_companydebt_all.py +655 -0
  154. xfintech/data/source/tushare/stock/companyoverview/__init__.py +21 -0
  155. xfintech/data/source/tushare/stock/companyoverview/companyoverview.py +214 -0
  156. xfintech/data/source/tushare/stock/companyoverview/constant.py +152 -0
  157. xfintech/data/source/tushare/stock/companyoverview/tests/__init__.py +0 -0
  158. xfintech/data/source/tushare/stock/companyoverview/tests/test_class_companyoverview_all.py +647 -0
  159. xfintech/data/source/tushare/stock/companyprofit/__init__.py +21 -0
  160. xfintech/data/source/tushare/stock/companyprofit/companyprofit.py +272 -0
  161. xfintech/data/source/tushare/stock/companyprofit/constant.py +259 -0
  162. xfintech/data/source/tushare/stock/companyprofit/tests/__init__.py +0 -0
  163. xfintech/data/source/tushare/stock/companyprofit/tests/test_class_companyprofit_all.py +635 -0
  164. xfintech/data/source/tushare/stock/conceptcapflowdc/__init__.py +21 -0
  165. xfintech/data/source/tushare/stock/conceptcapflowdc/conceptcapflowdc.py +175 -0
  166. xfintech/data/source/tushare/stock/conceptcapflowdc/constant.py +106 -0
  167. xfintech/data/source/tushare/stock/conceptcapflowdc/tests/__init__.py +0 -0
  168. xfintech/data/source/tushare/stock/conceptcapflowdc/tests/test_class_conceptcapflowdc_all.py +568 -0
  169. xfintech/data/source/tushare/stock/conceptcapflowths/__init__.py +21 -0
  170. xfintech/data/source/tushare/stock/conceptcapflowths/conceptcapflowths.py +188 -0
  171. xfintech/data/source/tushare/stock/conceptcapflowths/constant.py +89 -0
  172. xfintech/data/source/tushare/stock/conceptcapflowths/tests/__init__.py +0 -0
  173. xfintech/data/source/tushare/stock/conceptcapflowths/tests/test_class_conceptcapflowths_all.py +516 -0
  174. xfintech/data/source/tushare/stock/dayline/__init__.py +19 -0
  175. xfintech/data/source/tushare/stock/dayline/constant.py +87 -0
  176. xfintech/data/source/tushare/stock/dayline/dayline.py +177 -0
  177. xfintech/data/source/tushare/stock/dayline/tests/__init__.py +0 -0
  178. xfintech/data/source/tushare/stock/dayline/tests/test_class_dayline_all.py +585 -0
  179. xfintech/data/source/tushare/stock/industrycapflowths/__init__.py +21 -0
  180. xfintech/data/source/tushare/stock/industrycapflowths/constant.py +89 -0
  181. xfintech/data/source/tushare/stock/industrycapflowths/industrycapflowths.py +192 -0
  182. xfintech/data/source/tushare/stock/industrycapflowths/tests/__init__.py +0 -0
  183. xfintech/data/source/tushare/stock/industrycapflowths/tests/test_class_industrycapflowths_all.py +683 -0
  184. xfintech/data/source/tushare/stock/marketindexcapflowdc/__init__.py +21 -0
  185. xfintech/data/source/tushare/stock/marketindexcapflowdc/constant.py +90 -0
  186. xfintech/data/source/tushare/stock/marketindexcapflowdc/marketindexcapflowdc.py +173 -0
  187. xfintech/data/source/tushare/stock/marketindexcapflowdc/tests/__init__.py +0 -0
  188. xfintech/data/source/tushare/stock/marketindexcapflowdc/tests/test_class_marketindexcapflowdc_all.py +793 -0
  189. xfintech/data/source/tushare/stock/monthline/__init__.py +19 -0
  190. xfintech/data/source/tushare/stock/monthline/constant.py +87 -0
  191. xfintech/data/source/tushare/stock/monthline/monthline.py +180 -0
  192. xfintech/data/source/tushare/stock/monthline/tests/__init__.py +0 -0
  193. xfintech/data/source/tushare/stock/monthline/tests/test_class_monthline_all.py +574 -0
  194. xfintech/data/source/tushare/stock/stock/__init__.py +19 -0
  195. xfintech/data/source/tushare/stock/stock/constant.py +105 -0
  196. xfintech/data/source/tushare/stock/stock/stock.py +193 -0
  197. xfintech/data/source/tushare/stock/stock/tests/__init__.py +0 -0
  198. xfintech/data/source/tushare/stock/stock/tests/test_class_stock_all.py +788 -0
  199. xfintech/data/source/tushare/stock/stockdividend/__init__.py +21 -0
  200. xfintech/data/source/tushare/stock/stockdividend/constant.py +111 -0
  201. xfintech/data/source/tushare/stock/stockdividend/stockdividend.py +180 -0
  202. xfintech/data/source/tushare/stock/stockdividend/tests/__init__.py +0 -0
  203. xfintech/data/source/tushare/stock/stockdividend/tests/test_class_stockdividend_all.py +725 -0
  204. xfintech/data/source/tushare/stock/stockinfo/__init__.py +19 -0
  205. xfintech/data/source/tushare/stock/stockinfo/constant.py +104 -0
  206. xfintech/data/source/tushare/stock/stockinfo/stockinfo.py +208 -0
  207. xfintech/data/source/tushare/stock/stockinfo/tests/__init__.py +0 -0
  208. xfintech/data/source/tushare/stock/stockinfo/tests/test_class_stockinfo_all.py +881 -0
  209. xfintech/data/source/tushare/stock/stockipo/__init__.py +19 -0
  210. xfintech/data/source/tushare/stock/stockipo/constant.py +90 -0
  211. xfintech/data/source/tushare/stock/stockipo/stockipo.py +234 -0
  212. xfintech/data/source/tushare/stock/stockipo/tests/__init__.py +1 -0
  213. xfintech/data/source/tushare/stock/stockipo/tests/test_class_stockipo_all.py +750 -0
  214. xfintech/data/source/tushare/stock/stockpledge/__init__.py +19 -0
  215. xfintech/data/source/tushare/stock/stockpledge/constant.py +72 -0
  216. xfintech/data/source/tushare/stock/stockpledge/stockpledge.py +158 -0
  217. xfintech/data/source/tushare/stock/stockpledge/tests/__init__.py +0 -0
  218. xfintech/data/source/tushare/stock/stockpledge/tests/test_class_stockpledge_all.py +664 -0
  219. xfintech/data/source/tushare/stock/stockpledgedetail/__init__.py +21 -0
  220. xfintech/data/source/tushare/stock/stockpledgedetail/constant.py +85 -0
  221. xfintech/data/source/tushare/stock/stockpledgedetail/stockpledgedetail.py +171 -0
  222. xfintech/data/source/tushare/stock/stockpledgedetail/tests/__init__.py +0 -0
  223. xfintech/data/source/tushare/stock/stockpledgedetail/tests/test_class_stockpledgedetail_all.py +112 -0
  224. xfintech/data/source/tushare/stock/stockst/__init__.py +19 -0
  225. xfintech/data/source/tushare/stock/stockst/constant.py +80 -0
  226. xfintech/data/source/tushare/stock/stockst/stockst.py +189 -0
  227. xfintech/data/source/tushare/stock/stockst/tests/__init__.py +0 -0
  228. xfintech/data/source/tushare/stock/stockst/tests/test_class_stockst_all.py +693 -0
  229. xfintech/data/source/tushare/stock/stocksuspend/__init__.py +21 -0
  230. xfintech/data/source/tushare/stock/stocksuspend/constant.py +75 -0
  231. xfintech/data/source/tushare/stock/stocksuspend/stocksuspend.py +151 -0
  232. xfintech/data/source/tushare/stock/stocksuspend/tests/__init__.py +0 -0
  233. xfintech/data/source/tushare/stock/stocksuspend/tests/test_class_stocksuspend_all.py +626 -0
  234. xfintech/data/source/tushare/stock/techindex/__init__.py +19 -0
  235. xfintech/data/source/tushare/stock/techindex/constant.py +600 -0
  236. xfintech/data/source/tushare/stock/techindex/techindex.py +314 -0
  237. xfintech/data/source/tushare/stock/techindex/tests/__init__.py +0 -0
  238. xfintech/data/source/tushare/stock/techindex/tests/test_class_techindex_all.py +576 -0
  239. xfintech/data/source/tushare/stock/tradedate/__init__.py +19 -0
  240. xfintech/data/source/tushare/stock/tradedate/constant.py +93 -0
  241. xfintech/data/source/tushare/stock/tradedate/tests/__init__.py +0 -0
  242. xfintech/data/source/tushare/stock/tradedate/tests/test_class_tradedate_all.py +947 -0
  243. xfintech/data/source/tushare/stock/tradedate/tradedate.py +234 -0
  244. xfintech/data/source/tushare/stock/weekline/__init__.py +19 -0
  245. xfintech/data/source/tushare/stock/weekline/constant.py +87 -0
  246. xfintech/data/source/tushare/stock/weekline/tests/__init__.py +0 -0
  247. xfintech/data/source/tushare/stock/weekline/tests/test_class_weekline_all.py +575 -0
  248. xfintech/data/source/tushare/stock/weekline/weekline.py +182 -0
  249. xfintech/fabric/__init__.py +18 -0
  250. xfintech/fabric/column/__init__.py +7 -0
  251. xfintech/fabric/column/info.py +202 -0
  252. xfintech/fabric/column/kind.py +102 -0
  253. xfintech/fabric/column/tests/__init__.py +0 -0
  254. xfintech/fabric/column/tests/test_class_info_all.py +207 -0
  255. xfintech/fabric/column/tests/test_class_kind_all.py +80 -0
  256. xfintech/fabric/table/__init__.py +5 -0
  257. xfintech/fabric/table/info.py +263 -0
  258. xfintech/fabric/table/tests/__init__.py +0 -0
  259. xfintech/fabric/table/tests/test_class_info_all.py +547 -0
  260. xfintech/serde/__init__.py +35 -0
  261. xfintech/serde/common/__init__.py +9 -0
  262. xfintech/serde/common/dataformat.py +78 -0
  263. xfintech/serde/common/deserialiserlike.py +38 -0
  264. xfintech/serde/common/error.py +182 -0
  265. xfintech/serde/common/serialiserlike.py +38 -0
  266. xfintech/serde/common/tests/__init__.py +1 -0
  267. xfintech/serde/common/tests/test_class_dataformat_all.py +694 -0
  268. xfintech/serde/common/tests/test_class_deserialiserlike_all.py +500 -0
  269. xfintech/serde/common/tests/test_class_errors_all.py +518 -0
  270. xfintech/serde/common/tests/test_class_serialiserlike_all.py +401 -0
  271. xfintech/serde/deserialiser/__init__.py +7 -0
  272. xfintech/serde/deserialiser/pandas.py +113 -0
  273. xfintech/serde/deserialiser/python.py +68 -0
  274. xfintech/serde/deserialiser/tests/__init__.py +1 -0
  275. xfintech/serde/deserialiser/tests/test_class_pandasdeserialiser_all.py +503 -0
  276. xfintech/serde/deserialiser/tests/test_class_pythondeserialiser_all.py +570 -0
  277. xfintech/serde/serialiser/__init__.py +7 -0
  278. xfintech/serde/serialiser/pandas.py +116 -0
  279. xfintech/serde/serialiser/python.py +71 -0
  280. xfintech/serde/serialiser/tests/__init__.py +1 -0
  281. xfintech/serde/serialiser/tests/test_class_pandasserialiser_all.py +474 -0
  282. xfintech/serde/serialiser/tests/test_class_pythonserialiser_all.py +508 -0
@@ -0,0 +1,208 @@
1
+ from __future__ import annotations
2
+
3
+ from datetime import date, datetime
4
+ from typing import Any, Dict, Optional
5
+
6
+ import pandas as pd
7
+
8
+ from xfintech.data.common.cache import Cache
9
+ from xfintech.data.common.coolant import Coolant
10
+ from xfintech.data.common.params import Params
11
+ from xfintech.data.common.retry import Retry
12
+ from xfintech.data.job import JobHouse
13
+ from xfintech.data.source.baostock.job import BaostockJob
14
+ from xfintech.data.source.baostock.session.session import Session
15
+ from xfintech.data.source.baostock.stock.tradedate.constant import (
16
+ KEY,
17
+ NAME,
18
+ PAGINATE,
19
+ SOURCE,
20
+ TARGET,
21
+ )
22
+
23
+
24
+ @JobHouse.register(KEY, alias=KEY)
25
+ class TradeDate(BaostockJob):
26
+ """
27
+ 描述:
28
+ - 获取交易日历数据
29
+ - 包括交易所的交易日期和非交易日期信息
30
+ - 支持年份查询和日期范围查询
31
+ - API文档: http://www.baostock.com/mainContent?file=StockBasicInfoAPI.md
32
+ - SCALE: CrossSection
33
+ - TYPE: Static
34
+ - PAGINATE: 10000 rows / 100 pages
35
+
36
+ 属性:
37
+ - name: str, 作业名称 'tradedate'。
38
+ - key: str, 作业键 '/baostock/tradedate'。
39
+ - session: Session, Baostock会话对象。
40
+ - source: TableInfo, 源表信息(BaoStock原始格式)。
41
+ - target: TableInfo, 目标表信息(xfintech格式)。
42
+ - params: Params, 查询参数。
43
+ - start_date: str, 可选, 开始日期(YYYY-MM-DD 或 YYYYMMDD)
44
+ - end_date: str, 可选, 结束日期(YYYY-MM-DD 或 YYYYMMDD)
45
+ - year: str | int, 可选, 年份(YYYY)
46
+ - coolant: Coolant, 请求冷却控制。
47
+ - paginate: Paginate, 分页控制(pagesize=10000, pagelimit=100)。
48
+ - retry: Retry, 重试策略。
49
+ - cache: Cache, 缓存管理。
50
+
51
+ 方法:
52
+ - run(): 执行作业,返回交易日历DataFrame。
53
+ - _run(): 内部执行逻辑,处理日期参数并调用API。
54
+ - transform(data): 转换数据格式,将源格式转为目标格式。
55
+ - list_dates(): 返回所有日期列表。
56
+ - list_datecodes(): 返回所有日期代码列表。
57
+ - list_open_dates(): 返回交易日列表。
58
+ - list_open_datecodes(): 返回交易日代码列表。
59
+ - check(session, value): 检查指定日期是否为交易日。
60
+
61
+ 例子:
62
+ ```python
63
+ from xfintech.data.source.baostock.session import Session
64
+ from xfintech.data.source.baostock.stock.tradedate import TradeDate
65
+
66
+ session = Session()
67
+
68
+ # 获取2026年交易日历
69
+ job = TradeDate(session=session, params={"year": "2026"})
70
+ df = job.run()
71
+
72
+ # 获取指定日期范围
73
+ job = TradeDate(
74
+ session=session,
75
+ params={
76
+ "start_date": "20260101",
77
+ "end_date": "20260131"
78
+ }
79
+ )
80
+ df = job.run()
81
+
82
+ # 检查是否为交易日
83
+ is_trading = TradeDate.check(session, "2026-01-10")
84
+ ```
85
+ """
86
+
87
+ @classmethod
88
+ def check(
89
+ cls,
90
+ session: Session,
91
+ value: Optional[datetime | date | str] = None,
92
+ ) -> bool:
93
+ if isinstance(value, datetime):
94
+ value = value.date()
95
+ elif isinstance(value, date):
96
+ pass
97
+ elif isinstance(value, str):
98
+ if "-" in value:
99
+ value = datetime.strptime(value, "%Y-%m-%d").date()
100
+ else:
101
+ value = datetime.strptime(value, "%Y%m%d").date()
102
+ else:
103
+ value = datetime.now().date()
104
+
105
+ datecode = value.strftime("%Y-%m-%d")
106
+ job = cls(
107
+ session=session,
108
+ params={
109
+ "start_date": datecode,
110
+ "end_date": datecode,
111
+ },
112
+ )
113
+ result = job.run()
114
+ return not result.empty
115
+
116
+ def __init__(
117
+ self,
118
+ session: Session,
119
+ params: Optional[Params | Dict[str, Any]] = None,
120
+ coolant: Optional[Coolant | Dict[str, Any]] = None,
121
+ retry: Optional[Retry | Dict[str, Any]] = None,
122
+ cache: Optional[Cache | Dict[str, Any] | bool] = None,
123
+ ) -> None:
124
+ super().__init__(
125
+ session=session,
126
+ name=NAME,
127
+ key=KEY,
128
+ source=SOURCE,
129
+ target=TARGET,
130
+ params=params,
131
+ paginate=PAGINATE,
132
+ coolant=coolant,
133
+ retry=retry,
134
+ cache=cache,
135
+ )
136
+
137
+ def _run(self) -> pd.DataFrame:
138
+ cached = self._load_cache()
139
+ if cached is not None:
140
+ return cached
141
+
142
+ payload = self.params.to_dict()
143
+ payload = self._parse_date_params(
144
+ payload,
145
+ keys=["start_date", "end_date"],
146
+ )
147
+ payload = self._parse_year_params(
148
+ payload,
149
+ key="year",
150
+ )
151
+ data = self._fetchall(
152
+ api=self.connection.query_trade_dates,
153
+ **payload,
154
+ )
155
+ result = self.transform(data)
156
+ self._save_cache(result)
157
+ return result
158
+
159
+ # Transform logic
160
+ def transform(
161
+ self,
162
+ data: pd.DataFrame,
163
+ ) -> pd.DataFrame:
164
+ cols = self.target.list_column_names()
165
+ if data is None or data.empty:
166
+ return pd.DataFrame(columns=cols)
167
+
168
+ out = data.copy()
169
+ out["dt"] = pd.to_datetime(
170
+ out["calendar_date"],
171
+ format="%Y-%m-%d",
172
+ errors="coerce",
173
+ )
174
+ out["date"] = out["dt"].dt.strftime("%Y-%m-%d")
175
+ out["datecode"] = out["dt"].dt.strftime("%Y%m%d")
176
+ out["exchange"] = "ALL"
177
+ out["previous"] = out["dt"].shift(1).dt.strftime("%Y-%m-%d")
178
+ out["is_open"] = out["is_trading_day"].astype(int).eq(1)
179
+ out["year"] = out["dt"].dt.year.fillna(0).astype(int)
180
+ out["month"] = out["dt"].dt.month.fillna(0).astype(int)
181
+ out["day"] = out["dt"].dt.day.fillna(0).astype(int)
182
+ out["week"] = out["dt"].dt.isocalendar().week.fillna(0).astype(int)
183
+ out["weekday"] = out["dt"].dt.day_name().fillna("").str[:3]
184
+ out["quarter"] = out["dt"].dt.quarter.fillna(0).astype(int)
185
+
186
+ # Clean up temporary columns
187
+ out.drop(columns=["dt"], inplace=True)
188
+ out = out[cols].drop_duplicates()
189
+ out = out.sort_values(by=["datecode"])
190
+ out = out.reset_index(drop=True)
191
+ self.markpoint("transform[OK]")
192
+ return out
193
+
194
+ def list_dates(self) -> list[str]:
195
+ df = self.run()
196
+ return sorted(df["date"].unique().tolist())
197
+
198
+ def list_datecodes(self) -> list[str]:
199
+ df = self.run()
200
+ return sorted(df["datecode"].unique().tolist())
201
+
202
+ def list_open_dates(self) -> list[str]:
203
+ df = self.run()
204
+ return sorted(df.loc[df["is_open"], "date"].unique().tolist())
205
+
206
+ def list_open_datecodes(self) -> list[str]:
207
+ df = self.run()
208
+ return sorted(df.loc[df["is_open"], "datecode"].unique().tolist())
@@ -0,0 +1,3 @@
1
+ from .zz500stock import ZZ500Stock
2
+
3
+ __all__ = ["ZZ500Stock"]
@@ -0,0 +1,55 @@
1
+ """
2
+ Constants for ZZ500Stock module.
3
+
4
+ This module defines the source and target schemas for the Baostock ZZ500 constituent stocks data.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from xfintech.data.common.paginate import Paginate
10
+ from xfintech.fabric.column.info import ColumnInfo
11
+ from xfintech.fabric.column.kind import ColumnKind
12
+ from xfintech.fabric.table.info import TableInfo
13
+
14
+ PROVIDER = "baostock"
15
+ SOURCE_NAME = "query_zz500_stocks"
16
+ URL = "http://www.baostock.com/mainContent?file=zz500Stock.md"
17
+ ARGS = {}
18
+
19
+ # Exported constants
20
+ NAME = "zz500stock"
21
+ KEY = "/baostock/zz500stock"
22
+ PAGINATE = Paginate(
23
+ pagesize=10000,
24
+ pagelimit=100,
25
+ )
26
+ SOURCE = TableInfo(
27
+ desc="中证500成分股(BaoStock格式)",
28
+ meta={
29
+ "provider": PROVIDER,
30
+ "source": SOURCE_NAME,
31
+ "url": URL,
32
+ "args": ARGS,
33
+ "type": "static",
34
+ "scale": "crosssection",
35
+ },
36
+ columns=[
37
+ ColumnInfo(name="updateDate", kind=ColumnKind.STRING, desc="更新日期"),
38
+ ColumnInfo(name="code", kind=ColumnKind.STRING, desc="证券代码"),
39
+ ColumnInfo(name="code_name", kind=ColumnKind.STRING, desc="证券名称"),
40
+ ],
41
+ )
42
+ TARGET = TableInfo(
43
+ desc="中证500成分股(xfintech格式)",
44
+ meta={
45
+ "key": KEY,
46
+ "name": NAME,
47
+ "type": "static",
48
+ "scale": "crosssection",
49
+ },
50
+ columns=[
51
+ ColumnInfo(name="update_date", kind=ColumnKind.STRING, desc="更新日期"),
52
+ ColumnInfo(name="code", kind=ColumnKind.STRING, desc="证券代码"),
53
+ ColumnInfo(name="name", kind=ColumnKind.STRING, desc="证券名称"),
54
+ ],
55
+ )
@@ -0,0 +1 @@
1
+ """Tests for zz500stock module."""
@@ -0,0 +1,421 @@
1
+ """
2
+ Comprehensive tests for ZZ500Stock class.
3
+ """
4
+
5
+ from unittest.mock import MagicMock, patch
6
+
7
+ import pandas as pd
8
+ import pytest
9
+
10
+ from xfintech.data.common.cache import Cache
11
+ from xfintech.data.common.coolant import Coolant
12
+ from xfintech.data.common.retry import Retry
13
+ from xfintech.data.source.baostock.session.session import Session
14
+ from xfintech.data.source.baostock.stock.zz500stock.constant import (
15
+ KEY,
16
+ NAME,
17
+ SOURCE,
18
+ TARGET,
19
+ )
20
+ from xfintech.data.source.baostock.stock.zz500stock.zz500stock import ZZ500Stock
21
+
22
+ # ============================================================================
23
+ # Fixtures
24
+ # ============================================================================
25
+
26
+
27
+ @pytest.fixture
28
+ def mock_session():
29
+ """Create a mock Baostock session"""
30
+ session = MagicMock(spec=Session)
31
+ session._credential = None
32
+ session.id = "test1234"
33
+ session.mode = "direct"
34
+ session.relay_url = None
35
+ session.relay_secret = None
36
+ session.connected = True
37
+
38
+ # Mock the connection object
39
+ mock_connection = MagicMock()
40
+ mock_connection.query_zz500_stocks = MagicMock()
41
+ session.connection = mock_connection
42
+
43
+ return session
44
+
45
+
46
+ @pytest.fixture
47
+ def sample_source_data():
48
+ """Create sample source data in Baostock format"""
49
+ return pd.DataFrame(
50
+ {
51
+ "updateDate": ["2018-11-26"] * 15,
52
+ "code": [
53
+ "sh.600004",
54
+ "sh.600006",
55
+ "sh.600007",
56
+ "sh.600011",
57
+ "sh.600012",
58
+ "sh.600017",
59
+ "sh.600020",
60
+ "sh.600021",
61
+ "sh.600022",
62
+ "sh.600026",
63
+ "sz.000004",
64
+ "sz.000005",
65
+ "sz.000006",
66
+ "sz.000007",
67
+ "sz.000008",
68
+ ],
69
+ "code_name": [
70
+ "白云机场",
71
+ "东风汽车",
72
+ "中国国贸",
73
+ "华能国际",
74
+ "皖通高速",
75
+ "日照港",
76
+ "中原高速",
77
+ "上海电力",
78
+ "山东钢铁",
79
+ "中远海能",
80
+ "国华网安",
81
+ "ST星源",
82
+ "深振业A",
83
+ "全新好",
84
+ "神州高铁",
85
+ ],
86
+ }
87
+ )
88
+
89
+
90
+ # ============================================================================
91
+ # Initialization Tests
92
+ # ============================================================================
93
+
94
+
95
+ def test_zz500stock_initialization_basic(mock_session):
96
+ """Test basic initialization"""
97
+ job = ZZ500Stock(session=mock_session)
98
+
99
+ assert job.name == NAME
100
+ assert job.key == KEY
101
+ assert job.source == SOURCE
102
+ assert job.target == TARGET
103
+
104
+
105
+ def test_zz500stock_initialization_with_params(mock_session):
106
+ """Test initialization with params (not used for this API)"""
107
+ params = {}
108
+ job = ZZ500Stock(session=mock_session, params=params)
109
+
110
+ # No specific params needed for ZZ500 API
111
+ assert job.params is not None
112
+
113
+
114
+ def test_zz500stock_initialization_with_all_components(mock_session):
115
+ """Test initialization with all components"""
116
+ coolant = Coolant(interval=0.2)
117
+ retry = Retry(retry=3)
118
+ cache = Cache(path="/tmp/test_cache")
119
+
120
+ job = ZZ500Stock(
121
+ session=mock_session,
122
+ coolant=coolant,
123
+ retry=retry,
124
+ cache=cache,
125
+ )
126
+
127
+ assert job.coolant.interval == 0.2
128
+ assert job.retry.retry == 3
129
+ assert job.cache is not None
130
+ assert isinstance(job.cache, Cache)
131
+
132
+
133
+ def test_zz500stock_name_and_key():
134
+ """Test name and key constants"""
135
+ assert NAME == "zz500stock"
136
+ assert KEY == "/baostock/zz500stock"
137
+
138
+
139
+ def test_zz500stock_source_schema():
140
+ """Test source schema has all required columns"""
141
+ assert SOURCE is not None
142
+ assert "中证500成分股" in SOURCE.desc
143
+
144
+ column_names = SOURCE.columns
145
+ assert "updatedate" in column_names
146
+ assert "code" in column_names
147
+ assert "code_name" in column_names
148
+
149
+
150
+ def test_zz500stock_target_schema():
151
+ """Test target schema has all required columns"""
152
+ assert TARGET is not None
153
+ assert "中证500成分股" in TARGET.desc
154
+
155
+ column_names = TARGET.columns
156
+ assert "update_date" in column_names
157
+ assert "code" in column_names
158
+ assert "name" in column_names
159
+
160
+
161
+ # ============================================================================
162
+ # Transform Tests
163
+ # ============================================================================
164
+
165
+
166
+ def test_zz500stock_transform_basic(mock_session, sample_source_data):
167
+ """Test basic data transformation"""
168
+ job = ZZ500Stock(session=mock_session)
169
+ result = job.transform(sample_source_data)
170
+
171
+ assert len(result) == 15
172
+ assert "update_date" in result.columns
173
+ assert "code" in result.columns
174
+ assert "name" in result.columns
175
+ assert result.iloc[0]["code"] == "sh.600004"
176
+ assert result.iloc[0]["name"] == "白云机场"
177
+
178
+
179
+ def test_zz500stock_transform_field_types(mock_session, sample_source_data):
180
+ """Test field type conversions"""
181
+ job = ZZ500Stock(session=mock_session)
182
+ result = job.transform(sample_source_data)
183
+
184
+ # Check string fields
185
+ assert isinstance(result.iloc[0]["update_date"], str)
186
+ assert isinstance(result.iloc[0]["code"], str)
187
+ assert isinstance(result.iloc[0]["name"], str)
188
+
189
+
190
+ def test_zz500stock_transform_field_mapping(mock_session, sample_source_data):
191
+ """Test field name mappings"""
192
+ job = ZZ500Stock(session=mock_session)
193
+ result = job.transform(sample_source_data)
194
+
195
+ # Verify field mappings
196
+ row = result[result["code"] == "sh.600004"].iloc[0]
197
+ assert row["update_date"] == "2018-11-26" # from updateDate
198
+ assert row["name"] == "白云机场" # from code_name
199
+ assert row["code"] == "sh.600004"
200
+
201
+
202
+ def test_zz500stock_transform_empty_data(mock_session):
203
+ """Test transform with empty data"""
204
+ job = ZZ500Stock(session=mock_session)
205
+
206
+ # Test with None
207
+ result = job.transform(None)
208
+ assert result.empty
209
+ assert len(result.columns) == len(TARGET.columns)
210
+
211
+ # Test with empty DataFrame
212
+ empty_df = pd.DataFrame()
213
+ result = job.transform(empty_df)
214
+ assert result.empty
215
+ assert len(result.columns) == len(TARGET.columns)
216
+
217
+
218
+ def test_zz500stock_transform_duplicate_removal(mock_session):
219
+ """Test that duplicates are removed"""
220
+ data = pd.DataFrame(
221
+ {
222
+ "updateDate": ["2018-11-26", "2018-11-26", "2018-11-26"],
223
+ "code": ["sh.600004", "sh.600004", "sh.600006"],
224
+ "code_name": ["白云机场", "白云机场", "东风汽车"],
225
+ }
226
+ )
227
+ job = ZZ500Stock(session=mock_session)
228
+ result = job.transform(data)
229
+
230
+ assert len(result) == 2 # Duplicates removed
231
+
232
+
233
+ def test_zz500stock_transform_sorting(mock_session):
234
+ """Test that results are sorted by code"""
235
+ data = pd.DataFrame(
236
+ {
237
+ "updateDate": ["2018-11-26", "2018-11-26", "2018-11-26"],
238
+ "code": ["sz.000004", "sh.600004", "sh.600006"],
239
+ "code_name": ["国华网安", "白云机场", "东风汽车"],
240
+ }
241
+ )
242
+ job = ZZ500Stock(session=mock_session)
243
+ result = job.transform(data)
244
+
245
+ # Should be sorted by code
246
+ assert result.iloc[0]["code"] == "sh.600004"
247
+ assert result.iloc[1]["code"] == "sh.600006"
248
+ assert result.iloc[2]["code"] == "sz.000004"
249
+
250
+
251
+ def test_zz500stock_transform_mixed_exchanges(mock_session, sample_source_data):
252
+ """Test transform with stocks from both Shanghai and Shenzhen exchanges"""
253
+ job = ZZ500Stock(session=mock_session)
254
+ result = job.transform(sample_source_data)
255
+
256
+ # Should have both sh. and sz. codes
257
+ sh_codes = [code for code in result["code"] if code.startswith("sh.")]
258
+ sz_codes = [code for code in result["code"] if code.startswith("sz.")]
259
+
260
+ assert len(sh_codes) > 0
261
+ assert len(sz_codes) > 0
262
+
263
+
264
+ # ============================================================================
265
+ # Run Tests
266
+ # ============================================================================
267
+
268
+
269
+ def test_zz500stock_run_basic(mock_session, sample_source_data):
270
+ """Test basic run functionality"""
271
+ job = ZZ500Stock(session=mock_session)
272
+
273
+ # Mock _fetchall to return sample data
274
+ with patch.object(job, "_fetchall", return_value=sample_source_data):
275
+ with patch.object(job, "_load_cache", return_value=None):
276
+ with patch.object(job, "_save_cache"):
277
+ result = job._run()
278
+
279
+ assert isinstance(result, pd.DataFrame)
280
+ assert len(result) == 15
281
+ assert "update_date" in result.columns
282
+ assert "code" in result.columns
283
+ assert "name" in result.columns
284
+
285
+
286
+ def test_zz500stock_run_calls_api(mock_session, sample_source_data):
287
+ """Test that run calls the correct API"""
288
+ job = ZZ500Stock(session=mock_session)
289
+
290
+ with patch.object(job, "_fetchall", return_value=sample_source_data) as mock_fetchall:
291
+ with patch.object(job, "_load_cache", return_value=None):
292
+ with patch.object(job, "_save_cache"):
293
+ job._run()
294
+
295
+ # Verify API was called
296
+ mock_fetchall.assert_called_once()
297
+ call_kwargs = mock_fetchall.call_args[1]
298
+ assert call_kwargs["api"] == job.connection.query_zz500_stocks
299
+
300
+
301
+ def test_zz500stock_run_with_cache(mock_session, sample_source_data):
302
+ """Test that cache is used when available"""
303
+ job = ZZ500Stock(session=mock_session)
304
+ cached_data = sample_source_data.copy()
305
+
306
+ with patch.object(job, "_load_cache", return_value=cached_data):
307
+ with patch.object(job, "_fetchall") as mock_fetchall:
308
+ result = job._run()
309
+
310
+ # Should return cached data without calling API
311
+ mock_fetchall.assert_not_called()
312
+ assert len(result) == 15
313
+
314
+
315
+ # ============================================================================
316
+ # List Methods Tests
317
+ # ============================================================================
318
+
319
+
320
+ def test_zz500stock_list_codes(mock_session, sample_source_data):
321
+ """Test list_codes method"""
322
+ job = ZZ500Stock(session=mock_session)
323
+
324
+ with patch.object(job, "run", return_value=job.transform(sample_source_data)):
325
+ codes = job.list_codes()
326
+
327
+ assert isinstance(codes, list)
328
+ assert len(codes) == 15
329
+ assert "sh.600004" in codes
330
+ assert "sz.000008" in codes
331
+ # Should be sorted
332
+ assert codes == sorted(codes)
333
+
334
+
335
+ def test_zz500stock_list_names(mock_session, sample_source_data):
336
+ """Test list_names method"""
337
+ job = ZZ500Stock(session=mock_session)
338
+
339
+ with patch.object(job, "run", return_value=job.transform(sample_source_data)):
340
+ names = job.list_names()
341
+
342
+ assert isinstance(names, list)
343
+ assert len(names) == 15
344
+ assert "白云机场" in names
345
+ assert "神州高铁" in names
346
+ # Should be sorted
347
+ assert names == sorted(names)
348
+
349
+
350
+ def test_zz500stock_list_codes_with_duplicates(mock_session):
351
+ """Test list_codes with duplicate codes"""
352
+ data = pd.DataFrame(
353
+ {
354
+ "updateDate": ["2018-11-26", "2018-11-26", "2018-11-26"],
355
+ "code": ["sh.600004", "sh.600004", "sh.600006"],
356
+ "code_name": ["白云机场", "白云机场", "东风汽车"],
357
+ }
358
+ )
359
+ job = ZZ500Stock(session=mock_session)
360
+ transformed = job.transform(data)
361
+
362
+ with patch.object(job, "run", return_value=transformed):
363
+ codes = job.list_codes()
364
+
365
+ # Should return unique codes only
366
+ assert len(codes) == 2
367
+ assert "sh.600004" in codes
368
+ assert "sh.600006" in codes
369
+
370
+
371
+ # ============================================================================
372
+ # Integration Tests
373
+ # ============================================================================
374
+
375
+
376
+ def test_zz500stock_full_workflow(mock_session, sample_source_data):
377
+ """Test full workflow from initialization to result"""
378
+ job = ZZ500Stock(session=mock_session)
379
+
380
+ with patch.object(job, "_fetchall", return_value=sample_source_data):
381
+ with patch.object(job, "_load_cache", return_value=None):
382
+ with patch.object(job, "_save_cache"):
383
+ result = job._run()
384
+
385
+ # Verify result structure
386
+ assert isinstance(result, pd.DataFrame)
387
+ assert len(result) == 15
388
+ assert "update_date" in result.columns
389
+ assert "code" in result.columns
390
+ assert "name" in result.columns
391
+
392
+ # Verify data types
393
+ assert isinstance(result.iloc[0]["update_date"], str)
394
+ assert isinstance(result.iloc[0]["code"], str)
395
+ assert isinstance(result.iloc[0]["name"], str)
396
+
397
+ # Verify sorting
398
+ codes = result["code"].tolist()
399
+ assert codes == sorted(codes)
400
+
401
+
402
+ def test_zz500stock_same_update_date(mock_session, sample_source_data):
403
+ """Test that all stocks have the same update date"""
404
+ job = ZZ500Stock(session=mock_session)
405
+ result = job.transform(sample_source_data)
406
+
407
+ # All stocks should have the same update date
408
+ unique_dates = result["update_date"].unique()
409
+ assert len(unique_dates) == 1
410
+ assert unique_dates[0] == "2018-11-26"
411
+
412
+
413
+ def test_zz500stock_code_format(mock_session, sample_source_data):
414
+ """Test that codes follow sh./sz.XXXXXX format"""
415
+ job = ZZ500Stock(session=mock_session)
416
+ result = job.transform(sample_source_data)
417
+
418
+ # All codes should start with "sh." or "sz."
419
+ for code in result["code"]:
420
+ assert code.startswith("sh.") or code.startswith("sz.")
421
+ assert len(code) == 9 # sh./sz. + 6 digits