Problem Description
Solution
Here is a complete, runnable example of a Crypto Futures trading algorithm using the QuantConnect (Lean) Python API.
Strategy Overview
This algorithm implements a Trend Following Strategy on BTCUSDT Perpetual Futures.
- Setup: It uses the Binance Futures brokerage model.
- Indicators: It utilizes two Exponential Moving Averages (EMA): a fast (9-period) and a slow (21-period).
- Entry Logic:
- Long: When the Fast EMA crosses above the Slow EMA.
- Short: When the Fast EMA crosses below the Slow EMA.
- Leverage: It explicitly sets leverage to 3x to demonstrate how to manage margin in futures.
Python Code
# region imports
from AlgorithmImports import *
# endregion
class CryptoFuturesTrendFollow(QCAlgorithm):
def initialize(self):
# 1. Set Strategy Timeframe and Cash
self.set_start_date(2023, 1, 1)
self.set_end_date(2024, 1, 1)
self.set_cash(10000) # Set Strategy Cash (in USDT usually for Binance Futures)
# 2. Set Brokerage Model
# It is crucial to select the correct brokerage for Futures to get accurate fees and margin models.
self.set_brokerage_model(BrokerageName.BINANCE_FUTURES, AccountType.MARGIN)
# 3. Add Crypto Future Asset
# We request BTCUSDT. The resolution is set to Minute.
self.crypto_future = self.add_crypto_future("BTCUSDT", Resolution.MINUTE, Market.BINANCE)
self.symbol = self.crypto_future.symbol
# 4. Set Leverage (Optional, defaults vary by exchange)
# This allows us to use 3x leverage on our trades.
self.securities[self.symbol].set_leverage(3)
# 5. Define Indicators
self.fast_ema = self.ema(self.symbol, 9, Resolution.MINUTE)
self.slow_ema = self.ema(self.symbol, 21, Resolution.MINUTE)
# 6. Warm Up
# Automatically feeds historical data to indicators before the start date
self.set_warm_up(21, Resolution.MINUTE)
def on_data(self, data: Slice):
# Ensure data exists for our symbol and indicators are ready
if not self.fast_ema.is_ready or not self.slow_ema.is_ready:
return
if not data.bars.contains_key(self.symbol):
return
# Current values of indicators
fast_val = self.fast_ema.current.value
slow_val = self.slow_ema.current.value
# Current holdings
holdings = self.portfolio[self.symbol].quantity
# --- Entry Logic ---
# Long Signal: Fast EMA > Slow EMA
if fast_val > slow_val:
# If we are not already long
if holdings <= 0:
# set_holdings(symbol, percentage)
# 1.0 = 100% of equity. Since we set leverage to 3x,
# we could theoretically go up to 3.0, but we stick to 1.0 (1x effective leverage)
# or use a specific target like 2.0 for 2x leverage.
self.set_holdings(self.symbol, 1.0)
self.debug(f"GO LONG: {self.time} Price: {data[self.symbol].close}")
# Short Signal: Fast EMA < Slow EMA
elif fast_val < slow_val:
# If we are not already short
if holdings >= 0:
# -1.0 = Short 100% of equity
self.set_holdings(self.symbol, -1.0)
self.debug(f"GO SHORT: {self.time} Price: {data[self.symbol].close}")
def on_end_of_algorithm(self):
self.log(f"Final Portfolio Value: {self.portfolio.total_portfolio_value}")
Key Concepts Explained
1. add_crypto_future
Unlike standard equities or spot crypto, you must use add_crypto_future. This ensures the engine loads the correct contract specifications (tick size, contract multiplier, etc.).
self.add_crypto_future("BTCUSDT", Resolution.MINUTE, Market.BINANCE)
2. Brokerage Model
Setting BrokerageName.BINANCE_FUTURES is critical. It tells Lean to:
- Use the correct fee structure (Maker/Taker fees).
- Use the correct margin model (Cross/Isolated logic).
- Handle cash settlement in USDT (or BUSD depending on the pair).
3. Leverage
Crypto futures are highly leveraged products.
self.securities[self.symbol].set_leverage(3)
This line allows the algorithm to hold a position value up to 3 times the total cash in the account. If you do not set this, it defaults to the brokerage model's default (often 1x or specific to the asset class).
4. Shorting
In the on_data method, passing a negative value to set_holdings (e.g., -1.0) automatically initiates a Short position. You do not need to explicitly "borrow" shares as you would in equities; the Futures margin model handles the short selling mechanics.
Q&A
Q: How do I trade Coin-Margined futures (e.g., BTCUSD) instead of USDT-Margined?
A: You would change the ticker in add_crypto_future to the coin-margined pair (e.g., "BTCUSD") and ensure your account currency is set to the base asset (BTC) using self.set_account_currency("BTC") in initialize.
Q: Can I trade multiple futures contracts simultaneously?
A: Yes. You can call add_crypto_future multiple times for different symbols (e.g., ETHUSDT, SOLUSDT). You should store their symbols in a list or dictionary to iterate through them in on_data.
Q: How does settlement work in backtesting?
A: In the Binance Futures model, profit and loss are realized in the quote currency (USDT for BTCUSDT). The portfolio.cash_book will automatically update with USDT as trades are closed or marked-to-market.