qka 2.5.1.dev3__py3-none-any.whl → 2.5.2.dev2__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.
qka/core/data.py CHANGED
@@ -1211,9 +1211,17 @@ class Data():
1211
1211
  only: 只算这几个列名(None = 全算)。因子缓存按因子粒度补算时用。
1212
1212
  """
1213
1213
 
1214
- # 分区过小时跳过,避免 ta-lib 在 dask meta 推断时崩溃
1215
- min_rows = self._min_rows_for_factors()
1216
- if len(df) < min_rows:
1214
+ # 分区过小时跳过,避免 ta-lib 在 dask meta 推断时崩溃。
1215
+ # 只补尾巴时这张表必然比「全局最长窗口」短(10 年期 2500、1 年期只要 250),
1216
+ # 门槛得按**本次要算的那几个因子**自己的窗口来定,否则 1 年期因子会被
1217
+ # 2500 行的全局门槛整个拦掉、算出一片空白(2026-10-21 改)。
1218
+ need = 0
1219
+ if only:
1220
+ for _n in only:
1221
+ need = max(need, self._spec_window(self._factors.get(_n)))
1222
+ if need <= 0:
1223
+ need = self._min_rows_for_factors()
1224
+ if len(df) < need:
1217
1225
  return df
1218
1226
 
1219
1227
  for col_name, spec in self._factors.items():
@@ -1711,29 +1719,70 @@ class Data():
1711
1719
  pq.write_table(table.replace_schema_metadata(meta), path)
1712
1720
  self._quote_cache[symbol] = (out, new_metas)
1713
1721
 
1714
- def _recompute_target(self, full, cached):
1722
+ def _recompute_target(self, full, cached, window=None, probe_col=None):
1715
1723
  """算出「这次要重算哪一段」。
1716
1724
 
1717
1725
  - 没有缓存 → 整段算(返回 full)
1718
1726
  - 缓存全盖住 → 不用算(返回 None)
1719
- - 只缺尾巴 → **只算尾巴**(往前留出预热窗口,滚动指标才准)
1727
+ - 只缺尾巴 → **只算尾巴**(往前只留该因子自己那一个窗口当预热)
1720
1728
  - 前面也缺 → 整段算
1729
+
1730
+ ⚠️ 预热只留「一个窗口」,不是两倍(2026-10-21 改):算第 t 天的值,滚动
1731
+ 窗口最多用到 t-window+1 那天的数据,而调用方只把缓存末日之后的行写回,
1732
+ 多留的窗口纯属白算。之前固定按「全局最长窗口 × 2」= 5000 个工作日回溯,
1733
+ 等于把 2007 年以来的因子全部重算一遍,连只需 250 天的 1 年期因子也被拖着走。
1734
+
1735
+ ⚠️ 预热按**交易日位置**回溯,不按工作日推算:`BDay(2500)` 只覆盖约 1712 个
1736
+ 交易日(节假日不算工作日),长窗口因子会预热不足、尾部一串 NaN。
1721
1737
  """
1722
1738
  if cached is None:
1723
1739
  return full
1724
1740
  if cached.index.min() <= full.index.min() and cached.index.max() >= full.index.max():
1725
1741
  return None
1726
1742
  if cached.index.min() > full.index.min():
1727
- return full
1728
- min_rows = self._min_rows_for_factors()
1729
- if min_rows <= 0:
1743
+ # 组合宽表的索引是「所有标的日期的并集」:该票上市之前那些行,本来
1744
+ # 就没有它的数据,缓存从上市日起就是完整的 —— 不能因此判成「前面缺、
1745
+ # 整段重算」(2026-10-21 改)。之前这里只比 index.min(),导致除全场
1746
+ # 最早上市那只票以外,**每一只票每次都被判成整段重算**,增量形同虚设。
1747
+ # 真正的「缓存残缺」要看:该票明明有数据,缓存却没盖住那段。
1748
+ probe = probe_col if (probe_col and probe_col in full.columns) else None
1749
+ if probe is None:
1750
+ return full
1751
+ head = full.loc[full.index < cached.index.min(), probe]
1752
+ if head.notna().any():
1753
+ return full
1754
+ win = window if isinstance(window, int) and window > 0 else 0
1755
+ if win <= 0:
1756
+ win = self._min_rows_for_factors()
1757
+ if win <= 0:
1730
1758
  # 窗口完全靠猜(lambda + 没声明 warmup)⇒ 不敢只补尾巴,整段重算
1731
1759
  return full
1732
- margin = pd.offsets.BDay(min_rows * 2)
1733
- tail = full.loc[cached.index.max() - margin:].copy()
1734
- if len(tail) < min_rows:
1760
+ # cached 末日在这张表里的行号;它之后的每一行才是真正要新算的
1761
+ pos = int(full.index.searchsorted(cached.index.max(), side='right')) - 1
1762
+ start = pos - win
1763
+ if pos < 0 or start <= 0:
1764
+ # 历史还不够一个窗口(或缓存末日对不上这张表),谈不上「只补尾巴」
1735
1765
  return full
1736
- return tail
1766
+ return full.iloc[start:].copy()
1767
+
1768
+ @staticmethod
1769
+ def _spec_window(spec) -> int:
1770
+ """从因子定义里取窗口长度(最大的整数参数);取不到返回 0。
1771
+
1772
+ 口径与 `_min_rows_for_factors` 一致:跳过前导字符串(那些是列名),
1773
+ 剩下的整数里取最大。`('qka.alpha', 250, {'max_abs_return': 0.5})` → 250。
1774
+ """
1775
+ if not isinstance(spec, (list, tuple)):
1776
+ return 0
1777
+ rest = list(spec[1:])
1778
+ idx = 0
1779
+ while idx < len(rest) and isinstance(rest[idx], str):
1780
+ idx += 1
1781
+ win = 0
1782
+ for v in rest[idx:]:
1783
+ if isinstance(v, int) and v > win:
1784
+ win = v
1785
+ return win
1737
1786
 
1738
1787
  @staticmethod
1739
1788
  def _columns_by_symbol(columns):
@@ -1869,7 +1918,9 @@ class Data():
1869
1918
  for name in names:
1870
1919
  spec, fp = plan[name]
1871
1920
  cached = self._read_factor_cache(name, symbol, fp)
1872
- target = self._recompute_target(sub, cached)
1921
+ target = self._recompute_target(
1922
+ sub, cached, self._spec_window(spec),
1923
+ probe_col='close' if 'close' in sub.columns else None)
1873
1924
  if target is None:
1874
1925
  sub[name] = cached.reindex(sub.index)
1875
1926
  continue
@@ -1877,9 +1928,11 @@ class Data():
1877
1928
  if target is sub:
1878
1929
  series = sub[name]
1879
1930
  else:
1880
- # 补算的尾巴头部自带滚动预热 NaN,用缓存里的老值兜住,
1881
- # 不能拿 NaN 去覆盖本来好好的值。
1882
- series = target[name].reindex(sub.index).combine_first(
1931
+ # 只把缓存末日**之后**的行写回,历史值原样沿用缓存:后复权历史
1932
+ # 不会被将来的分红改写,早于缓存末日的因子值不可能变,重算它们
1933
+ # 纯属白烧 CPU;尾巴头部那些预热行同样不该覆盖老值。
1934
+ fresh = target[name][target.index > cached.index.max()]
1935
+ series = fresh.reindex(sub.index).combine_first(
1883
1936
  cached.reindex(sub.index)
1884
1937
  )
1885
1938
  sub[name] = series
@@ -1933,7 +1986,10 @@ class Data():
1933
1986
  collected[col] = view[col]
1934
1987
  continue
1935
1988
  cached = self._read_factor_cache(name, symbol, fp)
1936
- target = self._recompute_target(view, cached)
1989
+ target = self._recompute_target(
1990
+ view, cached, self._spec_window(spec),
1991
+ probe_col=(f'{symbol}|close' if f'{symbol}|close' in view.columns
1992
+ else None))
1937
1993
  if target is None:
1938
1994
  collected[col] = cached.reindex(result.index)
1939
1995
  continue
@@ -1942,8 +1998,9 @@ class Data():
1942
1998
  if target is view:
1943
1999
  series = view[col]
1944
2000
  else:
1945
- # 同上:补算尾巴头部的预热 NaN 用缓存老值兜住
1946
- series = target[col].reindex(result.index).combine_first(
2001
+ # 同上:只写回缓存末日之后的行,历史值沿用缓存
2002
+ fresh = target[col][target.index > cached.index.max()]
2003
+ series = fresh.reindex(result.index).combine_first(
1947
2004
  cached.reindex(result.index)
1948
2005
  )
1949
2006
  collected[col] = series
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: qka
3
- Version: 2.5.1.dev3
3
+ Version: 2.5.2.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
@@ -5,7 +5,7 @@ qka/core/accessor.py,sha256=iEZrteVe8nqv3s5Va2l3Vz50N2S6X0IOElKq9Ik3Wno,2893
5
5
  qka/core/analysis.py,sha256=EXCO4qhzd2av5Yb4jihcNji1V-VFPOceUHKtg-q8bLs,11142
6
6
  qka/core/backtest.py,sha256=SEbx1fFj7Poda9hGTtc2YoK-Enlxa83bauRzHVidk8I,13958
7
7
  qka/core/broker.py,sha256=FxeHh9qZ9M6FNFLd8EXwztA4kmC9zZzkrXin2hdVFa4,9662
8
- qka/core/data.py,sha256=O-HOzcbB7YPFTKrkyS4eYorr-rnCjyCyOWVxQxoYx4U,106300
8
+ qka/core/data.py,sha256=Z19mVoz9Qx18hjGEaUVQjx8W_YHjEd6UO4PaeCQ96aM,109854
9
9
  qka/core/indicator.py,sha256=WYzfM7pMrzC45A7EA3ygGdHah-NlAAGFBcPxzUkrtko,9355
10
10
  qka/core/report.py,sha256=AhlLgT6xTd9YjW7fF1T6PBCXSYEtP8GTSvrW-A4p_kg,22481
11
11
  qka/core/simulate.py,sha256=bdThckUFj0Y3GyAj5ahLIGv3HetJFTbyxbEcX2SUKn0,2915
@@ -21,8 +21,8 @@ qka/skills/qka/references/broker.md,sha256=wr62v1asRM-wBdsADIsB406eXm_ENYcrwGMAN
21
21
  qka/skills/qka/references/data.md,sha256=6mL44eGOk_xXLqQ62Wmv1DFYcXn3kayYWYWMd8-hFOk,28857
22
22
  qka/skills/qka/references/sizing.md,sha256=3SsX2ddQoHjE177dberW0Q7wYxW-M_2DG_-sV3CCo9A,656
23
23
  qka/skills/qka/references/strategy.md,sha256=-6xa_oigTTj77Siity5bTC5MbdF3UKfld9imnevlAAc,4455
24
- qka-2.5.1.dev3.dist-info/METADATA,sha256=LQ4Hi7O7VCPol26eQLuxAGhn_9E7Coq9lQ7HXAHO3Sg,7545
25
- qka-2.5.1.dev3.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
26
- qka-2.5.1.dev3.dist-info/entry_points.txt,sha256=quG9zg-rOKKtbg-6FiXkoxkUUTMltMBD0hZ0QUnJ6RM,37
27
- qka-2.5.1.dev3.dist-info/licenses/LICENSE,sha256=3iIG35SnJNF11yIzm3vxa2QUvy9SS5rktM3O1DL2vqM,1075
28
- qka-2.5.1.dev3.dist-info/RECORD,,
24
+ qka-2.5.2.dev2.dist-info/METADATA,sha256=mTYIcHuaAl9AU44CypExW1y0Y_846R1cY87gG37gFoE,7545
25
+ qka-2.5.2.dev2.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
26
+ qka-2.5.2.dev2.dist-info/entry_points.txt,sha256=quG9zg-rOKKtbg-6FiXkoxkUUTMltMBD0hZ0QUnJ6RM,37
27
+ qka-2.5.2.dev2.dist-info/licenses/LICENSE,sha256=3iIG35SnJNF11yIzm3vxa2QUvy9SS5rktM3O1DL2vqM,1075
28
+ qka-2.5.2.dev2.dist-info/RECORD,,