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,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()
|
azapyGUI/PortDataNode.py
ADDED
|
@@ -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()
|