🚀 AI One-Click Generation of quantconnect Strategy Code

How to Code a Multi-Asset Trading Strategy (Equities, Forex, Crypto)

quantconnect | Published on: 2025/11/27
The following content is generated by EasyQuant .

Problem Description

Show me how to build a trading strategy that uses different types of assets like stocks and crypto.

Solution

Here is a complete, robust multi-asset trading strategy implemented in Python using the QuantConnect Lean API.

This strategy demonstrates how to trade Equities (Stocks) and Cryptocurrencies simultaneously. It utilizes a trend-following approach where the algorithm allocates capital based on Moving Averages specific to each asset class.

Strategy Logic

  1. Assets:
    • Equity: SPY (S&P 500 ETF)
    • Crypto: BTCUSD (Bitcoin)
  2. Indicators:
    • Uses a 200-day Simple Moving Average (SMA) for SPY (Long-term trend).
    • Uses a 50-day Simple Moving Average (SMA) for BTC (Faster trend for volatile assets).
  3. Execution:
    • If SPY > 200 SMA: Allocate 60% of the portfolio to SPY. Otherwise, 0%.
    • If BTC > 50 SMA: Allocate 30% of the portfolio to BTC. Otherwise, 0%.
    • 10% of cash is reserved as a buffer to handle volatility and fees.

Python Code (PEP8 Compliant)

# region imports
from AlgorithmImports import *
# endregion

class MultiAssetTrendStrategy(QCAlgorithm):

    def initialize(self):
        """
        Initialize the algorithm settings, assets, and indicators.
        """
        # 1. Set Strategy Settings
        self.set_start_date(2018, 1, 1)    # Start Date
        self.set_end_date(2024, 1, 1)      # End Date
        self.set_cash(100000)              # Set Strategy Cash (USD)

        # 2. Add Assets
        # Add Equity (S&P 500 ETF)
        # We use Daily resolution for trend following to reduce noise
        self.spy = self.add_equity("SPY", Resolution.DAILY).symbol

        # Add Crypto (Bitcoin)
        # QuantConnect supports major exchanges. Default is usually Coinbase or Bitfinex data.
        self.btc = self.add_crypto("BTCUSD", Resolution.DAILY).symbol

        # 3. Define Indicators
        # 200-day SMA for the Stock Market (Slower trend)
        self.spy_sma = self.sma(self.spy, 200, Resolution.DAILY)
        
        # 50-day SMA for Crypto (Faster trend due to higher volatility)
        self.btc_sma = self.sma(self.btc, 50, Resolution.DAILY)

        # 4. Set Warm Up
        # Automatically feed historical data to ready the indicators before the start date
        self.set_warm_up(200)

    def on_data(self, data: Slice):
        """
        Event handler for new data packets.
        """
        # Ensure indicators are ready before trading
        if self.is_warming_up:
            return

        # Ensure we have data for both symbols in this slice to avoid errors
        if not (data.contains_key(self.spy) and data.contains_key(self.btc)):
            return

        # Define target allocations
        spy_target = 0.0
        btc_target = 0.0

        # --- Logic for Equity (SPY) ---
        # Check if current price is above the 200 SMA
        current_spy_price = self.securities[self.spy].price
        if current_spy_price > self.spy_sma.current.value:
            spy_target = 0.60  # Allocate 60%
        else:
            spy_target = 0.0   # Go to cash

        # --- Logic for Crypto (BTC) ---
        # Check if current price is above the 50 SMA
        current_btc_price = self.securities[self.btc].price
        if current_btc_price > self.btc_sma.current.value:
            btc_target = 0.30  # Allocate 30%
        else:
            btc_target = 0.0   # Go to cash

        # --- Execution ---
        # We use SetHoldings to automatically calculate the quantity needed
        # to reach the target percentage.
        
        # Note: We check if the target has changed significantly to avoid 
        # excessive trading fees on small fluctuations.
        if not self.portfolio[self.spy].invested and spy_target > 0:
            self.set_holdings(self.spy, spy_target)
        elif self.portfolio[self.spy].invested and spy_target == 0:
            self.liquidate(self.spy)

        if not self.portfolio[self.btc].invested and btc_target > 0:
            self.set_holdings(self.btc, btc_target)
        elif self.portfolio[self.btc].invested and btc_target == 0:
            self.liquidate(self.btc)

    def on_end_of_algorithm(self):
        """
        Optional: Log final statistics
        """
        self.log(f"Final Portfolio Value: {self.portfolio.total_portfolio_value}")

Key Implementation Details

1. Adding Different Asset Classes

  • add_equity("SPY"): Adds a US stock. The engine handles market hours (9:30 AM - 4:00 PM ET) automatically.
  • add_crypto("BTCUSD"): Adds a cryptocurrency pair. Crypto markets operate 24/7. The Lean engine handles the synchronization of these different timeframes automatically within the on_data loop.

2. Data Resolution
We used Resolution.DAILY. In a multi-asset strategy, synchronization is easier at lower resolutions. If you use Resolution.MINUTE, on_data will trigger every minute. Since Crypto trades 24/7 and Stocks do not, you would receive Crypto data updates while the Stock market is closed. The logic inside on_data must account for this (e.g., checking data.contains_key).

3. Portfolio Management (set_holdings)
The set_holdings(symbol, percentage) method is the most efficient way to manage multi-asset portfolios. It automatically:

  • Calculates the number of shares/coins based on the current price and total portfolio value.
  • Handles the currency conversion if the asset is quoted in a different currency (though BTCUSD and SPY are both USD based).
  • Accounts for buying power and margin requirements.

4. Warm Up Period
self.set_warm_up(200) is crucial. Since we use a 200-day Moving Average, the algorithm needs 200 days of historical data before it can produce a valid signal. Without this, the algorithm would start on day 1 with an invalid indicator value (0), potentially causing logic errors.

Q&A: Multi-Asset Strategies in QuantConnect

Q: How does QuantConnect handle the different market hours between Crypto and Stocks?
A: The Lean engine uses a "Slice" system. on_data(self, slice) is triggered whenever there is data for any subscribed asset. If it is midnight on a Sunday, the Slice will contain BTC data but no SPY data. You should always check if data exists for a specific symbol using slice.contains_key(symbol) or slice.get(symbol) before accessing it to avoid errors.

Q: Can I trade Crypto Futures and Stocks in the same algorithm?
A: Yes. You would use add_crypto_future alongside add_equity. However, you must manage the CashBook carefully. Crypto Futures often require margin in USDT or BTC, whereas Stocks require USD. You may need to manually convert cash using self.market_order on the currency pair (e.g., buying USDT with USD) to fund the futures margin.

Q: Why did you use Resolution.DAILY instead of Resolution.MINUTE?
A: Resolution.DAILY was chosen for simplicity and to match the trend-following nature of the strategy. It reduces market noise and simulation time. However, you can mix resolutions (e.g., Daily data for SPY and Minute data for BTC) within the same algorithm without issues; the engine handles the synchronization.

Q: What happens if I try to allocate 100% to SPY and 100% to BTC?
A: The set_holdings method attempts to execute the order. If you do not have sufficient Buying Power (cash + margin), the order will be rejected with an "Insufficient Buying Power" error. It is best practice to ensure your total target allocation sums to 1.0 (100%) or less, or utilize a leverage-aware logic.