示例#1
0
    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
示例#2
0
    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()
示例#3
0
    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 = []
示例#4
0
    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
        }
示例#5
0
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
示例#6
0
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
示例#7
0
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()
示例#8
0
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(' ')