diff --git a/.github/workflows/workflow-download-data.yml b/.github/workflows/workflow-download-data.yml index 4d29dccf..f4db5b47 100644 --- a/.github/workflows/workflow-download-data.yml +++ b/.github/workflows/workflow-download-data.yml @@ -18,32 +18,18 @@ jobs: Download_Stock_Data: - runs-on: windows-latest + runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: ref: actions-data-download + fetch-depth: 0 - - name: Set up Python 3.9.4 - uses: actions/setup-python@v2 + - name: Set up Python 3.11.6 + uses: actions/setup-python@v4 with: - python-version: 3.9.4 - - # - name: Restore Dependencies from Cache - # uses: actions/cache@v2 - # with: - # path: ~\AppData\Local\pip\Cache - # key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} - # restore-keys: | - # ${{ runner.os }}-pip- - - # - name: Install TA-Lib - # run: | - # python -m pip install --upgrade pip - # cd .github/dependencies/ - # echo %cd% - # pip install TA_Lib-0.4.19-cp39-cp39-win_amd64.whl + python-version: 3.11.6 - name: Install Python Dependencies run: | @@ -51,24 +37,22 @@ jobs: pip install -r requirements.txt - name: Download Stock Data - shell: cmd run: | - rmdir /s /q actions-data-download - mkdir actions-data-download + rm -rf actions-data-download + mkdir -p actions-data-download python src/screenipy.py -d - copy "stock_data_*.pkl" "actions-data-download" + mv stock_data_*.pkl actions-data-download/ - name: Push Pickle Data run: | git config user.name github-actions git config user.email github-actions@github.com git checkout actions-data-download - git add actions-data-download/stock_data_*.pkl --force - git commit -m "GitHub Action Workflow - Market Data Download (Default Config)" + git add --all + git diff --cached --quiet || git commit -m "GitHub Action Workflow - Market Data Download (Default Config)" git push - name: Squash Commits (Python) - shell: cmd run: | git config user.name github-actions git config user.email github-actions@github.com diff --git a/requirements.txt b/requirements.txt index f850f026..1137a4c6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -20,7 +20,7 @@ retrying scipy==1.11.2 TA-Lib-Precompiled tabulate -yfinance==0.2.32 +yfinance==0.2.54 alive-progress==1.6.2 Pillow scikit-learn==1.3.2 diff --git a/src/classes/Changelog.py b/src/classes/Changelog.py index 2dee965f..e1775097 100644 --- a/src/classes/Changelog.py +++ b/src/classes/Changelog.py @@ -7,7 +7,7 @@ from classes.ColorText import colorText -VERSION = "2.25" +VERSION = "2.26" changelog = colorText.BOLD + '[ChangeLog]\n' + colorText.END + colorText.BLUE + ''' [1.00 - Beta] @@ -302,4 +302,8 @@ [2.25] 1. Reduced docker image size by 50% (Special Thanks to https://github.com/smitpsanghavi) + +[2.26] +1. Bugfixes - yfinance package updated to 0.2.54 to fix Yahoo Finance API issue +2. Minor Improvements to maintain backward compatibility of the yfinance df ''' + colorText.END diff --git a/src/classes/Fetcher.py b/src/classes/Fetcher.py index 004a4c8e..b7441024 100644 --- a/src/classes/Fetcher.py +++ b/src/classes/Fetcher.py @@ -220,8 +220,12 @@ def fetchStockData(self, stockCode, period, duration, proxyServer, screenResults progress=False, timeout=10, start=self._getBacktestDate(backtest=backtestDate)[0], - end=self._getBacktestDate(backtest=backtestDate)[1] + end=self._getBacktestDate(backtest=backtestDate)[1], + auto_adjust=False ) + # For df backward compatibility towards yfinance 0.2.32 + data = self.makeDataBackwardCompatible(data) + # end if backtestDate != datetime.date.today(): dateDict = self._getDatesForBacktestReport(backtest=backtestDate) backtestData = yf.download( @@ -260,6 +264,7 @@ def fetchStockData(self, stockCode, period, duration, proxyServer, screenResults # Get Daily Nifty 50 Index: def fetchLatestNiftyDaily(self, proxyServer=None): data = yf.download( + auto_adjust=False, tickers="^NSEI", period='5d', interval='1d', @@ -268,6 +273,7 @@ def fetchLatestNiftyDaily(self, proxyServer=None): timeout=10 ) gold = yf.download( + auto_adjust=False, tickers="GC=F", period='5d', interval='1d', @@ -276,6 +282,7 @@ def fetchLatestNiftyDaily(self, proxyServer=None): timeout=10 ).add_prefix(prefix='gold_') crude = yf.download( + auto_adjust=False, tickers="CL=F", period='5d', interval='1d', @@ -283,12 +290,16 @@ def fetchLatestNiftyDaily(self, proxyServer=None): progress=False, timeout=10 ).add_prefix(prefix='crude_') + data = self.makeDataBackwardCompatible(data) + gold = self.makeDataBackwardCompatible(gold, column_prefix='gold_') + crude = self.makeDataBackwardCompatible(crude, column_prefix='crude_') data = pd.concat([data, gold, crude], axis=1) return data # Get Data for Five EMA strategy def fetchFiveEmaData(self, proxyServer=None): nifty_sell = yf.download( + auto_adjust=False, tickers="^NSEI", period='5d', interval='5m', @@ -297,6 +308,7 @@ def fetchFiveEmaData(self, proxyServer=None): timeout=10 ) banknifty_sell = yf.download( + auto_adjust=False, tickers="^NSEBANK", period='5d', interval='5m', @@ -305,6 +317,7 @@ def fetchFiveEmaData(self, proxyServer=None): timeout=10 ) nifty_buy = yf.download( + auto_adjust=False, tickers="^NSEI", period='5d', interval='15m', @@ -313,6 +326,7 @@ def fetchFiveEmaData(self, proxyServer=None): timeout=10 ) banknifty_buy = yf.download( + auto_adjust=False, tickers="^NSEBANK", period='5d', interval='15m', @@ -320,6 +334,10 @@ def fetchFiveEmaData(self, proxyServer=None): progress=False, timeout=10 ) + nifty_buy = self.makeDataBackwardCompatible(nifty_buy) + banknifty_buy = self.makeDataBackwardCompatible(banknifty_buy) + nifty_sell = self.makeDataBackwardCompatible(nifty_sell) + banknifty_sell = self.makeDataBackwardCompatible(banknifty_sell) return nifty_buy, banknifty_buy, nifty_sell, banknifty_sell # Load stockCodes from the watchlist.xlsx @@ -352,3 +370,19 @@ def fetchWatchlist(self): f'[+] watchlist_template.xlsx created in {os.getcwd()} as a referance template.' + colorText.END) return None return data + + def makeDataBackwardCompatible(self, data:pd.DataFrame, column_prefix:str=None) -> pd.DataFrame: + data = data.droplevel(level=1, axis=1) + data = data.rename_axis(None, axis=1) + column_prefix = '' if column_prefix is None else column_prefix + data = data[ + [ + f'{column_prefix}Open', + f'{column_prefix}High', + f'{column_prefix}Low', + f'{column_prefix}Close', + f'{column_prefix}Adj Close', + f'{column_prefix}Volume' + ] + ] + return data diff --git a/src/classes/Screener.py b/src/classes/Screener.py index e148f450..cc4f4089 100644 --- a/src/classes/Screener.py +++ b/src/classes/Screener.py @@ -54,23 +54,23 @@ def getCandleType(self, dailyData): # Preprocess the acquired data - def preprocessData(self, data, daysToLookback=None): + def preprocessData(self, data:pd.DataFrame, daysToLookback=None): if daysToLookback is None: daysToLookback = self.configManager.daysToLookback if self.configManager.useEMA: sma = ScreenerTA.EMA(data['Close'],timeperiod=50) lma = ScreenerTA.EMA(data['Close'],timeperiod=200) - data.insert(6,'SMA',sma) - data.insert(7,'LMA',lma) + data.insert(len(data.columns),'SMA',sma) + data.insert(len(data.columns),'LMA',lma) else: sma = data.rolling(window=50).mean() lma = data.rolling(window=200).mean() - data.insert(6,'SMA',sma['Close']) - data.insert(7,'LMA',lma['Close']) + data.insert(len(data.columns),'SMA',sma['Close']) + data.insert(len(data.columns),'LMA',lma['Close']) vol = data.rolling(window=20).mean() rsi = ScreenerTA.RSI(data['Close'], timeperiod=14) - data.insert(8,'VolMA',vol['Volume']) - data.insert(9,'RSI',rsi) + data.insert(len(data.columns),'VolMA',vol['Volume']) + data.insert(len(data.columns),'RSI',rsi) data = data[::-1] # Reverse the dataframe # data = data.fillna(0) # data = data.replace([np.inf, -np.inf], 0) @@ -411,7 +411,7 @@ def findReversalMA(self, data, screenDict, saveDict, maLength, percentage=0.015) maRev = ScreenerTA.EMA(data['Close'],timeperiod=maLength) else: maRev = ScreenerTA.MA(data['Close'],timeperiod=maLength) - data.insert(10,'maRev',maRev) + data.insert(len(data.columns),'maRev',maRev) data = data[::-1].head(3) if data.equals(data[(data.Close >= (data.maRev - (data.maRev*percentage))) & (data.Close <= (data.maRev + (data.maRev*percentage)))]) and data.head(1)['Close'].iloc[0] >= data.head(1)['maRev'].iloc[0]: if self.configManager.stageTwo: @@ -426,7 +426,7 @@ def findReversalMA(self, data, screenDict, saveDict, maLength, percentage=0.015) def findRSICrossingMA(self, data, screenDict, saveDict, maLength=9): data = data[::-1] maRsi = ScreenerTA.MA(data['RSI'], timeperiod=maLength) - data.insert(10,'maRsi',maRsi) + data.insert(len(data.columns),'maRsi',maRsi) data = data[::-1].head(3) if data['maRsi'].iloc[0] <= data['RSI'].iloc[0] and data['maRsi'].iloc[1] > data['RSI'].iloc[1]: screenDict['MA-Signal'] = colorText.BOLD + colorText.GREEN + f'RSI-MA-Buy' + colorText.END diff --git a/src/classes/ScreenipyTA.py b/src/classes/ScreenipyTA.py index 8af0382d..0baf85b5 100644 --- a/src/classes/ScreenipyTA.py +++ b/src/classes/ScreenipyTA.py @@ -18,42 +18,42 @@ def EMA(close, timeperiod): try: return talib.ema(close,timeperiod) except Exception as e: - return talib.EMA(close,timeperiod) + return talib.EMA(close.to_numpy().reshape(-1),timeperiod) @staticmethod def SMA(close, timeperiod): try: return talib.sma(close,timeperiod) except Exception as e: - return talib.SMA(close,timeperiod) + return talib.SMA(close.to_numpy().reshape(-1),timeperiod) @staticmethod def MA(close, timeperiod): try: return talib.ma(close,timeperiod) except Exception as e: - return talib.MA(close,timeperiod) + return talib.MA(close.to_numpy().reshape(-1),timeperiod) @staticmethod def MACD(close, fast, slow, signal): try: return talib.macd(close,fast,slow,signal) except Exception as e: - return talib.MACD(close,fast,slow,signal) + return talib.MACD(close.to_numpy().reshape(-1),fast.to_numpy().reshape(-1),slow.to_numpy().reshape(-1),signal.to_numpy().reshape(-1)) @staticmethod def RSI(close, timeperiod): try: return talib.rsi(close,timeperiod) except Exception as e: - return talib.RSI(close,timeperiod) + return talib.RSI(close.to_numpy().reshape(-1),timeperiod) @staticmethod def CCI(high, low, close, timeperiod): try: return talib.cci(high, low, close,timeperiod) except Exception as e: - return talib.CCI(high, low, close,timeperiod) + return talib.CCI(high.to_numpy().reshape(-1), low.to_numpy().reshape(-1), close.to_numpy().reshape(-1),timeperiod) @staticmethod @@ -62,7 +62,7 @@ def CDLMORNINGSTAR(open, high, low, close): try: return talib.cdl_pattern(open,high,low,close,'morningstar').tail(1).values[0][0] != 0 except Exception as e: - return talib.CDLMORNINGSTAR(open,high,low,close).tail(1).item() != 0 + return talib.CDLMORNINGSTAR(open.to_numpy().reshape(-1), high.to_numpy().reshape(-1), low.to_numpy().reshape(-1) ,close.to_numpy().reshape(-1)).tail(1).item() != 0 except AttributeError: return False @@ -72,7 +72,7 @@ def CDLMORNINGDOJISTAR(open, high, low, close): try: return talib.cdl_pattern(open,high,low,close,'morningdojistar').tail(1).values[0][0] != 0 except Exception as e: - return talib.CDLMORNINGDOJISTAR(open,high,low,close).tail(1).item() != 0 + return talib.CDLMORNINGDOJISTAR(open.to_numpy().reshape(-1), high.to_numpy().reshape(-1), low.to_numpy().reshape(-1) ,close.to_numpy().reshape(-1)).tail(1).item() != 0 except AttributeError: return False @@ -82,7 +82,7 @@ def CDLEVENINGSTAR(open, high, low, close): try: return talib.cdl_pattern(open,high,low,close,'eveningstar').tail(1).values[0][0] != 0 except Exception as e: - return talib.CDLEVENINGSTAR(open,high,low,close).tail(1).item() != 0 + return talib.CDLEVENINGSTAR(open.to_numpy().reshape(-1), high.to_numpy().reshape(-1), low.to_numpy().reshape(-1) ,close.to_numpy().reshape(-1)).tail(1).item() != 0 except AttributeError: return False @@ -92,7 +92,7 @@ def CDLEVENINGDOJISTAR(open, high, low, close): try: return talib.cdl_pattern(open,high,low,close,'eveningdojistar').tail(1).values[0][0] != 0 except Exception as e: - return talib.CDLEVENINGDOJISTAR(open,high,low,close).tail(1).item() != 0 + return talib.CDLEVENINGDOJISTAR(open.to_numpy().reshape(-1), high.to_numpy().reshape(-1), low.to_numpy().reshape(-1) ,close.to_numpy().reshape(-1)).tail(1).item() != 0 except AttributeError: return False @@ -102,7 +102,7 @@ def CDLLADDERBOTTOM(open, high, low, close): try: return talib.cdl_pattern(open,high,low,close,'ladderbottom').tail(1).values[0][0] != 0 except Exception as e: - return talib.CDLLADDERBOTTOM(open,high,low,close).tail(1).item() != 0 + return talib.CDLLADDERBOTTOM(open.to_numpy().reshape(-1), high.to_numpy().reshape(-1), low.to_numpy().reshape(-1) ,close.to_numpy().reshape(-1)).tail(1).item() != 0 except AttributeError: return False @@ -112,7 +112,7 @@ def CDL3LINESTRIKE(open, high, low, close): try: return talib.cdl_pattern(open,high,low,close,'3linestrike').tail(1).values[0][0] != 0 except Exception as e: - return talib.CDL3LINESTRIKE(open,high,low,close).tail(1).item() != 0 + return talib.CDL3LINESTRIKE(open.to_numpy().reshape(-1), high.to_numpy().reshape(-1), low.to_numpy().reshape(-1) ,close.to_numpy().reshape(-1)).tail(1).item() != 0 except AttributeError: return False @@ -122,7 +122,7 @@ def CDL3BLACKCROWS(open, high, low, close): try: return talib.cdl_pattern(open,high,low,close,'3blackcrows').tail(1).values[0][0] != 0 except Exception as e: - return talib.CDL3BLACKCROWS(open,high,low,close).tail(1).item() != 0 + return talib.CDL3BLACKCROWS(open.to_numpy().reshape(-1), high.to_numpy().reshape(-1), low.to_numpy().reshape(-1) ,close.to_numpy().reshape(-1)).tail(1).item() != 0 except AttributeError: return False @@ -132,7 +132,7 @@ def CDL3INSIDE(open, high, low, close): try: return talib.cdl_pattern(open,high,low,close,'3inside').tail(1).values[0][0] != 0 except Exception as e: - return talib.CDL3INSIDE(open,high,low,close).tail(1).item() != 0 + return talib.CDL3INSIDE(open.to_numpy().reshape(-1), high.to_numpy().reshape(-1), low.to_numpy().reshape(-1) ,close.to_numpy().reshape(-1)).tail(1).item() != 0 except AttributeError: return False @@ -142,7 +142,7 @@ def CDL3OUTSIDE(open, high, low, close): try: return talib.cdl_pattern(open,high,low,close,'3outside').tail(1).values[0][0] except Exception as e: - return talib.CDL3OUTSIDE(open,high,low,close).tail(1).item() != 0 + return talib.CDL3OUTSIDE(open.to_numpy().reshape(-1), high.to_numpy().reshape(-1), low.to_numpy().reshape(-1) ,close.to_numpy().reshape(-1)).tail(1).item() != 0 except AttributeError: return False @@ -152,7 +152,7 @@ def CDL3WHITESOLDIERS(open, high, low, close): try: return talib.cdl_pattern(open,high,low,close,'3whitesoldiers').tail(1).values[0][0] != 0 except Exception as e: - return talib.CDL3WHITESOLDIERS(open,high,low,close).tail(1).item() != 0 + return talib.CDL3WHITESOLDIERS(open.to_numpy().reshape(-1), high.to_numpy().reshape(-1), low.to_numpy().reshape(-1) ,close.to_numpy().reshape(-1)).tail(1).item() != 0 except AttributeError: return False @@ -162,7 +162,7 @@ def CDLHARAMI(open, high, low, close): try: return talib.cdl_pattern(open,high,low,close,'harami').tail(1).values[0][0] != 0 except Exception as e: - return talib.CDLHARAMI(open,high,low,close).tail(1).item() != 0 + return talib.CDLHARAMI(open.to_numpy().reshape(-1), high.to_numpy().reshape(-1), low.to_numpy().reshape(-1) ,close.to_numpy().reshape(-1)).tail(1).item() != 0 except AttributeError: return False @@ -172,7 +172,7 @@ def CDLHARAMICROSS(open, high, low, close): try: return talib.cdl_pattern(open,high,low,close,'haramicross').tail(1).values[0][0] != 0 except Exception as e: - return talib.CDLHARAMICROSS(open,high,low,close).tail(1).item() != 0 + return talib.CDLHARAMICROSS(open.to_numpy().reshape(-1), high.to_numpy().reshape(-1), low.to_numpy().reshape(-1) ,close.to_numpy().reshape(-1)).tail(1).item() != 0 except AttributeError: return False @@ -182,7 +182,7 @@ def CDLMARUBOZU(open, high, low, close): try: return talib.cdl_pattern(open,high,low,close,'marubozu').tail(1).values[0][0] != 0 except Exception as e: - return talib.CDLMARUBOZU(open,high,low,close).tail(1).item() != 0 + return talib.CDLMARUBOZU(open.to_numpy().reshape(-1), high.to_numpy().reshape(-1), low.to_numpy().reshape(-1) ,close.to_numpy().reshape(-1)).tail(1).item() != 0 except AttributeError: return False @@ -192,7 +192,7 @@ def CDLHANGINGMAN(open, high, low, close): try: return talib.cdl_pattern(open,high,low,close,'hangingman').tail(1).values[0][0] != 0 except Exception as e: - return talib.CDLHANGINGMAN(open,high,low,close).tail(1).item() != 0 + return talib.CDLHANGINGMAN(open.to_numpy().reshape(-1), high.to_numpy().reshape(-1), low.to_numpy().reshape(-1) ,close.to_numpy().reshape(-1)).tail(1).item() != 0 except AttributeError: return False @@ -202,7 +202,7 @@ def CDLHAMMER(open, high, low, close): try: return talib.cdl_pattern(open,high,low,close,'hammer').tail(1).values[0][0] != 0 except Exception as e: - return talib.CDLHAMMER(open,high,low,close).tail(1).item() != 0 + return talib.CDLHAMMER(open.to_numpy().reshape(-1), high.to_numpy().reshape(-1), low.to_numpy().reshape(-1) ,close.to_numpy().reshape(-1)).tail(1).item() != 0 except AttributeError: return False @@ -212,7 +212,7 @@ def CDLINVERTEDHAMMER(open, high, low, close): try: return talib.cdl_pattern(open,high,low,close,'invertedhammer').tail(1).values[0][0] != 0 except Exception as e: - return talib.CDLINVERTEDHAMMER(open,high,low,close).tail(1).item() != 0 + return talib.CDLINVERTEDHAMMER(open.to_numpy().reshape(-1), high.to_numpy().reshape(-1), low.to_numpy().reshape(-1) ,close.to_numpy().reshape(-1)).tail(1).item() != 0 except AttributeError: return False @@ -222,7 +222,7 @@ def CDLSHOOTINGSTAR(open, high, low, close): try: return talib.cdl_pattern(open,high,low,close,'shootingstar').tail(1).values[0][0] != 0 except Exception as e: - return talib.CDLSHOOTINGSTAR(open,high,low,close).tail(1).item() != 0 + return talib.CDLSHOOTINGSTAR(open.to_numpy().reshape(-1), high.to_numpy().reshape(-1), low.to_numpy().reshape(-1) ,close.to_numpy().reshape(-1)).tail(1).item() != 0 except AttributeError: return False @@ -232,7 +232,7 @@ def CDLDRAGONFLYDOJI(open, high, low, close): try: return talib.cdl_pattern(open,high,low,close,'dragonflydoji').tail(1).values[0][0] != 0 except Exception as e: - return talib.CDLDRAGONFLYDOJI(open,high,low,close).tail(1).item() != 0 + return talib.CDLDRAGONFLYDOJI(open.to_numpy().reshape(-1), high.to_numpy().reshape(-1), low.to_numpy().reshape(-1) ,close.to_numpy().reshape(-1)).tail(1).item() != 0 except AttributeError: return False @@ -242,7 +242,7 @@ def CDLGRAVESTONEDOJI(open, high, low, close): try: return talib.cdl_pattern(open,high,low,close,'gravestonedoji').tail(1).values[0][0] != 0 except Exception as e: - return talib.CDLGRAVESTONEDOJI(open,high,low,close).tail(1).item() != 0 + return talib.CDLGRAVESTONEDOJI(open.to_numpy().reshape(-1), high.to_numpy().reshape(-1), low.to_numpy().reshape(-1) ,close.to_numpy().reshape(-1)).tail(1).item() != 0 except AttributeError: return False @@ -252,7 +252,7 @@ def CDLDOJI(open, high, low, close): try: return talib.cdl_pattern(open,high,low,close,'doji').tail(1).values[0][0] != 0 except Exception as e: - return talib.CDLDOJI(open,high,low,close).tail(1).item() != 0 + return talib.CDLDOJI(open.to_numpy().reshape(-1), high.to_numpy().reshape(-1), low.to_numpy().reshape(-1) ,close.to_numpy().reshape(-1)).tail(1).item() != 0 except AttributeError: return False @@ -263,7 +263,7 @@ def CDLENGULFING(open, high, low, close): try: return talib.cdl_pattern(open,high,low,close,'engulfing').tail(1).values[0][0] except Exception as e: - return talib.CDLENGULFING(open,high,low,close).tail(1).item() != 0 + return talib.CDLENGULFING(open.to_numpy().reshape(-1), high.to_numpy().reshape(-1), low.to_numpy().reshape(-1) ,close.to_numpy().reshape(-1)).tail(1).item() != 0 except AttributeError: return False \ No newline at end of file diff --git a/src/release.md b/src/release.md index f18bf5a9..ddb0e55e 100644 --- a/src/release.md +++ b/src/release.md @@ -7,10 +7,10 @@ Screeni-py is now on **YouTube** for additional help! - Thank You for your suppo ⚠️ **Executable files (.exe, .bin and .run) are now DEPRECATED! Please Switch to Docker** -1. Added **Filters** to Result Table Headers (Apply Filters like Excel as per your strategy!) -2. Fixed Breakout Screening for **F&O Stocks** (Changed Data Source to Zerodha Kite from NSE website) -3. **RSI** based **Reversal** using *9 SMA* of RSI - Try `Option > 6 > 8` -4. **Backtesting Reports** Added for Screening Patterns to Develope and Test Strategies! +1. Fixed Blank Results issue by upgrading Yahoo Finance API client. +2. Added **Filters** to Result Table Headers (Apply Filters like Excel as per your strategy!) +3. Fixed Breakout Screening for **F&O Stocks** (Changed Data Source to Zerodha Kite from NSE website) +4. **RSI** based **Reversal** using *9 SMA* of RSI - Try `Option > 6 > 8` 5. **Position Size Calculator** tab added for Better and Quick Risk Management! 6. **Lorentzian Classification** (invented by Justin Dehorty) added for enhanced accuracy for your trades - - Try `Option > 6 > 7` 🤯 7. **Artificial Intelligence v3 for Nifty 50 Prediction** - Predict Next day Gap-up/down using Nifty, Gold and Crude prices! - Try `Select Index for Screening > N`