Skip to content

tesserspace/tesser

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Tesser: A High-Performance Quantitative Trading Framework in Rust

CI License: MIT/Apache-2.0 Crates.io

Tesser

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-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 like Order, Trade, Candle, Position, Tick. enums like Side (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) like MarketStream (for subscribing to live data) and ExecutionClient (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-powered EventBus and 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 Strategy trait. This is where you calculate indicators (e.g., RSI, Moving Averages) and generate Signal events (e.g., "go long", "exit position").
  • Rule: Strategy code must be pure and self-contained. It should only depend on tesser-core for 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 f64 drift by defaulting to rust_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 Fill events (i.e., when an order is executed).
  • Rule: Portfolio logic should not be aware of any specific exchange. It works with the abstract Position and Fill types from tesser-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 MarketStream implementation) and historical data feeds. It is responsible for tasks like building candles from ticks or replaying historical data files.
  • Rule: This crate uses a generic 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 Signal and decides how to act on it (e.g., calculate order size, set price). It then sends the order to the broker.
  • Rule: This crate uses a generic 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) and tesser-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 via config/[env].toml exchange 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 run until 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-data module, and uses the tesser-paper connector 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-cli

Responsibility: The user-facing application.

  • Contents: The main.rs file. 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

Building

  1. Clone the repository:

    git clone https://github.com/tesserspace/tesser.git
    cd tesser
  2. 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/btcusdt_1m_sample.parquet – canonical 6h BTCUSDT 1m candles produced via tesser-cli data normalize.
  • examples/data/btcusdt_1m_sample.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.

Use them to exercise the entire pipeline without downloading external data:

cargo run -p tesser-cli -- \
    backtest run \
    --strategy-config examples/strategies/sma_cross.toml \
    --data examples/data/btcusdt_1m_sample.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/sample_iso_csv.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; tune clip_size, refresh_secs, and the optional min_chase_distance per strategy to control how aggressively it chases.
  • ExecutionHint::Sniper – waits for a target price before sweeping liquidity (used by the VolatilitySkew playbook).
  • 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.

Additional indicators (ATR, MACD, Ichimoku Cloud) and reference strategies (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):

  1. config/default.toml
  2. config/{env}.toml (selected by --env)
  3. config/local.toml (gitignored)
  4. Environment variables prefixed with TESSER_ (e.g. TESSER_exchange__bybit_testnet__api_key)

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>] api_key/api_secret) 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-bybit maintains a resilient WebSocket connection to the public linear stream (kline.<interval>.<symbol> and publicTrade.<symbol> topics). The connection automatically heartbeats every 20s and reconnects on transient errors.
  • Execution & Reconciliation: Signals are routed through tesser-execution into 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, engine sqlite or lmdb). Restart the process or run tesser-cli state inspect to 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 to config.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 (default 127.0.0.1:9100). Scrape /metrics to 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 a webhook_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:

  1. Ad-hoc inspection: tesser-cli state inspect prints a human-readable summary, while --raw dumps the JSON payload you can edit or version-control.
  2. Offline backup: stop the live process and copy the store (for SQLite: cp reports/live_state.db reports/live_state.db.bak; for LMDB: copy the directory).
  3. Online backup: when a session must stay up, use SQLite's native snapshotting: sqlite3 reports/live_state.db ".backup 'reports/live_state.db.bak'". For LMDB, use mdb_copy or take a filesystem snapshot.
  4. 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
max_data_gap_secs = 300     # Alert if no ticks/candles are seen for 5 minutes
max_order_failures = 3      # Trigger after N consecutive execution errors
max_drawdown = 0.03         # 3% peak-to-trough drawdown guardrail

[risk_management]
max_order_quantity = 1.0    # Fat-finger guard per order (base asset qty)
max_order_notional = 0.0    # Optional notional clamp (quote currency); remove/zero to disable
max_position_quantity = 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 max_order_quantity are rejected, estimated notional above max_order_notional is blocked, projected exposure cannot exceed max_position_quantity, 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/find_optimal_sma.py --data ../data/btc.parquet
uv run python scripts/optimize_rsi.py --data ../data/btc.parquet --output strategies/rsi_from_python.toml
uv run python scripts/train_ml_classifier.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 form exchange:instrument so 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

at your option.

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.