rquote 0.3.6__py3-none-any.whl → 0.3.9__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.
rquote/__init__.py CHANGED
@@ -7,6 +7,9 @@ Copyright (c) 2021 Roi ZHAO
7
7
 
8
8
  '''
9
9
 
10
+ import re
11
+ from pathlib import Path
12
+
10
13
  # API函数
11
14
  from .api import (
12
15
  get_price,
@@ -34,7 +37,38 @@ from . import exceptions
34
37
  from .cache import MemoryCache, Cache
35
38
  from .utils.http import HTTPClient
36
39
 
37
- __version__ = '0.3.6'
40
+
41
+ def _get_version():
42
+ """从 pyproject.toml 读取版本号"""
43
+ # 优先尝试从已安装的包中读取版本
44
+ try:
45
+ from importlib import metadata
46
+ return metadata.version("rquote")
47
+ except Exception:
48
+ pass
49
+
50
+ # 如果包未安装,从 pyproject.toml 读取
51
+ try:
52
+ # 获取项目根目录(__init__.py 的父目录的父目录)
53
+ current_file = Path(__file__).resolve()
54
+ project_root = current_file.parent.parent
55
+ pyproject_path = project_root / "pyproject.toml"
56
+
57
+ if pyproject_path.exists():
58
+ with open(pyproject_path, 'r', encoding='utf-8') as f:
59
+ content = f.read()
60
+ # 使用正则表达式匹配 version = "x.x.x"
61
+ match = re.search(r'version\s*=\s*["\']([^"\']+)["\']', content)
62
+ if match:
63
+ return match.group(1)
64
+ except Exception:
65
+ pass
66
+
67
+ # 如果都失败了,返回默认版本
68
+ return "0.0.0"
69
+
70
+
71
+ __version__ = _get_version()
38
72
 
39
73
  __all__ = [
40
74
  # API函数
rquote/api/lists.py CHANGED
@@ -160,7 +160,7 @@ def get_industry_stocks(node):
160
160
  获取指定行业板块的股票列表
161
161
 
162
162
  Args:
163
- node: 行业节点代码
163
+ node: 行业节点名称
164
164
 
165
165
  Returns:
166
166
  股票列表
rquote/api/price.py CHANGED
@@ -11,6 +11,33 @@ from ..utils.date import check_date_format
11
11
  from ..exceptions import SymbolError
12
12
 
13
13
 
14
+ def _normalize_dataframe_index(df: pd.DataFrame) -> pd.DataFrame:
15
+ """
16
+ 统一处理 DataFrame 索引,转换为 DatetimeIndex
17
+
18
+ Args:
19
+ df: 输入的 DataFrame
20
+
21
+ Returns:
22
+ 处理后的 DataFrame,索引为 DatetimeIndex
23
+ """
24
+ if df.empty:
25
+ return df
26
+
27
+ # 如果已经是 DatetimeIndex,直接返回
28
+ if isinstance(df.index, pd.DatetimeIndex):
29
+ return df
30
+
31
+ # 尝试转换为 DatetimeIndex
32
+ try:
33
+ df.index = pd.to_datetime(df.index)
34
+ except (ValueError, TypeError) as e:
35
+ # 如果转换失败,保持原样(可能是其他类型的索引)
36
+ pass
37
+
38
+ return df
39
+
40
+
14
41
  def get_price(i: str, sdate: str = '', edate: str = '', freq: str = 'day',
15
42
  days: int = 320, fq: str = 'qfq', dd=None) -> Tuple[str, str, pd.DataFrame]:
16
43
  '''
@@ -20,6 +47,7 @@ def get_price(i: str, sdate: str = '', edate: str = '', freq: str = 'day',
20
47
  i: 股票代码
21
48
  sdate: 开始日期
22
49
  edate: 结束日期
50
+ freq: 频率,默认'day' (日线),可选:'week', 'month', 'min'
23
51
  dd: data dictionary或Cache对象,任何有get/put方法的本地缓存
24
52
  days: 获取天数,覆盖sdate
25
53
  fq: 复权方式,qfq为前复权
@@ -48,7 +76,12 @@ def get_price(i: str, sdate: str = '', edate: str = '', freq: str = 'day',
48
76
  market = MarketFactory.create_from_symbol(i, cache=cache)
49
77
 
50
78
  # 调用市场的get_price方法
51
- return market.get_price(i, sdate=sdate, edate=edate, freq=freq, days=days, fq=fq)
79
+ symbol, name, df = market.get_price(i, sdate=sdate, edate=edate, freq=freq, days=days, fq=fq)
80
+
81
+ # 统一后处理:转换索引为 DatetimeIndex
82
+ df = _normalize_dataframe_index(df)
83
+
84
+ return symbol, name, df
52
85
 
53
86
 
54
87
  def get_price_longer(i: str, l: int = 2, dd=None) -> Tuple[str, str, pd.DataFrame]:
@@ -64,10 +97,24 @@ def get_price_longer(i: str, l: int = 2, dd=None) -> Tuple[str, str, pd.DataFram
64
97
  (symbol, name, DataFrame)
65
98
  """
66
99
  _, name, a = get_price(i, dd=dd)
67
- d1 = a.index.format()[0]
100
+ # 使用 DatetimeIndex 的格式化方法(get_price 已统一转换为 DatetimeIndex)
101
+ if isinstance(a.index, pd.DatetimeIndex) and len(a.index) > 0:
102
+ d1 = a.index[0].strftime('%Y%m%d')
103
+ else:
104
+ # 降级处理:如果索引不是 DatetimeIndex(理论上不应该发生),尝试格式化
105
+ try:
106
+ d1 = str(a.index[0])[:8] if len(str(a.index[0])) >= 8 else str(a.index[0])
107
+ except:
108
+ d1 = a.index.format()[0] if hasattr(a.index, 'format') else str(a.index[0])
109
+
68
110
  for y in range(1, l):
69
111
  d0 = str(int(d1[:4]) - 1) + d1[4:]
70
112
  a = pd.concat((get_price(i, d0, d1, dd=dd)[2], a), 0).drop_duplicates()
71
113
  d1 = d0
72
114
  return i, name, a
73
115
 
116
+
117
+ if __name__ == '__main__':
118
+ i = 'usTSLA.N'
119
+ a = get_price(i, freq='min')
120
+ print(a)
rquote/api/stock_info.py CHANGED
@@ -18,6 +18,7 @@ def get_stock_concepts(i: str) -> List[str]:
18
18
  Returns:
19
19
  概念代码列表
20
20
  """
21
+ i = {'6': 'sh', '0': 'sz', '3': 'sz'}.get(i[0], '') + i if i[0] in ['6', '0', '3'] else i
21
22
  url = f'https://proxy.finance.qq.com/ifzqgtimg/appstock/app/stockinfo/plateNew?code={i}&app=wzq&zdf=1'
22
23
  a = hget(url)
23
24
  if not a:
@@ -38,6 +39,7 @@ def get_stock_industry(i: str) -> List[str]:
38
39
  Returns:
39
40
  行业代码列表
40
41
  """
42
+ i = {'6': 'sh', '0': 'sz', '3': 'sz'}.get(i[0], '') + i if i[0] in ['6', '0', '3'] else i
41
43
  url = f'https://proxy.finance.qq.com/ifzqgtimg/appstock/app/stockinfo/plateNew?code={i}&app=wzq&zdf=1'
42
44
  a = hget(url)
43
45
  if not a:
@@ -82,7 +82,10 @@ class CNStockMarket(Market):
82
82
  name = data['data']['name']
83
83
  df = pd.DataFrame([i.split(',') for i in data['data']['klines']],
84
84
  columns=['date', 'open', 'close', 'high', 'low', 'vol', 'money', 'p'])
85
- df = df.set_index(['date']).astype(float)
85
+ df = df.set_index(['date'])
86
+ # 转换数值列
87
+ for col in ['open', 'close', 'high', 'low', 'vol', 'money', 'p']:
88
+ df[col] = pd.to_numeric(df[col], errors='coerce')
86
89
 
87
90
  result = (symbol, name, df)
88
91
  self._put_cache(symbol, result)
rquote/markets/future.py CHANGED
@@ -27,7 +27,10 @@ class FutureMarket(Market):
27
27
 
28
28
  # 特殊处理BTC
29
29
  if symbol[2:5].lower() == 'btc':
30
- return self._get_btc_price(symbol)
30
+ if freq in ('min', '1min', 'minute'):
31
+ return self._get_btc_minute_price(symbol)
32
+ else:
33
+ return self._get_btc_price(symbol)
31
34
 
32
35
  cache_key = f"{symbol}:{sdate}:{edate}:{freq}:{days}"
33
36
  cached = self._get_cached(cache_key)
@@ -49,7 +52,7 @@ class FutureMarket(Market):
49
52
  return self._get_price_fallback(symbol, future_code, freq)
50
53
 
51
54
  def _get_btc_price(self, symbol: str) -> Tuple[str, str, pd.DataFrame]:
52
- """获取比特币价格"""
55
+ """获取比特币日线价格"""
53
56
  url = 'https://quotes.sina.cn/fx/api/openapi.php/BtcService.getDayKLine?symbol=btcbtcusd'
54
57
  response = hget(url)
55
58
  if not response:
@@ -60,12 +63,95 @@ class FutureMarket(Market):
60
63
  columns=['date', 'open', 'high', 'low', 'close', 'vol', 'amount'])
61
64
  for col in ['open', 'high', 'low', 'close', 'vol', 'amount']:
62
65
  df[col] = pd.to_numeric(df[col], errors='coerce')
63
- df = df.set_index('date').astype(float)
66
+ df = df.set_index('date')
64
67
 
65
68
  result = (symbol, 'BTC', df)
66
69
  self._put_cache(symbol, result)
67
70
  return result
68
71
 
72
+ def _get_btc_minute_price(self, symbol: str, datalen: int = 1440) -> Tuple[str, str, pd.DataFrame]:
73
+ """
74
+ 获取比特币分钟级价格
75
+
76
+ Args:
77
+ symbol: 股票代码(如 'fuBTC')
78
+ datalen: 数据长度,默认1440(24小时,每分钟1条)
79
+
80
+ Returns:
81
+ (symbol, name, DataFrame)
82
+ """
83
+ cache_key = f"{symbol}:min:{datalen}"
84
+ cached = self._get_cached(cache_key)
85
+ if cached:
86
+ return cached
87
+
88
+ url = f'https://quotes.sina.cn/fx/api/openapi.php/BtcService.getMinKline?symbol=btcbtcusd&scale=1&datalen={datalen}&callback=var%20_btcbtcusd'
89
+ response = hget(url)
90
+ if not response:
91
+ raise DataSourceError("Failed to fetch BTC minute data")
92
+
93
+ # 解析 JavaScript callback 格式: var _btcbtcusd({...})
94
+ text = response.text
95
+
96
+ # 移除开头的注释和脚本标签
97
+ if '*/' in text:
98
+ text = text.split('*/', 1)[1]
99
+ text = text.strip()
100
+
101
+ # 查找 JSON 部分(从第一个 { 开始)
102
+ json_start = text.find('{')
103
+ if json_start == -1:
104
+ raise DataSourceError("Invalid BTC minute data format: no JSON found")
105
+
106
+ # 提取 JSON 部分,需要找到匹配的最后一个 }
107
+ # 格式: var _btcbtcusd({...}) 或 var _btcbtcusd({...});
108
+ json_str = text[json_start:]
109
+ # 移除末尾可能的 ); 或 )
110
+ json_str = json_str.rstrip(');').rstrip(')')
111
+
112
+ try:
113
+ data = json.loads(json_str)
114
+ except json.JSONDecodeError as e:
115
+ raise DataSourceError(f"Failed to parse BTC minute data JSON: {e}")
116
+
117
+ # 检查返回状态
118
+ if data.get('result', {}).get('status', {}).get('code') != 0:
119
+ raise DataSourceError(f"BTC API error: {data.get('result', {}).get('status', {}).get('msg', 'Unknown error')}")
120
+
121
+ # 提取数据
122
+ kline_data = data.get('result', {}).get('data', [])
123
+ if not kline_data:
124
+ raise DataSourceError("No BTC minute data returned")
125
+
126
+ # 转换为 DataFrame
127
+ # 数据格式: {"d":"2025-11-16 15:35:00","o":"95835.37","h":"95919.90","l":"95835.37","c":"95919.89","v":"6","a":"551441.4297"}
128
+ records = []
129
+ for item in kline_data:
130
+ records.append({
131
+ 'date': item.get('d', ''),
132
+ 'open': item.get('o', '0'),
133
+ 'high': item.get('h', '0'),
134
+ 'low': item.get('l', '0'),
135
+ 'close': item.get('c', '0'),
136
+ 'vol': item.get('v', '0'),
137
+ 'amount': item.get('a', '0')
138
+ })
139
+
140
+ df = pd.DataFrame(records)
141
+ if df.empty:
142
+ raise DataSourceError("Empty BTC minute data")
143
+
144
+ # 转换数据类型
145
+ for col in ['open', 'high', 'low', 'close', 'vol', 'amount']:
146
+ df[col] = pd.to_numeric(df[col], errors='coerce')
147
+
148
+ # 设置索引
149
+ df = df.set_index('date')
150
+
151
+ result = (symbol, 'BTC', df)
152
+ self._put_cache(cache_key, result)
153
+ return result
154
+
69
155
  def _get_price_fallback(self, symbol: str, future_code: str, freq: str) -> Tuple[str, str, pd.DataFrame]:
70
156
  """降级方法"""
71
157
  from ..utils.helpers import load_js_var_json
@@ -84,7 +170,7 @@ class FutureMarket(Market):
84
170
  df.columns = ['date', 'open', 'high', 'low', 'close', 'vol', 'p', 's']
85
171
  for col in ['open', 'high', 'low', 'close', 'vol', 'p', 's']:
86
172
  df[col] = pd.to_numeric(df[col], errors='coerce')
87
- df = df.set_index('date').astype(float)
173
+ df = df.set_index('date')
88
174
  result = (symbol, future_code, df)
89
175
 
90
176
  self._put_cache(f"{symbol}:{freq}", result)
@@ -35,7 +35,7 @@ class HKStockMarket(Market):
35
35
  )
36
36
 
37
37
  parser = KlineParser()
38
- name, df = parser.parse_tencent_kline(raw_data, symbol)
38
+ name, df = parser.parse_tencent_kline(raw_data, symbol, fq=fq)
39
39
 
40
40
  result = (symbol, name, df)
41
41
  self._put_cache(cache_key, result)
rquote/parsers/kline.py CHANGED
@@ -11,7 +11,7 @@ class KlineParser:
11
11
  """K线数据解析器"""
12
12
 
13
13
  @staticmethod
14
- def parse_tencent_kline(data: Dict[str, Any], symbol: str) -> Tuple[str, pd.DataFrame]:
14
+ def parse_tencent_kline(data: Dict[str, Any], symbol: str, fq: str = 'qfq') -> Tuple[str, pd.DataFrame]:
15
15
  """
16
16
  解析腾讯K线数据
17
17
 
@@ -27,9 +27,17 @@ class KlineParser:
27
27
  if not symbol_data:
28
28
  raise ParseError(f"No data for symbol {symbol}")
29
29
 
30
- # 查找时间键
31
- time_keys = ['day', 'qfqday', 'hfqday', 'week', 'qfqweek', 'hfqweek',
32
- 'month', 'qfqmonth', 'hfqmonth']
30
+ # 查找时间键,优先使用与fq参数匹配的键
31
+ # 根据fq参数确定优先级:qfq -> qfqday优先,hfq -> hfqday优先,否则day优先
32
+ if fq == 'qfq':
33
+ time_keys = ['qfqday', 'day', 'hfqday', 'qfqweek', 'week', 'hfqweek',
34
+ 'qfqmonth', 'month', 'hfqmonth']
35
+ elif fq == 'hfq':
36
+ time_keys = ['hfqday', 'day', 'qfqday', 'hfqweek', 'week', 'qfqweek',
37
+ 'hfqmonth', 'month', 'qfqmonth']
38
+ else:
39
+ time_keys = ['day', 'qfqday', 'hfqday', 'week', 'qfqweek', 'hfqweek',
40
+ 'month', 'qfqmonth', 'hfqmonth']
33
41
  tk = None
34
42
  for tkt in time_keys:
35
43
  if tkt in symbol_data:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rquote
3
- Version: 0.3.6
3
+ Version: 0.3.9
4
4
  Summary: Mostly day quotes of cn/hk/us/fund/future markets, side with quote list fetch
5
5
  Requires-Python: >=3.6.1
6
6
  Description-Content-Type: text/markdown
@@ -83,7 +83,7 @@ sid, name, df = get_price('sh000001', dd=cache_dict)
83
83
  - `i`: 股票代码,使用新浪/腾讯的id形式
84
84
  - `sdate`: 开始日期 (可选,格式:YYYY-MM-DD)
85
85
  - `edate`: 结束日期 (可选,格式:YYYY-MM-DD)
86
- - `freq`: 频率,默认'day' (日线),可选:'week', 'month', 'min'
86
+ - `freq`: 频率,默认'day' (日线),可选:(港A)'week', 'month', (美股)'min'
87
87
  - `days`: 获取天数,默认320天
88
88
  - `fq`: 复权方式,默认'qfq' (前复权),可选:'hfq' (后复权)
89
89
  - `dd`: 本地缓存字典 (可选,已废弃,建议使用MemoryCache)
@@ -1,12 +1,12 @@
1
- rquote/__init__.py,sha256=l0zLfBtpfQB401d5EE4GLja3EAWu03LwWXopqFtwMmY,1159
1
+ rquote/__init__.py,sha256=-U5Uq4eT3Hhl2EkVmBgr5TAfU-ZfFzpOaGeJafnhyos,2208
2
2
  rquote/config.py,sha256=noep_VzY_nJehnkPQb4mkwzpeYLwkU1riqofQJ6Hhw0,1108
3
3
  rquote/exceptions.py,sha256=lJH2GC5dDhMoW_OtlBc03wlUn684-7jNPyF1NjmfVIE,569
4
4
  rquote/plots.py,sha256=UQn4sjhIzVwagfhUDM738b2HHjKo4tRdU2UCs_1-FbY,2341
5
5
  rquote/utils.py,sha256=bH0ZFIo-ZelNztzPS6BXFShXE3yGA9USI_P9INN0Y-s,310
6
6
  rquote/api/__init__.py,sha256=ptizO--im80HaxlzxkJo9BKdJPEnbu00R9UDgcoA0mU,656
7
- rquote/api/lists.py,sha256=jSvxtlBsGh67ruAq0_ASCqsEXMh6xC9lZEKQWS97UCY,5331
8
- rquote/api/price.py,sha256=a6ARAX66iPy79Bhxjj8Sn-Gk3p84Ukw0UDywViWTTzA,2092
9
- rquote/api/stock_info.py,sha256=aQwAhMQi8EymV5uVwOdkOLKYYnKfMRRMm6SqvHVYl-w,1349
7
+ rquote/api/lists.py,sha256=fRebS02Fi0qe6KpWBA-9W1UG0It6__DmRlNimtMa7L8,5331
8
+ rquote/api/price.py,sha256=I5lZl6cUQRlE4AtzNbR-uGZt1ho9vgP1cgNFDjaigMA,3575
9
+ rquote/api/stock_info.py,sha256=912ICdIBr8z2lKWDbq3gG0E94czTPvbx9aXsKUi-QkE,1537
10
10
  rquote/api/tick.py,sha256=nEcjuAjtBHUaD8KPRLg643piVa21PhKDQvkVWNwvvME,1431
11
11
  rquote/cache/__init__.py,sha256=IXGSRpvSgBlcM6twLuJEOEockbb09_VqORXdQpfwpCA,138
12
12
  rquote/cache/base.py,sha256=orzG4Yo-6gzVG027j1-LTZPT718JohnCdLDnOLoLUQ4,515
@@ -19,20 +19,20 @@ rquote/factors/__init__.py,sha256=_ZbH2XxYtXwCJpvRVdNvGncoPSpMqrtlYmf1_fMGIjM,11
19
19
  rquote/factors/technical.py,sha256=dPDs3pDEDRV9iQJBrSoKpGFLQMjOqyoBdN2rUntpOUU,4235
20
20
  rquote/markets/__init__.py,sha256=k4F8cZgb-phqemMqhZXFPdOKsR4P--DD3d5i21vKhbg,365
21
21
  rquote/markets/base.py,sha256=DjvxRcJqwUsBTxnsE28Gd-zJLFsCGwdQpezLRAZ_9sQ,1347
22
- rquote/markets/cn_stock.py,sha256=xf-uXDvkdPBPuc-4-VhEsX0OrxSsaMVsCl1wmSqnVl0,8064
22
+ rquote/markets/cn_stock.py,sha256=fyF7jJHFUrI5jwuqBKHXpsIE51H4kbyc3q-uuviPLGk,8224
23
23
  rquote/markets/factory.py,sha256=4Txpuok0LBOLT_vAiIU-NslwVnYF7sKHCdlacAboxpo,2875
24
- rquote/markets/future.py,sha256=AbA-gpmBhbyrRJn8Vu-ouQLCd-TlacXSVZQ2VHYn2pg,3907
25
- rquote/markets/hk_stock.py,sha256=iz0BriBDNGLkUCPExcCbe2jmuoqJinxx_cbDtKblABc,1445
24
+ rquote/markets/future.py,sha256=7AqViPp0S9OQZsaU2hkJzh4My6gYFqLo1OUW2mVMSDo,7215
25
+ rquote/markets/hk_stock.py,sha256=NlWaXQgXttpcQVFZjflcEkMTmXMxeP2C6Y7OGG50u7E,1452
26
26
  rquote/markets/us_stock.py,sha256=17mTg50g3ImOnGM4Re1MRSyvbD2mgFW6wjtMh86IEXA,2465
27
27
  rquote/parsers/__init__.py,sha256=q4g-FgpzxKBPfhJiQH3B5MEeZWUIXlyre-vAnOnfYmA,110
28
- rquote/parsers/kline.py,sha256=O1hieGTebSppcXUl3vS1CNzYftk6f2ElS0a9Tx8mqo4,3481
28
+ rquote/parsers/kline.py,sha256=g6k8W76-4hpYsuBgvwmb5G6ZkzHOJDX-JrVVXYksw4c,4020
29
29
  rquote/utils/__init__.py,sha256=-ZHABqFHQeJrCCsgnqEYWR57jl7GduCKn2V3hpFi-pE,348
30
30
  rquote/utils/date.py,sha256=nhK3xQ2kFvKhdkPw-2HR2V0PSzBfXmX8L7laG7VmG2E,913
31
31
  rquote/utils/helpers.py,sha256=V07n9BtRS8bEJH023Kca78-unk7iD3B9hn2UjELetYs,354
32
32
  rquote/utils/http.py,sha256=X0Alhnu0CNqyQeOt6ivUWmh2XwrWxXd2lSpQOKDdnzw,3249
33
33
  rquote/utils/logging.py,sha256=cbeRH4ODazn7iyQmGoEBT2lH5LX4Ca3zDfs_20J1T28,566
34
34
  rquote/utils/web.py,sha256=I8_pcThW6VUvahuRHdtp32iZwr85hEt1hB6TgznMy_U,3854
35
- rquote-0.3.6.dist-info/METADATA,sha256=IJ2xLFbl5KeQ_LktJ58cXTvmUC48ua4xzO2q6obGTN4,11248
36
- rquote-0.3.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
37
- rquote-0.3.6.dist-info/top_level.txt,sha256=CehAiaZx7Fo8HGoV2zd5GhILUW1jQEN8YS-cWMlrK9Y,7
38
- rquote-0.3.6.dist-info/RECORD,,
35
+ rquote-0.3.9.dist-info/METADATA,sha256=gFYIx3AMMzbJgDx9BlXn-Xn3T9PgkTuYkQTXthyjeIA,11262
36
+ rquote-0.3.9.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
37
+ rquote-0.3.9.dist-info/top_level.txt,sha256=CehAiaZx7Fo8HGoV2zd5GhILUW1jQEN8YS-cWMlrK9Y,7
38
+ rquote-0.3.9.dist-info/RECORD,,
File without changes