DEV Community

amaendeepm
amaendeepm

Posted on

Financial Calculations in Rust: Building a Reference Portfolio App from Firm Trades to Customer Earnings

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:

  1. Reconcile the firm’s income from various sources (trade returns, dividends, etc.) and validate that the firm has received the expected amounts.
  2. Calculate the firm’s overall earnings after reconciliation.
  3. Allocate earnings to each customer based on their share of the firm’s portfolio.**
  4. Handle payouts or reinvestments for each customer, applying specific rounding rules.**
  5. 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))
    }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

Handling Reinvestment

fn reinvest_earnings(customer: &mut Customer) {
    customer.balance += customer.earnings;
    customer.earnings = Decimal::ZERO;
}
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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
);
Enter fullscreen mode Exit fullscreen mode

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)