diff --git a/.gitattributes b/.gitattributes index a6344aac8c09253b3b630fb776ae94478aa0275b..6f33358855fa0967482cf5a52ea6e334bf806b91 100644 --- a/.gitattributes +++ b/.gitattributes @@ -33,3 +33,9 @@ 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 +screenshots/done.png filter=lfs diff=lfs merge=lfs -text +screenshots/results.png filter=lfs diff=lfs merge=lfs -text +screenshots/screenipy_demo.gif 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/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/README.md b/README.md index 157a2ac8cf97fee45b8373164f1cb12acbad1f19..25e74c8846526b232f3d8918a95e96a2a1992331 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,151 @@ +| | +| :-: | +| ![Screeni-py](https://user-images.githubusercontent.com/6128978/217816268-74c40180-fc47-434d-938b-3639898ee3e0.png) | +| [![GitHub release (latest by date)](https://img.shields.io/github/v/release/pranjal-joshi/Screeni-py?style=for-the-badge)](https://github.com/pranjal-joshi/Screeni-py/releases/latest) [![GitHub all releases](https://img.shields.io/github/downloads/pranjal-joshi/Screeni-py/total?color=Green&label=Downloads&style=for-the-badge)](#) ![Docker Pulls](https://img.shields.io/docker/pulls/joshipranjal/screeni-py?style=for-the-badge&logo=docker) [![GitHub](https://img.shields.io/github/license/pranjal-joshi/Screeni-py?style=for-the-badge)](https://github.com/pranjal-joshi/Screeni-py/blob/main/LICENSE) [![CodeFactor](https://www.codefactor.io/repository/github/pranjal-joshi/screeni-py/badge?style=for-the-badge)](https://www.codefactor.io/repository/github/pranjal-joshi/screeni-py) [![MADE-IN-INDIA](https://img.shields.io/badge/MADE%20WITH%20%E2%9D%A4%20IN-INDIA-orange?style=for-the-badge)](https://en.wikipedia.org/wiki/India) [![BADGE](https://img.shields.io/badge/PULL%20REQUEST-GUIDELINES-red?style=for-the-badge)](https://github.com/pranjal-joshi/Screeni-py/blob/new-features/CONTRIBUTING.md) | +| [![Screenipy Test - New Features](https://github.com/pranjal-joshi/Screeni-py/actions/workflows/workflow-test.yml/badge.svg?branch=new-features)](https://github.com/pranjal-joshi/Screeni-py/actions/workflows/workflow-test.yml) [![Screenipy Build - New Release](https://github.com/pranjal-joshi/Screeni-py/actions/workflows/workflow-build-matrix.yml/badge.svg)](https://github.com/pranjal-joshi/Screeni-py/actions/workflows/workflow-build-matrix.yml) | +| ![Windows](https://img.shields.io/badge/Windows-0078D6?style=for-the-badge&logo=windows&logoColor=white) ![Linux](https://img.shields.io/badge/Linux-FCC624?style=for-the-badge&logo=linux&logoColor=black) ![Mac OS](https://img.shields.io/badge/mac%20os-D3D3D3?style=for-the-badge&logo=apple&logoColor=000000) ![Docker](https://img.shields.io/badge/docker-%230db7ed.svg?style=for-the-badge&logo=docker&logoColor=white) | +|

Scan QR Code to join [Official Telegram Group](https://t.me/+0Tzy08mR0do0MzNl) for Additional Discussions

| + +| **YouTube** | **Use** | **Discussion** | **Bugs/Issues** | **Documentation** | +| :---: | :---: | :---: | :---: | :---: | +| [![youtube](https://github.com/pranjal-joshi/Screeni-py/assets/6128978/9673bbcf-4798-48f4-918d-692b90e28d37)](https://www.youtube.com/playlist?list=PLsGnKKT_974J3UVS8M6bxqePfWLeuMsBi) | [![docker](https://github.com/pranjal-joshi/Screeni-py/assets/6128978/f44054b8-9fcb-465c-a38b-63f6ecc4a0c9)](https://hub.docker.com/r/joshipranjal/screeni-py/tags) | [![meeting](https://user-images.githubusercontent.com/6128978/149935812-31266023-cc5b-4c98-a416-1d4cf8800c0c.png)](https://github.com/pranjal-joshi/Screeni-py/discussions) | [![warning](https://user-images.githubusercontent.com/6128978/149936142-04d7cf1c-5bc5-45c1-a8e4-015454a2de48.png)](https://github.com/pranjal-joshi/Screeni-py/issues?q=is%3Aissue) | [![help](https://user-images.githubusercontent.com/6128978/149937331-5ee5c00a-748d-4fbf-a9f9-e2273480d8a2.png)](https://github.com/pranjal-joshi/Screeni-py/blob/main/README.md#what-is-screeni-py) | +| Watch our [YouTube](https://www.youtube.com/playlist?list=PLsGnKKT_974J3UVS8M6bxqePfWLeuMsBi) playlist | Get started quickly using Docker | Join/Read the Community Discussion | Raise an Issue about a Problem | Get Help about Usage | + + + --- -title: Pksc -emoji: 👀 -colorFrom: red -colorTo: purple -sdk: docker -pinned: false -license: mit ---- -Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference +## What is Screeni-py? + +### A Python-based stock screener for NSE, India + +**Screenipy** is an advanced stock screener to find potential breakout stocks from NSE and tell its possible breakout values. It also helps to find the stocks that are consolidating and may breakout, or the particular chart patterns that you're looking for specifically to make your decisions. +Screenipy is totally customizable and it can screen stocks with the settings that you have provided. + +## How to use? (New Version - GUI Based) + +[![Screeni-py - Detailed Installation Guide](https://markdown-videos-api.jorgenkh.no/url?url=https%3A%2F%2Fyoutu.be%2F2HMN0ac4H20)](https://youtu.be/2HMN0ac4H20) +[![Screeni-py - Configuration and Usage | Screenipy - Python NSE Stock Screener](https://markdown-videos-api.jorgenkh.no/url?url=https%3A%2F%2Fyoutu.be%2FJCn6z1A7INI)](https://youtu.be/JCn6z1A7INI) +[![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) + +* Install Docker Desktop and pull the `latest` docker image from the [release](https://github.com/pranjal-joshi/Screeni-py/releases/latest) page. +* Checkout this [YouTube Video](https://youtu.be/2HMN0ac4H20) for detailed installation guide. + +image +image +image +image +image + +## How to use? (Older Version - CLI Based - DEPRECATED) + +* Download the suitable file according to your OS or install Docker Desktop and pull the `latest` docker image. +* Linux & Mac users should make sure that the `screenipy.bin or screenipy.run` has `execute` permission. +* **Run** the file. The following window will appear after a brief delay. + + ![home](screenshots/screenipy_demo.gif) + +* **Configure** the parameters as per your requirement using `Option > 8`. + + ![config](screenshots/config.png) + +* Following are the screenshots of screening and output results. + + ![screening](screenshots/screening.png) + ![results](screenshots/results.png) + ![done](screenshots/done.png) + +* Once done, you can also save the results in an Excel file. + +## Understanding the Result Table + +The Result table contains a lot of different parameters which can be pretty overwhelming to the new users, so here's the description and significance of each parameter. + +| Sr | Parameter | Description | Example | +|:---:|:---:|:---|:---| +|1|**Stock**|This is a NSE scrip symbol. If your OS/Terminal supports unicode, You can directly open **[TradingView](https://in.tradingview.com/)** charts by pressing `Ctrl+Click` on the stock name.|[TATAMOTORS](https://in.tradingview.com/chart?symbol=NSE%3ATATAMOTORS)| +|2|**Consolidating**|It gives the price range in which stock is trading since last `N` days. `N` is configurable and can be modified by executing `Edit User Configuration` option.|If stock is trading between price 100-120 in last 30 days, Output will be `Range = 20.0 %`| +|3|**Breakout (N Days)**|This is pure magic! The `BO` is Breakout level in last N days while `R` is the next resistance level if available. An investor should consider both BO & R level to decide entry/exits in their trades.|`B:302, R:313`(Breakout level is 100 & Next resistance is 102)| +|4|**LTP**|LTP is the Last Traded Price of an asset traded on NSE.|`298.7` (Stock is trading at this price)| +|5|**Volume**|Volume shows the relative volume of the recent candle with respect to 20 period MA of Volume. It could be `Unknown` for newly listed stocks.|if 20MA(Volume) is 1M and todays Volume is 2.8M, then `Volume = 2.8x`| +|6|**MA-Signal**|It describes the price trend of an asset by analyzing various 50-200 MA/EMA crossover strategies.|`200MA-Support`,`BullCross-50MA` etc| +|7|**RSI**|For the momentum traders, it describes 14-period RSI for quick decision-making about their trading plans|`0 to 100`| +|8|**Trend**|By using advanced algorithms, the average trendlines are computed for `N` days and their strength is displayed depending on the steepness of the trendlines. (This does NOT show any trendline on a chart, it is calculated internally)|`Strong Up`, `Weak Down` etc.| +|9|**Pattern**|If the chart or the candle itself forming any important pattern in the recent timeframe or as per the selected screening option, various important patterns will be indicated here.|`Momentum Gainer`, `Inside Bar (N)`,`Bullish Engulfing` etc.| + +## Hack it your way + +Feel free to Edit the parameters in the `screenipy.ini` file which will be generated by the application. + +```ini +[config] +period = 300d +daystolookback = 30 +duration = 1d +minprice = 30 +maxprice = 10000 +volumeratio = 2 +consolidationpercentage = 10 +shuffle = y +cachestockdata = y +onlystagetwostocks = y +useema = n +``` + +Try to tweak these parameters as per your trading styles. For example, If you're comfortable with weekly charts, make `duration=5d` and so on. + +## Installation Guide + +### YouTube Video of Detailed Installation Guide + +[![YouTube Video Views](https://img.shields.io/youtube/views/2HMN0ac4H20?style=for-the-badge&logo=youtube)](https://youtu.be/2HMN0ac4H20) + +[![Screeni-py - Detailed Installation Guide](https://markdown-videos-api.jorgenkh.no/url?url=https%3A%2F%2Fyoutu.be%2F2HMN0ac4H20)](https://youtu.be/2HMN0ac4H20) + +![Windows](https://img.shields.io/badge/Windows-0078D6?style=for-the-badge&logo=windows&logoColor=white) ![Linux](https://img.shields.io/badge/Linux-FCC624?style=for-the-badge&logo=linux&logoColor=black) ![Mac OS](https://img.shields.io/badge/mac%20os-D3D3D3?style=for-the-badge&logo=apple&logoColor=000000) ![Docker](https://img.shields.io/badge/docker-%230db7ed.svg?style=for-the-badge&logo=docker&logoColor=white) + +### Why we shifted to Docker from the Good old EXEs? + +| Executable/Binary File | Docker | +| :-- | :-- | +| [![GitHub Downloads](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 set up and use Screeni-py with Docker? + +1. Download and Install [Docker Desktop](https://www.docker.com/products/docker-desktop/) with default settings +2. If you are using Windows, update WSL (Windows subsystem for linux) by running `wsl --update` command in the command prompt +3. Restart your computer after installation +4. Open Docker Desktop and keep it as it is +5. Open Command Prompt (Windows) or Terminal (Mac/Linux) and run command `docker pull joshipranjal/screeni-py:latest` +6. Once installed, always start screenipy by running this command: + + ```bash + docker run -p 8501:8501 -p 8000:8000 joshipranjal/screeni-py:latest + + OR + + docker run -it --entrypoint /bin/bash joshipranjal/screeni-py:latest -c "run_screenipy.sh --cli" + ``` + +Check out this [YouTube Video](https://youtu.be/2HMN0ac4H20) for a detailed installation guide. + +## Contributing + +* Please feel free to Suggest improvements/report bugs by creating an issue. +* Please follow the [Guidelines for Contributing](https://github.com/pranjal-joshi/Screeni-py/blob/new-features/CONTRIBUTING.md) while making a Pull Request. + +## 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 and the software will not be held liable for your losses. 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 new file mode 100644 index 0000000000000000000000000000000000000000..5db658d76508e6b22ab384b436b538f2c37c36b0 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,66 @@ +# 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/screenshots/NSE_Logo.svg b/screenshots/NSE_Logo.svg new file mode 100644 index 0000000000000000000000000000000000000000..f0f13ba76db3fe065b0bc9e7b07232110602114f --- /dev/null +++ b/screenshots/NSE_Logo.svg @@ -0,0 +1,26 @@ + + + + + + diff --git a/screenshots/config.png b/screenshots/config.png new file mode 100644 index 0000000000000000000000000000000000000000..58aef1c5c1ce6b49257e3783d657c4a37697d42a Binary files /dev/null and b/screenshots/config.png differ diff --git a/screenshots/done.png b/screenshots/done.png new file mode 100644 index 0000000000000000000000000000000000000000..8d193650eebe2c2d769283089d2de66506bf5e20 --- /dev/null +++ b/screenshots/done.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:60dd625c27ace94b27295379ecb4cf38833b2fead1aedfa946af90eaad904c8e +size 315815 diff --git a/screenshots/home.png b/screenshots/home.png new file mode 100644 index 0000000000000000000000000000000000000000..de50ec9bf01d57266e59543a346869b599911e02 Binary files /dev/null and b/screenshots/home.png differ diff --git a/screenshots/results.png b/screenshots/results.png new file mode 100644 index 0000000000000000000000000000000000000000..43d8d921f9093336aa6f547597cf944748394ec0 --- /dev/null +++ b/screenshots/results.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5746d720b270fd8af0e3d26d14b0d69ac8d5d0434d3096b752561c43c193d9a4 +size 318535 diff --git a/screenshots/screening.png b/screenshots/screening.png new file mode 100644 index 0000000000000000000000000000000000000000..c6a875162d904930f56014722201238e2e9d8c8b Binary files /dev/null and b/screenshots/screening.png differ diff --git a/screenshots/screenipy_demo.gif b/screenshots/screenipy_demo.gif new file mode 100644 index 0000000000000000000000000000000000000000..7673ae60181b9029c241bbae3c3b63ff6ec2ffa1 --- /dev/null +++ b/screenshots/screenipy_demo.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:57453b0a381db2484263af8dc647a408154110067f8b88be5ae5a3e14b04b598 +size 2820116 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 new file mode 100644 index 0000000000000000000000000000000000000000..336c1e0012642e9e4e71d358d8d2131f37939058 --- /dev/null +++ b/src/streamlit_app.py @@ -0,0 +1,595 @@ +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') + +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)