diff --git a/.github/dependencies/sqlite3.dll b/.github/dependencies/sqlite3.dll new file mode 100644 index 00000000..e1230593 Binary files /dev/null and b/.github/dependencies/sqlite3.dll differ diff --git a/.github/workflows/workflow-build-matrix.yml b/.github/workflows/workflow-build-matrix.yml index 56d35732..091be3f7 100644 --- a/.github/workflows/workflow-build-matrix.yml +++ b/.github/workflows/workflow-build-matrix.yml @@ -15,7 +15,7 @@ jobs: # Job for builing packages Build-Executables: - name: Build Packages + name: Build Packages (Disabled onwards v2.03) #needs: Create-Release runs-on: ${{ matrix.os }} strategy: @@ -24,7 +24,7 @@ jobs: - os: windows-latest TARGET: Windows CMD_BUILD: | - pyinstaller --onefile --icon=src\icon.ico src\screenipy.py --hidden-import cmath --hidden-import talib.stream --hidden-import numpy --hidden-import pandas --hidden-import alive-progress --hidden-import alive_progress + pyinstaller --onefile --icon=src\icon.ico src\screenipy.py --hidden-import cmath --hidden-import talib.stream --hidden-import numpy --hidden-import pandas --hidden-import alive-progress --hidden-import alive_progress --hidden-import chromadb DEP_BUILD: | python -m pip install --upgrade pip echo Installing TA-lib... @@ -44,7 +44,7 @@ jobs: - os: ubuntu-20.04 TARGET: Linux CMD_BUILD: | - pyinstaller --onefile --icon=src/icon.ico src/screenipy.py --hidden-import cmath --hidden-import talib.stream --hidden-import numpy --hidden-import pandas --hidden-import alive-progress --hidden-import alive_progress + pyinstaller --onefile --icon=src/icon.ico src/screenipy.py --hidden-import cmath --hidden-import talib.stream --hidden-import numpy --hidden-import pandas --hidden-import alive-progress --hidden-import alive_progress --hidden-import chromadb mv /home/runner/work/Screeni-py/Screeni-py/dist/screenipy /home/runner/work/Screeni-py/Screeni-py/dist/screenipy.bin chmod +x /home/runner/work/Screeni-py/Screeni-py/dist/screenipy.bin DEP_BUILD: | @@ -68,7 +68,7 @@ jobs: - os: macos-latest TARGET: MacOS CMD_BUILD: | - pyinstaller --onefile --windowed --icon=src/icon.ico src/screenipy.py --hidden-import cmath --hidden-import talib.stream --hidden-import numpy --hidden-import pandas --hidden-import alive-progress --hidden-import alive_progress + pyinstaller --onefile --windowed --icon=src/icon.ico src/screenipy.py --hidden-import cmath --hidden-import talib.stream --hidden-import numpy --hidden-import pandas --hidden-import alive-progress --hidden-import alive_progress --hidden-import chromadb mv /Users/runner/work/Screeni-py/Screeni-py/dist/screenipy /Users/runner/work/Screeni-py/Screeni-py/dist/screenipy.run DEP_BUILD: | brew install ta-lib @@ -89,38 +89,38 @@ jobs: shell: bash run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//} - - name: Set up Python 3.9.4 - uses: actions/setup-python@v2 - with: - python-version: 3.9.4 - - - name: Load Cache for Linux Dependencies - uses: actions/cache@v2 - if: startsWith(runner.os, 'Linux') - with: - path: | - /usr/include/ta-lib - /usr/bin/ta-lib-config - key: ${{ runner.os }}-talib - restore-keys: | - ${{ runner.os }}-talib - - - name: Install dependencies for ${{ matrix.TARGET }} - run: ${{ matrix.DEP_BUILD }} - - - name: Build for ${{ matrix.TARGET }} - run: ${{ matrix.CMD_BUILD }} - - - name: Test Built Binary for ${{ matrix.TARGET }} - shell: bash - run: ${{ matrix.TEST_BUILD }} - continue-on-error: false - - - name: Save Binaries as Artifacts - uses: actions/upload-artifact@v2 - with: - name: ${{ matrix.FILE_NAME }} - path: ${{ matrix.OUT_PATH }} + # - name: Set up Python 3.9.4 + # uses: actions/setup-python@v2 + # with: + # python-version: 3.9.4 + + # - name: Load Cache for Linux Dependencies + # uses: actions/cache@v2 + # if: startsWith(runner.os, 'Linux') + # with: + # path: | + # /usr/include/ta-lib + # /usr/bin/ta-lib-config + # key: ${{ runner.os }}-talib + # restore-keys: | + # ${{ runner.os }}-talib + + # - name: Install dependencies for ${{ matrix.TARGET }} + # run: ${{ matrix.DEP_BUILD }} + + # - name: Build for ${{ matrix.TARGET }} + # run: ${{ matrix.CMD_BUILD }} + + # - name: Test Built Binary for ${{ matrix.TARGET }} + # shell: bash + # run: ${{ matrix.TEST_BUILD }} + # continue-on-error: false + + # - name: Save Binaries as Artifacts + # uses: actions/upload-artifact@v2 + # with: + # name: ${{ matrix.FILE_NAME }} + # path: ${{ matrix.OUT_PATH }} - name: Read release.md id: read_release @@ -136,8 +136,8 @@ jobs: uses: svenstaro/upload-release-action@v2 with: repo_token: ${{ secrets.GITHUB_TOKEN }} - file: ${{ matrix.OUT_PATH }} - asset_name: ${{ matrix.FILE_NAME }} + file: README.md + asset_name: README.md tag: ${{ github.ref }} release_name: Screenipy - v${{ steps.get_version.outputs.VERSION }} body: | @@ -170,8 +170,6 @@ jobs: run: | VER=$(grep 'VERSION = ' src/classes/Changelog.py | awk -F'"' '{print $2}') echo "VERSION=$VER" >> $GITHUB_ENV - # echo "ver=$(grep 'VERSION = ' src/classes/Changelog.py | awk -F'"' '{print $2}')" >> $GITHUB_OUTPUT - echo $VER - name: Streamlit Build and Push uses: docker/build-push-action@v4 @@ -180,5 +178,4 @@ jobs: platforms: linux/amd64, linux/arm64 push: true tags: joshipranjal/screeni-py:latest, joshipranjal/screeni-py:${{ env.VERSION }} - # tags: joshipranjal/screeni-py:latest, joshipranjal/screeni-py:${{ steps.version.outputs.ver }} diff --git a/README.md b/README.md index 2c70fbd2..ccce323f 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ | **Download** | **Use** | **Discussion** | **Bugs/Issues** | **Documentation** | | :---: | :---: | :---: | :---: | :---: | | [![cloud-computing (1)](https://user-images.githubusercontent.com/6128978/149935359-ca0a7155-d1e3-4e47-98e8-67f879e707e7.png)](https://github.com/pranjal-joshi/Screeni-py/releases/latest) | [![docker](https://github.com/pranjal-joshi/Screeni-py/assets/6128978/f44054b8-9fcb-465c-a38b-63f6ecc4a0c9)](https://hub.docker.com/r/joshipranjal/screeni-py/tags) | [![meeting](https://user-images.githubusercontent.com/6128978/149935812-31266023-cc5b-4c98-a416-1d4cf8800c0c.png)](https://github.com/pranjal-joshi/Screeni-py/discussions) | [![warning](https://user-images.githubusercontent.com/6128978/149936142-04d7cf1c-5bc5-45c1-a8e4-015454a2de48.png)](https://github.com/pranjal-joshi/Screeni-py/issues?q=is%3Aissue) | [![help](https://user-images.githubusercontent.com/6128978/149937331-5ee5c00a-748d-4fbf-a9f9-e2273480d8a2.png)](https://github.com/pranjal-joshi/Screeni-py/blob/main/README.md#what-is-screeni-py) | -| Download the Latest Version | Get started quickly using Docker | Join/Read the Community Discussion | Raise an Issue about a Problem | Get Help about Usage | +| Download the Latest Version (DEPRECATED) | Get started quickly using Docker | Join/Read the Community Discussion | Raise an Issue about a Problem | Get Help about Usage | @@ -19,13 +19,24 @@ ### A Python-based stock screener for NSE, India. -**Screenipy** is an advanced stock screener to find potential breakout stocks from NSE and tell it's possible breakout values. It also helps to find the stocks which are consolidating and may breakout, or the particular chart patterns that you're looking specifically to make your decisions. +**Screenipy** is an advanced stock screener to find potential breakout stocks from NSE and tell its possible breakout values. It also helps to find the stocks that are consolidating and may breakout, or the particular chart patterns that you're looking for specifically to make your decisions. Screenipy is totally customizable and it can screen stocks with the settings that you have provided. -## How to use? +## How to use? (New Version - GUI Based) +* Install Docker Desktop and pull the `latest` docker image from the [release](https://github.com/pranjal-joshi/Screeni-py/releases/latest) page. +* Checkout this [YouTube Video](https://youtu.be/2HMN0ac4H20) for detailed installation guide. + +image +image +image +image +image + + +## How to use? (Older Version - CLI Based - DEPRECATED) * Download the suitable file according to your OS or install Docker Desktop and pull the `latest` docker image. -* Linux & Mac users should make sure that the `screenipy.bin or screenipy.run` is having `execute` permission. -* **Run** the file. Following window will appear after a brief delay. +* Linux & Mac users should make sure that the `screenipy.bin or screenipy.run` has `execute` permission. +* **Run** the file. The following window will appear after a brief delay. ![home](https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/new-features/screenshots/screenipy_demo.gif) @@ -39,7 +50,7 @@ Screenipy is totally customizable and it can screen stocks with the settings tha ![results](https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/new-features/screenshots/results.png) ![done](https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/new-features/screenshots/done.png) -* Once done, you can also save the results in an excel file. +* Once done, you can also save the results in an Excel file. ## Understanding the Result Table: @@ -49,12 +60,12 @@ The Result table contains a lot of different parameters which can be pretty over |:---:|:---:|:---|:---| |1|**Stock**|This is a NSE scrip symbol. If your OS/Terminal supports unicode, You can directly open **[TradingView](https://in.tradingview.com/)** charts by pressing `Ctrl+Click` on the stock name.|[TATAMOTORS](https://in.tradingview.com/chart?symbol=NSE%3ATATAMOTORS)| |2|**Consolidating**|It gives the price range in which stock is trading since last `N` days. `N` is configurable and can be modified by executing `Edit User Configuration` option.|If stock is trading between price 100-120 in last 30 days, Output will be `Range = 20.0 %`| -|3|**Breakout (N Days)**|This is pure magic! The `BO` is Breakout level in last N days while `R` is the next resistance level if available. Investor should consider both BO & R level to decide entry/exits in their trades.|`B:302, R:313`(Breakout level is 100 & Next resistance is 102)| +|3|**Breakout (N Days)**|This is pure magic! The `BO` is Breakout level in last N days while `R` is the next resistance level if available. An investor should consider both BO & R level to decide entry/exits in their trades.|`B:302, R:313`(Breakout level is 100 & Next resistance is 102)| |4|**LTP**|LTP is the Last Traded Price of an asset traded on NSE.|`298.7` (Stock is trading at this price)| |5|**Volume**|Volume shows the relative volume of the recent candle with respect to 20 period MA of Volume. It could be `Unknown` for newly listed stocks.|if 20MA(Volume) is 1M and todays Volume is 2.8M, then `Volume = 2.8x`| -|6|**MA-Signal**|It describes the price trend of an asset by analysing various 50-200 MA/EMA crossover strategies.|`200MA-Support`,`BullCross-50MA` etc| -|7|**RSI**|For the momentum traders, it describes 14-period RSI for quick decision making about their trading plans|`0 to 100`| -|8|**Trend**|By using advance algorithms, the average trendlines are computed for `N` days and their strenght is displayed depending on steepness of trendlines. (This does NOT show any trendline on chart, it is calculated internally)|`Strong Up`, `Weak Down` etc.| +|6|**MA-Signal**|It describes the price trend of an asset by analyzing various 50-200 MA/EMA crossover strategies.|`200MA-Support`,`BullCross-50MA` etc| +|7|**RSI**|For the momentum traders, it describes 14-period RSI for quick decision-making about their trading plans|`0 to 100`| +|8|**Trend**|By using advanced algorithms, the average trendlines are computed for `N` days and their strength is displayed depending on the steepness of the trendlines. (This does NOT show any trendline on a chart, it is calculated internally)|`Strong Up`, `Weak Down` etc.| |9|**Pattern**|If the chart or the candle itself forming any important pattern in the recent timeframe or as per the selected screening option, various important patterns will be indicated here.|`Momentum Gainer`, `Inside Bar (N)`,`Bullish Engulfing` etc.| ## Hack it your way: @@ -73,7 +84,7 @@ cachestockdata = y onlystagetwostocks = y useema = n ``` -Try to tweak this parameters as per your trading styles. For example, If you're comfortable with weekly charts, make `duration=5d` and so on. +Try to tweak these parameters as per your trading styles. For example, If you're comfortable with weekly charts, make `duration=5d` and so on. ## Installation Guide: @@ -86,7 +97,7 @@ Try to tweak this parameters as per your trading styles. For example, If you're ![Windows](https://img.shields.io/badge/Windows-0078D6?style=for-the-badge&logo=windows&logoColor=white) ![Linux](https://img.shields.io/badge/Linux-FCC624?style=for-the-badge&logo=linux&logoColor=black) ![Mac OS](https://img.shields.io/badge/mac%20os-D3D3D3?style=for-the-badge&logo=apple&logoColor=000000) ![Docker](https://img.shields.io/badge/docker-%230db7ed.svg?style=for-the-badge&logo=docker&logoColor=white) -**Should I download the Exe/Bin file? Or Should I use Docker?** +**Why we shifted to Docker from the Good old EXEs?** | Executable/Binary File | Docker | | :-- | :-- | @@ -101,12 +112,13 @@ Try to tweak this parameters as per your trading styles. For example, If you're | Antivirus may block this as untrusted file ⚠ī¸ | No issues with Antivirus | | Need to download new file for every update | Updates quickly with minimal downloading | | No need of commands/technical knowledge | Very basic command execution skills may be required | -| Custom configuration works and persisted accross the runs/restarts | Config file resets at every run but your custom config can be uploaded in GUI mode | +| Custom configuration works and persists accross the runs/restarts | Config file resets at every run but your custom config can be uploaded in GUI mode | +| Incompatible with Vector Database ⚠ī¸ | Compatible with all Python libraries | -### How to setup and use Screeni-py with Docker? +### How to set up and use Screeni-py with Docker? 1. Download and Install [Docker Desktop](https://www.docker.com/products/docker-desktop/) with default settings -2. If you are using windows, update WSL (Windows subsystem for linux) by running `wsl --update` command in command prompt +2. If you are using Windows, update WSL (Windows subsystem for linux) by running `wsl --update` command in the command prompt 3. Restart your computer after installation 4. Open Docker Desktop and keep it as it is 5. Open Command Prompt (Windows) or Terminal (Mac/Linux) and run command `docker pull joshipranjal/screeni-py:latest` @@ -119,8 +131,10 @@ Try to tweak this parameters as per your trading styles. For example, If you're docker run -it --entrypoint /bin/bash joshipranjal/screeni-py:latest -c "run_screenipy.sh --cli" ``` +Check out this [YouTube Video](https://youtu.be/2HMN0ac4H20) for a detailed installation guide. + ## Contributing: -* Please feel free to Suggest improvements bugs by creating an issue. +* Please feel free to Suggest improvements/report bugs by creating an issue. * Please follow the [Guidelines for Contributing](https://github.com/pranjal-joshi/Screeni-py/blob/new-features/CONTRIBUTING.md) while making a Pull Request. ## Disclaimer: diff --git a/requirements.txt b/requirements.txt index c852a3a9..b8521039 100644 --- a/requirements.txt +++ b/requirements.txt @@ -18,7 +18,7 @@ pyinstaller==5.6.2 pytest-mock pytoml retrying -scipy==1.7.3 +scipy==1.11.2 ta-lib tabulate yfinance==0.1.87 @@ -61,4 +61,5 @@ pandas_ta # protobuf==3.19.6 protobuf streamlit==1.26.0 -tensorflow \ No newline at end of file +tensorflow +chromadb==0.4.10 \ No newline at end of file diff --git a/src/classes/Changelog.py b/src/classes/Changelog.py index 88b02db4..e7f9ee0e 100644 --- a/src/classes/Changelog.py +++ b/src/classes/Changelog.py @@ -7,7 +7,7 @@ from classes.ColorText import colorText -VERSION = "2.02" +VERSION = "2.03" changelog = colorText.BOLD + '[ChangeLog]\n' + colorText.END + colorText.BLUE + ''' [1.00 - Beta] @@ -216,4 +216,8 @@ 3. Cosmetic improvements 4. YouTube Video added to docs +[2.03] +1. AI based Nifty-50 Gap up/down prediction added to GUI +2. Cosmetic updates and minor bug-fixes. + ''' + colorText.END diff --git a/src/classes/OtaUpdater.py b/src/classes/OtaUpdater.py index 3dd1cfb1..57683de6 100644 --- a/src/classes/OtaUpdater.py +++ b/src/classes/OtaUpdater.py @@ -85,7 +85,8 @@ def showWhatsNew(): md = requests.get(url) txt = md.text txt = txt.split("New?")[1] - txt = txt.split("## Downloads")[0] + # txt = txt.split("## Downloads")[0] + txt = txt.split("## Installation Guide")[0] txt = txt.replace('**','').replace('`','').strip() return (txt+"\n") @@ -100,6 +101,8 @@ def checkForUpdate(proxyServer, VERSION="1.0"): resp = requests.get("https://api.github.com/repos/pranjal-joshi/Screeni-py/releases/latest",proxies={'https':proxyServer}) else: resp = requests.get("https://api.github.com/repos/pranjal-joshi/Screeni-py/releases/latest") + # Disabling Exe check as Executables are deprecated v2.03 onwards + ''' if 'Windows' in platform.system(): OTAUpdater.checkForUpdate.url = resp.json()['assets'][1]['browser_download_url'] size = int(resp.json()['assets'][1]['size']/(1024*1024)) @@ -124,6 +127,9 @@ def checkForUpdate(proxyServer, VERSION="1.0"): except Exception as e: print(colorText.BOLD + colorText.WARN + '[+] Error occured while updating!' + colorText.END) raise(e) + ''' + if(float(resp.json()['tag_name']) > now and not isDocker()): + print(colorText.BOLD + colorText.FAIL + "[+] Executables are now DEPRECATED!\nFollow instructions given at https://github.com/pranjal-joshi/Screeni-py to switch to Docker.\n" + colorText.END) elif(float(resp.json()['tag_name']) > now and isDocker()): # OTA not applicable if we're running in docker! print(colorText.BOLD + colorText.FAIL + ('\n[+] New Software update (v%s) available.\n[+] Run `docker pull joshipranjal/screeni-py:latest` to update your docker to the latest version!\n' % (str(resp.json()['tag_name']))) + colorText.END) print(colorText.BOLD + colorText.WARN + "[+] What's New in this Update?\n" + OTAUpdater.showWhatsNew() + colorText.END) diff --git a/src/classes/ParallelProcessing.py b/src/classes/ParallelProcessing.py index 54a94a26..6fdf7459 100644 --- a/src/classes/ParallelProcessing.py +++ b/src/classes/ParallelProcessing.py @@ -59,7 +59,7 @@ def run(self): sys.exit(0) def screenStocks(self, executeOption, reversalOption, maLength, daysForLowestVolume, minRSI, maxRSI, respChartPattern, insideBarToLookback, totalSymbols, - configManager, fetcher, screener, candlePatterns, stock, newlyListedOnly, downloadOnly, printCounter=False): + configManager, fetcher, screener, candlePatterns, stock, newlyListedOnly, downloadOnly, vectorSearch, printCounter=False): screenResults = pd.DataFrame(columns=[ 'Stock', 'Consolidating', 'Breaking-Out', 'MA-Signal', 'Volume', 'LTP', 'RSI', 'Trend', 'Pattern']) screeningDictionary = {'Stock': "", 'Consolidating': "", 'Breaking-Out': "", @@ -105,6 +105,11 @@ def screenStocks(self, executeOption, reversalOption, maLength, daysForLowestVol 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): diff --git a/src/classes/Screener.py b/src/classes/Screener.py index 7dd242c3..ecc3830e 100644 --- a/src/classes/Screener.py +++ b/src/classes/Screener.py @@ -9,16 +9,22 @@ import math import numpy as np import pandas as pd -# import talib import joblib import keras +import time import classes.Utility as Utility +from classes.Utility import isGui from sklearn.preprocessing import StandardScaler from scipy.signal import argrelextrema from scipy.stats import linregress from classes.ColorText import colorText from classes.SuppressOutput import SuppressOutput from classes.ScreenipyTA import ScreenerTA +try: + import chromadb + CHROMA_AVAILABLE = True +except: + CHROMA_AVAILABLE = False # Exception for newly listed stocks with candle nos < daysToLookback @@ -592,9 +598,11 @@ def getNiftyPrediction(self, data, proxyServer): out = colorText.BOLD + colorText.GREEN + "BULLISH" + colorText.END + colorText.BOLD sug = "Stay Bullish!" if not Utility.tools.isClosingHour(): - print(colorText.BOLD + colorText.WARN + "Note: The AI prediction should be executed After 3 PM or Near to Closing time as the Prediction Accuracy is based on the Closing price!" + colorText.END) + print(colorText.BOLD + colorText.WARN + "Note: The AI prediction should be executed After 3 PM Around the Closing hours as the Prediction Accuracy is based on the Closing price!" + colorText.END) print(colorText.BOLD + colorText.BLUE + "\n" + "[+] Nifty AI Prediction -> " + colorText.END + colorText.BOLD + "Market may Open {} next day! {}".format(out, sug) + colorText.END) print(colorText.BOLD + colorText.BLUE + "\n" + "[+] Nifty AI Prediction -> " + colorText.END + "Probability/Strength of Prediction = {}%".format(Utility.tools.getSigmoidConfidence(pred[0]))) + if isGui(): + return pred, 'BULLISH' if pred <= 0.5 else 'BEARISH', Utility.tools.getSigmoidConfidence(pred[0]) return pred def monitorFiveEma(self, proxyServer, fetcher, result_df, last_signal, risk_reward = 3): @@ -668,7 +676,27 @@ def monitorFiveEma(self, proxyServer, fetcher, result_df, last_signal, risk_rewa result_df.drop_duplicates(keep='last', inplace=True) result_df.sort_values(by='Time', inplace=True) return result_df[::-1] - + + # Add data to vector database + def addVector(self, data, stockCode, daysToLookback): + data = data[::-1] # Reinverting preprocessedData for pct_change + data = data.pct_change() + # data = data[::-1] # Do we need to invert again? No we dont - See operation after flatten + data = data[['Open', 'High', 'Low', 'Close']] + data = data.reset_index(drop=True) + data = data.dropna() + data = data.to_numpy().flatten().tolist() + data = data[(-4 * daysToLookback):] # Keep only OHLC * daysToLookback samples + if len(data) == (4 * daysToLookback): + chroma_client = chromadb.PersistentClient(path="./chromadb_store/") + collection = chroma_client.get_or_create_collection(name="nse_stocks") + collection.upsert( + embeddings=[data], + documents=[stockCode], + ids=[stockCode] + ) + return data + ''' # Find out trend for days to lookback diff --git a/src/classes/Utility.py b/src/classes/Utility.py index e037f910..42789412 100644 --- a/src/classes/Utility.py +++ b/src/classes/Utility.py @@ -286,6 +286,19 @@ def promptChartPatterns(): input(colorText.BOLD + colorText.FAIL + "\n[+] Invalid Option Selected. Press Any Key to Continue..." + colorText.END) return (None, None) + + # Prompt for Similar stock search + def promptSimilarStockSearch(): + try: + stockCode = str(input(colorText.BOLD + colorText.WARN + + "\n[+] Enter the Name of the stock to search similar stocks for: " + colorText.END)).upper() + candles = int(input(colorText.BOLD + colorText.WARN + + "\n[+] How many candles (TimeFrame) to look back for similarity? : " + colorText.END)) + return stockCode, candles + except ValueError: + input(colorText.BOLD + colorText.FAIL + + "\n[+] Invalid Option Selected. Press Any Key to Continue..." + colorText.END) + return None, None def getProgressbarStyle(): bar = 'smooth' diff --git a/src/release.md b/src/release.md index ef588070..8300651c 100644 --- a/src/release.md +++ b/src/release.md @@ -3,20 +3,22 @@ Celebrating more than 7K+ Downloads - Thank You for your support :tada: -đŸŗ **Docker containers are now released for quick setup and easy usage!** +đŸŗ **Docker containers are released for quick setup and easy usage!** -1. New Index - **F&O Stocks Only** Added for F&O traders -2. Trend detection fixed (#173) and % change added. +⚠ī¸ **Executable files (.exe, .bin and .run) are now DEPRECATED! Please Switch to Docker** + +1. **Search Similar Stocks** Added using Vector Similarity search - Try `Search Similar Stocks`. +2. New Index - **F&O Stocks Only** Added for F&O traders with modified screening criterias. 3. **Artificial Intelligence v2 for Nifty 50 Prediction** - Predict Next day Gap-up/down - Try `Select Index for Screening > N` -4. **Live Intraday Scanner - 5 EMA** - Try `Select Index for Screening > E` - Get live trade entries for Nifty and BankNifty with notifications! -5. New Screener **Buy at Trendline** added for Swing/Mid/Long term traders - Try `Option > 7 > 5`. -6. Alternate Data Source for faster After-Market Analysis - Optimizations and Cosmetic Updates! +4. New Screener **Buy at Trendline** added for Swing/Mid/Long term traders - Try `Option > 7 > 5`. +6. Alternate Data Source for faster After-Market Analysis - OTA Optimizations and Cosmetic Updates for Docker/GUI! ## Installation Guide [![Screeni-py - Detailed Installation Guide](https://markdown-videos-api.jorgenkh.no/url?url=https%3A%2F%2Fyoutu.be%2F2HMN0ac4H20)](https://youtu.be/2HMN0ac4H20) -## Downloads +## Downloads +### Deprycated - Use Docker Method mentioned in next section | Operating System | Executable File | | :-: | --- | | ![Windows](https://img.shields.io/badge/Windows-0078D6?style=for-the-badge&logo=windows&logoColor=white) | **[screenipy.exe](https://github.com/pranjal-joshi/Screeni-py/releases/download/2.02/screenipy.exe)** | @@ -32,7 +34,7 @@ Celebrating more than 7K+ Downloads - Thank You for your support :tada: | ![Docker](https://img.shields.io/badge/docker-%230db7ed.svg?style=for-the-badge&logo=docker&logoColor=white) | `dev` | `docker pull joshipranjal/screeni-py:dev` | Command Line | `docker run -it --entrypoint /bin/bash joshipranjal/screeni-py:dev -c "run_screenipy.sh --cli"` | | ![Docker](https://img.shields.io/badge/docker-%230db7ed.svg?style=for-the-badge&logo=docker&logoColor=white) | `dev` | `docker pull joshipranjal/screeni-py:dev` | GUI WebApp | `docker run -p 8501:8501 joshipranjal/screeni-py:dev` | -**Should I download the Exe/Bin file? Or Should I use Docker?** +**Why we shifted to Docker from the Good old EXEs?** | Executable/Binary File | Docker | | :-- | :-- | @@ -47,7 +49,8 @@ Celebrating more than 7K+ Downloads - Thank You for your support :tada: | Antivirus may block this as untrusted file ⚠ī¸ | No issues with Antivirus | | Need to download new file for every update | Updates quickly with minimal downloading | | No need of commands/technical knowledge | Very basic command execution skills may be required | -| Custom configuration works and persisted accross the runs/restarts | Config file resets at every run but your custom config can be uploaded in GUI mode | +| Custom configuration works and persists accross the runs/restarts | Config file resets at every run but your custom config can be uploaded in GUI mode | +| Incompatible with Vector Database ⚠ī¸ | Compatible with all Python libraries | ## How to use? diff --git a/src/screenipy.py b/src/screenipy.py index f5d37031..c15d105f 100644 --- a/src/screenipy.py +++ b/src/screenipy.py @@ -1,7 +1,7 @@ #!/usr/bin/python3 -# Pyinstaller compile Windows: pyinstaller --onefile --icon=src\icon.ico src\screenipy.py --hidden-import cmath --hidden-import talib.stream --hidden-import numpy --hidden-import pandas --hidden-import alive-progress -# Pyinstaller compile Linux : pyinstaller --onefile --icon=src/icon.ico src/screenipy.py --hidden-import cmath --hidden-import talib.stream --hidden-import numpy --hidden-import pandas --hidden-import alive-progress +# Pyinstaller compile Windows: pyinstaller --onefile --icon=src\icon.ico src\screenipy.py --hidden-import cmath --hidden-import talib.stream --hidden-import numpy --hidden-import pandas --hidden-import alive-progress --hidden-import chromadb +# Pyinstaller compile Linux : pyinstaller --onefile --icon=src/icon.ico src/screenipy.py --hidden-import cmath --hidden-import talib.stream --hidden-import numpy --hidden-import pandas --hidden-import alive-progress --hidden-import chromadb # Keep module imports prior to classes import os @@ -28,6 +28,11 @@ from tabulate import tabulate import multiprocessing multiprocessing.freeze_support() +try: + import chromadb + CHROMA_AVAILABLE = True +except: + CHROMA_AVAILABLE = False # Argument Parsing for test purpose argParser = argparse.ArgumentParser() @@ -51,6 +56,9 @@ loadCount = 0 maLength = None newlyListedOnly = False +vectorSearch = False + +CHROMADB_PATH = "chromadb_store/" configManager = ConfigManager.tools() fetcher = Fetcher.tools(configManager) @@ -63,6 +71,15 @@ except KeyError: proxyServer = "" +# Clear chromadb store initially +if CHROMA_AVAILABLE: + chroma_client = chromadb.PersistentClient(path=CHROMADB_PATH) + try: + chroma_client.delete_collection("nse_stocks") + except: + pass + + # Manage Execution flow @@ -74,6 +91,7 @@ def initExecution(): 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 @@ -108,7 +126,7 @@ def initExecution(): Utility.tools.clearScreen() return initExecution() - if tickerOption == 'N' or tickerOption == 'E': + if tickerOption == 'N' or tickerOption == 'E' or tickerOption == 'S': return tickerOption, 0 if tickerOption and tickerOption != 'W': @@ -153,7 +171,7 @@ def initExecution(): # Main function def main(testing=False, testBuild=False, downloadOnly=False, execute_inputs:list = []): - global screenCounter, screenResultsCounter, stockDict, loadedStockData, keyboardInterruptEvent, loadCount, maLength, newlyListedOnly + global screenCounter, screenResultsCounter, stockDict, loadedStockData, keyboardInterruptEvent, loadCount, maLength, newlyListedOnly, vectorSearch screenCounter = multiprocessing.Value('i', 1) screenResultsCounter = multiprocessing.Value('i', 0) keyboardInterruptEvent = multiprocessing.Manager().Event() @@ -184,7 +202,10 @@ def main(testing=False, testBuild=False, downloadOnly=False, execute_inputs:list if execute_inputs != []: if not configManager.checkConfigFile(): configManager.setConfig(ConfigManager.parser, default=True, showFileCreatedText=False) - tickerOption, executeOption = int(execute_inputs[0]), int(execute_inputs[1]) + try: + tickerOption, executeOption = int(execute_inputs[0]), int(execute_inputs[1]) + except: + tickerOption, executeOption = str(execute_inputs[0]), int(execute_inputs[1]) if tickerOption == 13: newlyListedOnly = True tickerOption = 12 @@ -268,7 +289,7 @@ def main(testing=False, testBuild=False, downloadOnly=False, execute_inputs:list "[+] Press any key to Exit!" + colorText.END) sys.exit(0) - if tickerOption == 'W' or tickerOption == 'N' or tickerOption == 'E' or (tickerOption >= 0 and tickerOption < 15): + if tickerOption == 'W' or tickerOption == 'N' or tickerOption == 'E' or tickerOption == 'S' or (tickerOption >= 0 and tickerOption < 15): configManager.getConfig(ConfigManager.parser) try: if tickerOption == 'W': @@ -316,6 +337,18 @@ def main(testing=False, testBuild=False, downloadOnly=False, execute_inputs:list 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: # Override config for F&O Stocks configManager.stageTwo = False @@ -338,7 +371,7 @@ def main(testing=False, testBuild=False, downloadOnly=False, execute_inputs:list "[+] Starting Stock Screening.. Press Ctrl+C to stop!\n") items = [(executeOption, reversalOption, maLength, daysForLowestVolume, minRSI, maxRSI, respChartPattern, insideBarToLookback, len(listStockCodes), - configManager, fetcher, screener, candlePatterns, stock, newlyListedOnly, downloadOnly) + configManager, fetcher, screener, candlePatterns, stock, newlyListedOnly, downloadOnly, vectorSearch) for stock in listStockCodes] tasks_queue = multiprocessing.JoinableQueue() @@ -416,6 +449,25 @@ def main(testing=False, testBuild=False, downloadOnly=False, execute_inputs:list 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 = matchedScreenResults.append(screenResults[screenResults['Stock'].str.contains(stk)]) + matchedSaveResults = matchedSaveResults.append(saveResults[saveResults['Stock'].str.contains(stk)]) + 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) @@ -456,6 +508,7 @@ def main(testing=False, testBuild=False, downloadOnly=False, execute_inputs:list if not isGui(): input('') newlyListedOnly = False + vectorSearch = False if __name__ == "__main__": @@ -474,7 +527,7 @@ def main(testing=False, testBuild=False, downloadOnly=False, execute_inputs:list while True: main() except Exception as e: - # raise e + raise e if isDevVersion == OTAUpdater.developmentVersion: raise(e) input(colorText.BOLD + colorText.FAIL + diff --git a/src/streamlit_app.py b/src/streamlit_app.py index 8ba42d07..c311b571 100644 --- a/src/streamlit_app.py +++ b/src/streamlit_app.py @@ -2,8 +2,6 @@ import streamlit.components.v1 as components import requests import os -import sys -import subprocess import configparser import urllib from time import sleep @@ -31,6 +29,23 @@ execute_inputs = [] +def show_df_as_result_table(): + try: + df = pd.read_pickle('last_screened_unformatted_results.pkl') + st.markdown(f'#### 🔍 Found {len(df)} Results') + df.index = df.index.map(lambda x: "https://in.tradingview.com/chart?symbol=NSE%3A" + x) + df.index = df.index.map(lambda x: f'{x.split("%3A")[-1]}') + df['Stock'] = df.index + stock_column = df.pop('Stock') # Remove 'Age' column and store it separately + df.insert(0, 'Stock', stock_column) + st.write(df.to_html(escape=False, index=False, index_names=False), unsafe_allow_html=True) + st.write(' ') + except FileNotFoundError: + st.error('Last Screened results are not available at the moment') + except Exception as e: + st.error('No Dataframe found for last_screened_results.pkl') + st.exception(e) + def on_config_change(): configManager = ConfigManager.tools() configManager.period = period @@ -47,13 +62,51 @@ def on_config_change(): def on_start_button_click(): global execute_inputs - st.info(f'Received inputs (Debug only): {execute_inputs}') + if isDevVersion != None: + st.info(f'Received inputs (Debug only): {execute_inputs}') with st.spinner('Screening stocks for you...'): - # with patch('builtins.input', side_effect=execute_inputs): try: screenipy_main(execute_inputs=execute_inputs) except StopIteration: pass + except requests.exceptions.RequestException as e: + st.error('Failed to reach Screeni-py server!', icon='đŸĢ¤') + st.info('This issue is related with your Internet Service Provider (ISP) - Many **Jio** users faced this issue as the screeni-py data cache server appeared to be not reachable for them!\n\nPlease go through this thread carefully to resolve this error: https://github.com/pranjal-joshi/Screeni-py/issues/164', icon='ℹī¸') + st.exception(e) + +def nifty_predict(col): + with col.container(): + with st.spinner('🔮 Taking a Look into the Future, Please wait...'): + import classes.Fetcher as Fetcher + import classes.Screener as Screener + configManager = ConfigManager.tools() + fetcher = Fetcher.tools(configManager) + screener = Screener.tools(configManager) + os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' + prediction, trend, confidence = screener.getNiftyPrediction( + data=fetcher.fetchLatestNiftyDaily(proxyServer=proxyServer), + proxyServer=proxyServer + ) + if 'BULLISH' in trend: + col.success(f'Market may Open **Gap Up** next day!\n\nProbability/Strength of Prediction = {confidence}%', icon='📈') + elif 'BEARISH' in trend: + col.error(f'Market may Open **Gap Down** next day!\n\nProbability/Strength of Prediction = {confidence}%', icon='📉') + else: + col.info("Couldn't determine the Trend. Try again later!") + col.warning('The AI prediction should be executed After 3 PM or Around the Closing hours as the Prediction Accuracy is based on the Closing price!\n\nThis is Just a Statistical Prediction and There are Chances of **False** Predictions!', icon='⚠ī¸') + +def find_similar_stocks(stockCode:str, candles:int): + global execute_inputs + stockCode = stockCode.upper() + if ',' in stockCode or ' ' in stockCode or stockCode == '': + st.error('Invalid Character in Stock Name!', icon='😾') + return False + else: + execute_inputs = ['S', 0, stockCode, candles, 'N'] + on_start_button_click() + st.toast('Screening Completed!', icon='🎉') + sleep(2) + return True def get_extra_inputs(tickerOption, executeOption, c_index=None, c_criteria=None, start_button=None): global execute_inputs @@ -135,7 +188,7 @@ def get_extra_inputs(tickerOption, executeOption, c_index=None, c_criteria=None, bc.divider() bc.image(telegram_url, width=96) -tab_screen, tab_config, tab_about = st.tabs(['Screen Stocks', 'Configuration', 'About']) +tab_screen, tab_similar, tab_nifty, tab_config, tab_about = st.tabs(['Screen Stocks', 'Search Similar Stocks', 'Nifty-50 Gap Prediction', 'Configuration', 'About']) with tab_screen: st.markdown(""" @@ -265,28 +318,15 @@ def get_extra_inputs(tickerOption, executeOption, c_index=None, c_criteria=None, sleep(2) with st.container(): - try: - df = pd.read_pickle('last_screened_unformatted_results.pkl') - st.markdown(f'#### Found {len(df)} Results') - df.index = df.index.map(lambda x: "https://in.tradingview.com/chart?symbol=NSE%3A" + x) - df.index = df.index.map(lambda x: f'{x.split("%3A")[-1]}') - df['Stock'] = df.index - stock_column = df.pop('Stock') # Remove 'Age' column and store it separately - df.insert(0, 'Stock', stock_column) - st.write(df.to_html(escape=False, index=False, index_names=False), unsafe_allow_html=True) - st.write(' ') - except FileNotFoundError: - st.error('Last Screened results are not available at the moment') - except Exception as e: - st.error('No Dataframe found for last_screened_results.pkl') - st.exception(e) + show_df_as_result_table() + with tab_config: configManager = ConfigManager.tools() configManager.getConfig(parser=ConfigManager.parser) ac, bc = st.columns([10,2]) - ac.markdown('### Screening Configuration') + ac.markdown('### 🔧 Screening Configuration') bc.download_button( label="Export Configuration", data=Path('screenipy.ini').read_text(), @@ -327,6 +367,27 @@ def get_extra_inputs(tickerOption, executeOption, c_index=None, c_criteria=None, f.write(bytes_data) st.toast('Configuration Imported', icon='⚙ī¸') +with tab_nifty: + ac, bc = st.columns([9,1]) + + ac.subheader('🧠 AI-based prediction for Next Day Nifty-50 Gap Up / Gap Down') + bc.button('**Predict**', type='primary', on_click=nifty_predict, args=(ac,), use_container_width=True) + +with tab_similar: + + st.subheader('đŸ•ĩđŸģ Find Stocks forming Similar Chart Patterns') + ac, bc, cc = st.columns([4,2,1]) + + stockCode = ac.text_input('Enter Stock Name and Press Enter', placeholder='HDFCBANK') + candles = bc.number_input('Lookback Period (No. of Candles)', min_value=1, step=1, value=int(configManager.daysToLookback)) + similar_search_button = cc.button('**Search**', type='primary', use_container_width=True) + + if similar_search_button: + result = find_similar_stocks(stockCode, candles) + if result: + with st.container(): + show_df_as_result_table() + with tab_about: from classes.Changelog import VERSION, changelog diff --git a/test/screenipy_test.py b/test/screenipy_test.py index 0232cfe2..10fba560 100644 --- a/test/screenipy_test.py +++ b/test/screenipy_test.py @@ -162,28 +162,28 @@ def test_option_14(mocker): pass -def test_ota_updater(): - try: - OTAUpdater.checkForUpdate(proxyServer, VERSION) - assert ( - "exe" in OTAUpdater.checkForUpdate.url or "bin" in OTAUpdater.checkForUpdate.url) - except StopIteration: - pass - - -def test_release_readme_urls(): - global last_release - f = open('../src/release.md', 'r', encoding='utf-8') - contents = f.read() - f.close() - failUrl = [f"https://github.com/pranjal-joshi/Screeni-py/releases/download/{last_release}/screenipy.bin", - f"https://github.com/pranjal-joshi/Screeni-py/releases/download/{last_release}/screenipy.exe"] - passUrl = [f"https://github.com/pranjal-joshi/Screeni-py/releases/download/{VERSION}/screenipy.bin", - f"https://github.com/pranjal-joshi/Screeni-py/releases/download/{VERSION}/screenipy.exe"] - for url in failUrl: - assert not url in contents - for url in passUrl: - assert url in contents +# def test_ota_updater(): +# try: +# OTAUpdater.checkForUpdate(proxyServer, VERSION) +# assert ( +# "exe" in OTAUpdater.checkForUpdate.url or "bin" in OTAUpdater.checkForUpdate.url) +# except StopIteration: +# pass + + +# def test_release_readme_urls(): +# global last_release +# f = open('../src/release.md', 'r', encoding='utf-8') +# contents = f.read() +# f.close() +# failUrl = [f"https://github.com/pranjal-joshi/Screeni-py/releases/download/{last_release}/screenipy.bin", +# f"https://github.com/pranjal-joshi/Screeni-py/releases/download/{last_release}/screenipy.exe"] +# passUrl = [f"https://github.com/pranjal-joshi/Screeni-py/releases/download/{VERSION}/screenipy.bin", +# f"https://github.com/pranjal-joshi/Screeni-py/releases/download/{VERSION}/screenipy.exe"] +# for url in failUrl: +# assert not url in contents +# for url in passUrl: +# assert url in contents def test_if_changelog_version_changed():