# -*- 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()