private Execution ExecuteArbitrageParallel(Arbitrage arbitrage) { Execution execution = new Execution(arbitrage); Parallel.ForEach(execution.GetSequence(), sequence => { Int32 offset = sequence.Item1; TriangulationStep x = sequence.Item2; TriangulationStep y = sequence.Item3; if (!Config.TradingEnabled) { m_ExchangeEngine.TestOrder(x.Position, x.Symbol, arbitrage.GetQuantity(offset)); return; } OrderResult result = m_ExchangeEngine.PlaceOrder(x.Position, x.Symbol, arbitrage.GetQuantity(offset)); if (x.Position == Position.Buy) { x.Out = result.OtherQuantity; y.In = result.ExecutedQuantity; } else { x.Out = result.ExecutedQuantity; y.In = result.OtherQuantity; } execution.Fees += result.Fees; }); return(execution); }
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 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; }
private void ExecuteArbitrage(Arbitrage arbitrage) { MessageCollection message = new MessageCollection(true, Utilities.FormatMessage(Resources.ArbitrageFound, arbitrage.Identifier, $"{arbitrage.Profit:F4}")); if (Config.TradingEnabled && (Config.TradingExecutionsCap > 0) && (m_Executions.Count >= Config.TradingExecutionsCap)) { message.AppendMessage(Utilities.FormatMessage(Resources.ArbitrageDiscardedCap, Config.TradingExecutionsCap)); m_MessagePump.Signal(message); return; } if (arbitrage.Profit < Config.TradingThresholdProfit) { message.AppendMessage(Utilities.FormatMessage(Resources.ArbitrageDiscardedProfit, $"{Config.TradingThresholdProfit:F4}")); return; } if (m_ExecutionSymbols.Any(arbitrage.GetReferenceAssets().Contains)) { message.AppendMessage(Resources.ArbitrageDiscardedAssets); m_MessagePump.Signal(message); return; } if (m_Executions.Count(x => x.Key >= DateTime.Now.AddSeconds(-1d)) > 1) { message.AppendMessage(Resources.ArbitrageDiscardedCooldown); m_MessagePump.Signal(message); return; } Int32 age = (DateTime.Now - arbitrage.ReferenceTime).Milliseconds; if (age > Config.TradingThresholdAge) { message.AppendMessage(Utilities.FormatMessage(Resources.ArbitrageDiscardedAge, age, Config.TradingThresholdAge)); m_MessagePump.Signal(message); return; } foreach (String referenceAsset in arbitrage.GetReferenceAssets()) { m_ExecutionSymbols.Add(referenceAsset); } try { Execution execution; if (Config.TradingStrategy == Strategy.Parallel) { execution = ExecuteArbitrageParallel(arbitrage); } else { execution = ExecuteArbitrageSequential(arbitrage); } m_Executions[DateTime.Now] = execution; message.AppendMessage(Utilities.FormatMessage(Resources.ArbitrageSucceded, Config.TradingStrategy.ToString().ToUpperInvariant())); foreach (var zip in arbitrage.GetSequence().Zip(execution.GetSequence(), (e, o) => new { Expected = e, Observed = o })) { Int32 offset = zip.Expected.Item1; TriangulationStep eX = zip.Expected.Item2; TriangulationStep eY = zip.Expected.Item3; TriangulationStep oX = zip.Observed.Item2; TriangulationStep oY = zip.Observed.Item3; message.AppendMessage(Utilities.FormatMessage(Resources.ArbitrageStep, offset)); message.AppendMessage(Utilities.FormatMessage(Resources.ArbitrageExpectedConversion, $"{eX.Out:F8}", eX.ReferenceAsset, $"{eY.Out:F8}", eY.ReferenceAsset)); message.AppendMessage(Utilities.FormatMessage(Resources.ArbitrageObservedConversion, $"{oX.Out:F8}", oX.ReferenceAsset, $"{oY.Out:F8}", oY.ReferenceAsset)); } message.AppendMessage(Resources.ArbitrageDeltas); foreach (TriangulationStep step in execution.GetSteps()) { Decimal eDelta = step.Delta; Decimal ePercent = (eDelta / step.Out) * 100M; message.AppendMessage(Utilities.FormatMessage(Resources.ArbitrageDelta, step.ReferenceAsset, (eDelta < 0M) ? String.Empty : " ", $"{eDelta:F8}", $"{ePercent:F4}")); } message.AppendMessage(Resources.ArbitrageOthers); message.AppendMessage(Utilities.FormatMessage(Resources.ArbitrageCommission, $"{(-1M * execution.Fees):F8}")); } catch (Exception e) { message.AppendMessage(Utilities.FormatMessage(Resources.ArbitrageFailed, Config.TradingStrategy.ToString().ToUpperInvariant(), Utilities.GetExceptionMessage(e))); } foreach (String referenceAsset in arbitrage.GetReferenceAssets()) { m_ExecutionSymbols.Remove(referenceAsset); } m_MessagePump.Signal(message); }