Saltar a contenido

Rendered notebook

Generated from 03_fx_multi_currency.ipynb. Run it yourself with make docs-up — see Run locally.

03 — Multi-currency arithmetic

Globaltrust customers hold accounts in USD, EUR, BRL, CNY. We model amounts in minor units (cents / centavos / fen) to avoid floating-point arithmetic in money math.

03_fx_multi_currency.ipynb · cell 1
from dataclasses import dataclass
from decimal import Decimal

MINOR_UNITS = {'USD': 100, 'EUR': 100, 'BRL': 100, 'CNY': 100}

@dataclass(frozen=True)
class Money:
    minor: int
    currency: str
    def __str__(self):
        unit = MINOR_UNITS[self.currency]
        amount = Decimal(self.minor) / unit
        symbol = {'USD': '$', 'EUR': '€', 'BRL': 'R$', 'CNY': '¥'}[self.currency]
        return f'{symbol}{amount:,.2f}'

Money(123456, 'EUR')
03_fx_multi_currency.ipynb · cell 2
# Snapshot of FX rates (in micro-units — 1e6 multiplier — for precision)
FX_RATES_MICRO = {
    ('USD', 'EUR'):    920_000,   # 1 USD = 0.92 EUR
    ('USD', 'BRL'):  5_080_000,   # 1 USD = 5.08 BRL
    ('USD', 'CNY'):  7_240_000,   # 1 USD = 7.24 CNY
    ('EUR', 'USD'):  1_087_000,
    ('EUR', 'BRL'):  5_521_000,
    ('EUR', 'CNY'):  7_870_000,
}

def convert(money: Money, target: str) -> Money:
    if money.currency == target:
        return money
    rate = FX_RATES_MICRO.get((money.currency, target))
    if rate is None:
        raise ValueError(f'no rate for {money.currency} → {target}')
    new_minor = (money.minor * rate) // 1_000_000
    return Money(int(new_minor), target)

balance = Money(1_500_000, 'USD')   # $15,000.00
print('USD balance →', balance)
for target in ['EUR', 'BRL', 'CNY']:
    print(f'  in {target}:', convert(balance, target))

In the real globaltrust-bank

The same logic lives in src/domain/business/banking/fx/services/fx_service.py (autogenerated stub) and consumes FxRateEntity rows fetched from the latest observed_at per (base_currency, quote_currency) pair.