Skip to content

Architecture

VETA is a multi-service trading platform connected by a Redpanda message bus (Kafka-compatible). The React frontend talks to a single API Gateway, which is the only service the browser can reach. Everything else communicates via bus topics.

The :5xxx port numbers in the diagrams below are the dev container and docker compose internal ports. They are not exposed publicly in production: external traffic enters through the Traefik ingress and is routed to services over the internal Docker network. The public-ingress diagram in the next section uses real public-facing ports (:443).

The architecture is split into focused diagrams below. Each one fits the text column at readable size; the previous single-diagram view became unreadable as the service count grew. Click any diagram to expand.

Public ingress: how traffic reaches the platform

Section titled “Public ingress: how traffic reaches the platform”
graph LR
USER["Internet user"]:::client
EDGE["Edge server<br/><edge-server><br/><i>Traefik :443 + LE TLS</i>"]:::edge
TUNNEL["Secure tunnel<br/><i>dialled OUT from server</i>"]:::edge
HL["Server Traefik :443<br/>private LAN, no inbound NAT"]:::gateway
SVC["frontend / gateway / etc."]:::support
USER -->|"HTTPS"| EDGE
EDGE -->|"localhost:18443"| TUNNEL
TUNNEL -->|"private LAN :443"| HL
HL -->|"PathPrefix routing"| SVC
classDef client fill:#818cf8,stroke:#6366f1,color:#fff
classDef edge fill:#a78bfa,stroke:#7c3aed,color:#fff
classDef gateway fill:#f59e0b,stroke:#d97706,color:#000
classDef support fill:#94a3b8,stroke:#64748b,color:#000

The server is on a private LAN with no inbound NAT. Public traffic reaches it through a secure tunnel dialled out from the server to the edge server, which terminates TLS and forwards into the tunnel.

A separate synthetic probe runs on the edge server every 60s to catch outages anywhere along this chain. Full detail: Edge architecture.

graph TD
FE["React Frontend<br/><i>Vite / Electron</i>"]:::client
GW["API Gateway :5011<br/><i>BFF, WebSocket hub + HTTP proxy</i>"]:::gateway
KAFKA["Redpanda Message Bus :9092<br/><i>Kafka-compatible pub/sub</i>"]:::bus
SVCS["~30 backend services<br/><i>see diagrams below</i>"]:::support
FE <-->|"WebSocket + HTTP"| GW
GW <-->|"pub/sub"| KAFKA
KAFKA <--> SVCS
classDef client fill:#818cf8,stroke:#6366f1,color:#fff
classDef gateway fill:#f59e0b,stroke:#d97706,color:#000
classDef bus fill:#64748b,stroke:#475569,color:#fff
classDef support fill:#94a3b8,stroke:#64748b,color:#000

The frontend talks only to the gateway. The gateway is the only externally reachable service. Everything else communicates via Kafka topics.

graph LR
KAFKA["Redpanda :9092"]:::bus
OMS["OMS :5002<br/><i>Validation + routing</i>"]:::trading
RISK["Risk Engine :5032<br/><i>6 pre-trade checks</i>"]:::risk
EMS["EMS :5001<br/><i>Execution + FIX bridge</i>"]:::trading
FIXEX["FIX Exchange :9880<br/><i>Matching engine</i>"]:::fix
JOURNAL["Journal :5009<br/><i>PostgreSQL store</i>"]:::storage
FIXARC["FIX Archive :5012<br/><i>Execution reports</i>"]:::storage
KAFKA -->|"orders.new"| OMS
OMS -->|"POST /check"| RISK
OMS -->|"orders.routed"| KAFKA
KAFKA -->|"orders.routed"| EMS
EMS --> FIXEX
KAFKA -->|"orders.*"| JOURNAL
EMS -->|"exec reports"| FIXARC
classDef bus fill:#64748b,stroke:#475569,color:#fff
classDef trading fill:#22c55e,stroke:#16a34a,color:#000
classDef risk fill:#ef4444,stroke:#dc2626,color:#fff
classDef fix fill:#f472b6,stroke:#ec4899,color:#000
classDef storage fill:#2dd4bf,stroke:#14b8a6,color:#000

Algo strategies (all 9 are independent Kafka consumers)

Section titled “Algo strategies (all 9 are independent Kafka consumers)”
graph TB
KAFKA["Redpanda :9092"]:::bus
subgraph row1[" "]
direction LR
LIMIT["LIMIT :5003"]:::algo
TWAP["TWAP :5004"]:::algo
POV["POV :5005"]:::algo
VWAP["VWAP :5006"]:::algo
ICEBERG["ICEBERG :5021"]:::algo
end
subgraph row2[" "]
direction LR
SNIPER["SNIPER :5022"]:::algo
AP["ARRIVAL_PRICE :5023"]:::algo
MOM["MOMENTUM :5025"]:::algo
IS["IS :5026"]:::algo
end
KAFKA --> row1
KAFKA --> row2
classDef bus fill:#64748b,stroke:#475569,color:#fff
classDef algo fill:#38bdf8,stroke:#0ea5e9,color:#000
style row1 fill:none,stroke:none
style row2 fill:none,stroke:none
graph LR
KAFKA["Redpanda :9092"]:::bus
MSIM["Market Sim :5000<br/><i>GBM price engine</i>"]:::market
MDS["Market Data :5015<br/><i>Alpha Vantage / Polygon</i>"]:::market
MDA["Adapters :5016<br/><i>Earnings + economic</i>"]:::market
ANA["Analytics :5014<br/><i>Black-Scholes / Monte Carlo</i>"]:::analytics
FE_ENG["Feature Engine :5017"]:::analytics
SIG["Signal Engine :5018"]:::analytics
REC["Recommendation :5019"]:::analytics
SCEN["Scenario Engine :5020"]:::analytics
MSIM --> KAFKA
MDS --> KAFKA
MDA --> KAFKA
KAFKA --> ANA
KAFKA --> FE_ENG
KAFKA --> SIG
KAFKA --> REC
KAFKA --> SCEN
classDef bus fill:#64748b,stroke:#475569,color:#fff
classDef market fill:#a78bfa,stroke:#8b5cf6,color:#000
classDef analytics fill:#fb923c,stroke:#f97316,color:#000
graph LR
KAFKA["Redpanda :9092"]:::bus
DARK["Dark Pool :5027"]:::fix
CCP["CCP :5028<br/><i>Central clearing</i>"]:::fix
RFQ["RFQ :5029<br/><i>Request for quote</i>"]:::fix
PRODUCT["Product Service :5030<br/><i>Structured products</i>"]:::fix
KAFKA --> DARK
KAFKA --> CCP
KAFKA --> RFQ
KAFKA --> PRODUCT
classDef bus fill:#64748b,stroke:#475569,color:#fff
classDef fix fill:#f472b6,stroke:#ec4899,color:#000
graph LR
GW["Gateway :5011"]:::gateway
KAFKA["Redpanda :9092"]:::bus
USER["User Service :5008<br/><i>OAuth2 + RBAC</i>"]:::storage
REPLAY["Session Replay :5031<br/><i>rrweb recordings</i>"]:::storage
NEWS["News :5013<br/><i>Sentiment scoring</i>"]:::support
LLM["LLM Advisory :5024<br/><i>Ollama</i>"]:::support
LLMW["LLM Worker :5033<br/><i>Inference worker</i>"]:::support
KRELAY["Kafka Relay :5007<br/><i>HTTP→Kafka</i>"]:::support
GW -->|"auth validate"| USER
GW --> REPLAY
KAFKA --> NEWS
KAFKA --> LLM
KAFKA --> LLMW
KAFKA --> KRELAY
classDef gateway fill:#f59e0b,stroke:#d97706,color:#000
classDef bus fill:#64748b,stroke:#475569,color:#fff
classDef storage fill:#2dd4bf,stroke:#14b8a6,color:#000
classDef support fill:#94a3b8,stroke:#64748b,color:#000
ColourGroupServices
🟣 PurpleClientReact Frontend
🟡 AmberGatewayAPI Gateway (BFF)
🟢 GreenTradingOMS, EMS
🔴 RedRiskRisk Engine
🔵 BlueAlgos9 algo strategies
🟣 VioletMarket DataMarket Sim, Market Data, Adapters
🟠 OrangeAnalyticsAnalytics, Feature/Signal/Recommendation/Scenario engines
🟢 TealStorageJournal, FIX Archive, User Service, Session Replay
🩷 PinkMicrostructureFIX Exchange, Dark Pool, CCP, RFQ, Product Service
⚪ GreySupportNews, LLM Advisory, LLM Worker, Kafka Relay
sequenceDiagram
actor Trader
participant FE as Frontend
participant GW as Gateway
participant Bus as Redpanda
participant OMS
participant Risk as Risk Engine
participant Algo as Algo Strategy
participant EMS
participant FIX as FIX Exchange
participant Journal
Trader->>FE: Submit order
FE->>GW: WebSocket submitOrder
GW->>Bus: orders.new
Bus->>OMS: orders.new
OMS->>OMS: Validate role, desk, limits
OMS->>Risk: POST /check
Risk-->>OMS: {allowed: true}
OMS->>Bus: orders.submitted
OMS->>Bus: orders.routed
Bus->>Algo: orders.routed
Algo->>Bus: orders.child (slice)
Bus->>EMS: orders.child
EMS->>FIX: Execute
FIX-->>EMS: Fill
EMS->>Bus: orders.filled
Bus->>Journal: Persist
Bus->>GW: Push to client
GW-->>FE: WebSocket orderFilled
FE-->>Trader: Blotter updated
CategoryTopics
Tradingorders.new, orders.submitted, orders.routed, orders.child, orders.filled, orders.expired, orders.rejected, orders.cancelled
Algoalgo.heartbeat
FIXfix.execution
Newsnews.feed, news.signal
Intelligencemarket.features, market.signals, market.recommendations

Sessions are stored as veta_user HTTP-only cookies set by the User Service via OAuth2 authorization-code flow with PKCE. The Gateway validates this cookie on every request (cached 10 seconds). The OMS independently fetches limits from the User Service (cached 30 seconds).

Roles: trader, desk-head, risk-manager, admin, compliance, sales, external-client, viewer. Only trader can submit orders.

See RBAC & Permissions and Trading Styles for details.