첫 번째 커밋
This commit is contained in:
521
trader.py
Normal file
521
trader.py
Normal file
@@ -0,0 +1,521 @@
|
||||
# -*- 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()
|
||||
|
||||
Reference in New Issue
Block a user