- Move all backtest/simulation scripts to tests/ - Add sys.path.insert to each script for correct import resolution - Move pkl cache files to data/ (git-ignored) - Move log files to logs/ (git-ignored) - Update main.py: trading.log path → logs/trading.log - Add ecosystem.config.js: pm2 log paths → logs/pm2*.log - Update .gitignore: ignore data/ and logs/ instead of *.pkl/*.log - core/fng.py: increase cache TTL 3600→86400s (API updates daily at KST 09:00) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
120 lines
4.4 KiB
Python
120 lines
4.4 KiB
Python
"""종목 수 확장 시뮬레이션 - 거래량 상위 N개 종목별 vol-lead 전략 비교."""
|
||
|
||
import os
|
||
import pickle
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
from dotenv import load_dotenv
|
||
load_dotenv()
|
||
|
||
import pandas as pd
|
||
import pyupbit
|
||
|
||
# vol_lead_sim.py의 공통 파라미터/함수 재사용
|
||
sys.path.insert(0, str(Path(__file__).parent))
|
||
from vol_lead_sim import (
|
||
STOP_LOSS_PCT, TIME_STOP_HOURS, TIME_STOP_MIN_PCT,
|
||
FEE, LOCAL_VOL_HOURS, VOL_MULT, PRICE_QUIET_PCT, SIGNAL_TIMEOUT_H,
|
||
FROM_DATE, simulate_pos, run_trend, run_vol_lead_thresh,
|
||
)
|
||
|
||
CACHE_FILE = Path("vol_lead_cache_30.pkl")
|
||
TOP30_FILE = Path("top30_tickers.pkl")
|
||
DAYS = 46.0
|
||
|
||
|
||
def load_data() -> dict:
|
||
return pickle.load(open(CACHE_FILE, "rb"))
|
||
|
||
|
||
def run_subset(data: dict, tickers: list, thresh: float) -> dict:
|
||
agg = {"total": 0, "wins": 0, "pnl": 0.0, "per_ticker": []}
|
||
for t in tickers:
|
||
if t not in data:
|
||
continue
|
||
trades = run_vol_lead_thresh(data[t], thresh)
|
||
n = len(trades)
|
||
w = sum(1 for x in trades if x[0])
|
||
p = sum(x[1] for x in trades)
|
||
agg["total"] += n
|
||
agg["wins"] += w
|
||
agg["pnl"] += p
|
||
agg["per_ticker"].append((t, n, w, p))
|
||
agg["wr"] = agg["wins"] / agg["total"] * 100 if agg["total"] else 0
|
||
return agg
|
||
|
||
|
||
def main() -> None:
|
||
data = load_data()
|
||
top30 = pickle.load(open(TOP30_FILE, "rb"))
|
||
|
||
# 데이터 충분한 종목만 (400봉 이상 = 16일 이상)
|
||
valid = [t for t in top30 if t in data and len(data[t]) >= 400]
|
||
n_max = len(valid)
|
||
print(f"유효 종목: {n_max}개")
|
||
print(f"기간: 46일 (2026-01-15 ~ 2026-03-02)\n")
|
||
|
||
# ── A 현행 기준선 (9종목) ─────────────────────────
|
||
orig9 = ["KRW-DKA","KRW-LAYER","KRW-SIGN","KRW-SOL","KRW-ETH",
|
||
"KRW-XRP","KRW-HOLO","KRW-OM","KRW-ORBS"]
|
||
orig9_valid = [t for t in orig9 if t in data]
|
||
a_agg = {"total": 0, "wins": 0, "pnl": 0.0}
|
||
for t in orig9_valid:
|
||
trades = run_trend(data[t])
|
||
a_agg["total"] += len(trades)
|
||
a_agg["wins"] += sum(1 for x in trades if x[0])
|
||
a_agg["pnl"] += sum(x[1] for x in trades)
|
||
a_wr = a_agg["wins"] / a_agg["total"] * 100 if a_agg["total"] else 0
|
||
print(f"[기준: A 현행 9종목] {a_agg['total']}건 | 승률={a_wr:.0f}% | 누적={a_agg['pnl']:+.2f}%\n")
|
||
|
||
# ── 종목수별 비교 (임계값 4.8% 고정) ────────────────
|
||
THRESH = 4.8
|
||
subset_ns = [9, 15, 20, n_max]
|
||
|
||
print(f"임계값 +{THRESH}% | 종목 수 확장 효과")
|
||
print(f"{'종목수':>6} │ {'총거래':>6} {'일평균':>7} {'월환산':>7} │ {'승률':>5} {'누적PnL':>10}")
|
||
print("─" * 56)
|
||
for n in subset_ns:
|
||
s = run_subset(data, valid[:n], THRESH)
|
||
pdm = s["total"] / DAYS
|
||
pmm = pdm * 30
|
||
marker = " ← 현재설정" if n == 9 else ""
|
||
print(f"{n:>5}종목 │ {s['total']:>6}건 {pdm:>6.2f}회/일 {pmm:>6.1f}회/월 │ "
|
||
f"{s['wr']:>4.0f}% {s['pnl']:>+9.2f}%{marker}")
|
||
|
||
# ── 임계값 × 종목수 매트릭스 ─────────────────────
|
||
thresholds = [3.6, 4.0, 4.4, 4.8]
|
||
col_ns = [9, 15, 20, n_max]
|
||
|
||
print(f"\n임계값 × 종목수 매트릭스 (건수 / 승률 / 누적PnL)")
|
||
col_w = 20
|
||
header = f"{'임계값':>6} │"
|
||
for n in col_ns:
|
||
header += f" {f'{n}종목':^{col_w}}"
|
||
print(header)
|
||
print("─" * (10 + col_w * len(col_ns)))
|
||
for thresh in thresholds:
|
||
row = f"+{thresh:.1f}% │"
|
||
for n in col_ns:
|
||
s = run_subset(data, valid[:n], thresh)
|
||
wr = s["wins"] / s["total"] * 100 if s["total"] else 0
|
||
cell = f"{s['total']}건 {wr:.0f}% {s['pnl']:+.1f}%"
|
||
row += f" {cell:<{col_w}}"
|
||
print(row)
|
||
|
||
# ── 전체 종목별 기여도 (4.8%) ────────────────────
|
||
print(f"\n종목별 기여도 ({n_max}종목, +4.8%)")
|
||
print(f"{'종목':<16} {'거래':>5} {'승률':>6} {'누적PnL':>10} {'평균PnL/거래':>12}")
|
||
print("─" * 55)
|
||
s = run_subset(data, valid, THRESH)
|
||
s["per_ticker"].sort(key=lambda x: x[3], reverse=True)
|
||
for t, n, w, p in s["per_ticker"]:
|
||
wr = w / n * 100 if n else 0
|
||
avg = p / n if n else 0
|
||
print(f"{t:<16} {n:>5}건 {wr:>5.0f}% {p:>+9.2f}% {avg:>+10.2f}%/건")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|