rquote 0.4.0__tar.gz → 0.4.1__tar.gz
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-0.4.0 → rquote-0.4.1}/PKG-INFO +4 -4
- {rquote-0.4.0 → rquote-0.4.1}/README.md +3 -3
- {rquote-0.4.0 → rquote-0.4.1}/pyproject.toml +1 -1
- {rquote-0.4.0 → rquote-0.4.1}/rquote/cache/persistent.py +96 -10
- {rquote-0.4.0 → rquote-0.4.1}/rquote/markets/base.py +62 -16
- {rquote-0.4.0 → rquote-0.4.1}/rquote.egg-info/PKG-INFO +4 -4
- {rquote-0.4.0 → rquote-0.4.1}/rquote/__init__.py +0 -0
- {rquote-0.4.0 → rquote-0.4.1}/rquote/api/__init__.py +0 -0
- {rquote-0.4.0 → rquote-0.4.1}/rquote/api/lists.py +0 -0
- {rquote-0.4.0 → rquote-0.4.1}/rquote/api/price.py +0 -0
- {rquote-0.4.0 → rquote-0.4.1}/rquote/api/stock_info.py +0 -0
- {rquote-0.4.0 → rquote-0.4.1}/rquote/api/tick.py +0 -0
- {rquote-0.4.0 → rquote-0.4.1}/rquote/cache/__init__.py +0 -0
- {rquote-0.4.0 → rquote-0.4.1}/rquote/cache/base.py +0 -0
- {rquote-0.4.0 → rquote-0.4.1}/rquote/cache/memory.py +0 -0
- {rquote-0.4.0 → rquote-0.4.1}/rquote/config.py +0 -0
- {rquote-0.4.0 → rquote-0.4.1}/rquote/data_sources/__init__.py +0 -0
- {rquote-0.4.0 → rquote-0.4.1}/rquote/data_sources/base.py +0 -0
- {rquote-0.4.0 → rquote-0.4.1}/rquote/data_sources/sina.py +0 -0
- {rquote-0.4.0 → rquote-0.4.1}/rquote/data_sources/tencent.py +0 -0
- {rquote-0.4.0 → rquote-0.4.1}/rquote/exceptions.py +0 -0
- {rquote-0.4.0 → rquote-0.4.1}/rquote/factors/__init__.py +0 -0
- {rquote-0.4.0 → rquote-0.4.1}/rquote/factors/technical.py +0 -0
- {rquote-0.4.0 → rquote-0.4.1}/rquote/markets/__init__.py +0 -0
- {rquote-0.4.0 → rquote-0.4.1}/rquote/markets/cn_stock.py +0 -0
- {rquote-0.4.0 → rquote-0.4.1}/rquote/markets/factory.py +0 -0
- {rquote-0.4.0 → rquote-0.4.1}/rquote/markets/future.py +0 -0
- {rquote-0.4.0 → rquote-0.4.1}/rquote/markets/hk_stock.py +0 -0
- {rquote-0.4.0 → rquote-0.4.1}/rquote/markets/us_stock.py +0 -0
- {rquote-0.4.0 → rquote-0.4.1}/rquote/parsers/__init__.py +0 -0
- {rquote-0.4.0 → rquote-0.4.1}/rquote/parsers/kline.py +0 -0
- {rquote-0.4.0 → rquote-0.4.1}/rquote/plots.py +0 -0
- {rquote-0.4.0 → rquote-0.4.1}/rquote/utils/__init__.py +0 -0
- {rquote-0.4.0 → rquote-0.4.1}/rquote/utils/date.py +0 -0
- {rquote-0.4.0 → rquote-0.4.1}/rquote/utils/helpers.py +0 -0
- {rquote-0.4.0 → rquote-0.4.1}/rquote/utils/http.py +0 -0
- {rquote-0.4.0 → rquote-0.4.1}/rquote/utils/logging.py +0 -0
- {rquote-0.4.0 → rquote-0.4.1}/rquote/utils/web.py +0 -0
- {rquote-0.4.0 → rquote-0.4.1}/rquote/utils.py +0 -0
- {rquote-0.4.0 → rquote-0.4.1}/rquote.egg-info/SOURCES.txt +0 -0
- {rquote-0.4.0 → rquote-0.4.1}/rquote.egg-info/dependency_links.txt +0 -0
- {rquote-0.4.0 → rquote-0.4.1}/rquote.egg-info/requires.txt +0 -0
- {rquote-0.4.0 → rquote-0.4.1}/rquote.egg-info/top_level.txt +0 -0
- {rquote-0.4.0 → rquote-0.4.1}/setup.cfg +0 -0
- {rquote-0.4.0 → rquote-0.4.1}/tests/test_api.py +0 -0
- {rquote-0.4.0 → rquote-0.4.1}/tests/test_cache.py +0 -0
- {rquote-0.4.0 → rquote-0.4.1}/tests/test_config.py +0 -0
- {rquote-0.4.0 → rquote-0.4.1}/tests/test_exceptions.py +0 -0
- {rquote-0.4.0 → rquote-0.4.1}/tests/test_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rquote
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.1
|
|
4
4
|
Summary: Mostly day quotes of cn/hk/us/fund/future markets, side with quote list fetch
|
|
5
5
|
Requires-Python: >=3.9.0
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -18,7 +18,7 @@ Requires-Dist: duckdb>=0.9.0; extra == "persistent"
|
|
|
18
18
|
|
|
19
19
|
## 版本信息
|
|
20
20
|
|
|
21
|
-
当前版本:**0.
|
|
21
|
+
当前版本:**0.4.1**
|
|
22
22
|
|
|
23
23
|
## 主要特性
|
|
24
24
|
|
|
@@ -199,13 +199,13 @@ stocks = get_cn_stock_list(money_min=5e8)
|
|
|
199
199
|
|
|
200
200
|
#### `get_hk_stocks_500()`
|
|
201
201
|
|
|
202
|
-
获取港股前500只股票列表
|
|
202
|
+
获取港股前500只股票列表(按当日成交额排序)
|
|
203
203
|
|
|
204
204
|
```python
|
|
205
205
|
from rquote import get_hk_stocks_500
|
|
206
206
|
|
|
207
207
|
stocks = get_hk_stocks_500()
|
|
208
|
-
# 返回格式: [[code, name, price, turnover, ...], ...]
|
|
208
|
+
# 返回格式: [[code, name, price, -, -, -, -, volume, turnover, ...], ...]
|
|
209
209
|
```
|
|
210
210
|
|
|
211
211
|
#### `get_us_stocks(k=100)`
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
## 版本信息
|
|
6
6
|
|
|
7
|
-
当前版本:**0.
|
|
7
|
+
当前版本:**0.4.1**
|
|
8
8
|
|
|
9
9
|
## 主要特性
|
|
10
10
|
|
|
@@ -185,13 +185,13 @@ stocks = get_cn_stock_list(money_min=5e8)
|
|
|
185
185
|
|
|
186
186
|
#### `get_hk_stocks_500()`
|
|
187
187
|
|
|
188
|
-
获取港股前500只股票列表
|
|
188
|
+
获取港股前500只股票列表(按当日成交额排序)
|
|
189
189
|
|
|
190
190
|
```python
|
|
191
191
|
from rquote import get_hk_stocks_500
|
|
192
192
|
|
|
193
193
|
stocks = get_hk_stocks_500()
|
|
194
|
-
# 返回格式: [[code, name, price, turnover, ...], ...]
|
|
194
|
+
# 返回格式: [[code, name, price, -, -, -, -, volume, turnover, ...], ...]
|
|
195
195
|
```
|
|
196
196
|
|
|
197
197
|
#### `get_us_stocks(k=100)`
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "rquote"
|
|
7
|
-
version = "0.4.
|
|
7
|
+
version = "0.4.1"
|
|
8
8
|
description = "Mostly day quotes of cn/hk/us/fund/future markets, side with quote list fetch"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
# requires-python = ">=3.6.1" # duckdb requires higher python version
|
|
@@ -9,6 +9,13 @@ from typing import Optional, Any, Tuple
|
|
|
9
9
|
import pandas as pd
|
|
10
10
|
from .base import Cache
|
|
11
11
|
|
|
12
|
+
# 导入日志
|
|
13
|
+
try:
|
|
14
|
+
from ..utils.logging import logger
|
|
15
|
+
except ImportError:
|
|
16
|
+
import logging
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
12
19
|
# 尝试导入 duckdb(可选依赖)
|
|
13
20
|
try:
|
|
14
21
|
import duckdb
|
|
@@ -125,8 +132,20 @@ class PersistentCache(Cache):
|
|
|
125
132
|
|
|
126
133
|
def _get_dataframe_date_range(self, df: pd.DataFrame) -> Tuple[Optional[pd.Timestamp], Optional[pd.Timestamp]]:
|
|
127
134
|
"""获取 DataFrame 的日期范围"""
|
|
128
|
-
if df.empty
|
|
135
|
+
if df.empty:
|
|
136
|
+
return None, None
|
|
137
|
+
|
|
138
|
+
# 如果索引不是 DatetimeIndex,尝试转换
|
|
139
|
+
if not isinstance(df.index, pd.DatetimeIndex):
|
|
140
|
+
try:
|
|
141
|
+
# 尝试转换为 DatetimeIndex
|
|
142
|
+
index = pd.to_datetime(df.index)
|
|
143
|
+
if len(index) > 0:
|
|
144
|
+
return index.min(), index.max()
|
|
145
|
+
except (ValueError, TypeError):
|
|
146
|
+
pass
|
|
129
147
|
return None, None
|
|
148
|
+
|
|
130
149
|
return df.index.min(), df.index.max()
|
|
131
150
|
|
|
132
151
|
def _filter_dataframe_by_date(self, df: pd.DataFrame, sdate: Optional[str] = None,
|
|
@@ -164,23 +183,50 @@ class PersistentCache(Cache):
|
|
|
164
183
|
combined = combined.sort_index()
|
|
165
184
|
return combined
|
|
166
185
|
|
|
167
|
-
def get(self, key: str) -> Optional[Any]:
|
|
186
|
+
def get(self, key: str, sdate: Optional[str] = None, edate: Optional[str] = None) -> Optional[Any]:
|
|
168
187
|
"""
|
|
169
188
|
获取缓存数据
|
|
170
189
|
|
|
171
190
|
Args:
|
|
172
|
-
key: 缓存 key
|
|
191
|
+
key: 缓存 key,可以是完整格式 "symbol:sdate:edate:freq:days:fq"
|
|
192
|
+
或 base_key 格式 "symbol:freq:fq"
|
|
193
|
+
sdate: 开始日期(可选,如果 key 是 base_key 格式则必须提供)
|
|
194
|
+
edate: 结束日期(可选,如果 key 是 base_key 格式则必须提供)
|
|
173
195
|
|
|
174
196
|
Returns:
|
|
175
197
|
(symbol, name, DataFrame) 或 None
|
|
176
198
|
"""
|
|
177
|
-
|
|
178
|
-
|
|
199
|
+
# 判断 key 格式:如果是 base_key 格式(只有3部分),使用参数中的日期
|
|
200
|
+
parts = key.split(':')
|
|
201
|
+
if len(parts) == 3:
|
|
202
|
+
# base_key 格式:symbol:freq:fq
|
|
203
|
+
symbol, freq, fq = parts
|
|
204
|
+
base_key = key
|
|
205
|
+
# 使用参数中的日期,如果没有则使用空字符串
|
|
206
|
+
sdate = sdate or ''
|
|
207
|
+
edate = edate or ''
|
|
208
|
+
else:
|
|
209
|
+
# 完整 key 格式:symbol:sdate:edate:freq:days:fq
|
|
210
|
+
symbol, sdate_from_key, edate_from_key, freq, fq = self._extract_key_parts(key)
|
|
211
|
+
base_key = self._get_base_key(symbol, freq, fq)
|
|
212
|
+
# 优先使用参数中的日期,如果没有则使用 key 中的日期
|
|
213
|
+
sdate = sdate if sdate is not None else sdate_from_key
|
|
214
|
+
edate = edate if edate is not None else edate_from_key
|
|
215
|
+
|
|
216
|
+
logger.info(f"[CACHE GET] key={key}, base_key={base_key}, sdate={sdate}, edate={edate}")
|
|
179
217
|
|
|
180
218
|
if self.use_duckdb:
|
|
181
|
-
|
|
219
|
+
result = self._get_duckdb(base_key, symbol, sdate, edate, freq, fq)
|
|
220
|
+
else:
|
|
221
|
+
result = self._get_pickle(base_key, symbol, sdate, edate, freq, fq)
|
|
222
|
+
|
|
223
|
+
if result:
|
|
224
|
+
_, _, df = result
|
|
225
|
+
logger.info(f"[CACHE HIT] key={key}, 返回数据行数={len(df)}, 日期范围={df.index.min()} 到 {df.index.max()}")
|
|
182
226
|
else:
|
|
183
|
-
|
|
227
|
+
logger.info(f"[CACHE MISS] key={key}, 缓存中无数据")
|
|
228
|
+
|
|
229
|
+
return result
|
|
184
230
|
|
|
185
231
|
def _get_duckdb(self, base_key: str, symbol: str, sdate: str, edate: str,
|
|
186
232
|
freq: str, fq: str) -> Optional[Tuple[str, str, pd.DataFrame]]:
|
|
@@ -238,6 +284,13 @@ class PersistentCache(Cache):
|
|
|
238
284
|
if filtered_df.empty:
|
|
239
285
|
return None
|
|
240
286
|
|
|
287
|
+
# 确保索引是 DatetimeIndex
|
|
288
|
+
if not isinstance(filtered_df.index, pd.DatetimeIndex):
|
|
289
|
+
try:
|
|
290
|
+
filtered_df.index = pd.to_datetime(filtered_df.index)
|
|
291
|
+
except (ValueError, TypeError):
|
|
292
|
+
pass # 如果转换失败,保持原样
|
|
293
|
+
|
|
241
294
|
return (symbol, name, filtered_df)
|
|
242
295
|
|
|
243
296
|
def _get_pickle(self, base_key: str, symbol: str, sdate: str, edate: str,
|
|
@@ -292,6 +345,13 @@ class PersistentCache(Cache):
|
|
|
292
345
|
if filtered_df.empty:
|
|
293
346
|
return None
|
|
294
347
|
|
|
348
|
+
# 确保索引是 DatetimeIndex
|
|
349
|
+
if not isinstance(filtered_df.index, pd.DatetimeIndex):
|
|
350
|
+
try:
|
|
351
|
+
filtered_df.index = pd.to_datetime(filtered_df.index)
|
|
352
|
+
except (ValueError, TypeError):
|
|
353
|
+
pass # 如果转换失败,保持原样
|
|
354
|
+
|
|
295
355
|
return (symbol, name, filtered_df)
|
|
296
356
|
|
|
297
357
|
def put(self, key: str, value: Any, ttl: Optional[int] = None) -> None:
|
|
@@ -299,7 +359,8 @@ class PersistentCache(Cache):
|
|
|
299
359
|
存储缓存数据
|
|
300
360
|
|
|
301
361
|
Args:
|
|
302
|
-
key: 缓存 key
|
|
362
|
+
key: 缓存 key,可以是完整格式 "symbol:sdate:edate:freq:days:fq"
|
|
363
|
+
或 base_key 格式 "symbol:freq:fq"(推荐使用 base_key)
|
|
303
364
|
value: (symbol, name, DataFrame) 元组
|
|
304
365
|
ttl: 过期时间(秒)
|
|
305
366
|
"""
|
|
@@ -310,8 +371,25 @@ class PersistentCache(Cache):
|
|
|
310
371
|
if not isinstance(df, pd.DataFrame) or df.empty:
|
|
311
372
|
return
|
|
312
373
|
|
|
313
|
-
|
|
314
|
-
|
|
374
|
+
logger.info(f"[CACHE PUT] key={key}, 数据行数={len(df)}, 日期范围={df.index.min() if not df.empty else 'N/A'} 到 {df.index.max() if not df.empty else 'N/A'}")
|
|
375
|
+
|
|
376
|
+
# 确保索引是 DatetimeIndex(用于正确获取日期范围)
|
|
377
|
+
if not isinstance(df.index, pd.DatetimeIndex):
|
|
378
|
+
try:
|
|
379
|
+
df.index = pd.to_datetime(df.index)
|
|
380
|
+
except (ValueError, TypeError):
|
|
381
|
+
pass # 如果转换失败,继续处理(_get_dataframe_date_range 会处理)
|
|
382
|
+
|
|
383
|
+
# 判断 key 格式:如果是 base_key 格式(只有3部分),直接使用
|
|
384
|
+
parts = key.split(':')
|
|
385
|
+
if len(parts) == 3:
|
|
386
|
+
# base_key 格式:symbol:freq:fq
|
|
387
|
+
base_key = key
|
|
388
|
+
freq, fq = parts[1], parts[2]
|
|
389
|
+
else:
|
|
390
|
+
# 完整 key 格式:symbol:sdate:edate:freq:days:fq
|
|
391
|
+
_, _, _, freq, fq = self._extract_key_parts(key)
|
|
392
|
+
base_key = self._get_base_key(symbol, freq, fq)
|
|
315
393
|
|
|
316
394
|
# 尝试从基础 key 获取完整数据并合并
|
|
317
395
|
existing = self._get_raw(base_key)
|
|
@@ -322,6 +400,12 @@ class PersistentCache(Cache):
|
|
|
322
400
|
name = existing_name
|
|
323
401
|
# 合并数据
|
|
324
402
|
df = self._merge_dataframes(existing_df, df)
|
|
403
|
+
# 合并后再次确保索引是 DatetimeIndex
|
|
404
|
+
if not isinstance(df.index, pd.DatetimeIndex):
|
|
405
|
+
try:
|
|
406
|
+
df.index = pd.to_datetime(df.index)
|
|
407
|
+
except (ValueError, TypeError):
|
|
408
|
+
pass
|
|
325
409
|
|
|
326
410
|
# 获取日期范围
|
|
327
411
|
earliest_date, latest_date = self._get_dataframe_date_range(df)
|
|
@@ -338,6 +422,8 @@ class PersistentCache(Cache):
|
|
|
338
422
|
self._put_duckdb(base_key, symbol, name, df, earliest_str, latest_str, freq, fq, expire_at)
|
|
339
423
|
else:
|
|
340
424
|
self._put_pickle(base_key, symbol, name, df, earliest_str, latest_str, freq, fq, expire_at)
|
|
425
|
+
|
|
426
|
+
logger.info(f"[CACHE PUT] 存储完成, base_key={base_key}, 日期范围={earliest_str} 到 {latest_str}")
|
|
341
427
|
|
|
342
428
|
def _get_raw(self, base_key: str) -> Optional[Tuple[str, str, pd.DataFrame]]:
|
|
343
429
|
"""获取原始数据(不进行日期过滤)"""
|
|
@@ -9,6 +9,13 @@ from datetime import datetime, timedelta
|
|
|
9
9
|
from ..cache import Cache
|
|
10
10
|
from ..data_sources.base import DataSource
|
|
11
11
|
|
|
12
|
+
# 导入日志
|
|
13
|
+
try:
|
|
14
|
+
from ..utils.logging import logger
|
|
15
|
+
except ImportError:
|
|
16
|
+
import logging
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
12
19
|
# 尝试导入持久化缓存(可选依赖)
|
|
13
20
|
try:
|
|
14
21
|
from ..cache.persistent import PersistentCache
|
|
@@ -84,10 +91,23 @@ class Market(ABC):
|
|
|
84
91
|
"""标准化股票代码"""
|
|
85
92
|
pass
|
|
86
93
|
|
|
87
|
-
def _get_cached(self, key: str) -> Optional[Tuple[str, str, pd.DataFrame]]:
|
|
94
|
+
def _get_cached(self, key: str, sdate: str = '', edate: str = '') -> Optional[Tuple[str, str, pd.DataFrame]]:
|
|
88
95
|
"""从缓存获取数据"""
|
|
89
96
|
if self.cache:
|
|
90
|
-
|
|
97
|
+
# 如果是 PersistentCache,使用 base_key + 日期参数的方式
|
|
98
|
+
if PersistentCache and isinstance(self.cache, PersistentCache):
|
|
99
|
+
# 从完整 key 中提取 base_key
|
|
100
|
+
parts = key.split(':')
|
|
101
|
+
if len(parts) >= 3:
|
|
102
|
+
symbol = parts[0]
|
|
103
|
+
freq = parts[3] if len(parts) > 3 else 'day'
|
|
104
|
+
fq = parts[5] if len(parts) > 5 else 'qfq'
|
|
105
|
+
base_key = f"{symbol}:{freq}:{fq}"
|
|
106
|
+
cached = self.cache.get(base_key, sdate=sdate, edate=edate)
|
|
107
|
+
else:
|
|
108
|
+
cached = self.cache.get(key)
|
|
109
|
+
else:
|
|
110
|
+
cached = self.cache.get(key)
|
|
91
111
|
if cached:
|
|
92
112
|
return cached
|
|
93
113
|
return None
|
|
@@ -95,7 +115,20 @@ class Market(ABC):
|
|
|
95
115
|
def _put_cache(self, key: str, value: Tuple[str, str, pd.DataFrame]) -> None:
|
|
96
116
|
"""存入缓存"""
|
|
97
117
|
if self.cache:
|
|
98
|
-
|
|
118
|
+
# 如果是 PersistentCache,使用 base_key 存储
|
|
119
|
+
if PersistentCache and isinstance(self.cache, PersistentCache):
|
|
120
|
+
# 从完整 key 中提取 base_key
|
|
121
|
+
parts = key.split(':')
|
|
122
|
+
if len(parts) >= 3:
|
|
123
|
+
symbol = parts[0]
|
|
124
|
+
freq = parts[3] if len(parts) > 3 else 'day'
|
|
125
|
+
fq = parts[5] if len(parts) > 5 else 'qfq'
|
|
126
|
+
base_key = f"{symbol}:{freq}:{fq}"
|
|
127
|
+
self.cache.put(base_key, value)
|
|
128
|
+
else:
|
|
129
|
+
self.cache.put(key, value)
|
|
130
|
+
else:
|
|
131
|
+
self.cache.put(key, value)
|
|
99
132
|
|
|
100
133
|
def _get_price_with_persistent_cache(self, symbol: str, sdate: str, edate: str,
|
|
101
134
|
freq: str, days: int, fq: str,
|
|
@@ -108,14 +141,18 @@ class Market(ABC):
|
|
|
108
141
|
"""
|
|
109
142
|
cache_key = f"{symbol}:{sdate}:{edate}:{freq}:{days}:{fq}"
|
|
110
143
|
|
|
111
|
-
|
|
112
|
-
|
|
144
|
+
logger.info(f"[PRICE GET] symbol={symbol}, sdate={sdate}, edate={edate}, freq={freq}, cache_key={cache_key}")
|
|
145
|
+
|
|
146
|
+
# 尝试从缓存获取(传入日期参数,PersistentCache 会使用 base_key + 日期参数)
|
|
147
|
+
cached = self._get_cached(cache_key, sdate=sdate, edate=edate)
|
|
113
148
|
if cached:
|
|
114
149
|
_, name, cached_df = cached
|
|
150
|
+
logger.info(f"[PRICE CACHE HIT] symbol={symbol}, 缓存数据行数={len(cached_df)}, 日期范围={cached_df.index.min() if not cached_df.empty else 'N/A'} 到 {cached_df.index.max() if not cached_df.empty else 'N/A'}")
|
|
115
151
|
|
|
116
152
|
# 检查是否需要扩展
|
|
117
153
|
if cached_df.empty or not isinstance(cached_df.index, pd.DatetimeIndex):
|
|
118
154
|
# 缓存为空或索引不是日期,直接获取新数据
|
|
155
|
+
logger.info(f"[PRICE FETCH] 缓存数据无效,从网络获取 symbol={symbol}, sdate={sdate}, edate={edate}")
|
|
119
156
|
result = fetch_func(symbol, sdate, edate, freq, days, fq)
|
|
120
157
|
self._put_cache(cache_key, result)
|
|
121
158
|
return result
|
|
@@ -146,11 +183,25 @@ class Market(ABC):
|
|
|
146
183
|
|
|
147
184
|
# 如果需要扩展,获取缺失的数据
|
|
148
185
|
if need_extend_forward or need_extend_backward:
|
|
186
|
+
logger.info(f"[PRICE EXTEND] 需要扩展数据, symbol={symbol}, extend_sdate={extend_sdate}, extend_edate={extend_edate}, need_forward={need_extend_forward}, need_backward={need_extend_backward}")
|
|
149
187
|
# 获取扩展的数据
|
|
150
188
|
extended_result = fetch_func(symbol, extend_sdate, extend_edate, freq, days, fq)
|
|
151
189
|
_, _, extended_df = extended_result
|
|
190
|
+
logger.info(f"[PRICE FETCH] 从网络获取扩展数据, 数据行数={len(extended_df)}")
|
|
152
191
|
|
|
153
192
|
if not extended_df.empty:
|
|
193
|
+
# 确保两个 DataFrame 的索引都是 DatetimeIndex
|
|
194
|
+
if not isinstance(cached_df.index, pd.DatetimeIndex):
|
|
195
|
+
try:
|
|
196
|
+
cached_df.index = pd.to_datetime(cached_df.index)
|
|
197
|
+
except (ValueError, TypeError):
|
|
198
|
+
pass
|
|
199
|
+
if not isinstance(extended_df.index, pd.DatetimeIndex):
|
|
200
|
+
try:
|
|
201
|
+
extended_df.index = pd.to_datetime(extended_df.index)
|
|
202
|
+
except (ValueError, TypeError):
|
|
203
|
+
pass
|
|
204
|
+
|
|
154
205
|
# 合并数据
|
|
155
206
|
merged_df = pd.concat([cached_df, extended_df])
|
|
156
207
|
merged_df = merged_df[~merged_df.index.duplicated(keep='last')]
|
|
@@ -172,22 +223,17 @@ class Market(ABC):
|
|
|
172
223
|
return result
|
|
173
224
|
|
|
174
225
|
# 不需要扩展,直接返回缓存的数据
|
|
175
|
-
#
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
mask = (cached_df.index >= request_sdate) & (cached_df.index <= request_edate)
|
|
179
|
-
elif request_sdate:
|
|
180
|
-
mask = cached_df.index >= request_sdate
|
|
181
|
-
else:
|
|
182
|
-
mask = cached_df.index <= request_edate
|
|
183
|
-
filtered_df = cached_df[mask]
|
|
184
|
-
return (symbol, name, filtered_df)
|
|
185
|
-
|
|
226
|
+
# 注意:PersistentCache.get() 已经根据请求的日期范围进行了过滤,
|
|
227
|
+
# 返回的数据已经是过滤后的,不需要再次过滤
|
|
228
|
+
logger.info(f"[PRICE RETURN] 直接返回缓存数据, symbol={symbol}, 数据行数={len(cached_df)}")
|
|
186
229
|
return (symbol, name, cached_df)
|
|
187
230
|
|
|
188
231
|
# 缓存未命中,直接获取
|
|
189
232
|
if fetch_func:
|
|
233
|
+
logger.info(f"[PRICE FETCH] 缓存未命中,从网络获取 symbol={symbol}, sdate={sdate}, edate={edate}")
|
|
190
234
|
result = fetch_func(symbol, sdate, edate, freq, days, fq)
|
|
235
|
+
_, _, df = result
|
|
236
|
+
logger.info(f"[PRICE FETCH] 网络获取完成, 数据行数={len(df)}, 准备存储到缓存")
|
|
191
237
|
self._put_cache(cache_key, result)
|
|
192
238
|
return result
|
|
193
239
|
else:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rquote
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.1
|
|
4
4
|
Summary: Mostly day quotes of cn/hk/us/fund/future markets, side with quote list fetch
|
|
5
5
|
Requires-Python: >=3.9.0
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -18,7 +18,7 @@ Requires-Dist: duckdb>=0.9.0; extra == "persistent"
|
|
|
18
18
|
|
|
19
19
|
## 版本信息
|
|
20
20
|
|
|
21
|
-
当前版本:**0.
|
|
21
|
+
当前版本:**0.4.1**
|
|
22
22
|
|
|
23
23
|
## 主要特性
|
|
24
24
|
|
|
@@ -199,13 +199,13 @@ stocks = get_cn_stock_list(money_min=5e8)
|
|
|
199
199
|
|
|
200
200
|
#### `get_hk_stocks_500()`
|
|
201
201
|
|
|
202
|
-
获取港股前500只股票列表
|
|
202
|
+
获取港股前500只股票列表(按当日成交额排序)
|
|
203
203
|
|
|
204
204
|
```python
|
|
205
205
|
from rquote import get_hk_stocks_500
|
|
206
206
|
|
|
207
207
|
stocks = get_hk_stocks_500()
|
|
208
|
-
# 返回格式: [[code, name, price, turnover, ...], ...]
|
|
208
|
+
# 返回格式: [[code, name, price, -, -, -, -, volume, turnover, ...], ...]
|
|
209
209
|
```
|
|
210
210
|
|
|
211
211
|
#### `get_us_stocks(k=100)`
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|