A loan portfolio simulation tool that takes total balance, weighted average lifetime, apr, cost of risk, cost of funds, opex, acquisition cost and generates returns and P&L for differing scenarios
To upload files, please first save the app
import streamlit as st
import pandas as pd
import numpy as np
import plotly.graph_objects as go
from datetime import datetime
from dateutil.relativedelta import relativedelta
def simulate_portfolio(total_balance, avg_lifetime_months, apr, cost_of_risk,
cost_of_funds, opex_ratio, acquisition_cost_ratio):
# Monthly rates
monthly_apr = apr / 12
monthly_cor = cost_of_risk / 12
monthly_cof = cost_of_funds / 12
# Create monthly schedule
months = range(int(avg_lifetime_months * 1.5)) # Extend beyond avg lifetime
# Initialize arrays
balance = []
interest_income = []
credit_losses = []
funding_costs = []
operating_costs = []
# Initial balance and acquisition cost
current_balance = total_balance
acquisition_cost = total_balance * acquisition_cost_ratio
for month in months:
# Calculate monthly changes
repayment = current_balance / (avg_lifetime_months - month) if month < avg_lifetime_months else current_balance
interest = current_balance * monthly_apr
losses = current_balance * monthly_cor
funding = current_balance * monthly_cof
opex = current_balance * (opex_ratio / 12)
# Store results
balance.append(current_balance)
interest_income.append(interest)
credit_losses.append(losses)
funding_costs.append(funding)
operating_costs.append(opex)
# Update balance
current_balance = max(0, current_balance - repayment)
if current_balance == 0:
break
# Create results DataFrame
results = pd.DataFrame({
'Month': range(len(balance)),
'Balance': balance,
'Interest Income': interest_income,
'Credit Losses': credit_losses,
'Funding Costs': funding_costs,
'Operating Costs': operating_costs
})
# Calculate cumulative P&L
results['Monthly P&L'] = (results['Interest Income'] -
results['Credit Losses'] -
results['Funding Costs'] -
results['Operating Costs'])
results['Cumulative P&L'] = results['Monthly P&L'].cumsum() - acquisition_cost
return results
st.title('Loan Portfolio Simulator')
st.sidebar.header('Portfolio Parameters')
total_balance = st.sidebar.number_input('Total Portfolio Balance',
min_value=1000000,
value=10000000,
step=1000000,
format='%d')
avg_lifetime = st.sidebar.number_input('Average Lifetime (months)',
min_value=1,
value=24,
step=1)
apr = st.sidebar.number_input('Annual Percentage Rate (APR)',
min_value=0.0,
value=0.15,
format='%.3f')
cost_of_risk = st.sidebar.number_input('Annual Cost of Risk',
min_value=0.0,
value=0.03,
format='%.3f')
cost_of_funds = st.sidebar.number_input('Annual Cost of Funds',
min_value=0.0,
value=0.04,
format='%.3f')
opex_ratio = st.sidebar.number_input('Annual Operating Expense Ratio',
min_value=0.0,
value=0.02,
format='%.3f')
acquisition_cost_ratio = st.sidebar.number_input('Acquisition Cost Ratio',
min_value=0.0,
value=0.01,
format='%.3f')
# Run simulation
results = simulate_portfolio(
total_balance=total_balance,
avg_lifetime_months=avg_lifetime,
apr=apr,
cost_of_risk=cost_of_risk,
cost_of_funds=cost_of_funds,
opex_ratio=opex_ratio,
acquisition_cost_ratio=acquisition_cost_ratio
)
# Display key metrics
st.header('Key Portfolio Metrics')
col1, col2, col3 = st.columns(3)
with col1:
st.metric('Total Interest Income',
f'${results["Interest Income"].sum():,.0f}')
with col2:
st.metric('Final Cumulative P&L',
f'${results["Cumulative P&L"].iloc[-1]:,.0f}')
with col3:
roi = (results["Cumulative P&L"].iloc[-1] / total_balance) * 100
st.metric('Return on Investment',
f'{roi:.1f}%')
# Create charts
st.header('Portfolio Performance Charts')
# Balance Evolution
fig_balance = go.Figure()
fig_balance.add_trace(go.Scatter(x=results['Month'],
y=results['Balance'],
name='Portfolio Balance',
fill='tozeroy'))
fig_balance.update_layout(title='Portfolio Balance Evolution',
xaxis_title='Month',
yaxis_title='Balance ($)')
st.plotly_chart(fig_balance, use_container_width=True)
# P&L Components
fig_pl = go.Figure()
fig_pl.add_trace(go.Scatter(x=results['Month'],
y=results['Interest Income'],
name='Interest Income',
stackgroup='positive'))
fig_pl.add_trace(go.Scatter(x=results['Month'],
y=-results['Credit Losses'],
name='Credit Losses',
stackgroup='negative'))
fig_pl.add_trace(go.Scatter(x=results['Month'],
y=-results['Funding Costs'],
name='Funding Costs',
stackgroup='negative'))
fig_pl.add_trace(go.Scatter(x=results['Month'],
y=-results['Operating Costs'],
name='Operating Costs',
stackgroup='negative'))
fig_pl.update_layout(title='Monthly P&L Components',
xaxis_title='Month',
yaxis_title='Amount ($)')
st.plotly_chart(fig_pl, use_container_width=True)
# Cumulative P&L
fig_cumulative = go.Figure()
fig_cumulative.add_trace(go.Scatter(x=results['Month'],
y=results['Cumulative P&L'],
name='Cumulative P&L',
fill='tozeroy'))
fig_cumulative.update_layout(title='Cumulative P&L',
xaxis_title='Month',
yaxis_title='Cumulative P&L ($)')
st.plotly_chart(fig_cumulative, use_container_width=True)
# Display detailed results
st.header('Detailed Results')
st.dataframe(results.style.format({
'Balance': '${:,.0f}',
'Interest Income': '${:,.0f}',
'Credit Losses': '${:,.0f}',
'Funding Costs': '${:,.0f}',
'Operating Costs': '${:,.0f}',
'Monthly P&L': '${:,.0f}',
'Cumulative P&L': '${:,.0f}'
}))
Hi! I can help you with any questions about Streamlit and Python. What would you like to know?