A high-performance algorithmic trading execution system built in Rust. This system implements multiple execution algorithms (TWAP, POV, VWAP, IS) with real-time monitoring, backtesting, and performance analytics.
- What This System Actually Does
- Installation & Setup
- Quick Start
- Mathematical Foundations
- Trading & Finance Concepts
- Execution Algorithms Explained
- Performance Metrics Deep Dive
- Understanding Results
- Architecture
- API Usage
- Development
You want to buy 10,000 shares of a stock. If you submit a market order for 10,000 shares all at once, you'll likely:
- Move the market - Your large buy order pushes the price up as you consume available liquidity
- Get terrible execution - You'll buy the first 1,000 shares at $100, the next 1,000 at $100.10, then $100.20, and so on
- Alert other traders - They see a large order and front-run you
This is called market impact, and it's expensive. If the stock is trading at $100 and you move it to $100.50 while buying, you've paid an extra $0.50 per share on average. On 10,000 shares, that's $5,000 in unnecessary costs.
Execution algorithms solve this by splitting your large order into smaller "child orders" and executing them over time. Instead of buying 10,000 shares at once, you might:
- Buy 100 shares every 30 seconds for 50 minutes (TWAP)
- Buy proportionally to market volume, so you're never more than 10% of the market (POV)
- Buy more when volume is higher and less when volume is lower (VWAP)
- Front-load execution if you expect the price to move against you (IS)
This system simulates these algorithms so you can test them, understand how they work, and measure their performance without risking real money.
- Execution Engine: Processes parent orders, breaks them into child orders, executes them on schedule
- Simulated Orderbook: Tests your algorithms against a realistic order book with bid-ask spread, market makers, and liquidity
- Real Market Data: Pulls live crypto prices from Binance to make the simulation realistic
- GUI: Visual interface to submit orders, monitor execution, and view performance
- Performance Analytics: Sharpe ratio, drawdown, win rate, VaR, and other institutional-grade metrics
- Backtesting Framework: Test your strategies on historical data
- gRPC API: Programmatic access for automated trading systems
1. Rust (1.70+)
# Install Rust via rustup
# Visit: https://rustup.rs/
# Or run:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Set stable as default
rustup default stable2. Visual Studio 2022 (Windows only)
- Download: Visual Studio Community
- Install workload: "Desktop development with C++"
- This provides the MSVC linker and Windows SDK that Rust needs
3. Protocol Buffers Compiler (optional)
- Only needed if modifying the gRPC API
- Download: protobuf releases
# Clone or navigate to project
cd C:\Users\YourName\Desktop\QuantSystem
# Build all components (takes 3-5 minutes first time)
cargo build --release
# Output binaries in target/release/:
# - engine.exe (execution engine)
# - trader-gui.exe (GUI application)Troubleshooting Build Issues:
- "Cannot find kernel32.lib": Install Windows SDK via Visual Studio
- "linker error": Make sure MSVC is installed, not just MinGW
- "Cargo.lock conflicts": Run
cargo updatethen rebuild
Open a terminal:
cargo run --release -p engineYou'll see:
INFO Starting Algo Execution Engine...
INFO Seeding orderbook BTCUSDT with market maker liquidity at price 10000000
INFO Starting gRPC server on 0.0.0.0:9090...
Keep this terminal running. The engine is now:
- Listening for orders on port 9090
- Maintaining a simulated orderbook with market maker liquidity
- Ready to execute child orders and generate fills
Open a second terminal:
cargo run --release -p trader-guiA window opens with:
- Real-time crypto prices (BTC, ETH, SOL, BNB, ADA) from Binance
- Live technical indicators (RSI, MACD, Bollinger Bands)
- Order entry form pre-filled with a sample TWAP order
- Empty positions and orders tables
-
Click "File" → "Connect"
- Status bar should show green "● Connected"
-
Submit a test order (form is pre-filled):
- Symbol: BTCUSDT
- Side: BUY
- Quantity: 1000
- Duration: 10 seconds
- Slices: 10
- Algorithm: TWAP
-
Click "🚀 Submit TWAP"
The engine terminal shows:
INFO [EXEC] Executing child order Order#1001 at target time...
INFO Fill: order=Order#1001 qty=100 price=$100.01000 time=...
INFO Fill: order=Order#1002 qty=100 price=$100.01000 time=...
...
INFO Parent order 12345 fully filled
The GUI updates in real-time:
- Orders table: Shows order status changing from ACCEPTED → FILLED
- Positions table: Shows your position building up (100, 200, 300, ..., 1000)
- Performance Dashboard: Updates with metrics after fills complete
Expand "📈 Performance Dashboard" to see:
- Equity curve: Visual graph of your P&L over time
- Sharpe ratio: Risk-adjusted return (> 1.5 is good)
- Max drawdown: Worst peak-to-trough decline
- Win rate: Percentage of profitable trades
- VaR/CVaR: Value at risk metrics (95% and 99% confidence)
Submit 5-10 more orders with different symbols and sides to generate meaningful statistics.
The simulated orderbook maintains buy and sell orders in price-time priority:
SELL SIDE (asks)
$100.02 │ 500 shares
$100.01 │ 1000 shares
─────────┼───────────── (spread = $0.02)
$99.99 │ 1000 shares
$99.98 │ 500 shares
BUY SIDE (bids)
Best bid: $99.99 (highest buy price) Best ask: $100.01 (lowest sell price) Mid-price: $100.00 (average of bid and ask) Spread: $0.02 (ask - bid)
When you submit a market buy order, it matches against the best ask:
- Your order "crosses the spread" by buying at $100.01
- This is called paying the spread or taking liquidity
When you submit a limit buy order at $99.99, it joins the bid side:
- You're "providing liquidity" and waiting to be filled
- You avoid paying the spread but might not get filled immediately
Price impact is the effect your order has on the market price. It's typically modeled as:
impact = k × sqrt(order_size / average_daily_volume)
Where:
kis a market-specific constant (typically 0.1 to 1.0)sqrtreflects that impact grows sub-linearly (doubling size doesn't double impact)
Example:
- Stock trades 1,000,000 shares/day
- Your order is 10,000 shares
- Impact ≈ 0.3 × sqrt(10,000 / 1,000,000) = 0.3 × 0.1 = 3%
So your 10,000 share buy might push the price up 3% from $100 to $103.
Slippage is the difference between expected and actual execution price:
slippage = execution_price - decision_price
Components:
- Spread cost: Half-spread on average (if mid-price is fair value)
- Market impact: Your order moves the price
- Delay cost: Price moves between decision and execution
- Opportunity cost: Price moves against you while waiting
Example:
- You decide to buy at $100.00 (mid-price)
- You execute at $100.50 over 10 minutes
- Slippage = $0.50 per share
- On 10,000 shares, that's $5,000 in costs
Execution algorithms aim to minimize slippage while ensuring you complete your order.
Markets don't trade uniformly throughout the day. Volume typically follows a U-shape:
Volume Pattern (typical equity market):
│
High │ ███░░░░░░░░░░░██
│ ███░░░░░░░░░░░██
Mid │ ███░░░░░░░░░░░██
│ ███░░░░░░░░░░███
Low │ ███░░░░░░░░░░███
└────────────────────
9am 12pm 4pm
(open) (mid) (close)
VWAP algorithms exploit this by:
- Executing more shares during high-volume periods (open and close)
- Executing fewer shares during low-volume periods (lunch)
- This minimizes your percentage of volume and reduces market impact
Bid-Ask Spread The spread represents the cost of immediacy. Market makers profit from the spread by:
- Buying at the bid ($99.99)
- Selling at the ask ($100.01)
- Capturing the spread ($0.02 per share)
In exchange, they provide liquidity to the market.
Liquidity Liquidity measures how easy it is to buy or sell without moving the price:
- High liquidity: Tight spreads, large order book depth, minimal price impact
- Low liquidity: Wide spreads, thin order books, large price impact
Order Types:
- Market order: Execute immediately at best available price (pay the spread)
- Limit order: Execute only at specified price or better (may not fill)
- IOC (Immediate or Cancel): Execute whatever fills immediately, cancel the rest
- FOK (Fill or Kill): Execute entire order immediately or cancel entirely
Net Position = Sum of all trades
- Long position: Net positive (you own shares)
- Short position: Net negative (you owe shares)
- Flat: Net zero (no position)
Example:
Trade 1: BUY 500 @ $100.00
Trade 2: BUY 500 @ $101.00
Trade 3: SELL 200 @ $102.00
─────────────────────────────
Net position: +800 shares (long)
Average Entry Price Weighted average of your entry prices:
avg_price = (qty1 × price1 + qty2 × price2 + ...) / total_qty
For the example above:
avg_price = (500 × 100 + 500 × 101) / 1000 = $100.50
Realized vs. Unrealized P&L
- Realized P&L: Profit/loss from closed trades (you've sold shares you bought)
- Unrealized P&L: Profit/loss from open position (you still hold shares)
Realized P&L = (exit_price - entry_price) × quantity
Unrealized P&L = (current_price - avg_entry_price) × open_quantity
Using the example above:
Realized P&L = (102 - 100.50) × 200 = $300
Unrealized P&L = (103 - 100.50) × 800 = $2,000 (if current price is $103)
Total P&L = $2,300
TCA measures execution quality by comparing actual results to benchmarks:
Arrival Price: Price when you decided to trade Benchmark: What you're comparing against (VWAP, arrival, close, etc.) Implementation Shortfall: Difference between arrival price and average execution price
implementation_shortfall = avg_execution_price - arrival_price
Example:
- Arrival price: $100.00
- You execute 10,000 shares over 10 minutes
- Average execution price: $100.30
- Implementation shortfall: $0.30 × 10,000 = $3,000 cost
Participation Rate: Your trades as % of market volume
participation_rate = your_volume / market_volume
- Low participation (< 5%): Less market impact, slower execution
- High participation (> 20%): More market impact, faster execution
Goal: Execute evenly over time, regardless of market conditions.
Schedule Formula:
For N slices over duration T:
slice_interval = T / N
slice_size = total_quantity / N
slice[i].time = start_time + (i × slice_interval)
slice[i].qty = slice_size
Example:
- Total quantity: 1,000 shares
- Duration: 10 seconds
- Slices: 10
Slice 1: t=0s, qty=100
Slice 2: t=1s, qty=100
Slice 3: t=2s, qty=100
...
Slice 10: t=9s, qty=100
When to use TWAP:
- ✅ You don't have a view on price direction
- ✅ You want predictable, evenly-spaced execution
- ✅ You're trading in a stable market
- ❌ Don't use when volume varies significantly throughout the day
Advantages:
- Simple and transparent
- Predictable execution pattern
- Easy to explain to clients
Disadvantages:
- Ignores market volume patterns
- Can be too aggressive during low-volume periods
- Can be too passive during high-volume periods
Goal: Execute as a fixed percentage of market volume.
Schedule Formula:
For each period:
observed_market_volume = market_trades_in_period
target_execution = participation_rate × observed_market_volume
# Adjust to meet overall target
remaining_qty = total_qty - executed_qty
remaining_time = end_time - current_time
slice_qty = min(target_execution, remaining_qty)
Example:
- Total quantity: 1,000 shares
- Participation rate: 10%
- Market trades 500 shares in period 1 → You execute 50 shares
- Market trades 1,000 shares in period 2 → You execute 100 shares
When to use POV:
- ✅ You want to stay "hidden" in the market flow
- ✅ You don't want to be more than X% of volume
- ✅ You have time and don't need urgent execution
- ❌ Don't use in illiquid markets (you might never finish)
Advantages:
- Minimizes market impact by following natural volume
- Adapts to market conditions
- Good for large orders in liquid markets
Disadvantages:
- Unpredictable completion time (what if volume dries up?)
- Complex to explain (clients want certainty)
- Requires accurate volume forecasts
Goal: Match the day's volume-weighted average price.
VWAP Calculation:
VWAP = Σ(price[i] × volume[i]) / Σ(volume[i])
Schedule Formula:
For each period:
expected_volume[i] = historical_volume_profile[i]
total_expected_volume = Σ expected_volume[i]
slice_qty[i] = (expected_volume[i] / total_expected_volume) × total_qty
Example: Historical volume profile for 9am-10am:
9:00-9:15: 30% of volume → Execute 300 shares (30% of 1,000)
9:15-9:30: 25% of volume → Execute 250 shares
9:30-9:45: 20% of volume → Execute 200 shares
9:45-10:00: 25% of volume → Execute 250 shares
When to use VWAP:
- ✅ You want to match the market's average price
- ✅ You're measured against VWAP as a benchmark
- ✅ You have historical volume patterns
- ❌ Don't use on days with unusual volume (earnings, news)
Advantages:
- Matches the market's natural rhythm
- Benchmark used by many institutional investors
- Minimizes adverse selection (not trading at wrong times)
Disadvantages:
- Relies on historical volume patterns (may not predict today)
- Can't adapt if volume pattern changes
- Front-loaded execution can alert traders
Goal: Minimize the cost of delay while managing market impact.
Cost Model:
total_cost = market_impact_cost + delay_cost
market_impact_cost = k × sqrt(execution_rate) × volatility
delay_cost = drift × time_to_completion
# Optimal urgency minimizes total cost
urgency = f(price_drift, volatility, remaining_qty)
Schedule Formula:
For each period:
# Higher urgency = front-load execution
weight[i] = urgency^i
slice_qty[i] = (weight[i] / Σ weight) × remaining_qty
# Dynamic adjustment based on price movement
if price_moving_against_us:
urgency += 0.1 # Execute faster
if price_moving_favorably:
urgency -= 0.1 # Execute slower
Example (high urgency = 0.7):
Period 1: 40% of total → 400 shares (front-loaded)
Period 2: 30% of total → 300 shares
Period 3: 20% of total → 200 shares
Period 4: 10% of total → 100 shares (back-loaded)
When to use IS:
- ✅ You have a strong view on price direction
- ✅ You expect momentum or price drift
- ✅ You need to balance urgency vs. impact
- ❌ Don't use if you have no view on price direction
Advantages:
- Adapts to price movements in real-time
- Optimal for minimizing implementation shortfall
- Can front-load or back-load based on urgency
Disadvantages:
- Complex to understand and explain
- Requires sophisticated models (volatility, drift, impact)
- Can be too aggressive if models are wrong
Simple Return:
R = (P_end - P_start) / P_start
Log Return (used in finance because they're additive):
r = ln(P_end / P_start)
Cumulative Return:
R_cumulative = (1 + R_1) × (1 + R_2) × ... × (1 + R_n) - 1
Formula:
Sharpe = (R_portfolio - R_risk_free) / σ_portfolio
Where:
R_portfolio: Average return of your strategyR_risk_free: Risk-free rate (Treasury bills, ~4% annually in 2024)σ_portfolio: Standard deviation of returns (volatility)
What it means:
- Sharpe < 1.0: Poor risk-adjusted returns
- Sharpe 1.0-2.0: Good risk-adjusted returns
- Sharpe > 2.0: Excellent risk-adjusted returns (rare)
- Sharpe > 3.0: Suspicious (might be overfitted or taking hidden risks)
Intuition: How much return are you getting per unit of risk?
Example:
Strategy A: 15% return, 10% volatility → Sharpe = (15 - 4) / 10 = 1.1
Strategy B: 20% return, 20% volatility → Sharpe = (20 - 4) / 20 = 0.8
Strategy A is better risk-adjusted, even though B has higher absolute return.
Annualization:
Sharpe_annual = Sharpe_period × sqrt(periods_per_year)
Daily Sharpe → Annual: multiply by sqrt(252)
Monthly Sharpe → Annual: multiply by sqrt(12)
Formula:
Sortino = (R_portfolio - R_target) / σ_downside
Where:
σ_downside: Standard deviation of negative returns only
Why it's better than Sharpe:
- Sharpe penalizes upside volatility (who cares if you make too much money?)
- Sortino only penalizes downside volatility (actual risk)
Example:
Returns: [+10%, +20%, -5%, +15%, -10%]
For Sharpe: σ = std([10, 20, -5, 15, -10]) = 11.7%
For Sortino: σ_downside = std([-5, -10]) = 3.5%
Sortino will be higher because it ignores upside volatility
Formula:
Drawdown[t] = (Peak[t] - Value[t]) / Peak[t]
Max Drawdown = max(Drawdown[t]) for all t
Intuition: Worst peak-to-trough decline in portfolio value.
Example:
Equity: $100k → $150k → $120k → $180k → $100k
Drawdown 1: ($150k - $120k) / $150k = 20%
Drawdown 2: ($180k - $100k) / $180k = 44.4%
Max Drawdown = 44.4%
What it means:
- < 10%: Low risk, conservative strategy
- 10-20%: Moderate risk, typical for balanced portfolios
- 20-30%: High risk, aggressive trading
- > 30%: Very high risk, many investors would have quit
Drawdown Duration: How long it takes to recover to previous peak.
Recovery Time = date[new_peak] - date[previous_peak]
Long drawdown durations are psychologically difficult. A 50% drawdown that recovers in 1 month is easier to handle than a 20% drawdown that lasts 2 years.
Formula:
Calmar = Annualized_Return / |Max_Drawdown|
Intuition: Return per unit of maximum loss.
Example:
Strategy: 18% annual return, 15% max drawdown
Calmar = 18 / 15 = 1.2
What it means:
- Calmar < 1.0: You're losing more in drawdowns than you're making in returns
- Calmar > 1.0: Returns exceed worst-case losses
- Calmar > 3.0: Excellent (stable returns, small drawdowns)
Formula:
VaR_95 = 5th percentile of return distribution
Intuition: "I'm 95% confident I won't lose more than X on any given day."
Example:
Daily returns: [-5%, -2%, -1%, 0%, +1%, +2%, +3%, +4%, +5%]
5th percentile = -5%
If portfolio value is $100k:
VaR_95 = $100k × 5% = $5,000
"I'm 95% confident I won't lose more than $5,000 in a day"
Limitations:
- Assumes normal distribution (real markets have fat tails)
- Doesn't tell you how bad the worst 5% of days can be
- Can be gamed (sell options to collect premium, hide tail risk)
Formula:
CVaR_95 = Average of returns worse than VaR_95
Intuition: "On my worst 5% of days, I lose X on average."
Example:
Daily returns: [-8%, -7%, -5%, -2%, -1%, 0%, +1%, +2%, +3%]
VaR_95 = -5% (5th percentile)
CVaR_95 = average([-8%, -7%]) = -7.5%
"On my worst days, I lose 7.5% on average"
Why it's better than VaR:
- Captures tail risk
- Tells you severity of worst-case scenarios
- More informative for risk management
Formula:
Win Rate = (Winning Trades / Total Trades) × 100%
What it means:
- < 40%: Low win rate (better be high profit factor)
- 40-60%: Typical for most strategies
- > 60%: High win rate (probably small wins, rare big losses)
Critical insight: Win rate alone is meaningless!
Strategy A: 90% win rate, avg win $100, avg loss $5,000
Strategy B: 40% win rate, avg win $1,000, avg loss $300
Strategy A is terrible (big losses wipe out small wins)
Strategy B is better (wins are larger than losses)
Formula:
Profit Factor = Gross Profit / Gross Loss
What it means:
- PF < 1.0: Losing money overall
- PF = 1.0: Breaking even
- PF 1.0-1.5: Barely profitable
- PF 1.5-2.0: Good
- PF > 2.0: Excellent (rare)
Example:
10 trades:
Wins: $500, $400, $600, $300 = $1,800 total
Losses: -$200, -$300, -$400, -$100, -$200, -$100 = -$1,300 total
Profit Factor = $1,800 / $1,300 = 1.38
For every $1 you lose, you make $1.38. That's a profitable system.
PENDING → ACCEPTED → WORKING → PARTIALLY_FILLED → FILLED
↓
CANCELLED
↓
REJECTED
PENDING: Order received, waiting to start execution ACCEPTED: Order validated, execution will begin WORKING: Child orders actively being sent to market PARTIALLY_FILLED: Some fills received, more expected FILLED: All quantity executed, order complete CANCELLED: User cancelled before completion REJECTED: Risk checks failed or invalid parameters
Each fill represents an actual execution:
Fill #1234
├── Order ID: 5678 (child order)
├── Quantity: 100 shares
├── Price: $100.01
└── Timestamp: 1,700,000,000,000 ns (nanoseconds since epoch)
Key metrics from fills:
- Fill rate: Percentage of order filled
- Average fill price: Weighted average of all fill prices
- Price variance: Spread of fill prices (measures slippage)
Example analysis:
10 fills of 100 shares each at prices:
[$100.01, $100.01, $100.02, $100.01, $100.03,
$100.02, $100.01, $100.04, $100.02, $100.01]
Average fill price = $100.018
Min = $100.01, Max = $100.04
Range = $0.03 (slippage)
Symbol: BTCUSDT
Quantity: +1,000 (positive = long)
Avg Price: $100.50
Realized P&L: +$300 (from closed portions)
Unrealized P&L: +$500 (from open position)
Total P&L: +$800
Scenario walkthrough:
- Buy 500 @ $100 → Position: +500, Avg: $100
- Buy 500 @ $101 → Position: +1,000, Avg: $100.50
- Sell 200 @ $102 → Position: +800, Realized: +$300 (sold at $102, avg cost $100.50)
- Current price: $101.125 → Unrealized: +$500 (800 shares × ($101.125 - $100.50))
Reading the Equity Curve:
$120k ┤ ╭─────╮
│ ╱ ╰╮
$100k ┤───╯ ╰─╮ ← Drawdown period
│ ╰──
$80k ┤
└─────────────────────
Jan Feb Mar Apr
- Uptrend: Strategy is working, keep going
- Flat: Strategy not generating returns, investigate
- Downtrend: Strategy losing money, stop or adjust
- Drawdown: Temporary decline (normal) or start of failure (concerning)?
Good equity curve traits:
- Smooth upward slope (consistent profits)
- Small drawdowns (< 15%)
- Quick recovery from drawdowns (< 1 month)
- Increasing slope over time (compounding)
Bad equity curve traits:
- Large drawdowns (> 30%)
- Long flat periods (opportunity cost)
- Declining slope (strategy degrading)
- High volatility (erratic returns)
┌─────────────────────────────────────────────────┐
│ trader-gui │
│ (User interface, charts, performance metrics) │
└────────────────┬────────────────────────────────┘
│ gRPC (port 9090)
↓
┌─────────────────────────────────────────────────┐
│ engine │
│ ┌────────────────────────────────────────────┐ │
│ │ Event Loop (100ms tick) │ │
│ │ ├─ Schedule child orders │ │
│ │ ├─ Execute orders on orderbook │ │
│ │ └─ Broadcast fills to subscribers │ │
│ └────────────────────────────────────────────┘ │
│ ┌────────────────────────────────────────────┐ │
│ │ Position Manager │ │
│ │ ├─ Track net positions │ │
│ │ ├─ Calculate realized/unrealized P&L │ │
│ │ └─ Update avg entry prices │ │
│ └────────────────────────────────────────────┘ │
│ ┌────────────────────────────────────────────┐ │
│ │ Risk Checker │ │
│ │ ├─ Position limits │ │
│ │ ├─ Max order size │ │
│ │ └─ Symbol whitelist │ │
│ └────────────────────────────────────────────┘ │
└────────────────┬────────────────────────────────┘
│
↓
┌─────────────────────────────────────────────────┐
│ orderbook │
│ ┌────────────────────────────────────────────┐ │
│ │ Price Levels (sorted) │ │
│ │ $100.02: [Order A: 500] ← best ask │ │
│ │ $100.01: [Order B: 1000] │ │
│ │ ─────────────────────────── (spread) │ │
│ │ $99.99: [Order C: 1000] ← best bid │ │
│ │ $99.98: [Order D: 500] │ │
│ └────────────────────────────────────────────┘ │
│ ┌────────────────────────────────────────────┐ │
│ │ Matching Engine │ │
│ │ ├─ Price-time priority │ │
│ │ ├─ Generate fills │ │
│ │ └─ Update book depth │ │
│ └────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────┘
orderbook (crates/orderbook/)
- Maintains buy/sell orders in price levels
- Implements price-time priority matching
- Generates fills when orders cross
- O(log n) insert, O(1) best bid/ask lookup
algo-core (crates/algo-core/)
- Pure algorithm implementations (no I/O)
- Takes parameters → Returns schedule of child orders
- Algorithms: TWAP, POV, VWAP, IS
- No dependencies on engine or orderbook
engine (crates/engine/)
- Main execution loop (100ms tick by default)
- Receives parent orders via gRPC API
- Calls algo-core to generate schedule
- Submits child orders to orderbook at scheduled times
- Tracks positions and enforces risk limits
- Broadcasts fills to subscribers
api (crates/api/)
- gRPC server implementation (tonic)
- Protocol buffer definitions (
proto/execution.proto) - Endpoints:
SubmitParentOrder: Submit algo orderGetOrderStatus: Query fill progressGetPositions: Query positionsStreamFills: Real-time fill stream
database (crates/database/)
- SQLite persistence
- Tables: orders, fills, positions
- CRUD operations with rusqlite
- Stores execution history for analysis
market-data (crates/market-data/)
- Fetches live crypto prices from Binance
- REST API:
/api/v3/ticker/bookTicker(bid/ask) - REST API:
/api/v3/ticker/24hr(volume) - REST API:
/api/v3/trades(recent trades) - Updates every 2 seconds (Binance rate limit)
indicators (crates/indicators/)
- Technical indicators library
- Moving averages: SMA, EMA
- Momentum: RSI, MACD
- Volatility: Bollinger Bands, ATR
- Volume: OBV
- All indicators work on
Vec<f64>price series
analytics (crates/analytics/)
- Performance metrics calculation
- Return metrics: Simple, log, cumulative, annualized
- Risk-adjusted: Sharpe, Sortino, Calmar
- Drawdown: Max, duration, recovery time
- Trade stats: Win rate, profit factor, avg win/loss
- Risk: Volatility, VaR, CVaR (95%, 99%)
- CSV export for further analysis
backtesting (crates/backtesting/)
- Event-driven backtesting framework
- Strategy trait for custom strategies
- Portfolio management (multi-position)
- Commission and slippage modeling
- Bar-by-bar replay of historical data
- Realistic fill simulation
strategies (crates/strategies/)
- Pre-built trading strategies
- Pairs Trading: Cointegration-based arbitrage
- Mean Reversion: RSI oversold/overbought
- Bollinger Bands: Volatility breakout
- MACD: Momentum trend following
trader-gui (crates/trader-gui/)
- egui immediate-mode GUI
- Real-time market data display
- Order entry form with algo selection
- Position and order monitoring
- Performance dashboard with equity curves
- Connects to engine via gRPC
The engine exposes a gRPC API on port 9090. You can interact with it from any language that supports gRPC.
// Rust example
use api::proto::execution_service_client::ExecutionServiceClient;
use api::proto::ParentOrderRequest;
let mut client = ExecutionServiceClient::connect("http://127.0.0.1:9090").await?;
let request = ParentOrderRequest {
symbol: "BTCUSDT".to_string(),
side: "BUY".to_string(),
quantity: 1000,
limit_price_ticks: 10_000_00, // $100.00 (cents as ticks)
start_time_ns: start_time,
end_time_ns: end_time,
algo_type: "TWAP".to_string(),
num_slices: 10,
};
let response = client.submit_parent_order(request).await?;
println!("Order ID: {}", response.into_inner().parent_order_id);# Python example
import grpc
from proto import execution_pb2, execution_pb2_grpc
channel = grpc.insecure_channel('localhost:9090')
stub = execution_pb2_grpc.ExecutionServiceStub(channel)
request = execution_pb2.ParentOrderRequest(
symbol="BTCUSDT",
side="BUY",
quantity=1000,
limit_price_ticks=10000_00,
start_time_ns=start_time,
end_time_ns=end_time,
algo_type="TWAP",
num_slices=10
)
response = stub.SubmitParentOrder(request)
print(f"Order ID: {response.parent_order_id}")let request = OrderStatusRequest {
parent_order_id: 12345,
};
let response = client.get_order_status(request).await?;
let status = response.into_inner();
println!("Status: {}", status.status);
println!("Filled: {}/{}", status.filled_qty, status.total_qty);let request = StreamFillsRequest {};
let mut stream = client.stream_fills(request).await?.into_inner();
while let Some(fill) = stream.message().await? {
println!("Fill: {} @ ${:.2}",
fill.fill_qty,
fill.fill_price as f64 / 100.0
);
}QuantSystem/
├── crates/
│ ├── algo-core/ (Execution algorithms)
│ ├── analytics/ (Performance metrics)
│ ├── api/ (gRPC server)
│ ├── backtesting/ (Backtesting framework)
│ ├── database/ (SQLite persistence)
│ ├── engine/ (Execution engine)
│ ├── indicators/ (Technical indicators)
│ ├── market-data/ (Market data fetcher)
│ ├── orderbook/ (Order matching)
│ ├── strategies/ (Trading strategies)
│ ├── trader-gui/ (GUI application)
│ └── python-bindings/ (Python interface)
├── proto/
│ └── execution.proto (gRPC API definition)
├── Cargo.toml (Workspace config)
└── README.md
# Run all tests
cargo test
# Run tests for specific crate
cargo test -p algo-core
cargo test -p orderbook
cargo test -p analytics
# Run tests with output
cargo test -- --nocapture
# Run specific test
cargo test test_twap_schedule- Create new module in
crates/algo-core/src/:
// crates/algo-core/src/myalgo.rs
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct MyAlgoParams {
pub start_ns: u64,
pub end_ns: u64,
pub total_qty: u64,
pub custom_param: f64,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct MyAlgoInstruction {
pub target_time_ns: u64,
pub qty: u64,
}
pub fn compute_myalgo_schedule(
params: MyAlgoParams,
) -> Vec<MyAlgoInstruction> {
let mut schedule = Vec::new();
// Your algorithm logic here
// Generate instructions based on parameters
schedule
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_myalgo_basic() {
let params = MyAlgoParams {
start_ns: 0,
end_ns: 10_000_000_000, // 10 seconds
total_qty: 1000,
custom_param: 0.5,
};
let schedule = compute_myalgo_schedule(params);
assert_eq!(schedule.len(), 10);
assert_eq!(
schedule.iter().map(|i| i.qty).sum::<u64>(),
1000
);
}
}- Export from
crates/algo-core/src/lib.rs:
pub mod myalgo;
pub use myalgo::*;- Add to engine in
crates/engine/src/event_loop.rs:
let schedule = match algo_type.as_str() {
"TWAP" => compute_twap_schedule(...),
"POV" => compute_pov_schedule(...),
"VWAP" => compute_vwap_schedule(...),
"IS" => compute_is_schedule(...),
"MYALGO" => compute_myalgo_schedule(...), // Add here
_ => return Err("Unknown algorithm"),
};- Edit
proto/execution.proto:
message ParentOrderRequest {
string symbol = 1;
string side = 2;
uint64 quantity = 3;
// Add new field
double custom_param = 10;
}- Rebuild to regenerate Rust code:
cargo buildThis runs build.rs which invokes tonic-build to regenerate execution.rs from the proto file.
- Update server in
crates/api/src/server.rs:
async fn submit_parent_order(
&self,
request: Request<ParentOrderRequest>,
) -> Result<Response<SubmitOrderResponse>, Status> {
let req = request.into_inner();
// Access new field
let custom_param = req.custom_param;
// Use it in your logic...
}-- Orders table
CREATE TABLE orders (
order_id INTEGER PRIMARY KEY,
parent_order_id INTEGER,
symbol TEXT NOT NULL,
side TEXT NOT NULL,
qty INTEGER NOT NULL,
price INTEGER NOT NULL,
filled_qty INTEGER DEFAULT 0,
status TEXT NOT NULL,
algo_type TEXT,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
);
-- Fills table
CREATE TABLE fills (
fill_id INTEGER PRIMARY KEY AUTOINCREMENT,
order_id INTEGER NOT NULL,
symbol TEXT NOT NULL,
side TEXT NOT NULL,
fill_qty INTEGER NOT NULL,
fill_price INTEGER NOT NULL,
timestamp_ns INTEGER NOT NULL,
created_at INTEGER NOT NULL,
FOREIGN KEY (order_id) REFERENCES orders(order_id)
);
-- Positions table
CREATE TABLE positions (
symbol TEXT PRIMARY KEY,
qty INTEGER NOT NULL,
avg_price INTEGER NOT NULL,
realized_pnl REAL DEFAULT 0.0,
updated_at INTEGER NOT NULL
);"Cannot find kernel32.lib"
- Install Windows SDK via Visual Studio Installer
- Modify → Visual Studio 2022 → Desktop development with C++
"linker error LNK1181"
- Ensure you're using MSVC toolchain, not GNU
- Run:
rustup default stable-x86_64-pc-windows-msvc
"Cargo.lock conflicts"
- Run:
cargo update - Then:
cargo build --release
"Failed to connect to engine"
- Ensure engine is running:
cargo run --release -p engine - Check port 9090 is not blocked by firewall
- Look for "Starting gRPC server on 0.0.0.0:9090" in logs
"Orders showing 0/1000 filled"
- Engine is working but GUI polling failed (fixed in current version)
- Check GUI terminal for
[GUI] Order X updatedmessages - Restart both engine and GUI if still stuck
"Performance Dashboard shows 'No performance data available'"
- Submit orders and wait for them to fill completely
- Dashboard needs at least 1 filled order to calculate metrics
- Check Orders table shows FILLED status
"GUI log spam filling terminal"
- Fixed in current version (logs only on changes)
- If still seeing spam, file an issue on GitHub
- Order submission: < 1 μs (microsecond) from receipt to child order generation
- Order matching: < 10 μs to match order and generate fill
- gRPC round-trip: ~100 μs on localhost
- Database write: ~1 ms (SQLite transaction)
All critical paths avoid allocations and use stack-based data structures.
- Orders/second: > 10,000 parent orders/sec
- Fills/second: > 50,000 fills/sec (limited by orderbook matching)
- GUI update rate: 60 FPS (16 ms per frame)
- Polling rate: Every 500ms for order status
- Engine: ~30 MB resident
- GUI: ~50 MB resident (includes egui and market data)
- Orderbook: ~1 KB per price level
- Per order: ~200 bytes
- Use release builds:
cargo build --release(10x+ faster) - Batch orders: Submit multiple parent orders at once
- Reduce slices: Fewer child orders = less overhead
- Increase tick interval: Default is 100ms, increase if not latency-sensitive
- Disable logging: Set
RUST_LOG=errorto reduce I/O
This is a sandbox project for learning and experimentation. Use at your own risk.
Rust
Finance
- Algorithmic Trading and DMA by Barry Johnson
- Trading and Exchanges by Larry Harris
- Quantitative Trading by Ernest Chan
Technical
Q: Can I connect this to a real exchange? A: This is a sandbox. To connect to real exchanges:
- Replace orderbook with exchange API client (REST/WebSocket/FIX)
- Implement exchange-specific authentication
- Add order type mapping (IOC, FOK, Post-Only, etc.)
- Handle exchange-specific errors and rate limits
- Add regulatory compliance (order tagging, reporting)
Q: Why Rust? A:
- Performance: Comparable to C++ but memory-safe
- No GC pauses: Critical for low-latency trading
- Fearless concurrency: Multi-threaded execution without data races
- Growing ecosystem: tokio, tonic, serde, sqlx all excellent
Q: How do I backtest algorithms? A: Use the backtesting crate:
cargo run --example backtest_demo --releaseOr implement your own strategy:
use backtesting::{Strategy, Signal, MarketDataBar, Portfolio};
struct MyStrategy;
impl Strategy for MyStrategy {
fn on_bar(&mut self, bar: &MarketDataBar, portfolio: &Portfolio) -> Vec<Signal> {
// Your logic here
}
}Q: What's the engine tick interval?
A: Default is 100ms. Change in engine/src/main.rs:
let engine = ExecutionEngine::new(..., 100); // millisecondsQ: Can I run multiple algorithms on one order? A: No, each parent order uses one algorithm. Submit multiple parent orders if you want to compare algorithms.
Q: How do I export results? A:
use analytics::{export_trades_to_csv, export_metrics_to_csv};
export_trades_to_csv(&trades, "trades.csv")?;
export_metrics_to_csv(&metrics, "metrics.csv")?;Q: Can I create custom indicators?
A: Yes! Add to crates/indicators/src/:
pub fn my_indicator(prices: &[f64], period: usize) -> Result<Vec<f64>, String> {
// Your indicator logic
}Q: Does this work on Linux/Mac? A: Yes, but you don't need Visual Studio. Just:
cargo build --releaseRust and the dependencies are cross-platform.
Built with Rust 🦀 | Powered by gRPC | Real-time market data from Binance