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.