← Back to blog
Anurag Kotha

How We Exposed a MATLAB Quantitative Platform Through REST APIs

A technical overview of aiqsaas, the REST-to-MATLAB service boundary that gave customers secure access to a mature quantitative engine.

#MATLAB#FastAPI#System Architecture#API Design#Quantitative Finance#Docker

How We Exposed a MATLAB Quantitative Platform Through REST APIs

VersaQuant was a small quantitative-finance startup with a valuable constraint: much of the quantitative engine already existed in MATLAB. It contained portfolio construction, risk, analytics, and data-processing capabilities that had grown with the product. Customers, however, could not be expected to run MATLAB or integrate directly with an internal numerical environment. They needed authenticated web APIs that fit ordinary applications.

There were three possible directions. The engine could be rewritten in another language, served through MATLAB Production Server (MPS), or placed behind a custom service boundary. A rewrite would have duplicated mature domain logic and introduced a long validation cycle. MPS offered a supported serving model, but its additional licensing and operating model did not fit the constraints of a small startup that needed control over authentication, transport, caching, and deployment.

We chose the third option and built aiqsaas: a FastAPI gateway in front of a compiled MATLAB Runtime application. The gateway exposed REST APIs, verified Auth0 tokens, applied permissions and rate limits, injected trusted user context, and translated HTTP requests into a compact command protocol. The compiled application retained responsibility for quantitative execution. An embedded Java WebSocket server connected the two layers.

This architecture allowed the quantitative engine to remain in MATLAB while customers consumed secure JSON APIs. The source code is proprietary, so the examples below use simplified pseudocode and omit quantitative business logic. The request path and system behavior are based on the implementation.

An abstract service boundary connecting an API gateway to a quantitative computation core.

The service boundary separated customer-facing web concerns from quantitative execution without requiring a rewrite of the engine.

Why a Service Boundary Fit the Problem

A mature quantitative codebase is more than a collection of equations. It accumulates validation rules, data conventions, numerical edge cases, model assumptions, and operational knowledge. Reimplementing the same functions in Python would not simply change syntax; it would create a second engine whose behavior had to be reconciled with the first.

At the same time, the MATLAB runtime was not an appropriate public boundary. Customer-facing access required:

  • REST endpoints and JSON responses
  • Auth0 authentication and permission checks
  • Organization and user context
  • Rate limiting and CORS controls
  • OpenAPI documentation
  • Isolation between web concerns and quantitative functions
  • Deployment through the existing container infrastructure

The service boundary assigned these concerns to the technologies best suited to them. FastAPI handled the internet-facing contract. The compiled MATLAB application handled numerical execution. The transport between them remained intentionally small.

Avoiding MPS was not a claim that a custom server had no cost. The custom path transferred responsibility for request handling, concurrency, failure behavior, and observability to the application. It was chosen because it avoided an additional production-serving dependency and gave the startup direct control over the behavior needed by its product.

System Architecture

The deployment used two primary services in a Docker Compose stack:

  • aiqpyapi — a Python/FastAPI gateway running on uvicorn.
  • vqserver — a compiled MATLAB Runtime application containing the execution engine.

The Java WebSocket server was not a third service. It was a third-party library embedded inside vqserver and shared the lifecycle of the MATLAB process. Java owned the socket and connection registry; MATLAB owned the callbacks, dispatch, and computation.

The split kept web policies out of the numerical engine. Authentication rules, endpoint behavior, rate limits, and HTTP response handling could evolve in Python without changing MATLAB computation. Quantitative functions could evolve inside the compiled package without adding a Java handler for each capability.

The Command Contract

The primary REST path was translated into a compact JSON command:

{
  "action": 3,
  "name": "pa.getSAA",
  "data": [
    "authenticated-user-id",
    {
      "organization": "customer-organization",
      "permissions": ["read:portfolio"]
    },
    [5, 3]
  ],
  "reqID": 42
}
FieldPurpose
actionSelects an execution path inside MATLAB
nameIdentifies the function within a permitted package namespace
dataCarries trusted identity context followed by function arguments
reqIDOptionally correlates the request with internal processing

The caller did not provide the trusted identity entries. FastAPI validated the bearer token, extracted the Auth0 subject and user information, and inserted both at the front of data. The convention for the main path was therefore:

data{1}   = userID
data{2}   = userInfo and permission context
data{3..} = function arguments

This made identity part of the standard MATLAB wrapper contract while keeping token verification at the HTTP boundary.

End-to-End Request Flow

A normal request crossed the system in five stages.

1. Authenticate and authorize

FastAPI received the HTTP request and validated the Auth0 JWT against the configured issuer, audience, and claims. Endpoint-level permissions and rate limits were applied before a command reached the calculation engine.

2. Build and send the command

The gateway parsed the caller's arguments, prefixed the trusted user context, and serialized {action, name, data, reqID}. It opened a WebSocket connection to ws://vqserver:50000 over the Compose network and sent one JSON text frame.

The Python path waited for one response with a configured limit of 60 seconds.

3. Enter the compiled runtime

The Java WebSocket server received the frame and raised a MATLAB callback. The callback stored connection metadata keyed by the socket's hash code so that a completed result could be returned to the correct caller.

4. Select the execution mode

When parallel execution was enabled, the callback submitted processJsonCommand to the MATLAB pool with parfeval. When it was disabled, the dispatcher ran inline on the main MATLAB thread. The external API contract did not change between the two modes.

5. Dispatch, encode, and return

The dispatcher decoded the command, selected a handler, performed the calculation or management operation, and encoded the result. The response was routed through Java over the same WebSocket connection. Python parsed the single JSON frame once and returned the native object as the HTTP response.

processJsonCommand: The Request-Management Core

processJsonCommand.m was the central control point for incoming engine requests. Its role extended beyond calling a MATLAB function. It managed the protocol boundary and the lifecycle of a request inside the runtime:

  • Decode the command and normalize its fields
  • Enforce maintenance-mode restrictions
  • Route action codes to the correct execution path
  • Validate the identity context used by the main path
  • Generate and query response-cache keys
  • Resolve and execute package functions dynamically
  • Translate MATLAB exceptions into transportable responses
  • Encode results for the WebSocket boundary
  • Surface internal deferred work to the reply manager

Maintenance and action routing

The command was first decoded into action, data, name, and optional reqID. A maintenance-mode gate rejected normal work while continuing to permit the management actions needed to inspect or control the engine.

The subsequent switch routed the request:

ActionRouteResponsibility
1EchoProtocol/connection check
2Demo handlerDemonstration response
3aiqw.<name>Primary quantitative API path
4Data blotter handlerReal-time market-data operation
5aiqwlab.<name>Lab and universe updates
900–902Administrative handlersHard quit, graceful shutdown, and history dump
903aiqwbatch.runJobBatch execution
904Engine statusCalculation-engine readiness

Unknown actions were converted into an error response rather than falling through to arbitrary execution.

The primary action path

Action 3 performed the main request-management sequence:

  1. Validate that the incoming data contains the expected caller context.
  2. Separate userID, userInfo, and the actual function arguments.
  3. Derive an MD5 cache key from the function name, request arguments, and user identity.
  4. Query MongoDB for a previously stored response.
  5. On a cache miss, construct aiqw.<name> and resolve it with str2func.
  6. Execute the function with feval, passing the complete data cell.
  7. Extract an internal fnRequest if the wrapper returned deferred work.
  8. Save eligible successful results according to the cacheability policy.
  9. Catch execution failures and translate the MATLAB exception into a JSON-serializable error structure.
  10. Encode the inner result for transport and attach any internal deferred-work metadata.

This arrangement concentrated request policy in one module. The WebSocket layer transported frames but did not interpret the command. Individual quantitative wrappers implemented domain behavior but did not need to manage routing, cache lookup, or reply delivery.

Response and deferred-work handling

The dispatcher returned an internal structure containing the encoded result and function metadata. Before transmission, the reply manager removed the internal fnName envelope and sent the inner JSON character vector as one WebSocket text frame. The Python client therefore performed one JSON parse; the customer did not receive a double-encoded document.

If the result contained fnRequest, the reply manager removed it from the public payload. The immediate result was returned to the gateway, and the referenced longer operation was submitted to the MATLAB pool outside the HTTP request.

This mechanism was useful for acknowledgement-first workflows, but it was not a durable job queue. The deferred function shared the lifecycle of the MATLAB process and did not provide persistent retries or queue-level failure recovery.

Dynamic Dispatch and Function Extension

The primary path constructed a function name inside the aiqw namespace:

fn = str2func(['aiqw.' name]);
result = feval(fn, data);

For example, a wrapper placed at:

+aiqw/
    +portfolio/
        calculateRisk.m

could be addressed as portfolio.calculateRisk. In development it became available when present on the MATLAB path. A compiled deployment required rebuilding the standalone application and redeploying vqserver, but it did not require a matching Java handler or an additional dispatcher registration.

The literal package prefixes confined this route to namespaces such as aiqw.*, aiqwlab.*, and aiqwbatch.*. That constraint prevented a caller from directly naming an arbitrary base-MATLAB function, but namespace confinement was not treated as authorization. Auth0 permissions were enforced at the API boundary, and business wrappers could apply user and organization rules.

Response Caching

Repeated quantitative requests could be expensive even when their effective inputs were identical. The action 3 path therefore checked a MongoDB-backed response cache using a key derived conceptually from:

function name + function arguments + user identity

Including the user identity separated cached results between customers. A cacheability policy excluded functions whose outputs were time-sensitive or had side effects. The design reduced duplicate computation for eligible requests while leaving invalidation and function behavior as explicit operational concerns.

Serial and Parallel Execution

The same server could run with or without a MATLAB parallel pool.

In serial mode, the Java-triggered MATLAB callback called processJsonCommand on the main MATLAB thread and immediately routed the reply. This supported development, testing, and single-core or batch environments.

In parallel mode, each incoming command was submitted with parfeval. A timer-driven reply pump collected completed futures with fetchNext and used the stored connection hash to return each result. This allowed the calculation engine to use multiple MATLAB workers without changing the REST or command contracts.

The execution selector belonged to the runtime configuration rather than the API. Customers called the same endpoints regardless of how the engine used the available host resources.

Operational Trade-offs

The custom serving layer provided the control the startup needed, but that control came with responsibilities that MPS or another managed serving product might otherwise absorb.

  • Request shape: the original primary endpoints accepted JSON through path parameters. Explicit POST models would provide stronger validation and clearer versioning.
  • Cancellation: the Python gateway stopped waiting after 60 seconds, but the MATLAB future did not receive a corresponding deadline or cancellation signal.
  • Callable policy: package prefixes constrained dynamic dispatch, while the reviewed function-name allowlist remained incomplete. A generated manifest would provide stronger defense in depth.
  • Gateway concurrency: one synchronous uvicorn worker could block while waiting for MATLAB. Async WebSocket I/O and multiple workers would create a clearer concurrency model.
  • Observability and testing: request identifiers, structured logs, and contract tests were limited across the Python, Java, JSON, and MATLAB boundary.

These limitations do not change the architectural purpose of the platform. They identify the engineering surface created by owning a custom service boundary and the areas that would need strengthening as usage and operational requirements grew.

What the Platform Enabled

aiqsaas gave a small startup a practical route from an internal MATLAB engine to a customer-facing service.

Customers could call authenticated REST APIs without installing MATLAB or understanding the calculation runtime. The FastAPI layer supplied the public security boundary, including Auth0 identity, permissions, rate limits, and trusted user-context injection. The compiled runtime preserved the existing quantitative implementation and its domain conventions.

The command protocol also created a repeatable extension path. A new quantitative wrapper could use the same authentication, dispatch, caching, execution, and reply infrastructure rather than requiring a new cross-language integration for every capability.

Most importantly, the architecture let the company expose its quantitative engine securely while retaining control over deployment and avoiding an additional MPS serving dependency. It solved the immediate business problem without forcing a rewrite of the system that already carried the quantitative expertise.

The central design principle was:

Keep the quantitative domain where it already works, and build a secure service boundary around it.


For the shorter origin story behind this project, see the earlier AIQSaaS overview on LinkedIn.

Tweaks

Theme