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:
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 algorithmThe 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.
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})
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.
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})