jarvisx17 commited on
Commit
9bf26a6
1 Parent(s): 88740b4

Upload 46 files

Browse files
.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

  • SHA256: 57453b0a381db2484263af8dc647a408154110067f8b88be5ae5a3e14b04b598
  • Pointer size: 132 Bytes
  • Size of remote file: 2.82 MB
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&amp;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)