Skip to content
5 changes: 4 additions & 1 deletion config/data_update.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ data:
root_dir: artifacts/cache/tushare/v1
refresh_recent_days: 14
refresh_dimension_days: 30
fina_tail_days: 400 # fina late-disclosure refetch tail (revision horizon,
# single source; both the warm and the backtest read
# through resolve it from here — factor-refactor R5)
force_refresh: []

universe:
Expand Down Expand Up @@ -60,7 +63,7 @@ data_update:
lookback_days: 400 # dense endpoints: re-warm [today - lookback, today]
tail_refresh_days: 14 # recent-tail always refetched (corrections/delays)
not_ready_days: 1 # today may be unpublished at 21:00 -> not_ready, not covered
fina_tail_days: 400 # refetch recent report periods to catch LATE disclosures
# fina_tail_days moved to data.cache.fina_tail_days (single source; R5).
# NOTE: the cache ALWAYS stores the canonical fina superset (roe / netprofit_yoy
# / grossprofit_margin) regardless of this list, so a warm here never blocks a
# later config that needs a different field. grossprofit_margin is listed
Expand Down
15 changes: 9 additions & 6 deletions config/data_update_all_a.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ data:
root_dir: artifacts/cache/tushare/v1
refresh_recent_days: 14
refresh_dimension_days: 30
fina_tail_days: 400 # fina late-disclosure refetch tail (revision horizon,
# single source; R5). == lookback_days below on purpose
# so late disclosures across the full lookback are caught.
force_refresh: []

# universe is IGNORED for symbol resolution when data_update.universe_scope=all_a
Expand Down Expand Up @@ -89,14 +92,14 @@ data_update:
lookback_days: 400 # dense endpoints: re-warm [today - lookback, today]
tail_refresh_days: 14 # recent-tail always refetched (corrections/delays)
not_ready_days: 1 # today may be unpublished at 21:00 -> not_ready, not covered
fina_tail_days: 400 # refetch recent report periods to catch LATE disclosures
# fina_tail_days moved to data.cache.fina_tail_days (single source; R5). It is
# 400 == lookback_days on purpose, so fina's gap spans the whole window every
# night (a redundant re-upsert of already-stored report periods, NOT a quota
# multiplier — one call per symbol per window); left equal so late disclosures
# across the full lookback are caught.
# NOTE: the cache ALWAYS stores the canonical fina superset (roe / netprofit_yoy
# / grossprofit_margin) regardless of this list, so a warm here never blocks a
# later config that needs a different field. fina_tail_days == lookback_days here,
# so fina's gap spans the whole window every night (a redundant re-upsert of
# already-stored report periods, NOT a quota multiplier — one call per symbol per
# window); left equal on purpose so late disclosures across the full lookback are
# caught.
# later config that needs a different field.
fina_fields:
- roe
- netprofit_yoy
Expand Down
28 changes: 28 additions & 0 deletions factors/spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@
the per-factor pre-assignments live in ``docs/factors/refactor_d0_contract.md``
(D0); this module never restates that table.

CONTRACT v1.1 (factor-refactor D3 — an ADDITIVE change stated outright, #74
precedent): the OPTIONAL ``lookback_depth`` field declares the factor's
transitive lookback depth (trading days) for the D3 tail-recompute engine. It is
optional here so contract v1.0 specs stay valid, but validated to a positive int
when present, and the store engine HARD-REQUIRES it (never a silent 0/headline
fallback) at the point it is consumed. See the ``lookback_depth`` attribute note.

Two objects together form the provenance an evaluator requires (design doc
``tmp/design/factor_eval_contract_v0.1.md`` §1):

Expand Down Expand Up @@ -167,6 +174,18 @@ class FactorSpec:
price_adjust: str = "qfq"
family: str | None = None
min_history_bars: int = 0
# Contract v1.1 ADDITIVE (factor-refactor D3): the factor's TRANSITIVE lookback
# depth in trading days — the sum of every nested lookback plus the valid-day
# slack, NOT the headline window (design §六.18: a nested-lookback factor such
# as ridge = lookback_days=20 over a baseline_days=20 has a depth of ~40+, and
# sizing anything by the headline 20 systematically under-samples). The D3
# tail-recompute engine sizes its incremental overlap window and warm-up load
# from this (``factors.store.incremental``). Declared OPTIONAL here (default
# None) so contract v1.0 specs stay valid; the engine HARD-REQUIRES it (a
# readable error, NEVER a silent 0 or headline fallback) at the point it is
# actually consumed — see ``factors.store.incremental.factor_lookback_depth``.
# Validated to a positive int WHEN present.
lookback_depth: int | None = None
decision_cutoff: str | None = None
data_lag: str | None = None
session_open: str | None = None
Expand Down Expand Up @@ -241,6 +260,15 @@ def _check_measurement(self) -> None:
f"FactorSpec.family must be None or a non-empty string; got "
f"{self.family!r} for {self.factor_id!r}."
)
depth = self.lookback_depth
if depth is not None and (
isinstance(depth, bool) or not isinstance(depth, int) or depth < 1
):
raise ValueError(
f"FactorSpec.lookback_depth must be None or a positive int (the "
f"TRANSITIVE lookback depth in trading days, NOT the headline "
f"window); got {depth!r} for {self.factor_id!r}."
)

def _check_inputs(self) -> None:
fields = self.input_fields
Expand Down
71 changes: 71 additions & 0 deletions factors/store/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
"""Factor value store (refactor D3): sparse raw factor-value persistence.

A PURE ADDITIVE layer (design §9 D3 rollback note): disabling the store falls back
to full recomputation, so nothing here is on a consumption critical path in D3 (the
D4 read-through wires it into the pipeline). The store persists RAW factor values
only (processed panels never land here as a source of truth, red line #7), addressed
by a COMPLETE key — factor_id + params + code_hash + view — with a data fingerprint
(schema version + adjustment-derived anchor events) validated on read.

Layering (red line #10): ``factors.store`` never imports ``qt``.
"""

from __future__ import annotations

from factors.store.code_hash import (
ALLOWED_INTERNAL_IMPORTS,
code_hash,
factor_source_files,
module_import_violations,
shared_set_labeled_files,
)
from factors.store.fingerprint import (
FINGERPRINT_VERSION,
adj_events_hash,
data_fingerprint,
schema_version_hash,
)
from factors.store.hashing import params_hash, short_hash
from factors.store.incremental import (
CacheHorizonConfig,
IncrementalResult,
endpoint_horizons,
factor_lookback_depth,
overlap_window,
tail_recompute,
)
from factors.store.keys import StoreKey, store_key
from factors.store.run_registry import (
STATUSES,
RunRecord,
RunRegistry,
assert_registry_consistent,
)
from factors.store.values import FactorValueStore

__all__ = [
"ALLOWED_INTERNAL_IMPORTS",
"FINGERPRINT_VERSION",
"STATUSES",
"CacheHorizonConfig",
"FactorValueStore",
"IncrementalResult",
"RunRecord",
"RunRegistry",
"StoreKey",
"adj_events_hash",
"assert_registry_consistent",
"code_hash",
"data_fingerprint",
"endpoint_horizons",
"factor_lookback_depth",
"factor_source_files",
"module_import_violations",
"overlap_window",
"params_hash",
"schema_version_hash",
"shared_set_labeled_files",
"short_hash",
"store_key",
"tail_recompute",
]
Loading