azapyGUI 0.0.1__py3-none-any.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 (54) hide show
  1. azapyGUI/AppSettingsPage.py +213 -0
  2. azapyGUI/AppSettingsPageMisc.py +101 -0
  3. azapyGUI/AppSettingsWindow.py +52 -0
  4. azapyGUI/BacktestComputation.py +166 -0
  5. azapyGUI/BacktestEntryWindow.py +307 -0
  6. azapyGUI/BacktestMenuPortfolioWindow.py +20 -0
  7. azapyGUI/CloneMenuPortfolioWindow.py +93 -0
  8. azapyGUI/CrossHairBCursor.py +80 -0
  9. azapyGUI/DF_Window.py +63 -0
  10. azapyGUI/DF_table.py +282 -0
  11. azapyGUI/EditMenuPortfolioWindow.py +16 -0
  12. azapyGUI/EditPortfolioWindow.py +475 -0
  13. azapyGUI/EntryClonePortfolioWindow.py +35 -0
  14. azapyGUI/EntryNameWindow.py +55 -0
  15. azapyGUI/EntryRenamePortfolioWindow.py +33 -0
  16. azapyGUI/GetMktData.py +85 -0
  17. azapyGUI/MenuApp.py +194 -0
  18. azapyGUI/MktDataFrame.py +129 -0
  19. azapyGUI/MktDataNode.py +34 -0
  20. azapyGUI/ModelParamEditWindow.py +143 -0
  21. azapyGUI/NrShares_table.py +54 -0
  22. azapyGUI/PortAnalyseWindow.py +179 -0
  23. azapyGUI/PortDataNode.py +180 -0
  24. azapyGUI/PortfolioFrame.py +197 -0
  25. azapyGUI/RebalanceMenuPortfolioWindow.py +21 -0
  26. azapyGUI/RemoveMenuPortfolioWindow.py +33 -0
  27. azapyGUI/SaveMenuPortfolioWindow.py +36 -0
  28. azapyGUI/Scrollable.py +60 -0
  29. azapyGUI/SelectOneWindow.py +65 -0
  30. azapyGUI/SymbAnalyseWindow.py +21 -0
  31. azapyGUI/SymbExtractWindow.py +129 -0
  32. azapyGUI/SymbTableEntry.py +109 -0
  33. azapyGUI/TimeSeriesViewWindow.py +480 -0
  34. azapyGUI/ViewTip.py +72 -0
  35. azapyGUI/WeightsWindow.py +352 -0
  36. azapyGUI/__init__.py +6 -0
  37. azapyGUI/azHelper.py +27 -0
  38. azapyGUI/azapyApp.py +89 -0
  39. azapyGUI/config.py +35 -0
  40. azapyGUI/configHelps.py +84 -0
  41. azapyGUI/configMSG.py +194 -0
  42. azapyGUI/configModels.py +519 -0
  43. azapyGUI/configPlot.py +70 -0
  44. azapyGUI/configSettings.py +138 -0
  45. azapyGUI/configTips.py +240 -0
  46. azapyGUI/mktDataValidation.py +42 -0
  47. azapyGUI/modelParametersValidation.py +442 -0
  48. azapyGUI/serviceMasterUserConfig.py +28 -0
  49. azapyGUI/tkHelper.py +18 -0
  50. azapyGUI-0.0.1.dist-info/LICENSE +674 -0
  51. azapyGUI-0.0.1.dist-info/METADATA +126 -0
  52. azapyGUI-0.0.1.dist-info/RECORD +54 -0
  53. azapyGUI-0.0.1.dist-info/WHEEL +5 -0
  54. azapyGUI-0.0.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,480 @@
1
+ import tkinter as tk
2
+ from tkinter import ttk
3
+ import matplotlib.dates as mdates
4
+ from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
5
+ from matplotlib.figure import Figure
6
+ import pandas as pd
7
+ import os
8
+ import webbrowser
9
+ import azapy as az
10
+
11
+ import azapyGUI.config as config
12
+ import azapyGUI.configPlot as configPlot
13
+ import azapyGUI.configSettings as configSettings
14
+ import azapyGUI.configHelps as configHelps
15
+ from azapyGUI.CrossHairBCursor import CrossHairBCursor
16
+ from azapyGUI.DF_Window import DF_Window
17
+
18
+ WIDTH_CBX = 11
19
+ SCALE_OFFSET = 0
20
+
21
+ class TimeSeriesViewWindow(tk.Toplevel):
22
+ def __init__(self, master=None, name=None, data=None, ref_name=None, col_name=None):
23
+ super().__init__()
24
+ self._scale_refresh = True
25
+ self._fig_legend_refresh = True
26
+ self._ref_name_refresh = True
27
+
28
+ self._plot_lines_dict = {}
29
+ self._tops_ww = {}
30
+
31
+ self._master = master
32
+ self._name = name
33
+ self._data = data
34
+ self._ref_name = ref_name
35
+ self._col_name = col_name
36
+ self._report_names = ['Summary', 'Annual', 'Quarterly', 'Monthly', 'Drawdowns']
37
+
38
+ self._visible = {kk: True for kk in self._data.keys()}
39
+
40
+ self._window = self
41
+ self._window.title("Statistics")
42
+ self._window.focus_set()
43
+ self._window.protocol("WM_DELETE_WINDOW", self._btn_quit_func)
44
+
45
+ menubar = tk.Menu(self._window)
46
+ filemenu = tk.Menu(menubar, tearoff=0)
47
+ filemenu.add_command(label='Help', command=self._menu_help_func)
48
+ menubar.add_cascade(label='Help', menu=filemenu)
49
+ self._window.config(menu=menubar)
50
+
51
+ self._data_info()
52
+
53
+ # frames: general <- (left-plot, right-btn)
54
+ frm_top = tk.LabelFrame(master=self._window, text='View & Stats')
55
+ frm_top.pack(pady=5, padx=5, fill=tk.BOTH, expand=True)
56
+
57
+ frm_plot_0 = tk.Frame(master=frm_top)
58
+ frm_plot_0.pack(fill=tk.BOTH, expand=True, side=tk.LEFT)
59
+
60
+ self.frm_plot = tk.Frame(master=frm_plot_0)
61
+ self.frm_plot.pack(fill=tk.BOTH, expand=True, side=tk.TOP)
62
+
63
+ self.frm_btn = tk.Frame(master=frm_top, width=100)
64
+ self.frm_btn.pack(fill=tk.Y, expand=False, side=tk.RIGHT)
65
+
66
+ # frm_plot - left side plot
67
+ self._fig = Figure(figsize=(8, 5), dpi=120)
68
+ self._ax = self._fig.add_subplot(111)
69
+
70
+ self._ax.label_outer()
71
+ self._ax.tick_params(axis='x', labelrotation=35, which='both')
72
+ self._ax.tick_params(axis='both', which='both', labelsize=8)
73
+
74
+ self._pt_name = 'linear' if len(self._data.keys()) < 2 else "rel-linear"
75
+ self._dstart = self._data[self._ref_name].index[0]
76
+ self._mark = '-'
77
+ #self._dref = pd.Timestamp(mdates.num2date(0)).normalize().tz_localize(None)
78
+ self._dref = self._dstart
79
+ self._from_date = tk.StringVar(master=frm_plot_0)
80
+ self._to_date = tk.StringVar(master=frm_plot_0)
81
+ self._current_mouse_x = tk.StringVar(master=frm_plot_0, value="")
82
+ self._current_mouse_y = tk.DoubleVar(master=frm_plot_0, value=0)
83
+
84
+ self._scale = tk.Scale(frm_plot_0, orient=tk.HORIZONTAL, length=200,
85
+ showvalue=0, sliderlength=20,
86
+ command=self._scale_func)
87
+ self._scale.pack(fill=tk.X, expand=True, side=tk.TOP)
88
+
89
+ self._canvas = FigureCanvasTkAgg(figure=self._fig, master=self.frm_plot)
90
+ self._canvas.draw()
91
+ self._canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=True, pady=5, padx=5)
92
+ self._prep_fig()
93
+ self._CHcursor = CrossHairBCursor(self._ax,
94
+ varx = tk.StringVar(), vary=tk.StringVar(),
95
+ transformx=lambda x: mdates.num2date(x).strftime("%Y-%m-%d"),
96
+ transformy=lambda y: str(round(y,2))
97
+ )
98
+ self._fig.canvas.mpl_connect('motion_notify_event', self._CHcursor.on_mouse_move)
99
+ self._fig.canvas.mpl_connect('button_press_event', self._on_mouse_press)
100
+
101
+ self._canvas.draw()
102
+
103
+ # self.frm_btn - right side buttons etc
104
+ frm_reference = tk.LabelFrame(master=self.frm_btn, text="Reference")
105
+ frm_reference.pack(side=tk.TOP, fill=tk.X)
106
+
107
+ self._cbx_ref = tk.StringVar(master=frm_reference, value=self._ref_name)
108
+ if self._isMultiLines and not self._isEqualLines:
109
+ cbx_ref = ttk.Combobox(master=frm_reference, textvariable=self._cbx_ref,
110
+ width=WIDTH_CBX, state='readonly')
111
+ cbx_ref['values'] = tuple(self._data.keys())
112
+ cbx_ref.pack(pady=5, padx=5, side=tk.TOP,)
113
+ cbx_ref.bind("<<ComboboxSelected>>", self._cbx_ref_func)
114
+
115
+ lfrm_view = tk.LabelFrame(master=self.frm_btn, text='Set view')
116
+ lfrm_view.pack(side=tk.TOP, fill=tk.X)
117
+
118
+ self._cbx = tk.StringVar(master=lfrm_view, value=self._col_name)
119
+ if self._isMultiColumns:
120
+ cbx = ttk.Combobox(master=lfrm_view, textvariable=self._cbx,
121
+ width=WIDTH_CBX, state='readonly')
122
+ cbx['values'] = tuple(self._data[self._ref_name].columns)
123
+ cbx.pack(pady=5, padx=5, side=tk.TOP,)
124
+ cbx.bind("<<ComboboxSelected>>", self._cbx_func)
125
+
126
+ self._cbx_ptype = tk.StringVar(master=lfrm_view, value=self._pt_name)
127
+ cbx_ptype = ttk.Combobox(master=lfrm_view, textvariable=self._cbx_ptype,
128
+ width=WIDTH_CBX, state='readonly')
129
+ cbx_ptype['values'] = tuple(configPlot._plot_types.keys())
130
+ cbx_ptype.pack(pady=5, padx=5, side=tk.TOP,)
131
+ cbx_ptype.bind("<<ComboboxSelected>>", self._cbx_ptype_func)
132
+
133
+ self._cbx_length = tk.StringVar(master=lfrm_view, value='Max')
134
+ cbx_length = ttk.Combobox(master=lfrm_view, textvariable=self._cbx_length,
135
+ width=WIDTH_CBX, state='readonly')
136
+ cbx_length['values'] = tuple(configPlot._plot_lengths.keys())
137
+ cbx_length.pack(pady=5, padx=5, side=tk.TOP,)
138
+ cbx_length.bind("<<ComboboxSelected>>", self._cbx_length_func)
139
+
140
+ lfrm_reports = tk.LabelFrame(master=self.frm_btn, text='Reports')
141
+ lfrm_reports.pack(side=tk.TOP, fill=tk.X)
142
+
143
+ self._cbx_reports = tk.StringVar(master=lfrm_reports)
144
+ cbx_reports = ttk.Combobox(master=lfrm_reports, textvariable=self._cbx_reports,
145
+ width=WIDTH_CBX, state='readonly')
146
+ cbx_reports['values'] = self._report_names
147
+ cbx_reports.current(0)
148
+ cbx_reports.pack(pady=5, padx=5, side=tk.TOP,)
149
+ cbx_reports.bind("<<ComboboxSelected>>", self._cbx_reports_func)
150
+
151
+ # TOP - BOTTOM
152
+ btn = tk.Button(master=self.frm_btn, text="Exit", width=WIDTH_CBX, command=self._btn_quit_func)
153
+ btn.pack(pady=5, padx=5, side=tk.BOTTOM,)
154
+
155
+ frm_ts = tk.LabelFrame(master=self.frm_btn, text="In view")
156
+ frm_ts.pack(side=tk.BOTTOM, fill=tk.X)
157
+ tk.Label(master=frm_ts, text="From:", anchor=tk.W).pack(side=tk.TOP, anchor=tk.W)
158
+ tk.Label(master=frm_ts, textvariable=self._from_date).pack(side=tk.TOP)
159
+ tk.Label(master=frm_ts, text="To:", anchor=tk.W).pack(side=tk.TOP, anchor=tk.W)
160
+ tk.Label(master=frm_ts, textvariable=self._to_date).pack(side=tk.TOP)
161
+
162
+ frm_current = tk.LabelFrame(master=self.frm_btn, text="Cross")
163
+ frm_current.pack(side=tk.BOTTOM, fill=tk.X)
164
+ tk.Label(master=frm_current, textvariable=self._CHcursor.varx).pack(side=tk.TOP)
165
+ tk.Label(master=frm_current, textvariable=self._CHcursor.vary).pack(side=tk.TOP)
166
+
167
+ lfrm_save = tk.LabelFrame(master=self.frm_btn, text='Save')
168
+ lfrm_save.pack(side=tk.BOTTOM, fill=tk.X)
169
+
170
+ self._cbx_save = tk.StringVar(master=lfrm_save)
171
+ cbx_save = ttk.Combobox(master=lfrm_save, textvariable=self._cbx_save,
172
+ width=WIDTH_CBX, state='readonly')
173
+ cbx_save['values'] = ('Chart', 'To Excel') + tuple(self._data.keys())
174
+ cbx_save.current(0)
175
+ cbx_save.pack(pady=5, padx=5, side=tk.TOP)
176
+ cbx_save.bind("<<ComboboxSelected>>", self._cbx_save_func)
177
+
178
+
179
+ def _data_info(self):
180
+ self._isMultiLines = True if len(self._data.keys()) > 1 else False
181
+ self._isMultiColumns = True if len(self._data[self._ref_name].columns) > 1 else False
182
+ self._isEqualLines = True
183
+ if self._isMultiLines:
184
+ sds = self._data[self._ref_name].index[-1]
185
+ ede = self._data[self._ref_name].index[0]
186
+ for dd in self._data.values():
187
+ if (dd.index[-1] != sds) or (dd.index[0] != ede):
188
+ self._isEqualLines = False
189
+ return
190
+
191
+
192
+ def _prep_fig(self):
193
+ # calc TS
194
+ tref = self._data[self._ref_name].index
195
+ tref = tref[tref >= self._dstart]
196
+ ty = {}
197
+ for kk, vv in self._data.items():
198
+ ty[kk] = {}
199
+ t = vv.index[vv.index >= self._dref]
200
+ ty[kk]['t'] = t
201
+ ty[kk]['y'] =vv[self._col_name].loc[t[0]:]
202
+
203
+ # relative and percent plots
204
+ if configPlot._plot_types[self._pt_name]['relative']:
205
+ ty[self._ref_name]['y'] = ty[self._ref_name]['y'] / ty[self._ref_name]['y'].iloc[0]
206
+ for kk in ty.keys():
207
+ if kk == self._ref_name: continue
208
+ ty[kk]['y'] = ty[kk]['y'] * (ty[self._ref_name]['y'].loc[ty[kk]['t'][0]] / ty[kk]['y'].iloc[0])
209
+
210
+ # percent plots
211
+ if configPlot._plot_types[self._pt_name]['percent']:
212
+ for kk in ty.keys():
213
+ ty[kk]['y'] = (ty[kk]['y'] - 1) * 100
214
+
215
+ # set from - to for labels
216
+ self._from_date.set(ty[self._ref_name]['t'][0].strftime("%Y-%m-%d"))
217
+ self._to_date.set(ty[self._ref_name]['t'][-1].strftime("%Y-%m-%d"))
218
+
219
+ # clear and redraw
220
+ self._ax.clear()
221
+ for kk, vv in ty.items():
222
+ self._plot_lines_dict[kk], = self._ax.plot(vv['t'], vv['y'],
223
+ self._mark,
224
+ label=kk, lw=1,
225
+ visible=self._visible[kk])
226
+ # title choice
227
+ if self._ref_name_refresh:
228
+ if self._name is None:
229
+ self._ax.set_title(self._ref_name +' ('+ self._col_name +')')
230
+ else:
231
+ self._ax.set_title(self._name +' ('+ self._col_name +')')
232
+
233
+ # format axis
234
+ self._ax.set_yscale(**configPlot._plot_types[self._pt_name]['param'])
235
+ self._ax.grid(**configPlot._plot_types[self._pt_name]['xgrid'])
236
+ self._ax.grid(**configPlot._plot_types[self._pt_name]['ygrid'])
237
+
238
+ self._ax.xaxis.set_major_formatter(mdates.DateFormatter('%y-%m-%d'))
239
+ self._ax.xaxis.set_minor_locator(mdates.MonthLocator(interval=3))
240
+
241
+ # build legend - only first time when this function is called
242
+ if self._fig_legend_refresh:
243
+ self._leg = self._fig.legend(loc='upper left', fontsize = 6)
244
+ self._fig.canvas.mpl_connect('pick_event', self._on_pick)
245
+ for legl in self._leg.get_lines():
246
+ legl.set_picker(5)
247
+ self._leg.set_draggable(True)
248
+ self._fig_legend_refresh = False
249
+
250
+ # set resolution for scale widget
251
+ if self._scale_refresh:
252
+ lfrom = mdates.date2num(self._dstart)
253
+ self._scale.configure(from_=lfrom, to=mdates.date2num(tref[-2]))
254
+ self._scale.set(mdates.date2num(self._dref))
255
+ self._scale_refresh = False
256
+
257
+
258
+ def _on_pick(self, event):
259
+ legend_line = event.artist
260
+ name = legend_line.get_label()
261
+ if name not in self._plot_lines_dict.keys(): return
262
+
263
+ ax_line = self._plot_lines_dict[name]
264
+ visible = not ax_line.get_visible()
265
+ ax_line.set_visible(visible)
266
+ self._visible[name] = visible
267
+ legend_line.set_alpha(1.0 if visible else 0.2)
268
+ self._fig.canvas.draw()
269
+
270
+
271
+ def _on_mouse_press(self, event):
272
+ xc = event.xdata
273
+ if xc is None: return
274
+ dl = mdates.date2num(self._dstart)
275
+ dr = mdates.date2num(self._data[self._ref_name].index[-1])
276
+ xc = int(xc)
277
+ if (xc <= dl) or (xc >= dr): return
278
+ self._dref = pd.Timestamp(mdates.num2date(xc)).normalize().tz_localize(None)
279
+ self._scale.set(xc)
280
+ self._cbx_func(1)
281
+
282
+
283
+ def _scale_func(self, val):
284
+ ival = int(val)
285
+ self._dref = pd.Timestamp(mdates.num2date(ival)).normalize().tz_localize(None)
286
+ self._cbx_func(1)
287
+
288
+
289
+ def _cbx_func(self, event):
290
+ self._col_name = self._cbx.get()
291
+ self._prep_fig()
292
+ self._CHcursor.set_ax(self._ax)
293
+ self._canvas.draw()
294
+
295
+
296
+ def _cbx_ptype_func(self, event):
297
+ self._pt_name = self._cbx_ptype.get()
298
+ self._cbx_func(event)
299
+
300
+
301
+ def _cbx_length_func(self, event):
302
+ lt = self._cbx_length.get()
303
+ self._dstart = max(self._data[self._ref_name].index[0],
304
+ configPlot._plot_lengths[lt]['offset'](self._data[self._ref_name].index[-1]))
305
+ self._mark = configPlot._plot_lengths[lt]['mark']
306
+ self._scale_refresh = True
307
+ self._dref = max(self._dstart, self._dref) if event == 1 else self._dstart
308
+ self._cbx_func(event)
309
+
310
+
311
+ def _cbx_ref_func(self, event):
312
+ self._ref_name = self._cbx_ref.get()
313
+ self._ref_name_refresh = True
314
+ self._cbx_length_func(1)
315
+
316
+
317
+ def _btn_quit_func(self):
318
+ for vv in self._tops_ww.values():
319
+ if vv['ww'] is not None: vv['ww'].destroy()
320
+ if (self._master is not None) and self._master.winfo_exists():
321
+ self._master.focus_set()
322
+ self._window.destroy()
323
+
324
+
325
+ def _btn_save_chart_func(self):
326
+ filesextensions = [("PNG", "*.png"), ("JPG", "*.jpg"), ("PDF", "*.pdf")]
327
+ path = tk.filedialog.asksaveasfilename(
328
+ filetypes=filesextensions,
329
+ defaultextension = filesextensions,
330
+ initialdir=configSettings.MasterApplicationSettings["UserOutputDirectory"],
331
+ initialfile='Fig.png',
332
+ )
333
+ if path:
334
+ self._fig.savefig(path)
335
+
336
+
337
+ def _btn_save_excel_func(self):
338
+ port_path = configSettings.MasterApplicationSettings["UserOutputDirectory"]
339
+ path = tk.filedialog.asksaveasfilename(
340
+ defaultextension=".xlsx",
341
+ filetypes=[("Excel Files", "*.xlsx")],
342
+ initialdir=port_path,
343
+ initialfile='Data.xlsx',
344
+ parent=self._window
345
+ )
346
+ if path:
347
+ with pd.ExcelWriter(path, mode='w', engine='xlsxwriter',
348
+ date_format="YYYY-MM-DD",
349
+ datetime_format="YYYY-MM-DD") as writer:
350
+ for kk, vv in self._data.items():
351
+ vv.to_excel(writer, sheet_name=kk)
352
+ if configSettings.MasterApplicationSettings["OpenExcel"]:
353
+ try:
354
+ os.system('start EXCEL.EXE ' + path)
355
+ except:
356
+ pass
357
+
358
+
359
+ def _cbx_save_func(self, event):
360
+ dname = self._cbx_save.get()
361
+ if dname == 'Chart':
362
+ self._btn_save_chart_func()
363
+ return
364
+ if dname == 'To Excel':
365
+ self._btn_save_excel_func()
366
+ return
367
+
368
+ port_path = configSettings.MasterApplicationSettings["UserOutputDirectory"]
369
+ path = tk.filedialog.asksaveasfilename(
370
+ defaultextension=".csv",
371
+ filetypes=[("Excel Files", "*.csv")],
372
+ initialdir=port_path,
373
+ initialfile=dname + '.csv',
374
+ parent=self._window
375
+ )
376
+ if path:
377
+ self._data[dname].to_csv(path)
378
+
379
+
380
+ def _prep_reports(self):
381
+ obj = az.Port_Simple(self._data, col=self._col_name)
382
+ obj.set_model()
383
+ su = obj.port_perf(componly=True)
384
+ an = obj.port_annual_returns(withcomp=True, componly=True)
385
+ mo = obj.port_monthly_returns(withcomp=True, componly=True)
386
+ qu = obj.port_quarterly_returns(withcomp=True, componly=True)
387
+ dr = obj.port_drawdown(withcomp=True, componly=True)
388
+
389
+ # monthly
390
+ mo.index = pd.MultiIndex.from_arrays(
391
+ [mo.index.year.to_list(),
392
+ mo.index.month_name().to_list()],
393
+ names= ['year', 'month'])
394
+
395
+ mo = mo.stack().unstack('month')
396
+ mo.columns = pd.Series(mo.columns).apply(lambda x: x[:3])
397
+ mo.columns = pd.Categorical(mo.columns, categories=config._months_name, ordered=True)
398
+ mo.sort_index(axis=1, inplace=True)
399
+
400
+ # quarterly
401
+ qu.index = pd.MultiIndex.from_arrays(
402
+ [qu.index.year.to_list(),
403
+ ['Q' + str(k) for k in qu.index.quarter]],
404
+ names= ['year', 'quarter'])
405
+
406
+ qu = qu.stack().unstack('quarter')
407
+
408
+ # annual
409
+ an = an.stack()
410
+ an.name = 'Annual'
411
+
412
+ # summary
413
+ cols = ['RR', 'DD', 'RoMaD']
414
+ su[cols] = (su[cols] * 100).round(2)
415
+ rname = self._report_names[0] + self._col_name
416
+ self._tops_ww[rname] = {}
417
+ self._tops_ww[rname]['report'] = su
418
+ self._tops_ww[rname]['ww'] = None
419
+ self._tops_ww[rname]['geometry'] = '400x200'
420
+ self._tops_ww[rname]['title'] = f'Summary Rep. {self._col_name} prices'
421
+
422
+ # monthly
423
+ mo = (pd.concat([an, mo], join='outer', axis=1) * 100).round(2).fillna('')
424
+ rname = self._report_names[3] + self._col_name
425
+ self._tops_ww[rname] = {}
426
+ self._tops_ww[rname]['report'] = mo
427
+ self._tops_ww[rname]['ww'] = None
428
+ self._tops_ww[rname]['geometry'] = '600x300'
429
+ self._tops_ww[rname]['title'] = f'Monthly Rep. {self._col_name} prices'
430
+
431
+ # quarterly
432
+ qu = (pd.concat([an, qu], join='outer', axis=1) * 100).round(2).fillna('')
433
+ rname = self._report_names[2] + self._col_name
434
+ self._tops_ww[rname] = {}
435
+ self._tops_ww[rname]['report'] = qu
436
+ self._tops_ww[rname]['ww'] = None
437
+ self._tops_ww[rname]['geometry'] = '350x300'
438
+ self._tops_ww[rname]['title'] = f'Quarterly Rep. {self._col_name} prices'
439
+
440
+ # annual
441
+ an = (pd.DataFrame(an).unstack('symbol').droplevel(0, 1) * 100).round(2).fillna('')
442
+ an.columns.name = None
443
+ rname = self._report_names[1] + self._col_name
444
+ self._tops_ww[rname] = {}
445
+ self._tops_ww[rname]['report'] = an
446
+ self._tops_ww[rname]['ww'] = None
447
+ self._tops_ww[rname]['geometry'] = '300x300'
448
+ self._tops_ww[rname]['title'] = 'Annual Rep. ' + self._col_name + ' prices'
449
+
450
+ # drawdown
451
+ dr.DD = (dr.DD * 100).round(2)
452
+ dr.fillna('', inplace=True)
453
+ rname = self._report_names[4] + self._col_name
454
+ self._tops_ww[rname] = {}
455
+ self._tops_ww[rname]['report'] = dr
456
+ self._tops_ww[rname]['ww'] = None
457
+ self._tops_ww[rname]['geometry'] = '400x300'
458
+ self._tops_ww[rname]['title'] = 'Drawdown Rep. ' + self._col_name + ' prices'
459
+
460
+
461
+ def _cbx_reports_func(self, event):
462
+ rep_name = self._cbx_reports.get()
463
+ rname = rep_name + self._col_name
464
+
465
+ if rname not in self._tops_ww.keys(): self._prep_reports()
466
+ if ((self._tops_ww[rname]['ww'] is not None) and
467
+ self._tops_ww[rname]['ww'].winfo_exists()):
468
+ self._tops_ww[rname]['ww'].lift()
469
+ return
470
+
471
+ self._tops_ww[rname]['ww'] = DF_Window(self._window,
472
+ self._tops_ww[rname]['report'],
473
+ self._tops_ww[rname]['title'],
474
+ self._tops_ww[rname]['geometry'],
475
+ rname,
476
+ )
477
+
478
+ def _menu_help_func(self):
479
+ webbrowser.open_new_tab(configHelps._Statistics_panel_help)
480
+
azapyGUI/ViewTip.py ADDED
@@ -0,0 +1,72 @@
1
+ import tkinter as tk
2
+ from tkinter import ttk
3
+ from typing import Union
4
+
5
+ Widget = Union[tk.Widget, ttk.Widget]
6
+
7
+ class ViewTip(tk.Toplevel):
8
+ _FADE_INC:float = .07
9
+ _FADE_MS :int = 20
10
+
11
+ def __init__(self, master, **kwargs):
12
+ tk.Toplevel.__init__(self, master)
13
+
14
+ self.attributes('-alpha', 0, '-topmost', True)
15
+ self.overrideredirect(1)
16
+
17
+ style = dict(bd=2, relief='raised', font='Ariel 10', bg='#D4D4D4',
18
+ anchor='w', justify='left')
19
+ self.label = tk.Label(self, **{**style, **kwargs})
20
+ self.label.grid(row=0, column=0, sticky='w')
21
+
22
+ self.fout:bool = False
23
+
24
+
25
+ def bind(self, target:Widget, text:str, **kwargs):
26
+ target.bind('<Enter>', lambda e: self._fadein(0, text, e))
27
+ target.bind('<Leave>', lambda e: self._fadeout(1 - ViewTip._FADE_INC, e))
28
+
29
+
30
+ def _fadein(self, alpha:float, text:str=None, event:tk.Event=None):
31
+ if event and text:
32
+ if self.fout:
33
+ self.attributes('-alpha', 0)
34
+ self.fout = False
35
+ self.label.configure(text=f'{text:^{len(text)+2}}')
36
+ self.update()
37
+
38
+ offset_x = event.widget.winfo_width()+2
39
+ offset_y = int((event.widget.winfo_height() - self.label.winfo_height()) / 2)
40
+
41
+ w = self.label.winfo_width()
42
+ h = self.label.winfo_height()
43
+ x = event.widget.winfo_rootx()+offset_x
44
+ y = event.widget.winfo_rooty()+offset_y
45
+
46
+ self.geometry(f'{w}x{h}+{x}+{y}')
47
+
48
+ if not self.fout:
49
+ self.attributes('-alpha', alpha)
50
+
51
+ if alpha < 1:
52
+ self.after(ViewTip._FADE_MS,
53
+ lambda: self._fadein(min(alpha + ViewTip._FADE_INC, 1)))
54
+
55
+
56
+ def _fadeout(self, alpha:float, event:tk.Event=None):
57
+ if event:
58
+ self.fout = True
59
+
60
+ if self.fout:
61
+ self.attributes('-alpha', alpha)
62
+
63
+ if alpha > 0:
64
+ self.after(ViewTip._FADE_MS,
65
+ lambda: self._fadeout(max(alpha - ViewTip._FADE_INC, 0)))
66
+
67
+
68
+ def turned(self, on=True):
69
+ if on:
70
+ self.deiconify()
71
+ else:
72
+ self.withdraw()