pkscreener 0.46.20250909.768__cp312-cp312-win_amd64.whl → 0.46.20250910.770__cp312-cp312-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pkscreener-0.46.20250910.770.data/purelib/pkscreener/MainApplication.py +721 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/README.txt +5 -5
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/AssetsManager.py +12 -12
- pkscreener-0.46.20250910.770.data/purelib/pkscreener/classes/MenuManager.py +2373 -0
- pkscreener-0.46.20250910.770.data/purelib/pkscreener/classes/PKScreenerMain.py +916 -0
- pkscreener-0.46.20250910.770.data/purelib/pkscreener/classes/__init__.py +1 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/pkscreenercli.py +1 -0
- {pkscreener-0.46.20250909.768.dist-info → pkscreener-0.46.20250910.770.dist-info}/METADATA +7 -7
- pkscreener-0.46.20250910.770.dist-info/RECORD +61 -0
- pkscreener-0.46.20250909.768.data/purelib/pkscreener/classes/__init__.py +0 -1
- pkscreener-0.46.20250909.768.dist-info/RECORD +0 -58
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/Disclaimer.txt +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/LICENSE-Others.txt +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/LICENSE.txt +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/LogoWM.txt +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/__init__.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/ArtTexts.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/Backtest.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/Barometer.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/BaseScreeningStatistics.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/CandlePatterns.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/Changelog.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/ConfigManager.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/ConsoleMenuUtility.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/ConsoleUtility.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/Fetcher.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/GlobalStore.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/ImageUtility.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/MarketMonitor.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/MarketStatus.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/MenuOptions.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/Messenger.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/OtaUpdater.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/PKAnalytics.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/PKDataService.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/PKDemoHandler.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/PKMarketOpenCloseAnalyser.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/PKPremiumHandler.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/PKScanRunner.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/PKScheduledTaskProgress.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/PKScheduler.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/PKSpreadsheets.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/PKTask.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/PKUserRegistration.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/Pktalib.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/Portfolio.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/PortfolioXRay.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/ScreeningStatistics.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/StockScreener.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/StockSentiment.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/UserMenuChoicesHandler.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/Utility.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/WorkflowManager.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/keys.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/courbd.ttf +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/globals.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/pkscreener.ini +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/pkscreenerbot.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/requirements.txt +0 -0
- {pkscreener-0.46.20250909.768.dist-info → pkscreener-0.46.20250910.770.dist-info}/WHEEL +0 -0
- {pkscreener-0.46.20250909.768.dist-info → pkscreener-0.46.20250910.770.dist-info}/entry_points.txt +0 -0
- {pkscreener-0.46.20250909.768.dist-info → pkscreener-0.46.20250910.770.dist-info}/licenses/LICENSE +0 -0
- {pkscreener-0.46.20250909.768.dist-info → pkscreener-0.46.20250910.770.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,721 @@
|
|
1
|
+
"""
|
2
|
+
Main application refactoring for PKScreener
|
3
|
+
This module breaks down the massive main function into manageable classes
|
4
|
+
while preserving all existing functionality.
|
5
|
+
"""
|
6
|
+
|
7
|
+
import multiprocessing
|
8
|
+
import os
|
9
|
+
import sys
|
10
|
+
import time
|
11
|
+
import pandas as pd
|
12
|
+
from datetime import timedelta
|
13
|
+
from typing import Dict, List, Tuple, Any, Optional
|
14
|
+
|
15
|
+
# Import all necessary modules (preserving original imports)
|
16
|
+
import urllib.error
|
17
|
+
from alive_progress import alive_bar
|
18
|
+
|
19
|
+
# Import project-specific modules
|
20
|
+
from pkscreener.classes import colorText, Utility, PKDateUtilities, ConsoleUtility, PKScanRunner
|
21
|
+
from pkscreener.classes.ConfigManager import ConfigManager
|
22
|
+
from pkscreener.classes.Fetcher import fetcher
|
23
|
+
from pkscreener.classes.PKMenu import m0, m1, m2, m3, m4
|
24
|
+
from pkscreener.classes.PortfolioXRay import PortfolioXRay
|
25
|
+
from pkscreener.classes.log import default_logger
|
26
|
+
from pkscreener.classes.PKAnalytics import PKAnalyticsService
|
27
|
+
from pkscreener.classes.PKPremiumHandler import PKPremiumHandler
|
28
|
+
from pkscreener.classes.PKSpreadsheets import PKSpreadsheets
|
29
|
+
from pkscreener.globals import *
|
30
|
+
|
31
|
+
class ApplicationState:
|
32
|
+
"""Manages the global application state and configuration"""
|
33
|
+
|
34
|
+
def __init__(self, userArgs=None):
|
35
|
+
self.userArgs = userArgs
|
36
|
+
self.initialize_globals()
|
37
|
+
self.setup_initial_state()
|
38
|
+
|
39
|
+
def initialize_globals(self):
|
40
|
+
"""Initialize all global variables with their default values"""
|
41
|
+
global lastScanOutputStockCodes, scanCycleRunning, runCleanUp, test_messages_queue
|
42
|
+
global show_saved_diff_results, criteria_dateTime, analysis_dict, mp_manager
|
43
|
+
global listStockCodes, screenResults, selectedChoice, defaultAnswer, menuChoiceHierarchy
|
44
|
+
global screenCounter, screenResultsCounter, stockDictPrimary, stockDictSecondary
|
45
|
+
global userPassedArgs, loadedStockData, keyboardInterruptEvent, loadCount, maLength
|
46
|
+
global newlyListedOnly, keyboardInterruptEventFired, strategyFilter, elapsed_time, start_time
|
47
|
+
|
48
|
+
selectedChoice = {"0": "", "1": "", "2": "", "3": "", "4": ""}
|
49
|
+
elapsed_time = 0 if not scanCycleRunning else elapsed_time
|
50
|
+
start_time = 0 if not scanCycleRunning else start_time
|
51
|
+
|
52
|
+
def setup_initial_state(self):
|
53
|
+
"""Set up the initial application state based on user arguments"""
|
54
|
+
global testing, testBuild, downloadOnly, startupoptions, user, defaultAnswer, userPassedArgs
|
55
|
+
global runOptionName, options, strategyFilter, test_messages_queue
|
56
|
+
|
57
|
+
testing = False if self.userArgs is None else (self.userArgs.testbuild and self.userArgs.prodbuild)
|
58
|
+
testBuild = False if self.userArgs is None else (self.userArgs.testbuild and not testing)
|
59
|
+
downloadOnly = False if self.userArgs is None else self.userArgs.download
|
60
|
+
startupoptions = None if self.userArgs is None else self.userArgs.options
|
61
|
+
user = None if self.userArgs is None else self.userArgs.user
|
62
|
+
defaultAnswer = None if self.userArgs is None else self.userArgs.answerdefault
|
63
|
+
userPassedArgs = self.userArgs
|
64
|
+
runOptionName = ""
|
65
|
+
options = []
|
66
|
+
strategyFilter = []
|
67
|
+
test_messages_queue = []
|
68
|
+
|
69
|
+
describeUser()
|
70
|
+
|
71
|
+
|
72
|
+
class MenuHandler:
|
73
|
+
"""Handles all menu-related operations and user interactions"""
|
74
|
+
|
75
|
+
def __init__(self, app_state):
|
76
|
+
self.app_state = app_state
|
77
|
+
self.configManager = ConfigManager()
|
78
|
+
|
79
|
+
def process_top_level_menu(self):
|
80
|
+
"""Process the top-level menu options"""
|
81
|
+
global screenResults, saveResults, options, menuOption, indexOption, executeOption
|
82
|
+
|
83
|
+
screenResults, saveResults = PKScanRunner.initDataframes()
|
84
|
+
options, menuOption, indexOption, executeOption = getTopLevelMenuChoices(
|
85
|
+
self.app_state.startupoptions, self.app_state.testBuild,
|
86
|
+
self.app_state.downloadOnly, defaultAnswer=self.app_state.defaultAnswer
|
87
|
+
)
|
88
|
+
|
89
|
+
selectedMenu = initExecution(menuOption=menuOption)
|
90
|
+
menuOption = selectedMenu.menuKey
|
91
|
+
|
92
|
+
return selectedMenu, menuOption, indexOption, executeOption
|
93
|
+
|
94
|
+
def handle_premium_checks(self, selectedMenu):
|
95
|
+
"""Check if user has premium access for premium features"""
|
96
|
+
global menuOption, indexOption, executeOption
|
97
|
+
|
98
|
+
if menuOption in ["F", "M", "S", "B", "G", "C", "P", "D"] or selectedMenu.isPremium:
|
99
|
+
ensureMenusLoaded(menuOption, indexOption, executeOption)
|
100
|
+
if not PKPremiumHandler.hasPremium(selectedMenu):
|
101
|
+
PKAnalyticsService().send_event(f"non_premium_user_{menuOption}_{indexOption}_{executeOption}")
|
102
|
+
PKAnalyticsService().send_event("app_exit")
|
103
|
+
sys.exit(0)
|
104
|
+
|
105
|
+
def handle_special_menu_options(self, menuOption, selectedMenu):
|
106
|
+
"""Handle special menu options like monitor, download, etc."""
|
107
|
+
if menuOption in ["M", "D", "I", "L", "F"]:
|
108
|
+
self.handle_monitor_download_options(menuOption, selectedMenu)
|
109
|
+
elif menuOption in ["P"]:
|
110
|
+
self.handle_predefined_scans()
|
111
|
+
elif menuOption in ["X", "T", "E", "Y", "U", "H", "C"]:
|
112
|
+
self.handle_scanner_menu_options()
|
113
|
+
elif menuOption in ["B", "G"]:
|
114
|
+
self.handle_backtest_options()
|
115
|
+
elif menuOption in ["S"]:
|
116
|
+
self.handle_strategy_options()
|
117
|
+
|
118
|
+
def handle_monitor_download_options(self, menuOption, selectedMenu):
|
119
|
+
"""Handle monitor and download menu options"""
|
120
|
+
launcher = f'"{sys.argv[0]}"' if " " in sys.argv[0] else sys.argv[0]
|
121
|
+
launcher = f"python3.12 {launcher}" if (launcher.endswith(".py\"") or launcher.endswith(".py")) else launcher
|
122
|
+
|
123
|
+
if menuOption in ["M"]:
|
124
|
+
OutputControls().printOutput(f"{colorText.GREEN}Launching PKScreener in monitoring mode. If it does not launch, please try with the following:{colorText.END}\n{colorText.FAIL}{launcher} --systemlaunched -a Y -m 'X'{colorText.END}\n{colorText.WARN}Press Ctrl + C to exit monitoring mode.{colorText.END}")
|
125
|
+
PKAnalyticsService().send_event(f"monitor_{menuOption}")
|
126
|
+
time.sleep(2)
|
127
|
+
os.system(f"{launcher} --systemlaunched -a Y -m 'X'")
|
128
|
+
elif menuOption in ["D"]:
|
129
|
+
self.handle_download_options(selectedMenu, launcher)
|
130
|
+
elif menuOption in ["L"]:
|
131
|
+
PKAnalyticsService().send_event(f"{menuOption}")
|
132
|
+
OutputControls().printOutput(f"{colorText.GREEN}Launching PKScreener to collect logs. If it does not launch, please try with the following:{colorText.END}\n{colorText.FAIL}{launcher} -a Y -l{colorText.END}\n{colorText.WARN}Press Ctrl + C to exit at any time.{colorText.END}")
|
133
|
+
time.sleep(2)
|
134
|
+
os.system(f"{launcher} -a Y -l")
|
135
|
+
elif menuOption in ["F"]:
|
136
|
+
self.handle_favorites_option()
|
137
|
+
|
138
|
+
def handle_download_options(self, selectedMenu, launcher):
|
139
|
+
"""Handle download-specific menu options"""
|
140
|
+
ConsoleUtility.PKConsoleTools.clearScreen(forceTop=True)
|
141
|
+
m1.renderForMenu(selectedMenu)
|
142
|
+
selDownloadOption = input(colorText.FAIL + " [+] Select option: ") or "D"
|
143
|
+
OutputControls().printOutput(colorText.END, end="")
|
144
|
+
|
145
|
+
if selDownloadOption.upper() == "D":
|
146
|
+
OutputControls().printOutput(f"{colorText.GREEN}Launching PKScreener to Download daily OHLC data. If it does not launch, please try with the following:{colorText.END}\n{colorText.FAIL}{launcher} -a Y -e -d{colorText.END}\n{colorText.WARN}Press Ctrl + C to exit at any time.{colorText.END}")
|
147
|
+
PKAnalyticsService().send_event(f"{menuOption}_{selDownloadOption.upper()}")
|
148
|
+
time.sleep(2)
|
149
|
+
os.system(f"{launcher} -a Y -e -d")
|
150
|
+
return None, None
|
151
|
+
# ... handle other download options
|
152
|
+
|
153
|
+
def handle_favorites_option(self):
|
154
|
+
"""Handle favorites menu option"""
|
155
|
+
global indexOption, selectedChoice, listStockCodes
|
156
|
+
|
157
|
+
PKAnalyticsService().send_event(f"{menuOption}")
|
158
|
+
indexOption = 0
|
159
|
+
selectedChoice["0"] = "F"
|
160
|
+
selectedChoice["1"] = "0"
|
161
|
+
executeOption = None
|
162
|
+
shouldSuppress = not OutputControls().enableMultipleLineOutput
|
163
|
+
|
164
|
+
if userPassedArgs is not None and userPassedArgs.options is not None and len(userPassedArgs.options.split(":")) >= 3:
|
165
|
+
stockOptions = userPassedArgs.options.split(":")
|
166
|
+
stockOptions = userPassedArgs.options.split(":")[2 if len(stockOptions)<=3 else 3]
|
167
|
+
listStockCodes = stockOptions.replace(".",",").split(",")
|
168
|
+
|
169
|
+
if listStockCodes is None or len(listStockCodes) == 0:
|
170
|
+
with SuppressOutput(suppress_stderr=shouldSuppress, suppress_stdout=shouldSuppress):
|
171
|
+
listStockCodes = fetcher.fetchStockCodes(tickerOption=0, stockCode=None)
|
172
|
+
|
173
|
+
ConsoleUtility.PKConsoleTools.clearScreen(clearAlways=True, forceTop=True)
|
174
|
+
|
175
|
+
def handle_predefined_scans(self):
|
176
|
+
"""Handle predefined scan options"""
|
177
|
+
global predefinedOption, selPredefinedOption, selIndexOption, selectedChoice
|
178
|
+
|
179
|
+
predefinedOption = None
|
180
|
+
selPredefinedOption = None
|
181
|
+
selIndexOption = None
|
182
|
+
|
183
|
+
if len(options) >= 3:
|
184
|
+
predefinedOption = str(options[1]) if str(options[1]).isnumeric() else '1'
|
185
|
+
selPredefinedOption = str(options[2]) if str(options[2]).isnumeric() else '1'
|
186
|
+
if len(options) >= 4:
|
187
|
+
selIndexOption = str(options[3]) if str(options[3]).isnumeric() else '12'
|
188
|
+
|
189
|
+
selectedChoice["0"] = "P"
|
190
|
+
updateMenuChoiceHierarchy()
|
191
|
+
selectedMenu = m0.find(menuOption)
|
192
|
+
m1.renderForMenu(selectedMenu, asList=(userPassedArgs is not None and userPassedArgs.options is not None))
|
193
|
+
|
194
|
+
# ... handle predefined scan logic
|
195
|
+
|
196
|
+
|
197
|
+
class ScannerHandler:
|
198
|
+
"""Handles scanner operations and screening logic"""
|
199
|
+
|
200
|
+
def __init__(self, app_state):
|
201
|
+
self.app_state = app_state
|
202
|
+
self.configManager = ConfigManager()
|
203
|
+
|
204
|
+
def execute_scanner(self, menuOption, indexOption, executeOption):
|
205
|
+
"""Execute the selected scanner with appropriate parameters"""
|
206
|
+
global volumeRatio, minRSI, maxRSI, insideBarToLookback, respChartPattern
|
207
|
+
global daysForLowestVolume, reversalOption, maLength
|
208
|
+
|
209
|
+
volumeRatio = self.configManager.volumeRatio
|
210
|
+
|
211
|
+
if executeOption == 3:
|
212
|
+
userPassedArgs.maxdisplayresults = max(self.configManager.maxdisplayresults, 2000)
|
213
|
+
elif executeOption == 4:
|
214
|
+
daysForLowestVolume = self.handle_scanner_execute_option_4(executeOption, options)
|
215
|
+
elif executeOption == 5:
|
216
|
+
self.handle_rsi_scanner(executeOption, options)
|
217
|
+
elif executeOption == 6:
|
218
|
+
self.handle_reversal_scanner(executeOption, options)
|
219
|
+
elif executeOption == 7:
|
220
|
+
self.handle_chart_pattern_scanner(executeOption, options)
|
221
|
+
# ... handle other execute options
|
222
|
+
|
223
|
+
def handle_rsi_scanner(self, executeOption, options):
|
224
|
+
"""Handle RSI scanner configuration"""
|
225
|
+
global minRSI, maxRSI, selectedMenu
|
226
|
+
|
227
|
+
selectedMenu = m2.find(str(executeOption))
|
228
|
+
if len(options) >= 5:
|
229
|
+
if str(options[3]).isnumeric():
|
230
|
+
minRSI = int(options[3])
|
231
|
+
maxRSI = int(options[4])
|
232
|
+
elif str(options[3]).upper() == "D" or userPassedArgs.systemlaunched:
|
233
|
+
minRSI = 60
|
234
|
+
maxRSI = 75
|
235
|
+
else:
|
236
|
+
minRSI, maxRSI = ConsoleMenuUtility.PKConsoleMenuTools.promptRSIValues()
|
237
|
+
|
238
|
+
if not minRSI and not maxRSI:
|
239
|
+
OutputControls().printOutput(
|
240
|
+
colorText.FAIL
|
241
|
+
+ "\n [+] Error: Invalid values for RSI! Values should be in range of 0 to 100. Please try again!"
|
242
|
+
+ colorText.END
|
243
|
+
)
|
244
|
+
OutputControls().takeUserInput("Press <Enter> to continue...")
|
245
|
+
return None, None
|
246
|
+
|
247
|
+
def handle_reversal_scanner(self, executeOption, options):
|
248
|
+
"""Handle reversal scanner configuration"""
|
249
|
+
global reversalOption, maLength, selectedMenu
|
250
|
+
|
251
|
+
selectedMenu = m2.find(str(executeOption))
|
252
|
+
if len(options) >= 4:
|
253
|
+
reversalOption = int(options[3])
|
254
|
+
if reversalOption in [4, 6, 7, 10]:
|
255
|
+
if len(options) >= 5:
|
256
|
+
if str(options[4]).isnumeric():
|
257
|
+
maLength = int(options[4])
|
258
|
+
elif str(options[4]).upper() == "D" or userPassedArgs.systemlaunched:
|
259
|
+
maLength = 50 if reversalOption == 4 else (3 if reversalOption in [7] else (2 if reversalOption in [10] else 7))
|
260
|
+
elif defaultAnswer == "Y" and user is not None:
|
261
|
+
maLength = 50 if reversalOption == 4 else (3 if reversalOption == 7 else 7)
|
262
|
+
else:
|
263
|
+
reversalOption, maLength = ConsoleMenuUtility.PKConsoleMenuTools.promptReversalScreening(selectedMenu)
|
264
|
+
else:
|
265
|
+
reversalOption, maLength = ConsoleMenuUtility.PKConsoleMenuTools.promptReversalScreening(selectedMenu)
|
266
|
+
|
267
|
+
if reversalOption is None or reversalOption == 0 or maLength == 0:
|
268
|
+
return None, None
|
269
|
+
else:
|
270
|
+
selectedChoice["3"] = str(reversalOption)
|
271
|
+
if str(reversalOption) in ["7", "10"]:
|
272
|
+
selectedChoice["4"] = str(maLength)
|
273
|
+
|
274
|
+
|
275
|
+
class BacktestHandler:
|
276
|
+
"""Handles backtesting operations and results processing"""
|
277
|
+
|
278
|
+
def __init__(self, app_state):
|
279
|
+
self.app_state = app_state
|
280
|
+
self.configManager = ConfigManager()
|
281
|
+
|
282
|
+
def handle_backtest_options(self):
|
283
|
+
"""Handle backtest menu options"""
|
284
|
+
global backtestPeriod
|
285
|
+
|
286
|
+
backtestPeriod = 0
|
287
|
+
if len(options) >= 2:
|
288
|
+
if str(indexOption).isnumeric():
|
289
|
+
backtestPeriod = int(indexOption)
|
290
|
+
if len(options) >= 4:
|
291
|
+
indexOption = executeOption
|
292
|
+
executeOption = options[3]
|
293
|
+
del options[1]
|
294
|
+
|
295
|
+
indexOption, executeOption, backtestPeriod = takeBacktestInputs(
|
296
|
+
str(menuOption).upper(), indexOption, executeOption, backtestPeriod
|
297
|
+
)
|
298
|
+
backtestPeriod = backtestPeriod * self.configManager.backtestPeriodFactor
|
299
|
+
|
300
|
+
def process_backtest_results(self, backtest_df):
|
301
|
+
"""Process and display backtest results"""
|
302
|
+
if backtest_df is not None and len(backtest_df) > 0:
|
303
|
+
ConsoleUtility.PKConsoleTools.clearScreen(forceTop=True)
|
304
|
+
df_xray = prepareGroupedXRay(backtestPeriod, backtest_df)
|
305
|
+
summary_df, sorting, sortKeys = FinishBacktestDataCleanup(backtest_df, df_xray)
|
306
|
+
|
307
|
+
while sorting:
|
308
|
+
sorting = showSortedBacktestData(backtest_df, summary_df, sortKeys)
|
309
|
+
|
310
|
+
if defaultAnswer is None:
|
311
|
+
OutputControls().takeUserInput("Press <Enter> to continue...")
|
312
|
+
else:
|
313
|
+
OutputControls().printOutput("Finished backtesting with no results to show!")
|
314
|
+
|
315
|
+
|
316
|
+
class StrategyHandler:
|
317
|
+
"""Handles strategy-related operations and filtering"""
|
318
|
+
|
319
|
+
def __init__(self, app_state):
|
320
|
+
self.app_state = app_state
|
321
|
+
|
322
|
+
def handle_strategy_options(self):
|
323
|
+
"""Handle strategy menu options"""
|
324
|
+
global userOption, strategyFilter, menuOption, indexOption, executeOption, selectedChoice
|
325
|
+
|
326
|
+
if len(options) >= 2:
|
327
|
+
userOption = options[1]
|
328
|
+
|
329
|
+
if defaultAnswer is None:
|
330
|
+
selectedMenu = m0.find(menuOption)
|
331
|
+
m1.strategyNames = PortfolioXRay.strategyNames()
|
332
|
+
m1.renderForMenu(selectedMenu=selectedMenu)
|
333
|
+
|
334
|
+
try:
|
335
|
+
userOption = input(colorText.FAIL + " [+] Select option: ")
|
336
|
+
OutputControls().printOutput(colorText.END, end="")
|
337
|
+
|
338
|
+
if userOption == "":
|
339
|
+
userOption = "37" # NoFilter
|
340
|
+
elif userOption == "38":
|
341
|
+
userOption = input(colorText.FAIL + " [+] Enter Exact Pattern name:")
|
342
|
+
OutputControls().printOutput(colorText.END, end="")
|
343
|
+
|
344
|
+
if userOption == "":
|
345
|
+
userOption = "37" # NoFilter
|
346
|
+
else:
|
347
|
+
strategyFilter.append(f"[P]{userOption}")
|
348
|
+
userOption = "38"
|
349
|
+
except EOFError:
|
350
|
+
userOption = "37" # NoFilter
|
351
|
+
except Exception as e:
|
352
|
+
default_logger().debug(e, exc_info=True)
|
353
|
+
|
354
|
+
userOption = userOption.upper()
|
355
|
+
|
356
|
+
if userOption == "M":
|
357
|
+
ConsoleUtility.PKConsoleTools.clearScreen(forceTop=True)
|
358
|
+
return None, None
|
359
|
+
elif userOption == "Z":
|
360
|
+
handleExitRequest(userOption)
|
361
|
+
return None, None
|
362
|
+
elif userOption == "S":
|
363
|
+
self.handle_strategy_summary()
|
364
|
+
else:
|
365
|
+
self.apply_strategy_filters(userOption)
|
366
|
+
|
367
|
+
def handle_strategy_summary(self):
|
368
|
+
"""Handle strategy summary display"""
|
369
|
+
OutputControls().printOutput(
|
370
|
+
colorText.GREEN
|
371
|
+
+ " [+] Collecting all metrics for summarising..."
|
372
|
+
+ colorText.END
|
373
|
+
)
|
374
|
+
|
375
|
+
savedValue = configManager.showPastStrategyData
|
376
|
+
configManager.showPastStrategyData = True
|
377
|
+
df_all = PortfolioXRay.summariseAllStrategies()
|
378
|
+
|
379
|
+
if df_all is not None and len(df_all) > 0:
|
380
|
+
OutputControls().printOutput(
|
381
|
+
colorText.miniTabulator().tabulate(
|
382
|
+
df_all,
|
383
|
+
headers="keys",
|
384
|
+
tablefmt=colorText.No_Pad_GridFormat,
|
385
|
+
showindex=False,
|
386
|
+
maxcolwidths=Utility.tools.getMaxColumnWidths(df_all)
|
387
|
+
).encode("utf-8").decode(STD_ENCODING)
|
388
|
+
)
|
389
|
+
showBacktestResults(df_all, sortKey="Scanner", optionalName="InsightsSummary")
|
390
|
+
else:
|
391
|
+
OutputControls().printOutput("[!] Nothing to show here yet. Check back later.")
|
392
|
+
|
393
|
+
configManager.showPastStrategyData = savedValue
|
394
|
+
|
395
|
+
if defaultAnswer is None:
|
396
|
+
OutputControls().takeUserInput("Press <Enter> to continue...")
|
397
|
+
|
398
|
+
return None, None
|
399
|
+
|
400
|
+
def apply_strategy_filters(self, userOption):
|
401
|
+
"""Apply selected strategy filters"""
|
402
|
+
global strategyFilter, menuOption, indexOption, executeOption, selectedChoice
|
403
|
+
|
404
|
+
userOptions = userOption.split(",")
|
405
|
+
for usrOption in userOptions:
|
406
|
+
strategyFilter.append(m1.find(usrOption).menuText.strip())
|
407
|
+
|
408
|
+
menuOption, indexOption, executeOption, selectedChoice = getScannerMenuChoices(
|
409
|
+
self.app_state.testBuild or self.app_state.testing,
|
410
|
+
self.app_state.downloadOnly,
|
411
|
+
self.app_state.startupoptions,
|
412
|
+
menuOption="X",
|
413
|
+
indexOption=indexOption,
|
414
|
+
executeOption=executeOption,
|
415
|
+
defaultAnswer=self.app_state.defaultAnswer,
|
416
|
+
user=self.app_state.user,
|
417
|
+
)
|
418
|
+
|
419
|
+
|
420
|
+
class DataLoader:
|
421
|
+
"""Handles data loading and preparation for screening"""
|
422
|
+
|
423
|
+
def __init__(self, app_state):
|
424
|
+
self.app_state = app_state
|
425
|
+
self.configManager = ConfigManager()
|
426
|
+
|
427
|
+
def load_stock_data(self, menuOption, indexOption, downloadOnly, listStockCodes):
|
428
|
+
"""Load stock data for screening"""
|
429
|
+
global stockDictPrimary, stockDictSecondary, loadedStockData
|
430
|
+
|
431
|
+
loadedStockData = loadedStockData and stockDictPrimary is not None and len(stockDictPrimary) > 0
|
432
|
+
|
433
|
+
if (menuOption in ["X", "B", "G", "S", "F"] and not loadedStockData) or (
|
434
|
+
self.configManager.cacheEnabled and not loadedStockData and not self.app_state.testing
|
435
|
+
):
|
436
|
+
try:
|
437
|
+
import tensorflow as tf
|
438
|
+
with tf.device("/device:GPU:0"):
|
439
|
+
stockDictPrimary, stockDictSecondary = loadDatabaseOrFetch(
|
440
|
+
downloadOnly, listStockCodes, menuOption, indexOption
|
441
|
+
)
|
442
|
+
except:
|
443
|
+
stockDictPrimary, stockDictSecondary = loadDatabaseOrFetch(
|
444
|
+
downloadOnly, listStockCodes, menuOption, indexOption
|
445
|
+
)
|
446
|
+
|
447
|
+
return len(stockDictPrimary) if stockDictPrimary is not None else 0
|
448
|
+
|
449
|
+
|
450
|
+
class MainApplication:
|
451
|
+
"""Main application class that orchestrates all components"""
|
452
|
+
|
453
|
+
def __init__(self, userArgs=None):
|
454
|
+
self.app_state = ApplicationState(userArgs)
|
455
|
+
self.menu_handler = MenuHandler(self.app_state)
|
456
|
+
self.scanner_handler = ScannerHandler(self.app_state)
|
457
|
+
self.backtest_handler = BacktestHandler(self.app_state)
|
458
|
+
self.strategy_handler = StrategyHandler(self.app_state)
|
459
|
+
self.data_loader = DataLoader(self.app_state)
|
460
|
+
self.configManager = ConfigManager()
|
461
|
+
|
462
|
+
def main(self, userArgs=None, optionalFinalOutcome_df=None):
|
463
|
+
"""
|
464
|
+
Main entry point for the application
|
465
|
+
Refactored from the original massive main function
|
466
|
+
|
467
|
+
Args:
|
468
|
+
userArgs: Command line arguments passed by user
|
469
|
+
optionalFinalOutcome_df: Optional dataframe for final results
|
470
|
+
|
471
|
+
Returns:
|
472
|
+
Tuple of screen results and save results
|
473
|
+
"""
|
474
|
+
global keyboardInterruptEventFired
|
475
|
+
|
476
|
+
# Check for keyboard interrupt
|
477
|
+
if keyboardInterruptEventFired:
|
478
|
+
return None, None
|
479
|
+
|
480
|
+
# Handle intraday analysis if requested
|
481
|
+
if self.handle_intraday_analysis(userArgs, optionalFinalOutcome_df):
|
482
|
+
savedAnalysisDict = analysis_dict.get(firstScanKey)
|
483
|
+
return analysisFinalResults(
|
484
|
+
savedAnalysisDict.get("S1"),
|
485
|
+
savedAnalysisDict.get("S2"),
|
486
|
+
optionalFinalOutcome_df,
|
487
|
+
None
|
488
|
+
)
|
489
|
+
|
490
|
+
# Initialize multiprocessing and market monitor
|
491
|
+
self.initialize_multiprocessing()
|
492
|
+
self.initialize_market_monitor()
|
493
|
+
|
494
|
+
# Handle cleanup if needed
|
495
|
+
if self.handle_cleanup(userArgs):
|
496
|
+
cleanupLocalResults()
|
497
|
+
|
498
|
+
# Process menu options
|
499
|
+
selectedMenu, menuOption, indexOption, executeOption = self.menu_handler.process_top_level_menu()
|
500
|
+
|
501
|
+
# Check premium access
|
502
|
+
self.menu_handler.handle_premium_checks(selectedMenu)
|
503
|
+
|
504
|
+
# Handle special menu options
|
505
|
+
self.menu_handler.handle_special_menu_options(menuOption, selectedMenu)
|
506
|
+
|
507
|
+
# Handle scanner menu options
|
508
|
+
if menuOption in ["X", "T", "E", "Y", "U", "H", "C"]:
|
509
|
+
menuOption, indexOption, executeOption, selectedChoice = getScannerMenuChoices(
|
510
|
+
self.app_state.testBuild or self.app_state.testing,
|
511
|
+
self.app_state.downloadOnly,
|
512
|
+
self.app_state.startupoptions,
|
513
|
+
menuOption=menuOption,
|
514
|
+
indexOption=indexOption,
|
515
|
+
executeOption=executeOption,
|
516
|
+
defaultAnswer=self.app_state.defaultAnswer,
|
517
|
+
user=self.app_state.user,
|
518
|
+
)
|
519
|
+
|
520
|
+
# Handle menu options X, B, G
|
521
|
+
handleMenu_XBG(menuOption, indexOption, executeOption)
|
522
|
+
|
523
|
+
# Check for exit request
|
524
|
+
if str(indexOption).upper() == "M" or str(executeOption).upper() == "M":
|
525
|
+
ConsoleUtility.PKConsoleTools.clearScreen(forceTop=True)
|
526
|
+
return None, None
|
527
|
+
|
528
|
+
# Prepare stocks for screening
|
529
|
+
listStockCodes = handleRequestForSpecificStocks(options, indexOption)
|
530
|
+
handleExitRequest(executeOption)
|
531
|
+
|
532
|
+
if executeOption is None:
|
533
|
+
executeOption = 0
|
534
|
+
executeOption = int(executeOption)
|
535
|
+
|
536
|
+
# Execute the selected scanner
|
537
|
+
self.scanner_handler.execute_scanner(menuOption, indexOption, executeOption)
|
538
|
+
|
539
|
+
# Load stock data
|
540
|
+
loadCount = self.data_loader.load_stock_data(
|
541
|
+
menuOption, indexOption, self.app_state.downloadOnly, listStockCodes
|
542
|
+
)
|
543
|
+
|
544
|
+
# Run the screening process
|
545
|
+
screenResults, saveResults = self.run_screening_process(
|
546
|
+
menuOption, indexOption, executeOption, listStockCodes, loadCount
|
547
|
+
)
|
548
|
+
|
549
|
+
# Process results
|
550
|
+
if menuOption in ["X", "C", "F"] and (userPassedArgs.monitor is None or self.configManager.alwaysExportToExcel) or ("|" not in userPassedArgs.options and menuOption not in ["B"]):
|
551
|
+
finishScreening(
|
552
|
+
self.app_state.downloadOnly,
|
553
|
+
self.app_state.testing,
|
554
|
+
stockDictPrimary,
|
555
|
+
self.configManager,
|
556
|
+
loadCount,
|
557
|
+
self.app_state.testBuild,
|
558
|
+
screenResults,
|
559
|
+
saveResults,
|
560
|
+
self.app_state.user,
|
561
|
+
)
|
562
|
+
|
563
|
+
# Handle backtest results
|
564
|
+
if menuOption == "B":
|
565
|
+
self.backtest_handler.process_backtest_results(backtest_df)
|
566
|
+
|
567
|
+
# Reset configuration
|
568
|
+
resetConfigToDefault()
|
569
|
+
|
570
|
+
# Save to Google Sheets if configured
|
571
|
+
self.save_to_google_sheets(saveResults)
|
572
|
+
|
573
|
+
# Handle pinned menu
|
574
|
+
self.handle_pinned_menu(saveResults, menuOption)
|
575
|
+
|
576
|
+
return screenResults, saveResults
|
577
|
+
|
578
|
+
def run_screening_process(self, menuOption, indexOption, executeOption, listStockCodes, loadCount):
|
579
|
+
"""Run the screening process with appropriate parameters"""
|
580
|
+
global screenResults, saveResults, backtest_df
|
581
|
+
|
582
|
+
# Prepare screening parameters
|
583
|
+
samplingDuration, fillerPlaceHolder, actualHistoricalDuration = PKScanRunner.getScanDurationParameters(
|
584
|
+
self.app_state.testing, menuOption
|
585
|
+
)
|
586
|
+
|
587
|
+
totalStocksInReview = 0
|
588
|
+
savedStocksCount = 0
|
589
|
+
downloadedRecently = False
|
590
|
+
items = []
|
591
|
+
backtest_df = None
|
592
|
+
|
593
|
+
# Get progress bar style
|
594
|
+
bar, spinner = Utility.tools.getProgressbarStyle()
|
595
|
+
|
596
|
+
# Begin screening process
|
597
|
+
OutputControls().printOutput(f"{colorText.GREEN} [+] Adding stocks to the queue...{colorText.END}")
|
598
|
+
|
599
|
+
with alive_bar(actualHistoricalDuration, bar=bar, spinner=spinner) as progressbar:
|
600
|
+
while actualHistoricalDuration >= 0:
|
601
|
+
daysInPast = PKScanRunner.getBacktestDaysForScan(
|
602
|
+
userPassedArgs, backtestPeriod, menuOption, actualHistoricalDuration
|
603
|
+
)
|
604
|
+
|
605
|
+
try:
|
606
|
+
listStockCodes, savedStocksCount, pastDate = PKScanRunner.getStocksListForScan(
|
607
|
+
userPassedArgs, menuOption, totalStocksInReview, downloadedRecently, daysInPast
|
608
|
+
) if menuOption not in ["C"] else (listStockCodes, 0, "")
|
609
|
+
except KeyboardInterrupt:
|
610
|
+
try:
|
611
|
+
keyboardInterruptEvent.set()
|
612
|
+
keyboardInterruptEventFired = True
|
613
|
+
actualHistoricalDuration = -1
|
614
|
+
break
|
615
|
+
except KeyboardInterrupt:
|
616
|
+
pass
|
617
|
+
OutputControls().printOutput(
|
618
|
+
colorText.FAIL
|
619
|
+
+ "\n [+] Terminating Script, Please wait..."
|
620
|
+
+ colorText.END
|
621
|
+
)
|
622
|
+
|
623
|
+
exchangeName = "NASDAQ" if (indexOption == 15 or (self.configManager.defaultIndex == 15 and indexOption == 0)) else "INDIA"
|
624
|
+
runOptionName = PKScanRunner.getFormattedChoices(userPassedArgs, selectedChoice)
|
625
|
+
|
626
|
+
if ((":0:" in runOptionName or "_0_" in runOptionName) and userPassedArgs.progressstatus is not None) or userPassedArgs.progressstatus is not None:
|
627
|
+
runOptionName = userPassedArgs.progressstatus.split("=>")[0].split(" [+] ")[1]
|
628
|
+
|
629
|
+
if menuOption in ["F"]:
|
630
|
+
if "^NSEI" in listStockCodes:
|
631
|
+
listStockCodes.remove("^NSEI")
|
632
|
+
items = PKScanRunner.addScansWithDefaultParams(
|
633
|
+
userPassedArgs, self.app_state.testing, self.app_state.testBuild,
|
634
|
+
newlyListedOnly, self.app_state.downloadOnly, backtestPeriod,
|
635
|
+
listStockCodes, menuOption, exchangeName, executeOption, volumeRatio,
|
636
|
+
items, daysInPast, runOption=f"{userPassedArgs.options} =>{runOptionName} => {menuChoiceHierarchy}"
|
637
|
+
)
|
638
|
+
else:
|
639
|
+
PKScanRunner.addStocksToItemList(
|
640
|
+
userPassedArgs, self.app_state.testing, self.app_state.testBuild,
|
641
|
+
newlyListedOnly, self.app_state.downloadOnly, minRSI, maxRSI,
|
642
|
+
insideBarToLookback, respChartPattern, daysForLowestVolume,
|
643
|
+
backtestPeriod, reversalOption, maLength, listStockCodes,
|
644
|
+
menuOption, exchangeName, executeOption, volumeRatio, items,
|
645
|
+
daysInPast, runOption=f"{userPassedArgs.options} =>{runOptionName} => {menuChoiceHierarchy}"
|
646
|
+
)
|
647
|
+
|
648
|
+
if savedStocksCount > 0:
|
649
|
+
progressbar.text(
|
650
|
+
colorText.GREEN
|
651
|
+
+ f"Total Stocks: {len(items)}. Added {savedStocksCount} to Stocks from {pastDate} saved from earlier..."
|
652
|
+
+ colorText.END
|
653
|
+
)
|
654
|
+
|
655
|
+
fillerPlaceHolder = fillerPlaceHolder + 1
|
656
|
+
actualHistoricalDuration = samplingDuration - fillerPlaceHolder
|
657
|
+
|
658
|
+
if actualHistoricalDuration >= 0:
|
659
|
+
progressbar()
|
660
|
+
|
661
|
+
# Run the scan with parameters
|
662
|
+
screenResults, saveResults, backtest_df, tasks_queue, results_queue, consumers, logging_queue = PKScanRunner.runScanWithParams(
|
663
|
+
userPassedArgs, keyboardInterruptEvent, screenCounter, screenResultsCounter,
|
664
|
+
stockDictPrimary, stockDictSecondary, self.app_state.testing, backtestPeriod,
|
665
|
+
menuOption, executeOption, samplingDuration, items, screenResults, saveResults,
|
666
|
+
backtest_df, scanningCb=runScanners, tasks_queue=tasks_queue,
|
667
|
+
results_queue=results_queue, consumers=consumers, logging_queue=logging_queue
|
668
|
+
)
|
669
|
+
|
670
|
+
return screenResults, saveResults
|
671
|
+
|
672
|
+
def save_to_google_sheets(self, saveResults):
|
673
|
+
"""Save results to Google Sheets if configured"""
|
674
|
+
if 'ALERT_TRIGGER' in os.environ.keys() and os.environ["ALERT_TRIGGER"] == 'Y':
|
675
|
+
if "GSHEET_SERVICE_ACCOUNT_DEV" in os.environ.keys() and (userPassedArgs.backtestdaysago is None):
|
676
|
+
begin = time.time()
|
677
|
+
creds = os.environ.get("GSHEET_SERVICE_ACCOUNT_DEV")
|
678
|
+
OutputControls().printOutput(f"{colorText.GREEN} [+] Saving data to Google Spreadsheets now...{colorText.END}")
|
679
|
+
|
680
|
+
gClient = PKSpreadsheets(credentialDictStr=creds)
|
681
|
+
runOption = PKScanRunner.getFormattedChoices(userPassedArgs, selectedChoice)
|
682
|
+
df = saveResults.copy()
|
683
|
+
df["LastTradeDate"], df["LastTradeTime"] = getLatestTradeDateTime(stockDictPrimary)
|
684
|
+
|
685
|
+
gClient.df_to_sheet(df=df, sheetName=runOption)
|
686
|
+
OutputControls().printOutput(f"{colorText.GREEN} => Done in {round(time.time()-begin,2)}s{colorText.END}")
|
687
|
+
|
688
|
+
def handle_pinned_menu(self, saveResults, menuOption):
|
689
|
+
"""Handle the pinned menu display and options"""
|
690
|
+
if ("RUNNER" not in os.environ.keys() and
|
691
|
+
not self.app_state.testing and
|
692
|
+
(userPassedArgs is None or
|
693
|
+
(userPassedArgs is not None and
|
694
|
+
(userPassedArgs.user is None or str(userPassedArgs.user) == DEV_CHANNEL_ID) and
|
695
|
+
(userPassedArgs.answerdefault is None or userPassedArgs.systemlaunched))) and
|
696
|
+
not userPassedArgs.testbuild and
|
697
|
+
userPassedArgs.monitor is None):
|
698
|
+
|
699
|
+
prevOutput_results = saveResults.index if (saveResults is not None and not saveResults.empty) else []
|
700
|
+
isNotPiped = (("|" not in userPassedArgs.options) if (userPassedArgs is not None and userPassedArgs.options is not None) else True)
|
701
|
+
hasFoundStocks = len(prevOutput_results) > 0 and isNotPiped
|
702
|
+
|
703
|
+
if hasFoundStocks or (self.configManager.showPinnedMenuEvenForNoResult and isNotPiped):
|
704
|
+
# ... handle pinned menu logic
|
705
|
+
pass
|
706
|
+
|
707
|
+
|
708
|
+
# Replace the original main function with the refactored version
|
709
|
+
def main(userArgs=None, optionalFinalOutcome_df=None):
|
710
|
+
"""
|
711
|
+
Main entry point - replaces the original massive main function
|
712
|
+
|
713
|
+
Args:
|
714
|
+
userArgs: Command line arguments passed by user
|
715
|
+
optionalFinalOutcome_df: Optional dataframe for final results
|
716
|
+
|
717
|
+
Returns:
|
718
|
+
Tuple of screen results and save results
|
719
|
+
"""
|
720
|
+
app = MainApplication(userArgs)
|
721
|
+
return app.main(userArgs, optionalFinalOutcome_df)
|