미국 주식을 자주 하는 개발 공부를 하고 있는 사람입니다. 매우 부족한 코드이지만 잘 봐주시면 감사하겠습니다ㅠ 앞으로의 글은 편의를 위해 반말을 사용하고 있습니다 양해 부탁드립니다ㅠㅠ
스윙(매수와 매도를 반복하는 방식)으로 주식을 많이 하는 나는 내가 정한 조건에 충족이 되면 매수 타이밍을 알려주는 프로그램이 있으면 좋겠다는 생각에 이 프로젝트를 시작하게 되었다.
물론 기존에 주가 알림 기능은 HTPS나 어플에서 제공하고 있다. 하지만 이러한 알림들은 단순히 주가가 5% 상승이나 하락하거나 일정 가격에 도달했을 때 알림을 주는 간단한 설정만 가능하다.
그래서 나는 내가 주식을 매수할 때 보는 여러가지 보조 지표가 모두 조건에 충족이 될 때 알림을 주는 프로그램을 만들고 싶었다.
일단 이 프로젝트를 만들 가치가 있는지 확인하고 싶었다. 내가 생각하는 조건대로 했을 때 원하는 만큼의 수익률이 나와야 만들 가치가 있다고 생각이 들었다. 그래서 일단은 수익률을 확인하는 소스부터 만들었다.
일단 어떤 종목을 사용할지 정해야 했다. 선택한 종목은 내가 제일 스윙을 많이 하는 주식인 "FNGU"이다.
이 종목을 고른 이유는 다음과 같다.
그리고 무엇보다도 내가 제일 많이 거래하는 종목이여서 익숙하기 때문이다.
그리고 이제 매수와 매도 조건을 정해야 하는데. 그러기 위해선 1년간의 주가를 보며 어느 시점에 주식을 사고 파는 것이 좋았는지 확인했다.
동그라미를 친 부분이 매수 또는 매도를 하기 좋은 지점이다. 이러한 지점들에서 보조 지표를 분석하여 매수와 매도의 조건을 다음과 같이 정했다(보조 지표에 대한 설명은 생략하겠다).
그러면 이제 실제로 위에서 정한 조건대로 매매를 했을 때 수익률이 얼마나 나는지 확인해보는 소스 코드를 작성해보자.
import yfinance as yf
ticker = 'FNGU'
data = yf.download(ticker,start = '2021-01-01') # 시작날을 입력 받으면 그 이후 33일 정도 기록누락 발생
if start_day not in data.index:
print('해당 날은 주가 기록이 없습니다.')
data = data.reset_index() # 날짜 인덱스를 열로 바꾸고 숫자 인덱스 사용
일단 yfinace
n 모듈을 활용하여 주가를 가져오는 소스 코드를 만들었다.
그리고 주가 시작일을 '2021-01-01'로 하였다. 가져올 주가 데이터는 실제로 매매를 시작하기 3개월 정도 앞으로 하는것이 좋다. 왜냐하면 우리는 주가의 가격 데이터만 가져오고 보조 지표는 가져오지 않기 때문이다.
이말은 즉, 우리가 직접 주가 데이터를 활용하여 보조 지표를 계산해야 하는데 그렇게 되면 가져온 시작일로부터 어느정도의 주가 정보 데이터가 쌓여야 'RIS, MFI, MACD'등등의 보조 지표를 계산할 수 있기 때문이다.
그리고 yfinance
로 가져오는 데이터 프레임을 확인해보면 다음과 같은데
보다시피 인덱스가 날짜로 되어있다. 나는 앞으로 매매를 할 때 어느 날이 더 앞에 있고 더 뒤에 있는지확인하기 위해서 날짜를 열로 바꾸고 새롭게 인덱스를 만들기 위해 data = data.reset_index()
와 같이 하였다.
마지막으로 나중에도 데이터 프레임을 가져오는 기능을 여러번 사용할 가능성이 있어서 위 기능을 다음과 같이 함수로 정의하고 함수를 호출하는 형식으로 하였다.
def return_dataframe(ticker, start_day):
""" 사용할 데이터 프레임을 리턴하는 함수"""
data = yf.download(ticker,start = '2021-01-01') # 시작날을 입력 받으면 그 이후 33일 정도 기록누락 발생
if start_day not in data.index:
print('해당 날은 주가 기록이 없습니다.')
return
data = data.reset_index() # 날짜 인덱스를 열로 바꾸고 숫자 인덱스 사용
fngu_data = return_dataframe('FNGU', '2021-04-01')
그러면 이제 가져온 데이터 프레임을 활용하여 주가 정보를 확인해보자
보조 지표는 ta
라이브러리를 가져오면 쉽게 계산할 수 있다. 내가 사용할 보조 지표는 여러개가 있었는데 일단 간단하게 RSI, MFI, MACD
만 사용하였다. 아무튼 다음과 같이 하면 이러한 보조 지표를 계산할 수 있다.
import ta
rsi = ta.momentum.rsi(data['Close'])
mfi = ta.volume.money_flow_index(data['High'],data['Low'],data['Close'],data['Volume'])
macd = ta.trend.macd(data['Close'])
macd_sig = ta.trend.macd_signal(data['Close'])
주가의 기준은 종가(Close price)로 하였다.
그러면 이제 매수 조건과 매도 조건에 충족되는 데이터 프레임만 추출하여 변수에 저장해보자
buy_condition = (rsi <= 35) & (mfi <= 35) & (macd - macd_sig < -0.5) # 매수하는 조건
sell_condition = (rsi >= 65) & (mfi >= 65) & (macd - macd_sig >= 0.6) # 매도하는 조건
buy_data = data[buy_condition] # 매수할 날에 대한 데이터 프레임
sell_data = data[sell_condition] # 매도할 날에 대한 데이터 프레임
이게 시작일부터 매매를 시작하면 되는데 여러번 반복적으로 매매를 하는 것이기 때문에 반복문을 돌아야한다고 생각하여 반복문을 활용할 생각을 했다. 일단 두 데이터프레임을 확인해 봤다.
아래 사진중 첫번째는 매수를 해야하는 날이고 두번째는 매도를 해야하는 날이다.
여기서 선택을 해야했다. 반복문을 두개 사용해서 각자의 데이터 프레임에서 돌아야할지 아니면 두 데이터프레임을 합쳐서 하나의 반복문으로 돌아야할지.
나는 후자를 선택했다. 그 이유는 다음과 같다.
따라서 일단 데이터 프레임을 합쳤다. 그리고 컴퓨터가 반복문을 돌면서 매수인지 매도인지를 확인할 수 있게 하기 위해 매수/매도 를 알려주는 열을 추가하고 마지막으로 인덱스 순서대로 정렬하였다.
buy_data['state'] = 'buy' # 상태 열 추가
sell_data['state'] = 'sell' # 상태 열 추가
data = pd.concat([buy_data, sell_data]).sort_index()
이렇게 하면 다음과 같이 나온다.
그러면 이제 반복문이 데이터프레임을 돌면서 매수인 경우 매수를 하고 매도인 경우 매도를 하면 된다. 그런데 매수를 할 때 이미 매수를 했으면 그냥 넘어가고 매도를 할 때 이미 매도를 했으면 그냥 넘어가도록 해야 했다. 그러기 위해서 flag 를 사용하였다. 따라서 다음과 같이 작성했다.
flag = 0 # 매수 여부 flag
for row in data.itertuples(): # 한 줄씩 가져오기
price = row[5] # 종가
state = row[8] # 매수 / 매도
if flag == 0 and state == 'buy':
# 전량 매수 함수
flag = 1
pass
elif flag == 1 and state == 'sell':
# 전량 매도 함수
flag = 0
pass
이제 매수 함수와 매도 함수를 만들면 된다. 그러기 위해선 함수를 실행할 때 마다 자산이 바뀌어야 한다. 그래서 클래스를 만들고 인스턴스를 생성하도록 하여 각자 자산을 넣고 메서드를 통해서 매수와 매도가 이루어지는 방식으로 구성해야겠다고 생각했다.
class stock_fn:
def __init__(self, cash):
self.first = cash # 초기 자산
self.cash = cash # 시작 자산
self.stock = 0 # 주식 자산
pass
def buy_fn(self, price, count):
""" 주식을 매수하는 함수 """
if (self.cash < price * count):
print("돈이 충분하지 않습니다.")
return
self.cash -= price * count
self.stock += count
pass
def sell_fn(self, price, count):
""" 주식을 매도하는 함수 """
if (self.stock < count):
print('그 정도 주식을 가지고 있지 않습니다.')
return
self.stock -= count
self.cash += price * count
pass
def print_profit(self):
print("{0:->30}".format(""))
print(f"순이익 : {self.cash - self.first}")
print(f"수익률 : {(self.cash / self.first) * 100 - 100}")
그리고 나중에 매도시 수익률을 보여주는 print_profit
메서드도 추가하였다.
최종적으로 완성된 코드는 다음과 같다.
import yfinance as yf
import pandas as pd
import ta
class stock_fn:
def __init__(self, cash):
self.first = cash # 초기 자산
self.cash = cash # 시작 자산
self.stock = 0 # 주식 자산
pass
def buy_fn(self, price, count):
""" 주식을 매수하는 함수 """
if (self.cash < price * count):
print("돈이 충분하지 않습니다.")
return
self.cash -= price * count
self.stock += count
pass
def sell_fn(self, price, count):
""" 주식을 매도하는 함수 """
if (self.stock < count):
print('그 정도 주식을 가지고 있지 않습니다.')
return
self.stock -= count
self.cash += price * count
pass
def print_profit(self):
print("{0:->30}".format(""))
print(f"순이익 : {self.cash - self.first}")
print(f"수익률 : {(self.cash / self.first) * 100 - 100}")
def return_dataframe(ticker, start_day):
""" 사용할 데이터 프레임을 리턴하는 함수"""
data = yf.download(ticker,start = '2021-01-01') # 시작날을 입력 받으면 그 이후 33일 정도 기록누락 발생
if start_day not in data.index:
print('해당 날은 주가 기록이 없습니다.')
return
data = data.reset_index() # 날짜 인덱스를 열로 바꾸고 숫자 인덱스 사용
return data
def if_used(data, person):
""" 만약 내가 정한 조건에 맞춰 매수, 매도시 수익률 확인 함수"""
rsi = ta.momentum.rsi(data['Close'])
mfi = ta.volume.money_flow_index(data['High'],data['Low'],data['Close'],data['Volume'])
macd = ta.trend.macd(data['Close'])
macd_sig = ta.trend.macd_signal(data['Close'])
buy_condition = (rsi <= 35) & (mfi <= 35) & (macd - macd_sig < -0.5) # 매수하는 조건
sell_condition = (rsi >= 65) & (mfi >= 65) & (macd - macd_sig >= 0.6) # 매도하는 조건
buy_data = data[buy_condition].copy() # 매수할 날에 대한 데이터 프레임, 수정시 기존에 파일을 수정할지 아니면 복사본만 수정할지 정해줘야 해서 copy() 시용하여 복사본을 만들어 수정한다고 명시
sell_data = data[sell_condition].copy() # 매도할 날에 대한 데이터 프레임
buy_data['state'] = 'buy' # 상태 열 추가
sell_data['state'] = 'sell' # 상태 열 추가
data = pd.concat([buy_data, sell_data]).sort_index()
flag = 0 # 매수 여부 flag
for row in data.itertuples(): # 한 줄씩 가져오기
price = row[5] # 종가
state = row[8] # 매수 / 매도
if flag == 0 and state == 'buy':
person.buy_fn(price, person.cash // price) # 전량 매수
flag = 1
pass
elif flag == 1 and state == 'sell':
person.sell_fn(price, person.stock) # 전량 매도
person.print_profit()
flag = 0
pass
james = stock_fn(20000) # james 라는 사람이 20000달러를 가지고 시작할 때
mike = stock_fn(10000) # mike 라는 사람이 10000달러를 가지고 시작할 때
fngu_data = return_dataframe('FNGU', '2021-04-01')
if_used(fngu_data, james)
#if_used(fngu_data, mike)
출력 결과
[*********************100%***********************] 1 of 1 completed
------------------------------
순이익 : 2875.8400268554688
수익률 : 28.75840026855471
------------------------------
순이익 : 7910.799396514893
수익률 : 79.10799396514892
------------------------------
순이익 : 12690.698879241943
수익률 : 126.90698879241941
------------------------------
순이익 : 21249.249727249146
수익률 : 212.49249727249145
james
라는 사람이 20000달러로 위와 같은 방법으로 매매시 최종 수익률이 212% 라고 나오는 것을 볼 수 있다. 이를 통해서 충분히 이런 방식으로 매매를 하기 적합하다고 판단하였다.
완성된 코드의 깃헙주소는 아래와 같다.
https://github.com/jhon3242/buy_now
흥미로운 블로그를 운영하고계시네요 구경하고 갑니다.
자주 방문하겠습니다. 혹시 퀀트투자에 관심있으시면 제 블로그도 한번 구경 와주세요. https://quantai.tistory.com/3