fix(portfolio): 周报双修——回测基准认 equity 列(生产四文件全被 balance-only 拒)+daily_returns 同日多行取末次(多写者防覆写) [vps]
CI/CD / test (push) Successful in 22s
CI/CD / nas-deploy (push) Successful in 12s
CI/CD / nas-verify (push) Successful in 8s

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-25 14:49:10 +08:00
parent 3fcf58728b
commit a2fb7b4dbb
2 changed files with 36 additions and 7 deletions
+12 -7
View File
@@ -31,13 +31,16 @@ def _week_start(d: date) -> date:
def daily_returns(series: list[tuple[str, float]]) -> dict[str, float]:
"""[(date, equity)] → {date: 当日收益率};首日无前值跳过,前值 0 防除零.
平盘日(收益 0.0)照记不剔除——剔除会低估 TE(M5 终审裁定).
"""[(date, equity)] → {date: 当日收益率};同日多行取末次写入(多写者日末终值),
首日无前值跳过,前值 0 防除零.平盘日(收益 0.0)照记不剔除——剔除会低估 TE(M5 终审裁定).
"""
last: dict[str, float] = {}
for d, eq in series:
last[d] = eq
out: dict[str, float] = {}
prev: float | None = None
for d, eq in sorted(series):
for d in sorted(last):
eq = last[d]
if prev is not None and prev != 0:
out[d] = eq / prev - 1.0
prev = eq
@@ -57,7 +60,7 @@ def load_actual_daily_returns(db_path: str, account_id: int,
def load_backtest_daily_returns(db_path: str,
task_id: str) -> dict[str, float] | None:
"""回测基准:backtest_stats 最新行 → equity_path JSON(date/balance,orient=records)."""
"""回测基准:backtest_stats 最新行 → equity_path JSON(date/balance|equity,orient=records)."""
with sqlite3.connect(db_path) as conn:
row = conn.execute(
"SELECT equity_path FROM backtest_stats WHERE task_id=? "
@@ -67,11 +70,13 @@ def load_backtest_daily_returns(db_path: str,
try:
import pandas as pd
ec = pd.read_json(row[0], orient="records")
if "balance" not in ec.columns:
col = "balance" if "balance" in ec.columns else (
"equity" if "equity" in ec.columns else None)
if col is None:
return None
dates = pd.to_datetime(ec["date"]).dt.strftime("%Y-%m-%d")
series = list(zip(dates.tolist(),
pd.to_numeric(ec["balance"], errors="coerce").tolist()))
pd.to_numeric(ec[col], errors="coerce").tolist()))
series = [(d, float(v)) for d, v in series if v == v] # NaN 剔除
return daily_returns(series) if series else None
except Exception as exc:
+24
View File
@@ -84,6 +84,15 @@ def test_daily_returns_keeps_flat_days():
assert r["c"] == pytest.approx(0.01)
def test_daily_returns_dedupes_same_date():
# 同日多行(paper_daily_balance 每日 2-3 行多写者)取末次后再 diff;
# 不去重则排序后同日第二行覆写成日内 0.0,毒化跨日收益
r = daily_returns([("2026-09-21", 100.0), ("2026-09-22", 110.0),
("2026-09-22", 110.0), ("2026-09-23", 121.0)])
assert r == {"2026-09-22": pytest.approx(0.1),
"2026-09-23": pytest.approx(0.1)}
def test_tracking_error_aligned_and_none():
a = {"d1": 0.01, "d2": -0.01, "d3": 0.01}
b = {"d1": 0.01, "d2": -0.01, "d3": 0.01}
@@ -114,6 +123,21 @@ def test_load_backtest_daily_returns(main_db):
assert load_backtest_daily_returns(main_db, "nope") is None
def test_backtest_loader_accepts_equity_column(main_db, tmp_path):
# 生产回测 equity JSON 用 equity 列(balance-only 校验把真文件全拒→te/vs 恒 None)
eq_path = str(tmp_path / "bt_equity.json")
with open(eq_path, "w", encoding="utf-8") as f:
json.dump([{"date": "2026-09-21", "equity": 100.0},
{"date": "2026-09-22", "equity": 101.0}], f)
conn = sqlite3.connect(main_db)
conn.execute("INSERT INTO backtest_stats(task_id, equity_path) "
"VALUES('bt_run_equity',?)", (eq_path,))
conn.commit()
conn.close()
got = load_backtest_daily_returns(main_db, "bt_run_equity")
assert got == {"2026-09-22": pytest.approx(0.01)}
def test_load_actual_daily_returns_skips_null_equity(tmp_path):
# M4:total_equity NULL 行剔除(不折算成 0 毒化收益链)
p = str(tmp_path / "null_eq.db")