qka 2.2.5.dev3__tar.gz → 2.2.6.dev2__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.
- {qka-2.2.5.dev3 → qka-2.2.6.dev2}/PKG-INFO +7 -5
- {qka-2.2.5.dev3 → qka-2.2.6.dev2}/README.md +5 -4
- {qka-2.2.5.dev3 → qka-2.2.6.dev2}/pyproject.toml +1 -0
- {qka-2.2.5.dev3 → qka-2.2.6.dev2}/qka/core/analysis.py +3 -3
- {qka-2.2.5.dev3 → qka-2.2.6.dev2}/qka/core/backtest.py +1 -1
- {qka-2.2.5.dev3 → qka-2.2.6.dev2}/qka/core/data.py +191 -29
- {qka-2.2.5.dev3 → qka-2.2.6.dev2}/qka/core/simulate.py +1 -1
- {qka-2.2.5.dev3 → qka-2.2.6.dev2}/skills/qka/references/analysis.md +7 -7
- {qka-2.2.5.dev3 → qka-2.2.6.dev2}/skills/qka/references/backtest.md +2 -2
- {qka-2.2.5.dev3 → qka-2.2.6.dev2}/skills/qka/references/broker.md +2 -2
- {qka-2.2.5.dev3 → qka-2.2.6.dev2}/skills/qka/references/data.md +23 -20
- {qka-2.2.5.dev3 → qka-2.2.6.dev2}/skills/qka/references/sizing.md +2 -2
- {qka-2.2.5.dev3 → qka-2.2.6.dev2}/skills/qka/references/strategy.md +3 -3
- {qka-2.2.5.dev3 → qka-2.2.6.dev2}/.github/workflows/release.yml +0 -0
- {qka-2.2.5.dev3 → qka-2.2.6.dev2}/.gitignore +0 -0
- {qka-2.2.5.dev3 → qka-2.2.6.dev2}/.vscode/settings.json +0 -0
- {qka-2.2.5.dev3 → qka-2.2.6.dev2}/CHANGELOG.md +0 -0
- {qka-2.2.5.dev3 → qka-2.2.6.dev2}/LICENSE +0 -0
- {qka-2.2.5.dev3 → qka-2.2.6.dev2}/qka/__init__.py +0 -0
- {qka-2.2.5.dev3 → qka-2.2.6.dev2}/qka/cli.py +0 -0
- {qka-2.2.5.dev3 → qka-2.2.6.dev2}/qka/core/__init__.py +0 -0
- {qka-2.2.5.dev3 → qka-2.2.6.dev2}/qka/core/accessor.py +0 -0
- {qka-2.2.5.dev3 → qka-2.2.6.dev2}/qka/core/broker.py +0 -0
- {qka-2.2.5.dev3 → qka-2.2.6.dev2}/qka/core/indicator.py +0 -0
- {qka-2.2.5.dev3 → qka-2.2.6.dev2}/qka/core/report.py +0 -0
- {qka-2.2.5.dev3 → qka-2.2.6.dev2}/qka/core/sizing.py +0 -0
- {qka-2.2.5.dev3 → qka-2.2.6.dev2}/qka/core/strategy.py +0 -0
- {qka-2.2.5.dev3 → qka-2.2.6.dev2}/qka/utils/__init__.py +0 -0
- {qka-2.2.5.dev3 → qka-2.2.6.dev2}/qka/utils/logger.py +0 -0
- {qka-2.2.5.dev3 → qka-2.2.6.dev2}/skills/qka/SKILL.md +0 -0
- {qka-2.2.5.dev3 → qka-2.2.6.dev2}/tools/generate_api_ref.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: qka
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.6.dev2
|
|
4
4
|
Summary: QKA(快量化 / Quant Kit for A-shares)- 简洁易用的 A 股量化回测框架
|
|
5
5
|
Project-URL: Home, https://github.com/zsrl/qka
|
|
6
6
|
Project-URL: Repository, https://github.com/zsrl/qka
|
|
@@ -40,6 +40,7 @@ Requires-Dist: plotly>=6.1.1
|
|
|
40
40
|
Requires-Dist: pyarrow>=21.0.0
|
|
41
41
|
Requires-Dist: statsmodels>=0.14.6
|
|
42
42
|
Requires-Dist: ta>=0.11.0
|
|
43
|
+
Requires-Dist: tickflow>=0.1.25
|
|
43
44
|
Requires-Dist: tqdm>=4.67.1
|
|
44
45
|
Provides-Extra: dev
|
|
45
46
|
Requires-Dist: hatch-vcs>=0.3.0; extra == 'dev'
|
|
@@ -95,7 +96,7 @@ npx skills add zsrl/qka
|
|
|
95
96
|
from qka import Data
|
|
96
97
|
|
|
97
98
|
data = Data(
|
|
98
|
-
symbols=['
|
|
99
|
+
symbols=['000001.SZ', '600000.SH'],
|
|
99
100
|
indicators={
|
|
100
101
|
'sma_5': ('ta.trend.sma_indicator', 'close', 5),
|
|
101
102
|
'rsi_14': ('ta.momentum.rsi', 'close', 14),
|
|
@@ -128,7 +129,7 @@ from qka import Backtest
|
|
|
128
129
|
|
|
129
130
|
strategy = MyStrategy()
|
|
130
131
|
bt = Backtest(data, strategy)
|
|
131
|
-
bt.run(cash=200000, start_date='2024-01-01', benchmark='
|
|
132
|
+
bt.run(cash=200000, start_date='2024-01-01', benchmark='000300.SH')
|
|
132
133
|
print(bt.metrics['total_return_pct']) # 总收益率
|
|
133
134
|
print(bt.metrics['sharpe_ratio']) # 夏普比率
|
|
134
135
|
```
|
|
@@ -156,7 +157,7 @@ print(bt.metrics['sharpe_ratio']) # 夏普比率
|
|
|
156
157
|
from qka import Data, Strategy, Backtest
|
|
157
158
|
|
|
158
159
|
data = Data(
|
|
159
|
-
symbols=['
|
|
160
|
+
symbols=['000001.SZ'],
|
|
160
161
|
indicators={
|
|
161
162
|
'sma_5': ('ta.trend.sma_indicator', 'close', 5),
|
|
162
163
|
'sma_20': ('ta.trend.sma_indicator', 'close', 20),
|
|
@@ -197,7 +198,8 @@ print(bt.metrics['total_return_pct'])
|
|
|
197
198
|
|
|
198
199
|
## 致谢
|
|
199
200
|
|
|
200
|
-
- [
|
|
201
|
+
- [tickflow](https://tickflow.org) — 免费 A 股 / ETF / 可转债行情(默认数据源)
|
|
202
|
+
- [baostock](http://baostock.com) — 免费 A 股数据(估值等扩展字段)
|
|
201
203
|
- [ta](https://github.com/bukosabino/ta) — 技术指标库
|
|
202
204
|
|
|
203
205
|
---
|
|
@@ -45,7 +45,7 @@ npx skills add zsrl/qka
|
|
|
45
45
|
from qka import Data
|
|
46
46
|
|
|
47
47
|
data = Data(
|
|
48
|
-
symbols=['
|
|
48
|
+
symbols=['000001.SZ', '600000.SH'],
|
|
49
49
|
indicators={
|
|
50
50
|
'sma_5': ('ta.trend.sma_indicator', 'close', 5),
|
|
51
51
|
'rsi_14': ('ta.momentum.rsi', 'close', 14),
|
|
@@ -78,7 +78,7 @@ from qka import Backtest
|
|
|
78
78
|
|
|
79
79
|
strategy = MyStrategy()
|
|
80
80
|
bt = Backtest(data, strategy)
|
|
81
|
-
bt.run(cash=200000, start_date='2024-01-01', benchmark='
|
|
81
|
+
bt.run(cash=200000, start_date='2024-01-01', benchmark='000300.SH')
|
|
82
82
|
print(bt.metrics['total_return_pct']) # 总收益率
|
|
83
83
|
print(bt.metrics['sharpe_ratio']) # 夏普比率
|
|
84
84
|
```
|
|
@@ -106,7 +106,7 @@ print(bt.metrics['sharpe_ratio']) # 夏普比率
|
|
|
106
106
|
from qka import Data, Strategy, Backtest
|
|
107
107
|
|
|
108
108
|
data = Data(
|
|
109
|
-
symbols=['
|
|
109
|
+
symbols=['000001.SZ'],
|
|
110
110
|
indicators={
|
|
111
111
|
'sma_5': ('ta.trend.sma_indicator', 'close', 5),
|
|
112
112
|
'sma_20': ('ta.trend.sma_indicator', 'close', 20),
|
|
@@ -147,7 +147,8 @@ print(bt.metrics['total_return_pct'])
|
|
|
147
147
|
|
|
148
148
|
## 致谢
|
|
149
149
|
|
|
150
|
-
- [
|
|
150
|
+
- [tickflow](https://tickflow.org) — 免费 A 股 / ETF / 可转债行情(默认数据源)
|
|
151
|
+
- [baostock](http://baostock.com) — 免费 A 股数据(估值等扩展字段)
|
|
151
152
|
- [ta](https://github.com/bukosabino/ta) — 技术指标库
|
|
152
153
|
|
|
153
154
|
---
|
|
@@ -31,11 +31,11 @@ class Analysis:
|
|
|
31
31
|
|
|
32
32
|
Example:
|
|
33
33
|
>>> from qka import Data, Analysis
|
|
34
|
-
>>> data = Data(symbols=['
|
|
34
|
+
>>> data = Data(symbols=['000300.SH'])
|
|
35
35
|
>>> df = data.get()
|
|
36
36
|
>>> analysis = Analysis()
|
|
37
|
-
>>> segs = analysis.zigzag(df['
|
|
38
|
-
>>> ab = analysis.alpha_beta(df['
|
|
37
|
+
>>> segs = analysis.zigzag(df['000300.SH|close'], threshold=0.3, min_days=90)
|
|
38
|
+
>>> ab = analysis.alpha_beta(df['000300.SH|returns'].dropna(),
|
|
39
39
|
... df['benchmark|returns'].dropna())
|
|
40
40
|
"""
|
|
41
41
|
|
|
@@ -188,7 +188,7 @@ class Backtest:
|
|
|
188
188
|
"""
|
|
189
189
|
try:
|
|
190
190
|
from qka.core.data import Data
|
|
191
|
-
bm_data = Data(symbols=[benchmark_code]
|
|
191
|
+
bm_data = Data(symbols=[benchmark_code])
|
|
192
192
|
bm_df = bm_data.get(lazy=False)
|
|
193
193
|
if bm_df is not None and not bm_df.empty:
|
|
194
194
|
# 提取 close 列(可能是多股票 MultiIndex,取第一只)
|
|
@@ -38,17 +38,22 @@ class Data():
|
|
|
38
38
|
通过 `indicators` 参数统一处理技术指标和自定义因子,在数据加载时一次性预计算。
|
|
39
39
|
|
|
40
40
|
Attributes:
|
|
41
|
-
symbols (List[str]):
|
|
41
|
+
symbols (List[str]): 股票代码列表,格式「代码.市场」,如 ['600519.SH', '000001.SZ']
|
|
42
|
+
(也兼容 sh.600519 旧写法,两种写法等价)
|
|
42
43
|
period (str): 数据周期,如 '1d'、'1m' 等
|
|
43
44
|
adjust (str): 复权方式,如 'qfq'、'hfq'、'bfq'
|
|
44
45
|
indicators (dict | Callable): 预计算指标/因子
|
|
45
|
-
source (str): 数据源,默认 'baostock'
|
|
46
|
+
source (str): 数据源,默认 'tickflow'(可选 'baostock')
|
|
46
47
|
pool_size (int): 并发下载线程数
|
|
47
48
|
datadir (Path): 数据缓存目录
|
|
48
49
|
target_dir (Path): 目标存储目录
|
|
49
|
-
extra_fields (List[str]):
|
|
50
|
+
extra_fields (List[str]): 扩展字段(选股/估值用),如 ['peTTM', 'pbMRQ', 'turn']。
|
|
51
|
+
仅 'baostock' 源提供(可选值见 BAOSTOCK_EXTRA_FIELDS);'tickflow' 源无扩展列,会忽略
|
|
50
52
|
"""
|
|
51
53
|
|
|
54
|
+
# 支持的行情源
|
|
55
|
+
SOURCES = ('tickflow', 'baostock')
|
|
56
|
+
|
|
52
57
|
# baostock query_history_k_data_plus 完整支持的基础字段(除 date 索引外)
|
|
53
58
|
BAOSTOCK_BASE_FIELDS = ["open", "high", "low", "close", "volume", "amount"]
|
|
54
59
|
# 可通过 extra_fields 追加的扩展字段白名单(行情/估值/选股类)
|
|
@@ -63,6 +68,9 @@ class Data():
|
|
|
63
68
|
"psTTM", # 市销率(TTM)
|
|
64
69
|
"pcfNcfTTM", # 市现率(TTM)
|
|
65
70
|
]
|
|
71
|
+
# TickFlow 可提供的扩展字段:其历史日线固定返回 OHLCV + amount,
|
|
72
|
+
# 无估值/换手率等扩展列(季报级 financials 不是每日序列,不接入)。
|
|
73
|
+
TICKFLOW_EXTRA_FIELDS = []
|
|
66
74
|
|
|
67
75
|
# ── 模拟数据源默认值 ──
|
|
68
76
|
SIM_START = '2020-01-02' # 模拟行情默认起点(调用方未给 start_date 时使用)
|
|
@@ -87,7 +95,7 @@ class Data():
|
|
|
87
95
|
benchmark: Optional[str] = None,
|
|
88
96
|
period: str = '1d',
|
|
89
97
|
adjust: str = 'qfq',
|
|
90
|
-
source: str = '
|
|
98
|
+
source: str = 'tickflow',
|
|
91
99
|
pool_size: int = 10,
|
|
92
100
|
datadir: Optional[Path] = None,
|
|
93
101
|
indicators: Optional[dict] = None,
|
|
@@ -99,13 +107,14 @@ class Data():
|
|
|
99
107
|
|
|
100
108
|
Args:
|
|
101
109
|
symbols: 标的列表,元素**可以是真实代码字符串,也可以是 Simulate 对象**。
|
|
102
|
-
|
|
110
|
+
真实代码用「代码.市场」格式,如 ['600519.SH', '000001.SZ'](同样兼容
|
|
111
|
+
sh.600519 旧写法);Simulate 对象
|
|
103
112
|
表示一只由 qka 现场生成的模拟标的,两者可以混排。写法完全同构,区别只在
|
|
104
113
|
元素本身:
|
|
105
114
|
|
|
106
115
|
```python
|
|
107
116
|
Data(symbols=[
|
|
108
|
-
'
|
|
117
|
+
'600900.SH',
|
|
109
118
|
Simulate('sim.a', drift=0.0, reversion=0.08, vol=0.018),
|
|
110
119
|
])
|
|
111
120
|
```
|
|
@@ -114,18 +123,20 @@ class Data():
|
|
|
114
123
|
一致(列名同样是 {symbol}|{field},扩展列同样是「不请求就不生成」)。
|
|
115
124
|
模拟标的的行情区间由 get(start_date, end_date) 决定 —— 用 baostock 的
|
|
116
125
|
交易日历把区间内的真实交易日取出来,一 日一根,与真实源同构。
|
|
117
|
-
benchmark: 基准代码,如 '
|
|
126
|
+
benchmark: 基准代码,如 '000300.SH'。基准数据以 benchmark| 前缀加入最终 DataFrame,
|
|
118
127
|
仅供辅助计算(β/α 等),不参与指标计算
|
|
119
128
|
period: 数据周期,如 '1d'(日线)、'1m'(分钟)
|
|
120
129
|
adjust: 复权方式,'qfq'(前复权)、'hfq'(后复权)、'bfq'(不复权)
|
|
121
|
-
source: 数据来源,默认 'baostock'
|
|
130
|
+
source: 数据来源,默认 'tickflow'(可选 'baostock')。两者返回结构一致,
|
|
131
|
+
切换源不影响上层调用;差异见各源的扩展字段能力
|
|
122
132
|
pool_size: 并发下载线程数
|
|
123
133
|
datadir: 缓存目录路径
|
|
124
134
|
indicators: 预计算指标/因子,支持三种格式:
|
|
125
|
-
extra_fields:
|
|
126
|
-
可选值见 BAOSTOCK_EXTRA_FIELDS
|
|
127
|
-
|
|
128
|
-
|
|
135
|
+
extra_fields: 扩展字段列表(选股/估值用,如 ['peTTM', 'pbMRQ', 'turn']),
|
|
136
|
+
可选值见 BAOSTOCK_EXTRA_FIELDS —— 仅 'baostock' 源提供;
|
|
137
|
+
'tickflow' 源无扩展列,传入的扩展字段会被静默忽略(不报错、不生成该列)。
|
|
138
|
+
追加的列同样遵循 {symbol}|{field} 命名,如 '600519.SH|peTTM'。注意:
|
|
139
|
+
首次下载后缓存字段固定,变更 extra_fields 会自动检测列缺失并重新下载对应股票。
|
|
129
140
|
|
|
130
141
|
warmup: 指标预热天数(默认 0)。回测/取数时自动多读取 warmup 个交易日的历史数据
|
|
131
142
|
用于计算指标,使第 1 个交易日即可拿到有效指标值,无需在策略里手写
|
|
@@ -174,18 +185,31 @@ class Data():
|
|
|
174
185
|
self.benchmark = benchmark
|
|
175
186
|
self.period = period
|
|
176
187
|
self.adjust = adjust
|
|
188
|
+
if source not in self.SOURCES:
|
|
189
|
+
raise ValueError(f"不支持的数据源: {source!r}。可选: {self.SOURCES}")
|
|
177
190
|
self.source = source
|
|
178
191
|
self.pool_size = pool_size
|
|
179
192
|
self.warmup = warmup
|
|
180
193
|
|
|
181
|
-
#
|
|
182
|
-
self.
|
|
194
|
+
# 当前数据源实际支持的扩展字段(源能力不同,按源过滤)
|
|
195
|
+
whitelist = (self.BAOSTOCK_EXTRA_FIELDS if source == 'baostock'
|
|
196
|
+
else self.TICKFLOW_EXTRA_FIELDS)
|
|
197
|
+
|
|
198
|
+
# extra_fields 校验 + 去重
|
|
199
|
+
# 未知字段(拼写错误)报错;当前源提供不了的字段静默忽略
|
|
200
|
+
# (如 tickflow 无估值列,调用方仍传 peTTM 时不报错、只是不生成该列)
|
|
201
|
+
self.extra_fields = [] # 当前源实际会下载的扩展字段
|
|
202
|
+
self._requested_extra_fields = [] # 调用方原始请求(不按源过滤,模拟标的按此生成)
|
|
183
203
|
for f in (extra_fields or []):
|
|
184
204
|
if f not in self.BAOSTOCK_EXTRA_FIELDS:
|
|
185
205
|
raise ValueError(
|
|
186
206
|
f"extra_fields 含不支持的字段: {f}。"
|
|
187
207
|
f"可选: {self.BAOSTOCK_EXTRA_FIELDS}"
|
|
188
208
|
)
|
|
209
|
+
if f not in self._requested_extra_fields:
|
|
210
|
+
self._requested_extra_fields.append(f)
|
|
211
|
+
if f not in whitelist:
|
|
212
|
+
continue
|
|
189
213
|
if f not in self.extra_fields:
|
|
190
214
|
self.extra_fields.append(f)
|
|
191
215
|
|
|
@@ -299,8 +323,10 @@ class Data():
|
|
|
299
323
|
}, index=idx)
|
|
300
324
|
|
|
301
325
|
# 4. 扩展列:只生成请求的字段(与真实源「不给就没有」一致)
|
|
326
|
+
# 用「调用方原始请求」而非 self.extra_fields —— 模拟数据由 qka 现场生成,
|
|
327
|
+
# 不依赖任何外部源,故不受数据源的扩展字段能力限制
|
|
302
328
|
log_ratio = np.log(close / close[0])
|
|
303
|
-
for field in self.
|
|
329
|
+
for field in self._requested_extra_fields:
|
|
304
330
|
if field == 'preclose':
|
|
305
331
|
df[field] = preclose
|
|
306
332
|
elif field == 'pctChg':
|
|
@@ -374,7 +400,9 @@ class Data():
|
|
|
374
400
|
existing = set(pq.read_schema(path).names)
|
|
375
401
|
except Exception:
|
|
376
402
|
return merged
|
|
377
|
-
|
|
403
|
+
whitelist = (self.BAOSTOCK_EXTRA_FIELDS if self.source == 'baostock'
|
|
404
|
+
else self.TICKFLOW_EXTRA_FIELDS)
|
|
405
|
+
for f in whitelist:
|
|
378
406
|
if f in existing and f not in merged:
|
|
379
407
|
merged.append(f)
|
|
380
408
|
return merged
|
|
@@ -411,22 +439,19 @@ class Data():
|
|
|
411
439
|
|
|
412
440
|
# ── 首次下载:只拉请求范围(缓存缺失 extra_fields 列时也全量重下)──
|
|
413
441
|
if not path.exists() or self._cache_missing_extra_fields(path):
|
|
414
|
-
df = self.
|
|
442
|
+
df = self._fetch(
|
|
415
443
|
symbol,
|
|
416
444
|
start_date=download_start or default_start,
|
|
417
445
|
end_date=download_end or default_end,
|
|
418
446
|
extra_fields=merged_extra,
|
|
419
447
|
)
|
|
420
448
|
if len(df) == 0:
|
|
421
|
-
raise RuntimeError(f"{symbol}:
|
|
449
|
+
raise RuntimeError(f"{symbol}: 数据源({self.source})返回空数据")
|
|
422
450
|
table = pa.Table.from_pandas(df)
|
|
423
451
|
pq.write_table(table, path)
|
|
424
452
|
return path
|
|
425
453
|
|
|
426
454
|
# ── 增量更新:检查缓存覆盖,补缺失范围 ──
|
|
427
|
-
if self.source != 'baostock':
|
|
428
|
-
return path
|
|
429
|
-
|
|
430
455
|
existing = pd.read_parquet(path)
|
|
431
456
|
if not isinstance(existing.index, pd.DatetimeIndex):
|
|
432
457
|
return path
|
|
@@ -440,7 +465,7 @@ class Data():
|
|
|
440
465
|
req_start = pd.Timestamp(download_start) if download_start else None
|
|
441
466
|
if req_start is not None and req_start < cache_min:
|
|
442
467
|
end_before = (cache_min - pd.Timedelta(days=1)).strftime("%Y-%m-%d")
|
|
443
|
-
df_before = self.
|
|
468
|
+
df_before = self._fetch(
|
|
444
469
|
symbol, start_date=download_start, end_date=end_before,
|
|
445
470
|
extra_fields=merged_extra,
|
|
446
471
|
)
|
|
@@ -449,10 +474,14 @@ class Data():
|
|
|
449
474
|
changed = True
|
|
450
475
|
|
|
451
476
|
# 往后补
|
|
452
|
-
|
|
477
|
+
# 口径与 _needs_download 保持一致:按「日」比较。若用带时刻的 now(),
|
|
478
|
+
# 缓存末日恰为今天时 now() > cache_max 恒成立,会去补 [明天, 今天] 这一段,
|
|
479
|
+
# 起始日期大于终止日期 ⇒ baostock 直接报错。
|
|
480
|
+
req_end = (pd.Timestamp(download_end) if download_end
|
|
481
|
+
else pd.Timestamp.now().floor('D'))
|
|
453
482
|
if req_end > cache_max:
|
|
454
483
|
start_after = (cache_max + pd.Timedelta(days=1)).strftime("%Y-%m-%d")
|
|
455
|
-
df_after = self.
|
|
484
|
+
df_after = self._fetch(
|
|
456
485
|
symbol,
|
|
457
486
|
start_date=start_after,
|
|
458
487
|
end_date=download_end or default_end,
|
|
@@ -483,8 +512,6 @@ class Data():
|
|
|
483
512
|
path = self.target_dir / f"{symbol}.parquet"
|
|
484
513
|
if not path.exists() or self._cache_missing_extra_fields(path):
|
|
485
514
|
return True
|
|
486
|
-
if self.source != 'baostock':
|
|
487
|
-
return False
|
|
488
515
|
existing = pd.read_parquet(path)
|
|
489
516
|
if not isinstance(existing.index, pd.DatetimeIndex):
|
|
490
517
|
return False
|
|
@@ -920,7 +947,7 @@ class Data():
|
|
|
920
947
|
从 baostock 获取单个股票的数据。
|
|
921
948
|
|
|
922
949
|
Args:
|
|
923
|
-
symbol:
|
|
950
|
+
symbol: 标的代码,任意写法(600519.SH 或 sh.600519),内部统一转换
|
|
924
951
|
start_date: 起始日期,格式 YYYY-MM-DD,默认 1990-01-01
|
|
925
952
|
end_date: 截止日期,格式 YYYY-MM-DD,默认 2050-12-31
|
|
926
953
|
extra_fields: 本次请求的扩展字段列表。None 时使用 self.extra_fields;
|
|
@@ -938,8 +965,11 @@ class Data():
|
|
|
938
965
|
extra = list(extra_fields) if extra_fields is not None else self.extra_fields
|
|
939
966
|
fields = ",".join(["date"] + self.BAOSTOCK_BASE_FIELDS + extra)
|
|
940
967
|
|
|
968
|
+
# baostock 只认 sh.600519 写法 ⇒ 无论调用方用哪种写法都统一转换
|
|
969
|
+
bs_symbol = self.to_baostock_symbol(symbol)
|
|
970
|
+
|
|
941
971
|
rs = bs.query_history_k_data_plus(
|
|
942
|
-
|
|
972
|
+
bs_symbol,
|
|
943
973
|
fields,
|
|
944
974
|
start_date=start_date,
|
|
945
975
|
end_date=end_date,
|
|
@@ -947,7 +977,7 @@ class Data():
|
|
|
947
977
|
adjustflag=adjustflag,
|
|
948
978
|
)
|
|
949
979
|
if rs.error_code != '0':
|
|
950
|
-
raise RuntimeError(f"baostock 查询 {symbol}({
|
|
980
|
+
raise RuntimeError(f"baostock 查询 {symbol}({bs_symbol}) 失败: {rs.error_msg}")
|
|
951
981
|
# 官网标准写法:get_row_data() 逐行取 + next() 翻页,避免 get_data() 的 df.append()
|
|
952
982
|
data_list = []
|
|
953
983
|
while (rs.error_code == '0') & rs.next():
|
|
@@ -966,3 +996,135 @@ class Data():
|
|
|
966
996
|
df["date"] = pd.to_datetime(df["date"])
|
|
967
997
|
df = df.set_index("date")
|
|
968
998
|
return df
|
|
999
|
+
|
|
1000
|
+
# ── 取数分发 ────────────────────────────────────────────────
|
|
1001
|
+
|
|
1002
|
+
def _fetch(
|
|
1003
|
+
self, symbol: str,
|
|
1004
|
+
start_date: str = '1990-01-01',
|
|
1005
|
+
end_date: str = '2050-12-31',
|
|
1006
|
+
extra_fields: Optional[List[str]] = None,
|
|
1007
|
+
) -> pd.DataFrame:
|
|
1008
|
+
"""按当前数据源分发到对应实现,返回结构一致(date 索引 + OHLCV/amount)。"""
|
|
1009
|
+
if self.source == 'baostock':
|
|
1010
|
+
return self._get_from_baostock(symbol, start_date, end_date, extra_fields)
|
|
1011
|
+
return self._get_from_tickflow(symbol, start_date, end_date, extra_fields)
|
|
1012
|
+
|
|
1013
|
+
# ── TickFlow 数据源 ────────────────────────────────────────
|
|
1014
|
+
|
|
1015
|
+
_tf_client = None # 进程级复用,避免重复初始化
|
|
1016
|
+
|
|
1017
|
+
@classmethod
|
|
1018
|
+
def _tickflow_client(cls):
|
|
1019
|
+
"""惰性创建并复用 TickFlow 客户端(当前走免费服务,无需 api_key)。"""
|
|
1020
|
+
if cls._tf_client is None:
|
|
1021
|
+
from tickflow import TickFlow
|
|
1022
|
+
cls._tf_client = TickFlow.free()
|
|
1023
|
+
return cls._tf_client
|
|
1024
|
+
|
|
1025
|
+
@staticmethod
|
|
1026
|
+
def _split_symbol(symbol: str):
|
|
1027
|
+
"""
|
|
1028
|
+
拆出 (小写市场, 代码)。两种写法等价、都支持:
|
|
1029
|
+
|
|
1030
|
+
600519.SH (推荐,数字在前)
|
|
1031
|
+
sh.600519 (旧写法,兼容保留)
|
|
1032
|
+
|
|
1033
|
+
Returns:
|
|
1034
|
+
(market, code),如 ('sh', '600519')
|
|
1035
|
+
"""
|
|
1036
|
+
if '.' not in symbol:
|
|
1037
|
+
raise ValueError(
|
|
1038
|
+
f"标的代码缺少市场标识: {symbol!r}(应形如 600519.SH 或 sh.600519)"
|
|
1039
|
+
)
|
|
1040
|
+
head, tail = symbol.split('.', 1)
|
|
1041
|
+
# 市场标识是两位字母(sh/sz/bj/hk/us),据此判断它在前还是在后
|
|
1042
|
+
if len(head) == 2 and head.isalpha():
|
|
1043
|
+
market, code = head, tail # sh.600519
|
|
1044
|
+
else:
|
|
1045
|
+
code, market = head, tail # 600519.SH
|
|
1046
|
+
return market.lower(), code
|
|
1047
|
+
|
|
1048
|
+
@classmethod
|
|
1049
|
+
def to_baostock_symbol(cls, symbol: str) -> str:
|
|
1050
|
+
"""任意写法 → baostock 写法,如 sh.600519"""
|
|
1051
|
+
market, code = cls._split_symbol(symbol)
|
|
1052
|
+
return f"{market}.{code}"
|
|
1053
|
+
|
|
1054
|
+
@classmethod
|
|
1055
|
+
def to_tickflow_symbol(cls, symbol: str) -> str:
|
|
1056
|
+
"""任意写法 → tickflow 写法,如 600519.SH"""
|
|
1057
|
+
market, code = cls._split_symbol(symbol)
|
|
1058
|
+
return f"{code}.{market.upper()}"
|
|
1059
|
+
|
|
1060
|
+
def _get_from_tickflow(
|
|
1061
|
+
self, symbol: str,
|
|
1062
|
+
start_date: str = '1990-01-01',
|
|
1063
|
+
end_date: str = '2050-12-31',
|
|
1064
|
+
extra_fields: Optional[List[str]] = None,
|
|
1065
|
+
) -> pd.DataFrame:
|
|
1066
|
+
"""
|
|
1067
|
+
从 TickFlow 获取单只标的的日线数据。
|
|
1068
|
+
|
|
1069
|
+
返回结构与 _get_from_baostock 一致(date 索引 + open/high/low/close/
|
|
1070
|
+
volume/amount),但有两处口径必须转换:
|
|
1071
|
+
- **volume**:TickFlow 单位为「手」,×100 换算成 baostock 口径的「股」
|
|
1072
|
+
- **timestamp**:TickFlow 给的是「北京时间当天零点」对应的 UTC 毫秒,
|
|
1073
|
+
需 +8 小时还原为北京日期,否则日期整体偏一天
|
|
1074
|
+
|
|
1075
|
+
extra_fields 对 TickFlow 无意义(其历史日线不含扩展列),此处不处理。
|
|
1076
|
+
"""
|
|
1077
|
+
# 周期映射(当前仅日/周/月线;分钟线 TickFlow 属付费能力,未接)
|
|
1078
|
+
period_map = {'1d': '1d', '1w': '1w', '1M': '1M'}
|
|
1079
|
+
if self.period not in period_map:
|
|
1080
|
+
raise RuntimeError(
|
|
1081
|
+
f"tickflow 源暂不支持周期 {self.period!r}(可选: {list(period_map)})"
|
|
1082
|
+
)
|
|
1083
|
+
period = period_map[self.period]
|
|
1084
|
+
|
|
1085
|
+
# 复权映射:tickflow forward=前复权 / backward=后复权 / none=不复权
|
|
1086
|
+
adjust_map = {'bfq': 'none', 'qfq': 'forward', 'hfq': 'backward'}
|
|
1087
|
+
adjust = adjust_map.get(self.adjust, 'forward')
|
|
1088
|
+
|
|
1089
|
+
tf_symbol = self.to_tickflow_symbol(symbol)
|
|
1090
|
+
|
|
1091
|
+
# 日期 → 毫秒时间戳(右端 +1 天,保证 end_date 当天落在闭区间内)
|
|
1092
|
+
start_ms = int(pd.Timestamp(start_date).timestamp() * 1000)
|
|
1093
|
+
end_ms = int((pd.Timestamp(end_date) + pd.Timedelta(days=1)).timestamp() * 1000)
|
|
1094
|
+
|
|
1095
|
+
client = self._tickflow_client()
|
|
1096
|
+
try:
|
|
1097
|
+
data = client.klines.get(
|
|
1098
|
+
tf_symbol,
|
|
1099
|
+
period=period,
|
|
1100
|
+
# count 必须显式给足:TickFlow 默认只返回最近 100 条,
|
|
1101
|
+
# 不给会静默截断长区间(全历史会只剩最近 100 个交易日)
|
|
1102
|
+
count=10000,
|
|
1103
|
+
start_time=start_ms,
|
|
1104
|
+
end_time=end_ms,
|
|
1105
|
+
adjust=adjust,
|
|
1106
|
+
)
|
|
1107
|
+
except Exception as e:
|
|
1108
|
+
raise RuntimeError(f"tickflow 查询 {symbol}({tf_symbol}) 失败: {e}")
|
|
1109
|
+
|
|
1110
|
+
ts = (data or {}).get('timestamp')
|
|
1111
|
+
if not ts:
|
|
1112
|
+
return pd.DataFrame()
|
|
1113
|
+
|
|
1114
|
+
# UTC 毫秒 → 北京日期(当天零点)
|
|
1115
|
+
idx = (pd.to_datetime(pd.Series(ts), unit='ms')
|
|
1116
|
+
+ pd.Timedelta(hours=8)).dt.normalize()
|
|
1117
|
+
idx.name = 'date'
|
|
1118
|
+
|
|
1119
|
+
df = pd.DataFrame({
|
|
1120
|
+
'open': data['open'],
|
|
1121
|
+
'high': data['high'],
|
|
1122
|
+
'low': data['low'],
|
|
1123
|
+
'close': data['close'],
|
|
1124
|
+
'volume': [v * 100 for v in data['volume']], # 手 → 股
|
|
1125
|
+
'amount': data['amount'],
|
|
1126
|
+
}, index=idx)
|
|
1127
|
+
|
|
1128
|
+
for col in self.BAOSTOCK_BASE_FIELDS:
|
|
1129
|
+
df[col] = pd.to_numeric(df[col], errors='coerce')
|
|
1130
|
+
return df
|
|
@@ -18,7 +18,7 @@ class Simulate:
|
|
|
18
18
|
from qka import Data, Simulate
|
|
19
19
|
|
|
20
20
|
data = Data(symbols=[
|
|
21
|
-
'
|
|
21
|
+
'600900.SH', # 真实标的,照常下载
|
|
22
22
|
Simulate('sim.a', drift=0.0, reversion=0.08, vol=0.018),
|
|
23
23
|
])
|
|
24
24
|
df = data.get(start_date='2023-01-01', end_date='2025-12-31')
|
|
@@ -64,7 +64,7 @@ from qka import Analysis
|
|
|
64
64
|
|
|
65
65
|
analysis = Analysis()
|
|
66
66
|
result = analysis.alpha_beta(
|
|
67
|
-
df['
|
|
67
|
+
df['000001.SZ|returns'].dropna(),
|
|
68
68
|
df['benchmark|returns'].dropna(),
|
|
69
69
|
)
|
|
70
70
|
print(f'α={result.alpha:.2%}, β={result.beta:.2f}')
|
|
@@ -93,25 +93,25 @@ print(f'α={result.alpha:.2%}, β={result.beta:.2f}')
|
|
|
93
93
|
```python
|
|
94
94
|
from qka import Data, Analysis
|
|
95
95
|
|
|
96
|
-
data = Data(symbols=['
|
|
96
|
+
data = Data(symbols=['000001.SZ'], benchmark='000300.SH')
|
|
97
97
|
df = data.get()
|
|
98
98
|
|
|
99
99
|
analysis = Analysis()
|
|
100
100
|
|
|
101
101
|
# 趋势分段
|
|
102
|
-
segs = analysis.zigzag(df['
|
|
102
|
+
segs = analysis.zigzag(df['000001.SZ|close'], threshold=0.3, min_days=90)
|
|
103
103
|
for s in segs:
|
|
104
104
|
print(f'{s.start.date()} ~ {s.end.date()} {s.direction} {s.change:+.1f}%')
|
|
105
105
|
|
|
106
106
|
# 单次指标(标量,非滚动)
|
|
107
107
|
ab = analysis.alpha_beta(
|
|
108
|
-
df['
|
|
108
|
+
df['000001.SZ|returns'].dropna(),
|
|
109
109
|
df['benchmark|returns'].dropna(),
|
|
110
110
|
)
|
|
111
111
|
print(f'α={ab.alpha:.2%}, β={ab.beta:.2f}')
|
|
112
112
|
|
|
113
|
-
sharpe = analysis.sharpe_ratio(df['
|
|
114
|
-
mdd = analysis.max_drawdown(df['
|
|
115
|
-
ir = analysis.information_ratio(df['
|
|
113
|
+
sharpe = analysis.sharpe_ratio(df['000001.SZ|returns'])
|
|
114
|
+
mdd = analysis.max_drawdown(df['000001.SZ|returns'])
|
|
115
|
+
ir = analysis.information_ratio(df['000001.SZ|returns'], df['benchmark|returns'])
|
|
116
116
|
print(f'夏普={sharpe:.2f}, 最大回撤={mdd:.1%}, IR={ir:.2f}')
|
|
117
117
|
```
|
|
@@ -28,7 +28,7 @@ bt = Backtest(data, strategy)
|
|
|
28
28
|
| `cash` | `100000.0` | 初始资金 |
|
|
29
29
|
| `start_date` | `None` | 回测起始日期 `'YYYY-MM-DD'` |
|
|
30
30
|
| `end_date` | `None` | 回测截止日期 |
|
|
31
|
-
| `benchmark` | `None` |
|
|
31
|
+
| `benchmark` | `None` | 基准指数代码,如 `'000300.SH'` |
|
|
32
32
|
|
|
33
33
|
> 500 bar 以上自动分块迭代,避免一次性加载全量数据。
|
|
34
34
|
|
|
@@ -122,7 +122,7 @@ bt.results.iloc[-1] # 最终状态
|
|
|
122
122
|
from qka import Data, Strategy, Backtest
|
|
123
123
|
|
|
124
124
|
data = Data(
|
|
125
|
-
symbols=['
|
|
125
|
+
symbols=['000001.SZ'],
|
|
126
126
|
indicators={
|
|
127
127
|
'sma_5': ('ta.trend.sma_indicator', 'close', 5),
|
|
128
128
|
'sma_20': ('ta.trend.sma_indicator', 'close', 20),
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
买入,`size` 必须是 100 的整数倍(A 股 1 手 = 100 股)。
|
|
17
17
|
|
|
18
18
|
```python
|
|
19
|
-
success = self.broker.buy('
|
|
19
|
+
success = self.broker.buy('000001.SZ', float(close['000001.SZ']), 100)
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
- 实际成交价 = `price * (1 + slippage)`(默认滑点 0.1%)
|
|
@@ -31,7 +31,7 @@ success = self.broker.buy('sz.000001', float(close['sz.000001']), 100)
|
|
|
31
31
|
卖出,`size` 必须是 100 的整数倍。
|
|
32
32
|
|
|
33
33
|
```python
|
|
34
|
-
success = self.broker.sell('
|
|
34
|
+
success = self.broker.sell('000001.SZ', float(close['000001.SZ']), 100)
|
|
35
35
|
```
|
|
36
36
|
|
|
37
37
|
- 自动扣佣金 + 印花税(万 5,仅卖出)
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
from qka import Data
|
|
11
11
|
|
|
12
12
|
data = Data(
|
|
13
|
-
symbols=['
|
|
13
|
+
symbols=['000001.SZ', '600000.SH'],
|
|
14
14
|
period='1d',
|
|
15
15
|
adjust='qfq',
|
|
16
16
|
benchmark=None,
|
|
@@ -20,12 +20,13 @@ data = Data(
|
|
|
20
20
|
|
|
21
21
|
| 参数 | 类型 | 默认值 | 说明 |
|
|
22
22
|
|------|------|--------|------|
|
|
23
|
-
| `symbols` | `list[str \| Simulate]` | `None` | 标的列表。元素可以是 A
|
|
23
|
+
| `symbols` | `list[str \| Simulate]` | `None` | 标的列表。元素可以是 A 股代码(**`代码.市场`**,如 `'000001.SZ'`、`'600000.SH'`;也兼容 `sz.000001` 旧写法),也可以是 `Simulate` 对象(模拟标的),两者可混排,见下方「模拟标的」 |
|
|
24
24
|
| `period` | `str` | `'1d'` | 数据周期,当前仅支持 `'1d'` |
|
|
25
25
|
| `adjust` | `str` | `'qfq'` | 复权方式:`'qfq'` 前复权,`'hfq'` 后复权,`'bfq'` 不复权 |
|
|
26
|
-
| `
|
|
26
|
+
| `source` | `str` | `'tickflow'` | 数据源。`'tickflow'`(默认)行情覆盖全,**含 ETF / 可转债全历史**;`'baostock'` 提供估值等扩展字段(见 `extra_fields`) |
|
|
27
|
+
| `benchmark` | `str` | `None` | 基准指数代码,如 `'000300.SH'`。下载后在 `get()` 结果中追加 `benchmark|returns` 列 |
|
|
27
28
|
| `indicators` | `dict` | `None` | 预计算指标,见下方 |
|
|
28
|
-
| `extra_fields` | `list[str]` | `None` | baostock
|
|
29
|
+
| `extra_fields` | `list[str]` | `None` | 扩展字段(选股/估值用)。**仅 `source='baostock'` 提供**;`tickflow` 源无这些列,传入会被静默忽略,见下方 |
|
|
29
30
|
| `warmup` | `int` | `0` | 指标预热天数。回测/取数时自动多读 `warmup` 个交易日历史用于计算指标,使第 1 个交易日即拿到有效指标值(无需手写跳过前 N 根 bar)。仅用于计算,不增加回测 bar 数 |
|
|
30
31
|
|
|
31
32
|
> **warmup(指标预热)**:若指标/因子需要较长历史窗口(如动量排名需看前 200 天),设 `warmup=200`。
|
|
@@ -34,7 +35,7 @@ data = Data(
|
|
|
34
35
|
>
|
|
35
36
|
> ```python
|
|
36
37
|
> data = Data(
|
|
37
|
-
> symbols=['
|
|
38
|
+
> symbols=['000001.SZ'],
|
|
38
39
|
> indicators={'mom200': lambda df: df['close'].pct_change(200)},
|
|
39
40
|
> warmup=200, # 自定义指标 qka 推断不出窗口,显式声明预热天数
|
|
40
41
|
> )
|
|
@@ -54,7 +55,7 @@ data = Data(
|
|
|
54
55
|
from qka import Data, Simulate
|
|
55
56
|
|
|
56
57
|
data = Data(symbols=[
|
|
57
|
-
'
|
|
58
|
+
'600900.SH', # 真实标的,照常下载
|
|
58
59
|
Simulate('sim.a', drift=0.0, reversion=0.08, vol=0.018), # 模拟标的
|
|
59
60
|
])
|
|
60
61
|
df = data.get(start_date='2023-01-01', end_date='2025-12-31')
|
|
@@ -95,7 +96,9 @@ r_t = drift + reversion * (logAnchor - logP_{t-1}) + vol * eps_t eps_t ~
|
|
|
95
96
|
|
|
96
97
|
### extra_fields 扩展字段
|
|
97
98
|
|
|
98
|
-
`extra_fields`
|
|
99
|
+
`extra_fields` 追加扩展列(选股/估值用),列名同样遵循 `{symbol}|{field}` 约定(如 `000001.SZ|peTTM`),数值自动转为 `float64`。**不是指标,不参与 indicators 预计算**,是随行情一起下载的原始字段。
|
|
100
|
+
|
|
101
|
+
> ⚠️ **仅 `source='baostock'` 提供**:这些扩展列来自 baostock 的 `query_history_k_data_plus` 接口。默认源 `'tickflow'` 的历史日线固定只有 OHLCV + 成交额,**没有**这些扩展列——此时传入的 `extra_fields` 会被静默忽略(不报错、也不生成对应列)。需要估值/换手率等字段时,显式用 `Data(..., source='baostock')`。
|
|
99
102
|
|
|
100
103
|
**不传 `extra_fields` 时,每只股票默认只有 6 个行情列 + 1 个常驻列:**
|
|
101
104
|
|
|
@@ -113,11 +116,11 @@ r_t = drift + reversion * (logAnchor - logP_{t-1}) + vol * eps_t eps_t ~
|
|
|
113
116
|
|
|
114
117
|
```python
|
|
115
118
|
data = Data(
|
|
116
|
-
symbols=['
|
|
119
|
+
symbols=['000001.SZ'],
|
|
117
120
|
extra_fields=['peTTM', 'pbMRQ', 'turn'], # 选股常用:估值 + 换手率
|
|
118
121
|
)
|
|
119
122
|
df = data.get(start_date='2024-01-02', end_date='2024-01-05')
|
|
120
|
-
df['
|
|
123
|
+
df['000001.SZ|peTTM'] # 市盈率(TTM),float64
|
|
121
124
|
```
|
|
122
125
|
|
|
123
126
|
可用的扩展字段(白名单,传其他字段会抛 `ValueError`):
|
|
@@ -153,7 +156,7 @@ df['sz.000001|peTTM'] # 市盈率(TTM),float64
|
|
|
153
156
|
|
|
154
157
|
```python
|
|
155
158
|
data = Data(
|
|
156
|
-
symbols=['
|
|
159
|
+
symbols=['000001.SZ'],
|
|
157
160
|
indicators={
|
|
158
161
|
'sma_5': ('ta.trend.sma_indicator', 'close', 5),
|
|
159
162
|
'sma_20': ('ta.trend.sma_indicator', 'close', 20),
|
|
@@ -277,8 +280,8 @@ qka 框架内置的滚动窗口指标,格式为 `{'列名': ('qka.函数名',
|
|
|
277
280
|
|
|
278
281
|
```python
|
|
279
282
|
data = Data(
|
|
280
|
-
symbols=['
|
|
281
|
-
benchmark='
|
|
283
|
+
symbols=['000001.SZ', '600000.SH'],
|
|
284
|
+
benchmark='000300.SH', # alpha/beta/information_ratio 必需
|
|
282
285
|
indicators={
|
|
283
286
|
'sma_20': ('ta.trend.sma_indicator', 'close', 20),
|
|
284
287
|
'beta_60': ('qka.beta', 60),
|
|
@@ -326,9 +329,9 @@ df = data.get(start_date='2024-01-01', end_date='2024-12-31')
|
|
|
326
329
|
|------|------|
|
|
327
330
|
| 类型 | `pd.DataFrame`(`lazy=True` 时返回 `dask.DataFrame`) |
|
|
328
331
|
| 索引 | 日期索引,**索引名为 `"date"`**。`reset_index()` 后日期列名也是 `"date"` |
|
|
329
|
-
| 列名 | `{symbol}|{factor}` — 例如 `
|
|
332
|
+
| 列名 | `{symbol}|{factor}` — 例如 `000001.SZ|close`、`000001.SZ|sma_5`、`600000.SH|volume` |
|
|
330
333
|
| 常驻列 | 除 `open/high/low/close/volume/amount` 外,自动内置 `{symbol}|returns` |
|
|
331
|
-
| 扩展列 | 构造时设了 `extra_fields` 时,追加 `{symbol}|{field}` 原始字段列(如 `
|
|
334
|
+
| 扩展列 | 构造时设了 `extra_fields` 时,追加 `{symbol}|{field}` 原始字段列(如 `000001.SZ|peTTM`),见上方 extra_fields 小节 |
|
|
332
335
|
| 基准列 | 若构造时设了 `benchmark`,追加 `benchmark|returns`(无 `{symbol}|` 前缀) |
|
|
333
336
|
| 列值 | 全部为 `float64`,指标列的早期行可能含 `NaN` |
|
|
334
337
|
| 异常 | 无数据时抛出 `RuntimeError` |
|
|
@@ -336,7 +339,7 @@ df = data.get(start_date='2024-01-01', end_date='2024-12-31')
|
|
|
336
339
|
**返回的宽表示例:**
|
|
337
340
|
|
|
338
341
|
```python
|
|
339
|
-
data = Data(symbols=['
|
|
342
|
+
data = Data(symbols=['000001.SZ', '600000.SH'], indicators={
|
|
340
343
|
'sma_5': ('ta.trend.sma_indicator', 'close', 5),
|
|
341
344
|
})
|
|
342
345
|
df = data.get(start_date='2024-01-02', end_date='2024-01-05')
|
|
@@ -345,7 +348,7 @@ df = data.get(start_date='2024-01-02', end_date='2024-01-05')
|
|
|
345
348
|
返回的 DataFrame 结构(行=日期,列=每只股票的完整字段堆叠):
|
|
346
349
|
|
|
347
350
|
```
|
|
348
|
-
|
|
351
|
+
000001.SZ|open 000001.SZ|close ... 000001.SZ|sma_5 600000.SH|open 600000.SH|close ... 600000.SH|sma_5
|
|
349
352
|
2024-01-02 10.0 10.2 ... NaN 15.0 15.3 ... NaN
|
|
350
353
|
2024-01-03 10.1 10.5 ... NaN 15.2 15.6 ... NaN
|
|
351
354
|
2024-01-04 10.3 10.8 ... NaN 14.9 15.1 ... NaN
|
|
@@ -355,13 +358,13 @@ df = data.get(start_date='2024-01-02', end_date='2024-01-05')
|
|
|
355
358
|
- 每只股票独占一组列,列前缀 = symbol
|
|
356
359
|
- `returns` 列自动存在,无需在 indicators 中声明
|
|
357
360
|
- 前 4 行 SMA 为 NaN(窗口=5,不足)
|
|
358
|
-
- 若设了 `benchmark='
|
|
361
|
+
- 若设了 `benchmark='000300.SH'`,末尾多一列 `benchmark|returns`
|
|
359
362
|
|
|
360
363
|
```python
|
|
361
364
|
# 列名格式:{symbol}|{factor}
|
|
362
|
-
df.columns # ['
|
|
363
|
-
# '
|
|
364
|
-
# '
|
|
365
|
+
df.columns # ['000001.SZ|open', '000001.SZ|close', '000001.SZ|returns',
|
|
366
|
+
# '000001.SZ|sma_5', '600000.SH|open', '600000.SH|close',
|
|
367
|
+
# '600000.SH|returns', '600000.SH|sma_5']
|
|
365
368
|
|
|
366
369
|
# 索引名为 "date",reset_index 后转为 pd.Timestamp 列
|
|
367
370
|
df = df.reset_index()
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
| `atr_risk(risk_ratio, price, atr_value, multiplier=2.0)` | ATR 风险仓位 |
|
|
11
11
|
|
|
12
12
|
```python
|
|
13
|
-
price = float(close['
|
|
13
|
+
price = float(close['000001.SZ'])
|
|
14
14
|
size = self.sizing.percent(0.1, price) # 10% 仓位,已按手取整
|
|
15
15
|
if size > 0:
|
|
16
|
-
self.broker.buy('
|
|
16
|
+
self.broker.buy('000001.SZ', price, size)
|
|
17
17
|
```
|
|
@@ -49,14 +49,14 @@ sma5 = self.get('sma_5') # indicators 中定义的指标列
|
|
|
49
49
|
| 属性 | 说明 |
|
|
50
50
|
|------|------|
|
|
51
51
|
| 返回类型 | `pd.Series` |
|
|
52
|
-
| index | 股票代码,如 `'
|
|
52
|
+
| index | 股票代码,如 `'000001.SZ'`、`'600000.SH'` |
|
|
53
53
|
| values | 当前 bar 的最新值,`float` |
|
|
54
54
|
| 空值 | 无数据时返回空 `pd.Series`,不是 `None` |
|
|
55
55
|
|
|
56
56
|
```python
|
|
57
57
|
# 安全访问
|
|
58
|
-
if '
|
|
59
|
-
price = float(close['
|
|
58
|
+
if '000001.SZ' in close.index:
|
|
59
|
+
price = float(close['000001.SZ'])
|
|
60
60
|
```
|
|
61
61
|
|
|
62
62
|
### self.history()
|
|
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
|