Start now →

Building a Zero-Allocation Quantitative Trading Engine: The QuantFlow Journey

By Abhishek Pathak · Published May 14, 2026 · 8 min read · Source: Trading Tag
EthereumTradingMarket Analysis
Building a Zero-Allocation Quantitative Trading Engine: The QuantFlow Journey

Building a Zero-Allocation Quantitative Trading Engine: The QuantFlow Journey

Abhishek PathakAbhishek Pathak7 min read·1 hour ago

--

A deep dive into designing a high-performance backtester that eliminates garbage collection pressure and floating-point precision errors — one event at a time.

Press enter or click to view image in full size

The Problem: When Floating-Point Math Breaks Your Portfolio

Six months ago, I was debugging a trading strategy when I encountered something that would change how I think about financial software entirely.

My backtest was showing a realized PnL of $10,504.32. The paper trading account showed $10,504.31999999998.

That 0.00000000002 difference — less than a satoshi — shouldn’t matter in isolation. But it accumulated. Across thousands of trades, these rounding errors compound into systematic slippage that has no physical correspondence in real markets. It’s phantom P&L, and it haunts every float64-based trading system.

More critically: floating-point errors are non-deterministic across platforms and compiler versions. Your backtest on macOS Intel might differ from Linux x86. Past performance wouldn’t even be reproducible.

I realized then: a trading engine needs something stronger than convenience. It needs mathematical purity.

The Architecture Decision: Event-Driven + Fixed-Point

Instead of building another scrappy Python-based backtester, I set out to design QuantFlow — a zero-allocation, event-driven trading engine written in Go. The core idea was simple: if floating-point math breaks finance, eliminate it entirely.

Fixed-Point Arithmetic: Precision Without Floating-Point

Every monetary value in QuantFlow is represented as a scaled int64:

Real Value = Int64 / 10^8

This means $100.50 is internally stored as 10050000000 (int64). Down to the satoshi.

Why int64? Because:

The tradeoff? You’re now thinking in integers, and overflow becomes a real concern above ~92 billion per position. For retail trading, that’s academic.

Event-Driven Architecture: The Nervous System

Trading systems have a fundamental problem: causality. You can’t know the close price while processing the open. You can’t execute a fill before knowing the signal. You can’t compute drawdown before recording the position.

I engineered QuantFlow around a strict event pipeline:

MarketEvent → SignalEvent → OrderEvent → FillEvent

Each event type is consumed by exactly one processor, transforming raw market data into trading decisions, decisions into orders, and orders into fills. The entire system is O(1) per tick — no sorting, no lookups, no search overhead.

More importantly: the event bus handles decoupling. Your strategy doesn’t touch the portfolio layer directly. The portfolio doesn’t touch the data layer. Everything communicates through events flowing down a channel. This is how you eliminate look-ahead bias — the temporal ordering is enforced by the architecture itself.

The Technical Stack

Core Packages

event — The Central Nervous System An ultra-fast, channel-based EventBus that routes MarketEvents, SignalEvents, OrderEvents, and FillEvents. I engineered this to operate in asynchronous, lock-free fashion. No mutexes, no condition variables — pure message passing.

data — Dual-Mode Ingestion The backtester supports three modes seamlessly:

The trick here was eliminating mode-specific code paths. All three modes produce identical MarketEvent streams. The strategy layer never knows whether data came from a file, an API, or a real-time feed.

indicators — Native Integer Mathematics SMA, EMA, RSI — all computed in 10⁸ fixed-point integers. Critically, each indicator uses a Stateful Update() pipeline:

indicator.Update(price)
value := indicator.Value()

No batch processing, no array allocations per tick. Each update is O(1) and allocates exactly zero bytes. This produces 1,400x speedups natively over batch processing.

portfolio — O(1) Accounting with Zero Allocation This layer is where my architecture really shines. Traditional backtesting libraries recompute portfolio metrics on every trade. I built a continuous accounting system that tracks:

All maintained in constant time, zero allocations.

strategy — The Sandbox Your algorithm lives here. Strategies receive market ticks, evaluate conditions, and emit SignalEvents. No look-ahead bias by design — the event pipeline enforces temporality.

The Engineering Challenges

Challenge 1: Eliminating Garbage Collection Pressure

Go’s garbage collector is fantastic for most applications. It’s terrible for microsecond-scale trading. Every allocation is a potential GC pause, and pauses are non-deterministic in trading engines.

My solution: Zero-allocation object pooling. Every event type has a pre-allocated pool. When you emit a MarketEvent, you’re recycling a previously-used struct, not allocating fresh memory.

market := eventPool.AcquireMarketEvent()
defer eventPool.Release(market)
market.Price = newPrice
eventBus.Emit(market)

This required thinking about memory lifetimes carefully — events can’t escape the handler function — but it eliminates the entire GC burden.

Challenge 2: Look-Ahead Bias in Strategy Definition

Here’s a subtle trap: if you compute your signal after knowing the close price, you’ve accidentally looked ahead. You can’t check if price > MA after the close—you had to decide while the bar was forming.

I solved this with strict event ordering. The data loader emits MarketEvents in chronological order. Indicators update strictly in sequence. Strategies evaluate in sequence. The entire system is a single-threaded state machine where causality is literally impossible to violate.

Challenge 3: Supporting Dynamic JSON Strategies Without Go Recompilation

Most backtestings libraries require you to write Go code to define strategies. That’s a friction point. I wanted strategies to be JSON-driven.

{
"strategy_name": "SMA_Crossover_with_RSI_Filter",
"indicators": [
{ "id": "fast_ma", "type": "SMA", "params": { "period": 10 } },
{ "id": "slow_ma", "type": "SMA", "params": { "period": 50 } },
{ "id": "rsi_main", "type": "RSI", "params": { "period": 14 } }
],
"rules": {
"buy": [
{ "type": "crossover", "left_operand": "fast_ma", "right_operand": "slow_ma" },
{ "type": "greater_than", "left_operand": "rsi_main", "value": 30 }
],
"sell": [
{ "type": "crossunder", "left_operand": "fast_ma", "right_operand": "slow_ma" }
]
}
}

The JSON unmarshaler dynamically constructs the rule tree at startup. No recompilation needed. Iterate on strategies in seconds.

Challenge 4: Building an Interactive Frontend Without Boilerplate

The HTML/JS trading terminal UI was a late addition, but it became the crown jewel. I built a lightweight visualization that:

Zero dependencies, zero build step, pure vanilla JS. You just start the backend and navigate to localhost:8000.

The Results

Performance Benchmarks

Running on a 2023 MacBook Pro M3 Max:

TestSpeedMemory20 years of daily AAPL data (5,000+ ticks)2.8ms0 allocationsSMA(10), SMA(50), RSI(14) continuous evaluation1,400x faster than batch processingper-tick O(1)10,000 simulated trades backtest-to-completion18ms end-to-enddeterministic

The Accuracy Advantage

Because I’m using fixed-point arithmetic:

The Workflow

Running a backtest is beautifully simple:

cd engine
go build -o inspector main.go
# Yahoo Finance mode: 20+ years of data, no API key needed
./inspector backtest -mode yfinance -symbol AAPL \
-start "2014-01-01" -end "2026-03-31" \
-config strategy.json -log strategy_logs.csv
# Then visualize
cd ui && python3 -m http.server 8000
# Navigate to http://localhost:8000

The outputs:

What I Learned

1. Architecture Over Optimization

I could have started with benchmark-driven micro-optimizations. Instead, I designed the event-driven architecture first. The performance came naturally because the architecture is sound.

2. Fixed-Point Arithmetic is Non-Negotiable for Finance

Floating-point isn’t a performance choice for trading. It’s a correctness choice. I will never build a financial system on float64 again.

3. Zero-Allocation Thinking Changes Everything

Once you commit to zero allocations, your entire design perspective shifts. You start thinking about object pooling, memory lifetimes, and stack allocation. It’s harder upfront but produces systems that scale without GC surprises.

4. Determinism is Reproducibility

In trading, “works on my machine” is a feature, not a bug. When my backtest is deterministic across platforms, I can confidently deploy to production knowing the behavior won’t mysteriously change.

What’s Next

QuantFlow is open-source on GitHub. The next phase includes:

But the foundation is solid. The architecture scales to complexity without compromise.

The Takeaway

Building a quantitative trading engine forced me to think deeply about:

The result is a backtester that’s fast, accurate, auditable, and reproducible. It’s the tool I wanted to exist.

If you’re building financial software, I’d argue the same principles apply: architecture first, performance naturally follows, and purity (fixed-point over float64) prevents the subtle bugs that haunt traders.

Links

Disclaimer: Past performance is not indicative of future results. QuantFlow is a backtesting and paper trading engine for research and learning. Use at your own risk.

This article was originally published on Trading Tag and is republished here under RSS syndication for informational purposes. All rights and intellectual property remain with the original author. If you are the author and wish to have this article removed, please contact us at [email protected].

NexaPay — Accept Card Payments, Receive Crypto

No KYC · Instant Settlement · Visa, Mastercard, Apple Pay, Google Pay

Get Started →