Skip to main content

Overview

HftBacktest provides full support for Binance Futures (USDⓈ-M) integration, allowing you to run the same algorithm code for both backtesting and live trading. The connector manages market data streaming and order execution through shared memory IPC.

Setup

1. Build the Connector

First, build the connector from source:
cd hftbacktest
cargo build --release --package connector
The executable will be located at target/release/connector.

2. Configure Connection

Create a configuration file for Binance Futures:
binancefutures.toml
# Testnet: wss://fstream.binancefuture.com/ws
# Mainnet: wss://fstream.binance.com/ws
# Private: wss://fstream-auth.binance.com/ws
# Low-Latency Market Maker: wss://fstream-mm.binance.com/ws
stream_url = "wss://fstream.binancefuture.com/ws"

# Testnet: https://testnet.binancefuture.com
# Mainnet: https://fapi.binance.com
# Low-Latency Market Maker: https://fapi-mm.binance.com
api_url = "https://testnet.binancefuture.com"

order_prefix = "bot"
api_key = "YOUR_API_KEY"
secret = "YOUR_SECRET_KEY"
Symbols must be lowercase for Binance Futures (e.g., btcusdt, not BTCUSDT).

3. Run the Connector

Start the connector with your configuration:
connector --name bf --connector binancefutures --config binancefutures.toml
The connector runs on the same machine as your bot and communicates via shared memory.

Data Conversion

Convert raw Binance Futures feed data for backtesting:

Basic Conversion

from hftbacktest.data.utils import binancefutures

# Convert gzipped feed data
data = binancefutures.convert(
    input_filename="btcusdt_20220831.gz",
    output_filename="btcusdt_20220831.npz"
)

Feed Data Format

The raw feed file contains local timestamp followed by JSON stream data:
1660228023037049 {"stream":"btcusdt@depth@0ms","data":{"e":"depthUpdate","E":1660228023941,"T":1660228023931,"s":"BTCUSDT","U":1801732831593,"u":1801732832589,"pu":1801732831561,"b":[["24644.00","44.832"],["24645.40","0.203"]],"a":[["24653.60","0.000"],["24670.20","0.000"]]}}
1660228023043260 {"stream":"btcusdt@trade","data":{"e":"trade","E":1660228023980,"T":1660228023973,"s":"BTCUSDT","t":2691833663,"p":"24670.90","q":"0.022","X":"MARKET","m":true}}

Advanced Options

# Process additional streams
data = binancefutures.convert(
    input_filename="btcusdt_20220831.gz",
    output_filename="btcusdt_20220831.npz",
    opt="mt",  # Include markPriceUpdate and bookTicker
    base_latency=0,
    combined_stream=True,
    buffer_size=100_000_000
)
Options:
  • opt="m": Process mark price updates with custom event IDs (100: index, 101: mark price, 102: funding rate)
  • opt="t": Process book ticker with custom event IDs (103: best bid, 104: best ask)
  • opt="mt": Combine both options
  • base_latency: Adjust feed latency by specified amount
  • combined_stream: Set to False for regular stream format

Live Trading (Rust)

Basic Live Bot

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

fn prepare_live() -> LiveBot<IceoryxUnifiedChannel, HashMapMarketDepth> {
    LiveBotBuilder::new()
        .register(Instrument::new(
            "binancefutures",  // connector name
            "1000shibusdt",    // symbol (lowercase!)
            0.000001,          // tick_size
            1.0,               // lot_size
            HashMapMarketDepth::new(0.000001, 1.0),
            0,
        ))
        .build()
        .unwrap()
}

fn main() {
    let mut hbt = prepare_live();
    
    // Run your algorithm
    // The same algo code works for both backtest and live
    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();
}

Error Handling

let mut hbt = LiveBotBuilder::new()
    .register(Instrument::new(
        "binancefutures",
        "btcusdt",
        0.1,
        0.001,
        HashMapMarketDepth::new(0.1, 0.001),
        0,
    ))
    .error_handler(|error| {
        match error.kind {
            ErrorKind::ConnectionInterrupted => {
                error!("Connection interrupted - will retry");
            }
            ErrorKind::CriticalConnectionError => {
                error!("Critical connection error");
            }
            ErrorKind::OrderError => {
                let error = error.value();
                error!(?error, "Order error occurred");
            }
            ErrorKind::Custom(errno) => {
                error!(%errno, "Custom error");
            }
        }
        Ok(())
    })
    .build()
    .unwrap();

Complete Example

Here’s a complete market making example that works for both backtest and live:
import numpy as np
from numba import njit
from hftbacktest import BacktestAsset, HashMapMarketDepthBacktest, BUY, SELL, GTX, LIMIT

@njit
def market_making_algo(hbt):
    asset_no = 0
    tick_size = hbt.depth(asset_no).tick_size
    lot_size = hbt.depth(asset_no).lot_size

    # in nanoseconds
    while hbt.elapse(10_000_000) == 0:
        hbt.clear_inactive_orders(asset_no)

        depth = hbt.depth(asset_no)
        mid_price = (depth.best_bid + depth.best_ask) / 2.0
        
        position = hbt.position(asset_no)
        half_spread = 0.0005 * mid_price
        
        # Calculate reservation price with inventory skew
        skew = -0.001 * position
        reservation_price = mid_price + skew
        
        new_bid = reservation_price - half_spread
        new_ask = reservation_price + half_spread
        
        new_bid_tick = min(np.round(new_bid / tick_size), depth.best_bid_tick)
        new_ask_tick = max(np.round(new_ask / tick_size), depth.best_ask_tick)
        
        order_qty = 100 / mid_price  # $100 per order
        order_qty = np.round(order_qty / lot_size) * lot_size
        
        # Submit orders
        hbt.submit_buy_order(
            asset_no, 
            new_bid_tick,  # order_id
            new_bid_tick * tick_size, 
            order_qty, 
            GTX, 
            LIMIT, 
            False
        )
        hbt.submit_sell_order(
            asset_no,
            new_ask_tick,  # order_id
            new_ask_tick * tick_size,
            order_qty,
            GTX,
            LIMIT,
            False
        )
    return True

if __name__ == '__main__':
    # Backtesting configuration
    asset = (
        BacktestAsset()
            .data(['data/btcusdt_20220831.npz'])
            .linear_asset(1.0)
            .intp_order_latency(['latency/live_order_latency_20220831.npz'])
            .power_prob_queue_model(2.0)
            .no_partial_fill_exchange()
            .trading_value_fee_model(-0.00005, 0.0007)  # maker rebate, taker fee
            .tick_size(0.1)
            .lot_size(0.001)
    )
    hbt = HashMapMarketDepthBacktest([asset])
    market_making_algo(hbt)

Best Practices

Use Market Maker Endpoints

Binance offers low-latency endpoints for market makers:
  • WebSocket: wss://fstream-mm.binance.com/ws
  • REST: https://fapi-mm.binance.com

Test on Testnet First

Always test your strategies on testnet before going live:
  • No real funds at risk
  • Same API structure
  • Testnet URL: https://testnet.binancefuture.com

Monitor Latency

Use the latency logging feature to track order latencies:
use hftbacktest::live::LoggingRecorder;
let mut recorder = LoggingRecorder::new();

Handle Reconnections

The connector handles reconnections automatically, but implement error handlers for critical issues.

Fee Structure

Binance Futures market maker rebate program:
TierMaker FeeTaker Fee
VIP 00.0200%0.0500%
VIP 10.0160%0.0400%
MM Program-0.0050%0.0700%
Use .trading_value_fee_model(-0.00005, 0.0007) in your backtest to match the market maker rebate program fees.

Resources

Build docs developers (and LLMs) love