Crypto Exchange Trading and Backtesting with LumiBot

This is the guide for Lumibot’s CCXT-based cryptocurrency broker integrations. CCXT is a popular library for cryptocurrency trading. If you are interested (but not required!), you can find the documentation for CCXT here: https://ccxt.readthedocs.io/en/latest/

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

The shared Ccxt broker accepts exchange IDs provided by your installed CCXT version. You do not need a new LumiBot broker class or an approved-exchange list for each venue. Exchange products, API permissions, order features, credentials, regional availability and test environments still differ.

Available adapters and qualification

Available means the adapter can be selected through generic configuration. Contract tested means LumiBot’s order and balance handling passed offline checks. Exchange tested means the workflow was exercised against that exchange’s API. These are different levels of evidence.

Current shared spot qualification

Exchange IDs

Offline qualification

Authenticated exchange qualification in this change

phemex

Unified spot orders, balances, cancellation; native CCXT request serialization

Spot balance/private snapshots and collateral/position reads verified; execution pending

coinbase, kraken, kucoin, okx, bybit

Unified spot orders, balances and cancellation

Not exercised

Other installed CCXT adapters

Available through the same configuration

Not qualified by this change

Qualification on October 23, 2026 used CCXT 4.5.64: 105 affected offline tests passed and two pre-existing historical-cache tests were skipped. Coverage: test_ccxt_unified_spot, test_ccxt, test_symbol_normalization, test_position_serialization, test_position_signs, test_broker_sync_positions, test_crypto_future_close_position, test_backtesting_ccxt_execution_semantics and test_ccxt_store. The pre-fix shared spot regression had 17 failures. No authenticated orders were placed during this qualification.

There is no LumiBot exchange allowlist. The following adapter IDs were available in CCXT 4.5.64; this is an availability list, not a claim that every product or operation has passed an exchange test:

aftermath, alpaca, apex, aster, backpack, bequant, bigone, binance,
binancecoinm, binanceus, binanceusdm, bingx, bit2c, bitbank, bitbns,
bitfinex, bitflyer, bitget, bithumb, bitmart, bitmex, bitopro, bitrue,
bitso, bitstamp, bitteam, bittrade, bitvavo, blockchaincom, blofin,
btcbox, btcmarkets, btcturk, bullish, bybit, bybiteu, bydfi, cex,
coinbase, coinbaseexchange, coinbaseinternational, coincheck, coinex,
coinmate, coinone, coinsph, coinspot, cryptocom, cryptomus, deepcoin,
delta, deribit, derive, digifinex, dydx, exmo, extended, fmfwio, foxbit,
gate, gateeu, gemini, grvt, hashkey, hibachi, hitbtc, hollaex, htx,
hyperliquid, independentreserve, indodax, kraken, krakenfutures,
kucoin, kucoineu, kucoinfutures, latoken, lbank, lighter, luno, mercado,
mexc, modetrade, mudrex, myokx, ndax, okx, okxus, onetrading, p2b,
pacifica, paradex, paymium, phemex, poloniex, tokocrypto, toobit, upbit,
weex, whitebit, woo, woofipro, xt, zaif, zebpay

Inspect the installed list and each exchange’s capabilities directly:

import ccxt

print(ccxt.exchanges)
exchange = ccxt.phemex()
print(exchange.has)
print(exchange.features)

Generic configuration

from lumibot.brokers import Ccxt

broker = Ccxt({
    "exchange_id": "phemex",  # Any installed CCXT exchange ID
    "apiKey": "<testnet-api-key>",
    "secret": "<testnet-api-secret>",
    "sandbox": True,
    "options": {"defaultType": "spot"},
})

Sandbox selection happens before market discovery or other requests. Use sandbox=False for an exchange’s production API. Some venues have no sandbox, and a static-response sandbox does not prove matching, fills or cancellation. Credentials are specific to the selected environment. Some exchanges additionally require password or other CCXT credential fields.

Selecting any exchange with environment variables

Set TRADING_BROKER (or DATA_SOURCE) to any CCXT exchange ID, such as okx, binance, bitget or hyperliquid. LumiBot reads the fields that exchange requires (CCXT’s requiredCredentials) from environment variables named after the exchange ID in upper case:

CCXT field

Environment variable

apiKey

<EXCHANGE>_API_KEY

secret

<EXCHANGE>_API_SECRET

password

<EXCHANGE>_API_PASSPHRASE

uid

<EXCHANGE>_UID

accountId

<EXCHANGE>_ACCOUNT_ID

login

<EXCHANGE>_LOGIN

twofa

<EXCHANGE>_TWOFA

privateKey

<EXCHANGE>_PRIVATE_KEY

walletAddress

<EXCHANGE>_WALLET_ADDRESS

token

<EXCHANGE>_TOKEN

<EXCHANGE>_SANDBOX=true selects the exchange’s CCXT testnet when one exists; <EXCHANGE>_MARGIN=true enables margin mode. Missing required fields stop start-up with a message naming the exact variables. Native LumiBot broker names (alpaca, tradier, schwab, bitunix and others) keep priority; use ccxt:<id> to reach a CCXT exchange whose ID collides with one.

TRADING_BROKER=okx
OKX_API_KEY=<your-key>
OKX_API_SECRET=<your-secret>
OKX_API_PASSPHRASE=<your-passphrase>

Trading-only keys for DEX wallets

Some DEXes let a bot trade with a key that is not the main wallet key. LumiBot uses it when you set these variables, and then the main wallet private key is not needed:

  • Paradex: PARADEX_L2_PRIVATE_KEY (a Paradex subkey, which can trade but cannot withdraw) and PARADEX_L2_ADDRESS (your Paradex account address), plus PARADEX_WALLET_ADDRESS.

  • Lighter: LIGHTER_API_KEY_PRIVATE_KEY, LIGHTER_API_KEY_INDEX, LIGHTER_ACCOUNT_INDEX and LIGHTER_LIBRARY_PATH (an existing Lighter API key and Lighter’s signer library). Lighter API keys can still make secure withdrawals back to the account’s own wallet.

  • dYdX: DYDX_MNEMONIC (the dYdX Chain secret phrase) instead of the Ethereum wallet key. This phrase still has full control of the dYdX account; CCXT does not support dYdX API Trading Keys yet.

  • Hyperliquid: its API wallets already trade without withdrawal rights; give the API wallet key as HYPERLIQUID_PRIVATE_KEY.

If you set only some of the trading-only variables, start-up names the ones still missing. These setups have offline tests but have not yet been run against live Paradex or Lighter accounts.

Partner IDs and builder fees

CCXT normally tags orders with its own partner (broker) ID on many exchanges, and on some DEXes (Hyperliquid, Lighter, GRVT, Aster, Pacifica, Extended) it adds an extra builder fee that you pay, paid to CCXT. On Hyperliquid it also registers CCXT as your referrer the first time a wallet connects.

LumiBot removes CCXT’s tags and turns CCXT’s builder fees and referrer off. LumiBot may attach its own exchange partner ID instead; a partner ID never changes what you pay. LumiBot never adds a builder fee unless one is configured with LUMIBOT_CCXT_BUILDER_FEES, and it logs a warning when one is, because it costs you extra on every order. On HTX (perpetual orders), WOO X and WOOFi Pro, CCXT re-adds its tag while signing each request, so LumiBot cannot remove it yet. See Environment Variables for LUMIBOT_CCXT_PARTNER_IDS, LUMIBOT_CCXT_BUILDER_FEES and LUMIBOT_CCXT_REFERRAL_CODES.

Exchanges that publish no historical candles (no fetchOHLCV in CCXT) still trade and quote live prices; price-history requests raise a clear NotImplementedError. python -m lumibot.tools.ccxt_exchange_catalog prints every installed exchange with its credential variables and capabilities as JSON.

Spot order quantities are base-coin units. LumiBot uses CCXT’s own amount and price precision methods, including tick-size and decimal-place modes. Native exchange options remain available through the CCXT config and an order’s custom_params; stop orders use the unified triggerPrice parameter. Provider-specific trigger direction and other required parameters must match the exchange API. Compound-order lifecycle remains separate from native attached orders supplied through custom_params.

Perpetual contracts and position modes

Use the complete CCXT instrument symbol so quote and settlement remain distinct: BTC/USDT:USDT, BTC/USDC:USDC or BTC/USD:BTC. The broker discovers contracts and collateral currencies from exchange metadata. An explicit contract never falls back to a spot instrument with the same base and quote.

contract = broker.get_asset("BTC/USDC:USDC")
# Inside a Strategy:
order = self.create_order(contract, 1, "buy", position_side="long")
self.submit_order(order)

Spot quantities mean base-coin units. Contract quantities mean CCXT contract units, which can be fractional when the market’s amount precision permits it. contract.multiplier is the exchange’s contractSize. For a linear contract, base exposure is quantity times contract size; for an inverse contract, quote exposure is quantity times contract size. broker.get_contract_exposure(asset, quantity, price) returns both exposures and the settlement currency. Existing broker-specific quantity conventions are unchanged.

For portable sizing, self.get_quantity_for_notional(asset, notional, price) returns the selected broker’s native quantity for a notional in the instrument’s quote currency. CCXT derivatives use contracts; Bitunix retains base-coin units. For example, the same 1,000 quote-unit exposure at a price of 50,000 is 20 contracts when each linear contract represents 0.001 BTC, 1,000 inverse contracts when each represents 1 quote unit, or 0.02 BTC in base-coin units. Provider precision and minimums still apply when submitting the order.

self.get_contract_exposure(asset, quantity, price) reports base/quote exposure and settlement. CCXT’s broker.get_asset(symbol) attaches a serializable ContractSpec to the asset, preserving the terms when saving orders or moving the instrument to a historical simulation. Historical CCXT candle requests and cache keys preserve the full settlement-aware symbol.

position_side="long" and position_side="short" identify separate hedge legs. For example, an account can retain ten long contracts and four short contracts as two positions. self.get_position(contract, position_side="short") selects the short leg; closing that position retains its identity and reduce-only intent. Omitting the selector when closing both legs closes each separately. One-way accounts continue to use a single signed position without a leg selector. An ambiguous single-position lookup asks for a selector instead of choosing a leg.

This is the same model as LumiBot’s Bitunix futures broker: a contract is an Asset with asset_type="crypto_future", hedge legs use position_side and position_id, and an exchange-style symbol such as BTCUSDT resolves to the perpetual. Dated futures use their full CCXT symbol, for example BTC/USDT:USDT-261225. Like Bitunix, an opening order on an asset created with leverage above 1 (Asset("BTC/USDT:USDT", "crypto_future", leverage=5)) sets that leverage once per contract before the order; closing orders never change it, and a failure is logged while the order still goes. The default leverage=1 never touches account leverage, so use set_leverage to return to 1x.

Options. CCXT option markets (for example on Deribit, OKX, Bybit and Binance) map to normal LumiBot option assets:

import datetime
from lumibot.entities import Asset

call = Asset("BTC", asset_type="option", expiration=datetime.date(2026, 12, 25),
             strike=100000, right="call")
order = self.create_order(call, 1, "buy", limit_price=0.05)  # price in the option's quote

broker.get_asset("BTC/USD:BTC-261225-100000-C") returns the same asset. Positions are read for every contract type the exchange lists (swaps, dated futures and options).

Account mode is never changed automatically. The broker exposes set_position_mode(hedged, asset, params=None), set_leverage(leverage, asset, params=None) and set_margin_mode(mode, asset, params=None). Native parameters remain available, including separate long and short leverage where supported. The venue determines which operations each contract supports. For Phemex, the V2 position-mode route follows product metadata rather than restricting it to one settlement currency.

Phemex collateral and unrealized PnL are valued in the strategy’s quote currency; leveraged notional is not added to account equity. A collateral currency without a direct or inverse conversion market is explicitly warned about and excluded from converted equity. This is an unresolved valuation limit, not an assertion that such collateral is worthless. fetch_funding_rate(asset) reads the current venue funding rate; it does not simulate historical funding payments. Retired market metadata may name a settlement currency the account API no longer supports. Only an explicit currency-not-supported response for an entirely inactive market group is omitted; supported inactive wallets are still read, and authentication failures or errors for active markets remain visible.

WebSocket market and account data

The broker uses the CCXT Pro client included in CCXT. One lazy managed event loop serves subscriptions for a broker. No network connection or private authentication starts merely from construction. Live last prices and quotes use streamed data after subscription, with REST fallback for unavailable or stale public snapshots.

source = broker.data_source
source.get_last_price(contract)
source.get_quote(contract)
source.get_order_book(contract)
source.get_trades(contract)
source.get_live_candles(contract, timeframe="1m")

Historical bars continue through the existing REST history/cache owner. A live candle subscription does not replace historical data. Order submission, amendment and cancellation use REST. Account order updates feed the normal order/fill callbacks, applying cumulative fill quantities once across REST acknowledgements, WebSocket updates and replays. An uncertain submission retains its client ID for reconciliation and is not automatically resubmitted. Balance updates invalidate the collateral snapshot. Phemex wallet snapshots are retained even when they arrive before the balance subscription starts. Spot, legacy contract and V2 contract balances remain separate, including when they use the same currency. Positions are reconciled through REST because the installed Phemex Pro adapter has no watch_positions implementation.

Call broker.cleanup_streams() when finished; normal broker shutdown also closes the managed client. connect_stream=False retains REST operation. Phemex requires a symbol for REST open-order discovery, so a stream-disabled account-wide refresh scans metadata-discovered active markets.

Phemex qualification and remaining work

The offline contract and stream tests cover linear USDT, linear USDC and coin-settled inverse identities, quantity/exposure conversion, separate hedge legs, native request serialization, cumulative fills, replay handling, fees, amendment and cancellation. Transport doubles are not exchange trading proof. Regression coverage also includes shared history/cache behavior, Bitunix compatibility, position identity and strategy execution callbacks. The contract/stream tests are tests/test_ccxt_derivatives.py and tests/test_ccxt_stream.py.

Public API qualification on October 23, 2026 with CCXT 4.5.64 established:

  • Testnet spot, USDT perpetual and inverse perpetual market discovery and prices.

  • Testnet ticker, order book, trades and candle subscriptions for those families.

  • USDC perpetual ticker, book, trades and candles on the production public feed, without credentials or trading.

  • USDC testnet prices returned provider error 6001 (invalid argument), and its public subscriptions failed to produce usable snapshots. The opt-in testnet checks retain this failure; USDC is not removed from discovery.

Authenticated read-only checks on October 25, 2026 additionally established spot balances, metadata-discovered supported collateral wallets, combined position and equity reads, and spot private order/balance snapshots. Perpetual private subscriptions were acknowledged without delivering account snapshots during the check; acknowledgments alone do not establish snapshot or fill-update behavior. Order execution, position-mode changes and a complete strategy trading cycle remain separate qualification steps. Run the public checks with pytest tests/test_ccxt_phemex_testnet.py -m apitest; these make no account writes. Other exchanges’ private/perpetual streams have not been exchange-qualified by this change. CCXT adapter availability is not a promise that every feature works.

CcxtBacktesting now supports contract-aware perpetual collateral, FIFO realized/unrealized P&L, native settlement fees and timestamped funding. Phemex history uses settlement-aware candle symbols and its current public funding endpoint for linear and inverse contracts. Public API qualification completed spot, USDT, USDC and inverse backtest round trips across a funding boundary; matching and fills were simulated locally.

Backtesting data sources accept funding_rates, mark_prices and perpetual_risk mappings keyed by an Asset or full symbol. Funding rows contain timestamp (UTC milliseconds or an aware datetime), fundingRate and optionally markPrice. Mark rows contain timestamp and markPrice. Risk inputs contain maintenance_margin_rate and liquidation_fee_rate. Strategies retain independent net or long/short legs and can use create_order(..., reduce_only=True).

The liquidation model supports cross-wallet and isolated collateral. When historical marks or risk inputs are unavailable it uses causal candle prices and collateral exhaustion, and records those assumptions. Isolated gap losses beyond allocated collateral record modeled insurance credits. Exchange-specific risk tiers, intrabar liquidation, insurance and matching still need venue calibration. Missing funding is disclosed, not silently claimed as complete zero-cost history. Inspect broker.backtest_assumptions and broker.perpetual_events; analyzed results include those records too.

Exchange-Specific Guides

Use these pages for exchange-specific setup details:

Every exchange has its own step by step guide. They are listed by name in the sidebar under Crypto exchanges, and in Crypto Exchanges for LumiBot and the A to Z list.

Features

  • Cryptocurrency Trading: Spot, margin and metadata-resolved contracts, subject to venue capabilities and the qualification above

  • Exchange Selection: Any installed CCXT adapter through explicit configuration; existing credential auto-detection remains available

  • Market Data: Streamed prices, quotes, books, trades and live candles; REST historical candles and public snapshot fallback

  • Order Types: Market, limit, stop orders

  • Auto-Detection: Automatically connects when environment variables are set

Prerequisites

  1. Account: Use an eligible account or the exchange’s separate testnet account

  2. API Credentials: Generate API credentials from your exchange’s website

  3. Configuration: Supply CCXT credentials explicitly, or use an existing credential auto-detection path

Note

Easy Setup with .env File

LumiBot automatically loads your API credentials from a .env file! Simply create a .env file in the same folder as your trading strategy and add your broker’s environment variables. LumiBot will automatically detect and use these credentials - no additional configuration required.

Example .env file:

# For Kraken
KRAKEN_API_KEY=your_kraken_api_key_here
KRAKEN_API_SECRET=your_kraken_secret_here

# For WEEX
WEEX_API_KEY=your_weex_api_key_here
WEEX_API_SECRET=your_weex_secret_here
WEEX_API_PASSPHRASE=your_weex_passphrase_here

That’s it! LumiBot handles the rest automatically.

Important note: If you want to use CCXT for backtesting, you should use CcxtBacktesting as shown here instead: CCXT Backtesting.

Before running any strategy, you first need to create an account on your desired broker and then obtain API credentials from the broker’s website. Remember, each broker’s website may be different, but you can generally find the API settings under account settings or something similar. Please see the documentation for your broker for more information on getting API credentials.

For trading with cryptocurrencies, always remember to set your market to 24/7 in the initialize function:

self.set_market("24/7")

Auto-Detected Credential Paths

These are the CCXT credential sets Lumibot can auto-detect from environment variables.

Kraken

KRAKEN_API_KEY=your_api_key
KRAKEN_API_SECRET=your_api_secret

Coinbase

Coinbase’s current Cloud Developer Platform (CDP) keys use ECDSA/Ed25519 private keys rather than the legacy HMAC API-key + secret scheme. Generate a key at https://portal.cdp.coinbase.com/ — the downloaded JSON contains name (an organization-qualified identifier like organizations/<org>/apiKeys/<id>) and privateKey (a multi-line PEM block starting with -----BEGIN EC PRIVATE KEY-----).

Paste the name into COINBASE_API_KEY_NAME and the full PEM block (newlines and all) into COINBASE_PRIVATE_KEY. COINBASE_API_PASSPHRASE is only required for legacy HMAC keys and should be omitted for new CDP keys.

COINBASE_API_KEY_NAME=organizations/<org-id>/apiKeys/<key-id>
COINBASE_PRIVATE_KEY="-----BEGIN EC PRIVATE KEY-----\n...\n-----END EC PRIVATE KEY-----\n"
# COINBASE_API_PASSPHRASE=your_passphrase   # only for legacy HMAC keys

WEEX

WEEX requires three credentials: an API key, a secret, and a mandatory passphrase. Generate these in the WEEX web UI under API Management — make sure the key has Trade permission and, if needed, Read permission for balance/order lookups.

WEEX_API_KEY=your_api_key
WEEX_API_SECRET=your_api_secret
WEEX_API_PASSPHRASE=your_passphrase

Warning

WEEX’s Terms of Use exclude residents of the United States, Canada, and several other jurisdictions (see https://weexsupport.zendesk.com/hc/en-us/articles/4417379529241). WEEX does not provide an API sandbox — all trades run against live infrastructure. Start with very small quantities until you have verified behavior.

Note

The shared broker now resolves contract symbols and exposes leverage, margin and funding-rate operations through CCXT. WEEX perpetual balances, account synchronization and live trading have not been qualified by this change.

Manual CCXT Config Examples

These exchanges have selected Lumibot/CCXT examples or exchange-specific handling, but they are not currently auto-detected from global credentials.py environment variables. Use an explicit Ccxt config and validate balances, positions, order submission, fills, and cancellation with a very small test before increasing size.

KuCoin

from lumibot.brokers import Ccxt

KUCOIN_CONFIG = {
    "exchange_id": "kucoin",
    "apiKey": "your_api_key",
    "secret": "your_api_secret",
    "password": "your_passphrase",
    "margin": False,
    "sandbox": False,
}

broker = Ccxt(KUCOIN_CONFIG)

Binance

from lumibot.brokers import Ccxt

BINANCE_CONFIG = {
    "exchange_id": "binance",
    "apiKey": "your_api_key",
    "secret": "your_api_secret",
    "margin": False,
    "sandbox": False,
}

broker = Ccxt(BINANCE_CONFIG)

BitMEX

from lumibot.brokers import Ccxt

BITMEX_CONFIG = {
    "exchange_id": "bitmex",
    "apiKey": "your_api_key",
    "secret": "your_api_secret",
    "margin": False,
    "sandbox": False,
}

broker = Ccxt(BITMEX_CONFIG)

Documented Backtesting Exchange IDs

The CCXT backtesting adapter has documented examples for Kraken, Binance, KuCoin, BitMEX, Bybit, and OKX. These examples are useful for historical crypto strategy tests, but they are not the same thing as tested live broker integrations. Do not assume a backtesting exchange id is live-trading ready unless the live broker path has been validated separately.

Usage

  1. Set Environment Variables: Configure your broker’s API credentials

  2. Create Strategy: Import Lumibot and create your trading strategy

  3. Run: CCXT will auto-detect and connect when one of the auto-detected credential sets is present, or you can pass an explicit Ccxt config as shown above

from lumibot.strategies import Strategy
from lumibot.entities import Asset

class MyStrategy(Strategy):
    def initialize(self):
        self.sleeptime = "1D"
        # Set the market to 24/7 since crypto markets are always open
        self.set_market("24/7")

    def on_trading_iteration(self):
        # Trade Bitcoin
        btc = Asset("BTC", asset_type=Asset.AssetType.CRYPTO)

        # Get current price
        last_price = self.get_last_price(btc)

        # Place a limit order
        if last_price:
            order = self.create_order(
                asset=btc,
                quantity=0.1,
                side="buy",
                order_type="limit",
                limit_price=last_price * 0.999
            )
            self.submit_order(order)

# Run the strategy (CCXT auto-detects supported environment variables,
# or use an explicit Ccxt config for manual exchange paths)
strategy = MyStrategy()
strategy.run_live()

Supported Features

✅ Spot Trading: Buy and sell cryptocurrencies ✅ Margin Trading: Trade with leverage (exchange dependent) ✅ Market Orders: Immediate execution ✅ Limit Orders: Execute at specified price ✅ Stop Orders: Stop-loss functionality ✅ Real-time Data: Live market data ✅ Historical Data: Minute, hour, day timeframes

❌ Stock Trading: Crypto only ❌ Options Trading: Crypto only

Full Example Strategy

Here’s a complete example of a strategy that demonstrates the use of important functions you might need when trading with these brokers:

import datetime

import pandas_ta  # If this gives an error, run `pip install pandas_ta` in your terminal
from lumibot.brokers import Ccxt
from lumibot.entities import Asset
from lumibot.strategies.strategy import Strategy
from lumibot.traders import Trader


class ImportantFunctions(Strategy):
    def initialize(self):
        # Set the time between trading iterations
        self.sleeptime = "30S"

        # Set the market to 24/7 since those are the hours for the crypto market
        self.set_market("24/7")

    def on_trading_iteration(self):
        ###########################
        # Placing an Order
        ###########################

        # Define the base and quote assets for our transactions
        base = Asset(symbol="BTC", asset_type=Asset.AssetType.CRYPTO)
        quote = self.quote_asset

        # Market Order for 0.1 BTC
        mkt_order = self.create_order(base, 0.1, "buy", quote=quote)
        self.submit_order(mkt_order)

        # Limit Order for 0.1 BTC at a limit price of $10,000
        lmt_order = self.create_order(base, 0.1, "buy", quote=quote, limit_price=10000)
        self.submit_order(lmt_order)

        ###########################
        # Getting Historical Data
        ###########################

        # Get the historical prices for our base/quote pair for the last 100 minutes
        bars = self.get_historical_prices(base, 100, "minute", quote=quote)
        if bars is not None:
            df = bars.df
            max_price = df["close"].max()
            self.log_message(f"Max price for {base} was {max_price}")

            ############################
            # TECHNICAL ANALYSIS
            ############################

            # Use pandas_ta to calculate the 20 period RSI
            rsi = df.ta.rsi(length=20)
            current_rsi = rsi.iloc[-1]
            self.log_message(f"RSI for {base} was {current_rsi}")

            # Use pandas_ta to calculate the MACD
            macd = df.ta.macd()
            current_macd = macd.iloc[-1]
            self.log_message(f"MACD for {base} was {current_macd}")

            # Use pandas_ta to calculate the 55 EMA
            ema = df.ta.ema(length=55)
            current_ema = ema.iloc[-1]
            self.log_message(f"EMA for {base} was {current_ema}")

        ###########################
        # Positions and Orders
        ###########################

        # Get all the positions that we own, including cash
        positions = self.get_positions()
        for position in positions:
            self.log_message(f"Position: {position}")

            # Get the asset of the position
            asset = position.asset

            # Get the quantity of the position
            quantity = position.quantity

            # Get the symbol from the asset
            symbol = asset.symbol

            self.log_message(f"we own {quantity} shares of {symbol}")

        # Get one specific position
        asset_to_get = Asset(symbol="BTC", asset_type=Asset.AssetType.CRYPTO)
        position = self.get_position(asset_to_get)

        # Get all of the outstanding orders
        orders = self.get_orders()
        for order in orders:
            self.log_message(f"Order: {order}")
            # Do whatever you need to do with the order

        # Get one specific order
        order = self.get_order(mkt_order.identifier)

        ###########################
        # Other Useful Functions
        ###########################

        # Get the current (last) price for the base/quote pair
        last_price = self.get_last_price(base, quote=quote)
        self.log_message(
            f"Last price for {base}/{quote} was {last_price}", color="green"
        )

        dt = self.get_datetime()
        self.log_message(f"The current datetime is {dt}")
        self.log_message(f"The current time is {dt.time()}")

        # If you want to check if it's after a certain time, you can do this (eg. trading only after 9:30am)
        if dt.time() > datetime.time(hour=9, minute=30):
            self.log_message("It's after 9:30am")

        # Get the value of the entire portfolio, including positions and cash
        portfolio_value = self.portfolio_value
        # Get the amount of cash in the account (the amount in the quote_asset)
        cash = self.cash

        self.log_message(f"The current value of your account is {portfolio_value}")
        self.log_message(f"The current amount of cash in your account is {cash}") # Note: Cash is based on the quote asset


if __name__ == "__main__":
    # LumiBot automatically detects your broker from environment variables
    # Just make sure you have a .env file with your broker's credentials
    strategy = ImportantFunctions()
    strategy.run_live()

In this example, we’ve demonstrated the following:

  • How to place a market order and a limit order

  • How to get historical data

  • How to use technical analysis indicators

  • How to get positions and orders

  • How to get the current price

  • How to get the current datetime

  • How to get the value of the portfolio and the amount of cash in the account

Note

You can find the full source code for this example in the example_strategies folder of the lumibot GitHub repository.

Frequently asked questions

What is CCXT and how does LumiBot use it?

CCXT is an open source Python library that gives one interface to many crypto exchanges. LumiBot’s Ccxt broker sits on top of it, so the same strategy code can trade on any exchange your installed CCXT version supports. The CCXT documentation covers the library itself.

Which crypto exchanges can I use with LumiBot through CCXT?

Any exchange ID in your installed CCXT version. LumiBot has no exchange allowlist. Being available is not the same as being tested, though: check the qualification table at the top of this page, and test balances, orders, fills and cancels with a tiny order first.

Run python -m lumibot.tools.ccxt_exchange_catalog to print every installed exchange with its credential variables and capabilities.

How do I connect an exchange like OKX or Binance with environment variables?

Set TRADING_BROKER to the CCXT exchange ID, such as okx or binance. Then add the keys that exchange needs, named after the ID in upper case, for example OKX_API_KEY, OKX_API_SECRET and OKX_API_PASSPHRASE. If a required key is missing, start-up stops and names the exact variable.

How do I use an exchange testnet or sandbox?

Set <EXCHANGE>_SANDBOX=true, or pass "sandbox": True in an explicit Ccxt config. LumiBot then uses the exchange testnet when CCXT publishes one. Testnet keys are separate from live keys, and some exchanges, such as WEEX, have no sandbox at all.

How do I trade a pair like BTC/USDT?

Use the base coin as the asset, for example Asset("BTC", asset_type=Asset.AssetType.CRYPTO). Then pass the quote coin with quote=Asset("USDT", asset_type=Asset.AssetType.CRYPTO) when you get prices and create orders, or set it once as the strategy quote_asset. The pair must exist on that exchange.

For perpetual contracts, use the full CCXT symbol, such as BTC/USDT:USDT, as shown in the perpetual contracts section above.

Can I backtest crypto strategies with CCXT?

Yes. Use CcxtBacktesting for spot or metadata-described perpetual contracts. Perpetual accounting includes collateral, linear/inverse P&L, fees and funding. Historical marks and risk settings improve the liquidation model; inspect its reported assumptions. The qualification above separates real public data from simulated fills and authenticated exchange execution.

Which order types work through CCXT?

Market, limit and stop orders. Stop orders use CCXT’s unified triggerPrice parameter. Extra exchange options can go in an order’s custom_params. What each exchange accepts still depends on that exchange’s API.

Does the CCXT broker use WebSockets?

Yes, for market and account data. Live prices, quotes, order books, trades and live candles stream through CCXT Pro, with REST as the fallback. Orders are still sent, changed and canceled over REST. Pass connect_stream=False to use REST only.

How do I set up Coinbase API keys?

Create a key in the Coinbase Developer Platform. Put the key name in COINBASE_API_KEY_NAME and the full private key block in COINBASE_PRIVATE_KEY. COINBASE_API_PASSPHRASE is only for older HMAC keys.

Do I need to set the market hours for crypto?

Yes, call self.set_market("24/7") in initialize, because crypto markets never close. The live Ccxt broker already defaults to 24/7 unless you set MARKET, but setting it in your strategy keeps the hours clear in every run.