Skip to content

Commit 74aef72

Browse files
committed
v2.13 - Backtesting Added along with Date picker UI
- Fixed Inside bar detection and enhanced debug for dev release - yfinance bumped to 0.2.32 from 0.1.87 - Data sources used for Nifty predictions are displayed
1 parent c3d5f8b commit 74aef72

File tree

10 files changed

+102
-93
lines changed

10 files changed

+102
-93
lines changed

requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ scipy==1.11.2
2222
# ta-lib
2323
TA-Lib-Precompiled
2424
tabulate
25-
yfinance==0.1.87
25+
# yfinance==0.1.87
26+
yfinance==0.2.32
2627
alive-progress==1.6.2
2728
Pillow
2829
scikit-learn==1.3.2

src/classes/Changelog.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
from classes.ColorText import colorText
99

10-
VERSION = "2.12"
10+
VERSION = "2.13"
1111

1212
changelog = colorText.BOLD + '[ChangeLog]\n' + colorText.END + colorText.BLUE + '''
1313
[1.00 - Beta]
@@ -260,5 +260,9 @@
260260
1. Cosmetic Updates for Position Size Calculator
261261
2. Python base bumped to 3.11.6-slim-bookworm
262262
263+
[2.13]
264+
1. Date based Backtesting Added for Screening
265+
2. Inside bar detection broken - bug fixed
266+
3. Auto enhanced debug on console in dev release
263267
264268
''' + colorText.END

src/classes/ConfigManager.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import sys
99
import os
1010
import glob
11+
import re
1112
import configparser
1213
from datetime import date
1314
from classes.ColorText import colorText
@@ -195,3 +196,11 @@ def checkConfigFile(self):
195196
return True
196197
except FileNotFoundError:
197198
return False
199+
200+
# Get period as a numeric value
201+
def getPeriodNumeric(self):
202+
import re
203+
pattern = re.compile(r'\d+')
204+
result = [int(match.group()) for match in pattern.finditer(self.period)][0]
205+
return result
206+

src/classes/Fetcher.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import requests
1212
import random
1313
import os
14+
import datetime
1415
import yfinance as yf
1516
import pandas as pd
1617
from nsetools import Nse
@@ -35,6 +36,22 @@ def __init__(self, configManager):
3536
self.configManager = configManager
3637
pass
3738

39+
def _getBacktestDate(self, backtest):
40+
try:
41+
end = backtest + datetime.timedelta(days=1)
42+
if "d" in self.configManager.period:
43+
delta = datetime.timedelta(days = self.configManager.getPeriodNumeric())
44+
elif "wk" in self.configManager.period:
45+
delta = datetime.timedelta(days = self.configManager.getPeriodNumeric() * 7)
46+
elif "m" in self.configManager.period:
47+
delta = datetime.timedelta(minutes = self.configManager.getPeriodNumeric())
48+
elif "h" in self.configManager.period:
49+
delta = datetime.timedelta(hours = self.configManager.getPeriodNumeric())
50+
start = end - delta
51+
return [start, end]
52+
except:
53+
return [None, None]
54+
3855
def fetchCodes(self, tickerOption,proxyServer=None):
3956
listStockCodes = []
4057
if tickerOption == 12:
@@ -120,7 +137,7 @@ def fetchStockCodes(self, tickerOption, proxyServer=None):
120137
return listStockCodes
121138

122139
# Fetch stock price data from Yahoo finance
123-
def fetchStockData(self, stockCode, period, duration, proxyServer, screenResultsCounter, screenCounter, totalSymbols, printCounter=False, tickerOption=None):
140+
def fetchStockData(self, stockCode, period, duration, proxyServer, screenResultsCounter, screenCounter, totalSymbols, backtestDate=None, printCounter=False, tickerOption=None):
124141
with SuppressOutput(suppress_stdout=True, suppress_stderr=True):
125142
append_exchange = ".NS"
126143
if tickerOption == 15:
@@ -131,7 +148,9 @@ def fetchStockData(self, stockCode, period, duration, proxyServer, screenResults
131148
interval=duration,
132149
proxy=proxyServer,
133150
progress=False,
134-
timeout=10
151+
timeout=10,
152+
start=self._getBacktestDate(backtest=backtestDate)[0],
153+
end=self._getBacktestDate(backtest=backtestDate)[1]
135154
)
136155
if printCounter:
137156
sys.stdout.write("\r\033[K")
@@ -163,13 +182,15 @@ def fetchLatestNiftyDaily(self, proxyServer=None):
163182
tickers="GC=F",
164183
period='5d',
165184
interval='1d',
185+
proxy=proxyServer,
166186
progress=False,
167187
timeout=10
168188
).add_prefix(prefix='gold_')
169189
crude = yf.download(
170190
tickers="CL=F",
171191
period='5d',
172192
interval='1d',
193+
proxy=proxyServer,
173194
progress=False,
174195
timeout=10
175196
).add_prefix(prefix='crude_')

src/classes/ParallelProcessing.py

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import sys
1313
import os
1414
import pytz
15+
import traceback
1516
from queue import Empty
1617
from datetime import datetime
1718
import classes.Fetcher as Fetcher
@@ -59,7 +60,7 @@ def run(self):
5960
sys.exit(0)
6061

6162
def screenStocks(self, tickerOption, executeOption, reversalOption, maLength, daysForLowestVolume, minRSI, maxRSI, respChartPattern, insideBarToLookback, totalSymbols,
62-
configManager, fetcher, screener, candlePatterns, stock, newlyListedOnly, downloadOnly, vectorSearch, printCounter=False):
63+
configManager, fetcher, screener, candlePatterns, stock, newlyListedOnly, downloadOnly, vectorSearch, isDevVersion, backtestDate, printCounter=False):
6364
screenResults = pd.DataFrame(columns=[
6465
'Stock', 'Consolidating', 'Breaking-Out', 'MA-Signal', 'Volume', 'LTP', 'RSI', 'Trend', 'Pattern'])
6566
screeningDictionary = {'Stock': "", 'Consolidating': "", 'Breaking-Out': "",
@@ -78,14 +79,18 @@ def screenStocks(self, tickerOption, executeOption, reversalOption, maLength, da
7879
period = configManager.period
7980

8081
if (self.stockDict.get(stock) is None) or (configManager.cacheEnabled is False) or self.isTradingTime or downloadOnly:
81-
data = fetcher.fetchStockData(stock,
82-
period,
83-
configManager.duration,
84-
self.proxyServer,
85-
self.screenResultsCounter,
86-
self.screenCounter,
87-
totalSymbols,
88-
tickerOption=tickerOption)
82+
try:
83+
data = fetcher.fetchStockData(stock,
84+
period,
85+
configManager.duration,
86+
self.proxyServer,
87+
self.screenResultsCounter,
88+
self.screenCounter,
89+
totalSymbols,
90+
backtestDate=backtestDate,
91+
tickerOption=tickerOption)
92+
except Exception as e:
93+
return screeningDictionary, saveDictionary
8994
if configManager.cacheEnabled is True and not self.isTradingTime and (self.stockDict.get(stock) is None) or downloadOnly:
9095
self.stockDict[stock] = data.to_dict('split')
9196
if downloadOnly:
@@ -165,7 +170,7 @@ def screenStocks(self, tickerOption, executeOption, reversalOption, maLength, da
165170
isConfluence = screener.validateConfluence(stock, processedData, screeningDictionary, saveDictionary, percentage=insideBarToLookback)
166171
else:
167172
isInsideBar = screener.validateInsideBar(processedData, screeningDictionary, saveDictionary, chartPattern=respChartPattern, daysToLookback=insideBarToLookback)
168-
173+
169174
with SuppressOutput(suppress_stderr=True, suppress_stdout=True):
170175
if maLength is not None and executeOption == 6 and reversalOption == 6:
171176
isNR = screener.validateNarrowRange(processedData, screeningDictionary, saveDictionary, nr=maLength)
@@ -261,6 +266,9 @@ def screenStocks(self, tickerOption, executeOption, reversalOption, maLength, da
261266
except KeyError:
262267
pass
263268
except Exception as e:
269+
if isDevVersion:
270+
print("[!] Dev Traceback:")
271+
traceback.print_exc()
264272
if printCounter:
265273
print(colorText.FAIL +
266274
("\n[+] Exception Occured while Screening %s! Skipping this stock.." % stock) + colorText.END)

src/classes/Screener.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import keras
1414
import time
1515
import classes.Utility as Utility
16+
from copy import copy
1617
from advanced_ta import LorentzianClassification
1718
from classes.Utility import isGui
1819
from sklearn.preprocessing import StandardScaler
@@ -253,6 +254,7 @@ def findBreakout(self, data, screenDict, saveDict, daysToLookback):
253254
# Validate 'Inside Bar' structure for recent days
254255
def validateInsideBar(self, data, screenDict, saveDict, chartPattern=1, daysToLookback=5):
255256
orgData = data
257+
daysToLookback = int(daysToLookback)
256258
for i in range(daysToLookback, round(daysToLookback*0.5)-1, -1):
257259
if i == 2:
258260
return 0 # Exit if only last 2 candles are left
@@ -610,6 +612,7 @@ def getNiftyPrediction(self, data, proxyServer):
610612
pass
611613
#
612614
model, pkl = Utility.tools.getNiftyModel(proxyServer=proxyServer)
615+
datacopy = copy(data[pkl['columns']])
613616
with SuppressOutput(suppress_stderr=True, suppress_stdout=True):
614617
data = data[pkl['columns']]
615618
### v2 Preprocessing
@@ -630,7 +633,7 @@ def getNiftyPrediction(self, data, proxyServer):
630633
print(colorText.BOLD + colorText.BLUE + "\n" + "[+] Nifty AI Prediction -> " + colorText.END + colorText.BOLD + "Market may Open {} next day! {}".format(out, sug) + colorText.END)
631634
print(colorText.BOLD + colorText.BLUE + "\n" + "[+] Nifty AI Prediction -> " + colorText.END + "Probability/Strength of Prediction = {}%".format(Utility.tools.getSigmoidConfidence(pred[0])))
632635
if isGui():
633-
return pred, 'BULLISH' if pred <= 0.5 else 'BEARISH', Utility.tools.getSigmoidConfidence(pred[0])
636+
return pred, 'BULLISH' if pred <= 0.5 else 'BEARISH', Utility.tools.getSigmoidConfidence(pred[0]), pd.DataFrame(datacopy.iloc[-1]).T
634637
return pred
635638

636639
def monitorFiveEma(self, proxyServer, fetcher, result_df, last_signal, risk_reward = 3):

src/classes/Utility.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,14 @@ def alertSound(beeps=3, delay=0.2):
377377
print('\a')
378378
sleep(delay)
379379

380+
def isBacktesting(backtestDate):
381+
try:
382+
if datetime.date.today() != backtestDate:
383+
return True
384+
return False
385+
except:
386+
return False
387+
380388
def isDocker():
381389
if 'SCREENIPY_DOCKER' in os.environ:
382390
return True

src/release.md

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@ Screeni-py is now on **YouTube** for additional help! - Thank You for your suppo
77

88
⚠️ **Executable files (.exe, .bin and .run) are now DEPRECATED! Please Switch to Docker**
99

10-
1. **Position Size Calculator** tab added for Better and Quick Risk Management!
11-
2. **Lorentzian Classification** (by @jdehorty) added for enhanced accuracy for your trades - - Try `Option > 6 > 7` 🤯
12-
3. **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`
13-
4. **US S&P 500** Index added for scanning US markets.
14-
5. **Search Similar Stocks** Added using Vector Similarity search - Try `Search Similar Stocks`.
15-
6. New Index - **F&O Stocks Only** Added for F&O traders with modified screening criterias.
10+
1. **Backtesting** Added for Screening Patterns to Develope and Test Strategies!
11+
2. **Position Size Calculator** tab added for Better and Quick Risk Management!
12+
3. **Lorentzian Classification** (by @jdehorty) added for enhanced accuracy for your trades - - Try `Option > 6 > 7` 🤯
13+
4. **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`
14+
5. **US S&P 500** Index added for scanning US markets.
15+
6. **Search Similar Stocks** Added using Vector Similarity search - Try `Search Similar Stocks`.
1616
7. New Screener **Buy at Trendline** added for Swing/Mid/Long term traders - Try `Option > 7 > 5`.
1717

1818
## Installation Guide
@@ -38,6 +38,10 @@ Screeni-py is now on **YouTube** for additional help! - Thank You for your suppo
3838
| ![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"` |
3939
| ![Docker](https://img.shields.io/badge/docker-%230db7ed.svg?style=for-the-badge&logo=docker&logoColor=white) | `dev` | `docker pull joshipranjal/screeni-py:dev` | GUI WebApp | `docker run -p 8501:8501 joshipranjal/screeni-py:dev` |
4040

41+
### Docker Issues? Troubleshooting Guide:
42+
43+
Read this [troubleshooting guide](https://github.com/pranjal-joshi/Screeni-py/discussions/217) for Windows to fix most common Docker issues easily!
44+
4145
**Why we shifted to Docker from the Good old EXEs?**
4246

4347
| Executable/Binary File | Docker |

src/screenipy.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
import urllib
2424
import numpy as np
2525
import pandas as pd
26-
from datetime import datetime
26+
from datetime import datetime, date
2727
from time import sleep
2828
from tabulate import tabulate
2929
import multiprocessing
@@ -170,13 +170,13 @@ def initExecution():
170170
return tickerOption, executeOption
171171

172172
# Main function
173-
def main(testing=False, testBuild=False, downloadOnly=False, execute_inputs:list = []):
173+
def main(testing=False, testBuild=False, downloadOnly=False, execute_inputs:list = [], isDevVersion=None, backtestDate=date.today()):
174174
global screenCounter, screenResultsCounter, stockDict, loadedStockData, keyboardInterruptEvent, loadCount, maLength, newlyListedOnly, vectorSearch
175175
screenCounter = multiprocessing.Value('i', 1)
176176
screenResultsCounter = multiprocessing.Value('i', 0)
177177
keyboardInterruptEvent = multiprocessing.Manager().Event()
178178

179-
if stockDict is None:
179+
if stockDict is None or Utility.tools.isBacktesting(backtestDate=backtestDate):
180180
stockDict = multiprocessing.Manager().dict()
181181
loadCount = 0
182182

@@ -371,7 +371,7 @@ def main(testing=False, testBuild=False, downloadOnly=False, execute_inputs:list
371371
input('')
372372
sys.exit(0)
373373

374-
if not Utility.tools.isTradingTime() and configManager.cacheEnabled and not loadedStockData and not testing:
374+
if not Utility.tools.isTradingTime() and configManager.cacheEnabled and not loadedStockData and not testing and not Utility.tools.isBacktesting(backtestDate=backtestDate):
375375
Utility.tools.loadStockData(stockDict, configManager, proxyServer)
376376
loadedStockData = True
377377
loadCount = len(stockDict)
@@ -380,7 +380,7 @@ def main(testing=False, testBuild=False, downloadOnly=False, execute_inputs:list
380380
"[+] Starting Stock Screening.. Press Ctrl+C to stop!\n")
381381

382382
items = [(tickerOption, executeOption, reversalOption, maLength, daysForLowestVolume, minRSI, maxRSI, respChartPattern, insideBarToLookback, len(listStockCodes),
383-
configManager, fetcher, screener, candlePatterns, stock, newlyListedOnly, downloadOnly, vectorSearch)
383+
configManager, fetcher, screener, candlePatterns, stock, newlyListedOnly, downloadOnly, vectorSearch, isDevVersion, backtestDate)
384384
for stock in listStockCodes]
385385

386386
tasks_queue = multiprocessing.JoinableQueue()
@@ -498,7 +498,7 @@ def main(testing=False, testBuild=False, downloadOnly=False, execute_inputs:list
498498

499499
print(colorText.BOLD + colorText.GREEN +
500500
f"[+] Found {len(screenResults)} Stocks." + colorText.END)
501-
if configManager.cacheEnabled and not Utility.tools.isTradingTime() and not testing:
501+
if configManager.cacheEnabled and not Utility.tools.isTradingTime() and not testing and not Utility.tools.isBacktesting(backtestDate=backtestDate):
502502
print(colorText.BOLD + colorText.GREEN +
503503
"[+] Caching Stock Data for future use, Please Wait... " + colorText.END, end='')
504504
Utility.tools.saveStockData(

0 commit comments

Comments
 (0)