App-InvestSim

 view release on metacpan or  search on metacpan

lib/App/InvestSim/Finance.pm  view on Meta::CPAN

# Financial computations for the simulation, based on all the values set in the
# UI. The fiscal rules implemented are believed to be correct as of 2020-01,
# although not every details of the rules is implemented (particularly, tax
# deduction for losses is not yet there).

package App::InvestSim::Finance;

use 5.022;
use strict;
use warnings;

use App::InvestSim::Config ':modes';
use App::InvestSim::LiteGUI ':all';
use App::InvestSim::Values ':all';
use List::Util qw(min);

sub pinel_rent_cap {
  return '+inf' unless $values{pinel_duration};
  my $rent_cap_per_sqm;
  if ($values{pinel_zone} == 0) {
    $rent_cap_per_sqm = 17.17;
  } elsif ($values{pinel_zone} == 1) {
    $rent_cap_per_sqm = 12.75;
  } elsif ($values{pinel_zone} == 2) {
    $rent_cap_per_sqm = 10.28;
  } elsif ($values{pinel_zone} == 3) {
    $rent_cap_per_sqm = 8.93;
  } else {
    message_and_die(
       "Erreur interne: valeur de zone Pinel invalide", $values{pinel_zone});
  }
  return $rent_cap_per_sqm * (0.7 + 19 / $values{surface}) * $values{surface};
}

sub pinel_investment_value {
  if ($values{pinel_duration}) {
    return min(300000, $values{invested}, $values{surface} * 5500);
  } else {
    return $values{invested};
  }
}

sub total_rent {
  my $net_rent = $values{base_rent} * 12 * (1 - $values{rent_charges} / 100);
  my $rent_duration = $values{duration} - $values{rent_delay};
  if ($values{rent_increase} == 0) {
    return $net_rent * $rent_duration;
  }
  $net_rent *= (1 + $values{rent_increase} / 100) ** $values{rent_delay};
  return $net_rent * ((1 + $values{rent_increase} / 100) ** $rent_duration - 1) / ($values{rent_increase} / 100);
}

sub notary_fees {
  return $values{invested} * $values{notary_fees} / 100;
}

sub total_invested {
  return $values{invested} + notary_fees();
}

# Takes a yearly loan interest rate as a percentage value (e.g. 2.0 for 2%) and
# returns the monthly interest rate.
sub monthly_rate {
  my ($yearly_rate) = @_;
  # Or should it be: (1 + yearly_rate) ** (1/12) - 1 ?
  # That calculation is more correct but it seems that real loans are using the
  # approximate value.
  my $ir = $yearly_rate / 100 / 12;  
}

# Payment par term for a loan.
# Args:
# - ir => yearly interest rate.
# - np => number of payments.
# - pv => present value.
sub monthly_payment {
  my (%args) = @_;
  # Or should it be (1 + ir / 100) ** (1/12) - 1 which is the correct value (but
  # it seems that real loan uses the approximate one)?
  my $ir = monthly_rate($args{ir});
  my $a = (1 + $ir) ** (0 - $args{np});
  my $denominator = 1 - $a;
  my $numerator   = $args{pv} * $ir;
  my $result = eval { $numerator / $denominator };
  return $@ ? 0 : $result;
}

# In addition to the arguments, all the content of the
# %App::InvestSim::Values::values hash is available to the computation.
sub calculate {
  my ($loan_amount, $loan_duration, $loan_rate) = @_;

  # The idea of the simulation is that you start with a sum of cash equal to the
  # value of the good to buy. You buy that good and also take a loan. So the
  # available cash at the beginning of the simulation is the amoun of the loan.
  my $starting_cash = $loan_amount;
  # 'application_fees' is the "frais de dossier".
  # 'mortgage_fees' is the "Frais d'hypothèque".
  my $loan_fee = $loan_amount > 0 ? $values{application_fees} + $loan_amount * $values{mortgage_fees} / 100 : 0;
  my $initial_cost = $loan_fee + notary_fees();
  # Yearly cost of the loan insurance.
  my $yearly_loan_insurance = $loan_amount * $values{loan_insurance} / 100;
  # The monthly gross rent. The rent is increased each year, even during the
  # initial rent delay period, if any.
  my $current_gross_rent = $values{base_rent};
  # Monthly loan payment during the loan duration.
  my $core_loan_payment = monthly_payment(ir => $loan_rate, np => $loan_duration * 12, pv => $loan_amount);
  # Amount that is still due on the loan (starting with the full loan).
  my $remaining_loan = $loan_amount;
  # The total cost of the loan (will be summed in the computation below).
  my $total_loan_cost = $loan_fee;
  # Duration of the loan, including the startup delay.
  my $total_loan_duration = $loan_duration + $values{loan_delay};
  # Value of the investment that can be used for the "Loi Pinel" tax deduction.
  my $pinel_value = min(300000, $values{invested}, $values{surface} * 5500);
  
  # Cash balance for the duration of the loan and afterward, per year.
  my ($mean_balance_loan_duration, $mean_balance_afterward) = (0, 0);
  
  # Total cash flow
  my ($total_gained, $total_spent) = ($values{invested}, $values{invested} - $loan_amount + $initial_cost);



( run in 1.147 second using v1.01-cache-2.11-cpan-39bf76dae61 )