Bitunix API Perpetual Futures Trading with LumiBot

What BotSpot MCP adds

Use Claude, ChatGPT, Codex, Cursor, VS Code or another MCP-compatible assistant? Connect the free BotSpot MCP for:

  • Skills: build LumiBot strategies.

  • Free data: news, economics and SEC filings.

  • Backtests: run tests and read charts.

  • Cloud hosting: low-cost scheduled bots.

  • Community: share bots and discuss trades.

  • Marketplace: discover and publish strategies.

  • Broker linking: connect accounts through BotSpot.

Connect your AI assistant

How to Use Bitunix

Bitunix integration in Lumibot supports only perpetual futures trading. Spot trading is not supported.

Bitunix support at a glance

Capability

Status

Requirement

USDT perpetual futures

Supported

Fund the Futures wallet with USDT

Spot trading

Not supported

Use another broker integration

Hedge-mode positions

Required

The account must confirm HEDGE mode

Historical futures bars

Supported

Use a native interval and available exchange history

Account Funding and Cash Calculation:

  • You must transfer funds to the Futures section of your Bitunix account. Money in the spot wallet will not be available for trading.

  • For accurate calculation of available cash, it is strongly recommended to deposit USDT into your Bitunix Futures account. If you deposit crypto (e.g., BTC), it will not register as available cash for order sizing, though it can still be used as margin by Bitunix.

Environment Variables

Set the following environment variables in your .env file or system environment:

BITUNIX_API_KEY=your_bitunix_api_key
BITUNIX_API_SECRET=your_bitunix_api_secret

Position Refresh Failures

Position reads require a complete successful response. A transport error, rejected request, or malformed position raises LumibotBrokerAPIError and leaves tracked positions unchanged. A failed read does not mean the account is flat and is not cached as a successful refresh; a later read can retry. An explicitly successful empty snapshot removes all stale non-cash positions. Concurrent polling and strategy reads preserve the latest successfully applied request: an older response cannot remove, resurrect, or overwrite its positions. Failed reads remain retryable and do not discard another successful response. Positions added locally during a pending read retain their fields and ownership until the next fresh snapshot. Polling reports the failure and retries on its next cycle. Strategy code using fresh get_position() or get_positions() reads should allow the error to stop that decision, rather than treating it as permission to open a position.

HEDGE mode can hold a long and a short position on the same symbol at once. Each one is tracked as its own position with position_side ("long" or "short") and position_id (the exchange positionId); the raw exchange row stays available as position._raw. get_positions() returns both. Select one with get_position(asset, position_side="short") or by position_id; an unselected get_position(asset) raises ValueError while both exist rather than silently picking one. With a single position on a symbol, get_position(asset) works exactly as before.

close_position(asset, position_side="short") sends a hedge-mode CLOSE for that position only. close_position(asset) with both open closes each by its own positionId and returns a list of orders. Opening orders record the position they open (a buy opens the long side, a sell the short side), so fills attach to the right position. Two nonzero rows for the same symbol and side without distinct position IDs are still ambiguous: they raise and preserve tracked state. Zero-quantity rows are ignored.

long_leg = self.get_position(contract, position_side="long")
short_leg = self.get_position(contract, position_side="short")
if short_leg is not None:
    self.close_position(contract, position_side="short")

Setting Leverage for Bitunix Orders

Specify leverage in the CRYPTO_FUTURE Asset constructor or set its leverage attribute before creating an order. The constructor preserves the requested leverage; its default is 1. LumiBot requests that leverage from Bitunix before submitting an opening order. Reduce-only orders, including full and fractional close_position calls, preserve the existing exchange leverage without requesting a leverage change. This also applies after a restart when the local leverage cache is empty. If the exchange rejects an opening leverage change, LumiBot logs a warning; the Asset value does not confirm the exchange’s actual leverage.

Example: Setting Leverage on a Bitunix Futures Order

from lumibot.entities import Asset, Order

asset = Asset("HBARUSDT", Asset.AssetType.CRYPTO_FUTURE, leverage=10)
order = self.create_order(
    asset=asset,
    quantity=100,
    side=Order.OrderSide.BUY,
    order_type=Order.OrderType.LIMIT,
    limit_price=0.18
)
submitted_order = self.submit_order(order)
if submitted_order:
    self.log_message(f"Placed order: ID={submitted_order.identifier}, Status={submitted_order.status}")

Order Precision and Position Mode

LumiBot loads and caches Bitunix trading-pair rules for each symbol during the broker session. Quantities round down to basePrecision decimal places; limit, take-profit, and stop-loss prices round down to quotePrecision. All quantity and price fields are sent as decimal strings. For example, with BTCUSDT rules of basePrecision=4 and minTradeVolume=0.0001, a requested quantity of 0.008868641 becomes "0.0088". The tracked order quantity uses this executable size. Rounding down can leave a small residual position after a partial close.

Quantities below minTradeVolume after rounding return an order with ERROR status without placing an exchange order. Missing or invalid pair rules also block submission; failed lookups are retried on the next order.

The adapter requires confirmed HEDGE mode before submitting. If mode initialization fails or reports ONE_WAY, the order receives a clear error and is not sent. Check the account mode and outstanding positions/orders before retrying: Bitunix can reject mode changes while positions or orders exist. Opens send tradeSide="OPEN". Reduce-only closes send tradeSide="CLOSE" with the matching exchange position ID and hedge side. An absent or ambiguous matching position blocks the close.

See the Bitunix place-order contract and trading-pair rules.

Historical Bars

Bitunix serves native crypto-futures intervals including 1m, 15m, 1h, 2h, 4h, and 1d. LumiBot requests a native interval when it matches the strategy timeframe instead of downloading one-minute bars and resampling them locally.

The Bitunix futures API limits each kline response to 200 candles. LumiBot automatically paginates timestamp-bounded windows when length is greater than 200. If the symbol does not have enough exchange history to satisfy the request, get_historical_prices raises a clear error with the returned and requested counts instead of silently returning a short frame.

Example Usage

Below are practical examples using the Bitunix broker in Lumibot, based on the bitunix_futures_example.py strategy.

Placing a Limit Order for a Futures Contract

from lumibot.entities import Asset, Order

asset = Asset("HBARUSDT", Asset.AssetType.CRYPTO_FUTURE)
asset.leverage = 5  # Example: set leverage to 5x
order = self.create_order(
    asset=asset,
    quantity=100,
    side=Order.OrderSide.BUY,
    order_type=Order.OrderType.LIMIT,
    limit_price=0.18
)
submitted_order = self.submit_order(order)
if submitted_order:
    self.log_message(f"Placed order: ID={submitted_order.identifier}, Status={submitted_order.status}")

Closing a Position

# Wait for a few seconds if needed
import time
time.sleep(10)
self.close_position(asset)

close_position uses reduce-only semantics. Partial closes are supported by passing fraction between 0 and 1, for example self.close_position(asset, fraction=0.5).

Cancelling Open Orders

orders = self.get_orders()
for order in orders:
    if order.asset.symbol == "HBARUSDT" and order.asset.asset_type == Asset.AssetType.CRYPTO_FUTURE and order.status in [
        Order.OrderStatus.NEW, Order.OrderStatus.SUBMITTED, Order.OrderStatus.OPEN, Order.OrderStatus.PARTIALLY_FILLED
    ]:
        self.cancel_order(order)
        self.log_message(f"Order {order.identifier} cancellation submitted.")

Checking Available Cash

cash = self.get_cash()
self.log_message(f"Current cash {cash}")

Note

For best results, ensure all funds in your Bitunix Futures account are in USDT. Crypto balances may not be counted as available cash for order sizing.

Frequently asked questions

Can I run a Bitunix trading bot with LumiBot?

Yes, for USDT perpetual futures. Set BITUNIX_API_KEY and BITUNIX_API_SECRET. Choose it with TRADING_BROKER=bitunix. If no other broker is configured, LumiBot also picks Bitunix on its own when both keys are set. A working example ships with LumiBot as lumibot/example_strategies/bitunix_futures_example.py.

Does LumiBot support Bitunix spot trading?

No. The Bitunix integration only trades perpetual futures. Orders must use a CRYPTO_FUTURE asset such as Asset("BTCUSDT", Asset.AssetType.CRYPTO_FUTURE). Any other asset type is rejected before it reaches the exchange.

Does LumiBot use CCXT for Bitunix?

No. Bitunix has its own native LumiBot broker that talks to the Bitunix futures API directly, so you do not need CCXT settings for it. The Bitunix API documentation covers the exchange side, including how requests are signed.

Is there a Bitunix testnet or paper trading mode in LumiBot?

No. The LumiBot Bitunix broker connects to the live Bitunix futures API (fapi.bitunix.com) and has no testnet switch. Every order uses real funds, so start with very small sizes.

Which order types can I use on Bitunix?

Market, limit, stop and stop limit orders. You can also attach a take-profit price and a stop-loss price to an order. Multi-leg, OCO, OTO and bracket order groups are not supported.

How do I set leverage for a Bitunix order?

Pass leverage when you create the CRYPTO_FUTURE asset, or set asset.leverage before you create the order. The default is 1. LumiBot asks Bitunix for that leverage before an opening order. See Setting Leverage for Bitunix Orders above for details.

Why did my Bitunix order get an ERROR status without reaching the exchange?

The two most common causes are size and account mode. LumiBot rounds the quantity down to the pair precision, and if the result is below the pair minimum the order stops with an error. LumiBot also needs the account in HEDGE position mode. If the account reports ONE_WAY mode, the order is not sent.

How much price history can I get from Bitunix?

The Bitunix futures API returns up to 200 candles per request. LumiBot pages through more requests on its own when you ask for more than 200 bars. If the symbol does not have enough history, get_historical_prices raises a clear error instead of returning a short result.

Why does LumiBot show less cash than my Bitunix balance?

LumiBot counts USDT in the Futures wallet as cash. Money in the spot wallet is not available for trading, and crypto like BTC in the Futures wallet is not counted as cash for order sizing. Move USDT into the Futures wallet to fix this.

Documentation

class lumibot.brokers.bitunix.Bitunix(config, max_workers: int = 1, chunk_size: int = 100, connect_stream: bool = True, poll_interval: float | None = None, data_source=None)

Bases: Broker

A broker class that connects to the Bitunix exchange for crypto futures trading.

This broker uses Bitunix’s perpetual futures REST API to submit, track, and close crypto futures positions (e.g., BTCUSDT perpetual).

Key Features: - Only supports crypto futures (TRADING_MODE must be “FUTURES”). - Closes futures positions with reduce-only market orders. - All positions and orders are managed using Bitunix’s API conventions. - Not suitable for spot trading or non-futures assets.

Notes: - The close_position method submits a reduce-only HEDGE close for the matching position. - All asset symbols should be the full Bitunix symbol (e.g., “BTCUSDT”). - Leverage and margin settings are managed per-symbol as needed.

ASSET_TYPE_MAP = {'crypto': ['crypto'], 'crypto_future': ['future'], 'forex': [], 'future': ['future'], 'option': [], 'stock': []}
DEFAULT_POLL_INTERVAL = 5
LUMIBOT_DEFAULT_QUOTE_ASSET = None
POLL_EVENT = 'poll'
cancel_order(order: Order)

Cancels a FUTURES order.

close_position(strategy_name: str, asset: Asset, fraction: float = 1.0, position_side=None, position_id=None)

Close all or part of an existing position using a reduce‑only market order (tradeSide=”CLOSE”).

Parameters:
  • strategy_name (str) – Name of the strategy that owns the position.

  • asset (Asset) – The asset whose position should be closed.

  • fraction (float, optional) – Fraction of the position to close (0 < fraction ≤ 1). Defaults to 1 (full close).

Returns:

The submitted order, or None if no position exists.

Return type:

Optional[Order]

do_polling()

Polls Bitunix for order status updates and dispatches events.

get_contract_spec(asset, quote=None)

Bitunix quantities remain base coin; currency names come from metadata.

get_historical_account_value(start_date=None, end_date=None, frequency=None) → dict

Not implemented: Bitunix does not support historical account value retrieval.

get_quote_asset()
get_time_to_close()

Return the remaining time for the market to close in seconds

get_time_to_open()

Return the remaining time for the market to open in seconds

get_timestamp()
is_market_open()

Determines if the market is open.

Parameters:

None

Returns:

True if market is open, false if the market is closed.

Return type:

boolean

Examples

>>> self.is_market_open()
True
sell_all(strategy_name, cancel_open_orders: bool = True, strategy=None, is_multileg: bool = False)

Override sell_all to use flash_close_position for futures.

set_position_mode(hedged, asset=None, params=None)

Explicitly change the account-wide mode; never called implicitly.

ws_url

Private websocket endpoint for authenticated Bitunix futures streams.

Bitunix.get_time_to_close()

Return the remaining time for the market to close in seconds

Bitunix.get_time_to_open()

Return the remaining time for the market to open in seconds

Bitunix.get_timestamp()
Bitunix.is_market_open()

Determines if the market is open.

Parameters:

None

Returns:

True if market is open, false if the market is closed.

Return type:

boolean

Examples

>>> self.is_market_open()
True
Bitunix.close_position(strategy_name: str, asset: Asset, fraction: float = 1.0, position_side=None, position_id=None)

Close all or part of an existing position using a reduce‑only market order (tradeSide=”CLOSE”).

Parameters:
  • strategy_name (str) – Name of the strategy that owns the position.

  • asset (Asset) – The asset whose position should be closed.

  • fraction (float, optional) – Fraction of the position to close (0 < fraction ≤ 1). Defaults to 1 (full close).

Returns:

The submitted order, or None if no position exists.

Return type:

Optional[Order]