When precision matters, especially in finance, the smallest decimal places can have a big impact on the bottom line. In this post, I will show a portfolio app in Rust that starts with reconciling the firm’s income from multiple sources, then allocates earnings to customers based on their portfolios, and finally calculates payouts or reinvestments based on their preferences. All while ensuring that our sums add up, from the firm level down to each individual customer.
The Use Case
Our app models a portfolio that deals with income from multiple sources. The steps are:
- Reconcile the firm’s income from various sources (trade returns, dividends, etc.) and validate that the firm has received the expected amounts.
- Calculate the firm’s overall earnings after reconciliation.
- Allocate earnings to each customer based on their share of the firm’s portfolio.**
- Handle payouts or reinvestments for each customer, applying specific rounding rules.**
- Validate that the sum of all customer payouts matches the reconciled firm earnings, ensuring accuracy across our calculation chain.
By using Rust’s Decimal
type and PostgreSQL’s NUMERIC
, it can maintain high precision and consistency for our data.
Step 1: Reconciling the Firm's Income from Multiple Sources
Before calculating the firm’s total earnings, first we need to reconcile income from different sources. Let’s assume the firm has several streams of income, such as:
- Market Trades
- Dividends
- Bond Interest Payments
Each income source is expected to contribute a certain amount, and we want to validate that we’ve received what was anticipated.
Firm Income Struct and Reconciliation
To model this, let’s define a FirmIncome
struct that records both expected and received income for each source. Then, we’ll sum these up and check if they match.
use rust_decimal::Decimal;
use std::collections::HashMap;
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Clone, Debug)]
struct FirmIncome {
source: String,
expected: Decimal,
received: Decimal,
}
fn reconcile_income(incomes: &Vec<FirmIncome>) -> Result<Decimal, String> {
let total_expected: Decimal = incomes.iter().map(|i| i.expected).sum();
let total_received: Decimal = incomes.iter().map(|i| i.received).sum();
if total_expected == total_received {
Ok(total_received)
} else {
Err(format!("Income mismatch! Expected: {}, Received: {}", total_expected, total_received))
}
}
Example Reconciliation
Here’s how we’d create and reconcile incomes from various sources:
let incomes = vec![
FirmIncome { source: "Market Trades".to_string(), expected: Decimal::new(200_000, 2), received: Decimal::new(200_000, 2) },
FirmIncome { source: "Dividends".to_string(), expected: Decimal::new(50_000, 2), received: Decimal::new(50_000, 2) },
FirmIncome { source: "Bond Interest".to_string(), expected: Decimal::new(20_000, 2), received: Decimal::new(20_000, 2) },
];
let reconciled_income = reconcile_income(&incomes).expect("Income reconciliation failed");
println!("Reconciled Income: {}", reconciled_income); // Reconciled Income: 270,000.00
Now we know the total amount received (270,000.00
) and can proceed with further calculations, confident that our firm’s income is accurately accounted for.
Step 2: Calculating the Firm’s Total Earnings Post-Reconciliation
With the reconciled income, we can calculate the firm’s total earnings using a market rate (or another performance metric). The firm’s earnings are calculated based on its market performance multiplied by the total assets.
#[derive(Serialize, Deserialize, Clone, Debug)]
struct Firm {
total_assets: Decimal,
market_rate: Decimal,
reconciled_income: Decimal,
}
fn calculate_firm_earnings(firm: &Firm) -> Decimal {
firm.reconciled_income + (firm.total_assets * firm.market_rate)
}
Example Usage
Assume the firm has 10,000,000.00
in total assets with a market rate of 2.5%
:
let firm = Firm {
total_assets: Decimal::new(10_000_000, 2),
market_rate: Decimal::new(25, 3),
reconciled_income,
};
let firm_earnings = calculate_firm_earnings(&firm);
println!("Firm Earnings after reconciliation: {}", firm_earnings); // Firm Earnings after reconciliation: 520,000.00
This calculation gives us the final amount for distribution across the customer portfolios.
Step 3: Allocating Earnings to Customer Portfolios
With the firm’s earnings calculated, let’s allocate these earnings to each customer based on their portfolio balance.
Customer Struct and Allocation Logic
#[derive(Serialize, Deserialize, Clone, Debug)]
struct Customer {
id: i32,
name: String,
balance: Decimal,
earnings: Decimal,
reinvest: bool,
}
fn allocate_customer_earnings(firm_earnings: Decimal, customer_balance: Decimal, total_assets: Decimal) -> Decimal {
firm_earnings * (customer_balance / total_assets)
}
Example Allocation
If a customer has 100,000.00
in their balance, the allocated earnings are calculated as follows:
let customer_balance = Decimal::new(100_000, 2);
let total_assets = firm.total_assets;
let customer_earnings = allocate_customer_earnings(firm_earnings, customer_balance, total_assets);
println!("Customer Earnings: {}", customer_earnings); // Customer Earnings: 5,200.00
Step 4: Processing Payouts and Reinvestments
Depending on each customer’s preference, we either round the earnings to two decimal places for a payout or add them to their balance for reinvestment.
Handling Payouts
fn payout(customer: &mut Customer) -> Decimal {
let payout_amount = customer.earnings.round_dp(2);
customer.balance -= payout_amount;
customer.earnings = Decimal::ZERO;
payout_amount
}
Handling Reinvestment
fn reinvest_earnings(customer: &mut Customer) {
customer.balance += customer.earnings;
customer.earnings = Decimal::ZERO;
}
Example Usage
let mut customer = Customer {
id: 1,
name: "Alice".to_string(),
balance: Decimal::new(100_000, 2),
earnings: customer_earnings,
reinvest: false,
};
if customer.reinvest {
reinvest_earnings(&mut customer);
} else {
let payout_amount = payout(&mut customer);
println!("Payout Amount: {}", payout_amount); // Payout Amount: 5,200.00
}
println!("Updated Balance: {}", customer.balance);
Step 5: Ensuring Bottom-Up Validation of Total Payouts
For consistency, we need to check that the total payouts match the firm’s reconciled earnings.
Validation Function
fn validate_total_payouts(customers: &Vec<Customer>, firm_earnings: Decimal) -> bool {
let total_payouts: Decimal = customers.iter().map(|c| c.earnings.round_dp(2)).sum();
total_payouts == firm_earnings.round_dp(2)
}
This validation adds an integrity check, ensuring that the sum of individual payouts aligns with the firm’s overall earnings, preventing discrepancies.
Precision Preservation with PostgreSQL’s NUMERIC
Using Decimal
alongside PostgreSQL’s NUMERIC
type keeps our data precise, ensuring our calculations maintain integrity across application and database layers.
PostgreSQL Schema Example
CREATE TABLE firm (
id SERIAL PRIMARY KEY,
total_assets NUMERIC(20, 2),
market_rate NUMERIC(20, 4),
reconciled_income NUMERIC(20, 2)
);
CREATE TABLE customers (
id SERIAL PRIMARY KEY,
name VARCHAR(100),
balance NUMERIC(20, 2),
earnings NUMERIC(20, 2),
reinvest BOOLEAN
);
Final Thoughts
This Rust reference application demonstrates a robust, precision-oriented approach to managing financial calculations. By starting with income reconciliation and layering on accurate payout allocation and validation, we are required to maintain precise accounting of every decimal, aligning the payouts with the firm’s total earnings.
Rust’s Decimal
and PostgreSQL’s NUMERIC
together make a winning combination, ideal for any business application where precision is not just preferred but mandatory.
Top comments (0)