Upload 46 files
Browse files- .gitattributes +1 -0
- CONTRIBUTING.md +42 -0
- Dockerfile +61 -0
- INSTALLATION.md +33 -0
- LICENSE +21 -0
- Makefile +30 -0
- actions-data-download/stock_data_140823.pkl +3 -0
- patterns/IPO1.png +0 -0
- patterns/IPO2.png +0 -0
- patterns/IPO3.png +0 -0
- patterns/MomentumGainer.png +0 -0
- requirements.txt +71 -0
- run_screenipy.sh +25 -0
- screenshots/NSE_Logo.svg +26 -0
- screenshots/config.png +0 -0
- screenshots/done.png +0 -0
- screenshots/home.png +0 -0
- screenshots/results.png +0 -0
- screenshots/screening.png +0 -0
- screenshots/screenipy_demo.gif +3 -0
- src/.streamlit/config.toml +2 -0
- src/classes/CandlePatterns.py +185 -0
- src/classes/Changelog.py +296 -0
- src/classes/ColorText.py +17 -0
- src/classes/ConfigManager.py +206 -0
- src/classes/Fetcher.py +354 -0
- src/classes/OtaUpdater.py +148 -0
- src/classes/ParallelProcessing.py +313 -0
- src/classes/Screener.py +800 -0
- src/classes/ScreenipyTA.py +269 -0
- src/classes/SuppressOutput.py +29 -0
- src/classes/Utility.py +417 -0
- src/icon-old.ico +0 -0
- src/icon.ico +0 -0
- src/ml/best_model_0.7438acc.h5 +3 -0
- src/ml/eval.py +130 -0
- src/ml/experiment.ipynb +673 -0
- src/ml/nifty_model_v2.h5 +3 -0
- src/ml/nifty_model_v2.pkl +3 -0
- src/ml/nifty_model_v3.h5 +3 -0
- src/ml/nifty_model_v3.pkl +3 -0
- src/release.md +82 -0
- src/screenipy.ini +13 -0
- src/screenipy.py +545 -0
- src/screenipy.spec +34 -0
- src/streamlit_app.py +529 -0
- test/screenipy_test.py +192 -0
.gitattributes
CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
36 |
+
screenshots/screenipy_demo.gif filter=lfs diff=lfs merge=lfs -text
|
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`.
|
Dockerfile
ADDED
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Project : Screenipy
|
2 |
+
# Author : Pranjal Joshi
|
3 |
+
# Created : 17/08/2023
|
4 |
+
# Description : Dockerfile to build Screeni-py image for GUI release
|
5 |
+
|
6 |
+
# FROM ubuntu:latest as base
|
7 |
+
# FROM tensorflow/tensorflow:2.9.2 as base
|
8 |
+
# FROM python:3.10.6-slim as base
|
9 |
+
FROM python:3.11.6-slim-bookworm as base
|
10 |
+
|
11 |
+
ARG DEBIAN_FRONTEND=noninteractive
|
12 |
+
|
13 |
+
RUN apt-get update && apt-get install -y software-properties-common
|
14 |
+
|
15 |
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
16 |
+
build-essential \
|
17 |
+
git \
|
18 |
+
vim nano wget curl \
|
19 |
+
&& \
|
20 |
+
apt-get clean && \
|
21 |
+
rm -rf /var/lib/apt/lists/*
|
22 |
+
|
23 |
+
ENV LANG C.UTF-8
|
24 |
+
|
25 |
+
ADD . /opt/program/
|
26 |
+
|
27 |
+
ENV PATH="/opt/program:${PATH}"
|
28 |
+
|
29 |
+
WORKDIR /opt/program
|
30 |
+
|
31 |
+
RUN chmod +x *
|
32 |
+
|
33 |
+
# Removed build ta-lib from source as we're using [pip3 install TA-Lib-Precompiled] for faster docker build
|
34 |
+
|
35 |
+
# WORKDIR /opt/program/.github/dependencies/
|
36 |
+
# RUN tar -xzf ta-lib-0.4.0-src.tar.gz
|
37 |
+
|
38 |
+
# WORKDIR /opt/program/.github/dependencies/ta-lib/
|
39 |
+
# RUN ./configure --prefix=/usr --build=$(uname -m)-unknown-linux-gnu
|
40 |
+
# RUN make
|
41 |
+
# RUN make install
|
42 |
+
|
43 |
+
WORKDIR /opt/program/
|
44 |
+
RUN python3 -m pip install --upgrade pip
|
45 |
+
|
46 |
+
RUN pip3 install -r "requirements.txt"
|
47 |
+
RUN pip3 install --no-deps advanced-ta
|
48 |
+
|
49 |
+
ENV PYTHONUNBUFFERED=TRUE
|
50 |
+
ENV PYTHONDONTWRITEBYTECODE=TRUE
|
51 |
+
|
52 |
+
ENV SCREENIPY_DOCKER = TRUE
|
53 |
+
|
54 |
+
ENV SCREENIPY_GUI = TRUE
|
55 |
+
|
56 |
+
EXPOSE 8501
|
57 |
+
|
58 |
+
HEALTHCHECK CMD curl --fail http://localhost:7860/_stcore/health
|
59 |
+
|
60 |
+
WORKDIR /opt/program/src/
|
61 |
+
ENTRYPOINT ["streamlit", "run", "streamlit_app.py", "--server.port=7860", "--server.address=0.0.0.0"]
|
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.
|
Makefile
ADDED
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
build:
|
2 |
+
docker build -t screeni-py .
|
3 |
+
|
4 |
+
run:
|
5 |
+
docker run -d --name screeni-py -p 8501:8501 screeni-py
|
6 |
+
|
7 |
+
interactive-run:
|
8 |
+
docker run -p 8501:8501 screeni-py
|
9 |
+
|
10 |
+
shell:
|
11 |
+
docker run -it --entrypoint /bin/bash screeni-py
|
12 |
+
|
13 |
+
stop-container:
|
14 |
+
@if [ "$(shell docker ps -q -f name=screeni-py)" ]; then \
|
15 |
+
docker stop screeni-py; \
|
16 |
+
else \
|
17 |
+
echo "Container screeni-py is not running."; \
|
18 |
+
fi
|
19 |
+
|
20 |
+
remove-container:
|
21 |
+
@if [ "$(shell docker ps -a -q -f name=screeni-py)" ]; then \
|
22 |
+
docker rm screeni-py; \
|
23 |
+
else \
|
24 |
+
echo "Container screeni-py does not exist."; \
|
25 |
+
fi
|
26 |
+
|
27 |
+
system-clean:
|
28 |
+
docker system prune --force
|
29 |
+
|
30 |
+
rebuild: stop-container remove-container build system-clean
|
actions-data-download/stock_data_140823.pkl
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:6d0f881b30f583d74a3956eb032ffad658ca05075c971466b4bf59ce033feb16
|
3 |
+
size 39838610
|
patterns/IPO1.png
ADDED
patterns/IPO2.png
ADDED
patterns/IPO3.png
ADDED
patterns/MomentumGainer.png
ADDED
requirements.txt
ADDED
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# numpy==1.21.0 # Installed as dependency for yfinance, scipy, matplotlib, pandas
|
2 |
+
numpy
|
3 |
+
appdirs
|
4 |
+
cachecontrol
|
5 |
+
contextlib2
|
6 |
+
distlib
|
7 |
+
distro
|
8 |
+
html5lib
|
9 |
+
ipaddr
|
10 |
+
lockfile
|
11 |
+
nsetools
|
12 |
+
openpyxl
|
13 |
+
pep517
|
14 |
+
pip
|
15 |
+
pip-chill
|
16 |
+
progress
|
17 |
+
# pyinstaller==5.6.2
|
18 |
+
pytest-mock
|
19 |
+
pytoml
|
20 |
+
retrying
|
21 |
+
scipy==1.11.2
|
22 |
+
# ta-lib
|
23 |
+
TA-Lib-Precompiled
|
24 |
+
tabulate
|
25 |
+
# yfinance==0.1.87
|
26 |
+
yfinance==0.2.32
|
27 |
+
alive-progress==1.6.2
|
28 |
+
Pillow
|
29 |
+
scikit-learn==1.3.2
|
30 |
+
joblib
|
31 |
+
altgraph # Installed as dependency for pyinstaller
|
32 |
+
atomicwrites # Installed as dependency for pytest
|
33 |
+
attrs # Installed as dependency for pytest
|
34 |
+
certifi # Installed as dependency for requests
|
35 |
+
chardet # Installed as dependency for requests
|
36 |
+
colorama # Installed as dependency for pytest
|
37 |
+
dateutils # Installed as dependency for nsetools
|
38 |
+
et-xmlfile # Installed as dependency for openpyxl
|
39 |
+
future # Installed as dependency for pefile
|
40 |
+
idna # Installed as dependency for requests
|
41 |
+
iniconfig # Installed as dependency for pytest
|
42 |
+
lxml # Installed as dependency for yfinance
|
43 |
+
msgpack # Installed as dependency for cachecontrol
|
44 |
+
multitasking # Installed as dependency for yfinance
|
45 |
+
packaging # Installed as dependency for pytest
|
46 |
+
pandas==2.1.2 # Installed as dependency for yfinance
|
47 |
+
pefile # Installed as dependency for pyinstaller
|
48 |
+
pluggy # Installed as dependency for pytest
|
49 |
+
py # Installed as dependency for pytest
|
50 |
+
pyinstaller-hooks-contrib # Installed as dependency for pyinstaller
|
51 |
+
pyparsing # Installed as dependency for packaging, matplotlib
|
52 |
+
pytest # Installed as dependency for pytest-mock
|
53 |
+
python-dateutil # Installed as dependency for dateutils, matplotlib, pandas
|
54 |
+
pytz # Installed as dependency for dateutils, pandas
|
55 |
+
pywin32-ctypes # Installed as dependency for pyinstaller
|
56 |
+
requests # Installed as dependency for yfinance, cachecontrol
|
57 |
+
setuptools # Installed as dependency for pyinstaller
|
58 |
+
six # Installed as dependency for cycler, nsetools, packaging, retrying, python-dateutil, html5lib
|
59 |
+
toml # Installed as dependency for pep517, pytest
|
60 |
+
urllib3 # Installed as dependency for requests
|
61 |
+
webencodings # Installed as dependency for html5lib
|
62 |
+
pandas_ta
|
63 |
+
# protobuf==3.19.6
|
64 |
+
protobuf
|
65 |
+
# streamlit==1.26.0
|
66 |
+
streamlit
|
67 |
+
tensorflow
|
68 |
+
chromadb==0.4.10
|
69 |
+
mplfinance==0.12.9-beta.7
|
70 |
+
num2words
|
71 |
+
advanced-ta
|
run_screenipy.sh
ADDED
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#!/bin/bash
|
2 |
+
variable_name="SCREENIPY_GUI"
|
3 |
+
|
4 |
+
cd src
|
5 |
+
|
6 |
+
# Check if the script was provided with at least one argument
|
7 |
+
if [ $# -lt 1 ]; then
|
8 |
+
echo "Usage: $0 [--gui|--cli]"
|
9 |
+
exit 1
|
10 |
+
fi
|
11 |
+
|
12 |
+
# Check the value of the first argument
|
13 |
+
if [ "$1" = "--gui" ]; then
|
14 |
+
export SCREENIPY_GUI=TRUE
|
15 |
+
echo " "
|
16 |
+
echo "Starting in GUI mode... Copy and Paste following URL in your browser.."
|
17 |
+
streamlit run streamlit_app.py --server.port=8501 --server.address=0.0.0.0
|
18 |
+
elif [ "$1" = "--cli" ]; then
|
19 |
+
unset "SCREENIPY_GUI"
|
20 |
+
echo "Starting in CLI mode..."
|
21 |
+
python3 screenipy.py
|
22 |
+
else
|
23 |
+
echo "Invalid argument. Usage: $0 [--gui|--cli]"
|
24 |
+
exit 1
|
25 |
+
fi
|
screenshots/NSE_Logo.svg
ADDED
screenshots/config.png
ADDED
screenshots/done.png
ADDED
screenshots/home.png
ADDED
screenshots/results.png
ADDED
screenshots/screening.png
ADDED
screenshots/screenipy_demo.gif
ADDED
Git LFS Details
|
src/.streamlit/config.toml
ADDED
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
1 |
+
[theme]
|
2 |
+
base="light"
|
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 |
+
from classes.ScreenipyTA import ScreenerTA
|
10 |
+
from classes.ColorText import colorText
|
11 |
+
|
12 |
+
class CandlePatterns:
|
13 |
+
|
14 |
+
reversalPatternsBullish = ['Morning Star', 'Morning Doji Star', '3 Inside Up', 'Hammer', '3 White Soldiers', 'Bullish Engulfing', 'Dragonfly Doji', 'Supply Drought', 'Demand Rise']
|
15 |
+
reversalPatternsBearish = ['Evening Star', 'Evening Doji Star', '3 Inside Down', 'Inverted Hammer', 'Hanging Man', '3 Black Crows', 'Bearish Engulfing', 'Shooting Star', 'Gravestone Doji']
|
16 |
+
|
17 |
+
def __init__(self):
|
18 |
+
pass
|
19 |
+
|
20 |
+
# Find candle-stick patterns
|
21 |
+
# Arrange if statements with max priority from top to bottom
|
22 |
+
def findPattern(self, data, dict, saveDict):
|
23 |
+
data = data.head(4)
|
24 |
+
data = data[::-1]
|
25 |
+
|
26 |
+
check = ScreenerTA.CDLMORNINGSTAR(data['Open'], data['High'], data['Low'], data['Close'])
|
27 |
+
if(check):
|
28 |
+
dict['Pattern'] = colorText.BOLD + colorText.GREEN + 'Morning Star' + colorText.END
|
29 |
+
saveDict['Pattern'] = 'Morning Star'
|
30 |
+
return True
|
31 |
+
|
32 |
+
check = ScreenerTA.CDLMORNINGDOJISTAR(data['Open'], data['High'], data['Low'], data['Close'])
|
33 |
+
if(check):
|
34 |
+
dict['Pattern'] = colorText.BOLD + colorText.GREEN + 'Morning Doji Star' + colorText.END
|
35 |
+
saveDict['Pattern'] = 'Morning Doji Star'
|
36 |
+
return True
|
37 |
+
|
38 |
+
check = ScreenerTA.CDLEVENINGSTAR(data['Open'], data['High'], data['Low'], data['Close'])
|
39 |
+
if(check):
|
40 |
+
dict['Pattern'] = colorText.BOLD + colorText.FAIL + 'Evening Star' + colorText.END
|
41 |
+
saveDict['Pattern'] = 'Evening Star'
|
42 |
+
return True
|
43 |
+
|
44 |
+
check = ScreenerTA.CDLEVENINGDOJISTAR(data['Open'], data['High'], data['Low'], data['Close'])
|
45 |
+
if(check):
|
46 |
+
dict['Pattern'] = colorText.BOLD + colorText.FAIL + 'Evening Doji Star' + colorText.END
|
47 |
+
saveDict['Pattern'] = 'Evening Doji Star'
|
48 |
+
return True
|
49 |
+
|
50 |
+
check = ScreenerTA.CDLLADDERBOTTOM(data['Open'], data['High'], data['Low'], data['Close'])
|
51 |
+
if(check):
|
52 |
+
if(check is not None and check.tail(1).item() > 0):
|
53 |
+
dict['Pattern'] = colorText.BOLD + colorText.GREEN + 'Ladder Bottom' + colorText.END
|
54 |
+
saveDict['Pattern'] = 'Bullish Ladder Bottom'
|
55 |
+
else:
|
56 |
+
dict['Pattern'] = colorText.BOLD + colorText.FAIL + 'Ladder Bottom' + colorText.END
|
57 |
+
saveDict['Pattern'] = 'Bearish Ladder Bottom'
|
58 |
+
return True
|
59 |
+
|
60 |
+
check = ScreenerTA.CDL3LINESTRIKE(data['Open'], data['High'], data['Low'], data['Close'])
|
61 |
+
if(check):
|
62 |
+
if(check is not None and check.tail(1).item() > 0):
|
63 |
+
dict['Pattern'] = colorText.BOLD + colorText.GREEN + '3 Line Strike' + colorText.END
|
64 |
+
else:
|
65 |
+
dict['Pattern'] = colorText.BOLD + colorText.FAIL + '3 Line Strike' + colorText.END
|
66 |
+
saveDict['Pattern'] = '3 Line Strike'
|
67 |
+
return True
|
68 |
+
|
69 |
+
check = ScreenerTA.CDL3BLACKCROWS(data['Open'], data['High'], data['Low'], data['Close'])
|
70 |
+
if(check):
|
71 |
+
dict['Pattern'] = colorText.BOLD + colorText.FAIL + '3 Black Crows' + colorText.END
|
72 |
+
saveDict['Pattern'] = '3 Black Crows'
|
73 |
+
return True
|
74 |
+
|
75 |
+
check = ScreenerTA.CDL3INSIDE(data['Open'], data['High'], data['Low'], data['Close'])
|
76 |
+
if(check):
|
77 |
+
if(check is not None and check.tail(1).item() > 0):
|
78 |
+
dict['Pattern'] = colorText.BOLD + colorText.GREEN + '3 Outside Up' + colorText.END
|
79 |
+
saveDict['Pattern'] = '3 Inside Up'
|
80 |
+
else:
|
81 |
+
dict['Pattern'] = colorText.BOLD + colorText.FAIL + '3 Outside Down' + colorText.END
|
82 |
+
saveDict['Pattern'] = '3 Inside Down'
|
83 |
+
return True
|
84 |
+
|
85 |
+
check = ScreenerTA.CDL3OUTSIDE(data['Open'], data['High'], data['Low'], data['Close'])
|
86 |
+
if(check > 0):
|
87 |
+
dict['Pattern'] = colorText.BOLD + colorText.GREEN + '3 Outside Up' + colorText.END
|
88 |
+
saveDict['Pattern'] = '3 Outside Up'
|
89 |
+
return True
|
90 |
+
elif(check < 0):
|
91 |
+
dict['Pattern'] = colorText.BOLD + colorText.FAIL + '3 Outside Down' + colorText.END
|
92 |
+
saveDict['Pattern'] = '3 Outside Down'
|
93 |
+
return True
|
94 |
+
|
95 |
+
check = ScreenerTA.CDL3WHITESOLDIERS(data['Open'], data['High'], data['Low'], data['Close'])
|
96 |
+
if(check):
|
97 |
+
dict['Pattern'] = colorText.BOLD + colorText.GREEN + '3 White Soldiers' + colorText.END
|
98 |
+
saveDict['Pattern'] = '3 White Soldiers'
|
99 |
+
return True
|
100 |
+
|
101 |
+
check = ScreenerTA.CDLHARAMI(data['Open'], data['High'], data['Low'], data['Close'])
|
102 |
+
if(check):
|
103 |
+
if(check is not None and check.tail(1).item() > 0):
|
104 |
+
dict['Pattern'] = colorText.BOLD + colorText.GREEN + 'Bullish Harami' + colorText.END
|
105 |
+
saveDict['Pattern'] = 'Bullish Harami'
|
106 |
+
else:
|
107 |
+
dict['Pattern'] = colorText.BOLD + colorText.FAIL + 'Bearish Harami' + colorText.END
|
108 |
+
saveDict['Pattern'] = 'Bearish Harami'
|
109 |
+
return True
|
110 |
+
|
111 |
+
check = ScreenerTA.CDLHARAMICROSS(data['Open'], data['High'], data['Low'], data['Close'])
|
112 |
+
if(check):
|
113 |
+
if(check is not None and check.tail(1).item() > 0):
|
114 |
+
dict['Pattern'] = colorText.BOLD + colorText.GREEN + 'Bullish Harami Cross' + colorText.END
|
115 |
+
saveDict['Pattern'] = 'Bullish Harami Cross'
|
116 |
+
else:
|
117 |
+
dict['Pattern'] = colorText.BOLD + colorText.FAIL + 'Bearish Harami Cross' + colorText.END
|
118 |
+
saveDict['Pattern'] = 'Bearish Harami Cross'
|
119 |
+
return True
|
120 |
+
|
121 |
+
check = ScreenerTA.CDLMARUBOZU(data['Open'], data['High'], data['Low'], data['Close'])
|
122 |
+
if(check):
|
123 |
+
if(check is not None and check.tail(1).item() > 0):
|
124 |
+
dict['Pattern'] = colorText.BOLD + colorText.GREEN + 'Bullish Marubozu' + colorText.END
|
125 |
+
saveDict['Pattern'] = 'Bullish Marubozu'
|
126 |
+
else:
|
127 |
+
dict['Pattern'] = colorText.BOLD + colorText.FAIL + 'Bearish Marubozu' + colorText.END
|
128 |
+
saveDict['Pattern'] = 'Bearish Marubozu'
|
129 |
+
return True
|
130 |
+
|
131 |
+
check = ScreenerTA.CDLHANGINGMAN(data['Open'], data['High'], data['Low'], data['Close'])
|
132 |
+
if(check):
|
133 |
+
dict['Pattern'] = colorText.BOLD + colorText.FAIL + 'Hanging Man' + colorText.END
|
134 |
+
saveDict['Pattern'] = 'Hanging Man'
|
135 |
+
return True
|
136 |
+
|
137 |
+
check = ScreenerTA.CDLHAMMER(data['Open'], data['High'], data['Low'], data['Close'])
|
138 |
+
if(check):
|
139 |
+
dict['Pattern'] = colorText.BOLD + colorText.GREEN + 'Hammer' + colorText.END
|
140 |
+
saveDict['Pattern'] = 'Hammer'
|
141 |
+
return True
|
142 |
+
|
143 |
+
check = ScreenerTA.CDLINVERTEDHAMMER(data['Open'], data['High'], data['Low'], data['Close'])
|
144 |
+
if(check):
|
145 |
+
dict['Pattern'] = colorText.BOLD + colorText.GREEN + 'Inverted Hammer' + colorText.END
|
146 |
+
saveDict['Pattern'] = 'Inverted Hammer'
|
147 |
+
return True
|
148 |
+
|
149 |
+
check = ScreenerTA.CDLSHOOTINGSTAR(data['Open'], data['High'], data['Low'], data['Close'])
|
150 |
+
if(check):
|
151 |
+
dict['Pattern'] = colorText.BOLD + colorText.FAIL + 'Shooting Star' + colorText.END
|
152 |
+
saveDict['Pattern'] = 'Shooting Star'
|
153 |
+
return True
|
154 |
+
|
155 |
+
check = ScreenerTA.CDLDRAGONFLYDOJI(data['Open'], data['High'], data['Low'], data['Close'])
|
156 |
+
if(check):
|
157 |
+
dict['Pattern'] = colorText.BOLD + colorText.GREEN + 'Dragonfly Doji' + colorText.END
|
158 |
+
saveDict['Pattern'] = 'Dragonfly Doji'
|
159 |
+
return True
|
160 |
+
|
161 |
+
check = ScreenerTA.CDLGRAVESTONEDOJI(data['Open'], data['High'], data['Low'], data['Close'])
|
162 |
+
if(check):
|
163 |
+
dict['Pattern'] = colorText.BOLD + colorText.FAIL + 'Gravestone Doji' + colorText.END
|
164 |
+
saveDict['Pattern'] = 'Gravestone Doji'
|
165 |
+
return True
|
166 |
+
|
167 |
+
check = ScreenerTA.CDLDOJI(data['Open'], data['High'], data['Low'], data['Close'])
|
168 |
+
if(check):
|
169 |
+
dict['Pattern'] = colorText.BOLD + 'Doji' + colorText.END
|
170 |
+
saveDict['Pattern'] = 'Doji'
|
171 |
+
return True
|
172 |
+
|
173 |
+
check = ScreenerTA.CDLENGULFING(data['Open'], data['High'], data['Low'], data['Close'])
|
174 |
+
if(check > 0):
|
175 |
+
dict['Pattern'] = colorText.BOLD + colorText.GREEN + 'Bullish Engulfing' + colorText.END
|
176 |
+
saveDict['Pattern'] = 'Bullish Engulfing'
|
177 |
+
return True
|
178 |
+
elif(check < 0):
|
179 |
+
dict['Pattern'] = colorText.BOLD + colorText.FAIL + 'Bearish Engulfing' + colorText.END
|
180 |
+
saveDict['Pattern'] = 'Bearish Engulfing'
|
181 |
+
return True
|
182 |
+
|
183 |
+
dict['Pattern'] = ''
|
184 |
+
saveDict['Pattern'] = ''
|
185 |
+
return False
|
src/classes/Changelog.py
ADDED
@@ -0,0 +1,296 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
'''
|
2 |
+
* Project : Screenipy
|
3 |
+
* Author : Pranjal Joshi
|
4 |
+
* Created : 28/04/2021
|
5 |
+
* Description : Class for maintaining changelog
|
6 |
+
'''
|
7 |
+
|
8 |
+
from classes.ColorText import colorText
|
9 |
+
|
10 |
+
VERSION = "2.22"
|
11 |
+
|
12 |
+
changelog = colorText.BOLD + '[ChangeLog]\n' + colorText.END + colorText.BLUE + '''
|
13 |
+
[1.00 - Beta]
|
14 |
+
1. Initial Release for beta testing
|
15 |
+
2. Minor Bug fixes
|
16 |
+
|
17 |
+
[1.01]
|
18 |
+
1. Inside Bar detection added.
|
19 |
+
2. OTA Software Update Implemented.
|
20 |
+
3. Stock shuffling added while screening
|
21 |
+
4. Results will be now also stored in the excel (screenipy-result.xlsx) file.
|
22 |
+
5. UI cosmetic updates for pretty-printing!
|
23 |
+
|
24 |
+
[1.02]
|
25 |
+
1. Feature added to screen only STAGE-2 stocks.
|
26 |
+
2. OTA update download bug-fixed.
|
27 |
+
3. Auto generate default config if not found.
|
28 |
+
4. Minor bug-fixes.
|
29 |
+
|
30 |
+
[1.03]
|
31 |
+
1. Result excel file will not be overwritten now. Each result file will be saved with timestamp.
|
32 |
+
2. Candlestick pattern recognition added.
|
33 |
+
|
34 |
+
[1.04]
|
35 |
+
1. OTA Software Update bug-fixed.
|
36 |
+
2. Minor Improvements.
|
37 |
+
|
38 |
+
[1.05]
|
39 |
+
1. More candlestick pattern added for recognition.
|
40 |
+
2. Option added to find stock with lowest volume in last 'N'-days to early detect possibility of breakout.
|
41 |
+
3. Last screened results will be stored and can be viewed with Option > 7.
|
42 |
+
4. Minor Bug-fixes and improvements.
|
43 |
+
|
44 |
+
[1.06]
|
45 |
+
1. Option > 0 added - Screen stocks by enterning it's name (stock code).
|
46 |
+
2. Stability fixes and improvements.
|
47 |
+
3. Last screened results will be stored and can be viewed with Option > 7.
|
48 |
+
|
49 |
+
[1.07]
|
50 |
+
1. Program Window will not automatically close now.
|
51 |
+
2. Bug fixes and improvements.
|
52 |
+
|
53 |
+
[1.08]
|
54 |
+
1. Prompt added for saving excel after screening.
|
55 |
+
2. Program back-end architecture updated.
|
56 |
+
|
57 |
+
[1.09]
|
58 |
+
1. RSI based screening added as Option > 5.
|
59 |
+
2. Minor Performance Improvements.
|
60 |
+
|
61 |
+
[1.10]
|
62 |
+
1. Trend detection for the timeframe of analysis added.
|
63 |
+
|
64 |
+
[1.11]
|
65 |
+
1. Option-6 -> Screen for stocks showing Reversal Signal added
|
66 |
+
2. Stage-2 Screening logic improved for identifying best stocks only.
|
67 |
+
3. Trend detection has been improved.
|
68 |
+
4. Bugs and Runtime warnings fixed.
|
69 |
+
|
70 |
+
[1.12]
|
71 |
+
1. MA now gives more info like Candle Crossing and At Support/Resistance.
|
72 |
+
2. More Patterns added for Reversal Detection.
|
73 |
+
4. Trend detection enhanced for the timeframe of analysis.
|
74 |
+
5. Runtime Warnings have been fixed.
|
75 |
+
|
76 |
+
[1.13]
|
77 |
+
1. Chart Pattern Detection added. Option > 7
|
78 |
+
2. Screen for Inside Bar Chart pattern.
|
79 |
+
3. Documentation updated and Performance fixes.
|
80 |
+
|
81 |
+
[1.14][1.15]
|
82 |
+
1. Screening stocks with parallel processing using all cores available in machine. (Thanks to @swarpatel23)
|
83 |
+
2. Minor Bug-fixes and Improvements.
|
84 |
+
|
85 |
+
[1.16]
|
86 |
+
1. Bullish Momentum Finder added. Option > 6 > 3
|
87 |
+
2. Stock Data Caching added. (Thanks to @swarpatel23)
|
88 |
+
3. Codefactoring Improved.
|
89 |
+
4. Ctrl+C crash fixed.
|
90 |
+
|
91 |
+
[1.17]
|
92 |
+
1. Breakout detection improved.
|
93 |
+
2. Progressbar added.
|
94 |
+
3. Watchlist creation in Excel file and its screening.
|
95 |
+
|
96 |
+
[1.18]
|
97 |
+
1. Cache and Performance fixes.
|
98 |
+
2. Breakout Calculation Enhanced.
|
99 |
+
|
100 |
+
[1.19]
|
101 |
+
1. New Feature: Search for Bullish Reversal at MA. Option > 6 > 4
|
102 |
+
|
103 |
+
[1.20]
|
104 |
+
1. Screen stocks as per your favorite index. (Thanks to @swarpatel23)
|
105 |
+
|
106 |
+
[1.21]
|
107 |
+
1. TradingView Hyperlink added for stock symbol.
|
108 |
+
|
109 |
+
[1.22]
|
110 |
+
1. Broken yfinance API fixed.
|
111 |
+
|
112 |
+
[1.23]
|
113 |
+
1. Bug fixed for DualCore CPU.
|
114 |
+
2. Dependencies updated.
|
115 |
+
|
116 |
+
[1.24]
|
117 |
+
1. IPO Base Breakout pattern added. Option > 7 > 3.
|
118 |
+
2. Data fetching interval fixed.
|
119 |
+
3. Permission bug-fixes for some windows users.
|
120 |
+
4. Result table optimized.
|
121 |
+
|
122 |
+
[1.25]
|
123 |
+
1. Default configuration parameters optimized.
|
124 |
+
2. Configuration generation on first time usage don't need restart anymore!
|
125 |
+
3. Minor bug-fixes.
|
126 |
+
|
127 |
+
[1.26]
|
128 |
+
1. New Feature: Screen for the MA Confluence pattern Option > 7 > 4.
|
129 |
+
|
130 |
+
[1.27]
|
131 |
+
1. Display more information about an update when it is available.
|
132 |
+
2. Minor Fixes (MA Confluence).
|
133 |
+
|
134 |
+
[1.28]
|
135 |
+
1. Volume Spread Analysis added for Bullish Reversals. Option > 6 > 5
|
136 |
+
|
137 |
+
[1.29]
|
138 |
+
1. VSA screening optimized.
|
139 |
+
2. Error handling and timeout optimized.
|
140 |
+
3. Build Test mode added for CI/CD.
|
141 |
+
|
142 |
+
[1.30]
|
143 |
+
1. New Tickers Group - Screen only for Newly Listed IPOs (Last 1 Yr)
|
144 |
+
2. Major bug fix - stage 2 criteria won't be applied for new listings.
|
145 |
+
3. Validation Fixed for Volume & MA Signal (Optimized for new listings)
|
146 |
+
4. Excel save header name bug fixed.
|
147 |
+
|
148 |
+
[1.31]
|
149 |
+
1. BugFixes for false detection of patterns - IPO Base, Inside Bar.
|
150 |
+
2. New Application Icon.
|
151 |
+
3. Experimental - VCP Detection : Option > 7 > 4
|
152 |
+
|
153 |
+
[1.32]
|
154 |
+
1. Performance Optimization.
|
155 |
+
2. Minor Improvements.
|
156 |
+
3. Argument added for Data download only : run screenipy.exe -d
|
157 |
+
|
158 |
+
[1.33]
|
159 |
+
1. Alternate Data source added.
|
160 |
+
2. Workflow added to create cache data on cloud.
|
161 |
+
|
162 |
+
[1.34]
|
163 |
+
1. New Reversal - Narrow Range : Try Option 6 > 6
|
164 |
+
2. Cache loading fixes for Pre-Market timings. Refer PR #103
|
165 |
+
3. Progressbar added for Alternate Source Cache Download.
|
166 |
+
|
167 |
+
[1.35]
|
168 |
+
1. Separate Algorithms for NR depending on Live/After-Market hours.
|
169 |
+
2. NRx results fixed in Momentum Gainer Screening.
|
170 |
+
|
171 |
+
[1.36]
|
172 |
+
1. Updated CSV URLs to New NSE Site. (#113)
|
173 |
+
|
174 |
+
[1.37]
|
175 |
+
1. New Chart Pattern -> Buy at Trendline : Try Option 7 > 5
|
176 |
+
|
177 |
+
[1.38]
|
178 |
+
1. Added AI based predictions for Nifty closing on next day : Select Index for Screening > N
|
179 |
+
|
180 |
+
[1.39]
|
181 |
+
1. Intraday Live Scanner - 5 EMA for Indices : Try Option `E`
|
182 |
+
|
183 |
+
[1.40]
|
184 |
+
1. Nifty AI Prediction - Model Accuracy Enhanced by new preprocessing - Better Gap predictions
|
185 |
+
|
186 |
+
[1.41]
|
187 |
+
1. Fetching of Stock Codes list fixed after NSE migration to newer website - Not using `nsetools` anymore
|
188 |
+
|
189 |
+
[1.42]
|
190 |
+
1. Down trend detection bug fixed
|
191 |
+
2. % Change added with LTP
|
192 |
+
|
193 |
+
[1.43]
|
194 |
+
1. New Index added - F&O Only stocks
|
195 |
+
|
196 |
+
[1.44]
|
197 |
+
1. Migrated ta-lib dependency to pandas_ta
|
198 |
+
|
199 |
+
[1.45]
|
200 |
+
1. Minor bug fixes after dependency change
|
201 |
+
|
202 |
+
[1.46]
|
203 |
+
1. TA-Lib reanabled. Dockerized for better distribution of the tool
|
204 |
+
|
205 |
+
|
206 |
+
[2.00]
|
207 |
+
1. Streamlit UI (WebApp) added
|
208 |
+
2. Multi-Arch Docker support enabled
|
209 |
+
|
210 |
+
[2.01]
|
211 |
+
1. Docker build fixed - Versioning critical bug fixed for further OTA updates
|
212 |
+
|
213 |
+
[2.02]
|
214 |
+
1. Newly Listed (IPO) index critical bug fixed
|
215 |
+
2. OTA Updates fixed for GUI
|
216 |
+
3. Cosmetic improvements
|
217 |
+
4. YouTube Video added to docs
|
218 |
+
|
219 |
+
[2.03]
|
220 |
+
1. AI based Nifty-50 Gap up/down prediction added to GUI
|
221 |
+
2. Cosmetic updates and minor bug-fixes
|
222 |
+
3. Search Similar Stock Added
|
223 |
+
4. Executables Deprecated now onwards
|
224 |
+
|
225 |
+
[2.04]
|
226 |
+
1. OTA update fixed - caching added in GUI
|
227 |
+
2. Moved to TA-Lib-Precompiled (0.4.25)
|
228 |
+
3. Progressbar added for screening to GUI
|
229 |
+
4. Documentation updated
|
230 |
+
|
231 |
+
[2.05]
|
232 |
+
1. Download Results button added
|
233 |
+
2. Configuration save bug fixed for checkboxes
|
234 |
+
3. Attempted to changed Docker DNS
|
235 |
+
|
236 |
+
|
237 |
+
[2.06]
|
238 |
+
1. Links added with cosmetic upgrade
|
239 |
+
2. Docs updated
|
240 |
+
|
241 |
+
[2.07]
|
242 |
+
1. US S&P 500 Index added - Try Index `15 > US S&P 500`
|
243 |
+
2. Minor improvemnets
|
244 |
+
|
245 |
+
[2.08]
|
246 |
+
1. Nifty Prediction enhanced - New AI model uses Crude and Gold data for Gap Prediction
|
247 |
+
|
248 |
+
[2.09]
|
249 |
+
1. Dependencies bumped to pandas-2.1.2 scikit-learn-1.3.2 for (pip install advanced-ta) compatibility
|
250 |
+
2. Added Lorentzian Classifier based screening criteria - Try Option `6 > Reversal signals and 7 > Lorentzian Classification` (Extending Gratitude towards Justin Dehorty and Loki Arya for Open-Sourcing this one ❤️)
|
251 |
+
3. MA-Confluence bug fixed
|
252 |
+
|
253 |
+
[2.10]
|
254 |
+
1. Position Size Calculator added as a new tab
|
255 |
+
|
256 |
+
[2.11]
|
257 |
+
1. Nifty Prediction issue fixed - Model is now trained on CPU instead of Apple-M1 GPU
|
258 |
+
|
259 |
+
[2.12]
|
260 |
+
1. Cosmetic Updates for Position Size Calculator
|
261 |
+
2. Python base bumped to 3.11.6-slim-bookworm
|
262 |
+
|
263 |
+
[2.13]
|
264 |
+
1. Date based Backtesting Added for Screening
|
265 |
+
2. Inside bar detection broken - bug fixed
|
266 |
+
3. Auto enhanced debug on console in dev release
|
267 |
+
|
268 |
+
[2.14]
|
269 |
+
1. Dropdowns added for duration and period in configration tab
|
270 |
+
|
271 |
+
[2.15]
|
272 |
+
1. MA Reversal improved for trend following (Inspired from Siddhart Bhanushali's 44 SMA)
|
273 |
+
|
274 |
+
[2.16]
|
275 |
+
1. Nifty Prediction NaN values handled gracefully with forward filling if data is absent
|
276 |
+
2. Ticker 0 > Search by Stock name - re-enabled in GUI
|
277 |
+
|
278 |
+
[2.17]
|
279 |
+
1. Backtest Report column added for backtest screening runs
|
280 |
+
|
281 |
+
[2.18]
|
282 |
+
1. Critical backtest bug fixed (dropna axis-1 removed from results)
|
283 |
+
2. Clear stock cached data button added
|
284 |
+
|
285 |
+
[2.19]
|
286 |
+
1. New Index (Group of Indices) `16 > Sectoral Indices` added
|
287 |
+
|
288 |
+
[2.20]
|
289 |
+
1. Bugfixes - Clear cache button random key added to fix re-rendering issues
|
290 |
+
|
291 |
+
[2.21]
|
292 |
+
1. Dependency updated - `advanced-ta` lib for bugfixes and performance improvement in Lorentzian Classifier
|
293 |
+
|
294 |
+
[2.22]
|
295 |
+
1. RSI and 9 SMA of RSI based reversal added - Momentum based execution strategy.
|
296 |
+
''' + 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,206 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
'''
|
2 |
+
* Project : Screenipy
|
3 |
+
* Author : Pranjal Joshi
|
4 |
+
* Created : 28/04/2021
|
5 |
+
* Description : Class for managing the user configuration
|
6 |
+
'''
|
7 |
+
|
8 |
+
import sys
|
9 |
+
import os
|
10 |
+
import glob
|
11 |
+
import re
|
12 |
+
import configparser
|
13 |
+
from datetime import date
|
14 |
+
from classes.ColorText import colorText
|
15 |
+
|
16 |
+
parser = configparser.ConfigParser(strict=False)
|
17 |
+
|
18 |
+
# Default attributes for Downloading Cache from Git repo
|
19 |
+
default_period = '300d'
|
20 |
+
default_duration = '1d'
|
21 |
+
|
22 |
+
# This Class manages read/write of user configuration
|
23 |
+
class tools:
|
24 |
+
|
25 |
+
def __init__(self):
|
26 |
+
self.consolidationPercentage = 10
|
27 |
+
self.volumeRatio = 2
|
28 |
+
self.minLTP = 20.0
|
29 |
+
self.maxLTP = 50000
|
30 |
+
self.period = '300d'
|
31 |
+
self.duration = '1d'
|
32 |
+
self.daysToLookback = 30
|
33 |
+
self.shuffleEnabled = True
|
34 |
+
self.cacheEnabled = True
|
35 |
+
self.stageTwo = False
|
36 |
+
self.useEMA = False
|
37 |
+
|
38 |
+
def deleteStockData(self,excludeFile=None):
|
39 |
+
for f in glob.glob('stock_data*.pkl'):
|
40 |
+
if excludeFile is not None:
|
41 |
+
if not f.endswith(excludeFile):
|
42 |
+
os.remove(f)
|
43 |
+
else:
|
44 |
+
os.remove(f)
|
45 |
+
|
46 |
+
# Handle user input and save config
|
47 |
+
|
48 |
+
def setConfig(self, parser, default=False, showFileCreatedText=True):
|
49 |
+
if default:
|
50 |
+
parser.add_section('config')
|
51 |
+
parser.set('config', 'period', self.period)
|
52 |
+
parser.set('config', 'daysToLookback', str(self.daysToLookback))
|
53 |
+
parser.set('config', 'duration', self.duration)
|
54 |
+
parser.set('config', 'minPrice', str(self.minLTP))
|
55 |
+
parser.set('config', 'maxPrice', str(self.maxLTP))
|
56 |
+
parser.set('config', 'volumeRatio', str(self.volumeRatio))
|
57 |
+
parser.set('config', 'consolidationPercentage',
|
58 |
+
str(self.consolidationPercentage))
|
59 |
+
parser.set('config', 'shuffle', 'y')
|
60 |
+
parser.set('config', 'cacheStockData', 'y')
|
61 |
+
parser.set('config', 'onlyStageTwoStocks', 'y' if self.stageTwo else 'n')
|
62 |
+
parser.set('config', 'useEMA', 'y' if self.useEMA else 'n')
|
63 |
+
try:
|
64 |
+
fp = open('screenipy.ini', 'w')
|
65 |
+
parser.write(fp)
|
66 |
+
fp.close()
|
67 |
+
if showFileCreatedText:
|
68 |
+
print(colorText.BOLD + colorText.GREEN +
|
69 |
+
'[+] Default configuration generated as user configuration is not found!' + colorText.END)
|
70 |
+
print(colorText.BOLD + colorText.GREEN +
|
71 |
+
'[+] Use Option > 5 to edit in future.' + colorText.END)
|
72 |
+
print(colorText.BOLD + colorText.GREEN +
|
73 |
+
'[+] Close and Restart the program now.' + colorText.END)
|
74 |
+
input('')
|
75 |
+
sys.exit(0)
|
76 |
+
except IOError:
|
77 |
+
print(colorText.BOLD + colorText.FAIL +
|
78 |
+
'[+] Failed to save user config. Exiting..' + colorText.END)
|
79 |
+
input('')
|
80 |
+
sys.exit(1)
|
81 |
+
else:
|
82 |
+
parser = configparser.ConfigParser(strict=False)
|
83 |
+
parser.add_section('config')
|
84 |
+
print('')
|
85 |
+
print(colorText.BOLD + colorText.GREEN +
|
86 |
+
'[+] Screeni-py User Configuration:' + colorText.END)
|
87 |
+
self.period = input(
|
88 |
+
'[+] Enter number of days for which stock data to be downloaded (Days)(Optimal = 365): ')
|
89 |
+
self.daysToLookback = input(
|
90 |
+
'[+] Number of recent days (TimeFrame) to screen for Breakout/Consolidation (Days)(Optimal = 20): ')
|
91 |
+
self.duration = input(
|
92 |
+
'[+] Enter Duration of each candle (Days)(Optimal = 1): ')
|
93 |
+
self.minLTP = input(
|
94 |
+
'[+] Minimum Price of Stock to Buy (in RS)(Optimal = 20): ')
|
95 |
+
self.maxLTP = input(
|
96 |
+
'[+] Maximum Price of Stock to Buy (in RS)(Optimal = 50000): ')
|
97 |
+
self.volumeRatio = input(
|
98 |
+
'[+] How many times the volume should be more than average for the breakout? (Number)(Optimal = 2.5): ')
|
99 |
+
self.consolidationPercentage = input(
|
100 |
+
'[+] How many % the price should be in range to consider it as consolidation? (Number)(Optimal = 10): ')
|
101 |
+
self.shuffle = str(input(
|
102 |
+
'[+] Shuffle stocks rather than screening alphabetically? (Y/N): ')).lower()
|
103 |
+
self.cacheStockData = str(input(
|
104 |
+
'[+] Enable High-Performance and Data-Saver mode? (This uses little bit more CPU but performs High Performance Screening) (Y/N): ')).lower()
|
105 |
+
self.stageTwoPrompt = str(input(
|
106 |
+
'[+] Screen only for Stage-2 stocks?\n(What are the stages? => https://www.investopedia.com/articles/trading/08/stock-cycle-trend-price.asp)\n(Y/N): ')).lower()
|
107 |
+
self.useEmaPrompt = str(input(
|
108 |
+
'[+] Use EMA instead of SMA? (EMA is good for Short-term & SMA for Mid/Long-term trades)[Y/N]: ')).lower()
|
109 |
+
parser.set('config', 'period', self.period + "d")
|
110 |
+
parser.set('config', 'daysToLookback', self.daysToLookback)
|
111 |
+
parser.set('config', 'duration', self.duration + "d")
|
112 |
+
parser.set('config', 'minPrice', self.minLTP)
|
113 |
+
parser.set('config', 'maxPrice', self.maxLTP)
|
114 |
+
parser.set('config', 'volumeRatio', self.volumeRatio)
|
115 |
+
parser.set('config', 'consolidationPercentage',
|
116 |
+
self.consolidationPercentage)
|
117 |
+
parser.set('config', 'shuffle', self.shuffle)
|
118 |
+
parser.set('config', 'cacheStockData', self.cacheStockData)
|
119 |
+
parser.set('config', 'onlyStageTwoStocks', self.stageTwoPrompt)
|
120 |
+
parser.set('config', 'useEMA', self.useEmaPrompt)
|
121 |
+
|
122 |
+
# delete stock data due to config change
|
123 |
+
self.deleteStockData()
|
124 |
+
print(colorText.BOLD + colorText.FAIL + "[+] Cached Stock Data Deleted." + colorText.END)
|
125 |
+
|
126 |
+
try:
|
127 |
+
fp = open('screenipy.ini', 'w')
|
128 |
+
parser.write(fp)
|
129 |
+
fp.close()
|
130 |
+
print(colorText.BOLD + colorText.GREEN +
|
131 |
+
'[+] User configuration saved.' + colorText.END)
|
132 |
+
print(colorText.BOLD + colorText.GREEN +
|
133 |
+
'[+] Restart the Program to start Screening...' + colorText.END)
|
134 |
+
input('')
|
135 |
+
sys.exit(0)
|
136 |
+
except IOError:
|
137 |
+
print(colorText.BOLD + colorText.FAIL +
|
138 |
+
'[+] Failed to save user config. Exiting..' + colorText.END)
|
139 |
+
input('')
|
140 |
+
sys.exit(1)
|
141 |
+
|
142 |
+
# Load user config from file
|
143 |
+
def getConfig(self, parser):
|
144 |
+
if len(parser.read('screenipy.ini')):
|
145 |
+
try:
|
146 |
+
self.duration = parser.get('config', 'duration')
|
147 |
+
self.period = parser.get('config', 'period')
|
148 |
+
self.minLTP = float(parser.get('config', 'minprice'))
|
149 |
+
self.maxLTP = float(parser.get('config', 'maxprice'))
|
150 |
+
self.volumeRatio = float(parser.get('config', 'volumeRatio'))
|
151 |
+
self.consolidationPercentage = float(
|
152 |
+
parser.get('config', 'consolidationPercentage'))
|
153 |
+
self.daysToLookback = int(
|
154 |
+
parser.get('config', 'daysToLookback'))
|
155 |
+
if 'n' not in str(parser.get('config', 'shuffle')).lower():
|
156 |
+
self.shuffleEnabled = True
|
157 |
+
if 'n' not in str(parser.get('config', 'cachestockdata')).lower():
|
158 |
+
self.cacheEnabled = True
|
159 |
+
if 'n' not in str(parser.get('config', 'onlyStageTwoStocks')).lower():
|
160 |
+
self.stageTwo = True
|
161 |
+
else:
|
162 |
+
self.stageTwo = False
|
163 |
+
if 'y' not in str(parser.get('config', 'useEMA')).lower():
|
164 |
+
self.useEMA = False
|
165 |
+
else:
|
166 |
+
self.useEMA = True
|
167 |
+
except configparser.NoOptionError:
|
168 |
+
input(colorText.BOLD + colorText.FAIL +
|
169 |
+
'[+] Screenipy requires user configuration again. Press enter to continue..' + colorText.END)
|
170 |
+
parser.remove_section('config')
|
171 |
+
self.setConfig(parser, default=False)
|
172 |
+
else:
|
173 |
+
self.setConfig(parser, default=True)
|
174 |
+
|
175 |
+
# Print config file
|
176 |
+
def showConfigFile(self):
|
177 |
+
try:
|
178 |
+
f = open('screenipy.ini', 'r')
|
179 |
+
print(colorText.BOLD + colorText.GREEN +
|
180 |
+
'[+] Screeni-py User Configuration:' + colorText.END)
|
181 |
+
print("\n"+f.read())
|
182 |
+
f.close()
|
183 |
+
input('')
|
184 |
+
except:
|
185 |
+
print(colorText.BOLD + colorText.FAIL +
|
186 |
+
"[+] User Configuration not found!" + colorText.END)
|
187 |
+
print(colorText.BOLD + colorText.WARN +
|
188 |
+
"[+] Configure the limits to continue." + colorText.END)
|
189 |
+
self.setConfig(parser)
|
190 |
+
|
191 |
+
# Check if config file exists
|
192 |
+
def checkConfigFile(self):
|
193 |
+
try:
|
194 |
+
f = open('screenipy.ini','r')
|
195 |
+
f.close()
|
196 |
+
return True
|
197 |
+
except FileNotFoundError:
|
198 |
+
return False
|
199 |
+
|
200 |
+
# Get period as a numeric value
|
201 |
+
def getPeriodNumeric(self):
|
202 |
+
import re
|
203 |
+
pattern = re.compile(r'\d+')
|
204 |
+
result = [int(match.group()) for match in pattern.finditer(self.period)][0]
|
205 |
+
return result
|
206 |
+
|
src/classes/Fetcher.py
ADDED
@@ -0,0 +1,354 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
'''
|
2 |
+
* Project : Screenipy
|
3 |
+
* Author : Pranjal Joshi
|
4 |
+
* Created : 28/04/2021
|
5 |
+
* Description : Class for handling networking for fetching stock codes and data
|
6 |
+
'''
|
7 |
+
|
8 |
+
import sys
|
9 |
+
import urllib.request
|
10 |
+
import csv
|
11 |
+
import requests
|
12 |
+
import random
|
13 |
+
import os
|
14 |
+
import datetime
|
15 |
+
import yfinance as yf
|
16 |
+
import pandas as pd
|
17 |
+
from nsetools import Nse
|
18 |
+
from classes.ColorText import colorText
|
19 |
+
from classes.SuppressOutput import SuppressOutput
|
20 |
+
from classes.Utility import isDocker
|
21 |
+
|
22 |
+
nse = Nse()
|
23 |
+
|
24 |
+
# Exception class if yfinance stock delisted
|
25 |
+
|
26 |
+
|
27 |
+
class StockDataEmptyException(Exception):
|
28 |
+
pass
|
29 |
+
|
30 |
+
# This Class Handles Fetching of Stock Data over the internet
|
31 |
+
|
32 |
+
|
33 |
+
class tools:
|
34 |
+
|
35 |
+
def __init__(self, configManager):
|
36 |
+
self.configManager = configManager
|
37 |
+
pass
|
38 |
+
|
39 |
+
def getAllNiftyIndices(self) -> dict:
|
40 |
+
return {
|
41 |
+
"^NSEI": "NIFTY 50",
|
42 |
+
"^NSMIDCP": "NIFTY NEXT 50",
|
43 |
+
"^CNX100": "NIFTY 100",
|
44 |
+
"^CNX200": "NIFTY 200",
|
45 |
+
"^CNX500": "NIFTY 500",
|
46 |
+
"^NSEMDCP50": "NIFTY MIDCAP 50",
|
47 |
+
"NIFTY_MIDCAP_100.NS": "NIFTY MIDCAP 100",
|
48 |
+
"^CNXSC": "NIFTY SMALLCAP 100",
|
49 |
+
"^INDIAVIX": "INDIA VIX",
|
50 |
+
"NIFTYMIDCAP150.NS": "NIFTY MIDCAP 150",
|
51 |
+
"NIFTYSMLCAP50.NS": "NIFTY SMALLCAP 50",
|
52 |
+
"NIFTYSMLCAP250.NS": "NIFTY SMALLCAP 250",
|
53 |
+
"NIFTYMIDSML400.NS": "NIFTY MIDSMALLCAP 400",
|
54 |
+
"NIFTY500_MULTICAP.NS": "NIFTY500 MULTICAP 50:25:25",
|
55 |
+
"NIFTY_LARGEMID250.NS": "NIFTY LARGEMIDCAP 250",
|
56 |
+
"NIFTY_MID_SELECT.NS": "NIFTY MIDCAP SELECT",
|
57 |
+
"NIFTY_TOTAL_MKT.NS": "NIFTY TOTAL MARKET",
|
58 |
+
"NIFTY_MICROCAP250.NS": "NIFTY MICROCAP 250",
|
59 |
+
"^NSEBANK": "NIFTY BANK",
|
60 |
+
"^CNXAUTO": "NIFTY AUTO",
|
61 |
+
"NIFTY_FIN_SERVICE.NS": "NIFTY FINANCIAL SERVICES",
|
62 |
+
"^CNXFMCG": "NIFTY FMCG",
|
63 |
+
"^CNXIT": "NIFTY IT",
|
64 |
+
"^CNXMEDIA": "NIFTY MEDIA",
|
65 |
+
"^CNXMETAL": "NIFTY METAL",
|
66 |
+
"^CNXPHARMA": "NIFTY PHARMA",
|
67 |
+
"^CNXPSUBANK": "NIFTY PSU BANK",
|
68 |
+
"^CNXREALTY": "NIFTY REALTY",
|
69 |
+
"NIFTY_HEALTHCARE.NS": "NIFTY HEALTHCARE INDEX",
|
70 |
+
"NIFTY_CONSR_DURBL.NS": "NIFTY CONSUMER DURABLES",
|
71 |
+
"NIFTY_OIL_AND_GAS.NS": "NIFTY OIL & GAS",
|
72 |
+
"NIFTYALPHA50.NS": "NIFTY ALPHA 50",
|
73 |
+
"^CNXCMDT": "NIFTY COMMODITIES",
|
74 |
+
"NIFTY_CPSE.NS": "NIFTY CPSE",
|
75 |
+
"^CNXENERGY": "NIFTY ENERGY",
|
76 |
+
"^CNXINFRA": "NIFTY INFRASTRUCTURE",
|
77 |
+
"^CNXMNC": "NIFTY MNC",
|
78 |
+
"^CNXPSE": "NIFTY PSE",
|
79 |
+
"^CNXSERVICE": "NIFTY SERVICES SECTOR",
|
80 |
+
"NIFTY100_ESG.NS": "NIFTY100 ESG SECTOR LEADERS",
|
81 |
+
}
|
82 |
+
|
83 |
+
def _getBacktestDate(self, backtest):
|
84 |
+
try:
|
85 |
+
end = backtest + datetime.timedelta(days=1)
|
86 |
+
if "d" in self.configManager.period:
|
87 |
+
delta = datetime.timedelta(days = self.configManager.getPeriodNumeric())
|
88 |
+
elif "wk" in self.configManager.period:
|
89 |
+
delta = datetime.timedelta(days = self.configManager.getPeriodNumeric() * 7)
|
90 |
+
elif "m" in self.configManager.period:
|
91 |
+
delta = datetime.timedelta(minutes = self.configManager.getPeriodNumeric())
|
92 |
+
elif "h" in self.configManager.period:
|
93 |
+
delta = datetime.timedelta(hours = self.configManager.getPeriodNumeric())
|
94 |
+
start = end - delta
|
95 |
+
return [start, end]
|
96 |
+
except:
|
97 |
+
return [None, None]
|
98 |
+
|
99 |
+
def _getDatesForBacktestReport(self, backtest):
|
100 |
+
dateDict = {}
|
101 |
+
try:
|
102 |
+
today = datetime.date.today()
|
103 |
+
dateDict['T+1d'] = backtest + datetime.timedelta(days=1) if backtest + datetime.timedelta(days=1) < today else None
|
104 |
+
dateDict['T+1wk'] = backtest + datetime.timedelta(weeks=1) if backtest + datetime.timedelta(weeks=1) < today else None
|
105 |
+
dateDict['T+1mo'] = backtest + datetime.timedelta(days=30) if backtest + datetime.timedelta(days=30) < today else None
|
106 |
+
dateDict['T+6mo'] = backtest + datetime.timedelta(days=180) if backtest + datetime.timedelta(days=180) < today else None
|
107 |
+
dateDict['T+1y'] = backtest + datetime.timedelta(days=365) if backtest + datetime.timedelta(days=365) < today else None
|
108 |
+
for key, val in dateDict.copy().items():
|
109 |
+
if val is not None:
|
110 |
+
if val.weekday() == 5: # 5 is Saturday, 6 is Sunday
|
111 |
+
adjusted_date = val + datetime.timedelta(days=2)
|
112 |
+
dateDict[key] = adjusted_date
|
113 |
+
elif val.weekday() == 6:
|
114 |
+
adjusted_date = val + datetime.timedelta(days=1)
|
115 |
+
dateDict[key] = adjusted_date
|
116 |
+
except:
|
117 |
+
pass
|
118 |
+
return dateDict
|
119 |
+
|
120 |
+
def fetchCodes(self, tickerOption,proxyServer=None):
|
121 |
+
listStockCodes = []
|
122 |
+
if tickerOption == 12:
|
123 |
+
url = "https://archives.nseindia.com/content/equities/EQUITY_L.csv"
|
124 |
+
return list(pd.read_csv(url)['SYMBOL'].values)
|
125 |
+
if tickerOption == 15:
|
126 |
+
return ["MMM", "ABT", "ABBV", "ABMD", "ACN", "ATVI", "ADBE", "AMD", "AAP", "AES", "AFL", "A", "APD", "AKAM", "ALK", "ALB", "ARE", "ALXN", "ALGN", "ALLE", "AGN", "ADS", "LNT", "ALL", "GOOGL", "GOOG", "MO", "AMZN", "AMCR", "AEE", "AAL", "AEP", "AXP", "AIG", "AMT", "AWK", "AMP", "ABC", "AME", "AMGN", "APH", "ADI", "ANSS", "ANTM", "AON", "AOS", "APA", "AIV", "AAPL", "AMAT", "APTV", "ADM", "ARNC", "ANET", "AJG", "AIZ", "ATO", "T", "ADSK", "ADP", "AZO", "AVB", "AVY", "BKR", "BLL", "BAC", "BK", "BAX", "BDX", "BRK.B", "BBY", "BIIB", "BLK", "BA", "BKNG", "BWA", "BXP", "BSX", "BMY", "AVGO", "BR", "BF.B", "CHRW", "COG", "CDNS", "CPB", "COF", "CPRI", "CAH", "KMX", "CCL", "CAT", "CBOE", "CBRE", "CDW", "CE", "CNC", "CNP", "CTL", "CERN", "CF", "SCHW", "CHTR", "CVX", "CMG", "CB", "CHD", "CI", "XEC", "CINF", "CTAS", "CSCO", "C", "CFG", "CTXS", "CLX", "CME", "CMS", "KO", "CTSH", "CL", "CMCSA", "CMA", "CAG", "CXO", "COP", "ED", "STZ", "COO", "CPRT", "GLW", "CTVA", "COST", "COTY", "CCI", "CSX", "CMI", "CVS", "DHI", "DHR", "DRI", "DVA", "DE", "DAL", "XRAY", "DVN", "FANG", "DLR", "DFS", "DISCA", "DISCK", "DISH", "DG", "DLTR", "D", "DOV", "DOW", "DTE", "DUK", "DRE", "DD", "DXC", "ETFC", "EMN", "ETN", "EBAY", "ECL", "EIX", "EW", "EA", "EMR", "ETR", "EOG", "EFX", "EQIX", "EQR", "ESS", "EL", "EVRG", "ES", "RE", "EXC", "EXPE", "EXPD", "EXR", "XOM", "FFIV", "FB", "FAST", "FRT", "FDX", "FIS", "FITB", "FE", "FRC", "FISV", "FLT", "FLIR", "FLS", "FMC", "F", "FTNT", "FTV", "FBHS", "FOXA", "FOX", "BEN", "FCX", "GPS", "GRMN", "IT", "GD", "GE", "GIS", "GM", "GPC", "GILD", "GL", "GPN", "GS", "GWW", "HRB", "HAL", "HBI", "HOG", "HIG", "HAS", "HCA", "PEAK", "HP", "HSIC", "HSY", "HES", "HPE", "HLT", "HFC", "HOLX", "HD", "HON", "HRL", "HST", "HPQ", "HUM", "HBAN", "HII", "IEX", "IDXX", "INFO", "ITW", "ILMN", "IR", "INTC", "ICE", "IBM", "INCY", "IP", "IPG", "IFF", "INTU", "ISRG", "IVZ", "IPGP", "IQV", "IRM", "JKHY", "J", "JBHT", "SJM", "JNJ", "JCI", "JPM", "JNPR", "KSU", "K", "KEY", "KEYS", "KMB", "KIM", "KMI", "KLAC", "KSS", "KHC", "KR", "LB", "LHX", "LH", "LRCX", "LW", "LVS", "LEG", "LDOS", "LEN", "LLY", "LNC", "LIN", "LYV", "LKQ", "LMT", "L", "LOW", "LYB", "MTB", "M", "MRO", "MPC", "MKTX", "MAR", "MMC", "MLM", "MAS", "MA", "MKC", "MXIM", "MCD", "MCK", "MDT", "MRK", "MET", "MTD", "MGM", "MCHP", "MU", "MSFT", "MAA", "MHK", "TAP", "MDLZ", "MNST", "MCO", "MS", "MOS", "MSI", "MSCI", "MYL", "NDAQ", "NOV", "NTAP", "NFLX", "NWL", "NEM", "NWSA", "NWS", "NEE", "NLSN", "NKE", "NI", "NBL", "JWN", "NSC", "NTRS", "NOC", "NLOK", "NCLH", "NRG", "NUE", "NVDA", "NVR", "ORLY", "OXY", "ODFL", "OMC", "OKE", "ORCL", "PCAR", "PKG", "PH", "PAYX", "PYPL", "PNR", "PBCT", "PEP", "PKI", "PRGO", "PFE", "PM", "PSX", "PNW", "PXD", "PNC", "PPG", "PPL", "PFG", "PG", "PGR", "PLD", "PRU", "PEG", "PSA", "PHM", "PVH", "QRVO", "PWR", "QCOM", "DGX", "RL", "RJF", "RTN", "O", "REG", "REGN", "RF", "RSG", "RMD", "RHI", "ROK", "ROL", "ROP", "ROST", "RCL", "SPGI", "CRM", "SBAC", "SLB", "STX", "SEE", "SRE", "NOW", "SHW", "SPG", "SWKS", "SLG", "SNA", "SO", "LUV", "SWK", "SBUX", "STT", "STE", "SYK", "SIVB", "SYF", "SNPS", "SYY", "TMUS", "TROW", "TTWO", "TPR", "TGT", "TEL", "FTI", "TFX", "TXN", "TXT", "TMO", "TIF", "TJX", "TSCO", "TDG", "TRV", "TFC", "TWTR", "TSN", "UDR", "ULTA", "USB", "UAA", "UA", "UNP", "UAL", "UNH", "UPS", "URI", "UTX", "UHS", "UNM", "VFC", "VLO", "VAR", "VTR", "VRSN", "VRSK", "VZ", "VRTX", "VIAC", "V", "VNO", "VMC", "WRB", "WAB", "WMT", "WBA", "DIS", "WM", "WAT", "WEC", "WCG", "WFC", "WELL", "WDC", "WU", "WRK", "WY", "WHR", "WMB", "WLTW", "WYNN", "XEL", "XRX", "XLNX", "XYL", "YUM", "ZBRA", "ZBH", "ZION", "ZTS"]
|
127 |
+
if tickerOption == 16:
|
128 |
+
return self.getAllNiftyIndices()
|
129 |
+
tickerMapping = {
|
130 |
+
1: "https://archives.nseindia.com/content/indices/ind_nifty50list.csv",
|
131 |
+
2: "https://archives.nseindia.com/content/indices/ind_niftynext50list.csv",
|
132 |
+
3: "https://archives.nseindia.com/content/indices/ind_nifty100list.csv",
|
133 |
+
4: "https://archives.nseindia.com/content/indices/ind_nifty200list.csv",
|
134 |
+
5: "https://archives.nseindia.com/content/indices/ind_nifty500list.csv",
|
135 |
+
6: "https://archives.nseindia.com/content/indices/ind_niftysmallcap50list.csv",
|
136 |
+
7: "https://archives.nseindia.com/content/indices/ind_niftysmallcap100list.csv",
|
137 |
+
8: "https://archives.nseindia.com/content/indices/ind_niftysmallcap250list.csv",
|
138 |
+
9: "https://archives.nseindia.com/content/indices/ind_niftymidcap50list.csv",
|
139 |
+
10: "https://archives.nseindia.com/content/indices/ind_niftymidcap100list.csv",
|
140 |
+
11: "https://archives.nseindia.com/content/indices/ind_niftymidcap150list.csv",
|
141 |
+
14: "https://archives.nseindia.com/content/fo/fo_mktlots.csv"
|
142 |
+
}
|
143 |
+
|
144 |
+
url = tickerMapping.get(tickerOption)
|
145 |
+
|
146 |
+
try:
|
147 |
+
if proxyServer:
|
148 |
+
res = requests.get(url,proxies={'https':proxyServer})
|
149 |
+
else:
|
150 |
+
res = requests.get(url)
|
151 |
+
|
152 |
+
cr = csv.reader(res.text.strip().split('\n'))
|
153 |
+
|
154 |
+
if tickerOption == 14:
|
155 |
+
for i in range(5):
|
156 |
+
next(cr) # skipping first line
|
157 |
+
for row in cr:
|
158 |
+
listStockCodes.append(row[1].strip())
|
159 |
+
else:
|
160 |
+
next(cr) # skipping first line
|
161 |
+
for row in cr:
|
162 |
+
listStockCodes.append(row[2])
|
163 |
+
except Exception as error:
|
164 |
+
print(error)
|
165 |
+
|
166 |
+
return listStockCodes
|
167 |
+
|
168 |
+
# Fetch all stock codes from NSE
|
169 |
+
def fetchStockCodes(self, tickerOption, proxyServer=None):
|
170 |
+
listStockCodes = []
|
171 |
+
if tickerOption == 0:
|
172 |
+
stockCode = None
|
173 |
+
while stockCode == None or stockCode == "":
|
174 |
+
stockCode = str(input(colorText.BOLD + colorText.BLUE +
|
175 |
+
"[+] Enter Stock Code(s) for screening (Multiple codes should be seperated by ,): ")).upper()
|
176 |
+
stockCode = stockCode.replace(" ", "")
|
177 |
+
listStockCodes = stockCode.split(',')
|
178 |
+
else:
|
179 |
+
print(colorText.BOLD +
|
180 |
+
"[+] Getting Stock Codes From NSE... ", end='')
|
181 |
+
listStockCodes = self.fetchCodes(tickerOption,proxyServer=proxyServer)
|
182 |
+
if type(listStockCodes) == dict:
|
183 |
+
listStockCodes = list(listStockCodes.keys())
|
184 |
+
if len(listStockCodes) > 10:
|
185 |
+
print(colorText.GREEN + ("=> Done! Fetched %d stock codes." %
|
186 |
+
len(listStockCodes)) + colorText.END)
|
187 |
+
if self.configManager.shuffleEnabled:
|
188 |
+
random.shuffle(listStockCodes)
|
189 |
+
print(colorText.BLUE +
|
190 |
+
"[+] Stock shuffling is active." + colorText.END)
|
191 |
+
else:
|
192 |
+
print(colorText.FAIL +
|
193 |
+
"[+] Stock shuffling is inactive." + colorText.END)
|
194 |
+
if self.configManager.stageTwo:
|
195 |
+
print(
|
196 |
+
colorText.BLUE + "[+] Screening only for the stocks in Stage-2! Edit User Config to change this." + colorText.END)
|
197 |
+
else:
|
198 |
+
print(
|
199 |
+
colorText.FAIL + "[+] Screening only for the stocks in all Stages! Edit User Config to change this." + colorText.END)
|
200 |
+
|
201 |
+
else:
|
202 |
+
input(
|
203 |
+
colorText.FAIL + "=> Error getting stock codes from NSE! Press any key to exit!" + colorText.END)
|
204 |
+
sys.exit("Exiting script..")
|
205 |
+
|
206 |
+
return listStockCodes
|
207 |
+
|
208 |
+
# Fetch stock price data from Yahoo finance
|
209 |
+
def fetchStockData(self, stockCode, period, duration, proxyServer, screenResultsCounter, screenCounter, totalSymbols, backtestDate=None, printCounter=False, tickerOption=None):
|
210 |
+
dateDict = None
|
211 |
+
with SuppressOutput(suppress_stdout=True, suppress_stderr=True):
|
212 |
+
append_exchange = ".NS"
|
213 |
+
if tickerOption == 15 or tickerOption == 16:
|
214 |
+
append_exchange = ""
|
215 |
+
data = yf.download(
|
216 |
+
tickers=stockCode + append_exchange,
|
217 |
+
period=period,
|
218 |
+
interval=duration,
|
219 |
+
proxy=proxyServer,
|
220 |
+
progress=False,
|
221 |
+
timeout=10,
|
222 |
+
start=self._getBacktestDate(backtest=backtestDate)[0],
|
223 |
+
end=self._getBacktestDate(backtest=backtestDate)[1]
|
224 |
+
)
|
225 |
+
if backtestDate != datetime.date.today():
|
226 |
+
dateDict = self._getDatesForBacktestReport(backtest=backtestDate)
|
227 |
+
backtestData = yf.download(
|
228 |
+
tickers=stockCode + append_exchange,
|
229 |
+
interval='1d',
|
230 |
+
proxy=proxyServer,
|
231 |
+
progress=False,
|
232 |
+
timeout=10,
|
233 |
+
start=backtestDate - datetime.timedelta(days=1),
|
234 |
+
end=backtestDate + datetime.timedelta(days=370)
|
235 |
+
)
|
236 |
+
for key, value in dateDict.copy().items():
|
237 |
+
if value is not None:
|
238 |
+
try:
|
239 |
+
dateDict[key] = backtestData.loc[pd.Timestamp(value)]['Close']
|
240 |
+
except KeyError:
|
241 |
+
continue
|
242 |
+
dateDict['T+52wkH'] = backtestData['High'].max()
|
243 |
+
dateDict['T+52wkL'] = backtestData['Low'].min()
|
244 |
+
if printCounter:
|
245 |
+
sys.stdout.write("\r\033[K")
|
246 |
+
try:
|
247 |
+
print(colorText.BOLD + colorText.GREEN + ("[%d%%] Screened %d, Found %d. Fetching data & Analyzing %s..." % (
|
248 |
+
int((screenCounter.value/totalSymbols)*100), screenCounter.value, screenResultsCounter.value, stockCode)) + colorText.END, end='')
|
249 |
+
except ZeroDivisionError:
|
250 |
+
pass
|
251 |
+
if len(data) == 0:
|
252 |
+
print(colorText.BOLD + colorText.FAIL +
|
253 |
+
"=> Failed to fetch!" + colorText.END, end='\r', flush=True)
|
254 |
+
raise StockDataEmptyException
|
255 |
+
return None
|
256 |
+
print(colorText.BOLD + colorText.GREEN + "=> Done!" +
|
257 |
+
colorText.END, end='\r', flush=True)
|
258 |
+
return data, dateDict
|
259 |
+
|
260 |
+
# Get Daily Nifty 50 Index:
|
261 |
+
def fetchLatestNiftyDaily(self, proxyServer=None):
|
262 |
+
data = yf.download(
|
263 |
+
tickers="^NSEI",
|
264 |
+
period='5d',
|
265 |
+
interval='1d',
|
266 |
+
proxy=proxyServer,
|
267 |
+
progress=False,
|
268 |
+
timeout=10
|
269 |
+
)
|
270 |
+
gold = yf.download(
|
271 |
+
tickers="GC=F",
|
272 |
+
period='5d',
|
273 |
+
interval='1d',
|
274 |
+
proxy=proxyServer,
|
275 |
+
progress=False,
|
276 |
+
timeout=10
|
277 |
+
).add_prefix(prefix='gold_')
|
278 |
+
crude = yf.download(
|
279 |
+
tickers="CL=F",
|
280 |
+
period='5d',
|
281 |
+
interval='1d',
|
282 |
+
proxy=proxyServer,
|
283 |
+
progress=False,
|
284 |
+
timeout=10
|
285 |
+
).add_prefix(prefix='crude_')
|
286 |
+
data = pd.concat([data, gold, crude], axis=1)
|
287 |
+
return data
|
288 |
+
|
289 |
+
# Get Data for Five EMA strategy
|
290 |
+
def fetchFiveEmaData(self, proxyServer=None):
|
291 |
+
nifty_sell = yf.download(
|
292 |
+
tickers="^NSEI",
|
293 |
+
period='5d',
|
294 |
+
interval='5m',
|
295 |
+
proxy=proxyServer,
|
296 |
+
progress=False,
|
297 |
+
timeout=10
|
298 |
+
)
|
299 |
+
banknifty_sell = yf.download(
|
300 |
+
tickers="^NSEBANK",
|
301 |
+
period='5d',
|
302 |
+
interval='5m',
|
303 |
+
proxy=proxyServer,
|
304 |
+
progress=False,
|
305 |
+
timeout=10
|
306 |
+
)
|
307 |
+
nifty_buy = yf.download(
|
308 |
+
tickers="^NSEI",
|
309 |
+
period='5d',
|
310 |
+
interval='15m',
|
311 |
+
proxy=proxyServer,
|
312 |
+
progress=False,
|
313 |
+
timeout=10
|
314 |
+
)
|
315 |
+
banknifty_buy = yf.download(
|
316 |
+
tickers="^NSEBANK",
|
317 |
+
period='5d',
|
318 |
+
interval='15m',
|
319 |
+
proxy=proxyServer,
|
320 |
+
progress=False,
|
321 |
+
timeout=10
|
322 |
+
)
|
323 |
+
return nifty_buy, banknifty_buy, nifty_sell, banknifty_sell
|
324 |
+
|
325 |
+
# Load stockCodes from the watchlist.xlsx
|
326 |
+
def fetchWatchlist(self):
|
327 |
+
createTemplate = False
|
328 |
+
data = pd.DataFrame()
|
329 |
+
try:
|
330 |
+
data = pd.read_excel('watchlist.xlsx')
|
331 |
+
except FileNotFoundError:
|
332 |
+
print(colorText.BOLD + colorText.FAIL +
|
333 |
+
f'[+] watchlist.xlsx not found in f{os.getcwd()}' + colorText.END)
|
334 |
+
createTemplate = True
|
335 |
+
try:
|
336 |
+
if not createTemplate:
|
337 |
+
data = data['Stock Code'].values.tolist()
|
338 |
+
except KeyError:
|
339 |
+
print(colorText.BOLD + colorText.FAIL +
|
340 |
+
'[+] Bad Watchlist Format: First Column (A1) should have Header named "Stock Code"' + colorText.END)
|
341 |
+
createTemplate = True
|
342 |
+
if createTemplate:
|
343 |
+
if isDocker():
|
344 |
+
print(colorText.BOLD + colorText.FAIL +
|
345 |
+
f'[+] This feature is not available with dockerized application. Try downloading .exe/.bin file to use this!' + colorText.END)
|
346 |
+
return None
|
347 |
+
sample = {'Stock Code': ['SBIN', 'INFY', 'TATAMOTORS', 'ITC']}
|
348 |
+
sample_data = pd.DataFrame(sample, columns=['Stock Code'])
|
349 |
+
sample_data.to_excel('watchlist_template.xlsx',
|
350 |
+
index=False, header=True)
|
351 |
+
print(colorText.BOLD + colorText.BLUE +
|
352 |
+
f'[+] watchlist_template.xlsx created in {os.getcwd()} as a referance template.' + colorText.END)
|
353 |
+
return None
|
354 |
+
return data
|
src/classes/OtaUpdater.py
ADDED
@@ -0,0 +1,148 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
'''
|
2 |
+
* Project : Screenipy
|
3 |
+
* Author : Pranjal Joshi
|
4 |
+
* Created : 21/04/2021
|
5 |
+
* Description : Class for handling OTA updates
|
6 |
+
'''
|
7 |
+
|
8 |
+
from classes.ColorText import colorText
|
9 |
+
from classes.Utility import isDocker, isGui
|
10 |
+
import requests
|
11 |
+
import os
|
12 |
+
import platform
|
13 |
+
import sys
|
14 |
+
import subprocess
|
15 |
+
import requests
|
16 |
+
|
17 |
+
class OTAUpdater:
|
18 |
+
|
19 |
+
developmentVersion = 'd'
|
20 |
+
|
21 |
+
# Download and replace exe through other process for Windows
|
22 |
+
def updateForWindows(url):
|
23 |
+
batFile = """@echo off
|
24 |
+
color a
|
25 |
+
echo [+] Screenipy Software Updater!
|
26 |
+
echo [+] Downloading Software Update...
|
27 |
+
echo [+] This may take some time as per your Internet Speed, Please Wait...
|
28 |
+
curl -o screenipy.exe -L """ + url + """
|
29 |
+
echo [+] Newly downloaded file saved in %cd%
|
30 |
+
echo [+] Software Update Completed! Run'screenipy.exe' again as usual to continue..
|
31 |
+
pause
|
32 |
+
del updater.bat & exit
|
33 |
+
"""
|
34 |
+
f = open("updater.bat",'w')
|
35 |
+
f.write(batFile)
|
36 |
+
f.close()
|
37 |
+
subprocess.Popen('start updater.bat', shell=True)
|
38 |
+
sys.exit(0)
|
39 |
+
|
40 |
+
# Download and replace bin through other process for Linux
|
41 |
+
def updateForLinux(url):
|
42 |
+
bashFile = """#!/bin/bash
|
43 |
+
echo ""
|
44 |
+
echo "[+] Starting Screeni-py updater, Please Wait..."
|
45 |
+
sleep 3
|
46 |
+
echo "[+] Screenipy Software Updater!"
|
47 |
+
echo "[+] Downloading Software Update..."
|
48 |
+
echo "[+] This may take some time as per your Internet Speed, Please Wait..."
|
49 |
+
wget -q """ + url + """ -O screenipy.bin
|
50 |
+
echo "[+] Newly downloaded file saved in $(pwd)"
|
51 |
+
chmod +x screenipy.bin
|
52 |
+
echo "[+] Update Completed! Run 'screenipy.bin' again as usual to continue.."
|
53 |
+
rm updater.sh
|
54 |
+
"""
|
55 |
+
f = open("updater.sh",'w')
|
56 |
+
f.write(bashFile)
|
57 |
+
f.close()
|
58 |
+
subprocess.Popen('bash updater.sh', shell=True)
|
59 |
+
sys.exit(0)
|
60 |
+
|
61 |
+
# Download and replace run through other process for Mac
|
62 |
+
def updateForMac(url):
|
63 |
+
bashFile = """#!/bin/bash
|
64 |
+
echo ""
|
65 |
+
echo "[+] Starting Screeni-py updater, Please Wait..."
|
66 |
+
sleep 3
|
67 |
+
echo "[+] Screenipy Software Updater!"
|
68 |
+
echo "[+] Downloading Software Update..."
|
69 |
+
echo "[+] This may take some time as per your Internet Speed, Please Wait..."
|
70 |
+
curl -o screenipy.run -L """ + url + """
|
71 |
+
echo "[+] Newly downloaded file saved in $(pwd)"
|
72 |
+
chmod +x screenipy.run
|
73 |
+
echo "[+] Update Completed! Run 'screenipy.run' again as usual to continue.."
|
74 |
+
rm updater.sh
|
75 |
+
"""
|
76 |
+
f = open("updater.sh",'w')
|
77 |
+
f.write(bashFile)
|
78 |
+
f.close()
|
79 |
+
subprocess.Popen('bash updater.sh', shell=True)
|
80 |
+
sys.exit(0)
|
81 |
+
|
82 |
+
# Parse changelog from release.md
|
83 |
+
def showWhatsNew():
|
84 |
+
url = "https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/main/src/release.md"
|
85 |
+
md = requests.get(url)
|
86 |
+
txt = md.text
|
87 |
+
txt = txt.split("New?")[1]
|
88 |
+
# txt = txt.split("## Downloads")[0]
|
89 |
+
txt = txt.split("## Installation Guide")[0]
|
90 |
+
txt = txt.replace('**','').replace('`','').strip()
|
91 |
+
return (txt+"\n")
|
92 |
+
|
93 |
+
# Check for update and download if available
|
94 |
+
def checkForUpdate(proxyServer, VERSION="1.0"):
|
95 |
+
OTAUpdater.checkForUpdate.url = None
|
96 |
+
guiUpdateMessage = ""
|
97 |
+
try:
|
98 |
+
resp = None
|
99 |
+
now = float(VERSION)
|
100 |
+
if proxyServer:
|
101 |
+
resp = requests.get("https://api.github.com/repos/pranjal-joshi/Screeni-py/releases/latest",proxies={'https':proxyServer})
|
102 |
+
else:
|
103 |
+
resp = requests.get("https://api.github.com/repos/pranjal-joshi/Screeni-py/releases/latest")
|
104 |
+
# Disabling Exe check as Executables are deprecated v2.03 onwards
|
105 |
+
'''
|
106 |
+
if 'Windows' in platform.system():
|
107 |
+
OTAUpdater.checkForUpdate.url = resp.json()['assets'][1]['browser_download_url']
|
108 |
+
size = int(resp.json()['assets'][1]['size']/(1024*1024))
|
109 |
+
elif 'Darwin' in platform.system():
|
110 |
+
OTAUpdater.checkForUpdate.url = resp.json()['assets'][2]['browser_download_url']
|
111 |
+
size = int(resp.json()['assets'][2]['size']/(1024*1024))
|
112 |
+
else:
|
113 |
+
OTAUpdater.checkForUpdate.url = resp.json()['assets'][0]['browser_download_url']
|
114 |
+
size = int(resp.json()['assets'][0]['size']/(1024*1024))
|
115 |
+
# if(float(resp.json()['tag_name']) > now):
|
116 |
+
if(float(resp.json()['tag_name']) > now and not isDocker()): # OTA not applicable if we're running in docker!
|
117 |
+
print(colorText.BOLD + colorText.WARN + "[+] What's New in this Update?\n" + OTAUpdater.showWhatsNew() + colorText.END)
|
118 |
+
action = str(input(colorText.BOLD + colorText.WARN + ('\n[+] New Software update (v%s) available. Download Now (Size: %dMB)? [Y/N]: ' % (str(resp.json()['tag_name']),size)))).lower()
|
119 |
+
if(action == 'y'):
|
120 |
+
try:
|
121 |
+
if 'Windows' in platform.system():
|
122 |
+
OTAUpdater.updateForWindows(OTAUpdater.checkForUpdate.url)
|
123 |
+
elif 'Darwin' in platform.system():
|
124 |
+
OTAUpdater.updateForMac(OTAUpdater.checkForUpdate.url)
|
125 |
+
else:
|
126 |
+
OTAUpdater.updateForLinux(OTAUpdater.checkForUpdate.url)
|
127 |
+
except Exception as e:
|
128 |
+
print(colorText.BOLD + colorText.WARN + '[+] Error occured while updating!' + colorText.END)
|
129 |
+
raise(e)
|
130 |
+
'''
|
131 |
+
if(float(resp.json()['tag_name']) > now and not isDocker()):
|
132 |
+
print(colorText.BOLD + colorText.FAIL + "[+] Executables are now DEPRECATED!\nFollow instructions given at https://github.com/pranjal-joshi/Screeni-py to switch to Docker.\n" + colorText.END)
|
133 |
+
elif(float(resp.json()['tag_name']) > now and isDocker()): # OTA not applicable if we're running in docker!
|
134 |
+
print(colorText.BOLD + colorText.FAIL + ('\n[+] New Software update (v%s) available.\n[+] Run `docker pull joshipranjal/screeni-py:latest` to update your docker to the latest version!\n' % (str(resp.json()['tag_name']))) + colorText.END)
|
135 |
+
print(colorText.BOLD + colorText.WARN + "[+] What's New in this Update?\n" + OTAUpdater.showWhatsNew() + colorText.END)
|
136 |
+
if isGui():
|
137 |
+
guiUpdateMessage = f"New Software update (v{resp.json()['tag_name']}) available - Watch this [**YouTube Video**](https://youtu.be/T41m13iMyJc) for additional help or Update by running following command:\n\n**`docker pull joshipranjal/screeni-py:latest`**"
|
138 |
+
elif(float(resp.json()['tag_name']) < now):
|
139 |
+
print(colorText.BOLD + colorText.FAIL + ('[+] This version (v%s) is in Development mode and unreleased!' % VERSION) + colorText.END)
|
140 |
+
if isGui():
|
141 |
+
guiUpdateMessage = f"This version (v{VERSION}) is in Development mode and unreleased!"
|
142 |
+
return OTAUpdater.developmentVersion, guiUpdateMessage
|
143 |
+
except Exception as e:
|
144 |
+
print(colorText.BOLD + colorText.FAIL + "[+] Failure while checking update!" + colorText.END)
|
145 |
+
print(e)
|
146 |
+
if OTAUpdater.checkForUpdate.url != None:
|
147 |
+
print(colorText.BOLD + colorText.BLUE + ("[+] Download update manually from %s\n" % OTAUpdater.checkForUpdate.url) + colorText.END)
|
148 |
+
return None, guiUpdateMessage
|
src/classes/ParallelProcessing.py
ADDED
@@ -0,0 +1,313 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
'''
|
3 |
+
* Project : Screenipy
|
4 |
+
* Author : Pranjal Joshi, Swar Patel
|
5 |
+
* Created : 18/05/2021
|
6 |
+
* Description : Class for managing multiprocessing
|
7 |
+
'''
|
8 |
+
|
9 |
+
import multiprocessing
|
10 |
+
import pandas as pd
|
11 |
+
import numpy as np
|
12 |
+
import sys
|
13 |
+
import os
|
14 |
+
import pytz
|
15 |
+
import traceback
|
16 |
+
from queue import Empty
|
17 |
+
from datetime import datetime
|
18 |
+
import classes.Fetcher as Fetcher
|
19 |
+
import classes.Screener as Screener
|
20 |
+
import classes.Utility as Utility
|
21 |
+
from copy import deepcopy
|
22 |
+
from classes.CandlePatterns import CandlePatterns
|
23 |
+
from classes.ColorText import colorText
|
24 |
+
from classes.SuppressOutput import SuppressOutput
|
25 |
+
|
26 |
+
if sys.platform.startswith('win'):
|
27 |
+
import multiprocessing.popen_spawn_win32 as forking
|
28 |
+
else:
|
29 |
+
import multiprocessing.popen_fork as forking
|
30 |
+
|
31 |
+
|
32 |
+
class StockConsumer(multiprocessing.Process):
|
33 |
+
|
34 |
+
def __init__(self, task_queue, result_queue, screenCounter, screenResultsCounter, stockDict, proxyServer, keyboardInterruptEvent):
|
35 |
+
multiprocessing.Process.__init__(self)
|
36 |
+
self.multiprocessingForWindows()
|
37 |
+
self.task_queue = task_queue
|
38 |
+
self.result_queue = result_queue
|
39 |
+
self.screenCounter = screenCounter
|
40 |
+
self.screenResultsCounter = screenResultsCounter
|
41 |
+
self.stockDict = stockDict
|
42 |
+
self.proxyServer = proxyServer
|
43 |
+
self.keyboardInterruptEvent = keyboardInterruptEvent
|
44 |
+
self.isTradingTime = Utility.tools.isTradingTime()
|
45 |
+
|
46 |
+
def run(self):
|
47 |
+
# while True:
|
48 |
+
try:
|
49 |
+
while not self.keyboardInterruptEvent.is_set():
|
50 |
+
try:
|
51 |
+
next_task = self.task_queue.get()
|
52 |
+
except Empty:
|
53 |
+
continue
|
54 |
+
if next_task is None:
|
55 |
+
self.task_queue.task_done()
|
56 |
+
break
|
57 |
+
answer = self.screenStocks(*(next_task))
|
58 |
+
self.task_queue.task_done()
|
59 |
+
self.result_queue.put(answer)
|
60 |
+
except Exception as e:
|
61 |
+
sys.exit(0)
|
62 |
+
|
63 |
+
def screenStocks(self, tickerOption, executeOption, reversalOption, maLength, daysForLowestVolume, minRSI, maxRSI, respChartPattern, insideBarToLookback, totalSymbols,
|
64 |
+
configManager, fetcher, screener:Screener.tools, candlePatterns, stock, newlyListedOnly, downloadOnly, vectorSearch, isDevVersion, backtestDate, printCounter=False):
|
65 |
+
screenResults = pd.DataFrame(columns=[
|
66 |
+
'Stock', 'Consolidating', 'Breaking-Out', 'MA-Signal', 'Volume', 'LTP', 'RSI', 'Trend', 'Pattern'])
|
67 |
+
screeningDictionary = {'Stock': "", 'Consolidating': "", 'Breaking-Out': "",
|
68 |
+
'MA-Signal': "", 'Volume': "", 'LTP': 0, 'RSI': 0, 'Trend': "", 'Pattern': ""}
|
69 |
+
saveDictionary = {'Stock': "", 'Consolidating': "", 'Breaking-Out': "",
|
70 |
+
'MA-Signal': "", 'Volume': "", 'LTP': 0, 'RSI': 0, 'Trend': "", 'Pattern': ""}
|
71 |
+
|
72 |
+
try:
|
73 |
+
period = configManager.period
|
74 |
+
|
75 |
+
# Data download adjustment for Newly Listed only feature
|
76 |
+
if newlyListedOnly:
|
77 |
+
if int(configManager.period[:-1]) > 250:
|
78 |
+
period = '250d'
|
79 |
+
else:
|
80 |
+
period = configManager.period
|
81 |
+
|
82 |
+
if (self.stockDict.get(stock) is None) or (configManager.cacheEnabled is False) or self.isTradingTime or downloadOnly:
|
83 |
+
try:
|
84 |
+
data, backtestReport = fetcher.fetchStockData(stock,
|
85 |
+
period,
|
86 |
+
configManager.duration,
|
87 |
+
self.proxyServer,
|
88 |
+
self.screenResultsCounter,
|
89 |
+
self.screenCounter,
|
90 |
+
totalSymbols,
|
91 |
+
backtestDate=backtestDate,
|
92 |
+
tickerOption=tickerOption)
|
93 |
+
except Exception as e:
|
94 |
+
return screeningDictionary, saveDictionary
|
95 |
+
if configManager.cacheEnabled is True and not self.isTradingTime and (self.stockDict.get(stock) is None) or downloadOnly:
|
96 |
+
self.stockDict[stock] = data.to_dict('split')
|
97 |
+
if downloadOnly:
|
98 |
+
raise Screener.DownloadDataOnly
|
99 |
+
else:
|
100 |
+
if printCounter:
|
101 |
+
try:
|
102 |
+
print(colorText.BOLD + colorText.GREEN + ("[%d%%] Screened %d, Found %d. Fetching data & Analyzing %s..." % (
|
103 |
+
int((self.screenCounter.value / totalSymbols) * 100), self.screenCounter.value, self.screenResultsCounter.value, stock)) + colorText.END, end='')
|
104 |
+
print(colorText.BOLD + colorText.GREEN + "=> Done!" +
|
105 |
+
colorText.END, end='\r', flush=True)
|
106 |
+
except ZeroDivisionError:
|
107 |
+
pass
|
108 |
+
sys.stdout.write("\r\033[K")
|
109 |
+
data = self.stockDict.get(stock)
|
110 |
+
data = pd.DataFrame(
|
111 |
+
data['data'], columns=data['columns'], index=data['index'])
|
112 |
+
|
113 |
+
fullData, processedData = screener.preprocessData(
|
114 |
+
data, daysToLookback=configManager.daysToLookback)
|
115 |
+
|
116 |
+
if type(vectorSearch) != bool and type(vectorSearch) and vectorSearch[2] == True:
|
117 |
+
executeOption = 0
|
118 |
+
with self.screenCounter.get_lock():
|
119 |
+
screener.addVector(fullData, stock, vectorSearch[1])
|
120 |
+
|
121 |
+
if newlyListedOnly:
|
122 |
+
if not screener.validateNewlyListed(fullData, period):
|
123 |
+
raise Screener.NotNewlyListed
|
124 |
+
|
125 |
+
with self.screenCounter.get_lock():
|
126 |
+
self.screenCounter.value += 1
|
127 |
+
if not processedData.empty:
|
128 |
+
urlStock = None
|
129 |
+
if tickerOption == 16:
|
130 |
+
urlStock = deepcopy(stock).replace('^','').replace('.NS','')
|
131 |
+
stock = fetcher.getAllNiftyIndices()[stock]
|
132 |
+
stock = stock.replace('^','').replace('.NS','')
|
133 |
+
urlStock = stock.replace('&','_') if urlStock is None else urlStock.replace('&','_')
|
134 |
+
screeningDictionary['Stock'] = colorText.BOLD + \
|
135 |
+
colorText.BLUE + f'\x1B]8;;https://in.tradingview.com/chart?symbol=NSE%3A{urlStock}\x1B\\{stock}\x1B]8;;\x1B\\' + colorText.END if tickerOption < 15 \
|
136 |
+
else colorText.BOLD + \
|
137 |
+
colorText.BLUE + f'\x1B]8;;https://in.tradingview.com/chart?symbol={urlStock}\x1B\\{stock}\x1B]8;;\x1B\\' + colorText.END
|
138 |
+
saveDictionary['Stock'] = stock
|
139 |
+
|
140 |
+
consolidationValue = screener.validateConsolidation(
|
141 |
+
processedData, screeningDictionary, saveDictionary, percentage=configManager.consolidationPercentage)
|
142 |
+
isMaReversal = screener.validateMovingAverages(
|
143 |
+
processedData, screeningDictionary, saveDictionary, maRange=1.25)
|
144 |
+
isVolumeHigh = screener.validateVolume(
|
145 |
+
processedData, screeningDictionary, saveDictionary, volumeRatio=configManager.volumeRatio)
|
146 |
+
isBreaking = screener.findBreakout(
|
147 |
+
processedData, screeningDictionary, saveDictionary, daysToLookback=configManager.daysToLookback)
|
148 |
+
isLtpValid = screener.validateLTP(
|
149 |
+
fullData, screeningDictionary, saveDictionary, minLTP=configManager.minLTP, maxLTP=configManager.maxLTP)
|
150 |
+
if executeOption == 4:
|
151 |
+
isLowestVolume = screener.validateLowestVolume(processedData, daysForLowestVolume)
|
152 |
+
else:
|
153 |
+
isLowestVolume = False
|
154 |
+
isValidRsi = screener.validateRSI(
|
155 |
+
processedData, screeningDictionary, saveDictionary, minRSI, maxRSI)
|
156 |
+
try:
|
157 |
+
with SuppressOutput(suppress_stderr=True, suppress_stdout=True):
|
158 |
+
currentTrend = screener.findTrend(
|
159 |
+
processedData,
|
160 |
+
screeningDictionary,
|
161 |
+
saveDictionary,
|
162 |
+
daysToLookback=configManager.daysToLookback,
|
163 |
+
stockName=stock)
|
164 |
+
except np.RankWarning:
|
165 |
+
screeningDictionary['Trend'] = 'Unknown'
|
166 |
+
saveDictionary['Trend'] = 'Unknown'
|
167 |
+
|
168 |
+
with SuppressOutput(suppress_stderr=True, suppress_stdout=True):
|
169 |
+
isCandlePattern = candlePatterns.findPattern(
|
170 |
+
processedData, screeningDictionary, saveDictionary)
|
171 |
+
|
172 |
+
isConfluence = False
|
173 |
+
isInsideBar = False
|
174 |
+
isIpoBase = False
|
175 |
+
if newlyListedOnly:
|
176 |
+
isIpoBase = screener.validateIpoBase(stock, fullData, screeningDictionary, saveDictionary)
|
177 |
+
if respChartPattern == 3 and executeOption == 7:
|
178 |
+
isConfluence = screener.validateConfluence(stock, processedData, screeningDictionary, saveDictionary, percentage=insideBarToLookback)
|
179 |
+
else:
|
180 |
+
isInsideBar = screener.validateInsideBar(processedData, screeningDictionary, saveDictionary, chartPattern=respChartPattern, daysToLookback=insideBarToLookback)
|
181 |
+
|
182 |
+
with SuppressOutput(suppress_stderr=True, suppress_stdout=True):
|
183 |
+
if maLength is not None and executeOption == 6 and reversalOption == 6:
|
184 |
+
isNR = screener.validateNarrowRange(processedData, screeningDictionary, saveDictionary, nr=maLength)
|
185 |
+
else:
|
186 |
+
isNR = screener.validateNarrowRange(processedData, screeningDictionary, saveDictionary)
|
187 |
+
|
188 |
+
isMomentum = screener.validateMomentum(processedData, screeningDictionary, saveDictionary)
|
189 |
+
|
190 |
+
isVSA = False
|
191 |
+
if not (executeOption == 7 and respChartPattern < 3):
|
192 |
+
isVSA = screener.validateVolumeSpreadAnalysis(processedData, screeningDictionary, saveDictionary)
|
193 |
+
if maLength is not None and executeOption == 6 and reversalOption == 4:
|
194 |
+
isMaSupport = screener.findReversalMA(fullData, screeningDictionary, saveDictionary, maLength)
|
195 |
+
if executeOption == 6 and reversalOption == 8:
|
196 |
+
isRsiReversal = screener.findRSICrossingMA(fullData, screeningDictionary, saveDictionary)
|
197 |
+
|
198 |
+
isVCP = False
|
199 |
+
if respChartPattern == 4:
|
200 |
+
with SuppressOutput(suppress_stderr=True, suppress_stdout=True):
|
201 |
+
isVCP = screener.validateVCP(fullData, screeningDictionary, saveDictionary)
|
202 |
+
|
203 |
+
isBuyingTrendline = False
|
204 |
+
if executeOption == 7 and respChartPattern == 5:
|
205 |
+
with SuppressOutput(suppress_stderr=True, suppress_stdout=True):
|
206 |
+
isBuyingTrendline = screener.findTrendlines(fullData, screeningDictionary, saveDictionary)
|
207 |
+
|
208 |
+
with SuppressOutput(suppress_stderr=True, suppress_stdout=True):
|
209 |
+
isLorentzian = screener.validateLorentzian(fullData, screeningDictionary, saveDictionary, lookFor = maLength)
|
210 |
+
|
211 |
+
try:
|
212 |
+
backtestReport = Utility.tools.calculateBacktestReport(data=processedData, backtestDict=backtestReport)
|
213 |
+
screeningDictionary.update(backtestReport)
|
214 |
+
saveDictionary.update(backtestReport)
|
215 |
+
except:
|
216 |
+
pass
|
217 |
+
|
218 |
+
with self.screenResultsCounter.get_lock():
|
219 |
+
if executeOption == 0:
|
220 |
+
self.screenResultsCounter.value += 1
|
221 |
+
return screeningDictionary, saveDictionary
|
222 |
+
if (executeOption == 1 or executeOption == 2) and isBreaking and isVolumeHigh and isLtpValid:
|
223 |
+
self.screenResultsCounter.value += 1
|
224 |
+
return screeningDictionary, saveDictionary
|
225 |
+
if (executeOption == 1 or executeOption == 3) and (consolidationValue <= configManager.consolidationPercentage and consolidationValue != 0) and isLtpValid:
|
226 |
+
self.screenResultsCounter.value += 1
|
227 |
+
return screeningDictionary, saveDictionary
|
228 |
+
if executeOption == 4 and isLtpValid and isLowestVolume:
|
229 |
+
self.screenResultsCounter.value += 1
|
230 |
+
return screeningDictionary, saveDictionary
|
231 |
+
if executeOption == 5 and isLtpValid and isValidRsi:
|
232 |
+
self.screenResultsCounter.value += 1
|
233 |
+
return screeningDictionary, saveDictionary
|
234 |
+
if executeOption == 6 and isLtpValid:
|
235 |
+
if reversalOption == 1:
|
236 |
+
if saveDictionary['Pattern'] in CandlePatterns.reversalPatternsBullish or isMaReversal > 0 or 'buy' in saveDictionary['Pattern'].lower():
|
237 |
+
self.screenResultsCounter.value += 1
|
238 |
+
return screeningDictionary, saveDictionary
|
239 |
+
elif reversalOption == 2:
|
240 |
+
if saveDictionary['Pattern'] in CandlePatterns.reversalPatternsBearish or isMaReversal < 0 or 'sell' in saveDictionary['Pattern'].lower():
|
241 |
+
self.screenResultsCounter.value += 1
|
242 |
+
return screeningDictionary, saveDictionary
|
243 |
+
elif reversalOption == 3 and isMomentum:
|
244 |
+
self.screenResultsCounter.value += 1
|
245 |
+
return screeningDictionary, saveDictionary
|
246 |
+
elif reversalOption == 4 and isMaSupport:
|
247 |
+
self.screenResultsCounter.value += 1
|
248 |
+
return screeningDictionary, saveDictionary
|
249 |
+
elif reversalOption == 5 and isVSA and saveDictionary['Pattern'] in CandlePatterns.reversalPatternsBullish:
|
250 |
+
self.screenResultsCounter.value += 1
|
251 |
+
return screeningDictionary, saveDictionary
|
252 |
+
elif reversalOption == 6 and isNR:
|
253 |
+
self.screenResultsCounter.value += 1
|
254 |
+
return screeningDictionary, saveDictionary
|
255 |
+
elif reversalOption == 7 and isLorentzian:
|
256 |
+
self.screenResultsCounter.value += 1
|
257 |
+
return screeningDictionary, saveDictionary
|
258 |
+
elif reversalOption == 8 and isRsiReversal:
|
259 |
+
self.screenResultsCounter.value += 1
|
260 |
+
return screeningDictionary, saveDictionary
|
261 |
+
if executeOption == 7 and isLtpValid:
|
262 |
+
if respChartPattern < 3 and isInsideBar:
|
263 |
+
self.screenResultsCounter.value += 1
|
264 |
+
return screeningDictionary, saveDictionary
|
265 |
+
if isConfluence:
|
266 |
+
self.screenResultsCounter.value += 1
|
267 |
+
return screeningDictionary, saveDictionary
|
268 |
+
if isIpoBase and newlyListedOnly and not respChartPattern < 3:
|
269 |
+
self.screenResultsCounter.value += 1
|
270 |
+
return screeningDictionary, saveDictionary
|
271 |
+
if isVCP:
|
272 |
+
self.screenResultsCounter.value += 1
|
273 |
+
return screeningDictionary, saveDictionary
|
274 |
+
if isBuyingTrendline:
|
275 |
+
self.screenResultsCounter.value += 1
|
276 |
+
return screeningDictionary, saveDictionary
|
277 |
+
except KeyboardInterrupt:
|
278 |
+
# Capturing Ctr+C Here isn't a great idea
|
279 |
+
pass
|
280 |
+
except Fetcher.StockDataEmptyException:
|
281 |
+
pass
|
282 |
+
except Screener.NotNewlyListed:
|
283 |
+
pass
|
284 |
+
except Screener.DownloadDataOnly:
|
285 |
+
pass
|
286 |
+
except KeyError:
|
287 |
+
pass
|
288 |
+
except Exception as e:
|
289 |
+
if isDevVersion:
|
290 |
+
print("[!] Dev Traceback:")
|
291 |
+
traceback.print_exc()
|
292 |
+
if printCounter:
|
293 |
+
print(colorText.FAIL +
|
294 |
+
("\n[+] Exception Occured while Screening %s! Skipping this stock.." % stock) + colorText.END)
|
295 |
+
return
|
296 |
+
|
297 |
+
def multiprocessingForWindows(self):
|
298 |
+
if sys.platform.startswith('win'):
|
299 |
+
|
300 |
+
class _Popen(forking.Popen):
|
301 |
+
def __init__(self, *args, **kw):
|
302 |
+
if hasattr(sys, 'frozen'):
|
303 |
+
os.putenv('_MEIPASS2', sys._MEIPASS)
|
304 |
+
try:
|
305 |
+
super(_Popen, self).__init__(*args, **kw)
|
306 |
+
finally:
|
307 |
+
if hasattr(sys, 'frozen'):
|
308 |
+
if hasattr(os, 'unsetenv'):
|
309 |
+
os.unsetenv('_MEIPASS2')
|
310 |
+
else:
|
311 |
+
os.putenv('_MEIPASS2', '')
|
312 |
+
|
313 |
+
forking.Popen = _Popen
|
src/classes/Screener.py
ADDED
@@ -0,0 +1,800 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 joblib
|
13 |
+
import keras
|
14 |
+
import time
|
15 |
+
import classes.Utility as Utility
|
16 |
+
from copy import copy
|
17 |
+
from advanced_ta import LorentzianClassification
|
18 |
+
from classes.Utility import isGui
|
19 |
+
from sklearn.preprocessing import StandardScaler
|
20 |
+
from scipy.signal import argrelextrema
|
21 |
+
from scipy.stats import linregress
|
22 |
+
from classes.ColorText import colorText
|
23 |
+
from classes.SuppressOutput import SuppressOutput
|
24 |
+
from classes.ScreenipyTA import ScreenerTA
|
25 |
+
try:
|
26 |
+
import chromadb
|
27 |
+
CHROMA_AVAILABLE = True
|
28 |
+
except:
|
29 |
+
CHROMA_AVAILABLE = False
|
30 |
+
|
31 |
+
|
32 |
+
# Exception for newly listed stocks with candle nos < daysToLookback
|
33 |
+
class StockDataNotAdequate(Exception):
|
34 |
+
pass
|
35 |
+
|
36 |
+
# Exception for only downloading stock data and not screening
|
37 |
+
class DownloadDataOnly(Exception):
|
38 |
+
pass
|
39 |
+
|
40 |
+
# Exception for stocks which are not newly listed when screening only for Newly Listed
|
41 |
+
class NotNewlyListed(Exception):
|
42 |
+
pass
|
43 |
+
|
44 |
+
# This Class contains methods for stock analysis and screening validation
|
45 |
+
class tools:
|
46 |
+
|
47 |
+
def __init__(self, configManager) -> None:
|
48 |
+
self.configManager = configManager
|
49 |
+
|
50 |
+
# Private method to find candle type
|
51 |
+
# True = Bullish, False = Bearish
|
52 |
+
def getCandleType(self, dailyData):
|
53 |
+
return bool(dailyData['Close'].iloc[0] >= dailyData['Open'].iloc[0])
|
54 |
+
|
55 |
+
|
56 |
+
# Preprocess the acquired data
|
57 |
+
def preprocessData(self, data, daysToLookback=None):
|
58 |
+
if daysToLookback is None:
|
59 |
+
daysToLookback = self.configManager.daysToLookback
|
60 |
+
if self.configManager.useEMA:
|
61 |
+
sma = ScreenerTA.EMA(data['Close'],timeperiod=50)
|
62 |
+
lma = ScreenerTA.EMA(data['Close'],timeperiod=200)
|
63 |
+
data.insert(6,'SMA',sma)
|
64 |
+
data.insert(7,'LMA',lma)
|
65 |
+
else:
|
66 |
+
sma = data.rolling(window=50).mean()
|
67 |
+
lma = data.rolling(window=200).mean()
|
68 |
+
data.insert(6,'SMA',sma['Close'])
|
69 |
+
data.insert(7,'LMA',lma['Close'])
|
70 |
+
vol = data.rolling(window=20).mean()
|
71 |
+
rsi = ScreenerTA.RSI(data['Close'], timeperiod=14)
|
72 |
+
data.insert(8,'VolMA',vol['Volume'])
|
73 |
+
data.insert(9,'RSI',rsi)
|
74 |
+
data = data[::-1] # Reverse the dataframe
|
75 |
+
# data = data.fillna(0)
|
76 |
+
# data = data.replace([np.inf, -np.inf], 0)
|
77 |
+
fullData = data
|
78 |
+
trimmedData = data.head(daysToLookback)
|
79 |
+
return (fullData, trimmedData)
|
80 |
+
|
81 |
+
# Validate LTP within limits
|
82 |
+
def validateLTP(self, data, screenDict, saveDict, minLTP=None, maxLTP=None):
|
83 |
+
if minLTP is None:
|
84 |
+
minLTP = self.configManager.minLTP
|
85 |
+
if maxLTP is None:
|
86 |
+
maxLTP = self.configManager.maxLTP
|
87 |
+
data = data.fillna(0)
|
88 |
+
data = data.replace([np.inf, -np.inf], 0)
|
89 |
+
recent = data.head(1)
|
90 |
+
|
91 |
+
pct_change = (data[::-1]['Close'].pct_change(fill_method=None) * 100).iloc[-1]
|
92 |
+
if pct_change > 0.2:
|
93 |
+
pct_change = colorText.GREEN + (" (%.1f%%)" % pct_change) + colorText.END
|
94 |
+
elif pct_change < -0.2:
|
95 |
+
pct_change = colorText.FAIL + (" (%.1f%%)" % pct_change) + colorText.END
|
96 |
+
else:
|
97 |
+
pct_change = colorText.WARN + (" (%.1f%%)" % pct_change) + colorText.END
|
98 |
+
|
99 |
+
ltp = round(recent['Close'].iloc[0],2)
|
100 |
+
saveDict['LTP'] = str(ltp)
|
101 |
+
verifyStageTwo = True
|
102 |
+
if self.configManager.stageTwo and len(data) > 250:
|
103 |
+
yearlyLow = data.head(250).min()['Close']
|
104 |
+
yearlyHigh = data.head(250).max()['Close']
|
105 |
+
if ltp < (2 * yearlyLow) or ltp < (0.75 * yearlyHigh):
|
106 |
+
verifyStageTwo = False
|
107 |
+
if(ltp >= minLTP and ltp <= maxLTP and verifyStageTwo):
|
108 |
+
screenDict['LTP'] = colorText.GREEN + ("%.2f" % ltp) + pct_change + colorText.END
|
109 |
+
return True
|
110 |
+
screenDict['LTP'] = colorText.FAIL + ("%.2f" % ltp) + pct_change + colorText.END
|
111 |
+
return False
|
112 |
+
|
113 |
+
# Validate if share prices are consolidating
|
114 |
+
def validateConsolidation(self, data, screenDict, saveDict, percentage=10):
|
115 |
+
data = data.fillna(0)
|
116 |
+
data = data.replace([np.inf, -np.inf], 0)
|
117 |
+
hc = data.describe()['Close']['max']
|
118 |
+
lc = data.describe()['Close']['min']
|
119 |
+
if ((hc - lc) <= (hc*percentage/100) and (hc - lc != 0)):
|
120 |
+
screenDict['Consolidating'] = colorText.BOLD + colorText.GREEN + "Range = " + str(round((abs((hc-lc)/hc)*100),1))+"%" + colorText.END
|
121 |
+
else:
|
122 |
+
screenDict['Consolidating'] = colorText.BOLD + colorText.FAIL + "Range = " + str(round((abs((hc-lc)/hc)*100),1)) + "%" + colorText.END
|
123 |
+
saveDict['Consolidating'] = str(round((abs((hc-lc)/hc)*100),1))+"%"
|
124 |
+
return round((abs((hc-lc)/hc)*100),1)
|
125 |
+
|
126 |
+
# Validate Moving averages and look for buy/sell signals
|
127 |
+
def validateMovingAverages(self, data, screenDict, saveDict, maRange=2.5):
|
128 |
+
data = data.fillna(0)
|
129 |
+
data = data.replace([np.inf, -np.inf], 0)
|
130 |
+
recent = data.head(1)
|
131 |
+
if(recent['SMA'].iloc[0] > recent['LMA'].iloc[0] and recent['Close'].iloc[0] > recent['SMA'].iloc[0]):
|
132 |
+
screenDict['MA-Signal'] = colorText.BOLD + colorText.GREEN + 'Bullish' + colorText.END
|
133 |
+
saveDict['MA-Signal'] = 'Bullish'
|
134 |
+
elif(recent['SMA'].iloc[0] < recent['LMA'].iloc[0]):
|
135 |
+
screenDict['MA-Signal'] = colorText.BOLD + colorText.FAIL + 'Bearish' + colorText.END
|
136 |
+
saveDict['MA-Signal'] = 'Bearish'
|
137 |
+
elif(recent['SMA'].iloc[0] == 0):
|
138 |
+
screenDict['MA-Signal'] = colorText.BOLD + colorText.WARN + 'Unknown' + colorText.END
|
139 |
+
saveDict['MA-Signal'] = 'Unknown'
|
140 |
+
else:
|
141 |
+
screenDict['MA-Signal'] = colorText.BOLD + colorText.WARN + 'Neutral' + colorText.END
|
142 |
+
saveDict['MA-Signal'] = 'Neutral'
|
143 |
+
|
144 |
+
smaDev = data['SMA'].iloc[0] * maRange / 100
|
145 |
+
lmaDev = data['LMA'].iloc[0] * maRange / 100
|
146 |
+
open, high, low, close, sma, lma = data['Open'].iloc[0], data['High'].iloc[0], data['Low'].iloc[0], data['Close'].iloc[0], data['SMA'].iloc[0], data['LMA'].iloc[0]
|
147 |
+
maReversal = 0
|
148 |
+
# Taking Support 50
|
149 |
+
if close > sma and low <= (sma + smaDev):
|
150 |
+
screenDict['MA-Signal'] = colorText.BOLD + colorText.GREEN + '50MA-Support' + colorText.END
|
151 |
+
saveDict['MA-Signal'] = '50MA-Support'
|
152 |
+
maReversal = 1
|
153 |
+
# Validating Resistance 50
|
154 |
+
elif close < sma and high >= (sma - smaDev):
|
155 |
+
screenDict['MA-Signal'] = colorText.BOLD + colorText.FAIL + '50MA-Resist' + colorText.END
|
156 |
+
saveDict['MA-Signal'] = '50MA-Resist'
|
157 |
+
maReversal = -1
|
158 |
+
# Taking Support 200
|
159 |
+
elif close > lma and low <= (lma + lmaDev):
|
160 |
+
screenDict['MA-Signal'] = colorText.BOLD + colorText.GREEN + '200MA-Support' + colorText.END
|
161 |
+
saveDict['MA-Signal'] = '200MA-Support'
|
162 |
+
maReversal = 1
|
163 |
+
# Validating Resistance 200
|
164 |
+
elif close < lma and high >= (lma - lmaDev):
|
165 |
+
screenDict['MA-Signal'] = colorText.BOLD + colorText.FAIL + '200MA-Resist' + colorText.END
|
166 |
+
saveDict['MA-Signal'] = '200MA-Resist'
|
167 |
+
maReversal = -1
|
168 |
+
# For a Bullish Candle
|
169 |
+
if self.getCandleType(data):
|
170 |
+
# Crossing up 50
|
171 |
+
if open < sma and close > sma:
|
172 |
+
screenDict['MA-Signal'] = colorText.BOLD + colorText.GREEN + 'BullCross-50MA' + colorText.END
|
173 |
+
saveDict['MA-Signal'] = 'BullCross-50MA'
|
174 |
+
maReversal = 1
|
175 |
+
# Crossing up 200
|
176 |
+
elif open < lma and close > lma:
|
177 |
+
screenDict['MA-Signal'] = colorText.BOLD + colorText.GREEN + 'BullCross-200MA' + colorText.END
|
178 |
+
saveDict['MA-Signal'] = 'BullCross-200MA'
|
179 |
+
maReversal = 1
|
180 |
+
# For a Bearish Candle
|
181 |
+
elif not self.getCandleType(data):
|
182 |
+
# Crossing down 50
|
183 |
+
if open > sma and close < sma:
|
184 |
+
screenDict['MA-Signal'] = colorText.BOLD + colorText.FAIL + 'BearCross-50MA' + colorText.END
|
185 |
+
saveDict['MA-Signal'] = 'BearCross-50MA'
|
186 |
+
maReversal = -1
|
187 |
+
# Crossing up 200
|
188 |
+
elif open > lma and close < lma:
|
189 |
+
screenDict['MA-Signal'] = colorText.BOLD + colorText.FAIL + 'BearCross-200MA' + colorText.END
|
190 |
+
saveDict['MA-Signal'] = 'BearCross-200MA'
|
191 |
+
maReversal = -1
|
192 |
+
return maReversal
|
193 |
+
|
194 |
+
# Validate if volume of last day is higher than avg
|
195 |
+
def validateVolume(self, data, screenDict, saveDict, volumeRatio=2.5):
|
196 |
+
data = data.fillna(0)
|
197 |
+
data = data.replace([np.inf, -np.inf], 0)
|
198 |
+
recent = data.head(1)
|
199 |
+
if recent['VolMA'].iloc[0] == 0: # Handles Divide by 0 warning
|
200 |
+
saveDict['Volume'] = "Unknown"
|
201 |
+
screenDict['Volume'] = colorText.BOLD + colorText.WARN + "Unknown" + colorText.END
|
202 |
+
return True
|
203 |
+
ratio = round(recent['Volume'].iloc[0]/recent['VolMA'].iloc[0],2)
|
204 |
+
saveDict['Volume'] = str(ratio)+"x"
|
205 |
+
if(ratio >= volumeRatio and ratio != np.nan and (not math.isinf(ratio)) and (ratio != 20)):
|
206 |
+
screenDict['Volume'] = colorText.BOLD + colorText.GREEN + str(ratio) + "x" + colorText.END
|
207 |
+
return True
|
208 |
+
screenDict['Volume'] = colorText.BOLD + colorText.FAIL + str(ratio) + "x" + colorText.END
|
209 |
+
return False
|
210 |
+
|
211 |
+
# Find accurate breakout value
|
212 |
+
def findBreakout(self, data, screenDict, saveDict, daysToLookback):
|
213 |
+
data = data.fillna(0)
|
214 |
+
data = data.replace([np.inf, -np.inf], 0)
|
215 |
+
recent = data.head(1)
|
216 |
+
data = data[1:]
|
217 |
+
hs = round(data.describe()['High']['max'],2)
|
218 |
+
hc = round(data.describe()['Close']['max'],2)
|
219 |
+
rc = round(recent['Close'].iloc[0],2)
|
220 |
+
if np.isnan(hc) or np.isnan(hs):
|
221 |
+
saveDict['Breaking-Out'] = 'BO: Unknown'
|
222 |
+
screenDict['Breaking-Out'] = colorText.BOLD + colorText.WARN + 'BO: Unknown' + colorText.END
|
223 |
+
return False
|
224 |
+
if hs > hc:
|
225 |
+
if ((hs - hc) <= (hs*2/100)):
|
226 |
+
saveDict['Breaking-Out'] = str(hc)
|
227 |
+
if rc >= hc:
|
228 |
+
screenDict['Breaking-Out'] = colorText.BOLD + colorText.GREEN + "BO: " + str(hc) + " R: " + str(hs) + colorText.END
|
229 |
+
return True and self.getCandleType(recent)
|
230 |
+
screenDict['Breaking-Out'] = colorText.BOLD + colorText.FAIL + "BO: " + str(hc) + " R: " + str(hs) + colorText.END
|
231 |
+
return False
|
232 |
+
noOfHigherShadows = len(data[data.High > hc])
|
233 |
+
if(daysToLookback/noOfHigherShadows <= 3):
|
234 |
+
saveDict['Breaking-Out'] = str(hs)
|
235 |
+
if rc >= hs:
|
236 |
+
screenDict['Breaking-Out'] = colorText.BOLD + colorText.GREEN + "BO: " + str(hs) + colorText.END
|
237 |
+
return True and self.getCandleType(recent)
|
238 |
+
screenDict['Breaking-Out'] = colorText.BOLD + colorText.FAIL + "BO: " + str(hs) + colorText.END
|
239 |
+
return False
|
240 |
+
saveDict['Breaking-Out'] = str(hc) + ", " + str(hs)
|
241 |
+
if rc >= hc:
|
242 |
+
screenDict['Breaking-Out'] = colorText.BOLD + colorText.GREEN + "BO: " + str(hc) + " R: " + str(hs) + colorText.END
|
243 |
+
return True and self.getCandleType(recent)
|
244 |
+
screenDict['Breaking-Out'] = colorText.BOLD + colorText.FAIL + "BO: " + str(hc) + " R: " + str(hs) + colorText.END
|
245 |
+
return False
|
246 |
+
else:
|
247 |
+
saveDict['Breaking-Out'] = str(hc)
|
248 |
+
if rc >= hc:
|
249 |
+
screenDict['Breaking-Out'] = colorText.BOLD + colorText.GREEN + "BO: " + str(hc) + colorText.END
|
250 |
+
return True and self.getCandleType(recent)
|
251 |
+
screenDict['Breaking-Out'] = colorText.BOLD + colorText.FAIL + "BO: " + str(hc) + colorText.END
|
252 |
+
return False
|
253 |
+
|
254 |
+
# Validate 'Inside Bar' structure for recent days
|
255 |
+
def validateInsideBar(self, data, screenDict, saveDict, chartPattern=1, daysToLookback=5):
|
256 |
+
orgData = data
|
257 |
+
daysToLookback = int(daysToLookback)
|
258 |
+
for i in range(daysToLookback, round(daysToLookback*0.5)-1, -1):
|
259 |
+
if i == 2:
|
260 |
+
return 0 # Exit if only last 2 candles are left
|
261 |
+
if chartPattern == 1:
|
262 |
+
if "Up" in saveDict['Trend'] and ("Bull" in saveDict['MA-Signal'] or "Support" 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 |
+
else:
|
272 |
+
if "Down" in saveDict['Trend'] and ("Bear" in saveDict['MA-Signal'] or "Resist" in saveDict['MA-Signal']):
|
273 |
+
data = orgData.head(i)
|
274 |
+
refCandle = data.tail(1)
|
275 |
+
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):
|
276 |
+
screenDict['Pattern'] = colorText.BOLD + colorText.WARN + ("Inside Bar (%d)" % i) + colorText.END
|
277 |
+
saveDict['Pattern'] = "Inside Bar (%d)" % i
|
278 |
+
return i
|
279 |
+
else:
|
280 |
+
return 0
|
281 |
+
return 0
|
282 |
+
|
283 |
+
# Validate if recent volume is lowest of last 'N' Days
|
284 |
+
def validateLowestVolume(self, data, daysForLowestVolume):
|
285 |
+
data = data.fillna(0)
|
286 |
+
data = data.replace([np.inf, -np.inf], 0)
|
287 |
+
if daysForLowestVolume is None:
|
288 |
+
daysForLowestVolume = 30
|
289 |
+
data = data.head(daysForLowestVolume)
|
290 |
+
recent = data.head(1)
|
291 |
+
if((recent['Volume'].iloc[0] <= data.describe()['Volume']['min']) and recent['Volume'].iloc[0] != np.nan):
|
292 |
+
return True
|
293 |
+
return False
|
294 |
+
|
295 |
+
# validate if RSI is within given range
|
296 |
+
def validateRSI(self, data, screenDict, saveDict, minRSI, maxRSI):
|
297 |
+
data = data.fillna(0)
|
298 |
+
data = data.replace([np.inf, -np.inf], 0)
|
299 |
+
rsi = int(data.head(1)['RSI'].iloc[0])
|
300 |
+
saveDict['RSI'] = rsi
|
301 |
+
if(rsi >= minRSI and rsi <= maxRSI) and (rsi <= 70 and rsi >= 30):
|
302 |
+
screenDict['RSI'] = colorText.BOLD + colorText.GREEN + str(rsi) + colorText.END
|
303 |
+
return True
|
304 |
+
screenDict['RSI'] = colorText.BOLD + colorText.FAIL + str(rsi) + colorText.END
|
305 |
+
return False
|
306 |
+
|
307 |
+
# Find out trend for days to lookback
|
308 |
+
def findTrend(self, data, screenDict, saveDict, daysToLookback=None,stockName=""):
|
309 |
+
if daysToLookback is None:
|
310 |
+
daysToLookback = self.configManager.daysToLookback
|
311 |
+
data = data.head(daysToLookback)
|
312 |
+
data = data[::-1]
|
313 |
+
data = data.set_index(np.arange(len(data)))
|
314 |
+
data = data.fillna(0)
|
315 |
+
data = data.replace([np.inf, -np.inf], 0)
|
316 |
+
with SuppressOutput(suppress_stdout=True,suppress_stderr=True):
|
317 |
+
data['tops'] = data['Close'].iloc[list(argrelextrema(np.array(data['Close']), np.greater_equal, order=1)[0])]
|
318 |
+
data = data.fillna(0)
|
319 |
+
data = data.replace([np.inf, -np.inf], 0)
|
320 |
+
try:
|
321 |
+
try:
|
322 |
+
if len(data) < daysToLookback:
|
323 |
+
raise StockDataNotAdequate
|
324 |
+
slope,c = np.polyfit(data.index[data.tops > 0], data['tops'][data.tops > 0], 1)
|
325 |
+
except Exception as e:
|
326 |
+
slope,c = 0,0
|
327 |
+
angle = np.rad2deg(np.arctan(slope))
|
328 |
+
if (angle == 0):
|
329 |
+
screenDict['Trend'] = colorText.BOLD + colorText.WARN + "Unknown" + colorText.END
|
330 |
+
saveDict['Trend'] = 'Unknown'
|
331 |
+
elif (angle <= 30 and angle >= -30):
|
332 |
+
screenDict['Trend'] = colorText.BOLD + colorText.WARN + "Sideways" + colorText.END
|
333 |
+
saveDict['Trend'] = 'Sideways'
|
334 |
+
elif (angle >= 30 and angle < 61):
|
335 |
+
screenDict['Trend'] = colorText.BOLD + colorText.GREEN + "Weak Up" + colorText.END
|
336 |
+
saveDict['Trend'] = 'Weak Up'
|
337 |
+
elif angle >= 60:
|
338 |
+
screenDict['Trend'] = colorText.BOLD + colorText.GREEN + "Strong Up" + colorText.END
|
339 |
+
saveDict['Trend'] = 'Strong Up'
|
340 |
+
elif (angle <= -30 and angle > -61):
|
341 |
+
screenDict['Trend'] = colorText.BOLD + colorText.FAIL + "Weak Down" + colorText.END
|
342 |
+
saveDict['Trend'] = 'Weak Down'
|
343 |
+
elif angle <= -60:
|
344 |
+
screenDict['Trend'] = colorText.BOLD + colorText.FAIL + "Strong Down" + colorText.END
|
345 |
+
saveDict['Trend'] = 'Strong Down'
|
346 |
+
except np.linalg.LinAlgError:
|
347 |
+
screenDict['Trend'] = colorText.BOLD + colorText.WARN + "Unknown" + colorText.END
|
348 |
+
saveDict['Trend'] = 'Unknown'
|
349 |
+
return saveDict['Trend']
|
350 |
+
|
351 |
+
# Find if stock is validating volume spread analysis
|
352 |
+
def validateVolumeSpreadAnalysis(self, data, screenDict, saveDict):
|
353 |
+
try:
|
354 |
+
data = data.head(2)
|
355 |
+
try:
|
356 |
+
# Check for previous RED candles
|
357 |
+
# Current candle = 0th, Previous Candle = 1st for following logic
|
358 |
+
if data.iloc[1]['Open'] >= data.iloc[1]['Close']:
|
359 |
+
spread1 = abs(data.iloc[1]['Open'] - data.iloc[1]['Close'])
|
360 |
+
spread0 = abs(data.iloc[0]['Open'] - data.iloc[0]['Close'])
|
361 |
+
lower_wick_spread0 = max(data.iloc[0]['Open'], data.iloc[0]['Close']) - data.iloc[0]['Low']
|
362 |
+
vol1 = data.iloc[1]['Volume']
|
363 |
+
vol0 = data.iloc[0]['Volume']
|
364 |
+
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):
|
365 |
+
screenDict['Pattern'] = colorText.BOLD + colorText.GREEN + 'Supply Drought' + colorText.END
|
366 |
+
saveDict['Pattern'] = 'Supply Drought'
|
367 |
+
return True
|
368 |
+
if spread0 < spread1 and vol0 > vol1 and data.iloc[0]['Volume'] > data.iloc[0]['VolMA'] and data.iloc[0]['Close'] <= data.iloc[1]['Open']:
|
369 |
+
screenDict['Pattern'] = colorText.BOLD + colorText.GREEN + 'Demand Rise' + colorText.END
|
370 |
+
saveDict['Pattern'] = 'Demand Rise'
|
371 |
+
return True
|
372 |
+
except IndexError:
|
373 |
+
pass
|
374 |
+
return False
|
375 |
+
except:
|
376 |
+
import traceback
|
377 |
+
traceback.print_exc()
|
378 |
+
return False
|
379 |
+
|
380 |
+
# Find if stock gaining bullish momentum
|
381 |
+
def validateMomentum(self, data, screenDict, saveDict):
|
382 |
+
try:
|
383 |
+
data = data.head(3)
|
384 |
+
for row in data.iterrows():
|
385 |
+
# All 3 candles should be Green and NOT Circuits
|
386 |
+
if row[1]['Close'].item() <= row[1]['Open'].item():
|
387 |
+
return False
|
388 |
+
openDesc = data.sort_values(by=['Open'], ascending=False)
|
389 |
+
closeDesc = data.sort_values(by=['Close'], ascending=False)
|
390 |
+
volDesc = data.sort_values(by=['Volume'], ascending=False)
|
391 |
+
try:
|
392 |
+
if data.equals(openDesc) and data.equals(closeDesc) and data.equals(volDesc):
|
393 |
+
if (data['Open'].iloc[0].item() >= data['Close'].iloc[1].item()) and (data['Open'].iloc[1].item() >= data['Close'].iloc[2].item()):
|
394 |
+
screenDict['Pattern'] = colorText.BOLD + colorText.GREEN + 'Momentum Gainer' + colorText.END
|
395 |
+
saveDict['Pattern'] = 'Momentum Gainer'
|
396 |
+
return True
|
397 |
+
except IndexError:
|
398 |
+
pass
|
399 |
+
return False
|
400 |
+
except Exception as e:
|
401 |
+
import traceback
|
402 |
+
traceback.print_exc()
|
403 |
+
return False
|
404 |
+
|
405 |
+
# Find stock reversing at given MA
|
406 |
+
def findReversalMA(self, data, screenDict, saveDict, maLength, percentage=0.015):
|
407 |
+
if maLength is None:
|
408 |
+
maLength = 20
|
409 |
+
data = data[::-1]
|
410 |
+
if self.configManager.useEMA:
|
411 |
+
maRev = ScreenerTA.EMA(data['Close'],timeperiod=maLength)
|
412 |
+
else:
|
413 |
+
maRev = ScreenerTA.MA(data['Close'],timeperiod=maLength)
|
414 |
+
data.insert(10,'maRev',maRev)
|
415 |
+
data = data[::-1].head(3)
|
416 |
+
if data.equals(data[(data.Close >= (data.maRev - (data.maRev*percentage))) & (data.Close <= (data.maRev + (data.maRev*percentage)))]) and data.head(1)['Close'].iloc[0] >= data.head(1)['maRev'].iloc[0]:
|
417 |
+
if self.configManager.stageTwo:
|
418 |
+
if data.head(1)['maRev'].iloc[0] < data.head(2)['maRev'].iloc[1] or data.head(2)['maRev'].iloc[1] < data.head(3)['maRev'].iloc[2] or data.head(1)['SMA'].iloc[0] < data.head(1)['LMA'].iloc[0]:
|
419 |
+
return False
|
420 |
+
screenDict['MA-Signal'] = colorText.BOLD + colorText.GREEN + f'Reversal-{maLength}MA' + colorText.END
|
421 |
+
saveDict['MA-Signal'] = f'Reversal-{maLength}MA'
|
422 |
+
return True
|
423 |
+
return False
|
424 |
+
|
425 |
+
# Find stock showing RSI crossing with RSI 9 SMA
|
426 |
+
def findRSICrossingMA(self, data, screenDict, saveDict, maLength=9):
|
427 |
+
data = data[::-1]
|
428 |
+
maRsi = ScreenerTA.MA(data['RSI'], timeperiod=maLength)
|
429 |
+
data.insert(10,'maRsi',maRsi)
|
430 |
+
data = data[::-1].head(3)
|
431 |
+
if data['maRsi'].iloc[0] <= data['RSI'].iloc[0] and data['maRsi'].iloc[1] > data['RSI'].iloc[1]:
|
432 |
+
screenDict['MA-Signal'] = colorText.BOLD + colorText.GREEN + f'RSI-MA-Buy' + colorText.END
|
433 |
+
saveDict['MA-Signal'] = f'RSI-MA-Buy'
|
434 |
+
return True
|
435 |
+
elif data['maRsi'].iloc[0] >= data['RSI'].iloc[0] and data['maRsi'].iloc[1] < data['RSI'].iloc[1]:
|
436 |
+
screenDict['MA-Signal'] = colorText.BOLD + colorText.GREEN + f'RSI-MA-Sell' + colorText.END
|
437 |
+
saveDict['MA-Signal'] = f'RSI-MA-Sell'
|
438 |
+
return True
|
439 |
+
return False
|
440 |
+
|
441 |
+
|
442 |
+
# Find IPO base
|
443 |
+
def validateIpoBase(self, stock, data, screenDict, saveDict, percentage=0.3):
|
444 |
+
listingPrice = data[::-1].head(1)['Open'].iloc[0]
|
445 |
+
currentPrice = data.head(1)['Close'].iloc[0]
|
446 |
+
ATH = data.describe()['High']['max']
|
447 |
+
if ATH > (listingPrice + (listingPrice * percentage)):
|
448 |
+
return False
|
449 |
+
away = round(((currentPrice - listingPrice)/listingPrice)*100, 1)
|
450 |
+
if((listingPrice - (listingPrice * percentage)) <= currentPrice <= (listingPrice + (listingPrice * percentage))):
|
451 |
+
if away > 0:
|
452 |
+
screenDict['Pattern'] = colorText.BOLD + colorText.GREEN + f'IPO Base ({away} %)' + colorText.END
|
453 |
+
else:
|
454 |
+
screenDict['Pattern'] = colorText.BOLD + colorText.GREEN + 'IPO Base ' + colorText.FAIL + f'({away} %)' + colorText.END
|
455 |
+
saveDict['Pattern'] = f'IPO Base ({away} %)'
|
456 |
+
return True
|
457 |
+
return False
|
458 |
+
|
459 |
+
# Find Conflucence
|
460 |
+
def validateConfluence(self, stock, data, screenDict, saveDict, percentage=0.1):
|
461 |
+
recent = data.head(1)
|
462 |
+
if(abs(recent['SMA'].iloc[0] - recent['LMA'].iloc[0]) <= (recent['SMA'].iloc[0] * percentage)):
|
463 |
+
difference = round(abs(recent['SMA'].iloc[0] - recent['LMA'].iloc[0])/recent['Close'].iloc[0] * 100,2)
|
464 |
+
if recent['SMA'].iloc[0] >= recent['LMA'].iloc[0]:
|
465 |
+
screenDict['MA-Signal'] = colorText.BOLD + colorText.GREEN + f'Confluence ({difference}%)' + colorText.END
|
466 |
+
saveDict['MA-Signal'] = f'Confluence ({difference}%)'
|
467 |
+
else:
|
468 |
+
screenDict['MA-Signal'] = colorText.BOLD + colorText.FAIL + f'Confluence ({difference}%)' + colorText.END
|
469 |
+
saveDict['MA-Signal'] = f'Confluence ({difference}%)'
|
470 |
+
return True
|
471 |
+
return False
|
472 |
+
|
473 |
+
# Find if stock is newly listed
|
474 |
+
def validateNewlyListed(self, data, daysToLookback):
|
475 |
+
daysToLookback = int(daysToLookback[:-1])
|
476 |
+
recent = data.head(1)
|
477 |
+
if len(data) < daysToLookback and (recent['Close'].iloc[0] != np.nan and recent['Close'].iloc[0] > 0):
|
478 |
+
return True
|
479 |
+
return False
|
480 |
+
|
481 |
+
# Find stocks approching to long term trendlines
|
482 |
+
def findTrendlines(self, data, screenDict, saveDict, percentage = 0.05):
|
483 |
+
period = int(''.join(c for c in self.configManager.period if c.isdigit()))
|
484 |
+
if len(data) < period:
|
485 |
+
return False
|
486 |
+
|
487 |
+
data = data[::-1]
|
488 |
+
data['Number'] = np.arange(len(data))+1
|
489 |
+
data_high = data.copy()
|
490 |
+
data_low = data.copy()
|
491 |
+
points = 30
|
492 |
+
|
493 |
+
''' Ignoring the Resitance for long-term purpose
|
494 |
+
while len(data_high) > points:
|
495 |
+
slope, intercept, r_value, p_value, std_err = linregress(x=data_high['Number'], y=data_high['High'])
|
496 |
+
data_high = data_high.loc[data_high['High'] > slope * data_high['Number'] + intercept]
|
497 |
+
slope, intercept, r_value, p_value, std_err = linregress(x=data_high['Number'], y=data_high['Close'])
|
498 |
+
data['Resistance'] = slope * data['Number'] + intercept
|
499 |
+
'''
|
500 |
+
|
501 |
+
while len(data_low) > points:
|
502 |
+
slope, intercept, r_value, p_value, std_err = linregress(x=data_low['Number'], y=data_low['Low'])
|
503 |
+
data_low = data_low.loc[data_low['Low'] < slope * data_low['Number'] + intercept]
|
504 |
+
|
505 |
+
slope, intercept, r_value, p_value, std_err = linregress(x=data_low['Number'], y=data_low['Close'])
|
506 |
+
data['Support'] = slope * data['Number'] + intercept
|
507 |
+
now = data.tail(1)
|
508 |
+
|
509 |
+
limit_upper = now['Support'].iloc[0].item() + (now['Support'].iloc[0].item() * percentage)
|
510 |
+
limit_lower = now['Support'].iloc[0].item() - (now['Support'].iloc[0].item() * percentage)
|
511 |
+
|
512 |
+
if limit_lower < now['Close'].iloc[0].item() < limit_upper and slope > 0.15:
|
513 |
+
screenDict['Pattern'] = colorText.BOLD + colorText.GREEN + 'Trendline-Support' + colorText.END
|
514 |
+
saveDict['Pattern'] = 'Trendline-Support'
|
515 |
+
return True
|
516 |
+
|
517 |
+
''' Plots for debugging
|
518 |
+
import matplotlib.pyplot as plt
|
519 |
+
fig, ax1 = plt.subplots(figsize=(15,10))
|
520 |
+
color = 'tab:green'
|
521 |
+
xdate = [x.date() for x in data.index]
|
522 |
+
ax1.set_xlabel('Date', color=color)
|
523 |
+
ax1.plot(xdate, data.Close, label="close", color=color)
|
524 |
+
ax1.tick_params(axis='x', labelcolor=color)
|
525 |
+
|
526 |
+
ax2 = ax1.twiny() # ax2 and ax1 will have common y axis and different x axis, twiny
|
527 |
+
ax2.plot(data.Number, data.Resistance, label="Res")
|
528 |
+
ax2.plot(data.Number, data.Support, label="Sup")
|
529 |
+
|
530 |
+
plt.legend()
|
531 |
+
plt.grid()
|
532 |
+
plt.show()
|
533 |
+
'''
|
534 |
+
return False
|
535 |
+
|
536 |
+
|
537 |
+
# Find NRx range for Reversal
|
538 |
+
def validateNarrowRange(self, data, screenDict, saveDict, nr=4):
|
539 |
+
if Utility.tools.isTradingTime():
|
540 |
+
rangeData = data.head(nr+1)[1:]
|
541 |
+
now_candle = data.head(1)
|
542 |
+
rangeData['Range'] = abs(rangeData['Close'] - rangeData['Open'])
|
543 |
+
recent = rangeData.head(1)
|
544 |
+
if recent['Range'].iloc[0] == rangeData.describe()['Range']['min']:
|
545 |
+
if self.getCandleType(recent) and now_candle['Close'].iloc[0] >= recent['Close'].iloc[0]:
|
546 |
+
screenDict['Pattern'] = colorText.BOLD + colorText.GREEN + f'Buy-NR{nr}' + colorText.END
|
547 |
+
saveDict['Pattern'] = f'Buy-NR{nr}'
|
548 |
+
return True
|
549 |
+
elif not self.getCandleType(recent) and now_candle['Close'].iloc[0] <= recent['Close'].iloc[0]:
|
550 |
+
screenDict['Pattern'] = colorText.BOLD + colorText.FAIL + f'Sell-NR{nr}' + colorText.END
|
551 |
+
saveDict['Pattern'] = f'Sell-NR{nr}'
|
552 |
+
return True
|
553 |
+
return False
|
554 |
+
else:
|
555 |
+
rangeData = data.head(nr)
|
556 |
+
rangeData['Range'] = abs(rangeData['Close'] - rangeData['Open'])
|
557 |
+
recent = rangeData.head(1)
|
558 |
+
if recent['Range'].iloc[0] == rangeData.describe()['Range']['min']:
|
559 |
+
screenDict['Pattern'] = colorText.BOLD + colorText.GREEN + f'NR{nr}' + colorText.END
|
560 |
+
saveDict['Pattern'] = f'NR{nr}'
|
561 |
+
return True
|
562 |
+
return False
|
563 |
+
|
564 |
+
# Validate Lorentzian Classification signal
|
565 |
+
def validateLorentzian(self, data, screenDict, saveDict, lookFor=1):
|
566 |
+
# lookFor: 1-Any, 2-Buy, 3-Sell
|
567 |
+
data = data[::-1] # Reverse the dataframe
|
568 |
+
data = data.rename(columns={'Open':'open', 'Close':'close', 'High':'high', 'Low':'low', 'Volume':'volume'})
|
569 |
+
lc = LorentzianClassification(data=data)
|
570 |
+
if lc.df.iloc[-1]['isNewBuySignal']:
|
571 |
+
screenDict['Pattern'] = colorText.BOLD + colorText.GREEN + f'Lorentzian-Buy' + colorText.END
|
572 |
+
saveDict['Pattern'] = f'Lorentzian-Buy'
|
573 |
+
if lookFor != 3:
|
574 |
+
return True
|
575 |
+
elif lc.df.iloc[-1]['isNewSellSignal']:
|
576 |
+
screenDict['Pattern'] = colorText.BOLD + colorText.FAIL + f'Lorentzian-Sell' + colorText.END
|
577 |
+
saveDict['Pattern'] = f'Lorentzian-Sell'
|
578 |
+
if lookFor != 2:
|
579 |
+
return True
|
580 |
+
return False
|
581 |
+
|
582 |
+
# Validate VPC
|
583 |
+
def validateVCP(self, data, screenDict, saveDict, stockName=None, window=3, percentageFromTop=3):
|
584 |
+
try:
|
585 |
+
percentageFromTop /= 100
|
586 |
+
data.reset_index(inplace=True)
|
587 |
+
data.rename(columns={'index':'Date'}, inplace=True)
|
588 |
+
data['tops'] = data['High'].iloc[list(argrelextrema(np.array(data['High']), np.greater_equal, order=window)[0])].head(4)
|
589 |
+
data['bots'] = data['Low'].iloc[list(argrelextrema(np.array(data['Low']), np.less_equal, order=window)[0])].head(4)
|
590 |
+
data = data.fillna(0)
|
591 |
+
data = data.replace([np.inf, -np.inf], 0)
|
592 |
+
tops = data[data.tops > 0]
|
593 |
+
bots = data[data.bots > 0]
|
594 |
+
highestTop = round(tops.describe()['High']['max'],1)
|
595 |
+
filteredTops = tops[tops.tops > (highestTop-(highestTop*percentageFromTop))]
|
596 |
+
# print(tops)
|
597 |
+
# print(filteredTops)
|
598 |
+
# print(tops.sort_values(by=['tops'], ascending=False))
|
599 |
+
# print(tops.describe())
|
600 |
+
# print(f"Till {highestTop-(highestTop*percentageFromTop)}")
|
601 |
+
if(filteredTops.equals(tops)): # Tops are in the range
|
602 |
+
lowPoints = []
|
603 |
+
for i in range(len(tops)-1):
|
604 |
+
endDate = tops.iloc[i]['Date']
|
605 |
+
startDate = tops.iloc[i+1]['Date']
|
606 |
+
lowPoints.append(data[(data.Date >= startDate) & (data.Date <= endDate)].describe()['Low']['min'])
|
607 |
+
lowPointsOrg = lowPoints
|
608 |
+
lowPoints.sort(reverse=True)
|
609 |
+
lowPointsSorted = lowPoints
|
610 |
+
ltp = data.head(1)['Close'].iloc[0]
|
611 |
+
if lowPointsOrg == lowPointsSorted and ltp < highestTop and ltp > lowPoints[0]:
|
612 |
+
screenDict['Pattern'] = colorText.BOLD + colorText.GREEN + f'VCP (BO: {highestTop})' + colorText.END
|
613 |
+
saveDict['Pattern'] = f'VCP (BO: {highestTop})'
|
614 |
+
return True
|
615 |
+
except Exception as e:
|
616 |
+
import traceback
|
617 |
+
print(traceback.format_exc())
|
618 |
+
return False
|
619 |
+
|
620 |
+
def getNiftyPrediction(self, data, proxyServer):
|
621 |
+
import warnings
|
622 |
+
warnings.filterwarnings("ignore")
|
623 |
+
# Disable GPUs as this causes wrong preds in Docker
|
624 |
+
import tensorflow as tf
|
625 |
+
physical_devices = tf.config.list_physical_devices('GPU')
|
626 |
+
try:
|
627 |
+
tf.config.set_visible_devices([], 'GPU')
|
628 |
+
visible_devices = tf.config.get_visible_devices()
|
629 |
+
for device in visible_devices:
|
630 |
+
assert device.device_type != 'GPU'
|
631 |
+
except:
|
632 |
+
pass
|
633 |
+
#
|
634 |
+
model, pkl = Utility.tools.getNiftyModel(proxyServer=proxyServer)
|
635 |
+
datacopy = copy(data[pkl['columns']])
|
636 |
+
with SuppressOutput(suppress_stderr=True, suppress_stdout=True):
|
637 |
+
data = data[pkl['columns']]
|
638 |
+
### v2 Preprocessing
|
639 |
+
for col in pkl['columns']:
|
640 |
+
data[col] = data[col].pct_change(fill_method=None) * 100
|
641 |
+
data = data.ffill().dropna()
|
642 |
+
data = data.iloc[-1]
|
643 |
+
###
|
644 |
+
data = pkl['scaler'].transform([data])
|
645 |
+
pred = model.predict(data)[0]
|
646 |
+
if pred > 0.5:
|
647 |
+
out = colorText.BOLD + colorText.FAIL + "BEARISH" + colorText.END + colorText.BOLD
|
648 |
+
sug = "Hold your Short position!"
|
649 |
+
else:
|
650 |
+
out = colorText.BOLD + colorText.GREEN + "BULLISH" + colorText.END + colorText.BOLD
|
651 |
+
sug = "Stay Bullish!"
|
652 |
+
if not Utility.tools.isClosingHour():
|
653 |
+
print(colorText.BOLD + colorText.WARN + "Note: The AI prediction should be executed After 3 PM Around the Closing hours as the Prediction Accuracy is based on the Closing price!" + colorText.END)
|
654 |
+
print(colorText.BOLD + colorText.BLUE + "\n" + "[+] Nifty AI Prediction -> " + colorText.END + colorText.BOLD + "Market may Open {} next day! {}".format(out, sug) + colorText.END)
|
655 |
+
print(colorText.BOLD + colorText.BLUE + "\n" + "[+] Nifty AI Prediction -> " + colorText.END + "Probability/Strength of Prediction = {}%".format(Utility.tools.getSigmoidConfidence(pred[0])))
|
656 |
+
if isGui():
|
657 |
+
return pred, 'BULLISH' if pred <= 0.5 else 'BEARISH', Utility.tools.getSigmoidConfidence(pred[0]), pd.DataFrame(datacopy.iloc[-1]).T
|
658 |
+
return pred
|
659 |
+
|
660 |
+
def monitorFiveEma(self, proxyServer, fetcher, result_df, last_signal, risk_reward = 3):
|
661 |
+
col_names = ['High', 'Low', 'Close', '5EMA']
|
662 |
+
data_list = ['nifty_buy', 'banknifty_buy', 'nifty_sell', 'banknifty_sell']
|
663 |
+
|
664 |
+
data_tuple = fetcher.fetchFiveEmaData()
|
665 |
+
for cnt in range(len(data_tuple)):
|
666 |
+
d = data_tuple[cnt]
|
667 |
+
d['5EMA'] = ScreenerTA.EMA(d['Close'],timeperiod=5)
|
668 |
+
d = d[col_names]
|
669 |
+
d = d.dropna().round(2)
|
670 |
+
|
671 |
+
with SuppressOutput(suppress_stderr=True, suppress_stdout=True):
|
672 |
+
if 'sell' in data_list[cnt]:
|
673 |
+
streched = d[(d.Low > d['5EMA']) & (d.Low - d['5EMA'] > 0.5)]
|
674 |
+
streched['SL'] = streched.High
|
675 |
+
validate = d[(d.Low.shift(1) > d['5EMA'].shift(1)) & (d.Low.shift(1) - d['5EMA'].shift(1) > 0.5)]
|
676 |
+
old_index = validate.index
|
677 |
+
else:
|
678 |
+
mask = (d.High < d['5EMA']) & (d['5EMA'] - d.High > 0.5) # Buy
|
679 |
+
streched = d[mask]
|
680 |
+
streched['SL'] = streched.Low
|
681 |
+
validate = d.loc[mask.shift(1).fillna(False)]
|
682 |
+
old_index = validate.index
|
683 |
+
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'])
|
684 |
+
validate = pd.concat([
|
685 |
+
validate.reset_index(drop=True),
|
686 |
+
streched['SL'].reset_index(drop=True),
|
687 |
+
tgt,
|
688 |
+
],
|
689 |
+
axis=1
|
690 |
+
)
|
691 |
+
validate = validate.tail(len(old_index))
|
692 |
+
validate = validate.set_index(old_index)
|
693 |
+
if 'sell' in data_list[cnt]:
|
694 |
+
final = validate[validate.Close < validate['5EMA']].tail(1)
|
695 |
+
else:
|
696 |
+
final = validate[validate.Close > validate['5EMA']].tail(1)
|
697 |
+
|
698 |
+
|
699 |
+
if data_list[cnt] not in last_signal:
|
700 |
+
last_signal[data_list[cnt]] = final
|
701 |
+
elif data_list[cnt] in last_signal:
|
702 |
+
try:
|
703 |
+
condition = last_signal[data_list[cnt]][0]['SL'].iloc[0]
|
704 |
+
except KeyError:
|
705 |
+
condition = last_signal[data_list[cnt]]['SL'].iloc[0]
|
706 |
+
# if last_signal[data_list[cnt]] is not final: # Debug - Shows all conditions
|
707 |
+
if condition != final['SL'].iloc[0]:
|
708 |
+
# Do something with results
|
709 |
+
try:
|
710 |
+
result_df = pd.concat([
|
711 |
+
result_df,
|
712 |
+
pd.DataFrame([
|
713 |
+
[
|
714 |
+
colorText.BLUE + str(final.index[0]) + colorText.END,
|
715 |
+
colorText.BOLD + colorText.WARN + data_list[cnt].split('_')[0].upper() + colorText.END,
|
716 |
+
(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),
|
717 |
+
colorText.FAIL + str(final.SL[0]) + colorText.END,
|
718 |
+
colorText.GREEN + str(final.Target[0]) + colorText.END,
|
719 |
+
f'1:{risk_reward}'
|
720 |
+
]
|
721 |
+
], columns=result_df.columns)
|
722 |
+
], axis=0)
|
723 |
+
result_df.reset_index(drop=True, inplace=True)
|
724 |
+
except Exception as e:
|
725 |
+
pass
|
726 |
+
# Then update
|
727 |
+
last_signal[data_list[cnt]] = [final]
|
728 |
+
result_df.drop_duplicates(keep='last', inplace=True)
|
729 |
+
result_df.sort_values(by='Time', inplace=True)
|
730 |
+
return result_df[::-1]
|
731 |
+
|
732 |
+
# Add data to vector database
|
733 |
+
def addVector(self, data, stockCode, daysToLookback):
|
734 |
+
data = data[::-1] # Reinverting preprocessedData for pct_change
|
735 |
+
data = data.pct_change(fill_method=None)
|
736 |
+
# data = data[::-1] # Do we need to invert again? No we dont - See operation after flatten
|
737 |
+
data = data[['Open', 'High', 'Low', 'Close']]
|
738 |
+
data = data.reset_index(drop=True)
|
739 |
+
data = data.dropna()
|
740 |
+
data = data.to_numpy().flatten().tolist()
|
741 |
+
data = data[(-4 * daysToLookback):] # Keep only OHLC * daysToLookback samples
|
742 |
+
if len(data) == (4 * daysToLookback):
|
743 |
+
chroma_client = chromadb.PersistentClient(path="./chromadb_store/")
|
744 |
+
collection = chroma_client.get_or_create_collection(name="nse_stocks")
|
745 |
+
collection.upsert(
|
746 |
+
embeddings=[data],
|
747 |
+
documents=[stockCode],
|
748 |
+
ids=[stockCode]
|
749 |
+
)
|
750 |
+
return data
|
751 |
+
|
752 |
+
|
753 |
+
'''
|
754 |
+
# Find out trend for days to lookback
|
755 |
+
def validateVCP(data, screenDict, saveDict, daysToLookback=ConfigManager.daysToLookback, stockName=None):
|
756 |
+
// De-index date
|
757 |
+
data.reset_index(inplace=True)
|
758 |
+
data.rename(columns={'index':'Date'}, inplace=True)
|
759 |
+
data = data.head(daysToLookback)
|
760 |
+
data = data[::-1]
|
761 |
+
data = data.set_index(np.arange(len(data)))
|
762 |
+
data = data.fillna(0)
|
763 |
+
data = data.replace([np.inf, -np.inf], 0)
|
764 |
+
data['tops'] = data['Close'].iloc[list(argrelextrema(np.array(data['Close']), np.greater_equal, order=3)[0])]
|
765 |
+
data['bots'] = data['Close'].iloc[list(argrelextrema(np.array(data['Close']), np.less_equal, order=3)[0])]
|
766 |
+
try:
|
767 |
+
try:
|
768 |
+
top_slope,top_c = np.polyfit(data.index[data.tops > 0], data['tops'][data.tops > 0], 1)
|
769 |
+
bot_slope,bot_c = np.polyfit(data.index[data.bots > 0], data['bots'][data.bots > 0], 1)
|
770 |
+
topAngle = math.degrees(math.atan(top_slope))
|
771 |
+
vcpAngle = math.degrees(math.atan(bot_slope) - math.atan(top_slope))
|
772 |
+
|
773 |
+
# print(math.degrees(math.atan(top_slope)))
|
774 |
+
# print(math.degrees(math.atan(bot_slope)))
|
775 |
+
# print(vcpAngle)
|
776 |
+
# print(topAngle)
|
777 |
+
# print(data.max()['bots'])
|
778 |
+
# print(data.max()['tops'])
|
779 |
+
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):
|
780 |
+
print("---> GOOD VCP %s at %sRs" % (stockName, top_c))
|
781 |
+
import os
|
782 |
+
os.system("echo %s >> vcp_plots\VCP.txt" % stockName)
|
783 |
+
|
784 |
+
import matplotlib.pyplot as plt
|
785 |
+
plt.scatter(data.index[data.tops > 0], data['tops'][data.tops > 0], c='g')
|
786 |
+
plt.scatter(data.index[data.bots > 0], data['bots'][data.bots > 0], c='r')
|
787 |
+
plt.plot(data.index, data['Close'])
|
788 |
+
plt.plot(data.index, top_slope*data.index+top_c,'g--')
|
789 |
+
plt.plot(data.index, bot_slope*data.index+bot_c,'r--')
|
790 |
+
if stockName != None:
|
791 |
+
plt.title(stockName)
|
792 |
+
# plt.show()
|
793 |
+
plt.savefig('vcp_plots\%s.png' % stockName)
|
794 |
+
plt.clf()
|
795 |
+
except np.RankWarning:
|
796 |
+
pass
|
797 |
+
except np.linalg.LinAlgError:
|
798 |
+
return False
|
799 |
+
'''
|
800 |
+
|
src/classes/ScreenipyTA.py
ADDED
@@ -0,0 +1,269 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import numpy as np
|
2 |
+
import os
|
3 |
+
|
4 |
+
if 'STREAMLIT_APP' in os.environ:
|
5 |
+
import pandas_ta as talib
|
6 |
+
print('[+] Importing pandas_ta as we are running on Streamlit cloud app')
|
7 |
+
else:
|
8 |
+
try:
|
9 |
+
import talib
|
10 |
+
except ImportError:
|
11 |
+
import pandas_ta as talib
|
12 |
+
|
13 |
+
|
14 |
+
class ScreenerTA:
|
15 |
+
|
16 |
+
@staticmethod
|
17 |
+
def EMA(close, timeperiod):
|
18 |
+
try:
|
19 |
+
return talib.ema(close,timeperiod)
|
20 |
+
except Exception as e:
|
21 |
+
return talib.EMA(close,timeperiod)
|
22 |
+
|
23 |
+
@staticmethod
|
24 |
+
def SMA(close, timeperiod):
|
25 |
+
try:
|
26 |
+
return talib.sma(close,timeperiod)
|
27 |
+
except Exception as e:
|
28 |
+
return talib.SMA(close,timeperiod)
|
29 |
+
|
30 |
+
@staticmethod
|
31 |
+
def MA(close, timeperiod):
|
32 |
+
try:
|
33 |
+
return talib.ma(close,timeperiod)
|
34 |
+
except Exception as e:
|
35 |
+
return talib.MA(close,timeperiod)
|
36 |
+
|
37 |
+
@staticmethod
|
38 |
+
def MACD(close, fast, slow, signal):
|
39 |
+
try:
|
40 |
+
return talib.macd(close,fast,slow,signal)
|
41 |
+
except Exception as e:
|
42 |
+
return talib.MACD(close,fast,slow,signal)
|
43 |
+
|
44 |
+
@staticmethod
|
45 |
+
def RSI(close, timeperiod):
|
46 |
+
try:
|
47 |
+
return talib.rsi(close,timeperiod)
|
48 |
+
except Exception as e:
|
49 |
+
return talib.RSI(close,timeperiod)
|
50 |
+
|
51 |
+
@staticmethod
|
52 |
+
def CCI(high, low, close, timeperiod):
|
53 |
+
try:
|
54 |
+
return talib.cci(high, low, close,timeperiod)
|
55 |
+
except Exception as e:
|
56 |
+
return talib.CCI(high, low, close,timeperiod)
|
57 |
+
|
58 |
+
|
59 |
+
@staticmethod
|
60 |
+
def CDLMORNINGSTAR(open, high, low, close):
|
61 |
+
try:
|
62 |
+
try:
|
63 |
+
return talib.cdl_pattern(open,high,low,close,'morningstar').tail(1).values[0][0] != 0
|
64 |
+
except Exception as e:
|
65 |
+
return talib.CDLMORNINGSTAR(open,high,low,close).tail(1).item() != 0
|
66 |
+
except AttributeError:
|
67 |
+
return False
|
68 |
+
|
69 |
+
@staticmethod
|
70 |
+
def CDLMORNINGDOJISTAR(open, high, low, close):
|
71 |
+
try:
|
72 |
+
try:
|
73 |
+
return talib.cdl_pattern(open,high,low,close,'morningdojistar').tail(1).values[0][0] != 0
|
74 |
+
except Exception as e:
|
75 |
+
return talib.CDLMORNINGDOJISTAR(open,high,low,close).tail(1).item() != 0
|
76 |
+
except AttributeError:
|
77 |
+
return False
|
78 |
+
|
79 |
+
@staticmethod
|
80 |
+
def CDLEVENINGSTAR(open, high, low, close):
|
81 |
+
try:
|
82 |
+
try:
|
83 |
+
return talib.cdl_pattern(open,high,low,close,'eveningstar').tail(1).values[0][0] != 0
|
84 |
+
except Exception as e:
|
85 |
+
return talib.CDLEVENINGSTAR(open,high,low,close).tail(1).item() != 0
|
86 |
+
except AttributeError:
|
87 |
+
return False
|
88 |
+
|
89 |
+
@staticmethod
|
90 |
+
def CDLEVENINGDOJISTAR(open, high, low, close):
|
91 |
+
try:
|
92 |
+
try:
|
93 |
+
return talib.cdl_pattern(open,high,low,close,'eveningdojistar').tail(1).values[0][0] != 0
|
94 |
+
except Exception as e:
|
95 |
+
return talib.CDLEVENINGDOJISTAR(open,high,low,close).tail(1).item() != 0
|
96 |
+
except AttributeError:
|
97 |
+
return False
|
98 |
+
|
99 |
+
@staticmethod
|
100 |
+
def CDLLADDERBOTTOM(open, high, low, close):
|
101 |
+
try:
|
102 |
+
try:
|
103 |
+
return talib.cdl_pattern(open,high,low,close,'ladderbottom').tail(1).values[0][0] != 0
|
104 |
+
except Exception as e:
|
105 |
+
return talib.CDLLADDERBOTTOM(open,high,low,close).tail(1).item() != 0
|
106 |
+
except AttributeError:
|
107 |
+
return False
|
108 |
+
|
109 |
+
@staticmethod
|
110 |
+
def CDL3LINESTRIKE(open, high, low, close):
|
111 |
+
try:
|
112 |
+
try:
|
113 |
+
return talib.cdl_pattern(open,high,low,close,'3linestrike').tail(1).values[0][0] != 0
|
114 |
+
except Exception as e:
|
115 |
+
return talib.CDL3LINESTRIKE(open,high,low,close).tail(1).item() != 0
|
116 |
+
except AttributeError:
|
117 |
+
return False
|
118 |
+
|
119 |
+
@staticmethod
|
120 |
+
def CDL3BLACKCROWS(open, high, low, close):
|
121 |
+
try:
|
122 |
+
try:
|
123 |
+
return talib.cdl_pattern(open,high,low,close,'3blackcrows').tail(1).values[0][0] != 0
|
124 |
+
except Exception as e:
|
125 |
+
return talib.CDL3BLACKCROWS(open,high,low,close).tail(1).item() != 0
|
126 |
+
except AttributeError:
|
127 |
+
return False
|
128 |
+
|
129 |
+
@staticmethod
|
130 |
+
def CDL3INSIDE(open, high, low, close):
|
131 |
+
try:
|
132 |
+
try:
|
133 |
+
return talib.cdl_pattern(open,high,low,close,'3inside').tail(1).values[0][0] != 0
|
134 |
+
except Exception as e:
|
135 |
+
return talib.CDL3INSIDE(open,high,low,close).tail(1).item() != 0
|
136 |
+
except AttributeError:
|
137 |
+
return False
|
138 |
+
|
139 |
+
@staticmethod
|
140 |
+
def CDL3OUTSIDE(open, high, low, close):
|
141 |
+
try:
|
142 |
+
try:
|
143 |
+
return talib.cdl_pattern(open,high,low,close,'3outside').tail(1).values[0][0]
|
144 |
+
except Exception as e:
|
145 |
+
return talib.CDL3OUTSIDE(open,high,low,close).tail(1).item() != 0
|
146 |
+
except AttributeError:
|
147 |
+
return False
|
148 |
+
|
149 |
+
@staticmethod
|
150 |
+
def CDL3WHITESOLDIERS(open, high, low, close):
|
151 |
+
try:
|
152 |
+
try:
|
153 |
+
return talib.cdl_pattern(open,high,low,close,'3whitesoldiers').tail(1).values[0][0] != 0
|
154 |
+
except Exception as e:
|
155 |
+
return talib.CDL3WHITESOLDIERS(open,high,low,close).tail(1).item() != 0
|
156 |
+
except AttributeError:
|
157 |
+
return False
|
158 |
+
|
159 |
+
@staticmethod
|
160 |
+
def CDLHARAMI(open, high, low, close):
|
161 |
+
try:
|
162 |
+
try:
|
163 |
+
return talib.cdl_pattern(open,high,low,close,'harami').tail(1).values[0][0] != 0
|
164 |
+
except Exception as e:
|
165 |
+
return talib.CDLHARAMI(open,high,low,close).tail(1).item() != 0
|
166 |
+
except AttributeError:
|
167 |
+
return False
|
168 |
+
|
169 |
+
@staticmethod
|
170 |
+
def CDLHARAMICROSS(open, high, low, close):
|
171 |
+
try:
|
172 |
+
try:
|
173 |
+
return talib.cdl_pattern(open,high,low,close,'haramicross').tail(1).values[0][0] != 0
|
174 |
+
except Exception as e:
|
175 |
+
return talib.CDLHARAMICROSS(open,high,low,close).tail(1).item() != 0
|
176 |
+
except AttributeError:
|
177 |
+
return False
|
178 |
+
|
179 |
+
@staticmethod
|
180 |
+
def CDLMARUBOZU(open, high, low, close):
|
181 |
+
try:
|
182 |
+
try:
|
183 |
+
return talib.cdl_pattern(open,high,low,close,'marubozu').tail(1).values[0][0] != 0
|
184 |
+
except Exception as e:
|
185 |
+
return talib.CDLMARUBOZU(open,high,low,close).tail(1).item() != 0
|
186 |
+
except AttributeError:
|
187 |
+
return False
|
188 |
+
|
189 |
+
@staticmethod
|
190 |
+
def CDLHANGINGMAN(open, high, low, close):
|
191 |
+
try:
|
192 |
+
try:
|
193 |
+
return talib.cdl_pattern(open,high,low,close,'hangingman').tail(1).values[0][0] != 0
|
194 |
+
except Exception as e:
|
195 |
+
return talib.CDLHANGINGMAN(open,high,low,close).tail(1).item() != 0
|
196 |
+
except AttributeError:
|
197 |
+
return False
|
198 |
+
|
199 |
+
@staticmethod
|
200 |
+
def CDLHAMMER(open, high, low, close):
|
201 |
+
try:
|
202 |
+
try:
|
203 |
+
return talib.cdl_pattern(open,high,low,close,'hammer').tail(1).values[0][0] != 0
|
204 |
+
except Exception as e:
|
205 |
+
return talib.CDLHAMMER(open,high,low,close).tail(1).item() != 0
|
206 |
+
except AttributeError:
|
207 |
+
return False
|
208 |
+
|
209 |
+
@staticmethod
|
210 |
+
def CDLINVERTEDHAMMER(open, high, low, close):
|
211 |
+
try:
|
212 |
+
try:
|
213 |
+
return talib.cdl_pattern(open,high,low,close,'invertedhammer').tail(1).values[0][0] != 0
|
214 |
+
except Exception as e:
|
215 |
+
return talib.CDLINVERTEDHAMMER(open,high,low,close).tail(1).item() != 0
|
216 |
+
except AttributeError:
|
217 |
+
return False
|
218 |
+
|
219 |
+
@staticmethod
|
220 |
+
def CDLSHOOTINGSTAR(open, high, low, close):
|
221 |
+
try:
|
222 |
+
try:
|
223 |
+
return talib.cdl_pattern(open,high,low,close,'shootingstar').tail(1).values[0][0] != 0
|
224 |
+
except Exception as e:
|
225 |
+
return talib.CDLSHOOTINGSTAR(open,high,low,close).tail(1).item() != 0
|
226 |
+
except AttributeError:
|
227 |
+
return False
|
228 |
+
|
229 |
+
@staticmethod
|
230 |
+
def CDLDRAGONFLYDOJI(open, high, low, close):
|
231 |
+
try:
|
232 |
+
try:
|
233 |
+
return talib.cdl_pattern(open,high,low,close,'dragonflydoji').tail(1).values[0][0] != 0
|
234 |
+
except Exception as e:
|
235 |
+
return talib.CDLDRAGONFLYDOJI(open,high,low,close).tail(1).item() != 0
|
236 |
+
except AttributeError:
|
237 |
+
return False
|
238 |
+
|
239 |
+
@staticmethod
|
240 |
+
def CDLGRAVESTONEDOJI(open, high, low, close):
|
241 |
+
try:
|
242 |
+
try:
|
243 |
+
return talib.cdl_pattern(open,high,low,close,'gravestonedoji').tail(1).values[0][0] != 0
|
244 |
+
except Exception as e:
|
245 |
+
return talib.CDLGRAVESTONEDOJI(open,high,low,close).tail(1).item() != 0
|
246 |
+
except AttributeError:
|
247 |
+
return False
|
248 |
+
|
249 |
+
@staticmethod
|
250 |
+
def CDLDOJI(open, high, low, close):
|
251 |
+
try:
|
252 |
+
try:
|
253 |
+
return talib.cdl_pattern(open,high,low,close,'doji').tail(1).values[0][0] != 0
|
254 |
+
except Exception as e:
|
255 |
+
return talib.CDLDOJI(open,high,low,close).tail(1).item() != 0
|
256 |
+
except AttributeError:
|
257 |
+
return False
|
258 |
+
|
259 |
+
|
260 |
+
@staticmethod
|
261 |
+
def CDLENGULFING(open, high, low, close):
|
262 |
+
try:
|
263 |
+
try:
|
264 |
+
return talib.cdl_pattern(open,high,low,close,'engulfing').tail(1).values[0][0]
|
265 |
+
except Exception as e:
|
266 |
+
return talib.CDLENGULFING(open,high,low,close).tail(1).item() != 0
|
267 |
+
except AttributeError:
|
268 |
+
return False
|
269 |
+
|
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,417 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
'''
|
2 |
+
* Project : Screenipy
|
3 |
+
* Author : Pranjal Joshi
|
4 |
+
* Created : 28/04/2021
|
5 |
+
* Description : Class for managing misc and utility methods
|
6 |
+
'''
|
7 |
+
|
8 |
+
import os
|
9 |
+
import sys
|
10 |
+
import platform
|
11 |
+
import datetime
|
12 |
+
import pytz
|
13 |
+
import pickle
|
14 |
+
import requests
|
15 |
+
import time
|
16 |
+
import joblib
|
17 |
+
import keras
|
18 |
+
import pandas as pd
|
19 |
+
from alive_progress import alive_bar
|
20 |
+
from tabulate import tabulate
|
21 |
+
from time import sleep
|
22 |
+
from classes.ColorText import colorText
|
23 |
+
from classes.Changelog import VERSION, changelog
|
24 |
+
import classes.ConfigManager as ConfigManager
|
25 |
+
|
26 |
+
art = colorText.GREEN + '''
|
27 |
+
.d8888b. d8b
|
28 |
+
d88P Y88b Y8P
|
29 |
+
Y88b.
|
30 |
+
"Y888b. .d8888b 888d888 .d88b. .d88b. 88888b. 888 88888b. 888 888
|
31 |
+
"Y88b. d88P" 888P" d8P Y8b d8P Y8b 888 "88b 888 888 "88b 888 888
|
32 |
+
"888 888 888 88888888 88888888 888 888 888 888 888 888 888
|
33 |
+
Y88b d88P Y88b. 888 Y8b. Y8b. 888 888 888 888 d88P Y88b 888
|
34 |
+
"Y8888P" "Y8888P 888 "Y8888 "Y8888 888 888 888 88888P" "Y88888
|
35 |
+
888 888
|
36 |
+
888 Y8b d88P
|
37 |
+
888 "Y88P"
|
38 |
+
|
39 |
+
''' + colorText.END
|
40 |
+
|
41 |
+
lastScreened = 'last_screened_results.pkl'
|
42 |
+
lastScreenedUnformatted = 'last_screened_unformatted_results.pkl'
|
43 |
+
|
44 |
+
# Class for managing misc and utility methods
|
45 |
+
|
46 |
+
|
47 |
+
class tools:
|
48 |
+
|
49 |
+
def clearScreen():
|
50 |
+
if platform.system() == 'Windows':
|
51 |
+
os.system('cls')
|
52 |
+
else:
|
53 |
+
os.system('clear')
|
54 |
+
print(art)
|
55 |
+
|
56 |
+
# Print about developers and repository
|
57 |
+
def showDevInfo():
|
58 |
+
print('\n'+changelog)
|
59 |
+
print(colorText.BOLD + colorText.WARN +
|
60 |
+
"\n[+] Developer: Pranjal Joshi." + colorText.END)
|
61 |
+
print(colorText.BOLD + colorText.WARN +
|
62 |
+
("[+] Version: %s" % VERSION) + colorText.END)
|
63 |
+
print(colorText.BOLD +
|
64 |
+
"[+] Home Page: https://github.com/pranjal-joshi/Screeni-py" + colorText.END)
|
65 |
+
print(colorText.BOLD + colorText.FAIL +
|
66 |
+
"[+] Read/Post Issues here: https://github.com/pranjal-joshi/Screeni-py/issues" + colorText.END)
|
67 |
+
print(colorText.BOLD + colorText.GREEN +
|
68 |
+
"[+] Join Community Discussions: https://github.com/pranjal-joshi/Screeni-py/discussions" + colorText.END)
|
69 |
+
print(colorText.BOLD + colorText.BLUE +
|
70 |
+
"[+] Download latest software from https://github.com/pranjal-joshi/Screeni-py/releases/latest" + colorText.END)
|
71 |
+
input('')
|
72 |
+
|
73 |
+
# Save last screened result to pickle file
|
74 |
+
def setLastScreenedResults(df, unformatted=False):
|
75 |
+
try:
|
76 |
+
if not unformatted:
|
77 |
+
df.sort_values(by=['Stock'], ascending=True, inplace=True)
|
78 |
+
df.to_pickle(lastScreened)
|
79 |
+
else:
|
80 |
+
df.sort_values(by=['Stock'], ascending=True, inplace=True)
|
81 |
+
df.to_pickle(lastScreenedUnformatted)
|
82 |
+
except IOError:
|
83 |
+
print(colorText.BOLD + colorText.FAIL +
|
84 |
+
'[+] Failed to save recently screened result table on disk! Skipping..' + colorText.END)
|
85 |
+
|
86 |
+
# Load last screened result to pickle file
|
87 |
+
def getLastScreenedResults():
|
88 |
+
try:
|
89 |
+
df = pd.read_pickle(lastScreened)
|
90 |
+
print(colorText.BOLD + colorText.GREEN +
|
91 |
+
'\n[+] Showing recently screened results..\n' + colorText.END)
|
92 |
+
print(tabulate(df, headers='keys', tablefmt='psql'))
|
93 |
+
print(colorText.BOLD + colorText.WARN +
|
94 |
+
"[+] Note: Trend calculation is based on number of recent days to screen as per your configuration." + colorText.END)
|
95 |
+
input(colorText.BOLD + colorText.GREEN +
|
96 |
+
'[+] Press any key to continue..' + colorText.END)
|
97 |
+
except FileNotFoundError:
|
98 |
+
print(colorText.BOLD + colorText.FAIL +
|
99 |
+
'[+] Failed to load recently screened result table from disk! Skipping..' + colorText.END)
|
100 |
+
|
101 |
+
def isTradingTime():
|
102 |
+
curr = datetime.datetime.now(pytz.timezone('Asia/Kolkata'))
|
103 |
+
openTime = curr.replace(hour=9, minute=15)
|
104 |
+
closeTime = curr.replace(hour=15, minute=30)
|
105 |
+
return ((openTime <= curr <= closeTime) and (0 <= curr.weekday() <= 4))
|
106 |
+
|
107 |
+
def isClosingHour():
|
108 |
+
curr = datetime.datetime.now(pytz.timezone('Asia/Kolkata'))
|
109 |
+
openTime = curr.replace(hour=15, minute=00)
|
110 |
+
closeTime = curr.replace(hour=15, minute=30)
|
111 |
+
return ((openTime <= curr <= closeTime) and (0 <= curr.weekday() <= 4))
|
112 |
+
|
113 |
+
def saveStockData(stockDict, configManager, loadCount):
|
114 |
+
curr = datetime.datetime.now(pytz.timezone('Asia/Kolkata'))
|
115 |
+
openTime = curr.replace(hour=9, minute=15)
|
116 |
+
cache_date = datetime.date.today() # for monday to friday
|
117 |
+
weekday = datetime.date.today().weekday()
|
118 |
+
if curr < openTime: # for monday to friday before 9:15
|
119 |
+
cache_date = datetime.datetime.today() - datetime.timedelta(1)
|
120 |
+
if weekday == 0 and curr < openTime: # for monday before 9:15
|
121 |
+
cache_date = datetime.datetime.today() - datetime.timedelta(3)
|
122 |
+
if weekday == 5 or weekday == 6: # for saturday and sunday
|
123 |
+
cache_date = datetime.datetime.today() - datetime.timedelta(days=weekday - 4)
|
124 |
+
cache_date = cache_date.strftime("%d%m%y")
|
125 |
+
cache_file = "stock_data_" + str(cache_date) + ".pkl"
|
126 |
+
configManager.deleteStockData(excludeFile=cache_file)
|
127 |
+
|
128 |
+
if not os.path.exists(cache_file) or len(stockDict) > (loadCount+1):
|
129 |
+
with open(cache_file, 'wb') as f:
|
130 |
+
try:
|
131 |
+
pickle.dump(stockDict.copy(), f)
|
132 |
+
print(colorText.BOLD + colorText.GREEN +
|
133 |
+
"=> Done." + colorText.END)
|
134 |
+
except pickle.PicklingError:
|
135 |
+
print(colorText.BOLD + colorText.FAIL +
|
136 |
+
"=> Error while Caching Stock Data." + colorText.END)
|
137 |
+
else:
|
138 |
+
print(colorText.BOLD + colorText.GREEN +
|
139 |
+
"=> Already Cached." + colorText.END)
|
140 |
+
|
141 |
+
def loadStockData(stockDict, configManager, proxyServer=None):
|
142 |
+
curr = datetime.datetime.now(pytz.timezone('Asia/Kolkata'))
|
143 |
+
openTime = curr.replace(hour=9, minute=15)
|
144 |
+
last_cached_date = datetime.date.today() # for monday to friday after 3:30
|
145 |
+
weekday = datetime.date.today().weekday()
|
146 |
+
if curr < openTime: # for monday to friday before 9:15
|
147 |
+
last_cached_date = datetime.datetime.today() - datetime.timedelta(1)
|
148 |
+
if weekday == 5 or weekday == 6: # for saturday and sunday
|
149 |
+
last_cached_date = datetime.datetime.today() - datetime.timedelta(days=weekday - 4)
|
150 |
+
if weekday == 0 and curr < openTime: # for monday before 9:15
|
151 |
+
last_cached_date = datetime.datetime.today() - datetime.timedelta(3)
|
152 |
+
last_cached_date = last_cached_date.strftime("%d%m%y")
|
153 |
+
cache_file = "stock_data_" + str(last_cached_date) + ".pkl"
|
154 |
+
if os.path.exists(cache_file):
|
155 |
+
with open(cache_file, 'rb') as f:
|
156 |
+
try:
|
157 |
+
stockData = pickle.load(f)
|
158 |
+
print(colorText.BOLD + colorText.GREEN +
|
159 |
+
"[+] Automatically Using Cached Stock Data due to After-Market hours!" + colorText.END)
|
160 |
+
for stock in stockData:
|
161 |
+
stockDict[stock] = stockData.get(stock)
|
162 |
+
except pickle.UnpicklingError:
|
163 |
+
print(colorText.BOLD + colorText.FAIL +
|
164 |
+
"[+] Error while Reading Stock Cache." + colorText.END)
|
165 |
+
except EOFError:
|
166 |
+
print(colorText.BOLD + colorText.FAIL +
|
167 |
+
"[+] Stock Cache Corrupted." + colorText.END)
|
168 |
+
elif ConfigManager.default_period == configManager.period and ConfigManager.default_duration == configManager.duration:
|
169 |
+
cache_url = "https://raw.github.com/pranjal-joshi/Screeni-py/actions-data-download/actions-data-download/" + cache_file
|
170 |
+
if proxyServer is not None:
|
171 |
+
resp = requests.get(cache_url, stream=True, proxies={'https':proxyServer})
|
172 |
+
else:
|
173 |
+
resp = requests.get(cache_url, stream=True)
|
174 |
+
if resp.status_code == 200:
|
175 |
+
print(colorText.BOLD + colorText.FAIL +
|
176 |
+
"[+] After-Market Stock Data is not cached.." + colorText.END)
|
177 |
+
print(colorText.BOLD + colorText.GREEN +
|
178 |
+
"[+] Downloading cache from Screenipy server for faster processing, Please Wait.." + colorText.END)
|
179 |
+
try:
|
180 |
+
chunksize = 1024*1024*1
|
181 |
+
filesize = int(int(resp.headers.get('content-length'))/chunksize)
|
182 |
+
bar, spinner = tools.getProgressbarStyle()
|
183 |
+
f = open(cache_file, 'wb')
|
184 |
+
dl = 0
|
185 |
+
with alive_bar(filesize, bar=bar, spinner=spinner, manual=True) as progressbar:
|
186 |
+
for data in resp.iter_content(chunk_size=chunksize):
|
187 |
+
dl += 1
|
188 |
+
f.write(data)
|
189 |
+
progressbar(dl/filesize)
|
190 |
+
if dl >= filesize:
|
191 |
+
progressbar(1.0)
|
192 |
+
f.close()
|
193 |
+
except Exception as e:
|
194 |
+
print("[!] Download Error - " + str(e))
|
195 |
+
print("")
|
196 |
+
tools.loadStockData(stockDict, configManager, proxyServer)
|
197 |
+
else:
|
198 |
+
print(colorText.BOLD + colorText.FAIL +
|
199 |
+
"[+] Cache unavailable on Screenipy server, Continuing.." + colorText.END)
|
200 |
+
|
201 |
+
# Save screened results to excel
|
202 |
+
def promptSaveResults(df):
|
203 |
+
if isDocker() or isGui(): # Skip export to excel inside docker
|
204 |
+
return
|
205 |
+
try:
|
206 |
+
response = str(input(colorText.BOLD + colorText.WARN +
|
207 |
+
'[>] Do you want to save the results in excel file? [Y/N]: ')).upper()
|
208 |
+
except ValueError:
|
209 |
+
response = 'Y'
|
210 |
+
if response != 'N':
|
211 |
+
filename = 'screenipy-result_' + \
|
212 |
+
datetime.datetime.now().strftime("%d-%m-%y_%H.%M.%S")+".xlsx"
|
213 |
+
df.to_excel(filename)
|
214 |
+
print(colorText.BOLD + colorText.GREEN +
|
215 |
+
("[+] Results saved to %s" % filename) + colorText.END)
|
216 |
+
|
217 |
+
# Prompt for asking RSI
|
218 |
+
def promptRSIValues():
|
219 |
+
try:
|
220 |
+
minRSI, maxRSI = int(input(colorText.BOLD + colorText.WARN + "\n[+] Enter Min RSI value: " + colorText.END)), int(
|
221 |
+
input(colorText.BOLD + colorText.WARN + "[+] Enter Max RSI value: " + colorText.END))
|
222 |
+
if (minRSI >= 0 and minRSI <= 100) and (maxRSI >= 0 and maxRSI <= 100) and (minRSI <= maxRSI):
|
223 |
+
return (minRSI, maxRSI)
|
224 |
+
raise ValueError
|
225 |
+
except ValueError:
|
226 |
+
return (0, 0)
|
227 |
+
|
228 |
+
# Prompt for Reversal screening
|
229 |
+
def promptReversalScreening():
|
230 |
+
try:
|
231 |
+
resp = int(input(colorText.BOLD + colorText.WARN + """\n[+] Select Option:
|
232 |
+
1 > Screen for Buy Signal (Bullish Reversal)
|
233 |
+
2 > Screen for Sell Signal (Bearish Reversal)
|
234 |
+
3 > Screen for Momentum Gainers (Rising Bullish Momentum)
|
235 |
+
4 > Screen for Reversal at Moving Average (Bullish Reversal)
|
236 |
+
5 > Screen for Volume Spread Analysis (Bullish VSA Reversal)
|
237 |
+
6 > Screen for Narrow Range (NRx) Reversal
|
238 |
+
7 > Screen for Reversal using Lorentzian Classifier (Machine Learning based indicator)
|
239 |
+
8 > Screen for Reversal using RSI MA Crossing
|
240 |
+
0 > Cancel
|
241 |
+
[+] Select option: """ + colorText.END))
|
242 |
+
if resp >= 0 and resp <= 8:
|
243 |
+
if resp == 4:
|
244 |
+
try:
|
245 |
+
maLength = int(input(colorText.BOLD + colorText.WARN +
|
246 |
+
'\n[+] Enter MA Length (E.g. 50 or 200): ' + colorText.END))
|
247 |
+
return resp, maLength
|
248 |
+
except ValueError:
|
249 |
+
print(colorText.BOLD + colorText.FAIL +
|
250 |
+
'\n[!] Invalid Input! MA Lenght should be single integer value!\n' + colorText.END)
|
251 |
+
raise ValueError
|
252 |
+
elif resp == 6:
|
253 |
+
try:
|
254 |
+
maLength = int(input(colorText.BOLD + colorText.WARN +
|
255 |
+
'\n[+] Enter NR timeframe [Integer Number] (E.g. 4, 7, etc.): ' + colorText.END))
|
256 |
+
return resp, maLength
|
257 |
+
except ValueError:
|
258 |
+
print(colorText.BOLD + colorText.FAIL + '\n[!] Invalid Input! NR timeframe should be single integer value!\n' + colorText.END)
|
259 |
+
raise ValueError
|
260 |
+
elif resp == 7:
|
261 |
+
try:
|
262 |
+
return resp, 1
|
263 |
+
except ValueError:
|
264 |
+
print(colorText.BOLD + colorText.FAIL + '\n[!] Invalid Input! Select valid Signal Type!\n' + colorText.END)
|
265 |
+
raise ValueError
|
266 |
+
elif resp == 8:
|
267 |
+
maLength = 9
|
268 |
+
return resp, maLength
|
269 |
+
return resp, None
|
270 |
+
raise ValueError
|
271 |
+
except ValueError:
|
272 |
+
return None, None
|
273 |
+
|
274 |
+
# Prompt for Reversal screening
|
275 |
+
def promptChartPatterns():
|
276 |
+
try:
|
277 |
+
resp = int(input(colorText.BOLD + colorText.WARN + """\n[+] Select Option:
|
278 |
+
1 > Screen for Bullish Inside Bar (Flag) Pattern
|
279 |
+
2 > Screen for Bearish Inside Bar (Flag) Pattern
|
280 |
+
3 > Screen for the Confluence (50 & 200 MA/EMA)
|
281 |
+
4 > Screen for VCP (Experimental)
|
282 |
+
5 > Screen for Buying at Trendline (Ideal for Swing/Mid/Long term)
|
283 |
+
0 > Cancel
|
284 |
+
[+] Select option: """ + colorText.END))
|
285 |
+
if resp == 1 or resp == 2:
|
286 |
+
candles = int(input(colorText.BOLD + colorText.WARN +
|
287 |
+
"\n[+] How many candles (TimeFrame) to look back Inside Bar formation? : " + colorText.END))
|
288 |
+
return (resp, candles)
|
289 |
+
if resp == 3:
|
290 |
+
percent = float(input(colorText.BOLD + colorText.WARN +
|
291 |
+
"\n[+] Enter Percentage within which all MA/EMAs should be (Ideal: 1-2%)? : " + colorText.END))
|
292 |
+
return (resp, percent/100.0)
|
293 |
+
if resp >= 0 and resp <= 5:
|
294 |
+
return resp, 0
|
295 |
+
raise ValueError
|
296 |
+
except ValueError:
|
297 |
+
input(colorText.BOLD + colorText.FAIL +
|
298 |
+
"\n[+] Invalid Option Selected. Press Any Key to Continue..." + colorText.END)
|
299 |
+
return (None, None)
|
300 |
+
|
301 |
+
# Prompt for Similar stock search
|
302 |
+
def promptSimilarStockSearch():
|
303 |
+
try:
|
304 |
+
stockCode = str(input(colorText.BOLD + colorText.WARN +
|
305 |
+
"\n[+] Enter the Name of the stock to search similar stocks for: " + colorText.END)).upper()
|
306 |
+
candles = int(input(colorText.BOLD + colorText.WARN +
|
307 |
+
"\n[+] How many candles (TimeFrame) to look back for similarity? : " + colorText.END))
|
308 |
+
return stockCode, candles
|
309 |
+
except ValueError:
|
310 |
+
input(colorText.BOLD + colorText.FAIL +
|
311 |
+
"\n[+] Invalid Option Selected. Press Any Key to Continue..." + colorText.END)
|
312 |
+
return None, None
|
313 |
+
|
314 |
+
def getProgressbarStyle():
|
315 |
+
bar = 'smooth'
|
316 |
+
spinner = 'waves'
|
317 |
+
if 'Windows' in platform.platform():
|
318 |
+
bar = 'classic2'
|
319 |
+
spinner = 'dots_recur'
|
320 |
+
return bar, spinner
|
321 |
+
|
322 |
+
def getNiftyModel(proxyServer=None):
|
323 |
+
files = ['nifty_model_v3.h5', 'nifty_model_v3.pkl']
|
324 |
+
urls = [
|
325 |
+
f"https://raw.github.com/pranjal-joshi/Screeni-py/new-features/src/ml/{files[0]}",
|
326 |
+
f"https://raw.github.com/pranjal-joshi/Screeni-py/new-features/src/ml/{files[1]}"
|
327 |
+
]
|
328 |
+
if os.path.isfile(files[0]) and os.path.isfile(files[1]):
|
329 |
+
file_age = (time.time() - os.path.getmtime(files[0]))/604800
|
330 |
+
if file_age > 1:
|
331 |
+
download = True
|
332 |
+
os.remove(files[0])
|
333 |
+
os.remove(files[1])
|
334 |
+
else:
|
335 |
+
download = False
|
336 |
+
else:
|
337 |
+
download = True
|
338 |
+
if download:
|
339 |
+
for file_url in urls:
|
340 |
+
if proxyServer is not None:
|
341 |
+
resp = requests.get(file_url, stream=True, proxies={'https':proxyServer})
|
342 |
+
else:
|
343 |
+
resp = requests.get(file_url, stream=True)
|
344 |
+
if resp.status_code == 200:
|
345 |
+
print(colorText.BOLD + colorText.GREEN +
|
346 |
+
"[+] Downloading AI model (v3) for Nifty predictions, Please Wait.." + colorText.END)
|
347 |
+
try:
|
348 |
+
chunksize = 1024*1024*1
|
349 |
+
filesize = int(int(resp.headers.get('content-length'))/chunksize)
|
350 |
+
filesize = 1 if not filesize else filesize
|
351 |
+
bar, spinner = tools.getProgressbarStyle()
|
352 |
+
f = open(file_url.split('/')[-1], 'wb')
|
353 |
+
dl = 0
|
354 |
+
with alive_bar(filesize, bar=bar, spinner=spinner, manual=True) as progressbar:
|
355 |
+
for data in resp.iter_content(chunk_size=chunksize):
|
356 |
+
dl += 1
|
357 |
+
f.write(data)
|
358 |
+
progressbar(dl/filesize)
|
359 |
+
if dl >= filesize:
|
360 |
+
progressbar(1.0)
|
361 |
+
f.close()
|
362 |
+
except Exception as e:
|
363 |
+
print("[!] Download Error - " + str(e))
|
364 |
+
time.sleep(3)
|
365 |
+
model = keras.models.load_model(files[0])
|
366 |
+
pkl = joblib.load(files[1])
|
367 |
+
return model, pkl
|
368 |
+
|
369 |
+
def getSigmoidConfidence(x):
|
370 |
+
out_min, out_max = 0, 100
|
371 |
+
if x > 0.5:
|
372 |
+
in_min = 0.50001
|
373 |
+
in_max = 1
|
374 |
+
else:
|
375 |
+
in_min = 0
|
376 |
+
in_max = 0.5
|
377 |
+
return round(((x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min),3)
|
378 |
+
|
379 |
+
def alertSound(beeps=3, delay=0.2):
|
380 |
+
for i in range(beeps):
|
381 |
+
print('\a')
|
382 |
+
sleep(delay)
|
383 |
+
|
384 |
+
def isBacktesting(backtestDate):
|
385 |
+
try:
|
386 |
+
if datetime.date.today() != backtestDate:
|
387 |
+
return True
|
388 |
+
return False
|
389 |
+
except:
|
390 |
+
return False
|
391 |
+
|
392 |
+
def calculateBacktestReport(data, backtestDict:dict):
|
393 |
+
try:
|
394 |
+
recent = data.head(1)['Close'].iloc[0]
|
395 |
+
for key, val in backtestDict.copy().items():
|
396 |
+
if val is not None:
|
397 |
+
try:
|
398 |
+
backtestDict[key] = str(round((backtestDict[key]-recent)/recent*100,1)) + "%"
|
399 |
+
except TypeError:
|
400 |
+
del backtestDict[key]
|
401 |
+
# backtestDict[key] = None
|
402 |
+
continue
|
403 |
+
else:
|
404 |
+
del backtestDict[key]
|
405 |
+
except:
|
406 |
+
pass
|
407 |
+
return backtestDict
|
408 |
+
|
409 |
+
def isDocker():
|
410 |
+
if 'SCREENIPY_DOCKER' in os.environ:
|
411 |
+
return True
|
412 |
+
return False
|
413 |
+
|
414 |
+
def isGui():
|
415 |
+
if 'SCREENIPY_GUI' in os.environ:
|
416 |
+
return True
|
417 |
+
return False
|
src/icon-old.ico
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/eval.py
ADDED
@@ -0,0 +1,130 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import yfinance as yf
|
2 |
+
import pandas as pd
|
3 |
+
import numpy as np
|
4 |
+
from sklearn.preprocessing import StandardScaler, MinMaxScaler
|
5 |
+
from sklearn.compose import ColumnTransformer
|
6 |
+
import joblib
|
7 |
+
import keras
|
8 |
+
import matplotlib.pyplot as plt
|
9 |
+
|
10 |
+
import tensorflow as tf
|
11 |
+
physical_devices = tf.config.list_physical_devices('GPU')
|
12 |
+
try:
|
13 |
+
# Disable all GPUS
|
14 |
+
tf.config.set_visible_devices([], 'GPU')
|
15 |
+
visible_devices = tf.config.get_visible_devices()
|
16 |
+
for device in visible_devices:
|
17 |
+
assert device.device_type != 'GPU'
|
18 |
+
except:
|
19 |
+
# Invalid device or cannot modify virtual devices once initialized.
|
20 |
+
pass
|
21 |
+
|
22 |
+
TEST_DAYS = 50
|
23 |
+
PERIOD = '5y'
|
24 |
+
|
25 |
+
INCLUDE_COMMODITIES = True
|
26 |
+
|
27 |
+
def preprocessBeforeScaling(df):
|
28 |
+
df['High'] = df['High'].pct_change() * 100
|
29 |
+
df['Low'] = df['Low'].pct_change() * 100
|
30 |
+
df['Open'] = df['Open'].pct_change() * 100
|
31 |
+
df['Close'] = df['Close'].pct_change() * 100
|
32 |
+
|
33 |
+
if INCLUDE_COMMODITIES:
|
34 |
+
df['gold_High'] = df['gold_High'].pct_change() * 100
|
35 |
+
df['gold_Low'] = df['gold_Low'].pct_change() * 100
|
36 |
+
df['gold_Open'] = df['gold_Open'].pct_change() * 100
|
37 |
+
df['gold_Close'] = df['gold_Close'].pct_change() * 100
|
38 |
+
|
39 |
+
df['crude_High'] = df['crude_High'].pct_change() * 100
|
40 |
+
df['crude_Low'] = df['crude_Low'].pct_change() * 100
|
41 |
+
df['crude_Open'] = df['crude_Open'].pct_change() * 100
|
42 |
+
df['crude_Close'] = df['crude_Close'].pct_change() * 100
|
43 |
+
return df
|
44 |
+
|
45 |
+
metrics = {
|
46 |
+
"TP": 0, "FP": 0, "TN": 0, "FN": 0
|
47 |
+
}
|
48 |
+
|
49 |
+
endpoint = keras.models.load_model('nifty_model_v3.h5')
|
50 |
+
try:
|
51 |
+
scaler
|
52 |
+
except NameError:
|
53 |
+
pkl = joblib.load('nifty_model_v3.pkl')
|
54 |
+
scaler = pkl['scaler']
|
55 |
+
today = yf.download(
|
56 |
+
tickers="^NSEI",
|
57 |
+
period=f'{TEST_DAYS}d',
|
58 |
+
interval='1d',
|
59 |
+
progress=False,
|
60 |
+
timeout=10
|
61 |
+
)
|
62 |
+
if INCLUDE_COMMODITIES:
|
63 |
+
gold = yf.download(
|
64 |
+
tickers="GC=F",
|
65 |
+
period=f'{TEST_DAYS}d',
|
66 |
+
interval='1d',
|
67 |
+
progress=False,
|
68 |
+
timeout=10
|
69 |
+
).add_prefix(prefix='gold_')
|
70 |
+
crude = yf.download(
|
71 |
+
tickers="CL=F",
|
72 |
+
period=f'{TEST_DAYS}d',
|
73 |
+
interval='1d',
|
74 |
+
progress=False,
|
75 |
+
timeout=10
|
76 |
+
).add_prefix(prefix='crude_')
|
77 |
+
|
78 |
+
today = pd.concat([today, gold, crude], axis=1)
|
79 |
+
today = today.drop(columns=['Adj Close', 'Volume', 'gold_Adj Close', 'gold_Volume', 'crude_Adj Close', 'crude_Volume'])
|
80 |
+
else:
|
81 |
+
today = today.drop(columns=['Adj Close', 'Volume'])
|
82 |
+
|
83 |
+
###
|
84 |
+
today = preprocessBeforeScaling(today)
|
85 |
+
today = today.drop(columns=['gold_Open', 'gold_High', 'gold_Low', 'crude_Open', 'crude_High', 'crude_Low'])
|
86 |
+
###
|
87 |
+
|
88 |
+
cnt_correct, cnt_wrong = 0, 0
|
89 |
+
for i in range(-TEST_DAYS,0):
|
90 |
+
df = today.iloc[i]
|
91 |
+
twr = today.iloc[i+1]['Close']
|
92 |
+
df = scaler.transform([df])
|
93 |
+
pred = endpoint.predict([df], verbose=0)
|
94 |
+
|
95 |
+
if twr > today.iloc[i]['Open']:
|
96 |
+
fact = "BULLISH"
|
97 |
+
else:
|
98 |
+
fact = "BEARISH"
|
99 |
+
|
100 |
+
if pred > 0.5:
|
101 |
+
out = "BEARISH"
|
102 |
+
else:
|
103 |
+
out = "BULLISH"
|
104 |
+
|
105 |
+
if out == fact:
|
106 |
+
cnt_correct += 1
|
107 |
+
if out == "BULLISH":
|
108 |
+
metrics["TP"] += 1
|
109 |
+
else:
|
110 |
+
metrics["TN"] += 1
|
111 |
+
else:
|
112 |
+
cnt_wrong += 1
|
113 |
+
if out == "BULLISH":
|
114 |
+
metrics["FN"] += 1
|
115 |
+
else:
|
116 |
+
metrics["FP"] += 1
|
117 |
+
|
118 |
+
|
119 |
+
print("{} Nifty Prediction -> Market may Close {} on {}! Actual -> {}, Prediction -> {}, Pred = {}".format(
|
120 |
+
today.iloc[i].name.strftime("%d-%m-%Y"),
|
121 |
+
out,
|
122 |
+
(today.iloc[i].name + pd.Timedelta(days=1)).strftime("%d-%m-%Y"),
|
123 |
+
fact,
|
124 |
+
"Correct" if fact == out else "Wrong",
|
125 |
+
str(np.round(pred[0][0], 2))
|
126 |
+
)
|
127 |
+
)
|
128 |
+
|
129 |
+
print("Correct: {}, Wrong: {}, Accuracy: {}".format(cnt_correct, cnt_wrong, cnt_correct/(cnt_correct+cnt_wrong)))
|
130 |
+
print(metrics)
|
src/ml/experiment.ipynb
ADDED
@@ -0,0 +1,673 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"cells": [
|
3 |
+
{
|
4 |
+
"cell_type": "code",
|
5 |
+
"execution_count": 11,
|
6 |
+
"metadata": {},
|
7 |
+
"outputs": [],
|
8 |
+
"source": [
|
9 |
+
"import tensorflow as tf\n",
|
10 |
+
"physical_devices = tf.config.list_physical_devices('GPU')\n",
|
11 |
+
"try:\n",
|
12 |
+
" # Disable all GPUS\n",
|
13 |
+
" tf.config.set_visible_devices([], 'GPU')\n",
|
14 |
+
" visible_devices = tf.config.get_visible_devices()\n",
|
15 |
+
" for device in visible_devices:\n",
|
16 |
+
" assert device.device_type != 'GPU'\n",
|
17 |
+
"except:\n",
|
18 |
+
" # Invalid device or cannot modify virtual devices once initialized.\n",
|
19 |
+
" pass"
|
20 |
+
]
|
21 |
+
},
|
22 |
+
{
|
23 |
+
"cell_type": "code",
|
24 |
+
"execution_count": 12,
|
25 |
+
"metadata": {},
|
26 |
+
"outputs": [],
|
27 |
+
"source": [
|
28 |
+
"import yfinance as yf\n",
|
29 |
+
"import pandas as pd\n",
|
30 |
+
"import numpy as np\n",
|
31 |
+
"from sklearn.preprocessing import StandardScaler, MinMaxScaler\n",
|
32 |
+
"from sklearn.compose import ColumnTransformer\n",
|
33 |
+
"import joblib\n",
|
34 |
+
"import keras\n",
|
35 |
+
"import matplotlib.pyplot as plt"
|
36 |
+
]
|
37 |
+
},
|
38 |
+
{
|
39 |
+
"cell_type": "code",
|
40 |
+
"execution_count": 13,
|
41 |
+
"metadata": {},
|
42 |
+
"outputs": [],
|
43 |
+
"source": [
|
44 |
+
"TEST_DAYS = 10\n",
|
45 |
+
"PERIOD = '5y'"
|
46 |
+
]
|
47 |
+
},
|
48 |
+
{
|
49 |
+
"cell_type": "code",
|
50 |
+
"execution_count": 14,
|
51 |
+
"metadata": {},
|
52 |
+
"outputs": [],
|
53 |
+
"source": [
|
54 |
+
"INDICATOR_DATASET = False"
|
55 |
+
]
|
56 |
+
},
|
57 |
+
{
|
58 |
+
"cell_type": "code",
|
59 |
+
"execution_count": 15,
|
60 |
+
"metadata": {},
|
61 |
+
"outputs": [],
|
62 |
+
"source": [
|
63 |
+
"INCLUDE_COMMODITIES = True"
|
64 |
+
]
|
65 |
+
},
|
66 |
+
{
|
67 |
+
"cell_type": "code",
|
68 |
+
"execution_count": null,
|
69 |
+
"metadata": {},
|
70 |
+
"outputs": [],
|
71 |
+
"source": [
|
72 |
+
"if INDICATOR_DATASET:\n",
|
73 |
+
" d = joblib.load('nifty_data.pkl')\n",
|
74 |
+
"else:\n",
|
75 |
+
" d = yf.download(\n",
|
76 |
+
" tickers=\"^NSEI\",\n",
|
77 |
+
" period=PERIOD,\n",
|
78 |
+
" interval='1d',\n",
|
79 |
+
" progress=False,\n",
|
80 |
+
" timeout=10\n",
|
81 |
+
" )\n",
|
82 |
+
" if INCLUDE_COMMODITIES:\n",
|
83 |
+
" gold = yf.download(\n",
|
84 |
+
" tickers=\"GC=F\",\n",
|
85 |
+
" period=PERIOD,\n",
|
86 |
+
" interval='1d',\n",
|
87 |
+
" progress=False,\n",
|
88 |
+
" timeout=10\n",
|
89 |
+
" ).add_prefix(prefix='gold_')\n",
|
90 |
+
" crude = yf.download(\n",
|
91 |
+
" tickers=\"CL=F\",\n",
|
92 |
+
" period=PERIOD,\n",
|
93 |
+
" interval='1d',\n",
|
94 |
+
" progress=False,\n",
|
95 |
+
" timeout=10\n",
|
96 |
+
" ).add_prefix(prefix='crude_')\n",
|
97 |
+
" d = pd.concat([d, gold, crude], axis=1)\n",
|
98 |
+
" \n",
|
99 |
+
" d['target'] = d.Open/d.Close.shift(-1)\n",
|
100 |
+
" d.target = d.target.apply(np.floor)\n",
|
101 |
+
"\n",
|
102 |
+
" d['change'] = abs(d['Close'].pct_change(fill_method=None) * 100)\n",
|
103 |
+
"\n",
|
104 |
+
" d['High'] = d['High'].pct_change(fill_method=None) * 100\n",
|
105 |
+
" d['Low'] = d['Low'].pct_change(fill_method=None) * 100\n",
|
106 |
+
" d['Open'] = d['Open'].pct_change(fill_method=None) * 100\n",
|
107 |
+
" d['Close'] = d['Close'].pct_change(fill_method=None) * 100 \n",
|
108 |
+
"\n",
|
109 |
+
" if INCLUDE_COMMODITIES:\n",
|
110 |
+
" d['gold_High'] = d['gold_High'].pct_change(fill_method=None) * 100\n",
|
111 |
+
" d['gold_Low'] = d['gold_Low'].pct_change(fill_method=None) * 100\n",
|
112 |
+
" d['gold_Open'] = d['gold_Open'].pct_change(fill_method=None) * 100\n",
|
113 |
+
" d['gold_Close'] = d['gold_Close'].pct_change(fill_method=None) * 100\n",
|
114 |
+
"\n",
|
115 |
+
" d['crude_High'] = d['crude_High'].pct_change(fill_method=None) * 100\n",
|
116 |
+
" d['crude_Low'] = d['crude_Low'].pct_change(fill_method=None) * 100\n",
|
117 |
+
" d['crude_Open'] = d['crude_Open'].pct_change(fill_method=None) * 100\n",
|
118 |
+
" d['crude_Close'] = d['crude_Close'].pct_change(fill_method=None) * 100\n",
|
119 |
+
" # d.rename(columns = {'HighNew':'High','LowNew':'Low','OpenNew':'Open','CloseNew':'Close'}, inplace = True)\n",
|
120 |
+
"\n",
|
121 |
+
" # Remove outliers when Market closes +- 3.5%\n",
|
122 |
+
" d = d[d['change'] < 3]\n",
|
123 |
+
" d.dropna(inplace=True)\n",
|
124 |
+
" d.tail()"
|
125 |
+
]
|
126 |
+
},
|
127 |
+
{
|
128 |
+
"cell_type": "code",
|
129 |
+
"execution_count": 23,
|
130 |
+
"metadata": {},
|
131 |
+
"outputs": [],
|
132 |
+
"source": [
|
133 |
+
"def preprocessBeforeScaling(df):\n",
|
134 |
+
" df['High'] = df['High'].pct_change(fill_method=None) * 100\n",
|
135 |
+
" df['Low'] = df['Low'].pct_change(fill_method=None) * 100\n",
|
136 |
+
" df['Open'] = df['Open'].pct_change(fill_method=None) * 100\n",
|
137 |
+
" df['Close'] = df['Close'].pct_change(fill_method=None) * 100 \n",
|
138 |
+
"\n",
|
139 |
+
" if INCLUDE_COMMODITIES:\n",
|
140 |
+
" df['gold_High'] = df['gold_High'].pct_change(fill_method=None) * 100\n",
|
141 |
+
" df['gold_Low'] = df['gold_Low'].pct_change(fill_method=None) * 100\n",
|
142 |
+
" df['gold_Open'] = df['gold_Open'].pct_change(fill_method=None) * 100\n",
|
143 |
+
" df['gold_Close'] = df['gold_Close'].pct_change(fill_method=None) * 100\n",
|
144 |
+
"\n",
|
145 |
+
" df['crude_High'] = df['crude_High'].pct_change(fill_method=None) * 100\n",
|
146 |
+
" df['crude_Low'] = df['crude_Low'].pct_change(fill_method=None) * 100\n",
|
147 |
+
" df['crude_Open'] = df['crude_Open'].pct_change(fill_method=None) * 100\n",
|
148 |
+
" df['crude_Close'] = df['crude_Close'].pct_change(fill_method=None) * 100\n",
|
149 |
+
" \n",
|
150 |
+
" df = df.ffill().dropna()\n",
|
151 |
+
" return df"
|
152 |
+
]
|
153 |
+
},
|
154 |
+
{
|
155 |
+
"cell_type": "code",
|
156 |
+
"execution_count": null,
|
157 |
+
"metadata": {},
|
158 |
+
"outputs": [],
|
159 |
+
"source": [
|
160 |
+
"test_dataset = d.tail(TEST_DAYS)"
|
161 |
+
]
|
162 |
+
},
|
163 |
+
{
|
164 |
+
"cell_type": "code",
|
165 |
+
"execution_count": null,
|
166 |
+
"metadata": {},
|
167 |
+
"outputs": [],
|
168 |
+
"source": [
|
169 |
+
"d = d[:-(TEST_DAYS+1)]"
|
170 |
+
]
|
171 |
+
},
|
172 |
+
{
|
173 |
+
"cell_type": "code",
|
174 |
+
"execution_count": null,
|
175 |
+
"metadata": {},
|
176 |
+
"outputs": [],
|
177 |
+
"source": [
|
178 |
+
"if INDICATOR_DATASET:\n",
|
179 |
+
" x = d.drop(columns=['target'])\n",
|
180 |
+
" y = d.target\n",
|
181 |
+
"else:\n",
|
182 |
+
" if INCLUDE_COMMODITIES:\n",
|
183 |
+
" # x = d.drop(columns=['target', 'Adj Close', 'Volume', 'change', 'gold_Adj Close', 'gold_Volume', 'crude_Adj Close', 'crude_Volume'], errors='ignore')\n",
|
184 |
+
" x = d.drop(columns=['target', 'Adj Close', 'Volume', 'change', 'gold_Open', 'gold_High', 'gold_Low', 'gold_Adj Close', 'gold_Volume', 'crude_Open', 'crude_High', 'crude_Low', 'crude_Adj Close', 'crude_Volume'], errors='ignore')\n",
|
185 |
+
" else:\n",
|
186 |
+
" x = d.drop(columns=['target', 'Adj Close', 'Volume', 'change'], errors='ignore')\n",
|
187 |
+
" y = d.target"
|
188 |
+
]
|
189 |
+
},
|
190 |
+
{
|
191 |
+
"cell_type": "code",
|
192 |
+
"execution_count": null,
|
193 |
+
"metadata": {},
|
194 |
+
"outputs": [],
|
195 |
+
"source": [
|
196 |
+
"x"
|
197 |
+
]
|
198 |
+
},
|
199 |
+
{
|
200 |
+
"cell_type": "code",
|
201 |
+
"execution_count": null,
|
202 |
+
"metadata": {},
|
203 |
+
"outputs": [],
|
204 |
+
"source": [
|
205 |
+
"y"
|
206 |
+
]
|
207 |
+
},
|
208 |
+
{
|
209 |
+
"cell_type": "code",
|
210 |
+
"execution_count": null,
|
211 |
+
"metadata": {},
|
212 |
+
"outputs": [],
|
213 |
+
"source": [
|
214 |
+
"print('No. of Bullish samples: {}'.format(y[y == 0].size))\n",
|
215 |
+
"print('No. of Bearish samples: {}'.format(y[y == 1].size))"
|
216 |
+
]
|
217 |
+
},
|
218 |
+
{
|
219 |
+
"cell_type": "code",
|
220 |
+
"execution_count": null,
|
221 |
+
"metadata": {},
|
222 |
+
"outputs": [],
|
223 |
+
"source": [
|
224 |
+
"if not INDICATOR_DATASET:\n",
|
225 |
+
" print(\"Using StandardScaler\")\n",
|
226 |
+
" scaler = StandardScaler()\n",
|
227 |
+
" x = scaler.fit_transform(x.to_numpy())\n",
|
228 |
+
" x\n",
|
229 |
+
"else:\n",
|
230 |
+
" print(\"Using ColumnTransformer\")\n",
|
231 |
+
" col_names = ['Open', 'High', 'Low', 'Close', 'ATR']\n",
|
232 |
+
" scaler = ColumnTransformer(\n",
|
233 |
+
" [('StandardScaler', StandardScaler(), col_names)],\n",
|
234 |
+
" remainder='passthrough'\n",
|
235 |
+
" )\n",
|
236 |
+
" x = scaler.fit_transform(x)\n",
|
237 |
+
"x"
|
238 |
+
]
|
239 |
+
},
|
240 |
+
{
|
241 |
+
"cell_type": "code",
|
242 |
+
"execution_count": null,
|
243 |
+
"metadata": {},
|
244 |
+
"outputs": [],
|
245 |
+
"source": [
|
246 |
+
"visible_devices"
|
247 |
+
]
|
248 |
+
},
|
249 |
+
{
|
250 |
+
"cell_type": "code",
|
251 |
+
"execution_count": null,
|
252 |
+
"metadata": {},
|
253 |
+
"outputs": [],
|
254 |
+
"source": [
|
255 |
+
"import tensorflow as tf\n",
|
256 |
+
"from keras import Sequential\n",
|
257 |
+
"from keras import Model\n",
|
258 |
+
"from keras.layers import Dense\n",
|
259 |
+
"from keras.optimizers import legacy, SGD\n",
|
260 |
+
"import keras\n",
|
261 |
+
"\n",
|
262 |
+
"lr_list = []\n",
|
263 |
+
"def scheduler(epoch, lr):\n",
|
264 |
+
" if epoch < 2:\n",
|
265 |
+
" lr = lr\n",
|
266 |
+
" else:\n",
|
267 |
+
" lr = lr * tf.math.exp(-0.0025)\n",
|
268 |
+
" lr_list.append(lr)\n",
|
269 |
+
" return lr\n",
|
270 |
+
"\n",
|
271 |
+
"units = 64 #128 #1024\n",
|
272 |
+
"# sgd = SGD(learning_rate=0.0001, momentum=0.0, nesterov=True)\n",
|
273 |
+
"sgd = legacy.SGD(learning_rate=0.001, momentum=0.9, nesterov=True)\n",
|
274 |
+
"kernel_init = 'he_uniform'\n",
|
275 |
+
"activation = 'relu'\n",
|
276 |
+
"\n",
|
277 |
+
"callback_mc = keras.callbacks.ModelCheckpoint(\n",
|
278 |
+
" 'best_model.h5',\n",
|
279 |
+
" verbose=1,\n",
|
280 |
+
" monitor='val_accuracy',\n",
|
281 |
+
" save_best_only=True,\n",
|
282 |
+
" mode='auto'\n",
|
283 |
+
" )\n",
|
284 |
+
"callback_es = keras.callbacks.EarlyStopping(\n",
|
285 |
+
" monitor='val_accuracy',\n",
|
286 |
+
" mode='auto',\n",
|
287 |
+
" verbose=0,\n",
|
288 |
+
" patience=100\n",
|
289 |
+
")\n",
|
290 |
+
"callback_lr = keras.callbacks.LearningRateScheduler(scheduler)\n",
|
291 |
+
"\n",
|
292 |
+
"model = Sequential([\n",
|
293 |
+
" Dense(units, kernel_initializer=kernel_init, activation=activation, input_dim=x.shape[1]),\n",
|
294 |
+
" # Dense(units, kernel_initializer=kernel_init, activation=activation),\n",
|
295 |
+
" Dense(units//2, kernel_initializer=kernel_init, activation=activation),\n",
|
296 |
+
" Dense(units//4, kernel_initializer=kernel_init, activation=activation),\n",
|
297 |
+
" Dense(units//8, kernel_initializer=kernel_init, activation=activation),\n",
|
298 |
+
" Dense(units//16, kernel_initializer=kernel_init, activation=activation),\n",
|
299 |
+
" Dense(units//32, kernel_initializer=kernel_init, activation=activation),\n",
|
300 |
+
" Dense(1, kernel_initializer=kernel_init, activation='sigmoid'),\n",
|
301 |
+
"])\n",
|
302 |
+
"model.compile(optimizer=sgd, loss='binary_crossentropy', metrics=['accuracy'])\n",
|
303 |
+
"model.summary()"
|
304 |
+
]
|
305 |
+
},
|
306 |
+
{
|
307 |
+
"cell_type": "code",
|
308 |
+
"execution_count": null,
|
309 |
+
"metadata": {},
|
310 |
+
"outputs": [],
|
311 |
+
"source": [
|
312 |
+
"BATCH_SIZE = int(len(y)/6.6125) #128 #24 #4\n",
|
313 |
+
"BATCH_SIZE = 256\n",
|
314 |
+
"print(f'BATCH SIZE = {BATCH_SIZE}')\n",
|
315 |
+
"history = model.fit(x, y, callbacks=[callback_mc, callback_es, callback_lr], batch_size=BATCH_SIZE, epochs=750, validation_split=0.15, verbose=2)"
|
316 |
+
]
|
317 |
+
},
|
318 |
+
{
|
319 |
+
"cell_type": "code",
|
320 |
+
"execution_count": null,
|
321 |
+
"metadata": {},
|
322 |
+
"outputs": [],
|
323 |
+
"source": [
|
324 |
+
"import matplotlib.pyplot as plt\n",
|
325 |
+
"\n",
|
326 |
+
"acc = history.history['accuracy']\n",
|
327 |
+
"loss = history.history['loss']\n",
|
328 |
+
"\n",
|
329 |
+
"plt.figure(figsize=(21,6))\n",
|
330 |
+
"plt.rcParams['figure.figsize'] = [8,8]\n",
|
331 |
+
"plt.rcParams['font.size'] = 14\n",
|
332 |
+
"plt.rcParams['axes.grid'] = True\n",
|
333 |
+
"plt.rcParams['figure.facecolor'] = 'white'\n",
|
334 |
+
"\n",
|
335 |
+
"plt.subplot(1, 3, 1)\n",
|
336 |
+
"plt.plot(acc, label='Training Accuracy')\n",
|
337 |
+
"plt.legend(loc='lower right')\n",
|
338 |
+
"plt.ylabel('Accuracy')\n",
|
339 |
+
"plt.title(f'\\nTrain Accuracy: {round(acc[-1],8)}')\n",
|
340 |
+
"\n",
|
341 |
+
"plt.subplot(1, 3, 2)\n",
|
342 |
+
"plt.plot(loss, label='Training Loss')\n",
|
343 |
+
"plt.legend(loc='upper right')\n",
|
344 |
+
"plt.ylabel('Cross Entropy')\n",
|
345 |
+
"plt.title(f'\\nTrain Loss: {round(loss[-1],8)}')\n",
|
346 |
+
"plt.xlabel('epoch')\n",
|
347 |
+
"\n",
|
348 |
+
"plt.subplot(1, 3, 3)\n",
|
349 |
+
"plt.plot(lr_list, label='Learning Rate')\n",
|
350 |
+
"plt.legend(loc='upper right')\n",
|
351 |
+
"plt.ylabel('LR')\n",
|
352 |
+
"plt.title(f'\\nLearning Rate')\n",
|
353 |
+
"plt.xlabel('epoch')\n",
|
354 |
+
"\n",
|
355 |
+
"plt.tight_layout(pad=3.0)\n",
|
356 |
+
"plt.show()\n",
|
357 |
+
"\n",
|
358 |
+
"acc = history.history['val_accuracy']\n",
|
359 |
+
"loss = history.history['val_loss']\n",
|
360 |
+
"\n",
|
361 |
+
"plt.figure(figsize=(14,6))\n",
|
362 |
+
"plt.rcParams['figure.figsize'] = [8,8]\n",
|
363 |
+
"plt.rcParams['font.size'] = 14\n",
|
364 |
+
"plt.rcParams['axes.grid'] = True\n",
|
365 |
+
"plt.rcParams['figure.facecolor'] = 'white'\n",
|
366 |
+
"plt.subplot(1, 2, 1)\n",
|
367 |
+
"plt.plot(acc, label='Val Accuracy')\n",
|
368 |
+
"plt.legend(loc='lower right')\n",
|
369 |
+
"plt.ylabel('Accuracy')\n",
|
370 |
+
"plt.title(f'\\nTest Accuracy: {round(acc[-1],8)}')\n",
|
371 |
+
"\n",
|
372 |
+
"plt.subplot(1, 2, 2)\n",
|
373 |
+
"plt.plot(loss, label='Val Loss')\n",
|
374 |
+
"plt.legend(loc='upper right')\n",
|
375 |
+
"plt.ylabel('Cross Entropy')\n",
|
376 |
+
"plt.title(f'\\nTest Loss: {round(loss[-1],8)}')\n",
|
377 |
+
"plt.xlabel('epoch')\n",
|
378 |
+
"plt.tight_layout(pad=3.0)\n",
|
379 |
+
"plt.show()"
|
380 |
+
]
|
381 |
+
},
|
382 |
+
{
|
383 |
+
"cell_type": "markdown",
|
384 |
+
"metadata": {},
|
385 |
+
"source": [
|
386 |
+
"## Try Realtime Inference"
|
387 |
+
]
|
388 |
+
},
|
389 |
+
{
|
390 |
+
"cell_type": "code",
|
391 |
+
"execution_count": 26,
|
392 |
+
"metadata": {},
|
393 |
+
"outputs": [],
|
394 |
+
"source": [
|
395 |
+
"metrics = {\n",
|
396 |
+
" \"TP\": 0, \"FP\": 0, \"TN\": 0, \"FN\": 0\n",
|
397 |
+
"}"
|
398 |
+
]
|
399 |
+
},
|
400 |
+
{
|
401 |
+
"cell_type": "code",
|
402 |
+
"execution_count": 27,
|
403 |
+
"metadata": {},
|
404 |
+
"outputs": [
|
405 |
+
{
|
406 |
+
"name": "stdout",
|
407 |
+
"output_type": "stream",
|
408 |
+
"text": [
|
409 |
+
" Open High Low Close gold_Close crude_Close\n",
|
410 |
+
"Date \n",
|
411 |
+
"2023-11-15 0.697093 0.221577 0.441300 0.093698 -0.086659 -2.044465\n",
|
412 |
+
"2023-11-16 0.118561 0.924435 0.241831 0.456152 1.214226 -4.904777\n",
|
413 |
+
"2023-11-17 0.000258 -0.348423 0.206090 -0.168976 -0.115936 4.101506\n",
|
414 |
+
"2023-11-20 0.286664 -0.250181 0.015512 -0.191573 -0.196812 2.253260\n",
|
415 |
+
"2023-11-21 0.201458 0.367730 0.424752 0.453947 1.092183 0.219070\n",
|
416 |
+
"2023-11-22 0.066257 -0.017897 -0.254131 0.143803 -0.395140 -0.861512\n",
|
417 |
+
"2023-11-23 0.224673 0.250180 0.420732 -0.049716 -0.395140 -0.861512\n",
|
418 |
+
"2023-11-24 -0.095063 -0.212833 -0.090467 -0.036869 -0.395140 -0.861512\n",
|
419 |
+
"15-11-2023 Nifty Prediction -> Market may Close BEARISH on 16-11-2023! Actual -> BEARISH, Prediction -> Correct, Pred = 0.59\n",
|
420 |
+
"16-11-2023 Nifty Prediction -> Market may Close BULLISH on 17-11-2023! Actual -> BEARISH, Prediction -> Wrong, Pred = 0.2\n",
|
421 |
+
"17-11-2023 Nifty Prediction -> Market may Close BEARISH on 18-11-2023! Actual -> BEARISH, Prediction -> Correct, Pred = 0.59\n",
|
422 |
+
"20-11-2023 Nifty Prediction -> Market may Close BEARISH on 21-11-2023! Actual -> BULLISH, Prediction -> Wrong, Pred = 0.59\n",
|
423 |
+
"21-11-2023 Nifty Prediction -> Market may Close BULLISH on 22-11-2023! Actual -> BEARISH, Prediction -> Wrong, Pred = 0.28\n",
|
424 |
+
"22-11-2023 Nifty Prediction -> Market may Close BULLISH on 23-11-2023! Actual -> BEARISH, Prediction -> Wrong, Pred = 0.48\n",
|
425 |
+
"23-11-2023 Nifty Prediction -> Market may Close BEARISH on 24-11-2023! Actual -> BEARISH, Prediction -> Correct, Pred = 0.57\n",
|
426 |
+
"24-11-2023 Nifty Prediction -> Market may Close BEARISH on 25-11-2023! Actual -> BULLISH, Prediction -> Wrong, Pred = 0.56\n",
|
427 |
+
"Correct: 3, Wrong: 5, Accuracy: 0.375\n",
|
428 |
+
"{'TP': 0, 'FP': 2, 'TN': 3, 'FN': 3}\n"
|
429 |
+
]
|
430 |
+
}
|
431 |
+
],
|
432 |
+
"source": [
|
433 |
+
"endpoint = keras.models.load_model('nifty_model_v3.h5')\n",
|
434 |
+
"# endpoint = keras.models.load_model('best_model.h5')\n",
|
435 |
+
"try:\n",
|
436 |
+
" scaler\n",
|
437 |
+
"except NameError:\n",
|
438 |
+
" # pkl = joblib.load('nifty_model.pkl')\n",
|
439 |
+
" pkl = joblib.load('nifty_model_v3.pkl')\n",
|
440 |
+
" scaler = pkl['scaler']\n",
|
441 |
+
"today = yf.download(\n",
|
442 |
+
" tickers=\"^NSEI\",\n",
|
443 |
+
" period=f'{TEST_DAYS}d',\n",
|
444 |
+
" interval='1d',\n",
|
445 |
+
" progress=False,\n",
|
446 |
+
" timeout=10\n",
|
447 |
+
" )\n",
|
448 |
+
"if INCLUDE_COMMODITIES:\n",
|
449 |
+
" gold = yf.download(\n",
|
450 |
+
" tickers=\"GC=F\",\n",
|
451 |
+
" period=f'{TEST_DAYS}d',\n",
|
452 |
+
" interval='1d',\n",
|
453 |
+
" progress=False,\n",
|
454 |
+
" timeout=10\n",
|
455 |
+
" ).add_prefix(prefix='gold_')\n",
|
456 |
+
" crude = yf.download(\n",
|
457 |
+
" tickers=\"CL=F\",\n",
|
458 |
+
" period=f'{TEST_DAYS}d',\n",
|
459 |
+
" interval='1d',\n",
|
460 |
+
" progress=False,\n",
|
461 |
+
" timeout=10\n",
|
462 |
+
" ).add_prefix(prefix='crude_')\n",
|
463 |
+
"\n",
|
464 |
+
" today = pd.concat([today, gold, crude], axis=1)\n",
|
465 |
+
" today = today.drop(columns=['Adj Close', 'Volume', 'gold_Adj Close', 'gold_Volume', 'crude_Adj Close', 'crude_Volume'])\n",
|
466 |
+
"else:\n",
|
467 |
+
" today = today.drop(columns=['Adj Close', 'Volume'])\n",
|
468 |
+
"\n",
|
469 |
+
"###\n",
|
470 |
+
"today = preprocessBeforeScaling(today)\n",
|
471 |
+
"today = today.drop(columns=['gold_Open', 'gold_High', 'gold_Low', 'crude_Open', 'crude_High', 'crude_Low'])\n",
|
472 |
+
"print(today)\n",
|
473 |
+
"###\n",
|
474 |
+
"\n",
|
475 |
+
"cnt_correct, cnt_wrong = 0, 0\n",
|
476 |
+
"for i in range(-TEST_DAYS,0):\n",
|
477 |
+
" try:\n",
|
478 |
+
" df = today.iloc[i]\n",
|
479 |
+
" twr = today.iloc[i+1]['Close']\n",
|
480 |
+
" except IndexError:\n",
|
481 |
+
" continue\n",
|
482 |
+
" df = scaler.transform([df])\n",
|
483 |
+
" pred = endpoint.predict([df], verbose=0)\n",
|
484 |
+
"\n",
|
485 |
+
" if twr > today.iloc[i]['Open']:\n",
|
486 |
+
" fact = \"BULLISH\"\n",
|
487 |
+
" else:\n",
|
488 |
+
" fact = \"BEARISH\"\n",
|
489 |
+
"\n",
|
490 |
+
" if pred > 0.5:\n",
|
491 |
+
" out = \"BEARISH\"\n",
|
492 |
+
" else:\n",
|
493 |
+
" out = \"BULLISH\"\n",
|
494 |
+
"\n",
|
495 |
+
" if out == fact:\n",
|
496 |
+
" cnt_correct += 1\n",
|
497 |
+
" if out == \"BULLISH\":\n",
|
498 |
+
" metrics[\"TP\"] += 1\n",
|
499 |
+
" else:\n",
|
500 |
+
" metrics[\"TN\"] += 1\n",
|
501 |
+
" else:\n",
|
502 |
+
" cnt_wrong += 1\n",
|
503 |
+
" if out == \"BULLISH\":\n",
|
504 |
+
" metrics[\"FN\"] += 1\n",
|
505 |
+
" else:\n",
|
506 |
+
" metrics[\"FP\"] += 1\n",
|
507 |
+
"\n",
|
508 |
+
" \n",
|
509 |
+
" print(\"{} Nifty Prediction -> Market may Close {} on {}! Actual -> {}, Prediction -> {}, Pred = {}\".format(\n",
|
510 |
+
" today.iloc[i].name.strftime(\"%d-%m-%Y\"),\n",
|
511 |
+
" out,\n",
|
512 |
+
" (today.iloc[i].name + pd.Timedelta(days=1)).strftime(\"%d-%m-%Y\"),\n",
|
513 |
+
" fact,\n",
|
514 |
+
" \"Correct\" if fact == out else \"Wrong\",\n",
|
515 |
+
" str(np.round(pred[0][0], 2))\n",
|
516 |
+
" )\n",
|
517 |
+
" )\n",
|
518 |
+
"\n",
|
519 |
+
"print(\"Correct: {}, Wrong: {}, Accuracy: {}\".format(cnt_correct, cnt_wrong, cnt_correct/(cnt_correct+cnt_wrong)))\n",
|
520 |
+
"print(metrics)"
|
521 |
+
]
|
522 |
+
},
|
523 |
+
{
|
524 |
+
"cell_type": "markdown",
|
525 |
+
"metadata": {},
|
526 |
+
"source": [
|
527 |
+
"## Save Model for Screeni-py integration"
|
528 |
+
]
|
529 |
+
},
|
530 |
+
{
|
531 |
+
"cell_type": "code",
|
532 |
+
"execution_count": null,
|
533 |
+
"metadata": {},
|
534 |
+
"outputs": [],
|
535 |
+
"source": [
|
536 |
+
"pkl = {\n",
|
537 |
+
" # 'model': model,\n",
|
538 |
+
" 'scaler': scaler,\n",
|
539 |
+
" 'columns': ['Open', 'Close', 'High', 'Low', 'gold_Close', 'crude_Close']\n",
|
540 |
+
"}\n",
|
541 |
+
"\n",
|
542 |
+
"joblib.dump(pkl, 'nifty_model.pkl')"
|
543 |
+
]
|
544 |
+
},
|
545 |
+
{
|
546 |
+
"cell_type": "code",
|
547 |
+
"execution_count": null,
|
548 |
+
"metadata": {},
|
549 |
+
"outputs": [],
|
550 |
+
"source": [
|
551 |
+
"pkl = joblib.load('nifty_model_v3.pkl')\n",
|
552 |
+
"z = yf.download(\n",
|
553 |
+
" tickers=\"^NSEI\",\n",
|
554 |
+
" period='5d',\n",
|
555 |
+
" interval='1d',\n",
|
556 |
+
" progress=False,\n",
|
557 |
+
" timeout=10\n",
|
558 |
+
" )\n",
|
559 |
+
"if INCLUDE_COMMODITIES:\n",
|
560 |
+
" gold = yf.download(\n",
|
561 |
+
" tickers=\"GC=F\",\n",
|
562 |
+
" period='5d',\n",
|
563 |
+
" interval='1d',\n",
|
564 |
+
" progress=False,\n",
|
565 |
+
" timeout=10\n",
|
566 |
+
" ).add_prefix(prefix='gold_')\n",
|
567 |
+
" crude = yf.download(\n",
|
568 |
+
" tickers=\"CL=F\",\n",
|
569 |
+
" period='5d',\n",
|
570 |
+
" interval='1d',\n",
|
571 |
+
" progress=False,\n",
|
572 |
+
" timeout=10\n",
|
573 |
+
" ).add_prefix(prefix='crude_')\n",
|
574 |
+
" z = pd.concat([z, gold, crude], axis=1)\n",
|
575 |
+
"z = preprocessBeforeScaling(z)\n",
|
576 |
+
"z = z.iloc[-1]\n",
|
577 |
+
"z = z[pkl['columns']]\n",
|
578 |
+
"print(z)\n",
|
579 |
+
"z = pkl['scaler'].transform([z])\n",
|
580 |
+
"endpoint.predict(z)"
|
581 |
+
]
|
582 |
+
},
|
583 |
+
{
|
584 |
+
"cell_type": "code",
|
585 |
+
"execution_count": null,
|
586 |
+
"metadata": {},
|
587 |
+
"outputs": [],
|
588 |
+
"source": [
|
589 |
+
"pkl['model'].save('nifty_model.h5')"
|
590 |
+
]
|
591 |
+
},
|
592 |
+
{
|
593 |
+
"cell_type": "code",
|
594 |
+
"execution_count": null,
|
595 |
+
"metadata": {},
|
596 |
+
"outputs": [],
|
597 |
+
"source": [
|
598 |
+
"pkl"
|
599 |
+
]
|
600 |
+
},
|
601 |
+
{
|
602 |
+
"cell_type": "code",
|
603 |
+
"execution_count": null,
|
604 |
+
"metadata": {},
|
605 |
+
"outputs": [],
|
606 |
+
"source": [
|
607 |
+
"del pkl['model']"
|
608 |
+
]
|
609 |
+
},
|
610 |
+
{
|
611 |
+
"cell_type": "code",
|
612 |
+
"execution_count": null,
|
613 |
+
"metadata": {},
|
614 |
+
"outputs": [],
|
615 |
+
"source": [
|
616 |
+
"pkl"
|
617 |
+
]
|
618 |
+
},
|
619 |
+
{
|
620 |
+
"cell_type": "code",
|
621 |
+
"execution_count": null,
|
622 |
+
"metadata": {},
|
623 |
+
"outputs": [],
|
624 |
+
"source": [
|
625 |
+
"def getSigmoidConfidence(x):\n",
|
626 |
+
" out_min, out_max = 0, 100\n",
|
627 |
+
" if x > 0.5:\n",
|
628 |
+
" in_min = 0.50001\n",
|
629 |
+
" in_max = 1\n",
|
630 |
+
" else:\n",
|
631 |
+
" in_min = 0\n",
|
632 |
+
" in_max = 0.5\n",
|
633 |
+
" return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min\n",
|
634 |
+
"\n",
|
635 |
+
"map_range(0.9633487, 0.5, 1, 0, 100)"
|
636 |
+
]
|
637 |
+
},
|
638 |
+
{
|
639 |
+
"cell_type": "code",
|
640 |
+
"execution_count": null,
|
641 |
+
"metadata": {},
|
642 |
+
"outputs": [],
|
643 |
+
"source": []
|
644 |
+
}
|
645 |
+
],
|
646 |
+
"metadata": {
|
647 |
+
"kernelspec": {
|
648 |
+
"display_name": "Python 3.9.13 ('ds')",
|
649 |
+
"language": "python",
|
650 |
+
"name": "python3"
|
651 |
+
},
|
652 |
+
"language_info": {
|
653 |
+
"codemirror_mode": {
|
654 |
+
"name": "ipython",
|
655 |
+
"version": 3
|
656 |
+
},
|
657 |
+
"file_extension": ".py",
|
658 |
+
"mimetype": "text/x-python",
|
659 |
+
"name": "python",
|
660 |
+
"nbconvert_exporter": "python",
|
661 |
+
"pygments_lexer": "ipython3",
|
662 |
+
"version": "3.10.6"
|
663 |
+
},
|
664 |
+
"orig_nbformat": 4,
|
665 |
+
"vscode": {
|
666 |
+
"interpreter": {
|
667 |
+
"hash": "272f5af4762c02c25377d17b8d5be1b9d83b050e7634f4572d665f6d13ef995d"
|
668 |
+
}
|
669 |
+
}
|
670 |
+
},
|
671 |
+
"nbformat": 4,
|
672 |
+
"nbformat_minor": 2
|
673 |
+
}
|
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/ml/nifty_model_v3.h5
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:78ce7d514872d56640f25f381db3a77cebf018b4564196e9fa6b9ac626cec6cf
|
3 |
+
size 86056
|
src/ml/nifty_model_v3.pkl
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:1f457771736ac5f744288b8bf29b4c54164067146ecbb58afbd66c1c0ea18bd9
|
3 |
+
size 845
|
src/release.md
ADDED
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
[![MADE-IN-INDIA](https://img.shields.io/badge/MADE%20WITH%20%E2%9D%A4%20IN-INDIA-orange?style=for-the-badge)](https://en.wikipedia.org/wiki/India) [![GitHub release (latest by date)](https://img.shields.io/github/v/release/pranjal-joshi/Screeni-py?style=for-the-badge)](#) [![GitHub all releases](https://img.shields.io/github/downloads/pranjal-joshi/Screeni-py/total?color=Green&label=Downloads&style=for-the-badge)](#) ![Docker Pulls](https://img.shields.io/docker/pulls/joshipranjal/screeni-py?style=for-the-badge&logo=docker) [![MADE_WITH](https://img.shields.io/badge/BUILT%20USING-PYTHON-yellow?style=for-the-badge&logo=python&logoColor=yellow)](https://www.python.org/)
|
2 |
+
## What's New?
|
3 |
+
|
4 |
+
Screeni-py is now on **YouTube** for additional help! - Thank You for your support :tada:
|
5 |
+
|
6 |
+
🐳 **Docker containers are released for quick setup and easy usage!**
|
7 |
+
|
8 |
+
⚠️ **Executable files (.exe, .bin and .run) are now DEPRECATED! Please Switch to Docker**
|
9 |
+
|
10 |
+
1. **RSI** based **Reversal** using *9 SMA* of RSI - Try `Option > 6 > 8`
|
11 |
+
2. **NSE Indices** added to find Sectoral opportunities - Try Index `16 > Sectoral Indices`
|
12 |
+
3. **Backtesting Reports** Added for Screening Patterns to Develope and Test Strategies!
|
13 |
+
4. **Position Size Calculator** tab added for Better and Quick Risk Management!
|
14 |
+
5. **Lorentzian Classification** (invented by Justin Dehorty) added for enhanced accuracy for your trades - - Try `Option > 6 > 7` 🤯
|
15 |
+
6. **Artificial Intelligence v3 for Nifty 50 Prediction** - Predict Next day Gap-up/down using Nifty, Gold and Crude prices! - Try `Select Index for Screening > N`
|
16 |
+
7. **Search Similar Stocks** Added using Vector Similarity search - Try `Search Similar Stocks`.
|
17 |
+
8. New Screener **Buy at Trendline** added for Swing/Mid/Long term traders - Try `Option > 7 > 5`.
|
18 |
+
|
19 |
+
## Installation Guide
|
20 |
+
|
21 |
+
[![Screeni-py - How to install Software Updates? | Screenipy - Python NSE Stock Screener](https://markdown-videos-api.jorgenkh.no/url?url=https%3A%2F%2Fyoutu.be%2FT41m13iMyJc)](https://youtu.be/T41m13iMyJc)
|
22 |
+
[![Screeni-py - Detailed Installation Guide](https://markdown-videos-api.jorgenkh.no/url?url=https%3A%2F%2Fyoutu.be%2F2HMN0ac4H20)](https://youtu.be/2HMN0ac4H20)
|
23 |
+
|
24 |
+
## Downloads
|
25 |
+
### Deprecated - Use Docker Method mentioned in next section
|
26 |
+
|
27 |
+
| Operating System | Executable File | Remarks |
|
28 |
+
| :-: | --- | --- |
|
29 |
+
| ![Windows](https://img.shields.io/badge/Windows-0078D6?style=for-the-badge&logo=windows&logoColor=white) | **[screenipy.exe](https://github.com/pranjal-joshi/Screeni-py/releases/download/2.02/screenipy.exe)** | Not supported anymore, Use Docker method |
|
30 |
+
| ![Linux](https://img.shields.io/badge/Linux-FCC624?style=for-the-badge&logo=linux&logoColor=black) | **[screenipy.bin](https://github.com/pranjal-joshi/Screeni-py/releases/download/2.02/screenipy.bin)** | Not supported anymore, Use Docker method |
|
31 |
+
| ![Mac OS](https://img.shields.io/badge/mac%20os-D3D3D3?style=for-the-badge&logo=apple&logoColor=000000) | **[screenipy.run](https://github.com/pranjal-joshi/Screeni-py/releases/download/2.02/screenipy.run)** ([Read Installation Guide](https://github.com/pranjal-joshi/Screeni-py/blob/main/INSTALLATION.md#for-macos)) | Not supported anymore, Use Docker method |
|
32 |
+
|
33 |
+
## [Docker Releases](https://hub.docker.com/r/joshipranjal/screeni-py/tags)
|
34 |
+
|
35 |
+
| | Tag | Pull Command | Run Mode | Run Command |
|
36 |
+
|:-: | :-: | --- | --- | --- |
|
37 |
+
| ![Docker](https://img.shields.io/badge/docker-%230db7ed.svg?style=for-the-badge&logo=docker&logoColor=white) | `latest` | `docker pull joshipranjal/screeni-py:latest` | Command Line | `docker run -it --entrypoint /bin/bash joshipranjal/screeni-py:latest -c "run_screenipy.sh --cli"` |
|
38 |
+
| ![Docker](https://img.shields.io/badge/docker-%230db7ed.svg?style=for-the-badge&logo=docker&logoColor=white) | `latest` | `docker pull joshipranjal/screeni-py:latest` | GUI WebApp | `docker run -p 8501:8501 joshipranjal/screeni-py:latest` |
|
39 |
+
| ![Docker](https://img.shields.io/badge/docker-%230db7ed.svg?style=for-the-badge&logo=docker&logoColor=white) | `dev` | `docker pull joshipranjal/screeni-py:dev` | Command Line | `docker run -it --entrypoint /bin/bash joshipranjal/screeni-py:dev -c "run_screenipy.sh --cli"` |
|
40 |
+
| ![Docker](https://img.shields.io/badge/docker-%230db7ed.svg?style=for-the-badge&logo=docker&logoColor=white) | `dev` | `docker pull joshipranjal/screeni-py:dev` | GUI WebApp | `docker run -p 8501:8501 joshipranjal/screeni-py:dev` |
|
41 |
+
|
42 |
+
### Docker Issues? Troubleshooting Guide:
|
43 |
+
|
44 |
+
Read this [troubleshooting guide](https://github.com/pranjal-joshi/Screeni-py/discussions/217) for Windows to fix most common Docker issues easily!
|
45 |
+
|
46 |
+
**Why we shifted to Docker from the Good old EXEs?**
|
47 |
+
|
48 |
+
| Executable/Binary File | Docker |
|
49 |
+
| :-- | :-- |
|
50 |
+
| [![GitHub all releases](https://img.shields.io/github/downloads/pranjal-joshi/Screeni-py/total?color=Green&label=Downloads&style=for-the-badge)](#) | ![Docker Pulls](https://img.shields.io/docker/pulls/joshipranjal/screeni-py?style=for-the-badge&logo=docker) |
|
51 |
+
| Download Directly from the [Release](https://github.com/pranjal-joshi/Screeni-py/releases/latest) page (DEPRECATED) | Need to Install [Docker Desktop](https://www.docker.com/products/docker-desktop/) ⚠️|
|
52 |
+
| May take a long time to open the app | Loads quickly |
|
53 |
+
| Slower screening | Performance boosted as per your CPU capabilities |
|
54 |
+
| You may face errors/warnings due to different CPU arch of your system ⚠️ | Compatible with all x86_64/amd64/arm64 CPUs irrespective of OS (including Mac M1/M2) |
|
55 |
+
| Works only with Windows 10/11 ⚠️ | Works with older versions of Windows as well |
|
56 |
+
| Different file for each OS | Same container is compatible with everyone |
|
57 |
+
| Antivirus may block this as untrusted file ⚠️ | No issues with Antivirus |
|
58 |
+
| Need to download new file for every update | Updates quickly with minimal downloading |
|
59 |
+
| No need of commands/technical knowledge | Very basic command execution skills may be required |
|
60 |
+
| Incompatible with Vector Database ⚠️ | Compatible with all Python libraries |
|
61 |
+
|
62 |
+
|
63 |
+
## How to use?
|
64 |
+
|
65 |
+
[**Click Here**](https://github.com/pranjal-joshi/Screeni-py) to read the documentation.
|
66 |
+
|
67 |
+
## Join our Community Discussion
|
68 |
+
|
69 |
+
[**Click Here**](https://github.com/pranjal-joshi/Screeni-py/discussions) to join the community discussion and see what other users are doing!
|
70 |
+
|
71 |
+
## Facing an Issue? Found a Bug?
|
72 |
+
|
73 |
+
[**Click Here**](https://github.com/pranjal-joshi/Screeni-py/issues/new/choose) to open an Issue so we can fix it for you!
|
74 |
+
|
75 |
+
## Want to Contribute?
|
76 |
+
|
77 |
+
[**Click Here**](https://github.com/pranjal-joshi/Screeni-py/blob/main/CONTRIBUTING.md) before you start working with us on new features!
|
78 |
+
|
79 |
+
## Disclaimer:
|
80 |
+
* DO NOT use the result provided by the software solely to make your trading decisions.
|
81 |
+
* Always backtest and analyze the stocks manually before you trade.
|
82 |
+
* 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.0
|
6 |
+
maxprice = 10000.0
|
7 |
+
volumeratio = 2.0
|
8 |
+
consolidationpercentage = 10
|
9 |
+
shuffle = y
|
10 |
+
cachestockdata = y
|
11 |
+
onlystagetwostocks = y
|
12 |
+
useema = n
|
13 |
+
|
src/screenipy.py
ADDED
@@ -0,0 +1,545 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 --hidden-import chromadb
|
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 --hidden-import chromadb
|
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 classes.Utility import isDocker, isGui
|
21 |
+
from alive_progress import alive_bar
|
22 |
+
import argparse
|
23 |
+
import urllib
|
24 |
+
import numpy as np
|
25 |
+
import pandas as pd
|
26 |
+
from datetime import datetime, date
|
27 |
+
from time import sleep
|
28 |
+
from tabulate import tabulate
|
29 |
+
import multiprocessing
|
30 |
+
multiprocessing.freeze_support()
|
31 |
+
try:
|
32 |
+
import chromadb
|
33 |
+
CHROMA_AVAILABLE = True
|
34 |
+
except:
|
35 |
+
CHROMA_AVAILABLE = False
|
36 |
+
|
37 |
+
# Argument Parsing for test purpose
|
38 |
+
argParser = argparse.ArgumentParser()
|
39 |
+
argParser.add_argument('-t', '--testbuild', action='store_true', help='Run in test-build mode', required=False)
|
40 |
+
argParser.add_argument('-d', '--download', action='store_true', help='Only Download Stock data in .pkl file', required=False)
|
41 |
+
argParser.add_argument('-v', action='store_true') # Dummy Arg for pytest -v
|
42 |
+
args = argParser.parse_args()
|
43 |
+
|
44 |
+
# Try Fixing bug with this symbol
|
45 |
+
TEST_STKCODE = "SBIN"
|
46 |
+
|
47 |
+
# Constants
|
48 |
+
np.seterr(divide='ignore', invalid='ignore')
|
49 |
+
|
50 |
+
# Global Variabls
|
51 |
+
screenCounter = None
|
52 |
+
screenResultsCounter = None
|
53 |
+
stockDict = None
|
54 |
+
keyboardInterruptEvent = None
|
55 |
+
loadedStockData = False
|
56 |
+
loadCount = 0
|
57 |
+
maLength = None
|
58 |
+
newlyListedOnly = False
|
59 |
+
vectorSearch = False
|
60 |
+
|
61 |
+
CHROMADB_PATH = "chromadb_store/"
|
62 |
+
|
63 |
+
configManager = ConfigManager.tools()
|
64 |
+
fetcher = Fetcher.tools(configManager)
|
65 |
+
screener = Screener.tools(configManager)
|
66 |
+
candlePatterns = CandlePatterns()
|
67 |
+
|
68 |
+
# Get system wide proxy for networking
|
69 |
+
try:
|
70 |
+
proxyServer = urllib.request.getproxies()['http']
|
71 |
+
except KeyError:
|
72 |
+
proxyServer = ""
|
73 |
+
|
74 |
+
# Clear chromadb store initially
|
75 |
+
if CHROMA_AVAILABLE:
|
76 |
+
chroma_client = chromadb.PersistentClient(path=CHROMADB_PATH)
|
77 |
+
try:
|
78 |
+
chroma_client.delete_collection("nse_stocks")
|
79 |
+
except:
|
80 |
+
pass
|
81 |
+
|
82 |
+
|
83 |
+
# Manage Execution flow
|
84 |
+
|
85 |
+
|
86 |
+
def initExecution():
|
87 |
+
global newlyListedOnly
|
88 |
+
print(colorText.BOLD + colorText.WARN +
|
89 |
+
'[+] Select an Index for Screening: ' + colorText.END)
|
90 |
+
print(colorText.BOLD + '''
|
91 |
+
W > Screen stocks from my own Watchlist
|
92 |
+
N > Nifty Prediction using Artifical Intelligence (Use for Gap-Up/Gap-Down/BTST/STBT)
|
93 |
+
E > Live Index Scan : 5 EMA for Intraday
|
94 |
+
S > Search for Similar Stocks (forming Similar Chart Pattern)
|
95 |
+
|
96 |
+
0 > Screen stocks by the stock names (NSE Stock Code)
|
97 |
+
1 > Nifty 50 2 > Nifty Next 50 3 > Nifty 100
|
98 |
+
4 > Nifty 200 5 > Nifty 500 6 > Nifty Smallcap 50
|
99 |
+
7 > Nifty Smallcap 100 8 > Nifty Smallcap 250 9 > Nifty Midcap 50
|
100 |
+
10 > Nifty Midcap 100 11 > Nifty Midcap 150 13 > Newly Listed (IPOs in last 2 Year)
|
101 |
+
14 > F&O Stocks Only 15 > US S&P 500 16 > Sectoral Indices (NSE)
|
102 |
+
Enter > All Stocks (default) ''' + colorText.END
|
103 |
+
)
|
104 |
+
try:
|
105 |
+
tickerOption = input(
|
106 |
+
colorText.BOLD + colorText.FAIL + '[+] Select option: ')
|
107 |
+
print(colorText.END, end='')
|
108 |
+
if tickerOption == '':
|
109 |
+
tickerOption = 12
|
110 |
+
# elif tickerOption == 'W' or tickerOption == 'w' or tickerOption == 'N' or tickerOption == 'n' or tickerOption == 'E' or tickerOption == 'e':
|
111 |
+
elif not tickerOption.isnumeric():
|
112 |
+
tickerOption = tickerOption.upper()
|
113 |
+
else:
|
114 |
+
tickerOption = int(tickerOption)
|
115 |
+
if(tickerOption < 0 or tickerOption > 16):
|
116 |
+
raise ValueError
|
117 |
+
elif tickerOption == 13:
|
118 |
+
newlyListedOnly = True
|
119 |
+
tickerOption = 12
|
120 |
+
except KeyboardInterrupt:
|
121 |
+
raise KeyboardInterrupt
|
122 |
+
except Exception as e:
|
123 |
+
print(colorText.BOLD + colorText.FAIL +
|
124 |
+
'\n[+] Please enter a valid numeric option & Try Again!' + colorText.END)
|
125 |
+
sleep(2)
|
126 |
+
Utility.tools.clearScreen()
|
127 |
+
return initExecution()
|
128 |
+
|
129 |
+
if tickerOption == 'N' or tickerOption == 'E' or tickerOption == 'S':
|
130 |
+
return tickerOption, 0
|
131 |
+
|
132 |
+
if tickerOption and tickerOption != 'W':
|
133 |
+
print(colorText.BOLD + colorText.WARN +
|
134 |
+
'\n[+] Select a Critera for Stock Screening: ' + colorText.END)
|
135 |
+
print(colorText.BOLD + '''
|
136 |
+
0 > Full Screening (Shows Technical Parameters without Any Criteria)
|
137 |
+
1 > Screen stocks for Breakout or Consolidation
|
138 |
+
2 > Screen for the stocks with recent Breakout & Volume
|
139 |
+
3 > Screen for the Consolidating stocks
|
140 |
+
4 > Screen for the stocks with Lowest Volume in last 'N'-days (Early Breakout Detection)
|
141 |
+
5 > Screen for the stocks with RSI
|
142 |
+
6 > Screen for the stocks showing Reversal Signals
|
143 |
+
7 > Screen for the stocks making Chart Patterns
|
144 |
+
8 > Edit user configuration
|
145 |
+
9 > Show user configuration
|
146 |
+
10 > Show Last Screened Results
|
147 |
+
11 > Help / About Developer
|
148 |
+
12 > Exit''' + colorText.END
|
149 |
+
)
|
150 |
+
try:
|
151 |
+
if tickerOption and tickerOption != 'W':
|
152 |
+
executeOption = input(
|
153 |
+
colorText.BOLD + colorText.FAIL + '[+] Select option: ')
|
154 |
+
print(colorText.END, end='')
|
155 |
+
if executeOption == '':
|
156 |
+
executeOption = 0
|
157 |
+
executeOption = int(executeOption)
|
158 |
+
if(executeOption < 0 or executeOption > 14):
|
159 |
+
raise ValueError
|
160 |
+
else:
|
161 |
+
executeOption = 0
|
162 |
+
except KeyboardInterrupt:
|
163 |
+
raise KeyboardInterrupt
|
164 |
+
except Exception as e:
|
165 |
+
print(colorText.BOLD + colorText.FAIL +
|
166 |
+
'\n[+] Please enter a valid numeric option & Try Again!' + colorText.END)
|
167 |
+
sleep(2)
|
168 |
+
Utility.tools.clearScreen()
|
169 |
+
return initExecution()
|
170 |
+
return tickerOption, executeOption
|
171 |
+
|
172 |
+
# Main function
|
173 |
+
def main(testing=False, testBuild=False, downloadOnly=False, execute_inputs:list = [], isDevVersion=None, backtestDate=date.today()):
|
174 |
+
global screenCounter, screenResultsCounter, stockDict, loadedStockData, keyboardInterruptEvent, loadCount, maLength, newlyListedOnly, vectorSearch
|
175 |
+
screenCounter = multiprocessing.Value('i', 1)
|
176 |
+
screenResultsCounter = multiprocessing.Value('i', 0)
|
177 |
+
keyboardInterruptEvent = multiprocessing.Manager().Event()
|
178 |
+
|
179 |
+
if stockDict is None or Utility.tools.isBacktesting(backtestDate=backtestDate):
|
180 |
+
stockDict = multiprocessing.Manager().dict()
|
181 |
+
loadCount = 0
|
182 |
+
|
183 |
+
minRSI = 0
|
184 |
+
maxRSI = 100
|
185 |
+
insideBarToLookback = 7
|
186 |
+
respChartPattern = 1
|
187 |
+
daysForLowestVolume = 30
|
188 |
+
reversalOption = None
|
189 |
+
|
190 |
+
screenResults = pd.DataFrame(columns=[
|
191 |
+
'Stock', 'Consolidating', 'Breaking-Out', 'LTP', 'Volume', 'MA-Signal', 'RSI', 'Trend', 'Pattern'])
|
192 |
+
saveResults = pd.DataFrame(columns=[
|
193 |
+
'Stock', 'Consolidating', 'Breaking-Out', 'LTP', 'Volume', 'MA-Signal', 'RSI', 'Trend', 'Pattern'])
|
194 |
+
|
195 |
+
|
196 |
+
if testBuild:
|
197 |
+
tickerOption, executeOption = 1, 0
|
198 |
+
elif downloadOnly:
|
199 |
+
tickerOption, executeOption = 12, 2
|
200 |
+
else:
|
201 |
+
try:
|
202 |
+
if execute_inputs != []:
|
203 |
+
if not configManager.checkConfigFile():
|
204 |
+
configManager.setConfig(ConfigManager.parser, default=True, showFileCreatedText=False)
|
205 |
+
try:
|
206 |
+
tickerOption, executeOption = int(execute_inputs[0]), int(execute_inputs[1])
|
207 |
+
if tickerOption == 0:
|
208 |
+
stockCode = execute_inputs[2].replace(" ", "")
|
209 |
+
listStockCodes = stockCode.split(',')
|
210 |
+
except:
|
211 |
+
tickerOption, executeOption = str(execute_inputs[0]), int(execute_inputs[1])
|
212 |
+
if tickerOption == 13:
|
213 |
+
newlyListedOnly = True
|
214 |
+
tickerOption = 12
|
215 |
+
else:
|
216 |
+
tickerOption, executeOption = initExecution()
|
217 |
+
except KeyboardInterrupt:
|
218 |
+
if execute_inputs == [] and not isGui():
|
219 |
+
input(colorText.BOLD + colorText.FAIL +
|
220 |
+
"[+] Press any key to Exit!" + colorText.END)
|
221 |
+
sys.exit(0)
|
222 |
+
|
223 |
+
if executeOption == 4:
|
224 |
+
try:
|
225 |
+
if execute_inputs != []:
|
226 |
+
daysForLowestVolume = int(execute_inputs[2])
|
227 |
+
else:
|
228 |
+
daysForLowestVolume = int(input(colorText.BOLD + colorText.WARN +
|
229 |
+
'\n[+] The Volume should be lowest since last how many candles? '))
|
230 |
+
except ValueError:
|
231 |
+
print(colorText.END)
|
232 |
+
print(colorText.BOLD + colorText.FAIL +
|
233 |
+
'[+] Error: Non-numeric value entered! Screening aborted.' + colorText.END)
|
234 |
+
if not isGui():
|
235 |
+
input('')
|
236 |
+
main()
|
237 |
+
print(colorText.END)
|
238 |
+
if executeOption == 5:
|
239 |
+
if execute_inputs != []:
|
240 |
+
minRSI, maxRSI = int(execute_inputs[2]), int(execute_inputs[3])
|
241 |
+
else:
|
242 |
+
minRSI, maxRSI = Utility.tools.promptRSIValues()
|
243 |
+
if (not minRSI and not maxRSI):
|
244 |
+
print(colorText.BOLD + colorText.FAIL +
|
245 |
+
'\n[+] Error: Invalid values for RSI! Values should be in range of 0 to 100. Screening aborted.' + colorText.END)
|
246 |
+
if not isGui():
|
247 |
+
input('')
|
248 |
+
main()
|
249 |
+
if executeOption == 6:
|
250 |
+
if execute_inputs != []:
|
251 |
+
reversalOption = int(execute_inputs[2])
|
252 |
+
try:
|
253 |
+
maLength = int(execute_inputs[3])
|
254 |
+
except ValueError:
|
255 |
+
pass
|
256 |
+
else:
|
257 |
+
reversalOption, maLength = Utility.tools.promptReversalScreening()
|
258 |
+
if reversalOption is None or reversalOption == 0:
|
259 |
+
if not isGui():
|
260 |
+
main()
|
261 |
+
if executeOption == 7:
|
262 |
+
if execute_inputs != []:
|
263 |
+
respChartPattern = int(execute_inputs[2])
|
264 |
+
try:
|
265 |
+
insideBarToLookback = float(execute_inputs[3])
|
266 |
+
except ValueError:
|
267 |
+
pass
|
268 |
+
else:
|
269 |
+
respChartPattern, insideBarToLookback = Utility.tools.promptChartPatterns()
|
270 |
+
if insideBarToLookback is None:
|
271 |
+
if not isGui():
|
272 |
+
main()
|
273 |
+
if executeOption == 8:
|
274 |
+
configManager.setConfig(ConfigManager.parser)
|
275 |
+
if not isGui():
|
276 |
+
main()
|
277 |
+
if executeOption == 9:
|
278 |
+
configManager.showConfigFile()
|
279 |
+
if not isGui():
|
280 |
+
main()
|
281 |
+
if executeOption == 10:
|
282 |
+
Utility.tools.getLastScreenedResults()
|
283 |
+
if not isGui():
|
284 |
+
main()
|
285 |
+
if executeOption == 11:
|
286 |
+
Utility.tools.showDevInfo()
|
287 |
+
if not isGui():
|
288 |
+
main()
|
289 |
+
if executeOption == 12:
|
290 |
+
if not isGui():
|
291 |
+
input(colorText.BOLD + colorText.FAIL +
|
292 |
+
"[+] Press any key to Exit!" + colorText.END)
|
293 |
+
sys.exit(0)
|
294 |
+
|
295 |
+
if tickerOption == 'W' or tickerOption == 'N' or tickerOption == 'E' or tickerOption == 'S' or (tickerOption >= 0 and tickerOption < 17):
|
296 |
+
configManager.getConfig(ConfigManager.parser)
|
297 |
+
try:
|
298 |
+
if tickerOption == 'W':
|
299 |
+
listStockCodes = fetcher.fetchWatchlist()
|
300 |
+
if listStockCodes is None:
|
301 |
+
input(colorText.BOLD + colorText.FAIL +
|
302 |
+
f'[+] Create the watchlist.xlsx file in {os.getcwd()} and Restart the Program!' + colorText.END)
|
303 |
+
sys.exit(0)
|
304 |
+
elif tickerOption == 'N':
|
305 |
+
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'
|
306 |
+
import tensorflow as tf
|
307 |
+
physical_devices = tf.config.list_physical_devices('GPU')
|
308 |
+
try:
|
309 |
+
tf.config.set_visible_devices([], 'GPU')
|
310 |
+
visible_devices = tf.config.get_visible_devices()
|
311 |
+
for device in visible_devices:
|
312 |
+
assert device.device_type != 'GPU'
|
313 |
+
except:
|
314 |
+
pass
|
315 |
+
prediction = screener.getNiftyPrediction(
|
316 |
+
data=fetcher.fetchLatestNiftyDaily(proxyServer=proxyServer),
|
317 |
+
proxyServer=proxyServer
|
318 |
+
)
|
319 |
+
input('\nPress any key to Continue...\n')
|
320 |
+
return
|
321 |
+
elif tickerOption == 'E':
|
322 |
+
result_df = pd.DataFrame(columns=['Time','Stock/Index','Action','SL','Target','R:R'])
|
323 |
+
last_signal = {}
|
324 |
+
first_scan = True
|
325 |
+
result_df = screener.monitorFiveEma( # Dummy scan to avoid blank table on 1st scan
|
326 |
+
proxyServer=proxyServer,
|
327 |
+
fetcher=fetcher,
|
328 |
+
result_df=result_df,
|
329 |
+
last_signal=last_signal
|
330 |
+
)
|
331 |
+
try:
|
332 |
+
while True:
|
333 |
+
Utility.tools.clearScreen()
|
334 |
+
last_result_len = len(result_df)
|
335 |
+
result_df = screener.monitorFiveEma(
|
336 |
+
proxyServer=proxyServer,
|
337 |
+
fetcher=fetcher,
|
338 |
+
result_df=result_df,
|
339 |
+
last_signal=last_signal
|
340 |
+
)
|
341 |
+
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)
|
342 |
+
print(tabulate(result_df, headers='keys', tablefmt='psql'))
|
343 |
+
print('\nPress Ctrl+C to exit.')
|
344 |
+
if len(result_df) != last_result_len and not first_scan:
|
345 |
+
Utility.tools.alertSound(beeps=5)
|
346 |
+
sleep(60)
|
347 |
+
first_scan = False
|
348 |
+
except KeyboardInterrupt:
|
349 |
+
if not isGui():
|
350 |
+
input('\nPress any key to Continue...\n')
|
351 |
+
return
|
352 |
+
elif tickerOption == 'S':
|
353 |
+
if not CHROMA_AVAILABLE:
|
354 |
+
print(colorText.BOLD + colorText.FAIL +
|
355 |
+
"\n\n[+] ChromaDB not available in your environment! You can't use this feature!\n" + colorText.END)
|
356 |
+
else:
|
357 |
+
if execute_inputs != []:
|
358 |
+
stockCode, candles = execute_inputs[2], execute_inputs[3]
|
359 |
+
else:
|
360 |
+
stockCode, candles = Utility.tools.promptSimilarStockSearch()
|
361 |
+
vectorSearch = [stockCode, candles, True]
|
362 |
+
tickerOption, executeOption = 12, 1
|
363 |
+
listStockCodes = fetcher.fetchStockCodes(tickerOption, proxyServer=proxyServer)
|
364 |
+
else:
|
365 |
+
if tickerOption == 14: # Override config for F&O Stocks
|
366 |
+
configManager.stageTwo = False
|
367 |
+
configManager.minLTP = 0.1
|
368 |
+
configManager.maxLTP = 999999999
|
369 |
+
if (execute_inputs != [] and tickerOption != 0) or execute_inputs == []:
|
370 |
+
listStockCodes = fetcher.fetchStockCodes(tickerOption, proxyServer=proxyServer)
|
371 |
+
except urllib.error.URLError:
|
372 |
+
print(colorText.BOLD + colorText.FAIL +
|
373 |
+
"\n\n[+] Oops! It looks like you don't have an Internet connectivity at the moment! Press any key to exit!" + colorText.END)
|
374 |
+
if not isGui():
|
375 |
+
input('')
|
376 |
+
sys.exit(0)
|
377 |
+
|
378 |
+
if not Utility.tools.isTradingTime() and configManager.cacheEnabled and not loadedStockData and not testing and not Utility.tools.isBacktesting(backtestDate=backtestDate):
|
379 |
+
Utility.tools.loadStockData(stockDict, configManager, proxyServer)
|
380 |
+
loadedStockData = True
|
381 |
+
loadCount = len(stockDict)
|
382 |
+
|
383 |
+
print(colorText.BOLD + colorText.WARN +
|
384 |
+
"[+] Starting Stock Screening.. Press Ctrl+C to stop!\n")
|
385 |
+
|
386 |
+
items = [(tickerOption, executeOption, reversalOption, maLength, daysForLowestVolume, minRSI, maxRSI, respChartPattern, insideBarToLookback, len(listStockCodes),
|
387 |
+
configManager, fetcher, screener, candlePatterns, stock, newlyListedOnly, downloadOnly, vectorSearch, isDevVersion, backtestDate)
|
388 |
+
for stock in listStockCodes]
|
389 |
+
|
390 |
+
tasks_queue = multiprocessing.JoinableQueue()
|
391 |
+
results_queue = multiprocessing.Queue()
|
392 |
+
|
393 |
+
totalConsumers = multiprocessing.cpu_count()
|
394 |
+
if totalConsumers == 1:
|
395 |
+
totalConsumers = 2 # This is required for single core machine
|
396 |
+
if configManager.cacheEnabled is True and multiprocessing.cpu_count() > 2:
|
397 |
+
totalConsumers -= 1
|
398 |
+
consumers = [StockConsumer(tasks_queue, results_queue, screenCounter, screenResultsCounter, stockDict, proxyServer, keyboardInterruptEvent)
|
399 |
+
for _ in range(totalConsumers)]
|
400 |
+
|
401 |
+
for worker in consumers:
|
402 |
+
worker.daemon = True
|
403 |
+
worker.start()
|
404 |
+
|
405 |
+
if testing or testBuild:
|
406 |
+
for item in items:
|
407 |
+
tasks_queue.put(item)
|
408 |
+
result = results_queue.get()
|
409 |
+
if result is not None:
|
410 |
+
screenResults = pd.concat([screenResults, pd.DataFrame([result[0]])], ignore_index=True)
|
411 |
+
saveResults = pd.concat([saveResults, pd.DataFrame([result[1]])], ignore_index=True)
|
412 |
+
if testing or (testBuild and len(screenResults) > 2):
|
413 |
+
break
|
414 |
+
else:
|
415 |
+
for item in items:
|
416 |
+
tasks_queue.put(item)
|
417 |
+
# Append exit signal for each process indicated by None
|
418 |
+
for _ in range(multiprocessing.cpu_count()):
|
419 |
+
tasks_queue.put(None)
|
420 |
+
try:
|
421 |
+
numStocks, totalStocks = len(listStockCodes), len(listStockCodes)
|
422 |
+
os.environ['SCREENIPY_TOTAL_STOCKS'] = str(totalStocks)
|
423 |
+
print(colorText.END+colorText.BOLD)
|
424 |
+
bar, spinner = Utility.tools.getProgressbarStyle()
|
425 |
+
with alive_bar(numStocks, bar=bar, spinner=spinner) as progressbar:
|
426 |
+
while numStocks:
|
427 |
+
result = results_queue.get()
|
428 |
+
if result is not None:
|
429 |
+
screenResults = pd.concat([screenResults, pd.DataFrame([result[0]])], ignore_index=True)
|
430 |
+
saveResults = pd.concat([saveResults, pd.DataFrame([result[1]])], ignore_index=True)
|
431 |
+
numStocks -= 1
|
432 |
+
os.environ['SCREENIPY_SCREEN_COUNTER'] = str(int((totalStocks-numStocks)/totalStocks*100))
|
433 |
+
progressbar.text(colorText.BOLD + colorText.GREEN +
|
434 |
+
f'Found {screenResultsCounter.value} Stocks' + colorText.END)
|
435 |
+
progressbar()
|
436 |
+
except KeyboardInterrupt:
|
437 |
+
try:
|
438 |
+
keyboardInterruptEvent.set()
|
439 |
+
except KeyboardInterrupt:
|
440 |
+
pass
|
441 |
+
print(colorText.BOLD + colorText.FAIL +
|
442 |
+
"\n[+] Terminating Script, Please wait..." + colorText.END)
|
443 |
+
for worker in consumers:
|
444 |
+
worker.terminate()
|
445 |
+
|
446 |
+
print(colorText.END)
|
447 |
+
# Exit all processes. Without this, it threw error in next screening session
|
448 |
+
for worker in consumers:
|
449 |
+
try:
|
450 |
+
worker.terminate()
|
451 |
+
except OSError as e:
|
452 |
+
if e.winerror == 5:
|
453 |
+
pass
|
454 |
+
|
455 |
+
# Flush the queue so depending processes will end
|
456 |
+
from queue import Empty
|
457 |
+
while True:
|
458 |
+
try:
|
459 |
+
_ = tasks_queue.get(False)
|
460 |
+
except Exception as e:
|
461 |
+
break
|
462 |
+
|
463 |
+
if CHROMA_AVAILABLE and type(vectorSearch) == list and vectorSearch[2]:
|
464 |
+
chroma_client = chromadb.PersistentClient(path=CHROMADB_PATH)
|
465 |
+
collection = chroma_client.get_collection(name="nse_stocks")
|
466 |
+
query_embeddings= collection.get(ids = [stockCode], include=["embeddings"])["embeddings"]
|
467 |
+
results = collection.query(
|
468 |
+
query_embeddings=query_embeddings,
|
469 |
+
n_results=4
|
470 |
+
)['ids'][0]
|
471 |
+
try:
|
472 |
+
results.remove(stockCode)
|
473 |
+
except ValueError:
|
474 |
+
pass
|
475 |
+
matchedScreenResults, matchedSaveResults = pd.DataFrame(columns=screenResults.columns), pd.DataFrame(columns=saveResults.columns)
|
476 |
+
for stk in results:
|
477 |
+
matchedScreenResults = pd.concat([matchedScreenResults, screenResults[screenResults['Stock'].str.contains(stk)]], ignore_index=True)
|
478 |
+
matchedSaveResults = pd.concat([matchedSaveResults, saveResults[saveResults['Stock'].str.contains(stk)]], ignore_index=True)
|
479 |
+
screenResults, saveResults = matchedScreenResults, matchedSaveResults
|
480 |
+
|
481 |
+
screenResults.sort_values(by=['Stock'], ascending=True, inplace=True)
|
482 |
+
saveResults.sort_values(by=['Stock'], ascending=True, inplace=True)
|
483 |
+
screenResults.set_index('Stock', inplace=True)
|
484 |
+
saveResults.set_index('Stock', inplace=True)
|
485 |
+
screenResults.rename(
|
486 |
+
columns={
|
487 |
+
'Trend': f'Trend ({configManager.daysToLookback}Days)',
|
488 |
+
'Breaking-Out': f'Breakout ({configManager.daysToLookback}Days)',
|
489 |
+
'LTP': 'LTP (%% Chng)'
|
490 |
+
},
|
491 |
+
inplace=True
|
492 |
+
)
|
493 |
+
saveResults.rename(
|
494 |
+
columns={
|
495 |
+
'Trend': f'Trend ({configManager.daysToLookback}Days)',
|
496 |
+
'Breaking-Out': f'Breakout ({configManager.daysToLookback}Days)',
|
497 |
+
},
|
498 |
+
inplace=True
|
499 |
+
)
|
500 |
+
print(tabulate(screenResults, headers='keys', tablefmt='psql'))
|
501 |
+
|
502 |
+
print(colorText.BOLD + colorText.GREEN +
|
503 |
+
f"[+] Found {len(screenResults)} Stocks." + colorText.END)
|
504 |
+
if configManager.cacheEnabled and not Utility.tools.isTradingTime() and not testing and not Utility.tools.isBacktesting(backtestDate=backtestDate):
|
505 |
+
print(colorText.BOLD + colorText.GREEN +
|
506 |
+
"[+] Caching Stock Data for future use, Please Wait... " + colorText.END, end='')
|
507 |
+
Utility.tools.saveStockData(
|
508 |
+
stockDict, configManager, loadCount)
|
509 |
+
|
510 |
+
Utility.tools.setLastScreenedResults(screenResults)
|
511 |
+
Utility.tools.setLastScreenedResults(saveResults, unformatted=True)
|
512 |
+
if not testBuild and not downloadOnly:
|
513 |
+
Utility.tools.promptSaveResults(saveResults)
|
514 |
+
print(colorText.BOLD + colorText.WARN +
|
515 |
+
"[+] Note: Trend calculation is based on number of days recent to screen as per your configuration." + colorText.END)
|
516 |
+
print(colorText.BOLD + colorText.GREEN +
|
517 |
+
"[+] Screening Completed! Press Enter to Continue.." + colorText.END)
|
518 |
+
if not isGui():
|
519 |
+
input('')
|
520 |
+
newlyListedOnly = False
|
521 |
+
vectorSearch = False
|
522 |
+
|
523 |
+
|
524 |
+
if __name__ == "__main__":
|
525 |
+
Utility.tools.clearScreen()
|
526 |
+
isDevVersion = OTAUpdater.checkForUpdate(proxyServer, VERSION)
|
527 |
+
if not configManager.checkConfigFile():
|
528 |
+
configManager.setConfig(ConfigManager.parser, default=True, showFileCreatedText=False)
|
529 |
+
if args.testbuild:
|
530 |
+
print(colorText.BOLD + colorText.FAIL +"[+] Started in TestBuild mode!" + colorText.END)
|
531 |
+
main(testBuild=True)
|
532 |
+
elif args.download:
|
533 |
+
print(colorText.BOLD + colorText.FAIL +"[+] Download ONLY mode! Stocks will not be screened!" + colorText.END)
|
534 |
+
main(downloadOnly=True)
|
535 |
+
else:
|
536 |
+
try:
|
537 |
+
while True:
|
538 |
+
main()
|
539 |
+
except Exception as e:
|
540 |
+
raise e
|
541 |
+
if isDevVersion == OTAUpdater.developmentVersion:
|
542 |
+
raise(e)
|
543 |
+
input(colorText.BOLD + colorText.FAIL +
|
544 |
+
"[+] Press any key to Exit!" + colorText.END)
|
545 |
+
sys.exit(1)
|
src/screenipy.spec
ADDED
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# -*- mode: python ; coding: utf-8 -*-
|
2 |
+
|
3 |
+
|
4 |
+
block_cipher = None
|
5 |
+
|
6 |
+
|
7 |
+
a = Analysis(['screenipy.py'],
|
8 |
+
pathex=['/mnt/40FEE0813E8AAC6F/Personal/Screeni-py/src'],
|
9 |
+
binaries=[],
|
10 |
+
datas=[],
|
11 |
+
hiddenimports=['cmath', 'talib.stream', 'numpy'],
|
12 |
+
hookspath=[],
|
13 |
+
runtime_hooks=[],
|
14 |
+
excludes=[],
|
15 |
+
win_no_prefer_redirects=False,
|
16 |
+
win_private_assemblies=False,
|
17 |
+
cipher=block_cipher,
|
18 |
+
noarchive=False)
|
19 |
+
pyz = PYZ(a.pure, a.zipped_data,
|
20 |
+
cipher=block_cipher)
|
21 |
+
exe = EXE(pyz,
|
22 |
+
a.scripts,
|
23 |
+
a.binaries,
|
24 |
+
a.zipfiles,
|
25 |
+
a.datas,
|
26 |
+
[],
|
27 |
+
name='screenipy',
|
28 |
+
debug=False,
|
29 |
+
bootloader_ignore_signals=False,
|
30 |
+
strip=False,
|
31 |
+
upx=True,
|
32 |
+
upx_exclude=[],
|
33 |
+
runtime_tmpdir=None,
|
34 |
+
console=True , icon='icon.ico')
|
src/streamlit_app.py
ADDED
@@ -0,0 +1,529 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import random
|
2 |
+
import streamlit as st
|
3 |
+
import streamlit.components.v1 as components
|
4 |
+
import requests
|
5 |
+
import os
|
6 |
+
import configparser
|
7 |
+
import urllib
|
8 |
+
import datetime
|
9 |
+
from num2words import num2words
|
10 |
+
from time import sleep
|
11 |
+
from pathlib import Path
|
12 |
+
from threading import Thread
|
13 |
+
from time import sleep
|
14 |
+
from math import floor
|
15 |
+
import classes.ConfigManager as ConfigManager
|
16 |
+
import classes.Utility as Utility
|
17 |
+
import classes.Fetcher as Fetcher
|
18 |
+
|
19 |
+
st.set_page_config(layout="wide", page_title="Screeni-py", page_icon="📈")
|
20 |
+
|
21 |
+
# Set protobuf to python to avoid TF error (This is a Slower infernece)
|
22 |
+
os.environ["PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION"] = "python"
|
23 |
+
os.environ["TERM"] = "xterm"
|
24 |
+
|
25 |
+
import pandas as pd
|
26 |
+
from screenipy import main as screenipy_main
|
27 |
+
from classes.OtaUpdater import OTAUpdater
|
28 |
+
from classes.Changelog import VERSION
|
29 |
+
|
30 |
+
# Get system wide proxy for networking
|
31 |
+
try:
|
32 |
+
proxyServer = urllib.request.getproxies()['http']
|
33 |
+
except KeyError:
|
34 |
+
proxyServer = ""
|
35 |
+
|
36 |
+
isDevVersion, guiUpdateMessage = None, None
|
37 |
+
|
38 |
+
@st.cache_data(ttl='1h', show_spinner=False)
|
39 |
+
def check_updates():
|
40 |
+
isDevVersion, guiUpdateMessage = OTAUpdater.checkForUpdate(proxyServer, VERSION)
|
41 |
+
return isDevVersion, guiUpdateMessage
|
42 |
+
|
43 |
+
isDevVersion, guiUpdateMessage = check_updates()
|
44 |
+
|
45 |
+
execute_inputs = []
|
46 |
+
|
47 |
+
def show_df_as_result_table():
|
48 |
+
try:
|
49 |
+
df:pd.DataFrame = pd.read_pickle('last_screened_unformatted_results.pkl')
|
50 |
+
ac, cc, bc = st.columns([6,1,1])
|
51 |
+
ac.markdown(f'#### 🔍 Found {len(df)} Results')
|
52 |
+
clear_cache_btn = cc.button(
|
53 |
+
label='Clear Cached Data',
|
54 |
+
use_container_width=True,
|
55 |
+
key=random.randint(1,999999999),
|
56 |
+
)
|
57 |
+
if clear_cache_btn:
|
58 |
+
os.system('rm stock_data_*.pkl')
|
59 |
+
st.toast('Stock Cache Deleted!', icon='🗑️')
|
60 |
+
bc.download_button(
|
61 |
+
label="Download Results",
|
62 |
+
data=df.to_csv().encode('utf-8'),
|
63 |
+
file_name=f'screenipy_results_{datetime.datetime.now().strftime("%H:%M:%S_%d-%m-%Y")}.csv',
|
64 |
+
mime='text/csv',
|
65 |
+
type='secondary',
|
66 |
+
use_container_width=True
|
67 |
+
)
|
68 |
+
if type(execute_inputs[0]) == str or int(execute_inputs[0]) < 15:
|
69 |
+
df.index = df.index.map(lambda x: "https://in.tradingview.com/chart?symbol=NSE%3A" + x)
|
70 |
+
df.index = df.index.map(lambda x: f'<a href="{x}" target="_blank">{x.split("%3A")[-1]}</a>')
|
71 |
+
elif execute_inputs[0] == '16':
|
72 |
+
try:
|
73 |
+
fetcher = Fetcher.tools(configManager=ConfigManager.tools())
|
74 |
+
url_dict_reversed = {key.replace('^','').replace('.NS',''): value for key, value in fetcher.getAllNiftyIndices().items()}
|
75 |
+
url_dict_reversed = {v: k for k, v in url_dict_reversed.items()}
|
76 |
+
df.index = df.index.map(lambda x: "https://in.tradingview.com/chart?symbol=NSE%3A" + url_dict_reversed[x])
|
77 |
+
url_dict_reversed = {v: k for k, v in url_dict_reversed.items()}
|
78 |
+
df.index = df.index.map(lambda x: f'<a href="{x}" target="_blank">{url_dict_reversed[x.split("%3A")[-1]]}</a>')
|
79 |
+
except KeyError:
|
80 |
+
pass
|
81 |
+
else:
|
82 |
+
df.index = df.index.map(lambda x: "https://in.tradingview.com/chart?symbol=" + x)
|
83 |
+
df.index = df.index.map(lambda x: f'<a href="{x}" target="_blank">{x.split("=")[-1]}</a>')
|
84 |
+
df['Stock'] = df.index
|
85 |
+
stock_column = df.pop('Stock') # Remove 'Age' column and store it separately
|
86 |
+
df.insert(0, 'Stock', stock_column)
|
87 |
+
st.write(df.to_html(escape=False, index=False, index_names=False), unsafe_allow_html=True)
|
88 |
+
st.write(' ')
|
89 |
+
except FileNotFoundError:
|
90 |
+
st.error('Last Screened results are not available at the moment')
|
91 |
+
except Exception as e:
|
92 |
+
st.error('No Dataframe found for last_screened_results.pkl')
|
93 |
+
st.exception(e)
|
94 |
+
|
95 |
+
def on_config_change():
|
96 |
+
configManager = ConfigManager.tools()
|
97 |
+
configManager.period = period
|
98 |
+
configManager.daysToLookback = daystolookback
|
99 |
+
configManager.duration = duration
|
100 |
+
configManager.minLTP, configManager.maxLTP = minprice, maxprice
|
101 |
+
configManager.volumeRatio, configManager.consolidationPercentage = volumeratio, consolidationpercentage
|
102 |
+
configManager.shuffle = shuffle
|
103 |
+
configManager.cacheEnabled = cache
|
104 |
+
configManager.stageTwo = stagetwo
|
105 |
+
configManager.useEMA = useema
|
106 |
+
configManager.setConfig(configparser.ConfigParser(strict=False), default=True, showFileCreatedText=False)
|
107 |
+
st.toast('Configuration Saved', icon='💾')
|
108 |
+
|
109 |
+
def on_start_button_click():
|
110 |
+
global execute_inputs
|
111 |
+
if isDevVersion != None:
|
112 |
+
st.info(f'Received inputs (Debug only): {execute_inputs}')
|
113 |
+
|
114 |
+
def dummy_call():
|
115 |
+
try:
|
116 |
+
screenipy_main(execute_inputs=execute_inputs, isDevVersion=isDevVersion, backtestDate=backtestDate)
|
117 |
+
except StopIteration:
|
118 |
+
pass
|
119 |
+
except requests.exceptions.RequestException:
|
120 |
+
os.environ['SCREENIPY_REQ_ERROR'] = "TRUE"
|
121 |
+
|
122 |
+
if Utility.tools.isBacktesting(backtestDate=backtestDate):
|
123 |
+
st.write(f'Running in :red[**Backtesting Mode**] for *T = {str(backtestDate)}* (Y-M-D) : [Backtesting data is subjected to availability as per the API limits]')
|
124 |
+
st.write('Backtesting is :red[Not Supported] for Intraday timeframes')
|
125 |
+
t = Thread(target=dummy_call)
|
126 |
+
t.start()
|
127 |
+
|
128 |
+
st.markdown("""
|
129 |
+
<style>
|
130 |
+
.stProgress p {
|
131 |
+
font-size: 17px;
|
132 |
+
}
|
133 |
+
</style>
|
134 |
+
""", unsafe_allow_html=True)
|
135 |
+
|
136 |
+
progress_text = "🚀 Preparing Screener, Please Wait! "
|
137 |
+
progress_bar = st.progress(0, text=progress_text)
|
138 |
+
|
139 |
+
os.environ['SCREENIPY_SCREEN_COUNTER'] = '0'
|
140 |
+
while int(os.environ.get('SCREENIPY_SCREEN_COUNTER')) < 100:
|
141 |
+
sleep(0.05)
|
142 |
+
cnt = int(os.environ.get('SCREENIPY_SCREEN_COUNTER'))
|
143 |
+
if cnt > 0:
|
144 |
+
progress_text = "🔍 Screening stocks for you... "
|
145 |
+
progress_bar.progress(cnt, text=progress_text + f"**:red[{cnt}%]** Done")
|
146 |
+
if os.environ.get('SCREENIPY_REQ_ERROR') and "TRUE" in os.environ.get('SCREENIPY_REQ_ERROR'):
|
147 |
+
ac, bc = st.columns([2,1])
|
148 |
+
ac.error(':disappointed: Failed to reach Screeni-py server!')
|
149 |
+
ac.info('This issue is related with your Internet Service Provider (ISP) - Many **Jio** users faced this issue as the screeni-py data cache server appeared to be not reachable for them!\n\nPlease watch the YouTube video attached here to resolve this issue on your local system\n\nTry with another ISP/Network or go through this thread carefully to resolve this error: https://github.com/pranjal-joshi/Screeni-py/issues/164', icon='ℹ️')
|
150 |
+
bc.video('https://youtu.be/JADNADDNTmU')
|
151 |
+
del os.environ['SCREENIPY_REQ_ERROR']
|
152 |
+
break
|
153 |
+
|
154 |
+
t.join()
|
155 |
+
progress_bar.empty()
|
156 |
+
|
157 |
+
def nifty_predict(col):
|
158 |
+
with col.container():
|
159 |
+
with st.spinner('🔮 Taking a Look into the Future, Please wait...'):
|
160 |
+
import classes.Fetcher as Fetcher
|
161 |
+
import classes.Screener as Screener
|
162 |
+
configManager = ConfigManager.tools()
|
163 |
+
fetcher = Fetcher.tools(configManager)
|
164 |
+
screener = Screener.tools(configManager)
|
165 |
+
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'
|
166 |
+
prediction, trend, confidence, data_used = screener.getNiftyPrediction(
|
167 |
+
data=fetcher.fetchLatestNiftyDaily(proxyServer=proxyServer),
|
168 |
+
proxyServer=proxyServer
|
169 |
+
)
|
170 |
+
if 'BULLISH' in trend:
|
171 |
+
col.success(f'Market may Open **Gap Up** next day!\n\nProbability/Strength of Prediction = {confidence}%', icon='📈')
|
172 |
+
elif 'BEARISH' in trend:
|
173 |
+
col.error(f'Market may Open **Gap Down** next day!\n\nProbability/Strength of Prediction = {confidence}%', icon='📉')
|
174 |
+
else:
|
175 |
+
col.info("Couldn't determine the Trend. Try again later!")
|
176 |
+
col.warning('The AI prediction should be executed After 3 PM or Around the Closing hours as the Prediction Accuracy is based on the Closing price!\n\nThis is Just a Statistical Prediction and There are Chances of **False** Predictions!', icon='⚠️')
|
177 |
+
col.info("What's New in **v3**?\n\nMachine Learning model (v3) now uses Nifty, Crude and Gold Historical prices to Predict the Gap!", icon='🆕')
|
178 |
+
col.markdown("**Following data is used to make above prediction:**")
|
179 |
+
col.dataframe(data_used)
|
180 |
+
|
181 |
+
def find_similar_stocks(stockCode:str, candles:int):
|
182 |
+
global execute_inputs
|
183 |
+
stockCode = stockCode.upper()
|
184 |
+
if ',' in stockCode or ' ' in stockCode or stockCode == '':
|
185 |
+
st.error('Invalid Character in Stock Name!', icon='😾')
|
186 |
+
return False
|
187 |
+
else:
|
188 |
+
execute_inputs = ['S', 0, stockCode, candles, 'N']
|
189 |
+
on_start_button_click()
|
190 |
+
st.toast('Screening Completed!', icon='🎉')
|
191 |
+
sleep(2)
|
192 |
+
return True
|
193 |
+
|
194 |
+
def get_extra_inputs(tickerOption, executeOption, c_index=None, c_criteria=None, start_button=None):
|
195 |
+
global execute_inputs
|
196 |
+
if not tickerOption.isnumeric():
|
197 |
+
execute_inputs = [tickerOption, 0, 'N']
|
198 |
+
elif int(tickerOption) == 0 or tickerOption is None:
|
199 |
+
stock_codes:str = c_index.text_input('Enter Stock Code(s)', placeholder='SBIN, INFY, ITC')
|
200 |
+
execute_inputs = [tickerOption, executeOption, stock_codes.upper(), 'N']
|
201 |
+
return
|
202 |
+
elif int(executeOption) >= 0 and int(executeOption) < 4:
|
203 |
+
execute_inputs = [tickerOption, executeOption, 'N']
|
204 |
+
elif int(executeOption) == 4:
|
205 |
+
num_candles = c_criteria.text_input('The Volume should be lowest since last how many candles?', value='20')
|
206 |
+
if num_candles:
|
207 |
+
execute_inputs = [tickerOption, executeOption, num_candles, 'N']
|
208 |
+
else:
|
209 |
+
c_criteria.error("Number of Candles can't be left blank!")
|
210 |
+
elif int(executeOption) == 5:
|
211 |
+
min_rsi, max_rsi = c_criteria.columns((1,1))
|
212 |
+
min_rsi = min_rsi.number_input('Min RSI', min_value=0, max_value=100, value=50, step=1, format="%d")
|
213 |
+
max_rsi = max_rsi.number_input('Max RSI', min_value=0, max_value=100, value=70, step=1, format="%d")
|
214 |
+
if min_rsi >= max_rsi:
|
215 |
+
c_criteria.warning('WARNING: Min RSI ≥ Max RSI')
|
216 |
+
else:
|
217 |
+
execute_inputs = [tickerOption, executeOption, min_rsi, max_rsi, 'N']
|
218 |
+
elif int(executeOption) == 6:
|
219 |
+
c1, c2 = c_criteria.columns((7,2))
|
220 |
+
select_reversal = int(c1.selectbox('Select Type of Reversal',
|
221 |
+
options = [
|
222 |
+
'1 > Buy Signal (Bullish Reversal)',
|
223 |
+
'2 > Sell Signal (Bearish Reversal)',
|
224 |
+
'3 > Momentum Gainers (Rising Bullish Momentum)',
|
225 |
+
'4 > Reversal at Moving Average (Bullish Reversal)',
|
226 |
+
'5 > Volume Spread Analysis (Bullish VSA Reversal)',
|
227 |
+
'6 > Narrow Range (NRx) Reversal',
|
228 |
+
'7 > Lorentzian Classifier (Machine Learning based indicator)',
|
229 |
+
'8 > RSI Crossing with 9 SMA of RSI itself'
|
230 |
+
]
|
231 |
+
).split(' ')[0])
|
232 |
+
if select_reversal == 4:
|
233 |
+
ma_length = c2.number_input('MA Length', value=44, step=1, format="%d")
|
234 |
+
execute_inputs = [tickerOption, executeOption, select_reversal, ma_length, 'N']
|
235 |
+
elif select_reversal == 6:
|
236 |
+
range = c2.number_input('NR(x)',min_value=1, max_value=14, value=4, step=1, format="%d")
|
237 |
+
execute_inputs = [tickerOption, executeOption, select_reversal, range, 'N']
|
238 |
+
elif select_reversal == 7:
|
239 |
+
signal = int(c2.selectbox('Signal Type',
|
240 |
+
options = [
|
241 |
+
'1 > Any',
|
242 |
+
'2 > Buy',
|
243 |
+
'3 > Sell',
|
244 |
+
]
|
245 |
+
).split(' ')[0])
|
246 |
+
execute_inputs = [tickerOption, executeOption, select_reversal, signal, 'N']
|
247 |
+
else:
|
248 |
+
execute_inputs = [tickerOption, executeOption, select_reversal, 'N']
|
249 |
+
elif int(executeOption) == 7:
|
250 |
+
c1, c2 = c_criteria.columns((11,4))
|
251 |
+
select_pattern = int(c1.selectbox('Select Chart Pattern',
|
252 |
+
options = [
|
253 |
+
'1 > Bullish Inside Bar (Flag) Pattern',
|
254 |
+
'2 > Bearish Inside Bar (Flag) Pattern',
|
255 |
+
'3 > Confluence (50 & 200 MA/EMA)',
|
256 |
+
'4 > VCP (Experimental)',
|
257 |
+
'5 > Buying at Trendline (Ideal for Swing/Mid/Long term)',
|
258 |
+
]
|
259 |
+
).split(' ')[0])
|
260 |
+
if select_pattern == 1 or select_pattern == 2:
|
261 |
+
num_candles = c2.number_input('Lookback Candles', min_value=1, max_value=25, value=12, step=1, format="%d")
|
262 |
+
execute_inputs = [tickerOption, executeOption, select_pattern, int(num_candles), 'N']
|
263 |
+
elif select_pattern == 3:
|
264 |
+
confluence_percentage = c2.number_input('MA Confluence %', min_value=0.1, max_value=5.0, value=1.0, step=0.1, format="%1.1f")/100.0
|
265 |
+
execute_inputs = [tickerOption, executeOption, select_pattern, confluence_percentage, 'N']
|
266 |
+
else:
|
267 |
+
execute_inputs = [tickerOption, executeOption, select_pattern, 'N']
|
268 |
+
|
269 |
+
ac, bc = st.columns([13,1])
|
270 |
+
|
271 |
+
ac.title('📈 Screeni-py')
|
272 |
+
if guiUpdateMessage == "":
|
273 |
+
ac.subheader('Find Breakouts, Just in Time!')
|
274 |
+
|
275 |
+
if isDevVersion:
|
276 |
+
ac.warning(guiUpdateMessage, icon='⚠️')
|
277 |
+
elif guiUpdateMessage != "":
|
278 |
+
ac.success(guiUpdateMessage, icon='❇️')
|
279 |
+
|
280 |
+
telegram_url = "https://user-images.githubusercontent.com/6128978/217814499-7934edf6-fcc3-46d7-887e-7757c94e1632.png"
|
281 |
+
bc.divider()
|
282 |
+
bc.image(telegram_url, width=96)
|
283 |
+
|
284 |
+
tab_screen, tab_similar, tab_nifty, tab_config, tab_psc, tab_about = st.tabs(['Screen Stocks', 'Search Similar Stocks', 'Nifty-50 Gap Prediction', 'Configuration', 'Position Size Calculator', 'About'])
|
285 |
+
|
286 |
+
with tab_screen:
|
287 |
+
st.markdown("""
|
288 |
+
<style>
|
289 |
+
.block-container {
|
290 |
+
padding-top: 1rem;
|
291 |
+
padding-bottom: 0rem;
|
292 |
+
padding-left: 5rem;
|
293 |
+
padding-right: 5rem;
|
294 |
+
}
|
295 |
+
.stButton>button {
|
296 |
+
height: 70px;
|
297 |
+
}
|
298 |
+
.stDownloadButton>button {
|
299 |
+
height: 70px;
|
300 |
+
}
|
301 |
+
th {
|
302 |
+
text-align: left;
|
303 |
+
}
|
304 |
+
</style>
|
305 |
+
""",
|
306 |
+
unsafe_allow_html=True)
|
307 |
+
|
308 |
+
list_index = [
|
309 |
+
'All Stocks (Default)',
|
310 |
+
# 'W > Screen stocks from my own Watchlist',
|
311 |
+
# 'N > Nifty Prediction using Artifical Intelligence (Use for Gap-Up/Gap-Down/BTST/STBT)',
|
312 |
+
# 'E > Live Index Scan : 5 EMA for Intraday',
|
313 |
+
'0 > By Stock Names (NSE Stock Code)',
|
314 |
+
'1 > Nifty 50',
|
315 |
+
'2 > Nifty Next 50',
|
316 |
+
'3 > Nifty 100',
|
317 |
+
'4 > Nifty 200',
|
318 |
+
'5 > Nifty 500',
|
319 |
+
'6 > Nifty Smallcap 50',
|
320 |
+
'7 > Nifty Smallcap 100',
|
321 |
+
'8 > Nifty Smallcap 250',
|
322 |
+
'9 > Nifty Midcap 50',
|
323 |
+
'10 > Nifty Midcap 100',
|
324 |
+
'11 > Nifty Midcap 150',
|
325 |
+
'13 > Newly Listed (IPOs in last 2 Year)',
|
326 |
+
'14 > F&O Stocks Only',
|
327 |
+
'15 > US S&P 500',
|
328 |
+
'16 > Sectoral Indices (NSE)'
|
329 |
+
]
|
330 |
+
|
331 |
+
list_criteria = [
|
332 |
+
'0 > Full Screening (Shows Technical Parameters without Any Criteria)',
|
333 |
+
'1 > Screen stocks for Breakout or Consolidation',
|
334 |
+
'2 > Screen for the stocks with recent Breakout & Volume',
|
335 |
+
'3 > Screen for the Consolidating stocks',
|
336 |
+
'4 > Screen for the stocks with Lowest Volume in last N-days (Early Breakout Detection)',
|
337 |
+
'5 > Screen for the stocks with RSI',
|
338 |
+
'6 > Screen for the stocks showing Reversal Signals',
|
339 |
+
'7 > Screen for the stocks making Chart Patterns',
|
340 |
+
]
|
341 |
+
|
342 |
+
configManager = ConfigManager.tools()
|
343 |
+
configManager.getConfig(parser=ConfigManager.parser)
|
344 |
+
|
345 |
+
c_index, c_datepick, c_criteria, c_button_start = st.columns((2,1,4,1))
|
346 |
+
|
347 |
+
tickerOption = c_index.selectbox('Select Index', options=list_index).split(' ')
|
348 |
+
tickerOption = str(12 if '>' not in tickerOption else int(tickerOption[0]) if tickerOption[0].isnumeric() else str(tickerOption[0]))
|
349 |
+
picked_date = c_datepick.date_input(label='Screen/Backtest For', max_value=datetime.date.today(), value=datetime.date.today())
|
350 |
+
if picked_date:
|
351 |
+
backtestDate = picked_date
|
352 |
+
|
353 |
+
executeOption = str(c_criteria.selectbox('Select Screening Criteria', options=list_criteria).split(' ')[0])
|
354 |
+
|
355 |
+
start_button = c_button_start.button('Start Screening', type='primary', key='start_button', use_container_width=True)
|
356 |
+
|
357 |
+
get_extra_inputs(tickerOption=tickerOption, executeOption=executeOption, c_index=c_index, c_criteria=c_criteria, start_button=start_button)
|
358 |
+
|
359 |
+
if start_button:
|
360 |
+
on_start_button_click()
|
361 |
+
st.toast('Screening Completed!', icon='🎉')
|
362 |
+
sleep(2)
|
363 |
+
|
364 |
+
with st.container():
|
365 |
+
show_df_as_result_table()
|
366 |
+
|
367 |
+
with tab_config:
|
368 |
+
configManager = ConfigManager.tools()
|
369 |
+
configManager.getConfig(parser=ConfigManager.parser)
|
370 |
+
|
371 |
+
ac, bc = st.columns([10,2])
|
372 |
+
ac.markdown('### 🔧 Screening Configuration')
|
373 |
+
bc.download_button(
|
374 |
+
label="Export Configuration",
|
375 |
+
data=Path('screenipy.ini').read_text(),
|
376 |
+
file_name='screenipy.ini',
|
377 |
+
mime='text/plain',
|
378 |
+
type='primary',
|
379 |
+
use_container_width=True
|
380 |
+
)
|
381 |
+
|
382 |
+
ac, bc, cc = st.columns([1,1,1])
|
383 |
+
|
384 |
+
period_options = ['15d','60d','300d','52wk','3y','5y','max']
|
385 |
+
duration_options = ['5m','15m','1h','4h','1d','1wk']
|
386 |
+
|
387 |
+
# period = ac.text_input('Period', value=f'{configManager.period}', placeholder='300d / 52wk ')
|
388 |
+
period = ac.selectbox('Period', options=period_options, index=period_options.index(configManager.period), placeholder='300d / 52wk')
|
389 |
+
daystolookback = bc.number_input('Lookback Period (Number of Candles)', value=configManager.daysToLookback, step=1)
|
390 |
+
# duration = cc.text_input('Candle Duration', value=f'{configManager.duration}', placeholder='15m / 1d / 1wk')
|
391 |
+
duration = cc.selectbox('Candle Duration', options=duration_options, index=duration_options.index(configManager.duration), placeholder='15m / 1d / 1wk')
|
392 |
+
if 'm' in duration or 'h' in duration:
|
393 |
+
cc.write('For Intraday duartion, Max :red[value of period <= 60d]')
|
394 |
+
|
395 |
+
ac, bc = st.columns([1,1])
|
396 |
+
minprice = ac.number_input('Minimum Price (Stocks below this will be ignored)', value=float(configManager.minLTP), step=0.1)
|
397 |
+
maxprice = bc.number_input('Maximum Price (Stocks above this will be ignored)', value=float(configManager.maxLTP), step=0.1)
|
398 |
+
|
399 |
+
ac, bc = st.columns([1,1])
|
400 |
+
volumeratio = ac.number_input('Volume multiplier for Breakout confirmation', value=float(configManager.volumeRatio), step=0.1)
|
401 |
+
consolidationpercentage = bc.number_input('Range consolidation (%)', value=int(configManager.consolidationPercentage), step=1)
|
402 |
+
|
403 |
+
ac, bc, cc, dc = st.columns([1,1,1,1])
|
404 |
+
shuffle = ac.checkbox('Shuffle stocks while screening', value=configManager.shuffleEnabled, disabled=True)
|
405 |
+
cache = bc.checkbox('Enable caching of stock data after market hours', value=configManager.cacheEnabled, disabled=True)
|
406 |
+
stagetwo = cc.checkbox('Screen only for [Stage-2](https://www.investopedia.com/articles/investing/070715/trading-stage-analysis.asp#:~:text=placed%20stops.-,Stage%202%3A%20Uptrends,-Image%20by%20Sabrina) stocks', value=configManager.stageTwo)
|
407 |
+
useema = dc.checkbox('Use EMA instead of SMA', value=configManager.useEMA)
|
408 |
+
|
409 |
+
save_button = st.button('Save Configuration', on_click=on_config_change, type='primary', use_container_width=True)
|
410 |
+
|
411 |
+
st.markdown('### Import Your Own Configuration:')
|
412 |
+
uploaded_file = st.file_uploader('Upload screenipy.ini file')
|
413 |
+
|
414 |
+
if uploaded_file is not None:
|
415 |
+
bytes_data = uploaded_file.getvalue()
|
416 |
+
with open('screenipy.ini', 'wb') as f:
|
417 |
+
f.write(bytes_data)
|
418 |
+
st.toast('Configuration Imported', icon='⚙️')
|
419 |
+
|
420 |
+
with tab_nifty:
|
421 |
+
ac, bc = st.columns([9,1])
|
422 |
+
|
423 |
+
ac.subheader('🧠 AI-based prediction for Next Day Nifty-50 Gap Up / Gap Down')
|
424 |
+
bc.button('**Predict**', type='primary', on_click=nifty_predict, args=(ac,), use_container_width=True)
|
425 |
+
|
426 |
+
with tab_similar:
|
427 |
+
|
428 |
+
st.subheader('🕵🏻 Find Stocks forming Similar Chart Patterns')
|
429 |
+
ac, bc, cc = st.columns([4,2,1])
|
430 |
+
|
431 |
+
stockCode = ac.text_input('Enter Stock Name and Press Enter', placeholder='HDFCBANK')
|
432 |
+
candles = bc.number_input('Lookback Period (No. of Candles)', min_value=1, step=1, value=int(configManager.daysToLookback))
|
433 |
+
similar_search_button = cc.button('**Search**', type='primary', use_container_width=True)
|
434 |
+
|
435 |
+
if similar_search_button:
|
436 |
+
result = find_similar_stocks(stockCode, candles)
|
437 |
+
if result:
|
438 |
+
with st.container():
|
439 |
+
show_df_as_result_table()
|
440 |
+
st.write('Click [**here**](https://medium.com/@joshi.pranjal5/spot-your-favourite-trading-setups-using-vector-databases-1651d747fbf0) to know How this Works? 🤔')
|
441 |
+
|
442 |
+
with tab_about:
|
443 |
+
from classes.Changelog import VERSION, changelog
|
444 |
+
|
445 |
+
st.success(f'Screeni-py v{VERSION}', icon='🔍')
|
446 |
+
ac, bc = st.columns([2,1])
|
447 |
+
ac.info("""
|
448 |
+
👨🏻💻 Developed and Maintained by: Pranjal Joshi
|
449 |
+
|
450 |
+
🏠 Home Page: https://github.com/pranjal-joshi/Screeni-py
|
451 |
+
|
452 |
+
⚠️ Read/Post Issues here: https://github.com/pranjal-joshi/Screeni-py/issues
|
453 |
+
|
454 |
+
📣 Join Community Discussions: https://github.com/pranjal-joshi/Screeni-py/discussions
|
455 |
+
|
456 |
+
⬇️ Download latest software from https://github.com/pranjal-joshi/Screeni-py/releases/latest
|
457 |
+
|
458 |
+
💬 Join Telegram Group for discussion: https://t.me/+0Tzy08mR0do0MzNl
|
459 |
+
|
460 |
+
🎬 YouTube Playlist: Watch [**Here**](https://youtube.com/playlist?list=PLsGnKKT_974J3UVS8M6bxqePfWLeuMsBi&si=b6JNMf03IbA_SsXs) [![YouTube Channel Subscribers](https://img.shields.io/youtube/channel/subscribers/UCb_4n0rRHCL2dUbmRvS7psA)](https://www.youtube.com/@PranjalJoshi)
|
461 |
+
""")
|
462 |
+
bc.write('<iframe width="445" height="295" src="https://www.youtube.com/embed/videoseries?si=aKXpyKKgwCcWIjhW&list=PLsGnKKT_974J3UVS8M6bxqePfWLeuMsBi" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>', unsafe_allow_html=True)
|
463 |
+
st.warning("ChangeLog:\n " + changelog[40:-3], icon='⚙️')
|
464 |
+
|
465 |
+
with tab_psc:
|
466 |
+
ac, oc = st.columns([1, 1])
|
467 |
+
ac, bc = ac.columns([4, 1])
|
468 |
+
ac.subheader('💸 Position Size Calculator')
|
469 |
+
calculate_qty_btn = bc.button('**Calculate Qty**', type='primary', use_container_width=True)
|
470 |
+
|
471 |
+
ac, bc = st.columns([1, 1])
|
472 |
+
capital = ac.number_input(label='Capital Size', min_value=0, value=100000, help='Total Amount used for Trading/Investing')
|
473 |
+
if capital:
|
474 |
+
in_words = num2words(capital, lang='en_IN').title()
|
475 |
+
bc.write(f"<p style='margin-top:35px; font-weight: bold;'>Your Capital is Rs. {in_words}</p>", unsafe_allow_html=True)
|
476 |
+
|
477 |
+
risk = ac.number_input(label="% Risk on Capital for this trade", min_value=0.0, max_value=10.0, step=0.1, value=0.5, help='How many percentage of your total capital you want to risk if your Stoploss hits? If you want a max loss of 1000 for an account value of 100,000 then your risk is 1%. It is not advised to take Risk more than 5% per trade! Think about your maximum loss before you trade!')
|
478 |
+
if risk:
|
479 |
+
risk_rs = capital * (risk/100.0)
|
480 |
+
in_words = num2words(risk_rs, lang='en_IN').title()
|
481 |
+
bc.write(f"<p style='margin-top:40px; font-weight: bold;'>Your Risk for this trade is Rs. {in_words}</p>", unsafe_allow_html=True)
|
482 |
+
|
483 |
+
ac.divider()
|
484 |
+
|
485 |
+
sl = ac.number_input(label="Stoploss in points", min_value=0.0, step=0.1, help='Stoploss in Points or Rupees calculated by you by analyzing the chart.')
|
486 |
+
if sl > 0:
|
487 |
+
in_words = num2words(sl, lang='en_IN').title()
|
488 |
+
bc.write(f"<p style='margin-top:105px;'>Your SL is {in_words} Rs. per share.</p>", unsafe_allow_html=True)
|
489 |
+
|
490 |
+
ac.write('<center><h5>OR</h5></center>', unsafe_allow_html=True)
|
491 |
+
|
492 |
+
a1, a2 = ac.columns([1, 1])
|
493 |
+
price = a1.number_input(label="Entry Price", min_value=0.0, help='Entry price for Long/Short position')
|
494 |
+
percentage_sl = a2.number_input(label="% SL", min_value=0.0, max_value=100.0, value=5.0, help='Stoploss in %')
|
495 |
+
if sl == 0 and (price > 0 and percentage_sl > 0):
|
496 |
+
actual_sl = round(price * (percentage_sl / 100),2)
|
497 |
+
in_words = num2words(actual_sl, lang='en_IN').title()
|
498 |
+
bc.write(f"<p style='margin-top:230px;'>Your SL is Rs. {actual_sl} per share</p>", unsafe_allow_html=True)
|
499 |
+
|
500 |
+
if calculate_qty_btn:
|
501 |
+
if sl > 0:
|
502 |
+
qty = floor(risk_rs / sl)
|
503 |
+
oc.metric(label='Quantity', value=qty, delta=f'Max Loss: {(-1 * qty * sl)}', delta_color='inverse', help='Trade this Quantity to prevent excessive unplanned losses')
|
504 |
+
elif price > 0 and percentage_sl > 0:
|
505 |
+
qty = floor(risk_rs / actual_sl)
|
506 |
+
oc.metric(label='Quantity', value=qty, delta=f'Max Loss: {(-1 * qty * actual_sl)}', delta_color='inverse', help='Trade this Quantity to prevent excessive unplanned losses')
|
507 |
+
|
508 |
+
marquee_html = '''
|
509 |
+
<!DOCTYPE html>
|
510 |
+
<html>
|
511 |
+
<head>
|
512 |
+
<style>
|
513 |
+
.sampleMarquee {
|
514 |
+
color: #f63366;
|
515 |
+
font-family: 'Ubuntu Mono', monospace;
|
516 |
+
background-color: #ffffff;
|
517 |
+
font-size: 18px;
|
518 |
+
line-height: 30px;
|
519 |
+
padding: px;
|
520 |
+
font-weight: bold;
|
521 |
+
}
|
522 |
+
</style>
|
523 |
+
</head>
|
524 |
+
<body>
|
525 |
+
<marquee class="sampleMarquee" direction="left" scrollamount="7" behavior="scroll">This tool should be used only for Analysis/Study purposes. We do NOT provide any Buy/Sell advice for any Securities. Authors of this tool will not be held liable for any losses. Understand the Risks subjected with Markets before Investing.</marquee>
|
526 |
+
</body>
|
527 |
+
</html>
|
528 |
+
'''
|
529 |
+
components.html(marquee_html)
|
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', encoding='utf-8')
|
177 |
+
# contents = f.read()
|
178 |
+
# f.close()
|
179 |
+
# failUrl = [f"https://github.com/pranjal-joshi/Screeni-py/releases/download/{last_release}/screenipy.bin",
|
180 |
+
# f"https://github.com/pranjal-joshi/Screeni-py/releases/download/{last_release}/screenipy.exe"]
|
181 |
+
# passUrl = [f"https://github.com/pranjal-joshi/Screeni-py/releases/download/{VERSION}/screenipy.bin",
|
182 |
+
# f"https://github.com/pranjal-joshi/Screeni-py/releases/download/{VERSION}/screenipy.exe"]
|
183 |
+
# for url in failUrl:
|
184 |
+
# assert not url in contents
|
185 |
+
# for url in passUrl:
|
186 |
+
# assert url in contents
|
187 |
+
|
188 |
+
|
189 |
+
def test_if_changelog_version_changed():
|
190 |
+
global last_release
|
191 |
+
v = changelog.split(']')[-2].split('[')[-1]
|
192 |
+
assert float(v) > float(last_release)
|