summaryrefslogtreecommitdiff
path: root/finance/simulate.py
diff options
context:
space:
mode:
Diffstat (limited to 'finance/simulate.py')
-rw-r--r--finance/simulate.py59
1 files changed, 59 insertions, 0 deletions
diff --git a/finance/simulate.py b/finance/simulate.py
new file mode 100644
index 0000000..3f8e3ad
--- /dev/null
+++ b/finance/simulate.py
@@ -0,0 +1,59 @@
+from datetime import datetime
+from decimal import Decimal
+from typing import Generator
+
+from finance.model import Flow
+
+
+def integrate(flow: Flow, start: datetime, end: datetime) -> Decimal:
+ """Integrate the flow between two dates to an amount of money"""
+
+ payments: int = 0
+
+ if flow.since is not None:
+ if start < flow.since:
+ start = flow.since
+
+ if flow.until is not None:
+ if end > flow.until:
+ end = flow.until
+
+ for candidate in monthly_candidates(start):
+ if start <= candidate <= end:
+ payments += 1
+ if candidate > end:
+ break
+
+ return flow.amount * Decimal(payments)
+
+
+def monthly_candidates(start: datetime) -> Generator[datetime, None, None]:
+ current = datetime(start.year, start.month, 1)
+ while True:
+ yield current
+ if current.month == 12:
+ current = datetime(current.year + 1, 1, 1)
+ else:
+ current = datetime(current.year, current.month + 1, 1)
+
+
+def simulate(
+ start: datetime, end: datetime, flows: tuple[Flow, ...]
+) -> list[tuple[datetime, Decimal]]:
+ dates: list[datetime] = []
+ values: list[Decimal] = []
+
+ for candidate in monthly_candidates(start):
+ if start <= candidate <= end:
+ dates.append(candidate)
+
+ if candidate > end:
+ break
+
+ for date in dates:
+ value = Decimal(0.0)
+ for flow in flows:
+ value += integrate(flow, start, date)
+ values.append(value)
+
+ return [(date, values[index]) for index, date in enumerate(dates)]