주식분석

HTFC v2: Hierarchical Temporal Fusion Chain with Shared Encoder

파동분석가 2026. 4. 30. 13:02

Overview

HTFC v2는 기존 HTFC 아키텍처에 공유 인코더 + 심볼 임베딩 방식을 도입하여 16개 종목의 데이터로 학습하는 멀티심볼 확장 버전이다.

핵심 변경: 하나의 WaveEncoder가 모든 종목의 파동 패턴을 공유 학습하되, 각 종목의 고유 특성은 학습 가능한 심볼 임베딩으로 구분한다. 데이터 다양성 20배 증가 효과 + 모델 크기 거의 동일.

16개 종목 입력 (시장 센서)
  → 공유 WaveEncoder × 6 TF → WSE per symbol per TF
  → Cross-Scale Attention (탑다운 체인)
  → Cross-Symbol Aggregation → Market Context Vector
  → EntryHead (타겟 심볼 WSE + Market Context)
  → Buy/Sell/NoTrade + SL/TP

TF 체인: D1 → H4 → H1 → M30 → M15 → M5
         (regime) (direction) (swing) (momentum) (setup) (entry)

종목 구성 (16개)

트레이딩 대상 (4개) — 실제 진입/청산

심볼역할스프레드

EURUSD 메이저, 최고 유동성 ~0.6 pip
GBPUSD 메이저, 변동성 큼 ~0.9 pip
AUDUSD 상품 통화 ~0.8 pip
NZDUSD 상품 통화, AUD와 상관 ~1.2 pip

시장 센서 (12개) — 입력만, 트레이딩 안 함

심볼역할제공하는 정보

USDJPY 메이저 금리차 센티먼트, 리스크온/오프
USDCAD 메이저 원유 연동, 북미 경제
USDCHF 메이저 안전자산 흐름
AUDCAD 크로스 상품 통화 간 상대 강도
AUDCHF 크로스 리스크 vs 안전자산
EURNZD 크로스 유럽 vs 오세아니아
USDX 인덱스 달러 종합 강도
US100 주식지수 리스크 센티먼트, 기술주 흐름
XAUUSD 금/달러 안전자산 수요, 인플레이션 기대
XAUAUD 금/호주달러 금 vs 상품 통화 상대 가치
XBRUSD 브렌트유 에너지, 글로벌 수요
CORN 곡물 인플레이션 압력, 상품 사이클

종목 간 관계 맵

           ┌─── USDX (달러 종합) ───┐
           │                        │
    ┌──────┼──────┐          ┌──────┼──────┐
    │ 달러 메이저  │          │  달러 약세   │
    │ USDJPY      │          │  EURUSD ★   │
    │ USDCAD      │          │  GBPUSD ★   │
    │ USDCHF      │          │  AUDUSD ★   │
    └─────────────┘          │  NZDUSD ★   │
                             └─────────────┘
    ┌─────────────┐          ┌─────────────┐
    │ 크로스 관계  │          │ 자산군 교차  │
    │ AUDCAD      │          │ XAUUSD (금) │
    │ AUDCHF      │          │ XAUAUD      │
    │ EURNZD      │          │ XBRUSD (유) │
    └─────────────┘          │ US100 (주식)│
                             │ CORN (곡물) │
                             └─────────────┘
    ★ = 트레이딩 대상

심볼 카테고리 임베딩

SYMBOL_CATEGORIES = {
    # category_id: 자산군 분류
    0: "usd_major",     # EURUSD, GBPUSD, USDJPY, USDCAD, USDCHF
    1: "commodity_fx",   # AUDUSD, NZDUSD
    2: "cross",          # AUDCAD, AUDCHF, EURNZD
    3: "index",          # USDX, US100
    4: "metal",          # XAUUSD, XAUAUD
    5: "energy_agri",    # XBRUSD, CORN
}

SYMBOL_IDS = {
    "EURUSD": 0,  "GBPUSD": 1,  "AUDUSD": 2,  "NZDUSD": 3,
    "USDJPY": 4,  "USDCAD": 5,  "USDCHF": 6,
    "AUDCAD": 7,  "AUDCHF": 8,  "EURNZD": 9,
    "USDX": 10,   "US100": 11,
    "XAUUSD": 12, "XAUAUD": 13,
    "XBRUSD": 14, "CORN": 15,
}

Architecture

전체 구조

입력: 16 symbols × 6 TFs × N bars × 22 features

  ┌──────────────────────────────────────────────────┐
  │          Shared WaveEncoder (TF별 1개, 총 6개)    │
  │                                                    │
  │  입력: [bar_features(22) + symbol_embed(16)        │
  │         + category_embed(8)] = 46-dim              │
  │                                                    │
  │  TCN (causal dilated conv) + positional encoding   │
  │  → 32-dim WSE per symbol per TF                    │
  │                                                    │
  │  파라미터: ~420K × 6 TF = ~2.52M                   │
  │  (기존과 동일, 입력 projection만 46→hidden으로 변경) │
  └──────────────────────┬─────────────────────────────┘
                         │
                         ▼
  ┌──────────────────────────────────────────────────┐
  │       Cross-Scale Attention (탑다운 체인)          │
  │                                                    │
  │  D1 WSE(16sym) → H4 WSE(16sym) → ... → M5 WSE    │
  │                                                    │
  │  각 TF에서: child WSE가 parent WSE에 attend        │
  │  "부모 파동의 어디에 내가 있는가?"                    │
  │                                                    │
  │  결과: TF별 context-enriched WSE (16sym × 6TF)     │
  └──────────────────────┬─────────────────────────────┘
                         │
                         ▼
  ┌──────────────────────────────────────────────────┐
  │      Cross-Symbol Aggregation                      │
  │                                                    │
  │  방법: Attention-weighted pooling                   │
  │                                                    │
  │  타겟 심볼의 M5 WSE = Query                        │
  │  전체 16심볼의 M5 WSE = Key/Value                  │
  │  → Market Context Vector (32-dim)                  │
  │                                                    │
  │  "EURUSD 진입 판단 시,                              │
  │   USDJPY/XAUUSD/US100 등의 상태가                  │
  │   얼마나 관련있는가?" 를 자동 학습                   │
  │                                                    │
  │  파라미터: ~8K (매우 경량)                           │
  └──────────────────────┬─────────────────────────────┘
                         │
                         ▼
  ┌──────────────────────────────────────────────────┐
  │          EntryHead (타겟 심볼별 1회 추론)           │
  │                                                    │
  │  입력: GatedFusion(                                │
  │    target_M5_WSE(32)      ← 타겟의 M5 상태        │
  │    + ancestor_WSEs(32×5)  ← 타겟의 D1~M15 상태    │
  │    + market_context(32)   ← 16심볼 종합 맥락       │
  │  )                                                 │
  │                                                    │
  │  출력: {action, confidence, SL, TP}                │
  │                                                    │
  │  파라미터: ~170K (기존 153K + context 입력 확장)     │
  └────────────────────────────────────────────────────┘

파라미터 총계

Shared WaveEncoder × 6 TF:     2,520K  (기존과 거의 동일)
Cross-Scale Attention:             25K  (기존과 동일)
Symbol Embedding (16 × 16):         256
Category Embedding (6 × 8):          48
Input Projection (46 → hidden):     ~5K  (기존 22→hidden 에서 확장)
Cross-Symbol Aggregation:           ~8K  (신규)
EntryHead:                         170K  (약간 확장)
─────────────────────────────────────────
총계:                            ~2.73M  (기존 3.36M 이하)

기존보다 오히려 작다. 심볼별 독립 인코더를 공유 인코더로 바꾸면서 절약.


Core Components

1. Symbol Embedding

class SymbolEmbedding(nn.Module):
    """
    각 종목의 고유 특성을 학습 가능한 벡터로 표현.
    
    symbol_embed: 종목 고유 ID → 16-dim 벡터
      "EURUSD는 유동성 높고 변동성 낮다" 같은 특성을 학습
    
    category_embed: 자산군 카테고리 → 8-dim 벡터
      "이건 메이저 FX다" vs "이건 상품이다" 같은 그룹 특성
    
    합산: 16 + 8 = 24-dim → bar features(22)와 concat → 46-dim
    """
    def __init__(self, n_symbols=16, n_categories=6):
        super().__init__()
        self.symbol_embed = nn.Embedding(n_symbols, 16)
        self.category_embed = nn.Embedding(n_categories, 8)
        
    def forward(self, symbol_id, category_id):
        # 출력: 24-dim vector (시퀀스의 모든 봉에 동일하게 더해짐)
        return torch.cat([
            self.symbol_embed(symbol_id),
            self.category_embed(category_id)
        ], dim=-1)

2. Shared WaveEncoder (수정)

class SharedWaveEncoder(nn.Module):
    """
    기존 WaveEncoder를 모든 심볼이 공유하도록 수정.
    
    핵심 변경: 
    - input_dim: 22 → 46 (bar features + symbol/category embed)
    - TCN 구조: 동일 (causal dilated conv)
    - 출력: 32-dim WSE (동일)
    
    효과:
    - EURUSD에서 배운 "급락 후 반등" 패턴을 GBPUSD에서도 활용
    - 심볼 임베딩이 "이 패턴의 이 심볼 버전" 을 조정
    - 학습 데이터 16배 증가 효과
    """
    def __init__(self, input_dim=46, hidden_dim=64, wse_dim=32,
                 tcn_channels=[64, 64, 64], kernel_size=3):
        super().__init__()
        self.input_projection = nn.Linear(input_dim, hidden_dim)
        self.tcn = TCN(
            num_inputs=hidden_dim,
            num_channels=tcn_channels,
            kernel_size=kernel_size,
            dropout=0.1
        )
        self.positional_encoding = PositionalEncoding(hidden_dim)
        self.wse_projection = nn.Linear(hidden_dim, wse_dim)
        
    def forward(self, x, symbol_embed):
        """
        x: [batch, seq_len, 22] (bar features)
        symbol_embed: [batch, 24] (symbol + category embed)
        
        return: [batch, wse_dim] (32-dim WSE)
        """
        # 심볼 임베딩을 모든 타임스텝에 broadcast
        sym = symbol_embed.unsqueeze(1).expand(-1, x.size(1), -1)
        x = torch.cat([x, sym], dim=-1)  # [batch, seq_len, 46]
        
        x = self.input_projection(x)      # [batch, seq_len, 64]
        x = self.positional_encoding(x)
        x = self.tcn(x)                   # [batch, seq_len, 64]
        x = x[:, -1, :]                   # 마지막 타임스텝
        wse = self.wse_projection(x)      # [batch, 32]
        return wse

3. Cross-Symbol Aggregation (신규)

class CrossSymbolAggregation(nn.Module):
    """
    타겟 심볼의 진입 판단을 위해 전체 16심볼의 상태를 종합.
    
    Attention-weighted pooling:
    - Query: 타겟 심볼의 WSE
    - Key/Value: 전체 16심볼의 WSE
    
    학습 효과 예시:
    - EURUSD 매수 판단 시, USDJPY 하락(달러 약세 확인) → 높은 가중치
    - XAUUSD 급등(리스크오프) → AUDUSD 매수 억제 방향으로 작용
    - CORN/XBRUSD 동반 상승(인플레이션) → 금리 민감 통화에 영향
    """
    def __init__(self, wse_dim=32):
        super().__init__()
        self.query_proj = nn.Linear(wse_dim, wse_dim)
        self.key_proj = nn.Linear(wse_dim, wse_dim)
        self.value_proj = nn.Linear(wse_dim, wse_dim)
        self.scale = wse_dim ** 0.5
        self.output_proj = nn.Linear(wse_dim, wse_dim)
        
    def forward(self, target_wse, all_wses):
        """
        target_wse: [batch, 32]           (타겟 심볼의 M5 WSE)
        all_wses:   [batch, 16, 32]       (전체 16심볼의 M5 WSE)
        
        return:     [batch, 32]           (Market Context Vector)
        """
        Q = self.query_proj(target_wse).unsqueeze(1)  # [B, 1, 32]
        K = self.key_proj(all_wses)                    # [B, 16, 32]
        V = self.value_proj(all_wses)                  # [B, 16, 32]
        
        attn = torch.matmul(Q, K.transpose(-2, -1)) / self.scale
        attn = F.softmax(attn, dim=-1)                 # [B, 1, 16]
        
        context = torch.matmul(attn, V).squeeze(1)     # [B, 32]
        return self.output_proj(context)

4. EntryHead (확장)

class EntryHeadV2(nn.Module):
    """
    기존 EntryHead에 Market Context 입력 추가.
    
    입력 구성:
    - target_m5_wse (32):    타겟 심볼의 M5 파동 상태
    - ancestor_wses (32×5):  타겟의 D1~M15 파동 상태
    - market_context (32):   16심볼 종합 시장 맥락  ← 신규
    
    총 입력: 32 + 160 + 32 = 224-dim (기존 192 + 32)
    """
    def __init__(self, wse_dim=32, n_timeframes=6):
        super().__init__()
        input_dim = wse_dim * n_timeframes + wse_dim  # 192 + 32 = 224
        
        self.gate = nn.Sequential(
            nn.Linear(input_dim, input_dim),
            nn.Sigmoid()
        )
        self.classifier = nn.Sequential(
            nn.Linear(input_dim, 128),
            nn.ReLU(),
            nn.Dropout(0.2),
            nn.Linear(128, 64),
            nn.ReLU(),
            nn.Dropout(0.1),
            nn.Linear(64, 3)  # Buy / Sell / NoTrade
        )
        self.sl_tp_head = nn.Sequential(
            nn.Linear(input_dim, 64),
            nn.ReLU(),
            nn.Linear(64, 2)  # SL, TP (ATR 배수)
        )
        
    def forward(self, target_wses, market_context):
        """
        target_wses:    [batch, 6, 32]  (D1~M5 WSE for target symbol)
        market_context: [batch, 32]     (Cross-Symbol output)
        """
        flat = target_wses.reshape(target_wses.size(0), -1)  # [B, 192]
        x = torch.cat([flat, market_context], dim=-1)         # [B, 224]
        
        gate = self.gate(x)
        x = x * gate  # Gated Fusion
        
        action_logits = self.classifier(x)  # [B, 3]
        sl_tp = self.sl_tp_head(x)          # [B, 2]
        
        return action_logits, sl_tp

5. Full Chain (통합)

class HTFCv2(nn.Module):
    """
    전체 HTFC v2 체인.
    
    Forward 흐름:
    1. 16심볼 × 6TF 데이터를 공유 인코더로 WSE 추출
    2. 탑다운 Cross-Scale Attention (D1→M5)
    3. Cross-Symbol Aggregation (타겟 기준 시장 맥락)
    4. EntryHead (진입 판단)
    """
    TIMEFRAMES = ["D1", "H4", "H1", "M30", "M15", "M5"]
    
    def __init__(self, config):
        super().__init__()
        self.symbol_embedding = SymbolEmbedding(
            n_symbols=config.n_symbols,       # 16
            n_categories=config.n_categories  # 6
        )
        # TF별 공유 인코더 (6개)
        # 같은 TF 내에서는 모든 심볼이 같은 인코더 사용
        self.shared_encoders = nn.ModuleDict({
            tf: SharedWaveEncoder(
                input_dim=config.bar_features + 24,  # 22 + 24 = 46
                hidden_dim=config.hidden_dim,         # 64
                wse_dim=config.wse_dim,               # 32
            )
            for tf in self.TIMEFRAMES
        })
        # 탑다운 체인 어텐션 (기존과 동일)
        self.cross_scale = nn.ModuleDict({
            f"{parent}_{child}": CrossScaleAttention(config.wse_dim)
            for parent, child in zip(self.TIMEFRAMES[:-1], self.TIMEFRAMES[1:])
        })
        # 심볼 간 종합 (신규)
        self.cross_symbol = CrossSymbolAggregation(config.wse_dim)
        # 진입 판단
        self.entry_head = EntryHeadV2(
            wse_dim=config.wse_dim,
            n_timeframes=len(self.TIMEFRAMES)
        )
    
    def forward(self, batch):
        """
        batch: {
            'bars': {tf: {sym: [B, seq_len, 22]} for all tf, sym},
            'symbol_ids': {sym: int},
            'category_ids': {sym: int},
            'target_symbol': str,  # e.g. "EURUSD"
        }
        """
        # Step 1: 전체 심볼 × TF의 WSE 추출
        all_wses = {}  # {tf: {sym: [B, 32]}}
        for tf in self.TIMEFRAMES:
            all_wses[tf] = {}
            for sym in batch['bars'][tf]:
                sym_embed = self.symbol_embedding(
                    batch['symbol_ids'][sym],
                    batch['category_ids'][sym]
                )
                wse = self.shared_encoders[tf](
                    batch['bars'][tf][sym],
                    sym_embed
                )
                all_wses[tf][sym] = wse
        
        # Step 2: 탑다운 Cross-Scale (심볼별 독립)
        for i in range(len(self.TIMEFRAMES) - 1):
            parent_tf = self.TIMEFRAMES[i]
            child_tf = self.TIMEFRAMES[i + 1]
            key = f"{parent_tf}_{child_tf}"
            for sym in all_wses[child_tf]:
                if sym in all_wses[parent_tf]:
                    all_wses[child_tf][sym] = self.cross_scale[key](
                        child_wse=all_wses[child_tf][sym],
                        parent_wse=all_wses[parent_tf][sym]
                    )
        
        # Step 3: Cross-Symbol Aggregation
        target = batch['target_symbol']
        target_m5_wse = all_wses["M5"][target]
        
        all_m5_wses = torch.stack([
            all_wses["M5"][sym] for sym in sorted(all_wses["M5"].keys())
        ], dim=1)  # [B, 16, 32]
        
        market_context = self.cross_symbol(target_m5_wse, all_m5_wses)
        
        # Step 4: EntryHead
        target_chain_wses = torch.stack([
            all_wses[tf][target] for tf in self.TIMEFRAMES
        ], dim=1)  # [B, 6, 32]
        
        action_logits, sl_tp = self.entry_head(
            target_chain_wses, market_context
        )
        
        return {
            'action_logits': action_logits,
            'sl_tp': sl_tp,
            'all_wses': all_wses,        # 디버깅/시각화용
            'market_context': market_context
        }

Features (22 per bar, 전 심볼 동일)

FEATURES = {
    # 가격 기반 (6)
    "returns": [
        "log_return",           # log(close/prev_close)
        "high_low_range",       # (high-low) / close
        "body_ratio",           # |close-open| / (high-low)
        "upper_wick_ratio",     # upper_wick / (high-low)
        "lower_wick_ratio",     # lower_wick / (high-low)
        "gap",                  # (open - prev_close) / prev_close
    ],
    
    # 변동성 (4)
    "volatility": [
        "atr_norm",             # ATR(14) / close
        "bb_width",             # (BB_upper - BB_lower) / BB_mid
        "realized_vol_20",      # 20-bar realized volatility
        "vol_of_vol",           # volatility of volatility
    ],
    
    # 모멘텀/추세 (4) — TCN이 학습하도록 최소 가공
    "momentum": [
        "roc_5",                # rate of change 5-bar
        "roc_20",               # rate of change 20-bar
        "close_vs_ema20",       # (close - EMA20) / ATR
        "close_vs_ema50",       # (close - EMA50) / ATR
    ],
    
    # 거래량 (2)
    "volume": [
        "volume_norm",          # volume / SMA(volume, 20)
        "volume_delta",         # (volume - prev_volume) / prev_volume
    ],
    
    # 시간 인코딩 (4)
    "time": [
        "hour_sin",             # sin(2π × hour/24)
        "hour_cos",             # cos(2π × hour/24)
        "dow_sin",              # sin(2π × day_of_week/5)
        "dow_cos",              # cos(2π × day_of_week/5)
    ],
    
    # 심볼 간 상관 (2) — 실시간 계산
    "cross_symbol": [
        "corr_vs_usdx_20",     # 20-bar rolling corr with USDX
        "relative_strength_20", # 같은 카테고리 내 상대 강도
    ],
}
# 총 22개 피처 — 기술지표 미사용, TCN이 패턴 학습

Training Pipeline (5 Phases)

Phase 1: Data Collection & Preprocessing

data_config = {
    "symbols": [
        "AUDUSD", "EURUSD", "GBPUSD", "NZDUSD",     # 트레이딩 대상
        "USDCAD", "USDCHF", "USDJPY",                 # 메이저 센서
        "AUDCAD", "AUDCHF", "EURNZD",                 # 크로스 센서
        "US100", "USDX",                               # 인덱스 센서
        "XBRUSD", "XAUAUD", "XAUUSD",                 # 상품 센서
        "CORN",                                        # 농산물 센서
    ],
    "timeframes": ["D1", "H4", "H1", "M30", "M15", "M5"],
    "history_bars": {
        "D1":  2000,    # ~8년
        "H4":  5000,    # ~3년
        "H1":  10000,   # ~2년
        "M30": 20000,   # ~2년
        "M15": 40000,   # ~2년
        "M5":  60000,   # ~1년
    },
    "trading_targets": ["EURUSD", "GBPUSD", "AUDUSD", "NZDUSD"],
    "preprocessing": {
        "normalization": "per_symbol_rolling_zscore",
        "window": 252,          # 1년 롤링 (D1 기준)
        "clip": 5.0,            # ±5σ 클리핑
        "missing_fill": "ffill_then_zero",
    },
}

비 FX 종목 주의사항:

special_handling = {
    "US100": {
        "trading_hours": "주중 거의 24시간이나 갭 존재",
        "weekend_gap": True,
        "scale": "가격 레벨 ~18000, 정규화 필수",
    },
    "CORN": {
        "trading_hours": "CME 시간만 (제한적)",
        "missing_bars": "많음 — ffill 필수",
        "liquidity": "FX 대비 매우 낮음",
    },
    "XBRUSD": {
        "rollover": "선물 만기 롤오버 처리 필요",
        "gap_risk": "주말 지정학 이벤트 시 갭",
    },
}

Phase 2: Self-Supervised Pre-training (공유 인코더)

목표: 공유 WaveEncoder가 "모든 종목에 공통된 파동 패턴"을 학습
태스크: Next-bar prediction (MSE)

핵심 변경:
- 기존: TF별 독립 인코더를 TF별 독립 학습
- v2: TF별 공유 인코더를 16심볼 데이터로 동시 학습

학습 순서:
1. D1 인코더: 16심볼 × D1 데이터로 프리트레이닝
2. H4 인코더: 16심볼 × H4 데이터로 프리트레이닝
3. ... (H1, M30, M15, M5)

각 TF 인코더는 16심볼의 데이터를 번갈아 학습:
- epoch 내에서 16심볼 데이터를 셔플
- 심볼 임베딩 덕분에 "이건 EURUSD의 패턴" vs "이건 CORN의 패턴" 구분
- 하지만 TCN 가중치는 공유 → 공통 파동 구조 학습

에포크 수: 30 per TF (기존 20보다 증가, 데이터 16배 많으므로)
pretrain_config = {
    "task": "next_bar_mse",
    "epochs_per_tf": 30,
    "batch_size": 64,          # 심볼 간 mix
    "lr": 1e-3,
    "symbol_sampling": "uniform",  # 모든 심볼 균등 샘플링
    "curriculum": False,        # 순서 무관하게 랜덤 학습
}

프리트레이닝 성공 기준:

각 TF 인코더의 next-bar MSE:
- 전체 16심볼 평균 MSE < 0.05
- 심볼별 MSE 편차 < 2× (특정 심볼만 나쁘지 않아야)
- CORN/XBRUSD 같은 비 FX도 FX와 비슷한 수렴 속도
  → 이것이 확인되면 공유 인코더가 범용 패턴을 학습한 증거

Phase 3: Entry Head Training

기존과 동일하되, Market Context 입력 추가.

라벨링: 트레이딩 대상 4개 심볼에 대해서만 Triple Barrier 라벨 생성
센서 12개: 라벨 없음, WSE 추출에만 사용

학습 데이터 흐름:
1. 16심볼 전체의 WSE를 top-down 체인으로 추출
2. Cross-Symbol Aggregation으로 Market Context 생성
3. 타겟 심볼(4개)의 WSE + Market Context → EntryHead
4. Triple Barrier 라벨과 비교하여 학습
entry_train_config = {
    "target_symbols": ["EURUSD", "GBPUSD", "AUDUSD", "NZDUSD"],
    "labels": "triple_barrier",
    "label_params": {
        "profit_taking": 2.0,    # ATR 배수
        "stop_loss": 1.0,        # ATR 배수
        "max_holding": {"M5": 36, "M15": 24, "M30": 16,
                        "H1": 24, "H4": 20, "D1": 10},
    },
    "loss": "class_weighted_cross_entropy + sl_tp_huber",
    "class_weights": "inverse_frequency",
    "epochs": 50,
    "lr": 5e-4,
    "early_stopping": {"patience": 10, "metric": "val_accuracy"},
    
    # 4개 타겟을 번갈아 학습 (멀티태스크 효과)
    "target_sampling": "round_robin",
}

Phase 3 성공 기준:

val_accuracy > 45% (베이스라인 33.3%, 기존 HTFC v1 41.8%)
- 4%p 이상 개선이면 Cross-Symbol 정보가 유효한 증거
- 심볼별 정확도 편차 < 5%p (특정 심볼만 잘하면 안됨)
- Attention 가중치 분석: USDJPY/XAUUSD에 높은 가중치 기대

Phase 4: End-to-End Fine-tuning

finetune_config = {
    "epochs": 15,
    "discriminative_lr": {
        # TF별 차등 학습률 (기존과 동일 원칙)
        "D1_encoder": 0.01,    # 거의 동결
        "H4_encoder": 0.05,
        "H1_encoder": 0.1,
        "M30_encoder": 0.3,
        "M15_encoder": 0.5,
        "M5_encoder":  1.0,    # 가장 적극 조정
        
        # 신규 컴포넌트
        "symbol_embedding": 0.1,        # 느리게 조정
        "cross_symbol_aggregation": 0.5,
        "entry_head": 1.0,
    },
    "base_lr": 1e-4,
}

Phase 5: Walk-Forward Validation & Backtest

walkforward_config = {
    "train_window": "6months",
    "test_window": "2months",
    "step": "1month",
    "purge_gap": "24h",
    "embargo": "12h",
    
    # 스트레스 테스트 기간
    "stress_periods": [
        "2020-03 (COVID crash)",
        "2022-02 (Russia-Ukraine)",
        "2022-09 (UK mini-budget, GBP crash)",
        "2023-03 (SVB crisis)",
        "2024-08 (Yen carry unwind)",
        "2025-04 (Trump tariff, if applicable)",
    ],
    
    # Go/No-Go 기준
    "pass_criteria": {
        "sharpe_ratio": "> 1.0",
        "max_drawdown": "< 20%",
        "profit_factor": "> 1.3",
        "total_trades": "> 200",
        "win_rate": "> 50%",
    },
}

기대 효과: v1 vs v2 비교

레짐 감지 능력

v1 (4 페어):
  EURUSD 급락 → "하락인가?" (맥락 부족)

v2 (16 종목):
  EURUSD 급락 + GBPUSD 급락 + USDJPY 급등 + USDCHF 급등
  + XAUUSD 급등 + US100 급락
  → Cross-Symbol Attention이 "글로벌 리스크오프" 패턴을 감지
  → AUDUSD/NZDUSD 매수 시그널 억제

달러 요인 분리

v1: EURUSD 하락 → 유로 약세? 달러 강세? 구분 불가

v2: EURUSD 하락 + GBPUSD 하락 + AUDUSD 하락 + USDX 상승
    + 하지만 XAUUSD 보합, US100 보합
    → "달러 강세, 리스크 중립"
    → EURNZD(달러 무관)는 별도 판단 가능

상품 사이클 연동

v1: AUDUSD에 대한 상품 가격 영향 → 수동 피처로 넣거나 누락

v2: XBRUSD 상승 + CORN 상승 + AUDUSD/NZDUSD/USDCAD 반응
    → 인코더가 "상품 사이클 상승기" 패턴을 WSE에 인코딩
    → 상품 통화 매수 bias 학습

Project Structure

wave/
  config/
    base.yaml               # 공통 설정
    symbols.yaml             # 16개 심볼 정의, 카테고리 매핑
    nzdusd.yaml             # 심볼별 오버라이드 (선택)
    
  scripts/
    collect.py              # 16심볼 데이터 수집
    train.py                # 전체 학습 파이프라인
    backtest.py             # 워크포워드 백테스트
    live.py                 # 실거래
    analyze_attention.py    # Cross-Symbol Attention 시각화 (신규)
    
  src/
    data/
      collector.py          # MT5 16심볼 수집 (확장)
      synthetic.py          # 합성 데이터 (16심볼 상관관계 포함)
      preprocessor.py       # 심볼별 정규화
      labeler.py            # Triple Barrier (타겟 4심볼만)
      resampler.py          # 타임프레임 리샘플링
      dataset.py            # MultiSymbolDataset (신규)
      
    models/
      symbol_embedding.py   # SymbolEmbedding (신규)
      tcn.py                # TCN (기존)
      wave_encoder.py       # SharedWaveEncoder (수정)
      cross_attention.py    # CrossScaleAttention (기존)
      cross_symbol.py       # CrossSymbolAggregation (신규)
      entry_head.py         # EntryHeadV2 (확장)
      chain.py              # HTFCv2 (수정)
      
    training/
      losses.py             # 손실 함수
      pretrain.py           # 공유 인코더 프리트레이닝 (수정)
      entry_train.py        # 엔트리 학습 (확장)
      finetune.py           # 파인튜닝
      walk_forward.py       # 워크포워드 검증
      
    backtest/
      engine.py             # 백테스트 엔진
      metrics.py            # 평가 지표
      
    live/
      executor.py           # MT5 주문 실행
      monitor.py            # 실시간 모니터링
      runner.py             # 메인 루프
      state_manager.py      # 상태 관리
      
    analysis/               # (신규)
      attention_viz.py      # Cross-Symbol Attention 히트맵
      wse_clustering.py     # WSE 클러스터링 (레짐 분석)
      symbol_importance.py  # 심볼별 기여도 분석
      
    utils/
      config.py
      logging_setup.py

CLI Usage

# 16심볼 데이터 수집
python scripts/collect.py --config config/symbols.yaml [--force-refresh]

# 학습 (전체 파이프라인)
python scripts/train.py --config config/base.yaml --device cuda [--phase all]

# 특정 Phase만 실행
python scripts/train.py --phase pretrain   # Phase 2만
python scripts/train.py --phase entry      # Phase 3만
python scripts/train.py --phase finetune   # Phase 4만

# 백테스트
python scripts/backtest.py \
  --model-path saved_models/htfc_v2.pt \
  --target-symbols EURUSD GBPUSD AUDUSD NZDUSD

# Cross-Symbol Attention 분석
python scripts/analyze_attention.py \
  --model-path saved_models/htfc_v2.pt \
  --target EURUSD \
  --date-range 2024-01-01 2024-12-31

# 실거래
python scripts/live.py \
  --model-path saved_models/htfc_v2.pt \
  --target-symbols EURUSD AUDUSD \
  --dry-run

RTX 4060 (8GB VRAM) 메모리 예산

모델 파라미터 (fp16):           ~5.5 MB
옵티마이저 상태 (AdamW):        ~16.5 MB
활성화 메모리 (batch=64):
  - 16심볼 × 6TF WSE 추출:     ~400 MB
  - Cross-Scale Attention:      ~50 MB
  - Cross-Symbol Aggregation:   ~10 MB
  - EntryHead:                  ~20 MB
그래디언트:                     ~5.5 MB
─────────────────────────────────────
총 예상:                        ~510 MB

8GB VRAM 대비 사용률:           ~6.4%
여유:                           충분 (batch 늘리거나 hidden_dim 확장 가능)

Design Decisions (v1 유지 + v2 추가)

  1. TCN over Transformer: 유지. dilated receptive field가 파동 구조에 적합
  2. Supervised over RL: 유지. 결정론적 라벨, 보상 설계 취약성 회피
  3. Rule-based exits: 유지. 모델 SL/TP 예측 + 규칙 청산
  4. 22 minimal features: 유지. 기술지표 미사용, TCN이 패턴 학습
  5. 32-dim WSE: 유지. 정보 병목으로 과적합 방지
  6. (v2) Shared encoder + symbol embedding: 모델 크기 유지하면서 데이터 16배
  7. (v2) Trading target ≠ Input set: 16개 입력, 4개만 트레이딩
  8. (v2) Cross-Symbol at WSE level: 원시 데이터가 아닌 학습된 표현 간 관계
  9. (v2) Category embedding: 자산군 그룹 특성을 명시적으로 제공

v1 → v2 마이그레이션

v1 학습 결과를 v2에 활용하는 방법:

1. 공유 인코더 초기화:
   - v1의 NZDUSD 인코더 가중치로 v2 공유 인코더를 초기화
   - 다른 심볼은 심볼 임베딩이 차이를 학습

2. Cross-Scale Attention:
   - v1 가중치 그대로 로드 가능 (구조 동일)

3. EntryHead:
   - 입력 차원 변경(192→224)으로 직접 로드 불가
   - 기존 가중치의 앞 192차원만 로드, 나머지 32차원은 랜덤 초기화

4. 신규 컴포넌트:
   - SymbolEmbedding: 랜덤 초기화
   - CrossSymbolAggregation: 랜덤 초기화
   → Phase 3~4에서 학습