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,352 @@
|
|
|
1
|
+
import tkinter as tk
|
|
2
|
+
from tkinter import ttk
|
|
3
|
+
import pandas as pd
|
|
4
|
+
import numpy as np
|
|
5
|
+
from copy import deepcopy
|
|
6
|
+
import webbrowser
|
|
7
|
+
import azapy as az
|
|
8
|
+
|
|
9
|
+
import azapyGUI.config as config
|
|
10
|
+
import azapyGUI.configSettings as configSettings
|
|
11
|
+
import azapyGUI.configModels as configModels
|
|
12
|
+
import azapyGUI.configTips as configTips
|
|
13
|
+
import azapyGUI.configHelps as configHelps
|
|
14
|
+
from azapyGUI.NrShares_table import NrShares_table
|
|
15
|
+
from azapyGUI.modelParametersValidation import _validDateMMDDYYYY, _validFloat
|
|
16
|
+
from azapyGUI.GetMktData import GetMktData
|
|
17
|
+
from azapyGUI.mktDataValidation import mkt_today
|
|
18
|
+
from azapyGUI.DF_Window import DF_Window
|
|
19
|
+
import azapyGUI.azHelper as azHelper
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class WeightsWindow():
|
|
23
|
+
def __init__(self, master, pname):
|
|
24
|
+
self._master = master
|
|
25
|
+
self._pname = pname
|
|
26
|
+
|
|
27
|
+
self._rep_show = []
|
|
28
|
+
self._dfw = None
|
|
29
|
+
self._dfts = None
|
|
30
|
+
self._old_param = None
|
|
31
|
+
|
|
32
|
+
self._window = tk.Toplevel()
|
|
33
|
+
self._window.geometry('320x340')
|
|
34
|
+
title = f'Rebalance - {self._pname}'
|
|
35
|
+
self._window.title(title)
|
|
36
|
+
self._window.focus_set()
|
|
37
|
+
self._window.protocol("WM_DELETE_WINDOW", self._btn_cancel)
|
|
38
|
+
self._window.columnconfigure(0, weight=1)
|
|
39
|
+
|
|
40
|
+
menubar = tk.Menu(self._window)
|
|
41
|
+
filemenu = tk.Menu(menubar, tearoff=0)
|
|
42
|
+
filemenu.add_command(label='Help', command=self._menu_help_func)
|
|
43
|
+
menubar.add_cascade(label='Help', menu=filemenu)
|
|
44
|
+
self._window.config(menu=menubar)
|
|
45
|
+
|
|
46
|
+
wwi = 300
|
|
47
|
+
row = 0
|
|
48
|
+
self._window.rowconfigure(row, weight=0)
|
|
49
|
+
frm_port = tk.Frame(master=self._window, width=wwi)
|
|
50
|
+
frm_port.grid(row=row, column=0, sticky=tk.EW, padx=10, pady=10)
|
|
51
|
+
|
|
52
|
+
row += 1
|
|
53
|
+
self._window.rowconfigure(row, weight=1)
|
|
54
|
+
frm_data = tk.Frame(master=self._window, width=wwi)
|
|
55
|
+
frm_data.grid(row=row, column=0, sticky=tk.NSEW, padx=5, pady=5)
|
|
56
|
+
|
|
57
|
+
row += 1
|
|
58
|
+
self._window.rowconfigure(row, weight=0)
|
|
59
|
+
frm_btn = tk.Frame(master=self._window, width=wwi)
|
|
60
|
+
frm_btn.grid(row=row, column=0, sticky=tk.EW, padx=5, pady=5)
|
|
61
|
+
|
|
62
|
+
# on frm_port
|
|
63
|
+
lbl = tk.Label(master=frm_port, text='Portfolio', anchor=tk.W, font=("Forte", 10), width=8)
|
|
64
|
+
lbl.pack(side=tk.LEFT)
|
|
65
|
+
|
|
66
|
+
lbl = tk.Label(master=frm_port, text=self._pname, anchor=tk.W, width=10)
|
|
67
|
+
lbl.pack(side=tk.LEFT)
|
|
68
|
+
|
|
69
|
+
self._ent_asof = tk.StringVar(master=frm_port, value='today')
|
|
70
|
+
ent = tk.Entry(master=frm_port, textvariable=self._ent_asof, validate='key', width=10)
|
|
71
|
+
ent.pack(side=tk.RIGHT)
|
|
72
|
+
ent['validatecommand'] = (ent.register(_validDateMMDDYYYY),'%S','%d','%P')
|
|
73
|
+
config.tiptil.bind(ent, configTips._reb_as_of_tip)
|
|
74
|
+
|
|
75
|
+
lbl = tk.Label(master=frm_port, text='As of', anchor=tk.W, font=("Forte", 10), width=5)
|
|
76
|
+
lbl.pack(side=tk.RIGHT)
|
|
77
|
+
|
|
78
|
+
# on frm_data
|
|
79
|
+
frm_data.rowconfigure(0, weight=1)
|
|
80
|
+
frm_data.columnconfigure(0, weight=1)
|
|
81
|
+
frm_data.columnconfigure(1, weight=0)
|
|
82
|
+
|
|
83
|
+
frm_left = tk.LabelFrame(master=frm_data, text='Reinv. Capital', font=("Forte", 10))
|
|
84
|
+
frm_left.grid(row=0, column=0, sticky=tk.NSEW)
|
|
85
|
+
|
|
86
|
+
frm_right = tk.LabelFrame(master=frm_data, text='Settings', font=("Forte", 10))
|
|
87
|
+
frm_right.grid(row=0, column=1, sticky=tk.NSEW)
|
|
88
|
+
|
|
89
|
+
# on frm_left
|
|
90
|
+
frm_left.columnconfigure(0, weight=1)
|
|
91
|
+
frm_left.columnconfigure(1, weight=1)
|
|
92
|
+
|
|
93
|
+
row = 0
|
|
94
|
+
frm_left.rowconfigure(row, weight=0)
|
|
95
|
+
lbl = tk.Label(master=frm_left, text='Cash', width=5, anchor=tk.W)
|
|
96
|
+
lbl.grid(row=row, column=0, padx=0, pady=5, sticky=tk.W)
|
|
97
|
+
|
|
98
|
+
self._ent_cash = tk.DoubleVar(master=frm_left,
|
|
99
|
+
value=configSettings.MasterApplicationSettings['capital'])
|
|
100
|
+
ent = tk.Entry(master=frm_left, textvariable=self._ent_cash, validate='key', width=8)
|
|
101
|
+
ent.grid(row=row, column=1, padx=0, pady=5, sticky=tk.W, ipadx=3)
|
|
102
|
+
ent['validatecommand'] = (ent.register(_validFloat),'%S','%d','%P')
|
|
103
|
+
config.tiptil.bind(ent, configTips._reb_cash_tip)
|
|
104
|
+
|
|
105
|
+
row += 1
|
|
106
|
+
frm_left.rowconfigure(row, weight=0)
|
|
107
|
+
lbl = tk.Label(master=frm_left, text='Symbol', width=6, anchor=tk.W)
|
|
108
|
+
lbl.grid(row=row, column=0, padx=0, pady=5, sticky=tk.W)
|
|
109
|
+
|
|
110
|
+
lbl = tk.Label(master=frm_left, text='Nr. Shares', width=8, anchor=tk.W)
|
|
111
|
+
lbl.grid(row=row, column=1, padx=0, pady=5, sticky=tk.W)
|
|
112
|
+
|
|
113
|
+
row += 1
|
|
114
|
+
frm_left.rowconfigure(row, weight=1)
|
|
115
|
+
self._symbols = config.PortDataDict[self._pname].symbols
|
|
116
|
+
data = pd.Series(0, index=self._symbols)
|
|
117
|
+
frm_sls = tk.Frame(master=frm_left)
|
|
118
|
+
frm_sls.grid(row=row, column=0, columnspan=2, padx=0, pady=5, sticky=tk.NSEW)
|
|
119
|
+
frm_sls.rowconfigure(0, weight=1)
|
|
120
|
+
frm_sls.columnconfigure(0, weight=1)
|
|
121
|
+
self._frm_symb = NrShares_table(master=frm_sls, data=data)
|
|
122
|
+
self._frm_symb.grid(row=0, column=0, sticky=tk.NSEW)
|
|
123
|
+
frm_sls.update()
|
|
124
|
+
|
|
125
|
+
row += 1
|
|
126
|
+
self._ent_nsh_round = tk.BooleanVar(master=frm_left, value=True)
|
|
127
|
+
cbx = tk.Checkbutton(frm_left, text='NSh Int', variable=self._ent_nsh_round,
|
|
128
|
+
onvalue=True, offvalue=False, width=5, anchor=tk.W)
|
|
129
|
+
cbx.grid(row=row, column=0, padx=0, pady=5, sticky=tk.W)
|
|
130
|
+
config.tiptil.bind(cbx, configTips._reb_nsh_round_tip)
|
|
131
|
+
|
|
132
|
+
btn = tk.Button(master=frm_left, text='Refresh', width=8, command=self._btn_refresh_func)
|
|
133
|
+
btn.grid(row=row, column=1, padx=2, pady=5, sticky=tk.E)
|
|
134
|
+
|
|
135
|
+
# on frm_right
|
|
136
|
+
frm_right.columnconfigure(0, weight=0)
|
|
137
|
+
frm_right.columnconfigure(1, weight=0)
|
|
138
|
+
|
|
139
|
+
row = 0
|
|
140
|
+
lbl = tk.Label(master=frm_right, text="Provider", anchor=tk.W, width=6)
|
|
141
|
+
lbl.grid(row=row, column=0, padx=0, pady=5, sticky=tk.W)
|
|
142
|
+
|
|
143
|
+
pval = list(configSettings.MasterApplicationSettings["Provider"].keys())
|
|
144
|
+
self._ent_provider = ttk.Combobox(master=frm_right, width=12)
|
|
145
|
+
self._ent_provider["values"] = pval
|
|
146
|
+
self._ent_provider.current(0)
|
|
147
|
+
self._ent_provider.grid(row=row, column=1, pady=5, padx=5, sticky=tk.EW)
|
|
148
|
+
config.tiptil.bind(self._ent_provider, configTips._reb_provider_tip)
|
|
149
|
+
|
|
150
|
+
row += 1
|
|
151
|
+
lbl = tk.Label(master=frm_right, text="Force", anchor=tk.W, width=6)
|
|
152
|
+
lbl.grid(row=row, column=0, padx=0, pady=5, sticky=tk.W)
|
|
153
|
+
|
|
154
|
+
self._ent_force = tk.BooleanVar(value=configSettings.MasterApplicationSettings['force'])
|
|
155
|
+
chk_force = tk.Checkbutton(master=frm_right, onvalue=True, offvalue=False, variable=self._ent_force)
|
|
156
|
+
chk_force.grid(row=row, column=1, padx=5, pady=5, sticky=tk.W)
|
|
157
|
+
config.tiptil.bind(chk_force, configTips._reb_force_tip)
|
|
158
|
+
|
|
159
|
+
row += 1
|
|
160
|
+
btn = tk.Button(master=frm_right, text='Weights', width=10, command=self._btn_weights_func)
|
|
161
|
+
btn.grid(row=row, column=1, padx=5, pady=5, sticky=tk.W)
|
|
162
|
+
|
|
163
|
+
# on frm_btn
|
|
164
|
+
frm_btn.rowconfigure(0, weight=1)
|
|
165
|
+
frm_btn.columnconfigure(0, weight=1)
|
|
166
|
+
frm_btn.columnconfigure(1, weight=1)
|
|
167
|
+
|
|
168
|
+
btn = tk.Button(master=frm_btn, text='Close', width=10, command=self._btn_cancel)
|
|
169
|
+
btn.grid(row=0, column=0, sticky=tk.W)
|
|
170
|
+
|
|
171
|
+
btn = tk.Button(master=frm_btn, text="Trading Sheet", width=14, command=self._btn_traiding_sheet_func)
|
|
172
|
+
btn.grid(row=0, column=1, sticky=tk.E)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _mktdata_info(self):
|
|
176
|
+
# edate
|
|
177
|
+
asof = self._ent_asof.get()
|
|
178
|
+
edate = pd.Timestamp(asof).normalize().tz_localize(None)
|
|
179
|
+
edate = config._bday.rollback(edate).normalize().tz_localize(None)
|
|
180
|
+
mkttoday = mkt_today()
|
|
181
|
+
self._edate = edate if edate < mkttoday else mkttoday
|
|
182
|
+
|
|
183
|
+
# sdate
|
|
184
|
+
model = config.PortDataDict[self._pname]
|
|
185
|
+
hlength = 0
|
|
186
|
+
for mo in model.selectors.values():
|
|
187
|
+
if 'hlength' in mo['param'].keys():
|
|
188
|
+
hlength = max(hlength, mo['param']['hlength'])
|
|
189
|
+
for mo in model.optimizer.values():
|
|
190
|
+
if 'hlength' in mo['param'].keys():
|
|
191
|
+
hlength = max(hlength, mo['param']['hlength'])
|
|
192
|
+
sdate = self._edate - pd.DateOffset(days=np.ceil(hlength * 365.25) + 10)
|
|
193
|
+
self._sdate = config._bday.rollback(sdate).normalize().tz_localize(None)
|
|
194
|
+
|
|
195
|
+
# provider, force
|
|
196
|
+
self._provider = self._ent_provider.get()
|
|
197
|
+
self._force = self._ent_force.get()
|
|
198
|
+
|
|
199
|
+
if self._old_param is not None:
|
|
200
|
+
if [self._edate, self._provider, self._force] == self._old_param:
|
|
201
|
+
return False
|
|
202
|
+
if (self._dfw is not None) and self._dfw.winfo_exists():
|
|
203
|
+
self._dfw.destroy()
|
|
204
|
+
self._old_param = [self._edate, self._provider, self._force]
|
|
205
|
+
return True
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _collectMktData(self):
|
|
209
|
+
gmd = GetMktData(self._provider, self._force)
|
|
210
|
+
gmd.getMkTDataSymb(self._symbols, self._sdate, self._edate)
|
|
211
|
+
|
|
212
|
+
if len(gmd.errorsymb) == 0: return True
|
|
213
|
+
|
|
214
|
+
msg = (f"The following symbols, {gmd.errorsymb},\n"
|
|
215
|
+
f"cannot be retrieved from the provider {self._provider}\n"
|
|
216
|
+
f"Abort the computation.")
|
|
217
|
+
tk.messagebox.showwarning("Warning", message=msg, parent=self._window)
|
|
218
|
+
return False
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def _computeWeights(self):
|
|
222
|
+
portdata = config.PortDataDict[self._pname]
|
|
223
|
+
mktdata = {symb: config.MktDataDict[symb].get_mktdata(self._edate).copy() for symb in portdata.symbols}
|
|
224
|
+
|
|
225
|
+
optname = list(portdata.optimizer.keys())[0]
|
|
226
|
+
if configModels.get_comptype(optname) == 'standalone':
|
|
227
|
+
fmname = configModels.get_model_family(optname)
|
|
228
|
+
mname = configModels.portfolio_model_family[fmname][optname]['azapy'][0]
|
|
229
|
+
eff_param = deepcopy(portdata.optimizer[optname]['param'])
|
|
230
|
+
eff_param['sdate'] = self._sdate
|
|
231
|
+
eff_param['edate'] = self._edate
|
|
232
|
+
exec(f"self._pipe = {mname}(**eff_param)")
|
|
233
|
+
else:
|
|
234
|
+
model = []
|
|
235
|
+
sel_list = [None] * len(portdata.selectors.keys())
|
|
236
|
+
for vv in portdata.selectors.values():
|
|
237
|
+
sel_list[vv['index']] = vv
|
|
238
|
+
for vv in sel_list:
|
|
239
|
+
mname = configModels.selector_models[vv['name']]['azapy']
|
|
240
|
+
oo = getattr(az, mname)
|
|
241
|
+
moo = oo(**vv['param'])
|
|
242
|
+
model.append(moo)
|
|
243
|
+
|
|
244
|
+
for vv in portdata.optimizer.values():
|
|
245
|
+
fmname = configModels.get_model_family(vv['name'])
|
|
246
|
+
mname = configModels.portfolio_model_family[fmname][vv['name']]['azapy']
|
|
247
|
+
if mname == 'EWPEngine':
|
|
248
|
+
model.append('EWP')
|
|
249
|
+
else:
|
|
250
|
+
oo = getattr(az, mname)
|
|
251
|
+
moo = oo(**vv['param'])
|
|
252
|
+
model.append(moo)
|
|
253
|
+
|
|
254
|
+
self._pipe = az.ModelPipeline(model)
|
|
255
|
+
|
|
256
|
+
self._weights = self._pipe.getWeights(mktdata, verbose=False)
|
|
257
|
+
if self._pipe.status == 0: return True
|
|
258
|
+
|
|
259
|
+
msg = 'Numerical errors in weights computation. Abort!'
|
|
260
|
+
tk.messagebox.showwarning("Warning", message=msg, parent=self._window)
|
|
261
|
+
return False
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def _btn_refresh_func(self):
|
|
265
|
+
self._frm_symb.sort()
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def _btn_weights_func(self):
|
|
269
|
+
if self._mktdata_info():
|
|
270
|
+
if not self._collectMktData():
|
|
271
|
+
return
|
|
272
|
+
if not self._computeWeights():
|
|
273
|
+
return
|
|
274
|
+
|
|
275
|
+
df = (pd.DataFrame(self._weights) * 100).round(2)
|
|
276
|
+
df.columns = ['weight']
|
|
277
|
+
df.index.name = 'symbol'
|
|
278
|
+
df.sort_values('weight', ascending=False, inplace=True)
|
|
279
|
+
if '_CASH_' in df.index:
|
|
280
|
+
ix = list(df.index)
|
|
281
|
+
ix.remove('_CASH_')
|
|
282
|
+
df = df.reindex(['_CASH_'] + ix)
|
|
283
|
+
title = self._pname + ' weights as of ' + self._edate.strftime('%Y%m%d')
|
|
284
|
+
geometry = '100x200'
|
|
285
|
+
fname = self._pname + '_ww_' + self._edate.strftime('%Y%m%d')
|
|
286
|
+
|
|
287
|
+
if (self._dfw is not None) and self._dfw.winfo_exists():
|
|
288
|
+
self._dfw.destroy()
|
|
289
|
+
self._dfw = DF_Window(master=self._window,
|
|
290
|
+
df=df,
|
|
291
|
+
title=title,
|
|
292
|
+
geometry=geometry,
|
|
293
|
+
fname=fname)
|
|
294
|
+
self._rep_show.append(self._dfw)
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def _ww_destroy(self):
|
|
298
|
+
for ww in self._rep_show:
|
|
299
|
+
if ww.winfo_exists(): ww.destroy()
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def _btn_cancel(self):
|
|
303
|
+
self._ww_destroy()
|
|
304
|
+
config.PortDataDict[self._pname].setActive(False)
|
|
305
|
+
config.appPortfolioFrame.refresh()
|
|
306
|
+
self._window.destroy()
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def _capital_info(self):
|
|
310
|
+
self._cash = self._ent_cash.get()
|
|
311
|
+
self._nshares = self._frm_symb.get_data()
|
|
312
|
+
self._nsh_round = self._ent_nsh_round.get()
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def _btn_traiding_sheet_func(self):
|
|
316
|
+
if self._mktdata_info():
|
|
317
|
+
if not self._collectMktData():
|
|
318
|
+
return
|
|
319
|
+
if not self._computeWeights():
|
|
320
|
+
return
|
|
321
|
+
|
|
322
|
+
self._capital_info()
|
|
323
|
+
tsheet = self._pipe.getPositions(nshares=self._nshares, cash=self._cash,
|
|
324
|
+
nsh_round=self._nsh_round, verbose=False)
|
|
325
|
+
cap = (tsheet['old_nsh'] * tsheet['prices']).sum()
|
|
326
|
+
if cap <= 0:
|
|
327
|
+
msg = "Total calipta, i.e. cash + cash values of shares, must be >0."
|
|
328
|
+
tk.messagebox.showwarning("Woarning", message=msg, parent=self._window)
|
|
329
|
+
return
|
|
330
|
+
tsheet['Allocation'] = (tsheet['new_nsh'] * tsheet['prices']).round(2)
|
|
331
|
+
tsheet.rename(columns={'old_nsh': 'Initial', 'new_nsh': 'Final',
|
|
332
|
+
'diff_nsh': 'Delta', 'weights': 'Weights',
|
|
333
|
+
'prices': 'Prices'}, inplace=True)
|
|
334
|
+
tsheet.index.name = 'Symbols'
|
|
335
|
+
title = 'Trading Sheet'
|
|
336
|
+
geometry = '400x200'
|
|
337
|
+
fname = self._pname + '_TS_' + self._edate.strftime('%Y%m%d')
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
if (self._dfts is not None) and self._dfts.winfo_exists():
|
|
341
|
+
self._dfts.destroy()
|
|
342
|
+
self._dfts = DF_Window(master=self._window,
|
|
343
|
+
df=tsheet,
|
|
344
|
+
title=title,
|
|
345
|
+
geometry=geometry,
|
|
346
|
+
fname=fname)
|
|
347
|
+
self._rep_show.append(self._dfts)
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def _menu_help_func(self):
|
|
351
|
+
webbrowser.open_new_tab(configHelps._Rebalance_panel_help)
|
|
352
|
+
|
azapyGUI/__init__.py
ADDED
azapyGUI/azHelper.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
import azapy as az
|
|
3
|
+
|
|
4
|
+
import azapyGUI.config as config
|
|
5
|
+
|
|
6
|
+
# need to mode this to azapy
|
|
7
|
+
def schedule_date(sdate, edate, freq='Q', fixoffset=-1, **kwargs):
|
|
8
|
+
qdate = pd.Timestamp(edate).to_period(freq).end_time.normalize().tz_localize(None)
|
|
9
|
+
noffset = -pd.bdate_range(edate, qdate, freq=config._bday).size + 1 - fixoffset
|
|
10
|
+
return az.schedule_simple(sdate=sdate, edate=edate, freq=freq,
|
|
11
|
+
noffset=noffset, fixoffset=fixoffset,
|
|
12
|
+
calendar=config.calendar)
|
|
13
|
+
|
|
14
|
+
# need to incorporate into azapy
|
|
15
|
+
def UniversalEngineWrap(mktdata=None, colname='adjusted', freq='M', name='Universal',
|
|
16
|
+
schedule=None, sdate=None, edate='today', noffset=0,
|
|
17
|
+
fixoffset=-1, hlength=12, dirichlet_alpha=None,
|
|
18
|
+
variance_reduction=True, nr_batches=16, mc_paths=100,
|
|
19
|
+
mc_seed=42, verbose=False):
|
|
20
|
+
|
|
21
|
+
fixing_schedule = schedule_date(sdate, edate, freq=freq, fixoffset=fixoffset)
|
|
22
|
+
return az.UniversalEngine(mktdata=mktdata, colname=colname, freq=freq,
|
|
23
|
+
schedule=fixing_schedule,
|
|
24
|
+
sdate=sdate, edate=edate, noffset=noffset, fixoffset=fixoffset, hlength=hlength,
|
|
25
|
+
dirichlet_alpha=dirichlet_alpha, variance_reduction=variance_reduction,
|
|
26
|
+
nr_batches=nr_batches, mc_paths=mc_paths, mc_seed=mc_seed,
|
|
27
|
+
verbose=verbose)
|
azapyGUI/azapyApp.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import tkinter as tk
|
|
2
|
+
import pandas as pd
|
|
3
|
+
import base64
|
|
4
|
+
import azapy as az
|
|
5
|
+
|
|
6
|
+
import azapyGUI.config as config
|
|
7
|
+
import azapyGUI.configSettings as configSettings
|
|
8
|
+
from azapyGUI.serviceMasterUserConfig import _readMasterUserConfig
|
|
9
|
+
from azapyGUI.ViewTip import ViewTip
|
|
10
|
+
from azapyGUI.PortfolioFrame import PortfolioFrame
|
|
11
|
+
from azapyGUI.MktDataFrame import MktDataFrame
|
|
12
|
+
from azapyGUI.MenuApp import MenuApp
|
|
13
|
+
|
|
14
|
+
class app:
|
|
15
|
+
def __init__(self):
|
|
16
|
+
self._root = tk.Tk()
|
|
17
|
+
self._on_start()
|
|
18
|
+
self._root.iconphoto(True, config.photo)
|
|
19
|
+
self._root.title("azapy")
|
|
20
|
+
self._root.rowconfigure(0, minsize=300, weight=1)
|
|
21
|
+
self._root.columnconfigure(0, minsize=100, weight=1)
|
|
22
|
+
self._root.protocol("WM_DELETE_WINDOW", self._on_exit)
|
|
23
|
+
|
|
24
|
+
MenuApp(self._root)
|
|
25
|
+
config.appPortfolioFrame = PortfolioFrame(self._root, text="Active Projects", font=("Forte", 10))
|
|
26
|
+
config.appPortfolioFrame.grid(row=0, column=0, padx=5, pady=5, sticky=tk.NSEW)
|
|
27
|
+
config.appMktDataFrame = MktDataFrame(self._root, text="Active Market Data", font=("Forte", 10))
|
|
28
|
+
config.appMktDataFrame.grid(row=0, column=1, padx=5, pady=5, sticky=tk.NSEW)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def run(self):
|
|
32
|
+
self._root.mainloop()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _on_start(self):
|
|
36
|
+
configSettings.MasterApplicationSettings = _readMasterUserConfig()
|
|
37
|
+
config.MktDataDict = {}
|
|
38
|
+
config.PortDataDict = {}
|
|
39
|
+
config.count_SymbTableEntry = -1
|
|
40
|
+
config.calendar = az.NYSEgen()
|
|
41
|
+
config._bday = pd.offsets.CustomBusinessDay(calendar=config.calendar)
|
|
42
|
+
img = base64.b64decode(config.iconimgdata)
|
|
43
|
+
config.photo = tk.PhotoImage(data=img, master=self._root)
|
|
44
|
+
config.tiptil = ViewTip(self._root)
|
|
45
|
+
config.tiptil.turned(on=configSettings.MasterApplicationSettings["ShowTips"])
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _on_exit(self):
|
|
49
|
+
port_path = configSettings.MasterApplicationSettings["UserPortfolioDirectory"]
|
|
50
|
+
for kk in config.PortDataDict.keys():
|
|
51
|
+
if not config.PortDataDict[kk].saved:
|
|
52
|
+
msg = "There are unsaved portfolios.\nDo you want to save them?"
|
|
53
|
+
res = tk.messagebox.askquestion("Quit", message=msg, parent=self._root)
|
|
54
|
+
if res == 'no':
|
|
55
|
+
self._root.destroy()
|
|
56
|
+
return
|
|
57
|
+
break
|
|
58
|
+
|
|
59
|
+
for kk, port in config.PortDataDict.items():
|
|
60
|
+
if port.saved: continue
|
|
61
|
+
filepath = tk.filedialog.asksaveasfilename(
|
|
62
|
+
defaultextension=".json",
|
|
63
|
+
filetypes=[("Json Files", "*.json")],
|
|
64
|
+
initialdir=port_path,
|
|
65
|
+
initialfile=port.name,
|
|
66
|
+
parent=self._root,
|
|
67
|
+
)
|
|
68
|
+
if filepath:
|
|
69
|
+
port.saved = True
|
|
70
|
+
config.PortDataDict[port.name].writeFile(filepath)
|
|
71
|
+
|
|
72
|
+
self._destroy_all_windows()
|
|
73
|
+
self._root.destroy()
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _destroy_all_windows(self):
|
|
77
|
+
for widget in self._root.winfo_children():
|
|
78
|
+
if isinstance(widget, tk.Toplevel):
|
|
79
|
+
widget.destroy()
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def start():
|
|
83
|
+
"""Starts azapyGUI application"""
|
|
84
|
+
app().run()
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
#==============================================================================
|
|
88
|
+
# if __name__ == "__main__":
|
|
89
|
+
# start()
|
azapyGUI/config.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
__version__ = '0.0.1'
|
|
2
|
+
|
|
3
|
+
MktDataDict = {}
|
|
4
|
+
PortDataDict = {}
|
|
5
|
+
|
|
6
|
+
appPortfolioFrame = None
|
|
7
|
+
appMktDataFrame = None
|
|
8
|
+
|
|
9
|
+
count_SymbTableEntry = -1
|
|
10
|
+
|
|
11
|
+
calendar = None
|
|
12
|
+
_bday = None
|
|
13
|
+
|
|
14
|
+
_months_name = ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
|
15
|
+
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
|
|
16
|
+
|
|
17
|
+
iconimgdata = b'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAIGNIUk0AAHomA' \
|
|
18
|
+
b'ACAhAAA+gAAAIDoAAB1MAAA6mAAADqYAAAXcJy6UTwAAAAwUExURQEAAAABAQ' \
|
|
19
|
+
b'ABAAEAAQEBAAAAAAAAAQEBAf8BAP4AAP4BAP8AAP8AAf8BAf4AAf///4idQz8' \
|
|
20
|
+
b'AAAAIdFJOUwAAAAAAAAAAt+dSoQAAAAFiS0dEDxi6ANkAAAAHdElNRQfoAhAW' \
|
|
21
|
+
b'LDbyOhZFAAAAAW9yTlQBz6J3mgAAAJtJREFUCNcBkABv/wABIgMjQAA1VQBYm' \
|
|
22
|
+
b'pq7y5zLsAAlVmVSVhUJlQBVIVUgVVWSgwBlUlUlBlhVkgAFRUVRZsVihgBVVV' \
|
|
23
|
+
b'VlKGABhQBlVFIA1VVVlQAyVlUJJWUltQBVVWLry5y7xgBWNWklViUlxgAlUcV' \
|
|
24
|
+
b'lICcilQAmC1VlVQYWsgBlxlRSUgJWswBcy7iIm4vN5QBgYFYgMmJSIK3tLxlm' \
|
|
25
|
+
b'qSAlAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDI0LTAyLTE2VDIyOjQ0OjUzKzAwO' \
|
|
26
|
+
b'jAwsbbnQwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAyNC0wMi0xNlQyMjo0NDo1My' \
|
|
27
|
+
b'swMDowMMDrX/8AAAAodEVYdGRhdGU6dGltZXN0YW1wADIwMjQtMDItMTZUMjI' \
|
|
28
|
+
b'6NDQ6NTQrMDA6MDBSWUCuAAAAAElFTkSuQmCC'
|
|
29
|
+
|
|
30
|
+
iconphoto = None
|
|
31
|
+
|
|
32
|
+
tiptil = None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
azapyGUI/configHelps.py
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
_Dual_Momentum_model_help = \
|
|
2
|
+
'https://azapygui.readthedocs.io/en/latest/Model_Panels.html#dual-momentum-selector'
|
|
3
|
+
|
|
4
|
+
_Correlation_Clustering_model_help = \
|
|
5
|
+
'https://azapygui.readthedocs.io/en/latest/Model_Panels.html#correlation-clustering-selector'
|
|
6
|
+
|
|
7
|
+
_mCVaR_model_help = \
|
|
8
|
+
'https://azapygui.readthedocs.io/en/latest/Model_Panels.html#mcvar-based-optimizers'
|
|
9
|
+
|
|
10
|
+
_mSMCR_model_help = \
|
|
11
|
+
'https://azapygui.readthedocs.io/en/latest/Model_Panels.html#msmcr-based-optimizers'
|
|
12
|
+
|
|
13
|
+
_mEVaR_model_help = \
|
|
14
|
+
'https://azapygui.readthedocs.io/en/latest/Model_Panels.html#mevar-based-optimizers'
|
|
15
|
+
|
|
16
|
+
_mMAD_model_help = \
|
|
17
|
+
'https://azapygui.readthedocs.io/en/latest/Model_Panels.html#mmad-based-optimizers'
|
|
18
|
+
|
|
19
|
+
_mLSD_model_help = \
|
|
20
|
+
'https://azapygui.readthedocs.io/en/latest/Model_Panels.html#mlsd-based-optimizers'
|
|
21
|
+
|
|
22
|
+
_mBTAD_model_help = \
|
|
23
|
+
'https://azapygui.readthedocs.io/en/latest/Model_Panels.html#mbtad-based-optimizers'
|
|
24
|
+
|
|
25
|
+
_mBTSD_model_help = \
|
|
26
|
+
'https://azapygui.readthedocs.io/en/latest/Model_Panels.html#mbtsd-based-optimizers'
|
|
27
|
+
|
|
28
|
+
_GINI_model_help = \
|
|
29
|
+
'https://azapygui.readthedocs.io/en/latest/Model_Panels.html#gini-based-optimizers'
|
|
30
|
+
|
|
31
|
+
_SD_model_help = \
|
|
32
|
+
'https://azapygui.readthedocs.io/en/latest/Model_Panels.html#standard-deviation-based-optimizers'
|
|
33
|
+
|
|
34
|
+
_MV_model_help = \
|
|
35
|
+
'https://azapygui.readthedocs.io/en/latest/Model_Panels.html#mean-variance-based-optimizers'
|
|
36
|
+
|
|
37
|
+
_Equal_Weighted_model_help = \
|
|
38
|
+
'https://azapygui.readthedocs.io/en/latest/Model_Panels.html#equal-weighted-portfolios'
|
|
39
|
+
|
|
40
|
+
_Inverse_Volatility_model_help = \
|
|
41
|
+
'https://azapygui.readthedocs.io/en/latest/Model_Panels.html#inverse-volatility-portfolios'
|
|
42
|
+
|
|
43
|
+
_Inverse_Variance_model_help = \
|
|
44
|
+
'https://azapygui.readthedocs.io/en/latest/Model_Panels.html#inverse-variance-portfolios'
|
|
45
|
+
|
|
46
|
+
_Inverse_Drawdown_model_help = \
|
|
47
|
+
'https://azapygui.readthedocs.io/en/latest/Model_Panels.html#inverse-drawdown-portfolios'
|
|
48
|
+
|
|
49
|
+
_Kelly_model_help = \
|
|
50
|
+
'https://azapygui.readthedocs.io/en/latest/Model_Panels.html#kellys-optimal-portfolios'
|
|
51
|
+
|
|
52
|
+
_Universal_model_help = \
|
|
53
|
+
'https://azapygui.readthedocs.io/en/latest/Model_Panels.html#universal-portfolios'
|
|
54
|
+
|
|
55
|
+
_Portfolio_Edit_help = \
|
|
56
|
+
'https://azapygui.readthedocs.io/en/latest/Portfolio_Edit_Panel.html'
|
|
57
|
+
|
|
58
|
+
_Settings_panel_help = \
|
|
59
|
+
'https://azapygui.readthedocs.io/en/latest/Settings_Panel.html'
|
|
60
|
+
|
|
61
|
+
_Backtest_panel_help = \
|
|
62
|
+
'https://azapygui.readthedocs.io/en/latest/Backtest_Panel.html'
|
|
63
|
+
|
|
64
|
+
_Rebalance_panel_help = \
|
|
65
|
+
'https://azapygui.readthedocs.io/en/latest/Rebalance_Panel.html'
|
|
66
|
+
|
|
67
|
+
_Statistics_panel_help = \
|
|
68
|
+
'https://azapygui.readthedocs.io/en/latest/Statistics_Panel.html'
|
|
69
|
+
|
|
70
|
+
_About_help = \
|
|
71
|
+
"""
|
|
72
|
+
azapyGUI is a graphical interface for
|
|
73
|
+
azapy portfolio optimizations library\n
|
|
74
|
+
Distributed under GNU General Public License v3 (GPLv3)\n
|
|
75
|
+
Author: Mircea Marinescu\n
|
|
76
|
+
azapyGUI version: {vgui}
|
|
77
|
+
azapy version: {v}
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
_Quick_Start_help = \
|
|
81
|
+
'https://azapygui.readthedocs.io/en/latest/Quick_Start.html'
|
|
82
|
+
|
|
83
|
+
_index_help = \
|
|
84
|
+
'https://azapygui.readthedocs.io/en/latest/index.html#'
|