Case Study

Fractional Commodities Trading Platform

A sophisticated PHP trading platform enabling fractional ownership of commodities, featuring multi-tenant API architecture, precision financial calculations, and real-time pricing.

Multiple
Commodities
Multiple
Currencies
3
API Surfaces
432
Commits

The Business Challenge

A commodities trading platform required a headless API platform to enable retail and trade customers to buy and sell fractional shares of commodities. The platform needed to support multiplecommodities with independent spot prices and spreads.

The challenge extended beyond simple transactions. Different customer segments required distinct pricing structures, separate credit facilities, and different authentication mechanisms—yet all operations needed to share core transaction processing logic to ensure consistency.

Key Requirements

  • Multi-commodity trading: Support for multiple commodities with independent spot prices and spreads
  • Multi-tenant architecture: Separate API surfaces for trade partners and retail customers with shared core logic
  • Precision arithmetic: Financial-grade calculations using arbitrary-precision mathematics
  • Credit facilities: Configurable credit limits with segment-specific rules and multi-currency support
  • Backward compatibility: Support legacy single-metal endpoints whilst evolving to multi-metal capabilities
Show why financial-grade precision matters

Floating-point arithmetic introduces rounding errors that compound over thousands of transactions. For financial systems handling fractional ownership of valuable commodities, precision errors can lead to significant discrepancies.

BC Math provides arbitrary-precision calculations that guarantee exact results for monetary and weight conversions. This approach eliminates rounding errors entirely, ensuring that customer holdings always reconcile to the penny across all currencies and weight units.

The Technical Solution

We built a Symfony 6.1 platform following domain-driven design principles with a layered architecture separating API surfaces from core business logic. The system uses Doctrine ORM ORM with custom embeddable value objects to encapsulate complex domain concepts like weight conversions and spread pricing.

Dual-API Architecture

The platform serves three distinct client types through separate API surfaces, each with its own authentication, pricing, and validation rules:

  • Admin API: Internal management for spread configuration, credit limits, and price cache invalidation
  • Trade API: Partner-facing with tighter spreads, server-side pricing, and commercial credit terms
  • Web API: Retail customer-facing with client-provided pricing and consumer credit facilities
Show how the architecture prevents code duplication

The dual-API design uses parallel controller hierarchies that share a common trait for validation logic. Trade API and Web API each have their own abstract base controllers, but both use the same ApiHelperTrait for vault validation and user verification.

Request objects differ between APIs (Trade API uses single-commodity requests, Web API supports multi-metal transactions), but a unified factory converts both formats to a common CreateOrderRequestDetails structure. This factory applies the appropriate SpreadTypes enum (TRADE or WEB) to ensure correct pricing without duplicating the order creation service.

Authentication uses separate token tables (TradeApiToken and WebApiToken) with independent expiration policies, yet all tokens resolve to the same User entity. This isolation enables different security requirements per segment whilst maintaining a single user model.

The shared validation logic demonstrates how traits provide code reuse without inheritance complexity, enabling both API surfaces to validate requests identically.

Precision Financial Calculations

All monetary and weight calculations use BC Math for arbitrary-precision arithmetic, avoiding floating-point errors. The system employs Money PHP for currency handling and custom Weight and Spread embeddables that enforce business rules at the domain level.

Show why we store weight in both grams and troy ounces

Every transaction stores weight in both units (violating database normalisation) to prioritise performance and consistency. Converting between grams and troy ounces involves irrational numbers that require arbitrary-precision calculations.

By converting once at write time and storing both representations, we avoid repeated runtime conversions and guarantee that all queries return identical values regardless of unit. This trade-off accepts redundant storage for the benefit of consistent precision across millions of potential transactions.

The Weight embeddable encapsulates all precision handling, conversion logic, and business rules for commodity weight calculations in a single reusable domain object.

Configuration-Driven Pricing

Spread configurations are stored in the database and keyed by commodity and API type, enabling runtime adjustments without deployment. Each commodity has separate buy/sell spreads for trade and retail segments, with business rule validation ensuring spreads always make financial sense.

Beyond pricing logic, the platform enforces strict commodity validation to ensure only supported metals are accepted for trading.

Show how commodity validation prevents errors

The platform validates all commodity identifiers against an explicit allowlist of supported commodities. This prevents typos, invalid identifiers, and unsupported commodities from entering the system.

The allowlist ensures complete coverage of supported commodities whilst maintaining type safety and preventing invalid data from entering the trading system.

The validation implementation demonstrates defensive programming, rejecting invalid input at the earliest possible point in the request lifecycle.

Ready to eliminate your technical debt?

Transform unmaintainable legacy code into a clean, modern codebase that your team can confidently build upon.

Technical Highlights

Multi-Metal Evolution with Backward Compatibility

The platform evolved from single-metal transactions to supporting multiple metals in a single order. Existing trade partner integrations relied on the original endpoint structure, requiring a non-breaking evolution path.

An adapter pattern converts legacy requests to the multi-metal format internally, allowing both endpoint styles to coexist whilst sharing 100% of the transaction creation logic. This pattern enabled the platform to evolve its capabilities without forcing partner re-integration.

Show adapter pattern implementation details

The SingleHoldingBuyOrderController accepts legacy single-commodity requests and converts them to the multi-metal format using an adapter. This conversion happens before business logic execution, ensuring the factory and service layers only handle one format.

Both legacy and modern endpoints invoke the same CreateOrderRequestDetails factory with the SpreadTypes parameter (TRADE or WEB). The factory handles spread application, price validation, and order creation identically regardless of request origin, eliminating code duplication whilst supporting both API styles.

Credit Limit System

Trade partners and retail customers have different credit limits stored in USD but applicable across all supportedcurrencies. The system performs real-time currency conversion when applying credit to balances, ensuring accurate purchasing power regardless of transaction currency.

Credit facilities integrate with segment-specific validation rules that enforce different trading restrictions for commercial partners versus retail customers.

Show how segment-specific validation prevents over-trading

Trade partners operate under commercial agreements and can execute sell orders regardless of their currency balance state. This reflects the business reality that trade partners have different risk profiles and contractual protections.

Retail customers, in contrast, are blocked from selling if they have negative balances. This consumer protection prevents over-trading and ensures retail customers only sell holdings they actually own.

The same CustomerBalanceValidator handles both scenarios with a simple branch on SpreadTypes enum, demonstrating how domain models can encode business rules concisely.

Token-Based Authentication with Expiration

Each API surface uses separate token tables with different expiration policies. Trade API tokens and Web API tokens are stored in distinct tables rather than a single polymorphic table, enabling independent schema evolution and clearer audit trails.

Authenticators validate tokens and check expiration on every request, with 120-character hex tokens generated using cryptographically secure random bytes.

Unique Transaction Dates with Microsecond Randomisation

Transaction dates include randomised microseconds to ensure uniqueness even for same-second transactions. This approach preserves meaningful date ordering for queries whilst avoiding collisions in high-frequency trading scenarios.

Show why randomised microseconds instead of auto-increment IDs

Most systems rely on auto-increment IDs or UUIDs for uniqueness, which works but loses temporal meaning. Auto-increment IDs reveal transaction volume (competitors can deduce activity from gaps), and UUIDs are non-sequential.

Randomising microseconds within the transaction second provides uniqueness without revealing business metrics. Queries can still use windowed date ranges without needing ID knowledge, and transactions sort chronologically by default.

This pattern works because commodity trading rarely generates multiple transactions per microsecond per user. The randomisation provides sufficient entropy for uniqueness whilst maintaining intuitive date-based query patterns.

Results & Outcomes

The platform successfully powers a production commodities trading platform, handling real-time transactions across multiple customer segments with zero-downtime evolution.

Delivered Capabilities

  • Multi-commodity support: Trading support for multiple commodities with independent pricing and spread configurations
  • Multi-currency operations: Transactions and credit facilities across multiple international currencies with real-time conversion
  • Segment isolation: Three separate API surfaces with distinct authentication, pricing, and validation rules
  • Precision calculations: Financial-grade arithmetic with arbitrary-precision handling preventing rounding errors
  • Backward compatibility: Legacy endpoints supported alongside new capabilities without breaking existing integrations
Show supported currencies and conversion

The platform supports multiple international currencies including major global currencies. All credit limits are stored in USD and converted to transaction currency in real time using current exchange rates.

This approach simplifies credit administration (single USD value per segment) whilst ensuring accurate purchasing power regardless of where customers transact. The CurrencyConverter fetches rates for all supported currencies and caches them for performance.

Technical Achievements

The platform demonstrates sophisticated domain modelling for financial services. Value objects like Weight and Spread encapsulate complex business rules and precision handling, preventing scattered BC Math calls and ensuring conversion consistency.

Show domain-Driven Design in practice

The platform uses Doctrine embeddables extensively to model complex domain concepts. Weight, Spread, and Price embeddables encapsulate not just data but behaviour and validation, following DDD principles of rich domain models.

This approach prevents anaemic domain models where entities are just data containers. Instead, business rules live alongside the data they govern, making the code self-documenting and reducing the likelihood of logic scatter across service layers.

The separation of API surfaces (controllers, request objects, authenticators) from core business logic (services, repositories, entities) demonstrates clean architecture principles, enabling independent evolution of interfaces whilst protecting core domain logic.

The adapter pattern for single-to-multi-metal migration proved highly effective, allowing non-breaking API evolution whilst consolidating business logic. Configuration-driven spreads enable business users to adjust pricing without developer involvement or deployment.

Show configuration-driven pricing advantages

Storing spreads in the database rather than configuration files enables runtime adjustments via API endpoints. Administrators can modify buy/sell spreads for any commodity without deployments, code changes, or server restarts.

Each commodity has separate spreads for Trade API (tighter margins) and Web API (wider margins). Business rules validation in the Spread embeddable prevents configuration errors like negative buy spreads or positive sell spreads that would violate financial logic.

Architecture Patterns

  • Domain-Driven Design: Rich domain models with behaviour encapsulation and business rule enforcement
  • Service layer abstraction: Controllers handle HTTP concerns, services handle business logic, repositories handle persistence
  • Factory pattern: Complex object creation centralised in factories with segment-awareness
  • Adapter pattern: Backward-compatible evolution without breaking existing integrations

Key Learnings

Architectural Decisions

Separating API surfaces whilst sharing core logic allowed independent evolution of Trade and Web APIs. Each API can add features without affecting the other, yet both benefit from improvements to shared transaction processing.

The decision to use separate token tables rather than a single polymorphic table proved valuable. Independent schema evolution per token type, simpler queries (no type filtering), and clearer audit trails outweighed the code duplication in token entities.

Domain Modelling

Embeddable value objects (Weight, Spread, Price) centralise precision handling and prevent business logic scatter. Having these domain concepts encapsulated with their own validation and conversion logic makes the codebase significantly more maintainable.

Storing derived data (weight in both grams and troy ounces) violates normalisation but prioritises correctness. Converting once at write time and storing both representations prevents precision drift and ensures query consistency.

Backward Compatibility

Planning for API evolution from the start paid dividends. The adapter pattern for single-to-multi-metal migration worked seamlessly and should be the default approach for non-breaking changes in mature APIs.

Show test coverage and quality assurance

With 251 test files covering 357 production files, the platform achieves thorough test coverage. Tests validate precision handling, spread application, credit calculation, currency conversion, and segment-specific validation rules.

The test suite includes data providers demonstrating correct currency conversion across all supported currencies, ensuring credit limits convert accurately from USD to any transaction currency. Integration tests verify that both legacy and modern endpoints produce identical results when given equivalent requests.

The technical patterns and architectural decisions demonstrated in this platform extend beyond commodities trading to a wide range of financial and e-commerce applications.

Show applicability to other financial platforms

The patterns demonstrated here apply broadly to financial services platforms: multi-tenant architectures with segment-specific pricing, precision arithmetic for monetary calculations, credit facilities with real-time currency conversion, and configuration-driven pricing engines.

Industries that could benefit include fractional ownership platforms (property, art, collectibles), commodity trading (agricultural products, energy), investment platforms (stocks, bonds, funds), and B2B platforms requiring different pricing for customer segments.

The technical patterns (embeddable value objects, service layer abstraction, factory pattern for complex creation, adapter pattern for backward compatibility) are applicable to any system requiring precision calculations and multi-tier customer segmentation.

Relevant Industries

This case study demonstrates capabilities applicable to these sectors

Ready to eliminate your technical debt?

Transform unmaintainable legacy code into a clean, modern codebase that your team can confidently build upon.