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,149 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, List, Literal, Optional
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
|
|
7
|
+
from xfintech.data.common.cache import Cache
|
|
8
|
+
from xfintech.data.common.coolant import Coolant
|
|
9
|
+
from xfintech.data.common.params import Params
|
|
10
|
+
from xfintech.data.common.retry import Retry
|
|
11
|
+
from xfintech.data.job import JobHouse
|
|
12
|
+
from xfintech.data.source.baostock.job import BaostockJob
|
|
13
|
+
from xfintech.data.source.baostock.session.session import Session
|
|
14
|
+
from xfintech.data.source.baostock.stock.stock.constant import (
|
|
15
|
+
KEY,
|
|
16
|
+
NAME,
|
|
17
|
+
PAGINATE,
|
|
18
|
+
SOURCE,
|
|
19
|
+
TARGET,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@JobHouse.register(KEY, alias=KEY)
|
|
24
|
+
class Stock(BaostockJob):
|
|
25
|
+
"""
|
|
26
|
+
描述:
|
|
27
|
+
- 获取上市股票基本信息
|
|
28
|
+
- 返回所有沪深A股上市公司的基础信息
|
|
29
|
+
- 包括股票代码、股票名称、交易状态等信息
|
|
30
|
+
- API文档: http://www.baostock.com/mainContent?file=StockBasicInfoAPI.md
|
|
31
|
+
- SCALE: CrossSection
|
|
32
|
+
- TYPE: Partitioned
|
|
33
|
+
- PAGINATE: 10000 rows / 100 pages
|
|
34
|
+
|
|
35
|
+
属性:
|
|
36
|
+
- name: str, 作业名称 'stock'。
|
|
37
|
+
- key: str, 作业键 '/baostock/stock'。
|
|
38
|
+
- session: Session, Baostock会话对象。
|
|
39
|
+
- source: TableInfo, 源表信息(BaoStock原始格式)。
|
|
40
|
+
- target: TableInfo, 目标表信息(xfintech格式)。
|
|
41
|
+
- params: Params, 查询参数。
|
|
42
|
+
- day: str, 可选, 指定交易日(YYYY-MM-DD 或 YYYYMMDD)
|
|
43
|
+
- coolant: Coolant, 请求冷却控制。
|
|
44
|
+
- paginate: Paginate, 分页控制(pagesize=10000, pagelimit=100)。
|
|
45
|
+
- retry: Retry, 重试策略。
|
|
46
|
+
- cache: Cache, 缓存管理。
|
|
47
|
+
|
|
48
|
+
方法:
|
|
49
|
+
- run(): 执行作业,返回股票基本信息DataFrame。
|
|
50
|
+
- _run(): 内部执行逻辑,处理日期参数并调用API。
|
|
51
|
+
- transform(data): 转换数据格式,将源格式转为目标格式。
|
|
52
|
+
- list_codes(): 返回所有股票代码列表。
|
|
53
|
+
- list_names(): 返回所有股票名称列表。
|
|
54
|
+
|
|
55
|
+
例子:
|
|
56
|
+
```python
|
|
57
|
+
from xfintech.data.source.baostock.session import Session
|
|
58
|
+
from xfintech.data.source.baostock.stock.stock import Stock
|
|
59
|
+
|
|
60
|
+
session = Session()
|
|
61
|
+
job = Stock(
|
|
62
|
+
session=session,
|
|
63
|
+
params={"day": "20260110"}
|
|
64
|
+
)
|
|
65
|
+
df = job.run()
|
|
66
|
+
```
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
def __init__(
|
|
70
|
+
self,
|
|
71
|
+
session: Session,
|
|
72
|
+
params: Optional[Params | Dict[str, Any]] = None,
|
|
73
|
+
coolant: Optional[Coolant | Dict[str, Any]] = None,
|
|
74
|
+
retry: Optional[Retry | Dict[str, Any]] = None,
|
|
75
|
+
cache: Optional[Cache | Dict[str, str] | bool] = None,
|
|
76
|
+
) -> None:
|
|
77
|
+
super().__init__(
|
|
78
|
+
name=NAME,
|
|
79
|
+
key=KEY,
|
|
80
|
+
session=session,
|
|
81
|
+
source=SOURCE,
|
|
82
|
+
target=TARGET,
|
|
83
|
+
params=params,
|
|
84
|
+
coolant=coolant,
|
|
85
|
+
paginate=PAGINATE,
|
|
86
|
+
retry=retry,
|
|
87
|
+
cache=cache,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
def _run(self) -> pd.DataFrame:
|
|
91
|
+
cached = self._load_cache()
|
|
92
|
+
if cached is not None:
|
|
93
|
+
return cached
|
|
94
|
+
|
|
95
|
+
# Prepare payload dict
|
|
96
|
+
payload = self.params.to_dict()
|
|
97
|
+
payload = self._parse_date_params(
|
|
98
|
+
payload,
|
|
99
|
+
keys=["day"],
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
# Fetch data from API
|
|
103
|
+
data = self._fetchall(
|
|
104
|
+
api=self.connection.query_all_stock,
|
|
105
|
+
**payload,
|
|
106
|
+
)
|
|
107
|
+
result = self.transform(data)
|
|
108
|
+
self._save_cache(result)
|
|
109
|
+
return result
|
|
110
|
+
|
|
111
|
+
def transform(
|
|
112
|
+
self,
|
|
113
|
+
data: pd.DataFrame,
|
|
114
|
+
) -> pd.DataFrame:
|
|
115
|
+
cols = self.target.list_column_names()
|
|
116
|
+
if data is None or data.empty:
|
|
117
|
+
return pd.DataFrame(columns=cols)
|
|
118
|
+
|
|
119
|
+
out = data.copy()
|
|
120
|
+
out["code"] = out["code"].astype(str)
|
|
121
|
+
out["trade_status"] = out["tradeStatus"].astype(str)
|
|
122
|
+
out["name"] = out["code_name"].astype(str)
|
|
123
|
+
|
|
124
|
+
# Finalize output
|
|
125
|
+
out = out[cols].drop_duplicates()
|
|
126
|
+
out = out.sort_values(by=["code"])
|
|
127
|
+
out = out.reset_index(drop=True)
|
|
128
|
+
self.markpoint("transform[OK]")
|
|
129
|
+
return out
|
|
130
|
+
|
|
131
|
+
def list_codes(self) -> List[str]:
|
|
132
|
+
df = self.run()
|
|
133
|
+
return sorted(df["code"].unique().tolist())
|
|
134
|
+
|
|
135
|
+
def list_names(self) -> List[str]:
|
|
136
|
+
df = self.run()
|
|
137
|
+
return sorted(df["name"].unique().tolist())
|
|
138
|
+
|
|
139
|
+
def list_tradeable_codes(
|
|
140
|
+
self,
|
|
141
|
+
type: Optional[Literal["0", "1"]] = None,
|
|
142
|
+
) -> List[str]:
|
|
143
|
+
df = self.run()
|
|
144
|
+
if type is None:
|
|
145
|
+
return sorted(df["code"].unique().tolist())
|
|
146
|
+
else:
|
|
147
|
+
type = str(type)
|
|
148
|
+
tradeable_df = df[df["trade_status"] == type]
|
|
149
|
+
return sorted(tradeable_df["code"].unique().tolist())
|
|
File without changes
|
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
from unittest.mock import MagicMock, patch
|
|
2
|
+
|
|
3
|
+
import pandas as pd
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
from xfintech.data.common.cache import Cache
|
|
7
|
+
from xfintech.data.common.coolant import Coolant
|
|
8
|
+
from xfintech.data.common.retry import Retry
|
|
9
|
+
from xfintech.data.source.baostock.session.session import Session
|
|
10
|
+
from xfintech.data.source.baostock.stock.stock.constant import (
|
|
11
|
+
KEY,
|
|
12
|
+
NAME,
|
|
13
|
+
SOURCE,
|
|
14
|
+
TARGET,
|
|
15
|
+
)
|
|
16
|
+
from xfintech.data.source.baostock.stock.stock.stock import Stock
|
|
17
|
+
|
|
18
|
+
# ============================================================================
|
|
19
|
+
# Fixtures
|
|
20
|
+
# ============================================================================
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@pytest.fixture
|
|
24
|
+
def mock_session():
|
|
25
|
+
"""Create a mock Baostock session"""
|
|
26
|
+
session = MagicMock(spec=Session)
|
|
27
|
+
session._credential = None
|
|
28
|
+
session.id = "test1234"
|
|
29
|
+
session.mode = "direct"
|
|
30
|
+
session.relay_url = None
|
|
31
|
+
session.relay_secret = None
|
|
32
|
+
session.connected = True
|
|
33
|
+
|
|
34
|
+
# Mock the connection object
|
|
35
|
+
mock_connection = MagicMock()
|
|
36
|
+
mock_connection.query_all_stock = MagicMock()
|
|
37
|
+
session.connection = mock_connection
|
|
38
|
+
|
|
39
|
+
return session
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@pytest.fixture
|
|
43
|
+
def sample_source_data():
|
|
44
|
+
"""Create sample source data in Baostock format"""
|
|
45
|
+
return pd.DataFrame(
|
|
46
|
+
{
|
|
47
|
+
"code": ["sh.600000", "sz.000001", "sz.000002"],
|
|
48
|
+
"tradeStatus": ["1", "1", "0"],
|
|
49
|
+
"code_name": ["浦发银行", "平安银行", "万科A"],
|
|
50
|
+
}
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# ============================================================================
|
|
55
|
+
# Initialization Tests
|
|
56
|
+
# ============================================================================
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test_stock_initialization_basic(mock_session):
|
|
60
|
+
"""Test basic initialization"""
|
|
61
|
+
job = Stock(session=mock_session)
|
|
62
|
+
|
|
63
|
+
assert job.name == NAME
|
|
64
|
+
assert job.key == KEY
|
|
65
|
+
assert job.source == SOURCE
|
|
66
|
+
assert job.target == TARGET
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def test_stock_initialization_with_params(mock_session):
|
|
70
|
+
"""Test initialization with params"""
|
|
71
|
+
params = {"day": "2026-01-10"}
|
|
72
|
+
job = Stock(session=mock_session, params=params)
|
|
73
|
+
|
|
74
|
+
assert job.params.day == "2026-01-10"
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def test_stock_initialization_with_all_components(mock_session):
|
|
78
|
+
"""Test initialization with all components"""
|
|
79
|
+
params = {"day": "2026-01-10"}
|
|
80
|
+
coolant = Coolant(interval=0.2)
|
|
81
|
+
retry = Retry(retry=3)
|
|
82
|
+
cache = Cache(path="/tmp/test_cache")
|
|
83
|
+
|
|
84
|
+
job = Stock(
|
|
85
|
+
session=mock_session,
|
|
86
|
+
params=params,
|
|
87
|
+
coolant=coolant,
|
|
88
|
+
retry=retry,
|
|
89
|
+
cache=cache,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
assert job.params.day == "2026-01-10"
|
|
93
|
+
assert job.coolant.interval == 0.2
|
|
94
|
+
assert job.retry.retry == 3
|
|
95
|
+
assert job.cache is not None
|
|
96
|
+
assert isinstance(job.cache, Cache)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def test_stock_name_and_key():
|
|
100
|
+
"""Test name and key constants"""
|
|
101
|
+
assert NAME == "stock"
|
|
102
|
+
assert KEY == "/baostock/stock"
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def test_stock_source_schema():
|
|
106
|
+
"""Test source schema has all required columns"""
|
|
107
|
+
assert SOURCE is not None
|
|
108
|
+
assert SOURCE.desc == "上市股票基本信息(Baostock格式)"
|
|
109
|
+
|
|
110
|
+
column_names = SOURCE.columns
|
|
111
|
+
assert "code" in column_names
|
|
112
|
+
assert "tradestatus" in column_names # lowercase
|
|
113
|
+
assert "code_name" in column_names
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def test_stock_target_schema():
|
|
117
|
+
"""Test target schema has all required columns"""
|
|
118
|
+
assert TARGET is not None
|
|
119
|
+
assert TARGET.desc == "上市公司基本信息(xfintech格式)"
|
|
120
|
+
|
|
121
|
+
column_names = TARGET.columns
|
|
122
|
+
assert "code" in column_names
|
|
123
|
+
assert "trade_status" in column_names
|
|
124
|
+
assert "name" in column_names
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
# ============================================================================
|
|
128
|
+
# Transform Tests
|
|
129
|
+
# ============================================================================
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def test_stock_transform_basic(mock_session, sample_source_data):
|
|
133
|
+
"""Test basic data transformation"""
|
|
134
|
+
job = Stock(session=mock_session)
|
|
135
|
+
result = job.transform(sample_source_data)
|
|
136
|
+
|
|
137
|
+
assert len(result) == 3
|
|
138
|
+
assert "code" in result.columns
|
|
139
|
+
assert "trade_status" in result.columns
|
|
140
|
+
assert "name" in result.columns
|
|
141
|
+
assert result.iloc[0]["code"] == "sh.600000"
|
|
142
|
+
assert result.iloc[0]["name"] == "浦发银行"
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def test_stock_transform_field_types(mock_session, sample_source_data):
|
|
146
|
+
"""Test field type conversions"""
|
|
147
|
+
job = Stock(session=mock_session)
|
|
148
|
+
result = job.transform(sample_source_data)
|
|
149
|
+
|
|
150
|
+
# Check string fields
|
|
151
|
+
assert isinstance(result.iloc[0]["code"], str)
|
|
152
|
+
assert isinstance(result.iloc[0]["trade_status"], str)
|
|
153
|
+
assert isinstance(result.iloc[0]["name"], str)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def test_stock_transform_field_mapping(mock_session, sample_source_data):
|
|
157
|
+
"""Test field name mappings"""
|
|
158
|
+
job = Stock(session=mock_session)
|
|
159
|
+
result = job.transform(sample_source_data)
|
|
160
|
+
|
|
161
|
+
# Verify field mappings
|
|
162
|
+
assert result.iloc[0]["trade_status"] == "1" # from tradeStatus
|
|
163
|
+
assert result.iloc[0]["name"] == "浦发银行" # from code_name
|
|
164
|
+
assert result.iloc[0]["code"] == "sh.600000"
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def test_stock_transform_empty_data(mock_session):
|
|
168
|
+
"""Test transform with empty data"""
|
|
169
|
+
job = Stock(session=mock_session)
|
|
170
|
+
|
|
171
|
+
# Test with None
|
|
172
|
+
result = job.transform(None)
|
|
173
|
+
assert result.empty
|
|
174
|
+
assert len(result.columns) == len(TARGET.columns)
|
|
175
|
+
|
|
176
|
+
# Test with empty DataFrame
|
|
177
|
+
empty_df = pd.DataFrame()
|
|
178
|
+
result = job.transform(empty_df)
|
|
179
|
+
assert result.empty
|
|
180
|
+
assert len(result.columns) == len(TARGET.columns)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def test_stock_transform_duplicate_removal(mock_session):
|
|
184
|
+
"""Test that duplicates are removed"""
|
|
185
|
+
data = pd.DataFrame(
|
|
186
|
+
{
|
|
187
|
+
"code": ["sh.600000", "sh.600000", "sz.000001"],
|
|
188
|
+
"tradeStatus": ["1", "1", "1"],
|
|
189
|
+
"code_name": ["浦发银行", "浦发银行", "平安银行"],
|
|
190
|
+
}
|
|
191
|
+
)
|
|
192
|
+
job = Stock(session=mock_session)
|
|
193
|
+
result = job.transform(data)
|
|
194
|
+
|
|
195
|
+
# Duplicates should be removed
|
|
196
|
+
assert len(result) == 2
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def test_stock_transform_sorting(mock_session):
|
|
200
|
+
"""Test that result is sorted by code"""
|
|
201
|
+
data = pd.DataFrame(
|
|
202
|
+
{
|
|
203
|
+
"code": ["sz.000002", "sh.600000", "sz.000001"],
|
|
204
|
+
"tradeStatus": ["1", "1", "1"],
|
|
205
|
+
"code_name": ["万科A", "浦发银行", "平安银行"],
|
|
206
|
+
}
|
|
207
|
+
)
|
|
208
|
+
job = Stock(session=mock_session)
|
|
209
|
+
result = job.transform(data)
|
|
210
|
+
|
|
211
|
+
# Should be sorted by code
|
|
212
|
+
assert result.iloc[0]["code"] == "sh.600000"
|
|
213
|
+
assert result.iloc[1]["code"] == "sz.000001"
|
|
214
|
+
assert result.iloc[2]["code"] == "sz.000002"
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
# ============================================================================
|
|
218
|
+
# Run Tests
|
|
219
|
+
# ============================================================================
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def test_stock_run_basic(mock_session, sample_source_data):
|
|
223
|
+
"""Test basic run method"""
|
|
224
|
+
job = Stock(session=mock_session)
|
|
225
|
+
|
|
226
|
+
with patch.object(job, "_fetchall", return_value=sample_source_data):
|
|
227
|
+
result = job.run()
|
|
228
|
+
|
|
229
|
+
assert isinstance(result, pd.DataFrame)
|
|
230
|
+
assert len(result) == 3
|
|
231
|
+
assert "code" in result.columns
|
|
232
|
+
assert "trade_status" in result.columns
|
|
233
|
+
assert "name" in result.columns
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def test_stock_run_with_day_param(mock_session, sample_source_data):
|
|
237
|
+
"""Test run with day parameter"""
|
|
238
|
+
job = Stock(session=mock_session, params={"day": "2026-01-10"})
|
|
239
|
+
|
|
240
|
+
with patch.object(job, "_fetchall", return_value=sample_source_data) as mock_fetchall:
|
|
241
|
+
job.run()
|
|
242
|
+
|
|
243
|
+
call_kwargs = mock_fetchall.call_args[1]
|
|
244
|
+
assert call_kwargs["day"] == "2026-01-10"
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def test_stock_run_calls_query_all_stock(mock_session, sample_source_data):
|
|
248
|
+
"""Test that run calls query_all_stock API"""
|
|
249
|
+
job = Stock(session=mock_session)
|
|
250
|
+
|
|
251
|
+
with patch.object(job, "_fetchall", return_value=sample_source_data) as mock_fetchall:
|
|
252
|
+
job.run()
|
|
253
|
+
|
|
254
|
+
# Verify that _fetchall was called with the correct API
|
|
255
|
+
assert mock_fetchall.call_count == 1
|
|
256
|
+
call_args = mock_fetchall.call_args
|
|
257
|
+
assert call_args[1]["api"] == job.connection.query_all_stock
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def test_stock_run_calls_transform(mock_session, sample_source_data):
|
|
261
|
+
"""Test that run calls transform"""
|
|
262
|
+
job = Stock(session=mock_session)
|
|
263
|
+
|
|
264
|
+
with patch.object(job, "_fetchall", return_value=sample_source_data):
|
|
265
|
+
with patch.object(job, "transform", wraps=job.transform) as mock_transform:
|
|
266
|
+
job.run()
|
|
267
|
+
|
|
268
|
+
mock_transform.assert_called_once()
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
# ============================================================================
|
|
272
|
+
# Cache Tests
|
|
273
|
+
# ============================================================================
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def test_stock_cache_persistence(mock_session, sample_source_data):
|
|
277
|
+
"""Test that cache persists across runs"""
|
|
278
|
+
job = Stock(session=mock_session, cache=True)
|
|
279
|
+
|
|
280
|
+
with patch.object(job, "_load_cache", return_value=None) as mock_load:
|
|
281
|
+
with patch.object(job, "_fetchall", return_value=sample_source_data) as mock_fetchall:
|
|
282
|
+
# First run - fetches data and caches it
|
|
283
|
+
result1 = job.run()
|
|
284
|
+
assert mock_fetchall.call_count == 1
|
|
285
|
+
assert mock_load.call_count == 1
|
|
286
|
+
|
|
287
|
+
# Second run - _load_cache still returns None, so _fetchall called again
|
|
288
|
+
result2 = job.run()
|
|
289
|
+
assert mock_fetchall.call_count == 2
|
|
290
|
+
assert mock_load.call_count == 2
|
|
291
|
+
|
|
292
|
+
pd.testing.assert_frame_equal(result1, result2)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def test_stock_params_identifier_uniqueness(mock_session):
|
|
296
|
+
"""Test that different params create different cache keys"""
|
|
297
|
+
job1 = Stock(session=mock_session, params={"day": "2026-01-10"}, cache=True)
|
|
298
|
+
job2 = Stock(session=mock_session, params={"day": "2026-01-11"}, cache=True)
|
|
299
|
+
|
|
300
|
+
assert job1.params.identifier != job2.params.identifier
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def test_stock_without_cache(mock_session, sample_source_data):
|
|
304
|
+
"""Test that stock works correctly without cache"""
|
|
305
|
+
job = Stock(session=mock_session, cache=False)
|
|
306
|
+
|
|
307
|
+
with patch.object(job, "_fetchall", return_value=sample_source_data) as mock_fetchall:
|
|
308
|
+
job.run()
|
|
309
|
+
job.run()
|
|
310
|
+
|
|
311
|
+
# Should fetch twice (no caching)
|
|
312
|
+
assert mock_fetchall.call_count == 2
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
# ============================================================================
|
|
316
|
+
# Date Parsing Tests
|
|
317
|
+
# ============================================================================
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def test_stock_date_parsing_yyyymmdd(mock_session, sample_source_data):
|
|
321
|
+
"""Test date parsing from YYYYMMDD format"""
|
|
322
|
+
job = Stock(session=mock_session, params={"day": "20260110"})
|
|
323
|
+
|
|
324
|
+
with patch.object(job, "_fetchall", return_value=sample_source_data) as mock_fetchall:
|
|
325
|
+
job.run()
|
|
326
|
+
|
|
327
|
+
call_kwargs = mock_fetchall.call_args[1]
|
|
328
|
+
# Should convert YYYYMMDD to YYYY-MM-DD
|
|
329
|
+
assert call_kwargs["day"] == "2026-01-10"
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def test_stock_date_parsing_hyphen_format(mock_session, sample_source_data):
|
|
333
|
+
"""Test date parsing preserves YYYY-MM-DD format"""
|
|
334
|
+
job = Stock(session=mock_session, params={"day": "2026-01-10"})
|
|
335
|
+
|
|
336
|
+
with patch.object(job, "_fetchall", return_value=sample_source_data) as mock_fetchall:
|
|
337
|
+
job.run()
|
|
338
|
+
|
|
339
|
+
call_kwargs = mock_fetchall.call_args[1]
|
|
340
|
+
assert call_kwargs["day"] == "2026-01-10"
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
# ============================================================================
|
|
344
|
+
# Integration Tests
|
|
345
|
+
# ============================================================================
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def test_stock_full_workflow(mock_session, sample_source_data):
|
|
349
|
+
"""Test complete workflow from initialization to data retrieval"""
|
|
350
|
+
job = Stock(session=mock_session, params={"day": "2026-01-10"})
|
|
351
|
+
|
|
352
|
+
with patch.object(job, "_fetchall", return_value=sample_source_data):
|
|
353
|
+
result = job.run()
|
|
354
|
+
|
|
355
|
+
assert not result.empty
|
|
356
|
+
assert len(result) == 3
|
|
357
|
+
assert list(result.columns) == ["code", "trade_status", "name"]
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def test_stock_with_large_dataset(mock_session):
|
|
361
|
+
"""Test handling of large dataset"""
|
|
362
|
+
# Create a large dataset
|
|
363
|
+
large_data = pd.DataFrame(
|
|
364
|
+
{
|
|
365
|
+
"code": [f"sh.{600000 + i}" for i in range(1000)],
|
|
366
|
+
"tradeStatus": ["1"] * 1000,
|
|
367
|
+
"code_name": [f"股票{i}" for i in range(1000)],
|
|
368
|
+
}
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
job = Stock(session=mock_session)
|
|
372
|
+
|
|
373
|
+
with patch.object(job, "_fetchall", return_value=large_data):
|
|
374
|
+
result = job.run()
|
|
375
|
+
|
|
376
|
+
assert len(result) == 1000
|
|
377
|
+
assert result.iloc[0]["code"] == "sh.600000"
|
|
378
|
+
assert result.iloc[-1]["code"] == "sh.600999"
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def test_stock_with_various_trade_statuses(mock_session):
|
|
382
|
+
"""Test handling stocks with different trade statuses"""
|
|
383
|
+
data = pd.DataFrame(
|
|
384
|
+
{
|
|
385
|
+
"code": ["sh.600000", "sz.000001", "sz.000002"],
|
|
386
|
+
"tradeStatus": ["1", "0", "1"],
|
|
387
|
+
"code_name": ["浦发银行", "平安银行", "万科A"],
|
|
388
|
+
}
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
job = Stock(session=mock_session)
|
|
392
|
+
|
|
393
|
+
with patch.object(job, "_fetchall", return_value=data):
|
|
394
|
+
result = job.run()
|
|
395
|
+
|
|
396
|
+
assert len(result) == 3
|
|
397
|
+
# Check that all trade statuses are preserved
|
|
398
|
+
assert "1" in result["trade_status"].values
|
|
399
|
+
assert "0" in result["trade_status"].values
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def test_stock_with_empty_result_from_api(mock_session):
|
|
403
|
+
"""Test handling of empty result from API"""
|
|
404
|
+
empty_data = pd.DataFrame(columns=["code", "tradeStatus", "code_name"])
|
|
405
|
+
|
|
406
|
+
job = Stock(session=mock_session)
|
|
407
|
+
|
|
408
|
+
with patch.object(job, "_fetchall", return_value=empty_data):
|
|
409
|
+
result = job.run()
|
|
410
|
+
|
|
411
|
+
assert result.empty
|
|
412
|
+
assert list(result.columns) == ["code", "trade_status", "name"]
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
def test_stock_with_special_characters_in_names(mock_session):
|
|
416
|
+
"""Test handling of special characters in stock names"""
|
|
417
|
+
data = pd.DataFrame(
|
|
418
|
+
{
|
|
419
|
+
"code": ["sh.600000", "sz.000001"],
|
|
420
|
+
"tradeStatus": ["1", "1"],
|
|
421
|
+
"code_name": ["*ST浦发", "ST平安"],
|
|
422
|
+
}
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
job = Stock(session=mock_session)
|
|
426
|
+
|
|
427
|
+
with patch.object(job, "_fetchall", return_value=data):
|
|
428
|
+
result = job.run()
|
|
429
|
+
|
|
430
|
+
assert result.iloc[0]["name"] == "*ST浦发"
|
|
431
|
+
assert result.iloc[1]["name"] == "ST平安"
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
# ============================================================================
|
|
435
|
+
# List Methods Tests
|
|
436
|
+
# ============================================================================
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
def test_stock_list_codes(mock_session, sample_source_data):
|
|
440
|
+
"""Test list_codes method"""
|
|
441
|
+
job = Stock(session=mock_session)
|
|
442
|
+
|
|
443
|
+
with patch.object(job, "_fetchall", return_value=sample_source_data):
|
|
444
|
+
codes = job.list_codes()
|
|
445
|
+
|
|
446
|
+
assert isinstance(codes, list)
|
|
447
|
+
assert len(codes) == 3
|
|
448
|
+
assert "sh.600000" in codes
|
|
449
|
+
assert "sz.000001" in codes
|
|
450
|
+
assert "sz.000002" in codes
|
|
451
|
+
# Should be sorted
|
|
452
|
+
assert codes == sorted(codes)
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
def test_stock_list_names(mock_session, sample_source_data):
|
|
456
|
+
"""Test list_names method"""
|
|
457
|
+
job = Stock(session=mock_session)
|
|
458
|
+
|
|
459
|
+
with patch.object(job, "_fetchall", return_value=sample_source_data):
|
|
460
|
+
names = job.list_names()
|
|
461
|
+
|
|
462
|
+
assert isinstance(names, list)
|
|
463
|
+
assert len(names) == 3
|
|
464
|
+
assert "浦发银行" in names
|
|
465
|
+
assert "平安银行" in names
|
|
466
|
+
assert "万科A" in names
|
|
467
|
+
# Should be sorted
|
|
468
|
+
assert names == sorted(names)
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
def test_stock_list_codes_with_duplicates(mock_session):
|
|
472
|
+
"""Test list_codes removes duplicates"""
|
|
473
|
+
data = pd.DataFrame(
|
|
474
|
+
{
|
|
475
|
+
"code": ["sh.600000", "sh.600000", "sz.000001"],
|
|
476
|
+
"tradeStatus": ["1", "1", "1"],
|
|
477
|
+
"code_name": ["浦发银行", "浦发银行", "平安银行"],
|
|
478
|
+
}
|
|
479
|
+
)
|
|
480
|
+
|
|
481
|
+
job = Stock(session=mock_session)
|
|
482
|
+
|
|
483
|
+
with patch.object(job, "_fetchall", return_value=data):
|
|
484
|
+
codes = job.list_codes()
|
|
485
|
+
|
|
486
|
+
# Should return unique codes only
|
|
487
|
+
assert len(codes) == 2
|
|
488
|
+
assert codes.count("sh.600000") == 1
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
def test_stock_list_names_with_duplicates(mock_session):
|
|
492
|
+
"""Test list_names removes duplicates"""
|
|
493
|
+
data = pd.DataFrame(
|
|
494
|
+
{
|
|
495
|
+
"code": ["sh.600000", "sz.000001", "sz.000002"],
|
|
496
|
+
"tradeStatus": ["1", "1", "1"],
|
|
497
|
+
"code_name": ["浦发银行", "平安银行", "平安银行"],
|
|
498
|
+
}
|
|
499
|
+
)
|
|
500
|
+
|
|
501
|
+
job = Stock(session=mock_session)
|
|
502
|
+
|
|
503
|
+
with patch.object(job, "_fetchall", return_value=data):
|
|
504
|
+
names = job.list_names()
|
|
505
|
+
|
|
506
|
+
# Should return unique names only
|
|
507
|
+
assert len(names) == 2
|
|
508
|
+
assert names.count("平安银行") == 1
|