Cambio
Turn scattered FX transactions into a real cost basis and see whether timing the market actually helped.
Why I built it#
Over several years of studying abroad I exchanged currency many times, each at a different rate. Eventually, the question “what did my foreign currency actually cost me on average” was one neither bank statements nor rate apps could answer. Cambio computes a real cost basis from scattered transaction records and compares it against the market rate.
It handles only base-to-foreign currency exchanges, not arbitrary cross-currency pairs. Each transaction stores the base-currency setting at the time it was made, so changing the setting later never rewrites history.
Position and cost#
Transactions replay chronologically using moving weighted-average cost. Buys fold into the cost pool weighted by amount; sells settle at the existing average, and the difference becomes realised P&L without altering the remaining position’s basis. Fees are absorbed into the effective rate rather than listed separately. When a position goes flat, the pool resets.
A decision card at the top of the ledger compares the latest market rate against the personal average, showing unrealised P&L and percentage deviation.

Ledger: transactions roll up into the current position; the decision card compares personal average with market rate.
Timing analysis#
The analysis view aligns daily market rates, position size, and personal cost on one timeline. The market moves every day; cost moves only on transaction dates, so each trade’s impact on the average is immediately visible. Before placing an order, What-if simulation lets you enter a hypothetical transaction and preview how it would change the position and average in real time; confirm to commit. Simulation and real entries run the same calculation.

Analysis: transaction points, personal cost, and market rates on one timeline.
The timing review provides two quantitative benchmarks. TWAP is the time-weighted average of market rates over the same period, measuring whether you did better than a random entry point. DCA is the weighted harmonic average of daily equal-amount purchases, measuring whether you beat a mechanical strategy that requires no judgement.

Timing review: personal average against TWAP and DCA, with the best and worst transactions marked.
Technical details#
Monorepo with four packages. packages/core is a pure-function domain engine depending only on decimal.js; all position tracking, simulation, and timing analysis are completed here. packages/shared defines every request and response as Zod schemas shared by frontend and backend, driving both validation and OpenAPI documentation. The API layer is Fastify 5 + Drizzle + PostgreSQL; the frontend is React 19 + Vite + TanStack Router.
Financial values stay decimal end to end: numeric in PostgreSQL, strings over the wire, decimal.js in the domain layer—no floating point anywhere. The full position history computes in a single O(n) pass along the timeline.
Authentication uses server-side sessions with Argon2id password hashing and SHA-256 token storage, supporting immediate revocation of all sessions on password change. Sharing is through revocable, optionally expiring read-only links that expose only aggregate positions, never individual transactions or notes.