본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 05. 21. 04:47

비정상성 시계열(Non-Stationary Time Series) 처리하기: XGBoost와 Python을 이용한 확률론적 엔진 구축

요약

금융 시장의 비정상성(Non-Stationarity) 문제를 해결하기 위해 XGBoost와 몬테카를로 시뮬레이션을 결합한 확률론적 예측 엔진 구축 방법을 소개합니다. 단일 가격 예측 대신 시장을 다중 우주로 취급하여, 입력 데이터에 확률적 노이즈를 주입하고 모델의 예측 수렴성을 통해 신호의 견고함을 검증합니다.

핵심 포인트

  • 금융 시계열의 비정상성 문제를 해결하기 위해 결정론적 모델 대신 확률론적 접근 방식 채택
  • 정형 데이터 처리에 탁월한 XGBoost를 핵심 엔진으로 사용하되, 과적합 방지를 위해 변동성 조정 수익률 등 상태 기반 피처 설계
  • 몬테카를로 시뮬레이션을 통해 입력 피처에 노이즈를 주입하여 예측 경로의 수렴 여부로 신호의 신뢰도 판단
  • 단일 스크립트가 아닌 상태 기반 피처 엔지니어링 등 독립적인 논리 블록으로 구성된 모듈형 아키텍처 설계

금융 시계열(Financial time series)에 머신러닝(Machine Learning)을 적용해 본 적이 있다면, '완벽한 백테스트(backtest)'가 주는 상실감을 알고 있을 것입니다. 모델을 구축하고 과거의 OHLC(Open, High, Low, Close) 데이터로 학습시키면, 다음 시퀀스를 아름답게 예측합니다. 하지만 이를 실제 운영 환경(production)에 배포하면 시장의 체제(regime)가 변하고 모델은 무너져 버립니다. 핵심 문제는 금융 시장이 매우 비정상성(non-stationary)을 띠며 혼돈스럽다는 점입니다. 단일하고 정확한 미래 가격을 예측하려는 결정론적 모델(Deterministic models)은 통계적으로 취약합니다. 이들은 미래가 과거를 정확히 반영할 것이라고 가정하기 때문입니다. AEMMtrader에서는 지난 1년 동안 우리의 아키텍처(architecture)를 완전히 재고하는 데 시간을 보냈습니다. 우리는 하나의 경로를 예측하려는 시도를 멈추고, 시장을 확률론적인 '다중 우주(multiverse)'로 취급하는 Python 기반의 예측 엔진을 구축했습니다. 이 엔진은 XGBoost의 비선형 회귀(non-linear regression) 능력과 몬테카를로 시뮬레이션(Monte Carlo simulations)의 스트레스 테스트(stress-testing) 능력을 결합합니다. 여기에서 우리 엔진을 구동하는 아키텍처와 Python 코드에 대해 심층적으로 살펴보겠습니다.

  1. 핵심 엔진: 왜 XGBoost인가?
    딥러닝(Deep Learning, LSTMs, Transformers)이 많은 화제를 모으고 있지만, Gradient Boosted Decision Trees (XGBoost)와 같은 트리 기반 모델(tree-based models)은 구조화된 정형 데이터(tabular data)에서 지속적으로 더 나은 성능을 보여줍니다. 그러나 원시 가격 데이터를 XGBoost 모델에 그대로 입력하는 것은 과적합(overfitting)으로 가는 지름길입니다. 우리의 피처 벡터(feature vector)는 절대적인 가격보다는 시장의 상태를 설명하는 지표들을 설계합니다:
  • 변동성 조정 수익률(Volatility-Adjusted Returns): 움직임의 에너지를 측정합니다.
  • 모멘텀(Momentum) 및 상대 거래량(Relative Volume): 갑작스러운 기관의 유동성 변화를 포착합니다.
  • Pandas를 통해 동적으로 계산된 RSI: 평균 회귀(mean-reversion) 확률을 측정합니다.
  1. 혼돈 주입하기: 몬테카를로 레이어(The Monte Carlo Layer)
    훌륭한 피처(features)가 있더라도 XGBoost는 최근의 시장 노이즈(noise)에 과적합될 수 있습니다. '가짜 돌파(fake breakouts)'를 방지하기 위해, 우리는 예측 모델을 몬테카를로 시뮬레이션 루프(Monte Carlo simulation loop)로 감쌉니다. 모델을 한 번 실행하는 대신, 30번의 독립적인 실행을 수행합니다. 각 시뮬레이션 동안, 우리는 입력 피처(가격과 거래량 모두)에 보정된 확률적 노이즈(stochastic noise)를 주입합니다.

기저 신호(underlying signal)가 견고하다면, 모델은 노이즈를 뚫고 동일한 목적지로 수렴할 것입니다. 만약 그것이 단순한 시장 노이즈라면, 30개의 경로(paths)는 무작위로 흩어질 것이며, 우리의 엔진은 해당 자산 상태를 "Neutral(중립)"로 표시합니다.

  1. 아키텍처 설계도 (Python)
    우리의 독점적인 로직을 보호하기 위해 정확한 프로덕션 코드를 공유하지는 않겠지만, 우리가 엔진을 구축한 방식의 기초적인 아키텍처를 소개합니다. 우리는 단일 구조의 스크립트(monolithic script) 대신, 문제를 세 개의 독립적인 논리 블록으로 나누었습니다.

블록 A: 상태 기반 피처 엔지니어링 (State-Based Feature Engineering)
우리는 XGBoost 모델에 원시 가격(raw prices)을 절대 입력하지 않습니다. 대신, 가격 움직임을 시장의 "에너지(energy)"와 "상태(state)"를 설명하는 벡터로 변환합니다.

import pandas as pd
import numpy as np
import xgboost as xgb

class ProbabilisticEngine:
    def __init__(self, timeframe):
        self.timeframe = timeframe
        self.model = xgb.XGBRegressor(max_depth=5, n_estimators=80)  # 기본 개념

    def _engineer_features(self, df):
        """
        절대 가격보다는 시장의 상태를 계산합니다.
        """
        features = pd.DataFrame(index=df.index)

        # 1. 에너지: 로그 수익률 (Logarithmic returns)
        features["log_ret"] = np.log(df["close"] / df["close"].shift(1))

        # 2. 기관 활동: 상대적 거래량 급증 (Relative volume spikes)
        features["vol_rel"] = df['tick_volume'] / df['tick_volume'].rolling(20).mean()

        # 3. 시장 구조: 커스텀 모멘텀 및 평균 회귀 지표
        # (독점적 계산식 생략)
        features["momentum"] = self._calculate_momentum(df)

        return features.dropna()

블록 B: 몬테카를로 멀티버스 루프 (The Monte Carlo Multiverse Loop)
이것이 엔진의 핵심입니다. 모델이 다음 캔들에 대한 기본 수학적 기대치(mathematical expectation)를 예측하면, 우리는 (최근 시장 변동성에 기반한) 확률적 노이즈(stochastic noise)를 주입하고 미래를 30번 시뮬레이션합니다.

    def generate_multiverse(self, current_state, n_steps=20, simulations=30):
        """
        가능한 미래 가격 경로의 행렬을 생성합니다.
        """
        all_paths = []
        historical_volatility = current_state[ " close " ].pct_change().std()
        for _ in range(simulations):
            path = []
            simulated_state = current_state.copy()
            for step in range(n_steps):
                # 1. XGBoost predicts the expected baseline move
                features = self._engineer_features(simulated_state)
                expected_move = self.model.predict(features.tail(1))[0]
                # 2. Inject stochastic noise to simulate market chaos
                price_noise = np.random.normal(0, historical_volatility)
                next_price = simulated_state[" close "].iloc[-1] * np.exp(expected_move + price_noise)
                path.append(next_price)
                # 3. Step forward in time (update the simulated state)
                simulated_state = self._update_state(simulated_state, next_price)
            all_paths.append(path)
        return np.array(all_paths) # Returns a shape of (30, 20)

# Block C: Synthesizing the Consensus
Having an array of 30 different paths is useless for execution. We must compress this "multiverse" into a clear, actionable signal with a mathematical confidence score.
def extract_signal(self, current_price, all_paths):
    """ Translates the Monte Carlo matrix into clean probabilities."""
    # Calculate the mathematical average of all 30 paths
    mean_path = np.mean(all_paths, axis=0)
    # Calculate Confidence Score based on terminal path locations
bullish_paths = sum(path[-1] > current_price for path in all_paths)
    buy_probability = bullish_paths / len(all_paths)
    # Calculate dynamic wicks (High/Low) using historical ATR return
    return {"mean_trajectory": mean_path, "confidence_score": buy_probability * 100, "direction": "BUY" if buy_probability > 0.5 else "SELL"}

# 4. Synthesizing the "Multiverse" into Actionable Visuals
Most Monte Carlo implementations output a "spaghetti chart" displaying dozens of overlapping lines. This is visually overwhelming. Instead, look at the np.mean calculation in the code above.

이 엔진은 노이즈가 주입된 30개의 모든 시뮬레이션에 걸쳐 평균 확률 경로 (Mean Probability Path)를 계산합니다. 이 경로로부터 수학적으로 미래의 OHLC 캔들을 재구성합니다. 미래 캔들의 몸통은 평균 궤적이며, 꼬리 (고가/저가 분산)는 평균 실질 변동폭 (Average True Range, ATR)의 일정 비율에 의해 동적으로 제한됩니다.

사례 연구: D1 타임프레임의 EUR/USD
위 차트에서 오른쪽으로 확장되는 예측 캔들의 시퀀스를 볼 수 있습니다. 이것은 단일한 결정론적 추측이 아닙니다. 만약 주입된 노이즈에도 불구하고 30개의 시뮬레이션 중 21개가 시작 가격보다 높은 곳에서 마감했다면, 기저의 수학적 신뢰도는 상승 측에 크게 기울어집니다.

5. 실시간 성능을 위한 스마트 캐싱 (Smart Caching)
수백 개의 자산과 여러 타임프레임에 걸쳐 이를 실제 운영 환경 (Production)에서 실행 가능하게 만들기 위해, 모든 틱 (Tick)마다 XGBoost 모델을 재학습시키는 것은 불가능했습니다. 우리는 타임프레임 제한에 기반한 스마트 캐싱 로직을 구현하는 오케스트레이터 (Orchestrator) 레이어를 구축했습니다:

```python
if model.tf_min >= 1440: # D1, W1
    retrain_limit = 1
elif model.tf_min == 240: # H4
    retrain_limit = 18
else: # H1, M30
    retrain_limit = 24

if new_candles_count < retrain_limit:
    needs_training = False
    model.update_data(df) # 상태 업데이트를 통한 핫 리로드 (Hot reload)

새로운 캔들의 임계값에 도달하지 않으면, 모델은 무거운 .fit() 단계를 건너뛰고, 핫 리로드를 통해 피처 배열 (Feature array)을 업데이트하며, 밀리초 단위로 몬테카를로 행렬 (Monte Carlo matrix)을 재계산합니다.

맺음말
결정론적 로직 (예: RSI < 30 이면 매수)에서 확률론적 머신러닝 (Probabilistic Machine Learning)으로 전환하려면 사고방식의 변화가 필요합니다. 이는 시장이 혼돈 상태임을 받아들이는 것을 의미하며, 퀀트 개발자 (Quantitative Developer)로서 우리의 역할은 정확한 미래를 예측하는 것이 아니라, 가격을 확률의 수학적 범위 내에 가두는 것입니다. 이 엔진이 다양한 타임프레임과 자산에 대해 이러한 몬테카를로 OHLC 구조를 실시간으로 어떻게 출력하는지 확인하는 데 관심이 있다면, AEMMtrader.com에서 라이브 대시보드를 모니터링할 수 있습니다.

다른 Python 개발자들과 데이터 과학자(Data Scientists)분들이 자신들의 머신러닝 (ML) 모델에서 비정상성 (non-stationarity)을 어떻게 처리하는지 정말 궁금합니다. 금융 데이터 (financial data)에서 과적합 (overfitting)을 방지하기 위해 여러분이 즐겨 사용하는 방법은 무엇인가요? 댓글로 여러분의 생각을 공유해 주세요!

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0