class TopTrader(QMainWindow, ui): def __init__(self): QMainWindow.__init__(self) # Application Configuration # UI self.setupUi(self) # Font(ko) self.set_ko_font() # Logger self.logger = TTlog(logger_name="TTRealCondi").logger # DB self.mongo = MongoClient() self.db = self.mongo.TopTrader # Slack #self.slack = Slacker(cfg_mgr.get_slack_token()) # Kiwoom self.kw = Kiwoom() self.login() cfg_mgr.stock_info = self.kw.get_stock_basic_info() # app main self.main() def login(self): err_code = self.kw.login() if err_code != 0: self.logger.error("Login Fail") exit(-1) self.logger.info("Login success") def set_ko_font(self): # 그래프에서 마이너스 폰트 깨지는 문제에 대한 대처 mpl.rcParams['axes.unicode_minus'] = False #path = 'c:/Windows/Fonts/D2Coding-Ver1.3-20171129.ttc' path = 'c:/Windows/Fonts/gulim.ttc' font_name = fm.FontProperties(fname=path).get_name() plt.rcParams["font.family"] = font_name def main(self): print("Start Application")
class TopTrader(QMainWindow, ui): def __init__(self): QMainWindow.__init__(self) # Application Configuration # UI self.setupUi(self) # Font(ko) self.set_ko_font() # Logger self.logger = TTlog(logger_name="TTRealCondi").logger # DB self.mongo = MongoClient() self.db = self.mongo.TopTrader # Slack self.slack = Slacker(config_manager.get_slack_token()) # Kiwoom self.kw = Kiwoom() self.login() self.stock_info = self.kw.get_stock_basic_info() # app main self.main() def login(self): err_code = self.kw.login() if err_code != 0: self.logger.error("Login Fail") exit(-1) self.logger.info("Login success") def set_ko_font(self): # 그래프에서 마이너스 폰트 깨지는 문제에 대한 대처 mpl.rcParams['axes.unicode_minus'] = False path = 'c:/Windows/Fonts/D2Coding-Ver1.3.2-20180524.ttf' font_name = fm.FontProperties(fname=path).get_name() plt.rcParams["font.family"] = font_name def main(self): print("Start Application") # 금일 급등했던 종목 정보를 가져온다. stock_info = self.kw.rapidly_rising_price_stock(market="000", time_gubun="2", time="0", vol_gubun="00000", screen_no="5000") # 급등했던 종목중 상위 20개를 추린다. stock_info = stock_info[:20] # 상위 20개에 대해 체결강도 정보를 수집하여 DB에 저장한다. for stock in stock_info: code = stock['종목코드'] ret = self.kw.get_chegyul_info(code, "9000") pdb.set_trace() for data in ret: h, m, s = data['시간'][:2], data['시간'][2:4], data['시간'][4:] t = datetime.today() data['date'] = datetime(t.year, t.month, t.day, int(h), int(m), int(s)) data['code'] = code data['stock_name'] = self.stock_info[code]["stock_name"] data['market'] = self.stock_info[code]["market"] self.db.short_trading_info_chegyul.insert(ret) print(ret) # 상위 20개에 대해 분봉/틱봉 정보를 수집하여 DB에 저장한다. t = datetime.today() s_date = datetime(t.year, t.month, t.day, 9, 0, 0) e_date = datetime(t.year, t.month, t.day, 17, 0, 0) for stock in stock_info: code = stock['종목코드'] ret = self.kw.stock_price_by_min(code, "1", "9001", s_date, e_date) for data in ret: data['stock_name'] = self.stock_info[code]["stock_name"] data['market'] = self.stock_info[code]["market"] self.db.time_series_min1.insert(ret) ret = self.kw.stock_price_by_tick(code, "10", "9002", s_date, e_date) for data in ret: data['stock_name'] = self.stock_info[code]["stock_name"] data['market'] = self.stock_info[code]["market"] self.db.time_series_tick10.insert(ret) # 상위 20개에 대해 호가 정보를 수집하여 DB에 저장한다. # for stock in stock_info: # code = stock['종목코드'] # ret = self.kw.get_hoga_info(code, "9001") # self.db.short_trading_info_hoga.insert(ret) # print(ret) print("end")
class TopTrader(QMainWindow, ui): def __init__(self): super().__init__() self.logger = TTlog().logger self.mongo = MongoClient() self.tt_db = self.mongo.TopTrader self.slack = Slack(config_manager.get_slack_token()) self.kw = Kiwoom() self.init_trading() # self.just_sell_all_stocks() self.auto_trading() def init_trading(self): self.login() # self.update_stock_info() self.load_stock_info() self.set_account() self.my_stock_pocket = set() # 보유중인 종목의 code를 저장 t = datetime.today() self.s_time = datetime(t.year, t.month, t.day, 9, 0, 0) # 장 시작시간, 오전9시 def login(self): # Login err_code = self.kw.login() if err_code != 0: self.logger.error("Login Fail") exit(-1) self.logger.info("Login success") def load_stock_info(self): self.stock_dict = {} doc = self.tt_db.stock_information.find({}) for d in doc: code = d["code"] self.stock_dict[code] = d self.logger.info("loading stock_information completed.") def update_stock_info(self): # kospi kospi = [] code_list = self.kw.get_code_list_by_market("0") for code in code_list: stock_name = self.kw.get_master_stock_name(code) kospi.append({ "code": code, "stock_name": stock_name, "market": "kospi" }) self.tt_db.stock_information.insert(kospi) # kosdaq kosdaq = [] code_list = self.kw.get_code_list_by_market("10") for code in code_list: stock_name = self.kw.get_master_stock_name(code) kosdaq.append({ "code": code, "stock_name": stock_name, "market": "kosdaq" }) self.tt_db.stock_information.insert(kosdaq) def set_account(self): self.acc_no = self.kw.get_login_info("ACCNO") self.acc_no = self.acc_no.strip(";") # 계좌 1개를 가정함. self.stock_account = self.get_account_info(self.acc_no) # kiwoom default account setting self.kw.set_account(self.acc_no) def start_timer(self): if self.timer: self.timer.stop() self.timer.deleteLater() self.timer = QTimer() self.timer.timeout.connect(self.check_stocks_to_sell) # self.timer.setSingleShot(True) self.timer.start(30000) # 30 sec interval def just_sell_all_stocks(self): curr_time = datetime.today() self.stock_account = self.get_account_info(self.acc_no) for data in self.stock_account["종목정보"]: self.logger.info("Trading시간 종료되어 보유한 종목 모두 매도처리합니다.") code, stock_name, quantity = data["종목코드"][1:], data["종목명"], int( data["보유수량"]) self.kw.시장가_신규매도(code, quantity) 손익율 = data["손익율"] self.tt_db.trading_history.insert({ 'date': curr_time, 'code': code, 'stock_name': self.stock_dict[code]["stock_name"], 'market': self.stock_dict[code]["market"], 'event': '', 'condi_name': '', 'trade': 'sell', 'profit': 손익율, 'quantity': quantity, 'hoga_gubun': '시장가', 'account_no': self.acc_no }) time.sleep(0.5) def check_stocks_to_sell(self): self.logger.info("[Timer Interrupt] 30 second") self.stock_account = self.get_account_info(self.acc_no) curr_time = datetime.today() self.logger.info("=" * 50) self.logger.info("현재 계좌 현황입니다...") if not bool(self.stock_account): self.logger.error("계좌정보를 제대로 받아오지 못했습니다.") return if datetime.today() < self.s_time: self.logger.info("시작시간이 되지 않아 매도하지 않습니다.") return self.my_stock_pocket = set() for data in self.stock_account["종목정보"]: code, stock_name, quantity = data["종목코드"][1:], data["종목명"], int( data["보유수량"]) self.logger.info("* 종목: {}, 손익율: {}%, 보유수량: {}, 평가금액: {}원".format( data["종목명"], ("%.2f" % data["손익율"]), int(data["보유수량"]), format(int(data["평가금액"]), ','))) self.my_stock_pocket.add(code) 손익율 = data["손익율"] if 손익율 >= 3.0 or 손익율 <= -2.0: if 손익율 > 0: self.logger.info( "시장가로 물량 전부 익절합니다. ^^ [{}:{}, {}주]".format( stock_name, code, quantity)) else: self.logger.info( "시장가로 물량 전부 손절합니다. ㅜㅜ. [{}:{}, {}주]".format( stock_name, code, quantity)) self.my_stock_pocket.remove(code) self.kw.시장가_신규매도(code, quantity) self.tt_db.trading_history.insert({ 'date': curr_time, 'code': code, 'stock_name': self.stock_dict[code]["stock_name"], 'market': self.stock_dict[code]["market"], 'event': '', 'condi_name': '', 'trade': 'sell', 'profit': 손익율, 'quantity': quantity, 'hoga_gubun': '시장가', 'account_no': self.acc_no }) def get_account_info(self, acc_no): self.logger.info("계좌평가현황요청") ret = self.kw.계좌평가현황요청("계좌평가현황요청", acc_no, "", "1", "6001") return ret def search_condi(self, event_data): """키움모듈의 OnReceiveRealCondition 이벤트 수신되면 호출되는 callback함수 이벤트 정보는 event_data 변수로 전달된다. ex) event_data = { "code": code, # "066570" "event_type": event_type, # "I"(종목편입), "D"(종목이탈) "condi_name": condi_name, # "스켈핑" "condi_index": condi_index # "004" } :param dict event_data: :return: """ curr_time = datetime.today() if curr_time < self.s_time: self.logger.info("시작시간이 되지 않아 매수하지 않습니다.") return # 실시간 조건검색 이력정보 self.tt_db.real_condi_search.insert({ 'date': curr_time, 'code': event_data["code"], 'stock_name': self.stock_dict[event_data["code"]]["stock_name"], 'market': self.stock_dict[event_data["code"]]["market"], 'event': event_data["event_type"], 'condi_name': event_data["condi_name"] }) if event_data["event_type"] == "I": 예수금 = self.stock_account["계좌정보"]["예수금"] D2_예수금 = self.stock_account["계좌정보"]["예수금"] 총예수금 = 예수금 + D2_예수금 if 총예수금 < 100000: # 잔고가 10만원 미만이면 매수 안함 self.logger.info("총예수금({}) 부족으로 추가 매수하지 않습니다.".format(총예수금)) return if event_data["code"] in self.my_stock_pocket: self.logger.info("해당 종목({}) 이미 보유중이라 추가매수하지 않습니다.".format( self.stock_dict[event_data["code"]]["stock_name"])) return # curr_price = self.kw.get_curr_price(event_data["code"]) # quantity = int(100000/curr_price) quantity = 20 # self.kw.reg_callback("OnReceiveChejanData", ("조건식매수", "5000"), self.account_stat) stock_name = self.stock_dict[event_data["code"]]["stock_name"] market = self.stock_dict[event_data["code"]]["market"] self.tt_db.trading_history.insert({ 'date': curr_time, 'code': event_data["code"], 'stock_name': stock_name, 'market': market, 'event': event_data["event_type"], 'condi_name': event_data["condi_name"], 'trade': 'buy', 'quantity': quantity, 'hoga_gubun': '시장가', 'account_no': self.acc_no }) self.logger.info("{}:{}를 {}주 시장가_신규매수합니다.".format( stock_name, event_data["code"], quantity)) self.my_stock_pocket.add(event_data["code"]) self.kw.시장가_신규매수(event_data["code"], quantity) # self.kw.send_order("조건식매수", "5000", self.acc_no, 1, event_data["code"], quantity, 0, "03", "") def auto_trading(self): """키움증권 HTS에 등록한 조건검색식에서 검출한 종목을 매수하고 -2%, +3%에 손절/익절 매도하는 기본적인 자동매매 함수 :return: """ self.logger.info("TopTrader 자동매매 시작합니다...") self.timer = None self.start_timer() # callback fn 등록 self.kw.reg_callback("OnReceiveRealCondition", "", self.search_condi) screen_no, cnt = 4000, 0 condi_info = self.kw.get_condition_load() self.logger.info("실시간 조건 검색 시작합니다.") for condi_name, condi_id in condi_info.items(): # 화면번호, 조건식이름, 조건식ID, 실시간조건검색(1) self.logger.info("화면번호: {}, 조건식명: {}, 조건식ID: {}".format( screen_no, condi_name, condi_id)) self.kw.send_condition(str(screen_no), condi_name, int(condi_id), 1) time.sleep(0.5) cnt += 1 if cnt == 10: screen_no += 1 cnt = 0
class TopTrader(QMainWindow, ui): def __init__(self): super().__init__() # self.setupUi(self) # load app screen duration = sys.argv[1] self.logger = TTlog(logger_name="TT"+duration).logger self.mongo = MongoClient() self.tt_db = self.mongo.TopTrader self.slack = Slack(config_manager.get_slack_token()) today = datetime.today() self.end_date = datetime(today.year, today.month, today.day, 16, 0, 0) self.kw = Kiwoom() self.login() self.get_screen_no = { "min1": "3000", "min3": "3001", "min5": "3002", "min10": "3003", "min60": "3004", "day": "3005", "week": "3006", "month": "3007", "year": "3008" } if duration.startswith("min"): self.collect_n_save_data_min(duration) else: self.collect_n_save_data(duration) def login(self): err_code = self.kw.login() if err_code != 0: self.logger.error("Login Fail") exit(-1) self.logger.info("Login success") def upsert_db(self, col, datas): self.logger.info("Upsert Data to DB") s_time = time.time() for doc in datas: # col.update(condition, new_data, upsert=True) col.update({'date': doc['date'], 'code': doc['code']}, doc, upsert=True) e_time = time.time() print("Time: ", int(e_time-s_time)) def get_stock_list(self, market): kospi_code_list = self.kw.get_code_list_by_market(market) stock_list = [[c, self.kw.get_master_stock_name(c)] for c in kospi_code_list] stock_list = [(c, name) for c, name in stock_list if not any(map(lambda x: x in name, constant.FILTER_KEYWORD))] stock_list.sort() return stock_list def get_last_data(self, cur, duration): current_flag = True if duration.startswith("min"): first_date = datetime(2018, 7, 23, 0, 0, 0) else: first_date = datetime(2018, 6, 1, 0, 0, 0) # 최초 저장하는 경우 if cur.count() == 0: s_date, e_date, s_index = first_date, self.end_date, 0 else: data = cur.next() if (data['last'] + 1) != data['total']: # 지난번에 저장하다가 중간에 멈춘 경우 if data['end_date'] != self.end_date: s_date, e_date, s_index = data['start_date'], data['end_date'], data['last'] + 1 current_flag = False # 이번에 저장하다가 중간에 멈춘 경우 else: s_date, e_date, s_index = data['start_date'], self.end_date, data['last'] + 1 else: # 지난번에 저장을 완료, 이번에 저장해야 하는 경우 if data['end_date'] != self.end_date: s_date, e_date, s_index = data['end_date'], self.end_date, 0 # 이번에 저장을 완료 else: s_date, e_date, s_index = data['end_date'], self.end_date, data['last'] + 1 return s_date, e_date, s_index, current_flag def collect_n_save_data_min(self, duration): """ 코스피 종목의 분단위 데이터를 수집한다. 1. 한번도 db에 저장한적이 없다면 6/1일부터 오늘까지의 데이터를 저장한다. 2. 지난번에 저장하다가 중간에 멈췄다면, 나머지 작업을 모두 완료 후, 해당시점의 e_date 부터 오늘까지의 데이터를 전 종목에 대해 저장한다. 3. 이번에 저장하다가 중간에 멈췄다면, 이전 e_date 부터 오늘까지의 데이터를 전 종목에 대해 저장한다. 4. 지난번에 저장을 완료했다면, 해당시점의 e_date 부터 오늘까지의 데이터를 전 종목에 대해 저장한다. :param duration: min1, min3, min5, min10, min60 중 하나의 값 :return: """ # stock_list = self.get_stock_list(constant.KOSPI) # stock_list += self.get_stock_list(constant.KOSDAQ) stock_list = self.get_stock_list(constant.KOSDAQ) cur = self.tt_db.time_series_temp2.find({'type': duration}) s_date, e_date, s_index, current_flag = self.get_last_data(cur, duration) if not current_flag: msg = "[{}] {} ~ {}. start to collect stock data. this is previous process.".format( duration, s_date, e_date, e_date, self.end_date ) else: msg = "[{}] {} ~ {}. start to collect stock data. this is current process.".format( duration, s_date, e_date ) self.slack.log(msg) col = { "min1": self.tt_db.time_series_min1, "min3": self.tt_db.time_series_min3, "min5": self.tt_db.time_series_min5, "min10": self.tt_db.time_series_min10, "min60": self.tt_db.time_series_min60 }[duration] fn = self.kw.stock_price_by_min total = len(stock_list) msg = "[{}] {} / {} -> start to collect stock data".format(duration, s_index, total) self.slack.log(msg) for i, stock in enumerate(stock_list[s_index:], s_index): code, stock_name = stock self.logger.info("%s/%s - %s/%s" % (i, total, code, stock_name)) self.logger.info("period : {} ~ {}".format(s_date, e_date)) self.logger.info("time_series_{}".format(duration)) try: doc = fn(code, tick=duration.strip("min"), screen_no=self.get_screen_no[duration], start_date=s_date, end_date=e_date) except KiwoomServerCheckTimeError as e: self.logger.error("[KiwoomServerCheckTimeError] {}".format(duration)) self.tt_db.urgent2.update({'type': 'error'}, {'type': 'error', 'error_code': e.error_code}, upsert=True) exit(0) try: self.upsert_db(col, doc) except pymongo.errors.InvalidOperation as e: # cannot do an empty bulk write ? self.logger.error(e) self.tt_db.time_series_temp2.update({'type': duration}, {'type': duration, 'code': code, 'stock_name': stock_name, 'last': i, 'start_date': s_date, 'end_date': e_date, 'total': total}, upsert=True) self.tt_db.urgent2.update({'type': 'error'}, {'type': 'error', 'error_code': 0}, upsert=True) exit(0) # Program exit def collect_n_save_data(self, duration): """ 코스피 종목의 분단위 데이터를 제외한 나머지 데이터를 수집한다. 1. 한번도 db에 저장한적이 없다면 6/1일부터 오늘까지의 데이터를 저장한다. 2. 지난번에 저장하다가 중간에 멈췄다면, 나머지 작업을 모두 완료 후, 해당시점의 e_date 부터 오늘까지의 데이터를 전 종목에 대해 저장한다. 3. 이번에 저장하다가 중간에 멈췄다면, 이전 e_date 부터 오늘까지의 데이터를 전 종목에 대해 저장한다. 4. 지난번에 저장을 완료했다면, 해당시점의 e_date 부터 오늘까지의 데이터를 전 종목에 대해 저장한다. :param duration: str - day, week, month, year 중 하나의 값 :return: """ stock_list = self.get_stock_list(constant.KOSPI) stock_list += self.get_stock_list(constant.KOSDAQ) cur = self.tt_db.time_series_temp2.find({'type': duration}) s_date, e_date, s_index, current_flag = self.get_last_data(cur, duration) if not current_flag: msg = "[{}] {} ~ {}. start to collect stock data. this is previous process.".format( duration, s_date, e_date, e_date, self.end_date ) else: msg = "[{}] {} ~ {}. start to collect stock data. this is current process.".format( duration, s_date, e_date ) self.slack.log(msg) col = { "day": self.tt_db.time_series_day, "week": self.tt_db.time_series_week, "month": self.tt_db.time_series_month, "year": self.tt_db.time_series_year }[duration] fn = { "day": self.kw.stock_price_by_day, "week": self.kw.stock_price_by_week, "month": self.kw.stock_price_by_month # "year": self.kw.stock_price_by_year }[duration] total = len(stock_list) msg = "[{}] {} / {} -> start to collect stock data".format(duration, s_index, total) self.slack.log(msg) for i, stock in enumerate(stock_list[s_index:], s_index): code, stock_name = stock self.logger.info("%s/%s - %s/%s" % (i, total, code, stock_name)) self.logger.info("time_series_{}".format(duration)) try: doc = fn(code, screen_no=self.get_screen_no[duration], start_date=s_date, end_date=e_date) except KiwoomServerCheckTimeError as e: self.logger.error("[KiwoomServerCheckTimeError] {}".format(duration)) self.tt_db.urgent2.update({'type': 'error'}, {'type': 'error', 'error_code': e.error_code}, upsert=True) exit(0) self.upsert_db(col, doc) self.tt_db.time_series_temp2.update({'type': duration}, {'type': duration, 'code': code, 'stock_name': stock_name, 'last': i, 'start_date': s_date, 'end_date': e_date, 'total': total}, upsert=True) self.tt_db.urgent2.update({'type': 'error'}, {'type': 'error', 'error_code': 0}, upsert=True) exit(0) # Program exit
class TopTrader(QMainWindow, ui): def __init__(self): QMainWindow.__init__(self) # Application Configuration # UI self.setupUi(self) # Font(ko) self.set_ko_font() # Logger self.logger = TTlog(logger_name="TTRealCondi").logger # DB self.dbm = DBM('TopTrader') # Slack self.slack = Slacker(config_manager.get_slack_token()) # Kiwoom self.kw = Kiwoom() self.login() self.stock_info = self.kw.get_stock_basic_info() # app main self.main() def login(self): err_code = self.kw.login() if err_code != 0: self.logger.error("Login Fail") exit(-1) self.logger.info("Login success") def set_ko_font(self): # 그래프에서 마이너스 폰트 깨지는 문제에 대한 대처 mpl.rcParams['axes.unicode_minus'] = False path = 'c:/Windows/Fonts/D2Coding-Ver1.3-20171129.ttc' font_name = fm.FontProperties(fname=path).get_name() plt.rcParams["font.family"] = font_name def main(self): print("Start Application") target_date = datetime(2018, 8, 2) y, m, d = target_date.year, target_date.month, target_date.day s_time = datetime(y, m, d, 9, 0, 0) e_time = datetime(y, m, d, 15, 30, 0) self.code = '000020' df = pd.DataFrame( self.dbm.get_tick_data(self.code, target_date, tick="1")) ts_group = df.groupby('timestamp') volumn = pd.DataFrame(ts_group.sum()['거래량']) price = pd.DataFrame(ts_group.max()['현재가']) index = pd.date_range(s_time, e_time, freq='S') volumn = volumn.reindex(index, method='ffill', fill_value=0) price = price.reindex(index, method='ffill', fill_value=0) price['volumn'] = volumn from trading.account import Stock tick_data = defaultdict(list) stock_list = [ Stock('900100', '주식1'), Stock('200710', '주식2'), Stock('206560', '주식3') ] for stock in stock_list: stock.gen_time_series_sec1(target_date) tick_data[stock.code] = stock.time_series_sec1 pdb.set_trace() db_data = self.dbm.get_real_condi_search_data(target_date, "소형주_급등_005_003") condi_hist = defaultdict(defaultdict) for data in db_data[:5]: code, date = data['code'], data['date'].replace(microsecond=0) price = tick_data[code].ix[date]['현재가'] condi_hist[code][date] = price pdb.set_trace() print("End Application")
class TopTrader(QMainWindow, ui): def __init__(self): super().__init__() # self.setupUi(self) # load app screen self.logger = TTlog(logger_name="RealCondi").logger self.mongo = MongoClient() self.tt_db = self.mongo.TopTrader self.slack = Slack(config_manager.get_slack_token()) self.kw = Kiwoom() self.login() # ready to search condi self.load_stock_info() t = datetime.today() self.s_time = datetime(t.year, t.month, t.day, 9, 0, 0) # 장 시작시간, 오전9시 # fake trading self.timer = None self.start_timer() # core function self.screen_no = 4000 self.N1, self.N2 = 0, 10 # self.screen_no = 4001 # self.N1, self.N2 = 10, 20 self.real_condi_search() def load_stock_info(self): self.stock_dict = {} doc = self.tt_db.stock_information.find({}) for d in doc: code = d["code"] self.stock_dict[code] = d self.logger.info("loading stock_information completed.") def start_timer(self): if self.timer: self.timer.stop() self.timer.deleteLater() self.timer = QTimer() self.timer.timeout.connect(self.fake_check_to_sell) # self.timer.setSingleShot(True) self.timer.start(1000) # 1 sec interval def fake_check_to_sell(self): """ :return: """ def search_result(self, event_data): """ :param event_data: :return: """ curr_time = datetime.today() if curr_time < self.s_time: self.logger.info("=" * 100) self.logger.info("장 Open 전 입니다. 오전 9:00 이후에 검색 시작합니다.") self.logger.info("=" * 100) return # 실시간 조건검색 이력정보 self.tt_db.real_condi_search.insert({ 'date': curr_time, 'code': event_data["code"], 'stock_name': self.stock_dict[event_data["code"]]["stock_name"], 'market': self.stock_dict[event_data["code"]]["market"], 'event': event_data["event_type"], 'condi_index': event_data["condi_index"], 'condi_name': event_data["condi_name"] }) def real_condi_search(self): # callback fn 등록 self.kw.reg_callback("OnReceiveRealCondition", "", self.search_result) # self.kw.notify_callback["OnReceiveRealCondition"] = self.search_condi condi_info = self.kw.get_condition_load() self.logger.info("실시간 조건 검색 시작합니다.") for condi_name, condi_id in list(condi_info.items())[self.N1:self.N2]: # 화면번호, 조건식이름, 조건식ID, 실시간조건검색(1) self.logger.info("화면번호: {}, 조건식명: {}, 조건식ID: {}".format( self.screen_no, condi_name, condi_id )) self.kw.send_condition(str(self.screen_no), condi_name, int(condi_id), 1) time.sleep(0.5) def login(self): err_code = self.kw.login() if err_code != 0: self.logger.error("Login Fail") exit(-1) self.logger.info("Login success")
class TopTrader(QMainWindow, ui): def __init__(self): super().__init__() # self.setupUi(self) # load app screen self.logger = TTlog().logger self.dbm = DBM('TopTrader') self.mongo = MongoClient() self.db = self.mongo.TopTrader self.slack = Slack(config_manager.get_slack_token()) self.kw = Kiwoom() self.login() self.main() def load_tick_data(self, code, stock_name, date, tick="1", screen_no="1234"): """특정 종목의 하루단위 tick data를 가져오는 함수 tick_data가 DB에 있으면 DB에서 가져오고, 없으면 kw module로부터 가져온다. :param code: :param stock_name: :param date: :param tick: :param screen_no: :return: """ ret, cur = self.dbm.check_tick_cache(code, date, tick="1") if ret: tick_data = cur.next() else: base_date = datetime(date.year, date.month, date.day, 0, 0, 0) raw_data = self.kw.stock_price_by_tick(code, tick=tick, screen_no=screen_no, date=base_date) tick_data = { 'code': code, 'stock_name': stock_name, 'date': base_date, 'time_series_1tick': [{ 'timestamp': _d['date'], '현재가': _d['현재가'], '거래량': _d['거래량'] } for _d in raw_data] } return tick_data def collect_tick_data(self, code, stock_name, s_date, e_date, tick="1", screen_no="1234"): """특정 종목의 일정 기간의 tick data를 DB에 저장하는 함수 DB에 이미 값이 저장되어 있으면 저장하지 않고 skip 한다. s_date, e_date는 초단위까지 지정이 가능하지만, 저장은 일단위로 저장한다. tick은 1, 3, 5, 10, 30 선택 가능하다. :param code: :param stock_name: :param tick: :param s_date: :param e_date: :return: """ s_base_date = datetime(s_date.year, s_date.month, s_date.day, 0, 0, 0) e_base_date = datetime(e_date.year, e_date.month, e_date.day, 0, 0, 0) days = (e_base_date - s_base_date).days + 1 date_list = [s_base_date + timedelta(days=x) for x in range(0, days)] for base_date in date_list: ret, cur = self.dbm.check_tick_cache(code, base_date, tick="1") if ret: self.logger.debug( "No need to save data. already has data in DB") continue raw_data = self.kw.stock_price_by_tick(code, tick=tick, screen_no=screen_no, date=base_date) tick_data = { 'code': code, 'stock_name': stock_name, 'date': base_date, 'time_series_1tick': [{ 'timestamp': _d['date'], '현재가': _d['현재가'], '거래량': _d['거래량'] } for _d in raw_data] # 'time_series': [{'timestamp': _d['date'], '현재가': _d['현재가'], '거래량': _d['거래량']} for _d in raw_data] } # DB에 없으면 수행되는 부분이라서 insert 함수를 사용. self.dbm.save_tick_data(tick_data, tick="1") def main(self): # 사용자 정의 부분_S # 8/2일자 조건검색식으로 검출된 모든 종목의 1tick data를 수집한다. with open("base_date_when_collect_tick_data.txt") as f: year, month, day = [int(i) for i in f.read().strip().split(" ")] self.base_date = datetime(year, month, day) # 사용자 정의 부분_E # kospi, kosdaq 모든 종목의 코드와 종목명 정보를 불러온다. self.stock_info = self.kw.get_stock_basic_info() code_list = self.dbm.get_code_list_condi_search_result(self.base_date) code_list.sort() self.dbm.record_collect_tick_data_status("START", self.base_date, tick="1") for num, code in enumerate(code_list, 1): stock_name = self.stock_info[code]["stock_name"] if self.dbm.already_collect_tick_data(code, self.base_date, tick="1"): self.logger.debug( "{}/{} - {}:{} Skip. Already saved data!".format( num, len(code_list), code, stock_name)) continue self.logger.debug("{}/{} - {}:{} Start !".format( num, len(code_list), code, stock_name)) self.collect_tick_data(code, stock_name, self.base_date, self.base_date, tick="1", screen_no="1234") self.logger.debug("{}/{} - {}:{} Completed !".format( num, len(code_list), code, stock_name)) self.dbm.save_collect_tick_data_history(code, self.base_date, tick="1") self.dbm.record_collect_tick_data_status("WORKING", self.base_date, tick="1") self.dbm.record_collect_tick_data_status("END", self.base_date, tick="1") self.logger.debug("save all 1tick information..") exit(0) # program exit def login(self): err_code = self.kw.login() if err_code != 0: self.logger.error("Login Fail") exit(-1) self.logger.info("Login success")
class TopTrader(QMainWindow, ui): def __init__(self): QMainWindow.__init__(self) # Application Configuration # UI self.setupUi(self) # Font(ko) self.set_ko_font() # Logger self.logger = TTlog(logger_name="TTRealCondi").logger # DB self.mongo = MongoClient() self.db = self.mongo.TopTrader self.dbm = DBM('TopTrader') # Slack self.slack = Slack(cfg_mgr.get_slack_token()) # Kiwoom self.kw = Kiwoom() self.login() cfg_mgr.STOCK_INFO = self.kw.get_stock_basic_info() # app main cfg_mgr.MODE = constant.DEBUG self.main() def login(self): err_code = self.kw.login() if err_code != 0: self.logger.error("Login Fail") exit(-1) self.logger.info("Login success") def set_ko_font(self): # 그래프에서 마이너스 폰트 깨지는 문제에 대한 대처 mpl.rcParams['axes.unicode_minus'] = False try: path = 'c:/Windows/Fonts/D2Coding-Ver1.3-20171129.ttc' font_name = fm.FontProperties(fname=path).get_name() plt.rcParams["font.family"] = font_name except FileNotFoundError as e: path = 'c:/Windows/Fonts/D2Coding-Ver1.3.2-20180524.ttf' font_name = fm.FontProperties(fname=path).get_name() plt.rcParams["font.family"] = font_name def collect_1tick_data(self, code, stock_name, date): col_tick1 = self.db.time_series_tick1 cur = col_tick1.find({'code': code, 'date': date}) if cur.count() != 0: print("{}:{} Already save data. so Skip to save data !".format( code, stock_name)) return s_date = date e_date = date + timedelta(days=1) raw_data = self.kw.stock_price_by_tick(code, tick="1", screen_no="1234", start_date=s_date, end_date=e_date) data = { 'code': code, 'stock_name': stock_name, 'date': date, 'time_series_1tick': [{ 'timestamp': _d['date'], '현재가': _d['현재가'], '거래량': _d['거래량'] } for _d in raw_data] } # col_tick1.insert(data) return data def check_cache_tick1(self, code, s_date, e_date): col_tick1 = self.db.time_series_tick1 s_base_date = datetime(s_date.year, s_date.month, s_date.day, 0, 0, 0) cur = col_tick1.find({ 'code': code, 'date': { '$gte': s_base_date, '$lte': e_date } }) if cur.count() != 0: return True, cur return False, None def main(self): # 목표 !!! # 하루 단위의 조건검색 결과를 분석한다. # 사용자 지정 변수__S self.target_date = datetime.today() self.target_date = datetime(2018, 8, 2) # temp code # 사용자 지정 변수__E # 조건검색식 load condi_info = self.kw.get_condition_load() strg_list = [] # loop를 돌면서 각각의 조건검색식에 대해 결과 분석 for condi_name, condi_index in condi_info.items(): # 특정일의 특정조건검색식을 특정전략으로 검증 condi = ConditionalSearch.get_instance(condi_index, condi_name) self.logger.info("=======[ Smulation(%s) Start! ]=======" % condi.condi_name) strg = Strategy("short_trading.strategy", condi) strg.simulate(self.target_date) strg_list.append(strg) self.logger.info("=======[ Smulation(%s) End! ]=======" % condi.condi_name) self.logger.info("\n\n\n") self.logger.info("end") exit(0)
class TopTrader(QMainWindow, ui): def __init__(self): super().__init__() # self.setupUi(self) # load app screen self.logger = TTlog().logger self.db = MongoClient().TopTrader self.kw = Kiwoom() self.login() self.realtime_stream() self.timer = None self.start_timer() def login(self): # Login err_code = self.kw.login() if err_code != 0: self.logger.error("Login Fail") return self.logger.info("Login success") def start_timer(self): if self.timer: self.timer.stop() self.timer.deleteLater() self.timer = QTimer() self.timer.timeout.connect(self.timer_call) # self.timer.setSingleShot(True) self.timer.start(10000) # 10 sec interval def timer_call(self): self.logger.info("") self.logger.info("=" * 100) self.logger.info("Timer Call !") self.logger.info("=" * 100) def realtime_stream_callback(self, data): self.logger.info("[realtime_stream_callback]") self.logger.info("data: {}".format(data)) self.db.realtime_stream.insert({'real_data': data}) def realtime_stream(self): code_list = "066570;000030;000270;000660;005930;068270;045390;064350;011390;025560" screen_no = "6001" self.kw.reg_callback("OnReceiveRealData", "", self.realtime_stream_callback) self.kw.set_real_reg(screen_no, code_list, "10;11;12;13;14;16;17;27;28;", 0)
class Stock(object): """code당 1개의 객체(singleton/code)를 생성한다. """ _inst = {} def __init__(self, code): self.empty = True self.first_trading = True # 첫 거래인지 self.timestamp = None self.first_buy_time = None # if add/delete index, must update self.core_index self.timestamp = None self.code = code self.stock_name = cfg_mgr.STOCK_INFO[code]['stock_name'] self.현재가 = 0 self.매매수량 = 0 self.보유수량 = 0 self.매매금액 = 0 self.매입금액 = 0 self.최대매입금액 = 0 self.매입평균가 = 0.0 self.평가손익 = 0.0 self.실현손익 = 0 self.누적손익 = 0 self.평가금액 = 0.0 self.수익률 = 0.0 self.누적수익률 = 0.0 self.평가금액변동 = 0.0 self.매입금액변동 = 0.0 self.실현손익변동 = 0.0 self.core_index = [ 'timestamp', 'code', 'stock_name', '현재가', '매매수량', '보유수량', '매매금액', '매입금액', '최대매입금액', '매입평균가', '평가손익', '실현손익', '누적손익', '평가금액', '수익률', '누적수익률', '평가금액변동', '매입금액변동', '실현손익변동' ] self.check_core_index() self.time_series_sec1 = None self.logger = TTlog().logger self.dbm = DBM('TopTrader') def print_attr(self, trading_type, attr_list=None): if not cfg_mgr.STOCK_MONITOR: return self.logger.debug("\n" + ("-" * 100)) self.logger.debug("[Stock] Information : {}/{}".format( self.stock_name, self.code)) self.logger.debug("[Trading Type] -> {}-{}".format( trading_type, self.trading_reason)) if bool(attr_list): attr_val = [] for attr in attr_list: val = self.__getattribute__(attr) log_str = "{}: {}".format(attr, val) if isinstance(val, float): log_str = "{}: {:.2f}".format(attr, val) attr_val.append(log_str) self.logger.debug("\t".join(attr_val)) else: self.logger.debug(self.__repr__()) # pass def __repr__(self): core_index = self.core_index # log = ["{}: {}".format(attr, str(self.__getattribute__(attr))) for attr in core_index] log = [] for attr in core_index: val = self.__getattribute__(attr) log_str = "{}: {}".format(attr, val) if isinstance(val, float): log_str = "{}: {:.2f}".format(attr, val) log.append(log_str) return "\t".join(log) @classmethod def get_instance(cls, code): if code not in cls._inst: cls._inst[code] = Stock(code) return cls._inst[code] @classmethod def get_new_instance(cls, code, recycle_time_series=False): if code in cls._inst and recycle_time_series: time_series_sec1 = cls._inst[code].time_series_sec1 cls._inst[code] = Stock(code) cls._inst[code].time_series_sec1 = time_series_sec1 else: cls._inst[code] = Stock(code) return cls._inst[code] def get_holding_period(self, timestamp=None): """최초 해당 주식을 보유한 시간으로부터 경과된 시간을 반환한다. :return: """ if not bool(timestamp): timestamp = self.timestamp if not bool(self.first_buy_time) or timestamp <= self.first_buy_time: return timedelta(seconds=0) return timestamp - self.first_buy_time def bep(self, event, price, amount=None): """Backup/Evaluate/PostAction prev : 기존 데이터 :return: """ # Backup self.backup() # dispatch Evaluate function eval_fn = self.__getattribute__("evaluate_{}".format(event)) eval_fn(**{'price': price, 'amount': amount}) # dispatch Post function post_fn = self.__getattribute__("post_{}".format(event)) post_fn(**{'price': price, 'amount': amount}) # Update def backup(self): """중요 지표들을 모두 백업한다. 백업 지표는 앞에 '기존' 키워드가 붙는다. :return: """ core_index = [ '현재가', '매매수량', '보유수량', '매매금액', '매입금액', '최대매입금액', '매입평균가', '평가손익', '실현손익', '누적손익', '평가금액', '수익률', '누적수익률' ] for index in core_index: val = self.__getattribute__(index) self.__setattr__('기존' + index, val) def evaluate_buy(self, **kwargs): price, amount = kwargs['price'], kwargs['amount'] self.현재가 = price self.매매수량 = amount self.보유수량 = self.기존보유수량 + self.매매수량 self.매매금액 = self.현재가 * self.매매수량 self.매입금액 = self.기존매입금액 + self.매매금액 self.매입금액변동 = self.매입금액 - self.기존매입금액 self.최대매입금액 = max(self.최대매입금액, self.매입금액) self.매입평균가 = self.매입금액 / self.보유수량 self.평가손익 = (self.현재가 - self.매입평균가) * self.보유수량 # self.실현손익 = self.실현손익 self.실현손익변동 = 0 # self.누적손익 = self.누적손익 self.평가금액 = self.현재가 * self.보유수량 if self.보유수량 == 0: self.수익률 = 0.0 else: self.수익률 = round((self.평가금액 - self.매입금액) / self.매입금액 * 100, 2) self.누적수익률 = round(float(self.누적손익) / self.최대매입금액 * 100, 2) self.평가금액변동 = self.평가금액 - self.기존평가금액 if self.first_trading: self.first_buy_time = self.timestamp self.first_trading = False def post_buy(self, **kwargs): price, amount = kwargs['price'], kwargs['amount'] core_index = [ 'timestamp', '현재가', '매입평균가', '수익률', '누적수익률', '누적손익', '매매수량', '보유수량', '매입금액', '최대매입금액', '평가손익', '실현손익', '평가금액', '매매금액' ] self.print_attr('BUY', core_index) # pdb.set_trace() def evaluate_sell(self, **kwargs): price, amount = kwargs['price'], kwargs['amount'] self.현재가 = price self.매매수량 = amount self.보유수량 = self.기존보유수량 - self.매매수량 self.매매금액 = self.현재가 * self.매매수량 self.매입금액 = self.기존매입평균가 * self.보유수량 self.매입금액변동 = self.매입금액 - self.기존매입금액 self.최대매입금액 = max(self.최대매입금액, self.매입금액) # self.매입평균가 = self.매입평균가 self.평가손익 = (self.현재가 - self.매입평균가) * self.보유수량 self.실현손익 = (self.현재가 - self.매입평균가) * self.매매수량 self.실현손익변동 = self.실현손익 - self.기존실현손익 self.누적손익 += self.실현손익 self.평가금액 = self.현재가 * self.보유수량 # self.기존수익률 = round((self.기존평가금액 - self.기존매입금액) / self.기존매입금액 * 100, 2) if self.보유수량 == 0: self.수익률 = 0.0 else: self.수익률 = round((self.평가금액 - self.매입금액) / self.매입금액 * 100, 2) self.누적수익률 = round(float(self.누적손익) / self.최대매입금액 * 100, 2) self.평가금액변동 = self.평가금액 - self.기존평가금액 if self.보유수량 == 0: self.first_trading = True self.first_buy_time = None def post_sell(self, **kwargs): price, amount = kwargs['price'], kwargs['amount'] core_index = [ 'timestamp', '현재가', '매입평균가', '실현손익', '기존수익률', '기존누적수익률', '누적손익', '매매수량', '보유수량', '매입금액', '최대매입금액', '평가손익', '평가금액', '매매금액' ] self.print_attr('SELL', core_index) # pdb.set_trace() def evaluate_change_price(self, **kwargs): price, amount = kwargs['price'], 0 self.현재가 = price self.매매수량 = amount # self.보유수량 = self.기존보유수량 - self.매매수량 self.매매금액 = 0 # self.매입금액 = self.기존매입평균가 * self.보유수량 # self.최대매입금액 = max(self.최대매입금액, self.매입금액) # self.매입평균가 = self.매입평균가 self.평가손익 = (self.현재가 - self.매입평균가) * self.보유수량 # self.실현손익 = (self.현재가 - self.매입평균가) * self.매매수량 # self.누적손익 += self.실현손익 self.평가금액 = self.현재가 * self.보유수량 if self.보유수량 == 0: self.수익률 = 0.0 else: self.수익률 = round((self.평가금액 - self.매입금액) / self.매입금액 * 100, 2) # self.누적수익률 = round(float(self.누적손익) / self.최대매입금액 * 100, 2) self.평가금액변동 = self.평가금액 - self.기존평가금액 def post_change_price(self, **kwargs): price, amount = kwargs['price'], 0 core_index = [ '현재가', '매매수량', '보유수량', '매매금액', '매입금액', '최대매입금액', '매입평균가', '평가손익', '실현손익', '누적손익', '평가금액', '수익률', '누적수익률' ] # self.print_attr('CHANGE PRICE') @common.type_check def update_sell(self, price_per_stock: int, amount: int): """현재가에 amount 만큼 매도를 하여, 내부 정보를 업데이트 한다. :param amount: :return: """ self.bep('sell', price_per_stock, amount) @common.type_check def update_buy(self, price_per_stock: int, amount: int): """해당 주식을 매수하였을 경우, 관련 지표를 업데이트 :param price_per_stock: :param amount: :return: """ self.bep('buy', price_per_stock, amount) def update_stock_value(self, timestamp): """해당 주식의 현재가가 변경되었을 경우, 관련 지표를 업데이트 :param timestamp: :return: """ self.timestamp = timestamp 현재가 = self.time_series_sec1.ix[timestamp]['현재가'] self.bep('change_price', 현재가) def set_strategy(self, strg): """ :param strg: :return: """ self.strg = strg def gen_time_series_sec1(self, target_date): """특정종목 특정일의 초단위 tick_data(DataFrame 객체)를 생성한다. timestamp 현재가 volumn 2018-08-02 09:00:00 0.0 0.0 2018-08-02 09:00:01 15000.0 130.0 2018-08-02 09:00:02 15000.0 91.0 2018-08-02 09:00:03 15500.0 0.0 ...(생략) :param target_date: :return: """ if self.time_series_sec1 is not None: self.logger.info( "{}/{} use existing gen_time_series_sec1..".format( self.stock_name, self.code)) return self.time_series_sec1 self.logger.info("{}/{} gen_time_series_sec1..".format( self.stock_name, self.code)) y, m, d = target_date.year, target_date.month, target_date.day s_time = datetime(y, m, d, 9, 0, 0) e_time = datetime(y, m, d, 15, 30, 0) df = pd.DataFrame( self.dbm.get_tick_data(self.code, target_date, tick="1")) ts_group = df.groupby('timestamp') price = pd.DataFrame(ts_group.max()['현재가']) index = pd.date_range(s_time, e_time, freq='S') price = price.reindex(index, method='ffill', fill_value=0) # volumn = pd.DataFrame(ts_group.sum()['거래량']) # volumn = volumn.reindex(index, method='ffill', fill_value=0) # price['volumn'] = volumn # index = timestamp (freq=second) # 현재가 = max, ffill, fill_value=0 # 거래량 = sum, ffill, fill_value=0 self.time_series_sec1 = price return self.time_series_sec1 def get_curr_price(self, timestamp): """현 시점(timestamp)의 주가를 반환한다. :param timestamp: :return: """ if self.time_series_sec1 is None: y, m, d = timestamp.year, timestamp.month, timestamp.day self.time_series_sec1 = self.gen_time_series_sec1(datetime( y, m, d)) return self.time_series_sec1.ix[timestamp]['현재가'] def get_core_index(self): """Stock객체의 핵심 지표 리스트. 반드시 Stock객체의 속성값과 동기화가 되어야 함. :return: """ return self.core_index def check_core_index(self): """ :return: """ for index in self.get_core_index(): self.__getattribute__(index)