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 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 Account(object): """ [Todo] 구현되어야 할 api list - """ def __init__(self, init_balance, th): self.시작잔고 = init_balance self.예수금 = init_balance self.총평가금액 = 0.0 self.총매입금액 = 0.0 self.총최대매입금액 = 0.0 self.총평가손익 = 0.0 self.총손익 = 0.0 # 총평가손익 + 총누적손익 self.추정자산 = init_balance self.총수익률 = 0.0 self.총누적손익 = 0.0 self.총누적수익률 = 0.0 self.보유주식 = {} self.core_index = ['시작잔고', '예수금', '총평가금액', '총최대매입금액', '총매입금액', '총평가손익', '총손익', '추정자산', '총수익률', '총누적손익', '총누적수익률'] self.core_index = ['총누적손익', '총누적수익률', '총손익', '총수익률', '총평가손익', '총평가금액', '총매입금액', '추정자산', '총최대매입금액', '예수금', '시작잔고'] self.check_core_index() self.dbm = DBM('TopTrader') self.timestamp = None self.trading_history = th self.logger = TTlog().logger def print_attr(self, trading_type, stock_name, code, reason, attr_list=None): if not cfg_mgr.ACCOUNT_MONITOR: return self.logger.debug("\n" + ("-" * 100)) self.logger.debug("[Account] Information : {}".format(self.timestamp)) self.logger.debug("[Trading Type] -> {}-{} ( {}/{} )".format(trading_type, reason, stock_name, code)) 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("\n".join(attr_val)) else: self.logger.debug(self.__repr__()) def __repr__(self): core_index = ['timestamp'] + 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) def sync(self): """실제 계좌 정보를 불러와서 멤버 변수를 초기화 한다. :return: """ @common.type_check def update_sell(self, stock: Stock, price_per_stock: int, amount: int, reason: str): """특정 주식을 매도했을때, 계좌정보를 업데이트 :param code: :param amount: :return: """ # stock 객체 업데이트 stock.trading_reason = reason stock.update_sell(price_per_stock, amount) if cfg_mgr.debug_mode(): pass # self.print_attr('SELL', stock.stock_name, stock.code, reason) # pdb.set_trace() # 계좌 정보 업데이트 self.예수금 += stock.매매금액 self.총평가금액 += stock.평가금액변동 self.총매입금액 += stock.매입금액변동 self.총최대매입금액 = max(self.총매입금액, self.총최대매입금액) self.추정자산 = self.예수금 + self.총평가금액 # 현금(예수금) + 주식(총평가금액) self.총누적손익 += stock.실현손익 self.총평가손익 = float(self.총평가금액 - self.총매입금액) # 보유한 주식의가치로 얻은 손익 self.총손익 = self.총평가손익 + self.총누적손익 # 개념상 손익 (현금의 손익 + 가치의 손익) try: self.총수익률 = self.총평가손익 / self.총매입금액 * 100 # 보유한 주식에 대한 총 수익률 except ZeroDivisionError as e: self.총수익률 = 0.0 self.총누적수익률 = float(self.총누적손익) / self.총최대매입금액 * 100 # 실현한 수익에 대한 총 수익률 self.sell_transaction(stock) if stock.보유수량 == 0: del self.보유주식[stock.code] else: self.보유주식[stock.code] = stock try: if cfg_mgr.debug_mode(): self.print_attr('SELL', stock.stock_name, stock.code, reason) # pdb.set_trace() except Exception: pdb.set_trace() print("Exception") return "" def gen_trading_info(self, stock, trading_type): """ self.time = "" # 또는 체결시간? self.trading_type = "" # [B]uy, [S]ell self.code = "" # 종목의 코드 self.stock_name = "" # 종목 이름 self.stock_price = "" # buy, sell 시 얼마에 사고팔았는지? self.loss_flag = None # 손해를 봤는지, 안봤는지 (buy 일땐 None, sell 일땐 True/False self.profit_amount = 0 # 손해 또는 이익을 본 금액 (sell에서만 사용) self.profit_rate = 0.0 # 손해 또는 이익을 본 비율 (sell에서만 사용) :return: """ tr = Trading(trading_type) # 속성값이 겹치지 않게 잘 관리되어야 함. # 겹치는 속성이 있다면, Account속성에는 '총' 붙이도록 함. stock_index = stock.get_core_index() account_index = self.get_core_index() # temp code - allow to duplicate 'timestamp' ret = (set(stock_index) & set(account_index)) - set('timestamp') if bool(ret): self.logger.error("{} is duplicate index. program exit") exit(-1) tr = common.copy_attr(stock, tr, stock_index) tr = common.copy_attr(self, tr, account_index) return tr def sell_transaction(self, stock): """ :return: """ tr = self.gen_trading_info(stock, constant.SELL_TRADING_TYPE) self.trading_history.record_sell_transaction(tr) @common.type_check def update_buy(self, stock: Stock, price_per_stock: int, amount: int, reason: str): """특정 주식을 매수했을때, 계좌정보를 업데이트 :param code: :param price_per_stock: :param amount: :return: """ # stock 객체 업데이트 stock.trading_reason = reason stock.update_buy(price_per_stock, amount) if cfg_mgr.debug_mode(): pass # self.print_attr('BUY', stock.stock_name, stock.code, reason) # need to move to util/common pkg # pdb.set_trace() # 계좌 정보 업데이트 self.예수금 -= stock.매매금액 self.총평가금액 += stock.평가금액변동 self.총매입금액 += stock.매입금액변동 self.총최대매입금액 = max(self.총매입금액, self.총최대매입금액) self.추정자산 = self.예수금 + self.총평가금액 # 현금(예수금) + 주식(총평가금액) # self.총누적손익 += self.총누적손익 --> 변동없음 self.총평가손익 = float(self.총평가금액 - self.총매입금액) # 보유한 주식의가치로 얻은 손익 self.총손익 = self.총평가손익 + self.총누적손익 # 개념상 손익 (현금의 손익 + 가치의 손익) self.총수익률 = self.총평가손익 / self.총매입금액 * 100 # 보유한 주식에 대한 총 수익률 self.총누적수익률 = float(self.총누적손익) / self.총최대매입금액 * 100 # 실현한 수익에 대한 총 수익률 # 보유주식에 포함 self.보유주식[stock.code] = stock self.buy_transaction(stock) try: if cfg_mgr.debug_mode(): self.print_attr('BUY', stock.stock_name, stock.code, reason) # need to move to util/common pkg # pdb.set_trace() except Exception: # pdb.set_trace() print("Exception") def buy_transaction(self, stock): """ :return: """ tr = self.gen_trading_info(stock, constant.BUY_TRADING_TYPE) self.trading_history.record_buy_transaction(tr) def revaluate(self): """주식가치가 변경(현재가 변경)되면서 계좌에 업데이트 해야 하는 정보를 모두 업데이트 총평가금액 추정자산 = 예수금 + 총평가금액 총수익률 = 총평가금액 - 총매입금액 / 총매입금액 * 100 총손익 = (총평가금액 - 총매입금액) + 실현손익 :return: """ # self.총평가금액 = 0 for stock in self.보유주식.values(): # if cfg_mgr.debug_mode(): # self.print_attr('KEEP', stock.stock_name, stock.code, 'CHANGE_PRICE') # need to move to util/common pkg # pdb.set_trace() # 계좌 정보 업데이트 # self.예수금 -= stock.매매금액 self.총평가금액 += stock.평가금액변동 # self.총매입금액 += stock.매입금액변동 # self.총최대매입금액 = max(self.총매입금액, self.총최대매입금액) self.추정자산 = self.예수금 + self.총평가금액 # 현금(예수금) + 주식(총평가금액) # self.총누적손익 += self.총누적손익 --> 변동없음 self.총평가손익 = float(self.총평가금액 - self.총매입금액) # 보유한 주식의가치로 얻은 손익 self.총손익 = self.총평가손익 + self.총누적손익 # 개념상 손익 (현금의 손익 + 가치의 손익) self.총수익률 = self.총평가손익 / self.총매입금액 * 100 # 보유한 주식에 대한 총 수익률 # self.총누적수익률 = float(self.총누적손익) / self.총최대매입금액 * 100 # 실현한 수익에 대한 총 수익률 # if cfg_mgr.debug_mode(): # self.print_attr('KEEP', stock.stock_name, stock.code, 'CHANGE_PRICE') # need to move to util/common pkg # pdb.set_trace() def update_account_value(self, timestamp): """보유주식을 현 timestamp에 맞게 내부 정보를 업데이트 한다. 변경된 현재가를 기준으로 아래 필드가 함께 업데이트 된다. self.수익률 = (self.현재가 - self.매입평균가) / self.매입평균가 * 100 self.평가손익 = self.총매입금액 * self.수익률 self.총평가금액 = self.현재가 * self.보유수량 :param timestamp: :return: """ self.timestamp = timestamp if not bool(self.보유주식): return for stock in self.보유주식.values(): stock.update_stock_value(timestamp) self.revaluate() def add_stocks(self, code_list): """보유주식에 포함시킨다. :param code: :return: """ for code in code_list: self.보유주식[code] = Stock.get_instance(code) def set_tick1_data(self, target_date): """보유한 주식(Stock)에 대해 tick1 data를 생성한다. :param target_date: :return: """ for stock in self.보유주식.values(): stock.gen_time_series_sec1(target_date) def get_tick_data(self, code): """ :param code: :return: """ return self.보유주식[code].tick_data def get_balance(self): """현재 잔고를 반환 :return: """ return self.balance def has_code(self, code): """해당 종목을 현재 보유중인지 :param code: :return: """ return code in self.보유주식 def get_stock_count(self): """현재 보유중인 종목의 갯수를 반환 :return: """ return self.get_code_count() def get_code_count(self): """현재 보유중인 종목의 갯수를 반환 :return: """ return len(self.get_code_list_in_account()) def get_code_list_in_account(self): """현재 보유중인 종목코드를 반환 :return: """ return self.보유주식.keys() def get_stock_list_in_account(self): """현재 보유중인 종목(Stock)을 반환 :return: """ return [stock for stock in self.보유주식.values()] def clear_stock(self, stock, price, reason): """보유한 주식중 특정 종목 일괄 청산 :param code: :return: """ stock.trading_reason = reason stock.update_sell(int(price), int(stock.보유수량)) def all_clear_stocks(self, timestamp): """보유한 주식을 모두 일괄 청산 :return: """ for stock in self.보유주식.values(): self.clear_stock(stock, stock.get_curr_price(timestamp), constant.TRADING_TIME_EXPIRED) def get_core_index(self): """Account 객체의 핵심 지표 리스트. 반드시 Account 객체의 속성값과 동기화가 되어야 함. :return: """ return self.core_index def check_core_index(self): """계좌의 핵심 지표들이 모두 설정되었는지 check. 누락된 지표가 있다면 Exception 발생하고 프로그램 종료 :return: """ for index in self.get_core_index(): self.__getattribute__(index) def is_empty(self): return bool(self.보유주식)