From 7db724b31f7cefff39c4df59b599a53393c4579b Mon Sep 17 00:00:00 2001 From: dsyoon Date: Fri, 31 Mar 2023 23:51:55 +0900 Subject: [PATCH] init --- HTS_etf.py | 123 ++++++++----- HTS_etf_long.py | 393 ++++++++++++++++++++++++++++++++++++++++ HTS_etf_short.py | 365 +++++++++++++++++++++++++++++++++++++ resources/BuyCount.xlsx | Bin 18463 -> 41838 bytes resources/preprocess.py | 39 ++++ 5 files changed, 870 insertions(+), 50 deletions(-) create mode 100644 HTS_etf_long.py create mode 100644 HTS_etf_short.py create mode 100644 resources/preprocess.py diff --git a/HTS_etf.py b/HTS_etf.py index 988fd0f..d3a02a1 100644 --- a/HTS_etf.py +++ b/HTS_etf.py @@ -13,8 +13,8 @@ from stock.util.LabelChecker import LabelChecker from stock.util.SlackBot import SlackBot from stock.analysis.StockStatus import StockStatus -class HTS_etf (HTS): +class HTS_etf(HTS): RESOURCE_PATH = None stock_code = None buy_count = None @@ -56,11 +56,12 @@ class HTS_etf (HTS): 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 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]['매도가능']) + self.slackBot.post_to_slack(code, jangoDic[code]['종목명'], "SELL", bs_sell_price, + jangoDic[code]['매도가능']) check = True else: continue @@ -70,11 +71,11 @@ class HTS_etf (HTS): # 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]['매도가능']) + 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로 팔겠다는 의미 @@ -85,20 +86,20 @@ class HTS_etf (HTS): for code in jangoDic: if jangoDic[code]['매도가능'] > 0: if without_loss: - if jangoDic[code]['장부가']*0.07 < jangoDic[code]['장부가'] - final_price: + 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) + 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: + 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": [], @@ -109,12 +110,12 @@ class HTS_etf (HTS): "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]) + 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["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])) @@ -124,7 +125,9 @@ class HTS_etf (HTS): 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,)) + 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): @@ -135,7 +138,9 @@ class HTS_etf (HTS): 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, )) + 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): @@ -146,7 +151,9 @@ class HTS_etf (HTS): 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,)) + 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): @@ -158,7 +165,7 @@ class HTS_etf (HTS): break if slow_k is None or p_slow_k is None: - slow_k , p_slow_k = -1, -1 + 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: @@ -166,12 +173,13 @@ class HTS_etf (HTS): 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): + 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} + 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 @@ -185,7 +193,7 @@ class HTS_etf (HTS): 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} + 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 @@ -201,17 +209,21 @@ class HTS_etf (HTS): 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): + type_stock['day'] == 1 and type_stock['week'] == 1 and type_stock['month'] == 1): return 0 - if stock_code == "252670": # "KODEX 200선물인버스2X" + if stock_code == "252670": # "KODEX 200선물인버스2X" base_price = 100000 - 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 + 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 - if stock_code == "122630": # "KODEX 레버리지" + if stock_code == "122630": # "KODEX 레버리지" base_price = 100000 - max_price_tmp = math.log(type_kospi['day'] * type_kospi['week'] * type_kospi['month'] * type_stock['day'] * type_stock['week'] * type_stock['month'], 1.3) * base_price + max_price_tmp = math.log( + type_kospi['day'] * type_kospi['week'] * type_kospi['month'] * type_stock['day'] * type_stock['week'] * + type_stock['month'], 1.3) * base_price max_price = max_price_tmp - max_price buy_count = int(math.floor(max_price / bs_buy_price)) @@ -220,18 +232,21 @@ class HTS_etf (HTS): def buyRealTime(self, today, stocks, analyzed_day=1000): - print ("START...") + 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") + 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'): + 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'): + 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() @@ -240,7 +255,7 @@ class HTS_etf (HTS): time.sleep(0.1) - print("%5d: %8s, %-50s"%(idx, stock['stock_code'], stock['stock_name'])) + print("%5d: %8s, %-50s" % (idx, stock['stock_code'], stock['stock_name'])) try: # 데이터를 가지고 온다. @@ -282,59 +297,68 @@ class HTS_etf (HTS): if bs_buy_price > 1000: if not self.orderChecker.exist(today, "A" + stock['stock_code'], hours=9): - 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) + 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) + 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) + 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) + 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') + 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("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])) + 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 """ @@ -350,7 +374,6 @@ class HTS_etf (HTS): if __name__ == "__main__": - today = datetime.today() PROJECT_HOME = os.getcwd() @@ -418,4 +441,4 @@ if __name__ == "__main__": hts.disconnectStockDB() hts.disconnect() - print ("done...") + print("done...") \ No newline at end of file diff --git a/HTS_etf_long.py b/HTS_etf_long.py new file mode 100644 index 0000000..c51eac5 --- /dev/null +++ b/HTS_etf_long.py @@ -0,0 +1,393 @@ +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 getStockType(self, stock_code, short=False): + 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 + + + type_stock = {'day':1, 'week':10, 'month':100} + if slow_k > p_slow_k: + if slow_k < 10: type_stock['day'] = 10 + if 10 < slow_k < 20: type_stock['day'] = 7.5 + if 20 < slow_k < 30: type_stock['day'] = 5 + if 30 < slow_k < 40: type_stock['day'] = 2.5 + if slow_k_week > p_slow_k_week: + if slow_k_week < 10: type_stock['week'] = 100 + if 10 < slow_k_week < 20: type_stock['week'] = 75 + if 20 < slow_k_week < 30: type_stock['week'] = 50 + if 30 < slow_k_week < 40: type_stock['week'] = 25 + if slow_k_month > p_slow_k_month: + if slow_k_month < 10: type_stock['month'] = 1000 + if 10 < slow_k_month < 20: type_stock['month'] = 750 + if 20 < slow_k_month < 30: type_stock['month'] = 500 + if 30 < slow_k_month < 40: type_stock['month'] = 250 + + return type_stock + + def getBuyCount(self, bs_buy_price, kospi_type, stock_type): + + base_price = 10000 + log_base = 1.2 + p_k_m, p_k_w, p_k_d, p_s_m, p_s_w, p_s_d = 0.3, 0.2, 0.05, 0.25, 0.18, 0.02 + weight_1, weight_2, weight_3, weight_4, weight_5 = 0.5, 0.3, 0.14, 0.05, 0.01 + kospi_weight = weight_5 + if kospi_type['day'] == 10: kospi_weight = weight_1 + if kospi_type['day'] == 7.5: kospi_weight = weight_2 + if kospi_type['day'] == 5: kospi_weight = weight_3 + if kospi_type['day'] == 2.5: kospi_weight = weight_4 + stock_weight = weight_5 + if stock_type['day'] == 10: stock_weight = weight_1 + if stock_type['day'] == 7.5: stock_weight = weight_2 + if stock_type['day'] == 5: stock_weight = weight_3 + if stock_type['day'] == 2.5: stock_weight = weight_4 + + max_price = math.log( + kospi_weight * p_k_m * kospi_type['month'] + + kospi_weight * p_k_w * kospi_type['week'] + + kospi_weight * p_k_d * kospi_type['day'] + + stock_weight * p_s_m * stock_type['month'] + + stock_weight * p_s_w * stock_type['week'] + + stock_weight * p_s_d * stock_type['day'], log_base) * base_price + + buy_count = 0 + if max_price > 1: + 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() + kospi_type = self.getStockType("^KS11", short=False) + + 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) + + # 현재 매수가 + bs_buy_price = data["close"][len(data["close"]) - 1] + + # 미체결 기록을 가져와서 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=9): + stock_type = self.getStockType(stock['stock_code'], short=False) + buy_count = self.getBuyCount(bs_buy_price, kospi_type, stock_type) + + 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) + + # 로그 출력 + 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") + + # KODEX 인버스 * 2 + stocks = [ + {"stock_code": "122630", "stock_name": "KODEX 레버리지"}, + {"stock_code": "305720", "stock_name": "KODEX 2차전지산업"}, + {"stock_code": "102780", "stock_name": "KODEX 삼성그룹"}, + {"stock_code": "139260", "stock_name": "TIGER 200 IT"}, + {"stock_code": "091180", "stock_name": "KODEX 자동차"}, + {"stock_code": "401470", "stock_name": "KODEX K-메타버스액티브"}, + {"stock_code": "329200", "stock_name": "TIGER 리츠부동산인프라"}, + {"stock_code": "091170", "stock_name": "KODEX 은행"}, + {"stock_code": "091160", "stock_name": "KODEX 반도체"}, + {"stock_code": "161510", "stock_name": "ARIRANG 고배당주"}, + {"stock_code": "228800", "stock_name": "TIGER 여행레저"}, + {"stock_code": "150460", "stock_name": "TIGER 중국소비테마"}, + {"stock_code": "143860", "stock_name": "TIGER 헬스케어"}, + {"stock_code": "228810", "stock_name": "TIGER 미디어컨텐츠"}, + {"stock_code": "139220", "stock_name": "TIGER 200 건설"}, + {"stock_code": "139280", "stock_name": "TIGER 경기방어"}, + {"stock_code": "322400", "stock_name": "HANARO e커머스"}, + {"stock_code": "157490", "stock_name": "TIGER 소프트웨어"}, + {"stock_code": "228790", "stock_name": "TIGER 화장품"}, + {"stock_code": "139230", "stock_name": "TIGER 200 중공업"}, + {"stock_code": "396500", "stock_name": "TIGER Fn반도체TOP10"}, + {"stock_code": "365000", "stock_name": "TIGER KRX인터넷K-뉴딜"}, + {"stock_code": "102970", "stock_name": "KODEX 증권"}, + {"stock_code": "117680", "stock_name": "KODEX 철강"}, + {"stock_code": "244580", "stock_name": "KODEX 바이오"}, + {"stock_code": "266360", "stock_name": "KODEX 미디어&엔터테인먼트"}, + {"stock_code": "375770", "stock_name": "KODEX 탄소효율그린뉴딜"}, + {"stock_code": "364990", "stock_name": "TIGER KRX게임K-뉴딜"}, + {"stock_code": "388420", "stock_name": "KBSTAR 비메모리반도체액티브"}, + {"stock_code": "117460", "stock_name": "KODEX 에너지화학"}, + {"stock_code": "300950", "stock_name": "KODEX 게임산업"}, + {"stock_code": "266410", "stock_name": "KODEX 필수소비재"}, + {"stock_code": "140700", "stock_name": "KODEX 보험"}, + {"stock_code": "139270", "stock_name": "TIGER 200 금융"}, + {"stock_code": "395160", "stock_name": "KODEX Fn시스템반도체"}, + {"stock_code": "140710", "stock_name": "KODEX 운송"}, + {"stock_code": "139240", "stock_name": "TIGER 200 철강소재"}, + {"stock_code": "395150", "stock_name": "KODEX Fn웹툰&드라마"}, + {"stock_code": "307510", "stock_name": "TIGER 의료기기"}, + {"stock_code": "315270", "stock_name": "TIGER 200커뮤니케이션서비스"}, + {"stock_code": "132030", "stock_name": "KODEX 골드선물(H)"}, + {"stock_code": "144600", "stock_name": "KODEX 은선물(H)"}, + {"stock_code": "261220", "stock_name": "KODEX WTI원유선물(H)"}, + {"stock_code": "271050", "stock_name": "KODEX WTI원유선물인버스(H)"}, + {"stock_code": "138910", "stock_name": "KODEX 구리선물(H)"} + ] + + 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...") diff --git a/HTS_etf_short.py b/HTS_etf_short.py new file mode 100644 index 0000000..bc8d919 --- /dev/null +++ b/HTS_etf_short.py @@ -0,0 +1,365 @@ +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 getStockType(self, stock_code, short=False): + 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 + + + type_stock = {'day':1, 'week':10, 'month':100} + if short: + if slow_k > p_slow_k: + if slow_k < 10: type_stock['day'] = 10 + if 10 < slow_k < 20: type_stock['day'] = 7.5 + if 20 < slow_k < 30: type_stock['day'] = 5 + if 30 < slow_k < 40: type_stock['day'] = 2.5 + if slow_k_week > p_slow_k_week: + if slow_k_week < 10: type_stock['week'] = 100 + if 10 < slow_k_week < 20: type_stock['week'] = 75 + if 20 < slow_k_week < 30: type_stock['week'] = 50 + if 30 < slow_k_week < 40: type_stock['week'] = 25 + if slow_k_month > p_slow_k_month: + if slow_k_month < 10: type_stock['month'] = 1000 + if 10 < slow_k_month < 20: type_stock['month'] = 750 + if 20 < slow_k_month < 30: type_stock['month'] = 500 + if 30 < slow_k_month < 40: type_stock['month'] = 250 + else: + if slow_k < p_slow_k: + if slow_k > 90: type_stock['day'] = 10 + if 80 < slow_k < 90: type_stock['day'] = 7.5 + if 70 < slow_k < 80: type_stock['day'] = 5 + if 60 < slow_k < 70: type_stock['day'] = 2.5 + if slow_k_week > p_slow_k_week: + if slow_k_week > 90: type_stock['week'] = 100 + if 80 < slow_k_week < 90: type_stock['week'] = 75 + if 70 < slow_k_week < 80: type_stock['week'] = 50 + if 60 < slow_k_week < 70: type_stock['week'] = 25 + if slow_k_month > p_slow_k_month: + if slow_k_month > 90: type_stock['month'] = 1000 + if 80 < slow_k_month < 90: type_stock['month'] = 750 + if 70 < slow_k_month < 80: type_stock['month'] = 500 + if 60 < slow_k_month < 70: type_stock['month'] = 250 + return type_stock + + def getBuyCount(self, bs_buy_price, kospi_type, stock_type): + + base_price = 10000 + log_base = 1.2 + p_k_m, p_k_w, p_k_d, p_s_m, p_s_w, p_s_d = 0.3, 0.2, 0.05, 0.25, 0.18, 0.02 + weight_1, weight_2, weight_3, weight_4, weight_5 = 0.5, 0.3, 0.14, 0.05, 0.01 + kospi_weight = weight_5 + if kospi_type['day'] == 10: kospi_weight = weight_1 + if kospi_type['day'] == 7.5: kospi_weight = weight_2 + if kospi_type['day'] == 5: kospi_weight = weight_3 + if kospi_type['day'] == 2.5: kospi_weight = weight_4 + stock_weight = weight_5 + if stock_type['day'] == 10: stock_weight = weight_1 + if stock_type['day'] == 7.5: stock_weight = weight_2 + if stock_type['day'] == 5: stock_weight = weight_3 + if stock_type['day'] == 2.5: stock_weight = weight_4 + + max_price = math.log( + kospi_weight * p_k_m * kospi_type['month'] + + kospi_weight * p_k_w * kospi_type['week'] + + kospi_weight * p_k_d * kospi_type['day'] + + stock_weight * p_s_m * stock_type['month'] + + stock_weight * p_s_w * stock_type['week'] + + stock_weight * p_s_d * stock_type['day'], log_base) * base_price + + buy_count = 0 + if max_price > 1: + 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() + kospi_type = self.getStockType("^KS11", short=False) + + 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) + + # 현재 매수가 + bs_buy_price = data["close"][len(data["close"]) - 1] + + # 미체결 기록을 가져와서 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=9): + stock_type = self.getStockType(stock['stock_code'], short=True) + buy_count = self.getBuyCount(bs_buy_price, kospi_type, stock_type) + + 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) + + # 로그 출력 + 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") + + # KODEX 인버스 * 2 + stocks = [ + {"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...") diff --git a/resources/BuyCount.xlsx b/resources/BuyCount.xlsx index 3afa0d1b9537d186ca9d6911a28c22b2d2ec98d4..d69dd0ef872d681cf75536588cb8febdb2b78856 100644 GIT binary patch literal 41838 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

;mHpvX zQ2fUg>5xyEf3(aQ%Af8R4_+bPi&=i4d18!FXGQ;*yci*@`;MTauN^>J96 zgKnpFf;Af%PMzin>j*QDNHRp0rs3V2iTQ~~vAaK$;orN&MyN%%^M?@<5Qs;Io3!iD z=JjTX`423+bm#o`QdR41U}h?c-!?yh>S<4(fqz~*BDH@=lA6`m9AoM=z>=c1-P8JR z{@hE!u_aJk){!j?k~#m_VB*O~LRkEMWmx}O;2IaJ$4Z35ZS`Z0UFli(_x3s zp^5kfs8CT3p;kiKQm(LdMdYLet&RdgRpPKJMMz(6P*Ed)o?eBX(W6Z0eJTdAL#XM> zLo7&(F+~-}dnmdStdl`#feJ*DVVlm@l`Kv@Ni}rN)(NRze^T1EO~C8-?D2z#ne8E! zOls1EN$o{7YoOO>&!z=_^bMD)E@=UkOlmuaB~E98hreS?i+nH2mMEidpBCv|&a~yW ze0|(NSY@h98`Z9K#n#iC)})@&R*6HG>rzSCs(coc<8C_V4Ns7JWhZm01B;CP+jnpd z3R%&MUC%A+9n%%`-X4k6s)#rt_Ji;V(edY=zlIPlS11McxyAtqLEv1_-HA?nWWh;n=0Jf{Ck6#sSU$+WB_?RTtr9Wv-dPO@RK)`yJ~P?vSj`o>HWn%iI> zSP8jG-yTdk;s45~nW!zQ;&$7`nU{d5jiH%IL7w|5nSJ&9)6MSd!{X}8{d(o=)8Xyv z%jWCzX{*1_ZM)Cw$?og@q5tcM|D&1Bs{iY;KhFN-_vAi*zuOn{{0<*|wN=08;alL6 zb(1LB@yBL0WtP3VBR-I@VC`1vEFS$y5dMV{%0DiU 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 diff --git a/resources/preprocess.py b/resources/preprocess.py new file mode 100644 index 0000000..240656e --- /dev/null +++ b/resources/preprocess.py @@ -0,0 +1,39 @@ +log_base = "$E$1" + +weight_1="$I$1" +weight_2="$I$2" +weight_3="$I$3" +weight_4="$I$4" +weight_5="$I$5" + +p_k_m = "$A$9" +p_k_w = "$B$9" +p_k_d = "$C$9" +p_s_m = "$D$9" +p_s_w = "$E$9" +p_s_d = "$F$9" + +base_price_1 = "$B$2" +base_price_2 = "$B$3" +base_price_3 = "$B$4" +base_price_4 = "$B$5" +base_price_5 = "$B$6" + +line_no_a, line_no_b, line_no_c = 11, 11, 11 +for a in range(1, 6): + for b in range(1, 6): + for c in range(1, 6): + weight = weight_1 + if c == 2: weight = weight_2 + if c == 3: weight = weight_3 + if c == 4: weight = weight_4 + if c == 5: weight = weight_5 + p1 = "=LOG("+weight+"*"+p_k_m+"*$A$"+str(line_no_a)+"+"+weight+"*"+p_k_w+"*$B$"+str(line_no_b)+"+"+weight+"*"+p_k_d+"*C"+str(line_no_c)+"+"+weight+"*"+p_s_m+"*$D$"+str(line_no_a)+"+"+weight+"*"+p_s_w+"*$E$"+str(line_no_b)+"+"+weight+"*"+p_s_d+"*F"+str(line_no_c)+", "+log_base+") * "+base_price_1 + p2 = "=LOG("+weight+"*"+p_k_m+"*$A$"+str(line_no_a)+"+"+weight+"*"+p_k_w+"*$B$"+str(line_no_b)+"+"+weight+"*"+p_k_d+"*C"+str(line_no_c)+"+"+weight+"*"+p_s_m+"*$D$"+str(line_no_a)+"+"+weight+"*"+p_s_w+"*$E$"+str(line_no_b)+"+"+weight+"*"+p_s_d+"*F"+str(line_no_c)+", "+log_base+") * "+base_price_2 + p3 = "=LOG("+weight+"*"+p_k_m+"*$A$"+str(line_no_a)+"+"+weight+"*"+p_k_w+"*$B$"+str(line_no_b)+"+"+weight+"*"+p_k_d+"*C"+str(line_no_c)+"+"+weight+"*"+p_s_m+"*$D$"+str(line_no_a)+"+"+weight+"*"+p_s_w+"*$E$"+str(line_no_b)+"+"+weight+"*"+p_s_d+"*F"+str(line_no_c)+", "+log_base+") * "+base_price_3 + p4 = "=LOG("+weight+"*"+p_k_m+"*$A$"+str(line_no_a)+"+"+weight+"*"+p_k_w+"*$B$"+str(line_no_b)+"+"+weight+"*"+p_k_d+"*C"+str(line_no_c)+"+"+weight+"*"+p_s_m+"*$D$"+str(line_no_a)+"+"+weight+"*"+p_s_w+"*$E$"+str(line_no_b)+"+"+weight+"*"+p_s_d+"*F"+str(line_no_c)+", "+log_base+") * "+base_price_4 + p5 = "=LOG("+weight+"*"+p_k_m+"*$A$"+str(line_no_a)+"+"+weight+"*"+p_k_w+"*$B$"+str(line_no_b)+"+"+weight+"*"+p_k_d+"*C"+str(line_no_c)+"+"+weight+"*"+p_s_m+"*$D$"+str(line_no_a)+"+"+weight+"*"+p_s_w+"*$E$"+str(line_no_b)+"+"+weight+"*"+p_s_d+"*F"+str(line_no_c)+", "+log_base+") * "+base_price_5 + print (p1 + "\t" + p2 + "\t" + p3 + "\t" + p4 + "\t" + p5) + line_no_c += 1 + line_no_b += 5 + line_no_a += 25