Hitendra1851 commited on
Commit
a4c7650
1 Parent(s): df3e2fc
.github/ISSUE_TEMPLATE/bug_report.md ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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.
.github/ISSUE_TEMPLATE/feature_request.md ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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.
.github/dependencies/TA_Lib-0.4.19-cp39-cp39-win_amd64.whl ADDED
Binary file (501 kB). View file
 
.github/dependencies/ta-lib-0.4.0-src.tar.gz ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9ff41efcb1c011a4b4b6dfc91610b06e39b1d7973ed5d4dee55029a0ac4dc651
3
+ size 1330299
.github/workflows/squash.py ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from time import sleep
2
+ import os
3
+
4
+ c_msg = "GitHub Action Workflow - Market Data Download (Default Config)"
5
+
6
+ print("[+] === SQUASHING COMMITS : actions-data-download branch ===")
7
+ print("[+] Saving Commit messages log..")
8
+ os.system("git log --pretty=oneline > msg.log")
9
+
10
+ sleep(5)
11
+
12
+ lines = None
13
+ with open('msg.log','r') as f:
14
+ lines = f.readlines()
15
+
16
+ cnt = 0
17
+ for l in lines:
18
+ if c_msg in l:
19
+ cnt += 1
20
+ else:
21
+ commit_hash = l.split(" ")[0]
22
+ cnt -= 1
23
+ break
24
+
25
+
26
+ print(f"[+] Reset at HEAD~{cnt}")
27
+ print(f"[+] Reset hash = {commit_hash}")
28
+ print(f"git reset --soft {commit_hash}")
29
+ print(f"git commit -m '{c_msg}'")
30
+
31
+ if cnt < 1:
32
+ print("[+] No Need to Squash! Skipping...")
33
+ else:
34
+ os.system(f"git reset --soft HEAD~{cnt}")
35
+ os.system(f"git commit -m '{c_msg}'")
36
+ os.system(f"git push -f")
37
+
38
+ os.remove("msg.log")
39
+ sleep(5)
40
+
41
+ print("[+] === SQUASHING COMMITS : DONE ===")
.github/workflows/workflow-build-matrix.yml ADDED
@@ -0,0 +1,145 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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:
18
+ name: Build Packages
19
+ #needs: Create-Release
20
+ runs-on: ${{ matrix.os }}
21
+ strategy:
22
+ matrix:
23
+ include:
24
+ - os: windows-latest
25
+ TARGET: Windows
26
+ CMD_BUILD: |
27
+ 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
28
+ DEP_BUILD: |
29
+ python -m pip install --upgrade pip
30
+ echo Installing TA-lib...
31
+ cd .github/dependencies/
32
+ echo %cd%
33
+ pip install TA_Lib-0.4.19-cp39-cp39-win_amd64.whl
34
+ cd ..
35
+ cd ..
36
+ python -m pip install --upgrade pip
37
+ pip install -r requirements.txt
38
+ TEST_BUILD: |
39
+ ./dist/screenipy.exe --testbuild
40
+ exit $?
41
+ OUT_PATH: .\dist\screenipy.exe
42
+ FILE_NAME: screenipy.exe
43
+
44
+ - os: ubuntu-20.04
45
+ TARGET: Linux
46
+ CMD_BUILD: |
47
+ 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
48
+ mv /home/runner/work/Screeni-py/Screeni-py/dist/screenipy /home/runner/work/Screeni-py/Screeni-py/dist/screenipy.bin
49
+ chmod +x /home/runner/work/Screeni-py/Screeni-py/dist/screenipy.bin
50
+ DEP_BUILD: |
51
+ cd .github/dependencies/
52
+ pwd
53
+ tar -xzf ta-lib-0.4.0-src.tar.gz
54
+ cd ta-lib/
55
+ ./configure --prefix=/usr
56
+ make
57
+ sudo make install
58
+ cd /home/runner/work/Screeni-py/Screeni-py/
59
+ python -m pip install --upgrade pip
60
+ pip install -r requirements.txt
61
+ pip install ta-lib==0.4.24
62
+ TEST_BUILD: |
63
+ /home/runner/work/Screeni-py/Screeni-py/dist/screenipy.bin --testbuild
64
+ exit $?
65
+ OUT_PATH: /home/runner/work/Screeni-py/Screeni-py/dist/screenipy.bin
66
+ FILE_NAME: screenipy.bin
67
+
68
+ - os: macos-latest
69
+ TARGET: MacOS
70
+ CMD_BUILD: |
71
+ 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
72
+ mv /Users/runner/work/Screeni-py/Screeni-py/dist/screenipy /Users/runner/work/Screeni-py/Screeni-py/dist/screenipy.run
73
+ DEP_BUILD: |
74
+ brew install ta-lib
75
+ python -m pip install --upgrade pip
76
+ pip install -r requirements.txt
77
+ pip install ta-lib==0.4.24
78
+ TEST_BUILD: |
79
+ /Users/runner/work/Screeni-py/Screeni-py/dist/screenipy.run --testbuild
80
+ exit $?
81
+ OUT_PATH: /Users/runner/work/Screeni-py/Screeni-py/dist/screenipy.run
82
+ FILE_NAME: screenipy.run
83
+
84
+ steps:
85
+ - uses: actions/checkout@v2
86
+
87
+ - name: Get the GitHub Tag version
88
+ id: get_version
89
+ shell: bash
90
+ run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//}
91
+
92
+ - name: Set up Python 3.9.4
93
+ uses: actions/setup-python@v2
94
+ with:
95
+ python-version: 3.9.4
96
+
97
+ - name: Load Cache for Linux Dependencies
98
+ uses: actions/cache@v2
99
+ if: startsWith(runner.os, 'Linux')
100
+ with:
101
+ path: |
102
+ /usr/include/ta-lib
103
+ /usr/bin/ta-lib-config
104
+ key: ${{ runner.os }}-talib
105
+ restore-keys: |
106
+ ${{ runner.os }}-talib
107
+
108
+ - name: Install dependencies for ${{ matrix.TARGET }}
109
+ run: ${{ matrix.DEP_BUILD }}
110
+
111
+ - name: Build for ${{ matrix.TARGET }}
112
+ run: ${{ matrix.CMD_BUILD }}
113
+
114
+ - name: Test Built Binary for ${{ matrix.TARGET }}
115
+ shell: bash
116
+ run: ${{ matrix.TEST_BUILD }}
117
+ continue-on-error: false
118
+
119
+ - name: Save Binaries as Artifacts
120
+ uses: actions/upload-artifact@v2
121
+ with:
122
+ name: ${{ matrix.FILE_NAME }}
123
+ path: ${{ matrix.OUT_PATH }}
124
+
125
+ - name: Read release.md
126
+ id: read_release
127
+ shell: bash
128
+ run: |
129
+ r=$(cat src/release.md)
130
+ r="${r//'%'/'%25'}"
131
+ r="${r//$'\n'/'%0A'}"
132
+ r="${r//$'\r'/'%0D'}"
133
+ echo "::set-output name=RELEASE_BODY::$r"
134
+
135
+ - name: Upload Binaries to Release
136
+ uses: svenstaro/upload-release-action@v2
137
+ with:
138
+ repo_token: ${{ secrets.GITHUB_TOKEN }}
139
+ file: ${{ matrix.OUT_PATH }}
140
+ asset_name: ${{ matrix.FILE_NAME }}
141
+ tag: ${{ github.ref }}
142
+ release_name: Screenipy - v${{ steps.get_version.outputs.VERSION }}
143
+ body: |
144
+ ${{ steps.read_release.outputs.RELEASE_BODY }}
145
+ overwrite: true
.github/workflows/workflow-download-data.yml ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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: windows-latest
22
+
23
+ steps:
24
+ - uses: actions/checkout@v2
25
+ with:
26
+ ref: actions-data-download
27
+
28
+ - name: Set up Python 3.9.4
29
+ uses: actions/setup-python@v2
30
+ with:
31
+ python-version: 3.9.4
32
+
33
+ - name: Restore Dependencies from Cache
34
+ uses: actions/cache@v2
35
+ with:
36
+ path: ~\AppData\Local\pip\Cache
37
+ key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
38
+ restore-keys: |
39
+ ${{ runner.os }}-pip-
40
+
41
+ - name: Install TA-Lib
42
+ run: |
43
+ python -m pip install --upgrade pip
44
+ cd .github/dependencies/
45
+ echo %cd%
46
+ pip install TA_Lib-0.4.19-cp39-cp39-win_amd64.whl
47
+
48
+ - name: Install Python Dependencies
49
+ run: |
50
+ pip install -r requirements.txt
51
+
52
+ - name: Download Stock Data
53
+ shell: cmd
54
+ run: |
55
+ rmdir /s /q actions-data-download
56
+ mkdir actions-data-download
57
+ python src/screenipy.py -d
58
+ copy "stock_data_*.pkl" "actions-data-download"
59
+
60
+ - name: Push Pickle Data
61
+ run: |
62
+ git config user.name github-actions
63
+ git config user.email github-actions@github.com
64
+ git checkout actions-data-download
65
+ git add actions-data-download/stock_data_*.pkl --force
66
+ git commit -m "GitHub Action Workflow - Market Data Download (Default Config)"
67
+ git push
68
+
69
+ - name: Squash Commits (Python)
70
+ shell: cmd
71
+ run: |
72
+ git config user.name github-actions
73
+ git config user.email github-actions@github.com
74
+ git fetch
75
+ git checkout actions-data-download
76
+ python .github/workflows/squash.py
.github/workflows/workflow-stale.yml ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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'
.github/workflows/workflow-test.yml ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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: windows-latest
21
+
22
+ steps:
23
+ - uses: actions/checkout@v2
24
+
25
+ - name: Set up Python 3.9.4
26
+ uses: actions/setup-python@v2
27
+ with:
28
+ python-version: 3.9.4
29
+
30
+ - name: Restore Dependencies from Cache
31
+ uses: actions/cache@v2
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 for TA-Lib
39
+ run: |
40
+ python -m pip install --upgrade pip
41
+ cd .github/dependencies/
42
+ echo %cd%
43
+ pip install TA_Lib-0.4.19-cp39-cp39-win_amd64.whl
44
+
45
+ # - name: Install Numpy-MKL for Windows
46
+ # run: |
47
+ # echo Downloading Numpy-MKL (212MB), Please wait!
48
+ # powershell.exe -Command (new-object System.Net.WebClient).DownloadFile('https://download.lfd.uci.edu/pythonlibs/w4tscw6k/numpy-1.20.2+mkl-cp39-cp39-win_amd64.whl','numpy-1.20.2+mkl-cp39-cp39-win_amd64.whl')
49
+ # pip install numpy-1.20.2+mkl-cp39-cp39-win_amd64.whl
50
+
51
+ - name: Install dependencies using pip
52
+ run: |
53
+ python -m pip install --upgrade pip
54
+ pip install flake8 pytest pytest-mock
55
+ pip install -r requirements.txt
56
+
57
+ - name: Lint with flake8
58
+ run: |
59
+ # stop the build if there are Python syntax errors or undefined names
60
+ flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
61
+ # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
62
+ flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
63
+
64
+ - name: Run a Test with pytest
65
+ run: |
66
+ cd test/
67
+ pytest -v
68
+
69
+ # Job to create PR
70
+ Create-Pull-Request:
71
+
72
+ runs-on: ubuntu-latest
73
+ needs: [Test-Source]
74
+
75
+ steps:
76
+ - name: Checkout Repo before PR
77
+ uses: actions/checkout@v2
78
+
79
+ - name: Create Pull Request (new-features -> main)
80
+ id: create_pr
81
+ uses: repo-sync/pull-request@v2
82
+ with:
83
+ source_branch: "new-features"
84
+ destination_branch: "main"
85
+ pr_title: "[Screenipy Test] New Features Added - Test Passed"
86
+ pr_body: |
87
+ **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.
88
+
89
+ View commits for changelog.
90
+ pr_label: "Test-Passed"
91
+ pr_draft: false
92
+ pr_allow_empty: true
93
+ github_token: ${{ secrets.GITHUB_TOKEN }}
94
+
95
+ - name: Create PR Log file
96
+ shell: bash
97
+ run: |
98
+ echo ${{steps.create_pr.outputs.pr_url}} >> pr.txt
99
+ echo ${{steps.create_pr.outputs.pr_number}} >> pr.txt
100
+
101
+ - name: Save PR Log File
102
+ uses: actions/upload-artifact@v2
103
+ with:
104
+ name: PR_Log.txt
105
+ path: pr.txt
.gitignore ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
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
CONTRIBUTING.md ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 <BRANCH_YOU_ARE_WORKING_ON>
16
+ git merge screenipy_upstream/<BRANCH_FROM_THIS_REPO_YOU_WANT_TO_MERGE_IN_YOUR_BRANCH>
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`.
INSTALLATION.md ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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.
README.md CHANGED
@@ -1,12 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
- title: StockScreeners
3
- emoji: ⚡
4
- colorFrom: pink
5
- colorTo: pink
6
- sdk: streamlit
7
- sdk_version: 1.28.0
8
- app_file: app.py
9
- pinned: false
10
- ---
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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)](#) [![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) |
7
+ | <img width="240" src="https://user-images.githubusercontent.com/6128978/217814499-7934edf6-fcc3-46d7-887e-7757c94e1632.png"><h2>Scan QR Code to join [Official Telegram Group](https://t.me/+0Tzy08mR0do0MzNl) for Additional Discussions</h2> |
8
+
9
+ | **Download** | **Discussion** | **Bugs/Issues** | **Documentation** |
10
+ | :---: | :---: | :---: | :---: |
11
+ | [![cloud-computing (1)](https://user-images.githubusercontent.com/6128978/149935359-ca0a7155-d1e3-4e47-98e8-67f879e707e7.png)](https://github.com/pranjal-joshi/Screeni-py/releases/latest) | [![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
+ | Download the Latest Version | Join/Read the Community Discussion | Raise an Issue about a Problem | Get Help about Usage |
13
+
14
+ <!-- ## [**Click to Download the Latest Version**](https://github.com/pranjal-joshi/Screeni-py/releases/latest) -->
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 it's possible breakout values. It also helps to find the stocks which are consolidating and may breakout, or the particular chart patterns that you're looking specifically to make your decisions.
23
+ Screenipy is totally customizable and it can screen stocks with the settings that you have provided.
24
+
25
+ ## How to use?
26
+ * Download the suitable file according to your OS.
27
+ * Linux & Mac users should make sure that the `screenipy.bin or screenipy.run` is having `execute` permission.
28
+ * **Run** the file. Following window will appear after a brief delay.
29
+
30
+ ![home](https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/new-features/screenshots/screenipy_demo.gif)
31
+
32
+ * **Configure** the parameters as per your requirement using `Option > 8`.
33
+
34
+ ![config](https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/new-features/screenshots/config.png)
35
+
36
+ * Following are the screenshots of screening and output results.
37
+
38
+ ![screening](https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/new-features/screenshots/screening.png)
39
+ ![results](https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/new-features/screenshots/results.png)
40
+ ![done](https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/new-features/screenshots/done.png)
41
+
42
+ * Once done, you can also save the results in an excel file.
43
+
44
+ ## Understanding the Result Table:
45
+
46
+ 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.
47
+
48
+ | Sr | Parameter | Description | Example |
49
+ |:---:|:---:|:---|:---|
50
+ |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)|
51
+ |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 %`|
52
+ |3|**Breakout (N Days)**|This is pure magic! The `BO` is Breakout level in last N days while `R` is the next resistance level if available. Investor should consider both BO & R level to decide entry/exits in their trades.|`B:302, R:313`(Breakout level is 100 & Next resistance is 102)|
53
+ |4|**LTP**|LTP is the Last Traded Price of an asset traded on NSE.|`298.7` (Stock is trading at this price)|
54
+ |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`|
55
+ |6|**MA-Signal**|It describes the price trend of an asset by analysing various 50-200 MA/EMA crossover strategies.|`200MA-Support`,`BullCross-50MA` etc|
56
+ |7|**RSI**|For the momentum traders, it describes 14-period RSI for quick decision making about their trading plans|`0 to 100`|
57
+ |8|**Trend**|By using advance algorithms, the average trendlines are computed for `N` days and their strenght is displayed depending on steepness of trendlines. (This does NOT show any trendline on chart, it is calculated internally)|`Strong Up`, `Weak Down` etc.|
58
+ |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.|
59
+
60
+ ## Hack it your way:
61
+ Feel free to Edit the parameters in the `screenipy.ini` file which will be generated by the application.
62
+ ```
63
+ [config]
64
+ period = 300d
65
+ daystolookback = 30
66
+ duration = 1d
67
+ minprice = 30
68
+ maxprice = 10000
69
+ volumeratio = 2
70
+ consolidationpercentage = 10
71
+ shuffle = y
72
+ cachestockdata = y
73
+ onlystagetwostocks = y
74
+ useema = n
75
+ ```
76
+ Try to tweak this parameters as per your trading styles. For example, If you're comfortable with weekly charts, make `duration=5d` and so on.
77
+
78
+ ## Contributing:
79
+ * Please feel free to Suggest improvements bugs by creating an issue.
80
+ * Please follow the [Guidelines for Contributing](https://github.com/pranjal-joshi/Screeni-py/blob/new-features/CONTRIBUTING.md) while making a Pull Request.
81
+
82
+ ## Disclaimer:
83
+ * DO NOT use the result provided by the software 'solely' to make your trading decisions.
84
+ * Always backtest and analyze the stocks manually before you trade.
85
+ * The Author and the software will not be held liable for your losses.
patterns/IPO1.png ADDED
patterns/IPO2.png ADDED
patterns/IPO3.png ADDED
patterns/MomentumGainer.png ADDED
requirements.txt ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ numpy==1.21.0 # Installed as dependency for yfinance, scipy, matplotlib, pandas
2
+ appdirs
3
+ cachecontrol
4
+ contextlib2
5
+ distlib
6
+ distro
7
+ html5lib
8
+ ipaddr
9
+ lockfile
10
+ nsetools
11
+ openpyxl
12
+ pep517
13
+ pip
14
+ pip-chill
15
+ progress
16
+ pyinstaller==5.6.2
17
+ pytest-mock
18
+ pytoml
19
+ retrying
20
+ scipy==1.7.3
21
+ ta-lib
22
+ tabulate
23
+ yfinance==0.1.87
24
+ alive-progress==1.6.2
25
+ Pillow
26
+ scikit-learn==1.1.1
27
+ tensorflow==2.9.2
28
+ joblib
29
+ altgraph # Installed as dependency for pyinstaller
30
+ atomicwrites # Installed as dependency for pytest
31
+ attrs # Installed as dependency for pytest
32
+ certifi # Installed as dependency for requests
33
+ chardet # Installed as dependency for requests
34
+ colorama # Installed as dependency for pytest
35
+ dateutils # Installed as dependency for nsetools
36
+ et-xmlfile # Installed as dependency for openpyxl
37
+ future # Installed as dependency for pefile
38
+ idna # Installed as dependency for requests
39
+ iniconfig # Installed as dependency for pytest
40
+ lxml # Installed as dependency for yfinance
41
+ msgpack # Installed as dependency for cachecontrol
42
+ multitasking # Installed as dependency for yfinance
43
+ packaging # Installed as dependency for pytest
44
+ pandas==1.3.5 # Installed as dependency for yfinance
45
+ pefile # Installed as dependency for pyinstaller
46
+ pluggy # Installed as dependency for pytest
47
+ py # Installed as dependency for pytest
48
+ pyinstaller-hooks-contrib # Installed as dependency for pyinstaller
49
+ pyparsing # Installed as dependency for packaging, matplotlib
50
+ pytest # Installed as dependency for pytest-mock
51
+ python-dateutil # Installed as dependency for dateutils, matplotlib, pandas
52
+ pytz # Installed as dependency for dateutils, pandas
53
+ pywin32-ctypes # Installed as dependency for pyinstaller
54
+ requests # Installed as dependency for yfinance, cachecontrol
55
+ setuptools # Installed as dependency for pyinstaller
56
+ six # Installed as dependency for cycler, nsetools, packaging, retrying, python-dateutil, html5lib
57
+ toml # Installed as dependency for pep517, pytest
58
+ urllib3 # Installed as dependency for requests
59
+ webencodings # Installed as dependency for html5lib
src/classes/CandlePatterns.py ADDED
@@ -0,0 +1,185 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ import talib
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 = talib.CDLMORNINGSTAR(data['Open'], data['High'], data['Low'], data['Close'])
27
+ if(check.tail(1).item() != 0):
28
+ dict['Pattern'] = colorText.BOLD + colorText.GREEN + 'Morning Star' + colorText.END
29
+ saveDict['Pattern'] = 'Morning Star'
30
+ return True
31
+
32
+ check = talib.CDLMORNINGDOJISTAR(data['Open'], data['High'], data['Low'], data['Close'])
33
+ if(check.tail(1).item() != 0):
34
+ dict['Pattern'] = colorText.BOLD + colorText.GREEN + 'Morning Doji Star' + colorText.END
35
+ saveDict['Pattern'] = 'Morning Doji Star'
36
+ return True
37
+
38
+ check = talib.CDLEVENINGSTAR(data['Open'], data['High'], data['Low'], data['Close'])
39
+ if(check.tail(1).item() != 0):
40
+ dict['Pattern'] = colorText.BOLD + colorText.FAIL + 'Evening Star' + colorText.END
41
+ saveDict['Pattern'] = 'Evening Star'
42
+ return True
43
+
44
+ check = talib.CDLEVENINGDOJISTAR(data['Open'], data['High'], data['Low'], data['Close'])
45
+ if(check.tail(1).item() != 0):
46
+ dict['Pattern'] = colorText.BOLD + colorText.FAIL + 'Evening Doji Star' + colorText.END
47
+ saveDict['Pattern'] = 'Evening Doji Star'
48
+ return True
49
+
50
+ check = talib.CDLLADDERBOTTOM(data['Open'], data['High'], data['Low'], data['Close'])
51
+ if(check.tail(1).item() != 0):
52
+ if(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 = talib.CDL3LINESTRIKE(data['Open'], data['High'], data['Low'], data['Close'])
61
+ if(check.tail(1).item() != 0):
62
+ if(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 = talib.CDL3BLACKCROWS(data['Open'], data['High'], data['Low'], data['Close'])
70
+ if(check.tail(1).item() != 0):
71
+ dict['Pattern'] = colorText.BOLD + colorText.FAIL + '3 Black Crows' + colorText.END
72
+ saveDict['Pattern'] = '3 Black Crows'
73
+ return True
74
+
75
+ check = talib.CDL3INSIDE(data['Open'], data['High'], data['Low'], data['Close'])
76
+ if(check.tail(1).item() != 0):
77
+ if(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 = talib.CDL3OUTSIDE(data['Open'], data['High'], data['Low'], data['Close'])
86
+ if(check.tail(1).item() != 0):
87
+ if(check.tail(1).item() > 0):
88
+ dict['Pattern'] = colorText.BOLD + colorText.GREEN + '3 Outside Up' + colorText.END
89
+ saveDict['Pattern'] = '3 Outside Up'
90
+ else:
91
+ dict['Pattern'] = colorText.BOLD + colorText.FAIL + '3 Outside Down' + colorText.END
92
+ saveDict['Pattern'] = '3 Outside Down'
93
+ return True
94
+
95
+ check = talib.CDL3WHITESOLDIERS(data['Open'], data['High'], data['Low'], data['Close'])
96
+ if(check.tail(1).item() != 0):
97
+ dict['Pattern'] = colorText.BOLD + colorText.GREEN + '3 White Soldiers' + colorText.END
98
+ saveDict['Pattern'] = '3 White Soldiers'
99
+ return True
100
+
101
+ check = talib.CDLHARAMI(data['Open'], data['High'], data['Low'], data['Close'])
102
+ if(check.tail(1).item() != 0):
103
+ if(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 = talib.CDLHARAMICROSS(data['Open'], data['High'], data['Low'], data['Close'])
112
+ if(check.tail(1).item() != 0):
113
+ if(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 = talib.CDLMARUBOZU(data['Open'], data['High'], data['Low'], data['Close'])
122
+ if(check.tail(1).item() != 0):
123
+ if(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 = talib.CDLHANGINGMAN(data['Open'], data['High'], data['Low'], data['Close'])
132
+ if(check.tail(1).item() != 0):
133
+ dict['Pattern'] = colorText.BOLD + colorText.FAIL + 'Hanging Man' + colorText.END
134
+ saveDict['Pattern'] = 'Hanging Man'
135
+ return True
136
+
137
+ check = talib.CDLHAMMER(data['Open'], data['High'], data['Low'], data['Close'])
138
+ if(check.tail(1).item() != 0):
139
+ dict['Pattern'] = colorText.BOLD + colorText.GREEN + 'Hammer' + colorText.END
140
+ saveDict['Pattern'] = 'Hammer'
141
+ return True
142
+
143
+ check = talib.CDLINVERTEDHAMMER(data['Open'], data['High'], data['Low'], data['Close'])
144
+ if(check.tail(1).item() != 0):
145
+ dict['Pattern'] = colorText.BOLD + colorText.GREEN + 'Inverted Hammer' + colorText.END
146
+ saveDict['Pattern'] = 'Inverted Hammer'
147
+ return True
148
+
149
+ check = talib.CDLSHOOTINGSTAR(data['Open'], data['High'], data['Low'], data['Close'])
150
+ if(check.tail(1).item() != 0):
151
+ dict['Pattern'] = colorText.BOLD + colorText.FAIL + 'Shooting Star' + colorText.END
152
+ saveDict['Pattern'] = 'Shooting Star'
153
+ return True
154
+
155
+ check = talib.CDLDRAGONFLYDOJI(data['Open'], data['High'], data['Low'], data['Close'])
156
+ if(check.tail(1).item() != 0):
157
+ dict['Pattern'] = colorText.BOLD + colorText.GREEN + 'Dragonfly Doji' + colorText.END
158
+ saveDict['Pattern'] = 'Dragonfly Doji'
159
+ return True
160
+
161
+ check = talib.CDLGRAVESTONEDOJI(data['Open'], data['High'], data['Low'], data['Close'])
162
+ if(check.tail(1).item() != 0):
163
+ dict['Pattern'] = colorText.BOLD + colorText.FAIL + 'Gravestone Doji' + colorText.END
164
+ saveDict['Pattern'] = 'Gravestone Doji'
165
+ return True
166
+
167
+ check = talib.CDLDOJI(data['Open'], data['High'], data['Low'], data['Close'])
168
+ if(check.tail(1).item() != 0):
169
+ dict['Pattern'] = colorText.BOLD + 'Doji' + colorText.END
170
+ saveDict['Pattern'] = 'Doji'
171
+ return True
172
+
173
+ check = talib.CDLENGULFING(data['Open'], data['High'], data['Low'], data['Close'])
174
+ if(check.tail(1).item() != 0):
175
+ if(check.tail(1).item() > 0):
176
+ dict['Pattern'] = colorText.BOLD + colorText.GREEN + 'Bullish Engulfing' + colorText.END
177
+ saveDict['Pattern'] = 'Bullish Engulfing'
178
+ else:
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
src/classes/Changelog.py ADDED
@@ -0,0 +1,197 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 = "1.43"
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
+ --- END ---
197
+ ''' + colorText.END
src/classes/ColorText.py ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 ADDED
@@ -0,0 +1,193 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 configparser
12
+ from datetime import date
13
+ from classes.ColorText import colorText
14
+
15
+ parser = configparser.ConfigParser(strict=False)
16
+
17
+ # Default attributes for Downloading Cache from Git repo
18
+ default_period = '300d'
19
+ default_duration = '1d'
20
+
21
+ # This Class manages read/write of user configuration
22
+ class tools:
23
+
24
+ def __init__(self):
25
+ self.consolidationPercentage = 10
26
+ self.volumeRatio = 2
27
+ self.minLTP = 20.0
28
+ self.maxLTP = 50000
29
+ self.period = '300d'
30
+ self.duration = '1d'
31
+ self.daysToLookback = 30
32
+ self.shuffleEnabled = True
33
+ self.cacheEnabled = True
34
+ self.stageTwo = False
35
+ self.useEMA = False
36
+
37
+ def deleteStockData(self,excludeFile=None):
38
+ for f in glob.glob('stock_data*.pkl'):
39
+ if excludeFile is not None:
40
+ if not f.endswith(excludeFile):
41
+ os.remove(f)
42
+ else:
43
+ os.remove(f)
44
+
45
+ # Handle user input and save config
46
+
47
+ def setConfig(self, parser, default=False, showFileCreatedText=True):
48
+ if default:
49
+ parser.add_section('config')
50
+ parser.set('config', 'period', self.period)
51
+ parser.set('config', 'daysToLookback', str(self.daysToLookback))
52
+ parser.set('config', 'duration', self.duration)
53
+ parser.set('config', 'minPrice', str(self.minLTP))
54
+ parser.set('config', 'maxPrice', str(self.maxLTP))
55
+ parser.set('config', 'volumeRatio', str(self.volumeRatio))
56
+ parser.set('config', 'consolidationPercentage',
57
+ str(self.consolidationPercentage))
58
+ parser.set('config', 'shuffle', 'y')
59
+ parser.set('config', 'cacheStockData', 'y')
60
+ parser.set('config', 'onlyStageTwoStocks', 'y')
61
+ parser.set('config', 'useEMA', 'n')
62
+ try:
63
+ fp = open('screenipy.ini', 'w')
64
+ parser.write(fp)
65
+ fp.close()
66
+ if showFileCreatedText:
67
+ print(colorText.BOLD + colorText.GREEN +
68
+ '[+] Default configuration generated as user configuration is not found!' + colorText.END)
69
+ print(colorText.BOLD + colorText.GREEN +
70
+ '[+] Use Option > 5 to edit in future.' + colorText.END)
71
+ print(colorText.BOLD + colorText.GREEN +
72
+ '[+] Close and Restart the program now.' + colorText.END)
73
+ input('')
74
+ sys.exit(0)
75
+ except IOError:
76
+ print(colorText.BOLD + colorText.FAIL +
77
+ '[+] Failed to save user config. Exiting..' + colorText.END)
78
+ input('')
79
+ sys.exit(1)
80
+ else:
81
+ parser = configparser.ConfigParser(strict=False)
82
+ parser.add_section('config')
83
+ print('')
84
+ print(colorText.BOLD + colorText.GREEN +
85
+ '[+] Screeni-py User Configuration:' + colorText.END)
86
+ self.period = input(
87
+ '[+] Enter number of days for which stock data to be downloaded (Days)(Optimal = 365): ')
88
+ self.daysToLookback = input(
89
+ '[+] Number of recent days (TimeFrame) to screen for Breakout/Consolidation (Days)(Optimal = 20): ')
90
+ self.duration = input(
91
+ '[+] Enter Duration of each candle (Days)(Optimal = 1): ')
92
+ self.minLTP = input(
93
+ '[+] Minimum Price of Stock to Buy (in RS)(Optimal = 20): ')
94
+ self.maxLTP = input(
95
+ '[+] Maximum Price of Stock to Buy (in RS)(Optimal = 50000): ')
96
+ self.volumeRatio = input(
97
+ '[+] How many times the volume should be more than average for the breakout? (Number)(Optimal = 2.5): ')
98
+ self.consolidationPercentage = input(
99
+ '[+] How many % the price should be in range to consider it as consolidation? (Number)(Optimal = 10): ')
100
+ self.shuffle = str(input(
101
+ '[+] Shuffle stocks rather than screening alphabetically? (Y/N): ')).lower()
102
+ self.cacheStockData = str(input(
103
+ '[+] Enable High-Performance and Data-Saver mode? (This uses little bit more CPU but performs High Performance Screening) (Y/N): ')).lower()
104
+ self.stageTwoPrompt = str(input(
105
+ '[+] 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()
106
+ self.useEmaPrompt = str(input(
107
+ '[+] Use EMA instead of SMA? (EMA is good for Short-term & SMA for Mid/Long-term trades)[Y/N]: ')).lower()
108
+ parser.set('config', 'period', self.period + "d")
109
+ parser.set('config', 'daysToLookback', self.daysToLookback)
110
+ parser.set('config', 'duration', self.duration + "d")
111
+ parser.set('config', 'minPrice', self.minLTP)
112
+ parser.set('config', 'maxPrice', self.maxLTP)
113
+ parser.set('config', 'volumeRatio', self.volumeRatio)
114
+ parser.set('config', 'consolidationPercentage',
115
+ self.consolidationPercentage)
116
+ parser.set('config', 'shuffle', self.shuffle)
117
+ parser.set('config', 'cacheStockData', self.cacheStockData)
118
+ parser.set('config', 'onlyStageTwoStocks', self.stageTwoPrompt)
119
+ parser.set('config', 'useEMA', self.useEmaPrompt)
120
+
121
+ # delete stock data due to config change
122
+ self.deleteStockData()
123
+ print(colorText.BOLD + colorText.FAIL + "[+] Cached Stock Data Deleted." + colorText.END)
124
+
125
+ try:
126
+ fp = open('screenipy.ini', 'w')
127
+ parser.write(fp)
128
+ fp.close()
129
+ print(colorText.BOLD + colorText.GREEN +
130
+ '[+] User configuration saved.' + colorText.END)
131
+ print(colorText.BOLD + colorText.GREEN +
132
+ '[+] Restart the Program to start Screening...' + colorText.END)
133
+ input('')
134
+ sys.exit(0)
135
+ except IOError:
136
+ print(colorText.BOLD + colorText.FAIL +
137
+ '[+] Failed to save user config. Exiting..' + colorText.END)
138
+ input('')
139
+ sys.exit(1)
140
+
141
+ # Load user config from file
142
+ def getConfig(self, parser):
143
+ if len(parser.read('screenipy.ini')):
144
+ try:
145
+ self.duration = parser.get('config', 'duration')
146
+ self.period = parser.get('config', 'period')
147
+ self.minLTP = float(parser.get('config', 'minprice'))
148
+ self.maxLTP = float(parser.get('config', 'maxprice'))
149
+ self.volumeRatio = float(parser.get('config', 'volumeRatio'))
150
+ self.consolidationPercentage = float(
151
+ parser.get('config', 'consolidationPercentage'))
152
+ self.daysToLookback = int(
153
+ parser.get('config', 'daysToLookback'))
154
+ if 'n' not in str(parser.get('config', 'shuffle')).lower():
155
+ self.shuffleEnabled = True
156
+ if 'n' not in str(parser.get('config', 'cachestockdata')).lower():
157
+ self.cacheEnabled = True
158
+ if 'n' not in str(parser.get('config', 'onlyStageTwoStocks')).lower():
159
+ self.stageTwo = True
160
+ if 'y' not in str(parser.get('config', 'useEMA')).lower():
161
+ self.useEMA = False
162
+ except configparser.NoOptionError:
163
+ input(colorText.BOLD + colorText.FAIL +
164
+ '[+] Screenipy requires user configuration again. Press enter to continue..' + colorText.END)
165
+ parser.remove_section('config')
166
+ self.setConfig(parser, default=False)
167
+ else:
168
+ self.setConfig(parser, default=True)
169
+
170
+ # Print config file
171
+ def showConfigFile(self):
172
+ try:
173
+ f = open('screenipy.ini', 'r')
174
+ print(colorText.BOLD + colorText.GREEN +
175
+ '[+] Screeni-py User Configuration:' + colorText.END)
176
+ print("\n"+f.read())
177
+ f.close()
178
+ input('')
179
+ except:
180
+ print(colorText.BOLD + colorText.FAIL +
181
+ "[+] User Configuration not found!" + colorText.END)
182
+ print(colorText.BOLD + colorText.WARN +
183
+ "[+] Configure the limits to continue." + colorText.END)
184
+ self.setConfig(parser)
185
+
186
+ # Check if config file exists
187
+ def checkConfigFile(self):
188
+ try:
189
+ f = open('screenipy.ini','r')
190
+ f.close()
191
+ return True
192
+ except FileNotFoundError:
193
+ return False
src/classes/Fetcher.py ADDED
@@ -0,0 +1,219 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 yfinance as yf
15
+ import pandas as pd
16
+ from nsetools import Nse
17
+ from classes.ColorText import colorText
18
+ from classes.SuppressOutput import SuppressOutput
19
+
20
+ nse = Nse()
21
+
22
+ # Exception class if yfinance stock delisted
23
+
24
+
25
+ class StockDataEmptyException(Exception):
26
+ pass
27
+
28
+ # This Class Handles Fetching of Stock Data over the internet
29
+
30
+
31
+ class tools:
32
+
33
+ def __init__(self, configManager):
34
+ self.configManager = configManager
35
+ pass
36
+
37
+ def fetchCodes(self, tickerOption,proxyServer=None):
38
+ listStockCodes = []
39
+ if tickerOption == 12:
40
+ url = "https://archives.nseindia.com/content/equities/EQUITY_L.csv"
41
+ return list(pd.read_csv(url)['SYMBOL'].values)
42
+ tickerMapping = {
43
+ 1: "https://archives.nseindia.com/content/indices/ind_nifty50list.csv",
44
+ 2: "https://archives.nseindia.com/content/indices/ind_niftynext50list.csv",
45
+ 3: "https://archives.nseindia.com/content/indices/ind_nifty100list.csv",
46
+ 4: "https://archives.nseindia.com/content/indices/ind_nifty200list.csv",
47
+ 5: "https://archives.nseindia.com/content/indices/ind_nifty500list.csv",
48
+ 6: "https://archives.nseindia.com/content/indices/ind_niftysmallcap50list.csv",
49
+ 7: "https://archives.nseindia.com/content/indices/ind_niftysmallcap100list.csv",
50
+ 8: "https://archives.nseindia.com/content/indices/ind_niftysmallcap250list.csv",
51
+ 9: "https://archives.nseindia.com/content/indices/ind_niftymidcap50list.csv",
52
+ 10: "https://archives.nseindia.com/content/indices/ind_niftymidcap100list.csv",
53
+ 11: "https://archives.nseindia.com/content/indices/ind_niftymidcap150list.csv",
54
+ 14: "https://archives.nseindia.com/content/fo/fo_mktlots.csv"
55
+ }
56
+
57
+ url = tickerMapping.get(tickerOption)
58
+
59
+ try:
60
+ if proxyServer:
61
+ res = requests.get(url,proxies={'https':proxyServer})
62
+ else:
63
+ res = requests.get(url)
64
+
65
+ cr = csv.reader(res.text.strip().split('\n'))
66
+
67
+ if tickerOption == 14:
68
+ for i in range(5):
69
+ next(cr) # skipping first line
70
+ for row in cr:
71
+ listStockCodes.append(row[1])
72
+ else:
73
+ next(cr) # skipping first line
74
+ for row in cr:
75
+ listStockCodes.append(row[2])
76
+ except Exception as error:
77
+ print(error)
78
+
79
+ return listStockCodes
80
+
81
+ # Fetch all stock codes from NSE
82
+ def fetchStockCodes(self, tickerOption, proxyServer=None):
83
+ listStockCodes = []
84
+ if tickerOption == 0:
85
+ stockCode = None
86
+ while stockCode == None or stockCode == "":
87
+ stockCode = str(input(colorText.BOLD + colorText.BLUE +
88
+ "[+] Enter Stock Code(s) for screening (Multiple codes should be seperated by ,): ")).upper()
89
+ stockCode = stockCode.replace(" ", "")
90
+ listStockCodes = stockCode.split(',')
91
+ else:
92
+ print(colorText.BOLD +
93
+ "[+] Getting Stock Codes From NSE... ", end='')
94
+ listStockCodes = self.fetchCodes(tickerOption,proxyServer=proxyServer)
95
+ if len(listStockCodes) > 10:
96
+ print(colorText.GREEN + ("=> Done! Fetched %d stock codes." %
97
+ len(listStockCodes)) + colorText.END)
98
+ if self.configManager.shuffleEnabled:
99
+ random.shuffle(listStockCodes)
100
+ print(colorText.BLUE +
101
+ "[+] Stock shuffling is active." + colorText.END)
102
+ else:
103
+ print(colorText.FAIL +
104
+ "[+] Stock shuffling is inactive." + colorText.END)
105
+ if self.configManager.stageTwo:
106
+ print(
107
+ colorText.BLUE + "[+] Screening only for the stocks in Stage-2! Edit User Config to change this." + colorText.END)
108
+ else:
109
+ print(
110
+ colorText.FAIL + "[+] Screening only for the stocks in all Stages! Edit User Config to change this." + colorText.END)
111
+
112
+ else:
113
+ input(
114
+ colorText.FAIL + "=> Error getting stock codes from NSE! Press any key to exit!" + colorText.END)
115
+ sys.exit("Exiting script..")
116
+
117
+ return listStockCodes
118
+
119
+ # Fetch stock price data from Yahoo finance
120
+ def fetchStockData(self, stockCode, period, duration, proxyServer, screenResultsCounter, screenCounter, totalSymbols, printCounter=False):
121
+ with SuppressOutput(suppress_stdout=True, suppress_stderr=True):
122
+ data = yf.download(
123
+ tickers=stockCode+".NS",
124
+ period=period,
125
+ interval=duration,
126
+ proxy=proxyServer,
127
+ progress=False,
128
+ timeout=10
129
+ )
130
+ if printCounter:
131
+ sys.stdout.write("\r\033[K")
132
+ try:
133
+ print(colorText.BOLD + colorText.GREEN + ("[%d%%] Screened %d, Found %d. Fetching data & Analyzing %s..." % (
134
+ int((screenCounter.value/totalSymbols)*100), screenCounter.value, screenResultsCounter.value, stockCode)) + colorText.END, end='')
135
+ except ZeroDivisionError:
136
+ pass
137
+ if len(data) == 0:
138
+ print(colorText.BOLD + colorText.FAIL +
139
+ "=> Failed to fetch!" + colorText.END, end='\r', flush=True)
140
+ raise StockDataEmptyException
141
+ return None
142
+ print(colorText.BOLD + colorText.GREEN + "=> Done!" +
143
+ colorText.END, end='\r', flush=True)
144
+ return data
145
+
146
+ # Get Daily Nifty 50 Index:
147
+ def fetchLatestNiftyDaily(self, proxyServer=None):
148
+ data = yf.download(
149
+ tickers="^NSEI",
150
+ period='5d',
151
+ interval='1d',
152
+ proxy=proxyServer,
153
+ progress=False,
154
+ timeout=10
155
+ )
156
+ return data
157
+
158
+ # Get Data for Five EMA strategy
159
+ def fetchFiveEmaData(self, proxyServer=None):
160
+ nifty_sell = yf.download(
161
+ tickers="^NSEI",
162
+ period='5d',
163
+ interval='5m',
164
+ proxy=proxyServer,
165
+ progress=False,
166
+ timeout=10
167
+ )
168
+ banknifty_sell = yf.download(
169
+ tickers="^NSEBANK",
170
+ period='5d',
171
+ interval='5m',
172
+ proxy=proxyServer,
173
+ progress=False,
174
+ timeout=10
175
+ )
176
+ nifty_buy = yf.download(
177
+ tickers="^NSEI",
178
+ period='5d',
179
+ interval='15m',
180
+ proxy=proxyServer,
181
+ progress=False,
182
+ timeout=10
183
+ )
184
+ banknifty_buy = yf.download(
185
+ tickers="^NSEBANK",
186
+ period='5d',
187
+ interval='15m',
188
+ proxy=proxyServer,
189
+ progress=False,
190
+ timeout=10
191
+ )
192
+ return nifty_buy, banknifty_buy, nifty_sell, banknifty_sell
193
+
194
+ # Load stockCodes from the watchlist.xlsx
195
+ def fetchWatchlist(self):
196
+ createTemplate = False
197
+ data = pd.DataFrame()
198
+ try:
199
+ data = pd.read_excel('watchlist.xlsx')
200
+ except FileNotFoundError:
201
+ print(colorText.BOLD + colorText.FAIL +
202
+ f'[+] watchlist.xlsx not found in f{os.getcwd()}' + colorText.END)
203
+ createTemplate = True
204
+ try:
205
+ if not createTemplate:
206
+ data = data['Stock Code'].values.tolist()
207
+ except KeyError:
208
+ print(colorText.BOLD + colorText.FAIL +
209
+ '[+] Bad Watchlist Format: First Column (A1) should have Header named "Stock Code"' + colorText.END)
210
+ createTemplate = True
211
+ if createTemplate:
212
+ sample = {'Stock Code': ['SBIN', 'INFY', 'TATAMOTORS', 'ITC']}
213
+ sample_data = pd.DataFrame(sample, columns=['Stock Code'])
214
+ sample_data.to_excel('watchlist_template.xlsx',
215
+ index=False, header=True)
216
+ print(colorText.BOLD + colorText.BLUE +
217
+ f'[+] watchlist_template.xlsx created in {os.getcwd()} as a referance template.' + colorText.END)
218
+ return None
219
+ return data
src/classes/OtaUpdater.py ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ import requests
10
+ import os
11
+ import platform
12
+ import sys
13
+ import subprocess
14
+ import requests
15
+
16
+ class OTAUpdater:
17
+
18
+ developmentVersion = 'd'
19
+
20
+ # Download and replace exe through other process for Windows
21
+ def updateForWindows(url):
22
+ batFile = """@echo off
23
+ color a
24
+ echo [+] Screenipy Software Updater!
25
+ echo [+] Downloading Software Update...
26
+ echo [+] This may take some time as per your Internet Speed, Please Wait...
27
+ curl -o screenipy.exe -L """ + url + """
28
+ echo [+] Newly downloaded file saved in %cd%
29
+ echo [+] Software Update Completed! Run'screenipy.exe' again as usual to continue..
30
+ pause
31
+ del updater.bat & exit
32
+ """
33
+ f = open("updater.bat",'w')
34
+ f.write(batFile)
35
+ f.close()
36
+ subprocess.Popen('start updater.bat', shell=True)
37
+ sys.exit(0)
38
+
39
+ # Download and replace bin through other process for Linux
40
+ def updateForLinux(url):
41
+ bashFile = """#!/bin/bash
42
+ echo ""
43
+ echo "[+] Starting Screeni-py updater, Please Wait..."
44
+ sleep 3
45
+ echo "[+] Screenipy Software Updater!"
46
+ echo "[+] Downloading Software Update..."
47
+ echo "[+] This may take some time as per your Internet Speed, Please Wait..."
48
+ wget -q """ + url + """ -O screenipy.bin
49
+ echo "[+] Newly downloaded file saved in $(pwd)"
50
+ chmod +x screenipy.bin
51
+ echo "[+] Update Completed! Run 'screenipy.bin' again as usual to continue.."
52
+ rm updater.sh
53
+ """
54
+ f = open("updater.sh",'w')
55
+ f.write(bashFile)
56
+ f.close()
57
+ subprocess.Popen('bash updater.sh', shell=True)
58
+ sys.exit(0)
59
+
60
+ # Download and replace run through other process for Mac
61
+ def updateForMac(url):
62
+ bashFile = """#!/bin/bash
63
+ echo ""
64
+ echo "[+] Starting Screeni-py updater, Please Wait..."
65
+ sleep 3
66
+ echo "[+] Screenipy Software Updater!"
67
+ echo "[+] Downloading Software Update..."
68
+ echo "[+] This may take some time as per your Internet Speed, Please Wait..."
69
+ curl -o screenipy.run -L """ + url + """
70
+ echo "[+] Newly downloaded file saved in $(pwd)"
71
+ chmod +x screenipy.run
72
+ echo "[+] Update Completed! Run 'screenipy.run' again as usual to continue.."
73
+ rm updater.sh
74
+ """
75
+ f = open("updater.sh",'w')
76
+ f.write(bashFile)
77
+ f.close()
78
+ subprocess.Popen('bash updater.sh', shell=True)
79
+ sys.exit(0)
80
+
81
+ # Parse changelog from release.md
82
+ def showWhatsNew():
83
+ url = "https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/main/src/release.md"
84
+ md = requests.get(url)
85
+ txt = md.text
86
+ txt = txt.split("New?")[1]
87
+ txt = txt.split("## Downloads")[0]
88
+ txt = txt.replace('**','').replace('`','').strip()
89
+ return (txt+"\n")
90
+
91
+ # Check for update and download if available
92
+ def checkForUpdate(proxyServer, VERSION="1.0"):
93
+ OTAUpdater.checkForUpdate.url = None
94
+ try:
95
+ resp = None
96
+ now = float(VERSION)
97
+ if proxyServer:
98
+ resp = requests.get("https://api.github.com/repos/pranjal-joshi/Screeni-py/releases/latest",proxies={'https':proxyServer})
99
+ else:
100
+ resp = requests.get("https://api.github.com/repos/pranjal-joshi/Screeni-py/releases/latest")
101
+ if 'Windows' in platform.system():
102
+ OTAUpdater.checkForUpdate.url = resp.json()['assets'][1]['browser_download_url']
103
+ size = int(resp.json()['assets'][1]['size']/(1024*1024))
104
+ elif 'Darwin' in platform.system():
105
+ OTAUpdater.checkForUpdate.url = resp.json()['assets'][2]['browser_download_url']
106
+ size = int(resp.json()['assets'][2]['size']/(1024*1024))
107
+ else:
108
+ OTAUpdater.checkForUpdate.url = resp.json()['assets'][0]['browser_download_url']
109
+ size = int(resp.json()['assets'][0]['size']/(1024*1024))
110
+ if(float(resp.json()['tag_name']) > now):
111
+ print(colorText.BOLD + colorText.WARN + "[+] What's New in this Update?\n" + OTAUpdater.showWhatsNew() + colorText.END)
112
+ 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()
113
+ if(action == 'y'):
114
+ try:
115
+ if 'Windows' in platform.system():
116
+ OTAUpdater.updateForWindows(OTAUpdater.checkForUpdate.url)
117
+ elif 'Darwin' in platform.system():
118
+ OTAUpdater.updateForMac(OTAUpdater.checkForUpdate.url)
119
+ else:
120
+ OTAUpdater.updateForLinux(OTAUpdater.checkForUpdate.url)
121
+ except Exception as e:
122
+ print(colorText.BOLD + colorText.WARN + '[+] Error occured while updating!' + colorText.END)
123
+ raise(e)
124
+ elif(float(resp.json()['tag_name']) < now):
125
+ print(colorText.BOLD + colorText.FAIL + ('[+] This version (v%s) is in Development mode and unreleased!' % VERSION) + colorText.END)
126
+ return OTAUpdater.developmentVersion
127
+ except Exception as e:
128
+ print(colorText.BOLD + colorText.FAIL + "[+] Failure while checking update!" + colorText.END)
129
+ print(e)
130
+ if OTAUpdater.checkForUpdate.url != None:
131
+ print(colorText.BOLD + colorText.BLUE + ("[+] Download update manually from %s\n" % OTAUpdater.checkForUpdate.url) + colorText.END)
132
+ return
src/classes/ParallelProcessing.py ADDED
@@ -0,0 +1,269 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ from queue import Empty
16
+ from datetime import datetime
17
+ import classes.Fetcher as Fetcher
18
+ import classes.Screener as Screener
19
+ import classes.Utility as Utility
20
+ from classes.CandlePatterns import CandlePatterns
21
+ from classes.ColorText import colorText
22
+ from classes.SuppressOutput import SuppressOutput
23
+
24
+ if sys.platform.startswith('win'):
25
+ import multiprocessing.popen_spawn_win32 as forking
26
+ else:
27
+ import multiprocessing.popen_fork as forking
28
+
29
+
30
+ class StockConsumer(multiprocessing.Process):
31
+
32
+ def __init__(self, task_queue, result_queue, screenCounter, screenResultsCounter, stockDict, proxyServer, keyboardInterruptEvent):
33
+ multiprocessing.Process.__init__(self)
34
+ self.multiprocessingForWindows()
35
+ self.task_queue = task_queue
36
+ self.result_queue = result_queue
37
+ self.screenCounter = screenCounter
38
+ self.screenResultsCounter = screenResultsCounter
39
+ self.stockDict = stockDict
40
+ self.proxyServer = proxyServer
41
+ self.keyboardInterruptEvent = keyboardInterruptEvent
42
+ self.isTradingTime = Utility.tools.isTradingTime()
43
+
44
+ def run(self):
45
+ # while True:
46
+ try:
47
+ while not self.keyboardInterruptEvent.is_set():
48
+ try:
49
+ next_task = self.task_queue.get()
50
+ except Empty:
51
+ continue
52
+ if next_task is None:
53
+ self.task_queue.task_done()
54
+ break
55
+ answer = self.screenStocks(*(next_task))
56
+ self.task_queue.task_done()
57
+ self.result_queue.put(answer)
58
+ except Exception as e:
59
+ sys.exit(0)
60
+
61
+ def screenStocks(self, executeOption, reversalOption, maLength, daysForLowestVolume, minRSI, maxRSI, respChartPattern, insideBarToLookback, totalSymbols,
62
+ configManager, fetcher, screener, candlePatterns, stock, newlyListedOnly, downloadOnly, printCounter=False):
63
+ screenResults = pd.DataFrame(columns=[
64
+ 'Stock', 'Consolidating', 'Breaking-Out', 'MA-Signal', 'Volume', 'LTP', 'RSI', 'Trend', 'Pattern'])
65
+ screeningDictionary = {'Stock': "", 'Consolidating': "", 'Breaking-Out': "",
66
+ 'MA-Signal': "", 'Volume': "", 'LTP': 0, 'RSI': 0, 'Trend': "", 'Pattern': ""}
67
+ saveDictionary = {'Stock': "", 'Consolidating': "", 'Breaking-Out': "",
68
+ 'MA-Signal': "", 'Volume': "", 'LTP': 0, 'RSI': 0, 'Trend': "", 'Pattern': ""}
69
+
70
+ try:
71
+ period = configManager.period
72
+
73
+ # Data download adjustment for Newly Listed only feature
74
+ if newlyListedOnly:
75
+ if int(configManager.period[:-1]) > 250:
76
+ period = '250d'
77
+ else:
78
+ period = configManager.period
79
+
80
+ if (self.stockDict.get(stock) is None) or (configManager.cacheEnabled is False) or self.isTradingTime or downloadOnly:
81
+ data = fetcher.fetchStockData(stock,
82
+ period,
83
+ configManager.duration,
84
+ self.proxyServer,
85
+ self.screenResultsCounter,
86
+ self.screenCounter,
87
+ totalSymbols)
88
+ if configManager.cacheEnabled is True and not self.isTradingTime and (self.stockDict.get(stock) is None) or downloadOnly:
89
+ self.stockDict[stock] = data.to_dict('split')
90
+ if downloadOnly:
91
+ raise Screener.DownloadDataOnly
92
+ else:
93
+ if printCounter:
94
+ try:
95
+ print(colorText.BOLD + colorText.GREEN + ("[%d%%] Screened %d, Found %d. Fetching data & Analyzing %s..." % (
96
+ int((self.screenCounter.value / totalSymbols) * 100), self.screenCounter.value, self.screenResultsCounter.value, stock)) + colorText.END, end='')
97
+ print(colorText.BOLD + colorText.GREEN + "=> Done!" +
98
+ colorText.END, end='\r', flush=True)
99
+ except ZeroDivisionError:
100
+ pass
101
+ sys.stdout.write("\r\033[K")
102
+ data = self.stockDict.get(stock)
103
+ data = pd.DataFrame(
104
+ data['data'], columns=data['columns'], index=data['index'])
105
+
106
+ fullData, processedData = screener.preprocessData(
107
+ data, daysToLookback=configManager.daysToLookback)
108
+
109
+ if newlyListedOnly:
110
+ if not screener.validateNewlyListed(fullData, period):
111
+ raise Screener.NotNewlyListed
112
+
113
+ with self.screenCounter.get_lock():
114
+ self.screenCounter.value += 1
115
+ if not processedData.empty:
116
+ screeningDictionary['Stock'] = colorText.BOLD + \
117
+ colorText.BLUE + f'\x1B]8;;https://in.tradingview.com/chart?symbol=NSE%3A{stock}\x1B\\{stock}\x1B]8;;\x1B\\' + colorText.END
118
+ saveDictionary['Stock'] = stock
119
+ consolidationValue = screener.validateConsolidation(
120
+ processedData, screeningDictionary, saveDictionary, percentage=configManager.consolidationPercentage)
121
+ isMaReversal = screener.validateMovingAverages(
122
+ processedData, screeningDictionary, saveDictionary, maRange=1.25)
123
+ isVolumeHigh = screener.validateVolume(
124
+ processedData, screeningDictionary, saveDictionary, volumeRatio=configManager.volumeRatio)
125
+ isBreaking = screener.findBreakout(
126
+ processedData, screeningDictionary, saveDictionary, daysToLookback=configManager.daysToLookback)
127
+ isLtpValid = screener.validateLTP(
128
+ fullData, screeningDictionary, saveDictionary, minLTP=configManager.minLTP, maxLTP=configManager.maxLTP)
129
+ if executeOption == 4:
130
+ isLowestVolume = screener.validateLowestVolume(processedData, daysForLowestVolume)
131
+ else:
132
+ isLowestVolume = False
133
+ isValidRsi = screener.validateRSI(
134
+ processedData, screeningDictionary, saveDictionary, minRSI, maxRSI)
135
+ try:
136
+ with SuppressOutput(suppress_stderr=True, suppress_stdout=True):
137
+ currentTrend = screener.findTrend(
138
+ processedData,
139
+ screeningDictionary,
140
+ saveDictionary,
141
+ daysToLookback=configManager.daysToLookback,
142
+ stockName=stock)
143
+ except np.RankWarning:
144
+ screeningDictionary['Trend'] = 'Unknown'
145
+ saveDictionary['Trend'] = 'Unknown'
146
+ isCandlePattern = candlePatterns.findPattern(
147
+ processedData, screeningDictionary, saveDictionary)
148
+
149
+ isConfluence = False
150
+ isInsideBar = False
151
+ isIpoBase = False
152
+ if newlyListedOnly:
153
+ isIpoBase = screener.validateIpoBase(stock, fullData, screeningDictionary, saveDictionary)
154
+ if respChartPattern == 3 and executeOption == 7:
155
+ isConfluence = screener.validateConfluence(stock, processedData, screeningDictionary, saveDictionary, percentage=insideBarToLookback)
156
+ else:
157
+ isInsideBar = screener.validateInsideBar(processedData, screeningDictionary, saveDictionary, chartPattern=respChartPattern, daysToLookback=insideBarToLookback)
158
+
159
+ with SuppressOutput(suppress_stderr=True, suppress_stdout=True):
160
+ if maLength is not None and executeOption == 6 and reversalOption == 6:
161
+ isNR = screener.validateNarrowRange(processedData, screeningDictionary, saveDictionary, nr=maLength)
162
+ else:
163
+ isNR = screener.validateNarrowRange(processedData, screeningDictionary, saveDictionary)
164
+
165
+ isMomentum = screener.validateMomentum(processedData, screeningDictionary, saveDictionary)
166
+
167
+ isVSA = False
168
+ if not (executeOption == 7 and respChartPattern < 3):
169
+ isVSA = screener.validateVolumeSpreadAnalysis(processedData, screeningDictionary, saveDictionary)
170
+ if maLength is not None and executeOption == 6 and reversalOption == 4:
171
+ isMaSupport = screener.findReversalMA(fullData, screeningDictionary, saveDictionary, maLength)
172
+
173
+ isVCP = False
174
+ if respChartPattern == 4:
175
+ with SuppressOutput(suppress_stderr=True, suppress_stdout=True):
176
+ isVCP = screener.validateVCP(fullData, screeningDictionary, saveDictionary)
177
+
178
+ isBuyingTrendline = False
179
+ if executeOption == 7 and respChartPattern == 5:
180
+ with SuppressOutput(suppress_stderr=True, suppress_stdout=True):
181
+ isBuyingTrendline = screener.findTrendlines(fullData, screeningDictionary, saveDictionary)
182
+
183
+ with self.screenResultsCounter.get_lock():
184
+ if executeOption == 0:
185
+ self.screenResultsCounter.value += 1
186
+ return screeningDictionary, saveDictionary
187
+ if (executeOption == 1 or executeOption == 2) and isBreaking and isVolumeHigh and isLtpValid:
188
+ self.screenResultsCounter.value += 1
189
+ return screeningDictionary, saveDictionary
190
+ if (executeOption == 1 or executeOption == 3) and (consolidationValue <= configManager.consolidationPercentage and consolidationValue != 0) and isLtpValid:
191
+ self.screenResultsCounter.value += 1
192
+ return screeningDictionary, saveDictionary
193
+ if executeOption == 4 and isLtpValid and isLowestVolume:
194
+ self.screenResultsCounter.value += 1
195
+ return screeningDictionary, saveDictionary
196
+ if executeOption == 5 and isLtpValid and isValidRsi:
197
+ self.screenResultsCounter.value += 1
198
+ return screeningDictionary, saveDictionary
199
+ if executeOption == 6 and isLtpValid:
200
+ if reversalOption == 1:
201
+ if saveDictionary['Pattern'] in CandlePatterns.reversalPatternsBullish or isMaReversal > 0:
202
+ self.screenResultsCounter.value += 1
203
+ return screeningDictionary, saveDictionary
204
+ elif reversalOption == 2:
205
+ if saveDictionary['Pattern'] in CandlePatterns.reversalPatternsBearish or isMaReversal < 0:
206
+ self.screenResultsCounter.value += 1
207
+ return screeningDictionary, saveDictionary
208
+ elif reversalOption == 3 and isMomentum:
209
+ self.screenResultsCounter.value += 1
210
+ return screeningDictionary, saveDictionary
211
+ elif reversalOption == 4 and isMaSupport:
212
+ self.screenResultsCounter.value += 1
213
+ return screeningDictionary, saveDictionary
214
+ elif reversalOption == 5 and isVSA and saveDictionary['Pattern'] in CandlePatterns.reversalPatternsBullish:
215
+ self.screenResultsCounter.value += 1
216
+ return screeningDictionary, saveDictionary
217
+ elif reversalOption == 6 and isNR:
218
+ self.screenResultsCounter.value += 1
219
+ return screeningDictionary, saveDictionary
220
+ if executeOption == 7 and isLtpValid:
221
+ if respChartPattern < 3 and isInsideBar:
222
+ self.screenResultsCounter.value += 1
223
+ return screeningDictionary, saveDictionary
224
+ if isConfluence:
225
+ self.screenResultsCounter.value += 1
226
+ return screeningDictionary, saveDictionary
227
+ if isIpoBase and newlyListedOnly and not respChartPattern < 3:
228
+ self.screenResultsCounter.value += 1
229
+ return screeningDictionary, saveDictionary
230
+ if isVCP:
231
+ self.screenResultsCounter.value += 1
232
+ return screeningDictionary, saveDictionary
233
+ if isBuyingTrendline:
234
+ self.screenResultsCounter.value += 1
235
+ return screeningDictionary, saveDictionary
236
+ except KeyboardInterrupt:
237
+ # Capturing Ctr+C Here isn't a great idea
238
+ pass
239
+ except Fetcher.StockDataEmptyException:
240
+ pass
241
+ except Screener.NotNewlyListed:
242
+ pass
243
+ except Screener.DownloadDataOnly:
244
+ pass
245
+ except KeyError:
246
+ pass
247
+ except Exception as e:
248
+ if printCounter:
249
+ print(colorText.FAIL +
250
+ ("\n[+] Exception Occured while Screening %s! Skipping this stock.." % stock) + colorText.END)
251
+ return
252
+
253
+ def multiprocessingForWindows(self):
254
+ if sys.platform.startswith('win'):
255
+
256
+ class _Popen(forking.Popen):
257
+ def __init__(self, *args, **kw):
258
+ if hasattr(sys, 'frozen'):
259
+ os.putenv('_MEIPASS2', sys._MEIPASS)
260
+ try:
261
+ super(_Popen, self).__init__(*args, **kw)
262
+ finally:
263
+ if hasattr(sys, 'frozen'):
264
+ if hasattr(os, 'unsetenv'):
265
+ os.unsetenv('_MEIPASS2')
266
+ else:
267
+ os.putenv('_MEIPASS2', '')
268
+
269
+ forking.Popen = _Popen
src/classes/Screener.py ADDED
@@ -0,0 +1,719 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ '''
2
+ * Project : Screenipy
3
+ * Author : Pranjal Joshi
4
+ * Created : 28/04/2021
5
+ * Description : Class for analyzing and validating stocks
6
+ '''
7
+
8
+ import sys
9
+ import math
10
+ import numpy as np
11
+ import pandas as pd
12
+ import talib
13
+ import joblib
14
+ import keras
15
+ import classes.Utility as Utility
16
+ from sklearn.preprocessing import StandardScaler
17
+ from scipy.signal import argrelextrema
18
+ from scipy.stats import linregress
19
+ from classes.ColorText import colorText
20
+ from classes.SuppressOutput import SuppressOutput
21
+
22
+
23
+ # Exception for newly listed stocks with candle nos < daysToLookback
24
+ class StockDataNotAdequate(Exception):
25
+ pass
26
+
27
+ # Exception for only downloading stock data and not screening
28
+ class DownloadDataOnly(Exception):
29
+ pass
30
+
31
+ # Exception for stocks which are not newly listed when screening only for Newly Listed
32
+ class NotNewlyListed(Exception):
33
+ pass
34
+
35
+ # This Class contains methods for stock analysis and screening validation
36
+ class tools:
37
+
38
+ def __init__(self, configManager) -> None:
39
+ self.configManager = configManager
40
+
41
+ # Private method to find candle type
42
+ # True = Bullish, False = Bearish
43
+ def getCandleType(self, dailyData):
44
+ return bool(dailyData['Close'][0] >= dailyData['Open'][0])
45
+
46
+
47
+ # Preprocess the acquired data
48
+ def preprocessData(self, data, daysToLookback=None):
49
+ if daysToLookback is None:
50
+ daysToLookback = self.configManager.daysToLookback
51
+ if self.configManager.useEMA:
52
+ sma = talib.EMA(data['Close'],timeperiod=50)
53
+ lma = talib.EMA(data['Close'],timeperiod=200)
54
+ data.insert(6,'SMA',sma)
55
+ data.insert(7,'LMA',lma)
56
+ else:
57
+ sma = data.rolling(window=50).mean()
58
+ lma = data.rolling(window=200).mean()
59
+ data.insert(6,'SMA',sma['Close'])
60
+ data.insert(7,'LMA',lma['Close'])
61
+ vol = data.rolling(window=20).mean()
62
+ rsi = talib.RSI(data['Close'], timeperiod=14)
63
+ data.insert(8,'VolMA',vol['Volume'])
64
+ data.insert(9,'RSI',rsi)
65
+ data = data[::-1] # Reverse the dataframe
66
+ # data = data.fillna(0)
67
+ # data = data.replace([np.inf, -np.inf], 0)
68
+ fullData = data
69
+ trimmedData = data.head(daysToLookback)
70
+ return (fullData, trimmedData)
71
+
72
+ # Validate LTP within limits
73
+ def validateLTP(self, data, screenDict, saveDict, minLTP=None, maxLTP=None):
74
+ if minLTP is None:
75
+ minLTP = self.configManager.minLTP
76
+ if maxLTP is None:
77
+ maxLTP = self.configManager.maxLTP
78
+ data = data.fillna(0)
79
+ data = data.replace([np.inf, -np.inf], 0)
80
+ recent = data.head(1)
81
+
82
+ pct_change = (data[::-1]['Close'].pct_change() * 100).iloc[-1]
83
+ if pct_change > 0.2:
84
+ pct_change = colorText.GREEN + (" (%.1f%%)" % pct_change) + colorText.END
85
+ elif pct_change < -0.2:
86
+ pct_change = colorText.FAIL + (" (%.1f%%)" % pct_change) + colorText.END
87
+ else:
88
+ pct_change = colorText.WARN + (" (%.1f%%)" % pct_change) + colorText.END
89
+
90
+ ltp = round(recent['Close'][0],2)
91
+ saveDict['LTP'] = str(ltp)
92
+ verifyStageTwo = True
93
+ if self.configManager.stageTwo and len(data) > 250:
94
+ yearlyLow = data.head(250).min()['Close']
95
+ yearlyHigh = data.head(250).max()['Close']
96
+ if ltp < (2 * yearlyLow) or ltp < (0.75 * yearlyHigh):
97
+ verifyStageTwo = False
98
+ if(ltp >= minLTP and ltp <= maxLTP and verifyStageTwo):
99
+ screenDict['LTP'] = colorText.GREEN + ("%.2f" % ltp) + pct_change + colorText.END
100
+ return True
101
+ screenDict['LTP'] = colorText.FAIL + ("%.2f" % ltp) + pct_change + colorText.END
102
+ return False
103
+
104
+ # Validate if share prices are consolidating
105
+ def validateConsolidation(self, data, screenDict, saveDict, percentage=10):
106
+ data = data.fillna(0)
107
+ data = data.replace([np.inf, -np.inf], 0)
108
+ hc = data.describe()['Close']['max']
109
+ lc = data.describe()['Close']['min']
110
+ if ((hc - lc) <= (hc*percentage/100) and (hc - lc != 0)):
111
+ screenDict['Consolidating'] = colorText.BOLD + colorText.GREEN + "Range = " + str(round((abs((hc-lc)/hc)*100),1))+"%" + colorText.END
112
+ else:
113
+ screenDict['Consolidating'] = colorText.BOLD + colorText.FAIL + "Range = " + str(round((abs((hc-lc)/hc)*100),1)) + "%" + colorText.END
114
+ saveDict['Consolidating'] = str(round((abs((hc-lc)/hc)*100),1))+"%"
115
+ return round((abs((hc-lc)/hc)*100),1)
116
+
117
+ # Validate Moving averages and look for buy/sell signals
118
+ def validateMovingAverages(self, data, screenDict, saveDict, maRange=2.5):
119
+ data = data.fillna(0)
120
+ data = data.replace([np.inf, -np.inf], 0)
121
+ recent = data.head(1)
122
+ if(recent['SMA'][0] > recent['LMA'][0] and recent['Close'][0] > recent['SMA'][0]):
123
+ screenDict['MA-Signal'] = colorText.BOLD + colorText.GREEN + 'Bullish' + colorText.END
124
+ saveDict['MA-Signal'] = 'Bullish'
125
+ elif(recent['SMA'][0] < recent['LMA'][0]):
126
+ screenDict['MA-Signal'] = colorText.BOLD + colorText.FAIL + 'Bearish' + colorText.END
127
+ saveDict['MA-Signal'] = 'Bearish'
128
+ elif(recent['SMA'][0] == 0):
129
+ screenDict['MA-Signal'] = colorText.BOLD + colorText.WARN + 'Unknown' + colorText.END
130
+ saveDict['MA-Signal'] = 'Unknown'
131
+ else:
132
+ screenDict['MA-Signal'] = colorText.BOLD + colorText.WARN + 'Neutral' + colorText.END
133
+ saveDict['MA-Signal'] = 'Neutral'
134
+
135
+ smaDev = data['SMA'][0] * maRange / 100
136
+ lmaDev = data['LMA'][0] * maRange / 100
137
+ open, high, low, close, sma, lma = data['Open'][0], data['High'][0], data['Low'][0], data['Close'][0], data['SMA'][0], data['LMA'][0]
138
+ maReversal = 0
139
+ # Taking Support 50
140
+ if close > sma and low <= (sma + smaDev):
141
+ screenDict['MA-Signal'] = colorText.BOLD + colorText.GREEN + '50MA-Support' + colorText.END
142
+ saveDict['MA-Signal'] = '50MA-Support'
143
+ maReversal = 1
144
+ # Validating Resistance 50
145
+ elif close < sma and high >= (sma - smaDev):
146
+ screenDict['MA-Signal'] = colorText.BOLD + colorText.FAIL + '50MA-Resist' + colorText.END
147
+ saveDict['MA-Signal'] = '50MA-Resist'
148
+ maReversal = -1
149
+ # Taking Support 200
150
+ elif close > lma and low <= (lma + lmaDev):
151
+ screenDict['MA-Signal'] = colorText.BOLD + colorText.GREEN + '200MA-Support' + colorText.END
152
+ saveDict['MA-Signal'] = '200MA-Support'
153
+ maReversal = 1
154
+ # Validating Resistance 200
155
+ elif close < lma and high >= (lma - lmaDev):
156
+ screenDict['MA-Signal'] = colorText.BOLD + colorText.FAIL + '200MA-Resist' + colorText.END
157
+ saveDict['MA-Signal'] = '200MA-Resist'
158
+ maReversal = -1
159
+ # For a Bullish Candle
160
+ if self.getCandleType(data):
161
+ # Crossing up 50
162
+ if open < sma and close > sma:
163
+ screenDict['MA-Signal'] = colorText.BOLD + colorText.GREEN + 'BullCross-50MA' + colorText.END
164
+ saveDict['MA-Signal'] = 'BullCross-50MA'
165
+ maReversal = 1
166
+ # Crossing up 200
167
+ elif open < lma and close > lma:
168
+ screenDict['MA-Signal'] = colorText.BOLD + colorText.GREEN + 'BullCross-200MA' + colorText.END
169
+ saveDict['MA-Signal'] = 'BullCross-200MA'
170
+ maReversal = 1
171
+ # For a Bearish Candle
172
+ elif not self.getCandleType(data):
173
+ # Crossing down 50
174
+ if open > sma and close < sma:
175
+ screenDict['MA-Signal'] = colorText.BOLD + colorText.FAIL + 'BearCross-50MA' + colorText.END
176
+ saveDict['MA-Signal'] = 'BearCross-50MA'
177
+ maReversal = -1
178
+ # Crossing up 200
179
+ elif open > lma and close < lma:
180
+ screenDict['MA-Signal'] = colorText.BOLD + colorText.FAIL + 'BearCross-200MA' + colorText.END
181
+ saveDict['MA-Signal'] = 'BearCross-200MA'
182
+ maReversal = -1
183
+ return maReversal
184
+
185
+ # Validate if volume of last day is higher than avg
186
+ def validateVolume(self, data, screenDict, saveDict, volumeRatio=2.5):
187
+ data = data.fillna(0)
188
+ data = data.replace([np.inf, -np.inf], 0)
189
+ recent = data.head(1)
190
+ if recent['VolMA'][0] == 0: # Handles Divide by 0 warning
191
+ saveDict['Volume'] = "Unknown"
192
+ screenDict['Volume'] = colorText.BOLD + colorText.WARN + "Unknown" + colorText.END
193
+ return True
194
+ ratio = round(recent['Volume'][0]/recent['VolMA'][0],2)
195
+ saveDict['Volume'] = str(ratio)+"x"
196
+ if(ratio >= volumeRatio and ratio != np.nan and (not math.isinf(ratio)) and (ratio != 20)):
197
+ screenDict['Volume'] = colorText.BOLD + colorText.GREEN + str(ratio) + "x" + colorText.END
198
+ return True
199
+ screenDict['Volume'] = colorText.BOLD + colorText.FAIL + str(ratio) + "x" + colorText.END
200
+ return False
201
+
202
+ # Find accurate breakout value
203
+ def findBreakout(self, data, screenDict, saveDict, daysToLookback):
204
+ data = data.fillna(0)
205
+ data = data.replace([np.inf, -np.inf], 0)
206
+ recent = data.head(1)
207
+ data = data[1:]
208
+ hs = round(data.describe()['High']['max'],2)
209
+ hc = round(data.describe()['Close']['max'],2)
210
+ rc = round(recent['Close'][0],2)
211
+ if np.isnan(hc) or np.isnan(hs):
212
+ saveDict['Breaking-Out'] = 'BO: Unknown'
213
+ screenDict['Breaking-Out'] = colorText.BOLD + colorText.WARN + 'BO: Unknown' + colorText.END
214
+ return False
215
+ if hs > hc:
216
+ if ((hs - hc) <= (hs*2/100)):
217
+ saveDict['Breaking-Out'] = str(hc)
218
+ if rc >= hc:
219
+ screenDict['Breaking-Out'] = colorText.BOLD + colorText.GREEN + "BO: " + str(hc) + " R: " + str(hs) + colorText.END
220
+ return True and self.getCandleType(recent)
221
+ screenDict['Breaking-Out'] = colorText.BOLD + colorText.FAIL + "BO: " + str(hc) + " R: " + str(hs) + colorText.END
222
+ return False
223
+ noOfHigherShadows = len(data[data.High > hc])
224
+ if(daysToLookback/noOfHigherShadows <= 3):
225
+ saveDict['Breaking-Out'] = str(hs)
226
+ if rc >= hs:
227
+ screenDict['Breaking-Out'] = colorText.BOLD + colorText.GREEN + "BO: " + str(hs) + colorText.END
228
+ return True and self.getCandleType(recent)
229
+ screenDict['Breaking-Out'] = colorText.BOLD + colorText.FAIL + "BO: " + str(hs) + colorText.END
230
+ return False
231
+ saveDict['Breaking-Out'] = str(hc) + ", " + str(hs)
232
+ if rc >= hc:
233
+ screenDict['Breaking-Out'] = colorText.BOLD + colorText.GREEN + "BO: " + str(hc) + " R: " + str(hs) + colorText.END
234
+ return True and self.getCandleType(recent)
235
+ screenDict['Breaking-Out'] = colorText.BOLD + colorText.FAIL + "BO: " + str(hc) + " R: " + str(hs) + colorText.END
236
+ return False
237
+ else:
238
+ saveDict['Breaking-Out'] = str(hc)
239
+ if rc >= hc:
240
+ screenDict['Breaking-Out'] = colorText.BOLD + colorText.GREEN + "BO: " + str(hc) + colorText.END
241
+ return True and self.getCandleType(recent)
242
+ screenDict['Breaking-Out'] = colorText.BOLD + colorText.FAIL + "BO: " + str(hc) + colorText.END
243
+ return False
244
+
245
+ # Validate 'Inside Bar' structure for recent days
246
+ def validateInsideBar(self, data, screenDict, saveDict, chartPattern=1, daysToLookback=5):
247
+ orgData = data
248
+ for i in range(daysToLookback, round(daysToLookback*0.5)-1, -1):
249
+ if i == 2:
250
+ return 0 # Exit if only last 2 candles are left
251
+ if chartPattern == 1:
252
+ if "Up" in saveDict['Trend'] and ("Bull" in saveDict['MA-Signal'] or "Support" in saveDict['MA-Signal']):
253
+ data = orgData.head(i)
254
+ refCandle = data.tail(1)
255
+ if (len(data.High[data.High > refCandle.High.item()]) == 0) and (len(data.Low[data.Low < refCandle.Low.item()]) == 0) and (len(data.Open[data.Open > refCandle.High.item()]) == 0) and (len(data.Close[data.Close < refCandle.Low.item()]) == 0):
256
+ screenDict['Pattern'] = colorText.BOLD + colorText.WARN + ("Inside Bar (%d)" % i) + colorText.END
257
+ saveDict['Pattern'] = "Inside Bar (%d)" % i
258
+ return i
259
+ else:
260
+ return 0
261
+ else:
262
+ if "Down" in saveDict['Trend'] and ("Bear" in saveDict['MA-Signal'] or "Resist" in saveDict['MA-Signal']):
263
+ data = orgData.head(i)
264
+ refCandle = data.tail(1)
265
+ if (len(data.High[data.High > refCandle.High.item()]) == 0) and (len(data.Low[data.Low < refCandle.Low.item()]) == 0) and (len(data.Open[data.Open > refCandle.High.item()]) == 0) and (len(data.Close[data.Close < refCandle.Low.item()]) == 0):
266
+ screenDict['Pattern'] = colorText.BOLD + colorText.WARN + ("Inside Bar (%d)" % i) + colorText.END
267
+ saveDict['Pattern'] = "Inside Bar (%d)" % i
268
+ return i
269
+ else:
270
+ return 0
271
+ return 0
272
+
273
+ # Validate if recent volume is lowest of last 'N' Days
274
+ def validateLowestVolume(self, data, daysForLowestVolume):
275
+ data = data.fillna(0)
276
+ data = data.replace([np.inf, -np.inf], 0)
277
+ if daysForLowestVolume is None:
278
+ daysForLowestVolume = 30
279
+ data = data.head(daysForLowestVolume)
280
+ recent = data.head(1)
281
+ if((recent['Volume'][0] <= data.describe()['Volume']['min']) and recent['Volume'][0] != np.nan):
282
+ return True
283
+ return False
284
+
285
+ # validate if RSI is within given range
286
+ def validateRSI(self, data, screenDict, saveDict, minRSI, maxRSI):
287
+ data = data.fillna(0)
288
+ data = data.replace([np.inf, -np.inf], 0)
289
+ rsi = int(data.head(1)['RSI'][0])
290
+ saveDict['RSI'] = rsi
291
+ if(rsi >= minRSI and rsi <= maxRSI) and (rsi <= 70 and rsi >= 30):
292
+ screenDict['RSI'] = colorText.BOLD + colorText.GREEN + str(rsi) + colorText.END
293
+ return True
294
+ screenDict['RSI'] = colorText.BOLD + colorText.FAIL + str(rsi) + colorText.END
295
+ return False
296
+
297
+ # Find out trend for days to lookback
298
+ def findTrend(self, data, screenDict, saveDict, daysToLookback=None,stockName=""):
299
+ if daysToLookback is None:
300
+ daysToLookback = self.configManager.daysToLookback
301
+ data = data.head(daysToLookback)
302
+ data = data[::-1]
303
+ data = data.set_index(np.arange(len(data)))
304
+ data = data.fillna(0)
305
+ data = data.replace([np.inf, -np.inf], 0)
306
+ with SuppressOutput(suppress_stdout=True,suppress_stderr=True):
307
+ data['tops'] = data['Close'].iloc[list(argrelextrema(np.array(data['Close']), np.greater_equal, order=1)[0])]
308
+ data = data.fillna(0)
309
+ data = data.replace([np.inf, -np.inf], 0)
310
+ try:
311
+ try:
312
+ if len(data) < daysToLookback:
313
+ raise StockDataNotAdequate
314
+ slope,c = np.polyfit(data.index[data.tops > 0], data['tops'][data.tops > 0], 1)
315
+ except Exception as e:
316
+ slope,c = 0,0
317
+ angle = np.rad2deg(np.arctan(slope))
318
+ if (angle == 0):
319
+ screenDict['Trend'] = colorText.BOLD + colorText.WARN + "Unknown" + colorText.END
320
+ saveDict['Trend'] = 'Unknown'
321
+ elif (angle <= 30 and angle >= -30):
322
+ screenDict['Trend'] = colorText.BOLD + colorText.WARN + "Sideways" + colorText.END
323
+ saveDict['Trend'] = 'Sideways'
324
+ elif (angle >= 30 and angle < 61):
325
+ screenDict['Trend'] = colorText.BOLD + colorText.GREEN + "Weak Up" + colorText.END
326
+ saveDict['Trend'] = 'Weak Up'
327
+ elif angle >= 60:
328
+ screenDict['Trend'] = colorText.BOLD + colorText.GREEN + "Strong Up" + colorText.END
329
+ saveDict['Trend'] = 'Strong Up'
330
+ elif (angle <= -30 and angle > -61):
331
+ screenDict['Trend'] = colorText.BOLD + colorText.FAIL + "Weak Down" + colorText.END
332
+ saveDict['Trend'] = 'Weak Down'
333
+ elif angle <= -60:
334
+ screenDict['Trend'] = colorText.BOLD + colorText.FAIL + "Strong Down" + colorText.END
335
+ saveDict['Trend'] = 'Strong Down'
336
+ except np.linalg.LinAlgError:
337
+ screenDict['Trend'] = colorText.BOLD + colorText.WARN + "Unknown" + colorText.END
338
+ saveDict['Trend'] = 'Unknown'
339
+ return saveDict['Trend']
340
+
341
+ # Find if stock is validating volume spread analysis
342
+ def validateVolumeSpreadAnalysis(self, data, screenDict, saveDict):
343
+ try:
344
+ data = data.head(2)
345
+ try:
346
+ # Check for previous RED candles
347
+ # Current candle = 0th, Previous Candle = 1st for following logic
348
+ if data.iloc[1]['Open'] >= data.iloc[1]['Close']:
349
+ spread1 = abs(data.iloc[1]['Open'] - data.iloc[1]['Close'])
350
+ spread0 = abs(data.iloc[0]['Open'] - data.iloc[0]['Close'])
351
+ lower_wick_spread0 = max(data.iloc[0]['Open'], data.iloc[0]['Close']) - data.iloc[0]['Low']
352
+ vol1 = data.iloc[1]['Volume']
353
+ vol0 = data.iloc[0]['Volume']
354
+ if spread0 > spread1 and vol0 < vol1 and data.iloc[0]['Volume'] < data.iloc[0]['VolMA'] and data.iloc[0]['Close'] <= data.iloc[1]['Open'] and spread0 < lower_wick_spread0 and data.iloc[0]['Volume'] <= int(data.iloc[1]['Volume']*0.75):
355
+ screenDict['Pattern'] = colorText.BOLD + colorText.GREEN + 'Supply Drought' + colorText.END
356
+ saveDict['Pattern'] = 'Supply Drought'
357
+ return True
358
+ if spread0 < spread1 and vol0 > vol1 and data.iloc[0]['Volume'] > data.iloc[0]['VolMA'] and data.iloc[0]['Close'] <= data.iloc[1]['Open']:
359
+ screenDict['Pattern'] = colorText.BOLD + colorText.GREEN + 'Demand Rise' + colorText.END
360
+ saveDict['Pattern'] = 'Demand Rise'
361
+ return True
362
+ except IndexError:
363
+ pass
364
+ return False
365
+ except:
366
+ import traceback
367
+ traceback.print_exc()
368
+ return False
369
+
370
+ # Find if stock gaining bullish momentum
371
+ def validateMomentum(self, data, screenDict, saveDict):
372
+ try:
373
+ data = data.head(3)
374
+ for row in data.iterrows():
375
+ # All 3 candles should be Green and NOT Circuits
376
+ if row[1]['Close'].item() <= row[1]['Open'].item():
377
+ return False
378
+ openDesc = data.sort_values(by=['Open'], ascending=False)
379
+ closeDesc = data.sort_values(by=['Close'], ascending=False)
380
+ volDesc = data.sort_values(by=['Volume'], ascending=False)
381
+ try:
382
+ if data.equals(openDesc) and data.equals(closeDesc) and data.equals(volDesc):
383
+ if (data['Open'][0].item() >= data['Close'][1].item()) and (data['Open'][1].item() >= data['Close'][2].item()):
384
+ screenDict['Pattern'] = colorText.BOLD + colorText.GREEN + 'Momentum Gainer' + colorText.END
385
+ saveDict['Pattern'] = 'Momentum Gainer'
386
+ return True
387
+ except IndexError:
388
+ pass
389
+ return False
390
+ except Exception as e:
391
+ import traceback
392
+ traceback.print_exc()
393
+ return False
394
+
395
+ # Find stock reversing at given MA
396
+ def findReversalMA(self, data, screenDict, saveDict, maLength, percentage=0.015):
397
+ if maLength is None:
398
+ maLength = 20
399
+ data = data[::-1]
400
+ if self.configManager.useEMA:
401
+ maRev = talib.EMA(data['Close'],timeperiod=maLength)
402
+ else:
403
+ maRev = talib.MA(data['Close'],timeperiod=maLength)
404
+ data.insert(10,'maRev',maRev)
405
+ data = data[::-1].head(3)
406
+ if data.equals(data[(data.Close >= (data.maRev - (data.maRev*percentage))) & (data.Close <= (data.maRev + (data.maRev*percentage)))]) and data.head(1)['Close'][0] >= data.head(1)['maRev'][0]:
407
+ screenDict['MA-Signal'] = colorText.BOLD + colorText.GREEN + f'Reversal-{maLength}MA' + colorText.END
408
+ saveDict['MA-Signal'] = f'Reversal-{maLength}MA'
409
+ return True
410
+ return False
411
+
412
+ # Find IPO base
413
+ def validateIpoBase(self, stock, data, screenDict, saveDict, percentage=0.3):
414
+ listingPrice = data[::-1].head(1)['Open'][0]
415
+ currentPrice = data.head(1)['Close'][0]
416
+ ATH = data.describe()['High']['max']
417
+ if ATH > (listingPrice + (listingPrice * percentage)):
418
+ return False
419
+ away = round(((currentPrice - listingPrice)/listingPrice)*100, 1)
420
+ if((listingPrice - (listingPrice * percentage)) <= currentPrice <= (listingPrice + (listingPrice * percentage))):
421
+ if away > 0:
422
+ screenDict['Pattern'] = colorText.BOLD + colorText.GREEN + f'IPO Base ({away} %)' + colorText.END
423
+ else:
424
+ screenDict['Pattern'] = colorText.BOLD + colorText.GREEN + 'IPO Base ' + colorText.FAIL + f'({away} %)' + colorText.END
425
+ saveDict['Pattern'] = f'IPO Base ({away} %)'
426
+ return True
427
+ return False
428
+
429
+ # Find Conflucence
430
+ def validateConfluence(self, stock, data, screenDict, saveDict, percentage=0.1):
431
+ recent = data.head(1)
432
+ if(abs(recent['SMA'][0] - recent['LMA'][0]) <= (recent['SMA'][0] * percentage)):
433
+ difference = round(abs(recent['SMA'][0] - recent['LMA'][0])/recent['Close'][0] * 100,2)
434
+ if recent['SMA'][0] >= recent['LMA'][0]:
435
+ screenDict['MA-Signal'] = colorText.BOLD + colorText.GREEN + f'Confluence ({difference}%)' + colorText.END
436
+ saveDict['MA-Signal'] = f'Confluence ({difference}%)'
437
+ else:
438
+ screenDict['MA-Signal'] = colorText.BOLD + colorText.FAIL + f'Confluence ({difference}%)' + colorText.END
439
+ saveDict['MA-Signal'] = f'Confluence ({difference}%)'
440
+ return True
441
+ return False
442
+
443
+ # Find if stock is newly listed
444
+ def validateNewlyListed(self, data, daysToLookback):
445
+ daysToLookback = int(daysToLookback[:-1])
446
+ recent = data.head(1)
447
+ if len(data) < daysToLookback and (recent['Close'][0] != np.nan and recent['Close'][0] > 0):
448
+ return True
449
+ return False
450
+
451
+ # Find stocks approching to long term trendlines
452
+ def findTrendlines(self, data, screenDict, saveDict, percentage = 0.05):
453
+ period = int(''.join(c for c in self.configManager.period if c.isdigit()))
454
+ if len(data) < period:
455
+ return False
456
+
457
+ data = data[::-1]
458
+ data['Number'] = np.arange(len(data))+1
459
+ data_high = data.copy()
460
+ data_low = data.copy()
461
+ points = 30
462
+
463
+ ''' Ignoring the Resitance for long-term purpose
464
+ while len(data_high) > points:
465
+ slope, intercept, r_value, p_value, std_err = linregress(x=data_high['Number'], y=data_high['High'])
466
+ data_high = data_high.loc[data_high['High'] > slope * data_high['Number'] + intercept]
467
+ slope, intercept, r_value, p_value, std_err = linregress(x=data_high['Number'], y=data_high['Close'])
468
+ data['Resistance'] = slope * data['Number'] + intercept
469
+ '''
470
+
471
+ while len(data_low) > points:
472
+ slope, intercept, r_value, p_value, std_err = linregress(x=data_low['Number'], y=data_low['Low'])
473
+ data_low = data_low.loc[data_low['Low'] < slope * data_low['Number'] + intercept]
474
+
475
+ slope, intercept, r_value, p_value, std_err = linregress(x=data_low['Number'], y=data_low['Close'])
476
+ data['Support'] = slope * data['Number'] + intercept
477
+ now = data.tail(1)
478
+
479
+ limit_upper = now['Support'][0].item() + (now['Support'][0].item() * percentage)
480
+ limit_lower = now['Support'][0].item() - (now['Support'][0].item() * percentage)
481
+
482
+ if limit_lower < now['Close'][0].item() < limit_upper and slope > 0.15:
483
+ screenDict['Pattern'] = colorText.BOLD + colorText.GREEN + 'Trendline-Support' + colorText.END
484
+ saveDict['Pattern'] = 'Trendline-Support'
485
+ return True
486
+
487
+ ''' Plots for debugging
488
+ import matplotlib.pyplot as plt
489
+ fig, ax1 = plt.subplots(figsize=(15,10))
490
+ color = 'tab:green'
491
+ xdate = [x.date() for x in data.index]
492
+ ax1.set_xlabel('Date', color=color)
493
+ ax1.plot(xdate, data.Close, label="close", color=color)
494
+ ax1.tick_params(axis='x', labelcolor=color)
495
+
496
+ ax2 = ax1.twiny() # ax2 and ax1 will have common y axis and different x axis, twiny
497
+ ax2.plot(data.Number, data.Resistance, label="Res")
498
+ ax2.plot(data.Number, data.Support, label="Sup")
499
+
500
+ plt.legend()
501
+ plt.grid()
502
+ plt.show()
503
+ '''
504
+ return False
505
+
506
+
507
+ # Find NRx range for Reversal
508
+ def validateNarrowRange(self, data, screenDict, saveDict, nr=4):
509
+ if Utility.tools.isTradingTime():
510
+ rangeData = data.head(nr+1)[1:]
511
+ now_candle = data.head(1)
512
+ rangeData['Range'] = abs(rangeData['Close'] - rangeData['Open'])
513
+ recent = rangeData.head(1)
514
+ if recent['Range'][0] == rangeData.describe()['Range']['min']:
515
+ if self.getCandleType(recent) and now_candle['Close'][0] >= recent['Close'][0]:
516
+ screenDict['Pattern'] = colorText.BOLD + colorText.GREEN + f'Buy-NR{nr}' + colorText.END
517
+ saveDict['Pattern'] = f'Buy-NR{nr}'
518
+ return True
519
+ elif not self.getCandleType(recent) and now_candle['Close'][0] <= recent['Close'][0]:
520
+ screenDict['Pattern'] = colorText.BOLD + colorText.FAIL + f'Sell-NR{nr}' + colorText.END
521
+ saveDict['Pattern'] = f'Sell-NR{nr}'
522
+ return True
523
+ return False
524
+ else:
525
+ rangeData = data.head(nr)
526
+ rangeData['Range'] = abs(rangeData['Close'] - rangeData['Open'])
527
+ recent = rangeData.head(1)
528
+ if recent['Range'][0] == rangeData.describe()['Range']['min']:
529
+ screenDict['Pattern'] = colorText.BOLD + colorText.GREEN + f'NR{nr}' + colorText.END
530
+ saveDict['Pattern'] = f'NR{nr}'
531
+ return True
532
+ return False
533
+
534
+ # Validate VPC
535
+ def validateVCP(self, data, screenDict, saveDict, stockName=None, window=3, percentageFromTop=3):
536
+ try:
537
+ percentageFromTop /= 100
538
+ data.reset_index(inplace=True)
539
+ data.rename(columns={'index':'Date'}, inplace=True)
540
+ data['tops'] = data['High'].iloc[list(argrelextrema(np.array(data['High']), np.greater_equal, order=window)[0])].head(4)
541
+ data['bots'] = data['Low'].iloc[list(argrelextrema(np.array(data['Low']), np.less_equal, order=window)[0])].head(4)
542
+ data = data.fillna(0)
543
+ data = data.replace([np.inf, -np.inf], 0)
544
+ tops = data[data.tops > 0]
545
+ bots = data[data.bots > 0]
546
+ highestTop = round(tops.describe()['High']['max'],1)
547
+ filteredTops = tops[tops.tops > (highestTop-(highestTop*percentageFromTop))]
548
+ # print(tops)
549
+ # print(filteredTops)
550
+ # print(tops.sort_values(by=['tops'], ascending=False))
551
+ # print(tops.describe())
552
+ # print(f"Till {highestTop-(highestTop*percentageFromTop)}")
553
+ if(filteredTops.equals(tops)): # Tops are in the range
554
+ lowPoints = []
555
+ for i in range(len(tops)-1):
556
+ endDate = tops.iloc[i]['Date']
557
+ startDate = tops.iloc[i+1]['Date']
558
+ lowPoints.append(data[(data.Date >= startDate) & (data.Date <= endDate)].describe()['Low']['min'])
559
+ lowPointsOrg = lowPoints
560
+ lowPoints.sort(reverse=True)
561
+ lowPointsSorted = lowPoints
562
+ ltp = data.head(1)['Close'][0]
563
+ if lowPointsOrg == lowPointsSorted and ltp < highestTop and ltp > lowPoints[0]:
564
+ screenDict['Pattern'] = colorText.BOLD + colorText.GREEN + f'VCP (BO: {highestTop})' + colorText.END
565
+ saveDict['Pattern'] = f'VCP (BO: {highestTop})'
566
+ return True
567
+ except Exception as e:
568
+ import traceback
569
+ print(traceback.format_exc())
570
+ return False
571
+
572
+ def getNiftyPrediction(self, data, proxyServer):
573
+ import warnings
574
+ warnings.filterwarnings("ignore")
575
+ model, pkl = Utility.tools.getNiftyModel(proxyServer=proxyServer)
576
+ with SuppressOutput(suppress_stderr=True, suppress_stdout=True):
577
+ data = data[pkl['columns']]
578
+ ### v2 Preprocessing
579
+ data['High'] = data['High'].pct_change() * 100
580
+ data['Low'] = data['Low'].pct_change() * 100
581
+ data['Open'] = data['Open'].pct_change() * 100
582
+ data['Close'] = data['Close'].pct_change() * 100
583
+ data = data.iloc[-1]
584
+ ###
585
+ data = pkl['scaler'].transform([data])
586
+ pred = model.predict(data)[0]
587
+ if pred > 0.5:
588
+ out = colorText.BOLD + colorText.FAIL + "BEARISH" + colorText.END + colorText.BOLD
589
+ sug = "Hold your Short position!"
590
+ else:
591
+ out = colorText.BOLD + colorText.GREEN + "BULLISH" + colorText.END + colorText.BOLD
592
+ sug = "Stay Bullish!"
593
+ if not Utility.tools.isClosingHour():
594
+ print(colorText.BOLD + colorText.WARN + "Note: The AI prediction should be executed After 3 PM or Near to Closing time as the Prediction Accuracy is based on the Closing price!" + colorText.END)
595
+ print(colorText.BOLD + colorText.BLUE + "\n" + "[+] Nifty AI Prediction -> " + colorText.END + colorText.BOLD + "Market may Open {} next day! {}".format(out, sug) + colorText.END)
596
+ print(colorText.BOLD + colorText.BLUE + "\n" + "[+] Nifty AI Prediction -> " + colorText.END + "Probability/Strength of Prediction = {}%".format(Utility.tools.getSigmoidConfidence(pred[0])))
597
+ return pred
598
+
599
+ def monitorFiveEma(self, proxyServer, fetcher, result_df, last_signal, risk_reward = 3):
600
+ col_names = ['High', 'Low', 'Close', '5EMA']
601
+ data_list = ['nifty_buy', 'banknifty_buy', 'nifty_sell', 'banknifty_sell']
602
+
603
+ data_tuple = fetcher.fetchFiveEmaData()
604
+ for cnt in range(len(data_tuple)):
605
+ d = data_tuple[cnt]
606
+ d['5EMA'] = talib.EMA(d['Close'],timeperiod=5)
607
+ d = d[col_names]
608
+ d = d.dropna().round(2)
609
+
610
+ with SuppressOutput(suppress_stderr=True, suppress_stdout=True):
611
+ if 'sell' in data_list[cnt]:
612
+ streched = d[(d.Low > d['5EMA']) & (d.Low - d['5EMA'] > 0.5)]
613
+ streched['SL'] = streched.High
614
+ validate = d[(d.Low.shift(1) > d['5EMA'].shift(1)) & (d.Low.shift(1) - d['5EMA'].shift(1) > 0.5)]
615
+ old_index = validate.index
616
+ else:
617
+ mask = (d.High < d['5EMA']) & (d['5EMA'] - d.High > 0.5) # Buy
618
+ streched = d[mask]
619
+ streched['SL'] = streched.Low
620
+ validate = d.loc[mask.shift(1).fillna(False)]
621
+ old_index = validate.index
622
+ tgt = pd.DataFrame((validate.Close.reset_index(drop=True) - ((streched.SL.reset_index(drop=True) - validate.Close.reset_index(drop=True)) * risk_reward)),columns=['Target'])
623
+ validate = pd.concat([
624
+ validate.reset_index(drop=True),
625
+ streched['SL'].reset_index(drop=True),
626
+ tgt,
627
+ ],
628
+ axis=1
629
+ )
630
+ validate = validate.tail(len(old_index))
631
+ validate = validate.set_index(old_index)
632
+ if 'sell' in data_list[cnt]:
633
+ final = validate[validate.Close < validate['5EMA']].tail(1)
634
+ else:
635
+ final = validate[validate.Close > validate['5EMA']].tail(1)
636
+
637
+
638
+ if data_list[cnt] not in last_signal:
639
+ last_signal[data_list[cnt]] = final
640
+ elif data_list[cnt] in last_signal:
641
+ try:
642
+ condition = last_signal[data_list[cnt]][0]['SL'][0]
643
+ except KeyError:
644
+ condition = last_signal[data_list[cnt]]['SL'][0]
645
+ # if last_signal[data_list[cnt]] is not final: # Debug - Shows all conditions
646
+ if condition != final['SL'][0]:
647
+ # Do something with results
648
+ try:
649
+ result_df = pd.concat([
650
+ result_df,
651
+ pd.DataFrame([
652
+ [
653
+ colorText.BLUE + str(final.index[0]) + colorText.END,
654
+ colorText.BOLD + colorText.WARN + data_list[cnt].split('_')[0].upper() + colorText.END,
655
+ (colorText.BOLD + colorText.FAIL + data_list[cnt].split('_')[1].upper() + colorText.END) if 'sell' in data_list[cnt] else (colorText.BOLD + colorText.GREEN + data_list[cnt].split('_')[1].upper() + colorText.END),
656
+ colorText.FAIL + str(final.SL[0]) + colorText.END,
657
+ colorText.GREEN + str(final.Target[0]) + colorText.END,
658
+ f'1:{risk_reward}'
659
+ ]
660
+ ], columns=result_df.columns)
661
+ ], axis=0)
662
+ result_df.reset_index(drop=True, inplace=True)
663
+ except Exception as e:
664
+ pass
665
+ # Then update
666
+ last_signal[data_list[cnt]] = [final]
667
+ result_df.drop_duplicates(keep='last', inplace=True)
668
+ result_df.sort_values(by='Time', inplace=True)
669
+ return result_df[::-1]
670
+
671
+
672
+ '''
673
+ # Find out trend for days to lookback
674
+ def validateVCP(data, screenDict, saveDict, daysToLookback=ConfigManager.daysToLookback, stockName=None):
675
+ // De-index date
676
+ data.reset_index(inplace=True)
677
+ data.rename(columns={'index':'Date'}, inplace=True)
678
+ data = data.head(daysToLookback)
679
+ data = data[::-1]
680
+ data = data.set_index(np.arange(len(data)))
681
+ data = data.fillna(0)
682
+ data = data.replace([np.inf, -np.inf], 0)
683
+ data['tops'] = data['Close'].iloc[list(argrelextrema(np.array(data['Close']), np.greater_equal, order=3)[0])]
684
+ data['bots'] = data['Close'].iloc[list(argrelextrema(np.array(data['Close']), np.less_equal, order=3)[0])]
685
+ try:
686
+ try:
687
+ top_slope,top_c = np.polyfit(data.index[data.tops > 0], data['tops'][data.tops > 0], 1)
688
+ bot_slope,bot_c = np.polyfit(data.index[data.bots > 0], data['bots'][data.bots > 0], 1)
689
+ topAngle = math.degrees(math.atan(top_slope))
690
+ vcpAngle = math.degrees(math.atan(bot_slope) - math.atan(top_slope))
691
+
692
+ # print(math.degrees(math.atan(top_slope)))
693
+ # print(math.degrees(math.atan(bot_slope)))
694
+ # print(vcpAngle)
695
+ # print(topAngle)
696
+ # print(data.max()['bots'])
697
+ # print(data.max()['tops'])
698
+ if (vcpAngle > 20 and vcpAngle < 70) and (topAngle > -10 and topAngle < 10) and (data['bots'].max() <= data['tops'].max()) and (len(data['bots'][data.bots > 0]) > 1):
699
+ print("---> GOOD VCP %s at %sRs" % (stockName, top_c))
700
+ import os
701
+ os.system("echo %s >> vcp_plots\VCP.txt" % stockName)
702
+
703
+ import matplotlib.pyplot as plt
704
+ plt.scatter(data.index[data.tops > 0], data['tops'][data.tops > 0], c='g')
705
+ plt.scatter(data.index[data.bots > 0], data['bots'][data.bots > 0], c='r')
706
+ plt.plot(data.index, data['Close'])
707
+ plt.plot(data.index, top_slope*data.index+top_c,'g--')
708
+ plt.plot(data.index, bot_slope*data.index+bot_c,'r--')
709
+ if stockName != None:
710
+ plt.title(stockName)
711
+ # plt.show()
712
+ plt.savefig('vcp_plots\%s.png' % stockName)
713
+ plt.clf()
714
+ except np.RankWarning:
715
+ pass
716
+ except np.linalg.LinAlgError:
717
+ return False
718
+ '''
719
+
src/classes/SuppressOutput.py ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 ADDED
@@ -0,0 +1,353 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ from decimal import DivisionByZero
9
+ from genericpath import isfile
10
+ import os
11
+ import sys
12
+ import platform
13
+ import datetime
14
+ import pytz
15
+ import pickle
16
+ import requests
17
+ import time
18
+ import joblib
19
+ import keras
20
+ import pandas as pd
21
+ from alive_progress import alive_bar
22
+ from tabulate import tabulate
23
+ from time import sleep
24
+ from classes.ColorText import colorText
25
+ from classes.Changelog import VERSION, changelog
26
+ import classes.ConfigManager as ConfigManager
27
+
28
+ art = colorText.GREEN + '''
29
+ .d8888b. d8b
30
+ d88P Y88b Y8P
31
+ Y88b.
32
+ "Y888b. .d8888b 888d888 .d88b. .d88b. 88888b. 888 88888b. 888 888
33
+ "Y88b. d88P" 888P" d8P Y8b d8P Y8b 888 "88b 888 888 "88b 888 888
34
+ "888 888 888 88888888 88888888 888 888 888 888 888 888 888
35
+ Y88b d88P Y88b. 888 Y8b. Y8b. 888 888 888 888 d88P Y88b 888
36
+ "Y8888P" "Y8888P 888 "Y8888 "Y8888 888 888 888 88888P" "Y88888
37
+ 888 888
38
+ 888 Y8b d88P
39
+ 888 "Y88P"
40
+
41
+ ''' + colorText.END
42
+
43
+ lastScreened = 'last_screened_results.pkl'
44
+
45
+ # Class for managing misc and utility methods
46
+
47
+
48
+ class tools:
49
+
50
+ def clearScreen():
51
+ if platform.system() == 'Windows':
52
+ os.system('cls')
53
+ else:
54
+ os.system('clear')
55
+ print(art)
56
+
57
+ # Print about developers and repository
58
+ def showDevInfo():
59
+ print('\n'+changelog)
60
+ print(colorText.BOLD + colorText.WARN +
61
+ "\n[+] Developer: Pranjal Joshi." + colorText.END)
62
+ print(colorText.BOLD + colorText.WARN +
63
+ ("[+] Version: %s" % VERSION) + colorText.END)
64
+ print(colorText.BOLD +
65
+ "[+] Home Page: https://github.com/pranjal-joshi/Screeni-py" + colorText.END)
66
+ print(colorText.BOLD + colorText.FAIL +
67
+ "[+] Read/Post Issues here: https://github.com/pranjal-joshi/Screeni-py/issues" + colorText.END)
68
+ print(colorText.BOLD + colorText.GREEN +
69
+ "[+] Join Community Discussions: https://github.com/pranjal-joshi/Screeni-py/discussions" + colorText.END)
70
+ print(colorText.BOLD + colorText.BLUE +
71
+ "[+] Download latest software from https://github.com/pranjal-joshi/Screeni-py/releases/latest" + colorText.END)
72
+ input('')
73
+
74
+ # Save last screened result to pickle file
75
+ def setLastScreenedResults(df):
76
+ try:
77
+ df.sort_values(by=['Stock'], ascending=True, inplace=True)
78
+ df.to_pickle(lastScreened)
79
+ except IOError:
80
+ input(colorText.BOLD + colorText.FAIL +
81
+ '[+] Failed to save recently screened result table on disk! Skipping..' + colorText.END)
82
+
83
+ # Load last screened result to pickle file
84
+ def getLastScreenedResults():
85
+ try:
86
+ df = pd.read_pickle(lastScreened)
87
+ print(colorText.BOLD + colorText.GREEN +
88
+ '\n[+] Showing recently screened results..\n' + colorText.END)
89
+ print(tabulate(df, headers='keys', tablefmt='psql'))
90
+ print(colorText.BOLD + colorText.WARN +
91
+ "[+] Note: Trend calculation is based on number of recent days to screen as per your configuration." + colorText.END)
92
+ input(colorText.BOLD + colorText.GREEN +
93
+ '[+] Press any key to continue..' + colorText.END)
94
+ except FileNotFoundError:
95
+ print(colorText.BOLD + colorText.FAIL +
96
+ '[+] Failed to load recently screened result table from disk! Skipping..' + colorText.END)
97
+
98
+ def isTradingTime():
99
+ curr = datetime.datetime.now(pytz.timezone('Asia/Kolkata'))
100
+ openTime = curr.replace(hour=9, minute=15)
101
+ closeTime = curr.replace(hour=15, minute=30)
102
+ return ((openTime <= curr <= closeTime) and (0 <= curr.weekday() <= 4))
103
+
104
+ def isClosingHour():
105
+ curr = datetime.datetime.now(pytz.timezone('Asia/Kolkata'))
106
+ openTime = curr.replace(hour=15, minute=00)
107
+ closeTime = curr.replace(hour=15, minute=30)
108
+ return ((openTime <= curr <= closeTime) and (0 <= curr.weekday() <= 4))
109
+
110
+ def saveStockData(stockDict, configManager, loadCount):
111
+ curr = datetime.datetime.now(pytz.timezone('Asia/Kolkata'))
112
+ openTime = curr.replace(hour=9, minute=15)
113
+ cache_date = datetime.date.today() # for monday to friday
114
+ weekday = datetime.date.today().weekday()
115
+ if curr < openTime: # for monday to friday before 9:15
116
+ cache_date = datetime.datetime.today() - datetime.timedelta(1)
117
+ if weekday == 0 and curr < openTime: # for monday before 9:15
118
+ cache_date = datetime.datetime.today() - datetime.timedelta(3)
119
+ if weekday == 5 or weekday == 6: # for saturday and sunday
120
+ cache_date = datetime.datetime.today() - datetime.timedelta(days=weekday - 4)
121
+ cache_date = cache_date.strftime("%d%m%y")
122
+ cache_file = "stock_data_" + str(cache_date) + ".pkl"
123
+ configManager.deleteStockData(excludeFile=cache_file)
124
+
125
+ if not os.path.exists(cache_file) or len(stockDict) > (loadCount+1):
126
+ with open(cache_file, 'wb') as f:
127
+ try:
128
+ pickle.dump(stockDict.copy(), f)
129
+ print(colorText.BOLD + colorText.GREEN +
130
+ "=> Done." + colorText.END)
131
+ except pickle.PicklingError:
132
+ print(colorText.BOLD + colorText.FAIL +
133
+ "=> Error while Caching Stock Data." + colorText.END)
134
+ else:
135
+ print(colorText.BOLD + colorText.GREEN +
136
+ "=> Already Cached." + colorText.END)
137
+
138
+ def loadStockData(stockDict, configManager, proxyServer=None):
139
+ curr = datetime.datetime.now(pytz.timezone('Asia/Kolkata'))
140
+ openTime = curr.replace(hour=9, minute=15)
141
+ last_cached_date = datetime.date.today() # for monday to friday after 3:30
142
+ weekday = datetime.date.today().weekday()
143
+ if curr < openTime: # for monday to friday before 9:15
144
+ last_cached_date = datetime.datetime.today() - datetime.timedelta(1)
145
+ if weekday == 5 or weekday == 6: # for saturday and sunday
146
+ last_cached_date = datetime.datetime.today() - datetime.timedelta(days=weekday - 4)
147
+ if weekday == 0 and curr < openTime: # for monday before 9:15
148
+ last_cached_date = datetime.datetime.today() - datetime.timedelta(3)
149
+ last_cached_date = last_cached_date.strftime("%d%m%y")
150
+ cache_file = "stock_data_" + str(last_cached_date) + ".pkl"
151
+ if os.path.exists(cache_file):
152
+ with open(cache_file, 'rb') as f:
153
+ try:
154
+ stockData = pickle.load(f)
155
+ print(colorText.BOLD + colorText.GREEN +
156
+ "[+] Automatically Using Cached Stock Data due to After-Market hours!" + colorText.END)
157
+ for stock in stockData:
158
+ stockDict[stock] = stockData.get(stock)
159
+ except pickle.UnpicklingError:
160
+ print(colorText.BOLD + colorText.FAIL +
161
+ "[+] Error while Reading Stock Cache." + colorText.END)
162
+ except EOFError:
163
+ print(colorText.BOLD + colorText.FAIL +
164
+ "[+] Stock Cache Corrupted." + colorText.END)
165
+ elif ConfigManager.default_period == configManager.period and ConfigManager.default_duration == configManager.duration:
166
+ cache_url = "https://raw.github.com/pranjal-joshi/Screeni-py/actions-data-download/actions-data-download/" + cache_file
167
+ if proxyServer is not None:
168
+ resp = requests.get(cache_url, stream=True, proxies={'https':proxyServer})
169
+ else:
170
+ resp = requests.get(cache_url, stream=True)
171
+ if resp.status_code == 200:
172
+ print(colorText.BOLD + colorText.FAIL +
173
+ "[+] After-Market Stock Data is not cached.." + colorText.END)
174
+ print(colorText.BOLD + colorText.GREEN +
175
+ "[+] Downloading cache from Screenipy server for faster processing, Please Wait.." + colorText.END)
176
+ try:
177
+ chunksize = 1024*1024*1
178
+ filesize = int(int(resp.headers.get('content-length'))/chunksize)
179
+ bar, spinner = tools.getProgressbarStyle()
180
+ f = open(cache_file, 'wb')
181
+ dl = 0
182
+ with alive_bar(filesize, bar=bar, spinner=spinner, manual=True) as progressbar:
183
+ for data in resp.iter_content(chunk_size=chunksize):
184
+ dl += 1
185
+ f.write(data)
186
+ progressbar(dl/filesize)
187
+ if dl >= filesize:
188
+ progressbar(1.0)
189
+ f.close()
190
+ except Exception as e:
191
+ print("[!] Download Error - " + str(e))
192
+ print("")
193
+ tools.loadStockData(stockDict, configManager, proxyServer)
194
+ else:
195
+ print(colorText.BOLD + colorText.FAIL +
196
+ "[+] Cache unavailable on Screenipy server, Continuing.." + colorText.END)
197
+
198
+ # Save screened results to excel
199
+ def promptSaveResults(df):
200
+ try:
201
+ response = str(input(colorText.BOLD + colorText.WARN +
202
+ '[>] Do you want to save the results in excel file? [Y/N]: ')).upper()
203
+ except ValueError:
204
+ response = 'Y'
205
+ if response != 'N':
206
+ filename = 'screenipy-result_' + \
207
+ datetime.datetime.now().strftime("%d-%m-%y_%H.%M.%S")+".xlsx"
208
+ df.to_excel(filename)
209
+ print(colorText.BOLD + colorText.GREEN +
210
+ ("[+] Results saved to %s" % filename) + colorText.END)
211
+
212
+ # Prompt for asking RSI
213
+ def promptRSIValues():
214
+ try:
215
+ minRSI, maxRSI = int(input(colorText.BOLD + colorText.WARN + "\n[+] Enter Min RSI value: " + colorText.END)), int(
216
+ input(colorText.BOLD + colorText.WARN + "[+] Enter Max RSI value: " + colorText.END))
217
+ if (minRSI >= 0 and minRSI <= 100) and (maxRSI >= 0 and maxRSI <= 100) and (minRSI <= maxRSI):
218
+ return (minRSI, maxRSI)
219
+ raise ValueError
220
+ except ValueError:
221
+ return (0, 0)
222
+
223
+ # Prompt for Reversal screening
224
+ def promptReversalScreening():
225
+ try:
226
+ resp = int(input(colorText.BOLD + colorText.WARN + """\n[+] Select Option:
227
+ 1 > Screen for Buy Signal (Bullish Reversal)
228
+ 2 > Screen for Sell Signal (Bearish Reversal)
229
+ 3 > Screen for Momentum Gainers (Rising Bullish Momentum)
230
+ 4 > Screen for Reversal at Moving Average (Bullish Reversal)
231
+ 5 > Screen for Volume Spread Analysis (Bullish VSA Reversal)
232
+ 6 > Screen for Narrow Range (NRx) Reversal
233
+ 0 > Cancel
234
+ [+] Select option: """ + colorText.END))
235
+ if resp >= 0 and resp <= 6:
236
+ if resp == 4:
237
+ try:
238
+ maLength = int(input(colorText.BOLD + colorText.WARN +
239
+ '\n[+] Enter MA Length (E.g. 50 or 200): ' + colorText.END))
240
+ return resp, maLength
241
+ except ValueError:
242
+ print(colorText.BOLD + colorText.FAIL +
243
+ '\n[!] Invalid Input! MA Lenght should be single integer value!\n' + colorText.END)
244
+ raise ValueError
245
+ elif resp == 6:
246
+ try:
247
+ maLength = int(input(colorText.BOLD + colorText.WARN +
248
+ '\n[+] Enter NR timeframe [Integer Number] (E.g. 4, 7, etc.): ' + colorText.END))
249
+ return resp, maLength
250
+ except ValueError:
251
+ print(colorText.BOLD + colorText.FAIL + '\n[!] Invalid Input! NR timeframe should be single integer value!\n' + colorText.END)
252
+ raise ValueError
253
+ return resp, None
254
+ raise ValueError
255
+ except ValueError:
256
+ return None, None
257
+
258
+ # Prompt for Reversal screening
259
+ def promptChartPatterns():
260
+ try:
261
+ resp = int(input(colorText.BOLD + colorText.WARN + """\n[+] Select Option:
262
+ 1 > Screen for Bullish Inside Bar (Flag) Pattern
263
+ 2 > Screen for Bearish Inside Bar (Flag) Pattern
264
+ 3 > Screen for the Confluence (50 & 200 MA/EMA)
265
+ 4 > Screen for VCP (Experimental)
266
+ 5 > Screen for Buying at Trendline (Ideal for Swing/Mid/Long term)
267
+ 0 > Cancel
268
+ [+] Select option: """ + colorText.END))
269
+ if resp == 1 or resp == 2:
270
+ candles = int(input(colorText.BOLD + colorText.WARN +
271
+ "\n[+] How many candles (TimeFrame) to look back Inside Bar formation? : " + colorText.END))
272
+ return (resp, candles)
273
+ if resp == 3:
274
+ percent = float(input(colorText.BOLD + colorText.WARN +
275
+ "\n[+] Enter Percentage within which all MA/EMAs should be (Ideal: 1-2%)? : " + colorText.END))
276
+ return (resp, percent/100.0)
277
+ if resp >= 0 and resp <= 5:
278
+ return resp, 0
279
+ raise ValueError
280
+ except ValueError:
281
+ input(colorText.BOLD + colorText.FAIL +
282
+ "\n[+] Invalid Option Selected. Press Any Key to Continue..." + colorText.END)
283
+ return (None, None)
284
+
285
+ def getProgressbarStyle():
286
+ bar = 'smooth'
287
+ spinner = 'waves'
288
+ if 'Windows' in platform.platform():
289
+ bar = 'classic2'
290
+ spinner = 'dots_recur'
291
+ return bar, spinner
292
+
293
+ def getNiftyModel(proxyServer=None):
294
+ files = ['nifty_model_v2.h5', 'nifty_model_v2.pkl']
295
+ urls = [
296
+ "https://raw.github.com/pranjal-joshi/Screeni-py/new-features/src/ml/nifty_model_v2.h5",
297
+ "https://raw.github.com/pranjal-joshi/Screeni-py/new-features/src/ml/nifty_model_v2.pkl"
298
+ ]
299
+ if os.path.isfile(files[0]) and os.path.isfile(files[1]):
300
+ file_age = (time.time() - os.path.getmtime(files[0]))/604800
301
+ if file_age > 1:
302
+ download = True
303
+ os.remove(files[0])
304
+ os.remove(files[1])
305
+ else:
306
+ download = False
307
+ else:
308
+ download = True
309
+ if download:
310
+ for file_url in urls:
311
+ if proxyServer is not None:
312
+ resp = requests.get(file_url, stream=True, proxies={'https':proxyServer})
313
+ else:
314
+ resp = requests.get(file_url, stream=True)
315
+ if resp.status_code == 200:
316
+ print(colorText.BOLD + colorText.GREEN +
317
+ "[+] Downloading AI model (v2) for Nifty predictions, Please Wait.." + colorText.END)
318
+ try:
319
+ chunksize = 1024*1024*1
320
+ filesize = int(int(resp.headers.get('content-length'))/chunksize)
321
+ filesize = 1 if not filesize else filesize
322
+ bar, spinner = tools.getProgressbarStyle()
323
+ f = open(file_url.split('/')[-1], 'wb')
324
+ dl = 0
325
+ with alive_bar(filesize, bar=bar, spinner=spinner, manual=True) as progressbar:
326
+ for data in resp.iter_content(chunk_size=chunksize):
327
+ dl += 1
328
+ f.write(data)
329
+ progressbar(dl/filesize)
330
+ if dl >= filesize:
331
+ progressbar(1.0)
332
+ f.close()
333
+ except Exception as e:
334
+ print("[!] Download Error - " + str(e))
335
+ time.sleep(3)
336
+ model = keras.models.load_model(files[0])
337
+ pkl = joblib.load(files[1])
338
+ return model, pkl
339
+
340
+ def getSigmoidConfidence(x):
341
+ out_min, out_max = 0, 100
342
+ if x > 0.5:
343
+ in_min = 0.50001
344
+ in_max = 1
345
+ else:
346
+ in_min = 0
347
+ in_max = 0.5
348
+ return round(((x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min),3)
349
+
350
+ def alertSound(beeps=3, delay=0.2):
351
+ for i in range(beeps):
352
+ print('\a')
353
+ sleep(delay)
src/icon-old.ico ADDED
src/icon.ico ADDED
src/ml/best_model_0.7438acc.h5 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:21aa1a70413f2177bf5738223a27e2f6fbb0ec29b9374b91dc24d5f8564e3dfe
3
+ size 21045528
src/ml/experiment.ipynb ADDED
The diff for this file is too large to render. See raw diff
 
src/ml/nifty_model.h5 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:44ac12dcf2e619d42d76a55a344973ef2b31e97a713bd970476c1f7ce40b3d05
3
+ size 86392
src/ml/nifty_model.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:baf28d0854495604916de1273e88ef2e020b7d046f78a4a7ad5ba160a9a492cb
3
+ size 687
src/ml/nifty_model_v2.h5 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4696b9dd9c04e42814e2f042ef2a89317ae4a3b35b7dd03c4eab05825a54540e
3
+ size 86368
src/ml/nifty_model_v2.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e44762314b0481d4f5b5da01b6c620bd4a9d3c5e403c94e0d00ce7853674ed1c
3
+ size 687
src/release.md ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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)](#) [![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
+ Celebrating more than 5K+ Downloads - Thank You for your support :tada:
5
+
6
+ 1. New Index - **F&O Stocks Only** Added for F&O traders
7
+ 2. Trend detection fixed (#173) and % change added.
8
+ 3. **Artificial Intelligence v2 for Nifty 50 Prediction** - Predict Next day Gap-up/down - Try `Select Index for Screening > N`
9
+ 4. **Live Intraday Scanner - 5 EMA** - Try `Select Index for Screening > E` - Get live trade entries for Nifty and BankNifty with notifications!
10
+ 5. New Screener **Buy at Trendline** added for Swing/Mid/Long term traders - Try `Option > 7 > 5`.
11
+ 6. Alternate Data Source for faster After-Market Analysis - Optimizations and Cosmetic Updates!
12
+
13
+ ## Downloads
14
+ | Operating System | Executable File |
15
+ | :-: | --- |
16
+ | ![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/1.43/screenipy.exe)** |
17
+ | ![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/1.43/screenipy.bin)** |
18
+ | ![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/1.43/screenipy.run)** ([Read Installation Guide](https://github.com/pranjal-joshi/Screeni-py/blob/main/INSTALLATION.md#for-macos)) |
19
+
20
+ ## How to use?
21
+
22
+ [**Click Here**](https://github.com/pranjal-joshi/Screeni-py) to read the documentation.
23
+
24
+ ## Join our Community Discussion
25
+
26
+ [**Click Here**](https://github.com/pranjal-joshi/Screeni-py/discussions) to join the community discussion and see what other users are doing!
27
+
28
+ ## Facing an Issue? Found a Bug?
29
+
30
+ [**Click Here**](https://github.com/pranjal-joshi/Screeni-py/issues/new/choose) to open an Issue so we can fix it for you!
31
+
32
+ ## Want to Contribute?
33
+
34
+ [**Click Here**](https://github.com/pranjal-joshi/Screeni-py/blob/main/CONTRIBUTING.md) before you start working with us on new features!
35
+
36
+ ## Disclaimer:
37
+ * DO NOT use the result provided by the software solely to make your trading decisions.
38
+ * Always backtest and analyze the stocks manually before you trade.
39
+ * The Author(s) and the software will not be held liable for any losses.
src/screenipy.ini ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [config]
2
+ period = 300d
3
+ daystolookback = 30
4
+ duration = 1d
5
+ minprice = 30
6
+ maxprice = 5000
7
+ volumeratio = 2
8
+ consolidationpercentage = 10
9
+ shuffle = y
10
+ cachestockdata = y
11
+ onlystagetwostocks = y
12
+ useema = n
13
+
src/screenipy.py ADDED
@@ -0,0 +1,435 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/python3
2
+
3
+ # Pyinstaller compile Windows: pyinstaller --onefile --icon=src\icon.ico src\screenipy.py --hidden-import cmath --hidden-import talib.stream --hidden-import numpy --hidden-import pandas --hidden-import alive-progress
4
+ # Pyinstaller compile Linux : pyinstaller --onefile --icon=src/icon.ico src/screenipy.py --hidden-import cmath --hidden-import talib.stream --hidden-import numpy --hidden-import pandas --hidden-import alive-progress
5
+
6
+ # Keep module imports prior to classes
7
+ import os
8
+ os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'
9
+ import platform
10
+ import sys
11
+ import classes.Fetcher as Fetcher
12
+ import classes.ConfigManager as ConfigManager
13
+ import classes.Screener as Screener
14
+ import classes.Utility as Utility
15
+ from classes.ColorText import colorText
16
+ from classes.OtaUpdater import OTAUpdater
17
+ from classes.CandlePatterns import CandlePatterns
18
+ from classes.ParallelProcessing import StockConsumer
19
+ from classes.Changelog import VERSION
20
+ from alive_progress import alive_bar
21
+ import argparse
22
+ import urllib
23
+ import numpy as np
24
+ import pandas as pd
25
+ from datetime import datetime
26
+ from time import sleep
27
+ from tabulate import tabulate
28
+ import multiprocessing
29
+ multiprocessing.freeze_support()
30
+
31
+ # Argument Parsing for test purpose
32
+ argParser = argparse.ArgumentParser()
33
+ argParser.add_argument('-t', '--testbuild', action='store_true', help='Run in test-build mode', required=False)
34
+ argParser.add_argument('-d', '--download', action='store_true', help='Only Download Stock data in .pkl file', required=False)
35
+ argParser.add_argument('-v', action='store_true') # Dummy Arg for pytest -v
36
+ args = argParser.parse_args()
37
+
38
+ # Try Fixing bug with this symbol
39
+ TEST_STKCODE = "SBIN"
40
+
41
+ # Constants
42
+ np.seterr(divide='ignore', invalid='ignore')
43
+
44
+ # Global Variabls
45
+ screenCounter = None
46
+ screenResultsCounter = None
47
+ stockDict = None
48
+ keyboardInterruptEvent = None
49
+ loadedStockData = False
50
+ loadCount = 0
51
+ maLength = None
52
+ newlyListedOnly = False
53
+
54
+ configManager = ConfigManager.tools()
55
+ fetcher = Fetcher.tools(configManager)
56
+ screener = Screener.tools(configManager)
57
+ candlePatterns = CandlePatterns()
58
+
59
+ # Get system wide proxy for networking
60
+ try:
61
+ proxyServer = urllib.request.getproxies()['http']
62
+ except KeyError:
63
+ proxyServer = ""
64
+
65
+ # Manage Execution flow
66
+
67
+
68
+ def initExecution():
69
+ global newlyListedOnly
70
+ print(colorText.BOLD + colorText.WARN +
71
+ '[+] Select an Index for Screening: ' + colorText.END)
72
+ print(colorText.BOLD + '''
73
+ W > Screen stocks from my own Watchlist
74
+ N > Nifty Prediction using Artifical Intelligence (Use for Gap-Up/Gap-Down/BTST/STBT)
75
+ E > Live Index Scan : 5 EMA for Intraday
76
+
77
+ 0 > Screen stocks by the stock names (NSE Stock Code)
78
+ 1 > Nifty 50 2 > Nifty Next 50 3 > Nifty 100
79
+ 4 > Nifty 200 5 > Nifty 500 6 > Nifty Smallcap 50
80
+ 7 > Nifty Smallcap 100 8 > Nifty Smallcap 250 9 > Nifty Midcap 50
81
+ 10 > Nifty Midcap 100 11 > Nifty Midcap 150 13 > Newly Listed (IPOs in last 2 Year)
82
+ 14 > F&O Stocks Only
83
+ Enter > All Stocks (default) ''' + colorText.END
84
+ )
85
+ try:
86
+ tickerOption = input(
87
+ colorText.BOLD + colorText.FAIL + '[+] Select option: ')
88
+ print(colorText.END, end='')
89
+ if tickerOption == '':
90
+ tickerOption = 12
91
+ # elif tickerOption == 'W' or tickerOption == 'w' or tickerOption == 'N' or tickerOption == 'n' or tickerOption == 'E' or tickerOption == 'e':
92
+ elif not tickerOption.isnumeric():
93
+ tickerOption = tickerOption.upper()
94
+ else:
95
+ tickerOption = int(tickerOption)
96
+ if(tickerOption < 0 or tickerOption > 14):
97
+ raise ValueError
98
+ elif tickerOption == 13:
99
+ newlyListedOnly = True
100
+ tickerOption = 12
101
+ except KeyboardInterrupt:
102
+ raise KeyboardInterrupt
103
+ except Exception as e:
104
+ print(colorText.BOLD + colorText.FAIL +
105
+ '\n[+] Please enter a valid numeric option & Try Again!' + colorText.END)
106
+ sleep(2)
107
+ Utility.tools.clearScreen()
108
+ return initExecution()
109
+
110
+ if tickerOption == 'N' or tickerOption == 'E':
111
+ return tickerOption, 0
112
+
113
+ if tickerOption and tickerOption != 'W':
114
+ print(colorText.BOLD + colorText.WARN +
115
+ '\n[+] Select a Critera for Stock Screening: ' + colorText.END)
116
+ print(colorText.BOLD + '''
117
+ 0 > Full Screening (Shows Technical Parameters without Any Criteria)
118
+ 1 > Screen stocks for Breakout or Consolidation
119
+ 2 > Screen for the stocks with recent Breakout & Volume
120
+ 3 > Screen for the Consolidating stocks
121
+ 4 > Screen for the stocks with Lowest Volume in last 'N'-days (Early Breakout Detection)
122
+ 5 > Screen for the stocks with RSI
123
+ 6 > Screen for the stocks showing Reversal Signals
124
+ 7 > Screen for the stocks making Chart Patterns
125
+ 8 > Edit user configuration
126
+ 9 > Show user configuration
127
+ 10 > Show Last Screened Results
128
+ 11 > Help / About Developer
129
+ 12 > Exit''' + colorText.END
130
+ )
131
+ try:
132
+ if tickerOption and tickerOption != 'W':
133
+ executeOption = input(
134
+ colorText.BOLD + colorText.FAIL + '[+] Select option: ')
135
+ print(colorText.END, end='')
136
+ if executeOption == '':
137
+ executeOption = 0
138
+ executeOption = int(executeOption)
139
+ if(executeOption < 0 or executeOption > 14):
140
+ raise ValueError
141
+ else:
142
+ executeOption = 0
143
+ except KeyboardInterrupt:
144
+ raise KeyboardInterrupt
145
+ except Exception as e:
146
+ print(colorText.BOLD + colorText.FAIL +
147
+ '\n[+] Please enter a valid numeric option & Try Again!' + colorText.END)
148
+ sleep(2)
149
+ Utility.tools.clearScreen()
150
+ return initExecution()
151
+ return tickerOption, executeOption
152
+
153
+ # Main function
154
+ def main(testing=False, testBuild=False, downloadOnly=False):
155
+ global screenCounter, screenResultsCounter, stockDict, loadedStockData, keyboardInterruptEvent, loadCount, maLength, newlyListedOnly
156
+ screenCounter = multiprocessing.Value('i', 1)
157
+ screenResultsCounter = multiprocessing.Value('i', 0)
158
+ keyboardInterruptEvent = multiprocessing.Manager().Event()
159
+
160
+ if stockDict is None:
161
+ stockDict = multiprocessing.Manager().dict()
162
+ loadCount = 0
163
+
164
+ minRSI = 0
165
+ maxRSI = 100
166
+ insideBarToLookback = 7
167
+ respChartPattern = 1
168
+ daysForLowestVolume = 30
169
+ reversalOption = None
170
+
171
+ screenResults = pd.DataFrame(columns=[
172
+ 'Stock', 'Consolidating', 'Breaking-Out', 'LTP', 'Volume', 'MA-Signal', 'RSI', 'Trend', 'Pattern'])
173
+ saveResults = pd.DataFrame(columns=[
174
+ 'Stock', 'Consolidating', 'Breaking-Out', 'LTP', 'Volume', 'MA-Signal', 'RSI', 'Trend', 'Pattern'])
175
+
176
+
177
+ if testBuild:
178
+ tickerOption, executeOption = 1, 0
179
+ elif downloadOnly:
180
+ tickerOption, executeOption = 12, 2
181
+ else:
182
+ try:
183
+ tickerOption, executeOption = initExecution()
184
+ except KeyboardInterrupt:
185
+ input(colorText.BOLD + colorText.FAIL +
186
+ "[+] Press any key to Exit!" + colorText.END)
187
+ sys.exit(0)
188
+
189
+ if executeOption == 4:
190
+ try:
191
+ daysForLowestVolume = int(input(colorText.BOLD + colorText.WARN +
192
+ '\n[+] The Volume should be lowest since last how many candles? '))
193
+ except ValueError:
194
+ print(colorText.END)
195
+ print(colorText.BOLD + colorText.FAIL +
196
+ '[+] Error: Non-numeric value entered! Screening aborted.' + colorText.END)
197
+ input('')
198
+ main()
199
+ print(colorText.END)
200
+ if executeOption == 5:
201
+ minRSI, maxRSI = Utility.tools.promptRSIValues()
202
+ if (not minRSI and not maxRSI):
203
+ print(colorText.BOLD + colorText.FAIL +
204
+ '\n[+] Error: Invalid values for RSI! Values should be in range of 0 to 100. Screening aborted.' + colorText.END)
205
+ input('')
206
+ main()
207
+ if executeOption == 6:
208
+ reversalOption, maLength = Utility.tools.promptReversalScreening()
209
+ if reversalOption is None or reversalOption == 0:
210
+ main()
211
+ if executeOption == 7:
212
+ respChartPattern, insideBarToLookback = Utility.tools.promptChartPatterns()
213
+ if insideBarToLookback is None:
214
+ main()
215
+ if executeOption == 8:
216
+ configManager.setConfig(ConfigManager.parser)
217
+ main()
218
+ if executeOption == 9:
219
+ configManager.showConfigFile()
220
+ main()
221
+ if executeOption == 10:
222
+ Utility.tools.getLastScreenedResults()
223
+ main()
224
+ if executeOption == 11:
225
+ Utility.tools.showDevInfo()
226
+ main()
227
+ if executeOption == 12:
228
+ input(colorText.BOLD + colorText.FAIL +
229
+ "[+] Press any key to Exit!" + colorText.END)
230
+ sys.exit(0)
231
+
232
+ if tickerOption == 'W' or tickerOption == 'N' or tickerOption == 'E' or (tickerOption >= 0 and tickerOption < 15):
233
+ configManager.getConfig(ConfigManager.parser)
234
+ try:
235
+ if tickerOption == 'W':
236
+ listStockCodes = fetcher.fetchWatchlist()
237
+ if listStockCodes is None:
238
+ input(colorText.BOLD + colorText.FAIL +
239
+ f'[+] Create the watchlist.xlsx file in {os.getcwd()} and Restart the Program!' + colorText.END)
240
+ sys.exit(0)
241
+ elif tickerOption == 'N':
242
+ os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'
243
+ prediction = screener.getNiftyPrediction(
244
+ data=fetcher.fetchLatestNiftyDaily(proxyServer=proxyServer),
245
+ proxyServer=proxyServer
246
+ )
247
+ input('\nPress any key to Continue...\n')
248
+ return
249
+ elif tickerOption == 'E':
250
+ result_df = pd.DataFrame(columns=['Time','Stock/Index','Action','SL','Target','R:R'])
251
+ last_signal = {}
252
+ first_scan = True
253
+ result_df = screener.monitorFiveEma( # Dummy scan to avoid blank table on 1st scan
254
+ proxyServer=proxyServer,
255
+ fetcher=fetcher,
256
+ result_df=result_df,
257
+ last_signal=last_signal
258
+ )
259
+ try:
260
+ while True:
261
+ Utility.tools.clearScreen()
262
+ last_result_len = len(result_df)
263
+ result_df = screener.monitorFiveEma(
264
+ proxyServer=proxyServer,
265
+ fetcher=fetcher,
266
+ result_df=result_df,
267
+ last_signal=last_signal
268
+ )
269
+ print(colorText.BOLD + colorText.WARN + '[+] 5-EMA : Live Intraday Scanner \t' + colorText.END + colorText.FAIL + f'Last Scanned: {datetime.now().strftime("%H:%M:%S")}\n' + colorText.END)
270
+ print(tabulate(result_df, headers='keys', tablefmt='psql'))
271
+ print('\nPress Ctrl+C to exit.')
272
+ if len(result_df) != last_result_len and not first_scan:
273
+ Utility.tools.alertSound(beeps=5)
274
+ sleep(60)
275
+ first_scan = False
276
+ except KeyboardInterrupt:
277
+ input('\nPress any key to Continue...\n')
278
+ return
279
+ else:
280
+ listStockCodes = fetcher.fetchStockCodes(tickerOption, proxyServer=proxyServer)
281
+ except urllib.error.URLError:
282
+ print(colorText.BOLD + colorText.FAIL +
283
+ "\n\n[+] Oops! It looks like you don't have an Internet connectivity at the moment! Press any key to exit!" + colorText.END)
284
+ input('')
285
+ sys.exit(0)
286
+
287
+ if not Utility.tools.isTradingTime() and configManager.cacheEnabled and not loadedStockData and not testing:
288
+ Utility.tools.loadStockData(stockDict, configManager, proxyServer)
289
+ loadedStockData = True
290
+ loadCount = len(stockDict)
291
+
292
+ print(colorText.BOLD + colorText.WARN +
293
+ "[+] Starting Stock Screening.. Press Ctrl+C to stop!\n")
294
+
295
+ items = [(executeOption, reversalOption, maLength, daysForLowestVolume, minRSI, maxRSI, respChartPattern, insideBarToLookback, len(listStockCodes),
296
+ configManager, fetcher, screener, candlePatterns, stock, newlyListedOnly, downloadOnly)
297
+ for stock in listStockCodes]
298
+
299
+ tasks_queue = multiprocessing.JoinableQueue()
300
+ results_queue = multiprocessing.Queue()
301
+
302
+ totalConsumers = multiprocessing.cpu_count()
303
+ if totalConsumers == 1:
304
+ totalConsumers = 2 # This is required for single core machine
305
+ if configManager.cacheEnabled is True and multiprocessing.cpu_count() > 2:
306
+ totalConsumers -= 1
307
+ consumers = [StockConsumer(tasks_queue, results_queue, screenCounter, screenResultsCounter, stockDict, proxyServer, keyboardInterruptEvent)
308
+ for _ in range(totalConsumers)]
309
+
310
+ for worker in consumers:
311
+ worker.daemon = True
312
+ worker.start()
313
+
314
+ if testing or testBuild:
315
+ for item in items:
316
+ tasks_queue.put(item)
317
+ result = results_queue.get()
318
+ if result is not None:
319
+ screenResults = screenResults.append(
320
+ result[0], ignore_index=True)
321
+ saveResults = saveResults.append(
322
+ result[1], ignore_index=True)
323
+ if testing or (testBuild and len(screenResults) > 2):
324
+ break
325
+ else:
326
+ for item in items:
327
+ tasks_queue.put(item)
328
+ # Append exit signal for each process indicated by None
329
+ for _ in range(multiprocessing.cpu_count()):
330
+ tasks_queue.put(None)
331
+ try:
332
+ numStocks = len(listStockCodes)
333
+ print(colorText.END+colorText.BOLD)
334
+ bar, spinner = Utility.tools.getProgressbarStyle()
335
+ with alive_bar(numStocks, bar=bar, spinner=spinner) as progressbar:
336
+ while numStocks:
337
+ result = results_queue.get()
338
+ if result is not None:
339
+ screenResults = screenResults.append(
340
+ result[0], ignore_index=True)
341
+ saveResults = saveResults.append(
342
+ result[1], ignore_index=True)
343
+ numStocks -= 1
344
+ progressbar.text(colorText.BOLD + colorText.GREEN +
345
+ f'Found {screenResultsCounter.value} Stocks' + colorText.END)
346
+ progressbar()
347
+ except KeyboardInterrupt:
348
+ try:
349
+ keyboardInterruptEvent.set()
350
+ except KeyboardInterrupt:
351
+ pass
352
+ print(colorText.BOLD + colorText.FAIL +
353
+ "\n[+] Terminating Script, Please wait..." + colorText.END)
354
+ for worker in consumers:
355
+ worker.terminate()
356
+
357
+ print(colorText.END)
358
+ # Exit all processes. Without this, it threw error in next screening session
359
+ for worker in consumers:
360
+ try:
361
+ worker.terminate()
362
+ except OSError as e:
363
+ if e.winerror == 5:
364
+ pass
365
+
366
+ # Flush the queue so depending processes will end
367
+ from queue import Empty
368
+ while True:
369
+ try:
370
+ _ = tasks_queue.get(False)
371
+ except Exception as e:
372
+ break
373
+
374
+ screenResults.sort_values(by=['Stock'], ascending=True, inplace=True)
375
+ saveResults.sort_values(by=['Stock'], ascending=True, inplace=True)
376
+ screenResults.set_index('Stock', inplace=True)
377
+ saveResults.set_index('Stock', inplace=True)
378
+ screenResults.rename(
379
+ columns={
380
+ 'Trend': f'Trend ({configManager.daysToLookback}Days)',
381
+ 'Breaking-Out': f'Breakout ({configManager.daysToLookback}Days)',
382
+ 'LTP': 'LTP (%% Chng)'
383
+ },
384
+ inplace=True
385
+ )
386
+ saveResults.rename(
387
+ columns={
388
+ 'Trend': f'Trend ({configManager.daysToLookback}Days)',
389
+ 'Breaking-Out': f'Breakout ({configManager.daysToLookback}Days)',
390
+ },
391
+ inplace=True
392
+ )
393
+ print(tabulate(screenResults, headers='keys', tablefmt='psql'))
394
+
395
+ print(colorText.BOLD + colorText.GREEN +
396
+ f"[+] Found {len(screenResults)} Stocks." + colorText.END)
397
+ if configManager.cacheEnabled and not Utility.tools.isTradingTime() and not testing:
398
+ print(colorText.BOLD + colorText.GREEN +
399
+ "[+] Caching Stock Data for future use, Please Wait... " + colorText.END, end='')
400
+ Utility.tools.saveStockData(
401
+ stockDict, configManager, loadCount)
402
+
403
+ Utility.tools.setLastScreenedResults(screenResults)
404
+ if not testBuild and not downloadOnly:
405
+ Utility.tools.promptSaveResults(saveResults)
406
+ print(colorText.BOLD + colorText.WARN +
407
+ "[+] Note: Trend calculation is based on number of days recent to screen as per your configuration." + colorText.END)
408
+ print(colorText.BOLD + colorText.GREEN +
409
+ "[+] Screening Completed! Press Enter to Continue.." + colorText.END)
410
+ input('')
411
+ newlyListedOnly = False
412
+
413
+
414
+ if __name__ == "__main__":
415
+ Utility.tools.clearScreen()
416
+ isDevVersion = OTAUpdater.checkForUpdate(proxyServer, VERSION)
417
+ if not configManager.checkConfigFile():
418
+ configManager.setConfig(ConfigManager.parser, default=True, showFileCreatedText=False)
419
+ if args.testbuild:
420
+ print(colorText.BOLD + colorText.FAIL +"[+] Started in TestBuild mode!" + colorText.END)
421
+ main(testBuild=True)
422
+ elif args.download:
423
+ print(colorText.BOLD + colorText.FAIL +"[+] Download ONLY mode! Stocks will not be screened!" + colorText.END)
424
+ main(downloadOnly=True)
425
+ else:
426
+ try:
427
+ while True:
428
+ main()
429
+ except Exception as e:
430
+ raise e
431
+ if isDevVersion == OTAUpdater.developmentVersion:
432
+ raise(e)
433
+ input(colorText.BOLD + colorText.FAIL +
434
+ "[+] Press any key to Exit!" + colorText.END)
435
+ sys.exit(0)
test/screenipy_test.py ADDED
@@ -0,0 +1,192 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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')
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)