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.
- azapyGUI/AppSettingsPage.py +213 -0
- azapyGUI/AppSettingsPageMisc.py +101 -0
- azapyGUI/AppSettingsWindow.py +52 -0
- azapyGUI/BacktestComputation.py +166 -0
- azapyGUI/BacktestEntryWindow.py +307 -0
- azapyGUI/BacktestMenuPortfolioWindow.py +20 -0
- azapyGUI/CloneMenuPortfolioWindow.py +93 -0
- azapyGUI/CrossHairBCursor.py +80 -0
- azapyGUI/DF_Window.py +63 -0
- azapyGUI/DF_table.py +282 -0
- azapyGUI/EditMenuPortfolioWindow.py +16 -0
- azapyGUI/EditPortfolioWindow.py +475 -0
- azapyGUI/EntryClonePortfolioWindow.py +35 -0
- azapyGUI/EntryNameWindow.py +55 -0
- azapyGUI/EntryRenamePortfolioWindow.py +33 -0
- azapyGUI/GetMktData.py +85 -0
- azapyGUI/MenuApp.py +194 -0
- azapyGUI/MktDataFrame.py +129 -0
- azapyGUI/MktDataNode.py +34 -0
- azapyGUI/ModelParamEditWindow.py +143 -0
- azapyGUI/NrShares_table.py +54 -0
- azapyGUI/PortAnalyseWindow.py +179 -0
- azapyGUI/PortDataNode.py +180 -0
- azapyGUI/PortfolioFrame.py +197 -0
- azapyGUI/RebalanceMenuPortfolioWindow.py +21 -0
- azapyGUI/RemoveMenuPortfolioWindow.py +33 -0
- azapyGUI/SaveMenuPortfolioWindow.py +36 -0
- azapyGUI/Scrollable.py +60 -0
- azapyGUI/SelectOneWindow.py +65 -0
- azapyGUI/SymbAnalyseWindow.py +21 -0
- azapyGUI/SymbExtractWindow.py +129 -0
- azapyGUI/SymbTableEntry.py +109 -0
- azapyGUI/TimeSeriesViewWindow.py +480 -0
- azapyGUI/ViewTip.py +72 -0
- azapyGUI/WeightsWindow.py +352 -0
- azapyGUI/__init__.py +6 -0
- azapyGUI/azHelper.py +27 -0
- azapyGUI/azapyApp.py +89 -0
- azapyGUI/config.py +35 -0
- azapyGUI/configHelps.py +84 -0
- azapyGUI/configMSG.py +194 -0
- azapyGUI/configModels.py +519 -0
- azapyGUI/configPlot.py +70 -0
- azapyGUI/configSettings.py +138 -0
- azapyGUI/configTips.py +240 -0
- azapyGUI/mktDataValidation.py +42 -0
- azapyGUI/modelParametersValidation.py +442 -0
- azapyGUI/serviceMasterUserConfig.py +28 -0
- azapyGUI/tkHelper.py +18 -0
- azapyGUI-0.0.1.dist-info/LICENSE +674 -0
- azapyGUI-0.0.1.dist-info/METADATA +126 -0
- azapyGUI-0.0.1.dist-info/RECORD +54 -0
- azapyGUI-0.0.1.dist-info/WHEEL +5 -0
- azapyGUI-0.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
import tkinter as tk
|
|
2
|
+
from tkinter import ttk
|
|
3
|
+
from copy import deepcopy
|
|
4
|
+
import webbrowser
|
|
5
|
+
|
|
6
|
+
import azapyGUI.config as config
|
|
7
|
+
import azapyGUI.configMSG as configMSG
|
|
8
|
+
import azapyGUI.configModels as configModels
|
|
9
|
+
import azapyGUI.configSettings as configSettings
|
|
10
|
+
import azapyGUI.configTips as configTips
|
|
11
|
+
import azapyGUI.configHelps as configHelps
|
|
12
|
+
import azapyGUI.modelParametersValidation as mpv
|
|
13
|
+
from azapyGUI.SymbTableEntry import SymbTableEntry
|
|
14
|
+
from azapyGUI.ModelParamEditWindow import ModelParamEditWindow
|
|
15
|
+
from azapyGUI.PortDataNode import PortDataNode
|
|
16
|
+
from azapyGUI.SymbExtractWindow import SymbExtractWindow
|
|
17
|
+
from azapyGUI.mktDataValidation import sdate_edate_validate
|
|
18
|
+
from azapyGUI.GetMktData import GetMktData
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class EditPortfolioWindow:
|
|
22
|
+
def __init__(self, master=None, portfolio=None):
|
|
23
|
+
self._master = master
|
|
24
|
+
if portfolio is not None:
|
|
25
|
+
portfolio.status = 'Edit'
|
|
26
|
+
config.appPortfolioFrame.refresh()
|
|
27
|
+
self._initial_portfolio_name = None if portfolio is None else portfolio.name
|
|
28
|
+
self.portfolio = deepcopy(portfolio) if portfolio is not None else PortDataNode()
|
|
29
|
+
|
|
30
|
+
self._window = tk.Toplevel()
|
|
31
|
+
self._window.geometry("800x400")
|
|
32
|
+
self._window.focus_set()
|
|
33
|
+
self._window.title("Portfolio Edit")
|
|
34
|
+
self._window.protocol("WM_DELETE_WINDOW", self._btn_cancel)
|
|
35
|
+
|
|
36
|
+
menubar = tk.Menu(self._window)
|
|
37
|
+
filemenu = tk.Menu(menubar, tearoff=0)
|
|
38
|
+
filemenu.add_command(label='Help', command=self._menu_help_func)
|
|
39
|
+
menubar.add_cascade(label='Help', menu=filemenu)
|
|
40
|
+
self._window.config(menu=menubar)
|
|
41
|
+
|
|
42
|
+
self._build()
|
|
43
|
+
|
|
44
|
+
self._window.update()
|
|
45
|
+
self._master.wait_window(self._window)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _build(self):
|
|
49
|
+
frm_name = tk.Frame(master=self._window)
|
|
50
|
+
frm_name.grid(row=0, column=0, padx=5, pady=5, sticky=tk.W)
|
|
51
|
+
|
|
52
|
+
lbl_text = "Edit Portfolio: "
|
|
53
|
+
lbl_port = tk.Label(master=frm_name, text=lbl_text, justify='left', font=("Harlow Solid Italic", 12))
|
|
54
|
+
lbl_port.pack(side='left', pady=5, padx=2)
|
|
55
|
+
self._ent_name = tk.Entry(master=frm_name, width=30)
|
|
56
|
+
self._ent_name.pack(side='right', pady=5, padx=2)
|
|
57
|
+
self._ent_name.insert(0, self.portfolio.name)
|
|
58
|
+
config.tiptil.bind(self._ent_name, configTips._epw_portfolio_name_tip)
|
|
59
|
+
|
|
60
|
+
self._build_symbPanel()
|
|
61
|
+
self._build_modelPanel()
|
|
62
|
+
|
|
63
|
+
frm_btn_done = tk.Frame(master=self._window)
|
|
64
|
+
frm_btn_done.grid(row=2, columnspan=2, padx=5, pady=5, sticky=tk.EW)
|
|
65
|
+
|
|
66
|
+
btn_set = tk.Button(master=frm_btn_done, text=" Set ", width=10, command=self._btn_set)
|
|
67
|
+
btn_set.pack(side=tk.RIGHT, padx=10, pady=5)
|
|
68
|
+
btn_cancel = tk.Button(master=frm_btn_done, text="Cancel", width=10, command=self._btn_cancel)
|
|
69
|
+
btn_cancel.pack(side=tk.LEFT, padx=10, pady=5)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _build_symbPanel(self):
|
|
73
|
+
# -- symb panel
|
|
74
|
+
symbPanel = tk.LabelFrame(master=self._window, width=300, text="Portfolio components", font=("Forte", 10))
|
|
75
|
+
symbPanel.grid(row=1, column=0, padx=5, pady=5, sticky=tk.NSEW)
|
|
76
|
+
|
|
77
|
+
self._window.rowconfigure(1, weight=1)
|
|
78
|
+
self._window.columnconfigure(0, weight=1)
|
|
79
|
+
|
|
80
|
+
frm_symb_btn = tk.Frame(master=symbPanel)
|
|
81
|
+
frm_symb_btn.grid(row=0, column=0, sticky=tk.EW)
|
|
82
|
+
|
|
83
|
+
btn_refresh = tk.Button(master=frm_symb_btn, text="Refresh", width=8, command=self._btn_refresh)
|
|
84
|
+
btn_refresh.pack(side=tk.LEFT, padx=2, pady=5)
|
|
85
|
+
|
|
86
|
+
btn_validate = tk.Button(master=frm_symb_btn, text="Validate", width=8, command=self._btn_symb_validate)
|
|
87
|
+
btn_validate.pack(side=tk.RIGHT, padx=2, pady=5)
|
|
88
|
+
|
|
89
|
+
self._symb_table = SymbTableEntry(master=symbPanel, )
|
|
90
|
+
self._symb_table.grid(row=1, column=0, sticky=tk.NSEW)
|
|
91
|
+
symbPanel.rowconfigure(1, weight=1)
|
|
92
|
+
symbPanel.columnconfigure(0, weight=1)
|
|
93
|
+
self._symb_table.write_order(self.portfolio.symbols)
|
|
94
|
+
|
|
95
|
+
self._window.bind('<KeyPress>', self._symb_table.key_press)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _build_modelPanel(self):
|
|
99
|
+
# -- model panel
|
|
100
|
+
modelPanel = tk.LabelFrame(master=self._window, text="Optimization model", width=400, font=("Forte", 10))
|
|
101
|
+
modelPanel.grid(row=1, column=1, padx=5, pady=5, sticky=tk.NSEW)
|
|
102
|
+
self._window.columnconfigure(1, weight=1)
|
|
103
|
+
|
|
104
|
+
frm_choice = tk.Frame(master=modelPanel)
|
|
105
|
+
frm_choice.pack(side='left', pady=2, expand=True, fill=tk.Y)
|
|
106
|
+
|
|
107
|
+
tk.Label(master=frm_choice, text="Selector:").pack(padx=2, pady=2, side='top', anchor="w")
|
|
108
|
+
self._cbx_selector = ttk.Combobox(master=frm_choice, width=20, state='readonly')
|
|
109
|
+
self._cbx_selector['values'] = tuple(configModels.selector_models.keys())
|
|
110
|
+
self._cbx_selector.current(0)
|
|
111
|
+
self._cbx_selector.pack(pady=5, padx=5, side='top',)
|
|
112
|
+
self._cbx_selector.bind("<<ComboboxSelected>>", self._cbx_selector_func)
|
|
113
|
+
|
|
114
|
+
tk.Label(master=frm_choice, text="Optimization type:").pack(padx=2, pady=2, side='top', anchor="w")
|
|
115
|
+
self._cbx_optim_type = ttk.Combobox(master=frm_choice, width=20, state='readonly')
|
|
116
|
+
self._cbx_optim_type['values'] = tuple(configModels.optim_model_types)
|
|
117
|
+
self._cbx_optim_type.current(0)
|
|
118
|
+
self._cbx_optim_type.pack(pady=5, padx=5, side='top',)
|
|
119
|
+
self._cbx_optim_type.bind("<<ComboboxSelected>>", self._cbx_optim_type_func)
|
|
120
|
+
|
|
121
|
+
tk.Label(master=frm_choice, text="Optimization model:").pack(padx=2, pady=2, side='top', anchor="w")
|
|
122
|
+
self._cbx_optim_model = ttk.Combobox(master=frm_choice, width=20, state='readonly')
|
|
123
|
+
self._cbx_optim_model['values'] = tuple(configModels.risk_based_models.keys())
|
|
124
|
+
self._cbx_optim_model.current(0)
|
|
125
|
+
self._cbx_optim_model.pack(pady=5, padx=5, side='top',)
|
|
126
|
+
self._cbx_optim_model.bind("<<ComboboxSelected>>", self._cbx_optim_model_func)
|
|
127
|
+
|
|
128
|
+
frm_view = tk.Frame(master=modelPanel, width=150)
|
|
129
|
+
frm_view.pack(side='right', expand=True, pady=2, fill=tk.BOTH)
|
|
130
|
+
|
|
131
|
+
self._tree_model = ttk.Treeview(master=frm_view, selectmode="browse", show="tree")
|
|
132
|
+
self._tree_model.pack(padx=0, pady=0, expand=True, side=tk.LEFT, fill=tk.BOTH)
|
|
133
|
+
self._tree_model.tag_bind("m_selector", "<<TreeviewSelect>>", self._tree_model_item_selector_func)
|
|
134
|
+
self._tree_model.tag_bind("m_optimizer", "<<TreeviewSelect>>", self._tree_model_item_optimizer_func)
|
|
135
|
+
|
|
136
|
+
vscrlb = ttk.Scrollbar(master=frm_view,
|
|
137
|
+
orient ="vertical",
|
|
138
|
+
command = self._tree_model.yview)
|
|
139
|
+
vscrlb.pack(padx=0, pady=2, side=tk.RIGHT, fill=tk.Y)
|
|
140
|
+
self._tree_model.configure(yscrollcommand = vscrlb.set)
|
|
141
|
+
self._tree_model_write()
|
|
142
|
+
|
|
143
|
+
self._set_tree_menu_selector()
|
|
144
|
+
self._set_tree_menu_optimizer()
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _tree_model_write(self):
|
|
148
|
+
for name, selector in self.portfolio.selectors.items():
|
|
149
|
+
item = self._tree_model.insert("", selector['index'], text=name, tags=("m_selector",))
|
|
150
|
+
for kk, vv in selector['param'].items():
|
|
151
|
+
self._tree_model.insert(item, tk.END, text=kk +" =" + str(vv))
|
|
152
|
+
|
|
153
|
+
for name, optimizer in self.portfolio.optimizer.items():
|
|
154
|
+
item = self._tree_model.insert("", tk.END, text=name, tags=("m_optimizer",))
|
|
155
|
+
for kk, vv in optimizer['param'].items():
|
|
156
|
+
self._tree_model.insert(item, tk.END, text=kk + " = " + str(vv))
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _set_tree_menu_selector(self):
|
|
160
|
+
self._tree_menu_selector = tk.Menu(self._tree_model, tearoff = 0)
|
|
161
|
+
self._tree_menu_selector.add_command(label ="Edit", command=self._tree_menu_selector_edit_func)
|
|
162
|
+
self._tree_menu_selector.add_command(label ="Move Up", command=self._tree_menu_selector_moveup_func)
|
|
163
|
+
self._tree_menu_selector.add_command(label ="Move Down", command=self._tree_menu_selector_movedown_func)
|
|
164
|
+
self._tree_menu_selector.add_separator()
|
|
165
|
+
self._tree_menu_selector.add_command(label ="Delete", command=self._tree_menu_selector_delete_func)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _tree_menu_selector_edit_func(self):
|
|
169
|
+
iid = self._tree_model.selection()[0]
|
|
170
|
+
model = self._tree_model.item(iid)['text']
|
|
171
|
+
|
|
172
|
+
selw = ModelParamEditWindow(model_name=model,
|
|
173
|
+
param=self.portfolio.get_selector(model)['param'],
|
|
174
|
+
master=self._window)
|
|
175
|
+
param = selw.param
|
|
176
|
+
if param is None: return
|
|
177
|
+
|
|
178
|
+
self.portfolio.update_selector(model, param)
|
|
179
|
+
|
|
180
|
+
for child in self._tree_model.get_children(iid):
|
|
181
|
+
self._tree_model.delete(child)
|
|
182
|
+
|
|
183
|
+
invisible = configModels.param_default(model, visible=False)
|
|
184
|
+
for kk, vv in param.items():
|
|
185
|
+
if kk in invisible: continue
|
|
186
|
+
self._tree_model.insert(iid, tk.END, text=kk +" = "+ str(vv))
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _tree_menu_selector_moveup_func(self):
|
|
190
|
+
item = self._tree_model.selection()[0]
|
|
191
|
+
loc = self._tree_model.index(item)
|
|
192
|
+
item_name = self._tree_model.item(item)['text']
|
|
193
|
+
|
|
194
|
+
self.portfolio.moveUp_selector(item_name)
|
|
195
|
+
self._tree_model.move(item, self._tree_model.parent(item), loc - 1)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def _tree_menu_selector_movedown_func(self):
|
|
199
|
+
item = self._tree_model.selection()[0]
|
|
200
|
+
loc = self._tree_model.index(item)
|
|
201
|
+
new_loc = loc + 1
|
|
202
|
+
|
|
203
|
+
if new_loc >= self.portfolio.number_selector(): return
|
|
204
|
+
item_name = self._tree_model.item(item)['text']
|
|
205
|
+
self.portfolio.moveDown_selector(item_name)
|
|
206
|
+
self._tree_model.move(item, self._tree_model.parent(item), new_loc)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def _tree_menu_selector_delete_func(self):
|
|
210
|
+
item = self._tree_model.selection()[0]
|
|
211
|
+
item_name = self._tree_model.item(item)['text']
|
|
212
|
+
self.portfolio.remove_selector(item_name)
|
|
213
|
+
self._tree_model.delete(item)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _set_tree_menu_optimizer(self):
|
|
217
|
+
self._tree_menu_optimizer = tk.Menu(self._tree_model, tearoff = 0)
|
|
218
|
+
self._tree_menu_optimizer.add_command(label ="Edit", command=self._tree_menu_optimizer_edit_func)
|
|
219
|
+
self._tree_menu_optimizer.add_separator()
|
|
220
|
+
self._tree_menu_optimizer.add_command(label ="Delete", command=self._tree_menu_optimizer_delete_func)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def _tree_menu_optimizer_edit_func(self):
|
|
224
|
+
iid = self._tree_model.selection()[0]
|
|
225
|
+
model = self._tree_model.item(iid)['text']
|
|
226
|
+
|
|
227
|
+
selw = ModelParamEditWindow(model_name=model,
|
|
228
|
+
param=self.portfolio.get_optimizer()['param'],
|
|
229
|
+
master=self._window)
|
|
230
|
+
param = selw.param
|
|
231
|
+
if param is None: return
|
|
232
|
+
|
|
233
|
+
self.portfolio.update_optimizer(param)
|
|
234
|
+
|
|
235
|
+
for child in self._tree_model.get_children(iid):
|
|
236
|
+
self._tree_model.delete(child)
|
|
237
|
+
|
|
238
|
+
invisible = configModels.param_default(model, visible=False)
|
|
239
|
+
for kk, vv in param.items():
|
|
240
|
+
if kk in invisible: continue
|
|
241
|
+
self._tree_model.insert(iid, tk.END, text=kk +" = "+ str(vv))
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _tree_menu_optimizer_delete_func(self):
|
|
245
|
+
item = self._tree_model.selection()[0]
|
|
246
|
+
self.portfolio.remove_optimizer()
|
|
247
|
+
self._tree_model.delete(item)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def _tree_model_item_selector_func(self, event):
|
|
251
|
+
try:
|
|
252
|
+
self._tree_menu_selector.tk_popup(self._tree_menu_selector.winfo_pointerx(),
|
|
253
|
+
self._tree_menu_selector.winfo_pointery())
|
|
254
|
+
finally:
|
|
255
|
+
self._tree_menu_selector.grab_release()
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def _tree_model_item_optimizer_func(self, event):
|
|
259
|
+
try:
|
|
260
|
+
self._tree_menu_optimizer.tk_popup(self._tree_menu_optimizer.winfo_pointerx(),
|
|
261
|
+
self._tree_menu_optimizer.winfo_pointery())
|
|
262
|
+
finally:
|
|
263
|
+
self._tree_menu_optimizer.grab_release()
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def _cbx_selector_func(self, event):
|
|
267
|
+
selo = self._cbx_selector.get()
|
|
268
|
+
if selo == 'Null': return
|
|
269
|
+
|
|
270
|
+
for kk in self.portfolio.optimizer.keys():
|
|
271
|
+
if configModels.get_comptype(kk) == 'standalone':
|
|
272
|
+
msg = f"The optimizer {kk} doesn't accept selectors. "
|
|
273
|
+
tk.messagebox.showwarning("Warning", message=msg, parent=self._window)
|
|
274
|
+
return
|
|
275
|
+
|
|
276
|
+
if self.portfolio.is_set_selector(selo):
|
|
277
|
+
tk.messagebox.showwarning("warning", "Already selected", parent=self._window)
|
|
278
|
+
return
|
|
279
|
+
|
|
280
|
+
selw = ModelParamEditWindow(master=self._window, model_name=selo)
|
|
281
|
+
param = selw.param
|
|
282
|
+
if param is None: return
|
|
283
|
+
|
|
284
|
+
self.portfolio.add_selector(name=selo, param=param)
|
|
285
|
+
|
|
286
|
+
loc = self.portfolio.number_selector() - 1
|
|
287
|
+
item = self._tree_model.insert("", loc, text=selo, tags=("m_selector",))
|
|
288
|
+
invisible = configModels.param_default(selo, visible=False)
|
|
289
|
+
for kk, vv in param.items():
|
|
290
|
+
if kk in invisible: continue
|
|
291
|
+
self._tree_model.insert(item, tk.END, text=kk +" = "+ str(vv))
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def _cbx_optim_type_func(self, event):
|
|
295
|
+
selo = self._cbx_optim_type.get()
|
|
296
|
+
|
|
297
|
+
if selo == 'Risk based':
|
|
298
|
+
self._cbx_optim_model['values'] = list(configModels.risk_based_models.keys())
|
|
299
|
+
self._cbx_optim_model.current(0)
|
|
300
|
+
elif selo == 'Naive':
|
|
301
|
+
self._cbx_optim_model['values'] = list(configModels.naive_models.keys())
|
|
302
|
+
self._cbx_optim_model.current(0)
|
|
303
|
+
elif selo == 'Greedy':
|
|
304
|
+
self._cbx_optim_model['values'] = list(configModels.greedy_models.keys())
|
|
305
|
+
self._cbx_optim_model.current(0)
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def _cbx_optim_model_func(self, event):
|
|
309
|
+
selo = self._cbx_optim_model.get()
|
|
310
|
+
|
|
311
|
+
if self.portfolio.is_set_optimizer():
|
|
312
|
+
msg = "The optimizer is already set. Only one optimizer is allowed."
|
|
313
|
+
tk.messagebox.showwarning("warning", message=msg, parent=self._window)
|
|
314
|
+
return
|
|
315
|
+
|
|
316
|
+
if len(self.portfolio.selectors.keys()) > 0:
|
|
317
|
+
if configModels.get_comptype(selo) == 'standalone':
|
|
318
|
+
msg = (f"The optimizer {selo} doesn’t accept selectors.\n"
|
|
319
|
+
"Do you want to remove the selectors?")
|
|
320
|
+
ans = tk.messagebox.askyesno("Warning", message=msg, parent=self._window)
|
|
321
|
+
if not ans: return
|
|
322
|
+
self._tree_model.delete(*self._tree_model.get_children())
|
|
323
|
+
self.portfolio.selectors = {}
|
|
324
|
+
|
|
325
|
+
selw = ModelParamEditWindow(master=self._window, model_name=selo)
|
|
326
|
+
param = selw.param
|
|
327
|
+
if param is None: return
|
|
328
|
+
|
|
329
|
+
self.portfolio.add_optimizer(name=selo, param=param)
|
|
330
|
+
|
|
331
|
+
item = self._tree_model.insert("", tk.END, text=selo, tags=("m_optimizer",))
|
|
332
|
+
invisible = configModels.param_default(selo, visible=False)
|
|
333
|
+
for kk, vv in param.items():
|
|
334
|
+
if kk in invisible.keys(): continue
|
|
335
|
+
self._tree_model.insert(item, tk.END, text=kk +" = "+ str(vv))
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def _btn_refresh(self):
|
|
339
|
+
_, tx = self._symb_table.get()
|
|
340
|
+
self._symb_table.write_order(tx)
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def _btn_cancel(self):
|
|
344
|
+
if self._initial_portfolio_name is not None:
|
|
345
|
+
config.PortDataDict[self._initial_portfolio_name].status = 'Set'
|
|
346
|
+
config.appPortfolioFrame.refresh()
|
|
347
|
+
|
|
348
|
+
self._window.grab_release()
|
|
349
|
+
if (self._master is not None) and self._master.winfo_exists():
|
|
350
|
+
self._master.focus_set()
|
|
351
|
+
self._window.destroy()
|
|
352
|
+
self.portfolio = None
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def _on_exit(self):
|
|
356
|
+
self._btn_cancel()
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def _btn_set(self):
|
|
360
|
+
# portfolio name
|
|
361
|
+
pname = self._ent_name.get()
|
|
362
|
+
status, val = mpv._validate_portfolio_name(pname)
|
|
363
|
+
if not status:
|
|
364
|
+
tk.messagebox.showwarning("Warning", val, parent=self._window)
|
|
365
|
+
return
|
|
366
|
+
|
|
367
|
+
if ((val != self._initial_portfolio_name) and (val in config.PortDataDict.keys())):
|
|
368
|
+
tk.messagebox.showwarning("Warning", configMSG._validate_portfolio_name_exist_msg,
|
|
369
|
+
parent=self._window)
|
|
370
|
+
return
|
|
371
|
+
|
|
372
|
+
self.portfolio.name = val
|
|
373
|
+
|
|
374
|
+
# symbols
|
|
375
|
+
status, tx = self._symb_table.get()
|
|
376
|
+
if (not status) or (len(tx) < 1):
|
|
377
|
+
tk.messagebox.showwarning("Warning", configMSG._validate_symbols_name_msg,
|
|
378
|
+
parent=self._window)
|
|
379
|
+
return
|
|
380
|
+
|
|
381
|
+
# validate symbols
|
|
382
|
+
source = list(configSettings.MasterApplicationSettings["Provider"].keys())[0]
|
|
383
|
+
_, sd, ed = sdate_edate_validate('', 'today')
|
|
384
|
+
|
|
385
|
+
gmd = GetMktData(source, force=False, validate=True)
|
|
386
|
+
gmd.getMkTDataSymb(tx, sd, ed)
|
|
387
|
+
symbols = gmd.symbols
|
|
388
|
+
error_symbols = gmd.errorsymb
|
|
389
|
+
|
|
390
|
+
self._symb_table.write_order(symbols)
|
|
391
|
+
if len(error_symbols) > 0:
|
|
392
|
+
msg = configMSG._validate_symbols_final_msg + '\n' + str(error_symbols)
|
|
393
|
+
tk.messagebox.showwarning("Warning", msg, parent=self._window)
|
|
394
|
+
|
|
395
|
+
if len(symbols) == 0:
|
|
396
|
+
tk.messagebox.showwarning("Warning", configMSG._validate_symbols_nr_msg,
|
|
397
|
+
parent=self._window)
|
|
398
|
+
return
|
|
399
|
+
|
|
400
|
+
self.portfolio.set_symbol(symbols)
|
|
401
|
+
nsymb = self.portfolio.number_symmbols()
|
|
402
|
+
|
|
403
|
+
# selectors
|
|
404
|
+
selectors = self.portfolio.to_list_selector()
|
|
405
|
+
for sel in selectors:
|
|
406
|
+
model_name = sel['name']
|
|
407
|
+
for fun in configModels.selector_models[model_name]['validate']:
|
|
408
|
+
param_name = list(fun.keys())[0]
|
|
409
|
+
if param_name not in sel['param'].keys(): continue
|
|
410
|
+
status, val = list(fun.values())[0](sel['param'], nsymb)
|
|
411
|
+
if not status:
|
|
412
|
+
msg = f"In {model_name}::{param_name}\n{val}"
|
|
413
|
+
tk.messagebox.showwarning("Warning", msg, parent = self._window)
|
|
414
|
+
return
|
|
415
|
+
sel['param'][param_name] = val
|
|
416
|
+
self.portfolio.update_selector(model_name, sel['param'])
|
|
417
|
+
|
|
418
|
+
# optimizer
|
|
419
|
+
optimizer = self.portfolio.get_optimizer()
|
|
420
|
+
if optimizer is None:
|
|
421
|
+
tk.messagebox.showwarning("Warning", configMSG._validate_portfolio_optimizer_msg,
|
|
422
|
+
parent=self._window)
|
|
423
|
+
return
|
|
424
|
+
|
|
425
|
+
model_name = optimizer['name']
|
|
426
|
+
model_family = configModels.get_model_family(model_name)
|
|
427
|
+
for fun in configModels.portfolio_model_family[model_family][model_name]['validate']:
|
|
428
|
+
param_name = list(fun.keys())[0]
|
|
429
|
+
if param_name not in optimizer['param'].keys(): continue
|
|
430
|
+
status, val = list(fun.values())[0](optimizer['param'], nsymb)
|
|
431
|
+
if not status:
|
|
432
|
+
msg = "In " + model_name + "::" + param_name + "\n" + val
|
|
433
|
+
tk.messagebox.showwarning("Warning", msg, parent = self._window)
|
|
434
|
+
return
|
|
435
|
+
optimizer['param'][param_name] = val
|
|
436
|
+
self.portfolio.update_optimizer(optimizer['param'])
|
|
437
|
+
|
|
438
|
+
# set
|
|
439
|
+
self.portfolio.status = "Set"
|
|
440
|
+
self.portfolio.saved = False
|
|
441
|
+
config.PortDataDict[self.portfolio.name] = self.portfolio
|
|
442
|
+
if ((self._initial_portfolio_name is not None) and
|
|
443
|
+
(self._initial_portfolio_name != self.portfolio.name)):
|
|
444
|
+
del config.PortDataDict[self._initial_portfolio_name]
|
|
445
|
+
|
|
446
|
+
config.appPortfolioFrame.refresh()
|
|
447
|
+
|
|
448
|
+
self._window.grab_release()
|
|
449
|
+
if (self._master is not None) and self._master.winfo_exists():
|
|
450
|
+
self._master.focus_set()
|
|
451
|
+
self._window.destroy()
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
def _menu_help_func(self):
|
|
455
|
+
webbrowser.open_new_tab(configHelps._Portfolio_Edit_help)
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
def _btn_symb_validate(self):
|
|
459
|
+
_, tx = self._symb_table.get()
|
|
460
|
+
if len(tx) < 1: return True
|
|
461
|
+
|
|
462
|
+
title = "Symbols validation"
|
|
463
|
+
btn_text = "Validate"
|
|
464
|
+
svw = SymbExtractWindow(master=self._window,
|
|
465
|
+
title=title, symbols=tx, btn_text=btn_text,
|
|
466
|
+
validate=True)
|
|
467
|
+
|
|
468
|
+
if len(svw.errorSymb) < 1:
|
|
469
|
+
self._symb_table.write_order(tx)
|
|
470
|
+
return
|
|
471
|
+
|
|
472
|
+
msg = f"These symbols cannot be retrieved.\n{svw.errorSymb}\nDo you want to delete them?"
|
|
473
|
+
askr = tk.messagebox.askyesno("Validation Failed", msg, parent=self._window)
|
|
474
|
+
self._symb_table.write_order(svw.symbols if askr else tx)
|
|
475
|
+
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import tkinter as tk
|
|
2
|
+
from copy import deepcopy
|
|
3
|
+
|
|
4
|
+
import azapyGUI.config as config
|
|
5
|
+
import azapyGUI.configMSG as configMSG
|
|
6
|
+
from azapyGUI.modelParametersValidation import _validate_portfolio_name
|
|
7
|
+
from azapyGUI.EntryNameWindow import EntryNameWindow
|
|
8
|
+
|
|
9
|
+
class EntryClonePortfolioWindow(EntryNameWindow):
|
|
10
|
+
def __init__(self, master, pname):
|
|
11
|
+
self._pname = pname
|
|
12
|
+
|
|
13
|
+
title = "Portfolio Clone"
|
|
14
|
+
text = f"Target portfolio: {pname}\nNew name:"
|
|
15
|
+
tip_text = "Unique portfolio name (letters + digits + .-_)"
|
|
16
|
+
super().__init__(master=master, title=title, text=text, tip_text=tip_text)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _btn_save(self):
|
|
20
|
+
nname = self._ent.get()
|
|
21
|
+
status, val = _validate_portfolio_name(nname)
|
|
22
|
+
if status:
|
|
23
|
+
if val in config.PortDataDict.keys():
|
|
24
|
+
tk.messagebox.showwarning("Warning", configMSG._validate_portfolio_name_exist_msg,
|
|
25
|
+
parent=self._window)
|
|
26
|
+
return
|
|
27
|
+
port = deepcopy(config.PortDataDict[self._pname])
|
|
28
|
+
port.name = val
|
|
29
|
+
port.saved = False
|
|
30
|
+
port.status = 'Set'
|
|
31
|
+
config.PortDataDict[val] = port
|
|
32
|
+
config.appPortfolioFrame.refresh()
|
|
33
|
+
self._btn_cancel()
|
|
34
|
+
return
|
|
35
|
+
tk.messagebox.showwarning("Warning", val, parent=self._window)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import tkinter as tk
|
|
2
|
+
|
|
3
|
+
import azapyGUI.config as config
|
|
4
|
+
|
|
5
|
+
class EntryNameWindow:
|
|
6
|
+
def __init__(self, master=None, title=None, text=None, tip_text=None, btn_text="OK"):
|
|
7
|
+
self._master = master
|
|
8
|
+
|
|
9
|
+
self._window = tk.Toplevel()
|
|
10
|
+
self._window.geometry("200x150")
|
|
11
|
+
self._window.title(title)
|
|
12
|
+
|
|
13
|
+
frm = tk.LabelFrame(master=self._window, text=title, font=("Forte", 10) )
|
|
14
|
+
frm.pack(expand=True, fill=tk.BOTH, padx=5, pady=5)
|
|
15
|
+
|
|
16
|
+
row = 0
|
|
17
|
+
frm.rowconfigure(row, weight=1)
|
|
18
|
+
lbl = tk.Label(master=frm, text=text)
|
|
19
|
+
lbl.grid(row=row, columnspan=2, pady=5, padx=5, sticky=tk.EW)
|
|
20
|
+
|
|
21
|
+
row += 1
|
|
22
|
+
frm.rowconfigure(row, weight=1)
|
|
23
|
+
self._ent = tk.Entry(master=frm, width=20)
|
|
24
|
+
self._ent.grid(row=row, columnspan=2, padx=10, pady=5, sticky=tk.EW)
|
|
25
|
+
self._ent.bind('<Return>', lambda event: self._btn_save())
|
|
26
|
+
self._ent.focus()
|
|
27
|
+
if tip_text is not None:
|
|
28
|
+
config.tiptil.bind(self._ent, tip_text)
|
|
29
|
+
|
|
30
|
+
row += 1
|
|
31
|
+
frm.columnconfigure(0, weight=1)
|
|
32
|
+
btn_calcel = tk.Button(master=frm, text="Cancel", command=self._btn_cancel, width=8)
|
|
33
|
+
btn_calcel.grid(row=row, column=0, padx=5, pady=5, sticky=tk.W)
|
|
34
|
+
frm.columnconfigure(0, weight=1)
|
|
35
|
+
|
|
36
|
+
btn_save = tk.Button(master=frm, text=btn_text, command=self._btn_save, width=8)
|
|
37
|
+
btn_save.grid(row=row, column=1, padx=5, pady=5, sticky=tk.E)
|
|
38
|
+
frm.columnconfigure(1, weight=1)
|
|
39
|
+
|
|
40
|
+
self._window.update()
|
|
41
|
+
if (self._master is not None) and self._master.winfo_exists():
|
|
42
|
+
self._master.wait_window(self._window)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _btn_cancel(self):
|
|
46
|
+
self._window.grab_release()
|
|
47
|
+
if (self._master is not None) and self._master.winfo_exists():
|
|
48
|
+
self._master.focus_set()
|
|
49
|
+
self._window.destroy()
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _btn_save(self):
|
|
53
|
+
# to be implemented by derived class
|
|
54
|
+
pass
|
|
55
|
+
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import tkinter as tk
|
|
2
|
+
|
|
3
|
+
import azapyGUI.config as config
|
|
4
|
+
import azapyGUI.configMSG as configMSG
|
|
5
|
+
from azapyGUI.modelParametersValidation import _validate_portfolio_name
|
|
6
|
+
from azapyGUI.EntryNameWindow import EntryNameWindow
|
|
7
|
+
|
|
8
|
+
class EntryRenamePortfolioWindow(EntryNameWindow):
|
|
9
|
+
def __init__(self, master, pname):
|
|
10
|
+
self._pname = pname
|
|
11
|
+
|
|
12
|
+
title = "Portfolio Rename"
|
|
13
|
+
text = f"Old name: {pname}\nNew name:"
|
|
14
|
+
tip_text = "Unique portfolio name (letters + digits + .-_)"
|
|
15
|
+
super().__init__(master=master, title=title, text=text, tip_text=tip_text)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _btn_save(self):
|
|
19
|
+
nname = self._ent.get()
|
|
20
|
+
status, val = _validate_portfolio_name(nname)
|
|
21
|
+
if status:
|
|
22
|
+
if val in config.PortDataDict.keys():
|
|
23
|
+
tk.messagebox.showwarning("Warning", configMSG._validate_portfolio_name_exist_msg,
|
|
24
|
+
parent=self._window)
|
|
25
|
+
return
|
|
26
|
+
port = config.PortDataDict.pop(self._pname)
|
|
27
|
+
port.name = val
|
|
28
|
+
port.saved = False
|
|
29
|
+
config.PortDataDict[val] = port
|
|
30
|
+
config.appPortfolioFrame.refresh()
|
|
31
|
+
self._btn_cancel()
|
|
32
|
+
return
|
|
33
|
+
tk.messagebox.showwarning("Warning", val, parent=self._window)
|
azapyGUI/GetMktData.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import azapy as az
|
|
2
|
+
from copy import deepcopy
|
|
3
|
+
|
|
4
|
+
import azapyGUI.config as config
|
|
5
|
+
import azapyGUI.configSettings as configSettings
|
|
6
|
+
from azapyGUI.MktDataNode import MktDataNode
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class GetMktData():
|
|
10
|
+
def __init__(self, source, force=False, validate=False):
|
|
11
|
+
self._validate = validate
|
|
12
|
+
|
|
13
|
+
api_key = None
|
|
14
|
+
param = {}
|
|
15
|
+
for kk, vv in configSettings.MasterApplicationSettings["Provider"][source].items():
|
|
16
|
+
if kk == "key":
|
|
17
|
+
api_key = vv["key"]
|
|
18
|
+
else:
|
|
19
|
+
param[kk] = vv
|
|
20
|
+
|
|
21
|
+
file_dir = configSettings.MasterApplicationSettings["UserMktDataDirectory"]
|
|
22
|
+
|
|
23
|
+
self._symb_req_default = {"symbol": None,
|
|
24
|
+
"sdate": None,
|
|
25
|
+
"edate": None,
|
|
26
|
+
"output_format": 'dict',
|
|
27
|
+
"source": source,
|
|
28
|
+
"force": force,
|
|
29
|
+
"save": True,
|
|
30
|
+
"file_dir": file_dir,
|
|
31
|
+
"file_format": 'csv',
|
|
32
|
+
'api_key': api_key,
|
|
33
|
+
'param': param,
|
|
34
|
+
}
|
|
35
|
+
self._mktr = az.MkTreader(verbose=False)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def getMkTDataSymb(self, symbols, sdate, edate):
|
|
39
|
+
# sdate and edate are assumed to be validated
|
|
40
|
+
self._symb_req = deepcopy(self._symb_req_default)
|
|
41
|
+
|
|
42
|
+
self._symb_req["sdate"] = sdate
|
|
43
|
+
self._symb_req["edate"] = edate
|
|
44
|
+
|
|
45
|
+
symb = [sy for sy in symbols if not self._checkCollection(sy, sdate, edate)]
|
|
46
|
+
if len(symb) == 0:
|
|
47
|
+
# all symb in the system - OK nothing else to do
|
|
48
|
+
self.symbols = symbols
|
|
49
|
+
self.errorsymb = []
|
|
50
|
+
return
|
|
51
|
+
|
|
52
|
+
# retrieve symb (not in the system)
|
|
53
|
+
self._symb_req["symbol"] = symb
|
|
54
|
+
mktdata = self._mktr.get(calendar=config.calendar, **self._symb_req)
|
|
55
|
+
if len(mktdata.keys()) == 0:
|
|
56
|
+
# no extraction was possible (all symb are error symbols)
|
|
57
|
+
self.symbols = list(set(symbols) - set(symb))
|
|
58
|
+
self.errorsymb = symb
|
|
59
|
+
return
|
|
60
|
+
|
|
61
|
+
status = self._mktr.get_request_status()
|
|
62
|
+
error_log = self._mktr.get_error_log()
|
|
63
|
+
self.symbols = list(mktdata.keys())
|
|
64
|
+
self.errorsymb = list(set(symb) - set(self.symbols))
|
|
65
|
+
|
|
66
|
+
# put in the collection newly extracted symbols
|
|
67
|
+
for sy in self.symbols:
|
|
68
|
+
mktData = MktDataNode()
|
|
69
|
+
mktData.name = sy
|
|
70
|
+
mktData.mktdata = mktdata[sy].copy()
|
|
71
|
+
mktData.stats = status[sy].copy()
|
|
72
|
+
mktData.error_log = error_log[sy].copy() if sy in error_log.keys() else {}
|
|
73
|
+
config.MktDataDict[sy] = mktData
|
|
74
|
+
config.appMktDataFrame.refresh()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _checkCollection(self, symb, sdate, edate):
|
|
78
|
+
if symb not in config.MktDataDict.keys(): return False
|
|
79
|
+
|
|
80
|
+
if self._validate: return True
|
|
81
|
+
|
|
82
|
+
if ((self._symb_req["sdate"] >= config.MktDataDict[symb].sdate()) and
|
|
83
|
+
(self._symb_req["edate"] <= config.MktDataDict[symb].edate())): return True
|
|
84
|
+
|
|
85
|
+
return False
|