├── .dockerignore ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── dependencies │ ├── TA_Lib-0.4.19-cp39-cp39-win_amd64.whl │ ├── sqlite3.dll │ └── ta-lib-0.4.0-src.tar.gz └── workflows │ ├── squash.py │ ├── workflow-build-matrix.yml │ ├── workflow-download-data.yml │ ├── workflow-stale.yml │ └── workflow-test.yml ├── .gitignore ├── CONTRIBUTING.md ├── Dockerfile ├── INSTALLATION.md ├── LICENSE ├── LICENSE_Third_Party ├── Makefile ├── README.md ├── actions-data-download └── stock_data_140823.pkl ├── patterns ├── IPO1.png ├── IPO2.png ├── IPO3.png └── MomentumGainer.png ├── requirements.txt ├── run_screenipy.sh ├── screenshots ├── NSE_Logo.svg ├── config.png ├── done.png ├── home.png ├── results.png ├── screening.png └── screenipy_demo.gif ├── src ├── .streamlit │ └── config.toml ├── classes │ ├── CandlePatterns.py │ ├── Changelog.py │ ├── ColorText.py │ ├── ConfigManager.py │ ├── Fetcher.py │ ├── OtaUpdater.py │ ├── ParallelProcessing.py │ ├── Screener.py │ ├── ScreenipyTA.py │ ├── SuppressOutput.py │ └── Utility.py ├── icon-old.ico ├── icon.ico ├── ml │ ├── best_model_0.7438acc.h5 │ ├── eval.py │ ├── experiment.ipynb │ ├── nifty_model_v2.h5 │ ├── nifty_model_v2.pkl │ ├── nifty_model_v3.h5 │ └── nifty_model_v3.pkl ├── release.md ├── screenipy.ini ├── screenipy.py ├── screenipy.spec ├── static │ └── tablefilter │ │ ├── style │ │ ├── colsVisibility.css │ │ ├── filtersVisibility.css │ │ ├── tablefilter.css │ │ └── themes │ │ │ ├── blank.png │ │ │ ├── btn_clear_filters.png │ │ │ ├── btn_filter.png │ │ │ ├── btn_first_page.gif │ │ │ ├── btn_last_page.gif │ │ │ ├── btn_next_page.gif │ │ │ ├── btn_previous_page.gif │ │ │ ├── default │ │ │ ├── default.css │ │ │ └── images │ │ │ │ ├── bg_infDiv.jpg │ │ │ │ ├── bg_th.jpg │ │ │ │ ├── btn_eraser.gif │ │ │ │ ├── btn_first_page.gif │ │ │ │ ├── btn_last_page.gif │ │ │ │ ├── btn_next_page.gif │ │ │ │ ├── btn_over_eraser.gif │ │ │ │ ├── btn_over_first_page.gif │ │ │ │ ├── btn_over_last_page.gif │ │ │ │ ├── btn_over_next_page.gif │ │ │ │ ├── btn_over_previous_page.gif │ │ │ │ ├── btn_previous_page.gif │ │ │ │ └── img_loading.gif │ │ │ ├── downsimple.png │ │ │ ├── icn_clp.png │ │ │ ├── icn_exp.png │ │ │ ├── icn_filter.gif │ │ │ ├── icn_filterActive.gif │ │ │ ├── mytheme │ │ │ ├── images │ │ │ │ ├── bg_headers.jpg │ │ │ │ ├── bg_infDiv.jpg │ │ │ │ ├── btn_filter.png │ │ │ │ ├── btn_first_page.gif │ │ │ │ ├── btn_last_page.gif │ │ │ │ ├── btn_next_page.gif │ │ │ │ ├── btn_previous_page.gif │ │ │ │ └── img_loading.gif │ │ │ └── mytheme.css │ │ │ ├── skyblue │ │ │ ├── images │ │ │ │ ├── bg_skyblue.gif │ │ │ │ ├── btn_first_page.gif │ │ │ │ ├── btn_last_page.gif │ │ │ │ ├── btn_next_page.gif │ │ │ │ ├── btn_prev_page.gif │ │ │ │ ├── icn_clear_filters.png │ │ │ │ └── img_loading.gif │ │ │ └── skyblue.css │ │ │ ├── transparent │ │ │ ├── images │ │ │ │ ├── btn_first_page.gif │ │ │ │ ├── btn_last_page.gif │ │ │ │ ├── btn_next_page.gif │ │ │ │ ├── btn_prev_page.gif │ │ │ │ ├── icn_clear_filters.png │ │ │ │ └── img_loading.gif │ │ │ └── transparent.css │ │ │ └── upsimple.png │ │ ├── tablefilter.js │ │ └── tf-1-2aa33b10e0e549020c12.js └── streamlit_app.py └── test └── screenipy_test.py /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | .github 3 | actions-data-download 4 | patterns 5 | screenshots 6 | test 7 | .dockerignore 8 | .gitignore 9 | **/*.md 10 | **/*.spec 11 | Dockerfile 12 | Makefile 13 | *.code-workspace -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/dependencies/TA_Lib-0.4.19-cp39-cp39-win_amd64.whl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/.github/dependencies/TA_Lib-0.4.19-cp39-cp39-win_amd64.whl -------------------------------------------------------------------------------- /.github/dependencies/sqlite3.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/.github/dependencies/sqlite3.dll -------------------------------------------------------------------------------- /.github/dependencies/ta-lib-0.4.0-src.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/.github/dependencies/ta-lib-0.4.0-src.tar.gz -------------------------------------------------------------------------------- /.github/workflows/squash.py: -------------------------------------------------------------------------------- 1 | from time import sleep 2 | import os 3 | 4 | try: 5 | c_msg = "GitHub Action Workflow - Market Data Download (Default Config)" 6 | 7 | print("[+] === SQUASHING COMMITS : actions-data-download branch ===") 8 | print("[+] Saving Commit messages log..") 9 | os.system("git log --pretty=oneline > msg.log") 10 | 11 | sleep(5) 12 | 13 | lines = None 14 | with open('msg.log','r') as f: 15 | lines = f.readlines() 16 | 17 | cnt = 0 18 | for l in lines: 19 | if c_msg in l: 20 | cnt += 1 21 | else: 22 | commit_hash = l.split(" ")[0] 23 | cnt -= 1 24 | break 25 | 26 | 27 | print(f"[+] Reset at HEAD~{cnt}") 28 | print(f"[+] Reset hash = {commit_hash}") 29 | print(f"git reset --soft {commit_hash}") 30 | print(f"git commit -m '{c_msg}'") 31 | 32 | if cnt < 1: 33 | print("[+] No Need to Squash! Skipping...") 34 | else: 35 | os.system(f"git reset --soft HEAD~{cnt}") 36 | os.system(f"git commit -m '{c_msg}'") 37 | os.system(f"git push -f") 38 | 39 | os.remove("msg.log") 40 | sleep(5) 41 | 42 | print("[+] === SQUASHING COMMITS : DONE ===") 43 | 44 | except Exception as e: 45 | print(f"Error while squashing commits - Skipping this step!\n{e}") -------------------------------------------------------------------------------- /.github/workflows/workflow-build-matrix.yml: -------------------------------------------------------------------------------- 1 | # Project : Screenipy 2 | # Author : Pranjal Joshi 3 | # Created : 30/04/2021 4 | # Description : Workflow for building screenipy on pushing a tag 5 | 6 | name: Screenipy Build - New Release 7 | 8 | on: 9 | push: 10 | #branches: [ pre-main ] 11 | tags: 12 | - '*' 13 | 14 | jobs: 15 | 16 | # Job for builing packages 17 | Build-Executables: 18 | name: Build Packages 19 | runs-on: ubuntu-latest 20 | needs: Docker-Build 21 | # runs-on: ${{ matrix.os }} 22 | # strategy: 23 | # matrix: 24 | # include: 25 | # - os: windows-latest 26 | # TARGET: Windows 27 | # CMD_BUILD: | 28 | # 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 29 | # DEP_BUILD: | 30 | # python -m pip install --upgrade pip 31 | # echo Installing TA-lib... 32 | # cd .github/dependencies/ 33 | # echo %cd% 34 | # pip install TA_Lib-0.4.19-cp39-cp39-win_amd64.whl 35 | # cd .. 36 | # cd .. 37 | # python -m pip install --upgrade pip 38 | # pip install -r requirements.txt 39 | # TEST_BUILD: | 40 | # ./dist/screenipy.exe --testbuild 41 | # exit $? 42 | # OUT_PATH: .\dist\screenipy.exe 43 | # FILE_NAME: screenipy.exe 44 | 45 | # - os: ubuntu-20.04 46 | # TARGET: Linux 47 | # CMD_BUILD: | 48 | # 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 49 | # mv /home/runner/work/Screeni-py/Screeni-py/dist/screenipy /home/runner/work/Screeni-py/Screeni-py/dist/screenipy.bin 50 | # chmod +x /home/runner/work/Screeni-py/Screeni-py/dist/screenipy.bin 51 | # DEP_BUILD: | 52 | # cd .github/dependencies/ 53 | # pwd 54 | # tar -xzf ta-lib-0.4.0-src.tar.gz 55 | # cd ta-lib/ 56 | # ./configure --prefix=/usr 57 | # make 58 | # sudo make install 59 | # cd /home/runner/work/Screeni-py/Screeni-py/ 60 | # python -m pip install --upgrade pip 61 | # pip install -r requirements.txt 62 | # pip install ta-lib==0.4.24 63 | # TEST_BUILD: | 64 | # /home/runner/work/Screeni-py/Screeni-py/dist/screenipy.bin --testbuild 65 | # exit $? 66 | # OUT_PATH: /home/runner/work/Screeni-py/Screeni-py/dist/screenipy.bin 67 | # FILE_NAME: screenipy.bin 68 | 69 | # - os: macos-latest 70 | # TARGET: MacOS 71 | # CMD_BUILD: | 72 | # 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 73 | # mv /Users/runner/work/Screeni-py/Screeni-py/dist/screenipy /Users/runner/work/Screeni-py/Screeni-py/dist/screenipy.run 74 | # DEP_BUILD: | 75 | # brew install ta-lib 76 | # python -m pip install --upgrade pip 77 | # pip install -r requirements.txt 78 | # pip install ta-lib==0.4.24 79 | # TEST_BUILD: | 80 | # /Users/runner/work/Screeni-py/Screeni-py/dist/screenipy.run --testbuild 81 | # exit $? 82 | # OUT_PATH: /Users/runner/work/Screeni-py/Screeni-py/dist/screenipy.run 83 | # FILE_NAME: screenipy.run 84 | 85 | steps: 86 | - uses: actions/checkout@v4 87 | 88 | - name: Get the GitHub Tag version 89 | id: get_version 90 | shell: bash 91 | run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//} 92 | 93 | # - name: Set up Python 3.9.4 94 | # uses: actions/setup-python@v2 95 | # with: 96 | # python-version: 3.9.4 97 | 98 | # - name: Load Cache for Linux Dependencies 99 | # uses: actions/cache@v2 100 | # if: startsWith(runner.os, 'Linux') 101 | # with: 102 | # path: | 103 | # /usr/include/ta-lib 104 | # /usr/bin/ta-lib-config 105 | # key: ${{ runner.os }}-talib 106 | # restore-keys: | 107 | # ${{ runner.os }}-talib 108 | 109 | # - name: Install dependencies for ${{ matrix.TARGET }} 110 | # run: ${{ matrix.DEP_BUILD }} 111 | 112 | # - name: Build for ${{ matrix.TARGET }} 113 | # run: ${{ matrix.CMD_BUILD }} 114 | 115 | # - name: Test Built Binary for ${{ matrix.TARGET }} 116 | # shell: bash 117 | # run: ${{ matrix.TEST_BUILD }} 118 | # continue-on-error: false 119 | 120 | # - name: Save Binaries as Artifacts 121 | # uses: actions/upload-artifact@v2 122 | # with: 123 | # name: ${{ matrix.FILE_NAME }} 124 | # path: ${{ matrix.OUT_PATH }} 125 | 126 | - name: Read release.md 127 | id: read_release 128 | shell: bash 129 | run: | 130 | r=$(cat src/release.md) 131 | r="${r//'%'/'%25'}" 132 | r="${r//$'\n'/'%0A'}" 133 | r="${r//$'\r'/'%0D'}" 134 | echo "::set-output name=RELEASE_BODY::$r" 135 | 136 | - name: Upload Binaries to Release 137 | uses: svenstaro/upload-release-action@v2 138 | with: 139 | repo_token: ${{ secrets.GITHUB_TOKEN }} 140 | file: README.md 141 | asset_name: README.md 142 | tag: ${{ github.ref }} 143 | release_name: Screenipy - v${{ steps.get_version.outputs.VERSION }} 144 | body: | 145 | ${{ steps.read_release.outputs.RELEASE_BODY }} 146 | overwrite: true 147 | 148 | Docker-Build: 149 | name: Build and Release Docker Multi-Arch images 150 | runs-on: ubuntu-latest 151 | 152 | steps: 153 | - uses: actions/checkout@v4 154 | 155 | # Setup hardware emulator using QEMU 156 | - name: Set up QEMU 157 | uses: docker/setup-qemu-action@v3 158 | 159 | # Setup Docker Buildx for multi-arch images 160 | - name: Set up Docker Buildx 161 | uses: docker/setup-buildx-action@v3 162 | 163 | - name: Login to Docker Hub 164 | uses: docker/login-action@v3 165 | with: 166 | username: ${{ secrets.DOCKERHUB_USERNAME }} 167 | password: ${{ secrets.DOCKERHUB_TOKEN }} 168 | 169 | - name: Get tag version version 170 | id: version 171 | run: | 172 | VER=$(grep 'VERSION = ' src/classes/Changelog.py | awk -F'"' '{print $2}') 173 | echo "VERSION=$VER" >> $GITHUB_ENV 174 | 175 | - name: Streamlit Build and Push 176 | uses: docker/build-push-action@v4 177 | with: 178 | context: . 179 | platforms: linux/amd64, linux/arm64 180 | push: true 181 | tags: joshipranjal/screeni-py:latest, joshipranjal/screeni-py:${{ env.VERSION }} 182 | 183 | -------------------------------------------------------------------------------- /.github/workflows/workflow-download-data.yml: -------------------------------------------------------------------------------- 1 | # Project : Screenipy 2 | # Author : Pranjal Joshi 3 | # Created : 24/01/2022 4 | # Description : Workflow for downloading pickled stock data to cloud [Use this when yfinance causing an issue] 5 | 6 | name: Screenipy Data - After-Market Data Gen 7 | on: 8 | workflow_dispatch: 9 | inputs: 10 | name: 11 | description: 'Run Details' 12 | required: false 13 | default: 'Screenipy - Data Download' 14 | schedule: 15 | - cron: '02 10 * * 1-5' 16 | 17 | jobs: 18 | 19 | Download_Stock_Data: 20 | 21 | runs-on: ubuntu-latest 22 | 23 | steps: 24 | - uses: actions/checkout@v4 25 | with: 26 | ref: actions-data-download 27 | fetch-depth: 0 28 | 29 | - name: Set up Python 3.11.6 30 | uses: actions/setup-python@v4 31 | with: 32 | python-version: 3.11.6 33 | 34 | - name: Install Python Dependencies 35 | run: | 36 | python -m pip install --upgrade pip 37 | pip install -r requirements.txt 38 | 39 | - name: Download Stock Data 40 | run: | 41 | rm -rf actions-data-download 42 | mkdir -p actions-data-download 43 | python src/screenipy.py -d 44 | mv stock_data_*.pkl actions-data-download/ 45 | 46 | - name: Push Pickle Data 47 | run: | 48 | git config user.name github-actions 49 | git config user.email github-actions@github.com 50 | git checkout actions-data-download 51 | git add --all 52 | git diff --cached --quiet || git commit -m "GitHub Action Workflow - Market Data Download (Default Config)" 53 | git push 54 | 55 | - name: Squash Commits (Python) 56 | run: | 57 | git config user.name github-actions 58 | git config user.email github-actions@github.com 59 | git fetch 60 | git checkout actions-data-download 61 | python .github/workflows/squash.py 62 | -------------------------------------------------------------------------------- /.github/workflows/workflow-stale.yml: -------------------------------------------------------------------------------- 1 | # Project : Screenipy 2 | # Author : Pranjal Joshi 3 | # Created : 03/09/2021 4 | # Description : Workflow for marking Stale Issues/PRs 5 | 6 | name: Screenipy Stale - Mark stale issues and pull requests 7 | 8 | on: 9 | schedule: 10 | - cron: '30 11 * * *' 11 | 12 | jobs: 13 | stale: 14 | 15 | runs-on: ubuntu-latest 16 | permissions: 17 | issues: write 18 | pull-requests: write 19 | 20 | steps: 21 | - uses: actions/stale@v3 22 | with: 23 | repo-token: ${{ secrets.GITHUB_TOKEN }} 24 | stale-issue-message: 'This Issue is marked as Stale due to Inactivity. This Issue will be Closed soon.' 25 | stale-pr-message: 'This PR is marked as Stale due to Inactivity.' 26 | days-before-stale: 15 27 | days-before-close: 5 28 | days-before-pr-close: -1 29 | exempt-issue-labels: 'question,Feedback' 30 | -------------------------------------------------------------------------------- /.github/workflows/workflow-test.yml: -------------------------------------------------------------------------------- 1 | # Project : Screenipy 2 | # Author : Pranjal Joshi 3 | # Created : 27/04/2021 4 | # Description : Workflow for CI - Testing source and PR main for release 5 | 6 | 7 | name: Screenipy Test - New Features 8 | 9 | on: 10 | push: 11 | branches: [ new-features ] 12 | pull_request: 13 | branches: [ new-features ] 14 | 15 | jobs: 16 | 17 | # Test the source-code 18 | Test-Source: 19 | 20 | runs-on: ubuntu-latest 21 | 22 | steps: 23 | - uses: actions/checkout@v4 24 | 25 | - name: Set up Python 3.11.6 26 | uses: actions/setup-python@v4 27 | with: 28 | python-version: 3.11.6 29 | 30 | - name: Restore Dependencies from Cache 31 | uses: actions/cache@v4 32 | with: 33 | path: ~\AppData\Local\pip\Cache 34 | key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} 35 | restore-keys: | 36 | ${{ runner.os }}-pip- 37 | 38 | - name: Install dependencies using pip 39 | run: | 40 | python -m pip install --upgrade pip 41 | pip install flake8 pytest pytest-mock 42 | pip install -r requirements.txt 43 | # pip install --no-deps advanced-ta 44 | 45 | - name: Lint with flake8 46 | run: | 47 | # stop the build if there are Python syntax errors or undefined names 48 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 49 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 50 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 51 | 52 | - name: Run a Test with pytest 53 | run: | 54 | cd test/ 55 | pytest -v 56 | 57 | Dev-Docker-Build: 58 | 59 | runs-on: ubuntu-latest 60 | # needs: [Test-Source] 61 | 62 | steps: 63 | - uses: actions/checkout@v4 64 | with: 65 | ref: new-features 66 | 67 | # Setup hardware emulator using QEMU 68 | - name: Set up QEMU 69 | uses: docker/setup-qemu-action@v3 70 | 71 | # Setup Docker Buildx for multi-arch images 72 | - name: Set up Docker Buildx 73 | uses: docker/setup-buildx-action@v3 74 | 75 | - name: Login to Docker Hub 76 | uses: docker/login-action@v3 77 | with: 78 | username: ${{ secrets.DOCKERHUB_USERNAME }} 79 | password: ${{ secrets.DOCKERHUB_TOKEN }} 80 | 81 | - name: Streamlit Build and Push 82 | uses: docker/build-push-action@v4 83 | with: 84 | context: . 85 | platforms: linux/amd64, linux/arm64 86 | push: true 87 | tags: joshipranjal/screeni-py:dev 88 | 89 | # Job to create PR 90 | Create-Pull-Request: 91 | 92 | runs-on: ubuntu-latest 93 | needs: [Test-Source] 94 | 95 | steps: 96 | - name: Checkout Repo before PR 97 | uses: actions/checkout@v4 98 | with: 99 | ref: new-features 100 | 101 | - name: Create Pull Request (new-features -> main) 102 | id: create_pr 103 | uses: repo-sync/pull-request@v2 104 | with: 105 | source_branch: "new-features" 106 | destination_branch: "main" 107 | pr_title: "[Screenipy Test] New Features Added - Test Passed" 108 | pr_body: | 109 | **This PR has been generated automatically** by **Screenipy Test - New Features** workflow due to test passed for a latest push on the **new-features** branch. 110 | 111 | View commits for changelog. 112 | pr_label: "Test-Passed" 113 | pr_draft: false 114 | pr_allow_empty: true 115 | github_token: ${{ secrets.GITHUB_TOKEN }} 116 | 117 | - name: Create PR Log file 118 | shell: bash 119 | run: | 120 | echo ${{steps.create_pr.outputs.pr_url}} >> pr.txt 121 | echo ${{steps.create_pr.outputs.pr_number}} >> pr.txt 122 | 123 | - name: Save PR Log File 124 | uses: actions/upload-artifact@v4 125 | with: 126 | name: PR_Log.txt 127 | path: pr.txt -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | venv/ 2 | build/ 3 | dist/ 4 | test/*.ini 5 | __pycache__/ 6 | .vscode/ 7 | *.xlsx 8 | stock*.pkl 9 | results*.pkl 10 | *.spec 11 | .DS_Store 12 | .history/ 13 | *.code-workspace -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | When contributing to this repository, please first discuss the change you wish to make via issue with owner or other contributers. 4 | 5 | ## 1. Keep your Fork up to date 6 | * Before statrting development of any new feature, Always check if this repo is ahead in commits as compared to your fork. 7 | * It is a good practice to always keep your fork up-to-date before starting development of features/fixes to avoid merge conflicts. 8 | * Update your fork using following code snippet. 9 | ``` 10 | # Add a new remote repo called as screenipy_upstream 11 | git remote add screenipy_upstream https://github.com/pranjal-joshi/Screeni-py.git 12 | 13 | # Sync your fork before starting work 14 | git fetch screenipy_upstream 15 | git checkout 16 | git merge screenipy_upstream/ 17 | ``` 18 | 19 | 20 | ## 2. Install Project Dependencies 21 | 22 | * This project uses [**TA-Lib**](https://github.com/mrjbq7/ta-lib). Please visit the hyperlink for the official guide of installation. 23 | * This Project requires Python 3.9 environment setup. [Click Here to Download](https://www.python.org/downloads/) 24 | * Install python dependencies by running `pip install -r requirements.txt` in the root directory of this project. 25 | 26 | ## 3. Create Dependency Requirements 27 | 28 | 1. Install [**pip-chill**](https://pypi.org/project/pip-chill/) by running `pip install pip-chill` which is a developer friendly version of classic `pip freeze`. 29 | 2. Update the `requirements.txt` file by running `pip-chill --all --no-version -v > requirements.txt`. 30 | 3. Ensure to **uncomment** all the dependency modules from the `requirements.txt` 31 | 32 | ## 4. Testing Code Locally 33 | 34 | 1. Update the test-cases as per the new features from `test/screenipy_test.py` if required. 35 | 2. Run a test locally with `pytest -v` and ensure that all tests are passed. 36 | 3. In case of a failure, Rectify code or Consider opening an issue for further discussion. 37 | 38 | ## 5. Pull Request Process 39 | 40 | 1. Ensure that dependecy list have been generated in the `requirements.txt` using above section. 41 | 2. Ensure that all test-cases are passed locally. 42 | 1. If you are contributing new feature or a bug-fix, Always create a Pull Request to `new-features` branch as it have workflows to test the source before merging with the `main`. -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Project : Screenipy 2 | # Author : Pranjal Joshi 3 | # Created : 17/08/2023 4 | # Description : Dockerfile to build Screeni-py image for GUI release 5 | 6 | FROM python:3.11.6-slim-bookworm AS base 7 | 8 | ARG DEBIAN_FRONTEND=noninteractive 9 | 10 | RUN apt-get update && apt-get install -y --no-install-recommends \ 11 | git vim nano wget curl && \ 12 | apt-get clean && \ 13 | rm -rf /var/lib/apt/lists/* 14 | 15 | ENV LANG=C.UTF-8 \ 16 | PYTHONUNBUFFERED=TRUE \ 17 | PYTHONDONTWRITEBYTECODE=TRUE \ 18 | SCREENIPY_DOCKER=TRUE \ 19 | SCREENIPY_GUI=TRUE \ 20 | PATH=/opt/program:$PATH 21 | 22 | ############## 23 | # Build Phase 24 | ############## 25 | FROM base AS build 26 | 27 | ARG PIP_DISABLE_PIP_VERSION_CHECK=1 28 | ARG PIP_NO_CACHE_DIR=1 29 | 30 | WORKDIR /opt/program 31 | 32 | RUN python3 -m venv /venv 33 | ENV PATH=/venv/bin:$PATH 34 | 35 | COPY requirements.txt . 36 | 37 | RUN --mount=type=cache,target=/root/.cache/pip pip3 install -r requirements.txt 38 | RUN --mount=type=cache,target=/root/.cache/pip pip3 install --no-deps advanced-ta 39 | 40 | ############## 41 | # Package Phase 42 | ############## 43 | FROM base AS app 44 | 45 | COPY --from=build /venv /venv 46 | ENV PATH=/venv/bin:$PATH 47 | 48 | WORKDIR /opt/program 49 | 50 | COPY . . 51 | 52 | RUN chmod +x ./* 53 | 54 | EXPOSE 8000 55 | 56 | EXPOSE 8501 57 | 58 | HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health 59 | 60 | WORKDIR /opt/program/src 61 | 62 | ENTRYPOINT ["streamlit", "run", "streamlit_app.py", "--server.port=8501", "--server.address=0.0.0.0"] 63 | # ENTRYPOINT ["tail", "-f", "/dev/null"] 64 | -------------------------------------------------------------------------------- /INSTALLATION.md: -------------------------------------------------------------------------------- 1 | # Installation Guide 2 | 3 | This is a troubleshooting guide for installing [Screenipy](https://github.com/pranjal-joshi/Screeni-py) on your computer. 4 | 5 | ## For MacOS 6 | 7 | ### One Time Configuration 8 | 9 | 1. Download the executable from the [Latest Release](https://github.com/pranjal-joshi/Screeni-py/releases/latest) 10 | 2. Open `Terminal` from `Applications > Utility > Terminal` 11 | 3. Execute following commands in the terminal (Commands are **Case Sensitive**) 12 | ``` 13 | cd Downloads # Navigate to Downloads folder 14 | chmod +x screenipy.run # Apply Execute permission to the file 15 | ``` 16 | 17 | 4. Right click on 'screenipy.run' and select option `Open with > Utilities > Terminal`. (Select All applications if `Terminal` is frozen) 18 | 5. You may get **Developer not Verified** error as follow: 19 | 20 | ![Error](https://user-images.githubusercontent.com/6128978/119251001-95214580-bbc1-11eb-8484-e07ba33730dc.PNG) 21 | 22 | 6.Click on the **`?`** Icon. The following prompt will appear on the right bottom of your screen. 23 | 24 | ![Prompt](https://user-images.githubusercontent.com/6128978/119251025-c39f2080-bbc1-11eb-8103-9f0d267ff4e4.PNG) 25 | 26 | 7. Click on `Open General Pane for me` option. 27 | 8. This will open following **Security and Privacy** window. 28 | 9. Click on **`Open Anyway`** Button to grant executable permission for the Screenipy. (Enter your password if prompted) 29 | 30 | ![Allow](https://user-images.githubusercontent.com/6128978/119251073-11b42400-bbc2-11eb-9a15-7ebb6fec1c66.PNG) 31 | 32 | 10. Close the window. 33 | 11. Now double click on `screenipy.run` file to use the application. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Pranjal Joshi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSE_Third_Party: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2023 pkjmesra 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | The MIT License (MIT) 24 | 25 | Copyright (c) 2025 smitpsanghavi 26 | 27 | Permission is hereby granted, free of charge, to any person obtaining a copy 28 | of this software and associated documentation files (the "Software"), to deal 29 | in the Software without restriction, including without limitation the rights 30 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 31 | copies of the Software, and to permit persons to whom the Software is 32 | furnished to do so, subject to the following conditions: 33 | 34 | The above copyright notice and this permission notice shall be included in all 35 | copies or substantial portions of the Software. 36 | 37 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 38 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 39 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 40 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 41 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 42 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 43 | SOFTWARE. 44 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | docker build -t screeni-py . 3 | 4 | run: 5 | docker run -d --name screeni-py -p 8501:8501 screeni-py 6 | 7 | interactive-run: 8 | docker run -p 8501:8501 screeni-py 9 | 10 | shell: 11 | docker run -it --entrypoint /bin/bash screeni-py 12 | 13 | stop-container: 14 | @if [ "$(shell docker ps -q -f name=screeni-py)" ]; then \ 15 | docker stop screeni-py; \ 16 | else \ 17 | echo "Container screeni-py is not running."; \ 18 | fi 19 | 20 | remove-container: 21 | @if [ "$(shell docker ps -a -q -f name=screeni-py)" ]; then \ 22 | docker rm screeni-py; \ 23 | else \ 24 | echo "Container screeni-py does not exist."; \ 25 | fi 26 | 27 | system-clean: 28 | docker system prune --force 29 | 30 | rebuild: stop-container remove-container build system-clean 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | | | 2 | | :-: | 3 | | ![Screeni-py](https://user-images.githubusercontent.com/6128978/217816268-74c40180-fc47-434d-938b-3639898ee3e0.png) | 4 | | [![GitHub release (latest by date)](https://img.shields.io/github/v/release/pranjal-joshi/Screeni-py?style=for-the-badge)](https://github.com/pranjal-joshi/Screeni-py/releases/latest) [![GitHub all releases](https://img.shields.io/github/downloads/pranjal-joshi/Screeni-py/total?color=Green&label=Downloads&style=for-the-badge)](#) ![Docker Pulls](https://img.shields.io/docker/pulls/joshipranjal/screeni-py?style=for-the-badge&logo=docker) [![GitHub](https://img.shields.io/github/license/pranjal-joshi/Screeni-py?style=for-the-badge)](https://github.com/pranjal-joshi/Screeni-py/blob/main/LICENSE) [![CodeFactor](https://www.codefactor.io/repository/github/pranjal-joshi/screeni-py/badge?style=for-the-badge)](https://www.codefactor.io/repository/github/pranjal-joshi/screeni-py) [![MADE-IN-INDIA](https://img.shields.io/badge/MADE%20WITH%20%E2%9D%A4%20IN-INDIA-orange?style=for-the-badge)](https://en.wikipedia.org/wiki/India) [![BADGE](https://img.shields.io/badge/PULL%20REQUEST-GUIDELINES-red?style=for-the-badge)](https://github.com/pranjal-joshi/Screeni-py/blob/new-features/CONTRIBUTING.md) | 5 | | [![Screenipy Test - New Features](https://github.com/pranjal-joshi/Screeni-py/actions/workflows/workflow-test.yml/badge.svg?branch=new-features)](https://github.com/pranjal-joshi/Screeni-py/actions/workflows/workflow-test.yml) [![Screenipy Build - New Release](https://github.com/pranjal-joshi/Screeni-py/actions/workflows/workflow-build-matrix.yml/badge.svg)](https://github.com/pranjal-joshi/Screeni-py/actions/workflows/workflow-build-matrix.yml) | 6 | | ![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) | 7 | |

Scan QR Code to join [Official Telegram Group](https://t.me/+0Tzy08mR0do0MzNl) for Additional Discussions

| 8 | 9 | | **YouTube** | **Use** | **Discussion** | **Bugs/Issues** | **Documentation** | 10 | | :---: | :---: | :---: | :---: | :---: | 11 | | [![youtube](https://github.com/pranjal-joshi/Screeni-py/assets/6128978/9673bbcf-4798-48f4-918d-692b90e28d37)](https://www.youtube.com/playlist?list=PLsGnKKT_974J3UVS8M6bxqePfWLeuMsBi) | [![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) | 12 | | Watch our [YouTube](https://www.youtube.com/playlist?list=PLsGnKKT_974J3UVS8M6bxqePfWLeuMsBi) playlist | Get started quickly using Docker | Join/Read the Community Discussion | Raise an Issue about a Problem | Get Help about Usage | 13 | 14 | 15 | 16 | --- 17 | 18 | ## What is Screeni-py? 19 | 20 | ### A Python-based stock screener for NSE, India 21 | 22 | **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. 23 | Screenipy is totally customizable and it can screen stocks with the settings that you have provided. 24 | 25 | ## How to use? (New Version - GUI Based) 26 | 27 | [![Screeni-py - Detailed Installation Guide](https://markdown-videos-api.jorgenkh.no/url?url=https%3A%2F%2Fyoutu.be%2F2HMN0ac4H20)](https://youtu.be/2HMN0ac4H20) 28 | [![Screeni-py - Configuration and Usage | Screenipy - Python NSE Stock Screener](https://markdown-videos-api.jorgenkh.no/url?url=https%3A%2F%2Fyoutu.be%2FJCn6z1A7INI)](https://youtu.be/JCn6z1A7INI) 29 | [![Screeni-py - How to install Software Updates? | Screenipy - Python NSE Stock Screener](https://markdown-videos-api.jorgenkh.no/url?url=https%3A%2F%2Fyoutu.be%2FT41m13iMyJc)](https://youtu.be/T41m13iMyJc) 30 | 31 | * Install Docker Desktop and pull the `latest` docker image from the [release](https://github.com/pranjal-joshi/Screeni-py/releases/latest) page. 32 | * Checkout this [YouTube Video](https://youtu.be/2HMN0ac4H20) for detailed installation guide. 33 | 34 | image 35 | image 36 | image 37 | image 38 | image 39 | 40 | ## How to use? (Older Version - CLI Based - DEPRECATED) 41 | 42 | * Download the suitable file according to your OS or install Docker Desktop and pull the `latest` docker image. 43 | * Linux & Mac users should make sure that the `screenipy.bin or screenipy.run` has `execute` permission. 44 | * **Run** the file. The following window will appear after a brief delay. 45 | 46 | ![home](screenshots/screenipy_demo.gif) 47 | 48 | * **Configure** the parameters as per your requirement using `Option > 8`. 49 | 50 | ![config](screenshots/config.png) 51 | 52 | * Following are the screenshots of screening and output results. 53 | 54 | ![screening](screenshots/screening.png) 55 | ![results](screenshots/results.png) 56 | ![done](screenshots/done.png) 57 | 58 | * Once done, you can also save the results in an Excel file. 59 | 60 | ## Understanding the Result Table 61 | 62 | The Result table contains a lot of different parameters which can be pretty overwhelming to the new users, so here's the description and significance of each parameter. 63 | 64 | | Sr | Parameter | Description | Example | 65 | |:---:|:---:|:---|:---| 66 | |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)| 67 | |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 %`| 68 | |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)| 69 | |4|**LTP**|LTP is the Last Traded Price of an asset traded on NSE.|`298.7` (Stock is trading at this price)| 70 | |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`| 71 | |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| 72 | |7|**RSI**|For the momentum traders, it describes 14-period RSI for quick decision-making about their trading plans|`0 to 100`| 73 | |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.| 74 | |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.| 75 | 76 | ## Hack it your way 77 | 78 | Feel free to Edit the parameters in the `screenipy.ini` file which will be generated by the application. 79 | 80 | ```ini 81 | [config] 82 | period = 300d 83 | daystolookback = 30 84 | duration = 1d 85 | minprice = 30 86 | maxprice = 10000 87 | volumeratio = 2 88 | consolidationpercentage = 10 89 | shuffle = y 90 | cachestockdata = y 91 | onlystagetwostocks = y 92 | useema = n 93 | ``` 94 | 95 | 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. 96 | 97 | ## Installation Guide 98 | 99 | ### YouTube Video of Detailed Installation Guide 100 | 101 | [![YouTube Video Views](https://img.shields.io/youtube/views/2HMN0ac4H20?style=for-the-badge&logo=youtube)](https://youtu.be/2HMN0ac4H20) 102 | 103 | [![Screeni-py - Detailed Installation Guide](https://markdown-videos-api.jorgenkh.no/url?url=https%3A%2F%2Fyoutu.be%2F2HMN0ac4H20)](https://youtu.be/2HMN0ac4H20) 104 | 105 | ![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) 106 | 107 | ### Why we shifted to Docker from the Good old EXEs? 108 | 109 | | Executable/Binary File | Docker | 110 | | :-- | :-- | 111 | | [![GitHub Downloads](https://img.shields.io/github/downloads/pranjal-joshi/Screeni-py/total?color=Green&label=Downloads&style=for-the-badge)](#) | ![Docker Pulls](https://img.shields.io/docker/pulls/joshipranjal/screeni-py?style=for-the-badge&logo=docker) | 112 | | Download Directly from the [Release](https://github.com/pranjal-joshi/Screeni-py/releases/latest) page (DEPRECATED) | Need to Install [Docker Desktop](https://www.docker.com/products/docker-desktop/) ⚠️| 113 | | May take a long time to open the app | Loads quickly | 114 | | Slower screening | Performance boosted as per your CPU capabilities | 115 | | You may face errors/warnings due to different CPU arch of your system ⚠️ | Compatible with all x86_64/amd64/arm64 CPUs irrespective of OS (including Mac M1/M2) | 116 | | Works only with Windows 10/11 ⚠️ | Works with older versions of Windows as well | 117 | | Different file for each OS | Same container is compatible with everyone | 118 | | Antivirus may block this as untrusted file ⚠️ | No issues with Antivirus | 119 | | Need to download new file for every update | Updates quickly with minimal downloading | 120 | | No need of commands/technical knowledge | Very basic command execution skills may be required | 121 | | Incompatible with Vector Database ⚠️ | Compatible with all Python libraries | 122 | 123 | ### How to set up and use Screeni-py with Docker? 124 | 125 | 1. Download and Install [Docker Desktop](https://www.docker.com/products/docker-desktop/) with default settings 126 | 2. If you are using Windows, update WSL (Windows subsystem for linux) by running `wsl --update` command in the command prompt 127 | 3. Restart your computer after installation 128 | 4. Open Docker Desktop and keep it as it is 129 | 5. Open Command Prompt (Windows) or Terminal (Mac/Linux) and run command `docker pull joshipranjal/screeni-py:latest` 130 | 6. Once installed, always start screenipy by running this command: 131 | 132 | ```bash 133 | docker run -p 8501:8501 -p 8000:8000 joshipranjal/screeni-py:latest 134 | 135 | OR 136 | 137 | docker run -it --entrypoint /bin/bash joshipranjal/screeni-py:latest -c "run_screenipy.sh --cli" 138 | ``` 139 | 140 | Check out this [YouTube Video](https://youtu.be/2HMN0ac4H20) for a detailed installation guide. 141 | 142 | ## Contributing 143 | 144 | * Please feel free to Suggest improvements/report bugs by creating an issue. 145 | * Please follow the [Guidelines for Contributing](https://github.com/pranjal-joshi/Screeni-py/blob/new-features/CONTRIBUTING.md) while making a Pull Request. 146 | 147 | ## Disclaimer 148 | 149 | * DO NOT use the result provided by the software 'solely' to make your trading decisions. 150 | * Always backtest and analyze the stocks manually before you trade. 151 | * The Author and the software will not be held liable for your losses. 152 | -------------------------------------------------------------------------------- /actions-data-download/stock_data_140823.pkl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/actions-data-download/stock_data_140823.pkl -------------------------------------------------------------------------------- /patterns/IPO1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/patterns/IPO1.png -------------------------------------------------------------------------------- /patterns/IPO2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/patterns/IPO2.png -------------------------------------------------------------------------------- /patterns/IPO3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/patterns/IPO3.png -------------------------------------------------------------------------------- /patterns/MomentumGainer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/patterns/MomentumGainer.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # numpy==1.21.0 # Installed as dependency for yfinance, scipy, matplotlib, pandas 2 | numpy 3 | appdirs 4 | cachecontrol 5 | contextlib2 6 | distlib 7 | distro 8 | html5lib 9 | ipaddr 10 | lockfile 11 | nsetools 12 | openpyxl 13 | pep517 14 | pip 15 | pip-chill 16 | progress 17 | pytest-mock 18 | pytoml 19 | retrying 20 | scipy==1.11.2 21 | TA-Lib-Precompiled 22 | tabulate 23 | yfinance==0.2.60 24 | alive-progress==1.6.2 25 | Pillow 26 | scikit-learn==1.3.2 27 | joblib 28 | altgraph # Installed as dependency for pyinstaller 29 | atomicwrites # Installed as dependency for pytest 30 | attrs # Installed as dependency for pytest 31 | certifi # Installed as dependency for requests 32 | chardet # Installed as dependency for requests 33 | colorama # Installed as dependency for pytest 34 | dateutils # Installed as dependency for nsetools 35 | et-xmlfile # Installed as dependency for openpyxl 36 | future # Installed as dependency for pefile 37 | idna # Installed as dependency for requests 38 | iniconfig # Installed as dependency for pytest 39 | lxml # Installed as dependency for yfinance 40 | msgpack # Installed as dependency for cachecontrol 41 | multitasking # Installed as dependency for yfinance 42 | packaging # Installed as dependency for pytest 43 | pandas==2.1.2 # Installed as dependency for yfinance 44 | pefile # Installed as dependency for pyinstaller 45 | pluggy # Installed as dependency for pytest 46 | py # Installed as dependency for pytest 47 | pyinstaller-hooks-contrib # Installed as dependency for pyinstaller 48 | pyparsing # Installed as dependency for packaging, matplotlib 49 | pytest # Installed as dependency for pytest-mock 50 | python-dateutil # Installed as dependency for dateutils, matplotlib, pandas 51 | pytz # Installed as dependency for dateutils, pandas 52 | pywin32-ctypes # Installed as dependency for pyinstaller 53 | requests # Installed as dependency for yfinance, cachecontrol 54 | setuptools # Installed as dependency for pyinstaller 55 | six # Installed as dependency for cycler, nsetools, packaging, retrying, python-dateutil, html5lib 56 | toml # Installed as dependency for pep517, pytest 57 | urllib3 # Installed as dependency for requests 58 | webencodings # Installed as dependency for html5lib 59 | pandas_ta 60 | protobuf 61 | streamlit 62 | tensorflow 63 | chromadb==0.4.10 64 | mplfinance==0.12.9-beta.7 65 | num2words 66 | advanced-ta -------------------------------------------------------------------------------- /run_screenipy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | variable_name="SCREENIPY_GUI" 3 | 4 | cd src 5 | 6 | # Check if the script was provided with at least one argument 7 | if [ $# -lt 1 ]; then 8 | echo "Usage: $0 [--gui|--cli]" 9 | exit 1 10 | fi 11 | 12 | # Check the value of the first argument 13 | if [ "$1" = "--gui" ]; then 14 | export SCREENIPY_GUI=TRUE 15 | echo " " 16 | echo "Starting in GUI mode... Copy and Paste following URL in your browser.." 17 | streamlit run streamlit_app.py --server.port=8501 --server.address=0.0.0.0 18 | elif [ "$1" = "--cli" ]; then 19 | unset "SCREENIPY_GUI" 20 | echo "Starting in CLI mode..." 21 | python3 screenipy.py 22 | else 23 | echo "Invalid argument. Usage: $0 [--gui|--cli]" 24 | exit 1 25 | fi 26 | -------------------------------------------------------------------------------- /screenshots/NSE_Logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 26 | 27 | -------------------------------------------------------------------------------- /screenshots/config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/screenshots/config.png -------------------------------------------------------------------------------- /screenshots/done.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/screenshots/done.png -------------------------------------------------------------------------------- /screenshots/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/screenshots/home.png -------------------------------------------------------------------------------- /screenshots/results.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/screenshots/results.png -------------------------------------------------------------------------------- /screenshots/screening.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/screenshots/screening.png -------------------------------------------------------------------------------- /screenshots/screenipy_demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/screenshots/screenipy_demo.gif -------------------------------------------------------------------------------- /src/.streamlit/config.toml: -------------------------------------------------------------------------------- 1 | [theme] 2 | base="light" 3 | -------------------------------------------------------------------------------- /src/classes/CandlePatterns.py: -------------------------------------------------------------------------------- 1 | ''' 2 | * Project : Screenipy 3 | * Author : Pranjal Joshi 4 | * Created : 11/04/2021 5 | * Description : Class for analyzing candle-stick patterns 6 | ''' 7 | 8 | import pandas as pd 9 | from classes.ScreenipyTA import ScreenerTA 10 | from classes.ColorText import colorText 11 | 12 | class CandlePatterns: 13 | 14 | reversalPatternsBullish = ['Morning Star', 'Morning Doji Star', '3 Inside Up', 'Hammer', '3 White Soldiers', 'Bullish Engulfing', 'Dragonfly Doji', 'Supply Drought', 'Demand Rise'] 15 | reversalPatternsBearish = ['Evening Star', 'Evening Doji Star', '3 Inside Down', 'Inverted Hammer', 'Hanging Man', '3 Black Crows', 'Bearish Engulfing', 'Shooting Star', 'Gravestone Doji'] 16 | 17 | def __init__(self): 18 | pass 19 | 20 | # Find candle-stick patterns 21 | # Arrange if statements with max priority from top to bottom 22 | def findPattern(self, data, dict, saveDict): 23 | data = data.head(4) 24 | data = data[::-1] 25 | 26 | check = ScreenerTA.CDLMORNINGSTAR(data['Open'], data['High'], data['Low'], data['Close']) 27 | if(check): 28 | dict['Pattern'] = colorText.BOLD + colorText.GREEN + 'Morning Star' + colorText.END 29 | saveDict['Pattern'] = 'Morning Star' 30 | return True 31 | 32 | check = ScreenerTA.CDLMORNINGDOJISTAR(data['Open'], data['High'], data['Low'], data['Close']) 33 | if(check): 34 | dict['Pattern'] = colorText.BOLD + colorText.GREEN + 'Morning Doji Star' + colorText.END 35 | saveDict['Pattern'] = 'Morning Doji Star' 36 | return True 37 | 38 | check = ScreenerTA.CDLEVENINGSTAR(data['Open'], data['High'], data['Low'], data['Close']) 39 | if(check): 40 | dict['Pattern'] = colorText.BOLD + colorText.FAIL + 'Evening Star' + colorText.END 41 | saveDict['Pattern'] = 'Evening Star' 42 | return True 43 | 44 | check = ScreenerTA.CDLEVENINGDOJISTAR(data['Open'], data['High'], data['Low'], data['Close']) 45 | if(check): 46 | dict['Pattern'] = colorText.BOLD + colorText.FAIL + 'Evening Doji Star' + colorText.END 47 | saveDict['Pattern'] = 'Evening Doji Star' 48 | return True 49 | 50 | check = ScreenerTA.CDLLADDERBOTTOM(data['Open'], data['High'], data['Low'], data['Close']) 51 | if(check): 52 | if(check is not None and check.tail(1).item() > 0): 53 | dict['Pattern'] = colorText.BOLD + colorText.GREEN + 'Ladder Bottom' + colorText.END 54 | saveDict['Pattern'] = 'Bullish Ladder Bottom' 55 | else: 56 | dict['Pattern'] = colorText.BOLD + colorText.FAIL + 'Ladder Bottom' + colorText.END 57 | saveDict['Pattern'] = 'Bearish Ladder Bottom' 58 | return True 59 | 60 | check = ScreenerTA.CDL3LINESTRIKE(data['Open'], data['High'], data['Low'], data['Close']) 61 | if(check): 62 | if(check is not None and check.tail(1).item() > 0): 63 | dict['Pattern'] = colorText.BOLD + colorText.GREEN + '3 Line Strike' + colorText.END 64 | else: 65 | dict['Pattern'] = colorText.BOLD + colorText.FAIL + '3 Line Strike' + colorText.END 66 | saveDict['Pattern'] = '3 Line Strike' 67 | return True 68 | 69 | check = ScreenerTA.CDL3BLACKCROWS(data['Open'], data['High'], data['Low'], data['Close']) 70 | if(check): 71 | dict['Pattern'] = colorText.BOLD + colorText.FAIL + '3 Black Crows' + colorText.END 72 | saveDict['Pattern'] = '3 Black Crows' 73 | return True 74 | 75 | check = ScreenerTA.CDL3INSIDE(data['Open'], data['High'], data['Low'], data['Close']) 76 | if(check): 77 | if(check is not None and check.tail(1).item() > 0): 78 | dict['Pattern'] = colorText.BOLD + colorText.GREEN + '3 Outside Up' + colorText.END 79 | saveDict['Pattern'] = '3 Inside Up' 80 | else: 81 | dict['Pattern'] = colorText.BOLD + colorText.FAIL + '3 Outside Down' + colorText.END 82 | saveDict['Pattern'] = '3 Inside Down' 83 | return True 84 | 85 | check = ScreenerTA.CDL3OUTSIDE(data['Open'], data['High'], data['Low'], data['Close']) 86 | if(check > 0): 87 | dict['Pattern'] = colorText.BOLD + colorText.GREEN + '3 Outside Up' + colorText.END 88 | saveDict['Pattern'] = '3 Outside Up' 89 | return True 90 | elif(check < 0): 91 | dict['Pattern'] = colorText.BOLD + colorText.FAIL + '3 Outside Down' + colorText.END 92 | saveDict['Pattern'] = '3 Outside Down' 93 | return True 94 | 95 | check = ScreenerTA.CDL3WHITESOLDIERS(data['Open'], data['High'], data['Low'], data['Close']) 96 | if(check): 97 | dict['Pattern'] = colorText.BOLD + colorText.GREEN + '3 White Soldiers' + colorText.END 98 | saveDict['Pattern'] = '3 White Soldiers' 99 | return True 100 | 101 | check = ScreenerTA.CDLHARAMI(data['Open'], data['High'], data['Low'], data['Close']) 102 | if(check): 103 | if(check is not None and check.tail(1).item() > 0): 104 | dict['Pattern'] = colorText.BOLD + colorText.GREEN + 'Bullish Harami' + colorText.END 105 | saveDict['Pattern'] = 'Bullish Harami' 106 | else: 107 | dict['Pattern'] = colorText.BOLD + colorText.FAIL + 'Bearish Harami' + colorText.END 108 | saveDict['Pattern'] = 'Bearish Harami' 109 | return True 110 | 111 | check = ScreenerTA.CDLHARAMICROSS(data['Open'], data['High'], data['Low'], data['Close']) 112 | if(check): 113 | if(check is not None and check.tail(1).item() > 0): 114 | dict['Pattern'] = colorText.BOLD + colorText.GREEN + 'Bullish Harami Cross' + colorText.END 115 | saveDict['Pattern'] = 'Bullish Harami Cross' 116 | else: 117 | dict['Pattern'] = colorText.BOLD + colorText.FAIL + 'Bearish Harami Cross' + colorText.END 118 | saveDict['Pattern'] = 'Bearish Harami Cross' 119 | return True 120 | 121 | check = ScreenerTA.CDLMARUBOZU(data['Open'], data['High'], data['Low'], data['Close']) 122 | if(check): 123 | if(check is not None and check.tail(1).item() > 0): 124 | dict['Pattern'] = colorText.BOLD + colorText.GREEN + 'Bullish Marubozu' + colorText.END 125 | saveDict['Pattern'] = 'Bullish Marubozu' 126 | else: 127 | dict['Pattern'] = colorText.BOLD + colorText.FAIL + 'Bearish Marubozu' + colorText.END 128 | saveDict['Pattern'] = 'Bearish Marubozu' 129 | return True 130 | 131 | check = ScreenerTA.CDLHANGINGMAN(data['Open'], data['High'], data['Low'], data['Close']) 132 | if(check): 133 | dict['Pattern'] = colorText.BOLD + colorText.FAIL + 'Hanging Man' + colorText.END 134 | saveDict['Pattern'] = 'Hanging Man' 135 | return True 136 | 137 | check = ScreenerTA.CDLHAMMER(data['Open'], data['High'], data['Low'], data['Close']) 138 | if(check): 139 | dict['Pattern'] = colorText.BOLD + colorText.GREEN + 'Hammer' + colorText.END 140 | saveDict['Pattern'] = 'Hammer' 141 | return True 142 | 143 | check = ScreenerTA.CDLINVERTEDHAMMER(data['Open'], data['High'], data['Low'], data['Close']) 144 | if(check): 145 | dict['Pattern'] = colorText.BOLD + colorText.GREEN + 'Inverted Hammer' + colorText.END 146 | saveDict['Pattern'] = 'Inverted Hammer' 147 | return True 148 | 149 | check = ScreenerTA.CDLSHOOTINGSTAR(data['Open'], data['High'], data['Low'], data['Close']) 150 | if(check): 151 | dict['Pattern'] = colorText.BOLD + colorText.FAIL + 'Shooting Star' + colorText.END 152 | saveDict['Pattern'] = 'Shooting Star' 153 | return True 154 | 155 | check = ScreenerTA.CDLDRAGONFLYDOJI(data['Open'], data['High'], data['Low'], data['Close']) 156 | if(check): 157 | dict['Pattern'] = colorText.BOLD + colorText.GREEN + 'Dragonfly Doji' + colorText.END 158 | saveDict['Pattern'] = 'Dragonfly Doji' 159 | return True 160 | 161 | check = ScreenerTA.CDLGRAVESTONEDOJI(data['Open'], data['High'], data['Low'], data['Close']) 162 | if(check): 163 | dict['Pattern'] = colorText.BOLD + colorText.FAIL + 'Gravestone Doji' + colorText.END 164 | saveDict['Pattern'] = 'Gravestone Doji' 165 | return True 166 | 167 | check = ScreenerTA.CDLDOJI(data['Open'], data['High'], data['Low'], data['Close']) 168 | if(check): 169 | dict['Pattern'] = colorText.BOLD + 'Doji' + colorText.END 170 | saveDict['Pattern'] = 'Doji' 171 | return True 172 | 173 | check = ScreenerTA.CDLENGULFING(data['Open'], data['High'], data['Low'], data['Close']) 174 | if(check > 0): 175 | dict['Pattern'] = colorText.BOLD + colorText.GREEN + 'Bullish Engulfing' + colorText.END 176 | saveDict['Pattern'] = 'Bullish Engulfing' 177 | return True 178 | elif(check < 0): 179 | dict['Pattern'] = colorText.BOLD + colorText.FAIL + 'Bearish Engulfing' + colorText.END 180 | saveDict['Pattern'] = 'Bearish Engulfing' 181 | return True 182 | 183 | dict['Pattern'] = '' 184 | saveDict['Pattern'] = '' 185 | return False 186 | -------------------------------------------------------------------------------- /src/classes/Changelog.py: -------------------------------------------------------------------------------- 1 | ''' 2 | * Project : Screenipy 3 | * Author : Pranjal Joshi 4 | * Created : 28/04/2021 5 | * Description : Class for maintaining changelog 6 | ''' 7 | 8 | from classes.ColorText import colorText 9 | 10 | VERSION = "2.27" 11 | 12 | changelog = colorText.BOLD + '[ChangeLog]\n' + colorText.END + colorText.BLUE + ''' 13 | [1.00 - Beta] 14 | 1. Initial Release for beta testing 15 | 2. Minor Bug fixes 16 | 17 | [1.01] 18 | 1. Inside Bar detection added. 19 | 2. OTA Software Update Implemented. 20 | 3. Stock shuffling added while screening 21 | 4. Results will be now also stored in the excel (screenipy-result.xlsx) file. 22 | 5. UI cosmetic updates for pretty-printing! 23 | 24 | [1.02] 25 | 1. Feature added to screen only STAGE-2 stocks. 26 | 2. OTA update download bug-fixed. 27 | 3. Auto generate default config if not found. 28 | 4. Minor bug-fixes. 29 | 30 | [1.03] 31 | 1. Result excel file will not be overwritten now. Each result file will be saved with timestamp. 32 | 2. Candlestick pattern recognition added. 33 | 34 | [1.04] 35 | 1. OTA Software Update bug-fixed. 36 | 2. Minor Improvements. 37 | 38 | [1.05] 39 | 1. More candlestick pattern added for recognition. 40 | 2. Option added to find stock with lowest volume in last 'N'-days to early detect possibility of breakout. 41 | 3. Last screened results will be stored and can be viewed with Option > 7. 42 | 4. Minor Bug-fixes and improvements. 43 | 44 | [1.06] 45 | 1. Option > 0 added - Screen stocks by enterning it's name (stock code). 46 | 2. Stability fixes and improvements. 47 | 3. Last screened results will be stored and can be viewed with Option > 7. 48 | 49 | [1.07] 50 | 1. Program Window will not automatically close now. 51 | 2. Bug fixes and improvements. 52 | 53 | [1.08] 54 | 1. Prompt added for saving excel after screening. 55 | 2. Program back-end architecture updated. 56 | 57 | [1.09] 58 | 1. RSI based screening added as Option > 5. 59 | 2. Minor Performance Improvements. 60 | 61 | [1.10] 62 | 1. Trend detection for the timeframe of analysis added. 63 | 64 | [1.11] 65 | 1. Option-6 -> Screen for stocks showing Reversal Signal added 66 | 2. Stage-2 Screening logic improved for identifying best stocks only. 67 | 3. Trend detection has been improved. 68 | 4. Bugs and Runtime warnings fixed. 69 | 70 | [1.12] 71 | 1. MA now gives more info like Candle Crossing and At Support/Resistance. 72 | 2. More Patterns added for Reversal Detection. 73 | 4. Trend detection enhanced for the timeframe of analysis. 74 | 5. Runtime Warnings have been fixed. 75 | 76 | [1.13] 77 | 1. Chart Pattern Detection added. Option > 7 78 | 2. Screen for Inside Bar Chart pattern. 79 | 3. Documentation updated and Performance fixes. 80 | 81 | [1.14][1.15] 82 | 1. Screening stocks with parallel processing using all cores available in machine. (Thanks to @swarpatel23) 83 | 2. Minor Bug-fixes and Improvements. 84 | 85 | [1.16] 86 | 1. Bullish Momentum Finder added. Option > 6 > 3 87 | 2. Stock Data Caching added. (Thanks to @swarpatel23) 88 | 3. Codefactoring Improved. 89 | 4. Ctrl+C crash fixed. 90 | 91 | [1.17] 92 | 1. Breakout detection improved. 93 | 2. Progressbar added. 94 | 3. Watchlist creation in Excel file and its screening. 95 | 96 | [1.18] 97 | 1. Cache and Performance fixes. 98 | 2. Breakout Calculation Enhanced. 99 | 100 | [1.19] 101 | 1. New Feature: Search for Bullish Reversal at MA. Option > 6 > 4 102 | 103 | [1.20] 104 | 1. Screen stocks as per your favorite index. (Thanks to @swarpatel23) 105 | 106 | [1.21] 107 | 1. TradingView Hyperlink added for stock symbol. 108 | 109 | [1.22] 110 | 1. Broken yfinance API fixed. 111 | 112 | [1.23] 113 | 1. Bug fixed for DualCore CPU. 114 | 2. Dependencies updated. 115 | 116 | [1.24] 117 | 1. IPO Base Breakout pattern added. Option > 7 > 3. 118 | 2. Data fetching interval fixed. 119 | 3. Permission bug-fixes for some windows users. 120 | 4. Result table optimized. 121 | 122 | [1.25] 123 | 1. Default configuration parameters optimized. 124 | 2. Configuration generation on first time usage don't need restart anymore! 125 | 3. Minor bug-fixes. 126 | 127 | [1.26] 128 | 1. New Feature: Screen for the MA Confluence pattern Option > 7 > 4. 129 | 130 | [1.27] 131 | 1. Display more information about an update when it is available. 132 | 2. Minor Fixes (MA Confluence). 133 | 134 | [1.28] 135 | 1. Volume Spread Analysis added for Bullish Reversals. Option > 6 > 5 136 | 137 | [1.29] 138 | 1. VSA screening optimized. 139 | 2. Error handling and timeout optimized. 140 | 3. Build Test mode added for CI/CD. 141 | 142 | [1.30] 143 | 1. New Tickers Group - Screen only for Newly Listed IPOs (Last 1 Yr) 144 | 2. Major bug fix - stage 2 criteria won't be applied for new listings. 145 | 3. Validation Fixed for Volume & MA Signal (Optimized for new listings) 146 | 4. Excel save header name bug fixed. 147 | 148 | [1.31] 149 | 1. BugFixes for false detection of patterns - IPO Base, Inside Bar. 150 | 2. New Application Icon. 151 | 3. Experimental - VCP Detection : Option > 7 > 4 152 | 153 | [1.32] 154 | 1. Performance Optimization. 155 | 2. Minor Improvements. 156 | 3. Argument added for Data download only : run screenipy.exe -d 157 | 158 | [1.33] 159 | 1. Alternate Data source added. 160 | 2. Workflow added to create cache data on cloud. 161 | 162 | [1.34] 163 | 1. New Reversal - Narrow Range : Try Option 6 > 6 164 | 2. Cache loading fixes for Pre-Market timings. Refer PR #103 165 | 3. Progressbar added for Alternate Source Cache Download. 166 | 167 | [1.35] 168 | 1. Separate Algorithms for NR depending on Live/After-Market hours. 169 | 2. NRx results fixed in Momentum Gainer Screening. 170 | 171 | [1.36] 172 | 1. Updated CSV URLs to New NSE Site. (#113) 173 | 174 | [1.37] 175 | 1. New Chart Pattern -> Buy at Trendline : Try Option 7 > 5 176 | 177 | [1.38] 178 | 1. Added AI based predictions for Nifty closing on next day : Select Index for Screening > N 179 | 180 | [1.39] 181 | 1. Intraday Live Scanner - 5 EMA for Indices : Try Option `E` 182 | 183 | [1.40] 184 | 1. Nifty AI Prediction - Model Accuracy Enhanced by new preprocessing - Better Gap predictions 185 | 186 | [1.41] 187 | 1. Fetching of Stock Codes list fixed after NSE migration to newer website - Not using `nsetools` anymore 188 | 189 | [1.42] 190 | 1. Down trend detection bug fixed 191 | 2. % Change added with LTP 192 | 193 | [1.43] 194 | 1. New Index added - F&O Only stocks 195 | 196 | [1.44] 197 | 1. Migrated ta-lib dependency to pandas_ta 198 | 199 | [1.45] 200 | 1. Minor bug fixes after dependency change 201 | 202 | [1.46] 203 | 1. TA-Lib reanabled. Dockerized for better distribution of the tool 204 | 205 | 206 | [2.00] 207 | 1. Streamlit UI (WebApp) added 208 | 2. Multi-Arch Docker support enabled 209 | 210 | [2.01] 211 | 1. Docker build fixed - Versioning critical bug fixed for further OTA updates 212 | 213 | [2.02] 214 | 1. Newly Listed (IPO) index critical bug fixed 215 | 2. OTA Updates fixed for GUI 216 | 3. Cosmetic improvements 217 | 4. YouTube Video added to docs 218 | 219 | [2.03] 220 | 1. AI based Nifty-50 Gap up/down prediction added to GUI 221 | 2. Cosmetic updates and minor bug-fixes 222 | 3. Search Similar Stock Added 223 | 4. Executables Deprecated now onwards 224 | 225 | [2.04] 226 | 1. OTA update fixed - caching added in GUI 227 | 2. Moved to TA-Lib-Precompiled (0.4.25) 228 | 3. Progressbar added for screening to GUI 229 | 4. Documentation updated 230 | 231 | [2.05] 232 | 1. Download Results button added 233 | 2. Configuration save bug fixed for checkboxes 234 | 3. Attempted to changed Docker DNS 235 | 236 | 237 | [2.06] 238 | 1. Links added with cosmetic upgrade 239 | 2. Docs updated 240 | 241 | [2.07] 242 | 1. US S&P 500 Index added - Try Index `15 > US S&P 500` 243 | 2. Minor improvemnets 244 | 245 | [2.08] 246 | 1. Nifty Prediction enhanced - New AI model uses Crude and Gold data for Gap Prediction 247 | 248 | [2.09] 249 | 1. Dependencies bumped to pandas-2.1.2 scikit-learn-1.3.2 for (pip install advanced-ta) compatibility 250 | 2. Added Lorentzian Classifier based screening criteria - Try Option `6 > Reversal signals and 7 > Lorentzian Classification` (Extending Gratitude towards Justin Dehorty and Loki Arya for Open-Sourcing this one ❤️) 251 | 3. MA-Confluence bug fixed 252 | 253 | [2.10] 254 | 1. Position Size Calculator added as a new tab 255 | 256 | [2.11] 257 | 1. Nifty Prediction issue fixed - Model is now trained on CPU instead of Apple-M1 GPU 258 | 259 | [2.12] 260 | 1. Cosmetic Updates for Position Size Calculator 261 | 2. Python base bumped to 3.11.6-slim-bookworm 262 | 263 | [2.13] 264 | 1. Date based Backtesting Added for Screening 265 | 2. Inside bar detection broken - bug fixed 266 | 3. Auto enhanced debug on console in dev release 267 | 268 | [2.14] 269 | 1. Dropdowns added for duration and period in configration tab 270 | 271 | [2.15] 272 | 1. MA Reversal improved for trend following (Inspired from Siddhart Bhanushali's 44 SMA) 273 | 274 | [2.16] 275 | 1. Nifty Prediction NaN values handled gracefully with forward filling if data is absent 276 | 2. Ticker 0 > Search by Stock name - re-enabled in GUI 277 | 278 | [2.17] 279 | 1. Backtest Report column added for backtest screening runs 280 | 281 | [2.18] 282 | 1. Critical backtest bug fixed (dropna axis-1 removed from results) 283 | 2. Clear stock cached data button added 284 | 285 | [2.19] 286 | 1. New Index (Group of Indices) `16 > Sectoral Indices` added 287 | 288 | [2.20] 289 | 1. Bugfixes - Clear cache button random key added to fix re-rendering issues 290 | 291 | [2.21] 292 | 1. Dependency updated - `advanced-ta` lib for bugfixes and performance improvement in Lorentzian Classifier 293 | 294 | [2.22] 295 | 1. RSI and 9 SMA of RSI based reversal added - Momentum based execution strategy. 296 | 297 | [2.23] 298 | 1. Changed Data Source for F&O Stocks - Using Zerodha Kite instead of Broken NSE Website 299 | 300 | [2.24] 301 | 1. Added Filters to Result Table (Special Thanks to https://github.com/koalyptus/TableFilter) 302 | 303 | [2.25] 304 | 1. Reduced docker image size by 50% (Special Thanks to https://github.com/smitpsanghavi) 305 | 306 | [2.26] 307 | 1. Bugfixes - yfinance package updated to 0.2.54 to fix Yahoo Finance API issue 308 | 2. Minor Improvements to maintain backward compatibility of the yfinance df 309 | 310 | [2.27] 311 | 1. Bugfixes - yfinance package updated to 0.2.61 to fix Yahoo Finance rate limit issue 312 | ''' + colorText.END 313 | -------------------------------------------------------------------------------- /src/classes/ColorText.py: -------------------------------------------------------------------------------- 1 | ''' 2 | * Project : Screenipy 3 | * Author : Pranjal Joshi 4 | * Created : 11/04/2021 5 | * Description : Class for terminal text decoration 6 | ''' 7 | 8 | # Decoration Class 9 | class colorText: 10 | HEAD = '\033[95m' 11 | BLUE = '\033[94m' 12 | GREEN = '\033[92m' 13 | WARN = '\033[93m' 14 | FAIL = '\033[91m' 15 | END = '\033[0m' 16 | BOLD = '\033[1m' 17 | UNDR = '\033[4m' -------------------------------------------------------------------------------- /src/classes/ConfigManager.py: -------------------------------------------------------------------------------- 1 | ''' 2 | * Project : Screenipy 3 | * Author : Pranjal Joshi 4 | * Created : 28/04/2021 5 | * Description : Class for managing the user configuration 6 | ''' 7 | 8 | import sys 9 | import os 10 | import glob 11 | import re 12 | import configparser 13 | from datetime import date 14 | from classes.ColorText import colorText 15 | 16 | parser = configparser.ConfigParser(strict=False) 17 | 18 | # Default attributes for Downloading Cache from Git repo 19 | default_period = '300d' 20 | default_duration = '1d' 21 | 22 | # This Class manages read/write of user configuration 23 | class tools: 24 | 25 | def __init__(self): 26 | self.consolidationPercentage = 10 27 | self.volumeRatio = 2 28 | self.minLTP = 20.0 29 | self.maxLTP = 50000 30 | self.period = '300d' 31 | self.duration = '1d' 32 | self.daysToLookback = 30 33 | self.shuffleEnabled = True 34 | self.cacheEnabled = True 35 | self.stageTwo = False 36 | self.useEMA = False 37 | 38 | def deleteStockData(self,excludeFile=None): 39 | for f in glob.glob('stock_data*.pkl'): 40 | if excludeFile is not None: 41 | if not f.endswith(excludeFile): 42 | os.remove(f) 43 | else: 44 | os.remove(f) 45 | 46 | # Handle user input and save config 47 | 48 | def setConfig(self, parser, default=False, showFileCreatedText=True): 49 | if default: 50 | parser.add_section('config') 51 | parser.set('config', 'period', self.period) 52 | parser.set('config', 'daysToLookback', str(self.daysToLookback)) 53 | parser.set('config', 'duration', self.duration) 54 | parser.set('config', 'minPrice', str(self.minLTP)) 55 | parser.set('config', 'maxPrice', str(self.maxLTP)) 56 | parser.set('config', 'volumeRatio', str(self.volumeRatio)) 57 | parser.set('config', 'consolidationPercentage', 58 | str(self.consolidationPercentage)) 59 | parser.set('config', 'shuffle', 'y') 60 | parser.set('config', 'cacheStockData', 'y') 61 | parser.set('config', 'onlyStageTwoStocks', 'y' if self.stageTwo else 'n') 62 | parser.set('config', 'useEMA', 'y' if self.useEMA else 'n') 63 | try: 64 | fp = open('screenipy.ini', 'w') 65 | parser.write(fp) 66 | fp.close() 67 | if showFileCreatedText: 68 | print(colorText.BOLD + colorText.GREEN + 69 | '[+] Default configuration generated as user configuration is not found!' + colorText.END) 70 | print(colorText.BOLD + colorText.GREEN + 71 | '[+] Use Option > 5 to edit in future.' + colorText.END) 72 | print(colorText.BOLD + colorText.GREEN + 73 | '[+] Close and Restart the program now.' + colorText.END) 74 | input('') 75 | sys.exit(0) 76 | except IOError: 77 | print(colorText.BOLD + colorText.FAIL + 78 | '[+] Failed to save user config. Exiting..' + colorText.END) 79 | input('') 80 | sys.exit(1) 81 | else: 82 | parser = configparser.ConfigParser(strict=False) 83 | parser.add_section('config') 84 | print('') 85 | print(colorText.BOLD + colorText.GREEN + 86 | '[+] Screeni-py User Configuration:' + colorText.END) 87 | self.period = input( 88 | '[+] Enter number of days for which stock data to be downloaded (Days)(Optimal = 365): ') 89 | self.daysToLookback = input( 90 | '[+] Number of recent days (TimeFrame) to screen for Breakout/Consolidation (Days)(Optimal = 20): ') 91 | self.duration = input( 92 | '[+] Enter Duration of each candle (Days)(Optimal = 1): ') 93 | self.minLTP = input( 94 | '[+] Minimum Price of Stock to Buy (in RS)(Optimal = 20): ') 95 | self.maxLTP = input( 96 | '[+] Maximum Price of Stock to Buy (in RS)(Optimal = 50000): ') 97 | self.volumeRatio = input( 98 | '[+] How many times the volume should be more than average for the breakout? (Number)(Optimal = 2.5): ') 99 | self.consolidationPercentage = input( 100 | '[+] How many % the price should be in range to consider it as consolidation? (Number)(Optimal = 10): ') 101 | self.shuffle = str(input( 102 | '[+] Shuffle stocks rather than screening alphabetically? (Y/N): ')).lower() 103 | self.cacheStockData = str(input( 104 | '[+] Enable High-Performance and Data-Saver mode? (This uses little bit more CPU but performs High Performance Screening) (Y/N): ')).lower() 105 | self.stageTwoPrompt = str(input( 106 | '[+] Screen only for Stage-2 stocks?\n(What are the stages? => https://www.investopedia.com/articles/trading/08/stock-cycle-trend-price.asp)\n(Y/N): ')).lower() 107 | self.useEmaPrompt = str(input( 108 | '[+] Use EMA instead of SMA? (EMA is good for Short-term & SMA for Mid/Long-term trades)[Y/N]: ')).lower() 109 | parser.set('config', 'period', self.period + "d") 110 | parser.set('config', 'daysToLookback', self.daysToLookback) 111 | parser.set('config', 'duration', self.duration + "d") 112 | parser.set('config', 'minPrice', self.minLTP) 113 | parser.set('config', 'maxPrice', self.maxLTP) 114 | parser.set('config', 'volumeRatio', self.volumeRatio) 115 | parser.set('config', 'consolidationPercentage', 116 | self.consolidationPercentage) 117 | parser.set('config', 'shuffle', self.shuffle) 118 | parser.set('config', 'cacheStockData', self.cacheStockData) 119 | parser.set('config', 'onlyStageTwoStocks', self.stageTwoPrompt) 120 | parser.set('config', 'useEMA', self.useEmaPrompt) 121 | 122 | # delete stock data due to config change 123 | self.deleteStockData() 124 | print(colorText.BOLD + colorText.FAIL + "[+] Cached Stock Data Deleted." + colorText.END) 125 | 126 | try: 127 | fp = open('screenipy.ini', 'w') 128 | parser.write(fp) 129 | fp.close() 130 | print(colorText.BOLD + colorText.GREEN + 131 | '[+] User configuration saved.' + colorText.END) 132 | print(colorText.BOLD + colorText.GREEN + 133 | '[+] Restart the Program to start Screening...' + colorText.END) 134 | input('') 135 | sys.exit(0) 136 | except IOError: 137 | print(colorText.BOLD + colorText.FAIL + 138 | '[+] Failed to save user config. Exiting..' + colorText.END) 139 | input('') 140 | sys.exit(1) 141 | 142 | # Load user config from file 143 | def getConfig(self, parser): 144 | if len(parser.read('screenipy.ini')): 145 | try: 146 | self.duration = parser.get('config', 'duration') 147 | self.period = parser.get('config', 'period') 148 | self.minLTP = float(parser.get('config', 'minprice')) 149 | self.maxLTP = float(parser.get('config', 'maxprice')) 150 | self.volumeRatio = float(parser.get('config', 'volumeRatio')) 151 | self.consolidationPercentage = float( 152 | parser.get('config', 'consolidationPercentage')) 153 | self.daysToLookback = int( 154 | parser.get('config', 'daysToLookback')) 155 | if 'n' not in str(parser.get('config', 'shuffle')).lower(): 156 | self.shuffleEnabled = True 157 | if 'n' not in str(parser.get('config', 'cachestockdata')).lower(): 158 | self.cacheEnabled = True 159 | if 'n' not in str(parser.get('config', 'onlyStageTwoStocks')).lower(): 160 | self.stageTwo = True 161 | else: 162 | self.stageTwo = False 163 | if 'y' not in str(parser.get('config', 'useEMA')).lower(): 164 | self.useEMA = False 165 | else: 166 | self.useEMA = True 167 | except configparser.NoOptionError: 168 | input(colorText.BOLD + colorText.FAIL + 169 | '[+] Screenipy requires user configuration again. Press enter to continue..' + colorText.END) 170 | parser.remove_section('config') 171 | self.setConfig(parser, default=False) 172 | else: 173 | self.setConfig(parser, default=True) 174 | 175 | # Print config file 176 | def showConfigFile(self): 177 | try: 178 | f = open('screenipy.ini', 'r') 179 | print(colorText.BOLD + colorText.GREEN + 180 | '[+] Screeni-py User Configuration:' + colorText.END) 181 | print("\n"+f.read()) 182 | f.close() 183 | input('') 184 | except: 185 | print(colorText.BOLD + colorText.FAIL + 186 | "[+] User Configuration not found!" + colorText.END) 187 | print(colorText.BOLD + colorText.WARN + 188 | "[+] Configure the limits to continue." + colorText.END) 189 | self.setConfig(parser) 190 | 191 | # Check if config file exists 192 | def checkConfigFile(self): 193 | try: 194 | f = open('screenipy.ini','r') 195 | f.close() 196 | return True 197 | except FileNotFoundError: 198 | return False 199 | 200 | # Get period as a numeric value 201 | def getPeriodNumeric(self): 202 | import re 203 | pattern = re.compile(r'\d+') 204 | result = [int(match.group()) for match in pattern.finditer(self.period)][0] 205 | return result 206 | 207 | -------------------------------------------------------------------------------- /src/classes/Fetcher.py: -------------------------------------------------------------------------------- 1 | ''' 2 | * Project : Screenipy 3 | * Author : Pranjal Joshi 4 | * Created : 28/04/2021 5 | * Description : Class for handling networking for fetching stock codes and data 6 | ''' 7 | 8 | import sys 9 | import urllib.request 10 | import csv 11 | import requests 12 | import random 13 | import os 14 | import datetime 15 | import yfinance as yf 16 | import pandas as pd 17 | from nsetools import Nse 18 | from classes.ColorText import colorText 19 | from classes.SuppressOutput import SuppressOutput 20 | from classes.Utility import isDocker 21 | 22 | nse = Nse() 23 | 24 | # Exception class if yfinance stock delisted 25 | 26 | 27 | class StockDataEmptyException(Exception): 28 | pass 29 | 30 | # This Class Handles Fetching of Stock Data over the internet 31 | 32 | 33 | class tools: 34 | 35 | def __init__(self, configManager): 36 | self.configManager = configManager 37 | pass 38 | 39 | def getAllNiftyIndices(self) -> dict: 40 | return { 41 | "^NSEI": "NIFTY 50", 42 | "^NSMIDCP": "NIFTY NEXT 50", 43 | "^CNX100": "NIFTY 100", 44 | "^CNX200": "NIFTY 200", 45 | "^CNX500": "NIFTY 500", 46 | "^NSEMDCP50": "NIFTY MIDCAP 50", 47 | "NIFTY_MIDCAP_100.NS": "NIFTY MIDCAP 100", 48 | "^CNXSC": "NIFTY SMALLCAP 100", 49 | "^INDIAVIX": "INDIA VIX", 50 | "NIFTYMIDCAP150.NS": "NIFTY MIDCAP 150", 51 | "NIFTYSMLCAP50.NS": "NIFTY SMALLCAP 50", 52 | "NIFTYSMLCAP250.NS": "NIFTY SMALLCAP 250", 53 | "NIFTYMIDSML400.NS": "NIFTY MIDSMALLCAP 400", 54 | "NIFTY500_MULTICAP.NS": "NIFTY500 MULTICAP 50:25:25", 55 | "NIFTY_LARGEMID250.NS": "NIFTY LARGEMIDCAP 250", 56 | "NIFTY_MID_SELECT.NS": "NIFTY MIDCAP SELECT", 57 | "NIFTY_TOTAL_MKT.NS": "NIFTY TOTAL MARKET", 58 | "NIFTY_MICROCAP250.NS": "NIFTY MICROCAP 250", 59 | "^NSEBANK": "NIFTY BANK", 60 | "^CNXAUTO": "NIFTY AUTO", 61 | "NIFTY_FIN_SERVICE.NS": "NIFTY FINANCIAL SERVICES", 62 | "^CNXFMCG": "NIFTY FMCG", 63 | "^CNXIT": "NIFTY IT", 64 | "^CNXMEDIA": "NIFTY MEDIA", 65 | "^CNXMETAL": "NIFTY METAL", 66 | "^CNXPHARMA": "NIFTY PHARMA", 67 | "^CNXPSUBANK": "NIFTY PSU BANK", 68 | "^CNXREALTY": "NIFTY REALTY", 69 | "NIFTY_HEALTHCARE.NS": "NIFTY HEALTHCARE INDEX", 70 | "NIFTY_CONSR_DURBL.NS": "NIFTY CONSUMER DURABLES", 71 | "NIFTY_OIL_AND_GAS.NS": "NIFTY OIL & GAS", 72 | "NIFTYALPHA50.NS": "NIFTY ALPHA 50", 73 | "^CNXCMDT": "NIFTY COMMODITIES", 74 | "NIFTY_CPSE.NS": "NIFTY CPSE", 75 | "^CNXENERGY": "NIFTY ENERGY", 76 | "^CNXINFRA": "NIFTY INFRASTRUCTURE", 77 | "^CNXMNC": "NIFTY MNC", 78 | "^CNXPSE": "NIFTY PSE", 79 | "^CNXSERVICE": "NIFTY SERVICES SECTOR", 80 | "NIFTY100_ESG.NS": "NIFTY100 ESG SECTOR LEADERS", 81 | } 82 | 83 | def _getBacktestDate(self, backtest): 84 | try: 85 | end = backtest + datetime.timedelta(days=1) 86 | if "d" in self.configManager.period: 87 | delta = datetime.timedelta(days = self.configManager.getPeriodNumeric()) 88 | elif "wk" in self.configManager.period: 89 | delta = datetime.timedelta(days = self.configManager.getPeriodNumeric() * 7) 90 | elif "m" in self.configManager.period: 91 | delta = datetime.timedelta(minutes = self.configManager.getPeriodNumeric()) 92 | elif "h" in self.configManager.period: 93 | delta = datetime.timedelta(hours = self.configManager.getPeriodNumeric()) 94 | start = end - delta 95 | return [start, end] 96 | except: 97 | return [None, None] 98 | 99 | def _getDatesForBacktestReport(self, backtest): 100 | dateDict = {} 101 | try: 102 | today = datetime.date.today() 103 | dateDict['T+1d'] = backtest + datetime.timedelta(days=1) if backtest + datetime.timedelta(days=1) < today else None 104 | dateDict['T+1wk'] = backtest + datetime.timedelta(weeks=1) if backtest + datetime.timedelta(weeks=1) < today else None 105 | dateDict['T+1mo'] = backtest + datetime.timedelta(days=30) if backtest + datetime.timedelta(days=30) < today else None 106 | dateDict['T+6mo'] = backtest + datetime.timedelta(days=180) if backtest + datetime.timedelta(days=180) < today else None 107 | dateDict['T+1y'] = backtest + datetime.timedelta(days=365) if backtest + datetime.timedelta(days=365) < today else None 108 | for key, val in dateDict.copy().items(): 109 | if val is not None: 110 | if val.weekday() == 5: # 5 is Saturday, 6 is Sunday 111 | adjusted_date = val + datetime.timedelta(days=2) 112 | dateDict[key] = adjusted_date 113 | elif val.weekday() == 6: 114 | adjusted_date = val + datetime.timedelta(days=1) 115 | dateDict[key] = adjusted_date 116 | except: 117 | pass 118 | return dateDict 119 | 120 | def fetchCodes(self, tickerOption,proxyServer=None): 121 | listStockCodes = [] 122 | if tickerOption == 12: 123 | url = "https://archives.nseindia.com/content/equities/EQUITY_L.csv" 124 | return list(pd.read_csv(url)['SYMBOL'].values) 125 | if tickerOption == 15: 126 | return ["MMM", "ABT", "ABBV", "ABMD", "ACN", "ATVI", "ADBE", "AMD", "AAP", "AES", "AFL", "A", "APD", "AKAM", "ALK", "ALB", "ARE", "ALXN", "ALGN", "ALLE", "AGN", "ADS", "LNT", "ALL", "GOOGL", "GOOG", "MO", "AMZN", "AMCR", "AEE", "AAL", "AEP", "AXP", "AIG", "AMT", "AWK", "AMP", "ABC", "AME", "AMGN", "APH", "ADI", "ANSS", "ANTM", "AON", "AOS", "APA", "AIV", "AAPL", "AMAT", "APTV", "ADM", "ARNC", "ANET", "AJG", "AIZ", "ATO", "T", "ADSK", "ADP", "AZO", "AVB", "AVY", "BKR", "BLL", "BAC", "BK", "BAX", "BDX", "BRK.B", "BBY", "BIIB", "BLK", "BA", "BKNG", "BWA", "BXP", "BSX", "BMY", "AVGO", "BR", "BF.B", "CHRW", "COG", "CDNS", "CPB", "COF", "CPRI", "CAH", "KMX", "CCL", "CAT", "CBOE", "CBRE", "CDW", "CE", "CNC", "CNP", "CTL", "CERN", "CF", "SCHW", "CHTR", "CVX", "CMG", "CB", "CHD", "CI", "XEC", "CINF", "CTAS", "CSCO", "C", "CFG", "CTXS", "CLX", "CME", "CMS", "KO", "CTSH", "CL", "CMCSA", "CMA", "CAG", "CXO", "COP", "ED", "STZ", "COO", "CPRT", "GLW", "CTVA", "COST", "COTY", "CCI", "CSX", "CMI", "CVS", "DHI", "DHR", "DRI", "DVA", "DE", "DAL", "XRAY", "DVN", "FANG", "DLR", "DFS", "DISCA", "DISCK", "DISH", "DG", "DLTR", "D", "DOV", "DOW", "DTE", "DUK", "DRE", "DD", "DXC", "ETFC", "EMN", "ETN", "EBAY", "ECL", "EIX", "EW", "EA", "EMR", "ETR", "EOG", "EFX", "EQIX", "EQR", "ESS", "EL", "EVRG", "ES", "RE", "EXC", "EXPE", "EXPD", "EXR", "XOM", "FFIV", "FB", "FAST", "FRT", "FDX", "FIS", "FITB", "FE", "FRC", "FISV", "FLT", "FLIR", "FLS", "FMC", "F", "FTNT", "FTV", "FBHS", "FOXA", "FOX", "BEN", "FCX", "GPS", "GRMN", "IT", "GD", "GE", "GIS", "GM", "GPC", "GILD", "GL", "GPN", "GS", "GWW", "HRB", "HAL", "HBI", "HOG", "HIG", "HAS", "HCA", "PEAK", "HP", "HSIC", "HSY", "HES", "HPE", "HLT", "HFC", "HOLX", "HD", "HON", "HRL", "HST", "HPQ", "HUM", "HBAN", "HII", "IEX", "IDXX", "INFO", "ITW", "ILMN", "IR", "INTC", "ICE", "IBM", "INCY", "IP", "IPG", "IFF", "INTU", "ISRG", "IVZ", "IPGP", "IQV", "IRM", "JKHY", "J", "JBHT", "SJM", "JNJ", "JCI", "JPM", "JNPR", "KSU", "K", "KEY", "KEYS", "KMB", "KIM", "KMI", "KLAC", "KSS", "KHC", "KR", "LB", "LHX", "LH", "LRCX", "LW", "LVS", "LEG", "LDOS", "LEN", "LLY", "LNC", "LIN", "LYV", "LKQ", "LMT", "L", "LOW", "LYB", "MTB", "M", "MRO", "MPC", "MKTX", "MAR", "MMC", "MLM", "MAS", "MA", "MKC", "MXIM", "MCD", "MCK", "MDT", "MRK", "MET", "MTD", "MGM", "MCHP", "MU", "MSFT", "MAA", "MHK", "TAP", "MDLZ", "MNST", "MCO", "MS", "MOS", "MSI", "MSCI", "MYL", "NDAQ", "NOV", "NTAP", "NFLX", "NWL", "NEM", "NWSA", "NWS", "NEE", "NLSN", "NKE", "NI", "NBL", "JWN", "NSC", "NTRS", "NOC", "NLOK", "NCLH", "NRG", "NUE", "NVDA", "NVR", "ORLY", "OXY", "ODFL", "OMC", "OKE", "ORCL", "PCAR", "PKG", "PH", "PAYX", "PYPL", "PNR", "PBCT", "PEP", "PKI", "PRGO", "PFE", "PM", "PSX", "PNW", "PXD", "PNC", "PPG", "PPL", "PFG", "PG", "PGR", "PLD", "PRU", "PEG", "PSA", "PHM", "PVH", "QRVO", "PWR", "QCOM", "DGX", "RL", "RJF", "RTN", "O", "REG", "REGN", "RF", "RSG", "RMD", "RHI", "ROK", "ROL", "ROP", "ROST", "RCL", "SPGI", "CRM", "SBAC", "SLB", "STX", "SEE", "SRE", "NOW", "SHW", "SPG", "SWKS", "SLG", "SNA", "SO", "LUV", "SWK", "SBUX", "STT", "STE", "SYK", "SIVB", "SYF", "SNPS", "SYY", "TMUS", "TROW", "TTWO", "TPR", "TGT", "TEL", "FTI", "TFX", "TXN", "TXT", "TMO", "TIF", "TJX", "TSCO", "TDG", "TRV", "TFC", "TWTR", "TSN", "UDR", "ULTA", "USB", "UAA", "UA", "UNP", "UAL", "UNH", "UPS", "URI", "UTX", "UHS", "UNM", "VFC", "VLO", "VAR", "VTR", "VRSN", "VRSK", "VZ", "VRTX", "VIAC", "V", "VNO", "VMC", "WRB", "WAB", "WMT", "WBA", "DIS", "WM", "WAT", "WEC", "WCG", "WFC", "WELL", "WDC", "WU", "WRK", "WY", "WHR", "WMB", "WLTW", "WYNN", "XEL", "XRX", "XLNX", "XYL", "YUM", "ZBRA", "ZBH", "ZION", "ZTS"] 127 | if tickerOption == 16: 128 | return self.getAllNiftyIndices() 129 | tickerMapping = { 130 | 1: "https://archives.nseindia.com/content/indices/ind_nifty50list.csv", 131 | 2: "https://archives.nseindia.com/content/indices/ind_niftynext50list.csv", 132 | 3: "https://archives.nseindia.com/content/indices/ind_nifty100list.csv", 133 | 4: "https://archives.nseindia.com/content/indices/ind_nifty200list.csv", 134 | 5: "https://archives.nseindia.com/content/indices/ind_nifty500list.csv", 135 | 6: "https://archives.nseindia.com/content/indices/ind_niftysmallcap50list.csv", 136 | 7: "https://archives.nseindia.com/content/indices/ind_niftysmallcap100list.csv", 137 | 8: "https://archives.nseindia.com/content/indices/ind_niftysmallcap250list.csv", 138 | 9: "https://archives.nseindia.com/content/indices/ind_niftymidcap50list.csv", 139 | 10: "https://archives.nseindia.com/content/indices/ind_niftymidcap100list.csv", 140 | 11: "https://archives.nseindia.com/content/indices/ind_niftymidcap150list.csv", 141 | 14: "https://api.kite.trade/instruments" 142 | } 143 | 144 | url = tickerMapping.get(tickerOption) 145 | 146 | try: 147 | if proxyServer: 148 | res = requests.get(url,proxies={'https':proxyServer}) 149 | else: 150 | res = requests.get(url) 151 | 152 | cr = csv.reader(res.text.strip().split('\n')) 153 | 154 | if tickerOption == 14: 155 | cols = next(cr) 156 | df = pd.DataFrame(cr, columns=cols) 157 | listStockCodes = list(set(df[df['segment'] == 'NFO-FUT']["name"].to_list())) 158 | listStockCodes.sort() 159 | else: 160 | next(cr) # skipping first line 161 | for row in cr: 162 | listStockCodes.append(row[2]) 163 | except Exception as error: 164 | print(error) 165 | 166 | return listStockCodes 167 | 168 | # Fetch all stock codes from NSE 169 | def fetchStockCodes(self, tickerOption, proxyServer=None): 170 | listStockCodes = [] 171 | if tickerOption == 0: 172 | stockCode = None 173 | while stockCode == None or stockCode == "": 174 | stockCode = str(input(colorText.BOLD + colorText.BLUE + 175 | "[+] Enter Stock Code(s) for screening (Multiple codes should be seperated by ,): ")).upper() 176 | stockCode = stockCode.replace(" ", "") 177 | listStockCodes = stockCode.split(',') 178 | else: 179 | print(colorText.BOLD + 180 | "[+] Getting Stock Codes From NSE... ", end='') 181 | listStockCodes = self.fetchCodes(tickerOption,proxyServer=proxyServer) 182 | if type(listStockCodes) == dict: 183 | listStockCodes = list(listStockCodes.keys()) 184 | if len(listStockCodes) > 10: 185 | print(colorText.GREEN + ("=> Done! Fetched %d stock codes." % 186 | len(listStockCodes)) + colorText.END) 187 | if self.configManager.shuffleEnabled: 188 | random.shuffle(listStockCodes) 189 | print(colorText.BLUE + 190 | "[+] Stock shuffling is active." + colorText.END) 191 | else: 192 | print(colorText.FAIL + 193 | "[+] Stock shuffling is inactive." + colorText.END) 194 | if self.configManager.stageTwo: 195 | print( 196 | colorText.BLUE + "[+] Screening only for the stocks in Stage-2! Edit User Config to change this." + colorText.END) 197 | else: 198 | print( 199 | colorText.FAIL + "[+] Screening only for the stocks in all Stages! Edit User Config to change this." + colorText.END) 200 | 201 | else: 202 | input( 203 | colorText.FAIL + "=> Error getting stock codes from NSE! Press any key to exit!" + colorText.END) 204 | sys.exit("Exiting script..") 205 | 206 | return listStockCodes 207 | 208 | # Fetch stock price data from Yahoo finance 209 | def fetchStockData(self, stockCode, period, duration, proxyServer, screenResultsCounter, screenCounter, totalSymbols, backtestDate=None, printCounter=False, tickerOption=None): 210 | dateDict = None 211 | with SuppressOutput(suppress_stdout=True, suppress_stderr=True): 212 | append_exchange = ".NS" 213 | if tickerOption == 15 or tickerOption == 16: 214 | append_exchange = "" 215 | data = yf.download( 216 | tickers=stockCode + append_exchange, 217 | period=period, 218 | interval=duration, 219 | proxy=proxyServer, 220 | progress=False, 221 | timeout=10, 222 | start=self._getBacktestDate(backtest=backtestDate)[0], 223 | end=self._getBacktestDate(backtest=backtestDate)[1], 224 | auto_adjust=False 225 | ) 226 | # For df backward compatibility towards yfinance 0.2.32 227 | data = self.makeDataBackwardCompatible(data) 228 | # end 229 | if backtestDate != datetime.date.today(): 230 | dateDict = self._getDatesForBacktestReport(backtest=backtestDate) 231 | backtestData = yf.download( 232 | tickers=stockCode + append_exchange, 233 | interval='1d', 234 | proxy=proxyServer, 235 | progress=False, 236 | timeout=10, 237 | start=backtestDate - datetime.timedelta(days=1), 238 | end=backtestDate + datetime.timedelta(days=370) 239 | ) 240 | for key, value in dateDict.copy().items(): 241 | if value is not None: 242 | try: 243 | dateDict[key] = backtestData.loc[pd.Timestamp(value)]['Close'] 244 | except KeyError: 245 | continue 246 | dateDict['T+52wkH'] = backtestData['High'].max() 247 | dateDict['T+52wkL'] = backtestData['Low'].min() 248 | if printCounter: 249 | sys.stdout.write("\r\033[K") 250 | try: 251 | print(colorText.BOLD + colorText.GREEN + ("[%d%%] Screened %d, Found %d. Fetching data & Analyzing %s..." % ( 252 | int((screenCounter.value/totalSymbols)*100), screenCounter.value, screenResultsCounter.value, stockCode)) + colorText.END, end='') 253 | except ZeroDivisionError: 254 | pass 255 | if len(data) == 0: 256 | print(colorText.BOLD + colorText.FAIL + 257 | "=> Failed to fetch!" + colorText.END, end='\r', flush=True) 258 | raise StockDataEmptyException 259 | return None 260 | print(colorText.BOLD + colorText.GREEN + "=> Done!" + 261 | colorText.END, end='\r', flush=True) 262 | return data, dateDict 263 | 264 | # Get Daily Nifty 50 Index: 265 | def fetchLatestNiftyDaily(self, proxyServer=None): 266 | data = yf.download( 267 | auto_adjust=False, 268 | tickers="^NSEI", 269 | period='5d', 270 | interval='1d', 271 | proxy=proxyServer, 272 | progress=False, 273 | timeout=10 274 | ) 275 | gold = yf.download( 276 | auto_adjust=False, 277 | tickers="GC=F", 278 | period='5d', 279 | interval='1d', 280 | proxy=proxyServer, 281 | progress=False, 282 | timeout=10 283 | ).add_prefix(prefix='gold_') 284 | crude = yf.download( 285 | auto_adjust=False, 286 | tickers="CL=F", 287 | period='5d', 288 | interval='1d', 289 | proxy=proxyServer, 290 | progress=False, 291 | timeout=10 292 | ).add_prefix(prefix='crude_') 293 | data = self.makeDataBackwardCompatible(data) 294 | gold = self.makeDataBackwardCompatible(gold, column_prefix='gold_') 295 | crude = self.makeDataBackwardCompatible(crude, column_prefix='crude_') 296 | data = pd.concat([data, gold, crude], axis=1) 297 | return data 298 | 299 | # Get Data for Five EMA strategy 300 | def fetchFiveEmaData(self, proxyServer=None): 301 | nifty_sell = yf.download( 302 | auto_adjust=False, 303 | tickers="^NSEI", 304 | period='5d', 305 | interval='5m', 306 | proxy=proxyServer, 307 | progress=False, 308 | timeout=10 309 | ) 310 | banknifty_sell = yf.download( 311 | auto_adjust=False, 312 | tickers="^NSEBANK", 313 | period='5d', 314 | interval='5m', 315 | proxy=proxyServer, 316 | progress=False, 317 | timeout=10 318 | ) 319 | nifty_buy = yf.download( 320 | auto_adjust=False, 321 | tickers="^NSEI", 322 | period='5d', 323 | interval='15m', 324 | proxy=proxyServer, 325 | progress=False, 326 | timeout=10 327 | ) 328 | banknifty_buy = yf.download( 329 | auto_adjust=False, 330 | tickers="^NSEBANK", 331 | period='5d', 332 | interval='15m', 333 | proxy=proxyServer, 334 | progress=False, 335 | timeout=10 336 | ) 337 | nifty_buy = self.makeDataBackwardCompatible(nifty_buy) 338 | banknifty_buy = self.makeDataBackwardCompatible(banknifty_buy) 339 | nifty_sell = self.makeDataBackwardCompatible(nifty_sell) 340 | banknifty_sell = self.makeDataBackwardCompatible(banknifty_sell) 341 | return nifty_buy, banknifty_buy, nifty_sell, banknifty_sell 342 | 343 | # Load stockCodes from the watchlist.xlsx 344 | def fetchWatchlist(self): 345 | createTemplate = False 346 | data = pd.DataFrame() 347 | try: 348 | data = pd.read_excel('watchlist.xlsx') 349 | except FileNotFoundError: 350 | print(colorText.BOLD + colorText.FAIL + 351 | f'[+] watchlist.xlsx not found in f{os.getcwd()}' + colorText.END) 352 | createTemplate = True 353 | try: 354 | if not createTemplate: 355 | data = data['Stock Code'].values.tolist() 356 | except KeyError: 357 | print(colorText.BOLD + colorText.FAIL + 358 | '[+] Bad Watchlist Format: First Column (A1) should have Header named "Stock Code"' + colorText.END) 359 | createTemplate = True 360 | if createTemplate: 361 | if isDocker(): 362 | print(colorText.BOLD + colorText.FAIL + 363 | f'[+] This feature is not available with dockerized application. Try downloading .exe/.bin file to use this!' + colorText.END) 364 | return None 365 | sample = {'Stock Code': ['SBIN', 'INFY', 'TATAMOTORS', 'ITC']} 366 | sample_data = pd.DataFrame(sample, columns=['Stock Code']) 367 | sample_data.to_excel('watchlist_template.xlsx', 368 | index=False, header=True) 369 | print(colorText.BOLD + colorText.BLUE + 370 | f'[+] watchlist_template.xlsx created in {os.getcwd()} as a referance template.' + colorText.END) 371 | return None 372 | return data 373 | 374 | def makeDataBackwardCompatible(self, data:pd.DataFrame, column_prefix:str=None) -> pd.DataFrame: 375 | data = data.droplevel(level=1, axis=1) 376 | data = data.rename_axis(None, axis=1) 377 | column_prefix = '' if column_prefix is None else column_prefix 378 | data = data[ 379 | [ 380 | f'{column_prefix}Open', 381 | f'{column_prefix}High', 382 | f'{column_prefix}Low', 383 | f'{column_prefix}Close', 384 | f'{column_prefix}Adj Close', 385 | f'{column_prefix}Volume' 386 | ] 387 | ] 388 | return data 389 | -------------------------------------------------------------------------------- /src/classes/OtaUpdater.py: -------------------------------------------------------------------------------- 1 | ''' 2 | * Project : Screenipy 3 | * Author : Pranjal Joshi 4 | * Created : 21/04/2021 5 | * Description : Class for handling OTA updates 6 | ''' 7 | 8 | from classes.ColorText import colorText 9 | from classes.Utility import isDocker, isGui 10 | import requests 11 | import os 12 | import platform 13 | import sys 14 | import subprocess 15 | import requests 16 | 17 | class OTAUpdater: 18 | 19 | developmentVersion = 'd' 20 | 21 | # Download and replace exe through other process for Windows 22 | def updateForWindows(url): 23 | batFile = """@echo off 24 | color a 25 | echo [+] Screenipy Software Updater! 26 | echo [+] Downloading Software Update... 27 | echo [+] This may take some time as per your Internet Speed, Please Wait... 28 | curl -o screenipy.exe -L """ + url + """ 29 | echo [+] Newly downloaded file saved in %cd% 30 | echo [+] Software Update Completed! Run'screenipy.exe' again as usual to continue.. 31 | pause 32 | del updater.bat & exit 33 | """ 34 | f = open("updater.bat",'w') 35 | f.write(batFile) 36 | f.close() 37 | subprocess.Popen('start updater.bat', shell=True) 38 | sys.exit(0) 39 | 40 | # Download and replace bin through other process for Linux 41 | def updateForLinux(url): 42 | bashFile = """#!/bin/bash 43 | echo "" 44 | echo "[+] Starting Screeni-py updater, Please Wait..." 45 | sleep 3 46 | echo "[+] Screenipy Software Updater!" 47 | echo "[+] Downloading Software Update..." 48 | echo "[+] This may take some time as per your Internet Speed, Please Wait..." 49 | wget -q """ + url + """ -O screenipy.bin 50 | echo "[+] Newly downloaded file saved in $(pwd)" 51 | chmod +x screenipy.bin 52 | echo "[+] Update Completed! Run 'screenipy.bin' again as usual to continue.." 53 | rm updater.sh 54 | """ 55 | f = open("updater.sh",'w') 56 | f.write(bashFile) 57 | f.close() 58 | subprocess.Popen('bash updater.sh', shell=True) 59 | sys.exit(0) 60 | 61 | # Download and replace run through other process for Mac 62 | def updateForMac(url): 63 | bashFile = """#!/bin/bash 64 | echo "" 65 | echo "[+] Starting Screeni-py updater, Please Wait..." 66 | sleep 3 67 | echo "[+] Screenipy Software Updater!" 68 | echo "[+] Downloading Software Update..." 69 | echo "[+] This may take some time as per your Internet Speed, Please Wait..." 70 | curl -o screenipy.run -L """ + url + """ 71 | echo "[+] Newly downloaded file saved in $(pwd)" 72 | chmod +x screenipy.run 73 | echo "[+] Update Completed! Run 'screenipy.run' again as usual to continue.." 74 | rm updater.sh 75 | """ 76 | f = open("updater.sh",'w') 77 | f.write(bashFile) 78 | f.close() 79 | subprocess.Popen('bash updater.sh', shell=True) 80 | sys.exit(0) 81 | 82 | # Parse changelog from release.md 83 | def showWhatsNew(): 84 | url = "https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/main/src/release.md" 85 | md = requests.get(url) 86 | txt = md.text 87 | txt = txt.split("New?")[1] 88 | # txt = txt.split("## Downloads")[0] 89 | txt = txt.split("## Installation Guide")[0] 90 | txt = txt.replace('**','').replace('`','').strip() 91 | return (txt+"\n") 92 | 93 | # Check for update and download if available 94 | def checkForUpdate(proxyServer, VERSION="1.0"): 95 | OTAUpdater.checkForUpdate.url = None 96 | guiUpdateMessage = "" 97 | try: 98 | resp = None 99 | now = float(VERSION) 100 | if proxyServer: 101 | resp = requests.get("https://api.github.com/repos/pranjal-joshi/Screeni-py/releases/latest",proxies={'https':proxyServer}) 102 | else: 103 | resp = requests.get("https://api.github.com/repos/pranjal-joshi/Screeni-py/releases/latest") 104 | # Disabling Exe check as Executables are deprecated v2.03 onwards 105 | ''' 106 | if 'Windows' in platform.system(): 107 | OTAUpdater.checkForUpdate.url = resp.json()['assets'][1]['browser_download_url'] 108 | size = int(resp.json()['assets'][1]['size']/(1024*1024)) 109 | elif 'Darwin' in platform.system(): 110 | OTAUpdater.checkForUpdate.url = resp.json()['assets'][2]['browser_download_url'] 111 | size = int(resp.json()['assets'][2]['size']/(1024*1024)) 112 | else: 113 | OTAUpdater.checkForUpdate.url = resp.json()['assets'][0]['browser_download_url'] 114 | size = int(resp.json()['assets'][0]['size']/(1024*1024)) 115 | # if(float(resp.json()['tag_name']) > now): 116 | if(float(resp.json()['tag_name']) > now and not isDocker()): # OTA not applicable if we're running in docker! 117 | print(colorText.BOLD + colorText.WARN + "[+] What's New in this Update?\n" + OTAUpdater.showWhatsNew() + colorText.END) 118 | action = str(input(colorText.BOLD + colorText.WARN + ('\n[+] New Software update (v%s) available. Download Now (Size: %dMB)? [Y/N]: ' % (str(resp.json()['tag_name']),size)))).lower() 119 | if(action == 'y'): 120 | try: 121 | if 'Windows' in platform.system(): 122 | OTAUpdater.updateForWindows(OTAUpdater.checkForUpdate.url) 123 | elif 'Darwin' in platform.system(): 124 | OTAUpdater.updateForMac(OTAUpdater.checkForUpdate.url) 125 | else: 126 | OTAUpdater.updateForLinux(OTAUpdater.checkForUpdate.url) 127 | except Exception as e: 128 | print(colorText.BOLD + colorText.WARN + '[+] Error occured while updating!' + colorText.END) 129 | raise(e) 130 | ''' 131 | if(float(resp.json()['tag_name']) > now and not isDocker()): 132 | 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) 133 | elif(float(resp.json()['tag_name']) > now and isDocker()): # OTA not applicable if we're running in docker! 134 | 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) 135 | print(colorText.BOLD + colorText.WARN + "[+] What's New in this Update?\n" + OTAUpdater.showWhatsNew() + colorText.END) 136 | if isGui(): 137 | guiUpdateMessage = f"New Software update (v{resp.json()['tag_name']}) available - Watch this [**YouTube Video**](https://youtu.be/T41m13iMyJc) for additional help or Update by running following command:\n\n**`docker pull joshipranjal/screeni-py:latest`**" 138 | elif(float(resp.json()['tag_name']) < now): 139 | print(colorText.BOLD + colorText.FAIL + ('[+] This version (v%s) is in Development mode and unreleased!' % VERSION) + colorText.END) 140 | if isGui(): 141 | guiUpdateMessage = f"This version (v{VERSION}) is in Development mode and unreleased!" 142 | return OTAUpdater.developmentVersion, guiUpdateMessage 143 | except Exception as e: 144 | print(colorText.BOLD + colorText.FAIL + "[+] Failure while checking update!" + colorText.END) 145 | print(e) 146 | if OTAUpdater.checkForUpdate.url != None: 147 | print(colorText.BOLD + colorText.BLUE + ("[+] Download update manually from %s\n" % OTAUpdater.checkForUpdate.url) + colorText.END) 148 | return None, guiUpdateMessage -------------------------------------------------------------------------------- /src/classes/ParallelProcessing.py: -------------------------------------------------------------------------------- 1 | 2 | ''' 3 | * Project : Screenipy 4 | * Author : Pranjal Joshi, Swar Patel 5 | * Created : 18/05/2021 6 | * Description : Class for managing multiprocessing 7 | ''' 8 | 9 | import multiprocessing 10 | import pandas as pd 11 | import numpy as np 12 | import sys 13 | import os 14 | import pytz 15 | import traceback 16 | from queue import Empty 17 | from datetime import datetime 18 | import classes.Fetcher as Fetcher 19 | import classes.Screener as Screener 20 | import classes.Utility as Utility 21 | from copy import deepcopy 22 | from classes.CandlePatterns import CandlePatterns 23 | from classes.ColorText import colorText 24 | from classes.SuppressOutput import SuppressOutput 25 | 26 | if sys.platform.startswith('win'): 27 | import multiprocessing.popen_spawn_win32 as forking 28 | else: 29 | import multiprocessing.popen_fork as forking 30 | 31 | 32 | class StockConsumer(multiprocessing.Process): 33 | 34 | def __init__(self, task_queue, result_queue, screenCounter, screenResultsCounter, stockDict, proxyServer, keyboardInterruptEvent): 35 | multiprocessing.Process.__init__(self) 36 | self.multiprocessingForWindows() 37 | self.task_queue = task_queue 38 | self.result_queue = result_queue 39 | self.screenCounter = screenCounter 40 | self.screenResultsCounter = screenResultsCounter 41 | self.stockDict = stockDict 42 | self.proxyServer = proxyServer 43 | self.keyboardInterruptEvent = keyboardInterruptEvent 44 | self.isTradingTime = Utility.tools.isTradingTime() 45 | 46 | def run(self): 47 | # while True: 48 | try: 49 | while not self.keyboardInterruptEvent.is_set(): 50 | try: 51 | next_task = self.task_queue.get() 52 | except Empty: 53 | continue 54 | if next_task is None: 55 | self.task_queue.task_done() 56 | break 57 | answer = self.screenStocks(*(next_task)) 58 | self.task_queue.task_done() 59 | self.result_queue.put(answer) 60 | except Exception as e: 61 | sys.exit(0) 62 | 63 | def screenStocks(self, tickerOption, executeOption, reversalOption, maLength, daysForLowestVolume, minRSI, maxRSI, respChartPattern, insideBarToLookback, totalSymbols, 64 | configManager, fetcher, screener:Screener.tools, candlePatterns, stock, newlyListedOnly, downloadOnly, vectorSearch, isDevVersion, backtestDate, printCounter=False): 65 | screenResults = pd.DataFrame(columns=[ 66 | 'Stock', 'Consolidating', 'Breaking-Out', 'MA-Signal', 'Volume', 'LTP', 'RSI', 'Trend', 'Pattern']) 67 | screeningDictionary = {'Stock': "", 'Consolidating': "", 'Breaking-Out': "", 68 | 'MA-Signal': "", 'Volume': "", 'LTP': 0, 'RSI': 0, 'Trend': "", 'Pattern': ""} 69 | saveDictionary = {'Stock': "", 'Consolidating': "", 'Breaking-Out': "", 70 | 'MA-Signal': "", 'Volume': "", 'LTP': 0, 'RSI': 0, 'Trend': "", 'Pattern': ""} 71 | 72 | try: 73 | period = configManager.period 74 | 75 | # Data download adjustment for Newly Listed only feature 76 | if newlyListedOnly: 77 | if int(configManager.period[:-1]) > 250: 78 | period = '250d' 79 | else: 80 | period = configManager.period 81 | 82 | if (self.stockDict.get(stock) is None) or (configManager.cacheEnabled is False) or self.isTradingTime or downloadOnly: 83 | try: 84 | data, backtestReport = fetcher.fetchStockData(stock, 85 | period, 86 | configManager.duration, 87 | self.proxyServer, 88 | self.screenResultsCounter, 89 | self.screenCounter, 90 | totalSymbols, 91 | backtestDate=backtestDate, 92 | tickerOption=tickerOption) 93 | except Exception as e: 94 | return screeningDictionary, saveDictionary 95 | if configManager.cacheEnabled is True and not self.isTradingTime and (self.stockDict.get(stock) is None) or downloadOnly: 96 | self.stockDict[stock] = data.to_dict('split') 97 | if downloadOnly: 98 | raise Screener.DownloadDataOnly 99 | else: 100 | if printCounter: 101 | try: 102 | print(colorText.BOLD + colorText.GREEN + ("[%d%%] Screened %d, Found %d. Fetching data & Analyzing %s..." % ( 103 | int((self.screenCounter.value / totalSymbols) * 100), self.screenCounter.value, self.screenResultsCounter.value, stock)) + colorText.END, end='') 104 | print(colorText.BOLD + colorText.GREEN + "=> Done!" + 105 | colorText.END, end='\r', flush=True) 106 | except ZeroDivisionError: 107 | pass 108 | sys.stdout.write("\r\033[K") 109 | data = self.stockDict.get(stock) 110 | data = pd.DataFrame( 111 | data['data'], columns=data['columns'], index=data['index']) 112 | 113 | fullData, processedData = screener.preprocessData( 114 | data, daysToLookback=configManager.daysToLookback) 115 | 116 | if type(vectorSearch) != bool and type(vectorSearch) and vectorSearch[2] == True: 117 | executeOption = 0 118 | with self.screenCounter.get_lock(): 119 | screener.addVector(fullData, stock, vectorSearch[1]) 120 | 121 | if newlyListedOnly: 122 | if not screener.validateNewlyListed(fullData, period): 123 | raise Screener.NotNewlyListed 124 | 125 | with self.screenCounter.get_lock(): 126 | self.screenCounter.value += 1 127 | if not processedData.empty: 128 | urlStock = None 129 | if tickerOption == 16: 130 | urlStock = deepcopy(stock).replace('^','').replace('.NS','') 131 | stock = fetcher.getAllNiftyIndices()[stock] 132 | stock = stock.replace('^','').replace('.NS','') 133 | urlStock = stock.replace('&','_') if urlStock is None else urlStock.replace('&','_') 134 | screeningDictionary['Stock'] = colorText.BOLD + \ 135 | colorText.BLUE + f'\x1B]8;;https://in.tradingview.com/chart?symbol=NSE%3A{urlStock}\x1B\\{stock}\x1B]8;;\x1B\\' + colorText.END if tickerOption < 15 \ 136 | else colorText.BOLD + \ 137 | colorText.BLUE + f'\x1B]8;;https://in.tradingview.com/chart?symbol={urlStock}\x1B\\{stock}\x1B]8;;\x1B\\' + colorText.END 138 | saveDictionary['Stock'] = stock 139 | 140 | consolidationValue = screener.validateConsolidation( 141 | processedData, screeningDictionary, saveDictionary, percentage=configManager.consolidationPercentage) 142 | isMaReversal = screener.validateMovingAverages( 143 | processedData, screeningDictionary, saveDictionary, maRange=1.25) 144 | isVolumeHigh = screener.validateVolume( 145 | processedData, screeningDictionary, saveDictionary, volumeRatio=configManager.volumeRatio) 146 | isBreaking = screener.findBreakout( 147 | processedData, screeningDictionary, saveDictionary, daysToLookback=configManager.daysToLookback) 148 | isLtpValid = screener.validateLTP( 149 | fullData, screeningDictionary, saveDictionary, minLTP=configManager.minLTP, maxLTP=configManager.maxLTP) 150 | if executeOption == 4: 151 | isLowestVolume = screener.validateLowestVolume(processedData, daysForLowestVolume) 152 | else: 153 | isLowestVolume = False 154 | isValidRsi = screener.validateRSI( 155 | processedData, screeningDictionary, saveDictionary, minRSI, maxRSI) 156 | try: 157 | with SuppressOutput(suppress_stderr=True, suppress_stdout=True): 158 | currentTrend = screener.findTrend( 159 | processedData, 160 | screeningDictionary, 161 | saveDictionary, 162 | daysToLookback=configManager.daysToLookback, 163 | stockName=stock) 164 | except np.RankWarning: 165 | screeningDictionary['Trend'] = 'Unknown' 166 | saveDictionary['Trend'] = 'Unknown' 167 | 168 | with SuppressOutput(suppress_stderr=True, suppress_stdout=True): 169 | isCandlePattern = candlePatterns.findPattern( 170 | processedData, screeningDictionary, saveDictionary) 171 | 172 | isConfluence = False 173 | isInsideBar = False 174 | isIpoBase = False 175 | if newlyListedOnly: 176 | isIpoBase = screener.validateIpoBase(stock, fullData, screeningDictionary, saveDictionary) 177 | if respChartPattern == 3 and executeOption == 7: 178 | isConfluence = screener.validateConfluence(stock, processedData, screeningDictionary, saveDictionary, percentage=insideBarToLookback) 179 | else: 180 | isInsideBar = screener.validateInsideBar(processedData, screeningDictionary, saveDictionary, chartPattern=respChartPattern, daysToLookback=insideBarToLookback) 181 | 182 | with SuppressOutput(suppress_stderr=True, suppress_stdout=True): 183 | if maLength is not None and executeOption == 6 and reversalOption == 6: 184 | isNR = screener.validateNarrowRange(processedData, screeningDictionary, saveDictionary, nr=maLength) 185 | else: 186 | isNR = screener.validateNarrowRange(processedData, screeningDictionary, saveDictionary) 187 | 188 | isMomentum = screener.validateMomentum(processedData, screeningDictionary, saveDictionary) 189 | 190 | isVSA = False 191 | if not (executeOption == 7 and respChartPattern < 3): 192 | isVSA = screener.validateVolumeSpreadAnalysis(processedData, screeningDictionary, saveDictionary) 193 | if maLength is not None and executeOption == 6 and reversalOption == 4: 194 | isMaSupport = screener.findReversalMA(fullData, screeningDictionary, saveDictionary, maLength) 195 | if executeOption == 6 and reversalOption == 8: 196 | isRsiReversal = screener.findRSICrossingMA(fullData, screeningDictionary, saveDictionary) 197 | 198 | isVCP = False 199 | if respChartPattern == 4: 200 | with SuppressOutput(suppress_stderr=True, suppress_stdout=True): 201 | isVCP = screener.validateVCP(fullData, screeningDictionary, saveDictionary) 202 | 203 | isBuyingTrendline = False 204 | if executeOption == 7 and respChartPattern == 5: 205 | with SuppressOutput(suppress_stderr=True, suppress_stdout=True): 206 | isBuyingTrendline = screener.findTrendlines(fullData, screeningDictionary, saveDictionary) 207 | 208 | with SuppressOutput(suppress_stderr=True, suppress_stdout=True): 209 | isLorentzian = screener.validateLorentzian(fullData, screeningDictionary, saveDictionary, lookFor = maLength) 210 | 211 | try: 212 | backtestReport = Utility.tools.calculateBacktestReport(data=processedData, backtestDict=backtestReport) 213 | screeningDictionary.update(backtestReport) 214 | saveDictionary.update(backtestReport) 215 | except: 216 | pass 217 | 218 | with self.screenResultsCounter.get_lock(): 219 | if executeOption == 0: 220 | self.screenResultsCounter.value += 1 221 | return screeningDictionary, saveDictionary 222 | if (executeOption == 1 or executeOption == 2) and isBreaking and isVolumeHigh and isLtpValid: 223 | self.screenResultsCounter.value += 1 224 | return screeningDictionary, saveDictionary 225 | if (executeOption == 1 or executeOption == 3) and (consolidationValue <= configManager.consolidationPercentage and consolidationValue != 0) and isLtpValid: 226 | self.screenResultsCounter.value += 1 227 | return screeningDictionary, saveDictionary 228 | if executeOption == 4 and isLtpValid and isLowestVolume: 229 | self.screenResultsCounter.value += 1 230 | return screeningDictionary, saveDictionary 231 | if executeOption == 5 and isLtpValid and isValidRsi: 232 | self.screenResultsCounter.value += 1 233 | return screeningDictionary, saveDictionary 234 | if executeOption == 6 and isLtpValid: 235 | if reversalOption == 1: 236 | if saveDictionary['Pattern'] in CandlePatterns.reversalPatternsBullish or isMaReversal > 0 or 'buy' in saveDictionary['Pattern'].lower(): 237 | self.screenResultsCounter.value += 1 238 | return screeningDictionary, saveDictionary 239 | elif reversalOption == 2: 240 | if saveDictionary['Pattern'] in CandlePatterns.reversalPatternsBearish or isMaReversal < 0 or 'sell' in saveDictionary['Pattern'].lower(): 241 | self.screenResultsCounter.value += 1 242 | return screeningDictionary, saveDictionary 243 | elif reversalOption == 3 and isMomentum: 244 | self.screenResultsCounter.value += 1 245 | return screeningDictionary, saveDictionary 246 | elif reversalOption == 4 and isMaSupport: 247 | self.screenResultsCounter.value += 1 248 | return screeningDictionary, saveDictionary 249 | elif reversalOption == 5 and isVSA and saveDictionary['Pattern'] in CandlePatterns.reversalPatternsBullish: 250 | self.screenResultsCounter.value += 1 251 | return screeningDictionary, saveDictionary 252 | elif reversalOption == 6 and isNR: 253 | self.screenResultsCounter.value += 1 254 | return screeningDictionary, saveDictionary 255 | elif reversalOption == 7 and isLorentzian: 256 | self.screenResultsCounter.value += 1 257 | return screeningDictionary, saveDictionary 258 | elif reversalOption == 8 and isRsiReversal: 259 | self.screenResultsCounter.value += 1 260 | return screeningDictionary, saveDictionary 261 | if executeOption == 7 and isLtpValid: 262 | if respChartPattern < 3 and isInsideBar: 263 | self.screenResultsCounter.value += 1 264 | return screeningDictionary, saveDictionary 265 | if isConfluence: 266 | self.screenResultsCounter.value += 1 267 | return screeningDictionary, saveDictionary 268 | if isIpoBase and newlyListedOnly and not respChartPattern < 3: 269 | self.screenResultsCounter.value += 1 270 | return screeningDictionary, saveDictionary 271 | if isVCP: 272 | self.screenResultsCounter.value += 1 273 | return screeningDictionary, saveDictionary 274 | if isBuyingTrendline: 275 | self.screenResultsCounter.value += 1 276 | return screeningDictionary, saveDictionary 277 | except KeyboardInterrupt: 278 | # Capturing Ctr+C Here isn't a great idea 279 | pass 280 | except Fetcher.StockDataEmptyException: 281 | pass 282 | except Screener.NotNewlyListed: 283 | pass 284 | except Screener.DownloadDataOnly: 285 | pass 286 | except KeyError: 287 | pass 288 | except Exception as e: 289 | if isDevVersion: 290 | print("[!] Dev Traceback:") 291 | traceback.print_exc() 292 | if printCounter: 293 | print(colorText.FAIL + 294 | ("\n[+] Exception Occured while Screening %s! Skipping this stock.." % stock) + colorText.END) 295 | return 296 | 297 | def multiprocessingForWindows(self): 298 | if sys.platform.startswith('win'): 299 | 300 | class _Popen(forking.Popen): 301 | def __init__(self, *args, **kw): 302 | if hasattr(sys, 'frozen'): 303 | os.putenv('_MEIPASS2', sys._MEIPASS) 304 | try: 305 | super(_Popen, self).__init__(*args, **kw) 306 | finally: 307 | if hasattr(sys, 'frozen'): 308 | if hasattr(os, 'unsetenv'): 309 | os.unsetenv('_MEIPASS2') 310 | else: 311 | os.putenv('_MEIPASS2', '') 312 | 313 | forking.Popen = _Popen 314 | -------------------------------------------------------------------------------- /src/classes/ScreenipyTA.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import os 3 | 4 | if 'STREAMLIT_APP' in os.environ: 5 | import pandas_ta as talib 6 | print('[+] Importing pandas_ta as we are running on Streamlit cloud app') 7 | else: 8 | try: 9 | import talib 10 | except ImportError: 11 | import pandas_ta as talib 12 | 13 | 14 | class ScreenerTA: 15 | 16 | @staticmethod 17 | def EMA(close, timeperiod): 18 | try: 19 | return talib.ema(close,timeperiod) 20 | except Exception as e: 21 | return talib.EMA(close.to_numpy().reshape(-1),timeperiod) 22 | 23 | @staticmethod 24 | def SMA(close, timeperiod): 25 | try: 26 | return talib.sma(close,timeperiod) 27 | except Exception as e: 28 | return talib.SMA(close.to_numpy().reshape(-1),timeperiod) 29 | 30 | @staticmethod 31 | def MA(close, timeperiod): 32 | try: 33 | return talib.ma(close,timeperiod) 34 | except Exception as e: 35 | return talib.MA(close.to_numpy().reshape(-1),timeperiod) 36 | 37 | @staticmethod 38 | def MACD(close, fast, slow, signal): 39 | try: 40 | return talib.macd(close,fast,slow,signal) 41 | except Exception as e: 42 | return talib.MACD(close.to_numpy().reshape(-1),fast.to_numpy().reshape(-1),slow.to_numpy().reshape(-1),signal.to_numpy().reshape(-1)) 43 | 44 | @staticmethod 45 | def RSI(close, timeperiod): 46 | try: 47 | return talib.rsi(close,timeperiod) 48 | except Exception as e: 49 | return talib.RSI(close.to_numpy().reshape(-1),timeperiod) 50 | 51 | @staticmethod 52 | def CCI(high, low, close, timeperiod): 53 | try: 54 | return talib.cci(high, low, close,timeperiod) 55 | except Exception as e: 56 | return talib.CCI(high.to_numpy().reshape(-1), low.to_numpy().reshape(-1), close.to_numpy().reshape(-1),timeperiod) 57 | 58 | 59 | @staticmethod 60 | def CDLMORNINGSTAR(open, high, low, close): 61 | try: 62 | try: 63 | return talib.cdl_pattern(open,high,low,close,'morningstar').tail(1).values[0][0] != 0 64 | except Exception as e: 65 | 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 66 | except AttributeError: 67 | return False 68 | 69 | @staticmethod 70 | def CDLMORNINGDOJISTAR(open, high, low, close): 71 | try: 72 | try: 73 | return talib.cdl_pattern(open,high,low,close,'morningdojistar').tail(1).values[0][0] != 0 74 | except Exception as e: 75 | 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 76 | except AttributeError: 77 | return False 78 | 79 | @staticmethod 80 | def CDLEVENINGSTAR(open, high, low, close): 81 | try: 82 | try: 83 | return talib.cdl_pattern(open,high,low,close,'eveningstar').tail(1).values[0][0] != 0 84 | except Exception as e: 85 | 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 86 | except AttributeError: 87 | return False 88 | 89 | @staticmethod 90 | def CDLEVENINGDOJISTAR(open, high, low, close): 91 | try: 92 | try: 93 | return talib.cdl_pattern(open,high,low,close,'eveningdojistar').tail(1).values[0][0] != 0 94 | except Exception as e: 95 | 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 96 | except AttributeError: 97 | return False 98 | 99 | @staticmethod 100 | def CDLLADDERBOTTOM(open, high, low, close): 101 | try: 102 | try: 103 | return talib.cdl_pattern(open,high,low,close,'ladderbottom').tail(1).values[0][0] != 0 104 | except Exception as e: 105 | 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 106 | except AttributeError: 107 | return False 108 | 109 | @staticmethod 110 | def CDL3LINESTRIKE(open, high, low, close): 111 | try: 112 | try: 113 | return talib.cdl_pattern(open,high,low,close,'3linestrike').tail(1).values[0][0] != 0 114 | except Exception as e: 115 | 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 116 | except AttributeError: 117 | return False 118 | 119 | @staticmethod 120 | def CDL3BLACKCROWS(open, high, low, close): 121 | try: 122 | try: 123 | return talib.cdl_pattern(open,high,low,close,'3blackcrows').tail(1).values[0][0] != 0 124 | except Exception as e: 125 | 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 126 | except AttributeError: 127 | return False 128 | 129 | @staticmethod 130 | def CDL3INSIDE(open, high, low, close): 131 | try: 132 | try: 133 | return talib.cdl_pattern(open,high,low,close,'3inside').tail(1).values[0][0] != 0 134 | except Exception as e: 135 | 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 136 | except AttributeError: 137 | return False 138 | 139 | @staticmethod 140 | def CDL3OUTSIDE(open, high, low, close): 141 | try: 142 | try: 143 | return talib.cdl_pattern(open,high,low,close,'3outside').tail(1).values[0][0] 144 | except Exception as e: 145 | 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 146 | except AttributeError: 147 | return False 148 | 149 | @staticmethod 150 | def CDL3WHITESOLDIERS(open, high, low, close): 151 | try: 152 | try: 153 | return talib.cdl_pattern(open,high,low,close,'3whitesoldiers').tail(1).values[0][0] != 0 154 | except Exception as e: 155 | 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 156 | except AttributeError: 157 | return False 158 | 159 | @staticmethod 160 | def CDLHARAMI(open, high, low, close): 161 | try: 162 | try: 163 | return talib.cdl_pattern(open,high,low,close,'harami').tail(1).values[0][0] != 0 164 | except Exception as e: 165 | 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 166 | except AttributeError: 167 | return False 168 | 169 | @staticmethod 170 | def CDLHARAMICROSS(open, high, low, close): 171 | try: 172 | try: 173 | return talib.cdl_pattern(open,high,low,close,'haramicross').tail(1).values[0][0] != 0 174 | except Exception as e: 175 | 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 176 | except AttributeError: 177 | return False 178 | 179 | @staticmethod 180 | def CDLMARUBOZU(open, high, low, close): 181 | try: 182 | try: 183 | return talib.cdl_pattern(open,high,low,close,'marubozu').tail(1).values[0][0] != 0 184 | except Exception as e: 185 | 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 186 | except AttributeError: 187 | return False 188 | 189 | @staticmethod 190 | def CDLHANGINGMAN(open, high, low, close): 191 | try: 192 | try: 193 | return talib.cdl_pattern(open,high,low,close,'hangingman').tail(1).values[0][0] != 0 194 | except Exception as e: 195 | 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 196 | except AttributeError: 197 | return False 198 | 199 | @staticmethod 200 | def CDLHAMMER(open, high, low, close): 201 | try: 202 | try: 203 | return talib.cdl_pattern(open,high,low,close,'hammer').tail(1).values[0][0] != 0 204 | except Exception as e: 205 | 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 206 | except AttributeError: 207 | return False 208 | 209 | @staticmethod 210 | def CDLINVERTEDHAMMER(open, high, low, close): 211 | try: 212 | try: 213 | return talib.cdl_pattern(open,high,low,close,'invertedhammer').tail(1).values[0][0] != 0 214 | except Exception as e: 215 | 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 216 | except AttributeError: 217 | return False 218 | 219 | @staticmethod 220 | def CDLSHOOTINGSTAR(open, high, low, close): 221 | try: 222 | try: 223 | return talib.cdl_pattern(open,high,low,close,'shootingstar').tail(1).values[0][0] != 0 224 | except Exception as e: 225 | 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 226 | except AttributeError: 227 | return False 228 | 229 | @staticmethod 230 | def CDLDRAGONFLYDOJI(open, high, low, close): 231 | try: 232 | try: 233 | return talib.cdl_pattern(open,high,low,close,'dragonflydoji').tail(1).values[0][0] != 0 234 | except Exception as e: 235 | 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 236 | except AttributeError: 237 | return False 238 | 239 | @staticmethod 240 | def CDLGRAVESTONEDOJI(open, high, low, close): 241 | try: 242 | try: 243 | return talib.cdl_pattern(open,high,low,close,'gravestonedoji').tail(1).values[0][0] != 0 244 | except Exception as e: 245 | 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 246 | except AttributeError: 247 | return False 248 | 249 | @staticmethod 250 | def CDLDOJI(open, high, low, close): 251 | try: 252 | try: 253 | return talib.cdl_pattern(open,high,low,close,'doji').tail(1).values[0][0] != 0 254 | except Exception as e: 255 | 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 256 | except AttributeError: 257 | return False 258 | 259 | 260 | @staticmethod 261 | def CDLENGULFING(open, high, low, close): 262 | try: 263 | try: 264 | return talib.cdl_pattern(open,high,low,close,'engulfing').tail(1).values[0][0] 265 | except Exception as e: 266 | 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 267 | except AttributeError: 268 | return False 269 | -------------------------------------------------------------------------------- /src/classes/SuppressOutput.py: -------------------------------------------------------------------------------- 1 | ''' 2 | * Project : Screenipy 3 | * Author : Pranjal Joshi 4 | * Created : 07/05/2021 5 | * Description : Class for supressing stdout & stderr 6 | ''' 7 | 8 | 9 | import os, sys 10 | 11 | class SuppressOutput: 12 | def __init__(self,suppress_stdout=False,suppress_stderr=False): 13 | self.suppress_stdout = suppress_stdout 14 | self.suppress_stderr = suppress_stderr 15 | self._stdout = None 16 | self._stderr = None 17 | def __enter__(self): 18 | devnull = open(os.devnull, "w") 19 | if self.suppress_stdout: 20 | self._stdout = sys.stdout 21 | sys.stdout = devnull 22 | if self.suppress_stderr: 23 | self._stderr = sys.stderr 24 | sys.stderr = devnull 25 | def __exit__(self, *args): 26 | if self.suppress_stdout: 27 | sys.stdout = self._stdout 28 | if self.suppress_stderr: 29 | sys.stderr = self._stderr -------------------------------------------------------------------------------- /src/classes/Utility.py: -------------------------------------------------------------------------------- 1 | ''' 2 | * Project : Screenipy 3 | * Author : Pranjal Joshi 4 | * Created : 28/04/2021 5 | * Description : Class for managing misc and utility methods 6 | ''' 7 | 8 | import os 9 | import sys 10 | import platform 11 | import datetime 12 | import pytz 13 | import pickle 14 | import requests 15 | import time 16 | import joblib 17 | import keras 18 | import pandas as pd 19 | from alive_progress import alive_bar 20 | from tabulate import tabulate 21 | from time import sleep 22 | from classes.ColorText import colorText 23 | from classes.Changelog import VERSION, changelog 24 | import classes.ConfigManager as ConfigManager 25 | 26 | art = colorText.GREEN + ''' 27 | .d8888b. d8b 28 | d88P Y88b Y8P 29 | Y88b. 30 | "Y888b. .d8888b 888d888 .d88b. .d88b. 88888b. 888 88888b. 888 888 31 | "Y88b. d88P" 888P" d8P Y8b d8P Y8b 888 "88b 888 888 "88b 888 888 32 | "888 888 888 88888888 88888888 888 888 888 888 888 888 888 33 | Y88b d88P Y88b. 888 Y8b. Y8b. 888 888 888 888 d88P Y88b 888 34 | "Y8888P" "Y8888P 888 "Y8888 "Y8888 888 888 888 88888P" "Y88888 35 | 888 888 36 | 888 Y8b d88P 37 | 888 "Y88P" 38 | 39 | ''' + colorText.END 40 | 41 | lastScreened = 'last_screened_results.pkl' 42 | lastScreenedUnformatted = 'last_screened_unformatted_results.pkl' 43 | 44 | # Class for managing misc and utility methods 45 | 46 | 47 | class tools: 48 | 49 | def clearScreen(): 50 | if platform.system() == 'Windows': 51 | os.system('cls') 52 | else: 53 | os.system('clear') 54 | print(art) 55 | 56 | # Print about developers and repository 57 | def showDevInfo(): 58 | print('\n'+changelog) 59 | print(colorText.BOLD + colorText.WARN + 60 | "\n[+] Developer: Pranjal Joshi." + colorText.END) 61 | print(colorText.BOLD + colorText.WARN + 62 | ("[+] Version: %s" % VERSION) + colorText.END) 63 | print(colorText.BOLD + 64 | "[+] Home Page: https://github.com/pranjal-joshi/Screeni-py" + colorText.END) 65 | print(colorText.BOLD + colorText.FAIL + 66 | "[+] Read/Post Issues here: https://github.com/pranjal-joshi/Screeni-py/issues" + colorText.END) 67 | print(colorText.BOLD + colorText.GREEN + 68 | "[+] Join Community Discussions: https://github.com/pranjal-joshi/Screeni-py/discussions" + colorText.END) 69 | print(colorText.BOLD + colorText.BLUE + 70 | "[+] Download latest software from https://github.com/pranjal-joshi/Screeni-py/releases/latest" + colorText.END) 71 | input('') 72 | 73 | # Save last screened result to pickle file 74 | def setLastScreenedResults(df, unformatted=False): 75 | try: 76 | if not unformatted: 77 | df.sort_values(by=['Stock'], ascending=True, inplace=True) 78 | df.to_pickle(lastScreened) 79 | else: 80 | df.sort_values(by=['Stock'], ascending=True, inplace=True) 81 | df.to_pickle(lastScreenedUnformatted) 82 | except IOError: 83 | print(colorText.BOLD + colorText.FAIL + 84 | '[+] Failed to save recently screened result table on disk! Skipping..' + colorText.END) 85 | 86 | # Load last screened result to pickle file 87 | def getLastScreenedResults(): 88 | try: 89 | df = pd.read_pickle(lastScreened) 90 | print(colorText.BOLD + colorText.GREEN + 91 | '\n[+] Showing recently screened results..\n' + colorText.END) 92 | print(tabulate(df, headers='keys', tablefmt='psql')) 93 | print(colorText.BOLD + colorText.WARN + 94 | "[+] Note: Trend calculation is based on number of recent days to screen as per your configuration." + colorText.END) 95 | input(colorText.BOLD + colorText.GREEN + 96 | '[+] Press any key to continue..' + colorText.END) 97 | except FileNotFoundError: 98 | print(colorText.BOLD + colorText.FAIL + 99 | '[+] Failed to load recently screened result table from disk! Skipping..' + colorText.END) 100 | 101 | def isTradingTime(): 102 | curr = datetime.datetime.now(pytz.timezone('Asia/Kolkata')) 103 | openTime = curr.replace(hour=9, minute=15) 104 | closeTime = curr.replace(hour=15, minute=30) 105 | return ((openTime <= curr <= closeTime) and (0 <= curr.weekday() <= 4)) 106 | 107 | def isClosingHour(): 108 | curr = datetime.datetime.now(pytz.timezone('Asia/Kolkata')) 109 | openTime = curr.replace(hour=15, minute=00) 110 | closeTime = curr.replace(hour=15, minute=30) 111 | return ((openTime <= curr <= closeTime) and (0 <= curr.weekday() <= 4)) 112 | 113 | def saveStockData(stockDict, configManager, loadCount): 114 | curr = datetime.datetime.now(pytz.timezone('Asia/Kolkata')) 115 | openTime = curr.replace(hour=9, minute=15) 116 | cache_date = datetime.date.today() # for monday to friday 117 | weekday = datetime.date.today().weekday() 118 | if curr < openTime: # for monday to friday before 9:15 119 | cache_date = datetime.datetime.today() - datetime.timedelta(1) 120 | if weekday == 0 and curr < openTime: # for monday before 9:15 121 | cache_date = datetime.datetime.today() - datetime.timedelta(3) 122 | if weekday == 5 or weekday == 6: # for saturday and sunday 123 | cache_date = datetime.datetime.today() - datetime.timedelta(days=weekday - 4) 124 | cache_date = cache_date.strftime("%d%m%y") 125 | cache_file = "stock_data_" + str(cache_date) + ".pkl" 126 | configManager.deleteStockData(excludeFile=cache_file) 127 | 128 | if not os.path.exists(cache_file) or len(stockDict) > (loadCount+1): 129 | with open(cache_file, 'wb') as f: 130 | try: 131 | pickle.dump(stockDict.copy(), f) 132 | print(colorText.BOLD + colorText.GREEN + 133 | "=> Done." + colorText.END) 134 | except pickle.PicklingError: 135 | print(colorText.BOLD + colorText.FAIL + 136 | "=> Error while Caching Stock Data." + colorText.END) 137 | else: 138 | print(colorText.BOLD + colorText.GREEN + 139 | "=> Already Cached." + colorText.END) 140 | 141 | def loadStockData(stockDict, configManager, proxyServer=None): 142 | curr = datetime.datetime.now(pytz.timezone('Asia/Kolkata')) 143 | openTime = curr.replace(hour=9, minute=15) 144 | last_cached_date = datetime.date.today() # for monday to friday after 3:30 145 | weekday = datetime.date.today().weekday() 146 | if curr < openTime: # for monday to friday before 9:15 147 | last_cached_date = datetime.datetime.today() - datetime.timedelta(1) 148 | if weekday == 5 or weekday == 6: # for saturday and sunday 149 | last_cached_date = datetime.datetime.today() - datetime.timedelta(days=weekday - 4) 150 | if weekday == 0 and curr < openTime: # for monday before 9:15 151 | last_cached_date = datetime.datetime.today() - datetime.timedelta(3) 152 | last_cached_date = last_cached_date.strftime("%d%m%y") 153 | cache_file = "stock_data_" + str(last_cached_date) + ".pkl" 154 | if os.path.exists(cache_file): 155 | with open(cache_file, 'rb') as f: 156 | try: 157 | stockData = pickle.load(f) 158 | print(colorText.BOLD + colorText.GREEN + 159 | "[+] Automatically Using Cached Stock Data due to After-Market hours!" + colorText.END) 160 | for stock in stockData: 161 | stockDict[stock] = stockData.get(stock) 162 | except pickle.UnpicklingError: 163 | print(colorText.BOLD + colorText.FAIL + 164 | "[+] Error while Reading Stock Cache." + colorText.END) 165 | except EOFError: 166 | print(colorText.BOLD + colorText.FAIL + 167 | "[+] Stock Cache Corrupted." + colorText.END) 168 | elif ConfigManager.default_period == configManager.period and ConfigManager.default_duration == configManager.duration: 169 | cache_url = "https://raw.github.com/pranjal-joshi/Screeni-py/actions-data-download/actions-data-download/" + cache_file 170 | if proxyServer is not None: 171 | resp = requests.get(cache_url, stream=True, proxies={'https':proxyServer}) 172 | else: 173 | resp = requests.get(cache_url, stream=True) 174 | if resp.status_code == 200: 175 | print(colorText.BOLD + colorText.FAIL + 176 | "[+] After-Market Stock Data is not cached.." + colorText.END) 177 | print(colorText.BOLD + colorText.GREEN + 178 | "[+] Downloading cache from Screenipy server for faster processing, Please Wait.." + colorText.END) 179 | try: 180 | chunksize = 1024*1024*1 181 | filesize = int(int(resp.headers.get('content-length'))/chunksize) 182 | bar, spinner = tools.getProgressbarStyle() 183 | f = open(cache_file, 'wb') 184 | dl = 0 185 | with alive_bar(filesize, bar=bar, spinner=spinner, manual=True) as progressbar: 186 | for data in resp.iter_content(chunk_size=chunksize): 187 | dl += 1 188 | f.write(data) 189 | progressbar(dl/filesize) 190 | if dl >= filesize: 191 | progressbar(1.0) 192 | f.close() 193 | except Exception as e: 194 | print("[!] Download Error - " + str(e)) 195 | print("") 196 | tools.loadStockData(stockDict, configManager, proxyServer) 197 | else: 198 | print(colorText.BOLD + colorText.FAIL + 199 | "[+] Cache unavailable on Screenipy server, Continuing.." + colorText.END) 200 | 201 | # Save screened results to excel 202 | def promptSaveResults(df): 203 | if isDocker() or isGui(): # Skip export to excel inside docker 204 | return 205 | try: 206 | response = str(input(colorText.BOLD + colorText.WARN + 207 | '[>] Do you want to save the results in excel file? [Y/N]: ')).upper() 208 | except ValueError: 209 | response = 'Y' 210 | if response != 'N': 211 | filename = 'screenipy-result_' + \ 212 | datetime.datetime.now().strftime("%d-%m-%y_%H.%M.%S")+".xlsx" 213 | df.to_excel(filename) 214 | print(colorText.BOLD + colorText.GREEN + 215 | ("[+] Results saved to %s" % filename) + colorText.END) 216 | 217 | # Prompt for asking RSI 218 | def promptRSIValues(): 219 | try: 220 | minRSI, maxRSI = int(input(colorText.BOLD + colorText.WARN + "\n[+] Enter Min RSI value: " + colorText.END)), int( 221 | input(colorText.BOLD + colorText.WARN + "[+] Enter Max RSI value: " + colorText.END)) 222 | if (minRSI >= 0 and minRSI <= 100) and (maxRSI >= 0 and maxRSI <= 100) and (minRSI <= maxRSI): 223 | return (minRSI, maxRSI) 224 | raise ValueError 225 | except ValueError: 226 | return (0, 0) 227 | 228 | # Prompt for Reversal screening 229 | def promptReversalScreening(): 230 | try: 231 | resp = int(input(colorText.BOLD + colorText.WARN + """\n[+] Select Option: 232 | 1 > Screen for Buy Signal (Bullish Reversal) 233 | 2 > Screen for Sell Signal (Bearish Reversal) 234 | 3 > Screen for Momentum Gainers (Rising Bullish Momentum) 235 | 4 > Screen for Reversal at Moving Average (Bullish Reversal) 236 | 5 > Screen for Volume Spread Analysis (Bullish VSA Reversal) 237 | 6 > Screen for Narrow Range (NRx) Reversal 238 | 7 > Screen for Reversal using Lorentzian Classifier (Machine Learning based indicator) 239 | 8 > Screen for Reversal using RSI MA Crossing 240 | 0 > Cancel 241 | [+] Select option: """ + colorText.END)) 242 | if resp >= 0 and resp <= 8: 243 | if resp == 4: 244 | try: 245 | maLength = int(input(colorText.BOLD + colorText.WARN + 246 | '\n[+] Enter MA Length (E.g. 50 or 200): ' + colorText.END)) 247 | return resp, maLength 248 | except ValueError: 249 | print(colorText.BOLD + colorText.FAIL + 250 | '\n[!] Invalid Input! MA Lenght should be single integer value!\n' + colorText.END) 251 | raise ValueError 252 | elif resp == 6: 253 | try: 254 | maLength = int(input(colorText.BOLD + colorText.WARN + 255 | '\n[+] Enter NR timeframe [Integer Number] (E.g. 4, 7, etc.): ' + colorText.END)) 256 | return resp, maLength 257 | except ValueError: 258 | print(colorText.BOLD + colorText.FAIL + '\n[!] Invalid Input! NR timeframe should be single integer value!\n' + colorText.END) 259 | raise ValueError 260 | elif resp == 7: 261 | try: 262 | return resp, 1 263 | except ValueError: 264 | print(colorText.BOLD + colorText.FAIL + '\n[!] Invalid Input! Select valid Signal Type!\n' + colorText.END) 265 | raise ValueError 266 | elif resp == 8: 267 | maLength = 9 268 | return resp, maLength 269 | return resp, None 270 | raise ValueError 271 | except ValueError: 272 | return None, None 273 | 274 | # Prompt for Reversal screening 275 | def promptChartPatterns(): 276 | try: 277 | resp = int(input(colorText.BOLD + colorText.WARN + """\n[+] Select Option: 278 | 1 > Screen for Bullish Inside Bar (Flag) Pattern 279 | 2 > Screen for Bearish Inside Bar (Flag) Pattern 280 | 3 > Screen for the Confluence (50 & 200 MA/EMA) 281 | 4 > Screen for VCP (Experimental) 282 | 5 > Screen for Buying at Trendline (Ideal for Swing/Mid/Long term) 283 | 0 > Cancel 284 | [+] Select option: """ + colorText.END)) 285 | if resp == 1 or resp == 2: 286 | candles = int(input(colorText.BOLD + colorText.WARN + 287 | "\n[+] How many candles (TimeFrame) to look back Inside Bar formation? : " + colorText.END)) 288 | return (resp, candles) 289 | if resp == 3: 290 | percent = float(input(colorText.BOLD + colorText.WARN + 291 | "\n[+] Enter Percentage within which all MA/EMAs should be (Ideal: 1-2%)? : " + colorText.END)) 292 | return (resp, percent/100.0) 293 | if resp >= 0 and resp <= 5: 294 | return resp, 0 295 | raise ValueError 296 | except ValueError: 297 | input(colorText.BOLD + colorText.FAIL + 298 | "\n[+] Invalid Option Selected. Press Any Key to Continue..." + colorText.END) 299 | return (None, None) 300 | 301 | # Prompt for Similar stock search 302 | def promptSimilarStockSearch(): 303 | try: 304 | stockCode = str(input(colorText.BOLD + colorText.WARN + 305 | "\n[+] Enter the Name of the stock to search similar stocks for: " + colorText.END)).upper() 306 | candles = int(input(colorText.BOLD + colorText.WARN + 307 | "\n[+] How many candles (TimeFrame) to look back for similarity? : " + colorText.END)) 308 | return stockCode, candles 309 | except ValueError: 310 | input(colorText.BOLD + colorText.FAIL + 311 | "\n[+] Invalid Option Selected. Press Any Key to Continue..." + colorText.END) 312 | return None, None 313 | 314 | def getProgressbarStyle(): 315 | bar = 'smooth' 316 | spinner = 'waves' 317 | if 'Windows' in platform.platform(): 318 | bar = 'classic2' 319 | spinner = 'dots_recur' 320 | return bar, spinner 321 | 322 | def getNiftyModel(proxyServer=None): 323 | files = ['nifty_model_v3.h5', 'nifty_model_v3.pkl'] 324 | urls = [ 325 | f"https://raw.github.com/pranjal-joshi/Screeni-py/new-features/src/ml/{files[0]}", 326 | f"https://raw.github.com/pranjal-joshi/Screeni-py/new-features/src/ml/{files[1]}" 327 | ] 328 | if os.path.isfile(files[0]) and os.path.isfile(files[1]): 329 | file_age = (time.time() - os.path.getmtime(files[0]))/604800 330 | if file_age > 1: 331 | download = True 332 | os.remove(files[0]) 333 | os.remove(files[1]) 334 | else: 335 | download = False 336 | else: 337 | download = True 338 | if download: 339 | for file_url in urls: 340 | if proxyServer is not None: 341 | resp = requests.get(file_url, stream=True, proxies={'https':proxyServer}) 342 | else: 343 | resp = requests.get(file_url, stream=True) 344 | if resp.status_code == 200: 345 | print(colorText.BOLD + colorText.GREEN + 346 | "[+] Downloading AI model (v3) for Nifty predictions, Please Wait.." + colorText.END) 347 | try: 348 | chunksize = 1024*1024*1 349 | filesize = int(int(resp.headers.get('content-length'))/chunksize) 350 | filesize = 1 if not filesize else filesize 351 | bar, spinner = tools.getProgressbarStyle() 352 | f = open(file_url.split('/')[-1], 'wb') 353 | dl = 0 354 | with alive_bar(filesize, bar=bar, spinner=spinner, manual=True) as progressbar: 355 | for data in resp.iter_content(chunk_size=chunksize): 356 | dl += 1 357 | f.write(data) 358 | progressbar(dl/filesize) 359 | if dl >= filesize: 360 | progressbar(1.0) 361 | f.close() 362 | except Exception as e: 363 | print("[!] Download Error - " + str(e)) 364 | time.sleep(3) 365 | model = keras.models.load_model(files[0]) 366 | pkl = joblib.load(files[1]) 367 | return model, pkl 368 | 369 | def getSigmoidConfidence(x): 370 | out_min, out_max = 0, 100 371 | if x > 0.5: 372 | in_min = 0.50001 373 | in_max = 1 374 | else: 375 | in_min = 0 376 | in_max = 0.5 377 | return round(((x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min),3) 378 | 379 | def alertSound(beeps=3, delay=0.2): 380 | for i in range(beeps): 381 | print('\a') 382 | sleep(delay) 383 | 384 | def isBacktesting(backtestDate): 385 | try: 386 | if datetime.date.today() != backtestDate: 387 | return True 388 | return False 389 | except: 390 | return False 391 | 392 | def calculateBacktestReport(data, backtestDict:dict): 393 | try: 394 | recent = data.head(1)['Close'].iloc[0] 395 | for key, val in backtestDict.copy().items(): 396 | if val is not None: 397 | try: 398 | backtestDict[key] = str(round((backtestDict[key]-recent)/recent*100,1)) + "%" 399 | except TypeError: 400 | del backtestDict[key] 401 | # backtestDict[key] = None 402 | continue 403 | else: 404 | del backtestDict[key] 405 | except: 406 | pass 407 | return backtestDict 408 | 409 | def isDocker(): 410 | if 'SCREENIPY_DOCKER' in os.environ: 411 | return True 412 | return False 413 | 414 | def isGui(): 415 | if 'SCREENIPY_GUI' in os.environ: 416 | return True 417 | return False -------------------------------------------------------------------------------- /src/icon-old.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/src/icon-old.ico -------------------------------------------------------------------------------- /src/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/src/icon.ico -------------------------------------------------------------------------------- /src/ml/best_model_0.7438acc.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/src/ml/best_model_0.7438acc.h5 -------------------------------------------------------------------------------- /src/ml/eval.py: -------------------------------------------------------------------------------- 1 | import yfinance as yf 2 | import pandas as pd 3 | import numpy as np 4 | from sklearn.preprocessing import StandardScaler, MinMaxScaler 5 | from sklearn.compose import ColumnTransformer 6 | import joblib 7 | import keras 8 | import matplotlib.pyplot as plt 9 | 10 | import tensorflow as tf 11 | physical_devices = tf.config.list_physical_devices('GPU') 12 | try: 13 | # Disable all GPUS 14 | tf.config.set_visible_devices([], 'GPU') 15 | visible_devices = tf.config.get_visible_devices() 16 | for device in visible_devices: 17 | assert device.device_type != 'GPU' 18 | except: 19 | # Invalid device or cannot modify virtual devices once initialized. 20 | pass 21 | 22 | TEST_DAYS = 50 23 | PERIOD = '5y' 24 | 25 | INCLUDE_COMMODITIES = True 26 | 27 | def preprocessBeforeScaling(df): 28 | df['High'] = df['High'].pct_change() * 100 29 | df['Low'] = df['Low'].pct_change() * 100 30 | df['Open'] = df['Open'].pct_change() * 100 31 | df['Close'] = df['Close'].pct_change() * 100 32 | 33 | if INCLUDE_COMMODITIES: 34 | df['gold_High'] = df['gold_High'].pct_change() * 100 35 | df['gold_Low'] = df['gold_Low'].pct_change() * 100 36 | df['gold_Open'] = df['gold_Open'].pct_change() * 100 37 | df['gold_Close'] = df['gold_Close'].pct_change() * 100 38 | 39 | df['crude_High'] = df['crude_High'].pct_change() * 100 40 | df['crude_Low'] = df['crude_Low'].pct_change() * 100 41 | df['crude_Open'] = df['crude_Open'].pct_change() * 100 42 | df['crude_Close'] = df['crude_Close'].pct_change() * 100 43 | return df 44 | 45 | metrics = { 46 | "TP": 0, "FP": 0, "TN": 0, "FN": 0 47 | } 48 | 49 | endpoint = keras.models.load_model('nifty_model_v3.h5') 50 | try: 51 | scaler 52 | except NameError: 53 | pkl = joblib.load('nifty_model_v3.pkl') 54 | scaler = pkl['scaler'] 55 | today = yf.download( 56 | tickers="^NSEI", 57 | period=f'{TEST_DAYS}d', 58 | interval='1d', 59 | progress=False, 60 | timeout=10 61 | ) 62 | if INCLUDE_COMMODITIES: 63 | gold = yf.download( 64 | tickers="GC=F", 65 | period=f'{TEST_DAYS}d', 66 | interval='1d', 67 | progress=False, 68 | timeout=10 69 | ).add_prefix(prefix='gold_') 70 | crude = yf.download( 71 | tickers="CL=F", 72 | period=f'{TEST_DAYS}d', 73 | interval='1d', 74 | progress=False, 75 | timeout=10 76 | ).add_prefix(prefix='crude_') 77 | 78 | today = pd.concat([today, gold, crude], axis=1) 79 | today = today.drop(columns=['Adj Close', 'Volume', 'gold_Adj Close', 'gold_Volume', 'crude_Adj Close', 'crude_Volume']) 80 | else: 81 | today = today.drop(columns=['Adj Close', 'Volume']) 82 | 83 | ### 84 | today = preprocessBeforeScaling(today) 85 | today = today.drop(columns=['gold_Open', 'gold_High', 'gold_Low', 'crude_Open', 'crude_High', 'crude_Low']) 86 | ### 87 | 88 | cnt_correct, cnt_wrong = 0, 0 89 | for i in range(-TEST_DAYS,0): 90 | df = today.iloc[i] 91 | twr = today.iloc[i+1]['Close'] 92 | df = scaler.transform([df]) 93 | pred = endpoint.predict([df], verbose=0) 94 | 95 | if twr > today.iloc[i]['Open']: 96 | fact = "BULLISH" 97 | else: 98 | fact = "BEARISH" 99 | 100 | if pred > 0.5: 101 | out = "BEARISH" 102 | else: 103 | out = "BULLISH" 104 | 105 | if out == fact: 106 | cnt_correct += 1 107 | if out == "BULLISH": 108 | metrics["TP"] += 1 109 | else: 110 | metrics["TN"] += 1 111 | else: 112 | cnt_wrong += 1 113 | if out == "BULLISH": 114 | metrics["FN"] += 1 115 | else: 116 | metrics["FP"] += 1 117 | 118 | 119 | print("{} Nifty Prediction -> Market may Close {} on {}! Actual -> {}, Prediction -> {}, Pred = {}".format( 120 | today.iloc[i].name.strftime("%d-%m-%Y"), 121 | out, 122 | (today.iloc[i].name + pd.Timedelta(days=1)).strftime("%d-%m-%Y"), 123 | fact, 124 | "Correct" if fact == out else "Wrong", 125 | str(np.round(pred[0][0], 2)) 126 | ) 127 | ) 128 | 129 | print("Correct: {}, Wrong: {}, Accuracy: {}".format(cnt_correct, cnt_wrong, cnt_correct/(cnt_correct+cnt_wrong))) 130 | print(metrics) -------------------------------------------------------------------------------- /src/ml/nifty_model_v2.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/src/ml/nifty_model_v2.h5 -------------------------------------------------------------------------------- /src/ml/nifty_model_v2.pkl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/src/ml/nifty_model_v2.pkl -------------------------------------------------------------------------------- /src/ml/nifty_model_v3.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/src/ml/nifty_model_v3.h5 -------------------------------------------------------------------------------- /src/ml/nifty_model_v3.pkl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/src/ml/nifty_model_v3.pkl -------------------------------------------------------------------------------- /src/release.md: -------------------------------------------------------------------------------- 1 | [![MADE-IN-INDIA](https://img.shields.io/badge/MADE%20WITH%20%E2%9D%A4%20IN-INDIA-orange?style=for-the-badge)](https://en.wikipedia.org/wiki/India) [![GitHub release (latest by date)](https://img.shields.io/github/v/release/pranjal-joshi/Screeni-py?style=for-the-badge)](#) [![GitHub all releases](https://img.shields.io/github/downloads/pranjal-joshi/Screeni-py/total?color=Green&label=Downloads&style=for-the-badge)](#) ![Docker Pulls](https://img.shields.io/docker/pulls/joshipranjal/screeni-py?style=for-the-badge&logo=docker) [![MADE_WITH](https://img.shields.io/badge/BUILT%20USING-PYTHON-yellow?style=for-the-badge&logo=python&logoColor=yellow)](https://www.python.org/) 2 | ## What's New? 3 | 4 | Screeni-py is now on **YouTube** for additional help! - Thank You for your support :tada: 5 | 6 | 🐳 **Docker containers are released for quick setup and easy usage!** 7 | 8 | ⚠️ **Executable files (.exe, .bin and .run) are now DEPRECATED! Please Switch to Docker** 9 | 10 | 1. Fixed Blank Results issue by upgrading Yahoo Finance API client. 11 | 2. Added **Filters** to Result Table Headers (Apply Filters like Excel as per your strategy!) 12 | 3. Fixed Breakout Screening for **F&O Stocks** (Changed Data Source to Zerodha Kite from NSE website) 13 | 4. **RSI** based **Reversal** using *9 SMA* of RSI - Try `Option > 6 > 8` 14 | 5. **Position Size Calculator** tab added for Better and Quick Risk Management! 15 | 6. **Lorentzian Classification** (invented by Justin Dehorty) added for enhanced accuracy for your trades - - Try `Option > 6 > 7` 🤯 16 | 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` 17 | 8. **Search Similar Stocks** Added using Vector Similarity search - Try `Search Similar Stocks`. 18 | 9. New Screener **Buy at Trendline** added for Swing/Mid/Long term traders - Try `Option > 7 > 5`. 19 | 20 | ## Installation Guide 21 | 22 | [![Screeni-py - How to install Software Updates? | Screenipy - Python NSE Stock Screener](https://markdown-videos-api.jorgenkh.no/url?url=https%3A%2F%2Fyoutu.be%2FT41m13iMyJc)](https://youtu.be/T41m13iMyJc) 23 | [![Screeni-py - Detailed Installation Guide](https://markdown-videos-api.jorgenkh.no/url?url=https%3A%2F%2Fyoutu.be%2F2HMN0ac4H20)](https://youtu.be/2HMN0ac4H20) 24 | 25 | ## Downloads 26 | ### Deprecated - Use Docker Method mentioned in next section 27 | 28 | | Operating System | Executable File | Remarks | 29 | | :-: | --- | --- | 30 | | ![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)** | Not supported anymore, Use Docker method | 31 | | ![Linux](https://img.shields.io/badge/Linux-FCC624?style=for-the-badge&logo=linux&logoColor=black) | **[screenipy.bin](https://github.com/pranjal-joshi/Screeni-py/releases/download/2.02/screenipy.bin)** | Not supported anymore, Use Docker method | 32 | | ![Mac OS](https://img.shields.io/badge/mac%20os-D3D3D3?style=for-the-badge&logo=apple&logoColor=000000) | **[screenipy.run](https://github.com/pranjal-joshi/Screeni-py/releases/download/2.02/screenipy.run)** ([Read Installation Guide](https://github.com/pranjal-joshi/Screeni-py/blob/main/INSTALLATION.md#for-macos)) | Not supported anymore, Use Docker method | 33 | 34 | ## [Docker Releases](https://hub.docker.com/r/joshipranjal/screeni-py/tags) 35 | 36 | | | Tag | Pull Command | Run Mode | Run Command | 37 | |:-: | :-: | --- | --- | --- | 38 | | ![Docker](https://img.shields.io/badge/docker-%230db7ed.svg?style=for-the-badge&logo=docker&logoColor=white) | `latest` | `docker pull joshipranjal/screeni-py:latest` | Command Line | `docker run -it --entrypoint /bin/bash joshipranjal/screeni-py:latest -c "run_screenipy.sh --cli"` | 39 | | ![Docker](https://img.shields.io/badge/docker-%230db7ed.svg?style=for-the-badge&logo=docker&logoColor=white) | `latest` | `docker pull joshipranjal/screeni-py:latest` | GUI WebApp | `docker run -p 8501:8501 -p 8000:8000 joshipranjal/screeni-py:latest` | 40 | | ![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"` | 41 | | ![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 -p 8000:8000 joshipranjal/screeni-py:dev` | 42 | 43 | ### Docker Issues? Troubleshooting Guide: 44 | 45 | Read this [troubleshooting guide](https://github.com/pranjal-joshi/Screeni-py/discussions/217) for Windows to fix most common Docker issues easily! 46 | 47 | **Why we shifted to Docker from the Good old EXEs?** 48 | 49 | | Executable/Binary File | Docker | 50 | | :-- | :-- | 51 | | [![GitHub all releases](https://img.shields.io/github/downloads/pranjal-joshi/Screeni-py/total?color=Green&label=Downloads&style=for-the-badge)](#) | ![Docker Pulls](https://img.shields.io/docker/pulls/joshipranjal/screeni-py?style=for-the-badge&logo=docker) | 52 | | Download Directly from the [Release](https://github.com/pranjal-joshi/Screeni-py/releases/latest) page (DEPRECATED) | Need to Install [Docker Desktop](https://www.docker.com/products/docker-desktop/) ⚠️| 53 | | May take a long time to open the app | Loads quickly | 54 | | Slower screening | Performance boosted as per your CPU capabilities | 55 | | You may face errors/warnings due to different CPU arch of your system ⚠️ | Compatible with all x86_64/amd64/arm64 CPUs irrespective of OS (including Mac M1/M2) | 56 | | Works only with Windows 10/11 ⚠️ | Works with older versions of Windows as well | 57 | | Different file for each OS | Same container is compatible with everyone | 58 | | Antivirus may block this as untrusted file ⚠️ | No issues with Antivirus | 59 | | Need to download new file for every update | Updates quickly with minimal downloading | 60 | | No need of commands/technical knowledge | Very basic command execution skills may be required | 61 | | Incompatible with Vector Database ⚠️ | Compatible with all Python libraries | 62 | 63 | 64 | ## How to use? 65 | 66 | [**Click Here**](https://github.com/pranjal-joshi/Screeni-py) to read the documentation. 67 | 68 | ## Join our Community Discussion 69 | 70 | [**Click Here**](https://github.com/pranjal-joshi/Screeni-py/discussions) to join the community discussion and see what other users are doing! 71 | 72 | ## Facing an Issue? Found a Bug? 73 | 74 | [**Click Here**](https://github.com/pranjal-joshi/Screeni-py/issues/new/choose) to open an Issue so we can fix it for you! 75 | 76 | ## Want to Contribute? 77 | 78 | [**Click Here**](https://github.com/pranjal-joshi/Screeni-py/blob/main/CONTRIBUTING.md) before you start working with us on new features! 79 | 80 | ## Disclaimer: 81 | * DO NOT use the result provided by the software solely to make your trading decisions. 82 | * Always backtest and analyze the stocks manually before you trade. 83 | * The Author(s) and the software will not be held liable for any losses. -------------------------------------------------------------------------------- /src/screenipy.ini: -------------------------------------------------------------------------------- 1 | [config] 2 | period = 300d 3 | daystolookback = 30 4 | duration = 1d 5 | minprice = 30.0 6 | maxprice = 10000.0 7 | volumeratio = 2.0 8 | consolidationpercentage = 10 9 | shuffle = y 10 | cachestockdata = y 11 | onlystagetwostocks = y 12 | useema = n 13 | 14 | -------------------------------------------------------------------------------- /src/screenipy.spec: -------------------------------------------------------------------------------- 1 | # -*- mode: python ; coding: utf-8 -*- 2 | 3 | 4 | block_cipher = None 5 | 6 | 7 | a = Analysis(['screenipy.py'], 8 | pathex=['/mnt/40FEE0813E8AAC6F/Personal/Screeni-py/src'], 9 | binaries=[], 10 | datas=[], 11 | hiddenimports=['cmath', 'talib.stream', 'numpy'], 12 | hookspath=[], 13 | runtime_hooks=[], 14 | excludes=[], 15 | win_no_prefer_redirects=False, 16 | win_private_assemblies=False, 17 | cipher=block_cipher, 18 | noarchive=False) 19 | pyz = PYZ(a.pure, a.zipped_data, 20 | cipher=block_cipher) 21 | exe = EXE(pyz, 22 | a.scripts, 23 | a.binaries, 24 | a.zipfiles, 25 | a.datas, 26 | [], 27 | name='screenipy', 28 | debug=False, 29 | bootloader_ignore_signals=False, 30 | strip=False, 31 | upx=True, 32 | upx_exclude=[], 33 | runtime_tmpdir=None, 34 | console=True , icon='icon.ico') 35 | -------------------------------------------------------------------------------- /src/static/tablefilter/style/colsVisibility.css: -------------------------------------------------------------------------------- 1 | span.colVisSpan{text-align:left;}span.colVisSpan a.colVis{display:inline-block;padding:7px 5px 0;font-size:inherit;font-weight:inherit;vertical-align:top}div.colVisCont{position:relative;background:#fff;-webkit-box-shadow:3px 3px 2px #888;-moz-box-shadow:3px 3px 2px #888;box-shadow:3px 3px 2px #888;position:absolute;display:none;border:1px solid #ccc;height:auto;width:250px;background-color:#fff;margin:35px 0 0 -100px;z-index:10000;padding:10px 10px 10px 10px;text-align:left;font-size:inherit;}div.colVisCont:after,div.colVisCont:before{bottom:100%;left:50%;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none}div.colVisCont:after{border-color:rgba(255,255,255,0);border-bottom-color:#fff;border-width:10px;margin-left:-10px}div.colVisCont:before{border-color:rgba(255,255,255,0);border-bottom-color:#ccc;border-width:12px;margin-left:-12px}div.colVisCont p{margin:6px auto 6px auto}div.colVisCont a.colVis{display:initial;font-weight:inherit}ul.cols_checklist{padding:0;margin:0;list-style-type:none;}ul.cols_checklist label{display:block}ul.cols_checklist input{vertical-align:middle;margin:2px 5px 2px 1px}li.cols_checklist_item{padding:4px;margin:0;}li.cols_checklist_item:hover{background-color:#335ea8;color:#fff}.cols_checklist_slc_item{background-color:#335ea8;color:#fff} -------------------------------------------------------------------------------- /src/static/tablefilter/style/filtersVisibility.css: -------------------------------------------------------------------------------- 1 | span.expClpFlt a.btnExpClpFlt{width:35px;height:35px;display:inline-block;}span.expClpFlt a.btnExpClpFlt:hover{background-color:#f4f4f4}span.expClpFlt img{padding:8px 11px 11px 11px} -------------------------------------------------------------------------------- /src/static/tablefilter/style/tablefilter.css: -------------------------------------------------------------------------------- 1 | .activeHeader{background-color:#66afe9 !important;color:#fff !important}.activeCell{background-color:rgba(0,0,0,0.075)}.even{background-color:#fff}.odd{background-color:#f9f9f9}.ezActiveRow{background-color:#2852a8 !important;color:#fff}.ezSelectedRow{background-color:#316ac5 !important;color:#fff}.ezActiveCell{background-color:#d9e8fb !important;color:#000 !important;font-weight:bold}.ezETSelectedCell{background-color:#ffdc61 !important;font-weight:bold;color:#000 !important}.ezUnselectable{-moz-user-select:-moz-none;-khtml-user-select:none;-webkit-user-select:none;-o-user-select:none;user-select:none}.ezInputEditor{width:95%;height:auto;font-size:inherit;border:1px solid #aaccf6}.ezTextareaEditor{width:95%;height:35px;font-size:inherit;border:1px solid #aaccf6}.ezSelectEditor{width:100%;font-size:inherit;border:1px solid #aaccf6}.ezModifiedCell{background:transparent url("themes/bg_mod_cell.png") 0 0 no-repeat}select[multiple="multiple"].ezSelectEditor{height:35px}.ezCommandEditor{margin:2px;}.ezCommandEditor button,.ezCommandEditor input[type="button"]{min-height:22px;margin:1px;padding:3px;border:1px solid #ccc;background:#fff;border-radius:4px 4px 4px 4px;-moz-border-radius:4px 4px 4px 4px;}.ezCommandEditor button:hover,.ezCommandEditor input[type="button"]:hover{border:1px solid #999}.ezCommandEditor img{border:0;vertical-align:middle;margin:2px}.ezOpacity{opacity:.6}.alignLeft{text-align:left}.alignCenter{text-align:center}.alignRight{text-align:right}.div_checklist{width:100%;height:90px;line-height:30px;border:1px solid #f4f4f4;overflow:auto;text-align:left;background-color:#fff;color:#444;}.div_checklist ul.flt_checklist{padding:0 !important;margin:0 !important;list-style:none !important}.div_checklist li.flt_checklist_item{padding:1px !important;margin:0 !important;font-size:inherit;border-bottom:1px solid #f4f4f4 !important;}.div_checklist li.flt_checklist_item:hover{background-color:#335ea8 !important;color:#fff !important}.div_checklist label{display:block !important;font-weight:inherit !important}.div_checklist input{vertical-align:middle !important;margin:2px 5px 3px 1px !important}.flt_checklist_item_disabled{background-color:#e5e5e5}.flt_checklist_slc_item{background-color:#335ea8 !important;color:#fff !important}.fltrow{height:1em;background-color:#eaeaea;}.fltrow td{border-bottom:1px solid #ccc !important;border-top:1px solid #f4f4f4;border-left:1px solid #ccc;border-right:1px solid #f4f4f4;padding:.2em !important;}.fltrow td:last-child{border-right:1px solid #ccc}.btnflt{height:35px;font-family:inherit;font-size:inherit;vertical-align:middle;margin:0 2px 0 2px;padding:0 1px 0 1px}.btnflt_icon{font-family:inherit;font-size:inherit;width:35px;height:35px;cursor:pointer !important;border:0 !important;vertical-align:middle;background:transparent url("themes/btn_filter.png") center center no-repeat !important}.flt,.flt_s,.single_flt{font-family:inherit;font-size:inherit;display:block;color:#444;background-color:#fff;border:1px inset #f4f4f4;margin:0;padding:0 0 0 .2em;width:100%;height:35px;vertical-align:middle;border-radius:2px;box-sizing:border-box;}.flt:focus,.flt_s:focus,.single_flt:focus{border-color:#66afe9;outline:0 none;-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.075) inset,0 0 8px rgba(102,175,233,0.6);-moz-box-shadow:0 1px 1px rgba(0,0,0,0.075) inset,0 0 8px rgba(102,175,233,0.6);box-shadow:0 1px 1px rgba(0,0,0,0.075) inset,0 0 8px rgba(102,175,233,0.6)}select.flt_multi{font-family:inherit;font-size:inherit;color:#444;background-color:#fff;border:1px solid #f4f4f4;margin:0;padding:.2em;width:100%;height:90px;vertical-align:middle;box-sizing:border-box;}select.flt_multi option{padding-top:5px;padding-bottom:5px}.flt_s{width:60%;box-sizing:initial;display:initial}.single_flt{width:70%;box-sizing:initial;display:initial}div.popUpFilter{position:relative;background:#fff;-webkit-box-shadow:3px 3px 2px #888;-moz-box-shadow:3px 3px 2px #888;box-shadow:3px 3px 2px #888;margin:30px auto 0 0;position:absolute;display:none;width:100px;background-color:#eaeaea;border:1px solid #eaeaea;padding:0}div.popUpFilter:after,div.popUpFilter:before{bottom:100%;left:50%;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none}div.popUpFilter:after{border-color:rgba(255,255,255,0);border-bottom-color:#fff;border-width:10px;margin-left:-10px}div.popUpFilter:before{border-color:rgba(255,255,255,0);border-bottom-color:#eaeaea;border-width:12px;margin-left:-12px}.popUpPlaceholder{position:relative}div.grd_Cont{-webkit-box-shadow:4px 4px 10px 0 rgba(50,50,50,0.75);-moz-box-shadow:4px 4px 10px 0 rgba(50,50,50,0.75);box-shadow:4px 4px 10px 0 rgba(50,50,50,0.75);width:800px;height:auto;overflow:hidden;background-color:#c8e0fb;border:1px solid #99bbe8;}div.grd_Cont .fltrow{background-color:transparent}div.grd_Cont .flt{border:1px solid #99bbe8;width:100%;}div.grd_Cont .flt :focus{border:1px solid #558dd9}div.grd_Cont .even{background-color:#fff}div.grd_Cont .odd{background-color:#dfe8f6}div.grd_Cont .no-results{background-color:transparent}div.grd_Cont .sort-arrow{position:initial}div.grd_tblCont{height:400px;width:800px;background:#fff;overflow-x:auto;overflow-y:scroll}div.grd_headTblCont{display:block;margin-right:20px;height:auto;overflow:hidden;border-bottom:1px solid #99bbe8;background-color:#c8e0fb}div.grd_tblCont table,div.grd_headTblCont table{border-collapse:collapse;table-layout:fixed;box-sizing:initial}div.grd_tblCont table th,div.grd_headTblCont table th,div.grd_headTblCont table td{height:35px;background-color:#c8e0fb;padding:.1em .5em;color:#333;border-right:1px solid #99bbe8 !important;overflow:hidden;text-overflow:ellipsis}div.grd_headTblCont table td{padding:.2em .2em}div.grd_tblCont table td{padding:.5em .7em;border-bottom:1px solid #99bbe8;overflow:hidden;text-overflow:ellipsis}.grd_inf{clear:both;width:auto;height:35px;background-color:#c8e0fb;margin:0;padding:1px 3px 1px 3px;border-top:1px solid #99bbe8;}.grd_inf a{color:#333;text-decoration:none;font-weight:bold;}.grd_inf a:hover{text-decoration:underline;background-color:transparent}.grd_inf input.reset:hover{background-color:transparent}.grd_inf .mdiv{width:40% !important}.grd_inf .ldiv div{border:0}.grd_inf .helpBtn{border:0 !important}.grd_inf div.status{position:absolute;float:none !important;height:auto !important;margin:19px 0 !important;font-size:12px;color:#333;border:0 !important}.grd_inf div.tot{border:0 !important}.helpBtn{display:inline-block;height:27px;margin:0;padding:8px 15px 0 15px;vertical-align:top;}.helpBtn:hover{background-color:#f4f4f4}div.helpCont{position:relative;background:#fff;-webkit-box-shadow:3px 3px 2px #888;-moz-box-shadow:3px 3px 2px #888;box-shadow:3px 3px 2px #888;position:absolute;display:none;width:300px;padding:10px;margin:45px 0 0 -150px;border:1px solid #ccc;line-height:20px;font-size:inherit;color:#333;background:#fff;text-align:left;z-index:1000;}div.helpCont:after,div.helpCont:before{bottom:100%;left:50%;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none}div.helpCont:after{border-color:rgba(255,255,255,0);border-bottom-color:#fff;border-width:10px;margin-left:-10px}div.helpCont:before{border-color:rgba(255,255,255,0);border-bottom-color:#ccc;border-width:12px;margin-left:-12px}div.helpCont a{color:#c00;text-decoration:underline;font-weight:normal}div.helpCont a.close{color:#333 !important;text-decoration:none !important;font-weight:bold;}div.helpCont a.close:hover{text-decoration:none}div.helpCont hr{border:1px solid #ccc}div.helpFooter{margin:10px 0 0 0;}div.helpFooter h4{margin:2px 2px 2px 2px;color:#333}span.keyword{font-weight:700;font-style:italic;border-bottom:1px dotted #ccc}.loader{position:absolute;padding:.5em .7em;margin:10em 0 0 3em;width:auto;z-index:1000;font-weight:600;background-color:#a7a7a8;vertical-align:middle;border-radius:10px;color:#fff;text-shadow:1px 1px #333}.no-results{display:none;color:#333;margin:0;padding:1em 0;text-align:center;max-height:5em;background-color:#f4f4f4}select.pgSlc{height:35px;margin:0;border:1px solid #f4f4f4;background-color:#fff;vertical-align:middle}select.pgSlc:focus{border-color:#66afe9;outline:0 none;-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.075) inset,0 0 8px rgba(102,175,233,0.6);-moz-box-shadow:0 1px 1px rgba(0,0,0,0.075) inset,0 0 8px rgba(102,175,233,0.6);box-shadow:0 1px 1px rgba(0,0,0,0.075) inset,0 0 8px rgba(102,175,233,0.6)}input.pgNbInp{height:35px;margin:0;border:1px solid #f4f4f4;background-color:#fff;width:35px}input.pgNbInp:focus{border-color:#66afe9;outline:0 none;-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.075) inset,0 0 8px rgba(102,175,233,0.6);-moz-box-shadow:0 1px 1px rgba(0,0,0,0.075) inset,0 0 8px rgba(102,175,233,0.6);box-shadow:0 1px 1px rgba(0,0,0,0.075) inset,0 0 8px rgba(102,175,233,0.6)}input.pgInp,.nextPage,.previousPage,.firstPage,.lastPage{height:35px;margin:0;border:1px solid #f4f4f4;background-color:#fff;vertical-align:middle;width:35px;border:0;font-weight:bold}input.pgInp:focus,.nextPage:focus,.previousPage:focus,.firstPage:focus,.lastPage:focus{border-color:#66afe9;outline:0 none;-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.075) inset,0 0 8px rgba(102,175,233,0.6);-moz-box-shadow:0 1px 1px rgba(0,0,0,0.075) inset,0 0 8px rgba(102,175,233,0.6);box-shadow:0 1px 1px rgba(0,0,0,0.075) inset,0 0 8px rgba(102,175,233,0.6)}.nextPage{background:transparent url("themes/btn_next_page.gif") center center no-repeat !important;}.nextPage:hover{background-color:#f4f4f4 !important}.previousPage{background:transparent url("themes/btn_previous_page.gif") center center no-repeat !important;}.previousPage:hover{background-color:#f4f4f4 !important}.firstPage{background:transparent url("themes/btn_first_page.gif") center center no-repeat !important;}.firstPage:hover{background-color:#f4f4f4 !important}.lastPage{background:transparent url("themes/btn_last_page.gif") center center no-repeat !important;}.lastPage:hover{background-color:#f4f4f4 !important}span.nbpg{padding:0 5px}select.rspg{height:35px;margin:0;border:1px solid #f4f4f4;background-color:#fff;margin:0 0 0 5px;vertical-align:middle}select.rspg:focus{border-color:#66afe9;outline:0 none;-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.075) inset,0 0 8px rgba(102,175,233,0.6);-moz-box-shadow:0 1px 1px rgba(0,0,0,0.075) inset,0 0 8px rgba(102,175,233,0.6);box-shadow:0 1px 1px rgba(0,0,0,0.075) inset,0 0 8px rgba(102,175,233,0.6)}span.rspgSpan{font-size:inherit}input.reset{display:inline-block;width:35px;height:35px;border:0;background:transparent url("themes/btn_clear_filters.png") center center no-repeat;vertical-align:top;}input.reset:hover{background-color:#f4f4f4}a.reset{font-weight:normal !important;line-height:35px;padding:5px 5px}div.tot{float:left;overflow:hidden;min-width:150px;height:100%;margin:0;padding:.5em;vertical-align:middle;}div.tot span{font-weight:500}.sort-arrow{position:absolute;display:none;width:11px;height:11px;margin:0;background-position:center center;background-repeat:no-repeat}.descending{display:inline;background-image:url("themes/downsimple.png")}.ascending{display:inline;background-image:url("themes/upsimple.png")}div.status{float:left;overflow:hidden;min-width:120px;height:100%;margin:0;padding:.5em;}div.status span{font-size:inherit}table.TF{font-family:inherit;border-spacing:0;border:0;}table.TF th{height:35px;margin:0;background-color:#eaeaea;border-bottom:1px solid #ccc;border-top:1px solid #f4f4f4;border-left:1px solid #ccc;border-right:1px solid #f4f4f4;padding:.1em .7em;color:#333;}table.TF th:last-child{border-right:1px solid #ccc}table.TF td{margin:0;padding:.5em .7em;border-bottom:1px solid #c6c6c6;text-overflow:ellipsis}table.TF.resp{display:block;overflow-x:auto;overflow-y:hidden;}table.TF.resp .sort-arrow{position:initial}table.TF.sticky th{position:sticky;top:-1px}.inf{clear:both;width:auto;height:35px;min-width:400px;background-color:#fff;font-size:inherit;margin:0;padding:0;border-top:1px solid #ccc;border-bottom:1px solid #ccc;border-left:1px solid #ccc;border-right:1px solid #ccc;overflow:hidden;border-top-left-radius:3px;border-top-right-radius:3px;}.inf a{color:#333;text-decoration:none;font-weight:bold;box-sizing:initial;}.inf a:hover{text-decoration:underline}.ldiv{float:left;width:30%;position:inherit;text-align:left}.ldiv:empty:after{content:"\00A0"}.mdiv{float:left;width:38%;position:inherit;text-align:center;padding:0}.mdiv:empty:after{content:"\00A0"}.rdiv{float:right;width:30%;position:inherit;text-align:right}.rdiv:empty:after{content:"\00A0"} -------------------------------------------------------------------------------- /src/static/tablefilter/style/themes/blank.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/src/static/tablefilter/style/themes/blank.png -------------------------------------------------------------------------------- /src/static/tablefilter/style/themes/btn_clear_filters.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/src/static/tablefilter/style/themes/btn_clear_filters.png -------------------------------------------------------------------------------- /src/static/tablefilter/style/themes/btn_filter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/src/static/tablefilter/style/themes/btn_filter.png -------------------------------------------------------------------------------- /src/static/tablefilter/style/themes/btn_first_page.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/src/static/tablefilter/style/themes/btn_first_page.gif -------------------------------------------------------------------------------- /src/static/tablefilter/style/themes/btn_last_page.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/src/static/tablefilter/style/themes/btn_last_page.gif -------------------------------------------------------------------------------- /src/static/tablefilter/style/themes/btn_next_page.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/src/static/tablefilter/style/themes/btn_next_page.gif -------------------------------------------------------------------------------- /src/static/tablefilter/style/themes/btn_previous_page.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/src/static/tablefilter/style/themes/btn_previous_page.gif -------------------------------------------------------------------------------- /src/static/tablefilter/style/themes/default/default.css: -------------------------------------------------------------------------------- 1 | table.TF{border-left:1px solid #ccc;border-top:none;border-right:none;border-bottom:none;}table.TF th{background:#ebecee url("images/bg_th.jpg") left top repeat-x;border-bottom:1px solid #d0d0d0;border-right:1px solid #d0d0d0;border-left:1px solid #fff;border-top:1px solid #fff;color:#333}table.TF td{border-bottom:1px dotted #999;padding:5px}.fltrow{background-color:#ebecee !important;}.fltrow th,.fltrow td{border-bottom:1px dotted #666 !important;padding:1px 3px 1px 3px !important}.flt,select.flt,select.flt_multi,.flt_s,.single_flt,.div_checklist{border:1px solid #999 !important}input.flt{width:99% !important}.inf{height:$min-height;background:#d7d7d7 url("images/bg_infDiv.jpg") 0 0 repeat-x !important}input.reset{background:transparent url("images/btn_eraser.gif") center center no-repeat !important}.helpBtn:hover{background-color:transparent}.nextPage{background:transparent url("images/btn_next_page.gif") center center no-repeat !important;}.nextPage:hover{background:transparent url("images/btn_over_next_page.gif") center center no-repeat !important}.previousPage{background:transparent url("images/btn_previous_page.gif") center center no-repeat !important;}.previousPage:hover{background:transparent url("images/btn_over_previous_page.gif") center center no-repeat !important}.firstPage{background:transparent url("images/btn_first_page.gif") center center no-repeat !important;}.firstPage:hover{background:transparent url("images/btn_over_first_page.gif") center center no-repeat !important}.lastPage{background:transparent url("images/btn_last_page.gif") center center no-repeat !important;}.lastPage:hover{background:transparent url("images/btn_over_last_page.gif") center center no-repeat !important}div.grd_Cont{background-color:#ebecee !important;border:1px solid #ccc !important;padding:0 !important;}div.grd_Cont .even{background-color:#fff}div.grd_Cont .odd{background-color:#d5d5d5}div.grd_headTblCont{background-color:#ebecee !important;border-bottom:none !important;}div.grd_headTblCont table{border-right:none !important}div.grd_tblCont table th,div.grd_headTblCont table th,div.grd_headTblCont table td{background:#ebecee url("images/bg_th.jpg") left top repeat-x !important;border-bottom:1px solid #d0d0d0 !important;border-right:1px solid #d0d0d0 !important;border-left:1px solid #fff !important;border-top:1px solid #fff !important}div.grd_tblCont table td{border-bottom:1px solid #999 !important}.grd_inf{background:#d7d7d7 url("images/bg_infDiv.jpg") 0 0 repeat-x !important;border-top:1px solid #d0d0d0 !important}.loader{border:1px solid #999}.defaultLoader{width:32px;height:32px;background:transparent url("images/img_loading.gif") 0 0 no-repeat !important}.even{background-color:#fff}.odd{background-color:#d5d5d5}span.expClpFlt a.btnExpClpFlt:hover{background-color:transparent !important}.activeHeader{background:#999 !important} -------------------------------------------------------------------------------- /src/static/tablefilter/style/themes/default/images/bg_infDiv.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/src/static/tablefilter/style/themes/default/images/bg_infDiv.jpg -------------------------------------------------------------------------------- /src/static/tablefilter/style/themes/default/images/bg_th.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/src/static/tablefilter/style/themes/default/images/bg_th.jpg -------------------------------------------------------------------------------- /src/static/tablefilter/style/themes/default/images/btn_eraser.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/src/static/tablefilter/style/themes/default/images/btn_eraser.gif -------------------------------------------------------------------------------- /src/static/tablefilter/style/themes/default/images/btn_first_page.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/src/static/tablefilter/style/themes/default/images/btn_first_page.gif -------------------------------------------------------------------------------- /src/static/tablefilter/style/themes/default/images/btn_last_page.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/src/static/tablefilter/style/themes/default/images/btn_last_page.gif -------------------------------------------------------------------------------- /src/static/tablefilter/style/themes/default/images/btn_next_page.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/src/static/tablefilter/style/themes/default/images/btn_next_page.gif -------------------------------------------------------------------------------- /src/static/tablefilter/style/themes/default/images/btn_over_eraser.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/src/static/tablefilter/style/themes/default/images/btn_over_eraser.gif -------------------------------------------------------------------------------- /src/static/tablefilter/style/themes/default/images/btn_over_first_page.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/src/static/tablefilter/style/themes/default/images/btn_over_first_page.gif -------------------------------------------------------------------------------- /src/static/tablefilter/style/themes/default/images/btn_over_last_page.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/src/static/tablefilter/style/themes/default/images/btn_over_last_page.gif -------------------------------------------------------------------------------- /src/static/tablefilter/style/themes/default/images/btn_over_next_page.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/src/static/tablefilter/style/themes/default/images/btn_over_next_page.gif -------------------------------------------------------------------------------- /src/static/tablefilter/style/themes/default/images/btn_over_previous_page.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/src/static/tablefilter/style/themes/default/images/btn_over_previous_page.gif -------------------------------------------------------------------------------- /src/static/tablefilter/style/themes/default/images/btn_previous_page.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/src/static/tablefilter/style/themes/default/images/btn_previous_page.gif -------------------------------------------------------------------------------- /src/static/tablefilter/style/themes/default/images/img_loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/src/static/tablefilter/style/themes/default/images/img_loading.gif -------------------------------------------------------------------------------- /src/static/tablefilter/style/themes/downsimple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/src/static/tablefilter/style/themes/downsimple.png -------------------------------------------------------------------------------- /src/static/tablefilter/style/themes/icn_clp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/src/static/tablefilter/style/themes/icn_clp.png -------------------------------------------------------------------------------- /src/static/tablefilter/style/themes/icn_exp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/src/static/tablefilter/style/themes/icn_exp.png -------------------------------------------------------------------------------- /src/static/tablefilter/style/themes/icn_filter.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/src/static/tablefilter/style/themes/icn_filter.gif -------------------------------------------------------------------------------- /src/static/tablefilter/style/themes/icn_filterActive.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/src/static/tablefilter/style/themes/icn_filterActive.gif -------------------------------------------------------------------------------- /src/static/tablefilter/style/themes/mytheme/images/bg_headers.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/src/static/tablefilter/style/themes/mytheme/images/bg_headers.jpg -------------------------------------------------------------------------------- /src/static/tablefilter/style/themes/mytheme/images/bg_infDiv.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/src/static/tablefilter/style/themes/mytheme/images/bg_infDiv.jpg -------------------------------------------------------------------------------- /src/static/tablefilter/style/themes/mytheme/images/btn_filter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/src/static/tablefilter/style/themes/mytheme/images/btn_filter.png -------------------------------------------------------------------------------- /src/static/tablefilter/style/themes/mytheme/images/btn_first_page.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/src/static/tablefilter/style/themes/mytheme/images/btn_first_page.gif -------------------------------------------------------------------------------- /src/static/tablefilter/style/themes/mytheme/images/btn_last_page.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/src/static/tablefilter/style/themes/mytheme/images/btn_last_page.gif -------------------------------------------------------------------------------- /src/static/tablefilter/style/themes/mytheme/images/btn_next_page.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/src/static/tablefilter/style/themes/mytheme/images/btn_next_page.gif -------------------------------------------------------------------------------- /src/static/tablefilter/style/themes/mytheme/images/btn_previous_page.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/src/static/tablefilter/style/themes/mytheme/images/btn_previous_page.gif -------------------------------------------------------------------------------- /src/static/tablefilter/style/themes/mytheme/images/img_loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/src/static/tablefilter/style/themes/mytheme/images/img_loading.gif -------------------------------------------------------------------------------- /src/static/tablefilter/style/themes/mytheme/mytheme.css: -------------------------------------------------------------------------------- 1 | table.TF{border-left:1px dotted #81963b;border-top:none;border-right:0;border-bottom:none;}table.TF th{background:#39424b url("images/bg_headers.jpg") left top repeat-x;border-bottom:0;border-right:1px dotted #d0d0d0;border-left:0;border-top:0;color:#fff}table.TF td{border-bottom:1px dotted #81963b;border-right:1px dotted #81963b;padding:5px}.fltrow{background-color:#81963b !important;}.fltrow th,.fltrow td{border-bottom:1px dotted #39424b !important;border-right:1px dotted #fff !important;border-left:0 !important;border-top:0 !important;padding:1px 3px 1px 3px !important}.flt,select.flt,select.flt_multi,.flt_s,.single_flt,.div_checklist{border:1px solid #687830 !important}input.flt{width:99% !important}.inf{background:#d8d8d8;height:$min-height}input.reset{width:53px;background:transparent url("images/btn_filter.png") center center no-repeat !important}.helpBtn:hover{background-color:transparent}.nextPage{background:transparent url("images/btn_next_page.gif") center center no-repeat !important}.previousPage{background:transparent url("images/btn_previous_page.gif") center center no-repeat !important}.firstPage{background:transparent url("images/btn_first_page.gif") center center no-repeat !important}.lastPage{background:transparent url("images/btn_last_page.gif") center center no-repeat !important}div.grd_Cont{background:#81963b url("images/bg_headers.jpg") left top repeat-x !important;border:1px solid #ccc !important;padding:0 1px 1px 1px !important;}div.grd_Cont .even{background-color:#bccd83}div.grd_Cont .odd{background-color:#fff}div.grd_headTblCont{background-color:#ebecee !important;border-bottom:none !important}div.grd_tblCont table{border-right:none !important;}div.grd_tblCont table td{border-bottom:1px dotted #81963b;border-right:1px dotted #81963b}div.grd_tblCont table th,div.grd_headTblCont table th{background:transparent url("images/bg_headers.jpg") 0 0 repeat-x !important;border-bottom:0 !important;border-right:1px dotted #d0d0d0 !important;border-left:0 !important;border-top:0 !important;padding:0 4px 0 4px !important;color:#fff !important;height:35px !important}div.grd_headTblCont table td{border-bottom:1px dotted #39424b !important;border-right:1px dotted #fff !important;border-left:0 !important;border-top:0 !important;background-color:#81963b !important;padding:1px 3px 1px 3px !important}.grd_inf{background-color:#d8d8d8;border-top:1px solid #d0d0d0 !important}.loader{border:0 !important;background:#81963b !important}.defaultLoader{width:32px;height:32px;background:transparent url("images/img_loading.gif") 0 0 no-repeat !important}.even{background-color:#bccd83}.odd{background-color:#fff}span.expClpFlt a.btnExpClpFlt:hover{background-color:transparent !important}.activeHeader{background:#81963b !important} -------------------------------------------------------------------------------- /src/static/tablefilter/style/themes/skyblue/images/bg_skyblue.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/src/static/tablefilter/style/themes/skyblue/images/bg_skyblue.gif -------------------------------------------------------------------------------- /src/static/tablefilter/style/themes/skyblue/images/btn_first_page.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/src/static/tablefilter/style/themes/skyblue/images/btn_first_page.gif -------------------------------------------------------------------------------- /src/static/tablefilter/style/themes/skyblue/images/btn_last_page.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/src/static/tablefilter/style/themes/skyblue/images/btn_last_page.gif -------------------------------------------------------------------------------- /src/static/tablefilter/style/themes/skyblue/images/btn_next_page.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/src/static/tablefilter/style/themes/skyblue/images/btn_next_page.gif -------------------------------------------------------------------------------- /src/static/tablefilter/style/themes/skyblue/images/btn_prev_page.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/src/static/tablefilter/style/themes/skyblue/images/btn_prev_page.gif -------------------------------------------------------------------------------- /src/static/tablefilter/style/themes/skyblue/images/icn_clear_filters.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/src/static/tablefilter/style/themes/skyblue/images/icn_clear_filters.png -------------------------------------------------------------------------------- /src/static/tablefilter/style/themes/skyblue/images/img_loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/src/static/tablefilter/style/themes/skyblue/images/img_loading.gif -------------------------------------------------------------------------------- /src/static/tablefilter/style/themes/skyblue/skyblue.css: -------------------------------------------------------------------------------- 1 | table.TF{padding:0;color:#000;border-right:1px solid #a4bed4;border-top:1px solid #a4bed4;border-left:1px solid #a4bed4;border-bottom:0;}table.TF th{margin:0;color:inherit;background:#d1e5fe url("images/bg_skyblue.gif") 0 0 repeat-x;border-color:#fdfdfd #a4bed4 #a4bed4 #fdfdfd;border-width:1px;border-style:solid}table.TF td{margin:0;padding:5px;color:inherit;border-bottom:1px solid #a4bed4;border-left:0;border-top:0;border-right:0}.fltrow{background-color:#d1e5fe !important;}.fltrow th,.fltrow td{padding:1px 3px 1px 3px !important}.flt,select.flt,select.flt_multi,.flt_s,.single_flt,.div_checklist{border:1px solid #a4bed4 !important}input.flt{width:99% !important}.inf{background-color:#e3efff !important;border:1px solid #a4bed4;height:$min-height;color:#004a6f}div.tot,div.status{border-right:0 !important}.helpBtn:hover{background-color:transparent}input.reset{background:transparent url("images/icn_clear_filters.png") center center no-repeat !important}.nextPage{background:transparent url("images/btn_next_page.gif") center center no-repeat !important;border:1px solid transparent !important;}.nextPage:hover{background:#ffe4ab url("images/btn_next_page.gif") center center no-repeat !important;border:1px solid #ffb552 !important}.previousPage{background:transparent url("images/btn_prev_page.gif") center center no-repeat !important;border:1px solid transparent !important;}.previousPage:hover{background:#ffe4ab url("images/btn_prev_page.gif") center center no-repeat !important;border:1px solid #ffb552 !important}.firstPage{background:transparent url("images/btn_first_page.gif") center center no-repeat !important;border:1px solid transparent !important;}.firstPage:hover{background:#ffe4ab url("images/btn_first_page.gif") center center no-repeat !important;border:1px solid #ffb552 !important}.lastPage{background:transparent url("images/btn_last_page.gif") center center no-repeat !important;border:1px solid transparent !important;}.lastPage:hover{background:#ffe4ab url("images/btn_last_page.gif") center center no-repeat !important;border:1px solid #ffb552 !important}.activeHeader{background:#ffe4ab !important;border:1px solid #ffb552 !important;color:inherit !important}div.grd_Cont{background-color:#d9eaed !important;border:1px solid #9cc !important;padding:0 !important;}div.grd_Cont .even{background-color:#fff}div.grd_Cont .odd{background-color:#e3efff}div.grd_headTblCont{background-color:#d9eaed !important;border-bottom:none !important}div.grd_tblCont table{border-right:none !important}div.grd_tblCont table th,div.grd_headTblCont table th,div.grd_headTblCont table td{background:#d9eaed url("images/bg_skyblue.gif") left top repeat-x;border-bottom:1px solid #a4bed4;border-right:1px solid #a4bed4 !important;border-left:1px solid #fff !important;border-top:1px solid #fff !important}div.grd_tblCont table td{border-bottom:1px solid #a4bed4 !important;border-right:0 !important;border-left:0 !important;border-top:0 !important}.grd_inf{background-color:#cce2fe;color:#004a6f;border-top:1px solid #9cc !important;}.grd_inf a{text-decoration:none;font-weight:bold}.loader{background-color:#2d8eef;border:1px solid #cce2fe;border-radius:5px}.even{background-color:#fff}.odd{background-color:#e3efff}span.expClpFlt a.btnExpClpFlt:hover{background-color:transparent !important}.ezActiveRow{background-color:#ffdc61 !important;color:inherit}.ezSelectedRow{background-color:#ffe4ab !important;color:inherit}.ezActiveCell{background-color:#fff !important;color:#000 !important;font-weight:bold}.ezETSelectedCell{background-color:#fff !important;font-weight:bold;color:#000 !important} -------------------------------------------------------------------------------- /src/static/tablefilter/style/themes/transparent/images/btn_first_page.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/src/static/tablefilter/style/themes/transparent/images/btn_first_page.gif -------------------------------------------------------------------------------- /src/static/tablefilter/style/themes/transparent/images/btn_last_page.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/src/static/tablefilter/style/themes/transparent/images/btn_last_page.gif -------------------------------------------------------------------------------- /src/static/tablefilter/style/themes/transparent/images/btn_next_page.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/src/static/tablefilter/style/themes/transparent/images/btn_next_page.gif -------------------------------------------------------------------------------- /src/static/tablefilter/style/themes/transparent/images/btn_prev_page.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/src/static/tablefilter/style/themes/transparent/images/btn_prev_page.gif -------------------------------------------------------------------------------- /src/static/tablefilter/style/themes/transparent/images/icn_clear_filters.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/src/static/tablefilter/style/themes/transparent/images/icn_clear_filters.png -------------------------------------------------------------------------------- /src/static/tablefilter/style/themes/transparent/images/img_loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/src/static/tablefilter/style/themes/transparent/images/img_loading.gif -------------------------------------------------------------------------------- /src/static/tablefilter/style/themes/transparent/transparent.css: -------------------------------------------------------------------------------- 1 | table.TF{padding:0;color:inherit;border-right:1px solid transparent;border-top:1px solid transparent;border-left:1px solid transparent;border-bottom:0;}table.TF th{margin:0;color:inherit;background-color:transparent;border-color:transparent;border-width:1px;border-style:solid;}table.TF th:last-child{border-right:1px solid transparent}table.TF td{margin:0;padding:5px;color:inherit;border-bottom:1px solid transparent;border-left:0;border-top:0;border-right:0}.fltrow{background-color:transparent;}.fltrow th,.fltrow td{padding:1px 3px 1px 3px;border-bottom:1px solid transparent !important;}.fltrow th:last-child,.fltrow td:last-child{border-right:1px solid transparent}.flt,select.flt,select.flt_multi,.flt_s,.single_flt,.div_checklist{border:1px solid #a4bed4}input.flt{width:99% !important}.inf{background-color:transparent;border:1px solid transparent;height:$min-height;color:inherit}div.tot,div.status{border-right:0 !important}.helpBtn:hover{background-color:transparent}input.reset{background:transparent url("images/icn_clear_filters.png") center center no-repeat !important}.nextPage{background:transparent url("images/btn_next_page.gif") center center no-repeat !important;border:1px solid transparent !important;}.nextPage:hover{background:#f7f7f7 url("images/btn_next_page.gif") center center no-repeat !important;border:1px solid #f7f7f7 !important}.previousPage{background:transparent url("images/btn_prev_page.gif") center center no-repeat !important;border:1px solid transparent !important;}.previousPage:hover{background:#f7f7f7 url("images/btn_prev_page.gif") center center no-repeat !important;border:1px solid #f7f7f7 !important}.firstPage{background:transparent url("images/btn_first_page.gif") center center no-repeat !important;border:1px solid transparent !important;}.firstPage:hover{background:#f7f7f7 url("images/btn_first_page.gif") center center no-repeat !important;border:1px solid #f7f7f7 !important}.lastPage{background:transparent url("images/btn_last_page.gif") center center no-repeat !important;border:1px solid transparent !important;}.lastPage:hover{background:#f7f7f7 url("images/btn_last_page.gif") center center no-repeat !important;border:1px solid #f7f7f7 !important}.activeHeader{background:#f7f7f7 !important;border:1px solid transparent;color:inherit !important}div.grd_Cont{-webkit-box-shadow:0 0 0 0 rgba(50,50,50,0.75);-moz-box-shadow:0 0 0 0 rgba(50,50,50,0.75);box-shadow:0 0 0 0 rgba(50,50,50,0.75);background-color:transparent;border:1px solid transparent;padding:0 !important;}div.grd_Cont .even{background-color:transparent}div.grd_Cont .odd{background-color:#f7f7f7}div.grd_headTblCont{background-color:transparent;border-bottom:none !important}div.grd_tblCont table{border-right:none !important}div.grd_tblCont table th,div.grd_headTblCont table th,div.grd_headTblCont table td{background:transparent;border-bottom:1px solid transparent;border-right:1px solid transparent !important;border-left:1px solid transparent;border-top:1px solid transparent}div.grd_tblCont table td{border-bottom:1px solid transparent;border-right:0 !important;border-left:0 !important;border-top:0 !important}.grd_inf{background-color:transparent;color:inherit;border-top:1px solid transparent;}.grd_inf a{text-decoration:none;font-weight:bold}.loader{background-color:#f7f7f7;border:1px solid #f7f7f7;border-radius:5px;color:#000;text-shadow:none}.even{background-color:transparent}.odd{background-color:#f7f7f7}span.expClpFlt a.btnExpClpFlt:hover{background-color:transparent !important}.ezActiveRow{background-color:#ccc !important;color:inherit}.ezSelectedRow{background-color:#ccc !important;color:inherit}.ezActiveCell{background-color:transparent;color:inherit;font-weight:bold}.ezETSelectedCell{background-color:transparent;font-weight:bold;color:inherit} -------------------------------------------------------------------------------- /src/static/tablefilter/style/themes/upsimple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/75558f6f00beb880e65c1751c1c07bcb33cbd95a/src/static/tablefilter/style/themes/upsimple.png -------------------------------------------------------------------------------- /test/screenipy_test.py: -------------------------------------------------------------------------------- 1 | ''' 2 | * Project : Screenipy 3 | * Author : Pranjal Joshi 4 | * Created : 29/04/2021 5 | * Description : Automated Test Script for Screenipy 6 | ''' 7 | 8 | 9 | import pytest 10 | import sys 11 | import os 12 | import numpy as np 13 | import pandas as pd 14 | import configparser 15 | import requests 16 | import json 17 | import platform 18 | 19 | sys.path.append(os.path.abspath('../src')) 20 | import classes.ConfigManager as ConfigManager 21 | from classes.Changelog import changelog 22 | from screenipy import * 23 | last_release = 0 24 | configManager = ConfigManager.tools() 25 | 26 | # Generate default configuration if not exist 27 | 28 | 29 | def test_generate_default_config(mocker, capsys): 30 | mocker.patch('builtins.input', side_effect=['5','0', '\n']) 31 | with pytest.raises(SystemExit): 32 | configManager.setConfig(ConfigManager.parser, default=True) 33 | out, err = capsys.readouterr() 34 | assert err == '' 35 | 36 | 37 | def test_if_release_version_increamented(): 38 | global last_release 39 | r = requests.get( 40 | "https://api.github.com/repos/pranjal-joshi/Screeni-py/releases/latest") 41 | last_release = float(r.json()['tag_name']) 42 | assert float(VERSION) > last_release 43 | 44 | 45 | def test_option_0(mocker): 46 | try: 47 | mocker.patch('builtins.input', side_effect=['0', TEST_STKCODE, 'y']) 48 | main(testing=True) 49 | assert len(screenResults) == 1 50 | except StopIteration: 51 | pass 52 | 53 | 54 | def test_option_1(mocker): 55 | try: 56 | mocker.patch('builtins.input', side_effect=['5', '1', 'y']) 57 | main(testing=True) 58 | assert len(screenResults) > 0 59 | except StopIteration: 60 | pass 61 | 62 | 63 | def test_option_2(mocker): 64 | try: 65 | mocker.patch('builtins.input', side_effect=['5', '2', 'y']) 66 | main(testing=True) 67 | assert len(screenResults) > 0 68 | except StopIteration: 69 | pass 70 | 71 | 72 | def test_option_3(mocker): 73 | try: 74 | mocker.patch('builtins.input', side_effect=['5', '3', 'y']) 75 | main(testing=True) 76 | assert len(screenResults) > 0 77 | except StopIteration: 78 | pass 79 | 80 | 81 | def test_option_4(mocker): 82 | try: 83 | mocker.patch('builtins.input', side_effect=['5', '4', '7', 'y']) 84 | main(testing=True) 85 | assert len(screenResults) > 0 86 | except StopIteration: 87 | pass 88 | 89 | 90 | def test_option_5(mocker): 91 | try: 92 | mocker.patch('builtins.input', side_effect=['5', '5', '30', '70']) 93 | main(testing=True) 94 | assert len(screenResults) > 0 95 | except StopIteration: 96 | pass 97 | 98 | 99 | def test_option_6(mocker): 100 | try: 101 | mocker.patch('builtins.input', side_effect=['5', '6', '1', 'y']) 102 | main(testing=True) 103 | assert len(screenResults) > 0 104 | except StopIteration: 105 | pass 106 | 107 | 108 | def test_option_7(mocker): 109 | try: 110 | mocker.patch('builtins.input', side_effect=['5', '7', '1', '7', 'y']) 111 | main(testing=True) 112 | assert len(screenResults) > 0 113 | except StopIteration: 114 | pass 115 | 116 | 117 | def test_option_8(mocker, capsys): 118 | try: 119 | mocker.patch('builtins.input', side_effect=['5', 120 | '8', 121 | str(configManager.period), 122 | str(configManager.daysToLookback), 123 | str(configManager.duration), 124 | str(configManager.minLTP), 125 | str(configManager.maxLTP), 126 | str(configManager.volumeRatio), 127 | str(configManager.consolidationPercentage), 128 | 'y', 129 | 'y', 130 | ]) 131 | with pytest.raises((SystemExit, configparser.DuplicateSectionError)): 132 | main(testing=True) 133 | out, err = capsys.readouterr() 134 | assert err == 0 or err == '' 135 | except StopIteration: 136 | pass 137 | 138 | 139 | def test_option_9(): 140 | configManager.getConfig(ConfigManager.parser) 141 | assert configManager.duration is not None 142 | assert configManager.period is not None 143 | assert configManager.consolidationPercentage is not None 144 | 145 | 146 | def test_option_12(mocker, capsys): 147 | try: 148 | mocker.patch('builtins.input', side_effect=['5','12']) 149 | with pytest.raises(SystemExit): 150 | main(testing=True) 151 | out, err = capsys.readouterr() 152 | assert err == '' 153 | except StopIteration: 154 | pass 155 | 156 | def test_option_14(mocker): 157 | try: 158 | mocker.patch('builtins.input', side_effect=['14', '0', 'y']) 159 | main(testing=True) 160 | assert len(screenResults) > 0 161 | except StopIteration: 162 | pass 163 | 164 | 165 | # def test_ota_updater(): 166 | # try: 167 | # OTAUpdater.checkForUpdate(proxyServer, VERSION) 168 | # assert ( 169 | # "exe" in OTAUpdater.checkForUpdate.url or "bin" in OTAUpdater.checkForUpdate.url) 170 | # except StopIteration: 171 | # pass 172 | 173 | 174 | # def test_release_readme_urls(): 175 | # global last_release 176 | # f = open('../src/release.md', 'r', encoding='utf-8') 177 | # contents = f.read() 178 | # f.close() 179 | # failUrl = [f"https://github.com/pranjal-joshi/Screeni-py/releases/download/{last_release}/screenipy.bin", 180 | # f"https://github.com/pranjal-joshi/Screeni-py/releases/download/{last_release}/screenipy.exe"] 181 | # passUrl = [f"https://github.com/pranjal-joshi/Screeni-py/releases/download/{VERSION}/screenipy.bin", 182 | # f"https://github.com/pranjal-joshi/Screeni-py/releases/download/{VERSION}/screenipy.exe"] 183 | # for url in failUrl: 184 | # assert not url in contents 185 | # for url in passUrl: 186 | # assert url in contents 187 | 188 | 189 | def test_if_changelog_version_changed(): 190 | global last_release 191 | v = changelog.split(']')[-2].split('[')[-1] 192 | assert float(v) > float(last_release) 193 | --------------------------------------------------------------------------------