Skip to content

Recipe: price adjustment research baseline

This recipe does a minimal check with a single stock: over the same window, returns from the unadjusted close and the hfq adj_close can differ; research code must choose its price basis explicitly and confirm that factor coverage is exact.

1. Build a small lake you can verify

pip install cnequity
cne init --profile demo --research --symbols 600519.SH

--research extends the window to about three years and additionally derives hfq factors from Sina. At the end, the command prints raw / hfq returns and a factor coverage summary; the exact results depend on the current window.

If you only need to confirm TDX connectivity, first run cne init --profile demo without --research; on a restricted network, do not read a failed research output as "no price adjustment changes".

2. Recheck the contract in Python

from pathlib import Path

import polars as pl

from cnequity.config import load_config
from cnequity.query import load

cfg = load_config(Path("configs/cnequity.demo.toml"))
raw = load(
    "daily_bars",
    symbols=["600519.SH"],
    config=cfg,
)
hfq = load(
    "daily_bars",
    symbols=["600519.SH"],
    adjust="hfq",
    strict_adj=True,
    config=cfg,
)

if not bool(hfq["adj_is_exact"].all()):
    raise RuntimeError("factor coverage is not exact; stop the research run")

comparison = (
    raw.select(["trade_date", "close"])
    .rename({"close": "raw_close"})
    .join(
        hfq.select(["trade_date", "adj_close", "adj_is_exact"]),
        on="trade_date",
        how="inner",
    )
    .sort("trade_date")
    .with_columns(
        (pl.col("raw_close") / pl.col("raw_close").first() - 1).alias("raw_return"),
        (pl.col("adj_close") / pl.col("adj_close").first() - 1).alias("hfq_return"),
    )
)

print(comparison.tail(1))
comparison.write_parquet("data/cnequity-demo/research-baseline.parquet")

Here raw_close is the original price kept in the lake, and adj_close is the result of applying the derived hfq factors to the original price at query time. The lake persists only hfq; qfq is derived per query window via load(adjust="qfq"). See product boundaries.

3. Checks before moving to real research

from cnequity.query import list_datasets

print(
    list_datasets(config=cfg)
    .filter(pl.col("dataset").is_in(["daily_bars", "adj_factors"]))
    .select(["dataset", "coverage_start", "coverage_end", "history_mode"])
)

Do not judge coverage as complete from row counts alone: history_mode may be snapshot_with_backfill, and coarse-grained partitions need their coverage confirmed against the actual date column. Universe research should name a versioned profile explicitly (for example, cn_a_sh_sz_research_v1 when studying only Shanghai and Shenzhen), so that evidence is strictly verified for that scope; do not treat the single-stock demo as proof of full-market coverage.

Production research saves the dependency revision map for prices, factors, security identity, trading status and the calendar, and reads through revision_map. For long-term retention, use cne snapshot create NAME --dataset daily_bars --research, which packages the complete data dependencies together with ST/delisting evidence and the non-sensitive read configuration; if a dependency has not been generated yet, it fails immediately. Also save the query parameters and software version; a successfully created snapshot does not mean research coverage has passed. See version boundaries in the Python API.