diff --git a/HISTORY.md b/HISTORY.md index 11f7e56..ac0bb31 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -9,6 +9,12 @@ master 专注股票量化数据,为Ai(爱)发电,向阳而生。 +2.3.0 (2024-07-06) +------------------ +1. 新增:股票:百度的概念接口。 +2. 新增:股票:龙虎榜单列表接口。 +3. 修复:股票:资金流,成立日期格式等bug。 + 2.2.0 (2024-07-01) ------------------ 1. 新增:股票:资金流接口。 @@ -24,11 +30,6 @@ master 1. 新增:基金ETF行情接口。 2. 新增:股票股东信息接口。 -1.2.4 (2024-02-02) ------------------- -1. 修复:修复概念返回为空bug。 -2. flag:保卫3000点失败,继续保卫2500点。 - ...... ------------------ diff --git a/README.md b/README.md index c2e324f..e3668ae 100644 --- a/README.md +++ b/README.md @@ -213,6 +213,7 @@ print(res_df) | **热度榜单** | sentiment.hot.pop_rank_100_east | 东方财富人气100榜单 | 来源:[东方财富](http://guba.eastmoney.com/rank/) | | | sentiment.hot.hot_rank_100_ths() | 同花顺热度100排行榜 | 来源:[同花顺](https://dq.10jqka.com.cn/fuyao/hot_list_data/out/hot_list/v1/stock?stock_type=a&type=hour&list_type=normal) | | | sentiment.hot.hot_concept_20_ths() | 同花顺热门概念板块20排行榜 | 来源:[同花顺](https://dq.10jqka.com.cn/fuyao/hot_list_data/out/hot_list/v1/stock?stock_type=a&type=hour&list_type=normal) | +| | sentiment.hot.list_a_list_daily() | 龙虎榜单列表 | 来源:[东方财富](https://data.eastmoney.com/stock/lhb/yyb/10033779.html) | | 其它数据排期中 | TODO | 若您有相关资源可以一起参与贡献 | | ## 三、[数据源](https://adata.30006124.xyz/dataSource.html) diff --git a/adata/__version__.py b/adata/__version__.py index 7445a0e..1bea758 100644 --- a/adata/__version__.py +++ b/adata/__version__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -VERSION = (2, 2, 0) +VERSION = (2, 3, 0) PRERELEASE = None # alpha, beta or rc REVISION = None diff --git a/adata/sentiment/alist.py b/adata/sentiment/alist.py index 4b6418f..09592da 100644 --- a/adata/sentiment/alist.py +++ b/adata/sentiment/alist.py @@ -20,6 +20,10 @@ class AList(BaseThs): """龙虎榜单""" + __A_LIST_DAILY_COLUMNS = ['trade_date', 'short_name', 'stock_code', 'close', 'change_cpt', 'turnover_ratio', + 'a_net_amount', 'a_buy_amount', 'a_sell_amount', 'a_amount', 'amount', + 'net_amount_rate', 'a_amount_rate', 'reason'] + # 东方财富人气榜 def list_a_list_daily(self, report_date=None): """ @@ -35,16 +39,20 @@ def list_a_list_daily(self, report_date=None): # 2. 请求数据 text = requests.request(method='post', url=url).text res = json.loads(text[text.index('{'):-2]) + if res['result'] is None: + return pd.DataFrame() df = pd.DataFrame(res['result']["data"]) - - # 3. 解析封装数据 TODO - rename = {'f2': 'price', 'f3': 'change_pct', 'f12': 'stock_code', 'f14': 'short_name', } - rank_df = pd.rename(columns=rename) - rank_df["change_pct"] = pd.to_numeric(rank_df["change_pct"], errors="coerce") - rank_df["price"] = pd.to_numeric(rank_df["price"], errors="coerce") - rank_df["change"] = rank_df["price"] * rank_df["change_pct"] / 100 - rank_df["rank"] = range(1, len(rank_df) + 1) - return rank_df[["rank", "stock_code", "short_name", "price", "change", "change_pct"]] + # 3. 解析封装数据 + rename = {'SECURITY_CODE': 'stock_code', 'SECURITY_NAME_ABBR': 'short_name', 'TRADE_DATE': 'trade_date', + 'CLOSE_PRICE': 'close', 'CHANGE_RATE': 'change_cpt', 'TURNOVERRATE': 'turnover_ratio', + 'BILLBOARD_NET_AMT': 'a_net_amount', 'BILLBOARD_BUY_AMT': 'a_buy_amount', + 'BILLBOARD_SELL_AMT': 'a_sell_amount', 'BILLBOARD_DEAL_AMT': 'a_amount', + 'ACCUM_AMOUNT': 'amount', 'DEAL_NET_RATIO': 'net_amount_rate', 'DEAL_AMOUNT_RATIO': 'a_amount_rate', + 'EXPLANATION': 'reason', } + df = df.rename(columns=rename) + df['trade_date'] = pd.to_datetime(df['trade_date']).dt.strftime('%Y-%m-%d') + df['short_name'] = df['short_name'].str.replace(' ', '') + return df[self.__A_LIST_DAILY_COLUMNS] def get_a_list(self, stock_code, report_date=None): """ @@ -56,4 +64,5 @@ def get_a_list(self, stock_code, report_date=None): if __name__ == '__main__': - AList().list_a_list_daily() + print(AList().list_a_list_daily(report_date='2024-07-04')) + print(AList().list_a_list_daily()) diff --git a/adata/sentiment/hot.py b/adata/sentiment/hot.py index 465a172..558bf62 100644 --- a/adata/sentiment/hot.py +++ b/adata/sentiment/hot.py @@ -11,12 +11,12 @@ """ import pandas as pd -from adata.common.base.base_ths import BaseThs from adata.common.headers import ths_headers from adata.common.utils import requests +from adata.sentiment.alist import AList -class Hot(BaseThs): +class Hot(AList): """热门榜单""" # 东方财富人气榜 diff --git a/adata/stock/info/concept/stock_concept.py b/adata/stock/info/concept/stock_concept.py index adf01ee..2058fd7 100644 --- a/adata/stock/info/concept/stock_concept.py +++ b/adata/stock/info/concept/stock_concept.py @@ -10,11 +10,12 @@ @author: 1nchaos @date: 2023/3/30 16:17 """ +from adata.stock.info.concept.stock_concept_baidu import StockConceptBaidu from adata.stock.info.concept.stock_concept_east import StockConceptEast from adata.stock.info.concept.stock_concept_ths import StockConceptThs -class StockConcept(StockConceptThs, StockConceptEast): +class StockConcept(StockConceptThs, StockConceptEast, StockConceptBaidu): def __init__(self) -> None: super().__init__() diff --git a/adata/stock/info/concept/stock_concept_baidu.py b/adata/stock/info/concept/stock_concept_baidu.py new file mode 100644 index 0000000..09e5f03 --- /dev/null +++ b/adata/stock/info/concept/stock_concept_baidu.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- +""" +@summary: 股票概念 +东方财富股票概念 + +https://data.eastmoney.com/bkzj/gn.html + +单个股票的所有概念板块 +https://datacenter.eastmoney.com/securities/api/data/v1/get?reportName=RPT_F10_CORETHEME_BOARDTYPE&columns=SECUCODE%2CSECURITY_CODE%2CSECURITY_NAME_ABBR%2CNEW_BOARD_CODE%2CBOARD_NAME%2CSELECTED_BOARD_REASON%2CIS_PRECISE%2CBOARD_RANK%2CBOARD_YIELD%2CDERIVE_BOARD_CODE"eColumns=f3~05~NEW_BOARD_CODE~BOARD_YIELD&filter=(SECUCODE%3D%22600138.SH%22)(IS_PRECISE%3D%221%22)&pageNumber=1&pageSize=&sortTypes=1&sortColumns=BOARD_RANK&source=HSF10&client=PC&v=0029565688091059528 +@author: 1nchaos +@date: 2023/3/30 16:17 +""" + +import json +from urllib.parse import parse_qs + +import pandas as pd + +from adata.common import requests +from adata.common.headers import baidu_headers +from adata.stock.info.concept.stock_concept_template import StockConceptTemplate + + +class StockConceptBaidu(StockConceptTemplate): + """ + 股票概念 + """ + + def __init__(self) -> None: + super().__init__() + + def get_concept_baidu(self, stock_code='000001'): + """ + 根据股票代码获取,股票所属的所有的概念信息 + https://finance.pae.baidu.com/api/getrelatedblock?stock=[{"code":"300059","market":"ab","type":"stock"}]&finClientType=pc + :param stock_code: 股票代码 + :return: 概念信息 + """ + # 1. 请求参数封装 + code_list = [] + if isinstance(stock_code, str): + stock_code = [stock_code] + for code in stock_code: + code_list.append({"code": code, "market": "ab", "type": "stock"}) + url = f"""https://finance.pae.baidu.com/api/getrelatedblock?stock={json.dumps(code_list)}&finClientType=pc""" + res_json = requests.request('get', url, headers=baidu_headers.json_headers, proxies={}).json() + + # 1. 返回结果判断 + if not res_json['Result']: + return pd.DataFrame(data=[], columns=self._CONCEPT_INFO_COLUMNS) + + # 2. 正常返回数据结果封装 + res_json = res_json['Result'] + data = [] + for key, value in res_json.items(): + for concept_type in value: + if concept_type['name'] == '概念': + for _ in concept_type['list']: + data.append({'stock_code': key, 'concept_code': parse_qs(_['xcx_query']).get('code', '')[0], + 'name': _['name'], + 'reason': '', 'source': '百度股市通'}) + result_df = pd.DataFrame(data=data, columns=self._CONCEPT_INFO_COLUMNS) + return result_df + + +if __name__ == '__main__': + print(StockConceptBaidu().get_concept_baidu(stock_code=["600020", '300059', '300033']).to_string()) diff --git a/adata/stock/info/stock_index.py b/adata/stock/info/stock_index.py index 77253e7..3ebd90d 100644 --- a/adata/stock/info/stock_index.py +++ b/adata/stock/info/stock_index.py @@ -101,9 +101,9 @@ def index_constituent(self, index_code=None, wait_time=None): :param wait_time: 等待时间:毫秒;表示每个请求的间隔时间,主要用于防止请求太频繁的限制。 :return: ['index_code', 'stock_code', 'short_name'] """ - # res = self.__index_constituent_sina(index_code=index_code) - # if not res.empty: - # return res + res = self.__index_constituent_baidu(index_code=index_code) + if not res.empty: + return res return self.__index_constituent_ths(index_code=index_code, wait_time=wait_time) def __index_constituent_ths(self, index_code=None, wait_time=None): @@ -245,5 +245,5 @@ def __index_constituent_sina(self, index_code=None, wait_time=None): if __name__ == '__main__': print(StockIndex().all_index_code()) - print(StockIndex().index_constituent(index_code='000033')) - print(StockIndex().index_constituent(index_code='399387', wait_time=158)) + # print(StockIndex().index_constituent(index_code='000033')) + # print(StockIndex().index_constituent(index_code='399387', wait_time=158)) diff --git a/adata/stock/info/stock_info.py b/adata/stock/info/stock_info.py index da06db0..47220ad 100644 --- a/adata/stock/info/stock_info.py +++ b/adata/stock/info/stock_info.py @@ -46,4 +46,4 @@ def get_stock_shares(self, stock_code: str = '000033', is_history=True): if __name__ == '__main__': - print(StockInfo().get_stock_shares(stock_code='600001', is_history=True)) + print(StockInfo().get_stock_shares(stock_code='300033', is_history=True)) diff --git a/tests/adata_test/sentiment/sentiment_hot_test.py b/tests/adata_test/sentiment/sentiment_hot_test.py new file mode 100644 index 0000000..b6e41b1 --- /dev/null +++ b/tests/adata_test/sentiment/sentiment_hot_test.py @@ -0,0 +1,48 @@ +import unittest + +import adata + + +class SentimentHotTestCase(unittest.TestCase): + + @classmethod + def setUpClass(cls) -> None: + print("----STAR执行舆情-热门-函数测试用例STAR----") + + @classmethod + def tearDownClass(cls) -> None: + print("----END执行舆情-热门-函数测试用例END----") + + def setUp(self): + pass + + def tearDown(self): + pass + + def test_pop_rank_100_east(self): + print("开始测试:pop_rank_100_east") + df = adata.sentiment.hot.pop_rank_100_east() + print(df) + self.assertEqual(True, len(df) == 100) + + def test_hot_rank_100_ths(self): + print("开始测试:hot_rank_100_ths") + df = adata.sentiment.hot.hot_rank_100_ths() + print(df) + self.assertEqual(True, len(df) == 100) + + def test_hot_concept_20_ths(self): + print("开始测试:hot_concept_20_ths") + df = adata.sentiment.hot.hot_concept_20_ths() + print(df) + self.assertEqual(True, len(df) == 20) + + def test_list_a_list_daily(self): + print("开始测试:list_a_list_daily") + df = adata.sentiment.hot.list_a_list_daily(report_date='2024-07-04') + print(df) + self.assertEqual(True, len(df) >= 20) + + +if __name__ == '__main__': + unittest.main()