public NewAndUpdatedFilledOrders IdentifyNewAndUpdatedOrders(TimeSortedCollection <FilledOrder> liveOrders, double lookbackMinutes) { DateTime cutoffTime = DateTime.Now.AddMinutes(-lookbackMinutes); NewAndUpdatedFilledOrders recentOrdersResult = IdentifyRecentNewAndUpdatedOrders(liveOrders, cutoffTime); TimeSortedCollection <FilledOrder> oldUnseenOrders = IdentifyOldUnseenOrders(liveOrders, cutoffTime); TimeSortedCollection <FilledOrder> allNewOrders = new TimeSortedCollection <FilledOrder>(recentOrdersResult.NewFilledOrders); foreach (FilledOrder oldUnseen in oldUnseenOrders) { allNewOrders.Add(oldUnseen); } return(new NewAndUpdatedFilledOrders(allNewOrders, recentOrdersResult.UpdatedFilledOrders)); }
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)); }
private TimeSortedCollection <FilledOrder> IdentifyOldUnseenOrders(TimeSortedCollection <FilledOrder> liveOrders, DateTime lookBeforeTime) { TimeSortedCollection <FilledOrder> unseenOrders = new TimeSortedCollection <FilledOrder>(); // Add any older olders that have not been seen before to the "newOrders" set. TimeSortedCollection <FilledOrder> oldLiveOrders = new TimeSortedCollection <FilledOrder>( liveOrders.Where(order => order.Time < lookBeforeTime)); foreach (FilledOrder oldLiveOrder in oldLiveOrders.Where(o => !OrderAlreadyExists(o))) { unseenOrders.Add(oldLiveOrder); } if (oldLiveOrders.Count > 0 && unseenOrders.Contains(oldLiveOrders.First())) { Log.Warning("Oldest visible live order was found to be new. This may indicate that some orders were missed."); } return(unseenOrders); }
protected override async Task ExecuteAsync(CancellationToken stoppingToken) { //TimeSortedCollection<FilledOrder> liveOrders = new TimeSortedCollection<FilledOrder>(); //liveOrders.Add(new FilledOrder("NIO_201204C55", (float)0.5, "BUY_TO_OPEN", "LIMIT", (float)0.5, 50, new DateTime(2020, 12, 1, 12, 25, 33))); //liveOrders.Add(new FilledOrder("NIO_201204C60", (float)0.5, "BUY_TO_OPEN", "LIMIT", (float)0.5, 50, new DateTime(2020, 12, 1, 12, 22, 30))); //liveOrders.Add(new FilledOrder("NIO_201204C62", (float)0.5, "BUY_TO_OPEN", "LIMIT", (float)0.5, 50, new DateTime(2020, 12, 1, 12, 22, 30))); //liveOrders.Add(new FilledOrder("NIO_201204C65", (float)0.5, "BUY_TO_OPEN", "LIMIT", (float)0.5, 50, new DateTime(2020, 12, 1, 12, 30, 00))); //LivePortfolioClient.IdentifyNewAndUpdatedOrders(liveOrders, 260); //IEnumerable<Position> positions = BrokerClient.GetPositions(); //OptionQuote quote; //try //{ // quote = MarketDataClient.GetOptionQuote("AAPL_201218C140"); // //OptionQuote quote = MarketDataClient.GetQuote("AAPL_121819C140"); //} //catch (Exception ex) //{ // Log.Information(ex, "hello"); //} //Order o1 = new Order("AAPL_201218C150", 1, InstructionType.SELL_TO_CLOSE, OrderType.MARKET, (float)0.08); //BrokerClient.PlaceOrder(o1); //Log.Information("Placing Order: {@Order}", o1); //IList<Position> positions = await ((LottoXClient)LivePortfolioClient).GetPositionsFromImage("C:/Users/Admin/WindowsServices/MarketCode/LottoXService/screenshots/portfolio-4380.png"); //Order o1 = new Order("AAPL_201231C150", 1, InstructionType.BUY_TO_OPEN, OrderType.LIMIT, (float).03); //Order o2 = new Order("CMG_201231C150", 1, InstructionType.BUY_TO_OPEN, OrderType.LIMIT, (float).03); //BrokerClient.PlaceOrder(o1); //BrokerClient.PlaceOrder(o2); BrokerClient.CancelExistingBuyOrders("ZM_210115C550"); Log.Information("RETURNING EARLY"); return; if (!MarketDataClient.IsMarketOpenToday()) { Log.Information("Market closed today"); return; } //Log.Information("NOT LOGGING IN"); await LivePortfolioClient.Login(); TimeSpan marketOpenTime = new TimeSpan(9, 30, 0); TimeSpan marketCloseTime = new TimeSpan(16, 0, 0); int invalidPortfolioStateCount = 0; int errorCount = 0; //string seedOrdersFilename = "C:/Users/Admin/WindowsServices/MarketCode/LottoXService/screenshots/orders-4173.png"; string seedOrdersFilename = ""; while (!stoppingToken.IsCancellationRequested) { TimeSpan now = DateTime.Now.TimeOfDay; if (now >= marketCloseTime) { Log.Information("Market now closed!"); ElmahClient.Dispose(); //Log.Information("NOT BREAKING ON MARKET CLOSED"); break; } else if (now <= marketOpenTime) { Log.Information("Market not open yet!"); // Or, wait until 9:30am await Task.Delay(30 * 1000, stoppingToken); continue; } try { Task _ = ElmahClient.Heartbeats.HealthyAsync(new Guid(_elmahConfig.LogId), _elmahConfig.HeartbeatId); TimeSortedCollection <PositionDelta> deltas; if (seedOrdersFilename.Length > 0) { Log.Information("*********Seeding live orders with file " + seedOrdersFilename); deltas = await LivePortfolioClient.GetLiveDeltasFromPositions(seedOrdersFilename); seedOrdersFilename = ""; } else if (errorCount > 0 || invalidPortfolioStateCount > 0) { Log.Information("***Getting live deltas after error"); deltas = await LivePortfolioClient.GetLiveDeltasFromPositions(); } else if (await LivePortfolioClient.HaveOrdersChanged(null)) { Log.Information("***Change in top orders detected- getting live orders"); deltas = await LivePortfolioClient.GetLiveDeltasFromPositions(); } else { deltas = new TimeSortedCollection <PositionDelta>(); } foreach (PositionDelta delta in deltas) { Order?order = OrderManager.DecideOrder(delta); if (order != null) { if (order.Instruction == InstructionType.SELL_TO_CLOSE) { BrokerClient.CancelExistingBuyOrders(order.Symbol); } BrokerClient.PlaceOrder(order); } } invalidPortfolioStateCount = 0; errorCount = 0; } catch (InvalidPortfolioStateException) { invalidPortfolioStateCount++; if (invalidPortfolioStateCount == 2) { Log.Error("Portfolio found invalid {InvalidCount} times", invalidPortfolioStateCount); } else if (invalidPortfolioStateCount > 2) { Log.Fatal("Portfolio found invalid {InvalidCount} times", invalidPortfolioStateCount); break; } await LivePortfolioClient.Login(); continue; } catch (PortfolioDatabaseException) { //Assume the exception is already logged break; } catch (OptionParsingException ex) { Log.Fatal(ex, "Error parsing option symbol. Symbol {Symbol}. Terminating program.", ex.Symbol); break; } catch (ArgumentException ex) { Log.Fatal(ex, "Arument exception encountered- terminating program."); break; } catch (ModelBuilderException ex) { Log.Error(ex, "ModelBuilderException encountered"); errorCount++; if (errorCount > 2) { Log.Fatal(ex, "Too many consecutive errors encountered. Terminating program. Error count = {ErrorCount}", errorCount); break; } } catch (Exception ex) { errorCount++; if (errorCount <= 2) { Log.Error(ex, "Unexpected error: count = {ErrorCount}", errorCount); } else if (errorCount > 2) { Log.Fatal(ex, "Too many consecutive errors encountered. Terminating program. Error count = {ErrorCount}", errorCount); break; } } await Task.Delay(15 *1000, stoppingToken); } _hostApplicationLifetime.StopApplication(); }
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); }
public IEnumerable <Order> DecideOrdersTimeSorted(TimeSortedCollection <PositionDelta> deltas) { return(deltas.Select(delta => DecideOrder(delta)) .OfType <Order>()); // filter out nulls }