Coverage for src/scrilla/gui/widgets/functions.py: 0%
343 statements
« prev ^ index » next coverage.py v6.4.2, created at 2022-07-18 18:14 +0000
« prev ^ index » next coverage.py v6.4.2, created at 2022-07-18 18:14 +0000
1# This file is part of scrilla: https://github.com/chinchalinchin/scrilla.
3# scrilla is free software: you can redistribute it and/or modify
4# it under the terms of the GNU General Public License version 3
5# as published by the Free Software Foundation.
7# scrilla is distributed in the hope that it will be useful,
8# but WITHOUT ANY WARRANTY; without even the implied warranty of
9# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10# GNU General Public License for more details.
12# You should have received a copy of the GNU General Public License
13# along with scrilla. If not, see <https://www.gnu.org/licenses/>
14# or <https://github.com/chinchalinchin/scrilla/blob/develop/main/LICENSE>.
16from typing import Union
17from PySide6 import QtGui, QtCore, QtWidgets
20from scrilla import settings, services
21from scrilla.static import keys
22from scrilla.static import formats as app_formats
23# TODO: conditional import based on ANALYSIS_MODE
24from scrilla.analysis import estimators, markets, optimizer, plotter
25from scrilla.analysis.models.geometric import statistics
26from scrilla.analysis.objects.portfolio import Portfolio
27from scrilla.analysis.objects.cashflow import Cashflow
29from scrilla.util import dater, outputter, helper
31from scrilla.gui import formats, utilities
32from scrilla.gui.widgets import factories, components
34logger = outputter.Logger('gui.functions', settings.LOG_LEVEL)
37class DistributionWidget(components.SkeletonWidget):
38 def __init__(self, layer: str, parent: Union[QtWidgets.QWidget, None] = None):
39 super().__init__(function='plot_return_dist', parent=parent)
40 self.setObjectName(layer)
41 self._init_widgets()
42 self._arrange_widgets()
43 self._stage_widgets()
45 def _init_widgets(self):
46 self.arg_widget = components.ArgumentWidget(calculate_function=self.calculate,
47 clear_function=self.clear,
48 controls=self.controls,
49 layer=utilities.get_next_layer(self.objectName()))
50 self.title = factories.atomic_widget_factory(
51 component='heading', title='Distribution of Returns')
52 self.tab_container = factories.layout_factory(layout='vertical-box')
53 self.tab_widget = QtWidgets.QTabWidget()
54 self.setLayout(QtWidgets.QHBoxLayout())
56 def _arrange_widgets(self):
57 self.tab_container.layout().addWidget(self.tab_widget)
58 self.layout().addWidget(self.tab_container)
59 self.layout().addWidget(self.arg_widget)
61 def _stage_widgets(self):
62 self.arg_widget.prime()
64 def resizeEvent(self, event: QtGui.QResizeEvent) -> None:
65 for i in range(self.tab_widget.count()):
66 if self.tab_widget.widget(i).figure.isVisible():
67 self.tab_widget.widget(i).set_pixmap()
68 return super().resizeEvent(event)
70 @QtCore.Slot()
71 def calculate(self):
72 symbols = self.arg_widget.get_symbol_input()
73 start_date = self.arg_widget.get_control_input('start_date')
74 end_date = self.arg_widget.get_control_input('end_date')
76 for symbol in symbols:
77 qq_plot = components.GraphWidget(tmp_graph_key=f'{keys.keys["GUI"]["TEMP"]["QQ"]}_{symbol}',
78 layer=utilities.get_next_layer(self.objectName()))
79 dist_plot = components.GraphWidget(tmp_graph_key=f'{keys.keys["GUI"]["TEMP"]["DIST"]}_{symbol}',
80 layer=utilities.get_next_layer(self.objectName()))
81 returns = statistics.get_sample_of_returns(ticker=symbol,
82 start_date=start_date,
83 end_date=end_date,
84 daily=True)
85 qq_series = estimators.qq_series_for_sample(sample=returns)
86 plotter.plot_qq_series(ticker=symbol,
87 qq_series=qq_series,
88 show=False,
89 savefile=f'{settings.TEMP_DIR}/{keys.keys["GUI"]["TEMP"]["QQ"]}_{symbol}')
90 plotter.plot_return_histogram(ticker=symbol,
91 sample=returns,
92 show=False,
93 savefile=f'{settings.TEMP_DIR}/{keys.keys["GUI"]["TEMP"]["DIST"]}_{symbol}',)
94 dist_plot.set_pixmap()
95 qq_plot.set_pixmap()
96 self.tab_widget.addTab(qq_plot, f'{symbol} QQ Plot')
97 self.tab_widget.addTab(dist_plot, f'{symbol} Distribution')
98 self.tab_widget.show()
99 self.arg_widget.fire()
101 @QtCore.Slot()
102 def clear(self):
103 self.arg_widget.prime()
104 total = self.tab_widget.count()
105 for _ in range(total):
106 self.tab_widget.removeTab(0)
109class YieldCurveWidget(components.SkeletonWidget):
110 def __init__(self, layer: str, parent: Union[QtWidgets.QWidget, None] = None):
111 super().__init__(function='yield_curve', parent=parent)
112 self.setObjectName(layer)
113 self._init_widgets()
114 self._arrange_widgets()
115 self._stage_widgets()
117 def _init_widgets(self):
118 self.graph_widget = components.GraphWidget(tmp_graph_key=keys.keys['GUI']['TEMP']['YIELD'],
119 layer=utilities.get_next_layer(self.objectName()))
120 self.arg_widget = components.ArgumentWidget(calculate_function=self.calculate,
121 clear_function=self.clear,
122 controls=self.controls,
123 layer=utilities.get_next_layer(
124 self.objectName()),
125 mode=components.SYMBOLS_NONE)
126 # TODO: initialize arg widget WITHOUT tickers
128 self.setLayout(QtWidgets.QHBoxLayout())
130 def _arrange_widgets(self):
131 self.layout().addWidget(self.graph_widget)
132 self.layout().addWidget(self.arg_widget)
134 def _stage_widgets(self):
135 self.arg_widget.prime()
137 def resizeEvent(self, event: QtGui.QResizeEvent) -> None:
138 if self.graph_widget.figure.isVisible():
139 self.graph_widget.set_pixmap()
140 return super().resizeEvent(event)
142 @QtCore.Slot()
143 def calculate(self):
144 if self.graph_widget.figure.isVisible():
145 self.graph_widget.figure.hide()
147 yield_curve = {}
148 start_date = dater.this_date_or_last_trading_date(
149 self.arg_widget.get_control_input('start_date'))
150 start_string = dater.to_string(start_date)
151 yield_curve[start_string] = []
152 for maturity in keys.keys['YIELD_CURVE']:
153 rate = services.get_daily_interest_history(maturity=maturity,
154 start_date=start_date,
155 end_date=start_date)
156 yield_curve[start_string].append(rate[start_string])
158 plotter.plot_yield_curve(yield_curve=yield_curve,
159 show=False,
160 savefile=f'{settings.TEMP_DIR}/{keys.keys["GUI"]["TEMP"]["YIELD"]}')
161 self.graph_widget.set_pixmap()
162 self.arg_widget.fire()
164 @QtCore.Slot()
165 def clear(self):
166 self.graph_widget.clear()
167 self.arg_widget.prime()
170class DiscountDividendWidget(components.SkeletonWidget):
171 def __init__(self, layer: str, parent: Union[QtWidgets.QWidget, None] = None):
172 super().__init__(function='discount_dividend', parent=parent)
173 self.setObjectName(layer)
174 self._init_widgets()
175 self._arrange_widgets()
176 self._stage_widgets()
178 def _init_widgets(self):
179 self.tab_container = factories.layout_factory(layout='vertical-box')
180 self.tab_widget = QtWidgets.QTabWidget()
181 self.arg_widget = components.ArgumentWidget(calculate_function=self.calculate,
182 clear_function=self.clear,
183 controls=self.controls,
184 layer=utilities.get_next_layer(self.objectName()))
185 # TODO: restrict arg symbol input to one symbol somehow
186 self.setLayout(QtWidgets.QHBoxLayout())
188 def _arrange_widgets(self):
189 self.tab_container.layout().addWidget(self.tab_widget)
190 self.layout().addWidget(self.tab_container)
191 self.layout().addWidget(self.arg_widget)
193 def _stage_widgets(self):
194 self.arg_widget.prime()
196 def resizeEvent(self, event: QtGui.QResizeEvent) -> None:
197 for i in range(self.tab_widget.count()):
198 if self.tab_widget.widget(i).figure.isVisible():
199 self.tab_widget.widget(i).set_pixmap()
200 return super().resizeEvent(event)
202 @QtCore.Slot()
203 def calculate(self):
204 symbols = self.arg_widget.get_symbol_input()
205 discount = self.arg_widget.get_control_input('discount')
207 for symbol in symbols:
208 if discount is None:
209 discount = markets.cost_of_equity(ticker=symbol,
210 start_date=self.arg_widget.get_control_input(
211 'start_date'),
212 end_date=self.arg_widget.get_control_input('end_date'))
213 dividends = services.get_dividend_history(ticker=symbol)
214 cashflow = Cashflow(sample=dividends, discount_rate=discount)
215 graph_widget = components.GraphWidget(tmp_graph_key=f'{keys.keys["GUI"]["TEMP"]["DIVIDEND"]}_{symbol}',
216 layer=utilities.get_next_layer(self.objectName()))
217 plotter.plot_cashflow(ticker=symbol,
218 cashflow=cashflow,
219 show=False,
220 savefile=f'{settings.TEMP_DIR}/{keys.keys["GUI"]["TEMP"]["DIVIDEND"]}_{symbol}')
222 graph_widget.set_pixmap()
223 self.tab_widget.addTab(graph_widget, f'{symbol} DDM PLOT')
224 self.tab_widget.show()
225 self.arg_widget.fire()
227 @QtCore.Slot()
228 def clear(self):
229 self.arg_widget.prime()
230 total = self.tab_widget.count()
231 for _ in range(total):
232 self.tab_widget.removeTab(0)
235class RiskProfileWidget(components.SkeletonWidget):
236 def __init__(self, layer: str, parent: Union[QtWidgets.QWidget, None] = None):
237 super().__init__(function='risk_profile', parent=parent)
238 self.setObjectName(layer)
239 self._init_widgets()
240 self._arrange_widgets()
241 self._stage_widgets()
243 def _init_widgets(self):
244 self.composite_widget = components.CompositeWidget(keys.keys['GUI']['TEMP']['PROFILE'],
245 widget_title="Risk Analysis",
246 table_title="CAPM Risk Profile",
247 graph_title="Risk-Return Plane",
248 layer=utilities.get_next_layer(self.objectName()))
249 self.arg_widget = components.ArgumentWidget(calculate_function=self.calculate,
250 clear_function=self.clear,
251 controls=self.controls,
252 layer=utilities.get_next_layer(self.objectName()))
253 self.setLayout(QtWidgets.QHBoxLayout())
255 def _arrange_widgets(self):
256 self.layout().addWidget(self.composite_widget)
257 self.layout().addWidget(self.arg_widget)
259 def _stage_widgets(self):
260 self.arg_widget.prime()
262 @QtCore.Slot()
263 def calculate(self):
264 if self.composite_widget.graph_widget.figure.isVisible():
265 self.composite_widget.graph_widget.figure.hide()
267 symbols = self.arg_widget.get_symbol_input()
269 self.composite_widget.table_widget.init_table(rows=symbols,
270 columns=['Return', 'Volatility', 'Sharpe', 'Beta', 'Equity Cost'])
272 profiles = {}
273 start_date = start_date = self.arg_widget.get_control_input(
274 'start_date')
275 end_date = self.arg_widget.get_control_input('end_date')
276 for i, symbol in enumerate(symbols):
277 profiles[symbol] = statistics.calculate_risk_return(ticker=symbol,
278 start_date=start_date,
279 end_date=end_date)
280 profiles[symbol][keys.keys['APP']['PROFILE']
281 ['SHARPE']] = markets.sharpe_ratio(ticker=symbol,
282 start_date=start_date,
283 end_date=end_date)
284 profiles[symbol][keys.keys['APP']['PROFILE']
285 ['BETA']] = markets.market_beta(ticker=symbol,
286 start_date=start_date,
287 end_date=end_date)
288 profiles[symbol][keys.keys['APP']['PROFILE']
289 ['EQUITY']] = markets.cost_of_equity(ticker=symbol,
290 start_date=start_date,
291 end_date=end_date)
293 formatted_profile = formats.format_profile(profiles[symbol])
295 for j, statistic in enumerate(formatted_profile.keys()):
296 table_item = factories.atomic_widget_factory(
297 component='table-item', title=formatted_profile[statistic])
298 self.composite_widget.table_widget.table.setItem(
299 i, j, table_item)
301 plotter.plot_profiles(symbols=symbols, profiles=profiles, show=False,
302 savefile=f'{settings.TEMP_DIR}/{keys.keys["GUI"]["TEMP"]["PROFILE"]}')
304 self.composite_widget.graph_widget.set_pixmap()
305 self.composite_widget.table_widget.show_table()
306 self.arg_widget.fire()
308 def resizeEvent(self, event: QtGui.QResizeEvent) -> None:
309 if self.composite_widget.graph_widget.figure.isVisible():
310 self.composite_widget.graph_widget.set_pixmap()
311 return super().resizeEvent(event)
313 @QtCore.Slot()
314 def clear(self):
315 self.composite_widget.graph_widget.clear()
316 self.composite_widget.table_widget.table.clear()
317 self.composite_widget.table_widget.table.hide()
318 self.arg_widget.prime()
321class CorrelationWidget(components.SkeletonWidget):
322 def __init__(self, layer: str, parent: Union[QtWidgets.QWidget, None] = None):
323 super().__init__(function='correlation', parent=parent)
324 self.setObjectName(layer)
325 self._init_widgets()
326 self._arrange_widgets()
327 self._stage_widgets()
329 def _init_widgets(self):
330 self.table_widget = components.TableWidget(widget_title="Correlation Matrix",
331 layer=utilities.get_next_layer(self.objectName()))
332 self.arg_widget = components.ArgumentWidget(calculate_function=self.calculate,
333 clear_function=self.clear,
334 controls=self.controls,
335 layer=utilities.get_next_layer(self.objectName()))
336 self.setLayout(QtWidgets.QHBoxLayout())
338 def _arrange_widgets(self):
339 self.layout().addWidget(self.table_widget)
340 self.layout().addWidget(self.arg_widget)
342 def _stage_widgets(self):
343 self.arg_widget.prime()
345 @QtCore.Slot()
346 def calculate(self):
347 if self.table_widget.table.isVisible():
348 self.table_widget.table.clear()
349 self.table_widget.table.hide()
351 symbols = self.arg_widget.get_symbol_input()
353 if len(symbols) > 1:
354 self.table_widget.init_table(rows=symbols, columns=symbols)
356 matrix = statistics.correlation_matrix(tickers=symbols,
357 start_date=self.arg_widget.get_control_input(
358 'start_date'),
359 end_date=self.arg_widget.get_control_input('end_date'))
360 for i in range(0, len(symbols)):
361 for j in range(i, len(symbols)):
362 item_upper = factories.atomic_widget_factory(
363 component='table-item', title=app_formats.format_float_percent(matrix[i][j]))
364 item_lower = factories.atomic_widget_factory(
365 component='table-item', title=app_formats.format_float_percent(matrix[j][i]))
366 self.table_widget.table.setItem(j, i, item_upper)
367 self.table_widget.table.setItem(i, j, item_lower)
368 else:
369 print('error handling goes here')
371 self.table_widget.show_table()
372 self.arg_widget.fire()
374 @QtCore.Slot()
375 def clear(self):
376 self.arg_widget.prime()
377 self.table_widget.table.clear()
378 self.table_widget.table.hide()
379 self.table_widget.download_button.hide()
382class OptimizerWidget(components.SkeletonWidget):
383 def __init__(self, layer: str, parent: Union[QtWidgets.QWidget, None] = None):
384 super().__init__(function='optimize_portfolio', parent=parent)
385 self.setObjectName(layer)
386 self._init_widgets()
387 self._arrange_widgets()
388 self._stage_widgets()
390 def _init_widgets(self):
391 self.title = factories.atomic_widget_factory(
392 component='heading', title=None)
393 self.table_widget = components.TableWidget(widget_title="Optimization Results",
394 layer=utilities.get_next_layer(self.objectName()))
395 self.arg_widget = components.ArgumentWidget(calculate_function=self.optimize,
396 clear_function=self.clear,
397 controls=self.controls,
398 layer=utilities.get_next_layer(self.objectName()))
399 self.setLayout(QtWidgets.QHBoxLayout())
401 def _arrange_widgets(self):
402 self.layout().addWidget(self.table_widget)
403 self.layout().addWidget(self.arg_widget)
405 def _stage_widgets(self):
406 self.arg_widget.prime()
408 @QtCore.Slot()
409 def optimize(self):
410 if self.table_widget.table.isVisible():
411 self.table_widget.table.clear()
412 self.table_widget.table.hide()
414 symbols = self.arg_widget.get_symbol_input()
416 # TODO: better error checking
417 if len(symbols) > 1:
418 investment = self.arg_widget.get_control_input('investment')
419 this_portfolio = Portfolio(tickers=symbols,
420 start_date=self.arg_widget.get_control_input(
421 'start_date'),
422 end_date=self.arg_widget.get_control_input('end_date'))
423 allocation = optimizer.optimize_portfolio_variance(portfolio=this_portfolio,
424 target_return=self.arg_widget.get_control_input('target'))
425 self.title.setText(formats.format_allocation_profile_title(
426 allocation, this_portfolio))
428 prices = services.get_daily_prices_latest(tickers=symbols)
430 if investment is None:
431 self.table_widget.init_table(
432 rows=symbols, columns=['Allocation'])
433 else:
434 self.table_widget.init_table(rows=symbols, columns=[
435 'Allocation', 'Shares'])
436 shares = this_portfolio.calculate_approximate_shares(
437 allocation, float(investment), prices)
439 for i in range(len(symbols)):
440 item = factories.atomic_widget_factory(
441 component='table-item', title=app_formats.format_float_percent(allocation[i]))
442 self.table_widget.table.setItem(i, 0, item)
444 if investment is not None:
445 share_item = factories.atomic_widget_factory(
446 component='table-item', title=str(shares[i]))
447 self.table_widget.table.setItem(i, 1, share_item)
449 # TODO: display amount vested per equity
450 # TODO: display total portfolio return and volatility
451 # TODO: display actual investment
452 self.table_widget.show_table()
453 self.arg_widget.fire()
455 else:
456 print('error handling goes here')
458 @QtCore.Slot()
459 def clear(self):
460 self.table_widget.table.clear()
461 self.table_widget.table.hide()
462 self.arg_widget.prime()
465class EfficientFrontierWidget(components.SkeletonWidget):
466 def __init__(self, layer: str, parent: Union[QtWidgets.QWidget, None] = None):
467 super().__init__(function='efficient_frontier', parent=parent)
468 self.setObjectName(layer)
469 self._init_widgets()
470 self._arrange_widgets()
471 self._stage_widgets()
473 def _init_widgets(self):
474 self.graph_widget = components.GraphWidget(tmp_graph_key=keys.keys['GUI']['TEMP']['FRONTIER'],
475 layer=utilities.get_next_layer(self.objectName()))
476 self.arg_widget = components.ArgumentWidget(calculate_function=self.calculate,
477 clear_function=self.clear,
478 controls=self.controls,
479 layer=utilities.get_next_layer(self.objectName()))
480 self.setLayout(QtWidgets.QHBoxLayout())
481 # TODO: portfolio tabs
483 def _arrange_widgets(self):
484 self.layout().addWidget(self.graph_widget)
485 self.layout().addWidget(self.arg_widget)
487 def _stage_widgets(self):
488 self.arg_widget.prime()
490 def resizeEvent(self, event: QtGui.QResizeEvent) -> None:
491 if self.graph_widget.figure.isVisible():
492 self.graph_widget.set_pixmap()
493 return super().resizeEvent(event)
495 @QtCore.Slot()
496 def calculate(self):
497 if self.graph_widget.figure.isVisible():
498 self.graph_widget.figure.hide()
500 this_portfolio = Portfolio(tickers=self.arg_widget.get_symbol_input(),
501 start_date=self.arg_widget.get_control_input(
502 'start_date'),
503 end_date=self.arg_widget.get_control_input('end_date'))
504 frontier = optimizer.calculate_efficient_frontier(portfolio=this_portfolio,
505 steps=self.arg_widget.get_control_input('steps'))
506 plotter.plot_frontier(portfolio=this_portfolio,
507 frontier=frontier,
508 show=False,
509 savefile=f'{settings.TEMP_DIR}/{keys.keys["GUI"]["TEMP"]["FRONTIER"]}')
511 self.graph_widget.set_pixmap()
512 self.arg_widget.fire()
514 @QtCore.Slot()
515 def clear(self):
516 self.graph_widget.figure.hide()
517 self.arg_widget.prime()
520class MovingAverageWidget(components.SkeletonWidget):
521 def __init__(self, layer: str, parent: Union[QtWidgets.QWidget, None] = None):
522 super().__init__(function='moving_averages', parent=parent)
523 self.setObjectName(layer)
524 self._init_widgets()
525 self._arrange_widgets()
526 self._stage_widgets()
528 def _init_widgets(self):
529 self.graph_widget = components.GraphWidget(keys.keys['GUI']['TEMP']['AVERAGES'],
530 layer=utilities.get_next_layer(self.objectName()))
531 self.arg_widget = components.ArgumentWidget(calculate_function=self.calculate,
532 clear_function=self.clear,
533 controls=self.controls,
534 layer=utilities.get_next_layer(
535 self.objectName()),
536 mode=components.SYMBOLS_SINGLE)
537 self.setLayout(QtWidgets.QHBoxLayout())
539 def _arrange_widgets(self):
540 self.layout().addWidget(self.graph_widget)
541 self.layout().addWidget(self.arg_widget)
543 def _stage_widgets(self):
544 self.arg_widget.prime()
546 def resizeEvent(self, event: QtGui.QResizeEvent) -> None:
547 if self.graph_widget.figure.isVisible():
548 self.graph_widget.set_pixmap()
549 return super().resizeEvent(event)
551 @QtCore.Slot()
552 def calculate(self):
553 if self.graph_widget.figure.isVisible():
554 self.graph_widget.figure.hide()
556 moving_averages = statistics.calculate_moving_averages(ticker=self.arg_widget.get_symbol_input()[0],
557 start_date=self.arg_widget.get_control_input(
558 'start_date'),
559 end_date=self.arg_widget.get_control_input('end_date'))
561 plotter.plot_moving_averages(ticker=self.arg_widget.get_symbol_input()[0],
562 averages=moving_averages,
563 show=False,
564 savefile=f'{settings.TEMP_DIR}/{keys.keys["GUI"]["TEMP"]["AVERAGES"]}')
565 self.graph_widget.set_pixmap()
566 self.arg_widget.fire()
568 @QtCore.Slot()
569 def clear(self):
570 self.arg_widget.prime()
571 self.graph_widget.clear()