├── Larry_williams1.py ├── Larry_williams2.py ├── Larry_williams3.py ├── Larry_williams4.py ├── README.md ├── TR_LW.py ├── TR_LW_1.py ├── arry_williams4.py ├── get_upbit_candles.py ├── get_upbit_day_candles.py ├── get_upbit_ticks.py ├── my_candle.py ├── my_util.py └── sim_data ├── BTC_day-2017-09-25-2020-11-12.csv ├── EOS_day-2017-09-25-2020-11-12.csv ├── ETH_day-2017-09-25-2020-11-12.csv ├── STEEM_day-2017-09-25-2020-11-15.csv └── XRP_day-2017-09-25-2020-11-12.csv /Larry_williams1.py: -------------------------------------------------------------------------------- 1 | # date : 2020/11/18 2 | # Larry Willams 변동성 돌파 시뮬레이션 3 | # 4 | # 보다 자세한 내용을 아래 tistory 참고 5 | # https://money-expert.tistory.com/34 6 | # 7 | 8 | import json 9 | import requests 10 | import csv 11 | 12 | # 13 | # for read data from cvs 14 | # 15 | # row : value list 16 | def get_new_item(keys, row) : 17 | data = {} 18 | for i in range(len(row)) : 19 | data[keys[i]] = row[i] 20 | return data 21 | 22 | # 첫 줄은 title이라고 가정, 이후에 title 값을 key로 갖는 dict로 읽기 23 | def read_csv_to_dict(fname) : 24 | data = [] 25 | keys =[] 26 | first = 1 27 | with open(fname, 'r', encoding='UTF8') as FILE : 28 | csv_reader = csv.reader(FILE, delimiter=',', quotechar='"') 29 | for row in csv_reader : 30 | if first : # make dict keys 31 | keys = row.copy() 32 | # for key in row : 33 | # keys .append(key) 34 | first = 0 35 | else : 36 | data.append(get_new_item(keys, row)) 37 | return data 38 | 39 | # 40 | # for writing data to cvs format 41 | # 42 | def save_to_file_csv(file_name, data) : 43 | with open(file_name,'w',encoding="cp949") as make_file: 44 | # title 저장 45 | vals = data[0].keys() 46 | ss = '' 47 | for val in vals: 48 | val = val.replace(',','') 49 | ss += (val + ',') 50 | ss += '\n' 51 | make_file.write(ss) 52 | 53 | for dt in data: 54 | vals = dt.values() 55 | ss = '' 56 | for val in vals: 57 | sval = str(val) 58 | sval = sval.replace(',','') 59 | ss += (sval + ',') 60 | ss += '\n' 61 | make_file.write(ss) 62 | make_file.close() 63 | 64 | 65 | # basic functions for candle 66 | class Candle : 67 | def __init__(self) : 68 | self.clear() 69 | 70 | def add(self, info) : 71 | self.vol += info['vol'] 72 | self.date = info['date'] 73 | self.time = '' 74 | if 'time' in info : 75 | self.time = info['time'] 76 | if self.open == 0 : 77 | self.open = info['open'] 78 | self.high = info['high'] 79 | self.low = info['low'] 80 | self.close = info['close'] 81 | if self.high < info['high'] : 82 | self.high = info['high'] 83 | if self.low > info['low'] : 84 | self.low = info['low'] 85 | self.close = info['close'] 86 | 87 | if info['rate'] != '0' : 88 | # 수정 주가 대상인 경우에만 반영 89 | # todo 90 | self.rate = float(info['rate']) 91 | self.rate_type = int(info['rate_type']) 92 | 93 | def clear(self) : 94 | self.date = '' 95 | self.time = '' 96 | self.open = 0 97 | self.high = 0 98 | self.low = 0 99 | self.close = 0 100 | self.vol = 0 101 | self.rate = 0 102 | self.rate_type = 0 103 | 104 | 105 | class TR_LW() : 106 | def __init__(self, interval, num) : 107 | self.interval = interval # min', 'day' 108 | self.interval_num = num # 1, 5, 20, 60 109 | self.pre = None 110 | self.cur = None 111 | self.k = 0.5 112 | self.bought = 0 # 현재 매수 중인지 113 | self.history = [] 114 | 115 | def update_new_range(self, cur) : # 116 | self.pre = cur 117 | self.history.append(cur) 118 | self.range = self.pre.high - self.pre.low # 전 candle의 range 119 | 120 | def is_enter_candle(self, candle) : 121 | enter = 0 122 | buy_price = (candle.open + self.range * self.k) 123 | if candle.high > buy_price : # 최고가가 buy_price보다 높으면 매수되었다고 가정 124 | enter = 1 125 | 126 | # 오늘 candle update 127 | self.update_new_range(candle) 128 | 129 | if enter : 130 | return buy_price 131 | return 0 132 | 133 | 134 | def make_candle_info(data) : 135 | dt = data['candleDateTimeKst'].split('T') 136 | pre = Candle() 137 | pre.date = dt[0] 138 | dtt = dt[1].split('+') 139 | pre.time = dtt[0] 140 | pre.open = float(data['openingPrice']) 141 | pre.high = float(data['highPrice']) 142 | pre.low = float(data['lowPrice']) 143 | pre.close = float(data['tradePrice']) 144 | pre.vol = float(data['candleAccTradeVolume']) 145 | return pre 146 | 147 | 148 | def simulation(ticker, sim_file, tr_login) : 149 | # simulation 데이터를 읽는다. 150 | candle_data = read_csv_to_dict(sim_file) 151 | if candle_data == [] : 152 | print('not exist ', sim_file) 153 | return 154 | 155 | # simulation 중 파일에 저장할 변수들 추가 156 | candle_data[-1]['buying'] = 0 # buy 여부 저장 157 | candle_data[-1]['profit'] = 0 # profit 저장 158 | candle_data[-1]['total_profit'] = 0 # total_profit 저장 159 | candle_data[-1]['dd'] = 0 # downd draw 저장 160 | 161 | # 제일 마지막 일봉이 어제 candle 162 | yest = candle_data[len(candle_data)-1] 163 | 164 | # 가장 오래된 일봉 데이터로 candle을 만든다. 165 | candle = make_candle_info(yest) 166 | 167 | # 어제 일봉 setting 168 | tr_logic.update_new_range(candle) 169 | 170 | deposit = 1000000 # 1회 최대 1백만원 매수 171 | balance = deposit # 현재 잔고 172 | 173 | bought = 0 174 | num_trading = 0 175 | total_profit = 0 176 | total_fee = 0 177 | num_winning = 0 178 | num_losing = 0 179 | max_loss = 0 180 | max_gain = -10000000 181 | mdd = 0 182 | trading_fee = 0.00035 # 0.1% 183 | 184 | for i in range(len(candle_data)-2,-1,-1) :# 내림차순으로 저장되어 있으므로 뒤에서 한개씩 185 | #현재 simulation 데이터로 candle을 만든다. 186 | candle = make_candle_info(candle_data[i]) 187 | 188 | # simulation 중 파일에 저장할 변수들 추가 189 | # simulation 결과를 검증하기 위하여 buy하는 일봉에 대하여 buying 가격, 수익, dd 값을 저장 190 | candle_data[i]['buying'] = 0 # buy 여부 저장 191 | candle_data[i]['profit'] = 0 # profit 저장 192 | candle_data[i]['total_profit'] = 0 # total_profit 저장 193 | candle_data[i]['dd'] = 0 # dd(draw down) 저장 194 | 195 | buy_price = tr_logic.is_enter_candle(candle) 196 | if buy_price > 0 : # 매수조건임 197 | 198 | # 1. 매수 금액 결정, min(balance, deposit) 초기 deposit 금액 혹은 balance가 초기 deposit 이하이면 balance 199 | buying_amount = min(balance, deposit) 200 | num_buying = buying_amount / buy_price # 매수 주수 201 | 202 | # 2. 일봉 종가에 판다고 가정 203 | profit = (candle.close - buy_price) * num_buying 204 | balance += profit 205 | 206 | # 3. 매수 수수료 207 | fee = num_buying * buy_price * trading_fee 208 | total_fee += fee 209 | balance -= fee 210 | # 매도 수수료 211 | fee = num_buying * candle.close * trading_fee 212 | total_fee += fee 213 | balance -= fee 214 | 215 | 216 | # 4. update statistics 217 | num_trading += 1 # 매수한 주문 수 218 | total_profit += profit # 누적 수익 219 | if profit > 0 : 220 | num_winning += 1 221 | else : 222 | num_losing += 1 223 | 224 | # 최대 손실, 최고 이익 값 update 225 | if max_loss > total_profit : 226 | max_loss = total_profit 227 | if max_gain < total_profit : 228 | max_gain = total_profit 229 | 230 | # draw down 계산 231 | dd = 100.0 * (max_gain - total_profit) / max_gain 232 | if dd > mdd : 233 | mdd = dd # mdd 계산 234 | 235 | # 5. adding log 236 | candle_data[i]['profit'] = profit # buying 결과 수익 추가 237 | candle_data[i]['total_profit'] = total_profit # 238 | candle_data[i]['buying'] = buy_price # buying한 매수가 추가 239 | candle_data[i]['dd'] = dd # draw down 값 240 | 241 | 242 | fname = ticker + '-LW-result-all.csv' 243 | save_to_file_csv(fname, candle_data) 244 | 245 | # print simulation results 246 | print('ticker : ', ticker) 247 | print('total # trading : ', num_trading) 248 | print('total profit : ', format(total_profit, ",.2f")) 249 | print('trading fee : ', format(total_fee, ",.2f")) 250 | print('total Net Profit : ', format(total_profit - total_fee, ",.2f")) 251 | print('# winning : ', num_winning) 252 | print('# losing : ', num_losing) 253 | print('MDD : ', format(mdd,'3.2f')) 254 | print('max loss : ', format(max_loss, '10,.2f')) 255 | print('max gain : ', format(max_gain, '10,.2f')) 256 | 257 | # save simulation results 258 | fname = ticker + '-LW-result-all.txt' 259 | file = open(fname, 'w') 260 | file.write('ticker : {0}\n'.format(ticker)) 261 | file.write('total # trading : {0} \n total profit: {1}\n'.format(num_trading, total_profit)) 262 | s = 'trading fee : ' + format(total_fee, ".2f") + '\n' 263 | file.write(s) 264 | s ='total profit with fee :' + format((total_profit - total_fee), ".2f") + '\n' 265 | file.write(s) 266 | file.write('# winning : {0} # losing: {1} \n'.format(num_winning, num_losing)) 267 | file.write('MDD: {0} max loss: {1} max gain : {2} \n'.format(mdd, max_loss, max_gain)) 268 | file.close() 269 | 270 | print('exit') 271 | 272 | 273 | if __name__ == '__main__': 274 | 275 | # back data 276 | fname = '.\\sim_data\\BTC_day-2017-09-25-2020-11-12.csv' 277 | ticker = 'KRW-BTC' 278 | 279 | # Larry William 변동성 돌파, 일봉 사용 280 | tr_logic = TR_LW('day', 1) 281 | 282 | simulation(ticker, fname, tr_logic) 283 | print('') 284 | -------------------------------------------------------------------------------- /Larry_williams2.py: -------------------------------------------------------------------------------- 1 | # date : 2020/11/19 2 | # Larry Willams 변동성 돌파 시뮬레이션 3 | # 4 | # 보다 자세한 내용을 아래 tistory 참고 5 | # https://money-expert.tistory.com/35 6 | # 7 | 8 | import json 9 | import csv 10 | 11 | from my_util import * 12 | from my_candle import * 13 | from TR_LW import * 14 | 15 | def save_sim_result(fname, ticker, stat) : 16 | file = open(fname, 'w') 17 | file.write('total # trading : '+ str(stat.num_trading)) 18 | file.write('total profit : '+ format(stat.total_profit, ",.2f")) 19 | file.write('trading fee : '+ format(stat.total_fee, ",.2f")) 20 | file.write('total Net Profit : '+ format(stat.total_profit - stat.total_fee, ",.2f")) 21 | file.write('# winning : '+ str(stat.num_winning)) 22 | file.write('# losing : '+ str(stat.num_losing)) 23 | file.write('MDD : '+ format(stat.mdd,'3.2f')) 24 | file.write('max loss : '+ format(stat.max_loss, '10,.2f')) 25 | file.write('max gain : '+ format(stat.max_gain, '10,.2f')) 26 | file.close() 27 | 28 | def print_sim_result(ticker, stat) : 29 | print('ticker : ', ticker) 30 | print('total # trading : ', stat.num_trading) 31 | print('total profit : ', format(stat.total_profit, ",.2f")) 32 | print('trading fee : ', format(stat.total_fee, ",.2f")) 33 | print('total Net Profit : ', format(stat.total_profit - stat.total_fee, ",.2f")) 34 | print('# winning : ', stat.num_winning) 35 | print('# losing : ', stat.num_losing) 36 | print('MDD : ', format(stat.mdd,'3.2f')) 37 | print('max loss : ', format(stat.max_loss, '10,.2f')) 38 | print('max gain : ', format(stat.max_gain, '10,.2f')) 39 | 40 | def save_mid_values(candle_data, buy_price, sell_price, profit, total_profit, dd) : 41 | candle_data['buy_price'] = buy_price # buy 여부 저장 42 | candle_data['sell_price'] = sell_price # buy 여부 저장 43 | candle_data['profit'] = profit # profit 저장 44 | candle_data['total_profit'] = total_profit # total_profit 저장 45 | candle_data['dd'] = dd # downd draw 저장 46 | 47 | def handle_fee(balance, stat, num_buying, price, trading_fee) : 48 | fee = num_buying * price * trading_fee 49 | stat.total_fee += fee 50 | balance -= fee 51 | return balance 52 | 53 | # 54 | # simulation 통계와 관련된 함수들 55 | # 56 | class sim_stat : 57 | def __init__(self) : 58 | self.num_trading = 0 59 | self.total_profit = 0 60 | self.total_fee = 0 61 | self.num_winning = 0 62 | self.num_losing = 0 63 | self.max_loss = 0 64 | self.max_gain = -10000000 65 | self.mdd = 0 66 | 67 | def update_stat(self, profit) : 68 | self.total_profit += profit # 누적 수익 69 | if profit > 0 : 70 | self.num_winning += 1 71 | else : 72 | self.num_losing += 1 73 | 74 | # 최대 손실, 최고 이익 값 update 75 | if self.max_loss > self.total_profit : 76 | self.max_loss = self.total_profit 77 | if self.max_gain < self.total_profit : 78 | self.max_gain = self.total_profit 79 | 80 | # draw down 계산 81 | dd = 100.0 * (self.max_gain - self.total_profit) / self.max_gain 82 | if dd > self.mdd : 83 | self.mdd = dd # mdd 계산 84 | return dd 85 | 86 | def simulation(ticker, sim_file, tr_login) : 87 | # simulation 데이터를 읽는다. 88 | candle_data = read_csv_to_dict(sim_file) 89 | if candle_data == [] : 90 | print('not exist ', sim_file) 91 | return 92 | 93 | # simulation 중 파일에 저장할 변수들 추가 94 | save_mid_values(candle_data[-1], 0, 0, 0, 0, 0) 95 | 96 | # 제일 마지막 일봉이 어제 candle 97 | yest = candle_data[len(candle_data)-1] 98 | 99 | # 가장 오래된 일봉 데이터로 candle을 만든다. 100 | candle = Candle() 101 | candle.new_candle(yest) 102 | 103 | # 어제 일봉 setting 104 | tr_logic.update_new_range(candle) 105 | 106 | deposit = 1000000 # 1회 최대 1백만원 매수 107 | balance = deposit # 현재 잔고 108 | 109 | bought = 0 # 매수 중인지 여부 110 | trading_fee = 0.00035 # 매매 수수료 111 | num_buying = 0 # 현재 매수 수량 112 | 113 | stat = sim_stat() # 통계 변수용 class 114 | 115 | for i in range(len(candle_data)-2,-1,-1) :# 내림차순으로 저장되어 있으므로 뒤에서 한개씩 116 | #현재 simulation 데이터로 candle을 만든다. 117 | candle = Candle() 118 | candle.new_candle(candle_data[i]) 119 | 120 | # simulation 중 파일에 저장할 변수들 추가 121 | save_mid_values(candle_data[i], 0, 0, 0, 0, 0) 122 | 123 | buy_price = tr_logic.is_enter_condition(candle) 124 | if buy_price > 0 : # 매수조건임 125 | # 1. 매수 금액 결정, min(balance, deposit) 초기 deposit 금액 혹은 balance가 초기 deposit 이하이면 balance 126 | buying_amount = min(balance, deposit) 127 | num_buying = buying_amount / buy_price # 매수 주수 128 | 129 | # 3. 수수료 130 | balance = handle_fee(balance, stat, num_buying, buy_price, trading_fee) # 매수 수수료 131 | 132 | # 4. update statistics 133 | stat.num_trading += 1 # 매수한 주문 수 134 | 135 | # 5. adding log 136 | save_mid_values(candle_data[i], buy_price, candle_data[i]['sell_price'], 0, stat.total_profit, 0) 137 | 138 | # 매수 중이라고 설정 139 | bought = 1 140 | 141 | if bought : # exit 조건 확인 142 | sell_price = tr_logic.is_exit_condition(candle) 143 | if sell_price > 0 : # exit 조건 144 | # 2. 일봉 종가에 판다고 가정 145 | profit = (candle.close - buy_price) * num_buying 146 | balance += profit 147 | 148 | # 3. 매도 수수료 149 | balance = handle_fee(balance, stat, num_buying, sell_price, trading_fee) # 매도 수수료 150 | 151 | # 4. update statistics 152 | dd = stat.update_stat(profit) 153 | 154 | # 5. adding log 155 | save_mid_values(candle_data[i], candle_data[i]['buy_price'], sell_price, profit, stat.total_profit, dd) 156 | 157 | # 매도 완료 158 | bought = 0 159 | 160 | fname = ticker + '-LW-result-all.csv' 161 | save_to_file_csv(fname, candle_data) 162 | 163 | # print simulation results 164 | print_sim_result(ticker, stat) 165 | 166 | # save simulation results 167 | fname = ticker + '-LW-result-all.txt' 168 | save_sim_result(fname, ticker, stat) 169 | 170 | print('exit') 171 | 172 | 173 | if __name__ == '__main__': 174 | 175 | # back data 176 | fname = '.\\sim_data\\BTC_day-2017-09-25-2020-11-12.csv' 177 | ticker = 'KRW-BTC' 178 | 179 | # Larry William 변동성 돌파, 일봉 사용 180 | tr_logic = TR_LW('day', 1) 181 | 182 | simulation(ticker, fname, tr_logic) 183 | print('') 184 | -------------------------------------------------------------------------------- /Larry_williams3.py: -------------------------------------------------------------------------------- 1 | # Larry Williams 변동성 돌파전략(4) 2 | # chart 그리기 3 | # 4 | # 보다 자세한 내용을 아래 tistory 참고 5 | # https://money-expert.tistory.com/36 6 | # https://money-expert.tistory.com/37 7 | 8 | import json 9 | import csv 10 | from datetime import datetime 11 | import time 12 | import pandas as pd 13 | 14 | from my_util import * 15 | from my_candle import * 16 | from plot import * 17 | from TR_LW import * 18 | 19 | import matplotlib.pyplot as plt 20 | from pandas_datareader import data as pdr 21 | import plotly.offline as py_offline 22 | import plotly.graph_objs as go 23 | 24 | def save_sim_result(fname, ticker, stat) : 25 | file = open(fname, 'w') 26 | file.write('total # trading : '+ str(stat.num_trading)) 27 | file.write('total profit : '+ format(stat.total_profit, ",.2f")) 28 | file.write('trading fee : '+ format(stat.total_fee, ",.2f")) 29 | file.write('total Net Profit : '+ format(stat.total_profit - stat.total_fee, ",.2f")) 30 | file.write('# winning : '+ str(stat.num_winning)) 31 | file.write('# losing : '+ str(stat.num_losing)) 32 | file.write('MDD : '+ format(stat.mdd,'3.2f')) 33 | file.write('max loss : '+ format(stat.max_loss, '10,.2f')) 34 | file.write('max gain : '+ format(stat.max_gain, '10,.2f')) 35 | file.close() 36 | 37 | def print_sim_result(ticker, stat) : 38 | print('ticker : ', ticker) 39 | print('total # trading : ', stat.num_trading) 40 | print('total profit : ', format(stat.total_profit, ",.2f")) 41 | print('trading fee : ', format(stat.total_fee, ",.2f")) 42 | print('total Net Profit : ', format(stat.total_profit - stat.total_fee, ",.2f")) 43 | print('# winning : ', stat.num_winning) 44 | print('# losing : ', stat.num_losing) 45 | print('MDD : ', format(stat.mdd,'3.2f')) 46 | print('max loss : ', format(stat.max_loss, '10,.2f')) 47 | print('max gain : ', format(stat.max_gain, '10,.2f')) 48 | 49 | def save_mid_values(candle_data, buy_price, sell_price, profit, total_profit, dd) : 50 | candle_data['buy_price'] = buy_price # buy 여부 저장 51 | candle_data['sell_price'] = sell_price # buy 여부 저장 52 | candle_data['profit'] = profit # profit 저장 53 | candle_data['total_profit'] = total_profit # total_profit 저장 54 | candle_data['dd'] = dd # downd draw 저장 55 | candle_data['trend'] = trend # 추세값 저장 56 | candle_data['ma7'] = ma7 # downd draw 저장 57 | 58 | def handle_fee(balance, stat, num_buying, price, trading_fee) : 59 | fee = num_buying * price * trading_fee 60 | stat.total_fee += fee 61 | balance -= fee 62 | return balance 63 | 64 | class sim_stat : 65 | def __init__(self, init_seed) : 66 | self.init_seed = init_seed 67 | self.num_trading = 0 68 | self.total_profit = 0 69 | self.total_fee = 0 70 | self.num_winning = 0 71 | self.num_losing = 0 72 | self.max_loss = 0 73 | self.max_gain = -10000000 74 | self.mdd = 0 75 | 76 | def get_profit_percent(self) : 77 | return self.total_profit / self.init_seed * 100 78 | 79 | def update_stat(self, profit) : 80 | self.total_profit += profit # 누적 수익 81 | if profit > 0 : 82 | self.num_winning += 1 83 | else : 84 | self.num_losing += 1 85 | 86 | # 최대 손실, 최고 이익 값 update 87 | if self.max_loss > self.total_profit : 88 | self.max_loss = self.total_profit 89 | if self.max_gain < self.total_profit : 90 | self.max_gain = self.total_profit 91 | 92 | # draw down 계산 93 | dd = 100.0 * (self.max_gain - self.total_profit) / (self.init_seed + self.max_gain) 94 | if dd > self.mdd : 95 | self.mdd = dd # mdd 계산 96 | return dd 97 | 98 | def save_ohlc(fname, candle_data) : 99 | with open(fname,'w',encoding="cp949") as make_file: 100 | pos = 0 101 | ss = 'Date,Open,High,Low,Close\n' 102 | make_file.write(ss) 103 | for i in range(len(candle_data)-1,-1,-1) :# 내림차순으로 저장되어 있으므로 뒤에서 한개씩 104 | dt = candle_data[i]['candleDateTimeKst'].split('+') 105 | dttime = candle_data[i]['candleDateTimeKst'] # 일단 datetime 모두 106 | if 0 : # only time 107 | dtt = dt[0].split('T') # time만 저장하기 108 | dttime = dtt[1] 109 | if 0 : # only date 110 | dtt = dt[0].split('T') # time만 저장하기 111 | dttime = dtt[0] 112 | 113 | ss = dttime + ',' + candle_data[i]['openingPrice'] + ',' + candle_data[i]['highPrice'] + ',' \ 114 | + candle_data[i]['lowPrice'] + ',' + candle_data[i]['tradePrice'] + '\n'# close 115 | make_file.write(ss) 116 | make_file.close() 117 | 118 | def draw_chart_plotly(candle_data, ticker, stat) : 119 | fname = 'chart.csv' 120 | save_ohlc(fname, candle_data) 121 | 122 | data = pd.read_csv(fname) 123 | 124 | # plotly를 이용하여 candelstkck에 text 출력하는 부분 설명한 글 125 | # https://info.cloudquant.com/2019/08/candlestick_plotly/ 126 | annotations = [] 127 | 128 | for i in range(len(candle_data)-1, -1, -1) :# 129 | text = 'L' 130 | if candle_data[i]['profit'] > 0 : # profit 이면 P lost이면 L 출력 131 | text = 'P' 132 | if candle_data[i]['profit'] != 0 : 133 | annotations.append(go.layout.Annotation(x=candle_data[i]['candleDateTimeKst'], 134 | y=candle_data[i]['highPrice'], 135 | xshift=1, # x 축 기준으로 오른쪽으로 x칸 이동 136 | yshift=10, # y 축 기준으로 오른쪽으로 y칸 이동 137 | showarrow=False, 138 | text=text)) 139 | 140 | # draw할 layout 생성 141 | width = len(candle_data) * 10 142 | layout = dict( 143 | title=ticker+':: # tradings : ' + str(stat.num_trading) + ' profit : ' + format(stat.get_profit_percent(), '.2f') + '% mdd : ' + format(stat.mdd, '.2f'), 144 | xaxis=go.layout.XAxis(title=go.layout.xaxis.Title( text="Time"), rangeslider=dict (visible = False)), 145 | yaxis=go.layout.YAxis(title=go.layout.yaxis.Title( text="Price")), 146 | width=width, 147 | height=800, 148 | annotations=annotations 149 | ) 150 | data_candle = go.Candlestick(x=data.Date,open=data.Open,high=data.High,low=data.Low,close=data.Close) 151 | data = [data_candle] 152 | 153 | fig = go.Figure(data=data,layout=layout) 154 | fig.show() 155 | 156 | def simulation(ticker, sim_file, tr_login) : 157 | # simulation 데이터를 읽는다. 158 | candle_data = read_csv_to_dict(sim_file) 159 | if candle_data == [] : 160 | print('not exist ', sim_file) 161 | return 162 | 163 | # simulation 중 파일에 저장할 변수들 추가 164 | save_mid_values(candle_data[-1], 0, 0, 0, 0, 0, 0, 0) 165 | 166 | # 제일 마지막 일봉이 어제 candle 167 | yest = candle_data[len(candle_data)-1] 168 | 169 | # 가장 오래된 일봉 데이터로 candle을 만든다. 170 | candle = Candle() 171 | candle.new_candle(yest) 172 | 173 | # 어제 일봉 setting 174 | tr_logic.update_new_range(candle) 175 | 176 | deposit = 1000000 # 1회 최대 1백만원 매수 177 | balance = deposit # 현재 잔고 178 | 179 | bought = 0 # 매수 중인지 여부 180 | trading_fee = 0.00035 # 매매 수수료 181 | num_buying = 0 # 현재 매수 수량 182 | 183 | stat = sim_stat(deposit) # 통계 변수용 class 184 | 185 | for i in range(len(candle_data)-2,-1,-1) :# 내림차순으로 저장되어 있으므로 뒤에서 한개씩 186 | #현재 simulation 데이터로 candle을 만든다. 187 | candle = Candle() 188 | candle.new_candle(candle_data[i]) 189 | 190 | # simulation 중 파일에 저장할 변수들 추가 191 | trend = tr_logic.get_trend() 192 | ma7 = tr_logic.get_MA(7) 193 | save_mid_values(candle_data[i], 0, 0, 0, 0, 0, trend, ma7) 194 | 195 | buy_price = tr_logic.is_enter_condition(candle) 196 | if buy_price > 0 : # 매수조건임 197 | # 1. 매수 금액 결정, min(balance, deposit) 초기 deposit 금액 혹은 balance가 초기 deposit 이하이면 balance 198 | buying_amount = min(balance, deposit) 199 | num_buying = buying_amount / buy_price # 매수 주수 200 | 201 | # 3. 수수료 202 | balance = handle_fee(balance, stat, num_buying, buy_price, trading_fee) # 매수 수수료 203 | 204 | # 4. update statistics 205 | stat.num_trading += 1 # 매수한 주문 수 206 | 207 | # 5. adding log 208 | save_mid_values(candle_data[i], buy_price, candle_data[i]['sell_price'], 0, stat.total_profit, 0, candle_data[i]['trend'], candle_data[i]['ma7']) 209 | 210 | # 매수 중이라고 설정 211 | bought = 1 212 | 213 | if bought : # exit 조건 확인 214 | sell_price = tr_logic.is_exit_condition(candle) 215 | if sell_price > 0 : # exit 조건 216 | # 2. 일봉 종가에 판다고 가정 217 | profit = (candle.close - buy_price) * num_buying 218 | balance += profit 219 | 220 | # 3. 매도 수수료 221 | balance = handle_fee(balance, stat, num_buying, sell_price, trading_fee) # 매도 수수료 222 | 223 | # 4. update statistics 224 | dd = stat.update_stat(profit) 225 | 226 | # 5. adding log 227 | save_mid_values(candle_data[i], candle_data[i]['buy_price'], sell_price, profit, stat.total_profit, dd, candle_data[i]['trend'], candle_data[i]['ma7']) 228 | 229 | # 매도 완료 230 | bought = 0 231 | 232 | fname = ticker + '-LW-result-all.csv' 233 | save_to_file_csv(fname, candle_data) 234 | 235 | # print simulation results 236 | print_sim_result(ticker, stat) 237 | 238 | # save simulation results 239 | fname = ticker + '-LW-result-all.txt' 240 | save_sim_result(fname, ticker, stat) 241 | 242 | # draw chart 243 | draw_chart_plotly(candle_data, ticker, stat) 244 | 245 | print('exit') 246 | 247 | if __name__ == '__main__': 248 | 249 | fname = '.\\sim_data\\BTC_day-2017-09-25-2020-11-12.csv' 250 | ticker = 'KRW-BTC' 251 | # Larry William 변동성 돌파, 일봉 사용 252 | tr_logic = TR_LW('day', 1) 253 | 254 | simulation(ticker, fname, tr_logic) 255 | -------------------------------------------------------------------------------- /Larry_williams4.py: -------------------------------------------------------------------------------- 1 | # Larry Williams 변동성 돌파전략(4) 2 | # refactoring 3 | # 4 | # 보다 자세한 내용을 아래 tistory 참고 5 | # https://money-expert.tistory.com/36 6 | # https://money-expert.tistory.com/38 7 | 8 | import json 9 | import csv 10 | from datetime import datetime 11 | import time 12 | import pandas as pd 13 | from plot import * 14 | import matplotlib.pyplot as plt 15 | from pandas_datareader import data as pdr 16 | import plotly.offline as py_offline 17 | import plotly.graph_objs as go 18 | 19 | from my_util import * 20 | from my_candle import * 21 | from TR_LW_1 import * 22 | 23 | def save_sim_result(fname, ticker, stat) : 24 | file = open(fname, 'w') 25 | file.write('total # trading : '+ str(stat.num_trading)) 26 | file.write('total profit : '+ format(stat.total_profit, ",.2f")) 27 | file.write('trading fee : '+ format(stat.total_fee, ",.2f")) 28 | file.write('total Net Profit : '+ format(stat.total_profit - stat.total_fee, ",.2f")) 29 | file.write('# winning : '+ str(stat.num_winning)) 30 | file.write('# losing : '+ str(stat.num_losing)) 31 | file.write('MDD : '+ format(stat.mdd,'3.2f')) 32 | file.write('max loss : '+ format(stat.max_loss, '10,.2f')) 33 | file.write('max gain : '+ format(stat.max_gain, '10,.2f')) 34 | file.close() 35 | 36 | def print_sim_result(ticker, stat) : 37 | print('ticker : ', ticker) 38 | print('total # trading : ', stat.num_trading) 39 | print('total profit : ', format(stat.total_profit, ",.2f")) 40 | print('trading fee : ', format(stat.total_fee, ",.2f")) 41 | print('total Net Profit : ', format(stat.total_profit - stat.total_fee, ",.2f")) 42 | print('# winning : ', stat.num_winning) 43 | print('# losing : ', stat.num_losing) 44 | print('MDD : ', format(stat.mdd,'3.2f')) 45 | print('max loss : ', format(stat.max_loss, '10,.2f')) 46 | print('max gain : ', format(stat.max_gain, '10,.2f')) 47 | 48 | def save_mid_values(candle_data, buy_price=0, sell_price=0, profit=0, total_profit=0, dd=0, trend=0, ma7=0) : 49 | save_mid_values_buy_sell(candle_data, buy_price, sell_price) 50 | save_mid_values_result(candle_data, profit, total_profit, dd) 51 | save_mid_values_jipyo(candle_data, trend, ma7) 52 | 53 | def save_mid_values_buy_sell(candle_data, buy_price, sell_price) : 54 | candle_data['buy_price'] = buy_price # buy 여부 저장 55 | candle_data['sell_price'] = sell_price # buy 여부 저장 56 | 57 | def save_mid_values_result(candle_data, profit, total_profit, dd) : 58 | candle_data['profit'] = profit # profit 저장 59 | candle_data['total_profit'] = total_profit # total_profit 저장 60 | candle_data['dd'] = dd # downd draw 저장 61 | 62 | def save_mid_values_jipyo(candle_data, trend, ma7) : 63 | candle_data['trend'] = trend # 추세값 저장 64 | candle_data['ma7'] = ma7 # downd draw 저장 65 | 66 | def handle_fee(balance, stat, num_buying, price, trading_fee) : 67 | fee = num_buying * price * trading_fee 68 | stat.total_fee += fee 69 | balance -= fee 70 | return balance 71 | 72 | class sim_stat : 73 | def __init__(self, init_seed) : 74 | self.init_seed = init_seed 75 | self.num_trading = 0 76 | self.total_profit = 0 77 | self.total_fee = 0 78 | self.num_winning = 0 79 | self.num_losing = 0 80 | self.max_loss = 0 81 | self.max_gain = -10000000 82 | self.mdd = 0 83 | 84 | def get_profit_percent(self) : 85 | return self.total_profit / self.init_seed * 100 86 | 87 | def update_stat(self, profit) : 88 | self.total_profit += profit # 누적 수익 89 | if profit > 0 : 90 | self.num_winning += 1 91 | else : 92 | self.num_losing += 1 93 | 94 | # 최대 손실, 최고 이익 값 update 95 | if self.max_loss > profit: 96 | self.max_loss = profit 97 | if self.max_gain < profit : 98 | self.max_gain = profit 99 | 100 | # draw down 계산 101 | dd = 100.0 * (self.max_gain - self.total_profit) / self.max_gain 102 | if dd > self.mdd : 103 | self.mdd = dd # mdd 계산 104 | return dd 105 | 106 | def save_ohlc(fname, candle_data) : 107 | with open(fname,'w',encoding="cp949") as make_file: 108 | pos = 0 109 | ss = 'Date,Open,High,Low,Close\n' 110 | make_file.write(ss) 111 | for i in range(len(candle_data)-1,-1,-1) :# 내림차순으로 저장되어 있으므로 뒤에서 한개씩 112 | dt = candle_data[i]['candleDateTimeKst'].split('+') 113 | dttime = candle_data[i]['candleDateTimeKst'] # 일단 datetime 모두 114 | if 0 : # only time 115 | dtt = dt[0].split('T') # time만 저장하기 116 | dttime = dtt[1] 117 | if 0 : # only date 118 | dtt = dt[0].split('T') # time만 저장하기 119 | dttime = dtt[0] 120 | 121 | ss = dttime + ',' + candle_data[i]['openingPrice'] + ',' + candle_data[i]['highPrice'] + ',' \ 122 | + candle_data[i]['lowPrice'] + ',' + candle_data[i]['tradePrice'] + '\n'# close 123 | make_file.write(ss) 124 | make_file.close() 125 | 126 | def draw_chart_plotly(candle_data, ticker, stat) : 127 | fname = 'chart.csv' 128 | save_ohlc(fname, candle_data) 129 | 130 | data = pd.read_csv(fname) 131 | 132 | # plotly를 이용하여 candelstkck에 text 출력하는 부분 설명한 글 133 | # https://info.cloudquant.com/2019/08/candlestick_plotly/ 134 | annotations = [] 135 | 136 | for i in range(len(candle_data)-1, -1, -1) :# 137 | if 'profit' not in candle_data[i] : 138 | continue 139 | text = 'L' 140 | if candle_data[i]['profit'] > 0 : # profit 이면 P lost이면 L 출력 141 | text = 'P' 142 | if candle_data[i]['profit'] != 0 : 143 | annotations.append(go.layout.Annotation(x=candle_data[i]['candleDateTimeKst'], 144 | y=candle_data[i]['highPrice'], 145 | xshift=1, # x 축 기준으로 오른쪽으로 x칸 이동 146 | yshift=10, # y 축 기준으로 오른쪽으로 y칸 이동 147 | showarrow=False, 148 | text=text)) 149 | annotations.append(go.layout.Annotation(x=candle_data[i]['candleDateTimeKst'], 150 | y=candle_data[i]['highPrice'], 151 | xshift=1, # x 축 기준으로 오른쪽으로 x칸 이동 152 | yshift=25, # y 축 기준으로 오른쪽으로 y칸 이동 153 | showarrow=False, 154 | text=candle_data[i]['trend'])) 155 | # annotations.append(go.layout.Annotation(x=candle_data[i]['candleDateTimeKst'], 156 | # y=candle_data[i]['highPrice'], 157 | # xshift=1, # x 축 기준으로 오른쪽으로 x칸 이동 158 | # yshift=40, # y 축 기준으로 오른쪽으로 y칸 이동 159 | # showarrow=False, 160 | # text=candle_data[i]['ma7'])) 161 | 162 | # draw할 layout 생성 163 | width = len(candle_data) * 15 164 | layout = dict( 165 | title=ticker+':: # tradings : ' + str(stat.num_trading) + ' profit : ' + format(stat.get_profit_percent(), '.2f') + '% mdd : ' + format(stat.mdd, '.2f'), 166 | xaxis=go.layout.XAxis(title=go.layout.xaxis.Title( text="Time"), rangeslider=dict (visible = False)), 167 | yaxis=go.layout.YAxis(title=go.layout.yaxis.Title( text="Price")), 168 | width=width, 169 | height=800, 170 | annotations=annotations 171 | ) 172 | data_candle = go.Candlestick(x=data.Date,open=data.Open,high=data.High,low=data.Low,close=data.Close) 173 | data = [data_candle] 174 | 175 | fig = go.Figure(data=data,layout=layout) 176 | fig.show() 177 | 178 | def simulation(ticker, sim_file, tr_login) : 179 | # simulation 데이터를 읽는다. 180 | candle_data = read_csv_to_dict(sim_file) 181 | if candle_data == [] : 182 | print('not exist ', sim_file) 183 | return 184 | 185 | # simulation 중 파일에 저장할 변수들 추가 186 | save_mid_values(candle_data[0]) 187 | 188 | # 제일 마지막 일봉이 어제 candle 189 | yest = candle_data[len(candle_data)-1] 190 | 191 | # 가장 오래된 일봉 데이터로 candle을 만든다. 192 | candle = Candle() 193 | candle.new_candle(yest) 194 | 195 | # 어제 일봉 setting 196 | tr_logic.update_new_candle(candle) 197 | 198 | deposit = 1000000 # 1회 최대 1백만원 매수 199 | balance = deposit # 현재 잔고 200 | 201 | bought = 0 # 매수 중인지 여부 202 | trading_fee = 0.0005 # 매매 수수료 203 | num_buying = 0 # 현재 매수 수량 204 | 205 | stat = sim_stat(deposit) # 통계 변수용 class 206 | 207 | for i in range(len(candle_data)-2,-1,-1) :# 내림차순으로 저장되어 있으므로 뒤에서 한개씩 208 | data = candle_data[i] 209 | 210 | #현재 simulation 데이터로 candle을 만든다. 211 | candle = Candle() 212 | candle.new_candle(data) 213 | 214 | # simulation 중 파일에 저장할 변수들 추가 215 | save_mid_values(data) 216 | 217 | # 지표 관련 변수들 218 | trend = 0 219 | ma7 = 0 220 | trend = tr_logic.get_trend() 221 | ma7 = tr_logic.get_MA(7) 222 | save_mid_values_jipyo(data, trend, ma7) 223 | 224 | if bought : # exit 조건 확인 225 | sell_price, losscut = tr_logic.is_exit_condition(candle) 226 | if sell_price > 0 : # exit 조건 227 | # 2. 일봉 종가에 판다고 가정 228 | profit = (sell_price - buy_price) * num_buying 229 | balance += profit 230 | 231 | # 3. 매도 수수료 232 | balance = handle_fee(balance, stat, num_buying, sell_price, trading_fee) # 매도 수수료 233 | 234 | # 4. update statistics 235 | dd = stat.update_stat(profit) 236 | 237 | # 5. adding log 238 | save_mid_values_buy_sell(data, losscut, sell_price) # tick과 같이 buy/sell 가격이 틀린 경우에는 buy_price에 1을 넣는다. 차후 excel에서 검증 편함 239 | save_mid_values_result(data, profit, stat.total_profit, dd) 240 | 241 | # 매도 완료 242 | bought = 0 243 | else : 244 | buy_price = tr_logic.is_enter_condition(candle) 245 | if buy_price > 0 : # 매수조건임 246 | # 1. 매수 금액 결정, min(balance, deposit) 초기 deposit 금액 혹은 balance가 초기 deposit 이하이면 balance 247 | buying_amount = min(balance, deposit) 248 | num_buying = buying_amount / buy_price # 매수 주수 249 | 250 | # 3. 수수료 251 | balance = handle_fee(balance, stat, num_buying, buy_price, trading_fee) # 매수 수수료 252 | 253 | # 4. update statistics 254 | stat.num_trading += 1 # 매수한 주문 수 255 | 256 | # 5. adding log 257 | save_mid_values_buy_sell(data, buy_price, 0) 258 | 259 | # 매수 중이라고 설정 260 | bought = 1 261 | 262 | fname = ticker + '-LW-result-all.csv' 263 | save_to_file_csv(fname, candle_data) 264 | 265 | # print simulation results 266 | print_sim_result(ticker, stat) 267 | 268 | # save simulation results 269 | fname = ticker + '-LW-result-all.txt' 270 | save_sim_result(fname, ticker, stat) 271 | 272 | # draw chart 273 | draw_chart_plotly(candle_data, ticker, stat) 274 | 275 | print('exit') 276 | 277 | if __name__ == '__main__': 278 | # prepare back data 279 | 280 | fname = '.\\sim_data\\BTC_day-2017-09-25-2020-11-12.csv' 281 | ticker = 'KRW-BTC' 282 | 283 | # Larry William 변동성 돌파, 일봉 사용 284 | tr_logic = TR_LW_SIM('min', 1) 285 | # tr_logic = TR_LW_TREND('min', 1) 286 | 287 | simulation(ticker, fname, tr_logic) 288 | print('') 289 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # date : 2020/11/18 2 | # 전략 시뮬레이션 만들어보기 3 | # 4 | 5 | 시스템 트레이딩 개발을 원하는 초보 개발자가 참고할 수 있는 있는 프로젝트입니다. 6 | 개발 단계를 거치면서 정리된 코드를 만들어보겠습니다. 7 | 개발과정에 대한 자세한 설명은 아래 tistory를 참고하세요. 8 | https://money-expert.tistory.com/ 9 | 시스템트레이딩 10 | 11 | 12 | 1. upbit 거래소 암호화폐 월봉 가져오는 파이썬 프로그램 : get_upbit_day_candles.py 13 | 14 | 2. Larry Williams 변동성 돌파 전략 시뮬레이션 15 | 16 | 2.1 막 만들어보기 : Larry_williams1.py 17 | 18 | 2.2 코드 정리 : Larry_williams2.py 19 | 20 | 2.3 chart그리(text 추가) : Larry_williams3.py, TR_LW.py 21 | 22 | 2.4 성능 개선 : Larry_williams3.py, TR_LW.py 23 | 24 | 2.5 전략class refactoring : Larry_williams4.py, TR_LW_1.py 25 | 26 | 27 | -------------------------------------------------------------------------------- /TR_LW.py: -------------------------------------------------------------------------------- 1 | # date : 2020/11/19 2 | # Larry Williams 변동성 돌파전략 3 | # 4 | # 보다 자세한 내용을 아래 tistory 참고 5 | # https://money-expert.tistory.com/34 6 | # 7 | import json 8 | from my_util import * 9 | from my_candle import * 10 | 11 | class TR_LW() : 12 | def __init__(self, interval, num) : 13 | self.interval = interval # min', 'day' 14 | self.interval_num = num # 1, 5, 20, 60 15 | self.pre = None 16 | self.cur = None 17 | self.k = 0.5 18 | self.bought = 0 # 현재 매수 중인지 19 | self.history = [] 20 | self.range = 0 21 | 22 | self.trend = 1 23 | self.MA = 0 24 | 25 | def update_new_range(self, cur) : # 26 | self.pre = cur 27 | self.history.append(cur) 28 | self.range = cur.high - cur.low # 전 candle의 range 29 | 30 | self.get_trend() # 추세값 계산 31 | self.get_MA(7) # 7일 단기 이평 32 | 33 | # MA(n)을 구한다. 34 | def get_MA(self, num_days) : 35 | hist_len = len(self.history) 36 | if hist_len >= num_days : 37 | day_sum = 0 38 | pos = len(self.history)-1 39 | for i in range(num_days) : 40 | candle = self.history[pos] 41 | pos -= 1 42 | day_sum += candle.close # 종가 합 43 | self.MA = day_sum / num_days 44 | self.MA = float(format(self.MA, '.2f')) # 소숫점 2자리 값으로 변경 45 | return self.MA 46 | 47 | # 7일간 trend를 구한다. 48 | # A : 7일간 전체 움직임의 절대값 = abs(전일 가격 - 7일전 가격) 49 | # B : 7일간 당일 움직임 절대값의 총합 : abs(7일전 시/종차)+ abs(6일전 시/종차)+ ...... abs(전일 시/종차) = 100 50 | # trend : A/B trend = 1 이면 추세 (상방,하방 구분은 없음 단순히 추세, 비추세 구분) 51 | # trend < 0.5 혹은 0.4 ( 비 추세 0.5로 할지 0.4로 할지 결정 필요 ) 52 | # https://cafe.naver.com/invest79/939 53 | def get_trend(self) : 54 | cur = self.history[-1] # last one 55 | 56 | day_sum = 0 57 | candle = None 58 | hist_len = len(self.history) 59 | if hist_len >= 8 : 60 | range_total = 0 61 | pos = len(self.history)-1 62 | for i in range(7) : 63 | candle = self.history[pos] 64 | day_sum += abs(candle.open - candle.close) # candle 시/종가 차이의 절대치 합 65 | pos -= 1 66 | 67 | period_range = abs(candle.close - cur.close) 68 | self.trend = period_range / day_sum 69 | self.trend = float(format(self.trend, '.2f')) # 소숫점 2자리 값으로 변경 70 | 71 | return self.trend 72 | 73 | # 진입조건 조사 74 | def is_enter_condition(self, candle) : 75 | enter = 0 76 | # 아래 세 조건 중에 원하는 조건 선택 77 | # if self.range > 0 : 78 | # if self.range > 0 and self.trend > 0.2 # trend 동시 적용 79 | if self.range > 0 and self.trend > 0.2 and (self.MA != 0 and self.MA < candle.open) : # trend & MA 동시 적용 80 | 81 | buy_price = (candle.open + self.range * self.k) 82 | if candle.high > buy_price : # 최고가가 buy_price보다 높으면 매수되었다고 가정 83 | enter = 1 84 | 85 | # 오늘 candle update 86 | self.update_new_range(candle) 87 | 88 | if enter : 89 | return buy_price 90 | return 0 91 | 92 | # 조건 조사 93 | def is_exit_condition(self, candle) : 94 | return candle.close # 일봉 종가에 무조건 판다 95 | 96 | if __name__ == '__main__': 97 | 98 | print('TR_LW') 99 | -------------------------------------------------------------------------------- /TR_LW_1.py: -------------------------------------------------------------------------------- 1 | # date : 2020/11/26 2 | # Larry Williams 변동성 돌파전략 refactoring 3 | # 4 | # 보다 자세한 내용을 아래 tistory 참고 5 | # https://money-expert.tistory.com/34 6 | # https://money-expert.tistory.com/38 7 | # 8 | import json 9 | from my_util import * 10 | from my_candle import * 11 | 12 | class TR_Basic() : 13 | def __init__(self, interval, num) : 14 | self.interval = interval # min', 'day' 15 | self.interval_num = num # 1, 5, 20, 60 16 | self.history = [] 17 | 18 | self.bought_price = 0 # 매수하였으면 매수가. > 0 매수 중 19 | 20 | self.trend = 1 21 | self.MA = 0 22 | 23 | # MA(n)을 구한다. 24 | def get_MA(self, num_days) : 25 | hist_len = len(self.history) 26 | if hist_len >= num_days : 27 | day_sum = 0 28 | pos = len(self.history)-1 29 | for i in range(num_days) : 30 | candle = self.history[pos] 31 | pos -= 1 32 | day_sum += candle.close # 종가 합 33 | self.MA = day_sum / num_days 34 | self.MA = float(format(self.MA, '.2f')) # 소숫점 2자리 값으로 변경 35 | return self.MA 36 | 37 | # 7일간 trend를 구한다. 38 | # A : 7일간 전체 움직임의 절대값 = abs(전일 가격 - 7일전 가격) 39 | # B : 7일간 당일 움직임 절대값의 총합 : abs(7일전 시/종차)+ abs(6일전 시/종차)+ ...... abs(전일 시/종차) = 100 40 | # trend : A/B trend = 1 이면 추세 (상방,하방 구분은 없음 단순히 추세, 비추세 구분) 41 | # trend < 0.5 혹은 0.4 ( 비 추세 0.5로 할지 0.4로 할지 결정 필요 ) 42 | # https://cafe.naver.com/invest79/939 43 | def get_trend(self) : 44 | if len(self.history) == 0 : 45 | return 1 46 | cur = self.history[-1] # last one 47 | 48 | day_sum = 0 49 | candle = None 50 | hist_len = len(self.history) 51 | if hist_len >= 8 : 52 | range_total = 0 53 | pos = len(self.history)-1 54 | for i in range(7) : 55 | candle = self.history[pos] 56 | day_sum += abs(candle.open - candle.close) # candle 시/종가 차이의 절대치 합 57 | pos -= 1 58 | 59 | period_range = abs(candle.close - cur.close) 60 | self.trend = period_range / day_sum 61 | self.trend = float(format(self.trend, '.2f')) # 소숫점 2자리 값으로 변경 62 | 63 | return self.trend 64 | 65 | # 진입조건 조사 66 | def is_enter_condition(self, candle) : 67 | enter = 0 68 | # 아래 세 조건 중에 원하는 조건 선택 69 | buy_price = self.get_buy_price(candle) 70 | enter = self.is_meet_buy_cond(candle, buy_price) 71 | 72 | # 오늘 candle update 73 | self.update_new_candle(candle) 74 | 75 | if enter : 76 | self.bought_price = buy_price 77 | return buy_price 78 | return 0 79 | 80 | # 조건 조사 81 | # cut : 익절(1) 혹은 손절 (2) 82 | def is_exit_condition(self, candle) : 83 | exit = 0 84 | # 아래 세 조건 중에 원하는 조건 선택 85 | sell_price = self.get_sell_price(candle) 86 | exit, cut = self.is_meet_sell_cond(candle, sell_price) 87 | if exit : 88 | self.bought_price = 0 89 | return sell_price, cut 90 | 91 | class TR_LW_SIM(TR_Basic) : 92 | def __init__(self, interval, num) : 93 | super().__init__(interval, num) 94 | self.range = 0 95 | self.k = 0.5 96 | 97 | def update_new_candle(self, cur) : # 98 | self.history.append(cur) 99 | self.range = cur.high - cur.low # 전 candle의 range 100 | 101 | self.get_trend() # 추세값 계산 102 | self.get_MA(7) # 7일 단기 이평 103 | 104 | # 매수 조건을 만족하면 매수가를 돌려준다. 105 | def get_buy_price(self, candle) : 106 | if self.range > 0 : 107 | buy_price = (candle.open + self.range * self.k) 108 | return buy_price 109 | return 0 110 | 111 | # 현재 정보를 바탕으로 매수를 해야할지 결정 112 | # simulation이므로 candle.high > buy_price 이면 매수되었다고 가정한다. 113 | # 실제 매매할 때는 buy_price와 candle.high 사이 적절한 값을 결정 114 | def is_meet_buy_cond(self, candle, buy_price) : 115 | if candle.high > buy_price : # 최고가가 buy_price보다 높으면 매수되었다고 가정 116 | return buy_price 117 | 118 | # candle 종가에 판다. 119 | def get_sell_price(self, candle) : 120 | return candle.close 121 | 122 | # 무조건 판다. 123 | def is_meet_sell_cond(self, candle, sell_price) : 124 | cut = 1 # 익절 125 | if sell_price < self.bought_price : 126 | cut = 2 # 손절 127 | return 1, cut 128 | 129 | # 비추세구간은 매수 안함 130 | class TR_LW_TREND(TR_LW_SIM) : 131 | # 매수 조건을 만족하면 매수가를 돌려준다. 132 | def get_buy_price(self, candle) : 133 | if self.range > 0 and self.trend > 0.2 : # trend 동시 적용 134 | buy_price = (candle.open + self.range * self.k) 135 | return buy_price 136 | return 0 137 | 138 | # 비추세 + 이평선이 현재가 보다 아래인 경우에만 매수 139 | class TR_LW_TREND_MA(TR_Basic) : 140 | def get_buy_price(self, candle) : 141 | if self.range > 0 and self.trend > 0.2 and (self.MA != 0 and self.MA < candle.open) : # trend & MA 동시 적용 142 | buy_price = (candle.open + self.range * self.k) 143 | return buy_price 144 | return 0 145 | 146 | if __name__ == '__main__': 147 | 148 | print('TR_LW') 149 | -------------------------------------------------------------------------------- /arry_williams4.py: -------------------------------------------------------------------------------- 1 | # Larry Williams 변동성 돌파전략(4) 2 | # refactoring 3 | # 4 | # 보다 자세한 내용을 아래 tistory 참고 5 | # https://money-expert.tistory.com/36 6 | # https://money-expert.tistory.com/38 7 | 8 | import json 9 | import csv 10 | from datetime import datetime 11 | import time 12 | import pandas as pd 13 | from plot import * 14 | import matplotlib.pyplot as plt 15 | from pandas_datareader import data as pdr 16 | import plotly.offline as py_offline 17 | import plotly.graph_objs as go 18 | 19 | from my_util import * 20 | from my_candle import * 21 | from TR_LW_1 import * 22 | 23 | def save_sim_result(fname, ticker, stat) : 24 | file = open(fname, 'w') 25 | file.write('total # trading : '+ str(stat.num_trading)) 26 | file.write('total profit : '+ format(stat.total_profit, ",.2f")) 27 | file.write('trading fee : '+ format(stat.total_fee, ",.2f")) 28 | file.write('total Net Profit : '+ format(stat.total_profit - stat.total_fee, ",.2f")) 29 | file.write('# winning : '+ str(stat.num_winning)) 30 | file.write('# losing : '+ str(stat.num_losing)) 31 | file.write('MDD : '+ format(stat.mdd,'3.2f')) 32 | file.write('max loss : '+ format(stat.max_loss, '10,.2f')) 33 | file.write('max gain : '+ format(stat.max_gain, '10,.2f')) 34 | file.close() 35 | 36 | def print_sim_result(ticker, stat) : 37 | print('ticker : ', ticker) 38 | print('total # trading : ', stat.num_trading) 39 | print('total profit : ', format(stat.total_profit, ",.2f")) 40 | print('trading fee : ', format(stat.total_fee, ",.2f")) 41 | print('total Net Profit : ', format(stat.total_profit - stat.total_fee, ",.2f")) 42 | print('# winning : ', stat.num_winning) 43 | print('# losing : ', stat.num_losing) 44 | print('MDD : ', format(stat.mdd,'3.2f')) 45 | print('max loss : ', format(stat.max_loss, '10,.2f')) 46 | print('max gain : ', format(stat.max_gain, '10,.2f')) 47 | 48 | def save_mid_values(candle_data, buy_price=0, sell_price=0, profit=0, total_profit=0, dd=0, trend=0, ma7=0) : 49 | save_mid_values_buy_sell(candle_data, buy_price, sell_price) 50 | save_mid_values_result(candle_data, profit, total_profit, dd) 51 | save_mid_values_jipyo(candle_data, trend, ma7) 52 | 53 | def save_mid_values_buy_sell(candle_data, buy_price, sell_price) : 54 | candle_data['buy_price'] = buy_price # buy 여부 저장 55 | candle_data['sell_price'] = sell_price # buy 여부 저장 56 | 57 | def save_mid_values_result(candle_data, profit, total_profit, dd) : 58 | candle_data['profit'] = profit # profit 저장 59 | candle_data['total_profit'] = total_profit # total_profit 저장 60 | candle_data['dd'] = dd # downd draw 저장 61 | 62 | def save_mid_values_jipyo(candle_data, trend, ma7) : 63 | candle_data['trend'] = trend # 추세값 저장 64 | candle_data['ma7'] = ma7 # downd draw 저장 65 | 66 | def handle_fee(balance, stat, num_buying, price, trading_fee) : 67 | fee = num_buying * price * trading_fee 68 | stat.total_fee += fee 69 | balance -= fee 70 | return balance 71 | 72 | class sim_stat : 73 | def __init__(self, init_seed) : 74 | self.init_seed = init_seed 75 | self.num_trading = 0 76 | self.total_profit = 0 77 | self.total_fee = 0 78 | self.num_winning = 0 79 | self.num_losing = 0 80 | self.max_loss = 0 81 | self.max_gain = -10000000 82 | self.mdd = 0 83 | 84 | def get_profit_percent(self) : 85 | return self.total_profit / self.init_seed * 100 86 | 87 | def update_stat(self, profit) : 88 | self.total_profit += profit # 누적 수익 89 | if profit > 0 : 90 | self.num_winning += 1 91 | else : 92 | self.num_losing += 1 93 | 94 | # 최대 손실, 최고 이익 값 update 95 | if self.max_loss > profit: 96 | self.max_loss = profit 97 | if self.max_gain < profit : 98 | self.max_gain = profit 99 | 100 | # draw down 계산 101 | dd = 100.0 * (self.max_gain - self.total_profit) / self.max_gain 102 | if dd > self.mdd : 103 | self.mdd = dd # mdd 계산 104 | return dd 105 | 106 | def save_ohlc(fname, candle_data) : 107 | with open(fname,'w',encoding="cp949") as make_file: 108 | pos = 0 109 | ss = 'Date,Open,High,Low,Close\n' 110 | make_file.write(ss) 111 | for i in range(len(candle_data)-1,-1,-1) :# 내림차순으로 저장되어 있으므로 뒤에서 한개씩 112 | dt = candle_data[i]['candleDateTimeKst'].split('+') 113 | dttime = candle_data[i]['candleDateTimeKst'] # 일단 datetime 모두 114 | if 0 : # only time 115 | dtt = dt[0].split('T') # time만 저장하기 116 | dttime = dtt[1] 117 | if 0 : # only date 118 | dtt = dt[0].split('T') # time만 저장하기 119 | dttime = dtt[0] 120 | 121 | ss = dttime + ',' + candle_data[i]['openingPrice'] + ',' + candle_data[i]['highPrice'] + ',' \ 122 | + candle_data[i]['lowPrice'] + ',' + candle_data[i]['tradePrice'] + '\n'# close 123 | make_file.write(ss) 124 | make_file.close() 125 | 126 | def draw_chart_plotly(candle_data, ticker, stat) : 127 | fname = 'chart.csv' 128 | save_ohlc(fname, candle_data) 129 | 130 | data = pd.read_csv(fname) 131 | 132 | # plotly를 이용하여 candelstkck에 text 출력하는 부분 설명한 글 133 | # https://info.cloudquant.com/2019/08/candlestick_plotly/ 134 | annotations = [] 135 | 136 | for i in range(len(candle_data)-1, -1, -1) :# 137 | if 'profit' not in candle_data[i] : 138 | continue 139 | text = 'L' 140 | if candle_data[i]['profit'] > 0 : # profit 이면 P lost이면 L 출력 141 | text = 'P' 142 | if candle_data[i]['profit'] != 0 : 143 | annotations.append(go.layout.Annotation(x=candle_data[i]['candleDateTimeKst'], 144 | y=candle_data[i]['highPrice'], 145 | xshift=1, # x 축 기준으로 오른쪽으로 x칸 이동 146 | yshift=10, # y 축 기준으로 오른쪽으로 y칸 이동 147 | showarrow=False, 148 | text=text)) 149 | annotations.append(go.layout.Annotation(x=candle_data[i]['candleDateTimeKst'], 150 | y=candle_data[i]['highPrice'], 151 | xshift=1, # x 축 기준으로 오른쪽으로 x칸 이동 152 | yshift=25, # y 축 기준으로 오른쪽으로 y칸 이동 153 | showarrow=False, 154 | text=candle_data[i]['trend'])) 155 | # annotations.append(go.layout.Annotation(x=candle_data[i]['candleDateTimeKst'], 156 | # y=candle_data[i]['highPrice'], 157 | # xshift=1, # x 축 기준으로 오른쪽으로 x칸 이동 158 | # yshift=40, # y 축 기준으로 오른쪽으로 y칸 이동 159 | # showarrow=False, 160 | # text=candle_data[i]['ma7'])) 161 | 162 | # draw할 layout 생성 163 | width = len(candle_data) * 15 164 | layout = dict( 165 | title=ticker+':: # tradings : ' + str(stat.num_trading) + ' profit : ' + format(stat.get_profit_percent(), '.2f') + '% mdd : ' + format(stat.mdd, '.2f'), 166 | xaxis=go.layout.XAxis(title=go.layout.xaxis.Title( text="Time"), rangeslider=dict (visible = False)), 167 | yaxis=go.layout.YAxis(title=go.layout.yaxis.Title( text="Price")), 168 | width=width, 169 | height=800, 170 | annotations=annotations 171 | ) 172 | data_candle = go.Candlestick(x=data.Date,open=data.Open,high=data.High,low=data.Low,close=data.Close) 173 | data = [data_candle] 174 | 175 | fig = go.Figure(data=data,layout=layout) 176 | fig.show() 177 | 178 | def simulation(ticker, sim_file, tr_login) : 179 | # simulation 데이터를 읽는다. 180 | candle_data = read_csv_to_dict(sim_file) 181 | if candle_data == [] : 182 | print('not exist ', sim_file) 183 | return 184 | 185 | # simulation 중 파일에 저장할 변수들 추가 186 | save_mid_values(candle_data[0]) 187 | 188 | # 제일 마지막 일봉이 어제 candle 189 | yest = candle_data[len(candle_data)-1] 190 | 191 | # 가장 오래된 일봉 데이터로 candle을 만든다. 192 | candle = Candle() 193 | candle.new_candle(yest) 194 | 195 | # 어제 일봉 setting 196 | tr_logic.update_new_candle(candle) 197 | 198 | deposit = 1000000 # 1회 최대 1백만원 매수 199 | balance = deposit # 현재 잔고 200 | 201 | bought = 0 # 매수 중인지 여부 202 | trading_fee = 0.0005 # 매매 수수료 203 | num_buying = 0 # 현재 매수 수량 204 | 205 | stat = sim_stat(deposit) # 통계 변수용 class 206 | 207 | for i in range(len(candle_data)-2,-1,-1) :# 내림차순으로 저장되어 있으므로 뒤에서 한개씩 208 | data = candle_data[i] 209 | 210 | #현재 simulation 데이터로 candle을 만든다. 211 | candle = Candle() 212 | candle.new_candle(data) 213 | 214 | # simulation 중 파일에 저장할 변수들 추가 215 | save_mid_values(data) 216 | 217 | # 지표 관련 변수들 218 | trend = 0 219 | ma7 = 0 220 | trend = tr_logic.get_trend() 221 | ma7 = tr_logic.get_MA(7) 222 | save_mid_values_jipyo(data, trend, ma7) 223 | 224 | if bought : # exit 조건 확인 225 | sell_price, losscut = tr_logic.is_exit_condition(candle) 226 | if sell_price > 0 : # exit 조건 227 | # 2. 일봉 종가에 판다고 가정 228 | profit = (sell_price - buy_price) * num_buying 229 | balance += profit 230 | 231 | # 3. 매도 수수료 232 | balance = handle_fee(balance, stat, num_buying, sell_price, trading_fee) # 매도 수수료 233 | 234 | # 4. update statistics 235 | dd = stat.update_stat(profit) 236 | 237 | # 5. adding log 238 | save_mid_values_buy_sell(data, losscut, sell_price) # tick과 같이 buy/sell 가격이 틀린 경우에는 buy_price에 1을 넣는다. 차후 excel에서 검증 편함 239 | save_mid_values_result(data, profit, stat.total_profit, dd) 240 | 241 | # 매도 완료 242 | bought = 0 243 | else : 244 | buy_price = tr_logic.is_enter_condition(candle) 245 | if buy_price > 0 : # 매수조건임 246 | # 1. 매수 금액 결정, min(balance, deposit) 초기 deposit 금액 혹은 balance가 초기 deposit 이하이면 balance 247 | buying_amount = min(balance, deposit) 248 | num_buying = buying_amount / buy_price # 매수 주수 249 | 250 | # 3. 수수료 251 | balance = handle_fee(balance, stat, num_buying, buy_price, trading_fee) # 매수 수수료 252 | 253 | # 4. update statistics 254 | stat.num_trading += 1 # 매수한 주문 수 255 | 256 | # 5. adding log 257 | save_mid_values_buy_sell(data, buy_price, 0) 258 | 259 | # 매수 중이라고 설정 260 | bought = 1 261 | 262 | fname = ticker + '-LW-result-all.csv' 263 | save_to_file_csv(fname, candle_data) 264 | 265 | # print simulation results 266 | print_sim_result(ticker, stat) 267 | 268 | # save simulation results 269 | fname = ticker + '-LW-result-all.txt' 270 | save_sim_result(fname, ticker, stat) 271 | 272 | # draw chart 273 | draw_chart_plotly(candle_data, ticker, stat) 274 | 275 | print('exit') 276 | 277 | if __name__ == '__main__': 278 | # prepare back data 279 | 280 | fname = '.\\sim_data\\BTC_day-2017-09-25-2020-11-12.csv' 281 | ticker = 'KRW-BTC' 282 | 283 | # Larry William 변동성 돌파, 일봉 사용 284 | tr_logic = TR_LW_SIM('min', 1) 285 | # tr_logic = TR_LW_TREND('min', 1) 286 | 287 | simulation(ticker, fname, tr_logic) 288 | print('') 289 | -------------------------------------------------------------------------------- /get_upbit_candles.py: -------------------------------------------------------------------------------- 1 | 2 | import requests 3 | import json 4 | import time 5 | import os 6 | from datetime import datetime, timedelta 7 | def save_list_to_file_csv(file_name, titles, data) : 8 | with open(file_name,'w',encoding="cp949") as make_file: 9 | ss = '' 10 | # title 저장 11 | for name in titles: 12 | ss += (name + ',') 13 | ss += '\n' 14 | make_file.write(ss) 15 | 16 | for vals in data: 17 | ss = '' 18 | for val in vals: 19 | sval = str(val) 20 | sval = sval.replace(',','') 21 | ss += (sval + ',') 22 | ss += '\n' 23 | make_file.write(ss) 24 | make_file.close() 25 | # 26 | # for writing dic data to cvs format 27 | # 28 | def save_to_file_csv(file_name, data) : 29 | with open(file_name,'w',encoding="cp949") as make_file: 30 | # title 저장 31 | vals = data[0].keys() 32 | ss = '' 33 | for val in vals: 34 | val = val.replace(',','') 35 | ss += (val + ',') 36 | ss += '\n' 37 | make_file.write(ss) 38 | 39 | for dt in data: 40 | vals = dt.values() 41 | ss = '' 42 | for val in vals: 43 | sval = str(val) 44 | sval = sval.replace(',','') 45 | ss += (sval + ',') 46 | ss += '\n' 47 | make_file.write(ss) 48 | make_file.close() 49 | 50 | def save_to_file_json(file_name, data) : 51 | with open(file_name,'w',encoding="cp949") as make_file: 52 | json.dump(data, make_file, ensure_ascii=False, indent="\t") 53 | make_file.close() 54 | 55 | 56 | def request_get(url, headers = 0, echo=0) : 57 | response = "" 58 | cnt2 = 0 59 | while str(response) != '' and cnt2 < 10: 60 | if echo : 61 | print("requests request_get", url) 62 | try : 63 | response = requests.get(url, headers=headers) 64 | except Exception as e: 65 | print(e) 66 | time.sleep(20) 67 | cnt2 += 1 68 | continue 69 | if str(response) != '': 70 | print('sleep ', url) 71 | time.sleep(15) 72 | cnt2 += 1 73 | return response.json() 74 | 75 | 76 | # coin : "KRW-BTC" 77 | # ex https://crix-api-endpoint.upbit.com/v1/crix/candles/days?code=CRIX.UPBIT.KRW-BTC&count=10&to=2019-09-01%2000:00:00 78 | def get_candle_history(coin, ty='days', interval=1, count=400, to=None) : 79 | if ty == 'day' : 80 | ss = 'days' 81 | elif ty == 'min' : 82 | ss = 'minutes/'+str(interval) 83 | 84 | base_url = 'https://crix-api-endpoint.upbit.com/v1/crix/candles/' + ss 85 | code = '?code=CRIX.UPBIT.' + coin 86 | s_count = '&count=' + str(count) 87 | 88 | url = base_url + code + s_count 89 | if to == None : 90 | s_to = '' 91 | else : 92 | url += ('&to='+to) 93 | # print(url) 94 | ret = request_get(url) 95 | return ret 96 | 97 | # data 중 frm보다 오래된 데이터는 지운다. 98 | # candle : 'candleDateTime' 99 | # tick : 'trade_time_utc' 100 | def remove_data(data, frm, key) : 101 | del_items = 0 102 | pos = 0 103 | for each in data : 104 | if each[key] < frm : 105 | del_items = pos 106 | break 107 | pos += 1 108 | 109 | return data[:pos] 110 | 111 | # candle from - 최근 구간 받기 112 | def get_data_continue_candle(coin, ty='day', interval=1, count=10, frm=None, to=None) : 113 | end = False 114 | cnt = 1 115 | if frm != None : 116 | # 입력은 '2021-01-12 12:00:00' 117 | # 내부format 형태로 변경 2021-01-12T11:50:00+09:00 118 | frm = frm.replace(' ', 'T') 119 | frm += '+09:00' 120 | 121 | if to != None : 122 | # utc로 변경해야 123 | 124 | # datetime 값으로 변환 125 | dt_tm_kst = datetime.strptime(to,'%Y-%m-%d %H:%M:%S') 126 | tm_utc = dt_tm_kst - timedelta(hours=9) 127 | 128 | # 일자 + 시간 문자열로 변환 129 | to = tm_utc.strftime('%Y-%m-%d %H:%M:%S') 130 | 131 | next_to = to 132 | while(end == False) : 133 | t = int(time.time()) 134 | ret = get_candle_history(coin, ty, interval, count, next_to) 135 | 136 | if len(ret) > 0 : 137 | print(ret[0]['candleDateTimeKst'], ret[-1]['candleDateTimeKst']) 138 | if len(ret) < 2 : # no more data 139 | return 140 | 141 | # candle은 내림차순 142 | # 마지막에 저장된 candle의 시간을 구한다. 143 | 144 | info = ret[-1] 145 | 146 | tm_kst = info['candleDateTimeKst'] 147 | 148 | dt = info['candleDateTimeKst'].split('+') 149 | tm = dt[0].replace('T', ' ') 150 | day = tm.split(' ')[0] 151 | 152 | if frm != None : # from보다 이전 데이터인지 확인 153 | # 마지막 candle이 from보다 적으면 from 이후 candle을 지운다. 154 | if tm_kst < frm : 155 | ret = remove_data(ret, frm, 'candleDateTimeKst') 156 | end = True 157 | 158 | # 계속 검색을 하는 경우에는 현재 받은 candle의 마지막 시간이 next_to가 된다. 159 | # 이때 시간은 UTC 160 | dt = info['candleDateTime'].split('+') 161 | tm = dt[0].replace('T', ' ') 162 | next_to = tm 163 | 164 | # cnt 번호를 추가하여 파일이름 생성 165 | fname = coin+'_' + ty + '_' + str(interval) + '_' + format(cnt, '03d') + '_' + day + '.csv' 166 | cnt += 1 167 | save_to_file_csv(fname, ret) 168 | print ('save ', fname, tm_kst) 169 | 170 | if ty == 'day' : # day는 400개만 받을 수 있다.ㅣ 171 | end = True 172 | else : # 분 봉은 계속 받을 수 있다. 173 | time.sleep(1) 174 | else : 175 | end = True 176 | 177 | # 178 | # for read data from cvs 179 | # 180 | # row : value list 181 | def get_new_item(keys, row) : 182 | data = {} 183 | for i in range(len(row)) : 184 | data[keys[i]] = row[i] 185 | return data 186 | 187 | import csv 188 | 189 | # 첫 줄은 title이라고 가정, 이후에 title 값을 key로 갖는 dict로 읽기 190 | def read_csv_to_dict(fname) : 191 | data = [] 192 | keys =[] 193 | first = 1 194 | with open(fname, 'r', encoding='UTF8') as FILE : 195 | csv_reader = csv.reader(FILE, delimiter=',', quotechar='"') 196 | for row in csv_reader : 197 | if first : # make dict keys 198 | keys = row.copy() 199 | # for key in row : 200 | # keys .append(key) 201 | first = 0 202 | else : 203 | data.append(get_new_item(keys, row)) 204 | return data 205 | 206 | def read_csv(fname) : 207 | data = [] 208 | with open(fname, 'r', encoding='UTF8') as FILE : 209 | csv_reader = csv.reader(FILE, delimiter=',', quotechar='"') 210 | for row in csv_reader : 211 | data.append(row) 212 | return data 213 | 214 | 215 | import glob 216 | def merge_excel_file(fname) : 217 | files = [] 218 | filter = '.\\' + fname + '*.csv' 219 | for filename in glob.glob(filter): 220 | files.append(filename) 221 | 222 | all = [] # 파일에서 읽어들인 모든 데이터를 저장하는 list 223 | key1 = -1 # sorting할때 사용할 첫 번째 key (time_t) 224 | title = [] 225 | 226 | num_file = 0 227 | for name in files : 228 | data = read_csv(name) 229 | if data != [] : 230 | # key 위치 얻기, 처음에 한번만 실행 key1 : timestamp 231 | if key1 == -1 : 232 | title = data[0].copy() 233 | for i in range(len(data[0])) : 234 | if data[0][i] == 'candleDateTimeKst' : # timestamp에 None이 들어오는 경우가 있음 235 | key1 = i 236 | 237 | first_tm = data[1][key1] # key1 = timestamp 238 | 239 | del_pos = [] 240 | for i in range(len(all)-1,0, -1) : # all에 저장된 데이터를 뒤에서 읽으면서 241 | if all[i][key1] == first_tm : 242 | del_pos.append(i) # 지울 데이터의 row를 저장 243 | else : 244 | break 245 | 246 | for pos in del_pos : # 지울 데이터가 있으면(중복된) 지운다. 247 | del(all[pos]) 248 | 249 | # 방금 읽은 data의 0번째(title)을 지운다. 250 | del(data[0]) # title은 지운다. 251 | all += data 252 | 253 | num_file += 1 254 | 255 | # 오름차순으로 정 256 | merged_data = sorted(all, key = lambda x: (x[key1]), reverse=False) # key1 번째가 key 257 | 258 | dt = merged_data[0][key1].split('T') 259 | 260 | out_name = '.\\out_' + fname + '_' + dt[0] + '.csv' 261 | save_list_to_file_csv(out_name, title, merged_data) 262 | print('merge finished', out_name) 263 | 264 | 265 | # upbit 과거 데이터 받기 266 | CANDLE_TYPE = 'min' # 'min' or 'day' 267 | CANDLE_INTERVAL = 60 # 1, 3,5,10,30,60 268 | 269 | COIN = 'KRW-BTC' # 원하는 코인 정보 270 | 271 | # 1/14일 하루치를 받고 싶을 때 272 | FROM = '2021-01-14 00:00:00' # 특정 일자부터 받고 싶을 때 format : candle : '2021-01-12 10:00:00' KST 273 | TO = '2021-01-15 00:00:00' # None(최근일자) or TO (KST 기준) 274 | 275 | 276 | if __name__ == '__main__': 277 | print('get candle ', COIN, CANDLE_TYPE, CANDLE_INTERVAL) 278 | get_data_continue_candle(COIN, ty=CANDLE_TYPE, interval = CANDLE_INTERVAL, count=400, frm=FROM, to=TO) 279 | 280 | # merge 281 | filename_prefix = COIN + '_' + CANDLE_TYPE + '_'+ str(CANDLE_INTERVAL) # 결과를 저장할 prefix 282 | merge_excel_file(filename_prefix) 283 | -------------------------------------------------------------------------------- /get_upbit_day_candles.py: -------------------------------------------------------------------------------- 1 | # date : 2020/11/16 2 | # upbit 거래소 암호화폐 일봉 자료 얻기 3 | # 4 | # 실행결과 5 | # coin-name_day1.csv 6 | # coin-name_day2.csv 7 | # coin-name_day3.csv 8 | # 9 | # 보다 자세한 내용을 아래 tistory 참고 10 | # https://money-expert.tistory.com/33 11 | # 12 | 13 | import requests 14 | import json 15 | 16 | def save_to_file_csv(file_name, data) : 17 | with open(file_name,'w',encoding="cp949") as make_file: 18 | # title 저장 19 | vals = data[0].keys() 20 | ss = '' 21 | for val in vals: 22 | val = val.replace(',','') 23 | ss += (val + ',') 24 | ss += '\n' 25 | make_file.write(ss) 26 | 27 | for dt in data: 28 | vals = dt.values() 29 | ss = '' 30 | for val in vals: 31 | sval = str(val) 32 | sval = sval.replace(',','') 33 | ss += (sval + ',') 34 | ss += '\n' 35 | make_file.write(ss) 36 | make_file.close() 37 | 38 | def save_to_file_json(file_name, data) : 39 | with open(file_name,'w',encoding="cp949") as make_file: 40 | json.dump(data, make_file, ensure_ascii=False, indent="\t") 41 | make_file.close() 42 | 43 | 44 | def request_get(url, headers = 0, echo=0) : 45 | response = "" 46 | cnt2 = 0 47 | while str(response) != '' and cnt2 < 10: 48 | if echo : 49 | print("requests request_get", url) 50 | try : 51 | response = requests.get(url, headers=headers) 52 | except Exception as e: 53 | print(e) 54 | time.sleep(20) 55 | cnt2 += 1 56 | continue 57 | if str(response) != '': 58 | print('sleep ', url) 59 | time.sleep(15) 60 | cnt2 += 1 61 | return response.json() 62 | 63 | 64 | # coin : "KRW-BTC" 65 | # ex https://crix-api-endpoint.upbit.com/v1/crix/candles/days?code=CRIX.UPBIT.KRW-BTC&count=10&to=2019-09-01%2000:00:00 66 | def get_coin_history_day(coin, count=400, to=None) : 67 | base_url = 'https://crix-api-endpoint.upbit.com/v1/crix/candles/days' 68 | code = '?code=CRIX.UPBIT.' + coin 69 | s_count = '&count=' + str(count) 70 | 71 | url = base_url + code + s_count 72 | if to == None : 73 | s_to = '' 74 | else : 75 | url += ('&to='+to) 76 | ret = request_get(url) 77 | return ret 78 | 79 | if __name__ == '__main__': 80 | 81 | coin = 'KRW-STEEM' 82 | ret = get_coin_history_day(coin, count=400) 83 | if ret != None : 84 | fname = coin+'_day1.csv' 85 | save_to_file_csv(fname, ret) 86 | 87 | # coin_day1.csv 파일을 open한 후 마지막에 저장된 날자를 아래 to에 입력 88 | to = '2019-10-20 09:00:00' 89 | ret = get_coin_history_day(coin, count=400, to=to) 90 | if ret != None : 91 | fname = coin+'_day2.csv' 92 | save_to_file_csv(fname, ret) 93 | 94 | to = '2018-09-20 09:00:00' 95 | ret = get_coin_history_day(coin, count=400, to=to) 96 | if ret != None : 97 | fname = coin+'_day3.csv' 98 | save_to_file_csv(fname, ret) 99 | 100 | print('') 101 | -------------------------------------------------------------------------------- /get_upbit_ticks.py: -------------------------------------------------------------------------------- 1 | # date : 2021/01/17 2 | # upbit 거래소 암호화폐 tick 자료 연속 얻기 3 | # 4 | # 실행결과 5 | # out_KRW-BTC_tick_2021-01-16.csv 6 | # 7 | # 보다 자세한 내용을 아래 tistory 참고 8 | # https://money-expert.tistory.com/47 9 | # 10 | 11 | import requests 12 | import json 13 | import time 14 | import os 15 | import csv 16 | import glob 17 | 18 | # 19 | # for read data from cvs 20 | # 21 | # row : value list 22 | 23 | def read_csv(fname) : 24 | data = [] 25 | with open(fname, 'r', encoding='UTF8') as FILE : 26 | csv_reader = csv.reader(FILE, delimiter=',', quotechar='"') 27 | for row in csv_reader : 28 | data.append(row) 29 | return data 30 | 31 | def save_list_to_file_csv(file_name, titles, data) : 32 | with open(file_name,'w',encoding="cp949") as make_file: 33 | ss = '' 34 | # title 저장 35 | for name in titles: 36 | ss += (name + ',') 37 | ss += '\n' 38 | make_file.write(ss) 39 | 40 | for vals in data: 41 | ss = '' 42 | for val in vals: 43 | sval = str(val) 44 | sval = sval.replace(',','') 45 | ss += (sval + ',') 46 | ss += '\n' 47 | make_file.write(ss) 48 | make_file.close() 49 | # 50 | # for writing dic data to cvs format 51 | # 52 | def save_to_file_csv(file_name, data) : 53 | with open(file_name,'w',encoding="cp949") as make_file: 54 | # title 저장 55 | vals = data[0].keys() 56 | ss = '' 57 | for val in vals: 58 | val = val.replace(',','') 59 | ss += (val + ',') 60 | ss += '\n' 61 | make_file.write(ss) 62 | 63 | for dt in data: 64 | vals = dt.values() 65 | ss = '' 66 | for val in vals: 67 | sval = str(val) 68 | sval = sval.replace(',','') 69 | ss += (sval + ',') 70 | ss += '\n' 71 | make_file.write(ss) 72 | make_file.close() 73 | 74 | def request_get(url, headers = 0, echo=0) : 75 | response = "" 76 | cnt2 = 0 77 | while str(response) != '' and cnt2 < 10: 78 | if echo : 79 | print("requests request_get", url) 80 | try : 81 | response = requests.get(url, headers=headers) 82 | except Exception as e: 83 | print(e) 84 | time.sleep(20) 85 | cnt2 += 1 86 | continue 87 | if str(response) != '': 88 | print('sleep ', url) 89 | time.sleep(15) 90 | cnt2 += 1 91 | return response.json() 92 | 93 | # 예 'https://api.upbit.com/v1/trades/ticks?market=KRW-JST&count=500&daysAgo=0' 94 | # count : 최대 500 95 | # ago : 0 -7까지 (0은 당일, 7은 7일 전) 96 | # to : HH:mm:ss 97 | 98 | def get_ticks_history(coin, ago=0, count=500, to=None) : 99 | base_url = 'https://api.upbit.com/v1/trades/ticks' 100 | code = '?market=' + coin 101 | s_count = '&count=' + str(count) 102 | s_ago = '&daysAgo='+ str(ago) 103 | url = base_url + code + s_count + s_ago 104 | if to == None : 105 | s_to = '' 106 | else : 107 | url += ('&to='+to) 108 | 109 | ret = request_get(url) 110 | return ret 111 | 112 | # data 중 frm보다 오래된 데이터는 지운다. 113 | # tick : 'trade_time_utc' 114 | def remove_data(data, frm, key) : 115 | del_items = 0 116 | pos = 0 117 | for each in data : 118 | if each[key] < frm : 119 | del_items = pos 120 | break 121 | pos += 1 122 | 123 | return data[:pos] 124 | 125 | # sequential_id는 int 형인데 str로 바꿈 126 | def change_seq_id_to_str(data, key) : 127 | for each in data : 128 | each[key] = str(each[key]) 129 | return data 130 | 131 | # 이어 받기 용 to(sec) 값을 얻음 132 | def get_next_to(data, key) : 133 | pre_to = '' 134 | for i in range(len(data)-1) : 135 | if data[i][key] != data[i+1][key] : 136 | pre_to = data[i][key] 137 | return pre_to 138 | 139 | # tick은 from 개념은 없고, ago 값으로 일자 조절함. ago = 0 이면 오늘 1이면 어제 등등 최대 7일 전까지 얻을 수 있음 140 | # to 는 HH:mm;ss 형태임 (UTC 기준) 혹은 None 141 | # 들어오는 시간 값은 uct 기준임 142 | # 예 'https://api.upbit.com/v1/trades/ticks?market=KRW-JST&count=500&daysAgo=0' 143 | 144 | def get_data_continue_tick(coin, count=10, ago=0, frm=None, to=None) : 145 | end = False 146 | cnt = 1 147 | 148 | next_to = to 149 | seq_id = 0 150 | # tick 정보는 utc 기준으로 일을 정함 151 | while(end == False) : 152 | t = int(time.time()) 153 | ret = get_ticks_history(coin, ago, count, next_to) # ago : 며칠 전인지. ticks는 하루 기준 154 | 155 | if len(ret) > 0 : 156 | # 마지막 sec 정보는 불안정할 수 있으므로, 연속으로 받을 sec을 구할 때 가장 적은 값보다 적은 sec을 구한다. 157 | # 즉 최종 sec은 다음 turn에서 다시 받는다. 158 | # 그 이유는 한번에 받을 수 있는 자료 수는 제한적이므로, 마지막 sec 정보는 모두 받지 못할 경우가 있음 159 | 160 | next_to = get_next_to(ret, 'trade_time_utc') 161 | 162 | if frm != None : # from보다 이전 데이터인지 확인 163 | # 마지막 candle이 from보다 적으면 from 이후 candle을 지운다. 164 | if next_to < frm : 165 | ret = remove_data(ret, frm, 'trade_time_utc') 166 | end = True 167 | 168 | ret = change_seq_id_to_str(ret, 'sequential_id') 169 | 170 | # cnt 번호를 추가하여 파일이름 생성 171 | info = ret[-1] 172 | fname = coin+'_tick_1_' + format(cnt, '03d') + '_' + info['trade_date_utc'] + '.csv' 173 | cnt += 1 174 | save_to_file_csv(fname, ret) 175 | print ('save ', fname, next_to) 176 | if next_to == '' : 177 | return 178 | if cnt % 10 == 0 : 179 | time.sleep(2) 180 | 181 | else : 182 | return 183 | 184 | def merge_excel_file(fname) : 185 | files = [] 186 | filter = '.\\' + fname + '*.csv' 187 | for filename in glob.glob(filter): 188 | files.append(filename) 189 | if len(files) == 0 : 190 | print('not exist files to merge') 191 | return 192 | all = [] # 파일에서 읽어들인 모든 데이터를 저장하는 list 193 | key1 = -1 # sorting할때 사용할 첫 번째 key (sequential_id) 194 | key2 = -1 # 파일저장할 때 사용할 일자 key (date) 195 | key3 = -1 # 파일 마지막에 있는 sec 삭제 용 key (time) 196 | title = [] 197 | 198 | num_file = 0 199 | for name in files : 200 | data = read_csv(name) 201 | if data != [] : 202 | # key 위치 얻기, 처음에 한번만 실행 key1 : timestamp 203 | if key1 == -1 or key2 == -1 or key3 == -1 : 204 | title = data[0].copy() 205 | for i in range(len(data[0])) : 206 | if data[0][i] == 'sequential_id' : 207 | key1 = i 208 | if data[0][i] == 'trade_date_utc' : 209 | key2 = i 210 | if data[0][i] == 'trade_time_utc' : 211 | key3 = i 212 | 213 | # tick의 경우에는 1초에 여러개 들어올 수 있음 214 | # 500개 제한 때문에 이전에 읽은 데이터는 불안정함 215 | # 따라서 all 데이터 중 마지막 to 값은 지운다. 216 | if len(all) > 0 : 217 | first_tm = all[-1][key3] # 218 | del_pos = [] 219 | for i in range(len(all)-1,0, -1) : # all에 저장된 데이터를 뒤에서 읽으면서 220 | if all[i][key3] == first_tm : 221 | del_pos.append(i) # 지울 데이터의 row를 저장 222 | else : 223 | break 224 | 225 | for pos in del_pos : # 지울 데이터가 있으면(중복된) 지운다. 226 | del(all[pos]) 227 | 228 | # 방금 읽은 data의 0번째(title)을 지운다. 229 | del(data[0]) # title은 지운다. 230 | all += data 231 | 232 | num_file += 1 233 | 234 | # 오름차순으로 정 235 | merged_data = sorted(all, key = lambda x: (x[key1]), reverse=False) # key1 번째가 key 236 | 237 | dt = merged_data[1][key2] 238 | 239 | out_name = '.\\out_' + fname + '_' + dt + '.csv' 240 | save_list_to_file_csv(out_name, title, merged_data) 241 | print('merge finished', out_name) 242 | 243 | 244 | # for tick 245 | # utc 기반으로 받됨 246 | # 하루 기준으로 받음. 247 | COIN = 'KRW-BTC' # 원하는 코인 정보 248 | TICK_AGO = 0 # tick정보는 ago를 to 개념으로 봐야함 ( 0: 오늘 1 : 어제 최대 7일 전 ) 249 | TICK_FROM = '08:05:00' # HH:mm:ss (utc 기준임) or None (00:00:00) 250 | TICK_TO = '00:05:00' # HH:mm:ss (utc 기준임) or None (최근) 251 | 252 | CANDLE_TYPE = 'tick' # 'min' or 'day' 253 | 254 | if __name__ == '__main__': 255 | 256 | print('get tick', COIN, 'ago: ', TICK_AGO) 257 | get_data_continue_tick(COIN, count=500, ago=TICK_AGO, frm=TICK_FROM, to=TICK_TO) # tick을 받을 때 258 | 259 | # merge 260 | filename_prefix = COIN + '_' + CANDLE_TYPE # 결과를 저장할 prefix 261 | print(' -- start merging --') 262 | merge_excel_file(filename_prefix) 263 | -------------------------------------------------------------------------------- /my_candle.py: -------------------------------------------------------------------------------- 1 | # date : 2020/11/19 2 | # 시뮬레이션에 사용하는 candle class 3 | # 4 | # 보다 자세한 내용을 아래 tistory 참고 5 | # https://money-expert.tistory.com/34 6 | # 7 | 8 | # basic functions for candle 9 | class Candle : 10 | def __init__(self) : 11 | self.clear() 12 | 13 | def add(self, info) : 14 | self.vol += info['vol'] 15 | self.date = info['date'] 16 | self.time = '' 17 | if 'time' in info : 18 | self.time = info['time'] 19 | if self.open == 0 : 20 | self.open = info['open'] 21 | self.high = info['high'] 22 | self.low = info['low'] 23 | self.close = info['close'] 24 | if self.high < info['high'] : 25 | self.high = info['high'] 26 | if self.low > info['low'] : 27 | self.low = info['low'] 28 | self.close = info['close'] 29 | 30 | if info['rate'] != '0' : 31 | # 수정 주가 대상인 경우에만 반영 32 | # todo 33 | self.rate = float(info['rate']) 34 | self.rate_type = int(info['rate_type']) 35 | 36 | def clear(self) : 37 | self.date = '' 38 | self.time = '' 39 | self.open = 0 40 | self.high = 0 41 | self.low = 0 42 | self.close = 0 43 | self.vol = 0 44 | self.rate = 0 45 | self.rate_type = 0 46 | 47 | def new_candle(self, data) : # upbit 데이터로 candle 만들기 48 | dt = data['candleDateTimeKst'].split('T') 49 | self.date = dt[0] 50 | dtt = dt[1].split('+') 51 | self.time = dtt[0] 52 | self.open = float(data['openingPrice']) 53 | self.high = float(data['highPrice']) 54 | self.low = float(data['lowPrice']) 55 | self.close = float(data['tradePrice']) 56 | self.vol = float(data['candleAccTradeVolume']) 57 | 58 | def print(self) : 59 | print(self.date, self.time, self.open, self.high, self.low, self.close, self.vol) 60 | 61 | if __name__ == '__main__': 62 | 63 | print('my_candle') 64 | -------------------------------------------------------------------------------- /my_util.py: -------------------------------------------------------------------------------- 1 | # date : 2020/11/19 2 | # 시뮬레이션에 사용하는 utilities 3 | # 4 | # 보다 자세한 내용을 아래 tistory 참고 5 | # https://money-expert.tistory.com/34 6 | # 7 | import json 8 | import csv 9 | 10 | # 11 | # for read data from cvs 12 | # 13 | # row : value list 14 | def get_new_item(keys, row) : 15 | data = {} 16 | for i in range(len(row)) : 17 | data[keys[i]] = row[i] 18 | return data 19 | 20 | # 첫 줄은 title이라고 가정, 이후에 title 값을 key로 갖는 dict로 읽기 21 | def read_csv_to_dict(fname) : 22 | data = [] 23 | keys =[] 24 | first = 1 25 | with open(fname, 'r', encoding='UTF8') as FILE : 26 | csv_reader = csv.reader(FILE, delimiter=',', quotechar='"') 27 | for row in csv_reader : 28 | if first : # make dict keys 29 | keys = row.copy() 30 | # for key in row : 31 | # keys .append(key) 32 | first = 0 33 | else : 34 | data.append(get_new_item(keys, row)) 35 | return data 36 | 37 | # 38 | # for writing data to cvs format 39 | # 40 | def save_to_file_csv(file_name, data) : 41 | with open(file_name,'w',encoding="cp949") as make_file: 42 | # title 저장 43 | vals = data[0].keys() 44 | ss = '' 45 | for val in vals: 46 | val = val.replace(',','') 47 | ss += (val + ',') 48 | ss += '\n' 49 | make_file.write(ss) 50 | 51 | for dt in data: 52 | vals = dt.values() 53 | ss = '' 54 | for val in vals: 55 | sval = str(val) 56 | sval = sval.replace(',','') 57 | ss += (sval + ',') 58 | ss += '\n' 59 | make_file.write(ss) 60 | make_file.close() 61 | 62 | if __name__ == '__main__': 63 | 64 | print('myutil') 65 | --------------------------------------------------------------------------------