public TradingEngine(MessagePump messagePump, ExchangeEngine exchangeEngine) { m_MessagePump = messagePump ?? throw new ArgumentNullException(nameof(messagePump)); m_ExchangeEngine = exchangeEngine ?? throw new ArgumentNullException(nameof(exchangeEngine)); if (!m_ExchangeEngine.Initialized) { throw new Exception(Resources.EngineNotInitialized); } m_Arbitrages = new BlockingCollection <Arbitrage>(new ConcurrentQueue <Arbitrage>()); m_Executions = new Dictionary <DateTime, Execution>(); m_ExecutionSymbols = new HashSet <String>(); m_CycleDurations = new List <Int32>(); String session = Utilities.FormatMessage(Resources.TradingSession, $"{DateTime.Now:yyyy-MM-dd HH:mm:ss}"); String sessionFrame = new String('*', session.Length); m_MessagePump.Signal(sessionFrame); m_MessagePump.Signal(session); m_MessagePump.Signal(sessionFrame); m_Consumer = Task.Factory.StartNew(() => { foreach (Arbitrage arbitrage in m_Arbitrages.GetConsumingEnumerable()) { Task.Run(() => ExecuteArbitrage(arbitrage)); } }, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default); m_Producer = Task.Factory.StartNew(() => { Thread.Sleep(Config.TradingCyclesDelay * 10); while (!m_Arbitrages.IsCompleted) { Task.Run(() => DetectArbitrages()); Thread.Sleep(Config.TradingCyclesDelay); } }, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default); }
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); }