|
|
|
|
|
|
|
|
|
|
|
|
|
import os |
|
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' |
|
import platform |
|
import sys |
|
import classes.Fetcher as Fetcher |
|
import classes.ConfigManager as ConfigManager |
|
import classes.Screener as Screener |
|
import classes.Utility as Utility |
|
from classes.ColorText import colorText |
|
from classes.OtaUpdater import OTAUpdater |
|
from classes.CandlePatterns import CandlePatterns |
|
from classes.ParallelProcessing import StockConsumer |
|
from classes.Changelog import VERSION |
|
from classes.Utility import isDocker, isGui |
|
from alive_progress import alive_bar |
|
import argparse |
|
import urllib |
|
import numpy as np |
|
import pandas as pd |
|
from datetime import datetime, date |
|
from time import sleep |
|
from tabulate import tabulate |
|
import multiprocessing |
|
multiprocessing.freeze_support() |
|
try: |
|
import chromadb |
|
CHROMA_AVAILABLE = True |
|
except: |
|
CHROMA_AVAILABLE = False |
|
|
|
|
|
argParser = argparse.ArgumentParser() |
|
argParser.add_argument('-t', '--testbuild', action='store_true', help='Run in test-build mode', required=False) |
|
argParser.add_argument('-d', '--download', action='store_true', help='Only Download Stock data in .pkl file', required=False) |
|
argParser.add_argument('-v', action='store_true') |
|
args = argParser.parse_args() |
|
|
|
|
|
TEST_STKCODE = "SBIN" |
|
|
|
|
|
np.seterr(divide='ignore', invalid='ignore') |
|
|
|
|
|
screenCounter = None |
|
screenResultsCounter = None |
|
stockDict = None |
|
keyboardInterruptEvent = None |
|
loadedStockData = False |
|
loadCount = 0 |
|
maLength = None |
|
newlyListedOnly = False |
|
vectorSearch = False |
|
|
|
CHROMADB_PATH = "chromadb_store/" |
|
|
|
configManager = ConfigManager.tools() |
|
fetcher = Fetcher.tools(configManager) |
|
screener = Screener.tools(configManager) |
|
candlePatterns = CandlePatterns() |
|
|
|
|
|
try: |
|
proxyServer = urllib.request.getproxies()['http'] |
|
except KeyError: |
|
proxyServer = "" |
|
|
|
|
|
if CHROMA_AVAILABLE: |
|
chroma_client = chromadb.PersistentClient(path=CHROMADB_PATH) |
|
try: |
|
chroma_client.delete_collection("nse_stocks") |
|
except: |
|
pass |
|
|
|
|
|
|
|
|
|
|
|
def initExecution(): |
|
global newlyListedOnly |
|
print(colorText.BOLD + colorText.WARN + |
|
'[+] Select an Index for Screening: ' + colorText.END) |
|
print(colorText.BOLD + ''' |
|
W > Screen stocks from my own Watchlist |
|
N > Nifty Prediction using Artifical Intelligence (Use for Gap-Up/Gap-Down/BTST/STBT) |
|
E > Live Index Scan : 5 EMA for Intraday |
|
S > Search for Similar Stocks (forming Similar Chart Pattern) |
|
|
|
0 > Screen stocks by the stock names (NSE Stock Code) |
|
1 > Nifty 50 2 > Nifty Next 50 3 > Nifty 100 |
|
4 > Nifty 200 5 > Nifty 500 6 > Nifty Smallcap 50 |
|
7 > Nifty Smallcap 100 8 > Nifty Smallcap 250 9 > Nifty Midcap 50 |
|
10 > Nifty Midcap 100 11 > Nifty Midcap 150 13 > Newly Listed (IPOs in last 2 Year) |
|
14 > F&O Stocks Only 15 > US S&P 500 16 > Sectoral Indices (NSE) |
|
Enter > All Stocks (default) ''' + colorText.END |
|
) |
|
try: |
|
tickerOption = input( |
|
colorText.BOLD + colorText.FAIL + '[+] Select option: ') |
|
print(colorText.END, end='') |
|
if tickerOption == '': |
|
tickerOption = 12 |
|
|
|
elif not tickerOption.isnumeric(): |
|
tickerOption = tickerOption.upper() |
|
else: |
|
tickerOption = int(tickerOption) |
|
if(tickerOption < 0 or tickerOption > 16): |
|
raise ValueError |
|
elif tickerOption == 13: |
|
newlyListedOnly = True |
|
tickerOption = 12 |
|
except KeyboardInterrupt: |
|
raise KeyboardInterrupt |
|
except Exception as e: |
|
print(colorText.BOLD + colorText.FAIL + |
|
'\n[+] Please enter a valid numeric option & Try Again!' + colorText.END) |
|
sleep(2) |
|
Utility.tools.clearScreen() |
|
return initExecution() |
|
|
|
if tickerOption == 'N' or tickerOption == 'E' or tickerOption == 'S': |
|
return tickerOption, 0 |
|
|
|
if tickerOption and tickerOption != 'W': |
|
print(colorText.BOLD + colorText.WARN + |
|
'\n[+] Select a Critera for Stock Screening: ' + colorText.END) |
|
print(colorText.BOLD + ''' |
|
0 > Full Screening (Shows Technical Parameters without Any Criteria) |
|
1 > Screen stocks for Breakout or Consolidation |
|
2 > Screen for the stocks with recent Breakout & Volume |
|
3 > Screen for the Consolidating stocks |
|
4 > Screen for the stocks with Lowest Volume in last 'N'-days (Early Breakout Detection) |
|
5 > Screen for the stocks with RSI |
|
6 > Screen for the stocks showing Reversal Signals |
|
7 > Screen for the stocks making Chart Patterns |
|
8 > Edit user configuration |
|
9 > Show user configuration |
|
10 > Show Last Screened Results |
|
11 > Help / About Developer |
|
12 > Exit''' + colorText.END |
|
) |
|
try: |
|
if tickerOption and tickerOption != 'W': |
|
executeOption = input( |
|
colorText.BOLD + colorText.FAIL + '[+] Select option: ') |
|
print(colorText.END, end='') |
|
if executeOption == '': |
|
executeOption = 0 |
|
executeOption = int(executeOption) |
|
if(executeOption < 0 or executeOption > 14): |
|
raise ValueError |
|
else: |
|
executeOption = 0 |
|
except KeyboardInterrupt: |
|
raise KeyboardInterrupt |
|
except Exception as e: |
|
print(colorText.BOLD + colorText.FAIL + |
|
'\n[+] Please enter a valid numeric option & Try Again!' + colorText.END) |
|
sleep(2) |
|
Utility.tools.clearScreen() |
|
return initExecution() |
|
return tickerOption, executeOption |
|
|
|
|
|
def main(testing=False, testBuild=False, downloadOnly=False, execute_inputs:list = [], isDevVersion=None, backtestDate=date.today()): |
|
global screenCounter, screenResultsCounter, stockDict, loadedStockData, keyboardInterruptEvent, loadCount, maLength, newlyListedOnly, vectorSearch |
|
screenCounter = multiprocessing.Value('i', 1) |
|
screenResultsCounter = multiprocessing.Value('i', 0) |
|
keyboardInterruptEvent = multiprocessing.Manager().Event() |
|
|
|
if stockDict is None or Utility.tools.isBacktesting(backtestDate=backtestDate): |
|
stockDict = multiprocessing.Manager().dict() |
|
loadCount = 0 |
|
|
|
minRSI = 0 |
|
maxRSI = 100 |
|
insideBarToLookback = 7 |
|
respChartPattern = 1 |
|
daysForLowestVolume = 30 |
|
reversalOption = None |
|
|
|
screenResults = pd.DataFrame(columns=[ |
|
'Stock', 'Consolidating', 'Breaking-Out', 'LTP', 'Volume', 'MA-Signal', 'RSI', 'Trend', 'Pattern']) |
|
saveResults = pd.DataFrame(columns=[ |
|
'Stock', 'Consolidating', 'Breaking-Out', 'LTP', 'Volume', 'MA-Signal', 'RSI', 'Trend', 'Pattern']) |
|
|
|
|
|
if testBuild: |
|
tickerOption, executeOption = 1, 0 |
|
elif downloadOnly: |
|
tickerOption, executeOption = 12, 2 |
|
else: |
|
try: |
|
if execute_inputs != []: |
|
if not configManager.checkConfigFile(): |
|
configManager.setConfig(ConfigManager.parser, default=True, showFileCreatedText=False) |
|
try: |
|
tickerOption, executeOption = int(execute_inputs[0]), int(execute_inputs[1]) |
|
if tickerOption == 0: |
|
stockCode = execute_inputs[2].replace(" ", "") |
|
listStockCodes = stockCode.split(',') |
|
except: |
|
tickerOption, executeOption = str(execute_inputs[0]), int(execute_inputs[1]) |
|
if tickerOption == 13: |
|
newlyListedOnly = True |
|
tickerOption = 12 |
|
else: |
|
tickerOption, executeOption = initExecution() |
|
except KeyboardInterrupt: |
|
if execute_inputs == [] and not isGui(): |
|
input(colorText.BOLD + colorText.FAIL + |
|
"[+] Press any key to Exit!" + colorText.END) |
|
sys.exit(0) |
|
|
|
if executeOption == 4: |
|
try: |
|
if execute_inputs != []: |
|
daysForLowestVolume = int(execute_inputs[2]) |
|
else: |
|
daysForLowestVolume = int(input(colorText.BOLD + colorText.WARN + |
|
'\n[+] The Volume should be lowest since last how many candles? ')) |
|
except ValueError: |
|
print(colorText.END) |
|
print(colorText.BOLD + colorText.FAIL + |
|
'[+] Error: Non-numeric value entered! Screening aborted.' + colorText.END) |
|
if not isGui(): |
|
input('') |
|
main() |
|
print(colorText.END) |
|
if executeOption == 5: |
|
if execute_inputs != []: |
|
minRSI, maxRSI = int(execute_inputs[2]), int(execute_inputs[3]) |
|
else: |
|
minRSI, maxRSI = Utility.tools.promptRSIValues() |
|
if (not minRSI and not maxRSI): |
|
print(colorText.BOLD + colorText.FAIL + |
|
'\n[+] Error: Invalid values for RSI! Values should be in range of 0 to 100. Screening aborted.' + colorText.END) |
|
if not isGui(): |
|
input('') |
|
main() |
|
if executeOption == 6: |
|
if execute_inputs != []: |
|
reversalOption = int(execute_inputs[2]) |
|
try: |
|
maLength = int(execute_inputs[3]) |
|
except ValueError: |
|
pass |
|
else: |
|
reversalOption, maLength = Utility.tools.promptReversalScreening() |
|
if reversalOption is None or reversalOption == 0: |
|
if not isGui(): |
|
main() |
|
if executeOption == 7: |
|
if execute_inputs != []: |
|
respChartPattern = int(execute_inputs[2]) |
|
try: |
|
insideBarToLookback = float(execute_inputs[3]) |
|
except ValueError: |
|
pass |
|
else: |
|
respChartPattern, insideBarToLookback = Utility.tools.promptChartPatterns() |
|
if insideBarToLookback is None: |
|
if not isGui(): |
|
main() |
|
if executeOption == 8: |
|
configManager.setConfig(ConfigManager.parser) |
|
if not isGui(): |
|
main() |
|
if executeOption == 9: |
|
configManager.showConfigFile() |
|
if not isGui(): |
|
main() |
|
if executeOption == 10: |
|
Utility.tools.getLastScreenedResults() |
|
if not isGui(): |
|
main() |
|
if executeOption == 11: |
|
Utility.tools.showDevInfo() |
|
if not isGui(): |
|
main() |
|
if executeOption == 12: |
|
if not isGui(): |
|
input(colorText.BOLD + colorText.FAIL + |
|
"[+] Press any key to Exit!" + colorText.END) |
|
sys.exit(0) |
|
|
|
if tickerOption == 'W' or tickerOption == 'N' or tickerOption == 'E' or tickerOption == 'S' or (tickerOption >= 0 and tickerOption < 17): |
|
configManager.getConfig(ConfigManager.parser) |
|
try: |
|
if tickerOption == '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) |
|
elif tickerOption == 'N': |
|
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' |
|
import tensorflow as tf |
|
physical_devices = tf.config.list_physical_devices('GPU') |
|
try: |
|
tf.config.set_visible_devices([], 'GPU') |
|
visible_devices = tf.config.get_visible_devices() |
|
for device in visible_devices: |
|
assert device.device_type != 'GPU' |
|
except: |
|
pass |
|
prediction = screener.getNiftyPrediction( |
|
data=fetcher.fetchLatestNiftyDaily(proxyServer=proxyServer), |
|
proxyServer=proxyServer |
|
) |
|
input('\nPress any key to Continue...\n') |
|
return |
|
elif tickerOption == 'E': |
|
result_df = pd.DataFrame(columns=['Time','Stock/Index','Action','SL','Target','R:R']) |
|
last_signal = {} |
|
first_scan = True |
|
result_df = screener.monitorFiveEma( |
|
proxyServer=proxyServer, |
|
fetcher=fetcher, |
|
result_df=result_df, |
|
last_signal=last_signal |
|
) |
|
try: |
|
while True: |
|
Utility.tools.clearScreen() |
|
last_result_len = len(result_df) |
|
result_df = screener.monitorFiveEma( |
|
proxyServer=proxyServer, |
|
fetcher=fetcher, |
|
result_df=result_df, |
|
last_signal=last_signal |
|
) |
|
print(colorText.BOLD + colorText.WARN + '[+] 5-EMA : Live Intraday Scanner \t' + colorText.END + colorText.FAIL + f'Last Scanned: {datetime.now().strftime("%H:%M:%S")}\n' + colorText.END) |
|
print(tabulate(result_df, headers='keys', tablefmt='psql')) |
|
print('\nPress Ctrl+C to exit.') |
|
if len(result_df) != last_result_len and not first_scan: |
|
Utility.tools.alertSound(beeps=5) |
|
sleep(60) |
|
first_scan = False |
|
except KeyboardInterrupt: |
|
if not isGui(): |
|
input('\nPress any key to Continue...\n') |
|
return |
|
elif tickerOption == 'S': |
|
if not CHROMA_AVAILABLE: |
|
print(colorText.BOLD + colorText.FAIL + |
|
"\n\n[+] ChromaDB not available in your environment! You can't use this feature!\n" + colorText.END) |
|
else: |
|
if execute_inputs != []: |
|
stockCode, candles = execute_inputs[2], execute_inputs[3] |
|
else: |
|
stockCode, candles = Utility.tools.promptSimilarStockSearch() |
|
vectorSearch = [stockCode, candles, True] |
|
tickerOption, executeOption = 12, 1 |
|
listStockCodes = fetcher.fetchStockCodes(tickerOption, proxyServer=proxyServer) |
|
else: |
|
if tickerOption == 14: |
|
configManager.stageTwo = False |
|
configManager.minLTP = 0.1 |
|
configManager.maxLTP = 999999999 |
|
if (execute_inputs != [] and tickerOption != 0) or execute_inputs == []: |
|
listStockCodes = fetcher.fetchStockCodes(tickerOption, proxyServer=proxyServer) |
|
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) |
|
if not isGui(): |
|
input('') |
|
sys.exit(0) |
|
|
|
if not Utility.tools.isTradingTime() and configManager.cacheEnabled and not loadedStockData and not testing and not Utility.tools.isBacktesting(backtestDate=backtestDate): |
|
Utility.tools.loadStockData(stockDict, configManager, proxyServer) |
|
loadedStockData = True |
|
loadCount = len(stockDict) |
|
|
|
print(colorText.BOLD + colorText.WARN + |
|
"[+] Starting Stock Screening.. Press Ctrl+C to stop!\n") |
|
|
|
items = [(tickerOption, executeOption, reversalOption, maLength, daysForLowestVolume, minRSI, maxRSI, respChartPattern, insideBarToLookback, len(listStockCodes), |
|
configManager, fetcher, screener, candlePatterns, stock, newlyListedOnly, downloadOnly, vectorSearch, isDevVersion, backtestDate) |
|
for stock in listStockCodes] |
|
|
|
tasks_queue = multiprocessing.JoinableQueue() |
|
results_queue = multiprocessing.Queue() |
|
|
|
totalConsumers = multiprocessing.cpu_count() |
|
if totalConsumers == 1: |
|
totalConsumers = 2 |
|
if configManager.cacheEnabled is True and multiprocessing.cpu_count() > 2: |
|
totalConsumers -= 1 |
|
consumers = [StockConsumer(tasks_queue, results_queue, screenCounter, screenResultsCounter, stockDict, proxyServer, keyboardInterruptEvent) |
|
for _ in range(totalConsumers)] |
|
|
|
for worker in consumers: |
|
worker.daemon = True |
|
worker.start() |
|
|
|
if testing or testBuild: |
|
for item in items: |
|
tasks_queue.put(item) |
|
result = results_queue.get() |
|
if result is not None: |
|
screenResults = pd.concat([screenResults, pd.DataFrame([result[0]])], ignore_index=True) |
|
saveResults = pd.concat([saveResults, pd.DataFrame([result[1]])], ignore_index=True) |
|
if testing or (testBuild and len(screenResults) > 2): |
|
break |
|
else: |
|
for item in items: |
|
tasks_queue.put(item) |
|
|
|
for _ in range(multiprocessing.cpu_count()): |
|
tasks_queue.put(None) |
|
try: |
|
numStocks, totalStocks = len(listStockCodes), len(listStockCodes) |
|
os.environ['SCREENIPY_TOTAL_STOCKS'] = str(totalStocks) |
|
print(colorText.END+colorText.BOLD) |
|
bar, spinner = Utility.tools.getProgressbarStyle() |
|
with alive_bar(numStocks, bar=bar, spinner=spinner) as progressbar: |
|
while numStocks: |
|
result = results_queue.get() |
|
if result is not None: |
|
screenResults = pd.concat([screenResults, pd.DataFrame([result[0]])], ignore_index=True) |
|
saveResults = pd.concat([saveResults, pd.DataFrame([result[1]])], ignore_index=True) |
|
numStocks -= 1 |
|
os.environ['SCREENIPY_SCREEN_COUNTER'] = str(int((totalStocks-numStocks)/totalStocks*100)) |
|
progressbar.text(colorText.BOLD + colorText.GREEN + |
|
f'Found {screenResultsCounter.value} Stocks' + colorText.END) |
|
progressbar() |
|
except KeyboardInterrupt: |
|
try: |
|
keyboardInterruptEvent.set() |
|
except KeyboardInterrupt: |
|
pass |
|
print(colorText.BOLD + colorText.FAIL + |
|
"\n[+] Terminating Script, Please wait..." + colorText.END) |
|
for worker in consumers: |
|
worker.terminate() |
|
|
|
print(colorText.END) |
|
|
|
for worker in consumers: |
|
try: |
|
worker.terminate() |
|
except OSError as e: |
|
if e.winerror == 5: |
|
pass |
|
|
|
|
|
from queue import Empty |
|
while True: |
|
try: |
|
_ = tasks_queue.get(False) |
|
except Exception as e: |
|
break |
|
|
|
if CHROMA_AVAILABLE and type(vectorSearch) == list and vectorSearch[2]: |
|
chroma_client = chromadb.PersistentClient(path=CHROMADB_PATH) |
|
collection = chroma_client.get_collection(name="nse_stocks") |
|
query_embeddings= collection.get(ids = [stockCode], include=["embeddings"])["embeddings"] |
|
results = collection.query( |
|
query_embeddings=query_embeddings, |
|
n_results=4 |
|
)['ids'][0] |
|
try: |
|
results.remove(stockCode) |
|
except ValueError: |
|
pass |
|
matchedScreenResults, matchedSaveResults = pd.DataFrame(columns=screenResults.columns), pd.DataFrame(columns=saveResults.columns) |
|
for stk in results: |
|
matchedScreenResults = pd.concat([matchedScreenResults, screenResults[screenResults['Stock'].str.contains(stk)]], ignore_index=True) |
|
matchedSaveResults = pd.concat([matchedSaveResults, saveResults[saveResults['Stock'].str.contains(stk)]], ignore_index=True) |
|
screenResults, saveResults = matchedScreenResults, matchedSaveResults |
|
|
|
screenResults.sort_values(by=['Stock'], ascending=True, inplace=True) |
|
saveResults.sort_values(by=['Stock'], ascending=True, inplace=True) |
|
screenResults.set_index('Stock', inplace=True) |
|
saveResults.set_index('Stock', inplace=True) |
|
screenResults.rename( |
|
columns={ |
|
'Trend': f'Trend ({configManager.daysToLookback}Days)', |
|
'Breaking-Out': f'Breakout ({configManager.daysToLookback}Days)', |
|
'LTP': 'LTP (%% Chng)' |
|
}, |
|
inplace=True |
|
) |
|
saveResults.rename( |
|
columns={ |
|
'Trend': f'Trend ({configManager.daysToLookback}Days)', |
|
'Breaking-Out': f'Breakout ({configManager.daysToLookback}Days)', |
|
}, |
|
inplace=True |
|
) |
|
print(tabulate(screenResults, headers='keys', tablefmt='psql')) |
|
|
|
print(colorText.BOLD + colorText.GREEN + |
|
f"[+] Found {len(screenResults)} Stocks." + colorText.END) |
|
if configManager.cacheEnabled and not Utility.tools.isTradingTime() and not testing and not Utility.tools.isBacktesting(backtestDate=backtestDate): |
|
print(colorText.BOLD + colorText.GREEN + |
|
"[+] Caching Stock Data for future use, Please Wait... " + colorText.END, end='') |
|
Utility.tools.saveStockData( |
|
stockDict, configManager, loadCount) |
|
|
|
Utility.tools.setLastScreenedResults(screenResults) |
|
Utility.tools.setLastScreenedResults(saveResults, unformatted=True) |
|
if not testBuild and not downloadOnly: |
|
Utility.tools.promptSaveResults(saveResults) |
|
print(colorText.BOLD + colorText.WARN + |
|
"[+] Note: Trend calculation is based on number of days recent to screen as per your configuration." + colorText.END) |
|
print(colorText.BOLD + colorText.GREEN + |
|
"[+] Screening Completed! Press Enter to Continue.." + colorText.END) |
|
if not isGui(): |
|
input('') |
|
newlyListedOnly = False |
|
vectorSearch = False |
|
|
|
|
|
if __name__ == "__main__": |
|
Utility.tools.clearScreen() |
|
isDevVersion = OTAUpdater.checkForUpdate(proxyServer, VERSION) |
|
if not configManager.checkConfigFile(): |
|
configManager.setConfig(ConfigManager.parser, default=True, showFileCreatedText=False) |
|
if args.testbuild: |
|
print(colorText.BOLD + colorText.FAIL +"[+] Started in TestBuild mode!" + colorText.END) |
|
main(testBuild=True) |
|
elif args.download: |
|
print(colorText.BOLD + colorText.FAIL +"[+] Download ONLY mode! Stocks will not be screened!" + colorText.END) |
|
main(downloadOnly=True) |
|
else: |
|
try: |
|
while True: |
|
main() |
|
except Exception as e: |
|
raise e |
|
if isDevVersion == OTAUpdater.developmentVersion: |
|
raise(e) |
|
input(colorText.BOLD + colorText.FAIL + |
|
"[+] Press any key to Exit!" + colorText.END) |
|
sys.exit(1) |
|
|