Hello everyone,
I'm learning how to use this framework to develop a perpetual futures strategy for crypto, but I'm encountering some problems along the way.

 

I referred to the following resources and wrote a simple strategy for backtesting

  1.  Lean/Algorithm.Python/BasicTemplateCryptoFutureAlgorithm.py at master · QuantConnect/Lean (github.com)
  2. Crypto Trades - QuantConnect.com
  3. Requesting Data - QuantConnect.com

 

Orders are always invalid during backtesting:

259702_1700465138.jpg2022-11-16 19:34:00BTCUSDT-0InvalidOrder Error: ids: [1], Insufficient buying power to complete orders (Value:[500013.4964]), Reason: Id: 1, Initial Margin: 166871.17084853999999999999998, Free Margin: 0.

 

This is my code:

# region imports
import datetime
from AlgorithmImports import *
# endregion

class Crypto(QCAlgorithm):

    def Initialize(self):
        # Locally Lean installs free sample data, to download more data please visit https://www.quantconnect.com/docs/v2/lean-cli/datasets/downloading-data
        self.SetStartDate(2022, 11, 17)  # Set Start Date
        self.SetEndDate(2023, 11, 17)  # Set End Date
        self.SetTimeZone(TimeZones.Utc)

        self.SetCash(1000000)  # Set Strategy Cash
        self.Settings.FreePortfolioValue = 100000
        
        self.SetBrokerageModel(BrokerageName.BinanceFutures, AccountType.Margin)
        self.btcUsd = self.AddCryptoFuture("BTCUSDT", leverage=3)
        

        
        # define our daily macd(12,26) with a 9 day signal
        self.__macd = self.MACD(self.btcUsd.Symbol, 12, 26, 9, MovingAverageType.Exponential, Resolution.Minute)
        self.PlotIndicator("MACD", True, self.__macd, self.__macd.Signal)
        self.PlotIndicator(self.btcUsd.Symbol, self.__macd.Fast, self.__macd.Slow)


    def OnData(self, data: Slice):
        """OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here.
            Arguments:
                data: Slice object keyed by symbol containing the stock data
        """
        if not self.__macd.IsReady: return

        btcUsdHoldings = self.btcUsd.Holdings

        # if our macd is greater than our signal, then let's go long
        if self.__macd.Current.Value > self.__macd.Signal.Current.Value:
            if (self.Transactions.OrdersCount > 0 and btcUsdHoldings.IsShort):
                self.liquidate_crypto(self.btcUsd.Symbol)
                self.set_crypto_holdings(self.btcUsd.Symbol, 0.5)
            elif (self.Transactions.OrdersCount == 0):
                self.set_crypto_holdings(self.btcUsd.Symbol, 0.5)

        # of our macd is less than our signal, then let's go short
        else:
            if (self.Transactions.OrdersCount > 0 and btcUsdHoldings.IsLong):
                self.liquidate_crypto(self.btcUsd.Symbol)
                self.set_crypto_holdings(self.btcUsd.Symbol, -0.5)
            elif (self.Transactions.OrdersCount == 0):
                self.set_crypto_holdings(self.btcUsd.Symbol, -0.5)

    def set_crypto_holdings(self, symbol, percentage):
        crypto = self.Securities[symbol]
        base_currency = crypto.BaseCurrency

        # Calculate the target quantity in the base currency
        target_quantity = percentage * (self.Portfolio.TotalPortfolioValue - self.Settings.FreePortfolioValue) / base_currency.ConversionRate    
        quantity = target_quantity - base_currency.Amount

        # Round down to observe the lot size
        lot_size = crypto.SymbolProperties.LotSize
        quantity = round(quantity / lot_size) * lot_size

        if self.is_valid_order_size(crypto, quantity):
            self.MarketOrder(symbol, quantity)

    # Brokerages have different order size rules
    # Binance considers the minimum volume (price x quantity):
    def is_valid_order_size(self, crypto, quantity):
        min_order_size = crypto.SymbolProperties.MinimumOrderSize
        if min_order_size is None:
            min_order_size = 0
        return abs(crypto.Price * quantity) > min_order_size

    def liquidate_crypto(self, symbol):
        crypto = self.Securities[symbol]
        base_currency = crypto.BaseCurrency

        # Avoid negative amount after liquidate
        quantity = min(crypto.Holdings.Quantity, base_currency.Amount)
            
        # Round down to observe the lot size
        lot_size = crypto.SymbolProperties.LotSize;
        quantity = (round(quantity / lot_size) - 1) * lot_size

        if self.is_valid_order_size(crypto, quantity):
            self.MarketOrder(symbol, -quantity)

 

Thanks and kind regards, Kenneth