rangekeeper 0.1.0__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.
- rangekeeper/__init__.py +40 -0
- rangekeeper/api.py +88 -0
- rangekeeper/distribution.py +269 -0
- rangekeeper/dynamics/__init__.py +6 -0
- rangekeeper/dynamics/black_swan.py +77 -0
- rangekeeper/dynamics/cyclicality.py +414 -0
- rangekeeper/dynamics/market.py +328 -0
- rangekeeper/dynamics/noise.py +34 -0
- rangekeeper/dynamics/trend.py +79 -0
- rangekeeper/dynamics/volatility.py +140 -0
- rangekeeper/extrapolation.py +100 -0
- rangekeeper/flux.py +733 -0
- rangekeeper/graph.py +144 -0
- rangekeeper/measure.py +138 -0
- rangekeeper/models/__init__.py +5 -0
- rangekeeper/models/deterministic.py +143 -0
- rangekeeper/models/flexible.py +177 -0
- rangekeeper/models/linear.py +144 -0
- rangekeeper/models/linear_graph.py +171 -0
- rangekeeper/models/probabilistic.py +157 -0
- rangekeeper/periodicity.py +236 -0
- rangekeeper/policy.py +20 -0
- rangekeeper/projection.py +248 -0
- rangekeeper/segmentation.py +220 -0
- rangekeeper/setup.py +14 -0
- rangekeeper/space.py +42 -0
- rangekeeper/span.py +226 -0
- rangekeeper-0.1.0.dist-info/LICENSE +373 -0
- rangekeeper-0.1.0.dist-info/METADATA +32 -0
- rangekeeper-0.1.0.dist-info/RECORD +31 -0
- rangekeeper-0.1.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import rangekeeper as rk
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
# Base Model:
|
|
5
|
+
class Model:
|
|
6
|
+
def __init__(
|
|
7
|
+
self,
|
|
8
|
+
params: dict):
|
|
9
|
+
# Phasing:
|
|
10
|
+
self.acquisition_span = rk.span.Span.from_num_periods(
|
|
11
|
+
name='Acquisition',
|
|
12
|
+
date=params['start_date'],
|
|
13
|
+
period_type=rk.periodicity.Type.YEAR,
|
|
14
|
+
num_periods=1)
|
|
15
|
+
|
|
16
|
+
self.operation_span = rk.span.Span.from_num_periods(
|
|
17
|
+
name='Operation',
|
|
18
|
+
date=rk.periodicity.offset_date(
|
|
19
|
+
date=self.acquisition_span.end_date,
|
|
20
|
+
period_type=rk.periodicity.Type.DAY,
|
|
21
|
+
num_periods=1),
|
|
22
|
+
period_type=rk.periodicity.Type.YEAR,
|
|
23
|
+
num_periods=params['num_periods'])
|
|
24
|
+
|
|
25
|
+
self.disposition_span = rk.span.Span.from_num_periods(
|
|
26
|
+
name='Disposition',
|
|
27
|
+
date=rk.periodicity.offset_date(
|
|
28
|
+
date=self.acquisition_span.start_date,
|
|
29
|
+
period_type=rk.periodicity.Type.YEAR,
|
|
30
|
+
num_periods=params['num_periods']),
|
|
31
|
+
period_type=rk.periodicity.Type.YEAR,
|
|
32
|
+
num_periods=1)
|
|
33
|
+
|
|
34
|
+
self.projection_span = rk.span.Span.from_num_periods(
|
|
35
|
+
name='Projection',
|
|
36
|
+
date=rk.periodicity.offset_date(
|
|
37
|
+
date=self.operation_span.end_date,
|
|
38
|
+
period_type=rk.periodicity.Type.DAY,
|
|
39
|
+
num_periods=1),
|
|
40
|
+
period_type=rk.periodicity.Type.YEAR,
|
|
41
|
+
num_periods=1)
|
|
42
|
+
|
|
43
|
+
self.noi_calc_span = rk.span.Span.merge(
|
|
44
|
+
name='NOI Calculation Span',
|
|
45
|
+
spans=[self.operation_span, self.projection_span])
|
|
46
|
+
|
|
47
|
+
# Factors:
|
|
48
|
+
self.escalation = rk.extrapolation.Compounding(rate=params['growth_rate'])
|
|
49
|
+
|
|
50
|
+
# Cashflows:
|
|
51
|
+
# Potential Gross Income
|
|
52
|
+
self.pgi = rk.flux.Flow.from_projection(
|
|
53
|
+
name='Potential Gross Income',
|
|
54
|
+
value=params['initial_pgi'],
|
|
55
|
+
proj=rk.projection.Extrapolation(
|
|
56
|
+
form=self.escalation,
|
|
57
|
+
sequence=self.noi_calc_span.to_index(period_type=params['period_type'])),
|
|
58
|
+
units=params['units'])
|
|
59
|
+
|
|
60
|
+
# Vacancy Allowance
|
|
61
|
+
# This should be displayed as a row in a table with xxxx, xxx,....
|
|
62
|
+
self.vacancy = rk.flux.Flow.from_periods(
|
|
63
|
+
name='Vacancy Allowance',
|
|
64
|
+
index=self.noi_calc_span.to_index(period_type=params['period_type']),
|
|
65
|
+
data=self.pgi.movements * params['vacancy_rate'],
|
|
66
|
+
units=params['units']).invert()
|
|
67
|
+
|
|
68
|
+
# Effective Gross Income:
|
|
69
|
+
self.egi = rk.flux.Stream(
|
|
70
|
+
name='Effective Gross Income',
|
|
71
|
+
flows=[self.pgi, self.vacancy],
|
|
72
|
+
period_type=params['period_type'])
|
|
73
|
+
|
|
74
|
+
# Operating Expenses:
|
|
75
|
+
self.opex = rk.flux.Flow.from_periods(
|
|
76
|
+
name='Operating Expenses',
|
|
77
|
+
index=self.noi_calc_span.to_index(period_type=params['period_type']),
|
|
78
|
+
data=self.pgi.movements * params['opex_pgi_ratio'],
|
|
79
|
+
units=params['units']).invert()
|
|
80
|
+
|
|
81
|
+
# Net Operating Income:
|
|
82
|
+
self.noi = rk.flux.Stream(
|
|
83
|
+
name='Net Operating Income',
|
|
84
|
+
flows=[self.egi.sum('Effective Gross Income'), self.opex],
|
|
85
|
+
period_type=params['period_type'])
|
|
86
|
+
|
|
87
|
+
# Capital Expenses:
|
|
88
|
+
self.capex = rk.flux.Flow.from_periods(
|
|
89
|
+
name='Capital Expenditures',
|
|
90
|
+
index=self.noi_calc_span.to_index(period_type=params['period_type']),
|
|
91
|
+
data=self.pgi.movements * params['capex_pgi_ratio'],
|
|
92
|
+
units=params['units']).invert()
|
|
93
|
+
|
|
94
|
+
# Net Cashflows:
|
|
95
|
+
self.ncf = rk.flux.Stream(
|
|
96
|
+
name='Net Cashflows',
|
|
97
|
+
flows=[self.noi.sum(), self.capex],
|
|
98
|
+
period_type=params['period_type'])
|
|
99
|
+
|
|
100
|
+
# Disposition (Reversion):
|
|
101
|
+
sale_value = self.ncf.sum().movements.tail(1).item() / params['cap_rate']
|
|
102
|
+
self.disposition = rk.flux.Flow.from_periods(
|
|
103
|
+
name='Disposition',
|
|
104
|
+
index=self.disposition_span.to_index(period_type=params['period_type']),
|
|
105
|
+
data=[sale_value],
|
|
106
|
+
units=params['units'])
|
|
107
|
+
|
|
108
|
+
# Net Cash Flows with Disposition:
|
|
109
|
+
self.ncf_disposition = self.ncf.duplicate().trim_to_span(self.operation_span)
|
|
110
|
+
self.ncf_disposition.append(flows=[self.disposition])
|
|
111
|
+
|
|
112
|
+
# Calculate the Present Value of the NCFs:
|
|
113
|
+
self.pv_ncf = self.ncf.sum().pv(
|
|
114
|
+
period_type=params['period_type'],
|
|
115
|
+
discount_rate=params['discount_rate'])
|
|
116
|
+
|
|
117
|
+
# Calculate the Present Value of Disposition CFs:
|
|
118
|
+
self.pv_disposition = self.disposition.pv(
|
|
119
|
+
period_type=params['period_type'],
|
|
120
|
+
discount_rate=params['discount_rate'])
|
|
121
|
+
|
|
122
|
+
self.pv_ncf_agg = rk.flux.Stream(
|
|
123
|
+
name='Discounted Net Cashflows',
|
|
124
|
+
flows=[self.pv_ncf, self.pv_disposition],
|
|
125
|
+
period_type=params['period_type'])
|
|
126
|
+
|
|
127
|
+
self.pv_sums = self.pv_ncf_agg.sum()
|
|
128
|
+
self.pv_sums.movements = self.pv_sums.movements[:-1]
|
|
129
|
+
|
|
130
|
+
self.acquisition = rk.flux.Flow.from_periods(
|
|
131
|
+
index=self.acquisition_span.to_index(rk.periodicity.Type.YEAR),
|
|
132
|
+
data=[-abs(params['acquisition_price'])],
|
|
133
|
+
units=params['units'],
|
|
134
|
+
name='Acquisition Price')
|
|
135
|
+
|
|
136
|
+
self.investment_cashflows = rk.flux.Stream(
|
|
137
|
+
name='Investment Cashflows',
|
|
138
|
+
flows=[self.ncf.sum(), self.disposition, self.acquisition],
|
|
139
|
+
period_type=params['period_type'])
|
|
140
|
+
|
|
141
|
+
self.investment_cashflows.frame = self.investment_cashflows.frame[:-1]
|
|
142
|
+
|
|
143
|
+
# IRR is displayed in the right hand panel...
|
|
144
|
+
self.irr = self.investment_cashflows.sum().xirr()
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# from __future__ import annotations
|
|
2
|
+
# from typing import List
|
|
3
|
+
#
|
|
4
|
+
#
|
|
5
|
+
# try:
|
|
6
|
+
# import escalation
|
|
7
|
+
# from flux import Flow, Stream
|
|
8
|
+
# from periodicity import Periodicity
|
|
9
|
+
# from span import Span
|
|
10
|
+
# except:
|
|
11
|
+
# import modules.rangekeeper.distribution
|
|
12
|
+
# from modules.rangekeeper.flux import Flow, Stream
|
|
13
|
+
# from modules.rangekeeper.periodicity import Periodicity
|
|
14
|
+
# from modules.rangekeeper.span import Span
|
|
15
|
+
#
|
|
16
|
+
#
|
|
17
|
+
# class Graph:
|
|
18
|
+
# def __init__(
|
|
19
|
+
# self,
|
|
20
|
+
# params: dict):
|
|
21
|
+
# if 'podium' in params:
|
|
22
|
+
#
|
|
23
|
+
#
|
|
24
|
+
#
|
|
25
|
+
#
|
|
26
|
+
#
|
|
27
|
+
# # Base Model:
|
|
28
|
+
# class Model:
|
|
29
|
+
# def __init__(
|
|
30
|
+
# self,
|
|
31
|
+
# params: dict):
|
|
32
|
+
#
|
|
33
|
+
# # Phasing:
|
|
34
|
+
# self.acquisition_span = Span.from_num_periods(
|
|
35
|
+
# name='Acquisition',
|
|
36
|
+
# start_date=params['start_date'],
|
|
37
|
+
# period_type=Periodicity.Type.year,
|
|
38
|
+
# num_periods=1)
|
|
39
|
+
#
|
|
40
|
+
# self.operation_span = Span.from_num_periods(
|
|
41
|
+
# name='Operation',
|
|
42
|
+
# start_date=Periodicity.offset_date(
|
|
43
|
+
# date=self.acquisition_span.end_date,
|
|
44
|
+
# period_type=Periodicity.Type.day,
|
|
45
|
+
# num_periods=1),
|
|
46
|
+
# period_type=Periodicity.Type.year,
|
|
47
|
+
# num_periods=params['num_periods'])
|
|
48
|
+
#
|
|
49
|
+
# self.disposition_span = Span.from_num_periods(
|
|
50
|
+
# name='Disposition',
|
|
51
|
+
# start_date=Periodicity.offset_date(
|
|
52
|
+
# date=self.acquisition_span.start_date,
|
|
53
|
+
# period_type=Periodicity.Type.year,
|
|
54
|
+
# num_periods=params['num_periods']),
|
|
55
|
+
# period_type=Periodicity.Type.year,
|
|
56
|
+
# num_periods=1)
|
|
57
|
+
#
|
|
58
|
+
# self.projection_span = Span.from_num_periods(
|
|
59
|
+
# name='Projection',
|
|
60
|
+
# start_date=Periodicity.offset_date(
|
|
61
|
+
# date=self.operation_span.end_date,
|
|
62
|
+
# period_type=Periodicity.Type.day,
|
|
63
|
+
# num_periods=1),
|
|
64
|
+
# period_type=Periodicity.Type.year,
|
|
65
|
+
# num_periods=1)
|
|
66
|
+
#
|
|
67
|
+
# self.noi_calc_span = Span.merge(
|
|
68
|
+
# name='NOI Calculation Span',
|
|
69
|
+
# spans=[self.operation_span, self.projection_span])
|
|
70
|
+
#
|
|
71
|
+
# # Factors:
|
|
72
|
+
# self.distribution = distribution.Exponential(
|
|
73
|
+
# rate=params['growth_rate'],
|
|
74
|
+
# num_periods=self.noi_calc_span.duration(
|
|
75
|
+
# period_type=params['period_type'],
|
|
76
|
+
# inclusive=True))
|
|
77
|
+
#
|
|
78
|
+
# # Cashflows:
|
|
79
|
+
# # Potential Gross Income
|
|
80
|
+
# self.pgi = Flow.from_initial(
|
|
81
|
+
# name='Potential Gross Income',
|
|
82
|
+
# initial=params['initial_pgi'],
|
|
83
|
+
# index=self.noi_calc_span.to_index(periodicity=params['period_type']),
|
|
84
|
+
# dist=self.distribution,
|
|
85
|
+
# units=params['units'])
|
|
86
|
+
#
|
|
87
|
+
# # Vacancy Allowance
|
|
88
|
+
# # This should be displayed as a row in a table with xxxx, xxx,....
|
|
89
|
+
# self.vacancy = Flow.from_periods(
|
|
90
|
+
# name='Vacancy Allowance',
|
|
91
|
+
# periods=self.noi_calc_span.to_index(periodicity=params['period_type']),
|
|
92
|
+
# data=self.pgi.movements * params['vacancy_rate'],
|
|
93
|
+
# units=params['units']).invert()
|
|
94
|
+
#
|
|
95
|
+
# # Effective Gross Income:
|
|
96
|
+
# self.egi = Stream(
|
|
97
|
+
# name='Effective Gross Income',
|
|
98
|
+
# flows=[self.pgi, self.vacancy],
|
|
99
|
+
# periodicity=params['period_type'])
|
|
100
|
+
#
|
|
101
|
+
# # Operating Expenses:
|
|
102
|
+
# self.opex = Flow.from_periods(
|
|
103
|
+
# name='Operating Expenses',
|
|
104
|
+
# periods=self.noi_calc_span.to_index(periodicity=params['period_type']),
|
|
105
|
+
# data=self.pgi.movements * params['opex_pgi_ratio'],
|
|
106
|
+
# units=params['units']).invert()
|
|
107
|
+
#
|
|
108
|
+
# # Net Operating Income:
|
|
109
|
+
# self.noi = Stream(
|
|
110
|
+
# name='Net Operating Income',
|
|
111
|
+
# flows=[self.egi.sum('Effective Gross Income'), self.opex],
|
|
112
|
+
# periodicity=params['period_type'])
|
|
113
|
+
#
|
|
114
|
+
# # Capital Expenses:
|
|
115
|
+
# self.capex = Flow.from_periods(
|
|
116
|
+
# name='Capital Expenditures',
|
|
117
|
+
# periods=self.noi_calc_span.to_index(periodicity=params['period_type']),
|
|
118
|
+
# data=self.pgi.movements * params['capex_pgi_ratio'],
|
|
119
|
+
# units=params['units']).invert()
|
|
120
|
+
#
|
|
121
|
+
# # Net Cashflows:
|
|
122
|
+
# self.ncf = Stream(
|
|
123
|
+
# name='Net Cashflows',
|
|
124
|
+
# flows=[self.noi.sum(), self.capex],
|
|
125
|
+
# periodicity=params['period_type'])
|
|
126
|
+
#
|
|
127
|
+
# # Disposition (Reversion):
|
|
128
|
+
# sale_value = self.ncf.sum().movements.tail(1).item() / params['cap_rate']
|
|
129
|
+
# self.disposition = Flow.from_periods(
|
|
130
|
+
# name='Disposition',
|
|
131
|
+
# periods=self.disposition_span.to_index(periodicity=params['period_type']),
|
|
132
|
+
# data=[sale_value],
|
|
133
|
+
# units=params['units'])
|
|
134
|
+
#
|
|
135
|
+
# # Net Cash Flows with Disposition:
|
|
136
|
+
# self.ncf_disposition = self.ncf.duplicate().trim_to_span(self.operation_span)
|
|
137
|
+
# self.ncf_disposition.append(flows=[self.disposition])
|
|
138
|
+
#
|
|
139
|
+
# # Calculate the Present Value of the NCFs:
|
|
140
|
+
# self.pv_ncf = self.ncf.sum().pv(
|
|
141
|
+
# periodicity=params['period_type'],
|
|
142
|
+
# discount_rate=params['discount_rate'])
|
|
143
|
+
#
|
|
144
|
+
# # Calculate the Present Value of Disposition CFs:
|
|
145
|
+
# self.pv_disposition = self.disposition.pv(
|
|
146
|
+
# periodicity=params['period_type'],
|
|
147
|
+
# discount_rate=params['discount_rate'])
|
|
148
|
+
#
|
|
149
|
+
# self.pv_ncf_agg = Stream(
|
|
150
|
+
# name='Discounted Net Cashflows',
|
|
151
|
+
# flows=[self.pv_ncf, self.pv_disposition],
|
|
152
|
+
# periodicity=params['period_type'])
|
|
153
|
+
#
|
|
154
|
+
# self.pv_sums = self.pv_ncf_agg.sum()
|
|
155
|
+
# self.pv_sums.movements = self.pv_sums.movements[:-1]
|
|
156
|
+
#
|
|
157
|
+
# self.acquisition = Flow.from_periods(
|
|
158
|
+
# periods=self.acquisition_span.to_index(Periodicity.Type.year),
|
|
159
|
+
# data=[-abs(params['acquisition_price'])],
|
|
160
|
+
# units=params['units'],
|
|
161
|
+
# name='Acquisition Price')
|
|
162
|
+
#
|
|
163
|
+
# self.investment_cashflows = Stream(
|
|
164
|
+
# name='Investment Cashflows',
|
|
165
|
+
# flows=[self.ncf.sum(), self.disposition, self.acquisition],
|
|
166
|
+
# periodicity=params['period_type'])
|
|
167
|
+
#
|
|
168
|
+
# self.investment_cashflows.frame = self.investment_cashflows.frame[:-1]
|
|
169
|
+
#
|
|
170
|
+
# # IRR is displayed in the right hand panel...
|
|
171
|
+
# self.irr = self.investment_cashflows.sum().xirr()
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
import rangekeeper as rk
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
# Base Model:
|
|
6
|
+
class Model:
|
|
7
|
+
def __init__(
|
|
8
|
+
self,
|
|
9
|
+
params: dict):
|
|
10
|
+
|
|
11
|
+
# Phasing:
|
|
12
|
+
self.acquisition_span = rk.span.Span.from_num_periods(
|
|
13
|
+
name='Acquisition',
|
|
14
|
+
date=params['start_date'],
|
|
15
|
+
period_type=rk.periodicity.Type.YEAR,
|
|
16
|
+
num_periods=1)
|
|
17
|
+
|
|
18
|
+
self.operation_span = rk.span.Span.from_num_periods(
|
|
19
|
+
name='Operation',
|
|
20
|
+
date=rk.periodicity.offset_date(
|
|
21
|
+
date=self.acquisition_span.end_date,
|
|
22
|
+
period_type=rk.periodicity.Type.DAY,
|
|
23
|
+
num_periods=1),
|
|
24
|
+
period_type=rk.periodicity.Type.YEAR,
|
|
25
|
+
num_periods=params['num_periods'])
|
|
26
|
+
|
|
27
|
+
self.disposition_span = rk.span.Span.from_num_periods(
|
|
28
|
+
name='Reversion',
|
|
29
|
+
date=rk.periodicity.offset_date(
|
|
30
|
+
date=self.acquisition_span.start_date,
|
|
31
|
+
period_type=rk.periodicity.Type.YEAR,
|
|
32
|
+
num_periods=params['num_periods']),
|
|
33
|
+
period_type=rk.periodicity.Type.YEAR,
|
|
34
|
+
num_periods=1)
|
|
35
|
+
|
|
36
|
+
self.projection_span = rk.span.Span.from_num_periods(
|
|
37
|
+
name='Projection',
|
|
38
|
+
date=rk.periodicity.offset_date(
|
|
39
|
+
date=self.operation_span.end_date,
|
|
40
|
+
period_type=rk.periodicity.Type.DAY,
|
|
41
|
+
num_periods=1),
|
|
42
|
+
period_type=rk.periodicity.Type.YEAR,
|
|
43
|
+
num_periods=1)
|
|
44
|
+
|
|
45
|
+
self.noi_calc_span = rk.span.Span.merge(
|
|
46
|
+
name='NOI Calculation Span',
|
|
47
|
+
spans=[self.operation_span, self.projection_span])
|
|
48
|
+
|
|
49
|
+
# Cashflows:
|
|
50
|
+
self.escalation = rk.extrapolation.Compounding(rate=params['growth_rate'])
|
|
51
|
+
|
|
52
|
+
# Potential Gross Income
|
|
53
|
+
self.pgi = rk.flux.Flow.from_projection(
|
|
54
|
+
name='Potential Gross Income',
|
|
55
|
+
value=params['initial_pgi'],
|
|
56
|
+
proj=rk.projection.Extrapolation(
|
|
57
|
+
form=self.escalation,
|
|
58
|
+
sequence=self.noi_calc_span.to_index(period_type=params['period_type'])),
|
|
59
|
+
units=params['units'])
|
|
60
|
+
|
|
61
|
+
factors = params['space_market_dist'].sample(size=self.pgi.movements.size)
|
|
62
|
+
|
|
63
|
+
self.pgi_factor = rk.flux.Flow(
|
|
64
|
+
movements=pd.Series(
|
|
65
|
+
data=factors,
|
|
66
|
+
index=self.pgi.movements.index,
|
|
67
|
+
dtype=float),
|
|
68
|
+
units=params['units'],
|
|
69
|
+
name='Space Market Pricing Factors')
|
|
70
|
+
|
|
71
|
+
self.pgi = rk.flux.Flow(
|
|
72
|
+
movements=self.pgi.movements * self.pgi_factor.movements,
|
|
73
|
+
units=params['units'],
|
|
74
|
+
name=self.pgi.name)
|
|
75
|
+
|
|
76
|
+
# Vacancy Allowance
|
|
77
|
+
self.vacancy = rk.flux.Flow.from_periods(
|
|
78
|
+
name='Vacancy Allowance',
|
|
79
|
+
index=self.noi_calc_span.to_index(period_type=params['period_type']),
|
|
80
|
+
data=self.pgi.movements * params['vacancy_rate'],
|
|
81
|
+
units=params['units']).invert()
|
|
82
|
+
|
|
83
|
+
# Effective Gross Income:
|
|
84
|
+
self.egi = rk.flux.Stream(
|
|
85
|
+
name='Effective Gross Income',
|
|
86
|
+
flows=[self.pgi, self.vacancy],
|
|
87
|
+
period_type=params['period_type'])
|
|
88
|
+
|
|
89
|
+
# Operating Expenses:
|
|
90
|
+
self.opex = rk.flux.Flow.from_periods(
|
|
91
|
+
name='Operating Expenses',
|
|
92
|
+
index=self.noi_calc_span.to_index(period_type=params['period_type']),
|
|
93
|
+
data=self.pgi.movements * params['opex_pgi_ratio'],
|
|
94
|
+
units=params['units']).invert()
|
|
95
|
+
|
|
96
|
+
# Net Operating Income:
|
|
97
|
+
self.noi = rk.flux.Stream(
|
|
98
|
+
name='Net Operating Income',
|
|
99
|
+
flows=[self.egi.sum('Effective Gross Income'), self.opex],
|
|
100
|
+
period_type=params['period_type'])
|
|
101
|
+
|
|
102
|
+
# Capital Expenses:
|
|
103
|
+
self.capex = rk.flux.Flow.from_periods(
|
|
104
|
+
name='Capital Expenditures',
|
|
105
|
+
index=self.noi_calc_span.to_index(period_type=params['period_type']),
|
|
106
|
+
data=self.pgi.movements * params['capex_pgi_ratio'],
|
|
107
|
+
units=params['units']).invert()
|
|
108
|
+
|
|
109
|
+
# Net Cashflows:
|
|
110
|
+
self.ncf = rk.flux.Stream(
|
|
111
|
+
name='Net Cashflows',
|
|
112
|
+
flows=[self.noi.sum(), self.capex],
|
|
113
|
+
period_type=params['period_type'])
|
|
114
|
+
|
|
115
|
+
# Disposition:
|
|
116
|
+
sale_value = self.ncf.sum().movements.tail(1).item() / params['cap_rate']
|
|
117
|
+
self.disposition = rk.flux.Flow.from_periods(
|
|
118
|
+
name='Disposition',
|
|
119
|
+
index=self.disposition_span.to_index(period_type=params['period_type']),
|
|
120
|
+
data=[sale_value],
|
|
121
|
+
units=params['units'])
|
|
122
|
+
|
|
123
|
+
# Calculate the Present Value of the NCFs:
|
|
124
|
+
self.pv_ncf = self.ncf.sum().pv(
|
|
125
|
+
period_type=params['period_type'],
|
|
126
|
+
discount_rate=params['discount_rate'])
|
|
127
|
+
|
|
128
|
+
# Calculate the Present Value of Reversion CFs:
|
|
129
|
+
self.pv_disposition = self.disposition.pv(
|
|
130
|
+
period_type=params['period_type'],
|
|
131
|
+
discount_rate=params['discount_rate'])
|
|
132
|
+
|
|
133
|
+
# Add Cumulative Sum of Discounted Net Cashflows to each period's Discounted Disposition:
|
|
134
|
+
# pv_ncf_cumsum = Flow(movements=self.pv_ncf.movements.cumsum(),
|
|
135
|
+
# name='Discounted Net Cashflow Cumulative Sums',
|
|
136
|
+
# units=params['units'])
|
|
137
|
+
self.pv_ncf_agg = rk.flux.Stream(
|
|
138
|
+
name='Discounted Net Cashflows',
|
|
139
|
+
flows=[self.pv_ncf, self.pv_disposition],
|
|
140
|
+
period_type=params['period_type'])
|
|
141
|
+
|
|
142
|
+
self.pv_sums = self.pv_ncf_agg.sum()
|
|
143
|
+
self.pv_sums.movements = self.pv_sums.movements[:-1]
|
|
144
|
+
|
|
145
|
+
self.acquisition = rk.flux.Flow.from_periods(
|
|
146
|
+
index=self.acquisition_span.to_index(rk.periodicity.Type.YEAR),
|
|
147
|
+
data=[-abs(params['acquisition_price'])],
|
|
148
|
+
units=params['units'],
|
|
149
|
+
name='Acquisition Price')
|
|
150
|
+
|
|
151
|
+
self.investment_cashflows = rk.flux.Stream(
|
|
152
|
+
name='Investment Cashflows',
|
|
153
|
+
flows=[self.ncf.sum(), self.disposition, self.acquisition],
|
|
154
|
+
period_type=params['period_type'])
|
|
155
|
+
|
|
156
|
+
self.investment_cashflows.frame = self.investment_cashflows.frame[:-1]
|
|
157
|
+
self.irr = self.investment_cashflows.sum().xirr()
|