첫 번째 커밋
This commit is contained in:
137
.gitignore
vendored
Normal file
137
.gitignore
vendored
Normal file
@@ -0,0 +1,137 @@
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
__pycache__
|
||||
/__pycache__
|
||||
__pycache__/
|
||||
.idea
|
||||
/.idea
|
||||
.idea/
|
||||
venv
|
||||
/venv
|
||||
venv/
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
pip-wheel-metadata/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
66
app.py
Normal file
66
app.py
Normal file
@@ -0,0 +1,66 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import time
|
||||
import datetime
|
||||
|
||||
# 데이터베이스(MYSQL) ----
|
||||
from db import DB
|
||||
|
||||
# 배치프로세스 (Cron) ----
|
||||
from cron import Cron
|
||||
|
||||
# 지표 ----
|
||||
# 머신러닝 ----
|
||||
# 헬퍼함수 ----
|
||||
|
||||
# base path
|
||||
# BASE_DIR = os.path.dirname(os.path.abspath(__file__)) + '/collectors'
|
||||
|
||||
# Cron(scheduler) : 반복적인 작업(배치 프로세스)를 위한 클래스 => 지속적으로 추가되는 테이블 생성 및 데이터(크롤링) 저장
|
||||
# Trader : 현재가를 기준으로 트레이딩하는 클래스 => 10초 마다 실행
|
||||
# Analysis : 트레이더 클래스에서 현재가를 전달 받으면 진입할 지 안할 지를 리턴해주는 클래스 => 물타기 등 다양한 판단에 따른 거래를 위한 데이터 리턴
|
||||
# Indicator : 기술적 관점에 따라 매매 포지션 상태값을 리턴 => 보조지표, 주 지표 분석(봉 패턴 등)을 활용
|
||||
# Muchine : 머신러닝 기반(통계적 관점)에 따라 매매 포지션 상태값을 리턴
|
||||
# App : 앱의 초기화 및 기본 셋업 클래스 => 인터페이스의 역할도 수행(실행된 프로세스 리스트 출력)
|
||||
|
||||
class App:
|
||||
exchange_instance_list = {}
|
||||
count = 0 # for multi threading
|
||||
|
||||
def __init__(self):
|
||||
# Set db cursor obj
|
||||
self.db = DB()
|
||||
|
||||
# create default tables
|
||||
self.db.create_default_tables()
|
||||
|
||||
# insert default data
|
||||
self.db.insert_default_tables()
|
||||
|
||||
# set cron
|
||||
self.cron = Cron()
|
||||
|
||||
# def __del__(self): return False
|
||||
|
||||
# def start_batch_process(self):
|
||||
# t_cron = threading.Thread(target=self.cron.start)
|
||||
# t_cron.daemon = True
|
||||
# t_cron.start()
|
||||
|
||||
# 금융 종목 추가
|
||||
def add_new_exchange(self, finance, exchange):
|
||||
self.db.insert_finance_to_base(finance, exchange)
|
||||
|
||||
# 배치 프로세스 실행
|
||||
def start(self):
|
||||
self.cron.start()
|
||||
# self.start_batch_process()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print('Oh! my bot. Started.', datetime.datetime.now())
|
||||
app = App()
|
||||
app.start()
|
||||
|
||||
while True:
|
||||
time.sleep(3600)
|
||||
187
bot.py
Normal file
187
bot.py
Normal file
@@ -0,0 +1,187 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from db import DB
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import sys
|
||||
|
||||
from trader import Trader
|
||||
|
||||
# Email
|
||||
from mail import Mail
|
||||
|
||||
|
||||
''' 완료 항목
|
||||
# Bot Table : 금융 아이디, 거래소 아이디, 아이템, 예상수익, 실제 수익, 예상 승률,
|
||||
# 실제 승률, 거래수, 매매전략, 시간종류
|
||||
|
||||
# 봇 생성 조건
|
||||
# 1. 시뮬레이팅 완료된 종목
|
||||
# 2. 수익률 15프로 이상 / 승률 80프로 이상의 매매전략이 있을 경우만
|
||||
|
||||
# 봇 매매전략 업데이트
|
||||
# 중지된 봇을 재 시뮬레이팅 후 최고 수익률로 전략 업데이트
|
||||
|
||||
# 봇 삭제 조건
|
||||
# 거래수 5번이상 일 때, 실제 수익이 3퍼센트 미만일 경우 봇 상태값 N으로 변경 => 수익성 없음 판단
|
||||
'''
|
||||
|
||||
''' 진행중 항목
|
||||
# 포지션 존재 => 트레이더 객체를 통해 포지션이 있을 경우 포지션은 종료한다. => 트레이더 객체 구현 시 적용
|
||||
'''
|
||||
|
||||
|
||||
class Bot:
|
||||
db = DB()
|
||||
trader = Trader()
|
||||
mail = Mail()
|
||||
|
||||
def send_email(self, title, msg):
|
||||
self.mail.send_email(title, msg)
|
||||
|
||||
def run(self):
|
||||
# 테이블 존재 체크
|
||||
if not (self.db.is_table('bot') or self.db.is_table('simulation')):
|
||||
return
|
||||
|
||||
print('Started. Bot Process')
|
||||
|
||||
# 2달 지난 시뮬레이터 기록 삭제
|
||||
self._remove_simul_results_deadline()
|
||||
|
||||
# 거래소별 봇 생성 및 업데이트
|
||||
for e in self.db.select_exchange_list_for_bot():
|
||||
info = e['exchange_info']
|
||||
|
||||
# 시뮬레이팅이 완료된 종목만 실행
|
||||
if not self.is_exchange_complete_sumulations(info):
|
||||
continue
|
||||
|
||||
# 봇 생성 및 갱신
|
||||
self._make_update_bots(info)
|
||||
|
||||
# 봇 생성 및 업데이트
|
||||
# for i in self._get_trade_list():
|
||||
# # 해당 거래소 시뮬레이팅 완료 체크
|
||||
# self._make_update_bots(i)
|
||||
|
||||
# 수익이 저조한 봇 중지
|
||||
for bot in self.get_running_bots():
|
||||
self._check_bot_profit_and_stop(bot)
|
||||
|
||||
# 초기 시드 갱신
|
||||
# self.update_seed_data_in_bot(bot)
|
||||
|
||||
print('Ended. Bot Process')
|
||||
|
||||
def is_exchange_complete_sumulations(self, exchange_info):
|
||||
# i = info['job'].split('/')
|
||||
# ex = '_'.join(i[:-2])
|
||||
|
||||
job_info = self.db.select_tb_cron_simul_status(exchange_info)
|
||||
|
||||
for s in job_info:
|
||||
if s['init_simul'] == 'N':
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def update_seed_data_in_bot(self, bot):
|
||||
deadline_date = datetime.now() - timedelta(weeks=5)
|
||||
if bot['update_date'] < deadline_date:
|
||||
updated_date = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
self.db.update_first_seed_by_now_seed(bot, updated_date)
|
||||
|
||||
def _remove_simul_results_deadline(self):
|
||||
deadline_date = datetime.now() - timedelta(weeks=10)
|
||||
self.db.delete_simul_results_by_deadline(deadline_date)
|
||||
|
||||
# 트레이더 객체를 통해 해당 봇의 포지션을 종료한다.
|
||||
def _close_bot_position(self, bot_info):
|
||||
if bot_info['position'] != 'None':
|
||||
self.trader.force_close_position(bot_info)
|
||||
|
||||
def _check_bot_profit_and_stop(self, bot):
|
||||
if int(bot['trade_cnt']) >= 6 and float(bot['profit']) < -1 or float(bot['last_profit']) <= -2:
|
||||
self._close_bot_position(bot)
|
||||
self._stop_bot_by_id(bot['id'])
|
||||
self._init_simul_status(bot['target'])
|
||||
title = '[%s] %s mode - 해당 거래소 봇이 수익 미달로 중지되었습니다' % (bot['target'], bot['mode'])
|
||||
body = "\n".join('{} : {}'.format(key, value) for key, value in bot.items())
|
||||
self.send_email(title, body)
|
||||
|
||||
def _stop_bot_by_id(self, bot_id):
|
||||
self.db.update_bot_data_for_stop_by_id(bot_id)
|
||||
|
||||
def _init_simul_status(self, target):
|
||||
t = str(target).split('_')
|
||||
i = '/'.join(t[:-1])
|
||||
self.db.update_tb_cron_simul_status(i)
|
||||
|
||||
def get_running_bots(self):
|
||||
return self.db.select_running_bots()
|
||||
|
||||
def _make_update_bots(self, e_info):
|
||||
# s_item = t_item['job'].split('/')
|
||||
# e_i = self.db.get_id_by_exchange_name(s_item[1])
|
||||
res = self._get_satisfy_condition_result_row(e_info)
|
||||
|
||||
if res:
|
||||
self._upsert_bot_data(res)
|
||||
|
||||
def _upsert_bot_data(self, this_bot):
|
||||
item = this_bot['t_table_name'].split('_')
|
||||
target = '_'.join(item[:-1])
|
||||
this_bot['target'] = target
|
||||
|
||||
prev_bot = self.db.get_bot_data_by_target(target)
|
||||
|
||||
return self.compare_prev_bot_and_new_bot(prev_bot, this_bot)
|
||||
|
||||
def compare_prev_bot_and_new_bot(self, prev_bot, this_bot):
|
||||
# 수익률이 없다고 판단 될 시 봇 생성 거부
|
||||
if float(this_bot['profit_rate']) < 15:
|
||||
return False
|
||||
|
||||
# 봇이 없을 경우 새로운 봇 생성
|
||||
if prev_bot is ():
|
||||
title = '[%s] - 해당 거래소 봇이 생성되었습니다.' % (this_bot['target'])
|
||||
body = "\n".join('{} : {}'.format(key, value) for key, value in this_bot.items())
|
||||
self.send_email(title, body)
|
||||
|
||||
return self.db.upsert_bot_data(this_bot)
|
||||
|
||||
# 현재 봇이 최신 봇일 경우
|
||||
if prev_bot['strategy'] == this_bot['used_patterns']:
|
||||
return False
|
||||
|
||||
# 봇이 중지 상태이고, 이전 전략과 다를 경우 갱신
|
||||
if str(prev_bot['status']) == 'N' and \
|
||||
str(prev_bot['strategy']).replace('"', '') != str(this_bot['used_patterns']).replace('"', ''):
|
||||
title = '[%s] - 해당 거래소 봇이 갱신되었습니다.' % (this_bot['target'])
|
||||
body = "\n".join('{} : {}'.format(key, value) for key, value in this_bot.items())
|
||||
self.send_email(title, body)
|
||||
return self.db.upsert_bot_data(this_bot)
|
||||
|
||||
# 비교 로직(수익률/승률 비교)
|
||||
# if float(this_bot['profit_rate']) > float(prev_bot['p_profit']) and \
|
||||
# float(this_bot['win_rate']) >= float(prev_bot['p_winrate']):
|
||||
# return self.db.upsert_bot_data(this_bot)
|
||||
|
||||
# 시뮬레이션 결과 중 조건에 만족하는 데이터 가져오기
|
||||
def _get_satisfy_condition_result_row(self, t_name):
|
||||
res = self.db.get_simul_res_top_row(t_name.replace('/', '_'))
|
||||
|
||||
if res is ():
|
||||
return False
|
||||
|
||||
return res
|
||||
|
||||
def _get_trade_list(self):
|
||||
return self.db.get_completed_simul_list()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
b = Bot()
|
||||
b.run()
|
||||
|
||||
0
collectors/__init__.py
Normal file
0
collectors/__init__.py
Normal file
207
collectors/collector.py
Normal file
207
collectors/collector.py
Normal file
@@ -0,0 +1,207 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import requests, logging, json, time, sys
|
||||
from datetime import datetime
|
||||
from db import DB
|
||||
|
||||
from math import log10, floor
|
||||
|
||||
class Collector:
|
||||
_auth = {}
|
||||
_heaeder = {}
|
||||
_mathod = ''
|
||||
_name = 'target'
|
||||
_temp_api_url = ''
|
||||
_api_url = ''
|
||||
|
||||
allow_items = None
|
||||
|
||||
db = None
|
||||
|
||||
def _set_allow_items(self):
|
||||
if self.is_db_connection():
|
||||
self.allow_items = [_['item_code'] for _ in self.db.select_allow_items_all()]
|
||||
|
||||
def _get(self, url, headers=None, data=None, params=None):
|
||||
resp = requests.get(url, headers=headers, data=data, params=params)
|
||||
|
||||
if resp.status_code == 429 : # 업비트 요청 수 제한으로 인한 1분 지연
|
||||
time.sleep(60)
|
||||
return self._get(url, headers, data, params)
|
||||
|
||||
if resp.status_code not in [200, 201]:
|
||||
logging.error('get(%s) failed(%d)' % (url, resp.status_code))
|
||||
if resp.text is not None:
|
||||
logging.error('resp: %s' % resp.text)
|
||||
raise Exception('request.get() failed(%s)' % resp.text)
|
||||
raise Exception(
|
||||
'request.get() failed(status_code:%d)' % resp.status_code)
|
||||
self._update_remaining_req(resp)
|
||||
|
||||
return json.loads(resp.text)
|
||||
|
||||
def data_columns_init(self, df):
|
||||
t_col = []
|
||||
for c in df.columns:
|
||||
t_col.append(c.lower().capitalize())
|
||||
|
||||
df.columns = t_col
|
||||
|
||||
def cal_ceil(self, x, sig=4):
|
||||
return float(str(x)[:sig])
|
||||
|
||||
def packaging_data_per_time_unit(self, df, t_time):
|
||||
time_type = {
|
||||
'hour': '60min',
|
||||
'hour4':'240min',
|
||||
'hour6': '360min',
|
||||
'hour12': '720min',
|
||||
}
|
||||
ohlc_dict = {
|
||||
'Open': 'first',
|
||||
'High': 'max',
|
||||
'Low': 'min',
|
||||
'Close': 'last',
|
||||
'Volume': 'sum'
|
||||
}
|
||||
|
||||
df = df.resample(time_type[t_time], how=ohlc_dict, label='left', base=540) # UTC (트레이딩뷰)
|
||||
|
||||
# return df[:-1]
|
||||
return df
|
||||
|
||||
def _update_remaining_req(self, resp):
|
||||
if 'Remaining-Req' not in resp.headers.keys():
|
||||
return None
|
||||
keyvals = resp.headers['Remaining-Req'].split('; ')
|
||||
group = None
|
||||
keyval = dict()
|
||||
for _keyval in keyvals:
|
||||
kv = _keyval.split('=')
|
||||
if kv[0] == 'group':
|
||||
group = kv[1]
|
||||
else:
|
||||
keyval[kv[0]] = kv[1]
|
||||
if group is None:
|
||||
return
|
||||
keyval['update_time'] = datetime.now()
|
||||
self.remaining_req[group] = keyval
|
||||
|
||||
def is_db_connection(self):
|
||||
if self.db is None :
|
||||
self.db = DB()
|
||||
|
||||
return True
|
||||
|
||||
# 테이블 체크 후 없을 시 기존 데이터를 추가(거래소별 max의 개수)
|
||||
def is_table_in_db(self, finance, exchange, item, time_type = None):
|
||||
if self.is_db_connection() :
|
||||
return self.db.is_item_table_in_db(finance, exchange, item, time_type)
|
||||
|
||||
# 테이블 확인이 안될 시 없는 것으로 간주
|
||||
return False
|
||||
|
||||
def _save_to_db_from_collectors_data(self, finance = 'crypto', exchange = 'upbit', item='KRW-BTC', data = None, time_type = None):
|
||||
if self.is_db_connection() :
|
||||
if data is not None and time_type is not None :
|
||||
self.db.insertItemData(finance, exchange, item, data, time_type)
|
||||
|
||||
def _save_to_db_from_collectors_dataframe(self, finance = 'crypto', exchange = 'upbit', item='KRW-BTC', data = None, time_type = None):
|
||||
if self.is_db_connection() :
|
||||
if data is not None and time_type is not None :
|
||||
self.db.insertItemDataframe(finance, exchange, item, data, time_type)
|
||||
|
||||
|
||||
def _get_api_url(self):
|
||||
if self.is_db_connection() :
|
||||
url = self.db.select_api_url_from_tb_exchange(self._name)
|
||||
if url is () :
|
||||
return self._temp_api_url
|
||||
|
||||
return str(url['api_url'])
|
||||
|
||||
# 거래소 24시간 기준금액 업데이트
|
||||
def update_exchange_standard_options(self):
|
||||
markets = self._get_data_all_markets()
|
||||
|
||||
self._standard_price = self._get_standard_trade_price(markets)
|
||||
self.db.update_standard_options_from_exchange(self._name, self._standard_price)
|
||||
|
||||
def get_exchange_info_from_db(self):
|
||||
return self.db.select_row_data_from_exchange(self._name)
|
||||
|
||||
def _get_item_list_tb_exchange_and_item(self): # dev
|
||||
e_info = self.get_exchange_info_from_db()
|
||||
|
||||
if e_info is () :
|
||||
return None
|
||||
|
||||
return self.db.select_items_data_from_tb_items_by_exchange_id(e_info['id'])
|
||||
|
||||
# 거래소 기본정보 데이터 저장
|
||||
def _add_exchange_info_to_db(self, finance, exchange, s_price, url):
|
||||
if self.is_db_connection() :
|
||||
self.db.insert_finance_and_exchange_data(finance, exchange, s_price, url)
|
||||
|
||||
# 아이템 종목 리스트 저장(종목 행 데이터 생성)
|
||||
def _save_item_data_to_item_table(self, finance, exchange, item, total_trade_price_24h, total_trade_volume_24h):
|
||||
if self.is_db_connection() :
|
||||
self.db.insert_item_main_data(finance, exchange, item, total_trade_price_24h, total_trade_volume_24h)
|
||||
|
||||
# 거래종목 리스트 로드
|
||||
def _load_markets(self):
|
||||
print('not defined function : %s' % sys._getframe().f_code.co_name)
|
||||
|
||||
# 거래금액으로 필터링 된 거래종목 리스트 반환
|
||||
def _return_list_filted_markets(self, markets):
|
||||
print('not defined function : %s' % sys._getframe().f_code.co_name)
|
||||
|
||||
# 거래 금액 기준 반환 (달러 or 원화)
|
||||
def _get_trade_price(self, markets):
|
||||
print('not defined function : %s' % sys._getframe().f_code.co_name)
|
||||
|
||||
def _get_currency_type(self, item):
|
||||
'''
|
||||
0 : doller
|
||||
1 : krw
|
||||
|
||||
:param item: target item
|
||||
:return: currency_type
|
||||
'''
|
||||
|
||||
if 'KRW' in str(item).upper() :
|
||||
return 1
|
||||
else :
|
||||
return 0
|
||||
|
||||
def get_based_time_from_data(self, data):
|
||||
if 'KRW' in data['target']:
|
||||
return '9'
|
||||
|
||||
return '0'
|
||||
|
||||
# need override
|
||||
def save_current_data(self):
|
||||
print('not defined function : %s' % sys._getframe().f_code.co_name)
|
||||
|
||||
def _return_list_filted_markets(self, markets):
|
||||
print('not defined function : %s' % sys._getframe().f_code.co_name)
|
||||
|
||||
def save_current_min_data(self):
|
||||
print('not defined function : %s' % sys._getframe().f_code.co_name)
|
||||
|
||||
def save_current_hour_data(self):
|
||||
print('not defined function : %s' % sys._getframe().f_code.co_name)
|
||||
|
||||
def save_current_day_data(self):
|
||||
print('not defined function : %s' % sys._getframe().f_code.co_name)
|
||||
|
||||
def _set_standard_price(self):
|
||||
print('not defined function : %s' % sys._getframe().f_code.co_name)
|
||||
|
||||
def get_history_data(self):
|
||||
print('not defined function : %s' % sys._getframe().f_code.co_name)
|
||||
|
||||
def get_last_price_from_orderbook(self):
|
||||
print('not defined function : %s' % sys._getframe().f_code.co_name)
|
||||
|
||||
0
collectors/crypto/__init__.py
Normal file
0
collectors/crypto/__init__.py
Normal file
370
collectors/crypto/c_binance.py
Normal file
370
collectors/crypto/c_binance.py
Normal file
@@ -0,0 +1,370 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import sys
|
||||
from collectors.collector import Collector
|
||||
import logging
|
||||
|
||||
# import API
|
||||
from lib.pybinancefutures import *
|
||||
|
||||
# curl
|
||||
import json
|
||||
import datetime, time
|
||||
import requests
|
||||
from pandas import DataFrame
|
||||
import pandas as pd
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
from db import DB
|
||||
|
||||
# access
|
||||
# DPob3MlV51nb55D6OovjKTNRiyoiMWihX2phcunUNxI73Z7gSyo2ALX87dxcmuXB
|
||||
# secret
|
||||
# qgd5YHf4TiWvD8KjOL1qoPz9QX354mYMIoQ6FBt5VCv1tswQq3X6eGaFFrHZ7a7a
|
||||
|
||||
angel = 'power'
|
||||
|
||||
class Binance(Collector):
|
||||
def __init__(self, access=None, secret=None):
|
||||
self.remaining_req = {}
|
||||
self._heaeder = {}
|
||||
self._mathod = 'get'
|
||||
self._finance = 'crypto' # 금융 종목
|
||||
self._name = 'binance' # 거래소 이름
|
||||
self.leverage = 1
|
||||
self._standard_price = 0
|
||||
self._temp_api_url = 'https://fapi.binance.com/fapi/v1/' # 임시 api 주소
|
||||
|
||||
self._market = MarketData()
|
||||
|
||||
if secret is not None:
|
||||
self._api = Client(access, secret)
|
||||
|
||||
self._api_url = self._temp_api_url
|
||||
|
||||
self._set_allow_items()
|
||||
|
||||
# get filtered markets
|
||||
self.markets = self._load_markets() # 테스트때 주석
|
||||
|
||||
'''
|
||||
For Cron Funtions
|
||||
'''
|
||||
# 바이낸스 기준 시총 정의
|
||||
def _get_standard_trade_price(self, markets=None):
|
||||
p_list = []
|
||||
for m in self._market.ticker_price_24h():
|
||||
p = m['lastPrice']
|
||||
v = m['volume']
|
||||
|
||||
p_list.append(float(p)*float(v))
|
||||
|
||||
p_list.sort(reverse=True)
|
||||
|
||||
return float(p_list[1] - p_list[1] * 0.01)
|
||||
|
||||
def _return_list_filted_markets(self):
|
||||
filtered_list = []
|
||||
|
||||
for m in self._market.ticker_price_24h():
|
||||
s = m['symbol']
|
||||
p = m['lastPrice']
|
||||
v = m['volume']
|
||||
|
||||
f_if = False
|
||||
|
||||
# 리플 제외
|
||||
if 'XRP' in s:
|
||||
continue
|
||||
|
||||
# 허용 아이템 조건
|
||||
for i in self.allow_items:
|
||||
if i == s.replace('USDT', ''):
|
||||
f_if = True
|
||||
|
||||
# 24시간 거래금액 및 종목 가격 제한 조건
|
||||
# f_if = float(p) * float(v) > self._standard_price \
|
||||
# and float(p) > float(100)
|
||||
|
||||
if f_if:
|
||||
item = {
|
||||
'market': str(s),
|
||||
'acc_trade_price_24h': float(p) * float(v), # 거래 총 금액
|
||||
'acc_trade_volume_24h': float(v), # 거래량
|
||||
}
|
||||
filtered_list.append(item)
|
||||
|
||||
return filtered_list
|
||||
|
||||
def _save_market_list_to_db(self, markets):
|
||||
for m in markets:
|
||||
self._save_item_data_to_item_table(self._finance, # 금융
|
||||
self._name, # 거래소
|
||||
m['market'], # 종목
|
||||
m['acc_trade_price_24h'], # 24시간 거래 금액
|
||||
m['acc_trade_volume_24h'] # 24시간 거래량
|
||||
)
|
||||
|
||||
def _load_markets(self):
|
||||
# DB에서 종목이 없을 경우 아래 로직 실행
|
||||
try:
|
||||
markets = self._return_list_filted_markets()
|
||||
|
||||
# set exchange min marget cap(시총)
|
||||
self._standard_price = self._get_standard_trade_price(markets)
|
||||
|
||||
# 거래소 기본정보 DB 저장 (거래 기준가, api_url 등)
|
||||
self._add_exchange_info_to_db(self._finance, self._name, self._standard_price, self._api_url)
|
||||
|
||||
# 종목 리스트 DB 저장
|
||||
self._save_market_list_to_db(markets)
|
||||
|
||||
return markets
|
||||
|
||||
except Exception as e:
|
||||
logging.error(e)
|
||||
return self._load_markets()
|
||||
# raise Exception(e) # for dev
|
||||
|
||||
def save_current_min_data(self, market):
|
||||
time_type = 'minute'
|
||||
data = self.get_history_data(market, time_type)
|
||||
self._save_to_db_from_collectors_dataframe(self.finance, self._name, market, data, time_type)
|
||||
|
||||
def save_current_hour_data(self, market):
|
||||
time_type = 'hour'
|
||||
data = self.get_history_data(market, time_type)
|
||||
self._save_to_db_from_collectors_dataframe(self.finance, self._name, market, data, time_type)
|
||||
|
||||
def save_current_day_data(self, market):
|
||||
time_type = 'day'
|
||||
data = self.get_history_data(market, time_type)
|
||||
self._save_to_db_from_collectors_dataframe(self.finance, self._name, market, data, time_type)
|
||||
|
||||
def get_history_data(self, symbol="BTCUSDT", interval="hour", rm_last=True):
|
||||
int2type = {
|
||||
"day": "1d",
|
||||
"hour12": "12h",
|
||||
"hour6": "6h",
|
||||
"hour4": "4h", # custom hour
|
||||
"hour": "1h",
|
||||
"minute30": "30m",
|
||||
"minute15": "15m",
|
||||
"minute10": "15m",
|
||||
"minute5": "5m",
|
||||
"minute3": "3m",
|
||||
"minute": "1m",
|
||||
}
|
||||
# set symbol
|
||||
self._market.symbol = symbol
|
||||
|
||||
data = self._market.candles_data(int2type[interval], None, None, 1500)
|
||||
columns = ['Date', 'Open', 'High', 'Low', 'Close', 'Volume', 'Close_Time',
|
||||
'Quote_Asset_Volume', 'Number_of_Trades', 'Taker_Buy_Base_Asset_Volume',
|
||||
'Taker_Buy_Quote_Asset_Volume', 'Ignore']
|
||||
|
||||
df = DataFrame(data, columns=columns)
|
||||
self.data_columns_init(df)
|
||||
|
||||
if not df.empty:
|
||||
# Convert timestamp to date format
|
||||
dates = [datetime.datetime.fromtimestamp(int(d) / 1000).strftime("%Y-%m-%d %H:%M:%S")
|
||||
for d in df['Date'].values]
|
||||
# df.loc[:, 'Date'] = df['Date']\
|
||||
# .apply(lambda d: datetime.datetime.fromtimestamp(int(d)/1000).strftime("%Y-%m-%d %H:%M:%S"))
|
||||
|
||||
# Set needs columns
|
||||
df = df[['Open', 'High', 'Low', 'Close', 'Volume']]
|
||||
|
||||
# Remove last candle
|
||||
if rm_last:
|
||||
df = df[:-1]
|
||||
|
||||
df.loc[:, symbol] = pd.Series(dates)
|
||||
df = df.set_index(symbol)
|
||||
|
||||
# return df
|
||||
return df.astype(float)
|
||||
else:
|
||||
return None
|
||||
|
||||
'''
|
||||
For Trade Funtions
|
||||
'''
|
||||
|
||||
def get_current_price(self, symbol):
|
||||
self._market.symbol = symbol
|
||||
recent_price = self._market.ticker_price_symbol(symbol)
|
||||
|
||||
if recent_price:
|
||||
return recent_price[0]['price']
|
||||
|
||||
return None
|
||||
|
||||
def get_position_info(self):
|
||||
return self._api.position_info()
|
||||
|
||||
def get_last_price_from_orderbook(self, symbol, position, cnt=0):
|
||||
if cnt > 10:
|
||||
return None, None
|
||||
|
||||
self._market.symbol = symbol
|
||||
order_book = self._market.ticker_orderbook_symbol(symbol)
|
||||
cnt += 1
|
||||
|
||||
for b_d in order_book:
|
||||
if b_d['symbol'] in symbol:
|
||||
if position == 'long':
|
||||
return float(b_d['bidPrice'])
|
||||
elif position == 'short':
|
||||
return float(b_d['askPrice'])
|
||||
|
||||
time.sleep(1)
|
||||
return self.get_last_price_from_orderbook(symbol, position, cnt)
|
||||
|
||||
def get_trading_fee(self):
|
||||
return float(0.02)
|
||||
|
||||
# get_position_balance
|
||||
def get_position_balance(self, symbol='USDT'):
|
||||
time.sleep(1)
|
||||
|
||||
for b in self._api.position_info():
|
||||
if str(b['symbol']) == str(symbol):
|
||||
return float(abs(float(b['positionAmt'])))
|
||||
|
||||
return False
|
||||
|
||||
def get_balance(self, symbol='USDT'):
|
||||
for b in self._api.balance():
|
||||
if str(b['symbol']) == str(symbol):
|
||||
return self.cal_ceil(b['positionAmt'], 5)
|
||||
return 0
|
||||
|
||||
def get_now_amount(self, symbol):
|
||||
res = {}
|
||||
|
||||
for b in self._api.balance():
|
||||
if str(b['asset']) == 'USDT':
|
||||
res['KRW'] = '{:.8f}'.format(float(b['withdrawAvailable']))
|
||||
elif str(b['asset']) in symbol:
|
||||
res[b['asset']] = '{:.8f}'.format(float(b['withdrawAvailable']))
|
||||
|
||||
return res
|
||||
|
||||
def get_all_seeds(self):
|
||||
for b in self._api.balance():
|
||||
if b['asset'] == 'USDT':
|
||||
return '{:.8f}'.format(float(b['balance']))
|
||||
|
||||
# BinanceFuturesPy
|
||||
def order_long(self, symbol, order=None, cnt=0):
|
||||
self._api.symbol = symbol
|
||||
|
||||
# 레버리지 설정
|
||||
self._api.change_leverage(self.leverage)
|
||||
|
||||
# 이전 주문 취소
|
||||
self.order_cancel(order)
|
||||
|
||||
target_price = self.get_last_price_from_orderbook(symbol, 'long')
|
||||
seeds = float(self.get_now_amount(symbol)['KRW'])
|
||||
amount = self.get_position_balance(symbol)
|
||||
|
||||
if not amount > 0:
|
||||
amount = self.cal_ceil(seeds / target_price, 5)*int(self.leverage)
|
||||
|
||||
if not amount > 0:
|
||||
return False, None
|
||||
|
||||
if cnt > 100:
|
||||
# 시장가 매수
|
||||
order = self._api.new_order(side='BUY',
|
||||
quantity=amount,
|
||||
orderType='MARKET')
|
||||
|
||||
return True, {'symbol': symbol, 'target_price': target_price, 'amount': amount, 'seeds': seeds}
|
||||
else:
|
||||
# 지정가 매수
|
||||
order = self._api.new_order(side='BUY',
|
||||
quantity=amount,
|
||||
price=target_price,
|
||||
orderType='LIMIT',
|
||||
timeInForce='GTC')
|
||||
|
||||
if order is None or 'msg' in order:
|
||||
print('주문 오류 -', order['msg'])
|
||||
return False, None
|
||||
|
||||
time.sleep(2)
|
||||
|
||||
ordered = self._api.query_order(order['orderId'])
|
||||
|
||||
if ordered['origQty'] != ordered['executedQty']:
|
||||
cnt += 1
|
||||
|
||||
return self.order_long(symbol, order, cnt)
|
||||
|
||||
seeds = float(self.get_now_amount(symbol)['KRW'])
|
||||
# seeds = self.get_now_amount(symbol)['KRW']
|
||||
return True, {'symbol': symbol, 'target_price': target_price, 'amount': amount, 'seeds': seeds}
|
||||
|
||||
def order_short(self, symbol, order=None, cnt=0):
|
||||
# for test
|
||||
# return True, {'symbol': symbol, 'target_price': 8952000, 'amount': 1, 'seeds': 22000}
|
||||
|
||||
self._api.symbol = symbol
|
||||
|
||||
# 레버리지 설정
|
||||
self._api.change_leverage(self.leverage)
|
||||
|
||||
# 이전 주문 취소
|
||||
self.order_cancel(order)
|
||||
|
||||
target_price = self.get_last_price_from_orderbook(symbol, 'short')
|
||||
seeds = float(self.get_now_amount(symbol)['KRW'])
|
||||
amount = self.get_position_balance(symbol)
|
||||
|
||||
if not amount > 0:
|
||||
amount = self.cal_ceil(seeds / target_price, 5)*int(self.leverage)
|
||||
|
||||
if not amount > 0:
|
||||
return False, None
|
||||
|
||||
if cnt > 100:
|
||||
# 시장가 매도
|
||||
self._api.new_order(side='SELL',
|
||||
quantity=amount,
|
||||
orderType='MARKET')
|
||||
|
||||
seeds = self.get_now_amount(symbol)['KRW']
|
||||
|
||||
return True, {'symbol': symbol, 'target_price': target_price, 'amount': amount, 'seeds': seeds}
|
||||
else:
|
||||
order = self._api.new_order(side='SELL',
|
||||
quantity=amount,
|
||||
price=target_price,
|
||||
orderType='LIMIT',
|
||||
timeInForce='GTC')
|
||||
|
||||
if order is None or 'msg' in order:
|
||||
print('주문 오류 -', order['msg'])
|
||||
return False, None
|
||||
|
||||
time.sleep(2)
|
||||
|
||||
ordered = self._api.query_order(order['orderId'])
|
||||
|
||||
if ordered['origQty'] != ordered['executedQty']:
|
||||
cnt += 1
|
||||
return self.order_short(symbol, order, cnt)
|
||||
|
||||
seeds = float(self.get_now_amount(symbol)['KRW'])
|
||||
# seeds = self.get_now_amount(symbol)['KRW']
|
||||
return True, {'symbol': symbol, 'target_price': target_price, 'amount': amount, 'seeds': seeds}
|
||||
|
||||
def order_cancel(self, order=None):
|
||||
if order is not None:
|
||||
return self._api.cancel_order(order['orderId'])
|
||||
|
||||
return False
|
||||
343
collectors/crypto/c_bithumb.py
Normal file
343
collectors/crypto/c_bithumb.py
Normal file
@@ -0,0 +1,343 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import sys
|
||||
from collectors.collector import Collector
|
||||
import logging
|
||||
|
||||
# import API
|
||||
import pybithumb
|
||||
|
||||
# curl
|
||||
import json
|
||||
import datetime, time
|
||||
import requests
|
||||
from pandas import DataFrame
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
from db import DB
|
||||
|
||||
|
||||
class Bithumb(Collector):
|
||||
|
||||
def __init__(self, access=None, secret=None):
|
||||
self.remaining_req = {}
|
||||
self._heaeder = {}
|
||||
self._mathod = 'get'
|
||||
self._finance = 'crypto' # 금융 종목
|
||||
self._name = 'bithumb' # 거래소 이름
|
||||
self.leverage = 1
|
||||
self._standard_price = 0
|
||||
self._temp_api_url = 'https://api.bithumb.com/' # 임시 api 주소
|
||||
|
||||
# self._api = pybithumb
|
||||
|
||||
if secret is None:
|
||||
self._api = pybithumb.Bithumb
|
||||
else:
|
||||
self._api = pybithumb.Bithumb(access, secret)
|
||||
|
||||
self._api_url = self._temp_api_url
|
||||
|
||||
self._set_allow_items()
|
||||
|
||||
# get filtered markets
|
||||
self.markets = self._load_markets() # 테스트때 주석
|
||||
|
||||
# 빗썸 거래 기준금액 정의
|
||||
def _get_standard_trade_price(self, markets=None):
|
||||
p_list = []
|
||||
for m in markets:
|
||||
res = pybithumb.get_market_detail(m)
|
||||
# 종목 가격 제한 조건 추가 2019-11-12 => 잡코 방지
|
||||
|
||||
p_list.append(float(res[3])*float(res[4]))
|
||||
|
||||
# if res['status'] == '0000':
|
||||
# p_list.append(float(res['data']['acc_trade_value_24H']))
|
||||
|
||||
p_list.sort(reverse=True)
|
||||
|
||||
return float(p_list[0] - p_list[0] * 0.01)
|
||||
# return float(p_list[1] - p_list[1] * 0.01)
|
||||
|
||||
def _return_list_filted_markets(self, markets):
|
||||
filtered_list = []
|
||||
|
||||
for m in markets:
|
||||
f_if = False
|
||||
|
||||
# 리플 제외
|
||||
if 'XRP' in m:
|
||||
continue
|
||||
|
||||
res = pybithumb.get_market_detail(m)
|
||||
|
||||
# 허용 아이템 조건
|
||||
for i in self.allow_items:
|
||||
if i == m.replace('KRW-', ''):
|
||||
f_if = True
|
||||
|
||||
# 24시간 거래금액 및 종목 가격 제한 조건
|
||||
# f_if = float(res[3]) * float(res[4]) > self._standard_price \
|
||||
# and float(res[3]) > float(100)
|
||||
|
||||
if f_if:
|
||||
item = {
|
||||
'market': 'KRW-' + str(m),
|
||||
'acc_trade_price_24h': float(res[3]) * float(res[4]), # 거래 총 금액
|
||||
'acc_trade_volume_24h': float(res[4]), # 거래량
|
||||
}
|
||||
filtered_list.append(item)
|
||||
|
||||
return filtered_list
|
||||
|
||||
def _save_market_list_to_db(self, markets):
|
||||
for m in markets:
|
||||
self._save_item_data_to_item_table(self._finance, # 금융
|
||||
self._name, # 거래소
|
||||
m['market'], # 종목
|
||||
m['acc_trade_price_24h'], # 24시간 거래 금액
|
||||
m['acc_trade_volume_24h'] # 24시간 거래량
|
||||
)
|
||||
|
||||
def _load_markets(self):
|
||||
# DB에서 종목이 없을 경우 아래 로직 실행
|
||||
try:
|
||||
markets = self._api.get_tickers()
|
||||
|
||||
# set exchange min volume
|
||||
self._standard_price = self._get_standard_trade_price(markets)
|
||||
|
||||
filtered_markets = self._return_list_filted_markets(markets)
|
||||
|
||||
# 거래소 기본정보 DB 저장 (거래 기준가, api_url 등)
|
||||
self._add_exchange_info_to_db(self._finance, self._name, self._standard_price, self._api_url)
|
||||
|
||||
# 종목 리스트 DB 저장
|
||||
self._save_market_list_to_db(filtered_markets)
|
||||
|
||||
return filtered_markets
|
||||
|
||||
except Exception as e:
|
||||
logging.error(e)
|
||||
raise Exception(e) # for dev
|
||||
|
||||
return markets
|
||||
|
||||
def get_symbol_for_chart_view(self, market):
|
||||
s = market.split('-')
|
||||
return s[1] + '_' + s[0]
|
||||
|
||||
def save_current_min_data(self, market):
|
||||
time_type = 'min'
|
||||
data = self.get_history_data(self.get_symbol_for_chart_view(market), 'minute')[:-1]
|
||||
self._save_to_db_from_collectors_dataframe(self.finance, self._name, market, data, time_type)
|
||||
|
||||
def save_current_hour_data(self, market):
|
||||
time_type = 'hour'
|
||||
data = self.get_history_data(self.get_symbol_for_chart_view(market), 'hour')[:-1]
|
||||
self._save_to_db_from_collectors_dataframe(self.finance, self._name, market, data, time_type)
|
||||
|
||||
def save_current_day_data(self, market):
|
||||
time_type = 'day'
|
||||
data = self.get_history_data(self.get_symbol_for_chart_view(market), 'day')[:-1]
|
||||
self._save_to_db_from_collectors_dataframe(self.finance, self._name, market, data, time_type)
|
||||
|
||||
def _swap_item_name(self, symbol):
|
||||
symbol = str(symbol).replace('-', '_')
|
||||
|
||||
if 'KRW' in symbol.split('_')[0]:
|
||||
s = symbol.split('_')
|
||||
r_symbol = '_'.join([s[1], s[0]])
|
||||
|
||||
return r_symbol
|
||||
|
||||
return symbol
|
||||
|
||||
def get_history_data(self, symbol="BTC_KRW", interval="day", rm_last=True):
|
||||
symbol = self._swap_item_name(symbol)
|
||||
|
||||
int2type = {
|
||||
# "hour12": "12H",
|
||||
# "hour6": "06H",
|
||||
# "hour4": "01H", # custom hour
|
||||
# "hour": "01H",
|
||||
|
||||
"day": "24H",
|
||||
"hour12": "01H",
|
||||
"hour6": "01H",
|
||||
"hour4": "01H", # custom hour
|
||||
"hour": "01H",
|
||||
"minute30": "30M",
|
||||
"minute10": "10M",
|
||||
"minute5": "05M",
|
||||
"minute3": "03M",
|
||||
"minute": "01M",
|
||||
}
|
||||
|
||||
url = "https://m.bithumb.com/trade/chart/{}".format(symbol)
|
||||
resp = requests.get(url)
|
||||
html = resp.text
|
||||
|
||||
# parsing coin type
|
||||
string = html.split("COIN = ")[1].split(";")[0]
|
||||
coin = json.loads(string)
|
||||
tk2ct = {v['symbol_name']: k for k, v in coin['C0100'].items()}
|
||||
|
||||
# parsing xcoin name
|
||||
selector = "#barcodeForm > input[name=csrf_xcoin_name]"
|
||||
soup = BeautifulSoup(html, 'html5lib')
|
||||
xcoin_name = soup.select(selector)[0]['value']
|
||||
|
||||
url = "https://m.bithumb.com/trade_history/chart_data"
|
||||
headers = {
|
||||
"cookie": 'csrf_xcoin_name={}'.format(xcoin_name),
|
||||
"x-requested-with": "XMLHttpRequest"
|
||||
}
|
||||
|
||||
symbol = symbol.replace("KRW", "")
|
||||
symbol = symbol.replace("_", "")
|
||||
|
||||
data = {
|
||||
"coinType": tk2ct[symbol],
|
||||
"crncCd": "C0100",
|
||||
"tickType": int2type[interval],
|
||||
"csrf_xcoin_name": xcoin_name
|
||||
}
|
||||
|
||||
resp = requests.post(url, data=data, headers=headers).json()
|
||||
for x in resp['data']:
|
||||
x[0] = datetime.datetime.fromtimestamp(x[0] / 1000)
|
||||
|
||||
columns = [symbol, 'open', 'close', 'high', 'low', 'volume']
|
||||
df = DataFrame(resp['data'], columns=columns)
|
||||
df = df.set_index(symbol)
|
||||
|
||||
if not df.empty:
|
||||
# Remove last candle
|
||||
if rm_last:
|
||||
df = df[:-1]
|
||||
|
||||
self.data_columns_init(df)
|
||||
|
||||
if interval == 'hour4' or interval == 'hour6' or interval == 'hour12':
|
||||
return self.packaging_data_per_time_unit(df, interval)
|
||||
|
||||
return df.astype(float)
|
||||
else:
|
||||
return None
|
||||
|
||||
def get_current_price(self, symbol):
|
||||
symbol = self._swap_item_name(symbol)
|
||||
return self._api.get_current_price(symbol)
|
||||
|
||||
def get_last_price_from_orderbook(self, symbol, position):
|
||||
symbol = self._swap_item_name(symbol)
|
||||
res = self._api.get_orderbook(symbol)
|
||||
|
||||
if res:
|
||||
if position == 'long':
|
||||
return int(res['bids'][0]['price'])
|
||||
|
||||
elif position == 'short':
|
||||
return int(res['asks'][0]['price'])
|
||||
|
||||
return None, None
|
||||
|
||||
def get_trading_fee(self):
|
||||
return self._api.get_trading_fee()
|
||||
|
||||
def get_all_balance(self, symbol='ALL'):
|
||||
return self._api.get_balance(symbol)
|
||||
|
||||
def get_now_amount(self, symbol):
|
||||
s = symbol.replace('KRW', '').replace('-', '')
|
||||
res = {}
|
||||
|
||||
c, u, k, u = self.get_all_balance(s)
|
||||
|
||||
res[symbol] = '{:.8f}'.format(c)
|
||||
res['KRW'] = int(k)-int(u)
|
||||
|
||||
return res
|
||||
|
||||
# https://github.com/sharebook-kr/pybithumb
|
||||
# https://wikidocs.net/21887
|
||||
def order_long(self, symbol, order=None, cnt=0):
|
||||
# for test
|
||||
# return True, {'symbol': symbol, 'target_price': 8723000, 'amount': 1, 'seeds': 20000}
|
||||
|
||||
# 이전 주문 취소
|
||||
self.order_cancel(order)
|
||||
|
||||
target_price = self.get_last_price_from_orderbook(symbol, 'long')
|
||||
s = symbol.replace('KRW', '').replace('-', '')
|
||||
seeds = self.get_now_amount(symbol)['KRW']
|
||||
amount = float('{:.4f}'.format(seeds/target_price, 4))
|
||||
|
||||
if cnt > 100:
|
||||
# 시장가 매수
|
||||
self._api.buy_market_order(s, amount)
|
||||
return True, {'symbol': symbol, 'target_price': target_price, 'amount': amount, 'seeds': seeds}
|
||||
else:
|
||||
# 지정가 매수
|
||||
order = self._api.buy_limit_order(s, target_price, amount)
|
||||
|
||||
if order is None or 'message' in order:
|
||||
print('주문 오류 -', order['message'])
|
||||
return False
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
if self._api.get_outstanding_order(order) is not None:
|
||||
cnt += 1
|
||||
return self.order_long(symbol, order, cnt)
|
||||
|
||||
return True, {'symbol': symbol, 'target_price': target_price, 'amount': amount, 'seeds': seeds}
|
||||
|
||||
def order_short(self, symbol, order=None, cnt=0):
|
||||
# for test
|
||||
# return True, {'symbol': symbol, 'target_price': 8952000, 'amount': 1, 'seeds': 22000}
|
||||
|
||||
# 이전 주문 취소
|
||||
self.order_cancel(order)
|
||||
|
||||
target_price = self.get_last_price_from_orderbook(symbol, 'short')
|
||||
s = symbol.replace('KRW', '').replace('-', '')
|
||||
amount = float(format(self._api.get_balance(s)[0], ".4f"))
|
||||
|
||||
if not amount > 0:
|
||||
return True
|
||||
|
||||
if cnt > 100:
|
||||
# 시장가 매도
|
||||
self._api.sell_market_order(s, amount)
|
||||
seeds = self.get_now_amount(symbol)['KRW']
|
||||
|
||||
return True, {'symbol': symbol, 'target_price': target_price, 'amount': amount, 'seeds': seeds}
|
||||
else:
|
||||
# 지정가 매도
|
||||
order = self._api.sell_limit_order(s, target_price, amount)
|
||||
|
||||
if order is None or 'message' in order:
|
||||
print('주문 오류 -', order['message'])
|
||||
return False
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
if self._api.get_outstanding_order(order) is not None:
|
||||
cnt += 1
|
||||
return self.order_short(symbol, order, cnt)
|
||||
|
||||
seeds = self.get_now_amount(symbol)['KRW']
|
||||
return True, {'symbol': symbol, 'target_price': target_price, 'amount': amount, 'seeds': seeds}
|
||||
|
||||
def order_cancel(self, order=None):
|
||||
if order is not None:
|
||||
return self._api.cancel_order(order)
|
||||
|
||||
return False
|
||||
|
||||
# 포지션 종료 => 매도
|
||||
def close_position(self, symbol):
|
||||
return self.order_short(symbol)
|
||||
169
collectors/crypto/c_upbit.py
Normal file
169
collectors/crypto/c_upbit.py
Normal file
@@ -0,0 +1,169 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from collectors.collector import Collector
|
||||
|
||||
# Logging
|
||||
import logging
|
||||
|
||||
# Exchage API
|
||||
import pyupbit
|
||||
|
||||
import sys
|
||||
|
||||
# 거래금액 기준은 거래소 아이템 중 상위 3개에서 3번째의 종목 최소 거래금액 기준으로 정한다. # 종목 추가 기능을 추가한다.
|
||||
# 상위 3개 종목 기본 거래종목 후보로 추가(데이터 수집) => 조건 만족 시 거래
|
||||
# 1. 거래량
|
||||
# 2. 변동폭
|
||||
# 3. 수익성(시뮬레이터 결과 기반) : 머신러닝, 인디케이터 등 포함된 결과
|
||||
|
||||
|
||||
# 거래 종목에서 제외 => 거래량, 변동폭, 수익성
|
||||
# 거래 후보 종목에서 제외 => 거래량
|
||||
|
||||
# 거래량 기준 => 거래소 추가 시 상위 3개 종목 중 3번째 데이터 기준 => 사용자가 변경 가능 및 종목 추가 가능
|
||||
class Upbit(Collector):
|
||||
|
||||
def __init__(self):
|
||||
self._auth = {
|
||||
'access': 'IdO6IXpgC07 XB2KWoID37jxvFXgpLMkxZRTxHViI',
|
||||
'secret': 'QteNfsNZly1kZ1t3MGAc9bOxMDiIouozuQCZVVJI',
|
||||
}
|
||||
self.remaining_req = {}
|
||||
self._heaeder = {}
|
||||
self._mathod = 'get'
|
||||
self._finance = 'crypto' # 금융 종목
|
||||
self._name = 'upbit' # 거래소 이름
|
||||
self.leverage = 1
|
||||
self._standard_price = 0
|
||||
self._temp_api_url = 'https://api.upbit.com/v1/' # 임시 api 주소
|
||||
|
||||
# get api url from db
|
||||
self._api_url = self._get_api_url()
|
||||
self._api = pyupbit
|
||||
self._secret_api = pyupbit.Upbit(self._auth['access'], self._auth['secret'])
|
||||
|
||||
self._set_allow_items()
|
||||
|
||||
# get filtered markets
|
||||
self.markets = self._load_markets() # 테스트때 주석
|
||||
|
||||
|
||||
def set_key_data(self, access, secret):
|
||||
self._secret_api = pyupbit.Upbit(access, secret)
|
||||
|
||||
# 업비트 거래 기준금액 정의
|
||||
def _get_standard_trade_price(self, markets = None):
|
||||
if markets == None:
|
||||
markets = self._get_data_all_markets()
|
||||
|
||||
p_list = []
|
||||
for m in markets :
|
||||
c_type = self._get_currency_type(m['market'])
|
||||
|
||||
# 원화 거래
|
||||
if c_type is 1:
|
||||
p_list.append(int(m['acc_trade_price_24h']))
|
||||
|
||||
p_list.sort(reverse=True)
|
||||
|
||||
return int(p_list[0] - p_list[0] * 0.01)
|
||||
# return int(p_list[2] - p_list[2] * 0.01)
|
||||
|
||||
def _get_data_all_markets(self):
|
||||
market_all = self._get_market_all()
|
||||
|
||||
if market_all is None:
|
||||
return
|
||||
|
||||
markets = []
|
||||
for market in market_all:
|
||||
markets.append(market['market'])
|
||||
|
||||
URL = self._api_url + 'ticker?markets=%s' % str(','.join(markets))
|
||||
|
||||
return self._get(URL)
|
||||
|
||||
def _return_list_filted_markets(self, markets):
|
||||
filtered_list = []
|
||||
|
||||
for m in markets:
|
||||
c_type = self._get_currency_type(m['market'])
|
||||
f_if = False
|
||||
|
||||
# except ripple
|
||||
if 'XRP' in m['market']:
|
||||
continue
|
||||
|
||||
# 허용 아이템 조건
|
||||
for i in self.allow_items:
|
||||
if i == m['market'].replace('KRW-', ''):
|
||||
f_if = True
|
||||
|
||||
# 24시간 거래금액 및 종목 가격 제한 조건
|
||||
# f_if = int(m['acc_trade_price_24h']) > self._standard_price \
|
||||
# and float(m['low_price']) > float(100)
|
||||
|
||||
# 원화 거래 항목으로 제한
|
||||
if c_type is 1 and f_if:
|
||||
filtered_list.append(m)
|
||||
|
||||
return filtered_list
|
||||
|
||||
def _save_market_list_to_db(self, markets):
|
||||
for m in markets :
|
||||
self._save_item_data_to_item_table(self._finance, # 금융
|
||||
self._name, # 거래소
|
||||
m['market'], # 종목
|
||||
m['acc_trade_price_24h'], # 24시간 거래 금액
|
||||
m['acc_trade_volume_24h'] # 24시간 거래량
|
||||
)
|
||||
|
||||
def _load_markets(self):
|
||||
# DB에서 종목이 없을 경우 아래 로직 실행
|
||||
try:
|
||||
markets = self._get_data_all_markets()
|
||||
|
||||
# set exchange min price
|
||||
self._standard_price = self._get_standard_trade_price(markets)
|
||||
|
||||
filtered_markets = self._return_list_filted_markets(markets)
|
||||
|
||||
# 거래소 기본정보 DB 저장 (거래 기준가, api_url 등)
|
||||
self._add_exchange_info_to_db(self._finance, self._name, self._standard_price, self._api_url)
|
||||
|
||||
# 종목 리스트 DB 저장
|
||||
self._save_market_list_to_db(filtered_markets)
|
||||
|
||||
return filtered_markets
|
||||
|
||||
except Exception as e:
|
||||
logging.error(e)
|
||||
raise Exception(e) # for dev
|
||||
|
||||
return markets
|
||||
|
||||
def _get_market_all(self):
|
||||
'''
|
||||
마켓 코드 조회
|
||||
업비트에서 거래 가능한 마켓 목록
|
||||
https://docs.upbit.com/v1.0/reference#%EB%A7%88%EC%BC%93-%EC%BD%94%EB%93%9C-%EC%A1%B0%ED%9A%8C
|
||||
:return: json array
|
||||
'''
|
||||
URL = self._api_url + 'market/all'
|
||||
|
||||
return self._get(URL)
|
||||
|
||||
def save_current_min_data(self, market):
|
||||
time_type = 'min'
|
||||
data = self._api.get_ohlcv(market, interval="minute1")[:-1]
|
||||
self._save_to_db_from_collectors_dataframe(self.finance, self._name, market, data, time_type)
|
||||
|
||||
def save_current_hour_data(self, market):
|
||||
time_type = 'hour'
|
||||
data = self._api.get_ohlcv(market, interval="minute60")[:-1]
|
||||
self._save_to_db_from_collectors_dataframe(self.finance, self._name, market, data, time_type)
|
||||
|
||||
def save_current_day_data(self, market):
|
||||
time_type = 'day'
|
||||
data = self._api.get_ohlcv(market, interval="day")[:-1]
|
||||
self._save_to_db_from_collectors_dataframe(self.finance, self._name, market, data, time_type)
|
||||
5
collectors/crypto/desktop.ini
Normal file
5
collectors/crypto/desktop.ini
Normal file
@@ -0,0 +1,5 @@
|
||||
[.ShellClassInfo]
|
||||
InfoTip=<EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20>¶<EFBFBD><C2B6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>˴ϴ<CBB4>.
|
||||
IconFile=C:\Program Files\Google\Drive\googledrivesync.exe
|
||||
IconIndex=16
|
||||
|
||||
5
collectors/desktop.ini
Normal file
5
collectors/desktop.ini
Normal file
@@ -0,0 +1,5 @@
|
||||
[.ShellClassInfo]
|
||||
InfoTip=<EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20>¶<EFBFBD><C2B6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>˴ϴ<CBB4>.
|
||||
IconFile=C:\Program Files\Google\Drive\googledrivesync.exe
|
||||
IconIndex=16
|
||||
|
||||
0
collectors/libs/__init__.py
Normal file
0
collectors/libs/__init__.py
Normal file
5
collectors/libs/desktop.ini
Normal file
5
collectors/libs/desktop.ini
Normal file
@@ -0,0 +1,5 @@
|
||||
[.ShellClassInfo]
|
||||
InfoTip=<EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20>¶<EFBFBD><C2B6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>˴ϴ<CBB4>.
|
||||
IconFile=C:\Program Files\Google\Drive\googledrivesync.exe
|
||||
IconIndex=16
|
||||
|
||||
594
collectors/libs/upbitpy.py
Normal file
594
collectors/libs/upbitpy.py
Normal file
@@ -0,0 +1,594 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import json
|
||||
import time
|
||||
import requests
|
||||
import jwt
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from urllib.parse import urlencode
|
||||
|
||||
|
||||
class Upbitpy():
|
||||
"""
|
||||
Upbit API
|
||||
https://docs.upbit.com/v1.0/reference
|
||||
"""
|
||||
|
||||
def __init__(self, access_key=None, secret=None):
|
||||
'''
|
||||
Constructor
|
||||
access_key, secret이 없으면 인증가능 요청(EXCHANGE API)은 사용할 수 없음
|
||||
:param str access_key: 발급 받은 acccess key
|
||||
:param str secret: 발급 받은 secret
|
||||
'''
|
||||
self.access_key = access_key
|
||||
self.secret = secret
|
||||
self.remaining_req = dict()
|
||||
self.markets = self._load_markets()
|
||||
|
||||
###############################################################
|
||||
# EXCHANGE API
|
||||
###############################################################
|
||||
|
||||
def get_accounts(self):
|
||||
'''
|
||||
전체 계좌 조회
|
||||
내가 보유한 자산 리스트를 보여줍니다.
|
||||
https://docs.upbit.com/v1.0/reference#%EC%9E%90%EC%82%B0-%EC%A0%84%EC%B2%B4-%EC%A1%B0%ED%9A%8C
|
||||
:return: json array
|
||||
'''
|
||||
URL = 'https://api.upbit.com/v1/accounts'
|
||||
return self._get(URL, self._get_headers())
|
||||
|
||||
def get_chance(self, market):
|
||||
'''
|
||||
주문 가능 정보
|
||||
마켓별 주문 가능 정보를 확인한다.
|
||||
https://docs.upbit.com/v1.0/reference#%EC%A3%BC%EB%AC%B8-%EA%B0%80%EB%8A%A5-%EC%A0%95%EB%B3%B4
|
||||
:param str market: Market ID
|
||||
:return: json object
|
||||
'''
|
||||
URL = 'https://api.upbit.com/v1/orders/chance'
|
||||
if market not in self.markets:
|
||||
logging.error('invalid market: %s' % market)
|
||||
raise Exception('invalid market: %s' % market)
|
||||
data = {'market': market}
|
||||
return self._get(URL, self._get_headers(data), data)
|
||||
|
||||
def get_order(self, uuid):
|
||||
'''
|
||||
개별 주문 조회
|
||||
주문 UUID 를 통해 개별 주문건을 조회한다.
|
||||
https://docs.upbit.com/v1.0/reference#%EA%B0%9C%EB%B3%84-%EC%A3%BC%EB%AC%B8-%EC%A1%B0%ED%9A%8C
|
||||
:param str uuid: 주문 UUID
|
||||
:return: json object
|
||||
'''
|
||||
URL = 'https://api.upbit.com/v1/order'
|
||||
try:
|
||||
data = {'uuid': uuid}
|
||||
return self._get(URL, self._get_headers(data), data)
|
||||
except Exception as e:
|
||||
logging.error(e)
|
||||
raise Exception(e)
|
||||
|
||||
def get_orders(self, market, state, page=1, order_by='asc'):
|
||||
'''
|
||||
주문 리스트 조회
|
||||
주문 리스트를 조회한다.
|
||||
https://docs.upbit.com/v1.0/reference#%EC%A3%BC%EB%AC%B8-%EB%A6%AC%EC%8A%A4%ED%8A%B8-%EC%A1%B0%ED%9A%8C
|
||||
:param str market: Market ID
|
||||
:param str state: 주문 상태
|
||||
wait:체결 대기(default)
|
||||
done: 체결 완료
|
||||
cancel: 주문 취소
|
||||
:param int page: 페이지 수, default: 1
|
||||
:param str order_by: 정렬 방식
|
||||
asc: 오름차순(default)
|
||||
desc:내림차순
|
||||
:return: json array
|
||||
'''
|
||||
URL = 'https://api.upbit.com/v1/orders'
|
||||
if market not in self.markets:
|
||||
logging.error('invalid market: %s' % market)
|
||||
raise Exception('invalid market: %s' % market)
|
||||
|
||||
if state not in ['wait', 'done', 'cancel']:
|
||||
logging.error('invalid state: %s' % state)
|
||||
raise Exception('invalid state: %s' % state)
|
||||
|
||||
if order_by not in ['asc', 'desc']:
|
||||
logging.error('invalid order_by: %s' % order_by)
|
||||
raise Exception('invalid order_by: %s' % order_by)
|
||||
|
||||
data = {
|
||||
'market': market,
|
||||
'state': state,
|
||||
'page': page,
|
||||
'order_by': order_by
|
||||
}
|
||||
return self._get(URL, self._get_headers(data), data)
|
||||
|
||||
def order(self, market, side, volume, price):
|
||||
'''
|
||||
주문하기
|
||||
주문 요청을 한다.
|
||||
https://docs.upbit.com/v1.0/reference#%EC%A3%BC%EB%AC%B8%ED%95%98%EA%B8%B0-1
|
||||
:param str market: 마켓 ID (필수)
|
||||
:param str side: 주문 종류 (필수)
|
||||
bid : 매수
|
||||
ask : 매도
|
||||
:param str volume: 주문량 (필수)
|
||||
:param str price: 유닛당 주문 가격. (필수)
|
||||
ex) KRW-BTC 마켓에서 1BTC당 1,000 KRW로 거래할 경우, 값은 1000 이 된다.
|
||||
:return: json object
|
||||
'''
|
||||
URL = 'https://api.upbit.com/v1/orders'
|
||||
if market not in self.markets:
|
||||
logging.error('invalid market: %s' % market)
|
||||
raise Exception('invalid market: %s' % market)
|
||||
|
||||
if side not in ['bid', 'ask']:
|
||||
logging.error('invalid side: %s' % side)
|
||||
raise Exception('invalid side: %s' % side)
|
||||
|
||||
if market.startswith('KRW') and not self._is_valid_price(price):
|
||||
logging.error('invalid price: %.2f' % price)
|
||||
raise Exception('invalid price: %.2f' % price)
|
||||
|
||||
data = {
|
||||
'market': market,
|
||||
'side': side,
|
||||
'volume': str(volume),
|
||||
'price': str(price),
|
||||
'ord_type': 'limit'
|
||||
}
|
||||
return self._post(URL, self._get_headers(data), data)
|
||||
|
||||
def cancel_order(self, uuid):
|
||||
'''
|
||||
주문 취소
|
||||
주문 UUID를 통해 해당 주문을 취소한다.
|
||||
https://docs.upbit.com/v1.0/reference#%EC%A3%BC%EB%AC%B8-%EC%B7%A8%EC%86%8C
|
||||
:param str uuid: 주문 UUID
|
||||
:return: json object
|
||||
'''
|
||||
URL = 'https://api.upbit.com/v1/order'
|
||||
data = {'uuid': uuid}
|
||||
return self._delete(URL, self._get_headers(data), data)
|
||||
|
||||
def get_withraws(self, currency, state, limit):
|
||||
'''
|
||||
출금 리스트 조회
|
||||
https://docs.upbit.com/v1.0/reference#%EC%A0%84%EC%B2%B4-%EC%B6%9C%EA%B8%88-%EC%A1%B0%ED%9A%8C
|
||||
:param str currency: Currency 코드
|
||||
:param str state: 출금 상태
|
||||
submitting : 처리 중
|
||||
submitted : 처리 완료
|
||||
almost_accepted : 출금대기중
|
||||
rejected : 거부
|
||||
accepted : 승인됨
|
||||
processing : 처리 중
|
||||
done : 완료
|
||||
canceled : 취소됨
|
||||
:param int limit: 갯수 제한
|
||||
:return: json array
|
||||
'''
|
||||
LIMIT_MAX = 100
|
||||
VALID_STATE = ['submitting', 'submitted', 'almost_accepted',
|
||||
'rejected', 'accepted', 'processing', 'done', 'canceled']
|
||||
URL = 'https://api.upbit.com/v1/withdraws'
|
||||
data = {}
|
||||
if currency is not None:
|
||||
data['currency'] = currency
|
||||
if state is not None:
|
||||
if state not in VALID_STATE:
|
||||
logging.error('invalid state(%s)' % state)
|
||||
raise Exception('invalid state(%s)' % state)
|
||||
data['state'] = state
|
||||
if limit is not None:
|
||||
if limit <= 0 or limit > LIMIT_MAX:
|
||||
logging.error('invalid limit(%d)' % limit)
|
||||
raise Exception('invalid limit(%d)' % limit)
|
||||
data['limit'] = limit
|
||||
return self._get(URL, self._get_headers(data), data)
|
||||
|
||||
def get_withraw(self, uuid):
|
||||
'''
|
||||
개별 출금 조회
|
||||
출금 UUID를 통해 개별 출금 정보를 조회한다.
|
||||
https://docs.upbit.com/v1.0/reference#%EA%B0%9C%EB%B3%84-%EC%B6%9C%EA%B8%88-%EC%A1%B0%ED%9A%8C
|
||||
:param str uuid: 출금 UUID
|
||||
:return: json object
|
||||
'''
|
||||
URL = 'https://api.upbit.com/v1/withdraw'
|
||||
data = {'uuid': uuid}
|
||||
return self._get(URL, self._get_headers(data), data)
|
||||
|
||||
def get_withraws_chance(self, currency):
|
||||
'''
|
||||
출금 가능 정보
|
||||
해당 통화의 가능한 출금 정보를 확인한다.
|
||||
https://docs.upbit.com/v1.0/reference#%EC%B6%9C%EA%B8%88-%EA%B0%80%EB%8A%A5-%EC%A0%95%EB%B3%B4
|
||||
:param str currency: Currency symbol
|
||||
:return: json object
|
||||
'''
|
||||
URL = 'https://api.upbit.com/v1/withdraws/chance'
|
||||
data = {'currency': currency}
|
||||
return self._get(URL, self._get_headers(data), data)
|
||||
|
||||
def withdraws_coin(self, currency, amount, address, secondary_address=None):
|
||||
'''
|
||||
코인 출금하기
|
||||
코인 출금을 요청한다.
|
||||
https://docs.upbit.com/v1.0/reference#%EC%BD%94%EC%9D%B8-%EC%B6%9C%EA%B8%88%ED%95%98%EA%B8%B0
|
||||
:param str currency: Currency symbol
|
||||
:param str amount: 출금 코인 수량
|
||||
:param str address: 출금 지갑 주소
|
||||
:param str secondary_address: 2차 출금 주소 (필요한 코인에 한해서)
|
||||
'''
|
||||
URL = 'https://api.upbit.com/v1/withdraws/coin'
|
||||
data = {
|
||||
'currency': currency,
|
||||
'amount': amount,
|
||||
'address': address
|
||||
}
|
||||
if secondary_address is not None:
|
||||
data['secondary_address'] = secondary_address
|
||||
return self._post(URL, self._get_headers(data), data)
|
||||
|
||||
def withdraws_krw(self, amount):
|
||||
'''
|
||||
원화 출금하기
|
||||
원화 출금을 요청한다. 등록된 출금 계좌로 출금된다.
|
||||
https://docs.upbit.com/v1.0/reference#%EC%9B%90%ED%99%94-%EC%B6%9C%EA%B8%88%ED%95%98%EA%B8%B0
|
||||
:param str amount: 출금 원화 수량
|
||||
'''
|
||||
URL = 'https://api.upbit.com/v1/withdraws/krw'
|
||||
data = {'amount': amount}
|
||||
return self._post(URL, self._get_headers(data), data)
|
||||
|
||||
def get_deposits(self, currency=None, limit=None, page=None, order_by=None):
|
||||
'''
|
||||
입금 리스트 조회
|
||||
https://docs.upbit.com/v1.0/reference#%EC%9E%85%EA%B8%88-%EB%A6%AC%EC%8A%A4%ED%8A%B8-%EC%A1%B0%ED%9A%8C
|
||||
:param str currency: Currency 코드
|
||||
:param int limit: 페이지당 개수
|
||||
:param int page: 페이지 번호
|
||||
:param str order_by: 정렬 방식
|
||||
:return: json array
|
||||
'''
|
||||
URL = 'https://api.upbit.com/v1/deposits'
|
||||
data = {}
|
||||
if currency is not None:
|
||||
data['currency'] = currency
|
||||
if limit is not None:
|
||||
data['limit'] = limit
|
||||
if page is not None:
|
||||
data['page'] = page
|
||||
if order_by is not None:
|
||||
data['order_by'] = order_by
|
||||
return self._get(URL, self._get_headers(data), data)
|
||||
|
||||
def get_deposit(self, uuid):
|
||||
'''
|
||||
개별 입금 조회
|
||||
https://docs.upbit.com/v1.0/reference#%EA%B0%9C%EB%B3%84-%EC%9E%85%EA%B8%88-%EC%A1%B0%ED%9A%8C
|
||||
:param str uuid: 개별 입금의 UUID
|
||||
:return: json object
|
||||
'''
|
||||
URL = 'https://api.upbit.com/v1/deposit'
|
||||
data = {'uuid': uuid}
|
||||
return self._get(URL, self._get_headers(data), data)
|
||||
|
||||
###############################################################
|
||||
# QUOTATION API
|
||||
###############################################################
|
||||
|
||||
def get_market_all(self):
|
||||
'''
|
||||
마켓 코드 조회
|
||||
업비트에서 거래 가능한 마켓 목록
|
||||
https://docs.upbit.com/v1.0/reference#%EB%A7%88%EC%BC%93-%EC%BD%94%EB%93%9C-%EC%A1%B0%ED%9A%8C
|
||||
:return: json array
|
||||
'''
|
||||
URL = 'https://api.upbit.com/v1/market/all'
|
||||
return self._get(URL)
|
||||
|
||||
def get_minutes_candles(self, unit, market, to=None, count=None):
|
||||
'''
|
||||
분(Minute) 캔들
|
||||
https://docs.upbit.com/v1.0/reference#%EB%B6%84minute-%EC%BA%94%EB%93%A4-1
|
||||
:param int unit: 분 단위. 가능한 값 : 1, 3, 5, 15, 10, 30, 60, 240
|
||||
:param str market: 마켓 코드 (ex. KRW-BTC, BTC-BCC)
|
||||
:param str to: 마지막 캔들 시각 (exclusive). 포맷 : yyyy-MM-dd'T'HH:mm:ssXXX. 비워서 요청시 가장 최근 캔들
|
||||
:param int count: 캔들 개수(최대 200개까지 요청 가능)
|
||||
:return: json array
|
||||
'''
|
||||
URL = 'https://api.upbit.com/v1/candles/minutes/%s' % str(unit)
|
||||
if unit not in [1, 3, 5, 10, 15, 30, 60, 240]:
|
||||
logging.error('invalid unit: %s' % str(unit))
|
||||
raise Exception('invalid unit: %s' % str(unit))
|
||||
if market not in self.markets:
|
||||
logging.error('invalid market: %s' % market)
|
||||
raise Exception('invalid market: %s' % market)
|
||||
|
||||
params = {'market': market}
|
||||
if to is not None:
|
||||
params['to'] = to
|
||||
if count is not None:
|
||||
params['count'] = count
|
||||
return self._get(URL, params=params)
|
||||
|
||||
def get_days_candles(self, market, to=None, count=None):
|
||||
'''
|
||||
일(Day) 캔들
|
||||
https://docs.upbit.com/v1.0/reference#%EC%9D%BCday-%EC%BA%94%EB%93%A4-1
|
||||
:param str market: 마켓 코드 (ex. KRW-BTC, BTC-BCC)
|
||||
:param str to: 마지막 캔들 시각 (exclusive). 포맷 : yyyy-MM-dd'T'HH:mm:ssXXX. 비워서 요청시 가장 최근 캔들
|
||||
:param int count: 캔들 개수
|
||||
:return: json array
|
||||
'''
|
||||
URL = 'https://api.upbit.com/v1/candles/days'
|
||||
if market not in self.markets:
|
||||
logging.error('invalid market: %s' % market)
|
||||
raise Exception('invalid market: %s' % market)
|
||||
|
||||
params = {'market': market}
|
||||
if to is not None:
|
||||
params['to'] = to
|
||||
if count is not None:
|
||||
params['count'] = count
|
||||
return self._get(URL, params=params)
|
||||
|
||||
def get_weeks_candles(self, market, to=None, count=None):
|
||||
'''
|
||||
주(Week) 캔들
|
||||
https://docs.upbit.com/v1.0/reference#%EC%A3%BCweek-%EC%BA%94%EB%93%A4-1
|
||||
:param str market: 마켓 코드 (ex. KRW-BTC, BTC-BCC)
|
||||
:param str to: 마지막 캔들 시각 (exclusive). 포맷 : yyyy-MM-dd'T'HH:mm:ssXXX. 비워서 요청시 가장 최근 캔들
|
||||
:param int count: 캔들 개수
|
||||
:return: json array
|
||||
'''
|
||||
URL = 'https://api.upbit.com/v1/candles/weeks'
|
||||
if market not in self.markets:
|
||||
logging.error('invalid market: %s' % market)
|
||||
raise Exception('invalid market: %s' % market)
|
||||
params = {'market': market}
|
||||
if to is not None:
|
||||
params['to'] = to
|
||||
if count is not None:
|
||||
params['count'] = count
|
||||
return self._get(URL, params=params)
|
||||
|
||||
def get_months_candles(self, market, to=None, count=None):
|
||||
'''
|
||||
월(Month) 캔들
|
||||
https://docs.upbit.com/v1.0/reference#%EC%9B%94month-%EC%BA%94%EB%93%A4-1
|
||||
:param str market: 마켓 코드 (ex. KRW-BTC, BTC-BCC)
|
||||
:param str to: 마지막 캔들 시각 (exclusive). 포맷 : yyyy-MM-dd'T'HH:mm:ssXXX. 비워서 요청시 가장 최근 캔들
|
||||
:param int count: 캔들 개수
|
||||
:return: json array
|
||||
'''
|
||||
|
||||
URL = 'https://api.upbit.com/v1/candles/months'
|
||||
if market not in self.markets:
|
||||
logging.error('invalid market: %s' % market)
|
||||
raise Exception('invalid market: %s' % market)
|
||||
params = {'market': market}
|
||||
if to is not None:
|
||||
params['to'] = to
|
||||
if count is not None:
|
||||
params['count'] = count
|
||||
return self._get(URL, params=params)
|
||||
|
||||
def get_trades_ticks(self, market, to=None, count=None, cursor=None):
|
||||
'''
|
||||
당일 체결 내역
|
||||
https://docs.upbit.com/v1.0/reference#%EC%8B%9C%EC%84%B8-%EC%B2%B4%EA%B2%B0-%EC%A1%B0%ED%9A%8C
|
||||
:param str market: 마켓 코드 (ex. KRW-BTC, BTC-BCC)
|
||||
:param str to: 마지막 체결 시각. 형식 : [HHmmss 또는 HH:mm:ss]. 비워서 요청시 가장 최근 데이터
|
||||
:param int count: 체결 개수
|
||||
:param str cursor: 페이지네이션 커서 (sequentialId)
|
||||
:return: json array
|
||||
'''
|
||||
URL = 'https://api.upbit.com/v1/trades/ticks'
|
||||
if market not in self.markets:
|
||||
logging.error('invalid market: %s' % market)
|
||||
raise Exception('invalid market: %s' % market)
|
||||
params = {'market': market}
|
||||
if to is not None:
|
||||
params['to'] = to
|
||||
if count is not None:
|
||||
params['count'] = count
|
||||
if cursor is not None:
|
||||
params['cursor'] = cursor
|
||||
return self._get(URL, params=params)
|
||||
|
||||
def get_ticker(self, markets):
|
||||
'''
|
||||
현재가 정보
|
||||
요청 당시 종목의 스냅샷을 반환한다.
|
||||
https://docs.upbit.com/v1.0/reference#%EC%8B%9C%EC%84%B8-ticker-%EC%A1%B0%ED%9A%8C
|
||||
:param str[] markets: 마켓 코드 리스트 (ex. KRW-BTC, BTC-BCC)
|
||||
:return: json array
|
||||
'''
|
||||
URL = 'https://api.upbit.com/v1/ticker'
|
||||
if not isinstance(markets, list):
|
||||
logging.error('invalid parameter: markets should be list')
|
||||
raise Exception('invalid parameter: markets should be list')
|
||||
|
||||
if len(markets) == 0:
|
||||
logging.error('invalid parameter: no markets')
|
||||
raise Exception('invalid parameter: no markets')
|
||||
|
||||
for market in markets:
|
||||
if market not in self.markets:
|
||||
logging.error('invalid market: %s' % market)
|
||||
raise Exception('invalid market: %s' % market)
|
||||
|
||||
markets_data = markets[0]
|
||||
for market in markets[1:]:
|
||||
markets_data += ',%s' % market
|
||||
params = {'markets': markets_data}
|
||||
return self._get(URL, params=params)
|
||||
|
||||
def get_orderbook(self, markets):
|
||||
'''
|
||||
호가 정보 조회
|
||||
https://docs.upbit.com/v1.0/reference#%ED%98%B8%EA%B0%80-%EC%A0%95%EB%B3%B4-%EC%A1%B0%ED%9A%8C
|
||||
:param str[] markets: 마켓 코드 목록 리스트 (ex. KRW-BTC,KRW-ADA)
|
||||
:return: json array
|
||||
'''
|
||||
URL = 'https://api.upbit.com/v1/orderbook?'
|
||||
if not isinstance(markets, list):
|
||||
logging.error('invalid parameter: markets should be list')
|
||||
raise Exception('invalid parameter: markets should be list')
|
||||
|
||||
if len(markets) == 0:
|
||||
logging.error('invalid parameter: no markets')
|
||||
raise Exception('invalid parameter: no markets')
|
||||
|
||||
for market in markets:
|
||||
if market not in self.markets:
|
||||
logging.error('invalid market: %s' % market)
|
||||
raise Exception('invalid market: %s' % market)
|
||||
|
||||
markets_data = markets[0]
|
||||
for market in markets[1:]:
|
||||
markets_data += ',%s' % market
|
||||
params = {'markets': markets_data}
|
||||
return self._get(URL, params=params)
|
||||
|
||||
def get_remaining_req(self):
|
||||
'''
|
||||
요청 수 제한
|
||||
https://docs.upbit.com/docs/user-request-guide
|
||||
:return: dict
|
||||
ex) {'market': {'min': '582', 'sec': '2', 'update_time': datetime.datetime(2019, 6, 6, 7, 7, 12, 153219)}, 'candles': {'min': '592', 'sec': '6', 'update_time': datetime.datetime(2019, 6, 6, 7, 7, 12, 197177)}}
|
||||
- market 관련 요청은 2019년6월6일 7시7분12.153219초 이후 1분동안 582회, 1초동안 2회 호출 가능
|
||||
- candles 관련 요청은 2019년6월6일 7시7분12.197177초 이후 1분동안 592회, 1초동안 6회 호출 가능
|
||||
'''
|
||||
return self.remaining_req
|
||||
|
||||
###############################################################
|
||||
|
||||
def _update_remaining_req(self, resp):
|
||||
if 'Remaining-Req' not in resp.headers.keys():
|
||||
return None
|
||||
keyvals = resp.headers['Remaining-Req'].split('; ')
|
||||
group = None
|
||||
keyval = dict()
|
||||
for _keyval in keyvals:
|
||||
kv = _keyval.split('=')
|
||||
if kv[0] == 'group':
|
||||
group = kv[1]
|
||||
else:
|
||||
keyval[kv[0]] = kv[1]
|
||||
if group is None:
|
||||
return
|
||||
keyval['update_time'] = datetime.now()
|
||||
self.remaining_req[group] = keyval
|
||||
|
||||
|
||||
def _get(self, url, headers=None, data=None, params=None):
|
||||
resp = requests.get(url, headers=headers, data=data, params=params)
|
||||
if resp.status_code not in [200, 201]:
|
||||
logging.error('get(%s) failed(%d)' % (url, resp.status_code))
|
||||
if resp.text is not None:
|
||||
logging.error('resp: %s' % resp.text)
|
||||
raise Exception('request.get() failed(%s)' % resp.text)
|
||||
raise Exception(
|
||||
'request.get() failed(status_code:%d)' % resp.status_code)
|
||||
self._update_remaining_req(resp)
|
||||
return json.loads(resp.text)
|
||||
|
||||
def _post(self, url, headers, data):
|
||||
resp = requests.post(url, headers=headers, data=data)
|
||||
if resp.status_code not in [200, 201]:
|
||||
logging.error('post(%s) failed(%d)' % (url, resp.status_code))
|
||||
if resp.text is not None:
|
||||
raise Exception('request.post() failed(%s)' % resp.text)
|
||||
raise Exception(
|
||||
'request.post() failed(status_code:%d)' % resp.status_code)
|
||||
self._update_remaining_req(resp)
|
||||
return json.loads(resp.text)
|
||||
|
||||
def _delete(self, url, headers, data):
|
||||
resp = requests.delete(url, headers=headers, data=data)
|
||||
if resp.status_code not in [200, 201]:
|
||||
logging.error('delete(%s) failed(%d)' % (url, resp.status_code))
|
||||
if resp.text is not None:
|
||||
raise Exception('request.delete() failed(%s)' % resp.text)
|
||||
raise Exception(
|
||||
'request.delete() failed(status_code:%d)' % resp.status_code)
|
||||
self._update_remaining_req(resp)
|
||||
return json.loads(resp.text)
|
||||
|
||||
def _load_markets(self):
|
||||
try:
|
||||
market_all = self.get_market_all()
|
||||
if market_all is None:
|
||||
return
|
||||
markets = []
|
||||
for market in market_all:
|
||||
markets.append(market['market'])
|
||||
return markets
|
||||
except Exception as e:
|
||||
logging.error(e)
|
||||
raise Exception(e)
|
||||
|
||||
def _get_token(self, query):
|
||||
payload = {
|
||||
'access_key': self.access_key,
|
||||
'nonce': int(time.time() * 1000),
|
||||
}
|
||||
if query is not None:
|
||||
payload['query'] = urlencode(query)
|
||||
return jwt.encode(payload, self.secret, algorithm='HS256').decode('utf-8')
|
||||
|
||||
def _get_headers(self, query=None):
|
||||
headers = {'Authorization': 'Bearer %s' % self._get_token(query)}
|
||||
return headers
|
||||
|
||||
def _is_valid_price(self, price):
|
||||
'''
|
||||
원화 마켓 주문 가격 단위
|
||||
원화 마켓은 호가 별 주문 가격의 단위가 다릅니다. 아래 표를 참고하여 해당 단위로 주문하여 주세요.
|
||||
https://docs.upbit.com/v1.0/docs/%EC%9B%90%ED%99%94-%EB%A7%88%EC%BC%93-%EC%A3%BC%EB%AC%B8-%EA%B0%80%EA%B2%A9-%EB%8B%A8%EC%9C%84
|
||||
~10 : 0.01
|
||||
~100 : 0.1
|
||||
~1,000 : 1
|
||||
~10,000 : 5
|
||||
~100,000 : 10
|
||||
~500,000 : 50
|
||||
~1,000,000 : 100
|
||||
~2,000,000 : 500
|
||||
+2,000,000 : 1,000
|
||||
'''
|
||||
if price <= 10:
|
||||
if (price*100) != int(price*100):
|
||||
return False
|
||||
elif price <= 100:
|
||||
if (price*10) != int(price*10):
|
||||
return False
|
||||
elif price <= 1000:
|
||||
if price != int(price):
|
||||
return False
|
||||
elif price <= 10000:
|
||||
if (price % 5) != 0:
|
||||
return False
|
||||
elif price <= 100000:
|
||||
if (price % 10) != 0:
|
||||
return False
|
||||
elif price <= 500000:
|
||||
if (price % 50) != 0:
|
||||
return False
|
||||
elif price <= 1000000:
|
||||
if (price % 100) != 0:
|
||||
return False
|
||||
elif price <= 2000000:
|
||||
if (price % 500) != 0:
|
||||
return False
|
||||
elif (price % 1000) != 0:
|
||||
return False
|
||||
return True
|
||||
5
config/desktop.ini
Normal file
5
config/desktop.ini
Normal file
@@ -0,0 +1,5 @@
|
||||
[.ShellClassInfo]
|
||||
InfoTip=<EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20>¶<EFBFBD><C2B6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>˴ϴ<CBB4>.
|
||||
IconFile=C:\Program Files\Google\Drive\googledrivesync.exe
|
||||
IconIndex=16
|
||||
|
||||
5
config/ssh/desktop.ini
Normal file
5
config/ssh/desktop.ini
Normal file
@@ -0,0 +1,5 @@
|
||||
[.ShellClassInfo]
|
||||
InfoTip=<EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20>¶<EFBFBD><C2B6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>˴ϴ<CBB4>.
|
||||
IconFile=C:\Program Files\Google\Drive\googledrivesync.exe
|
||||
IconIndex=16
|
||||
|
||||
27
config/ssh/id_rsa
Normal file
27
config/ssh/id_rsa
Normal file
@@ -0,0 +1,27 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEowIBAAKCAQEA4TkLJGm766JSex5u17VfM9k2918Wo4BQinUE/8NoGQU48J10
|
||||
90Kh/vzFJxMlfGGFwRG6jW0sld/SqH3UR0zTNxudgH0qmEA2EBSwavSrRcjj6KJG
|
||||
n3hsDOjBxQM+C147gywLUycczX0oTlJ8RzvL2WVCye2ifjiL932PrY6jwdWT5ZCg
|
||||
PaYV0QQmLuJfSwS3Y3owkurRSU1LAJ1dU8UByl4dOUv/1wnUF1DdzoYROV7bAHcU
|
||||
H72Gzr3KBKAhOd/EI+4tPzAj+sy+LtFoGAmHfHBzzfUosOIEQvYayDqvhiqUqjBx
|
||||
DmhN57T7h/EDh2hIOr88hzdY+zj6jkfYn2p31QIDAQABAoIBACPUaVtfns+7wRdp
|
||||
HgUfC4g6FC6CaB2WujlsqGDv+02PsSFGS2dSqGbiW5L9zsbc1OSEJ4N8TM2DEAZS
|
||||
DkVRiYCI2iOoxe/2tyMTx4Uca2rsrKVDu6x/AeGD3d+zxlkUoq8ZgKu32zMtqA3d
|
||||
vXuvnZx2KYvqTCF8aXfz97mKqa6aOUT4JGOhRW1G5As6rJu7/l9plaMEv8Se7pVW
|
||||
H87XAoZyAMr2B1S6R4nJwGnhTLFu6pu7Lzu1o1k1CMDom3FHSGKavon2pm5i3mtb
|
||||
ghrHJhYGlGnsY4JUvVH3hIfuWCRAYoPGoA5rk1C7qEL2S2SYGpVBBH8mOBVKIx+a
|
||||
HN8C67kCgYEA9Naih594KwUEO1DPFjncAEg1cr3XfUKW71Wa4s3/FX3jHFAtXXlB
|
||||
31JlYft2YbD1VSgOuc09KmT/Mrrfy3125PhnzqyXfrloETJEMY3hhnlGkscmyD54
|
||||
RGuTZMBLx0EGzxb7fjbQXoZE1a88cMH4ezLtzkGnkb0WPsEOPp/P/8MCgYEA6318
|
||||
kwAofUXHm3T3cfbF9NmQJUm/I0cG0QePlj6xm6CwDxRD8vusQLBDSKm4KUDrqHNZ
|
||||
NDFIczo/Zveb/IJxxMCv75I5K33DoX1dGbTM4yzKc41wynQTn10WCo80S41ENp4h
|
||||
QqqFehpnbmm1J2Y6ifb73b4nPwd5GrvcCgkJiIcCgYEAh2oatHYfyXNZ1tCn6LwR
|
||||
kNpfLVoQUAg/YJnxM1YJqkJZFTTHEnbZVwHEbv/chsWPuwyPsHXySXtYph8zXeHD
|
||||
m3pEN8u/cmhrRW+OxfWZ8X0r6kxZh9D9RaJWABhXERpHAMzORg1dC5qpgaINBLRT
|
||||
kRgm7LflTTbOkeDG1x4etW8CgYA/sZtGL17EM2F/3K0o9/Qm+8mPLFk0c3uWghMG
|
||||
MkLbsySrj3GpgQTgIkywlKcpEVQsJbbU9ReBgxmvAf4A2E9pRizQAZ7Q8p09Sqkp
|
||||
0MsyDuVbR8BgIfFEw2q4xG8CmF0A4NhiLbkYg5fCN3k2BOKEenc5TLtUwvKwlms3
|
||||
k6YudQKBgDshgcABXiUedBtG9ngpl7tNqfu/z6kq+7zkHRzRDVgxMOboQX+mgyjZ
|
||||
YCdnA/j0jbGmE3s19G9P014R8gT6UwWUkeJAGoek2+OHsyZPvzgKjbrDKhcTqUBm
|
||||
xrvjAYbeUA4EilH/wfK0SEUgSAOsa+CvJRm7MtEFlhDLTfhzYYr0
|
||||
-----END RSA PRIVATE KEY-----
|
||||
1
config/ssh/id_rsa.pub
Normal file
1
config/ssh/id_rsa.pub
Normal file
@@ -0,0 +1 @@
|
||||
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDhOQskabvrolJ7Hm7XtV8z2Tb3XxajgFCKdQT/w2gZBTjwnXT3QqH+/MUnEyV8YYXBEbqNbSyV39KofdRHTNM3G52AfSqYQDYQFLBq9KtFyOPookafeGwM6MHFAz4LXjuDLAtTJxzNfShOUnxHO8vZZULJ7aJ+OIv3fY+tjqPB1ZPlkKA9phXRBCYu4l9LBLdjejCS6tFJTUsAnV1TxQHKXh05S//XCdQXUN3OhhE5XtsAdxQfvYbOvcoEoCE538Qj7i0/MCP6zL4u0WgYCYd8cHPN9Siw4gRC9hrIOq+GKpSqMHEOaE3ntPuH8QOHaEg6vzyHN1j7OPqOR9ifanfV javamon@javamon-ui-MacBook-Pro.local
|
||||
277
cron.py
Normal file
277
cron.py
Normal file
@@ -0,0 +1,277 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import importlib
|
||||
import os
|
||||
import random
|
||||
import sys
|
||||
from tzlocal import get_localzone
|
||||
|
||||
import logging
|
||||
|
||||
from apscheduler.schedulers.background import BackgroundScheduler
|
||||
from datetime import datetime
|
||||
from db import DB
|
||||
|
||||
from trader import Trader
|
||||
from bot import Bot
|
||||
|
||||
'''
|
||||
- (매시간 단위)Base 테이블의 새 데이터 추가시 테이블 생성(종목-거래소)
|
||||
- (매일 단위) 거래소-종목 거래량 미달 시 해당 종목 포지션 청산 후 테이블 삭제
|
||||
- (매분 단위)종목-거래소별 Tinker 정보 저장(1분, 1시간, 1일) => 1분만 할지 다시 고민
|
||||
- (매시간 단위) 시간봉 데이터 저장 및 분석 클래스에 전달
|
||||
- (매일 단위) 일봉 데이터 저장 및 분석 클래스에 전달
|
||||
|
||||
* 잡 단위는 함수
|
||||
|
||||
- 크론 테이블에 데이터 등록
|
||||
=> 업비트-비트코인 아이템 테이블에 분, 시간, 일을 구분하는 컬럼을 추가하여, 봉별 시간을 유지.
|
||||
=> 업비트-비트코인 아이템 테이블에 2개의 used 컬럼을 추가하여, 해당 데이터가 머신러닝, 보조지표에 활용 되었는 지 식별
|
||||
=> 거래소-종목 아이템이 추가 될때 크론 테이블에 추가(3개가 한 세트로 분마다, 시간마다, 일 마다)
|
||||
|
||||
- 크론 테이블 데이터 로드 후 스케쥴러에 등록
|
||||
|
||||
* 작업 목록 생성 -> 등록 -> 작업 목록 실행
|
||||
- 다음 작업 스케쥴러 라이브러리 연동 후 데이터 저장 프로세스 구현하기
|
||||
'''
|
||||
|
||||
# base path
|
||||
BASE_DIR = os.path.dirname(os.path.abspath(__file__)) + '/collectors'
|
||||
|
||||
# time array for curl data
|
||||
columns = ['hour', 'day'] # 분봉, 시간봉, 일봉
|
||||
added_job = ['bot', 'trader']
|
||||
|
||||
|
||||
class Cron:
|
||||
mode = 'Test'
|
||||
# mode = 'Service'
|
||||
|
||||
exchange_instance_list = {}
|
||||
|
||||
def __init__(self):
|
||||
# init variables
|
||||
self.db = DB()
|
||||
|
||||
# set logger
|
||||
# self._set_logger_for_cron()
|
||||
logging.basicConfig()
|
||||
|
||||
# set base data
|
||||
self.f_list = self.db.select_base_table()
|
||||
|
||||
# def _set_logger_for_cron(self):
|
||||
# logging.basicConfig()
|
||||
|
||||
def start(self):
|
||||
print('For %s Working. ' % self.mode)
|
||||
|
||||
if self.mode != 'Test':
|
||||
# 배치 프로세스를 위한 인스턴스 저장
|
||||
self.save_exchange_instances_2_cron()
|
||||
|
||||
# 배치 목록 생성
|
||||
self.save_batch_job_list()
|
||||
|
||||
# 배치 목록 실행
|
||||
self.excute_batch_job_list()
|
||||
|
||||
else:
|
||||
self.save_exchange_instances_2_cron()
|
||||
|
||||
self.save_batch_job_list() # DB에 배치 목록 없을 시 주석 해제
|
||||
|
||||
# 거래 종목 생성 및 데이터 저장 로직
|
||||
self.test_excute_batch_job_list()
|
||||
|
||||
# 거래소 거래 기준 업데이트 => 외부에서 호출 => 크론에 잡 추가 예정
|
||||
def update_exchange_standard_options(self):
|
||||
for e in self.f_list:
|
||||
obj = self.exchange_instance_list[e['exchange_name']]
|
||||
obj.update_exchange_standard_options()
|
||||
|
||||
def save_exchange_instances_2_cron(self):
|
||||
# for finance, exchange in self.f_list.items() : # dynamically create an exchange instance
|
||||
for f_item in self.f_list: # dynamically create an exchange instance
|
||||
finance = f_item['finance_name']
|
||||
exchange = f_item['exchange_name']
|
||||
|
||||
# add dynamically path to os.path
|
||||
if not BASE_DIR + '/' + finance in sys.path:
|
||||
sys.path.append(BASE_DIR + '/' + finance)
|
||||
|
||||
module = importlib.import_module('c_' + exchange)
|
||||
|
||||
self.exchange_instance_list.update({
|
||||
exchange: getattr(module, str(exchange).capitalize())()
|
||||
})
|
||||
|
||||
self.exchange_instance_list[exchange].finance = finance # set finance name
|
||||
self.exchange_instance_list[exchange].db = self.db # set db object
|
||||
|
||||
# 잡 리스트를 디비에 등록하기(시간, 일 세트)
|
||||
def save_batch_job_list(self, job_type='price'):
|
||||
# 거래소별 잡 등록
|
||||
for f_item in self.f_list:
|
||||
finance = f_item['finance_name']
|
||||
exchange = f_item['exchange_name']
|
||||
trade_type = f_item['trade_type']
|
||||
|
||||
# 거래소 정보 로드
|
||||
e_info = self.db.select_row_data_from_exchange(exchange)
|
||||
|
||||
# 거래소 종목 로드
|
||||
items = self.db.select_items_data_from_tb_items_by_exchange_id(e_info['id'])
|
||||
|
||||
# 아이템별 잡 디비= 저장
|
||||
for i in items:
|
||||
# 상태값 준비-시작의 아이템만 잡 리스트 추가
|
||||
if i['status'] == 'ready' or i['status'] == 'started':
|
||||
self.db.insert_job_data_to_cron(finance, exchange, job_type, i['name'], columns, 1, trade_type)
|
||||
|
||||
# 크론 잡 등록
|
||||
def _add_job_to_sched(self, job_info):
|
||||
# custum job
|
||||
if job_info == 'bot':
|
||||
self.sched.add_job(self.excute_outer_obj, 'interval', minutes=60, id='bot', args=[job_info])
|
||||
elif job_info == 'trader':
|
||||
self.sched.add_job(self.excute_outer_obj, 'interval', minutes=10, id='trader', args=[job_info])
|
||||
# curl job
|
||||
else:
|
||||
self.add(self.save_candel_data, 'cron', job_info['time_unit'], job_info['job'], job_info)
|
||||
|
||||
# self.add(self.save_candel_data, 'cron', job_info['time_unit'], job_info['job'], job_info)
|
||||
# self.save_candel_data(job_info) # for test
|
||||
# self.sched.add_job(job, 'cron', second='5', id="test_10") # 매 지정 일-시간 실행(매일)
|
||||
# self.sched.add_job(job, 'cron', minute="01", second='5', id="test_10") # 매 지정 시간 실행(매 시간)
|
||||
# self.sched.add_job(job, 'cron', hour="9", minute="01", second='5', id="test_10") # 매 지정 초 실행(매 분)
|
||||
# self.sched.add_job(self.save_candel_data, 'interval', seconds=3, id=job_info['job'], args=[job_info], replace_existing=True) # 초 마다 실행
|
||||
|
||||
def excute_outer_obj(self, obj_name):
|
||||
o = importlib.import_module(obj_name)
|
||||
c = getattr(o, obj_name.capitalize())()
|
||||
c.run()
|
||||
|
||||
del o, c
|
||||
|
||||
def test_excute_batch_job_list(self):
|
||||
print('function called : test_excute_batch_job_list()')
|
||||
job_list = self.db.select_job_list()
|
||||
|
||||
for job in job_list:
|
||||
# print('this job :', job)
|
||||
self.save_candel_data(job)
|
||||
|
||||
def excute_batch_job_list(self): # 디비에서 잡 리스트 가져온 뒤 크론 등록
|
||||
# 스케쥴러 실행
|
||||
# self.sched = BackgroundScheduler({'apscheduler.timezone': 'UTC'})
|
||||
# self.sched = BackgroundScheduler({'apscheduler.timezone': get_localzone()})
|
||||
self.sched = BackgroundScheduler({'apscheduler.timezone': 'Asia/Seoul'})
|
||||
|
||||
# reset job list
|
||||
self.sched.remove_all_jobs()
|
||||
|
||||
# added jobs
|
||||
if self.mode == 'Service':
|
||||
for job in added_job:
|
||||
# continue # for test
|
||||
self._add_job_to_sched(job)
|
||||
|
||||
# curl data job
|
||||
job_list = self.db.select_job_list()
|
||||
|
||||
for job in job_list:
|
||||
self._add_job_to_sched(job)
|
||||
|
||||
self.sched.start()
|
||||
|
||||
# 스케쥴러 전체 잡 리스트 출력
|
||||
self.sched.print_jobs()
|
||||
|
||||
def _convert_slash_string_to_array(self, str):
|
||||
return str.split('/')
|
||||
|
||||
# 봉데이터 저장
|
||||
def save_candel_data(self, job_info):
|
||||
job = self._convert_slash_string_to_array(job_info['job'])
|
||||
obj = self.exchange_instance_list[job[1]]
|
||||
|
||||
print('now job :', job, datetime.now())
|
||||
|
||||
# 종목별 day 테이블 생성 => 종목 당 min-day 테이블 두개 운영
|
||||
if 'min' in job_info['time_unit']:
|
||||
obj.save_current_min_data(job[2])
|
||||
elif 'hour' in job_info['time_unit']:
|
||||
obj.save_current_hour_data(job[2])
|
||||
elif 'day' in job_info['time_unit']:
|
||||
obj.save_current_day_data(job[2])
|
||||
|
||||
# 정적 시간 스케쥴 등록
|
||||
def add(self, func, job_type, time_type, id, data, day_of_week=0, day=1):
|
||||
job = self._convert_slash_string_to_array(data['job'])
|
||||
obj = self.exchange_instance_list[job[1]]
|
||||
|
||||
hour = 9
|
||||
# hour = obj.get_based_time_from_data(data)
|
||||
sec = str(random.randrange(2, 57))
|
||||
min = str(1)
|
||||
# min = str(random.randrange(2, 10))
|
||||
|
||||
''' https://apscheduler.readthedocs.io/en/3.0/modules/triggers/cron.html#module-apscheduler.triggers.cron
|
||||
year (int|str) – 4-digit year
|
||||
month (int|str) – month (1-12)
|
||||
day (int|str) – day of the (1-31)
|
||||
week (int|str) – ISO week (1-53)
|
||||
day_of_week (int|str) – number or name of weekday (0-6 or mon,tue,wed,thu,fri,sat,sun)
|
||||
hour (int|str) – hour (0-23)
|
||||
minute (int|str) – minute (0-59)
|
||||
second (int|str) – second (0-59)
|
||||
start_date (datetime|str) – earliest possible date/time to trigger on (inclusive)
|
||||
end_date (datetime|str) – latest possible date/time to trigger on (inclusive)
|
||||
timezone (datetime.tzinfo|str) – time zone to use for the date/time calculations (defaults to scheduler timezone)
|
||||
kst : 한국 기준 시간 9시 마감
|
||||
utc : 세계 공용 기준 시간 0시 마감
|
||||
'''
|
||||
|
||||
if 'min' in time_type: # 매분
|
||||
self.sched.add_job(func, job_type, second=sec, id=id, args=[data], replace_existing=True)
|
||||
|
||||
elif 'hour' in time_type: # 매시
|
||||
# self.sched.add_job(func, job_type, minute=10, second=sec, id=id, args=[data], replace_existing=True)
|
||||
self.sched.add_job(func, job_type, minute=min, second=sec, id=id, args=[data], replace_existing=True)
|
||||
|
||||
elif 'day' in time_type: # 매일
|
||||
self.sched.add_job(func, job_type, hour=hour, minute=min, second=sec, id=id, args=[data],
|
||||
replace_existing=True)
|
||||
|
||||
elif 'day_of_week' in time_type: # 요일 마다
|
||||
self.sched.add_job(func, job_type, day_of_week=day_of_week, hour=hour, minute=min, second=sec, id=id,
|
||||
args=[data], replace_existing=True)
|
||||
|
||||
elif 'month' in time_type: # 매달-일 마다
|
||||
self.sched.add_job(func, job_type, day=day, hour=hour, minute=min, second=sec, id=id, args=[data],
|
||||
replace_existing=True)
|
||||
|
||||
def remove_job(self, id):
|
||||
self.sched.remove_job(id)
|
||||
|
||||
def get_job(self, id):
|
||||
return self.sched.get_job(id)
|
||||
|
||||
def update_job(self, job_id, jobstore=None, trigger=None, **trigger_args):
|
||||
'''
|
||||
reschedule_job(job_id, jobstore=None, trigger=None, **trigger_args)
|
||||
Constructs a new trigger for a job and updates its next run time.
|
||||
Extra keyword arguments are passed directly to the trigger’s constructor.
|
||||
|
||||
Parameters
|
||||
job_id (str|unicode) – the identifier of the job
|
||||
jobstore (str|unicode) – alias of the job store that contains the job
|
||||
trigger – alias of the trigger type or a trigger instance
|
||||
Return Job
|
||||
the relevant job instance
|
||||
'''
|
||||
return self.sched.reschedule_job(job_id, jobstore, trigger, trigger_args)
|
||||
|
||||
def remove_all_jobs(self):
|
||||
self.sched.remove_all_jobs()
|
||||
10
cron.sh
Normal file
10
cron.sh
Normal file
@@ -0,0 +1,10 @@
|
||||
#!/bin/sh
|
||||
|
||||
if [ -f ~/.bashrc ]; then
|
||||
. ~/.bashrc
|
||||
fi
|
||||
|
||||
((
|
||||
cd /home/oh_my_bot/project/oh_my_bot && \
|
||||
python3 app.py
|
||||
) > /cron.log 2>&1)
|
||||
853
db.py
Normal file
853
db.py
Normal file
@@ -0,0 +1,853 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import sys # for test
|
||||
import pymysql
|
||||
import json
|
||||
import time
|
||||
|
||||
from sqlalchemy import create_engine
|
||||
import warnings
|
||||
|
||||
# 기본 테이블 추후 필요시 머신러닝 관련 테이블 추가
|
||||
default_tables = ['base', 'finance', 'exchange', 'score', 'bot', 'cron', 'cron_log',
|
||||
'item', 'simulation', 'trade', 'allow_item', 'user', 'sumul_item_for_time']
|
||||
# 기본테이블 쿼리 작성부 _make_query_default_table_for_type()
|
||||
|
||||
|
||||
'''
|
||||
완료 작업
|
||||
- 디비 클래스 재구성 => 단발성 요청(커넥트 - 디스커넥트) 방식으로 변경 + 인터페이스를 통해 싱글톤 가능하도록 부수적인 기능 구현
|
||||
=> 타임아웃 이슈, 버퍼 이슈, 배치 프로세스(크론) 실행 시 커넥션 부조화 발생
|
||||
- 변경 된 DB 클래스 디버깅
|
||||
- 잡 스케쥴링 테스트
|
||||
|
||||
진행중 작업
|
||||
- 기존 데이터 수집(거래소별) -> 거래소 클래스에 함수 추가 및 테이블 생성 시 자동 삽입 기능 로직 추가
|
||||
- 시간 설정값 보완 => 디비에 삽입해서 사용하도록 수정하기
|
||||
- 종목 추가하는 부분을 배치로(일단위 배치로 구현)하여 자동으로 종목 추가, 삭제 구현
|
||||
|
||||
예정 작업
|
||||
- exchange 테이블 필드 추가 : api_url, f_id(finance), 1일 거래량, 1일 거래금액(종목 추가/삭제 시) => 업빗 기준 5000백만 이상 거래금액
|
||||
- 거래소별 종목테이블 생성 : 상폐 또는 거래랑이 낮을 시 거래종목에서 제외(분기 또는 반기 후 거래량이 급감한 종목 테이블 삭제)
|
||||
|
||||
'''
|
||||
warnings.simplefilter("ignore")
|
||||
|
||||
unique_field = {'upbit': 'candle_date_time_utc'}
|
||||
db_info = {'host':'192.168.0.2', 'port': 3306, 'user': 'javamon', 'passwd': '@Wtkdwns117424', 'db': 'oh_my_bot_admin', 'charset': 'utf8',
|
||||
'max_allowed_packet': '67108864'}
|
||||
default_data_tables = ['allow_item', 'user']
|
||||
|
||||
class DB:
|
||||
engine = None
|
||||
db = None
|
||||
|
||||
# def __init__(self):
|
||||
# self.engine = self.get_db_engine()
|
||||
# self.db = self.get_db_instance()
|
||||
|
||||
# def get_db_engine(self):
|
||||
# return create_engine("mysql://%s:%s@%s/%s" % (
|
||||
# db_info['user'],
|
||||
# db_info['passwd'],
|
||||
# db_info['host'],
|
||||
# db_info['db'],
|
||||
# ))
|
||||
|
||||
# 싱글톤 요청
|
||||
def get_db_instance(self):
|
||||
db = pymysql.connect(host=db_info['host'], port=db_info['port'], user=db_info['user'], passwd=db_info['passwd'], db=db_info['db'],
|
||||
charset=db_info['charset'], max_allowed_packet=db_info['max_allowed_packet'], init_command="SET SESSION time_zone='+09:00'")
|
||||
|
||||
return db.cursor()
|
||||
|
||||
# 단발성 요청
|
||||
def execute(self, sql = None, data = None):
|
||||
res = None
|
||||
|
||||
try:
|
||||
if not sql is None :
|
||||
connection = pymysql.connect(host=db_info['host'], port=db_info['port'], user=db_info['user'], passwd=db_info['passwd'], db=db_info['db'],
|
||||
charset=db_info['charset'], max_allowed_packet=db_info['max_allowed_packet'],
|
||||
init_command="SET SESSION time_zone='+09:00'", connect_timeout=60)
|
||||
|
||||
try:
|
||||
with connection.cursor(pymysql.cursors.DictCursor) as cursor:
|
||||
cursor.execute(sql, data)
|
||||
res = cursor.fetchall()
|
||||
|
||||
connection.commit()
|
||||
|
||||
finally:
|
||||
connection.close()
|
||||
|
||||
return res
|
||||
except Exception as e:
|
||||
print(e)
|
||||
time.sleep(3)
|
||||
return self.execute(sql, data)
|
||||
|
||||
|
||||
def insert_finance_to_base (self, finance, exchange): # 금융 종목 추가
|
||||
return self.execute(
|
||||
'''
|
||||
INSERT INTO `oh_my_bot_admin`.`base` (`finance_name`, `exchange_name`)
|
||||
VALUES ('%s', '%s');
|
||||
''' % (finance, exchange)
|
||||
)
|
||||
|
||||
def select_base_table(self):
|
||||
return self.execute("SELECT `finance_name`, `exchange_name`, `trade_type` FROM base;")
|
||||
|
||||
def create_default_tables(self):
|
||||
# need_tables = self._is_default_tables()
|
||||
for tb in self._is_default_tables() : # 기본 테이블들 생성
|
||||
tb_create_sql = self._make_query_default_table_for_type(tb)
|
||||
|
||||
self.execute(tb_create_sql)
|
||||
|
||||
|
||||
def insert_default_tables(self):
|
||||
self._insert_default_allow_item()
|
||||
|
||||
|
||||
def _insert_default_allow_item(self):
|
||||
for tb in default_data_tables:
|
||||
i_null = self._is_void_table_data(tb)
|
||||
if i_null:
|
||||
self._insert_default_data_to_table(tb)
|
||||
|
||||
def _insert_default_data_to_table(self, table_name):
|
||||
sql = ''
|
||||
|
||||
if table_name == 'allow_item':
|
||||
sql = '''
|
||||
REPLACE INTO `oh_my_bot_admin`.`allow_item` (`item_code`)
|
||||
VALUES ('%s'), ('%s');
|
||||
''' % ('BTC', 'ETH')
|
||||
|
||||
elif table_name == 'user':
|
||||
key_data = {
|
||||
'bithumb':
|
||||
{
|
||||
'access': 'f557cba1cbc531b0c51e699266f9868f',
|
||||
'secret': '8078dcf58f344afbd3f2cf0df60db1b3',
|
||||
},
|
||||
'upbit':
|
||||
{
|
||||
'access': 'IdO6IXpgC07 XB2KWoID37jxvFXgpLMkxZRTxHViI',
|
||||
'secret': 'QteNfsNZly1kZ1t3MGAc9bOxMDiIouozuQCZVVJI',
|
||||
},
|
||||
'binance':
|
||||
{
|
||||
'access': 'DPob3MlV51nb55D6OovjKTNRiyoiMWihX2phcunUNxI73Z7gSyo2ALX87dxcmuXB',
|
||||
'secret': 'qgd5YHf4TiWvD8KjOL1qoPz9QX354mYMIoQ6FBt5VCv1tswQq3X6eGaFFrHZ7a7a',
|
||||
},
|
||||
}
|
||||
|
||||
sql = '''
|
||||
REPLACE INTO `oh_my_bot_admin`.`user` (`email`, `password`, `grade`, `auth_info`)
|
||||
VALUES ('javamon1174@gmail.com', '1111', '0', '%s');
|
||||
''' % json.dumps(key_data)
|
||||
|
||||
self.execute(sql)
|
||||
|
||||
def _is_void_table_data(self, table_name):
|
||||
sql = "SELECT `id` FROM oh_my_bot_admin.%s LIMIT 1;" % table_name
|
||||
|
||||
res = self.execute(sql)
|
||||
|
||||
if res is ():
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def select_allow_items_all(self):
|
||||
sql = "SELECT `item_code` FROM oh_my_bot_admin.allow_item;"
|
||||
return self.execute(sql)
|
||||
|
||||
def _get_one_row_from_rows(self, tuple):
|
||||
if tuple is ():
|
||||
return ()
|
||||
|
||||
return tuple[0]
|
||||
|
||||
def _is_table(self, table_name):
|
||||
is_table = self._get_one_row_from_rows(self.execute("check table `%s`;" % table_name))
|
||||
|
||||
return 'OK' in is_table['Msg_text']
|
||||
|
||||
def is_table(self, table_name):
|
||||
return self._is_table(table_name)
|
||||
|
||||
# 기본 테이블 존재 유뮤 체크
|
||||
def _is_default_tables(self):
|
||||
need_tables = []
|
||||
|
||||
for tb in default_tables :
|
||||
if not self._is_table(tb) :
|
||||
need_tables.append(tb)
|
||||
|
||||
return need_tables
|
||||
|
||||
# 기본 테이블 생성을 위한 쿼리 리턴
|
||||
def _make_query_default_table_for_type(self, table_name):
|
||||
tb_sql = ''
|
||||
|
||||
if table_name == 'base':
|
||||
tb_sql = '''
|
||||
CREATE TABLE `%s` (
|
||||
`id` INT AUTO_INCREMENT,
|
||||
`finance_name` VARCHAR(100) CHARACTER SET utf8mb4 NOT NULL,
|
||||
`exchange_name` VARCHAR(100) CHARACTER SET utf8mb4 NOT NULL,
|
||||
`trade_type` VARCHAR(100) NULL DEFAULT 'single' COMMENT '단반향/양방향 트레이딩 여부',
|
||||
`added_date` DATETIME NULL DEFAULT NOW(),
|
||||
PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
''' % table_name
|
||||
|
||||
elif table_name == 'finance':
|
||||
tb_sql = '''
|
||||
CREATE TABLE `%s` (
|
||||
`id` int(100) AUTO_INCREMENT,
|
||||
`name` varchar(100) CHARACTER SET utf8mb4 NOT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
''' % table_name
|
||||
|
||||
elif table_name == 'exchange' :
|
||||
tb_sql = '''
|
||||
CREATE TABLE `%s` (
|
||||
`id` int(100) AUTO_INCREMENT,
|
||||
`f_id` int(100) NOT NULL,
|
||||
`name` varchar(100) CHARACTER SET utf8mb4 NOT NULL,
|
||||
`standard_volume` varchar(255) CHARACTER SET utf8mb4 NOT NULL COMMENT '기준 거래 금액',
|
||||
`api_url` varchar(255) CHARACTER SET utf8mb4 NOT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
''' % table_name
|
||||
|
||||
elif table_name == 'score':
|
||||
tb_sql = '''
|
||||
CREATE TABLE `oh_my_bot_admin`.`%s` (
|
||||
`id` INT AUTO_INCREMENT,
|
||||
`i_id` INT NOT NULL COMMENT 'item id',
|
||||
`strategy_score` INT NOT NULL,
|
||||
`model_score` INT NOT NULL,
|
||||
`total` INT NOT NULL,
|
||||
PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
''' % table_name
|
||||
|
||||
elif table_name == 'bot':
|
||||
tb_sql = '''
|
||||
CREATE TABLE `oh_my_bot_admin`.`%s` (
|
||||
`id` INT AUTO_INCREMENT,
|
||||
`f_i` INT NOT NULL COMMENT '금융 아이디',
|
||||
`e_i` INT NOT NULL COMMENT '거래소 아이디',
|
||||
`user_id` INT NULL DEFAULT '1' COMMENT '유저 아이디',
|
||||
`status` VARCHAR(10) NULL DEFAULT 'Y' COMMENT '봇 가동 상태(Y-RUN, N-REMOVED, S-STOP)',
|
||||
`mode` VARCHAR(50) NULL DEFAULT 'test' COMMENT '봇 모드 - test or service',
|
||||
`position` VARCHAR(50) NULL DEFAULT 'None' COMMENT '봇의 현재 포지션',
|
||||
`position_price` FLOAT NULL DEFAULT 0 COMMENT '봇의 현재 포지션 가격',
|
||||
`amount` FLOAT NULL DEFAULT 0 COMMENT '시작 총 보유액',
|
||||
`last_amount` FLOAT NULL DEFAULT 0 COMMENT '현재 총 보유액',
|
||||
`target` VARCHAR(255) NOT NULL COMMENT '타겟 아이템',
|
||||
`time_unit` VARCHAR(100) NOT NULL COMMENT '시간 종류',
|
||||
`duration_days` INT NOT NULL COMMENT '시뮬레이팅 일수',
|
||||
`p_profit` FLOAT NOT NULL COMMENT '예상 수익(Profit Projection)',
|
||||
`profit` FLOAT NULL DEFAULT 0 COMMENT '실제 수익',
|
||||
`last_profit` FLOAT NULL DEFAULT 0 COMMENT '마지막 거래 수익률',
|
||||
`p_winrate` FLOAT NOT NULL COMMENT '예상 승률',
|
||||
`winrate` FLOAT NULL DEFAULT 0 COMMENT '실제 승률',
|
||||
`stop_loss` FLOAT NOT NULL COMMENT '스탑 로스',
|
||||
`stop_profit` FLOAT NOT NULL COMMENT '스탑 프로핏',
|
||||
`trade_cnt` INT NULL DEFAULT 0 COMMENT '거래 수',
|
||||
`win_cnt` INT NULL DEFAULT 0 COMMENT '승 수',
|
||||
`lose_cnt` INT NULL DEFAULT 0 COMMENT '패배 수',
|
||||
`tb_name` VARCHAR(255) NOT NULL COMMENT '타겟 테이블 이름',
|
||||
`strategy` VARCHAR(255) NOT NULL COMMENT '매매 전략',
|
||||
`trade_type` VARCHAR(100) NOT NULL COMMENT '단반향/양방향 트레이딩 여부',
|
||||
`leverage` INT NULL DEFAULT 1 COMMENT '마진(leverage)',
|
||||
`last_signal` DATETIME NULL DEFAULT NOW() COMMENT '추가 일자',
|
||||
`update_date` DATETIME NULL DEFAULT NOW() COMMENT '갱신 일자',
|
||||
`added_date` DATETIME NULL DEFAULT NOW() COMMENT '추가 일자',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE INDEX `target_UNIQUE` (`target`)) COMMENT = 'trading bots' ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
''' % table_name
|
||||
|
||||
|
||||
elif table_name == 'cron':
|
||||
tb_sql = '''
|
||||
CREATE TABLE `oh_my_bot_admin`.`%s` (
|
||||
`id` INT NOT NULL AUTO_INCREMENT,
|
||||
`type` VARCHAR(100) CHARACTER SET utf8mb4 NOT NULL COMMENT '작업 종류',
|
||||
`target` VARCHAR(100) CHARACTER SET utf8mb4 NOT NULL COMMENT '금융/거래소/항목 이름',
|
||||
`time_unit` VARCHAR(20) CHARACTER SET utf8mb4 NOT NULL DEFAULT 'min' COMMENT '시간 단위',
|
||||
`repeat_cycle` VARCHAR(100) CHARACTER SET utf8mb4 NOT NULL COMMENT '반복 주기',
|
||||
`job` VARCHAR(255) CHARACTER SET utf8mb4 NOT NULL COMMENT 'Job => Function name',
|
||||
`period_cycle` VARCHAR(255) CHARACTER SET utf8mb4 NULL DEFAULT '2_M' COMMENT '시뮬레이팅 기간 주기',
|
||||
`init_simul` VARCHAR(255) CHARACTER SET utf8mb4 NULL DEFAULT 'N' COMMENT '초기 시뮬레이팅 여부',
|
||||
`trade_type` VARCHAR(100) NULL DEFAULT 'single' COMMENT '단반향/양방향 트레이딩 여부',
|
||||
`etc` VARCHAR(255) CHARACTER SET utf8mb4 NULL DEFAULT 'NULL' COMMENT '잡 호출 함수명',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE INDEX `job_UNIQUE` (`job` ASC)) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
''' % table_name
|
||||
|
||||
|
||||
elif table_name == 'cron_log':
|
||||
tb_sql = '''
|
||||
CREATE TABLE `oh_my_bot_admin`.`%s` (
|
||||
`id` INT NOT NULL AUTO_INCREMENT,
|
||||
`c_id` INT NOT NULL,
|
||||
`job` VARCHAR(255) CHARACTER SET utf8mb4 NOT NULL,
|
||||
`reason` VARCHAR(255) CHARACTER SET utf8mb4 NULL DEFAULT 'Unknown' COMMENT '실패 사유',
|
||||
`error_msg` VARCHAR(255) CHARACTER SET utf8mb4 NOT NULL,
|
||||
`treat` VARCHAR(255) CHARACTER SET utf8mb4 NULL DEFAULT 'Nothing' COMMENT '후처리',
|
||||
PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
''' % table_name
|
||||
|
||||
|
||||
elif table_name == 'item':
|
||||
tb_sql = '''
|
||||
CREATE TABLE `oh_my_bot_admin`.`%s` (
|
||||
`id` INT NOT NULL AUTO_INCREMENT,
|
||||
`name` VARCHAR(100) CHARACTER SET utf8mb4 NOT NULL COMMENT 'item name',
|
||||
`f_id` INT NOT NULL COMMENT 'Finance id',
|
||||
`e_id` INT NOT NULL COMMENT 'Exchange id',
|
||||
`status` VARCHAR(100) CHARACTER SET utf8mb4 NULL DEFAULT 'ready',
|
||||
`acc_trade_price_24h` BIGINT DEFAULT 0 NULL COMMENT '24시간 누적 거래 금액',
|
||||
`acc_trade_volume_24h` BIGINT DEFAULT 0 NULL COMMENT '24시간 누적 거래량',
|
||||
`i_res` INT NULL DEFAULT 0 COMMENT 'Indicate Result',
|
||||
`m_res` INT NULL DEFAULT 0 COMMENT 'Mushine Running Result',
|
||||
`t_res` INT NULL DEFAULT 0 COMMENT 'Total Result(수익성 판단)',
|
||||
PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
''' % table_name
|
||||
|
||||
|
||||
elif table_name == 'simulation':
|
||||
tb_sql = '''
|
||||
CREATE TABLE `oh_my_bot_admin`.`%s` (
|
||||
`id` INT AUTO_INCREMENT,
|
||||
`f_i` INT NOT NULL COMMENT 'finance_id',
|
||||
`e_i` INT NOT NULL COMMENT 'exchage_id',
|
||||
`item_id` INT NOT NULL,
|
||||
`time_type` VARCHAR(100) NOT NULL COMMENT 'item data time type',
|
||||
`t_table_name` VARCHAR(100) NOT NULL COMMENT 'Target item table name',
|
||||
`profit_rate` FLOAT NOT NULL COMMENT 'Return(Profit) rate',
|
||||
`win_rate` FLOAT NOT NULL COMMENT 'Wins rate',
|
||||
`trades` INT NOT NULL,
|
||||
`stop_loss` FLOAT NOT NULL,
|
||||
`stop_profit` FLOAT NOT NULL,
|
||||
`used_patterns` VARCHAR(255) NOT NULL COMMENT 'used indicator and patterns',
|
||||
`file_name` VARCHAR(255) NULL COMMENT 'simulation result file name',
|
||||
`start_date` DATETIME NOT NULL,
|
||||
`end_date` DATETIME NOT NULL,
|
||||
`duration_days` INT NOT NULL COMMENT 'simulation period days',
|
||||
`trade_type` VARCHAR(100) NULL DEFAULT 'single' COMMENT '단반향/양방향 트레이딩 여부',
|
||||
`added_date` DATETIME NULL DEFAULT NOW(),
|
||||
PRIMARY KEY (`id`))
|
||||
COMMENT = 'simulation result table';
|
||||
''' % table_name
|
||||
|
||||
elif table_name == 'trade': # 해당 테이블 사용 안함
|
||||
tb_sql = '''
|
||||
|
||||
CREATE TABLE `oh_my_bot_admin`.`%s` (
|
||||
`id` INT AUTO_INCREMENT,
|
||||
`f_i` INT NOT NULL COMMENT '금융 아이디',
|
||||
`e_i` INT NOT NULL COMMENT '거래소 아이디',
|
||||
`target` VARCHAR(255) NOT NULL COMMENT '타겟 종목',
|
||||
`position` VARCHAR(30) NOT NULL COMMENT '매매 포지션',
|
||||
`price` FLOAT NOT NULL COMMENT '진입가',
|
||||
`target_price_1` FLOAT NULL COMMENT '1차 목표가',
|
||||
`target_price_2` FLOAT NULL COMMENT '2차 목표가',
|
||||
`strategy` VARCHAR(255) NOT NULL COMMENT '매매전략(JSON)',
|
||||
`added_date` DATETIME NULL DEFAULT NOW() COMMENT '등록 일자',
|
||||
PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
''' % table_name
|
||||
|
||||
elif table_name == 'allow_item':
|
||||
tb_sql = '''
|
||||
CREATE TABLE `oh_my_bot_admin`.`%s` (
|
||||
`id` INT AUTO_INCREMENT,
|
||||
`item_code` VARCHAR(100) NOT NULL COMMENT '아이템 코드',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE INDEX `item_code_UNIQUE` (`item_code` ASC))
|
||||
COMMENT = '허용 아이템 목록 테이블';
|
||||
''' % table_name
|
||||
|
||||
elif table_name == 'user':
|
||||
tb_sql = '''
|
||||
CREATE TABLE `oh_my_bot_admin`.`%s` (
|
||||
`id` INT AUTO_INCREMENT,
|
||||
`email` VARCHAR(100) NOT NULL COMMENT '이메일 주소',
|
||||
`password` VARCHAR(255) NOT NULL COMMENT '비밀번호',
|
||||
`grade` INT NULL DEFAULT 1 COMMENT '등급',
|
||||
`auth_info` LONGTEXT NULL COMMENT '유저 거래소별 키 정보',
|
||||
`added_date` DATETIME NULL DEFAULT NOW(),
|
||||
PRIMARY KEY (`id`))
|
||||
COMMENT = '유저 테이블';
|
||||
''' % table_name
|
||||
|
||||
elif table_name == 'sumul_item_for_time':
|
||||
tb_sql = '''
|
||||
CREATE TABLE `oh_my_bot_admin`.`%s` (
|
||||
`id` INT NOT NULL AUTO_INCREMENT,
|
||||
`job` VARCHAR(255) NOT NULL,
|
||||
`time_unit` VARCHAR(255) NOT NULL,
|
||||
`added_date` DATETIME NULL DEFAULT NOW(),
|
||||
PRIMARY KEY (`id`))
|
||||
COMMENT = '시뮬레이팅 완료된 아이템 임시저장 테이블';
|
||||
''' % table_name
|
||||
|
||||
return tb_sql
|
||||
|
||||
|
||||
def is_void(self, data):
|
||||
return data is ()
|
||||
|
||||
def select_items_data_from_tb_items_by_exchange_id(self, e_id):
|
||||
res = self.execute("SELECT * FROM `oh_my_bot_admin`.`item` WHERE `e_id` = '%s';"% e_id)
|
||||
|
||||
return res
|
||||
|
||||
def select_api_url_from_tb_exchange(self, exchange_name):
|
||||
return self._get_one_row_from_rows(self.execute("SELECT `api_url` FROM `oh_my_bot_admin`.`exchange` WHERE `name` = '%s';"% exchange_name))
|
||||
|
||||
def insert_job_data_to_cron(self, finance, exchange, type, market, columns, times, trade_type='single'):
|
||||
target = finance+'/'+exchange+'/'+ market+'/'
|
||||
for col in columns:
|
||||
res = self.execute("SELECT `id` FROM `oh_my_bot_admin`.`cron` WHERE `job` = '%s'" % (target+col))
|
||||
|
||||
if self.is_void(res):
|
||||
sql = '''INSERT INTO `oh_my_bot_admin`.`cron` (`type`, `target`, `time_unit`, `repeat_cycle`, `job`, `trade_type`)
|
||||
VALUES ('%s', '%s', '%s', '%s', '%s', '%s');
|
||||
''' % (type, market, col, str(times), target+col, str(trade_type))
|
||||
|
||||
self.execute(sql)
|
||||
|
||||
def select_job_list(self):
|
||||
return self.execute("SELECT * FROM `oh_my_bot_admin`.`cron`")
|
||||
|
||||
def select_exchange_list_for_bot(self):
|
||||
sql = '''
|
||||
SELECT concat(finance.name, '/', exchange.name) as exchange_info
|
||||
FROM `oh_my_bot_admin`.`exchange`
|
||||
LEFT JOIN `oh_my_bot_admin`.`finance`
|
||||
ON `exchange`.`f_id` = `finance`.`id`;
|
||||
'''
|
||||
|
||||
return self.execute(sql)
|
||||
|
||||
def insert_item_main_data(self, finance, exchange, item, total_trade_price_24h, total_trade_volume_24h):
|
||||
info = self._get_one_row_from_rows(self.execute("SELECT id, f_id FROM oh_my_bot_admin.exchange WHERE `name` = '%s';" % exchange))
|
||||
|
||||
res = self.execute("SELECT id FROM oh_my_bot_admin.item "
|
||||
"WHERE `name` = '%s' AND `f_id` = '%s' AND `e_id`= '%s';" % (item, info['f_id'], info['id']))
|
||||
|
||||
if self.is_void(res):
|
||||
sql = '''
|
||||
INSERT INTO `oh_my_bot_admin`.`item` (`name`, `f_id`, `e_id`, `acc_trade_price_24h`, `acc_trade_volume_24h`)
|
||||
VALUES ('%s', '%s', '%s', '%s', '%s');
|
||||
''' % (item, info['f_id'], info['id'], total_trade_price_24h, total_trade_volume_24h)
|
||||
|
||||
self.execute(sql)
|
||||
|
||||
# 기존 데이터 삽입
|
||||
def insertItemData(self, finance, exchange, item, t_data, time_type):
|
||||
# 종목별 분-시간-일 데이터에 맞춰 테이블 생성
|
||||
item = item.replace('-', '_')
|
||||
item += '_' +str(time_type).upper()
|
||||
|
||||
if not self._is_item_table(finance, exchange, item) :
|
||||
# 테이블 없을 경우 생성 => 금융 종류(finance), 거래소(exchange), 종목(item)
|
||||
self._create_table_for_item(finance, exchange, item, t_data)
|
||||
|
||||
# 테이블이 있을 경우 바로 데이터 삽입
|
||||
self._insert_item_data(finance, exchange, item, t_data, time_type)
|
||||
|
||||
def get_last_idx_from_datetime(self, tb_name, t_data):
|
||||
sql = '''
|
||||
SELECT `id`, `date` FROM `oh_my_bot_admin`.`%s` order by date desc LIMIT 1;
|
||||
''' % tb_name
|
||||
|
||||
res = self._get_one_row_from_rows(self.execute(sql))
|
||||
|
||||
# init auto_increment => 안넣을려고 했던 코드
|
||||
self.execute("ALTER TABLE `oh_my_bot_admin`.`%s` AUTO_INCREMENT = %s" % (tb_name, int(res['id'])+1))
|
||||
|
||||
try:
|
||||
return t_data[t_data['date'] > str(res['date'])].index[0]
|
||||
# return t_data.loc[t_data['date'] == res['date']].index[0] + 1
|
||||
except:
|
||||
return int(t_data.index[-1])+1
|
||||
|
||||
# Dataframe 타입 데이터 삽입
|
||||
def insertItemDataframe(self, finance, exchange, item, t_data, time_type):
|
||||
item = item.replace('-', '_')
|
||||
item += '_' +str(time_type).upper()
|
||||
|
||||
tb_name = self._get_table_name(finance, exchange, item)
|
||||
|
||||
t_data['time_type'] = time_type
|
||||
t_data['e_id'] = self._get_exchange_id_by_name(exchange)
|
||||
t_data['f_id'] = self._get_finance_id_by_name(finance)
|
||||
|
||||
self.data_columns_init(t_data) # 컬럼명 소문자 변환
|
||||
t_data = t_data.iloc[:, ::-1] # 열 반전
|
||||
|
||||
if not self._is_item_table(finance, exchange, item):
|
||||
self._get_dataframe_create_table_sql(tb_name)
|
||||
else:
|
||||
last_idx = self.get_last_idx_from_datetime(tb_name, t_data)
|
||||
t_data = t_data[last_idx:]
|
||||
|
||||
if t_data.empty:
|
||||
return
|
||||
|
||||
# Inert Item Data
|
||||
self._insert_dataframe_to_table(tb_name, t_data)
|
||||
|
||||
del t_data
|
||||
# t_data.to_sql(name=tb_name,
|
||||
# con=self.engine,
|
||||
# if_exists='append',
|
||||
# method='multi',
|
||||
# schema=db_info['db'],
|
||||
# index=False
|
||||
# )
|
||||
|
||||
|
||||
def _insert_dataframe_to_table(self, tb_name, t_data):
|
||||
sql = "REPLACE INTO `oh_my_bot_admin`.`%s` (" % tb_name
|
||||
columns = t_data.columns # index-date / open, high, low, close, volume
|
||||
|
||||
for col in columns:
|
||||
sql += "`%s`," % str(col)
|
||||
|
||||
sql = sql[:-1]
|
||||
sql += ")"
|
||||
sql += " VALUES "
|
||||
|
||||
for i, row in t_data.iterrows():
|
||||
sql += " ("
|
||||
for col in columns:
|
||||
sql += "'%s'," % str(row[col])
|
||||
|
||||
sql = sql[:-1]
|
||||
sql += "),"
|
||||
|
||||
sql = sql[:-1]
|
||||
sql += ";"
|
||||
|
||||
self.execute(sql)
|
||||
|
||||
def _get_dataframe_create_table_sql(self, tb_name):
|
||||
sql = '''
|
||||
CREATE TABLE IF NOT EXISTS `oh_my_bot_admin`.`%s` (
|
||||
`id` INT AUTO_INCREMENT,
|
||||
`f_id` INT NOT NULL,
|
||||
`e_id` INT NOT NULL,
|
||||
`time_type` VARCHAR(45) NOT NULL,
|
||||
`open` FLOAT NOT NULL,
|
||||
`high` FLOAT NOT NULL,
|
||||
`low` FLOAT NOT NULL,
|
||||
`close` FLOAT NOT NULL,
|
||||
`volume` FLOAT NOT NULL,
|
||||
`date` DATETIME NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE INDEX `date_UNIQUE` (`date` ASC)) ENGINE=InnoDB AUTO_INCREMENT=1
|
||||
DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
''' % tb_name
|
||||
|
||||
self.execute(sql)
|
||||
|
||||
def update_standard_options_from_exchange(self, exchange_name, standard_volume):
|
||||
sql = "UPDATE `oh_my_bot_admin`.`exchange` SET `standard_volume`='%s' WHERE `name`='%s';"
|
||||
|
||||
return self.execute(sql % (standard_volume, exchange_name))
|
||||
|
||||
def is_item_table_in_db(self, finance, exchange, item, time_type):
|
||||
item = item.replace('-', '_')
|
||||
item += '_' +str(time_type).upper()
|
||||
tb_name = finance+'_'+exchange+'_'+item
|
||||
|
||||
return self.is_table_in_db(tb_name)
|
||||
|
||||
def is_table_in_db(self, tb_name):
|
||||
return self._is_table(tb_name)
|
||||
|
||||
def _is_item_table(self, finance, exchange, item):
|
||||
tb_name = self._get_table_name(finance, exchange, item)
|
||||
res = self.execute("check table %s;" % tb_name)
|
||||
|
||||
return self._is_table(tb_name)
|
||||
|
||||
def _get_table_name(self, finance, exchange, item):
|
||||
return "_".join([finance, exchange, item])
|
||||
|
||||
def insert_finance_and_exchange_data(self, finance, exchange, total_trade_price_24h, api_url):
|
||||
self._insert_finanace_data(finance)
|
||||
self._insert_exchange_data(exchange, total_trade_price_24h, api_url)
|
||||
|
||||
def _create_table_for_item(self, finance, exchange, item, data):
|
||||
# self.insert_finance_and_exchange_data(finance, exchange)
|
||||
|
||||
# 종목 테이블 생성
|
||||
table_name = self._get_table_name(finance, exchange, item)
|
||||
|
||||
if self._is_table(table_name) is False :
|
||||
sql = '''
|
||||
CREATE TABLE `oh_my_bot_admin`.`%s` (
|
||||
`id` INT AUTO_INCREMENT,
|
||||
`f_id` INT NOT NULL,
|
||||
`e_id` INT NOT NULL,
|
||||
`i_used` VARCHAR(5) NULL DEFAULT 'N' COMMENT '보조지표 데이터로 활용 유무',
|
||||
`m_used` VARCHAR(5) NULL DEFAULT 'N' COMMENT '머신러닝 데이터로 활용 유무',
|
||||
`time_type` VARCHAR(10) NOT NULL COMMENT '시간 단위',
|
||||
'''.strip() % table_name
|
||||
# .decode('utf-8')\
|
||||
|
||||
sql += self._get_sql_for_field(data)
|
||||
sql += " UNIQUE INDEX `%s_UNIQUE` (`%s` ASC), PRIMARY KEY (`id`))" % \
|
||||
(unique_field[exchange], unique_field[exchange])
|
||||
sql += " ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;"
|
||||
self.execute(sql)
|
||||
|
||||
def _insert_finanace_data(self, finance):
|
||||
# 금융 데이터 있는 지 확인
|
||||
res = self.is_void(self.execute("SELECT `id` FROM `finance` WHERE `name` = '%s'" % finance))
|
||||
|
||||
if res :
|
||||
# 금융 데이터 행 추가
|
||||
self.execute("INSERT INTO `oh_my_bot_admin`.`finance` (`name`) VALUES ('%s');" % finance)
|
||||
|
||||
def _get_finance_id_by_exchange_name(self, exchange_name):
|
||||
res = self._get_one_row_from_rows(self.execute("SELECT `id` FROM oh_my_bot_admin.finance WHERE `name` = "
|
||||
"(SELECT `finance_name` FROM oh_my_bot_admin.base WHERE `exchange_name` = '%s');"% exchange_name))
|
||||
return res['id']
|
||||
|
||||
def _insert_exchange_data(self, exchange, total_trade_price_24h, api_url):
|
||||
# 거래소 데이터 있는 지 확인
|
||||
res = self.is_void(self.execute("SELECT `id` FROM `exchange` WHERE `name` = '%s'" % exchange))
|
||||
|
||||
if res :
|
||||
f_id = self._get_finance_id_by_exchange_name(exchange)
|
||||
|
||||
# 거래소 데이터 행 추가
|
||||
self.execute("INSERT INTO `oh_my_bot_admin`.`exchange` (`name`, `f_id`, `standard_volume`, `api_url`) "
|
||||
"VALUES ('%s', '%s', '%s', '%s');" % (exchange, f_id, total_trade_price_24h, api_url))
|
||||
|
||||
def _get_sql_for_field(self, item):
|
||||
temp = list(item)
|
||||
r_sql = ''
|
||||
|
||||
for index, element in enumerate(temp.pop()):
|
||||
r_sql += "`%s` VARCHAR(255) NOT NULL, " % element
|
||||
|
||||
return r_sql
|
||||
|
||||
def _insert_item_data(self, finance, exchange, item, data, time_type):
|
||||
if len(data) > 0 :
|
||||
data.reverse() # 데이터 역순 재정렬
|
||||
|
||||
query = self._get_query_for_insert_item_data(finance, exchange, item, data, time_type)
|
||||
try : # 중복 메세지 스킵(duplication)
|
||||
self.execute(query)
|
||||
except pymysql.IntegrityError :
|
||||
pass
|
||||
|
||||
def select_row_data_from_exchange(self, name):
|
||||
res = self._get_one_row_from_rows(self.execute("SELECT * FROM oh_my_bot_admin.exchange WHERE `name` = '%s';" % name))
|
||||
return res
|
||||
|
||||
def _get_finance_id_by_name(self, finance):
|
||||
res = self._get_one_row_from_rows(
|
||||
self.execute("SELECT id FROM oh_my_bot_admin.finance WHERE `name` = '%s';" % finance))
|
||||
return res['id']
|
||||
|
||||
def _get_exchange_id_by_name(self, finance):
|
||||
res = self._get_one_row_from_rows(
|
||||
self.execute("SELECT id FROM oh_my_bot_admin.exchange WHERE `name` = '%s';" % finance))
|
||||
return res['id']
|
||||
|
||||
def get_id_by_exchange_name(self, finance):
|
||||
return self._get_exchange_id_by_name(finance)
|
||||
|
||||
def _get_query_for_insert_item_data(self, finance, exchange, item, data, time_type):
|
||||
table_name = self._get_table_name(finance, exchange, item)
|
||||
|
||||
# sql = 'INSERT IGNORE INTO `oh_my_bot_admin`.`%s` ' % table_name
|
||||
sql = 'REPLACE INTO `oh_my_bot_admin`.`%s` ' % table_name
|
||||
|
||||
res = self._get_one_row_from_rows(self.execute("SELECT id FROM oh_my_bot_admin.finance WHERE `name` = '%s';"% finance))
|
||||
f_id = res['id']
|
||||
|
||||
res = self._get_one_row_from_rows(self.execute("SELECT id FROM oh_my_bot_admin.exchange WHERE `name` = '%s';" % exchange))
|
||||
e_id = res['id']
|
||||
|
||||
fields = '(`f_id`, `e_id`, `time_type`, '
|
||||
|
||||
for index, key in enumerate(data[0]):
|
||||
fields += '`%s`, ' % key
|
||||
|
||||
fields += ') '
|
||||
fields = fields.replace(', )', ')')
|
||||
sql += fields # add field row
|
||||
|
||||
sql += "VALUES "
|
||||
|
||||
for d in data:
|
||||
values = "('%s', '%s', '%s', " % (f_id, e_id, time_type)
|
||||
|
||||
for key, value in d.items() :
|
||||
values += "'%s'," % value
|
||||
|
||||
sql += values[:-1] + '),' # add value row
|
||||
|
||||
sql = sql[:-1] + ';'
|
||||
|
||||
return sql
|
||||
|
||||
def data_columns_init(self, data):
|
||||
t_col = []
|
||||
for c in data.columns:
|
||||
t_col.append(c.lower())
|
||||
|
||||
data.reset_index(level=0, inplace=True)
|
||||
t_col.insert(0, 'date')
|
||||
|
||||
data.columns = t_col
|
||||
|
||||
'''
|
||||
for Trade functions
|
||||
'''
|
||||
def delete_simul_results_by_deadline(self, deadline):
|
||||
sql = "DELETE FROM oh_my_bot_admin.simulation WHERE `added_date` < '%s'" % deadline
|
||||
|
||||
self.execute(sql)
|
||||
|
||||
|
||||
def get_completed_simul_list(self):
|
||||
sql = "SELECT `job` FROM oh_my_bot_admin.cron WHERE `init_simul` = 'Y';"
|
||||
|
||||
return self.execute(sql)
|
||||
|
||||
def get_bot_list_by_exchange_id(self, e_i):
|
||||
sql = "SELECT `tb_name` FROM oh_my_bot_admin.bot WHERE `e_i` = '%s' LIMIT 1;" % e_i
|
||||
|
||||
return self.execute(sql)
|
||||
|
||||
|
||||
def get_simul_res_top_row(self, t_name):
|
||||
sql = "SELECT * FROM oh_my_bot_admin.simulation WHERE `t_table_name` like '%s%%' " \
|
||||
"ORDER BY `win_rate` DESC, `profit_rate` DESC, `added_date` DESC LIMIT 1;" % t_name
|
||||
return self._get_one_row_from_rows(self.execute(sql))
|
||||
|
||||
|
||||
def get_bot_data_by_target(self, target):
|
||||
sql = "SELECT * FROM oh_my_bot_admin.bot WHERE `target` = '%s';" % target
|
||||
|
||||
return self._get_one_row_from_rows(self.execute(sql))
|
||||
|
||||
def upsert_bot_data(self, bot_data):
|
||||
sql = '''
|
||||
REPLACE INTO `oh_my_bot_admin`.`bot` (`f_i`, `e_i`, `target`, `time_unit`, `duration_days`,
|
||||
`p_profit`, `p_winrate`, `tb_name`, `stop_loss`, `stop_profit`, `strategy`, `status`, `trade_cnt`,
|
||||
`profit`, `trade_type`, `mode`)
|
||||
VALUES ('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', 'Y', '0', '0', '%s', 'test');
|
||||
''' % (bot_data['f_i'],
|
||||
bot_data['e_i'], bot_data['target'], bot_data['time_type'], bot_data['duration_days'],
|
||||
bot_data['profit_rate'],bot_data['win_rate'], bot_data['t_table_name'],
|
||||
bot_data['stop_loss'], bot_data['stop_profit'], bot_data['used_patterns'],
|
||||
bot_data['trade_type'])
|
||||
|
||||
self.execute(sql)
|
||||
|
||||
def select_running_bots(self):
|
||||
sql = "SELECT * FROM `oh_my_bot_admin`.`bot` WHERE `status` = 'Y'";
|
||||
|
||||
return self.execute(sql)
|
||||
|
||||
def update_bot_data_for_stop_by_id(self, id):
|
||||
self.execute("UPDATE `oh_my_bot_admin`.`bot` SET `status` = 'N' WHERE (`id` = '%s');" % id)
|
||||
|
||||
def select_all_users_id_for_trade(self):
|
||||
return self.execute("SELECT id FROM oh_my_bot_admin.user;")
|
||||
|
||||
def get_bots_by_user_id(self, id):
|
||||
sql = "SELECT * FROM oh_my_bot_admin.bot WHERE `user_id` = '%s' AND `status` = 'Y';" % id
|
||||
|
||||
return self.execute(sql)
|
||||
|
||||
def update_tb_cron_simul_status(self, job_string):
|
||||
sql = "UPDATE `oh_my_bot_admin`.`cron` SET `init_simul` = 'N' WHERE `job` LIKE '%s%%';" % job_string
|
||||
return self.execute(sql)
|
||||
|
||||
def select_tb_cron_simul_status(self, job_string):
|
||||
sql = "SELECT `init_simul` FROM `oh_my_bot_admin`.`cron` WHERE `job` LIKE '%s%%';" % job_string
|
||||
return self.execute(sql)
|
||||
|
||||
def bot_update_by_trade_data(self, bot_info):
|
||||
sql = '''
|
||||
UPDATE `oh_my_bot_admin`.`bot` SET `mode` = '%s', `position` = '%s', `position_price` = '%s',
|
||||
`amount` = '%s', `last_amount` = '%s', `profit` = '%s', `winrate` = '%s', `trade_cnt` = '%s',
|
||||
`win_cnt` = '%s',`lose_cnt` = '%s', `last_signal` = '%s', `last_profit` = '%s' WHERE (`target` = '%s');
|
||||
''' % (bot_info['mode'], bot_info['position'], bot_info['position_price'], bot_info['amount'],
|
||||
bot_info['last_amount'], bot_info['profit'], bot_info['winrate'], bot_info['trade_cnt'],
|
||||
bot_info['win_cnt'], bot_info['lose_cnt'], bot_info['last_signal'], bot_info['last_profit'], bot_info['target'])
|
||||
|
||||
return self.execute(sql)
|
||||
|
||||
def select_user_keys_by_id(self, user_id):
|
||||
# res = self.execute("SELECT `auth_info` FROM oh_my_bot_admin.user WHERE `id` = '2';")
|
||||
res = self.execute("SELECT `auth_info` FROM oh_my_bot_admin.user WHERE `id` = %s;" % user_id)
|
||||
|
||||
if res is not ():
|
||||
return json.loads(res[0]['auth_info'])
|
||||
# return res[0]['auth_info']
|
||||
|
||||
return False
|
||||
|
||||
def insert_trade_data_to_tb_trade(self, b_info):
|
||||
sql = '''
|
||||
REPLACE INTO `oh_my_bot_admin`.`trade` (`f_i`, `e_i`, `target`, `position`, `price`, `strategy`)
|
||||
VALUES ('%s', '%s', '%s', '%s', '%s', '%s');
|
||||
''' % (b_info['f_i'], b_info['e_i'], b_info['target'], b_info['position'],
|
||||
b_info['position_price'], b_info['strategy'])
|
||||
|
||||
return self.execute(sql)
|
||||
|
||||
# 초기 시드 갱신
|
||||
def update_first_seed_by_now_seed(self, bot_info, updated_date):
|
||||
sql = '''
|
||||
UPDATE `oh_my_bot_admin`.`bot` SET `amount` = '%s', `update_date` = '%s'
|
||||
WHERE (`id` = '%s');
|
||||
''' % (float(bot_info['last_amount']), updated_date, int(bot_info['id']))
|
||||
|
||||
return self.execute(sql)
|
||||
|
||||
'''
|
||||
User Info
|
||||
'''
|
||||
|
||||
''' 유저정보에 거래소 API 키 추가 add and update key template
|
||||
key = {'binance' :{
|
||||
'access': '123',
|
||||
'secret': '123'
|
||||
}}
|
||||
'''
|
||||
def upsert_keys_to_user_row(self, user_email, key):
|
||||
sql = "SELECT `auth_info` FROM oh_my_bot_admin.user " \
|
||||
"WHERE `email` = '%s';" % user_email
|
||||
|
||||
res = self.execute(sql)
|
||||
|
||||
if res is not ():
|
||||
auth = json.loads(res[0]['auth_info'])
|
||||
auth.update(key)
|
||||
|
||||
sql = "UPDATE `oh_my_bot_admin`.`user` SET `auth_info` = '%s' WHERE (`email` = '%s');" % \
|
||||
(json.dumps(auth), user_email)
|
||||
|
||||
return self.execute(sql)
|
||||
5
desktop.ini
Normal file
5
desktop.ini
Normal file
@@ -0,0 +1,5 @@
|
||||
[.ShellClassInfo]
|
||||
InfoTip=<EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20>¶<EFBFBD><C2B6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>˴ϴ<CBB4>.
|
||||
IconFile=C:\Program Files\Google\Drive\googledrivesync.exe
|
||||
IconIndex=16
|
||||
|
||||
1094
indicator_util.py
Normal file
1094
indicator_util.py
Normal file
File diff suppressed because it is too large
Load Diff
5
lib/desktop.ini
Normal file
5
lib/desktop.ini
Normal file
@@ -0,0 +1,5 @@
|
||||
[.ShellClassInfo]
|
||||
InfoTip=<EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20>¶<EFBFBD><C2B6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>˴ϴ<CBB4>.
|
||||
IconFile=C:\Program Files\Google\Drive\googledrivesync.exe
|
||||
IconIndex=16
|
||||
|
||||
628
lib/pybinancefutures.py
Normal file
628
lib/pybinancefutures.py
Normal file
@@ -0,0 +1,628 @@
|
||||
from sys import stdout
|
||||
import time
|
||||
|
||||
import pandas as pd
|
||||
|
||||
import websocket
|
||||
import requests
|
||||
import urllib
|
||||
import json
|
||||
|
||||
import hmac
|
||||
import hashlib
|
||||
|
||||
import threading
|
||||
|
||||
'''
|
||||
That library have got just a part of Documentation
|
||||
If you want to know what does certain function use can find more on
|
||||
|
||||
! ! !
|
||||
https://binance-docs.github.io/apidocs/futures/en
|
||||
! ! !
|
||||
'''
|
||||
|
||||
|
||||
class MarketData:
|
||||
|
||||
def __init__(self,
|
||||
testnet: bool = False,
|
||||
symbol: str = 'btcusdt',
|
||||
interval: str = '1m'):
|
||||
|
||||
'''
|
||||
|
||||
To use TESTNET Binance Futures API -> testnet = True
|
||||
|
||||
To change currency pair -> symbol = 'ethusdt'
|
||||
|
||||
To change interval -> interval = '5m'
|
||||
(m -> minutes
|
||||
h -> hours
|
||||
d -> days
|
||||
w -> weeks
|
||||
M -> months;
|
||||
|
||||
Valid values: [1m, 3m, 5m, 15m, 30m, 1h, 2h, 4h, 6h, 8h, 12h, 1d, 3d, 1w, 1M])
|
||||
|
||||
'''
|
||||
|
||||
if testnet == True:
|
||||
self.http_way = 'http://testnet.binancefuture.com/fapi/v1/'
|
||||
else:
|
||||
self.http_way = 'http://fapi.binance.com/fapi/v1/'
|
||||
|
||||
self.wss_way = 'wss://fstream.binance.com/ws/'
|
||||
self.interval = interval
|
||||
self.symbol = symbol.lower()
|
||||
|
||||
def ping(self):
|
||||
return requests.get(f'{self.http_way}ping').json()
|
||||
|
||||
def server_time(self):
|
||||
return requests.get(f'{self.http_way}time').json()
|
||||
|
||||
def exchange_info(self):
|
||||
return requests.get(f'{self.http_way}exchangeInfo').json()
|
||||
|
||||
def order_book(self, limit: int = 100):
|
||||
'''
|
||||
To change limit -> limit = 1000
|
||||
(Valid limits:[5, 10, 20, 50, 100, 500, 1000])
|
||||
'''
|
||||
return requests.get(f'{self.http_way}depth?limit={limit}').json()
|
||||
|
||||
def recent_trades(self, limit: int = 500):
|
||||
'''
|
||||
To change limit -> limit = 1000
|
||||
(max 1000)
|
||||
'''
|
||||
return requests.get(f'{self.http_way}trades?symbol={self.symbol}&limit={limit}').json()
|
||||
|
||||
def historical_trades(self, limit: int = 500):
|
||||
'''
|
||||
To change limit -> limit = 1000
|
||||
(max 1000)
|
||||
'''
|
||||
return requests.get(f'{self.http_way}historicalTrades?symbol={self.symbol}&limit={limit}').json()
|
||||
|
||||
def aggregate_trades(self,
|
||||
fromId: int = None,
|
||||
startTime: int = None,
|
||||
endTime: int = None,
|
||||
limit: int = 500):
|
||||
'''
|
||||
To change limit -> limit = 1000
|
||||
(max 1000)
|
||||
|
||||
To use fromId -> fromId = 1231
|
||||
To use start time and end time -> startTime = 1573661424937
|
||||
-> endTime = 1573661428706
|
||||
'''
|
||||
return requests.get(
|
||||
f'{self.http_way}aggTrades?symbol={self.symbol}&fromId={fromId}&startTime={startTime}&endTime={endTime}&limit={limit}').json()
|
||||
|
||||
def candles_data(self,
|
||||
interval: str = '1m',
|
||||
startTime: int = None,
|
||||
endTime: int = None,
|
||||
limit: int = 500):
|
||||
'''
|
||||
To change interval -> interval = '5m'
|
||||
(Valid values: [1m, 3m, 5m, 15m, 30m, 1h, 2h, 4h, 6h, 8h, 12h, 1d, 3d, 1w, 1M])
|
||||
|
||||
To use limit -> limit = 1231
|
||||
(Default 500; max 1500)
|
||||
|
||||
To use start time and end time -> startTime = 1573661424937
|
||||
-> endTime = 1573661428706
|
||||
'''
|
||||
return requests.get(
|
||||
f'{self.http_way}klines?symbol={self.symbol}&interval={interval}&startTime={startTime}&endTime={endTime}&limit={limit}').json()
|
||||
|
||||
def mark_price(self):
|
||||
return requests.get(f'{self.http_way}premiumIndex?symbol={self.symbol}').json()
|
||||
|
||||
def funding_rate(self,
|
||||
startTime: int = None,
|
||||
endTime: int = None,
|
||||
limit: int = 100):
|
||||
'''
|
||||
To change limit -> limit = 1000
|
||||
(max 1096)
|
||||
|
||||
To use start time and end time -> startTime = 1573661424937
|
||||
-> endTime = 1573661428706
|
||||
'''
|
||||
return requests.get(
|
||||
f'{self.http_way}klines?symbol={self.symbol}&startTime={startTime}&endTime={endTime}&limit={limit}').json()
|
||||
|
||||
def ticker_price_24h(self,
|
||||
symbol: bool = False):
|
||||
if symbol is True:
|
||||
return requests.get(f'{self.http_way}ticker/24hr?symbol={self.symbol}').json()
|
||||
else:
|
||||
return requests.get(f'{self.http_way}ticker/24hr').json()
|
||||
|
||||
def ticker_price_symbol(self,
|
||||
symbol: bool = False):
|
||||
if symbol is True:
|
||||
return requests.get(f'{self.http_way}ticker/price?symbol={self.symbol}').json()
|
||||
else:
|
||||
return requests.get(f'{self.http_way}ticker/price').json()
|
||||
|
||||
def ticker_orderbook_symbol(self,
|
||||
symbol: bool = False):
|
||||
if symbol is True:
|
||||
return requests.get(f'{self.http_way}ticker/bookTicker?symbol={self.symbol}').json()
|
||||
else:
|
||||
return requests.get(f'{self.http_way}ticker/bookTicker').json()
|
||||
|
||||
def load_last_candles(self, days=30):
|
||||
limit = 1440
|
||||
|
||||
one_hour_in_milliseconds = 3600000
|
||||
one_day_in_milliseconds = one_hour_in_milliseconds * 24
|
||||
|
||||
startTime = int(round(time.time() * 1000)) - (one_day_in_milliseconds * 30)
|
||||
|
||||
data = []
|
||||
|
||||
for k in range(days):
|
||||
r = requests.get(
|
||||
f"{self.http_way}klines?symbol={self.symbol}&interval={self.interval}&limit={limit}&starttime={startTime}")
|
||||
startTime += one_day_in_milliseconds
|
||||
response = r.json()
|
||||
|
||||
for i in range(len(response)):
|
||||
data.append(response[i])
|
||||
stdout.write(f'\r{k + 1} of {days}')
|
||||
stdout.flush()
|
||||
print('\n')
|
||||
|
||||
'''
|
||||
last_req = requests.get(f"{self.http_way}klines?symbol={self.symbol}&interval={self.interval}&limit=1")
|
||||
last_res = last_req.json()
|
||||
last_res = last_res[0]
|
||||
|
||||
if last_res[0] != data[-1][0]:
|
||||
print("New candle added!")
|
||||
|
||||
data.append(last_res)
|
||||
del data[0]
|
||||
'''
|
||||
|
||||
df = pd.DataFrame(data)
|
||||
df = df.iloc[:, :6]
|
||||
df.columns = (['Date', 'Open', 'High', 'Low', 'Close', 'Volume'])
|
||||
|
||||
df['Date'] = pd.to_datetime(df['Date'], unit='ms')
|
||||
df['Date'] = df['Date'].map(lambda x: x.strftime("%Y-%m-%d %H:%M"))
|
||||
|
||||
df = df.astype({'Open': 'float64',
|
||||
'High': 'float64',
|
||||
'Low': 'float64',
|
||||
'Close': 'float64',
|
||||
'Volume': 'float64'})
|
||||
|
||||
return df
|
||||
|
||||
|
||||
# %%
|
||||
|
||||
|
||||
class WebsocketMarket:
|
||||
|
||||
def __init__(self,
|
||||
on_message=lambda ws, message: (stdout.write(f'\r{json.loads(message)}'), stdout.flush()),
|
||||
on_error=lambda ws, error: print(error),
|
||||
on_close=lambda ws: print("### closed ###"),
|
||||
testnet: bool = False,
|
||||
symbol: str = 'btcusdt',
|
||||
speed: str = '100ms'):
|
||||
'''
|
||||
|
||||
To use TESTNET Binance Futures API -> testnet = True
|
||||
|
||||
To change currency pair -> symbol = 'ethusdt'
|
||||
|
||||
To change speed -> speed = '250ms'
|
||||
|
||||
'''
|
||||
|
||||
if testnet == True:
|
||||
self.wss_way = 'wss://stream.binancefuture.com/ws/'
|
||||
else:
|
||||
self.wss_way = 'wss://fstream.binance.com/ws/'
|
||||
|
||||
self.interval = '1m'
|
||||
self.symbol = symbol.lower()
|
||||
self.speed = speed
|
||||
|
||||
self.on_message = on_message
|
||||
self.on_error = on_error
|
||||
self.on_close = on_close
|
||||
|
||||
@staticmethod
|
||||
def parced(func):
|
||||
def parced_func(ws, msg):
|
||||
return func(ws, json.loads(msg))
|
||||
|
||||
return parced_func
|
||||
|
||||
def open_socket(self, way):
|
||||
thread = threading.Thread(target=lambda: self._open_socket(way))
|
||||
thread.start()
|
||||
|
||||
def _open_socket(self, way):
|
||||
websocket.enableTrace(False)
|
||||
|
||||
on_message_with_parce = WebsocketMarket.parced(self.on_message)
|
||||
self.ws = websocket.WebSocketApp(way,
|
||||
on_message=on_message_with_parce,
|
||||
on_close=self.on_close,
|
||||
on_error=self.on_error)
|
||||
self.ws.run_forever()
|
||||
|
||||
def aggregate_trade_socket(self):
|
||||
self.open_socket(f'{self.wss_way}{self.symbol}@aggTrade')
|
||||
|
||||
def mark_price_socket(self):
|
||||
self.open_socket(f'{self.wss_way}{self.symbol}@markPrice')
|
||||
|
||||
def candle_socket(self):
|
||||
self.open_socket(f'{self.wss_way}{self.symbol}@kline_{self.interval}')
|
||||
|
||||
def individual_symbol_mini_ticker(self):
|
||||
self.open_socket(f'{self.wss_way}{self.symbol}@miniTicker')
|
||||
|
||||
def individual_symbol_ticker(self):
|
||||
self.open_socket(f'{self.wss_way}{self.symbol}@ticker')
|
||||
|
||||
def all_book_ticker(self):
|
||||
self.open_socket(f'{self.wss_way}!bookTicker')
|
||||
|
||||
def partial_book_depth_socket(self,
|
||||
levels: int = 20):
|
||||
'''
|
||||
To change count of top bids and asks -> levels = 5
|
||||
(5, 10 or 20 values are valid)
|
||||
'''
|
||||
self.open_socket(f'{self.wss_way}{self.symbol}@depth{levels}@{self.speed}')
|
||||
|
||||
def diff_book_depth_socket(self):
|
||||
self.open_socket(f'{self.wss_way}{self.symbol}@depth@{self.speed}')
|
||||
|
||||
|
||||
# %%
|
||||
|
||||
class Client:
|
||||
def __init__(self,
|
||||
api_key: str,
|
||||
sec_key: str,
|
||||
testnet: bool = False,
|
||||
symbol: str = 'BTCUSDT'):
|
||||
'''
|
||||
In any case you must give your API key and API secret to work with Client
|
||||
|
||||
To use TESTNET Binance Futures API -> testnet = True
|
||||
To change currency pair -> symbol = 'ethusdt'
|
||||
'''
|
||||
|
||||
self.api_key = api_key
|
||||
self.sec_key = sec_key
|
||||
self.http_way = 'http://fapi.binance.com/fapi/v1/'
|
||||
self.symbol = symbol
|
||||
self.X_MBX_APIKEY = {"X-MBX-APIKEY": self.api_key}
|
||||
|
||||
if testnet == True:
|
||||
self.http_way = 'http://testnet.binancefuture.com/fapi/v1/'
|
||||
self.wss_way = 'wss://stream.binancefuture.com/ws/'
|
||||
else:
|
||||
self.http_way = 'http://fapi.binance.com/fapi/v1/'
|
||||
self.wss_way = 'wss://fstream.binance.com/ws/'
|
||||
|
||||
def open_socket(self, way, on_message, on_error, on_close):
|
||||
websocket.enableTrace(False)
|
||||
|
||||
self.ws = websocket.WebSocketApp(way,
|
||||
on_message=on_message,
|
||||
on_error=on_error,
|
||||
on_close=on_close)
|
||||
self.ws.run_forever()
|
||||
|
||||
def _get_request(self,
|
||||
req,
|
||||
query):
|
||||
r = requests.get(self.request_url(req=req,
|
||||
query=query,
|
||||
signature=self.get_sign(query=query)),
|
||||
headers=self.X_MBX_APIKEY)
|
||||
|
||||
try:
|
||||
return r.json()
|
||||
except:
|
||||
if str(r) == '<Response [200]>':
|
||||
return dict([])
|
||||
else:
|
||||
return r
|
||||
|
||||
def _post_request(self,
|
||||
req,
|
||||
query):
|
||||
r = requests.post(self.request_url(req=req,
|
||||
query=query,
|
||||
signature=self.get_sign(query=query)),
|
||||
headers=self.X_MBX_APIKEY)
|
||||
|
||||
try:
|
||||
return r.json()
|
||||
except:
|
||||
if str(r) == '<Response [200]>':
|
||||
return dict([])
|
||||
else:
|
||||
return r
|
||||
|
||||
def _delete_request(self,
|
||||
req,
|
||||
query):
|
||||
r = requests.delete(self.request_url(req=req,
|
||||
query=query,
|
||||
signature=self.get_sign(query=query)),
|
||||
headers=self.X_MBX_APIKEY)
|
||||
|
||||
try:
|
||||
return r.json()
|
||||
except:
|
||||
if str(r) == '<Response [200]>':
|
||||
return dict([])
|
||||
else:
|
||||
return r
|
||||
|
||||
def _put_request(self,
|
||||
req,
|
||||
query):
|
||||
r = requests.put(self.request_url(req=req,
|
||||
query=query,
|
||||
signature=self.get_sign(query=query)),
|
||||
headers=self.X_MBX_APIKEY)
|
||||
try:
|
||||
return r.json()
|
||||
except:
|
||||
if str(r) == '<Response [200]>':
|
||||
return dict([])
|
||||
else:
|
||||
return r
|
||||
|
||||
@staticmethod
|
||||
def timestamp():
|
||||
return int(time.time() * 1000) - 1000
|
||||
return int(time.time() * 1000)
|
||||
|
||||
def get_sign(self, query):
|
||||
|
||||
return hmac.new(self.sec_key.encode('utf-8'), query.encode('utf-8'), hashlib.sha256).hexdigest()
|
||||
|
||||
def request_url(self, req, query, signature):
|
||||
|
||||
return self.http_way + req + query + '&signature=' + signature
|
||||
|
||||
def new_order(self,
|
||||
side: str,
|
||||
orderType: str,
|
||||
quantity: float,
|
||||
timeInForce: float = None,
|
||||
reduceOnly: bool = False,
|
||||
price: float = None,
|
||||
newClientOrderId: str = None,
|
||||
stopPrice: float = None,
|
||||
workingType: str = None):
|
||||
'''
|
||||
POST
|
||||
|
||||
Choose side: SELL or BUY
|
||||
Choose quantity: 0.001
|
||||
Choose price: 7500
|
||||
|
||||
To change order type -> orderType = 'MARKET'
|
||||
To change time in force -> timeInForce = 'IOC'
|
||||
'''
|
||||
|
||||
req = 'order?'
|
||||
|
||||
querystring = {'symbol': self.symbol,
|
||||
'side': side,
|
||||
'type': orderType,
|
||||
'quantity': quantity,
|
||||
'reduceOnly': reduceOnly}
|
||||
if timeInForce is not None:
|
||||
querystring['timeInForce'] = timeInForce
|
||||
if price is not None:
|
||||
querystring['price'] = price
|
||||
if newClientOrderId is not None:
|
||||
querystring['newClientOrderId'] = newClientOrderId
|
||||
if stopPrice is not None:
|
||||
querystring['stopPrice'] = stopPrice
|
||||
if workingType is not None:
|
||||
querystring['workingType'] = workingType
|
||||
querystring['timestamp'] = self.timestamp()
|
||||
|
||||
querystring = urllib.parse.urlencode(querystring)
|
||||
|
||||
return self._post_request(req, querystring)
|
||||
|
||||
def query_order(self, orderId):
|
||||
'''
|
||||
GET
|
||||
|
||||
Choose orderId: 156316486
|
||||
'''
|
||||
req = 'order?'
|
||||
querystring = urllib.parse.urlencode({'symbol': self.symbol,
|
||||
'orderId': orderId,
|
||||
'timestamp': self.timestamp()})
|
||||
|
||||
return self._get_request(req, querystring)
|
||||
|
||||
def cancel_order(self, orderId):
|
||||
'''
|
||||
DELETE
|
||||
|
||||
Choose orderId: 156316486
|
||||
'''
|
||||
req = 'order?'
|
||||
querystring = urllib.parse.urlencode({'symbol': self.symbol,
|
||||
'orderId': orderId,
|
||||
'timestamp': self.timestamp()})
|
||||
|
||||
return self._delete_request(req, querystring)
|
||||
|
||||
def current_open_orders(self):
|
||||
'''
|
||||
GET
|
||||
'''
|
||||
req = 'openOrders?'
|
||||
querystring = urllib.parse.urlencode({'timestamp': self.timestamp()})
|
||||
|
||||
return self._get_request(req, querystring)
|
||||
|
||||
def all_orders(self,
|
||||
limit: int = 1000,
|
||||
startTime: int = None,
|
||||
endTime: int = None):
|
||||
'''
|
||||
GET
|
||||
|
||||
To change limit of output orders -> limit = 1000
|
||||
(max value is 1000)
|
||||
To use start time and end time -> startTime = 1573661424937
|
||||
-> endTime = 1573661428706
|
||||
'''
|
||||
req = 'allOrders?'
|
||||
querystring = urllib.parse.urlencode({'symbol': self.symbol,
|
||||
'timestamp': self.timestamp(),
|
||||
'limit': limit,
|
||||
'startTime': startTime,
|
||||
'endTime': endTime})
|
||||
|
||||
return self._get_request(req, querystring)
|
||||
|
||||
def balance(self):
|
||||
'''
|
||||
GET
|
||||
'''
|
||||
req = 'balance?'
|
||||
querystring = urllib.parse.urlencode({'timestamp': self.timestamp()})
|
||||
|
||||
return self._get_request(req, querystring)
|
||||
|
||||
def account_info(self):
|
||||
'''
|
||||
GET
|
||||
'''
|
||||
req = 'account?'
|
||||
querystring = urllib.parse.urlencode({'timestamp': self.timestamp()})
|
||||
|
||||
return self._get_request(req, querystring)
|
||||
|
||||
def change_leverage(self, leverage):
|
||||
'''
|
||||
POST
|
||||
|
||||
To change leverage -> leverage = 25
|
||||
(from 1 to 125 are valid values)
|
||||
'''
|
||||
req = 'leverage?'
|
||||
querystring = urllib.parse.urlencode({'symbol': self.symbol,
|
||||
'leverage': leverage,
|
||||
'timestamp': self.timestamp()})
|
||||
|
||||
return self._post_request(req, querystring)
|
||||
|
||||
def position_info(self):
|
||||
'''GET'''
|
||||
req = 'positionRisk?'
|
||||
querystring = urllib.parse.urlencode({'timestamp': self.timestamp()})
|
||||
|
||||
return self._get_request(req, querystring)
|
||||
|
||||
def trade_list(self,
|
||||
limit: int = 1000,
|
||||
startTime: int = None,
|
||||
endTime: int = None):
|
||||
'''
|
||||
GET
|
||||
|
||||
To change limit of output orders -> limit = 1000
|
||||
(max value is 1000)
|
||||
To use start time and end time -> startTime = 1573661424937
|
||||
-> endTime = 1573661428706
|
||||
'''
|
||||
req = 'userTrades?'
|
||||
querystring = urllib.parse.urlencode({'symbol': self.symbol,
|
||||
'timestamp': self.timestamp(),
|
||||
'limit': limit,
|
||||
'startTime': startTime,
|
||||
'endTime': endTime})
|
||||
|
||||
return self._get_request(req, querystring)
|
||||
|
||||
def income_history(self,
|
||||
limit: int = 1000):
|
||||
'''
|
||||
GET
|
||||
|
||||
To change limit of output orders -> limit = 1000
|
||||
(max value is 1000)
|
||||
'''
|
||||
req = 'income?'
|
||||
querystring = urllib.parse.urlencode({'symbol': self.symbol,
|
||||
'timestamp': self.timestamp(),
|
||||
'limit': limit})
|
||||
|
||||
return self._get_request(req, querystring)
|
||||
|
||||
def start_stream(self):
|
||||
'''
|
||||
POST
|
||||
'''
|
||||
req = 'listenKey?'
|
||||
querystring = urllib.parse.urlencode({'timestamp': self.timestamp()})
|
||||
|
||||
return self._post_request(req, querystring)
|
||||
|
||||
def get_listen_key(self):
|
||||
return self.start_stream()['listenKey']
|
||||
|
||||
def keepalive_stream(self):
|
||||
'''
|
||||
PUT
|
||||
'''
|
||||
req = 'listenKey?'
|
||||
querystring = urllib.parse.urlencode({'timestamp': self.timestamp()})
|
||||
|
||||
return self._put_request(req, querystring)
|
||||
|
||||
def close_stream(self):
|
||||
'''
|
||||
DELETE
|
||||
'''
|
||||
req = 'listenKey?'
|
||||
querystring = urllib.parse.urlencode({'timestamp': self.timestamp()})
|
||||
|
||||
return self._delete_request(req, querystring)
|
||||
|
||||
def user_update_socket(self,
|
||||
on_message,
|
||||
on_error,
|
||||
on_close):
|
||||
|
||||
listen_key = self.get_listen_key()
|
||||
self.open_socket(f'{self.wss_way}{listen_key}', on_message, on_error, on_close)
|
||||
|
||||
def stop_user_update_socket(self):
|
||||
self.close_stream()
|
||||
|
||||
|
||||
def on_new_candle_loaded(ws, candle):
|
||||
print(ws, candle)
|
||||
27
mail.py
Normal file
27
mail.py
Normal file
@@ -0,0 +1,27 @@
|
||||
import smtplib
|
||||
from email.mime.text import MIMEText
|
||||
|
||||
|
||||
class Mail:
|
||||
def send_email(self, title='[oh_my_bot] Test Mail Title', body='Test Mail Body'):
|
||||
self.server = smtplib.SMTP('smtp.gmail.com', 587)
|
||||
self.server.starttls()
|
||||
self.server.login("javamon1174@gmail.com", "jtpsharbrcgvdlex")
|
||||
|
||||
title.encode('utf-8')
|
||||
body.encode('utf-8')
|
||||
|
||||
body += '\n\n--------------------------------------------------------------------------------\n'
|
||||
body += 'created by javamon\n'
|
||||
body += 'email : javamon1174@gmail.com\n'
|
||||
body += 'blog : https://javamon1174.github.io/\n'
|
||||
|
||||
data = MIMEText(body, _charset='euc-kr')
|
||||
data['Subject'] = title
|
||||
data['From'] = 'javamon1174@gmail.com'
|
||||
data['To'] = 'javamon1174@gmail.com'
|
||||
|
||||
self.server.sendmail("javamon1174@gmail.com", 'javamon1174@gmail.com', data.as_string())
|
||||
self.server.quit()
|
||||
|
||||
del self.server, title, body
|
||||
3
print.py
Normal file
3
print.py
Normal file
@@ -0,0 +1,3 @@
|
||||
import datetime
|
||||
|
||||
print('this is test print file', datetime.datetime.now())
|
||||
51
requirements.txt
Normal file
51
requirements.txt
Normal file
@@ -0,0 +1,51 @@
|
||||
APScheduler==3.6.3
|
||||
Backtesting==0.1.2
|
||||
backtrader==1.9.74.123
|
||||
beautifulsoup4==4.8.0
|
||||
bokeh==1.3.4
|
||||
bs4==0.0.1
|
||||
certifi==2019.9.11
|
||||
cffi==1.13.2
|
||||
chardet==3.0.4
|
||||
cycler==0.10.0
|
||||
Cython==0.29.14
|
||||
DateTime==4.3
|
||||
gevent==1.4.0
|
||||
greenlet==0.4.15
|
||||
html5lib==1.0.1
|
||||
idna==2.8
|
||||
Jinja2==2.10.1
|
||||
kiwisolver==1.1.0
|
||||
MarkupSafe==1.1.1
|
||||
matplotlib==3.1.1
|
||||
mysqlclient==1.4.5
|
||||
numpy==1.16.5
|
||||
packaging==19.2
|
||||
pandas==0.25.1
|
||||
patsy==0.5.1
|
||||
Pillow==6.1.0
|
||||
psutil==5.6.4
|
||||
pybithumb==1.0.7
|
||||
pycparser==2.19
|
||||
PyJWT==1.7.1
|
||||
PyMySQL==0.9.3
|
||||
pyparsing==2.4.2
|
||||
pystan==2.19.1.1
|
||||
python-dateutil==2.8.0
|
||||
pyti==0.2.2
|
||||
pytz==2019.2
|
||||
pyupbit==0.2.5
|
||||
PyYAML==5.1.2
|
||||
requests==2.22.0
|
||||
scipy==1.3.1
|
||||
six==1.12.0
|
||||
soupsieve==1.9.3
|
||||
SQLAlchemy==1.3.10
|
||||
statsmodels==0.10.1
|
||||
TA-Lib==0.4.17
|
||||
tornado==6.0.3
|
||||
tzlocal==2.0.0
|
||||
urllib3==1.25.4
|
||||
webencodings==0.5.1
|
||||
websocket==0.2.1
|
||||
zope.interface==4.7.1
|
||||
667
signal_helper.py
Normal file
667
signal_helper.py
Normal file
@@ -0,0 +1,667 @@
|
||||
import talib
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
import sys
|
||||
|
||||
def data_columns_init(data):
|
||||
# data.reset_index(level=0, inplace=True)
|
||||
|
||||
t_col = []
|
||||
for c in data.columns:
|
||||
t_col.append(c.lower().capitalize())
|
||||
|
||||
# data.reset_index(level=0, inplace=True)
|
||||
# t_col.insert(0, 'Date')
|
||||
|
||||
data.columns = t_col
|
||||
|
||||
|
||||
def heikin_ashi(df):
|
||||
heikin_ashi_df = pd.DataFrame(index=df.index.values, columns=['open', 'high', 'low', 'close'])
|
||||
|
||||
heikin_ashi_df['close'] = (df['open'] + df['high'] + df['low'] + df['close']) / 4
|
||||
|
||||
for i in range(len(df)):
|
||||
if i == 0:
|
||||
heikin_ashi_df.iat[0, 0] = df['open'].iloc[0]
|
||||
else:
|
||||
heikin_ashi_df.iat[i, 0] = (heikin_ashi_df.iat[i - 1, 0] + heikin_ashi_df.iat[i - 1, 3]) / 2
|
||||
|
||||
heikin_ashi_df['high'] = heikin_ashi_df.loc[:, ['open', 'close']].join(df['high']).max(axis=1)
|
||||
|
||||
heikin_ashi_df['low'] = heikin_ashi_df.loc[:, ['open', 'close']].join(df['low']).min(axis=1)
|
||||
|
||||
return heikin_ashi_df
|
||||
|
||||
def get_candle_pattern_arr(data=None, use_patterns=talib.get_function_groups()['Pattern Recognition']):
|
||||
if data is None:
|
||||
return []
|
||||
|
||||
r_data = [None] * len(data.Close)
|
||||
|
||||
for p in use_patterns:
|
||||
f = getattr(talib, p)
|
||||
res_arr = f(data.Open, data.High, data.Low, data.Close)
|
||||
|
||||
# 100 is plus candle / -100 is minus candle
|
||||
for i in range(0, len(res_arr)):
|
||||
# print(p, res_arr[i])
|
||||
|
||||
if int(res_arr[i]) is not 0:
|
||||
r_data[i] = {p: int(res_arr[i])}
|
||||
|
||||
return r_data
|
||||
|
||||
|
||||
def crossover(k, pre_k, d, pre_d) -> bool:
|
||||
try:
|
||||
return k > d and pre_k < pre_d
|
||||
except IndexError:
|
||||
return False
|
||||
|
||||
|
||||
def is_divergence(i, cdl_cnt, low, high, res_arr):
|
||||
# 저가 갱신 / 지표 저점 상승 : 매수
|
||||
if min(low[i - cdl_cnt:i]) < min(low[i - (cdl_cnt * 2):i - cdl_cnt + 1]) and \
|
||||
min(res_arr[i - cdl_cnt:i]) > min(res_arr[i - (cdl_cnt * 2):i - cdl_cnt + 1]):
|
||||
return True
|
||||
# 고가 갱신 / 지표 고점 하락 : 매도
|
||||
elif max(high[i - cdl_cnt:i]) > max(high[i - (cdl_cnt * 2):i - cdl_cnt + 1]) and \
|
||||
max(res_arr[i - cdl_cnt:i]) < max(res_arr[i - (cdl_cnt * 2):i - cdl_cnt + 1]):
|
||||
return False
|
||||
|
||||
|
||||
def moving_average(data, n=3) :
|
||||
return talib.EMA(data, timeperiod=n) # 이동평균
|
||||
|
||||
|
||||
def stoch_rsi(data, rsi_period=14, stoch_period=14, slowk_period=3):
|
||||
# rsi = talib.RSI(close, 14)
|
||||
# rsi_fastd, rsi_slowd = talib.STOCH(rsi, rsi, rsi, fastk_period=14, slowk_period=3, slowd_period=3,
|
||||
# slowk_matype=0, slowd_matype=0)
|
||||
|
||||
rsi = talib.RSI(data, rsi_period)
|
||||
return talib.STOCH(rsi, rsi, rsi, fastk_period=stoch_period, slowk_period=slowk_period, slowd_period=slowk_period,
|
||||
slowk_matype=0, slowd_matype=0)
|
||||
|
||||
|
||||
def is_trade_fence_pattern(pattern, value):
|
||||
p = pattern.upper()
|
||||
|
||||
if p == "CDL3INSIDE":
|
||||
if int(value) < 0:
|
||||
return True
|
||||
elif int(value) > 0:
|
||||
return False
|
||||
|
||||
elif p == "CDL3LINESTRIKE":
|
||||
if int(value) < 0:
|
||||
return True
|
||||
elif int(value) > 0:
|
||||
return False
|
||||
|
||||
elif p == "CDL3OUTSIDE":
|
||||
if int(value) > 0:
|
||||
return True
|
||||
elif int(value) < 0:
|
||||
return False
|
||||
|
||||
elif p == "CDLABANDONEDBABY": # no data
|
||||
return None
|
||||
if int(value) > 0:
|
||||
return True
|
||||
elif int(value) < 0:
|
||||
return False
|
||||
|
||||
elif p == "CDLBELTHOLD":
|
||||
if int(value) > 0:
|
||||
return True
|
||||
elif int(value) < 0:
|
||||
return False
|
||||
|
||||
elif p == "CDLBREAKAWAY":
|
||||
if int(value) > 0:
|
||||
return True
|
||||
elif int(value) < 0:
|
||||
return False
|
||||
|
||||
elif p == "CDLCLOSINGMARUBOZU": # 거래 빈도 높고 60퍼 이상의 확률
|
||||
if int(value) > 0:
|
||||
return True
|
||||
elif int(value) < 0:
|
||||
return False
|
||||
|
||||
elif p == "CDLCOUNTERATTACK":
|
||||
if int(value) > 0:
|
||||
return True
|
||||
elif int(value) < 0:
|
||||
return False
|
||||
|
||||
elif p == "CDLCONCEALBABYSWALL" or p == "CDLMATHOLD":
|
||||
return None
|
||||
if int(value) > 0:
|
||||
return True
|
||||
elif int(value) < 0:
|
||||
return False
|
||||
|
||||
elif p == "CDLENGULFING":
|
||||
if int(value) > 0:
|
||||
return True
|
||||
elif int(value) < 0:
|
||||
return False
|
||||
|
||||
elif p == "CDLGAPSIDESIDEWHITE":
|
||||
if int(value) > 0:
|
||||
return True
|
||||
elif int(value) < 0:
|
||||
return False
|
||||
|
||||
elif p == "CDLHARAMI":
|
||||
if int(value) > 0:
|
||||
return True
|
||||
elif int(value) < 0:
|
||||
return False
|
||||
|
||||
elif p == "CDLHARAMICROSS":
|
||||
if int(value) > 0:
|
||||
return True
|
||||
elif int(value) < 0:
|
||||
return False
|
||||
|
||||
elif p == "CDLHIKKAKEMOD" or p == "CDLHIKKAKE":
|
||||
if int(value) > 0:
|
||||
return True
|
||||
elif int(value) < 0:
|
||||
return False
|
||||
|
||||
elif p == "CDLKICKING" or p == "CDLKICKINGBYLENGTH":
|
||||
if int(value) > 0:
|
||||
return True
|
||||
elif int(value) < 0:
|
||||
return False
|
||||
|
||||
elif p == "CDLPIERCING":
|
||||
if int(value) > 0:
|
||||
return True
|
||||
elif int(value) < 0:
|
||||
return False
|
||||
|
||||
elif p == "CDLRISEFALL3METHODS":
|
||||
if int(value) > 0:
|
||||
return True
|
||||
elif int(value) < 0:
|
||||
return False
|
||||
|
||||
elif p == "CDLSEPARATINGLINES": # 역추세로 활용 보류
|
||||
return None
|
||||
if int(value) > 0:
|
||||
return True
|
||||
elif int(value) < 0:
|
||||
return False
|
||||
|
||||
|
||||
elif p == "CDLSTALLEDPATTERN": # 역추세 - 시그널
|
||||
return None
|
||||
if int(value) > 0:
|
||||
return True
|
||||
elif int(value) < 0:
|
||||
return False
|
||||
|
||||
elif p == "CDLTASUKIGAP":
|
||||
if int(value) > 0:
|
||||
return True
|
||||
elif int(value) < 0:
|
||||
return False
|
||||
|
||||
elif p == "CDLUNIQUE3RIVER": # 역추세 - 시그널
|
||||
return None
|
||||
if int(value) > 0:
|
||||
return True
|
||||
elif int(value) < 0:
|
||||
return False
|
||||
|
||||
elif p == "CDLXSIDEGAP3METHODS":
|
||||
if int(value) > 0:
|
||||
return True
|
||||
elif int(value) < 0:
|
||||
return False
|
||||
|
||||
elif p == "CDLSTICKSANDWICH":
|
||||
if int(value) > 0:
|
||||
return True
|
||||
elif int(value) < 0:
|
||||
return False
|
||||
|
||||
elif p == "CDLTRISTAR":
|
||||
if int(value) > 0:
|
||||
return True
|
||||
elif int(value) < 0:
|
||||
return False
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def get_indicators_args(indicators):
|
||||
res_data = []
|
||||
|
||||
for i in indicators:
|
||||
if i is 'RSI' or i is 'RSI_DIV': # 베이스 지표
|
||||
e_range = list(range(9, 31))
|
||||
# e_range = [14]
|
||||
for v in e_range:
|
||||
res_data.append({i: v})
|
||||
# res_data = list(range(9, 31))
|
||||
elif i is 'STOCH': # 주도 지표
|
||||
# res_data.append({i: [5, 3, 3]})
|
||||
# continue
|
||||
f_range = list(range(5, 21))
|
||||
for f_r in f_range:
|
||||
s_range = list(range(1, f_r + 1))
|
||||
for s_r in s_range:
|
||||
t_range = list(range(1, s_r + 1))
|
||||
for t_r in t_range:
|
||||
res_data.append({i: [f_r, s_r, t_r]})
|
||||
|
||||
elif i is 'ADX' or i is 'ADXR' or i is 'DMI': # 베이스 지표
|
||||
e_range = list(range(9, 21))
|
||||
# e_range = [14]DMI
|
||||
for v in e_range:
|
||||
res_data.append({i: v})
|
||||
|
||||
elif i is 'DI': # Directional Indicator Plus/Minus # 주도 지표
|
||||
e_range = list(range(9, 31))
|
||||
# e_range = [14]
|
||||
for v in e_range:
|
||||
res_data.append({i: v})
|
||||
|
||||
elif i is 'APO': # 주도 지표
|
||||
f_range = list(range(17, 31))
|
||||
for f_r in f_range:
|
||||
s_range = list(range(10, f_r + 1))
|
||||
for s_r in s_range:
|
||||
res_data.append({i: [s_r, f_r]})
|
||||
|
||||
elif i is 'AROON': # 주도 지표
|
||||
e_range = list(range(11, 31))
|
||||
for v in e_range:
|
||||
res_data.append({i: v})
|
||||
|
||||
elif i is 'AROONOSC': # 베이스 지표
|
||||
e_range = list(range(3, 21))
|
||||
for v in e_range:
|
||||
res_data.append({i: v})
|
||||
|
||||
elif i is 'BOP': # 베이스
|
||||
res_data.append({i: None})
|
||||
|
||||
elif i is 'CCI': # 베이스
|
||||
e_range = list(range(11, 31))
|
||||
for v in e_range:
|
||||
res_data.append({i: v})
|
||||
|
||||
elif i is 'CMO': # 베이스
|
||||
e_range = list(range(9, 31))
|
||||
for v in e_range:
|
||||
res_data.append({i: v})
|
||||
|
||||
# elif i is 'DX': # 베이스 / 추세 추종
|
||||
# e_range = list(range(9, 31))
|
||||
# for v in e_range:
|
||||
# res_data.append({i: v})
|
||||
|
||||
elif i is 'MACD': # 주도 지표
|
||||
f_range = list(range(11, 31))
|
||||
for f_r in f_range:
|
||||
s_range = list(range(9, f_r + 1))
|
||||
for s_r in s_range:
|
||||
t_range = list(range(7, s_r + 1))
|
||||
for t_r in t_range:
|
||||
res_data.append({i: [s_r, f_r, t_r]})
|
||||
|
||||
elif i is 'MACDFIX': # 주도 지표
|
||||
e_range = list(range(5, 21))
|
||||
for v in e_range:
|
||||
res_data.append({i: v})
|
||||
|
||||
elif i is 'MFI': # 베이스 / 추세
|
||||
e_range = list(range(9, 31))
|
||||
for v in e_range:
|
||||
res_data.append({i: v})
|
||||
|
||||
elif i is 'MOM': # 베이스 / 역추세
|
||||
e_range = list(range(9, 31))
|
||||
for v in e_range:
|
||||
res_data.append({i: v})
|
||||
|
||||
elif i is 'PPO': # 주도 지표
|
||||
f_range = list(range(9, 26))
|
||||
for f_r in f_range:
|
||||
s_range = list(range(10, f_r + 1))
|
||||
for s_r in s_range:
|
||||
res_data.append({i: [s_r, f_r]})
|
||||
|
||||
elif i is 'ROC' or i is 'ROCP' or i is 'WILLR' or i is 'MIDPOINT' or i is 'MIDPRICE': # 베이스 지표 / 추세
|
||||
e_range = list(range(9, 31))
|
||||
for v in e_range:
|
||||
res_data.append({i: v})
|
||||
|
||||
# elif i is 'ROCR':# 베이스 지표 / 추세 => 보류
|
||||
# e_range = list(range(9, 31))
|
||||
# for v in e_range:
|
||||
# res_data.append({i: v}
|
||||
|
||||
# elif i is 'ROCR100':# 베이스 지표 / 추세 => 보류
|
||||
# e_range = list(range(9, 31))
|
||||
# for v in e_range:
|
||||
# res_data.append({i: v})
|
||||
|
||||
elif i is 'STOCHF': # 주도 지표
|
||||
f_range = list(range(5, 21))
|
||||
for f_r in f_range:
|
||||
s_range = list(range(3, f_r + 1))
|
||||
for s_r in s_range:
|
||||
res_data.append({i: [f_r, s_r]})
|
||||
|
||||
elif i is 'STOCHRSI': # 주도 지표
|
||||
f_range = list(range(5, 21))
|
||||
for f_r in f_range:
|
||||
s_range = list(range(3, f_r + 1))
|
||||
for s_r in s_range:
|
||||
t_range = list(range(3, s_r + 1))
|
||||
for t_r in t_range:
|
||||
res_data.append({i: [f_r, s_r, t_r]})
|
||||
|
||||
elif i is 'TRIX': # 베이스 지표 / 역추세
|
||||
f_range = list(range(3, 36))
|
||||
for f_r in f_range:
|
||||
s_range = list(range(2, f_r + 1))
|
||||
for s_r in s_range:
|
||||
res_data.append({i: [f_r, s_r]})
|
||||
|
||||
elif i is 'ULTOSC': # 주도 지표
|
||||
f_range = list(range(7, 31))
|
||||
for f_r in f_range:
|
||||
s_range = list(range(5, f_r + 1))
|
||||
for s_r in s_range:
|
||||
t_range = list(range(5, s_r + 1))
|
||||
for t_r in t_range:
|
||||
res_data.append({i: [t_r, s_r, f_r]})
|
||||
|
||||
elif i is 'BBANDS': # 베이스 지표
|
||||
f_range = list(range(9, 31))
|
||||
for f_r in f_range:
|
||||
s_range = list(range(1, f_r + 1))
|
||||
for s_r in s_range:
|
||||
res_data.append({i: [f_r, s_r]})
|
||||
|
||||
elif i is 'EMA' or i is 'DEMA' or i is 'MA' or i is 'SMA': # 주도 지표
|
||||
f_range = list(range(9, 36))
|
||||
for f_r in f_range:
|
||||
s_range = list(range(5, f_r + 1))
|
||||
for s_r in s_range:
|
||||
res_data.append({i: [f_r, s_r]})
|
||||
|
||||
# elif i is 'KAMA': # 베이스 지표 / 추세 => 사용법 모름
|
||||
# e_range = list(range(9, 36))
|
||||
# for v in e_range:
|
||||
# res_data.append({i: v})
|
||||
|
||||
elif i is 'MAMA': # 주도 지표
|
||||
f_range = list(range(1, 10))
|
||||
for f_r in f_range:
|
||||
s_range = list(range(1, 10))
|
||||
for s_r in s_range:
|
||||
res_data.append({i: [f_r / 10, s_r / 100]})
|
||||
|
||||
# elif i is 'MAVP': # 주도 지표
|
||||
# f_range = list(range(9, 36))
|
||||
# for f_r in f_range:
|
||||
# s_range = list(range(10, f_r+1))
|
||||
# for s_r in s_range:
|
||||
# res_data.append({i : [s_r, f_r]})
|
||||
|
||||
elif i is 'SAR': # 베이스 지표 / 역추세 => 거래 빈도만 줄이면 훌륭할 듯
|
||||
e_range = list(range(1, 5))
|
||||
for v in e_range:
|
||||
res_data.append({i: v})
|
||||
|
||||
elif i is 'T3': # 주도 지표
|
||||
f_range = list(range(5, 31))
|
||||
for f_r in f_range:
|
||||
s_range = list(range(3, f_r))
|
||||
for s_r in s_range:
|
||||
res_data.append({i: [f_r, s_r]})
|
||||
|
||||
elif i is 'TEMA' or i is 'TRIMA' or i is 'WMA': # 주도 지표
|
||||
f_range = list(range(15, 41))
|
||||
for f_r in f_range:
|
||||
s_range = list(range(7, f_r))
|
||||
for s_r in s_range:
|
||||
res_data.append({i: [f_r, s_r]})
|
||||
|
||||
elif i is 'AD': # 베이스 지표 - 거래량
|
||||
res_data.append({i: None})
|
||||
|
||||
elif i is 'ADOSC': # 주도 지표 - 거래량 => 추세
|
||||
f_range = list(range(7, 31))
|
||||
for f_r in f_range:
|
||||
s_range = list(range(3, f_r))
|
||||
for s_r in s_range:
|
||||
res_data.append({i: [s_r, f_r]})
|
||||
|
||||
elif i is 'OBV' or i is 'BOP_DIV': # 베이스 지표 - 거래량 기반 상승장, 하락장 지표
|
||||
res_data.append({i: None})
|
||||
|
||||
# 다이버전스 주도 지표 / 역추세
|
||||
elif i is 'ADX_DIV' or i is 'ADXR_DIV' or i is 'AROONOSC_DIV' or i is 'CCI_DIV' or i is 'CMO_DIV' \
|
||||
or i is 'MFI_DIV' or i is 'MOM_DIV' or i is 'ROC_DIV' or i is 'ROCP_DIV' or i is 'ROCR_DIV' \
|
||||
or i is 'TRIX_DIV' or i is 'WILLR_DIV':
|
||||
e_range = list(range(9, 31))
|
||||
for v in e_range:
|
||||
res_data.append({i: v})
|
||||
|
||||
elif i is 'STOCH_DIV':
|
||||
f_range = list(range(5, 21))
|
||||
for f_r in f_range:
|
||||
s_range = list(range(1, f_r + 1))
|
||||
for s_r in s_range:
|
||||
t_range = list(range(1, s_r + 1))
|
||||
for t_r in t_range:
|
||||
res_data.append({i: [f_r, s_r, t_r]})
|
||||
|
||||
elif i is 'STOCHF_DIV':
|
||||
f_range = list(range(7, 31))
|
||||
for f_r in f_range:
|
||||
s_range = list(range(3, f_r))
|
||||
for s_r in s_range:
|
||||
res_data.append({i: [f_r, s_r]})
|
||||
|
||||
elif i is 'STOCHRSI_DIV':
|
||||
f_range = list(range(9, 14))
|
||||
for f_r in f_range:
|
||||
s_range = list(range(1, 5))
|
||||
for s_r in s_range:
|
||||
t_range = list(range(1, 5))
|
||||
for t_r in t_range:
|
||||
res_data.append({i: [f_r, s_r, t_r]})
|
||||
|
||||
return res_data
|
||||
|
||||
'''
|
||||
def is_divergence_v4(high, low, res_arr, date, cdl_cnt=50):
|
||||
m_idxs = [i for i, x in enumerate(res_arr) if
|
||||
res_arr[i - 1] < res_arr[i] and res_arr[i - 1] < res_arr[i - 2]]
|
||||
is_diver_long = [v for i, v in enumerate(m_idxs) if low[m_idxs[i]] < low[m_idxs[i-1]] and res_arr[m_idxs[i]] > res_arr[m_idxs[i-1]]]
|
||||
m_idxs = [i for i, x in enumerate(res_arr) if
|
||||
res_arr[i - 1] > res_arr[i] and res_arr[i - 1] > res_arr[i - 2]]
|
||||
is_diver_short = [v for i, v in enumerate(m_idxs) if high[m_idxs[i]] > high[m_idxs[i-1]] and res_arr[m_idxs[i]] < res_arr[m_idxs[i-1]]]
|
||||
|
||||
'''
|
||||
|
||||
# 변곡점 캐치 다이버전스 함수 / 고점 및 저점 활용
|
||||
def is_divergence_v2(i, high, low, res_arr, date, cdl_cnt=50):
|
||||
is_rsi_min_1 = res_arr[i - 1] < res_arr[i] and res_arr[i - 1] < res_arr[i - 2]
|
||||
is_rsi_max_1 = res_arr[i - 1] > res_arr[i] and res_arr[i - 1] > res_arr[i - 2]
|
||||
|
||||
# 상승 다이버전스
|
||||
if is_rsi_min_1: # 첫번째 저점 형성
|
||||
rsi_min_1 = res_arr[i - 1]
|
||||
low_1 = low[i - 1]
|
||||
|
||||
for s_i in range(i - 3, i - cdl_cnt, -1):
|
||||
# 두번째 저점 형성
|
||||
is_rsi_min_2 = res_arr[s_i - 1] < res_arr[s_i] and res_arr[s_i - 1] < res_arr[s_i - 2]
|
||||
|
||||
if is_rsi_min_2:
|
||||
rsi_min_2 = res_arr[s_i - 1]
|
||||
low_2 = low[s_i - 1]
|
||||
|
||||
if low_1 < low_2: # 저점 갱신
|
||||
if rsi_min_1 > rsi_min_2: # 지표 저점 상승
|
||||
# print('매수 포지션', i, '-' * 50)
|
||||
# print(date[i - 1], '=>', date[s_i - 1])
|
||||
# print(low_1, '=>', low_2)
|
||||
# print(rsi_min_1, '=>', rsi_min_2)
|
||||
# print('현재', res_arr[i-2], '>', res_arr[i - 1], '<', res_arr[i])
|
||||
# print('이전', res_arr[s_i-2], '>', res_arr[s_i - 1], '<', res_arr[s_i])
|
||||
return True
|
||||
return None
|
||||
|
||||
# 하락 다이버전스
|
||||
if is_rsi_max_1:
|
||||
rsi_max_1 = res_arr[i - 1]
|
||||
max_1 = high[i - 1]
|
||||
|
||||
for s_i in range(i - 3, i - cdl_cnt, -1):
|
||||
is_rsi_max_2 = res_arr[s_i - 1] > res_arr[s_i] and res_arr[s_i - 1] > res_arr[s_i - 2]
|
||||
|
||||
if is_rsi_max_2:
|
||||
rsi_max_2 = res_arr[s_i - 1]
|
||||
max_2 = high[s_i - 1]
|
||||
|
||||
if max_1 > max_2: # 고점갱신 갱신
|
||||
if rsi_max_1 < rsi_max_2 : # 지표 고점 하락
|
||||
# print('매도 포지션', i, '-'*50)
|
||||
# print(date[i-1], '=>', date[s_i-1])
|
||||
# print(max_1, '=>', max_2)
|
||||
# print(rsi_max_1, '=>', rsi_max_2)
|
||||
# print('현재',res_arr[i-2], '<', res_arr[i - 1], '>', res_arr[i])
|
||||
# print('이전', res_arr[s_i-2], '<', res_arr[s_i - 1], '>', res_arr[s_i])
|
||||
return False
|
||||
return None
|
||||
return None
|
||||
|
||||
# def is_divergence_v2(i, high, low, res_arr, date, cdl_cnt=50):
|
||||
# is_rsi_min_1 = res_arr[i - 1] < res_arr[i] and res_arr[i - 1] < res_arr[i - 2]
|
||||
# is_rsi_max_1 = res_arr[i - 1] > res_arr[i] and res_arr[i - 1] > res_arr[i - 2]
|
||||
#
|
||||
# if is_rsi_min_1: # 상승 다이버전스
|
||||
# rsi_min_1 = res_arr[i - 1]
|
||||
# low_1 = low[i - 1]
|
||||
#
|
||||
# for s_i in range(i - 3, i - cdl_cnt, -1):
|
||||
# # 두번째 저점 형성
|
||||
# is_rsi_min_2 = res_arr[s_i - 1] < res_arr[s_i] and res_arr[s_i - 1] < res_arr[s_i - 2]
|
||||
#
|
||||
# if is_rsi_min_2:
|
||||
# rsi_min_2 = res_arr[s_i - 1]
|
||||
# low_2 = low[s_i - 1]
|
||||
#
|
||||
# if low_1 < low_2: # 저점 갱신
|
||||
# # if rsi_min_1 > rsi_min_2 and res_arr[i - 2] > res_arr[s_i - 2]: # 지표 저점 상승
|
||||
# if rsi_min_1 > rsi_min_2: # 지표 저점 상승
|
||||
# # print('매수 포지션', i, '-' * 50)
|
||||
# # print(date[i - 1], '=>', date[s_i - 1])
|
||||
# # print(low_1, '=>', low_2)
|
||||
# # print(rsi_min_1, '=>', rsi_min_2)
|
||||
# # print('현재', res_arr[i-2], '>', res_arr[i - 1], '<', res_arr[i])
|
||||
# # print('이전', res_arr[s_i-2], '>', res_arr[s_i - 1], '<', res_arr[s_i])
|
||||
# return True
|
||||
# return None
|
||||
#
|
||||
# if is_rsi_max_1: # 하락 다이버전스
|
||||
# rsi_max_1 = res_arr[i - 1]
|
||||
# max_1 = high[i-1]
|
||||
#
|
||||
# for s_i in range(i - 3, i - cdl_cnt, -1):
|
||||
# is_rsi_max_2 = res_arr[s_i - 1] > res_arr[s_i] and res_arr[s_i - 1] > res_arr[s_i - 2]
|
||||
#
|
||||
# if is_rsi_max_2:
|
||||
# rsi_max_2 = res_arr[s_i - 1]
|
||||
# max_2 = high[s_i - 1]
|
||||
#
|
||||
# if max_1 > max_2: # 고점갱신 갱신
|
||||
# if rsi_max_1 < rsi_max_2: # 지표 고점 하락
|
||||
# # print('매도 포지션', i, '-'*50)
|
||||
# # print(date[i-1], '=>', date[s_i-1])
|
||||
# # print(max_1, '=>', max_2)
|
||||
# # print(rsi_max_1, '=>', rsi_max_2)
|
||||
# # print('현재',res_arr[i-2], '<', res_arr[i - 1], '>', res_arr[i])
|
||||
# # print('이전', res_arr[s_i-2], '<', res_arr[s_i - 1], '>', res_arr[s_i])
|
||||
# return False
|
||||
# return None
|
||||
#
|
||||
# return None
|
||||
|
||||
# 변곡점 캐치 다이버전스 함수 / 종가 활용
|
||||
def is_divergence_v3(i, close, res_arr, date, cdl_cnt=50):
|
||||
high = close
|
||||
low = close
|
||||
|
||||
is_rsi_min_1 = res_arr[i - 1] < res_arr[i] and res_arr[i - 1] < res_arr[i - 2]
|
||||
is_rsi_max_1 = res_arr[i - 1] > res_arr[i] and res_arr[i - 1] > res_arr[i - 2]
|
||||
|
||||
# 상승 다이버전스
|
||||
if is_rsi_min_1:
|
||||
rsi_min_1 = res_arr[i - 1]
|
||||
low_1 = low[i - 1]
|
||||
|
||||
for s_i in range(i - 3, i - cdl_cnt, -1):
|
||||
is_rsi_min_2 = res_arr[s_i - 1] < res_arr[s_i] and res_arr[s_i - 1] < res_arr[s_i - 2]
|
||||
|
||||
if is_rsi_min_2:
|
||||
rsi_min_2 = res_arr[s_i - 1]
|
||||
low_2 = low[s_i - 1]
|
||||
|
||||
if low_1 < low_2: # 저점 갱신
|
||||
if rsi_min_1 > rsi_min_2: # 지표 저점 상승
|
||||
return True
|
||||
|
||||
return None
|
||||
|
||||
# 하락 다이버전스
|
||||
if is_rsi_max_1:
|
||||
rsi_max_1 = res_arr[i - 1]
|
||||
max_1 = high[i - 1]
|
||||
|
||||
for s_i in range(i - 3, i - cdl_cnt, -1):
|
||||
is_rsi_max_2 = res_arr[s_i - 1] > res_arr[s_i] and res_arr[s_i - 1] > res_arr[s_i - 2]
|
||||
|
||||
if is_rsi_max_2:
|
||||
rsi_max_2 = res_arr[s_i - 1]
|
||||
max_2 = high[s_i - 1]
|
||||
|
||||
if max_1 > max_2: # 고점갱신 갱신
|
||||
if rsi_max_2 > rsi_max_1: # 지표 고점 하락
|
||||
return False
|
||||
return None
|
||||
return None
|
||||
|
||||
# get pivo percent
|
||||
def pivo(n = 1000):
|
||||
if n is 0:
|
||||
return
|
||||
|
||||
pivo_arr = [0, 1]
|
||||
res_arr = []
|
||||
while pivo_arr[len(pivo_arr)-2] + pivo_arr[len(pivo_arr)-1] <= n:
|
||||
pivo_arr.append(pivo_arr[len(pivo_arr)-2] + pivo_arr[len(pivo_arr)-1]) # for percent
|
||||
|
||||
# calculate percent
|
||||
for pivo_val in pivo_arr[2:]:
|
||||
res_arr.append(pivo_val/100)
|
||||
|
||||
return res_arr
|
||||
|
||||
|
||||
14
test.py
Normal file
14
test.py
Normal file
@@ -0,0 +1,14 @@
|
||||
import math
|
||||
from math import log10, floor
|
||||
|
||||
|
||||
def round_sig(x, sig=2):
|
||||
return round(x, sig - int(floor(log10(abs(x)))) - 1)
|
||||
|
||||
|
||||
price = 7200
|
||||
seeds = 27.86
|
||||
|
||||
cnt = seeds/price
|
||||
|
||||
print(str(cnt)[:5])
|
||||
173
test_backtrader.py
Normal file
173
test_backtrader.py
Normal file
@@ -0,0 +1,173 @@
|
||||
from __future__ import (absolute_import, division, print_function,
|
||||
unicode_literals)
|
||||
|
||||
import datetime # For datetime objects
|
||||
import os.path # To manage paths
|
||||
import sys # To find out the script name (in argv[0])
|
||||
|
||||
# Import the backtrader platform
|
||||
import backtrader as bt
|
||||
|
||||
import backtrader.analyzers as btanalyzers
|
||||
|
||||
import math
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
|
||||
# 데이터 넣기
|
||||
# 주 지표 적용
|
||||
# 보조 지표 적용
|
||||
# 지표 혼합 적용
|
||||
# 경우의 수에 따른 시뮬레이팅
|
||||
|
||||
PARAMS = (
|
||||
('maperiod', 15), # moving average period
|
||||
('period', 15), #
|
||||
('willperiod', 14), # moving average period
|
||||
('sizer', None),
|
||||
)
|
||||
|
||||
|
||||
class TestStrategy(bt.Strategy):
|
||||
params = PARAMS
|
||||
|
||||
def log(self, txt, dt=None):
|
||||
''' Logging function fot this strategy'''
|
||||
dt = dt or self.datas[0].datetime.date(0)
|
||||
print('%s, %s' % (dt.isoformat(), txt))
|
||||
|
||||
def __init__(self):
|
||||
# Keep a reference to the "close" line in the data[0] dataseries
|
||||
|
||||
self.dataclose = self.datas[0].close
|
||||
|
||||
# To keep track of pending orders and buy price/commission
|
||||
self.order = None
|
||||
# self.order = self.order_target_percent(target=0.1)
|
||||
self.buyprice = None
|
||||
self.buycomm = None
|
||||
# self.sma = bt.indicators.SimpleMovingAverage(self.datas[0], period=self.params.maperiod)
|
||||
self.sma = bt.indicators.ExponentialMovingAverage(self.datas[0], period=25)
|
||||
|
||||
|
||||
def notify_order(self, order):
|
||||
if order.status in [order.Submitted, order.Accepted]:
|
||||
# Buy/Sell order submitted/accepted to/by broker - Nothing to do
|
||||
return
|
||||
|
||||
# Check if an order has been completed
|
||||
# Attention: broker could reject order if not enougth cash
|
||||
if order.status in [order.Completed, order.Canceled, order.Margin]:
|
||||
if order.isbuy():
|
||||
self.log(
|
||||
'BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
|
||||
(order.executed.price,
|
||||
order.executed.value,
|
||||
order.executed.comm))
|
||||
|
||||
self.buyprice = order.executed.price
|
||||
self.buycomm = order.executed.comm
|
||||
else: # Sell
|
||||
self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
|
||||
(order.executed.price,
|
||||
order.executed.value,
|
||||
order.executed.comm))
|
||||
|
||||
self.bar_executed = len(self)
|
||||
|
||||
# Write down: no pending order
|
||||
self.order = None
|
||||
|
||||
def notify_trade(self, trade):
|
||||
if not trade.isclosed:
|
||||
return
|
||||
|
||||
self.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' %
|
||||
(trade.pnl, trade.pnlcomm))
|
||||
|
||||
def next(self):
|
||||
# Simply log the closing price of the series from the reference
|
||||
# Check if an order is pending ... if yes, we cannot send a 2nd one
|
||||
if self.order:
|
||||
return
|
||||
|
||||
# Check if we are in the market
|
||||
if not self.position:
|
||||
# Not yet ... we MIGHT BUY if ...
|
||||
if self.dataclose[0] > self.sma[0]:
|
||||
# BUY, BUY, BUY!!! (with all possible default parameters)
|
||||
self.log('BUY CREATE, close : %.2f, sma25 : %.2f' % (self.dataclose[0], self.sma[0]))
|
||||
# Keep track of the created order to avoid a 2nd order
|
||||
self.order = self.buy()
|
||||
else:
|
||||
|
||||
if self.dataclose[0] < self.sma[0]:
|
||||
# SELL, SELL, SELL!!! (with all possible default parameters)
|
||||
self.log('SELL CREATE, close : %.2f, sma25 : %.2f' % (self.dataclose[0], self.sma[0]))
|
||||
|
||||
# Keep track of the created order to avoid a 2nd order
|
||||
self.order = self.sell()
|
||||
|
||||
|
||||
class LongOnly(bt.Sizer):
|
||||
params = (('stake', 1),)
|
||||
|
||||
def _getsizing(self, comminfo, cash, data, isbuy):
|
||||
if isbuy:
|
||||
divide = math.floor(cash / data.close[0])
|
||||
# divide = math.floor(cash/data.open[1])
|
||||
self.p.stake = divide
|
||||
# print(self.p.stake)
|
||||
# print(math.floor(cash/data.close[0]))
|
||||
|
||||
return self.p.stake
|
||||
# Sell situation
|
||||
position = self.broker.getposition(data)
|
||||
if not position.size:
|
||||
return 0 # do not sell if nothing is open
|
||||
return self.p.stake
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
cerebro = bt.Cerebro()
|
||||
# Add a strategy
|
||||
cerebro.addstrategy(TestStrategy)
|
||||
# Add Sizer
|
||||
cerebro.addsizer(LongOnly)
|
||||
|
||||
# because it could have been called from anywhere
|
||||
modpath = os.path.dirname(os.path.abspath(sys.argv[0]))
|
||||
datapath = os.path.join(modpath, 'orcl-2014.txt')
|
||||
|
||||
# Create a Data Feed
|
||||
data = bt.feeds.YahooFinanceCSVData(
|
||||
dataname=datapath,
|
||||
# Do not pass values before this date
|
||||
# Do not pass values before this date
|
||||
# Do not pass values after this date
|
||||
reverse=False)
|
||||
|
||||
# Add the Data Feed to Cerebro
|
||||
cerebro.adddata(data)
|
||||
|
||||
# Set our desired cash start
|
||||
cerebro.broker.setcash(1000.0)
|
||||
|
||||
# In order to buy on open you may want to
|
||||
# bt.filters.BarReplayer_Open(data)
|
||||
cerebro.broker.set_coc(True)
|
||||
|
||||
# Set the commission
|
||||
cerebro.broker.setcommission(commission=0.005)
|
||||
|
||||
# Print out the starting conditions
|
||||
print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())
|
||||
|
||||
# Run over everything
|
||||
cerebro.run()
|
||||
|
||||
# Print out the final result
|
||||
print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())
|
||||
|
||||
# Plot the result
|
||||
cerebro.plot()
|
||||
159
test_backtrader_2.py
Normal file
159
test_backtrader_2.py
Normal file
@@ -0,0 +1,159 @@
|
||||
from __future__ import (absolute_import, division, print_function,
|
||||
unicode_literals)
|
||||
|
||||
import datetime # For datetime objects
|
||||
import os.path # To manage paths
|
||||
import sys # To find out the script name (in argv[0])
|
||||
|
||||
# Import the backtrader platform
|
||||
import backtrader as bt
|
||||
|
||||
|
||||
# Create a Stratey
|
||||
class TestStrategy(bt.Strategy):
|
||||
params = (
|
||||
('maperiod', 15),
|
||||
)
|
||||
|
||||
def log(self, txt, dt=None):
|
||||
''' Logging function fot this strategy'''
|
||||
dt = dt or self.datas[0].datetime.date(0)
|
||||
print('%s, %s' % (dt.isoformat(), txt))
|
||||
|
||||
def __init__(self):
|
||||
# Keep a reference to the "close" line in the data[0] dataseries
|
||||
self.dataclose = self.datas[0].close
|
||||
|
||||
# To keep track of pending orders and buy price/commission
|
||||
self.order = None
|
||||
self.buyprice = None
|
||||
self.buycomm = None
|
||||
|
||||
# Add a MovingAverageSimple indicator
|
||||
self.sma = bt.indicators.SimpleMovingAverage(
|
||||
self.datas[0], period=self.params.maperiod)
|
||||
|
||||
# Indicators for the plotting show
|
||||
bt.indicators.ExponentialMovingAverage(self.datas[0], period=25)
|
||||
bt.indicators.WeightedMovingAverage(self.datas[0], period=25,
|
||||
subplot=True)
|
||||
bt.indicators.StochasticSlow(self.datas[0])
|
||||
bt.indicators.MACDHisto(self.datas[0])
|
||||
rsi = bt.indicators.RSI(self.datas[0])
|
||||
bt.indicators.SmoothedMovingAverage(rsi, period=10)
|
||||
bt.indicators.ATR(self.datas[0], plot=False)
|
||||
|
||||
def notify_order(self, order):
|
||||
if order.status in [order.Submitted, order.Accepted]:
|
||||
# Buy/Sell order submitted/accepted to/by broker - Nothing to do
|
||||
return
|
||||
|
||||
# Check if an order has been completed
|
||||
# Attention: broker could reject order if not enough cash
|
||||
if order.status in [order.Completed]:
|
||||
if order.isbuy():
|
||||
self.log(
|
||||
'BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
|
||||
(order.executed.price,
|
||||
order.executed.value,
|
||||
order.executed.comm))
|
||||
|
||||
self.buyprice = order.executed.price
|
||||
self.buycomm = order.executed.comm
|
||||
else: # Sell
|
||||
self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
|
||||
(order.executed.price,
|
||||
order.executed.value,
|
||||
order.executed.comm))
|
||||
|
||||
self.bar_executed = len(self)
|
||||
|
||||
elif order.status in [order.Canceled, order.Margin, order.Rejected]:
|
||||
self.log('Order Canceled/Margin/Rejected')
|
||||
|
||||
# Write down: no pending order
|
||||
self.order = None
|
||||
|
||||
def notify_trade(self, trade):
|
||||
if not trade.isclosed:
|
||||
return
|
||||
|
||||
self.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' %
|
||||
(trade.pnl, trade.pnlcomm))
|
||||
|
||||
def next(self):
|
||||
# Simply log the closing price of the series from the reference
|
||||
self.log('Close, %.2f' % self.dataclose[0])
|
||||
|
||||
# Check if an order is pending ... if yes, we cannot send a 2nd one
|
||||
if self.order:
|
||||
return
|
||||
|
||||
# Check if we are in the market
|
||||
if not self.position:
|
||||
|
||||
# Not yet ... we MIGHT BUY if ...
|
||||
if self.dataclose[0] > self.sma[0]:
|
||||
|
||||
# BUY, BUY, BUY!!! (with all possible default parameters)
|
||||
self.log('BUY CREATE, %.2f' % self.dataclose[0])
|
||||
|
||||
# Keep track of the created order to avoid a 2nd order
|
||||
self.order = self.buy()
|
||||
|
||||
else:
|
||||
|
||||
if self.dataclose[0] < self.sma[0]:
|
||||
# SELL, SELL, SELL!!! (with all possible default parameters)
|
||||
self.log('SELL CREATE, %.2f' % self.dataclose[0])
|
||||
|
||||
# Keep track of the created order to avoid a 2nd order
|
||||
self.order = self.sell()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Create a cerebro entity
|
||||
cerebro = bt.Cerebro()
|
||||
|
||||
# Add a strategy
|
||||
cerebro.addstrategy(TestStrategy)
|
||||
|
||||
# Datas are in a subfolder of the samples. Need to find where the script is
|
||||
# because it could have been called from anywhere
|
||||
modpath = os.path.dirname(os.path.abspath(sys.argv[0]))
|
||||
datapath = os.path.join(modpath, 'orcl-1995-2014.txt')
|
||||
|
||||
# Create a Data Feed
|
||||
data = bt.feeds.YahooFinanceCSVData(
|
||||
dataname=datapath,
|
||||
# Do not pass values before this date
|
||||
fromdate=datetime.datetime(1000, 1, 1),
|
||||
# Do not pass values before this date
|
||||
todate=datetime.datetime(3000, 12, 31),
|
||||
# Do not pass values after this date
|
||||
reverse=False)
|
||||
|
||||
# Add the Data Feed to Cerebro
|
||||
cerebro.adddata(data)
|
||||
|
||||
# Set our desired cash start
|
||||
cerebro.broker.setcash(100000.0)
|
||||
|
||||
# Add a FixedSize sizer according to the stake
|
||||
cerebro.addsizer(bt.sizers.FixedSize, stake=10)
|
||||
|
||||
# Set the commission
|
||||
cerebro.broker.setcommission(commission=0.005)
|
||||
cerebro.broker.set_coc(True)
|
||||
|
||||
# Print out the starting conditions
|
||||
print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())
|
||||
|
||||
# Run over everything
|
||||
cerebro.run()
|
||||
|
||||
# Print out the final result
|
||||
print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())
|
||||
|
||||
# Plot the result
|
||||
# cerebro.plot()
|
||||
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