/// <summary>Keep the 'Trades' collection in sync with the live positions</summary> private void SyncTrades() { var live_positions = Positions.ToHashSet(x => x.Id); Trades.RemoveIf(x => !live_positions.Contains(x.Id)); }
/// <summary>Refresh the set of trades</summary> private void UpdateTrades() { m_trades_invalidated = false; if (Model == null) { return; } // Create lists of trades per instrument. // Once complete, go through existing trades looking for matches. // Remove any trades not in 'trades', and any trades not in 'Trades'. var trades_map = new Dictionary <Instrument, List <Trade> >(); // Remove orders from trades that are no longer in 'Orders' foreach (var trade in Trades) { trade.Orders.RemoveIf(x => !OrderLookup.ContainsKey(x.Id)); } // Remove trades that contain no orders Trades.RemoveIf(x => x.Orders.Count == 0); // Group the orders into instruments foreach (var order_grp in Orders.GroupBy(x => x.Instrument)) { // Create an ordered list of trades based on non-overlapping time ranges var instr = order_grp.Key; var trades = trades_map.GetOrAdd(instr, k => new List <Trade>()); foreach (var order in order_grp) { // Look for a trade in the list that intersects the time range var idx = trades.BinarySearch(r => order.ExitTimeUTC <r.EntryTimeUTC ? -1 : order.EntryTimeUTC> r.ExitTimeUTC ? +1 : 0); // If index is positive, then it's the index of a trade that intersects the time range. // If not, then there is no trade yet that covers this order so create one. var trade = (idx >= 0) ? trades[idx] : trades.Insert2(idx = ~idx, new Trade(Model, instr)); trade.Orders.Add(order); // Sanity check that the trade time range overlaps the order time range Debug.Assert( order.EntryTimeUTC >= trade.EntryTimeUTC && order.ExitTimeUTC <= trade.ExitTimeUTC && trade.Orders.Count(x => x == order) == 1); // Merge adjacent trades if their time ranges overlap for (int i = idx; i-- != 0 && trades[i].ExitTimeUTC >= trades[idx].ExitTimeUTC;) { // Trade automatically updates its Entry/Exit time when its 'Orders' collection is changed trades[i].Orders.AddRange(trades[idx].Orders); trades[idx].Dispose(); trades.RemoveAt(idx); idx = i; } for (int i = idx; ++i != trades.Count && trades[idx].ExitTimeUTC >= trades[i].EntryTimeUTC;) { // Trade automatically updates its Entry/Exit time when its 'Orders' collection is changed trades[idx].Orders.AddRange(trades[i].Orders); trades[i].Dispose(); trades.RemoveAt(i); i = idx; } } // Check the ranges are non-overlapping #if DEBUG for (int i = 0; i != trades.Count; ++i) { Debug.Assert(trades[i].EntryTimeUTC <= trades[i].ExitTimeUTC); Debug.Assert(i == 0 || trades[i - 1].EntryTimeUTC < trades[i].EntryTimeUTC); Debug.Assert(i == 0 || trades[i - 1].ExitTimeUTC < trades[i].EntryTimeUTC); } #endif } // Remove trades from the trades map that match existing trades. // Want to avoid deleting existing trades where possible. foreach (var existing in Trades) { // There shouldn't be any existing trades for instruments that aren't in 'trades_map' var trades = trades_map[existing.Instrument]; // Try to find a match in trades for 'existing' var idx = trades.IndexOf(trade => trade.Orders.SequenceEqualUnordered(existing.Orders)); // Matching existing trade found, remove from trades list in the map if (idx != -1) { trades[idx].Dispose(); trades.RemoveAt(idx); } // No more trades for this instrument? Remove the entry from the map if (trades.Count == 0) { trades_map.Remove(existing.Instrument); } } // Add all trades left in the trades map to the current trades list foreach (var trades in trades_map.Values) { Trades.AddRange(trades); } }