| |
|
| | ''' |
| | * Project : Screenipy |
| | * Author : Pranjal Joshi, Swar Patel |
| | * Created : 18/05/2021 |
| | * Description : Class for managing multiprocessing |
| | ''' |
| |
|
| | import multiprocessing |
| | import pandas as pd |
| | import numpy as np |
| | import sys |
| | import os |
| | import pytz |
| | import traceback |
| | from queue import Empty |
| | from datetime import datetime |
| | import classes.Fetcher as Fetcher |
| | import classes.Screener as Screener |
| | import classes.Utility as Utility |
| | from copy import deepcopy |
| | 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 |
| | else: |
| | import multiprocessing.popen_fork as forking |
| |
|
| |
|
| | class StockConsumer(multiprocessing.Process): |
| |
|
| | def __init__(self, task_queue, result_queue, screenCounter, screenResultsCounter, stockDict, proxyServer, keyboardInterruptEvent): |
| | multiprocessing.Process.__init__(self) |
| | self.multiprocessingForWindows() |
| | self.task_queue = task_queue |
| | self.result_queue = result_queue |
| | self.screenCounter = screenCounter |
| | self.screenResultsCounter = screenResultsCounter |
| | self.stockDict = stockDict |
| | self.proxyServer = proxyServer |
| | self.keyboardInterruptEvent = keyboardInterruptEvent |
| | self.isTradingTime = Utility.tools.isTradingTime() |
| |
|
| | def run(self): |
| | |
| | try: |
| | while not self.keyboardInterruptEvent.is_set(): |
| | try: |
| | next_task = self.task_queue.get() |
| | except Empty: |
| | continue |
| | if next_task is None: |
| | self.task_queue.task_done() |
| | break |
| | answer = self.screenStocks(*(next_task)) |
| | self.task_queue.task_done() |
| | self.result_queue.put(answer) |
| | except Exception as e: |
| | sys.exit(0) |
| |
|
| | def screenStocks(self, tickerOption, executeOption, reversalOption, maLength, daysForLowestVolume, minRSI, maxRSI, respChartPattern, insideBarToLookback, totalSymbols, |
| | configManager, fetcher, screener:Screener.tools, candlePatterns, stock, newlyListedOnly, downloadOnly, vectorSearch, isDevVersion, backtestDate, printCounter=False): |
| | screenResults = pd.DataFrame(columns=[ |
| | 'Stock', 'Consolidating', 'Breaking-Out', 'MA-Signal', 'Volume', 'LTP', 'RSI', 'Trend', 'Pattern']) |
| | screeningDictionary = {'Stock': "", 'Consolidating': "", 'Breaking-Out': "", |
| | 'MA-Signal': "", 'Volume': "", 'LTP': 0, 'RSI': 0, 'Trend': "", 'Pattern': ""} |
| | saveDictionary = {'Stock': "", 'Consolidating': "", 'Breaking-Out': "", |
| | 'MA-Signal': "", 'Volume': "", 'LTP': 0, 'RSI': 0, 'Trend': "", 'Pattern': ""} |
| |
|
| | try: |
| | period = configManager.period |
| |
|
| | |
| | if newlyListedOnly: |
| | if int(configManager.period[:-1]) > 250: |
| | period = '250d' |
| | else: |
| | period = configManager.period |
| |
|
| | if (self.stockDict.get(stock) is None) or (configManager.cacheEnabled is False) or self.isTradingTime or downloadOnly: |
| | try: |
| | data, backtestReport = fetcher.fetchStockData(stock, |
| | period, |
| | configManager.duration, |
| | self.proxyServer, |
| | self.screenResultsCounter, |
| | self.screenCounter, |
| | totalSymbols, |
| | backtestDate=backtestDate, |
| | tickerOption=tickerOption) |
| | except Exception as e: |
| | return screeningDictionary, saveDictionary |
| | if configManager.cacheEnabled is True and not self.isTradingTime and (self.stockDict.get(stock) is None) or downloadOnly: |
| | self.stockDict[stock] = data.to_dict('split') |
| | if downloadOnly: |
| | raise Screener.DownloadDataOnly |
| | else: |
| | 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']) |
| |
|
| | fullData, processedData = screener.preprocessData( |
| | data, daysToLookback=configManager.daysToLookback) |
| | |
| | if type(vectorSearch) != bool and type(vectorSearch) and vectorSearch[2] == True: |
| | executeOption = 0 |
| | with self.screenCounter.get_lock(): |
| | screener.addVector(fullData, stock, vectorSearch[1]) |
| |
|
| | if newlyListedOnly: |
| | if not screener.validateNewlyListed(fullData, period): |
| | raise Screener.NotNewlyListed |
| |
|
| | with self.screenCounter.get_lock(): |
| | self.screenCounter.value += 1 |
| | if not processedData.empty: |
| | urlStock = None |
| | if tickerOption == 16: |
| | urlStock = deepcopy(stock).replace('^','').replace('.NS','') |
| | stock = fetcher.getAllNiftyIndices()[stock] |
| | stock = stock.replace('^','').replace('.NS','') |
| | urlStock = stock.replace('&','_') if urlStock is None else urlStock.replace('&','_') |
| | screeningDictionary['Stock'] = colorText.BOLD + \ |
| | colorText.BLUE + f'\x1B]8;;https://in.tradingview.com/chart?symbol=NSE%3A{urlStock}\x1B\\{stock}\x1B]8;;\x1B\\' + colorText.END if tickerOption < 15 \ |
| | else colorText.BOLD + \ |
| | colorText.BLUE + f'\x1B]8;;https://in.tradingview.com/chart?symbol={urlStock}\x1B\\{stock}\x1B]8;;\x1B\\' + colorText.END |
| | saveDictionary['Stock'] = stock |
| |
|
| | consolidationValue = screener.validateConsolidation( |
| | processedData, screeningDictionary, saveDictionary, percentage=configManager.consolidationPercentage) |
| | isMaReversal = screener.validateMovingAverages( |
| | processedData, screeningDictionary, saveDictionary, maRange=1.25) |
| | isVolumeHigh = screener.validateVolume( |
| | processedData, screeningDictionary, saveDictionary, volumeRatio=configManager.volumeRatio) |
| | isBreaking = screener.findBreakout( |
| | processedData, screeningDictionary, saveDictionary, daysToLookback=configManager.daysToLookback) |
| | isLtpValid = screener.validateLTP( |
| | fullData, screeningDictionary, saveDictionary, minLTP=configManager.minLTP, maxLTP=configManager.maxLTP) |
| | if executeOption == 4: |
| | isLowestVolume = screener.validateLowestVolume(processedData, daysForLowestVolume) |
| | else: |
| | isLowestVolume = False |
| | isValidRsi = screener.validateRSI( |
| | processedData, screeningDictionary, saveDictionary, minRSI, maxRSI) |
| | try: |
| | 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' |
| |
|
| | with SuppressOutput(suppress_stderr=True, suppress_stdout=True): |
| | isCandlePattern = candlePatterns.findPattern( |
| | processedData, screeningDictionary, saveDictionary) |
| | |
| | isConfluence = False |
| | isInsideBar = False |
| | isIpoBase = False |
| | if newlyListedOnly: |
| | isIpoBase = screener.validateIpoBase(stock, fullData, screeningDictionary, saveDictionary) |
| | if respChartPattern == 3 and executeOption == 7: |
| | isConfluence = screener.validateConfluence(stock, processedData, screeningDictionary, saveDictionary, percentage=insideBarToLookback) |
| | else: |
| | isInsideBar = screener.validateInsideBar(processedData, screeningDictionary, saveDictionary, chartPattern=respChartPattern, daysToLookback=insideBarToLookback) |
| |
|
| | with SuppressOutput(suppress_stderr=True, suppress_stdout=True): |
| | if maLength is not None and executeOption == 6 and reversalOption == 6: |
| | isNR = screener.validateNarrowRange(processedData, screeningDictionary, saveDictionary, nr=maLength) |
| | else: |
| | isNR = screener.validateNarrowRange(processedData, screeningDictionary, saveDictionary) |
| | |
| | isMomentum = screener.validateMomentum(processedData, screeningDictionary, saveDictionary) |
| | |
| | isVSA = False |
| | if not (executeOption == 7 and respChartPattern < 3): |
| | isVSA = screener.validateVolumeSpreadAnalysis(processedData, screeningDictionary, saveDictionary) |
| | if maLength is not None and executeOption == 6 and reversalOption == 4: |
| | isMaSupport = screener.findReversalMA(fullData, screeningDictionary, saveDictionary, maLength) |
| | if executeOption == 6 and reversalOption == 8: |
| | isRsiReversal = screener.findRSICrossingMA(fullData, screeningDictionary, saveDictionary) |
| |
|
| | isVCP = False |
| | if respChartPattern == 4: |
| | with SuppressOutput(suppress_stderr=True, suppress_stdout=True): |
| | isVCP = screener.validateVCP(fullData, screeningDictionary, saveDictionary) |
| |
|
| | isBuyingTrendline = False |
| | if executeOption == 7 and respChartPattern == 5: |
| | with SuppressOutput(suppress_stderr=True, suppress_stdout=True): |
| | isBuyingTrendline = screener.findTrendlines(fullData, screeningDictionary, saveDictionary) |
| |
|
| | with SuppressOutput(suppress_stderr=True, suppress_stdout=True): |
| | isLorentzian = screener.validateLorentzian(fullData, screeningDictionary, saveDictionary, lookFor = maLength) |
| |
|
| | try: |
| | backtestReport = Utility.tools.calculateBacktestReport(data=processedData, backtestDict=backtestReport) |
| | screeningDictionary.update(backtestReport) |
| | saveDictionary.update(backtestReport) |
| | except: |
| | pass |
| |
|
| | with self.screenResultsCounter.get_lock(): |
| | if executeOption == 0: |
| | self.screenResultsCounter.value += 1 |
| | return screeningDictionary, saveDictionary |
| | if (executeOption == 1 or executeOption == 2) and isBreaking and isVolumeHigh and isLtpValid: |
| | self.screenResultsCounter.value += 1 |
| | return screeningDictionary, saveDictionary |
| | if (executeOption == 1 or executeOption == 3) and (consolidationValue <= configManager.consolidationPercentage and consolidationValue != 0) and isLtpValid: |
| | self.screenResultsCounter.value += 1 |
| | return screeningDictionary, saveDictionary |
| | if executeOption == 4 and isLtpValid and isLowestVolume: |
| | self.screenResultsCounter.value += 1 |
| | return screeningDictionary, saveDictionary |
| | if executeOption == 5 and isLtpValid and isValidRsi: |
| | self.screenResultsCounter.value += 1 |
| | return screeningDictionary, saveDictionary |
| | if executeOption == 6 and isLtpValid: |
| | if reversalOption == 1: |
| | if saveDictionary['Pattern'] in CandlePatterns.reversalPatternsBullish or isMaReversal > 0 or 'buy' in saveDictionary['Pattern'].lower(): |
| | self.screenResultsCounter.value += 1 |
| | return screeningDictionary, saveDictionary |
| | elif reversalOption == 2: |
| | if saveDictionary['Pattern'] in CandlePatterns.reversalPatternsBearish or isMaReversal < 0 or 'sell' in saveDictionary['Pattern'].lower(): |
| | self.screenResultsCounter.value += 1 |
| | return screeningDictionary, saveDictionary |
| | elif reversalOption == 3 and isMomentum: |
| | self.screenResultsCounter.value += 1 |
| | return screeningDictionary, saveDictionary |
| | elif reversalOption == 4 and isMaSupport: |
| | self.screenResultsCounter.value += 1 |
| | return screeningDictionary, saveDictionary |
| | elif reversalOption == 5 and isVSA and saveDictionary['Pattern'] in CandlePatterns.reversalPatternsBullish: |
| | self.screenResultsCounter.value += 1 |
| | return screeningDictionary, saveDictionary |
| | elif reversalOption == 6 and isNR: |
| | self.screenResultsCounter.value += 1 |
| | return screeningDictionary, saveDictionary |
| | elif reversalOption == 7 and isLorentzian: |
| | self.screenResultsCounter.value += 1 |
| | return screeningDictionary, saveDictionary |
| | elif reversalOption == 8 and isRsiReversal: |
| | self.screenResultsCounter.value += 1 |
| | return screeningDictionary, saveDictionary |
| | if executeOption == 7 and isLtpValid: |
| | if respChartPattern < 3 and isInsideBar: |
| | self.screenResultsCounter.value += 1 |
| | return screeningDictionary, saveDictionary |
| | if isConfluence: |
| | self.screenResultsCounter.value += 1 |
| | return screeningDictionary, saveDictionary |
| | if isIpoBase and newlyListedOnly and not respChartPattern < 3: |
| | self.screenResultsCounter.value += 1 |
| | return screeningDictionary, saveDictionary |
| | if isVCP: |
| | self.screenResultsCounter.value += 1 |
| | return screeningDictionary, saveDictionary |
| | if isBuyingTrendline: |
| | self.screenResultsCounter.value += 1 |
| | return screeningDictionary, saveDictionary |
| | except KeyboardInterrupt: |
| | |
| | pass |
| | except Fetcher.StockDataEmptyException: |
| | pass |
| | except Screener.NotNewlyListed: |
| | pass |
| | except Screener.DownloadDataOnly: |
| | pass |
| | except KeyError: |
| | pass |
| | except Exception as e: |
| | if isDevVersion: |
| | print("[!] Dev Traceback:") |
| | traceback.print_exc() |
| | if printCounter: |
| | print(colorText.FAIL + |
| | ("\n[+] Exception Occured while Screening %s! Skipping this stock.." % stock) + colorText.END) |
| | return |
| |
|
| | def multiprocessingForWindows(self): |
| | if sys.platform.startswith('win'): |
| |
|
| | class _Popen(forking.Popen): |
| | def __init__(self, *args, **kw): |
| | if hasattr(sys, 'frozen'): |
| | os.putenv('_MEIPASS2', sys._MEIPASS) |
| | try: |
| | super(_Popen, self).__init__(*args, **kw) |
| | finally: |
| | if hasattr(sys, 'frozen'): |
| | if hasattr(os, 'unsetenv'): |
| | os.unsetenv('_MEIPASS2') |
| | else: |
| | os.putenv('_MEIPASS2', '') |
| |
|
| | forking.Popen = _Popen |
| |
|