Overall Statistics |
Total Trades 540 Average Win 0.01% Average Loss -0.02% Compounding Annual Return -44.726% Drawdown 4.500% Expectancy -0.840 Net Profit -4.550% Sharpe Ratio -9.943 Probabilistic Sharpe Ratio 0% Loss Rate 89% Win Rate 11% Profit-Loss Ratio 0.49 Alpha 0 Beta 0 Annual Standard Deviation 0.034 Annual Variance 0.001 Information Ratio -9.943 Tracking Error 0.034 Treynor Ratio 0 Total Fees $83.73 Estimated Strategy Capacity $0 Lowest Capacity Asset TSLA UNU3P8Y3WFAD |
namespace QuantConnect.Algorithm.CSharp { public class InterExchangeArbitrageAlgorithm : QCAlgorithm { private Symbol _symbol; private decimal _fees; private ExchangeMapper _exchangeMapper = new ExchangeMapper(); private DateTime ptime = DateTime.MinValue; private AtreyuOrderProperties lowestSellorderProperties = new AtreyuOrderProperties(); private AtreyuOrderProperties highestBuyorderProperties = new AtreyuOrderProperties(); private decimal quantity; public override void Initialize() { SetStartDate(2021, 1, 1); SetEndDate(2021, 1, 31); SetCash(100000); SetBrokerageModel(BrokerageName.Atreyu); var security = AddEquity("TSLA", Resolution.Tick); security.SetFillModel(new CustomFillModel(this)); _symbol = security.Symbol; _fees = 0.0015m; // Atreyu fees are $0.0015/share } public override void OnData(Slice data) { // Gather quote ticks for each exchange var quotes = data.Ticks[_symbol].Where(tick => tick.TickType == TickType.Quote && _exchangeMapper.ContainsKey(tick.Exchange)); var sellQuotes = quotes.Where(quote => quote.AskPrice != 0); var buyQuotes = quotes.Where(quote => quote.BidPrice != 0); // Check if there are both buy and sell quotes if (sellQuotes.Count() == 0 || buyQuotes.Count() == 0) { return; } var time = data.Time; if (ptime != DateTime.MinValue) { TimeSpan diff = time - ptime; int milliseconds = (int) diff.TotalMilliseconds; if (milliseconds > 3 && milliseconds < 10) { var sSellQuote = sellQuotes.Where(quote => quote.Exchange == lowestSellorderProperties.Exchange); if (sSellQuote.Count() == 0) { return; } var sBuyQuote = buyQuotes.Where(quote => quote.Exchange == highestBuyorderProperties.Exchange); if (sBuyQuote.Count() == 0) { return; } MarketOrder(_symbol, quantity, true, $"{lowestSellorderProperties.Exchange}", lowestSellorderProperties); MarketOrder(_symbol, -quantity, true, $"{highestBuyorderProperties.Exchange}", highestBuyorderProperties); ptime = DateTime.MinValue; } if (milliseconds >= 10) { ptime = DateTime.MinValue; } return; } // Find lowest sell quote var lowestSellQuote = sellQuotes.OrderBy(quote => quote.AskPrice).First(); // Find highest buy quote var highestBuyQuote = buyQuotes.OrderByDescending(quote => quote.BidPrice).First(); // Check if there is an arbitrage opportunity if (lowestSellQuote.AskPrice >= highestBuyQuote.BidPrice - _fees) { return; } // Determine order size quantity = Math.Min(lowestSellQuote.AskSize, highestBuyQuote.BidSize); quantity = Math.Min(quantity, CalculateOrderQuantity(_symbol, 1.0m)); // Buy from underpriced exchange lowestSellorderProperties.Exchange = _exchangeMapper.getExchangeFromName(lowestSellQuote.Exchange); // Sell on overpriced exchange highestBuyorderProperties.Exchange = _exchangeMapper.getExchangeFromName(highestBuyQuote.Exchange); ptime = time; } internal class CustomFillModel : FillModel { private readonly QCAlgorithm _algorithm; private ExchangeMapper _exchangeMapper = new ExchangeMapper(); public CustomFillModel(QCAlgorithm algorithm) : base() { _algorithm = algorithm; } public override OrderEvent MarketFill(Security asset, MarketOrder order) { var fill = new OrderEvent(order, order.Time, OrderFee.Zero); var quotes = asset.Cache.GetAll<Tick>().Where(tick => tick.TickType == TickType.Quote && _exchangeMapper.ContainsKey(tick.Exchange)); if (order.Quantity > 0) // Buy order { // Set fill price to price of lowest sell quote fill.FillPrice = quotes.Where(quote => quote.AskPrice != 0) .OrderBy(quote => quote.AskPrice) .First() .AskPrice; } else // Sell order { // Set fill price to price of highest bid quote fill.FillPrice = quotes.Where(quote => quote.BidPrice != 0) .OrderByDescending(quote => quote.BidPrice) .First() .BidPrice; } fill.FillQuantity = order.Quantity; fill.Status = OrderStatus.Filled; return fill; } } internal class ExchangeMapper { private Dictionary<string, Exchange> _exchangeByName = new Dictionary<string, Exchange>() { {"NASDAQ", Exchange.NASDAQ}, {"BATS", Exchange.BATS}, {"ARCA", Exchange.ARCA}, {"NYSE", Exchange.NYSE}, {"NSX", Exchange.NSX}, {"FINRA", Exchange.FINRA}, {"ISE", Exchange.ISE}, {"CSE", Exchange.CSE}, {"CBOE", Exchange.CBOE}, {"NASDAQ_BX", Exchange.NASDAQ_BX}, {"SIAC", Exchange.SIAC}, {"EDGA", Exchange.EDGA}, {"EDGX", Exchange.EDGX}, {"NASDAQ_PSX", Exchange.NASDAQ_PSX}, {"BATS_Y", Exchange.BATS_Y}, {"BOSTON", Exchange.BOSTON}, {"AMEX", Exchange.AMEX}, {"BSE", Exchange.BSE}, {"NSE", Exchange.NSE} }; public Exchange getExchangeFromName(string name) { return _exchangeByName[name]; } public bool ContainsKey(string key) { return _exchangeByName.ContainsKey(key); } } } }