주식 데이터 시각화
지윤은 주식 데이터를 담당했습니다. 교수님이 "실시간 데이터도 좋지만 과거 데이터로 트렌드를 보여주는 것도 훌륭해요"라고 말씀하신 것을 떠올리며 yfinance 라이브러리를 설치했습니다.
주식 데이터는 일반 데이터와 구조가 조금 다릅니다. 날짜마다 하나의 값이 아니라 시가, 고가, 저가, 종가, 거래량 다섯 가지가 붙습니다. 이런 데이터를 가장 잘 표현하는 차트가 바로 캔들차트입니다.
yfinance 설치와 데이터 수집
pip install yfinance
# 새 파일: chapter04_04_stock.pyimport yfinance as yfimport pandas as pd# 삼성전자 1년치 데이터 수집 (KRX: 005930.KS)samsung = yf.download("005930.KS", start="2023-01-01", end="2023-12-31")print(samsung.head())print(samsung.columns.tolist())print(f"데이터 행 수: {len(samsung)}")
yfinance는 야후 파이낸스 API를 사용합니다. 한국 주식은 티커 뒤에 .KS(KRX 유가증권시장)를 붙입니다. 코스닥은 .KQ입니다.
Open High Low Close Volume Dividends Stock Splits
Date
2023-01-02 58900 59300 58200 58700 9856200 0.0 0.0
...
['Open', 'High', 'Low', 'Close', 'Volume', 'Dividends', 'Stock Splits']
OHLCV는 Open(시가), High(고가), Low(저가), Close(종가), Volume(거래량)의 약자입니다.
px.line()으로 종가 추이 그리기
# 수정: chapter04_04_stock.pyimport yfinance as yfimport plotly.express as pximport plotly.graph_objects as goimport pandas as pdsamsung = yf.download("005930.KS", start="2023-01-01", end="2023-12-31")# MultiIndex를 단순 컬럼으로 변환samsung.columns = samsung.columns.droplevel(1) if isinstance( samsung.columns, pd.MultiIndex) else samsung.columnssamsung = samsung.reset_index()# px.line()으로 종가 추이fig_line = px.line( samsung, x="Date", y="Close", title="삼성전자 종가 추이 (2023)", labels={"Date": "날짜", "Close": "종가 (원)"})fig_line.update_traces(line_color="royalblue")fig_line.show()
최근 버전의 yfinance는 MultiIndex 형태로 컬럼을 반환하는 경우가 있습니다. droplevel(1)로 단순 컬럼으로 바꿔야 이후 코드가 깔끔합니다.
go.Candlestick()으로 캔들차트 그리기
# 수정: chapter04_04_stock.pyimport yfinance as yfimport plotly.express as pximport plotly.graph_objects as goimport pandas as pdsamsung = yf.download("005930.KS", start="2023-01-01", end="2023-12-31")samsung.columns = samsung.columns.droplevel(1) if isinstance( samsung.columns, pd.MultiIndex) else samsung.columnssamsung = samsung.reset_index()fig_line = px.line( samsung, x="Date", y="Close", title="삼성전자 종가 추이 (2023)", labels={"Date": "날짜", "Close": "종가 (원)"})fig_line.update_traces(line_color="royalblue")fig_line.show()# go.Candlestick으로 캔들차트fig_candle = go.Figure( data=[ go.Candlestick( x=samsung["Date"], open=samsung["Open"], high=samsung["High"], low=samsung["Low"], close=samsung["Close"], increasing_line_color="red", # 양봉 (종가 > 시가) decreasing_line_color="blue", # 음봉 (종가 < 시가) name="삼성전자" ) ])fig_candle.update_layout( title="삼성전자 캔들차트 (2023)", xaxis_title="날짜", yaxis_title="주가 (원)", xaxis_rangeslider_visible=True # 하단 범위 슬라이더)fig_candle.show()
캔들 하나는 하루의 가격 움직임을 보여줍니다. 몸통은 시가와 종가의 차이, 꼬리(위아래 선)는 고가와 저가입니다. 한국 주식 차트 관행에서는 양봉(오름)을 빨간색, 음봉(내림)을 파란색으로 표시합니다.
이동평균선 추가하기
# 수정: chapter04_04_stock.pyimport yfinance as yfimport plotly.express as pximport plotly.graph_objects as goimport pandas as pdsamsung = yf.download("005930.KS", start="2023-01-01", end="2023-12-31")samsung.columns = samsung.columns.droplevel(1) if isinstance( samsung.columns, pd.MultiIndex) else samsung.columnssamsung = samsung.reset_index()fig_line = px.line( samsung, x="Date", y="Close", title="삼성전자 종가 추이 (2023)", labels={"Date": "날짜", "Close": "종가 (원)"})fig_line.update_traces(line_color="royalblue")fig_line.show()fig_candle = go.Figure( data=[ go.Candlestick( x=samsung["Date"], open=samsung["Open"], high=samsung["High"], low=samsung["Low"], close=samsung["Close"], increasing_line_color="red", decreasing_line_color="blue", name="삼성전자" ) ])fig_candle.update_layout( title="삼성전자 캔들차트 (2023)", xaxis_title="날짜", yaxis_title="주가 (원)", xaxis_rangeslider_visible=True)fig_candle.show()# 이동평균선 계산 (5일, 20일, 60일)samsung["MA5"] = samsung["Close"].rolling(window=5).mean()samsung["MA20"] = samsung["Close"].rolling(window=20).mean()samsung["MA60"] = samsung["Close"].rolling(window=60).mean()# 캔들 + 이동평균선 조합fig_ma = go.Figure( data=[ go.Candlestick( x=samsung["Date"], open=samsung["Open"], high=samsung["High"], low=samsung["Low"], close=samsung["Close"], increasing_line_color="red", decreasing_line_color="blue", name="캔들" ) ])# 이동평균선 추가fig_ma.add_trace(go.Scatter( x=samsung["Date"], y=samsung["MA5"], mode="lines", name="5일 이동평균", line=dict(color="orange", width=1)))fig_ma.add_trace(go.Scatter( x=samsung["Date"], y=samsung["MA20"], mode="lines", name="20일 이동평균", line=dict(color="purple", width=1.5)))fig_ma.add_trace(go.Scatter( x=samsung["Date"], y=samsung["MA60"], mode="lines", name="60일 이동평균", line=dict(color="green", width=2)))fig_ma.update_layout( title="삼성전자 캔들차트 + 이동평균선 (2023)", xaxis_title="날짜", yaxis_title="주가 (원)", xaxis_rangeslider_visible=False, # 이동평균선 있을 때는 끄는 경우가 많음 legend=dict(orientation="h", y=1.02))fig_ma.show()
rolling(window=5).mean()은 5거래일 이동평균입니다. 처음 4개 행은 NaN이 되지만 Plotly는 NaN을 자동으로 건너뜁니다.
range_slider 범위 슬라이더 활용
# 수정: chapter04_04_stock.pyimport yfinance as yfimport plotly.express as pximport plotly.graph_objects as goimport pandas as pdsamsung = yf.download("005930.KS", start="2023-01-01", end="2023-12-31")samsung.columns = samsung.columns.droplevel(1) if isinstance( samsung.columns, pd.MultiIndex) else samsung.columnssamsung = samsung.reset_index()samsung["MA5"] = samsung["Close"].rolling(window=5).mean()samsung["MA20"] = samsung["Close"].rolling(window=20).mean()samsung["MA60"] = samsung["Close"].rolling(window=60).mean()fig_ma = go.Figure( data=[ go.Candlestick( x=samsung["Date"], open=samsung["Open"], high=samsung["High"], low=samsung["Low"], close=samsung["Close"], increasing_line_color="red", decreasing_line_color="blue", name="캔들" ) ])fig_ma.add_trace(go.Scatter( x=samsung["Date"], y=samsung["MA5"], mode="lines", name="5일 이동평균", line=dict(color="orange", width=1)))fig_ma.add_trace(go.Scatter( x=samsung["Date"], y=samsung["MA20"], mode="lines", name="20일 이동평균", line=dict(color="purple", width=1.5)))fig_ma.add_trace(go.Scatter( x=samsung["Date"], y=samsung["MA60"], mode="lines", name="60일 이동평균", line=dict(color="green", width=2)))fig_ma.update_layout( title="삼성전자 캔들차트 + 이동평균선 (2023)", xaxis_title="날짜", yaxis_title="주가 (원)", xaxis_rangeslider_visible=False, legend=dict(orientation="h", y=1.02))fig_ma.show()# 범위 슬라이더와 범위 선택 버튼 추가fig_slider = go.Figure( data=[ go.Candlestick( x=samsung["Date"], open=samsung["Open"], high=samsung["High"], low=samsung["Low"], close=samsung["Close"], increasing_line_color="red", decreasing_line_color="blue", name="삼성전자" ) ])fig_slider.add_trace(go.Scatter( x=samsung["Date"], y=samsung["MA20"], mode="lines", name="20일선", line=dict(color="orange", width=1.5)))fig_slider.update_layout( title="삼성전자 (2023) - 범위 선택", xaxis=dict( rangeslider=dict(visible=True), # 하단 슬라이더 rangeselector=dict( # 상단 버튼 buttons=list([ dict(count=1, label="1개월", step="month", stepmode="backward"), dict(count=3, label="3개월", step="month", stepmode="backward"), dict(count=6, label="6개월", step="month", stepmode="backward"), dict(step="all", label="전체") ]) ) ), yaxis_title="주가 (원)")fig_slider.show()
rangeselector의 버튼을 클릭하면 지정한 기간만큼 자동으로 범위가 설정됩니다. 분기별, 반기별 트렌드를 빠르게 전환할 때 유용합니다.
실전: 삼성전자·애플 주가 비교 대시보드
두 종목을 같은 차트에 놓고 비교합니다. 단위가 달라서(원 vs 달러) 이중 Y축을 활용합니다.
# 새 파일: chapter04_04_stock_compare.pyimport yfinance as yfimport plotly.graph_objects as goimport pandas as pdfrom plotly.subplots import make_subplots# 데이터 수집samsung = yf.download("005930.KS", start="2023-01-01", end="2023-12-31")apple = yf.download("AAPL", start="2023-01-01", end="2023-12-31")# MultiIndex 처리 (yfinance가 MultiIndex 컬럼을 반환하는 경우)if isinstance(samsung.columns, pd.MultiIndex): samsung.columns = samsung.columns.droplevel(1)if isinstance(apple.columns, pd.MultiIndex): apple.columns = apple.columns.droplevel(1)samsung = samsung.reset_index()apple = apple.reset_index()# 이동평균 계산samsung["MA20"] = samsung["Close"].rolling(20).mean()apple["MA20"] = apple["Close"].rolling(20).mean()# 이중 Y축 서브플롯fig = make_subplots( rows=2, cols=1, shared_xaxes=True, vertical_spacing=0.05, subplot_titles=["삼성전자 vs 애플 종가 비교", "거래량"], specs=[[{"secondary_y": True}], [{"secondary_y": False}]])# 삼성전자 종가 (왼쪽 Y축)fig.add_trace( go.Scatter(x=samsung["Date"], y=samsung["Close"], name="삼성전자", line=dict(color="royalblue")), row=1, col=1, secondary_y=False)fig.add_trace( go.Scatter(x=samsung["Date"], y=samsung["MA20"], name="삼성 20일선", line=dict(color="royalblue", dash="dot", width=1)), row=1, col=1, secondary_y=False)# 애플 종가 (오른쪽 Y축)fig.add_trace( go.Scatter(x=apple["Date"], y=apple["Close"], name="애플", line=dict(color="tomato")), row=1, col=1, secondary_y=True)fig.add_trace( go.Scatter(x=apple["Date"], y=apple["MA20"], name="애플 20일선", line=dict(color="tomato", dash="dot", width=1)), row=1, col=1, secondary_y=True)# 거래량 막대fig.add_trace( go.Bar(x=samsung["Date"], y=samsung["Volume"], name="삼성 거래량", marker_color="steelblue", opacity=0.5), row=2, col=1)fig.update_yaxes(title_text="삼성전자 (원)", secondary_y=False, row=1, col=1)fig.update_yaxes(title_text="애플 (USD)", secondary_y=True, row=1, col=1)fig.update_yaxes(title_text="거래량", row=2, col=1)fig.update_layout( title="삼성전자 vs 애플 주가 비교 대시보드 (2023)", height=600, legend=dict(orientation="h", y=1.05))fig.show()
실행하면 삼성전자와 애플의 종가를 이중 Y축으로 비교하고, 아래에 삼성전자 거래량을 함께 보여주는 대시보드가 나타납니다. 상단의 범위를 드래그하면 하단 거래량 차트도 함께 이동합니다.
secondary_y=True로 오른쪽 Y축을 활성화하면 단위가 다른 두 데이터를 같은 차트에 표시할 수 있습니다. shared_xaxes=True로 위아래 차트의 X축을 연동시켜 한쪽을 드래그하면 다른 쪽도 같이 이동합니다.