pkscreener 0.46.20250908.767__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.
Files changed (63) hide show
  1. pkscreener-0.46.20250910.770.data/purelib/pkscreener/MainApplication.py +721 -0
  2. {pkscreener-0.46.20250908.767.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/README.txt +5 -5
  3. {pkscreener-0.46.20250908.767.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/AssetsManager.py +16 -0
  4. pkscreener-0.46.20250910.770.data/purelib/pkscreener/classes/MenuManager.py +2373 -0
  5. pkscreener-0.46.20250910.770.data/purelib/pkscreener/classes/PKScreenerMain.py +916 -0
  6. pkscreener-0.46.20250910.770.data/purelib/pkscreener/classes/__init__.py +1 -0
  7. {pkscreener-0.46.20250908.767.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/pkscreenercli.py +1 -0
  8. {pkscreener-0.46.20250908.767.dist-info → pkscreener-0.46.20250910.770.dist-info}/METADATA +7 -7
  9. pkscreener-0.46.20250910.770.dist-info/RECORD +61 -0
  10. pkscreener-0.46.20250908.767.data/purelib/pkscreener/classes/__init__.py +0 -1
  11. pkscreener-0.46.20250908.767.dist-info/RECORD +0 -58
  12. {pkscreener-0.46.20250908.767.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/Disclaimer.txt +0 -0
  13. {pkscreener-0.46.20250908.767.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/LICENSE-Others.txt +0 -0
  14. {pkscreener-0.46.20250908.767.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/LICENSE.txt +0 -0
  15. {pkscreener-0.46.20250908.767.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/LogoWM.txt +0 -0
  16. {pkscreener-0.46.20250908.767.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/__init__.py +0 -0
  17. {pkscreener-0.46.20250908.767.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/ArtTexts.py +0 -0
  18. {pkscreener-0.46.20250908.767.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/Backtest.py +0 -0
  19. {pkscreener-0.46.20250908.767.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/Barometer.py +0 -0
  20. {pkscreener-0.46.20250908.767.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/BaseScreeningStatistics.py +0 -0
  21. {pkscreener-0.46.20250908.767.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/CandlePatterns.py +0 -0
  22. {pkscreener-0.46.20250908.767.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/Changelog.py +0 -0
  23. {pkscreener-0.46.20250908.767.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/ConfigManager.py +0 -0
  24. {pkscreener-0.46.20250908.767.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/ConsoleMenuUtility.py +0 -0
  25. {pkscreener-0.46.20250908.767.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/ConsoleUtility.py +0 -0
  26. {pkscreener-0.46.20250908.767.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/Fetcher.py +0 -0
  27. {pkscreener-0.46.20250908.767.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/GlobalStore.py +0 -0
  28. {pkscreener-0.46.20250908.767.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/ImageUtility.py +0 -0
  29. {pkscreener-0.46.20250908.767.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/MarketMonitor.py +0 -0
  30. {pkscreener-0.46.20250908.767.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/MarketStatus.py +0 -0
  31. {pkscreener-0.46.20250908.767.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/MenuOptions.py +0 -0
  32. {pkscreener-0.46.20250908.767.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/Messenger.py +0 -0
  33. {pkscreener-0.46.20250908.767.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/OtaUpdater.py +0 -0
  34. {pkscreener-0.46.20250908.767.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/PKAnalytics.py +0 -0
  35. {pkscreener-0.46.20250908.767.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/PKDataService.py +0 -0
  36. {pkscreener-0.46.20250908.767.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/PKDemoHandler.py +0 -0
  37. {pkscreener-0.46.20250908.767.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/PKMarketOpenCloseAnalyser.py +0 -0
  38. {pkscreener-0.46.20250908.767.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/PKPremiumHandler.py +0 -0
  39. {pkscreener-0.46.20250908.767.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/PKScanRunner.py +0 -0
  40. {pkscreener-0.46.20250908.767.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/PKScheduledTaskProgress.py +0 -0
  41. {pkscreener-0.46.20250908.767.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/PKScheduler.py +0 -0
  42. {pkscreener-0.46.20250908.767.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/PKSpreadsheets.py +0 -0
  43. {pkscreener-0.46.20250908.767.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/PKTask.py +0 -0
  44. {pkscreener-0.46.20250908.767.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/PKUserRegistration.py +0 -0
  45. {pkscreener-0.46.20250908.767.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/Pktalib.py +0 -0
  46. {pkscreener-0.46.20250908.767.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/Portfolio.py +0 -0
  47. {pkscreener-0.46.20250908.767.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/PortfolioXRay.py +0 -0
  48. {pkscreener-0.46.20250908.767.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/ScreeningStatistics.py +0 -0
  49. {pkscreener-0.46.20250908.767.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/StockScreener.py +0 -0
  50. {pkscreener-0.46.20250908.767.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/StockSentiment.py +0 -0
  51. {pkscreener-0.46.20250908.767.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/UserMenuChoicesHandler.py +0 -0
  52. {pkscreener-0.46.20250908.767.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/Utility.py +0 -0
  53. {pkscreener-0.46.20250908.767.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/WorkflowManager.py +0 -0
  54. {pkscreener-0.46.20250908.767.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/classes/keys.py +0 -0
  55. {pkscreener-0.46.20250908.767.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/courbd.ttf +0 -0
  56. {pkscreener-0.46.20250908.767.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/globals.py +0 -0
  57. {pkscreener-0.46.20250908.767.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/pkscreener.ini +0 -0
  58. {pkscreener-0.46.20250908.767.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/pkscreenerbot.py +0 -0
  59. {pkscreener-0.46.20250908.767.data → pkscreener-0.46.20250910.770.data}/purelib/pkscreener/requirements.txt +0 -0
  60. {pkscreener-0.46.20250908.767.dist-info → pkscreener-0.46.20250910.770.dist-info}/WHEEL +0 -0
  61. {pkscreener-0.46.20250908.767.dist-info → pkscreener-0.46.20250910.770.dist-info}/entry_points.txt +0 -0
  62. {pkscreener-0.46.20250908.767.dist-info → pkscreener-0.46.20250910.770.dist-info}/licenses/LICENSE +0 -0
  63. {pkscreener-0.46.20250908.767.dist-info → pkscreener-0.46.20250910.770.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,916 @@
1
+ #!/usr/bin/python3
2
+ """
3
+ The MIT License (MIT)
4
+
5
+ Copyright (c) 2023 pkjmesra
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ of this software and associated documentation files (the "Software"), to deal
9
+ in the Software without restriction, including without limitation the rights
10
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the Software is
12
+ furnished to do so, subject to the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be included in all
15
+ copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
+ SOFTWARE.
24
+
25
+ """
26
+
27
+ import os
28
+ import random
29
+ import warnings
30
+ warnings.simplefilter("ignore", UserWarning, append=True)
31
+ os.environ["PYTHONWARNINGS"] = "ignore::UserWarning"
32
+ os.environ["TF_CPP_MIN_LOG_LEVEL"] = "3"
33
+ import logging
34
+ import multiprocessing
35
+ import sys
36
+ import time
37
+ import urllib
38
+ import warnings
39
+ from datetime import datetime, UTC, timedelta
40
+ from time import sleep
41
+
42
+ import numpy as np
43
+
44
+ warnings.simplefilter("ignore", DeprecationWarning)
45
+ warnings.simplefilter("ignore", FutureWarning)
46
+ import pandas as pd
47
+ from alive_progress import alive_bar
48
+ from PKDevTools.classes.Committer import Committer
49
+ from PKDevTools.classes.ColorText import colorText
50
+ from PKDevTools.classes.PKDateUtilities import PKDateUtilities
51
+ from PKDevTools.classes.log import default_logger
52
+ from PKDevTools.classes.SuppressOutput import SuppressOutput
53
+ from PKDevTools.classes import Archiver
54
+ from PKDevTools.classes.Telegram import (
55
+ is_token_telegram_configured,
56
+ send_document,
57
+ send_message,
58
+ send_photo,
59
+ send_media_group
60
+ )
61
+ from PKNSETools.morningstartools.PKMorningstarDataFetcher import morningstarDataFetcher
62
+ from PKNSETools.Nasdaq.PKNasdaqIndex import PKNasdaqIndexFetcher
63
+ from tabulate import tabulate
64
+ from halo import Halo
65
+
66
+ import pkscreener.classes.ConfigManager as ConfigManager
67
+ import pkscreener.classes.Fetcher as Fetcher
68
+ import pkscreener.classes.ScreeningStatistics as ScreeningStatistics
69
+ from pkscreener.classes import Utility, ConsoleUtility, ConsoleMenuUtility, ImageUtility
70
+ from pkscreener.classes.Utility import STD_ENCODING
71
+ from pkscreener.classes import VERSION, PortfolioXRay
72
+ from pkscreener.classes.Backtest import backtest, backtestSummary
73
+ from pkscreener.classes.PKSpreadsheets import PKSpreadsheets
74
+ from PKDevTools.classes.OutputControls import OutputControls
75
+ from PKDevTools.classes.Environment import PKEnvironment
76
+ from pkscreener.classes.CandlePatterns import CandlePatterns
77
+ from pkscreener.classes import AssetsManager
78
+ from PKDevTools.classes.FunctionTimeouts import exit_after
79
+ from pkscreener.classes.MenuOptions import (
80
+ level0MenuDict,
81
+ level1_X_MenuDict,
82
+ level1_P_MenuDict,
83
+ level2_X_MenuDict,
84
+ level2_P_MenuDict,
85
+ level3_X_ChartPattern_MenuDict,
86
+ level3_X_PopularStocks_MenuDict,
87
+ level3_X_PotentialProfitable_MenuDict,
88
+ PRICE_CROSS_SMA_EMA_DIRECTION_MENUDICT,
89
+ PRICE_CROSS_SMA_EMA_TYPE_MENUDICT,
90
+ PRICE_CROSS_PIVOT_POINT_TYPE_MENUDICT,
91
+ level3_X_Reversal_MenuDict,
92
+ level4_X_Lorenzian_MenuDict,
93
+ level4_X_ChartPattern_Confluence_MenuDict,
94
+ level4_X_ChartPattern_BBands_SQZ_MenuDict,
95
+ level4_X_ChartPattern_MASignalMenuDict,
96
+ level1_index_options_sectoral,
97
+ menus,
98
+ MAX_SUPPORTED_MENU_OPTION,
99
+ MAX_MENU_OPTION,
100
+ PIPED_SCANNERS,
101
+ PREDEFINED_SCAN_MENU_KEYS,
102
+ PREDEFINED_SCAN_MENU_TEXTS,
103
+ INDICES_MAP,
104
+ CANDLESTICK_DICT
105
+ )
106
+ from pkscreener.classes.OtaUpdater import OTAUpdater
107
+ from pkscreener.classes.Portfolio import PortfolioCollection
108
+ from pkscreener.classes.PKTask import PKTask
109
+ from pkscreener.classes.PKScheduler import PKScheduler
110
+ from pkscreener.classes.PKScanRunner import PKScanRunner
111
+ from pkscreener.classes.PKMarketOpenCloseAnalyser import PKMarketOpenCloseAnalyser
112
+ from pkscreener.classes.PKPremiumHandler import PKPremiumHandler
113
+ from pkscreener.classes.AssetsManager import PKAssetsManager
114
+ from pkscreener.classes.PKAnalytics import PKAnalyticsService
115
+ from pkscreener.classes.MenuManager import MenuManager, ScanExecutor, ResultProcessor, TelegramNotifier, DataManager, BacktestManager
116
+ if __name__ == '__main__':
117
+ multiprocessing.freeze_support()
118
+
119
+ # Constants
120
+ np.seterr(divide="ignore", invalid="ignore")
121
+ TEST_STKCODE = "SBIN"
122
+
123
+
124
+ class PKScreenerMain:
125
+ """
126
+ Main application class for PKScreener that orchestrates the entire screening process.
127
+ Coordinates between all manager classes and handles the main execution flow.
128
+ """
129
+
130
+ def __init__(self):
131
+ """Initialize the PKScreener application with all manager classes."""
132
+ self.config_manager = ConfigManager.tools()
133
+ self.config_manager.getConfig(ConfigManager.parser)
134
+ self.user_passed_args = None
135
+ self.default_answer = None
136
+
137
+ # Initialize manager classes
138
+ self.menu_manager = MenuManager(self.config_manager, self.user_passed_args)
139
+ self.scan_executor = ScanExecutor(self.config_manager, self.user_passed_args)
140
+ self.result_processor = ResultProcessor(self.config_manager, self.user_passed_args)
141
+ self.telegram_notifier = TelegramNotifier()
142
+ self.data_manager = DataManager(self.config_manager, self.user_passed_args)
143
+ self.backtest_manager = BacktestManager(self.config_manager, self.user_passed_args)
144
+
145
+ # Share state between managers
146
+ self.menu_manager.list_stock_codes = self.data_manager.list_stock_codes
147
+ self.scan_executor.selected_choice = self.menu_manager.selected_choice
148
+ self.scan_executor.criteria_date_time = self.result_processor.criteria_date_time
149
+ self.result_processor.selected_choice = self.menu_manager.selected_choice
150
+ self.result_processor.menu_choice_hierarchy = self.menu_manager.menu_choice_hierarchy
151
+ self.data_manager.selected_choice = self.menu_manager.selected_choice
152
+ self.data_manager.default_answer = self.default_answer
153
+ self.backtest_manager.selected_choice = self.menu_manager.selected_choice
154
+ self.backtest_manager.menu_choice_hierarchy = self.menu_manager.menu_choice_hierarchy
155
+ self.backtest_manager.elapsed_time = self.scan_executor.elapsed_time
156
+ self.backtest_manager.default_answer = self.default_answer
157
+
158
+ def main(self, userArgs=None, optionalFinalOutcome_df=None):
159
+ """
160
+ Main entry point for the PKScreener application.
161
+
162
+ This method orchestrates the entire screening process by coordinating between
163
+ all manager classes. It handles menu navigation, scanning execution, result
164
+ processing, and notifications while maintaining all existing functionality.
165
+
166
+ Args:
167
+ userArgs: User arguments passed to the application
168
+ optionalFinalOutcome_df: Optional final outcome dataframe for intraday analysis
169
+
170
+ Returns:
171
+ tuple: Screen results and save results dataframes
172
+ """
173
+ # Initialize state variables
174
+ self.user_passed_args = userArgs
175
+ self.default_answer = None if userArgs is None else userArgs.answerdefault
176
+
177
+ # Update references with actual user args
178
+ self.menu_manager.user_passed_args = self.user_passed_args
179
+ self.scan_executor.user_passed_args = self.user_passed_args
180
+ self.result_processor.user_passed_args = self.user_passed_args
181
+ self.telegram_notifier.user_passed_args = self.user_passed_args
182
+ self.data_manager.user_passed_args = self.user_passed_args
183
+ self.backtest_manager.user_passed_args = self.user_passed_args
184
+
185
+ # Set initial state
186
+ testing = False if userArgs is None else (userArgs.testbuild and userArgs.prodbuild)
187
+ testBuild = False if userArgs is None else (userArgs.testbuild and not testing)
188
+ downloadOnly = False if userArgs is None else userArgs.download
189
+ startupoptions = None if userArgs is None else userArgs.options
190
+ user = None if userArgs is None else userArgs.user
191
+ self.default_answer = None if userArgs is None else userArgs.answerdefault
192
+
193
+ # Initialize screening counters
194
+ self.scan_executor.screen_counter = multiprocessing.Value("i", 1)
195
+ self.scan_executor.screen_results_counter = multiprocessing.Value("i", 0)
196
+
197
+ # Initialize multiprocessing manager
198
+ if self.scan_executor.mp_manager is None:
199
+ self.scan_executor.mp_manager = multiprocessing.Manager()
200
+
201
+ # Setup keyboard interrupt handling
202
+ if self.scan_executor.keyboard_interrupt_event is None and not self.scan_executor.keyboard_interrupt_event_fired:
203
+ self.scan_executor.keyboard_interrupt_event = self.scan_executor.mp_manager.Event()
204
+ mkt_monitor_dict = self.scan_executor.mp_manager.dict()
205
+ self.startMarketMonitor(mkt_monitor_dict, self.scan_executor.keyboard_interrupt_event)
206
+
207
+ self.scan_executor.keyboard_interrupt_event_fired = False
208
+
209
+ # Initialize stock data dictionaries
210
+ if self.data_manager.stock_dict_primary is None or isinstance(self.data_manager.stock_dict_primary, dict):
211
+ self.data_manager.stock_dict_primary = self.scan_executor.mp_manager.dict()
212
+ self.data_manager.stock_dict_secondary = self.scan_executor.mp_manager.dict()
213
+ self.data_manager.load_count = 0
214
+
215
+ # Handle cleanup if needed
216
+ if not self.data_manager.run_clean_up and self.user_passed_args is not None and not self.user_passed_args.systemlaunched:
217
+ self.data_manager.cleanup_local_results()
218
+
219
+ # Log user arguments if enabled
220
+ if self.user_passed_args.log:
221
+ default_logger().debug(f"User Passed args: {self.user_passed_args}")
222
+
223
+ # Initialize results dataframes
224
+ self.scan_executor.screen_results, self.scan_executor.save_results = PKScanRunner.initDataframes()
225
+
226
+ # Get top level menu choices
227
+ options, menuOption, indexOption, executeOption = self.menu_manager.getTopLevelMenuChoices(
228
+ startupoptions, testBuild, downloadOnly, defaultAnswer=self.default_answer
229
+ )
230
+
231
+ # Execute main menu navigation and processing
232
+ selectedMenu = self.menu_manager.initExecution(menuOption=menuOption)
233
+ menuOption = selectedMenu.menuKey
234
+
235
+ # Handle premium feature checks
236
+ if menuOption in ["F", "M", "S", "B", "G", "C", "P", "D"] or selectedMenu.isPremium:
237
+ self.menu_manager.ensureMenusLoaded(menuOption, indexOption, executeOption)
238
+ if not PKPremiumHandler.hasPremium(selectedMenu):
239
+ PKAnalyticsService().send_event(f"non_premium_user_{menuOption}_{indexOption}_{executeOption}")
240
+ PKAnalyticsService().send_event("app_exit")
241
+ sys.exit(0)
242
+
243
+ # Handle special menu options
244
+ if menuOption in ["M", "D", "I", "L", "F"]:
245
+ self.handle_special_menu_options(menuOption)
246
+ return None, None
247
+
248
+ # Process scanner menu choices
249
+ if menuOption in ["X", "T", "E", "Y", "U", "H", "C"]:
250
+ menuOption, indexOption, executeOption, self.menu_manager.selected_choice = self.menu_manager.getScannerMenuChoices(
251
+ testBuild or testing, downloadOnly, startupoptions, menuOption=menuOption,
252
+ indexOption=indexOption, executeOption=executeOption, defaultAnswer=self.default_answer,
253
+ user=user
254
+ )
255
+ if indexOption is None:
256
+ return None, None
257
+
258
+ # Handle backtest options
259
+ elif menuOption in ["B", "G"]:
260
+ indexOption, executeOption, backtestPeriod = self.backtest_manager.takeBacktestInputs(
261
+ str(menuOption).upper(), indexOption, executeOption, 0
262
+ )
263
+ backtestPeriod = backtestPeriod * self.config_manager.backtestPeriodFactor
264
+
265
+ # Handle strategy screening
266
+ elif menuOption in ["S"]:
267
+ strategyFilter = self.handle_strategy_screening(options)
268
+ if strategyFilter:
269
+ menuOption, indexOption, executeOption, self.menu_manager.selected_choice = self.menu_manager.getScannerMenuChoices(
270
+ testBuild or testing, downloadOnly, startupoptions, menuOption="X",
271
+ indexOption=indexOption, executeOption=executeOption, defaultAnswer=self.default_answer,
272
+ user=user
273
+ )
274
+
275
+ # Update menu choice hierarchy
276
+ self.menu_manager.update_menu_choice_hierarchy()
277
+
278
+ # Prepare stocks for screening
279
+ self.data_manager.list_stock_codes = self.menu_manager.handleRequestForSpecificStocks(options, indexOption)
280
+ self.data_manager.list_stock_codes = self.data_manager.prepareStocksForScreening(
281
+ testing, downloadOnly, self.data_manager.list_stock_codes, indexOption
282
+ )
283
+
284
+ # Handle exit requests
285
+ self.menu_manager.handleExitRequest(executeOption)
286
+
287
+ # Process execute options
288
+ volumeRatio = self.config_manager.volumeRatio
289
+ reversalOption = None
290
+ respChartPattern = None
291
+ daysForLowestVolume = 30
292
+ maLength = None
293
+
294
+ if executeOption == 3:
295
+ self.user_passed_args.maxdisplayresults = max(self.config_manager.maxdisplayresults, 2000)
296
+ elif executeOption == 4:
297
+ daysForLowestVolume = self.menu_manager.handleScannerExecuteOption4(executeOption, options)
298
+ elif executeOption == 5:
299
+ minRSI, maxRSI = ConsoleMenuUtility.PKConsoleMenuTools.promptRSIValues()
300
+ elif executeOption == 6:
301
+ reversalOption, maLength = ConsoleMenuUtility.PKConsoleMenuTools.promptReversalScreening(
302
+ self.menu_manager.m2.find(str(executeOption))
303
+ )
304
+ elif executeOption == 7:
305
+ respChartPattern, insideBarToLookback = ConsoleMenuUtility.PKConsoleMenuTools.promptChartPatterns(
306
+ self.menu_manager.m2.find(str(executeOption))
307
+ )
308
+ if respChartPattern in [3, 6, 9]:
309
+ maLength = ConsoleMenuUtility.PKConsoleMenuTools.promptChartPatternSubMenu(
310
+ self.menu_manager.m2.find(str(executeOption)), respChartPattern
311
+ )
312
+ # ... handle other execute options
313
+
314
+ # Load or fetch stock data
315
+ if not self.data_manager.loaded_stock_data:
316
+ try:
317
+ import tensorflow as tf
318
+ with tf.device("/device:GPU:0"):
319
+ self.data_manager.stock_dict_primary, self.data_manager.stock_dict_secondary = self.data_manager.loadDatabaseOrFetch(
320
+ downloadOnly, self.data_manager.list_stock_codes, menuOption, indexOption
321
+ )
322
+ except:
323
+ self.data_manager.stock_dict_primary, self.data_manager.stock_dict_secondary = self.data_manager.loadDatabaseOrFetch(
324
+ downloadOnly, self.data_manager.list_stock_codes, menuOption, indexOption
325
+ )
326
+
327
+ self.data_manager.load_count = len(self.data_manager.stock_dict_primary) if self.data_manager.stock_dict_primary is not None else 0
328
+
329
+ # Run the scanning process
330
+ if menuOption in ["X", "B", "G", "C", "F"]:
331
+ self.scan_executor.screen_results, self.scan_executor.save_results, self.scan_executor.backtest_df = self.scan_executor.runScanners(
332
+ menuOption, [], self.scan_executor.tasks_queue, self.scan_executor.results_queue,
333
+ len(self.data_manager.list_stock_codes), 0, 1, self.scan_executor.consumers,
334
+ self.scan_executor.screen_results, self.scan_executor.save_results, self.scan_executor.backtest_df, testing
335
+ )
336
+
337
+ # Process and display results
338
+ if not downloadOnly and menuOption in ["X", "G", "C", "F"]:
339
+ if menuOption == "G":
340
+ self.user_passed_args.backtestdaysago = 0 # backtestPeriod would be set appropriately
341
+
342
+ if self.scan_executor.screen_results is not None and len(self.scan_executor.screen_results) > 0:
343
+ self.scan_executor.screen_results, self.scan_executor.save_results = self.result_processor.labelDataForPrinting(
344
+ self.scan_executor.screen_results, self.scan_executor.save_results, volumeRatio, executeOption, reversalOption or respChartPattern, menuOption
345
+ )
346
+
347
+ # Remove unknown values if configured
348
+ if not self.menu_manager.newlyListedOnly and not self.config_manager.showunknowntrends and self.scan_executor.screen_results is not None and len(self.scan_executor.screen_results) > 0 and not self.user_passed_args.runintradayanalysis:
349
+ self.scan_executor.screen_results, self.scan_executor.save_results = self.result_processor.removeUnknowns(
350
+ self.scan_executor.screen_results, self.scan_executor.save_results
351
+ )
352
+
353
+ # Handle backtest results
354
+ if menuOption == "B":
355
+ if self.scan_executor.backtest_df is not None and len(self.scan_executor.backtest_df) > 0:
356
+ ConsoleUtility.PKConsoleTools.clearScreen(forceTop=True)
357
+ df_xray = self.backtest_manager.prepareGroupedXRay(0, self.scan_executor.backtest_df)
358
+ summary_df, sorting, sortKeys = self.backtest_manager.finishBacktestDataCleanup(self.scan_executor.backtest_df, df_xray)
359
+ while sorting:
360
+ sorting = self.backtest_manager.showSortedBacktestData(self.scan_executor.backtest_df, summary_df, sortKeys)
361
+
362
+ # Finish screening process
363
+ self.finishScreening(
364
+ downloadOnly, testing, self.data_manager.stock_dict_primary, self.data_manager.load_count,
365
+ testBuild, self.scan_executor.screen_results, self.scan_executor.save_results, user
366
+ )
367
+
368
+ # Reset configuration to default
369
+ self.resetConfigToDefault()
370
+
371
+ # Handle Google Sheets integration if enabled
372
+ self.handle_google_sheets_integration()
373
+
374
+ # Handle pinned menu options
375
+ self.handle_pinned_menu_options(testing)
376
+
377
+ # Handle intraday analysis if requested
378
+ if self.user_passed_args is not None and self.user_passed_args.runintradayanalysis:
379
+ return self.result_processor.analysisFinalResults(
380
+ self.scan_executor.screen_results, self.scan_executor.save_results, optionalFinalOutcome_df,
381
+ PKScanRunner.getFormattedChoices(self.user_passed_args, self.menu_manager.selected_choice)
382
+ )
383
+ else:
384
+ return self.scan_executor.screen_results, self.scan_executor.save_results
385
+
386
+ def handle_special_menu_options(self, menu_option):
387
+ """
388
+ Handle special menu options that require external execution.
389
+
390
+ Args:
391
+ menu_option: Selected menu option
392
+ """
393
+ launcher = f'"{sys.argv[0]}"' if " " in sys.argv[0] else sys.argv[0]
394
+ launcher = f"python3.12 {launcher}" if (launcher.endswith(".py\"") or launcher.endswith(".py")) else launcher
395
+
396
+ if menu_option == "M":
397
+ 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}")
398
+ PKAnalyticsService().send_event(f"monitor_{menu_option}")
399
+ sleep(2)
400
+ os.system(f"{launcher} --systemlaunched -a Y -m 'X'")
401
+ elif menu_option == "D":
402
+ self.handle_download_menu_option(launcher)
403
+ elif menu_option == "L":
404
+ PKAnalyticsService().send_event(f"{menu_option}")
405
+ 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}")
406
+ sleep(2)
407
+ os.system(f"{launcher} -a Y -l")
408
+ elif menu_option == "F":
409
+ PKAnalyticsService().send_event(f"{menu_option}")
410
+ indexOption = 0
411
+ self.menu_manager.selected_choice["0"] = "F"
412
+ self.menu_manager.selected_choice["1"] = "0"
413
+ executeOption = None
414
+
415
+ if self.user_passed_args is not None and self.user_passed_args.options is not None and len(self.user_passed_args.options.split(":")) >= 3:
416
+ stockOptions = self.user_passed_args.options.split(":")[2 if len(stockOptions)<=3 else 3]
417
+ self.data_manager.list_stock_codes = stockOptions.replace(".",",").split(",")
418
+
419
+ if self.data_manager.list_stock_codes is None or len(self.data_manager.list_stock_codes) == 0:
420
+ shouldSuppress = not OutputControls().enableMultipleLineOutput
421
+ with SuppressOutput(suppress_stderr=shouldSuppress, suppress_stdout=shouldSuppress):
422
+ self.data_manager.list_stock_codes = self.data_manager.fetcher.fetchStockCodes(tickerOption=0, stockCode=None)
423
+
424
+ ConsoleUtility.PKConsoleTools.clearScreen(clearAlways=True, forceTop=True)
425
+
426
+ def handle_download_menu_option(self, launcher):
427
+ """
428
+ Handle the download menu option with its sub-options.
429
+
430
+ Args:
431
+ launcher: Launcher command for external execution
432
+ """
433
+ selectedMenu = self.menu_manager.m0.find("D")
434
+ ConsoleUtility.PKConsoleTools.clearScreen(forceTop=True)
435
+ self.menu_manager.m1.renderForMenu(selectedMenu)
436
+ selDownloadOption = input(colorText.FAIL + " [+] Select option: ") or "D"
437
+ OutputControls().printOutput(colorText.END, end="")
438
+
439
+ if selDownloadOption.upper() == "D":
440
+ 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}")
441
+ PKAnalyticsService().send_event(f"D_{selDownloadOption.upper()}")
442
+ sleep(2)
443
+ os.system(f"{launcher} -a Y -e -d")
444
+ elif selDownloadOption.upper() == "I":
445
+ OutputControls().printOutput(f"{colorText.GREEN}Launching PKScreener to Download intraday OHLC data. If it does not launch, please try with the following:{colorText.END}\n{colorText.FAIL}{launcher} -a Y -e -d -i 1m{colorText.END}\n{colorText.WARN}Press Ctrl + C to exit at any time.{colorText.END}")
446
+ PKAnalyticsService().send_event(f"D_{selDownloadOption.upper()}")
447
+ sleep(2)
448
+ os.system(f"{launcher} -a Y -e -d -i 1m")
449
+ elif selDownloadOption.upper() == "N":
450
+ self.handle_nasdaq_download_option(selectedMenu, selDownloadOption)
451
+ elif selDownloadOption.upper() == "S":
452
+ self.handle_sector_download_option(selectedMenu, selDownloadOption)
453
+
454
+ def handle_nasdaq_download_option(self, selectedMenu, selDownloadOption):
455
+ """
456
+ Handle NASDAQ download option.
457
+
458
+ Args:
459
+ selectedMenu: Selected menu object
460
+ selDownloadOption: Selected download option
461
+ """
462
+ selectedMenu = self.menu_manager.m1.find(selDownloadOption.upper())
463
+ ConsoleUtility.PKConsoleTools.clearScreen(forceTop=True)
464
+ self.menu_manager.m2.renderForMenu(selectedMenu)
465
+ PKAnalyticsService().send_event(f"D_{selDownloadOption.upper()}")
466
+ selDownloadOption = input(colorText.FAIL + " [+] Select option: ") or "12"
467
+ OutputControls().printOutput(colorText.END, end="")
468
+
469
+ filePrefix = "Download"
470
+ if selDownloadOption.upper() in INDICES_MAP.keys():
471
+ filePrefix = INDICES_MAP.get(selDownloadOption.upper()).replace(" ","")
472
+
473
+ filename = (
474
+ f"PKS_Data_{filePrefix}_"
475
+ + PKDateUtilities.currentDateTime().strftime("%d-%m-%y_%H.%M.%S")
476
+ + ".csv"
477
+ )
478
+ filePath = os.path.join(Archiver.get_user_indices_dir(), filename)
479
+ PKAnalyticsService().send_event(f"D_{selDownloadOption.upper()}")
480
+
481
+ if selDownloadOption.upper() == "15":
482
+ nasdaq = PKNasdaqIndexFetcher(self.config_manager)
483
+ _, nasdaq_df = nasdaq.fetchNasdaqIndexConstituents()
484
+ try:
485
+ nasdaq_df.to_csv(filePath)
486
+ except Exception as e:
487
+ OutputControls().printOutput(f"{colorText.FAIL}We encountered an error. Please try again!{colorText.END}\n{colorText.WARN}{e}{colorText.END}")
488
+ pass
489
+ OutputControls().printOutput(f"{colorText.GREEN}{filePrefix} Saved at: {filePath}{colorText.END}")
490
+ input(f"{colorText.GREEN}Press any key to continue...{colorText.END}")
491
+ elif selDownloadOption.upper() == "M":
492
+ PKAnalyticsService().send_event(f"D_{selDownloadOption.upper()}")
493
+ else:
494
+ fileContents = self.data_manager.fetcher.fetchFileFromHostServer(filePath=filePath, tickerOption=int(selDownloadOption), fileContents="")
495
+ if len(fileContents) > 0:
496
+ OutputControls().printOutput(f"{colorText.GREEN}{filePrefix} Saved at: {filePath}{colorText.END}")
497
+ else:
498
+ OutputControls().printOutput(f"{colorText.FAIL}We encountered an error. Please try again!{colorText.END}")
499
+ input(f"{colorText.GREEN}Press any key to continue...{colorText.END}")
500
+
501
+ def handle_sector_download_option(self, selectedMenu, selDownloadOption):
502
+ """
503
+ Handle sector download option.
504
+
505
+ Args:
506
+ selectedMenu: Selected menu object
507
+ selDownloadOption: Selected download option
508
+ """
509
+ selectedMenu = self.menu_manager.m1.find(selDownloadOption.upper())
510
+ ConsoleUtility.PKConsoleTools.clearScreen(forceTop=True)
511
+ self.menu_manager.m2.renderForMenu(selectedMenu, skip=["15"])
512
+ selDownloadOption = input(colorText.FAIL + " [+] Select option: ") or "12"
513
+ OutputControls().printOutput(colorText.END, end="")
514
+
515
+ filePrefix = "Download"
516
+ if selDownloadOption.upper() in INDICES_MAP.keys():
517
+ filePrefix = INDICES_MAP.get(selDownloadOption.upper()).replace(" ","")
518
+
519
+ filename = (
520
+ f"PKS_Data_{filePrefix}_"
521
+ + PKDateUtilities.currentDateTime().strftime("%d-%m-%y_%H.%M.%S")
522
+ + ".csv"
523
+ )
524
+ PKAnalyticsService().send_event(f"D_{selDownloadOption.upper()}")
525
+ filePath = os.path.join(Archiver.get_user_reports_dir(), filename)
526
+
527
+ if selDownloadOption.upper() == "M":
528
+ return
529
+ else:
530
+ indexOption = int(selDownloadOption)
531
+ if indexOption > 0 and indexOption <= 14:
532
+ PKAnalyticsService().send_event(f"D_{selDownloadOption.upper()}")
533
+ shouldSuppress = not OutputControls().enableMultipleLineOutput
534
+ with SuppressOutput(suppress_stderr=shouldSuppress, suppress_stdout=shouldSuppress):
535
+ self.data_manager.list_stock_codes = self.data_manager.fetcher.fetchStockCodes(indexOption, stockCode=None)
536
+
537
+ OutputControls().printOutput(f"{colorText.GREEN}Please be patient. It might take a while...{colorText.END}")
538
+ from pkscreener.classes.PKDataService import PKDataService
539
+ dataSvc = PKDataService()
540
+ stockDictList, leftOutStocks = dataSvc.getSymbolsAndSectorInfo(self.config_manager, stockCodes=self.data_manager.list_stock_codes)
541
+
542
+ if len(stockDictList) > 0:
543
+ sector_df = pd.DataFrame(stockDictList)
544
+ sector_df.to_csv(filePath)
545
+ OutputControls().printOutput(f"{colorText.GREEN}Sector/Industry info for {filePrefix}, saved at: {filePath}{colorText.END}")
546
+ else:
547
+ OutputControls().printOutput(f"{colorText.FAIL}We encountered an error. Please try again!{colorText.END}")
548
+
549
+ input(f"{colorText.GREEN}Press any key to continue...{colorText.END}")
550
+
551
+ def handle_strategy_screening(self, options):
552
+ """
553
+ Handle strategy screening menu option.
554
+
555
+ Args:
556
+ options: Options list
557
+
558
+ Returns:
559
+ list: Strategy filter list
560
+ """
561
+ strategyFilter = []
562
+
563
+ if len(options) >= 2:
564
+ userOption = options[1]
565
+
566
+ if self.default_answer is None:
567
+ selectedMenu = self.menu_manager.m0.find("S")
568
+ self.menu_manager.m1.strategyNames = PortfolioXRay.strategyNames()
569
+ self.menu_manager.m1.renderForMenu(selectedMenu=selectedMenu)
570
+
571
+ try:
572
+ userOption = input(colorText.FAIL + " [+] Select option: ")
573
+ OutputControls().printOutput(colorText.END, end="")
574
+
575
+ if userOption == "":
576
+ userOption = "37" # NoFilter
577
+ elif userOption == "38":
578
+ userOption = input(colorText.FAIL + " [+] Enter Exact Pattern name:")
579
+ OutputControls().printOutput(colorText.END, end="")
580
+
581
+ if userOption == "":
582
+ userOption = "37" # NoFilter
583
+ else:
584
+ strategyFilter.append(f"[P]{userOption}")
585
+ userOption = "38"
586
+ except EOFError:
587
+ userOption = "37" # NoFilter
588
+ pass
589
+ except Exception as e:
590
+ default_logger().debug(e, exc_info=True)
591
+ pass
592
+
593
+ userOption = userOption.upper()
594
+
595
+ if userOption == "M":
596
+ ConsoleUtility.PKConsoleTools.clearScreen(forceTop=True)
597
+ return None
598
+ elif userOption == "Z":
599
+ self.menu_manager.handleExitRequest(userOption)
600
+ return None
601
+ elif userOption == "S":
602
+ OutputControls().printOutput(
603
+ colorText.GREEN
604
+ + " [+] Collecting all metrics for summarising..."
605
+ + colorText.END
606
+ )
607
+
608
+ # Enable showing/saving past strategy data
609
+ savedValue = self.config_manager.showPastStrategyData
610
+ self.config_manager.showPastStrategyData = True
611
+ df_all = PortfolioXRay.summariseAllStrategies()
612
+
613
+ if df_all is not None and len(df_all) > 0:
614
+ OutputControls().printOutput(
615
+ colorText.miniTabulator().tabulate(
616
+ df_all,
617
+ headers="keys",
618
+ tablefmt=colorText.No_Pad_GridFormat,
619
+ showindex=False,
620
+ maxcolwidths=Utility.tools.getMaxColumnWidths(df_all)
621
+ ).encode("utf-8").decode(STD_ENCODING)
622
+ )
623
+ self.backtest_manager.showBacktestResults(df_all, sortKey="Scanner", optionalName="InsightsSummary")
624
+ else:
625
+ OutputControls().printOutput("[!] Nothing to show here yet. Check back later.")
626
+
627
+ # Reinstate whatever was the earlier saved value
628
+ self.config_manager.showPastStrategyData = savedValue
629
+
630
+ if self.default_answer is None:
631
+ OutputControls().takeUserInput("Press <Enter> to continue...")
632
+
633
+ return None
634
+ else:
635
+ userOptions = userOption.split(",")
636
+ for usrOption in userOptions:
637
+ strategyFilter.append(self.menu_manager.m1.find(usrOption).menuText.strip())
638
+
639
+ return strategyFilter
640
+
641
+ def finishScreening(self, downloadOnly, testing, stockDictPrimary, loadCount,
642
+ testBuild, screenResults, saveResults, user=None):
643
+ """
644
+ Finalize the screening process by saving data and notifying results.
645
+
646
+ Args:
647
+ downloadOnly (bool): Whether only downloading data without screening
648
+ testing (bool): Whether running in test mode
649
+ stockDictPrimary (dict): Primary stock data dictionary
650
+ loadCount (int): Number of stocks loaded
651
+ testBuild (bool): Whether running test build
652
+ screenResults: Screen results data
653
+ saveResults: Results to save
654
+ user: User identifier for notifications
655
+ """
656
+ if "RUNNER" not in os.environ.keys() or downloadOnly:
657
+ self.data_manager.saveDownloadedData(downloadOnly, testing, stockDictPrimary, loadCount)
658
+
659
+ if not testBuild and not downloadOnly and not testing and \
660
+ ((self.user_passed_args is not None and "|" not in self.user_passed_args.options) or self.user_passed_args is None):
661
+ self.result_processor.saveNotifyResultsFile(
662
+ screenResults, saveResults, self.default_answer,
663
+ self.menu_manager.menu_choice_hierarchy, user=user
664
+ )
665
+
666
+ if ("RUNNER" in os.environ.keys() and not downloadOnly) or self.user_passed_args.log:
667
+ self.telegram_notifier.sendMessageToTelegramChannel(mediagroup=True, user=self.user_passed_args.user)
668
+
669
+ def resetConfigToDefault(self, force=False):
670
+ """
671
+ Reset configuration to default values.
672
+
673
+ Args:
674
+ force (bool): Whether to force reset
675
+ """
676
+ if self.user_passed_args is not None and self.user_passed_args.monitor is None:
677
+ if "PKDevTools_Default_Log_Level" in os.environ.keys():
678
+ if self.user_passed_args is None or (self.user_passed_args is not None and self.user_passed_args.options is not None and "|" not in self.user_passed_args.options and not self.user_passed_args.runintradayanalysis and self.user_passed_args.pipedtitle is None):
679
+ del os.environ['PKDevTools_Default_Log_Level']
680
+
681
+ self.config_manager.logsEnabled = False
682
+
683
+ if force:
684
+ self.config_manager.logsEnabled = False
685
+
686
+ self.config_manager.setConfig(ConfigManager.parser, default=True, showFileCreatedText=False)
687
+
688
+ def handle_google_sheets_integration(self):
689
+ """Handle Google Sheets integration if enabled."""
690
+ try:
691
+ creds = None
692
+ # Write into sheet only if it's the regular scan alert trigger in the morning and evening
693
+ if 'ALERT_TRIGGER' in os.environ.keys() and os.environ["ALERT_TRIGGER"] == 'Y':
694
+ if "GSHEET_SERVICE_ACCOUNT_DEV" in os.environ.keys() and (self.user_passed_args.backtestdaysago is None):
695
+ begin = time.time()
696
+ creds = os.environ.get("GSHEET_SERVICE_ACCOUNT_DEV")
697
+ OutputControls().printOutput(f"{colorText.GREEN} [+] Saving data to Google Spreadsheets now...{colorText.END}")
698
+ gClient = PKSpreadsheets(credentialDictStr=creds)
699
+ runOption = PKScanRunner.getFormattedChoices(self.user_passed_args, self.menu_manager.selected_choice)
700
+ df = self.scan_executor.saveResults.copy()
701
+ df["LastTradeDate"], df["LastTradeTime"] = self.data_manager.getLatestTradeDateTime(self.data_manager.stock_dict_primary)
702
+ gClient.df_to_sheet(df=df, sheetName=runOption)
703
+ OutputControls().printOutput(f"{colorText.GREEN} => Done in {round(time.time()-begin,2)}s{colorText.END}")
704
+ except:
705
+ pass
706
+
707
+ def handle_pinned_menu_options(self,testing):
708
+ """Handle pinned menu options for monitoring and quick access."""
709
+ if ("RUNNER" not in os.environ.keys() and
710
+ not testing and
711
+ (self.user_passed_args is None or
712
+ (self.user_passed_args is not None and
713
+ (self.user_passed_args.user is None or
714
+ str(self.user_passed_args.user) == self.telegram_notifier.DEV_CHANNEL_ID) and
715
+ (self.user_passed_args.answerdefault is None or self.user_passed_args.systemlaunched))) and
716
+ not self.user_passed_args.testbuild and
717
+ self.user_passed_args.monitor is None):
718
+
719
+ prevOutput_results = self.scan_executor.saveResults.index if (self.scan_executor.saveResults is not None and not self.scan_executor.saveResults.empty) else []
720
+ isNotPiped = (("|" not in self.user_passed_args.options) if (self.user_passed_args is not None and self.user_passed_args.options is not None) else True)
721
+ hasFoundStocks = len(prevOutput_results) > 0 and isNotPiped
722
+
723
+ if hasFoundStocks or (self.config_manager.showPinnedMenuEvenForNoResult and isNotPiped):
724
+ monitorOption = self.user_passed_args.systemlaunched if (self.user_passed_args is not None and isinstance(self.user_passed_args.systemlaunched, str) and self.user_passed_args.systemlaunched is not None) else (self.user_passed_args.options if (self.user_passed_args is not None and self.user_passed_args.options is not None) else "")
725
+
726
+ if len(monitorOption) == 0:
727
+ for choice in self.menu_manager.selected_choice.keys():
728
+ monitorOption = (f"{monitorOption}:" if len(monitorOption) > 0 else '') + f"{self.menu_manager.selected_choice[choice]}"
729
+
730
+ self.menu_manager.m0.renderPinnedMenu(substitutes=[monitorOption, len(prevOutput_results), monitorOption, monitorOption, monitorOption], skip=(["1","2","4","5"] if self.menu_manager.selected_choice["0"] in ["F"] else []))
731
+ pinOption = input(colorText.FAIL + " [+] Select option: ") or 'M'
732
+ OutputControls().printOutput(colorText.END, end="")
733
+
734
+ self.menu_manager.ensureMenusLoaded(self.menu_manager.selected_choice["0"], self.menu_manager.selected_choice["1"], self.menu_manager.selected_choice["2"])
735
+
736
+ if not PKPremiumHandler.hasPremium(self.menu_manager.m0.find(str(pinOption).upper())):
737
+ PKAnalyticsService().send_event(f"non_premium_user_pin_{self.menu_manager.selected_choice['0']}_{self.menu_manager.selected_choice['1']}_{self.menu_manager.selected_choice['2']}_{pinOption}")
738
+ PKAnalyticsService().send_event("app_exit")
739
+ sys.exit(0)
740
+
741
+ if pinOption in ["1", "2"]:
742
+ self.handle_pinned_monitoring_options(pinOption, monitorOption, prevOutput_results)
743
+ elif pinOption in ["3"]:
744
+ self.handle_time_window_navigation()
745
+ elif pinOption in ["4"]:
746
+ self.handle_pinned_option_saving(monitorOption)
747
+ elif pinOption in ["5"]:
748
+ self.handle_restart_with_previous_results(prevOutput_results)
749
+
750
+ self.result_processor.show_saved_diff_results = False
751
+
752
+ def handle_pinned_monitoring_options(self, pinOption, monitorOption, prevOutput_results):
753
+ """
754
+ Handle pinned monitoring options.
755
+
756
+ Args:
757
+ pinOption: Pinned option selected
758
+ monitorOption: Monitor option string
759
+ prevOutput_results: Previous results
760
+ """
761
+ if pinOption in ["2"]:
762
+ monitorOption = "X:0:0"
763
+ prevOutput_results = ",".join(prevOutput_results)
764
+ monitorOption = f"{monitorOption}:{prevOutput_results}"
765
+
766
+ launcher = f'"{sys.argv[0]}"' if " " in sys.argv[0] else sys.argv[0]
767
+ launcher = f"python3.12 {launcher}" if (launcher.endswith(".py\"") or launcher.endswith(".py")) else launcher
768
+ monitorOption = f'"{monitorOption}"'
769
+ scannerOptionQuoted = monitorOption.replace("'", '"')
770
+
771
+ OutputControls().printOutput(f"{colorText.GREEN}Launching PKScreener with pinned scan option. If it does not launch, please try with the following:{colorText.END}\n{colorText.FAIL}{launcher} --systemlaunched -a Y -m {scannerOptionQuoted}{colorText.END}")
772
+ sleep(2)
773
+ os.system(f"{launcher} --systemlaunched -a Y -m {scannerOptionQuoted}")
774
+
775
+ def handle_time_window_navigation(self):
776
+ """Handle time window navigation for intraday analysis."""
777
+ from pkscreener.classes.keys import getKeyBoardArrowInput
778
+
779
+ message = f"\n [+] {colorText.FAIL}Please use {colorText.END}{colorText.GREEN}Left / Right arrow keys{colorText.END} to slide through the {colorText.WARN}time-window by every 1 minute.{colorText.END}\n [+] Use {colorText.GREEN}Up / Down arrow keys{colorText.END} to jump {colorText.GREEN}forward / backwards{colorText.END} by {colorText.WARN}{self.config_manager.duration}{colorText.END}\n [+] {colorText.FAIL}Press any oher key to cancel.{colorText.END}"
780
+ currentTime = PKDateUtilities.currentDateTime()
781
+ requestTime = PKDateUtilities.currentDateTime()
782
+
783
+ OutputControls().printOutput(message)
784
+ direction = getKeyBoardArrowInput(message=None)
785
+ numRequestsInASecond = 0
786
+
787
+ while (direction is not None and direction not in ["RETURN", "CANCEL"]):
788
+ requestTimeDiff = PKDateUtilities.currentDateTime() - requestTime
789
+
790
+ if requestTimeDiff.total_seconds() <= 0.4:
791
+ numRequestsInASecond += 1
792
+ else:
793
+ numRequestsInASecond = 0
794
+
795
+ if numRequestsInASecond >= 10:
796
+ numRequestsInASecond = 0
797
+ fastMultiplier = 60
798
+ else:
799
+ fastMultiplier = 1
800
+
801
+ candleFrequency = self.config_manager.candleDurationFrequency
802
+ candleDuration = self.config_manager.candleDurationInt
803
+ multiplier = fastMultiplier * (60 if candleFrequency == "h" else (24*60 if candleFrequency == "d" else (24*60*5 if candleFrequency == "wk" else (24*60*5*20 if candleFrequency == "mo" else 1))))
804
+
805
+ if direction in ["LEFT", "DOWN"]:
806
+ prevTime = currentTime - timedelta(minutes=(candleDuration*multiplier if direction == "DOWN" else 1*fastMultiplier))
807
+ minPastDate = PKDateUtilities.currentDateTime() - timedelta(days=364)
808
+
809
+ if prevTime <= minPastDate:
810
+ prevTime = minPastDate
811
+
812
+ currentTime = prevTime
813
+ prevTime_comps = prevTime.strftime("%Y-%m-%d %H:%M:%S").split(" ")
814
+ dateComp = prevTime_comps[0]
815
+ timeComp = prevTime_comps[1].split(":")
816
+ prevTime = f"{colorText.FAIL}{dateComp}{colorText.END} {prevTime_comps[1]}" if direction == "DOWN" else f"{dateComp} {colorText.FAIL}{timeComp[0]}:{timeComp[1]}{colorText.END}:{timeComp[2]}"
817
+
818
+ OutputControls().moveCursorUpLines(lines=5)
819
+ OutputControls().printOutput(message)
820
+ OutputControls().printOutput(f" [+] {colorText.WARN}Go back to: {colorText.END}{colorText.GREEN}{prevTime}{colorText.END}{colorText.WARN} ? Press <Enter> to confirm.{colorText.END}")
821
+ elif direction in ["RIGHT", "UP"]:
822
+ prevTime = currentTime + timedelta(minutes=(candleDuration*multiplier if direction == "UP" else 1*fastMultiplier))
823
+
824
+ if prevTime > PKDateUtilities.currentDateTime():
825
+ prevTime = PKDateUtilities.currentDateTime()
826
+
827
+ currentTime = prevTime
828
+ prevTime_comps = prevTime.strftime("%Y-%m-%d %H:%M:%S").split(" ")
829
+ dateComp = prevTime_comps[0]
830
+ timeComp = prevTime_comps[1].split(":")
831
+ prevTime = f"{colorText.FAIL}{dateComp}{colorText.END} {prevTime_comps[1]}" if direction == "UP" else f"{dateComp} {colorText.FAIL}{timeComp[0]}:{timeComp[1]}{colorText.END}:{timeComp[2]}"
832
+
833
+ OutputControls().moveCursorUpLines(lines=5)
834
+ OutputControls().printOutput(message)
835
+ OutputControls().printOutput(f" [+] {colorText.WARN}Go forward to: {colorText.END}{colorText.GREEN}{prevTime}{colorText.END}{colorText.WARN} ? Press <Enter> to confirm.{colorText.END}")
836
+
837
+ requestTime = PKDateUtilities.currentDateTime()
838
+ direction = getKeyBoardArrowInput(message=None)
839
+
840
+ if direction is not None and direction == "RETURN":
841
+ # We need to take the data until "currentTime" from intraday data
842
+ if self.user_passed_args is not None and self.user_passed_args.progressstatus is not None:
843
+ runOptionName = self.user_passed_args.progressstatus.split("=>")[0].split(" [+] ")[1].strip()
844
+
845
+ if runOptionName.startswith("P"):
846
+ self.user_passed_args.options = runOptionName.replace("_", ":")
847
+
848
+ self.user_passed_args.stocklist = ','.join(self.scan_executor.screen_results.index)
849
+ tradingDaysInThePast = PKDateUtilities.trading_days_between(currentTime, PKDateUtilities.tradingDate())
850
+
851
+ if tradingDaysInThePast > 0:
852
+ self.user_passed_args.backtestdaysago = tradingDaysInThePast
853
+ elif tradingDaysInThePast < 0:
854
+ self.user_passed_args.backtestdaysago = None
855
+ elif tradingDaysInThePast == 0:
856
+ self.user_passed_args.slicewindow = f"'{currentTime}'"
857
+
858
+ ConsoleUtility.PKConsoleTools.clearScreen(clearAlways=True, forceTop=True)
859
+ OutputControls().printOutput(f"{colorText.WARN}Launching into the selected time-window!{colorText.END}{colorText.GREEN} Brace yourself for the time-travel!{colorText.END}")
860
+ sleep(5)
861
+
862
+ # Restart the main method with new parameters
863
+ return self.main(userArgs=self.user_passed_args, optionalFinalOutcome_df=None)
864
+
865
+ def handle_pinned_option_saving(self, monitorOption):
866
+ """
867
+ Handle saving pinned options for monitoring.
868
+
869
+ Args:
870
+ monitorOption: Monitor option to save
871
+ """
872
+ prevMonitorOption = f"{self.config_manager.myMonitorOptions}~" if len(self.config_manager.myMonitorOptions) > 0 else ""
873
+ self.config_manager.myMonitorOptions = f"{prevMonitorOption}{monitorOption}"
874
+ self.config_manager.setConfig(ConfigManager.parser, default=True, showFileCreatedText=False)
875
+
876
+ OutputControls().printOutput(f"[+] {colorText.GREEN} Your monitoring options have been updated:{colorText.END}\n {colorText.WARN}{self.config_manager.myMonitorOptions}{colorText.END}\n[+] {colorText.GREEN}You can run it using the option menu {colorText.END}{colorText.FAIL}-m{colorText.END} {colorText.GREEN}or using the main launch option {colorText.END}{colorText.FAIL}M >{colorText.END}")
877
+ sleep(4)
878
+
879
+ def handle_restart_with_previous_results(self, prevOutput_results):
880
+ """
881
+ Handle restarting with previous results.
882
+
883
+ Args:
884
+ prevOutput_results: Previous results to use
885
+ """
886
+ if len(self.scan_executor.save_results) > 0:
887
+ self.data_manager.last_scan_output_stock_codes = list(self.scan_executor.save_results.index)
888
+ self.user_passed_args.options = None
889
+ menuOption = None
890
+ executeOption = 0
891
+
892
+ # Restart the main method with previous results
893
+ return self.main(userArgs=self.user_passed_args, optionalFinalOutcome_df=None)
894
+
895
+ def startMarketMonitor(self, mp_dict, keyboardevent):
896
+ """
897
+ Start monitoring the market status in a separate process.
898
+
899
+ Args:
900
+ mp_dict: Multiprocessing dictionary
901
+ keyboardevent: Keyboard interrupt event
902
+ """
903
+ if not 'pytest' in sys.modules:
904
+ from PKDevTools.classes.NSEMarketStatus import NSEMarketStatus
905
+ NSEMarketStatus(mp_dict, keyboardevent).startMarketMonitor()
906
+
907
+ # Global functions for backward compatibility
908
+ def startMarketMonitor(mp_dict, keyboardevent):
909
+ """Start market monitor - maintained for backward compatibility."""
910
+ screener = PKScreenerMain()
911
+ screener.startMarketMonitor(mp_dict, keyboardevent)
912
+
913
+
914
+ if __name__ == "__main__":
915
+ screener = PKScreenerMain()
916
+ screener.main()