변동성 돌파 전략
특정 조건이 만족되면 매수하고 종가에 매도하는 전략
전일의 변동폭을 이용해 당일의 목표가를 설정하고, 목표가에 도달하면 매수 후 종가에 매도
1. 목표가 설정
거래일의 시가에 전일 변동폭의 절반을 더한 값을 목표가로 설정
import FinanceDataReader as fdr
import numpy as np
# 1. 코스닥 150 데이터 불러오기 ('229200'은 코스닥 150 ETF의 코드)
df = fdr.DataReader('229200')
# 2. 전일 변동폭 계산 (변동폭: 고가 - 저가)
df['변동'] = df['High'] - df['Low']
df['전일변동'] = df['변동'].shift(1)
2. 매수 조건
거래일의 장 중 주가가 목표가를 돌파할 경우 매수
# 3. 목표가 설정 (목표가: 시가 + 전일 변동폭 * 0.5) - 매수조건
df['목표가'] = df['Open'] + df['전일변동'] * 0.5
3. 매도 조건
당일 종가에 매도
4. 수익률, 누적 수익률 계산
매수한 후 종가에서 목표가를 나눈 비율로 수익률을 계산하고, 매수가 일어나지 않으면 수익률은 1로 유지
매일의 수익률을 곱해 누적 수익률을 계산
# 거래일의 고가가 목표가보다 크거나 같다면 그 목표가에서 매수
# 만약 매수가 일어나지 않으면 그대로 유지되므로 그 날의 수익률은 1
df['수익률'] = np.where(df['High'] >= df['목표가'], df['Close'] / df['목표가'], 1)
df['누적수익률'] = df['수익률'].cumprod()
- 수익률 계산:
- 주식을 매수한 후, 매수한 날의 종가에서 목표가를 나누어 수익률을 계산함.
- 예를 들어, 매수한 날의 종가가 100원이고 목표가가 110원이라면 수익률은 110/100=1.1 (즉, 10% 수익)
- 반대로, 목표가보다 종가가 낮다면 수익률이 1보다 작아질 수 있다.
- 주식을 매수한 후, 매수한 날의 종가에서 목표가를 나누어 수익률을 계산함.
- 매수가 없을 경우:
- 특정 날에 매수하는 거래가 발생하지 않았다면, 그날의 수익률은 변동 없이 1로 유지된다는 의미
- 즉, 그날의 수익률이 그대로 유지되며, 아무런 수익도 손실도 발생하지 않는다는 뜻
- 누적 수익률 계산:
- 매일의 수익률을 연속적으로 곱하여 누적 수익률을 계산.
- 예를 들어, 첫날 수익률이 1.1이고, 둘째 날 수익률이 0.95라면 누적 수익률은 1.1×0.95=1.045
- 누적 수익률은 시간이 지날수록 매일의 수익률 변동에 따라 계속 업데이트됨.
- 매일의 수익률을 연속적으로 곱하여 누적 수익률을 계산.
매수했을 때 목표가 대비 수익률을 계산하고, 매수가 없을 때는 수익률을 유지하며, 누적 수익률은 모든 날의 수익률을 곱해서 계산하는 방식입니다.
5. CAGR(연평균 수익률)
주어진 기간 동안의 연평균 수익률을 계산
# 기간 계산 (전체 기간의 일수 계산)
delta = df.index[-1] - df.index[0] # 시작일과 종료일 사이의 차이 계산
year = delta.days / 365 # 연도 수로 변환
# 연평균 수익률 (CAGR) 계산
CAGR = df['누적수익률'].iloc[-1] ** (1/year) - 1
# 누적 수익률의 가장 마지막 값에서 전체 기간 년도 수로 나누어 복리 수익률 계산
# 수익률을 계산할 때는 1을 빼서 100% 대신 0%부터 시작하도록 맞춰줌 -> 순수한 수익률만 남기게 됨.
print(f"연평균 수익률 (CAGR): {CAGR * 100:.2f}%")

→ 연평균 11.7% 수익률
6. 변동성 돌파 전략 vs 단순 보유 비교 시각화
- 변동성 돌파 전략: 이전에 설명한 전략으로, 목표가를 돌파할 때 매수하여 종가에 매도하는 전략
- 단순 보유 전략: 주식을 매수한 후 매매 없이 계속 보유하는 전략입니다. 여기서는 주가의 종가를 기준으로 계산
# 변동성 돌파 전략 > 단순 보유
import matplotlib.pyplot as plt
import koreanize_matplotlib
plt.figure(figsize = (10, 5))
plt.plot(df['누적수익률'])
plt.plot(df['Close'] / df['Close'].iloc[0]) # 종가를 처음 값으로 나누어 비율 계산
plt.grid()
plt.legend(['변동성 돌파 전략', '단순 보유'])
plt.show()

7. 최대 낙폭(MDD, Maximum Drawdown) 계산
- MDD: 자산이 가장 큰 고점에서 얼마나 하락했는지를 측정하는 지표
- 현재 시점의 누적 수익률을 그 시점까지의 최고점(전고점)으로 나누어, 자산이 고점 대비 몇 퍼센트인지 계산
- 즉, 최고점에서 최저점까지의 손실률을 의미 ( 자산의 손실 위험을 측정 )
df['전고점'] = df['누적수익률'].cummax()
# 누적된 수익률에서 최고점을 계속 추적
# 현재 시점에서 몇 퍼센트 정도 하락했는지
df['DD'] = (1 - df['누적수익률'] / df['전고점']) * 100
df['DD'].max() # 최대 낙폭 MDD

→ 즉, 자산이 고점에서 가장 크게 하락한 비율
→ 자산의 누적 수익률에서 전고점을 계산하고, 그 전고점 대비 자산이 얼마나 하락했는지를 퍼센트로 나타냄.
마켓 타이밍 전략
- 이동평균선(MA) : 주가의 단기나 중기 변동성을 보정해주며, 추세를 확인할 수 있는 지표
- 10일 이동평균선을 기준으로 상승 구간에서만 투자를 하고, 하락 구간에서는 투자를 하지 않는 전략
- 이동 평균선, RSI, 매매 신호 등을 활용하여 투자 기회를 모색하고, XGBoost 모델을 통해 각 ETF의 성과를 예측
- 최종적으로 예측된 종목과 확률을 출력하여 사용자에게 추천
# 마켓 타이밍: 하락 구간일 땐 무시하고 상승 구간에서만 투자
# 10일 이동평균선 위에 있을 때만 투자한다고 가정
df['MA10'] = df['Close'].rolling(window=10).mean() # 10일 이동 평균선 계산
# 시가가 전날 이동 평균선 위에 있을 때(이동평균선 보다 높을 때) 매매 신호 생성
df['매매신호'] = df['Open'] > df['MA10'].shift(1) # 시가가 전날(shift(1))의 이동평균선보다 높을 때 매매 신호를 생성
# (df['매매신호'] == 1)는 상승 추세가 이어질 가능성이 높을 때 매수 결정을 하겠다는 의미
# 목표가에 도달했을 때 수익률 계산
df['수익률2'] = np.where((df['매매신호'] == 1) & (df['High'] >= df['목표가']), df['Close'] / df['목표가'], 1)
# (df['매매신호'] == 1): 매매 신호가 발생한 경우(즉, 상승 구간에서만).
# (df['High'] >= df['목표가']): 그날의 최고가가 설정한 목표가에 도달했을 때.
# df['Close'] / df['목표가']: 목표가에 도달한 경우 종가를 목표가로 나누어 수익률을 계산
# 1: 매매 신호가 없거나 목표가에 도달하지 않았을 경우, 수익률은 1(즉, 변동 없음)로 설정
# 누적 수익률 계산
df['누적수익률2'] = df['수익률2'].cumprod() # 수익률을 누적 곱으로 계산
df['누적수익률2'].iloc[-1] # 마지막 누적 수익률 확인 (예: 1.56배)

→ 처음 자산 대비 1.56배로 불어났음을 의미합니다(56% 수익).
# 최대 낙폭(MDD) : 최대 고점에서 최대 손실까지의 비율 측정
# (투자 기간 동안 자산의 고점에서 가장 크게 하락한 정도)
df['전고점2'] = df['누적수익률2'].cummax() # 누적 수익률의 최대값 계산
df['DD2'] = (1 - df['누적수익률2'] / df['전고점2']) * 100 # 최대 손실 계산 (Drawdown)
# (1 - df['누적수익률2'] / df['전고점2']): 현재 누적 수익률이 전고점에서 얼마나 하락했는지 계산
# 0.25가 나온 경우 25% 손실을 의미, *100을 해서 백분율로 표현
df['DD2'].max() # 최대 손실값 (최대 낙폭) (MDD) 확인

→ 투자 기간 동안 자산의 누적 수익률이 가장 높았던 시점에서 14.36% 하락한 적이 있다
XGBoost 모델을 사용하여 ETF(상장지수펀드)의 수익률을 예측
ETF 데이터로부터 RSI와 가격 변화 등을 사용하여 XGBoost 모델을 학습시키고,
예측된 ETF 종목 중 확률이 0.6 이상인 종목을 추출하여 리스트에 저장
# ETF 수익률 예측 모델링: XGBoost를 사용한 기계 학습 모델
import os
import pandas as pd
from tqdm import tqdm
# RSI: 상대 강도 지수 (0~100)
X = [] # 입력 데이터 (특징, Feature)
Y = [] # 출력 데이터 (라벨, Label)
window = 100 # 윈도우 크기 설정
# ETF 데이터 파일 처리
for i in tqdm(os.listdir("ETFs")): # "ETFs" 폴더에 있는 파일을 순회
name = i.split(".")[0] # 파일 이름에서 확장자를 제외한 이름 추출
df = pd.read_csv("ETFs/" + i) # CSV 파일 읽기
df['Date'] = pd.to_datetime(df['Date']) # 날짜 열을 datetime 형식으로 변환
df = df.set_index("Date") # 날짜를 인덱스로 설정
data = df[['Change', 'RSI', 'Close']].values # 필요한 열을 numpy 배열로 변환
# 윈도우 크기만큼 데이터 슬라이싱
for j in range(len(data) - window):
try:
a = data[j:j + window, :-1].flatten() # 윈도우 크기만큼 데이터를 평탄화
b = int(data[j + window - 1, -1] * 1.01 < data[j + window, -1]) # 목표가 도달 여부 (종가가 목표가에 도달했는지 확인)
# data[j:j + window, :-1]: 현재 인덱스 j부터 윈도우 크기만큼의 데이터(행)와 마지막 열(여기서는 'Close')을 제외한 첫 두 열('Change', 'RSI')을 선택
# .flatten(): 선택된 2D 배열을 1D 배열로 평탄화. 이렇게 하면 윈도우 크기(100)만큼의 데이터가 한 줄로 나열된 형태가 됨(원래는 행과 열이 있는 2D 배열)
# 다음 날의 'Close' 값이 목표가의 1% 상승한 값보다 크면 1, 그렇지 않으면 0
except:
continue # 예외 발생 시 다음 반복으로 넘어감
X.append(a) # 입력 데이터 추가
Y.append(b) # 출력 데이터 추가
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
from xgboost import XGBClassifier
# 데이터 분할: 훈련 데이터와 테스트 데이터로 나누기
train_x, test_x, train_y, test_y = train_test_split(X, Y)
# XGBoost 모델 초기화 및 훈련
model = XGBClassifier()
model.fit(train_x, train_y)
# 테스트 데이터에 대한 예측 수행
pred = model.predict(test_x)
# 예측 결과 평가
report = classification_report(test_y, pred)
print(report)

total = [] # 예측 결과를 저장할 리스트
# 각 ETF에 대해 최신 데이터를 이용한 예측 수행
for i in os.listdir("ETFs"):
name = i.split(".")[0] # 파일 이름에서 주식 이름 추출
df = pd.read_csv("ETFs/" + i) # CSV 파일 읽기
df['Date'] = pd.to_datetime(df['Date']) # 날짜 열을 datetime 형식으로 변환
df = df.set_index("Date") # 날짜를 인덱스로 설정
data = df[['Change', 'RSI', 'Close']] # 필요한 열 추출
today_data = data.iloc[-window:][['Change', 'RSI']].to_numpy().flatten() # 최근 윈도우 데이터 추출
try:
pred = model.predict_proba([today_data])[0][1] # 확률 예측
# XGBoost 모델에 today_data를 입력하여 각 클래스(여기서는 목표가 도달 여부 0 또는 1)에 대한 확률을 예측
# [0][1]: 반환된 배열의 첫 번째 요소(첫 번째 데이터 포인트에 대한 결과)에서 두 번째 값(1에 대한 확률)을 선택
if pred > 0.6: # 예측 확률이 0.6 이상일 경우
total.append([name, pred]) # 결과 저장
except:
continue # 예외 발생 시 다음 반복으로 넘어감
total # 예측된 종목과 확률 리스트 출력

from datetime import datetime
today = str(datetime.today().date())
t = f'{today} 기준 종목 예측\n\n'
for i in total:
t += f'추천종목 : {i[0]} ({i[1]:.2f})\n'
print(t)

LINE Notify를 통한 블로그 글 자동 생성 및 전송
# LINE Notify 패키지 설치
#!pip install line_notify
# LINE Notify를 사용하기 위한 import
from line_notify import LineNotify
# 사용자 고유의 LINE Notify API 키를 설정
my_key = 'b0QYTeG7lM70Os3UkwQZddYRadLweOghfhjgsf4TpU5xAFgzSzcKtzrM'
my_line = LineNotify(my_key) # LineNotify 객체 생성
# t 변수를 LINE으로 전송
my_line.send(t) # 변수 t에 저장된 내용을 LINE으로 전송
# OpenAI API를 사용하기 위한 import
from openai import OpenAI
# OpenAI API 키 설정
ai = OpenAI(api_key='sk-isBPj62UYX1yNyH8EBndF6Fbggi0CJovQihtmaeIovT3BlbkFJJsnGigBL5gR-DG8rTwYfZT2CZ9QQI9rujltA-g_NcA')
# OpenAI에게 전달할 메시지를 설정
box = [
{'role': 'system', 'content': '내가 글을 보여주면 그 글 내용을 블로그에 돌아갈 수 있게 블로그 글로 작성해줘'}, # 시스템 역할 설정
{'role': 'user', 'content': t} # 사용자 역할로 변수 t의 내용을 전달
]
# OpenAI GPT 모델을 사용하여 메시지를 생성
model = ai.chat.completions.create(model='gpt-4o', messages=box)
# 생성된 내용을 LINE으로 전송
my_line.send(model.choices[0].message.content) # 생성된 블로그 글 내용을 LINE으로 전송
# t 변수에 생성된 내용을 저장
t = model.choices[0].message.content
# 생성된 블로그 글 출력
print(t) # 콘솔에 생성된 내용을 출력
inf 문제 발생한 경우
import os
import pandas as pd
from tqdm import tqdm
# 데이터 및 레이블을 저장할 리스트
X = [] # 문제지
Y = [] # 정답지
window = 50 # 윈도우 크기
# ETF 파일들을 처리
for i in tqdm(os.listdir("ETFs")):
name = i.split(".")[0]
df = pd.read_csv("ETFs/" + i)
df['Date'] = pd.to_datetime(df['Date'])
df = df.set_index("Date")
# 필요한 변수 선택
data = df[['Change', 'RSI', 'Close']].values # np.array 형태로 변환
# 윈도우 기반 데이터 생성
for j in range(len(data) - window):
try:
a = data[j:j + window, :-1].flatten() # 평탄화
b = int(data[j + window - 1, -1] * 1.03 < data[j + window, -1]) # 3% 이상 상승 여부
except:
continue
X.append(a)
Y.append(b)
print("데이터 준비 완료!")
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
from xgboost import XGBClassifier
# 데이터 처리
all_data = []
for i in tqdm(os.listdir("ETFs")):
name = i.split(".")[0]
df = pd.read_csv("ETFs/" + i)
df['Date'] = pd.to_datetime(df['Date'])
df = df.set_index("Date")
# 파생변수 생성
df = create_technical_indicators(df)
# 모든 데이터프레임 저장
all_data.append(df)
X = []
Y = []
window = 50
# X, Y 데이터 생성
for df in all_data:
data = df[['변동성 지표','전일변동성','RSI', 'Volume Ratio', 'Momentum', 'Close']].values
for j in range(len(data) - window):
try:
a = data[j:j + window].flatten() # 평탄화
if np.inf in a:
continue
b = int(data[j + window - 1, -1] * 1.01 < data[j + window, -1]) # 1% 이상 상승 여부
except:
continue
X.append(a)
Y.append(b)
if np.inf in a:
continue
→ 이 부분 추가
# 데이터 분할 및 모델 학습
train_x, test_x, train_y, test_y = train_test_split(X, Y, test_size=0.2, random_state=42)
# 모델 학습
model = XGBClassifier()
model.fit(train_x, train_y)
# 성능 평가
pred = model.predict(test_x)
report = classification_report(test_y, pred)
print(report)
'파이썬 & 머신러닝과 딥러닝' 카테고리의 다른 글
RSI, 볼린저 밴드를 활용한 투자 전략 (EMA, finterstellar 라이브러리 사용) (3) | 2024.10.29 |
---|---|
강화학습 - 밴디트 알고리즘(슬롯 확률 시뮬레이션, 상승장 종목 찾기) (2) | 2024.10.24 |
주식 포트폴리오 분석을 위한 시뮬레이션 - 몬테카를로 시뮬레이션, 샤프지수, KOSPI 통계분석, 종목간 상관관계, 최대 낙폭, 할로윈 투자 전략 (2) | 2024.10.21 |
금융 데이터 전처리 (주식 데이터월별, 분기별, 주별 데이터 집계, 거래량 변화 탐지, 모멘텀 전략 및 수익률 계산, 볼린저 밴드) (1) | 2024.10.21 |
Okt 형태소 분석기(빈도기반), 워드클라우드, 상대빈도분석(오즈비 분석), TF-IDF 분석 (1) | 2024.10.14 |