Skip to content

Commit

Permalink
v1.17 - Watchlist & Progressbar added
Browse files Browse the repository at this point in the history
[Screenipy Test] New Features Added - Test Passed - Merge OK
  • Loading branch information
pranjal-joshi authored May 28, 2021
2 parents bbb00f6 + 5f63bcf commit 4cfc63f
Show file tree
Hide file tree
Showing 8 changed files with 112 additions and 49 deletions.
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 6 additions & 1 deletion src/classes/Changelog.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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
51 changes: 38 additions & 13 deletions src/classes/Fetcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import urllib
import requests
import random
import os
import yfinance as yf
import pandas as pd
from nsetools import Nse
Expand Down Expand Up @@ -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",
Expand All @@ -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
4 changes: 2 additions & 2 deletions src/classes/OtaUpdater.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
34 changes: 21 additions & 13 deletions src/classes/ParallelProcessing.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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': "",
Expand All @@ -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)
Expand All @@ -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'
Expand All @@ -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:
Expand Down Expand Up @@ -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

Expand Down
3 changes: 3 additions & 0 deletions src/classes/Screener.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
16 changes: 8 additions & 8 deletions src/release.md
Original file line number Diff line number Diff line change
@@ -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?

Expand Down
45 changes: 33 additions & 12 deletions src/screenipy.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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()
Expand All @@ -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()
Expand Down

0 comments on commit 4cfc63f

Please sign in to comment.