Phase C dry-run·문서화·DB 증분 저장 및 운영 env 동기화
- 1분봉 다운로드 제외, MONITOR_PERSIST로 05/06 수집 시 coins.db INSERT - Phase C paper_fires 로그·07 모의 리포트, hybrid 시뮬 산출물·reference 문서 갱신 - .env Phase C(LIVE=0), bootstrap dotenv override=True Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
165
scripts/07_phase_c_paper_report.py
Normal file
165
scripts/07_phase_c_paper_report.py
Normal file
@@ -0,0 +1,165 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Phase C dry-run 종료 후 모의 수익률(참고) 집계.
|
||||
|
||||
- 입력: data/ops/paper_fires.jsonl (06 dry-run 발화 로그)
|
||||
- 출력: docs/05_ops/phase_c_paper_report.json + 콘솔 요약
|
||||
|
||||
주의: 실계좌 수익이 아님. 발화가 N봉 후 가격으로 계산한 forward % 합산(참고).
|
||||
hybrid 복리 PnL은 simulation_report.html 과 다릅니다.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import runpy
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
runpy.run_path(str(Path(__file__).resolve().parent / "_bootstrap.py"))
|
||||
|
||||
from config import ( # noqa: E402
|
||||
MATCH_FORWARD_BARS,
|
||||
MATCH_PRIMARY_INTERVAL,
|
||||
SYMBOL,
|
||||
TRADING_FEE_RATE,
|
||||
)
|
||||
from deepcoin.matching.label_outcomes import _forward_ret_vectorized # noqa: E402
|
||||
from deepcoin.ops.monitor import Monitor # noqa: E402
|
||||
from deepcoin.paths import PAPER_FIRES_LOG, PAPER_WEEKLY_REPORT_JSON # noqa: E402
|
||||
|
||||
_FEE_PCT = TRADING_FEE_RATE * 2 * 100
|
||||
|
||||
|
||||
def load_paper_fires(path: Path) -> pd.DataFrame:
|
||||
"""paper_fires.jsonl → DataFrame."""
|
||||
if not path.is_file():
|
||||
return pd.DataFrame()
|
||||
rows = []
|
||||
for line in path.read_text(encoding="utf-8").splitlines():
|
||||
line = line.strip()
|
||||
if line:
|
||||
rows.append(json.loads(line))
|
||||
if not rows:
|
||||
return pd.DataFrame()
|
||||
return pd.DataFrame(rows)
|
||||
|
||||
|
||||
def attach_forward_returns(fires: pd.DataFrame, close_df: pd.DataFrame) -> pd.DataFrame:
|
||||
"""
|
||||
would_trade=True 발화에 MATCH_FORWARD_BARS 기준 forward 수익률(%) 부여.
|
||||
|
||||
Args:
|
||||
fires: paper 발화.
|
||||
close_df: 3분 종가 (datetime index).
|
||||
|
||||
Returns:
|
||||
forward_ret_pct 컬럼 추가.
|
||||
"""
|
||||
if fires.empty or close_df.empty:
|
||||
fires["forward_ret_pct"] = np.nan
|
||||
return fires
|
||||
|
||||
close_df = close_df.sort_index()
|
||||
if not isinstance(close_df.index, pd.DatetimeIndex):
|
||||
close_df.index = pd.to_datetime(close_df.index)
|
||||
close_ts_ns = close_df.index.astype(np.int64).values
|
||||
close_px = close_df["Close"].astype(float).values
|
||||
|
||||
sub = fires[fires["would_trade"] == True].copy() # noqa: E712
|
||||
if sub.empty:
|
||||
fires["forward_ret_pct"] = np.nan
|
||||
return fires
|
||||
|
||||
sig = pd.to_datetime(sub["signal_dt"])
|
||||
fire_ns = sig.astype(np.int64).values
|
||||
c0 = sub["close"].astype(float).values
|
||||
side = sub["side"].astype(str).values
|
||||
ret, valid = _forward_ret_vectorized(
|
||||
fire_ns, c0, close_ts_ns, close_px, side, MATCH_FORWARD_BARS, _FEE_PCT
|
||||
)
|
||||
fires = fires.copy()
|
||||
fires["forward_ret_pct"] = np.nan
|
||||
idx = sub.index
|
||||
fires.loc[idx, "forward_ret_pct"] = np.where(valid, ret, np.nan)
|
||||
return fires
|
||||
|
||||
|
||||
def summarize(fires: pd.DataFrame) -> dict:
|
||||
"""집계 dict."""
|
||||
traded = fires[fires["would_trade"] == True] # noqa: E712
|
||||
with_ret = traded[traded["forward_ret_pct"].notna()]
|
||||
out: dict = {
|
||||
"generated_at": datetime.now().isoformat(timespec="seconds"),
|
||||
"symbol": SYMBOL,
|
||||
"forward_bars": MATCH_FORWARD_BARS,
|
||||
"fee_round_trip_pct": _FEE_PCT,
|
||||
"total_signals": int(len(fires)),
|
||||
"would_trade_count": int(len(traded)),
|
||||
"skipped_count": int(len(fires) - len(traded)),
|
||||
"labeled_count": int(len(with_ret)),
|
||||
"note": (
|
||||
"모의 forward 수익률. 실계좌·hybrid 복리 PnL 아님. "
|
||||
"매수·매도 leg 미결합 단순 합산."
|
||||
),
|
||||
}
|
||||
if not with_ret.empty:
|
||||
out["mean_forward_ret_pct"] = round(float(with_ret["forward_ret_pct"].mean()), 4)
|
||||
out["sum_forward_ret_pct"] = round(float(with_ret["forward_ret_pct"].sum()), 4)
|
||||
by_side = (
|
||||
with_ret.groupby("side")["forward_ret_pct"]
|
||||
.agg(["count", "mean", "sum"])
|
||||
.round(4)
|
||||
)
|
||||
out["by_side"] = {k: v.to_dict() for k, v in by_side.iterrows()}
|
||||
by_rule = (
|
||||
traded.groupby("rule_id")
|
||||
.size()
|
||||
.to_dict()
|
||||
if not traded.empty
|
||||
else {}
|
||||
)
|
||||
out["fires_by_rule"] = by_rule
|
||||
return out
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""paper_fires 로드 → forward % → 리포트 저장."""
|
||||
fires = load_paper_fires(PAPER_FIRES_LOG)
|
||||
if fires.empty:
|
||||
print(f"[07] 발화 로그 없음: {PAPER_FIRES_LOG}")
|
||||
print(" Phase C 기간 06_execute_live.py (LIVE=0) 상시 실행 후 재시도")
|
||||
return
|
||||
|
||||
mon = Monitor(cooldown_file=None)
|
||||
df = mon.read_candles_from_db(SYMBOL, MATCH_PRIMARY_INTERVAL, max_rows=50000)
|
||||
if df.empty:
|
||||
df = mon.get_coin_some_data(SYMBOL, MATCH_PRIMARY_INTERVAL)
|
||||
if not isinstance(df.index, pd.DatetimeIndex):
|
||||
df = df.set_index(pd.to_datetime(df["datetime"]))
|
||||
|
||||
fires = attach_forward_returns(fires, df)
|
||||
report = summarize(fires)
|
||||
PAPER_WEEKLY_REPORT_JSON.parent.mkdir(parents=True, exist_ok=True)
|
||||
PAPER_WEEKLY_REPORT_JSON.write_text(
|
||||
json.dumps(report, ensure_ascii=False, indent=2),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
print(f"[07] 저장: {PAPER_WEEKLY_REPORT_JSON}")
|
||||
print(f" 기간 로그: {fires['ts'].min()} ~ {fires['ts'].max()}")
|
||||
print(f" 발화 {report['total_signals']} · 체결가정(would_trade) {report['would_trade_count']}")
|
||||
if "sum_forward_ret_pct" in report:
|
||||
print(
|
||||
f" 모의 forward 합산: {report['sum_forward_ret_pct']}% "
|
||||
f"(평균 {report['mean_forward_ret_pct']}%, "
|
||||
f"{MATCH_FORWARD_BARS}봉 후, 참고용)"
|
||||
)
|
||||
else:
|
||||
print(" forward 라벨 가능 건 없음 (봉 데이터 부족 또는 발화 없음)")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user