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