Compare commits

..
Author SHA1 Message Date
ao gong 4d84f6a26a Merge remote-tracking branch 'origin/main' into codex/research-artifact-contract-20260821 2026-08-25 22:39:54 +08:00
ao gong c12c9ba335 fix(artifact): enforce risk data lineage 2026-08-25 22:39:45 +08:00
ao gong 7c7f0c06a7 docs(handoff): record market lineage validation 2026-08-24 10:44:29 +08:00
ao gong 741d5f1ad3 feat(data): snapshot asset returns with stable lineage 2026-08-24 10:42:34 +08:00
ao gong cbb56d2f52 docs: record covariance estimator decision 2026-08-21 23:49:06 +08:00
ao gong 524fc73852 feat: estimate deterministic covariance snapshots 2026-08-21 23:47:29 +08:00
ao gong 8ef8e3208e test: define deterministic covariance estimator contract 2026-08-21 23:45:40 +08:00
ao gong dc67f0e982 docs: record reproducible risk artifact contract 2026-08-21 23:41:21 +08:00
ao gong 1a60fef6e1 feat: publish reproducible portfolio risk facts 2026-08-21 23:28:34 +08:00
ao gong a5dc04bf7e test: define reproducible risk snapshot contract 2026-08-21 23:24:41 +08:00
ao gong a3cefe364d feat: add deterministic trade and signal identity 2026-08-21 22:32:37 +08:00
ao gong d6c3614301 test: require deterministic trade identity 2026-08-21 22:32:14 +08:00
ao gong 8addce4a68 wip: hand off research artifact contract 2026-08-21 22:31:14 +08:00
ao gong 2071d508d0 test: cover sortino boundary contracts 2026-08-21 22:30:28 +08:00
ao gong 0568cabda1 feat: include signal facts in research artifact 2026-08-21 22:30:01 +08:00
ao gong b2f3da9d66 test: require signal facts in research artifact 2026-08-21 22:29:32 +08:00
ao gong cfa5bed188 feat: add deterministic research run artifact 2026-08-21 22:29:10 +08:00
ao gong a9465e6479 test: define versioned research artifact contract 2026-08-21 22:26:51 +08:00
ao gong 55eeff3951 docs: add realized ledger weights to handoff 2026-08-21 22:22:00 +08:00
ao gong 3b1ad07c69 feat: expose realized ledger position weights 2026-08-21 22:21:32 +08:00
ao gong 1b5b353098 test: define realized ledger weight projection contract 2026-08-21 22:21:03 +08:00
ao gong 2bb9f52080 wip: hand off ledger-backed attribution 2026-08-21 22:19:20 +08:00
ao gong 76bb5494a2 docs: record lightweight attribution design references 2026-08-21 22:18:24 +08:00
ao gong f7ad82534a feat: add label-safe component risk decomposition 2026-08-21 22:17:05 +08:00
ao gong 35a52d781e test: define labeled component risk contract 2026-08-21 22:16:24 +08:00
ao gong f14ab464f7 feat: add strict benchmark-relative performance metrics 2026-08-21 22:15:47 +08:00
ao gong de2f9494fc test: define benchmark-relative performance contract 2026-08-21 22:15:05 +08:00
ao gong 19fe22b01a feat: add ledger-backed daily return attribution 2026-08-21 22:14:18 +08:00
ao gong 212351e984 test: define post-execution return attribution contract 2026-08-21 22:13:04 +08:00
ao gong 5fb4b85cc3 wip: hand off stacked daily ledger 2026-08-21 22:06:51 +08:00
ao gong a421278527 fix: align ledger performance with signal window 2026-08-21 22:05:28 +08:00
ao gong c774a4546a test: exclude factor warmup from performance window 2026-08-21 22:04:52 +08:00
ao gong 022b87fdac feat: expose platform-neutral ledger projection 2026-08-21 22:03:21 +08:00
ao gong 1b39c53f18 test: define daily ledger projection contract 2026-08-21 22:02:58 +08:00
ao gong b794ab2e8f feat: add post-execution daily ledger 2026-08-21 22:01:35 +08:00
ao gong a44e2d306a test: define post-execution daily ledger contract 2026-08-21 21:58:01 +08:00
ao gong 15b283bdf9 docs: distinguish signal execution and holding times
CI / lite (pull_request) Successful in 4s
2026-08-21 21:49:07 +08:00
ao gong f9b7f2ab1a feat: schedule factor weights for next-session execution 2026-08-21 21:47:53 +08:00
ao gong 213aa88deb test: require execution-price input snapshot 2026-08-21 21:46:47 +08:00
ao gong b7f77e2a6c test: define lagged factor-to-execution contract 2026-08-21 21:46:00 +08:00
ao gong c9fb5978f0 docs: clarify execution audit result contract
CI / lite (pull_request) Successful in 3s
2026-08-21 21:39:12 +08:00
ao gong b2af2a10a6 docs: document auditable execution workflow 2026-08-21 21:38:08 +08:00
ao gong da2ca51ff7 fix: enforce cash-backed long-only rebalancing 2026-08-21 21:37:02 +08:00
ao gong ee22d1eb9d test: reproduce execution cash and long-only violations 2026-08-21 21:35:47 +08:00
ao gong 17b680604d fix: derive multi-day trades from target-weight deltas 2026-08-21 21:35:15 +08:00
ao gong c51d803daf test: reproduce multi-day execution audit gaps 2026-08-21 21:31:52 +08:00
ao gong 5578851d85 fix: enforce chronological rebalance scores
CI / lite (pull_request) Canceled after 0s
2026-08-21 21:25:27 +08:00
ao gong b4f7b74c04 test: reject unsorted rebalance dates 2026-08-21 21:25:11 +08:00
ao gong 0a236622f3 docs: add factor-to-backtest portfolio workflow 2026-08-21 21:24:41 +08:00
ao gong 88af157ee7 fix: validate unique rebalance dates 2026-08-21 21:24:17 +08:00
ao gong a1130ea43b test: reject ambiguous duplicate rebalance dates 2026-08-21 21:24:03 +08:00
ao gong 96e8f1ad62 feat: build target weights from factor scores 2026-08-21 21:23:25 +08:00
ao gong ecf6b4e4bf test: add red factor-to-weights portfolio contract 2026-08-21 21:22:36 +08:00
ao gong 979166ad16 docs: document unified backtest result workflow
CI / lite (pull_request) Successful in 3s
2026-08-21 21:11:52 +08:00
ao gong cdf41edf1f feat: add unified weight backtest result facade 2026-08-21 21:11:06 +08:00
ao gong 585a635797 test: add red contract for unified backtest result 2026-08-21 21:10:33 +08:00
ao gong 2ff0630973 refactor: clarify quant core validation internals
CI / lite (pull_request) Successful in 3s
2026-08-21 21:00:19 +08:00
ao gong bae4dedf70 test: satisfy metric contract lint 2026-08-21 20:59:27 +08:00
ao gong e359792ef5 fix: harden quant core calculation boundaries 2026-08-21 20:58:30 +08:00
ao gong 0c3a375b1e test: add red contracts for quant core boundaries 2026-08-21 20:57:49 +08:00
2 changed files with 0 additions and 235 deletions
-139
View File
@@ -15,7 +15,6 @@ v1.2.0 Phase 0:5 个基础算子 + 5 个 alpha 公式(alpha001–alpha005)
from __future__ import annotations from __future__ import annotations
from collections.abc import Callable
from typing import Any from typing import Any
import numpy as np import numpy as np
@@ -210,140 +209,6 @@ def indneutralize(series: pd.Series, groups: pd.Series) -> pd.Series:
return series - series.groupby(groups).transform("mean") return series - series.groupby(groups).transform("mean")
# ── Phase 1 operator contract ──────────────────────────
# This is deliberately a small, stable surface for downstream research
# orchestration. The full alpha158 formula catalogue can continue to grow,
# while callers use one validated dispatch entry point for the first ten
# deterministic building blocks.
ALPHA158_PHASE1_MAX_WINDOW = 252
ALPHA158_PHASE1_OPERATOR_SPECS: dict[str, dict[str, Any]] = {
"rank": {
"name": "rank",
"formula": "rank(series)",
"inputs": ["series"],
"windowed": False,
},
"delta": {
"name": "delta",
"formula": "delta(series, window)",
"inputs": ["series"],
"windowed": True,
},
"ts_mean": {
"name": "ts_mean",
"formula": "ts_mean(series, window)",
"inputs": ["series"],
"windowed": True,
},
"ts_std": {
"name": "ts_std",
"formula": "ts_std(series, window)",
"inputs": ["series"],
"windowed": True,
},
"ts_rank": {
"name": "ts_rank",
"formula": "ts_rank(series, window)",
"inputs": ["series"],
"windowed": True,
},
"correlation": {
"name": "correlation",
"formula": "correlation(series, secondary, window)",
"inputs": ["series", "secondary"],
"windowed": True,
},
"ts_min": {
"name": "ts_min",
"formula": "ts_min(series, window)",
"inputs": ["series"],
"windowed": True,
},
"ts_max": {
"name": "ts_max",
"formula": "ts_max(series, window)",
"inputs": ["series"],
"windowed": True,
},
"ts_sum": {
"name": "ts_sum",
"formula": "ts_sum(series, window)",
"inputs": ["series"],
"windowed": True,
},
"decay_linear": {
"name": "decay_linear",
"formula": "decay_linear(series, window)",
"inputs": ["series"],
"windowed": True,
},
}
_PHASE1_OPERATOR_FUNCTIONS: dict[str, Callable[..., pd.Series]] = {
"rank": rank,
"delta": delta,
"ts_mean": ts_mean,
"ts_std": ts_std,
"ts_rank": ts_rank,
"correlation": correlation,
"ts_min": ts_min,
"ts_max": ts_max,
"ts_sum": ts_sum,
"decay_linear": decay_linear,
}
def list_phase1_operators() -> tuple[str, ...]:
"""Return the deterministic Phase 1 operator names in stable order."""
return tuple(ALPHA158_PHASE1_OPERATOR_SPECS)
def evaluate_phase1_operator(
name: str,
series: pd.Series,
secondary: pd.Series | None = None,
*,
window: int | None = None,
) -> pd.Series:
"""Evaluate one of the ten Phase 1 operators with a validated contract.
``window`` is required for time-series operators and forbidden for the
cross-sectional ``rank`` operator. Binary ``correlation`` also requires
a same-index secondary series so that callers cannot silently introduce
alignment-dependent results.
"""
if name not in ALPHA158_PHASE1_OPERATOR_SPECS:
raise KeyError(f"operator {name!r} not registered")
is_windowed = bool(ALPHA158_PHASE1_OPERATOR_SPECS[name]["windowed"])
if is_windowed:
if isinstance(window, bool) or not isinstance(window, int) or window <= 0:
raise ValueError(f"window must be a positive integer for {name}")
if window > ALPHA158_PHASE1_MAX_WINDOW:
raise ValueError(
f"window exceeds maximum supported value {ALPHA158_PHASE1_MAX_WINDOW} for {name}"
)
if not is_windowed and window is not None:
raise ValueError(f"window is not supported for {name}")
if name == "correlation":
if secondary is None:
raise ValueError("secondary is required for correlation")
if not series.index.equals(secondary.index):
raise ValueError("secondary index must align with series")
return correlation(series, secondary, window) # type: ignore[arg-type]
if secondary is not None:
raise ValueError(f"secondary is not supported for {name}")
operator = _PHASE1_OPERATOR_FUNCTIONS[name]
if name == "rank":
return operator(series)
return operator(series, window)
# ── 组合算子(alpha158 公式样本) ───────────────────────── # ── 组合算子(alpha158 公式样本) ─────────────────────────
@@ -2890,10 +2755,6 @@ __all__ = [
"max_pair", "max_pair",
"min_pair", "min_pair",
"indneutralize", "indneutralize",
"ALPHA158_PHASE1_MAX_WINDOW",
"ALPHA158_PHASE1_OPERATOR_SPECS",
"list_phase1_operators",
"evaluate_phase1_operator",
"alpha_001", "alpha_001",
"alpha_002", "alpha_002",
"alpha_003", "alpha_003",
-96
View File
@@ -8,7 +8,6 @@ import pytest
from quant_engine.alpha_factors import ( from quant_engine.alpha_factors import (
ALPHA158_REGISTRY, ALPHA158_REGISTRY,
ALPHA158_PHASE1_OPERATOR_SPECS,
alpha_001, alpha_001,
alpha_002, alpha_002,
alpha_003, alpha_003,
@@ -167,8 +166,6 @@ from quant_engine.alpha_factors import (
alpha_156, alpha_156,
alpha_157, alpha_157,
alpha_158, alpha_158,
evaluate_phase1_operator,
list_phase1_operators,
correlation, correlation,
covariance, covariance,
decay_linear, decay_linear,
@@ -1235,96 +1232,3 @@ def test_parse_alpha_formula_round_trip_jsonb():
serialized = json.dumps(parsed) serialized = json.dumps(parsed)
assert isinstance(serialized, str) assert isinstance(serialized, str)
assert "ts_rank" in serialized assert "ts_rank" in serialized
# ── v1.2.0 Phase 1: deterministic operator dispatch contract ──────────────
def test_phase1_operator_catalog_is_explicit_and_serializable():
"""Phase 1 exposes a stable, JSON-friendly catalog for downstream callers."""
import json
expected = {
"rank",
"delta",
"ts_mean",
"ts_std",
"ts_rank",
"correlation",
"ts_min",
"ts_max",
"ts_sum",
"decay_linear",
}
assert set(list_phase1_operators()) == expected
assert set(ALPHA158_PHASE1_OPERATOR_SPECS) == expected
json.dumps(ALPHA158_PHASE1_OPERATOR_SPECS)
for name, spec in ALPHA158_PHASE1_OPERATOR_SPECS.items():
assert spec["name"] == name
assert isinstance(spec["inputs"], list)
assert isinstance(spec["formula"], str)
def test_phase1_unary_operators_preserve_index_and_are_deterministic():
values = pd.Series([1.0, 2.0, 3.0, 4.0], index=["a", "b", "c", "d"])
first = evaluate_phase1_operator("rank", values)
second = evaluate_phase1_operator("rank", values)
pd.testing.assert_series_equal(first, second)
assert first.index.equals(values.index)
assert first.iloc[-1] == pytest.approx(1.0)
@pytest.mark.parametrize(
("name", "window"),
[
("delta", 2),
("ts_mean", 2),
("ts_std", 2),
("ts_rank", 2),
("ts_min", 2),
("ts_max", 2),
("ts_sum", 2),
("decay_linear", 2),
],
)
def test_phase1_windowed_operators_require_explicit_window(name: str, window: int):
values = pd.Series([1.0, 2.0, 3.0, 4.0])
result = evaluate_phase1_operator(name, values, window=window)
assert result.index.equals(values.index)
with pytest.raises(ValueError, match="window"):
evaluate_phase1_operator(name, values)
with pytest.raises(ValueError, match="positive integer"):
evaluate_phase1_operator(name, values, window=1.5) # type: ignore[arg-type]
def test_phase1_binary_correlation_requires_aligned_secondary_input():
values = pd.Series([1.0, 2.0, 3.0, 4.0])
other = pd.Series([4.0, 3.0, 2.0, 1.0])
result = evaluate_phase1_operator("correlation", values, other, window=2)
assert result.iloc[-1] == pytest.approx(-1.0)
with pytest.raises(ValueError, match="secondary"):
evaluate_phase1_operator("correlation", values, window=2)
def test_phase1_dispatch_rejects_unknown_or_unused_arguments():
values = pd.Series([1.0, 2.0, 3.0])
with pytest.raises(KeyError, match="not registered"):
evaluate_phase1_operator("unknown", values)
with pytest.raises(ValueError, match="window"):
evaluate_phase1_operator("rank", values, window=2)
with pytest.raises(ValueError, match="secondary"):
evaluate_phase1_operator("rank", values, values)
def test_phase1_dispatch_rejects_window_above_supported_limit():
values = pd.Series([1.0, 2.0, 3.0])
with pytest.raises(ValueError, match="maximum"):
evaluate_phase1_operator("ts_mean", values, window=2**63)