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,179 @@
1
+ import tkinter as tk
2
+ from tkinter import ttk
3
+ import re
4
+ import pandas as pd
5
+
6
+ import azapyGUI.config as config
7
+ from azapyGUI.TimeSeriesViewWindow import TimeSeriesViewWindow, WIDTH_CBX
8
+ from azapyGUI.DF_Window import DF_Window
9
+
10
+
11
+ class PortAnalyseWindow(TimeSeriesViewWindow):
12
+ def __init__(self, master, ports):
13
+ self._ports = ports
14
+
15
+ self._rep_show = {}
16
+ data = {pname: self._ports[pname].get_port() for pname in self._ports.keys()}
17
+ ll = 0
18
+ for pname, vv in data.items():
19
+ if vv.shape[0] > ll:
20
+ ll = vv.shape[0]
21
+ ref_name = pname
22
+ col_name = data[ref_name].columns[0]
23
+ name = 'Performance'
24
+
25
+ super().__init__(master=master, name=name, data=data,
26
+ ref_name=ref_name, col_name=col_name)
27
+
28
+ frm_specrep = tk.LabelFrame(master=self.frm_btn, text='Specific')
29
+ frm_specrep.pack(side=tk.TOP, fill=tk.X)
30
+
31
+ self._cbx_sprot = ttk.Combobox(master=frm_specrep, width=WIDTH_CBX, state='readonly')
32
+ self._cbx_sprot.pack(pady=5, padx=5, side=tk.TOP,)
33
+ self._cbx_sprot['values'] = tuple(data.keys())
34
+ self._cbx_sprot.current(0)
35
+
36
+ self._specific_rep = {'Summary': self._spec_rep_summary,
37
+ 'Weights': self._spec_rep_weights,
38
+ 'Nr. Shares': self._spec_rep_nshares,
39
+ 'Annual': self._spec_rep_annula,
40
+ 'Quarterly': self._spec_rep_quarterly,
41
+ 'Monthly': self._spec_rep_monthly,
42
+ 'Reinv. Ret.': self._spec_rep_reinvret,
43
+ 'Drawdowns': self._spec_rep_drawdown,
44
+ 'Reinv. Perf.': self._spec_rep_reinvperf,
45
+ 'Account': self._spec_rep_account,
46
+ }
47
+ self._cbx_srep = ttk.Combobox(master=frm_specrep, width=WIDTH_CBX, state='readonly')
48
+ self._cbx_srep.pack(pady=5, padx=5, side=tk.TOP,)
49
+ self._cbx_srep['values'] = tuple(self._specific_rep.keys())
50
+ self._cbx_srep.set('Summary')
51
+ self._cbx_srep.bind("<<ComboboxSelected>>", self._cbx_srep_func)
52
+
53
+
54
+ def _spec_rep_summary(self, pname):
55
+ pp = self._ports[pname]
56
+ rep = pp.port_perf()
57
+ cols = ['RR', 'DD', 'RoMaD']
58
+ rep[cols] = (rep[cols] * 100).round(2)
59
+
60
+ return rep, f'{pname} Summary', '400x200'
61
+
62
+
63
+ def _spec_rep_weights(self, pname):
64
+ pp = self._ports[pname]
65
+ rep = pp.get_weights().set_index(['Droll', 'Dfix'])
66
+ rep = (rep * 100).round(2)
67
+
68
+ return rep, f'{pname} Weights', '400x400'
69
+
70
+
71
+ def _spec_rep_nshares(self, pname):
72
+ pp = self._ports[pname]
73
+ rep = pp.get_nshares()
74
+
75
+ return rep, f'{pname} Number of shares', '400x300'
76
+
77
+
78
+ def _spec_rep_annula(self, pname):
79
+ pp = self._ports[pname]
80
+ rep = pp.port_annual_returns(withcomp=True)
81
+ rep = (rep * 100).round(2)
82
+ rep.rename(columns={'close': pname}, inplace=True)
83
+
84
+ return rep, f'{pname} Annual Returns (portfolio and components)', '400x300'
85
+
86
+
87
+ def _spec_rep_quarterly(self, pname):
88
+ pp = self._ports[pname]
89
+ rep = pp.port_quarterly_returns(withcomp=True)
90
+ rep = (rep * 100).round(2)
91
+ rep.rename(columns={'close': pname}, inplace=True)
92
+ rep.columns.name = 'Symbols'
93
+
94
+ rep.index = pd.MultiIndex.from_arrays(
95
+ [rep.index.year.to_list(),
96
+ ['Q' + str(k) for k in rep.index.quarter]],
97
+ names= ['year', 'quarter'])
98
+
99
+ rep = rep.stack().unstack('quarter')
100
+
101
+ return rep, f'{pname} Quarterly Returns (portfolio and components)', '300x400'
102
+
103
+
104
+ def _spec_rep_monthly(self, pname):
105
+ pp = self._ports[pname]
106
+ rep = pp.port_monthly_returns(withcomp=True)
107
+ rep = (rep * 100).round(2)
108
+ rep.rename(columns={'close': pname}, inplace=True)
109
+ rep.columns.name = 'Symbols'
110
+
111
+ rep.index = pd.MultiIndex.from_arrays(
112
+ [rep.index.year.to_list(),
113
+ rep.index.month_name().to_list()],
114
+ names= ['year', 'month'])
115
+
116
+ rep = rep.stack().unstack('month')
117
+ rep.columns = pd.Series(rep.columns).apply(lambda x: x[:3])
118
+ rep.columns = pd.Categorical(rep.columns, categories=config._months_name, ordered=True)
119
+ rep.sort_index(axis=1, inplace=True)
120
+
121
+ return rep, f'{pname} Monthly Returns (portfolio and components)', '500x400'
122
+
123
+
124
+ def _spec_rep_reinvret(self, pname):
125
+ pp = self._ports[pname]
126
+ rep = pp.port_period_returns().set_index(['Droll', 'Dfix'])
127
+ rep = (rep * 100).round(2)
128
+
129
+ return rep, f'{pname} Reinvestment Returns', '400x400'
130
+
131
+
132
+ def _spec_rep_drawdown(self, pname):
133
+ pp = self._ports[pname]
134
+ rep = pp.port_drawdown(withcomp=True)
135
+ rep['DD'] = (rep['DD'] * 100).round(2)
136
+
137
+ return rep, f'{pname} Drawdown (portfolio and component)', '400x400'
138
+
139
+
140
+ def _spec_rep_reinvperf(self, pname):
141
+ pp = self._ports[pname]
142
+ rep = pp.port_period_perf().set_index('Droll')
143
+ rep.iloc[:, :4] = (rep.iloc[:, :4] * 100).round(2)
144
+
145
+ return rep, f'{pname} Performance per reinvestment', '520x400'
146
+
147
+
148
+ def _spec_rep_account(self, pname):
149
+ pp = self._ports[pname]
150
+ rep = pp.get_account()
151
+
152
+ rep.iloc[:,-3:] = rep.iloc[:, -3:].round(2)
153
+
154
+ return rep, f'{pname} Account Info.', '500x400'
155
+
156
+
157
+ def _cbx_srep_func(self, event):
158
+ pname = self._cbx_sprot.get()
159
+ srep = self._cbx_srep.get()
160
+
161
+ if pname not in self._rep_show.keys():
162
+ self._rep_show[pname] = {}
163
+ if ((srep in self._rep_show[pname].keys()) and
164
+ self._rep_show[pname][srep].winfo_exists()):
165
+ self._rep_show[pname][srep].lift()
166
+ return
167
+
168
+ df, title, geometry = self._specific_rep[srep](pname)
169
+ fname = pname + '_' + re.sub('[. ]', '', srep)
170
+
171
+ dfw = DF_Window(master=self._window, df=df, title=title, geometry=geometry, fname=fname)
172
+ self._rep_show[pname][srep] = dfw
173
+
174
+
175
+ def _btn_quit_func(self):
176
+ for pp in self._rep_show.values():
177
+ for vv in pp.values():
178
+ if vv.winfo_exists(): vv.destroy()
179
+ super()._btn_quit_func()
@@ -0,0 +1,180 @@
1
+ import json
2
+
3
+
4
+ class PortDataNode:
5
+ def __init__(self, name=None, symbols=None, selectors=None, optimizer=None,
6
+ status=None, saved=None, **rest):
7
+ self.name = name if name is not None else "MyPort"
8
+ self.symbols = symbols if symbols is not None else []
9
+ self.selectors = selectors if selectors is not None else {}
10
+ self.optimizer = optimizer if optimizer is not None else {}
11
+ self.status = status if status is not None else "New"
12
+ self.saved = saved if saved is not None else False
13
+ self._active_level = 0
14
+
15
+
16
+ def add_optimizer(self, name, param):
17
+ if len(self.optimizer.keys()) > 0: return False
18
+ self.optimizer[name] = {}
19
+ self.optimizer[name]['index'] = 0
20
+ self.optimizer[name]['name'] = name
21
+ self.optimizer[name]['param'] = param
22
+
23
+ return True
24
+
25
+
26
+ def remove_optimizer(self):
27
+ self.optimizer.clear()
28
+ return True
29
+
30
+
31
+ def get_optimizer(self):
32
+ ele = self.optimizer.keys()
33
+ if len(ele) == 0: return None
34
+ return self.optimizer[list(ele)[0]]
35
+
36
+
37
+ def clear_optimizer(self):
38
+ self.optimizer = {}
39
+
40
+
41
+ def update_optimizer(self, param):
42
+ self.optimizer[self.get_optimizer()['name']]['param'] = param
43
+ return True
44
+
45
+
46
+ def is_set_optimizer(self):
47
+ return (True if len(self.optimizer.keys()) > 0 else False)
48
+
49
+
50
+ def is_set_selector(self, name):
51
+ return (name in self.selectors.keys())
52
+
53
+
54
+ def number_selector(self):
55
+ return len(self.selectors.keys())
56
+
57
+
58
+ def add_selector(self, name, param):
59
+ if name in self.selectors.keys(): return False
60
+ self.selectors[name] = {}
61
+ self.selectors[name]['index'] = len(self.selectors.keys()) - 1
62
+ self.selectors[name]['name'] = name
63
+ self.selectors[name]['param'] = param
64
+ return True
65
+
66
+
67
+ def insert_selector(self, name, param, index):
68
+ kk_sel = self.selectors.keys()
69
+ if index >= len(kk_sel):
70
+ return self.append_selector(name, param)
71
+ for sel in kk_sel:
72
+ if self.selectors[sel]['index'] >= index:
73
+ self.selectors[sel]['index'] += 1
74
+ self.selectors[name] = {}
75
+ self.selectors[name]['param'] = param
76
+ self.selectors[name]['index'] = index if index >= 0 else 0
77
+ self.selectors[name]['name'] = name
78
+
79
+ return True
80
+
81
+
82
+ def get_selector(self, index_or_name):
83
+ if isinstance(index_or_name, str):
84
+ if index_or_name in self.selectors.keys():
85
+ return self.selectors[index_or_name]
86
+ elif isinstance(index_or_name, int):
87
+ for kk in self.selectors.keys():
88
+ if self.selectors[kk]['index'] == index_or_name:
89
+ return self.selectors[kk]
90
+ return None
91
+
92
+
93
+ def to_list_selector(self):
94
+ if len(self.selectors.keys()) == 0: return []
95
+ return sorted(list(self.selectors.values()), key=lambda x: x['index'])
96
+
97
+
98
+ def remove_selector(self, name):
99
+ ele = self.get_selector(name)
100
+ if ele is None: return False
101
+
102
+ for kk in self.selectors.keys():
103
+ if self.selectors[kk]['index'] > ele['index']:
104
+ self.selectors[kk]['index'] -= 1
105
+ del self.selectors[ele['name']]
106
+ return True
107
+
108
+
109
+ def moveUp_selector(self, name):
110
+ index = self.selectors[name]['index']
111
+ if index != 0:
112
+ nindex = index - 1
113
+ upname = self.get_selector(nindex)['name']
114
+ self.selectors[upname]['index'] = index
115
+ self.selectors[name]['index'] = nindex
116
+
117
+
118
+ def moveDown_selector(self, name):
119
+ index = self.selectors[name]['index']
120
+ if index != self.number_selector() - 1:
121
+ nindex = index + 1
122
+ upname = self.get_selector(nindex)['name']
123
+ self.selectors[upname]['index'] = index
124
+ self.selectors[name]['index'] = nindex
125
+
126
+
127
+ def update_selector(self, name, param):
128
+ if name in self.selectors.keys():
129
+ self.selectors[name]['param'] = param
130
+ return True
131
+ return False
132
+
133
+
134
+ def add_symbol(self, lsymb):
135
+ if isinstance(lsymb, str):
136
+ self.symbols += [lsymb]
137
+ self.symbols += lsymb
138
+
139
+
140
+ def clear_symbol(self):
141
+ self.symbols = []
142
+
143
+
144
+ def remove_symbol (self, symb):
145
+ self.symbols.remove(symb)
146
+
147
+
148
+ def set_symbol(self, symb):
149
+ self.clear_symbol()
150
+ self.add_symbol(symb)
151
+
152
+
153
+ def number_symmbols(self):
154
+ return len(self.symbols)
155
+
156
+
157
+ def writeFile(self, file_name):
158
+ fname = file_name
159
+ with open(fname, "w") as write_file:
160
+ json.dump(self.__dict__, write_file)
161
+
162
+
163
+ def loadFile(self, file_name):
164
+ fname = file_name
165
+ with open(fname, "r") as read_file:
166
+ data = json.load(read_file)
167
+ self.__init__(**data)
168
+ return self
169
+
170
+
171
+ def setActive(self, value=True):
172
+ if value:
173
+ self.status = 'Active'
174
+ self._active_level += 1
175
+ else:
176
+ if self._active_level > 1:
177
+ self._active_level -= 1
178
+ return
179
+ self._active_level = 0
180
+ self.status = 'Set'
@@ -0,0 +1,197 @@
1
+ import tkinter as tk
2
+ from tkinter import ttk
3
+
4
+ import azapyGUI.config as config
5
+ import azapyGUI.configSettings as configSettings
6
+ from azapyGUI.EntryRenamePortfolioWindow import EntryRenamePortfolioWindow
7
+ from azapyGUI.EntryClonePortfolioWindow import EntryClonePortfolioWindow
8
+ from azapyGUI.EditPortfolioWindow import EditPortfolioWindow
9
+ from azapyGUI.PortDataNode import PortDataNode
10
+ from azapyGUI.BacktestEntryWindow import BacktestEntryWindow
11
+ from azapyGUI.WeightsWindow import WeightsWindow
12
+
13
+
14
+ class PortfolioFrame(tk.LabelFrame):
15
+ def __init__(self, master, **kwargs):
16
+ self._master = master
17
+ super().__init__(master=self._master, **kwargs)
18
+
19
+ self.grid_rowconfigure(0, weight=1)
20
+ self.grid_columnconfigure(0, weight=1)
21
+ self.grid_columnconfigure(1, weight=0)
22
+
23
+ self._trv = ttk.Treeview(master=self, columns=("#1", "#2"))
24
+ self._trv.grid(row=0, column=0, sticky=tk.NSEW)
25
+
26
+ self._trv.heading("#0", text="Portfolio", command=self._load_port)
27
+ self._trv.heading("#1", text="Status")
28
+ self._trv.heading("#2", text="Saved")
29
+
30
+ self._trv.column("#0", minwidth=10, width=120, stretch=True, anchor=tk.W)
31
+ self._trv.column("#1", minwidth=10, width=60, stretch=False, anchor=tk.CENTER)
32
+ self._trv.column("#2", minwidth=10, width=60, stretch=False, anchor=tk.CENTER)
33
+
34
+ vscrlb = ttk.Scrollbar(master=self,
35
+ orient ="vertical",
36
+ command = self._trv.yview)
37
+ vscrlb.grid(row=0, column=1, sticky=tk.NS)
38
+ self._trv.configure(yscrollcommand = vscrlb.set)
39
+ self._trv.tag_bind("trv_tag", "<<TreeviewSelect>>", self._item_select)
40
+
41
+ self.refresh()
42
+ self._set_menu()
43
+
44
+
45
+ def _set_menu(self):
46
+ self._mmenu = tk.Menu(self._trv, tearoff = 0)
47
+ self._mmenu.add_command(label ="Edit", command=self._edit_port)
48
+ self._mmenu.add_command(label="Backtest", command=self._backtest_port)
49
+ self._mmenu.add_command(label="Rebalance", command=self._rebalance_port)
50
+ self._mmenu.add_command(label ="Clone", command=self._clone_port)
51
+ self._mmenu.add_command(label ="Rename", command=self._rename_port)
52
+ self._mmenu.add_command(label ="Save", command=self._save_port)
53
+ self._mmenu.add_separator()
54
+ self._mmenu.add_command(label ="Remove", command=self._delete_port)
55
+
56
+
57
+ def refresh(self):
58
+ for item in self._trv.get_children():
59
+ self._trv.delete(item)
60
+
61
+ for kk, val in config.PortDataDict.items():
62
+ self._trv.insert(
63
+ "",
64
+ tk.END,
65
+ text=val.name,
66
+ values=(val.status, val.saved),
67
+ tags=("trv_tag",),
68
+ )
69
+
70
+
71
+ def _item_select(self, _event: tk.Event):
72
+ iids = self._trv.selection()
73
+ self._port_selected = [self._trv.item(iid,'text') for iid in iids]
74
+
75
+ try:
76
+ self._mmenu.tk_popup(self._trv.winfo_pointerx(), self._trv.winfo_pointery())
77
+ finally:
78
+ self._mmenu.grab_release()
79
+
80
+
81
+ def _save_port(self):
82
+ port_path = configSettings.MasterApplicationSettings["UserPortfolioDirectory"]
83
+ for tport in self._port_selected:
84
+ if config.PortDataDict[tport].saved: continue
85
+ filepath = tk.filedialog.asksaveasfilename(
86
+ defaultextension=".json",
87
+ filetypes=[("Json Files", "*.json")],
88
+ initialdir=port_path,
89
+ initialfile=tport,
90
+ )
91
+ if filepath:
92
+ config.PortDataDict[tport].saved = True
93
+ config.PortDataDict[tport].writeFile(filepath)
94
+ self.refresh()
95
+
96
+
97
+ def _rename_port(self):
98
+ tport = self._port_selected[0]
99
+ if config.PortDataDict[tport].status != 'Set': return
100
+ EntryRenamePortfolioWindow(master=self._master, pname=tport)
101
+
102
+
103
+ def _clone_port(self):
104
+ tport = self._port_selected[0]
105
+ EntryClonePortfolioWindow(master=self._master, pname=tport)
106
+
107
+
108
+ def _delete_port(self):
109
+ for tport in self._port_selected:
110
+ if config.PortDataDict[tport].status != 'Set':
111
+ msg = f"Portfolio {tport} cannot be removed at this time"
112
+ tk.messagebox.showwarning("Warning", message=msg, parent=self._master)
113
+ continue
114
+ if not config.PortDataDict[tport].saved:
115
+ filepath = tk.filedialog.asksaveasfilename(
116
+ defaultextension=".json",
117
+ filetypes=[("Json Files", "*.json")],
118
+ initialfile=tport,
119
+ parent=self._master
120
+ )
121
+ if filepath:
122
+ config.PortDataDict[tport].saved = True
123
+ config.PortDataDict[tport].writeFile(filepath)
124
+ del config.PortDataDict[tport]
125
+ self.refresh()
126
+
127
+
128
+ def _edit_port(self):
129
+ pname = self._port_selected[0]
130
+ if config.PortDataDict[pname].status != 'Set': return
131
+ EditPortfolioWindow(master=self._master, portfolio=config.PortDataDict[pname])
132
+
133
+
134
+ def _load_port(self):
135
+ port_path = configSettings.MasterApplicationSettings["UserPortfolioDirectory"]
136
+ filepaths = tk.filedialog.askopenfilename(
137
+ filetypes=[("Json Files", "*.json")],
138
+ initialdir=port_path,
139
+ title='Portfolio loading',
140
+ multiple=True,
141
+ parent=self._master,
142
+ )
143
+ for filepath in list(filepaths):
144
+ sport = PortDataNode().loadFile(filepath)
145
+ if sport.name in config.PortDataDict.keys():
146
+ tk.messagebox.showwarning(
147
+ "Warning",
148
+ f"Name {sport.name} already in use!\nAbort opening.",
149
+ parent=self._master)
150
+ continue
151
+ sport.status = 'Set'
152
+ config.PortDataDict[sport.name] = sport
153
+ config.appPortfolioFrame.refresh()
154
+
155
+
156
+ def _backtest_port(self):
157
+ tport = self._port_selected
158
+ lport = [pp for pp in tport if config.PortDataDict[pp].status != 'Edit']
159
+ port_error = set(tport) - set(lport)
160
+
161
+ if len(port_error) > 0:
162
+ msg = (f"The following portfolios {port_error}\n"
163
+ "are being edited. They cannot be included in the\n"
164
+ "computations at this time."
165
+ )
166
+ if len(lport) == 0:
167
+ msg += "Abort backtesting!"
168
+ tk.messagebox.showwarning("Warning", message=msg, parent=self._master)
169
+ return
170
+ else:
171
+ msg += ("Do you want to continue with the backtesting for\n"
172
+ f"{lport}"
173
+ )
174
+ ask = tk.messagebox.askyesno("Warning", message=msg, parent=self._master)
175
+ if ask == 'no':
176
+ return
177
+
178
+ for pname in lport:
179
+ config.PortDataDict[pname].setActive(True)
180
+ self.refresh()
181
+
182
+ BacktestEntryWindow(master=self._master, pnames=lport)
183
+
184
+
185
+ def _rebalance_port(self):
186
+ pname = self._port_selected[0]
187
+ if config.PortDataDict[pname].status == 'Edit':
188
+ msg = ("This portfolio is being edited. \n"
189
+ "It cannot be evaluated at this time."
190
+ )
191
+ tk.messagebox.showwarning('Warning', message=msg, parent=self._master)
192
+ return
193
+
194
+ config.PortDataDict[pname].setActive(True)
195
+ self.refresh()
196
+ WeightsWindow(master=self._master, pname=pname)
197
+
@@ -0,0 +1,21 @@
1
+ import azapyGUI.config as config
2
+ from azapyGUI.SelectOneWindow import SelectOneWindow
3
+
4
+ class RebalanceMenuPortfolioWindow(SelectOneWindow):
5
+ def __init__(self, master=None):
6
+ values = [kk for kk, vv in config.PortDataDict.items() if vv.status != 'Edit']
7
+ if len(values) == 0:
8
+ self.selection = None
9
+ return
10
+ title = "Rebalance Portfolio"
11
+ text = "Choose portfolio"
12
+ tip_text = "Only portfolios with status=Set or Active can be computed."
13
+ btn_text = "Rebalance"
14
+ super().__init__(master=master, title=title, text=text, values=values,
15
+ tip_text=tip_text, btn_text=btn_text)
16
+
17
+
18
+ def _btn_action(self):
19
+ self.selection = self._ent.get()
20
+ self._btn_cancel()
21
+
@@ -0,0 +1,33 @@
1
+ import tkinter as tk
2
+
3
+ import azapyGUI.config as config
4
+ from azapyGUI.SelectOneWindow import SelectOneWindow
5
+
6
+
7
+ class RemoveMenuPortfolioWindow(SelectOneWindow):
8
+ def __init__(self, master=None):
9
+ values = [kk for kk, vv in config.PortDataDict.items() if vv.status == 'Set']
10
+ title = "Remove Portfolio"
11
+ text = "Choose portfolio to remove"
12
+ tip_text = "Only portfolios with status=Set can be removed."
13
+ btn_text = "Remove"
14
+ super().__init__(master=master, title=title, text=text, values=values,
15
+ tip_text=tip_text, btn_text=btn_text)
16
+
17
+
18
+ def _btn_action(self):
19
+ port_name = self._ent.get()
20
+ if not config.PortDataDict[port_name].saved:
21
+ filepath = tk.filedialog.asksaveasfilename(
22
+ defaultextension=".json",
23
+ filetypes=[("Json Files", "*.json")],
24
+ initialfile=port_name,
25
+ )
26
+ if filepath:
27
+ config.PortDataDict[port_name].saved = True
28
+ config.PortDataDict[port_name].writeFile(filepath)
29
+ if config.PortDataDict[port_name].status == 'Set':
30
+ del config.PortDataDict[port_name]
31
+ self._btn_cancel()
32
+ config.appPortfolioFrame.refresh()
33
+
@@ -0,0 +1,36 @@
1
+ import tkinter as tk
2
+
3
+ import azapyGUI.config as config
4
+ import azapyGUI.configSettings as configSettings
5
+ from azapyGUI.SelectOneWindow import SelectOneWindow
6
+
7
+
8
+ class SaveMenuPortfolioWindow(SelectOneWindow):
9
+ def __init__(self, master=None):
10
+ values = [kk for kk, vv in config.PortDataDict.items() if not vv.saved]
11
+ if len(values) == 0: return
12
+
13
+ title = 'Save Portfolio'
14
+ text = 'Choose portfolio to save'
15
+ tip_text = 'Only unsaved portfolios are listed.'
16
+ btn_text = 'Save'
17
+ super().__init__(master=master, title=title, text=text, values=values,
18
+ tip_text=tip_text, btn_text=btn_text)
19
+
20
+
21
+ def _btn_action(self):
22
+ port = self._ent.get()
23
+ port_path = configSettings.MasterApplicationSettings["UserPortfolioDirectory"]
24
+ filepath = tk.filedialog.asksaveasfilename(
25
+ defaultextension=".json",
26
+ filetypes=[("Json Files", "*.json")],
27
+ initialdir=port_path,
28
+ initialfile=port,
29
+ parent=self._window
30
+ )
31
+ if not filepath: return
32
+
33
+ config.PortDataDict[port].saved = True
34
+ config.PortDataDict[port].writeFile(filepath)
35
+ config.appPortfolioFrame.refresh()
36
+ self._btn_cancel()