from backtesting import Backtest, Strategy from backtesting.lib import crossover from backtesting.test import SMA, GOOG import sys import talib, numpy, time, random from itertools import product, combinations import pybithumb class CandlePatterns: # 매수 패턴 _positive_patterns = [ 'CDL3STARSINSOUTH', # Three Stars In The South 'CDL3WHITESOLDIERS', # 적삼병 'CDLCONCEALBABYSWALL', 'CDLDRAGONFLYDOJI', 'CDLLADDERBOTTOM', 'CDLMORNINGDOJISTAR', 'CDLMORNINGSTAR', 'CDLTAKURI', 'CDLHAMMER', ] # 매도 패턴 _negative_patterns = ['CDLEVENINGDOJISTAR', # 석별형 'CDL2CROWS', # 2봉 까마귀형 'CDL3BLACKCROWS', # 흑삼병 'CDLADVANCEBLOCK', # 블록형 : 매수 탄력 약화, 고점에서 경고 패턴 'CDLDARKCLOUDCOVER', 'CDLEVENINGDOJISTAR', 'CDLEVENINGSTAR', 'CDLGRAVESTONEDOJI', 'CDLHANGINGMAN', 'CDLIDENTICAL3CROWS', 'CDLINNECK', 'CDLHOMINGPIGEON', 'CDLMATCHINGLOW', 'CDLONNECK', 'CDLSHOOTINGSTAR', 'CDLUPSIDEGAP2CROWS', 'CDLINVERTEDHAMMER', ] # 중립 패턴 _fence_patterns = ['CDL3INSIDE', 'CDL3LINESTRIKE', 'CDL3OUTSIDE', 'CDLABANDONEDBABY', 'CDLBELTHOLD', # 상승/하락 샅바형 'CDLBREAKAWAY', 'CDLCLOSINGMARUBOZU', 'CDLCOUNTERATTACK', 'CDLCONCEALBABYSWALL', 'CDLENGULFING', 'CDLGAPSIDESIDEWHITE', 'CDLHARAMI', 'CDLHARAMICROSS', # 'CDLHIGHWAVE', # 꼬리나 머리털이 길때 'CDLHIKKAKE', 'CDLHIKKAKEMOD', 'CDLKICKING', 'CDLKICKINGBYLENGTH', # 'CDLLONGLEGGEDDOJI', # Long Legged Doji # 'CDLLONGLINE', # Long Line Candle # 'CDLMARUBOZU', # Marubozu 'CDLMATHOLD', 'CDLPIERCING', # 'CDLRICKSHAWMAN ', # 그냥 도지임 # 'CDLSHORTLINE', # Short Line Candle 5:5 'CDLRISEFALL3METHODS', 'CDLSEPARATINGLINES', # 'CDLSPINNINGTOP', # 그냥 도지임 'CDLSTALLEDPATTERN', 'CDLTASUKIGAP', # 'CDLTHRUSTING', # 지속형 # 'CDLTRISTAR', # 이 패턴은 거의 안나옴 추세 반전 패턴 'CDLUNIQUE3RIVER', 'CDLXSIDEGAP3METHODS', ] # 역 중립 패턴(음봉때 진입, 양봉때 탈출) _r_fence_patterns = ['CDLSTICKSANDWICH', ] def get_fence_patterns(self): return self._fence_patterns def get_r_fence_patterns(self): return self._r_fence_patterns # 중복 제거 def get_trade_patterns(self): return self._negative_patterns + self._positive_patterns def get_long_patterns(self): return self._positive_patterns def get_short_patterns(self): return self._negative_patterns class StrategyCandlePattern(Strategy): _use_patterns = None pattern_data = None _sl_percent = 0.03 # 2% # 캔들 패턴 cp = CandlePatterns() # 매수 패턴 _positive_patterns = cp.get_long_patterns() _negative_patterns = cp.get_short_patterns() _fence_patterns = cp.get_fence_patterns() _r_fence_patterns = cp.get_r_fence_patterns() def init(self): close = self.data.Close low = self.data.Low high = self.data.High open = self.data.Open data = [None] * len(self.data.Close) for p in self._use_patterns: f = getattr(talib, p) res_arr = f(open, high, low, close) # 100 is plus candle / -100 is minus candle for i in range(0, len(res_arr)): # print(p, res_arr[i]) if int(res_arr[i]) is not 0: data[i] = {p : int(res_arr[i])} self.pattern_data = data def next(self): idx = (self._broker._i)-1 # 스탑 로스 = 현재가격 - (현재가격*0.03) => 2퍼센트 스탑로스 if self.pattern_data[idx] is not None: pattern, value = list(self.pattern_data[idx].items())[0] sl = self.data.Close[-1] - (self.data.Close[-1] * self._sl_percent) if pattern in self._positive_patterns: # 매수 패턴 if not self.orders.is_long: self.buy(sl=sl) elif pattern in self._negative_patterns: # 매도 패턴 if self.orders.is_long: self.position.close() elif pattern in self._fence_patterns: # 중립 패턴 if int(value) > 0: if not self.orders.is_long: self.buy(sl=sl) elif int(value) < 0: if self.orders.is_long: self.position.close() # self.sell() elif pattern in self._r_fence_patterns: # 역중립 패턴(역 추세) if int(value) > 0: if self.orders.is_long: self.position.close() # self.sell() elif int(value) < 0: if not self.orders.is_long: self.buy(sl=sl) def data_columns_init(data): # data.reset_index(level=0, inplace=True) t_col = [] for c in data.columns: t_col.append(c.lower().capitalize()) data.columns = t_col start_time = time.time() ''' "day": "24H", "hour12": "12H", "hour6": "06H", "hour": "01H", "minute30": "30M", "minute10": "10M", "minute5": "05M", "minute3": "03M", ''' df = pybithumb.get_ohlcv('BTC', 'hour') # params : 종목, 시간 df = df[:-1] data_columns_init(df) df = df[-1440:] # 최근 두달 데이터 cash = 1000 commission = .005 top_profit = 0 top_cash = 0 data = df # GOOG # for test count = 0 cp = CandlePatterns() fence_patterns = cp.get_fence_patterns() + cp.get_r_fence_patterns() trade_patterns = cp.get_trade_patterns() filtered_patterns = [] long_patterns = cp.get_long_patterns() short_patterns = cp.get_short_patterns() # 베스트 패턴 best_patterns = ['CDLUNIQUE3RIVER', 'CDLSHOOTINGSTAR', 'CDL3BLACKCROWS', 'CDL3STARSINSOUTH', 'CDLXSIDEGAP3METHODS', 'CDLHARAMI', 'CDLGRAVESTONEDOJI', 'CDLONNECK', 'CDLDARKCLOUDCOVER', 'CDLEVENINGDOJISTAR'] StrategyCandlePattern._use_patterns = best_patterns bt = Backtest(data, StrategyCandlePattern, cash=cash, commission=commission) bt.run() if bt._results['Return [%]'] > top_profit: top_profit = bt._results['Return [%]'] top_cash = bt._results['Equity Final [$]'] print("최종 금액 : %0.2f" % bt._results['Equity Final [$]']) print("총 수익률 : %0.2f%%" % bt._results['Return [%]']) print('-' * 60) bt.plot() # 랜덤 픽 while True: r_long_patterns = random.choices(long_patterns, k=random.randrange(1, len(long_patterns))) r_short_patterns = random.choices(short_patterns, k=random.randrange(1, len(short_patterns))) r_fence_patterns = random.choices(fence_patterns, k=random.randrange(0, len(fence_patterns))) filtered_patterns = list(set(r_long_patterns + r_short_patterns + r_fence_patterns)) StrategyCandlePattern._use_patterns = filtered_patterns bt = Backtest(data, StrategyCandlePattern, cash=cash, commission=commission) bt.run() if bt._results['Return [%]'] > top_profit: top_profit = bt._results['Return [%]'] top_cash = bt._results['Equity Final [$]'] print("최종 금액 : %0.2f" % bt._results['Equity Final [$]']) print("총 수익률 : %0.2f%%" % bt._results['Return [%]']) print(StrategyCandlePattern._use_patterns) print('-' * 60) bt.plot() pass # Filtering Trade Patterns for pattern in list(combinations(trade_patterns, 2)): StrategyCandlePattern._use_patterns = pattern bt = Backtest(data, StrategyCandlePattern, cash=cash, commission=commission) bt.run() # 해당 패턴이 데이터에 존재 할 경우 추가 if bt._results['Return [%]'] != 0: filtered_patterns += pattern filtered_patterns = list(set(filtered_patterns)) # Filtering Fence Patterns for pattern in fence_patterns: StrategyCandlePattern._use_patterns = [pattern] bt = Backtest(data, StrategyCandlePattern, cash=cash, commission=commission) bt.run() # 수익률, 승률, 승패 등으로 필터링 기준 조정 => 승률이 좋던가, 수익이 좋던가 if bt._results['Return [%]'] > top_profit: top_profit = bt._results['Return [%]'] top_cash = bt._results['Equity Final [$]'] print("최종 금액 : %0.2f" % bt._results['Equity Final [$]']) print("총 수익률 : %0.2f%%" % bt._results['Return [%]']) print(StrategyCandlePattern._use_patterns) print('-' * 60) # 해당 패턴이 데이터에 존재 할 경우 추가 if bt._results['Return [%]'] != 0: filtered_patterns.append(pattern) # 모든 경우의 수 for index in list(range(2, len(filtered_patterns) + 1)): # for index in list(range(len(all_patterns), len(all_patterns) + 1)): for patterns in list(combinations(filtered_patterns, index)): StrategyCandlePattern._use_patterns = patterns bt = Backtest(data, StrategyCandlePattern, cash=cash, commission=commission) bt.run() if count < len(StrategyCandlePattern._use_patterns): count = len(StrategyCandlePattern._use_patterns) print(len(StrategyCandlePattern._use_patterns)) e = int(time.time() - start_time) print('{:02d}:{:02d}:{:02d}'.format(e // 3600, (e % 3600 // 60), e % 60)) if bt._results['Return [%]'] > top_profit: top_profit = bt._results['Return [%]'] top_cash = bt._results['Equity Final [$]'] # print(bt._results) print("최종 금액 : %0.2f" % bt._results['Equity Final [$]']) print("총 수익률 : %0.2f%%" % bt._results['Return [%]']) print(StrategyCandlePattern._use_patterns) print('-'*60) bt.plot() del bt e = int(time.time() - start_time) print('{:02d}:{:02d}:{:02d}'.format(e // 3600, (e % 3600 // 60), e % 60)) ''' 사이클 주기 : 3달? 매수/매도 패턴 쌍으로 수익 실현 횟수를 수치화 하여 확률적 접근으로? => 패턴별 승패수 = 승률제 시간봉 두달 37.62% ['CDLHOMINGPIGEON', 'CDLSHOOTINGSTAR', 'CDLRISEFALL3METHODS', 'CDLINNECK', 'CDLXSIDEGAP3METHODS', 'CDLLADDERBOTTOM', 'CDLABANDONEDBABY', 'CDL3LINESTRIKE', 'CDLTASUKIGAP', 'CDL3STARSINSOUTH'] 시간봉 두달 35퍼 : ['CDL3WHITESOLDIERS', 'CDL3WHITESOLDIERS', 'CDLEVENINGDOJISTAR', 'CDL2CROWS', 'CDLSHOOTINGSTAR', 'CDLINNECK', 'CDLONNECK', 'CDLADVANCEBLOCK', 'CDLIDENTICAL3CROWS', 'CDLDARKCLOUDCOVER', 'CDLINNECK', 'CDL3LINESTRIKE', 'CDLCONCEALBABYSWALL', 'CDLXSIDEGAP3METHODS'] 5분봉 일주일 6퍼 : ('CDLMORNINGDOJISTAR', 'CDLHANGINGMAN', 'CDLHIKKAKEMOD', 'CDLSEPARATINGLINES', 'CDLUNIQUE3RIVER') - 캔들 신호에 따라 매매, 보조지표는 시그널로 - 패턴별 신뢰도 측정 => 이브닝스타 - 5봉 뒤 가격 다운(종가) - 해당 데이터에 패턴이 있는지 체크 후 필터 리스트에 추가 - 매매 패턴은 그래도 사용하고, 보조지표 기반 시그널로 작동 => 패턴별 가중치가 다르게 => 슈팅스타 백점 등 - 보조지표를 베이스 시그널로 활용 - 시간대별로 반복문 추가 - 최대 패턴 갯수 구하기 # 매수/매도 패턴끼리 먼저 조합하여 경우의 수 도출 # 수익이 있는 캔들 패턴만 보조지표와 조합 => 중립 캔들 패턴만, 매수/매도 시그널 패턴은 그냥 조합 사용 # 패턴을 시그널로 활용(사용중인 모든 패턴이 True 일때만 매수/ False 매도로 구성) => 보류 => 보조지표를 시그널로 활용 # 봉별 단일 시그널로 활용할 지, 복합 시그널(두개 이상)로 활용 할지.. # 매매 패턴의 경우 데이터에 존재하는지 체크 후 패턴 리스트에 추가 '''