diff --git a/stock/analysis/AnalyzerSqlite.py b/stock/analysis/AnalyzerSqlite.py index 9e8f91d..eb0bf73 100644 --- a/stock/analysis/AnalyzerSqlite.py +++ b/stock/analysis/AnalyzerSqlite.py @@ -262,35 +262,7 @@ class AnalyzerSqlite: return energy1, energy2 - def makeDir(self, dir_code, dir_name): - if os.path.isdir(self.outPath + "/" + dir_code+"_"+dir_name): - os.rmdir(self.outPath + "/" + dir_code+"_"+dir_name) - os.mkdir(self.outPath + "/" + dir_code+"_"+dir_name) - return - - def makeDirectory(self, outPath): - self.outPath = outPath - if os.path.isdir(outPath): - shutil.rmtree(outPath) - os.mkdir(outPath) - self.makeDir("0", "final") - - self.makeDir("1", "monthly_macd_-300이하") - self.makeDir("2", "monthly_rsi_20이하") - self.makeDir("3", "monthly_EV하단위로_올라옴") - - self.makeDir("11", "daily_macd_-1000이하") - self.makeDir("12", "daily_weekly_monthly_rsi_10_20_30이하") - self.makeDir("13", "daily_이전에_없던_거래량") - self.makeDir("14", "daily_이격도") - self.makeDir("15", "daily_낙폭과대") - self.makeDir("16", "daily_EV하단_내려옴") - self.makeDir("17", "daily_BB하단_내려옴") - self.makeDir("18", "daily_rsi_20이하") - - return - - def writeFile(self, dir_code, dir_name, CODE, NAME, top, stock, state, final_status_count=-1): + def writeFile(self, dir_name, CODE, NAME, top, stock, state, final_status_count=-1): # 3년 이내 한번이라도 영업이익이 났는지 체크를 함 fnguide = None if CODE in self.fnguide: @@ -304,15 +276,11 @@ class AnalyzerSqlite: if check: fig = self.draw(stock) - title = "%s (%s), %d, %s 차트 (URL1, URL2)" % (NAME, CODE, stock['close'][0], dir_code+"_"+dir_name, CODE, CODE) + title = "%s (%s), %d, %s 차트 (URL1, URL2)" % (NAME, CODE, stock['close'][0], dir_name, CODE, CODE) fig['layout'].update(title=title) - fileName = self.outPath + "/" + dir_code+"_"+dir_name - - if dir_code in ("0", "6", "26"): - fileName = "%s/%s_%s_%s_%s_%s.html" % (fileName, str(final_status_count), top, NAME.replace(" ", ""), CODE, state) - else: - fileName = "%s/%s_%s_%s_%s.html" % (fileName, state, NAME.replace(" ", ""), CODE, top) + fileName = self.outPath + "/" + dir_name + fileName = "%s/%s_%s_%s_%s.html" % (fileName, state, NAME.replace(" ", ""), CODE, top) po.write_html(fig, file=fileName, auto_open=False) return @@ -427,6 +395,37 @@ class AnalyzerSqlite: return stock + + def makeDir(self, dir_name): + if os.path.isdir(self.outPath + "/" + dir_name): + os.rmdir(self.outPath + "/" + dir_name) + os.mkdir(self.outPath + "/" + dir_name) + return + + def makeDirectory(self, outPath): + self.outPath = outPath + if os.path.isdir(outPath): + shutil.rmtree(outPath) + os.mkdir(outPath) + self.makeDir("final") + + self.makeDir("monthly_macd_n이하") + self.makeDir("monthly_rsi_n이하") + + self.makeDir("weekly_macd_n이하") + self.makeDir("weekly_rsi_n이하") + + self.makeDir("daily_macd_n이하") + self.makeDir("daily_rsi_n이하") + + self.makeDir("daily_이전에_없던_거래량") + self.makeDir("daily_이격도") + self.makeDir("daily_낙폭과대") + self.makeDir("daily_EV하단_내려옴") + self.makeDir("daily_BB하단_내려옴") + + return + # 후보 찾기 def findCandidates(self, outPath): self.makeDirectory(outPath) @@ -460,161 +459,113 @@ class AnalyzerSqlite: stock_weekly = self.getStockData(stockAnalysisWeeklyTableName, CODE) stock_monthly = self.getStockData(stockAnalysisMonthlyTableName, CODE) - status = "" final_status = "" final_status_count = 0 - # 거래량이 100만 이상이고, 종가가 1천원 이상인지 체크 (https://happpy-rich.tistory.com/94) - if stock_weekly['volume'][0] > 100000 and stock_weekly['close'][0] > 1000: + # 거래량이 10만 이상이고, 종가가 1천원 이상인지 체크 (https://happpy-rich.tistory.com/94) + if stock_daily['volume'][0] > 100000 and stock_daily['close'][0] > 1000: # 종목 상태 체크 분석 - # MACD가 -300 이하에서 macd가 macds 위로 올라온 경우 - if len(stock_monthly['close']) > 1: - if stock_monthly['macd'][1] is not None and stock_monthly['macds'][1] is not None and stock_monthly['macd'][0] is not None and stock_monthly['macds'][0] is not None: - if stock_monthly['macd'][0] <= -300: - if stock_monthly['macd'][1] < stock_monthly['macds'][1] and stock_monthly['macds'][0] < stock_monthly['macds'][0]: - dir_code = "1" - dir_name = "monthly_macd_-300이하" - final_status_count += 1 - status = "{:.2f}".format(stock_monthly['macd'][0]) + "_" + status - self.writeFile(dir_code, dir_name, CODE, NAME, top, stock_monthly, status) - # RSI가 20 이하인 경우, rsi가 rsis위로 올라온 경우 - if len(stock_monthly['close']) > 1: - if stock_monthly['rsi'][0] is not None and stock_monthly['rsi'][1] is not None and stock_monthly['rsis'][0] is not None and stock_monthly['rsis'][1] is not None: - if stock_monthly['rsi'][0] <= 20: - if stock_monthly['rsi'][1] < stock_monthly['rsis'][1] and stock_monthly['rsis'][0] < stock_monthly['rsi'][0]: - dir_code = "2" - dir_name = "monthly_rsi_20이하" - final_status_count += 1 - status = "{:.2f}".format(stock_monthly['rsi'][0]) + "_" + status - self.writeFile(dir_code, dir_name, CODE, NAME, top, stock_monthly, status) + # Monthly 체크 + if len(stock_monthly['volume']) > 100: - if len(stock_monthly['volume']) > 5: - if stock_monthly['envelope_lower'][0] is not None and stock_monthly['envelope_lower'][1] is not None: - # env 하단에 부딪힘 - if stock_monthly['close'][1] < stock_monthly['envelope_lower'][1] and stock_monthly['envelope_lower'][0] < stock_monthly['close'][0]: - if stock_daily['close'][1] < stock_daily['avg5'][1] and stock_daily['avg5'][0] < stock_daily['close'][0]: - dir_code = "3" - dir_name = "monthly_EV하단위로_올라옴" - status = str(top) + "_" + status - final_status_count += 1 - self.writeFile(dir_code, dir_name, CODE, NAME, top, stock_monthly, status) + # MACD가 -300 이하에서 macd가 macds 위로 올라온 경우 + check = self.common.check_macd(stock_monthly, -300) + if check: + dir_name = "monthly_macd_n이하" + final_status_count += 1 + log = "{:.2f}".format(stock_monthly['macd'][0]) + self.writeFile(dir_name, CODE, NAME, top, stock_monthly, log) + # RSI가 20 이하인 경우, rsi가 rsis위로 올라온 경우 + check = self.common.check_rsi(stock_monthly, 20) + if check: + dir_name = "monthly_rsi_n이하" + final_status_count += 1 + log = "{:.2f}".format(stock_monthly['rsi'][0]) + self.writeFile(dir_name, CODE, NAME, top, stock_monthly, log) + + # Weekly 체크 + if len(stock_weekly['volume']) > 100: + + # MACD가 -300 이하에서 macd가 macds 위로 올라온 경우 + check = self.common.check_macd(stock_weekly, -300) + if check: + dir_name = "weekly_macd_n이하" + final_status_count += 1 + log = "{:.2f}".format(stock_weekly['macd'][0]) + self.writeFile(dir_name, CODE, NAME, top, stock_weekly, log) + + # RSI가 20 이하인 경우, rsi가 rsis위로 올라온 경우 + check = self.common.check_rsi(stock_weekly, 20) + if check: + dir_name = "weekly_rsi_n이하" + final_status_count += 1 + log = "{:.2f}".format(stock_weekly['rsi'][0]) + self.writeFile(dir_name, CODE, NAME, top, stock_weekly, log) # 2) daily if len(stock_daily['volume']) > 100: - # MSCD -500 이하인 경우 - if stock_daily['macd'][1] is not None and stock_daily['macds'][1] is not None and stock_daily['macd'][0] is not None and stock_daily['macds'][0] is not None: - if stock_daily['macd'][0] <= -1000: - if stock_daily['macd'][1] < stock_daily['macds'][1] and stock_daily['macds'][0] < stock_daily['macds'][0]: - dir_code = "11" - dir_name = "daily_macd_-1000이하" - final_status_count += 1 - status = "{:.2f}".format(stock_daily['macd'][0]) + "_" + status - self.writeFile(dir_code, dir_name, CODE, NAME, top, stock_daily, status) + check = self.common.check_macd(stock_daily, -1000) + if check: + dir_name = "daily_macd_n이하" + final_status_count += 1 + log = "{:.2f}".format(stock_daily['macd'][0]) + self.writeFile(dir_name, CODE, NAME, top, stock_daily, log) + + # RSI가 10 이하인 경우 + check = self.common.check_rsi(stock_daily, 10) + if check: + dir_name = "daily_rsi_n이하" + final_status_count += 1 + log = "{:.2f}".format(stock_daily['macd'][0]) + self.writeFile(dir_name, CODE, NAME, top, stock_daily, log) - # daily_weekly_monthly_rsi_10_20_30이하 - if len(stock_monthly['close']) > 1: - if stock_monthly['rsi'][0] is not None and stock_weekly['rsi'][0] is not None and stock_daily['rsi'][0] is not None: - if stock_monthly['rsi'][0] < 30 and stock_weekly['rsi'][0] < 20 and stock_daily['rsi'][0] < 10: - dir_code = "12" - dir_name = "daily_weekly_monthly_rsi_10_20_30이하" - status = str(top) + "_" + status - final_status_count += 1 - self.writeFile(dir_code, dir_name, CODE, NAME, top, stock_daily, status) # 52주 200일 기준 평균 + 50% 보다 높은 거래량의 경우 - c_index = 200 - if len(stock_daily['volume']) < c_index: - c_index = len(stock_daily['volume']) - max_volume = max(stock_daily['volume'][1:c_index]) - if max_volume < stock_daily['volume'][0]: - dir_code = "13" + check, log = self.common.check_volume(stock_daily) + if check: dir_name = "daily_이전에_없던_거래량" final_status_count += 1 - status = "{:.2f}".format((stock_daily['volume'][0] - max_volume)/max_volume) - self.writeFile(dir_code, dir_name, CODE, NAME, top, stock_daily, status) + self.writeFile(dir_name, CODE, NAME, top, stock_daily, log) # daily_이격도 - if (98 0.6: - if stock_daily['stochastic_slow_k'][0] is not None and stock_daily['stochastic_slow_k'][1] is not None and stock_daily['stochastic_slow_d'][1] is not None and stock_daily['stochastic_slow_d'][0] is not None and stock_monthly['stochastic_slow_k'][0] is not None: - if stock_daily['stochastic_slow_k'][0] < 20 and (stock_daily['stochastic_slow_k'][1] < stock_daily['stochastic_slow_d'][1] and stock_daily['stochastic_slow_d'][0] < stock_monthly['stochastic_slow_k'][0]): - dir_code = "15" - dir_name = "daily_낙폭과대" - final_status_count += 1 - status = str(top) + "_" + status - self.writeFile(dir_code, dir_name, CODE, NAME, top, stock_daily, status) + # daily_EV하단_내려옴 + check = self.common.check_under_EV_Low(stock_daily) + if check: + dir_name = "daily_EV하단_내려옴" + log = str(top) + final_status_count += 1 + self.writeFile(dir_name, CODE, NAME, top, stock_daily, log) - if len(stock_daily['volume']) > 5: - if stock_daily['envelope_lower'][0] is not None and stock_daily['envelope_lower'][1] is not None: - # ev 하단에 부딪힘 - if stock_daily['close'][1] < stock_daily['avg5'][1] and stock_daily['avg5'][0] < stock_daily['close'][0]: - check = False - for c in range(1, 4): - if stock_daily['close'][c] < stock_daily['envelope_lower'][c]: - check = True - break - if check: - dir_code = "16" - dir_name = "daily_EV하단_내려옴" - status = str(top) + "_" + status - final_status_count += 1 - self.writeFile(dir_code, dir_name, CODE, NAME, top, stock_daily, status) + check = self.common.check_under_BB_Low(stock_daily) + if check: + dir_name = "daily_BB하단_내려옴" + log = str(top) + final_status_count += 1 + self.writeFile(dir_name, CODE, NAME, top, stock_daily, log) - if len(stock_daily['volume']) > 5: - if stock_daily['bolingerband_lower'][0] is not None and stock_daily['bolingerband_lower'][1] is not None: - # bb 하단에 부딪힘 - if stock_daily['close'][1] < stock_daily['avg5'][1] and stock_daily['avg5'][0] < stock_daily['close'][0]: - check = False - for c in range(1, 4): - if stock_daily['close'][c] < stock_daily['bolingerband_lower'][c]: - check = True - break - if check: - dir_code = "17" - dir_name = "daily_BB하단_내려옴" - status = str(top) + "_" + status - final_status_count += 1 - self.writeFile(dir_code, dir_name, CODE, NAME, top, stock_daily, status) - - # RSI가 20 이하인 경우, rsi가 rsis위로 올라온 경우 - if len(stock_daily['close']) > 1: - if stock_daily['rsi'][0] is not None and stock_daily['rsi'][1] is not None and \ - stock_daily['rsis'][0] is not None and stock_daily['rsis'][1] is not None: - if stock_daily['rsi'][0] <= 20: - if stock_daily['rsi'][1] < stock_daily['rsis'][1] and stock_daily['rsis'][0] < stock_daily['rsi'][0]: - dir_code = "18" - dir_name = "daily_rsi_20이하" - final_status_count += 1 - status = "{:.2f}".format(stock_daily['rsi'][0]) + "_" + status - self.writeFile(dir_code, dir_name, CODE, NAME, top, stock_daily, status) if final_status_count >= 5: - dir_code = "0" dir_name = "final" - self.writeFile(dir_code, dir_name, CODE, NAME, top, stock_daily, final_status, final_status_count) + self.writeFile(dir_name, CODE, NAME, top, stock_daily, final_status, final_status_count) return diff --git a/stock/analysis/Common.py b/stock/analysis/Common.py index 55ae629..9d40ecf 100644 --- a/stock/analysis/Common.py +++ b/stock/analysis/Common.py @@ -479,4 +479,76 @@ class Common: rate = round((stock["close"][0] - stock["close"][1]) / stock["close"][1], 2) if rate <= limit: return "1d("+str(rate)+")_" - return "" \ No newline at end of file + return "" + + + # macd가 n보다 낮은 지 체크 + def check_macd(self, stock, n=-1000): + if stock['macd'][0] <= n: + if stock['macd'][1] < stock['macds'][1] and stock['macds'][0] < stock['macds'][0]: + return True, + return False + + def check_rsi(self, stock, n=10): + if stock['macd'][0] <= n: + if stock['macd'][1] < stock['macds'][1] and stock['macds'][0] < stock['macds'][0]: + return True + return False + + # 거래량 체크 + # 52주 200일 기준 평균 + 50% 보다 높은 거래량의 경우 + def check_volume(self, stock): + c_index = 200 + if len(stock['volume']) < c_index: + c_index = len(stock['volume']) + max_volume = max(stock['volume'][1:c_index]) + if max_volume < stock['volume'][0]: + log = "{:.2f}".format((stock['volume'][0] - max_volume)/max_volume) + return True, log + + return False, "" + + # 이격도 체크 + def check_disparity(self, stock): + if (98 < stock['disparity_avg5'][0] < 102 and 98 < stock['disparity_avg10'][0] < 102 and 98 < stock['disparity_avg20'][0] < 102 and 98 < stock['disparity_avg60'][0] < 102 and 98 < stock['disparity_avg120'][0] < 102): + if stock['close'][1] < stock['avg5'][1] and stock['avg5'][0] < stock['close'][0]: + if stock['stochastic_slow_k'][0] < 20: + if stock['stochastic_slow_k'][1] < stock['stochastic_slow_d'][1] and stock['stochastic_slow_d'][0] < stock['stochastic_slow_k'][0]: + return True + return False + + # 낙폭 과대 체크 + def check_excessive_drop(self, stock): + c_index = 52 * 5 + if len(stock['close']) < c_index: + c_index = len(stock['close']) + for idx in range(1, c_index): + if stock['close'][idx - 1] < int(stock['close'][idx] / 3): + c_index = idx + + location = (max(stock['close'][1:c_index]) - stock['close'][0]) / max( + stock['close'][1:c_index]) + if location > 0.6: + if stock['stochastic_slow_k'][0] is not None and stock['stochastic_slow_k'][1] is not None and stock['stochastic_slow_d'][1] is not None and stock['stochastic_slow_d'][0] is not None and stock['stochastic_slow_k'][0] is not None: + if stock['stochastic_slow_k'][0] < 20 and (stock['stochastic_slow_k'][1] < stock['stochastic_slow_d'][1] and stock['stochastic_slow_d'][0] < stock['stochastic_slow_k'][0]): + return True + return False + + def check_under_EV_Low(self, stock): + if stock['envelope_lower'][0] is not None and stock['envelope_lower'][1] is not None: + # ev 하단에 부딪힘 + if stock['close'][1] < stock['avg5'][1] and stock['avg5'][0] < stock['close'][0]: + for c in range(1, 4): + if stock['close'][c] < stock['envelope_lower'][c]: + return True + return False + + def check_under_BB_Low(self, stock): + if stock['bolingerband_lower'][0] is not None and stock['bolingerband_lower'][1] is not None: + # bb 하단에 부딪힘 + if stock['close'][1] < stock['avg5'][1] and stock['avg5'][0] < stock['close'][0]: + check = False + for c in range(1, 4): + if stock['close'][c] < stock['bolingerband_lower'][c]: + return True + return False \ No newline at end of file