qka 2.4.1.dev2__tar.gz → 2.4.2__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.4.1.dev2 → qka-2.4.2}/PKG-INFO +2 -1
- {qka-2.4.1.dev2 → qka-2.4.2}/pyproject.toml +1 -0
- {qka-2.4.1.dev2 → qka-2.4.2}/qka/core/data.py +156 -25
- {qka-2.4.1.dev2 → qka-2.4.2}/skills/qka/references/data.md +21 -0
- {qka-2.4.1.dev2 → qka-2.4.2}/.github/workflows/release.yml +0 -0
- {qka-2.4.1.dev2 → qka-2.4.2}/.gitignore +0 -0
- {qka-2.4.1.dev2 → qka-2.4.2}/.vscode/settings.json +0 -0
- {qka-2.4.1.dev2 → qka-2.4.2}/CHANGELOG.md +0 -0
- {qka-2.4.1.dev2 → qka-2.4.2}/LICENSE +0 -0
- {qka-2.4.1.dev2 → qka-2.4.2}/README.md +0 -0
- {qka-2.4.1.dev2 → qka-2.4.2}/qka/__init__.py +0 -0
- {qka-2.4.1.dev2 → qka-2.4.2}/qka/cli.py +0 -0
- {qka-2.4.1.dev2 → qka-2.4.2}/qka/core/__init__.py +0 -0
- {qka-2.4.1.dev2 → qka-2.4.2}/qka/core/accessor.py +0 -0
- {qka-2.4.1.dev2 → qka-2.4.2}/qka/core/analysis.py +0 -0
- {qka-2.4.1.dev2 → qka-2.4.2}/qka/core/backtest.py +0 -0
- {qka-2.4.1.dev2 → qka-2.4.2}/qka/core/broker.py +0 -0
- {qka-2.4.1.dev2 → qka-2.4.2}/qka/core/indicator.py +0 -0
- {qka-2.4.1.dev2 → qka-2.4.2}/qka/core/report.py +0 -0
- {qka-2.4.1.dev2 → qka-2.4.2}/qka/core/simulate.py +0 -0
- {qka-2.4.1.dev2 → qka-2.4.2}/qka/core/sizing.py +0 -0
- {qka-2.4.1.dev2 → qka-2.4.2}/qka/core/strategy.py +0 -0
- {qka-2.4.1.dev2 → qka-2.4.2}/qka/core/tdx.py +0 -0
- {qka-2.4.1.dev2 → qka-2.4.2}/qka/utils/__init__.py +0 -0
- {qka-2.4.1.dev2 → qka-2.4.2}/qka/utils/logger.py +0 -0
- {qka-2.4.1.dev2 → qka-2.4.2}/skills/qka/SKILL.md +0 -0
- {qka-2.4.1.dev2 → qka-2.4.2}/skills/qka/references/analysis.md +0 -0
- {qka-2.4.1.dev2 → qka-2.4.2}/skills/qka/references/backtest.md +0 -0
- {qka-2.4.1.dev2 → qka-2.4.2}/skills/qka/references/broker.md +0 -0
- {qka-2.4.1.dev2 → qka-2.4.2}/skills/qka/references/sizing.md +0 -0
- {qka-2.4.1.dev2 → qka-2.4.2}/skills/qka/references/strategy.md +0 -0
- {qka-2.4.1.dev2 → qka-2.4.2}/tools/generate_api_ref.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: qka
|
|
3
|
-
Version: 2.4.
|
|
3
|
+
Version: 2.4.2
|
|
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
|
|
@@ -38,6 +38,7 @@ Requires-Dist: baostock>=0.9.1
|
|
|
38
38
|
Requires-Dist: dask[dataframe]>=2025.7.0
|
|
39
39
|
Requires-Dist: plotly>=6.1.1
|
|
40
40
|
Requires-Dist: pyarrow>=21.0.0
|
|
41
|
+
Requires-Dist: requests>=2.31.0
|
|
41
42
|
Requires-Dist: statsmodels>=0.14.6
|
|
42
43
|
Requires-Dist: ta>=0.11.0
|
|
43
44
|
Requires-Dist: tickflow>=0.1.25
|
|
@@ -153,6 +153,7 @@ class Data():
|
|
|
153
153
|
extra_fields: Optional[List[str]] = None,
|
|
154
154
|
warmup: int = 0,
|
|
155
155
|
factor_cache: bool = False,
|
|
156
|
+
fill_suspended: bool = False,
|
|
156
157
|
):
|
|
157
158
|
"""
|
|
158
159
|
初始化数据对象
|
|
@@ -229,6 +230,13 @@ class Data():
|
|
|
229
230
|
详见 `skills/qka/references/data.md` 的「因子缓存」。
|
|
230
231
|
只作用于默认(`lazy=False`)路径。
|
|
231
232
|
|
|
233
|
+
fill_suspended: 是否把**停牌日按前收补齐**(默认 False)。
|
|
234
|
+
各标的只在自己的交易日有 K 线,对齐到同一张宽表后停牌日就是空缺;
|
|
235
|
+
滚动类因子(α/β/波动率……)默认要求窗口内一天都不能缺,**停过一天牌
|
|
236
|
+
就会整条算不出来**。开启后:价格四列取前一交易日的值(当天收益率
|
|
237
|
+
变成 0),`returns` 按补齐后的 close 重算;**上市前的空缺仍然留空**。
|
|
238
|
+
只作用于默认(`lazy=False`)路径。
|
|
239
|
+
|
|
232
240
|
"""
|
|
233
241
|
# ── 归一化 symbols:str = 真实标的,Simulate 对象 = 模拟标的,可混排 ──
|
|
234
242
|
names, sim_specs = [], {}
|
|
@@ -261,6 +269,8 @@ class Data():
|
|
|
261
269
|
self.warmup = warmup
|
|
262
270
|
# 因子缓存:默认关。要用得显式开(见 _factors_with_cache)
|
|
263
271
|
self.factor_cache = bool(factor_cache)
|
|
272
|
+
# 停牌日按前收填充:默认关。见 _fill_suspended_prices
|
|
273
|
+
self.fill_suspended = bool(fill_suspended)
|
|
264
274
|
|
|
265
275
|
# 当前数据源实际支持的扩展字段(源能力不同,按源过滤)
|
|
266
276
|
whitelist = self._extra_fields_whitelist(source)
|
|
@@ -935,6 +945,10 @@ class Data():
|
|
|
935
945
|
ddf = dd.concat(dfs, axis=1, join='outer')
|
|
936
946
|
result = ddf.compute()
|
|
937
947
|
|
|
948
|
+
# 停牌日按前收补齐(默认关,见 _fill_suspended_prices)。必须在算因子之前 ——
|
|
949
|
+
# 否则因子看到的还是带空缺的序列,长窗口照样算不出来。
|
|
950
|
+
result = self._fill_suspended_prices(result)
|
|
951
|
+
|
|
938
952
|
# 因子:逐票逐因子贴上(带缓存 —— 全盖住就不算,只缺尾巴就只补尾巴)
|
|
939
953
|
# 注意在切片之前算:此时还带着预热数据,滚动窗口才够长。
|
|
940
954
|
result = self._factors_with_cache(result)
|
|
@@ -1182,6 +1196,59 @@ class Data():
|
|
|
1182
1196
|
FACTOR_CACHE_VERSION = 1
|
|
1183
1197
|
_UNSET = object()
|
|
1184
1198
|
|
|
1199
|
+
# 停牌日要按前收填充的列(价格类)。停牌当天没有成交,价格就跟前一天一样;
|
|
1200
|
+
# volume / amount 不填 —— 那天确实没有成交量,硬填反而不诚实。
|
|
1201
|
+
SUSPENDED_FILL_COLUMNS = ('open', 'high', 'low', 'close')
|
|
1202
|
+
|
|
1203
|
+
def _fill_suspended_prices(self, result):
|
|
1204
|
+
"""把停牌日按「前收」补齐:当天价格 = 前一个交易日的收盘价。
|
|
1205
|
+
|
|
1206
|
+
为什么需要:各标的只在自己的交易日有 K 线,对齐到同一张宽表后,**停牌日就是空缺**。
|
|
1207
|
+
而滚动类因子(α/β/波动率……)默认要求窗口内一天都不能缺 —— 只要这只票在过去 N 天里
|
|
1208
|
+
停过一天牌,指标就整条算不出来。
|
|
1209
|
+
|
|
1210
|
+
填充口径:价格四列取前一交易日的值(当天收益率因此为 0,等价于「那天没交易」);
|
|
1211
|
+
`returns` 按填充后的 close 重算。**上市前的空缺没有前值,仍然留空** ——
|
|
1212
|
+
不会凭空造出一段历史。
|
|
1213
|
+
|
|
1214
|
+
只作用于默认(`lazy=False`)路径,由 `fill_suspended=True` 显式开启。
|
|
1215
|
+
"""
|
|
1216
|
+
if not self.fill_suspended:
|
|
1217
|
+
return result
|
|
1218
|
+
col_index = self._columns_by_symbol(result.columns)
|
|
1219
|
+
replacements = {}
|
|
1220
|
+
for symbol in self.symbols:
|
|
1221
|
+
sym_cols = col_index.get(symbol)
|
|
1222
|
+
if not sym_cols:
|
|
1223
|
+
continue
|
|
1224
|
+
cols = [
|
|
1225
|
+
c for c in sym_cols
|
|
1226
|
+
if c.split('|', 1)[1] in self.SUSPENDED_FILL_COLUMNS
|
|
1227
|
+
]
|
|
1228
|
+
if not cols:
|
|
1229
|
+
continue
|
|
1230
|
+
before = result[cols]
|
|
1231
|
+
filled = before.ffill()
|
|
1232
|
+
if filled.equals(before):
|
|
1233
|
+
continue # 这只票一次没停过牌,什么都别动(省掉重算 returns)
|
|
1234
|
+
for c in cols:
|
|
1235
|
+
replacements[c] = filled[c]
|
|
1236
|
+
if f'{symbol}|close' in cols:
|
|
1237
|
+
# 口径与 get() 里一致:diff/shift,不用 pct_change(它的 fill_method 默认值在弃用中)
|
|
1238
|
+
close = filled[f'{symbol}|close']
|
|
1239
|
+
replacements[f'{symbol}|returns'] = close.diff() / close.shift(1)
|
|
1240
|
+
if not replacements:
|
|
1241
|
+
return result
|
|
1242
|
+
# 一次性换掉,而不是每票各写两遍(全市场就是上万次逐列插入)。
|
|
1243
|
+
# 拼回后按原列序重排,保证列顺序一点不变。
|
|
1244
|
+
names = list(replacements)
|
|
1245
|
+
merged = pd.concat(
|
|
1246
|
+
[result.drop(columns=names),
|
|
1247
|
+
pd.DataFrame(replacements, index=result.index)],
|
|
1248
|
+
axis=1,
|
|
1249
|
+
)
|
|
1250
|
+
return merged[result.columns]
|
|
1251
|
+
|
|
1185
1252
|
def _factor_plan(self):
|
|
1186
1253
|
"""{因子名: (定义, 指纹)};只要有因子认不出配方就整体返回 None(这次不缓存)。
|
|
1187
1254
|
|
|
@@ -1207,7 +1274,11 @@ class Data():
|
|
|
1207
1274
|
return plan
|
|
1208
1275
|
|
|
1209
1276
|
def _factor_signature(self, name, spec):
|
|
1210
|
-
"""单个因子的配方指纹(定义 + 数据口径);认不出返回 None。
|
|
1277
|
+
"""单个因子的配方指纹(定义 + 数据口径);认不出返回 None。
|
|
1278
|
+
|
|
1279
|
+
数据口径 = 数据源 / 周期 / 复权 / **停牌是否补齐** —— 任一样变了,
|
|
1280
|
+
算出来的值就不一样,指纹必须跟着变,否则缓存会拿旧口径的值冒充新值。
|
|
1281
|
+
"""
|
|
1211
1282
|
payload = self._spec_payload(spec)
|
|
1212
1283
|
if payload is None:
|
|
1213
1284
|
return None
|
|
@@ -1218,6 +1289,7 @@ class Data():
|
|
|
1218
1289
|
'source': self.source,
|
|
1219
1290
|
'period': self.period,
|
|
1220
1291
|
'adjust': self.adjust,
|
|
1292
|
+
'fill_suspended': self.fill_suspended,
|
|
1221
1293
|
}, sort_keys=True, ensure_ascii=False)
|
|
1222
1294
|
return hashlib.sha1(blob.encode('utf-8')).hexdigest()
|
|
1223
1295
|
|
|
@@ -1314,19 +1386,56 @@ class Data():
|
|
|
1314
1386
|
return tail
|
|
1315
1387
|
|
|
1316
1388
|
@staticmethod
|
|
1317
|
-
def
|
|
1318
|
-
"""
|
|
1389
|
+
def _columns_by_symbol(columns):
|
|
1390
|
+
"""一次遍历建「票 → 它有哪些列」的索引。
|
|
1391
|
+
|
|
1392
|
+
全市场宽表 6 万列,而下面几处都是按票走的:若每票都重新扫一遍全部列名,
|
|
1393
|
+
就是 7556 × 6 万次字符串比较 —— 光这一项就足以把整轮跑批拖垮。
|
|
1394
|
+
"""
|
|
1395
|
+
groups = {}
|
|
1396
|
+
for col in columns:
|
|
1397
|
+
if '|' in col:
|
|
1398
|
+
groups.setdefault(col.split('|', 1)[0], []).append(col)
|
|
1399
|
+
return groups
|
|
1400
|
+
|
|
1401
|
+
@staticmethod
|
|
1402
|
+
def _symbol_slice(result, symbol, cols=None):
|
|
1403
|
+
"""从宽表里取出该票的列、去掉 {symbol}| 前缀(缺失返回空表)。
|
|
1404
|
+
|
|
1405
|
+
`cols` 传 ``_columns_by_symbol`` 里该票那一份,省掉一次全表扫描。
|
|
1406
|
+
"""
|
|
1319
1407
|
prefix = f'{symbol}|'
|
|
1320
|
-
cols
|
|
1408
|
+
if cols is None:
|
|
1409
|
+
cols = [c for c in result.columns if c.startswith(prefix)]
|
|
1321
1410
|
sub = result[cols].copy()
|
|
1322
1411
|
sub.columns = [c[len(prefix):] for c in cols]
|
|
1323
1412
|
return sub
|
|
1324
1413
|
|
|
1325
1414
|
@staticmethod
|
|
1326
|
-
def
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1415
|
+
def _collect_slice(symbol, sub, names):
|
|
1416
|
+
"""把该票新算出来的列收进 ``{列名: 序列}``,交给调用方最后一次性拼上。
|
|
1417
|
+
|
|
1418
|
+
逐列写进结果表会让 pandas 在内存里碎成几万块(每插一列就可能多出一个
|
|
1419
|
+
block),此后每次读写都得逐个遍历这些块。收集起来一次 concat,块数就稳住。
|
|
1420
|
+
"""
|
|
1421
|
+
prefix = f'{symbol}|'
|
|
1422
|
+
return {
|
|
1423
|
+
f'{prefix}{name}': sub[name]
|
|
1424
|
+
for name in names
|
|
1425
|
+
if name in sub.columns
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
@staticmethod
|
|
1429
|
+
def _attach_columns(result, collected):
|
|
1430
|
+
"""把收集到的列**一次**拼上。
|
|
1431
|
+
|
|
1432
|
+
这是整套流程里唯一「往结果表加列」的动作 —— 一次 concat 取代原先的
|
|
1433
|
+
几万次逐列插入。
|
|
1434
|
+
"""
|
|
1435
|
+
if not collected:
|
|
1436
|
+
return result
|
|
1437
|
+
new = pd.DataFrame(collected, index=result.index)
|
|
1438
|
+
return pd.concat([result, new], axis=1)
|
|
1330
1439
|
|
|
1331
1440
|
def _qka_factor_names(self):
|
|
1332
1441
|
"""`self._factors` 里属于 qka 内置因子(α/β/夏普…)的列名,按声明顺序。"""
|
|
@@ -1365,16 +1474,22 @@ class Data():
|
|
|
1365
1474
|
return result[order]
|
|
1366
1475
|
|
|
1367
1476
|
def _factors_with_cache(self, result):
|
|
1368
|
-
"""逐票把 ta /
|
|
1477
|
+
"""逐票把 ta / 自定义因子算出来:命中缓存直接用,没命中才现算并写回。
|
|
1369
1478
|
|
|
1370
1479
|
只用于默认(非懒加载)路径。懒加载路径行为完全不变。
|
|
1480
|
+
新列先收在 collected 里,循环结束一次拼上(见 `_attach_columns`)。
|
|
1371
1481
|
"""
|
|
1372
1482
|
if not self._factors:
|
|
1373
1483
|
return result
|
|
1374
1484
|
# 没开因子缓存 → plan 强制为 None,走下面「现算不落盘」那条路
|
|
1375
1485
|
plan = self._factor_plan() if self.factor_cache else None
|
|
1486
|
+
col_index = self._columns_by_symbol(result.columns)
|
|
1487
|
+
collected = {}
|
|
1376
1488
|
for symbol in self.symbols:
|
|
1377
|
-
|
|
1489
|
+
cols = col_index.get(symbol)
|
|
1490
|
+
if not cols:
|
|
1491
|
+
continue
|
|
1492
|
+
sub = self._symbol_slice(result, symbol, cols)
|
|
1378
1493
|
if sub.empty:
|
|
1379
1494
|
continue
|
|
1380
1495
|
|
|
@@ -1382,9 +1497,9 @@ class Data():
|
|
|
1382
1497
|
if plan is None or symbol in self._sim_specs:
|
|
1383
1498
|
before = set(sub.columns)
|
|
1384
1499
|
sub = self._apply_factors(sub)
|
|
1385
|
-
self.
|
|
1386
|
-
|
|
1387
|
-
)
|
|
1500
|
+
collected.update(self._collect_slice(
|
|
1501
|
+
symbol, sub, [c for c in sub.columns if c not in before]
|
|
1502
|
+
))
|
|
1388
1503
|
continue
|
|
1389
1504
|
|
|
1390
1505
|
names = [
|
|
@@ -1409,11 +1524,16 @@ class Data():
|
|
|
1409
1524
|
)
|
|
1410
1525
|
sub[name] = series
|
|
1411
1526
|
self._write_factor_cache(name, symbol, fp, series)
|
|
1412
|
-
self.
|
|
1413
|
-
return result
|
|
1527
|
+
collected.update(self._collect_slice(symbol, sub, names))
|
|
1528
|
+
return self._attach_columns(result, collected)
|
|
1414
1529
|
|
|
1415
1530
|
def _qka_factors_with_cache(self, result):
|
|
1416
|
-
"""逐票把 qka
|
|
1531
|
+
"""逐票把 qka 内置因子(α/β/夏普…)算出来;开了因子缓存才吃缓存。需在 benchmark 之后调用。
|
|
1532
|
+
|
|
1533
|
+
与 `_factors_with_cache` 一样:新列先收集、最后一次性拼上。另外这里不再
|
|
1534
|
+
拿整张宽表当计算台 —— 内置因子只看该票的 returns 加基准,给它一张只含
|
|
1535
|
+
这几列的小表就够了(宽表 6 万列,逐票拿它来算太贵)。
|
|
1536
|
+
"""
|
|
1417
1537
|
if not self.factor_cache:
|
|
1418
1538
|
return self._apply_qka_factors(result)
|
|
1419
1539
|
plan = self._factor_plan()
|
|
@@ -1426,33 +1546,44 @@ class Data():
|
|
|
1426
1546
|
if not names:
|
|
1427
1547
|
return self._apply_qka_factors(result)
|
|
1428
1548
|
|
|
1549
|
+
col_index = self._columns_by_symbol(result.columns)
|
|
1550
|
+
bench_cols = col_index.get('benchmark', [])
|
|
1551
|
+
collected = {}
|
|
1429
1552
|
for name in names:
|
|
1430
1553
|
spec, fp = plan[name]
|
|
1431
1554
|
# 按「先因子名、再标的」的顺序贴,保持与重构前一致
|
|
1432
1555
|
for symbol in self.symbols:
|
|
1433
|
-
|
|
1556
|
+
sym_cols = col_index.get(symbol)
|
|
1557
|
+
if not sym_cols or f'{symbol}|returns' not in result.columns:
|
|
1434
1558
|
continue
|
|
1435
1559
|
col = f'{symbol}|{name}'
|
|
1560
|
+
# 明确复制一份当计算台:内置因子要往它上面加列,而它是从宽表
|
|
1561
|
+
# 切出来的视图 —— 不复制的话 pandas 每次都会警告「在切片副本上赋值」。
|
|
1562
|
+
view = result[sym_cols + bench_cols].copy()
|
|
1563
|
+
if view.empty:
|
|
1564
|
+
continue
|
|
1436
1565
|
if symbol in self._sim_specs:
|
|
1437
1566
|
# 模拟标的没有缓存文件,直接算
|
|
1438
|
-
self._compute_qka_factor_cols(
|
|
1567
|
+
self._compute_qka_factor_cols(view, [(name, spec)], symbols=[symbol])
|
|
1568
|
+
collected[col] = view[col]
|
|
1439
1569
|
continue
|
|
1440
1570
|
cached = self._read_factor_cache(name, symbol, fp)
|
|
1441
|
-
target = self._recompute_target(
|
|
1571
|
+
target = self._recompute_target(view, cached)
|
|
1442
1572
|
if target is None:
|
|
1443
|
-
|
|
1573
|
+
collected[col] = cached.reindex(result.index)
|
|
1444
1574
|
continue
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1575
|
+
# 这里给的是一张只有该票的小表,必须把标的限定成它自己
|
|
1576
|
+
self._compute_qka_factor_cols(target, [(name, spec)], symbols=[symbol])
|
|
1577
|
+
if target is view:
|
|
1578
|
+
series = view[col]
|
|
1448
1579
|
else:
|
|
1449
1580
|
# 同上:补算尾巴头部的预热 NaN 用缓存老值兜住
|
|
1450
1581
|
series = target[col].reindex(result.index).combine_first(
|
|
1451
1582
|
cached.reindex(result.index)
|
|
1452
1583
|
)
|
|
1453
|
-
|
|
1584
|
+
collected[col] = series
|
|
1454
1585
|
self._write_factor_cache(name, symbol, fp, series)
|
|
1455
|
-
return result
|
|
1586
|
+
return self._attach_columns(result, collected)
|
|
1456
1587
|
|
|
1457
1588
|
def _get_from_baostock(
|
|
1458
1589
|
self, symbol: str,
|
|
@@ -29,6 +29,7 @@ data = Data(
|
|
|
29
29
|
| `extra_fields` | `list[str]` | `None` | 扩展字段(选股/估值用)。**仅 `source='baostock'` 提供**;`tickflow` / `tdx` 源无这些列,传入会被静默忽略,见下方 |
|
|
30
30
|
| `warmup` | `int` | `0` | 因子预热天数。回测/取数时自动多读 `warmup` 个交易日历史用于计算因子,使第 1 个交易日即拿到有效值(无需手写跳过前 N 根 bar)。仅用于计算,不增加回测 bar 数 |
|
|
31
31
|
| `factor_cache` | `bool` | `False` | 是否把算出来的因子**纵向落盘**(默认关)。开了才读写 `datadir/factors/…`,见下方「因子缓存」 |
|
|
32
|
+
| `fill_suspended` | `bool` | `False` | 是否把**停牌日按前收补齐**(默认关)。滚动类因子要求窗口内一天不缺,停过牌就整条算不出来,见下方「停牌日」 |
|
|
32
33
|
|
|
33
34
|
> **warmup(因子预热)**:若因子需要较长历史窗口(如动量排名需看前 200 天),设 `warmup=200`。
|
|
34
35
|
> qka 会在 `[start_date, end_date]` 之前额外多读 `warmup` 天数据用于计算因子,但返回和回测仍严格从
|
|
@@ -61,6 +62,26 @@ data = Data(
|
|
|
61
62
|
|
|
62
63
|
代价:前复权下只要请求碰到缓存之外(最常见的是要看最新一根),就会重新下载整段历史;请求完全落在缓存内时,仍然直接读缓存。
|
|
63
64
|
|
|
65
|
+
### 停牌日
|
|
66
|
+
|
|
67
|
+
**默认关闭**,要用得显式开:`Data(..., fill_suspended=True)`。
|
|
68
|
+
|
|
69
|
+
各标的只在自己的交易日有 K 线。对齐到同一张宽表(多标的)之后,**别人在交易、它停牌的那些天就是空缺**。
|
|
70
|
+
|
|
71
|
+
问题在于:滚动类因子(α / β / 夏普 / 波动率……)内部是 `rolling(window)`,**默认要求窗口内一天都不能缺** ⇒ 只要这只票在过去 N 天里停过一天牌,这个因子就整条算不出来(哪怕它上市二十年)。
|
|
72
|
+
|
|
73
|
+
开启后会做两件事:
|
|
74
|
+
|
|
75
|
+
- **价格四列**(open / high / low / close)取**前一交易日的值**——当天价格 = 前一天,收益率因此为 0,等价于「那天没交易」;
|
|
76
|
+
- `returns` 按补齐后的 close 重算。
|
|
77
|
+
|
|
78
|
+
两点边界:
|
|
79
|
+
|
|
80
|
+
- **上市前的空缺不填**(前面没有值可取),仍然留空——不会凭空造出一段历史。
|
|
81
|
+
- `volume` / `amount` **不填**:停牌那天确实没有成交,硬填反而不诚实。所以量类因子仍要自己处理停牌。
|
|
82
|
+
|
|
83
|
+
只作用于默认(`lazy=False`)路径。
|
|
84
|
+
|
|
64
85
|
### 因子缓存
|
|
65
86
|
|
|
66
87
|
**默认关闭**,要用得显式开:`Data(..., factor_cache=True)`。
|
|
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
|