diff --git a/adata/bond/market/bond_market.py b/adata/bond/market/bond_market.py index a79a721..52de226 100644 --- a/adata/bond/market/bond_market.py +++ b/adata/bond/market/bond_market.py @@ -7,7 +7,10 @@ @time:2023/4/5 @log: """ +from adata.bond.market.bond_market_baidu import BondMarketBaiDu +from adata.bond.market.bond_market_qq import BondMarketQQ from adata.bond.market.bond_market_sina import BondMarketSina +from adata.bond.market.bond_market_east import BondMarketEast class BondMarket(object): @@ -16,6 +19,9 @@ class BondMarket(object): def __init__(self) -> None: super().__init__() self.sina = BondMarketSina() + self.baidu_market = BondMarketBaiDu() + self.qq_market = BondMarketQQ() + self.east_market = BondMarketEast() def list_market_current(self, code_list=None): """ @@ -25,6 +31,37 @@ def list_market_current(self, code_list=None): _MARKET_CURRENT_COLUMNS """ return self.sina.list_market_current(code_list) + + def get_market(self, bond_code: str, start_date='1990-01-01', end_date=None, k_type=1, adjust_type: int = 1): + """ + 获取单个债券的历史行情 + :param bond_code: 债券代码 + :param start_date: 开始时间 + :param end_date: 结束日期 + :param k_type: k线类型:1.日;2.周;3.月 默认:1 日k + :param adjust_type: k线复权类型:0.不复权;1.前复权;2.后复权 默认:1 前复权 + :return: k线行情数据 + """ + df = self.baidu_market.get_market(bond_code=bond_code, start_date=start_date, end_date=end_date, + k_type=k_type, adjust_type=adjust_type) + if df.empty: + df = self.qq_market.get_market(bond_code=bond_code, start_date=start_date, end_date=end_date, + k_type=k_type, adjust_type=adjust_type) + if df.empty: + df = self.east_market.get_market(bond_code=bond_code, start_date=start_date, end_date=end_date, + k_type=k_type, adjust_type=adjust_type) + return df + + def get_market_min(self, bond_code: str): + """ + 获取单个债券的今日分时行情 + :param bond_code: 债券代码 + :return: 当日分钟行情数据 + """ + df = self.qq_market.get_market_min(bond_code=bond_code) + if df.empty: + df = self.east_market.get_market_min(bond_code=bond_code) + return df if __name__ == '__main__': diff --git a/adata/bond/market/bond_market_east.py b/adata/bond/market/bond_market_east.py new file mode 100644 index 0000000..4921ad8 --- /dev/null +++ b/adata/bond/market/bond_market_east.py @@ -0,0 +1,197 @@ +# -*- coding: utf-8 -*- +""" +@desc: 东方财富债券行情 +@author: 1nchaos +@time: 2024/05/05 +@log: change log +""" +import pandas as pd +from datetime import datetime +from adata.bond.market.bond_market_template import BondMarketTemplate +from adata.common.utils.date_utils import get_n_days_date, get_cur_time +from adata.common.utils.sunrequests import SunRequests + + +class BondMarketEast(BondMarketTemplate): + """ + 东方财富债券行情 + """ + + def __init__(self) -> None: + super().__init__() + self._requests = SunRequests() + + def get_market(self, bond_code: str, start_date='1990-01-01', end_date=None, k_type=1, adjust_type: int = 1): + """ + 获取单个债券的历史行情 + :param bond_code: 债券代码 + :param start_date: 开始时间 + :param end_date: 结束日期 + :param k_type: k线类型:1.日;2.周;3.月 默认:1 日k + :param adjust_type: k线复权类型:0.不复权;1.前复权;2.后复权 默认:1 前复权 + :return: k线行情数据 + """ + try: + # 1. 参数处理 + if end_date is None: + end_date = get_n_days_date(0) + + # 2. 构建请求参数 + # 债券代码前缀规则:沪市债券以1开头,深市债券以0开头 + se_cid = 1 if bond_code.startswith('1') else 0 + # 格式化日期,移除横杠 + start_date_str = start_date.replace('-', '') + end_date_str = end_date.replace('-', '') + # 处理k_type参数,与股票模块保持一致 + k_type_str = f"10{k_type}" if int(k_type) < 5 else k_type + + params = { + 'fields1': 'f1,f2,f3,f4,f5,f6', + 'fields2': 'f51,f52,f53,f54,f55,f56,f57,f58,f59,f60,f61,f116', + 'ut': '7eea3edcaed734bea9cbfc24409ed989', + 'klt': k_type_str, # k线类型 + 'fqt': adjust_type, # 复权类型 + 'secid': f'{se_cid}.{bond_code}', + 'beg': start_date_str, + 'end': end_date_str, + '_': int(datetime.now().timestamp() * 1000) + } + + # 3. 发送请求 + url = 'https://push2his.eastmoney.com/api/qt/stock/kline/get' + # 使用与股票模块相同的请求方法 + from adata.common.utils import requests as adata_requests + try: + res = adata_requests.request(method='get', url=url, params=params, timeout=10) + except Exception: + # 请求失败,返回空DataFrame + return pd.DataFrame(columns=self._MARKET_COLUMNS) + + # 4. 处理响应 + if res is None or res.status_code != 200: + return pd.DataFrame(columns=self._MARKET_COLUMNS) + + try: + res_json = res.json() + except Exception: + # 解析JSON失败,返回空DataFrame + return pd.DataFrame(columns=self._MARKET_COLUMNS) + + if res_json.get('data') is None: + return pd.DataFrame(columns=self._MARKET_COLUMNS) + + data = res_json['data'] + if not data.get('klines'): + return pd.DataFrame(columns=self._MARKET_COLUMNS) + + # 5. 解析数据 + klines = data['klines'] + rows = [] + for kline in klines: + items = kline.split(',') + if len(items) < 11: + continue + try: + row = { + 'trade_time': items[0], + 'open': float(items[1]), + 'close': float(items[2]), + 'high': float(items[3]), + 'low': float(items[4]), + 'volume': float(items[5]), + 'amount': float(items[6]), + 'pre_close': float(items[7]), + 'change': float(items[8]), + 'change_pct': float(items[9]), + 'bond_code': bond_code, + 'trade_date': items[0].split(' ')[0] + } + rows.append(row) + except (ValueError, IndexError): + # 数据解析失败,跳过当前行 + continue + + # 6. 构建DataFrame + df = pd.DataFrame(rows, columns=self._MARKET_COLUMNS) + return df + except Exception: + # 异常处理,确保返回结构一致 + return pd.DataFrame(columns=self._MARKET_COLUMNS) + + def get_market_min(self, bond_code: str): + """ + 获取单个债券的今日分时行情 + :param bond_code: 债券代码 + :return: 当日分钟行情数据 + """ + try: + # 1. 构建请求参数 + # 债券代码前缀规则:沪市债券以1开头,深市债券以0开头 + se_cid = 1 if bond_code.startswith('1') else 0 + params = { + "fields1": "f1,f2,f3,f4,f5,f6,f7,f8,f9,f10,f11,f12,f13", + "fields2": "f51,f52,f53,f54,f55,f56,f57,f58", + "ut": "fa5fd1943c7b386f172d6893dbfba10b", + "ndays": "1", "iscr": "1", + "iscca": "0", "secid": f"{se_cid}.{bond_code}", + "_": int(datetime.now().timestamp() * 1000), + } + + # 2. 发送请求 + url = "https://push2.eastmoney.com/api/qt/stock/trends2/get" + # 使用与股票模块相同的请求方法 + from adata.common.utils import requests as adata_requests + try: + res = adata_requests.request(method='get', url=url, params=params, timeout=10) + except Exception: + # 请求失败,返回空DataFrame + return pd.DataFrame(columns=self._MARKET_MIN_COLUMNS) + + # 3. 处理响应 + if res is None or res.status_code != 200: + return pd.DataFrame(columns=self._MARKET_MIN_COLUMNS) + + try: + res_json = res.json() + except Exception: + # 解析JSON失败,返回空DataFrame + return pd.DataFrame(columns=self._MARKET_MIN_COLUMNS) + + if res_json.get('data') is None: + return pd.DataFrame(columns=self._MARKET_MIN_COLUMNS) + + data = res_json['data'] + if not data.get('trends'): + return pd.DataFrame(columns=self._MARKET_MIN_COLUMNS) + + # 4. 解析数据 + trends = data['trends'] + rows = [] + for trend in trends: + items = trend.split(',') + if len(items) < 8: + continue + try: + row = { + 'time': items[0], + 'price': float(items[1]), + 'volume': float(items[5]) * 100, # 换算成股 + 'amount': float(items[6]), + 'bond_code': bond_code + } + rows.append(row) + except (ValueError, IndexError): + # 数据解析失败,跳过当前行 + continue + + # 5. 构建DataFrame + df = pd.DataFrame(rows, columns=self._MARKET_MIN_COLUMNS) + return df + except Exception: + # 异常处理,确保返回结构一致 + return pd.DataFrame(columns=self._MARKET_MIN_COLUMNS) + + +if __name__ == '__main__': + print(BondMarketEast().get_market(bond_code='110044', start_date='2024-07-01', k_type=1)) + print(BondMarketEast().get_market_min(bond_code='110044')) diff --git a/adata/bond/market/bond_market_template.py b/adata/bond/market/bond_market_template.py index 17d257c..00a47b0 100644 --- a/adata/bond/market/bond_market_template.py +++ b/adata/bond/market/bond_market_template.py @@ -13,6 +13,10 @@ class BondMarketTemplate(object): """ _MARKET_CURRENT_COLUMNS = ['bond_code', 'bond_name', 'price', 'open', 'high', 'low', 'pre_close', 'change', 'change_pct', 'volume', 'amount', 'time'] + + _MARKET_COLUMNS = ['trade_time', 'open', 'close', 'high', 'low', 'volume', 'amount', 'pre_close', 'change', 'change_pct', 'bond_code', 'trade_date'] + + _MARKET_MIN_COLUMNS = ['time', 'price', 'volume', 'amount', 'bond_code'] def list_market_current(self, code_list=None): """ @@ -22,3 +26,25 @@ def list_market_current(self, code_list=None): _MARKET_CURRENT_COLUMNS """ pass + + def get_market(self, bond_code: str, start_date='1990-01-01', end_date=None, k_type=1, adjust_type: int = 1): + """ + 获取单个债券的历史行情 + :param bond_code: 债券代码 + :param start_date: 开始时间 + :param end_date: 结束日期 + :param k_type: k线类型:1.日;2.周;3.月 默认:1 日k + :param adjust_type: k线复权类型:0.不复权;1.前复权;2.后复权 默认:1 前复权 + :return: k线行情数据 + _MARKET_COLUMNS + """ + pass + + def get_market_min(self, bond_code: str): + """ + 获取单个债券的今日分时行情 + :param bond_code: 债券代码 + :return: 当日分钟行情数据 + _MARKET_MIN_COLUMNS + """ + pass diff --git a/tests/other/test_bond_market.py b/tests/other/test_bond_market.py new file mode 100644 index 0000000..3e2bc49 --- /dev/null +++ b/tests/other/test_bond_market.py @@ -0,0 +1,16 @@ +import adata + +bond_code = "110100" + +df1 = adata.bond.market.get_market( + bond_code=bond_code, + start_date="2026-03-01", + end_date="2026-03-31", + k_type=1 +) +print("history rows:", len(df1)) +print(df1.head()) + +df2 = adata.bond.market.get_market_min(bond_code=bond_code) +print("min rows:", len(df2)) +print(df2.head())