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,213 @@
|
|
|
1
|
+
import tkinter as tk
|
|
2
|
+
from tkinter import ttk
|
|
3
|
+
from tkinter import filedialog
|
|
4
|
+
|
|
5
|
+
from copy import deepcopy
|
|
6
|
+
|
|
7
|
+
import azapyGUI.configSettings as configSettings
|
|
8
|
+
import azapyGUI.config as config
|
|
9
|
+
import azapyGUI.configMSG as configMSG
|
|
10
|
+
from azapyGUI.serviceMasterUserConfig import _saveMasterUserConfig
|
|
11
|
+
|
|
12
|
+
class AppSettingsPage(tk.Frame):
|
|
13
|
+
def __init__(self, master, category, title):
|
|
14
|
+
self._master = master
|
|
15
|
+
self._category = category
|
|
16
|
+
super().__init__(self._master)
|
|
17
|
+
|
|
18
|
+
self._window = self
|
|
19
|
+
|
|
20
|
+
frm = tk.LabelFrame(master=self._window, text=title, font=("Forte", 10))
|
|
21
|
+
frm.grid(row=0, column=0, padx=5, pady=5, sticky=tk.NSEW)
|
|
22
|
+
self._window.rowconfigure(0, weight=1)
|
|
23
|
+
self._window.columnconfigure(0, weight=1)
|
|
24
|
+
|
|
25
|
+
frm_btn = tk.Frame(master=self._window)
|
|
26
|
+
frm_btn.grid(row=1, column=0, padx=5, pady=5, sticky=tk.EW)
|
|
27
|
+
|
|
28
|
+
# on frm
|
|
29
|
+
frm_set = tk.Frame(master=frm)
|
|
30
|
+
frm_set.grid(row=0, column=0, padx=5, pady=5, sticky=tk.NS)
|
|
31
|
+
frm.rowconfigure(0, weight=1)
|
|
32
|
+
|
|
33
|
+
frm_view = tk.Frame(master=frm, width=150)
|
|
34
|
+
frm_view.grid(row=0, column=1, padx=5, pady=5, sticky=tk.NSEW)
|
|
35
|
+
frm.columnconfigure(1, weight=1)
|
|
36
|
+
|
|
37
|
+
# on frm_set
|
|
38
|
+
self._setDef = configSettings.settings_model[self._category]
|
|
39
|
+
self.settings = deepcopy(configSettings.MasterApplicationSettings)
|
|
40
|
+
row = 0
|
|
41
|
+
self._chk_val = {}
|
|
42
|
+
self._chk_btn = {}
|
|
43
|
+
for param, value in self._setDef.items():
|
|
44
|
+
match value["type"]:
|
|
45
|
+
case 'ButtonDir':
|
|
46
|
+
btn = tk.Button(master=frm_set, text=value['field'], width=18,
|
|
47
|
+
command=lambda pp=param: self._get_directory_func(pp))
|
|
48
|
+
btn.grid(row=row, column=0, padx=5, pady=5, sticky=tk.W)
|
|
49
|
+
config.tiptil.bind(btn, value["tip"])
|
|
50
|
+
row += 1
|
|
51
|
+
case 'Checkbutton':
|
|
52
|
+
chk_var = tk.BooleanVar(master=frm_set, value=self.settings[param])
|
|
53
|
+
chk_btn = tk.Checkbutton(master=frm_set, text=value["field"],
|
|
54
|
+
variable = chk_var,
|
|
55
|
+
onvalue = True, offvalue = False,
|
|
56
|
+
height=2, width=18,
|
|
57
|
+
command=lambda pp=param: self._chk_btn_func(pp),
|
|
58
|
+
anchor=tk.W)
|
|
59
|
+
chk_btn.grid(row=row, column=0, padx=5, pady=5, sticky=tk.W)
|
|
60
|
+
self._chk_val[param] = chk_var
|
|
61
|
+
self._chk_btn[param] = chk_btn
|
|
62
|
+
config.tiptil.bind(chk_btn, value["tip"])
|
|
63
|
+
row += 1
|
|
64
|
+
case 'MCheckbutton':
|
|
65
|
+
frm_chk = tk.LabelFrame(master=frm_set, text=value["field"])
|
|
66
|
+
frm_chk.grid(row=row, column=0, padx=5, pady=5, sticky=tk.W)
|
|
67
|
+
row += 1
|
|
68
|
+
row_chk = 0
|
|
69
|
+
self._chk_val[param] = {}
|
|
70
|
+
self._chk_btn[param] = {}
|
|
71
|
+
for param_chk, value_chk in value["values"].items():
|
|
72
|
+
chk_var = tk.BooleanVar(master=frm_chk,
|
|
73
|
+
value=True if param_chk in self.settings[param].keys() else False)
|
|
74
|
+
chk_btn = tk.Checkbutton(master=frm_chk, text=param_chk,
|
|
75
|
+
variable = chk_var,
|
|
76
|
+
onvalue = True, offvalue = False,
|
|
77
|
+
height=2, width=18,
|
|
78
|
+
command=lambda param=param, pp=param_chk: self._mchk_btn_func(param, pp),
|
|
79
|
+
anchor=tk.W)
|
|
80
|
+
chk_btn.grid(row=row_chk, column=0, padx=5, pady=0, sticky=tk.W)
|
|
81
|
+
self._chk_val[param][param_chk] = chk_var
|
|
82
|
+
self._chk_btn[param][param_chk] = chk_btn
|
|
83
|
+
row_chk += 1
|
|
84
|
+
case _:
|
|
85
|
+
# you should not be here
|
|
86
|
+
raise ValueError("Error: Unknown configSetting type")
|
|
87
|
+
|
|
88
|
+
# on frm_view
|
|
89
|
+
self._stv = ttk.Treeview(master=frm_view, selectmode="browse", show="tree")
|
|
90
|
+
self._stv.pack(padx=0, pady=0, expand=True, side=tk.LEFT, fill=tk.BOTH)
|
|
91
|
+
self._stv_write()
|
|
92
|
+
|
|
93
|
+
vscrlb = ttk.Scrollbar(master=frm_view,
|
|
94
|
+
orient ="vertical",
|
|
95
|
+
command = self._stv.yview)
|
|
96
|
+
vscrlb.pack(padx=0, pady=2, side=tk.RIGHT, fill=tk.Y)
|
|
97
|
+
self._stv.configure(yscrollcommand = vscrlb.set)
|
|
98
|
+
|
|
99
|
+
# on frm_btn
|
|
100
|
+
btn_cancel = tk.Button(master=frm_btn, text="Cancel/Exit", width=12,
|
|
101
|
+
command=self._btn_cancel_func)
|
|
102
|
+
btn_cancel.pack(padx=5, pady=5, side=tk.LEFT)
|
|
103
|
+
|
|
104
|
+
btn_save = tk.Button(master=frm_btn, text="Save", width=12,
|
|
105
|
+
command=self._btn_save_func)
|
|
106
|
+
btn_save.pack(padx=5, pady=5, side=tk.RIGHT)
|
|
107
|
+
|
|
108
|
+
btn_reset = tk.Button(master=frm_btn, text="Default Values", width=12,
|
|
109
|
+
command=self._btn_reset_func)
|
|
110
|
+
btn_reset.pack(padx=5, pady=5, side=tk.LEFT)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _stv_write(self):
|
|
114
|
+
for kk in self._setDef.keys():
|
|
115
|
+
val = self.settings[kk]
|
|
116
|
+
item = self._stv.insert("", tk.END, text=self._setDef[kk]["field"] + ":", open=True)
|
|
117
|
+
if isinstance(val, dict):
|
|
118
|
+
for subkk, subval in val.items():
|
|
119
|
+
subitem = self._stv.insert(item, tk.END, text=subkk, open=True)
|
|
120
|
+
for subsubkk, subsubval in subval.items():
|
|
121
|
+
self._stv.insert(subitem, tk.END, text=subsubkk +" = "+ str(subsubval))
|
|
122
|
+
else:
|
|
123
|
+
self._stv.insert(item, tk.END, text=str(val))
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _stv_refresh(self):
|
|
127
|
+
for item in self._stv.get_children():
|
|
128
|
+
self._stv.delete(item)
|
|
129
|
+
self._stv_write()
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _get_directory_func(self, param=None):
|
|
133
|
+
title = self._setDef[param]["title"]
|
|
134
|
+
folder_selected = filedialog.askdirectory(parent=self._window, title=title)
|
|
135
|
+
if folder_selected:
|
|
136
|
+
self.settings[param] = folder_selected
|
|
137
|
+
self._stv_refresh()
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _chk_btn_func(self, param):
|
|
141
|
+
self.settings[param] = self._chk_val[param].get()
|
|
142
|
+
self._stv_refresh()
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _mchk_btn_func(self, param, pp):
|
|
146
|
+
if self._chk_val[param][pp].get():
|
|
147
|
+
self.settings[param][pp] = {}
|
|
148
|
+
for kk, vv in self._setDef[param]["values"][pp].items():
|
|
149
|
+
match vv['type']:
|
|
150
|
+
case 'str':
|
|
151
|
+
vpar = tk.simpledialog.askstring(pp, vv["field"],
|
|
152
|
+
parent=self._window,
|
|
153
|
+
initialvalue=vv["default"])
|
|
154
|
+
if vpar == '':
|
|
155
|
+
vpar = configSettings.get_envkey(pp)
|
|
156
|
+
if vpar is None:
|
|
157
|
+
msg = (configMSG._validate_provider_key_msg
|
|
158
|
+
+'\n'
|
|
159
|
+
+ configSettings.get_envkey_valriable_name(pp))
|
|
160
|
+
tk.messagebox.showwarning(title="Warning", message=msg, parent=self._window)
|
|
161
|
+
del self.settings[param][pp]
|
|
162
|
+
self._chk_btn[param][pp].deselect()
|
|
163
|
+
return
|
|
164
|
+
self.settings[param][pp][kk] = vpar
|
|
165
|
+
case 'int':
|
|
166
|
+
vpar = tk.simpledialog.askinteger(pp, vv["field"],
|
|
167
|
+
parent=self._window,
|
|
168
|
+
initialvalue=vv["default"],
|
|
169
|
+
minvalue=1)
|
|
170
|
+
self.settings[param][pp][kk] = vpar
|
|
171
|
+
case 'float':
|
|
172
|
+
vpar = tk.simpledialog.askfloat(pp, vv["field"],
|
|
173
|
+
parent=self._window,
|
|
174
|
+
initialvalue=vv["default"],
|
|
175
|
+
minvalue=1)
|
|
176
|
+
self.settings[param][pp][kk] = vpar
|
|
177
|
+
case _:
|
|
178
|
+
# you should not be here
|
|
179
|
+
raise ValueError("Error: in AppSettingWindow::_chk_bth_func wrong type")
|
|
180
|
+
else:
|
|
181
|
+
del self.settings[param][pp]
|
|
182
|
+
self._stv_refresh()
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _btn_cancel_func(self):
|
|
186
|
+
if self._master.winfo_exists():
|
|
187
|
+
self._master.grab_release()
|
|
188
|
+
self._master.destroy()
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _btn_save_func(self):
|
|
192
|
+
configSettings.MasterApplicationSettings = deepcopy(self.settings)
|
|
193
|
+
_saveMasterUserConfig(configSettings.MasterApplicationSettings)
|
|
194
|
+
#self._btn_cancel_func()
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _btn_reset_func(self):
|
|
198
|
+
for kk, vv in self._setDef.items():
|
|
199
|
+
self.settings[kk] = deepcopy(vv["default"])
|
|
200
|
+
match vv["type"]:
|
|
201
|
+
case "Checkbutton":
|
|
202
|
+
if self.settings[kk]:
|
|
203
|
+
self._chk_btn[kk].select()
|
|
204
|
+
else:
|
|
205
|
+
self._chk_btn[kk].deselect()
|
|
206
|
+
case "MCheckbutton":
|
|
207
|
+
for ll in self._setDef[kk]["values"].keys():
|
|
208
|
+
if ll in self.settings[kk].keys():
|
|
209
|
+
self._chk_btn[kk][ll].select()
|
|
210
|
+
else:
|
|
211
|
+
self._chk_btn[kk][ll].deselect()
|
|
212
|
+
self._stv_refresh()
|
|
213
|
+
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import tkinter as tk
|
|
2
|
+
from copy import deepcopy
|
|
3
|
+
|
|
4
|
+
import azapyGUI.config as config
|
|
5
|
+
import azapyGUI.configSettings as configSettings
|
|
6
|
+
from azapyGUI.serviceMasterUserConfig import _saveMasterUserConfig
|
|
7
|
+
|
|
8
|
+
class AppSettingsPageMisc(tk.Frame):
|
|
9
|
+
def __init__(self, master):
|
|
10
|
+
self._master = master
|
|
11
|
+
super().__init__(self._master)
|
|
12
|
+
|
|
13
|
+
self._window = self
|
|
14
|
+
self._category = "Miscellaneous"
|
|
15
|
+
|
|
16
|
+
title = "Miscellaneous"
|
|
17
|
+
frm_set = tk.LabelFrame(master=self._window, text=title, font=("Forte", 10))
|
|
18
|
+
frm_set.grid(row=0, column=0, padx=5, pady=5, sticky=tk.NSEW)
|
|
19
|
+
self._window.rowconfigure(0, weight=1)
|
|
20
|
+
self._window.columnconfigure(0, weight=1)
|
|
21
|
+
frm_set.columnconfigure(0, weight=1)
|
|
22
|
+
frm_set.columnconfigure(1, weight=1)
|
|
23
|
+
|
|
24
|
+
frm_btn = tk.Frame(master=self._window)
|
|
25
|
+
frm_btn.grid(row=1, column=0, padx=5, pady=5, sticky=tk.EW)
|
|
26
|
+
|
|
27
|
+
# on frm
|
|
28
|
+
self._setDef = configSettings.settings_model[self._category]
|
|
29
|
+
self.settings = deepcopy(configSettings.MasterApplicationSettings)
|
|
30
|
+
row = 0
|
|
31
|
+
self._chk_val = {}
|
|
32
|
+
self._chk_btn = {}
|
|
33
|
+
for param, value in self._setDef.items():
|
|
34
|
+
match value["type"]:
|
|
35
|
+
case 'Checkbutton':
|
|
36
|
+
lbl = tk.Label(master=frm_set, text=value["field"], anchor=tk.W)
|
|
37
|
+
lbl.grid(row=row, column=0, padx=5, pady=5, sticky=tk.EW)
|
|
38
|
+
chk_var = tk.BooleanVar(master=frm_set, value=self.settings[param])
|
|
39
|
+
chk_btn = tk.Checkbutton(master=frm_set, variable = chk_var,
|
|
40
|
+
onvalue = True, offvalue = False,
|
|
41
|
+
height=2, width=18, anchor=tk.W)
|
|
42
|
+
chk_btn.grid(row=row, column=1, padx=5, pady=5, sticky=tk.W)
|
|
43
|
+
self._chk_val[param] = chk_var
|
|
44
|
+
self._chk_btn[param] = chk_btn
|
|
45
|
+
config.tiptil.bind(chk_btn, value["tip"])
|
|
46
|
+
row += 1
|
|
47
|
+
case 'Entry':
|
|
48
|
+
lbl = tk.Label(master=frm_set, text=value["field"], anchor=tk.W)
|
|
49
|
+
lbl.grid(row=row, column=0, padx=5, pady=5, sticky=tk.W)
|
|
50
|
+
ent_var = tk.StringVar(master=frm_set, value=self.settings[param])
|
|
51
|
+
ent = tk.Entry(master=frm_set, textvariable=ent_var, validate='key', width=10)
|
|
52
|
+
ent['validatecommand'] = (ent.register(value["validate"]),'%S','%d','%P')
|
|
53
|
+
ent.grid(row=row, column=1, padx=5, pady=5, sticky=tk.W)
|
|
54
|
+
self._chk_val[param] = ent_var
|
|
55
|
+
config.tiptil.bind(ent, value["tip"])
|
|
56
|
+
row += 1
|
|
57
|
+
case _:
|
|
58
|
+
# you should not be here
|
|
59
|
+
raise ValueError("Error: Unknown configSetting type")
|
|
60
|
+
|
|
61
|
+
# on frm_btn
|
|
62
|
+
btn_cancel = tk.Button(master=frm_btn, text="Cancel/Exit", width=12,
|
|
63
|
+
command=self._btn_cancel_func)
|
|
64
|
+
btn_cancel.pack(padx=5, pady=5, side=tk.LEFT)
|
|
65
|
+
|
|
66
|
+
btn_save = tk.Button(master=frm_btn, text="Save", width=12,
|
|
67
|
+
command=self._btn_save_func)
|
|
68
|
+
btn_save.pack(padx=5, pady=5, side=tk.RIGHT)
|
|
69
|
+
|
|
70
|
+
btn_reset = tk.Button(master=frm_btn, text="Default Values", width=12,
|
|
71
|
+
command=self._btn_reset_func)
|
|
72
|
+
btn_reset.pack(padx=5, pady=5, side=tk.LEFT)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _btn_cancel_func(self):
|
|
76
|
+
if self._master.winfo_exists():
|
|
77
|
+
self._master.grab_release()
|
|
78
|
+
self._master.destroy()
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _btn_reset_func(self):
|
|
82
|
+
for kk, vv in self._setDef.items():
|
|
83
|
+
self.settings[kk] = deepcopy(vv["default"])
|
|
84
|
+
match vv["type"]:
|
|
85
|
+
case "Checkbutton":
|
|
86
|
+
if self.settings[kk]:
|
|
87
|
+
self._chk_btn[kk].select()
|
|
88
|
+
else:
|
|
89
|
+
self._chk_btn[kk].deselect()
|
|
90
|
+
case "Entry":
|
|
91
|
+
self._chk_val[kk].set(self.settings[kk])
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _btn_save_func(self):
|
|
95
|
+
for param in self._chk_val.keys():
|
|
96
|
+
self.settings[param] = self._chk_val[param].get()
|
|
97
|
+
configSettings.MasterApplicationSettings = deepcopy(self.settings)
|
|
98
|
+
_saveMasterUserConfig(configSettings.MasterApplicationSettings)
|
|
99
|
+
config.tiptil.turned(on=configSettings.MasterApplicationSettings["ShowTips"])
|
|
100
|
+
|
|
101
|
+
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import tkinter as tk
|
|
2
|
+
from tkinter import ttk
|
|
3
|
+
import webbrowser
|
|
4
|
+
|
|
5
|
+
import azapyGUI.configSettings as configSettings
|
|
6
|
+
import azapyGUI.configHelps as configHelps
|
|
7
|
+
from azapyGUI.AppSettingsPage import AppSettingsPage
|
|
8
|
+
from azapyGUI.AppSettingsPageMisc import AppSettingsPageMisc
|
|
9
|
+
|
|
10
|
+
class AppSettingsWindow:
|
|
11
|
+
def __init__(self, master):
|
|
12
|
+
self._master = master
|
|
13
|
+
|
|
14
|
+
self._window = tk.Toplevel()
|
|
15
|
+
title = f"User Default Settings v.{configSettings.Version}"
|
|
16
|
+
self._window.title(title)
|
|
17
|
+
self._window.focus_set()
|
|
18
|
+
self._window.grab_set()
|
|
19
|
+
self._window.protocol("WM_DELETE_WINDOW", self._on_exit)
|
|
20
|
+
|
|
21
|
+
menubar = tk.Menu(self._window)
|
|
22
|
+
filemenu = tk.Menu(menubar, tearoff=0)
|
|
23
|
+
filemenu.add_command(label='Help', command=self._menu_help_func)
|
|
24
|
+
menubar.add_cascade(label='Help', menu=filemenu)
|
|
25
|
+
self._window.config(menu=menubar)
|
|
26
|
+
|
|
27
|
+
ntb = ttk.Notebook(master=self._window)
|
|
28
|
+
ntb.pack(expand=True, fill=tk.BOTH)
|
|
29
|
+
|
|
30
|
+
category = "Directors"
|
|
31
|
+
title = "Default Directories"
|
|
32
|
+
pg_dirctor = AppSettingsPage(master=self._window, category=category, title=title)
|
|
33
|
+
ntb.add(pg_dirctor, text="Directories")
|
|
34
|
+
|
|
35
|
+
category = "MkTData"
|
|
36
|
+
title = "Market Data"
|
|
37
|
+
pg_mktdata = AppSettingsPage(master=self._window, category=category, title=title)
|
|
38
|
+
ntb.add(pg_mktdata, text="Market Data")
|
|
39
|
+
|
|
40
|
+
pg_misc = AppSettingsPageMisc(master=self._window)
|
|
41
|
+
ntb.add(pg_misc, text="Miscellaneous")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _on_exit(self):
|
|
45
|
+
self._window.grab_release()
|
|
46
|
+
if self._master.winfo_exists():
|
|
47
|
+
self._master.focus_set()
|
|
48
|
+
self._window.destroy()
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _menu_help_func(self):
|
|
52
|
+
webbrowser.open_new_tab(configHelps._Settings_panel_help)
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import tkinter as tk
|
|
2
|
+
import time
|
|
3
|
+
import azapy as az
|
|
4
|
+
|
|
5
|
+
import azapyGUI.config as config
|
|
6
|
+
import azapyGUI.configModels as configModels
|
|
7
|
+
from azapyGUI.GetMktData import GetMktData
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class BacktestComputation:
|
|
11
|
+
def __init__(self, master, pnames, sdate, edate, noffset, fixoffset,
|
|
12
|
+
capital, nsh_round, provider, force):
|
|
13
|
+
self._master = master
|
|
14
|
+
self._pnames = pnames
|
|
15
|
+
self._sdate = sdate
|
|
16
|
+
self._edate = edate
|
|
17
|
+
self._noffset = noffset
|
|
18
|
+
self._fixoffset = fixoffset
|
|
19
|
+
self._capital = capital
|
|
20
|
+
self._nsh_round = nsh_round
|
|
21
|
+
self._provider = provider
|
|
22
|
+
self._force = force
|
|
23
|
+
|
|
24
|
+
self.backtest = {}
|
|
25
|
+
self.backtest_error = []
|
|
26
|
+
|
|
27
|
+
def collectMktData(self):
|
|
28
|
+
symbols = set()
|
|
29
|
+
for pname in self._pnames:
|
|
30
|
+
port = config.PortDataDict[pname]
|
|
31
|
+
symbols.update(port.symbols)
|
|
32
|
+
|
|
33
|
+
gmd = GetMktData(self._provider, self._force)
|
|
34
|
+
gmd.getMkTDataSymb(list(symbols), self._sdate, self._edate)
|
|
35
|
+
self.symbols = gmd.symbols
|
|
36
|
+
errorsymb = set(gmd.errorsymb)
|
|
37
|
+
|
|
38
|
+
if len(errorsymb) == 0: return True
|
|
39
|
+
porterror = []
|
|
40
|
+
for pname in self._pnames:
|
|
41
|
+
port = config.PortDataDict[pname]
|
|
42
|
+
res = errorsymb.intersection(port.symbols)
|
|
43
|
+
if len(res) > 0:
|
|
44
|
+
porterror.append(pname)
|
|
45
|
+
config.PortDataDict[pname].status = 'Unset'
|
|
46
|
+
port = set(self._pnames).intersection(porterror)
|
|
47
|
+
msg = (f"The following symbols, {errorsymb},\n"
|
|
48
|
+
f"cannot be retrieved from the provider {self._provider}\n"
|
|
49
|
+
f"Abort the backtesting for portfolios {porterror}.")
|
|
50
|
+
if len(port) == 0:
|
|
51
|
+
msg += "Abort backtesting!"
|
|
52
|
+
tk.messagebox.showwarning("Warning", message=msg, parent=self._master)
|
|
53
|
+
return False
|
|
54
|
+
|
|
55
|
+
msg += ("Do you want to continue with the \n"
|
|
56
|
+
f"backtesting for {port}?")
|
|
57
|
+
ask = tk.messagebox.askyesno("Warning", message=msg)
|
|
58
|
+
if ask == 'yes':
|
|
59
|
+
self._pnames = list[port]
|
|
60
|
+
return True
|
|
61
|
+
|
|
62
|
+
return False
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def compute(self, time_port):
|
|
66
|
+
for pname in self._pnames:
|
|
67
|
+
|
|
68
|
+
tic = time.perf_counter()
|
|
69
|
+
res = self._computePort(pname)
|
|
70
|
+
toc = time.perf_counter()
|
|
71
|
+
|
|
72
|
+
msg = str(round(toc - tic, 4)) + 's' if res else 'error'
|
|
73
|
+
time_port[pname].set(msg)
|
|
74
|
+
self._master.update()
|
|
75
|
+
|
|
76
|
+
if not res:
|
|
77
|
+
self.backtest_error.append(pname)
|
|
78
|
+
else:
|
|
79
|
+
self.backtest[pname] = self._btest
|
|
80
|
+
|
|
81
|
+
return len(self.backtest_error) == 0
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _computePort(self, pname):
|
|
85
|
+
optname = list(config.PortDataDict[pname].optimizer.keys())[0]
|
|
86
|
+
if configModels.get_comptype(optname) == 'pipe':
|
|
87
|
+
return self._computePort_pipe(pname)
|
|
88
|
+
else:
|
|
89
|
+
return self._computePort_standalone(pname)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _computePort_standalone(self, pname):
|
|
93
|
+
portdata = config.PortDataDict[pname]
|
|
94
|
+
mktdata = {symb: config.MktDataDict[symb].get_mktdata().copy() for symb in portdata.symbols}
|
|
95
|
+
|
|
96
|
+
optname = list(config.PortDataDict[pname].optimizer.keys())[0]
|
|
97
|
+
fmname = configModels.get_model_family(optname)
|
|
98
|
+
mname = configModels.portfolio_model_family[fmname][optname]['azapy'][1]
|
|
99
|
+
param = portdata.optimizer[optname]['param']
|
|
100
|
+
freq = param['freq']
|
|
101
|
+
hlength = 0
|
|
102
|
+
pp = getattr(az, mname)(mktdata=mktdata,
|
|
103
|
+
sdate=self._sdate,
|
|
104
|
+
edate=self._edate,
|
|
105
|
+
pname=pname,
|
|
106
|
+
pcolname='close',
|
|
107
|
+
capital=self._capital,
|
|
108
|
+
freq=freq,
|
|
109
|
+
noffset=self._noffset,
|
|
110
|
+
fixoffset=self._fixoffset,
|
|
111
|
+
histoffset=hlength,
|
|
112
|
+
calendar=config.calendar,
|
|
113
|
+
nsh_round=self._nsh_round,
|
|
114
|
+
)
|
|
115
|
+
pp.set_model(**param)
|
|
116
|
+
self._btest = pp
|
|
117
|
+
return (pp.status == 0)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _computePort_pipe(self, pname):
|
|
121
|
+
portdata = config.PortDataDict[pname]
|
|
122
|
+
mktdata = {symb: config.MktDataDict[symb].mktdata.copy() for symb in portdata.symbols}
|
|
123
|
+
model = []
|
|
124
|
+
hlength = 0
|
|
125
|
+
|
|
126
|
+
sel_list = [None] * len(portdata.selectors.keys())
|
|
127
|
+
for name, vv in portdata.selectors.items():
|
|
128
|
+
sel_list[vv['index']] = vv
|
|
129
|
+
for vv in sel_list:
|
|
130
|
+
mname = configModels.selector_models[vv['name']]['azapy']
|
|
131
|
+
oo = getattr(az, mname)
|
|
132
|
+
moo = oo(**vv['param'])
|
|
133
|
+
model.append(moo)
|
|
134
|
+
if 'hlength' in vv['param'].keys():
|
|
135
|
+
hlength = max(hlength, vv['param']['hlength'])
|
|
136
|
+
|
|
137
|
+
for mopt, vv in portdata.optimizer.items():
|
|
138
|
+
fmname = configModels.get_model_family(vv['name'])
|
|
139
|
+
mname = configModels.portfolio_model_family[fmname][vv['name']]['azapy']
|
|
140
|
+
if mname == 'EWPEngine':
|
|
141
|
+
model.append('EWP')
|
|
142
|
+
else:
|
|
143
|
+
oo = getattr(az, mname)
|
|
144
|
+
moo = oo(**vv['param'])
|
|
145
|
+
model.append(moo)
|
|
146
|
+
hlength = max(hlength, vv['param']['hlength'])
|
|
147
|
+
freq = vv['param']['freq']
|
|
148
|
+
|
|
149
|
+
pipe = az.ModelPipeline(model)
|
|
150
|
+
|
|
151
|
+
pp = az.Port_Generator(mktdata=mktdata,
|
|
152
|
+
sdate=self._sdate,
|
|
153
|
+
edate=self._edate,
|
|
154
|
+
pname=pname,
|
|
155
|
+
pcolname='close',
|
|
156
|
+
capital=self._capital,
|
|
157
|
+
freq=freq,
|
|
158
|
+
noffset=self._noffset,
|
|
159
|
+
fixoffset=self._fixoffset,
|
|
160
|
+
histoffset=hlength,
|
|
161
|
+
calendar=config.calendar,
|
|
162
|
+
nsh_round=self._nsh_round,
|
|
163
|
+
)
|
|
164
|
+
pp.set_model(pipe)
|
|
165
|
+
self._btest = pp
|
|
166
|
+
return (pp.status == 0)
|