Repository: MartinPyka/financial_life
Branch: master
Commit: 24c4ce0a026f
Files: 48
Total size: 185.7 KB
Directory structure:
gitextract_j_yn7vd1/
├── .github/
│ └── workflows/
│ └── sonarcloud.yml
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── MANIFEST
├── README.md
├── docs/
│ ├── 01_first_simulation.md
│ ├── 02_using_callables_for_dynamic_changes.md
│ ├── 03_dependencies_between_accounts.md
│ └── README.md
├── financial_life/
│ ├── README.md
│ ├── __init__.py
│ ├── calendar_help/
│ │ └── __init__.py
│ ├── constants/
│ │ ├── __init__.py
│ │ └── intervals.py
│ ├── examples/
│ │ ├── README.md
│ │ ├── __init__.py
│ │ ├── dependencies.py
│ │ ├── meta_data.md
│ │ ├── meta_data.py
│ │ └── simple_examples.py
│ ├── financing/
│ │ ├── __init__.py
│ │ ├── accounts.py
│ │ ├── colors.py
│ │ ├── identity.py
│ │ ├── plotting.py
│ │ ├── test_financing.py
│ │ ├── test_meta.py
│ │ ├── test_status.py
│ │ └── validate.py
│ ├── products/
│ │ ├── __init__.py
│ │ └── germany/
│ │ ├── __init__.py
│ │ └── lbs/
│ │ └── __init__.py
│ ├── reports/
│ │ ├── __init__.py
│ │ ├── excel.py
│ │ └── html.py
│ ├── tax/
│ │ ├── __init__.py
│ │ └── germany/
│ │ └── __init__.py
│ ├── templates/
│ │ ├── __init__.py
│ │ └── html/
│ │ ├── __init__.py
│ │ └── standard/
│ │ ├── __init__.py
│ │ ├── account_details.html
│ │ ├── index.html
│ │ └── render.py
│ └── test_general.py
├── requirements.txt
├── setup.py
└── unittests.sh
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/sonarcloud.yml
================================================
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# This workflow helps you trigger a SonarCloud analysis of your code and populates
# GitHub Code Scanning alerts with the vulnerabilities found.
# Free for open source project.
# 1. Login to SonarCloud.io using your GitHub account
# 2. Import your project on SonarCloud
# * Add your GitHub organization first, then add your repository as a new project.
# * Please note that many languages are eligible for automatic analysis,
# which means that the analysis will start automatically without the need to set up GitHub Actions.
# * This behavior can be changed in Administration > Analysis Method.
#
# 3. Follow the SonarCloud in-product tutorial
# * a. Copy/paste the Project Key and the Organization Key into the args parameter below
# (You'll find this information in SonarCloud. Click on "Information" at the bottom left)
#
# * b. Generate a new token and add it to your Github repository's secrets using the name SONAR_TOKEN
# (On SonarCloud, click on your avatar on top-right > My account > Security
# or go directly to https://sonarcloud.io/account/security/)
name: SonarCloud analysis
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
workflow_dispatch:
permissions:
pull-requests: read # allows SonarCloud to decorate PRs with analysis results
jobs:
Analysis:
runs-on: ubuntu-latest
steps:
- name: Analyze with SonarCloud
# You can pin the exact commit or the version.
# uses: SonarSource/sonarcloud-github-action@de2e56b42aa84d0b1c5b622644ac17e505c9a049
uses: SonarSource/sonarcloud-github-action@de2e56b42aa84d0b1c5b622644ac17e505c9a049
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} # Generate a token on Sonarcloud.io, add it to the secrets of this repo with the name SONAR_TOKEN (Settings > Secrets > Actions > add new repository secret)
with:
# Additional arguments for the sonarcloud scanner
args:
# Unique keys of your project and organization. You can find them in SonarCloud > Information (bottom-left menu)
# mandatory
-Dsonar.projectKey=
-Dsonar.organization=
-X
# Comma-separated paths to directories containing main source files.
#-Dsonar.sources= # optional, default is project base directory
# When you need the analysis to take place in a directory other than the one from which it was launched
#-Dsonar.projectBaseDir= # optional, default is .
# Comma-separated paths to directories containing test source files.
#-Dsonar.tests= # optional. For more info about Code Coverage, please refer to https://docs.sonarcloud.io/enriching/test-coverage/overview/
# Adds more detail to both client and server-side analysis logs, activating DEBUG mode for the scanner, and adding client-side environment variables and system properties to the server-side log of analysis report processing.
#-Dsonar.verbose= # optional, default is false
================================================
FILE: .gitignore
================================================
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover
.hypothesis/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# IPython Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# dotenv
.env
# virtualenv
venv/
ENV/
# Spyder project settings
.spyderproject
# Rope project settings
.ropeproject
# Release jobs
release.sh
================================================
FILE: CHANGELOG.md
================================================
# 0.9.4 (31.05.2017
* added support for export to Excel
# 0.9.3 (13.01.2017)
* changed some bugs for rendering HTML content
# 0.9.2 (05.01.2017)
* financial_life now supports meta-data for account objects and payments
# 0.9.1
* minor issues with DataFrame support
* plots in examples are displayed, when examples are called from command line
* improved setup-script
# 0.9
* financial-life supports DataFrames
# 0.8
* first release on Github
================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: MANIFEST
================================================
# file GENERATED by distutils, do NOT edit
setup.py
financial_life/__init__.py
financial_life/test_general.py
financial_life/calendar_help/__init__.py
financial_life/examples/__init__.py
financial_life/examples/dependencies.py
financial_life/examples/meta_data.py
financial_life/examples/simple_examples.py
financial_life/financing/__init__.py
financial_life/financing/accounts.py
financial_life/financing/colors.py
financial_life/financing/identity.py
financial_life/financing/plotting.py
financial_life/financing/test_financing.py
financial_life/financing/test_meta.py
financial_life/financing/test_status.py
financial_life/financing/validate.py
financial_life/products/germany/lbs/__init__.py
financial_life/reports/__init__.py
financial_life/reports/html.py
financial_life/tax/germany/__init__.py
financial_life/templates/html/standard/__init__.py
financial_life/templates/html/standard/account_details.html
financial_life/templates/html/standard/index.html
financial_life/templates/html/standard/render.py
================================================
FILE: README.md
================================================
# financial-life
A framework for analysing financial products in personalized contexts
<table>
<tr>
<td>
Latest Release
</td>
<td>
<img src="https://img.shields.io/pypi/v/financial_life.svg" alt="latest release" />
</td>
</tr>
<table>
[CHANGELOG.md](CHANGELOG.md)
# Description
financial_life is an opinionated framework written in Python that allows to simulate monetary flows between different types of accounts. These simulations allow a deeper understanding of financial plans and a better comparison of financial products (in particular loan conditions) for personal circumstances. With financial_life you can
* analyse loan conditions and payment strategies
* describe the properties of your financial plans with a few lines of code
* create dynamic monetary flows between accounts for modeling more realistic scenarios
* extend the code by controller functions (e.g. for modeling tax payments)
View [documentation](docs/README.md) for a more detailed introduction.
# Example
Say you want to model an account with regular income and payments to a loan
```python
from financial_life.financing import accounts as a
from datetime import timedelta, datetime
# create a private bank account and a loan account
account = a.Bank_Account(amount = 1000, interest = 0.001, name = 'Main account')
loan = a.Loan(amount = 100000, interest = 0.01, name = 'House Credit')
# add these accounts to the simulation
simulation = a.Simulation(account, loan)
# describe monetary flows between accounts
simulation.add_regular('Income', account, 2000, interval = 'monthly')
simulation.add_regular(account, loan, lambda: min(1500, -loan.account), interval = 'monthly')
# simulate for ten years
simulation.simulate(delta = timedelta(days=365*10))
# plot the data
simulation.plt_summary()
# print reports summarized by years
print(account.report.yearly())
print(loan.report.yearly())
# analyze data
print("Interests on bank account: %.2f" % sum(account.report.yearly().interest))
print("Interests on loan account: %.2f" % sum(loan.report.yearly().interest))
```
The output will look like this:
<img src="docs/img/simple_example_01_small.png" alt="Simple simulation in financial_life" width="800">
Main account
Date account output input interest
---------- --------- --------- -------- ----------
31.12.2016 2000.32 -3000.00 4000.00 0.32
31.12.2017 8005.58 -18000.00 24000.00 5.26
31.12.2018 14016.85 -18000.00 24000.00 11.27
31.12.2019 20034.13 -18000.00 24000.00 17.28
31.12.2020 26057.42 -18000.00 24000.00 23.29
31.12.2021 32086.74 -18000.00 24000.00 29.32
31.12.2022 46271.00 -9853.30 24000.00 37.56
31.12.2023 70330.32 0.00 24000.00 59.32
31.12.2024 94413.68 0.00 24000.00 83.36
31.12.2025 118521.15 0.00 24000.00 107.47
01.10.2026 138521.15 0.00 20000.00 0.00
House Credit
Date account interest payment
---------- --------- ---------- ---------
31.12.2016 -97190.22 -190.22 3000.00
31.12.2017 -80064.23 -874.01 18000.00
31.12.2018 -62766.98 -702.75 18000.00
31.12.2019 -45296.76 -529.78 18000.00
31.12.2020 -27652.02 -355.26 18000.00
31.12.2021 -9830.65 -178.63 18000.00
31.12.2022 0.00 -22.65 9853.30
31.12.2023 0.00 0.00 0.00
31.12.2024 0.00 0.00 0.00
31.12.2025 0.00 0.00 0.00
Interests on bank account: 374.45
Interests on loan account: -2853.30
Now let's say, we put some money on a special savings account with better interests, because we want to purchase in two years a car. With financial_life, you just add the necessary changes to your model.
```python
# create new account
savings = a.Bank_Account(amount = 5000, interest = 0.007, name = 'Savings')
# add it to the simulation (or create a new simulation with all three accounts)
simulation.add_account(savings)
# add regular payment to the savings-account
simulation.add_regular(account, savings, 500, interval = 'monthly')
# somewhere in the distant future we will make a payment to
# the vendor of a car
simulation.add_unique(savings, 'Vendor of a car', 10000, '17.03.2019')
```
The plot will now include the savings-account as well.
<img src="docs/img/simple_example_02_small.png" alt="Simple simulation in financial_life" width="800">
You can also export the simulation to HTML to explore your model in the browser:
```python
from financial_life.reports import html
html.report(simulation, style="standard", output_dir = result_folder)
```
<img src="docs/img/html_summary_01.png" alt="Simple simulation in financial_life" width="800" height="407">
You can analyse the reports as [pandas](https://github.com/pandas-dev/pandas) DataFrame as well and export it to excel:
```python
import pandas as pd
from financial_life.reports import excel
account.report.as_df() # Hello pandas
excel.report(simulation, filename='reports.xls') # explore the results in excel
```
[Here](financial_life/examples/README.md) are more examples. financial_life supports:
* [dependencies between accounts](financial_life/examples/dependencies.py), e.g. to model how the ownership of a property rises when the loan decreases
* [meta-data](financial_life/examples/meta_data.md), e.g. for writing tax-calculations, which require additional knowledge about your payments
* [controller-functions](financial_life/examples/meta_data.md) for dynamic changes of the simulation properties during simulation
# Installation
financial_life is available in version 0.9.2. It is written in Python 3.4 and has not been tested for Python 2.x.
To get a working environment, simply do
git clone https://github.com/MartinPyka/financial_life.git
cd financial_life
virtualenv venv
source venv/bin/activate
pip install -r requirements.txt
# test an example
python financial_life/examples/simple_examples.py
For installing the package:
git clone https://github.com/MartinPyka/financial_life.git
cd financial_life
python setup.py install
Or use pip
pip install financial_life
You can checkout the example with
python financial_life/examples/simple_examples.py
# Why financial_life
financial_life was designed with the idea in mind that any line of code should contribute to the description of the problem you want to model. In spreadsheets, you would deal with a lot of auxiliary tables to accurately calculate the course of a loan influenced by incoming payments and generated interests. In financial_life, you just create your loan account with the given interests rate and you define the regular payments going into this loan account. That's it. Changes in the model and the exploration of different parameters within this model are therefore way easier to accomplish than in a spreadsheet-based simulation.
================================================
FILE: docs/01_first_simulation.md
================================================
# The first simulation
In this chapter, we are going to introduce the basic concepts of creating a simulation in order to analyse monetary flows and growth on bank accounts.
In the following, financial_life will be abbreviated with fl.
Each simulation setup can be devided into three different steps:
1. Define the accounts involved in the simulation
2. Define monetary flows between these accounts
3. Simulate
After that you have several options to explore the outcome of your simulation.
## 1. Define the accounts involved in the simulation
The module `financial_life.financing.accounts` features some basic accounts like a normal bank account with yearly interests and a loan account that does not allow to be in the positive value range and only permits money transferd to the account but not from the account.
The package also contains a Property account that establishes a dependency to a loan account. We will come to that later.
Lets start with creating an ordinary bank account, which you would use to receive your salary and pay your bills. This account is called `Bank_Account` and it is defined in the following way:
```python
class Bank_Account(Account):
""" This is a normal bank account that can be used to manage income and
outgoings within a normal household """
def __init__(self, amount, interest, date = None, name = None):
```
<table>
<tr>
<th>
Keyword Argument
</th>
<th>
Description
</th>
</tr>
<tr>
<td>
amount
</td>
<td>
The amount of money to start with
</td>
</tr>
<tr>
<td>
interest
</td>
<td>
The yearly interest rate for this account. 1% correspond to 0.01 as argument value
</td>
</tr>
<tr>
<td>
date
</td>
<td>
The date, when this account should exist. Before this date, the account will not be simulated. For quick simulation, where this nuance is not relevant, this field can be left empty. As date, datetime and strings in the format '%d.%m.%y', '%m/%d/%Y' are accepted.
</td>
</tr>
<tr>
<td>
name
</td>
<td>
Simply a string representation of the account id. If empty, fl will create a random string sequence.
</td>
</tr>
</table>
So an ordinary bank account with a volume of 1000 and an interest rate of 0.1% is defined like this.
```python
from financial_life.financing import accounts as a
account = a.Bank_Account(amount = 1000,
interest = 0.001,
name = 'Main account')
```
A `loan` account, although its behavior is slightly different, is defined in the same manner. For a loan of 100,000 you would create a `loan` account like this:
```python
loan = a.Loan(amount = 100000, interest = 0.01, name = 'House Credit')
```
Last but not least, you need to assign these accounts to a simulation. This is a one-liner as well.
```python
simulation = a.Simulation(account, loan)
```
## 2. Define monetary flows between these accounts
Next. we define monetary flows between these accounts. Here, we will introduce the methods for regular and unique payments but we will cover only a part of it. In later chapters, we will dig deeper into the kind of stuff that you can create with it.
The simulation class is also defined in `financial_life.financing.accounts` and looks like this
```python
def add_regular(self, from_acc, to_acc, payment, interval,
date_start=datetime.min,
day=1,
name = '',
date_stop = None,
fixed = False):
def add_unique(self, from_acc, to_acc, payment,
date,
name = '',
fixed = False):
```
<table>
<tr>
<th>
Keyword Argument
</th>
<th>
Description
</th>
</tr>
<tr>
<td>
from_acc,
to_acc
</td>
<td>
The account objects that define from where money flows and to which account money flows. For money transfers that involve accounts outside of the simulation, a string can be used.
</td>
</tr>
<tr>
<td>
payment
</td>
<td>
The amount of money that is transfered.
</td>
</tr>
<tr>
<td>
interval
</td>
<td>
'monthly' or 'yearly' are possible currently.
</td>
</tr>
<tr>
<td>
date_start / date
</td>
<td>
The start date of this regular payments. It can be either a datetime object or a string. It has also a default value, so it does not need to be used for quick simulations.
</td>
</tr>
<tr>
<td>
day
</td>
<td>
The day in a month on which this transfer should be initiated.
</td>
</tr>
<tr>
<td>
name
</td>
<td>
Name of the money transfer. Corresponds to the subject in a normal money transfer.
</td>
</tr>
<tr>
<td>
date_stop
</td>
<td>
Stop date of regular payments. Can be either datetime, string or even a callable. We will cover this later.
</td>
</tr>
<tr>
<td>
fixed
</td>
<td>
Whether sender and receiver of the money should insist of the full money transfer. If, for example, a loan has only 100 to be payed, but the transfer defines 150, this would cause an error, when `fixed` is true. If it is false, the rest money is transfered back.
</td>
</tr>
</table>
Here are two examples of how money flows can be defined. The first one describes the salary, coming from an account outside of this simulation. The second one is a money transfer within our simulation.
```python
simulation.add_regular('Income', account, 2000, interval = 'monthly')
simulation.add_regular(account, loan, 1500, interval = 'monthly')
```
## 3. Simulate
The simulation is started for a given amount of time. This period is either defined through a stop date or by a time interval delta. If both keywords are used the earlier stop date ends the simulation.
```python
def simulate(self, date_stop = None, delta = None)
```
<table>
<tr>
<th>
Keyword Argument
</th>
<th>
Description
</th>
</tr>
<tr>
<td>
date_stop
</td>
<td>
Stop date of the simulation
</td>
</tr>
<tr>
<td>
delta
</td>
<td>
Number of days to simulate.
</td>
</tr>
</table>
In order to simulate our model for 10 years from now on, we use the delta-keyword.
```python
simulation.simulate(delta = timedelta(days=365*10))
```
## Explore the simulation
Here are some examples of exploring the outcome of your data.
```python
# use matplotlib for a graphical summary of the simulation
simulation.plt_summary()
# print reports summarized in years
print(account.report.yearly())
print(loan.report.yearly())
# analyze data
print("Interests on bank account: %.2f" % sum(account.report.interest))
print("Interests on loan account: %.2f" % sum(loan.report.interest))
# create html report
cwd = os.path.dirname(os.path.realpath(__file__))
result_folder = cwd + '/example2'
html.report(simulation, style="standard", output_dir = result_folder)
```
## Code snippet
And here is the code for the simulation again as a whole
```python
account = a.Bank_Account(amount = 1000, interest = 0.001, name = 'Main account')
loan = a.Loan(amount = 100000, interest = 0.01, name = 'House Credit')
simulation = a.Simulation(account, loan)
simulation.add_regular('Income', account, 2000, interval = 'monthly')
simulation.add_regular(account, loan, 1500, interval = 'monthly')
simulation.simulate(delta = timedelta(days=365*10))
simulation.plt_summary()
```
================================================
FILE: docs/02_using_callables_for_dynamic_changes.md
================================================
# Using callables for dynamic changes
Sometimes, parameters like the amount of money to be transfered or the stop date cannot be statically defined at the beginning of your simulation. Instead, they depend on the current state of your simulation.
Therefore, fl supports callables as arguments for some keywords, in order to cover more complex modeling scenarios.
## Dynamic payments
Callables can be used to determine the amount of money to be transfered from one account to the other. This means, instead of writing
```python
simulation.add_regular(from_acc=account,
to_acc=loan,
payment=1500,
interval='monthly')
```
we can make sure, that we transfer only the amount of money to the loan account that is necessary: either 1500 or what is left. We can achieve this with a simple lambda-function.
The loan account provide the property `loan.account`, which returns the debts we still need to pay. This is a negative number, therefore, we must put a minus-sign in front of it:
```python
simulation.add_regular(from_acc=account,
to_acc=loan,
payment=lambda: min(1500, -loan.account),
interval='monthly')
```
Let's go a step further and say we want to transfer at maximum 1500 but also make sure that we always have 2000 on our account and that we transfer only the money that is really need on the loan account.
```python
simulation.add_regular(from_acc=account,
to_acc=loan,
payment=lambda: min(
max(
min(1500, account.account-2000),
0),
-loan.account),
interval='monthly')
```
The max-statement in this lambda-function is included to make sure that when `account.account - 2000` is a negative number, we won't initiate a negative transfer to the loan-account (which would be captured by the loan-account anyway).
These callables are executed in each simulation cycle (which is every day within the simulation) and therefore decide depending on the simulation state how much money is transfered to the loan account.
================================================
FILE: docs/03_dependencies_between_accounts.md
================================================
# Dependencies between accounts
Modeling loan accounts can be helpful to calculate payment durations and interests costs. But modeling them alone, does not reflect the accumulation of asset that one gains by decreasing the amount of debts. For example, if you start a loan in order to buy a house, you would like to take into account that the value of the house is more and more part of your asset the more you decrease the loan. This means, there is a dependency between the loan you pay and the value of the house that belongs to your asset.
In fl, there is a class `Property` in order to model this relationship. And it can be used in the following way:
```python
loan = a.Loan(200000, 0.0185, name = 'Credit' )
house = a.Property(200000, 0, loan, name='House')
```
You create a new object of type `Property`, in which you state the value of the property, the amount of own capital you put into it, and the loan account on which it depends. When during the simulation, the loan goes down, the property will go up.
<img src="img/dependencies.png" alt="Dependency between loan and house" width="800">
Here is the [example code](../financial_life/examples/dependencies.py)
The class `Property` is defined in the following way:
```python
class Property(Account):
"""
This class can be used to reflect the amount of property that is gained
from filling up a loan. This account does nothing else than adjusting the
amount of property depending on the payments transfered to the loan class
"""
def __init__(self, property_value, amount, loan, date = None, name = None):
```
<table>
<tr>
<th>
Keyword Argument
</th>
<th>
Description
</th>
</tr>
<tr>
<td>
property_value
</td>
<td>
The value of the property.
</td>
</tr>
<tr>
<td>
amount
</td>
<td>
The amount of money that is already part of the own property.
</td>
</tr>
<tr>
<td>
loan
</td>
<td>
A loan object to which a dependency is created.
</td>
</tr>
<tr>
<td>
date
</td>
<td>
The date, when this account should exist. Before this date, the account will not be simulated. For quick simulation, where this nuance is not relevant, this field can be left empty. As date, datetime and strings in the format '%d.%m.%y', '%m/%d/%Y' are accepted.
</td>
</tr>
<tr>
<td>
name
</td>
<td>
Simply a string representation of the property id. If empty, fl will create a random string sequence.
</td>
</tr>
</table>
================================================
FILE: docs/README.md
================================================
# Documentation
This documentations aims to introduce the user into the concepts of financial_life to start developing your own applications.
## User guide
1. [The first simulation](01_first_simulation.md)
2. [Using callables for dynamic changes](02_using_callables_for_dynamic_changes.md)
3. [Dependencies between accounts](03_dependencies_between_accounts.md)
## Developers guide
1. Short intro into the main routines
2. Adding your own account class
================================================
FILE: financial_life/README.md
================================================
# Enter the code
Here, you are about to enter the code part of this repository. You might want to check out the [examples folder](examples/), where you will find another [README.md](examples/README.md). The other folders do not contain an MD file anymore.
================================================
FILE: financial_life/__init__.py
================================================
'''
Created on 03.12.2016
@author: martin
'''
__version__ = '0.9.4'
================================================
FILE: financial_life/calendar_help/__init__.py
================================================
from calendar import monthrange
from datetime import date
from datetime import timedelta
from datetime import datetime
class Bank_Date(datetime):
""" This is a helper class that adds some additional functionality
to the datetime class, like adding months to the date or calculating
the difference between two dates in months
"""
def is_end_of_month(self):
""" returns true, if the current day is the end of month """
return monthrange(self.year, self.month)[1] == self.day
def add_month(self, months):
""" introduces calculation with months """
new_year = self.year + int((self.month + months - 1)/12)
new_month = ((self.month + months - 1) % 12) + 1
new_day = min(self.day, monthrange(new_year, new_month)[1])
return Bank_Date(year = new_year, month = new_month, day = new_day)
def diff_months(self, sub2):
""" calculates the differences in months between two dates """
if not isinstance(sub2, datetime):
raise NotImplementedError
years = 0
months = 0
if (sub2.year > self.year):
years = max(0, sub2.year - (self.year + 1))
months = (12 - self._month + 1) + sub2.month
elif (sub2.year == self.year):
months = sub2.month - self.month
elif sub2.year < self.year:
years = min(0, sub2.year + 1 - self.year)
months = -(12 - sub2.month + 1) - self.month
return years * 12 + months
def get_days_per_year(year):
# returns the number of days per year
return 365 if monthrange(year, 2)[1] == 28 else 366
# deprecated, old methods for maniuplating datetime
def add_month(start_date, months):
""" introduces calculation with months """
new_year = start_date.year + int((start_date.month + months - 1)/12)
new_month = ((start_date.month + months - 1) % 12) + 1
new_day = min(start_date.day, monthrange(new_year, new_month)[1])
new_date = date(new_year, new_month, new_day)
return new_date
def diff_months(sub1, sub2):
""" calculates the differences in months between two dates """
years = 0
months = 0
if (sub2.year > sub1.year):
years = max(0, sub2.year - (sub1.year + 1))
months = (12 - sub1.month + 1) + sub2.month
elif (sub2.year == sub1.year):
months = sub2.month - sub1.month
elif sub2.year < sub1.year:
years = min(0, sub2.year + 1 - sub1.year)
months = -(12 - sub2.month + 1) - sub1.month
return years * 12 + months
================================================
FILE: financial_life/constants/__init__.py
================================================
================================================
FILE: financial_life/constants/intervals.py
================================================
'''
Created on 20.01.2017
This module lists all intervals, that can be used through out fl
@author: martin
'''
daily = 'daily'
monthly = 'monthly'
yearly = 'yearly'
================================================
FILE: financial_life/examples/README.md
================================================
# Examples
In this folder, you find a list of examples that will help you getting started.
### simple_examples.py
[simple_examples.py](simple_examples.py) shows three beginner examples that are also highlighted on the [front-page](../../README.md) of this repository.
### dependencies.py
[dependencies.py](dependencies.py) showcases the account-class `Property`, which uses access to a loan class to determine, how much value of a given property has been transfered to the owner, when the loan is decreased. The key lines are:
```python
loan = a.Loan(200000, 0.0185, name = 'Credit' )
# the class property defines a dependency on loan. When loan
# decreases, the house-property increases
house = a.Property(200000, 0, loan, name='House')
```
The value of `house` will becomes bigger when the value of `loan` decreases. See also more information about this construct in the [documentation](../../docs/03_dependencies_between_accounts.md).
### meta_data.py
[meta_data.py](meta_data.py) demonstrates the usage of meta-data and controllers, in order to model tax returns. A full explanation of this example can be found [here](meta_data.md).
================================================
FILE: financial_life/examples/__init__.py
================================================
================================================
FILE: financial_life/examples/dependencies.py
================================================
'''
Created on 14.08.2016
@author: martin
'''
# standard libraries
from datetime import timedelta, datetime
import os
# third-party libraries
# own libraries
from financial_life.financing import accounts as a
from financial_life.reports import html
from matplotlib.pyplot import show
def dependencies():
loan = a.Loan(200000, 0.0185, name = 'Credit' )
# the class property defines a dependency on loan. When loan
# decreases, the house-property increases
house = a.Property(200000, 0, loan, name='House')
simulation = a.Simulation(loan, house)
simulation.add_regular('Income', loan, 1000, interval = 'monthly')
simulation.simulate(delta = timedelta(days=365*20))
simulation.plt_summary()
show(block=True)
if __name__ == '__main__':
dependencies()
================================================
FILE: financial_life/examples/meta_data.md
================================================
# Meta-Fields and Controller functions
You can attach meta-data to any account class and any payment. These meta-data can be used to facilitate more complex calculations within your simulations, like the correct calculation of tax payments. With the help of controller-functions you can access these information to incorporate them into your simulation.
Meta-data can be attached like this in your simulation definition:
```python
# meta data for account classes, like loans
loan = a.Loan(amount = 100000,
interest = 0.01,
name = 'House Credit',
date="01.09.2016",
meta = {'tax': {'outcome': 'yearly_interests'}}
)
# meta data for payments
simulation.add_regular('Income', account, 2000,
interval = 'monthly',
date_start="01.09.2016",
meta={'type': 'income',
'tax': {
'brutto': 2500,
'paid': 310,
'insurance': 190
}
}
)
```
financial_life let's you add controller-functions to your simultions, which are executed every day before money is transfered between accounts. The controller-function is called with the simulation-object as argument:
```python
def controller_tax(s):
""" This is a controller function that calculates annual tax rates
's' is the simulation object
"""
# Do something, e.g. check, how much brutto income have been
# transfered to the account and home much interests from the
# loan account must be subtracted from it. See meta_data.py
# for a full example
# add controller function to simulation
simulation.add_controller(controller_tax)
```
With `account.report.with_meta()` or `simulation.report.with_meta()` you can make these information visible, when you print the report.
The result will look like this:
```
Main account
Date description kind interest input foreign_account account output Meta
---------- ------------- --------------- ---------- ------- ----------------- --------- -------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------
01.09.2016 0.00 0.00 1000.00 0.00 {}
01.09.2016 regular 0.00 2000.00 Income 3000.00 0.00 {'type': 'income', 'tax': {'insurance': 190, 'paid': 310, 'brutto': 2500}}
01.09.2016 regular 0.00 0.00 House Credit 1500.00 -1500.00 {}
01.10.2016 regular 0.00 2000.00 Income 3500.00 0.00 {'type': 'income', 'tax': {'insurance': 190, 'paid': 310, 'brutto': 2500}}
01.10.2016 regular 0.00 0.00 House Credit 2000.00 -1500.00 {}
01.11.2016 regular 0.00 2000.00 Income 4000.00 0.00 {'type': 'income', 'tax': {'insurance': 190, 'paid': 310, 'brutto': 2500}}
01.11.2016 regular 0.00 0.00 House Credit 2500.00 -1500.00 {}
01.12.2016 regular 0.00 2000.00 Income 4500.00 0.00 {'type': 'income', 'tax': {'insurance': 190, 'paid': 310, 'brutto': 2500}}
01.12.2016 regular 0.00 0.00 House Credit 3000.00 -1500.00 {}
31.12.2016 yearly interest 0.75 0.00 3000.75 0.00 {}
01.01.2017 regular 0.00 2000.00 Income 5000.75 0.00 {'type': 'income', 'tax': {'insurance': 190, 'paid': 310, 'brutto': 2500}}
```
See the full example [here](meta_data.py).
================================================
FILE: financial_life/examples/meta_data.py
================================================
'''
Created on 21.12.2016
@author: martin
'''
# standard libraries
from datetime import timedelta
# third-party libraries
# own libraries
from financial_life.financing import accounts as a
from financial_life.tax import germany as tax_ger
def controller_tax(s):
""" This is a controller function that calculates annual tax rates
's' is the simulation object
"""
# perform tax calculation always on 15th of February, just to
# reflect the fact that tax payments for the previous year are
# never made on the 1st of January, which has an impact on
# interests as well
if ((s.current_date.month == 2) and
(s.current_date.day == 15)):
# account class for payments
account = s.accounts[0]
# filter for all transactions that occured in the previous year
# and of type 'income'
income_report = s.report.subset(lambda st: (st.date.year == (s.current_date.year-1)) and
(st.meta.get('type','') == 'income'))
# using list comprehensions, we can easily calculate a few sums
#m_income = sum(income.value)
m_brutto = sum(payment.meta['tax']['brutto'] for payment in income_report)
m_paid = sum(payment.meta['tax']['paid'] for payment in income_report)
# get all accounts which have the field tax.outcome == 'yearly_interests'
loans = [account for account in s.accounts
if account.meta.get('tax', {}).get('outcome','') == 'yearly_interests']
# get only the reports of last year
interests_reports = [loan.report.subset(lambda st: st.date.year == (s.current_date.year-1))
for loan in loans]
# sum up all interests from all interests reports
m_interests = sum(sum(report.interest) for report in interests_reports)
# as interests for loans are negative, we effectively
# subtract the payed interests from the brutto we earned in the last year
m_tax_relevant_money = m_brutto + m_interests
# now, we apply german tax rules from 2016 to the tax-relevant money
m_tax, m_tax_percentage = tax_ger.tax_to_pay(2016, m_tax_relevant_money)
# this is the money we either receive from the state (positive value
# or we need to pay (negative value)
m_diff = m_paid - m_tax
s.add_unique('State', account, m_diff,
date = s.current_date + timedelta(days=1),
name = 'Tax',
fixed = True,
meta = {
'taxpayment': {
'tax_relevant_money': m_tax_relevant_money,
'tax_to_pay': m_tax,
'tax_percentage': m_tax_percentage,
'paid': m_paid,
'difference': m_diff
}
}
)
def example_meta_controller(print_it = True):
""" This example shows, how meta-information for payments and account data could
be used to calculate annual tax-return """
account = a.Bank_Account(amount = 1000, interest = 0.001, name = 'Main account', date="01.09.2016")
# define meta-data for accounts. here: some fields that are relevant for
# tax calculations
loan = a.Loan(amount = 100000, interest = 0.01, name = 'House Credit', date="01.09.2016",
meta = {'tax': {
'outcome': 'yearly_interests'
}
}
)
# add these accounts to the simulation
simulation = a.Simulation(account, loan, date='01.09.2016')
# our employee receives monthly 2000 netto, coming from 2500 brutto,
# 310 are subtracted directly from the loan, which is less than she
# needs to pay. 190 are paid for insurance
simulation.add_regular('Income', account, 2000,
interval = 'monthly',
date_start="01.09.2016",
meta={'type': 'income',
'tax': {
'brutto': 2500,
'paid': 310,
'insurance': 190
}
}
)
simulation.add_regular(account, loan, lambda: min(1500, -loan.account),
interval = 'monthly',
date_start="01.09.2016")
simulation.add_controller(controller_tax)
# simulate for ten years
simulation.simulate(delta = timedelta(days=365*10))
# this function is also part of a unittest. Therefore, we want to be able to
# control, whether we print some information or not
if print_it:
# print reports summarized in years
print(account.report.with_meta())
#print(loan.report.with_meta())
return simulation
if __name__ == '__main__':
example_meta_controller()
================================================
FILE: financial_life/examples/simple_examples.py
================================================
'''
Created on 14.08.2016
@author: martin
'''
# standard libraries
from datetime import timedelta, datetime
import os
# third-party libraries
from matplotlib.pyplot import show
# own libraries
from financial_life.financing import accounts as a
from financial_life.reports import html
def example1():
# create a private bank account and a loan
account = a.Bank_Account(amount = 1000, interest = 0.001, name = 'Main account')
loan = a.Loan(amount = 100000, interest = 0.01, name = 'House Credit')
# add these accounts to the simulation
simulation = a.Simulation(account, loan)
# describe single or regular payments between accounts. note, that
# a string can be used for external accounts that you don't want to model.
# also note the lambda function for the payments to the loan.
simulation.add_regular('Income', account, 2000, interval = 'monthly')
# you can also use lambda function to dynamically decide how much money
# you would like to transfer
simulation.add_regular(account, loan, lambda: min(1500, -loan.account), interval = 'monthly')
# simulate for ten years
simulation.simulate(delta = timedelta(days=365*10))
# plot the data
simulation.plt_summary()
show()
# print reports summarized in years
print(account.report.yearly().as_df())
print(loan.report.yearly().as_df())
# analyze data
print("Interests on bank account: %.2f" % sum(account.report.yearly().interest))
print("Interests on loan account: %.2f" % sum(loan.report.yearly().interest))
return simulation
def example2():
# create a private bank account and a loan
account = a.Bank_Account(amount = 1000, interest = 0.001, name = 'Main account')
savings = a.Bank_Account(amount = 5000, interest = 0.007, name = 'Savings')
loan = a.Loan(amount = 100000, interest = 0.01, name = 'House Credit')
# add these accounts to the simulation
simulation = a.Simulation(account, savings, loan)
# describe single or regular payments between accounts. note, that
# a string can be used for external accounts that you don't want to model.
# also note the lambda function for the payments to the loan.
simulation.add_regular('Income', account, 2000, interval = 'monthly')
simulation.add_regular(account, savings, 500, interval = 'monthly')
simulation.add_regular(account, loan, lambda: min(1500, -loan.account), interval = 'monthly')
simulation.add_unique(savings, 'Vendor for car', 10000, '17.03.2019')
# simulate for ten years
simulation.simulate(delta = timedelta(days=365*10))
# plot the data
simulation.plt_summary()
# print reports summarized in years
print(account.report.yearly().as_df())
print(loan.report.yearly().as_df())
# analyze data
print("Bank account: %.2f" % (account.account + savings.account))
cwd = os.path.dirname(os.path.realpath(__file__))
result_folder = cwd + '/example2'
html.report(simulation, style="standard", output_dir = result_folder)
show()
def example3():
account = a.Bank_Account(amount = 1000, interest = 0.001, name = 'Main account')
savings = a.Bank_Account(amount = 5000, interest = 0.013, name = 'Savings')
loan = a.Loan(amount = 100000, interest = 0.01, name = 'House Credit')
simulation = a.Simulation(account, savings, loan, name = 'Testsimulation')
simulation.add_regular(from_acc = 'Income',
to_acc = account,
payment = 2000,
interval = 'monthly',
date_start = datetime(2016,9,15),
day = 15,
name = 'Income')
simulation.add_regular(from_acc = account,
to_acc = savings,
payment = 500,
interval = 'monthly',
date_start = datetime(2016,9,30),
day = 30,
name = 'Savings')
simulation.add_regular(from_acc = account,
to_acc= loan,
payment = 1000,
interval = 'monthly',
date_start = datetime(2016,9,15),
day = 15,
name = 'Debts',
fixed = False,
date_stop = lambda cdate: loan.is_finished())
simulation.add_regular(from_acc = account,
to_acc= loan,
payment = lambda : min(8000, max(0,account.get_account()-4000)),
interval = 'yearly',
date_start = datetime(2016,11,20),
day = 20,
name = 'Debts',
fixed = False,
date_stop = lambda cdate: loan.is_finished())
simulation.simulate(delta=timedelta(days=2000))
simulation.plt_summary()
show()
print(account.report)
print(loan.report)
if __name__ == '__main__':
example1()
================================================
FILE: financial_life/financing/__init__.py
================================================
""" some basic classes for creating financing classes and
reports """
# standard libraries
from datetime import datetime
from calendar import monthrange
import warnings
from copy import deepcopy
from collections import defaultdict
from collections import Callable
# third-party libraries
from tabulate import tabulate
import numpy as np
from numpy.core.numeric import result_type
import pandas as pd
# own libraries
from financial_life.calendar_help import Bank_Date
from financial_life.financing.identity import id_generator
from financial_life.financing import validate
pd.set_option('display.width', 1000)
# degrees of precision. the higher the number the more
# precise is the category
C_precisions = {'year' : 1.,
'month': 2.,
'day': 3.,
}
C_default_payment = {'date': Bank_Date.max,
'payment': 0.,
'name': 'End reached'}
# semantic of reports. Columns of the report can be assigned
# to one or several of this categories. This allows to
# visualize collection of reports in a semantical meaningful manner
report_semantics = {'input_abs': [], # money transfered to the financial product as absolute number
'input_cum': [], # ...as increment
'output_abs': [], # money transfered from the financial product
'output_cum': [], # ...as decrement
'cost_abs': [], # created costs by the f.p.
'cost_cum': [], # ...as increment
'win_abs': [], # created win by the f.p.
'win_cum': [], # ...as increment
'debt_abs': [], # debt value
'debt_cum': [], # ...as increment
'debtpayment_abs': [], # payment to equalize debts
'debtpayment_cum': [], # ...as increment
'saving_abs': [], # saving value
'saving_cum': [], # ...as increment
'none': [], # none of the above things
}
def conv_payment_func(x):
""" converts any payment to what is needed in order to be applicable in
the simulation process. if it is a number, it is multiplied by 100, if it
is a function, the function is called and the result is multiplied by 100.
"""
return Payment_Value(x)
def create_stop_criteria(date_stop):
""" This is a function that returns a functions, which defines
a stop criteria for the iterators. If date_stop is a date,
the resulting functions simply compares date_stop with a given date.
if date_stop is a callable, it executes the callable """
if isinstance(date_stop, datetime) or isinstance(date_stop, Bank_Date):
def compare_dates(cdate):
return cdate < date_stop
return compare_dates
elif isinstance(date_stop, Callable):
def check_callable(cdate):
return not date_stop(cdate)
return check_callable
else:
raise ValueError("date_stop is %s but should be either date-type or Callable" % type(date_stop))
def iter_regular_month(regular, date_start = None):
""" creates an iterator for a regular payment. this function is for example
used by payment to create iterators for every item in _regular
regular: item of the structure Payments._regular
date_start: date the payment generator wants to start the payments,
this can be a date after regular['date_start']
"""
if not date_start:
date_start = regular['date_start']
else:
# determine the greater date
date_start = max(date_start, regular['date_start'])
# if day is bigger than start_date.day, than this month is gonna
# be the first payment
if date_start.day <= regular['day']:
i = 0
# otherwise it will be in the next month
else:
i = 1
date_start = Bank_Date(year = date_start.year,
month = date_start.month,
day = min(regular['day'], monthrange(date_start.year, date_start.month)[1]),
hour = date_start.hour,
minute = date_start.minute,
second = date_start.second)
date_stop = regular.get('date_stop', Bank_Date.max)
stop_criteria = create_stop_criteria(date_stop)
current_date = date_start.add_month(i)
while stop_criteria(current_date):
yield Payment(from_acc = regular['from_acc'],
to_acc = regular['to_acc'],
date = current_date,
name = regular['name'],
kind = 'regular',
payment = regular['payment'],
fixed = regular['fixed'],
meta = regular['meta']
)
i += 1
current_date = date_start.add_month(i)
def iter_regular_year(regular, date_start = None):
""" creates an iterator for a yearly payment. this function is
used by payment to create iterators for every item in _regular
It takes the day and month in regular['date_start'] to schedule the payment
regular: item of the structure Payments._regular
date_start: date the payment generator wants to start the payments,
this can be a date after regular['date_start']
"""
if not date_start:
date_start = regular['date_start']
else:
# determine the greater date
date_start = max(date_start, regular['date_start'])
current_date = datetime(year=date_start.year,
month=regular['date_start'].month,
day=regular['date_start'].day)
if current_date < date_start:
current_date = datetime(year=date_start.year + 1,
month=regular['date_start'].month,
day=regular['date_start'].day)
date_stop = regular.get('date_stop', Bank_Date.max)
stop_criteria = create_stop_criteria(date_stop)
while stop_criteria(current_date):
yield Payment(from_acc = regular['from_acc'],
to_acc = regular['to_acc'],
date = current_date,
name = regular['name'],
kind = 'regular',
payment = regular['payment'],
fixed = regular['fixed'],
meta = regular['meta']
)
current_date = datetime(year = current_date.year + 1,
month=regular['date_start'].month,
day=regular['date_start'].day)
# functions for generating regular payments
C_interval = {
'monthly': iter_regular_month,
'yearly': iter_regular_year,
}
class Status(object):
""" This class represents the status of a financing product
at a particular date """
def __init__(self, date, **kwargs):
""" Creates a new status object. Note, that all of kwargs
elements are written to _status, except 'meta', which is
treated special, as it may contain dict-data again """
if not isinstance(date, datetime):
raise TypeError("date must be from type datetime")
self._date = date
self._meta = {}
if 'meta' in kwargs:
self._meta = kwargs.pop('meta')
self._status = kwargs
self._format = "%d.%m.%Y"
def __str__(self):
result = "Date: %s" % self._date.strftime(self._format) + '\n'
for key, value in self._status.items():
result += ("%s: %s\n" % (key, str(value)))
return result
def keys(self):
""" Returns a list of keys """
return self._status.keys()
@property
def date(self):
return self._date
@property
def strdate(self):
return self._date.strftime(self._format)
@property
def status(self):
return self._status
@property
def meta(self):
return self._meta
def __getitem__(self, key):
if key == 'date':
return self._date
return self._status[key]
def __getattr__(self, name):
return self.__getitem__(name)
def get(self, attr, default):
""" Get attribute or default value from data-dictionary """
if attr == 'date':
return self._date
return self._status.get(attr, default)
class Report(object):
""" A report is a collection of statuses with some additional
functionallity in order to merge and plot reports. One key
feature of report is that it can handle heterogenous types of
statuses
"""
def __init__(self, name=None,
format_date = "%d.%m.%Y",
precision = 'daily'
):
self._statuses = []
self._keys = [] # list of all keys used so far
self._format_date = format_date
# precision for merging statuses with similar date
self._precision = precision
self._semantics = deepcopy(report_semantics)
if not name:
name = id_generator(8)
self._name = name
def add_semantics(self, key, semantics=None):
""" adds semantic description to the report, important for
plotting
usage:
Single assignments
.add_semantics('loan', 'debt_abs')
Group assignments
.add_semantics(['interest', 'insurence'], 'cost_cum')
Entire assignments
.add_semantics({'cost_cum':['interest', 'insurence']})
"""
if isinstance(key, dict):
for k, items in key.items():
if k in self._semantics:
self._semantics[k] = items
else:
raise AttributeError('Key %s not in semantics' % k)
return
if semantics not in self._semantics:
raise AttributeError('Semantic "%s" not in semantics' % semantics)
if isinstance(key, list):
self._semantics[semantics] = self._semantics[semantics] + key
self._keys = list(set(self._keys) | set(key))
return
if isinstance(key, str):
self._semantics[semantics].append(key)
self._keys = list(set(self._keys) | set((key,)))
return
def semantics(self, semantic):
""" returns list of elements in semantic """
return self._semantics[semantic]
def semantics_of(self, key):
""" returns the semantic in which the key appears """
for semantic, values in self._semantics.items():
if key in values:
return semantic
return ''
def append(self, status = None, date = None, **kwargs):
""" adds either an instance of status to the list or
data given to the append method as keyword arguments """
assert((status and not date) or (date and not status))
if date:
status = Status( Bank_Date.fromtimestamp(date.timestamp()), **kwargs )
if not isinstance(status, Status):
raise TypeError("status must be of type Status")
self._statuses.append(status)
# add potential new keys to the list
self._keys = list(set(self._keys) | set(status.keys()))
@property
def size(self):
""" Returns the number of status entries"""
return len(self._statuses)
@property
def name(self):
return self._name
@name.setter
def name(self, name):
self._name = name
@property
def precision(self):
return self._precision
def get_from_date(self, date, interval):
""" help function to make the creation monthly, yearly reports more
generic. This function returns e.g. month or year from a given date """
if interval == 'yearly':
return Bank_Date(date.year, 1, 1)
if interval == 'monthly':
return Bank_Date(date.year, date.month, 1)
if interval == 'daily':
return date
raise TypeError("interval has to be either monthly or yearly")
def monthly(self):
return self.create_report(interval='monthly')
def yearly(self):
return self.create_report(interval='yearly')
def create_report(self, interval='yearly'):
""" generic function for returning a report for certain
intervals """
def add_data(data, status):
""" add status data to existing dictionary """
for key, value in status.status.items():
# for cumulative data, we need to add, for other we just
# need to take the latest value
if "cum" in self.semantics_of(key):
data[key] += value
elif not self.semantics_of(key) is "none":
data[key] = value
return data
if interval == 'daily':
return self
i = 0
result = Report(name = self._name,
format_date = self._format_date,
precision = interval
)
result._semantics = deepcopy(self._semantics)
while (i < len(self._statuses)):
# get the value of the interval type, e.g. exact month or exact year
frame = self.get_from_date(self._statuses[i].date, interval)
# create a new dictionary and add the current status to it
data = add_data(defaultdict(int), self._statuses[i])
i += 1
# iterate to the following statuses as long as frame equals the current value of the
# the given interval type (e.g. as long as the month is the same)
while (i < len(self._statuses)) and (frame == self.get_from_date(self._statuses[i].date, interval)):
data = add_data(data, self._statuses[i])
i += 1
# if the while loop ended because of i, we need to correct it again
if (i == len(self._statuses)):
result.append(date=self._statuses[i-1].date, **data)
else:
result.append(date=self._statuses[i-1].date, **data)
return result
def subset(self, lambda_func):
""" creates a subset of report based on lambda-function that is
used within a list comprehension. The lambda function gets every
status element of report and returns either true (to be inlcuded
in subset) or false (excluded). This is a very generic way of
applying queries to the report in order to reduce its the report
to its requsted items.
Note, that this is not a deepcopy of the report. Therefore, the
it is more appropriate to use it for reading data rather than
writing data.
"""
if not isinstance(lambda_func, Callable):
raise TypeError('lambda_func must be of the form lambda status: True <or> False')
result = Report(name = self._name,
format_date = self._format_date,
precision = 'custom'
)
result._semantics = self._semantics
result._keys = self._keys
result._statuses = [s for s in self._statuses if lambda_func(s)]
return result
def table_rows(self):
""" Creates a list of lists, where each inner list
represents a row of a table. This is used by the tabulate
package for plotting tables. """
records = []
for s in self._statuses:
data = [s.date.strftime(self._format_date)] + [s.get(key, '') for key in self._keys]
records.append(data)
return records
def with_meta(self):
""" Returns the table with meta-information """
print(self.name)
records = []
for s in self._statuses:
data = [s.date.strftime(self._format_date)] + [s.get(key, '') for key in self._keys] + [str(s._meta)]
records.append(data)
return tabulate(records, headers=(['Date'] + self._keys + ['Meta']), floatfmt=".2f")
def sum_of(self, semantic):
"""
Returns the sum of a given semantic, e.g.
.sum_of('cost')
for all cost_cum and cost_abs items, or
.sum_of('cost_cum')
for cost_cum items only
"""
result = 0
for sem in self._semantics:
if semantic in sem:
# if this is a cumulative list, we need to calculate the sum
if '_cum' in sem:
for key in self._semantics[sem]:
result += np.sum(self.get(key, num_only = True))
# if abs, get only the last element
if '_abs' in sem:
for key in self._semantics[sem]:
result += self.get(key, num_only = True)[-1]
return result
def __getitem__(self, key):
result = Report(format_date = self._format_date,
precision = self._precision)
for s in self._statuses:
if key in s.keys():
result.append(date = s['date'], **{key: s[key]})
return result
def __getattr__(self, name):
result = [s.get(name, 'None') for s in self._statuses]
return result
if (name == 'date'):
return result
else:
return np.array(result)
def __len__(self):
return len(self._statuses)
def get(self, name, num_only = False):
replace = 0 if num_only else 'None'
result = [s.get(name, replace) for s in self._statuses]
return result
if (name == 'date'):
return result
else:
return np.array(result)
def __str__(self):
""" Prints all statuses in table view """
print(self.name)
records = self.table_rows()
return tabulate(records, headers=(['Date'] + self._keys), floatfmt=".2f")
def __iter__(self):
""" Iteratores through all statuses """
return self._statuses.__iter__()
def as_df(self):
""" Returns the report as pandas.DataFrame """
dates, data = list(zip(*((s.date, s.status) for s in self._statuses)))
return pd.DataFrame(list(data), index=dates)
class Payment_Value(object):
""" This is a class that represents a payment value. If the payment
is an integer or float it is returned right away, if it is a
function it is evaluated during runtime. Furthermore, this class
can return a clear statement, whether it is a payment or not
to any reporting instance (e.g. html reports)
"""
def __init__(self, payment):
""" checks, whether x is a number or a function and
prepares the reporting variable """
self._name = "could not be determined"
self._payment = payment
if isinstance(payment, int) or isinstance(payment, float):
self._name = str('%0.2f' % payment)
if isinstance(payment, Callable):
self._name = "dynamic"
@property
def name(self):
return self._name
def __call__(self):
if isinstance(self._payment, int) or isinstance(self._payment, float):
return int(self._payment * 100)
if isinstance(self._payment, Callable):
return int(self._payment() * 100)
class Payment(object):
""" Class that describes one specific payment between two accounts
accounts can here be of type "Account" or str, which is an
abstract account that always complies """
def __init__(self, from_acc, to_acc, date, name,
kind, payment, fixed=True, meta={}):
""" Initialization of Payment
from_acc: sending account
to_acc: receiving account
date: due date
name: Name of the payment
payment: Amount of the payment (money value)
fixed: Whether the receiving side needs to take the entire money
or can accept less (useful for loans)
"""
self._data = {
'from_acc': from_acc,
'to_acc': to_acc,
'date': date,
'name': name,
'kind': kind,
'payment': payment,
'fixed': fixed,
'meta': meta
}
@property
def from_acc(self):
return self._data['from_acc']
@property
def to_acc(self):
return self._data['to_acc']
@property
def date(self):
return self._data['date']
@property
def name(self):
return self._data['name']
@property
def kind(self):
return self._data['kind']
@property
def payment(self):
return self._data['payment']
@property
def json(self):
return {'from_acc': self._data['from_acc'].name,
'to_acc': self._data['to_acc'].name,
'date': self._data['date'].date(),
'name': self._data['name'],
'kind': self._data['kind'],
'payment': self._data['payment'].name,
'fixed': self._data['fixed'],
'meta': self._data['meta'],
}
def __getitem__(self, key):
return self._data[key]
class PaymentList(object):
""" Hanldes the complexities of payments including unique
payments and regular payments """
def __init__(self):
self._uniques = []
self._regular = []
@property
def uniques(self):
return self._uniques
@property
def regular(self):
return self._regular
def check_errors_payment(self, payment):
""" checks for any errors in the payment variable """
if (not isinstance(payment, int) and
not isinstance(payment, float) and
not isinstance(payment, Callable)):
raise TypeError("Payment must be int, float or a function")
def add_unique(self, from_acc, to_acc, payment,
date, name = '', fixed = True, meta={}):
""" adds a one-time payment to the list, optional give it
a name """
if not isinstance(date, datetime):
raise TypeError("Date must be at least from type datetime")
self.check_errors_payment(payment)
# converts any input to a function that returns the right value
conv_payment = conv_payment_func(payment)
self._uniques.append(
Payment(
from_acc = from_acc,
to_acc = to_acc,
date = Bank_Date.fromtimestamp(date.timestamp()),
name = name,
kind = 'unique',
payment = conv_payment,
fixed = fixed,
meta = meta
)
)
# sort the whole list with date as key
self._uniques = sorted(self._uniques, key = lambda p: p['date'] )
def add_regular(self, from_acc, to_acc, payment, interval,
date_start, day=1, name='', date_stop = None,
fixed = False, meta={}):
""" Adds a regular payment to the list, with a given
payment: amount to pay
interval: 'monthly': every month
'quarter': every quarter with date_start as start month
'quarter_year': every quarter of a year (mar, jun, sep, dec)
'yearly': every year
day: day to start with
date_start: start date of this payment
name : optional name
fixed: only everything or nothing must be transfered (true)
or depending on the receiving account a smaller amount
can be transfered (false)
"""
if not interval in C_interval.keys():
raise ValueError("interval must be one of '" + '\',\''.join(C_interval))
if day >= 29:
warnings.warn(("note that in months which have less days than {} the " +
"payment will be transferred earlier").format(day)
)
self.check_errors_payment(payment)
if not date_stop:
date_stop = Bank_Date.max
else:
date_stop = validate.valid_stop_date(date_stop)
# converts any payment to a function
conv_payment = conv_payment_func(payment)
self._regular.append({'from_acc': from_acc,
'to_acc': to_acc,
'interval': interval,
'day' : day,
'date_start': Bank_Date.fromtimestamp(date_start.timestamp()),
'date_stop': date_stop,
'payment': conv_payment,
'name' : name,
'fixed': fixed,
'meta': meta
}
)
def clear_regular(self):
""" Removes all regular payments """
self._regular = []
def payment(self, start_date):
""" returns an interator that iterates through all
payments """
assert isinstance(start_date, datetime), "start_date must be of type datetime"
# creates for each item an iterator that returns just this
# item. this list is later on amended by iterators for regular
# payments
iters = [iter([u]) for u in self._uniques if u['date']>= start_date]
for r in self._regular:
# creates an iterator based on the interval in r
iters.append(C_interval[r['interval']](r, start_date))
# list of next dates. this list is inline with the iters list
# the second parameter in next prevents the command to raise a
# StopIteration Exception
dates = [next(iter, C_default_payment) for iter in iters]
# as long as there is still a date, below infinity
min_date = min(dates, key = lambda d: d['date'])
# in this routine, the next command must be called after yield, as there might
# be some callables which need to be called right after the payments, but not
# before
while min_date['date'] != Bank_Date.max:
# find all indices and payments in the list that have the same daet
indices, payments = zip(*[(i, d) for (i, d) in enumerate(dates) if (d['date'].date() == min_date['date'].date())])
yield payments
for i in indices:
dates[i] = next(iters[i], C_default_payment)
min_date = min(dates, key = lambda d: d['date'])
class Currency():
""" Standard class for currencies to assure correct computing
of numbers. Right now this class is not in use """
def __init__(self, value, digits = 2):
self._value = int(value * (10**digits))
================================================
FILE: financial_life/financing/accounts.py
================================================
'''
Created on 24.03.2016
@author: martin
'''
# standard libraries
from datetime import datetime, timedelta
from collections import Callable
import warnings
import logging
# third-party libraries
# own libraries
from financial_life.financing import PaymentList
from financial_life.financing import Report
from financial_life.financing import C_default_payment
from financial_life.calendar_help import Bank_Date, get_days_per_year
from financial_life.financing import plotting as plt
from financial_life.financing import validate
logger = logging.getLogger(__name__)
# maximal time span that will be simulated
C_max_time = 365 * 100
# format for dates
C_format_date = '%d.%m.%Y'
# generic transfer codes
C_transfer_OK = 0 # transfer confirmed
C_transfer_NA = 1 # transfer not allowed
C_transfer_NEM = 2 # not enough money on the account
C_transfer_ERR = 3 # general transfer error
C_transfer_codes = {C_transfer_OK: 'OK',
C_transfer_NA: 'Not allowed',
C_transfer_NEM: 'Not enough money',
C_transfer_ERR: 'ERROR'}
def neg_func(func):
""" negates the outcome of func. this function is used as a wrapper
to negate the output of payments which are determined at runtime. This
wrapper is used e.g. by the class Transfers
"""
def foo():
result = func()
return -result
return foo
def valid_account_type(*accounts):
""" Checks whether all accounts given to this function
are either from type Account or from type string
Accounts of type string are converted to DummyAccount
The corrected list is returned
The only reason this method is not in the validate module
is because it would create an import loop
"""
result = []
for account in accounts:
if (isinstance(account, Account)):
result.append(account)
elif (isinstance(account, str)):
result.append(DummyAccount(account))
else:
raise TypeError('the given account must be either derived from type Account or of type string')
return tuple(result)
class TransferMessage(object):
""" Message returned by a transfer function with some information about
the success or failure of the money transfer """
def __init__(self, code, money, message = ''):
if code in C_transfer_codes:
self._code = code
else:
raise ValueError("Transfercode is not in C_transfer_codes")
self._message = message
self._money = money
@property
def code(self):
return self._code
@property
def message(self):
return self._message
@property
def money(self):
return self._money
class Simulation(object):
""" This class simulates the interaction between different accounts. It
provides the framework in which dependencies between accounts and state-
dependent changes of account-modi can be managed """
def __init__(self, *accounts, name = None, date = None, meta = None):
""" Simulations can be initialized with names, to make differentiate
between different simulations """
# check for errors in the input of accounts
for account in accounts:
if not isinstance(account, Account):
raise TypeError(str(account) + " is not of type or subtype Account")
if name is None:
self._name = 'Simulation ' + str(datetime.now())
else:
self._name = name
# a simuation can also store meta information
self._meta = meta
self._report = Report(self._name)
self._report.add_semantics('from_acc', 'none')
self._report.add_semantics('to_acc', 'none')
self._report.add_semantics('value', 'input_cum')
self._report.add_semantics('kind', 'none')
self._report.add_semantics('name', 'none')
self._report.add_semantics('code', 'none')
self._report.add_semantics('message', 'none')
self._payments = PaymentList()
self._payments_iter = None
self._next_pay = None
self._date_start = validate.valid_date(date)
self._day = 0
self._current_date = self._date_start
# list of accounts to manage
self._accounts = list(accounts)
# list of controller-functions executed before day-simulation.
# controller functions are executed before the day to check custom
# states of the accounts and perform actions
self._controller = []
@property
def name(self):
return self._name
@name.setter
def name(self, name):
self._name = name
@property
def meta(self):
return self._meta
@property
def accounts(self):
return self._accounts
@property
def current_date(self):
return self._current_date
@property
def report(self):
return self._report
def as_df(self):
df = self.report.as_df()
df = df[['from_acc', 'to_acc', 'value', 'kind', 'name', ]]
return df
def get_report_jinja(self, interval="yearly"):
""" creates a data-structure of the report data that can be used for
displaying the report as table in html files (in jinja2 templates)
interval can be one of the common intervals of the report class (e.g.
yearly or monthly) or None. In this case the raw data are exported """
if interval is None:
report = self._report
else:
report = self._report.create_report(interval)
header = ['date', 'from', 'to', 'value', 'kind', 'name', 'code', 'message']
rows = []
for status in report._statuses:
item = [status.strdate,
status._data['from_acc'].name,
status._data['to_acc'].name,
'%.02f EUR' % status._data['value'],
status._data['kind'],
status._data['name'],
status._data['code'],
status._data['message'],
]
rows.append(item)
return {'header': header, 'rows': rows}
def get_payments_unique_json(self):
""" returns a list of all unique payments in json format for
html rendering """
return {'payments_unique': [u.json for u in self._payments.uniques]}
def get_payments_regular_json(self):
""" returns a list of all unique payments in json format for
html rendering """
return {'payments_regular': [
{
'from_acc': r['from_acc'].name,
'to_acc': r['to_acc'].name,
'interval': r['interval'],
'day': r['day'],
'date_start': r['date_start'].date(),
'date_stop': r['date_stop'].date() if isinstance(r['date_stop'], datetime) else '',
'payment': r['payment'].name,
'name': r['name'],
'fixed': r['fixed'],
} for r in self._payments.regular]
}
def get_accounts_json(self):
return {'accounts': [
{
'index': i,
'name': a.name,
'type': a.__class__.__name__,
'start_value': a._account / 100.,
'start_date': a.date_start.date()
}
for i, a in enumerate(self.accounts)]
}
def add_unique(self, from_acc, to_acc, payment,
date, name = '',
fixed = False,
meta = {}
):
""" Transfers money from one account to the other """
from_acc, to_acc = valid_account_type(from_acc, to_acc)
date = validate.valid_date(date)
self._payments.add_unique(
from_acc, to_acc, payment, date, name, fixed, meta)
self.update_payment_iterators()
def add_regular(self, from_acc, to_acc, payment, interval,
date_start=datetime(1971,1,1),
day=1, name = '',
date_stop = None,
fixed = False,
meta = {}
):
""" Transfers money from one account to the other on regular basis
date_stop can be a function of the form lambda x: x > datetime(...)
If it returns true, the payment is stopped
"""
from_acc, to_acc = valid_account_type(from_acc, to_acc)
date_start = validate.valid_date(date_start)
if date_stop is not None:
date_stop = validate.valid_stop_date(date_stop)
self._payments.add_regular(
from_acc, to_acc, payment, interval,
date_start, day, name, date_stop, fixed, meta)
self.update_payment_iterators()
def update_payment_iterators(self):
""" Whenever a new payment is added via add_unique or add_regular,
this function is triggered to update the payment iterator. This is
necessary, as payments could be dynamically added during the
simulation as well """
self._payments_iter = self._payments.payment(self._current_date)
try:
self._next_pay = next(self._payments_iter, C_default_payment)
except StopIteration:
# if there are no payments, create a date for a payment
# that lies in the distant future
self._next_pay = [{'date': Bank_Date.max}]
def add_account(self, account):
""" adds an account to the simulation and returns it to the
user so that he/she can proceed with it """
if isinstance(account, Account):
self._accounts.append(account)
else:
raise TypeError(("account must be of type Account but is of type " +
str(type(account))))
return account
def add_controller(self, controller):
if isinstance(controller, Callable):
self._controller.append(controller)
else:
raise TypeError(("controller must be of type Callable but is of type " +
str(type(controller))))
def get_payment(self, payment):
""" functions that returns the amount of payment for the current day.
it handles the distinction between variables that represent just numbers
and variables that represent functions to be executed """
payed = 0
if isinstance(payment['payment'], int) or isinstance(payment['payment'], float):
payed = payment['payment']
elif isinstance(payment['payment'], Callable):
payed = payment['payment']()
else:
raise TypeError("payment must be int, float or Callable but is " + str(type(payment['payment'])))
return payed
def make_report(self, from_acc, to_acc, value, kind,
name, code, message, meta):
self._report.append(
date = self._current_date,
from_acc = from_acc,
to_acc = to_acc,
value = value / 100,
kind = kind,
name = name,
code = code,
message = message,
meta = meta
)
def make_transfer(self, payment):
""" Transfers money from one account to the other and tries to assure
full consistency.
The idea is that a payments gets started by the sender. If this succeeds,
the money is tried to move on the account of the receiver. If this fails,
the money is transfered back to the sender.
If the money to be transfered is zero, no payment procedure will be
initiated
"""
if not (isinstance(payment['from_acc'], DummyAccount)):
assert payment['from_acc']._date_start <= self._current_date, (str(payment['from_acc']) + ' has a later creation date than the payment ' + payment['name'])
if not (isinstance(payment['to_acc'], DummyAccount)):
assert payment['to_acc']._date_start <= self._current_date, (str(payment['to_acc']) + ' has a later creation date than the payment ' + payment['name'])
try:
# this is now the money that will be transfered, if there is
# a receiver. this amount of money remains fixed for the transfer
money = self.get_payment(payment)
if money == 0:
self.make_report(
from_acc = payment['from_acc'],
to_acc = payment['to_acc'],
value = 0,
kind = payment['kind'],
name = payment['name'],
code = C_transfer_NA,
message = "Transfer with zero money will not be initiated",
meta = payment['meta']
)
return False
except TypeError as e:
logger.debug("make_transfer: money of wrong type")
self.make_report(
from_acc = payment['from_acc'],
to_acc = payment['to_acc'],
value = 0,
kind = payment['kind'],
name = payment['name'],
code = C_transfer_ERR,
message = e.message(),
meta = payment['meta']
)
return False
# first, try to get the money from the sender account, tm = TransferMessage()
tm_sender = payment['from_acc'].payment_output(
account_str = payment['to_acc'].name,
payment = -money,
kind = payment['kind'],
description = payment['name'],
meta = payment['meta']
)
# if sending money succeeded, try the receiver side
if tm_sender.code == C_transfer_OK:
logger.debug("make_transfer: sender code is OK")
# in the wired case that money is less than what has been returned by the sender,
# throw an error message
if money < (-tm_sender.money):
raise ValueError("%f was requested from account '%s' but %f returned" % (money,
payment['from_acc'].name,
-tm_sender.money))
if money > (-tm_sender.money):
# if payment is fixed, throw an error, otherwise proceed
if payment['fixed']:
raise ValueError("%f was requested from account '%s' but %f returned" % (money,
payment['from_acc'].name,
-tm_sender.money))
else:
money = -tm_sender.money
tm_receiver = payment['to_acc'].payment_input(
account_str = payment['from_acc'].name,
payment = money,
kind = payment['kind'],
description = payment['name'],
meta = payment['meta']
)
# if receiving succeeded, return success
if tm_receiver.code == C_transfer_OK:
# in the wired case that money is less than what has been returned by the sender,
# throw an error message
if money < tm_receiver.money:
raise ValueError("%f was submitted to account '%s' but %f returned" % (money,
payment['to_acc'].name,
tm_receiver.money))
# if the receiver does not accept the entir money
if money > tm_receiver.money:
# check, whether payment is fixed
if payment['fixed']:
raise ValueError("%f was submitted to account '%s' but %f returned because it is fixed" % (money,
payment['to_acc'].name,
tm_receiver.money))
else:
# if payment is not fixed, we need to transfer the difference back to
# the sender account
payment['from_acc'].return_money( money - tm_receiver.money)
logger.debug("make_transfer: receiver code is OK")
self.make_report(
from_acc = payment['from_acc'],
to_acc = payment['to_acc'],
value = tm_receiver.money,
kind = payment['kind'],
name = payment['name'],
code = C_transfer_OK,
message = '',
meta = payment['meta']
)
return True
else:
# if an error on the receiver side happened,
# return the money back and report that
logger.debug("make_transfer: receiver code is not ok")
payment['from_acc'].return_money(money)
self.make_report(
from_acc = payment['from_acc'],
to_acc = payment['to_acc'],
value = tm_sender.money,
kind = payment['kind'],
name = payment['name'],
code = tm_receiver.code,
message = tm_receiver.message,
meta = payment['meta']
)
return False
else:
# if an error occured on the sending side, report this and return false
logger.debug("make_transfer: sending code is not OK")
self.make_report(
from_acc = payment['from_acc'],
to_acc = payment['to_acc'],
value = money,
kind = payment['kind'],
name = payment['name'],
code = tm_sender.code,
message = tm_sender.message,
meta = payment['meta']
)
return False
def simulate(self, date_stop = None, delta = None, last_report = True):
""" Simulation routine for the entire simulation """
# Initialization
date_stop = validate.valid_date_stop(date_stop)
if (not self._payments_iter):
self._payments_iter = self._payments.payment(self._current_date)
if (not self._next_pay):
try:
self._next_pay = next(self._payments_iter, C_default_payment)
except StopIteration:
# if there are no payments, create a date for a payment
# that lies in the distant future
self._next_pay = [{'date': Bank_Date.max}]
delta = validate.valid_delta(delta)
temp_delta = 0
while ((self._current_date < date_stop) and # ...stop-date is reached
(temp_delta < delta.days) and # and delta has not been exeeded
((self._current_date - self._date_start).days < C_max_time)): # ...number of simulated days exceeds max
# 0. set the current day
for account in self._accounts:
if account._date_start <= self._current_date:
account.set_date(self._current_date)
# 1. execute start-of-day function
# everything that should happen before the money transfer
for account in self._accounts:
if account._date_start <= self._current_date:
account.start_of_day()
# 2. execute all controller functions
for controller in self._controller:
controller(self)
# 3. apply all payments for the day in correct temporal order
if self._next_pay[0]['date'].date() == self._current_date.date():
for payment in self._next_pay:
self.make_transfer(payment)
self._next_pay = next(self._payments_iter, C_default_payment)
# 4. execute end-of-day function
# everything that should happen after the money transfer
for account in self._accounts:
if account._date_start <= self._current_date:
account.end_of_day()
# go to the next day within the simulation
self._day += 1
self._current_date = self._date_start + timedelta(days = self._day)
temp_delta += 1
def reports(self, interval='yearly'):
""" Returns a tuple of reports for a given interval """
return (account.report.create_report(interval) for account in self._accounts)
def plt_summary(self, interval='yearly'):
""" plots a summary of the simulation """
reports = self.reports(interval=interval)
plt.summary(*reports)
def report_sum_of(self, semantic):
""" creates the sum for every report.sum_of(semantic) of each account """
return sum([a.report.sum_of(semantic) for a in self._accounts])
def print_reports(self, interval):
""" Creates for every account a report for a given interval """
for a in self._accounts:
print(a.name)
print(a.report.create_report(interval))
print(' ')
class Account(object):
""" Basic class for all types of accounts with reporting and simulation
functionality
obligatory methods for each account to be part of a simulation
- set_date
- start_of_day
- end_of_day
- payment_output
- payment_input
- return_money
"""
def __init__(self, amount, interest, date=None, name = None, meta = {}):
self._date_start = validate.valid_date(date)
self._name = validate.valid_name(name)
self._meta = meta
# check for problems
assert((isinstance(amount, int) or (isinstance(amount, float))))
if interest > 1.:
interest = interest / 100.
## generic variables, which are basically used in any class ##
## that inherits from account ##
# setting up the report and the semantics
self._report = Report(name = self._name)
self._account = int(amount * 100) # amount of money to start with
self._interest = interest # interest rate
self._current_date = self._date_start # current date of the simulation
self._caccount = self._account # current account, this variable
# is used in all subclasses
# sum helper variables for interest calculation and keep
self._sum_interest = 0
def __str__(self):
return self._name
@property
def date(self):
return self._date
@property
def date_start(self):
return self._date_start
@property
def name(self):
return self._name
@name.setter
def name(self, name):
self._name = name
self._report.name = self._name + ' - ' + str(self._date_start.strftime(C_format_date))
@property
def meta(self):
return self._meta
@property
def account(self):
return self._caccount / 100
def get_account(self):
""" alternative method to get the current account value. this method
can be used, e.g. in payment-definitions to transfer the amount of
money that a specific account has in the moment this payment is done.
Instead of using an actual value, this method is called, evaluated and
the return value is used """
return self.account
@property
def interest(self):
return self._interest / 100
@property
def payments(self):
return self._payments
@property
def current_date(self):
return self._current_date
@property
def report(self):
return self._report
def as_df(self):
return self.report.as_df()
def report_time(self, date):
""" returns true, if the requirements for a report are met """
return True
def get_table_json(self, report):
""" Creates a table for a given report """
return {'header': [], 'rows': []}
def get_all_tables_json(self):
""" Creates tables for all intervals in report """
# create all intervals
daily = self._report
monthly = daily.create_report(interval='monthly')
yearly = monthly.create_report(interval='yearly')
return [{'category': 'Yearly',
'data': self.get_table_json(yearly)},
{'category': 'Monthly',
'data': self.get_table_json(monthly)},
{'category': 'Daily',
'data': self.get_table_json(daily)} ]
def get_report_json(self, interval="yearly"):
""" creates a data-structure of the report data that can be used for
displaying the report as table in html files (in jinja2 templates).
interval can be one of the common intervals of the report class (e.g.
yearly, monthly, daily) or None. If None, thee raw data are exported.
If interval is 'all', all intervals will be returned with a
different json structure """
if interval is 'all':
# create all intervals
return self.get_all_tables_json()
else:
if interval is None:
report = self._report
else:
report = self._report.create_report(interval)
return self.get_table_json(report)
def payment_input(self, account_str, payment, kind, description, meta):
""" Input function for payments. This account is the receiver
of a transfer. This function, if derived from,
can account for special checks for input operations """
return TransferMessage(C_transfer_OK, money = payment)
def payment_output(self, account_str, payment, kind, description, meta):
""" Output function for payments. This account is the sender
of a transfer. This function, if derived from,
can account for special checks for output operations """
return TransferMessage(C_transfer_OK, money = payment)
def return_money(self, money):
""" this is a hard return of transfer-money, in case the receiving side
rejected the transfer """
pass
def set_date(self, date):
""" This function is called by the simulation class to set the current date
for the simulation """
# if there is an inconsistency in the date progression, report
# a warning on the command line
delta = (date - self._current_date).days
if delta != 1:
warnings.warn('Difference between current date and next date is %i and not 1' % delta)
if date < self._date_start:
warnings.warn('Date is before start date of account.')
self._current_date = date
def start_of_day(self):
""" Things that should happen on the start of the day, before any money
transfer happens """
pass
def end_of_day(self):
""" Things that should happen at the end of the day, after all money
transfers have been accomplished """
pass
class DummyAccount(Account):
""" This account is used when the user creates a Transfer using a
String as the from-account or to-account. This account basically agrees
to everything. It can be used to create payments for loans or for
outgoing costs """
def __init__(self, name):
""" Creates a dummy account class """
self._name = validate.valid_name(name)
# now the implementation of the real, usable classes begins. In contrast to the account class,
# in these classes, report gets some semantic information about how to handle different
# properties of the class
class Bank_Account(Account):
""" This is a normal bank account that can be used to manage income and
outgoings within a normal household """
def __init__(self, amount, interest, date = None, name = None, meta = {}):
""" Creates a bank account class """
# call inherited method __init__
super().__init__(
amount = amount, interest = interest, date = date, name = name, meta = meta)
self._report_input = 0
self._report_output = 0
self._report.add_semantics('account', 'saving_abs')
self._report.add_semantics('interest', 'win_cum')
self._report.add_semantics('input', 'input_cum')
self._report.add_semantics('output', 'output_cum')
self._report.add_semantics('foreign_account', 'none')
self._report.add_semantics('kind', 'none')
self._report.add_semantics('description', 'none')
self._interest_paydate = {'month': 12, 'day': 31}
# reporting functionality
self._report_interest = 0
self.make_report()
# overwriting function
def make_report(self, interest=0, input=0, output=0,
foreign_account = '', kind = '', description = '',
meta = {}):
""" creates a report entry and resets some variables """
self._report.append(date = self._current_date,
account = self._caccount / 100,
interest = float('%.2f' % (interest / 100)),
input = input / 100,
output = output / 100,
foreign_account = foreign_account,
kind = kind,
description = description,
meta = meta
)
def exec_interest_time(self):
""" Does all things, when self.interest_time() returns true (like adding
interests to the account """
self._caccount = int(round(self._caccount + self._sum_interest))
self.make_report(
interest = self._sum_interest,
kind = 'yearly interest'
)
self._sum_interest = 0
def as_df(self):
df = self.report.as_df()
df = df[['foreign_account', 'description', 'input', 'output', 'interest', 'account']]
return df
def get_table_json(self, report):
""" Creates a table for a given report """
rows = []
if report.precision is 'daily':
header = ['date', 'from', 'description', 'input', 'output', 'interest', 'account']
for status in report._statuses:
item = [status.strdate, status._status['foreign_account'],
status._status['description'],
'%.02f EUR' % status._status['input'],
'%.02f EUR' % status._status['output'],
'%.02f EUR' % status._status['interest'],
'%.02f EUR' % status._status['account']]
rows.append(item)
else:
header = ['date', 'input', 'output', 'interest', 'account']
for status in report._statuses:
item = [status.strdate,
'%.02f EUR' % status._status['input'],
'%.02f EUR' % status._status['output'],
'%.02f EUR' % status._status['interest'],
'%.02f EUR' % status._status['account']]
rows.append(item)
return {'header': header, 'rows': rows}
def interest_time(self):
""" Checks, whether it is time to book the interests to the account """
return ((self._current_date.day == self._interest_paydate['day']) and
(self._current_date.month == self._interest_paydate['month']))
def payment_input(self, account_str, payment, kind, description, meta):
""" Input function for payments. This account is the receiver
of a transfer. This function, if derived from,
can account for special checks for input operations """
return self.payment_move(account_str, payment, kind, description, meta)
def payment_output(self, account_str, payment, kind, description, meta):
""" Output function for payments. This account is the sender
of a transfer. This function, if derived from,
can account for special checks for output operations """
return self.payment_move(account_str, payment, kind, description, meta)
def payment_move(self, account_str, payment, kind, description, meta):
""" in the base class, payment_input and payment_output have almost
the same behavior. Only the type of reporting differs
account_str : the opposite account, sender or receiver
payment : the int or function which includes the payment
kind : whether this is a regular payment or a unique one
description: description of the payment (usually its name)
move_type: "input" or "output" for indicating the direction of movement """
move_type = 'input'
if payment < 0:
move_type = 'output'
self._caccount = int(self._caccount + payment)
report = {'foreign_account': account_str,
move_type: payment,
'kind': kind,
'description': description,
'meta': meta}
self.make_report(**report)
return TransferMessage(C_transfer_OK, money = payment)
def return_money(self, money):
""" this is a hard return of transfer-money, in case the receiving side
rejected the transfer """
self._caccount = int(self._caccount + money)
report = {
'input': money,
'kind': 'storno',
'description': 'transfer did not succeeded'}
self.make_report(**report)
def start_of_day(self):
""" Things that should happen on the start of the day, before any money
transfer happens """
pass
def end_of_day(self):
""" Things that should happen at the end of the day, after all money
transfers have been accomplished """
# TODO: needs to be replaced by a mechanism that checks not every day
days_per_year = get_days_per_year(self._current_date.year)
# calculate interest for this day
interest = self._caccount * (self._interest / days_per_year)
# store interest for later calculations
self._sum_interest += interest
# if paydate is there, add the summed interest to the account
if self.interest_time():
self.exec_interest_time()
class Loan(Account):
"""
This is the default account class that should capture the essential
functionalities of account models
"""
def __init__(self, amount, interest, date = None, name = None, meta = {}):
"""
Creates the data for a basic account model
"""
# call inherited method __init__
super().__init__(
amount = -amount, interest = interest, date = date, name = name, meta = meta)
# reporting functionality
self._report_payment = 0
self._report.add_semantics('account', 'debt_abs')
self._report.add_semantics('interest', 'cost_cum')
self._report.add_semantics('payment', 'debtpayment_cum')
self._report.add_semantics('foreign_account', 'none')
self._report.add_semantics('kind', 'none')
self._report.add_semantics('description', 'none')
self._interest_paydate = {'month': 12, 'day': 31}
self.make_report()
def as_df(self):
df = self.report.as_df()
df = df[['foreign_account', 'description', 'payment', 'interest', 'account']]
return df
def get_table_json(self, report):
rows = []
if report.precision is 'daily':
header = ['date', 'from', 'description', 'payment', 'interest', 'account']
for status in report._statuses:
item = [status.strdate,
status._status['foreign_account'],
status._status['description'],
'%.02f EUR' % status._status['payment'],
'%.02f EUR' % status._status['interest'],
'%.02f EUR' % status._status['account']]
rows.append(item)
else:
header = ['date', 'payment', 'interest', 'account']
for status in report._statuses:
item = [status.strdate,
'%.02f EUR' % status._status['payment'],
'%.02f EUR' % status._status['interest'],
'%.02f EUR' % status._status['account']]
rows.append(item)
return {'header': header, 'rows': rows}
def is_finished(self):
""" Returns true, if the loan has been payed back, including
interest for the current year """
return (self._caccount + self._sum_interest) >= 0.
def make_report(self, payment = 0, interest = 0,
foreign_account = '', kind = '', description = '',
meta = {}):
""" creates a report entry and resets some variables """
self._report.append(
date = self._current_date,
account = self._caccount / 100,
payment = payment / 100,
interest = float('%.2f' % (interest / 100)),
foreign_account = foreign_account,
kind = kind,
description = description,
meta = meta
)
@property
def account(self):
return (self._caccount + self._sum_interest) / 100
def get_account(self):
return self.account
def exec_interest_time(self):
""" Does all things, when self.interest_time() returns true (like adding
interests to the account """
self._caccount = int(round(self._caccount + self._sum_interest))
self.make_report(
interest = self._sum_interest,
kind = 'yearly interest'
)
self._sum_interest = 0
def interest_time(self):
""" Checks, whether it is time to book the interests to the account """
return (((self._current_date.day == self._interest_paydate['day']) and
(self._current_date.month == self._interest_paydate['month'])) or
(self._caccount > 0))
def payment_input(self, account_str, payment, kind, description, meta):
""" Input function for payments. This account is the receiver
of a transfer. This function, if derived from,
can account for special checks for input operations """
if ((self._caccount + self._sum_interest) >= 0):
return TransferMessage(C_transfer_NA, money = 0, message = "No credit to pay for")
payed = min(-(self._caccount + self._sum_interest), payment)
if payed == payment:
self._caccount = int(self._caccount + payed)
report = {'payment': payed,
'foreign_account': account_str,
'kind': kind,
'description': description,
'meta': meta}
self.make_report(**report)
else:
self._caccount = int(self._caccount + self._sum_interest + payed)
report = {'payment': payed,
'interest': self._sum_interest,
'foreign_account': account_str,
'kind': kind,
'description': description + ' + Interests',
'meta': meta}
self.make_report(**report)
self._sum_interest = 0
return TransferMessage(C_transfer_OK, money = payed)
def payment_output(self, account_str, payment, kind, description, meta):
""" Output function for payments. This account is the sender
of a transfer. This function, if derived from,
can account for special checks for output operations """
return TransferMessage(C_transfer_NA, money = 0, message = "Credit cannot be increased")
def return_money(self, money):
""" this is a hard return of transfer-money, in case the receiving side
rejected the transfer """
self._caccount = int(self._caccount + money)
report = {'date': self._current_date,
'account': self._caccount,
'payment': money,
'kind': 'storno',
'description': 'transfer did not succeeded'}
self._report.append(**report)
def start_of_day(self):
""" Things that should happen on the start of the day, before any money
transfer happens """
pass
def end_of_day(self):
""" Things that should happen at the end of the day, after all money
transfers have been accomplished """
# TODO: needs to be replaced by a mechanism that checks not every day
days_per_year = get_days_per_year(self._current_date.year)
# calculate interest for this day
interest = self._caccount * (self._interest / days_per_year)
# store interest for later calculations
self._sum_interest += interest
# if paydate is there, add the summed interest to the account
if self.interest_time():
self.exec_interest_time()
class Property(Account):
"""
This class can be used to reflect the amount of property that is gained
from filling up a loan. This account does nothing else than adjusting the
amount of property depending on the payments transfered to the loan class
"""
def __init__(self, property_value, amount, loan, date = None, name = None, meta = {}):
"""
For a property with a given value (property_value), the current amount
that is transfered to the owner (amount) is reflected by the amount of
money that has been transfered to the loan. Loan must here of class
loan
property_value : value of the property
amount : amount of money that represents the ownership. if
amount=property_value, the property totally belongs to the owner, if
amount<property_value, the property partly belongs to the loan holder
loan : object of type loan, that is linked to this property
date : date, for which this property starts to exist
name : name of this property
meta : meta-information
"""
assert isinstance(loan, Loan), 'loan must be of type Loan, but is in fact of type ' + str(type(loan))
assert property_value >= amount, 'property_value must be greater than amount'
self._name = validate.valid_name(name)
self._date_start = validate.valid_date(date)
self._meta = meta
self._property_value = int(property_value * 100)
self._account = int(amount*100) # amount of money already invested
self._caccount = self._account
self._loan = loan
# setting up the report and the semantics
self._report = Report(name = self._name)
self._report.add_semantics('account', 'saving_abs')
self._report.add_semantics('property_value', 'none')
self._current_date = self._date_start
self.make_report()
def make_report(self):
""" creates a report entry and resets some variables """
self._report.append(
date = self._current_date,
account = self._caccount / 100,
property_value = self._property_value / 100
)
def get_table_json(self, report):
""" Creates a table for a given report """
header = ['date', 'account']
rows = []
for status in report._statuses:
item = [status.strdate,
'%.02f' % status._status['account']
]
rows.append(item)
return {'header': header, 'rows': rows}
def get_account(self):
return self._caccount / 100
def payment_input(self, account_str, payment, kind, description, meta):
""" Input function for payments. This account is the receiver
of a transfer. This function, if derived from,
can account for special checks for input operations """
return TransferMessage(C_transfer_ERR, money = payment, message="Properties cannot be involved in transfers")
def payment_output(self, account_str, payment, kind, description, meta):
""" Output function for payments. This account is the sender
of a transfer. This function, if derived from,
can account for special checks for output operations """
return TransferMessage(C_transfer_ERR, money = payment, message="Properties cannot be involved in transfers")
def return_money(self, money):
""" this is a hard return of transfer-money, in case the receiving side
rejected the transfer """
pass
def end_of_day(self):
""" Things that should happen at the end of the day, after all money
transfers have been accomplished """
new_caccount = self._account + (1- (self._loan._caccount / self._loan._account)) * (self._property_value - self._account)
# this if-clause is included to avoid daily reporting. Reports are
# just updates, if account volume changes or if if is the end of a year
if ((new_caccount != self._caccount) or
((self._current_date.day == 31) and
(self._current_date.month == 12))):
self._caccount = new_caccount
self.make_report()
================================================
FILE: financial_life/financing/colors.py
================================================
'''
Created on 30.03.2016
@author: martin
'''
import xml.etree.ElementTree as ET
import re
def import_colors():
group_tag = '{http://www.w3.org/2000/svg}g'
rect_tag = '{http://www.w3.org/2000/svg}rect'
color_file = '/home/martin/ownCloud/Software/credit/misc/colors.svg'
tree = ET.parse(color_file)
root = tree.getroot()
layer = root.find(group_tag)
groups = layer.findall(group_tag)
colorgroups = []
for group in groups:
schemas = group.findall(group_tag)
colorgroup = []
for schema in schemas:
rects = schema.findall(rect_tag)
colors = []
for rect in rects:
colors.append(re.search('#[0-9a-f]{6}', rect.attrib['style']).group())
colorgroup.append(colors)
colorgroups.append(colorgroup)
print(colorgroups)
colors = [
[
['#9e63e7', '#b283ed', '#cbaaf5', '#e0ccf9', '#884dd7', '#6925c5', '#5512b0', '#430596'],
['#6463e6', '#8483ec', '#aaa9f5', '#cccbf9', '#4c4dd7', '#2526c4', '#1113b0', '#050596'],
['#629ce6', '#83b1eb', '#a8caf5', '#cbdef9', '#4b8bd7', '#256dc3', '#105ab0', '#054696'],
['#62cae5', '#82d5eb', '#a7e4f5', '#cbeef9', '#4bbbd6', '#25a4c3', '#0f92b0', '#057896'],
['#62e4df', '#82eae6', '#a6f5f2', '#cbf9f8', '#4ad6ce', '#25c3ba', '#0fb0a5', '#05968f']
],
[
['#c6e662', '#d2eb83', '#e2f5a8', '#edf9cb', '#b7d74b', '#a0c325', '#8cb010', '#749605'],
['#e6e462', '#ebeb83', '#f5f4a8', '#f8f9cb', '#d7d34b', '#c3c025', '#b0ab10', '#969305'],
['#e5a962', '#ebbb82', '#f5d1a7', '#f9e5cb', '#d6944b', '#c37825', '#b0630f', '#965205'],
['#e47a62', '#ea9682', '#f5b6a6', '#f9d5cb', '#d6634a', '#c34125', '#b02a0f', '#962005'],
['#e36269', '#e98287', '#f4a6aa', '#f9cbcc', '#d64954', '#c32530', '#b00f1c', '#96050e']
],
[
['#a2a2a2', '#b5b5b5', '#cdcdcd', '#e2e2e2', '#8f8f8f', '#747474', '#5f5f5f', '#4d4d4d']
],
[
['#7a68b3', '#9080c1', '#aa9dd3', '#c1b7df', '#6b5e97', '#4f4378', '#392b68', '#261953'],
['#6876b2', '#808dc0', '#9ca6d3', '#b6bddf', '#5d6997', '#434e77', '#2a3768', '#192553'],
['#6797b2', '#81a8be', '#9bbfd3', '#b6cfdf', '#5d8396', '#426577', '#295368', '#193f53'],
['#68b0b0', '#80bdbe', '#9ad2d3', '#b6dddf', '#5d9595', '#427777', '#286867', '#195353'],
['#68af9e', '#81bdae', '#99d3c5', '#b6dfd6', '#5d9486', '#427769', '#286857', '#195345']
],
[
['#aeb267', '#bcbe81', '#d1d39b', '#dcdfb6', '#95965d', '#767742', '#676829', '#515319'],
['#b2a267', '#beb281', '#d3c79b', '#dfd8b6', '#96895d', '#776b42', '#685a29', '#534619'],
['#b08168', '#be9580', '#d3ad9a', '#dfc5b6', '#956f5d', '#775342', '#683d28', '#532c19'],
['#af6869', '#bd8181', '#d39999', '#dfb7b6', '#945d5e', '#774244', '#68282a', '#53191a'],
['#ad697a', '#bb818f', '#d19aa8', '#dfb6bf', '#945c6c', '#774251', '#68283a', '#531928']
]
]
no_colors = len(colors[0])
================================================
FILE: financial_life/financing/identity.py
================================================
'''
Created on 18.11.2016
Some basic classes for generating and managing identities
@author: martin
'''
# standard libraries
import string
import random
def id_generator(
size=6,
chars=string.ascii_uppercase + string.ascii_lowercase + string.digits
):
""" Creates a unique ID consisting of a given number of characters with
a given set of characters """
return ''.join(random.choice(chars) for _ in range(size))
================================================
FILE: financial_life/financing/plotting.py
================================================
'''
Created on 30.03.2016
@author: martin
'''
#standard libraries
from datetime import timedelta
# custom libraries
from matplotlib.pyplot import *
from matplotlib import pyplot as plt
import matplotlib.patches as mpatches
import numpy as np
# own libraries
from .colors import colors, no_colors
from financial_life.calendar_help import Bank_Date
#ion()
# indices for colors in the colors-list
C_cold_colors = 0
C_warm_colors = 1
C_format_date = '%d.%m.%Y'
def bank_account(*reports):
fig = figure(figsize=(16, 13))
plot_stack_mult_abs(['input_cum', 'output_cum'], *reports,
color_themes = [C_warm_colors, C_cold_colors],
color_offset = 1)
title('Demands on the account')
return fig
def summary(*reports):
""" Summary plot for all reports """
fig = figure(figsize=(16, 13))
ax_date = subplot(3,2,1)
plot_stack_abs('saving_abs', *reports, color_theme = C_warm_colors, color_offset = 0)
title('Wealth')
subplot(3,2,2, sharex = ax_date)
plot_stack_abs('debt_abs', *reports, color_theme = C_cold_colors, color_offset = 0)
title('Debts')
subplot(3,2,3, sharex = ax_date)
plot_stack_mult_abs(['input_cum', 'output_cum'], *reports,
color_themes = [C_warm_colors, C_cold_colors],
color_offset = 1)
title('Input and output')
subplot(3,2,4, sharex = ax_date)
plot_stack_abs('debtpayment_cum', *reports, color_offset = 1)
title('Yearly payments')
ax_winloss = subplot(3,2,5, sharex = ax_date)
plot_stack_cum('win_cum', *reports, color_theme = C_warm_colors, color_offset = 2)
title('Cumulated win')
subplot(3,2,6, sharex = ax_date, sharey = ax_winloss)
plot_stack_cum('cost_cum', *reports, color_offset = 2)
title('Cumulated costs')
plt.tight_layout()
return fig
def summary_img(*reports, target = './', figsize = (10, 5), dpi = 100, prefix=''):
""" creates a series of images and stores them in the target directory """
data = {}
fig = figure(figsize = figsize)
plot_stack_abs('saving_abs', *reports, color_theme = C_warm_colors, color_offset = 0)
title('Wealth')
fig.savefig(target + prefix + 'wealth.png', dpi = dpi)
plt.close(fig)
data['img_wealth'] = target + prefix + 'wealth.png'
fig = figure(figsize = figsize)
plot_stack_abs('debt_abs', *reports, color_theme = C_cold_colors, color_offset = 0)
title('Debts')
fig.savefig(target + prefix + 'debts.png', dpi = dpi)
plt.close(fig)
data['img_debts'] = target + prefix + 'debts.png'
fig = figure(figsize = figsize)
plot_stack_mult_abs(['input_cum', 'output_cum'], *reports,
color_themes = [C_warm_colors, C_cold_colors],
color_offset = 1)
title('Input and output')
fig.savefig(target + prefix + 'io_money.png', dpi = dpi)
plt.close(fig)
data['img_io_money'] = target + prefix + 'io_money.png'
fig = figure(figsize = figsize)
plot_stack_abs('debtpayment_cum', *reports, color_offset = 1)
title('Yearly payments')
fig.savefig(target + prefix + 'debtpayment.png', dpi = dpi)
plt.close(fig)
data['img_debtpayment'] = target + prefix + 'debtpayment.png'
fig = figure(figsize = figsize)
plot_stack_cum('win_cum', *reports, color_theme = C_warm_colors, color_offset = 2)
title('Cumulated win')
fig.savefig(target + prefix + 'win_cum.png', dpi = dpi)
plt.close(fig)
data['img_win_cum'] = target + prefix + 'win_cum.png'
fig = figure(figsize = figsize)
plot_stack_cum('cost_cum', *reports, color_offset = 2)
title('Interests')
fig.savefig(target + prefix + 'cost_cum.png', dpi = dpi)
plt.close(fig)
data['img_cost_cum'] = target + prefix + 'cost_cum.png'
return data
def extract_data(semantic, *reports, color_theme = C_cold_colors, color_offset = 0):
""" helping function that extracts the dates and data from the reports """
X = []
Y = []
c = []
# create cost plots
for j, r in enumerate(reports):
X = X + [[d.timestamp() for d in r.date]]
Y = Y + [[r.get(k) for k in r.semantics(semantic)]]
c = c + [colors[color_theme][j % no_colors][i+color_offset] for i, k in enumerate(r.semantics(semantic))]
return X, Y, c
def add_labels(semantic, *reports, color_theme = C_cold_colors, color_offset = 0):
""" routine that adds labels to the plot in order to add a legend """
# these empty plots are needed as stack plot does not support labels for legends. bit of
# a hack from http://stackoverflow.com/questions/14534130/legend-not-showing-up-in-matplotlib-stacked-area-plot
for j, r in enumerate(reports):
for i, k in enumerate(r.semantics(semantic)):
plot([], [],
color=colors[color_theme][j % no_colors][i+color_offset],
label=r.name + ': ' + k,
linewidth=10)
def plot_stack_generic(X, Y, c, semantic, *reports, color_theme = C_cold_colors, color_offset = 0):
""" generic function for plotting stacks """
# bring the data together
dates, data = join_data(X, Y)
# format the dates to a readable format
str_dates = [Bank_Date.fromtimestamp(d).strftime(C_format_date) for d in dates]
add_labels(semantic, *reports, color_theme = color_theme, color_offset = color_offset)
if len(data) > 0:
stackplot(dates, data, colors = c)
else:
# if data is empty, plot at least zeros to make this plot more complete
plot(dates, np.zeros(len(dates)))
xticks(dates, str_dates, rotation=45)
def remove_nones(X):
""" Removes all Nones from a list and replaces them by zero """
return [[[0. if v is 'None' else v for v in d] for d in data] for data in X]
def add_zeros(X, Y):
""" add zeros at the beginning and end to prevent interpolation in join_data
from stacking to much up """
for x in X:
x.insert(0, x[0] - 1)
x.append(x[-1] + 1)
for data in Y:
for d in data:
d.insert(0, 0.)
d.append(0.)
return X, Y
def plot_stack_abs(semantic, *reports, color_theme = C_cold_colors, color_offset = 0):
""" Creates a stacked plot with cumulated sums for a given semantic """
X, Y, c = extract_data(semantic, *reports, color_theme = color_theme, color_offset = color_offset)
Y = remove_nones(Y)
X, Y = add_zeros(X, Y)
# create the generic plot
plot_stack_generic(X, Y, c, semantic, *reports, color_theme = color_theme, color_offset = color_offset)
legend(loc = 'upper right', fancybox=True, framealpha=0.4, prop={'size':10})
def plot_stack_mult_abs(semantics, *reports, color_themes, color_offset = 0):
""" Creates a stacked plot with cumulated sums for a given semantic """
for semantic, color_theme in zip(semantics, color_themes):
plot_stack_abs(semantic, *reports, color_theme = color_theme, color_offset = color_offset)
def plot_stack_cum(semantic, *reports, color_theme = C_cold_colors, color_offset = 0):
""" Creates a stacked plot with cumulated sums for a given semantic """
X, Y, c = extract_data(semantic, *reports, color_theme = color_theme, color_offset = color_offset)
Y = remove_nones(Y)
# add zeros only at the beginning
for x in X:
x.insert(0, x[0] - 1)
for data in Y:
for d in data:
d.insert(0, 0.)
# create the cumulated sum
Y = [np.cumsum(np.array(yi), 1) if yi else [] for yi in Y]
plot_stack_generic(X, Y, c, semantic, *reports, color_theme = color_theme, color_offset = color_offset)
legend(loc = 'upper left', fancybox=True, framealpha=0.4, prop={'size':10})
def join_data(dates_list, data_list):
""" This functions makes heterogenous time series data align
with one time series axis
dates : list of date-lists
data : list of data-lists_lock
Returns:
dates, and data, but this time, data shares the same
date-points
"""
# first get all unique dates from every sublist and make one list out of them
rdates = sorted(list(set([date for sublist in dates_list for date in sublist])))
rdata = []
# go through each vector and interpolate data if necessary
for dates, data_vecs in zip(dates_list, data_list):
for data in data_vecs:
if len(data) > 0:
rdata.append(np.interp(rdates,dates, data).tolist())
else: # if data is empty, then just create a zero-length vector
rdata.append(np.zeros(len(rdates)))
return rdates, rdata
================================================
FILE: financial_life/financing/test_financing.py
================================================
# -*- coding: utf-8 -*-
"""
Created on Thu Jun 23 21:40:47 2016
@author: martin
"""
import unittest
import financial_life.financing as financing
from financial_life.calendar_help import Bank_Date
from datetime import datetime
class Test_Create_Stop_Criteria(unittest.TestCase):
def test_date(self):
t = datetime(2016,10,15)
foo = financing.create_stop_criteria(t)
self.assertTrue(foo(datetime(2016,10,14)))
self.assertFalse(foo(datetime(2016,10,16)))
def test_callable(self):
t = lambda x: x < datetime(2016,11,18)
foo = financing.create_stop_criteria(t)
self.assertFalse(foo(datetime(2016,10,14)))
self.assertFalse(foo(datetime(2016,11,14)))
self.assertTrue(foo(datetime(2016,11,19)))
class TestRegular_Month_Payment(unittest.TestCase):
def setUp(self):
self.regular = {'from_acc': 'Dummy',
'to_acc': 'Dummy',
'interval': 'month',
'day' : 15,
'date_start': Bank_Date(2015, 3, 15),
'date_stop': Bank_Date(2015, 6, 15),
'payment': 3000,
'name' : 'Test',
'fixed': True,
'meta': {}
}
self.infinite = {'from_acc': 'Dummy',
'to_acc': 'Dummy',
'interval': 'month',
'day' : 15,
'date_start': Bank_Date(2015, 3, 15),
'date_stop': Bank_Date.max,
'payment': 3000,
'name' : 'Test',
'fixed': True,
'meta': {}
}
self.lastcal = {'from_acc': 'Dummy',
'to_acc': 'Dummy',
'interval': 'month',
'day' : 31,
'date_start': Bank_Date(2015, 1, 31),
'date_stop': Bank_Date(2015, 6, 15),
'payment': 3000,
'name' : 'Test',
'fixed': True,
'meta': {}
}
def test_begin_payment_next_month(self):
iterator = financing.iter_regular_month(self.regular, date_start = datetime(2015,3, 16))
payment = next(iterator)
self.assertEqual(payment['date'], datetime(2015,4,15))
iterator = financing.iter_regular_month(self.regular, date_start = datetime(2016,3, 16))
self.assertRaises(StopIteration, next, iterator)
def test_begin_payment_next_year(self):
iterator = financing.iter_regular_month(self.infinite, date_start = datetime(2016,3, 16))
payment = next(iterator)
self.assertEqual(payment['date'], datetime(2016, 4, 15))
def test_begin_payment_this_month(self):
iterator = financing.iter_regular_month(self.regular, date_start = datetime(2015,3, 15))
payment = next(iterator)
self.assertEqual(payment['date'], datetime(2015,3,15))
def test_proper_sequence(self):
iterator = financing.iter_regular_month(self.regular, date_start = datetime(2015,3, 20))
payment = next(iterator)
self.assertEqual(payment['date'], datetime(2015,4,15))
payment = next(iterator)
self.assertEqual(payment['date'], datetime(2015,5,15))
self.assertRaises(StopIteration, next, iterator)
def test_last_calender_day(self):
iterator = financing.iter_regular_month(self.lastcal, date_start = datetime(2015,2, 28))
payment = next(iterator)
self.assertEqual(payment['date'], datetime(2015,2,28))
class TestRegular_Year_Payment(unittest.TestCase):
def setUp(self):
self.regular = {'from_acc': 'Dummy',
'to_acc': 'Dummy',
'interval': 'yearly',
'day' : 15,
'date_start': Bank_Date(2015, 3, 15),
'date_stop': Bank_Date(2018, 3, 14),
'payment': 3000,
'name' : 'Test',
'fixed': True,
'meta': {}
}
self.infinite = {'from_acc': 'Dummy',
'to_acc': 'Dummy',
'interval': 'yearly',
'day' : 15,
'date_start': Bank_Date(2015, 3, 15),
'date_stop': Bank_Date.max,
'payment': 3000,
'name' : 'Test',
'fixed': True,
'meta': {}
}
self.lastcal = {'from_acc': 'Dummy',
'to_acc': 'Dummy',
'interval': 'yearly',
'day' : 31,
'date_start': Bank_Date(2015, 1, 31),
'date_stop': Bank_Date(2017, 6, 15),
'payment': 3000,
'name' : 'Test',
'fixed': True,
'meta': {}
}
def test_begin_payment_same_year(self):
iterator = financing.iter_regular_year(self.regular, date_start = datetime(2015,3, 15))
payment = next(iterator)
self.assertEqual(payment['date'], datetime(2015,3,15))
def test_begin_payment_next_year(self):
iterator = financing.iter_regular_year(self.infinite, date_start = datetime(2015,3, 16))
payment = next(iterator)
self.assertEqual(payment['date'], datetime(2016, 3, 15))
def test_proper_sequence(self):
iterator = financing.iter_regular_year(self.regular, date_start = datetime(2015,3, 20))
payment = next(iterator)
self.assertEqual(payment['date'], datetime(2016,3,15))
payment = next(iterator)
self.assertEqual(payment['date'], datetime(2017,3,15))
self.assertRaises(StopIteration, next, iterator)
if __name__ == '__main__':
unittest.main()
================================================
FILE: financial_life/financing/test_meta.py
================================================
'''
Created on 04.01.2017
@author: martin
'''
# standard libraries
from datetime import datetime, timedelta
import unittest
# own libraries
from financial_life.examples import meta_data
class Test(unittest.TestCase):
def setUp(self):
# run a simulation that makes use of meta-information
self.s = meta_data.example_meta_controller(print_it=False)
def tearDown(self):
pass
def test_account_and_simulation(self):
""" Test that meta-information are in the account class and
in the simulation class """
accounts = self.s.accounts[0]
s_income_report = self.s.report.subset(
lambda st: st.meta.get('type','') == 'income')
a_income_report = accounts.report.subset(
lambda st: st.meta.get('type','') == 'income')
s_interests = sum(s_income_report.value)
a_interests = sum(a_income_report.input)
self.assertTrue(len(s_income_report) == len(a_income_report),
'Reprots in Simulation and Account class with meta-information have not the same length')
self.assertTrue(s_interests == a_interests,
'Values in the account and simulation class are not the same')
if __name__ == "__main__":
#import sys;sys.argv = ['', 'Test.testName']
unittest.main()
================================================
FILE: financial_life/financing/test_status.py
================================================
'''
Created on 15.12.2016
@author: martin
'''
# standard libraries
from datetime import datetime, timedelta
import unittest
# own libraries
from financial_life.financing import accounts as a
from financial_life.financing import Status
class Test(unittest.TestCase):
def setUp(self):
""" Create setup for making sure that meta-data are
transfered to the status of payments """
account = a.Bank_Account(amount = 1000, interest = 0.001, name = 'Main account', date=datetime(2016,9, 1))
savings = a.Bank_Account(amount = 5000, interest = 0.013, name = 'Savings', date=datetime(2016,9, 1))
loan = a.Loan(amount = 100000, interest = 0.01, name = 'House Credit', date=datetime(2016,9, 1))
simulation = a.Simulation(account, savings, loan, name = 'Testsimulation', date=datetime(2016,9, 1))
simulation.add_regular(from_acc = 'Income',
to_acc = account,
payment = 2000,
interval = 'monthly',
date_start = datetime(2016,9,15),
day = 15,
name = 'Income')
simulation.add_regular(from_acc = account,
to_acc = savings,
payment = 500,
interval = 'monthly',
date_start = datetime(2016,9,30),
day = 30,
name = 'Savings',
meta = {'tax': 100})
simulation.simulate(delta=timedelta(days=2000))
self.simulation = simulation
def test_InitStatus(self):
# initialize without data field
s = Status(datetime(2016,9,1), a=2, b=3, ceta=4)
self.assertDictEqual(s._status, {'a':2, 'b':3, 'ceta':4})
self.assertDictEqual(s._meta, {})
# initialize with data field
s = Status(datetime(2016,9,1), a=2, b=3, meta={'test': 3})
self.assertDictEqual(s._status, {'a':2, 'b':3})
self.assertDictEqual(s._meta, {'test': 3})
def test_str(self):
s = Status(datetime(2016,9,1), a=2, b=3, ceta=4)
str(s)
def test_SimulationReport(self):
# the report of the simulation-class itself should be displayable
status1 = self.simulation.report._statuses[0]
status2 = self.simulation.report._statuses[1]
self.assertDictEqual(status1._meta, {})
print(status2._meta)
self.assertDictEqual(status2._meta, {'tax': 100})
if __name__ == "__main__":
#import sys;sys.argv = ['', 'Test.testName']
unittest.main()
================================================
FILE: financial_life/financing/validate.py
================================================
# -*- coding: utf-8 -*-
"""
Created on Mon Jun 27 21:27:23 2016
Collection of validation messages
@author: martin
"""
# standard libraries
from datetime import datetime, timedelta
from collections import Callable
# custom libraries
# own libraries
from financial_life.financing.identity import id_generator
from financial_life.calendar_help import Bank_Date
date_formats = [
'%d.%m.%Y',
'%d.%m.%y',
'%m/%d/%Y',
'%m/%d/%y',
'%Y-%m-%d',
'%y-%m-%d',
]
def parse_datestring(datestr):
""" Tries to parse the datestring against a few common formats """
for format in date_formats:
try:
date = Bank_Date.strptime(datestr, format)
return date
except ValueError:
pass
def valid_date(date):
""" routine for making a date out of anything that the user might
have given to the function """
if date is None:
return Bank_Date.today()
if isinstance(date, Bank_Date):
return date
if isinstance(date, datetime):
return Bank_Date.fromtimestamp(date.timestamp())
if isinstance(date, str):
return parse_datestring(date)
raise TypeError("Date must be from type datetime, callable or a parseable string")
def valid_stop_date(date):
""" routine for makig a date out of anything that the user might
have given to the function """
if isinstance(date, Callable):
return date
else:
return valid_date(date)
raise TypeError("Date must be at least from type datetime or callable")
def valid_name(name):
if not name:
name = id_generator(8)
return name
def valid_date_stop(date_stop):
""" checks, whether date_stop has a valid value """
if not date_stop:
date_stop = Bank_Date.max
return date_stop
def valid_delta(delta):
""" converts delta, if necessary, into a timedelta instance """
if not delta:
delta = timedelta.max
if isinstance(delta, int):
delta = timedelta(days = delta)
assert isinstance(delta, timedelta), 'delta must be of type timedelta or int'
assert delta.days > 0, 'delta must be positive'
return delta
================================================
FILE: financial_life/products/__init__.py
================================================
================================================
FILE: financial_life/products/germany/__init__.py
================================================
================================================
FILE: financial_life/products/germany/lbs/__init__.py
================================================
'''
This is a reconstruction of the LBS Bauspar Tarife
WARNING: This is a manual attempt to simualate some of the LBS products. This is by no means
a reliable and accurate simulation! For a realistic simulation of your financial plans, please
consult a pofessional assistent from the company selling these products.
'''
# standard libraries
from datetime import datetime, timedelta
from calendar import monthrange
from decimal import *
# custom libraries
import numpy as np
# own libraries
from financial_life.financing import Report, Payments
from financial_life.financing import C_default_payment, id_generator
from financial_life.financing.colors import colors
from financial_life.financing import validate
from financial_life.financing.accounts import Account, C_format_date, C_max_time
from financial_life.calendar_help import Bank_Date
flex_l5 = {
'C_POINT_PER_DAY': 0.0563,
'C_POINT_PER_EUR': 1 / 750.,
'C_POINT_LIMIT': 171,
'guthabenzins' : 0.0025,
'entgelt' : 7.2,
'bausparanteil': 0.4,
'darlehenszins': 0.0215,
'wartemonate': 4,
'agio': 0.02,
'versicherung': 0.003,
'name' : 'Flex L5',
}
direkt_10 = {
'C_POINT_PER_DAY': 0.0403,
'C_POINT_PER_EUR': 1 / 650.,
'C_POINT_LIMIT': 176,
'guthabenzins' : 0.001,
'entgelt' : 7.2,
'bausparanteil': 0.4,
'darlehenszins': 0.0195,
'wartemonate': 3,
'agio': 0.02,
'versicherung': 0.003,
'name' : 'Direkt 10',
}
direkt_15 = {
'C_POINT_PER_DAY': 0.0403,
'C_POINT_PER_EUR': 1 / 650.,
'C_POINT_LIMIT': 176,
'guthabenzins' : 0.001,
'entgelt' : 7.2,
'bausparanteil': 0.4,
'darlehenszins': 0.0175,
'wartemonate': 3,
'agio': 0.02,
'versicherung': 0.003,
'name' : 'Direkt 15',
}
alternative = {
'C_POINT_PER_DAY': 0.0403,
'C_POINT_PER_EUR': 1 / 650.,
'C_POINT_LIMIT': 176,
'guthabenzins' : 0.001,
'entgelt' : 7.2,
'bausparanteil': 0.4,
'darlehenszins': 0.025,
'wartemonate': 3,
'agio': 0.02,
'versicherung': 0.003,
'name' : 'Direkt 15',
}
tarife = {
'flex_l5': flex_l5,
'direkt_10': direkt_10,
'direkt_15': direkt_15,
'alternative': alternative
}
class Bauspar(Account):
""" This is a generic class for the german LBS Bauspar product """
def __init__(self, guthaben, bausparsumme, punkte, tarif, date = None, name = None):
if tarif not in tarife:
raise TypeError("Contract type not found in contract list: {}".format(tarif))
self._tarif = tarife[tarif]
self._date_start = self.valid_date(date)
self._name = self.valid_name(name)
self._report = Report(
name = self._name + ' - ' + str(self._date_start.strftime(C_format_date)))
self._report.add_semantics('account', 'saving_abs')
self._report.add_semantics('loan', 'debt_abs')
self._report.add_semantics('loan_interest', 'cost_cum')
self._report.add_semantics('account_interest', 'win_cum')
self._report.add_semantics('points', 'none')
self._report.add_semantics('payments', 'debtpayment_cum')
self._report.add_semantics('agio', 'cost_cum')
self._report.add_semantics('insurance', 'cost_cum')
self._report.add_semantics('entgelt', 'cost_cum')
self._guthaben = int(guthaben * 100)
self._bausparsumme = int(bausparsumme * 100)
self._darlehen = self._bausparsumme - np.max(
[
self._bausparsumme * self._tarif['bausparanteil'],
self._guthaben
]
)
self._punkte = punkte
self._payments = Payments()
self._payments_iter = None
self._sum_interest = 0 # this value is used because it is inherited from account
self._sum_loan_interest = 0 # but we need an extra variable for the loan interest
self._sum_loan_insurance = 0
self._day = -1
self._current_date = self._date_start
self._caccount = self._guthaben
self._cdarlehen = self._darlehen
self._next_pay = None
self._interest_paydate = {'month': 12, 'day': 31}
# this function determines, in which phase of the product we are
self._phase = self.saving_phase
# reporting functionality
self._record = {
'loan_interest': 0,
'account_interest': 0,
'payments' : 0,
'entgelt' : 0,
'insurance': 0,
'agio': 0
}
@property
def loan(self):
return self._cdarlehen / 100
def get_loan(self):
""" alternative method to get the current loan value. this method
can be used, e.g. in payment-definitions to transfer the amount of
money that a specific account has in the moment this payment is done.
Instead of using an actual value, this method is called, evaluated and
the return value is used """
return self.loan
def simulate(self, date_stop = None, delta = None, last_report = True):
""" Simulates the state of the account to the end-date.
If there is no end_date, the simulation will run until account is either
zero or the account continuously increases 10 times in a row
delta:
Time (e.g. days) to simulate. This argument can be used along
with date_stop. Whatever comes first, aborts the while-loop
last_report:
if True, after the while-loop a report will be added. For
simulations along with other products, this can be omitted by
setting this argument to False
"""
date_stop = validate.valid_date_stop(date_stop)
delta = validate.valid_delta(delta)
# if this is not the time for this product, abort
if date_stop < self._current_date:
return
if (not self._payments_iter):
self._payments_iter = self._payments.payment(self._current_date)
if (not self._next_pay):
self._next_pay = next(self._payments_iter, C_default_payment)
self._phase(date_stop, delta, last_report)
def get_credit(self):
""" Switches to a new modus in this product, in which the loan is given to the
customer. Depending on the amount of points and the account, the customer
needs to enter the so-called "zwischenfinanzierung"
"""
self._cdarlehen = self._bausparsumme - self._caccount
if (self._punkte < self._tarif['C_POINT_LIMIT'] or
self._caccount < (self._tarif['bausparanteil'] * self._bausparsumme)):
self._phase = self.zwischen_phase
else:
self._phase = self.loan_phase
agio = self._cdarlehen * self._tarif['agio']
self._cdarlehen += agio
self._report.append(date = self._current_date,
agio = (agio / 100))
def loan_track_data(self, loan_interest, loan_insurance, payed):
self._record['loan_interest'] += loan_interest
self._record['insurance'] += loan_insurance
self._record['payments'] += payed
def loan_make_report(self):
self._report.append(date = self._current_date,
loan_interest = self._record['loan_interest'] / 100,
insurance = self._record['insurance'] / 100,
payments = self._record['payments'] / 100,
loan = self._cdarlehen / 100,
)
# set everything to zero
self._record = dict.fromkeys(self._record, 0)
def loan_exec_interest_time(self):
self._cdarlehen = int(round(self._cdarlehen + self._sum_loan_interest + self._sum_loan_insurance))
self._sum_loan_interest = 0
self._sum_loan_insurance = 0
def loan_phase(self, date_stop = None, delta = None, last_report = True):
""" Routine for the payment of the loan """
temp_delta = 0
while ((self._current_date < date_stop) and # ...stop-date is reached
(temp_delta < delta.days) and # and delta has not been exeeded
((self._current_date - self._date_start).days < C_max_time) and
(self._cdarlehen > 0)): # ...number of simulated days exceeds max
# go to next day
self._day += 1
self._current_date = self._date_start + timedelta(days = self._day)
temp_delta += 1
# calculate the day
self._cdarlehen, loan_interest, loan_insurance, payed = self.loan_simulate_day()
# store interest for later calculations
self._sum_loan_interest += loan_interest
self._sum_loan_insurance += loan_insurance
# if paydate is there, add the summed interest to the account
if self.interest_time():
self.loan_exec_interest_time()
# tracking for reports
self.loan_track_data(loan_interest, loan_insurance, payed)
# make a report
if self.report_time(self._current_date):
self.loan_make_report()
# create report at the end of the simulation
if last_report:
# as the simulation might not end at the end of the year,
# we need to apply exec_interest_time() one last time
self.exec_interest_time()
self.loan_make_report()
def loan_simulate_day(self):
days_per_year = self.get_days_per_year()
payed = self.get_payments()
payed = min(self._cdarlehen + self._sum_loan_interest + self._sum_loan_insurance, payed)
new_darlehen = int(self._cdarlehen - payed)
loan_interest = new_darlehen * (self._tarif['darlehenszins'] / days_per_year)
loan_insurance = new_darlehen * (self._tarif['versicherung'] / days_per_year)
return new_darlehen, loan_interest, loan_insurance, payed
def zwischen_track_data(self, account_interest, loan_interest, payed, entgelt):
""" tracks data during saving phase """
self._record['account_interest'] += account_interest
self._record['loan_interest'] += loan_interest
self._record['payments'] += payed
self._record['entgelt'] += entgelt
def zwischen_make_report(self):
self._report.append(date = self._current_date,
account = self._caccount / 100,
account_interest = self._record['account_interest'] / 100,
loan_interest = self._record['loan_interest'] / 100,
payments = self._record['payments'] / 100,
entgelt = self._record['entgelt'] / 100,
loan = self._cdarlehen / 100,
points = self._punkte
)
# set everything to zero
self._record = dict.fromkeys(self._record, 0)
def zwischen_exec_interest_time(self):
self._caccount = int(round(self._caccount + self._sum_interest - self._sum_loan_interest))
self._sum_interest = 0
self._sum_loan_interest = 0
def zwischen_phase(self, date_stop = None, delta = None, last_report = True):
""" Routine for the phase called 'Zwischenfinanzierung' """
temp_delta = 0
while ((self._current_date < date_stop) and # ...stop-date is reached
(temp_delta < delta.days) and # and delta has not been exeeded
((self._current_date - self._date_start).days < C_max_time) and
(self._punkte < self._tarif['C_POINT_LIMIT'] or
self._caccount < (self._tarif['bausparanteil'] * self._bausparsumme) )): # ...number of simulated days exceeds max
# go to next day
self._day += 1
self._current_date = self._date_start + timedelta(days = self._day)
temp_delta += 1
# calculate the day
self._caccount, account_interest, loan_interest, payed, entgelt = self.zwischen_simulate_day()
# store interest for later calculations
self._sum_interest += account_interest
self._sum_loan_interest += loan_interest
# if paydate is there, add the summed interest to the account
if self.interest_time():
self.zwischen_exec_interest_time()
self._cdarlehen = self._bausparsumme - self._caccount
# tracking for reports
self.zwischen_track_data(account_interest, loan_interest, payed, entgelt)
# make a report
if self.report_time(self._current_date):
self.zwischen_make_report()
# when the while loop ended because the points are above the limit, then we can
# switch to the next phase
if (self._punkte >= self._tarif['C_POINT_LIMIT']):
self.get_credit()
# if simulation time is not over yet, continue with simulating the loan_phase
if ((self._current_date < date_stop) and
(temp_delta < delta.days) and
((self._current_date - self._date_start).days < C_max_time)):
self.loan_phase(date_stop, delta, last_report)
else:
# create report at the end of the simulation
if last_report:
# as the simulation might not end at the end of the year,
# we need to apply exec_interest_time() one last time
self.exec_interest_time()
self.zwischen_make_report()
def zwischen_simulate_day(self):
days_per_year = self.get_days_per_year()
new_account = self._caccount
entgelt = 0
if (self._current_date.day == 1) and (self._current_date.month == 1):
entgelt = self._tarif['entgelt'] * 100
new_account -= entgelt
payed = self.get_payments()
new_account = int(new_account + payed)
self._punkte += (payed / 100) * self._tarif['C_POINT_PER_EUR']
account_interest = new_account * (self._tarif['guthabenzins'] / days_per_year)
loan_interest = self._bausparsumme * (self._tarif['darlehenszins'] / days_per_year)
self._punkte += self._tarif['C_POINT_PER_DAY']
return new_account, account_interest, loan_interest, payed, entgelt
def saving_track_data(self, interest, payed, entgelt):
""" tracks data during saving phase """
self._record['account_interest'] += interest
self._record['payments'] += payed
self._record['entgelt'] += entgelt
def saving_make_report(self):
self._report.append(date = self._current_date,
account = self._caccount / 100,
account_interest = self._record['account_interest'] / 100,
payments = self._record['payments'] / 100,
entgelt = self._record['entgelt'] / 100,
points = self._punkte
)
# set everything to zero
self._record = dict.fromkeys(self._record, 0)
def saving_phase(self, date_stop = None, delta = None, last_report = True):
temp_delta = 0
while ((self._current_date < date_stop) and # ...stop-date is reached
(temp_delta < delta.days) and # and delta has not been exeeded
((self._current_date - self._date_start).days < C_max_time)): # ...number of simulated days exceeds max
# go to next day
self._day += 1
self._current_date = self._date_start + timedelta(days = self._day)
temp_delta += 1
# calculate the day
self._caccount, interest, payed, entgelt = self.saving_simulate_day()
# store interest for later calculations
self._sum_interest += interest
# if paydate is there, add the summed interest to the account
if self.interest_time():
self.exec_interest_time()
# tracking for reports
self.saving_track_data(interest, payed, entgelt)
# make a report
if self.report_time(self._current_date):
self.saving_make_report()
# create report at the end of the simulation
if last_report:
# as the simulation might not end at the end of the year,
# we need to apply exec_interest_time() one last time
self.exec_interest_time()
self.saving_make_report()
def saving_simulate_day(self):
days_per_year = self.get_days_per_year()
new_account = self._caccount
entgelt = 0
if (self._current_date.day == 1) and (self._current_date.month == 1):
entgelt = self._tarif['entgelt'] * 100
new_account -= entgelt
payed = self.get_payments()
new_account = int(new_account + payed)
self._punkte += (payed / 100) * self._tarif['C_POINT_PER_EUR']
interest = new_account * (self._tarif['guthabenzins'] / days_per_year)
self._punkte += self._tarif['C_POINT_PER_DAY']
return new_account, interest, payed, entgelt
================================================
FILE: financial_life/reports/__init__.py
================================================
import platform
sl = '/'
if platform.system() == 'Windows':
sl = '\\'
================================================
FILE: financial_life/reports/excel.py
================================================
'''
Created on 29.05.2017
@author: martin
'''
# standard libraries
import os
# third-party libraries
import pandas as pd
# own libraries
from financial_life.reports import sl
def report(simulation, filename='report.xls'):
""" This function generates a report as an excel sheet.
simulation the simualation that should be exported to excel
filename filename of the excel file
"""
writer = pd.ExcelWriter(filename)
for account in simulation.accounts:
df = account.report.as_df()
df.to_excel(writer, sheet_name=account.name)
writer.save()
================================================
FILE: financial_life/reports/html.py
================================================
'''
Created on 03.10.2016
@author: martin
'''
# standard libraries
import os
import imp
import platform
# third-party libraries
from jinja2 import Template
# own libraries
from financial_life.reports import sl
path_template = '..{sl}templates{sl}html'.format(sl=sl)
def report(simulation, style = 'standard', output_dir = 'report'):
""" This is a generic report function that renders html-templates
defined by the style-argument.
'style' refers to a template-folder in '../templates/html'. A
file render.py must be included in the template-folder with a method
'render(simulation, output_dir)' that does the job. New html-templates
can be easily added by creating new subfolders in '../templates/html/'
with html files and a render.py
"""
cwd = os.path.dirname(os.path.realpath(__file__))
template_folder = cwd + sl + path_template + sl + style + sl
render_module = imp.load_source('render', template_folder + 'render.py')
render_module.render(simulation, output_dir)
================================================
FILE: financial_life/tax/__init__.py
================================================
================================================
FILE: financial_life/tax/germany/__init__.py
================================================
""" help functions for german tax declaration """
def tax_to_pay(year, *args, **kwargs):
""" generic functions to call the tax-function for any year.
This functions simply calls the year-dependent function with
the given parameters, but this function don't really care about
the content of the other arguments """
return tax_functions[year](*args, **kwargs)
def tax_to_pay_2016(tax_relevant_money, splitting = False):
""" calculates the tax for year 2016
Returns the tax and the percentage of the tax """
if tax_relevant_money <= 0:
return 0, 0
if splitting:
trm = tax_relevant_money / 2.
else:
trm = tax_relevant_money
if trm > 250730:
tax = trm * 0.45 - 15783.19
elif trm > 52881:
tax = trm * 0.42-8261.29
elif trm > 13469:
tax = (trm-13469)*((trm-13469)*0.0000022874+0.2397)+948.68
elif trm > 8472:
tax = (trm-8472)*((trm-8472)*0.000009976+0.14)
else:
tax = 0
if splitting:
return tax*2, ((tax*2) / tax_relevant_money)
else:
return float(int(tax*100)/100), (tax / tax_relevant_money)
tax_functions = {2016: tax_to_pay_2016}
================================================
FILE: financial_life/templates/__init__.py
================================================
================================================
FILE: financial_life/templates/html/__init__.py
================================================
================================================
FILE: financial_life/templates/html/standard/__init__.py
================================================
================================================
FILE: financial_life/templates/html/standard/account_details.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<title>Account Detail: {{account_name}}</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
</head>
<body>
<h1>{{account_name}}</h1>
<div class="col-md-6">
<img alt="" width="100%" src="{{img_wealth}}">
</div>
<div class="col-md-6">
<img alt="" width="100%" src="{{img_debts}}">
</div>
<div class="col-md-6">
<img alt="" width="100%" src="{{img_io_money}}">
</div>
<div class="col-md-6">
<img alt="" width="100%" src="{{img_debtpayment}}">
</div>
<div class="col-md-6">
<img alt="" width="100%" src="{{img_win_cum}}">
</div>
<div class="col-md-6">
<img alt="" width="100%" src="{{img_cost_cum}}">
</div>
<p><a href="{{backlink}}">back to overview</a></p>
{% for table in tables %}
<div class="col-md-12">
<div class="row">
<h3>{{ table.category }}</h3>
<table class="table">
<tr>
{% for head in table.data.header %}
<th>{{head}}</th>
{% endfor %}
</tr>
{% for row in table.data.rows %}
<tr>
{% for item in row %}
<td>{{item}}</td>
{% endfor %}
</tr>
{% endfor %}
</table>
</div>
</div>
{% endfor %}
</body>
</html>
================================================
FILE: financial_life/templates/html/standard/index.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<title>Finanzuebersicht</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
</head>
<body>
<h2>{{title}}</h2>
<p>{{date}}</p>
<div class="col-md-6">
<img alt="" width="100%" src="{{img_wealth}}">
</div>
<div class="col-md-6">
<img alt="" width="100%" src="{{img_debts}}">
</div>
<div class="col-md-6">
<img alt="" width="100%" src="{{img_io_money}}">
</div>
<div class="col-md-6">
<img alt="" width="100%" src="{{img_debtpayment}}">
</div>
<div class="col-md-6">
<img alt="" width="100%" src="{{img_win_cum}}">
</div>
<div class="col-md-6">
<img alt="" width="100%" src="{{img_cost_cum}}">
</div>
<div class="col-md-8">
<h3>Accounts</h3>
<table class="table">
<tr>
<th>No.</th>
<th>name</th>
<th>type</th>
<th>start value</th>
<th>start date</th>
</tr>
{% for a in accounts %}
<tr>
<td>{{ a.index }}</td>
<td><a href="{{a.link}}">{{ a.name }}</a></td>
<td>{{ a.type }}</td>
<td>{{ "{:,.2f}".format(a.start_value) }}</td>
<td>{{ a.start_date }}</td>
</tr>
{% endfor %}
</table>
</div>
<div class="col-md-8">
<h3>Regular Payments</h3>
<table class="table">
<tr>
<th>from</th>
<th>to</th>
<th>interval</th>
<th>day</th>
<th>start date</th>
<th>stop date</th>
<th>payment</th>
<th>name</th>
<th>fixed</th>
</tr>
{% for r in payments_regular %}
<tr>
<td>{{ r.from_acc }}</td>
<td>{{ r.to_acc }}</td>
<td>{{ r.interval }}</td>
<td>{{ r.day }}</td>
<td>{{ r.date_start }}</td>
<td>{{ r.date_stop }}</td>
<td>{{ r.payment }}</td>
<td>{{ r.name }}</td>
<td>{{ r.fixed }}</td>
</tr>
{% endfor %}
</table>
</div>
<div class="col-md-8">
<h3>Unique Payments</h3>
<table class="table">
<tr>
<th>from</th>
<th>to</th>
<th>date</th>
<th>payment</th>
<th>name</th>
<th>fixed</th>
</tr>
{% for u in payments_unique %}
<tr>
<td>{{ u.from_acc }}</td>
<td>{{ u.to_acc }}</td>
<td>{{ u.date }}</td>
<td>{{ u.payment }}</td>
<td>{{ u.name }}</td>
<td>{{ u.fixed }}</td>
</tr>
{% endfor %}
</table>
</div>
</body>
</html>
================================================
FILE: financial_life/templates/html/standard/render.py
================================================
'''
Created on 03.10.2016
@author: martin
'''
# standard libraries
import os
from datetime import datetime
# third-party libraries
from jinja2 import Template
# own libraries
from financial_life.financing import plotting as plt
from financial_life.reports import sl
path_img = 'img'
path_accounts = 'accounts'
def render(simulation, output_dir = 'report'):
print("Calling render function")
template_folder = os.path.dirname(os.path.realpath(__file__))
img_folder = output_dir + sl + path_img + sl
accounts_folder = path_accounts + sl
print('Template Folder: %s' % template_folder)
# makedirs creates also all intermediate folders, therefore, we don't need
# to create result_folder explicitely
if not os.path.exists(img_folder):
os.makedirs(img_folder)
if not os.path.exists(output_dir + sl + accounts_folder):
os.makedirs(output_dir + sl + accounts_folder)
img_data = plt.summary_img(*simulation.reports('yearly'), target = img_folder)
data = {}
data['title'] = 'Kalkulation: ' + output_dir
data['date'] = datetime.now().strftime("%d.%m.%Y - %H:%M:%S")
data.update(img_data)
data.update(simulation.get_payments_unique_json())
data.update(simulation.get_payments_regular_json())
accounts = simulation.get_accounts_json()
links = render_accounts(simulation, template_folder, output_dir, accounts_folder)
# get_accounts_json and render_accounts iterate through simulation.account
# therefore, the order in both is equal and we can add the link to the
# accounts json. this is not the safest way but I did not wanted to put
# the get_accounts_json routine into this render-function
for a, l in zip(accounts['accounts'], links):
a['link'] = l
data.update(accounts)
index_file = "index.html"
with open(template_folder + sl + index_file, 'r') as f:
content = f.read()
t = Template(content)
with open(output_dir + sl + index_file, 'w') as o:
o.write(t.render(**data))
def render_accounts(simulation, template_folder, output_dir = 'report', accounts_folder = path_accounts):
""" Renders for each account a detailed page with all account-specific
data """
accounts = simulation.accounts
links = []
# image folder for the account related pictures
img_folder = output_dir + sl + accounts_folder + 'img' + sl
if not os.path.exists(img_folder):
os.makedirs(img_folder)
for (i, a) in enumerate(accounts):
account_name = a.name
account_name.replace(' ', '_')
prefix = 'account_details_%03i_%s' % (i, account_name)
account_link = accounts_folder + prefix + '.html'
print('Render %s' % account_link)
links.append(account_link)
data = {}
data['account_name'] = a.name
data['tables'] = a.get_report_json(interval='all')
data['backlink'] = '..' + sl + 'index.html'
img_data = plt.summary_img(a.report.yearly(), target = img_folder, prefix = prefix)
data.update(img_data)
template_file = 'account_details.html'
with open(template_folder + sl + template_file, 'r') as f:
content = f.read()
t = Template(content)
with open(output_dir + sl + account_link, 'w') as o:
o.write(t.render(**data))
return links
================================================
FILE: financial_life/test_general.py
================================================
'''
Created on 12.12.2016
@author: martin
'''
# standard libraries
from datetime import timedelta, datetime
import os
import unittest
# own libraries
from financial_life.financing import accounts as a
from financial_life.reports import html
class Test(unittest.TestCase):
def setUp(self):
""" Mostly taken from examples/simple_example.py """
account = a.Bank_Account(amount = 1000, interest = 0.001, name = 'Main account', date=datetime(2016,9, 1))
savings = a.Bank_Account(amount = 5000, interest = 0.013, name = 'Savings', date=datetime(2016,9, 1))
loan = a.Loan(amount = 100000, interest = 0.01, name = 'House Credit', date=datetime(2016,9, 1))
simulation = a.Simulation(account, savings, loan, name = 'Testsimulation', date=datetime(2016,9, 1))
simulation.add_regular(from_acc = 'Income',
to_acc = account,
payment = 2000,
interval = 'monthly',
date_start = datetime(2016,9,15),
day = 15,
name = 'Income')
simulation.add_regular(from_acc = account,
to_acc = savings,
payment = 500,
interval = 'monthly',
date_start = datetime(2016,9,30),
day = 30,
name = 'Savings')
simulation.add_regular(from_acc = account,
to_acc= loan,
payment = 1000,
interval = 'monthly',
date_start = datetime(2016,9,15),
day = 15,
name = 'Debts',
fixed = False,
date_stop = lambda cdate: loan.is_finished())
simulation.add_regular(from_acc = account,
to_acc= loan,
payment = lambda : min(8000, max(0,account.get_account()-4000)),
interval = 'yearly',
date_start = datetime(2016,11,20),
day = 20,
name = 'Debts',
fixed = False,
date_stop = lambda cdate: loan.is_finished())
simulation.simulate(delta=timedelta(days=2000))
self.simulation = simulation
self.account = account
self.loan = loan
def tearDown(self):
pass
def testGeneral(self):
interests = sum(self.account.report.yearly().interest)+sum(self.loan.report.yearly().interest)
self.assertTrue(abs(interests - (-3086.08)) < 0.00001)
if __name__ == "__main__":
#import sys;sys.argv = ['', 'Test.testName']
unittest.main()
================================================
FILE: requirements.txt
================================================
Jinja2>=2.7.2
matplotlib>=1.5.3
numpy>=1.11.2
pandas>=0.25.3
tabulate>=0.7.5
xlwt
================================================
FILE: setup.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Created on Tue Nov 8 21:26:07 2016
@author: martin
"""
try:
from setuptools import setup
have_setuptools = True
except ImportError:
from distutils.core import setup
have_setuptools = False
skw = dict(
name='financial_life',
version='0.9.4',
description='A framework for analysing financial products in personalized contexts',
author='Martin Pyka',
author_email='martin.pyka@gmail.com',
maintainer='Martin Pyka',
maintainer_email='martin.pyka@gmail.com',
url='https://github.com/MartinPyka/financial_life',
keywords=["finance", "analysis", "simulation", "loan", "bank"],
license="Apache License, Version 2.0",
packages=['financial_life',
'financial_life.calendar_help',
'financial_life.examples',
'financial_life.financing',
'financial_life.products.germany.lbs',
'financial_life.reports',
'financial_life.tax.germany',
'financial_life.templates.html.standard',
],
package_data={'financial_life': ['templates/html/standard/*.html']}
)
if have_setuptools is True:
skw['install_requires'] = [
'Jinja2>=2.7.2,<3',
'matplotlib>=1.3.1,<2',
'numpy>=1.8.1,<2',
'pandas>=0.18.1,<1',
'tabulate>=0.7.5,<1',
'xlwt>=1.2.0',
]
setup(**skw)
================================================
FILE: unittests.sh
================================================
#!/bin/bash
# run all unittests in this package
python3 -m unittest discover -v
gitextract_j_yn7vd1/ ├── .github/ │ └── workflows/ │ └── sonarcloud.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── MANIFEST ├── README.md ├── docs/ │ ├── 01_first_simulation.md │ ├── 02_using_callables_for_dynamic_changes.md │ ├── 03_dependencies_between_accounts.md │ └── README.md ├── financial_life/ │ ├── README.md │ ├── __init__.py │ ├── calendar_help/ │ │ └── __init__.py │ ├── constants/ │ │ ├── __init__.py │ │ └── intervals.py │ ├── examples/ │ │ ├── README.md │ │ ├── __init__.py │ │ ├── dependencies.py │ │ ├── meta_data.md │ │ ├── meta_data.py │ │ └── simple_examples.py │ ├── financing/ │ │ ├── __init__.py │ │ ├── accounts.py │ │ ├── colors.py │ │ ├── identity.py │ │ ├── plotting.py │ │ ├── test_financing.py │ │ ├── test_meta.py │ │ ├── test_status.py │ │ └── validate.py │ ├── products/ │ │ ├── __init__.py │ │ └── germany/ │ │ ├── __init__.py │ │ └── lbs/ │ │ └── __init__.py │ ├── reports/ │ │ ├── __init__.py │ │ ├── excel.py │ │ └── html.py │ ├── tax/ │ │ ├── __init__.py │ │ └── germany/ │ │ └── __init__.py │ ├── templates/ │ │ ├── __init__.py │ │ └── html/ │ │ ├── __init__.py │ │ └── standard/ │ │ ├── __init__.py │ │ ├── account_details.html │ │ ├── index.html │ │ └── render.py │ └── test_general.py ├── requirements.txt ├── setup.py └── unittests.sh
SYMBOL INDEX (249 symbols across 19 files)
FILE: financial_life/calendar_help/__init__.py
class Bank_Date (line 6) | class Bank_Date(datetime):
method is_end_of_month (line 11) | def is_end_of_month(self):
method add_month (line 15) | def add_month(self, months):
method diff_months (line 22) | def diff_months(self, sub2):
function get_days_per_year (line 41) | def get_days_per_year(year):
function add_month (line 47) | def add_month(start_date, months):
function diff_months (line 55) | def diff_months(sub1, sub2):
FILE: financial_life/examples/dependencies.py
function dependencies (line 17) | def dependencies():
FILE: financial_life/examples/meta_data.py
function controller_tax (line 16) | def controller_tax(s):
function example_meta_controller (line 73) | def example_meta_controller(print_it = True):
FILE: financial_life/examples/simple_examples.py
function example1 (line 17) | def example1():
function example2 (line 49) | def example2():
function example3 (line 84) | def example3():
FILE: financial_life/financing/__init__.py
function conv_payment_func (line 57) | def conv_payment_func(x):
function create_stop_criteria (line 65) | def create_stop_criteria(date_stop):
function iter_regular_month (line 82) | def iter_regular_month(regular, date_start = None):
function iter_regular_year (line 128) | def iter_regular_year(regular, date_start = None):
class Status (line 179) | class Status(object):
method __init__ (line 183) | def __init__(self, date, **kwargs):
method __str__ (line 199) | def __str__(self):
method keys (line 205) | def keys(self):
method date (line 210) | def date(self):
method strdate (line 214) | def strdate(self):
method status (line 218) | def status(self):
method meta (line 222) | def meta(self):
method __getitem__ (line 225) | def __getitem__(self, key):
method __getattr__ (line 230) | def __getattr__(self, name):
method get (line 233) | def get(self, attr, default):
class Report (line 239) | class Report(object):
method __init__ (line 246) | def __init__(self, name=None,
method add_semantics (line 262) | def add_semantics(self, key, semantics=None):
method semantics (line 297) | def semantics(self, semantic):
method semantics_of (line 301) | def semantics_of(self, key):
method append (line 308) | def append(self, status = None, date = None, **kwargs):
method size (line 324) | def size(self):
method name (line 329) | def name(self):
method name (line 333) | def name(self, name):
method precision (line 337) | def precision(self):
method get_from_date (line 340) | def get_from_date(self, date, interval):
method monthly (line 351) | def monthly(self):
method yearly (line 354) | def yearly(self):
method create_report (line 357) | def create_report(self, interval='yearly'):
method subset (line 402) | def subset(self, lambda_func):
method table_rows (line 424) | def table_rows(self):
method with_meta (line 434) | def with_meta(self):
method sum_of (line 443) | def sum_of(self, semantic):
method __getitem__ (line 464) | def __getitem__(self, key):
method __getattr__ (line 472) | def __getattr__(self, name):
method __len__ (line 480) | def __len__(self):
method get (line 483) | def get(self, name, num_only = False):
method __str__ (line 492) | def __str__(self):
method __iter__ (line 498) | def __iter__(self):
method as_df (line 502) | def as_df(self):
class Payment_Value (line 507) | class Payment_Value(object):
method __init__ (line 514) | def __init__(self, payment):
method name (line 525) | def name(self):
method __call__ (line 528) | def __call__(self):
class Payment (line 534) | class Payment(object):
method __init__ (line 539) | def __init__(self, from_acc, to_acc, date, name,
method from_acc (line 562) | def from_acc(self):
method to_acc (line 566) | def to_acc(self):
method date (line 570) | def date(self):
method name (line 574) | def name(self):
method kind (line 578) | def kind(self):
method payment (line 582) | def payment(self):
method json (line 586) | def json(self):
method __getitem__ (line 597) | def __getitem__(self, key):
class PaymentList (line 601) | class PaymentList(object):
method __init__ (line 605) | def __init__(self):
method uniques (line 610) | def uniques(self):
method regular (line 614) | def regular(self):
method check_errors_payment (line 617) | def check_errors_payment(self, payment):
method add_unique (line 624) | def add_unique(self, from_acc, to_acc, payment,
method add_regular (line 652) | def add_regular(self, from_acc, to_acc, payment, interval,
method clear_regular (line 697) | def clear_regular(self):
method payment (line 701) | def payment(self, start_date):
class Currency (line 733) | class Currency():
method __init__ (line 736) | def __init__(self, value, digits = 2):
FILE: financial_life/financing/accounts.py
function neg_func (line 41) | def neg_func(func):
function valid_account_type (line 51) | def valid_account_type(*accounts):
class TransferMessage (line 73) | class TransferMessage(object):
method __init__ (line 76) | def __init__(self, code, money, message = ''):
method code (line 86) | def code(self):
method message (line 90) | def message(self):
method money (line 94) | def money(self):
class Simulation (line 98) | class Simulation(object):
method __init__ (line 103) | def __init__(self, *accounts, name = None, date = None, meta = None):
method name (line 146) | def name(self):
method name (line 150) | def name(self, name):
method meta (line 154) | def meta(self):
method accounts (line 158) | def accounts(self):
method current_date (line 162) | def current_date(self):
method report (line 166) | def report(self):
method as_df (line 169) | def as_df(self):
method get_report_jinja (line 174) | def get_report_jinja(self, interval="yearly"):
method get_payments_unique_json (line 199) | def get_payments_unique_json(self):
method get_payments_regular_json (line 204) | def get_payments_regular_json(self):
method get_accounts_json (line 221) | def get_accounts_json(self):
method add_unique (line 233) | def add_unique(self, from_acc, to_acc, payment,
method add_regular (line 245) | def add_regular(self, from_acc, to_acc, payment, interval,
method update_payment_iterators (line 265) | def update_payment_iterators(self):
method add_account (line 279) | def add_account(self, account):
method add_controller (line 289) | def add_controller(self, controller):
method get_payment (line 296) | def get_payment(self, payment):
method make_report (line 309) | def make_report(self, from_acc, to_acc, value, kind,
method make_transfer (line 323) | def make_transfer(self, payment):
method simulate (line 466) | def simulate(self, date_stop = None, delta = None, last_report = True):
method reports (line 522) | def reports(self, interval='yearly'):
method plt_summary (line 526) | def plt_summary(self, interval='yearly'):
method report_sum_of (line 531) | def report_sum_of(self, semantic):
method print_reports (line 535) | def print_reports(self, interval):
class Account (line 543) | class Account(object):
method __init__ (line 556) | def __init__(self, amount, interest, date=None, name = None, meta = {}):
method __str__ (line 583) | def __str__(self):
method date (line 587) | def date(self):
method date_start (line 592) | def date_start(self):
method name (line 596) | def name(self):
method name (line 600) | def name(self, name):
method meta (line 605) | def meta(self):
method account (line 609) | def account(self):
method get_account (line 612) | def get_account(self):
method interest (line 621) | def interest(self):
method payments (line 625) | def payments(self):
method current_date (line 629) | def current_date(self):
method report (line 633) | def report(self):
method as_df (line 636) | def as_df(self):
method report_time (line 639) | def report_time(self, date):
method get_table_json (line 643) | def get_table_json(self, report):
method get_all_tables_json (line 647) | def get_all_tables_json(self):
method get_report_json (line 660) | def get_report_json(self, interval="yearly"):
method payment_input (line 680) | def payment_input(self, account_str, payment, kind, description, meta):
method payment_output (line 686) | def payment_output(self, account_str, payment, kind, description, meta):
method return_money (line 692) | def return_money(self, money):
method set_date (line 697) | def set_date(self, date):
method start_of_day (line 710) | def start_of_day(self):
method end_of_day (line 715) | def end_of_day(self):
class DummyAccount (line 721) | class DummyAccount(Account):
method __init__ (line 727) | def __init__(self, name):
class Bank_Account (line 737) | class Bank_Account(Account):
method __init__ (line 741) | def __init__(self, amount, interest, date = None, name = None, meta = ...
method make_report (line 766) | def make_report(self, interest=0, input=0, output=0,
method exec_interest_time (line 781) | def exec_interest_time(self):
method as_df (line 791) | def as_df(self):
method get_table_json (line 796) | def get_table_json(self, report):
method interest_time (line 823) | def interest_time(self):
method payment_input (line 828) | def payment_input(self, account_str, payment, kind, description, meta):
method payment_output (line 834) | def payment_output(self, account_str, payment, kind, description, meta):
method payment_move (line 840) | def payment_move(self, account_str, payment, kind, description, meta):
method return_money (line 863) | def return_money(self, money):
method start_of_day (line 874) | def start_of_day(self):
method end_of_day (line 879) | def end_of_day(self):
class Loan (line 896) | class Loan(Account):
method __init__ (line 902) | def __init__(self, amount, interest, date = None, name = None, meta = ...
method as_df (line 924) | def as_df(self):
method get_table_json (line 929) | def get_table_json(self, report):
method is_finished (line 951) | def is_finished(self):
method make_report (line 956) | def make_report(self, payment = 0, interest = 0,
method account (line 972) | def account(self):
method get_account (line 975) | def get_account(self):
method exec_interest_time (line 978) | def exec_interest_time(self):
method interest_time (line 988) | def interest_time(self):
method payment_input (line 994) | def payment_input(self, account_str, payment, kind, description, meta):
method payment_output (line 1022) | def payment_output(self, account_str, payment, kind, description, meta):
method return_money (line 1028) | def return_money(self, money):
method start_of_day (line 1039) | def start_of_day(self):
method end_of_day (line 1044) | def end_of_day(self):
class Property (line 1060) | class Property(Account):
method __init__ (line 1067) | def __init__(self, property_value, amount, loan, date = None, name = N...
method make_report (line 1105) | def make_report(self):
method get_table_json (line 1113) | def get_table_json(self, report):
method get_account (line 1125) | def get_account(self):
method payment_input (line 1129) | def payment_input(self, account_str, payment, kind, description, meta):
method payment_output (line 1135) | def payment_output(self, account_str, payment, kind, description, meta):
method return_money (line 1141) | def return_money(self, money):
method end_of_day (line 1146) | def end_of_day(self):
FILE: financial_life/financing/colors.py
function import_colors (line 10) | def import_colors():
FILE: financial_life/financing/identity.py
function id_generator (line 12) | def id_generator(
FILE: financial_life/financing/plotting.py
function bank_account (line 26) | def bank_account(*reports):
function summary (line 34) | def summary(*reports):
function summary_img (line 66) | def summary_img(*reports, target = './', figsize = (10, 5), dpi = 100, p...
function extract_data (line 118) | def extract_data(semantic, *reports, color_theme = C_cold_colors, color_...
function add_labels (line 132) | def add_labels(semantic, *reports, color_theme = C_cold_colors, color_of...
function plot_stack_generic (line 143) | def plot_stack_generic(X, Y, c, semantic, *reports, color_theme = C_cold...
function remove_nones (line 161) | def remove_nones(X):
function add_zeros (line 165) | def add_zeros(X, Y):
function plot_stack_abs (line 179) | def plot_stack_abs(semantic, *reports, color_theme = C_cold_colors, colo...
function plot_stack_mult_abs (line 189) | def plot_stack_mult_abs(semantics, *reports, color_themes, color_offset ...
function plot_stack_cum (line 194) | def plot_stack_cum(semantic, *reports, color_theme = C_cold_colors, colo...
function join_data (line 212) | def join_data(dates_list, data_list):
FILE: financial_life/financing/test_financing.py
class Test_Create_Stop_Criteria (line 13) | class Test_Create_Stop_Criteria(unittest.TestCase):
method test_date (line 15) | def test_date(self):
method test_callable (line 21) | def test_callable(self):
class TestRegular_Month_Payment (line 28) | class TestRegular_Month_Payment(unittest.TestCase):
method setUp (line 31) | def setUp(self):
method test_begin_payment_next_month (line 66) | def test_begin_payment_next_month(self):
method test_begin_payment_next_year (line 74) | def test_begin_payment_next_year(self):
method test_begin_payment_this_month (line 79) | def test_begin_payment_this_month(self):
method test_proper_sequence (line 84) | def test_proper_sequence(self):
method test_last_calender_day (line 92) | def test_last_calender_day(self):
class TestRegular_Year_Payment (line 98) | class TestRegular_Year_Payment(unittest.TestCase):
method setUp (line 101) | def setUp(self):
method test_begin_payment_same_year (line 136) | def test_begin_payment_same_year(self):
method test_begin_payment_next_year (line 142) | def test_begin_payment_next_year(self):
method test_proper_sequence (line 147) | def test_proper_sequence(self):
FILE: financial_life/financing/test_meta.py
class Test (line 13) | class Test(unittest.TestCase):
method setUp (line 16) | def setUp(self):
method tearDown (line 21) | def tearDown(self):
method test_account_and_simulation (line 25) | def test_account_and_simulation(self):
FILE: financial_life/financing/test_status.py
class Test (line 15) | class Test(unittest.TestCase):
method setUp (line 18) | def setUp(self):
method test_InitStatus (line 47) | def test_InitStatus(self):
method test_str (line 58) | def test_str(self):
method test_SimulationReport (line 62) | def test_SimulationReport(self):
FILE: financial_life/financing/validate.py
function parse_datestring (line 29) | def parse_datestring(datestr):
function valid_date (line 38) | def valid_date(date):
function valid_stop_date (line 51) | def valid_stop_date(date):
function valid_name (line 61) | def valid_name(name):
function valid_date_stop (line 66) | def valid_date_stop(date_stop):
function valid_delta (line 72) | def valid_delta(delta):
FILE: financial_life/products/germany/lbs/__init__.py
class Bauspar (line 90) | class Bauspar(Account):
method __init__ (line 93) | def __init__(self, guthaben, bausparsumme, punkte, tarif, date = None,...
method loan (line 153) | def loan(self):
method get_loan (line 156) | def get_loan(self):
method simulate (line 164) | def simulate(self, date_stop = None, delta = None, last_report = True):
method get_credit (line 191) | def get_credit(self):
method loan_track_data (line 208) | def loan_track_data(self, loan_interest, loan_insurance, payed):
method loan_make_report (line 213) | def loan_make_report(self):
method loan_exec_interest_time (line 224) | def loan_exec_interest_time(self):
method loan_phase (line 229) | def loan_phase(self, date_stop = None, delta = None, last_report = True):
method loan_simulate_day (line 268) | def loan_simulate_day(self):
method zwischen_track_data (line 280) | def zwischen_track_data(self, account_interest, loan_interest, payed, ...
method zwischen_make_report (line 287) | def zwischen_make_report(self):
method zwischen_exec_interest_time (line 301) | def zwischen_exec_interest_time(self):
method zwischen_phase (line 306) | def zwischen_phase(self, date_stop = None, delta = None, last_report =...
method zwischen_simulate_day (line 358) | def zwischen_simulate_day(self):
method saving_track_data (line 378) | def saving_track_data(self, interest, payed, entgelt):
method saving_make_report (line 384) | def saving_make_report(self):
method saving_phase (line 396) | def saving_phase(self, date_stop = None, delta = None, last_report = T...
method saving_simulate_day (line 431) | def saving_simulate_day(self):
FILE: financial_life/reports/excel.py
function report (line 15) | def report(simulation, filename='report.xls'):
FILE: financial_life/reports/html.py
function report (line 20) | def report(simulation, style = 'standard', output_dir = 'report'):
FILE: financial_life/tax/germany/__init__.py
function tax_to_pay (line 3) | def tax_to_pay(year, *args, **kwargs):
function tax_to_pay_2016 (line 10) | def tax_to_pay_2016(tax_relevant_money, splitting = False):
FILE: financial_life/templates/html/standard/render.py
function render (line 21) | def render(simulation, output_dir = 'report'):
function render_accounts (line 62) | def render_accounts(simulation, template_folder, output_dir = 'report', ...
FILE: financial_life/test_general.py
class Test (line 16) | class Test(unittest.TestCase):
method setUp (line 19) | def setUp(self):
method tearDown (line 69) | def tearDown(self):
method testGeneral (line 73) | def testGeneral(self):
Condensed preview — 48 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (200K chars).
[
{
"path": ".github/workflows/sonarcloud.yml",
"chars": 3374,
"preview": "# This workflow uses actions that are not certified by GitHub.\n# They are provided by a third-party and are governed by\n"
},
{
"path": ".gitignore",
"chars": 1072,
"preview": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packagi"
},
{
"path": "CHANGELOG.md",
"chars": 446,
"preview": "# 0.9.4 (31.05.2017\n* added support for export to Excel\n\n# 0.9.3 (13.01.2017)\n* changed some bugs for rendering HTML con"
},
{
"path": "LICENSE",
"chars": 11357,
"preview": " Apache License\n Version 2.0, January 2004\n "
},
{
"path": "MANIFEST",
"chars": 1011,
"preview": "# file GENERATED by distutils, do NOT edit\nsetup.py\nfinancial_life/__init__.py\nfinancial_life/test_general.py\nfinancial_"
},
{
"path": "README.md",
"chars": 6905,
"preview": "# financial-life\nA framework for analysing financial products in personalized contexts\n\n<table>\n<tr>\n\t<td>\n\tLatest Relea"
},
{
"path": "docs/01_first_simulation.md",
"chars": 7614,
"preview": "# The first simulation\n\nIn this chapter, we are going to introduce the basic concepts of creating a simulation in order "
},
{
"path": "docs/02_using_callables_for_dynamic_changes.md",
"chars": 2311,
"preview": "# Using callables for dynamic changes\n\nSometimes, parameters like the amount of money to be transfered or the stop date "
},
{
"path": "docs/03_dependencies_between_accounts.md",
"chars": 2579,
"preview": "# Dependencies between accounts\n\nModeling loan accounts can be helpful to calculate payment durations and interests cost"
},
{
"path": "docs/README.md",
"chars": 458,
"preview": "# Documentation\n\nThis documentations aims to introduce the user into the concepts of financial_life to start developing "
},
{
"path": "financial_life/README.md",
"chars": 257,
"preview": "# Enter the code\n\nHere, you are about to enter the code part of this repository. You might want to check out the [exampl"
},
{
"path": "financial_life/__init__.py",
"chars": 70,
"preview": "'''\nCreated on 03.12.2016\n\n@author: martin\n'''\n\n__version__ = '0.9.4'\n"
},
{
"path": "financial_life/calendar_help/__init__.py",
"chars": 2636,
"preview": "from calendar import monthrange\nfrom datetime import date\nfrom datetime import timedelta\nfrom datetime import datetime\n\n"
},
{
"path": "financial_life/constants/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "financial_life/constants/intervals.py",
"chars": 167,
"preview": "'''\nCreated on 20.01.2017\n\nThis module lists all intervals, that can be used through out fl\n\n@author: martin\n'''\n\ndaily "
},
{
"path": "financial_life/examples/README.md",
"chars": 1147,
"preview": "# Examples\n\nIn this folder, you find a list of examples that will help you getting started.\n\n### simple_examples.py\n\n[si"
},
{
"path": "financial_life/examples/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "financial_life/examples/dependencies.py",
"chars": 800,
"preview": "'''\nCreated on 14.08.2016\n\n@author: martin\n'''\n# standard libraries\nfrom datetime import timedelta, datetime\nimport os\n\n"
},
{
"path": "financial_life/examples/meta_data.md",
"chars": 4023,
"preview": "# Meta-Fields and Controller functions\n\nYou can attach meta-data to any account class and any payment. These meta-data c"
},
{
"path": "financial_life/examples/meta_data.py",
"chars": 5335,
"preview": "'''\nCreated on 21.12.2016\n\n@author: martin\n'''\n# standard libraries\nfrom datetime import timedelta\n\n# third-party librar"
},
{
"path": "financial_life/examples/simple_examples.py",
"chars": 5149,
"preview": "'''\nCreated on 14.08.2016\n\n@author: martin\n'''\n# standard libraries\nfrom datetime import timedelta, datetime\nimport os\n\n"
},
{
"path": "financial_life/financing/__init__.py",
"chars": 27369,
"preview": "\"\"\" some basic classes for creating financing classes and\nreports \"\"\"\n\n# standard libraries\nfrom datetime import datetim"
},
{
"path": "financial_life/financing/accounts.py",
"chars": 48170,
"preview": "'''\nCreated on 24.03.2016\n\n@author: martin\n'''\n\n# standard libraries\nfrom datetime import datetime, timedelta\nfrom colle"
},
{
"path": "financial_life/financing/colors.py",
"chars": 3181,
"preview": "'''\nCreated on 30.03.2016\n\n@author: martin\n'''\n\nimport xml.etree.ElementTree as ET\nimport re\n\ndef import_colors():\n g"
},
{
"path": "financial_life/financing/identity.py",
"chars": 479,
"preview": "'''\nCreated on 18.11.2016\n\nSome basic classes for generating and managing identities\n\n@author: martin\n'''\n# standard lib"
},
{
"path": "financial_life/financing/plotting.py",
"chars": 8778,
"preview": "'''\nCreated on 30.03.2016\n\n@author: martin\n'''\n#standard libraries\nfrom datetime import timedelta\n\n# custom libraries\nfr"
},
{
"path": "financial_life/financing/test_financing.py",
"chars": 6215,
"preview": "# -*- coding: utf-8 -*-\n\"\"\"\nCreated on Thu Jun 23 21:40:47 2016\n\n@author: martin\n\"\"\"\n\nimport unittest\nimport financial_l"
},
{
"path": "financial_life/financing/test_meta.py",
"chars": 1327,
"preview": "'''\nCreated on 04.01.2017\n\n@author: martin\n'''\n# standard libraries\nfrom datetime import datetime, timedelta\nimport unit"
},
{
"path": "financial_life/financing/test_status.py",
"chars": 2698,
"preview": "'''\nCreated on 15.12.2016\n\n@author: martin\n'''\n# standard libraries\nfrom datetime import datetime, timedelta\nimport unit"
},
{
"path": "financial_life/financing/validate.py",
"chars": 2257,
"preview": "# -*- coding: utf-8 -*-\n\"\"\"\nCreated on Mon Jun 27 21:27:23 2016\n\nCollection of validation messages\n\n@author: martin\n\"\"\"\n"
},
{
"path": "financial_life/products/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "financial_life/products/germany/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "financial_life/products/germany/lbs/__init__.py",
"chars": 18454,
"preview": "''' \n\nThis is a reconstruction of the LBS Bauspar Tarife \n\nWARNING: This is a manual attempt to simualate some of the LB"
},
{
"path": "financial_life/reports/__init__.py",
"chars": 74,
"preview": "import platform\n\nsl = '/'\nif platform.system() == 'Windows':\n sl = '\\\\'"
},
{
"path": "financial_life/reports/excel.py",
"chars": 604,
"preview": "'''\nCreated on 29.05.2017\n\n@author: martin\n'''\n# standard libraries\nimport os\n\n# third-party libraries\nimport pandas as "
},
{
"path": "financial_life/reports/html.py",
"chars": 1037,
"preview": "'''\nCreated on 03.10.2016\n\n@author: martin\n'''\n# standard libraries\nimport os\nimport imp\nimport platform\n\n# third-party "
},
{
"path": "financial_life/tax/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "financial_life/tax/germany/__init__.py",
"chars": 1201,
"preview": "\"\"\" help functions for german tax declaration \"\"\"\n\ndef tax_to_pay(year, *args, **kwargs):\n \"\"\" generic functions to c"
},
{
"path": "financial_life/templates/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "financial_life/templates/html/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "financial_life/templates/html/standard/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "financial_life/templates/html/standard/account_details.html",
"chars": 1298,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <title>Account Detail: {{account_name}}</title>\n <link rel=\"stylesheet\" h"
},
{
"path": "financial_life/templates/html/standard/index.html",
"chars": 2347,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <title>Finanzuebersicht</title>\n <link rel=\"stylesheet\" href=\"https://max"
},
{
"path": "financial_life/templates/html/standard/render.py",
"chars": 3446,
"preview": "'''\nCreated on 03.10.2016\n\n@author: martin\n'''\n\n# standard libraries\nimport os\nfrom datetime import datetime\n\n# third-pa"
},
{
"path": "financial_life/test_general.py",
"chars": 3011,
"preview": "'''\nCreated on 12.12.2016\n\n@author: martin\n'''\n# standard libraries\nfrom datetime import timedelta, datetime\nimport os\ni"
},
{
"path": "requirements.txt",
"chars": 81,
"preview": "Jinja2>=2.7.2\nmatplotlib>=1.5.3\nnumpy>=1.11.2\npandas>=0.25.3\ntabulate>=0.7.5\nxlwt"
},
{
"path": "setup.py",
"chars": 1361,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\"\"\"\nCreated on Tue Nov 8 21:26:07 2016\n\n@author: martin\n\"\"\"\ntry:\n from"
},
{
"path": "unittests.sh",
"chars": 82,
"preview": "#!/bin/bash\n\n# run all unittests in this package\n python3 -m unittest discover -v\n"
}
]
About this extraction
This page contains the full source code of the MartinPyka/financial_life GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 48 files (185.7 KB), approximately 44.3k tokens, and a symbol index with 249 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.