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,947 @@
1
+ from datetime import date, datetime
2
+ from unittest.mock import MagicMock, patch
3
+
4
+ import pandas as pd
5
+ import pytest
6
+
7
+ from xfintech.data.common.cache import Cache
8
+ from xfintech.data.common.coolant import Coolant
9
+ from xfintech.data.common.paginate import Paginate
10
+ from xfintech.data.common.params import Params
11
+ from xfintech.data.common.retry import Retry
12
+ from xfintech.data.source.tushare.session.session import Session
13
+ from xfintech.data.source.tushare.stock.tradedate.constant import (
14
+ EXCHANGES,
15
+ KEY,
16
+ NAME,
17
+ PAGINATE,
18
+ SOURCE,
19
+ TARGET,
20
+ )
21
+ from xfintech.data.source.tushare.stock.tradedate.tradedate import TradeDate
22
+
23
+ # ============================================================================
24
+ # Fixtures
25
+ # ============================================================================
26
+
27
+
28
+ @pytest.fixture
29
+ def mock_session():
30
+ """Create a mock Tushare session"""
31
+ session = MagicMock(spec=Session)
32
+ session._credential = "test_token"
33
+ session.id = "test1234"
34
+ session.mode = "direct"
35
+ session.relay_url = None
36
+ session.relay_secret = None
37
+ session.connected = True
38
+
39
+ # Mock the connection object
40
+ mock_connection = MagicMock()
41
+ mock_connection.trade_cal = MagicMock()
42
+ session.connection = mock_connection
43
+
44
+ return session
45
+
46
+
47
+ @pytest.fixture
48
+ def sample_source_data():
49
+ """Create sample source data in Tushare format"""
50
+ return pd.DataFrame(
51
+ {
52
+ "exchange": ["SSE", "SSE", "SSE", "SSE"],
53
+ "cal_date": ["20230101", "20230102", "20230103", "20230104"],
54
+ "is_open": [0, 1, 1, 0],
55
+ "pretrade_date": ["20221230", "20221230", "20230102", "20230103"],
56
+ }
57
+ )
58
+
59
+
60
+ @pytest.fixture
61
+ def expected_transformed_data():
62
+ """Create expected transformed data"""
63
+ return pd.DataFrame(
64
+ {
65
+ "datecode": ["20230101", "20230102", "20230103", "20230104"],
66
+ "date": ["2023-01-01", "2023-01-02", "2023-01-03", "2023-01-04"],
67
+ "exchange": ["ALL", "ALL", "ALL", "ALL"],
68
+ "previous": ["2022-12-30", "2022-12-30", "2023-01-02", "2023-01-03"],
69
+ "is_open": [False, True, True, False],
70
+ "year": [2023, 2023, 2023, 2023],
71
+ "month": [1, 1, 1, 1],
72
+ "day": [1, 2, 3, 4],
73
+ "week": [52, 1, 1, 1],
74
+ "weekday": ["Sun", "Mon", "Tue", "Wed"],
75
+ "quarter": [1, 1, 1, 1],
76
+ }
77
+ )
78
+
79
+
80
+ # ============================================================================
81
+ # Initialization Tests
82
+ # ============================================================================
83
+
84
+
85
+ def test_tradedate_init_basic(mock_session):
86
+ """Test TradeDate initialization with minimal parameters"""
87
+ td = TradeDate(session=mock_session)
88
+
89
+ assert td.name == NAME
90
+ assert td.key == KEY
91
+ assert td.source == SOURCE
92
+ assert td.target == TARGET
93
+ assert isinstance(td.params, Params)
94
+ assert isinstance(td.coolant, Coolant)
95
+ assert isinstance(td.paginate, Paginate)
96
+ assert isinstance(td.retry, Retry)
97
+ assert td.paginate.pagesize == PAGINATE.pagesize
98
+ assert td.paginate.pagelimit == PAGINATE.pagelimit
99
+
100
+
101
+ def test_tradedate_init_with_params_dict(mock_session):
102
+ """Test TradeDate initialization with params as dict"""
103
+ params = {"start_date": "20230101", "end_date": "20231231"}
104
+ td = TradeDate(session=mock_session, params=params)
105
+
106
+ assert td.params.start_date == "20230101"
107
+ assert td.params.end_date == "20231231"
108
+
109
+
110
+ def test_tradedate_init_with_params_object(mock_session):
111
+ """Test TradeDate initialization with Params object"""
112
+ params = Params(year="2023")
113
+ td = TradeDate(session=mock_session, params=params)
114
+
115
+ assert td.params.year == "2023"
116
+
117
+
118
+ def test_tradedate_init_with_year_param(mock_session):
119
+ """Test TradeDate initialization with year parameter"""
120
+ td = TradeDate(session=mock_session, params={"year": "2023"})
121
+
122
+ assert td.params.year == "2023"
123
+
124
+
125
+ def test_tradedate_init_with_exchange_param(mock_session):
126
+ """Test TradeDate initialization with exchange parameter"""
127
+ td = TradeDate(session=mock_session, params={"exchange": "SSE"})
128
+
129
+ assert td.params.exchange == "SSE"
130
+
131
+
132
+ def test_tradedate_init_with_is_open_param(mock_session):
133
+ """Test TradeDate initialization with is_open parameter"""
134
+ td = TradeDate(session=mock_session, params={"is_open": "1"})
135
+
136
+ assert td.params.is_open == "1"
137
+
138
+
139
+ def test_tradedate_init_with_cache_bool_true(mock_session):
140
+ """Test TradeDate initialization with cache=True"""
141
+ td = TradeDate(session=mock_session, cache=True)
142
+
143
+ assert td.cache is not None
144
+ assert isinstance(td.cache, Cache)
145
+
146
+
147
+ def test_tradedate_init_with_cache_bool_false(mock_session):
148
+ """Test TradeDate initialization with cache=False"""
149
+ td = TradeDate(session=mock_session, cache=False)
150
+
151
+ assert td.cache is None
152
+
153
+
154
+ def test_tradedate_init_with_cache_dict(mock_session):
155
+ """Test TradeDate initialization with cache as dict"""
156
+ cache_config = {"directory": "/tmp/cache"}
157
+ td = TradeDate(session=mock_session, cache=cache_config)
158
+
159
+ assert td.cache is not None
160
+ assert isinstance(td.cache, Cache)
161
+
162
+
163
+ def test_tradedate_init_with_all_params(mock_session):
164
+ """Test TradeDate initialization with all parameters"""
165
+ td = TradeDate(
166
+ session=mock_session,
167
+ params={"year": "2023", "exchange": "SSE"},
168
+ coolant={"interval": 1.0},
169
+ retry={"max_retries": 3},
170
+ cache=True,
171
+ )
172
+
173
+ assert td.name == NAME
174
+ assert td.params.year == "2023"
175
+ assert td.params.exchange == "SSE"
176
+ assert td.cache is not None
177
+ assert td.paginate.pagesize == PAGINATE.pagesize
178
+ assert td.paginate.pagelimit == PAGINATE.pagelimit
179
+
180
+
181
+ def test_tradedate_constants():
182
+ """Test that constants are properly defined"""
183
+ assert NAME == "tradedate"
184
+ assert KEY == "/tushare/tradedate"
185
+ assert SOURCE is not None
186
+ assert TARGET is not None
187
+ assert PAGINATE.pagesize == 1000
188
+ assert PAGINATE.pagelimit == 1000
189
+ assert len(EXCHANGES) == 7
190
+
191
+
192
+ # ============================================================================
193
+ # Transform Method Tests
194
+ # ============================================================================
195
+
196
+
197
+ def test_tradedate_transform_basic(mock_session, sample_source_data):
198
+ """Test basic data transformation"""
199
+ td = TradeDate(session=mock_session)
200
+ result = td.transform(sample_source_data)
201
+
202
+ assert not result.empty
203
+ assert len(result) == 4
204
+ assert "datecode" in result.columns
205
+ assert "date" in result.columns
206
+ assert "is_open" in result.columns
207
+
208
+
209
+ def test_tradedate_transform_datecode_mapping(mock_session, sample_source_data):
210
+ """Test that cal_date is mapped to datecode"""
211
+ td = TradeDate(session=mock_session)
212
+ result = td.transform(sample_source_data)
213
+
214
+ assert result["datecode"].tolist() == ["20230101", "20230102", "20230103", "20230104"]
215
+
216
+
217
+ def test_tradedate_transform_date_format(mock_session, sample_source_data):
218
+ """Test that dates are converted from YYYYMMDD to YYYY-MM-DD"""
219
+ td = TradeDate(session=mock_session)
220
+ result = td.transform(sample_source_data)
221
+
222
+ assert "2023-01-01" in result["date"].values
223
+ assert "2023-01-02" in result["date"].values
224
+
225
+
226
+ def test_tradedate_transform_is_open_boolean(mock_session, sample_source_data):
227
+ """Test that is_open is converted to boolean"""
228
+ td = TradeDate(session=mock_session)
229
+ result = td.transform(sample_source_data)
230
+
231
+ assert result["is_open"].dtype == bool
232
+ assert not result.loc[result["datecode"] == "20230101", "is_open"].iloc[0]
233
+ assert result.loc[result["datecode"] == "20230102", "is_open"].iloc[0]
234
+
235
+
236
+ def test_tradedate_transform_previous_date(mock_session, sample_source_data):
237
+ """Test that previous trade date is formatted correctly"""
238
+ td = TradeDate(session=mock_session)
239
+ result = td.transform(sample_source_data)
240
+
241
+ assert "2022-12-30" in result["previous"].values
242
+ assert "2023-01-02" in result["previous"].values
243
+
244
+
245
+ def test_tradedate_transform_exchange_field(mock_session, sample_source_data):
246
+ """Test that exchange is set to ALL"""
247
+ td = TradeDate(session=mock_session)
248
+ result = td.transform(sample_source_data)
249
+
250
+ assert all(result["exchange"] == "ALL")
251
+
252
+
253
+ def test_tradedate_transform_date_components(mock_session, sample_source_data):
254
+ """Test that date components are extracted correctly"""
255
+ td = TradeDate(session=mock_session)
256
+ result = td.transform(sample_source_data)
257
+
258
+ first_row = result.iloc[0]
259
+ assert first_row["year"] == 2023
260
+ assert first_row["month"] == 1
261
+ assert first_row["day"] == 1
262
+ assert first_row["quarter"] == 1
263
+
264
+
265
+ def test_tradedate_transform_weekday(mock_session, sample_source_data):
266
+ """Test that weekday is extracted correctly"""
267
+ td = TradeDate(session=mock_session)
268
+ result = td.transform(sample_source_data)
269
+
270
+ # 2023-01-01 is Sunday, 2023-01-02 is Monday
271
+ assert result.loc[result["datecode"] == "20230101", "weekday"].iloc[0] == "Sun"
272
+ assert result.loc[result["datecode"] == "20230102", "weekday"].iloc[0] == "Mon"
273
+
274
+
275
+ def test_tradedate_transform_empty_dataframe(mock_session):
276
+ """Test transform with empty DataFrame"""
277
+ td = TradeDate(session=mock_session)
278
+ empty_df = pd.DataFrame()
279
+ result = td.transform(empty_df)
280
+
281
+ assert result.empty
282
+ assert list(result.columns) == TARGET.list_column_names()
283
+
284
+
285
+ def test_tradedate_transform_none_input(mock_session):
286
+ """Test transform with None input"""
287
+ td = TradeDate(session=mock_session)
288
+ result = td.transform(None)
289
+
290
+ assert result.empty
291
+ assert list(result.columns) == TARGET.list_column_names()
292
+
293
+
294
+ def test_tradedate_transform_handles_invalid_dates(mock_session):
295
+ """Test transform handles invalid date formats"""
296
+ td = TradeDate(session=mock_session)
297
+ data = pd.DataFrame(
298
+ {
299
+ "exchange": ["SSE"],
300
+ "cal_date": ["invalid"],
301
+ "is_open": [1],
302
+ "pretrade_date": ["20230101"],
303
+ }
304
+ )
305
+
306
+ result = td.transform(data)
307
+ assert pd.isna(result["date"].iloc[0]) or result["date"].iloc[0] == "NaT"
308
+
309
+
310
+ def test_tradedate_transform_removes_duplicates(mock_session):
311
+ """Test that transform removes duplicate rows"""
312
+ td = TradeDate(session=mock_session)
313
+ data = pd.DataFrame(
314
+ {
315
+ "exchange": ["SSE", "SSE"], # Duplicate
316
+ "cal_date": ["20230101", "20230101"],
317
+ "is_open": [1, 1],
318
+ "pretrade_date": ["20221230", "20221230"],
319
+ }
320
+ )
321
+
322
+ result = td.transform(data)
323
+ assert len(result) == 1
324
+
325
+
326
+ def test_tradedate_transform_sorts_by_datecode(mock_session, sample_source_data):
327
+ """Test that result is sorted by datecode"""
328
+ td = TradeDate(session=mock_session)
329
+ shuffled = sample_source_data.sample(frac=1).reset_index(drop=True)
330
+ result = td.transform(shuffled)
331
+
332
+ assert result["datecode"].tolist() == sorted(result["datecode"].tolist())
333
+
334
+
335
+ def test_tradedate_transform_resets_index(mock_session, sample_source_data):
336
+ """Test that result has reset index"""
337
+ td = TradeDate(session=mock_session)
338
+ result = td.transform(sample_source_data)
339
+
340
+ assert result.index.tolist() == list(range(len(result)))
341
+
342
+
343
+ def test_tradedate_transform_only_target_columns(mock_session, sample_source_data):
344
+ """Test that only target columns are in result"""
345
+ td = TradeDate(session=mock_session)
346
+ result = td.transform(sample_source_data)
347
+
348
+ expected_cols = set(TARGET.list_column_names())
349
+ actual_cols = set(result.columns)
350
+ assert actual_cols == expected_cols
351
+
352
+
353
+ # ============================================================================
354
+ # _run Method Tests
355
+ # ============================================================================
356
+
357
+
358
+ def test_tradedate_run_with_cache_hit(mock_session):
359
+ """Test _run returns cached data when available"""
360
+ td = TradeDate(session=mock_session, cache=True)
361
+
362
+ cached_df = pd.DataFrame({"datecode": ["20230101"]})
363
+ td.cache.set(td.params.identifier, cached_df)
364
+
365
+ result = td._run()
366
+
367
+ assert result.equals(cached_df)
368
+
369
+
370
+ def test_tradedate_run_basic_date_range(mock_session, sample_source_data):
371
+ """Test _run with start_date and end_date"""
372
+ td = TradeDate(session=mock_session, params={"start_date": "20230101", "end_date": "20231231"})
373
+
374
+ with patch.object(td, "_fetchall", return_value=sample_source_data):
375
+ result = td._run()
376
+
377
+ assert not result.empty
378
+ # _fetchall should be called twice (open and close days)
379
+ assert td._fetchall.call_count == 2
380
+
381
+
382
+ def test_tradedate_run_with_year_param(mock_session, sample_source_data):
383
+ """Test _run converts year to start/end date"""
384
+ td = TradeDate(session=mock_session, params={"year": "2023"})
385
+
386
+ with patch.object(td, "_fetchall", return_value=sample_source_data):
387
+ td._run()
388
+
389
+ # Check that year was converted
390
+ assert td._fetchall.call_count == 2
391
+ first_call_kwargs = td._fetchall.call_args_list[0][1]
392
+ assert first_call_kwargs["start_date"] == "20230101"
393
+ assert first_call_kwargs["end_date"] == "20231231"
394
+
395
+
396
+ def test_tradedate_run_with_year_param_int(mock_session, sample_source_data):
397
+ """Test _run handles year as integer"""
398
+ td = TradeDate(session=mock_session, params={"year": 2023})
399
+
400
+ with patch.object(td, "_fetchall", return_value=sample_source_data):
401
+ td._run()
402
+
403
+ # Check that year was converted
404
+ assert td._fetchall.call_count == 2
405
+ first_call_kwargs = td._fetchall.call_args_list[0][1]
406
+ assert first_call_kwargs["start_date"] == "20230101"
407
+ assert first_call_kwargs["end_date"] == "20231231"
408
+
409
+
410
+ def test_tradedate_run_with_is_open_param(mock_session, sample_source_data):
411
+ """Test _run with is_open parameter (only fetch one type)"""
412
+ td = TradeDate(session=mock_session, params={"is_open": "1", "start_date": "20230101", "end_date": "20231231"})
413
+
414
+ with patch.object(td, "_fetchall", return_value=sample_source_data):
415
+ td._run()
416
+
417
+ # Should only call _fetchall once when is_open is specified
418
+ assert td._fetchall.call_count == 1
419
+ call_kwargs = td._fetchall.call_args[1]
420
+ assert call_kwargs["is_open"] == "1"
421
+
422
+
423
+ def test_tradedate_run_without_is_open_fetches_both(mock_session, sample_source_data):
424
+ """Test _run without is_open parameter fetches both trading and non-trading days"""
425
+ td = TradeDate(session=mock_session, params={"start_date": "20230101", "end_date": "20231231"})
426
+
427
+ with patch.object(td, "_fetchall", return_value=sample_source_data):
428
+ td._run()
429
+
430
+ # Should call _fetchall twice (once for open, once for close)
431
+ assert td._fetchall.call_count == 2
432
+
433
+ # Verify both calls
434
+ calls = td._fetchall.call_args_list
435
+ assert calls[0][1]["is_open"] == "1"
436
+ assert calls[1][1]["is_open"] == "0"
437
+
438
+
439
+ def test_tradedate_run_adds_fields_param(mock_session, sample_source_data):
440
+ """Test _run adds fields parameter if not provided"""
441
+ td = TradeDate(session=mock_session, params={"year": "2023"})
442
+
443
+ with patch.object(td, "_fetchall", return_value=sample_source_data):
444
+ td._run()
445
+
446
+ call_kwargs = td._fetchall.call_args_list[0][1]
447
+ assert "fields" in call_kwargs
448
+
449
+
450
+ def test_tradedate_run_sets_cache(mock_session, sample_source_data):
451
+ """Test _run saves result to cache"""
452
+ td = TradeDate(session=mock_session, cache=True, params={"year": "2023"})
453
+
454
+ with patch.object(td, "_fetchall", return_value=sample_source_data):
455
+ td._run()
456
+
457
+ cached = td.cache.get(td.params.identifier)
458
+ assert cached is not None
459
+
460
+
461
+ def test_tradedate_run_calls_transform(mock_session, sample_source_data):
462
+ """Test _run calls transform method"""
463
+ td = TradeDate(session=mock_session, params={"year": "2023"})
464
+
465
+ with patch.object(td, "_fetchall", return_value=sample_source_data):
466
+ with patch.object(td, "transform", return_value=sample_source_data) as mock_transform:
467
+ td._run()
468
+
469
+ mock_transform.assert_called_once()
470
+
471
+
472
+ def test_tradedate_run_concatenates_open_close(mock_session):
473
+ """Test _run concatenates open and close day data"""
474
+ td = TradeDate(session=mock_session, params={"year": "2023"})
475
+
476
+ open_data = pd.DataFrame(
477
+ {
478
+ "exchange": ["SSE"],
479
+ "cal_date": ["20230102"],
480
+ "is_open": [1],
481
+ "pretrade_date": ["20221230"],
482
+ }
483
+ )
484
+
485
+ close_data = pd.DataFrame(
486
+ {
487
+ "exchange": ["SSE"],
488
+ "cal_date": ["20230101"],
489
+ "is_open": [0],
490
+ "pretrade_date": ["20221230"],
491
+ }
492
+ )
493
+
494
+ with patch.object(td, "_fetchall", side_effect=[open_data, close_data]):
495
+ with patch.object(td, "transform", side_effect=lambda x: x) as mock_transform:
496
+ td._run()
497
+
498
+ # Check that concat happened
499
+ called_df = mock_transform.call_args[0][0]
500
+ assert len(called_df) == 2
501
+
502
+
503
+ # ============================================================================
504
+ # list_dates Method Tests
505
+ # ============================================================================
506
+
507
+
508
+ def test_tradedate_list_dates_basic(mock_session, sample_source_data):
509
+ """Test list_dates returns list of all dates"""
510
+ td = TradeDate(session=mock_session, cache=True, params={"year": "2023"})
511
+
512
+ transformed = td.transform(sample_source_data)
513
+ td.cache.set(td.params.identifier, transformed)
514
+
515
+ dates = td.list_dates()
516
+
517
+ assert isinstance(dates, list)
518
+ assert len(dates) == 4
519
+ assert "2023-01-01" in dates
520
+
521
+
522
+ def test_tradedate_list_dates_unique(mock_session):
523
+ """Test list_dates returns unique dates"""
524
+ td = TradeDate(session=mock_session, cache=True)
525
+
526
+ df = pd.DataFrame(
527
+ {
528
+ "date": ["2023-01-01", "2023-01-01", "2023-01-02"],
529
+ "is_open": [True, True, True],
530
+ "datecode": ["20230101", "20230101", "20230102"],
531
+ }
532
+ )
533
+ td.cache.set(td.params.identifier, df)
534
+
535
+ dates = td.list_dates()
536
+
537
+ assert len(dates) == 2
538
+
539
+
540
+ def test_tradedate_list_dates_sorted(mock_session, sample_source_data):
541
+ """Test list_dates returns sorted list"""
542
+ td = TradeDate(session=mock_session, cache=True)
543
+
544
+ transformed = td.transform(sample_source_data)
545
+ td.cache.set(td.params.identifier, transformed)
546
+
547
+ dates = td.list_dates()
548
+
549
+ assert dates == sorted(dates)
550
+
551
+
552
+ def test_tradedate_list_dates_calls_run_when_not_cached(mock_session, sample_source_data):
553
+ """Test list_dates calls run() when data not in cache"""
554
+ td = TradeDate(session=mock_session, params={"year": "2023"})
555
+
556
+ with patch.object(td, "run", return_value=td.transform(sample_source_data)):
557
+ dates = td.list_dates()
558
+
559
+ td.run.assert_called_once()
560
+ assert len(dates) == 4
561
+
562
+
563
+ # ============================================================================
564
+ # list_datecodes Method Tests
565
+ # ============================================================================
566
+
567
+
568
+ def test_tradedate_list_datecodes_basic(mock_session, sample_source_data):
569
+ """Test list_datecodes returns list of all datecodes"""
570
+ td = TradeDate(session=mock_session, cache=True)
571
+
572
+ transformed = td.transform(sample_source_data)
573
+ td.cache.set(td.params.identifier, transformed)
574
+
575
+ datecodes = td.list_datecodes()
576
+
577
+ assert isinstance(datecodes, list)
578
+ assert len(datecodes) == 4
579
+ assert "20230101" in datecodes
580
+
581
+
582
+ def test_tradedate_list_datecodes_sorted(mock_session, sample_source_data):
583
+ """Test list_datecodes returns sorted list"""
584
+ td = TradeDate(session=mock_session, cache=True)
585
+
586
+ transformed = td.transform(sample_source_data)
587
+ td.cache.set(td.params.identifier, transformed)
588
+
589
+ datecodes = td.list_datecodes()
590
+
591
+ assert datecodes == sorted(datecodes)
592
+
593
+
594
+ def test_tradedate_list_datecodes_calls_run_when_not_cached(mock_session, sample_source_data):
595
+ """Test list_datecodes calls run() when data not in cache"""
596
+ td = TradeDate(session=mock_session)
597
+
598
+ with patch.object(td, "run", return_value=td.transform(sample_source_data)):
599
+ datecodes = td.list_datecodes()
600
+
601
+ td.run.assert_called_once()
602
+ assert len(datecodes) == 4
603
+
604
+
605
+ # ============================================================================
606
+ # list_open_dates Method Tests
607
+ # ============================================================================
608
+
609
+
610
+ def test_tradedate_list_open_dates_basic(mock_session, sample_source_data):
611
+ """Test list_open_dates returns only trading days"""
612
+ td = TradeDate(session=mock_session, cache=True)
613
+
614
+ transformed = td.transform(sample_source_data)
615
+ td.cache.set(td.params.identifier, transformed)
616
+
617
+ open_dates = td.list_open_dates()
618
+
619
+ assert isinstance(open_dates, list)
620
+ assert len(open_dates) == 2 # Only 2 trading days
621
+ assert "2023-01-02" in open_dates
622
+ assert "2023-01-03" in open_dates
623
+ assert "2023-01-01" not in open_dates # Non-trading day
624
+
625
+
626
+ def test_tradedate_list_open_dates_sorted(mock_session, sample_source_data):
627
+ """Test list_open_dates returns sorted list"""
628
+ td = TradeDate(session=mock_session, cache=True)
629
+
630
+ transformed = td.transform(sample_source_data)
631
+ td.cache.set(td.params.identifier, transformed)
632
+
633
+ open_dates = td.list_open_dates()
634
+
635
+ assert open_dates == sorted(open_dates)
636
+
637
+
638
+ def test_tradedate_list_open_dates_calls_run_when_not_cached(mock_session, sample_source_data):
639
+ """Test list_open_dates calls run() when data not in cache"""
640
+ td = TradeDate(session=mock_session)
641
+
642
+ with patch.object(td, "run", return_value=td.transform(sample_source_data)):
643
+ open_dates = td.list_open_dates()
644
+
645
+ td.run.assert_called_once()
646
+ assert len(open_dates) == 2
647
+
648
+
649
+ # ============================================================================
650
+ # list_open_datecodes Method Tests
651
+ # ============================================================================
652
+
653
+
654
+ def test_tradedate_list_open_datecodes_basic(mock_session, sample_source_data):
655
+ """Test list_open_datecodes returns only trading day codes"""
656
+ td = TradeDate(session=mock_session, cache=True)
657
+
658
+ transformed = td.transform(sample_source_data)
659
+ td.cache.set(td.params.identifier, transformed)
660
+
661
+ open_datecodes = td.list_open_datecodes()
662
+
663
+ assert isinstance(open_datecodes, list)
664
+ assert len(open_datecodes) == 2 # Only 2 trading days
665
+ assert "20230102" in open_datecodes
666
+ assert "20230103" in open_datecodes
667
+ assert "20230101" not in open_datecodes # Non-trading day
668
+
669
+
670
+ def test_tradedate_list_open_datecodes_sorted(mock_session, sample_source_data):
671
+ """Test list_open_datecodes returns sorted list"""
672
+ td = TradeDate(session=mock_session, cache=True)
673
+
674
+ transformed = td.transform(sample_source_data)
675
+ td.cache.set(td.params.identifier, transformed)
676
+
677
+ open_datecodes = td.list_open_datecodes()
678
+
679
+ assert open_datecodes == sorted(open_datecodes)
680
+
681
+
682
+ def test_tradedate_list_open_datecodes_calls_run_when_not_cached(mock_session, sample_source_data):
683
+ """Test list_open_datecodes calls run() when data not in cache"""
684
+ td = TradeDate(session=mock_session)
685
+
686
+ with patch.object(td, "run", return_value=td.transform(sample_source_data)):
687
+ open_datecodes = td.list_open_datecodes()
688
+
689
+ td.run.assert_called_once()
690
+ assert len(open_datecodes) == 2
691
+
692
+
693
+ # ============================================================================
694
+ # check Method Tests
695
+ # ============================================================================
696
+
697
+
698
+ def test_tradedate_check_with_string_date_hyphen(mock_session, sample_source_data):
699
+ """Test check method with string date (YYYY-MM-DD format)"""
700
+ with patch.object(
701
+ TradeDate,
702
+ "run",
703
+ return_value=pd.DataFrame(
704
+ {
705
+ "datecode": ["20230102"],
706
+ "is_open": [True],
707
+ }
708
+ ),
709
+ ):
710
+ result = TradeDate.check(mock_session, "2023-01-02")
711
+
712
+ assert result is True
713
+
714
+
715
+ def test_tradedate_check_with_string_date_no_hyphen(mock_session, sample_source_data):
716
+ """Test check method with string date (YYYYMMDD format)"""
717
+ with patch.object(
718
+ TradeDate,
719
+ "run",
720
+ return_value=pd.DataFrame(
721
+ {
722
+ "datecode": ["20230102"],
723
+ "is_open": [True],
724
+ }
725
+ ),
726
+ ):
727
+ result = TradeDate.check(mock_session, "20230102")
728
+
729
+ assert result is True
730
+
731
+
732
+ def test_tradedate_check_with_datetime(mock_session):
733
+ """Test check method with datetime object"""
734
+ test_date = datetime(2023, 1, 2)
735
+
736
+ with patch.object(
737
+ TradeDate,
738
+ "run",
739
+ return_value=pd.DataFrame(
740
+ {
741
+ "datecode": ["20230102"],
742
+ "is_open": [True],
743
+ }
744
+ ),
745
+ ):
746
+ result = TradeDate.check(mock_session, test_date)
747
+
748
+ assert result is True
749
+
750
+
751
+ def test_tradedate_check_with_date(mock_session):
752
+ """Test check method with date object"""
753
+ test_date = date(2023, 1, 2)
754
+
755
+ with patch.object(
756
+ TradeDate,
757
+ "run",
758
+ return_value=pd.DataFrame(
759
+ {
760
+ "datecode": ["20230102"],
761
+ "is_open": [True],
762
+ }
763
+ ),
764
+ ):
765
+ result = TradeDate.check(mock_session, test_date)
766
+
767
+ assert result is True
768
+
769
+
770
+ def test_tradedate_check_non_trading_day(mock_session):
771
+ """Test check method returns False for non-trading day"""
772
+ with patch.object(
773
+ TradeDate,
774
+ "run",
775
+ return_value=pd.DataFrame(
776
+ {
777
+ "datecode": [],
778
+ "is_open": [],
779
+ }
780
+ ),
781
+ ):
782
+ result = TradeDate.check(mock_session, "2023-01-01")
783
+
784
+ assert result is False
785
+
786
+
787
+ def test_tradedate_check_with_none_uses_current_date(mock_session):
788
+ """Test check method uses current date when None is passed"""
789
+ today = datetime.now().date()
790
+ today_code = today.strftime("%Y%m%d")
791
+
792
+ with patch.object(
793
+ TradeDate,
794
+ "run",
795
+ return_value=pd.DataFrame(
796
+ {
797
+ "datecode": [today_code],
798
+ "is_open": [True],
799
+ }
800
+ ),
801
+ ):
802
+ result = TradeDate.check(mock_session, None)
803
+
804
+ assert isinstance(result, bool)
805
+
806
+
807
+ # ============================================================================
808
+ # Integration Tests
809
+ # ============================================================================
810
+
811
+
812
+ def test_tradedate_full_workflow(mock_session, sample_source_data):
813
+ """Test complete workflow from initialization to data retrieval"""
814
+ td = TradeDate(
815
+ session=mock_session,
816
+ params={"year": "2023"},
817
+ cache=True,
818
+ )
819
+
820
+ with patch.object(td, "_fetchall", return_value=sample_source_data):
821
+ result = td.run()
822
+
823
+ assert not result.empty
824
+ assert "datecode" in result.columns
825
+
826
+ dates = td.list_dates()
827
+ open_dates = td.list_open_dates()
828
+ datecodes = td.list_datecodes()
829
+ open_datecodes = td.list_open_datecodes()
830
+
831
+ assert len(dates) == 4
832
+ assert len(open_dates) == 2
833
+ assert len(datecodes) == 4
834
+ assert len(open_datecodes) == 2
835
+
836
+
837
+ def test_tradedate_cache_persistence(mock_session, sample_source_data):
838
+ """Test that cache persists across method calls"""
839
+ td = TradeDate(session=mock_session, cache=True, params={"year": "2023"})
840
+
841
+ with patch.object(td, "_load_cache", return_value=None) as mock_load:
842
+ with patch.object(td, "_fetchall", return_value=sample_source_data) as mock_fetch:
843
+ # First run - fetches data and caches it
844
+ result1 = td.run()
845
+ assert mock_fetch.call_count == 2 # Called twice (open/close)
846
+ assert mock_load.call_count == 1
847
+
848
+ # Second run - _load_cache still returns None, so _fetchall called again
849
+ result2 = td.run()
850
+ assert mock_fetch.call_count == 4 # Called twice more
851
+ assert mock_load.call_count == 2
852
+
853
+ pd.testing.assert_frame_equal(result1, result2)
854
+
855
+
856
+ def test_tradedate_params_identifier_uniqueness(mock_session):
857
+ """Test that different params produce different cache keys"""
858
+ td1 = TradeDate(session=mock_session, params={"year": "2022"}, cache=True)
859
+ td2 = TradeDate(session=mock_session, params={"year": "2023"}, cache=True)
860
+
861
+ assert td1.params.identifier != td2.params.identifier
862
+
863
+
864
+ def test_tradedate_different_exchanges(mock_session, sample_source_data):
865
+ """Test with different exchange parameters"""
866
+ for exchange in EXCHANGES:
867
+ td = TradeDate(session=mock_session, params={"exchange": exchange, "year": "2023"})
868
+
869
+ with patch.object(td, "_fetchall", return_value=sample_source_data):
870
+ td._run()
871
+
872
+ call_kwargs = td._fetchall.call_args_list[0][1]
873
+ assert call_kwargs["exchange"] == exchange
874
+
875
+
876
+ # ============================================================================
877
+ # Edge Case Tests
878
+ # ============================================================================
879
+
880
+
881
+ def test_tradedate_empty_result_handling(mock_session):
882
+ """Test handling of empty API results"""
883
+ td = TradeDate(session=mock_session, params={"year": "2023"})
884
+
885
+ empty_df = pd.DataFrame()
886
+ with patch.object(td, "_fetchall", return_value=empty_df):
887
+ result = td._run()
888
+
889
+ assert result.empty
890
+ assert list(result.columns) == TARGET.list_column_names()
891
+
892
+
893
+ def test_tradedate_large_dataset_handling(mock_session):
894
+ """Test handling of large datasets"""
895
+ td = TradeDate(session=mock_session, params={"year": "2023"})
896
+
897
+ # Create large dataset (365 days)
898
+ large_data = pd.DataFrame(
899
+ {
900
+ "exchange": ["SSE"] * 365,
901
+ "cal_date": [f"2023{i:04d}" for i in range(101, 466)], # Simplified
902
+ "is_open": [1] * 365,
903
+ "pretrade_date": [f"2023{i:04d}" for i in range(100, 465)],
904
+ }
905
+ )
906
+
907
+ result = td.transform(large_data)
908
+
909
+ assert len(result) <= 365 # Some may be filtered
910
+ assert not result.empty
911
+
912
+
913
+ def test_tradedate_without_cache(mock_session, sample_source_data):
914
+ """Test TradeDate works correctly without cache"""
915
+ td = TradeDate(session=mock_session, cache=False, params={"year": "2023"})
916
+
917
+ assert td.cache is None
918
+
919
+ with patch.object(td, "_fetchall", return_value=sample_source_data):
920
+ result = td.run()
921
+
922
+ assert not result.empty
923
+
924
+ dates = td.list_dates()
925
+ open_dates = td.list_open_dates()
926
+
927
+ assert len(dates) > 0
928
+ assert len(open_dates) > 0
929
+
930
+
931
+ def test_tradedate_mixed_trading_non_trading_days(mock_session):
932
+ """Test handling of mixed trading and non-trading days"""
933
+ td = TradeDate(session=mock_session)
934
+
935
+ data = pd.DataFrame(
936
+ {
937
+ "exchange": ["SSE"] * 7,
938
+ "cal_date": [f"2023010{i}" for i in range(1, 8)],
939
+ "is_open": [0, 1, 1, 1, 1, 1, 0], # Weekend, weekdays, weekend
940
+ "pretrade_date": ["20221230"] + [f"2023010{i}" for i in range(1, 7)],
941
+ }
942
+ )
943
+
944
+ result = td.transform(data)
945
+
946
+ assert len(result) == 7
947
+ assert result["is_open"].sum() == 5 # 5 trading days