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,881 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Test suite for StockInfo class
|
|
3
|
+
Tests cover initialization, data fetching, transformation, date handling, and utility methods
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from datetime import date, datetime
|
|
7
|
+
from unittest.mock import MagicMock, patch
|
|
8
|
+
|
|
9
|
+
import pandas as pd
|
|
10
|
+
import pytest
|
|
11
|
+
|
|
12
|
+
from xfintech.data.common.cache import Cache
|
|
13
|
+
from xfintech.data.common.coolant import Coolant
|
|
14
|
+
from xfintech.data.common.paginate import Paginate
|
|
15
|
+
from xfintech.data.common.params import Params
|
|
16
|
+
from xfintech.data.common.retry import Retry
|
|
17
|
+
from xfintech.data.source.tushare.session.session import Session
|
|
18
|
+
from xfintech.data.source.tushare.stock.stockinfo.constant import (
|
|
19
|
+
KEY,
|
|
20
|
+
NAME,
|
|
21
|
+
PAGINATE,
|
|
22
|
+
SOURCE,
|
|
23
|
+
TARGET,
|
|
24
|
+
)
|
|
25
|
+
from xfintech.data.source.tushare.stock.stockinfo.stockinfo import StockInfo
|
|
26
|
+
|
|
27
|
+
# ============================================================================
|
|
28
|
+
# Fixtures
|
|
29
|
+
# ============================================================================
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@pytest.fixture
|
|
33
|
+
def mock_session():
|
|
34
|
+
"""Create a mock Tushare session"""
|
|
35
|
+
session = MagicMock(spec=Session)
|
|
36
|
+
session._credential = "test_token"
|
|
37
|
+
session.id = "test1234"
|
|
38
|
+
session.mode = "direct"
|
|
39
|
+
session.relay_url = None
|
|
40
|
+
session.relay_secret = None
|
|
41
|
+
session.connected = True
|
|
42
|
+
|
|
43
|
+
# Mock the connection object
|
|
44
|
+
mock_connection = MagicMock()
|
|
45
|
+
mock_connection.bak_basic = MagicMock()
|
|
46
|
+
session.connection = mock_connection
|
|
47
|
+
|
|
48
|
+
return session
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@pytest.fixture
|
|
52
|
+
def sample_source_data():
|
|
53
|
+
"""Create sample source data in Tushare format"""
|
|
54
|
+
return pd.DataFrame(
|
|
55
|
+
{
|
|
56
|
+
"trade_date": ["20230101", "20230101", "20230102"],
|
|
57
|
+
"ts_code": ["000001.SZ", "000002.SZ", "600000.SH"],
|
|
58
|
+
"name": ["平安银行", "万科A", "浦发银行"],
|
|
59
|
+
"industry": ["银行", "房地产", "银行"],
|
|
60
|
+
"area": ["深圳", "深圳", "上海"],
|
|
61
|
+
"pe": [5.5, 10.2, 6.3],
|
|
62
|
+
"float_share": [100.5, 50.3, 80.2],
|
|
63
|
+
"total_share": [150.2, 80.5, 120.3],
|
|
64
|
+
"total_assets": [1000.5, 500.3, 800.2],
|
|
65
|
+
"liquid_assets": [500.2, 250.1, 400.1],
|
|
66
|
+
"fixed_assets": [300.1, 150.2, 250.3],
|
|
67
|
+
"reserved": [100.5, 50.3, 80.2],
|
|
68
|
+
"reserved_pershare": [0.67, 0.63, 0.67],
|
|
69
|
+
"eps": [1.2, 0.8, 1.1],
|
|
70
|
+
"bvps": [8.5, 6.3, 7.8],
|
|
71
|
+
"pb": [1.5, 1.8, 1.6],
|
|
72
|
+
"list_date": ["19910403", "19910129", "19991110"],
|
|
73
|
+
"undp": [50.2, 25.1, 40.1],
|
|
74
|
+
"per_undp": [0.33, 0.31, 0.33],
|
|
75
|
+
"rev_yoy": [5.5, 10.2, 6.3],
|
|
76
|
+
"profit_yoy": [8.5, 12.3, 9.1],
|
|
77
|
+
"gpr": [30.5, 25.3, 28.2],
|
|
78
|
+
"npr": [20.1, 18.5, 19.3],
|
|
79
|
+
"holder_num": [50000, 30000, 40000],
|
|
80
|
+
}
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@pytest.fixture
|
|
85
|
+
def expected_transformed_data():
|
|
86
|
+
"""Create expected transformed data"""
|
|
87
|
+
return pd.DataFrame(
|
|
88
|
+
{
|
|
89
|
+
"code": ["000001.SZ", "000002.SZ", "600000.SH"],
|
|
90
|
+
"name": ["平安银行", "万科A", "浦发银行"],
|
|
91
|
+
"date": ["2023-01-01", "2023-01-01", "2023-01-02"],
|
|
92
|
+
"datecode": ["20230101", "20230101", "20230102"],
|
|
93
|
+
"list_date": ["1991-04-03", "1991-01-29", "1999-11-10"],
|
|
94
|
+
"list_datecode": ["19910403", "19910129", "19991110"],
|
|
95
|
+
"industry": ["银行", "房地产", "银行"],
|
|
96
|
+
"area": ["深圳", "深圳", "上海"],
|
|
97
|
+
"pe": [5.5, 10.2, 6.3],
|
|
98
|
+
"float_share": [100.5, 50.3, 80.2],
|
|
99
|
+
"total_share": [150.2, 80.5, 120.3],
|
|
100
|
+
"total_assets": [1000.5, 500.3, 800.2],
|
|
101
|
+
"liquid_assets": [500.2, 250.1, 400.1],
|
|
102
|
+
"fixed_assets": [300.1, 150.2, 250.3],
|
|
103
|
+
"reserved": [100.5, 50.3, 80.2],
|
|
104
|
+
"reserved_pershare": [0.67, 0.63, 0.67],
|
|
105
|
+
"eps": [1.2, 0.8, 1.1],
|
|
106
|
+
"bvps": [8.5, 6.3, 7.8],
|
|
107
|
+
"pb": [1.5, 1.8, 1.6],
|
|
108
|
+
"undp": [50.2, 25.1, 40.1],
|
|
109
|
+
"per_undp": [0.33, 0.31, 0.33],
|
|
110
|
+
"rev_yoy": [5.5, 10.2, 6.3],
|
|
111
|
+
"profit_yoy": [8.5, 12.3, 9.1],
|
|
112
|
+
"gpr": [30.5, 25.3, 28.2],
|
|
113
|
+
"npr": [20.1, 18.5, 19.3],
|
|
114
|
+
"holder_num": [50000, 30000, 40000],
|
|
115
|
+
}
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
# ============================================================================
|
|
120
|
+
# Initialization Tests
|
|
121
|
+
# ============================================================================
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def test_stockinfo_init_basic(mock_session):
|
|
125
|
+
"""Test StockInfo initialization with minimal parameters"""
|
|
126
|
+
stockinfo = StockInfo(session=mock_session)
|
|
127
|
+
|
|
128
|
+
assert stockinfo.name == NAME
|
|
129
|
+
assert stockinfo.key == KEY
|
|
130
|
+
assert stockinfo.source == SOURCE
|
|
131
|
+
assert stockinfo.target == TARGET
|
|
132
|
+
assert isinstance(stockinfo.params, Params)
|
|
133
|
+
assert isinstance(stockinfo.coolant, Coolant)
|
|
134
|
+
assert isinstance(stockinfo.paginate, Paginate)
|
|
135
|
+
assert isinstance(stockinfo.retry, Retry)
|
|
136
|
+
assert stockinfo.paginate.pagesize == PAGINATE.pagesize
|
|
137
|
+
assert stockinfo.paginate.pagelimit == PAGINATE.pagelimit
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def test_stockinfo_init_with_params_dict(mock_session):
|
|
141
|
+
"""Test StockInfo initialization with params as dict"""
|
|
142
|
+
params = {"ts_code": "000001.SZ"}
|
|
143
|
+
stockinfo = StockInfo(session=mock_session, params=params)
|
|
144
|
+
|
|
145
|
+
assert isinstance(stockinfo.params, Params)
|
|
146
|
+
assert stockinfo.params.to_dict()["ts_code"] == "000001.SZ"
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def test_stockinfo_init_with_params_object(mock_session):
|
|
150
|
+
"""Test StockInfo initialization with params as Params object"""
|
|
151
|
+
params = Params(ts_code="000001.SZ")
|
|
152
|
+
stockinfo = StockInfo(session=mock_session, params=params)
|
|
153
|
+
|
|
154
|
+
assert isinstance(stockinfo.params, Params)
|
|
155
|
+
assert stockinfo.params.to_dict()["ts_code"] == "000001.SZ"
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def test_stockinfo_init_with_trade_date_param(mock_session):
|
|
159
|
+
"""Test StockInfo initialization with trade_date param"""
|
|
160
|
+
params = {"trade_date": "20230101"}
|
|
161
|
+
stockinfo = StockInfo(session=mock_session, params=params)
|
|
162
|
+
|
|
163
|
+
assert stockinfo.params.to_dict()["trade_date"] == "20230101"
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def test_stockinfo_init_with_ts_code_param(mock_session):
|
|
167
|
+
"""Test StockInfo initialization with ts_code param"""
|
|
168
|
+
params = {"ts_code": "000001.SZ"}
|
|
169
|
+
stockinfo = StockInfo(session=mock_session, params=params)
|
|
170
|
+
|
|
171
|
+
assert stockinfo.params.to_dict()["ts_code"] == "000001.SZ"
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def test_stockinfo_init_with_cache_bool_true(mock_session):
|
|
175
|
+
"""Test StockInfo initialization with cache as bool True"""
|
|
176
|
+
stockinfo = StockInfo(session=mock_session, cache=True)
|
|
177
|
+
|
|
178
|
+
assert isinstance(stockinfo.cache, Cache)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def test_stockinfo_init_with_cache_bool_false(mock_session):
|
|
182
|
+
"""Test StockInfo initialization with cache as bool False"""
|
|
183
|
+
stockinfo = StockInfo(session=mock_session, cache=False)
|
|
184
|
+
|
|
185
|
+
assert stockinfo.cache is None
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def test_stockinfo_init_with_cache_dict(mock_session):
|
|
189
|
+
"""Test StockInfo initialization with cache as dict"""
|
|
190
|
+
cache_config = {"enabled": True, "ttl": 3600}
|
|
191
|
+
stockinfo = StockInfo(session=mock_session, cache=cache_config)
|
|
192
|
+
|
|
193
|
+
assert isinstance(stockinfo.cache, Cache)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def test_stockinfo_init_with_all_params(mock_session):
|
|
197
|
+
"""Test StockInfo initialization with all parameters"""
|
|
198
|
+
params = {"ts_code": "000001.SZ", "trade_date": "20230101"}
|
|
199
|
+
coolant = {"enabled": True, "interval": 1.0}
|
|
200
|
+
retry = {"max_attempts": 3, "backoff": 2.0}
|
|
201
|
+
cache = {"enabled": True, "ttl": 3600}
|
|
202
|
+
|
|
203
|
+
stockinfo = StockInfo(
|
|
204
|
+
session=mock_session,
|
|
205
|
+
params=params,
|
|
206
|
+
coolant=coolant,
|
|
207
|
+
retry=retry,
|
|
208
|
+
cache=cache,
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
assert stockinfo.params.to_dict()["ts_code"] == "000001.SZ"
|
|
212
|
+
assert isinstance(stockinfo.coolant, Coolant)
|
|
213
|
+
assert isinstance(stockinfo.retry, Retry)
|
|
214
|
+
assert isinstance(stockinfo.cache, Cache)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def test_stockinfo_constants():
|
|
218
|
+
"""Test that constants are properly defined"""
|
|
219
|
+
assert NAME == "stockinfo"
|
|
220
|
+
assert KEY == "/tushare/stockinfo"
|
|
221
|
+
assert PAGINATE.pagesize == 7000
|
|
222
|
+
assert SOURCE is not None
|
|
223
|
+
assert TARGET is not None
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
# ============================================================================
|
|
227
|
+
# Transform Tests
|
|
228
|
+
# ============================================================================
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def test_stockinfo_transform_basic(mock_session, sample_source_data):
|
|
232
|
+
"""Test basic transform functionality"""
|
|
233
|
+
stockinfo = StockInfo(session=mock_session)
|
|
234
|
+
result = stockinfo.transform(sample_source_data)
|
|
235
|
+
|
|
236
|
+
assert isinstance(result, pd.DataFrame)
|
|
237
|
+
assert len(result) == 3
|
|
238
|
+
assert list(result.columns) == stockinfo.target.list_column_names()
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def test_stockinfo_transform_code_mapping(mock_session, sample_source_data):
|
|
242
|
+
"""Test that ts_code is mapped to code"""
|
|
243
|
+
stockinfo = StockInfo(session=mock_session)
|
|
244
|
+
result = stockinfo.transform(sample_source_data)
|
|
245
|
+
|
|
246
|
+
assert "code" in result.columns
|
|
247
|
+
assert result["code"].tolist() == ["000001.SZ", "000002.SZ", "600000.SH"]
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def test_stockinfo_transform_name_mapping(mock_session, sample_source_data):
|
|
251
|
+
"""Test that name is preserved"""
|
|
252
|
+
stockinfo = StockInfo(session=mock_session)
|
|
253
|
+
result = stockinfo.transform(sample_source_data)
|
|
254
|
+
|
|
255
|
+
assert "name" in result.columns
|
|
256
|
+
assert result["name"].tolist() == ["平安银行", "万科A", "浦发银行"]
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def test_stockinfo_transform_date_format(mock_session, sample_source_data):
|
|
260
|
+
"""Test that trade_date is formatted correctly"""
|
|
261
|
+
stockinfo = StockInfo(session=mock_session)
|
|
262
|
+
result = stockinfo.transform(sample_source_data)
|
|
263
|
+
|
|
264
|
+
assert "date" in result.columns
|
|
265
|
+
assert result["date"].dtype == object
|
|
266
|
+
assert result["date"].iloc[0] == "2023-01-01"
|
|
267
|
+
assert result["date"].iloc[2] == "2023-01-02"
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def test_stockinfo_transform_datecode_preserved(mock_session, sample_source_data):
|
|
271
|
+
"""Test that datecode preserves original format"""
|
|
272
|
+
stockinfo = StockInfo(session=mock_session)
|
|
273
|
+
result = stockinfo.transform(sample_source_data)
|
|
274
|
+
|
|
275
|
+
assert "datecode" in result.columns
|
|
276
|
+
assert result["datecode"].tolist() == ["20230101", "20230101", "20230102"]
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def test_stockinfo_transform_list_date_format(mock_session, sample_source_data):
|
|
280
|
+
"""Test that list_date is formatted correctly"""
|
|
281
|
+
stockinfo = StockInfo(session=mock_session)
|
|
282
|
+
result = stockinfo.transform(sample_source_data)
|
|
283
|
+
|
|
284
|
+
assert "list_date" in result.columns
|
|
285
|
+
assert result["list_date"].dtype == object
|
|
286
|
+
assert result["list_date"].iloc[0] == "1991-04-03"
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def test_stockinfo_transform_list_datecode_preserved(mock_session, sample_source_data):
|
|
290
|
+
"""Test that list_datecode preserves original format"""
|
|
291
|
+
stockinfo = StockInfo(session=mock_session)
|
|
292
|
+
result = stockinfo.transform(sample_source_data)
|
|
293
|
+
|
|
294
|
+
assert "list_datecode" in result.columns
|
|
295
|
+
assert result["list_datecode"].tolist() == ["19910403", "19910129", "19991110"]
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def test_stockinfo_transform_numeric_conversions(mock_session, sample_source_data):
|
|
299
|
+
"""Test that numeric fields are converted correctly"""
|
|
300
|
+
stockinfo = StockInfo(session=mock_session)
|
|
301
|
+
result = stockinfo.transform(sample_source_data)
|
|
302
|
+
|
|
303
|
+
# Check numeric fields
|
|
304
|
+
numeric_fields = ["pe", "float_share", "total_share", "eps", "bvps", "pb"]
|
|
305
|
+
for field in numeric_fields:
|
|
306
|
+
assert field in result.columns
|
|
307
|
+
assert pd.api.types.is_numeric_dtype(result[field])
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def test_stockinfo_transform_string_fields(mock_session, sample_source_data):
|
|
311
|
+
"""Test that string fields are converted correctly"""
|
|
312
|
+
stockinfo = StockInfo(session=mock_session)
|
|
313
|
+
result = stockinfo.transform(sample_source_data)
|
|
314
|
+
|
|
315
|
+
assert result["industry"].tolist() == ["银行", "房地产", "银行"]
|
|
316
|
+
assert result["area"].tolist() == ["深圳", "深圳", "上海"]
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def test_stockinfo_transform_empty_dataframe(mock_session):
|
|
320
|
+
"""Test transform with empty DataFrame"""
|
|
321
|
+
stockinfo = StockInfo(session=mock_session)
|
|
322
|
+
empty_df = pd.DataFrame()
|
|
323
|
+
result = stockinfo.transform(empty_df)
|
|
324
|
+
|
|
325
|
+
assert isinstance(result, pd.DataFrame)
|
|
326
|
+
assert len(result) == 0
|
|
327
|
+
assert list(result.columns) == stockinfo.target.list_column_names()
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def test_stockinfo_transform_none_input(mock_session):
|
|
331
|
+
"""Test transform with None input"""
|
|
332
|
+
stockinfo = StockInfo(session=mock_session)
|
|
333
|
+
result = stockinfo.transform(None)
|
|
334
|
+
|
|
335
|
+
assert isinstance(result, pd.DataFrame)
|
|
336
|
+
assert len(result) == 0
|
|
337
|
+
assert list(result.columns) == stockinfo.target.list_column_names()
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def test_stockinfo_transform_handles_invalid_dates(mock_session):
|
|
341
|
+
"""Test that transform handles invalid dates gracefully"""
|
|
342
|
+
stockinfo = StockInfo(session=mock_session)
|
|
343
|
+
data = pd.DataFrame(
|
|
344
|
+
{
|
|
345
|
+
"trade_date": ["invalid", "20230101"],
|
|
346
|
+
"ts_code": ["000001.SZ", "000002.SZ"],
|
|
347
|
+
"name": ["平安银行", "万科A"],
|
|
348
|
+
"industry": ["银行", "房地产"],
|
|
349
|
+
"area": ["深圳", "深圳"],
|
|
350
|
+
"pe": [5.5, 10.2],
|
|
351
|
+
"float_share": [100.5, 50.3],
|
|
352
|
+
"total_share": [150.2, 80.5],
|
|
353
|
+
"total_assets": [1000.5, 500.3],
|
|
354
|
+
"liquid_assets": [500.2, 250.1],
|
|
355
|
+
"fixed_assets": [300.1, 150.2],
|
|
356
|
+
"reserved": [100.5, 50.3],
|
|
357
|
+
"reserved_pershare": [0.67, 0.63],
|
|
358
|
+
"eps": [1.2, 0.8],
|
|
359
|
+
"bvps": [8.5, 6.3],
|
|
360
|
+
"pb": [1.5, 1.8],
|
|
361
|
+
"list_date": ["19910403", "19910129"],
|
|
362
|
+
"undp": [50.2, 25.1],
|
|
363
|
+
"per_undp": [0.33, 0.31],
|
|
364
|
+
"rev_yoy": [5.5, 10.2],
|
|
365
|
+
"profit_yoy": [8.5, 12.3],
|
|
366
|
+
"gpr": [30.5, 25.3],
|
|
367
|
+
"npr": [20.1, 18.5],
|
|
368
|
+
"holder_num": [50000, 30000],
|
|
369
|
+
}
|
|
370
|
+
)
|
|
371
|
+
result = stockinfo.transform(data)
|
|
372
|
+
|
|
373
|
+
assert len(result) == 2
|
|
374
|
+
assert pd.isna(result.loc[result["code"] == "000001.SZ", "date"].iloc[0])
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def test_stockinfo_transform_removes_duplicates(mock_session, sample_source_data):
|
|
378
|
+
"""Test that transform removes duplicate rows"""
|
|
379
|
+
stockinfo = StockInfo(session=mock_session)
|
|
380
|
+
# Add duplicate rows
|
|
381
|
+
duplicated_data = pd.concat([sample_source_data, sample_source_data.iloc[[0]]])
|
|
382
|
+
result = stockinfo.transform(duplicated_data)
|
|
383
|
+
|
|
384
|
+
assert len(result) == 3 # Original length without duplicates
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def test_stockinfo_transform_sorts_by_code(mock_session):
|
|
388
|
+
"""Test that transform sorts by code"""
|
|
389
|
+
stockinfo = StockInfo(session=mock_session)
|
|
390
|
+
data = pd.DataFrame(
|
|
391
|
+
{
|
|
392
|
+
"trade_date": ["20230101", "20230101", "20230101"],
|
|
393
|
+
"ts_code": ["600000.SH", "000001.SZ", "000002.SZ"],
|
|
394
|
+
"name": ["浦发银行", "平安银行", "万科A"],
|
|
395
|
+
"industry": ["银行", "银行", "房地产"],
|
|
396
|
+
"area": ["上海", "深圳", "深圳"],
|
|
397
|
+
"pe": [6.3, 5.5, 10.2],
|
|
398
|
+
"float_share": [80.2, 100.5, 50.3],
|
|
399
|
+
"total_share": [120.3, 150.2, 80.5],
|
|
400
|
+
"total_assets": [800.2, 1000.5, 500.3],
|
|
401
|
+
"liquid_assets": [400.1, 500.2, 250.1],
|
|
402
|
+
"fixed_assets": [250.3, 300.1, 150.2],
|
|
403
|
+
"reserved": [80.2, 100.5, 50.3],
|
|
404
|
+
"reserved_pershare": [0.67, 0.67, 0.63],
|
|
405
|
+
"eps": [1.1, 1.2, 0.8],
|
|
406
|
+
"bvps": [7.8, 8.5, 6.3],
|
|
407
|
+
"pb": [1.6, 1.5, 1.8],
|
|
408
|
+
"list_date": ["19991110", "19910403", "19910129"],
|
|
409
|
+
"undp": [40.1, 50.2, 25.1],
|
|
410
|
+
"per_undp": [0.33, 0.33, 0.31],
|
|
411
|
+
"rev_yoy": [6.3, 5.5, 10.2],
|
|
412
|
+
"profit_yoy": [9.1, 8.5, 12.3],
|
|
413
|
+
"gpr": [28.2, 30.5, 25.3],
|
|
414
|
+
"npr": [19.3, 20.1, 18.5],
|
|
415
|
+
"holder_num": [40000, 50000, 30000],
|
|
416
|
+
}
|
|
417
|
+
)
|
|
418
|
+
result = stockinfo.transform(data)
|
|
419
|
+
|
|
420
|
+
# Should be sorted by code
|
|
421
|
+
assert result["code"].tolist() == ["000001.SZ", "000002.SZ", "600000.SH"]
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
def test_stockinfo_transform_resets_index(mock_session, sample_source_data):
|
|
425
|
+
"""Test that transform resets the index"""
|
|
426
|
+
stockinfo = StockInfo(session=mock_session)
|
|
427
|
+
result = stockinfo.transform(sample_source_data)
|
|
428
|
+
|
|
429
|
+
assert result.index.tolist() == [0, 1, 2]
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
def test_stockinfo_transform_only_target_columns(mock_session, sample_source_data):
|
|
433
|
+
"""Test that transform only includes target columns"""
|
|
434
|
+
stockinfo = StockInfo(session=mock_session)
|
|
435
|
+
result = stockinfo.transform(sample_source_data)
|
|
436
|
+
|
|
437
|
+
expected_columns = stockinfo.target.list_column_names()
|
|
438
|
+
assert list(result.columns) == expected_columns
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
# ============================================================================
|
|
442
|
+
# Run Tests
|
|
443
|
+
# ============================================================================
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
def test_stockinfo_run_with_cache_hit(mock_session, sample_source_data):
|
|
447
|
+
"""Test run method with cache hit"""
|
|
448
|
+
stockinfo = StockInfo(session=mock_session, cache=True)
|
|
449
|
+
|
|
450
|
+
# Pre-populate cache
|
|
451
|
+
cached_data = stockinfo.transform(sample_source_data)
|
|
452
|
+
stockinfo.cache.set(stockinfo.params.identifier, cached_data)
|
|
453
|
+
|
|
454
|
+
# Run should return cached data
|
|
455
|
+
with patch.object(stockinfo, "_fetchall") as mock_fetchall:
|
|
456
|
+
result = stockinfo.run()
|
|
457
|
+
mock_fetchall.assert_not_called()
|
|
458
|
+
|
|
459
|
+
assert len(result) == 3
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
def test_stockinfo_run_basic_date(mock_session, sample_source_data):
|
|
463
|
+
"""Test run with basic trade_date parameter"""
|
|
464
|
+
stockinfo = StockInfo(session=mock_session, params={"trade_date": "20230101"})
|
|
465
|
+
|
|
466
|
+
with patch.object(stockinfo, "_fetchall", return_value=sample_source_data):
|
|
467
|
+
result = stockinfo.run()
|
|
468
|
+
|
|
469
|
+
assert len(result) == 3
|
|
470
|
+
assert isinstance(result, pd.DataFrame)
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
def test_stockinfo_run_with_trade_date_string(mock_session, sample_source_data):
|
|
474
|
+
"""Test run with trade_date as string"""
|
|
475
|
+
stockinfo = StockInfo(session=mock_session, params={"trade_date": "20230101"})
|
|
476
|
+
|
|
477
|
+
with patch.object(stockinfo, "_fetchall", return_value=sample_source_data) as mock_fetchall:
|
|
478
|
+
stockinfo.run()
|
|
479
|
+
|
|
480
|
+
# Check that trade_date is preserved as string
|
|
481
|
+
call_kwargs = mock_fetchall.call_args[1]
|
|
482
|
+
assert call_kwargs["trade_date"] == "20230101"
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
def test_stockinfo_run_with_trade_date_datetime(mock_session, sample_source_data):
|
|
486
|
+
"""Test run with trade_date as datetime object"""
|
|
487
|
+
trade_date = datetime(2023, 1, 1)
|
|
488
|
+
stockinfo = StockInfo(session=mock_session, params={"trade_date": trade_date})
|
|
489
|
+
|
|
490
|
+
with patch.object(stockinfo, "_fetchall", return_value=sample_source_data) as mock_fetchall:
|
|
491
|
+
stockinfo.run()
|
|
492
|
+
|
|
493
|
+
# Check that datetime was converted to string format YYYYMMDD
|
|
494
|
+
call_kwargs = mock_fetchall.call_args[1]
|
|
495
|
+
assert call_kwargs["trade_date"] == "20230101"
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
def test_stockinfo_run_with_ts_code_param(mock_session, sample_source_data):
|
|
499
|
+
"""Test run with ts_code parameter"""
|
|
500
|
+
stockinfo = StockInfo(session=mock_session, params={"ts_code": "000001.SZ"})
|
|
501
|
+
|
|
502
|
+
with patch.object(stockinfo, "_fetchall", return_value=sample_source_data) as mock_fetchall:
|
|
503
|
+
stockinfo.run()
|
|
504
|
+
|
|
505
|
+
call_kwargs = mock_fetchall.call_args[1]
|
|
506
|
+
assert call_kwargs["ts_code"] == "000001.SZ"
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
def test_stockinfo_run_adds_fields_param(mock_session, sample_source_data):
|
|
510
|
+
"""Test that run adds fields parameter if not present"""
|
|
511
|
+
stockinfo = StockInfo(session=mock_session)
|
|
512
|
+
|
|
513
|
+
with patch.object(stockinfo, "_fetchall", return_value=sample_source_data) as mock_fetchall:
|
|
514
|
+
stockinfo.run()
|
|
515
|
+
|
|
516
|
+
call_kwargs = mock_fetchall.call_args[1]
|
|
517
|
+
assert "fields" in call_kwargs
|
|
518
|
+
assert isinstance(call_kwargs["fields"], str)
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
def test_stockinfo_run_sets_cache(mock_session, sample_source_data):
|
|
522
|
+
"""Test that run sets cache after fetching"""
|
|
523
|
+
stockinfo = StockInfo(session=mock_session, cache=True)
|
|
524
|
+
|
|
525
|
+
with patch.object(stockinfo, "_fetchall", return_value=sample_source_data):
|
|
526
|
+
result = stockinfo.run()
|
|
527
|
+
|
|
528
|
+
cached = stockinfo.cache.get(stockinfo.params.identifier)
|
|
529
|
+
assert cached is not None
|
|
530
|
+
assert len(cached) == len(result)
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
def test_stockinfo_run_calls_transform(mock_session, sample_source_data):
|
|
534
|
+
"""Test that run calls transform method"""
|
|
535
|
+
stockinfo = StockInfo(session=mock_session)
|
|
536
|
+
|
|
537
|
+
with patch.object(stockinfo, "_fetchall", return_value=sample_source_data):
|
|
538
|
+
with patch.object(stockinfo, "transform", wraps=stockinfo.transform) as mock_transform:
|
|
539
|
+
stockinfo.run()
|
|
540
|
+
mock_transform.assert_called_once()
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
def test_stockinfo_run_with_trade_date_as_date(mock_session, sample_source_data):
|
|
544
|
+
"""Test run with trade_date as date object (not datetime)"""
|
|
545
|
+
trade_date = date(2023, 1, 1)
|
|
546
|
+
stockinfo = StockInfo(session=mock_session, params={"trade_date": trade_date})
|
|
547
|
+
|
|
548
|
+
with patch.object(stockinfo, "_fetchall", return_value=sample_source_data) as mock_fetchall:
|
|
549
|
+
stockinfo.run()
|
|
550
|
+
|
|
551
|
+
# Check that date was converted to string format YYYYMMDD
|
|
552
|
+
call_kwargs = mock_fetchall.call_args[1]
|
|
553
|
+
assert call_kwargs["trade_date"] == "20230101"
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
# ============================================================================
|
|
557
|
+
# List Methods Tests
|
|
558
|
+
# ============================================================================
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
def test_stockinfo_list_codes_basic(mock_session, sample_source_data):
|
|
562
|
+
"""Test list_codes returns list of stock codes"""
|
|
563
|
+
stockinfo = StockInfo(session=mock_session, cache=True)
|
|
564
|
+
|
|
565
|
+
with patch.object(stockinfo, "_fetchall", return_value=sample_source_data):
|
|
566
|
+
codes = stockinfo.list_codes()
|
|
567
|
+
|
|
568
|
+
assert isinstance(codes, list)
|
|
569
|
+
assert len(codes) == 3
|
|
570
|
+
assert "000001.SZ" in codes
|
|
571
|
+
assert "000002.SZ" in codes
|
|
572
|
+
assert "600000.SH" in codes
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
def test_stockinfo_list_codes_unique(mock_session):
|
|
576
|
+
"""Test list_codes returns unique codes"""
|
|
577
|
+
stockinfo = StockInfo(session=mock_session, cache=False) # Disable cache
|
|
578
|
+
|
|
579
|
+
# Create data with duplicate codes
|
|
580
|
+
data = pd.DataFrame(
|
|
581
|
+
{
|
|
582
|
+
"trade_date": ["20230101", "20230102", "20230101"],
|
|
583
|
+
"ts_code": ["000001.SZ", "000001.SZ", "000002.SZ"],
|
|
584
|
+
"name": ["平安银行", "平安银行", "万科A"],
|
|
585
|
+
"industry": ["银行", "银行", "房地产"],
|
|
586
|
+
"area": ["深圳", "深圳", "深圳"],
|
|
587
|
+
"pe": [5.5, 5.6, 10.2],
|
|
588
|
+
"float_share": [100.5, 100.5, 50.3],
|
|
589
|
+
"total_share": [150.2, 150.2, 80.5],
|
|
590
|
+
"total_assets": [1000.5, 1000.5, 500.3],
|
|
591
|
+
"liquid_assets": [500.2, 500.2, 250.1],
|
|
592
|
+
"fixed_assets": [300.1, 300.1, 150.2],
|
|
593
|
+
"reserved": [100.5, 100.5, 50.3],
|
|
594
|
+
"reserved_pershare": [0.67, 0.67, 0.63],
|
|
595
|
+
"eps": [1.2, 1.2, 0.8],
|
|
596
|
+
"bvps": [8.5, 8.5, 6.3],
|
|
597
|
+
"pb": [1.5, 1.5, 1.8],
|
|
598
|
+
"list_date": ["19910403", "19910403", "19910129"],
|
|
599
|
+
"undp": [50.2, 50.2, 25.1],
|
|
600
|
+
"per_undp": [0.33, 0.33, 0.31],
|
|
601
|
+
"rev_yoy": [5.5, 5.5, 10.2],
|
|
602
|
+
"profit_yoy": [8.5, 8.5, 12.3],
|
|
603
|
+
"gpr": [30.5, 30.5, 25.3],
|
|
604
|
+
"npr": [20.1, 20.1, 18.5],
|
|
605
|
+
"holder_num": [50000, 50000, 30000],
|
|
606
|
+
}
|
|
607
|
+
)
|
|
608
|
+
|
|
609
|
+
with patch.object(stockinfo, "_fetchall", return_value=data):
|
|
610
|
+
codes = stockinfo.list_codes()
|
|
611
|
+
|
|
612
|
+
assert len(codes) == 2 # Only unique codes
|
|
613
|
+
assert codes == ["000001.SZ", "000002.SZ"]
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
def test_stockinfo_list_codes_sorted(mock_session, sample_source_data):
|
|
617
|
+
"""Test list_codes returns sorted codes"""
|
|
618
|
+
stockinfo = StockInfo(session=mock_session, cache=True)
|
|
619
|
+
|
|
620
|
+
with patch.object(stockinfo, "_fetchall", return_value=sample_source_data):
|
|
621
|
+
codes = stockinfo.list_codes()
|
|
622
|
+
|
|
623
|
+
assert codes == sorted(codes)
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
def test_stockinfo_list_codes_calls_run_when_not_cached(mock_session, sample_source_data):
|
|
627
|
+
"""Test list_codes calls run when cache is empty"""
|
|
628
|
+
stockinfo = StockInfo(session=mock_session, cache=True)
|
|
629
|
+
|
|
630
|
+
with patch.object(stockinfo, "_fetchall", return_value=sample_source_data):
|
|
631
|
+
codes = stockinfo.list_codes()
|
|
632
|
+
|
|
633
|
+
assert len(codes) > 0
|
|
634
|
+
|
|
635
|
+
|
|
636
|
+
def test_stockinfo_list_names_basic(mock_session, sample_source_data):
|
|
637
|
+
"""Test list_names returns list of stock names"""
|
|
638
|
+
stockinfo = StockInfo(session=mock_session, cache=True)
|
|
639
|
+
|
|
640
|
+
with patch.object(stockinfo, "_fetchall", return_value=sample_source_data):
|
|
641
|
+
names = stockinfo.list_names()
|
|
642
|
+
|
|
643
|
+
assert isinstance(names, list)
|
|
644
|
+
assert len(names) == 3
|
|
645
|
+
assert "平安银行" in names
|
|
646
|
+
assert "万科A" in names
|
|
647
|
+
assert "浦发银行" in names
|
|
648
|
+
|
|
649
|
+
|
|
650
|
+
def test_stockinfo_list_names_sorted(mock_session, sample_source_data):
|
|
651
|
+
"""Test list_names returns sorted names"""
|
|
652
|
+
stockinfo = StockInfo(session=mock_session, cache=True)
|
|
653
|
+
|
|
654
|
+
with patch.object(stockinfo, "_fetchall", return_value=sample_source_data):
|
|
655
|
+
names = stockinfo.list_names()
|
|
656
|
+
|
|
657
|
+
assert names == sorted(names)
|
|
658
|
+
|
|
659
|
+
|
|
660
|
+
def test_stockinfo_list_names_unique(mock_session):
|
|
661
|
+
"""Test list_names returns unique names"""
|
|
662
|
+
stockinfo = StockInfo(session=mock_session, cache=False) # Disable cache for this test
|
|
663
|
+
|
|
664
|
+
# Create data with duplicate names
|
|
665
|
+
data = pd.DataFrame(
|
|
666
|
+
{
|
|
667
|
+
"trade_date": ["20230101", "20230102", "20230101"],
|
|
668
|
+
"ts_code": ["000001.SZ", "000001.SZ", "000002.SZ"],
|
|
669
|
+
"name": ["平安银行", "平安银行", "万科A"],
|
|
670
|
+
"industry": ["银行", "银行", "房地产"],
|
|
671
|
+
"area": ["深圳", "深圳", "深圳"],
|
|
672
|
+
"pe": [5.5, 5.6, 10.2],
|
|
673
|
+
"float_share": [100.5, 100.5, 50.3],
|
|
674
|
+
"total_share": [150.2, 150.2, 80.5],
|
|
675
|
+
"total_assets": [1000.5, 1000.5, 500.3],
|
|
676
|
+
"liquid_assets": [500.2, 500.2, 250.1],
|
|
677
|
+
"fixed_assets": [300.1, 300.1, 150.2],
|
|
678
|
+
"reserved": [100.5, 100.5, 50.3],
|
|
679
|
+
"reserved_pershare": [0.67, 0.67, 0.63],
|
|
680
|
+
"eps": [1.2, 1.2, 0.8],
|
|
681
|
+
"bvps": [8.5, 8.5, 6.3],
|
|
682
|
+
"pb": [1.5, 1.5, 1.8],
|
|
683
|
+
"list_date": ["19910403", "19910403", "19910129"],
|
|
684
|
+
"undp": [50.2, 50.2, 25.1],
|
|
685
|
+
"per_undp": [0.33, 0.33, 0.31],
|
|
686
|
+
"rev_yoy": [5.5, 5.5, 10.2],
|
|
687
|
+
"profit_yoy": [8.5, 8.5, 12.3],
|
|
688
|
+
"gpr": [30.5, 30.5, 25.3],
|
|
689
|
+
"npr": [20.1, 20.1, 18.5],
|
|
690
|
+
"holder_num": [50000, 50000, 30000],
|
|
691
|
+
}
|
|
692
|
+
)
|
|
693
|
+
|
|
694
|
+
with patch.object(stockinfo, "_fetchall", return_value=data):
|
|
695
|
+
names = stockinfo.list_names()
|
|
696
|
+
|
|
697
|
+
assert len(names) == 2 # Only unique names
|
|
698
|
+
|
|
699
|
+
|
|
700
|
+
def test_stockinfo_list_names_calls_run_when_not_cached(mock_session, sample_source_data):
|
|
701
|
+
"""Test list_names calls run when cache is empty"""
|
|
702
|
+
stockinfo = StockInfo(session=mock_session, cache=True)
|
|
703
|
+
|
|
704
|
+
with patch.object(stockinfo, "_fetchall", return_value=sample_source_data):
|
|
705
|
+
names = stockinfo.list_names()
|
|
706
|
+
|
|
707
|
+
assert len(names) > 0
|
|
708
|
+
|
|
709
|
+
|
|
710
|
+
# ============================================================================
|
|
711
|
+
# Integration Tests
|
|
712
|
+
# ============================================================================
|
|
713
|
+
|
|
714
|
+
|
|
715
|
+
def test_stockinfo_full_workflow(mock_session, sample_source_data):
|
|
716
|
+
"""Test complete workflow from initialization to data retrieval"""
|
|
717
|
+
stockinfo = StockInfo(
|
|
718
|
+
session=mock_session,
|
|
719
|
+
params={"trade_date": "20230101"},
|
|
720
|
+
cache=True,
|
|
721
|
+
)
|
|
722
|
+
|
|
723
|
+
with patch.object(stockinfo, "_fetchall", return_value=sample_source_data):
|
|
724
|
+
# First run - should fetch from source
|
|
725
|
+
result1 = stockinfo.run()
|
|
726
|
+
assert len(result1) == 3
|
|
727
|
+
|
|
728
|
+
# Second run - should use cache
|
|
729
|
+
result2 = stockinfo.run()
|
|
730
|
+
assert len(result2) == 3
|
|
731
|
+
assert result1.equals(result2)
|
|
732
|
+
|
|
733
|
+
# List methods should work with cached data
|
|
734
|
+
codes = stockinfo.list_codes()
|
|
735
|
+
names = stockinfo.list_names()
|
|
736
|
+
assert len(codes) == 3
|
|
737
|
+
assert len(names) == 3
|
|
738
|
+
|
|
739
|
+
|
|
740
|
+
def test_stockinfo_cache_persistence(mock_session, sample_source_data):
|
|
741
|
+
"""Test that cache persists across runs"""
|
|
742
|
+
stockinfo = StockInfo(session=mock_session, cache=True)
|
|
743
|
+
|
|
744
|
+
with patch.object(stockinfo, "_load_cache", return_value=None) as mock_load:
|
|
745
|
+
with patch.object(stockinfo, "_fetchall", return_value=sample_source_data) as mock_fetchall:
|
|
746
|
+
# First run - fetches data and caches it
|
|
747
|
+
result1 = stockinfo.run()
|
|
748
|
+
assert mock_fetchall.call_count == 1
|
|
749
|
+
assert mock_load.call_count == 1
|
|
750
|
+
|
|
751
|
+
# Second run - _load_cache still returns None because we're mocking it
|
|
752
|
+
result2 = stockinfo.run()
|
|
753
|
+
assert mock_fetchall.call_count == 2 # Called again since cache mock returns None
|
|
754
|
+
assert mock_load.call_count == 2
|
|
755
|
+
|
|
756
|
+
pd.testing.assert_frame_equal(result1, result2)
|
|
757
|
+
|
|
758
|
+
|
|
759
|
+
def test_stockinfo_params_identifier_uniqueness(mock_session):
|
|
760
|
+
"""Test that different params create different cache keys"""
|
|
761
|
+
stockinfo1 = StockInfo(session=mock_session, params={"trade_date": "20230101"}, cache=True)
|
|
762
|
+
stockinfo2 = StockInfo(session=mock_session, params={"trade_date": "20230102"}, cache=True)
|
|
763
|
+
|
|
764
|
+
assert stockinfo1.params.identifier != stockinfo2.params.identifier
|
|
765
|
+
|
|
766
|
+
|
|
767
|
+
def test_stockinfo_different_date_params(mock_session, sample_source_data):
|
|
768
|
+
"""Test handling of different date parameters"""
|
|
769
|
+
stockinfo1 = StockInfo(session=mock_session, params={"trade_date": "20230101"})
|
|
770
|
+
stockinfo2 = StockInfo(session=mock_session, params={"trade_date": datetime(2023, 1, 2)})
|
|
771
|
+
|
|
772
|
+
with patch.object(stockinfo1, "_fetchall", return_value=sample_source_data):
|
|
773
|
+
result1 = stockinfo1.run()
|
|
774
|
+
|
|
775
|
+
with patch.object(stockinfo2, "_fetchall", return_value=sample_source_data):
|
|
776
|
+
result2 = stockinfo2.run()
|
|
777
|
+
|
|
778
|
+
assert len(result1) == 3
|
|
779
|
+
assert len(result2) == 3
|
|
780
|
+
|
|
781
|
+
|
|
782
|
+
def test_stockinfo_empty_result_handling(mock_session):
|
|
783
|
+
"""Test handling of empty results from API"""
|
|
784
|
+
stockinfo = StockInfo(session=mock_session)
|
|
785
|
+
empty_df = pd.DataFrame()
|
|
786
|
+
|
|
787
|
+
with patch.object(stockinfo, "_fetchall", return_value=empty_df):
|
|
788
|
+
result = stockinfo.run()
|
|
789
|
+
|
|
790
|
+
assert len(result) == 0
|
|
791
|
+
assert list(result.columns) == stockinfo.target.list_column_names()
|
|
792
|
+
|
|
793
|
+
|
|
794
|
+
def test_stockinfo_large_dataset_handling(mock_session):
|
|
795
|
+
"""Test handling of large datasets"""
|
|
796
|
+
stockinfo = StockInfo(session=mock_session)
|
|
797
|
+
|
|
798
|
+
# Create a large dataset
|
|
799
|
+
large_data = pd.DataFrame(
|
|
800
|
+
{
|
|
801
|
+
"trade_date": ["20230101"] * 1000,
|
|
802
|
+
"ts_code": [f"{str(i).zfill(6)}.SZ" for i in range(1000)],
|
|
803
|
+
"name": [f"股票{i}" for i in range(1000)],
|
|
804
|
+
"industry": ["银行"] * 1000,
|
|
805
|
+
"area": ["深圳"] * 1000,
|
|
806
|
+
"pe": [5.5] * 1000,
|
|
807
|
+
"float_share": [100.5] * 1000,
|
|
808
|
+
"total_share": [150.2] * 1000,
|
|
809
|
+
"total_assets": [1000.5] * 1000,
|
|
810
|
+
"liquid_assets": [500.2] * 1000,
|
|
811
|
+
"fixed_assets": [300.1] * 1000,
|
|
812
|
+
"reserved": [100.5] * 1000,
|
|
813
|
+
"reserved_pershare": [0.67] * 1000,
|
|
814
|
+
"eps": [1.2] * 1000,
|
|
815
|
+
"bvps": [8.5] * 1000,
|
|
816
|
+
"pb": [1.5] * 1000,
|
|
817
|
+
"list_date": ["19910403"] * 1000,
|
|
818
|
+
"undp": [50.2] * 1000,
|
|
819
|
+
"per_undp": [0.33] * 1000,
|
|
820
|
+
"rev_yoy": [5.5] * 1000,
|
|
821
|
+
"profit_yoy": [8.5] * 1000,
|
|
822
|
+
"gpr": [30.5] * 1000,
|
|
823
|
+
"npr": [20.1] * 1000,
|
|
824
|
+
"holder_num": [50000] * 1000,
|
|
825
|
+
}
|
|
826
|
+
)
|
|
827
|
+
|
|
828
|
+
with patch.object(stockinfo, "_fetchall", return_value=large_data):
|
|
829
|
+
result = stockinfo.run()
|
|
830
|
+
|
|
831
|
+
assert len(result) == 1000
|
|
832
|
+
|
|
833
|
+
|
|
834
|
+
def test_stockinfo_without_cache(mock_session, sample_source_data):
|
|
835
|
+
"""Test that stockinfo works correctly without cache"""
|
|
836
|
+
stockinfo = StockInfo(session=mock_session, cache=False)
|
|
837
|
+
|
|
838
|
+
with patch.object(stockinfo, "_fetchall", return_value=sample_source_data) as mock_fetchall:
|
|
839
|
+
stockinfo.run()
|
|
840
|
+
stockinfo.run()
|
|
841
|
+
|
|
842
|
+
# Should fetch twice (no caching)
|
|
843
|
+
assert mock_fetchall.call_count == 2
|
|
844
|
+
|
|
845
|
+
|
|
846
|
+
def test_stockinfo_handles_missing_numeric_fields(mock_session):
|
|
847
|
+
"""Test handling of data with missing numeric fields"""
|
|
848
|
+
stockinfo = StockInfo(session=mock_session)
|
|
849
|
+
|
|
850
|
+
# Create data with some missing numeric fields
|
|
851
|
+
data = pd.DataFrame(
|
|
852
|
+
{
|
|
853
|
+
"trade_date": ["20230101"],
|
|
854
|
+
"ts_code": ["000001.SZ"],
|
|
855
|
+
"name": ["平安银行"],
|
|
856
|
+
"industry": ["银行"],
|
|
857
|
+
"area": ["深圳"],
|
|
858
|
+
"pe": [None],
|
|
859
|
+
"float_share": [100.5],
|
|
860
|
+
"total_share": [150.2],
|
|
861
|
+
"total_assets": [1000.5],
|
|
862
|
+
"liquid_assets": [500.2],
|
|
863
|
+
"fixed_assets": [300.1],
|
|
864
|
+
"reserved": [100.5],
|
|
865
|
+
"reserved_pershare": [0.67],
|
|
866
|
+
"eps": [1.2],
|
|
867
|
+
"bvps": [8.5],
|
|
868
|
+
"pb": [1.5],
|
|
869
|
+
"list_date": ["19910403"],
|
|
870
|
+
"undp": [50.2],
|
|
871
|
+
"per_undp": [0.33],
|
|
872
|
+
"rev_yoy": [5.5],
|
|
873
|
+
"profit_yoy": [8.5],
|
|
874
|
+
"gpr": [30.5],
|
|
875
|
+
"npr": [20.1],
|
|
876
|
+
"holder_num": [50000],
|
|
877
|
+
}
|
|
878
|
+
)
|
|
879
|
+
|
|
880
|
+
result = stockinfo.transform(data)
|
|
881
|
+
assert pd.isna(result["pe"].iloc[0])
|