Crypto

Holdings

Introduction

Crypto holdings require special consideration because not all brokerages track your positions in each currency pair.

Cash vs Margin Brokerage Accounts

Some of the Crypto brokerages integrated into QuantConnect support cash and margin accounts while some only support cash accounts. If you want to short Crypto assets or use leverage, you need a margin account. Follow these steps to view which account types each brokerage supports:

  1. Open the brokerage guides.
  2. Click a Crypto brokerage.
  3. Scroll down to the Account Types section.

Virtual Pairs

All fiat and Crypto currencies are individual assets. When you buy a pair like BTCUSD, you trade USD for BTC. In this case, LEAN removes some USD from your portfolio cashbook and adds some BTC. The virtual pair BTCUSD represents your position in that trade, but the virtual pair doesn't actually exist. It simply represents an open trade. If you call the Liquidate method with the Symbolsymbol of a virtual pair, the method only liquidates the quantity in your virtual pair. For more information about liquidating Crypto postitions, see Crypto Trades.

Access Cypto Holdings

The CashBookcash_book stores the quantity of your Crypto holdings. To access your Crypto holdings, index the CashBookcash_book with the currency ticker. The values of the CashBook dictionary are Cash objects, which have the following properties:

var btcQuantity = Portfolio.CashBook["BTC"].Amount;
var btcValue = Portfolio.CashBook["BTC"].ValueInAccountCurrency;
btc_quantity = self.portfolio.cash_book['BTC'].amount
btc_value = self.portfolio.cash_book['BTC'].value_in_account_currency

To access the virtual pair of your Crypto trades, index the Portfolioportfolio object with the pair Symbolsymbol.

var securityHolding = Portfolio[_btcUsdSymbol];
security_holding  = self.portfolio[self._btc_usd_symbol]

Stateful Redeployments

When you make a trade inside of a running algorithm, LEAN tracks the virtual position state for you, but it won't survive between deployments. Some brokerages save your virtual pairs, so you can load them into your algorithm when you stop and redeploy it. Depending on the brokerage you select, you may be able to manually add virtual pairs when you deploy live algorithms with the LEAN CLI or cloud deployment wizard.

Examples

The following examples demonstrate some common practices for crypto holdings.

Example 1: ETH-BTC Proxy Trading

The following algorithm trades trend-following of the ETHBTC crypto pair using a 20-day EMA indicator. To reduce friction (e.g. slippage), we trade the more liquid and popular BTCUSDT and ETHUSDT pair instead. For example, if ETHBTC is above EMA (uptrend), we sell all BTCUSDT holdings and buy ETHUSDT with the same USDT value, going flat on BTC while holding ETH.

public class CryptoHoldingsAlgorithm : QCAlgorithm
{
    private Symbol _btcusdt, _ethusdt, _ethbtc;
    private ExponentialMovingAverage _ema;

    public override void Initialize()
    {
        SetStartDate(2024, 9, 1);
        SetEndDate(2024, 12, 31);
        SetAccountCurrency("USDT", 1000000m);

        // We would like to trade the EMA cross between 2 popular cryptos: BTC & ETH,
        // so request ETHBTC data to find trading opportunities.
        _ethbtc = AddCrypto("ETHBTC", Resolution.Daily, market: Market.Coinbase).Symbol;
        // Trade through BTCUSDT & ETHUSDT, though, since stable coin trades have lower friction costs and higher liquidity.
        var btcusdt = AddCrypto("BTCUSDT", Resolution.Daily, market: Market.Coinbase);
        var ethusdt = AddCrypto("ETHUSDT", Resolution.Daily, market: Market.Coinbase);
        _btcusdt = btcusdt.Symbol;
        _ethusdt = ethusdt.Symbol;
        
        // Simulate an account with various crypto cash through holdings.
        btcusdt.Holdings.SetHoldings(btcusdt.Price, 5m);
        btcusdt.BaseCurrency.AddAmount(5m);
        ethusdt.Holdings.SetHoldings(ethusdt.Price, 22.5m);
        ethusdt.BaseCurrency.AddAmount(22.5m);

        // Add automatic updating of the EMA indicator for trend trade signal emission.
        _ema = EMA(_ethbtc, 20, Resolution.Daily);
        // Warm up the indicator for its readiness usage immediately.
        WarmUpIndicator(_ethbtc, _ema, Resolution.Daily);
    }

    public override void OnData(Slice slice)
    {
        if (slice.Bars.TryGetValue(_ethbtc, out var bar) && _ema.IsReady &&
            slice.Bars.TryGetValue(_btcusdt, out var btc) && slice.Bars.TryGetValue(_ethusdt, out var eth))
        {
            var ema = _ema.Current.Value;
            var btcHoldings = Portfolio.CashBook["BTC"].Amount;
            var ethHoldings = Portfolio.CashBook["ETH"].Amount;
            
            // ETHBTC's current price is higher than EMA, suggesting an uptrend.
            if (bar.Close > ema && !Portfolio[_btcusdt].IsShort)
            {
                // To follow the up trend of ETHBTC, sell ALL BTCUSDT and buy ETHUSDT with the same value.
                if (btcHoldings > 0)
                {
                    var btcValue = btcHoldings * btc.Close;
                    MarketOrder(_btcusdt, -btcHoldings);
                    var ethSize = btcValue / eth.Close;
                    MarketOrder(_ethusdt, ethSize);
                }
            }
            // ETHBTC's current price is below the EMA, suggesting a downtrend.
            else if (bar.Close < ema && !Portfolio[_btcusdt].IsLong)
            {
                // To follow the downtrend of ETHBTC, sell ALL ETHUSDT and buy BTCUSDT with the same value.
                if (ethHoldings > 0)
                {
                    var ethValue = ethHoldings * eth.Close;
                    MarketOrder(_ethusdt, -ethHoldings);
                    var btcSize = ethValue / btc.Close;
                    MarketOrder(_btcusdt, btcSize);
                }
            }
        }
    }
}
class CryptoHoldingsAlgorithm(QCAlgorithm):
    def initialize(self) -> None:
        self.set_start_date(2024, 9, 1)
        self.set_end_date(2024, 12, 31)
        self.set_account_currency("USDT", 1000000)
        
        
        # We would like to trade the EMA cross between 2 popular cryptos: BTC & ETH,
        # so we request ETHBTC data to find trading opportunities.
        self.ethbtc = self.add_crypto("ETHBTC", Resolution.DAILY, market=Market.COINBASE).symbol
        # Trade through BTCUSDT & ETHUSDT, though, since stable coin trades have lower friction costs and higher liquidity.
        btcusdt = self.add_crypto("BTCUSDT", Resolution.DAILY, market=Market.COINBASE)
        ethusdt = self.add_crypto("ETHUSDT", Resolution.DAILY, market=Market.COINBASE)
        self.btcusdt = btcusdt.symbol
        self.ethusdt = ethusdt.symbol
        
        # Simulate an account with various crypto cash through holdings.
        # Set the cash book directly with the crypto amounts
        btcusdt.holdings.set_holdings(btcusdt.price, 5)
        btcusdt.base_currency.add_amount(5)
        ethusdt.holdings.set_holdings(ethusdt.price, 22.5)
        ethusdt.base_currency.add_amount(22.5)

        # Add automatic updating of the EMA indicator for trend trade signal emission.
        self._ema = self.ema(self.ethbtc, 20, Resolution.DAILY)
        # Warm up the indicator for its readiness usage immediately.
        self.warm_up_indicator(self.ethbtc, self._ema, Resolution.DAILY)

    def on_data(self, slice: Slice) -> None:
        bar = slice.bars.get(self.ethbtc)
        btc = slice.bars.get(self.btcusdt)
        eth = slice.bars.get(self.ethusdt)
        
        if bar and self._ema.is_ready and btc and eth:
            ema = self._ema.current.value
            btc_holdings = self.portfolio.cash_book["BTC"].amount
            eth_holdings = self.portfolio.cash_book["ETH"].amount
            
            # ETHBTC's current price is higher than EMA, suggesting an uptrend.
            if bar.close > ema and not self.portfolio[self.btcusdt].is_short:
                # To follow the up trend of ETHBTC, sell ALL BTCUSDT and buy ETHUSDT with the same value.
                if btc_holdings > 0:
                    btc_value = btc_holdings * btc.close
                    # Sell all BTC
                    self.market_order(self.btcusdt, -btc_holdings)
                    # Buy ETH with the same USDT value
                    eth_size = btc_value / eth.close
                    self.market_order(self.ethusdt, eth_size)
            # ETHBTC's current price is below the EMA, suggesting a downtrend.
            elif bar.close < ema and not self.portfolio[self.btcusdt].is_long:
                # To follow the downtrend of ETHBTC, sell ALL ETHUSDT and buy BTCUSDT with the same value.
                if eth_holdings > 0:
                    eth_value = eth_holdings * eth.close
                    # Sell all ETH
                    self.market_order(self.ethusdt, -eth_holdings)
                    # Buy BTC with the same USDT value
                    btc_size = eth_value / btc.close
                    self.market_order(self.btcusdt, btc_size)

You can also see our Videos. You can also get in touch with us via Discord.

Did you find this page helpful?

Contribute to the documentation: