Full Code of MartinPyka/financial_life for AI

master 24c4ce0a026f cached
48 files
185.7 KB
44.3k tokens
249 symbols
1 requests
Download .txt
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
Download .txt
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
Download .txt
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.

Copied to clipboard!