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.
@@ -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()