A production-grade High-Frequency Trading (HFT) Simulation engine built on .NET 9 and .NET Aspire, demonstrating enterprise-level microservices architecture with extreme performance optimizations for financial systems.
FalconFX simulates a complete electronic trading ecosystem capable of processing 300,000+ orders per second with sub-millisecond latency. The system employs zero-allocation matching algorithms, lock-free data structures, and event-driven architecture to achieve high throughput while maintaining reliability and observability.
flowchart LR
%% PERSON
user[Market Participant<br/><sub>Trading clients submitting orders</sub>]
%% GROUPS (BOUNDARIES)
subgraph aspire[.NET Aspire Orchestration]
apphost[AppHost<br/><sub>.NET Aspire<br/>Orchestrates all services</sub>]
end
subgraph services[Microservices]
marketmaker[MarketMaker<br/><sub>.NET Worker<br/>Generates synthetic order flow</sub>]
engine[MatchingEngine<br/><sub>.NET gRPC/Kafka<br/>Zero-allocation OrderBook</sub>]
processor[TradeProcessor<br/><sub>.NET Worker<br/>Persists trades & updates tickers</sub>]
end
subgraph infra[Infrastructure]
kafka[(Kafka<br/><sub>Event Streaming<br/>Order/Trade topics</sub>)]
postgres[(PostgreSQL<br/><sub>Time-Series DB<br/>Trade persistence</sub>)]
redis[(Redis<br/><sub>In-Memory Cache<br/>Real-time tickers</sub>)]
end
dashboard[Aspire Dashboard<br/><sub>Observability: Metrics, Traces, Logs</sub>]
%% RELATIONSHIPS
user -->|Submits orders via gRPC| engine
marketmaker -->|Produces orders| kafka
kafka -->|Streams orders| engine
engine -->|Publishes trades| kafka
kafka -->|Streams trades| processor
processor -->|Bulk insert trades| postgres
processor -->|Updates ticker| redis
%% ASP.NET ASPIRE ORCHESTRATION
apphost -->|Orchestrates| marketmaker
apphost -->|Orchestrates| engine
apphost -->|Orchestrates| processor
apphost -->|Provisions| kafka
apphost -->|Provisions| postgres
apphost -->|Provisions| redis
%% TELEMETRY
marketmaker -->|OTLP| dashboard
engine -->|OTLP| dashboard
processor -->|OTLP| dashboard
sequenceDiagram
autonumber
participant MM as MarketMaker
participant K1 as Kafka (orders)
participant ME as MatchingEngine
participant K2 as Kafka (trades)
participant TP as TradeProcessor
participant PG as PostgreSQL
participant RD as Redis
MM->>K1: Produce Order (Protobuf)
activate K1
K1-->>ME: Consume Order
deactivate K1
activate ME
Note over ME: Zero-Alloc Matching<br/>OrderPool + Intrusive Lists
ME->>ME: Match Order
alt Trade Executed
ME->>K2: Produce Trade (Protobuf)
end
deactivate ME
activate K2
K2-->>TP: Consume Trade
deactivate K2
activate TP
TP->>TP: Batch (1000 trades)
par Parallel Processing
TP->>PG: Bulk Insert Trades
activate PG
PG-->>TP: Ack
deactivate PG
and
TP->>RD: Update Ticker (Fire-and-Forget)
end
deactivate TP
The core matching engine eliminates GC pressure through advanced memory management techniques:
- Struct-Based Order Pool: Pre-allocated array of
OrderNodestructs (10M capacity) prevents heap allocations during hot path execution - Intrusive Linked Lists: Orders maintain
NextandPrevindices instead of references, enabling O(1) insertion/removal without allocations - Price-Level Arrays: Fixed-size arrays for bid/ask levels (prices 90-110) enable constant-time price lookups
- Callback-Based Trade Publishing: Delegates avoid lambda allocations in tight loops
// Example: Zero-allocation order processing
public void ProcessOrder(Order order, TradeCallback onTrade)
{
// All operations use stack-allocated structs and array indices
// No 'new' keyword in the hot path!
}- System.Threading.Channels: Lock-free producer-consumer queues for order ingestion and trade publishing
- Batch Processing: TradeProcessor accumulates 1,000 trades before bulk database insert
- Kafka Optimization:
- LZ4 compression for network efficiency
- Leader acknowledgment for balanced durability/speed
- Configurable linger times for batching
- Redis Fire-and-Forget: Non-blocking ticker updates prevent I/O bottlenecks
- WaitForBrokerReady: Health checks ensure Kafka is fully initialized before service startup
- Topic Auto-Creation:
EnsureTopicExistsAsyncutility prevents "unknown topic" errors - Retry Policies: Graceful handling of transient failures in Kafka consumption
- Service Dependencies:
.WaitFor()ensures correct startup order (e.g., Engine waits for Kafka)
Complete distributed tracing and metrics via OpenTelemetry:
- Custom Metrics:
orders_processed,trades_createdcounters - Distributed Traces: End-to-end transaction visibility across Kafka and microservices
- Aspire Dashboard: Real-time visualization of all telemetry data
- Batched OTLP Export: 5-second intervals prevent telemetry overhead
Based on production-equivalent load testing:
| Metric | Value | Notes |
|---|---|---|
| Order Throughput | 300,000+ orders/sec | Sustained load with tight price spreads |
| Matching Latency | < 1ms (p99) | Hot path execution time |
| Trade Persistence | 1,000 trades/batch | Bulk insert to PostgreSQL |
| Memory Allocation | ~0 bytes/order | Zero-alloc matching algorithm |
| GC Pressure | Minimal | Gen0 only, no Gen1/Gen2 collections |
Tested on: AMD Ryzen 7 / 16GB RAM / Docker Desktop
FalconFX/
βββ src/
β βββ FalconFX.AppHost/ # .NET Aspire orchestrator
β β βββ Program.cs # Infrastructure provisioning
β βββ FalconFX.ServiceDefaults/ # Shared telemetry & Kafka utilities
β β βββ Extensions.cs # OpenTelemetry configuration
β β βββ KafkaUtils.cs # Broker health checks
β βββ MarketMaker/ # Order generation service
β β βββ Worker.cs # Synthetic order producer
β βββ MatchingEngine/ # Core matching logic
β β βββ EngineWorker.cs # Order processing loop
β β βββ KafkaWorker.cs # Kafka consumer
β β βββ OrderBook.cs # Zero-alloc matching algorithm
β β βββ OrderPool.cs # Pre-allocated memory pool
β β βββ Models/ # Order, Trade, OrderNode structs
β βββ TradeProcessor/ # Trade persistence service
β βββ Worker.cs # Kafka-to-DB pipeline
β βββ TradeDbContext.cs # EF Core context
βββ tests/
βββ MatchingEngine.Tests/ # Unit tests
- Produces synthetic orders at high velocity (100 orders per batch)
- Tight price spread (99-101) ensures frequent matches
- Uses Protobuf for efficient serialization
- Publishes to
ordersKafka topic
- Consumes orders from Kafka (
orderstopic) - Implements Price-Time Priority matching algorithm
- Publishes executed trades to Kafka (
tradestopic) - Custom metrics exported to Aspire Dashboard
- Supports both Kafka and gRPC ingestion
- Consumes trades from Kafka (
tradestopic) - Batch inserts to PostgreSQL (1,000 trades/batch)
- Updates Redis tickers in fire-and-forget mode
- TimescaleDB-ready schema with timestamp indexing
-
Clone the repository
git clone https://github.com/yourusername/FalconFX.git cd FalconFX -
Launch the entire system
cd src/FalconFX.AppHost dotnet run -
Access the Aspire Dashboard
The dashboard URL will be displayed in the console (typically
https://localhost:17171)- Structured Logs: Real-time log aggregation from all services
- Metrics: Live charts for order throughput and trade volume
- Traces: Distributed transaction timelines
- Resources: Container health and resource usage
-
Explore Infrastructure UIs
- Kafka UI:
http://localhost:8080- Topic inspection and consumer lag monitoring - pgAdmin:
http://localhost:5050- PostgreSQL query interface - Redis Commander:
http://localhost:8081- Real-time key-value viewer
- Kafka UI:
Watch for these log messages indicating successful startup:
β
Kafka is READY. Found 1 brokers.
β
Topic 'orders' created successfully.
β
Topic 'trades' created successfully.
π Engine Started. Waiting for orders...
π Starting Producer...
πΎ Trade Processor Started. Listening...
π STATS: Processed: 300,000 orders | Matches: 150,000 trades
LingerMs = 5, // Batch window
BatchSize = 1MB, // Max batch size
CompressionType = Lz4, // Compression algorithm
Acks = Leader // Durability levelAdjust the OrderPool capacity based on expected concurrent orders:
private readonly OrderBook _orderBook = new(10_000_000); // 10M ordersOptimize for database write performance:
private const int BatchSize = 1000; // Trades per DB flushRun unit tests for the matching engine:
cd tests/MatchingEngine.Tests
dotnet testTest Coverage:
- β Full match scenarios
- β Partial fill logic
- β Price-Time Priority enforcement
- β Order book depth validation
orders_processed(Counter): Total orders consumed by MatchingEnginetrades_created(Counter): Total trades executed
Distributed traces include custom spans for:
- Order deserialization
- Matching algorithm execution
- Trade serialization and publishing
- Live Metrics: Real-time graphs of throughput and latency
- Distributed Traces: End-to-end request tracking across Kafka
- Structured Logs: Centralized logging with filtering and search
- Resource Health: Container status and dependency graph
| Component | Technology | Purpose |
|---|---|---|
| Framework | .NET 9 | Latest performance improvements |
| Orchestration | .NET Aspire 13.0 | Service orchestration and observability |
| Messaging | Apache Kafka | Event streaming backbone |
| Caching | Redis | Real-time ticker storage |
| Database | PostgreSQL | Time-series trade persistence |
| Serialization | Protobuf | Efficient binary encoding |
| Telemetry | OpenTelemetry | Distributed tracing and metrics |
| Containerization | Docker | Consistent deployment environments |
This project demonstrates:
- Microservices Architecture: Service decomposition, communication patterns
- Event-Driven Design: Kafka producers/consumers, event sourcing
- Performance Engineering: Zero-allocation algorithms, memory pooling
- Observability: OpenTelemetry instrumentation, distributed tracing
- .NET Aspire: Modern cloud-native orchestration
Contributions are welcome! Areas for enhancement:
- gRPC streaming client for external order submission
- WebSocket API for real-time ticker broadcasting
- Advanced order types (Stop, FOK, IOC)
- Market depth visualization dashboard
- Kubernetes deployment manifests
This project is licensed under the MIT License - see the LICENSE file for details.
- Aspire Team for the excellent orchestration framework
- Confluent for Kafka client libraries
- PostgreSQL and Redis communities for robust infrastructure
Built with β€οΈ using .NET 9 and Aspire | Report Issues | Documentation