자동매매 결과 시각화 분석
2022.11.07 - [개발일지] - 3. 투자지표를 활용한 매매 시점 모니터링 - 벡테스팅
이전 글에서 자동 매매의 결과를 시각화 자료를 간단히 소개했다.
자동매매 전략을 짜기 전에 전략에 따른 매매 타이밍, 투자 금액 및 총자산의 흐름을 잘 파악하고 있어야 자동 매매 프로그램이 내가 의도한 대로 혹은 최선의 전략을 만들 수 있다.
따라서 이번 글에서는 내가 만든 시각화 코드를 소개하고 설명하겠다.
이전 글에서 자동 매매를 정상적으로 실행시켰다면 파일 세 개가 생성이 될 것이다.
순서대로 설명하자면
trading_simulation_test.txt 파일은
[0, 'name', 'time', '보유KRW', '보유주', 'price', '총자산', 'TAG', 'stock', 'money', 'trading_fee', 'target_index', 0]
[0, '삼성전자', 1371513600.0, 5006800, 180, 27740, 10000000, 'buy', 180, 4993200, 0, 35.420493345195524, 0]
[0, '삼성전자', 1372291200.0, 25720, 366, 26780, 9827200, 'buy', 186, 4981080, 0, 40.14866988453213, 0]
[0, '삼성전자', 1379030400.0, 5158815.1, 183, 28120, 10304775.1, 'sell', 183, 5133095.1, 12864.9, 69.5311975413225, 0]
[0, '삼성전자', 1379376000.0, 10218893.2, 0, 27720, 10218893.2, 'sell', 183, 5060078.1, 12681.9, 59.381278875752635, 0]
[0, '삼성전자', 1389571200.0, 5220193.199999999, 193, 25900, 10218893.2, 'buy', 193, 4998700, 0, 32.54077446441633, 0]
[0, '삼성전자', 1391644800.0, 234553.19999999925, 391, 25180, 10079933.2, 'buy', 198, 4985640, 0, 36.42119650212527, 0]
[0, '삼성전자', 1411603200.0, 3353.199999999255, 401.0, 23120, 9274473.2, 'buy', 10.0, 231200.0, 0, 32.06083482044599, 0]
자동 매매가 이루어졌을 때의 기록이다. TAG 컬럼에 buy 혹은 sell로 표시되며 주식수는 stock에 표시된다.
trading_simulation_test_log.txt 파일은
[0, 'name', 'time', 'price', 'target_index', 'buy', 'sell', 'current_stock', 'avg_price', 'yield', 'total_money', 0]
[0, '삼성전자', 1356307200.0, 29480, 52.1554000373811, 0, 0, 0, 1, 1, 10000000, 0]
[0, '삼성전자', 1356480000.0, 29400, 51.071072580117594, 0, 0, 0, 1, 1, 10000000, 0]
[0, '삼성전자', 1356566400.0, 29920, 57.287166867049415, 0, 0, 0, 1, 1, 10000000, 0]
[0, '삼성전자', 1356652800.0, 30440, 62.4276604315165, 0, 0, 0, 1, 1, 10000000, 0]
[0, '삼성전자', 1357084800.0, 31520, 70.39649295689097, 0, 0, 0, 1, 1, 10000000, 0]
[0, '삼성전자', 1357171200.0, 30860, 61.77392682874736, 0, 0, 0, 1, 1, 10000000, 0]
[0, '삼성전자', 1357257600.0, 30500, 57.62763776538388, 0, 0, 0, 1, 1, 10000000, 0]
매 일 별 종가 및 투자지표(RSI), 매수 여부와 평단가, 총자산 등이 기록된다.
trading_simulation_test_total_price_log.txt 파일은
[0, 'time', 'money', '삼성전자', '삼성전자_price', 0]
[0, 1354492800.0, 10000000, 0, 28600, 0]
[0, 1354579200.0, 10000000, 0, 28600, 0]
[0, 1354665600.0, 10000000, 0, 29100, 0]
[0, 1354752000.0, 10000000, 0, 29080, 0]
[0, 1354838400.0, 10000000, 0, 29600, 0]
[0, 1355097600.0, 10000000, 0, 29820, 0]
[0, 1355184000.0, 10000000, 0, 29520, 0]
[0, 1355270400.0, 10000000, 0, 29800, 0]
[0, 1355356800.0, 10000000, 0, 30660, 0]
매 일 별 종목 가격을 반영한 총자산이 계산된다.
총자산을 따로 계산한 이유는 후에 여러 종목을 동시에 투자 후보에 올려두고 매매를 했을 경우 매 일 별 손익률 및 총자산을 확인하기 위함이다.
테이블이 만들어졌으면 이를 시각화의 기초 자료로 사용한다.
file_path = "./trading_simulation/trading_simulation_test"
def read_log(file_path):
log1 = pd.read_csv(file_path+".txt",sep = ",")
log1 = log1.iloc[:,1:-1]
log1.columns = ["name","time","price","target_index","buy","sell","current_stock","avg_price","yield","total_money"]
log1["yield"] = log1["yield"].fillna(0)
log1["yield"] = log1["yield"].replace(' nan',1)
log1["yield"] = log1["yield"].astype(float)
log1["yield"] = np.round((log1["yield"]-1)*100,2)
log1.loc[log1["yield"]<= -99,"yield"] = 0
log1["avg_price"] = log1["avg_price"].fillna(0)
log1["avg_price"] = log1["avg_price"].replace(' nan',0)
log1["avg_price"] = log1["avg_price"].astype(float)
log1["avg_price"] = np.round(log1["avg_price"],0)
log1.loc[log1["avg_price"]<=1,"avg_price"] = np.nan
for i,ii in enumerate(log1.time):
log1.loc[i,"time"] = datetime.datetime.utcfromtimestamp(ii)
log1.set_index(log1.time,inplace= True)
log1["target_index"] = log1["target_index"].replace(' nan',0)
log1["target_index"] = log1["target_index"].astype(float)
log1["stock_rate"] = log1["current_stock"]*log1["price"]/log1["total_money"]*100
return log1
def log_plot(log1,start_day=None,last_day=None):
if start_day is not None:
log1 = log1[log1.index>=start_day]
if last_day is not None:
log1 = log1[log1.index<=last_day]
log_p = log1[log1["buy"]==1]
log_p = log1[log1["buy"]==1]
fig, ax1 = plt.subplots(figsize = (14,7))
ax1.set_ylabel('Price')
ax1.plot(log1["price"],color = "skyblue",alpha = 1, zorder=3)
ax1.plot(log1["avg_price"],color = "black",alpha = 1, zorder=4)
ax1.scatter(log_p.index,log_p["price"],s = 40,color = "blue", zorder=5)
log_p = log1[log1["sell"]==1]
ax1.scatter(log_p.index,log_p["price"],s = 40,color = "red", zorder=5)
plt.legend(["price","avg_price","buy","sell"],loc = 'upper left')
ax2 = ax1.twinx()
ax2.set_ylabel('Index')
ax2.plot(log1["target_index"],color = "green",alpha = 0.2)
ax2.fill_between(log1.index,log1["stock_rate"],color = "black", zorder=5,alpha = 0.2)
ax2.plot(log1["lower"],color = "blue",alpha = 0.2)
ax2.plot(log1["upper"],color = "red",alpha = 0.2)
ax2.set_ylim(0,100)
plt.title(log1["name"].values[0])
plt.legend(["target_index",'Stock_Rate'],loc = 'upper right')
plt.show()
log1 = read_log(file_path+"_log")
log1["lower"] = lower_cut
log1["upper"] = upper_cut
log_plot(log1)
가장 처음 나타낸 그래프는 거래를 어느 시점에 했는지, 보유량을 얼만큼인지 알 수 있는 그래프이다. 위의 그래프의 x축은 2013년부터 22년 11월 초까지의 날짜를 나타낸다. 기간이 매우 길기 때문에 python에서 자동으로 연도만 표시했다. 기간을 짧게 하면 월 혹은 일 단위로 출력될 것이다.
하늘색 선은 해당 종목의 일 별 종가를 나타낸다. 가격은 왼쪽 축의 20000~95000 사이에 표시가 된다. 또한, 가격 선 위의 파란색 점은 매수 포인수, 빨간색 점은 매도 포인트를 나타낸다.
즉, 점이 찍혀있는 날짜에 종가 기준으로 매수, 매도가 이루어졌다는 뜻이다.
여기서 매수 기준은 연두색 선으로 표시가 된다. 나는 RSI 기준 30, 70선으로 매수, 매도를 했기 때문에 30과 70선도 같이 붉은색, 파란색으로 그려 넣었다. RSI값은 오른쪽 축에 0~100 사이로 표시가 된다.
다음 검은색 선이 파란색 매수 포인트 이후에 그려진 것이 보일 것이다. 매수 한 종목의 평단가를 나타내는 선이다. 왼쪽 가격 축과 동일한 값을 나타낸다. 전량 매도를 하면 해당 선은 나타나지 않고, 추가 매수를 하면 평단가가 조정이 된다. 이를 통해 어떤 가격대를 보유하고 있고 매매를 했는지 알 수 있다.
마지막으로 회색의 bar그래프가 보인다. 이는 전체 총자산을 100으로 두었을 때 해당 종목을 총자산의 몇 %를 보유하고 있는지 나타낸다. 값의 범위가 0~100으로 오른쪽 축인 RSI지표와 동일하기 때문에 왼쪽 축 값을 동일하게 사용하고 있다.
정리하면,
- 왼쪽 축
- 하늘색 선 : 종가
- 파란색 점 : 매수 포인트
- 빨간색 점 : 매도 포인트
- 검은색 선 : 평단가
- 오른쪽 축
- 연두색 선 : RSI
- 연 빨간색 선 : RSI 70 선
- 연 파란색 선 : RSI 30 선
- 회색 bar 그래프 : 총자산 대비 해당 종목 투자금액 비율(%)
이렇게 정리할 수 있겠다.
전체 연도를 다 나타내는 것을 너무 촘촘해서 보기 어려운 경우를 고려해 특정 기간 별 조회가 가능하도록 했다.
log_plot 함수의 두 번째 입력은 시작 일, 세 번째 입력은 마지막 일을 입력받을 수 있게 하였다.
입력 방식은 열도, 월, 일로 "2013", "2013-01", "2013-01-01" 이런 양식으로 입력하면 된다.
예시로 연도별 거래 기록을 나타내 보겠다.
for i in range(2013,2023):
log_plot(log1,str(i),str(i+1))
코드 상에는 22년까지 그릴 수 있지만, 2013, 2014년만 나타내었다. 그래프가 확대되어 좀 더 상세하게 가격과 RSI, 투자 현황들을 확인할 수 있다.
매수를 했을 때 회색 bar 그래프가 생기면서 현재 자산 대비 얼마큼 투자되었는지 나타난다. 이후 추가 매수를 하면 회색 bar그래프는 높아지고 검은색 평단가가 조정되는 것을 볼 수 있다. 빨간색 점으로 매도가 이루어지면, 보유 비중이 작아지지만, 평단가는 그대로 있는 것을 볼 수 있다.
또한, RSI 기준으로 매수, 매도 지점을 확인할 수 있으므로, 매매 전략이 잘 먹히는지 판단할 수 있다.
다음은 손익률 그래프이다.
def profit_rate_plot(file_path,color_list,color_list2):
invest_rate_df = None
fig, ax1 = plt.subplots(figsize = (14,7))
ax1.set_ylabel("Rate",size =12)
ax2 = ax1.twinx()
stock_name = []
log1 = read_log(file_path+"_log")
df = pd.DataFrame(np.round(log1["price"] *log1["current_stock"]))
invest_rate_df = pd.concat([invest_rate_df,df],axis =1)
stock_name.append(log1.tail(1)["name"].values[0])
log1 = read_log(file_path+"_log")
log_p = log1[log1["buy"]==1]
ax2.scatter(log_p.index,log_p["yield"],color = "blue", zorder=5)
log_p = log1[log1["sell"]==1]
ax2.scatter(log_p.index,log_p["yield"],color = "red", zorder=5)
ax2.plot(log1["yield"],color = color_list2,alpha = 0.5, zorder=5)
ax2.legend(stock_name,loc = "upper left")
ax2.set_ylabel('Profit Rate')
invest_rate_df.columns = stock_name
invest_rate_df["sum"] = invest_rate_df.sum(axis = 1)
invest_rate_df.iloc[:,0] = np.round(invest_rate_df.iloc[:,0]/invest_rate_df["sum"]*100,2)
invest_rate_df = invest_rate_df[:-2]
invest_rate_df.fillna(0, inplace = True)
invest_rate_df["sum2"] = invest_rate_df.iloc[:,:-1].sum(axis = 1)
ax1.set_ylabel('Invest Rate')
ax1.fill_between(invest_rate_df.index, invest_rate_df["sum2"],color = "moccasin", zorder=1)
invest_rate_df["tmp"] = 0
invest_rate_df["tmp"] = invest_rate_df["tmp"] + invest_rate_df.iloc[:,0]
if max(invest_rate_df.iloc[:,0])>0:
ax1.fill_between(invest_rate_df.index, invest_rate_df["tmp"],color = color_list, zorder=10)
else:
ax1.plot()
plt.title("Investment Status")
plt.show()
color_list = "moccasin"
color_list2 = "tab:orange"
profit_rate_plot(file_path,color_list,color_list2)
왼쪽 축은 투자 비중, 오른쪽 축은 손익률을 나타낸다.
지금 단일 종목으로 벡테스팅을 하고있기 때문에 현금을 제외하면 모든 투자금이 삼성전자에 해당된다. 따라서 소량만 보유해도 100%로 나타난다. 이는 여러 종목 투자 벡테스팅을 했을 때 다른 그래프가 나타나기에 이후에 소개하겠다.
역시 파란색 점은 매수, 빨간색 점은 매도를 나타낸다. 만약 보유 주식 수가 0일 때 매수를 진행하면 그때의 손익률은 0%이다. 이후 주가에 따라 주황선 선이 변동되다가 매도를 하면 몇 % 의 수익률을 내고 했는지 알 수 있다. 반대로 추가 매수의 경우 몇 %의 손익률을 기록할 때 매수했는지 나타낸다. 즉, 물타기 혹은 불타기를 하고 있는지 볼 수 있다. 또한, 적절한 수익률을 잘 내면서 매도를 하고 있는지도 나타난다.
정리하자면,
- 왼쪽 축
- 투자 비중(0~100%)
- 오른쪽 축
- 손익률
- 매수, 매도 포인트
마지막 그래프는 총자산과 관련된 그래프이다.
def total_price_df(file_path,target_stock,seed_money):
log_total = pd.read_csv(file_path+"_total_price_log.txt",sep =",")
log_total = log_total.iloc[:,1:-1]
log_column = ["time","current_money"]
log_column.append(target_stock)
log_column.append(target_stock+"_price")
log_total.columns = log_column
for i,ii in enumerate(log_total.time):
log_total.loc[i,"time"] = datetime.datetime.utcfromtimestamp(ii)
log_total.set_index(log_total.time,inplace= True)
log_total["current_money"] = np.round(log_total["current_money"])
log_total["total_money"] = log_total["current_money"]
log_total[target_stock+"_money"] = np.round(log_total[target_stock]*log_total[target_stock+"_price"])
log_total["total_money"] = log_total["total_money"] + log_total[target_stock+"_money"]
log_total["profit_rate"] = np.round(log_total["total_money"]/seed_money*100-100,2)
return log_total
def total_plot(log_total_all,stock_list,color_list,seg = False,start_day = None, last_day = None):
log_total = log_total_all.copy()
if start_day is not None:
log_total = log_total[log_total.index>=start_day]
if last_day is not None:
log_total = log_total[log_total.index<=last_day]
fig, ax1 = plt.subplots(figsize = (14,7))
ax1.set_ylabel("Invest Price",size =12)
ax2 = ax1.twinx()
ax1.set_ylim(0,max(log_total["total_money"])+1000000)
ax1.ticklabel_format(axis='y',useOffset=False, style='plain')
ax1.fill_between(log_total.index, log_total["total_money"],color = "tab:gray",alpha = 0.2)
if seg == True:
log_total["tmp"] = 0
log_total["tmp"] = log_total["tmp"] + log_total[target_stock+"_money"]
if max(log_total[target_stock+"_money"])>0:
ax1.fill_between(log_total.index, log_total["tmp"],color = color_list, zorder=10)
else:
ax1.plot()
ax1.legend(["TOTAL"]+[target_stock],loc = "upper left")
else:
ax1.legend(["TOTAL"],loc = "upper left")
ax2.plot(log_total["profit_rate"],color = "tab:red")
ax2.set_ylabel("Portfolio Rate",size = 12)
ax2.set_ylim(min(log_total["profit_rate"])-0.1,max(log_total["profit_rate"])+0.1)
plt.title("Total Amount",size = 13)
plt.show()
log_total = total_price_df(file_path,target_stock,seed_money)
total_plot(log_total,target_stock,color_list,True)
입력 인수로 이전 글에서 target_stock, seed_money 등은 지정했으니 참고 바란다.
총 자산과 투자 비중을 나타낸 그래프이다.
왼쪽 축은 금액, 오른쪽 축은 손익률을 나타낸다. 회색 bar그래프는 총 자산, 주황색 bar그래프는 삼성전자 투자 금액이다. 빨간색 선은 초기 투자자산 대비 총자산을 나타낸 것으로 1,000만원 기준의 손익률을 나타낸다.
1,000만원으로 시작한 벡테스팅에서 첫 매수를 한 후 손익률과 총자산이 줄어드는 것을 볼 수 있다. 이후 수익이 났을 때 매도한 후 다시 매수, 매도를 반복하며 총자산을 불려 가는 것을 볼 수 있다.
여러 종목을 매매한다면, 총 자산 대비 투자 현황을 볼 수 있는 그래프가 될 것이다.
결국 자동 매매 프로그램을 만드는 것은 수익을 잘, 안정적으로 낼 수 있도록 하는 것이기 때문에 위의 그래프에서 볼 수 있도록 했다.
여기까지 시각화 자료에 대해 설명했다.
코드가 조금 복잡하기 때문에 천천히 이해하면서 구현하는 것이 필요할 듯싶다.
시각화 자료는 이정도로 정리하고 여러 종목을 동시에 모니터링하고 매매할 수 있도록 수정한 다음 본격적으로 전략을 짜서 최적화된 매매를 할 수 있도록 계속해보겠다.
'개발일지' 카테고리의 다른 글
6. 투자지표를 활용한 매매 시점 모니터링 - 손절선 (0) | 2022.11.09 |
---|---|
5. 투자지표를 활용한 매매 시점 모니터링 - Kospi 종목 벡테스팅 (0) | 2022.11.08 |
3. 투자지표를 활용한 매매 시점 모니터링 - 벡테스팅 (0) | 2022.11.07 |
2. 투자지표를 활용한 매매 시점 모니터링 - RSI, RMI (0) | 2022.11.07 |
1. 투자지표를 활용한 매매 시점 모니터링 - 개요 (0) | 2022.11.01 |
댓글