Skip to content

Portfolio Service

tien.nguyen edited this page Jul 30, 2026 · 1 revision

Portfolio Service

The Portfolio Service is the internal balance accounting and risk-seed authority of the Emporia Trading Platform. It owns fully funded cash and equity balance tracking per client, manages pre-trade exchange risk seeding, and enforces strict HTTP idempotency contracts for exchange matching engine snapshot deliveries (exchange-core).


πŸ“Œ Service Specifications

  • Directory: portfolio-service
  • HTTP Port: 8088 (Internal service boundary; unrouted by public API gateway)
  • Database Boundary: emporia_portfolio (PostgreSQL)
  • Primary Roles:
    • Maintain client cash and equity asset balances in portfolio_balance.
    • Supply client risk seed data for exchange-core matching engine initialization.
    • Process durable exchange portfolio snapshot receipts with SHA-256 digest idempotency validation.
    • Protect against concurrent balance replacement races via row-level pessimistic locking (SELECT ... FOR UPDATE).

πŸ—οΈ Architecture & Component Flow

sequenceDiagram
    autonumber
    participant ExchangeCore as Exchange-Core Simulator
    participant PortfolioCtrl as PortfolioController (:8088)
    participant ReceiptSvc as PortfolioReceiptService
    participant DB as PostgreSQL (emporia_portfolio)

    Note over ExchangeCore,PortfolioCtrl: Startup Phase: Risk Seed Retrieval
    ExchangeCore->>PortfolioCtrl: GET /internal/v1/portfolios/{clientId}/risk-seed
    PortfolioCtrl->>DB: Fetch client cash & asset positions
    DB-->>PortfolioCtrl: Balances & Last Transaction ID
    PortfolioCtrl-->>ExchangeCore: 200 OK (ValidatedPortfolioSnapshot)

    Note over ExchangeCore,PortfolioCtrl: Snapshot Phase: Durable Receipt Delivery
    ExchangeCore->>PortfolioCtrl: PUT /internal/v1/portfolio-snapshots/{deliveryId}/{clientId}<br/>Header: Idempotency-Key: {exchangeId}:{deliveryId}:{clientId}
    PortfolioCtrl->>ReceiptSvc: process(deliveryId, clientId, idempotencyKey, body)
    ReceiptSvc->>DB: Lock client portfolio_state (FOR UPDATE)
    ReceiptSvc->>DB: Check idempotencyKey & SHA-256 digest
    alt New Receipt
        ReceiptSvc->>DB: Replace portfolio_balance rows & Insert portfolio_receipt
        ReceiptSvc-->>PortfolioCtrl: Success (200 OK)
    else Identical Redelivery
        ReceiptSvc-->>PortfolioCtrl: Cached Receipt (204 No Content)
    else Conflicting Digest for Same Key
        ReceiptSvc-->>PortfolioCtrl: Idempotency Conflict (409 Conflict)
    end
Loading

πŸ”‘ Internal API Endpoints

Method Path Required Headers Description
GET /internal/v1/portfolios/{clientId}/risk-seed Authorization: Bearer <token> Returns current cash/asset balances and last transaction ID for exchange risk seeding.
PUT /internal/v1/portfolio-snapshots/{deliveryId}/{clientId} Idempotency-Key: {exchangeId}:{deliveryId}:{clientId}
Content-Type: application/json
Idempotently updates client balances from an exchange snapshot.

πŸ›‘οΈ Idempotency & SHA-256 Digest Contract

To prevent duplicate balance replacements or lost updates when exchange instances redeliver snapshots:

  1. Header Requirement: The caller must supply a unique Idempotency-Key header (e.g. XOSR:1004:101).
  2. Transactional Storage: The raw JSON payload and its computed SHA-256 digest are persisted into the portfolio_receipt table within the same transaction that updates portfolio_balance.
  3. Redelivery Outcomes:
    • New Key: Replaces client available balances and inserts a new receipt (200 OK).
    • Same Key + Identical SHA-256: Recognizes identical redelivery and returns 204 No Content without mutating database balances.
    • Same Key + Different SHA-256: Detects payload tampering/conflict and throws PortfolioIdempotencyConflictException (409 Conflict).

πŸ—„οΈ Database Schema (emporia_portfolio)

Table Name Entity / Description Key Columns
portfolio_state Primary client account anchor client_id (PK), first_transaction_id, created_at, updated_at
portfolio_balance Asset balances per client client_id (FK), asset_id (e.g. USD cash or stock ID), available_balance
portfolio_receipt Idempotency log delivery_id (PK), client_id, idempotency_key, sha256_digest, body

πŸ”’ Concurrency Protections

  • Pessimistic Row Locking: During receipt delivery, PortfolioReceiptService locks the client's portfolio_state row using SELECT ... FOR UPDATE.
  • Race Condition Prevention: Concurrent deliveries of the same snapshot event execute sequentially, resulting in exactly one balance application and one clean duplicate response without racing balance updates.
  • Verified by Integration Specs: Validated using real PostgreSQL 16 containers (TradingOrderPostgresConcurrencySpec & PortfolioReceiptPostgresSpec).

Clone this wiki locally