commit 849a100fa9db2596b641d20617cda8990731c91a Author: javamon Date: Sat Dec 6 22:31:19 2025 +0900 첫 번째 커밋 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d0610cf --- /dev/null +++ b/.gitignore @@ -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/ \ No newline at end of file diff --git a/app.py b/app.py new file mode 100644 index 0000000..89a0b09 --- /dev/null +++ b/app.py @@ -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) diff --git a/bot.py b/bot.py new file mode 100644 index 0000000..ba87051 --- /dev/null +++ b/bot.py @@ -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() + diff --git a/collectors/__init__.py b/collectors/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/collectors/collector.py b/collectors/collector.py new file mode 100644 index 0000000..ac8559f --- /dev/null +++ b/collectors/collector.py @@ -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) + diff --git a/collectors/crypto/__init__.py b/collectors/crypto/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/collectors/crypto/c_binance.py b/collectors/crypto/c_binance.py new file mode 100644 index 0000000..110e397 --- /dev/null +++ b/collectors/crypto/c_binance.py @@ -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 diff --git a/collectors/crypto/c_bithumb.py b/collectors/crypto/c_bithumb.py new file mode 100644 index 0000000..aaa47e3 --- /dev/null +++ b/collectors/crypto/c_bithumb.py @@ -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) diff --git a/collectors/crypto/c_upbit.py b/collectors/crypto/c_upbit.py new file mode 100644 index 0000000..2a1aee9 --- /dev/null +++ b/collectors/crypto/c_upbit.py @@ -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) diff --git a/collectors/crypto/desktop.ini b/collectors/crypto/desktop.ini new file mode 100644 index 0000000..8934518 --- /dev/null +++ b/collectors/crypto/desktop.ini @@ -0,0 +1,5 @@ +[.ShellClassInfo] +InfoTip= ¶ ˴ϴ. +IconFile=C:\Program Files\Google\Drive\googledrivesync.exe +IconIndex=16 + \ No newline at end of file diff --git a/collectors/desktop.ini b/collectors/desktop.ini new file mode 100644 index 0000000..8934518 --- /dev/null +++ b/collectors/desktop.ini @@ -0,0 +1,5 @@ +[.ShellClassInfo] +InfoTip= ¶ ˴ϴ. +IconFile=C:\Program Files\Google\Drive\googledrivesync.exe +IconIndex=16 + \ No newline at end of file diff --git a/collectors/libs/__init__.py b/collectors/libs/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/collectors/libs/desktop.ini b/collectors/libs/desktop.ini new file mode 100644 index 0000000..8934518 --- /dev/null +++ b/collectors/libs/desktop.ini @@ -0,0 +1,5 @@ +[.ShellClassInfo] +InfoTip= ¶ ˴ϴ. +IconFile=C:\Program Files\Google\Drive\googledrivesync.exe +IconIndex=16 + \ No newline at end of file diff --git a/collectors/libs/upbitpy.py b/collectors/libs/upbitpy.py new file mode 100644 index 0000000..92357ea --- /dev/null +++ b/collectors/libs/upbitpy.py @@ -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 diff --git a/config/desktop.ini b/config/desktop.ini new file mode 100644 index 0000000..8934518 --- /dev/null +++ b/config/desktop.ini @@ -0,0 +1,5 @@ +[.ShellClassInfo] +InfoTip= ¶ ˴ϴ. +IconFile=C:\Program Files\Google\Drive\googledrivesync.exe +IconIndex=16 + \ No newline at end of file diff --git a/config/ssh/desktop.ini b/config/ssh/desktop.ini new file mode 100644 index 0000000..8934518 --- /dev/null +++ b/config/ssh/desktop.ini @@ -0,0 +1,5 @@ +[.ShellClassInfo] +InfoTip= ¶ ˴ϴ. +IconFile=C:\Program Files\Google\Drive\googledrivesync.exe +IconIndex=16 + \ No newline at end of file diff --git a/config/ssh/id_rsa b/config/ssh/id_rsa new file mode 100644 index 0000000..7b9963e --- /dev/null +++ b/config/ssh/id_rsa @@ -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----- diff --git a/config/ssh/id_rsa.pub b/config/ssh/id_rsa.pub new file mode 100644 index 0000000..3575de5 --- /dev/null +++ b/config/ssh/id_rsa.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDhOQskabvrolJ7Hm7XtV8z2Tb3XxajgFCKdQT/w2gZBTjwnXT3QqH+/MUnEyV8YYXBEbqNbSyV39KofdRHTNM3G52AfSqYQDYQFLBq9KtFyOPookafeGwM6MHFAz4LXjuDLAtTJxzNfShOUnxHO8vZZULJ7aJ+OIv3fY+tjqPB1ZPlkKA9phXRBCYu4l9LBLdjejCS6tFJTUsAnV1TxQHKXh05S//XCdQXUN3OhhE5XtsAdxQfvYbOvcoEoCE538Qj7i0/MCP6zL4u0WgYCYd8cHPN9Siw4gRC9hrIOq+GKpSqMHEOaE3ntPuH8QOHaEg6vzyHN1j7OPqOR9ifanfV javamon@javamon-ui-MacBook-Pro.local diff --git a/cron.py b/cron.py new file mode 100644 index 0000000..72f24cd --- /dev/null +++ b/cron.py @@ -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() diff --git a/cron.sh b/cron.sh new file mode 100644 index 0000000..c74d5ba --- /dev/null +++ b/cron.sh @@ -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) diff --git a/db.py b/db.py new file mode 100644 index 0000000..f6145ab --- /dev/null +++ b/db.py @@ -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) diff --git a/desktop.ini b/desktop.ini new file mode 100644 index 0000000..8934518 --- /dev/null +++ b/desktop.ini @@ -0,0 +1,5 @@ +[.ShellClassInfo] +InfoTip= ¶ ˴ϴ. +IconFile=C:\Program Files\Google\Drive\googledrivesync.exe +IconIndex=16 + \ No newline at end of file diff --git a/indicator_util.py b/indicator_util.py new file mode 100644 index 0000000..5014e5e --- /dev/null +++ b/indicator_util.py @@ -0,0 +1,1094 @@ +from pyti import chande_momentum_oscillator +import talib, numpy, math +from signal_helper import * + +class CandlePatterns: + # Long Patterns + _positive_patterns = [ + 'CDL3STARSINSOUTH', # Three Stars In The South + 'CDL3WHITESOLDIERS', # long + 'CDLCONCEALBABYSWALL', # long - no data + # 'CDLDRAGONFLYDOJI', # long 빈도 높음 + 'CDLLADDERBOTTOM', # long + 'CDLMORNINGDOJISTAR', # long + 'CDLMORNINGSTAR', # long + 'CDLADVANCEBLOCK', # long + 'CDLHOMINGPIGEON', # long + # 'CDLINVERTEDHAMMER', # long # 빈도 높음 + # 'CDLHAMMER', # 빈도 높음 + # 'CDLTAKURI', # 빈도 높음 + # 'CDLHANGINGMAN', # 빈도 높음 + ] + + # Short Patterns + _negative_patterns = [ + 'CDLEVENINGDOJISTAR', # short + 'CDL2CROWS', # short + 'CDL3BLACKCROWS', # short + 'CDLDARKCLOUDCOVER', # short + 'CDLEVENINGDOJISTAR', # short + 'CDLEVENINGSTAR', # short + 'CDLGRAVESTONEDOJI', # short 빈도 높음 + 'CDLIDENTICAL3CROWS', # short + 'CDLINNECK', # short + 'CDLONNECK', # short + 'CDLSHOOTINGSTAR', # short + 'CDLUPSIDEGAP2CROWS', # short + # 'CDLMATCHINGLOW', # 6:4 short + ] + + # 중립 패턴 + _fence_patterns = \ + [ + 'CDL3INSIDE', + 'CDL3LINESTRIKE', + 'CDL3OUTSIDE', + 'CDLABANDONEDBABY', + 'CDLBELTHOLD', # 샅바형 + 'CDLBREAKAWAY', + 'CDLCLOSINGMARUBOZU', + 'CDLCOUNTERATTACK', + 'CDLCONCEALBABYSWALL', + 'CDLENGULFING', + 'CDLGAPSIDESIDEWHITE', + 'CDLHARAMI', + 'CDLHARAMICROSS', + 'CDLHIKKAKEMOD', + 'CDLKICKING', + 'CDLKICKINGBYLENGTH', + 'CDLMATHOLD', + 'CDLPIERCING', + 'CDLRISEFALL3METHODS', + 'CDLSEPARATINGLINES', + 'CDLSTALLEDPATTERN', + 'CDLTASUKIGAP', + 'CDLTRISTAR', + 'CDLUNIQUE3RIVER', + 'CDLXSIDEGAP3METHODS', + 'CDLSTICKSANDWICH', + # 'CDLHIKKAKE', # 높은 빈도 + # 'CDLHIGHWAVE', # 꼬리나 머리털이 길때 + # 'CDLLONGLEGGEDDOJI', # Long Legged Doji + # 'CDLLONGLINE', # Long Line Candle + # 'CDLMARUBOZU', # Marubozu + # 'CDLRICKSHAWMAN ', # 그냥 도지임 + # 'CDLSHORTLINE', # Short Line Candle 5:5 + # 'CDLTHRUSTING', # 지속형 + # 'CDLSPINNINGTOP', # 그냥 도지임 + ] + + def get_fence_patterns(self): + return self._fence_patterns + + def get_trade_patterns(self): + return self._negative_patterns + self._positive_patterns + + def get_long_patterns(self): + return self._positive_patterns + + def get_short_patterns(self): + return self._negative_patterns + + def get_all_patterns(self): + return self._positive_patterns + self._fence_patterns + self._negative_patterns + + +class Signal: + def get_signal_by_indicators(self, t_data, indicators): + # init + self.data = t_data + self.use_indicators = indicators + + data_columns_init(self.data) + + # signal logic + if 'Date' in dir(self.data): + date = self.data.Date + else: + date = self.data.Volume + + close = self.data.Close + low = self.data.Low + high = self.data.High + open = self.data.Open + volume = self.data.Volume + + self.data_len = len(close) - 1 + + data = [list()] * len(self.data.Close) + # data = np.array([list()] * len(self.data.Close)) + + for p in self.use_indicators: + indicator = None + values = None + f = None + + indicator, values = list(p.items())[0] + + if indicator != 'DI' and hasattr(talib, indicator): + f = getattr(talib, indicator) + + if indicator == 'CDLPTN': + + for i in range(0, len(close)): + t_d = [] + + if self.data.Candle_pattern[i] is None: + continue + + pattern, value = list(self.data.Candle_pattern[i].items())[0] + + if pattern in self._fence_patterns: # 중립 패턴 + t_d.append(is_trade_fence_pattern(pattern, value)) + elif pattern in self._positive_patterns: # 매수 패턴 + t_d.append(True) + elif pattern in self._negative_patterns: # 매도 패턴 + t_d.append(False) + + data[i] = data[i] + t_d + + # 보조지표 추가 시작 + + elif indicator == 'STOCH': # 주도 지표 + slowk, slowd = f(high, low, close, fastk_period=values[0], + slowk_period=values[1], slowd_period=values[2]) + + start = 1 + numpy.isnan(slowk).sum() + is_trade = None + + for i in range(start, len(slowk)): + t_d = [] + + if crossover(slowk[i], slowk[i - 1], slowd[i], slowd[i - 1]) and slowd[i] < 50: + is_trade = True + elif crossover(slowd[i], slowd[i - 1], slowk[i], slowk[i - 1]) and slowd[i] > 50: + is_trade = False + + t_d.append(is_trade) + + data[i] = data[i] + t_d + + elif indicator == 'ADX' or indicator == 'ADXR': # 베이스 지표 + res_arr = f(high, low, close, timeperiod=values) + start = 1 + numpy.isnan(res_arr).sum() + + for i in range(start, len(res_arr)): + t_d = [] + + if res_arr[i] < 13: + t_d.append(True) + elif res_arr[i] > 45: + t_d.append(False) + + data[i] = data[i] + t_d + + elif indicator == 'DI': # 주도 지표 + res_arr_plus = talib.PLUS_DI(high, low, close, timeperiod=values) + res_arr_minus = talib.MINUS_DI(high, low, close, timeperiod=values) + + start = 1 + numpy.isnan(res_arr_plus).sum() + is_trade = None + + for i in range(start, len(res_arr_plus)): + t_d = [] + + if crossover(res_arr_plus[i], res_arr_plus[i - 1], res_arr_minus[i], res_arr_minus[i - 1]): + is_trade = True + elif crossover(res_arr_minus[i], res_arr_minus[i - 1], res_arr_plus[i], res_arr_plus[i - 1]): + is_trade = False + + t_d.append(is_trade) + + data[i] = data[i] + t_d + + elif indicator == 'APO': # 주도 지표 + res_arr = f(close, fastperiod=values[0], slowperiod=values[1], matype=0) + start = 1 + numpy.isnan(res_arr).sum() + is_trade = None + + for i in range(start, len(res_arr)): + t_d = [] + + if res_arr[i] > 0: + is_trade = True + elif res_arr[i] < 0: + is_trade = False + + t_d.append(is_trade) + + data[i] = data[i] + t_d + + if indicator == 'AROON': # 주도 지표 => 추격 + aroondown, aroonup = f(high, low, timeperiod=values) + start = 1 + numpy.isnan(aroondown).sum() + is_trade = None + + for i in range(start, len(aroondown)): + t_d = [] + + if crossover(aroonup[i], aroonup[i - 1], aroondown[i], aroondown[i - 1]): + is_trade = True + elif crossover(aroondown[i], aroondown[i - 1], aroonup[i], aroonup[i - 1]): + is_trade = False + + t_d.append(is_trade) + + data[i] = data[i] + t_d + + elif indicator == 'AROONOSC': # 베이스 지표 - 추세 + res_arr = f(high, low, timeperiod=values) + start = 1 + numpy.isnan(res_arr).sum() + is_trade = None + + for i in range(start, len(res_arr)): + t_d = [] + + # 0 초과 상승추세, 0 미만 하락 추세 + if res_arr[i] > 0 and res_arr[i - 1] < 0: + is_trade = True + elif res_arr[i] < 0 and res_arr[i - 1] > 0: + is_trade = False + + t_d.append(is_trade) + + # print(date[i], res_arr[i]) + + data[i] = data[i] + t_d + + elif indicator == 'BOP': # 베이스 지표 + res_arr = f(open, high, low, close) + start = 1 + numpy.isnan(res_arr).sum() + is_trade = None + + for i in range(start, len(res_arr)): + t_d = [] + + if res_arr[i] > 0 and res_arr[i - 1] < 0: + is_trade = True + elif res_arr[i] < 0 and res_arr[i - 1] > 0: + is_trade = False + + t_d.append(is_trade) + + data[i] = data[i] + t_d + + elif indicator == 'CCI': # 베이스 지표 / 추세 + res_arr = f(close, close, close, timeperiod=values) + # res_arr = f(high, low, close, timeperiod=values) + start = 1 + numpy.isnan(res_arr).sum() + is_trade = None + + for i in range(start, len(res_arr)): + t_d = [] + + # 베이스 + if res_arr[i] < -100: # 과매도 -100 / 과매수 +100 + is_trade = True + elif res_arr[i] > 100: + is_trade = False + + t_d.append(is_trade) + + data[i] = data[i] + t_d + + elif indicator == 'CMO': # 베이스 지표 / 추세 => 수치 불일치 + res_arr = chande_momentum_oscillator.chande_momentum_oscillator(close.values, values) + + start = 1 + numpy.isnan(res_arr).sum() + is_trade = None + + for i in range(start, len(res_arr)): + t_d = [] + + if res_arr[i] > 0: # 과매도 -100 / 과매수 +100 + is_trade = True + elif res_arr[i] < 0: + is_trade = False + + t_d.append(is_trade) + + data[i] = data[i] + t_d + + elif indicator == 'MACD': # 주도 지표 + macd, macdsignal, macdhist = f(close, + fastperiod=values[0], + slowperiod=values[1], + signalperiod=values[2]) + + start = 1 + numpy.isnan(macd).sum() + is_trade = None + + for i in range(start, len(macd)): + t_d = [] + + if crossover(macd[i], macd[i - 1], macdsignal[i], macdsignal[i - 1]): + is_trade = True + elif crossover(macdsignal[i], macdsignal[i - 1], macd[i], macd[i - 1]): + is_trade = False + + t_d.append(is_trade) + + data[i] = data[i] + t_d + + elif indicator == 'MACDFIX': # 주도 지표 + macd, macdsignal, macdhist = f(close, signalperiod=values) + start = 1 + numpy.isnan(macd).sum() + is_trade = None + + for i in range(start, len(macd)): + t_d = [] + + if crossover(macd[i], macd[i - 1], macdsignal[i], macdsignal[i - 1]): + is_trade = True + elif crossover(macdsignal[i], macdsignal[i - 1], macd[i], macd[i - 1]): + is_trade = False + + t_d.append(is_trade) + + data[i] = data[i] + t_d + + elif indicator == 'MFI': # 베이스 지표 / 역추세 + res_arr = f(close, close, close, volume, timeperiod=values) + start = 1 + numpy.isnan(res_arr).sum() + is_trade = None + + for i in range(start, len(res_arr)): + t_d = [] + + # 베이스 + if res_arr[i] > 20 and res_arr[i - 1] < 20: + is_trade = True + elif res_arr[i] > 80 and res_arr[i - 1] < 80: + is_trade = False + + t_d.append(is_trade) + + data[i] = data[i] + t_d + + elif indicator == 'MOM': # 베이스 지표 / 추세 + res_arr = f(close, timeperiod=values) + start = 1 + numpy.isnan(res_arr).sum() + is_trade = None + + for i in range(start, len(res_arr)): + t_d = [] + + # 베이스 + if res_arr[i] > 0 and res_arr[i - 1] < 0: + is_trade = True + elif res_arr[i] < 0 and res_arr[i - 1] > 0: + is_trade = False + + t_d.append(is_trade) + + data[i] = data[i] + t_d + + elif indicator == 'PPO': # 주도 지표 + ppo = f(close, fastperiod=values[0], slowperiod=values[1], matype=1) + ppo_slow = moving_average(ppo, values[2]) + + start = 1 + numpy.isnan(ppo_slow).sum() + + is_trade = None + for i in range(start, len(ppo)): + t_d = [] + + if crossover(ppo[i], ppo[i - 1], ppo_slow[i], ppo_slow[i - 1]): + is_trade = True + elif crossover(ppo_slow[i], ppo_slow[i - 1], ppo[i], ppo[i - 1]): + is_trade = False + + t_d.append(is_trade) + + data[i] = data[i] + t_d + + elif indicator == 'ROC': # 베이스 지표 / 추세 + res_arr = f(close, timeperiod=values) + start = 1 + numpy.isnan(res_arr).sum() + is_trade = None + + for i in range(start, len(res_arr)): + t_d = [] + + if res_arr[i] > 0 and res_arr[i - 1] < 0: + is_trade = True + elif res_arr[i] < 0 and res_arr[i - 1] > 0: + is_trade = False + + t_d.append(is_trade) + + data[i] = data[i] + t_d + + elif indicator == 'ROCP' or indicator == 'ROCR' or indicator == 'ROCR100': # 베이스 지표 / 추세 + res_arr = f(close, timeperiod=values) + start = 1 + numpy.isnan(res_arr).sum() + + for i in range(start, len(res_arr)): + t_d = [] + + # 베이스 + if res_arr[i] > 0: + t_d.append(True) + elif res_arr[i] < 0: + t_d.append(False) + + data[i] = data[i] + t_d + + elif indicator == 'STOCHF': # 주도 지표 + fastk, fastd = f(high, low, close, fastk_period=values[0], fastd_period=values[1], fastd_matype=0) + start = 1 + numpy.isnan(fastk).sum() + is_trade = None + + for i in range(start, len(fastk)): + t_d = [] + + # 높은 거래 빈도로 인해 매매조건 추가 + if fastd[i] < 40 and crossover(fastk[i], fastk[i - 1], fastd[i], fastd[i - 1]): + is_trade = True + elif fastd[i] > 60 and crossover(fastd[i], fastd[i - 1], fastk[i], fastk[i - 1]): + is_trade = False + + t_d.append(is_trade) + + data[i] = data[i] + t_d + + elif indicator == 'STOCHRSI': # 주도 지표 + rsi_k, rsi_d = stoch_rsi(close, values[0], values[1], values[2]) + + start = 1 + numpy.isnan(rsi_d).sum() + is_trade = None + + for i in range(start, len(rsi_d)): + t_d = [] + + if rsi_k[i] < 25 and crossover(rsi_k[i], rsi_k[i - 1], rsi_d[i], rsi_d[i - 1]): + is_trade = True + elif rsi_k[i] > 75 and crossover(rsi_d[i], rsi_d[i - 1], rsi_k[i], rsi_k[i - 1]): + is_trade = False + + t_d.append(is_trade) + + data[i] = data[i] + t_d + + elif indicator == 'TRIX': # 베이스 지표 / 역추세 => 매수/매도 시점 괜찮다 + trix = f(close, timeperiod=values[0]) + trix_signal = moving_average(trix, values[1]) + + start = 1 + numpy.isnan(trix_signal).sum() + is_trade = None + + for i in range(start, len(trix_signal)): + t_d = [] + + if crossover(trix[i], trix[i - 1], trix_signal[i], trix_signal[i - 1]): + is_trade = True + elif crossover(trix_signal[i], trix_signal[i - 1], trix[i], trix[i - 1]): + is_trade = False + + t_d.append(is_trade) + + data[i] = data[i] + t_d + + elif indicator == 'ULTOSC': # 주도 지표 + res_arr = f(high, low, close, timeperiod1=values[0], timeperiod2=values[1], timeperiod3=values[2]) + start = 1 + numpy.isnan(res_arr).sum() + + is_trade = None + for i in range(start, len(res_arr)): + t_d = [] + + if res_arr[i] < 30: + is_trade = True + elif res_arr[i] > 70: + is_trade = False + + t_d.append(is_trade) + + data[i] = data[i] + t_d + + elif indicator == 'WILLR': # 베이스 지표 => 강세장에서 효과를 발휘 + res_arr = f(high, low, close, timeperiod=values) + start = 1 + numpy.isnan(res_arr).sum() + is_trade = None + + for i in range(start, len(res_arr)): + t_d = [] + + # print(date[i], res_arr[i]) + if res_arr[i] > -20: + is_trade = True + elif res_arr[i] < -80: + is_trade = False + + t_d.append(is_trade) + + data[i] = data[i] + t_d + + elif indicator == 'BBANDS': # 베이스 지표 / 역추세 + upperband, middleband, lowerband = f(close, timeperiod=values[0], nbdevup=values[1], nbdevdn=values[1], + matype=0) + + start = 1 + numpy.isnan(upperband).sum() + is_trade = None + + for i in range(start, len(upperband)): + t_d = [] + + if high[i] > upperband[i] and upperband[i] > upperband[i - 1] and low[i] > middleband[i]: + is_trade = True + elif low[i] < lowerband[i] and lowerband[i] < lowerband[i - 1] and high[i] < middleband[i]: + is_trade = False + + t_d.append(is_trade) + + data[i] = data[i] + t_d + + elif indicator == 'EMA' or \ + indicator == 'DEMA' or \ + indicator == 'MA' or \ + indicator == 'SMA': # 주도 지표 / 추세 + + res_arr = f(close, timeperiod=values) + start = 1 + numpy.isnan(res_arr).sum() + is_trade = None + + for i in range(start, len(res_arr)): + t_d = [] + + if close[i] > res_arr[i]: + is_trade = True + elif close[i] < res_arr[i]: + is_trade = False + + t_d.append(is_trade) + + data[i] = data[i] + t_d + + elif indicator == 'MAMA': # 주도 지표 / 추세 + mama, fama = f(close, fastlimit=values[0], slowlimit=values[1]) + + start = 1 + numpy.isnan(mama).sum() + is_trade = None + + for i in range(start, len(mama)): + t_d = [] + + if mama[i] > fama[i]: + is_trade = True + elif mama[i] < fama[i]: + is_trade = False + + t_d.append(is_trade) + + data[i] = data[i] + t_d + + elif indicator == 'MIDPOINT': # 주도 지표 => 추세 + res_arr = f(close, timeperiod=values) + start = 1 + numpy.isnan(res_arr).sum() + + for i in range(start, len(res_arr)): + t_d = [] + + if close[i] > res_arr[i]: + t_d.append(True) + elif close[i] < res_arr[i]: + t_d.append(False) + + data[i] = data[i] + t_d + + elif indicator == 'MIDPRICE': # 주도 지표 => 추세 + res_arr = f(high, low, timeperiod=values) + start = 1 + numpy.isnan(res_arr).sum() + + for i in range(start, len(res_arr)): + t_d = [] + + if close[i] > res_arr[i]: + t_d.append(True) + elif close[i] < res_arr[i]: + t_d.append(False) + + data[i] = data[i] + t_d + + elif indicator == 'SAR': # 주도 지표 => 역추세 + res_arr = f(high, low, acceleration=values[0], maximum=values[1]) + start = 1 + numpy.isnan(res_arr).sum() + is_trade = None + + for i in range(start, len(res_arr)): + t_d = [] + + if close[i] > res_arr[i] and close[i - 1] < res_arr[i - 1]: + is_trade = True + elif res_arr[i] > close[i] and res_arr[i - 1] < close[i - 1]: + is_trade = False + + t_d.append(is_trade) + + data[i] = data[i] + t_d + + elif indicator == 'T3': # indicator is 'WMA': # 주도 지표 => 추세 + long = f(close, timeperiod=values[0]) + short = f(close, timeperiod=values[1]) + + start = 1 + numpy.isnan(long).sum() + is_trade = None + + for i in range(start, len(long)): + t_d = [] + + if short[i] > long[i]: + is_trade = True + elif short[i] < long[i]: + is_trade = False + + t_d.append(is_trade) + + data[i] = data[i] + t_d + + elif indicator == 'TRIMA' or indicator == 'WMA': # 주도 지표 => 추세 + res_arr = f(close, timeperiod=values) + + start = 1 + numpy.isnan(res_arr).sum() + is_trade = None + + for i in range(start, len(res_arr)): + t_d = [] + + if close[i] > res_arr[i]: + is_trade = True + elif close[i] < res_arr[i]: + is_trade = False + + t_d.append(is_trade) + + data[i] = data[i] + t_d + + elif indicator == 'AD': # 베이스 지표 - 거래량 + res_arr = f(high, low, close, volume) + start = 1 + numpy.isnan(res_arr).sum() + is_trade = None + + for i in range(start, len(res_arr)): + t_d = [] + + # 가격-거래량 반비례 => 추세 반전 + if close[i] > close[i - 1] and res_arr[i] < res_arr[i - 1]: + is_trade = False + elif close[i] < close[i - 1] and res_arr[i] > res_arr[i - 1]: + is_trade = False + + t_d.append(is_trade) + + data[i] = data[i] + t_d + + + elif indicator == 'ADOSC_DIV': # 베이스 지표 - 거래량 + res_arr = talib.ADOSC(high, low, close, volume, fastperiod=values[0], slowperiod=values[1]) + start = 1 + numpy.isnan(res_arr).sum() + is_trade = None + + for i in range(start, len(res_arr)): + t_d = [] + + is_divergence = is_divergence_v2(i, high, low, res_arr, date) + + if is_divergence is True: + is_trade = True + elif is_divergence is False: + is_trade = False + + t_d.append(is_trade) + + data[i] = data[i] + t_d + + elif indicator == 'DMI': # 주도 지표 - 추세(추매) + adx = talib.ADX(high, low, close, timeperiod=values) + plus_di = talib.PLUS_DI(high, low, close, timeperiod=values) + minus_di = talib.MINUS_DI(high, low, close, timeperiod=values) + + start = 1 + numpy.isnan(adx).sum() + is_trade = None + + for i in range(start, len(adx)): + t_d = [] + + if adx[i] > adx[i - 1]: + if plus_di[i] > minus_di[i] and adx[i] > minus_di[i]: + is_trade = True + elif minus_di[i] > minus_di[i - 1]: + is_trade = False + + t_d.append(is_trade) + + data[i] = data[i] + t_d + + elif indicator == 'OBV': # 베이스 지표 / 역추세 + res_arr = f(close, volume) + cdl_cnt = 20 + start = 1 + numpy.isnan(res_arr).sum() + + if start < cdl_cnt: + start = cdl_cnt + + for i in range(start, len(res_arr)): + t_d = [] + + # 고가 갱신 시 매도 / 저가 갱신 시 매수 + if res_arr[i] > max(res_arr[i - cdl_cnt:i]): + t_d.append(False) + elif res_arr[i] < min(res_arr[i - cdl_cnt:i]): + t_d.append(True) + + data[i] = data[i] + t_d + + # 보조지표 추가 끝 + + elif indicator == 'RSI': # 베이스 지표 + res_arr = f(close, values) + start = 1 + numpy.isnan(res_arr).sum() + is_trade = None + + for i in range(start, len(res_arr)): + t_d = [] + + if res_arr[i] < 30: + is_trade = True + elif res_arr[i] > 70: + is_trade = False + + t_d.append(is_trade) + + data[i] = data[i] + t_d + + elif indicator == 'RSI_DIV': # 주도 지표 / 역추세 - 다이버전스 + f = getattr(talib, 'RSI') + res_arr = f(close, values) + + cdl_cnt = 100 + start = 1 + numpy.isnan(res_arr).sum() + is_trade = None + + if start < cdl_cnt: + start = cdl_cnt + + for i in range(start, len(res_arr)): + t_d = [] + + is_divergence = is_divergence_v2(i, high, low, res_arr, date) + if is_divergence is True and res_arr[i - 1] < 50: + is_trade = True + elif is_divergence is False and res_arr[i - 1] > 50: + is_trade = False + + t_d.append(is_trade) + + data[i] = data[i] + t_d + + if indicator == 'OBV_DIV': + f = getattr(talib, 'OBV') + res_arr = f(close, volume) + + start = 1 + numpy.isnan(res_arr).sum() + + is_trade = None + + for i in range(start, len(res_arr)): + t_d = [] + i = i - 1 + + is_divergence = is_divergence_v2(i, high, low, res_arr, date) + + if is_divergence is True: + is_trade = True + elif is_divergence is False: + is_trade = False + + t_d.append(is_trade) + + data[i] = data[i] + t_d + + if indicator == 'WILLR_DIV': + f = getattr(talib, 'WILLR') + res_arr = f(high, low, close, timeperiod=values) + + start = 1 + numpy.isnan(res_arr).sum() + + is_trade = None + + for i in range(start, len(res_arr)): + t_d = [] + i = i - 1 + + is_divergence = is_divergence_v2(i, high, low, res_arr, date) + + if is_divergence is True and res_arr[i - 1] < -50: + is_trade = True + elif is_divergence is False and res_arr[i - 1] > -50: + is_trade = False + + t_d.append(is_trade) + + data[i] = data[i] + t_d + + if indicator == 'ADX_DIV': + f = getattr(talib, 'ADX') + res_arr = f(high, low, close, timeperiod=values) + + start = 1 + numpy.isnan(res_arr).sum() + + is_trade = None + + for i in range(start, len(res_arr)): + t_d = [] + i = i - 1 + + is_divergence = is_divergence_v2(i, high, low, res_arr, date) + + if is_divergence is True: + is_trade = True + elif is_divergence is False: + is_trade = False + + t_d.append(is_trade) + + data[i] = data[i] + t_d + + if indicator == 'BOP_DIV': + f = getattr(talib, 'BOP') + res_arr = f(open, high, low, close) + + start = 1 + numpy.isnan(res_arr).sum() + + is_trade = None + + for i in range(start, len(res_arr)): + t_d = [] + i = i - 1 + + is_divergence = is_divergence_v2(i, high, low, res_arr, date) + + if is_divergence is True: + is_trade = True + elif is_divergence is False: + is_trade = False + + t_d.append(is_trade) + + data[i] = data[i] + t_d + + if indicator == 'CCI_DIV': + f = getattr(talib, 'CCI') + res_arr = f(high, low, close, timeperiod=values) + + start = 1 + numpy.isnan(res_arr).sum() + + is_trade = None + + for i in range(start, len(res_arr)): + t_d = [] + i = i - 1 + + is_divergence = is_divergence_v2(i, high, low, res_arr, date) + + if is_divergence is True and res_arr[i - 1] < -100: + is_trade = True + elif is_divergence is False and res_arr[i - 1] > 100: + is_trade = False + + t_d.append(is_trade) + + data[i] = data[i] + t_d + + if indicator == 'MFI_DIV': + f = getattr(talib, 'MFI') + res_arr = f(close, close, close, volume, timeperiod=values) + # res_arr = f(high, low, close, volume, timeperiod=values) + + start = 1 + numpy.isnan(res_arr).sum() + + is_trade = None + + for i in range(start, len(res_arr)): + t_d = [] + i = i - 1 + + is_divergence = is_divergence_v2(i, high, low, res_arr, date) + # is_divergence = is_divergence_v3(i, close, res_arr, date) + + if is_divergence is True and res_arr[i - 1]: + is_trade = True + elif is_divergence is False and res_arr[i - 1]: + is_trade = False + + t_d.append(is_trade) + + data[i] = data[i] + t_d + + ''' CMO 지표 수치 불일치 ''' + if indicator == 'CMO_DIV': + # res_arr = chande_momentum_oscillator.chande_momentum_oscillator(close.values, 9) + + f = getattr(talib, 'CMO') + res_arr = f(close, timeperiod=9) + + start = 1 + numpy.isnan(res_arr).sum() + + is_trade = None + + for i in range(start, len(res_arr)): + t_d = [] + i = i - 1 + + is_divergence = is_divergence_v2(i, high, low, res_arr, date) + + if is_divergence is True and res_arr[i - 1] < 0: + is_trade = True + elif is_divergence is False and res_arr[i - 1] > 0: + is_trade = False + + t_d.append(is_trade) + + data[i] = data[i] + t_d + + if indicator == 'MOM_DIV': + f = getattr(talib, 'MOM') + res_arr = f(close, timeperiod=values) + + start = 1 + numpy.isnan(res_arr).sum() + + is_trade = None + + for i in range(start, len(res_arr)): + t_d = [] + i = i - 1 + + is_divergence = is_divergence_v2(i, high, low, res_arr, date) + + if is_divergence is True: # and res_arr[i-1] < 0: + is_trade = True + elif is_divergence is False: # and res_arr[i-1] > 0: + is_trade = False + + t_d.append(is_trade) + + data[i] = data[i] + t_d + + if indicator == 'ROC_DIV': + f = getattr(talib, 'ROC') + res_arr = f(close, timeperiod=values) + + start = 1 + numpy.isnan(res_arr).sum() + + is_trade = None + + for i in range(start, len(res_arr)): + t_d = [] + i = i - 1 + + is_divergence = is_divergence_v2(i, high, low, res_arr, date) + + if is_divergence is True: # and round(res_arr[i-1]) < 0: + is_trade = True + elif is_divergence is False: # and round(res_arr[i-1]) > 0: + is_trade = False + + t_d.append(is_trade) + + data[i] = data[i] + t_d + + if indicator == 'STOCH_DIV': + f = getattr(talib, 'STOCH') + # res_arr, slowd = f(high, low, close, + slowk, res_arr = f(high, low, close, + fastk_period=values[0], + slowk_period=values[1], + slowd_period=values[2]) + + start = 1 + numpy.isnan(res_arr).sum() + + is_trade = None + for i in range(start, len(res_arr)): + t_d = [] + i = i - 1 + + is_divergence = is_divergence_v2(i, high, low, res_arr, date) + + if is_divergence is True: # and round(res_arr[i-1]) < 0: + is_trade = True + elif is_divergence is False: # and round(res_arr[i-1]) > 0: + is_trade = False + + t_d.append(is_trade) + + data[i] = data[i] + t_d + + if indicator == 'STOCHRSI_DIV': + rsi_k, res_arr = stoch_rsi(close, values[0], values[1], values[2]) + + start = 1 + numpy.isnan(res_arr).sum() + + is_trade = None + + for i in range(start, len(res_arr)): + t_d = [] + i = i - 1 + + is_divergence = is_divergence_v3(i, close, res_arr, date) + if is_divergence is True: + is_trade = True + elif is_divergence is False: + is_trade = False + + t_d.append(is_trade) + + data[i] = data[i] + t_d + + elif indicator == 'ULTOSC_DIV': # 주도 지표 + res_arr = talib.ULTOSC(high, low, close, timeperiod1=values[0], timeperiod2=values[1], + timeperiod3=values[2]) + start = 1 + numpy.isnan(res_arr).sum() + is_trade = None + + for i in range(start, len(res_arr)): + t_d = [] + + is_divergence = is_divergence_v2(i, high, low, res_arr, date) + if is_divergence is True: # and round(res_arr[i-1]) < 0: + is_trade = True + elif is_divergence is False: # and round(res_arr[i-1]) > 0: + is_trade = False + + t_d.append(is_trade) + + data[i] = data[i] + t_d + + elif indicator == 'HEI': # heikenashi + # df = pd.DataFrame( + # {'Open': open, 'High': high, 'Low': low, 'Close': close, 'Volume': volume}, + # ).copy() + # res_arr = heikenashi(df) + + df = pd.DataFrame( + {'open': open, 'high': high, 'low': low, 'close': close, 'volume': volume}, + ).copy() + res_arr = heikin_ashi(df) + + is_trade = None + + for i in range(1, len(res_arr)): + + t_d = [] + + if res_arr.loc[i]['close'] > res_arr.loc[i]['open'] and \ + res_arr.loc[i - 1]['open'] > res_arr.loc[i - 1]['close']: + is_trade = True + elif res_arr.loc[i]['open'] > res_arr.loc[i]['close'] and \ + res_arr.loc[i - 1]['close'] > res_arr.loc[i - 1]['open']: + is_trade = False + + t_d.append(is_trade) + + data[i] = data[i] + t_d + + return data diff --git a/lib/desktop.ini b/lib/desktop.ini new file mode 100644 index 0000000..8934518 --- /dev/null +++ b/lib/desktop.ini @@ -0,0 +1,5 @@ +[.ShellClassInfo] +InfoTip= ¶ ˴ϴ. +IconFile=C:\Program Files\Google\Drive\googledrivesync.exe +IconIndex=16 + \ No newline at end of file diff --git a/lib/pybinancefutures.py b/lib/pybinancefutures.py new file mode 100644 index 0000000..25f8aa1 --- /dev/null +++ b/lib/pybinancefutures.py @@ -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) == '': + 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) == '': + 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) == '': + 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) == '': + 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) diff --git a/mail.py b/mail.py new file mode 100644 index 0000000..199e51b --- /dev/null +++ b/mail.py @@ -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 diff --git a/print.py b/print.py new file mode 100644 index 0000000..d47b9bb --- /dev/null +++ b/print.py @@ -0,0 +1,3 @@ +import datetime + +print('this is test print file', datetime.datetime.now()) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..2aa8809 --- /dev/null +++ b/requirements.txt @@ -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 diff --git a/signal_helper.py b/signal_helper.py new file mode 100644 index 0000000..bfd104d --- /dev/null +++ b/signal_helper.py @@ -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 + + diff --git a/test.py b/test.py new file mode 100644 index 0000000..bde2c40 --- /dev/null +++ b/test.py @@ -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]) diff --git a/test_backtrader.py b/test_backtrader.py new file mode 100644 index 0000000..8421bfb --- /dev/null +++ b/test_backtrader.py @@ -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() \ No newline at end of file diff --git a/test_backtrader_2.py b/test_backtrader_2.py new file mode 100644 index 0000000..f8dc479 --- /dev/null +++ b/test_backtrader_2.py @@ -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() \ No newline at end of file diff --git a/trader.py b/trader.py new file mode 100644 index 0000000..ded4d4f --- /dev/null +++ b/trader.py @@ -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() +