<파이썬 데이터 분석 4주차 개발일지>
4주차에선 백테스팅을 직접 구현해보고 그래프를 그려 본다고 한다.
백테스팅이란 ?
이전 주가의 추이에 전략을 대입해보는 것
날씨가 흐리면 사고, 좋으면 팔아볼까? 하는 모든 전략이 벡테스팅 대상이라고함
이런 전략을 몇 년치 과거 주가에 대입해보고 수익률이 좋았으면, 앞으로도 좋을 확률이 높다 라는!
전략 1. 이동평균선
* 이동평균선 : 5일 20일 50일 등 며칠 간의 가격을 평균하여 움직이는 선
이동평균선을 이용한 골든크로스, 데드크로스 전략!
골든크로스 : 주가가 높아질 가능성이 있다는 신호
데드크로스 : 주가가 낮아질 가능성이 있다는 신호
두 가지 단기 이평선(20일)이 장기 이평선(60일)을 돌파하는 경우인데,
그 방향이 다르다고 보면 됨
Colab 새노트를 열어서 라이브러리 설치부터 먼저
# 라이브러리 3개 설치
!pip install yfinance pandas-datareader finance-datareader
라이브러리 가져오기
# 라이브러리 가져오기
from pandas_datareader import data as pdr
import yfinance as yf
yf.pdr_override()
import numpy as np
import pandas as pd
import FinanceDataReader as fdr
df = fdr.DataReader('005930','2018') # 삼성전자의 종목코드
# 2018년부터 현재까지 보고싶다
df.head() # 상위 5개만
Open : 시초가 / High : 최고가 / Low : 최저가 / Close : 종가 / Volume : 거래량 / Change : 전일대비등락
아래와 같이 특정 컬럼, 조건 값만 추출할 수 있음
df = fdr.DataReader('005930','2018')
df[['Close']] # 삼성전자의 종가만
df = fdr.DataReader('005930','2018')
df[df['Change'] > 0.05] # 삼성전자 주가가 전일대비 5% 이상 오른 것
그래프 그려보기
.plot 만 붙여주면 끝!
df = fdr.DataReader('066570','2018') # LG전자의 2018년~현재까지
df.plot(y=['Close']) # lg전자의 종가를 그래프로 그림
df = fdr.DataReader('066570','2018')
df.plot(y=['Close'],figsize=[15,8]) # 조금 더 큰 사이즈 그래프 그리기
df = fdr.DataReader('066570','2018')
df.plot(y=['Close'],figsize=[15,8],grid=True) # 격자 선 추가
df = fdr.DataReader('066570','2018')
df.plot(y=['Open','Close'],figsize=[15,8],grid=True) # 시가, 종가 같이 그리기
df = fdr.DataReader('066570','2018')
df.plot() # 아무것도 안넣으면, 전체 데이터를 그래프화
df_1 = fdr.DataReader('005930','2018') # 삼성전자
df_2 = fdr.DataReader('066570','2018') # lg전가
df = pd.DataFrame() # 새로운 데이터프레임 만들기
df['Samsung'] = df_1['Close'] # Samsung 컬럼을 만들어서 df_1 의 종가데이터를 넣어주고
df['LG'] = df_2['Close'] #LG 컬럼을 만들어서 df_2 의 종가데이터를 넣어주고
df.plot() #그래프 그리기
하지만 주식수도 금액도 다른 종목을 종가로 확인하면 정확하지 않기 때문에, 변동률을 보는게 정확함
df_1 = fdr.DataReader('005930','2018')
df_2 = fdr.DataReader('066570','2018')
df = pd.DataFrame()
df['Samsung'] = df_1['Change'] # 삼성의 전일대비 등락률
df['LG'] = df_2['Change'] # 엘전의 전일대비 등락률
df.tail(100).plot(figsize=[15,8]) # 최근 100일(tail(1oo))로 그래프 그리기, 사이즈크게
백테스팅 해보기
이동평균선을 가지고 백테스팅을 해봐야 하기 때문에,
이동평균값을 만들어야함
종가가 필요!
df = fdr.DataReader('005930','2018') # 삼성전자
df = df[['Close']] # df의 종가를 df 자체에 넣어주기
df
df = fdr.DataReader('005930','2018')
df = df[['Close']]
df.rolling(3).mean() # 3개씩 묶어서 평균을 보겠다
여기서 잠깐!
3개씩 본다고해서 2일 3일 4일 / 5일 6일 7일 이 아니라!
2일 3일 4일 / 3일 4일 5일 / 4일 5일 6일 이 되는것임
df = fdr.DataReader('005930','2018')
df = df[['Close']]
df['ma'] = df.rolling(3).mean() # ma라는 컬럼을 새로 만들어 3일 평균값을 넣어줌
df
2018-01-04 ma 에 있는 값은 2일 3일 4일의 평균값임
2 3 4일의 평균값과 다음날 종가를 비교해야 하는 것
(다음날 종가가, 전일3일의 평균보다 높으면 상승추세)
두 값을 비교해야하니, ma컬럼의 값을 전체적으로 한 칸 아래로 내려줘야함!
df = fdr.DataReader('005930','2018')
df = df[['Close']]
df['ma'] = df.rolling(3).mean().shift(1) # shift(1)을 쓰면 한 칸 아래로 내려감
df
한 칸 아래로 내려간 것 확인!
이제 두 값을 비교해서 Close - ma가 플러스면 사는 것, 마이너스면 파는 것!
조건넣기? np.where
df = fdr.DataReader('005930','2018')
df = df[['Close']]
df['ma'] = df.rolling(3).mean().shift(1)
df['action'] = np.where(df['Close'] > df['ma'], 'buy', 'sell')
# action컬럼 생성 # Close가 ma보다 크면 '산다', 아니면 '판다'
df
이제 action 값을 기반으로 사고 팔 때는 알 수 있음
buy buy buy 일 때 계속 사는게 아니라 갖고 있으면 되고
sell sell sell 일 때 계속 파는게 아니라 안사면 됨
그럼 사고 팔 때는?
buy에서 sell로 바뀔 때 팔면되고
sell에서 buy로 바뀔 때 사면됨!
df = fdr.DataReader('005930','2018')
df = df[['Close']]
df['ma'] = df.rolling(3).mean().shift(1)
df['action'] = np.where(df['Close'] > df['ma'], 'buy', 'sell')
df['action_temp'] = df['action'].shift(1) # action_temp라는 컬럼을 생성하고, action을 한칸씩 내림
df
action과 action_temp값을 비교하면됨
조건을 달아서 쓰자면
df = fdr.DataReader('005930','2018')
df = df[['Close']]
df['ma'] = df.rolling(3).mean().shift(1)
df['action'] = np.where(df['Close'] > df['ma'], 'buy', 'sell')
cond = (df['action'] =='buy') & (df['action'].shift(1) == 'sell')
# aciton이 buy이고, action한 칸 내린 값(action_temp)이 sell인 조건
df['real_buy'] = np.where(cond,'buy','')
# real_buy라는 컬럼을 생성해서, 위의 조건이 맞으면 buy, 아니면 공백으로 두는 값을 넣어줌
df
real_buy가 buy 인게 사는 시점
df = fdr.DataReader('005930','2018')
df = df[['Close']]
df['ma'] = df.rolling(3).mean().shift(1)
df['action'] = np.where(df['Close'] > df['ma'], 'buy', 'sell')
cond1 = (df['action'] =='buy') & (df['action'].shift(1) == 'sell') # 사는 시점
cond2 = (df['action'] =='sell') & (df['action'].shift(1) == 'buy') # 파는 시점
df[cond1]
이렇게 컨디션만 지정해서 넣으면, 이 시기가 사는 시점!
1/5 이전에 1/4은 sell 이었을 거고,
1/15 이전에 1/14도 sell 이었을 것,
sell -> buy로 바뀐 시점
마찬가지로 con2를 넣으면 파는 시점
1/9 이전 1/8은 buy 였을것이고,
1/19 이전 1/18은 buy 였을 것이니
buy -> sell로 바뀐 시점
사는 시점, 파는 시점을 구해두고
마지막 날은? 무조건 팔아야 내가 수익률 책정을 하니
마지막날 셀은 무조건 파는 것으로 값을 넣어둔다(iloc 사용)
df = fdr.DataReader('005930','2018')
df = df[['Close']]
df['ma'] = df.rolling(3).mean().shift(1)
df['action'] = np.where(df['Close'] > df['ma'], 'buy', 'sell')
df.iloc[-1, -1] = 'sell' # 마지막날은 무조건 팔아야 수익률 책정을하니 마지막셀은 sell로
cond1 = (df['action'] =='buy') & (df['action'].shift(1) == 'sell')
cond2 = (df['action'] =='sell') & (df['action'].shift(1) == 'buy')
df_buy = df[cond1] # 사는 타이밍
df_sell = df[cond2] # 파는 타이밍
사는 타이밍과, 파는 타이밍을 같이 붙여서 언제 사고 팔아야하는지 알아보기!
df = fdr.DataReader('005930','2018')
df = df[['Close']]
df['ma'] = df.rolling(3).mean().shift(1)
df['action'] = np.where(df['Close'] > df['ma'], 'buy', 'sell')
df.iloc[-1, -1] = 'sell'
cond1 = (df['action'] =='buy') & (df['action'].shift(1) == 'sell')
cond2 = (df['action'] =='sell') & (df['action'].shift(1) == 'buy')
df_buy = df[cond1]
df_sell = df[cond2]
df_result = pd.concat([df_buy,df_sell],axis=1) # buy와 sell을 같이 합치는데,
# axis=1 을 쓰면 옆에(가로)붙음
df_result.head(30)
axis = 1을 해서 buy와 sell을 나란히 붙여놓고
1/5 52120원에 사서, 1/9 50400원에 팔고
1/15 48540원에 사서, 1/19 49320원에 팔고
이런식으로 데이터를 분석하면 되는 것!
근데 또 한칸씩 아래 있고, NaN이 많아서 보기 힘드니 한 줄로 합쳐주는 작업을 해줘야함
.reset_index()
df = fdr.DataReader('005930','2018')
df = df[['Close']]
df['ma'] = df.rolling(3).mean().shift(1)
df['action'] = np.where(df['Close'] > df['ma'], 'buy', 'sell')
df.iloc[-1, -1] = 'sell'
cond1 = (df['action'] =='buy') & (df['action'].shift(1) == 'sell')
cond2 = (df['action'] =='sell') & (df['action'].shift(1) == 'buy')
df_buy = df[cond1].reset_index() #reset_index()만 추가로 붙여주기
df_sell = df[cond2].reset_index() #reset_index()만 추가로 붙여주기
df_result = pd.concat([df_buy,df_sell],axis=1)
df_result.head(30)
위에 데이터프레임을 보면, 날짜가 인덱스로 되어있으니
인덱스를 reset해서 일반 숫자 인덱스 0 1 2 3~ 으로 만들어주면
날짜는 컬럼으로 빠져서 날짜까지 옆으로 붙여주게 되는 것!
위의 표만 보면 어디가 buy이고, 어디가 sell인지 구분하기 어려우니 컬럼명 변경
df_buy = df[cond1].reset_index()
df_buy.columns = ['날짜', '종가(buy)','이평값', '액션'] # buy 컬럼명 순서대로
df_sell = df[cond2].reset_index()
df_sell.columns = ['날짜', '종가(sell)','이평값', '액션'] # sell 컬럼명 순서대로
수익률을 구하기 위해선, 종가가 필요함!
매도가 / 매수가 = 를 해주면 수익률이 구해지니 수익률 컬럼 추가하기
df_result['수익률'] = df_result['종가(sell)'] / df_result['종가(buy)']
#컬럼 추가해서 = 값넣기(수익률구하기)
df_result
저렇게 사고 팔았을 때의 전체 총 수익률이 어떤지 알아보기 위해선,
수익률 전체를 곱해주면됨!
df_result[['수익률']].cumprod() # 수익률만 따로 꺼낸다음
# .cumprod() 를 써주면 누적곱을 나타내줌
누적 곱!
위의 표와 비교해보면 되는데, 1번 인덱스는 0번 1번의 수익률을 곱한값,
2번 인덱스는 0번 1번 2번의 수익률을 곱한값으로 이해하면 됨
그렇다면 제일 끝의 값을 위의 수익률을 전부 다 곱한 값이 되는 것
df_result[['수익률']].cumprod().iloc[-1,-1] -1 # 제일 마지막 누적 수익률 구하는 것
# 1을 빼줘야 수익률
0.3024082832109143
삼성전자를 2018년부터 현재까지 3일 이평선을 가지고 했을 때의 수익률이 되는 것
위의 코드에 종목과 이동평균선 기준일자를 넣으면 수익률을 나오도록
함수로 만들어 주면 됨
def get_return(code, n): # code : 종목 n : 이동평균선 기준일
df = fdr.DataReader(code ,'2018') # code 들어감
df = df[['Close']].copy()
df['ma'] = df.rolling(n).mean().shift(1) # 이동평균선 기준일 들어감
df['action'] = np.where(df['Close'] > df['ma'], 'buy', 'sell')
df.iloc[-1, -1] = 'sell'
cond1 = (df['action'] =='buy') & (df['action'].shift(1) == 'sell')
cond2 = (df['action'] =='sell') & (df['action'].shift(1) == 'buy')
df_buy = df[cond1].reset_index()
df_buy.columns = ['날짜', '종가(buy)','이평값', '액션']
df_sell = df[cond2].reset_index()
df_sell.columns = ['날짜', '종가(sell)','이평값', '액션']
df_result = pd.concat([df_buy,df_sell],axis=1)
df_result['수익률'] = df_result['종가(sell)'] / df_result['종가(buy)']
return df_result[['수익률']].cumprod().iloc[-1,-1] -1 # 마지막은 return
이렇게 한번 짜놓은 코드로 이평선을 이용한 수익률을 예상해볼 수 있다!
이제 주목적이었던 단기 이평선과 장기 이평선의 골든크로스, 데드크로스 때를 확인해야함!
위의 코드를 끌고와서 단기(3일) 장기(30일) 두 가지 이평선을 만들고
단기 종가가 장기 종가보다 클 때(골든크로스) 매수하는 코드를 만들면됨
df = fdr.DataReader('005930' ,'2018') # 2018년부터 현재까지, 삼성전지
df = df[['Close']].copy() # 종가
df['ma1'] = df.rolling(3).mean().shift(1) # 3일 단기 이평선
df['ma2'] = df.rolling(30).mean().shift(1) # 30일 장기 이평선
df['action'] = np.where(df['ma1'] > df['ma2'], 'buy', 'sell')
# 단기 이평선이 장기 이평선보다 크면, 사고, 아니면 팔기
df
결과는 에러!
이유는 ma1의 값만 우선 확인해보면
df 자체의 3일 평균을 내기 때문에,
처음 시작의 2 3 4일은 평균을 낼 수 없어서 NaN이 뜨고 그로인해 발생한 에러!
df['Close'] 의 값으로 rolling 해주면 해결
df = fdr.DataReader('005930' ,'2018')
df = df[['Close']].copy()
df['ma1'] = df['Close'].rolling(3).mean().shift(1) # 3일 단기 이평선
df['ma2'] = df['Close'].rolling(30).mean().shift(1) # 30일 장기 이평선
df['action'] = np.where(df['ma1'] > df['ma2'], 'buy', 'sell')
df
이어서 위의 코드 계속 복붙
df = fdr.DataReader('005930' ,'2018')
df = df[['Close']].copy()
df['ma1'] = df['Close'].rolling(3).mean().shift(1) # 3일 단기 이평선
df['ma2'] = df['Close'].rolling(30).mean().shift(1) # 30일 장기 이평선
df['action'] = np.where(df['ma1'] > df['ma2'], 'buy', 'sell')
cond1 = (df['action'] =='buy') & (df['action'].shift(1) == 'sell') # 사는 조건
cond2 = (df['action'] =='sell') & (df['action'].shift(1) == 'buy') # 파는 조건
df_buy = df[cond1].reset_index()
df_buy.columns = ['날짜', '종가(buy)','이평값1','이평값2', '액션'] # 이평선1, 이평선2로 수정
df_sell = df[cond2].reset_index()
df_sell.columns = ['날짜', '종가(sell)','이평값1','이평값2','액션'] # 이평선1, 이평선2로 수정
df_result = pd.concat([df_buy,df_sell],axis=1) # 두 컬럼 가로로 붙여 합치기
df_result['수익률'] = df_result['종가(sell)'] / df_result['종가(buy)']
df_result[['수익률']].cumprod().iloc[-1,-1] -1
0.03710270766835899
위의 코드를 표로 만들어보기!
df = fdr.DataReader('005930' ,'2018')
df = df[['Close']].copy()
df['ma1'] = df['Close'].rolling(3).mean().shift(1)
df['ma2'] = df['Close'].rolling(30).mean().shift(1)
df['action'] = np.where(df['ma1'] > df['ma2'], 'buy', 'sell')
cond1 = (df['action'] =='buy') & (df['action'].shift(1) == 'sell')
cond2 = (df['action'] =='sell') & (df['action'].shift(1) == 'buy')
df_buy = df[cond1].reset_index()
df_buy.columns = ['날짜', '종가(buy)','이평값1','이평값2', '액션']
df_sell = df[cond2].reset_index()
df_sell.columns = ['날짜', '종가(sell)','이평값1','이평값2','액션']
df_result = pd.concat([df_buy,df_sell],axis=1)
df_result['수익률'] = df_result['종가(sell)'] / df_result['종가(buy)']
df_final = (df_result[['수익률']].cumprod().tail(1) -1)*100 # 최종 수익률 마지막 끝값만 불러옴
df_final['단기'] = 3 # 3일
df_final['장기'] = 30 # 30일
df_final
표로 깔끔하게 내가 필요한 값만 추출!
이제 위의 코드를 함수로 만들어버리기!
def get_return_sl(code, short, long): # code, 단기, 장기
df = fdr.DataReader(code ,'2018') # code 추가
df = df[['Close']].copy()
df['ma1'] = df['Close'].rolling(short).mean().shift(1) # short 추가
df['ma2'] = df['Close'].rolling(long).mean().shift(1) # long 추가
df['action'] = np.where(df['ma1'] > df['ma2'], 'buy', 'sell')
cond1 = (df['action'] =='buy') & (df['action'].shift(1) == 'sell')
cond2 = (df['action'] =='sell') & (df['action'].shift(1) == 'buy')
df_buy = df[cond1].reset_index()
df_buy.columns = ['날짜', '종가(buy)','이평값1','이평값2', '액션']
df_sell = df[cond2].reset_index()
df_sell.columns = ['날짜', '종가(sell)','이평값1','이평값2','액션']
df_result = pd.concat([df_buy,df_sell],axis=1)
df_result['수익률'] = df_result['종가(sell)'] / df_result['종가(buy)']
df_final = (df_result[['수익률']].cumprod().tail(1) -1) *100
df_final['단기'] = short # short 추가
df_final['장기'] = long # long 추가
return df_final # return값 추가
최적의 단기 이평선과 최적의 장기 이평선을 알아보기
이중 반복문 사용!
for short in range(3,11): # 3~10일
for long in range(30,61): # 30~60일
print(short, long)
이렇게, 단기 안에 장기를 돌려 이중 반복문이 되도록!
dfs = [] # df를 담을 빈리스트
for short in range(3,11): # 단기
for long in range(30,61): # 장기
df = get_return_sl('005930',short,long) df라는 변수에 함수 값을 넣어주고
dfs.append(df) # 나온 df를 dfs에 추가해주기
df_result = pd.concat(dfs) # dfs 합치기
df_result.sort_values(by='수익률', ascending=False) # 수익률을 내림차순해서 수익률이 제일 높은순으로
단기 10일, 장기 59일일 때 약 47%로 수익률이 제일 높다!
이렇게 4주차는 마무리 되었고,
마지막에 배운 단기 이평선, 장기 이평선을 사용해서 수익률을 확인해보니
실제로 내 주식 매수 매도 할 때 이용해볼 수 있을 것 같다!
(전부 물려서 암것도 못하지만,,,,,,)

실제로 내가 활용할 수 있는 코드라 넘나 유용한 주차였지만,
함수를 만들고, 반복문을 사용하는게 아직 혼자서는 버벅거려 어렵다고 느낀다ㅠ_ㅠ
오랜만에 들으니 금새 또 까묵,.....
남은 마지막 주차도 화이탱

'코딩 개발일지 > 파이썬 데이터분석(스파르타 코딩클럽)' 카테고리의 다른 글
<스파르타 코딩클럽 파이썬 데이터분석 5주차> (0) | 2023.03.19 |
---|---|
<스파르타 코딩클럽 파이썬 데이터분석 3주차> (0) | 2023.03.04 |
<스파르타 코딩클럽 파이썬 데이터분석 2주차> (0) | 2023.03.03 |
<스파르타 코딩클럽 파이썬 데이터분석 1주차> (0) | 2023.02.26 |