Spaces:
Configuration error
Configuration error
Commit
•
a4c7650
1
Parent(s):
df3e2fc
- .github/ISSUE_TEMPLATE/bug_report.md +38 -0
- .github/ISSUE_TEMPLATE/feature_request.md +20 -0
- .github/dependencies/TA_Lib-0.4.19-cp39-cp39-win_amd64.whl +0 -0
- .github/dependencies/ta-lib-0.4.0-src.tar.gz +3 -0
- .github/workflows/squash.py +41 -0
- .github/workflows/workflow-build-matrix.yml +145 -0
- .github/workflows/workflow-download-data.yml +76 -0
- .github/workflows/workflow-stale.yml +29 -0
- .github/workflows/workflow-test.yml +105 -0
- .gitignore +11 -0
- CONTRIBUTING.md +42 -0
- INSTALLATION.md +33 -0
- LICENSE +21 -0
- README.md +83 -10
- patterns/IPO1.png +0 -0
- patterns/IPO2.png +0 -0
- patterns/IPO3.png +0 -0
- patterns/MomentumGainer.png +0 -0
- requirements.txt +59 -0
- src/classes/CandlePatterns.py +185 -0
- src/classes/Changelog.py +197 -0
- src/classes/ColorText.py +17 -0
- src/classes/ConfigManager.py +193 -0
- src/classes/Fetcher.py +219 -0
- src/classes/OtaUpdater.py +132 -0
- src/classes/ParallelProcessing.py +269 -0
- src/classes/Screener.py +719 -0
- src/classes/SuppressOutput.py +29 -0
- src/classes/Utility.py +353 -0
- src/icon-old.ico +0 -0
- src/icon.ico +0 -0
- src/ml/best_model_0.7438acc.h5 +3 -0
- src/ml/experiment.ipynb +0 -0
- src/ml/nifty_model.h5 +3 -0
- src/ml/nifty_model.pkl +3 -0
- src/ml/nifty_model_v2.h5 +3 -0
- src/ml/nifty_model_v2.pkl +3 -0
- src/release.md +39 -0
- src/screenipy.ini +13 -0
- src/screenipy.py +435 -0
- test/screenipy_test.py +192 -0
.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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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)
|