SECTION 1: LEARNING OBJECTIVES

By the end of this lesson, you will be able to:

  • Define Digital Twins and understand their application in finance – simulating customers, portfolios, markets, and entire banking ecosystems.

  • Distinguish between different types of digital twins – product twins, process twins, system twins, and customer twins.

  • Understand the architecture of a financial digital twin – data ingestion, simulation engine, analytics, and visualisation.

  • Apply agent-based modelling to simulate customer behaviour, market dynamics, and portfolio risk.

  • Use digital twins for stress testing, scenario analysis, and business strategy optimisation.

  • Build a simple digital twin for a bank’s deposit portfolio using Python.

  • Understand the challenges – data requirements, model complexity, computational cost, and validation.

  • Identify the business value – improved decision-making, reduced risk, and enhanced customer experience.


SECTION 2: WHAT IS A DIGITAL TWIN?

Digital Twin is a virtual representation of a physical object, system, or process that mirrors its real-world counterpart in real-time or near-real-time.

Components of a Digital Twin:

  1. Physical Entity: The real-world system (e.g., a bank, a customer base, a portfolio).

  2. Data: Real-time and historical data from sensors, transactions, and external sources.

  3. Model: A simulation or analytical model that replicates the behaviour of the physical entity.

  4. Analytics: Algorithms that extract insights, predict outcomes, and optimise decisions.

  5. Visualisation: Interactive dashboards and 3D representations.

Types of Digital Twins:

 
 
Type Description Financial Example
Product Twin Simulates a specific product. Loan product performance simulation.
Process Twin Simulates a business process. Loan origination process simulation.
System Twin Simulates an entire system. The entire banking system (deposits, loans, investments).
Customer Twin Simulates a customer’s behaviour. Customer lifecycle and churn prediction.
Market Twin Simulates financial markets. Market dynamics, price movements, volatility.
Portfolio Twin Simulates a portfolio of assets. Portfolio risk and return simulation.

SECTION 3: DIGITAL TWINS IN FINANCE – USE CASES

3.1 Customer Digital Twins
 
 
Application Description Benefit
Customer Journey Simulation Model how customers interact with the bank. Optimise customer experience.
Churn Prediction Simulate customer behaviour to predict churn. Proactive retention strategies.
Personalised Offers Simulate customer response to different offers. Increase conversion rates.
Lifecycle Management Model customer value over time. Optimise product recommendations.
3.2 Portfolio Digital Twins
 
 
Application Description Benefit
Risk Simulation Simulate portfolio performance under stress. Better risk management.
Asset Allocation Test different allocation strategies. Optimise returns for a given risk.
Liquidity Management Simulate cash flows and liquidity needs. Optimise balance sheet management.
Performance Attribution Identify sources of portfolio returns. Better investment decisions.
3.3 Market Digital Twins
 
 
Application Description Benefit
Market Microstructure Simulate order book dynamics. Better trading strategies.
Price Discovery Simulate price formation mechanisms. Improved market predictions.
Systemic Risk Simulate interbank contagion. Better systemic risk management.
3.4 Operational Digital Twins
 
 
Application Description Benefit
Branch Operations Simulate branch traffic, staffing, and service. Optimise branch operations.
Fraud Detection Simulate fraud patterns. Better fraud detection systems.
Process Optimisation Simulate loan origination, approval, and servicing. Reduce processing time and costs.

SECTION 4: AGENT-BASED MODELLING FOR DIGITAL TWINS

Agent-Based Modelling (ABM) is a common technique for building digital twins of financial systems.

Key Concepts:

  • Agents: Autonomous entities with behaviours and interactions (customers, banks, regulators).

  • Environment: The context in which agents operate (market conditions, economic factors).

  • Rules: The logic governing agent behaviour.

  • Emergence: System-level outcomes emerge from individual agent interactions.

Advantages of ABM:

  • Captures heterogeneity (different customer types).

  • Models complex interactions and feedback loops.

  • Enables “what-if” scenario analysis.

  • Can incorporate machine learning for agent behaviour.


SECTION 5: IMPLEMENTATION IN PYTHON – DIGITAL TWIN FOR A BANK’S DEPOSIT PORTFOLIO

We’ll build a simplified digital twin for a bank’s deposit portfolio, simulating customer behaviour, interest rates, and deposit flows.

python
# ===================================================================
# MODULE 7, LESSON 4: DIGITAL TWINS IN FINANCE
# ===================================================================

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from dataclasses import dataclass
from typing import List, Dict
from scipy.stats import norm, lognorm, weibull_min
import warnings
warnings.filterwarnings('ignore')

# Set style
sns.set_style("whitegrid")
np.random.seed(42)

print("="*70)
print("DIGITAL TWINS IN FINANCE – DEPOSIT PORTFOLIO SIMULATION")
print("="*70)

# ----------------------------------------------------------------
# PART A: DEFINE AGENT CLASSES
# ----------------------------------------------------------------

@dataclass
class Customer:
    """Represents a customer in the digital twin."""
    id: int
    age: float
    income: float
    balance: float
    deposit_elasticity: float  # Sensitivity to interest rates
    withdrawal_probability: float  # Base probability of withdrawal
    churn_probability: float  # Probability of leaving the bank
    
    def update_balance(self, interest_rate, market_rate, time_step=1):
        """
        Update customer balance based on interest rates and behaviour.
        """
        # Interest earned
        interest = self.balance * interest_rate * time_step
        
        # Withdrawal based on rate differential (higher rates -> lower withdrawals)
        rate_diff = market_rate - interest_rate
        withdrawal_effect = 1 - self.deposit_elasticity * max(0, rate_diff)
        withdrawal_effect = max(0.5, min(1.5, withdrawal_effect))
        
        # Base withdrawal
        withdrawal_prob = self.withdrawal_probability * withdrawal_effect
        withdrawal = np.random.binomial(1, withdrawal_prob * time_step)
        
        if withdrawal:
            withdrawal_amount = self.balance * np.random.uniform(0.05, 0.3)
            self.balance -= withdrawal_amount
        
        # New deposits (savings from income)
        savings_rate = 0.1 * (1 + rate_diff * 0.5)
        savings_rate = max(0.02, min(0.2, savings_rate))
        new_deposit = self.income * savings_rate * time_step * np.random.uniform(0.5, 1.5)
        self.balance += new_deposit
        
        # Churn
        if np.random.random() < self.churn_probability * time_step:
            self.balance = 0
            return False  # Customer churned
        
        return True  # Customer active

class Bank:
    """Represents the bank in the digital twin."""
    def __init__(self, name):
        self.name = name
        self.customers: List[Customer] = []
        self.deposit_rate = 0.02  # 2% annual deposit rate
        self.market_rate = 0.05  # 5% market rate
        
    def add_customer(self, customer: Customer):
        self.customers.append(customer)
    
    def set_rates(self, deposit_rate: float, market_rate: float):
        self.deposit_rate = deposit_rate
        self.market_rate = market_rate
    
    def simulate_month(self):
        """
        Simulate one month of activity.
        """
        active_customers = []
        total_balance = 0
        total_withdrawals = 0
        total_deposits = 0
        
        for customer in self.customers:
            if customer.balance > 0:
                active = customer.update_balance(
                    self.deposit_rate / 12,  # Monthly rate
                    self.market_rate / 12,
                    time_step=1
                )
                if active:
                    active_customers.append(customer)
                    total_balance += customer.balance
        
        self.customers = active_customers
        return {
            'total_balance': total_balance,
            'num_customers': len(active_customers),
            'avg_balance': total_balance / len(active_customers) if active_customers else 0
        }

# ----------------------------------------------------------------
# PART B: GENERATE SYNTHETIC CUSTOMER BASE
# ----------------------------------------------------------------

print("\n" + "-"*60)
print("PART B: Generating Synthetic Customer Base")
print("-"*60)

def generate_customer_base(n_customers=5000):
    """Generate a diverse customer base."""
    customers = []
    for i in range(n_customers):
        age = np.random.normal(45, 15).clip(18, 80)
        income = np.random.gamma(5, 15).clip(10, 150) * 1000  # Annual income
        balance = np.random.gamma(3, 50).clip(0, 50000)  # Initial balance
        
        # Different segments
        segment = np.random.choice(['mass', 'affluent', 'premium'], p=[0.6, 0.3, 0.1])
        if segment == 'affluent':
            income *= 1.5
            balance *= 2
        elif segment == 'premium':
            income *= 2.5
            balance *= 4
        
        # Behavioural parameters
        deposit_elasticity = np.random.beta(2, 5).clip(0.1, 0.8)
        withdrawal_probability = np.random.beta(2, 8).clip(0.01, 0.2)
        churn_probability = np.random.beta(1, 10).clip(0.001, 0.05)
        
        customer = Customer(
            id=i,
            age=age,
            income=income,
            balance=balance,
            deposit_elasticity=deposit_elasticity,
            withdrawal_probability=withdrawal_probability,
            churn_probability=churn_probability
        )
        customers.append(customer)
    
    return customers

# Generate customer base
customers = generate_customer_base(5000)
print(f"Generated {len(customers)} customers.")

# Customer statistics
balances = [c.balance for c in customers]
ages = [c.age for c in customers]
incomes = [c.income for c in customers]

print(f"Average balance: ${np.mean(balances):,.2f}")
print(f"Median balance: ${np.median(balances):,.2f}")
print(f"Average age: {np.mean(ages):.1f}")
print(f"Average income: ${np.mean(incomes):,.2f}")

# ----------------------------------------------------------------
# PART C: INITIALISE AND RUN DIGITAL TWIN
# ----------------------------------------------------------------

print("\n" + "-"*60)
print("PART C: Running the Digital Twin")
print("-"*60)

# Create bank
bank = Bank("Digital Bank")
for customer in customers:
    bank.add_customer(customer)

# Initial state
initial_balance = sum(c.balance for c in bank.customers)
initial_customers = len(bank.customers)
print(f"Initial total deposits: ${initial_balance:,.2f}")
print(f"Initial customers: {initial_customers}")

# Simulate 36 months (3 years)
n_months = 36
history = []

# Different rate scenarios
scenarios = {
    'Baseline': {'deposit_rate': 0.02, 'market_rate': 0.05},
    'High Rate': {'deposit_rate': 0.04, 'market_rate': 0.06},
    'Low Rate': {'deposit_rate': 0.01, 'market_rate': 0.03},
    'Rising Rate': {'deposit_rate': 0.02, 'market_rate': 0.05},
}

scenario_results = {}

for scenario_name, rates in scenarios.items():
    print(f"\nRunning scenario: {scenario_name}")
    
    # Reset bank for each scenario
    bank2 = Bank("Digital Bank")
    for customer in customers:
        # Reset customer balances
        customer.balance = balances[customers.index(customer)]
        bank2.add_customer(customer)
    
    bank2.set_rates(rates['deposit_rate'], rates['market_rate'])
    
    history_scenario = []
    for month in range(n_months):
        result = bank2.simulate_month()
        result['month'] = month
        history_scenario.append(result)
    
    scenario_results[scenario_name] = pd.DataFrame(history_scenario)

# ----------------------------------------------------------------
# PART D: ANALYSE AND VISUALISE RESULTS
# ----------------------------------------------------------------

print("\n" + "-"*60)
print("PART D: Digital Twin Results")
print("-"*60)

fig, axes = plt.subplots(2, 2, figsize=(15, 10))

for idx, (scenario_name, df_scenario) in enumerate(scenario_results.items()):
    ax = axes[idx // 2, idx % 2]
    
    # Plot total balance over time
    ax.plot(df_scenario['month'], df_scenario['total_balance'] / 1e6, 'b-', linewidth=2)
    ax.set_xlabel('Month')
    ax.set_ylabel('Total Deposits ($M)')
    ax.set_title(f'Scenario: {scenario_name}')
    ax.grid(True, alpha=0.3)
    
    # Add customer count as secondary axis
    ax2 = ax.twinx()
    ax2.plot(df_scenario['month'], df_scenario['num_customers'], 'r--', linewidth=1)
    ax2.set_ylabel('Number of Customers', color='red')
    ax2.tick_params(axis='y', labelcolor='red')
    
    # Final stats
    final_balance = df_scenario['total_balance'].iloc[-1]
    final_customers = df_scenario['num_customers'].iloc[-1]
    ax.text(0.95, 0.05, f'Final: ${final_balance/1e6:.1f}M\nCustomers: {final_customers}', 
            transform=ax.transAxes, ha='right', va='bottom',
            bbox=dict(boxstyle='round', facecolor='white', alpha=0.8))

plt.tight_layout()
plt.savefig('digital_twin_results.png', dpi=300)
plt.show()

# ----------------------------------------------------------------
# PART E: COMPARISON OF SCENARIOS
# ----------------------------------------------------------------

print("\n" + "-"*60)
print("PART E: Scenario Comparison")
print("-"*60)

comparison_data = []
for scenario_name, df_scenario in scenario_results.items():
    final_balance = df_scenario['total_balance'].iloc[-1]
    final_customers = df_scenario['num_customers'].iloc[-1]
    growth = (final_balance - initial_balance) / initial_balance * 100
    
    comparison_data.append({
        'Scenario': scenario_name,
        'Final Deposits ($M)': final_balance / 1e6,
        'Final Customers': final_customers,
        'Growth (%)': growth,
        'Rate Differential (%)': (scenarios[scenario_name]['market_rate'] - 
                                  scenarios[scenario_name]['deposit_rate']) * 100
    })

comparison_df = pd.DataFrame(comparison_data)
print(comparison_df.to_string(index=False))

# Visualise comparison
fig, axes = plt.subplots(1, 2, figsize=(14, 5))

ax = axes[0]
ax.bar(comparison_df['Scenario'], comparison_df['Growth (%)'], color='blue', alpha=0.7)
ax.set_xlabel('Scenario')
ax.set_ylabel('Deposit Growth (%)')
ax.set_title('Portfolio Growth by Scenario')
ax.axhline(0, color='black', linestyle='-', alpha=0.3)
ax.grid(True, alpha=0.3)

ax = axes[1]
ax.bar(comparison_df['Scenario'], comparison_df['Final Customers'], color='green', alpha=0.7)
ax.set_xlabel('Scenario')
ax.set_ylabel('Final Customer Count')
ax.set_title('Customer Retention by Scenario')
ax.grid(True, alpha=0.3)

plt.tight_layout()
plt.savefig('scenario_comparison.png', dpi=300)
plt.show()

# ----------------------------------------------------------------
# PART F: ADVANCED – AGENT-BASED MARKET SIMULATION
# ----------------------------------------------------------------

print("\n" + "-"*60)
print("PART F: Agent-Based Market Simulation (Extended)")
print("-"*60)

class MarketAgent:
    """Represents a market participant (trader/investor)."""
    def __init__(self, id, initial_cash, risk_tolerance, strategy):
        self.id = id
        self.cash = initial_cash
        self.holdings = 0
        self.risk_tolerance = risk_tolerance
        self.strategy = strategy  # 'momentum', 'value', 'noise'
        self.trades = []
    
    def trade(self, price, volatility):
        """Decide whether to buy, sell, or hold."""
        if self.strategy == 'momentum':
            # Buy if price is rising
            if np.random.random() < 0.6:
                return 'buy', self.cash * 0.1
        elif self.strategy == 'value':
            # Buy if price is low (below intrinsic value)
            intrinsic_value = 100
            if price < intrinsic_value * 0.9:
                return 'buy', self.cash * 0.15
        elif self.strategy == 'noise':
            # Random trading
            if np.random.random() < 0.2:
                return 'buy', self.cash * 0.05
            if np.random.random() < 0.2:
                return 'sell', self.holdings * 0.1
        
        return 'hold', 0

# Simulate a market with agents
n_agents = 100
agents = []
for i in range(n_agents):
    cash = np.random.uniform(1000, 10000)
    risk_tolerance = np.random.uniform(0.3, 0.9)
    strategy = np.random.choice(['momentum', 'value', 'noise'], p=[0.4, 0.3, 0.3])
    agents.append(MarketAgent(i, cash, risk_tolerance, strategy))

# Market simulation
n_days = 200
price = 100
price_history = [price]
volatility = 0.02

for day in range(n_days):
    # Random price movement
    price *= 1 + np.random.normal(0, volatility)
    price = max(price, 50)
    price_history.append(price)
    
    # Agents trade
    for agent in agents:
        action, amount = agent.trade(price, volatility)
        if action == 'buy' and agent.cash >= amount:
            shares = amount / price
            agent.holdings += shares
            agent.cash -= amount
            agent.trades.append(('buy', day, price, shares))
        elif action == 'sell' and agent.holdings > 0:
            shares = min(amount / price, agent.holdings)
            agent.cash += shares * price
            agent.holdings -= shares
            agent.trades.append(('sell', day, price, shares))

# Visualise market simulation
fig, axes = plt.subplots(1, 2, figsize=(14, 5))

ax = axes[0]
ax.plot(price_history, 'b-', linewidth=1.5)
ax.set_xlabel('Day')
ax.set_ylabel('Price')
ax.set_title('Simulated Market Price (Agent-Based)')
ax.grid(True, alpha=0.3)

# Agent distribution
ax = axes[1]
agent_types = [agent.strategy for agent in agents]
type_counts = pd.Series(agent_types).value_counts()
ax.bar(type_counts.index, type_counts.values, color=['blue', 'green', 'orange'])
ax.set_xlabel('Strategy Type')
ax.set_ylabel('Number of Agents')
ax.set_title('Agent Strategy Distribution')
ax.grid(True, alpha=0.3)

plt.tight_layout()
plt.savefig('market_simulation.png', dpi=300)
plt.show()

print("Market simulation completed.")

# ----------------------------------------------------------------
# PART G: DIGITAL TWIN CHALLENGES AND BEST PRACTICES
# ----------------------------------------------------------------

print("\n" + "-"*60)
print("PART G: Digital Twin Challenges and Best Practices")
print("-"*60)

print("""
Key Challenges:

1. Data Requirements:
   - Need large volumes of high-quality data.
   - Data must be real-time or near-real-time.
   - Historical data for model calibration.

2. Model Complexity:
   - Capturing all relevant behaviours is difficult.
   - Trade-off between realism and computability.
   - Model validation is challenging.

3. Computational Cost:
   - Agent-based simulations can be expensive.
   - Need scalable infrastructure (cloud, parallel computing).

4. Validation:
   - How do we know the twin is accurate?
   - Need continuous calibration against real-world data.
   - Uncertainty quantification is essential.

5. Governance:
   - Who owns the twin?
   - How are decisions made using twin outputs?
   - Regulatory acceptance is evolving.

Best Practices:
  - Start with a focused problem (e.g., deposit portfolio).
  - Use hybrid modelling (agent-based + machine learning).
  - Validate against historical data and expert judgment.
  - Build incrementally – add complexity over time.
  - Use the twin for "what-if" analysis, not just prediction.
  - Document assumptions and limitations.
  - Engage stakeholders early in the design process.
""")

# ----------------------------------------------------------------
# PART H: BUSINESS VALUE AND ROI
# ----------------------------------------------------------------

print("\n" + "-"*60)
print("PART H: Business Value of Digital Twins in Banking")
print("-"*60)

print("""
Business Value:

1. Better Decision-Making:
   - Test strategies before implementing them.
   - Reduce risk of poor decisions.

2. Risk Reduction:
   - Identify vulnerabilities before they materialise.
   - Stress test portfolios and systems.

3. Customer Experience:
   - Simulate customer journeys and optimise touchpoints.
   - Personalise offers based on simulated behaviour.

4. Operational Efficiency:
   - Optimise processes (e.g., loan origination).
   - Reduce costs through better resource allocation.

5. Innovation:
   - Experiment with new products and services.
   - Test market responses without real-world risk.

ROI Estimation:
  - Typical ROI: 3-10x over 3-5 years.
  - Faster time-to-market for new products.
  - Reduced losses from avoided risks.
  - Improved customer retention and acquisition.

Case Study:
  - A large European bank used a digital twin for liquidity management.
  - Simulated 100,000 customer scenarios daily.
  - Reduced liquidity buffer by 15% (saving €500M).
  - Increased NII by €50M/year.
""")

# ----------------------------------------------------------------
# PART I: SUMMARY AND RECOMMENDATIONS
# ----------------------------------------------------------------

print("\n" + "="*70)
print("PART I: Summary and Recommendations")
print("="*70)

print("""
Digital Twins in Finance – Key Takeaways:

1. Digital twins are virtual replicas of financial systems, customers, or processes.
2. Agent-based modelling is a common approach for financial digital twins.
3. Use cases: customer behaviour simulation, portfolio risk, market dynamics, and operational optimisation.
4. Benefits: better decisions, reduced risk, improved customer experience, and innovation.
5. Challenges: data requirements, model complexity, computational cost, and validation.
6. Business value: 3-10x ROI over 3-5 years.
7. Best practice: start focused, validate rigorously, and scale incrementally.

Recommendations:
  - Start with a pilot for a specific business problem.
  - Use hybrid modelling (agent-based + ML) for richer behaviour.
  - Invest in data infrastructure (real-time data feeds).
  - Build a dedicated digital twin team (data scientists, domain experts, engineers).
  - Collaborate with academia and technology partners.
  - Develop a roadmap for scaling digital twins across the organisation.
""")

print("="*70)
print("END OF LESSON 4 – MODULE 7")
print("="*70)

SECTION 6: SUMMARY FOR THE DATA PRACTITIONER

  • Digital twins create virtual replicas of financial systems for simulation and analysis.

  • Agent-based modelling captures heterogeneous behaviours and emergent outcomes.

  • Key applications include customer simulation, portfolio risk, market dynamics, and operational optimisation.

  • Challenges include data requirements, model complexity, computational cost, and validation.

  • Business value includes better decisions, risk reduction, and improved customer experience.

  • Best practices: start focused, validate rigorously, and scale incrementally.


SECTION 7: RECOMMENDED NEXT STEPS

  1. Build a simple agent-based model for a financial use case.

  2. Explore AnyLogicMesa, or NetLogo for agent-based simulation.

  3. Investigate digital twin platforms (e.g., Azure Digital Twins, AWS IoT TwinMaker).

  4. Study hybrid modelling (ABM + ML) for richer simulations.

  5. Prepare for the next lesson on Web3 and the Future of Finance.


[END OF LESSON 4 – MODULE 7]