본문 바로가기
경제 금융 포털사이트 경제 금융 포털사이트

파이썬으로 뉴스와 주가 데이터 결합하기, AI 투자모델 데이터셋 만드는 법

금알남 읽는 시간 약 17분
ai thumbnail minimal 파이썬으로 뉴스와 주가 데이터 결합하기 AI 투자모델 데이터셋 만드는 법 1790237465

AI 주가예측을 시작할 때 모델보다 먼저 확인해야 하는 것이 뉴스와 가격 데이터의 시간 정렬입니다. 특히 미래 뉴스가 학습 데이터에 섞이면 백테스트 성능이 실제보다 좋아 보일 수 있습니다. 예를 들어 오후에 나온 실적 발표 기사가 장 마감 전 가격 예측에 반영되는 오류를 범하면 실전에서는 낭패를 보기 쉽습니다. 이러한 데이터 누수 문제를 방지하기 위해, 아래 글에 데이터 수집 단계부터 정확한 거래일 결합, 그리고 올바른 정답값 생성 과정까지 실무적인 노하우를 한 번에 알기 쉽게 정리해 두었습니다. 성공적인 알고리즘 트레이딩을 위한 첫걸음으로 꼭 읽어보시길 권합니다.

AI 투자모델, 모델보다 데이터셋이 먼저입니다

“뉴스 감성점수도 만들었고 주가 데이터도 받았으니 이제 LSTM에 넣으면 되는 것 아닌가?”

처음 AI 투자모델을 만드는 분들이 가장 많이 하는 생각입니다. 하지만 실제 작업에서는 모델보다 그 전에 해결해야 할 문제가 있습니다.

바로 뉴스가 나온 시간과 실제 주식시장이 움직인 시간을 정확하게 연결하는 것입니다.

애플(AAPL), 엔비디아(NVDA), 테슬라(TSLA) 같은 개별 종목뿐 아니라 S&P500을 추종하는 SPY, 나스닥100을 추종하는 QQQ를 분석할 때도 원리는 같습니다.

가격 데이터가 아무리 정확하고 FinBERT 감성점수가 아무리 정교해도 뉴스와 주가가 잘못 연결되면 모델이 엉뚱한 규칙을 학습하게 됩니다.

더 심각한 문제는 결과가 나쁘게 나오는 것이 아닙니다. 오히려 백테스트 결과가 지나치게 좋아 보일 수 있다는 점입니다. 미래에 알게 될 정보를 과거 데이터에 넣는 이른바 ‘데이터 누수(Data Leakage)’가 발생하기 때문입니다.

이번 글에서는 파이썬을 이용해 뉴스와 주가 데이터를 하나의 AI 학습 데이터셋으로 만드는 기본 구조를 살펴보겠습니다.

뉴스 데이터와 주가 데이터는 왜 바로 합치면 안 될까

예를 들어 애플 관련 호재 기사가 미국 증시 정규장 마감 이후 발표됐다고 가정해보겠습니다.

그런데 기사 날짜가 같다는 이유만으로 그날 종가 데이터와 결합한다면 문제가 생깁니다. 그 종가는 이미 뉴스가 발표되기 전에 결정된 값이기 때문입니다. 따라서 먼저 결정해야 합니다.

“나는 어느 시점까지 알고 있는 정보로 어느 시점의 가격을 예측할 것인가?”

예를 들어 모델의 기준을 다음과 같이 정할 수 있습니다.

  • 거래일 t의 장 마감까지 확인할 수 있었던 정보만 사용
  • 가격, 거래량, 뉴스 감성점수를 입력값으로 사용
  • 거래일 t+1의 상승·하락을 예측

이렇게 기준점을 정해 놓으면 뉴스와 가격 데이터를 연결하는 방법도 명확해집니다.

Pandas의 merge_asof는 일반적인 동일 값 기준 JOIN과 달리 시간적으로 가장 가까운 이전 또는 이후 데이터를 연결할 수 있어 시계열 데이터 정렬에 활용할 수 있습니다. 사용 전 데이터는 시간 기준으로 정렬되어 있어야 합니다.

파이썬으로 가격 데이터와 뉴스 데이터 불러오기

예시는 애플 AAPL을 사용해보겠습니다.

Alpha Vantage에서는 일별 주가의 시가·고가·저가·종가·거래량 데이터를 제공하고, 별도의 NEWS_SENTIMENT API에서는 종목과 관련된 금융 뉴스와 감성 데이터를 조회할 수 있습니다.

import requests
import pandas as pd
from io import StringIO

API_KEY = "YOUR_API_KEY"
TICKER = "AAPL"

price_url = (
    "https://www.alphavantage.co/query"
    f"?function=TIME_SERIES_DAILY"
    f"&symbol={TICKER}"
    f"&datatype=csv"
    f"&apikey={API_KEY}"
)

price_res = requests.get(price_url)
price = pd.read_csv(StringIO(price_res.text))

price["timestamp"] = pd.to_datetime(price["timestamp"])
price = price.sort_values("timestamp")

print(price.head())

기본적으로 다음과 같은 데이터가 만들어집니다.

timestamp
open
high
low
close
volume

이번에는 뉴스입니다.

news_url = (
    "https://www.alphavantage.co/query"
    f"?function=NEWS_SENTIMENT"
    f"&tickers={TICKER}"
    f"&limit=1000"
    f"&apikey={API_KEY}"
)

news_json = requests.get(news_url).json()

rows = []

for item in news_json.get("feed", []):
    rows.append({
        "title": item.get("title"),
        "published_at": item.get("time_published"),
        "sentiment": item.get("overall_sentiment_score")
    })

news = pd.DataFrame(rows)

news["published_at"] = pd.to_datetime(
    news["published_at"],
    errors="coerce",
    utc=True
)

news = news.dropna(subset=["published_at"])

print(news.head())

실전에서는 API마다 발행 시각의 시간대와 제공 필드가 다를 수 있으므로 사용 중인 데이터 제공업체의 명세를 반드시 확인해야 합니다.

뉴스 감성점수와 주가를 하나의 거래일로 결합하기

이제 뉴스 한 건 한 건을 그대로 넣기보다는 하루 단위로 집계해보겠습니다.

예를 들어 하루 동안 애플 관련 뉴스가 12건 나왔다면 다음과 같은 변수를 만들 수 있습니다.

  • 뉴스 개수
  • 평균 감성점수
  • 가장 긍정적인 뉴스 점수
  • 가장 부정적인 뉴스 점수
  • 긍정 뉴스 비율

간단한 형태는 다음과 같습니다.

news["date"] = news["published_at"].dt.date

daily_news = (
    news.groupby("date")
    .agg(
        news_count=("sentiment", "count"),
        sentiment_mean=("sentiment", "mean"),
        sentiment_max=("sentiment", "max"),
        sentiment_min=("sentiment", "min")
    )
    .reset_index()
)

daily_news["date"] = pd.to_datetime(daily_news["date"])

price = price.rename(columns={"timestamp": "date"})

dataset = pd.merge(
    price,
    daily_news,
    on="date",
    how="left"
)

dataset[
    ["news_count", "sentiment_mean",
     "sentiment_max", "sentiment_min"]
] = dataset[
    ["news_count", "sentiment_mean",
     "sentiment_max", "sentiment_min"]
].fillna(0)

다만 이 코드는 학습용 구조를 이해하기 위한 기본형입니다.

실전에서는 장 마감 이후 뉴스, 주말 뉴스, 미국 시장 공휴일 등을 다음 거래일 기준으로 재배치하는 과정이 필요합니다.

예를 들어 토요일에 발표된 뉴스는 토요일 주가가 없으므로 다음 거래일과 연결하는 식입니다.

이럴 때 merge_asofforward, backward 같은 옵션을 이용하면 시간 기준 결합을 보다 정교하게 만들 수 있습니다.

파이썬으로 뉴스와 주가 데이터 결합하기, AI 투자모델 데이터셋 만드는 법

AI가 학습할 입력값과 정답값 만들기

데이터를 결합했다고 바로 딥러닝에 넣는 것은 아닙니다.

먼저 모델에게 무엇을 맞히게 할지 결정해야 합니다.

다음 날 상승·하락을 맞히는 모델이라면 다음과 같이 만들 수 있습니다.

dataset["return"] = dataset["close"].pct_change()

dataset["next_return"] = (
    dataset["close"].shift(-1) / dataset["close"] - 1
)

dataset["target"] = (
    dataset["next_return"] > 0
).astype(int)

target = 1이면 다음 거래일 상승, target = 0이면 상승하지 않은 것으로 정의한 것입니다.

여기에 기술적 변수를 추가할 수도 있습니다.

dataset["ma5"] = dataset["close"].rolling(5).mean()
dataset["ma20"] = dataset["close"].rolling(20).mean()

dataset["volatility20"] = (
    dataset["return"]
    .rolling(20)
    .std()
)

dataset["volume_change"] = (
    dataset["volume"]
    .pct_change()
)

그러면 최종 데이터는 대략 이런 구조가 됩니다.

date
close
volume
return
ma5
ma20
volatility20
news_count
sentiment_mean
sentiment_max
sentiment_min
target

이 데이터가 바로 LSTM, GRU, XGBoost, Transformer 등에 넣을 수 있는 AI 투자모델의 기초 재료가 됩니다.

정확도보다 먼저 확인해야 할 데이터 누수 4가지

AI 투자모델을 만들다 보면 높은 정확도가 나오면 먼저 기분이 좋아집니다.

그런데 금융 머신러닝에서는 오히려 너무 좋은 결과가 나오면 데이터 구조부터 다시 살펴볼 필요가 있습니다.

첫째, 미래 가격으로 계산된 변수가 현재 행에 들어가 있지 않은지 확인해야 합니다.

둘째, 기사 발행시각보다 이전의 시장 가격을 예측하면서 해당 기사의 감성점수를 사용하고 있지 않은지 봐야 합니다.

셋째, 전체 데이터의 평균과 표준편차를 먼저 계산한 뒤 Train과 Test를 나누는 식으로 미래 데이터의 통계정보가 학습 구간에 들어가지는 않았는지 확인해야 합니다.

넷째, 주가 데이터를 무작위로 섞어 Train과 Test로 나누는 방식도 주의해야 합니다.

시계열 데이터는 과거로 학습하고 이후 구간에서 테스트하는 구조가 중요합니다. Scikit-learn에서도 시계열 데이터에는 일반적인 무작위 분할 대신 시간 순서를 유지하는 TimeSeriesSplit을 제공하고 있습니다.

from sklearn.model_selection import TimeSeriesSplit

tscv = TimeSeriesSplit(n_splits=5)

for train_idx, test_idx in tscv.split(dataset):
    train = dataset.iloc[train_idx]
    test = dataset.iloc[test_idx]

이 과정까지 제대로 만들어 놓아야 이후 LSTM이나 Transformer 모델 성능을 비교하는 것이 의미가 있습니다.

결국 좋은 AI 투자모델은 좋은 데이터 정의에서 시작됩니다

많은 분들이 주가예측을 공부하면 처음부터 LSTM의 층을 몇 개 쌓을지, Transformer의 Attention Head를 몇 개 사용할지를 고민합니다. 하지만 실제 프로젝트에서 더 중요한 질문은 그보다 앞에 있습니다.

“이 데이터는 예측하는 당시 실제로 알 수 있었던 정보인가?”

“뉴스와 가격의 시간이 정확하게 연결되어 있는가?”

“내가 만든 target이 정말 모델이 예측하려는 투자 상황을 표현하는가?”

애플 AAPL이든 엔비디아 NVDA든 SPY와 QQQ 같은 ETF든 이 원칙은 달라지지 않습니다.

모델을 복잡하게 만드는 것은 그다음입니다.

뉴스 제목을 FinBERT로 감성 분석하고, 가격·거래량·변동성과 결합한 뒤 LSTM이나 Transformer에 입력하면 비로소 앞선 글에서 설명한 ‘텍스트 + 시계열 다중 모달 AI 투자모델’의 형태가 만들어집니다.

다음 단계에서는 여기서 만든 sentiment_mean, 가격, 거래량, 변동성 데이터를 실제 시퀀스 데이터로 변환한 뒤 LSTM에 입력하여 다음 거래일 방향성을 학습시키는 과정을 연결해볼 수 있습니다.

AI 투자모델을 직접 만들고 있다면 정확도가 몇 퍼센트 나왔는지만 보기보다 데이터 생성 시점, 뉴스 연결 기준, target 정의부터 한 번 점검해보세요.

모델 성능을 개선하는 실마리가 알고리즘이 아니라 데이터셋 안에 숨어 있을 수 있습니다.

자주 묻는 질문 Q&A

Q1. 뉴스가 없는 날은 감성점수를 어떻게 처리하나요?

단순한 초기 모델에서는 뉴스 개수를 0, 감성점수를 중립값으로 처리할 수 있습니다. 다만 ‘뉴스가 없었다’는 사실 자체도 정보가 될 수 있으므로 뉴스 존재 여부를 별도의 변수로 두는 방법도 있습니다.

Q2. FinBERT 대신 API에서 제공하는 감성점수를 사용해도 되나요?

기초 실험에는 활용할 수 있습니다. 이후 모델을 고도화할 때 FinBERT 등 금융 특화 언어모델로 동일한 뉴스 데이터를 다시 분석하고 성능 차이를 비교하는 방식이 좋습니다.

Q3. 높은 예측 정확도가 나오면 실제 투자에도 사용할 수 있나요?

정확도 하나만으로 판단하기는 어렵습니다. 거래비용, 슬리피지, 클래스 불균형, 최대낙폭, 시장 국면 변화 등을 함께 검증해야 합니다. 과거 데이터에서 좋은 결과가 나왔다고 향후 투자성과가 보장되는 것은 아닙니다.

Q4. 이 글에 있는 파이썬 코드를 그대로 복사해서 사용해도 되나요?

네, 기본 구조는 실제 실행을 전제로 작성한 코드이기 때문에 복사해서 활용할 수 있습니다. 다만 YOUR_API_KEY 부분에는 본인이 발급받은 API 키를 입력해야 하고, Python·pandas·requests 등 필요한 라이브러리가 설치되어 있어야 합니다.

또한 API 제공업체의 정책이나 응답 형식, 라이브러리 버전이 변경되면 일부 코드는 수정이 필요할 수 있습니다. 특히 뉴스 발행시간, 장 마감 이후 뉴스 처리, 주말·공휴일 데이터 연결은 실제 투자모델에서 중요한 부분이므로 그대로 실전 투자에 적용하기보다 먼저 작은 데이터로 정상 작동 여부를 확인하는 것이 좋습니다.

이 코드는 AI 투자모델을 만들기 위한 학습·실습용 기본 구조이며, 코드가 정상 실행되더라도 예측 정확도나 투자수익을 보장하는 것은 아닙니다.

금융소비자보호 및 투자 유의사항

본 글은 금융·AI 데이터 분석 방법에 관한 정보 제공 및 교육을 목적으로 작성되었으며 특정 금융상품이나 종목의 매수·매도를 권유하기 위한 자료가 아닙니다.

글에서 언급한 Apple(AAPL), NVIDIA(NVDA), Tesla(TSLA), SPY, QQQ 등의 종목 및 ETF는 데이터 분석 방법을 설명하기 위한 예시입니다.

주식 및 ETF 등 금융투자상품은 시장 상황에 따라 원금 손실이 발생할 수 있으며 과거 데이터와 백테스트 결과가 미래 수익률을 보장하지 않습니다. 실제 투자 판단은 투자자의 재무상황, 투자목적, 위험수용능력 등을 충분히 고려해 결정해야 합니다.

금융상품 판매 및 투자상담이 포함되는 경우 실제 게시 전 소속 회사의 광고·심의 기준과 관련 법규를 확인하시기 바랍니다.

금알남

금알남
함께 보면 좋은 글

댓글 0

첫 댓글을 남겨보세요.

광고 차단 알림

광고 클릭 제한을 초과하여 광고가 차단되었습니다.

단시간에 반복적인 광고 클릭은 시스템에 의해 감지되며, IP가 수집되어 사이트 관리자가 확인 가능합니다.