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,413 @@
1
+ from unittest.mock import MagicMock, patch
2
+
3
+ import pandas as pd
4
+ import pytest
5
+
6
+ from xfintech.data.common.cache import Cache
7
+ from xfintech.data.common.coolant import Coolant
8
+ from xfintech.data.common.retry import Retry
9
+ from xfintech.data.source.baostock.session.session import Session
10
+ from xfintech.data.source.baostock.stock.hs300stock.constant import (
11
+ KEY,
12
+ NAME,
13
+ SOURCE,
14
+ TARGET,
15
+ )
16
+ from xfintech.data.source.baostock.stock.hs300stock.hs300stock import HS300Stock
17
+
18
+
19
+ @pytest.fixture
20
+ def mock_session():
21
+ """Create a mock Baostock session"""
22
+ session = MagicMock(spec=Session)
23
+ session._credential = None
24
+ session.id = "test1234"
25
+ session.mode = "direct"
26
+ session.relay_url = None
27
+ session.relay_secret = None
28
+ session.connected = True
29
+
30
+ # Mock the connection object
31
+ mock_connection = MagicMock()
32
+ mock_connection.query_hs300_stocks = MagicMock()
33
+ session.connection = mock_connection
34
+
35
+ return session
36
+
37
+
38
+ @pytest.fixture
39
+ def sample_source_data():
40
+ """Create sample source data in Baostock format"""
41
+ return pd.DataFrame(
42
+ {
43
+ "updateDate": ["2018-11-26"] * 15,
44
+ "code": [
45
+ "sh.600000",
46
+ "sh.600008",
47
+ "sh.600009",
48
+ "sh.600010",
49
+ "sh.600015",
50
+ "sh.600016",
51
+ "sh.600018",
52
+ "sh.600019",
53
+ "sh.600028",
54
+ "sh.600029",
55
+ "sz.000001",
56
+ "sz.000002",
57
+ "sz.000063",
58
+ "sz.000069",
59
+ "sz.000100",
60
+ ],
61
+ "code_name": [
62
+ "浦发银行",
63
+ "首创股份",
64
+ "上海机场",
65
+ "包钢股份",
66
+ "华夏银行",
67
+ "民生银行",
68
+ "上港集团",
69
+ "宝钢股份",
70
+ "中国石化",
71
+ "南方航空",
72
+ "平安银行",
73
+ "万科A",
74
+ "中兴通讯",
75
+ "华侨城A",
76
+ "TCL科技",
77
+ ],
78
+ }
79
+ )
80
+
81
+
82
+ # ============================================================================
83
+ # Initialization Tests
84
+ # ============================================================================
85
+
86
+
87
+ def test_hs300stock_initialization_basic(mock_session):
88
+ """Test basic initialization"""
89
+ job = HS300Stock(session=mock_session)
90
+
91
+ assert job.name == NAME
92
+ assert job.key == KEY
93
+ assert job.source == SOURCE
94
+ assert job.target == TARGET
95
+
96
+
97
+ def test_hs300stock_initialization_with_params(mock_session):
98
+ """Test initialization with params (not used for this API)"""
99
+ params = {}
100
+ job = HS300Stock(session=mock_session, params=params)
101
+
102
+ # No specific params needed for HS300 API
103
+ assert job.params is not None
104
+
105
+
106
+ def test_hs300stock_initialization_with_all_components(mock_session):
107
+ """Test initialization with all components"""
108
+ coolant = Coolant(interval=0.2)
109
+ retry = Retry(retry=3)
110
+ cache = Cache(path="/tmp/test_cache")
111
+
112
+ job = HS300Stock(
113
+ session=mock_session,
114
+ coolant=coolant,
115
+ retry=retry,
116
+ cache=cache,
117
+ )
118
+
119
+ assert job.coolant.interval == 0.2
120
+ assert job.retry.retry == 3
121
+ assert job.cache is not None
122
+ assert isinstance(job.cache, Cache)
123
+
124
+
125
+ def test_hs300stock_name_and_key():
126
+ """Test name and key constants"""
127
+ assert NAME == "hs300stock"
128
+ assert KEY == "/baostock/hs300stock"
129
+
130
+
131
+ def test_hs300stock_source_schema():
132
+ """Test source schema has all required columns"""
133
+ assert SOURCE is not None
134
+ assert "沪深300成分股" in SOURCE.desc
135
+
136
+ column_names = SOURCE.columns
137
+ assert "updatedate" in column_names
138
+ assert "code" in column_names
139
+ assert "code_name" in column_names
140
+
141
+
142
+ def test_hs300stock_target_schema():
143
+ """Test target schema has all required columns"""
144
+ assert TARGET is not None
145
+ assert "沪深300成分股" in TARGET.desc
146
+
147
+ column_names = TARGET.columns
148
+ assert "update_date" in column_names
149
+ assert "code" in column_names
150
+ assert "name" in column_names
151
+
152
+
153
+ # ============================================================================
154
+ # Transform Tests
155
+ # ============================================================================
156
+
157
+
158
+ def test_hs300stock_transform_basic(mock_session, sample_source_data):
159
+ """Test basic data transformation"""
160
+ job = HS300Stock(session=mock_session)
161
+ result = job.transform(sample_source_data)
162
+
163
+ assert len(result) == 15
164
+ assert "update_date" in result.columns
165
+ assert "code" in result.columns
166
+ assert "name" in result.columns
167
+ assert result.iloc[0]["code"] == "sh.600000"
168
+ assert result.iloc[0]["name"] == "浦发银行"
169
+
170
+
171
+ def test_hs300stock_transform_field_types(mock_session, sample_source_data):
172
+ """Test field type conversions"""
173
+ job = HS300Stock(session=mock_session)
174
+ result = job.transform(sample_source_data)
175
+
176
+ # Check string fields
177
+ assert isinstance(result.iloc[0]["update_date"], str)
178
+ assert isinstance(result.iloc[0]["code"], str)
179
+ assert isinstance(result.iloc[0]["name"], str)
180
+
181
+
182
+ def test_hs300stock_transform_field_mapping(mock_session, sample_source_data):
183
+ """Test field name mappings"""
184
+ job = HS300Stock(session=mock_session)
185
+ result = job.transform(sample_source_data)
186
+
187
+ # Verify field mappings
188
+ row = result[result["code"] == "sh.600000"].iloc[0]
189
+ assert row["update_date"] == "2018-11-26" # from updateDate
190
+ assert row["name"] == "浦发银行" # from code_name
191
+ assert row["code"] == "sh.600000"
192
+
193
+
194
+ def test_hs300stock_transform_empty_data(mock_session):
195
+ """Test transform with empty data"""
196
+ job = HS300Stock(session=mock_session)
197
+
198
+ # Test with None
199
+ result = job.transform(None)
200
+ assert result.empty
201
+ assert len(result.columns) == len(TARGET.columns)
202
+
203
+ # Test with empty DataFrame
204
+ empty_df = pd.DataFrame()
205
+ result = job.transform(empty_df)
206
+ assert result.empty
207
+ assert len(result.columns) == len(TARGET.columns)
208
+
209
+
210
+ def test_hs300stock_transform_duplicate_removal(mock_session):
211
+ """Test that duplicates are removed"""
212
+ data = pd.DataFrame(
213
+ {
214
+ "updateDate": ["2018-11-26", "2018-11-26", "2018-11-26"],
215
+ "code": ["sh.600000", "sh.600000", "sh.600008"],
216
+ "code_name": ["浦发银行", "浦发银行", "首创股份"],
217
+ }
218
+ )
219
+ job = HS300Stock(session=mock_session)
220
+ result = job.transform(data)
221
+
222
+ assert len(result) == 2 # Duplicates removed
223
+
224
+
225
+ def test_hs300stock_transform_sorting(mock_session):
226
+ """Test that results are sorted by code"""
227
+ data = pd.DataFrame(
228
+ {
229
+ "updateDate": ["2018-11-26", "2018-11-26", "2018-11-26"],
230
+ "code": ["sz.000001", "sh.600000", "sh.600008"],
231
+ "code_name": ["平安银行", "浦发银行", "首创股份"],
232
+ }
233
+ )
234
+ job = HS300Stock(session=mock_session)
235
+ result = job.transform(data)
236
+
237
+ # Should be sorted by code
238
+ assert result.iloc[0]["code"] == "sh.600000"
239
+ assert result.iloc[1]["code"] == "sh.600008"
240
+ assert result.iloc[2]["code"] == "sz.000001"
241
+
242
+
243
+ def test_hs300stock_transform_mixed_exchanges(mock_session, sample_source_data):
244
+ """Test transform with stocks from both Shanghai and Shenzhen exchanges"""
245
+ job = HS300Stock(session=mock_session)
246
+ result = job.transform(sample_source_data)
247
+
248
+ # Should have both sh. and sz. codes
249
+ sh_codes = [code for code in result["code"] if code.startswith("sh.")]
250
+ sz_codes = [code for code in result["code"] if code.startswith("sz.")]
251
+
252
+ assert len(sh_codes) > 0
253
+ assert len(sz_codes) > 0
254
+
255
+
256
+ # ============================================================================
257
+ # Run Tests
258
+ # ============================================================================
259
+
260
+
261
+ def test_hs300stock_run_basic(mock_session, sample_source_data):
262
+ """Test basic run functionality"""
263
+ job = HS300Stock(session=mock_session)
264
+
265
+ # Mock _fetchall to return sample data
266
+ with patch.object(job, "_fetchall", return_value=sample_source_data):
267
+ with patch.object(job, "_load_cache", return_value=None):
268
+ with patch.object(job, "_save_cache"):
269
+ result = job._run()
270
+
271
+ assert isinstance(result, pd.DataFrame)
272
+ assert len(result) == 15
273
+ assert "update_date" in result.columns
274
+ assert "code" in result.columns
275
+ assert "name" in result.columns
276
+
277
+
278
+ def test_hs300stock_run_calls_api(mock_session, sample_source_data):
279
+ """Test that run calls the correct API"""
280
+ job = HS300Stock(session=mock_session)
281
+
282
+ with patch.object(job, "_fetchall", return_value=sample_source_data) as mock_fetchall:
283
+ with patch.object(job, "_load_cache", return_value=None):
284
+ with patch.object(job, "_save_cache"):
285
+ job._run()
286
+
287
+ # Verify API was called
288
+ mock_fetchall.assert_called_once()
289
+ call_kwargs = mock_fetchall.call_args[1]
290
+ assert call_kwargs["api"] == job.connection.query_hs300_stocks
291
+
292
+
293
+ def test_hs300stock_run_with_cache(mock_session, sample_source_data):
294
+ """Test that cache is used when available"""
295
+ job = HS300Stock(session=mock_session)
296
+ cached_data = sample_source_data.copy()
297
+
298
+ with patch.object(job, "_load_cache", return_value=cached_data):
299
+ with patch.object(job, "_fetchall") as mock_fetchall:
300
+ result = job._run()
301
+
302
+ # Should return cached data without calling API
303
+ mock_fetchall.assert_not_called()
304
+ assert len(result) == 15
305
+
306
+
307
+ # ============================================================================
308
+ # List Methods Tests
309
+ # ============================================================================
310
+
311
+
312
+ def test_hs300stock_list_codes(mock_session, sample_source_data):
313
+ """Test list_codes method"""
314
+ job = HS300Stock(session=mock_session)
315
+
316
+ with patch.object(job, "run", return_value=job.transform(sample_source_data)):
317
+ codes = job.list_codes()
318
+
319
+ assert isinstance(codes, list)
320
+ assert len(codes) == 15
321
+ assert "sh.600000" in codes
322
+ assert "sz.000100" in codes
323
+ # Should be sorted
324
+ assert codes == sorted(codes)
325
+
326
+
327
+ def test_hs300stock_list_names(mock_session, sample_source_data):
328
+ """Test list_names method"""
329
+ job = HS300Stock(session=mock_session)
330
+
331
+ with patch.object(job, "run", return_value=job.transform(sample_source_data)):
332
+ names = job.list_names()
333
+
334
+ assert isinstance(names, list)
335
+ assert len(names) == 15
336
+ assert "浦发银行" in names
337
+ assert "TCL科技" in names
338
+ # Should be sorted
339
+ assert names == sorted(names)
340
+
341
+
342
+ def test_hs300stock_list_codes_with_duplicates(mock_session):
343
+ """Test list_codes with duplicate codes"""
344
+ data = pd.DataFrame(
345
+ {
346
+ "updateDate": ["2018-11-26", "2018-11-26", "2018-11-26"],
347
+ "code": ["sh.600000", "sh.600000", "sh.600008"],
348
+ "code_name": ["浦发银行", "浦发银行", "首创股份"],
349
+ }
350
+ )
351
+ job = HS300Stock(session=mock_session)
352
+ transformed = job.transform(data)
353
+
354
+ with patch.object(job, "run", return_value=transformed):
355
+ codes = job.list_codes()
356
+
357
+ # Should return unique codes only
358
+ assert len(codes) == 2
359
+ assert "sh.600000" in codes
360
+ assert "sh.600008" in codes
361
+
362
+
363
+ # ============================================================================
364
+ # Integration Tests
365
+ # ============================================================================
366
+
367
+
368
+ def test_hs300stock_full_workflow(mock_session, sample_source_data):
369
+ """Test full workflow from initialization to result"""
370
+ job = HS300Stock(session=mock_session)
371
+
372
+ with patch.object(job, "_fetchall", return_value=sample_source_data):
373
+ with patch.object(job, "_load_cache", return_value=None):
374
+ with patch.object(job, "_save_cache"):
375
+ result = job._run()
376
+
377
+ # Verify result structure
378
+ assert isinstance(result, pd.DataFrame)
379
+ assert len(result) == 15
380
+ assert "update_date" in result.columns
381
+ assert "code" in result.columns
382
+ assert "name" in result.columns
383
+
384
+ # Verify data types
385
+ assert isinstance(result.iloc[0]["update_date"], str)
386
+ assert isinstance(result.iloc[0]["code"], str)
387
+ assert isinstance(result.iloc[0]["name"], str)
388
+
389
+ # Verify sorting
390
+ codes = result["code"].tolist()
391
+ assert codes == sorted(codes)
392
+
393
+
394
+ def test_hs300stock_same_update_date(mock_session, sample_source_data):
395
+ """Test that all stocks have the same update date"""
396
+ job = HS300Stock(session=mock_session)
397
+ result = job.transform(sample_source_data)
398
+
399
+ # All stocks should have the same update date
400
+ unique_dates = result["update_date"].unique()
401
+ assert len(unique_dates) == 1
402
+ assert unique_dates[0] == "2018-11-26"
403
+
404
+
405
+ def test_hs300stock_code_format(mock_session, sample_source_data):
406
+ """Test that codes follow sh./sz.XXXXXX format"""
407
+ job = HS300Stock(session=mock_session)
408
+ result = job.transform(sample_source_data)
409
+
410
+ # All codes should start with "sh." or "sz."
411
+ for code in result["code"]:
412
+ assert code.startswith("sh.") or code.startswith("sz.")
413
+ assert len(code) == 9 # sh./sz. + 6 digits
@@ -0,0 +1,19 @@
1
+ from __future__ import annotations
2
+
3
+ from xfintech.data.source.baostock.stock.minuteline.constant import (
4
+ KEY,
5
+ NAME,
6
+ PAGINATE,
7
+ SOURCE,
8
+ TARGET,
9
+ )
10
+ from xfintech.data.source.baostock.stock.minuteline.minuteline import Minuteline
11
+
12
+ __all__ = [
13
+ "Minuteline",
14
+ "KEY",
15
+ "NAME",
16
+ "PAGINATE",
17
+ "SOURCE",
18
+ "TARGET",
19
+ ]
@@ -0,0 +1,89 @@
1
+ from __future__ import annotations
2
+
3
+ from xfintech.data.common.paginate import Paginate
4
+ from xfintech.fabric.column.info import ColumnInfo
5
+ from xfintech.fabric.column.kind import ColumnKind
6
+ from xfintech.fabric.table.info import TableInfo
7
+
8
+ PROVIDER = "baostock"
9
+ SOURCE_NAME = "query_history_k_data_plus"
10
+ URL = "http://www.baostock.com/mainContent?file=stockKData.md"
11
+ ARGS = {
12
+ "code": {
13
+ "type": ColumnKind.STRING,
14
+ "required": "N",
15
+ "desc": "股票代码(支持多个股票同时提取,逗号分隔)",
16
+ },
17
+ "start": {
18
+ "type": ColumnKind.STRING,
19
+ "required": "N",
20
+ "desc": "开始日期(YYYYMMDD)",
21
+ },
22
+ "end": {
23
+ "type": ColumnKind.STRING,
24
+ "required": "N",
25
+ "desc": "结束日期(YYYYMMDD)",
26
+ },
27
+ "frequency": {
28
+ "type": ColumnKind.STRING,
29
+ "required": "N",
30
+ "desc": "5=5分钟、15=15分钟、30=30分钟、60=60分钟",
31
+ },
32
+ "adjustflag": {
33
+ "type": ColumnKind.STRING,
34
+ "required": "N",
35
+ "desc": "复权类型,默认不复权:3;1:后复权;2:前复权",
36
+ },
37
+ }
38
+
39
+ # Exported constants
40
+ NAME = "minuteline"
41
+ KEY = "/baostock/minuteline"
42
+ PAGINATE = Paginate(
43
+ pagesize=10000,
44
+ pagelimit=100,
45
+ )
46
+ SOURCE = TableInfo(
47
+ desc="A股分钟线行情数据(BaoStock格式)",
48
+ meta={
49
+ "provider": PROVIDER,
50
+ "source": SOURCE_NAME,
51
+ "url": URL,
52
+ "args": ARGS,
53
+ "type": "partitioned",
54
+ "scale": "individual",
55
+ },
56
+ columns=[
57
+ ColumnInfo(name="code", kind=ColumnKind.STRING, desc="证券代码 格式:sh.600000。sh:上海,sz:深圳"),
58
+ ColumnInfo(name="date", kind=ColumnKind.STRING, desc="交易日期: 格式:YYYY-MM-DD"),
59
+ ColumnInfo(name="time", kind=ColumnKind.STRING, desc="交易时间, 格式:YYYYMMDDHHMMSSsss"),
60
+ ColumnInfo(name="open", kind=ColumnKind.FLOAT, desc="开盘价"),
61
+ ColumnInfo(name="high", kind=ColumnKind.FLOAT, desc="最高价"),
62
+ ColumnInfo(name="low", kind=ColumnKind.FLOAT, desc="最低价"),
63
+ ColumnInfo(name="close", kind=ColumnKind.FLOAT, desc="收盘价"),
64
+ ColumnInfo(name="volume", kind=ColumnKind.FLOAT, desc="成交量(股)"),
65
+ ColumnInfo(name="amount", kind=ColumnKind.FLOAT, desc="成交额(元)"),
66
+ ColumnInfo(name="adjustflag", kind=ColumnKind.STRING, desc="复权状态: 不复权、前复权、后复权"),
67
+ ],
68
+ )
69
+ TARGET = TableInfo(
70
+ desc="A股分钟线行情数据(xfintech格式)",
71
+ meta={
72
+ "key": KEY,
73
+ "name": NAME,
74
+ "type": "partitioned",
75
+ "scale": "individual",
76
+ },
77
+ columns=[
78
+ ColumnInfo(name="code", kind=ColumnKind.STRING, desc="股票代码"),
79
+ ColumnInfo(name="date", kind=ColumnKind.STRING, desc="交易日期: 格式:YYYY-MM-DD"),
80
+ ColumnInfo(name="time", kind=ColumnKind.STRING, desc="交易时间代码: 格式:YYYYMMDDHHMMSSsss"),
81
+ ColumnInfo(name="open", kind=ColumnKind.FLOAT, desc="开盘价"),
82
+ ColumnInfo(name="high", kind=ColumnKind.FLOAT, desc="最高价"),
83
+ ColumnInfo(name="low", kind=ColumnKind.FLOAT, desc="最低价"),
84
+ ColumnInfo(name="close", kind=ColumnKind.FLOAT, desc="收盘价"),
85
+ ColumnInfo(name="volume", kind=ColumnKind.FLOAT, desc="成交量(股)"),
86
+ ColumnInfo(name="amount", kind=ColumnKind.FLOAT, desc="成交额(元)"),
87
+ ColumnInfo(name="adjustflag", kind=ColumnKind.STRING, desc="复权状态: 不复权、前复权、后复权"),
88
+ ],
89
+ )
@@ -0,0 +1,163 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Dict, Optional
4
+
5
+ import pandas as pd
6
+
7
+ from xfintech.data.common.cache import Cache
8
+ from xfintech.data.common.coolant import Coolant
9
+ from xfintech.data.common.params import Params
10
+ from xfintech.data.common.retry import Retry
11
+ from xfintech.data.job import JobHouse
12
+ from xfintech.data.source.baostock.job import BaostockJob
13
+ from xfintech.data.source.baostock.session.session import Session
14
+ from xfintech.data.source.baostock.stock.minuteline.constant import (
15
+ KEY,
16
+ NAME,
17
+ PAGINATE,
18
+ SOURCE,
19
+ TARGET,
20
+ )
21
+
22
+
23
+ @JobHouse.register(KEY, alias=KEY)
24
+ class Minuteline(BaostockJob):
25
+ """
26
+ 描述:
27
+ - 获取A股分钟线行情数据
28
+ - 支持5分钟、15分钟、30分钟、60分钟K线数据
29
+ - 支持前复权、后复权、不复权三种复权方式
30
+ - API文档: http://www.baostock.com/mainContent?file=stockKData.md
31
+ - SCALE: Individual
32
+ - TYPE: Partitioned
33
+ - PAGINATE: 10000 rows / 100 pages
34
+
35
+ 属性:
36
+ - name: str, 作业名称 'minuteline'。
37
+ - key: str, 作业键 '/baostock/minuteline'。
38
+ - session: Session, Baostock会话对象。
39
+ - source: TableInfo, 源表信息(Baostock原始格式)。
40
+ - target: TableInfo, 目标表信息(转换后格式)。
41
+ - params: Params, 查询参数。
42
+ - code: str, 必需, 股票代码(如 "sh.600000" 或 "sz.000001")
43
+ - start_date: str, 可选, 开始日期(YYYY-MM-DD 或 YYYYMMDD)
44
+ - end_date: str, 可选, 结束日期(YYYY-MM-DD 或 YYYYMMDD)
45
+ - frequency: str, 可选, 频率(5/15/30/60),默认5
46
+ - adjustflag: str, 可选, 复权类型(1=后复权/2=前复权/3=不复权),默认3
47
+ - coolant: Coolant, 请求冷却控制。
48
+ - paginate: Paginate, 分页控制(pagesize=10000, pagelimit=100)。
49
+ - retry: Retry, 重试策略。
50
+ - cache: Cache, 缓存管理。
51
+
52
+ 方法:
53
+ - run(): 执行作业,返回分钟线行情DataFrame。
54
+ - _run(): 内部执行逻辑,处理日期参数并调用API。
55
+ - transform(data): 转换数据格式,将源格式转为目标格式。
56
+
57
+ 例子:
58
+ ```python
59
+ from xfintech.data.source.baostock.session import Session
60
+ from xfintech.data.source.baostock.stock.minuteline import Minuteline
61
+
62
+ session = Session()
63
+ job = Minuteline(
64
+ session=session,
65
+ params={
66
+ "code": "sh.600000",
67
+ "start_date": "20260101",
68
+ "end_date": "20260131",
69
+ "frequency": "5",
70
+ "adjustflag": "3"
71
+ }
72
+ )
73
+ df = job.run()
74
+ ```
75
+ """
76
+
77
+ def __init__(
78
+ self,
79
+ session: Session,
80
+ params: Optional[Params | Dict[str, Any]] = None,
81
+ coolant: Optional[Coolant | Dict[str, Any]] = None,
82
+ retry: Optional[Retry | Dict[str, Any]] = None,
83
+ cache: Optional[Cache | Dict[str, str] | bool] = None,
84
+ ) -> None:
85
+ super().__init__(
86
+ name=NAME,
87
+ key=KEY,
88
+ session=session,
89
+ source=SOURCE,
90
+ target=TARGET,
91
+ params=params,
92
+ coolant=coolant,
93
+ paginate=PAGINATE,
94
+ retry=retry,
95
+ cache=cache,
96
+ )
97
+
98
+ def _run(self) -> pd.DataFrame:
99
+ cached = self._load_cache()
100
+ if cached is not None:
101
+ return cached
102
+
103
+ payload = self.params.to_dict()
104
+ payload = self._parse_string_params(
105
+ payload,
106
+ keys=[
107
+ "code",
108
+ "frequency",
109
+ "adjustflag",
110
+ ],
111
+ )
112
+ payload = self._parse_date_params(
113
+ payload,
114
+ keys=["start_date", "end_date"],
115
+ )
116
+ fields = self.source.list_column_names()
117
+ payload["fields"] = ",".join(fields)
118
+
119
+ # Fetch data from API
120
+ data = self._fetchall(
121
+ api=self.connection.query_history_k_data_plus,
122
+ **payload,
123
+ )
124
+ result = self.transform(data)
125
+ self._save_cache(result)
126
+ return result
127
+
128
+ # Transform logic
129
+ def transform(
130
+ self,
131
+ data: pd.DataFrame,
132
+ ) -> pd.DataFrame:
133
+ cols = self.target.list_column_names()
134
+ if data is None or data.empty:
135
+ return pd.DataFrame(columns=cols)
136
+
137
+ out = data.copy()
138
+ out["code"] = out["code"].astype(str)
139
+ out["date"] = out["date"].astype(str)
140
+ out["time"] = out["time"].astype(str)
141
+ out["adjustflag"] = out["adjustflag"].astype(str)
142
+
143
+ # Convert numeric fields
144
+ numeric_fields = [
145
+ "open",
146
+ "high",
147
+ "low",
148
+ "close",
149
+ "volume",
150
+ "amount",
151
+ ]
152
+ for field in numeric_fields:
153
+ out[field] = pd.to_numeric(
154
+ out[field],
155
+ errors="coerce",
156
+ )
157
+
158
+ # Finalize output
159
+ out = out[cols].drop_duplicates()
160
+ out = out.sort_values(by=["code", "date", "time"])
161
+ out = out.reset_index(drop=True)
162
+ self.markpoint("transform[OK]")
163
+ return out