From 5f63bcf86715c2871e05237c09deebc9c77293a7 Mon Sep 17 00:00:00 2001 From: Pranjal Joshi Date: Wed, 26 May 2021 21:42:30 +0530 Subject: [PATCH] v1.17 - Feature Enhancements * Watchlist Creation and Screening implemented * Cosmetic Updates -> Progressbar added * validateBreakout now ignores Red gap-up candle * Polyfit warning suppressed * OTA Fix for MacOS * Documentation updated & Issue templates added * CodeFactor Improvements --- requirements.txt | 1 + src/classes/Changelog.py | 7 ++++- src/classes/Fetcher.py | 51 +++++++++++++++++++++++-------- src/classes/OtaUpdater.py | 4 +-- src/classes/ParallelProcessing.py | 34 +++++++++++++-------- src/classes/Screener.py | 3 ++ src/release.md | 16 +++++----- src/screenipy.py | 45 +++++++++++++++++++-------- 8 files changed, 112 insertions(+), 49 deletions(-) diff --git a/requirements.txt b/requirements.txt index fff0c21..53828c8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -20,6 +20,7 @@ scipy ta-lib tabulate yfinance +alive-progress altgraph # Installed as dependency for pyinstaller atomicwrites # Installed as dependency for pytest attrs # Installed as dependency for pytest diff --git a/src/classes/Changelog.py b/src/classes/Changelog.py index 192d1a3..4966f75 100644 --- a/src/classes/Changelog.py +++ b/src/classes/Changelog.py @@ -7,7 +7,7 @@ from classes.ColorText import colorText -VERSION = "1.16" +VERSION = "1.17" changelog = colorText.BOLD + '[ChangeLog]\n' + colorText.END + colorText.BLUE + ''' [1.00 - Beta] @@ -88,5 +88,10 @@ 3. Codefactoring Improved. 4. Ctrl+C crash fixed. +[1.17] +1. Breakout detection improved. +2. Progressbar added. +3. Watchlist creation in Excel file and its screening. + --- END --- ''' + colorText.END diff --git a/src/classes/Fetcher.py b/src/classes/Fetcher.py index f9c0f80..85ffe6a 100644 --- a/src/classes/Fetcher.py +++ b/src/classes/Fetcher.py @@ -9,6 +9,7 @@ import urllib import requests import random +import os import yfinance as yf import pandas as pd from nsetools import Nse @@ -69,7 +70,7 @@ def fetchStockCodes(self, executeOption): return listStockCodes # Fetch stock price data from Yahoo finance - def fetchStockData(self, stockCode, period, duration, proxyServer, screenResultsCounter, screenCounter, totalSymbols): + def fetchStockData(self, stockCode, period, duration, proxyServer, screenResultsCounter, screenCounter, totalSymbols,printCounter=False): with SuppressOutput(suppress_stdout=True, suppress_stderr=True): data = yf.download( tickers=stockCode+".NS", @@ -79,17 +80,41 @@ def fetchStockData(self, stockCode, period, duration, proxyServer, screenResults progress=False, threads=True ) - sys.stdout.write("\r\033[K") + if printCounter: + sys.stdout.write("\r\033[K") + try: + print(colorText.BOLD + colorText.GREEN + ("[%d%%] Screened %d, Found %d. Fetching data & Analyzing %s..." % ( + int((screenCounter.value/totalSymbols)*100), screenCounter.value, screenResultsCounter.value, stockCode)) + colorText.END, end='') + except ZeroDivisionError: + pass + if len(data) == 0: + print(colorText.BOLD + colorText.FAIL + + "=> Failed to fetch!" + colorText.END, end='\r', flush=True) + raise StockDataEmptyException + return None + print(colorText.BOLD + colorText.GREEN + "=> Done!" + + colorText.END, end='\r', flush=True) + return data + + # Load stockCodes from the watchlist.xlsx + def fetchWatchlist(self): + createTemplate = False + data = pd.DataFrame() try: - print(colorText.BOLD + colorText.GREEN + ("[%d%%] Screened %d, Found %d. Fetching data & Analyzing %s..." % ( - int((screenCounter.value/totalSymbols)*100), screenCounter.value, screenResultsCounter.value, stockCode)) + colorText.END, end='') - except ZeroDivisionError: - pass - if len(data) == 0: - print(colorText.BOLD + colorText.FAIL + - "=> Failed to fetch!" + colorText.END, end='\r', flush=True) - raise StockDataEmptyException + data = pd.read_excel('watchlist.xlsx') + except FileNotFoundError: + print(colorText.BOLD + colorText.FAIL + f'[+] watchlist.xlsx not found in f{os.getcwd()}' + colorText.END) + createTemplate = True + try: + if not createTemplate: + data = data['Stock Code'].values.tolist() + except KeyError: + print(colorText.BOLD + colorText.FAIL + '[+] Bad Watchlist Format: First Column (A1) should have Header named "Stock Code"' + colorText.END) + createTemplate = True + if createTemplate: + sample = {'Stock Code': ['SBIN', 'INFY', 'TATAMOTORS', 'ITC']} + sample_data = pd.DataFrame(sample, columns=['Stock Code']) + sample_data.to_excel('watchlist_template.xlsx', index=False, header=True) + print(colorText.BOLD + colorText.BLUE + f'[+] watchlist_template.xlsx created in {os.getcwd()} as a referance template.' + colorText.END) return None - print(colorText.BOLD + colorText.GREEN + "=> Done!" + - colorText.END, end='\r', flush=True) - return data + return data \ No newline at end of file diff --git a/src/classes/OtaUpdater.py b/src/classes/OtaUpdater.py index 6594a64..681e342 100644 --- a/src/classes/OtaUpdater.py +++ b/src/classes/OtaUpdater.py @@ -67,8 +67,8 @@ def updateForMac(url): echo "[+] This may take some time as per your Internet Speed, Please Wait..." curl -o screenipy.run -L """ + url + """ echo "[+] Newly downloaded file saved in $(pwd)" -chmod +x screenipy.bin -echo "[+] Update Completed! Run 'screenipy.bin' again as usual to continue.." +chmod +x screenipy.run +echo "[+] Update Completed! Run 'screenipy.run' again as usual to continue.." rm updater.sh """ f = open("updater.sh",'w') diff --git a/src/classes/ParallelProcessing.py b/src/classes/ParallelProcessing.py index a9fbf58..0ec0533 100644 --- a/src/classes/ParallelProcessing.py +++ b/src/classes/ParallelProcessing.py @@ -18,6 +18,7 @@ import classes.Utility as Utility from classes.CandlePatterns import CandlePatterns from classes.ColorText import colorText +from classes.SuppressOutput import SuppressOutput if sys.platform.startswith('win'): import multiprocessing.popen_spawn_win32 as forking @@ -56,7 +57,7 @@ def run(self): sys.exit(0) def screenStocks(self, executeOption, reversalOption, daysForLowestVolume, minRSI, maxRSI, respBullBear, insideBarToLookback, totalSymbols, - configManager, fetcher, screener, candlePatterns, stock): + configManager, fetcher, screener, candlePatterns, stock, printCounter=False): screenResults = pd.DataFrame(columns=[ 'Stock', 'Consolidating', 'Breaking-Out', 'MA-Signal', 'Volume', 'LTP', 'RSI', 'Trend', 'Pattern']) screeningDictionary = {'Stock': "", 'Consolidating': "", 'Breaking-Out': "", @@ -76,17 +77,18 @@ def screenStocks(self, executeOption, reversalOption, daysForLowestVolume, minRS if configManager.cacheEnabled is True and not self.isTradingTime and (self.stockDict.get(stock) is None): self.stockDict[stock] = data.to_dict('split') else: - try: - print(colorText.BOLD + colorText.GREEN + ("[%d%%] Screened %d, Found %d. Fetching data & Analyzing %s..." % ( - int((self.screenCounter.value / totalSymbols) * 100), self.screenCounter.value, self.screenResultsCounter.value, stock)) + colorText.END, end='') - print(colorText.BOLD + colorText.GREEN + "=> Done!" + - colorText.END, end='\r', flush=True) - except ZeroDivisionError: - pass + if printCounter: + try: + print(colorText.BOLD + colorText.GREEN + ("[%d%%] Screened %d, Found %d. Fetching data & Analyzing %s..." % ( + int((self.screenCounter.value / totalSymbols) * 100), self.screenCounter.value, self.screenResultsCounter.value, stock)) + colorText.END, end='') + print(colorText.BOLD + colorText.GREEN + "=> Done!" + + colorText.END, end='\r', flush=True) + except ZeroDivisionError: + pass + sys.stdout.write("\r\033[K") data = self.stockDict.get(stock) data = pd.DataFrame( data['data'], columns=data['columns'], index=data['index']) - sys.stdout.write("\r\033[K") fullData, processedData = screener.preprocessData( data, daysToLookback=configManager.daysToLookback) @@ -112,8 +114,13 @@ def screenStocks(self, executeOption, reversalOption, daysForLowestVolume, minRS isValidRsi = screener.validateRSI( processedData, screeningDictionary, saveDictionary, minRSI, maxRSI) try: - currentTrend = screener.findTrend( - processedData, screeningDictionary, saveDictionary, daysToLookback=configManager.daysToLookback, stockName=stock) + with SuppressOutput(suppress_stderr=True, suppress_stdout=True): + currentTrend = screener.findTrend( + processedData, + screeningDictionary, + saveDictionary, + daysToLookback=configManager.daysToLookback, + stockName=stock) except np.RankWarning: screeningDictionary['Trend'] = 'Unknown' saveDictionary['Trend'] = 'Unknown' @@ -124,7 +131,7 @@ def screenStocks(self, executeOption, reversalOption, daysForLowestVolume, minRS isMomentum = screener.validateMomentum(processedData, screeningDictionary, saveDictionary) with self.screenResultsCounter.get_lock(): - if executeOption == 0: + if executeOption == 0 or executeOption == 'W': self.screenResultsCounter.value += 1 return screeningDictionary, saveDictionary if (executeOption == 1 or executeOption == 2) and isBreaking and isVolumeHigh and isLtpValid: @@ -162,7 +169,8 @@ def screenStocks(self, executeOption, reversalOption, daysForLowestVolume, minRS except KeyError: pass except Exception as e: - print(colorText.FAIL + + if printCounter: + print(colorText.FAIL + ("\n[+] Exception Occured while Screening %s! Skipping this stock.." % stock) + colorText.END) return diff --git a/src/classes/Screener.py b/src/classes/Screener.py index 7684b5d..b5a11b5 100644 --- a/src/classes/Screener.py +++ b/src/classes/Screener.py @@ -176,6 +176,9 @@ def findBreakout(self, data, screenDict, saveDict, daysToLookback): data = data.fillna(0) data = data.replace([np.inf, -np.inf], 0) recent = data.head(1) + # Consider only for Green Candle + if not self.getCandleType(recent): + return False data = data[1:] hs = round(data.describe()['High']['max'],2) hc = round(data.describe()['Close']['max'],2) diff --git a/src/release.md b/src/release.md index 7b1c7ee..937770f 100644 --- a/src/release.md +++ b/src/release.md @@ -1,16 +1,16 @@ # Make sure to download the latest release! ![GitHub release (latest by date)](https://img.shields.io/github/v/release/pranjal-joshi/Screeni-py) ## What's New? -1. New **Chart Pattern** **`Bullish Momentum Gainer`** added! Try `Option > 6 > 3` :rocket: -2. **Data Saver Mode**: Intellegently Stores Stock Data for After-Market hours screening without using extra bandwidth! :pencil: (Thanks to [**swarpatel23**](https://github.com/swarpatel23)) -3. **Massive Improvement in screening speed!** :zap: Now we take just around 3-4 minutes to screen thousands of stocks in real time! (Thanks to [**swarpatel23**](https://github.com/swarpatel23)) -4. **MA** now gives more info like **Candle Crossing and At Support/Resistance**. :chart_with_upwards_trend: -5. Fixed Crashes! :wrench: +1. Now **Create Your Own Watchlist** in Excel and screen for only those stocks! Try `Option > W` :chart_with_upwards_trend: +2. New Improved **Breakout Detection.** :rocket: +3. New **Chart Pattern** **`Bullish Momentum Gainer`** added! Try `Option > 6 > 3` :tada: +4. **Data Saver & High Performance Mode**: Intellegently Stores Stock Data for After-Market hours screening without using extra bandwidth. Also, uses multiple CPU cores available on your computer for supperfast screening! :sparkles: (Thanks to [**swarpatel23**](https://github.com/swarpatel23)) +5. Cosmetic Updates! :lipstick: ## Downloads -* For :desktop_computer: **Windows** users, download **[screenipy.exe](https://github.com/pranjal-joshi/Screeni-py/releases/download/1.16/screenipy.exe)** -* For :penguin: **Linux** users, download **[screenipy.bin](https://github.com/pranjal-joshi/Screeni-py/releases/download/1.16/screenipy.bin)** -* For :apple: **MacOS** users, download **[screenipy.run](https://github.com/pranjal-joshi/Screeni-py/releases/download/1.16/screenipy.run)** ([Read Installation Guide](../INSTALLATION.md#For-MacOS)) +* For :desktop_computer: **Windows** users, download **[screenipy.exe](https://github.com/pranjal-joshi/Screeni-py/releases/download/1.17/screenipy.exe)** +* For :penguin: **Linux** users, download **[screenipy.bin](https://github.com/pranjal-joshi/Screeni-py/releases/download/1.17/screenipy.bin)** +* For :apple: **MacOS** users, download **[screenipy.run](https://github.com/pranjal-joshi/Screeni-py/releases/download/1.17/screenipy.run)** ([Read Installation Guide](https://github.com/pranjal-joshi/Screeni-py/blob/main/INSTALLATION.md#for-macos)) ## How to use? diff --git a/src/screenipy.py b/src/screenipy.py index 1dd1430..68847e5 100644 --- a/src/screenipy.py +++ b/src/screenipy.py @@ -13,7 +13,9 @@ import numpy as np import urllib import sys +import platform import os +from alive_progress import alive_bar from classes.Changelog import VERSION from classes.ParallelProcessing import StockConsumer from classes.CandlePatterns import CandlePatterns @@ -55,7 +57,8 @@ def initExecution(): print(colorText.BOLD + colorText.WARN + '[+] Press a number to start stock screening: ' + colorText.END) - print(colorText.BOLD + ''' 0 > Screen stocks by stock name (NSE Stock Code) + print(colorText.BOLD + ''' W > Screen stocks from the Watchlist + 0 > Screen stocks by stock name (NSE Stock Code) 1 > Screen stocks for Breakout or Consolidation 2 > Screen for the stocks with recent Breakout & Volume 3 > Screen for the Consolidating stocks @@ -72,6 +75,8 @@ def initExecution(): try: result = input(colorText.BOLD + colorText.FAIL + '[+] Select option: ') print(colorText.END, end='') + if isinstance(result, str) and result.upper() == 'W': + return result.upper() result = int(result) if(result < 0 or result > 12): raise ValueError @@ -159,10 +164,16 @@ def main(testing=False): input(colorText.BOLD + colorText.FAIL + "[+] Press any key to Exit!" + colorText.END) sys.exit(0) - if executeOption >= 0 and executeOption < 8: + if executeOption == 'W' or (executeOption >= 0 and executeOption < 8): configManager.getConfig(ConfigManager.parser) try: - listStockCodes = fetcher.fetchStockCodes(executeOption) + if executeOption == 'W': + listStockCodes = fetcher.fetchWatchlist() + if listStockCodes is None: + input(colorText.BOLD + colorText.FAIL + f'[+] Create the watchlist.xlsx file in {os.getcwd()} and Restart the Program!' + colorText.END) + sys.exit(0) + else: + listStockCodes = fetcher.fetchStockCodes(executeOption) except urllib.error.URLError: print(colorText.BOLD + colorText.FAIL + "\n\n[+] Oops! It looks like you don't have an Internet connectivity at the moment! Press any key to exit!" + colorText.END) @@ -214,14 +225,23 @@ def main(testing=False): tasks_queue.put(None) try: numStocks = len(listStockCodes) - while numStocks: - result = results_queue.get() - if result is not None: - screenResults = screenResults.append( - result[0], ignore_index=True) - saveResults = saveResults.append( - result[1], ignore_index=True) - numStocks -= 1 + print(colorText.END+colorText.BOLD) + bar = 'smooth' + spinner = 'waves' + if 'Windows' in platform.platform(): + bar = 'classic2' + spinner = 'dots_recur' + with alive_bar(numStocks,bar=bar,spinner=spinner) as progressbar: + while numStocks: + result = results_queue.get() + if result is not None: + screenResults = screenResults.append( + result[0], ignore_index=True) + saveResults = saveResults.append( + result[1], ignore_index=True) + numStocks -= 1 + progressbar.text(colorText.BOLD + colorText.GREEN + f'Found {screenResultsCounter.value} Stocks' + colorText.END) + progressbar() except KeyboardInterrupt: try: keyboardInterruptEvent.set() @@ -230,7 +250,8 @@ def main(testing=False): print(colorText.BOLD + colorText.FAIL +"\n[+] Terminating Script, Please wait..." + colorText.END) for worker in consumers: worker.terminate() - + + print(colorText.END) # Exit all processes. Without this, it threw error in next screening session for worker in consumers: worker.terminate()