Free & Open · MariaDB 10.11 · Version 6

Portfolio Management System (PMS)
database schema

A complete MariaDB schema that sits between a portfolio manager and the OACT trade-execution platform. It handles order intake, execution routing, transaction settlement, daily NAV valuation, and fee management — end to end, fully auditable. Important: portfolio_core_v6 is not intended to be a complete investment platform. It is built with AI, the sole use-case being to provide a realistic test-scenario for OACT. Apart from OACT-integration, Only ad-hoc testing has been carried out. In any production scenario, it should be viewed as a good head-start, to be further developed and adapted it to a real-life business model, interfaces, and regulatory environment. portfolio_core_v6 is 100% SQL (MariaDB) and can be downloaded and used freely.

37 Tables
6 Reporting views
32 Stored procedures
Fully idempotent
SYSTEM OVERVIEW OPERATOR Fund Admin CSV INBOUND File intake MASTERDATA Discovery ORDERS DRAFT → EXPORTED CSV export OACT Executes trades confirm TRANSACTIONS Import & normalize NAV SNAPSHOT Daily valuation CHARGES Fee engine INTEGRATION AUDIT — integration_run + integration_error
Business Documentation
Overview

What does it do?

portfolio_core_v6 manages the complete lifecycle of portfolio orders — from initial order-intake, through trade execution, to back-office settlement and daily valuations. Everything is tracked, auditable, and safe to re-run.

📥

Receive order requests

Operators/interfaces submit order requests. The system registers, validates, and loads every order line into a staging table — idempotently.

→ See flow
🔍

(Optional) Discover & promote masterdata

New securities, channels, and order types found in the order requests are automatically surfaced as candidates and promoted to live reference tables.

→ Masterdata
📤

Submit orders to a trading venue

Approved orders are batched and submitted to a platform (e.g. OACT, or directly to a trading venue), which executes the actual trades.

→ Order lifecycle
📊

Daily NAV valuation

Each day the system aggregates transactions, market prices, and FX rates to compute each portfolio's total value (NAV) in its base currency.

→ Snapshot detail


  1. Customer places an order
    Orders can originate in a rebalancing or allocation module, or via a client interface (mobile/web). An "order" is, for example, a buy order for a specific amount (Currency or units) in a specific security (e.g. a mutual fund).
  2. Order is sent to portfolio_core_v6
    The frontend registers the order (sp_create_orders) in portfolio_core_v6. The order is validated (portfolio, security, channel, measurements, transaction type, and other necessary masterdata exists and is configured).
  3. Order is stored and enters lifecycle management
    portfolio_core_v6 creates a row in container_order and records an initial lifecycle event (CREATED). Idempotency checks ensure the same request cannot create duplicates.
  4. Order approval and batching
    Orders move to APPROVED status (manually or automatically). sp_prepare_order_batch groups approved orders into an order_batch ready for market execution. This stage is commonly known as "cut-off" (from the front-ends perspective).
  5. Orders exported (e.g. to OACT, but any Routing mechanism is fine)
    sp_write_oact_order_file exports the batch. Where OACT is used, a CSV order file arrives to the OACT operator directory. Orders are marked EXPORTED. Native transfers to/from portfolio_core_v6 are CSV files, but any format or protocol can be added (API, JSON, etc.).
  6. (OPTIONAL) OACT aggregates and routes orders
    OACT reads the file and performs:
    • aggregation of simliar orders
    • routing to the correct marketplace / transfer agent
    • submission for execution
    Refer to the OACT User Manual for specifics.
  7. Trading platform/Marketplace executes the trade
    The external Trading platform/Marketplace executes the buy order and returns a transaction confirmation (typically by API or file). Where OACT is used, OACT will redistribute the aggregated confirmations back to smaller transaction confirmations representing the underlying orders.
  8. Transaction confirmations imported back to portfolio_core_v6
    sp_run_oact_transaction_import loads the transactions. Rows are matched to orders via external_id.
  9. Transactions normalized and recorded
    portfolio_core_v6 creates:
    • container_transaction
    • container_transaction_component
    This represents cash movements, units purchased, fees, etc.
  10. Portfolio valuation updated
    Daily snapshot (sp_run_snapshot) combines:
    • transactions
    • security prices
    • FX rates
    to produce positions and valuations in container_security_position_daily, container_cash_balance_daily, and container_valuation_daily.
Result: The customer’s portfolio view updates with current valuations, based on holdings, prices, and cashflows.. A historical timeline of the valuations is maintained. Data now exists which can be used for Analysis, accounting, reconciliation, etc.

How it works

End-to-end flow

From order-intake to portfolio valuation — everything happens inside the database via stored procedures.

① Inbound order intake
Operator CSV/JSON/XML/API, etc.
Order information delivered to the system
CSV/JSON/XML/API, etc.
sp_register_
inbound_file
inbound_file
The delivery is registered with operator, path, and status. Duplicate submissions are silently skipped.
Table
sp_process_
inbound_file
Staging table
Each order is validated and loaded into inbound_order_request_stage.
Staging
Idempotency: Deliveries are identified by (source_system, request_id). Re-submitting the same delivery is safe — the system recognises and skips duplicates at every step.
② Masterdata discovery & promotion
Staged rows
Each inbound order triggers a discovery run over its staged order lines.
Input
sp_discover_
masterdata
Candidate tables
New channels, securities, order types, and measurement codes found in the delivery are written to candidate tables with an exists_in_masterdata flag.
Discovery
sp_upsert_
masterdata
Live config tables
Truly new items are (after approval, if required) inserted into security, config_channel, security_channel_map, etc. Existing records are updated non-destructively.
Promoted
Why this matters: Operators never have to pre-register new instruments. The first time a new security appears in an order, portfolio-core adds it automatically (further configurations may be required later on, though).
③ Order lifecycle

Each order line from staging becomes a container_order row, passing through a clear status progression before being passed to OACT or other recipient.

DRAFT
→ approve
APPROVED
→ batch
BATCHED
→ export CSV
EXPORTED
↺ reset if needed
APPROVED
container_order
One row per trade instruction. Carries security, amount, BUY/SELL direction, and a UUID external_id used to match OACT's confirmation back.
Core table
batch &
export
order_batch
Groups approved orders for one operator. Stores metadata and timestamps.
Export container
SELECT
INTO OUTFILE
Order transfer
Submission to the trading application (or OACT, written to /var/OACT/…/orders/) for execution.
Order → trading application
Full audit trail: Every status transition is recorded in order_lifecycle_event. A BEFORE UPDATE trigger auto-stamps approved_utc when an order is approved.
④ Transaction import (OACT confirmation)
OACT confirmation CSV
After executing trades, OACT returns a semicolon-delimited file at /var/OACT/…/transactions/. Contains 29 columns including prices, units, fees, and taxes.
File from OACT
register +
LOAD DATA
Staging
All 29 raw OACT columns are preserved as-is in oact_transaction_import_stage. No data is discarded.
Staging
sp_process_
oact_txns
container_transaction
Normalized trade event (BUY/SELL/FEE/TAX). Joined back to the original order via external_id. Broken into components: money, fees, taxes, rounding.
Core table
⑤ Daily NAV snapshot
Inputs
Transactions (units + cash), market prices from security_price_history, FX rates from fx_rate_history.
Daily data
sp_snapshot_
container_day
3 snapshot tables
Security positions (units × price), cash balances (balance × FX), and total valuation (securities + cash = NAV) — all upserted per container per day.
Upserted daily
6 reporting views
Current positions, cash balances, market values, NAV time-series, and order overview — ready to query directly.
Views
Snapshot is safe to re-run: All snapshot tables use UPSERT (INSERT … ON DUPLICATE KEY UPDATE), so multiple runs for the same date is harmless. For real-time snapshots, simply set a trigger on the container_transaction table and update affected containers (but consider performance implications first).
Core concepts

Key entities

Three business objects sit at the heart of the system.

📦 Container

A container is an investment portfolio or account, denominated in a base currency. It holds securities (positions) and cash, on behalf of one or more holders. Most of the system revolves around containers — orders, transactions, snapshots, and charges are all linked to a container.

👤 Holder

A holder is a person or legal entity. Holders are attached to containers via container_holder_role — a temporal table (with valid_from / valid_to) that tracks ownership over time. One container can have multiple holders with different roles.

📈 Security

A security is a tradable instrument. Each security has a unique code, a base currency, and can carry additional identifiers (ISIN, Bloomberg, etc.). The link between a security and an execution channel is stored in security_channel_map — this is how the system knows which OACT item code to use when exporting an order.

🔧 Operator

An operator (e.g. fundagg) is a registered administrator or aggregator authorised to submit files. The operator code ties inbound files, order batches, and transaction imports together and controls file-system path routing.

Fee engine

Charges & fees

The system can automatically apply recurring fees, taxes, bonuses, and kickbacks to containers on a schedule.

container_charge_scheme
Defines the rule: charge type (FEE/TAX/BONUS/KICKBACK), calculation method (FIXED or % of NAV), rate, and the validity window (valid_from / valid_to).
Config
sp_apply_container
_charges (scheduled)
container_charge_event
A realized posting on a specific date and amount. Idempotent — a scheme that already has an event for today is skipped.
Event
container_transaction
Each charge event also writes a container_transaction row, so fees flow through the same settlement and NAV pipeline as trades.
Feeds NAV
Audit

Full traceability

Every automated operation is wrapped in an audit record. Nothing runs silently.

integration_run

One row per orchestration call (ingest, export, snapshot). Records run_type_code, start time, finish time, status (STARTED / SUCCESS / ERROR), and a human-readable message.

→ IT detail

integration_error

Exceptions captured by EXIT HANDLER blocks inside each orchestration procedure. Linked to the integration_run that failed. Carries the entity name, key, and full error message.

→ IT detail

order_lifecycle_event

Append-only log for every status change on every order. Events: CREATED, APPROVED, PREPARED_FOR_EXPORT, EXPORTED, BATCH_RESET. Indexed by (order_id, event_utc) for per-order timelines.

→ IT detail

Trigger

trg_container_order_approved_utc fires BEFORE UPDATE on container_order. When the status transitions to APPROVED and approved_utc is NULL, it auto-stamps the timestamp — no application logic required.

IT Reference
IT Reference

Technical reference

MariaDB 10.11 · Definer: oact_admin@localhost · All text stored as utf8mb4. This section lists every stored procedure, view, and key table grouped by purpose.

Stored procedures

All orchestration procedures wrap their work in an integration_run / integration_error audit record and use EXIT HANDLER for error capture. All ingest procedures are idempotent.

ProcedureWhat it doesBusiness step
sp_register_inbound_file Registers an inbound file (any type) into inbound_file. Idempotent via (source_system, request_id) or (file_type_code, file_path). Returns the file ID as an OUT parameter. ① Intake
sp_process_inbound_file Reads an inbound_file record and dispatches it — for order-request files, creates the inbound_order_request_file metadata record and bulk-loads rows into staging via LOAD DATA INFILE. ① Intake
sp_discover_masterdata_candidates Scans staged rows for a given file, writes candidate records to five candidate tables, and sets exists_in_masterdata flags. ② Masterdata
sp_upsert_masterdata_from_candidates Promotes candidates to live config/security tables. Inserts new records, updates existing ones. Marks the masterdata_candidate_run as PROMOTED. ② Masterdata
sp_create_orders Creates one or more orders from a JSON array payload. Validates all reference data, resolves or creates the container_order_set, writes a CREATED lifecycle event. Idempotent on (source_system, request_id, request_item_no). ③ Orders
sp_create_order Single-order convenience wrapper around sp_create_orders. Accepts individual fields instead of a JSON array. ③ Orders
sp_create_orders_from_stage Reads all rows from inbound_order_request_stage for a given file and calls sp_create_orders for each. Resolves security via security_channel_map.oact_item. ③ Orders
sp_approve_container_orders Transitions DRAFT orders to APPROVED for one container (or all if NULL). Writes APPROVED lifecycle events. The trigger stamps approved_utc automatically. ③ Orders
sp_prepare_order_batch Creates an order_batch record for an operator, links all APPROVED orders to it, and transitions them to BATCHED. ③ Orders
sp_write_oact_order_file Writes the OACT-format CSV export for a batch using SELECT … INTO OUTFILE. Marks the batch EXPORTED and stamps exported_utc on all included orders. ③ Orders
sp_reset_order_batch Rolls back a batch: unlinks orders (sets batch_id = NULL), resets status to APPROVED, writes BATCH_RESET events. Allows re-export after an error. ③ Orders
sp_register_oact_transaction_file Registers an OACT transaction confirmation CSV into inbound_file and oact_transaction_import_file. ④ Transactions
sp_load_oact_transaction_stage Bulk-loads the raw OACT confirmation CSV into oact_transaction_import_stage (all 29 columns preserved). ④ Transactions
sp_process_oact_transactions Normalises staged rows into container_transaction + container_transaction_component. Joins via external_id to resolve container and cash account. Inserts component rows for money, fees, taxes, costs, and rounding. ④ Transactions
sp_snapshot_container_day The daily NAV engine. Upserts security positions, cash balances, and total valuation for all active containers on a given date. Safe to re-run. ⑤ NAV
sp_apply_container_charges Applies FIXED_AMOUNT charge events to every active scheme whose validity window covers the process date. Skips schemes already processed for that date. Charges
Functions
FunctionReturnsWhat it does
fn_get_latest_fx_rate DECIMAL(28,12) Most recent FX rate from fx_rate_history for a currency pair on or before a given date. Returns NULL if not found. Used by the snapshot engine.
fn_get_container_id BIGINT Resolves a container_code string to its internal container_id. Raises SQLSTATE 45000 if not found — prevents silent NULL propagation.
fn_get_security_id BIGINT Resolves a security_code string to its internal security_id. Same error-raising pattern as above.
Reporting views

All views are read-only surfaces over the snapshot and transaction tables. MariaDB does not support COMMENT= on views — descriptions live here and in the SQL script header.

ViewWhat it showsBusiness section
vw_container_total_value Most recent NAV per container: securities value + cash value + total, all in base currency. ⑤ NAV
vw_container_nav_timeseries Full history of daily NAV per container. Ready for time-series charting. ⑤ NAV
vw_container_security_market_value Latest security positions per container: units, market price, and market value. ⑤ NAV
vw_container_current_cash_positions Running cash balance per container and cash account (SUM of all transaction cash amounts). ④ Transactions
vw_container_current_security_positions Running unit balance per container and security (SUM of all transaction units). ④ Transactions
vw_orders Wide order view joining container, security, channel. Adds a draft_order_count window aggregate for pending-order dashboards. ③ Orders
Key tables

The 37 tables fall into a few clear groups. Reference/config tables are seeded once and rarely change.

Core domain

container · holder · security
container_cash_account
container_holder_role
security_channel_map
security_identifier

Orders & batches

container_order
container_order_set
order_batch
order_lifecycle_event

Transactions

container_transaction
container_transaction_component
oact_transaction_import_file
oact_transaction_import_stage

Snapshot / NAV

container_security_position_daily
container_cash_balance_daily
container_valuation_daily
security_price_history
fx_rate_history

Charges

container_charge_scheme
container_charge_event

Intake & audit

inbound_file
inbound_order_request_file
inbound_order_request_stage
integration_run
integration_error

Reference / config tables (seeded once): config_currency · config_channel · config_measurement · config_aggregatable_type · config_order_status · config_batch_status · config_charge_type · config_charge_calc_method · config_transaction_event_type · config_txn_component_type · config_role_type · operator_ref · masterdata_candidate_run + 4 candidate detail tables