rangekeeper 0.7.2.post4__tar.gz → 0.7.3__tar.gz
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-0.7.2.post4 → rangekeeper-0.7.3}/PKG-INFO +7 -7
- {rangekeeper-0.7.2.post4 → rangekeeper-0.7.3}/pyproject.toml +8 -8
- rangekeeper-0.7.3/rangekeeper/formula/__init__.py +1 -0
- rangekeeper-0.7.2.post4/rangekeeper/formula.py → rangekeeper-0.7.3/rangekeeper/formula/financial.py +109 -101
- {rangekeeper-0.7.2.post4 → rangekeeper-0.7.3}/rangekeeper/span.py +3 -3
- {rangekeeper-0.7.2.post4 → rangekeeper-0.7.3}/README.md +0 -0
- {rangekeeper-0.7.2.post4 → rangekeeper-0.7.3}/rangekeeper/__init__.py +0 -0
- {rangekeeper-0.7.2.post4 → rangekeeper-0.7.3}/rangekeeper/api.py +0 -0
- {rangekeeper-0.7.2.post4 → rangekeeper-0.7.3}/rangekeeper/conftest.py +0 -0
- {rangekeeper-0.7.2.post4 → rangekeeper-0.7.3}/rangekeeper/distribution.py +0 -0
- {rangekeeper-0.7.2.post4 → rangekeeper-0.7.3}/rangekeeper/duration.py +0 -0
- {rangekeeper-0.7.2.post4 → rangekeeper-0.7.3}/rangekeeper/dynamics/__init__.py +0 -0
- {rangekeeper-0.7.2.post4 → rangekeeper-0.7.3}/rangekeeper/dynamics/black_swan.py +0 -0
- {rangekeeper-0.7.2.post4 → rangekeeper-0.7.3}/rangekeeper/dynamics/cyclicality.py +0 -0
- {rangekeeper-0.7.2.post4 → rangekeeper-0.7.3}/rangekeeper/dynamics/market.py +0 -0
- {rangekeeper-0.7.2.post4 → rangekeeper-0.7.3}/rangekeeper/dynamics/noise.py +0 -0
- {rangekeeper-0.7.2.post4 → rangekeeper-0.7.3}/rangekeeper/dynamics/trend.py +0 -0
- {rangekeeper-0.7.2.post4 → rangekeeper-0.7.3}/rangekeeper/dynamics/volatility.py +0 -0
- {rangekeeper-0.7.2.post4 → rangekeeper-0.7.3}/rangekeeper/extrapolation.py +0 -0
- {rangekeeper-0.7.2.post4 → rangekeeper-0.7.3}/rangekeeper/flux.py +0 -0
- {rangekeeper-0.7.2.post4 → rangekeeper-0.7.3}/rangekeeper/format.py +0 -0
- {rangekeeper-0.7.2.post4 → rangekeeper-0.7.3}/rangekeeper/graph.py +0 -0
- {rangekeeper-0.7.2.post4 → rangekeeper-0.7.3}/rangekeeper/measure.py +0 -0
- {rangekeeper-0.7.2.post4 → rangekeeper-0.7.3}/rangekeeper/policy.py +0 -0
- {rangekeeper-0.7.2.post4 → rangekeeper-0.7.3}/rangekeeper/projection.py +0 -0
- {rangekeeper-0.7.2.post4 → rangekeeper-0.7.3}/rangekeeper/segmentation.py +0 -0
- {rangekeeper-0.7.2.post4 → rangekeeper-0.7.3}/rangekeeper/space.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: Rangekeeper
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.3
|
|
4
4
|
Summary: A Python library assisting financial modelling in real estate asset & development planning, decision-making, cashflow forecasting, and scenario analysis.
|
|
5
5
|
License: MPL-2.0
|
|
6
6
|
Author: Daniel Fink
|
|
@@ -12,23 +12,23 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.11
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.12
|
|
14
14
|
Requires-Dist: aenum (>=3.1,<4.0)
|
|
15
|
-
Requires-Dist: matplotlib (>=3.
|
|
15
|
+
Requires-Dist: matplotlib (>=3.10,<4.0)
|
|
16
16
|
Requires-Dist: mpld3 (>=0.5,<0.6)
|
|
17
17
|
Requires-Dist: multiprocess (>=0.70,<0.71)
|
|
18
18
|
Requires-Dist: networkx (>=3.3,<4.0)
|
|
19
|
-
Requires-Dist: numba (>=0.
|
|
19
|
+
Requires-Dist: numba (>=0.61,<0.62)
|
|
20
20
|
Requires-Dist: numpy-financial (>=1.0,<2.0)
|
|
21
21
|
Requires-Dist: pandas (>=2,<3)
|
|
22
22
|
Requires-Dist: pint (>=0.24,<0.25)
|
|
23
|
-
Requires-Dist: plotly (>=
|
|
23
|
+
Requires-Dist: plotly (>=6.0,<7.0)
|
|
24
24
|
Requires-Dist: py-moneyed (>=3.0,<4.0)
|
|
25
|
-
Requires-Dist: pyarrow (>=
|
|
25
|
+
Requires-Dist: pyarrow (>=19.0,<20.0)
|
|
26
26
|
Requires-Dist: pyvis (>=0.3,<0.4)
|
|
27
27
|
Requires-Dist: pyxirr (>=0.10,<0.11)
|
|
28
28
|
Requires-Dist: pyyaml (>=6.0,<7.0)
|
|
29
|
-
Requires-Dist: scipy (>=1.
|
|
29
|
+
Requires-Dist: scipy (>=1.14,<2.0)
|
|
30
30
|
Requires-Dist: seaborn (>=0.13,<0.14)
|
|
31
|
-
Requires-Dist: specklepy (>=2.
|
|
31
|
+
Requires-Dist: specklepy (>=2.21,<3.0)
|
|
32
32
|
Requires-Dist: tabulate (>=0.9,<0.10)
|
|
33
33
|
Description-Content-Type: text/markdown
|
|
34
34
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "Rangekeeper"
|
|
3
|
-
version = "0.7.
|
|
3
|
+
version = "0.7.3"
|
|
4
4
|
description = "A Python library assisting financial modelling in real estate asset & development planning, decision-making, cashflow forecasting, and scenario analysis."
|
|
5
5
|
authors = ["Daniel Fink <danfink@mit.edu>"]
|
|
6
6
|
license = "MPL-2.0"
|
|
@@ -8,28 +8,28 @@ readme = "README.md"
|
|
|
8
8
|
|
|
9
9
|
[tool.poetry.dependencies]
|
|
10
10
|
python = ">=3.10, <3.13"
|
|
11
|
-
matplotlib = "^3.
|
|
11
|
+
matplotlib = "^3.10"
|
|
12
12
|
pandas = "^2"
|
|
13
13
|
pint = "^0.24"
|
|
14
14
|
pyxirr = "^0.10"
|
|
15
|
-
scipy = "^1.
|
|
15
|
+
scipy = "^1.14"
|
|
16
16
|
networkx = "^3.3"
|
|
17
17
|
py-moneyed = "^3.0"
|
|
18
18
|
tabulate = "^0.9"
|
|
19
19
|
pyyaml = "^6.0"
|
|
20
|
-
specklepy = "^2.
|
|
21
|
-
numba = "^0.
|
|
20
|
+
specklepy = "^2.21"
|
|
21
|
+
numba = "^0.61"
|
|
22
22
|
multiprocess = "^0.70"
|
|
23
23
|
seaborn = "^0.13"
|
|
24
24
|
pyvis = "^0.3"
|
|
25
|
-
plotly = "^
|
|
25
|
+
plotly = "^6.0"
|
|
26
26
|
mpld3 = "^0.5"
|
|
27
|
-
pyarrow = "^
|
|
27
|
+
pyarrow = "^19.0"
|
|
28
28
|
aenum = "^3.1"
|
|
29
29
|
numpy-financial = "^1.0"
|
|
30
30
|
|
|
31
31
|
[tool.poetry.dev-dependencies]
|
|
32
|
-
pytest = "^8.
|
|
32
|
+
pytest = "^8.3"
|
|
33
33
|
|
|
34
34
|
[build-system]
|
|
35
35
|
requires = ["setuptools", "poetry-core>=1.0.0"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from . import financial as financial
|
rangekeeper-0.7.2.post4/rangekeeper/formula.py → rangekeeper-0.7.3/rangekeeper/formula/financial.py
RENAMED
|
@@ -1,29 +1,101 @@
|
|
|
1
|
-
from typing import Callable, Optional
|
|
2
1
|
import enum
|
|
2
|
+
import dataclasses
|
|
3
3
|
import numpy as np
|
|
4
|
-
from numba import jit
|
|
5
4
|
import numba
|
|
6
5
|
import pandas as pd
|
|
7
|
-
import scipy.optimize as opt
|
|
8
6
|
|
|
9
7
|
import rangekeeper as rk
|
|
10
8
|
|
|
11
9
|
|
|
12
|
-
class
|
|
10
|
+
class Balance:
|
|
11
|
+
startings: rk.flux.Flow
|
|
12
|
+
endings: rk.flux.Flow
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
starting: float,
|
|
17
|
+
transactions: rk.flux.Flow,
|
|
18
|
+
frequency: rk.duration.Type,
|
|
19
|
+
):
|
|
20
|
+
"""
|
|
21
|
+
Calculate the balance of a financial account given a starting balance and a series of transactions.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
transactions = transactions.resample(frequency=frequency)
|
|
25
|
+
|
|
26
|
+
result = self._calculate_balance(
|
|
27
|
+
startings=numba.typed.List([np.float64(starting)]),
|
|
28
|
+
transactions=numba.typed.List(transactions.movements.to_list()))
|
|
29
|
+
|
|
30
|
+
self.startings=rk.flux.Flow(
|
|
31
|
+
movements=pd.Series(result[0], index=transactions.movements.index),
|
|
32
|
+
units=transactions.units,
|
|
33
|
+
name='Start Balance')
|
|
34
|
+
|
|
35
|
+
self.endings=rk.flux.Flow(
|
|
36
|
+
movements=pd.Series(result[1], index=transactions.movements.index),
|
|
37
|
+
units=transactions.units,
|
|
38
|
+
name='End Balance')
|
|
39
|
+
|
|
40
|
+
@classmethod
|
|
41
|
+
def from_flows(
|
|
42
|
+
cls,
|
|
43
|
+
startings: rk.flux.Flow,
|
|
44
|
+
endings: rk.flux.Flow,
|
|
45
|
+
):
|
|
46
|
+
balance = super().__new__(cls)
|
|
47
|
+
balance.startings = startings
|
|
48
|
+
balance.endings = endings
|
|
49
|
+
|
|
50
|
+
return balance
|
|
51
|
+
|
|
52
|
+
def overdraft(
|
|
53
|
+
self,
|
|
54
|
+
name: str = None,
|
|
55
|
+
) -> rk.flux.Flow:
|
|
56
|
+
"""
|
|
57
|
+
Identifies the portion of transactions occurring while the balance is negative.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
starts = self.startings.movements.where(self.startings.movements < 0).fillna(0)
|
|
61
|
+
ends = self.endings.movements.where(self.endings.movements < 0).fillna(0)
|
|
62
|
+
|
|
63
|
+
return rk.flux.Flow(
|
|
64
|
+
movements=ends - starts,
|
|
65
|
+
units=self.startings.units,
|
|
66
|
+
name='Overdraft' if name is None else name)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@staticmethod
|
|
70
|
+
@numba.jit
|
|
71
|
+
def _calculate_balance(
|
|
72
|
+
startings: numba.typed.List,
|
|
73
|
+
transactions: numba.typed.List) -> (numba.typed.List, numba.typed.List):
|
|
74
|
+
|
|
75
|
+
endings = numba.typed.List.empty_list(numba.float64)
|
|
76
|
+
for i in range(len(transactions)):
|
|
77
|
+
endings.append(float(startings[-1] + transactions[i]))
|
|
78
|
+
startings.append(endings[-1])
|
|
79
|
+
|
|
80
|
+
return (startings[:-1], endings)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class Interest:
|
|
84
|
+
amounts: rk.flux.Flow
|
|
85
|
+
balance: Balance
|
|
86
|
+
|
|
87
|
+
class Type(enum.Enum):
|
|
15
88
|
INTEREST_ONLY = 'interest_only'
|
|
16
89
|
COMPOUND = 'compound'
|
|
17
90
|
CAPITALIZED = 'capitalized'
|
|
18
91
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def interest(
|
|
92
|
+
def __init__(
|
|
93
|
+
self,
|
|
22
94
|
rate: float | rk.flux.Flow,
|
|
23
95
|
transactions: rk.flux.Flow,
|
|
24
96
|
frequency: rk.duration.Type,
|
|
25
|
-
|
|
26
|
-
|
|
97
|
+
type: Type = Type.COMPOUND,
|
|
98
|
+
):
|
|
27
99
|
"""
|
|
28
100
|
Calculate interest expense on a loan or other interest-bearing liability,
|
|
29
101
|
described by a series of transactions.
|
|
@@ -43,47 +115,45 @@ class Financial:
|
|
|
43
115
|
sequence=rk.duration.Sequence.from_datestamps(
|
|
44
116
|
datestamps=transactions.movements.index,
|
|
45
117
|
frequency=frequency)))
|
|
118
|
+
else:
|
|
119
|
+
raise NotImplementedError('Only float rates are supported currently.')
|
|
46
120
|
|
|
47
121
|
if len(transactions.movements) != len(rate.movements):
|
|
48
122
|
raise ValueError('The number of transactions must match the number of rates.')
|
|
49
123
|
|
|
50
|
-
startings, endings, interests =
|
|
124
|
+
startings, endings, interests = self._calc_interest(
|
|
51
125
|
transactions=numba.typed.List(transactions.movements.to_list()),
|
|
52
126
|
rates=numba.typed.List(rate.movements.to_list()),
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
balance = rk.
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
frequency=frequency)
|
|
68
|
-
|
|
69
|
-
interest = rk.flux.Flow(
|
|
127
|
+
type=type.value)
|
|
128
|
+
|
|
129
|
+
self.balance = rk.formula.financial.Balance.from_flows(
|
|
130
|
+
startings=rk.flux.Flow(
|
|
131
|
+
movements=pd.Series(startings, index=transactions.movements.index),
|
|
132
|
+
units=transactions.units,
|
|
133
|
+
name='Start Balance'),
|
|
134
|
+
endings=rk.flux.Flow(
|
|
135
|
+
movements=pd.Series(endings, index=transactions.movements.index),
|
|
136
|
+
units=transactions.units,
|
|
137
|
+
name='End Balance')
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
self.amounts = rk.flux.Flow(
|
|
70
141
|
movements=pd.Series(interests, index=transactions.movements.index),
|
|
71
142
|
units=transactions.units,
|
|
72
143
|
name='Interest')
|
|
73
144
|
|
|
74
|
-
return (balance, interest)
|
|
75
|
-
|
|
76
145
|
|
|
77
146
|
@staticmethod
|
|
78
147
|
@numba.jit
|
|
79
148
|
def _calc_interest(
|
|
80
149
|
transactions: numba.typed.List,
|
|
81
150
|
rates: numba.typed.List,
|
|
82
|
-
|
|
151
|
+
type: str,
|
|
152
|
+
) -> (numba.typed.List, numba.typed.List, numba.typed.List):
|
|
83
153
|
|
|
84
154
|
startings = numba.typed.List.empty_list(numba.float64)
|
|
85
155
|
endings = numba.typed.List.empty_list(numba.float64)
|
|
86
|
-
|
|
156
|
+
amounts = numba.typed.List.empty_list(numba.float64)
|
|
87
157
|
|
|
88
158
|
for i in range(len(transactions)):
|
|
89
159
|
if i == 0:
|
|
@@ -92,87 +162,25 @@ class Financial:
|
|
|
92
162
|
startings.append(endings[-1])
|
|
93
163
|
|
|
94
164
|
principal = startings[i] + transactions[i]
|
|
95
|
-
if
|
|
165
|
+
if type == 'interest_only':
|
|
96
166
|
interest = principal * rates[i] if principal > 0 else 0
|
|
97
167
|
endings.append(principal)
|
|
98
168
|
|
|
99
|
-
elif
|
|
169
|
+
elif type == 'compound':
|
|
100
170
|
interest = principal * rates[i] if principal > 0 else 0
|
|
101
171
|
endings.append(principal + interest)
|
|
102
172
|
|
|
103
|
-
elif
|
|
173
|
+
elif type == 'capitalized':
|
|
104
174
|
interest = (principal * rates[i]) / (1 - rates[i]) if principal > 0 else 0 # Since we are capitalizing
|
|
105
|
-
# interest, the
|
|
175
|
+
# interest, the amount (draw) must include interest to pay on the principal. Derived from i = r * (P + i)
|
|
106
176
|
endings.append(principal + interest)
|
|
107
177
|
|
|
108
178
|
else:
|
|
109
|
-
raise ValueError(f'Invalid instrument: {
|
|
110
|
-
|
|
111
|
-
interests.append(interest)
|
|
179
|
+
raise ValueError(f'Invalid instrument: {type}')
|
|
112
180
|
|
|
113
|
-
|
|
181
|
+
amounts.append(interest)
|
|
114
182
|
|
|
183
|
+
return (startings, endings, amounts)
|
|
115
184
|
|
|
116
|
-
@staticmethod
|
|
117
|
-
def balance(
|
|
118
|
-
starting: float,
|
|
119
|
-
transactions: rk.flux.Flow,
|
|
120
|
-
frequency: rk.duration.Type,
|
|
121
|
-
name: str = None) -> rk.flux.Stream:
|
|
122
|
-
"""
|
|
123
|
-
Calculate the balance of a financial account given a starting balance and a series of transactions.
|
|
124
|
-
"""
|
|
125
|
-
|
|
126
|
-
starting = numba.typed.List([np.float64(starting)])
|
|
127
|
-
transaction_amounts = numba.typed.List(transactions.movements.to_list())
|
|
128
|
-
|
|
129
|
-
balance = rk.formula.Financial._calculate_balance(
|
|
130
|
-
starting=starting,
|
|
131
|
-
transactions=transaction_amounts)
|
|
132
|
-
|
|
133
|
-
flows = [
|
|
134
|
-
rk.flux.Flow(
|
|
135
|
-
movements=pd.Series(record, index=transactions.movements.index),
|
|
136
|
-
units=transactions.units,
|
|
137
|
-
name=name)
|
|
138
|
-
for record, name in zip(balance, ('Start Balance', 'End Balance'))
|
|
139
|
-
]
|
|
140
185
|
|
|
141
|
-
statement = rk.flux.Stream(
|
|
142
|
-
name=name,
|
|
143
|
-
flows=flows,
|
|
144
|
-
frequency=frequency)
|
|
145
186
|
|
|
146
|
-
return statement
|
|
147
|
-
|
|
148
|
-
@staticmethod
|
|
149
|
-
@numba.jit
|
|
150
|
-
def _calculate_balance(
|
|
151
|
-
starting: numba.typed.List,
|
|
152
|
-
transactions: numba.typed.List) -> (numba.typed.List, numba.typed.List):
|
|
153
|
-
|
|
154
|
-
ending = numba.typed.List.empty_list(numba.float64)
|
|
155
|
-
for i in range(len(transactions)):
|
|
156
|
-
ending.append(float(starting[-1] + transactions[i]))
|
|
157
|
-
starting.append(ending[-1])
|
|
158
|
-
return (starting[:-1], ending)
|
|
159
|
-
|
|
160
|
-
@staticmethod
|
|
161
|
-
def overdraft(
|
|
162
|
-
balance: rk.flux.Stream,
|
|
163
|
-
name: str = None) -> rk.flux.Flow:
|
|
164
|
-
"""
|
|
165
|
-
Identifies the portion of transactions occurring while the balance is negative.
|
|
166
|
-
"""
|
|
167
|
-
if len(balance.flows) != 2:
|
|
168
|
-
raise ValueError('Balance must be Stream with two flows; Start and End.')
|
|
169
|
-
if balance.flows[0].units != balance.flows[1].units:
|
|
170
|
-
raise ValueError('Units of the Start and End Flows must be the same.')
|
|
171
|
-
|
|
172
|
-
starts = balance.flows[0].movements.where(balance.flows[0].movements < 0).fillna(0)
|
|
173
|
-
ends = balance.flows[1].movements.where(balance.flows[1].movements < 0).fillna(0)
|
|
174
|
-
|
|
175
|
-
return rk.flux.Flow(
|
|
176
|
-
movements=ends - starts,
|
|
177
|
-
units=balance.flows[0].units,
|
|
178
|
-
name='Overdraft' if name is None else name)
|
|
@@ -41,9 +41,9 @@ class Span:
|
|
|
41
41
|
self.end_date = self._interval.right
|
|
42
42
|
|
|
43
43
|
def __str__(self):
|
|
44
|
-
return 'Span: {}\n' \
|
|
45
|
-
'Start Date: {}\n' \
|
|
46
|
-
'End Date: {
|
|
44
|
+
return f'Span: {self.name}\n' \
|
|
45
|
+
f'Start Date: {self.start_date.date()}\n' \
|
|
46
|
+
f'End Date: {self.end_date.date()}\n'
|
|
47
47
|
|
|
48
48
|
def __repr__(self):
|
|
49
49
|
return self.__str__()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|