Lux Docs
Regulated

Alternative Trading System (ATS)

Build an SEC-registered ATS with CLOB matching, compliance, and multi-venue routing

An Alternative Trading System (ATS) is an SEC-registered trading venue that matches buyer and seller orders internally. Unlike a national securities exchange, an ATS operates under Regulation ATS (Rules 300-303) and files Form ATS-N. It must maintain at least $250,000 in net capital and comply with fair access, order display, and record-keeping obligations.

The Lux stack provides every technical component needed to build and operate an ATS.

Architecture

An ATS uses all four core modules:

                     ┌────────────────────┐
                     │      atsd          │
                     │     :8080          │
                     └─────────┬──────────┘

          ┌────────────────────┼────────────────────┐
          │                    │                    │
   ┌──────┴──────┐     ┌──────┴──────┐     ┌──────┴──────┐
   │   Broker    │     │  CEX Engine │     │ Compliance  │
   │  Providers  │     │  CLOB Match │     │  KYC / AML  │
   └──────┬──────┘     └──────┬──────┘     └─────────────┘
          │                    │
   16 external venues    Internal order book
  • Broker provides external venue connectivity for routing unmatched orders and sourcing liquidity
  • CEX Engine runs the central limit order book that matches orders internally
  • Compliance screens every participant (KYC) and every transaction (AML) before execution

Step-by-Step Build

1. Create Go project

mkdir atsd && cd atsd
go mod init github.com/yourorg/atsd
go get github.com/hanzoai/base
go get github.com/luxfi/broker
go get github.com/luxfi/cex
go get github.com/luxfi/compliance

2. Register providers from environment

import (
    "github.com/luxfi/broker/pkg/provider"
    "github.com/luxfi/broker/pkg/provider/envconfig"
)

registry := provider.NewRegistry()
n := envconfig.RegisterFromEnv(registry)
slog.Info("providers ready", "count", n)

RegisterFromEnv reads environment variables for all 16 providers and registers whichever ones have credentials set. See Provider Integration for the full list.

3. Create the broker server

import "github.com/luxfi/broker/pkg/api"

brokerSrv := api.NewServer(api.Config{
    Registry:   registry,
    ListenAddr: ":8090",
})

4. Create the CEX matching engine

import "github.com/luxfi/cex/pkg/engine"

eng := engine.New(engine.Config{
    EnableSurveillance: true,
    EnableAudit:        true,
})

5. Register markets

import "github.com/luxfi/cex/pkg/markets"

// Auto-discover tradable assets from all connected providers
markets.RegisterFromProviders(ctx, registry, eng)

// Add explicit default markets with custom fee schedules
markets.RegisterDefaults(eng, []markets.DefaultMarket{
    {Symbol: "BTC-USD", Base: "BTC", Quote: "USD", Class: types.AssetClassCrypto,
     Tick: "0.01", Lot: "0.00001", MakerFee: "0.001", TakerFee: "0.002"},
    {Symbol: "ETH-USD", Base: "ETH", Quote: "USD", Class: types.AssetClassCrypto,
     Tick: "0.01", Lot: "0.0001", MakerFee: "0.001", TakerFee: "0.002"},
})

RegisterFromProviders queries every registered provider for tradable assets and adds them to the matching engine. RegisterDefaults adds specific markets with explicit parameters (tick size, lot size, fees).

6. Install compliance rules

import (
    "github.com/luxfi/compliance/pkg/aml"
    "github.com/luxfi/compliance/pkg/jube"
)

// Install FinCEN-mandated baseline rules
monitoringSvc := aml.NewMonitoringService()
aml.InstallDefaultRules(monitoringSvc)

// Optional: connect Jube for ML-based transaction scoring
jubeClient := jube.NewClient(jube.Config{
    BaseURL:  os.Getenv("JUBE_BASE_URL"),
    ModelID:  os.Getenv("JUBE_MODEL_ID"),
    FailOpen: false, // reject transactions when Jube is unreachable
    Timeout:  5 * time.Second,
})

InstallDefaultRules adds three mandatory FinCEN rules:

  • CTR threshold: flag transactions over $10,000 (31 CFR 1010.311)
  • Structuring: detect $9,000-$9,999 patterns (31 USC 5324)
  • Velocity: block if 24h volume exceeds $50,000 without enhanced KYC

7. Mount and serve

import "github.com/hanzoai/base"

srv := base.NewServer(base.Config{
    Addr: ":8080",
})
srv.Mount("/broker", brokerSrv.Handler())
srv.Mount("/exchange", cexGateway.Handler())
srv.Mount("/compliance", complianceSrv.Handler())
srv.ListenAndServe()

Minimal ATS main.go

package main

import (
    "context"
    "log/slog"
    "os"
    "time"

    "github.com/hanzoai/base"
    "github.com/luxfi/broker/pkg/api"
    "github.com/luxfi/broker/pkg/provider"
    "github.com/luxfi/broker/pkg/provider/envconfig"
    "github.com/luxfi/cex/pkg/engine"
    "github.com/luxfi/cex/pkg/gateway"
    "github.com/luxfi/cex/pkg/markets"
    "github.com/luxfi/compliance/pkg/aml"
    "github.com/luxfi/compliance/pkg/jube"
    compapi "github.com/luxfi/compliance/pkg/api"
)

func main() {
    ctx := context.Background()

    // 1. Providers
    registry := provider.NewRegistry()
    n := envconfig.RegisterFromEnv(registry)
    slog.Info("providers registered", "count", n)

    // 2. CEX matching engine
    eng := engine.New(engine.Config{EnableSurveillance: true, EnableAudit: true})
    markets.RegisterFromProviders(ctx, registry, eng)

    // 3. Compliance
    monSvc := aml.NewMonitoringService()
    aml.InstallDefaultRules(monSvc)

    if url := os.Getenv("JUBE_BASE_URL"); url != "" {
        jc := jube.NewClient(jube.Config{
            BaseURL: url, ModelID: os.Getenv("JUBE_MODEL_ID"),
            FailOpen: false, Timeout: 5 * time.Second,
        })
        monSvc.SetExternalScorer(jc)
    }

    // 4. HTTP servers
    brokerSrv := api.NewServer(api.Config{Registry: registry})
    cexGw := gateway.New(gateway.Config{Engine: eng})
    compSrv := compapi.NewServer(compapi.Config{Monitoring: monSvc})

    // 5. Mount on base and serve
    srv := base.NewServer(base.Config{Addr: ":8080"})
    srv.Mount("/broker", brokerSrv.Handler())
    srv.Mount("/exchange", cexGw.Handler())
    srv.Mount("/compliance", compSrv.Handler())

    slog.Info("ATS starting", "addr", ":8080", "providers", n)
    if err := srv.ListenAndServe(); err != nil {
        slog.Error("server error", "error", err)
        os.Exit(1)
    }
}

Configuration

VariableRequiredDescription
At least one provider key pairYesSee Provider Integration
JUBE_BASE_URLNoJube transaction monitoring endpoint
JUBE_MODEL_IDNoJube EntityAnalysisModel GUID
COMPLIANCE_API_KEYYesAPI key for compliance endpoints
JUMIO_API_TOKENRecommendedJumio IDV for subscriber KYC
BROKER_API_KEYYesAPI key for broker endpoints

SEC Compliance Checklist

An ATS operating under Regulation ATS must:

  • File Form ATS-N with the SEC before commencing operations
  • Register as a broker-dealer (Form BD) with FINRA
  • Maintain minimum $250,000 net capital (Rule 15c3-1)
  • Implement fair access policies (Rule 301(b)(5)) if volume exceeds 5%
  • Display best-priced orders (Rule 301(b)(3)) if volume exceeds 5%
  • Maintain records of all orders, executions, and cancellations
  • File quarterly reports on Form ATS-R
  • Implement and test business continuity / disaster recovery plans
  • Screen all subscribers via KYC/AML before granting access
  • Monitor all transactions for CTR, structuring, and suspicious activity
  • File SARs within 30 days of detection

Docker Deployment

docker build --platform linux/amd64 -t ghcr.io/yourorg/atsd:latest .
docker run -p 8080:8080 \
  -e ALPACA_API_KEY=pk_... \
  -e ALPACA_API_SECRET=sk_... \
  -e COMPLIANCE_API_KEY=... \
  -e JUMIO_API_TOKEN=... \
  -e JUMIO_API_SECRET=... \
  ghcr.io/yourorg/atsd:latest

Reference

See liquidityio/ats for a working ATS implementation built on these components.

On this page