diff --git a/.gitattributes b/.gitattributes index a6344aac8c09253b3b630fb776ae94478aa0275b..058f780f49994ca2b35562736a3e006898687d9b 100644 --- a/.gitattributes +++ b/.gitattributes @@ -33,3 +33,6 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text *.zip filter=lfs diff=lfs merge=lfs -text *.zst filter=lfs diff=lfs merge=lfs -text *tfevents* filter=lfs diff=lfs merge=lfs -text +patterns/IPO1.png filter=lfs diff=lfs merge=lfs -text +patterns/IPO2.png filter=lfs diff=lfs merge=lfs -text +patterns/IPO3.png filter=lfs diff=lfs merge=lfs -text diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000000000000000000000000000000000..3e2cb105a04319c9939df83129e302a41ebda1e7 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,42 @@ +# Contributing + +When contributing to this repository, please first discuss the change you wish to make via issue with owner or other contributers. + +## 1. Keep your Fork up to date +* Before statrting development of any new feature, Always check if this repo is ahead in commits as compared to your fork. +* It is a good practice to always keep your fork up-to-date before starting development of features/fixes to avoid merge conflicts. +* Update your fork using following code snippet. +``` +# Add a new remote repo called as screenipy_upstream +git remote add screenipy_upstream https://github.com/pranjal-joshi/Screeni-py.git + +# Sync your fork before starting work +git fetch screenipy_upstream +git checkout +git merge screenipy_upstream/ +``` + + +## 2. Install Project Dependencies + +* This project uses [**TA-Lib**](https://github.com/mrjbq7/ta-lib). Please visit the hyperlink for the official guide of installation. +* This Project requires Python 3.9 environment setup. [Click Here to Download](https://www.python.org/downloads/) +* Install python dependencies by running `pip install -r requirements.txt` in the root directory of this project. + +## 3. Create Dependency Requirements + +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`. +2. Update the `requirements.txt` file by running `pip-chill --all --no-version -v > requirements.txt`. +3. Ensure to **uncomment** all the dependency modules from the `requirements.txt` + +## 4. Testing Code Locally + +1. Update the test-cases as per the new features from `test/screenipy_test.py` if required. +2. Run a test locally with `pytest -v` and ensure that all tests are passed. +3. In case of a failure, Rectify code or Consider opening an issue for further discussion. + +## 5. Pull Request Process + +1. Ensure that dependecy list have been generated in the `requirements.txt` using above section. +2. Ensure that all test-cases are passed locally. +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`. \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 88a3d42894d90412f1143120fbca9d7f419e92bf..cfe6d7292e1a48f9358754401c31a2d104b6e796 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,21 +1,63 @@ -FROM python:3.9-slim +# Project : Screenipy +# Author : Pranjal Joshi +# Created : 17/08/2023 +# Description : Dockerfile to build Screeni-py image for GUI release -WORKDIR /app +FROM python:3.11.6-slim-bookworm AS base -RUN apt-get update && apt-get install -y \ - build-essential \ - curl \ - software-properties-common \ - git \ - && rm -rf /var/lib/apt/lists/* +ARG DEBIAN_FRONTEND=noninteractive -COPY requirements.txt ./ -COPY src/ ./src/ +RUN apt-get update && apt-get install -y --no-install-recommends \ + git vim nano wget curl && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* -RUN pip3 install -r requirements.txt +ENV LANG=C.UTF-8 \ + PYTHONUNBUFFERED=TRUE \ + PYTHONDONTWRITEBYTECODE=TRUE \ + SCREENIPY_DOCKER=TRUE \ + SCREENIPY_GUI=TRUE \ + PATH=/opt/program:$PATH + +############## +# Build Phase +############## +FROM base AS build + +ARG PIP_DISABLE_PIP_VERSION_CHECK=1 +ARG PIP_NO_CACHE_DIR=1 + +WORKDIR /opt/program + +RUN python3 -m venv /venv +ENV PATH=/venv/bin:$PATH + +COPY requirements.txt . + +RUN --mount=type=cache,target=/root/.cache/pip pip3 install -r requirements.txt +RUN --mount=type=cache,target=/root/.cache/pip pip3 install --no-deps advanced-ta + +############## +# Package Phase +############## +FROM base AS app + +COPY --from=build /venv /venv +ENV PATH=/venv/bin:$PATH + +WORKDIR /opt/program + +COPY . . + +RUN chmod +x ./* + +EXPOSE 8000 EXPOSE 8501 HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health -ENTRYPOINT ["streamlit", "run", "src/streamlit_app.py", "--server.port=8501", "--server.address=0.0.0.0"] \ No newline at end of file +WORKDIR /opt/program/src + +ENTRYPOINT ["streamlit", "run", "streamlit_app.py", "--server.port=8501", "--server.address=0.0.0.0"] +# ENTRYPOINT ["tail", "-f", "/dev/null"] diff --git a/INSTALLATION.md b/INSTALLATION.md new file mode 100644 index 0000000000000000000000000000000000000000..487dd8d753396e2a7a9652f013f525e52a90578f --- /dev/null +++ b/INSTALLATION.md @@ -0,0 +1,33 @@ +# Installation Guide + +This is a troubleshooting guide for installing [Screenipy](https://github.com/pranjal-joshi/Screeni-py) on your computer. + +## For MacOS + +### One Time Configuration + +1. Download the executable from the [Latest Release](https://github.com/pranjal-joshi/Screeni-py/releases/latest) +2. Open `Terminal` from `Applications > Utility > Terminal` +3. Execute following commands in the terminal (Commands are **Case Sensitive**) +``` +cd Downloads # Navigate to Downloads folder +chmod +x screenipy.run # Apply Execute permission to the file +``` + +4. Right click on 'screenipy.run' and select option `Open with > Utilities > Terminal`. (Select All applications if `Terminal` is frozen) +5. You may get **Developer not Verified** error as follow: + +![Error](https://user-images.githubusercontent.com/6128978/119251001-95214580-bbc1-11eb-8484-e07ba33730dc.PNG) + +6.Click on the **`?`** Icon. The following prompt will appear on the right bottom of your screen. + +![Prompt](https://user-images.githubusercontent.com/6128978/119251025-c39f2080-bbc1-11eb-8103-9f0d267ff4e4.PNG) + +7. Click on `Open General Pane for me` option. +8. This will open following **Security and Privacy** window. +9. Click on **`Open Anyway`** Button to grant executable permission for the Screenipy. (Enter your password if prompted) + +![Allow](https://user-images.githubusercontent.com/6128978/119251073-11b42400-bbc2-11eb-9a15-7ebb6fec1c66.PNG) + +10. Close the window. +11. Now double click on `screenipy.run` file to use the application. \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..9849ce25a6d8110188dba7c6ce41a20a1a23606f --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Pranjal Joshi + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/LICENSE_Third_Party b/LICENSE_Third_Party new file mode 100644 index 0000000000000000000000000000000000000000..41f2f1e5a1bff599ae570bd696e3411e5772ab18 --- /dev/null +++ b/LICENSE_Third_Party @@ -0,0 +1,43 @@ +The MIT License (MIT) + +Copyright (c) 2023 pkjmesra + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +The MIT License (MIT) + +Copyright (c) 2025 smitpsanghavi + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..68313901fb589298424dcc6ec4d8a8324b79bf59 --- /dev/null +++ b/Makefile @@ -0,0 +1,30 @@ +build: + docker build -t screeni-py . + +run: + docker run -d --name screeni-py -p 8501:8501 screeni-py + +interactive-run: + docker run -p 8501:8501 screeni-py + +shell: + docker run -it --entrypoint /bin/bash screeni-py + +stop-container: + @if [ "$(shell docker ps -q -f name=screeni-py)" ]; then \ + docker stop screeni-py; \ + else \ + echo "Container screeni-py is not running."; \ + fi + +remove-container: + @if [ "$(shell docker ps -a -q -f name=screeni-py)" ]; then \ + docker rm screeni-py; \ + else \ + echo "Container screeni-py does not exist."; \ + fi + +system-clean: + docker system prune --force + +rebuild: stop-container remove-container build system-clean diff --git a/actions-data-download/stock_data_140823.pkl b/actions-data-download/stock_data_140823.pkl new file mode 100644 index 0000000000000000000000000000000000000000..8aa5ae3de09a4f89753fdbf898577ad4b6f110d0 --- /dev/null +++ b/actions-data-download/stock_data_140823.pkl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6d0f881b30f583d74a3956eb032ffad658ca05075c971466b4bf59ce033feb16 +size 39838610 diff --git a/patterns/IPO1.png b/patterns/IPO1.png new file mode 100644 index 0000000000000000000000000000000000000000..ed69ce04359fefdc42f19bf9e29294f01a30f5f9 --- /dev/null +++ b/patterns/IPO1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:40e9e15683b4424b91dff99747dd04693d6d8d7249b6fa31d62518a5f0b53d88 +size 101961 diff --git a/patterns/IPO2.png b/patterns/IPO2.png new file mode 100644 index 0000000000000000000000000000000000000000..94886ee78abad339f5795b5db6822f010e57da9a --- /dev/null +++ b/patterns/IPO2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e04dcedceb1351f161182f9def484a128c1cbdfb1d4e0367a605fbd6c7d52d4f +size 121893 diff --git a/patterns/IPO3.png b/patterns/IPO3.png new file mode 100644 index 0000000000000000000000000000000000000000..141c8c743dd914aa8e6dae2c881c820030061003 --- /dev/null +++ b/patterns/IPO3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b364c2402694b4896507983a5bcc227b29c29059d7778e139f265f3d3ec5eb31 +size 135132 diff --git a/patterns/MomentumGainer.png b/patterns/MomentumGainer.png new file mode 100644 index 0000000000000000000000000000000000000000..26b6fdb73201361914c69dc9a9b402736b13c394 Binary files /dev/null and b/patterns/MomentumGainer.png differ diff --git a/requirements.txt b/requirements.txt index 28d994e22f8dd432b51df193562052e315ad95f7..5db658d76508e6b22ab384b436b538f2c37c36b0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,66 @@ -altair -pandas -streamlit \ No newline at end of file +# numpy==1.21.0 # Installed as dependency for yfinance, scipy, matplotlib, pandas +numpy +appdirs +cachecontrol +contextlib2 +distlib +distro +html5lib +ipaddr +lockfile +nsetools +openpyxl +pep517 +pip +pip-chill +progress +pytest-mock +pytoml +retrying +scipy==1.11.2 +TA-Lib-Precompiled +tabulate +yfinance==0.2.60 +alive-progress==1.6.2 +Pillow +scikit-learn==1.3.2 +joblib +altgraph # Installed as dependency for pyinstaller +atomicwrites # Installed as dependency for pytest +attrs # Installed as dependency for pytest +certifi # Installed as dependency for requests +chardet # Installed as dependency for requests +colorama # Installed as dependency for pytest +dateutils # Installed as dependency for nsetools +et-xmlfile # Installed as dependency for openpyxl +future # Installed as dependency for pefile +idna # Installed as dependency for requests +iniconfig # Installed as dependency for pytest +lxml # Installed as dependency for yfinance +msgpack # Installed as dependency for cachecontrol +multitasking # Installed as dependency for yfinance +packaging # Installed as dependency for pytest +pandas==2.1.2 # Installed as dependency for yfinance +pefile # Installed as dependency for pyinstaller +pluggy # Installed as dependency for pytest +py # Installed as dependency for pytest +pyinstaller-hooks-contrib # Installed as dependency for pyinstaller +pyparsing # Installed as dependency for packaging, matplotlib +pytest # Installed as dependency for pytest-mock +python-dateutil # Installed as dependency for dateutils, matplotlib, pandas +pytz # Installed as dependency for dateutils, pandas +pywin32-ctypes # Installed as dependency for pyinstaller +requests # Installed as dependency for yfinance, cachecontrol +setuptools # Installed as dependency for pyinstaller +six # Installed as dependency for cycler, nsetools, packaging, retrying, python-dateutil, html5lib +toml # Installed as dependency for pep517, pytest +urllib3 # Installed as dependency for requests +webencodings # Installed as dependency for html5lib +pandas_ta +protobuf +streamlit +tensorflow +chromadb==0.4.10 +mplfinance==0.12.9-beta.7 +num2words +advanced-ta \ No newline at end of file diff --git a/run_screenipy.sh b/run_screenipy.sh new file mode 100644 index 0000000000000000000000000000000000000000..dfe247c217e901f7a164701cb1e559a7b961b8f0 --- /dev/null +++ b/run_screenipy.sh @@ -0,0 +1,25 @@ +#!/bin/bash +variable_name="SCREENIPY_GUI" + +cd src + +# Check if the script was provided with at least one argument +if [ $# -lt 1 ]; then + echo "Usage: $0 [--gui|--cli]" + exit 1 +fi + +# Check the value of the first argument +if [ "$1" = "--gui" ]; then + export SCREENIPY_GUI=TRUE + echo " " + echo "Starting in GUI mode... Copy and Paste following URL in your browser.." + streamlit run streamlit_app.py --server.port=8501 --server.address=0.0.0.0 +elif [ "$1" = "--cli" ]; then + unset "SCREENIPY_GUI" + echo "Starting in CLI mode..." + python3 screenipy.py +else + echo "Invalid argument. Usage: $0 [--gui|--cli]" + exit 1 +fi diff --git a/src/.streamlit/config.toml b/src/.streamlit/config.toml new file mode 100644 index 0000000000000000000000000000000000000000..32df6824a54e727076c9fa70f1231eac5759853a --- /dev/null +++ b/src/.streamlit/config.toml @@ -0,0 +1,2 @@ +[theme] +base="light" diff --git a/src/classes/CandlePatterns.py b/src/classes/CandlePatterns.py new file mode 100644 index 0000000000000000000000000000000000000000..f23eac952c5a1e8367882b2a88b16cbdcb9c79b3 --- /dev/null +++ b/src/classes/CandlePatterns.py @@ -0,0 +1,185 @@ +''' + * Project : Screenipy + * Author : Pranjal Joshi + * Created : 11/04/2021 + * Description : Class for analyzing candle-stick patterns +''' + +import pandas as pd +from classes.ScreenipyTA import ScreenerTA +from classes.ColorText import colorText + +class CandlePatterns: + + reversalPatternsBullish = ['Morning Star', 'Morning Doji Star', '3 Inside Up', 'Hammer', '3 White Soldiers', 'Bullish Engulfing', 'Dragonfly Doji', 'Supply Drought', 'Demand Rise'] + reversalPatternsBearish = ['Evening Star', 'Evening Doji Star', '3 Inside Down', 'Inverted Hammer', 'Hanging Man', '3 Black Crows', 'Bearish Engulfing', 'Shooting Star', 'Gravestone Doji'] + + def __init__(self): + pass + + # Find candle-stick patterns + # Arrange if statements with max priority from top to bottom + def findPattern(self, data, dict, saveDict): + data = data.head(4) + data = data[::-1] + + check = ScreenerTA.CDLMORNINGSTAR(data['Open'], data['High'], data['Low'], data['Close']) + if(check): + dict['Pattern'] = colorText.BOLD + colorText.GREEN + 'Morning Star' + colorText.END + saveDict['Pattern'] = 'Morning Star' + return True + + check = ScreenerTA.CDLMORNINGDOJISTAR(data['Open'], data['High'], data['Low'], data['Close']) + if(check): + dict['Pattern'] = colorText.BOLD + colorText.GREEN + 'Morning Doji Star' + colorText.END + saveDict['Pattern'] = 'Morning Doji Star' + return True + + check = ScreenerTA.CDLEVENINGSTAR(data['Open'], data['High'], data['Low'], data['Close']) + if(check): + dict['Pattern'] = colorText.BOLD + colorText.FAIL + 'Evening Star' + colorText.END + saveDict['Pattern'] = 'Evening Star' + return True + + check = ScreenerTA.CDLEVENINGDOJISTAR(data['Open'], data['High'], data['Low'], data['Close']) + if(check): + dict['Pattern'] = colorText.BOLD + colorText.FAIL + 'Evening Doji Star' + colorText.END + saveDict['Pattern'] = 'Evening Doji Star' + return True + + check = ScreenerTA.CDLLADDERBOTTOM(data['Open'], data['High'], data['Low'], data['Close']) + if(check): + if(check is not None and check.tail(1).item() > 0): + dict['Pattern'] = colorText.BOLD + colorText.GREEN + 'Ladder Bottom' + colorText.END + saveDict['Pattern'] = 'Bullish Ladder Bottom' + else: + dict['Pattern'] = colorText.BOLD + colorText.FAIL + 'Ladder Bottom' + colorText.END + saveDict['Pattern'] = 'Bearish Ladder Bottom' + return True + + check = ScreenerTA.CDL3LINESTRIKE(data['Open'], data['High'], data['Low'], data['Close']) + if(check): + if(check is not None and check.tail(1).item() > 0): + dict['Pattern'] = colorText.BOLD + colorText.GREEN + '3 Line Strike' + colorText.END + else: + dict['Pattern'] = colorText.BOLD + colorText.FAIL + '3 Line Strike' + colorText.END + saveDict['Pattern'] = '3 Line Strike' + return True + + check = ScreenerTA.CDL3BLACKCROWS(data['Open'], data['High'], data['Low'], data['Close']) + if(check): + dict['Pattern'] = colorText.BOLD + colorText.FAIL + '3 Black Crows' + colorText.END + saveDict['Pattern'] = '3 Black Crows' + return True + + check = ScreenerTA.CDL3INSIDE(data['Open'], data['High'], data['Low'], data['Close']) + if(check): + if(check is not None and check.tail(1).item() > 0): + dict['Pattern'] = colorText.BOLD + colorText.GREEN + '3 Outside Up' + colorText.END + saveDict['Pattern'] = '3 Inside Up' + else: + dict['Pattern'] = colorText.BOLD + colorText.FAIL + '3 Outside Down' + colorText.END + saveDict['Pattern'] = '3 Inside Down' + return True + + check = ScreenerTA.CDL3OUTSIDE(data['Open'], data['High'], data['Low'], data['Close']) + if(check > 0): + dict['Pattern'] = colorText.BOLD + colorText.GREEN + '3 Outside Up' + colorText.END + saveDict['Pattern'] = '3 Outside Up' + return True + elif(check < 0): + dict['Pattern'] = colorText.BOLD + colorText.FAIL + '3 Outside Down' + colorText.END + saveDict['Pattern'] = '3 Outside Down' + return True + + check = ScreenerTA.CDL3WHITESOLDIERS(data['Open'], data['High'], data['Low'], data['Close']) + if(check): + dict['Pattern'] = colorText.BOLD + colorText.GREEN + '3 White Soldiers' + colorText.END + saveDict['Pattern'] = '3 White Soldiers' + return True + + check = ScreenerTA.CDLHARAMI(data['Open'], data['High'], data['Low'], data['Close']) + if(check): + if(check is not None and check.tail(1).item() > 0): + dict['Pattern'] = colorText.BOLD + colorText.GREEN + 'Bullish Harami' + colorText.END + saveDict['Pattern'] = 'Bullish Harami' + else: + dict['Pattern'] = colorText.BOLD + colorText.FAIL + 'Bearish Harami' + colorText.END + saveDict['Pattern'] = 'Bearish Harami' + return True + + check = ScreenerTA.CDLHARAMICROSS(data['Open'], data['High'], data['Low'], data['Close']) + if(check): + if(check is not None and check.tail(1).item() > 0): + dict['Pattern'] = colorText.BOLD + colorText.GREEN + 'Bullish Harami Cross' + colorText.END + saveDict['Pattern'] = 'Bullish Harami Cross' + else: + dict['Pattern'] = colorText.BOLD + colorText.FAIL + 'Bearish Harami Cross' + colorText.END + saveDict['Pattern'] = 'Bearish Harami Cross' + return True + + check = ScreenerTA.CDLMARUBOZU(data['Open'], data['High'], data['Low'], data['Close']) + if(check): + if(check is not None and check.tail(1).item() > 0): + dict['Pattern'] = colorText.BOLD + colorText.GREEN + 'Bullish Marubozu' + colorText.END + saveDict['Pattern'] = 'Bullish Marubozu' + else: + dict['Pattern'] = colorText.BOLD + colorText.FAIL + 'Bearish Marubozu' + colorText.END + saveDict['Pattern'] = 'Bearish Marubozu' + return True + + check = ScreenerTA.CDLHANGINGMAN(data['Open'], data['High'], data['Low'], data['Close']) + if(check): + dict['Pattern'] = colorText.BOLD + colorText.FAIL + 'Hanging Man' + colorText.END + saveDict['Pattern'] = 'Hanging Man' + return True + + check = ScreenerTA.CDLHAMMER(data['Open'], data['High'], data['Low'], data['Close']) + if(check): + dict['Pattern'] = colorText.BOLD + colorText.GREEN + 'Hammer' + colorText.END + saveDict['Pattern'] = 'Hammer' + return True + + check = ScreenerTA.CDLINVERTEDHAMMER(data['Open'], data['High'], data['Low'], data['Close']) + if(check): + dict['Pattern'] = colorText.BOLD + colorText.GREEN + 'Inverted Hammer' + colorText.END + saveDict['Pattern'] = 'Inverted Hammer' + return True + + check = ScreenerTA.CDLSHOOTINGSTAR(data['Open'], data['High'], data['Low'], data['Close']) + if(check): + dict['Pattern'] = colorText.BOLD + colorText.FAIL + 'Shooting Star' + colorText.END + saveDict['Pattern'] = 'Shooting Star' + return True + + check = ScreenerTA.CDLDRAGONFLYDOJI(data['Open'], data['High'], data['Low'], data['Close']) + if(check): + dict['Pattern'] = colorText.BOLD + colorText.GREEN + 'Dragonfly Doji' + colorText.END + saveDict['Pattern'] = 'Dragonfly Doji' + return True + + check = ScreenerTA.CDLGRAVESTONEDOJI(data['Open'], data['High'], data['Low'], data['Close']) + if(check): + dict['Pattern'] = colorText.BOLD + colorText.FAIL + 'Gravestone Doji' + colorText.END + saveDict['Pattern'] = 'Gravestone Doji' + return True + + check = ScreenerTA.CDLDOJI(data['Open'], data['High'], data['Low'], data['Close']) + if(check): + dict['Pattern'] = colorText.BOLD + 'Doji' + colorText.END + saveDict['Pattern'] = 'Doji' + return True + + check = ScreenerTA.CDLENGULFING(data['Open'], data['High'], data['Low'], data['Close']) + if(check > 0): + dict['Pattern'] = colorText.BOLD + colorText.GREEN + 'Bullish Engulfing' + colorText.END + saveDict['Pattern'] = 'Bullish Engulfing' + return True + elif(check < 0): + dict['Pattern'] = colorText.BOLD + colorText.FAIL + 'Bearish Engulfing' + colorText.END + saveDict['Pattern'] = 'Bearish Engulfing' + return True + + dict['Pattern'] = '' + saveDict['Pattern'] = '' + return False diff --git a/src/classes/Changelog.py b/src/classes/Changelog.py new file mode 100644 index 0000000000000000000000000000000000000000..7c5b5dafe36f630db7e6aa097a5b033c72180f8f --- /dev/null +++ b/src/classes/Changelog.py @@ -0,0 +1,312 @@ +''' + * Project : Screenipy + * Author : Pranjal Joshi + * Created : 28/04/2021 + * Description : Class for maintaining changelog +''' + +from classes.ColorText import colorText + +VERSION = "2.27" + +changelog = colorText.BOLD + '[ChangeLog]\n' + colorText.END + colorText.BLUE + ''' +[1.00 - Beta] +1. Initial Release for beta testing +2. Minor Bug fixes + +[1.01] +1. Inside Bar detection added. +2. OTA Software Update Implemented. +3. Stock shuffling added while screening +4. Results will be now also stored in the excel (screenipy-result.xlsx) file. +5. UI cosmetic updates for pretty-printing! + +[1.02] +1. Feature added to screen only STAGE-2 stocks. +2. OTA update download bug-fixed. +3. Auto generate default config if not found. +4. Minor bug-fixes. + +[1.03] +1. Result excel file will not be overwritten now. Each result file will be saved with timestamp. +2. Candlestick pattern recognition added. + +[1.04] +1. OTA Software Update bug-fixed. +2. Minor Improvements. + +[1.05] +1. More candlestick pattern added for recognition. +2. Option added to find stock with lowest volume in last 'N'-days to early detect possibility of breakout. +3. Last screened results will be stored and can be viewed with Option > 7. +4. Minor Bug-fixes and improvements. + +[1.06] +1. Option > 0 added - Screen stocks by enterning it's name (stock code). +2. Stability fixes and improvements. +3. Last screened results will be stored and can be viewed with Option > 7. + +[1.07] +1. Program Window will not automatically close now. +2. Bug fixes and improvements. + +[1.08] +1. Prompt added for saving excel after screening. +2. Program back-end architecture updated. + +[1.09] +1. RSI based screening added as Option > 5. +2. Minor Performance Improvements. + +[1.10] +1. Trend detection for the timeframe of analysis added. + +[1.11] +1. Option-6 -> Screen for stocks showing Reversal Signal added +2. Stage-2 Screening logic improved for identifying best stocks only. +3. Trend detection has been improved. +4. Bugs and Runtime warnings fixed. + +[1.12] +1. MA now gives more info like Candle Crossing and At Support/Resistance. +2. More Patterns added for Reversal Detection. +4. Trend detection enhanced for the timeframe of analysis. +5. Runtime Warnings have been fixed. + +[1.13] +1. Chart Pattern Detection added. Option > 7 +2. Screen for Inside Bar Chart pattern. +3. Documentation updated and Performance fixes. + +[1.14][1.15] +1. Screening stocks with parallel processing using all cores available in machine. (Thanks to @swarpatel23) +2. Minor Bug-fixes and Improvements. + +[1.16] +1. Bullish Momentum Finder added. Option > 6 > 3 +2. Stock Data Caching added. (Thanks to @swarpatel23) +3. Codefactoring Improved. +4. Ctrl+C crash fixed. + +[1.17] +1. Breakout detection improved. +2. Progressbar added. +3. Watchlist creation in Excel file and its screening. + +[1.18] +1. Cache and Performance fixes. +2. Breakout Calculation Enhanced. + +[1.19] +1. New Feature: Search for Bullish Reversal at MA. Option > 6 > 4 + +[1.20] +1. Screen stocks as per your favorite index. (Thanks to @swarpatel23) + +[1.21] +1. TradingView Hyperlink added for stock symbol. + +[1.22] +1. Broken yfinance API fixed. + +[1.23] +1. Bug fixed for DualCore CPU. +2. Dependencies updated. + +[1.24] +1. IPO Base Breakout pattern added. Option > 7 > 3. +2. Data fetching interval fixed. +3. Permission bug-fixes for some windows users. +4. Result table optimized. + +[1.25] +1. Default configuration parameters optimized. +2. Configuration generation on first time usage don't need restart anymore! +3. Minor bug-fixes. + +[1.26] +1. New Feature: Screen for the MA Confluence pattern Option > 7 > 4. + +[1.27] +1. Display more information about an update when it is available. +2. Minor Fixes (MA Confluence). + +[1.28] +1. Volume Spread Analysis added for Bullish Reversals. Option > 6 > 5 + +[1.29] +1. VSA screening optimized. +2. Error handling and timeout optimized. +3. Build Test mode added for CI/CD. + +[1.30] +1. New Tickers Group - Screen only for Newly Listed IPOs (Last 1 Yr) +2. Major bug fix - stage 2 criteria won't be applied for new listings. +3. Validation Fixed for Volume & MA Signal (Optimized for new listings) +4. Excel save header name bug fixed. + +[1.31] +1. BugFixes for false detection of patterns - IPO Base, Inside Bar. +2. New Application Icon. +3. Experimental - VCP Detection : Option > 7 > 4 + +[1.32] +1. Performance Optimization. +2. Minor Improvements. +3. Argument added for Data download only : run screenipy.exe -d + +[1.33] +1. Alternate Data source added. +2. Workflow added to create cache data on cloud. + +[1.34] +1. New Reversal - Narrow Range : Try Option 6 > 6 +2. Cache loading fixes for Pre-Market timings. Refer PR #103 +3. Progressbar added for Alternate Source Cache Download. + +[1.35] +1. Separate Algorithms for NR depending on Live/After-Market hours. +2. NRx results fixed in Momentum Gainer Screening. + +[1.36] +1. Updated CSV URLs to New NSE Site. (#113) + +[1.37] +1. New Chart Pattern -> Buy at Trendline : Try Option 7 > 5 + +[1.38] +1. Added AI based predictions for Nifty closing on next day : Select Index for Screening > N + +[1.39] +1. Intraday Live Scanner - 5 EMA for Indices : Try Option `E` + +[1.40] +1. Nifty AI Prediction - Model Accuracy Enhanced by new preprocessing - Better Gap predictions + +[1.41] +1. Fetching of Stock Codes list fixed after NSE migration to newer website - Not using `nsetools` anymore + +[1.42] +1. Down trend detection bug fixed +2. % Change added with LTP + +[1.43] +1. New Index added - F&O Only stocks + +[1.44] +1. Migrated ta-lib dependency to pandas_ta + +[1.45] +1. Minor bug fixes after dependency change + +[1.46] +1. TA-Lib reanabled. Dockerized for better distribution of the tool + + +[2.00] +1. Streamlit UI (WebApp) added +2. Multi-Arch Docker support enabled + +[2.01] +1. Docker build fixed - Versioning critical bug fixed for further OTA updates + +[2.02] +1. Newly Listed (IPO) index critical bug fixed +2. OTA Updates fixed for GUI +3. Cosmetic improvements +4. YouTube Video added to docs + +[2.03] +1. AI based Nifty-50 Gap up/down prediction added to GUI +2. Cosmetic updates and minor bug-fixes +3. Search Similar Stock Added +4. Executables Deprecated now onwards + +[2.04] +1. OTA update fixed - caching added in GUI +2. Moved to TA-Lib-Precompiled (0.4.25) +3. Progressbar added for screening to GUI +4. Documentation updated + +[2.05] +1. Download Results button added +2. Configuration save bug fixed for checkboxes +3. Attempted to changed Docker DNS + + +[2.06] +1. Links added with cosmetic upgrade +2. Docs updated + +[2.07] +1. US S&P 500 Index added - Try Index `15 > US S&P 500` +2. Minor improvemnets + +[2.08] +1. Nifty Prediction enhanced - New AI model uses Crude and Gold data for Gap Prediction + +[2.09] +1. Dependencies bumped to pandas-2.1.2 scikit-learn-1.3.2 for (pip install advanced-ta) compatibility +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 â¤ī¸) +3. MA-Confluence bug fixed + +[2.10] +1. Position Size Calculator added as a new tab + +[2.11] +1. Nifty Prediction issue fixed - Model is now trained on CPU instead of Apple-M1 GPU + +[2.12] +1. Cosmetic Updates for Position Size Calculator +2. Python base bumped to 3.11.6-slim-bookworm + +[2.13] +1. Date based Backtesting Added for Screening +2. Inside bar detection broken - bug fixed +3. Auto enhanced debug on console in dev release + +[2.14] +1. Dropdowns added for duration and period in configration tab + +[2.15] +1. MA Reversal improved for trend following (Inspired from Siddhart Bhanushali's 44 SMA) + +[2.16] +1. Nifty Prediction NaN values handled gracefully with forward filling if data is absent +2. Ticker 0 > Search by Stock name - re-enabled in GUI + +[2.17] +1. Backtest Report column added for backtest screening runs + +[2.18] +1. Critical backtest bug fixed (dropna axis-1 removed from results) +2. Clear stock cached data button added + +[2.19] +1. New Index (Group of Indices) `16 > Sectoral Indices` added + +[2.20] +1. Bugfixes - Clear cache button random key added to fix re-rendering issues + +[2.21] +1. Dependency updated - `advanced-ta` lib for bugfixes and performance improvement in Lorentzian Classifier + +[2.22] +1. RSI and 9 SMA of RSI based reversal added - Momentum based execution strategy. + +[2.23] +1. Changed Data Source for F&O Stocks - Using Zerodha Kite instead of Broken NSE Website + +[2.24] +1. Added Filters to Result Table (Special Thanks to https://github.com/koalyptus/TableFilter) + +[2.25] +1. Reduced docker image size by 50% (Special Thanks to https://github.com/smitpsanghavi) + +[2.26] +1. Bugfixes - yfinance package updated to 0.2.54 to fix Yahoo Finance API issue +2. Minor Improvements to maintain backward compatibility of the yfinance df + +[2.27] +1. Bugfixes - yfinance package updated to 0.2.61 to fix Yahoo Finance rate limit issue +''' + colorText.END diff --git a/src/classes/ColorText.py b/src/classes/ColorText.py new file mode 100644 index 0000000000000000000000000000000000000000..c8874c5f47c8a9307fcf05a3715c9e1694ab6cf6 --- /dev/null +++ b/src/classes/ColorText.py @@ -0,0 +1,17 @@ +''' + * Project : Screenipy + * Author : Pranjal Joshi + * Created : 11/04/2021 + * Description : Class for terminal text decoration +''' + +# Decoration Class +class colorText: + HEAD = '\033[95m' + BLUE = '\033[94m' + GREEN = '\033[92m' + WARN = '\033[93m' + FAIL = '\033[91m' + END = '\033[0m' + BOLD = '\033[1m' + UNDR = '\033[4m' \ No newline at end of file diff --git a/src/classes/ConfigManager.py b/src/classes/ConfigManager.py new file mode 100644 index 0000000000000000000000000000000000000000..09aded19f7ea96d3c60100cccd52f876be286389 --- /dev/null +++ b/src/classes/ConfigManager.py @@ -0,0 +1,206 @@ +''' + * Project : Screenipy + * Author : Pranjal Joshi + * Created : 28/04/2021 + * Description : Class for managing the user configuration +''' + +import sys +import os +import glob +import re +import configparser +from datetime import date +from classes.ColorText import colorText + +parser = configparser.ConfigParser(strict=False) + +# Default attributes for Downloading Cache from Git repo +default_period = '300d' +default_duration = '1d' + +# This Class manages read/write of user configuration +class tools: + + def __init__(self): + self.consolidationPercentage = 10 + self.volumeRatio = 2 + self.minLTP = 20.0 + self.maxLTP = 50000 + self.period = '300d' + self.duration = '1d' + self.daysToLookback = 30 + self.shuffleEnabled = True + self.cacheEnabled = True + self.stageTwo = False + self.useEMA = False + + def deleteStockData(self,excludeFile=None): + for f in glob.glob('stock_data*.pkl'): + if excludeFile is not None: + if not f.endswith(excludeFile): + os.remove(f) + else: + os.remove(f) + + # Handle user input and save config + + def setConfig(self, parser, default=False, showFileCreatedText=True): + if default: + parser.add_section('config') + parser.set('config', 'period', self.period) + parser.set('config', 'daysToLookback', str(self.daysToLookback)) + parser.set('config', 'duration', self.duration) + parser.set('config', 'minPrice', str(self.minLTP)) + parser.set('config', 'maxPrice', str(self.maxLTP)) + parser.set('config', 'volumeRatio', str(self.volumeRatio)) + parser.set('config', 'consolidationPercentage', + str(self.consolidationPercentage)) + parser.set('config', 'shuffle', 'y') + parser.set('config', 'cacheStockData', 'y') + parser.set('config', 'onlyStageTwoStocks', 'y' if self.stageTwo else 'n') + parser.set('config', 'useEMA', 'y' if self.useEMA else 'n') + try: + fp = open('screenipy.ini', 'w') + parser.write(fp) + fp.close() + if showFileCreatedText: + print(colorText.BOLD + colorText.GREEN + + '[+] Default configuration generated as user configuration is not found!' + colorText.END) + print(colorText.BOLD + colorText.GREEN + + '[+] Use Option > 5 to edit in future.' + colorText.END) + print(colorText.BOLD + colorText.GREEN + + '[+] Close and Restart the program now.' + colorText.END) + input('') + sys.exit(0) + except IOError: + print(colorText.BOLD + colorText.FAIL + + '[+] Failed to save user config. Exiting..' + colorText.END) + input('') + sys.exit(1) + else: + parser = configparser.ConfigParser(strict=False) + parser.add_section('config') + print('') + print(colorText.BOLD + colorText.GREEN + + '[+] Screeni-py User Configuration:' + colorText.END) + self.period = input( + '[+] Enter number of days for which stock data to be downloaded (Days)(Optimal = 365): ') + self.daysToLookback = input( + '[+] Number of recent days (TimeFrame) to screen for Breakout/Consolidation (Days)(Optimal = 20): ') + self.duration = input( + '[+] Enter Duration of each candle (Days)(Optimal = 1): ') + self.minLTP = input( + '[+] Minimum Price of Stock to Buy (in RS)(Optimal = 20): ') + self.maxLTP = input( + '[+] Maximum Price of Stock to Buy (in RS)(Optimal = 50000): ') + self.volumeRatio = input( + '[+] How many times the volume should be more than average for the breakout? (Number)(Optimal = 2.5): ') + self.consolidationPercentage = input( + '[+] How many % the price should be in range to consider it as consolidation? (Number)(Optimal = 10): ') + self.shuffle = str(input( + '[+] Shuffle stocks rather than screening alphabetically? (Y/N): ')).lower() + self.cacheStockData = str(input( + '[+] Enable High-Performance and Data-Saver mode? (This uses little bit more CPU but performs High Performance Screening) (Y/N): ')).lower() + self.stageTwoPrompt = str(input( + '[+] 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() + self.useEmaPrompt = str(input( + '[+] Use EMA instead of SMA? (EMA is good for Short-term & SMA for Mid/Long-term trades)[Y/N]: ')).lower() + parser.set('config', 'period', self.period + "d") + parser.set('config', 'daysToLookback', self.daysToLookback) + parser.set('config', 'duration', self.duration + "d") + parser.set('config', 'minPrice', self.minLTP) + parser.set('config', 'maxPrice', self.maxLTP) + parser.set('config', 'volumeRatio', self.volumeRatio) + parser.set('config', 'consolidationPercentage', + self.consolidationPercentage) + parser.set('config', 'shuffle', self.shuffle) + parser.set('config', 'cacheStockData', self.cacheStockData) + parser.set('config', 'onlyStageTwoStocks', self.stageTwoPrompt) + parser.set('config', 'useEMA', self.useEmaPrompt) + + # delete stock data due to config change + self.deleteStockData() + print(colorText.BOLD + colorText.FAIL + "[+] Cached Stock Data Deleted." + colorText.END) + + try: + fp = open('screenipy.ini', 'w') + parser.write(fp) + fp.close() + print(colorText.BOLD + colorText.GREEN + + '[+] User configuration saved.' + colorText.END) + print(colorText.BOLD + colorText.GREEN + + '[+] Restart the Program to start Screening...' + colorText.END) + input('') + sys.exit(0) + except IOError: + print(colorText.BOLD + colorText.FAIL + + '[+] Failed to save user config. Exiting..' + colorText.END) + input('') + sys.exit(1) + + # Load user config from file + def getConfig(self, parser): + if len(parser.read('screenipy.ini')): + try: + self.duration = parser.get('config', 'duration') + self.period = parser.get('config', 'period') + self.minLTP = float(parser.get('config', 'minprice')) + self.maxLTP = float(parser.get('config', 'maxprice')) + self.volumeRatio = float(parser.get('config', 'volumeRatio')) + self.consolidationPercentage = float( + parser.get('config', 'consolidationPercentage')) + self.daysToLookback = int( + parser.get('config', 'daysToLookback')) + if 'n' not in str(parser.get('config', 'shuffle')).lower(): + self.shuffleEnabled = True + if 'n' not in str(parser.get('config', 'cachestockdata')).lower(): + self.cacheEnabled = True + if 'n' not in str(parser.get('config', 'onlyStageTwoStocks')).lower(): + self.stageTwo = True + else: + self.stageTwo = False + if 'y' not in str(parser.get('config', 'useEMA')).lower(): + self.useEMA = False + else: + self.useEMA = True + except configparser.NoOptionError: + input(colorText.BOLD + colorText.FAIL + + '[+] Screenipy requires user configuration again. Press enter to continue..' + colorText.END) + parser.remove_section('config') + self.setConfig(parser, default=False) + else: + self.setConfig(parser, default=True) + + # Print config file + def showConfigFile(self): + try: + f = open('screenipy.ini', 'r') + print(colorText.BOLD + colorText.GREEN + + '[+] Screeni-py User Configuration:' + colorText.END) + print("\n"+f.read()) + f.close() + input('') + except: + print(colorText.BOLD + colorText.FAIL + + "[+] User Configuration not found!" + colorText.END) + print(colorText.BOLD + colorText.WARN + + "[+] Configure the limits to continue." + colorText.END) + self.setConfig(parser) + + # Check if config file exists + def checkConfigFile(self): + try: + f = open('screenipy.ini','r') + f.close() + return True + except FileNotFoundError: + return False + + # Get period as a numeric value + def getPeriodNumeric(self): + import re + pattern = re.compile(r'\d+') + result = [int(match.group()) for match in pattern.finditer(self.period)][0] + return result + diff --git a/src/classes/Fetcher.py b/src/classes/Fetcher.py new file mode 100644 index 0000000000000000000000000000000000000000..b744102415af499cd9c7b2b199edda5eff112e1c --- /dev/null +++ b/src/classes/Fetcher.py @@ -0,0 +1,388 @@ +''' + * Project : Screenipy + * Author : Pranjal Joshi + * Created : 28/04/2021 + * Description : Class for handling networking for fetching stock codes and data +''' + +import sys +import urllib.request +import csv +import requests +import random +import os +import datetime +import yfinance as yf +import pandas as pd +from nsetools import Nse +from classes.ColorText import colorText +from classes.SuppressOutput import SuppressOutput +from classes.Utility import isDocker + +nse = Nse() + +# Exception class if yfinance stock delisted + + +class StockDataEmptyException(Exception): + pass + +# This Class Handles Fetching of Stock Data over the internet + + +class tools: + + def __init__(self, configManager): + self.configManager = configManager + pass + + def getAllNiftyIndices(self) -> dict: + return { + "^NSEI": "NIFTY 50", + "^NSMIDCP": "NIFTY NEXT 50", + "^CNX100": "NIFTY 100", + "^CNX200": "NIFTY 200", + "^CNX500": "NIFTY 500", + "^NSEMDCP50": "NIFTY MIDCAP 50", + "NIFTY_MIDCAP_100.NS": "NIFTY MIDCAP 100", + "^CNXSC": "NIFTY SMALLCAP 100", + "^INDIAVIX": "INDIA VIX", + "NIFTYMIDCAP150.NS": "NIFTY MIDCAP 150", + "NIFTYSMLCAP50.NS": "NIFTY SMALLCAP 50", + "NIFTYSMLCAP250.NS": "NIFTY SMALLCAP 250", + "NIFTYMIDSML400.NS": "NIFTY MIDSMALLCAP 400", + "NIFTY500_MULTICAP.NS": "NIFTY500 MULTICAP 50:25:25", + "NIFTY_LARGEMID250.NS": "NIFTY LARGEMIDCAP 250", + "NIFTY_MID_SELECT.NS": "NIFTY MIDCAP SELECT", + "NIFTY_TOTAL_MKT.NS": "NIFTY TOTAL MARKET", + "NIFTY_MICROCAP250.NS": "NIFTY MICROCAP 250", + "^NSEBANK": "NIFTY BANK", + "^CNXAUTO": "NIFTY AUTO", + "NIFTY_FIN_SERVICE.NS": "NIFTY FINANCIAL SERVICES", + "^CNXFMCG": "NIFTY FMCG", + "^CNXIT": "NIFTY IT", + "^CNXMEDIA": "NIFTY MEDIA", + "^CNXMETAL": "NIFTY METAL", + "^CNXPHARMA": "NIFTY PHARMA", + "^CNXPSUBANK": "NIFTY PSU BANK", + "^CNXREALTY": "NIFTY REALTY", + "NIFTY_HEALTHCARE.NS": "NIFTY HEALTHCARE INDEX", + "NIFTY_CONSR_DURBL.NS": "NIFTY CONSUMER DURABLES", + "NIFTY_OIL_AND_GAS.NS": "NIFTY OIL & GAS", + "NIFTYALPHA50.NS": "NIFTY ALPHA 50", + "^CNXCMDT": "NIFTY COMMODITIES", + "NIFTY_CPSE.NS": "NIFTY CPSE", + "^CNXENERGY": "NIFTY ENERGY", + "^CNXINFRA": "NIFTY INFRASTRUCTURE", + "^CNXMNC": "NIFTY MNC", + "^CNXPSE": "NIFTY PSE", + "^CNXSERVICE": "NIFTY SERVICES SECTOR", + "NIFTY100_ESG.NS": "NIFTY100 ESG SECTOR LEADERS", + } + + def _getBacktestDate(self, backtest): + try: + end = backtest + datetime.timedelta(days=1) + if "d" in self.configManager.period: + delta = datetime.timedelta(days = self.configManager.getPeriodNumeric()) + elif "wk" in self.configManager.period: + delta = datetime.timedelta(days = self.configManager.getPeriodNumeric() * 7) + elif "m" in self.configManager.period: + delta = datetime.timedelta(minutes = self.configManager.getPeriodNumeric()) + elif "h" in self.configManager.period: + delta = datetime.timedelta(hours = self.configManager.getPeriodNumeric()) + start = end - delta + return [start, end] + except: + return [None, None] + + def _getDatesForBacktestReport(self, backtest): + dateDict = {} + try: + today = datetime.date.today() + dateDict['T+1d'] = backtest + datetime.timedelta(days=1) if backtest + datetime.timedelta(days=1) < today else None + dateDict['T+1wk'] = backtest + datetime.timedelta(weeks=1) if backtest + datetime.timedelta(weeks=1) < today else None + dateDict['T+1mo'] = backtest + datetime.timedelta(days=30) if backtest + datetime.timedelta(days=30) < today else None + dateDict['T+6mo'] = backtest + datetime.timedelta(days=180) if backtest + datetime.timedelta(days=180) < today else None + dateDict['T+1y'] = backtest + datetime.timedelta(days=365) if backtest + datetime.timedelta(days=365) < today else None + for key, val in dateDict.copy().items(): + if val is not None: + if val.weekday() == 5: # 5 is Saturday, 6 is Sunday + adjusted_date = val + datetime.timedelta(days=2) + dateDict[key] = adjusted_date + elif val.weekday() == 6: + adjusted_date = val + datetime.timedelta(days=1) + dateDict[key] = adjusted_date + except: + pass + return dateDict + + def fetchCodes(self, tickerOption,proxyServer=None): + listStockCodes = [] + if tickerOption == 12: + url = "https://archives.nseindia.com/content/equities/EQUITY_L.csv" + return list(pd.read_csv(url)['SYMBOL'].values) + if tickerOption == 15: + 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"] + if tickerOption == 16: + return self.getAllNiftyIndices() + tickerMapping = { + 1: "https://archives.nseindia.com/content/indices/ind_nifty50list.csv", + 2: "https://archives.nseindia.com/content/indices/ind_niftynext50list.csv", + 3: "https://archives.nseindia.com/content/indices/ind_nifty100list.csv", + 4: "https://archives.nseindia.com/content/indices/ind_nifty200list.csv", + 5: "https://archives.nseindia.com/content/indices/ind_nifty500list.csv", + 6: "https://archives.nseindia.com/content/indices/ind_niftysmallcap50list.csv", + 7: "https://archives.nseindia.com/content/indices/ind_niftysmallcap100list.csv", + 8: "https://archives.nseindia.com/content/indices/ind_niftysmallcap250list.csv", + 9: "https://archives.nseindia.com/content/indices/ind_niftymidcap50list.csv", + 10: "https://archives.nseindia.com/content/indices/ind_niftymidcap100list.csv", + 11: "https://archives.nseindia.com/content/indices/ind_niftymidcap150list.csv", + 14: "https://api.kite.trade/instruments" + } + + url = tickerMapping.get(tickerOption) + + try: + if proxyServer: + res = requests.get(url,proxies={'https':proxyServer}) + else: + res = requests.get(url) + + cr = csv.reader(res.text.strip().split('\n')) + + if tickerOption == 14: + cols = next(cr) + df = pd.DataFrame(cr, columns=cols) + listStockCodes = list(set(df[df['segment'] == 'NFO-FUT']["name"].to_list())) + listStockCodes.sort() + else: + next(cr) # skipping first line + for row in cr: + listStockCodes.append(row[2]) + except Exception as error: + print(error) + + return listStockCodes + + # Fetch all stock codes from NSE + def fetchStockCodes(self, tickerOption, proxyServer=None): + listStockCodes = [] + if tickerOption == 0: + stockCode = None + while stockCode == None or stockCode == "": + stockCode = str(input(colorText.BOLD + colorText.BLUE + + "[+] Enter Stock Code(s) for screening (Multiple codes should be seperated by ,): ")).upper() + stockCode = stockCode.replace(" ", "") + listStockCodes = stockCode.split(',') + else: + print(colorText.BOLD + + "[+] Getting Stock Codes From NSE... ", end='') + listStockCodes = self.fetchCodes(tickerOption,proxyServer=proxyServer) + if type(listStockCodes) == dict: + listStockCodes = list(listStockCodes.keys()) + if len(listStockCodes) > 10: + print(colorText.GREEN + ("=> Done! Fetched %d stock codes." % + len(listStockCodes)) + colorText.END) + if self.configManager.shuffleEnabled: + random.shuffle(listStockCodes) + print(colorText.BLUE + + "[+] Stock shuffling is active." + colorText.END) + else: + print(colorText.FAIL + + "[+] Stock shuffling is inactive." + colorText.END) + if self.configManager.stageTwo: + print( + colorText.BLUE + "[+] Screening only for the stocks in Stage-2! Edit User Config to change this." + colorText.END) + else: + print( + colorText.FAIL + "[+] Screening only for the stocks in all Stages! Edit User Config to change this." + colorText.END) + + else: + input( + colorText.FAIL + "=> Error getting stock codes from NSE! Press any key to exit!" + colorText.END) + sys.exit("Exiting script..") + + return listStockCodes + + # Fetch stock price data from Yahoo finance + def fetchStockData(self, stockCode, period, duration, proxyServer, screenResultsCounter, screenCounter, totalSymbols, backtestDate=None, printCounter=False, tickerOption=None): + dateDict = None + with SuppressOutput(suppress_stdout=True, suppress_stderr=True): + append_exchange = ".NS" + if tickerOption == 15 or tickerOption == 16: + append_exchange = "" + data = yf.download( + tickers=stockCode + append_exchange, + period=period, + interval=duration, + proxy=proxyServer, + progress=False, + timeout=10, + start=self._getBacktestDate(backtest=backtestDate)[0], + end=self._getBacktestDate(backtest=backtestDate)[1], + auto_adjust=False + ) + # For df backward compatibility towards yfinance 0.2.32 + data = self.makeDataBackwardCompatible(data) + # end + if backtestDate != datetime.date.today(): + dateDict = self._getDatesForBacktestReport(backtest=backtestDate) + backtestData = yf.download( + tickers=stockCode + append_exchange, + interval='1d', + proxy=proxyServer, + progress=False, + timeout=10, + start=backtestDate - datetime.timedelta(days=1), + end=backtestDate + datetime.timedelta(days=370) + ) + for key, value in dateDict.copy().items(): + if value is not None: + try: + dateDict[key] = backtestData.loc[pd.Timestamp(value)]['Close'] + except KeyError: + continue + dateDict['T+52wkH'] = backtestData['High'].max() + dateDict['T+52wkL'] = backtestData['Low'].min() + if printCounter: + sys.stdout.write("\r\033[K") + try: + print(colorText.BOLD + colorText.GREEN + ("[%d%%] Screened %d, Found %d. Fetching data & Analyzing %s..." % ( + int((screenCounter.value/totalSymbols)*100), screenCounter.value, screenResultsCounter.value, stockCode)) + colorText.END, end='') + except ZeroDivisionError: + pass + if len(data) == 0: + print(colorText.BOLD + colorText.FAIL + + "=> Failed to fetch!" + colorText.END, end='\r', flush=True) + raise StockDataEmptyException + return None + print(colorText.BOLD + colorText.GREEN + "=> Done!" + + colorText.END, end='\r', flush=True) + return data, dateDict + + # Get Daily Nifty 50 Index: + def fetchLatestNiftyDaily(self, proxyServer=None): + data = yf.download( + auto_adjust=False, + tickers="^NSEI", + period='5d', + interval='1d', + proxy=proxyServer, + progress=False, + timeout=10 + ) + gold = yf.download( + auto_adjust=False, + tickers="GC=F", + period='5d', + interval='1d', + proxy=proxyServer, + progress=False, + timeout=10 + ).add_prefix(prefix='gold_') + crude = yf.download( + auto_adjust=False, + tickers="CL=F", + period='5d', + interval='1d', + proxy=proxyServer, + progress=False, + timeout=10 + ).add_prefix(prefix='crude_') + data = self.makeDataBackwardCompatible(data) + gold = self.makeDataBackwardCompatible(gold, column_prefix='gold_') + crude = self.makeDataBackwardCompatible(crude, column_prefix='crude_') + data = pd.concat([data, gold, crude], axis=1) + return data + + # Get Data for Five EMA strategy + def fetchFiveEmaData(self, proxyServer=None): + nifty_sell = yf.download( + auto_adjust=False, + tickers="^NSEI", + period='5d', + interval='5m', + proxy=proxyServer, + progress=False, + timeout=10 + ) + banknifty_sell = yf.download( + auto_adjust=False, + tickers="^NSEBANK", + period='5d', + interval='5m', + proxy=proxyServer, + progress=False, + timeout=10 + ) + nifty_buy = yf.download( + auto_adjust=False, + tickers="^NSEI", + period='5d', + interval='15m', + proxy=proxyServer, + progress=False, + timeout=10 + ) + banknifty_buy = yf.download( + auto_adjust=False, + tickers="^NSEBANK", + period='5d', + interval='15m', + proxy=proxyServer, + progress=False, + timeout=10 + ) + nifty_buy = self.makeDataBackwardCompatible(nifty_buy) + banknifty_buy = self.makeDataBackwardCompatible(banknifty_buy) + nifty_sell = self.makeDataBackwardCompatible(nifty_sell) + banknifty_sell = self.makeDataBackwardCompatible(banknifty_sell) + return nifty_buy, banknifty_buy, nifty_sell, banknifty_sell + + # Load stockCodes from the watchlist.xlsx + def fetchWatchlist(self): + createTemplate = False + data = pd.DataFrame() + try: + data = pd.read_excel('watchlist.xlsx') + except FileNotFoundError: + print(colorText.BOLD + colorText.FAIL + + f'[+] watchlist.xlsx not found in f{os.getcwd()}' + colorText.END) + createTemplate = True + try: + if not createTemplate: + data = data['Stock Code'].values.tolist() + except KeyError: + print(colorText.BOLD + colorText.FAIL + + '[+] Bad Watchlist Format: First Column (A1) should have Header named "Stock Code"' + colorText.END) + createTemplate = True + if createTemplate: + if isDocker(): + print(colorText.BOLD + colorText.FAIL + + f'[+] This feature is not available with dockerized application. Try downloading .exe/.bin file to use this!' + colorText.END) + return None + sample = {'Stock Code': ['SBIN', 'INFY', 'TATAMOTORS', 'ITC']} + sample_data = pd.DataFrame(sample, columns=['Stock Code']) + sample_data.to_excel('watchlist_template.xlsx', + index=False, header=True) + print(colorText.BOLD + colorText.BLUE + + f'[+] watchlist_template.xlsx created in {os.getcwd()} as a referance template.' + colorText.END) + return None + return data + + def makeDataBackwardCompatible(self, data:pd.DataFrame, column_prefix:str=None) -> pd.DataFrame: + data = data.droplevel(level=1, axis=1) + data = data.rename_axis(None, axis=1) + column_prefix = '' if column_prefix is None else column_prefix + data = data[ + [ + f'{column_prefix}Open', + f'{column_prefix}High', + f'{column_prefix}Low', + f'{column_prefix}Close', + f'{column_prefix}Adj Close', + f'{column_prefix}Volume' + ] + ] + return data diff --git a/src/classes/OtaUpdater.py b/src/classes/OtaUpdater.py new file mode 100644 index 0000000000000000000000000000000000000000..0b2c3295e330296df87bac022fe4c5ad7c148794 --- /dev/null +++ b/src/classes/OtaUpdater.py @@ -0,0 +1,148 @@ +''' + * Project : Screenipy + * Author : Pranjal Joshi + * Created : 21/04/2021 + * Description : Class for handling OTA updates +''' + +from classes.ColorText import colorText +from classes.Utility import isDocker, isGui +import requests +import os +import platform +import sys +import subprocess +import requests + +class OTAUpdater: + + developmentVersion = 'd' + + # Download and replace exe through other process for Windows + def updateForWindows(url): + batFile = """@echo off +color a +echo [+] Screenipy Software Updater! +echo [+] Downloading Software Update... +echo [+] This may take some time as per your Internet Speed, Please Wait... +curl -o screenipy.exe -L """ + url + """ +echo [+] Newly downloaded file saved in %cd% +echo [+] Software Update Completed! Run'screenipy.exe' again as usual to continue.. +pause +del updater.bat & exit + """ + f = open("updater.bat",'w') + f.write(batFile) + f.close() + subprocess.Popen('start updater.bat', shell=True) + sys.exit(0) + + # Download and replace bin through other process for Linux + def updateForLinux(url): + bashFile = """#!/bin/bash +echo "" +echo "[+] Starting Screeni-py updater, Please Wait..." +sleep 3 +echo "[+] Screenipy Software Updater!" +echo "[+] Downloading Software Update..." +echo "[+] This may take some time as per your Internet Speed, Please Wait..." +wget -q """ + url + """ -O screenipy.bin +echo "[+] Newly downloaded file saved in $(pwd)" +chmod +x screenipy.bin +echo "[+] Update Completed! Run 'screenipy.bin' again as usual to continue.." +rm updater.sh + """ + f = open("updater.sh",'w') + f.write(bashFile) + f.close() + subprocess.Popen('bash updater.sh', shell=True) + sys.exit(0) + + # Download and replace run through other process for Mac + def updateForMac(url): + bashFile = """#!/bin/bash +echo "" +echo "[+] Starting Screeni-py updater, Please Wait..." +sleep 3 +echo "[+] Screenipy Software Updater!" +echo "[+] Downloading Software Update..." +echo "[+] This may take some time as per your Internet Speed, Please Wait..." +curl -o screenipy.run -L """ + url + """ +echo "[+] Newly downloaded file saved in $(pwd)" +chmod +x screenipy.run +echo "[+] Update Completed! Run 'screenipy.run' again as usual to continue.." +rm updater.sh + """ + f = open("updater.sh",'w') + f.write(bashFile) + f.close() + subprocess.Popen('bash updater.sh', shell=True) + sys.exit(0) + + # Parse changelog from release.md + def showWhatsNew(): + url = "https://raw.githubusercontent.com/pranjal-joshi/Screeni-py/main/src/release.md" + md = requests.get(url) + txt = md.text + txt = txt.split("New?")[1] + # txt = txt.split("## Downloads")[0] + txt = txt.split("## Installation Guide")[0] + txt = txt.replace('**','').replace('`','').strip() + return (txt+"\n") + + # Check for update and download if available + def checkForUpdate(proxyServer, VERSION="1.0"): + OTAUpdater.checkForUpdate.url = None + guiUpdateMessage = "" + try: + resp = None + now = float(VERSION) + if proxyServer: + resp = requests.get("https://api.github.com/repos/pranjal-joshi/Screeni-py/releases/latest",proxies={'https':proxyServer}) + else: + resp = requests.get("https://api.github.com/repos/pranjal-joshi/Screeni-py/releases/latest") + # Disabling Exe check as Executables are deprecated v2.03 onwards + ''' + if 'Windows' in platform.system(): + OTAUpdater.checkForUpdate.url = resp.json()['assets'][1]['browser_download_url'] + size = int(resp.json()['assets'][1]['size']/(1024*1024)) + elif 'Darwin' in platform.system(): + OTAUpdater.checkForUpdate.url = resp.json()['assets'][2]['browser_download_url'] + size = int(resp.json()['assets'][2]['size']/(1024*1024)) + else: + OTAUpdater.checkForUpdate.url = resp.json()['assets'][0]['browser_download_url'] + size = int(resp.json()['assets'][0]['size']/(1024*1024)) + # if(float(resp.json()['tag_name']) > now): + if(float(resp.json()['tag_name']) > now and not isDocker()): # OTA not applicable if we're running in docker! + print(colorText.BOLD + colorText.WARN + "[+] What's New in this Update?\n" + OTAUpdater.showWhatsNew() + colorText.END) + 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() + if(action == 'y'): + try: + if 'Windows' in platform.system(): + OTAUpdater.updateForWindows(OTAUpdater.checkForUpdate.url) + elif 'Darwin' in platform.system(): + OTAUpdater.updateForMac(OTAUpdater.checkForUpdate.url) + else: + OTAUpdater.updateForLinux(OTAUpdater.checkForUpdate.url) + except Exception as e: + print(colorText.BOLD + colorText.WARN + '[+] Error occured while updating!' + colorText.END) + raise(e) + ''' + if(float(resp.json()['tag_name']) > now and not isDocker()): + 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) + elif(float(resp.json()['tag_name']) > now and isDocker()): # OTA not applicable if we're running in docker! + 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) + print(colorText.BOLD + colorText.WARN + "[+] What's New in this Update?\n" + OTAUpdater.showWhatsNew() + colorText.END) + if isGui(): + 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`**" + elif(float(resp.json()['tag_name']) < now): + print(colorText.BOLD + colorText.FAIL + ('[+] This version (v%s) is in Development mode and unreleased!' % VERSION) + colorText.END) + if isGui(): + guiUpdateMessage = f"This version (v{VERSION}) is in Development mode and unreleased!" + return OTAUpdater.developmentVersion, guiUpdateMessage + except Exception as e: + print(colorText.BOLD + colorText.FAIL + "[+] Failure while checking update!" + colorText.END) + print(e) + if OTAUpdater.checkForUpdate.url != None: + print(colorText.BOLD + colorText.BLUE + ("[+] Download update manually from %s\n" % OTAUpdater.checkForUpdate.url) + colorText.END) + return None, guiUpdateMessage \ No newline at end of file diff --git a/src/classes/ParallelProcessing.py b/src/classes/ParallelProcessing.py new file mode 100644 index 0000000000000000000000000000000000000000..038617b455af2fcdd17dcf6d359598e116855789 --- /dev/null +++ b/src/classes/ParallelProcessing.py @@ -0,0 +1,313 @@ + +''' + * Project : Screenipy + * Author : Pranjal Joshi, Swar Patel + * Created : 18/05/2021 + * Description : Class for managing multiprocessing +''' + +import multiprocessing +import pandas as pd +import numpy as np +import sys +import os +import pytz +import traceback +from queue import Empty +from datetime import datetime +import classes.Fetcher as Fetcher +import classes.Screener as Screener +import classes.Utility as Utility +from copy import deepcopy +from classes.CandlePatterns import CandlePatterns +from classes.ColorText import colorText +from classes.SuppressOutput import SuppressOutput + +if sys.platform.startswith('win'): + import multiprocessing.popen_spawn_win32 as forking +else: + import multiprocessing.popen_fork as forking + + +class StockConsumer(multiprocessing.Process): + + def __init__(self, task_queue, result_queue, screenCounter, screenResultsCounter, stockDict, proxyServer, keyboardInterruptEvent): + multiprocessing.Process.__init__(self) + self.multiprocessingForWindows() + self.task_queue = task_queue + self.result_queue = result_queue + self.screenCounter = screenCounter + self.screenResultsCounter = screenResultsCounter + self.stockDict = stockDict + self.proxyServer = proxyServer + self.keyboardInterruptEvent = keyboardInterruptEvent + self.isTradingTime = Utility.tools.isTradingTime() + + def run(self): + # while True: + try: + while not self.keyboardInterruptEvent.is_set(): + try: + next_task = self.task_queue.get() + except Empty: + continue + if next_task is None: + self.task_queue.task_done() + break + answer = self.screenStocks(*(next_task)) + self.task_queue.task_done() + self.result_queue.put(answer) + except Exception as e: + sys.exit(0) + + def screenStocks(self, tickerOption, executeOption, reversalOption, maLength, daysForLowestVolume, minRSI, maxRSI, respChartPattern, insideBarToLookback, totalSymbols, + configManager, fetcher, screener:Screener.tools, candlePatterns, stock, newlyListedOnly, downloadOnly, vectorSearch, isDevVersion, backtestDate, printCounter=False): + screenResults = pd.DataFrame(columns=[ + 'Stock', 'Consolidating', 'Breaking-Out', 'MA-Signal', 'Volume', 'LTP', 'RSI', 'Trend', 'Pattern']) + screeningDictionary = {'Stock': "", 'Consolidating': "", 'Breaking-Out': "", + 'MA-Signal': "", 'Volume': "", 'LTP': 0, 'RSI': 0, 'Trend': "", 'Pattern': ""} + saveDictionary = {'Stock': "", 'Consolidating': "", 'Breaking-Out': "", + 'MA-Signal': "", 'Volume': "", 'LTP': 0, 'RSI': 0, 'Trend': "", 'Pattern': ""} + + try: + period = configManager.period + + # Data download adjustment for Newly Listed only feature + if newlyListedOnly: + if int(configManager.period[:-1]) > 250: + period = '250d' + else: + period = configManager.period + + if (self.stockDict.get(stock) is None) or (configManager.cacheEnabled is False) or self.isTradingTime or downloadOnly: + try: + data, backtestReport = fetcher.fetchStockData(stock, + period, + configManager.duration, + self.proxyServer, + self.screenResultsCounter, + self.screenCounter, + totalSymbols, + backtestDate=backtestDate, + tickerOption=tickerOption) + except Exception as e: + return screeningDictionary, saveDictionary + if configManager.cacheEnabled is True and not self.isTradingTime and (self.stockDict.get(stock) is None) or downloadOnly: + self.stockDict[stock] = data.to_dict('split') + if downloadOnly: + raise Screener.DownloadDataOnly + else: + if printCounter: + try: + print(colorText.BOLD + colorText.GREEN + ("[%d%%] Screened %d, Found %d. Fetching data & Analyzing %s..." % ( + int((self.screenCounter.value / totalSymbols) * 100), self.screenCounter.value, self.screenResultsCounter.value, stock)) + colorText.END, end='') + print(colorText.BOLD + colorText.GREEN + "=> Done!" + + colorText.END, end='\r', flush=True) + except ZeroDivisionError: + pass + sys.stdout.write("\r\033[K") + data = self.stockDict.get(stock) + data = pd.DataFrame( + data['data'], columns=data['columns'], index=data['index']) + + fullData, processedData = screener.preprocessData( + data, daysToLookback=configManager.daysToLookback) + + if type(vectorSearch) != bool and type(vectorSearch) and vectorSearch[2] == True: + executeOption = 0 + with self.screenCounter.get_lock(): + screener.addVector(fullData, stock, vectorSearch[1]) + + if newlyListedOnly: + if not screener.validateNewlyListed(fullData, period): + raise Screener.NotNewlyListed + + with self.screenCounter.get_lock(): + self.screenCounter.value += 1 + if not processedData.empty: + urlStock = None + if tickerOption == 16: + urlStock = deepcopy(stock).replace('^','').replace('.NS','') + stock = fetcher.getAllNiftyIndices()[stock] + stock = stock.replace('^','').replace('.NS','') + urlStock = stock.replace('&','_') if urlStock is None else urlStock.replace('&','_') + screeningDictionary['Stock'] = colorText.BOLD + \ + colorText.BLUE + f'\x1B]8;;https://in.tradingview.com/chart?symbol=NSE%3A{urlStock}\x1B\\{stock}\x1B]8;;\x1B\\' + colorText.END if tickerOption < 15 \ + else colorText.BOLD + \ + colorText.BLUE + f'\x1B]8;;https://in.tradingview.com/chart?symbol={urlStock}\x1B\\{stock}\x1B]8;;\x1B\\' + colorText.END + saveDictionary['Stock'] = stock + + consolidationValue = screener.validateConsolidation( + processedData, screeningDictionary, saveDictionary, percentage=configManager.consolidationPercentage) + isMaReversal = screener.validateMovingAverages( + processedData, screeningDictionary, saveDictionary, maRange=1.25) + isVolumeHigh = screener.validateVolume( + processedData, screeningDictionary, saveDictionary, volumeRatio=configManager.volumeRatio) + isBreaking = screener.findBreakout( + processedData, screeningDictionary, saveDictionary, daysToLookback=configManager.daysToLookback) + isLtpValid = screener.validateLTP( + fullData, screeningDictionary, saveDictionary, minLTP=configManager.minLTP, maxLTP=configManager.maxLTP) + if executeOption == 4: + isLowestVolume = screener.validateLowestVolume(processedData, daysForLowestVolume) + else: + isLowestVolume = False + isValidRsi = screener.validateRSI( + processedData, screeningDictionary, saveDictionary, minRSI, maxRSI) + try: + with SuppressOutput(suppress_stderr=True, suppress_stdout=True): + currentTrend = screener.findTrend( + processedData, + screeningDictionary, + saveDictionary, + daysToLookback=configManager.daysToLookback, + stockName=stock) + except np.RankWarning: + screeningDictionary['Trend'] = 'Unknown' + saveDictionary['Trend'] = 'Unknown' + + with SuppressOutput(suppress_stderr=True, suppress_stdout=True): + isCandlePattern = candlePatterns.findPattern( + processedData, screeningDictionary, saveDictionary) + + isConfluence = False + isInsideBar = False + isIpoBase = False + if newlyListedOnly: + isIpoBase = screener.validateIpoBase(stock, fullData, screeningDictionary, saveDictionary) + if respChartPattern == 3 and executeOption == 7: + isConfluence = screener.validateConfluence(stock, processedData, screeningDictionary, saveDictionary, percentage=insideBarToLookback) + else: + isInsideBar = screener.validateInsideBar(processedData, screeningDictionary, saveDictionary, chartPattern=respChartPattern, daysToLookback=insideBarToLookback) + + with SuppressOutput(suppress_stderr=True, suppress_stdout=True): + if maLength is not None and executeOption == 6 and reversalOption == 6: + isNR = screener.validateNarrowRange(processedData, screeningDictionary, saveDictionary, nr=maLength) + else: + isNR = screener.validateNarrowRange(processedData, screeningDictionary, saveDictionary) + + isMomentum = screener.validateMomentum(processedData, screeningDictionary, saveDictionary) + + isVSA = False + if not (executeOption == 7 and respChartPattern < 3): + isVSA = screener.validateVolumeSpreadAnalysis(processedData, screeningDictionary, saveDictionary) + if maLength is not None and executeOption == 6 and reversalOption == 4: + isMaSupport = screener.findReversalMA(fullData, screeningDictionary, saveDictionary, maLength) + if executeOption == 6 and reversalOption == 8: + isRsiReversal = screener.findRSICrossingMA(fullData, screeningDictionary, saveDictionary) + + isVCP = False + if respChartPattern == 4: + with SuppressOutput(suppress_stderr=True, suppress_stdout=True): + isVCP = screener.validateVCP(fullData, screeningDictionary, saveDictionary) + + isBuyingTrendline = False + if executeOption == 7 and respChartPattern == 5: + with SuppressOutput(suppress_stderr=True, suppress_stdout=True): + isBuyingTrendline = screener.findTrendlines(fullData, screeningDictionary, saveDictionary) + + with SuppressOutput(suppress_stderr=True, suppress_stdout=True): + isLorentzian = screener.validateLorentzian(fullData, screeningDictionary, saveDictionary, lookFor = maLength) + + try: + backtestReport = Utility.tools.calculateBacktestReport(data=processedData, backtestDict=backtestReport) + screeningDictionary.update(backtestReport) + saveDictionary.update(backtestReport) + except: + pass + + with self.screenResultsCounter.get_lock(): + if executeOption == 0: + self.screenResultsCounter.value += 1 + return screeningDictionary, saveDictionary + if (executeOption == 1 or executeOption == 2) and isBreaking and isVolumeHigh and isLtpValid: + self.screenResultsCounter.value += 1 + return screeningDictionary, saveDictionary + if (executeOption == 1 or executeOption == 3) and (consolidationValue <= configManager.consolidationPercentage and consolidationValue != 0) and isLtpValid: + self.screenResultsCounter.value += 1 + return screeningDictionary, saveDictionary + if executeOption == 4 and isLtpValid and isLowestVolume: + self.screenResultsCounter.value += 1 + return screeningDictionary, saveDictionary + if executeOption == 5 and isLtpValid and isValidRsi: + self.screenResultsCounter.value += 1 + return screeningDictionary, saveDictionary + if executeOption == 6 and isLtpValid: + if reversalOption == 1: + if saveDictionary['Pattern'] in CandlePatterns.reversalPatternsBullish or isMaReversal > 0 or 'buy' in saveDictionary['Pattern'].lower(): + self.screenResultsCounter.value += 1 + return screeningDictionary, saveDictionary + elif reversalOption == 2: + if saveDictionary['Pattern'] in CandlePatterns.reversalPatternsBearish or isMaReversal < 0 or 'sell' in saveDictionary['Pattern'].lower(): + self.screenResultsCounter.value += 1 + return screeningDictionary, saveDictionary + elif reversalOption == 3 and isMomentum: + self.screenResultsCounter.value += 1 + return screeningDictionary, saveDictionary + elif reversalOption == 4 and isMaSupport: + self.screenResultsCounter.value += 1 + return screeningDictionary, saveDictionary + elif reversalOption == 5 and isVSA and saveDictionary['Pattern'] in CandlePatterns.reversalPatternsBullish: + self.screenResultsCounter.value += 1 + return screeningDictionary, saveDictionary + elif reversalOption == 6 and isNR: + self.screenResultsCounter.value += 1 + return screeningDictionary, saveDictionary + elif reversalOption == 7 and isLorentzian: + self.screenResultsCounter.value += 1 + return screeningDictionary, saveDictionary + elif reversalOption == 8 and isRsiReversal: + self.screenResultsCounter.value += 1 + return screeningDictionary, saveDictionary + if executeOption == 7 and isLtpValid: + if respChartPattern < 3 and isInsideBar: + self.screenResultsCounter.value += 1 + return screeningDictionary, saveDictionary + if isConfluence: + self.screenResultsCounter.value += 1 + return screeningDictionary, saveDictionary + if isIpoBase and newlyListedOnly and not respChartPattern < 3: + self.screenResultsCounter.value += 1 + return screeningDictionary, saveDictionary + if isVCP: + self.screenResultsCounter.value += 1 + return screeningDictionary, saveDictionary + if isBuyingTrendline: + self.screenResultsCounter.value += 1 + return screeningDictionary, saveDictionary + except KeyboardInterrupt: + # Capturing Ctr+C Here isn't a great idea + pass + except Fetcher.StockDataEmptyException: + pass + except Screener.NotNewlyListed: + pass + except Screener.DownloadDataOnly: + pass + except KeyError: + pass + except Exception as e: + if isDevVersion: + print("[!] Dev Traceback:") + traceback.print_exc() + if printCounter: + print(colorText.FAIL + + ("\n[+] Exception Occured while Screening %s! Skipping this stock.." % stock) + colorText.END) + return + + def multiprocessingForWindows(self): + if sys.platform.startswith('win'): + + class _Popen(forking.Popen): + def __init__(self, *args, **kw): + if hasattr(sys, 'frozen'): + os.putenv('_MEIPASS2', sys._MEIPASS) + try: + super(_Popen, self).__init__(*args, **kw) + finally: + if hasattr(sys, 'frozen'): + if hasattr(os, 'unsetenv'): + os.unsetenv('_MEIPASS2') + else: + os.putenv('_MEIPASS2', '') + + forking.Popen = _Popen diff --git a/src/classes/Screener.py b/src/classes/Screener.py new file mode 100644 index 0000000000000000000000000000000000000000..cc4f408969e58e429451174422957e3838587a8d --- /dev/null +++ b/src/classes/Screener.py @@ -0,0 +1,800 @@ +''' + * Project : Screenipy + * Author : Pranjal Joshi + * Created : 28/04/2021 + * Description : Class for analyzing and validating stocks +''' + +import sys +import math +import numpy as np +import pandas as pd +import joblib +import keras +import time +import classes.Utility as Utility +from copy import copy +from advanced_ta import LorentzianClassification +from classes.Utility import isGui +from sklearn.preprocessing import StandardScaler +from scipy.signal import argrelextrema +from scipy.stats import linregress +from classes.ColorText import colorText +from classes.SuppressOutput import SuppressOutput +from classes.ScreenipyTA import ScreenerTA +try: + import chromadb + CHROMA_AVAILABLE = True +except: + CHROMA_AVAILABLE = False + + +# Exception for newly listed stocks with candle nos < daysToLookback +class StockDataNotAdequate(Exception): + pass + +# Exception for only downloading stock data and not screening +class DownloadDataOnly(Exception): + pass + +# Exception for stocks which are not newly listed when screening only for Newly Listed +class NotNewlyListed(Exception): + pass + +# This Class contains methods for stock analysis and screening validation +class tools: + + def __init__(self, configManager) -> None: + self.configManager = configManager + + # Private method to find candle type + # True = Bullish, False = Bearish + def getCandleType(self, dailyData): + return bool(dailyData['Close'].iloc[0] >= dailyData['Open'].iloc[0]) + + + # Preprocess the acquired data + def preprocessData(self, data:pd.DataFrame, daysToLookback=None): + if daysToLookback is None: + daysToLookback = self.configManager.daysToLookback + if self.configManager.useEMA: + sma = ScreenerTA.EMA(data['Close'],timeperiod=50) + lma = ScreenerTA.EMA(data['Close'],timeperiod=200) + data.insert(len(data.columns),'SMA',sma) + data.insert(len(data.columns),'LMA',lma) + else: + sma = data.rolling(window=50).mean() + lma = data.rolling(window=200).mean() + data.insert(len(data.columns),'SMA',sma['Close']) + data.insert(len(data.columns),'LMA',lma['Close']) + vol = data.rolling(window=20).mean() + rsi = ScreenerTA.RSI(data['Close'], timeperiod=14) + data.insert(len(data.columns),'VolMA',vol['Volume']) + data.insert(len(data.columns),'RSI',rsi) + data = data[::-1] # Reverse the dataframe + # data = data.fillna(0) + # data = data.replace([np.inf, -np.inf], 0) + fullData = data + trimmedData = data.head(daysToLookback) + return (fullData, trimmedData) + + # Validate LTP within limits + def validateLTP(self, data, screenDict, saveDict, minLTP=None, maxLTP=None): + if minLTP is None: + minLTP = self.configManager.minLTP + if maxLTP is None: + maxLTP = self.configManager.maxLTP + data = data.fillna(0) + data = data.replace([np.inf, -np.inf], 0) + recent = data.head(1) + + pct_change = (data[::-1]['Close'].pct_change(fill_method=None) * 100).iloc[-1] + if pct_change > 0.2: + pct_change = colorText.GREEN + (" (%.1f%%)" % pct_change) + colorText.END + elif pct_change < -0.2: + pct_change = colorText.FAIL + (" (%.1f%%)" % pct_change) + colorText.END + else: + pct_change = colorText.WARN + (" (%.1f%%)" % pct_change) + colorText.END + + ltp = round(recent['Close'].iloc[0],2) + saveDict['LTP'] = str(ltp) + verifyStageTwo = True + if self.configManager.stageTwo and len(data) > 250: + yearlyLow = data.head(250).min()['Close'] + yearlyHigh = data.head(250).max()['Close'] + if ltp < (2 * yearlyLow) or ltp < (0.75 * yearlyHigh): + verifyStageTwo = False + if(ltp >= minLTP and ltp <= maxLTP and verifyStageTwo): + screenDict['LTP'] = colorText.GREEN + ("%.2f" % ltp) + pct_change + colorText.END + return True + screenDict['LTP'] = colorText.FAIL + ("%.2f" % ltp) + pct_change + colorText.END + return False + + # Validate if share prices are consolidating + def validateConsolidation(self, data, screenDict, saveDict, percentage=10): + data = data.fillna(0) + data = data.replace([np.inf, -np.inf], 0) + hc = data.describe()['Close']['max'] + lc = data.describe()['Close']['min'] + if ((hc - lc) <= (hc*percentage/100) and (hc - lc != 0)): + screenDict['Consolidating'] = colorText.BOLD + colorText.GREEN + "Range = " + str(round((abs((hc-lc)/hc)*100),1))+"%" + colorText.END + else: + screenDict['Consolidating'] = colorText.BOLD + colorText.FAIL + "Range = " + str(round((abs((hc-lc)/hc)*100),1)) + "%" + colorText.END + saveDict['Consolidating'] = str(round((abs((hc-lc)/hc)*100),1))+"%" + return round((abs((hc-lc)/hc)*100),1) + + # Validate Moving averages and look for buy/sell signals + def validateMovingAverages(self, data, screenDict, saveDict, maRange=2.5): + data = data.fillna(0) + data = data.replace([np.inf, -np.inf], 0) + recent = data.head(1) + if(recent['SMA'].iloc[0] > recent['LMA'].iloc[0] and recent['Close'].iloc[0] > recent['SMA'].iloc[0]): + screenDict['MA-Signal'] = colorText.BOLD + colorText.GREEN + 'Bullish' + colorText.END + saveDict['MA-Signal'] = 'Bullish' + elif(recent['SMA'].iloc[0] < recent['LMA'].iloc[0]): + screenDict['MA-Signal'] = colorText.BOLD + colorText.FAIL + 'Bearish' + colorText.END + saveDict['MA-Signal'] = 'Bearish' + elif(recent['SMA'].iloc[0] == 0): + screenDict['MA-Signal'] = colorText.BOLD + colorText.WARN + 'Unknown' + colorText.END + saveDict['MA-Signal'] = 'Unknown' + else: + screenDict['MA-Signal'] = colorText.BOLD + colorText.WARN + 'Neutral' + colorText.END + saveDict['MA-Signal'] = 'Neutral' + + smaDev = data['SMA'].iloc[0] * maRange / 100 + lmaDev = data['LMA'].iloc[0] * maRange / 100 + 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] + maReversal = 0 + # Taking Support 50 + if close > sma and low <= (sma + smaDev): + screenDict['MA-Signal'] = colorText.BOLD + colorText.GREEN + '50MA-Support' + colorText.END + saveDict['MA-Signal'] = '50MA-Support' + maReversal = 1 + # Validating Resistance 50 + elif close < sma and high >= (sma - smaDev): + screenDict['MA-Signal'] = colorText.BOLD + colorText.FAIL + '50MA-Resist' + colorText.END + saveDict['MA-Signal'] = '50MA-Resist' + maReversal = -1 + # Taking Support 200 + elif close > lma and low <= (lma + lmaDev): + screenDict['MA-Signal'] = colorText.BOLD + colorText.GREEN + '200MA-Support' + colorText.END + saveDict['MA-Signal'] = '200MA-Support' + maReversal = 1 + # Validating Resistance 200 + elif close < lma and high >= (lma - lmaDev): + screenDict['MA-Signal'] = colorText.BOLD + colorText.FAIL + '200MA-Resist' + colorText.END + saveDict['MA-Signal'] = '200MA-Resist' + maReversal = -1 + # For a Bullish Candle + if self.getCandleType(data): + # Crossing up 50 + if open < sma and close > sma: + screenDict['MA-Signal'] = colorText.BOLD + colorText.GREEN + 'BullCross-50MA' + colorText.END + saveDict['MA-Signal'] = 'BullCross-50MA' + maReversal = 1 + # Crossing up 200 + elif open < lma and close > lma: + screenDict['MA-Signal'] = colorText.BOLD + colorText.GREEN + 'BullCross-200MA' + colorText.END + saveDict['MA-Signal'] = 'BullCross-200MA' + maReversal = 1 + # For a Bearish Candle + elif not self.getCandleType(data): + # Crossing down 50 + if open > sma and close < sma: + screenDict['MA-Signal'] = colorText.BOLD + colorText.FAIL + 'BearCross-50MA' + colorText.END + saveDict['MA-Signal'] = 'BearCross-50MA' + maReversal = -1 + # Crossing up 200 + elif open > lma and close < lma: + screenDict['MA-Signal'] = colorText.BOLD + colorText.FAIL + 'BearCross-200MA' + colorText.END + saveDict['MA-Signal'] = 'BearCross-200MA' + maReversal = -1 + return maReversal + + # Validate if volume of last day is higher than avg + def validateVolume(self, data, screenDict, saveDict, volumeRatio=2.5): + data = data.fillna(0) + data = data.replace([np.inf, -np.inf], 0) + recent = data.head(1) + if recent['VolMA'].iloc[0] == 0: # Handles Divide by 0 warning + saveDict['Volume'] = "Unknown" + screenDict['Volume'] = colorText.BOLD + colorText.WARN + "Unknown" + colorText.END + return True + ratio = round(recent['Volume'].iloc[0]/recent['VolMA'].iloc[0],2) + saveDict['Volume'] = str(ratio)+"x" + if(ratio >= volumeRatio and ratio != np.nan and (not math.isinf(ratio)) and (ratio != 20)): + screenDict['Volume'] = colorText.BOLD + colorText.GREEN + str(ratio) + "x" + colorText.END + return True + screenDict['Volume'] = colorText.BOLD + colorText.FAIL + str(ratio) + "x" + colorText.END + return False + + # Find accurate breakout value + def findBreakout(self, data, screenDict, saveDict, daysToLookback): + data = data.fillna(0) + data = data.replace([np.inf, -np.inf], 0) + recent = data.head(1) + data = data[1:] + hs = round(data.describe()['High']['max'],2) + hc = round(data.describe()['Close']['max'],2) + rc = round(recent['Close'].iloc[0],2) + if np.isnan(hc) or np.isnan(hs): + saveDict['Breaking-Out'] = 'BO: Unknown' + screenDict['Breaking-Out'] = colorText.BOLD + colorText.WARN + 'BO: Unknown' + colorText.END + return False + if hs > hc: + if ((hs - hc) <= (hs*2/100)): + saveDict['Breaking-Out'] = str(hc) + if rc >= hc: + screenDict['Breaking-Out'] = colorText.BOLD + colorText.GREEN + "BO: " + str(hc) + " R: " + str(hs) + colorText.END + return True and self.getCandleType(recent) + screenDict['Breaking-Out'] = colorText.BOLD + colorText.FAIL + "BO: " + str(hc) + " R: " + str(hs) + colorText.END + return False + noOfHigherShadows = len(data[data.High > hc]) + if(daysToLookback/noOfHigherShadows <= 3): + saveDict['Breaking-Out'] = str(hs) + if rc >= hs: + screenDict['Breaking-Out'] = colorText.BOLD + colorText.GREEN + "BO: " + str(hs) + colorText.END + return True and self.getCandleType(recent) + screenDict['Breaking-Out'] = colorText.BOLD + colorText.FAIL + "BO: " + str(hs) + colorText.END + return False + saveDict['Breaking-Out'] = str(hc) + ", " + str(hs) + if rc >= hc: + screenDict['Breaking-Out'] = colorText.BOLD + colorText.GREEN + "BO: " + str(hc) + " R: " + str(hs) + colorText.END + return True and self.getCandleType(recent) + screenDict['Breaking-Out'] = colorText.BOLD + colorText.FAIL + "BO: " + str(hc) + " R: " + str(hs) + colorText.END + return False + else: + saveDict['Breaking-Out'] = str(hc) + if rc >= hc: + screenDict['Breaking-Out'] = colorText.BOLD + colorText.GREEN + "BO: " + str(hc) + colorText.END + return True and self.getCandleType(recent) + screenDict['Breaking-Out'] = colorText.BOLD + colorText.FAIL + "BO: " + str(hc) + colorText.END + return False + + # Validate 'Inside Bar' structure for recent days + def validateInsideBar(self, data, screenDict, saveDict, chartPattern=1, daysToLookback=5): + orgData = data + daysToLookback = int(daysToLookback) + for i in range(daysToLookback, round(daysToLookback*0.5)-1, -1): + if i == 2: + return 0 # Exit if only last 2 candles are left + if chartPattern == 1: + if "Up" in saveDict['Trend'] and ("Bull" in saveDict['MA-Signal'] or "Support" in saveDict['MA-Signal']): + data = orgData.head(i) + refCandle = data.tail(1) + 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): + screenDict['Pattern'] = colorText.BOLD + colorText.WARN + ("Inside Bar (%d)" % i) + colorText.END + saveDict['Pattern'] = "Inside Bar (%d)" % i + return i + else: + return 0 + else: + if "Down" in saveDict['Trend'] and ("Bear" in saveDict['MA-Signal'] or "Resist" in saveDict['MA-Signal']): + data = orgData.head(i) + refCandle = data.tail(1) + 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): + screenDict['Pattern'] = colorText.BOLD + colorText.WARN + ("Inside Bar (%d)" % i) + colorText.END + saveDict['Pattern'] = "Inside Bar (%d)" % i + return i + else: + return 0 + return 0 + + # Validate if recent volume is lowest of last 'N' Days + def validateLowestVolume(self, data, daysForLowestVolume): + data = data.fillna(0) + data = data.replace([np.inf, -np.inf], 0) + if daysForLowestVolume is None: + daysForLowestVolume = 30 + data = data.head(daysForLowestVolume) + recent = data.head(1) + if((recent['Volume'].iloc[0] <= data.describe()['Volume']['min']) and recent['Volume'].iloc[0] != np.nan): + return True + return False + + # validate if RSI is within given range + def validateRSI(self, data, screenDict, saveDict, minRSI, maxRSI): + data = data.fillna(0) + data = data.replace([np.inf, -np.inf], 0) + rsi = int(data.head(1)['RSI'].iloc[0]) + saveDict['RSI'] = rsi + if(rsi >= minRSI and rsi <= maxRSI) and (rsi <= 70 and rsi >= 30): + screenDict['RSI'] = colorText.BOLD + colorText.GREEN + str(rsi) + colorText.END + return True + screenDict['RSI'] = colorText.BOLD + colorText.FAIL + str(rsi) + colorText.END + return False + + # Find out trend for days to lookback + def findTrend(self, data, screenDict, saveDict, daysToLookback=None,stockName=""): + if daysToLookback is None: + daysToLookback = self.configManager.daysToLookback + data = data.head(daysToLookback) + data = data[::-1] + data = data.set_index(np.arange(len(data))) + data = data.fillna(0) + data = data.replace([np.inf, -np.inf], 0) + with SuppressOutput(suppress_stdout=True,suppress_stderr=True): + data['tops'] = data['Close'].iloc[list(argrelextrema(np.array(data['Close']), np.greater_equal, order=1)[0])] + data = data.fillna(0) + data = data.replace([np.inf, -np.inf], 0) + try: + try: + if len(data) < daysToLookback: + raise StockDataNotAdequate + slope,c = np.polyfit(data.index[data.tops > 0], data['tops'][data.tops > 0], 1) + except Exception as e: + slope,c = 0,0 + angle = np.rad2deg(np.arctan(slope)) + if (angle == 0): + screenDict['Trend'] = colorText.BOLD + colorText.WARN + "Unknown" + colorText.END + saveDict['Trend'] = 'Unknown' + elif (angle <= 30 and angle >= -30): + screenDict['Trend'] = colorText.BOLD + colorText.WARN + "Sideways" + colorText.END + saveDict['Trend'] = 'Sideways' + elif (angle >= 30 and angle < 61): + screenDict['Trend'] = colorText.BOLD + colorText.GREEN + "Weak Up" + colorText.END + saveDict['Trend'] = 'Weak Up' + elif angle >= 60: + screenDict['Trend'] = colorText.BOLD + colorText.GREEN + "Strong Up" + colorText.END + saveDict['Trend'] = 'Strong Up' + elif (angle <= -30 and angle > -61): + screenDict['Trend'] = colorText.BOLD + colorText.FAIL + "Weak Down" + colorText.END + saveDict['Trend'] = 'Weak Down' + elif angle <= -60: + screenDict['Trend'] = colorText.BOLD + colorText.FAIL + "Strong Down" + colorText.END + saveDict['Trend'] = 'Strong Down' + except np.linalg.LinAlgError: + screenDict['Trend'] = colorText.BOLD + colorText.WARN + "Unknown" + colorText.END + saveDict['Trend'] = 'Unknown' + return saveDict['Trend'] + + # Find if stock is validating volume spread analysis + def validateVolumeSpreadAnalysis(self, data, screenDict, saveDict): + try: + data = data.head(2) + try: + # Check for previous RED candles + # Current candle = 0th, Previous Candle = 1st for following logic + if data.iloc[1]['Open'] >= data.iloc[1]['Close']: + spread1 = abs(data.iloc[1]['Open'] - data.iloc[1]['Close']) + spread0 = abs(data.iloc[0]['Open'] - data.iloc[0]['Close']) + lower_wick_spread0 = max(data.iloc[0]['Open'], data.iloc[0]['Close']) - data.iloc[0]['Low'] + vol1 = data.iloc[1]['Volume'] + vol0 = data.iloc[0]['Volume'] + 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): + screenDict['Pattern'] = colorText.BOLD + colorText.GREEN + 'Supply Drought' + colorText.END + saveDict['Pattern'] = 'Supply Drought' + return True + if spread0 < spread1 and vol0 > vol1 and data.iloc[0]['Volume'] > data.iloc[0]['VolMA'] and data.iloc[0]['Close'] <= data.iloc[1]['Open']: + screenDict['Pattern'] = colorText.BOLD + colorText.GREEN + 'Demand Rise' + colorText.END + saveDict['Pattern'] = 'Demand Rise' + return True + except IndexError: + pass + return False + except: + import traceback + traceback.print_exc() + return False + + # Find if stock gaining bullish momentum + def validateMomentum(self, data, screenDict, saveDict): + try: + data = data.head(3) + for row in data.iterrows(): + # All 3 candles should be Green and NOT Circuits + if row[1]['Close'].item() <= row[1]['Open'].item(): + return False + openDesc = data.sort_values(by=['Open'], ascending=False) + closeDesc = data.sort_values(by=['Close'], ascending=False) + volDesc = data.sort_values(by=['Volume'], ascending=False) + try: + if data.equals(openDesc) and data.equals(closeDesc) and data.equals(volDesc): + if (data['Open'].iloc[0].item() >= data['Close'].iloc[1].item()) and (data['Open'].iloc[1].item() >= data['Close'].iloc[2].item()): + screenDict['Pattern'] = colorText.BOLD + colorText.GREEN + 'Momentum Gainer' + colorText.END + saveDict['Pattern'] = 'Momentum Gainer' + return True + except IndexError: + pass + return False + except Exception as e: + import traceback + traceback.print_exc() + return False + + # Find stock reversing at given MA + def findReversalMA(self, data, screenDict, saveDict, maLength, percentage=0.015): + if maLength is None: + maLength = 20 + data = data[::-1] + if self.configManager.useEMA: + maRev = ScreenerTA.EMA(data['Close'],timeperiod=maLength) + else: + maRev = ScreenerTA.MA(data['Close'],timeperiod=maLength) + data.insert(len(data.columns),'maRev',maRev) + data = data[::-1].head(3) + 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]: + if self.configManager.stageTwo: + 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]: + return False + screenDict['MA-Signal'] = colorText.BOLD + colorText.GREEN + f'Reversal-{maLength}MA' + colorText.END + saveDict['MA-Signal'] = f'Reversal-{maLength}MA' + return True + return False + + # Find stock showing RSI crossing with RSI 9 SMA + def findRSICrossingMA(self, data, screenDict, saveDict, maLength=9): + data = data[::-1] + maRsi = ScreenerTA.MA(data['RSI'], timeperiod=maLength) + data.insert(len(data.columns),'maRsi',maRsi) + data = data[::-1].head(3) + if data['maRsi'].iloc[0] <= data['RSI'].iloc[0] and data['maRsi'].iloc[1] > data['RSI'].iloc[1]: + screenDict['MA-Signal'] = colorText.BOLD + colorText.GREEN + f'RSI-MA-Buy' + colorText.END + saveDict['MA-Signal'] = f'RSI-MA-Buy' + return True + elif data['maRsi'].iloc[0] >= data['RSI'].iloc[0] and data['maRsi'].iloc[1] < data['RSI'].iloc[1]: + screenDict['MA-Signal'] = colorText.BOLD + colorText.GREEN + f'RSI-MA-Sell' + colorText.END + saveDict['MA-Signal'] = f'RSI-MA-Sell' + return True + return False + + + # Find IPO base + def validateIpoBase(self, stock, data, screenDict, saveDict, percentage=0.3): + listingPrice = data[::-1].head(1)['Open'].iloc[0] + currentPrice = data.head(1)['Close'].iloc[0] + ATH = data.describe()['High']['max'] + if ATH > (listingPrice + (listingPrice * percentage)): + return False + away = round(((currentPrice - listingPrice)/listingPrice)*100, 1) + if((listingPrice - (listingPrice * percentage)) <= currentPrice <= (listingPrice + (listingPrice * percentage))): + if away > 0: + screenDict['Pattern'] = colorText.BOLD + colorText.GREEN + f'IPO Base ({away} %)' + colorText.END + else: + screenDict['Pattern'] = colorText.BOLD + colorText.GREEN + 'IPO Base ' + colorText.FAIL + f'({away} %)' + colorText.END + saveDict['Pattern'] = f'IPO Base ({away} %)' + return True + return False + + # Find Conflucence + def validateConfluence(self, stock, data, screenDict, saveDict, percentage=0.1): + recent = data.head(1) + if(abs(recent['SMA'].iloc[0] - recent['LMA'].iloc[0]) <= (recent['SMA'].iloc[0] * percentage)): + difference = round(abs(recent['SMA'].iloc[0] - recent['LMA'].iloc[0])/recent['Close'].iloc[0] * 100,2) + if recent['SMA'].iloc[0] >= recent['LMA'].iloc[0]: + screenDict['MA-Signal'] = colorText.BOLD + colorText.GREEN + f'Confluence ({difference}%)' + colorText.END + saveDict['MA-Signal'] = f'Confluence ({difference}%)' + else: + screenDict['MA-Signal'] = colorText.BOLD + colorText.FAIL + f'Confluence ({difference}%)' + colorText.END + saveDict['MA-Signal'] = f'Confluence ({difference}%)' + return True + return False + + # Find if stock is newly listed + def validateNewlyListed(self, data, daysToLookback): + daysToLookback = int(daysToLookback[:-1]) + recent = data.head(1) + if len(data) < daysToLookback and (recent['Close'].iloc[0] != np.nan and recent['Close'].iloc[0] > 0): + return True + return False + + # Find stocks approching to long term trendlines + def findTrendlines(self, data, screenDict, saveDict, percentage = 0.05): + period = int(''.join(c for c in self.configManager.period if c.isdigit())) + if len(data) < period: + return False + + data = data[::-1] + data['Number'] = np.arange(len(data))+1 + data_high = data.copy() + data_low = data.copy() + points = 30 + + ''' Ignoring the Resitance for long-term purpose + while len(data_high) > points: + slope, intercept, r_value, p_value, std_err = linregress(x=data_high['Number'], y=data_high['High']) + data_high = data_high.loc[data_high['High'] > slope * data_high['Number'] + intercept] + slope, intercept, r_value, p_value, std_err = linregress(x=data_high['Number'], y=data_high['Close']) + data['Resistance'] = slope * data['Number'] + intercept + ''' + + while len(data_low) > points: + slope, intercept, r_value, p_value, std_err = linregress(x=data_low['Number'], y=data_low['Low']) + data_low = data_low.loc[data_low['Low'] < slope * data_low['Number'] + intercept] + + slope, intercept, r_value, p_value, std_err = linregress(x=data_low['Number'], y=data_low['Close']) + data['Support'] = slope * data['Number'] + intercept + now = data.tail(1) + + limit_upper = now['Support'].iloc[0].item() + (now['Support'].iloc[0].item() * percentage) + limit_lower = now['Support'].iloc[0].item() - (now['Support'].iloc[0].item() * percentage) + + if limit_lower < now['Close'].iloc[0].item() < limit_upper and slope > 0.15: + screenDict['Pattern'] = colorText.BOLD + colorText.GREEN + 'Trendline-Support' + colorText.END + saveDict['Pattern'] = 'Trendline-Support' + return True + + ''' Plots for debugging + import matplotlib.pyplot as plt + fig, ax1 = plt.subplots(figsize=(15,10)) + color = 'tab:green' + xdate = [x.date() for x in data.index] + ax1.set_xlabel('Date', color=color) + ax1.plot(xdate, data.Close, label="close", color=color) + ax1.tick_params(axis='x', labelcolor=color) + + ax2 = ax1.twiny() # ax2 and ax1 will have common y axis and different x axis, twiny + ax2.plot(data.Number, data.Resistance, label="Res") + ax2.plot(data.Number, data.Support, label="Sup") + + plt.legend() + plt.grid() + plt.show() + ''' + return False + + + # Find NRx range for Reversal + def validateNarrowRange(self, data, screenDict, saveDict, nr=4): + if Utility.tools.isTradingTime(): + rangeData = data.head(nr+1)[1:] + now_candle = data.head(1) + rangeData['Range'] = abs(rangeData['Close'] - rangeData['Open']) + recent = rangeData.head(1) + if recent['Range'].iloc[0] == rangeData.describe()['Range']['min']: + if self.getCandleType(recent) and now_candle['Close'].iloc[0] >= recent['Close'].iloc[0]: + screenDict['Pattern'] = colorText.BOLD + colorText.GREEN + f'Buy-NR{nr}' + colorText.END + saveDict['Pattern'] = f'Buy-NR{nr}' + return True + elif not self.getCandleType(recent) and now_candle['Close'].iloc[0] <= recent['Close'].iloc[0]: + screenDict['Pattern'] = colorText.BOLD + colorText.FAIL + f'Sell-NR{nr}' + colorText.END + saveDict['Pattern'] = f'Sell-NR{nr}' + return True + return False + else: + rangeData = data.head(nr) + rangeData['Range'] = abs(rangeData['Close'] - rangeData['Open']) + recent = rangeData.head(1) + if recent['Range'].iloc[0] == rangeData.describe()['Range']['min']: + screenDict['Pattern'] = colorText.BOLD + colorText.GREEN + f'NR{nr}' + colorText.END + saveDict['Pattern'] = f'NR{nr}' + return True + return False + + # Validate Lorentzian Classification signal + def validateLorentzian(self, data, screenDict, saveDict, lookFor=1): + # lookFor: 1-Any, 2-Buy, 3-Sell + data = data[::-1] # Reverse the dataframe + data = data.rename(columns={'Open':'open', 'Close':'close', 'High':'high', 'Low':'low', 'Volume':'volume'}) + lc = LorentzianClassification(data=data) + if lc.df.iloc[-1]['isNewBuySignal']: + screenDict['Pattern'] = colorText.BOLD + colorText.GREEN + f'Lorentzian-Buy' + colorText.END + saveDict['Pattern'] = f'Lorentzian-Buy' + if lookFor != 3: + return True + elif lc.df.iloc[-1]['isNewSellSignal']: + screenDict['Pattern'] = colorText.BOLD + colorText.FAIL + f'Lorentzian-Sell' + colorText.END + saveDict['Pattern'] = f'Lorentzian-Sell' + if lookFor != 2: + return True + return False + + # Validate VPC + def validateVCP(self, data, screenDict, saveDict, stockName=None, window=3, percentageFromTop=3): + try: + percentageFromTop /= 100 + data.reset_index(inplace=True) + data.rename(columns={'index':'Date'}, inplace=True) + data['tops'] = data['High'].iloc[list(argrelextrema(np.array(data['High']), np.greater_equal, order=window)[0])].head(4) + data['bots'] = data['Low'].iloc[list(argrelextrema(np.array(data['Low']), np.less_equal, order=window)[0])].head(4) + data = data.fillna(0) + data = data.replace([np.inf, -np.inf], 0) + tops = data[data.tops > 0] + bots = data[data.bots > 0] + highestTop = round(tops.describe()['High']['max'],1) + filteredTops = tops[tops.tops > (highestTop-(highestTop*percentageFromTop))] + # print(tops) + # print(filteredTops) + # print(tops.sort_values(by=['tops'], ascending=False)) + # print(tops.describe()) + # print(f"Till {highestTop-(highestTop*percentageFromTop)}") + if(filteredTops.equals(tops)): # Tops are in the range + lowPoints = [] + for i in range(len(tops)-1): + endDate = tops.iloc[i]['Date'] + startDate = tops.iloc[i+1]['Date'] + lowPoints.append(data[(data.Date >= startDate) & (data.Date <= endDate)].describe()['Low']['min']) + lowPointsOrg = lowPoints + lowPoints.sort(reverse=True) + lowPointsSorted = lowPoints + ltp = data.head(1)['Close'].iloc[0] + if lowPointsOrg == lowPointsSorted and ltp < highestTop and ltp > lowPoints[0]: + screenDict['Pattern'] = colorText.BOLD + colorText.GREEN + f'VCP (BO: {highestTop})' + colorText.END + saveDict['Pattern'] = f'VCP (BO: {highestTop})' + return True + except Exception as e: + import traceback + print(traceback.format_exc()) + return False + + def getNiftyPrediction(self, data, proxyServer): + import warnings + warnings.filterwarnings("ignore") + # Disable GPUs as this causes wrong preds in Docker + import tensorflow as tf + physical_devices = tf.config.list_physical_devices('GPU') + try: + tf.config.set_visible_devices([], 'GPU') + visible_devices = tf.config.get_visible_devices() + for device in visible_devices: + assert device.device_type != 'GPU' + except: + pass + # + model, pkl = Utility.tools.getNiftyModel(proxyServer=proxyServer) + datacopy = copy(data[pkl['columns']]) + with SuppressOutput(suppress_stderr=True, suppress_stdout=True): + data = data[pkl['columns']] + ### v2 Preprocessing + for col in pkl['columns']: + data[col] = data[col].pct_change(fill_method=None) * 100 + data = data.ffill().dropna() + data = data.iloc[-1] + ### + data = pkl['scaler'].transform([data]) + pred = model.predict(data)[0] + if pred > 0.5: + out = colorText.BOLD + colorText.FAIL + "BEARISH" + colorText.END + colorText.BOLD + sug = "Hold your Short position!" + else: + out = colorText.BOLD + colorText.GREEN + "BULLISH" + colorText.END + colorText.BOLD + sug = "Stay Bullish!" + if not Utility.tools.isClosingHour(): + 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) + print(colorText.BOLD + colorText.BLUE + "\n" + "[+] Nifty AI Prediction -> " + colorText.END + colorText.BOLD + "Market may Open {} next day! {}".format(out, sug) + colorText.END) + print(colorText.BOLD + colorText.BLUE + "\n" + "[+] Nifty AI Prediction -> " + colorText.END + "Probability/Strength of Prediction = {}%".format(Utility.tools.getSigmoidConfidence(pred[0]))) + if isGui(): + return pred, 'BULLISH' if pred <= 0.5 else 'BEARISH', Utility.tools.getSigmoidConfidence(pred[0]), pd.DataFrame(datacopy.iloc[-1]).T + return pred + + def monitorFiveEma(self, proxyServer, fetcher, result_df, last_signal, risk_reward = 3): + col_names = ['High', 'Low', 'Close', '5EMA'] + data_list = ['nifty_buy', 'banknifty_buy', 'nifty_sell', 'banknifty_sell'] + + data_tuple = fetcher.fetchFiveEmaData() + for cnt in range(len(data_tuple)): + d = data_tuple[cnt] + d['5EMA'] = ScreenerTA.EMA(d['Close'],timeperiod=5) + d = d[col_names] + d = d.dropna().round(2) + + with SuppressOutput(suppress_stderr=True, suppress_stdout=True): + if 'sell' in data_list[cnt]: + streched = d[(d.Low > d['5EMA']) & (d.Low - d['5EMA'] > 0.5)] + streched['SL'] = streched.High + validate = d[(d.Low.shift(1) > d['5EMA'].shift(1)) & (d.Low.shift(1) - d['5EMA'].shift(1) > 0.5)] + old_index = validate.index + else: + mask = (d.High < d['5EMA']) & (d['5EMA'] - d.High > 0.5) # Buy + streched = d[mask] + streched['SL'] = streched.Low + validate = d.loc[mask.shift(1).fillna(False)] + old_index = validate.index + 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']) + validate = pd.concat([ + validate.reset_index(drop=True), + streched['SL'].reset_index(drop=True), + tgt, + ], + axis=1 + ) + validate = validate.tail(len(old_index)) + validate = validate.set_index(old_index) + if 'sell' in data_list[cnt]: + final = validate[validate.Close < validate['5EMA']].tail(1) + else: + final = validate[validate.Close > validate['5EMA']].tail(1) + + + if data_list[cnt] not in last_signal: + last_signal[data_list[cnt]] = final + elif data_list[cnt] in last_signal: + try: + condition = last_signal[data_list[cnt]][0]['SL'].iloc[0] + except KeyError: + condition = last_signal[data_list[cnt]]['SL'].iloc[0] + # if last_signal[data_list[cnt]] is not final: # Debug - Shows all conditions + if condition != final['SL'].iloc[0]: + # Do something with results + try: + result_df = pd.concat([ + result_df, + pd.DataFrame([ + [ + colorText.BLUE + str(final.index[0]) + colorText.END, + colorText.BOLD + colorText.WARN + data_list[cnt].split('_')[0].upper() + colorText.END, + (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), + colorText.FAIL + str(final.SL[0]) + colorText.END, + colorText.GREEN + str(final.Target[0]) + colorText.END, + f'1:{risk_reward}' + ] + ], columns=result_df.columns) + ], axis=0) + result_df.reset_index(drop=True, inplace=True) + except Exception as e: + pass + # Then update + last_signal[data_list[cnt]] = [final] + result_df.drop_duplicates(keep='last', inplace=True) + result_df.sort_values(by='Time', inplace=True) + return result_df[::-1] + + # Add data to vector database + def addVector(self, data, stockCode, daysToLookback): + data = data[::-1] # Reinverting preprocessedData for pct_change + data = data.pct_change(fill_method=None) + # data = data[::-1] # Do we need to invert again? No we dont - See operation after flatten + data = data[['Open', 'High', 'Low', 'Close']] + data = data.reset_index(drop=True) + data = data.dropna() + data = data.to_numpy().flatten().tolist() + data = data[(-4 * daysToLookback):] # Keep only OHLC * daysToLookback samples + if len(data) == (4 * daysToLookback): + chroma_client = chromadb.PersistentClient(path="./chromadb_store/") + collection = chroma_client.get_or_create_collection(name="nse_stocks") + collection.upsert( + embeddings=[data], + documents=[stockCode], + ids=[stockCode] + ) + return data + + + ''' + # Find out trend for days to lookback + def validateVCP(data, screenDict, saveDict, daysToLookback=ConfigManager.daysToLookback, stockName=None): + // De-index date + data.reset_index(inplace=True) + data.rename(columns={'index':'Date'}, inplace=True) + data = data.head(daysToLookback) + data = data[::-1] + data = data.set_index(np.arange(len(data))) + data = data.fillna(0) + data = data.replace([np.inf, -np.inf], 0) + data['tops'] = data['Close'].iloc[list(argrelextrema(np.array(data['Close']), np.greater_equal, order=3)[0])] + data['bots'] = data['Close'].iloc[list(argrelextrema(np.array(data['Close']), np.less_equal, order=3)[0])] + try: + try: + top_slope,top_c = np.polyfit(data.index[data.tops > 0], data['tops'][data.tops > 0], 1) + bot_slope,bot_c = np.polyfit(data.index[data.bots > 0], data['bots'][data.bots > 0], 1) + topAngle = math.degrees(math.atan(top_slope)) + vcpAngle = math.degrees(math.atan(bot_slope) - math.atan(top_slope)) + + # print(math.degrees(math.atan(top_slope))) + # print(math.degrees(math.atan(bot_slope))) + # print(vcpAngle) + # print(topAngle) + # print(data.max()['bots']) + # print(data.max()['tops']) + 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): + print("---> GOOD VCP %s at %sRs" % (stockName, top_c)) + import os + os.system("echo %s >> vcp_plots\VCP.txt" % stockName) + + import matplotlib.pyplot as plt + plt.scatter(data.index[data.tops > 0], data['tops'][data.tops > 0], c='g') + plt.scatter(data.index[data.bots > 0], data['bots'][data.bots > 0], c='r') + plt.plot(data.index, data['Close']) + plt.plot(data.index, top_slope*data.index+top_c,'g--') + plt.plot(data.index, bot_slope*data.index+bot_c,'r--') + if stockName != None: + plt.title(stockName) + # plt.show() + plt.savefig('vcp_plots\%s.png' % stockName) + plt.clf() + except np.RankWarning: + pass + except np.linalg.LinAlgError: + return False + ''' + diff --git a/src/classes/ScreenipyTA.py b/src/classes/ScreenipyTA.py new file mode 100644 index 0000000000000000000000000000000000000000..0baf85b56f154b57154283666c37c69f02f7a868 --- /dev/null +++ b/src/classes/ScreenipyTA.py @@ -0,0 +1,269 @@ +import numpy as np +import os + +if 'STREAMLIT_APP' in os.environ: + import pandas_ta as talib + print('[+] Importing pandas_ta as we are running on Streamlit cloud app') +else: + try: + import talib + except ImportError: + import pandas_ta as talib + + +class ScreenerTA: + + @staticmethod + def EMA(close, timeperiod): + try: + return talib.ema(close,timeperiod) + except Exception as e: + return talib.EMA(close.to_numpy().reshape(-1),timeperiod) + + @staticmethod + def SMA(close, timeperiod): + try: + return talib.sma(close,timeperiod) + except Exception as e: + return talib.SMA(close.to_numpy().reshape(-1),timeperiod) + + @staticmethod + def MA(close, timeperiod): + try: + return talib.ma(close,timeperiod) + except Exception as e: + return talib.MA(close.to_numpy().reshape(-1),timeperiod) + + @staticmethod + def MACD(close, fast, slow, signal): + try: + return talib.macd(close,fast,slow,signal) + except Exception as e: + return talib.MACD(close.to_numpy().reshape(-1),fast.to_numpy().reshape(-1),slow.to_numpy().reshape(-1),signal.to_numpy().reshape(-1)) + + @staticmethod + def RSI(close, timeperiod): + try: + return talib.rsi(close,timeperiod) + except Exception as e: + return talib.RSI(close.to_numpy().reshape(-1),timeperiod) + + @staticmethod + def CCI(high, low, close, timeperiod): + try: + return talib.cci(high, low, close,timeperiod) + except Exception as e: + return talib.CCI(high.to_numpy().reshape(-1), low.to_numpy().reshape(-1), close.to_numpy().reshape(-1),timeperiod) + + + @staticmethod + def CDLMORNINGSTAR(open, high, low, close): + try: + try: + return talib.cdl_pattern(open,high,low,close,'morningstar').tail(1).values[0][0] != 0 + except Exception as e: + return talib.CDLMORNINGSTAR(open.to_numpy().reshape(-1), high.to_numpy().reshape(-1), low.to_numpy().reshape(-1) ,close.to_numpy().reshape(-1)).tail(1).item() != 0 + except AttributeError: + return False + + @staticmethod + def CDLMORNINGDOJISTAR(open, high, low, close): + try: + try: + return talib.cdl_pattern(open,high,low,close,'morningdojistar').tail(1).values[0][0] != 0 + except Exception as e: + return talib.CDLMORNINGDOJISTAR(open.to_numpy().reshape(-1), high.to_numpy().reshape(-1), low.to_numpy().reshape(-1) ,close.to_numpy().reshape(-1)).tail(1).item() != 0 + except AttributeError: + return False + + @staticmethod + def CDLEVENINGSTAR(open, high, low, close): + try: + try: + return talib.cdl_pattern(open,high,low,close,'eveningstar').tail(1).values[0][0] != 0 + except Exception as e: + return talib.CDLEVENINGSTAR(open.to_numpy().reshape(-1), high.to_numpy().reshape(-1), low.to_numpy().reshape(-1) ,close.to_numpy().reshape(-1)).tail(1).item() != 0 + except AttributeError: + return False + + @staticmethod + def CDLEVENINGDOJISTAR(open, high, low, close): + try: + try: + return talib.cdl_pattern(open,high,low,close,'eveningdojistar').tail(1).values[0][0] != 0 + except Exception as e: + return talib.CDLEVENINGDOJISTAR(open.to_numpy().reshape(-1), high.to_numpy().reshape(-1), low.to_numpy().reshape(-1) ,close.to_numpy().reshape(-1)).tail(1).item() != 0 + except AttributeError: + return False + + @staticmethod + def CDLLADDERBOTTOM(open, high, low, close): + try: + try: + return talib.cdl_pattern(open,high,low,close,'ladderbottom').tail(1).values[0][0] != 0 + except Exception as e: + return talib.CDLLADDERBOTTOM(open.to_numpy().reshape(-1), high.to_numpy().reshape(-1), low.to_numpy().reshape(-1) ,close.to_numpy().reshape(-1)).tail(1).item() != 0 + except AttributeError: + return False + + @staticmethod + def CDL3LINESTRIKE(open, high, low, close): + try: + try: + return talib.cdl_pattern(open,high,low,close,'3linestrike').tail(1).values[0][0] != 0 + except Exception as e: + return talib.CDL3LINESTRIKE(open.to_numpy().reshape(-1), high.to_numpy().reshape(-1), low.to_numpy().reshape(-1) ,close.to_numpy().reshape(-1)).tail(1).item() != 0 + except AttributeError: + return False + + @staticmethod + def CDL3BLACKCROWS(open, high, low, close): + try: + try: + return talib.cdl_pattern(open,high,low,close,'3blackcrows').tail(1).values[0][0] != 0 + except Exception as e: + return talib.CDL3BLACKCROWS(open.to_numpy().reshape(-1), high.to_numpy().reshape(-1), low.to_numpy().reshape(-1) ,close.to_numpy().reshape(-1)).tail(1).item() != 0 + except AttributeError: + return False + + @staticmethod + def CDL3INSIDE(open, high, low, close): + try: + try: + return talib.cdl_pattern(open,high,low,close,'3inside').tail(1).values[0][0] != 0 + except Exception as e: + return talib.CDL3INSIDE(open.to_numpy().reshape(-1), high.to_numpy().reshape(-1), low.to_numpy().reshape(-1) ,close.to_numpy().reshape(-1)).tail(1).item() != 0 + except AttributeError: + return False + + @staticmethod + def CDL3OUTSIDE(open, high, low, close): + try: + try: + return talib.cdl_pattern(open,high,low,close,'3outside').tail(1).values[0][0] + except Exception as e: + return talib.CDL3OUTSIDE(open.to_numpy().reshape(-1), high.to_numpy().reshape(-1), low.to_numpy().reshape(-1) ,close.to_numpy().reshape(-1)).tail(1).item() != 0 + except AttributeError: + return False + + @staticmethod + def CDL3WHITESOLDIERS(open, high, low, close): + try: + try: + return talib.cdl_pattern(open,high,low,close,'3whitesoldiers').tail(1).values[0][0] != 0 + except Exception as e: + return talib.CDL3WHITESOLDIERS(open.to_numpy().reshape(-1), high.to_numpy().reshape(-1), low.to_numpy().reshape(-1) ,close.to_numpy().reshape(-1)).tail(1).item() != 0 + except AttributeError: + return False + + @staticmethod + def CDLHARAMI(open, high, low, close): + try: + try: + return talib.cdl_pattern(open,high,low,close,'harami').tail(1).values[0][0] != 0 + except Exception as e: + return talib.CDLHARAMI(open.to_numpy().reshape(-1), high.to_numpy().reshape(-1), low.to_numpy().reshape(-1) ,close.to_numpy().reshape(-1)).tail(1).item() != 0 + except AttributeError: + return False + + @staticmethod + def CDLHARAMICROSS(open, high, low, close): + try: + try: + return talib.cdl_pattern(open,high,low,close,'haramicross').tail(1).values[0][0] != 0 + except Exception as e: + return talib.CDLHARAMICROSS(open.to_numpy().reshape(-1), high.to_numpy().reshape(-1), low.to_numpy().reshape(-1) ,close.to_numpy().reshape(-1)).tail(1).item() != 0 + except AttributeError: + return False + + @staticmethod + def CDLMARUBOZU(open, high, low, close): + try: + try: + return talib.cdl_pattern(open,high,low,close,'marubozu').tail(1).values[0][0] != 0 + except Exception as e: + return talib.CDLMARUBOZU(open.to_numpy().reshape(-1), high.to_numpy().reshape(-1), low.to_numpy().reshape(-1) ,close.to_numpy().reshape(-1)).tail(1).item() != 0 + except AttributeError: + return False + + @staticmethod + def CDLHANGINGMAN(open, high, low, close): + try: + try: + return talib.cdl_pattern(open,high,low,close,'hangingman').tail(1).values[0][0] != 0 + except Exception as e: + return talib.CDLHANGINGMAN(open.to_numpy().reshape(-1), high.to_numpy().reshape(-1), low.to_numpy().reshape(-1) ,close.to_numpy().reshape(-1)).tail(1).item() != 0 + except AttributeError: + return False + + @staticmethod + def CDLHAMMER(open, high, low, close): + try: + try: + return talib.cdl_pattern(open,high,low,close,'hammer').tail(1).values[0][0] != 0 + except Exception as e: + return talib.CDLHAMMER(open.to_numpy().reshape(-1), high.to_numpy().reshape(-1), low.to_numpy().reshape(-1) ,close.to_numpy().reshape(-1)).tail(1).item() != 0 + except AttributeError: + return False + + @staticmethod + def CDLINVERTEDHAMMER(open, high, low, close): + try: + try: + return talib.cdl_pattern(open,high,low,close,'invertedhammer').tail(1).values[0][0] != 0 + except Exception as e: + return talib.CDLINVERTEDHAMMER(open.to_numpy().reshape(-1), high.to_numpy().reshape(-1), low.to_numpy().reshape(-1) ,close.to_numpy().reshape(-1)).tail(1).item() != 0 + except AttributeError: + return False + + @staticmethod + def CDLSHOOTINGSTAR(open, high, low, close): + try: + try: + return talib.cdl_pattern(open,high,low,close,'shootingstar').tail(1).values[0][0] != 0 + except Exception as e: + return talib.CDLSHOOTINGSTAR(open.to_numpy().reshape(-1), high.to_numpy().reshape(-1), low.to_numpy().reshape(-1) ,close.to_numpy().reshape(-1)).tail(1).item() != 0 + except AttributeError: + return False + + @staticmethod + def CDLDRAGONFLYDOJI(open, high, low, close): + try: + try: + return talib.cdl_pattern(open,high,low,close,'dragonflydoji').tail(1).values[0][0] != 0 + except Exception as e: + return talib.CDLDRAGONFLYDOJI(open.to_numpy().reshape(-1), high.to_numpy().reshape(-1), low.to_numpy().reshape(-1) ,close.to_numpy().reshape(-1)).tail(1).item() != 0 + except AttributeError: + return False + + @staticmethod + def CDLGRAVESTONEDOJI(open, high, low, close): + try: + try: + return talib.cdl_pattern(open,high,low,close,'gravestonedoji').tail(1).values[0][0] != 0 + except Exception as e: + return talib.CDLGRAVESTONEDOJI(open.to_numpy().reshape(-1), high.to_numpy().reshape(-1), low.to_numpy().reshape(-1) ,close.to_numpy().reshape(-1)).tail(1).item() != 0 + except AttributeError: + return False + + @staticmethod + def CDLDOJI(open, high, low, close): + try: + try: + return talib.cdl_pattern(open,high,low,close,'doji').tail(1).values[0][0] != 0 + except Exception as e: + return talib.CDLDOJI(open.to_numpy().reshape(-1), high.to_numpy().reshape(-1), low.to_numpy().reshape(-1) ,close.to_numpy().reshape(-1)).tail(1).item() != 0 + except AttributeError: + return False + + + @staticmethod + def CDLENGULFING(open, high, low, close): + try: + try: + return talib.cdl_pattern(open,high,low,close,'engulfing').tail(1).values[0][0] + except Exception as e: + return talib.CDLENGULFING(open.to_numpy().reshape(-1), high.to_numpy().reshape(-1), low.to_numpy().reshape(-1) ,close.to_numpy().reshape(-1)).tail(1).item() != 0 + except AttributeError: + return False + \ No newline at end of file diff --git a/src/classes/SuppressOutput.py b/src/classes/SuppressOutput.py new file mode 100644 index 0000000000000000000000000000000000000000..887ce5853a298c463117cb0296f5c5b05b078207 --- /dev/null +++ b/src/classes/SuppressOutput.py @@ -0,0 +1,29 @@ +''' + * Project : Screenipy + * Author : Pranjal Joshi + * Created : 07/05/2021 + * Description : Class for supressing stdout & stderr +''' + + +import os, sys + +class SuppressOutput: + def __init__(self,suppress_stdout=False,suppress_stderr=False): + self.suppress_stdout = suppress_stdout + self.suppress_stderr = suppress_stderr + self._stdout = None + self._stderr = None + def __enter__(self): + devnull = open(os.devnull, "w") + if self.suppress_stdout: + self._stdout = sys.stdout + sys.stdout = devnull + if self.suppress_stderr: + self._stderr = sys.stderr + sys.stderr = devnull + def __exit__(self, *args): + if self.suppress_stdout: + sys.stdout = self._stdout + if self.suppress_stderr: + sys.stderr = self._stderr \ No newline at end of file diff --git a/src/classes/Utility.py b/src/classes/Utility.py new file mode 100644 index 0000000000000000000000000000000000000000..968db1c869c7146fc6c5861e15b7c800d63b84cb --- /dev/null +++ b/src/classes/Utility.py @@ -0,0 +1,417 @@ +''' + * Project : Screenipy + * Author : Pranjal Joshi + * Created : 28/04/2021 + * Description : Class for managing misc and utility methods +''' + +import os +import sys +import platform +import datetime +import pytz +import pickle +import requests +import time +import joblib +import keras +import pandas as pd +from alive_progress import alive_bar +from tabulate import tabulate +from time import sleep +from classes.ColorText import colorText +from classes.Changelog import VERSION, changelog +import classes.ConfigManager as ConfigManager + +art = colorText.GREEN + ''' + .d8888b. d8b + d88P Y88b Y8P + Y88b. + "Y888b. .d8888b 888d888 .d88b. .d88b. 88888b. 888 88888b. 888 888 + "Y88b. d88P" 888P" d8P Y8b d8P Y8b 888 "88b 888 888 "88b 888 888 + "888 888 888 88888888 88888888 888 888 888 888 888 888 888 + Y88b d88P Y88b. 888 Y8b. Y8b. 888 888 888 888 d88P Y88b 888 + "Y8888P" "Y8888P 888 "Y8888 "Y8888 888 888 888 88888P" "Y88888 + 888 888 + 888 Y8b d88P + 888 "Y88P" + +''' + colorText.END + +lastScreened = 'last_screened_results.pkl' +lastScreenedUnformatted = 'last_screened_unformatted_results.pkl' + +# Class for managing misc and utility methods + + +class tools: + + def clearScreen(): + if platform.system() == 'Windows': + os.system('cls') + else: + os.system('clear') + print(art) + + # Print about developers and repository + def showDevInfo(): + print('\n'+changelog) + print(colorText.BOLD + colorText.WARN + + "\n[+] Developer: Pranjal Joshi." + colorText.END) + print(colorText.BOLD + colorText.WARN + + ("[+] Version: %s" % VERSION) + colorText.END) + print(colorText.BOLD + + "[+] Home Page: https://github.com/pranjal-joshi/Screeni-py" + colorText.END) + print(colorText.BOLD + colorText.FAIL + + "[+] Read/Post Issues here: https://github.com/pranjal-joshi/Screeni-py/issues" + colorText.END) + print(colorText.BOLD + colorText.GREEN + + "[+] Join Community Discussions: https://github.com/pranjal-joshi/Screeni-py/discussions" + colorText.END) + print(colorText.BOLD + colorText.BLUE + + "[+] Download latest software from https://github.com/pranjal-joshi/Screeni-py/releases/latest" + colorText.END) + input('') + + # Save last screened result to pickle file + def setLastScreenedResults(df, unformatted=False): + try: + if not unformatted: + df.sort_values(by=['Stock'], ascending=True, inplace=True) + df.to_pickle(lastScreened) + else: + df.sort_values(by=['Stock'], ascending=True, inplace=True) + df.to_pickle(lastScreenedUnformatted) + except IOError: + print(colorText.BOLD + colorText.FAIL + + '[+] Failed to save recently screened result table on disk! Skipping..' + colorText.END) + + # Load last screened result to pickle file + def getLastScreenedResults(): + try: + df = pd.read_pickle(lastScreened) + print(colorText.BOLD + colorText.GREEN + + '\n[+] Showing recently screened results..\n' + colorText.END) + print(tabulate(df, headers='keys', tablefmt='psql')) + print(colorText.BOLD + colorText.WARN + + "[+] Note: Trend calculation is based on number of recent days to screen as per your configuration." + colorText.END) + input(colorText.BOLD + colorText.GREEN + + '[+] Press any key to continue..' + colorText.END) + except FileNotFoundError: + print(colorText.BOLD + colorText.FAIL + + '[+] Failed to load recently screened result table from disk! Skipping..' + colorText.END) + + def isTradingTime(): + curr = datetime.datetime.now(pytz.timezone('Asia/Kolkata')) + openTime = curr.replace(hour=9, minute=15) + closeTime = curr.replace(hour=15, minute=30) + return ((openTime <= curr <= closeTime) and (0 <= curr.weekday() <= 4)) + + def isClosingHour(): + curr = datetime.datetime.now(pytz.timezone('Asia/Kolkata')) + openTime = curr.replace(hour=15, minute=00) + closeTime = curr.replace(hour=15, minute=30) + return ((openTime <= curr <= closeTime) and (0 <= curr.weekday() <= 4)) + + def saveStockData(stockDict, configManager, loadCount): + curr = datetime.datetime.now(pytz.timezone('Asia/Kolkata')) + openTime = curr.replace(hour=9, minute=15) + cache_date = datetime.date.today() # for monday to friday + weekday = datetime.date.today().weekday() + if curr < openTime: # for monday to friday before 9:15 + cache_date = datetime.datetime.today() - datetime.timedelta(1) + if weekday == 0 and curr < openTime: # for monday before 9:15 + cache_date = datetime.datetime.today() - datetime.timedelta(3) + if weekday == 5 or weekday == 6: # for saturday and sunday + cache_date = datetime.datetime.today() - datetime.timedelta(days=weekday - 4) + cache_date = cache_date.strftime("%d%m%y") + cache_file = "stock_data_" + str(cache_date) + ".pkl" + configManager.deleteStockData(excludeFile=cache_file) + + if not os.path.exists(cache_file) or len(stockDict) > (loadCount+1): + with open(cache_file, 'wb') as f: + try: + pickle.dump(stockDict.copy(), f) + print(colorText.BOLD + colorText.GREEN + + "=> Done." + colorText.END) + except pickle.PicklingError: + print(colorText.BOLD + colorText.FAIL + + "=> Error while Caching Stock Data." + colorText.END) + else: + print(colorText.BOLD + colorText.GREEN + + "=> Already Cached." + colorText.END) + + def loadStockData(stockDict, configManager, proxyServer=None): + curr = datetime.datetime.now(pytz.timezone('Asia/Kolkata')) + openTime = curr.replace(hour=9, minute=15) + last_cached_date = datetime.date.today() # for monday to friday after 3:30 + weekday = datetime.date.today().weekday() + if curr < openTime: # for monday to friday before 9:15 + last_cached_date = datetime.datetime.today() - datetime.timedelta(1) + if weekday == 5 or weekday == 6: # for saturday and sunday + last_cached_date = datetime.datetime.today() - datetime.timedelta(days=weekday - 4) + if weekday == 0 and curr < openTime: # for monday before 9:15 + last_cached_date = datetime.datetime.today() - datetime.timedelta(3) + last_cached_date = last_cached_date.strftime("%d%m%y") + cache_file = "stock_data_" + str(last_cached_date) + ".pkl" + if os.path.exists(cache_file): + with open(cache_file, 'rb') as f: + try: + stockData = pickle.load(f) + print(colorText.BOLD + colorText.GREEN + + "[+] Automatically Using Cached Stock Data due to After-Market hours!" + colorText.END) + for stock in stockData: + stockDict[stock] = stockData.get(stock) + except pickle.UnpicklingError: + print(colorText.BOLD + colorText.FAIL + + "[+] Error while Reading Stock Cache." + colorText.END) + except EOFError: + print(colorText.BOLD + colorText.FAIL + + "[+] Stock Cache Corrupted." + colorText.END) + elif ConfigManager.default_period == configManager.period and ConfigManager.default_duration == configManager.duration: + cache_url = "https://raw.github.com/pranjal-joshi/Screeni-py/actions-data-download/actions-data-download/" + cache_file + if proxyServer is not None: + resp = requests.get(cache_url, stream=True, proxies={'https':proxyServer}) + else: + resp = requests.get(cache_url, stream=True) + if resp.status_code == 200: + print(colorText.BOLD + colorText.FAIL + + "[+] After-Market Stock Data is not cached.." + colorText.END) + print(colorText.BOLD + colorText.GREEN + + "[+] Downloading cache from Screenipy server for faster processing, Please Wait.." + colorText.END) + try: + chunksize = 1024*1024*1 + filesize = int(int(resp.headers.get('content-length'))/chunksize) + bar, spinner = tools.getProgressbarStyle() + f = open(cache_file, 'wb') + dl = 0 + with alive_bar(filesize, bar=bar, spinner=spinner, manual=True) as progressbar: + for data in resp.iter_content(chunk_size=chunksize): + dl += 1 + f.write(data) + progressbar(dl/filesize) + if dl >= filesize: + progressbar(1.0) + f.close() + except Exception as e: + print("[!] Download Error - " + str(e)) + print("") + tools.loadStockData(stockDict, configManager, proxyServer) + else: + print(colorText.BOLD + colorText.FAIL + + "[+] Cache unavailable on Screenipy server, Continuing.." + colorText.END) + + # Save screened results to excel + def promptSaveResults(df): + if isDocker() or isGui(): # Skip export to excel inside docker + return + try: + response = str(input(colorText.BOLD + colorText.WARN + + '[>] Do you want to save the results in excel file? [Y/N]: ')).upper() + except ValueError: + response = 'Y' + if response != 'N': + filename = 'screenipy-result_' + \ + datetime.datetime.now().strftime("%d-%m-%y_%H.%M.%S")+".xlsx" + df.to_excel(filename) + print(colorText.BOLD + colorText.GREEN + + ("[+] Results saved to %s" % filename) + colorText.END) + + # Prompt for asking RSI + def promptRSIValues(): + try: + minRSI, maxRSI = int(input(colorText.BOLD + colorText.WARN + "\n[+] Enter Min RSI value: " + colorText.END)), int( + input(colorText.BOLD + colorText.WARN + "[+] Enter Max RSI value: " + colorText.END)) + if (minRSI >= 0 and minRSI <= 100) and (maxRSI >= 0 and maxRSI <= 100) and (minRSI <= maxRSI): + return (minRSI, maxRSI) + raise ValueError + except ValueError: + return (0, 0) + + # Prompt for Reversal screening + def promptReversalScreening(): + try: + resp = int(input(colorText.BOLD + colorText.WARN + """\n[+] Select Option: + 1 > Screen for Buy Signal (Bullish Reversal) + 2 > Screen for Sell Signal (Bearish Reversal) + 3 > Screen for Momentum Gainers (Rising Bullish Momentum) + 4 > Screen for Reversal at Moving Average (Bullish Reversal) + 5 > Screen for Volume Spread Analysis (Bullish VSA Reversal) + 6 > Screen for Narrow Range (NRx) Reversal + 7 > Screen for Reversal using Lorentzian Classifier (Machine Learning based indicator) + 8 > Screen for Reversal using RSI MA Crossing + 0 > Cancel +[+] Select option: """ + colorText.END)) + if resp >= 0 and resp <= 8: + if resp == 4: + try: + maLength = int(input(colorText.BOLD + colorText.WARN + + '\n[+] Enter MA Length (E.g. 50 or 200): ' + colorText.END)) + return resp, maLength + except ValueError: + print(colorText.BOLD + colorText.FAIL + + '\n[!] Invalid Input! MA Lenght should be single integer value!\n' + colorText.END) + raise ValueError + elif resp == 6: + try: + maLength = int(input(colorText.BOLD + colorText.WARN + + '\n[+] Enter NR timeframe [Integer Number] (E.g. 4, 7, etc.): ' + colorText.END)) + return resp, maLength + except ValueError: + print(colorText.BOLD + colorText.FAIL + '\n[!] Invalid Input! NR timeframe should be single integer value!\n' + colorText.END) + raise ValueError + elif resp == 7: + try: + return resp, 1 + except ValueError: + print(colorText.BOLD + colorText.FAIL + '\n[!] Invalid Input! Select valid Signal Type!\n' + colorText.END) + raise ValueError + elif resp == 8: + maLength = 9 + return resp, maLength + return resp, None + raise ValueError + except ValueError: + return None, None + + # Prompt for Reversal screening + def promptChartPatterns(): + try: + resp = int(input(colorText.BOLD + colorText.WARN + """\n[+] Select Option: + 1 > Screen for Bullish Inside Bar (Flag) Pattern + 2 > Screen for Bearish Inside Bar (Flag) Pattern + 3 > Screen for the Confluence (50 & 200 MA/EMA) + 4 > Screen for VCP (Experimental) + 5 > Screen for Buying at Trendline (Ideal for Swing/Mid/Long term) + 0 > Cancel +[+] Select option: """ + colorText.END)) + if resp == 1 or resp == 2: + candles = int(input(colorText.BOLD + colorText.WARN + + "\n[+] How many candles (TimeFrame) to look back Inside Bar formation? : " + colorText.END)) + return (resp, candles) + if resp == 3: + percent = float(input(colorText.BOLD + colorText.WARN + + "\n[+] Enter Percentage within which all MA/EMAs should be (Ideal: 1-2%)? : " + colorText.END)) + return (resp, percent/100.0) + if resp >= 0 and resp <= 5: + return resp, 0 + raise ValueError + except ValueError: + input(colorText.BOLD + colorText.FAIL + + "\n[+] Invalid Option Selected. Press Any Key to Continue..." + colorText.END) + return (None, None) + + # Prompt for Similar stock search + def promptSimilarStockSearch(): + try: + stockCode = str(input(colorText.BOLD + colorText.WARN + + "\n[+] Enter the Name of the stock to search similar stocks for: " + colorText.END)).upper() + candles = int(input(colorText.BOLD + colorText.WARN + + "\n[+] How many candles (TimeFrame) to look back for similarity? : " + colorText.END)) + return stockCode, candles + except ValueError: + input(colorText.BOLD + colorText.FAIL + + "\n[+] Invalid Option Selected. Press Any Key to Continue..." + colorText.END) + return None, None + + def getProgressbarStyle(): + bar = 'smooth' + spinner = 'waves' + if 'Windows' in platform.platform(): + bar = 'classic2' + spinner = 'dots_recur' + return bar, spinner + + def getNiftyModel(proxyServer=None): + files = ['nifty_model_v3.h5', 'nifty_model_v3.pkl'] + urls = [ + f"https://raw.github.com/pranjal-joshi/Screeni-py/new-features/src/ml/{files[0]}", + f"https://raw.github.com/pranjal-joshi/Screeni-py/new-features/src/ml/{files[1]}" + ] + if os.path.isfile(files[0]) and os.path.isfile(files[1]): + file_age = (time.time() - os.path.getmtime(files[0]))/604800 + if file_age > 1: + download = True + os.remove(files[0]) + os.remove(files[1]) + else: + download = False + else: + download = True + if download: + for file_url in urls: + if proxyServer is not None: + resp = requests.get(file_url, stream=True, proxies={'https':proxyServer}) + else: + resp = requests.get(file_url, stream=True) + if resp.status_code == 200: + print(colorText.BOLD + colorText.GREEN + + "[+] Downloading AI model (v3) for Nifty predictions, Please Wait.." + colorText.END) + try: + chunksize = 1024*1024*1 + filesize = int(int(resp.headers.get('content-length'))/chunksize) + filesize = 1 if not filesize else filesize + bar, spinner = tools.getProgressbarStyle() + f = open(file_url.split('/')[-1], 'wb') + dl = 0 + with alive_bar(filesize, bar=bar, spinner=spinner, manual=True) as progressbar: + for data in resp.iter_content(chunk_size=chunksize): + dl += 1 + f.write(data) + progressbar(dl/filesize) + if dl >= filesize: + progressbar(1.0) + f.close() + except Exception as e: + print("[!] Download Error - " + str(e)) + time.sleep(3) + model = keras.models.load_model(files[0]) + pkl = joblib.load(files[1]) + return model, pkl + + def getSigmoidConfidence(x): + out_min, out_max = 0, 100 + if x > 0.5: + in_min = 0.50001 + in_max = 1 + else: + in_min = 0 + in_max = 0.5 + return round(((x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min),3) + + def alertSound(beeps=3, delay=0.2): + for i in range(beeps): + print('\a') + sleep(delay) + + def isBacktesting(backtestDate): + try: + if datetime.date.today() != backtestDate: + return True + return False + except: + return False + + def calculateBacktestReport(data, backtestDict:dict): + try: + recent = data.head(1)['Close'].iloc[0] + for key, val in backtestDict.copy().items(): + if val is not None: + try: + backtestDict[key] = str(round((backtestDict[key]-recent)/recent*100,1)) + "%" + except TypeError: + del backtestDict[key] + # backtestDict[key] = None + continue + else: + del backtestDict[key] + except: + pass + return backtestDict + +def isDocker(): + if 'SCREENIPY_DOCKER' in os.environ: + return True + return False + +def isGui(): + if 'SCREENIPY_GUI' in os.environ: + return True + return False \ No newline at end of file diff --git a/src/icon-old.ico b/src/icon-old.ico new file mode 100644 index 0000000000000000000000000000000000000000..791e3f6512470913d616d0905b17b1cfd814f9f5 Binary files /dev/null and b/src/icon-old.ico differ diff --git a/src/icon.ico b/src/icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..ded3f8cb396f0e0da3630c11796197a5de3dd404 Binary files /dev/null and b/src/icon.ico differ diff --git a/src/ml/best_model_0.7438acc.h5 b/src/ml/best_model_0.7438acc.h5 new file mode 100644 index 0000000000000000000000000000000000000000..f509b67d6dc50a3a223372002905d7e6acbc06f4 --- /dev/null +++ b/src/ml/best_model_0.7438acc.h5 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:21aa1a70413f2177bf5738223a27e2f6fbb0ec29b9374b91dc24d5f8564e3dfe +size 21045528 diff --git a/src/ml/eval.py b/src/ml/eval.py new file mode 100644 index 0000000000000000000000000000000000000000..539ecce27db33769161d054cd4961ab4d9dc3a3a --- /dev/null +++ b/src/ml/eval.py @@ -0,0 +1,130 @@ +import yfinance as yf +import pandas as pd +import numpy as np +from sklearn.preprocessing import StandardScaler, MinMaxScaler +from sklearn.compose import ColumnTransformer +import joblib +import keras +import matplotlib.pyplot as plt + +import tensorflow as tf +physical_devices = tf.config.list_physical_devices('GPU') +try: + # Disable all GPUS + tf.config.set_visible_devices([], 'GPU') + visible_devices = tf.config.get_visible_devices() + for device in visible_devices: + assert device.device_type != 'GPU' +except: + # Invalid device or cannot modify virtual devices once initialized. + pass + +TEST_DAYS = 50 +PERIOD = '5y' + +INCLUDE_COMMODITIES = True + +def preprocessBeforeScaling(df): + df['High'] = df['High'].pct_change() * 100 + df['Low'] = df['Low'].pct_change() * 100 + df['Open'] = df['Open'].pct_change() * 100 + df['Close'] = df['Close'].pct_change() * 100 + + if INCLUDE_COMMODITIES: + df['gold_High'] = df['gold_High'].pct_change() * 100 + df['gold_Low'] = df['gold_Low'].pct_change() * 100 + df['gold_Open'] = df['gold_Open'].pct_change() * 100 + df['gold_Close'] = df['gold_Close'].pct_change() * 100 + + df['crude_High'] = df['crude_High'].pct_change() * 100 + df['crude_Low'] = df['crude_Low'].pct_change() * 100 + df['crude_Open'] = df['crude_Open'].pct_change() * 100 + df['crude_Close'] = df['crude_Close'].pct_change() * 100 + return df + +metrics = { + "TP": 0, "FP": 0, "TN": 0, "FN": 0 +} + +endpoint = keras.models.load_model('nifty_model_v3.h5') +try: + scaler +except NameError: + pkl = joblib.load('nifty_model_v3.pkl') + scaler = pkl['scaler'] +today = yf.download( + tickers="^NSEI", + period=f'{TEST_DAYS}d', + interval='1d', + progress=False, + timeout=10 + ) +if INCLUDE_COMMODITIES: + gold = yf.download( + tickers="GC=F", + period=f'{TEST_DAYS}d', + interval='1d', + progress=False, + timeout=10 + ).add_prefix(prefix='gold_') + crude = yf.download( + tickers="CL=F", + period=f'{TEST_DAYS}d', + interval='1d', + progress=False, + timeout=10 + ).add_prefix(prefix='crude_') + + today = pd.concat([today, gold, crude], axis=1) + today = today.drop(columns=['Adj Close', 'Volume', 'gold_Adj Close', 'gold_Volume', 'crude_Adj Close', 'crude_Volume']) +else: + today = today.drop(columns=['Adj Close', 'Volume']) + +### +today = preprocessBeforeScaling(today) +today = today.drop(columns=['gold_Open', 'gold_High', 'gold_Low', 'crude_Open', 'crude_High', 'crude_Low']) +### + +cnt_correct, cnt_wrong = 0, 0 +for i in range(-TEST_DAYS,0): + df = today.iloc[i] + twr = today.iloc[i+1]['Close'] + df = scaler.transform([df]) + pred = endpoint.predict([df], verbose=0) + + if twr > today.iloc[i]['Open']: + fact = "BULLISH" + else: + fact = "BEARISH" + + if pred > 0.5: + out = "BEARISH" + else: + out = "BULLISH" + + if out == fact: + cnt_correct += 1 + if out == "BULLISH": + metrics["TP"] += 1 + else: + metrics["TN"] += 1 + else: + cnt_wrong += 1 + if out == "BULLISH": + metrics["FN"] += 1 + else: + metrics["FP"] += 1 + + + print("{} Nifty Prediction -> Market may Close {} on {}! Actual -> {}, Prediction -> {}, Pred = {}".format( + today.iloc[i].name.strftime("%d-%m-%Y"), + out, + (today.iloc[i].name + pd.Timedelta(days=1)).strftime("%d-%m-%Y"), + fact, + "Correct" if fact == out else "Wrong", + str(np.round(pred[0][0], 2)) + ) + ) + +print("Correct: {}, Wrong: {}, Accuracy: {}".format(cnt_correct, cnt_wrong, cnt_correct/(cnt_correct+cnt_wrong))) +print(metrics) \ No newline at end of file diff --git a/src/ml/experiment.ipynb b/src/ml/experiment.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..4604af1f0b43c0a02f3783c4d7b1b1dfae7be242 --- /dev/null +++ b/src/ml/experiment.ipynb @@ -0,0 +1,673 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "import tensorflow as tf\n", + "physical_devices = tf.config.list_physical_devices('GPU')\n", + "try:\n", + " # Disable all GPUS\n", + " tf.config.set_visible_devices([], 'GPU')\n", + " visible_devices = tf.config.get_visible_devices()\n", + " for device in visible_devices:\n", + " assert device.device_type != 'GPU'\n", + "except:\n", + " # Invalid device or cannot modify virtual devices once initialized.\n", + " pass" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "import yfinance as yf\n", + "import pandas as pd\n", + "import numpy as np\n", + "from sklearn.preprocessing import StandardScaler, MinMaxScaler\n", + "from sklearn.compose import ColumnTransformer\n", + "import joblib\n", + "import keras\n", + "import matplotlib.pyplot as plt" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "TEST_DAYS = 10\n", + "PERIOD = '5y'" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [], + "source": [ + "INDICATOR_DATASET = False" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [], + "source": [ + "INCLUDE_COMMODITIES = True" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "if INDICATOR_DATASET:\n", + " d = joblib.load('nifty_data.pkl')\n", + "else:\n", + " d = yf.download(\n", + " tickers=\"^NSEI\",\n", + " period=PERIOD,\n", + " interval='1d',\n", + " progress=False,\n", + " timeout=10\n", + " )\n", + " if INCLUDE_COMMODITIES:\n", + " gold = yf.download(\n", + " tickers=\"GC=F\",\n", + " period=PERIOD,\n", + " interval='1d',\n", + " progress=False,\n", + " timeout=10\n", + " ).add_prefix(prefix='gold_')\n", + " crude = yf.download(\n", + " tickers=\"CL=F\",\n", + " period=PERIOD,\n", + " interval='1d',\n", + " progress=False,\n", + " timeout=10\n", + " ).add_prefix(prefix='crude_')\n", + " d = pd.concat([d, gold, crude], axis=1)\n", + " \n", + " d['target'] = d.Open/d.Close.shift(-1)\n", + " d.target = d.target.apply(np.floor)\n", + "\n", + " d['change'] = abs(d['Close'].pct_change(fill_method=None) * 100)\n", + "\n", + " d['High'] = d['High'].pct_change(fill_method=None) * 100\n", + " d['Low'] = d['Low'].pct_change(fill_method=None) * 100\n", + " d['Open'] = d['Open'].pct_change(fill_method=None) * 100\n", + " d['Close'] = d['Close'].pct_change(fill_method=None) * 100 \n", + "\n", + " if INCLUDE_COMMODITIES:\n", + " d['gold_High'] = d['gold_High'].pct_change(fill_method=None) * 100\n", + " d['gold_Low'] = d['gold_Low'].pct_change(fill_method=None) * 100\n", + " d['gold_Open'] = d['gold_Open'].pct_change(fill_method=None) * 100\n", + " d['gold_Close'] = d['gold_Close'].pct_change(fill_method=None) * 100\n", + "\n", + " d['crude_High'] = d['crude_High'].pct_change(fill_method=None) * 100\n", + " d['crude_Low'] = d['crude_Low'].pct_change(fill_method=None) * 100\n", + " d['crude_Open'] = d['crude_Open'].pct_change(fill_method=None) * 100\n", + " d['crude_Close'] = d['crude_Close'].pct_change(fill_method=None) * 100\n", + " # d.rename(columns = {'HighNew':'High','LowNew':'Low','OpenNew':'Open','CloseNew':'Close'}, inplace = True)\n", + "\n", + " # Remove outliers when Market closes +- 3.5%\n", + " d = d[d['change'] < 3]\n", + " d.dropna(inplace=True)\n", + " d.tail()" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [], + "source": [ + "def preprocessBeforeScaling(df):\n", + " df['High'] = df['High'].pct_change(fill_method=None) * 100\n", + " df['Low'] = df['Low'].pct_change(fill_method=None) * 100\n", + " df['Open'] = df['Open'].pct_change(fill_method=None) * 100\n", + " df['Close'] = df['Close'].pct_change(fill_method=None) * 100 \n", + "\n", + " if INCLUDE_COMMODITIES:\n", + " df['gold_High'] = df['gold_High'].pct_change(fill_method=None) * 100\n", + " df['gold_Low'] = df['gold_Low'].pct_change(fill_method=None) * 100\n", + " df['gold_Open'] = df['gold_Open'].pct_change(fill_method=None) * 100\n", + " df['gold_Close'] = df['gold_Close'].pct_change(fill_method=None) * 100\n", + "\n", + " df['crude_High'] = df['crude_High'].pct_change(fill_method=None) * 100\n", + " df['crude_Low'] = df['crude_Low'].pct_change(fill_method=None) * 100\n", + " df['crude_Open'] = df['crude_Open'].pct_change(fill_method=None) * 100\n", + " df['crude_Close'] = df['crude_Close'].pct_change(fill_method=None) * 100\n", + " \n", + " df = df.ffill().dropna()\n", + " return df" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "test_dataset = d.tail(TEST_DAYS)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "d = d[:-(TEST_DAYS+1)]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "if INDICATOR_DATASET:\n", + " x = d.drop(columns=['target'])\n", + " y = d.target\n", + "else:\n", + " if INCLUDE_COMMODITIES:\n", + " # x = d.drop(columns=['target', 'Adj Close', 'Volume', 'change', 'gold_Adj Close', 'gold_Volume', 'crude_Adj Close', 'crude_Volume'], errors='ignore')\n", + " 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", + " else:\n", + " x = d.drop(columns=['target', 'Adj Close', 'Volume', 'change'], errors='ignore')\n", + " y = d.target" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "x" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "y" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print('No. of Bullish samples: {}'.format(y[y == 0].size))\n", + "print('No. of Bearish samples: {}'.format(y[y == 1].size))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "if not INDICATOR_DATASET:\n", + " print(\"Using StandardScaler\")\n", + " scaler = StandardScaler()\n", + " x = scaler.fit_transform(x.to_numpy())\n", + " x\n", + "else:\n", + " print(\"Using ColumnTransformer\")\n", + " col_names = ['Open', 'High', 'Low', 'Close', 'ATR']\n", + " scaler = ColumnTransformer(\n", + " [('StandardScaler', StandardScaler(), col_names)],\n", + " remainder='passthrough'\n", + " )\n", + " x = scaler.fit_transform(x)\n", + "x" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "visible_devices" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import tensorflow as tf\n", + "from keras import Sequential\n", + "from keras import Model\n", + "from keras.layers import Dense\n", + "from keras.optimizers import legacy, SGD\n", + "import keras\n", + "\n", + "lr_list = []\n", + "def scheduler(epoch, lr):\n", + " if epoch < 2:\n", + " lr = lr\n", + " else:\n", + " lr = lr * tf.math.exp(-0.0025)\n", + " lr_list.append(lr)\n", + " return lr\n", + "\n", + "units = 64 #128 #1024\n", + "# sgd = SGD(learning_rate=0.0001, momentum=0.0, nesterov=True)\n", + "sgd = legacy.SGD(learning_rate=0.001, momentum=0.9, nesterov=True)\n", + "kernel_init = 'he_uniform'\n", + "activation = 'relu'\n", + "\n", + "callback_mc = keras.callbacks.ModelCheckpoint(\n", + " 'best_model.h5',\n", + " verbose=1,\n", + " monitor='val_accuracy',\n", + " save_best_only=True,\n", + " mode='auto'\n", + " )\n", + "callback_es = keras.callbacks.EarlyStopping(\n", + " monitor='val_accuracy',\n", + " mode='auto',\n", + " verbose=0,\n", + " patience=100\n", + ")\n", + "callback_lr = keras.callbacks.LearningRateScheduler(scheduler)\n", + "\n", + "model = Sequential([\n", + " Dense(units, kernel_initializer=kernel_init, activation=activation, input_dim=x.shape[1]),\n", + " # Dense(units, kernel_initializer=kernel_init, activation=activation),\n", + " Dense(units//2, kernel_initializer=kernel_init, activation=activation),\n", + " Dense(units//4, kernel_initializer=kernel_init, activation=activation),\n", + " Dense(units//8, kernel_initializer=kernel_init, activation=activation),\n", + " Dense(units//16, kernel_initializer=kernel_init, activation=activation),\n", + " Dense(units//32, kernel_initializer=kernel_init, activation=activation),\n", + " Dense(1, kernel_initializer=kernel_init, activation='sigmoid'),\n", + "])\n", + "model.compile(optimizer=sgd, loss='binary_crossentropy', metrics=['accuracy'])\n", + "model.summary()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "BATCH_SIZE = int(len(y)/6.6125) #128 #24 #4\n", + "BATCH_SIZE = 256\n", + "print(f'BATCH SIZE = {BATCH_SIZE}')\n", + "history = model.fit(x, y, callbacks=[callback_mc, callback_es, callback_lr], batch_size=BATCH_SIZE, epochs=750, validation_split=0.15, verbose=2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "acc = history.history['accuracy']\n", + "loss = history.history['loss']\n", + "\n", + "plt.figure(figsize=(21,6))\n", + "plt.rcParams['figure.figsize'] = [8,8]\n", + "plt.rcParams['font.size'] = 14\n", + "plt.rcParams['axes.grid'] = True\n", + "plt.rcParams['figure.facecolor'] = 'white'\n", + "\n", + "plt.subplot(1, 3, 1)\n", + "plt.plot(acc, label='Training Accuracy')\n", + "plt.legend(loc='lower right')\n", + "plt.ylabel('Accuracy')\n", + "plt.title(f'\\nTrain Accuracy: {round(acc[-1],8)}')\n", + "\n", + "plt.subplot(1, 3, 2)\n", + "plt.plot(loss, label='Training Loss')\n", + "plt.legend(loc='upper right')\n", + "plt.ylabel('Cross Entropy')\n", + "plt.title(f'\\nTrain Loss: {round(loss[-1],8)}')\n", + "plt.xlabel('epoch')\n", + "\n", + "plt.subplot(1, 3, 3)\n", + "plt.plot(lr_list, label='Learning Rate')\n", + "plt.legend(loc='upper right')\n", + "plt.ylabel('LR')\n", + "plt.title(f'\\nLearning Rate')\n", + "plt.xlabel('epoch')\n", + "\n", + "plt.tight_layout(pad=3.0)\n", + "plt.show()\n", + "\n", + "acc = history.history['val_accuracy']\n", + "loss = history.history['val_loss']\n", + "\n", + "plt.figure(figsize=(14,6))\n", + "plt.rcParams['figure.figsize'] = [8,8]\n", + "plt.rcParams['font.size'] = 14\n", + "plt.rcParams['axes.grid'] = True\n", + "plt.rcParams['figure.facecolor'] = 'white'\n", + "plt.subplot(1, 2, 1)\n", + "plt.plot(acc, label='Val Accuracy')\n", + "plt.legend(loc='lower right')\n", + "plt.ylabel('Accuracy')\n", + "plt.title(f'\\nTest Accuracy: {round(acc[-1],8)}')\n", + "\n", + "plt.subplot(1, 2, 2)\n", + "plt.plot(loss, label='Val Loss')\n", + "plt.legend(loc='upper right')\n", + "plt.ylabel('Cross Entropy')\n", + "plt.title(f'\\nTest Loss: {round(loss[-1],8)}')\n", + "plt.xlabel('epoch')\n", + "plt.tight_layout(pad=3.0)\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Try Realtime Inference" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [], + "source": [ + "metrics = {\n", + " \"TP\": 0, \"FP\": 0, \"TN\": 0, \"FN\": 0\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " Open High Low Close gold_Close crude_Close\n", + "Date \n", + "2023-11-15 0.697093 0.221577 0.441300 0.093698 -0.086659 -2.044465\n", + "2023-11-16 0.118561 0.924435 0.241831 0.456152 1.214226 -4.904777\n", + "2023-11-17 0.000258 -0.348423 0.206090 -0.168976 -0.115936 4.101506\n", + "2023-11-20 0.286664 -0.250181 0.015512 -0.191573 -0.196812 2.253260\n", + "2023-11-21 0.201458 0.367730 0.424752 0.453947 1.092183 0.219070\n", + "2023-11-22 0.066257 -0.017897 -0.254131 0.143803 -0.395140 -0.861512\n", + "2023-11-23 0.224673 0.250180 0.420732 -0.049716 -0.395140 -0.861512\n", + "2023-11-24 -0.095063 -0.212833 -0.090467 -0.036869 -0.395140 -0.861512\n", + "15-11-2023 Nifty Prediction -> Market may Close BEARISH on 16-11-2023! Actual -> BEARISH, Prediction -> Correct, Pred = 0.59\n", + "16-11-2023 Nifty Prediction -> Market may Close BULLISH on 17-11-2023! Actual -> BEARISH, Prediction -> Wrong, Pred = 0.2\n", + "17-11-2023 Nifty Prediction -> Market may Close BEARISH on 18-11-2023! Actual -> BEARISH, Prediction -> Correct, Pred = 0.59\n", + "20-11-2023 Nifty Prediction -> Market may Close BEARISH on 21-11-2023! Actual -> BULLISH, Prediction -> Wrong, Pred = 0.59\n", + "21-11-2023 Nifty Prediction -> Market may Close BULLISH on 22-11-2023! Actual -> BEARISH, Prediction -> Wrong, Pred = 0.28\n", + "22-11-2023 Nifty Prediction -> Market may Close BULLISH on 23-11-2023! Actual -> BEARISH, Prediction -> Wrong, Pred = 0.48\n", + "23-11-2023 Nifty Prediction -> Market may Close BEARISH on 24-11-2023! Actual -> BEARISH, Prediction -> Correct, Pred = 0.57\n", + "24-11-2023 Nifty Prediction -> Market may Close BEARISH on 25-11-2023! Actual -> BULLISH, Prediction -> Wrong, Pred = 0.56\n", + "Correct: 3, Wrong: 5, Accuracy: 0.375\n", + "{'TP': 0, 'FP': 2, 'TN': 3, 'FN': 3}\n" + ] + } + ], + "source": [ + "endpoint = keras.models.load_model('nifty_model_v3.h5')\n", + "# endpoint = keras.models.load_model('best_model.h5')\n", + "try:\n", + " scaler\n", + "except NameError:\n", + " # pkl = joblib.load('nifty_model.pkl')\n", + " pkl = joblib.load('nifty_model_v3.pkl')\n", + " scaler = pkl['scaler']\n", + "today = yf.download(\n", + " tickers=\"^NSEI\",\n", + " period=f'{TEST_DAYS}d',\n", + " interval='1d',\n", + " progress=False,\n", + " timeout=10\n", + " )\n", + "if INCLUDE_COMMODITIES:\n", + " gold = yf.download(\n", + " tickers=\"GC=F\",\n", + " period=f'{TEST_DAYS}d',\n", + " interval='1d',\n", + " progress=False,\n", + " timeout=10\n", + " ).add_prefix(prefix='gold_')\n", + " crude = yf.download(\n", + " tickers=\"CL=F\",\n", + " period=f'{TEST_DAYS}d',\n", + " interval='1d',\n", + " progress=False,\n", + " timeout=10\n", + " ).add_prefix(prefix='crude_')\n", + "\n", + " today = pd.concat([today, gold, crude], axis=1)\n", + " today = today.drop(columns=['Adj Close', 'Volume', 'gold_Adj Close', 'gold_Volume', 'crude_Adj Close', 'crude_Volume'])\n", + "else:\n", + " today = today.drop(columns=['Adj Close', 'Volume'])\n", + "\n", + "###\n", + "today = preprocessBeforeScaling(today)\n", + "today = today.drop(columns=['gold_Open', 'gold_High', 'gold_Low', 'crude_Open', 'crude_High', 'crude_Low'])\n", + "print(today)\n", + "###\n", + "\n", + "cnt_correct, cnt_wrong = 0, 0\n", + "for i in range(-TEST_DAYS,0):\n", + " try:\n", + " df = today.iloc[i]\n", + " twr = today.iloc[i+1]['Close']\n", + " except IndexError:\n", + " continue\n", + " df = scaler.transform([df])\n", + " pred = endpoint.predict([df], verbose=0)\n", + "\n", + " if twr > today.iloc[i]['Open']:\n", + " fact = \"BULLISH\"\n", + " else:\n", + " fact = \"BEARISH\"\n", + "\n", + " if pred > 0.5:\n", + " out = \"BEARISH\"\n", + " else:\n", + " out = \"BULLISH\"\n", + "\n", + " if out == fact:\n", + " cnt_correct += 1\n", + " if out == \"BULLISH\":\n", + " metrics[\"TP\"] += 1\n", + " else:\n", + " metrics[\"TN\"] += 1\n", + " else:\n", + " cnt_wrong += 1\n", + " if out == \"BULLISH\":\n", + " metrics[\"FN\"] += 1\n", + " else:\n", + " metrics[\"FP\"] += 1\n", + "\n", + " \n", + " print(\"{} Nifty Prediction -> Market may Close {} on {}! Actual -> {}, Prediction -> {}, Pred = {}\".format(\n", + " today.iloc[i].name.strftime(\"%d-%m-%Y\"),\n", + " out,\n", + " (today.iloc[i].name + pd.Timedelta(days=1)).strftime(\"%d-%m-%Y\"),\n", + " fact,\n", + " \"Correct\" if fact == out else \"Wrong\",\n", + " str(np.round(pred[0][0], 2))\n", + " )\n", + " )\n", + "\n", + "print(\"Correct: {}, Wrong: {}, Accuracy: {}\".format(cnt_correct, cnt_wrong, cnt_correct/(cnt_correct+cnt_wrong)))\n", + "print(metrics)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Save Model for Screeni-py integration" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pkl = {\n", + " # 'model': model,\n", + " 'scaler': scaler,\n", + " 'columns': ['Open', 'Close', 'High', 'Low', 'gold_Close', 'crude_Close']\n", + "}\n", + "\n", + "joblib.dump(pkl, 'nifty_model.pkl')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pkl = joblib.load('nifty_model_v3.pkl')\n", + "z = yf.download(\n", + " tickers=\"^NSEI\",\n", + " period='5d',\n", + " interval='1d',\n", + " progress=False,\n", + " timeout=10\n", + " )\n", + "if INCLUDE_COMMODITIES:\n", + " gold = yf.download(\n", + " tickers=\"GC=F\",\n", + " period='5d',\n", + " interval='1d',\n", + " progress=False,\n", + " timeout=10\n", + " ).add_prefix(prefix='gold_')\n", + " crude = yf.download(\n", + " tickers=\"CL=F\",\n", + " period='5d',\n", + " interval='1d',\n", + " progress=False,\n", + " timeout=10\n", + " ).add_prefix(prefix='crude_')\n", + " z = pd.concat([z, gold, crude], axis=1)\n", + "z = preprocessBeforeScaling(z)\n", + "z = z.iloc[-1]\n", + "z = z[pkl['columns']]\n", + "print(z)\n", + "z = pkl['scaler'].transform([z])\n", + "endpoint.predict(z)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pkl['model'].save('nifty_model.h5')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pkl" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "del pkl['model']" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pkl" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def getSigmoidConfidence(x):\n", + " out_min, out_max = 0, 100\n", + " if x > 0.5:\n", + " in_min = 0.50001\n", + " in_max = 1\n", + " else:\n", + " in_min = 0\n", + " in_max = 0.5\n", + " return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min\n", + "\n", + "map_range(0.9633487, 0.5, 1, 0, 100)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3.9.13 ('ds')", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.6" + }, + "orig_nbformat": 4, + "vscode": { + "interpreter": { + "hash": "272f5af4762c02c25377d17b8d5be1b9d83b050e7634f4572d665f6d13ef995d" + } + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/src/ml/nifty_model_v2.h5 b/src/ml/nifty_model_v2.h5 new file mode 100644 index 0000000000000000000000000000000000000000..6b88dd052d5d6cb18a2b43107bcdb4e6ef0177c1 --- /dev/null +++ b/src/ml/nifty_model_v2.h5 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4696b9dd9c04e42814e2f042ef2a89317ae4a3b35b7dd03c4eab05825a54540e +size 86368 diff --git a/src/ml/nifty_model_v2.pkl b/src/ml/nifty_model_v2.pkl new file mode 100644 index 0000000000000000000000000000000000000000..749f0dd29b6079e37388fb36ddf4b2eafa80ad56 --- /dev/null +++ b/src/ml/nifty_model_v2.pkl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e44762314b0481d4f5b5da01b6c620bd4a9d3c5e403c94e0d00ce7853674ed1c +size 687 diff --git a/src/ml/nifty_model_v3.h5 b/src/ml/nifty_model_v3.h5 new file mode 100644 index 0000000000000000000000000000000000000000..792218dee11691605d9a4edb0e18130c874374bb --- /dev/null +++ b/src/ml/nifty_model_v3.h5 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:78ce7d514872d56640f25f381db3a77cebf018b4564196e9fa6b9ac626cec6cf +size 86056 diff --git a/src/ml/nifty_model_v3.pkl b/src/ml/nifty_model_v3.pkl new file mode 100644 index 0000000000000000000000000000000000000000..76c5867a3fb20a62fa006643eab6e4f96d877f69 --- /dev/null +++ b/src/ml/nifty_model_v3.pkl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1f457771736ac5f744288b8bf29b4c54164067146ecbb58afbd66c1c0ea18bd9 +size 845 diff --git a/src/release.md b/src/release.md new file mode 100644 index 0000000000000000000000000000000000000000..ddb0e55e554cda889c0d57b74592a0d2e8b9cbf1 --- /dev/null +++ b/src/release.md @@ -0,0 +1,83 @@ +[![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/) +## What's New? + +Screeni-py is now on **YouTube** for additional help! - Thank You for your support :tada: + +đŸŗ **Docker containers are released for quick setup and easy usage!** + +âš ī¸ **Executable files (.exe, .bin and .run) are now DEPRECATED! Please Switch to Docker** + +1. Fixed Blank Results issue by upgrading Yahoo Finance API client. +2. Added **Filters** to Result Table Headers (Apply Filters like Excel as per your strategy!) +3. Fixed Breakout Screening for **F&O Stocks** (Changed Data Source to Zerodha Kite from NSE website) +4. **RSI** based **Reversal** using *9 SMA* of RSI - Try `Option > 6 > 8` +5. **Position Size Calculator** tab added for Better and Quick Risk Management! +6. **Lorentzian Classification** (invented by Justin Dehorty) added for enhanced accuracy for your trades - - Try `Option > 6 > 7` đŸ¤¯ +7. **Artificial Intelligence v3 for Nifty 50 Prediction** - Predict Next day Gap-up/down using Nifty, Gold and Crude prices! - Try `Select Index for Screening > N` +8. **Search Similar Stocks** Added using Vector Similarity search - Try `Search Similar Stocks`. +9. New Screener **Buy at Trendline** added for Swing/Mid/Long term traders - Try `Option > 7 > 5`. + +## Installation Guide + +[![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) +[![Screeni-py - Detailed Installation Guide](https://markdown-videos-api.jorgenkh.no/url?url=https%3A%2F%2Fyoutu.be%2F2HMN0ac4H20)](https://youtu.be/2HMN0ac4H20) + +## Downloads +### Deprecated - Use Docker Method mentioned in next section + +| Operating System | Executable File | Remarks | +| :-: | --- | --- | +| ![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 | +| ![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 | +| ![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 | + +## [Docker Releases](https://hub.docker.com/r/joshipranjal/screeni-py/tags) + +| | Tag | Pull Command | Run Mode | Run Command | +|:-: | :-: | --- | --- | --- | +| ![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"` | +| ![Docker](https://img.shields.io/badge/docker-%230db7ed.svg?style=for-the-badge&logo=docker&logoColor=white) | `latest` | `docker pull joshipranjal/screeni-py:latest` | GUI WebApp | `docker run -p 8501:8501 -p 8000:8000 joshipranjal/screeni-py:latest` | +| ![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"` | +| ![Docker](https://img.shields.io/badge/docker-%230db7ed.svg?style=for-the-badge&logo=docker&logoColor=white) | `dev` | `docker pull joshipranjal/screeni-py:dev` | GUI WebApp | `docker run -p 8501:8501 -p 8000:8000 joshipranjal/screeni-py:dev` | + +### Docker Issues? Troubleshooting Guide: + +Read this [troubleshooting guide](https://github.com/pranjal-joshi/Screeni-py/discussions/217) for Windows to fix most common Docker issues easily! + +**Why we shifted to Docker from the Good old EXEs?** + +| Executable/Binary File | Docker | +| :-- | :-- | +| [![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) | +| 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/) âš ī¸| +| May take a long time to open the app | Loads quickly | +| Slower screening | Performance boosted as per your CPU capabilities | +| 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) | +| Works only with Windows 10/11 âš ī¸ | Works with older versions of Windows as well | +| Different file for each OS | Same container is compatible with everyone | +| Antivirus may block this as untrusted file âš ī¸ | No issues with Antivirus | +| Need to download new file for every update | Updates quickly with minimal downloading | +| No need of commands/technical knowledge | Very basic command execution skills may be required | +| Incompatible with Vector Database âš ī¸ | Compatible with all Python libraries | + + +## How to use? + +[**Click Here**](https://github.com/pranjal-joshi/Screeni-py) to read the documentation. + +## Join our Community Discussion + +[**Click Here**](https://github.com/pranjal-joshi/Screeni-py/discussions) to join the community discussion and see what other users are doing! + +## Facing an Issue? Found a Bug? + +[**Click Here**](https://github.com/pranjal-joshi/Screeni-py/issues/new/choose) to open an Issue so we can fix it for you! + +## Want to Contribute? + +[**Click Here**](https://github.com/pranjal-joshi/Screeni-py/blob/main/CONTRIBUTING.md) before you start working with us on new features! + +## Disclaimer: +* DO NOT use the result provided by the software solely to make your trading decisions. +* Always backtest and analyze the stocks manually before you trade. +* The Author(s) and the software will not be held liable for any losses. \ No newline at end of file diff --git a/src/screenipy.ini b/src/screenipy.ini new file mode 100644 index 0000000000000000000000000000000000000000..0e8141f43d2c7c0470c440ff3d6f7664214264c6 --- /dev/null +++ b/src/screenipy.ini @@ -0,0 +1,13 @@ +[config] +period = 300d +daystolookback = 30 +duration = 1d +minprice = 30.0 +maxprice = 10000.0 +volumeratio = 2.0 +consolidationpercentage = 10 +shuffle = y +cachestockdata = y +onlystagetwostocks = y +useema = n + diff --git a/src/screenipy.py b/src/screenipy.py new file mode 100644 index 0000000000000000000000000000000000000000..a7f02c90cf3464bacd739caaeb282a822c5a1aa2 --- /dev/null +++ b/src/screenipy.py @@ -0,0 +1,545 @@ +#!/usr/bin/python3 + +# 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 +# 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 + +# Keep module imports prior to classes +import os +os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' +import platform +import sys +import classes.Fetcher as Fetcher +import classes.ConfigManager as ConfigManager +import classes.Screener as Screener +import classes.Utility as Utility +from classes.ColorText import colorText +from classes.OtaUpdater import OTAUpdater +from classes.CandlePatterns import CandlePatterns +from classes.ParallelProcessing import StockConsumer +from classes.Changelog import VERSION +from classes.Utility import isDocker, isGui +from alive_progress import alive_bar +import argparse +import urllib +import numpy as np +import pandas as pd +from datetime import datetime, date +from time import sleep +from tabulate import tabulate +import multiprocessing +multiprocessing.freeze_support() +try: + import chromadb + CHROMA_AVAILABLE = True +except: + CHROMA_AVAILABLE = False + +# Argument Parsing for test purpose +argParser = argparse.ArgumentParser() +argParser.add_argument('-t', '--testbuild', action='store_true', help='Run in test-build mode', required=False) +argParser.add_argument('-d', '--download', action='store_true', help='Only Download Stock data in .pkl file', required=False) +argParser.add_argument('-v', action='store_true') # Dummy Arg for pytest -v +args = argParser.parse_args() + +# Try Fixing bug with this symbol +TEST_STKCODE = "SBIN" + +# Constants +np.seterr(divide='ignore', invalid='ignore') + +# Global Variabls +screenCounter = None +screenResultsCounter = None +stockDict = None +keyboardInterruptEvent = None +loadedStockData = False +loadCount = 0 +maLength = None +newlyListedOnly = False +vectorSearch = False + +CHROMADB_PATH = "chromadb_store/" + +configManager = ConfigManager.tools() +fetcher = Fetcher.tools(configManager) +screener = Screener.tools(configManager) +candlePatterns = CandlePatterns() + +# Get system wide proxy for networking +try: + proxyServer = urllib.request.getproxies()['http'] +except KeyError: + proxyServer = "" + +# Clear chromadb store initially +if CHROMA_AVAILABLE: + chroma_client = chromadb.PersistentClient(path=CHROMADB_PATH) + try: + chroma_client.delete_collection("nse_stocks") + except: + pass + + +# Manage Execution flow + + +def initExecution(): + global newlyListedOnly + print(colorText.BOLD + colorText.WARN + + '[+] Select an Index for Screening: ' + colorText.END) + print(colorText.BOLD + ''' + W > Screen stocks from my own Watchlist + N > Nifty Prediction using Artifical Intelligence (Use for Gap-Up/Gap-Down/BTST/STBT) + E > Live Index Scan : 5 EMA for Intraday + S > Search for Similar Stocks (forming Similar Chart Pattern) + + 0 > Screen stocks by the stock names (NSE Stock Code) + 1 > Nifty 50 2 > Nifty Next 50 3 > Nifty 100 + 4 > Nifty 200 5 > Nifty 500 6 > Nifty Smallcap 50 + 7 > Nifty Smallcap 100 8 > Nifty Smallcap 250 9 > Nifty Midcap 50 + 10 > Nifty Midcap 100 11 > Nifty Midcap 150 13 > Newly Listed (IPOs in last 2 Year) + 14 > F&O Stocks Only 15 > US S&P 500 16 > Sectoral Indices (NSE) + Enter > All Stocks (default) ''' + colorText.END + ) + try: + tickerOption = input( + colorText.BOLD + colorText.FAIL + '[+] Select option: ') + print(colorText.END, end='') + if tickerOption == '': + tickerOption = 12 + # elif tickerOption == 'W' or tickerOption == 'w' or tickerOption == 'N' or tickerOption == 'n' or tickerOption == 'E' or tickerOption == 'e': + elif not tickerOption.isnumeric(): + tickerOption = tickerOption.upper() + else: + tickerOption = int(tickerOption) + if(tickerOption < 0 or tickerOption > 16): + raise ValueError + elif tickerOption == 13: + newlyListedOnly = True + tickerOption = 12 + except KeyboardInterrupt: + raise KeyboardInterrupt + except Exception as e: + print(colorText.BOLD + colorText.FAIL + + '\n[+] Please enter a valid numeric option & Try Again!' + colorText.END) + sleep(2) + Utility.tools.clearScreen() + return initExecution() + + if tickerOption == 'N' or tickerOption == 'E' or tickerOption == 'S': + return tickerOption, 0 + + if tickerOption and tickerOption != 'W': + print(colorText.BOLD + colorText.WARN + + '\n[+] Select a Critera for Stock Screening: ' + colorText.END) + print(colorText.BOLD + ''' + 0 > Full Screening (Shows Technical Parameters without Any Criteria) + 1 > Screen stocks for Breakout or Consolidation + 2 > Screen for the stocks with recent Breakout & Volume + 3 > Screen for the Consolidating stocks + 4 > Screen for the stocks with Lowest Volume in last 'N'-days (Early Breakout Detection) + 5 > Screen for the stocks with RSI + 6 > Screen for the stocks showing Reversal Signals + 7 > Screen for the stocks making Chart Patterns + 8 > Edit user configuration + 9 > Show user configuration + 10 > Show Last Screened Results + 11 > Help / About Developer + 12 > Exit''' + colorText.END + ) + try: + if tickerOption and tickerOption != 'W': + executeOption = input( + colorText.BOLD + colorText.FAIL + '[+] Select option: ') + print(colorText.END, end='') + if executeOption == '': + executeOption = 0 + executeOption = int(executeOption) + if(executeOption < 0 or executeOption > 14): + raise ValueError + else: + executeOption = 0 + except KeyboardInterrupt: + raise KeyboardInterrupt + except Exception as e: + print(colorText.BOLD + colorText.FAIL + + '\n[+] Please enter a valid numeric option & Try Again!' + colorText.END) + sleep(2) + Utility.tools.clearScreen() + return initExecution() + return tickerOption, executeOption + +# Main function +def main(testing=False, testBuild=False, downloadOnly=False, execute_inputs:list = [], isDevVersion=None, backtestDate=date.today()): + global screenCounter, screenResultsCounter, stockDict, loadedStockData, keyboardInterruptEvent, loadCount, maLength, newlyListedOnly, vectorSearch + screenCounter = multiprocessing.Value('i', 1) + screenResultsCounter = multiprocessing.Value('i', 0) + keyboardInterruptEvent = multiprocessing.Manager().Event() + + if stockDict is None or Utility.tools.isBacktesting(backtestDate=backtestDate): + stockDict = multiprocessing.Manager().dict() + loadCount = 0 + + minRSI = 0 + maxRSI = 100 + insideBarToLookback = 7 + respChartPattern = 1 + daysForLowestVolume = 30 + reversalOption = None + + screenResults = pd.DataFrame(columns=[ + 'Stock', 'Consolidating', 'Breaking-Out', 'LTP', 'Volume', 'MA-Signal', 'RSI', 'Trend', 'Pattern']) + saveResults = pd.DataFrame(columns=[ + 'Stock', 'Consolidating', 'Breaking-Out', 'LTP', 'Volume', 'MA-Signal', 'RSI', 'Trend', 'Pattern']) + + + if testBuild: + tickerOption, executeOption = 1, 0 + elif downloadOnly: + tickerOption, executeOption = 12, 2 + else: + try: + if execute_inputs != []: + if not configManager.checkConfigFile(): + configManager.setConfig(ConfigManager.parser, default=True, showFileCreatedText=False) + try: + tickerOption, executeOption = int(execute_inputs[0]), int(execute_inputs[1]) + if tickerOption == 0: + stockCode = execute_inputs[2].replace(" ", "") + listStockCodes = stockCode.split(',') + except: + tickerOption, executeOption = str(execute_inputs[0]), int(execute_inputs[1]) + if tickerOption == 13: + newlyListedOnly = True + tickerOption = 12 + else: + tickerOption, executeOption = initExecution() + except KeyboardInterrupt: + if execute_inputs == [] and not isGui(): + input(colorText.BOLD + colorText.FAIL + + "[+] Press any key to Exit!" + colorText.END) + sys.exit(0) + + if executeOption == 4: + try: + if execute_inputs != []: + daysForLowestVolume = int(execute_inputs[2]) + else: + daysForLowestVolume = int(input(colorText.BOLD + colorText.WARN + + '\n[+] The Volume should be lowest since last how many candles? ')) + except ValueError: + print(colorText.END) + print(colorText.BOLD + colorText.FAIL + + '[+] Error: Non-numeric value entered! Screening aborted.' + colorText.END) + if not isGui(): + input('') + main() + print(colorText.END) + if executeOption == 5: + if execute_inputs != []: + minRSI, maxRSI = int(execute_inputs[2]), int(execute_inputs[3]) + else: + minRSI, maxRSI = Utility.tools.promptRSIValues() + if (not minRSI and not maxRSI): + print(colorText.BOLD + colorText.FAIL + + '\n[+] Error: Invalid values for RSI! Values should be in range of 0 to 100. Screening aborted.' + colorText.END) + if not isGui(): + input('') + main() + if executeOption == 6: + if execute_inputs != []: + reversalOption = int(execute_inputs[2]) + try: + maLength = int(execute_inputs[3]) + except ValueError: + pass + else: + reversalOption, maLength = Utility.tools.promptReversalScreening() + if reversalOption is None or reversalOption == 0: + if not isGui(): + main() + if executeOption == 7: + if execute_inputs != []: + respChartPattern = int(execute_inputs[2]) + try: + insideBarToLookback = float(execute_inputs[3]) + except ValueError: + pass + else: + respChartPattern, insideBarToLookback = Utility.tools.promptChartPatterns() + if insideBarToLookback is None: + if not isGui(): + main() + if executeOption == 8: + configManager.setConfig(ConfigManager.parser) + if not isGui(): + main() + if executeOption == 9: + configManager.showConfigFile() + if not isGui(): + main() + if executeOption == 10: + Utility.tools.getLastScreenedResults() + if not isGui(): + main() + if executeOption == 11: + Utility.tools.showDevInfo() + if not isGui(): + main() + if executeOption == 12: + if not isGui(): + input(colorText.BOLD + colorText.FAIL + + "[+] Press any key to Exit!" + colorText.END) + sys.exit(0) + + if tickerOption == 'W' or tickerOption == 'N' or tickerOption == 'E' or tickerOption == 'S' or (tickerOption >= 0 and tickerOption < 17): + configManager.getConfig(ConfigManager.parser) + try: + if tickerOption == 'W': + listStockCodes = fetcher.fetchWatchlist() + if listStockCodes is None: + input(colorText.BOLD + colorText.FAIL + + f'[+] Create the watchlist.xlsx file in {os.getcwd()} and Restart the Program!' + colorText.END) + sys.exit(0) + elif tickerOption == 'N': + os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' + import tensorflow as tf + physical_devices = tf.config.list_physical_devices('GPU') + try: + tf.config.set_visible_devices([], 'GPU') + visible_devices = tf.config.get_visible_devices() + for device in visible_devices: + assert device.device_type != 'GPU' + except: + pass + prediction = screener.getNiftyPrediction( + data=fetcher.fetchLatestNiftyDaily(proxyServer=proxyServer), + proxyServer=proxyServer + ) + input('\nPress any key to Continue...\n') + return + elif tickerOption == 'E': + result_df = pd.DataFrame(columns=['Time','Stock/Index','Action','SL','Target','R:R']) + last_signal = {} + first_scan = True + result_df = screener.monitorFiveEma( # Dummy scan to avoid blank table on 1st scan + proxyServer=proxyServer, + fetcher=fetcher, + result_df=result_df, + last_signal=last_signal + ) + try: + while True: + Utility.tools.clearScreen() + last_result_len = len(result_df) + result_df = screener.monitorFiveEma( + proxyServer=proxyServer, + fetcher=fetcher, + result_df=result_df, + last_signal=last_signal + ) + 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) + print(tabulate(result_df, headers='keys', tablefmt='psql')) + print('\nPress Ctrl+C to exit.') + if len(result_df) != last_result_len and not first_scan: + Utility.tools.alertSound(beeps=5) + sleep(60) + first_scan = False + except KeyboardInterrupt: + if not isGui(): + input('\nPress any key to Continue...\n') + return + elif tickerOption == 'S': + if not CHROMA_AVAILABLE: + print(colorText.BOLD + colorText.FAIL + + "\n\n[+] ChromaDB not available in your environment! You can't use this feature!\n" + colorText.END) + else: + if execute_inputs != []: + stockCode, candles = execute_inputs[2], execute_inputs[3] + else: + stockCode, candles = Utility.tools.promptSimilarStockSearch() + vectorSearch = [stockCode, candles, True] + tickerOption, executeOption = 12, 1 + listStockCodes = fetcher.fetchStockCodes(tickerOption, proxyServer=proxyServer) + else: + if tickerOption == 14: # Override config for F&O Stocks + configManager.stageTwo = False + configManager.minLTP = 0.1 + configManager.maxLTP = 999999999 + if (execute_inputs != [] and tickerOption != 0) or execute_inputs == []: + listStockCodes = fetcher.fetchStockCodes(tickerOption, proxyServer=proxyServer) + except urllib.error.URLError: + print(colorText.BOLD + colorText.FAIL + + "\n\n[+] Oops! It looks like you don't have an Internet connectivity at the moment! Press any key to exit!" + colorText.END) + if not isGui(): + input('') + sys.exit(0) + + if not Utility.tools.isTradingTime() and configManager.cacheEnabled and not loadedStockData and not testing and not Utility.tools.isBacktesting(backtestDate=backtestDate): + Utility.tools.loadStockData(stockDict, configManager, proxyServer) + loadedStockData = True + loadCount = len(stockDict) + + print(colorText.BOLD + colorText.WARN + + "[+] Starting Stock Screening.. Press Ctrl+C to stop!\n") + + items = [(tickerOption, executeOption, reversalOption, maLength, daysForLowestVolume, minRSI, maxRSI, respChartPattern, insideBarToLookback, len(listStockCodes), + configManager, fetcher, screener, candlePatterns, stock, newlyListedOnly, downloadOnly, vectorSearch, isDevVersion, backtestDate) + for stock in listStockCodes] + + tasks_queue = multiprocessing.JoinableQueue() + results_queue = multiprocessing.Queue() + + totalConsumers = multiprocessing.cpu_count() + if totalConsumers == 1: + totalConsumers = 2 # This is required for single core machine + if configManager.cacheEnabled is True and multiprocessing.cpu_count() > 2: + totalConsumers -= 1 + consumers = [StockConsumer(tasks_queue, results_queue, screenCounter, screenResultsCounter, stockDict, proxyServer, keyboardInterruptEvent) + for _ in range(totalConsumers)] + + for worker in consumers: + worker.daemon = True + worker.start() + + if testing or testBuild: + for item in items: + tasks_queue.put(item) + result = results_queue.get() + if result is not None: + screenResults = pd.concat([screenResults, pd.DataFrame([result[0]])], ignore_index=True) + saveResults = pd.concat([saveResults, pd.DataFrame([result[1]])], ignore_index=True) + if testing or (testBuild and len(screenResults) > 2): + break + else: + for item in items: + tasks_queue.put(item) + # Append exit signal for each process indicated by None + for _ in range(multiprocessing.cpu_count()): + tasks_queue.put(None) + try: + numStocks, totalStocks = len(listStockCodes), len(listStockCodes) + os.environ['SCREENIPY_TOTAL_STOCKS'] = str(totalStocks) + print(colorText.END+colorText.BOLD) + bar, spinner = Utility.tools.getProgressbarStyle() + with alive_bar(numStocks, bar=bar, spinner=spinner) as progressbar: + while numStocks: + result = results_queue.get() + if result is not None: + screenResults = pd.concat([screenResults, pd.DataFrame([result[0]])], ignore_index=True) + saveResults = pd.concat([saveResults, pd.DataFrame([result[1]])], ignore_index=True) + numStocks -= 1 + os.environ['SCREENIPY_SCREEN_COUNTER'] = str(int((totalStocks-numStocks)/totalStocks*100)) + progressbar.text(colorText.BOLD + colorText.GREEN + + f'Found {screenResultsCounter.value} Stocks' + colorText.END) + progressbar() + except KeyboardInterrupt: + try: + keyboardInterruptEvent.set() + except KeyboardInterrupt: + pass + print(colorText.BOLD + colorText.FAIL + + "\n[+] Terminating Script, Please wait..." + colorText.END) + for worker in consumers: + worker.terminate() + + print(colorText.END) + # Exit all processes. Without this, it threw error in next screening session + for worker in consumers: + try: + worker.terminate() + except OSError as e: + if e.winerror == 5: + pass + + # Flush the queue so depending processes will end + from queue import Empty + while True: + try: + _ = tasks_queue.get(False) + except Exception as e: + break + + if CHROMA_AVAILABLE and type(vectorSearch) == list and vectorSearch[2]: + chroma_client = chromadb.PersistentClient(path=CHROMADB_PATH) + collection = chroma_client.get_collection(name="nse_stocks") + query_embeddings= collection.get(ids = [stockCode], include=["embeddings"])["embeddings"] + results = collection.query( + query_embeddings=query_embeddings, + n_results=4 + )['ids'][0] + try: + results.remove(stockCode) + except ValueError: + pass + matchedScreenResults, matchedSaveResults = pd.DataFrame(columns=screenResults.columns), pd.DataFrame(columns=saveResults.columns) + for stk in results: + matchedScreenResults = pd.concat([matchedScreenResults, screenResults[screenResults['Stock'].str.contains(stk)]], ignore_index=True) + matchedSaveResults = pd.concat([matchedSaveResults, saveResults[saveResults['Stock'].str.contains(stk)]], ignore_index=True) + screenResults, saveResults = matchedScreenResults, matchedSaveResults + + screenResults.sort_values(by=['Stock'], ascending=True, inplace=True) + saveResults.sort_values(by=['Stock'], ascending=True, inplace=True) + screenResults.set_index('Stock', inplace=True) + saveResults.set_index('Stock', inplace=True) + screenResults.rename( + columns={ + 'Trend': f'Trend ({configManager.daysToLookback}Days)', + 'Breaking-Out': f'Breakout ({configManager.daysToLookback}Days)', + 'LTP': 'LTP (%% Chng)' + }, + inplace=True + ) + saveResults.rename( + columns={ + 'Trend': f'Trend ({configManager.daysToLookback}Days)', + 'Breaking-Out': f'Breakout ({configManager.daysToLookback}Days)', + }, + inplace=True + ) + print(tabulate(screenResults, headers='keys', tablefmt='psql')) + + print(colorText.BOLD + colorText.GREEN + + f"[+] Found {len(screenResults)} Stocks." + colorText.END) + if configManager.cacheEnabled and not Utility.tools.isTradingTime() and not testing and not Utility.tools.isBacktesting(backtestDate=backtestDate): + print(colorText.BOLD + colorText.GREEN + + "[+] Caching Stock Data for future use, Please Wait... " + colorText.END, end='') + Utility.tools.saveStockData( + stockDict, configManager, loadCount) + + Utility.tools.setLastScreenedResults(screenResults) + Utility.tools.setLastScreenedResults(saveResults, unformatted=True) + if not testBuild and not downloadOnly: + Utility.tools.promptSaveResults(saveResults) + print(colorText.BOLD + colorText.WARN + + "[+] Note: Trend calculation is based on number of days recent to screen as per your configuration." + colorText.END) + print(colorText.BOLD + colorText.GREEN + + "[+] Screening Completed! Press Enter to Continue.." + colorText.END) + if not isGui(): + input('') + newlyListedOnly = False + vectorSearch = False + + +if __name__ == "__main__": + Utility.tools.clearScreen() + isDevVersion = OTAUpdater.checkForUpdate(proxyServer, VERSION) + if not configManager.checkConfigFile(): + configManager.setConfig(ConfigManager.parser, default=True, showFileCreatedText=False) + if args.testbuild: + print(colorText.BOLD + colorText.FAIL +"[+] Started in TestBuild mode!" + colorText.END) + main(testBuild=True) + elif args.download: + print(colorText.BOLD + colorText.FAIL +"[+] Download ONLY mode! Stocks will not be screened!" + colorText.END) + main(downloadOnly=True) + else: + try: + while True: + main() + except Exception as e: + raise e + if isDevVersion == OTAUpdater.developmentVersion: + raise(e) + input(colorText.BOLD + colorText.FAIL + + "[+] Press any key to Exit!" + colorText.END) + sys.exit(1) diff --git a/src/screenipy.spec b/src/screenipy.spec new file mode 100644 index 0000000000000000000000000000000000000000..940744bb153f9234192556a66b11c4e832afc834 --- /dev/null +++ b/src/screenipy.spec @@ -0,0 +1,34 @@ +# -*- mode: python ; coding: utf-8 -*- + + +block_cipher = None + + +a = Analysis(['screenipy.py'], + pathex=['/mnt/40FEE0813E8AAC6F/Personal/Screeni-py/src'], + binaries=[], + datas=[], + hiddenimports=['cmath', 'talib.stream', 'numpy'], + hookspath=[], + runtime_hooks=[], + excludes=[], + win_no_prefer_redirects=False, + win_private_assemblies=False, + cipher=block_cipher, + noarchive=False) +pyz = PYZ(a.pure, a.zipped_data, + cipher=block_cipher) +exe = EXE(pyz, + a.scripts, + a.binaries, + a.zipfiles, + a.datas, + [], + name='screenipy', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + upx_exclude=[], + runtime_tmpdir=None, + console=True , icon='icon.ico') diff --git a/src/static/tablefilter/style/colsVisibility.css b/src/static/tablefilter/style/colsVisibility.css new file mode 100644 index 0000000000000000000000000000000000000000..3f2a86d695dd6429dbe07adbfd117ebd061e0d7d --- /dev/null +++ b/src/static/tablefilter/style/colsVisibility.css @@ -0,0 +1 @@ +span.colVisSpan{text-align:left;}span.colVisSpan a.colVis{display:inline-block;padding:7px 5px 0;font-size:inherit;font-weight:inherit;vertical-align:top}div.colVisCont{position:relative;background:#fff;-webkit-box-shadow:3px 3px 2px #888;-moz-box-shadow:3px 3px 2px #888;box-shadow:3px 3px 2px #888;position:absolute;display:none;border:1px solid #ccc;height:auto;width:250px;background-color:#fff;margin:35px 0 0 -100px;z-index:10000;padding:10px 10px 10px 10px;text-align:left;font-size:inherit;}div.colVisCont:after,div.colVisCont:before{bottom:100%;left:50%;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none}div.colVisCont:after{border-color:rgba(255,255,255,0);border-bottom-color:#fff;border-width:10px;margin-left:-10px}div.colVisCont:before{border-color:rgba(255,255,255,0);border-bottom-color:#ccc;border-width:12px;margin-left:-12px}div.colVisCont p{margin:6px auto 6px auto}div.colVisCont a.colVis{display:initial;font-weight:inherit}ul.cols_checklist{padding:0;margin:0;list-style-type:none;}ul.cols_checklist label{display:block}ul.cols_checklist input{vertical-align:middle;margin:2px 5px 2px 1px}li.cols_checklist_item{padding:4px;margin:0;}li.cols_checklist_item:hover{background-color:#335ea8;color:#fff}.cols_checklist_slc_item{background-color:#335ea8;color:#fff} \ No newline at end of file diff --git a/src/static/tablefilter/style/filtersVisibility.css b/src/static/tablefilter/style/filtersVisibility.css new file mode 100644 index 0000000000000000000000000000000000000000..bac6bf511f630bb4fd30e4dd4034ca1bc6faa0c1 --- /dev/null +++ b/src/static/tablefilter/style/filtersVisibility.css @@ -0,0 +1 @@ +span.expClpFlt a.btnExpClpFlt{width:35px;height:35px;display:inline-block;}span.expClpFlt a.btnExpClpFlt:hover{background-color:#f4f4f4}span.expClpFlt img{padding:8px 11px 11px 11px} \ No newline at end of file diff --git a/src/static/tablefilter/style/tablefilter.css b/src/static/tablefilter/style/tablefilter.css new file mode 100644 index 0000000000000000000000000000000000000000..9d0c52f1337f2a7122cbafa9a58061f463b31a12 --- /dev/null +++ b/src/static/tablefilter/style/tablefilter.css @@ -0,0 +1 @@ +.activeHeader{background-color:#66afe9 !important;color:#fff !important}.activeCell{background-color:rgba(0,0,0,0.075)}.even{background-color:#fff}.odd{background-color:#f9f9f9}.ezActiveRow{background-color:#2852a8 !important;color:#fff}.ezSelectedRow{background-color:#316ac5 !important;color:#fff}.ezActiveCell{background-color:#d9e8fb !important;color:#000 !important;font-weight:bold}.ezETSelectedCell{background-color:#ffdc61 !important;font-weight:bold;color:#000 !important}.ezUnselectable{-moz-user-select:-moz-none;-khtml-user-select:none;-webkit-user-select:none;-o-user-select:none;user-select:none}.ezInputEditor{width:95%;height:auto;font-size:inherit;border:1px solid #aaccf6}.ezTextareaEditor{width:95%;height:35px;font-size:inherit;border:1px solid #aaccf6}.ezSelectEditor{width:100%;font-size:inherit;border:1px solid #aaccf6}.ezModifiedCell{background:transparent url("themes/bg_mod_cell.png") 0 0 no-repeat}select[multiple="multiple"].ezSelectEditor{height:35px}.ezCommandEditor{margin:2px;}.ezCommandEditor button,.ezCommandEditor input[type="button"]{min-height:22px;margin:1px;padding:3px;border:1px solid #ccc;background:#fff;border-radius:4px 4px 4px 4px;-moz-border-radius:4px 4px 4px 4px;}.ezCommandEditor button:hover,.ezCommandEditor input[type="button"]:hover{border:1px solid #999}.ezCommandEditor img{border:0;vertical-align:middle;margin:2px}.ezOpacity{opacity:.6}.alignLeft{text-align:left}.alignCenter{text-align:center}.alignRight{text-align:right}.div_checklist{width:100%;height:90px;line-height:30px;border:1px solid #f4f4f4;overflow:auto;text-align:left;background-color:#fff;color:#444;}.div_checklist ul.flt_checklist{padding:0 !important;margin:0 !important;list-style:none !important}.div_checklist li.flt_checklist_item{padding:1px !important;margin:0 !important;font-size:inherit;border-bottom:1px solid #f4f4f4 !important;}.div_checklist li.flt_checklist_item:hover{background-color:#335ea8 !important;color:#fff !important}.div_checklist label{display:block !important;font-weight:inherit !important}.div_checklist input{vertical-align:middle !important;margin:2px 5px 3px 1px !important}.flt_checklist_item_disabled{background-color:#e5e5e5}.flt_checklist_slc_item{background-color:#335ea8 !important;color:#fff !important}.fltrow{height:1em;background-color:#eaeaea;}.fltrow td{border-bottom:1px solid #ccc !important;border-top:1px solid #f4f4f4;border-left:1px solid #ccc;border-right:1px solid #f4f4f4;padding:.2em !important;}.fltrow td:last-child{border-right:1px solid #ccc}.btnflt{height:35px;font-family:inherit;font-size:inherit;vertical-align:middle;margin:0 2px 0 2px;padding:0 1px 0 1px}.btnflt_icon{font-family:inherit;font-size:inherit;width:35px;height:35px;cursor:pointer !important;border:0 !important;vertical-align:middle;background:transparent url("themes/btn_filter.png") center center no-repeat !important}.flt,.flt_s,.single_flt{font-family:inherit;font-size:inherit;display:block;color:#444;background-color:#fff;border:1px inset #f4f4f4;margin:0;padding:0 0 0 .2em;width:100%;height:35px;vertical-align:middle;border-radius:2px;box-sizing:border-box;}.flt:focus,.flt_s:focus,.single_flt:focus{border-color:#66afe9;outline:0 none;-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.075) inset,0 0 8px rgba(102,175,233,0.6);-moz-box-shadow:0 1px 1px rgba(0,0,0,0.075) inset,0 0 8px rgba(102,175,233,0.6);box-shadow:0 1px 1px rgba(0,0,0,0.075) inset,0 0 8px rgba(102,175,233,0.6)}select.flt_multi{font-family:inherit;font-size:inherit;color:#444;background-color:#fff;border:1px solid #f4f4f4;margin:0;padding:.2em;width:100%;height:90px;vertical-align:middle;box-sizing:border-box;}select.flt_multi option{padding-top:5px;padding-bottom:5px}.flt_s{width:60%;box-sizing:initial;display:initial}.single_flt{width:70%;box-sizing:initial;display:initial}div.popUpFilter{position:relative;background:#fff;-webkit-box-shadow:3px 3px 2px #888;-moz-box-shadow:3px 3px 2px #888;box-shadow:3px 3px 2px #888;margin:30px auto 0 0;position:absolute;display:none;width:100px;background-color:#eaeaea;border:1px solid #eaeaea;padding:0}div.popUpFilter:after,div.popUpFilter:before{bottom:100%;left:50%;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none}div.popUpFilter:after{border-color:rgba(255,255,255,0);border-bottom-color:#fff;border-width:10px;margin-left:-10px}div.popUpFilter:before{border-color:rgba(255,255,255,0);border-bottom-color:#eaeaea;border-width:12px;margin-left:-12px}.popUpPlaceholder{position:relative}div.grd_Cont{-webkit-box-shadow:4px 4px 10px 0 rgba(50,50,50,0.75);-moz-box-shadow:4px 4px 10px 0 rgba(50,50,50,0.75);box-shadow:4px 4px 10px 0 rgba(50,50,50,0.75);width:800px;height:auto;overflow:hidden;background-color:#c8e0fb;border:1px solid #99bbe8;}div.grd_Cont .fltrow{background-color:transparent}div.grd_Cont .flt{border:1px solid #99bbe8;width:100%;}div.grd_Cont .flt :focus{border:1px solid #558dd9}div.grd_Cont .even{background-color:#fff}div.grd_Cont .odd{background-color:#dfe8f6}div.grd_Cont .no-results{background-color:transparent}div.grd_Cont .sort-arrow{position:initial}div.grd_tblCont{height:400px;width:800px;background:#fff;overflow-x:auto;overflow-y:scroll}div.grd_headTblCont{display:block;margin-right:20px;height:auto;overflow:hidden;border-bottom:1px solid #99bbe8;background-color:#c8e0fb}div.grd_tblCont table,div.grd_headTblCont table{border-collapse:collapse;table-layout:fixed;box-sizing:initial}div.grd_tblCont table th,div.grd_headTblCont table th,div.grd_headTblCont table td{height:35px;background-color:#c8e0fb;padding:.1em .5em;color:#333;border-right:1px solid #99bbe8 !important;overflow:hidden;text-overflow:ellipsis}div.grd_headTblCont table td{padding:.2em .2em}div.grd_tblCont table td{padding:.5em .7em;border-bottom:1px solid #99bbe8;overflow:hidden;text-overflow:ellipsis}.grd_inf{clear:both;width:auto;height:35px;background-color:#c8e0fb;margin:0;padding:1px 3px 1px 3px;border-top:1px solid #99bbe8;}.grd_inf a{color:#333;text-decoration:none;font-weight:bold;}.grd_inf a:hover{text-decoration:underline;background-color:transparent}.grd_inf input.reset:hover{background-color:transparent}.grd_inf .mdiv{width:40% !important}.grd_inf .ldiv div{border:0}.grd_inf .helpBtn{border:0 !important}.grd_inf div.status{position:absolute;float:none !important;height:auto !important;margin:19px 0 !important;font-size:12px;color:#333;border:0 !important}.grd_inf div.tot{border:0 !important}.helpBtn{display:inline-block;height:27px;margin:0;padding:8px 15px 0 15px;vertical-align:top;}.helpBtn:hover{background-color:#f4f4f4}div.helpCont{position:relative;background:#fff;-webkit-box-shadow:3px 3px 2px #888;-moz-box-shadow:3px 3px 2px #888;box-shadow:3px 3px 2px #888;position:absolute;display:none;width:300px;padding:10px;margin:45px 0 0 -150px;border:1px solid #ccc;line-height:20px;font-size:inherit;color:#333;background:#fff;text-align:left;z-index:1000;}div.helpCont:after,div.helpCont:before{bottom:100%;left:50%;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none}div.helpCont:after{border-color:rgba(255,255,255,0);border-bottom-color:#fff;border-width:10px;margin-left:-10px}div.helpCont:before{border-color:rgba(255,255,255,0);border-bottom-color:#ccc;border-width:12px;margin-left:-12px}div.helpCont a{color:#c00;text-decoration:underline;font-weight:normal}div.helpCont a.close{color:#333 !important;text-decoration:none !important;font-weight:bold;}div.helpCont a.close:hover{text-decoration:none}div.helpCont hr{border:1px solid #ccc}div.helpFooter{margin:10px 0 0 0;}div.helpFooter h4{margin:2px 2px 2px 2px;color:#333}span.keyword{font-weight:700;font-style:italic;border-bottom:1px dotted #ccc}.loader{position:absolute;padding:.5em .7em;margin:10em 0 0 3em;width:auto;z-index:1000;font-weight:600;background-color:#a7a7a8;vertical-align:middle;border-radius:10px;color:#fff;text-shadow:1px 1px #333}.no-results{display:none;color:#333;margin:0;padding:1em 0;text-align:center;max-height:5em;background-color:#f4f4f4}select.pgSlc{height:35px;margin:0;border:1px solid #f4f4f4;background-color:#fff;vertical-align:middle}select.pgSlc:focus{border-color:#66afe9;outline:0 none;-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.075) inset,0 0 8px rgba(102,175,233,0.6);-moz-box-shadow:0 1px 1px rgba(0,0,0,0.075) inset,0 0 8px rgba(102,175,233,0.6);box-shadow:0 1px 1px rgba(0,0,0,0.075) inset,0 0 8px rgba(102,175,233,0.6)}input.pgNbInp{height:35px;margin:0;border:1px solid #f4f4f4;background-color:#fff;width:35px}input.pgNbInp:focus{border-color:#66afe9;outline:0 none;-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.075) inset,0 0 8px rgba(102,175,233,0.6);-moz-box-shadow:0 1px 1px rgba(0,0,0,0.075) inset,0 0 8px rgba(102,175,233,0.6);box-shadow:0 1px 1px rgba(0,0,0,0.075) inset,0 0 8px rgba(102,175,233,0.6)}input.pgInp,.nextPage,.previousPage,.firstPage,.lastPage{height:35px;margin:0;border:1px solid #f4f4f4;background-color:#fff;vertical-align:middle;width:35px;border:0;font-weight:bold}input.pgInp:focus,.nextPage:focus,.previousPage:focus,.firstPage:focus,.lastPage:focus{border-color:#66afe9;outline:0 none;-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.075) inset,0 0 8px rgba(102,175,233,0.6);-moz-box-shadow:0 1px 1px rgba(0,0,0,0.075) inset,0 0 8px rgba(102,175,233,0.6);box-shadow:0 1px 1px rgba(0,0,0,0.075) inset,0 0 8px rgba(102,175,233,0.6)}.nextPage{background:transparent url("themes/btn_next_page.gif") center center no-repeat !important;}.nextPage:hover{background-color:#f4f4f4 !important}.previousPage{background:transparent url("themes/btn_previous_page.gif") center center no-repeat !important;}.previousPage:hover{background-color:#f4f4f4 !important}.firstPage{background:transparent url("themes/btn_first_page.gif") center center no-repeat !important;}.firstPage:hover{background-color:#f4f4f4 !important}.lastPage{background:transparent url("themes/btn_last_page.gif") center center no-repeat !important;}.lastPage:hover{background-color:#f4f4f4 !important}span.nbpg{padding:0 5px}select.rspg{height:35px;margin:0;border:1px solid #f4f4f4;background-color:#fff;margin:0 0 0 5px;vertical-align:middle}select.rspg:focus{border-color:#66afe9;outline:0 none;-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.075) inset,0 0 8px rgba(102,175,233,0.6);-moz-box-shadow:0 1px 1px rgba(0,0,0,0.075) inset,0 0 8px rgba(102,175,233,0.6);box-shadow:0 1px 1px rgba(0,0,0,0.075) inset,0 0 8px rgba(102,175,233,0.6)}span.rspgSpan{font-size:inherit}input.reset{display:inline-block;width:35px;height:35px;border:0;background:transparent url("themes/btn_clear_filters.png") center center no-repeat;vertical-align:top;}input.reset:hover{background-color:#f4f4f4}a.reset{font-weight:normal !important;line-height:35px;padding:5px 5px}div.tot{float:left;overflow:hidden;min-width:150px;height:100%;margin:0;padding:.5em;vertical-align:middle;}div.tot span{font-weight:500}.sort-arrow{position:absolute;display:none;width:11px;height:11px;margin:0;background-position:center center;background-repeat:no-repeat}.descending{display:inline;background-image:url("themes/downsimple.png")}.ascending{display:inline;background-image:url("themes/upsimple.png")}div.status{float:left;overflow:hidden;min-width:120px;height:100%;margin:0;padding:.5em;}div.status span{font-size:inherit}table.TF{font-family:inherit;border-spacing:0;border:0;}table.TF th{height:35px;margin:0;background-color:#eaeaea;border-bottom:1px solid #ccc;border-top:1px solid #f4f4f4;border-left:1px solid #ccc;border-right:1px solid #f4f4f4;padding:.1em .7em;color:#333;}table.TF th:last-child{border-right:1px solid #ccc}table.TF td{margin:0;padding:.5em .7em;border-bottom:1px solid #c6c6c6;text-overflow:ellipsis}table.TF.resp{display:block;overflow-x:auto;overflow-y:hidden;}table.TF.resp .sort-arrow{position:initial}table.TF.sticky th{position:sticky;top:-1px}.inf{clear:both;width:auto;height:35px;min-width:400px;background-color:#fff;font-size:inherit;margin:0;padding:0;border-top:1px solid #ccc;border-bottom:1px solid #ccc;border-left:1px solid #ccc;border-right:1px solid #ccc;overflow:hidden;border-top-left-radius:3px;border-top-right-radius:3px;}.inf a{color:#333;text-decoration:none;font-weight:bold;box-sizing:initial;}.inf a:hover{text-decoration:underline}.ldiv{float:left;width:30%;position:inherit;text-align:left}.ldiv:empty:after{content:"\00A0"}.mdiv{float:left;width:38%;position:inherit;text-align:center;padding:0}.mdiv:empty:after{content:"\00A0"}.rdiv{float:right;width:30%;position:inherit;text-align:right}.rdiv:empty:after{content:"\00A0"} \ No newline at end of file diff --git a/src/static/tablefilter/style/themes/blank.png b/src/static/tablefilter/style/themes/blank.png new file mode 100644 index 0000000000000000000000000000000000000000..cee9cd37a10ebe8d7fe6a6ed0d8d74a2889f6e9f Binary files /dev/null and b/src/static/tablefilter/style/themes/blank.png differ diff --git a/src/static/tablefilter/style/themes/btn_clear_filters.png b/src/static/tablefilter/style/themes/btn_clear_filters.png new file mode 100644 index 0000000000000000000000000000000000000000..b431837531cc4551169cde888d76e75f3b6e248b Binary files /dev/null and b/src/static/tablefilter/style/themes/btn_clear_filters.png differ diff --git a/src/static/tablefilter/style/themes/btn_filter.png b/src/static/tablefilter/style/themes/btn_filter.png new file mode 100644 index 0000000000000000000000000000000000000000..67d47930b2262a7087631bc8c94f7104801b8c83 Binary files /dev/null and b/src/static/tablefilter/style/themes/btn_filter.png differ diff --git a/src/static/tablefilter/style/themes/btn_first_page.gif b/src/static/tablefilter/style/themes/btn_first_page.gif new file mode 100644 index 0000000000000000000000000000000000000000..ba03bde6ec9d37926ebd5972d55978b261c130d9 Binary files /dev/null and b/src/static/tablefilter/style/themes/btn_first_page.gif differ diff --git a/src/static/tablefilter/style/themes/btn_last_page.gif b/src/static/tablefilter/style/themes/btn_last_page.gif new file mode 100644 index 0000000000000000000000000000000000000000..965f55ec3a732da484487c0a98c8961da68f7390 Binary files /dev/null and b/src/static/tablefilter/style/themes/btn_last_page.gif differ diff --git a/src/static/tablefilter/style/themes/btn_next_page.gif b/src/static/tablefilter/style/themes/btn_next_page.gif new file mode 100644 index 0000000000000000000000000000000000000000..5b9cd5ddaa89d3bc8a1b7b6ebeffb1c507eac7f9 Binary files /dev/null and b/src/static/tablefilter/style/themes/btn_next_page.gif differ diff --git a/src/static/tablefilter/style/themes/btn_previous_page.gif b/src/static/tablefilter/style/themes/btn_previous_page.gif new file mode 100644 index 0000000000000000000000000000000000000000..c5071f86ddfdf7fc9e8f281c6e2c087855c976e7 Binary files /dev/null and b/src/static/tablefilter/style/themes/btn_previous_page.gif differ diff --git a/src/static/tablefilter/style/themes/default/default.css b/src/static/tablefilter/style/themes/default/default.css new file mode 100644 index 0000000000000000000000000000000000000000..0a57c38b3bff9b688968ee13e28ad6dc35fead38 --- /dev/null +++ b/src/static/tablefilter/style/themes/default/default.css @@ -0,0 +1 @@ +table.TF{border-left:1px solid #ccc;border-top:none;border-right:none;border-bottom:none;}table.TF th{background:#ebecee url("images/bg_th.jpg") left top repeat-x;border-bottom:1px solid #d0d0d0;border-right:1px solid #d0d0d0;border-left:1px solid #fff;border-top:1px solid #fff;color:#333}table.TF td{border-bottom:1px dotted #999;padding:5px}.fltrow{background-color:#ebecee !important;}.fltrow th,.fltrow td{border-bottom:1px dotted #666 !important;padding:1px 3px 1px 3px !important}.flt,select.flt,select.flt_multi,.flt_s,.single_flt,.div_checklist{border:1px solid #999 !important}input.flt{width:99% !important}.inf{height:$min-height;background:#d7d7d7 url("images/bg_infDiv.jpg") 0 0 repeat-x !important}input.reset{background:transparent url("images/btn_eraser.gif") center center no-repeat !important}.helpBtn:hover{background-color:transparent}.nextPage{background:transparent url("images/btn_next_page.gif") center center no-repeat !important;}.nextPage:hover{background:transparent url("images/btn_over_next_page.gif") center center no-repeat !important}.previousPage{background:transparent url("images/btn_previous_page.gif") center center no-repeat !important;}.previousPage:hover{background:transparent url("images/btn_over_previous_page.gif") center center no-repeat !important}.firstPage{background:transparent url("images/btn_first_page.gif") center center no-repeat !important;}.firstPage:hover{background:transparent url("images/btn_over_first_page.gif") center center no-repeat !important}.lastPage{background:transparent url("images/btn_last_page.gif") center center no-repeat !important;}.lastPage:hover{background:transparent url("images/btn_over_last_page.gif") center center no-repeat !important}div.grd_Cont{background-color:#ebecee !important;border:1px solid #ccc !important;padding:0 !important;}div.grd_Cont .even{background-color:#fff}div.grd_Cont .odd{background-color:#d5d5d5}div.grd_headTblCont{background-color:#ebecee !important;border-bottom:none !important;}div.grd_headTblCont table{border-right:none !important}div.grd_tblCont table th,div.grd_headTblCont table th,div.grd_headTblCont table td{background:#ebecee url("images/bg_th.jpg") left top repeat-x !important;border-bottom:1px solid #d0d0d0 !important;border-right:1px solid #d0d0d0 !important;border-left:1px solid #fff !important;border-top:1px solid #fff !important}div.grd_tblCont table td{border-bottom:1px solid #999 !important}.grd_inf{background:#d7d7d7 url("images/bg_infDiv.jpg") 0 0 repeat-x !important;border-top:1px solid #d0d0d0 !important}.loader{border:1px solid #999}.defaultLoader{width:32px;height:32px;background:transparent url("images/img_loading.gif") 0 0 no-repeat !important}.even{background-color:#fff}.odd{background-color:#d5d5d5}span.expClpFlt a.btnExpClpFlt:hover{background-color:transparent !important}.activeHeader{background:#999 !important} \ No newline at end of file diff --git a/src/static/tablefilter/style/themes/default/images/bg_infDiv.jpg b/src/static/tablefilter/style/themes/default/images/bg_infDiv.jpg new file mode 100644 index 0000000000000000000000000000000000000000..958953092796d5f7af9f21768f392b879ec08132 Binary files /dev/null and b/src/static/tablefilter/style/themes/default/images/bg_infDiv.jpg differ diff --git a/src/static/tablefilter/style/themes/default/images/bg_th.jpg b/src/static/tablefilter/style/themes/default/images/bg_th.jpg new file mode 100644 index 0000000000000000000000000000000000000000..eea01e93ee370ee3ead7dbf37b4233a405a28ea7 Binary files /dev/null and b/src/static/tablefilter/style/themes/default/images/bg_th.jpg differ diff --git a/src/static/tablefilter/style/themes/default/images/btn_eraser.gif b/src/static/tablefilter/style/themes/default/images/btn_eraser.gif new file mode 100644 index 0000000000000000000000000000000000000000..bb1050a35196945dc52e847d91ec789ac426e987 Binary files /dev/null and b/src/static/tablefilter/style/themes/default/images/btn_eraser.gif differ diff --git a/src/static/tablefilter/style/themes/default/images/btn_first_page.gif b/src/static/tablefilter/style/themes/default/images/btn_first_page.gif new file mode 100644 index 0000000000000000000000000000000000000000..2405816fae529ca0601a55a8578e4506a3209467 Binary files /dev/null and b/src/static/tablefilter/style/themes/default/images/btn_first_page.gif differ diff --git a/src/static/tablefilter/style/themes/default/images/btn_last_page.gif b/src/static/tablefilter/style/themes/default/images/btn_last_page.gif new file mode 100644 index 0000000000000000000000000000000000000000..32de4b5d81435fb423b1da8f538fb2ff64338ec4 Binary files /dev/null and b/src/static/tablefilter/style/themes/default/images/btn_last_page.gif differ diff --git a/src/static/tablefilter/style/themes/default/images/btn_next_page.gif b/src/static/tablefilter/style/themes/default/images/btn_next_page.gif new file mode 100644 index 0000000000000000000000000000000000000000..6344ff1e63495e73bf1d64ec3a2dd87a170475bf Binary files /dev/null and b/src/static/tablefilter/style/themes/default/images/btn_next_page.gif differ diff --git a/src/static/tablefilter/style/themes/default/images/btn_over_eraser.gif b/src/static/tablefilter/style/themes/default/images/btn_over_eraser.gif new file mode 100644 index 0000000000000000000000000000000000000000..e22f48bdcddc8b7938244408d5973b5aa8bbe5a2 Binary files /dev/null and b/src/static/tablefilter/style/themes/default/images/btn_over_eraser.gif differ diff --git a/src/static/tablefilter/style/themes/default/images/btn_over_first_page.gif b/src/static/tablefilter/style/themes/default/images/btn_over_first_page.gif new file mode 100644 index 0000000000000000000000000000000000000000..23ffe53ffde0881e7e47a7512a9b8f0b001117dd Binary files /dev/null and b/src/static/tablefilter/style/themes/default/images/btn_over_first_page.gif differ diff --git a/src/static/tablefilter/style/themes/default/images/btn_over_last_page.gif b/src/static/tablefilter/style/themes/default/images/btn_over_last_page.gif new file mode 100644 index 0000000000000000000000000000000000000000..cb41d28dcc414d8dd2edad4b4c64240434213e8c Binary files /dev/null and b/src/static/tablefilter/style/themes/default/images/btn_over_last_page.gif differ diff --git a/src/static/tablefilter/style/themes/default/images/btn_over_next_page.gif b/src/static/tablefilter/style/themes/default/images/btn_over_next_page.gif new file mode 100644 index 0000000000000000000000000000000000000000..49c5456009724fc34af77754ee6efab6e9181b28 Binary files /dev/null and b/src/static/tablefilter/style/themes/default/images/btn_over_next_page.gif differ diff --git a/src/static/tablefilter/style/themes/default/images/btn_over_previous_page.gif b/src/static/tablefilter/style/themes/default/images/btn_over_previous_page.gif new file mode 100644 index 0000000000000000000000000000000000000000..6c260129e32009fe290c6788e1cf066f52e9bbc5 Binary files /dev/null and b/src/static/tablefilter/style/themes/default/images/btn_over_previous_page.gif differ diff --git a/src/static/tablefilter/style/themes/default/images/btn_previous_page.gif b/src/static/tablefilter/style/themes/default/images/btn_previous_page.gif new file mode 100644 index 0000000000000000000000000000000000000000..e9f081430e7335a18568d6b2d22317dbfb09408c Binary files /dev/null and b/src/static/tablefilter/style/themes/default/images/btn_previous_page.gif differ diff --git a/src/static/tablefilter/style/themes/default/images/img_loading.gif b/src/static/tablefilter/style/themes/default/images/img_loading.gif new file mode 100644 index 0000000000000000000000000000000000000000..3bbf639efae54ae59e83067121a5283ca34fc319 Binary files /dev/null and b/src/static/tablefilter/style/themes/default/images/img_loading.gif differ diff --git a/src/static/tablefilter/style/themes/downsimple.png b/src/static/tablefilter/style/themes/downsimple.png new file mode 100644 index 0000000000000000000000000000000000000000..4accf927bed9f998040ada65a35695bb758c78f1 Binary files /dev/null and b/src/static/tablefilter/style/themes/downsimple.png differ diff --git a/src/static/tablefilter/style/themes/icn_clp.png b/src/static/tablefilter/style/themes/icn_clp.png new file mode 100644 index 0000000000000000000000000000000000000000..d259393426c9583252ca5282d82e76aa084dc1d8 Binary files /dev/null and b/src/static/tablefilter/style/themes/icn_clp.png differ diff --git a/src/static/tablefilter/style/themes/icn_exp.png b/src/static/tablefilter/style/themes/icn_exp.png new file mode 100644 index 0000000000000000000000000000000000000000..13909c15d1c2c6650202ab43e0541ef0ce890c1f Binary files /dev/null and b/src/static/tablefilter/style/themes/icn_exp.png differ diff --git a/src/static/tablefilter/style/themes/icn_filter.gif b/src/static/tablefilter/style/themes/icn_filter.gif new file mode 100644 index 0000000000000000000000000000000000000000..f08a4641673f5a257e998b052492e955b0b90774 Binary files /dev/null and b/src/static/tablefilter/style/themes/icn_filter.gif differ diff --git a/src/static/tablefilter/style/themes/icn_filterActive.gif b/src/static/tablefilter/style/themes/icn_filterActive.gif new file mode 100644 index 0000000000000000000000000000000000000000..55b73607142cdb2065f3151c8b8b968845cf1b1f Binary files /dev/null and b/src/static/tablefilter/style/themes/icn_filterActive.gif differ diff --git a/src/static/tablefilter/style/themes/mytheme/images/bg_headers.jpg b/src/static/tablefilter/style/themes/mytheme/images/bg_headers.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9409afd9e512558180fd7c6faf8ee2b296e63b8f Binary files /dev/null and b/src/static/tablefilter/style/themes/mytheme/images/bg_headers.jpg differ diff --git a/src/static/tablefilter/style/themes/mytheme/images/bg_infDiv.jpg b/src/static/tablefilter/style/themes/mytheme/images/bg_infDiv.jpg new file mode 100644 index 0000000000000000000000000000000000000000..958953092796d5f7af9f21768f392b879ec08132 Binary files /dev/null and b/src/static/tablefilter/style/themes/mytheme/images/bg_infDiv.jpg differ diff --git a/src/static/tablefilter/style/themes/mytheme/images/btn_filter.png b/src/static/tablefilter/style/themes/mytheme/images/btn_filter.png new file mode 100644 index 0000000000000000000000000000000000000000..618349088988cafe425e13a8f4b053a1b3f06a5f Binary files /dev/null and b/src/static/tablefilter/style/themes/mytheme/images/btn_filter.png differ diff --git a/src/static/tablefilter/style/themes/mytheme/images/btn_first_page.gif b/src/static/tablefilter/style/themes/mytheme/images/btn_first_page.gif new file mode 100644 index 0000000000000000000000000000000000000000..ba03bde6ec9d37926ebd5972d55978b261c130d9 Binary files /dev/null and b/src/static/tablefilter/style/themes/mytheme/images/btn_first_page.gif differ diff --git a/src/static/tablefilter/style/themes/mytheme/images/btn_last_page.gif b/src/static/tablefilter/style/themes/mytheme/images/btn_last_page.gif new file mode 100644 index 0000000000000000000000000000000000000000..965f55ec3a732da484487c0a98c8961da68f7390 Binary files /dev/null and b/src/static/tablefilter/style/themes/mytheme/images/btn_last_page.gif differ diff --git a/src/static/tablefilter/style/themes/mytheme/images/btn_next_page.gif b/src/static/tablefilter/style/themes/mytheme/images/btn_next_page.gif new file mode 100644 index 0000000000000000000000000000000000000000..5b9cd5ddaa89d3bc8a1b7b6ebeffb1c507eac7f9 Binary files /dev/null and b/src/static/tablefilter/style/themes/mytheme/images/btn_next_page.gif differ diff --git a/src/static/tablefilter/style/themes/mytheme/images/btn_previous_page.gif b/src/static/tablefilter/style/themes/mytheme/images/btn_previous_page.gif new file mode 100644 index 0000000000000000000000000000000000000000..c5071f86ddfdf7fc9e8f281c6e2c087855c976e7 Binary files /dev/null and b/src/static/tablefilter/style/themes/mytheme/images/btn_previous_page.gif differ diff --git a/src/static/tablefilter/style/themes/mytheme/images/img_loading.gif b/src/static/tablefilter/style/themes/mytheme/images/img_loading.gif new file mode 100644 index 0000000000000000000000000000000000000000..cbe59fb438d575e2b8aa6b356b5c903dacea6cd5 Binary files /dev/null and b/src/static/tablefilter/style/themes/mytheme/images/img_loading.gif differ diff --git a/src/static/tablefilter/style/themes/mytheme/mytheme.css b/src/static/tablefilter/style/themes/mytheme/mytheme.css new file mode 100644 index 0000000000000000000000000000000000000000..fa91a66fb47b06c2e4ced62f241930af086588f5 --- /dev/null +++ b/src/static/tablefilter/style/themes/mytheme/mytheme.css @@ -0,0 +1 @@ +table.TF{border-left:1px dotted #81963b;border-top:none;border-right:0;border-bottom:none;}table.TF th{background:#39424b url("images/bg_headers.jpg") left top repeat-x;border-bottom:0;border-right:1px dotted #d0d0d0;border-left:0;border-top:0;color:#fff}table.TF td{border-bottom:1px dotted #81963b;border-right:1px dotted #81963b;padding:5px}.fltrow{background-color:#81963b !important;}.fltrow th,.fltrow td{border-bottom:1px dotted #39424b !important;border-right:1px dotted #fff !important;border-left:0 !important;border-top:0 !important;padding:1px 3px 1px 3px !important}.flt,select.flt,select.flt_multi,.flt_s,.single_flt,.div_checklist{border:1px solid #687830 !important}input.flt{width:99% !important}.inf{background:#d8d8d8;height:$min-height}input.reset{width:53px;background:transparent url("images/btn_filter.png") center center no-repeat !important}.helpBtn:hover{background-color:transparent}.nextPage{background:transparent url("images/btn_next_page.gif") center center no-repeat !important}.previousPage{background:transparent url("images/btn_previous_page.gif") center center no-repeat !important}.firstPage{background:transparent url("images/btn_first_page.gif") center center no-repeat !important}.lastPage{background:transparent url("images/btn_last_page.gif") center center no-repeat !important}div.grd_Cont{background:#81963b url("images/bg_headers.jpg") left top repeat-x !important;border:1px solid #ccc !important;padding:0 1px 1px 1px !important;}div.grd_Cont .even{background-color:#bccd83}div.grd_Cont .odd{background-color:#fff}div.grd_headTblCont{background-color:#ebecee !important;border-bottom:none !important}div.grd_tblCont table{border-right:none !important;}div.grd_tblCont table td{border-bottom:1px dotted #81963b;border-right:1px dotted #81963b}div.grd_tblCont table th,div.grd_headTblCont table th{background:transparent url("images/bg_headers.jpg") 0 0 repeat-x !important;border-bottom:0 !important;border-right:1px dotted #d0d0d0 !important;border-left:0 !important;border-top:0 !important;padding:0 4px 0 4px !important;color:#fff !important;height:35px !important}div.grd_headTblCont table td{border-bottom:1px dotted #39424b !important;border-right:1px dotted #fff !important;border-left:0 !important;border-top:0 !important;background-color:#81963b !important;padding:1px 3px 1px 3px !important}.grd_inf{background-color:#d8d8d8;border-top:1px solid #d0d0d0 !important}.loader{border:0 !important;background:#81963b !important}.defaultLoader{width:32px;height:32px;background:transparent url("images/img_loading.gif") 0 0 no-repeat !important}.even{background-color:#bccd83}.odd{background-color:#fff}span.expClpFlt a.btnExpClpFlt:hover{background-color:transparent !important}.activeHeader{background:#81963b !important} \ No newline at end of file diff --git a/src/static/tablefilter/style/themes/skyblue/images/bg_skyblue.gif b/src/static/tablefilter/style/themes/skyblue/images/bg_skyblue.gif new file mode 100644 index 0000000000000000000000000000000000000000..430aa2451a0dfbf9e167a671312c8d87b44a606d Binary files /dev/null and b/src/static/tablefilter/style/themes/skyblue/images/bg_skyblue.gif differ diff --git a/src/static/tablefilter/style/themes/skyblue/images/btn_first_page.gif b/src/static/tablefilter/style/themes/skyblue/images/btn_first_page.gif new file mode 100644 index 0000000000000000000000000000000000000000..56441d9f4bf343afe20c9495e37673141d021859 Binary files /dev/null and b/src/static/tablefilter/style/themes/skyblue/images/btn_first_page.gif differ diff --git a/src/static/tablefilter/style/themes/skyblue/images/btn_last_page.gif b/src/static/tablefilter/style/themes/skyblue/images/btn_last_page.gif new file mode 100644 index 0000000000000000000000000000000000000000..cd99c92958104519a719a4e6e69e5de671bcdafd Binary files /dev/null and b/src/static/tablefilter/style/themes/skyblue/images/btn_last_page.gif differ diff --git a/src/static/tablefilter/style/themes/skyblue/images/btn_next_page.gif b/src/static/tablefilter/style/themes/skyblue/images/btn_next_page.gif new file mode 100644 index 0000000000000000000000000000000000000000..2422770d214d617e9a6d76db426672adb04c9f2c Binary files /dev/null and b/src/static/tablefilter/style/themes/skyblue/images/btn_next_page.gif differ diff --git a/src/static/tablefilter/style/themes/skyblue/images/btn_prev_page.gif b/src/static/tablefilter/style/themes/skyblue/images/btn_prev_page.gif new file mode 100644 index 0000000000000000000000000000000000000000..0288506ab40b49a04f6deb7033111719099b70d4 Binary files /dev/null and b/src/static/tablefilter/style/themes/skyblue/images/btn_prev_page.gif differ diff --git a/src/static/tablefilter/style/themes/skyblue/images/icn_clear_filters.png b/src/static/tablefilter/style/themes/skyblue/images/icn_clear_filters.png new file mode 100644 index 0000000000000000000000000000000000000000..def51bf4e984c3d4e5333e0039c9708fe2c5c21e Binary files /dev/null and b/src/static/tablefilter/style/themes/skyblue/images/icn_clear_filters.png differ diff --git a/src/static/tablefilter/style/themes/skyblue/images/img_loading.gif b/src/static/tablefilter/style/themes/skyblue/images/img_loading.gif new file mode 100644 index 0000000000000000000000000000000000000000..8fed7bebe9dda3850e5dea3c3f50140212a67f4f Binary files /dev/null and b/src/static/tablefilter/style/themes/skyblue/images/img_loading.gif differ diff --git a/src/static/tablefilter/style/themes/skyblue/skyblue.css b/src/static/tablefilter/style/themes/skyblue/skyblue.css new file mode 100644 index 0000000000000000000000000000000000000000..4aa04b2db331a6e1fc7713a23ddf36f6acc6a3fb --- /dev/null +++ b/src/static/tablefilter/style/themes/skyblue/skyblue.css @@ -0,0 +1 @@ +table.TF{padding:0;color:#000;border-right:1px solid #a4bed4;border-top:1px solid #a4bed4;border-left:1px solid #a4bed4;border-bottom:0;}table.TF th{margin:0;color:inherit;background:#d1e5fe url("images/bg_skyblue.gif") 0 0 repeat-x;border-color:#fdfdfd #a4bed4 #a4bed4 #fdfdfd;border-width:1px;border-style:solid}table.TF td{margin:0;padding:5px;color:inherit;border-bottom:1px solid #a4bed4;border-left:0;border-top:0;border-right:0}.fltrow{background-color:#d1e5fe !important;}.fltrow th,.fltrow td{padding:1px 3px 1px 3px !important}.flt,select.flt,select.flt_multi,.flt_s,.single_flt,.div_checklist{border:1px solid #a4bed4 !important}input.flt{width:99% !important}.inf{background-color:#e3efff !important;border:1px solid #a4bed4;height:$min-height;color:#004a6f}div.tot,div.status{border-right:0 !important}.helpBtn:hover{background-color:transparent}input.reset{background:transparent url("images/icn_clear_filters.png") center center no-repeat !important}.nextPage{background:transparent url("images/btn_next_page.gif") center center no-repeat !important;border:1px solid transparent !important;}.nextPage:hover{background:#ffe4ab url("images/btn_next_page.gif") center center no-repeat !important;border:1px solid #ffb552 !important}.previousPage{background:transparent url("images/btn_prev_page.gif") center center no-repeat !important;border:1px solid transparent !important;}.previousPage:hover{background:#ffe4ab url("images/btn_prev_page.gif") center center no-repeat !important;border:1px solid #ffb552 !important}.firstPage{background:transparent url("images/btn_first_page.gif") center center no-repeat !important;border:1px solid transparent !important;}.firstPage:hover{background:#ffe4ab url("images/btn_first_page.gif") center center no-repeat !important;border:1px solid #ffb552 !important}.lastPage{background:transparent url("images/btn_last_page.gif") center center no-repeat !important;border:1px solid transparent !important;}.lastPage:hover{background:#ffe4ab url("images/btn_last_page.gif") center center no-repeat !important;border:1px solid #ffb552 !important}.activeHeader{background:#ffe4ab !important;border:1px solid #ffb552 !important;color:inherit !important}div.grd_Cont{background-color:#d9eaed !important;border:1px solid #9cc !important;padding:0 !important;}div.grd_Cont .even{background-color:#fff}div.grd_Cont .odd{background-color:#e3efff}div.grd_headTblCont{background-color:#d9eaed !important;border-bottom:none !important}div.grd_tblCont table{border-right:none !important}div.grd_tblCont table th,div.grd_headTblCont table th,div.grd_headTblCont table td{background:#d9eaed url("images/bg_skyblue.gif") left top repeat-x;border-bottom:1px solid #a4bed4;border-right:1px solid #a4bed4 !important;border-left:1px solid #fff !important;border-top:1px solid #fff !important}div.grd_tblCont table td{border-bottom:1px solid #a4bed4 !important;border-right:0 !important;border-left:0 !important;border-top:0 !important}.grd_inf{background-color:#cce2fe;color:#004a6f;border-top:1px solid #9cc !important;}.grd_inf a{text-decoration:none;font-weight:bold}.loader{background-color:#2d8eef;border:1px solid #cce2fe;border-radius:5px}.even{background-color:#fff}.odd{background-color:#e3efff}span.expClpFlt a.btnExpClpFlt:hover{background-color:transparent !important}.ezActiveRow{background-color:#ffdc61 !important;color:inherit}.ezSelectedRow{background-color:#ffe4ab !important;color:inherit}.ezActiveCell{background-color:#fff !important;color:#000 !important;font-weight:bold}.ezETSelectedCell{background-color:#fff !important;font-weight:bold;color:#000 !important} \ No newline at end of file diff --git a/src/static/tablefilter/style/themes/transparent/images/btn_first_page.gif b/src/static/tablefilter/style/themes/transparent/images/btn_first_page.gif new file mode 100644 index 0000000000000000000000000000000000000000..ba03bde6ec9d37926ebd5972d55978b261c130d9 Binary files /dev/null and b/src/static/tablefilter/style/themes/transparent/images/btn_first_page.gif differ diff --git a/src/static/tablefilter/style/themes/transparent/images/btn_last_page.gif b/src/static/tablefilter/style/themes/transparent/images/btn_last_page.gif new file mode 100644 index 0000000000000000000000000000000000000000..965f55ec3a732da484487c0a98c8961da68f7390 Binary files /dev/null and b/src/static/tablefilter/style/themes/transparent/images/btn_last_page.gif differ diff --git a/src/static/tablefilter/style/themes/transparent/images/btn_next_page.gif b/src/static/tablefilter/style/themes/transparent/images/btn_next_page.gif new file mode 100644 index 0000000000000000000000000000000000000000..5b9cd5ddaa89d3bc8a1b7b6ebeffb1c507eac7f9 Binary files /dev/null and b/src/static/tablefilter/style/themes/transparent/images/btn_next_page.gif differ diff --git a/src/static/tablefilter/style/themes/transparent/images/btn_prev_page.gif b/src/static/tablefilter/style/themes/transparent/images/btn_prev_page.gif new file mode 100644 index 0000000000000000000000000000000000000000..c5071f86ddfdf7fc9e8f281c6e2c087855c976e7 Binary files /dev/null and b/src/static/tablefilter/style/themes/transparent/images/btn_prev_page.gif differ diff --git a/src/static/tablefilter/style/themes/transparent/images/icn_clear_filters.png b/src/static/tablefilter/style/themes/transparent/images/icn_clear_filters.png new file mode 100644 index 0000000000000000000000000000000000000000..def51bf4e984c3d4e5333e0039c9708fe2c5c21e Binary files /dev/null and b/src/static/tablefilter/style/themes/transparent/images/icn_clear_filters.png differ diff --git a/src/static/tablefilter/style/themes/transparent/images/img_loading.gif b/src/static/tablefilter/style/themes/transparent/images/img_loading.gif new file mode 100644 index 0000000000000000000000000000000000000000..8fed7bebe9dda3850e5dea3c3f50140212a67f4f Binary files /dev/null and b/src/static/tablefilter/style/themes/transparent/images/img_loading.gif differ diff --git a/src/static/tablefilter/style/themes/transparent/transparent.css b/src/static/tablefilter/style/themes/transparent/transparent.css new file mode 100644 index 0000000000000000000000000000000000000000..f9c90a3527a41a1d0f4d7b2b905dae0f05296278 --- /dev/null +++ b/src/static/tablefilter/style/themes/transparent/transparent.css @@ -0,0 +1 @@ +table.TF{padding:0;color:inherit;border-right:1px solid transparent;border-top:1px solid transparent;border-left:1px solid transparent;border-bottom:0;}table.TF th{margin:0;color:inherit;background-color:transparent;border-color:transparent;border-width:1px;border-style:solid;}table.TF th:last-child{border-right:1px solid transparent}table.TF td{margin:0;padding:5px;color:inherit;border-bottom:1px solid transparent;border-left:0;border-top:0;border-right:0}.fltrow{background-color:transparent;}.fltrow th,.fltrow td{padding:1px 3px 1px 3px;border-bottom:1px solid transparent !important;}.fltrow th:last-child,.fltrow td:last-child{border-right:1px solid transparent}.flt,select.flt,select.flt_multi,.flt_s,.single_flt,.div_checklist{border:1px solid #a4bed4}input.flt{width:99% !important}.inf{background-color:transparent;border:1px solid transparent;height:$min-height;color:inherit}div.tot,div.status{border-right:0 !important}.helpBtn:hover{background-color:transparent}input.reset{background:transparent url("images/icn_clear_filters.png") center center no-repeat !important}.nextPage{background:transparent url("images/btn_next_page.gif") center center no-repeat !important;border:1px solid transparent !important;}.nextPage:hover{background:#f7f7f7 url("images/btn_next_page.gif") center center no-repeat !important;border:1px solid #f7f7f7 !important}.previousPage{background:transparent url("images/btn_prev_page.gif") center center no-repeat !important;border:1px solid transparent !important;}.previousPage:hover{background:#f7f7f7 url("images/btn_prev_page.gif") center center no-repeat !important;border:1px solid #f7f7f7 !important}.firstPage{background:transparent url("images/btn_first_page.gif") center center no-repeat !important;border:1px solid transparent !important;}.firstPage:hover{background:#f7f7f7 url("images/btn_first_page.gif") center center no-repeat !important;border:1px solid #f7f7f7 !important}.lastPage{background:transparent url("images/btn_last_page.gif") center center no-repeat !important;border:1px solid transparent !important;}.lastPage:hover{background:#f7f7f7 url("images/btn_last_page.gif") center center no-repeat !important;border:1px solid #f7f7f7 !important}.activeHeader{background:#f7f7f7 !important;border:1px solid transparent;color:inherit !important}div.grd_Cont{-webkit-box-shadow:0 0 0 0 rgba(50,50,50,0.75);-moz-box-shadow:0 0 0 0 rgba(50,50,50,0.75);box-shadow:0 0 0 0 rgba(50,50,50,0.75);background-color:transparent;border:1px solid transparent;padding:0 !important;}div.grd_Cont .even{background-color:transparent}div.grd_Cont .odd{background-color:#f7f7f7}div.grd_headTblCont{background-color:transparent;border-bottom:none !important}div.grd_tblCont table{border-right:none !important}div.grd_tblCont table th,div.grd_headTblCont table th,div.grd_headTblCont table td{background:transparent;border-bottom:1px solid transparent;border-right:1px solid transparent !important;border-left:1px solid transparent;border-top:1px solid transparent}div.grd_tblCont table td{border-bottom:1px solid transparent;border-right:0 !important;border-left:0 !important;border-top:0 !important}.grd_inf{background-color:transparent;color:inherit;border-top:1px solid transparent;}.grd_inf a{text-decoration:none;font-weight:bold}.loader{background-color:#f7f7f7;border:1px solid #f7f7f7;border-radius:5px;color:#000;text-shadow:none}.even{background-color:transparent}.odd{background-color:#f7f7f7}span.expClpFlt a.btnExpClpFlt:hover{background-color:transparent !important}.ezActiveRow{background-color:#ccc !important;color:inherit}.ezSelectedRow{background-color:#ccc !important;color:inherit}.ezActiveCell{background-color:transparent;color:inherit;font-weight:bold}.ezETSelectedCell{background-color:transparent;font-weight:bold;color:inherit} \ No newline at end of file diff --git a/src/static/tablefilter/style/themes/upsimple.png b/src/static/tablefilter/style/themes/upsimple.png new file mode 100644 index 0000000000000000000000000000000000000000..c82b76ffe2c9d41a2f38a1c1115013cc0852f522 Binary files /dev/null and b/src/static/tablefilter/style/themes/upsimple.png differ diff --git a/src/static/tablefilter/tablefilter.js b/src/static/tablefilter/tablefilter.js new file mode 100644 index 0000000000000000000000000000000000000000..4201ceaf723b79d9c222dcaae8854ec9d776e1df --- /dev/null +++ b/src/static/tablefilter/tablefilter.js @@ -0,0 +1 @@ +!function webpackUniversalModuleDefinition(t,e){if("object"==typeof exports&&"object"==typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var n=e();for(var r in n)("object"==typeof exports?exports:t)[r]=n[r]}}(window,function(){return function(o){function webpackJsonpCallback(t){for(var e,n,r=t[0],i=t[1],s=0,a=[];s>>0==t&&4294967295!=t}function iterateOverSparseArray(t,e,n,r){for(var i,s=getSparseArrayIndexes(t,n,r),a=0,o=s.length;a>=1)&&(t+=t);return n}(i||"0",e-s.replace(/\.\d+/,"").length)+s,(n||t<0)&&(s=(t<0?"-":"+")+s),s}var N=Math.abs,F=(Math.pow,Math.min,Math.max,Math.ceil),R=Math.floor,D=(Math.round,String.fromCharCode);(function privatePropertyAccessor(t){var n="_sugar_"+t;return function(t,e){return 1u.specificity||(u.specificity=t)}(r),(s=e%1)&&(function handleFraction(t,e,n){if(e){var r=h[_(e)],i=E(t.multiplier/r.multiplier*n);u[r.name]=i}}(n,r,s),e=p(e)),"weekday"!==t?(i=r===S&&28=(e||i());case 1:return o<=(e||i())}}()&&function disambiguateHigherUnit(){var t=h[d];c=l,setUnit(t.name,1,t,d)}(),o}},function(t,e,n){"use strict";var r=n(13),a=n(33),o=n(167),u=n(64),c=r.DAY_INDEX;t.exports=function iterateOverDateParams(i,s,t,e){function run(t,e,n){var r=o(i,t);a(r)&&s(t,r,e,n)}u(function(t,e){var n=run(t.name,t,e);return!1!==n&&e===c&&(n=run("weekday",t,e)),n},t,e)}},function(t,e,n){"use strict";var r=n(14),i=n(13),s=n(110),a=n(43),o=n(37),u=i.WEEK_INDEX,c=r.localeManager;t.exports=function moveToEndOfUnit(t,e,n,r){return e===u&&s(t,c.get(n).getFirstDayOfWeek()),o(t,a(e),r,!0)}},function(t,e,n){"use strict";var r=n(14),i=n(13),s=n(43),a=n(67),o=n(37),u=i.WEEK_INDEX,c=r.localeManager;t.exports=function moveToBeginningOfUnit(t,e,n){return e===u&&a(t,c.get(n).getFirstDayOfWeek()),o(t,s(e))}},function(t,e,n){"use strict";var r=n(183),i=n(185),s=r.defineInstance;t.exports=function defineInstanceSimilar(t,e,n,r){s(t,i(e,n),r)}},function(t,e,n){"use strict";var r=n(400);t.exports=function rangeIsValid(t){return r(t.start)&&r(t.end)&&typeof t.start==typeof t.end}},function(t,e,n){"use strict";n.r(e);var a=n(9).root.document;e.default={write:function write(t,e,n){var r="";n&&(r="; expires="+(r=new Date((new Date).getTime()+36e5*n)).toGMTString()),a.cookie=t+"="+escape(e)+r},read:function read(t){var e="",n=t+"=";if(0<, <=, >, >=, =, *, !, {, }, ||,&&, [empty], [nonempty], rgx:
Learn more
':n.text,e.instrHtml=Object(l.defaultsStr)(n.html,null),e.btnText=Object(l.defaultsStr)(n.btn_text,"?"),e.btnHtml=Object(l.defaultsStr)(n.btn_html,null),e.btnCssClass=Object(l.defaultsStr)(n.btn_css_class,"helpBtn"),e.contCssClass=Object(l.defaultsStr)(n.container_css_class,"helpCont"),e.btn=null,e.cont=null,e.contAdjustLeftPosition=Object(l.defaultsNb)(n.container_adjust_left_position,25),e.boundMouseup=null,e.defaultHtml='

TableFilter v'+t.version+'

'+d+"
©2015-"+t.year+' Max Guglielmi
',e.toolbarPosition=Object(l.defaultsStr)(n.toolbar_position,f.RIGHT),e.emitter.on(["init-help"],function(){return e.init()}),e}return function _createClass(t,e,n){return e&&_defineProperties(t.prototype,e),n&&_defineProperties(t,n),t}(Help,[{key:"onMouseup",value:function onMouseup(t){for(var e=Object(u.targetEvt)(t);e&&e!==this.cont&&e!==this.btn;)e=e.parentNode;e!==this.cont&&e!==this.btn&&this.toggle()}},{key:"init",value:function init(){var t=this;if(!this.initialized){this.emitter.emit("initializing-feature",this,!Object(c.isNull)(this.tgtId));var e=this.tf,n=Object(o.createElm)("span"),r=Object(o.createElm)("div");this.boundMouseup=this.onMouseup.bind(this),(this.tgtId?Object(o.elm)(this.tgtId):e.feature("toolbar").container(this.toolbarPosition)).appendChild(n);var i=this.contTgtId?Object(o.elm)(this.contTgtId):n;if(this.btnHtml){n.innerHTML=this.btnHtml;var s=n.firstChild;Object(u.addEvt)(s,"click",function(){return t.toggle()}),i.appendChild(r)}else{i.appendChild(r);var a=Object(o.createElm)("a",["href","javascript:void(0);"]);a.className=this.btnCssClass,a.appendChild(Object(o.createText)(this.btnText)),n.appendChild(a),Object(u.addEvt)(a,"click",function(){return t.toggle()})}this.instrHtml?(this.contTgtId&&i.appendChild(r),r.innerHTML=this.instrHtml,this.contTgtId||(r.className=this.contCssClass)):(r.innerHTML=this.instrText,r.className=this.contCssClass),r.innerHTML+=this.defaultHtml,Object(u.addEvt)(r,"click",function(){return t.toggle()}),this.cont=r,this.btn=n,this.initialized=!0,this.emitter.emit("feature-initialized",this)}}},{key:"toggle",value:function toggle(){if(this.isEnabled()){Object(u.removeEvt)(a.root,"mouseup",this.boundMouseup);var t=this.cont.style.display;""===t||t===s.NONE?(this.cont.style.display="inline",0'),e.placeholderCssClass=Object(l.defaultsStr)(n.placeholder_css_class,"popUpPlaceholder"),e.containerCssClass=Object(l.defaultsStr)(n.div_css_class,"popUpFilter"),e.adjustToContainer=Object(l.defaultsBool)(n.adjust_to_container,!0),e.onBeforeOpen=Object(l.defaultsFn)(n.on_before_popup_filter_open,s.EMPTY_FN),e.onAfterOpen=Object(l.defaultsFn)(n.on_after_popup_filter_open,s.EMPTY_FN),e.onBeforeClose=Object(l.defaultsFn)(n.on_before_popup_filter_close,s.EMPTY_FN),e.onAfterClose=Object(l.defaultsFn)(n.on_after_popup_filter_close,s.EMPTY_FN),e.fltSpans=[],e.fltIcons=[],e.filtersCache=null,e.fltElms=Object(l.defaultsArr)(e.filtersCache,[]),e.prfxDiv="popup_",e.activeFilterIdx=-1,e}return function _createClass(t,e,n){return e&&_defineProperties(t.prototype,e),n&&_defineProperties(t,n),t}(PopupFilter,[{key:"onClick",value:function onClick(t){var e=Object(u.targetEvt)(t).parentNode,n=parseInt(e.getAttribute("ci"),10);if(this.closeAll(n),this.toggle(n),this.adjustToContainer){var r=this.fltElms[n],i=.95*this.tf.getHeaderElement(n).clientWidth;r.style.width=parseInt(i,10)+"px"}Object(u.cancelEvt)(t),Object(u.stopEvt)(t)}},{key:"onMouseup",value:function onMouseup(t){if(-1!==this.activeFilterIdx){var e=Object(u.targetEvt)(t),n=this.fltElms[this.activeFilterIdx];if(this.fltIcons[this.activeFilterIdx]!==e){for(;e&&e!==n;)e=e.parentNode;e!==n&&this.close(this.activeFilterIdx)}}}},{key:"init",value:function init(){var n=this;if(!this.initialized){var t=this.tf;t.externalFltIds=[""],t.filtersRowIndex=0,t.headersRow<=1&&isNaN(t.config().headers_row_index)&&(t.headersRow=0),t.gridLayout&&(t.headersRow--,this.buildIcons()),this.emitter.on(["before-filtering"],function(){return n.setIconsState()}),this.emitter.on(["after-filtering"],function(){return n.closeAll()}),this.emitter.on(["cell-processed"],function(t,e){return n.changeState(e,!0)}),this.emitter.on(["filters-row-inserted"],function(){return n.buildIcons()}),this.emitter.on(["before-filter-init"],function(t,e){return n.build(e)}),this.initialized=!0}}},{key:"reset",value:function reset(){this.enable(),this.init(),this.buildIcons(),this.buildAll()}},{key:"buildIcons",value:function buildIcons(){var n=this,r=this.tf;r.headersRow++,r.eachCol(function(t){var e=Object(o.createElm)("span",["ci",t]);e.innerHTML=n.iconHtml,r.getHeaderElement(t).appendChild(e),Object(u.addEvt)(e,"click",function(t){return n.onClick(t)}),n.fltSpans[t]=e,n.fltIcons[t]=e.firstChild},function(t){return r.getFilterType(t)===a.NONE})}},{key:"buildAll",value:function buildAll(){for(var t=0;t'),e.toolbarPosition=Object(o.defaultsStr)(n.toolbar_position,c.RIGHT),e.container=null,e.element=null,e}return function _createClass(t,e,n){return e&&_defineProperties(t.prototype,e),n&&_defineProperties(t,n),t}(ClearButton,[{key:"onClick",value:function onClick(){this.isEnabled()&&this.tf.clearFilters()}},{key:"init",value:function init(){var t=this,e=this.tf;if(!this.initialized){this.emitter.emit("initializing-feature",this,!Object(u.isNull)(this.targetId));var n=Object(s.createElm)("span");if((this.targetId?Object(s.elm)(this.targetId):e.feature("toolbar").container(this.toolbarPosition)).appendChild(n),this.html){n.innerHTML=this.html;var r=n.firstChild;Object(a.addEvt)(r,"click",function(){return t.onClick()})}else{var i=Object(s.createElm)("a",["href","javascript:void(0);"]);i.className=this.cssClass,i.appendChild(Object(s.createText)(this.text)),n.appendChild(i),Object(a.addEvt)(i,"click",function(){return t.onClick()})}this.element=n.firstChild,this.container=n,this.initialized=!0,this.emitter.emit("feature-initialized",this)}}},{key:"destroy",value:function destroy(){this.initialized&&(Object(s.removeElm)(this.element),Object(s.removeElm)(this.container),this.element=null,this.container=null,this.initialized=!1)}}]),ClearButton}();r.meta={altName:"btnReset"}},function(t,e,n){"use strict";n.r(e),n.d(e,"AlternateRows",function(){return r});var i=n(10),s=n(2),a=n(1),o=n(5);function _typeof(t){return(_typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function _typeof(t){return typeof t}:function _typeof(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function _defineProperties(t,e){for(var n=0;n"),t.btnPrevPageText=Object(o.defaultsStr)(n.btn_prev_page_text,"<"),t.btnLastPageText=Object(o.defaultsStr)(n.btn_last_page_text,">|"),t.btnFirstPageText=Object(o.defaultsStr)(n.btn_first_page_text,"|<"),t.btnNextPageHtml=Object(o.defaultsStr)(n.btn_next_page_html,e.enableIcons?'':null),t.btnPrevPageHtml=Object(o.defaultsStr)(n.btn_prev_page_html,e.enableIcons?'':null),t.btnFirstPageHtml=Object(o.defaultsStr)(n.btn_first_page_html,e.enableIcons?'':null),t.btnLastPageHtml=Object(o.defaultsStr)(n.btn_last_page_html,e.enableIcons?'':null),t.pageText=Object(o.defaultsStr)(n.page_text," Page "),t.ofText=Object(o.defaultsStr)(n.of_text," of "),t.nbPgSpanCssClass=Object(o.defaultsStr)(n.nb_pages_css_class,"nbpg"),t.hasBtns=Object(o.defaultsBool)(n.btns,!0),t.pageSelectorType=Object(o.defaultsStr)(n.page_selector_type,v.SELECT),t.toolbarPosition=Object(o.defaultsStr)(n.toolbar_position,f.CENTER),t.onBeforeChangePage=Object(o.defaultsFn)(n.on_before_change_page,g.EMPTY_FN),t.onAfterChangePage=Object(o.defaultsFn)(n.on_after_change_page,g.EMPTY_FN),t.slcResultsTxt=null,t.btnNextCont=null,t.btnPrevCont=null,t.btnLastCont=null,t.btnFirstCont=null,t.pgCont=null,t.pgBefore=null,t.pgAfter=null;var r=e.refRow,i=e.getRowsNb(!0);t.nbPages=Math.ceil((i-r)/t.pageLength);var s=_assertThisInitialized(t);return t.evt={slcIndex:function slcIndex(){return s.pageSelectorType===v.SELECT?s.pageSlc.options.selectedIndex:parseInt(s.pageSlc.value,10)-1},nbOpts:function nbOpts(){return s.pageSelectorType===v.SELECT?parseInt(s.pageSlc.options.length,10)-1:s.nbPages-1},next:function next(){var t=s.evt.slcIndex()=t.nbFilterableRows&&(this.startPagingRow=t.nbFilterableRows-this.pageLength),this.setPagingInfo(),n===v.SELECT)){var o=r.options.length-1<=a?r.options.length-1:a;r.options[o].selected=!0}i.emit("after-page-length-change",t,this.pageLength)}}},{key:"resetPage",value:function resetPage(){var t=this.tf;if(this.isEnabled()){this.emitter.emit("before-reset-page",t);var e=t.feature("store").getPageNb();""!==e&&this.changePage(e-1),this.emitter.emit("after-reset-page",t,e)}}},{key:"resetPageLength",value:function resetPageLength(){var t=this.tf;if(this.isEnabled()){this.emitter.emit("before-reset-page-length",t);var e=t.feature("store").getPageLength();""!==e&&(this.pageLengthSlc.options[e].selected=!0,this.changeResultsPerPage()),this.emitter.emit("after-reset-page-length",t,e)}}},{key:"changePageHandler",value:function changePageHandler(t,e){this.setPage(e)}},{key:"changePageResultsHandler",value:function changePageResultsHandler(t,e){this.changeResultsPerPage(e)}},{key:"destroy",value:function destroy(){if(this.initialized){var t=this.evt;this.pageSlc&&(this.pageSelectorType===v.SELECT?Object(b.removeEvt)(this.pageSlc,"change",t.slcPagesChange):this.pageSelectorType===v.INPUT&&Object(b.removeEvt)(this.pageSlc,"keypress",t._detectKey),Object(y.removeElm)(this.pageSlc)),this.btnNextCont&&(Object(b.removeEvt)(this.btnNextCont,"click",t.next),Object(y.removeElm)(this.btnNextCont),this.btnNextCont=null),this.btnPrevCont&&(Object(b.removeEvt)(this.btnPrevCont,"click",t.prev),Object(y.removeElm)(this.btnPrevCont),this.btnPrevCont=null),this.btnLastCont&&(Object(b.removeEvt)(this.btnLastCont,"click",t.last),Object(y.removeElm)(this.btnLastCont),this.btnLastCont=null),this.btnFirstCont&&(Object(b.removeEvt)(this.btnFirstCont,"click",t.first),Object(y.removeElm)(this.btnFirstCont),this.btnFirstCont=null),this.pgBefore&&(Object(y.removeElm)(this.pgBefore),this.pgBefore=null),this.pgAfter&&(Object(y.removeElm)(this.pgAfter),this.pgAfter=null),this.pgCont&&(Object(y.removeElm)(this.pgCont),this.pgCont=null),this.hasResultsPerPage&&this.removeResultsPerPage(),this.emitter.off(["after-filtering"],Object(b.bound)(this.resetPagingInfo,this)),this.emitter.off(["change-page"],Object(b.bound)(this.changePageHandler,this)),this.emitter.off(["change-page-results"],Object(b.bound)(this.changePageResultsHandler,this)),this.pageSlc=null,this.nbPages=0,this.initialized=!1}}}]),Paging}()},function(t,e){e.remove=function removeDiacritics(t){return t.replace(/[^\u0000-\u007e]/g,function(t){return r[t]||t})};for(var n=[{base:" ",chars:" "},{base:"0",chars:"߀"},{base:"A",chars:"â’ļīŧĄÃ€ÃÃ‚áēĻáē¤áēĒáē¨ÃƒÄ€Ä‚áē°áēŽáē´áē˛ČĻĮ Ã„ĮžáēĸÅĮēĮČ€Č‚áē áēŦáēļḀĄČēâą¯"},{base:"AA",chars:"朞"},{base:"AE",chars:"ÆĮŧĮĸ"},{base:"AO",chars:"朴"},{base:"AU",chars:"ęœļ"},{base:"AV",chars:"朏ęœē"},{base:"AY",chars:"ęœŧ"},{base:"B",chars:"ⒷīŧĸḂḄḆɃƁ"},{base:"C",chars:"ⒸīŧŖęœžá¸ˆÄ†CĈĊČÇƇČģ"},{base:"D",chars:"Ⓓīŧ¤á¸ŠÄŽá¸Œá¸á¸’á¸ŽÄÆŠÆ‰á´…ęš"},{base:"Dh",chars:"Ð"},{base:"DZ",chars:"ĮąĮ„"},{base:"Dz",chars:"Į˛Į…"},{base:"E",chars:"ɛâ’ēīŧĨÈÉÊáģ€áēžáģ„áģ‚áēŧĒḔḖĔĖËáēēĚȄȆáē¸áģ†Č¨á¸œÄ˜á¸˜á¸šÆÆŽá´‡"},{base:"F",chars:"ęŧâ’ģīŧĻḞƑęģ"},{base:"G",chars:"â’ŧīŧ§Į´Äœá¸ ÄžÄ ĮĻÄĸĮ¤Æ“ęž ęŊꝾÉĸ"},{base:"H",chars:"â’Ŋīŧ¨Ä¤á¸ĸá¸ĻȞḤḨá¸ĒÄĻâą§âąĩꞍ"},{base:"I",chars:"ⒾīŧŠÃŒÃÃŽÄ¨ÄĒÄŦİÏḎáģˆĮČˆČŠáģŠÄŽá¸ŦƗ"},{base:"J",chars:"â’ŋīŧĒĴɈȡ"},{base:"K",chars:"ⓀīŧĢḰĮ¨á¸˛Äļá¸´Æ˜âąŠę€ę‚ę„ęžĸ"},{base:"L",chars:"ⓁīŧŦÄŋÄšÄŊá¸ļḸÄģá¸ŧá¸ēŁČŊâąĸⱠꝈꝆꞀ"},{base:"LJ",chars:"Į‡"},{base:"Lj",chars:"Įˆ"},{base:"M",chars:"Ⓜīŧ­á¸žáš€áš‚âąŽÆœĪģ"},{base:"N",chars:"ꞤȠⓃīŧŽĮ¸ÅƒÃ‘áš„Å‡áš†Å…ášŠášˆÆęžá´Ž"},{base:"NJ",chars:"ĮŠ"},{base:"Nj",chars:"Į‹"},{base:"O",chars:"Ⓞīŧ¯Ã’ÓÔáģ’áģáģ–áģ”ÕᚌČŦášŽÅŒášáš’ÅŽČŽČ°Ã–ČĒáģŽÅĮ‘ČŒČŽÆ áģœáģšáģ áģžáģĸáģŒáģ˜ĮĒĮŦØĮžÆ†ÆŸęŠęŒ"},{base:"OE",chars:"Œ"},{base:"OI",chars:"Æĸ"},{base:"OO",chars:"Ꝏ"},{base:"OU",chars:"Čĸ"},{base:"P",chars:"Ⓟīŧ°áš”áš–Æ¤âąŖęę’ę”"},{base:"Q",chars:"Ⓠīŧąę–ę˜ÉŠ"},{base:"R",chars:"Ⓡīŧ˛Å”áš˜Å˜ČČ’áššášœÅ–ášžÉŒâą¤ęšęžĻꞂ"},{base:"S",chars:"ⓈīŧŗáēžÅšáš¤Åœáš Å ášĻášĸáš¨Č˜Åžâąžęž¨ęž„"},{base:"T",chars:"Ⓣīŧ´ášĒŤášŦȚÅĸᚰᚎÅĻÆŦÆŽČžęž†"},{base:"Th",chars:"Þ"},{base:"TZ",chars:"服"},{base:"U",chars:"ⓊīŧĩÙÚÛŨᚸÅĒášēÅŦÜĮ›Į—Į•Į™áģĻŎŰĮ“Č”Č–Æ¯áģĒáģ¨áģŽáģŦáģ°áģ¤áš˛Å˛ášļṴɄ"},{base:"V",chars:"ⓋīŧļášŧášžÆ˛ęžÉ…"},{base:"VY",chars:"Ꝡ"},{base:"W",chars:"Ⓦīŧˇáē€áē‚Å´áē†áē„áēˆâą˛"},{base:"X",chars:"Ⓧīŧ¸áēŠáēŒ"},{base:"Y",chars:"Ⓨīŧšáģ˛ÃÅļáģ¸Č˛áēŽÅ¸áģļáģ´ÆŗÉŽáģž"},{base:"Z",chars:"ⓏīŧēÅšáēÅģÅŊáē’á甯ĩȤâąŋâąĢęĸ"},{base:"a",chars:"ⓐīŊáēšÃ ÃĄÃĸáē§áēĨáēĢáēŠÃŖÄÄƒáēąáē¯áēĩáēŗČ§ĮĄÃ¤ĮŸáēŖÃĨĮģĮŽČČƒáēĄáē­áēˇá¸Ä…âąĨɐɑ"},{base:"aa",chars:"机"},{base:"ae",chars:"ÃĻĮŊĮŖ"},{base:"ao",chars:"ęœĩ"},{base:"au",chars:"朎"},{base:"av",chars:"ꜹęœģ"},{base:"ay",chars:"ęœŊ"},{base:"b",chars:"ⓑīŊ‚ḃḅḇƀƃɓƂ"},{base:"c",chars:"īŊƒâ“’ćĉċčçḉƈČŧęœŋↄ"},{base:"d",chars:"ⓓīŊ„ḋďḍḑḓḏđƌɖɗƋᏧԁęžĒ"},{base:"dh",chars:"ð"},{base:"dz",chars:"ĮŗĮ†"},{base:"e",chars:"ⓔīŊ…èÊÃĒáģáēŋáģ…áģƒáēŊēḕḗĕėÃĢáēģěȅȇáēšáģ‡ČŠá¸Ä™á¸™á¸›É‡Į"},{base:"f",chars:"ⓕīŊ†á¸ŸÆ’"},{base:"ff",chars:"īŦ€"},{base:"fi",chars:"īŦ"},{base:"fl",chars:"īŦ‚"},{base:"ffi",chars:"īŦƒ"},{base:"ffl",chars:"īŦ„"},{base:"g",chars:"ⓖīŊ‡ĮĩĝḡğġĮ§ÄŖĮĨɠꞡęŋáĩš"},{base:"h",chars:"ⓗīŊˆÄĨá¸Ŗá¸§ČŸá¸ĨḊá¸Ģáē–ħ⹨âąļÉĨ"},{base:"hv",chars:"ƕ"},{base:"i",chars:"ⓘīŊ‰ÃŦíÎĊÄĢĭïḯáģ‰ĮČ‰Č‹áģ‹Ä¯á¸­É¨Äą"},{base:"j",chars:"ⓙīŊŠÄĩĮ°É‰"},{base:"k",chars:"ⓚīŊ‹á¸ąĮŠá¸ŗÄˇá¸ĩƙâąĒęęƒę…ęžŖ"},{base:"l",chars:"ⓛīŊŒÅ€ÄēĞḡḚÄŧá¸Ŋá¸ģÅŋłƚÉĢⱡꝉꞁꝇɭ"},{base:"lj",chars:"Į‰"},{base:"m",chars:"ⓜīŊá¸ŋášášƒÉąÉ¯"},{base:"n",chars:"ⓝīŊŽĮšÅ„Ãąáš…Åˆáš‡Å†áš‹áš‰ÆžÉ˛Å‰ęž‘ęžĨĐģԉ"},{base:"nj",chars:"ĮŒ"},{base:"o",chars:"ⓞīŊÃ˛ÃŗÃ´áģ“áģ‘áģ—áģ•ÃĩášČ­ášÅáš‘áš“ÅČ¯ČąÃļČĢáģÅ‘Į’ČČÆĄáģáģ›áģĄáģŸáģŖáģáģ™ĮĢĮ­Ã¸ĮŋꝋꝍÉĩɔᴑ"},{base:"oe",chars:"œ"},{base:"oi",chars:"ÆŖ"},{base:"oo",chars:"ꝏ"},{base:"ou",chars:"ČŖ"},{base:"p",chars:"ⓟīŊáš•áš—ÆĨáĩŊę‘ę“ę•Ī"},{base:"q",chars:"ⓠīŊ‘É‹ę—ę™"},{base:"r",chars:"ⓡīŊ’Å•áš™Å™Č‘Č“áš›ášÅ—ášŸÉÉŊꝛꞧꞃ"},{base:"s",chars:"â“ĸīŊ“Å›ášĨÅášĄÅĄáš§ášŖášŠČ™ÅŸČŋꞩꞅáē›Ę‚"},{base:"ss",chars:"ß"},{base:"t",chars:"â“ŖīŊ”ášĢáē—ÅĨáš­Č›ÅŖášąáš¯Å§Æ­ĘˆâąĻꞇ"},{base:"th",chars:"Þ"},{base:"tz",chars:"ꜩ"},{base:"u",chars:"ⓤīŊ•ÚÃēÃģÅŠáššÅĢášģÅ­ÃŧĮœĮ˜Į–Įšáģ§Å¯ÅąĮ”Č•Č—Æ°áģĢáģŠáģ¯áģ­áģąáģĨášŗÅŗášˇášĩʉ"},{base:"v",chars:"â“ĨīŊ–ášŊášŋʋꝟʌ"},{base:"vy",chars:"ꝡ"},{base:"w",chars:"â“ĻīŊ—áēáēƒÅĩáē‡áē…áē˜áē‰âąŗ"},{base:"x",chars:"ⓧīŊ˜áē‹áē"},{base:"y",chars:"ⓨīŊ™áģŗÃŊŎáģšČŗáēÃŋáģˇáē™áģĩƴɏáģŋ"},{base:"z",chars:"ⓩīŊšÅēáē‘ÅŧÅžáē“á畯ļČĨɀâąŦęŖ"}],r={},i=0;io().getTime();case"past"===e:return t.getTime()"),this.lwOperator=Object(a.defaultsStr)(s.lower_operator,"<"),this.leOperator=Object(a.defaultsStr)(s.lower_equal_operator,"<="),this.geOperator=Object(a.defaultsStr)(s.greater_equal_operator,">="),this.dfOperator=Object(a.defaultsStr)(s.different_operator,"!"),this.lkOperator=Object(a.defaultsStr)(s.like_operator,"*"),this.eqOperator=Object(a.defaultsStr)(s.equal_operator,"="),this.stOperator=Object(a.defaultsStr)(s.start_with_operator,"{"),this.enOperator=Object(a.defaultsStr)(s.end_with_operator,"}"),this.separator=Object(a.defaultsStr)(s.separator,","),this.rowsCounter=Object(K.isObj)(s.rows_counter)||Boolean(s.rows_counter),this.statusBar=Object(K.isObj)(s.status_bar)||Boolean(s.status_bar),this.loader=Object(K.isObj)(s.loader)||Boolean(s.loader),this.displayBtn=Boolean(s.btn),this.btnText=Object(a.defaultsStr)(s.btn_text,this.enableIcons?"":"Go"),this.btnCssClass=Object(a.defaultsStr)(s.btn_css_class,this.enableIcons?"btnflt_icon":"btnflt"),this.btnReset=Object(K.isObj)(s.btn_reset)||Boolean(s.btn_reset),this.onBeforeReset=Object(a.defaultsFn)(s.on_before_reset,K.EMPTY_FN),this.onAfterReset=Object(a.defaultsFn)(s.on_after_reset,K.EMPTY_FN),this.paging=Object(K.isObj)(s.paging)||Boolean(s.paging),this.nbHiddenRows=0,this.autoFilter=Object(K.isObj)(s.auto_filter)||Boolean(s.auto_filter),this.autoFilterDelay=Object(K.isObj)(s.auto_filter)&&Object(K.isNumber)(s.auto_filter.delay)?s.auto_filter.delay:q.AUTO_FILTER_DELAY,this.isUserTyping=null,this.autoFilterTimer=null,this.highlightKeywords=Boolean(s.highlight_keywords),this.noResults=Object(K.isObj)(s.no_results_message)||Boolean(s.no_results_message),this.state=Object(K.isObj)(s.state)||Boolean(s.state),this.dateType=!0,this.locale=Object(a.defaultsStr)(s.locale,"en"),this.thousandsSeparator=Object(a.defaultsStr)(s.thousands_separator,","),this.decimalSeparator=Object(a.defaultsStr)(s.decimal_separator,"."),this.colTypes=Object(K.isArray)(s.col_types)?s.col_types:[],this.prfxTf="TF",this.prfxFlt="flt",this.prfxValButton="btn",this.prfxResponsive="resp",this.stickyCssClass="sticky",this.extensions=Object(a.defaultsArr)(s.extensions,[]),this.enableDefaultTheme=Boolean(s.enable_default_theme),this.hasThemes=this.enableDefaultTheme||Object(K.isArray)(s.themes),this.themes=Object(a.defaultsArr)(s.themes,[]),this.themesPath=this.getThemesPath(),this.responsive=Boolean(s.responsive),this.toolbar=Object(K.isObj)(s.toolbar)||Boolean(s.toolbar),this.stickyHeaders=Boolean(s.sticky_headers),this.Mod={},this.ExtRegistry={},this.instantiateFeatures(P)}return function _createClass(t,e,n){return e&&_defineProperties(t.prototype,e),n&&_defineProperties(t,n),t}(TableFilter,[{key:"init",value:function init(){var n=this;if(!this.initialized){this.import(this.stylesheetId,this.getStylesheetPath(),null,"link");var t,e=this.Mod;if(this.loadThemes(),this.initFeatures([d.DateType,h.Help,p.State,v.MarkActiveColumns,m.GridLayout,y.Loader,g.HighlightKeyword,b.PopupFilter]),this.fltGrid){var r=this._insertFiltersRow();this.nbCells=this.getCellsNb(this.refRow),this.nbFilterableRows=this.getRowsNb();for(var i=this.singleFlt?1:this.nbCells,s=0;s=Object(G.parse)(t.replace(o,""),s);else if(v)b=rObject(G.parse)(t.replace(c,""),s);else if(w)b=!Object(Y.contains)(t.replace(l,""),e,!1,this.caseSensitive);else if(k)b=Object(Y.contains)(t.replace(f,""),e,!1,this.caseSensitive);else if(x)b=Object(Y.contains)(t.replace(d,""),e,!0,this.caseSensitive);else if(j)b=0===e.indexOf(t.replace(h,""));else if(S){var V=t.replace(p,"");b=e.lastIndexOf(V,e.length-1)===e.length-1-(V.length-1)&&-1 tr").length:e:t?e:e-this.refRow}},{key:"getWorkingRows",value:function getWorkingRows(){return S.querySelectorAll("table#".concat(this.id," > tbody > tr"))}},{key:"getCellValue",value:function getCellValue(t){var e=t.cellIndex,n=this.cellParser;return-1!==n.cols.indexOf(e)?n.parse(this,t,e):Object(l.getText)(t)}},{key:"getCellData",value:function getCellData(t){var e=t.cellIndex,n=this.getCellValue(t);if(this.hasType(e,[q.FORMATTED_NUMBER]))return Object(G.parse)(n,this.getDecimal(e));if(this.hasType(e,[q.NUMBER]))return Number(n);if(this.hasType(e,[q.DATE])){var r=this.Mod.dateType;return r.parse(n,r.getLocale(e))}return n}},{key:"getData",value:function getData(t,e){var n=0=a[1]&&n<=(a[2]||a[1])})),n=C(r))),n?(n=s?v(n):(u.push(o),"("+n+")"),e&&(n=k(o,n,e)),i&&(n+="?"),n):"")}function formatToSrc(t){return t=(t=t.replace(/ /g," ?")).replace(/\{([^,]+?)\}/g,function(t,e){var n=e.split("|");return 1>>0==t&&4294967295!=t}},function(t,e,n){"use strict";var r=n(51).HALF_WIDTH_COMMA;t.exports=function commaSplit(t){return t.split(r)}},function(t,e,n){"use strict";t.exports="Boolean Number String Date RegExp Function Array Error Set Map"},function(t,e,n){"use strict";var r=n(98),i=n(63),s=n(144),a=n(145);t.exports=function isPlainObject(t,e){return i(t)&&r(t,"Object",e)&&a(t)&&s(t)}},function(t,e,n){"use strict";var i=n(16).hasOwn;t.exports=function hasOwnEnumeratedProperties(t){var e=Object.prototype;for(var n in t){var r=t[n];if(!i(t,n)&&r!==e[n])return!1}return!0}},function(t,e,n){"use strict";var r=n(16).hasOwn;t.exports=function hasValidPlainObjectPrototype(t){var e="constructor"in t;return!e&&!("toString"in t)||e&&!r(t,"constructor")&&r(t.constructor.prototype,"isPrototypeOf")}},function(t,e,n){"use strict";t.exports=function getOrdinalSuffix(t){if(11<=t&&t<=13)return"th";switch(t%10){case 1:return"st";case 2:return"nd";case 3:return"rd";default:return"th"}}},function(t,e,n){"use strict";t.exports=function getArrayWithOffset(t,e,n,r){var i;return 1>=1)&&(t+=t);return n}},function(t,e,n){"use strict";var r=n(14),c=n(35),l=n(36),f=n(71),d=r.localeManager;t.exports=function getWeekYear(t,e,n){var r,i,s,a,o,u;return r=c(t),0!==(i=l(t))&&11!==i||(n||(s=(u=d.get(e)).getFirstDayOfWeek(e),a=u.getFirstDayOfWeekYear(e)),o=f(t,!1,s,a),0===i&&0===o?r-=1:11===i&&1===o&&(r+=1)),r}},function(t,e,n){"use strict";var r=n(34),i=n(13),s=n(69),a=i.DAY_INDEX;t.exports=function getDaysSince(t,e){return s(t,e,r[a])}},function(t,e,n){"use strict";var r=n(14),i=n(26),s=n(116),a=r.localeManager;t.exports=function getMeridiemToken(t,e){var n=s(t);return a.get(e).ampm[i(n/12)]||""}},function(t,e,n){"use strict";var r=n(303),i=n(51),s=n(304),o=i.OPEN_BRACE,u=i.CLOSE_BRACE;t.exports=function createFormatMatcher(c,l,f){var i=r,a=s(function compile(t){var e,n=[],r=0;i.lastIndex=0;for(;e=i.exec(t);)getSubstring(n,t,r,e.index),getToken(n,e),r=i.lastIndex;return getSubstring(n,t,r,t.length),n});function getToken(t,e){var n,r,i,s,a=e[2],o=e[3],u=e[5];e[4]&&l?(r=u,n=l):a?(r=a,n=c):i=o&&l?o:e[1]||e[0],n&&(function assertPassesPrecheck(t,e,n){if(t&&!t(e,n))throw new TypeError("Invalid token "+(e||n)+" in format string")}(f,a,u),s=function(t,e){return n(t,r,e)}),t.push(s||function getLiteral(t){return function(){return t}}(i))}function getSubstring(t,e,n,r){if(ni(e).getTime()-(n||0)}}),t.exports=r.Date.isAfter},function(t,e,n){"use strict";var r=n(0),i=n(27);r.Date.defineInstance({isBefore:function(t,e,n){return t.getTime()=this.start&&t.start<=this.end&&t.end>=this.start&&t.end<=this.end:t>=this.start&&t<=this.end)}})},function(t,e,n){"use strict";n(30)},function(t,e,n){"use strict";var a=n(125),r=n(72),i=n(19),o=n(26),u=n(32),c=n(73),l=n(68),f=n(21);t.exports=function buildDateRangeUnits(){var s={};u(r.split("|"),function(t,e){var n,r,i=t+"s";r=e<4?function(){return c(this,t,!0)}:(n=a[l(i)],function(){return o((this.end-this.start)/n)}),s[i]=r}),f(i,s)}},function(t,e,n){"use strict";var r=n(401),i=n(122);t.exports=function isValidRangeMember(t){var e=i(t);return(!!e||0===e)&&r(t)}},function(t,e,n){"use strict";t.exports=function valueIsNotInfinite(t){return t!==-1/0&&t!==1/0}},function(t,e,n){"use strict";var r=n(102);t.exports=function incrementNumber(t,e,n){return r(t+e,n)}},function(t,e,n){"use strict";var r=n(101);t.exports=function incrementString(t,e){return r(t.charCodeAt(0)+e)}},function(t,e,n){"use strict";var r=n(15),i=n(405),s=r.max;t.exports=function getGreaterPrecision(t,e){return s(i(t),i(e))}},function(t,e,n){"use strict";var r=n(406);t.exports=function getPrecision(t){var e=r(t.toString());return e[1]?e[1].length:0}},function(t,e,n){"use strict";var r=n(51).HALF_WIDTH_PERIOD;t.exports=function periodSplit(t){return t.split(r)}},function(t,e,n){"use strict";var r=n(19),i=n(73);n(21)(r,{every:function(t,e){return i(this,t,!1,e)}})},function(t,e,n){"use strict";n(30)},function(t,e,n){"use strict";var r=n(19);n(21)(r,{intersect:function(t){return t.start>this.end||t.endt.start?this.start:t.start,this.endt.end?this.end:t.end)}})},function(t,e,n){"use strict";n(30)},function(t,e,n){"use strict";n(30)},function(t,e,n){"use strict";n(423),n(424),n(425),n(426),n(427),n(428),n(429),n(430),n(431),n(432),n(433),n(434),n(435),n(436),n(437),n(438),n(439),t.exports=n(0)},function(t,e,n){"use strict";n(11)("ca",{plural:!0,units:"milisegon:|s,segon:|s,minut:|s,hor:a|es,di:a|es,setman:a|es,mes:|os,any:|s",months:"gen:er|,febr:er|,mar:ç|,abr:il|,mai:g|,jun:y|,jul:iol|,ag:ost|,set:embre|,oct:ubre|,nov:embre|,des:embre|",weekdays:"diumenge|dg,dilluns|dl,dimarts|dt,dimecres|dc,dijous|dj,divendres|dv,dissabte|ds",numerals:"zero,un,dos,tres,quatre,cinc,sis,set,vuit,nou,deu",tokens:"el,la,de",short:"{dd}/{MM}/{yyyy}",medium:"{d} {month} {yyyy}",long:"{d} {month} {yyyy} {time}",full:"{weekday} {d} {month} {yyyy} {time}",stamp:"{dow} {d} {mon} {yyyy} {time}",time:"{H}:{mm}",past:"{sign} {num} {unit}",future:"{sign} {num} {unit}",duration:"{num} {unit}",timeMarkers:"a las",ampm:"am,pm",modifiers:[{name:"day",src:"abans d'ahir",value:-2},{name:"day",src:"ahir",value:-1},{name:"day",src:"avui",value:0},{name:"day",src:"demà|dema",value:1},{name:"sign",src:"fa",value:-1},{name:"sign",src:"en",value:1},{name:"shift",src:"passat",value:-1},{name:"shift",src:"el proper|la propera",value:1}],parse:["{sign} {num} {unit}","{num} {unit} {sign}","{0?}{1?} {unit:5-7} {shift}","{0?}{1?} {shift} {unit:5-7}"],timeParse:["{shift} {weekday}","{weekday} {shift}","{date?} {2?} {months}\\.? {2?} {year?}"]})},function(t,e,n){"use strict";n(11)("da",{plural:!0,units:"millisekund:|er,sekund:|er,minut:|ter,tim:e|er,dag:|e,ug:e|er|en,mÃĨned:|er|en+maaned:|er|en,ÃĨr:||et+aar:||et",months:"jan:uar|,feb:ruar|,mar:ts|,apr:il|,maj,jun:i|,jul:i|,aug:ust|,sep:tember|,okt:ober|,nov:ember|,dec:ember|",weekdays:"søn:dag|+son:dag|,man:dag|,tir:sdag|,ons:dag|,tor:sdag|,fre:dag|,lør:dag|+lor:dag|",numerals:"nul,en|et,to,tre,fire,fem,seks,syv,otte,ni,ti",tokens:"den,for",articles:"den",short:"{dd}-{MM}-{yyyy}",medium:"{d}. {month} {yyyy}",long:"{d}. {month} {yyyy} {time}",full:"{weekday} d. {d}. {month} {yyyy} {time}",stamp:"{dow} {d} {mon} {yyyy} {time}",time:"{H}:{mm}",past:"{num} {unit} {sign}",future:"{sign} {num} {unit}",duration:"{num} {unit}",ampm:"am,pm",modifiers:[{name:"day",src:"forgÃĨrs|i forgÃĨrs|forgaars|i forgaars",value:-2},{name:"day",src:"i gÃĨr|igÃĨr|i gaar|igaar",value:-1},{name:"day",src:"i dag|idag",value:0},{name:"day",src:"i morgen|imorgen",value:1},{name:"day",src:"over morgon|overmorgen|i over morgen|i overmorgen|iovermorgen",value:2},{name:"sign",src:"siden",value:-1},{name:"sign",src:"om",value:1},{name:"shift",src:"i sidste|sidste",value:-1},{name:"shift",src:"denne",value:0},{name:"shift",src:"nÃĻste|naeste",value:1}],parse:["{months} {year?}","{num} {unit} {sign}","{sign} {num} {unit}","{1?} {num} {unit} {sign}","{shift} {unit:5-7}"],timeParse:["{day|weekday}","{date} {months?}\\.? {year?}"],timeFrontParse:["{shift} {weekday}","{0?} {weekday?},? {date}\\.? {months?}\\.? {year?}"]})},function(t,e,n){"use strict";n(11)("de",{plural:!0,units:"Millisekunde:|n,Sekunde:|n,Minute:|n,Stunde:|n,Tag:|en,Woche:|n,Monat:|en,Jahr:|en|e",months:"Jan:uar|,Feb:ruar|,M:är|ärz|ar|arz,Apr:il|,Mai,Juni,Juli,Aug:ust|,Sept:ember|,Okt:ober|,Nov:ember|,Dez:ember|",weekdays:"So:nntag|,Mo:ntag|,Di:enstag|,Mi:ttwoch|,Do:nnerstag|,Fr:eitag|,Sa:mstag|",numerals:"null,ein:|e|er|en|em,zwei,drei,vier,fuenf,sechs,sieben,acht,neun,zehn",tokens:"der",short:"{dd}.{MM}.{yyyy}",medium:"{d}. {Month} {yyyy}",long:"{d}. {Month} {yyyy} {time}",full:"{Weekday}, {d}. {Month} {yyyy} {time}",stamp:"{Dow} {d} {Mon} {yyyy} {time}",time:"{H}:{mm}",past:"{sign} {num} {unit}",future:"{sign} {num} {unit}",duration:"{num} {unit}",timeMarkers:"um",ampm:"am,pm",modifiers:[{name:"day",src:"vorgestern",value:-2},{name:"day",src:"gestern",value:-1},{name:"day",src:"heute",value:0},{name:"day",src:"morgen",value:1},{name:"day",src:"Ãŧbermorgen|ubermorgen|uebermorgen",value:2},{name:"sign",src:"vor:|her",value:-1},{name:"sign",src:"in",value:1},{name:"shift",src:"letzte:|r|n|s",value:-1},{name:"shift",src:"nächste:|r|n|s+nachste:|r|n|s+naechste:|r|n|s+kommende:n|r",value:1}],parse:["{months} {year?}","{sign} {num} {unit}","{num} {unit} {sign}","{shift} {unit:5-7}"],timeParse:["{shift?} {day|weekday}","{weekday?},? {date}\\.? {months?}\\.? {year?}"],timeFrontParse:["{shift} {weekday}","{weekday?},? {date}\\.? {months?}\\.? {year?}"]})},function(t,e,n){"use strict";n(11)("es",{plural:!0,units:"milisegundo:|s,segundo:|s,minuto:|s,hora:|s,día|días|dia|dias,semana:|s,mes:|es,aÃąo|aÃąos|ano|anos",months:"ene:ro|,feb:rero|,mar:zo|,abr:il|,may:o|,jun:io|,jul:io|,ago:sto|,sep:tiembre|,oct:ubre|,nov:iembre|,dic:iembre|",weekdays:"dom:ingo|,lun:es|,mar:tes|,miÊ:rcoles|+mie:rcoles|,jue:ves|,vie:rnes|,sÃĄb:ado|+sab:ado|",numerals:"cero,uno,dos,tres,cuatro,cinco,seis,siete,ocho,nueve,diez",tokens:"el,la,de",short:"{dd}/{MM}/{yyyy}",medium:"{d} de {Month} de {yyyy}",long:"{d} de {Month} de {yyyy} {time}",full:"{weekday}, {d} de {month} de {yyyy} {time}",stamp:"{dow} {d} {mon} {yyyy} {time}",time:"{H}:{mm}",past:"{sign} {num} {unit}",future:"{sign} {num} {unit}",duration:"{num} {unit}",timeMarkers:"a las",ampm:"am,pm",modifiers:[{name:"day",src:"anteayer",value:-2},{name:"day",src:"ayer",value:-1},{name:"day",src:"hoy",value:0},{name:"day",src:"maÃąana|manana",value:1},{name:"sign",src:"hace",value:-1},{name:"sign",src:"dentro de",value:1},{name:"shift",src:"pasad:o|a",value:-1},{name:"shift",src:"prÃŗximo|prÃŗxima|proximo|proxima",value:1}],parse:["{months} {2?} {year?}","{sign} {num} {unit}","{num} {unit} {sign}","{0?}{1?} {unit:5-7} {shift}","{0?}{1?} {shift} {unit:5-7}"],timeParse:["{shift?} {day|weekday} {shift?}","{date} {2?} {months?}\\.? {2?} {year?}"],timeFrontParse:["{shift?} {weekday} {shift?}","{date} {2?} {months?}\\.? {2?} {year?}"]})},function(t,e,n){"use strict";n(11)("fi",{plural:!0,units:"millisekun:ti|tia|nin|teja|tina,sekun:ti|tia|nin|teja|tina,minuut:ti|tia|in|teja|tina,tun:ti|tia|nin|teja|tina,päiv:ä|ää|än|iä|änä,viik:ko|koa|on|olla|koja|kona,kuukau:si|tta|den+kuussa,vuo:si|tta|den|sia|tena|nna",months:"tammi:kuuta||kuu,helmi:kuuta||kuu,maalis:kuuta||kuu,huhti:kuuta||kuu,touko:kuuta||kuu,kesä:kuuta||kuu,heinä:kuuta||kuu,elo:kuuta||kuu,syys:kuuta||kuu,loka:kuuta||kuu,marras:kuuta||kuu,joulu:kuuta||kuu",weekdays:"su:nnuntai||nnuntaina,ma:anantai||anantaina,ti:istai||istaina,ke:skiviikko||skiviikkona,to:rstai||rstaina,pe:rjantai||rjantaina,la:uantai||uantaina",numerals:"nolla,yksi|ensimmäinen,kaksi|toinen,kolm:e|as,neljä:|s,vii:si|des,kuu:si|des,seitsemä:n|s,kahdeksa:n|s,yhdeksä:n|s,kymmene:n|s",short:"{d}.{M}.{yyyy}",medium:"{d}. {month} {yyyy}",long:"{d}. {month} {yyyy} klo {time}",full:"{weekday} {d}. {month} {yyyy} klo {time}",stamp:"{dow} {d} {mon} {yyyy} {time}",time:"{H}.{mm}",timeMarkers:"klo,kello",timeSeparator:".",ordinalSuffix:".",relative:function(e,n,t,r){var i=this.units;function numberWithUnit(t){return e+" "+i[8*t+n]}function baseUnit(){return numberWithUnit(1===e?0:1)}switch(r){case"duration":return baseUnit();case"past":return baseUnit()+" sitten";case"future":return numberWithUnit(2)+" kuluttua"}},modifiers:[{name:"day",src:"toissa päivänä",value:-2},{name:"day",src:"eilen|eilistä",value:-1},{name:"day",src:"tänään",value:0},{name:"day",src:"huomenna|huomista",value:1},{name:"day",src:"ylihuomenna|ylihuomista",value:2},{name:"sign",src:"sitten|aiemmin",value:-1},{name:"sign",src:"päästä|kuluttua|myÃļhemmin",value:1},{name:"edge",src:"lopussa",value:2},{name:"edge",src:"ensimmäinen|ensimmäisenä",value:-2},{name:"shift",src:"edel:linen|lisenä",value:-1},{name:"shift",src:"viime",value:-1},{name:"shift",src:"tä:llä|ssä|nä|mä",value:0},{name:"shift",src:"seuraava|seuraavana|tuleva|tulevana|ensi",value:1}],parse:["{months} {year?}","{shift} {unit:5-7}"],timeParse:["{shift?} {day|weekday}","{weekday?},? {date}\\.? {months?}\\.? {year?}"],timeFrontParse:["{shift?} {day|weekday}","{num?} {unit} {sign}","{weekday?},? {date}\\.? {months?}\\.? {year?}"]})},function(t,e,n){"use strict";n(11)("fr",{plural:!0,units:"milliseconde:|s,seconde:|s,minute:|s,heure:|s,jour:|s,semaine:|s,mois,an:|s|nÊe|nee",months:"janv:ier|,fÊvr:ier|+fevr:ier|,mars,avr:il|,mai,juin,juil:let|,aoÃģt,sept:embre|,oct:obre|,nov:embre|,dÊc:embre|+dec:embre|",weekdays:"dim:anche|,lun:di|,mar:di|,mer:credi|,jeu:di|,ven:dredi|,sam:edi|",numerals:"zÊro,un:|e,deux,trois,quatre,cinq,six,sept,huit,neuf,dix",tokens:"l'|la|le,er",short:"{dd}/{MM}/{yyyy}",medium:"{d} {month} {yyyy}",long:"{d} {month} {yyyy} {time}",full:"{weekday} {d} {month} {yyyy} {time}",stamp:"{dow} {d} {mon} {yyyy} {time}",time:"{H}:{mm}",past:"{sign} {num} {unit}",future:"{sign} {num} {unit}",duration:"{num} {unit}",timeMarkers:"à",ampm:"am,pm",modifiers:[{name:"day",src:"hier",value:-1},{name:"day",src:"aujourd'hui",value:0},{name:"day",src:"demain",value:1},{name:"sign",src:"il y a",value:-1},{name:"sign",src:"dans|d'ici",value:1},{name:"shift",src:"derni:èr|er|ère|ere",value:-1},{name:"shift",src:"prochain:|e",value:1}],parse:["{months} {year?}","{sign} {num} {unit}","{0?} {unit:5-7} {shift}"],timeParse:["{day|weekday} {shift?}","{weekday?},? {0?} {date}{1?} {months}\\.? {year?}"],timeFrontParse:["{0?} {weekday} {shift}","{weekday?},? {0?} {date}{1?} {months}\\.? {year?}"]})},function(t,e,n){"use strict";n(11)("it",{plural:!0,units:"millisecond:o|i,second:o|i,minut:o|i,or:a|e,giorn:o|i,settiman:a|e,mes:e|i,ann:o|i",months:"gen:naio|,feb:braio|,mar:zo|,apr:ile|,mag:gio|,giu:gno|,lug:lio|,ago:sto|,set:tembre|,ott:obre|,nov:embre|,dic:embre|",weekdays:"dom:enica|,lun:edÃŦ||edi,mar:tedÃŦ||tedi,mer:coledÃŦ||coledi,gio:vedÃŦ||vedi,ven:erdÃŦ||erdi,sab:ato|",numerals:"zero,un:|a|o|',due,tre,quattro,cinque,sei,sette,otto,nove,dieci",tokens:"l'|la|il",short:"{dd}/{MM}/{yyyy}",medium:"{d} {month} {yyyy}",long:"{d} {month} {yyyy} {time}",full:"{weekday}, {d} {month} {yyyy} {time}",stamp:"{dow} {d} {mon} {yyyy} {time}",time:"{H}:{mm}",past:"{num} {unit} {sign}",future:"{num} {unit} {sign}",duration:"{num} {unit}",timeMarkers:"alle",ampm:"am,pm",modifiers:[{name:"day",src:"ieri",value:-1},{name:"day",src:"oggi",value:0},{name:"day",src:"domani",value:1},{name:"day",src:"dopodomani",value:2},{name:"sign",src:"fa",value:-1},{name:"sign",src:"da adesso",value:1},{name:"shift",src:"scors:o|a",value:-1},{name:"shift",src:"prossim:o|a",value:1}],parse:["{months} {year?}","{num} {unit} {sign}","{0?} {unit:5-7} {shift}","{0?} {shift} {unit:5-7}"],timeParse:["{day|weekday} {shift?}","{weekday?},? {date} {months?}\\.? {year?}"],timeFrontParse:["{day|weekday} {shift?}","{weekday?},? {date} {months?}\\.? {year?}"]})},function(t,e,n){"use strict";n(11)("ja",{ampmFront:!0,numeralUnits:!0,allowsFullWidth:!0,timeMarkerOptional:!0,firstDayOfWeek:0,firstDayOfWeekYear:1,units:"ミãƒĒį§’,į§’,分,時間,æ—Ĩ,週間|週,ãƒļ月|ãƒĩ月|月,åš´|åš´åēĻ",weekdays:"æ—Ĩ:曜æ—Ĩ||曜,月:曜æ—Ĩ||曜,įĢ:曜æ—Ĩ||曜,æ°´:曜æ—Ĩ||曜,木:曜æ—Ĩ||曜,金:曜æ—Ĩ||曜,土:曜æ—Ĩ||曜",numerals:"〇,一,äēŒ,三,四,äē”,六,七,å…Ģ,䚝",placeholders:"十,į™ž,千,万",timeSuffixes:",į§’,分,時,æ—Ĩ,,月,åš´åēĻ?",short:"{yyyy}/{MM}/{dd}",medium:"{yyyy}åš´{M}月{d}æ—Ĩ",long:"{yyyy}åš´{M}月{d}æ—Ĩ{time}",full:"{yyyy}åš´{M}月{d}æ—Ĩ{time} {weekday}",stamp:"{yyyy}åš´{M}月{d}æ—Ĩ {H}:{mm} {dow}",time:"{tt}{h}時{mm}分",past:"{num}{unit}{sign}",future:"{num}{unit}{sign}",duration:"{num}{unit}",ampm:"午前,午垌",modifiers:[{name:"day",src:"一昨々æ—Ĩ|前々々æ—Ĩ",value:-3},{name:"day",src:"一昨æ—Ĩ|おととい|前々æ—Ĩ",value:-2},{name:"day",src:"昨æ—Ĩ|前æ—Ĩ",value:-1},{name:"day",src:"ä슿—Ĩ|åŊ“æ—Ĩ|æœŦæ—Ĩ",value:0},{name:"day",src:"明æ—Ĩ|įŋŒæ—Ĩ|æŦĄæ—Ĩ",value:1},{name:"day",src:"明垌æ—Ĩ|įŋŒã€…æ—Ĩ",value:2},{name:"day",src:"明々垌æ—Ĩ|įŋŒã€…々æ—Ĩ",value:3},{name:"sign",src:"前",value:-1},{name:"sign",src:"垌",value:1},{name:"edge",src:"始|初æ—Ĩ|é ­",value:-2},{name:"edge",src:"æœĢ|å°ģ",value:2},{name:"edge",src:"æœĢæ—Ĩ",value:1},{name:"shift",src:"一昨々|前々々",value:-3},{name:"shift",src:"一昨|前々|先々",value:-2},{name:"shift",src:"先|昨|åŽģ|前",value:-1},{name:"shift",src:"äģŠ|æœŦ|åŊ“",value:0},{name:"shift",src:"æĨ|明|įŋŒ|æŦĄ",value:1},{name:"shift",src:"明垌|įŋŒã€…|æŦĄã€…|再æĨ|さæĨ",value:2},{name:"shift",src:"明々垌|įŋŒã€…々",value:3}],parse:["{month}{edge}","{num}{unit}{sign}","{year?}{month}","{year}"],timeParse:["{day|weekday}","{shift}{unit:5}{weekday?}","{shift}{unit:7}{month}{edge}","{shift}{unit:7}{month?}{date?}","{shift}{unit:6}{edge?}{date?}","{year?}{month?}{date}"]})},function(t,e,n){"use strict";n(11)("ko",{ampmFront:!0,numeralUnits:!0,units:"밀ëĻŦ봈,봈,ëļ„,ė‹œę°„,ėŧ,ėŖŧ,ę°œė›”|ë‹Ŧ,년|해",weekdays:"ėŧ:ėš”ėŧ|,ė›”:ėš”ėŧ|,화:ėš”ėŧ|,눘:ėš”ėŧ|,ëĒŠ:ėš”ėŧ|,금:ėš”ėŧ|,토:ėš”ėŧ|",numerals:"똁|ė œëĄœ,ėŧ|한,ė´,ė‚ŧ,ė‚Ŧ,똤,ėœĄ,ėš ,팔,ęĩŦ,ė‹­",short:"{yyyy}.{MM}.{dd}",medium:"{yyyy}년 {M}ė›” {d}ėŧ",long:"{yyyy}년 {M}ė›” {d}ėŧ {time}",full:"{yyyy}년 {M}ė›” {d}ėŧ {weekday} {time}",stamp:"{yyyy}년 {M}ė›” {d}ėŧ {H}:{mm} {dow}",time:"{tt} {h}ė‹œ {mm}ëļ„",past:"{num}{unit} {sign}",future:"{num}{unit} {sign}",duration:"{num}{unit}",timeSuffixes:",봈,ëļ„,ė‹œ,ėŧ,,ė›”,년",ampm:"ė˜¤ė „,ė˜¤í›„",modifiers:[{name:"day",src:"掏렀ęģ˜",value:-2},{name:"day",src:"ė–´ė œ",value:-1},{name:"day",src:"ė˜¤ëŠ˜",value:0},{name:"day",src:"내ėŧ",value:1},{name:"day",src:"ëĒ¨ë ˆ",value:2},{name:"sign",src:"ė „",value:-1},{name:"sign",src:"후",value:1},{name:"shift",src:"ė§€ë‚œ|ėž‘",value:-1},{name:"shift",src:"ė´ë˛ˆ|ė˜Ŧ",value:0},{name:"shift",src:"ë‹¤ėŒ|내",value:1}],parse:["{num}{unit} {sign}","{shift?} {unit:5-7}","{year?} {month}","{year}"],timeParse:["{day|weekday}","{shift} {unit:5?} {weekday}","{year?} {month?} {date} {weekday?}"]})},function(t,e,n){"use strict";n(11)("nl",{plural:!0,units:"milliseconde:|n,seconde:|n,minu:ut|ten,uur,dag:|en,we:ek|ken,maand:|en,jaar",months:"jan:uari|,feb:ruari|,maart|mrt,apr:il|,mei,jun:i|,jul:i|,aug:ustus|,sep:tember|,okt:ober|,nov:ember|,dec:ember|",weekdays:"zondag|zo,maandag|ma,dinsdag|di,woensdag|wo|woe,donderdag|do,vrijdag|vr|vrij,zaterdag|za",numerals:"nul,een,twee,drie,vier,vijf,zes,zeven,acht,negen,tien",short:"{dd}-{MM}-{yyyy}",medium:"{d} {month} {yyyy}",long:"{d} {Month} {yyyy} {time}",full:"{weekday} {d} {Month} {yyyy} {time}",stamp:"{dow} {d} {Mon} {yyyy} {time}",time:"{H}:{mm}",past:"{num} {unit} {sign}",future:"{num} {unit} {sign}",duration:"{num} {unit}",timeMarkers:"'s,om",modifiers:[{name:"day",src:"gisteren",value:-1},{name:"day",src:"vandaag",value:0},{name:"day",src:"morgen",value:1},{name:"day",src:"overmorgen",value:2},{name:"sign",src:"geleden",value:-1},{name:"sign",src:"vanaf nu",value:1},{name:"shift",src:"laatste|vorige|afgelopen",value:-1},{name:"shift",src:"volgend:|e",value:1}],parse:["{months} {year?}","{num} {unit} {sign}","{0?} {unit:5-7} {shift}","{0?} {shift} {unit:5-7}"],timeParse:["{shift?} {day|weekday}","{weekday?},? {date} {months?}\\.? {year?}"],timeFrontParse:["{shift?} {day|weekday}","{weekday?},? {date} {months?}\\.? {year?}"]})},function(t,e,n){"use strict";n(11)("no",{plural:!0,units:"millisekund:|er,sekund:|er,minutt:|er,tim:e|er,dag:|er,uk:e|er|en,mÃĨned:|er|en+maaned:|er|en,ÃĨr:||et+aar:||et",months:"januar,februar,mars,april,mai,juni,juli,august,september,oktober,november,desember",weekdays:"søndag|sondag,mandag,tirsdag,onsdag,torsdag,fredag,lørdag|lordag",numerals:"en|et,to,tre,fire,fem,seks,sju|syv,ÃĨtte,ni,ti",tokens:"den,for",articles:"den",short:"d. {d}. {month} {yyyy}",long:"den {d}. {month} {yyyy} {H}:{mm}",full:"{Weekday} den {d}. {month} {yyyy} {H}:{mm}:{ss}",past:"{num} {unit} {sign}",future:"{sign} {num} {unit}",duration:"{num} {unit}",ampm:"am,pm",modifiers:[{name:"day",src:"forgÃĨrs|i forgÃĨrs|forgaars|i forgaars",value:-2},{name:"day",src:"i gÃĨr|igÃĨr|i gaar|igaar",value:-1},{name:"day",src:"i dag|idag",value:0},{name:"day",src:"i morgen|imorgen",value:1},{name:"day",src:"overimorgen|overmorgen|over i morgen",value:2},{name:"sign",src:"siden",value:-1},{name:"sign",src:"om",value:1},{name:"shift",src:"i siste|siste",value:-1},{name:"shift",src:"denne",value:0},{name:"shift",src:"neste",value:1}],parse:["{num} {unit} {sign}","{sign} {num} {unit}","{1?} {num} {unit} {sign}","{shift} {unit:5-7}"],timeParse:["{date} {month}","{shift} {weekday}","{0?} {weekday?},? {date?} {month}\\.? {year}"]})},function(t,e,n){"use strict";n(11)("pl",{plural:!0,units:"milisekund:a|y|,sekund:a|y|,minut:a|y|,godzin:a|y|,dzień|dni|dni,tydzień|tygodnie|tygodni,miesiąc|miesiące|miesięcy,rok|lata|lat",months:"sty:cznia||czeń,lut:ego||y,mar:ca||zec,kwi:etnia||ecień,maj:a|,cze:rwca||rwiec,lip:ca||iec,sie:rpnia||rpień,wrz:eśnia||esień,paÅē:dziernika||dziernik,lis:topada||topad,gru:dnia||dzień",weekdays:"nie:dziela||dzielę,pon:iedziałek|,wt:orek|,śr:oda||odę,czw:artek|,piątek|pt,sobota|sb|sobotę",numerals:"zero,jeden|jedną,dwa|dwie,trzy,cztery,pięć,sześć,siedem,osiem,dziewięć,dziesięć",tokens:"w|we,roku",short:"{dd}.{MM}.{yyyy}",medium:"{d} {month} {yyyy}",long:"{d} {month} {yyyy} {time}",full:"{weekday}, {d} {month} {yyyy} {time}",stamp:"{dow} {d} {mon} {yyyy} {time}",time:"{H}:{mm}",timeMarkers:"o",ampm:"am,pm",modifiers:[{name:"day",src:"przedwczoraj",value:-2},{name:"day",src:"wczoraj",value:-1},{name:"day",src:"dzisiaj|dziś",value:0},{name:"day",src:"jutro",value:1},{name:"day",src:"pojutrze",value:2},{name:"sign",src:"temu|przed",value:-1},{name:"sign",src:"za",value:1},{name:"shift",src:"zeszły|zeszła|ostatni|ostatnia",value:-1},{name:"shift",src:"następny|następna|następnego|przyszły|przyszła|przyszłego",value:1}],relative:function(t,e,n,r){var i;if(4===e){if(1===t&&"past"===r)return"wczoraj";if(1===t&&"future"===r)return"jutro";if(2===t&&"past"===r)return"przedwczoraj";if(2===t&&"future"===r)return"pojutrze"}var s=+t.toFixed(0).slice(-1),a=+t.toFixed(0).slice(-2);switch(!0){case 1===t:i=0;break;case 12<=a&&a<=14:i=2;break;case 2<=s&&s<=4:i=1;break;default:i=2}var o=this.units[8*i+e],u=t+" ";switch("past"!==r&&"future"!==r||1!==t||(o=o.replace(/a$/,"ę")),o=u+o,r){case"duration":return o;case"past":return o+" temu";case"future":return"za "+o}},parse:["{num} {unit} {sign}","{sign} {num} {unit}","{months} {year?}","{shift} {unit:5-7}","{0} {shift?} {weekday}"],timeFrontParse:["{day|weekday}","{date} {months} {year?} {1?}","{0?} {shift?} {weekday}"]})},function(t,e,n){"use strict";n(11)("pt",{plural:!0,units:"milisegundo:|s,segundo:|s,minuto:|s,hora:|s,dia:|s,semana:|s,mÃĒs|mÃĒses|mes|meses,ano:|s",months:"jan:eiro|,fev:ereiro|,mar:ço|,abr:il|,mai:o|,jun:ho|,jul:ho|,ago:sto|,set:embro|,out:ubro|,nov:embro|,dez:embro|",weekdays:"dom:ingo|,seg:unda-feira|,ter:ça-feira|,qua:rta-feira|,qui:nta-feira|,sex:ta-feira|,sÃĄb:ado||ado",numerals:"zero,um:|a,dois|duas,trÃĒs|tres,quatro,cinco,seis,sete,oito,nove,dez",tokens:"a,de",short:"{dd}/{MM}/{yyyy}",medium:"{d} de {Month} de {yyyy}",long:"{d} de {Month} de {yyyy} {time}",full:"{Weekday}, {d} de {Month} de {yyyy} {time}",stamp:"{Dow} {d} {Mon} {yyyy} {time}",time:"{H}:{mm}",past:"{num} {unit} {sign}",future:"{sign} {num} {unit}",duration:"{num} {unit}",timeMarkers:"às",ampm:"am,pm",modifiers:[{name:"day",src:"anteontem",value:-2},{name:"day",src:"ontem",value:-1},{name:"day",src:"hoje",value:0},{name:"day",src:"amanh:ÃŖ|a",value:1},{name:"sign",src:"atrÃĄs|atras|hÃĄ|ha",value:-1},{name:"sign",src:"daqui a",value:1},{name:"shift",src:"passad:o|a",value:-1},{name:"shift",src:"prÃŗximo|prÃŗxima|proximo|proxima",value:1}],parse:["{months} {1?} {year?}","{num} {unit} {sign}","{sign} {num} {unit}","{0?} {unit:5-7} {shift}","{0?} {shift} {unit:5-7}"],timeParse:["{shift?} {day|weekday}","{0?} {shift} {weekday}","{date} {1?} {months?} {1?} {year?}"],timeFrontParse:["{shift?} {day|weekday}","{date} {1?} {months?} {1?} {year?}"]})},function(t,e,n){"use strict";n(11)("ru",{firstDayOfWeekYear:1,units:"ĐŧиĐģĐģĐ¸ŅĐĩĐē҃ĐŊĐ´:а|҃|Ņ‹|,ҁĐĩĐē҃ĐŊĐ´:а|҃|Ņ‹|,ĐŧиĐŊŅƒŅ‚:а|҃|Ņ‹|,Ņ‡Đ°Ņ:||а|Ов,Đ´ĐĩĐŊҌ|Đ´ĐĩĐŊҌ|Đ´ĐŊŅ|Đ´ĐŊĐĩĐš,ĐŊĐĩĐ´ĐĩĐģ:Ņ|ŅŽ|и|Ҍ|Đĩ,ĐŧĐĩŅŅŅ†:||а|Đĩв|Đĩ,ĐŗĐžĐ´|ĐŗĐžĐ´|ĐŗĐžĐ´Đ°|ĐģĐĩŅ‚|ĐŗĐžĐ´Ņƒ",months:"ŅĐŊв:Đ°Ņ€Ņ||.|Đ°Ņ€ŅŒ,Ņ„Đĩв:Ņ€Đ°ĐģŅ||Ņ€.|Ņ€Đ°ĐģҌ,ĐŧĐ°Ņ€:Ņ‚Đ°||Ņ‚,аĐŋŅ€:ĐĩĐģŅ||.|ĐĩĐģҌ,ĐŧĐ°Ņ|ĐŧаК,Đ¸ŅŽĐŊ:Ņ||Ҍ,Đ¸ŅŽĐģ:Ņ||Ҍ,Đ°Đ˛Đŗ:ŅƒŅŅ‚Đ°||.|ŅƒŅŅ‚,ҁĐĩĐŊ:Ņ‚ŅĐąŅ€Ņ||Ņ‚.|Ņ‚ŅĐąŅ€ŅŒ,ĐžĐēŅ‚:ŅĐąŅ€Ņ||.|ŅĐąŅ€ŅŒ,ĐŊĐžŅ:ĐąŅ€Ņ||ĐąŅ€ŅŒ,Đ´ĐĩĐē:Đ°ĐąŅ€Ņ||.|Đ°ĐąŅ€ŅŒ",weekdays:"Đ˛ĐžŅĐēŅ€ĐĩҁĐĩĐŊҌĐĩ|Đ˛Ņ,ĐŋĐžĐŊĐĩĐ´ĐĩĐģҌĐŊиĐē|ĐŋĐŊ,Đ˛Ņ‚ĐžŅ€ĐŊиĐē|Đ˛Ņ‚,ҁҀĐĩда|ҁҀ,҇ĐĩŅ‚Đ˛ĐĩŅ€Đŗ|҇҂,ĐŋŅŅ‚ĐŊĐ¸Ņ†Đ°|ĐŋŅ‚,ŅŅƒĐąĐąĐžŅ‚Đ°|ŅĐą",numerals:"ĐŊĐžĐģҌ,Од:иĐŊ|ĐŊ҃,дв:а|Đĩ,Ņ‚Ņ€Đ¸,҇Đĩ҂ҋҀĐĩ,ĐŋŅŅ‚ŅŒ,҈ĐĩŅŅ‚ŅŒ,ҁĐĩĐŧҌ,Đ˛ĐžŅĐĩĐŧҌ,Đ´ĐĩĐ˛ŅŅ‚ŅŒ,Đ´ĐĩŅŅŅ‚ŅŒ",tokens:"в|ĐŊа,Đŗ\\.?(?:Ода)?",short:"{dd}.{MM}.{yyyy}",medium:"{d} {month} {yyyy} Đŗ.",long:"{d} {month} {yyyy} Đŗ., {time}",full:"{weekday}, {d} {month} {yyyy} Đŗ., {time}",stamp:"{dow} {d} {mon} {yyyy} {time}",time:"{H}:{mm}",timeMarkers:"в",ampm:" ŅƒŅ‚Ņ€Đ°, вĐĩ҇ĐĩŅ€Đ°",modifiers:[{name:"day",src:"ĐŋĐžĐˇĐ°Đ˛Ņ‡ĐĩŅ€Đ°",value:-2},{name:"day",src:"Đ˛Ņ‡ĐĩŅ€Đ°",value:-1},{name:"day",src:"ҁĐĩĐŗĐžĐ´ĐŊŅ",value:0},{name:"day",src:"ĐˇĐ°Đ˛Ņ‚Ņ€Đ°",value:1},{name:"day",src:"ĐŋĐžŅĐģĐĩĐˇĐ°Đ˛Ņ‚Ņ€Đ°",value:2},{name:"sign",src:"ĐŊаСад",value:-1},{name:"sign",src:"҇ĐĩŅ€ĐĩС",value:1},{name:"shift",src:"ĐŋŅ€ĐžŅˆĐģ:Ņ‹Đš|ОК|ĐžĐŧ",value:-1},{name:"shift",src:"ҁĐģĐĩĐ´ŅƒŅŽŅ‰:иК|ĐĩĐš|ĐĩĐŧ",value:1}],relative:function(t,e,n,r){var i,s,a=t.toString().slice(-1);switch(!0){case 11<=t&&t<=15:s=3;break;case 1==a:s=1;break;case 2<=a&&a<=4:s=2;break;default:s=3}switch(i=t+" "+this.units[8*s+e],r){case"duration":return i;case"past":return i+" ĐŊаСад";case"future":return"҇ĐĩŅ€ĐĩС "+i}},parse:["{num} {unit} {sign}","{sign} {num} {unit}","{months} {year?}","{0?} {shift} {unit:5-7}"],timeParse:["{day|weekday}","{0?} {shift} {weekday}","{date} {months?} {year?} {1?}"],timeFrontParse:["{0?} {shift} {weekday}","{date} {months?} {year?} {1?}"]})},function(t,e,n){"use strict";n(11)("sv",{plural:!0,units:"millisekund:|er,sekund:|er,minut:|er,timm:e|ar,dag:|ar,veck:a|or|an,mÃĨnad:|er|en+manad:|er|en,ÃĨr:||et+ar:||et",months:"jan:uari|,feb:ruari|,mar:s|,apr:il|,maj,jun:i|,jul:i|,aug:usti|,sep:tember|,okt:ober|,nov:ember|,dec:ember|",weekdays:"sÃļn:dag|+son:dag|,mÃĨn:dag||dagen+man:dag||dagen,tis:dag|,ons:dag|,tor:sdag|,fre:dag|,lÃļr:dag||dag",numerals:"noll,en|ett,tvÃĨ|tva,tre,fyra,fem,sex,sju,ÃĨtta|atta,nio,tio",tokens:"den,fÃļr|for",articles:"den",short:"{yyyy}-{MM}-{dd}",medium:"{d} {month} {yyyy}",long:"{d} {month} {yyyy} {time}",full:"{weekday} {d} {month} {yyyy} {time}",stamp:"{dow} {d} {mon} {yyyy} {time}",time:"{H}:{mm}",past:"{num} {unit} {sign}",future:"{sign} {num} {unit}",duration:"{num} {unit}",ampm:"am,pm",modifiers:[{name:"day",src:"fÃļrrgÃĨr|i fÃļrrgÃĨr|ifÃļrrgÃĨr|forrgar|i forrgar|iforrgar",value:-2},{name:"day",src:"gÃĨr|i gÃĨr|igÃĨr|gar|i gar|igar",value:-1},{name:"day",src:"dag|i dag|idag",value:0},{name:"day",src:"morgon|i morgon|imorgon",value:1},{name:"day",src:"Ãļver morgon|Ãļvermorgon|i Ãļver morgon|i Ãļvermorgon|iÃļvermorgon|over morgon|overmorgon|i over morgon|i overmorgon|iovermorgon",value:2},{name:"sign",src:"sedan|sen",value:-1},{name:"sign",src:"om",value:1},{name:"shift",src:"i fÃļrra|fÃļrra|i forra|forra",value:-1},{name:"shift",src:"denna",value:0},{name:"shift",src:"nästa|nasta",value:1}],parse:["{months} {year?}","{num} {unit} {sign}","{sign} {num} {unit}","{1?} {num} {unit} {sign}","{shift} {unit:5-7}"],timeParse:["{day|weekday}","{shift} {weekday}","{0?} {weekday?},? {date} {months?}\\.? {year?}"],timeFrontParse:["{day|weekday}","{shift} {weekday}","{0?} {weekday?},? {date} {months?}\\.? {year?}"]})},function(t,e,n){"use strict";n(11)("zh-CN",{ampmFront:!0,numeralUnits:!0,allowsFullWidth:!0,timeMarkerOptional:!0,units:"æ¯Ģį§’,į§’é’Ÿ,分钟,小æ—ļ,夊,ä¸Ē星期|周,ä¸Ē月,åš´",weekdays:"星期æ—Ĩ|æ—Ĩ|周æ—Ĩ|星期夊,星期一|一|周一,星期äēŒ|äēŒ|周äēŒ,星期三|三|周三,星期四|四|周四,星期äē”|äē”|周äē”,星期六|六|周六",numerals:"〇,一,äēŒ,三,四,äē”,六,七,å…Ģ,䚝",placeholders:"十,į™ž,千,万",short:"{yyyy}-{MM}-{dd}",medium:"{yyyy}åš´{M}月{d}æ—Ĩ",long:"{yyyy}åš´{M}月{d}æ—Ĩ{time}",full:"{yyyy}åš´{M}月{d}æ—Ĩ{weekday}{time}",stamp:"{yyyy}åš´{M}月{d}æ—Ĩ{H}:{mm}{dow}",time:"{tt}{h}į‚š{mm}分",past:"{num}{unit}{sign}",future:"{num}{unit}{sign}",duration:"{num}{unit}",timeSuffixes:",į§’,分钟?,į‚š|æ—ļ,æ—Ĩ|åˇ,,月,åš´",ampm:"上午,下午",modifiers:[{name:"day",src:"大前夊",value:-3},{name:"day",src:"前夊",value:-2},{name:"day",src:"昨夊",value:-1},{name:"day",src:"äģŠå¤Š",value:0},{name:"day",src:"明夊",value:1},{name:"day",src:"后夊",value:2},{name:"day",src:"大后夊",value:3},{name:"sign",src:"前",value:-1},{name:"sign",src:"后",value:1},{name:"shift",src:"上|åŽģ",value:-1},{name:"shift",src:"čŋ™",value:0},{name:"shift",src:"下|明",value:1}],parse:["{num}{unit}{sign}","{shift}{unit:5-7}","{year?}{month}","{year}"],timeParse:["{day|weekday}","{shift}{weekday}","{year?}{month?}{date}"]})},function(t,e,n){"use strict";n(11)("zh-TW",{ampmFront:!0,numeralUnits:!0,allowsFullWidth:!0,timeMarkerOptional:!0,units:"æ¯Ģį§’,į§’é˜,分鐘,小時,夊,個星期|週,個月,åš´",weekdays:"星期æ—Ĩ|æ—Ĩ|週æ—Ĩ|星期夊,星期一|一|週一,星期äēŒ|äēŒ|週äēŒ,星期三|三|週三,星期四|四|é€ąå››,星期äē”|äē”|週äē”,星期六|六|é€ąå…­",numerals:"〇,一,äēŒ,三,四,äē”,六,七,å…Ģ,䚝",placeholders:"十,į™ž,千,万",short:"{yyyy}/{MM}/{dd}",medium:"{yyyy}åš´{M}月{d}æ—Ĩ",long:"{yyyy}åš´{M}月{d}æ—Ĩ{time}",full:"{yyyy}åš´{M}月{d}æ—Ĩ{weekday}{time}",stamp:"{yyyy}åš´{M}月{d}æ—Ĩ{H}:{mm}{dow}",time:"{tt}{h}éģž{mm}分",past:"{num}{unit}{sign}",future:"{num}{unit}{sign}",duration:"{num}{unit}",timeSuffixes:",į§’,分鐘?,éģž|時,æ—Ĩ|號,,月,åš´",ampm:"上午,下午",modifiers:[{name:"day",src:"大前夊",value:-3},{name:"day",src:"前夊",value:-2},{name:"day",src:"昨夊",value:-1},{name:"day",src:"äģŠå¤Š",value:0},{name:"day",src:"明夊",value:1},{name:"day",src:"垌夊",value:2},{name:"day",src:"大垌夊",value:3},{name:"sign",src:"前",value:-1},{name:"sign",src:"垌",value:1},{name:"shift",src:"上|åŽģ",value:-1},{name:"shift",src:"這",value:0},{name:"shift",src:"下|明",value:1}],parse:["{num}{unit}{sign}","{shift}{unit:5-7}","{year?}{month}","{year}"],timeParse:["{day|weekday}","{shift}{weekday}","{year?}{month?}{date}"]})}])}); \ No newline at end of file diff --git a/src/static/tablefilter/tf-1-2aa33b10e0e549020c12.js b/src/static/tablefilter/tf-1-2aa33b10e0e549020c12.js new file mode 100644 index 0000000000000000000000000000000000000000..305abc23bebd90bf885f3f1e7abf0b3cc757830d --- /dev/null +++ b/src/static/tablefilter/tf-1-2aa33b10e0e549020c12.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[1],{440:function(t,e,n){var r={"./array":20,"./array.js":20,"./const":4,"./const.js":4,"./cookie":60,"./cookie.js":60,"./dom":2,"./dom.js":2,"./emitter":89,"./emitter.js":89,"./event":5,"./event.js":5,"./extensions/advancedGrid/adapterEzEditTable":441,"./extensions/advancedGrid/adapterEzEditTable.js":441,"./extensions/advancedGrid/advancedGrid":443,"./extensions/advancedGrid/advancedGrid.js":443,"./extensions/colOps/colOps":444,"./extensions/colOps/colOps.js":444,"./extensions/colsVisibility/colsVisibility":445,"./extensions/colsVisibility/colsVisibility.js":445,"./extensions/filtersVisibility/filtersVisibility":446,"./extensions/filtersVisibility/filtersVisibility.js":446,"./extensions/sort/adapterSortabletable":442,"./extensions/sort/adapterSortabletable.js":442,"./extensions/sort/sort":447,"./extensions/sort/sort.js":447,"./feature":10,"./feature.js":10,"./modules/alternateRows":85,"./modules/alternateRows.js":85,"./modules/baseDropdown":48,"./modules/baseDropdown.js":48,"./modules/checkList":91,"./modules/checkList.js":91,"./modules/clearButton":84,"./modules/clearButton.js":84,"./modules/dateType":74,"./modules/dateType.js":74,"./modules/dropdown":90,"./modules/dropdown.js":90,"./modules/gridLayout":77,"./modules/gridLayout.js":77,"./modules/hash":92,"./modules/hash.js":92,"./modules/help":75,"./modules/help.js":75,"./modules/highlightKeywords":79,"./modules/highlightKeywords.js":79,"./modules/loader":78,"./modules/loader.js":78,"./modules/markActiveColumns":81,"./modules/markActiveColumns.js":81,"./modules/noResults":86,"./modules/noResults.js":86,"./modules/paging":87,"./modules/paging.js":87,"./modules/popupFilter":80,"./modules/popupFilter.js":80,"./modules/rowsCounter":82,"./modules/rowsCounter.js":82,"./modules/state":76,"./modules/state.js":76,"./modules/statusBar":83,"./modules/statusBar.js":83,"./modules/storage":93,"./modules/storage.js":93,"./modules/toolbar":18,"./modules/toolbar.js":18,"./number":22,"./number.js":22,"./root":9,"./root.js":9,"./settings":1,"./settings.js":1,"./sort":31,"./sort.js":31,"./string":8,"./string.js":8,"./tablefilter":127,"./tablefilter.js":127,"./types":3,"./types.js":3};function webpackContext(t){var e=webpackContextResolve(t);return n(e)}function webpackContextResolve(t){if(n.o(r,t))return r[t];var e=new Error("Cannot find module '"+t+"'");throw e.code="MODULE_NOT_FOUND",e}webpackContext.keys=function webpackContextKeys(){return Object.keys(r)},webpackContext.resolve=webpackContextResolve,(t.exports=webpackContext).id=440},441:function(t,e,n){"use strict";n.r(e),n.d(e,"default",function(){return r});var o=n(10),f=n(2),i=n(4),s=n(1),a=n(9);function _typeof(t){return(_typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function _typeof(t){return typeof t}:function _typeof(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function _defineProperties(t,e){for(var n=0;nm)if(a.rowIndex>=n[r-1])e=n[r-1];else{var h=y+f;e=r-1o[s-1]&&ao[0]&&r.setPage("previous")}};if(b.paging&&(b.feature("paging").onAfterChangePage=function(t){var e=t.tf.extension("advancedGrid")._ezEditTable.Selection,n=e.GetActiveRow();n&&n.scrollIntoView(!1);var r=e.GetActiveCell();r&&r.scrollIntoView(!1)}),"row"===e.default_selection){var s=e.on_before_selected_row;e.on_before_selected_row=function(){var t=arguments;i(t[0],t[1]),s&&s.call(null,t[0],t[1],t[2])};var a=e.on_after_selected_row;e.on_after_selected_row=function(){var t=arguments;o(t[0],t[1],t[2]),a&&a.call(null,t[0],t[1],t[2])}}else{var l=e.on_before_selected_cell;e.on_before_selected_cell=function(){var t=arguments;i(t[0],t[1]),l&&l.call(null,t[0],t[1],t[2])};var c=e.on_after_selected_cell;e.on_after_selected_cell=function(){var t=arguments;o(t[0],t[1],t[2]),c&&c.call(null,t[0],t[1],t[2])}}}if(n){var u=e.on_added_dom_row;if(e.on_added_dom_row=function(){var t=arguments;b.nbFilterableRows++,b.paging?(b.nbFilterableRows++,b.paging=!1,b.feature("paging").destroy(),b.feature("paging").reset()):b.emitter.emit("rows-changed",b,this),b.alternateRows&&b.feature("alternateRows").init(),u&&u.call(null,t[0],t[1],t[2])},e.actions&&e.actions.delete){var d=e.actions.delete.on_after_submit;e.actions.delete.on_after_submit=function(){var t=arguments;b.nbFilterableRows--,b.paging?(b.nbFilterableRows--,b.paging=!1,b.feature("paging").destroy(),b.feature("paging").reset(!1)):b.emitter.emit("rows-changed",b,this),b.alternateRows&&b.feature("alternateRows").init(),d&&d.call(null,t[0],t[1])}}}try{this._ezEditTable=new EditTable(b.id,e,t),this._ezEditTable.Init()}catch(t){throw new Error('Failed to instantiate EditTable object.\n \n"ezEditTable" dependency not found.')}this.initialized=!0}},{key:"reset",value:function reset(){var t=this._ezEditTable;t&&(this.cfg.selection&&t.Selection.Set(),this.cfg.editable&&t.Editable.Set())}},{key:"toggle",value:function toggle(){var t=this._ezEditTable;t.editable?t.Editable.Remove():t.Editable.Set(),t.selection?t.Selection.Remove():t.Selection.Set()}},{key:"_toggleForInputFilter",value:function _toggleForInputFilter(){var t=this.tf;if(t.getActiveFilterId()){var e=t.getColumnIndexFromFilterId(t.getActiveFilterId());t.getFilterType(e)===i.INPUT&&this.toggle()}}},{key:"destroy",value:function destroy(){var t=this;if(this.initialized){var e=this._ezEditTable;e&&(this.cfg.selection&&(e.Selection.ClearSelections(),e.Selection.Remove()),this.cfg.editable&&e.Editable.Remove()),this.emitter.off(["filter-focus","filter-blur"],function(){return t._toggleForInputFilter()}),this.initialized=!1}}}]),AdapterEzEditTable}();r.meta={altName:"advancedGrid"}},442:function(t,e,n){"use strict";n.r(e),n.d(e,"default",function(){return r});var o=n(10),a=n(3),c=n(2),u=n(5),i=n(22),d=n(4),s=n(1);function _typeof(t){return(_typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function _typeof(t){return typeof t}:function _typeof(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function _defineProperties(t,e){for(var n=0;n',n.icnCollapseHtml='Collapse filters',n.defaultText="Toggle filters",n.targetId=e.target_id||null,n.enableIcon=Object(l.defaultsBool)(e.enable_icon,!0),n.btnText=Object(l.defaultsStr)(e.btn_text,""),n.collapseBtnHtml=n.enableIcon?n.icnCollapseHtml+n.btnText:n.btnText||n.defaultText,n.expandBtnHtml=n.enableIcon?n.icnExpandHtml+n.btnText:n.btnText||n.defaultText,n.btnHtml=Object(l.defaultsStr)(e.btn_html,null),n.btnCssClass=Object(l.defaultsStr)(e.btn_css_class,"btnExpClpFlt"),n.contCssClass=Object(l.defaultsStr)(e.cont_css_class,"expClpFlt"),n.filtersRowIndex=Object(l.defaultsNb)(e.filters_row_index,t.getFiltersRowIndex()),n.visibleAtStart=Object(l.defaultsNb)(e.visible_at_start,!0),n.toolbarPosition=Object(l.defaultsStr)(e.toolbar_position,c.RIGHT),n.onBeforeShow=Object(l.defaultsFn)(e.on_before_show,i.EMPTY_FN),n.onAfterShow=Object(l.defaultsFn)(e.on_after_show,i.EMPTY_FN),n.onBeforeHide=Object(l.defaultsFn)(e.on_before_hide,i.EMPTY_FN),n.onAfterHide=Object(l.defaultsFn)(e.on_after_hide,i.EMPTY_FN),t.import(e.name+"Style",t.getStylePath()+n.stylesheet,null,"link"),n.enable(),n}return function _createClass(t,e,n){return e&&_defineProperties(t.prototype,e),n&&_defineProperties(t,n),t}(FiltersVisibility,[{key:"init",value:function init(){var n=this;this.initialized||(this.emitter.emit("initializing-extension",this,!Object(i.isNull)(this.targetId)),this.buildUI(),this.initialized=!0,this.emitter.on(["show-filters"],function(t,e){return n.show(e)}),this.emitter.emit("filters-visibility-initialized",this.tf,this),this.emitter.emit("extension-initialized",this))}},{key:"buildUI",value:function buildUI(){var t=this,e=this.tf,n=Object(s.createElm)("span");n.className=this.contCssClass;var r,o=this.targetId?Object(s.elm)(this.targetId):e.feature("toolbar").container(this.toolbarPosition);if(this.targetId)o.appendChild(n);else{var i=o.firstChild;i.parentNode.insertBefore(n,i)}this.btnHtml?(n.innerHTML=this.btnHtml,r=n.firstChild):((r=Object(s.createElm)("a",["href","javascript:void(0);"])).className=this.btnCssClass,r.title=this.btnText||this.defaultText,r.innerHTML=this.collapseBtnHtml,n.appendChild(r)),Object(a.addEvt)(r,"click",function(){return t.toggle()}),this.contEl=n,this.btnEl=r,this.visibleAtStart||this.toggle()}},{key:"toggle",value:function toggle(){var t=this.tf,e=""===(t.gridLayout?t.feature("gridLayout").headTbl:t.dom()).rows[this.filtersRowIndex].style.display;this.show(!e)}},{key:"show",value:function show(t){var e=!(0e){var n=t[1].slice(0,e);if(5<=+t[1].substr(e,1)){for(var r="";"0"===n.charAt(0);)r+="0",n=n.substr(1);(n=r+(n=+n+1+"")).length>e&&(t[0]=+t[0]+ +n.charAt(0)+"",n=n.substring(1))}t[1]=n}return t}(t,o.round),null!=o.truncate&&(t[1]=function truncate(t,e){t&&(t+="");return t&&t.length>e?t.substr(0,e):t}(t[1],o.truncate)),0 descending, false -> ascending\r\nSortableTable.prototype.defaultDescending = false;\r\n\r\n// shared between all instances. This is intentional to allow external files\r\n// to modify the prototype\r\nSortableTable.prototype._sortTypeInfo = {};\r\n\r\nSortableTable.prototype.setTable = function (oTable) {\r\n\tif ( this.tHead )\r\n\t\tthis.uninitHeader();\r\n\tthis.element = oTable;\r\n\tthis.setTHead( oTable.tHead );\r\n\tthis.setTBody( oTable.tBodies[0] );\r\n};\r\n\r\nSortableTable.prototype.setTHead = function (oTHead) {\r\n\tif (this.tHead && this.tHead != oTHead )\r\n\t\tthis.uninitHeader();\r\n\tthis.tHead = oTHead;\r\n\tthis.initHeader( this.sortTypes );\r\n};\r\n\r\nSortableTable.prototype.setTBody = function (oTBody) {\r\n\tthis.tBody = oTBody;\r\n};\r\n\r\nSortableTable.prototype.setSortTypes = function ( oSortTypes ) {\r\n\tif ( this.tHead )\r\n\t\tthis.uninitHeader();\r\n\tthis.sortTypes = oSortTypes || [];\r\n\tif ( this.tHead )\r\n\t\tthis.initHeader( this.sortTypes );\r\n};\r\n\r\n// adds arrow containers and events\r\n// also binds sort type to the header cells so that reordering columns does\r\n// not break the sort types\r\nSortableTable.prototype.initHeader = function (oSortTypes) {\r\n\tif (!this.tHead) return;\r\n\tvar cells = this.tHead.rows[0].cells;\r\n\tvar doc = this.tHead.ownerDocument || this.tHead.document;\r\n\tthis.sortTypes = oSortTypes || [];\r\n\tvar l = cells.length;\r\n\tvar img, c;\r\n\tfor (var i = 0; i < l; i++) {\r\n\t\tc = cells[i];\r\n\t\tif (this.sortTypes[i] != null && this.sortTypes[i] != "None") {\r\n\t\t\timg = doc.createElement("IMG");\r\n\t\t\timg.src = "images/blank.png";\r\n\t\t\tc.appendChild(img);\r\n\t\t\tif (this.sortTypes[i] != null)\r\n\t\t\t\tc._sortType = this.sortTypes[i];\r\n\t\t\tif (typeof c.addEventListener != "undefined")\r\n\t\t\t\tc.addEventListener("click", this._headerOnclick, false);\r\n\t\t\telse if (typeof c.attachEvent != "undefined")\r\n\t\t\t\tc.attachEvent("onclick", this._headerOnclick);\r\n\t\t\telse\r\n\t\t\t\tc.onclick = this._headerOnclick;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tc.setAttribute( "_sortType", oSortTypes[i] );\r\n\t\t\tc._sortType = "None";\r\n\t\t}\r\n\t}\r\n\tthis.updateHeaderArrows();\r\n};\r\n\r\n// remove arrows and events\r\nSortableTable.prototype.uninitHeader = function () {\r\n\tif (!this.tHead) return;\r\n\tvar cells = this.tHead.rows[0].cells;\r\n\tvar l = cells.length;\r\n\tvar c;\r\n\tfor (var i = 0; i < l; i++) {\r\n\t\tc = cells[i];\r\n\t\tif (c._sortType != null && c._sortType != "None") {\r\n\t\t\tc.removeChild(c.lastChild);\r\n\t\t\tif (typeof c.removeEventListener != "undefined")\r\n\t\t\t\tc.removeEventListener("click", this._headerOnclick, false);\r\n\t\t\telse if (typeof c.detachEvent != "undefined")\r\n\t\t\t\tc.detachEvent("onclick", this._headerOnclick);\r\n\t\t\tc._sortType = null;\r\n\t\t\tc.removeAttribute( "_sortType" );\r\n\t\t}\r\n\t}\r\n};\r\n\r\nSortableTable.prototype.updateHeaderArrows = function () {\r\n\tif (!this.tHead) return;\r\n\tvar cells = this.tHead.rows[0].cells;\r\n\tvar l = cells.length;\r\n\tvar img;\r\n\tfor (var i = 0; i < l; i++) {\r\n\t\tif (cells[i]._sortType != null && cells[i]._sortType != "None") {\r\n\t\t\timg = cells[i].lastChild;\r\n\t\t\tif (i == this.sortColumn)\r\n\t\t\t\timg.className = "sort-arrow " + (this.descending ? "descending" : "ascending");\r\n\t\t\telse\r\n\t\t\t\timg.className = "sort-arrow";\r\n\t\t}\r\n\t}\r\n};\r\n\r\nSortableTable.prototype.headerOnclick = function (e) {\r\n\t// find TD element\r\n\tvar el = e.target || e.srcElement;\r\n\twhile (el.tagName != "TD")\r\n\t\tel = el.parentNode;\r\n\r\n\tthis.sort(SortableTable.msie ? SortableTable.getCellIndex(el) : el.cellIndex);\r\n};\r\n\r\n// IE returns wrong cellIndex when columns are hidden\r\nSortableTable.getCellIndex = function (oTd) {\r\n\tvar cells = oTd.parentNode.childNodes\r\n\tvar l = cells.length;\r\n\tvar i;\r\n\tfor (i = 0; cells[i] != oTd && i < l; i++)\r\n\t\t;\r\n\treturn i;\r\n};\r\n\r\nSortableTable.prototype.getSortType = function (nColumn) {\r\n\treturn this.sortTypes[nColumn] || "String";\r\n};\r\n\r\n// only nColumn is required\r\n// if bDescending is left out the old value is taken into account\r\n// if sSortType is left out the sort type is found from the sortTypes array\r\n\r\nSortableTable.prototype.sort = function (nColumn, bDescending, sSortType) {\r\n\tif (!this.tBody) return;\r\n\tif (sSortType == null)\r\n\t\tsSortType = this.getSortType(nColumn);\r\n\r\n\t// exit if None\r\n\tif (sSortType == "None")\r\n\t\treturn;\r\n\r\n\tif (bDescending == null) {\r\n\t\tif (this.sortColumn != nColumn)\r\n\t\t\tthis.descending = this.defaultDescending;\r\n\t\telse\r\n\t\t\tthis.descending = !this.descending;\r\n\t}\r\n\telse\r\n\t\tthis.descending = bDescending;\r\n\r\n\tthis.sortColumn = nColumn;\r\n\r\n\tif (typeof this.onbeforesort == "function")\r\n\t\tthis.onbeforesort();\r\n\r\n\tvar f = this.getSortFunction(sSortType, nColumn);\r\n\tvar a = this.getCache(sSortType, nColumn);\r\n\tvar tBody = this.tBody;\r\n\r\n\ta.sort(f);\r\n\r\n\tif (this.descending)\r\n\t\ta.reverse();\r\n\r\n\tif (SortableTable.removeBeforeSort) {\r\n\t\t// remove from doc\r\n\t\tvar nextSibling = tBody.nextSibling;\r\n\t\tvar p = tBody.parentNode;\r\n\t\tp.removeChild(tBody);\r\n\t}\r\n\r\n\t// insert in the new order\r\n\tvar l = a.length;\r\n\tfor (var i = 0; i < l; i++)\r\n\t\ttBody.appendChild(a[i].element);\r\n\r\n\tif (SortableTable.removeBeforeSort) {\r\n\t\t// insert into doc\r\n\t\tp.insertBefore(tBody, nextSibling);\r\n\t}\r\n\r\n\tthis.updateHeaderArrows();\r\n\r\n\tthis.destroyCache(a);\r\n\r\n\tif (typeof this.onsort == "function")\r\n\t\tthis.onsort();\r\n};\r\n\r\nSortableTable.prototype.asyncSort = function (nColumn, bDescending, sSortType) {\r\n\tvar oThis = this;\r\n\tthis._asyncsort = function () {\r\n\t\toThis.sort(nColumn, bDescending, sSortType);\r\n\t};\r\n\twindow.setTimeout(this._asyncsort, 1);\r\n};\r\n\r\nSortableTable.prototype.getCache = function (sType, nColumn) {\r\n\tif (!this.tBody) return [];\r\n\tvar rows = this.tBody.rows;\r\n\tvar l = rows.length;\r\n\tvar a = new Array(l);\r\n\tvar r;\r\n\tfor (var i = 0; i < l; i++) {\r\n\t\tr = rows[i];\r\n\t\ta[i] = {\r\n\t\t\tvalue:\t\tthis.getRowValue(r, sType, nColumn),\r\n\t\t\telement:\tr\r\n\t\t};\r\n\t};\r\n\treturn a;\r\n};\r\n\r\nSortableTable.prototype.destroyCache = function (oArray) {\r\n\tvar l = oArray.length;\r\n\tfor (var i = 0; i < l; i++) {\r\n\t\toArray[i].value = null;\r\n\t\toArray[i].element = null;\r\n\t\toArray[i] = null;\r\n\t}\r\n};\r\n\r\nSortableTable.prototype.getRowValue = function (oRow, sType, nColumn) {\r\n\t// if we have defined a custom getRowValue use that\r\n\tif (this._sortTypeInfo[sType] && this._sortTypeInfo[sType].getRowValue)\r\n\t\treturn this._sortTypeInfo[sType].getRowValue(oRow, nColumn);\r\n\r\n\tvar s;\r\n\tvar c = oRow.cells[nColumn];\r\n\tif (typeof c.innerText != "undefined")\r\n\t\ts = c.innerText;\r\n\telse\r\n\t\ts = SortableTable.getInnerText(c);\r\n\treturn this.getValueFromString(s, sType);\r\n};\r\n\r\nSortableTable.getInnerText = function (oNode) {\r\n\tvar s = "";\r\n\tvar cs = oNode.childNodes;\r\n\tvar l = cs.length;\r\n\tfor (var i = 0; i < l; i++) {\r\n\t\tswitch (cs[i].nodeType) {\r\n\t\t\tcase 1: //ELEMENT_NODE\r\n\t\t\t\ts += SortableTable.getInnerText(cs[i]);\r\n\t\t\t\tbreak;\r\n\t\t\tcase 3:\t//TEXT_NODE\r\n\t\t\t\ts += cs[i].nodeValue;\r\n\t\t\t\tbreak;\r\n\t\t}\r\n\t}\r\n\treturn s;\r\n};\r\n\r\nSortableTable.prototype.getValueFromString = function (sText, sType) {\r\n\tif (this._sortTypeInfo[sType])\r\n\t\treturn this._sortTypeInfo[sType].getValueFromString( sText );\r\n\treturn sText;\r\n\t/*\r\n\tswitch (sType) {\r\n\t\tcase "Number":\r\n\t\t\treturn Number(sText);\r\n\t\tcase "CaseInsensitiveString":\r\n\t\t\treturn sText.toUpperCase();\r\n\t\tcase "Date":\r\n\t\t\tvar parts = sText.split("-");\r\n\t\t\tvar d = new Date(0);\r\n\t\t\td.setFullYear(parts[0]);\r\n\t\t\td.setDate(parts[2]);\r\n\t\t\td.setMonth(parts[1] - 1);\r\n\t\t\treturn d.valueOf();\r\n\t}\r\n\treturn sText;\r\n\t*/\r\n\t};\r\n\r\nSortableTable.prototype.getSortFunction = function (sType, nColumn) {\r\n\tif (this._sortTypeInfo[sType])\r\n\t\treturn this._sortTypeInfo[sType].compare;\r\n\treturn SortableTable.basicCompare;\r\n};\r\n\r\nSortableTable.prototype.destroy = function () {\r\n\tthis.uninitHeader();\r\n\tvar win = this.document.parentWindow;\r\n\tif (win && typeof win.detachEvent != "undefined") {\t// only IE needs this\r\n\t\twin.detachEvent("onunload", this._onunload);\r\n\t}\r\n\tthis._onunload = null;\r\n\tthis.element = null;\r\n\tthis.tHead = null;\r\n\tthis.tBody = null;\r\n\tthis.document = null;\r\n\tthis._headerOnclick = null;\r\n\tthis.sortTypes = null;\r\n\tthis._asyncsort = null;\r\n\tthis.onsort = null;\r\n};\r\n\r\n// Adds a sort type to all instance of SortableTable\r\n// sType : String - the identifier of the sort type\r\n// fGetValueFromString : function ( s : string ) : T - A function that takes a\r\n// string and casts it to a desired format. If left out the string is just\r\n// returned\r\n// fCompareFunction : function ( n1 : T, n2 : T ) : Number - A normal JS sort\r\n// compare function. Takes two values and compares them. If left out less than,\r\n// <, compare is used\r\n// fGetRowValue : function( oRow : HTMLTRElement, nColumn : int ) : T - A function\r\n// that takes the row and the column index and returns the value used to compare.\r\n// If left out then the innerText is first taken for the cell and then the\r\n// fGetValueFromString is used to convert that string the desired value and type\r\n\r\nSortableTable.prototype.addSortType = function (sType, fGetValueFromString, fCompareFunction, fGetRowValue) {\r\n\tthis._sortTypeInfo[sType] = {\r\n\t\ttype:\t\t\t\tsType,\r\n\t\tgetValueFromString:\tfGetValueFromString || SortableTable.idFunction,\r\n\t\tcompare:\t\t\tfCompareFunction || SortableTable.basicCompare,\r\n\t\tgetRowValue:\t\tfGetRowValue\r\n\t};\r\n};\r\n\r\n// this removes the sort type from all instances of SortableTable\r\nSortableTable.prototype.removeSortType = function (sType) {\r\n\tdelete this._sortTypeInfo[sType];\r\n};\r\n\r\nSortableTable.basicCompare = function compare(n1, n2) {\r\n\tif (n1.value < n2.value)\r\n\t\treturn -1;\r\n\tif (n2.value < n1.value)\r\n\t\treturn 1;\r\n\treturn 0;\r\n};\r\n\r\nSortableTable.idFunction = function (x) {\r\n\treturn x;\r\n};\r\n\r\nSortableTable.toUpperCase = function (s) {\r\n\treturn s.toUpperCase();\r\n};\r\n\r\nSortableTable.toDate = function (s) {\r\n\tvar parts = s.split("-");\r\n\tvar d = new Date(0);\r\n\td.setFullYear(parts[0]);\r\n\td.setDate(parts[2]);\r\n\td.setMonth(parts[1] - 1);\r\n\treturn d.valueOf();\r\n};\r\n\r\n\r\n// add sort types\r\nSortableTable.prototype.addSortType("Number", Number);\r\nSortableTable.prototype.addSortType("CaseInsensitiveString", SortableTable.toUpperCase);\r\nSortableTable.prototype.addSortType("Date", SortableTable.toDate);\r\nSortableTable.prototype.addSortType("String");\r\n// None is a special case\r\n'}}]); \ No newline at end of file diff --git a/src/streamlit_app.py b/src/streamlit_app.py index 99d0b84662681e7d21a08fcce44908344fa86f80..336c1e0012642e9e4e71d358d8d2131f37939058 100644 --- a/src/streamlit_app.py +++ b/src/streamlit_app.py @@ -1,40 +1,595 @@ -import altair as alt -import numpy as np -import pandas as pd +import random import streamlit as st +import streamlit.components.v1 as components +import requests +import os +import configparser +import urllib +import datetime +from num2words import num2words +from time import sleep +from pathlib import Path +from threading import Thread +from time import sleep +from math import floor +import threading +from http.server import SimpleHTTPRequestHandler +from socketserver import TCPServer +import classes.ConfigManager as ConfigManager +import classes.Utility as Utility +import classes.Fetcher as Fetcher + +st.set_page_config(layout="wide", page_title="Screeni-py", page_icon="📈") + +# Set protobuf to python to avoid TF error (This is a Slower infernece) +os.environ["PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION"] = "python" +os.environ["TERM"] = "xterm" + +import pandas as pd +from screenipy import main as screenipy_main +from classes.OtaUpdater import OTAUpdater +from classes.Changelog import VERSION + +# Get system wide proxy for networking +try: + proxyServer = urllib.request.getproxies()['http'] +except KeyError: + proxyServer = "" + +# Start webserver to serve static files - js/css +def start_static_file_server(): + + class ThreadedHTTPServer(TCPServer): + allow_reuse_address = True # Allow immediate reuse of the address + + server = ThreadedHTTPServer(("0.0.0.0", 8000), SimpleHTTPRequestHandler) + + def serve(): + with server: + print("Static File WebServer started on port 8000") + server.serve_forever() + + server_thread = threading.Thread(target=serve, daemon=True) + server_thread.start() + return server + +try: + staticFileServer = start_static_file_server() +except OSError as e: + if e.errno == 98: + pass + else: + raise(e) + +isDevVersion, guiUpdateMessage = None, None + +@st.cache_data(ttl='1h', show_spinner=False) +def check_updates(): + isDevVersion, guiUpdateMessage = OTAUpdater.checkForUpdate(proxyServer, VERSION) + return isDevVersion, guiUpdateMessage + +isDevVersion, guiUpdateMessage = check_updates() + +execute_inputs = [] + +def show_df_as_result_table(): + try: + df:pd.DataFrame = pd.read_pickle('last_screened_unformatted_results.pkl') + ac, cc, bc = st.columns([6,1,1]) + ac.markdown(f'#### 🔍 Found {len(df)} Results') + clear_cache_btn = cc.button( + label='Clear Cached Data', + use_container_width=True, + key=random.randint(1,999999999), + ) + if clear_cache_btn: + os.system('rm stock_data_*.pkl') + st.toast('Stock Cache Deleted!', icon='đŸ—‘ī¸') + bc.download_button( + label="Download Results", + data=df.to_csv().encode('utf-8'), + file_name=f'screenipy_results_{datetime.datetime.now().strftime("%H:%M:%S_%d-%m-%Y")}.csv', + mime='text/csv', + type='secondary', + use_container_width=True + ) + if type(execute_inputs[0]) == str or int(execute_inputs[0]) < 15: + df.index = df.index.map(lambda x: "https://in.tradingview.com/chart?symbol=NSE%3A" + x) + df.index = df.index.map(lambda x: f'{x.split("%3A")[-1]}') + elif execute_inputs[0] == '16': + try: + fetcher = Fetcher.tools(configManager=ConfigManager.tools()) + url_dict_reversed = {key.replace('^','').replace('.NS',''): value for key, value in fetcher.getAllNiftyIndices().items()} + url_dict_reversed = {v: k for k, v in url_dict_reversed.items()} + df.index = df.index.map(lambda x: "https://in.tradingview.com/chart?symbol=NSE%3A" + url_dict_reversed[x]) + url_dict_reversed = {v: k for k, v in url_dict_reversed.items()} + df.index = df.index.map(lambda x: f'{url_dict_reversed[x.split("%3A")[-1]]}') + except KeyError: + pass + else: + df.index = df.index.map(lambda x: "https://in.tradingview.com/chart?symbol=" + x) + df.index = df.index.map(lambda x: f'{x.split("=")[-1]}') + df['Stock'] = df.index + stock_column = df.pop('Stock') # Remove 'Age' column and store it separately + df.insert(0, 'Stock', stock_column) + st.components.v1.html(f""" + {df.to_html(escape=False, index=False, index_names=False, table_id='resultTable')} + + + """, height=500, scrolling=True) + except FileNotFoundError: + st.error('Last Screened results are not available at the moment') + except Exception as e: + st.error('No Dataframe found for last_screened_results.pkl') + st.exception(e) + +def on_config_change(): + configManager = ConfigManager.tools() + configManager.period = period + configManager.daysToLookback = daystolookback + configManager.duration = duration + configManager.minLTP, configManager.maxLTP = minprice, maxprice + configManager.volumeRatio, configManager.consolidationPercentage = volumeratio, consolidationpercentage + configManager.shuffle = shuffle + configManager.cacheEnabled = cache + configManager.stageTwo = stagetwo + configManager.useEMA = useema + configManager.setConfig(configparser.ConfigParser(strict=False), default=True, showFileCreatedText=False) + st.toast('Configuration Saved', icon='💾') + +def on_start_button_click(): + global execute_inputs + if isDevVersion != None: + st.info(f'Received inputs (Debug only): {execute_inputs}') + + def dummy_call(): + try: + screenipy_main(execute_inputs=execute_inputs, isDevVersion=isDevVersion, backtestDate=backtestDate) + except StopIteration: + pass + except requests.exceptions.RequestException: + os.environ['SCREENIPY_REQ_ERROR'] = "TRUE" + + if Utility.tools.isBacktesting(backtestDate=backtestDate): + 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]') + st.write('Backtesting is :red[Not Supported] for Intraday timeframes') + t = Thread(target=dummy_call) + t.start() + + st.markdown(""" + + """, unsafe_allow_html=True) + + progress_text = "🚀 Preparing Screener, Please Wait! " + progress_bar = st.progress(0, text=progress_text) + + os.environ['SCREENIPY_SCREEN_COUNTER'] = '0' + while int(os.environ.get('SCREENIPY_SCREEN_COUNTER')) < 100: + sleep(0.05) + cnt = int(os.environ.get('SCREENIPY_SCREEN_COUNTER')) + if cnt > 0: + progress_text = "🔍 Screening stocks for you... " + progress_bar.progress(cnt, text=progress_text + f"**:red[{cnt}%]** Done") + if os.environ.get('SCREENIPY_REQ_ERROR') and "TRUE" in os.environ.get('SCREENIPY_REQ_ERROR'): + ac, bc = st.columns([2,1]) + ac.error(':disappointed: Failed to reach Screeni-py server!') + 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='â„šī¸') + bc.video('https://youtu.be/JADNADDNTmU') + del os.environ['SCREENIPY_REQ_ERROR'] + break + + t.join() + progress_bar.empty() + +def nifty_predict(col): + with col.container(): + with st.spinner('🔮 Taking a Look into the Future, Please wait...'): + import classes.Fetcher as Fetcher + import classes.Screener as Screener + configManager = ConfigManager.tools() + fetcher = Fetcher.tools(configManager) + screener = Screener.tools(configManager) + os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' + prediction, trend, confidence, data_used = screener.getNiftyPrediction( + data=fetcher.fetchLatestNiftyDaily(proxyServer=proxyServer), + proxyServer=proxyServer + ) + if 'BULLISH' in trend: + col.success(f'Market may Open **Gap Up** next day!\n\nProbability/Strength of Prediction = {confidence}%', icon='📈') + elif 'BEARISH' in trend: + col.error(f'Market may Open **Gap Down** next day!\n\nProbability/Strength of Prediction = {confidence}%', icon='📉') + else: + col.info("Couldn't determine the Trend. Try again later!") + 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='âš ī¸') + 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='🆕') + col.markdown("**Following data is used to make above prediction:**") + col.dataframe(data_used) + +def find_similar_stocks(stockCode:str, candles:int): + global execute_inputs + stockCode = stockCode.upper() + if ',' in stockCode or ' ' in stockCode or stockCode == '': + st.error('Invalid Character in Stock Name!', icon='😾') + return False + else: + execute_inputs = ['S', 0, stockCode, candles, 'N'] + on_start_button_click() + st.toast('Screening Completed!', icon='🎉') + sleep(2) + return True + +def get_extra_inputs(tickerOption, executeOption, c_index=None, c_criteria=None, start_button=None): + global execute_inputs + if not tickerOption.isnumeric(): + execute_inputs = [tickerOption, 0, 'N'] + elif int(tickerOption) == 0 or tickerOption is None: + stock_codes:str = c_index.text_input('Enter Stock Code(s)', placeholder='SBIN, INFY, ITC') + execute_inputs = [tickerOption, executeOption, stock_codes.upper(), 'N'] + return + elif int(executeOption) >= 0 and int(executeOption) < 4: + execute_inputs = [tickerOption, executeOption, 'N'] + elif int(executeOption) == 4: + num_candles = c_criteria.text_input('The Volume should be lowest since last how many candles?', value='20') + if num_candles: + execute_inputs = [tickerOption, executeOption, num_candles, 'N'] + else: + c_criteria.error("Number of Candles can't be left blank!") + elif int(executeOption) == 5: + min_rsi, max_rsi = c_criteria.columns((1,1)) + min_rsi = min_rsi.number_input('Min RSI', min_value=0, max_value=100, value=50, step=1, format="%d") + max_rsi = max_rsi.number_input('Max RSI', min_value=0, max_value=100, value=70, step=1, format="%d") + if min_rsi >= max_rsi: + c_criteria.warning('WARNING: Min RSI â‰Ĩ Max RSI') + else: + execute_inputs = [tickerOption, executeOption, min_rsi, max_rsi, 'N'] + elif int(executeOption) == 6: + c1, c2 = c_criteria.columns((7,2)) + select_reversal = int(c1.selectbox('Select Type of Reversal', + options = [ + '1 > Buy Signal (Bullish Reversal)', + '2 > Sell Signal (Bearish Reversal)', + '3 > Momentum Gainers (Rising Bullish Momentum)', + '4 > Reversal at Moving Average (Bullish Reversal)', + '5 > Volume Spread Analysis (Bullish VSA Reversal)', + '6 > Narrow Range (NRx) Reversal', + '7 > Lorentzian Classifier (Machine Learning based indicator)', + '8 > RSI Crossing with 9 SMA of RSI itself' + ] + ).split(' ')[0]) + if select_reversal == 4: + ma_length = c2.number_input('MA Length', value=44, step=1, format="%d") + execute_inputs = [tickerOption, executeOption, select_reversal, ma_length, 'N'] + elif select_reversal == 6: + range = c2.number_input('NR(x)',min_value=1, max_value=14, value=4, step=1, format="%d") + execute_inputs = [tickerOption, executeOption, select_reversal, range, 'N'] + elif select_reversal == 7: + signal = int(c2.selectbox('Signal Type', + options = [ + '1 > Any', + '2 > Buy', + '3 > Sell', + ] + ).split(' ')[0]) + execute_inputs = [tickerOption, executeOption, select_reversal, signal, 'N'] + else: + execute_inputs = [tickerOption, executeOption, select_reversal, 'N'] + elif int(executeOption) == 7: + c1, c2 = c_criteria.columns((11,4)) + select_pattern = int(c1.selectbox('Select Chart Pattern', + options = [ + '1 > Bullish Inside Bar (Flag) Pattern', + '2 > Bearish Inside Bar (Flag) Pattern', + '3 > Confluence (50 & 200 MA/EMA)', + '4 > VCP (Experimental)', + '5 > Buying at Trendline (Ideal for Swing/Mid/Long term)', + ] + ).split(' ')[0]) + if select_pattern == 1 or select_pattern == 2: + num_candles = c2.number_input('Lookback Candles', min_value=1, max_value=25, value=12, step=1, format="%d") + execute_inputs = [tickerOption, executeOption, select_pattern, int(num_candles), 'N'] + elif select_pattern == 3: + 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 + execute_inputs = [tickerOption, executeOption, select_pattern, confluence_percentage, 'N'] + else: + execute_inputs = [tickerOption, executeOption, select_pattern, 'N'] + +header_padding = """ + + """ +st.markdown(header_padding, unsafe_allow_html=True) + +ac, bc = st.columns([13,1]) + +ac.title('📈 Screeni-py') +if guiUpdateMessage == "": + ac.subheader('Find Breakouts, Just in Time!') + +if isDevVersion: + ac.warning(guiUpdateMessage, icon='âš ī¸') +elif guiUpdateMessage != "": + ac.success(guiUpdateMessage, icon='â‡ī¸') + +telegram_url = "https://user-images.githubusercontent.com/6128978/217814499-7934edf6-fcc3-46d7-887e-7757c94e1632.png" +bc.divider() +bc.image(telegram_url, width=96) + +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']) + +with tab_screen: + st.markdown(""" + + """, + unsafe_allow_html=True) + + list_index = [ + 'All Stocks (Default)', + # 'W > Screen stocks from my own Watchlist', + # 'N > Nifty Prediction using Artifical Intelligence (Use for Gap-Up/Gap-Down/BTST/STBT)', + # 'E > Live Index Scan : 5 EMA for Intraday', + '0 > By Stock Names (NSE Stock Code)', + '1 > Nifty 50', + '2 > Nifty Next 50', + '3 > Nifty 100', + '4 > Nifty 200', + '5 > Nifty 500', + '6 > Nifty Smallcap 50', + '7 > Nifty Smallcap 100', + '8 > Nifty Smallcap 250', + '9 > Nifty Midcap 50', + '10 > Nifty Midcap 100', + '11 > Nifty Midcap 150', + '13 > Newly Listed (IPOs in last 2 Year)', + '14 > F&O Stocks Only', + '15 > US S&P 500', + '16 > Sectoral Indices (NSE)' + ] + + list_criteria = [ + '0 > Full Screening (Shows Technical Parameters without Any Criteria)', + '1 > Screen stocks for Breakout or Consolidation', + '2 > Screen for the stocks with recent Breakout & Volume', + '3 > Screen for the Consolidating stocks', + '4 > Screen for the stocks with Lowest Volume in last N-days (Early Breakout Detection)', + '5 > Screen for the stocks with RSI', + '6 > Screen for the stocks showing Reversal Signals', + '7 > Screen for the stocks making Chart Patterns', + ] + + configManager = ConfigManager.tools() + configManager.getConfig(parser=ConfigManager.parser) + + c_index, c_datepick, c_criteria, c_button_start = st.columns((2,1,4,1)) + + tickerOption = c_index.selectbox('Select Index', options=list_index).split(' ') + tickerOption = str(12 if '>' not in tickerOption else int(tickerOption[0]) if tickerOption[0].isnumeric() else str(tickerOption[0])) + picked_date = c_datepick.date_input(label='Screen/Backtest For', max_value=datetime.date.today(), value=datetime.date.today()) + if picked_date: + backtestDate = picked_date + + executeOption = str(c_criteria.selectbox('Select Screening Criteria', options=list_criteria).split(' ')[0]) + + start_button = c_button_start.button('Start Screening', type='primary', key='start_button', use_container_width=True) + + get_extra_inputs(tickerOption=tickerOption, executeOption=executeOption, c_index=c_index, c_criteria=c_criteria, start_button=start_button) + + if start_button: + on_start_button_click() + st.toast('Screening Completed!', icon='🎉') + sleep(2) + + with st.container(): + show_df_as_result_table() + +with tab_config: + configManager = ConfigManager.tools() + configManager.getConfig(parser=ConfigManager.parser) + + ac, bc = st.columns([10,2]) + ac.markdown('### 🔧 Screening Configuration') + bc.download_button( + label="Export Configuration", + data=Path('screenipy.ini').read_text(), + file_name='screenipy.ini', + mime='text/plain', + type='primary', + use_container_width=True +) + + ac, bc, cc = st.columns([1,1,1]) + + period_options = ['15d','60d','300d','52wk','3y','5y','max'] + duration_options = ['5m','15m','1h','4h','1d','1wk'] + + # period = ac.text_input('Period', value=f'{configManager.period}', placeholder='300d / 52wk ') + period = ac.selectbox('Period', options=period_options, index=period_options.index(configManager.period), placeholder='300d / 52wk') + daystolookback = bc.number_input('Lookback Period (Number of Candles)', value=configManager.daysToLookback, step=1) + # duration = cc.text_input('Candle Duration', value=f'{configManager.duration}', placeholder='15m / 1d / 1wk') + duration = cc.selectbox('Candle Duration', options=duration_options, index=duration_options.index(configManager.duration), placeholder='15m / 1d / 1wk') + if 'm' in duration or 'h' in duration: + cc.write('For Intraday duartion, Max :red[value of period <= 60d]') + + ac, bc = st.columns([1,1]) + minprice = ac.number_input('Minimum Price (Stocks below this will be ignored)', value=float(configManager.minLTP), step=0.1) + maxprice = bc.number_input('Maximum Price (Stocks above this will be ignored)', value=float(configManager.maxLTP), step=0.1) + + ac, bc = st.columns([1,1]) + volumeratio = ac.number_input('Volume multiplier for Breakout confirmation', value=float(configManager.volumeRatio), step=0.1) + consolidationpercentage = bc.number_input('Range consolidation (%)', value=int(configManager.consolidationPercentage), step=1) + + ac, bc, cc, dc = st.columns([1,1,1,1]) + shuffle = ac.checkbox('Shuffle stocks while screening', value=configManager.shuffleEnabled, disabled=True) + cache = bc.checkbox('Enable caching of stock data after market hours', value=configManager.cacheEnabled, disabled=True) + 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) + useema = dc.checkbox('Use EMA instead of SMA', value=configManager.useEMA) + + save_button = st.button('Save Configuration', on_click=on_config_change, type='primary', use_container_width=True) + + st.markdown('### Import Your Own Configuration:') + uploaded_file = st.file_uploader('Upload screenipy.ini file') + + if uploaded_file is not None: + bytes_data = uploaded_file.getvalue() + with open('screenipy.ini', 'wb') as f: + f.write(bytes_data) + st.toast('Configuration Imported', icon='âš™ī¸') + +with tab_nifty: + ac, bc = st.columns([9,1]) + + ac.subheader('🧠 AI-based prediction for Next Day Nifty-50 Gap Up / Gap Down') + bc.button('**Predict**', type='primary', on_click=nifty_predict, args=(ac,), use_container_width=True) + +with tab_similar: + + st.subheader('đŸ•ĩđŸģ Find Stocks forming Similar Chart Patterns') + ac, bc, cc = st.columns([4,2,1]) + + stockCode = ac.text_input('Enter Stock Name and Press Enter', placeholder='HDFCBANK') + candles = bc.number_input('Lookback Period (No. of Candles)', min_value=1, step=1, value=int(configManager.daysToLookback)) + similar_search_button = cc.button('**Search**', type='primary', use_container_width=True) + + if similar_search_button: + result = find_similar_stocks(stockCode, candles) + if result: + with st.container(): + show_df_as_result_table() + st.write('Click [**here**](https://medium.com/@joshi.pranjal5/spot-your-favourite-trading-setups-using-vector-databases-1651d747fbf0) to know How this Works? 🤔') + +with tab_about: + from classes.Changelog import VERSION, changelog + + st.success(f'Screeni-py v{VERSION}', icon='🔍') + ac, bc = st.columns([2,1]) + ac.info(""" +👨đŸģ‍đŸ’ģ Developed and Maintained by: Pranjal Joshi + +🏠 Home Page: https://github.com/pranjal-joshi/Screeni-py + +âš ī¸ Read/Post Issues here: https://github.com/pranjal-joshi/Screeni-py/issues + +đŸ“Ŗ Join Community Discussions: https://github.com/pranjal-joshi/Screeni-py/discussions + +âŦ‡ī¸ Download latest software from https://github.com/pranjal-joshi/Screeni-py/releases/latest + +đŸ’Ŧ Join Telegram Group for discussion: https://t.me/+0Tzy08mR0do0MzNl + +đŸŽŦ 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) + """) + bc.write('', unsafe_allow_html=True) + st.warning("ChangeLog:\n " + changelog[40:-3], icon='âš™ī¸') + +with tab_psc: + ac, oc = st.columns([1, 1]) + ac, bc = ac.columns([4, 1]) + ac.subheader('💸 Position Size Calculator') + calculate_qty_btn = bc.button('**Calculate Qty**', type='primary', use_container_width=True) + + ac, bc = st.columns([1, 1]) + capital = ac.number_input(label='Capital Size', min_value=0, value=100000, help='Total Amount used for Trading/Investing') + if capital: + in_words = num2words(capital, lang='en_IN').title() + bc.write(f"

Your Capital is Rs. {in_words}

", unsafe_allow_html=True) + + 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!') + if risk: + risk_rs = capital * (risk/100.0) + in_words = num2words(risk_rs, lang='en_IN').title() + bc.write(f"

Your Risk for this trade is Rs. {in_words}

", unsafe_allow_html=True) + + ac.divider() + + 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.') + if sl > 0: + in_words = num2words(sl, lang='en_IN').title() + bc.write(f"

Your SL is {in_words} Rs. per share.

", unsafe_allow_html=True) + + ac.write('
OR
', unsafe_allow_html=True) + + a1, a2 = ac.columns([1, 1]) + price = a1.number_input(label="Entry Price", min_value=0.0, help='Entry price for Long/Short position') + percentage_sl = a2.number_input(label="% SL", min_value=0.0, max_value=100.0, value=5.0, help='Stoploss in %') + if sl == 0 and (price > 0 and percentage_sl > 0): + actual_sl = round(price * (percentage_sl / 100),2) + in_words = num2words(actual_sl, lang='en_IN').title() + bc.write(f"

Your SL is Rs. {actual_sl} per share

", unsafe_allow_html=True) + + if calculate_qty_btn: + if sl > 0: + qty = floor(risk_rs / sl) + 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') + elif price > 0 and percentage_sl > 0: + qty = floor(risk_rs / actual_sl) + 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') -""" -# Welcome to Streamlit! - -Edit `/streamlit_app.py` to customize this app to your heart's desire :heart:. -If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community -forums](https://discuss.streamlit.io). - -In the meantime, below is an example of what you can do with just a few lines of code: -""" - -num_points = st.slider("Number of points in spiral", 1, 10000, 1100) -num_turns = st.slider("Number of turns in spiral", 1, 300, 31) - -indices = np.linspace(0, 1, num_points) -theta = 2 * np.pi * num_turns * indices -radius = indices - -x = radius * np.cos(theta) -y = radius * np.sin(theta) - -df = pd.DataFrame({ - "x": x, - "y": y, - "idx": indices, - "rand": np.random.randn(num_points), -}) - -st.altair_chart(alt.Chart(df, height=700, width=700) - .mark_point(filled=True) - .encode( - x=alt.X("x", axis=None), - y=alt.Y("y", axis=None), - color=alt.Color("idx", legend=None, scale=alt.Scale()), - size=alt.Size("rand", legend=None, scale=alt.Scale(range=[1, 150])), - )) \ No newline at end of file +marquee_html = ''' + + + + + + + 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. + + +''' +components.html(marquee_html) \ No newline at end of file diff --git a/test/screenipy_test.py b/test/screenipy_test.py new file mode 100644 index 0000000000000000000000000000000000000000..10fba56013329bd935d731a9fcb73cb245daed59 --- /dev/null +++ b/test/screenipy_test.py @@ -0,0 +1,192 @@ +''' + * Project : Screenipy + * Author : Pranjal Joshi + * Created : 29/04/2021 + * Description : Automated Test Script for Screenipy +''' + + +import pytest +import sys +import os +import numpy as np +import pandas as pd +import configparser +import requests +import json +import platform + +sys.path.append(os.path.abspath('../src')) +import classes.ConfigManager as ConfigManager +from classes.Changelog import changelog +from screenipy import * +last_release = 0 +configManager = ConfigManager.tools() + +# Generate default configuration if not exist + + +def test_generate_default_config(mocker, capsys): + mocker.patch('builtins.input', side_effect=['5','0', '\n']) + with pytest.raises(SystemExit): + configManager.setConfig(ConfigManager.parser, default=True) + out, err = capsys.readouterr() + assert err == '' + + +def test_if_release_version_increamented(): + global last_release + r = requests.get( + "https://api.github.com/repos/pranjal-joshi/Screeni-py/releases/latest") + last_release = float(r.json()['tag_name']) + assert float(VERSION) > last_release + + +def test_option_0(mocker): + try: + mocker.patch('builtins.input', side_effect=['0', TEST_STKCODE, 'y']) + main(testing=True) + assert len(screenResults) == 1 + except StopIteration: + pass + + +def test_option_1(mocker): + try: + mocker.patch('builtins.input', side_effect=['5', '1', 'y']) + main(testing=True) + assert len(screenResults) > 0 + except StopIteration: + pass + + +def test_option_2(mocker): + try: + mocker.patch('builtins.input', side_effect=['5', '2', 'y']) + main(testing=True) + assert len(screenResults) > 0 + except StopIteration: + pass + + +def test_option_3(mocker): + try: + mocker.patch('builtins.input', side_effect=['5', '3', 'y']) + main(testing=True) + assert len(screenResults) > 0 + except StopIteration: + pass + + +def test_option_4(mocker): + try: + mocker.patch('builtins.input', side_effect=['5', '4', '7', 'y']) + main(testing=True) + assert len(screenResults) > 0 + except StopIteration: + pass + + +def test_option_5(mocker): + try: + mocker.patch('builtins.input', side_effect=['5', '5', '30', '70']) + main(testing=True) + assert len(screenResults) > 0 + except StopIteration: + pass + + +def test_option_6(mocker): + try: + mocker.patch('builtins.input', side_effect=['5', '6', '1', 'y']) + main(testing=True) + assert len(screenResults) > 0 + except StopIteration: + pass + + +def test_option_7(mocker): + try: + mocker.patch('builtins.input', side_effect=['5', '7', '1', '7', 'y']) + main(testing=True) + assert len(screenResults) > 0 + except StopIteration: + pass + + +def test_option_8(mocker, capsys): + try: + mocker.patch('builtins.input', side_effect=['5', + '8', + str(configManager.period), + str(configManager.daysToLookback), + str(configManager.duration), + str(configManager.minLTP), + str(configManager.maxLTP), + str(configManager.volumeRatio), + str(configManager.consolidationPercentage), + 'y', + 'y', + ]) + with pytest.raises((SystemExit, configparser.DuplicateSectionError)): + main(testing=True) + out, err = capsys.readouterr() + assert err == 0 or err == '' + except StopIteration: + pass + + +def test_option_9(): + configManager.getConfig(ConfigManager.parser) + assert configManager.duration is not None + assert configManager.period is not None + assert configManager.consolidationPercentage is not None + + +def test_option_12(mocker, capsys): + try: + mocker.patch('builtins.input', side_effect=['5','12']) + with pytest.raises(SystemExit): + main(testing=True) + out, err = capsys.readouterr() + assert err == '' + except StopIteration: + pass + +def test_option_14(mocker): + try: + mocker.patch('builtins.input', side_effect=['14', '0', 'y']) + main(testing=True) + assert len(screenResults) > 0 + except StopIteration: + pass + + +# def test_ota_updater(): +# try: +# OTAUpdater.checkForUpdate(proxyServer, VERSION) +# assert ( +# "exe" in OTAUpdater.checkForUpdate.url or "bin" in OTAUpdater.checkForUpdate.url) +# except StopIteration: +# pass + + +# def test_release_readme_urls(): +# global last_release +# f = open('../src/release.md', 'r', encoding='utf-8') +# contents = f.read() +# f.close() +# failUrl = [f"https://github.com/pranjal-joshi/Screeni-py/releases/download/{last_release}/screenipy.bin", +# f"https://github.com/pranjal-joshi/Screeni-py/releases/download/{last_release}/screenipy.exe"] +# passUrl = [f"https://github.com/pranjal-joshi/Screeni-py/releases/download/{VERSION}/screenipy.bin", +# f"https://github.com/pranjal-joshi/Screeni-py/releases/download/{VERSION}/screenipy.exe"] +# for url in failUrl: +# assert not url in contents +# for url in passUrl: +# assert url in contents + + +def test_if_changelog_version_changed(): + global last_release + v = changelog.split(']')[-2].split('[')[-1] + assert float(v) > float(last_release)