#!/usr/bin/env python # coding: utf-8 # # **2 기술적 분석** # Fundamental Analysis & Back Testing # # ## **1 Candle Chart 그리기** # 기술적 지표 분석을 위한 데이터 수집 및 분석하기 # - plotly 를 활용하여 SMA, EMA 보조지표 그리기 # In[1]: get_ipython().run_line_magic('matplotlib', 'inline') get_ipython().run_line_magic('config', "InlineBackend.figure_format = 'retina'") import warnings import matplotlib.pyplot as plt from matplotlib.font_manager import fontManager as fm plt.style.use('seaborn') # 'seaborn-colorblind' plt.rc('axes', unicode_minus=False) warnings.simplefilter(action='ignore', category=FutureWarning) font_list_check = ['D2Coding', 'NanumGothicCoding', 'NanumGothic'] for font in font_list_check: font_counts = [ f for f in font_list_check if font in [_.name for _ in fm.ttflist]] if len(font_counts) > 1: print(f"found : {font}"); break plt.rc('font', family=font) # 폰트 적용하기 # In[2]: DATA_FILENAME = 'data/stock-twitter.pkl' import pandas as pd import yfinance as yf try: df_twiter = pd.read_pickle(DATA_FILENAME) except FileNotFoundError: df_twiter = yf.download('TWTR', start='2020-01-01', end='2022-12-31', progress=False) df_twiter.to_pickle(DATA_FILENAME) df_twiter.head(3) # In[3]: # plotly 활성화 하기 import cufflinks as cf from plotly.offline import iplot, init_notebook_mode cf.set_config_file(world_readable=True, theme='pearl', offline=True) # set up settings (run it once) init_notebook_mode() # 수집한 데이터를 활용하여 그림 그리기 (1.캔들차트, 2.거래량, 3,4.이동평균 곡선) # SMA : 단순 이동 평균 (Simple Moving Average) # EMA : 지수 이동 평균 (Exponential Moving Average) qf = cf.QuantFig(df_twiter, title='Twitter Stock Price', legend='top', name='twitter') qf.add_sma(periods=20, column='Close', color='red') qf.add_ema(periods=20, color='green') qf.add_volume() qf.iplot() #
# # ## **2 Back Testing 시작하기** # - **[BackTesting with PANDAS](https://towardsdatascience.com/backtest-trading-strategies-with-pandas-vectorized-backtesting-26001b0ba3a5)** # - **[다중 데이터 BackTesting](https://jsp-dev.tistory.com/104)** # - **[matplotlib Warning Error Fix](https://community.backtrader.com/topic/981/importerror-cannot-import-name-min_per_hour-when-trying-to-plot/8)** # - 단순 이동평균 전략등 다양한 전략을 간편하게 Simulation 가능 # - 보조적인 기술적 지표를 활용한 매매분석에 용이 # # ### **01 Loading the DataSet** # In[4]: DATA_FILENAME = 'data/stock-apple.pkl' import pandas as pd import yfinance as yf try: data_csv = pd.read_pickle(DATA_FILENAME) except FileNotFoundError: data_csv = yf.download('TWTR', start='2020-01-01', end='2022-12-31', progress=False) data_csv.to_pickle(DATA_FILENAME) data_csv.head(3) # ### **02 Converting The Price DataSet** # loading from CSV to **Pandas DataFrame** of the BackTrader DataSet. # - https://www.backtrader.com/docu/datafeed/ # In[5]: # Back Trading 전략에 DataFrame 데이터로 Simulation 합니다. import backtrader as bt data_stock = bt.feeds.PandasData(dataname = data_csv) type(data_stock) #
# # ## **3 1개 기준선을 활용한 Back Testing 1** # - 종가가 20일 SMA 보다 높으면 매수를 한다 # - 종가가 20일 SMA 보다 낮으면 보유한 주식을 매도한다 # - 주식 매매는 1주만 허용 된다 # # ### **01 Strategy SIGNAL Class** # - **SMA Signal's Class** 를 정의하고, BackTesting 진행한다 # In[6]: # Signal Class 를 정의합니다. # => 20일 이동평균선 전략을 정의 합니다. class SmaSignal(bt.Signal): params = (('period', 20), ) def __init__(self): self.lines.signal = self.data - bt.ind.SMA(period=self.p.period) # In[7]: # Back Testing 을 설정에 추가합니다 cerebro = bt.Cerebro(stdstats=False) cerebro.adddata(data_stock) # Add the data feed cerebro.broker.setcash(1000.0) cerebro.add_signal(bt.SIGNAL_LONG, SmaSignal) cerebro.addobserver(bt.observers.BuySell) cerebro.addobserver(bt.observers.Value) # Back Testing 실행 결과를 계산 합니다. print(f'Starting Portfolio Value : {cerebro.broker.getvalue():.2f}') cerebro.run() print(f'Final Portfolio Value: {cerebro.broker.getvalue():.2f}') # ### **02 Visualization** # - **[matplotlib Warning Error Fix](https://community.backtrader.com/topic/981/importerror-cannot-import-name-min_per_hour-when-trying-to-plot/8)** # - **[matplotlib Warning Error Fix2](https://community.backtrader.com/topic/2881/cannot-import-name-warnings-from-matplotlib-dates)** # In[8]: plt.rcParams['figure.dpi'] = 80 plt.rcParams['figure.figsize'] = [20, 6] cerebro.plot(iplot=False, volume=False) plt.show() #
# # ## **4 2개의 이동평균을 활용한 Back Testing** # - 단기 (10일) SMA 가 장기(30일) 보다 높아질 때 매수한다 # - 장기 (30일) SMA 가 단기(10일) 보다 높아질 때 매도한다 # # ### **01 Strategy SIGNAL Class** # - **SMA Signal's Class** 를 정의하고, BackTesting 진행한다 # In[9]: import backtrader as bt # 이동평균선 전략을 정의 합니다. class SmaCross(bt.Strategy): params = dict( pfast=10, # fast moving average pslow=30) # slow moving average def __init__(self): sma1 = bt.ind.SMA(period=self.p.pfast) sma2 = bt.ind.SMA(period=self.p.pslow) self.crossover = bt.ind.CrossOver(sma1, sma2) # crossover signal def next(self): if not self.position: # not in the market if self.crossover > 0: # if fast crosses slow to the upside self.buy() # enter long (매수 진행한다) elif self.crossover < 0: # in the market & cross to the downside self.close() # close long position (보유시 매도를 진행한다) # In[10]: # 위 Back Trading 전략에 DataFrame 데이터로 Simulation 합니다. cerebro = bt.Cerebro() # create a "Cerebro" engine instance cerebro.adddata(data_stock) # Add the data feed cerebro.addstrategy(SmaCross) # Add the trading strategy # backtest 를 실행한다. print(f'Starting Portfolio Value : {cerebro.broker.getvalue():.2f}') cerebro.run() # run it all print(f'Final Portfolio Value: {cerebro.broker.getvalue():.2f}') # ### **02 Visualization** # In[11]: plt.rcParams['figure.dpi'] = 300 plt.rcParams['figure.figsize'] = [20, 6] cerebro.plot(iplot=False, volume=False) plt.show() #
# # ## **5 1개 기준선을 활용한 Back Testing2** # - 종가가 20일 SMA 보다 높으면 매수를 한다 # - 종가가 20일 SMA 보다 낮으면 보유한 주식을 매도한다 # - 주식 매매는 1주만 허용 된다 # # ### **01 Strategy SIGNAL Class** # - **SMA Signal's Class** 를 정의하고, BackTesting 진행한다 # - 매수, 매도 실행시 세부조건을 설정한다 # In[12]: class SmaStrategy(bt.Strategy): params = (('ma_period', 20), ) def __init__(self): # keep track of pending orders/buy price/buy commission self.data_close = self.datas[0].close # Series 에서 close 추출 self.order = None self.price = None self.comm = None self.sma = bt.ind.SMA( # add a simple moving average indicator self.datas[0], period=self.params.ma_period) def log(self, txt): dt = self.datas[0].datetime.date(0).isoformat() print(f'{dt}, {txt}') def notify_order(self, order): if order.status in [order.Submitted, order.Accepted]: # order already submitted/accepted - no action required return # report executed order if order.status in [order.Completed]: if order.isbuy(): self.log(f'BUY EXECUTED --- Price: {order.executed.price:.2f}, Cost: {order.executed.value:.2f}, Commission: {order.executed.comm:.2f}') self.price = order.executed.price self.comm = order.executed.comm else: self.log(f'SELL EXECUTED --- Price: {order.executed.price:.2f}, Cost: {order.executed.value:.2f}, Commission: {order.executed.comm:.2f}') # report failed order elif order.status in [ order.Canceled, order.Margin, order.Rejected]: self.log('Order Failed') # set no pending order self.order = None def notify_trade(self, trade): if not trade.isclosed: return self.log(f'OPERATION RESULT --- Gross: {trade.pnl:.2f}, Net: {trade.pnlcomm:.2f}') def next(self): # do nothing if an order is pending if self.order: return # check if there is already a position if not self.position: # buy condition if self.data_close[0] > self.sma[0]: self.log(f'BUY CREATED --- Price: {self.data_close[0]:.2f}') self.order = self.buy() else: # sell condition if self.data_close[0] < self.sma[0]: self.log(f'SELL CREATED --- Price: {self.data_close[0]:.2f}') self.order = self.sell() # In[13]: cerebro = bt.Cerebro(stdstats = False) cerebro.adddata(data_stock) # Add the data feed cerebro.broker.setcash(1000.0) cerebro.addstrategy(SmaStrategy) cerebro.addobserver(bt.observers.BuySell) cerebro.addobserver(bt.observers.Value) print(f'Starting Portfolio Value: {cerebro.broker.getvalue():.2f}') cerebro.run() print(f'Final Portfolio Value: {cerebro.broker.getvalue():.2f}') # ### **02 Visualization** # - https://community.backtrader.com/topic/2881/cannot-import-name-warnings-from-matplotlib-dates # In[14]: cerebro.plot(iplot=False) plt.show()