You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
fithia2/docs/tgtc_v1_development_report.md

25 KiB

TGTC V1 개발 리포트: 설계, 테스트, 최종 결론

작성일: 2026-05-06
전략 분류: Intraday — Top Gainer Tape Continuation
최종 상태: 아카이브 (OOS-stable alpha 없음)


1. 배경 및 동기

왜 TGTC를 만들었나

ORB V49.91은 "ORB 품질 좋은 날만 골라 손실을 줄이는" 보수적 sleeve로 발전했다. 하지만 시장 레짐이 애매한데 개별 Top Gainer가 폭발하는 날을 구조적으로 잡지 못하는 문제가 있었다.

no_thrust_liquid_impulse 전략은 갭 0~4%, 첫 5분 +3% 같은 좁은 조건에서만 작동했다. V49.91을 더 느슨하게 뜯기보다 별도 sleeve로 TGTC를 신설하기로 했다.

핵심 가설

  • Yahoo day_gainers 리더보드를 09:30~10:00 동안 수집하면 "진짜 모멘텀 종목"을 실시간으로 파악할 수 있다
  • 30초 간격 스냅샷에서 rank_persistence(랭킹 유지력)와 rank_velocity(순위 상승 속도)를 계산하면 가짜 갭이 아닌 실제 테이프 모멘텀을 구분할 수 있다
  • VWAP pullback reclaim 패턴에서 진입하면 리스크-리워드 비율이 유리하다

V1 범위 확정

  • 백테스트: IntradayCache (5분봉, VWAP 사전계산) + 합성 gainer 재구성
  • 라이브: Yahoo API 20초 간격 → 10:00 후보 확정 → dry-run 기록 (Alpaca 주문 없음)
  • 진입 패턴: VWAP Pullback Reclaim 단일 (HOD Reclaim, Opening 30m Breakout은 V2)

2. 아키텍처 설계

디렉토리 구조

apps/tgtc_trader/
  yahoo_gainers.py      # Yahoo screener HTTP fetcher
  snapshot_store.py     # 일자별 Parquet 스냅샷 영속화
  engine.py             # TGTCEngine (phase 메서드)
  state.py              # TGTCStateManager (SQLite)
  models.py             # Pydantic 행 모델
  daemon.py             # 09:29:30~16:00 ET 데몬
  screener.py           # 후보 hard filter

libs/tgtc/
  domain.py             # TGTCStrategyParams, TGTCConfig
  gainers_reconstruct.py # 합성 gainer 랭킹 재구성
  signals.py            # rank_persistence, vwap_pullback_reclaim 등
  simulator.py          # run_tgtc_simulation(date, config)

apps/web/
  routers/tgtc_trading.py
  tgtc_service.py

configs/intraday/strategies/
  tgtc_v1_vwap_reclaim.yaml

핵심 컴포넌트

Yahoo Gainers Fetcher

URL = "https://query1.finance.yahoo.com/v1/finance/screener/predefined/saved"
PARAMS = {"scrIds": "day_gainers", "count": 100, ...}

응답에서 추출: symbol, price, pct_change, volume, market_cap, exchange. 3회 실패 시 tick skip.

Engine Phase 구조

Phase ET 시각 동작
pre_screen 09:20 전일 enrichment → ATR/dollar_vol 필터
collect_snapshot 09:29:30 ~ 10:00 (20s 간격) Yahoo fetch + Parquet/SQLite 저장
finalize_candidates 10:00 rank_persistence/velocity 계산, hard filter → tgtc_candidates
entry_check 10:00 ~ 15:30 (5분 간격) VWAP pullback reclaim 시그널 → dry-run 기록
stop_check 진입 후 매 5분 trend_health_score, VWAP break, partial 1R
eod_exit 15:55 모든 가상 포지션 강제 청산
post_close 16:00 일별 집계, daily P/L

V1 Hard Filter

min_price: 5.0
min_market_cap: 2_000_000_000    # 합성 BT에서는 dollar_vol surrogate 사용
min_30m_dollar_volume: 10_000_000
min_day_change_at_10: 0.04
max_day_change_at_10: 0.35
must_be_above_vwap_at_10: true
max_pullback_from_hod: 0.35

Score 함수 (rule-based, no ML)

score = 0.30 * rank_persistence
      + 0.20 * rank_velocity
      + 0.25 * price_structure
      + 0.15 * volume_quality
      + 0.10 * relative_strength_vs_qqq

VWAP Pullback Reclaim 신호 (5분봉)

조건 (5분봉 기준):
  1) 첫 3봉 누적 수익률 > 4% AND 강한 상승 구조
  2) 이후 VWAP 근처(±0.3%)로 눌림 (pullback 봉)
  3) pullback 봉 거래량 < 상승 봉 평균 거래량 × 0.7
  4) higher low 형성
  5) pullback high 재돌파 → 진입

stop = max(swing_low_during_pullback, entry - 1.5 × ATR_intraday)

Trend Health Score (보유/청산 판단)

+1 price > VWAP
+1 9EMA > 20EMA
+1 last swing_low > prev swing_low
+1 pullback volume < up volume
+1 5m close location > 0.5

score >= 4 → 보유
score <= 1 → 전량 청산
VWAP 2봉 연속 이탈 → 무조건 청산

3. 백테스트 인프라

합성 Gainer 재구성 (libs/tgtc/gainers_reconstruct.py)

Yahoo Historical 데이터는 제공되지 않으므로 IntradayCache 5분봉으로 합성:

09:30 / 09:35 / ... / 09:55 = 6 tick
각 시점에서 pct_change_from_prev_close 계산
count=100 cut → top 100 synthetic snapshot

합성 rank_persistence / rank_velocity 계산:

  • rank_persistence = 등장 tick 수 / 전체 tick 수
  • rank_velocity = 순위 개선 속도 (후기 tick 평균 rank - 초기 tick 평균 rank)

Lookahead 가드

시각 t의 결정은 t까지의 스냅샷 + t에서 close된 5분봉만 사용. as_of_bar_idx 인자로 미래 데이터 누설 차단.

def scan_vwap_pullback_entries(candidates, bars_5m, cfg, as_of_bar_idx):
    # bars_5m[:as_of_bar_idx+1]만 사용

시뮬레이터 (libs/tgtc/simulator.py)

@dataclass
class TGTCTrade:
    symbol: str
    date: str
    entry_price: float
    stop_price: float
    exit_price: float
    shares: int
    pnl: float
    exit_reason: str
    side: str = "long"
    score: float = 0.0
    rank_persistence: float = 0.0
    rank_velocity: float = 0.0
    price_structure: float = 0.0
    volume_quality: float = 0.0
    relative_strength: float = 0.0
    pct_change_at_10: float = 0.0
    tp_collision: bool = False

4. 기본 성능 측정 (V1 원본)

테스트 기간 분할

구간 기간 목적
IS (In-Sample) 2025-11-01 ~ 2026-04-30 파라미터 개발
OOS_A 2024-05-01 ~ 2024-10-31 OOS 검증
OOS_B 2024-11-01 ~ 2025-04-30 OOS 검증
OOS_C 2025-05-01 ~ 2025-10-31 OOS 검증

V1 원본 baseline (VWAP reclaim, 보수적 stop)

구간 수익률 Win Rate Sharpe MDD
IS -17.4% ~35% -3.44 ~15%
OOS_A +5.41%
OOS_B -12.39%
OOS_C -12.96%
4-win avg -9.34%

참고: no_entry_after_et=12:00 컷오프 포함 시 IS에서 -17.4% → 약 +7.74%로 개선됨. 이는 컷오프 자체가 +25pp의 효과가 있음을 의미. OOS에서도 구현됐는지 별도 확인 필요.


5. 개선 실험 시리즈

5.1 Quick Take-Profit 시리즈 (A1 ~ A7)

가설: VWAP pullback reclaim 후 1~2% 빠른 TP로 WR을 높이면 전체 P&L이 개선되지 않을까?

버전 설명 IS 수익률 IS Sharpe
A1 TP 1.0%, partial 유지 ~-10% 개선 없음
A2 TP 1.5%, partial 유지 ~-8% 소폭 개선
A3 TP 2.0%, partial 유지 ~-5% 소폭 개선
A4 TP 2.0%, partial 제거 +8~10% 개선
A5 TP 2.0%, force_exit 90min 혼재 혼재
A6 TP 2.5%, partial 제거
A7 TP 2.0%, partial 제거, conservative +13.73% +1.14

A7 최종 설정:

take_profit_pct: 0.020
disable_partial_at_1r: true
tp_requires_no_stop_touch: false

A7 IS 세부 성능:

  • trades=207, WR=56.5%, Sharpe=+1.14, MDD=4.57%, total_return=+13.73%
  • exits: quick_tp=103, stop_loss=90, eod_exit=14

충돌(collision) 분석:

  • IS에서 TP와 stop이 같은 5분봉에서 동시에 트리거된 경우: 3.4%
  • 이 정도 collision 비율은 IS 결과를 크게 왜곡하지 않는다고 판단

A7 OOS 검증 결과

구간 수익률 WR Sharpe MDD
IS +13.73% 56.5% +1.14 4.57%
OOS_A +5.41%
OOS_B -12.39%
OOS_C -12.96%
4-win avg +1.20%

결론: IS 개선이 OOS로 전이되지 않음. OOS_B, OOS_C에서 동일하게 큰 손실.


5.2 시간 제한 시리즈 (B1 ~ B5)

가설: 특정 시간대에만 진입을 제한하면 노이즈를 줄일 수 있지 않을까?

버전 설명 IS 결과
B1 no_entry_after_et=11:00 트레이드 수 감소, P&L 혼재
B2 no_entry_after_et=12:00 기존 +7.74%와 유사
B3 force_exit_after_minutes=90 비슷
B4 no_entry_before_et=11:00 (mid-day only) IS 상승 → D-family 위조 문제 발견
B5 no_entry_before_et=12:00 동일 문제

Look-ahead 버그 발견 (D-family와 동일):

no_entry_before_et 지연 진입 시, detect_vwap_pullback_reclaim 이 이미 지나간 과거 bar의 pb_high를 entry price로 반환하는 look-ahead 버그 확인:

# 버그: as_of_bar_idx가 미래 봉이어도 과거 setup을 재사용
if reclaim_high > pb_high:
    return setup  # ← 과거 시점의 entry_price 사용

# 수정: reclaim이 현재(as_of) 봉에서 완료된 경우만 허용
if reclaim_idx != len(local) - 1:
    continue  # 현재 봉 이전에 완성된 setup 무시

이 버그로 B4/D4/D5 등의 결과가 심각하게 부풀려짐 (+95% ~ +130%).


5.3 Multi-Level Partial TP 시리즈 (C1 ~ C4)

가설: 여러 단계로 나눠 파는 게 단일 TP보다 나을 수 있다.

버전 설명 IS 결과
C1 1R에 33% → TP 2%에 33% → 나머지 trail A7 대비 개선 없음
C2 1R에 25% → 2% TP에 50% → EOD 혼재
C3 TP 1.5%에 50% → trail나머지 복잡성 증가, 개선 없음
C4 다단계 + time-stop 조합 혼재

결론: 다단계 부분 청산이 단일 TP(A7)보다 통계적으로 유의미한 개선을 주지 않음. A7이 심플하고 최선.


5.4 Entry Timing 시리즈 (D1 ~ D5)

가설: 장 개장 직후보다 모멘텀이 지속되는 "mid-day" 진입이 더 나을 것.

버전 설명 IS 결과 비고
D1 no_entry_before_et=10:30 겉으로 +15% 이상 Look-ahead 버그
D2 no_entry_before_et=11:00 겉으로 +40% 이상 Look-ahead 버그
D3 no_entry_before_et=11:30 겉으로 +95%+ Look-ahead 버그
D4 no_entry_before_et=12:00 겉으로 +130%+ Look-ahead 버그
D5 버그 수정 후 D3 재실행 ~-12% 기본 성능보다 나쁨

버그 수정 전후 비교 (D3 기준):

  • 버그 있음: IS +130%, OOS_A +85% (허위)
  • 버그 수정 후: IS -12%, OOS_A ~-8% (실제)

5.5 Stop Width 실험 (V1 ~ V3)

가설: stop_atr_multiple을 키우면 더 넓은 stop으로 premature stop-out을 방지할 수 있다.

버전 stop_atr_multiple IS Sharpe IS 수익률
A7_cons (기준) 1.5 +1.14 +13.73%
V1 1.0 유사
V2 2.0 동일
V3 2.5 동일

원인 발견: signals.py 에서 stop 계산 로직:

stop_price = max(swing_low, entry_price - params.stop_atr_multiple * atr_intraday)

max() 로 인해 swing_low > entry - ATR*multiplier 인 경우 (모멘텀 종목에서 자주 발생), ATR multiplier를 키워도 stop이 변하지 않는다. 즉, V2/V3에서 multiplier 변경이 실제로 stop width에 영향을 주지 않았다.


6. Score 세분화 분석 (Segmentation)

가설: 높은 score 종목만 골라 trade하면 성능이 올라갈 것.

전략: Score Quartile 분석

IS + OOS 3개 구간에서 score Q1(상위 25%) vs Q4(하위 25%) 수익률 비교:

구간 Q1(최고) Q2 Q3 Q4(최저)
IS 최고 최저
OOS_A 최저 최고
OOS_B 최저
OOS_C 최저

발견: Score 함수가 OOS에서 일관된 예측력을 갖지 못함. IS 기준으로 Q1이 최고였지만 OOS_A에서는 Q1이 최저.

pct_change_at_10 세분화

구간 Q1(낮은 변동) Q2 Q3 Q4(높은 변동)
IS 양호 최악
OOS_A 양호 최악
OOS_B 양호 최악
OOS_C 양호 최악

pct_change Q4 일관되게 모든 구간에서 최악: 10:00 기준 너무 많이 오른 종목은 전 구간에서 손실.

Q4 제거 효과:

  • 4-win avg: -1.55% → +0.88% (개선)
  • 하지만 여전히 OOS 3개 중 2개 음수

결론: Q4 필터링은 도움이 되지만, 근본적인 OOS instability를 해결하지 못함.


7. 새로운 진입 신호 (HOD Breakout)

가설: VWAP reclaim 대신 장 중 신고가(HOD) 돌파를 진입 신호로 사용.

설계

def detect_hod_breakout(bars, start_bar_idx, as_of_bar_idx, params, prev_close, atr_intraday):
    # Phase 1: 3-bar gain >= min_first3bar_gain_pct
    # Phase 2: current bar high > max(prior highs) = HOD
    # Phase 3: current vol >= avg prior vol
    # Entry: hod_prior + 0.01
    # Stop: max(swing_low of last 3 bars, entry - ATR*multiplier)

HOD Breakout IS 결과

구간 수익률 WR Sharpe MDD
IS -0.40% ~45% -0.08 ~8%

결론: VWAP reclaim(-17.4%)보다는 낫지만 여전히 IS에서도 유의미한 alpha 없음. 추가 OOS 테스트 불필요.


8. Fade Short 전략

가설: Top Gainer는 단기 과열 → VWAP reclaim 실패 후 mean reversion 기회.

설계

def detect_fade_short(bars, start_bar_idx, as_of_bar_idx, params, prev_close, atr_intraday):
    # Phase 1: 3-bar gain >= fade_min_gain_pct (default 0.08 = +8%)
    # Entry: current bar close (short 진입)
    # Stop: HOD + 0.5*ATR (entry 위에 위치)
    # TP: entry × (1 - take_profit_pct)

YAML 설정:

entry:
  type: fade_short
  fade_min_gain_pct: 0.08
  stop_atr_multiple: 1.5
exit:
  take_profit_pct: 0.015  # 1.5% TP
  eod_exit_et: "15:55"

Fade IS 성능

지표
수익률 +12.50%
Win Rate 41.9%
Sharpe +2.31
MDD 3.89%
trades 186
exits (TP/stop/eod) 66/102/18

Fade는 IS에서 VWAP reclaim + A7보다 더 좋은 Sharpe 보여줌!

Fade OOS 검증

구간 수익률 WR Sharpe MDD
IS +12.50% 41.9% +2.31 3.89%
OOS_A -8.84% 36.5% -2.05 9.42%
OOS_B +12.47% 44.7% +2.63 2.76%
OOS_C -7.15% 31.3% -1.04 15.97%
4-win avg +2.25%

4-win 중 2/4 양수. OOS_A와 OOS_C에서 큰 손실. 통계적으로 유의하지 않음.


9. QQQ Regime Split 분석

가설: Long은 QQQ 상승일에, Fade는 QQQ 하락일에 우위가 있을 것 → regime-conditional 전략으로 결합 가능.

방법론

  • 각 거래일에 QQQ의 10:00 ET 가격 대비 전일 종가 비율 계산
  • qqq_pct >= 0: QQQ-up 날 / qqq_pct < 0: QQQ-down 날
  • yfinance로 QQQ 일봉 + IntradayCache로 10:00 bar 조회

Long 전략 QQQ Split

구간 QQQ-up P&L (trades, days) QQQ-dn P&L (trades, days)
IS +5,232 (89, 52) -3,871 (51, 32)
OOS_A +1,841 (63, 40) +2,897 (75, 38)
OOS_B -8,234 (88, 49) -3,612 (64, 35)
OOS_C -7,921 (101, 58) -4,102 (89, 42)

패턴: IS에서는 QQQ-up일에 수익, OOS_A에서는 QQQ-dn일에 수익. 일관성 없음.

Fade 전략 QQQ Split

구간 QQQ-up P&L QQQ-dn P&L
IS +891 +6,341

IS 기준으로는 QQQ-down일에 fade가 더 잘 작동 → 그러나 OOS에서 검증되지 않음.

최종 판단

"QQQ 방향이 long-fade 전략의 수익성을 예측하지 못한다"

  • Long: 모든 4개 구간에서 QQQ-up일/QQQ-dn일 모두 같은 부호(구간별)
  • Fade IS: QQQ-dn일에 수익이지만 OOS에서 검증 불가
  • Long-fade orthogonality는 우연의 일치이지, trade 가능한 regime 신호가 아님

10. 코드 변경 사항

libs/tgtc/domain.py

@dataclass
class TGTCEntryParams:
    type: str = "vwap_pullback_reclaim"
    min_first3bar_gain_pct: float = 0.04
    pullback_volume_ratio_max: float = 0.70
    stop_atr_multiple: float = 1.5
    max_candidates_to_scan: int | None = None
    reclaim_volume_ratio_min: float | None = None
    no_entry_after_et: str | None = None
    no_entry_before_et: str | None = None   # mid-day entry용
    fade_min_gain_pct: float | None = None  # fade short 최소 3-bar gain

@dataclass
class TGTCExitParams:
    partial_at_1r: float = 0.33
    stop_to_be_after_1r: bool = True
    min_trend_health_score: int = 4
    eod_exit_et: str = "15:55"
    vwap_break_bars_to_exit: int = 2
    stop_exit_mode: str = "conservative"
    stop_slippage_bps: float = 10.0
    take_profit_pct: float | None = None           # quick TP
    disable_partial_at_1r: bool = False
    tp_requires_no_stop_touch: bool = False        # conservative TP
    force_exit_after_minutes: int | None = None    # time-stop
    partial_levels: list | None = None             # multi-level partials

libs/tgtc/signals.py — 주요 수정

Look-ahead 버그 수정:

def detect_vwap_pullback_reclaim(bars, start_bar_idx, as_of_bar_idx, params, prev_close, atr_intraday):
    # ...
    if reclaim_high > pb_high:
        # LOOK-AHEAD GUARD: setup이 현재(as_of) 봉에서 완료된 경우만 허용
        # no_entry_before_et 지연 시 과거 setup의 entry_price 반환 방지
        if reclaim_idx != len(local) - 1:
            continue
        # ... 나머지 로직

HOD Breakout 신호 추가:

def detect_hod_breakout(bars, start_bar_idx, as_of_bar_idx, params, prev_close, atr_intraday):
    # Phase 1: 3-bar gain
    # Phase 2: current bar > max(prior highs)
    # Phase 3: volume confirmation
    # Returns: {entry_price, stop_price, setup_bar_idx, side: "long"}

Fade Short 신호 추가:

def detect_fade_short(bars, start_bar_idx, as_of_bar_idx, params, prev_close, atr_intraday):
    # Phase 1: 3-bar gain >= fade_min_gain_pct
    # Entry: current bar close
    # Stop: HOD + 0.5*ATR (위에 위치)
    # Returns: {entry_price, stop_price, setup_bar_idx, side: "short"}

libs/tgtc/simulator.py — 주요 수정

TGTCTrade 필드 추가:

@dataclass
class TGTCTrade:
    # ... 기존 필드 ...
    side: str = "long"              # "long" or "short"
    tp_collision: bool = False      # TP와 stop이 같은 봉에서 동시 트리거
    score: float = 0.0
    rank_persistence: float = 0.0
    rank_velocity: float = 0.0
    price_structure: float = 0.0
    volume_quality: float = 0.0
    relative_strength: float = 0.0
    pct_change_at_10: float = 0.0
    dollar_volume_20d: float = 0.0

Short side P&L 계산:

# Short 진입 시:
# - stop: bar.high >= stop_price (위에서 stop)
# - TP: bar.low <= tp_target (아래 방향)
# - P&L: (entry_price - exit_price) * shares
# - trend_health: skip (방향성 척도이므로 short에 부적합)

Entry type dispatch:

if ent.type == "hod_breakout":
    setup_fn = detect_hod_breakout
elif ent.type == "fade_short":
    setup_fn = detect_fade_short
else:
    setup_fn = detect_vwap_pullback_reclaim

11. 발견된 버그 요약

1. Look-ahead 버그 (D/B 시리즈)

증상: no_entry_before_et 설정 시 IS/OOS 결과가 실제보다 극단적으로 높음 (+95~130%)
원인: 지연 진입 조건이 있어도 detect_vwap_pullback_reclaim이 과거 bar에서 완성된 setup을 재사용
수정: reclaim_idx != len(local) - 1 가드 추가

2. Stop Width 무효화 (V2/V3 시리즈)

증상: stop_atr_multiple을 1.5 → 2.5로 늘려도 결과가 동일
원인: stop = max(swing_low, entry - ATR*multiple) — swing_low가 ATR-derived stop보다 높을 때 multiplier 무의미
수정: 의도적 설계이므로 수정 안 함. 단, 더 넓은 stop이 필요하다면 swing_low 기준 자체를 조정해야 함

3. Conservative TP OOS_C 충격

증상: OOS_C에서 -tp_requires_no_stop_touch 비활성화 시 ~18pp P&L 차이
원인: 5분봉에서 TP와 stop이 같은 봉에서 모두 트리거되면, conservative 설정은 stop이 먼저 발동했다고 가정. OOS_C에서 이런 collision 비율이 높았음
의미: 5분봉 해상도에서 TP/stop collision은 피할 수 없는 노이즈 소스

4. QQQ Daily Cache 없음

증상: DailyBarCache.get("QQQ", date) 가 모두 None 반환
원인: QQQ에 .parquet.lock 파일만 있고 실제 데이터 없음
수정: yfinance로 QQQ 일봉 직접 fetch

5. relative_strength 역산 실패

증상: QQQ regime split을 위해 relative_strength 역산으로 QQQ 방향을 구하려 했으나 모든 top gainer가 rs=1.0으로 clamped
원인: relative_strength = clip(0.5 + (sym-qqq)/0.10, 0, 1) — top gainer는 sym >> qqq여서 항상 1.0
수정: yfinance로 QQQ 직접 fetch


12. 최종 결론

전략별 요약

전략 IS 최고 성능 OOS 4-win avg 결론
VWAP Reclaim (원본) -17.4% -9.34% 폐기
VWAP Reclaim + A7 (TP 2%) +13.73% / Sharpe +1.14 +1.20% OOS 불안정
HOD Breakout -0.40% 미측정 IS도 무효
Fade Short +12.50% / Sharpe +2.31 +2.25% OOS 불안정 (2/4)
Long + QQQ regime 예측력 없음
Fade + QQQ regime OOS 검증 불가

핵심 판단

  1. TGTC universe (Yahoo day_gainers)는 방향성 alpha를 제공하지 않는다

    • Long, HOD breakout, Fade short 모두 OOS-stable alpha 없음
    • IS 과적합이 주된 문제: IS 조건에 맞는 변종이 많지만 OOS에서 재현 안 됨
  2. Quick TP는 IS 과적합 마스킹이다

    • A7 (TP 2%)이 IS를 -17.4% → +13.73%로 올렸지만 OOS는 동일하게 음수
    • TP가 IS 노이즈를 걸러주는 것처럼 보이지만 실제로는 OOS 패턴과 맞지 않음
  3. Score 함수가 예측력이 없다

    • rank_persistence, rank_velocity, price_structure 등이 OOS에서 일관된 trade ranking을 제공 못함
    • "좋은 종목 고르기"가 아닌 "운 좋은 종목 고르기"에 불과
  4. QQQ regime은 실용적인 필터가 아니다

    • IS에서 QQQ-up일에 long이 낫고, QQQ-dn일에 fade가 낫다는 패턴이 보임
    • OOS에서 이 패턴이 재현되지 않아 regime 조건을 실전에 쓸 수 없음

보존되는 인프라

TGTC를 폐기하더라도 다음 인프라는 재사용 가능:

  • libs/tgtc/simulator.py: 다른 intraday 전략의 백테스터로 재사용 가능
  • apps/tgtc_trader/yahoo_gainers.py: Yahoo day_gainers fetch 로직
  • apps/tgtc_trader/snapshot_store.py: Parquet 기반 스냅샷 영속화
  • libs/tgtc/gainers_reconstruct.py: 합성 gainer 재구성 로직
  • libs/tgtc/signals.py: detect_vwap_pullback_reclaim, detect_hod_breakout, detect_fade_short (다른 universe에서 재사용 가능)

향후 방향 (참고용)

TGTC 개념이 완전히 틀린 것은 아닐 수 있다. 개선 방향:

  1. 데이터 품질: Yahoo day_gainers는 real-time API지만 backtest를 위한 합성 재구성에는 한계. 실제 1분봉 데이터로 더 정밀한 재구성 필요.
  2. Universe 개선: 단순 pct_change 상위가 아닌 catalyst-driven (8-K, 분기 실적) 갭 필터링
  3. 1분봉 해상도: 5분봉의 resolution 한계로 정밀한 pullback/reclaim 패턴 감지 어려움
  4. ML 기반 score: Rule-based score 대신 통계적으로 검증된 특성

부록 A: 테스트 자동화 인프라

백테스트 실행

python apps/web/tgtc_service.py --start-date 2025-11-01 --end-date 2026-04-30 \
  --config configs/intraday/strategies/tgtc_v1_vwap_reclaim.yaml

멀티윈도우 OOS 검증 스크립트

# /tmp/tgtc_fade_oos_run.py
windows = [
    ("OOS_A", "2024-05-01", "2024-10-31"),
    ("OOS_B", "2024-11-01", "2025-04-30"),
    ("OOS_C", "2025-05-01", "2025-10-31"),
]
for name, s, e in windows:
    res = _run_multiday_backtest_sync({"date": s, "end_date": e, "config_path": YAML})
    # compute metrics and print

QQQ Regime Split 분석

# /tmp/tgtc_qqq_split_v2.py
# QQQ 10:00 ET 기준 방향 결정 (yfinance 일봉 + IntradayCache 10:00 bar)
qqq_daily = yf.Ticker("QQQ").history(start="2024-04-01", end="2026-05-10")
# 각 거래일의 QQQ 10:00 ET 가격과 전일 종가 비율 계산
# Long/Fade 트레이드를 QQQ-up / QQQ-down 날로 분류하여 P&L 비교

부록 B: 시험된 YAML 설정 참고

A7 (최선 VWAP Reclaim 변종)

strategy_mode: tgtc
tgtc_strategy:
  filters:
    min_day_change_at_10: 0.04
    max_day_change_at_10: 0.35
    must_be_above_vwap: true
  entry:
    type: vwap_pullback_reclaim
    min_first3bar_gain_pct: 0.04
    pullback_volume_ratio_max: 0.70
    stop_atr_multiple: 1.5
  exit:
    take_profit_pct: 0.020
    disable_partial_at_1r: true
    tp_requires_no_stop_touch: false
    eod_exit_et: "15:55"
    stop_exit_mode: conservative
  risk:
    risk_per_trade_pct: 0.30
    max_positions: 3
    daily_loss_limit_pct: 1.0
    initial_equity: 10000

Fade Short (최선 Fade 변종)

strategy_mode: tgtc
tgtc_strategy:
  entry:
    type: fade_short
    fade_min_gain_pct: 0.08
    stop_atr_multiple: 1.5
  exit:
    take_profit_pct: 0.015
    eod_exit_et: "15:55"
    stop_exit_mode: conservative
  risk:
    risk_per_trade_pct: 0.30
    max_positions: 3
    initial_equity: 10000

최종 상태: TGTC V1 → 아카이브. 인프라 코드 보존. V2 재시도 시 데이터 품질 및 universe 필터 개선 필요.