Skip to main content

Rust-backed Python package for target-weight backtests, portfolio metrics, deterministic order-book simulation, and pre-trade risk checks

Project description

nanobook

CI crates.io docs.rs License: MIT cargo-deny

Rust execution layer for Python trading strategies.

nanobook is for traders and researchers who already generate signals or target weights in Python, but want execution mechanics handled by compiled, deterministic Rust code: portfolio accounting, transaction costs, stops, limit-order-book simulation, pre-trade risk checks, and optional IBKR rebalancing.

Keep factor research, sizing, and scheduling in Python. Use nanobook for the stateful execution layer around it.

Architecture

┌─────────────────────────────────────────────────┐
│        Your Python Strategy  (private)          │
│   Factors · Signals · Sizing · Scheduling       │
├─────────────────────────────────────────────────┤
│            nanobook  (Rust, open-source)         │
│  ┌──────────┬──────────┬──────────┬──────────┐  │
│  │  Broker  │   Risk   │Portfolio │   LOB    │  │
│  │   IBKR   │  Engine  │Simulator │  Engine  │  │
│  │  Binance │ PreTrade │ Backtest │ 6M ops/s │  │
│  └──────────┴──────────┴──────────┴──────────┘  │
│   Rebalancer CLI: weights → diff → execute      │
└─────────────────────────────────────────────────┘

Use Cases

  • Backtest target-weight strategies from Python with Rust portfolio accounting.
  • Simulate rebalances, transaction costs, fixed/trailing stops, and portfolio metrics.
  • Test limit-order-book behavior with deterministic matching and event logs.
  • Run pre-trade risk checks for concentration, leverage, and short exposure.
  • Rebalance an IBKR account from a target-weight file with dry-run and audit logs.

What nanobook is NOT

  • Not a full trading platform. For venue breadth, calendars, and operator UIs, see NautilusTrader or LEAN.
  • Not a connector zoo. For broad crypto exchange coverage, see CCXT or Hummingbot.
  • Not a research framework. For vectorized backtests and factor research, see vectorbt or Riskfolio-Lib.
  • Not FIX. No FIX 4.4 / 5.0 adapter and none is planned.
  • Not an OMS (yet). Event-sourced OMS with deterministic replay is on the v1.0 roadmap.

nanobook is a compact Rust execution kernel for Python strategies: limit-order-book matching, portfolio simulation, broker abstraction, pre-trade risk, and rebalancer, all in one MIT-licensed workspace.

Python computes what to trade — factor rankings, signals, target weights. nanobook executes how — order routing, risk checks, portfolio simulation, and a deterministic matching engine. Clean separation: strategy logic stays in Python, execution runs in Rust.

Workspace

Crate Description
nanobook Core Rust crate: deterministic LOB, portfolio simulator, metrics, backtest bridge, GARCH, optimizers
nanobook-broker Broker trait with IBKR and Binance adapters
nanobook-risk Pre-trade risk engine (position limits, leverage, short exposure)
nanobook-python PyO3 bindings for all layers
nanobook-rebalancer CLI: target weights → IBKR execution with audit trail

Install

Python:

pip install nanobook

Rust:

[dependencies]
nanobook = "0.9.3"

From source:

git clone https://github.com/ricardofrantz/nanobook
cd nanobook
cargo build --release
cargo test

# Python bindings
cd python && maturin develop --release

# Binance adapter (feature-gated, not in PyPI wheels)
cd python && maturin develop --release --features binance

The Bridge: Python Strategy → Rust Execution

The canonical integration pattern — Python computes a weight schedule, Rust simulates the portfolio and returns metrics:

import nanobook

result = nanobook.backtest_weights(
    weight_schedule=[
        [("AAPL", 0.5), ("MSFT", 0.5)],
        [("AAPL", 0.3), ("NVDA", 0.7)],
    ],
    price_schedule=[
        [("AAPL", 185_00), ("MSFT", 370_00)],
        [("AAPL", 190_00), ("MSFT", 380_00), ("NVDA", 600_00)],
    ],
    initial_cash=1_000_000_00,  # $1M in cents
    cost_bps=15,                # 15 bps round-trip
    stop_cfg={"trailing_stop_pct": 0.05},
)

print(f"Sharpe: {result['metrics'].sharpe:.2f}")
print(f"Max DD: {result['metrics'].max_drawdown:.1%}")
print(result["holdings"][-1])    # per-period symbol weights
print(result["stop_events"])     # stop trigger metadata

Your optimizer produces weights. backtest_weights() handles rebalancing, cost modeling, position tracking, and return computation at compiled speed with the GIL released.

v0.9 additions: fixed-parameter EWMA-style GARCH forecasting, portfolio optimizers (min-variance, max-Sharpe, risk-parity, inverse CVaR, inverse CDaR), and trailing/fixed stop-loss simulation — all accessible from Python.

Optimizer Example

import nanobook
import numpy as np

# Daily returns matrix (T × N)
returns = np.random.randn(252, 5) * 0.01

weights = nanobook.optimize_max_sharpe(returns, risk_free_rate=0.0)
print(dict(zip(["A","B","C","D","E"], weights)))

Layer Examples

LOB Engine (Rust)

use nanobook::{Exchange, Side, Price, TimeInForce};

let mut exchange = Exchange::new();
exchange.submit_limit(Side::Sell, Price(50_00), 100, TimeInForce::GTC);
let result = exchange.submit_limit(Side::Buy, Price(50_00), 100, TimeInForce::GTC);

assert_eq!(result.trades.len(), 1);
assert_eq!(result.trades[0].price, Price(50_00));

Portfolio + Metrics (Python)

portfolio = nanobook.Portfolio(1_000_000_00, nanobook.CostModel(commission_bps=5))
portfolio.rebalance_simple([("AAPL", 0.6)], [("AAPL", 150_00)])
portfolio.record_return([("AAPL", 155_00)])
metrics = portfolio.compute_metrics(252.0, 0.0)
print(f"Sharpe: {metrics.sharpe:.2f}")

Broker + Risk (Python)

# Pre-trade risk check
risk = nanobook.RiskEngine(max_position_pct=0.25, max_leverage=1.5)
checks = risk.check_order("AAPL", "buy", 100, 185_00,
                          equity_cents=1_000_000_00,
                          positions=[("AAPL", 200)])

# Execute through IBKR
broker = nanobook.IbkrBroker("127.0.0.1", 4002, client_id=1)
broker.connect()
oid = broker.submit_order("AAPL", "buy", 100, order_type="limit",
                          limit_price_cents=185_00)

Rebalancer CLI

# Build
cargo build -p nanobook-rebalancer --release

# Dry run — show plan without executing
rebalancer run target.json --dry-run

# Execute with confirmation prompt
rebalancer run target.json

# Compare actual vs target positions
rebalancer reconcile target.json

Performance

Single-threaded benchmarks (macOS arm64, v0.10.0, stock clocks):

Operation Latency Throughput
Submit (no match) ~155 ns ~6.4M ops/sec
Submit (with match) ~197 ns ~5M ops/sec
BBO query ~1.1 ns ~900M ops/sec
Cancel (tombstone, deep queue) ~385 ns ~2.6M ops/sec
L2 snapshot (10 levels) ~255 ns ~4M ops/sec

Numbers are criterion medians measured with cargo bench --bench throughput. Hardware, build flags, and background load all move these ±10-20 %. See benches/README.md for the methodology and benches/v0.10-comparison.md for the v0.9.3 → v0.10.0 delta (the v0.10.0 submit path pays a ~20 ns cost for the new self-trade-prevention field even with STP off — a future revision will lift the off-path check out of the matching loop).

Single-threaded throughput is roughly equivalent to Numba (both compile to LLVM IR). Where Rust wins: zero cold-start, true parallelism via Rayon with no GIL contention, and deterministic memory without GC pauses.

cargo bench

Feature Flags

Feature Default Description
event-log Yes Event recording for deterministic replay
serde No Serialize/deserialize all public types
persistence No File-based event sourcing (JSON Lines)
portfolio No Portfolio engine, position tracking, metrics, strategy trait
parallel No Rayon-based parallel parameter sweeps
itch No NASDAQ ITCH 5.0 binary protocol parser

Design Constraints

Engineering decisions that keep the system simple and fast:

  • Single-threaded — deterministic by design; same inputs always produce same outputs
  • In-process — no networking overhead; wrap externally if needed
  • No compliance layer — no self-trade prevention or circuit breakers (out of scope)
  • No complex order types — no iceberg or pegged orders

Documentation

  • Full developer reference is merged below in this README (## Full Reference (Merged from DOC.md)).
  • docs.rs — Rust API docs

License

MIT

Full Reference (Merged from DOC.md)

CI crates.io docs.rs License: MIT

Developer Reference — Full API documentation for the nanobook workspace.


Table of Contents


Quick Start

[dependencies]
nanobook = "0.9"
use nanobook::{Exchange, Side, Price, TimeInForce};

let mut exchange = Exchange::new();
exchange.submit_limit(Side::Sell, Price(50_00), 100, TimeInForce::GTC);
let result = exchange.submit_limit(Side::Buy, Price(50_00), 100, TimeInForce::GTC);

assert_eq!(result.trades.len(), 1);
assert_eq!(result.trades[0].price, Price(50_00));

Core Concepts

Prices

Prices are integers in the smallest currency unit (cents for USD), avoiding floating-point errors.

let price = Price(100_50);  // $100.50
assert!(Price(100_00) < Price(101_00));

Display formats as dollars: Price(10050) prints as $100.50.

Constants: Price::ZERO, Price::MAX (market buys), Price::MIN (market sells).

Quantities and Timestamps

  • Quantity = u64 — shares or contracts, always positive.
  • Timestamp = u64 — monotonic nanosecond counter (not system clock), guaranteeing deterministic ordering.

Determinism

No randomness anywhere. Same sequence of operations always produces identical trades. Event replay reconstructs exact state.


Exchange API

Exchange is the main entry point, wrapping an OrderBook with order submission, cancellation, modification, and queries.

Order Submission

// Limit order — matches against opposite side, remainder handled by TIF
let result = exchange.submit_limit(Side::Buy, Price(100_00), 100, TimeInForce::GTC);

// Market order — IOC semantics at Price::MAX (buy) or Price::MIN (sell)
let result = exchange.submit_market(Side::Buy, 500);

Order Management

// Cancel — O(1) via tombstones
let result = exchange.cancel(order_id);  // CancelResult { success, cancelled_quantity, error }

// Modify — cancel + replace (loses time priority, gets new OrderId)
let result = exchange.modify(order_id, Price(101_00), 200);

Queries

let (bid, ask) = exchange.best_bid_ask();  // L1 — O(1)
let spread = exchange.spread();             // Option<i64>
let snap = exchange.depth(10);              // L2 — top 10 levels
let full = exchange.full_book();            // L3 — everything
let order = exchange.get_order(OrderId(1)); // Option<&Order>
let trades = exchange.trades();             // &[Trade]

Memory Management

exchange.clear_trades();           // Clear trade history
exchange.clear_order_history();    // Remove filled/cancelled orders
exchange.compact();                // Reclaim tombstone memory

Input Validation

The try_submit_* methods validate inputs before processing:

let err = exchange.try_submit_limit(Side::Buy, Price(0), 100, TimeInForce::GTC);
assert_eq!(err.unwrap_err(), ValidationError::ZeroPrice);

Time-in-Force Semantics

TIF Partial Fill? Rests on Book? No Liquidity
GTC Yes Yes (remainder) Rests entirely
IOC Yes No (remainder cancelled) Cancelled
FOK No No (all-or-nothing) Cancelled

Types Reference

Type Definition Description Display
Price struct Price(pub i64) Price in smallest units (cents) $100.50
Quantity type Quantity = u64 Number of shares/contracts
OrderId struct OrderId(pub u64) Unique order identifier O42
TradeId struct TradeId(pub u64) Unique trade identifier T7
Timestamp type Timestamp = u64 Nanosecond counter (not wall clock)

Result Types

pub struct SubmitResult {
    pub order_id: OrderId,
    pub status: OrderStatus,          // New | PartiallyFilled | Filled | Cancelled
    pub trades: Vec<Trade>,
    pub filled_quantity: Quantity,
    pub resting_quantity: Quantity,
    pub cancelled_quantity: Quantity,
}

pub struct Trade {
    pub id: TradeId,
    pub price: Price,                 // Resting order's price (aggressor gets price improvement)
    pub quantity: Quantity,
    pub aggressor_order_id: OrderId,
    pub passive_order_id: OrderId,
    pub aggressor_side: Side,
    pub timestamp: Timestamp,
}

Enums

Enum Variants Key Methods
Side Buy, Sell opposite()
TimeInForce GTC, IOC, FOK can_rest(), allows_partial()
OrderStatus New, PartiallyFilled, Filled, Cancelled is_active(), is_terminal()

Book Snapshots

pub struct BookSnapshot {
    pub bids: Vec<LevelSnapshot>,  // Highest price first
    pub asks: Vec<LevelSnapshot>,  // Lowest price first
    pub timestamp: Timestamp,
}

pub struct LevelSnapshot {
    pub price: Price,
    pub quantity: Quantity,
    pub order_count: usize,
}
Method Returns
snap.best_bid() / best_ask() Option<Price>
snap.spread() Option<i64>
snap.mid_price() Option<f64>
snap.total_bid_quantity() / total_ask_quantity() Quantity
snap.imbalance() Option<f64> — [-1.0, 1.0], positive = buy pressure
snap.weighted_mid() Option<f64> — leans toward less liquid side

Stop Orders & Trailing Stops

Stop Orders

// Stop-market: triggers market order when last trade price hits stop
exchange.submit_stop_market(Side::Sell, Price(95_00), 100);

// Stop-limit: triggers limit order at limit_price when stop hits
exchange.submit_stop_limit(Side::Sell, Price(95_00), Price(94_50), 100, TimeInForce::GTC);
Side Triggers When
Buy stop last_trade_price >= stop_price
Sell stop last_trade_price <= stop_price

Key behaviors: immediate trigger if price already past stop, cascade up to 100 iterations, cancel via exchange.cancel(stop_id).

Trailing Stops

Three trailing methods — stop price tracks the market and only moves in the favorable direction:

// Fixed: triggers if price drops $2.00 from peak
exchange.submit_trailing_stop_market(Side::Sell, Price(98_00), 100, TrailMethod::Fixed(200));

// Percentage: trail by 5% from peak
exchange.submit_trailing_stop_market(Side::Sell, Price(95_00), 100, TrailMethod::Percentage(0.05));

// Adaptive trail: 2x simple moving average of |Δ price| over the last
// 14 trades. NOT Wilder's ATR (no OHLC available at the stop module);
// for true Wilder ATR, pre-compute via `indicators::atr` on bar data
// and pass the result as `TrailMethod::Fixed(offset_cents)`.
exchange.submit_trailing_stop_market(Side::Sell, Price(95_00), 100,
    TrailMethod::SmaAbsChange { multiplier: 2.0, period: 14 });

Trailing stop-limit variant: submit_trailing_stop_limit() — same parameters plus limit_price and TimeInForce.


Event Replay

Feature flag: event-log (enabled by default)

Every operation is recorded as an Event. Replaying events on a fresh exchange produces identical state.

// Save events
let events = exchange.events().to_vec();

// Reconstruct exact state
let replayed = Exchange::replay(&events);
assert_eq!(exchange.best_bid_ask(), replayed.best_bid_ask());

Event types: SubmitLimit, SubmitMarket, Cancel, Modify.

Disable for max performance:

nanobook = { version = "0.9", default-features = false }

Symbol & MultiExchange

Symbol

Fixed-size instrument identifier. [u8; 8] inline — Copy, no heap allocation, max 8 ASCII bytes.

let sym = Symbol::new("AAPL");
assert!(Symbol::try_new("TOOLONGNAME").is_none());

MultiExchange

Independent per-symbol order books:

let mut multi = MultiExchange::new();
let aapl = Symbol::new("AAPL");

multi.get_or_create(&aapl).submit_limit(Side::Sell, Price(150_00), 100, TimeInForce::GTC);

for (sym, bid, ask) in multi.best_prices() {
    println!("{sym}: bid={bid:?} ask={ask:?}");
}

Portfolio Engine

Feature flag: portfolio

Tracks cash, positions, costs, returns, and equity over time.

use nanobook::portfolio::{Portfolio, CostModel};

let cost = CostModel { commission_bps: 5, slippage_bps: 3, min_trade_fee: 1_00 };
let mut portfolio = Portfolio::new(1_000_000_00, cost);

// Rebalance to target weights
portfolio.rebalance_simple(&[(Symbol::new("AAPL"), 0.6)], &[(Symbol::new("AAPL"), 150_00)]);

// Record period return and compute metrics
portfolio.record_return(&[(Symbol::new("AAPL"), 155_00)]);
let metrics = compute_metrics(portfolio.returns(), 252.0, 0.0);

Execution Modes

  • SimpleFill — instant at bar prices: portfolio.rebalance_simple(targets, prices)
  • LOBFill — route through Exchange matching engines: portfolio.rebalance_lob(targets, exchanges)

Position

Per-symbol tracking with VWAP entry price and realized PnL:

let mut pos = Position::new(Symbol::new("AAPL"));
pos.apply_fill(100, 150_00);   // buy 100 @ $150
pos.apply_fill(-50, 160_00);   // sell 50 @ $160 → $500 realized PnL

Financial Metrics

compute_metrics(&returns, periods_per_year, risk_free) returns: total_return, cagr, volatility, sharpe, sortino, max_drawdown, calmar, num_periods, winning_periods, losing_periods.

Parallel Sweep

Feature flag: parallel (implies portfolio)

use nanobook::portfolio::sweep::sweep;

let results = sweep(&params, 12.0, 0.0, |&leverage| {
    vec![0.01 * leverage, -0.005 * leverage]
});

Strategy Trait

Feature flag: portfolio

Implement compute_weights() for batch-oriented backtesting:

impl Strategy for MomentumStrategy {
    fn compute_weights(
        &self,
        bar_index: usize,
        prices: &[(Symbol, i64)],
        _portfolio: &Portfolio,
    ) -> Vec<(Symbol, f64)> {
        if bar_index < self.lookback { return vec![]; }
        let w = 1.0 / prices.len() as f64;
        prices.iter().map(|(sym, _)| (*sym, w)).collect()
    }
}

let result = run_backtest(&strategy, &price_series, 1_000_000_00, CostModel::zero(), 12.0, 0.0);

Built-in: EqualWeight strategy. Parallel variant: sweep_strategy().


Backtest Bridge

The bridge between Python strategy code and Rust execution. Python computes a weight schedule, Rust simulates the portfolio at compiled speed.

Rust API

use nanobook::backtest_bridge::backtest_weights;

let result = backtest_weights(
    &weight_schedule,    // &[Vec<(Symbol, f64)>] — target weights per period
    &price_schedule,     // &[Vec<(Symbol, i64)>] — prices per period
    1_000_000_00,        // initial cash in cents
    15,                  // cost in basis points
    252.0,               // periods per year
    0.0,                 // risk-free rate per period
);

Returns BacktestBridgeResult:

Field Type Description
returns Vec<f64> Per-period returns
equity_curve Vec<i64> Equity at each period (cents)
final_cash i64 Ending cash balance
metrics Option<Metrics> Sharpe, Sortino, max drawdown, etc.
holdings Vec<Vec<(Symbol, f64)>> Per-period holdings weights
symbol_returns Vec<Vec<(Symbol, f64)>> Per-period close-to-close symbol returns
stop_events Vec<BacktestStopEvent> Stop trigger metadata (index, symbol, price, reason)

Python API

result = nanobook.py_backtest_weights(
    weight_schedule=[[("AAPL", 0.5), ("MSFT", 0.5)], ...],
    price_schedule=[[("AAPL", 185_00), ("MSFT", 370_00)], ...],
    initial_cash=1_000_000_00,
    cost_bps=15,
    periods_per_year=252.0,
    risk_free=0.0,
    stop_cfg={"trailing_stop_pct": 0.05},
)
# result["returns"], result["equity_curve"], result["metrics"],
# result["holdings"], result["symbol_returns"], result["stop_events"]

GIL is released during computation for maximum throughput.

Clean aliases (no py_ prefix) are exported for new integrations: backtest_weights, capabilities, garch_ewma_forecast, inverse_cvar_weights, inverse_cdar_weights, and other optimizer helpers.

qtrade v0.4 Bridge Pattern

Capability probing contract used by calc.bridge:

import nanobook

def has_nanobook_feature(name: str) -> bool:
    caps = set(nanobook.py_capabilities()) if hasattr(nanobook, "py_capabilities") else set()
    if name in caps:
        return True

    symbol_map = {
        "backtest_stops": "py_backtest_weights",
        "garch_ewma_forecast": "py_garch_ewma_forecast",
        "optimize_min_variance": "py_optimize_min_variance",
        "optimize_max_sharpe": "py_optimize_max_sharpe",
        "optimize_risk_parity": "py_optimize_risk_parity",
        "inverse_cvar_weights": "py_inverse_cvar_weights",
        "inverse_cdar_weights": "py_inverse_cdar_weights",
        "backtest_holdings": "py_backtest_weights",
    }
    sym = symbol_map.get(name)
    return bool(sym and hasattr(nanobook, sym))

Broker Abstraction

Crate: nanobook-broker

Generic trait over brokerages with concrete adapters for IBKR and Binance.

Broker Trait

pub trait Broker {
    fn connect(&mut self) -> Result<(), BrokerError>;
    fn disconnect(&mut self) -> Result<(), BrokerError>;
    fn positions(&self) -> Result<Vec<Position>, BrokerError>;
    fn account(&self) -> Result<Account, BrokerError>;
    fn submit_order(&self, order: &BrokerOrder) -> Result<OrderId, BrokerError>;
    fn order_status(&self, id: OrderId) -> Result<BrokerOrderStatus, BrokerError>;
    fn cancel_order(&self, id: OrderId) -> Result<(), BrokerError>;
    fn quote(&self, symbol: &Symbol) -> Result<Quote, BrokerError>;
}

Key Types

pub struct Position {
    pub symbol: Symbol,
    pub quantity: i64,             // Positive = long, negative = short
    pub avg_cost_cents: i64,
    pub market_value_cents: i64,
    pub unrealized_pnl_cents: i64,
}

pub struct Account {
    pub equity_cents: i64,
    pub buying_power_cents: i64,
    pub cash_cents: i64,
    pub gross_position_value_cents: i64,
}

pub struct BrokerOrder {
    pub symbol: Symbol,
    pub side: BrokerSide,          // Buy or Sell
    pub quantity: u64,
    pub order_type: BrokerOrderType,  // Market or Limit(Price)
}

pub struct Quote {
    pub symbol: Symbol,
    pub bid_cents: i64,
    pub ask_cents: i64,
    pub last_cents: i64,
    pub volume: u64,
}

IBKR Adapter

Feature: ibkr

Connects to TWS/Gateway via the ibapi crate (blocking API).

let mut broker = IbkrBroker::new("127.0.0.1", 4002, 1);  // 4002 = paper, 4001 = live
broker.connect()?;
let positions = broker.positions()?;
let quote = broker.quote(&Symbol::new("AAPL"))?;

Binance Adapter

Feature: binance

REST API via reqwest::blocking. Converts nanobook symbols (e.g., "BTC") to Binance pairs (e.g., "BTCUSDT").

let mut broker = BinanceBroker::new(api_key, secret_key, true);  // testnet
broker.connect()?;

Python

broker = nanobook.IbkrBroker("127.0.0.1", 4002, client_id=1)
broker.connect()
positions = broker.positions()   # List[Dict] with symbol, quantity, avg_cost_cents, ...
oid = broker.submit_order("AAPL", "buy", 100, order_type="limit", limit_price_cents=185_00)
quote = broker.quote("AAPL")     # Dict with bid_cents, ask_cents, last_cents, volume

broker = nanobook.BinanceBroker(api_key, secret_key, testnet=True, quote_asset="USDT")

Risk Engine

Crate: nanobook-risk

Pre-trade risk validation for single orders and rebalance batches.

RiskConfig

pub struct RiskConfig {
    pub max_position_pct: f64,       // Max single position as fraction of equity (default 0.25)
    pub max_order_value_cents: i64,  // Max single order value
    pub max_batch_value_cents: i64,  // Max rebalance batch value
    pub max_leverage: f64,           // Max gross exposure / equity (default 1.5)
    pub max_drawdown_pct: f64,       // Circuit breaker threshold (default 0.20)
    pub allow_short: bool,           // Allow short positions (default true)
    pub max_short_pct: f64,          // Max short exposure fraction (default 0.30)
    pub min_trade_usd: f64,
    pub max_trade_usd: f64,          // Max single trade USD (default 100,000)
}

Notes:

  • max_drawdown_pct is validated at engine construction and preserved in config, but not yet used in execution-time checks.

Single Order Check

let engine = RiskEngine::new(RiskConfig::default());
let report = engine.check_order(
    &Symbol::new("AAPL"),
    BrokerSide::Buy,
    100,              // quantity
    185_00,           // price in cents
    &account,
    &current_positions,
);

if report.has_failures() {
    // Order violates risk limits — position concentration, short selling, etc.
}

Batch Check

Validates a full rebalance against position limits, leverage, and short exposure:

let report = engine.check_batch(
    &orders,              // &[(Symbol, BrokerSide, u64, i64)]
    &account,
    &current_positions,
    &target_weights,      // &[(Symbol, f64)]
);

RiskReport

pub struct RiskReport {
    pub checks: Vec<RiskCheck>,
}

pub struct RiskCheck {
    pub name: &'static str,
    pub status: RiskStatus,  // Pass | Warn | Fail
    pub detail: String,
}

impl RiskReport {
    pub fn has_failures(&self) -> bool;
    pub fn has_warnings(&self) -> bool;
}

Python

risk = nanobook.RiskEngine(max_position_pct=0.25, max_leverage=1.5)

# Single order
checks = risk.check_order("AAPL", "buy", 100, 185_00,
                          equity_cents=1_000_000_00,
                          positions=[("AAPL", 200)])

# Batch (full rebalance)
checks = risk.check_batch(
    orders=[("AAPL", "buy", 100, 185_00), ("MSFT", "sell", 50, 370_00)],
    equity_cents=1_000_000_00,
    positions=[("AAPL", 200), ("MSFT", 100)],
    target_weights=[("AAPL", 0.6), ("MSFT", 0.2)],
)
# Each check: {"name": "...", "status": "Pass|Warn|Fail", "detail": "..."}

Rebalancer CLI

Crate: nanobook-rebalancer

CLI tool that bridges target weights to IBKR execution with risk checks, rate limiting, and audit trail.

Pipeline

  1. Read target weights from target.json (output of your optimizer)
  2. Connect to IBKR Gateway for live positions, prices, account data
  3. Compute CURRENT → TARGET diff (share quantities, limit prices)
  4. Run pre-trade risk checks (position limits, leverage, short exposure)
  5. Show plan, confirm (or --force for automation)
  6. Execute limit orders with rate limiting and timeout-based cancellation
  7. Reconcile and log to JSONL audit trail

Commands

rebalancer status                     # Check IBKR connection
rebalancer positions                  # Show current positions
rebalancer run target.json            # Plan → confirm → execute
rebalancer run target.json --dry-run  # Plan only
rebalancer run target.json --force    # Skip confirmation (cron/automation)
rebalancer reconcile target.json      # Compare actual vs target

target.json

{
  "timestamp": "2026-02-08T15:30:00Z",
  "targets": [
    { "symbol": "AAPL", "weight": 0.40 },
    { "symbol": "MSFT", "weight": 0.30 },
    { "symbol": "SPY",  "weight": -0.10 },
    { "symbol": "QQQ",  "weight": 0.20 }
  ],
  "constraints": {
    "max_position_pct": 0.40,
    "max_leverage": 1.5
  }
}

Positive weights are long, negative are short. Symbols absent from the target but present in the account get closed. See rebalancer/config.toml.example for the full configuration reference.


Python Bindings

Install: pip install nanobook or cd python && maturin develop --release

Exchange

ex = nanobook.Exchange()
result = ex.submit_limit("buy", 10050, 100, "gtc")
result = ex.submit_market("sell", 50)
ex.cancel(result.order_id)
bid, ask = ex.best_bid_ask()
snap = ex.depth(10)

Stop Orders

ex.submit_stop_market("sell", 9500, 100)
ex.submit_stop_limit("buy", 10500, 10600, 100, "gtc")
ex.submit_trailing_stop_market("sell", 9500, 100, "percentage", 0.05)

Portfolio

portfolio = nanobook.Portfolio(1_000_000_00, nanobook.CostModel(commission_bps=10))
portfolio.rebalance_simple([("AAPL", 0.6)], [("AAPL", 150_00)])
portfolio.record_return([("AAPL", 155_00)])
metrics = portfolio.compute_metrics(252.0, 0.0)

Strategy Callback

result = nanobook.run_backtest(
    strategy=lambda bar, prices, portfolio: [("AAPL", 0.5), ("GOOG", 0.5)],
    price_series=[{"AAPL": 150_00, "GOOG": 280_00}] * 252,
    initial_cash=1_000_000_00,
    cost_model=nanobook.CostModel.zero(),
)

ITCH Parser

events = nanobook.parse_itch("data/sample.itch")

Book Analytics

Imbalance

let snap = exchange.depth(10);
if let Some(imb) = snap.imbalance() {
    // [-1.0, 1.0]: positive = buy pressure
    println!("Imbalance: {imb:.4}");
}

Weighted Midpoint

if let Some(wmid) = snap.weighted_mid() {
    println!("Weighted mid: {wmid:.2}");
}

VWAP

if let Some(vwap) = Trade::vwap(exchange.trades()) {
    println!("VWAP: {vwap}");
}

Persistence & Serde

Persistence

Feature flag: persistence (includes serde and event-log)

// Exchange — JSON Lines event sourcing
exchange.save(Path::new("orders.jsonl")).unwrap();
let loaded = Exchange::load(Path::new("orders.jsonl")).unwrap();

// Portfolio — JSON
portfolio.save_json(Path::new("portfolio.json")).unwrap();
let loaded = Portfolio::load_json(Path::new("portfolio.json")).unwrap();

Serde

Feature flag: serde

All public types derive Serialize/Deserialize: Price, OrderId, TradeId, Symbol, Side, TimeInForce, OrderStatus, Order, Trade, Event, SubmitResult, CancelResult, ModifyResult, BookSnapshot, LevelSnapshot, StopOrder, Position, CostModel, and more.


CLI Reference

Interactive REPL for the order book:

cargo run --bin lob
Command Example
buy <price> <qty> [ioc|fok] buy 100.50 100
sell <price> <qty> [ioc|fok] sell 101.00 50 ioc
market <buy|sell> <qty> market buy 200
stop <buy|sell> <price> <qty> stop buy 105.00 100
cancel <order_id> cancel 3
book / trades Show book or trade history
save <path> / load <path> Persistence (requires feature)

Performance

Benchmarks

Single-threaded (AMD Ryzen / Intel Core):

Operation Latency Throughput Complexity
Submit (no match) ~155 ns ~6.4M ops/sec O(log P)
Submit (with match) ~197 ns ~5M ops/sec O(log P + M)
BBO query ~1.1 ns ~900M ops/sec O(1)
Cancel (tombstone, deep queue) ~385 ns ~2.6M ops/sec O(1)
L2 snapshot (10 levels) ~255 ns ~4M ops/sec O(D)

Where P = price levels, M = orders matched, D = depth. Numbers are from benches/v0.10-comparison.md on macOS arm64; expect ±10-20 % across hardware and build flags.

Time Breakdown (Submit, No Match)

submit_limit() ~155 ns:
├── FxHashMap insert     ~30 ns   order storage
├── BTreeMap insert      ~30 ns   price level (O(log P))
├── VecDeque push         ~5 ns   FIFO queue
├── Event recording      ~10 ns   (optional, for replay)
├── STP branch           ~20 ns   `owner` field + policy read (Off path)
└── Overhead             ~60 ns   struct creation, dispatch, etc.

Optimizations

  1. O(1) cancel — Tombstone-based, 350x faster than linear scan
  2. FxHash — Non-cryptographic hash for OrderId lookups (+25% vs std HashMap)
  3. Cached BBO — Best bid/ask cached for O(1) access
  4. Optional event logging — disable event-log feature for max throughput

Rust vs Numba

Single-threaded throughput is roughly equivalent (both compile to LLVM IR). Where Rust wins: zero cold-start (vs Numba's ~300 ms JIT), true parallelism via Rayon with no GIL contention, and deterministic memory without GC pauses.


Comparison with Other Rust LOBs

Library Throughput Order Types Deterministic Use Case
nanobook ~6M ops/sec Limit, Market, Stops, GTC/IOC/FOK Yes Strategy backtesting
limitbook 3-5M ops/sec Limit, Market No General purpose
lobster ~300K ops/sec Limit, Market No Simple matching
OrderBook-rs 200K ops/sec Many (iceberg, peg, etc.) No Production HFT

Design Constraints

Engineering decisions that keep the system simple and fast:

Constraint Rationale
Single-threaded Deterministic by design — same inputs always produce same outputs
In-process No networking overhead; wrap externally if needed
No compliance No self-trade prevention or circuit breakers (out of scope)
No complex orders No iceberg or pegged orders
Integer prices Fixed-point arithmetic avoids floating-point rounding
Statistics in Python Spearman/IC/t-stat belong in scipy/Polars — proven, mature

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

nanobook-0.10.0.tar.gz (340.4 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

nanobook-0.10.0-cp314-cp314-win_amd64.whl (983.3 kB view details)

Uploaded CPython 3.14Windows x86-64

nanobook-0.10.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (1.3 MB view details)

Uploaded CPython 3.14manylinux: glibc 2.17+ x86-64

nanobook-0.10.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (1.3 MB view details)

Uploaded CPython 3.14manylinux: glibc 2.17+ ARM64

nanobook-0.10.0-cp314-cp314-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl (2.2 MB view details)

Uploaded CPython 3.14macOS 10.12+ universal2 (ARM64, x86-64)macOS 10.12+ x86-64macOS 11.0+ ARM64

nanobook-0.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (1.3 MB view details)

Uploaded CPython 3.13manylinux: glibc 2.17+ x86-64

nanobook-0.10.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (1.3 MB view details)

Uploaded CPython 3.13manylinux: glibc 2.17+ ARM64

nanobook-0.10.0-cp313-cp313-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl (2.2 MB view details)

Uploaded CPython 3.13macOS 10.12+ universal2 (ARM64, x86-64)macOS 10.12+ x86-64macOS 11.0+ ARM64

nanobook-0.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (1.3 MB view details)

Uploaded CPython 3.12manylinux: glibc 2.17+ x86-64

nanobook-0.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (1.3 MB view details)

Uploaded CPython 3.12manylinux: glibc 2.17+ ARM64

nanobook-0.10.0-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl (2.2 MB view details)

Uploaded CPython 3.12macOS 10.12+ universal2 (ARM64, x86-64)macOS 10.12+ x86-64macOS 11.0+ ARM64

nanobook-0.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (1.3 MB view details)

Uploaded CPython 3.11manylinux: glibc 2.17+ x86-64

nanobook-0.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (1.3 MB view details)

Uploaded CPython 3.11manylinux: glibc 2.17+ ARM64

nanobook-0.10.0-cp311-cp311-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl (2.2 MB view details)

Uploaded CPython 3.11macOS 10.12+ universal2 (ARM64, x86-64)macOS 10.12+ x86-64macOS 11.0+ ARM64

File details

Details for the file nanobook-0.10.0.tar.gz.

File metadata

  • Download URL: nanobook-0.10.0.tar.gz
  • Upload date:
  • Size: 340.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for nanobook-0.10.0.tar.gz
Algorithm Hash digest
SHA256 f2393ba4afedc1fc105a7d028a399efe13bcb6444262b1efd74ea5a316c15e48
MD5 668b747f66f272c80c03f01394bef72a
BLAKE2b-256 2e4a6c46c13b9e6ae077902ec6bc4634b48ea70deebfe9c347e2de83e7fe4392

See more details on using hashes here.

Provenance

The following attestation bundles were made for nanobook-0.10.0.tar.gz:

Publisher: wheels.yml on ricardofrantz/nanobook

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file nanobook-0.10.0-cp314-cp314-win_amd64.whl.

File metadata

  • Download URL: nanobook-0.10.0-cp314-cp314-win_amd64.whl
  • Upload date:
  • Size: 983.3 kB
  • Tags: CPython 3.14, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for nanobook-0.10.0-cp314-cp314-win_amd64.whl
Algorithm Hash digest
SHA256 f32d8c2a8d46f6aa0a7671b61765b990ada9e493290636953adf9ff997d9203a
MD5 39af15e93894b42ce50756267075ca2d
BLAKE2b-256 73cb23e590e3d71535e7270e56d24315bab31347a57875e6a580c5954d2350c5

See more details on using hashes here.

Provenance

The following attestation bundles were made for nanobook-0.10.0-cp314-cp314-win_amd64.whl:

Publisher: wheels.yml on ricardofrantz/nanobook

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file nanobook-0.10.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for nanobook-0.10.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 0543fa741ea2c18ec835d9881afe1456f68034e4b4d2e79a5c3ed8f555224d5e
MD5 43d843b041d3a99703987d433fafbdd3
BLAKE2b-256 f4a909cda8a4985067faf846479b90070a5b55df8a92619c5a31a04d29f3a71f

See more details on using hashes here.

Provenance

The following attestation bundles were made for nanobook-0.10.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: wheels.yml on ricardofrantz/nanobook

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file nanobook-0.10.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for nanobook-0.10.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 8bc2d0c42e161e1f4283a32c6cc91ff06e59fb328269d4c7b78323d07c82625b
MD5 8a094ab3ac1cb4d3ffb644317ca5b6eb
BLAKE2b-256 c656a951da1ce8c39300c53f2943969dc80f8c4df2c42c73400e8767f286182b

See more details on using hashes here.

Provenance

The following attestation bundles were made for nanobook-0.10.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:

Publisher: wheels.yml on ricardofrantz/nanobook

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file nanobook-0.10.0-cp314-cp314-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl.

File metadata

File hashes

Hashes for nanobook-0.10.0-cp314-cp314-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl
Algorithm Hash digest
SHA256 c13da59e991b479df1472e158c5e518c38b40974f38aa2b01d6c84035d1b9a7a
MD5 952f85aa15368f7bc853ca7d053297e8
BLAKE2b-256 bfbda1ba59c72a68d9f25404d79a414dace268e6e47f8ffa744c378f74e9e072

See more details on using hashes here.

Provenance

The following attestation bundles were made for nanobook-0.10.0-cp314-cp314-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl:

Publisher: wheels.yml on ricardofrantz/nanobook

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file nanobook-0.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for nanobook-0.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 ddb48f3c33128483cc028413e49b79c042d3af12e4d5eeb68ba67dcd8adc7c85
MD5 498074d18016fc5027a26080a484138f
BLAKE2b-256 9cdbfe25107134eee19ca289ca62f570f6cf080d0b5b3b9252e6e45812ec664f

See more details on using hashes here.

Provenance

The following attestation bundles were made for nanobook-0.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: wheels.yml on ricardofrantz/nanobook

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file nanobook-0.10.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for nanobook-0.10.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 35f88a6d2f55e22747b703863a3a94471218ce880c39d4894b0f1fb105b2dfd0
MD5 bbfe546c69475964a49ade230ca2b3fd
BLAKE2b-256 201dbe7e121caed0a1aeaab32520f8dadd25f7985d475d0e8eae2ba33b56bd07

See more details on using hashes here.

Provenance

The following attestation bundles were made for nanobook-0.10.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:

Publisher: wheels.yml on ricardofrantz/nanobook

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file nanobook-0.10.0-cp313-cp313-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl.

File metadata

File hashes

Hashes for nanobook-0.10.0-cp313-cp313-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl
Algorithm Hash digest
SHA256 279ae0b0db8a76e296e12c07e1d3c7e2c91779d9f440e252a223090f13be1f8d
MD5 ceccf0637057325884b3f2f13f9a5581
BLAKE2b-256 609528440731a61a15b64b173aee06beceb44f3012e380b8720f830558607281

See more details on using hashes here.

Provenance

The following attestation bundles were made for nanobook-0.10.0-cp313-cp313-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl:

Publisher: wheels.yml on ricardofrantz/nanobook

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file nanobook-0.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for nanobook-0.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 baf670d0011553c0e657b2c5a768124765fa26310949024c3f34a9796ddb0099
MD5 a66c3f22909f69780a0e38edbff65de7
BLAKE2b-256 ad832e4eefe362e8a9837a91794dd0efe63bb165064fa17f60aa01a0e1ad7c74

See more details on using hashes here.

Provenance

The following attestation bundles were made for nanobook-0.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: wheels.yml on ricardofrantz/nanobook

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file nanobook-0.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for nanobook-0.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 fb28a74a71d520d1aec8b5a36d1cd274c3957136a6b855bed2aa6be25bb21812
MD5 8fa85851e85128b701366f6e1b84bcce
BLAKE2b-256 150227b12b9aafa0cd0eecbff4502c4657bb7e3ff276efa667b6f1c6964e7484

See more details on using hashes here.

Provenance

The following attestation bundles were made for nanobook-0.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:

Publisher: wheels.yml on ricardofrantz/nanobook

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file nanobook-0.10.0-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl.

File metadata

File hashes

Hashes for nanobook-0.10.0-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl
Algorithm Hash digest
SHA256 e75311081892c3fa478877a0d978f4489d038eedd418e5231870de8b65072f42
MD5 8606b621bd17a42027cd10ea60008ae3
BLAKE2b-256 73cd76434fefa5731b9c7ba7b31807c611c9d5d2857b1c25f49d6cc12bcf33ea

See more details on using hashes here.

Provenance

The following attestation bundles were made for nanobook-0.10.0-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl:

Publisher: wheels.yml on ricardofrantz/nanobook

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file nanobook-0.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for nanobook-0.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 6e0a0c07aeca6c0771a8ae8632b3967a14761e0bc753482ad23bcc0b40b443f6
MD5 b2e112ad02a1bfac25cf43b313cee1eb
BLAKE2b-256 63900decae0201ae10cdcb1585f87f05e8a4a863737f05edc7a1e1bf244a92d2

See more details on using hashes here.

Provenance

The following attestation bundles were made for nanobook-0.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: wheels.yml on ricardofrantz/nanobook

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file nanobook-0.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for nanobook-0.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 3ed5de4de95792b77b07e621be1930ddebb9759ceb4d6d1ffb05dd85c638edee
MD5 35765e1f5227db796a8f1035a810d966
BLAKE2b-256 e3b8dc0411c4f2a12ecd7e7207ed6ebe9d026b9e0aa64d418f7a5bbb8b741e66

See more details on using hashes here.

Provenance

The following attestation bundles were made for nanobook-0.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:

Publisher: wheels.yml on ricardofrantz/nanobook

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file nanobook-0.10.0-cp311-cp311-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl.

File metadata

File hashes

Hashes for nanobook-0.10.0-cp311-cp311-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl
Algorithm Hash digest
SHA256 a0921f37a09c9ea4032fde98413f00356554b07f8d1adaaa5679c221922941a8
MD5 5080719bbef7b6822e3eace08f83655b
BLAKE2b-256 d478d687ce3d419fa57e560569933be80639cb13b528daa9173b10aa6f05d6ea

See more details on using hashes here.

Provenance

The following attestation bundles were made for nanobook-0.10.0-cp311-cp311-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl:

Publisher: wheels.yml on ricardofrantz/nanobook

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page