A Unified Framework
Managing a lending portfolio means fighting a war on two fronts simultaneously. On one side, credit risk — the danger that a borrower simply can’t repay. On the other, fraud risk — the danger that a borrower never intended to repay. Optimizing for one while ignoring the other leads to portfolios that look clean on paper but bleed money in practice.
This post walks through a concrete, mathematically grounded model that jointly optimizes both risks, solves it in Python, and visualizes the results in 2D and 3D.
The Problem Setup
Imagine a bank evaluating 1,000 loan applicants. Each applicant has two scores:
- $s_c \in [0, 1]$ — credit score (higher = less likely to default)
- $s_f \in [0, 1]$ — fraud score (higher = less likely to be fraudulent)
The bank must set two thresholds:
- $\tau_c$ — minimum credit score to approve
- $\tau_f$ — minimum fraud score to approve
An applicant is approved if and only if $s_c \geq \tau_c$ and $s_f \geq \tau_f$.
The Mathematical Model
Expected profit for an approved applicant:
$$\Pi(s_c, s_f) = R \cdot (1 - P_d(s_c)) \cdot (1 - P_f(s_f)) - L_d \cdot P_d(s_c) - L_f \cdot P_f(s_f)$$
Where:
- $R$ = revenue from a performing loan
- $L_d$ = loss given credit default
- $L_f$ = loss given fraud
- $P_d(s_c) = e^{-\alpha s_c}$ — default probability (decreasing in credit score)
- $P_f(s_f) = e^{-\beta s_f}$ — fraud probability (decreasing in fraud score)
Total expected profit over all applicants:
$$\mathcal{J}(\tau_c, \tau_f) = \sum_{i=1}^{N} \mathbf{1}[s_c^{(i)} \geq \tau_c,\ s_f^{(i)} \geq \tau_f] \cdot \Pi(s_c^{(i)}, s_f^{(i)})$$
Optimization problem:
$$\max_{\tau_c,\ \tau_f \in [0,1]} \quad \mathcal{J}(\tau_c, \tau_f)$$
subject to:
$$\text{Approval Rate} \geq \delta_{\min}$$
$$\mathbb{E}[P_d \mid \text{approved}] \leq \rho_c$$
$$\mathbb{E}[P_f \mid \text{approved}] \leq \rho_f$$
The Integrated Risk Score
Rather than treating the two risks independently, we define a combined risk score:
$$\mathcal{R}(s_c, s_f) = \lambda \cdot P_d(s_c) + (1 - \lambda) \cdot P_f(s_f), \quad \lambda \in [0,1]$$
The parameter $\lambda$ controls the trade-off weight between credit and fraud risk. The bank can then set a single threshold $\tau_r$ on $\mathcal{R}$, or jointly optimize $(\tau_c, \tau_f)$ on the full profit surface.
Python Implementation
1 | # ============================================================ |
Code Walkthrough
Section 1 — Synthetic Population
We generate 1,000 applicants using a bivariate normal distribution with correlation $\rho = 0.3$ between credit and fraud scores. The positive correlation is realistic: borrowers with high creditworthiness also tend to have lower fraud propensity.
Section 2–3 — Probability and Profit Functions
Default probability $P_d(s_c) = e^{-\alpha s_c}$ is an exponentially decaying function of credit score. With $\alpha = 4$, an applicant with $s_c = 0.5$ has $P_d \approx 13.5%$, while $s_c = 0.9$ gives $P_d \approx 2.8%$. Fraud probability follows the same logic with $\beta = 5$, reflecting that fraud risk is steeper — a modest improvement in fraud score rapidly reduces exposure. The per-applicant profit formula rewards safe, clean borrowers and penalizes the bank for both types of loss.
Section 4 — Objective and Constraints
The objective is total portfolio profit, penalized heavily when any of the three constraints is violated (minimum approval rate, maximum average default probability, maximum average fraud probability). Using a penalty method like this converts the constrained problem into an unconstrained one amenable to global search.
Section 5 — Grid Search for the Full Surface
We evaluate profit at every point on an 80×80 grid of $(\tau_c, \tau_f)$ pairs. This gives us the complete landscape for 3D visualization and helps confirm the optimizer’s result.
Section 6 — Differential Evolution
Differential Evolution is a population-based global optimizer. Unlike gradient descent, it does not get trapped in local optima, which matters here because the profit surface has flat regions (all applicants rejected) and sharp constraint boundaries. The algorithm evolves a population of 15 candidate threshold pairs over up to 500 generations.
Section 7 — Lambda Sweep
By varying $\lambda$ from 0 (pure fraud focus) to 1 (pure credit focus), we trace out how the optimal thresholds and portfolio statistics shift as management’s risk priority changes. This is critical for regulatory scenarios — a regulator concerned primarily with fraud losses would push $\lambda$ toward 0, tightening $\tau_f$.
Results
======================================================= OPTIMIZATION RESULTS ======================================================= Optimal credit threshold τ_c : 0.4245 Optimal fraud threshold τ_f : 0.4223 Total expected profit : $775,350 Applicants approved : 444 / 1000 Approval rate : 44.4% Avg default probability : 0.0944 (limit 0.15) Avg fraud probability : 0.0557 (limit 0.1) =======================================================
Understanding the Visualizations
Figure 1 — Population Panel:

The left scatter plot colors each applicant by their expected individual profit. Applicants in the upper-right quadrant (high credit score, high fraud score) glow green; those near the origin are deep red. The optimal thresholds divide the space into four quadrants; the approved zone is upper-right. The histogram shows both score distributions peaking around 0.5, with the dashed lines marking where the thresholds cut.
Figure 2 — 2D Profit Heatmap:

This is the profit surface viewed from directly above. The red star marks the globally optimal $(\tau_c^*, \tau_f^*)$. Notice that the surface is not symmetric: because $L_f > L_d$ (fraud losses exceed credit losses), the contours are more sensitive to $\tau_f$ — moving horizontally (relaxing fraud standards) drops profit faster than moving vertically (relaxing credit standards).
Figure 3 — 3D Profit Surface:

The three-dimensional view reveals the ridge structure. The profit rises as both thresholds increase (stricter screening), but then falls sharply when thresholds are too high (too few applicants approved to generate revenue). The optimal point sits on the shoulder of this ridge — the exact balance between selectivity and volume.
Figure 4 — Lambda Trade-off (four panels):

- Top left: Portfolio profit as a function of $\lambda$. The curve is not monotone — there is a sweet spot.
- Top right: Approval rate shifts as emphasis moves between risks.
- Bottom left: Average default and fraud probabilities track the constraints. When $\lambda \approx 0$ (fraud-focused), the bank tightly controls $P_f$ but may allow higher $P_d$.
- Bottom right: The optimal thresholds $\tau_c, \tau_f$ as $\lambda$ varies — showing how the screening policy adapts.
Figure 5 — 3D Pareto Frontier:

Each point is a feasible threshold pair satisfying all constraints. The x-axis is average default probability, the y-axis is average fraud probability, and the z-axis is total profit. High-profit portfolios cluster in the lower-left corner of the risk plane (low default, low fraud), but reaching them requires increasingly strict thresholds that reduce approval volume — the fundamental three-way tension at the heart of the model.
Key Takeaways
Joint optimization dominates sequential optimization. Setting $\tau_c$ first and $\tau_f$ second (or vice versa) misses interactions between the two risk dimensions.
The profit surface has a well-defined ridge. Being too strict leaves money on the table through rejected good customers; being too lenient generates losses. The optimizer finds the ridge precisely.
$\lambda$ is a management lever, not just a hyperparameter. Shifting $\lambda$ translates directly into different regulatory postures, capital allocations, and approval policies — it should be calibrated against loss data quarterly.
Fraud losses dominate when $L_f > L_d$. In our setup, the optimal $\tau_f^*$ ends up higher than $\tau_c^*$ because the cost of a fraudulent loan exceeds the cost of a defaulted loan. This asymmetry is almost always present in consumer lending.














