private static Arbitrage DetectArbitrage(Trade trade, Decimal quantity, SortedOrderBook[] orderBooks) { try { DateTime referenceTime = orderBooks.Min(x => x.UpdateTime); Arbitrage arbitrage = new Arbitrage(referenceTime, trade); foreach ((Int32 offset, TriangulationStep x, TriangulationStep y) in arbitrage.GetSequence()) { if (x.Position == Position.Buy) { SortedOrderBook orderBook = orderBooks[offset]; Decimal dustedQuantity = OrderBookConversion(false, quantity, x.ReferenceAsset, y.ReferenceAsset, orderBook); y.In = CalculateDustless(dustedQuantity, x.DustDecimals); x.Out = OrderBookConversion(true, y.In, y.ReferenceAsset, x.ReferenceAsset, orderBook); } else { x.Out = CalculateDustless(quantity, x.DustDecimals); y.In = OrderBookConversion(false, x.Out, x.ReferenceAsset, y.ReferenceAsset, orderBooks[offset]); } quantity = y.In; } return(arbitrage); } catch { return(null); } }
private Execution ExecuteArbitrageSequential(Arbitrage arbitrage) { Decimal? recalculatedQuantity = null; Execution execution = new Execution(arbitrage); foreach ((Int32 offset, TriangulationStep x, TriangulationStep y) in execution.GetSequence()) { Decimal quantity = recalculatedQuantity ?? arbitrage.GetQuantity(offset); if (!Config.TradingEnabled) { m_ExchangeEngine.TestOrder(x.Position, x.Symbol, quantity); continue; } OrderResult result = m_ExchangeEngine.PlaceOrder(x.Position, x.Symbol, quantity); if (x.Position == Position.Buy) { x.Out = result.OtherQuantity; y.In = result.ExecutedQuantity; SortedOrderBook orderBook = m_ExchangeEngine.GetOrderBook(y.Symbol); recalculatedQuantity = CalculateDustless(OrderBookConversion(false, y.In, y.QuoteAsset, y.BaseAsset, orderBook), y.DustDecimals); } else { x.Out = result.ExecutedQuantity; y.In = result.OtherQuantity; recalculatedQuantity = CalculateDustless(y.In, y.DustDecimals); } execution.Fees += result.Fees; } return(execution); }
private static Decimal OrderBookConversion(Boolean reversed, Decimal quantityFrom, String assetFrom, String assetTo, SortedOrderBook orderBook) { if (quantityFrom == 0M) { return(0M); } ReadOnlyCollection <SortedOrderBookElement> asks = orderBook.Asks; if (asks.Count == 0) { throw new InvalidOperationException(Resources.NoAvailableAsks); } ReadOnlyCollection <SortedOrderBookElement> bids = orderBook.Bids; if (bids.Count == 0) { throw new Exception(Resources.NoAvailableBids); } Decimal quantityTo = 0M; if (bids[0].Price > asks[0].Price) { throw new Exception(Resources.NoBidAskSpread); } if (reversed) { if ((assetFrom + assetTo) == orderBook.Symbol) { foreach (SortedOrderBookElement ask in asks) { Decimal price = ask.Price; Decimal quantity = ask.Quantity; Decimal quantityExchangeable = price * quantity; if (quantity >= quantityFrom) { return(quantityTo + (quantityFrom * price)); } quantityFrom -= quantity; quantityTo += quantityExchangeable; } } else { foreach (SortedOrderBookElement bid in bids) { Decimal price = bid.Price; Decimal quantity = bid.Quantity; Decimal quantityExchangeable = price * quantity; if (quantityExchangeable >= quantityFrom) { return(quantityTo + (quantityFrom / price)); } quantityFrom -= quantityExchangeable; quantityTo += quantity; } } } else { if ((assetFrom + assetTo) == orderBook.Symbol) { foreach (SortedOrderBookElement bid in bids) { Decimal price = bid.Price; Decimal quantity = bid.Quantity; Decimal quantityExchangeable = price * quantity; if (quantity >= quantityFrom) { return(quantityTo + (quantityFrom * price)); } quantityFrom -= quantity; quantityTo += quantityExchangeable; } } else { foreach (SortedOrderBookElement ask in asks) { Decimal price = ask.Price; Decimal quantity = ask.Quantity; Decimal quantityExchangeable = price * quantity; if (quantityExchangeable >= quantityFrom) { return(quantityTo + (quantityFrom / price)); } quantityFrom -= quantityExchangeable; quantityTo += quantity; } } } throw new Exception(Resources.DepthsTooShallow); }
private void DetectArbitrages() { if (m_Trading) { m_MessagePump.Signal(Utilities.FormatMessage(Resources.CycleSkipped, Config.TradingCyclesDelay)); return; } m_Trading = true; DateTime start = DateTime.Now; ICollection <Trade> trades = m_ExchangeEngine.GetTrades(); IDictionary <String, SortedOrderBook> orderBooks = m_ExchangeEngine.GetOrderBooks(); Parallel.ForEach(trades, trade => { SortedOrderBook orderBook1 = orderBooks[trade.Leg1.Symbol]; if (orderBook1.Invalid) { return; } SortedOrderBook orderBook2 = orderBooks[trade.Leg2.Symbol]; if (orderBook2.Invalid) { return; } SortedOrderBook orderBook3 = orderBooks[trade.Leg3.Symbol]; if (orderBook3.Invalid) { return; } Arbitrage bestArbitrage = null; SortedOrderBook[] orderBooksArray = { orderBook1, orderBook2, orderBook3 }; for (Decimal quantity = Config.InvestmentMinimum; quantity <= Config.InvestmentMaximum; quantity += Config.InvestmentStep) { Arbitrage arbitrage = DetectArbitrage(trade, quantity, orderBooksArray); if ((arbitrage == null) || (arbitrage.Profit <= 0M)) { continue; } if ((bestArbitrage == null) || (arbitrage.Profit > bestArbitrage.Profit)) { bestArbitrage = arbitrage; } } if (bestArbitrage != null) { m_Arbitrages.Add(bestArbitrage); } }); Int32 duration = (DateTime.Now - start).Milliseconds; if (m_CycleDurations.Count == 300) { m_CycleDurations.RemoveAt(0); } m_CycleDurations.Add(duration); m_MessagePump.Signal(Utilities.FormatMessage(Resources.CycleCompleted, duration, $"{(Int32)Math.Round(m_CycleDurations.Average(), 0)}")); m_Trading = false; }
public override Boolean Initialize() { m_MessagePump.Signal(Resources.ExchangeInitialization); try { BinanceStatus status = m_Api.GetSystemStatusAsync().Result; if (status == BinanceStatus.Maintenance) { throw new Exception(Resources.ServerMaintenance); } TimeSpan delta = TimeSpan.Zero; for (Int32 i = 0; i < 5; ++i) { DateTime now = DateTime.Now; Boolean result = m_Api.PingAsync().Result; if (!result) { throw new Exception(Resources.ServerPing); } delta += DateTime.Now - now; } m_MessagePump.Signal(Utilities.FormatMessage(Resources.MessageConnectivityOK, delta.Milliseconds / 5)); } catch (Exception e) { m_MessagePump.Signal(Utilities.FormatMessage(Resources.ConnectivityKO, Utilities.GetExceptionMessage(e))); return(false); } try { m_User = new BinanceApiUser(Config.KeyApi, Config.KeySecret); m_MessagePump.Signal(Resources.LoginOK); } catch (Exception e) { m_MessagePump.Signal(Utilities.FormatMessage(Resources.LoginKO, Utilities.GetExceptionMessage(e))); return(false); } try { UpdateBalances(m_Api.GetAccountInfoAsync(m_User).Result); m_UserDataWebSocket = new UserDataWebSocketManager(); m_UserDataWebSocket.SubscribeAsync <AccountUpdateEventArgs>(m_User, x => UpdateBalances(x.AccountInfo)).Wait(); m_UserDataWebSocket.WaitUntilWebSocketOpenAsync().Wait(); if (m_Balances.Count == 0) { m_MessagePump.Signal(Resources.BalancesOK0); } else { m_MessagePump.Signal(Utilities.FormatMessage(Resources.BalancesKO, m_Balances.Count, (m_Balances.Count == 1 ? String.Empty : "s"))); foreach (KeyValuePair <String, Decimal> balance in m_Balances.OrderBy(x => x.Key)) { m_MessagePump.Signal($" - {balance.Value:F8} {balance.Key}"); } } } catch (Exception e) { m_MessagePump.Signal(Utilities.FormatMessage(Resources.BalancesKO, Utilities.GetExceptionMessage(e))); return(false); } try { Dictionary <String, Int32> symbolsData = new Dictionary <String, Int32>(); HashSet <String> assets = new HashSet <String>(); foreach (Symbol symbol in m_Api.GetSymbolsAsync().Result) { if (symbol.Status != SymbolStatus.Trading) { continue; } String baseAsset = symbol.BaseAsset; String quoteAsset = symbol.BaseAsset; String name = symbol.ToString(); if ((Config.TradingWhitelist.Count > 0) && (!Config.TradingWhitelist.Contains(baseAsset) || !Config.TradingWhitelist.Contains(quoteAsset))) { continue; } String mininumQuantity = symbol.Quantity.Minimum.ToString(CultureInfo.InvariantCulture); Int32 dustDecimals = Math.Max(0, mininumQuantity.IndexOf("1", StringComparison.OrdinalIgnoreCase) - 1); assets.Add(baseAsset); assets.Add(quoteAsset); symbolsData[name] = dustDecimals; } ConcurrentBag <Trade> trades = new ConcurrentBag <Trade>(); Parallel.ForEach(assets.SelectMany(x => assets, (a1, a2) => new { Asset1 = a1, Asset2 = a2 }), pair => { if (pair.Asset1 == pair.Asset2) { return; } TradeLeg leg1 = BuildTradeLeg(symbolsData, Config.InvestmentBaseAsset, pair.Asset1); if ((leg1 == null) || (leg1.Position != Config.TradingPositions[0])) { return; } TradeLeg leg2 = BuildTradeLeg(symbolsData, pair.Asset1, pair.Asset2); if ((leg2 == null) || (leg2.Position != Config.TradingPositions[1])) { return; } TradeLeg leg3 = BuildTradeLeg(symbolsData, pair.Asset2, Config.InvestmentBaseAsset); if ((leg3 == null) || (leg3.Position != Config.TradingPositions[2])) { return; } trades.Add(new Trade(leg1, leg2, leg3)); }); if (trades.Count == 0) { throw new Exception(Resources.NoTriangularRelationships); } m_Trades.AddRange(trades); m_MessagePump.Signal(Utilities.FormatMessage(Resources.TradesOK, m_Trades.Count, (m_Trades.Count == 1 ? String.Empty : "s"))); } catch (Exception e) { m_MessagePump.Signal(Utilities.FormatMessage(Resources.TradesKO, Utilities.GetExceptionMessage(e))); return(false); } try { List <String> symbols = m_Trades.Select(x => x.Leg1.Symbol) .Union(m_Trades.Select(x => x.Leg2.Symbol)) .Union(m_Trades.Select(x => x.Leg3.Symbol)) .Distinct().OrderBy(x => x).ToList(); foreach (String symbol in symbols) { m_OrderBooks[symbol] = new SortedOrderBook(m_Api.GetOrderBookAsync(symbol, Config.DataSize).Result); m_OrderBooksCache[symbol] = new DepthWebSocketCache(); m_OrderBooksCache[symbol].Error += (sender, e) => m_OrderBooks[symbol].Invalid = true; m_OrderBooksCache[symbol].OutOfSync += (sender, e) => m_OrderBooks[symbol].Invalid = true; m_OrderBooksCache[symbol].Subscribe(symbol, e => m_OrderBooks[symbol] = new SortedOrderBook(e.OrderBook)); Thread.Sleep(Config.DataStagger); } m_MessagePump.Signal(Utilities.FormatMessage(Resources.OrderBooksOK, symbols.Count, (symbols.Count == 1 ? String.Empty : "s"))); m_MessagePump.Signal(String.Empty); m_Initialized = true; return(true); } catch (Exception e) { m_MessagePump.Signal(Utilities.FormatMessage(Resources.OrderBooksKO, Utilities.GetExceptionMessage(e))); return(false); } }