- tick_trader.py를 Controller로 축소, 로직을 3개 모듈로 분리: - core/signal.py: 시그널 감지, 지표 계산 (calc_vr, calc_atr, detect_signal) - core/order.py: Upbit 주문 실행 (매수/매도/취소/조회) - core/position_manager.py: 포지션 관리, DB sync, 복구, 청산 조건 - type hints, Google docstring, 구체적 예외 타입 적용 - 50줄 초과 함수 분리 (process_signal, restore_positions) - 미사용 파일 58개 archive/ 폴더로 이동 - README.md 추가 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
144 lines
4.5 KiB
Python
144 lines
4.5 KiB
Python
"""WF 윈도우 크기별 비교 시뮬레이션.
|
|
|
|
실제 42건 거래를 시간순으로 재생하며
|
|
WF_WINDOW 크기(2, 3, 5)에 따라 차단/허용 여부를 시뮬레이션.
|
|
차단된 거래 → P&L 0 (진입 안 함)
|
|
허용된 거래 → 실제 P&L 반영
|
|
"""
|
|
|
|
import os as _os, sys as _sys
|
|
_sys.path.insert(0, _os.path.dirname(_os.path.dirname(_os.path.abspath(__file__))))
|
|
|
|
import os
|
|
from dotenv import load_dotenv
|
|
load_dotenv()
|
|
import oracledb
|
|
|
|
def get_conn():
|
|
return oracledb.connect(
|
|
user=os.getenv('ORACLE_USER'), password=os.getenv('ORACLE_PASSWORD'),
|
|
dsn=os.getenv('ORACLE_DSN'), config_dir=os.getenv('ORACLE_WALLET'))
|
|
|
|
def simulate_wf(trades, window, min_wr):
|
|
"""
|
|
trades: [(ticker, is_win, pnl_pct, krw_profit, traded_at), ...] 시간순
|
|
window: WF 윈도우 크기
|
|
min_wr: 최소 승률 임계값
|
|
|
|
Returns: 허용된 거래 목록, 차단된 거래 목록, 요약
|
|
"""
|
|
history = {} # ticker → [bool, ...]
|
|
allowed = []
|
|
blocked = []
|
|
|
|
for t in trades:
|
|
ticker, is_win, pnl, profit, dt = t
|
|
|
|
hist = history.get(ticker, [])
|
|
|
|
# WF 차단 여부 판단
|
|
is_blocked = False
|
|
if len(hist) >= window:
|
|
recent_wr = sum(hist[-window:]) / window
|
|
if recent_wr < min_wr:
|
|
is_blocked = True
|
|
|
|
if is_blocked:
|
|
blocked.append(t)
|
|
else:
|
|
allowed.append(t)
|
|
# 실제 결과를 이력에 추가
|
|
hist = hist + [bool(is_win)]
|
|
if len(hist) > window * 2:
|
|
hist = hist[-window:]
|
|
history[ticker] = hist
|
|
|
|
total = len(allowed)
|
|
wins = sum(1 for t in allowed if t[1])
|
|
pnl = sum(t[2] for t in allowed)
|
|
profit = sum(t[3] for t in allowed)
|
|
|
|
return allowed, blocked, {
|
|
'total': total, 'wins': wins,
|
|
'wr': wins/total*100 if total else 0,
|
|
'pnl': pnl, 'profit': profit,
|
|
'blocked_count': len(blocked),
|
|
}
|
|
|
|
|
|
def main():
|
|
conn = get_conn()
|
|
cur = conn.cursor()
|
|
|
|
# 전체 거래 시간순 로드
|
|
cur.execute("""
|
|
SELECT ticker, is_win, pnl_pct, NVL(krw_profit,0), traded_at
|
|
FROM trade_results
|
|
ORDER BY traded_at
|
|
""")
|
|
trades = cur.fetchall()
|
|
print(f"전체 거래: {len(trades)}건\n")
|
|
|
|
configs = [
|
|
(2, 0.5, "WF=2 (2연패→차단, 1승→해제)"),
|
|
(3, 0.34, "WF=3 (3건중 1승 이상 필요)"),
|
|
(5, 0.40, "WF=5 (5건중 2승 이상, 현행)"),
|
|
]
|
|
|
|
results = []
|
|
for window, min_wr, label in configs:
|
|
allowed, blocked, stats = simulate_wf(trades, window, min_wr)
|
|
stats['label'] = label
|
|
stats['window'] = window
|
|
results.append((label, allowed, blocked, stats))
|
|
|
|
print(f"[{label}]")
|
|
print(f" 허용: {stats['total']}건 | 승률={stats['wr']:.1f}% | "
|
|
f"누적수익={stats['profit']:+,.0f}원 | 차단={stats['blocked_count']}건")
|
|
|
|
# 차단된 거래 상세
|
|
if blocked:
|
|
print(f" 차단된 거래:")
|
|
blocked_by_ticker = {}
|
|
for t in blocked:
|
|
blocked_by_ticker.setdefault(t[0], []).append(t)
|
|
for ticker, ts in blocked_by_ticker.items():
|
|
pnls = [f"{t[2]:+.1f}%" for t in ts]
|
|
print(f" {ticker}: {len(ts)}건 {pnls}")
|
|
print()
|
|
|
|
# 상세 비교표: 거래별 허용/차단 여부
|
|
print("=" * 70)
|
|
print(f"{'날짜':>12} {'종목':>12} {'결과':>6} {'PnL':>8} │ "
|
|
f"{'WF=2':>6} {'WF=3':>6} {'WF=5':>6}")
|
|
print("─" * 70)
|
|
|
|
# 각 설정별 허용 set 구성 (traded_at + ticker로 식별)
|
|
allowed_sets = []
|
|
for _, allowed, _, _ in results:
|
|
allowed_sets.append(set((t[0], t[4]) for t in allowed))
|
|
|
|
for t in trades:
|
|
ticker, is_win, pnl, profit, dt = t
|
|
win_mark = "✅" if is_win else "❌"
|
|
cols = []
|
|
for aset in allowed_sets:
|
|
if (ticker, dt) in aset:
|
|
cols.append("허용")
|
|
else:
|
|
cols.append("🔴차단")
|
|
print(f"{dt.strftime('%m-%d %H:%M'):>12} {ticker:>12} {win_mark:>4} {pnl:>+7.1f}% │ "
|
|
f"{cols[0]:>6} {cols[1]:>6} {cols[2]:>6}")
|
|
|
|
# 최종 요약
|
|
print("\n" + "=" * 70)
|
|
print(f"{'설정':<35} {'거래':>5} {'승률':>7} {'KRW수익':>12} {'차단':>5}")
|
|
print("─" * 70)
|
|
for label, _, _, s in results:
|
|
print(f"{label:<35} {s['total']:>5}건 {s['wr']:>6.1f}% {s['profit']:>+12,.0f}원 {s['blocked_count']:>4}건 차단")
|
|
|
|
conn.close()
|
|
|
|
if __name__ == "__main__":
|
|
main()
|