pkscreener 0.46.20250909.768__cp312-cp312-manylinux2014_x86_64.whl → 0.46.20250911.771__cp312-cp312-manylinux2014_x86_64.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.20250911.771.data/purelib/pkscreener/MainApplication.py +721 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250911.771.data}/purelib/pkscreener/README.txt +5 -5
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250911.771.data}/purelib/pkscreener/classes/AssetsManager.py +12 -12
- pkscreener-0.46.20250911.771.data/purelib/pkscreener/classes/MenuManager.py +2373 -0
- pkscreener-0.46.20250911.771.data/purelib/pkscreener/classes/PKScreenerMain.py +916 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250911.771.data}/purelib/pkscreener/classes/ScreeningStatistics.py +2 -0
- pkscreener-0.46.20250911.771.data/purelib/pkscreener/classes/__init__.py +1 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250911.771.data}/purelib/pkscreener/pkscreenercli.py +1 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250911.771.data}/purelib/pkscreener/requirements.txt +1 -0
- {pkscreener-0.46.20250909.768.dist-info → pkscreener-0.46.20250911.771.dist-info}/METADATA +8 -7
- pkscreener-0.46.20250911.771.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.20250911.771.data}/purelib/pkscreener/Disclaimer.txt +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250911.771.data}/purelib/pkscreener/LICENSE-Others.txt +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250911.771.data}/purelib/pkscreener/LICENSE.txt +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250911.771.data}/purelib/pkscreener/LogoWM.txt +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250911.771.data}/purelib/pkscreener/__init__.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250911.771.data}/purelib/pkscreener/classes/ArtTexts.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250911.771.data}/purelib/pkscreener/classes/Backtest.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250911.771.data}/purelib/pkscreener/classes/Barometer.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250911.771.data}/purelib/pkscreener/classes/BaseScreeningStatistics.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250911.771.data}/purelib/pkscreener/classes/CandlePatterns.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250911.771.data}/purelib/pkscreener/classes/Changelog.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250911.771.data}/purelib/pkscreener/classes/ConfigManager.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250911.771.data}/purelib/pkscreener/classes/ConsoleMenuUtility.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250911.771.data}/purelib/pkscreener/classes/ConsoleUtility.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250911.771.data}/purelib/pkscreener/classes/Fetcher.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250911.771.data}/purelib/pkscreener/classes/GlobalStore.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250911.771.data}/purelib/pkscreener/classes/ImageUtility.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250911.771.data}/purelib/pkscreener/classes/MarketMonitor.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250911.771.data}/purelib/pkscreener/classes/MarketStatus.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250911.771.data}/purelib/pkscreener/classes/MenuOptions.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250911.771.data}/purelib/pkscreener/classes/Messenger.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250911.771.data}/purelib/pkscreener/classes/OtaUpdater.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250911.771.data}/purelib/pkscreener/classes/PKAnalytics.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250911.771.data}/purelib/pkscreener/classes/PKDataService.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250911.771.data}/purelib/pkscreener/classes/PKDemoHandler.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250911.771.data}/purelib/pkscreener/classes/PKMarketOpenCloseAnalyser.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250911.771.data}/purelib/pkscreener/classes/PKPremiumHandler.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250911.771.data}/purelib/pkscreener/classes/PKScanRunner.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250911.771.data}/purelib/pkscreener/classes/PKScheduledTaskProgress.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250911.771.data}/purelib/pkscreener/classes/PKScheduler.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250911.771.data}/purelib/pkscreener/classes/PKSpreadsheets.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250911.771.data}/purelib/pkscreener/classes/PKTask.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250911.771.data}/purelib/pkscreener/classes/PKUserRegistration.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250911.771.data}/purelib/pkscreener/classes/Pktalib.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250911.771.data}/purelib/pkscreener/classes/Portfolio.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250911.771.data}/purelib/pkscreener/classes/PortfolioXRay.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250911.771.data}/purelib/pkscreener/classes/StockScreener.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250911.771.data}/purelib/pkscreener/classes/StockSentiment.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250911.771.data}/purelib/pkscreener/classes/UserMenuChoicesHandler.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250911.771.data}/purelib/pkscreener/classes/Utility.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250911.771.data}/purelib/pkscreener/classes/WorkflowManager.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250911.771.data}/purelib/pkscreener/classes/keys.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250911.771.data}/purelib/pkscreener/courbd.ttf +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250911.771.data}/purelib/pkscreener/globals.py +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250911.771.data}/purelib/pkscreener/pkscreener.ini +0 -0
- {pkscreener-0.46.20250909.768.data → pkscreener-0.46.20250911.771.data}/purelib/pkscreener/pkscreenerbot.py +0 -0
- {pkscreener-0.46.20250909.768.dist-info → pkscreener-0.46.20250911.771.dist-info}/WHEEL +0 -0
- {pkscreener-0.46.20250909.768.dist-info → pkscreener-0.46.20250911.771.dist-info}/entry_points.txt +0 -0
- {pkscreener-0.46.20250909.768.dist-info → pkscreener-0.46.20250911.771.dist-info}/licenses/LICENSE +0 -0
- {pkscreener-0.46.20250909.768.dist-info → pkscreener-0.46.20250911.771.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()
|