Rangekeeper 0.8.32__tar.gz → 0.8.34__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.
Files changed (39) hide show
  1. {rangekeeper-0.8.32 → rangekeeper-0.8.34}/PKG-INFO +1 -1
  2. {rangekeeper-0.8.32 → rangekeeper-0.8.34}/Rangekeeper.egg-info/PKG-INFO +1 -1
  3. {rangekeeper-0.8.32 → rangekeeper-0.8.34}/pyproject.toml +1 -1
  4. {rangekeeper-0.8.32 → rangekeeper-0.8.34}/rangekeeper/duration.py +16 -3
  5. {rangekeeper-0.8.32 → rangekeeper-0.8.34}/rangekeeper/flux.py +45 -24
  6. {rangekeeper-0.8.32 → rangekeeper-0.8.34}/tests/test_dynamics.py +4 -4
  7. {rangekeeper-0.8.32 → rangekeeper-0.8.34}/tests/test_formulas.py +11 -11
  8. {rangekeeper-0.8.32 → rangekeeper-0.8.34}/tests/test_modules.py +91 -36
  9. {rangekeeper-0.8.32 → rangekeeper-0.8.34}/README.md +0 -0
  10. {rangekeeper-0.8.32 → rangekeeper-0.8.34}/Rangekeeper.egg-info/SOURCES.txt +0 -0
  11. {rangekeeper-0.8.32 → rangekeeper-0.8.34}/Rangekeeper.egg-info/dependency_links.txt +0 -0
  12. {rangekeeper-0.8.32 → rangekeeper-0.8.34}/Rangekeeper.egg-info/requires.txt +0 -0
  13. {rangekeeper-0.8.32 → rangekeeper-0.8.34}/Rangekeeper.egg-info/top_level.txt +0 -0
  14. {rangekeeper-0.8.32 → rangekeeper-0.8.34}/rangekeeper/__init__.py +0 -0
  15. {rangekeeper-0.8.32 → rangekeeper-0.8.34}/rangekeeper/api.py +0 -0
  16. {rangekeeper-0.8.32 → rangekeeper-0.8.34}/rangekeeper/distribution.py +0 -0
  17. {rangekeeper-0.8.32 → rangekeeper-0.8.34}/rangekeeper/dynamics/__init__.py +0 -0
  18. {rangekeeper-0.8.32 → rangekeeper-0.8.34}/rangekeeper/dynamics/black_swan.py +0 -0
  19. {rangekeeper-0.8.32 → rangekeeper-0.8.34}/rangekeeper/dynamics/cyclicality.py +0 -0
  20. {rangekeeper-0.8.32 → rangekeeper-0.8.34}/rangekeeper/dynamics/market.py +0 -0
  21. {rangekeeper-0.8.32 → rangekeeper-0.8.34}/rangekeeper/dynamics/noise.py +0 -0
  22. {rangekeeper-0.8.32 → rangekeeper-0.8.34}/rangekeeper/dynamics/trend.py +0 -0
  23. {rangekeeper-0.8.32 → rangekeeper-0.8.34}/rangekeeper/dynamics/volatility.py +0 -0
  24. {rangekeeper-0.8.32 → rangekeeper-0.8.34}/rangekeeper/extrapolation.py +0 -0
  25. {rangekeeper-0.8.32 → rangekeeper-0.8.34}/rangekeeper/format.py +0 -0
  26. {rangekeeper-0.8.32 → rangekeeper-0.8.34}/rangekeeper/formula/__init__.py +0 -0
  27. {rangekeeper-0.8.32 → rangekeeper-0.8.34}/rangekeeper/formula/financial.py +0 -0
  28. {rangekeeper-0.8.32 → rangekeeper-0.8.34}/rangekeeper/graph.py +0 -0
  29. {rangekeeper-0.8.32 → rangekeeper-0.8.34}/rangekeeper/measure.py +0 -0
  30. {rangekeeper-0.8.32 → rangekeeper-0.8.34}/rangekeeper/policy.py +0 -0
  31. {rangekeeper-0.8.32 → rangekeeper-0.8.34}/rangekeeper/projection.py +0 -0
  32. {rangekeeper-0.8.32 → rangekeeper-0.8.34}/rangekeeper/segmentation.py +0 -0
  33. {rangekeeper-0.8.32 → rangekeeper-0.8.34}/rangekeeper/space.py +0 -0
  34. {rangekeeper-0.8.32 → rangekeeper-0.8.34}/setup.cfg +0 -0
  35. {rangekeeper-0.8.32 → rangekeeper-0.8.34}/tests/test_api.py +0 -0
  36. {rangekeeper-0.8.32 → rangekeeper-0.8.34}/tests/test_graph.py +0 -0
  37. {rangekeeper-0.8.32 → rangekeeper-0.8.34}/tests/test_measures.py +0 -0
  38. {rangekeeper-0.8.32 → rangekeeper-0.8.34}/tests/test_models.py +0 -0
  39. {rangekeeper-0.8.32 → rangekeeper-0.8.34}/tests/test_projections.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Rangekeeper
3
- Version: 0.8.32
3
+ Version: 0.8.34
4
4
  Summary: A Python library assisting financial modelling in real estate asset & development planning, decision-making, cashflow forecasting, and scenario analysis.
5
5
  Author-email: Daniel Fink <danfink@mit.edu>
6
6
  License-Expression: MPL-2.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Rangekeeper
3
- Version: 0.8.32
3
+ Version: 0.8.34
4
4
  Summary: A Python library assisting financial modelling in real estate asset & development planning, decision-making, cashflow forecasting, and scenario analysis.
5
5
  Author-email: Daniel Fink <danfink@mit.edu>
6
6
  License-Expression: MPL-2.0
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "Rangekeeper"
3
- version = "0.8.32"
3
+ version = "0.8.34"
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 = [
6
6
  { name = "Daniel Fink", email = "danfink@mit.edu" }
@@ -277,10 +277,23 @@ class Sequence:
277
277
  :param bound: A terminating condition; either a pd.Timestamp end date or a (integer) number of periods
278
278
  """
279
279
  if isinstance(bound, datetime.date):
280
+ freq = Type.period(frequency)
281
+
282
+ # Align to the start & ends of first & last periods
283
+ aligned_start = pd.Period(
284
+ value=include_start,
285
+ freq=freq,
286
+ ).start_time.date()
287
+
288
+ aligned_end = pd.Period(
289
+ value=bound,
290
+ freq=freq,
291
+ ).end_time.date()
292
+
280
293
  return pd.period_range(
281
- start=include_start,
282
- end=bound,
283
- freq=Type.period(frequency),
294
+ start=aligned_start,
295
+ end=aligned_end,
296
+ freq=freq,
284
297
  name="periods",
285
298
  )
286
299
  elif isinstance(bound, int):
@@ -272,9 +272,9 @@ class Flow:
272
272
 
273
273
  return cls(movements=movements, units=units, name=name)
274
274
 
275
- def invert(self) -> Flow:
275
+ def negate(self) -> Flow:
276
276
  """
277
- Returns a Flow with movement values inverted (multiplied by -1)
277
+ Returns a Flow with movement values negated (multiplied by -1)
278
278
  """
279
279
  return self.__class__(
280
280
  movements=self.movements.copy(deep=True).multiply(-1),
@@ -403,25 +403,44 @@ class Flow:
403
403
 
404
404
  def to_periods(
405
405
  self,
406
- frequency: rk.duration.Type,
407
- origin: Optional[Union[str, pd.Timestamp]] = "end_day",
406
+ index: pd.PeriodIndex,
408
407
  ) -> pd.Series:
409
408
  """
410
409
  Returns a pd.Series (of index pd.PeriodIndex) with movements summed to specified frequency
411
410
  """
412
- return (
413
- self.resample(
414
- frequency=frequency,
415
- origin=origin,
416
- )
417
- .movements.to_period(
418
- freq=rk.duration.Type.period(frequency),
419
- copy=True,
420
- )
421
- .rename_axis("period")
422
- .groupby(level="period")
423
- .sum()
411
+ resampled = self.resample(
412
+ frequency=rk.duration.Type.from_value(index.freqstr),
413
+ origin=index[0].start_time.date(),
414
+ )
415
+
416
+ return resampled.movements.to_period(freq=index.freq).reindex(
417
+ index=index,
418
+ method="pad",
419
+ limit=1,
420
+ fill_value=0,
424
421
  )
422
+ # return self.resample(
423
+ # frequency=rk.duration.Type.from_value(index.freqstr)
424
+ # ).movements.reindex(
425
+ # index=index,
426
+ # # method="pad",
427
+ # # limit=1,
428
+ # fill_value=0,
429
+ # )
430
+
431
+ # return (
432
+ # # self.resample(
433
+ # # frequency=frequency,
434
+ # # origin=origin,
435
+ # # )
436
+ # self.movements.to_period(
437
+ # freq=rk.duration.Type.period(frequency),
438
+ # copy=True,
439
+ # )
440
+ # .rename_axis("period")
441
+ # .groupby(level="period")
442
+ # .sum()
443
+ # )
425
444
 
426
445
  def earliest(self) -> datetime.date:
427
446
  """
@@ -550,20 +569,22 @@ class Stream:
550
569
  self.end_date = max(dates)
551
570
  """The latest date of the Stream's constituent Flows' movements."""
552
571
 
572
+ self.index = rk.duration.Sequence.from_bounds(
573
+ include_start=self.start_date,
574
+ bound=self.end_date,
575
+ frequency=self.frequency,
576
+ )
577
+
553
578
  self._resampled_flows = [
554
- flow.to_periods(
555
- frequency=self.frequency,
556
- origin=self.start_date,
557
- )
558
- for flow in self.flows
579
+ flow.to_periods(index=self.index) for flow in self.flows
559
580
  ]
560
581
  self.frame = (
561
582
  pd.concat(
562
583
  self._resampled_flows,
563
584
  axis=1,
564
585
  )
565
- .fillna(0)
566
- .sort_index()
586
+ # .fillna(0)
587
+ # .sort_index()
567
588
  )
568
589
  """
569
590
  A pd.DataFrame of the Stream's flow Flows accumulated into the Stream's frequency
@@ -584,7 +605,7 @@ class Stream:
584
605
 
585
606
  formatted_flows = []
586
607
  for flow in self.flows:
587
- series = flow.to_periods(frequency=self.frequency)
608
+ series = flow.to_periods(index=self.frame.index)
588
609
  formatted_flows.append(
589
610
  _format_series(
590
611
  series=series,
@@ -108,7 +108,7 @@ class ExAnteInflexibleModel:
108
108
  name="Operating Expenses",
109
109
  movements=self.pgi.movements * params["opex_pgi_ratio"],
110
110
  units=currency.units,
111
- ).invert()
111
+ ).negate()
112
112
  self.noi = rk.flux.Stream(
113
113
  name="Net Operating Income",
114
114
  flows=[self.egi, self.opex],
@@ -118,7 +118,7 @@ class ExAnteInflexibleModel:
118
118
  name="Capital Expenditures",
119
119
  movements=self.pgi.movements * params["capex_pgi_ratio"],
120
120
  units=currency.units,
121
- ).invert()
121
+ ).negate()
122
122
  self.net_cfs = rk.flux.Stream(
123
123
  name="Net Annual Cashflows",
124
124
  flows=[self.noi, self.capex],
@@ -264,7 +264,7 @@ class ExPostInflexibleModel:
264
264
  name="Operating Expenses",
265
265
  movements=self.pgi.movements * self.params["opex_pgi_ratio"],
266
266
  units=currency.units,
267
- ).invert()
267
+ ).negate()
268
268
  self.noi = rk.flux.Stream(
269
269
  name="Net Operating Income",
270
270
  flows=[self.egi, self.opex],
@@ -274,7 +274,7 @@ class ExPostInflexibleModel:
274
274
  name="Capital Expenditures",
275
275
  movements=self.pgi.movements * self.params["capex_pgi_ratio"],
276
276
  units=currency.units,
277
- ).invert()
277
+ ).negate()
278
278
  self.net_cfs = rk.flux.Stream(
279
279
  name="Net Annual Cashflows",
280
280
  flows=[self.noi, self.capex],
@@ -100,8 +100,8 @@ class Model:
100
100
  starting=0,
101
101
  transactions=rk.flux.Stream(
102
102
  flows=[
103
- self.equity.overdraft.diff().invert(),
104
- self.payments.invert(),
103
+ self.equity.overdraft.diff().negate(),
104
+ self.payments.negate(),
105
105
  ],
106
106
  frequency=self.params["frequency"],
107
107
  ).sum(),
@@ -252,7 +252,7 @@ class TestFinancial:
252
252
  data=transactions,
253
253
  sequence=sequence,
254
254
  units=currency.units,
255
- ).invert(),
255
+ ).negate(),
256
256
  frequency=self.params["frequency"],
257
257
  type=rk.formula.financial.Account.Type.SIMPLE,
258
258
  rate=rate,
@@ -277,7 +277,7 @@ class TestFinancial:
277
277
  def test_capitalized_interest(self):
278
278
  account = rk.formula.financial.Account(
279
279
  starting=0,
280
- transactions=self.model.draws.sum().invert(),
280
+ transactions=self.model.draws.sum().negate(),
281
281
  frequency=self.params["frequency"],
282
282
  type=rk.formula.financial.Account.Type.CAPITALIZED,
283
283
  rate=self.params["interest_rate_pa"]
@@ -297,8 +297,8 @@ class TestFinancial:
297
297
  transactions = rk.flux.Stream(
298
298
  name="Transactions",
299
299
  flows=[
300
- self.model.draws.sum().invert(),
301
- self.model.payments.invert(),
300
+ self.model.draws.sum().negate(),
301
+ self.model.payments.negate(),
302
302
  ],
303
303
  frequency=self.params["frequency"],
304
304
  )
@@ -326,13 +326,13 @@ class TestFinancial:
326
326
  def test_balances(self):
327
327
  transactions = rk.flux.Stream(
328
328
  name="Transactions",
329
- flows=[self.model.draws.sum().invert()],
329
+ flows=[self.model.draws.sum().negate()],
330
330
  frequency=self.params["frequency"],
331
331
  )
332
332
 
333
333
  equity = rk.formula.financial.Account(
334
334
  starting=176631.99,
335
- transactions=transactions.sum().invert(),
335
+ transactions=transactions.sum().negate(),
336
336
  frequency=self.params["frequency"],
337
337
  type=rk.formula.financial.Account.Type.SIMPLE,
338
338
  name="Equity Account",
@@ -349,8 +349,8 @@ class TestFinancial:
349
349
  starting=0,
350
350
  transactions=rk.flux.Stream(
351
351
  flows=[
352
- equity.overdraft.diff().invert(),
353
- self.model.payments.invert(),
352
+ equity.overdraft.diff().negate(),
353
+ self.model.payments.negate(),
354
354
  ],
355
355
  frequency=self.params["frequency"],
356
356
  ).sum(),
@@ -369,7 +369,7 @@ class TestFinancial:
369
369
  profit = rk.flux.Stream(
370
370
  flows=[
371
371
  equity.diff(),
372
- loan.overdraft.diff().invert(),
372
+ loan.overdraft.diff().negate(),
373
373
  ],
374
374
  frequency=self.params["frequency"],
375
375
  name="Profit",
@@ -204,7 +204,7 @@ class TestFlow:
204
204
  assert collapse.movements.size == 1
205
205
  assert collapse.movements.array[0] == 100.0
206
206
 
207
- invert_flow = flow.invert()
207
+ invert_flow = flow.negate()
208
208
 
209
209
  def test_flow_inversion(self):
210
210
  # TestFlow.invert_flow.display()
@@ -226,17 +226,18 @@ class TestFlow:
226
226
  assert TestFlow.resample_flow.movements.iloc[1] == -48
227
227
  assert TestFlow.resample_flow.movements.iloc[2] == pytest.approx(-4)
228
228
 
229
- to_periods = flow.to_periods(frequency=rk.duration.Type.YEAR)
229
+ # to_periods = flow.to_periods(index=rk.duration.Type.YEAR)
230
230
 
231
231
  def test_conversion_to_period_index(self):
232
+ pass
232
233
  # print(TestFlow.to_periods)
233
234
  # assert TestFlow.to_periods
234
235
 
235
- resampled = TestFlow.invert_flow.resample(frequency=rk.duration.Type.BIWEEK)
236
- resampled.display()
236
+ # resampled = TestFlow.invert_flow.resample(frequency=rk.duration.Type.BIWEEK)
237
+ # resampled.display()
237
238
 
238
- fortnightly = TestFlow.invert_flow.to_periods(frequency=rk.duration.Type.BIWEEK)
239
- print(fortnightly)
239
+ # fortnightly = TestFlow.invert_flow.to_periods(frequency=rk.duration.Type.BIWEEK)
240
+ # print(fortnightly)
240
241
 
241
242
  def test_distribution_as_input(self):
242
243
  periods = rk.duration.Sequence.from_bounds(
@@ -309,43 +310,96 @@ class TestStream:
309
310
  units=currency.units,
310
311
  )
311
312
 
312
- stream = rk.flux.Stream(
313
- name="stream", flows=[flow1, flow2], frequency=rk.duration.Type.MONTH
313
+ flow3 = rk.flux.Flow.from_projection(
314
+ name="fortnightly_flow",
315
+ value=-20.0,
316
+ proj=rk.projection.Distribution(
317
+ form=rk.distribution.Uniform(),
318
+ sequence=rk.duration.Sequence.from_bounds(
319
+ include_start=datetime.date(2020, 1, 31),
320
+ bound=datetime.date(2022, 1, 1),
321
+ frequency=rk.duration.Type.BIWEEK,
322
+ ),
323
+ ),
324
+ units=currency.units,
314
325
  )
315
326
 
316
- stream.display()
327
+ stream = rk.flux.Stream(
328
+ name="stream",
329
+ flows=[flow1, flow2, flow3],
330
+ frequency=rk.duration.Type.BIWEEK,
331
+ )
317
332
 
318
333
  def test_stream_validity(self):
319
- assert TestStream.stream.name == "stream"
320
- assert len(TestStream.stream.flows) == 2
321
- assert TestStream.stream.start_date == pd.Timestamp(2020, 3, 1)
322
- assert TestStream.stream.end_date == pd.Timestamp(2022, 12, 31)
334
+ print(TestStream.stream.start_date)
335
+ # print(TestStream.stream.index)
336
+ print(TestStream.stream.index.freq)
337
+ print(TestStream.stream.index.freqstr)
323
338
 
324
339
  TestStream.stream.display()
340
+ TestStream.stream.sum().display()
325
341
 
326
- assert (
327
- TestStream.stream.sum().movements.index.size == 24 + 10
328
- ) # Two full years plus March-Dec inclusive
329
- assert TestStream.stream.frame["weekly_flow"].sum() == -50
330
- assert TestStream.stream.frame.index.freqstr == "M"
331
-
332
- product = TestStream.stream.product(
333
- name="product", registry=units, scope=dict(globals(), **locals())
334
- )
335
- product.display()
342
+ # TestStream.flow1.display()
343
+ TestStream.flow1.resample(frequency=rk.duration.Type.BIWEEK).display()
344
+ #
345
+ print(TestStream.flow1.to_periods(index=TestStream.stream.index).to_string())
346
+ # print(f"\nFlow2 resampled to biweek:")
347
+ # TestStream.flow2.resample(
348
+ # frequency=rk.duration.Type.BIWEEK,
349
+ # origin=TestStream.stream.start_date,
350
+ # ).display()
351
+ # print(f"\nFlow2 to biweek periods:")
352
+ # print(
353
+ # TestStream.flow2.to_periods(
354
+ # index=
355
+ # origin=TestStream.stream.start_date,
356
+ # )
357
+ # )
358
+ #
359
+ # print(f"\nFlow3 resampled to biweek:")
360
+ # TestStream.flow3.resample(
361
+ # frequency=rk.duration.Type.BIWEEK,
362
+ # origin=TestStream.stream.start_date,
363
+ # ).display()
364
+ # print(f"\nFlow3 to biweek periods:")
365
+ # print(
366
+ # TestStream.flow3.to_periods(
367
+ # frequency=rk.duration.Type.BIWEEK,
368
+ # origin=TestStream.stream.start_date,
369
+ # )
370
+ # )
336
371
 
337
- datestamp = pd.Timestamp(2020, 12, 31)
338
- print(TestStream.stream.frame["yearly_flow"][datestamp])
339
- print(TestStream.stream.frame["weekly_flow"][datestamp])
340
- assert product.movements[datestamp] == approx(-125.786163522)
341
-
342
- cumsum_flow = rk.flux.Flow(
343
- name="cumsum_flow",
344
- movements=TestStream.flow1.movements.cumsum(),
345
- units=currency.units,
346
- )
347
- cumsum_flow.display()
348
- assert cumsum_flow.movements.iloc[-1] == 100
372
+ # assert TestStream.stream.name == "stream"
373
+ # assert len(TestStream.stream.flows) == 3
374
+ # assert TestStream.stream.start_date == pd.Timestamp(2020, 3, 1)
375
+ # assert TestStream.stream.end_date == pd.Timestamp(2022, 12, 31)
376
+ #
377
+ # TestStream.stream.display()
378
+ #
379
+ # assert (
380
+ # TestStream.stream.sum().movements.index.size == 24 + 10
381
+ # ) # Two full years plus March-Dec inclusive
382
+ # assert TestStream.stream.frame["weekly_flow"].sum() == -50
383
+ # assert TestStream.stream.frame.index.freqstr == "M"
384
+ #
385
+ # product = TestStream.stream.product(
386
+ # name="product",
387
+ # registry=units,
388
+ # )
389
+ # product.display()
390
+ #
391
+ # datestamp = pd.Timestamp(2020, 12, 31)
392
+ # print(TestStream.stream.frame["yearly_flow"][datestamp])
393
+ # print(TestStream.stream.frame["weekly_flow"][datestamp])
394
+ # assert product.movements[datestamp] == approx(-125.786163522)
395
+ #
396
+ # cumsum_flow = rk.flux.Flow(
397
+ # name="cumsum_flow",
398
+ # movements=TestStream.flow1.movements.cumsum(),
399
+ # units=currency.units,
400
+ # )
401
+ # cumsum_flow.display()
402
+ # assert cumsum_flow.movements.iloc[-1] == 100
349
403
 
350
404
  def test_stream_aggregation(self):
351
405
  flow2_sqm = TestStream.flow2.duplicate()
@@ -358,7 +412,8 @@ class TestStream:
358
412
  )
359
413
 
360
414
  stream_sqm_agg = stream_sqm.product(
361
- name="stream_sqm_agg", scope=scope, registry=units
415
+ name="stream_sqm_agg",
416
+ registry=units,
362
417
  )
363
418
 
364
419
  assert stream_sqm_agg.units == "AUD * squaremeter"
File without changes
File without changes