import time import os import math import sqlite3 from datetime import datetime, timedelta from hts.HTS import HTS from hts.OrderType import OrderType from hts.BuySellChecker import BuySellChecker from hts.OrderChecker import OrderChecker from stock.util.LabelChecker import LabelChecker from stock.util.SlackBot import SlackBot from stock.analysis.StockStatus import StockStatus class HTS_etf (HTS): RESOURCE_PATH = None stock_code = None buy_count = None orderChecker = None buySellChecker = None labelChecker = None slackBot = None stockStatus = None def __init__(self, RESOURCE_PATH): super().__init__(RESOURCE_PATH) self.RESOURCE_PATH = RESOURCE_PATH self.orderChecker = OrderChecker(self.RESOURCE_PATH, "ETF") self.buySellChecker = BuySellChecker() self.labelChecker = LabelChecker(RESOURCE_PATH) self.slackBot = SlackBot() self.stockStatus = StockStatus(RESOURCE_PATH) return def connect2StockDB(self): self.conn_stock = sqlite3.connect(os.path.join(self.RESOURCE_PATH, "stock.db")) self.cursor_stock = self.conn_stock.cursor() return def disconnectStockDB(self): self.cursor_stock.close() self.conn_stock.close() return def sellStocks(self, stock_code=None, bs_sell_price=None): check = False jangoDic = self.requstJango() if jangoDic and len(jangoDic.keys()) > 0: for code in jangoDic: if stock_code is not None: if code == "A"+stock_code and bs_sell_price is not None: if jangoDic[code]['매도가능'] > 0: if 2 < jangoDic[code]['평가손익']: self.requestOrder(OrderType.sell, code[1:], jangoDic[code]['매도가능'], bs_sell_price) self.slackBot.post_to_slack(code, jangoDic[code]['종목명'], "SELL", bs_sell_price, jangoDic[code]['매도가능']) check = True else: continue else: if jangoDic[code]['매도가능'] > 0: if 3 < jangoDic[code]['평가손익']: # 3% 이상 시 수익 매도 currentStock = self.currentStock(code[1:]) self.requestOrder(OrderType.sell, code[1:], jangoDic[code]['매도가능'], currentStock['close']) self.slackBot.post_to_slack(code, jangoDic[code]['종목명'], "SELL", currentStock['close'], jangoDic[code]['매도가능']) check = True return check def getSellingPrice(self, log_time, stock_code, final_price, without_loss=False): # final_price와 diff를 받으면, 해당 가격으로 그냥 매도한다는 의미 # final_price와 diff가 None이면 장부가와 final 중 max로 팔겠다는 의미 # final_price가 0이고 diff가 None이면 장부가로 팔겠다는 의미임 orderNum = None jangoDic = self.requstJango() if jangoDic and len(jangoDic.keys()) > 0: for code in jangoDic: if jangoDic[code]['매도가능'] > 0: if without_loss: if jangoDic[code]['장부가']*0.07 < jangoDic[code]['장부가'] - final_price: sell_price = jangoDic[code]['장부가'] if code == "A" + stock_code: orderNum = self.requestOrder(OrderType.sell, stock_code, jangoDic[code]['매도가능'], sell_price) return orderNum, log_time.strftime('%Y%m%d %H%M%S'), jangoDic[code]['매도가능'], sell_price else: max_price = max(jangoDic[code]['장부가'], final_price) sell_price = (int(max_price) - int(max_price) % 5) + 5 if code == "A"+stock_code: orderNum = self.requestOrder(OrderType.sell, stock_code, jangoDic[code]['매도가능'], sell_price) return orderNum, log_time.strftime('%Y%m%d %H%M%S'), jangoDic[code]['매도가능'], sell_price return orderNum, None, None, None def makeTickData(self, data, mins=30): result = {"check": set(), "time": [], "open": [], "close": [], "high": [], "low": [], "vol": [], "label": []} for i in range(mins, len(data['time'])+1): result["check"].add(data['time'][i-1]) result["time"].append(data['time'][i-1]) result["open"].append(data['open'][i-mins]) result["close"].append(data['close'][i-1]) result["high"].append(max(data['high'][i - mins: i])) result["low"].append(min(data['low'][i - mins: i])) result["vol"].append(sum(data['vol'][i - mins: i])) return result def getSlowK(self, stock_code): slow_k, p_slow_k, slow_k_week, p_slow_k_week, slow_k_month, p_slow_k_month = -1, -1, -1, -1, -1, -1 self.cursor_stock.execute('select stochastic_slow_k, max(ymd) from stock_analysis where code=? group by 1 order by ymd desc',(stock_code,)) items = self.cursor_stock.fetchall() if items is not None and len(items) > 1: for i, item in enumerate(items): if i == 0: slow_k = item[0] elif i == 1: p_slow_k = item[0] else: break self.cursor_stock.execute('select stochastic_slow_k, max(ymd) from stock_analysis_weekly where code=? group by 1 order by ymd desc', (stock_code, )) items = self.cursor_stock.fetchall() if items is not None and len(items) > 1: for i, item in enumerate(items): if i == 0: slow_k_week = item[0] elif i == 1: p_slow_k_week = item[0] else: break self.cursor_stock.execute('select stochastic_slow_k, max(ymd) from stock_analysis_monthly where code=? group by 1 order by ymd desc',(stock_code,)) items = self.cursor_stock.fetchall() if items is not None and len(items) > 1: for i, item in enumerate(items): if i == 0: slow_k_month = item[0] elif i == 1: p_slow_k_month = item[0] else: break if slow_k is None or p_slow_k is None: slow_k , p_slow_k = -1, -1 if slow_k_week is None or p_slow_k_week is None: slow_k_week, p_slow_k_week = -1, -1 if slow_k_month is None or p_slow_k_month is None: slow_k_month, p_slow_k_month = -1, -1 return slow_k, p_slow_k, slow_k_week, p_slow_k_week, slow_k_month, p_slow_k_month def getBuyCount(self, stock_code, bs_buy_price, slow_k_kospi, p_slow_k_kospi, slow_k_week_kospi, p_slow_k_week_kospi, slow_k_month_kospi, p_slow_k_month_kospi): base_price = 10000 # kospi 상태 파악 type_kospi = {'day':1, 'week':1, 'month':1} if slow_k_kospi < p_slow_k_kospi: if slow_k_kospi < 20: type_kospi['day'] = 4 if 20 < slow_k_kospi < 30: type_kospi['day'] = 3 if 30 < slow_k_kospi < 40: type_kospi['day'] = 2 if slow_k_week_kospi < p_slow_k_week_kospi: if slow_k_week_kospi < 20: type_kospi['week'] = 4 if 20 < slow_k_week_kospi < 30: type_kospi['week'] = 3 if 30 < slow_k_week_kospi < 40: type_kospi['week'] = 2 if slow_k_month_kospi < p_slow_k_month_kospi: if slow_k_month_kospi < 20: type_kospi['month'] = 4 if 20 < slow_k_month_kospi < 30: type_kospi['month'] = 3 if 30 < slow_k_month_kospi < 40: type_kospi['month'] = 2 type_stock = {'day':1, 'week':1, 'month':1} slow_k, p_slow_k, slow_k_week, p_slow_k_week, slow_k_month, p_slow_k_month = self.getSlowK(stock_code) if slow_k < p_slow_k: if slow_k < 20: type_stock['day'] = 4 if 20 < slow_k < 30: type_stock['day'] = 3 if 30 < slow_k < 40: type_stock['day'] = 2 if slow_k_week < p_slow_k_week: if slow_k_week < 20: type_stock['week'] = 4 if 20 < slow_k_week < 30: type_stock['week'] = 3 if 30 < slow_k_week < 40: type_stock['week'] = 2 if slow_k_month < p_slow_k_month: if slow_k_month < 20: type_stock['month'] = 4 if 20 < slow_k_month < 30: type_stock['month'] = 3 if 30 < slow_k_month < 40: type_stock['month'] = 2 if (type_kospi['day'] == 1 and type_kospi['week'] == 1 and type_kospi['month'] == 1 and type_stock['day'] == 1 and type_stock['week'] == 1 and type_stock['month'] == 1): return 0 max_price = math.log(type_kospi['day']*type_kospi['week']*type_kospi['month']*type_stock['day']*type_stock['week']*type_stock['month'], 1.3) * base_price buy_count = int(math.floor(max_price / bs_buy_price)) return buy_count def buyRealTime(self, today, stocks, analyzed_day=1000): print ("START...") THIS_TIME = datetime.now() slow_k_kospi, p_slow_k_kospi, slow_k_week_kospi, p_slow_k_week_kospi, slow_k_month_kospi, p_slow_k_month_kospi = self.getSlowK("^KS11") LAST_DATA = {} for stock in stocks: LAST_DATA[stock['stock_code']] = self.getLastData(stock['stock_code'], today) while datetime.strptime(today + " 070000", '%Y%m%d %H%M%S') < THIS_TIME < datetime.strptime(today + " 153100", '%Y%m%d %H%M%S'): if datetime.strptime(today + " 090000", '%Y%m%d %H%M%S') < THIS_TIME < datetime.strptime(today + " 151500", '%Y%m%d %H%M%S'): # 매도를 체크한다. self.sellStocks() for idx, stock in enumerate(stocks): time.sleep(0.1) print("%5d: %8s, %-50s"%(idx, stock['stock_code'], stock['stock_name'])) try: # 데이터를 가지고 온다. result = self.getRealTime(stock['stock_code'], today, LAST_DATA[stock['stock_code']]) except: print("#ERROR:", stock['stock_code'], stock['stock_name']) continue result_5 = self.makeTickData(result, mins=5) result_30 = self.makeTickData(result, mins=30) if len(result_30['time']) < 100: continue data = self.buySellChecker.analyze(result) data.drop(data.index[:len(data) - analyzed_day], inplace=True) # 5분 이동평균, RSI, MACD, 일목균형, 볼린저밴드 상/하단을 계산한다. data_5 = self.buySellChecker.analyze(result_5) # 분석일 데이터만 활용한다 (이전 데이터는 제거) data_5.drop(data_5.index[:len(data_5) - analyzed_day], inplace=True) # 30분 이동평균, RSI, MACD, 일목균형, 볼린저밴드 상/하단을 계산한다. data_30 = self.buySellChecker.analyze(result_30) # 분석일 데이터만 활용한다 (이전 데이터는 제거) data_30.drop(data_30.index[:len(data_30) - analyzed_day], inplace=True) # 사야 할 시점과 팔아야 할 시점을 체크한다. bsLine = self.buySellChecker.checkTransaction(data, data_5, data_30, isRealTime=True) bs_buy_price = bsLine['buy'][0] bs_buy_weight = bsLine['buy_weight'][0] bs_sell_price = bsLine['sell'][0] # 미체결 기록을 가져와서 10분 이상 된 매수 주문을 취소 한다. ORDER_LIST = self.requestOrderList() orderListToCancel = self.orderChecker.cancel(today, "A" + stock['stock_code'], ORDER_LIST, mins=10) if len(orderListToCancel) > 0: self.cancelOrderList(orderListToCancel) if bs_buy_price > 1000: if not self.orderChecker.exist(today, "A" + stock['stock_code'], hours=1): buy_count = self.getBuyCount(stock['stock_code'], bs_buy_price, slow_k_kospi, p_slow_k_kospi, slow_k_week_kospi, p_slow_k_week_kospi, slow_k_month_kospi, p_slow_k_month_kospi) if buy_count > 0: # 매수를 주문한다. orderNum = self.requestOrder(OrderType.buy, stock['stock_code'], buy_count , bs_buy_price) self.orderChecker.buy(today, "A" + stock['stock_code'], buy_count, bs_buy_price, orderNum) # slackbot에 메시지를 보냄 self.slackBot.post_to_slack(stock['stock_code'], stock['stock_name'], "BUY", bsLine['buy'][len(bsLine['buy']) - 1], buy_count) # 로그 출력 print("BUY", THIS_TIME.strftime('%Y%m%d %H%M%S'), orderNum, stock['stock_code'], stock['stock_name'], bs_buy_price, buy_count) if bs_sell_price > 1000: check = self.sellStocks(stock['stock_code'], bs_sell_price) if check: # slackbot에 메시지를 보냄 self.slackBot.post_to_slack(stock['stock_code'], stock['stock_name'], "SELL", bs_sell_price, 'ALL') # 로그 출력 print("SELL", THIS_TIME.strftime('%Y%m%d %H%M%S'), stock['stock_code'], stock['stock_name'], bs_sell_price) # 로그 출력 print("TIMECHECK: %s, code: %s, name: %s, buy: %d, sell: %d, avg5: %.2f, avg30: %.2f, open: %d, high: %d, low: %d, slow_k: %.2f, slow_k_5: %.2f, slow_k_30: %.2f" % (str(THIS_TIME), stock['stock_code'], stock['stock_name'], bs_buy_price, bs_sell_price, data["avg5"][0], data["avg30"][0], data["open"][0], data["high"][0], data["low"][0], data["slow_k"][0], data_5["slow_k"][0], data_30["slow_k"][0])) """ elif datetime.strptime(today + " 151530", '%Y%m%d %H%M%S') < THIS_TIME < datetime.strptime(today + " 151600", '%Y%m%d %H%M%S'): # 3시 15분 30초부터 3시 16분 사이는 잔량을 매도한다. if not final_sell_check: #### # 손해 보지 않는 가격에 매도한다. #### for stock in stocks: # 주문 리스트를 가져온다. orderList = self.requestOrderList() # 15:10:00 이후라면 모든 미체결 취소한다. self.cancelOrderList(orderList) # 매도 가격을 가져온다. result = self.getRealTime(stock['stock_code'], today, LAST_DATA[stock['stock_code']]) final_price = result["close"][len(result["close"]) - 1] orderNum, sell_time, jango, sell_price = self.getSellingPrice(THIS_TIME, stock['stock_code'], final_price, without_loss=True) # 로그 출력 print("SELL", sell_time, stock['stock_code'], stock['stock_name'], final_price, str(orderNum), jango, sell_price) final_sell_check = True """ time.sleep(3600) THIS_TIME = datetime.now() return True def updteTodayStock(self, stock_code, today_str): bsLine, data = self.labelChecker.makeCandidate(stock_code, today_str) self.labelChecker.updateLabel(stock_code, bsLine, data, today_str) return if __name__ == "__main__": today = datetime.today() PROJECT_HOME = os.getcwd() RESOURCE_PATH = os.path.join(PROJECT_HOME, "resources") stocks = [ #{"stock_code": "122630", "stock_name": "KODEX 레버리지"}, {"stock_code": "252670", "stock_name": "KODEX 200선물인버스2X"} ] hts = HTS_etf(RESOURCE_PATH) hts.connect2DB("hts.db") hts.connect2StockDB() today_str = today.strftime('%Y%m%d') hts.buyRealTime(today_str, stocks, analyzed_day=1000) db_filename = os.path.join(RESOURCE_PATH, "hts.db") hts.insertStockData(stocks, today) hts.disconnectStockDB() hts.disconnect() print ("done...")