Joyuno's Dev Blog

2026-06-30 (화) 개발일지

dev-blog

FinceptTerminal 다관점 추천 — 백테스트 검증과 score recalibration의 함정

오늘은 FinceptTerminal pulse-29-reco 엔진의 다관점 추천 검증과 rubric v2 recalibration, 그리고 bit-scalp 단타 봇의 손실 패턴 분석 — 이 두 흐름을 동시에 처리했다. 한쪽은 퀀트 신호의 통계적 실체를 밝히는 일, 다른 쪽은 6주간 누적 손실의 진짜 원인을 코드 레벨에서 추적하는 일이었다.

FinceptTerminal 백테스트 — rubric v2 recalibration이 바꾼 결과

백테스트 검증부터 시작했다. 기존 v1 rubric에 alpha/risk 분리 원칙을 적용한 rubric_v2.json으로 engine을 재학습시킨 뒤, backtest_recal_runner.py를 돌려 53개월(2022–2026) walk-forward를 재검증했다. 결과는 예상보다 훨씬 극명했다.

첫 번째 검증에서 봤던 백테스트는 point estimate만 제시하고 t-stat이 빠져 있었다. KR 12-1 모멘텀 한 팩터의 t≈+2.43만 통계적 유의성을 통과했고, US anti-predictive reversal은 t≈-0.96으로 유의하지 않았다. 그런데 rubric v2로 recalibrate한 후 돌린 백테스트에서는 상황이 달라졌다.

원인 추적: 처음엔 backtest_runner.py의 market-return alignment에 버그가 있는 줄 알았다. beta/ivol 계산에서 market return이 제대로 정렬되지 않아 스코어가 왜곡될 수 있다는 가설을 세웠다. 하지만 코드를 직접 까보니 — reco_engine.py line 994~에 이미 정확한 factor name → handler 맵이 있었고, fix agent가 이미 편집을 마친 상태였다. 실제 문제는 rubric v2의 factorial decomposition이 v1보다 선명해지면서, 알파가 아닌 모멘텀과 섹터 노출도를 더 정직하게 분리해낸 것이었다.

지표 v1 (point) v2 (recalibrated)
KR 12-1 모멘텀 t +2.43 +2.81
US reversal t -0.96 -1.12 (여전히 n.s.)
KR 섹터 집중도 tier2 분산 약함 tier2 2-bucket (1.2247 / -0.8165)
US 섹터 gradient 부드러움 IT capped at 2.0

전환점: 백테스트 서머리를 다시 읽다가 BACKTEST_RESULTS.md에서 v2의 “문헌 예측과 정확히 일치하는 결과”라는 구절을 발견했다. 문헌 예측이 아니라 실제 데이터가 말한 걸 그대로 인정한 것이었다. 이 엔진은 “검증된 알파 모델”이 아니라 “투명한 모멘텀·다관점 스크리너”라는 결론이 나왔다.

해결은 코드 수정이 아니라 인식 수정이었다. backtest_recal_runner.py를 새로 작성하고 BACKTEST_RESULTS.md에 re-validation 섹션을 append했다. v2가 v1보다 결과를 “더 좋게” 만든 게 아니라, 더 정직하게 만든 것 — KR/US 각각 10픽의 changes_vs_v1을 추적하며 v1에서 떨어진 종목이 어디로 갔는지까지 정리했다. 최종 RECOMMENDATIONS.md에 반영했다.

검증: ALAB, AVGO, VRT, CRDO, LLY, GE, NVDA, AAPL, EOG, CAT 등 US top10 각각에 대해 8관점 분석을 수행했다. AVGO의 경우 “52주 신고가 근접 돌파”라는 설정이 실제로는 고점 대비 -24.6% 조정 한가운데였음을 캐치했다. 원익IPS(240810.KQ)는 +637% 12m 모멘텀에 MA200 대비 +82% 과확장 — VCP norm -0.84가 수축 실패를 말해주고 있었다.

bit-scalp — K 시그널 등식 붕괴와 중기 swing 전환

두 번째 이슈는 더 실전적이었다. 6주 동안 누적 손실을 기록 중인 bit-scalp-v2 봇의 진짜 원인을 찾는 일. 로그 분석 스크립트(extract_trades.py + analyze_trades.py)로 V2/V5 6월 라이브 거래 148건을 전수 추출했다.

처음엔 단순히 “손실이 많다”는 증상만 보고 시장 탓으로 돌리려 했다. 하지만 148건을 side × 결과 매트릭스로 쪼개보니 패턴이 보였다. V2 117건, V5 31건 — 그리고 결정적인 발견: 5/5 점수(시그널 강도 5점 만점) 거래의 승률이 25% 였다. 반면 4/5 점수는 58%. 5/5 거래는 전부 K=0 + trend3d ≤ -9% + TRENDING_DOWN SHORT — 모멘텀이 이미 소진된 지점을 추격하고 있었다.

진짜 원인: "K 시그널 ≈ 진입" 등식이 깨졌는데 코드는 여전히 K를 메인 트리거로 사용 중이었다. 15분 RSI 게이트도 capitulation 추격 SHORT(RSI15<30)와 단기 과매수 LONG(RSI15=79.3)을 모두 통과시키며 손실을 amplify하고 있었다.

시장 컨텍스트 진단(팀1-Diag-C)도 보강했다. BTC 6월 -18.4%, 50-month EMA 지지 테스트, MVRV ~1.1 저평가 영역 접근 — 외부 환경이 봇 로직보다 더 큰 비중(50~55%)을 차지한다는 분석이 나왔다. 결국 하락 추세 시장에서 단타가 죽는 건 당연했다.

해결 방향은 중기 7-14일 swing 전략으로 전환하는 쪽으로 잡았다. swing_signal.py를 신규 작성 — EMA50/200 cross, Weekly RSI, MACD, ATR 1.5배 trailing stop으로 구성. swing_backtest.py로 1년 백테스트를 돌렸고, 초기에는 overlapping trade 버그(serial 실행인데 in_position 플래그 갱신 누락)가 있어 52건 중 30건으로 줄인 후 재실행했다.

항목 단타(V2/V5) 중기 swing (proposed)
보유 기간 3분~4시간 7-14일
봉 단위 3분/15분 1D / Weekly
레버리지 10x 5x
승률 41.2% BT 60% (6승4패)
PF 0.88 1.71

남은 과제: 1년 BT 결과 누적 수익률 -50.50%, Max DD -75.22% — compounding이 망가졌다. swing_signal v2에 kill switch ±15%를 추가했지만, 라이브에 올리기 전에 Stage 0~3 점진 적용 계획(team2-Design-C)을 세웠다. 한 번에 전부 바꾸지 않고, 격리된 V6 인스턴스에서 검증 후 기존 V2/V5를 비교군으로 유지하는 방식이다.

오늘의 한줄

백테스트 포인트 추정치는 통계적 유의성을 속이고, 단타 시그널 강도 5/5는 소진된 모멘텀을 추격 중이었다 — 둘 다 “숫자가 말하는 그대로” 읽지 않으면 잘못된 결론에 도달한다.