from __future__ import annotations
|
|
import hashlib
|
import json
|
import re
|
from datetime import datetime
|
from pathlib import Path
|
from urllib.parse import unquote
|
|
import pandas as pd
|
|
|
RUN_ID = "RUN-ANA-WUJI-FULL-2023-2026-20260608-001"
|
TASK_ID = "ANA-WUJI-BASELINE-2023-2026"
|
DESIGN_ID = "DESIGN-WUJI-FULL-2023-2026-20260608"
|
DESIGN_AUDIT_ID = "AUDIT-ANA-WUJI-FULL-2023-2026-20260608-DESIGN-001"
|
ROOT = Path(__file__).resolve().parents[1]
|
|
STAGE = "FULL_2023_2026_EXECUTION_REREVIEW_PASSED_RETURN_STAT_HELD"
|
EXEC_REVIEW_STATUS = "全量分批执行审核通过 / 返修复审通过;RETURN_STAT_READY=false,完整结论必须按 full_return_stat_* 分层引用"
|
EXEC_REVIEW_MESSAGE_ID = "msg_20260608134003937_c4484f0e"
|
EXEC_HELD_AUDIT_ID = "AUDIT-ANA-WUJI-FULL-2023-2026-20260608-EXEC-001"
|
EXEC_HELD_MESSAGE_ID = "msg_20260608135433448_d5f47560"
|
EXEC_REREVIEW_HELD_AUDIT_ID = "AUDIT-ANA-WUJI-FULL-2023-2026-20260608-EXEC-REREVIEW-001"
|
EXEC_REREVIEW_HELD_MESSAGE_ID = "msg_20260608142652470_d00969d7"
|
EXEC_REREVIEW_PASS_AUDIT_ID = "AUDIT-ANA-WUJI-FULL-2023-2026-20260608-EXEC-REREVIEW-002"
|
EXEC_REREVIEW_PASS_MESSAGE_ID = "msg_20260608145302303_8c8a3273"
|
RETURN_SCOPE_ISSUE_ID = "ANA-ISSUE-WUJI-FULL-RETURN-STAT-SCOPE-20260608-001"
|
RETURN_READY = False
|
ALLOWED_UNRESOLVED = {"WINDOW_END_VALUATION_ONLY", "EXIT_DATA_GAP_HELD", "EXIT_REVIEW_HELD"}
|
|
ROLE_TITLES = {
|
"candidate_daily_100d_decision_view": "1. 选股日K图(约100个交易日)",
|
"entry_1m_morning_review_view": "2. 买点早盘1分钟复核图",
|
"entry_1m_late_review_view": "3. 买点尾盘1分钟复核图",
|
"entry_1m_buy_decision_view": "4. 买入裁决1分钟图",
|
"exit_daily_signal_review_view": "5. 卖点 / 持仓日K信号图",
|
"exit_1m_sell_decision_view": "6. 卖出裁决1分钟图",
|
}
|
|
|
def now_iso() -> str:
|
return datetime.now().astimezone().isoformat(timespec="seconds")
|
|
|
def sha256_file(path: Path) -> str:
|
h = hashlib.sha256()
|
with path.open("rb") as f:
|
for chunk in iter(lambda: f.read(1024 * 1024), b""):
|
h.update(chunk)
|
return h.hexdigest()
|
|
|
def read_json(name: str) -> dict:
|
path = ROOT / name
|
return json.loads(path.read_text(encoding="utf-8")) if path.exists() else {}
|
|
|
def read_csv(name: str) -> pd.DataFrame:
|
path = ROOT / name
|
return pd.read_csv(path, encoding="utf-8-sig") if path.exists() else pd.DataFrame()
|
|
|
def write_csv(df: pd.DataFrame, path: Path) -> None:
|
path.parent.mkdir(parents=True, exist_ok=True)
|
df.to_csv(path, index=False, encoding="utf-8-sig")
|
|
|
def clean(value: object, default: str = "") -> str:
|
text = str(value).strip()
|
if text.lower() in {"", "nan", "none", "nat"}:
|
return default
|
return text
|
|
|
def has_value(value: object) -> bool:
|
return clean(value) != ""
|
|
|
def fmt_int(value: object) -> str:
|
if not has_value(value):
|
return "0"
|
return str(int(float(value)))
|
|
|
def fmt_price(value: object) -> str:
|
if not has_value(value):
|
return ""
|
return f"{float(value):.2f}"
|
|
|
def fmt_pct(value: object) -> str:
|
if not has_value(value):
|
return ""
|
return f"{float(value):.4%}"
|
|
|
def safe_rel(from_dir: Path, root_relative_path: object) -> str:
|
target = clean(root_relative_path)
|
if not target:
|
return ""
|
return Path("../../", target).as_posix() if from_dir.name == "img" else Path(target).as_posix()
|
|
|
def link_from_doc(doc_path: Path, root_relative_path: object) -> str:
|
target = clean(root_relative_path)
|
if not target:
|
return ""
|
try:
|
rel = Path(target).relative_to(doc_path.parent.relative_to(ROOT))
|
return rel.as_posix()
|
except ValueError:
|
return Path(*([".."] * len(doc_path.parent.relative_to(ROOT).parts)), target).as_posix()
|
|
|
def markdown_links(path: Path) -> list[Path]:
|
text = path.read_text(encoding="utf-8")
|
links: list[Path] = []
|
for raw in re.findall(r"\]\(([^)]+)\)", text):
|
if "://" in raw or raw.startswith("#"):
|
continue
|
target = unquote(raw.split("#", 1)[0]).strip()
|
if not target:
|
continue
|
links.append((path.parent / target).resolve())
|
return links
|
|
|
def manifest_for_dir(base: Path) -> dict:
|
files: list[dict] = []
|
for path in sorted(base.rglob("*")):
|
if not path.is_file() or "__pycache__" in path.parts:
|
continue
|
rel = path.relative_to(base).as_posix()
|
if rel == "manifest.json":
|
continue
|
files.append({"path": rel, "size": path.stat().st_size, "sha256": sha256_file(path)})
|
return {
|
"schema_version": "1.0",
|
"run_id": RUN_ID,
|
"generated_at": now_iso(),
|
"base": base.relative_to(ROOT).as_posix() if base != ROOT else ".",
|
"file_count": len(files),
|
"files": files,
|
}
|
|
|
def role_counts(df: pd.DataFrame, column: str) -> dict:
|
if df.empty or column not in df.columns:
|
return {}
|
return {str(k): int(v) for k, v in df[column].value_counts(dropna=False).to_dict().items()}
|
|
|
def build_scope_lines(
|
case_index: pd.DataFrame,
|
batch_index: pd.DataFrame,
|
selected: pd.DataFrame,
|
orders: pd.DataFrame,
|
lots: pd.DataFrame,
|
case_summary: pd.DataFrame,
|
) -> list[str]:
|
boundary = lots[lots.lot_status != "CLOSED_BY_AI_SELL"] if not lots.empty else pd.DataFrame()
|
boundary_text = ";".join(f"{k} {v} 笔" for k, v in role_counts(boundary, "lot_status").items()) or "无"
|
order_counts = role_counts(orders, "action")
|
return [
|
f"- 当前 run:`{RUN_ID}`",
|
f"- 当前阶段:`{STAGE}`",
|
f"- 审核状态:{EXEC_REVIEW_STATUS}",
|
f"- 设计审核:`{DESIGN_AUDIT_ID}`",
|
f"- 范围:{len(case_index)} 个 entry_trade_date,{len(batch_index)} 个批次,每个 entry date 取前 5 个候选,共 {len(selected)} 条候选选择记录",
|
f"- 市场闸门:打开 {int((case_index.market_gate_status == 'MKT_GATE_OPEN_PREV_DAY_UP_3000').sum())} 个 entry date,关闭 {int((case_index.market_gate_status == 'NO_TRADE_MARKET_GATE_CLOSED').sum())} 个 entry date",
|
f"- 订单:BUY {order_counts.get('BUY', 0)},SELL {order_counts.get('SELL', 0)};有 BUY 的 case:{len(case_summary)}",
|
f"- Lot:共 {len(lots)},已闭合 {role_counts(lots, 'lot_status').get('CLOSED_BY_AI_SELL', 0)},边界保留 {len(boundary)}({boundary_text})",
|
"- `RETURN_STAT_READY=false`",
|
"- 本入口已通过全量分批执行返修复审;完整结论仍必须按 `full_return_stat_*` 分层引用,`RETURN_STAT_READY=false` 继续保留。",
|
]
|
|
|
def write_case_boards(
|
case_index: pd.DataFrame,
|
selected: pd.DataFrame,
|
decisions: pd.DataFrame,
|
lots: pd.DataFrame,
|
case_summary: pd.DataFrame,
|
image_manifest: pd.DataFrame,
|
) -> None:
|
summary_by_case = case_summary.set_index("case_id") if not case_summary.empty else pd.DataFrame()
|
case_scope_df = read_csv("full_return_stat_case_scope.csv")
|
scope_by_case = case_scope_df.set_index("case_id") if not case_scope_df.empty else pd.DataFrame()
|
for _, meta in case_index.sort_values("entry_trade_date").iterrows():
|
case_id = str(meta.case_id)
|
case_dir = ROOT / "cases" / case_id
|
case_dir.mkdir(parents=True, exist_ok=True)
|
rows = image_manifest[image_manifest.case_id == case_id].copy()
|
case_selected = selected[selected.case_id == case_id].copy()
|
case_decisions = decisions[decisions.case_id == case_id].copy()
|
case_lots = lots[lots.case_id == case_id].copy()
|
scope_row = scope_by_case.loc[case_id] if not scope_by_case.empty and case_id in scope_by_case.index else None
|
|
board_path = case_dir / "case_image_board.md"
|
lines = [
|
f"# {case_id} 图片审核板",
|
"",
|
"用途:人工审核员按图复核本案例从选股、买入、卖点信号到卖出裁决的全链路。",
|
"",
|
"## 当前全量执行包状态",
|
"",
|
f"- 当前 run:`{RUN_ID}`",
|
f"- 当前阶段:`{STAGE}`",
|
f"- 审核状态:{EXEC_REVIEW_STATUS}",
|
f"- 批次:`{meta.batch_id}`;入场日:{meta.entry_trade_date};信号日:{meta.signal_trade_date}",
|
f"- 市场闸门:{meta.market_gate_status};候选数:{fmt_int(meta.candidate_count)};入选 top5:{len(case_selected)}",
|
"- `RETURN_STAT_READY=false`",
|
"",
|
]
|
if not summary_by_case.empty and case_id in summary_by_case.index:
|
s = summary_by_case.loc[case_id]
|
return_scope = clean(s.get("return_stat_scope"), "RETURN_STAT_HELD_BOUNDARY_TABLE")
|
scope_status = clean(s.get("primary_strict_closed_case_reason"))
|
boundary_category = clean(scope_row.get("boundary_category")) if scope_row is not None else ""
|
boundary_reason = clean(scope_row.get("boundary_reason")) if scope_row is not None else ""
|
lines.extend(
|
[
|
"## 案例读数",
|
"",
|
f"- 买入 lot:{fmt_int(s.buy_lot_count)}",
|
f"- 已闭合 lot:{fmt_int(s.closed_lot_count)}",
|
f"- 未闭合 / 边界 lot:{fmt_int(s.unresolved_lot_count)}",
|
f"- 闭合 lot 账户贡献合计:{fmt_pct(s.account_return_closed_lots)}(送审候选读数,不是完整 baseline 结论)",
|
f"- 当前收益口径:`{return_scope}`",
|
f"- 收益口径状态:`{scope_status}`",
|
f"- 主口径标记:{fmt_int(s.get('primary_strict_closed_case_flag'))}",
|
f"- 边界分类:{boundary_category or '无'}",
|
f"- 边界原因:{boundary_reason or '无,当前 case 满足 PRIMARY_STRICT_CLOSED_CASE。'}",
|
"",
|
]
|
)
|
else:
|
if scope_row is not None:
|
scope_status = clean(scope_row.get("case_scope_status"), "NO_SUMMARY_BOUNDARY")
|
return_scope = "PRIMARY_STRICT_CLOSED_CASE" if fmt_int(scope_row.get("primary_strict_closed_case_flag")) == "1" else "RETURN_STAT_HELD_BOUNDARY_TABLE"
|
boundary_category = clean(scope_row.get("boundary_category"))
|
boundary_reason = clean(scope_row.get("boundary_reason"))
|
else:
|
scope_status = "NO_SCOPE_ROW"
|
return_scope = "RETURN_STAT_HELD_BOUNDARY_TABLE"
|
boundary_category = ""
|
boundary_reason = "未找到 full_return_stat_case_scope.csv 对应行。"
|
lines.extend(
|
[
|
"## 案例读数",
|
"",
|
"- 本案例日无 BUY,未进入主收益候选。",
|
"- 若市场闸门关闭,应只能看到 NO_TRADE_MARKET_GATE_CLOSED 决策。",
|
f"- 当前收益口径:`{return_scope}`",
|
f"- 收益口径状态:`{scope_status}`",
|
f"- 边界分类:{boundary_category or '无'}",
|
f"- 边界原因:{boundary_reason or '无'}",
|
"",
|
]
|
)
|
|
if not case_lots.empty:
|
lines.extend(["## 持仓状态", ""])
|
for _, lot in case_lots.iterrows():
|
exit_part = f";卖出 {lot.exit_trade_date} {clean(lot.exit_time)} @ {fmt_price(lot.exit_price)}" if has_value(lot.exit_price) else ""
|
contrib = f";账户贡献 {fmt_pct(lot.account_return_contribution_pct)}" if has_value(lot.account_return_contribution_pct) else ""
|
lines.append(
|
f"- {lot.trade_lot_id} / {lot.symbol}:`{lot.lot_status}`;买入 {lot.entry_trade_date} {lot.entry_time} @ {fmt_price(lot.entry_price)}{exit_part}{contrib}"
|
)
|
lines.append("")
|
|
for role, title in ROLE_TITLES.items():
|
group = rows[rows.chart_role == role].copy()
|
if group.empty:
|
continue
|
lines.extend([f"## {title}", ""])
|
for _, row in group.iterrows():
|
rel = link_from_doc(board_path, row.path)
|
decision = clean(row.decision_time)
|
note = clean(row.note)
|
status = clean(row.status)
|
lines.extend(
|
[
|
f"### {row.symbol} {decision}".rstrip(),
|
"",
|
f"",
|
"",
|
f"- 图状态:`{status}`",
|
f"- 说明:{note}",
|
"",
|
]
|
)
|
|
lines.extend(
|
[
|
"## 审核边界",
|
"",
|
"- 本板是当前全量分批执行包的图片第一入口;CSV/JSON 是反查材料。",
|
"- 本案例读数只能按 `full_return_stat_*` 分层口径引用,不得脱离主 / 覆盖 / 辅助 lot 口径写成无边界完整 baseline 结论。",
|
"- 非真实 SELL、数据缺口或窗口末估值 lot 必须保留边界,不得强行转 SELL。",
|
"",
|
]
|
)
|
board_path.write_text("\n".join(lines), encoding="utf-8")
|
|
story_path = case_dir / "case_story_board.md"
|
story = [
|
f"# {case_id} 一页式故事板",
|
"",
|
f"- 当前 run:`{RUN_ID}`",
|
f"- 当前阶段:`{STAGE}`",
|
f"- 批次:`{meta.batch_id}`;入场日:{meta.entry_trade_date}",
|
f"- 市场闸门:{meta.market_gate_status}",
|
"- 图片审核板:`case_image_board.md`",
|
"- `RETURN_STAT_READY=false`",
|
"",
|
"## 收益口径",
|
"",
|
]
|
if scope_row is not None:
|
story_scope = "PRIMARY_STRICT_CLOSED_CASE" if fmt_int(scope_row.get("primary_strict_closed_case_flag")) == "1" else "RETURN_STAT_HELD_BOUNDARY_TABLE"
|
story.extend(
|
[
|
f"- 当前收益口径:`{story_scope}`",
|
f"- 收益口径状态:`{clean(scope_row.get('case_scope_status'))}`",
|
f"- 边界分类:{clean(scope_row.get('boundary_category'), '无')}",
|
f"- 边界原因:{clean(scope_row.get('boundary_reason'), '无')}",
|
"",
|
]
|
)
|
else:
|
story.extend(["- 当前收益口径:`RETURN_STAT_HELD_BOUNDARY_TABLE`", "- 收益口径状态:`NO_SCOPE_ROW`", ""])
|
story.extend(
|
[
|
"## 操作时间线",
|
"",
|
]
|
)
|
if case_decisions.empty:
|
story.append("- 本案例无决策记录。")
|
else:
|
for _, row in case_decisions.iterrows():
|
image = clean(row.evidence_image_path)
|
image_link = link_from_doc(story_path, image) if image else ""
|
img_text = f";图:[{Path(image_link).name}]({image_link})" if image_link else ""
|
price = f" @ {fmt_price(row.price)}" if has_value(row.price) else ""
|
story.append(
|
f"- {row.decision_stage} / `{row.action_status}`:{row.symbol} {clean(row.decision_time, '无裁决时间')}{price};{clean(row.decision_reason_cn)}{img_text}"
|
)
|
story.extend(["", "## Lot 收口", ""])
|
if case_lots.empty:
|
story.append("- 本案例无 BUY / 无 lot。")
|
else:
|
for _, lot in case_lots.iterrows():
|
exit_part = f";卖出 {lot.exit_trade_date} {clean(lot.exit_time)} @ {fmt_price(lot.exit_price)}" if has_value(lot.exit_price) else ""
|
story.append(f"- {lot.symbol}:`{lot.lot_status}`;买入 {lot.entry_trade_date} {lot.entry_time} @ {fmt_price(lot.entry_price)}{exit_part}")
|
story.extend(
|
[
|
"",
|
"## 审核提示",
|
"",
|
"- 先看图片审核板,再回查账本。",
|
"- 本页读数只能按 `full_return_stat_*` 分层口径引用,不得脱离边界样本说明转写为无边界完整 baseline 结论。",
|
"",
|
]
|
)
|
story_path.write_text("\n".join(story), encoding="utf-8")
|
|
|
def write_root_and_batch_boards(
|
case_index: pd.DataFrame,
|
batch_index: pd.DataFrame,
|
selected: pd.DataFrame,
|
decisions: pd.DataFrame,
|
orders: pd.DataFrame,
|
lots: pd.DataFrame,
|
case_summary: pd.DataFrame,
|
image_manifest: pd.DataFrame,
|
) -> None:
|
scope_lines = build_scope_lines(case_index, batch_index, selected, orders, lots, case_summary)
|
summary_by_case = case_summary.set_index("case_id") if not case_summary.empty else pd.DataFrame()
|
|
root_lines = [
|
"# 全量分批执行图片审核入口",
|
"",
|
*scope_lines,
|
"",
|
"## 批次入口",
|
"",
|
"| batch_id | 案例日 | 入场日期范围 | 候选 | BUY | SELL | 边界 lot | 批次图片板 | 批次故事板 |",
|
"|---|---:|---|---:|---:|---:|---:|---|---|",
|
]
|
story_lines = [
|
f"# {RUN_ID} 全量分批执行故事板总入口",
|
"",
|
"用途:给人工审核员从批次入口进入 743 个案例日;每个案例优先看图片,再回查账本。",
|
"",
|
"## 总体边界",
|
"",
|
*scope_lines,
|
"",
|
"## 批次总览",
|
"",
|
"| batch_id | 案例日 | 入场日期范围 | BUY | SELL | 边界 lot |",
|
"|---|---:|---|---:|---:|---:|",
|
]
|
|
for _, batch in batch_index.sort_values("batch_order").iterrows():
|
batch_id = str(batch.batch_id)
|
batch_dir = ROOT / "batches" / batch_id
|
batch_dir.mkdir(parents=True, exist_ok=True)
|
case_ids = case_index[case_index.batch_id == batch_id].case_id.astype(str).tolist()
|
batch_cases = case_index[case_index.case_id.isin(case_ids)].copy()
|
batch_selected = selected[selected.case_id.isin(case_ids)].copy()
|
batch_decisions = decisions[decisions.case_id.isin(case_ids)].copy()
|
batch_orders = orders[orders.case_id.isin(case_ids)].copy()
|
batch_lots = lots[lots.case_id.isin(case_ids)].copy()
|
batch_summary = case_summary[case_summary.case_id.isin(case_ids)].copy()
|
batch_images = image_manifest[image_manifest.case_id.isin(case_ids)].copy()
|
boundary = batch_lots[batch_lots.lot_status != "CLOSED_BY_AI_SELL"]
|
order_counts = role_counts(batch_orders, "action")
|
|
for name, df in [
|
("case_index.csv", batch_cases),
|
("selected_candidate_ledger.csv", batch_selected),
|
("decision_log.csv", batch_decisions),
|
("order_ledger.csv", batch_orders),
|
("position_lot_ledger.csv", batch_lots),
|
("daily_account_ledger.csv", read_csv("daily_account_ledger.csv")[read_csv("daily_account_ledger.csv").case_id.isin(case_ids)]),
|
("case_summary.csv", batch_summary),
|
("sell_decision_log.csv", read_csv("sell_decision_log.csv")[read_csv("sell_decision_log.csv").case_id.isin(case_ids)]),
|
("exit_resolution_log.csv", read_csv("exit_resolution_log.csv")[read_csv("exit_resolution_log.csv").case_id.isin(case_ids)]),
|
("image_manifest.csv", batch_images),
|
]:
|
write_csv(df, batch_dir / name)
|
|
batch_summary_json = {
|
"schema_version": "1.0",
|
"run_id": RUN_ID,
|
"batch_id": batch_id,
|
"stage": STAGE,
|
"case_count": int(len(batch_cases)),
|
"selected_candidate_rows": int(len(batch_selected)),
|
"order_counts": role_counts(batch_orders, "action"),
|
"lot_status_counts": role_counts(batch_lots, "lot_status"),
|
"image_role_counts": role_counts(batch_images, "chart_role"),
|
"return_stat_ready": False,
|
"boundary": "batch package for audit only; full execution audit pending",
|
}
|
(batch_dir / "batch_summary.json").write_text(json.dumps(batch_summary_json, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
(batch_dir / "batch_summary.md").write_text(
|
"\n".join(
|
[
|
f"# {batch_id} 批次摘要",
|
"",
|
f"- run:`{RUN_ID}`",
|
f"- 阶段:`{STAGE}`",
|
f"- 案例日:{len(batch_cases)}",
|
f"- 日期范围:{batch.entry_date_start} 至 {batch.entry_date_end}",
|
f"- 候选:{len(batch_selected)}",
|
f"- BUY:{order_counts.get('BUY', 0)},SELL:{order_counts.get('SELL', 0)}",
|
f"- 边界 lot:{len(boundary)}",
|
"- `RETURN_STAT_READY=false`",
|
"",
|
]
|
),
|
encoding="utf-8",
|
)
|
|
batch_board = [
|
f"# {batch_id} 批次图片审核入口",
|
"",
|
f"- 当前 run:`{RUN_ID}`",
|
f"- 当前阶段:`{STAGE}`",
|
f"- 日期范围:{batch.entry_date_start} 至 {batch.entry_date_end}",
|
f"- 案例日:{len(batch_cases)};候选:{len(batch_selected)};BUY:{order_counts.get('BUY', 0)};SELL:{order_counts.get('SELL', 0)};边界 lot:{len(boundary)}",
|
"- `RETURN_STAT_READY=false`",
|
"",
|
"| case_id | 入场日 | 市场闸门 | BUY lot | 已闭合 | 边界 lot | 图片板 | 故事板 |",
|
"|---|---|---|---:|---:|---:|---|---|",
|
]
|
for _, case in batch_cases.sort_values("entry_trade_date").iterrows():
|
case_id = str(case.case_id)
|
if not summary_by_case.empty and case_id in summary_by_case.index:
|
s = summary_by_case.loc[case_id]
|
buy = fmt_int(s.buy_lot_count)
|
closed = fmt_int(s.closed_lot_count)
|
unresolved = fmt_int(s.unresolved_lot_count)
|
else:
|
buy = closed = unresolved = "0"
|
batch_board.append(
|
f"| {case_id} | {case.entry_trade_date} | {case.market_gate_status} | {buy} | {closed} | {unresolved} | [图片板](../../cases/{case_id}/case_image_board.md) | [故事板](../../cases/{case_id}/case_story_board.md) |"
|
)
|
(batch_dir / "case_image_board.md").write_text("\n".join(batch_board) + "\n", encoding="utf-8")
|
(batch_dir / "case_story_board.md").write_text("\n".join(batch_board).replace("图片审核入口", "故事板入口") + "\n", encoding="utf-8")
|
(batch_dir / "manifest.json").write_text(json.dumps(manifest_for_dir(batch_dir), ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
|
root_lines.append(
|
f"| {batch_id} | {len(batch_cases)} | {batch.entry_date_start} 至 {batch.entry_date_end} | {len(batch_selected)} | {order_counts.get('BUY', 0)} | {order_counts.get('SELL', 0)} | {len(boundary)} | [图片板](batches/{batch_id}/case_image_board.md) | [故事板](batches/{batch_id}/case_story_board.md) |"
|
)
|
story_lines.append(
|
f"| {batch_id} | {len(batch_cases)} | {batch.entry_date_start} 至 {batch.entry_date_end} | {order_counts.get('BUY', 0)} | {order_counts.get('SELL', 0)} | {len(boundary)} |"
|
)
|
|
root_lines.extend(
|
[
|
"",
|
"## 图片角色统计",
|
"",
|
*[f"- {ROLE_TITLES.get(role, role)}:{count} 张" for role, count in role_counts(image_manifest, "chart_role").items()],
|
"",
|
"## Lot 状态统计",
|
"",
|
*[f"- {status}:{count}" for status, count in role_counts(lots, "lot_status").items()],
|
"",
|
]
|
)
|
story_lines.extend(
|
[
|
"",
|
"## 审核路径",
|
"",
|
"- 从批次入口进入案例图板。",
|
"- 图片是人工审核第一入口;CSV/JSON 是反查证据。",
|
"- 当前全量分批执行包已通过返修复审;完整结论仍必须按 `full_return_stat_*` 分层引用,`RETURN_STAT_READY=false` 继续保留。",
|
"",
|
]
|
)
|
(ROOT / "case_image_board.md").write_text("\n".join(root_lines), encoding="utf-8")
|
(ROOT / "case_story_board.md").write_text("\n".join(story_lines), encoding="utf-8")
|
|
|
def check(name: str, passed: bool, detail: str, rows: list[dict]) -> None:
|
rows.append({"check_name": name, "status": "PASS" if passed else "FAIL", "detail": detail})
|
|
|
def to_float(value: object, default: float = 0.0) -> float:
|
if not has_value(value):
|
return default
|
return float(value)
|
|
|
def boolish(value: object) -> bool:
|
return clean(value).lower() in {"1", "true", "yes"}
|
|
|
def action_status_summary(df: pd.DataFrame) -> str:
|
if df.empty or "action_status" not in df.columns:
|
return ""
|
counts = df.action_status.value_counts(dropna=False).to_dict()
|
return "; ".join(f"{k}:{int(v)}" for k, v in counts.items())
|
|
|
def classify_case_scope(meta: pd.Series, case_lots: pd.DataFrame, entry_decisions: pd.DataFrame, summary_row: pd.Series | None) -> tuple[str, str, str]:
|
market_status = clean(meta.get("market_gate_status"))
|
has_buy = summary_row is not None and int(to_float(summary_row.get("buy_lot_count"))) > 0
|
unresolved_count = int((case_lots.lot_status != "CLOSED_BY_AI_SELL").sum()) if not case_lots.empty else 0
|
all_closed = has_buy and unresolved_count == 0 and bool((case_lots.lot_status == "CLOSED_BY_AI_SELL").all())
|
if market_status == "MKT_GATE_OPEN_PREV_DAY_UP_3000" and all_closed:
|
return "PRIMARY_STRICT_CLOSED_CASE", "PRIMARY_STRICT_CLOSED_CASE", "市场闸门打开、有真实 BUY,且全部 lot 均为 CLOSED_BY_AI_SELL。"
|
if market_status == "NO_TRADE_MARKET_GATE_CLOSED":
|
return "NO_TRADE_MARKET_GATE_CLOSED", "MARKET_GATE_CLOSED", "市场闸门关闭,覆盖口径保留,不进入主收益 / 成功率口径。"
|
if not has_buy:
|
statuses = set(entry_decisions.action_status.astype(str).tolist()) if not entry_decisions.empty else set()
|
if "DATA_GAP_HELD" in statuses:
|
return "ENTRY_DATA_GAP_HELD_NO_BUY", "ENTRY_DATA_GAP_HELD", "市场闸门打开但入场分钟线或裁决数据缺口,未生成 BUY。"
|
return "OPEN_GATE_NO_BUY_AI_REVIEWED", "NO_BUY_AI_REVIEWED", "市场闸门打开但 AI 买点复核未确认 BUY。"
|
if unresolved_count > 0:
|
statuses = ", ".join(sorted(case_lots[case_lots.lot_status != "CLOSED_BY_AI_SELL"].lot_status.astype(str).unique()))
|
return "BOUNDARY_UNRESOLVED_LOT", "UNRESOLVED_LOT_BOUNDARY", f"存在未真实 SELL / 数据缺口 lot:{statuses}。"
|
return "RETURN_STAT_HELD_OTHER", "OTHER_BOUNDARY", "未满足 PRIMARY_STRICT_CLOSED_CASE 的其他边界状态。"
|
|
|
def build_return_stat_artifacts(
|
case_index: pd.DataFrame,
|
decisions: pd.DataFrame,
|
lots: pd.DataFrame,
|
case_summary: pd.DataFrame,
|
) -> pd.DataFrame:
|
summary_by_case = {str(row.case_id): row for _, row in case_summary.iterrows()} if not case_summary.empty else {}
|
lot_groups = {str(case_id): group.copy() for case_id, group in lots.groupby("case_id", sort=False)} if not lots.empty else {}
|
entry_decisions = decisions[decisions.decision_stage == "ENTRY_AI_REVIEW"].copy() if not decisions.empty else pd.DataFrame()
|
entry_groups = {str(case_id): group.copy() for case_id, group in entry_decisions.groupby("case_id", sort=False)} if not entry_decisions.empty else {}
|
|
case_scope_rows: list[dict] = []
|
boundary_rows: list[dict] = []
|
primary_case_ids: set[str] = set()
|
|
for _, meta in case_index.sort_values("entry_trade_date").iterrows():
|
case_id = str(meta.case_id)
|
case_lots = lot_groups.get(case_id, pd.DataFrame(columns=lots.columns))
|
summary_row = summary_by_case.get(case_id)
|
entry_group = entry_groups.get(case_id, pd.DataFrame(columns=entry_decisions.columns))
|
scope_status, boundary_category, reason = classify_case_scope(meta, case_lots, entry_group, summary_row)
|
primary = scope_status == "PRIMARY_STRICT_CLOSED_CASE"
|
if primary:
|
primary_case_ids.add(case_id)
|
|
buy_count = int(to_float(summary_row.get("buy_lot_count"))) if summary_row is not None else 0
|
closed_count = int(to_float(summary_row.get("closed_lot_count"))) if summary_row is not None else 0
|
unresolved_count = int(to_float(summary_row.get("unresolved_lot_count"))) if summary_row is not None else 0
|
case_return = to_float(summary_row.get("account_return_closed_lots")) if summary_row is not None else 0.0
|
unresolved_statuses = ""
|
if not case_lots.empty:
|
unresolved_statuses = "; ".join(
|
f"{k}:{int(v)}"
|
for k, v in case_lots[case_lots.lot_status != "CLOSED_BY_AI_SELL"].lot_status.value_counts(dropna=False).to_dict().items()
|
)
|
|
case_scope_rows.append(
|
{
|
"run_id": RUN_ID,
|
"case_id": case_id,
|
"batch_id": clean(meta.get("batch_id")),
|
"entry_trade_date": clean(meta.get("entry_trade_date")),
|
"signal_trade_date": clean(meta.get("signal_trade_date")),
|
"market_gate_status": clean(meta.get("market_gate_status")),
|
"market_gate_open_flag": clean(meta.get("market_gate_open_flag")),
|
"coverage_scope": "ALL_ENTRY_DATE_COVERAGE",
|
"case_scope_status": scope_status,
|
"primary_scope": "PRIMARY_STRICT_CLOSED_CASE" if primary else "",
|
"primary_strict_closed_case_flag": int(primary),
|
"strict_closed_lot_recalc_only_count": closed_count,
|
"buy_lot_count": buy_count,
|
"closed_lot_count": closed_count,
|
"unresolved_lot_count": unresolved_count,
|
"unresolved_lot_statuses": unresolved_statuses,
|
"account_return_closed_lots": f"{case_return:.8f}" if summary_row is not None else "",
|
"primary_case_success_flag": int(primary and case_return > 0),
|
"boundary_category": "" if primary else boundary_category,
|
"boundary_reason": "" if primary else reason,
|
"entry_action_status_summary": action_status_summary(entry_group),
|
"excluded_from_primary_flag": int(not primary),
|
"source_case_summary_present": int(summary_row is not None),
|
}
|
)
|
if not primary:
|
boundary_rows.append(
|
{
|
"run_id": RUN_ID,
|
"boundary_id": f"CASE-{case_id}",
|
"boundary_level": "CASE",
|
"case_id": case_id,
|
"trade_lot_id": "",
|
"symbol": "",
|
"entry_trade_date": clean(meta.get("entry_trade_date")),
|
"boundary_category": boundary_category,
|
"boundary_status": scope_status,
|
"boundary_reason": reason,
|
"excluded_from_primary_flag": 1,
|
"coverage_scope": "ALL_ENTRY_DATE_COVERAGE",
|
}
|
)
|
|
case_scope = pd.DataFrame(case_scope_rows)
|
primary_flag_by_case = dict(zip(case_scope.case_id, case_scope.primary_strict_closed_case_flag))
|
primary_reason_by_case = dict(zip(case_scope.case_id, case_scope.case_scope_status))
|
|
lot_rows: list[dict] = []
|
for _, lot in lots.iterrows():
|
case_id = str(lot.case_id)
|
closed = clean(lot.lot_status) == "CLOSED_BY_AI_SELL"
|
primary_case = bool(primary_flag_by_case.get(case_id, 0))
|
lot_scope = "STRICT_CLOSED_LOT_RECALC_ONLY" if closed else "RETURN_STAT_HELD_BOUNDARY_TABLE"
|
boundary_category = "" if closed else clean(lot.lot_status)
|
boundary_reason = "" if closed else "lot 未形成真实 AI SELL,必须排除出主 case 收益 / 成功率口径。"
|
lot_rows.append(
|
{
|
"run_id": RUN_ID,
|
"trade_lot_id": clean(lot.trade_lot_id),
|
"case_id": case_id,
|
"symbol": clean(lot.symbol),
|
"entry_trade_date": clean(lot.entry_trade_date),
|
"entry_time": clean(lot.entry_time),
|
"entry_price": clean(lot.entry_price),
|
"exit_trade_date": clean(lot.exit_trade_date),
|
"exit_time": clean(lot.exit_time),
|
"exit_price": clean(lot.exit_price),
|
"lot_status": clean(lot.lot_status),
|
"lot_scope": lot_scope,
|
"closed_lot_recalc_included_flag": int(closed),
|
"primary_case_included_flag": int(primary_case),
|
"position_pct": clean(lot.position_pct),
|
"lot_return_pct": clean(lot.lot_return_pct),
|
"account_return_contribution_pct": clean(lot.account_return_contribution_pct),
|
"boundary_category": boundary_category,
|
"boundary_reason": boundary_reason,
|
"case_scope_status": primary_reason_by_case.get(case_id, ""),
|
}
|
)
|
if not closed:
|
boundary_rows.append(
|
{
|
"run_id": RUN_ID,
|
"boundary_id": f"LOT-{clean(lot.trade_lot_id)}",
|
"boundary_level": "LOT",
|
"case_id": case_id,
|
"trade_lot_id": clean(lot.trade_lot_id),
|
"symbol": clean(lot.symbol),
|
"entry_trade_date": clean(lot.entry_trade_date),
|
"boundary_category": clean(lot.lot_status),
|
"boundary_status": "LOT_NOT_REAL_SELL",
|
"boundary_reason": boundary_reason,
|
"excluded_from_primary_flag": 1,
|
"coverage_scope": "STRICT_CLOSED_LOT_RECALC_ONLY_EXCLUSION",
|
}
|
)
|
|
lot_scope = pd.DataFrame(lot_rows)
|
boundary_table = pd.DataFrame(boundary_rows)
|
|
primary_cases = case_scope[case_scope.primary_strict_closed_case_flag == 1].copy()
|
primary_returns = pd.to_numeric(primary_cases.account_return_closed_lots, errors="coerce").fillna(0.0)
|
closed_lots = lot_scope[lot_scope.closed_lot_recalc_included_flag == 1].copy()
|
closed_lot_contrib = pd.to_numeric(closed_lots.account_return_contribution_pct, errors="coerce").fillna(0.0)
|
|
summary = {
|
"schema_version": "1.0",
|
"task_id": TASK_ID,
|
"run_id": RUN_ID,
|
"generated_at": now_iso(),
|
"stage": STAGE,
|
"execution_review_status": "EXECUTION_REREVIEW_PASSED_RETURN_STAT_HELD",
|
"design_audit_id": DESIGN_AUDIT_ID,
|
"held_audit_id": EXEC_HELD_AUDIT_ID,
|
"held_message_id": EXEC_HELD_MESSAGE_ID,
|
"rereview_held_audit_id": EXEC_REREVIEW_HELD_AUDIT_ID,
|
"rereview_held_message_id": EXEC_REREVIEW_HELD_MESSAGE_ID,
|
"rereview_pass_audit_id": EXEC_REREVIEW_PASS_AUDIT_ID,
|
"rereview_pass_message_id": EXEC_REREVIEW_PASS_MESSAGE_ID,
|
"issue_id": RETURN_SCOPE_ISSUE_ID,
|
"return_stat_ready": False,
|
"full_baseline_conclusion_allowed": False,
|
"primary_scope": {
|
"name": "PRIMARY_STRICT_CLOSED_CASE",
|
"case_count": int(len(primary_cases)),
|
"positive_case_count": int((primary_returns > 0).sum()),
|
"non_positive_case_count": int((primary_returns <= 0).sum()),
|
"candidate_success_rate_for_audit_only": float((primary_returns > 0).mean()) if len(primary_returns) else 0.0,
|
"account_return_sum_for_audit_only": float(primary_returns.sum()) if len(primary_returns) else 0.0,
|
"account_return_mean_for_audit_only": float(primary_returns.mean()) if len(primary_returns) else 0.0,
|
"account_return_median_for_audit_only": float(primary_returns.median()) if len(primary_returns) else 0.0,
|
"account_return_min_for_audit_only": float(primary_returns.min()) if len(primary_returns) else 0.0,
|
"account_return_max_for_audit_only": float(primary_returns.max()) if len(primary_returns) else 0.0,
|
},
|
"coverage_scope": {
|
"name": "ALL_ENTRY_DATE_COVERAGE",
|
"entry_date_count": int(len(case_scope)),
|
"market_gate_open_entry_dates": int((case_scope.market_gate_status == "MKT_GATE_OPEN_PREV_DAY_UP_3000").sum()),
|
"market_gate_closed_entry_dates": int((case_scope.market_gate_status == "NO_TRADE_MARKET_GATE_CLOSED").sum()),
|
"buy_case_count": int((pd.to_numeric(case_scope.buy_lot_count, errors="coerce").fillna(0) > 0).sum()),
|
"open_gate_no_buy_case_count": int(((case_scope.market_gate_status == "MKT_GATE_OPEN_PREV_DAY_UP_3000") & (pd.to_numeric(case_scope.buy_lot_count, errors="coerce").fillna(0) == 0)).sum()),
|
"excluded_case_count": int((case_scope.excluded_from_primary_flag == 1).sum()),
|
},
|
"lot_recalc_scope": {
|
"name": "STRICT_CLOSED_LOT_RECALC_ONLY",
|
"total_lot_count": int(len(lot_scope)),
|
"closed_lot_count": int((lot_scope.closed_lot_recalc_included_flag == 1).sum()),
|
"unresolved_lot_count": int((lot_scope.closed_lot_recalc_included_flag == 0).sum()),
|
"positive_closed_lot_count": int((closed_lot_contrib > 0).sum()),
|
"closed_lot_account_return_sum_for_recalc_only": float(closed_lot_contrib.sum()) if len(closed_lot_contrib) else 0.0,
|
},
|
"boundary": {
|
"case_boundary_count": int((boundary_table.boundary_level == "CASE").sum()) if not boundary_table.empty else 0,
|
"lot_boundary_count": int((boundary_table.boundary_level == "LOT").sum()) if not boundary_table.empty else 0,
|
"case_boundary_counts": role_counts(boundary_table[boundary_table.boundary_level == "CASE"], "boundary_category") if not boundary_table.empty else {},
|
"lot_boundary_counts": role_counts(boundary_table[boundary_table.boundary_level == "LOT"], "boundary_category") if not boundary_table.empty else {},
|
},
|
"timestamp_note": "entry_* generated_at values are stage artifact timestamps from long-running execution; current package status is governed by summary.json, self_check.json, full_return_stat_summary.json and manifest.json.",
|
"boundary_statement": "Execution re-review passed for the full batch package, but RETURN_STAT_READY remains false. Readouts may only be cited with the explicit full_return_stat_* layered scopes and boundaries.",
|
}
|
|
write_csv(case_scope, ROOT / "full_return_stat_case_scope.csv")
|
write_csv(lot_scope, ROOT / "full_return_stat_lot_scope.csv")
|
write_csv(boundary_table, ROOT / "full_return_stat_boundary_table.csv")
|
(ROOT / "full_return_stat_summary.json").write_text(json.dumps(summary, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
(ROOT / "full_return_stat_summary.md").write_text(
|
"\n".join(
|
[
|
f"# {RUN_ID} 全量收益口径分层复审通过摘要",
|
"",
|
f"- 阶段:`{STAGE}`",
|
f"- 设计审核:`{DESIGN_AUDIT_ID}`",
|
f"- 原执行审核 HELD:`{EXEC_HELD_AUDIT_ID}`",
|
f"- 关联问题:`{RETURN_SCOPE_ISSUE_ID}`",
|
f"- 执行复审 HELD:`{EXEC_REREVIEW_HELD_AUDIT_ID}`",
|
f"- 执行复审通过:`{EXEC_REREVIEW_PASS_AUDIT_ID}`",
|
"- `RETURN_STAT_READY=false`",
|
"- 当前读数已通过执行复审,但只能按 `full_return_stat_*` 分层口径引用,不得脱离边界写成无边界完整 baseline 结论。",
|
"",
|
"## PRIMARY_STRICT_CLOSED_CASE",
|
"",
|
f"- case 数:{summary['primary_scope']['case_count']}",
|
f"- 正收益 case:{summary['primary_scope']['positive_case_count']}",
|
f"- 非正收益 case:{summary['primary_scope']['non_positive_case_count']}",
|
f"- 成功率候选读数(仅供复审):{summary['primary_scope']['candidate_success_rate_for_audit_only']:.6f}",
|
f"- 账户贡献合计候选读数(仅供复审):{summary['primary_scope']['account_return_sum_for_audit_only']:.8f}",
|
"",
|
"## ALL_ENTRY_DATE_COVERAGE",
|
"",
|
f"- entry date:{summary['coverage_scope']['entry_date_count']}",
|
f"- 市场闸门打开:{summary['coverage_scope']['market_gate_open_entry_dates']}",
|
f"- 市场闸门关闭:{summary['coverage_scope']['market_gate_closed_entry_dates']}",
|
f"- 有 BUY case:{summary['coverage_scope']['buy_case_count']}",
|
f"- 市场闸门打开但无 BUY case:{summary['coverage_scope']['open_gate_no_buy_case_count']}",
|
f"- 排除出主口径 case:{summary['coverage_scope']['excluded_case_count']}",
|
"",
|
"## STRICT_CLOSED_LOT_RECALC_ONLY",
|
"",
|
f"- 总 lot:{summary['lot_recalc_scope']['total_lot_count']}",
|
f"- 闭合 lot:{summary['lot_recalc_scope']['closed_lot_count']}",
|
f"- 边界 lot:{summary['lot_recalc_scope']['unresolved_lot_count']}",
|
f"- 闭合 lot 账户贡献合计(仅供复算定位):{summary['lot_recalc_scope']['closed_lot_account_return_sum_for_recalc_only']:.8f}",
|
"",
|
"## 边界",
|
"",
|
f"- case 边界:{summary['boundary']['case_boundary_count']}",
|
f"- lot 边界:{summary['boundary']['lot_boundary_count']}",
|
"- 市场闸门关闭、无 BUY、非真实 SELL、数据缺口等样本不得混入主成功率 / 主收益率。",
|
"",
|
]
|
),
|
encoding="utf-8",
|
)
|
|
if not case_summary.empty:
|
case_summary = case_summary.copy()
|
case_summary["primary_strict_closed_case_flag"] = case_summary.case_id.astype(str).map(primary_flag_by_case).fillna(0).astype(int)
|
case_summary["primary_strict_closed_case_reason"] = case_summary.case_id.astype(str).map(primary_reason_by_case).fillna("CASE_NOT_IN_FULL_SCOPE")
|
case_summary["return_stat_scope"] = case_summary["primary_strict_closed_case_flag"].map(
|
{1: "PRIMARY_STRICT_CLOSED_CASE", 0: "RETURN_STAT_HELD_BOUNDARY_TABLE"}
|
)
|
case_summary["return_stat_ready_global_flag"] = 0
|
write_csv(case_summary, ROOT / "case_summary.csv")
|
|
return case_summary
|
|
|
def run_self_check(
|
case_index: pd.DataFrame,
|
batch_index: pd.DataFrame,
|
selected: pd.DataFrame,
|
candidate_diff: pd.DataFrame,
|
decisions: pd.DataFrame,
|
sell_decisions: pd.DataFrame,
|
exit_resolution: pd.DataFrame,
|
orders: pd.DataFrame,
|
lots: pd.DataFrame,
|
account: pd.DataFrame,
|
case_summary: pd.DataFrame,
|
image_manifest: pd.DataFrame,
|
) -> dict:
|
checks: list[dict] = []
|
action_counts = role_counts(orders, "action")
|
lot_counts = role_counts(lots, "lot_status")
|
buy_orders = int(action_counts.get("BUY", 0))
|
sell_orders = int(action_counts.get("SELL", 0))
|
closed = lots[lots.lot_status == "CLOSED_BY_AI_SELL"].copy()
|
unresolved = lots[lots.lot_status != "CLOSED_BY_AI_SELL"].copy()
|
|
check(
|
"FULL_CASE_SCOPE_FROZEN",
|
len(case_index) == 743 and case_index.entry_trade_date.nunique() == 743 and len(batch_index) == 15,
|
f"cases={len(case_index)}, unique_dates={case_index.entry_trade_date.nunique()}, batches={len(batch_index)}",
|
checks,
|
)
|
per_case_counts = selected.groupby("case_id").size()
|
check(
|
"FULL_SELECTED_CANDIDATE_ROWS",
|
len(selected) == 3715 and selected.entry_trade_date.nunique() == 743 and per_case_counts.min() == 5 and per_case_counts.max() == 5,
|
f"selected={len(selected)}, entry_dates={selected.entry_trade_date.nunique()}, per_case_min={per_case_counts.min()}, per_case_max={per_case_counts.max()}",
|
checks,
|
)
|
check(
|
"FULL_CANDIDATE_POOL_DIFF_PASS",
|
not candidate_diff.empty and (candidate_diff.status == "PASS").all(),
|
candidate_diff.to_dict("records").__repr__(),
|
checks,
|
)
|
closed_gate_cases = case_index[case_index.market_gate_status == "NO_TRADE_MARKET_GATE_CLOSED"].case_id.astype(str).tolist()
|
closed_gate_decisions = decisions[(decisions.case_id.isin(closed_gate_cases)) & (decisions.action_status == "NO_TRADE_MARKET_GATE_CLOSED")]
|
closed_gate_orders = orders[orders.case_id.isin(closed_gate_cases)]
|
check(
|
"MARKET_GATE_CLOSED_NO_TRADE",
|
len(closed_gate_cases) == 476 and len(closed_gate_decisions) == 476 * 5 and closed_gate_orders.empty,
|
f"closed_gate_cases={len(closed_gate_cases)}, no_trade_decisions={len(closed_gate_decisions)}, closed_gate_orders={len(closed_gate_orders)}",
|
checks,
|
)
|
entry_decisions = decisions[decisions.decision_stage == "ENTRY_AI_REVIEW"]
|
exit_decisions = decisions[decisions.decision_stage == "EXIT_AI_REVIEW"]
|
check(
|
"DECISION_LOG_ENTRY_EXIT_COUNTS",
|
len(entry_decisions) == len(selected) and len(exit_decisions) == len(sell_decisions) == len(lots),
|
f"entry={len(entry_decisions)}, selected={len(selected)}, exit={len(exit_decisions)}, sell_decisions={len(sell_decisions)}, lots={len(lots)}",
|
checks,
|
)
|
check(
|
"ORDERS_AND_LOTS_MATCH",
|
buy_orders == len(lots) and sell_orders == len(closed) and len(orders) == buy_orders + sell_orders,
|
f"orders={action_counts}; lots={len(lots)}; closed={len(closed)}",
|
checks,
|
)
|
check(
|
"UNRESOLVED_LOTS_EXPLICIT",
|
set(unresolved.lot_status).issubset(ALLOWED_UNRESOLVED),
|
f"unresolved={role_counts(unresolved, 'lot_status')}",
|
checks,
|
)
|
check(
|
"EXIT_RESOLUTION_ALL_LOTS_EXPLICIT",
|
len(exit_resolution) == len(lots) and not exit_resolution.final_action_status.isna().any(),
|
f"exit_resolution={len(exit_resolution)}, statuses={role_counts(exit_resolution, 'final_action_status')}",
|
checks,
|
)
|
|
sell_orders_df = orders[orders.action == "SELL"].copy()
|
merged = sell_orders_df.merge(lots, left_on="source_lot_id", right_on="trade_lot_id", suffixes=("_order", "_lot"))
|
t1_ok = True
|
if not merged.empty:
|
t1_ok = (
|
(pd.to_datetime(merged.trade_date) >= pd.to_datetime(merged.sellable_from_trade_date)).all()
|
and (pd.to_datetime(merged.trade_date) > pd.to_datetime(merged.entry_trade_date)).all()
|
)
|
check("T1_GUARD_FOR_SELL_ORDERS", bool(t1_ok), f"sell_orders={len(sell_orders_df)}", checks)
|
|
lookahead_ok = True
|
for df in [orders, decisions, sell_decisions]:
|
if "lookahead_violation_flag" in df.columns:
|
lookahead_ok = lookahead_ok and not df.lookahead_violation_flag.astype(str).str.lower().isin(["true", "1", "yes"]).any()
|
check("LOOKAHEAD_FLAGS_ZERO", bool(lookahead_ok), "orders/decision/sell_decision checked", checks)
|
|
lot_return_ok = True
|
for _, lot in closed.iterrows():
|
entry = float(lot.entry_price)
|
exit_price = float(lot.exit_price)
|
position = float(lot.position_pct)
|
lot_return = exit_price / entry - 1.0
|
contribution = lot_return * position
|
lot_return_ok = lot_return_ok and abs(lot_return - float(lot.lot_return_pct)) < 1e-6
|
lot_return_ok = lot_return_ok and abs(contribution - float(lot.account_return_contribution_pct)) < 1e-6
|
check("LOT_RETURN_RECOMPUTE", bool(lot_return_ok), f"closed_lots={len(closed)}", checks)
|
|
case_ok = True
|
for _, row in case_summary.iterrows():
|
group = lots[lots.case_id == row.case_id]
|
closed_count = int((group.lot_status == "CLOSED_BY_AI_SELL").sum())
|
unresolved_count = int((group.lot_status != "CLOSED_BY_AI_SELL").sum())
|
contrib = pd.to_numeric(group.account_return_contribution_pct, errors="coerce").fillna(0).sum()
|
case_ok = case_ok and int(row.buy_lot_count) == len(group)
|
case_ok = case_ok and int(row.closed_lot_count) == closed_count
|
case_ok = case_ok and int(row.unresolved_lot_count) == unresolved_count
|
case_ok = case_ok and abs(float(row.account_return_closed_lots) - float(contrib)) < 1e-6
|
case_ok = case_ok and str(row.strict_baseline_return_ready_flag) in {"0", "False", "false"}
|
check("CASE_SUMMARY_RECOMPUTE", bool(case_ok), f"case_summary_rows={len(case_summary)}", checks)
|
|
account_direction_ok = True
|
account_nav_ok = True
|
account_final_open_ok = True
|
for case_id, group in account.groupby("case_id", sort=False):
|
prev_cash = 1.0
|
prev_open = 0.0
|
for _, event in group.iterrows():
|
cash = float(event.cash_pct_after_event)
|
open_pos = float(event.open_position_pct_after_event)
|
nav = float(event.account_nav_after_event)
|
account_nav_ok = account_nav_ok and abs(nav - (cash + open_pos)) < 1e-6
|
if event.action == "BUY":
|
account_direction_ok = account_direction_ok and cash < prev_cash + 1e-12 and open_pos > prev_open - 1e-12
|
elif event.action == "SELL":
|
account_direction_ok = account_direction_ok and cash > prev_cash - 1e-12 and open_pos < prev_open + 1e-12 and open_pos >= -1e-9
|
prev_cash = cash
|
prev_open = open_pos
|
expected_open = pd.to_numeric(
|
lots[(lots.case_id == case_id) & (lots.lot_status != "CLOSED_BY_AI_SELL")].position_pct,
|
errors="coerce",
|
).fillna(0).sum()
|
account_final_open_ok = account_final_open_ok and abs(prev_open - expected_open) < 1e-6
|
check("ACCOUNT_CASH_POSITION_DIRECTION", bool(account_direction_ok), "BUY cash down/open up; SELL cash up/open down", checks)
|
check("ACCOUNT_NAV_EQUALS_CASH_PLUS_OPEN", bool(account_nav_ok), "nav equals cash + open position after each event", checks)
|
check("ACCOUNT_FINAL_OPEN_MATCHES_UNCLOSED_LOTS", bool(account_final_open_ok), "final open position equals unresolved lot position per case", checks)
|
|
chart_rows: list[dict] = []
|
image_hash_ok = True
|
for _, row in image_manifest.iterrows():
|
path = ROOT / str(row.path)
|
exists = path.exists()
|
actual = sha256_file(path) if exists else ""
|
match = exists and actual == str(row.sha256)
|
image_hash_ok = image_hash_ok and match
|
chart_rows.append(
|
{
|
"case_id": row.case_id,
|
"symbol": row.symbol,
|
"chart_role": row.chart_role,
|
"path": row.path,
|
"exists": str(exists),
|
"sha256_match": str(match),
|
}
|
)
|
write_csv(pd.DataFrame(chart_rows), ROOT / "chart_evidence_audit.csv")
|
image_counts = role_counts(image_manifest, "chart_role")
|
open_selected_count = int((selected.market_gate_status == "MKT_GATE_OPEN_PREV_DAY_UP_3000").sum())
|
expected_image_counts = {
|
"candidate_daily_100d_decision_view": len(selected),
|
"entry_1m_morning_review_view": open_selected_count,
|
"entry_1m_late_review_view": open_selected_count,
|
"entry_1m_buy_decision_view": buy_orders,
|
"exit_daily_signal_review_view": len(lots),
|
"exit_1m_sell_decision_view": sell_orders,
|
}
|
check("IMAGE_MANIFEST_HASH_MATCH", bool(image_hash_ok), f"images={len(image_manifest)}", checks)
|
check(
|
"IMAGE_ROLE_COUNTS_EXPECTED",
|
image_counts == expected_image_counts and len(image_manifest) == sum(expected_image_counts.values()),
|
f"actual={image_counts}; expected={expected_image_counts}",
|
checks,
|
)
|
|
board_paths = [
|
ROOT / "case_image_board.md",
|
ROOT / "case_story_board.md",
|
*sorted((ROOT / "batches").glob("*/case_image_board.md")),
|
*sorted((ROOT / "batches").glob("*/case_story_board.md")),
|
*sorted((ROOT / "cases").glob("*/case_image_board.md")),
|
*sorted((ROOT / "cases").glob("*/case_story_board.md")),
|
]
|
link_rows: list[dict] = []
|
links_ok = True
|
for board in board_paths:
|
if not board.exists():
|
links_ok = False
|
link_rows.append({"board": board.relative_to(ROOT).as_posix(), "target": "", "exists": "False"})
|
continue
|
for target in markdown_links(board):
|
exists = target.exists()
|
links_ok = links_ok and exists
|
try:
|
rel_target = target.relative_to(ROOT).as_posix()
|
except ValueError:
|
rel_target = str(target)
|
link_rows.append({"board": board.relative_to(ROOT).as_posix(), "target": rel_target, "exists": str(exists)})
|
write_csv(pd.DataFrame(link_rows), ROOT / "link_evidence_audit.csv")
|
check("MARKDOWN_LOCAL_LINKS_REACHABLE", bool(links_ok), f"boards={len(board_paths)}, links={len(link_rows)}", checks)
|
|
stale_terms = [
|
"EXPAND_30_EXECUTION_SELF_CHECK_DONE",
|
"7 个小样本",
|
"30 案例日受控扩样",
|
"扩样执行审核 HELD",
|
"当前仍为结构试点",
|
"当前执行审核未提交",
|
]
|
status_ok = True
|
stale_hits: list[str] = []
|
required_terms = [RUN_ID, STAGE, "743 个", "15 个批次", "RETURN_STAT_READY=false", "复审通过"]
|
for board in [ROOT / "case_image_board.md", ROOT / "case_story_board.md"]:
|
text = board.read_text(encoding="utf-8")
|
for term in stale_terms:
|
if term in text:
|
status_ok = False
|
stale_hits.append(f"{board.name}:{term}")
|
for term in required_terms:
|
if term not in text:
|
status_ok = False
|
stale_hits.append(f"{board.name}:missing:{term}")
|
check("BOARD_STATUS_MATCHES_FULL_SUMMARY", bool(status_ok), "|".join(stale_hits) or "root boards match full-stage status", checks)
|
|
batch_dirs = sorted((ROOT / "batches").glob("B*"))
|
batch_manifest_ok = len(batch_dirs) == 15 and all((p / "manifest.json").exists() for p in batch_dirs)
|
batch_case_total = sum(len(pd.read_csv(p / "case_index.csv", encoding="utf-8-sig")) for p in batch_dirs if (p / "case_index.csv").exists())
|
batch_selected_total = sum(len(pd.read_csv(p / "selected_candidate_ledger.csv", encoding="utf-8-sig")) for p in batch_dirs if (p / "selected_candidate_ledger.csv").exists())
|
check(
|
"BATCH_PACKAGE_COVERAGE",
|
batch_manifest_ok and batch_case_total == len(case_index) and batch_selected_total == len(selected),
|
f"batch_dirs={len(batch_dirs)}, batch_case_total={batch_case_total}, batch_selected_total={batch_selected_total}",
|
checks,
|
)
|
|
expected_return_files = [
|
ROOT / "full_return_stat_case_scope.csv",
|
ROOT / "full_return_stat_lot_scope.csv",
|
ROOT / "full_return_stat_boundary_table.csv",
|
ROOT / "full_return_stat_summary.md",
|
ROOT / "full_return_stat_summary.json",
|
]
|
return_files_ok = all(path.exists() and path.stat().st_size > 0 for path in expected_return_files)
|
check(
|
"FULL_RETURN_STAT_FILES_PRESENT",
|
bool(return_files_ok),
|
"; ".join(f"{path.name}:{path.exists()}:{path.stat().st_size if path.exists() else 0}" for path in expected_return_files),
|
checks,
|
)
|
|
case_scope = read_csv("full_return_stat_case_scope.csv")
|
lot_scope = read_csv("full_return_stat_lot_scope.csv")
|
boundary_table = read_csv("full_return_stat_boundary_table.csv")
|
return_summary = read_json("full_return_stat_summary.json")
|
case_scope_ok = (
|
not case_scope.empty
|
and len(case_scope) == len(case_index) == 743
|
and case_scope.case_id.nunique() == 743
|
and (case_scope.coverage_scope == "ALL_ENTRY_DATE_COVERAGE").all()
|
)
|
check(
|
"FULL_RETURN_STAT_CASE_SCOPE_COVERAGE",
|
bool(case_scope_ok),
|
f"case_scope_rows={len(case_scope)}, unique_cases={case_scope.case_id.nunique() if not case_scope.empty else 0}",
|
checks,
|
)
|
|
primary_count = int(case_scope.primary_strict_closed_case_flag.sum()) if not case_scope.empty else 0
|
primary_case_ids = set(case_scope[case_scope.primary_strict_closed_case_flag == 1].case_id.astype(str)) if not case_scope.empty else set()
|
primary_lots = lots[lots.case_id.astype(str).isin(primary_case_ids)].copy() if primary_case_ids else pd.DataFrame()
|
primary_boundary_lots = primary_lots[primary_lots.lot_status != "CLOSED_BY_AI_SELL"] if not primary_lots.empty else pd.DataFrame()
|
primary_summary_ok = primary_count == 234 and primary_boundary_lots.empty
|
check(
|
"PRIMARY_STRICT_CLOSED_CASE_SCOPE_VALID",
|
bool(primary_summary_ok),
|
f"primary_cases={primary_count}, primary_boundary_lots={len(primary_boundary_lots)}",
|
checks,
|
)
|
|
lot_scope_ok = (
|
not lot_scope.empty
|
and len(lot_scope) == len(lots)
|
and int(lot_scope.closed_lot_recalc_included_flag.sum()) == len(closed)
|
and int((lot_scope.closed_lot_recalc_included_flag == 0).sum()) == len(unresolved)
|
)
|
check(
|
"FULL_RETURN_STAT_LOT_SCOPE_COMPLETE",
|
bool(lot_scope_ok),
|
f"lot_scope_rows={len(lot_scope)}, lots={len(lots)}, closed={len(closed)}, unresolved={len(unresolved)}",
|
checks,
|
)
|
|
case_boundary_count = int((boundary_table.boundary_level == "CASE").sum()) if not boundary_table.empty else 0
|
lot_boundary_count = int((boundary_table.boundary_level == "LOT").sum()) if not boundary_table.empty else 0
|
boundary_ok = (
|
not boundary_table.empty
|
and case_boundary_count == len(case_index) - primary_count
|
and lot_boundary_count == len(unresolved)
|
and boundary_table.excluded_from_primary_flag.astype(str).isin(["1", "True", "true"]).all()
|
)
|
check(
|
"FULL_RETURN_STAT_BOUNDARY_TABLE_COMPLETE",
|
bool(boundary_ok),
|
f"case_boundaries={case_boundary_count}, expected_case_boundaries={len(case_index) - primary_count}, lot_boundaries={lot_boundary_count}, expected_lot_boundaries={len(unresolved)}",
|
checks,
|
)
|
|
return_summary_ok = False
|
if return_summary:
|
return_summary_ok = (
|
return_summary.get("return_stat_ready") is False
|
and int(return_summary.get("primary_scope", {}).get("case_count", -1)) == primary_count
|
and int(return_summary.get("coverage_scope", {}).get("entry_date_count", -1)) == len(case_index)
|
and int(return_summary.get("lot_recalc_scope", {}).get("closed_lot_count", -1)) == len(closed)
|
and int(return_summary.get("lot_recalc_scope", {}).get("unresolved_lot_count", -1)) == len(unresolved)
|
)
|
check(
|
"FULL_RETURN_STAT_SUMMARY_CONSISTENT",
|
bool(return_summary_ok),
|
f"summary_primary={return_summary.get('primary_scope', {}).get('case_count') if return_summary else ''}, primary_cases={primary_count}",
|
checks,
|
)
|
|
case_summary_primary_ok = (
|
"primary_strict_closed_case_flag" in case_summary.columns
|
and int(case_summary.primary_strict_closed_case_flag.sum()) == primary_count
|
and "return_stat_scope" in case_summary.columns
|
)
|
check(
|
"CASE_SUMMARY_PRIMARY_SCOPE_FLAG_PRESENT",
|
bool(case_summary_primary_ok),
|
f"case_summary_primary={int(case_summary.primary_strict_closed_case_flag.sum()) if 'primary_strict_closed_case_flag' in case_summary.columns else 'missing'}, scope_column={'return_stat_scope' in case_summary.columns}",
|
checks,
|
)
|
|
stale_scope_text = "当前收益口径:STRUCTURE_PILOT_AI_SELL_REVIEWED__EXEC_AUDIT_PENDING__NOT_RETURN_STAT_READY"
|
case_board_scope_ok = not case_scope.empty
|
case_board_scope_errors: list[str] = []
|
for _, scope in case_scope.iterrows():
|
case_id = str(scope.case_id)
|
expected_scope = "PRIMARY_STRICT_CLOSED_CASE" if fmt_int(scope.get("primary_strict_closed_case_flag")) == "1" else "RETURN_STAT_HELD_BOUNDARY_TABLE"
|
expected_status = clean(scope.get("case_scope_status"))
|
expected_boundary = clean(scope.get("boundary_category"))
|
for board_name in ["case_image_board.md", "case_story_board.md"]:
|
board = ROOT / "cases" / case_id / board_name
|
if not board.exists():
|
case_board_scope_ok = False
|
if len(case_board_scope_errors) < 12:
|
case_board_scope_errors.append(f"{case_id}/{board_name}:missing")
|
continue
|
text = board.read_text(encoding="utf-8")
|
if stale_scope_text in text:
|
case_board_scope_ok = False
|
if len(case_board_scope_errors) < 12:
|
case_board_scope_errors.append(f"{case_id}/{board_name}:stale_scope_text")
|
if f"当前收益口径:`{expected_scope}`" not in text:
|
case_board_scope_ok = False
|
if len(case_board_scope_errors) < 12:
|
case_board_scope_errors.append(f"{case_id}/{board_name}:scope_mismatch:{expected_scope}")
|
if expected_status and f"收益口径状态:`{expected_status}`" not in text:
|
case_board_scope_ok = False
|
if len(case_board_scope_errors) < 12:
|
case_board_scope_errors.append(f"{case_id}/{board_name}:status_mismatch:{expected_status}")
|
if expected_scope != "PRIMARY_STRICT_CLOSED_CASE" and expected_boundary and expected_boundary not in text:
|
case_board_scope_ok = False
|
if len(case_board_scope_errors) < 12:
|
case_board_scope_errors.append(f"{case_id}/{board_name}:boundary_missing:{expected_boundary}")
|
check(
|
"CASE_BOARD_RETURN_SCOPE_MATCHES_SCOPE_TABLE",
|
bool(case_board_scope_ok),
|
"case_boards=743 image + 743 story; " + (";".join(case_board_scope_errors) if case_board_scope_errors else "all match full_return_stat_case_scope.csv"),
|
checks,
|
)
|
|
check(
|
"RETURN_STAT_READY_FALSE",
|
RETURN_READY is False and (case_summary.strict_baseline_return_ready_flag.astype(str).isin(["0", "False", "false"])).all(),
|
"full execution package remains not RETURN_STAT_READY before execution audit",
|
checks,
|
)
|
|
checks_df = pd.DataFrame(checks)
|
write_csv(checks_df, ROOT / "self_check_items.csv")
|
fail_count = int((checks_df.status == "FAIL").sum())
|
self_check = {
|
"schema_version": "1.0",
|
"run_id": RUN_ID,
|
"generated_at": now_iso(),
|
"stage": STAGE,
|
"overall_status": "PASS_FOR_FULL_2023_2026_EXECUTION_REREVIEW_READY" if fail_count == 0 else "FAIL",
|
"check_count": int(len(checks_df)),
|
"fail_count": fail_count,
|
"case_count": int(len(case_index)),
|
"batch_count": int(len(batch_index)),
|
"selected_candidate_rows": int(len(selected)),
|
"order_counts": action_counts,
|
"lot_status_counts": lot_counts,
|
"image_role_counts": image_counts,
|
"strict_baseline_return_ready_flag": False,
|
"boundary": "Machine self-check for full 2023-2026 batch execution package after execution re-review pass. RETURN_STAT_READY remains false and readouts require layered full_return_stat_* scope citation.",
|
}
|
(ROOT / "self_check.json").write_text(json.dumps(self_check, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
(ROOT / "self_check.md").write_text(
|
"\n".join(
|
[
|
"# full self_check",
|
"",
|
f"- run:`{RUN_ID}`",
|
f"- 阶段:`{STAGE}`",
|
f"- 状态:`{self_check['overall_status']}`",
|
f"- 检查项:{self_check['check_count']},失败:{self_check['fail_count']}",
|
f"- 案例日:{len(case_index)};批次:{len(batch_index)};候选:{len(selected)}",
|
f"- BUY:{buy_orders};SELL:{sell_orders};边界 lot:{len(unresolved)}",
|
"- `RETURN_STAT_READY=false`",
|
"",
|
"边界:执行复审已通过,但 `RETURN_STAT_READY=false`;完整读数必须按 `full_return_stat_*` 分层口径引用。",
|
"",
|
]
|
),
|
encoding="utf-8",
|
)
|
return self_check
|
|
|
def write_summary_and_manifest(
|
case_index: pd.DataFrame,
|
batch_index: pd.DataFrame,
|
selected: pd.DataFrame,
|
decisions: pd.DataFrame,
|
orders: pd.DataFrame,
|
lots: pd.DataFrame,
|
case_summary: pd.DataFrame,
|
image_manifest: pd.DataFrame,
|
self_check: dict,
|
) -> None:
|
order_counts = role_counts(orders, "action")
|
lot_counts = role_counts(lots, "lot_status")
|
return_stat_summary = read_json("full_return_stat_summary.json")
|
if "primary_strict_closed_case_flag" in case_summary.columns:
|
closed_cases = case_summary[case_summary.primary_strict_closed_case_flag == 1].copy()
|
else:
|
closed_cases = case_summary[(case_summary.closed_lot_count > 0) & (case_summary.unresolved_lot_count == 0)].copy()
|
audit_candidate_readouts = {
|
"primary_strict_closed_case_candidate_count": int(len(closed_cases)),
|
"primary_strict_closed_case_positive_count": int((closed_cases.account_return_closed_lots > 0).sum()) if not closed_cases.empty else 0,
|
"primary_strict_closed_case_return_sum_for_audit_only": float(closed_cases.account_return_closed_lots.sum()) if not closed_cases.empty else 0.0,
|
"closed_lot_count_for_recalc_only": int((lots.lot_status == "CLOSED_BY_AI_SELL").sum()),
|
"closed_lot_account_return_sum_for_recalc_only": float(pd.to_numeric(lots.account_return_contribution_pct, errors="coerce").fillna(0).sum()),
|
"not_final_conclusion": True,
|
}
|
summary = {
|
"schema_version": "1.0",
|
"task_id": TASK_ID,
|
"run_id": RUN_ID,
|
"design_id": DESIGN_ID,
|
"design_audit_id": DESIGN_AUDIT_ID,
|
"generated_at": now_iso(),
|
"stage": STAGE,
|
"execution_review_status": "EXECUTION_REREVIEW_PASSED_RETURN_STAT_HELD",
|
"execution_review_original_message_id": EXEC_REVIEW_MESSAGE_ID,
|
"execution_review_held_audit_id": EXEC_HELD_AUDIT_ID,
|
"execution_review_held_message_id": EXEC_HELD_MESSAGE_ID,
|
"execution_rereview_held_audit_id": EXEC_REREVIEW_HELD_AUDIT_ID,
|
"execution_rereview_held_message_id": EXEC_REREVIEW_HELD_MESSAGE_ID,
|
"execution_rereview_pass_audit_id": EXEC_REREVIEW_PASS_AUDIT_ID,
|
"execution_rereview_pass_message_id": EXEC_REREVIEW_PASS_MESSAGE_ID,
|
"issue_id": RETURN_SCOPE_ISSUE_ID,
|
"strict_baseline_return_ready_flag": False,
|
"scope": {
|
"case_count": int(len(case_index)),
|
"batch_count": int(len(batch_index)),
|
"selected_candidate_rows": int(len(selected)),
|
"market_gate_open_entry_dates": int((case_index.market_gate_status == "MKT_GATE_OPEN_PREV_DAY_UP_3000").sum()),
|
"market_gate_closed_entry_dates": int((case_index.market_gate_status == "NO_TRADE_MARKET_GATE_CLOSED").sum()),
|
"buy_case_count": int(len(case_summary)),
|
},
|
"decision_counts": {f"{k[0]}::{k[1]}": int(v) for k, v in decisions.groupby(["decision_stage", "action_status"]).size().to_dict().items()},
|
"trade_ledger": {
|
"order_counts": order_counts,
|
"lot_status_counts": lot_counts,
|
"case_summary_rows": int(len(case_summary)),
|
},
|
"image_package": {
|
"image_count": int(len(image_manifest)),
|
"role_counts": role_counts(image_manifest, "chart_role"),
|
},
|
"full_return_stat": {
|
"case_scope": "full_return_stat_case_scope.csv",
|
"lot_scope": "full_return_stat_lot_scope.csv",
|
"boundary_table": "full_return_stat_boundary_table.csv",
|
"summary": "full_return_stat_summary.json",
|
"summary_readouts": return_stat_summary,
|
},
|
"audit_candidate_readouts_not_final": audit_candidate_readouts,
|
"self_check": {
|
"overall_status": self_check["overall_status"],
|
"check_count": int(self_check["check_count"]),
|
"fail_count": int(self_check["fail_count"]),
|
"evidence": "self_check.json",
|
},
|
"boundary": (
|
"Full 2023-2026 batch execution package passed execution re-review after return-scope repairs. "
|
"RETURN_STAT_READY remains false; success, return, win-rate, drawdown, and effectiveness readouts must be cited with explicit full_return_stat_* scopes and boundaries."
|
),
|
}
|
(ROOT / "summary.json").write_text(json.dumps(summary, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
(ROOT / "summary.md").write_text(
|
"\n".join(
|
[
|
f"# {RUN_ID} summary",
|
"",
|
f"- 案例事项:`{TASK_ID}`",
|
f"- 设计:`{DESIGN_ID}`",
|
f"- 设计审核:`{DESIGN_AUDIT_ID}`",
|
f"- 阶段:`{STAGE}`",
|
"- 执行审核状态:`EXECUTION_REREVIEW_PASSED_RETURN_STAT_HELD`",
|
f"- 原执行送审消息:`{EXEC_REVIEW_MESSAGE_ID}`",
|
f"- 原 HELD 审计:`{EXEC_HELD_AUDIT_ID}`",
|
f"- 执行复审 HELD:`{EXEC_REREVIEW_HELD_AUDIT_ID}`",
|
f"- 执行复审通过:`{EXEC_REREVIEW_PASS_AUDIT_ID}`",
|
f"- 返修问题:`{RETURN_SCOPE_ISSUE_ID}`",
|
"- `RETURN_STAT_READY=false`",
|
"",
|
"## 范围",
|
"",
|
f"- entry_trade_date:{len(case_index)} 个",
|
f"- 批次:{len(batch_index)} 个",
|
f"- 选中候选:{len(selected)} 条",
|
f"- 市场闸门打开:{summary['scope']['market_gate_open_entry_dates']} 个;关闭:{summary['scope']['market_gate_closed_entry_dates']} 个",
|
"",
|
"## 执行读数(送审候选,不是最终结论)",
|
"",
|
f"- BUY:{order_counts.get('BUY', 0)};SELL:{order_counts.get('SELL', 0)}",
|
f"- lot 状态:{lot_counts}",
|
f"- 图片:{len(image_manifest)} 张",
|
f"- 自检:{self_check['check_count']} 项,失败 {self_check['fail_count']} 项",
|
"- 收益分层产物:`full_return_stat_case_scope.csv`、`full_return_stat_lot_scope.csv`、`full_return_stat_boundary_table.csv`、`full_return_stat_summary.json/md`",
|
"",
|
"## 边界",
|
"",
|
"- 当前包已通过全量执行返修复审,但 `RETURN_STAT_READY=false` 继续保留。",
|
"- 完整 2023-2026 baseline 成功率、收益率、胜率、回撤或策略有效性读数必须按 `full_return_stat_*` 分层口径和边界引用。",
|
"- 非真实 SELL、窗口末估值和数据缺口 lot 均保持边界,不强行补结论。",
|
"",
|
]
|
),
|
encoding="utf-8",
|
)
|
|
manifest = manifest_for_dir(ROOT)
|
manifest.update(
|
{
|
"manifest_stage": STAGE,
|
"overall_status": self_check["overall_status"],
|
"strict_baseline_return_ready_flag": False,
|
"notes": "manifest.json excludes itself for stable hashing. Full execution return-scope repair passed re-review; RETURN_STAT_READY remains false.",
|
}
|
)
|
(ROOT / "manifest.json").write_text(json.dumps(manifest, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
|
|
def main() -> None:
|
case_index = read_csv("case_index.csv")
|
batch_index = read_csv("full_batch_index.csv")
|
selected = read_csv("selected_candidate_ledger.csv")
|
candidate_diff = read_csv("full_candidate_pool_diff.csv")
|
decisions = read_csv("decision_log.csv")
|
sell_decisions = read_csv("sell_decision_log.csv")
|
exit_resolution = read_csv("exit_resolution_log.csv")
|
orders = read_csv("order_ledger.csv")
|
lots = read_csv("position_lot_ledger.csv")
|
account = read_csv("daily_account_ledger.csv")
|
case_summary = read_csv("case_summary.csv")
|
image_manifest = read_csv("image_manifest.csv")
|
|
case_summary = build_return_stat_artifacts(case_index, decisions, lots, case_summary)
|
write_case_boards(case_index, selected, decisions, lots, case_summary, image_manifest)
|
write_root_and_batch_boards(case_index, batch_index, selected, decisions, orders, lots, case_summary, image_manifest)
|
self_check = run_self_check(
|
case_index,
|
batch_index,
|
selected,
|
candidate_diff,
|
decisions,
|
sell_decisions,
|
exit_resolution,
|
orders,
|
lots,
|
account,
|
case_summary,
|
image_manifest,
|
)
|
write_summary_and_manifest(case_index, batch_index, selected, decisions, orders, lots, case_summary, image_manifest, self_check)
|
|
|
if __name__ == "__main__":
|
main()
|