Table of Contents

Runnable samples

The repository ships runnable, self-contained sample projects for the financial packages under samples/Financial/. Every sample runs fully offline - exchange-rate samples build their providers from committed static data files instead of calling live feeds - and each carries a clearly fenced comment block showing how to switch to a real web provider. The samples are members of bodu.slnx and are built and executed by CI, so the code they show cannot drift from the current API.

Run any sample from the repository root:

dotnet run --project samples/Financial/<SampleName>

The samples

Bodu.Financial.Samples.MoneyBasics

The core value types, offline with in-code data only. Covers the three-tier rounding model (Money<TCurrency> per step, CalculatedMoney deferred, Fraction<BigInteger> exact via MultiplyExact), the typed↔runtime bridges (As<T>, TryAs<T>, casts), sum-preserving allocation and cash rounding, the format-specifier vocabulary and MoneyFormatterBuilder, the four MoneyParseMode levels, MoneyBag ledgers with ConvertToWithAudit, and the three FinancialJsonPolicy shapes. Packages: Bodu.Financial.

Bodu.Financial.Samples.OfflineRates

The flagship static-rate-file pattern: a committed CSV poured through RateTableBuilder into an immutable RateBook, served through FixedDatedRateProvider - the same contracts the live web providers implement. Works the four RateLookupOptions date-resolution modes over weekend gaps and converts typed and runtime money through dated rates. Packages: Bodu.Financial.

Bodu.Financial.Samples.CachedRates

The caching layer against an offline source: the read-through CachingRateProvider, coverage-based range serving (including negative caching of empty windows), tiered stacking (in-memory L1 over durable file L2, surviving a simulated restart), and the RateHistoryAvailability clamping model. A closing scenario runs the two durable backends: a SQLite-backed cache read back by a second provider after the first is disposed, and two providers over one IDistributedCache standing in for two processes - with the result's RateProvenance still reporting Origin=Cache so a stored answer is never mistaken for a fresh fetch. A small counting decorator makes hit-vs-fetch behaviour visible throughout. Packages: Bodu.Financial, Bodu.Financial.ExchangeRates.Caching, …Caching.Sqlite, …Caching.Distributed.

Bodu.Financial.Samples.AggregatedRates

Multi-provider aggregation over two offline feeds with complementary coverage: AggregatingRateProvider with priority fallback, AverageStrategy (and its synthetic provenance label), per-pair CurrencyPairRoute overrides, and the fluent AddAggregatedRateProvider DI builder with keyed per-child resolution. Packages: Bodu.Financial, Bodu.Financial.ExchangeRates.Caching, Bodu.Financial.DependencyInjection.

Bodu.Financial.Samples.CurrencyServices

Currency services and host wiring: the ambient CurrencyResolution seam (PushScoped over a restricted-lookup decorator), named MonetaryContext registrations, and the AddFinancialService composition root with UseCurrencyResolution. Packages: Bodu.Financial, Bodu.Financial.DependencyInjection.

Bodu.Financial.Samples.JsonSerialization

System.Text.Json integration from the Bodu.Financial.Serialization.Json companion: AddFinancialJsonConverters() round-tripping Money, Money<TCurrency>, and MoneyBag; ExchangeRate and CurrencyPair converters; the FinancialJsonPolicy Strict/Lenient/Compact wire shapes; and the AddFinancialJson() DI registration exposing keyed JsonSerializerOptions (key "Financial"). Packages: Bodu.Financial, Bodu.Financial.Serialization.Json.

Bodu.Financial.Samples.UnitPricing

Higher-than-currency precision for unit prices - a six-decimal-place share price in two-decimal USD - and preserving it through serialization: a Money carrying an explicit scale (settled through a custom-scale MonetaryContext) whose Strict JSON shape records a scale property, unrounded CalculatedMoney written verbatim, and six-place prices round-tripping inside a POCO price list - see Monetary precision & unit pricing. Packages: Bodu.Financial, Bodu.Financial.Serialization.Json.

Bodu.Financial.Samples.CustomProvider (+ .Test)

Consumer extensibility: a custom CsvFileRateProvider in the recommended shape (builder → book → delegated fixed provider), used directly, through the conversion extensions, and under the caching decorator. Its companion test project derives DatedRateProviderContractTests<CsvFileRateProvider> from the in-repo Bodu.Financial.ExchangeRates.Testing project - see Testing your own provider. Packages: Bodu.Financial, Bodu.Financial.ExchangeRates.Caching; the test project also references the in-repo Bodu.Financial.ExchangeRates.Testing project, which is not published to NuGet.

Bodu.Financial.Samples.LiveRates

The one sample that goes online (and is therefore excluded from the CI samples run): it fetches real published rates from a live web provider for a computed historical date - the most recent Wednesday at least five days old, with a PreviousWithin(5) tolerance so a published fixing is near-certain - plus that date's trailing week as a single range read. The ECB feed is active by default; RBA, BoE, Yahoo, OFX, OANDA, XE, Fixer, exchangerate.host, FRED, and IMF are comment-switchable blocks (the API-key sources need a key set), and every provider package is referenced so the switch is a comment flip. Packages: one of the Bodu.Financial.ExchangeRates.<Source> provider packages.

Offline by default, live by choice

With the exception of LiveRates above, the samples never touch the network. Where a live feed could be used, a fenced comment block shows the exact switch:

// --- To use the live Reserve Bank of Australia feed instead -----------------
// 1. dotnet add package Bodu.Financial.ExchangeRates.Rba
// 2. Replace the offline source with:
//
//     using var rba = new RbaRateProvider(new RbaRateProviderOptions());
//     await rba.LoadRangeAsync(new DateOnly(2024, 1, 1), new DateOnly(2024, 6, 30));
// ----------------------------------------------------------------------------

Because every provider in the family serves the same IDatedRateProvider / IRateProvider contracts, the rest of each sample works unchanged after the switch.