# -*- coding: utf-8 -*- # 데이터베이스(MYSQL) ---- from db import DB # Email from mail import Mail import importlib # 지표 적용 모듈 로드 from indicator_util import * import json import time import sys, os # trade 테이블(거래 내역) : 금융, 거래소, 매매 타입, 종목, 시간타입, 가격, 목표가, 매매전략, 알림유무, 일자 BASE_DIR = os.path.dirname(os.path.abspath(__file__)) + '/collectors' ''' - (매시간 단위)Base 테이블의 새 데이터 추가시 테이블 생성(종목-거래소) - (매일 단위) 거래소-종목 거래량 미달 시 해당 종목 포지션 청산 후 테이블 삭제 - (매분 단위)종목-거래소별 Tinker 정보 저장(1분, 1시간, 1일) => 1분만 할지 다시 고민 - (매시간 단위) 시간봉 데이터 저장 및 분석 클래스에 전달 - (매일 단위) 일봉 데이터 저장 및 분석 클래스에 전달 (현행) - 트레이더 클래스 구현 - 트레이더 유저별 거래 기능 구현 => 진행중 - 거래소별 반복문 => 시뮬레이션 완료된 시간 종목만 - 차트 데이터 로드(거래소 or DB) - 매매 전략 적용(현재 포지션 상태 가져오기) - 매매 구현 => 거래소별 => 트레이드 테이블에 데이터 추가 - 스톱 로스/프로핏 구현 - 분할 매매 => 보류(구현만) - 파동 관점 타켓가 구현 => 보류(구현만) - 트레이딩 데이터 DB 저장 구현 - 봇 데이터 생성 및 상태 지속적으로 유지 구현 - 시뮬레이터 최고 수익/승률 시뮬레이팅 부분 재확인 및 보완 ''' class Trader: db = DB() mail = Mail() trade_list = None api_obj = {} def send_email(self, title, msg): self.mail.send_email(title, msg) def run(self): print('Oh! my bot. Trading Bot Working.') users = self.get_users_id() # 유저 수 비례 반복문 for user in users: bot_list = self.bot_list_by_user_id(user['id']) # 작동중인 봇 리스트 반복문(상태값 Y) for b in bot_list: self.bot_trading(b) def bot_trading(self, bot_info): # bot_info['time_unit'] = 'hour6' # for test # bot_info['target'] = 'crypto_bithumb_KRW-BTC' d = self._get_price_data_by_info(bot_info) current_price = float(self._get_last_price(bot_info)) # 포지션 존재 시 if bot_info['position'] != 'None' or str(bot_info['position']).lower() != 'none': bot_info['position_price'] = float(bot_info['position_price']) # 스탑 로스 / 프로핏 선실행 if bot_info['stop_profit'] > 0: # long - profit / short - loss tp = bot_info['position_price'] + float(bot_info['stop_profit'])*bot_info['position_price'] if tp < current_price: bot_info['last_signal'] = d.index[-1] return self.close_position(bot_info) if bot_info['stop_loss'] > 0: # long - loss / short - profit lp = bot_info['position_price'] - float(bot_info['stop_loss'])*bot_info['position_price'] if lp > current_price: bot_info['last_signal'] = d.index[-1] return self.close_position(bot_info) is_stop = False # 손절 로직 => 5퍼 이상 손실일 경우 강제 손절 => 종목 시뮬레이팅 재실행 stop_persent = 0.056 # 숏 포지션일 경우 if bot_info['position'].lower() == 'short': limit = bot_info['position_price'] + bot_info['position_price'] * stop_persent if current_price > limit: is_stop = True # 롱 포지션일 경우 if bot_info['position'].lower() == 'long': limit = bot_info['position_price'] - bot_info['position_price'] * stop_persent if current_price < limit: is_stop = True if is_stop: self.db.update_bot_data_for_stop_by_id(bot_info['id']) self._init_simul_status(bot_info['target']) self.close_position(bot_info) title = '[%s] - 봇이 손절프로세스를 실행합니다.' % (bot_info['target']) body = "\n".join('{} : {}'.format(key, value) for key, value in bot_info.items()) return self.send_email(title, body) # 최근 시그널에 포지션 진입했을 경우 생략 if str(d.index[-1]) == str(bot_info['last_signal']): print('Previous signal is equal') return # 시그널 획득 signal = self._get_trade_signal_by_data(d, bot_info) # 포지션 진입 및 종료 if signal is not None and bot_info['position'] != signal: bot_info['last_signal'] = d.index[-1] if bot_info['trade_type'] == 'double' and bot_info['position'] != 'None': self.close_position(bot_info) # for swap if signal == 'long': self.long(bot_info) elif signal == 'short': self.short(bot_info) del d, signal def _init_simul_status(self, target): t = str(target).split('_') i = '/'.join(t[:-1]) self.db.update_tb_cron_simul_status(i) def _get_last_price(self, b_info): e_name = self._get_exchange_name_from_target_name(b_info['target']) i_name = self._get_item_name_from_target_name(b_info['target']) return self.api_obj[e_name].get_current_price(i_name) ''' Only Long - Single - Long Signal : 매수 진입 - Short Signal : 매도(포지션 종료) - Stop Signal(close_position) : 현재 포지션 종료 Margin(Swap) - Double - Long Signal : 숏 포지션 종료 => 롱 포지션 진입 - Short Signal : 롱 포지션 종료 => 숏 포지션 진입 - Stop Signal(close_position) : 현재 포지션 종료 ''' def long(self, b_info, is_swap=False): time.sleep(1) if str(b_info['position']).lower() == 'long': return profit = None e_name = self._get_exchange_name_from_target_name(b_info['target']) i_name = self._get_item_name_from_target_name(b_info['target']) # 수익 발생 시 봇 모드 변경 if b_info['mode'] == 'test': price = self._get_last_order_price_by_position(b_info, 'long') if b_info['position'] == 'short': profit = bool(float(price) < float(b_info['position_price'])) b_info['position'] = 'long' b_info['position_price'] = price b_info['trade_cnt'] += 1 if profit: # 봇 정보 초기화 b_info['mode'] = 'service' b_info['position'] = 'None' b_info['position_price'] = 0 b_info['profit'] = 0 b_info['winrate'] = 0 b_info['trade_cnt'] = 0 b_info['win_cnt'] = 0 b_info['lose_cnt'] = 0 # 계정 자산(원화) 동기화 => 첫 포지션 진입 시로 대체 # amt = self._get_amount_from_exchange(b_info) b_info['amount'] = 0 b_info['last_amount'] = 0 title = '[%s] %s - 모의트레이딩 종료 알림' % (e_name, i_name) body = "\n".join('{} : {}'.format(key, value) for key, value in b_info.items()) self.send_email(title, body) if is_swap: b_info['position'] = 'None' b_info['position_price'] = 0 self.db.bot_update_by_trade_data(b_info) return res, info = self.api_obj[e_name].order_long(i_name) if res: profit = 0 info['seeds'] = self.api_obj[e_name].get_all_seeds() if float(b_info['last_amount']) != 0: b_info['last_profit'] = round((float(info['seeds']) / float(b_info['last_amount']) - 1) * 100, 2) b_info['last_amount'] = info['seeds'] if b_info['amount'] != 0: profit = round((float(info['seeds']) / float(b_info['amount']) - 1) * 100, 2) else: b_info['amount'] = info['seeds'] # 포지션 종료 및 수익 데이터 저장 if b_info['position'] != 'None': b_info['trade_cnt'] += 1 b_info['profit'] = profit # 수익률 기반 승률 산출 if profit > 0: b_info['win_cnt'] += 1 b_info['winrate'] = round((int(b_info['win_cnt'])/(int(b_info['win_cnt'])+int(b_info['lose_cnt'])))*100, 2) else: b_info['lose_cnt'] += 1 # 수익 25프로 이상 시 시드 초기화 => 봇 사이클 제한 ''' if b_info['profit'] > 25 or b_info['last_profit'] < -2: b_info['profit'] = 0 b_info['amount'] = b_info['last_amount'] ''' b_info['position'] = 'long' b_info['position_price'] = info['target_price'] if is_swap: b_info['position'] = 'None' b_info['position_price'] = 0 self.db.bot_update_by_trade_data(b_info) self.db.insert_trade_data_to_tb_trade(b_info) self.send_msg_about_position(b_info, info, b_info['position']) def short(self, b_info, is_swap=False): time.sleep(1) if str(b_info['position']).lower() == 'short': return # 단방향 봇은 무포시 숏포지션 로직 실행 안함 if str(b_info['position']).lower() == 'none' and b_info['trade_type'] == 'single': return profit = None e_name = self._get_exchange_name_from_target_name(b_info['target']) i_name = self._get_item_name_from_target_name(b_info['target']) if b_info['mode'] == 'test': price = self._get_last_order_price_by_position(b_info, 'short') profit = 0 if b_info['position'] == 'long': profit = bool(float(price) > float(b_info['position_price'])) # 봇 수익 타입별 포지션 지정 if b_info['trade_type'] == 'single': b_info['position'] = 'None' b_info['position_price'] = 0 elif b_info['trade_type'] == 'double': b_info['position'] = 'short' b_info['position_price'] = price b_info['trade_cnt'] += 1 if profit: # 실 수익 시 봇 모드 변경 및 초기화 b_info['mode'] = 'service' b_info['position'] = 'None' b_info['position_price'] = 0 b_info['profit'] = 0 b_info['winrate'] = 0 b_info['trade_cnt'] = 0 b_info['win_cnt'] = 0 b_info['lose_cnt'] = 0 # 계정 자산(원화) 동기화 # amt = self._get_amount_from_exchange(b_info) b_info['amount'] = 0 b_info['last_amount'] = 0 # b_info['amount'] = amt['KRW'] # b_info['last_amount'] = amt['KRW'] title = '[%s] %s - 모의트레이딩 종료 알림' % (e_name, i_name) body = "\n".join('{} : {}'.format(key, value) for key, value in b_info.items()) self.send_email(title, body) if is_swap: b_info['position'] = 'None' b_info['position_price'] = 0 self.db.bot_update_by_trade_data(b_info) return res, info = self.api_obj[e_name].order_short(i_name) if res: profit = 0 info['seeds'] = self.api_obj[e_name].get_all_seeds() if float(b_info['last_amount']) != 0: b_info['last_profit'] = round((float(info['seeds']) / float(b_info['last_amount']) - 1) * 100, 2) b_info['last_amount'] = info['seeds'] if b_info['amount'] != 0: # 수익률 산출 profit = round((float(info['seeds']) / float(b_info['amount']) - 1) * 100, 2) else: b_info['amount'] = info['seeds'] # profit = round((float(info['target_price']) / float(b_info['position_price']) - 1) * 100, 2) # 포지션 종료 및 수익 데이터 저장 if b_info['position'] != 'None': b_info['trade_cnt'] += 1 b_info['profit'] = profit # 포지션 종료 시 승률 산출 if profit > 0: b_info['win_cnt'] += 1 b_info['winrate'] = round((int(b_info['win_cnt'])/(int(b_info['win_cnt'])+int(b_info['lose_cnt'])))*100, 2) # b_info['winrate'] = round((b_info['win_cnt'] / (b_info['trade_cnt'] / 2)) * 100, 2) else: b_info['lose_cnt'] += 1 # 수익 25프로 이상 시 시드 초기화 => 봇 사이클 제한 ''' if b_info['profit'] > 50 or b_info['last_profit'] < -2: b_info['profit'] = 0 b_info['amount'] = b_info['last_amount'] ''' # 새 포지션 진입 b_info['position'] = 'short' b_info['position_price'] = info['target_price'] if 'single' in b_info['trade_type']: b_info['position'] = 'None' b_info['position_price'] = 0 if is_swap: b_info['position'] = 'None' b_info['position_price'] = 0 self.db.bot_update_by_trade_data(b_info) self.db.insert_trade_data_to_tb_trade(b_info) self.send_msg_about_position(b_info, info, b_info['position']) # 외부 클래스에서 봇 정보를 통해 강제 종료 로직 def force_close_position(self, b_info): # set exchage obj self._get_last_order_price_by_position(b_info, b_info['position']) # force close position self.close_position(b_info) # DB 안의 상태값을 통해 현재의 포지션 정보 획득 # 거래소 API를 통해 현재의 포지션을 받아온다. => 추후 기능 추가 def close_position(self, b_info): if str(b_info['position']).lower() == 'long': self.short(b_info, True) elif str(b_info['position']).lower() == 'short': self.long(b_info, True) time.sleep(5) def send_msg_about_position(self, b_info, trade_info, position='None'): e_name = self._get_exchange_name_from_target_name(b_info['target']) i_name = self._get_item_name_from_target_name(b_info['target']) if 'none' in str(position).lower() or position is None: title = '[%s] %s - 포지션 종료(봇 수익률 : %s%%)' % (e_name, i_name, b_info['profit']) else: title = '[%s] %s - %s 포지션 진입' % (e_name, i_name, position.upper()) body = "\n".join('{} : {}'.format(key, value) for key, value in b_info.items()) return self.send_email(title, body) def update_bot_position(self, b_info): self.db.upsert_bot_data(b_info) # Use Secret API def _get_amount_from_exchange(self, b_info): e_name = self._get_exchange_name_from_target_name(b_info['target']) i_name = self._get_item_name_from_target_name(b_info['target']) return self.api_obj[e_name].get_now_amount(i_name) # api 키 조회 def _get_access_key(self, b_info): return self.db.select_user_keys_by_id(b_info['user_id']) def _get_last_order_price_by_position(self, b_info, position): e_name = self._get_exchange_name_from_target_name(b_info['target']) i_name = self._get_item_name_from_target_name(b_info['target']) keys = self._get_access_key(b_info) if not hasattr(self.api_obj, e_name): # for test # module = self.load_module_by_path('.'.join(['collectors', 'crypto', e_name])) module = importlib.import_module('.'.join(['collectors', 'crypto', 'c_' + e_name])) if keys[e_name]: access = keys[e_name]['access'] secret = keys[e_name]['secret'] e_obj = getattr(module, str(e_name).capitalize())(access, secret) else: e_obj = getattr(module, str(e_name).capitalize())() self.api_obj[e_name] = e_obj # test end # 호가 조회 return self.api_obj[e_name].get_last_price_from_orderbook(i_name, position) def _get_item_name_from_target_name(self, target): return target.split('_')[2] def _get_exchange_name_from_target_name(self, target): return target.split('_')[1] def _get_trade_signal_by_data(self, data, bot_info): # return 'long' # return 'short' strategy = json.loads(bot_info['strategy']) res = Signal().get_signal_by_indicators(data, strategy) print(res[-5:-1]) # test - check signal match # for idx in range(len(res)): # if len(strategy) is len(res[idx]): # if all(res[idx]): # print('long -', data.index[idx]) # 시그널 확인 # print(res);sys.exit(1); if len(strategy) is len(res[-2]): if all(res[-2]): return 'long' elif not any(res[-2]): return 'short' return None # def load_module_by_path(self, module_name): # mod = __import__('%s' % (module_name), fromlist=[module_name]) # return mod def _get_price_data_by_info(self, info): exchange_info = info['target'].split('_') # set secret api to exchange object keys = self._get_access_key(info) # add dynamically path to os.path if not BASE_DIR + '/' + exchange_info[0] in sys.path: sys.path.append(BASE_DIR + '/' + exchange_info[0]) # module = self.load_module_by_path('.'.join(['collectors', exchange_info[0], 'c_'+exchange_info[1]])) mod = importlib.import_module('.'.join(['collectors', exchange_info[0], 'c_'+exchange_info[1]])) # if not exchange_info[1] in keys: # key = {exchange_info[1]: { # 'access': 'access', # 'secret': 'secret' # }} # self.db.upsert_keys_to_user_row('javamon1174@gmail.com', key) # keys = self._get_access_key(info) if keys[exchange_info[1]]: access = keys[exchange_info[1]]['access'] secret = keys[exchange_info[1]]['secret'] e_obj = getattr(mod, str(exchange_info[1]).capitalize())(access, secret) else: e_obj = getattr(mod, str(exchange_info[1]).capitalize())() # set leverage to obj e_obj.leverage = int(info['leverage']) # set api obj(to instance variable) self.api_obj[exchange_info[1]] = e_obj r_data = e_obj.get_history_data(exchange_info[2], info['time_unit'], False) if r_data is None: return self._get_price_data_by_info(info) return r_data # for test # return r_data[-200:] def bot_list_by_user_id(self, id): return self.db.get_bots_by_user_id(id) def get_users_id(self): return self.db.select_all_users_id_for_trade() if __name__ == "__main__": print('Oh! my bot. Trading Started.') t = Trader() t.run()