book
Checkout our new book! Hands on AI Trading with Python, QuantConnect, and AWS Learn More arrow

Live Trading

Arbitrage

Introduction

Arbitrage is the practice of taking advantage of a price difference between two or more markets. QuantConnect supports arbitrage strategies by allowing you to run multiple live algorithms deployments simultaneously and communicate between them using commands.

Basic Implementation

You can broadcast a command from and execute the event handler in another project. Add the project ID to the command data to identify the source of the command:

Select Language:
class BasicBroadcastCommandAlgorithm(QCAlgorithm):
    def initialize(self):
        self.set_benchmark(lambda x: 0) # So the algorithm needs no asset data.
        broadcast_result = self.broadcast_command({"sender": self.project_id, "ticker": "AAPL", "quantity": 1})
    
    def on_command(self, data):
        # Receive the command from the other algorithm
        self.log(f'Got command at {self.time} with data: {data}')
        return True
    

Examples

The following examples demonstrate common practices for implementing arbitrage algorithms.

Example 1: Long-short Bitcoin in two exchanges

The following algorithms trade an arbitrage strategy with BTCUSDT.

Primary algorithm

The primary algorithm trades a simple EMA cross strategy on BTCUSDT in Kraken. It broadcasts a command with the order filled quantity to all live deployments, and logs the command data it receives from the secondary algorithm.

Select Language:
class CryptoArbitrageA(QCAlgorithm):
    def initialize(self) -> None:
        self.set_start_date(2023, 1, 1)
        self.set_account_currency('USDT', 100000)

        # Set the brokerage model to trade on Kraken.
        self.set_brokerage_model(BrokerageName.KRAKEN, AccountType.MARGIN)
        # Request BTCUSDT data to trade.
        self._btc = self.add_crypto("BTCUSDT").symbol
        # Create an EMA indicator to generate trade signals.
        self._ema = self.ema(self._btc, 22, Resolution.MINUTE)
        # Warm-up indicator for immediate readiness.
        self.warm_up_indicator(self._btc, self._ema, Resolution.MINUTE)
        # Trade when the EMA is updated.
        self._ema.updated += self.trade

    def trade(self, _, current: IndicatorDataPoint) -> None:
        ema = current.value
        holdings = self.portfolio[self._btc]
        price = holdings.security.price
        
        # Trend-following strategy using price and EMA.
        # If the price is above EMA, SPY is in an uptrend, and we buy it.
        if not holdings.is_long and price > ema:
            self.set_holdings(self._btc, 1)
        if not holdings.is_short and price < ema:
            self.set_holdings(self._btc, -1)
        if not holdings.invested:
            self.set_holdings(self._btc, 1)

    def on_command(self, data: DynamicData) -> Optional[bool]:
        # The algorithm will log the command data from the secondary algorithm.
        self.log(f'Got command at {self.time} with data: {data}')
        return True

    def on_order_event(self, order_event: OrderEvent) -> None:
        # If an order is filled, we call the broadcast_command method to send a command to all live deployments.
        # You can add the project ID to the command data to identigy the sender.
        if not self.live_mode or order_event.status != OrderStatus.FILLED:
            return
        broadcast_result = self.broadcast_command({"sender": self.project_id, "ticker": f"{order_event.symbol.value}", "quantity": self.portfolio[order_event.symbol].quantity})
Secondary algorithm

The secondary algorithm trades the opposite direction of the primary in Bybit using the command it receives from the primary algorithm. It broadcasts a command with the order filled quantity to all live deployments.

Select Language:
class CryptoArbitrageB(QCAlgorithm):
    def initialize(self) -> None:
        self.set_start_date(2023, 1, 1)
        self.set_account_currency('USDT', 100000)

        # Set the brokerage model to trade on Bybit.
        self.set_brokerage_model(BrokerageName.BYBIT, AccountType.MARGIN)
        # Request BTCUSDT data to trade.
        self._btc = self.add_crypto("BTCUSDT").symbol

    def on_command(self, data: DynamicData) -> Optional[bool]:
        # The algorithm will trade the opposite direction of the primary algorithm.
        self.log(f'Got command at {self.time} with data: {data}')
        quantity = -(self.portfolio[self._btc].quantity + float(data.quantity))
        if quantity != 0:
            self.market_order(self._btc, quantity, tag=str(data))
        return True

    def on_order_event(self, order_event: OrderEvent) -> None:
        # If an order is filled, we call the broadcast_command method to send a command to all live deployments.
        # You can add the project ID to the command data to identigy the sender.
        if not self.live_mode or order_event.status != OrderStatus.FILLED:
            return
        broadcast_result = self.broadcast_command({"sender": self.project_id, "ticker": f"{order_event.symbol.value}", "quantity": self.portfolio[order_event.symbol].quantity})

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: