Skip to main content

Numerai Signals 시작하기: 감성 분석

이 보고서는 Numerai Signals 토너먼트를 위해 Stock News API와 FinBERT를 사용하는 방법을 보여줍니다 이 글은 AI 번역 기사입니다. 오역이 있다면 댓글로 알려주세요
Created on September 12|Last edited on September 12
Numerai Signals

Kaggle 노트북→

소개

이 글에서는 Numerai Signals 토너먼트를 소개하고, 감성 분석 예제를 통해 시작하는 방법을 보여드립니다. 구체적으로는 뉴스 헤드라인을 활용해 감성 점수에 기반한 주식 순위를 예측하는 과정을 살펴보겠습니다. Numerai 전반에 대한 소개는 제 다른 블로그 글을 참고하세요. 여기.

Numerai Signals란 무엇인가요?

뉴머라이 는 전 세계 데이터 과학자들의 예측에 기반해 운영되는 크라우드소싱 AI 헤지 펀드입니다. The 클래식 Numerai 토너먼트 은 익명화된 데이터를 제공하여 머신러닝으로 모델링할 수 있게 해줍니다. Numerai Signals는 이 아이디어를 확장해 원하는 어떤 데이터든 사용할 수 있도록 합니다. 이를 통해 데이터 과학자들은 데이터 소스와 모델링 기법 모두에서 창의성을 발휘할 수 있습니다.


Numerai Signals의 비전을 이해하려면 다음을 확인해 보세요 이 짧은 영상:


그럼 이제 감성 분석을 활용해 Numerai Signals에 제출할 수 있는 전체 파이프라인을 살펴보겠습니다!
전체 코드는 또한 Kaggle 노트북우리는 어떤 데이터 사이언스 파이프라인에서든 가장 중요한 부분부터 시작하겠습니다. 바로, 양질의 데이터를 확보하는 것입니다! 이를 위해 주식 뉴스 기사에 접근할 수 있는 깔끔한 API(애플리케이션 프로그래밍 인터페이스)를 사용하겠습니다.

전체 코드 →

Stock News API (stocknewsapi.com)

Stock News API (stocknewsapi.com) 는 광범위한 주식 종목을 기반으로 뉴스 기사를 손쉽게 가져올 수 있는 온라인 서비스입니다. 약 10개의 다양한 뉴스 소스에서 기사를 수집하고, 데이터 파싱을 쉽게 할 수 있도록 추가 정보를 제공합니다. 매주 각 종목의 최신 기사를 깔끔한 JSON 형식으로 받아올 수 있으며, 이를 CSV로 변환해 데이터 결합에 사용할 것입니다. Stock News API는 14일 무료 체험 프로그램 API 호출 100회까지 사용할 수 있습니다. 그 이후에는 가입하여 월 $20에 월 20,000회 API 호출API는 다음 뉴스 소스에서 데이터를 가져옵니다:



데이터 수집은 Python과 requests 라이브러리를 통해 수행할 수 있습니다. 접근하려면 Stock News API의 API 키/토큰이 필요합니다. 예를 들어, 다음은 최신 뉴스 기사를 가져오는 방법입니다. 테슬라 주식 (TSLA):
import requests
api_key = "YOUR_API_KEY_HERE"
api_request = f"https://stocknewsapi.com/api/v1?tickers=TSLA&items=1&token={api_key}"
data = requests.get(api_request).json()['data']
print(data)
JSON 출력은 대략 다음과 같습니다:
[
{
"news_url": "https://www.barrons.com/articles/fund-bought-tesla-apple-microsoft-stock-sold-att-51613063598",
"image_url": "https://cdn.snapi.dev/images/v1/a/7/im-298821.jpg",
"title": "A Huge Fund Bought Tesla, Apple, and Microsoft Stock. Here's What It Sold.",
"text": "Dutch pension fund PGGM initiated a position in EV-giant Tesla, bought more Apple and Microsoft stock, and sold AT&T stock in the fourth quarter.",
"source_name": "Barrons",
"date": "Sun, 14 Feb 2021 07:00:00 -0500",
"topics": ["paywall"],
"sentiment": "Neutral",
"type": "Article",
"tickers": [
"AAPL",
"MSFT",
"TSLA",
"T"
]
}
우리 문제에서 우리가 관심 있는 것은 다음과 같습니다:
  • “title”를 사용해 감성 예측을 수행합니다.
  • 데이터 조인을 위해서는 “date”가 필요합니다. 날짜에는 시간대 정보가 포함되어 있으며(이 경우 GMT-5), 뉴스 소스마다 달라질 수 있습니다.
  • 예측을 올바른 주식과 연결하기 위한 “tickers”
API에서 제공하는 자체 감성 분류는 “Positive”, “Neutral”, “Negative”일 수 있습니다. 문제를 더 다루기 쉽게 만들기 위해 “Neutral”로 분류된 기사는 필터링하겠습니다. 여기서의 가정은 극성(긍정/부정)이 뚜렷한 기사들이 단기 시장 변동을 예측하는 데 가장 중요하다는 것입니다.

데이터 수집

우리가 필요한 관련 데이터는 지난주 뉴스 기사입니다. 다음과 같이 지정하여 최근 7일로 필터링합니다. &days=7 API 호출에서 다음 조건을 지정하고, 다음과 같은 기사로 필터링합니다 &type=article.
또한 API는 단일 호출당 최대 50개의 기사로 제한합니다 (&items=50). 한 주 동안 단일 주식 티커에 대해 50개가 넘는 뉴스 기사가 있을 수 있으므로, 그 경우 해당 주의 모든 기사를 가져오거나 우리가 지정한 한도에 도달할 때까지 추가 호출을 수행합니다. 불필요한 API 호출을 줄이기 위해 이 한도를 설정합니다.


앞서 계산한 날짜를 사용하여 Tesla Inc. (TSLA)의 모든 뉴스 기사를 가져오는 루프는 대략 다음과 같은 형태가 됩니다:
import requests
import pandas as pd

page_cutoff_point = 10
dfs = []
i = 1
while i <= page_cutoff_point:
api_request = f"https://stocknewsapi.com/api/v1?tickers=TSLA&items=50&type=article&page={i}sortby=rank&days=7&token={api_key}"
data = requests.get(api_request).json()['data']
df = pd.DataFrame(data)
if df.empty:
break
dfs.append(df)
i += 1
tesla_df = pd.concat(dfs)
최종 Pandas DataFrame은 모든 관련 주식 티커에 대해 이 루프를 실행하고 결과를 이어 붙여(concatenate) 얻을 수 있습니다.

전체 코드 →

데이터 정리


감성 분석 예측을 위한 데이터를 준비하기 위해 일련의 단계를 거칩니다. 높은 수준에서 다음과 같은 전처리 작업을 수행합니다:
  1. 불필요한 열 제거하기 ('text', 'news_url', 'image_url', 'topics' 그리고 'source_name')
  2. 다음 조건을 만족하는 모든 기사를 필터링합니다 "Neutral" 감성.
  3. 모든 타임스탬프를 UTC(협정 세계시)로 변환하여 모든 행이 공통의 datetime 형식
각 종목 티커별로 모든 뉴스 헤드라인을 포함하는 고유한 행을 하나씩 만들고자 합니다. 이를 위해 티커와 주차 단위로 데이터를 집계합니다. 일부 뉴스 기사는 여러 종목 티커를 함께 언급하므로, 먼저 집합 교집합을 통해 Numerai Signals에 해당하는 모든 관련 티커를 찾습니다. 겹치는 2090개 티커는 이미 계산되어 있으며 pickle 파일로 저장되어 있습니다. 여기하지만 아래 코드를 사용하면 직접 계산할 수 있습니다. 신규 종목의 상장과 상장폐지가 수시로 발생하므로, 교집합에 해당하는 종목 티커는 정기적으로 다시 계산하는 것이 좋습니다.
relevant_tickers=stock_news_tickersnumerai_tickersrelevant\_tickers = stock\_news\_tickers \cap numerai\_tickers
import pickle
import requests
import pandas as pd

# Get all tickers available in stocknewsapi.com
ticker_request = f"https://stocknewsapi.com/api/v1/account/tickersdbv2?token={api_key}"
json_data = requests.get(ticker_request).json()['data']
stock_news_tickers = []
for row in json_data:
stock_news_tickers.append(row['ticker'])

# Get all Numerai Signals tickers
ticker_df = pd.read_csv("https://numerai-signals-public-data.s3-us-west-2.amazonaws.com/signals_ticker_map_w_bbg.csv")
numerai_tickers = set(ticker_df['ticker'])

# Compute intersection
relevant_tickers = list(set(stock_news_tickers).intersection(numerai_tickers))


특정 티커를 언급한 모든 뉴스 헤드라인을 집계하면, 각 티커마다 한 행을 얻고 헤드라인은 다음과 같이 구분된 형태로 정리됩니다 [SEP][SEP]  토큰으로, 뉴스 헤드라인 입력을 배치 처리하는 데 사용합니다.
data.loc[:, 'title'] = data['title'] + " [SEP] "
마지막으로, 제출 단계에서 필요하므로 Numerai 티커를 기준으로 새로운 DataFrame을 병합해 Bloomberg 형식의 티커를 가져옵니다.
# Aggregate news headlines for each ticker
dfs = []
for ticker in relevant_tickers:
aggregated = data[data['tickers'].apply(lambda x: ticker in x)].resample("W-fri", on='date').sum()
aggregated = aggregated.drop("tickers", axis=1)
aggregated['ticker'] = ticker
aggregated = aggregated.drop_duplicates("ticker", keep='last')
if aggregated.empty:
continue
dfs.append(aggregated)
new_df = pd.concat(dfs)
new_df['title'] = new_df['title'].astype(str)
merged = new_df.merge(ticker_df, on='ticker')
merged = merged.drop("yahoo", axis=1).dropna()

전체 코드 →

추론 (FinBERT)

이제 드디어 머신러닝 부분을 시작할 준비가 되었습니다!
FinBERT 는 석사 학위 논문의 일부로 개발된 트랜스포머 모델입니다 도구 아라즈 금융 뉴스 기사만으로 완전히 학습되었습니다. FinBERT는 금융 뉴스로 학습되었기 때문에 금융 도메인의 자연어 처리(NLP) 과제를 해결하는 데 적합한 모델입니다. FinBERT는 다음을 기반으로 한 언어 모델입니다 BERT(양방향 인코더 표현) 2018년에 Google이 개발한 모델입니다. 트랜스포머 모델에 대한 자세한 설명은 제 글을 확인해 보세요. "트랜스포머 깊이 파보기" 기사.
우리는 …을 사용할 것입니다 Hugging Face Transformers 라이브러리와 PyTorch 모든 헤드라인에 대한 감성 예측을 얻기 위해서입니다. 구체적으로 우리는 사전 학습된 FinBERT 모델 Hugging Face 웹사이트에서 이용할 수 있습니다.


전체적으로 파이프라인은 다음 단계를 거칩니다:
  1. GPU 메모리가 부족하지 않도록 뉴스 헤드라인을 배치로 처리하세요.
def _chunks(lst, n):
""" Yield n-sized chunks from list. """
for i in range(0, len(lst), n):
yield lst[i:i + n]

batch_size = 8
for row in data:
ticker_headlines = row.split(" [SEP] ")[:-1]
.
for batch in _chunks(ticker_headlines, batch_size):
.
2. 각각에 대해 batch, 헤드라인을 토크나이즈하세요. input_ids 그리고 attention_mask FinBERT 모델의 입력이 됩니다.
import torch
from transformers import AutoTokenizer
batch = ["The stock will go up in the upcoming week!", "Earnings are down for Q3"]
tokenizer = AutoTokenizer.from_pretrained("ipuneetrathore/bert-base-cased-finetuned-finBERT")
encoded = tokenizer(batch, add_special_tokens=True, max_length=200,
padding='max_length',return_attention_mask=True,
return_tensors='pt', truncation=True)
input_ids = torch.cat([encoded['input_ids']], dim=0).to('cuda')
attention_mask = torch.cat([encoded['attention_mask']], dim=0).to('cuda')
3. 사전 학습된 FinBERT 모델에서 소프트맥스 활성값을 얻습니다.
import torch.nn.functional as F
from transformers import AutoModelForSequenceClassification
model = AutoModelForSequenceClassification.from_pretrained("ipuneetrathore/bert-base-cased-finetuned-finBERT").eval().to('cuda')
model_output = model(input_ids, token_type_ids=None, attention_mask=attention_mask)
logits = model_output[0]
softmax_output = F.softmax(logits, dim=1).cpu().detach().numpy()
4. 각 종목 티커에 대한 모든 예측값으로부터 감정 점수를 계산하고, 그 평균을 산출합니다.
score(T)=1ni=1npositiveinegativeiscore(T) = \frac{1}{n} \sum_{i=1}^{n} positive_i - negative_i

어디에서 TT 는 특정 종목 티커를 나타내며 nn 헤드라인의 총량
단일 감정 점수의 경우:
sentiment_score = softmax_output[:, 2] - softmax_output[:, 0]
특정 티커에 대한 모든 감정 점수를 수집한 다음에는:
mean_score = np.array(sent_scores_ticker).ravel().mean()
5. 감정 점수의 범위는 다음과 같습니다 [1...1][-1...1], 그러나 Numerai Signals는 예측값을 다음 범위로만 허용합니다 [0...1][0...1]따라서 모든 종목 티커의 감정 점수 예측값을 다음 범위로 스케일링합니다 [0...1][0 ... 1] 사용하여 scikit-learn의 MinMaxScaler.
from sklearn.preprocessing import MinMaxScaler
def scale_sentiment(sentiments):
""" Scale sentiment scores from [-1...1] to [0...1] """
mm = MinMaxScaler()
sent_proc = np.array(sentiments).reshape(-1, 1)
return mm.fit_transform(sent_proc)

전체 코드 →

제출

이제 예측값을 얻었으니 거의 Numerai Signals에 제출할 준비가 되었습니다! DataFrame을 마무리하기 위해 해당 예측이 “live” 데이터(예: 다가오는 주)를 위한 것임을 나타내는 열을 추가합니다. 또한 다음 금요일을 표시하는 날짜 열을 만듭니다. 마지막으로 DataFrame을 CSV로 저장한 뒤 Numerai의 API를 사용해 업로드합니다.
import numerapi
from datetime import datetime
from dateutil.relativedelta import relativedelta, FR

# API settings for submitting to Numerai
NMR_PUBLIC_ID = "YOUR PUBLIC KEY"
NMR_SECRET_KEY = "YOUR SECRET KEY"
MODEL_NAME = "YOUR MODEL NAME"
SUB_PATH = "finbert_submission.csv"

# Initialize API with API Keys and add data_type column
NAPI = numerapi.SignalsAPI(NMR_PUBLIC_ID, NMR_SECRET_KEY)
final_df.loc[:, "data_type"] = "live"
# Add date column denoting last Friday
friday = int(str((datetime.now() + relativedelta(weekday=FR(-1))).date()).replace("-", ""))
final_df["friday_date"] = friday

# Save final DataFrame to CSV and upload predictions
cols = ["bloomberg_ticker", "friday_date", "data_type", "signal"]
final_df[cols].reset_index(drop=True).to_csv(SUB_PATH, index=False)
model_id = NAPI.get_models()[MODEL_NAME]
NAPI.upload_predictions(SUB_PATH, model_id=model_id)
제출한 후에는 모델 페이지에서 제출 내용을 확인할 수 있습니다 signals.numer.ai/tournament이미지처럼 보이면 됩니다. 제출물에는 최소 10개의 주식 티커가 포함되어야 하며, 매주 제출할 수 있는 국제 주식은 총 약 5,300개입니다. 신규 상장과 상장 폐지에 따라 이 총수는 주마다 달라질 수 있습니다.

signals.numer.ai/tournament에서의 제출 예시 검증

전체 코드 →

평가 및 스테이킹

참고로 이 파이프라인의 예측값은 우리가 지정한 모든 종목에 대한 순위를 나타냅니다. 0에 가까우면 다음 주에 해당 종목이 하락할 것으로, 1에 가까우면 상승할 것으로 본다는 뜻입니다. Numerai는 모든 사용자의 예측을 집계해 주식 포트폴리오를 구성합니다. 시그널 점수가 0에 가까운 종목은 매도하고, 1에 가까운 종목은 매수합니다. 사용자는 다음 기준에 따라 평가됩니다 스피어만 상관 점수 다음 주에 그들의 예측이 가지게 될 영향입니다. 또한 신호의 고유성은 다음 기준에 따라 평가됩니다 메타 모델 기여도(MMC) 점수. Numerai는 사용자가 자신의 예측에 스테이킹할 수 있도록 합니다 Numeraire (NMR) 암호화폐 그리고 사용자는 이러한 지표에 따라 Numeraire를 획득하거나 잃게 됩니다.
Numerai에 참여하기 위한 배경 정보와 일반적인 조언은 제 안내서를 참고하세요 Numerai 관련 기사. 또한 확인하세요 Numerai Signals 문서 Numerai Signals에 참여하는 방법에 대한 자세한 설명은 다음을 참고하세요.
Numerai Signals 예측 예시


여기까지입니다! 이 글이 Numerai Signals를 시작하는 데 흥미를 느끼게 해 주었길 바랍니다. 또한 이 글을 통해 새로운 개념도 하나쯤 배우셨기를 바랍니다. 더 알아보고 싶다면 아래의 자료 섹션에서 Numerai Signals 소개, 데이터 소스, 추천 논문, 그리고 Numerai 커뮤니티 링크를 확인해 보세요.
질문이나 피드백이 있다면 아래에 자유롭게 댓글을 남겨 주세요. 트위터로도 연락하실 수 있습니다. @carlolepelaars.

전체 코드 →

자료

학습 자료

데이터 소스

추천 논문

커뮤니티


이 글은 AI로 번역된 기사입니다. 오역이 있을 수 있으니 댓글로 알려주세요. 원문 보고서는 아래 링크에서 확인할 수 있습니다: 원문 보고서 보기