335 lines
13 KiB
Python
335 lines
13 KiB
Python
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 매도로 구성) => 보류 => 보조지표를 시그널로 활용
|
|
# 봉별 단일 시그널로 활용할 지, 복합 시그널(두개 이상)로 활용 할지..
|
|
# 매매 패턴의 경우 데이터에 존재하는지 체크 후 패턴 리스트에 추가
|
|
''' |