| Overall Statistics |
|
Total Trades 1 Average Win 0% Average Loss 0% Compounding Annual Return 0% Drawdown 0% Expectancy 0 Net Profit 0% Sharpe Ratio 0 Probabilistic Sharpe Ratio 0% Loss Rate 0% Win Rate 0% Profit-Loss Ratio 0 Alpha 0 Beta 0 Annual Standard Deviation 0 Annual Variance 0 Information Ratio 0 Tracking Error 0 Treynor Ratio 0 Total Fees $39.83 |
from QuantConnect.Indicators import *
class CryptoArb(QCAlgorithm):
# ==================================================================================
# Main entry point for the algo
# ==================================================================================
def Initialize(self):
self.InitAlgoParams()
#self.Schedule.On(self.DateRules.EveryDay(),
# self.TimeRules.Every(timedelta(minutes=1)),
# self.Arbitrage)
# ==================================================================================
# Set algo params: Symbol, broker model, ticker, etc. Called from Initialize().
# ==================================================================================
def InitAlgoParams(self):
# Set params for data and brokerage
# -----------------------------------
self.SetBrokerageModel(BrokerageName.Bitfinex, AccountType.Margin)
self.initCash = 20000 # todo: use this to track buy+hold
self.SetStartDate(2018, 9, 10) # Set Start Date
self.SetEndDate (2018, 9, 11) # Set End Date
self.SetCash(self.initCash) # Set Strategy Cash
self.ETHUSD = self.AddCrypto("ETHUSD", Resolution.Tick).Symbol # ETH in USD
self.BTCUSD = self.AddCrypto("BTCUSD", Resolution.Tick).Symbol # BTC in USD
self.ETHBTC = self.AddCrypto("ETHBTC", Resolution.Tick).Symbol # ETH in BTC
self.triangle = (
self.ETHUSD,
self.BTCUSD,
self.ETHBTC
)
self.SetWarmup(10)
# Tracking variables
self.imbalance = None
self.next_index = None
self.buy_or_sell = 0 # none = 0, buy = 1, sell = 2
self.trade_lock = False
# Limit trades
self.max_iterations = 20
self.iteration = 0
def find_imbalance(self, triangle):
"""Returns the index of the crypto pair which is out of balance.
if the first crypto pair is cheaper than its synthetic, we want to arbitrage it
if the first crypto pair is more expensive than its synthetic, we want to arbitrage the second pair
"""
sym_1 = triangle[0] # BTCUSD
sym_1_price = self.Securities[sym_1].Price
sym_2 = triangle[1] # ETHUSD
sym_2_price = self.Securities[sym_2].Price
sym_3 = triangle[2] # BTCETH
sym_3_price = self.Securities[sym_3].Price
# Check imbalance (as percentage of asset price)
imbalance = (sym_1_price - (sym_2_price*sym_3_price)) / sym_1_price # E.g. USD/ETH - USD/BTC*BTC/ETH
return imbalance
def reset_variables(self):
self.imbalance = None
self.next_index = None
self.buy_or_sell = 0
self.trade_lock = False
self.iteration = self.iteration + 1
def order_symbol_allocation(self, symbol, allocation):
quantity = self.CalculateOrderQuantity(symbol, allocation)
return self.MarketOrder(symbol, quantity)
# ========================================================================
# Plot Charts. Called whenever we want to plot current values.
# ========================================================================
def PlotCharts(self):
self.Plot('Prices', 'ETHUSD', self.Securities[self.ETHUSD].Price)
self.Plot('Prices', 'BTCUSD', self.Securities[self.BTCUSD].Price)
self.Plot('Prices', 'ETHBTC', self.Securities[self.ETHBTC].Price)
# ========================================================================
# State change logic for handling order flow
# ========================================================================
def OnOrderEvent(self, orderEvent):
order = self.Transactions.GetOrderById(orderEvent.OrderId)
# We only want to execute when an order has been filled
if order.Status != OrderStatus.Filled:
return
# Second stage of the arbitrage
## Handle the intermediate step (exchanging the two cryptos for each other)
if self.next_index == 2:
# Intermediate sell
if self.buy_or_sell == 2:
self.Debug("Step 2: Selling {}".format(self.triangle[self.next_index]))
self.next_index = 1 # Finally sell the alternate crypto for USD
self.buy_or_sell = 2 # Need to sell back to USD
self.SetHoldings(self.triangle[self.next_index], 0.0) # Sell the primary crypto for the alternate crypto
return
# Intermediate buy
elif self.buy_or_sell == 1:
self.Debug("Step 2: Buying {}".format(self.triangle[self.next_index]))
self.next_index = 0 # Finally sell the primary crypto for USD
self.buy_or_sell = 2 # Need to sell back to USD
self.SetHoldings(self.triangle[self.next_index], 1.0) # Buy the primary crypto with the alternate crypto
return
# Final stage of the arbitrage
## Handle the final step (exchanging the crypto back to USD)
elif self.next_index < 2 and self.next_index >= 0:
# Final sell
self.Debug("Step 3: Selling {}".format(self.triangle[self.next_index]))
self.next_index = -1 # Last trade
self.SetHoldings(self.triangle[self.next_index], 0.0) # Sell the primary or alternate crypto for USD
return
# Exited
elif self.next_index == -1:
self.reset_variables()
# ============================================================================
# OnData handler. Triggered by data event (i.e. every time there is new data).
# ============================================================================
# todo: track 'close' on multiple resolutions (daily bars and hourly bars)
def OnData(self, data):
if self.IsWarmingUp:
return
# Exit on max iterations
if self.iteration > self.max_iterations:
self.Liquidate()
self.Quit()
# Don't initiate if we are in the middle of an arbitrage trade
if self.trade_lock:
return
# We are not currently in the middle of an arbitrage trade
if not self.imbalance and self.next_index is None:# or abs(self.imbalance) < 1:
self.imbalance = self.find_imbalance(self.triangle)
# TODO: Calculate if trade will be worth it after transaction fees
if abs(self.imbalance) < .01:
self.imbalance = None
return
# First stage of the arbitrage
## Initiates the order cascade
if self.next_index is None and not self.trade_lock:
self.Debug("imbalance: {}, next_index: {}, buy_or_sell: {}".format(self.imbalance, self.next_index, self.buy_or_sell))
# We are going to start an arbitrage trade, lock trading
self.trade_lock = True
## If the imbalance is negative, we buy the primary crypto
if self.imbalance < 0:
self.Debug("Step 1: Buying {}".format(self.triangle[0]))
self.next_index = 2 # Need to sell to get the intermediate crypto next
self.buy_or_sell = 2 # Need to sell the intermediate next
self.SetHoldings(self.triangle[0], 1.0) # Buy the primary crypto
return
elif self.imbalance > 0:
self.Debug("Step 1: Buying {}".format(self.triangle[1]))
self.next_index = 2 # Need to buy the primary crypto with the alternate
self.buy_or_sell = 1 # Need to buy the intermediate next
self.SetHoldings(self.triangle[1], 1.0) # Buy the alternate crypto
return