๐ง A High-Perf Quantitative Trading Framework for Crypto
Tesser: A High-Performance Quantitative Trading Framework in Rust

Tesser is a modular, event-driven framework for building and executing quantitative trading strategies. Written entirely in Rust, it is designed for performance, reliability, and extensibility, allowing traders and developers to move from idea to backtesting and live deployment with confidence.
The core design principle is the separation of concerns, decoupling trading logic from the specifics of any particular exchange.
Key Features
- Unified Exchange Interface: Connect to any exchange by implementing a common set of traits. Add a new exchange without touching the core logic.
- Event-Driven Architecture: A single, consistent event loop for both high-performance backtesting and live trading.
- Accurate Backtesting Engine: Simulate strategies against historical market data to evaluate performance before risking capital.
- Decoupled Components: Clear boundaries between data handling, strategy logic, execution, and portfolio management make the system robust and easy to maintain.
- Built for Performance: Leverages Rust's zero-cost abstractions to handle high-frequency data and execute orders with minimal latency.
Architecture & Crate Responsibilities
Tesser is organized as a Cargo workspace. Understanding the role of each crate is crucial for contributing to the project. The primary goal is to keep the core logic (strategy, portfolio) completely independent of any specific broker or data source.
tesser/
โโโ Cargo.toml
|
โโโ tesser-core # Foundational data structures
โโโ tesser-broker # The universal API for all exchanges (Traits)
|
โโโ tesser-strategy # Your trading algorithms
โโโ tesser-indicators # High-precision, composable technical indicators
โโโ tesser-portfolio # Position, risk, and PnL management
|
โโโ tesser-data # Consumes data via the broker trait
โโโ tesser-execution # Sends orders via the broker trait
โโโ tesser-events # In-process pub/sub event bus
|
โโโ tesser-backtester # The simulation engine
โโโ tesser-backtest-core # Deterministic candle backtest core (browser/CI)
โโโ tesser-backtest-wasm # WASM wrapper for the docs playground
โโโ tesser-cli # The command-line user interface
|
โโโ connectors/ # Directory for specific exchange implementations
โโโ tesser-bybit # Concrete Bybit connector (REST + WebSocket)
โโโ tesser-binance # Concrete Binance connector (REST + WebSocket)
โโโ tesser-paper # A simulated exchange for backtesting/paper fills
Core Crates
tesser-core
Responsibility: Defines the universal, foundational data structures and enums used everywhere.
- Contents:
structs likeOrder,Trade,Candle,Position,Tick.enums likeSide(Buy/Sell),OrderType(Limit/Market),Interval(1m, 1h). - Rule: This crate should have minimal to no dependencies. It is the bedrock of the entire project.
tesser-broker
Responsibility: The API Unification Layer. It defines the abstract interface for interacting with any exchange.
- Contents:
traits (interfaces) likeMarketStream(for subscribing to live data) andExecutionClient(for placing orders and managing accounts). It contains no concrete implementations. - Rule: If you are defining a behavior that an exchange must provide (e.g., "fetch open orders"), the trait for it belongs here.
tesser-events
Responsibility: Encapsulates every framework-level event type (ticks, candles, order-book deltas, signals, fills, order updates) plus the publish/subscribe bus used across the CLI, backtester, and future services.
- Contents: A
tokio::broadcast-poweredEventBusand typed wrappers (TickEvent,SignalEvent, etc.) that decouple producers from consumers. - Rule: Emit and consume runtime activity via the bus instead of calling modules directly; this keeps the event flow extensible (risk daemons, telemetry sinks, notification services) without rewriting the core loop.
Logic Crates
tesser-strategy
Responsibility: Contains the "brains" of the trading systemโyour algorithms.
- Contents: Implementations of a
Strategytrait. This is where you calculate indicators (e.g., RSI, Moving Averages) and generateSignalevents (e.g., "go long", "exit position"). - Rule: Strategy code must be pure and self-contained. It should only depend on
tesser-corefor data types and operate on the data it is given, without any knowledge of where the data comes from (live feed or backtest).
tesser-cortex
Responsibility: AI inference engine (ONNX Runtime wrapper) used by strategies.
- Contents:
CortexEngine,FeatureBuffer, and configuration types that manage zero-copy dataflow into ONNX Runtime. - Highlights: Hardware-agnostic design supporting AVX-512 CPU mode for microsecond latency and CUDA/TensorRT toggles via configuration.
tesser-indicators
Responsibility: Houses all reusable technical indicators and signal building blocks.
- Contents: Decimal-native implementations of SMA, EMA, RSI, Bollinger Bands, and the plumbing required to compose them through zero-cost
pipe()adapters. - Rule: Indicators must update in O(1) time, avoid
f64drift by defaulting torust_decimal::Decimal, and never depend on higher-level crates.
tesser-portfolio
Responsibility: Manages the state of your trading portfolio. It is the system's "accountant."
- Contents: Logic for tracking positions, calculating Profit and Loss (PnL), managing margin, and evaluating risk metrics. It updates its state by listening to
Fillevents (i.e., when an order is executed). - Rule: Portfolio logic should not be aware of any specific exchange. It works with the abstract
PositionandFilltypes fromtesser-core.
Engine Crates
tesser-data
Responsibility: Manages the flow of market data from a source to the strategies.
- Contents: The logic to handle incoming data streams (from a
MarketStreamimplementation) and historical data feeds. It is responsible for tasks like building candles from ticks or replaying historical data files.
MarketStream trait object. It does not know which* exchange it is getting data from.
tesser-execution
Responsibility: Translates trading signals from strategies into actionable orders.
- Contents: The Order Management System (OMS). It receives a
Signaland decides how to act on it (e.g., calculate order size, set price). It then sends the order to the broker.
ExecutionClient trait object. It does not know which* exchange it is sending orders to.
Application & Connector Crates
connectors/ (Directory)
Responsibility: This is where all the specific, concrete exchange logic lives.
- Contents: One crate per exchange. Today that includes
tesser-bybit(production-grade REST + WebSocket client based on the official v5 docs) andtesser-paper(deterministic connector used by the backtester and paper trading engine). - Rule: All code that is specific to one exchange (e.g., endpoint URLs, authentication methods, JSON payload formats) must be confined to a crate within this directory.
tesser-bybit
- Implements the Bybit v5 and Binance USD-M REST/WebSocket APIs (
ExecutionClient+MarketStream) that power live trading. Select the driver viaconfig/[env].tomlexchange profiles (driver = "bybit"or"binance").
tesser-paper
- Simulates fills instantly and replays deterministic ticks/candles. Used by the backtester and as the default execution target for
live rununtil you're ready to wire real capital.
tesser-backtester
Responsibility: An offline engine that simulates a strategy's performance against historical data.
- Contents: An event loop that reads historical data, feeds it to the
tesser-datamodule, and uses thetesser-paperconnector to simulate order fills. It generates performance reports (Sharpe ratio, max drawdown, etc.). - Rule: The backtester's job is to wire the other components together in a simulated environment.
tesser-backtest-core
Responsibility: Deterministic, synchronous candle backtest core used by the browser playground and lightweight tooling.
- Contents: Next-open/no-lookahead execution semantics, target-weight rebalancing, fee/slippage model, equity curve + trade list + summary metrics.
- Rule: Keep it IO-free and WASM-friendly so it can run identically in the browser and native builds.
tesser-backtest-wasm
Responsibility: WebAssembly wrapper for tesser-backtest-core used by the docs playground.
- Contents: Minimal JSON-in/JSON-out ABI to drive the Rust backtest engine from the Next.js UI.
tesser-cli
Responsibility: The user-facing application.
- Contents: The
main.rsfile. It parses command-line arguments and configuration files to decide what to do (e.g., run a backtest, start a live trading session). - Rule: This is the application's entry point. It is responsible for initializing and connecting all the necessary components for a given task.
Getting Started
Prerequisites
- Rust toolchain (latest stable version recommended): https://rustup.rs/
Building
- Clone the repository:
git clone https://github.com/tesserspace/tesser.git
cd tesser
- Build the entire project workspace:
cargo build --release
Example: Running a Backtest
The repository now ships with a minimal end-to-end bundle under examples/:
examples/data/btcusdt1msample.parquetโ canonical 6h BTCUSDT 1m candles produced viatesser-cli data normalize.examples/data/btcusdt1msample.csvโ the raw CSV source that can be re-normalized if needed.examples/strategies/sma_cross.tomlโ double moving-average crossover tuned for the sample data.examples/strategies/rsi_reversion.tomlโ RSI mean-reversion variant for the same symbol.
cargo run -p tesser-cli -- \
backtest run \
--strategy-config examples/strategies/sma_cross.toml \
--data examples/data/btcusdt1msample.parquet \
--quantity 0.01
Swap in examples/strategies/rsi_reversion.toml (or any file under research/) to compare behaviors. Omit --data to fall back to synthetic candles, or pass multiple canonical Parquet paths to stitch larger datasets. Normalize new downloads first:
cargo run -p tesser-cli -- data normalize \
--source ./raw/binance/*.csv \
--output ./data_lake/candles \
--config ./configs/etl/sampleisocsv.toml \
--symbol binance_perp:BTCUSDT
Point --data at the resulting .parquet files (globbed or enumerated) to keep the backtester consistent and fast.
Tick-Level Backtests & Advanced Execution
The CLI now understands both candle- and tick-driven simulations. Pass --mode tick to tesser-cli backtest run alongside one or more Level 2 / trade JSONL files:
cargo run -p tesser-cli -- \
backtest run \
--strategy-config research/strategies/orderbook_scalper.toml \
--mode tick \
--lob-data data/btcusdt/orderbook.jsonl data/btcusdt/trades.jsonl
In tick mode the backtester replays historical depth snapshots through the high-fidelity MatchingEngine, honoring your strategy's limit prices, conditional orders, and latency requirements. This path is ideal for testing microstructure-sensitive strategies and validating slippage assumptions.
Execution hints now support specialized algorithms (configured through your strategies):
ExecutionHint::PeggedBestโ refreshes passive orders at the top of book using native amend/replace so queue position is preserved; tuneclipsize,refreshsecs, and the optionalminchasedistanceper strategy to control how aggressively it chases.ExecutionHint::Sniperโ waits for a target price before sweeping liquidity (used by theVolatilitySkewplaybook).ExecutionHint::TrailingStopโ arms above an activation price and issues a market exit once price retraces by the configured callback percentage, implementing an exchange-native trailing stop without relying on venue-specific order types.- Existing hints (
Twap,Vwap,IcebergSimulated) continue to work unchanged, and their state is persisted via SQLite so in-flight schedules recover from process restarts.
OrderBookScalper, CrossExchangeArb, VolatilitySkew) ship with the workspace to showcase how these hints and the matching engine interact end to end.
CLI Overview
tesser-cli is the single entry point for local research and operations:
tesser-cli --env default <COMMAND>
Commands: data download|validate|resample # Download/inspect historical data backtest run --strategy-config # Executes a single backtest (pass canonical Parquet via --data) backtest batch --config ... # Runs multiple configs and writes an optional summary CSV live run --strategy-config # Runs the live exchange stream (Bybit/Binance) + paper execution loop state inspect [--path <file>] # Prints the persisted SQLite state snapshot (use --raw for JSON) strategies # Lists compiled strategies
Configuration files live in config/. The loader merges the following sources (lowest โ highest priority):
config/default.tomlconfig/{env}.toml(selected by--env)config/local.toml(gitignored)- Environment variables prefixed with
TESSER(e.g.TESSERexchangebybittestnetapikey)
Live Trading & Observability
tesser-cli live run now drives the production event loop:
cargo run -p tesser-cli -- \
live run \
--strategy-config research/strategies/sma_cross.toml \
--quantity 0.02 \
--interval 1m
Add --exec live (and populate your [exchange.<name>] apikey/apisecret) to forward orders to the real exchange REST API instead of the paper engine. This will route live capital; use the exchange testnet profile while validating your setup.
What happens under the hood:
- Market data:
tesser-bybitmaintains a resilient WebSocket connection to the publiclinearstream (kline.<interval>.<symbol>andpublicTrade.<symbol>topics). The connection automatically heartbeats every 20s and reconnects on transient errors. - Execution & Reconciliation: Signals are routed through
tesser-executioninto the selected backend.
--exec paper: Keeps the previous behavior (instant synthetic fills).
- --exec live: Submits real REST orders. A separate private WebSocket connection listens for real-time order status updates (Accepted, Filled, Canceled) and execution reports (Fill events).
- When a real Fill is received, it's applied to the portfolio, updating your cash, positions, and realized PnL. This creates a closed-loop system where your local state reflects the exchange's reality.
- State persistence: Portfolio equity, open orders and last prices are serialized via
config.live.persistence(path defaults to./reports/live_state.db, enginesqliteorlmdb). Restart the process or runtesser-cli state inspectto review the snapshot. - State Reconciliation: On startup and periodically, the system fetches your open positions and balances via the REST API and compares them to its local state. Discrepancies are logged as warnings, providing a crucial safety net against state drift.
- Structured logging: When running
live, a JSON file is written toconfig.live.log_path(default./logs/live.json). Point Promtail/Loki/Grafana at that file to build dashboards without touching stdout logs. - Metrics: A Prometheus endpoint is exposed at
config.live.metrics_addr(default127.0.0.1:9100). Scrape/metricsto monitor tick/candle throughput, portfolio equity, order errors, and data-gap gauges. - Alerting: The
[live.alerting]section lets you enforce guardrails (max data gap, consecutive order failures, drawdown limit). Provide awebhook_url(Slack, Telegram, Alertmanager, etc.) or leave it empty for log-only alerts.
State Database Backups
The state store referenced by config.live.persistence.path is the source of truth for portfolio and order recovery. With engine = "sqlite" it's a single file; with engine = "lmdb" it's a directory containing data.mdb/lock.mdb. Treat it like any other operational database:
- Ad-hoc inspection:
tesser-cli state inspectprints a human-readable summary, while--rawdumps the JSON payload you can edit or version-control. - Offline backup: stop the live process and copy the store (for SQLite:
cp reports/livestate.db reports/livestate.db.bak; for LMDB: copy the directory). - Online backup: when a session must stay up, use SQLite's native snapshotting:
sqlite3 reports/livestate.db ".backup 'reports/livestate.db.bak'". For LMDB, usemdb_copyor take a filesystem snapshot. - Restoring: replace the file with a known-good backup and restart
tesser-cli live run; the runtime will hydrate the portfolio from the restored snapshot.
โ ๏ธ Risk warning: --exec live forwards orders exactly as produced by your strategyโthere is no extra confirmation prompt, and portfolio PnL stays paper-based until a future release. Always dry-run on the exchange testnet before pointing to mainnet keys.
โ ๏ธ Risk warning: --exec live is a real trading mode. With the new reconciliation features, the portfolio PnL is now based on real fills, but risks inherent to automated trading remain. Always dry-run on the exchange testnet before pointing to mainnet keys.
Key CLI flags:
| Flag | Description | Default | | --- | --- | --- | | --exchange | Exchange profile defined under [exchange.*] | paper_sandbox | | --category | Bybit channel (linear, inverse, spot, โฆ). Ignored for Binance. | linear | | --interval | Candle interval understood by tesser_core::Interval | 1m | | --quantity | Fixed order size routed through FixedOrderSizer | 1.0 | | --exec | Execution backend (paper or live) | paper | | --slippage-bps / --fee-bps | Synthetic execution frictions in basis points | 0 | | --latency-ms | Delay between signal and fill simulation | 0 | | --state-path, --metrics-addr, --log-path | Override the [live] config block | see config | | --webhook-url | Per-run override for [live.alerting].webhook_url | empty | | --initial-equity | Override the [backtest.initial_balances] entry for the reporting currency | see config | | --risk-max-order-qty, --risk-max-order-notional, --risk-max-position-qty, --risk-max-drawdown | Override [risk_management] guardrails | see config | | --alert-max-data-gap-secs, --alert-max-order-failures, --alert-max-drawdown | Override [live.alerting] thresholds | see config |
Inspect all options with cargo run -p tesser-cli -- live run --help.
Sample snippet from config/default.toml:
[live]
metrics_addr = "127.0.0.1:9100"
log_path = "./logs/live.json"
[live.persistence] engine = "sqlite" path = "./reports/live_state.db"
[live.alerting] webhook_url = "" # Optional HTTP endpoint maxdatagap_secs = 300 # Alert if no ticks/candles are seen for 5 minutes maxorderfailures = 3 # Trigger after N consecutive execution errors max_drawdown = 0.03 # 3% peak-to-trough drawdown guardrail
[risk_management] maxorderquantity = 1.0 # Fat-finger guard per order (base asset qty) maxordernotional = 0.0 # Optional notional clamp (quote currency); remove/zero to disable maxpositionquantity = 2.0 # Absolute cap on aggregate exposure per symbol max_drawdown = 0.05 # Liquidate-only kill switch threshold (fractional)
Every CLI flag (e.g., --state-path, --persistence, --metrics-addr, --log-path, --initial-equity, --risk-max-, --alert-max-) overrides the config file so you can spin up multiple isolated sessions with tailored risk and telemetry controls.
When tesser-cli live run executes, each order is filtered through the pre-trade risk layer: quantities above maxorderquantity are rejected, estimated notional above maxordernotional is blocked, projected exposure cannot exceed maxpositionquantity, and once equity suffers a drawdown beyond max_drawdown the portfolio flips into liquidate-only mode (only allowing exposure-reducing orders) until the process is restarted.
Multi-Strategy Deployment (Multi-Process Model)
Instead of embedding a heavy multi-strategy scheduler inside the binary, the recommended path is to run one tesser-cli live run process per strategy. Each process receives its own SQLite state file, metrics port, log file, risk guardrails, and alert thresholds via the CLI overrides above. This makes it trivial to mix and match paper/live runs or roll strategies independently via Docker Compose, Nomad, or systemd.
Compose example:
version: "3.9"
services:
momentum:
image: ghcr.io/tesserspace/tesser:latest
restart: unless-stopped
volumes:
- ./config:/app/config:ro
- ./reports:/app/reports
- ./strategies:/app/strategies:ro
- ./logs:/app/logs
command: >
tesser-cli --env prod live run
--strategy-config /app/strategies/sma_cross.toml
--quantity 0.5
--state-path /app/reports/momentum_state.db
--metrics-addr 0.0.0.0:9200
--log-path /app/logs/momentum.json
--initial-equity 20000
--risk-max-order-qty 0.5
--risk-max-position-qty 1.0
--alert-max-data-gap-secs 120
--webhook-url https://hooks.slack.com/services/XXX/YYY/ZZZ
meanrev:
image: ghcr.io/tesserspace/tesser:latest
restart: unless-stopped
volumes:
- ./config:/app/config:ro
- ./reports:/app/reports
- ./strategies:/app/strategies:ro
- ./logs:/app/logs
command: >
tesser-cli --env prod live run
--strategy-config /app/strategies/rsi_reversion.toml
--quantity 0.2
--state-path /app/reports/meanrev_state.db
--metrics-addr 0.0.0.0:9201
--log-path /app/logs/meanrev.json
--initial-equity 15000
--risk-max-order-qty 0.25
--risk-max-position-qty 0.5
--alert-max-drawdown 0.02
Bring the stack up with docker compose up -d and point Prometheus at the exposed ports (9200, 9201, โฆ). Because state lives in independent SQLite files, restarting or upgrading one strategy does not impact the others; you can introspect each database with tesser-cli state inspect --path reports/momentum_state.db before and after rollouts. Systemd and other supervisors work the same wayโsimply copy the command, change the overrides, and keep the services isolated.
Python Research Workflow
Rust handles live execution; Python (powered by uv) owns fast research loops. The research/ directory provides:
research/
โโโ notebooks/ # Exploratory analysis
โโโ scripts/ # Batch jobs (e.g., parameter sweeps)
โโโ strategies/ # Outputs consumed by Rust (TOML, ONNX, etc.)
โโโ pyproject.toml # Locked dependencies for uv
Quick start:
cd research
uv venv
source .venv/bin/activate
uv pip install -e .
uv run python scripts/findoptimalsma.py --data ../data/btc.parquet
uv run python scripts/optimizersi.py --data ../data/btc.parquet --output strategies/rsifrom_python.toml
uv run python scripts/trainmlclassifier.py --data ../data/btc.parquet --output models/ml_linear.toml
The generated TOML files feed directly into tesser-cli backtest run --strategy-config ....
Strategy Portfolio
The upgraded tesser-strategy crate bundles a diverse suite for pressure-testing the stack:
| Name | Type | Highlights | | --- | --- | --- | | SmaCross | Trend following | Dual moving-average crossover | | RsiReversion | Mean reversion | RSI thresholds with configurable lookbacks | | BollingerBreakout | Volatility/Band breakout | Uses standard deviation bands for entries | | MlClassifier | Machine learning | Loads an external model artifact for real-time inference | | PairsTradingArbitrage | Statistical arbitrage | Operates on two correlated symbols (configure as exchange:SYMBOL, e.g., binance_perp:BTCUSDT) | | OrderBookImbalance | Microstructure | Consumes order-book snapshots to trade short-term imbalances |
Each strategy exposes a typed configuration schema and registers the symbols (one or many) it operates on. Sample configs live in research/strategies/ and the ML artifact in research/models/, so you can run them directly with the CLI.
Note: Multi-symbol strategies (e.g.,PairsTradingArbitrage) require fully-qualified identifiers in the formexchange:instrumentso the router can distinguish venues.
Contributing
Contributions are highly welcome! To get started, please read our CODING_STYLE.md guide. A great way to contribute is by adding a new exchange implementation in the connectors/ directory.
License
This project is licensed under either of
- Apache License, Version 2.0, (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
Disclaimer
Trading financial markets involves substantial risk. Tesser is a software framework and not financial advice. All trading decisions are your own. The authors and contributors are not responsible for any financial losses. Always test your strategies thoroughly in a simulated environment before deploying with real capital.