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()
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
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 USD' % 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) #print("Next Pay: ", self._next_pay) except StopIteration: print("No payments! 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) print("Next pay: ", self._next_pay) except StopIteration: print("No payments! 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) 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 #print("Type of _next_pay: ",type(self._next_pay), "Len: ", len(self._next_pay)) pp = pprint.PrettyPrinter(indent=4) #pp.pprint(self._next_pay) if isinstance(self._next_pay, tuple): #print('Tuple! ', self._next_pay) if len(self._next_pay) > 0 and 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) if isinstance(self._next_pay, dict): #print('Dict! ', self._next_pay) if len(self._next_pay) > 0 and self._next_pay['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(' ')