/// <summary>Find the available trade loops</summary> public void Start(CancellationToken cancel) { Debug.Assert(Misc.AssertMainThread()); #if false//todo // Create a map from coins to pairs involving those coins var map = new Dictionary <Coin, List <TradePair> >(); foreach (var pair in pairs) { map.GetOrAdd(pair.Base, k => new List <TradePair>()).Add(pair); map.GetOrAdd(pair.Quote, k => new List <TradePair>()).Add(pair); } // Don't use CrossExchange pairs to start the loops to guarantee all // loops have at least one non-CrossExchange pair in them. Queue foreach (var exch in Model.TradingExchanges) { // Create a seed loop from each pair on each exchange foreach (var pair in exch.Pairs) { Queue.Add(new Loop(pair)); } } // Local copy of shared variables //var pairs = Model.Pairs.ToList(); //var x_exch = Model.CrossExchange; //var max_loop_count = Settings.MaximumLoopCount; //var sw = new Stopwatch().StartNow(); // Record the update issue number Model.Log.Write(ELogLevel.Info, "Finding arbitrage loops ..."); m_rebuild_loops_pending = false; bool Abort() => m_rebuild_loops_pending || Shutdown.IsCancellationRequested; if (Abort()) { return; } // Run the background process ThreadPool.QueueUserWorkItem(_ => { try { // Create a producer/consumer queue of partial loops // Don't use CrossExchange pairs to start the loops to guarantee all // loops have at least one non-CrossExchange pair in them var queue = new BlockingCollection <Loop>(); foreach (var pair in pairs.Where(x => x.Exchange != x_exch)) { queue.Add(new Loop(pair)); } // Create a map from coins to pairs involving those coins var map = new Dictionary <Coin, List <TradePair> >(); foreach (var pair in pairs) { map.GetOrAdd(pair.Base, k => new List <TradePair>()).Add(pair); map.GetOrAdd(pair.Quote, k => new List <TradePair>()).Add(pair); } // Stop when there are no more partial loops to process var workers = new List <Completion>(); for (; ;) { // If the loops have been invalidated in the meantime, give up if (Abort()) { return; } // Take a partial loop from the queue if (!queue.TryTake(out var loop, TimeSpan.FromMilliseconds(50))) { // If there are no partial loops to process and // no running workers we must be done. workers.RemoveIf(x => x.IsCompleted); if (workers.Count == 0) { break; } continue; } // Process the partial loop in a worker thread ThreadPool.QueueUserWorkItem(completion_ => { // - A closed loop is a chain of pairs that go from one currency, through one or more other // currencies, and back to the starting currency. Note: the ending currency does *NOT* have to // be on the same exchange. // - Loops cannot start or end with a cross-exchange pair // - Don't allow sequential cross-exchange pairs var completion = (Completion)completion_; try { var beg = loop.Beg; var end = loop.End; // Special case for single-pair loops, allow for the first pair to be reversed if (loop.Pairs.Count == 1) { // Get all the pairs that match 'beg' or 'end' and don't close the loop var links = Enumerable.Concat( map[beg].Except(loop.Pairs[0]).Where(x => x.OtherCoin(beg).Symbol != end.Symbol), map[end].Except(loop.Pairs[0]).Where(x => x.OtherCoin(end).Symbol != beg.Symbol)); foreach (var pair in links) { if (Abort()) { break; } // Grow the loop var nue_loop = new Loop(loop); nue_loop.Pairs.Add(pair); queue.Add(nue_loop); } } else { // Look for a pair to add to the loop var existing_pairs = loop.Pairs.ToHashSet(); foreach (var pair in map[end].Except(existing_pairs)) { if (Abort()) { break; } // Don't allow sequential cross-exchange pairs if (loop.Pairs.Back().Exchange == Model.CrossExchange && pair.Exchange == Model.CrossExchange) { continue; } // Does this pair close the loop? if (pair.OtherCoin(end).Symbol == beg.Symbol) { // Add a complete loop var nue_loop = new Loop(loop); nue_loop.Pairs.Add(pair); result.TryAdd(nue_loop.HashKey, nue_loop); } else if (loop.Pairs.Count < max_loop_count) { // Grow the loop var nue_loop = new Loop(loop); nue_loop.Pairs.Add(pair); queue.Add(nue_loop); } } } if (Abort()) { completion.Cancelled = true; } else { completion.Completed(); } } catch (Exception ex) { completion.Exception = ex; } }, workers.Add2(new Completion())); } // Update the loops collection Model.RunOnGuiThread(() => { // If the loops have been invalidated in the meantime, give up if (Abort()) { return; } Loops.Clear(); Loops.AddRange(result.Values.ToArray()); // Done Log.Write(ELogLevel.Info, $"{Loops.Count} trading pair loops found. (Taking {sw.Elapsed.TotalSeconds} seconds)"); }); } catch (Exception ex) { Log.Write(ELogLevel.Error, ex, "Unhandled exception in 'FindLoops'"); } }); }