owlplanner 2025.2.8__py3-none-any.whl → 2025.2.9__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.
- owlplanner/abcapi.py +15 -15
- owlplanner/config.py +138 -133
- owlplanner/logging.py +13 -13
- owlplanner/plan.py +622 -619
- owlplanner/progress.py +2 -2
- owlplanner/rates.py +72 -72
- owlplanner/tax2025.py +3 -3
- owlplanner/timelists.py +31 -29
- owlplanner/utils.py +9 -9
- owlplanner/version.py +1 -1
- {owlplanner-2025.2.8.dist-info → owlplanner-2025.2.9.dist-info}/METADATA +1 -1
- owlplanner-2025.2.9.dist-info/RECORD +17 -0
- owlplanner-2025.2.8.dist-info/RECORD +0 -17
- {owlplanner-2025.2.8.dist-info → owlplanner-2025.2.9.dist-info}/WHEEL +0 -0
- {owlplanner-2025.2.8.dist-info → owlplanner-2025.2.9.dist-info}/licenses/LICENSE +0 -0
owlplanner/abcapi.py
CHANGED
|
@@ -43,7 +43,7 @@ class Row(object):
|
|
|
43
43
|
"""
|
|
44
44
|
Add an element at index ``ind`` of value ``val`` to the row.
|
|
45
45
|
"""
|
|
46
|
-
assert 0 <= ind and ind < self.nvars,
|
|
46
|
+
assert 0 <= ind and ind < self.nvars, "Index %d out of range." % ind
|
|
47
47
|
self.ind.append(ind)
|
|
48
48
|
self.val.append(val)
|
|
49
49
|
|
|
@@ -96,11 +96,11 @@ class ConstraintMatrix(object):
|
|
|
96
96
|
self.lb.append(lb)
|
|
97
97
|
self.ub.append(ub)
|
|
98
98
|
if lb == ub:
|
|
99
|
-
self.key.append(
|
|
99
|
+
self.key.append("fx")
|
|
100
100
|
elif ub == np.inf:
|
|
101
|
-
self.key.append(
|
|
101
|
+
self.key.append("lo")
|
|
102
102
|
else:
|
|
103
|
-
self.key.append(
|
|
103
|
+
self.key.append("ra")
|
|
104
104
|
self.ncons += 1
|
|
105
105
|
|
|
106
106
|
def addNewRow(self, rowDic, lb, ub):
|
|
@@ -154,39 +154,39 @@ class Bounds(object):
|
|
|
154
154
|
self.integrality = []
|
|
155
155
|
|
|
156
156
|
def setBinary(self, ii):
|
|
157
|
-
assert 0 <= ii and ii < self.nvars,
|
|
157
|
+
assert 0 <= ii and ii < self.nvars, "Index %d out of range." % ii
|
|
158
158
|
self.ind.append(ii)
|
|
159
159
|
self.lb.append(0)
|
|
160
160
|
self.ub.append(1)
|
|
161
|
-
self.key.append(
|
|
161
|
+
self.key.append("ra")
|
|
162
162
|
self.integrality.append(ii)
|
|
163
163
|
|
|
164
164
|
def set0_Ub(self, ii, ub):
|
|
165
|
-
assert 0 <= ii and ii < self.nvars,
|
|
165
|
+
assert 0 <= ii and ii < self.nvars, "Index %d out of range." % ii
|
|
166
166
|
self.ind.append(ii)
|
|
167
167
|
self.lb.append(0)
|
|
168
168
|
self.ub.append(ub)
|
|
169
|
-
self.key.append(
|
|
169
|
+
self.key.append("ra")
|
|
170
170
|
|
|
171
171
|
def setLb_Inf(self, ii, lb):
|
|
172
|
-
assert 0 <= ii and ii < self.nvars,
|
|
172
|
+
assert 0 <= ii and ii < self.nvars, "Index %d out of range." % ii
|
|
173
173
|
self.ind.append(ii)
|
|
174
174
|
self.lb.append(lb)
|
|
175
175
|
self.ub.append(np.inf)
|
|
176
|
-
self.key.append(
|
|
176
|
+
self.key.append("lo")
|
|
177
177
|
|
|
178
178
|
def setRange(self, ii, lb, ub):
|
|
179
|
-
assert 0 <= ii and ii < self.nvars,
|
|
179
|
+
assert 0 <= ii and ii < self.nvars, "Index %d out of range." % ii
|
|
180
180
|
self.ind.append(ii)
|
|
181
181
|
self.lb.append(lb)
|
|
182
182
|
self.ub.append(ub)
|
|
183
183
|
if lb == ub:
|
|
184
|
-
self.key.append(
|
|
184
|
+
self.key.append("fx")
|
|
185
185
|
else:
|
|
186
|
-
self.key.append(
|
|
186
|
+
self.key.append("ra")
|
|
187
187
|
|
|
188
188
|
def keys(self):
|
|
189
|
-
keys = [
|
|
189
|
+
keys = ["lo"] * self.nvars
|
|
190
190
|
for ii in range(len(self.ind)):
|
|
191
191
|
keys[self.ind[ii]] = self.key[ii]
|
|
192
192
|
|
|
@@ -223,7 +223,7 @@ class Objective(object):
|
|
|
223
223
|
self.val = []
|
|
224
224
|
|
|
225
225
|
def setElem(self, ind, val):
|
|
226
|
-
assert 0 <= ind and ind < self.nvars,
|
|
226
|
+
assert 0 <= ind and ind < self.nvars, "Index %d out of range." % ind
|
|
227
227
|
self.ind.append(ind)
|
|
228
228
|
self.val.append(val)
|
|
229
229
|
|
owlplanner/config.py
CHANGED
|
@@ -24,108 +24,112 @@ def saveConfig(plan, file, mylog):
|
|
|
24
24
|
Save case parameters and return a dictionary containing all parameters.
|
|
25
25
|
"""
|
|
26
26
|
# np.set_printoptions(legacy='1.21')
|
|
27
|
-
accountTypes = [
|
|
27
|
+
accountTypes = ["taxable", "tax-deferred", "tax-free"]
|
|
28
28
|
|
|
29
29
|
diconf = {}
|
|
30
|
-
diconf[
|
|
30
|
+
diconf["Plan Name"] = plan._name
|
|
31
31
|
|
|
32
32
|
# Basic Info.
|
|
33
|
-
diconf[
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
33
|
+
diconf["Basic Info"] = {
|
|
34
|
+
"Status": ["unknown", "single", "married"][plan.N_i],
|
|
35
|
+
"Names": plan.inames,
|
|
36
|
+
"Birth year": plan.yobs.tolist(),
|
|
37
|
+
"Life expectancy": plan.expectancy.tolist(),
|
|
38
|
+
"Start date": plan.startDate,
|
|
39
|
+
}
|
|
39
40
|
|
|
40
41
|
# Assets.
|
|
41
|
-
diconf[
|
|
42
|
+
diconf["Assets"] = {}
|
|
42
43
|
for j in range(plan.N_j):
|
|
43
44
|
amounts = plan.beta_ij[:, j] / 1000
|
|
44
|
-
diconf[
|
|
45
|
+
diconf["Assets"]["%s savings balances" % accountTypes[j]] = amounts.tolist()
|
|
45
46
|
if plan.N_i == 2:
|
|
46
|
-
diconf[
|
|
47
|
-
diconf[
|
|
47
|
+
diconf["Assets"]["Beneficiary fractions"] = plan.phi_j.tolist()
|
|
48
|
+
diconf["Assets"]["Spousal surplus deposit fraction"] = plan.eta
|
|
48
49
|
|
|
49
50
|
# Wages and Contributions.
|
|
50
|
-
diconf[
|
|
51
|
+
diconf["Wages and Contributions"] = {"Contributions file name": plan.timeListsFileName}
|
|
51
52
|
|
|
52
53
|
# Fixed Income.
|
|
53
|
-
diconf[
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if plan.rateMethod in [
|
|
69
|
-
diconf[
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
diconf[
|
|
73
|
-
|
|
54
|
+
diconf["Fixed Income"] = {
|
|
55
|
+
"Pension amounts": (plan.pensionAmounts / 1000).tolist(),
|
|
56
|
+
"Pension ages": plan.pensionAges.tolist(),
|
|
57
|
+
"Pension indexed": plan.pensionIndexed,
|
|
58
|
+
"Social security amounts": (plan.ssecAmounts / 1000).tolist(),
|
|
59
|
+
"Social security ages": plan.ssecAges.tolist(),
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
# Rates Selection.
|
|
63
|
+
diconf["Rates Selection"] = {
|
|
64
|
+
"Heirs rate on tax-deferred estate": float(100 * plan.nu),
|
|
65
|
+
"Long-term capital gain tax rate": float(100 * plan.psi),
|
|
66
|
+
"Dividend tax rate": float(100 * plan.mu),
|
|
67
|
+
"Method": plan.rateMethod,
|
|
68
|
+
}
|
|
69
|
+
if plan.rateMethod in ["user", "stochastic"]:
|
|
70
|
+
diconf["Rates Selection"]["Values"] = (100 * plan.rateValues).tolist()
|
|
71
|
+
if plan.rateMethod in ["stochastic"]:
|
|
72
|
+
diconf["Rates Selection"]["Standard deviations"] = (100 * plan.rateStdev).tolist()
|
|
73
|
+
diconf["Rates Selection"]["Correlations"] = plan.rateCorr.tolist()
|
|
74
|
+
if plan.rateMethod in ["historical average", "historical", "histochastic"]:
|
|
75
|
+
diconf["Rates Selection"]["From"] = int(plan.rateFrm)
|
|
76
|
+
diconf["Rates Selection"]["To"] = int(plan.rateTo)
|
|
74
77
|
else:
|
|
75
|
-
diconf[
|
|
76
|
-
diconf[
|
|
77
|
-
|
|
78
|
-
# Asset
|
|
79
|
-
diconf[
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
78
|
+
diconf["Rates Selection"]["From"] = int(FROM)
|
|
79
|
+
diconf["Rates Selection"]["To"] = int(TO)
|
|
80
|
+
|
|
81
|
+
# Asset Allocation.
|
|
82
|
+
diconf["Asset Allocation"] = {
|
|
83
|
+
"Interpolation method": plan.interpMethod,
|
|
84
|
+
"Interpolation center": float(plan.interpCenter),
|
|
85
|
+
"Interpolation width": float(plan.interpWidth),
|
|
86
|
+
"Type": plan.ARCoord,
|
|
87
|
+
}
|
|
88
|
+
if plan.ARCoord == "account":
|
|
85
89
|
for accType in accountTypes:
|
|
86
|
-
diconf[
|
|
90
|
+
diconf["Asset Allocation"][accType] = plan.boundsAR[accType]
|
|
87
91
|
else:
|
|
88
|
-
diconf[
|
|
92
|
+
diconf["Asset Allocation"]["generic"] = plan.boundsAR["generic"]
|
|
89
93
|
|
|
90
94
|
# Optimization Parameters.
|
|
91
|
-
diconf[
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
if plan.spendingProfile ==
|
|
96
|
-
diconf[
|
|
97
|
-
diconf[
|
|
98
|
-
diconf[
|
|
99
|
-
|
|
100
|
-
diconf[
|
|
101
|
-
diconf[
|
|
95
|
+
diconf["Optimization Parameters"] = {
|
|
96
|
+
"Spending profile": plan.spendingProfile,
|
|
97
|
+
"Surviving spouse spending percent": int(100 * plan.chi),
|
|
98
|
+
}
|
|
99
|
+
if plan.spendingProfile == "smile":
|
|
100
|
+
diconf["Optimization Parameters"]["Smile dip"] = int(plan.smileDip)
|
|
101
|
+
diconf["Optimization Parameters"]["Smile increase"] = int(plan.smileIncrease)
|
|
102
|
+
diconf["Optimization Parameters"]["Smile delay"] = int(plan.smileDelay)
|
|
103
|
+
|
|
104
|
+
diconf["Optimization Parameters"]["Objective"] = plan.objective
|
|
105
|
+
diconf["Solver Options"] = plan.solverOptions
|
|
102
106
|
|
|
103
107
|
# Results.
|
|
104
|
-
diconf[
|
|
108
|
+
diconf["Results"] = {"Default plots": plan.defaultPlots}
|
|
105
109
|
|
|
106
110
|
if isinstance(file, str):
|
|
107
111
|
filename = file
|
|
108
|
-
if not file.endswith(
|
|
109
|
-
filename = filename +
|
|
110
|
-
if not filename.startswith(
|
|
111
|
-
filename =
|
|
112
|
+
if not file.endswith(".toml"):
|
|
113
|
+
filename = filename + ".toml"
|
|
114
|
+
if not filename.startswith("case_"):
|
|
115
|
+
filename = "case_" + filename
|
|
112
116
|
mylog.vprint("Saving plan case file as '%s'." % filename)
|
|
113
117
|
|
|
114
118
|
try:
|
|
115
|
-
with open(filename,
|
|
119
|
+
with open(filename, "w") as casefile:
|
|
116
120
|
toml.dump(diconf, casefile, encoder=toml.TomlNumpyEncoder())
|
|
117
121
|
except Exception as e:
|
|
118
|
-
raise RuntimeError(
|
|
122
|
+
raise RuntimeError("Failed to save case file %s: %s" % (filename, e))
|
|
119
123
|
elif isinstance(file, StringIO):
|
|
120
124
|
try:
|
|
121
125
|
string = toml.dumps(diconf, encoder=toml.TomlNumpyEncoder())
|
|
122
126
|
file.write(string)
|
|
123
127
|
except Exception as e:
|
|
124
|
-
raise RuntimeError(
|
|
128
|
+
raise RuntimeError("Failed to save case to StringIO: %s", e)
|
|
125
129
|
elif file is None:
|
|
126
130
|
pass
|
|
127
131
|
else:
|
|
128
|
-
raise ValueError(
|
|
132
|
+
raise ValueError("Argument %s has unknown type" % type(file))
|
|
129
133
|
|
|
130
134
|
return diconf
|
|
131
135
|
|
|
@@ -138,146 +142,147 @@ def readConfig(file, *, verbose=True, logstreams=None, readContributions=True):
|
|
|
138
142
|
"""
|
|
139
143
|
mylog = logging.Logger(verbose, logstreams)
|
|
140
144
|
|
|
141
|
-
accountTypes = [
|
|
145
|
+
accountTypes = ["taxable", "tax-deferred", "tax-free"]
|
|
142
146
|
|
|
143
|
-
dirname =
|
|
147
|
+
dirname = ""
|
|
144
148
|
if isinstance(file, str):
|
|
145
149
|
filename = file
|
|
146
150
|
dirname = os.path.dirname(filename)
|
|
147
|
-
if not filename.endswith(
|
|
148
|
-
filename = filename +
|
|
151
|
+
if not filename.endswith(".toml"):
|
|
152
|
+
filename = filename + ".toml"
|
|
149
153
|
|
|
150
154
|
mylog.vprint("Reading plan from case file '%s'." % filename)
|
|
151
155
|
|
|
152
156
|
try:
|
|
153
|
-
with open(filename,
|
|
157
|
+
with open(filename, "r") as f:
|
|
154
158
|
diconf = toml.load(f)
|
|
155
159
|
except Exception as e:
|
|
156
|
-
raise FileNotFoundError(
|
|
160
|
+
raise FileNotFoundError("File %s not found: %s" % (filename, e))
|
|
157
161
|
elif isinstance(file, BytesIO):
|
|
158
162
|
try:
|
|
159
|
-
string = file.getvalue().decode(
|
|
163
|
+
string = file.getvalue().decode("utf-8")
|
|
160
164
|
diconf = toml.loads(string)
|
|
161
165
|
except Exception as e:
|
|
162
|
-
raise RuntimeError(
|
|
166
|
+
raise RuntimeError("Cannot read from BytesIO: %s" % e)
|
|
163
167
|
elif isinstance(file, StringIO):
|
|
164
168
|
try:
|
|
165
169
|
string = file.getvalue()
|
|
166
170
|
diconf = toml.loads(string)
|
|
167
171
|
except Exception as e:
|
|
168
|
-
raise RuntimeError(
|
|
172
|
+
raise RuntimeError("Cannot read from StringIO: %s" % e)
|
|
169
173
|
else:
|
|
170
|
-
raise ValueError(
|
|
174
|
+
raise ValueError("%s not a valid type" % type(file))
|
|
171
175
|
|
|
172
176
|
# Basic Info.
|
|
173
|
-
name = diconf[
|
|
174
|
-
inames = diconf[
|
|
177
|
+
name = diconf["Plan Name"]
|
|
178
|
+
inames = diconf["Basic Info"]["Names"]
|
|
175
179
|
# status = diconf['Basic Info']['Status']
|
|
176
|
-
yobs = diconf[
|
|
177
|
-
expectancy = diconf[
|
|
178
|
-
startDate = diconf[
|
|
180
|
+
yobs = diconf["Basic Info"]["Birth year"]
|
|
181
|
+
expectancy = diconf["Basic Info"]["Life expectancy"]
|
|
182
|
+
startDate = diconf["Basic Info"]["Start date"]
|
|
179
183
|
icount = len(yobs)
|
|
180
184
|
|
|
181
|
-
mylog.vprint(
|
|
185
|
+
mylog.vprint("Plan for %d individual%s: %s." % (icount, ["", "s"][icount - 1], inames))
|
|
182
186
|
p = plan.Plan(inames, yobs, expectancy, name, startDate=startDate, verbose=True, logstreams=logstreams)
|
|
183
187
|
|
|
184
188
|
# Assets.
|
|
185
189
|
balances = {}
|
|
186
190
|
for acc in accountTypes:
|
|
187
|
-
balances[acc] = diconf[
|
|
188
|
-
p.setAccountBalances(
|
|
189
|
-
|
|
191
|
+
balances[acc] = diconf["Assets"]["%s savings balances" % acc]
|
|
192
|
+
p.setAccountBalances(
|
|
193
|
+
taxable=balances["taxable"], taxDeferred=balances["tax-deferred"], taxFree=balances["tax-free"]
|
|
194
|
+
)
|
|
190
195
|
if icount == 2:
|
|
191
|
-
phi_j = diconf[
|
|
196
|
+
phi_j = diconf["Assets"]["Beneficiary fractions"]
|
|
192
197
|
p.setBeneficiaryFractions(phi_j)
|
|
193
|
-
eta = diconf[
|
|
198
|
+
eta = diconf["Assets"]["Spousal surplus deposit fraction"]
|
|
194
199
|
p.setSpousalDepositFraction(eta)
|
|
195
200
|
|
|
196
201
|
# Wages and Contributions.
|
|
197
|
-
timeListsFileName = diconf[
|
|
198
|
-
if timeListsFileName !=
|
|
202
|
+
timeListsFileName = diconf["Wages and Contributions"]["Contributions file name"]
|
|
203
|
+
if timeListsFileName != "None":
|
|
199
204
|
if readContributions:
|
|
200
205
|
if os.path.exists(timeListsFileName):
|
|
201
206
|
myfile = timeListsFileName
|
|
202
|
-
elif dirname !=
|
|
203
|
-
myfile = dirname +
|
|
207
|
+
elif dirname != "" and os.path.exists(dirname + "/" + timeListsFileName):
|
|
208
|
+
myfile = dirname + "/" + timeListsFileName
|
|
204
209
|
else:
|
|
205
210
|
raise FileNotFoundError("File '%s' not found." % timeListsFileName)
|
|
206
211
|
p.readContributions(myfile)
|
|
207
212
|
else:
|
|
208
213
|
p.timeListsFileName = timeListsFileName
|
|
209
|
-
mylog.vprint(
|
|
214
|
+
mylog.vprint("Ignoring to read contributions file %s." % timeListsFileName)
|
|
210
215
|
|
|
211
216
|
# Fixed Income.
|
|
212
|
-
ssecAmounts = np.array(diconf[
|
|
213
|
-
ssecAges = np.array(diconf[
|
|
217
|
+
ssecAmounts = np.array(diconf["Fixed Income"]["Social security amounts"], dtype=np.float32)
|
|
218
|
+
ssecAges = np.array(diconf["Fixed Income"]["Social security ages"], dtype=np.int32)
|
|
214
219
|
p.setSocialSecurity(ssecAmounts, ssecAges)
|
|
215
|
-
pensionAmounts = np.array(diconf[
|
|
216
|
-
pensionAges = np.array(diconf[
|
|
217
|
-
pensionIndexed = diconf[
|
|
220
|
+
pensionAmounts = np.array(diconf["Fixed Income"]["Pension amounts"], dtype=np.float32)
|
|
221
|
+
pensionAges = np.array(diconf["Fixed Income"]["Pension ages"], dtype=np.int32)
|
|
222
|
+
pensionIndexed = diconf["Fixed Income"]["Pension indexed"]
|
|
218
223
|
p.setPension(pensionAmounts, pensionAges, pensionIndexed)
|
|
219
224
|
|
|
220
|
-
#
|
|
221
|
-
p.setDividendRate(float(diconf[
|
|
222
|
-
p.setLongTermCapitalTaxRate(float(diconf[
|
|
223
|
-
p.setHeirsTaxRate(float(diconf[
|
|
225
|
+
# Rates Selection.
|
|
226
|
+
p.setDividendRate(float(diconf["Rates Selection"]["Dividend tax rate"]))
|
|
227
|
+
p.setLongTermCapitalTaxRate(float(diconf["Rates Selection"]["Long-term capital gain tax rate"]))
|
|
228
|
+
p.setHeirsTaxRate(float(diconf["Rates Selection"]["Heirs rate on tax-deferred estate"]))
|
|
224
229
|
|
|
225
230
|
frm = None
|
|
226
231
|
to = None
|
|
227
232
|
rateValues = None
|
|
228
233
|
stdev = None
|
|
229
234
|
rateCorr = None
|
|
230
|
-
rateMethod = diconf[
|
|
231
|
-
if rateMethod in [
|
|
232
|
-
frm = diconf[
|
|
235
|
+
rateMethod = diconf["Rates Selection"]["Method"]
|
|
236
|
+
if rateMethod in ["historical average", "historical", "histochastic"]:
|
|
237
|
+
frm = diconf["Rates Selection"]["From"]
|
|
233
238
|
if not isinstance(frm, int):
|
|
234
239
|
frm = int(frm)
|
|
235
|
-
to = int(diconf[
|
|
240
|
+
to = int(diconf["Rates Selection"]["To"])
|
|
236
241
|
if not isinstance(to, int):
|
|
237
242
|
to = int(to)
|
|
238
|
-
if rateMethod in [
|
|
239
|
-
rateValues = np.array(diconf[
|
|
240
|
-
if rateMethod in [
|
|
241
|
-
stdev = np.array(diconf[
|
|
242
|
-
rateCorr = np.array(diconf[
|
|
243
|
+
if rateMethod in ["user", "stochastic"]:
|
|
244
|
+
rateValues = np.array(diconf["Rates Selection"]["Values"], dtype=np.float32)
|
|
245
|
+
if rateMethod in ["stochastic"]:
|
|
246
|
+
stdev = np.array(diconf["Rates Selection"]["Standard deviations"], dtype=np.float32)
|
|
247
|
+
rateCorr = np.array(diconf["Rates Selection"]["Correlations"], dtype=np.float32)
|
|
243
248
|
p.setRates(rateMethod, frm, to, rateValues, stdev, rateCorr)
|
|
244
249
|
|
|
245
250
|
# Asset Allocation.
|
|
246
251
|
boundsAR = {}
|
|
247
252
|
p.setInterpolationMethod(
|
|
248
|
-
diconf[
|
|
249
|
-
float(diconf[
|
|
250
|
-
float(diconf[
|
|
253
|
+
diconf["Asset Allocation"]["Interpolation method"],
|
|
254
|
+
float(diconf["Asset Allocation"]["Interpolation center"]),
|
|
255
|
+
float(diconf["Asset Allocation"]["Interpolation width"]),
|
|
251
256
|
)
|
|
252
|
-
allocType = diconf[
|
|
253
|
-
if allocType ==
|
|
257
|
+
allocType = diconf["Asset Allocation"]["Type"]
|
|
258
|
+
if allocType == "account":
|
|
254
259
|
for aType in accountTypes:
|
|
255
|
-
boundsAR[aType] = np.array(diconf[
|
|
260
|
+
boundsAR[aType] = np.array(diconf["Asset Allocation"][aType], dtype=np.float32)
|
|
256
261
|
|
|
257
262
|
p.setAllocationRatios(
|
|
258
263
|
allocType,
|
|
259
|
-
taxable=boundsAR[
|
|
260
|
-
taxDeferred=boundsAR[
|
|
261
|
-
taxFree=boundsAR[
|
|
264
|
+
taxable=boundsAR["taxable"],
|
|
265
|
+
taxDeferred=boundsAR["tax-deferred"],
|
|
266
|
+
taxFree=boundsAR["tax-free"],
|
|
262
267
|
)
|
|
263
|
-
elif allocType ==
|
|
264
|
-
boundsAR[
|
|
268
|
+
elif allocType == "individual" or allocType == "spouses":
|
|
269
|
+
boundsAR["generic"] = np.array(diconf["Asset Allocation"]["generic"], dtype=np.float32)
|
|
265
270
|
p.setAllocationRatios(
|
|
266
271
|
allocType,
|
|
267
|
-
generic=boundsAR[
|
|
272
|
+
generic=boundsAR["generic"],
|
|
268
273
|
)
|
|
269
274
|
else:
|
|
270
|
-
raise ValueError(
|
|
275
|
+
raise ValueError("Unknown asset allocation type %s." % allocType)
|
|
271
276
|
|
|
272
277
|
# Optimization Parameters.
|
|
273
|
-
p.objective = diconf[
|
|
274
|
-
|
|
275
|
-
profile = diconf[
|
|
276
|
-
survivor = int(diconf[
|
|
277
|
-
if profile ==
|
|
278
|
-
dip = int(diconf[
|
|
279
|
-
increase = int(diconf[
|
|
280
|
-
delay = int(diconf[
|
|
278
|
+
p.objective = diconf["Optimization Parameters"]["Objective"]
|
|
279
|
+
|
|
280
|
+
profile = diconf["Optimization Parameters"]["Spending profile"]
|
|
281
|
+
survivor = int(diconf["Optimization Parameters"]["Surviving spouse spending percent"])
|
|
282
|
+
if profile == "smile":
|
|
283
|
+
dip = int(diconf["Optimization Parameters"]["Smile dip"])
|
|
284
|
+
increase = int(diconf["Optimization Parameters"]["Smile increase"])
|
|
285
|
+
delay = int(diconf["Optimization Parameters"]["Smile delay"])
|
|
281
286
|
else:
|
|
282
287
|
dip = 15
|
|
283
288
|
increase = 12
|
|
@@ -286,9 +291,9 @@ def readConfig(file, *, verbose=True, logstreams=None, readContributions=True):
|
|
|
286
291
|
p.setSpendingProfile(profile, survivor, dip, increase, delay)
|
|
287
292
|
|
|
288
293
|
# Solver Options.
|
|
289
|
-
p.solverOptions = diconf[
|
|
294
|
+
p.solverOptions = diconf["Solver Options"]
|
|
290
295
|
|
|
291
296
|
# Results.
|
|
292
|
-
p.setDefaultPlots(diconf[
|
|
297
|
+
p.setDefaultPlots(diconf["Results"]["Default plots"])
|
|
293
298
|
|
|
294
299
|
return p
|
owlplanner/logging.py
CHANGED
|
@@ -19,15 +19,15 @@ class Logger(object):
|
|
|
19
19
|
self._prevState = self._verbose
|
|
20
20
|
if logstreams is None or logstreams == [] or len(logstreams) > 2:
|
|
21
21
|
self._logstreams = [sys.stdout, sys.stderr]
|
|
22
|
-
self.vprint(
|
|
22
|
+
self.vprint("Using stdout and stderr as stream loggers.")
|
|
23
23
|
elif len(logstreams) == 2:
|
|
24
24
|
self._logstreams = logstreams
|
|
25
|
-
self.vprint(
|
|
25
|
+
self.vprint("Using logstreams as stream loggers.")
|
|
26
26
|
elif len(logstreams) == 1:
|
|
27
|
-
self._logstreams = 2*logstreams
|
|
28
|
-
self.vprint(
|
|
27
|
+
self._logstreams = 2 * logstreams
|
|
28
|
+
self.vprint("Using logstream as stream logger.")
|
|
29
29
|
else:
|
|
30
|
-
raise ValueError(
|
|
30
|
+
raise ValueError("Log streams %r must be a list." % logstreams)
|
|
31
31
|
|
|
32
32
|
def setVerbose(self, verbose=True):
|
|
33
33
|
"""
|
|
@@ -36,7 +36,7 @@ class Logger(object):
|
|
|
36
36
|
"""
|
|
37
37
|
self._prevState = self._verbose
|
|
38
38
|
self._verbose = verbose
|
|
39
|
-
self.vprint(
|
|
39
|
+
self.vprint("Setting verbose to", verbose)
|
|
40
40
|
|
|
41
41
|
return self._prevState
|
|
42
42
|
|
|
@@ -51,9 +51,9 @@ class Logger(object):
|
|
|
51
51
|
Unconditional printing regardless of the value of the verbose variable
|
|
52
52
|
previously set.
|
|
53
53
|
"""
|
|
54
|
-
if
|
|
54
|
+
if "file" not in kwargs:
|
|
55
55
|
file = self._logstreams[0]
|
|
56
|
-
kwargs[
|
|
56
|
+
kwargs["file"] = file
|
|
57
57
|
|
|
58
58
|
print(*args, **kwargs)
|
|
59
59
|
file.flush()
|
|
@@ -71,14 +71,14 @@ class Logger(object):
|
|
|
71
71
|
Print message and exit. Use to print error messages on stderr.
|
|
72
72
|
The exit() used throws an exception in an interactive environment.
|
|
73
73
|
"""
|
|
74
|
-
if
|
|
74
|
+
if "file" not in kwargs:
|
|
75
75
|
file = self._logstreams[1]
|
|
76
|
-
kwargs[
|
|
76
|
+
kwargs["file"] = file
|
|
77
77
|
|
|
78
78
|
if self._verbose:
|
|
79
|
-
print(
|
|
80
|
-
print(
|
|
79
|
+
print("ERROR:", *args, **kwargs)
|
|
80
|
+
print("Exiting...")
|
|
81
81
|
file.flush()
|
|
82
82
|
|
|
83
|
-
raise Exception(
|
|
83
|
+
raise Exception("Fatal error.")
|
|
84
84
|
# sys.exit(-1)
|