Skip to main content

Overview

HftBacktest provides integration with Bybit’s linear perpetual futures, supporting both backtesting and live trading with the same algorithm code. The Bybit connector is currently under active development.
Development Status: Bybit support is under development. Use at your own risk and report any issues you encounter.

Setup

1. Build the Connector

Build the connector from source:
cd hftbacktest
cargo build --release --package connector

2. Configure Connection

Create a Bybit configuration file:
bybit.toml
# Mainnet:
#  - Linear: wss://stream.bybit.com/v5/public/linear
# Testnet:
#  - Linear: wss://stream-testnet.bybit.com/v5/public/linear
public_url = "wss://stream-testnet.bybit.com/v5/public/linear"

# Mainnet: wss://stream.bybit.com/v5/private
# Testnet: wss://stream-testnet.bybit.com/v5/private
private_url = "wss://stream-testnet.bybit.com/v5/private"

# Mainnet: wss://stream.bybit.com/v5/trade
# Testnet: wss://stream-testnet.bybit.com/v5/trade
trade_url = "wss://stream-testnet.bybit.com/v5/trade"

# Mainnet: https://api.bybit.com
# Testnet: https://api-testnet.bybit.com
rest_url = "https://api-testnet.bybit.com"

# Linear
category = "linear"

order_prefix = ""
api_key = "YOUR_API_KEY"
secret = "YOUR_SECRET_KEY"
Symbols must be uppercase for Bybit (e.g., BTCUSDT, not btcusdt).

3. Run the Connector

Start the connector:
connector --name bybit-futures --connector bybit --config bybit.toml

Data Conversion

Bybit provides two conversion methods depending on your needs:

Method 1: Fused Multi-Level Depth

Use convert_fused to combine multiple order book depth levels (1, 25, 50, 200, etc.) into a single market depth representation:
from hftbacktest.data.utils import bybit

# Fuse all depth levels for more accurate order book
data = bybit.convert_fused(
    input_filename="examples/bybit/btcusdt_20250926.gz",
    tick_size=0.1,
    lot_size=0.001,
    output_filename="btcusdt_20250926.npz"
)

print(f"Loaded {len(data)} events from fused conversion")
When to use:
  • You have data with multiple depth levels
  • You want the most accurate order book reconstruction
  • You need to handle overlapping depth updates from different levels

Method 2: Single Depth Level

Use convert_depth to process only a specific depth level:
from hftbacktest.data.utils.bybit import BybitDepthLevel
import bybit

# Process only level 200 data
data = bybit.convert_depth(
    input_filename="examples/bybit/btcusdt_20250926.gz",
    single_depth_level=BybitDepthLevel.LEVEL_200,
    output_filename="btcusdt_20250926.npz"
)

print(f"Loaded {len(data)} events from single depth conversion")
Available depth levels:
BybitDepthLevel.LEVEL_1     # Top of book
BybitDepthLevel.LEVEL_25    # 25 levels
BybitDepthLevel.LEVEL_50    # 50 levels (default)
BybitDepthLevel.LEVEL_100   # 100 levels
BybitDepthLevel.LEVEL_200   # 200 levels
BybitDepthLevel.LEVEL_1000  # 1000 levels
When to use:
  • You only need one specific depth level
  • Your data contains a single depth level
  • You want faster processing

Feed Data Format

Bybit feed format contains local timestamp followed by JSON:
1758841137168651303 {"topic":"orderbook.1.BTCUSDT","type":"snapshot","ts":1758841134603,"data":{"s":"BTCUSDT","b":[["109378.80","1.273"]],"a":[["109378.90","6.278"]],"u":14869255,"seq":457514742271},"cts":1758841134598}
1758841138293664629 {"topic":"publicTrade.BTCUSDT","type":"snapshot","ts":1758841135824,"data":[{"T":1758841135823,"s":"BTCUSDT","S":"Buy","v":"0.020","p":"109378.90","L":"ZeroPlusTick","i":"2a74ac78-691c-5b54-9dbe-5aafe8364627","BT":false,"RPI":false,"seq":457514743450}]}
1758845432767124368 {"topic":"orderbook.50.BTCUSDT","type":"delta","ts":1758845431663,"data":{"s":"BTCUSDT","b":[["109147.80","1.687"],["109144.10","0.027"]],"a":[["109148.00","0"],["109154.30","0.076"]],"u":21111423,"seq":457538796406},"cts":1758845431662}

Complete Example

Here’s a complete example using Bybit data:
import numpy as np
from numba import njit
from hftbacktest import BacktestAsset, HashMapMarketDepthBacktest
from hftbacktest.data.utils import bybit
from hftbacktest.data.utils.bybit import BybitDepthLevel

@njit
def market_making_algo(hbt):
    while hbt.elapse(2.5e8) == 0:  # 250ms intervals
        depth = hbt.depth(0)
        
        # Prints the best bid and ask
        print(
            'current_timestamp:', hbt.current_timestamp,
            ', best_bid:', np.round(depth.best_bid, 1),
            ', best_ask:', np.round(depth.best_ask, 1)
        )
    return True

if __name__ == "__main__":
    # Example 1: Using convert_fused for multi-level depth
    data = bybit.convert_fused(
        input_filename="examples/bybit/btcusdt_20250926.gz",
        tick_size=0.1,
        lot_size=0.001,
    )
    
    print(f"Loaded {len(data)} events from fused conversion")
    
    # Example 2: Using convert_depth for single level
    data_single = bybit.convert_depth(
        input_filename="examples/bybit/btcusdt_20250926.gz",
        single_depth_level=BybitDepthLevel.LEVEL_200
    )
    
    print(f"Loaded {len(data_single)} events from single depth conversion")
    
    # Create backtest asset
    asset = (
        BacktestAsset()
            .data(data)
            .linear_asset(1.0)
            .power_prob_queue_model(2.0)
            .no_partial_fill_exchange()
            .trading_value_fee_model(-0.00005, 0.0007)
            .tick_size(0.1)
            .lot_size(0.001)
    )
    
    hbt = HashMapMarketDepthBacktest([asset])
    market_making_algo(hbt)

Live Trading (Rust)

Basic Live Bot

use hftbacktest::{
    live::{
        Instrument,
        LiveBot,
        LiveBotBuilder,
        LoggingRecorder,
        ipc::iceoryx::IceoryxUnifiedChannel,
    },
    prelude::{Bot, ErrorKind, HashMapMarketDepth},
};
use tracing::error;

fn prepare_live() -> LiveBot<IceoryxUnifiedChannel, HashMapMarketDepth> {
    LiveBotBuilder::new()
        .register(Instrument::new(
            "bybit-futures",  // connector name
            "BTCUSDT",        // symbol (uppercase!)
            0.1,              // tick_size
            0.001,            // lot_size
            HashMapMarketDepth::new(0.1, 0.001),
            0,
        ))
        .error_handler(|error| {
            match error.kind {
                ErrorKind::ConnectionInterrupted => {
                    error!("ConnectionInterrupted");
                }
                ErrorKind::CriticalConnectionError => {
                    error!("CriticalConnectionError");
                }
                ErrorKind::OrderError => {
                    let error = error.value();
                    error!(?error, "OrderError");
                }
                ErrorKind::Custom(errno) => {
                    error!(%errno, "custom");
                }
            }
            Ok(())
        })
        .build()
        .unwrap()
}

fn main() {
    tracing_subscriber::fmt::init();
    
    let mut hbt = prepare_live();
    hbt.run().unwrap();
    
    // Grid trading parameters
    let relative_half_spread = 0.0001;
    let relative_grid_interval = 0.0001;
    let grid_num = 2;
    let min_grid_step = 0.1;  // tick size
    let skew = relative_half_spread / grid_num as f64;
    let order_qty = 0.001;
    let max_position = grid_num as f64 * order_qty;
    
    let mut recorder = LoggingRecorder::new();
    gridtrading(
        &mut hbt,
        &mut recorder,
        relative_half_spread,
        relative_grid_interval,
        grid_num,
        min_grid_step,
        skew,
        order_qty,
        max_position,
    )
    .unwrap();
    
    hbt.close().unwrap();
}

Comparison: Fused vs Single Depth

Advantages:
  • More accurate order book representation
  • Combines all depth levels intelligently
  • Better for strategies requiring full depth visibility
Disadvantages:
  • More processing overhead
  • Requires tick_size and lot_size parameters
  • Larger resulting dataset
Best for:
  • Market making strategies
  • Strategies analyzing order book depth
  • When you have multi-level depth data

Best Practices

Choose the Right Depth Level

Select depth level based on your strategy:
  • LEVEL_1: For tick-by-tick trading
  • LEVEL_50: Good balance for most strategies
  • LEVEL_200+: For deep order book analysis

Test Thoroughly

Bybit integration is under development:
  • Start with testnet
  • Test with small positions
  • Report issues on GitHub

Handle Depth Updates

Bybit sends snapshot and delta updates:
  • Snapshots rebuild the book
  • Deltas update incrementally
  • The converter handles both automatically

Symbol Format

Always use uppercase symbols for Bybit:
  • BTCUSDT
  • btcusdt

Fee Structure

Bybit Linear Perpetual fee structure:
Account TypeMaker FeeTaker Fee
Standard0.0200%0.0550%
Professional0.0150%0.0450%
Market Maker-0.0050%0.0700%
Use .trading_value_fee_model(-0.00005, 0.0007) to model market maker rebates in your backtests.

Resources

Build docs developers (and LLMs) love