private NewAndUpdatedFilledOrders IdentifyRecentNewAndUpdatedOrders(TimeSortedCollection <FilledOrder> liveOrders, DateTime lookAfterTime)
        {
            // After the matching process, any unmatched filled orders will be the new filled orders
            TimeSortedCollection <FilledOrder> unmatchedRecentLiveOrders = new TimeSortedCollection <FilledOrder>(
                liveOrders.Where(order => order.Time >= lookAfterTime));
            IList <UpdatedFilledOrder> updatedFilledOrders = new List <UpdatedFilledOrder>();

            // Only retrieve orders from the last "lookbackMinutes" minutes (call it X) for matching. This assumes that:
            // 1) Order times will not be updated AFTER X minutes
            // 2) It takes longer than X minutes for every visible order in the live portfolio to be pushed out of view
            TimeSortedCollection <FilledOrder> unmatchedRecentDbOrders = new TimeSortedCollection <FilledOrder>(
                GetTodaysFilledOrders().Where(order => order.Time >= lookAfterTime));

            // 1st pass: Match with exact time. DO NOT use ToList(), since it creates a copy of each of the items!
            foreach (FilledOrder dbOrder in unmatchedRecentDbOrders)
            {
                FilledOrder?match = unmatchedRecentLiveOrders.FirstOrDefault(o => dbOrder.StrictEquals(o));
                if (match != null)
                {
                    unmatchedRecentLiveOrders.Remove(match);
                    unmatchedRecentDbOrders.Remove(dbOrder);
                }
            }
            // 2nd pass: Match using closest time
            foreach (FilledOrder dbOrder in unmatchedRecentDbOrders)
            {
                FilledOrder?match = unmatchedRecentLiveOrders.Where(o => dbOrder.EqualsIgnoreTime(o) && o.Time > dbOrder.Time).FirstOrDefault();
                if (match != null)
                {
                    unmatchedRecentLiveOrders.Remove(match);
                    UpdatedFilledOrder updated = new UpdatedFilledOrder(dbOrder, match);
                    updatedFilledOrders.Add(updated);
                    Log.Information("Updated order {@OldOrder} to {@NewOrder}", dbOrder, match);
                }
                else
                {
                    PortfolioDatabaseException ex = new PortfolioDatabaseException("No live order matched to database order");
                    Log.Error(ex, "No live order matched to database order {@Order}- Symbol {Symbol}. Current live orders {@LiveOrders}", dbOrder, dbOrder.Symbol, liveOrders);
                    throw ex;
                }
            }

            TimeSortedCollection <FilledOrder> newOrders = new TimeSortedCollection <FilledOrder>(unmatchedRecentLiveOrders);

            return(new NewAndUpdatedFilledOrders(newOrders, updatedFilledOrders));
        }
        public TimeSortedCollection <PositionDelta> ComputeDeltasAndUpdateTables(TimeSortedCollection <FilledOrder> newOrders)
        {
            TimeSortedCollection <PositionDelta> deltas = new TimeSortedCollection <PositionDelta>();

            foreach (FilledOrder order in newOrders)
            {
                Position?     oldPos = GetPosition(order.Symbol);
                PositionDelta delta;
                if (oldPos == null) // NEW
                {
                    if (order.Instruction == InstructionType.SELL_TO_CLOSE)
                    {
                        PortfolioDatabaseException ex = new PortfolioDatabaseException("No existing position corresponding to sell order");
                        Log.Fatal(ex, "No existing position corresponding to sell order {@Order}- Symbol {Symbol}", order, order.Symbol);
                        throw ex;
                    }
                    delta = new PositionDelta(
                        DeltaType.NEW,
                        order.Symbol,
                        order.Quantity,
                        order.Price,
                        0,
                        order.Quote,
                        order.Time);
                    deltas.Add(delta);

                    Position newPos = new Position(order.Symbol, order.Quantity, order.Price);
                    InsertPosition(newPos);
                }
                else if (order.Instruction == InstructionType.BUY_TO_OPEN) // ADD
                {
                    delta = new PositionDelta(
                        DeltaType.ADD,
                        order.Symbol,
                        order.Quantity,
                        order.Price,
                        order.Quantity / oldPos.LongQuantity,
                        order.Quote,
                        order.Time);
                    deltas.Add(delta);

                    DeletePosition(oldPos);

                    int      newQuantity  = (int)oldPos.LongQuantity + order.Quantity;
                    float    averagePrice = (oldPos.AveragePrice * oldPos.LongQuantity + order.Quantity * order.Price) / newQuantity;
                    Position position     = new Position(order.Symbol, newQuantity, averagePrice);
                    InsertPosition(position);
                }
                else if (order.Instruction == InstructionType.SELL_TO_CLOSE) // SELL
                {
                    delta = new PositionDelta(
                        DeltaType.SELL,
                        order.Symbol,
                        order.Quantity,
                        order.Price,
                        order.Quantity / oldPos.LongQuantity,
                        order.Quote,
                        order.Time);
                    deltas.Add(delta);

                    DeletePosition(oldPos);

                    int newQuantity = (int)oldPos.LongQuantity - order.Quantity;
                    if (newQuantity > 0)
                    {
                        Position position = new Position(order.Symbol, newQuantity, oldPos.AveragePrice);
                        InsertPosition(position);
                    }
                }
                else
                {
                    PortfolioDatabaseException ex = new PortfolioDatabaseException("Unexpected instruction type: " + order.Instruction);
                    Log.Fatal(ex, "Unexpected instruction type");
                    throw ex;
                }
                InsertOrder(order);
                InsertDeltaAndUpsertUsedUnderlyingSymbol(delta);
            }
            return(deltas);
        }