// ***************************************************************** // **** Members **** // ***************************************************************** // #endregion// members #region Constructors // ***************************************************************** // **** Constructors **** // ***************************************************************** // // // #endregion//Constructors #region no Properties // ***************************************************************** // **** Properties **** // ***************************************************************** // // #endregion//Properties #region Public Methods // ***************************************************************** // **** Public Methods **** // ***************************************************************** // // /// <summary> /// Caller would like to check if a quote has been filled. This implementation simply looks to see if /// we are on the market or have bettered it. If so, we then create a fill. /// </summary> /// <param name="quote"></param> /// <param name="fill"></param> /// <param name="tickRounding"></param> /// <returns></returns> public static bool TryFill(Quote quote, out Fill fill, double tickRounding = 0.0001) { fill = null; int tradeSign = QTMath.MktSideToMktSign(quote.Side); double sameSidePrice = quote.PricingEngine.ImpliedMarket.Price[quote.Side][0]; double otherSidePrice = quote.PricingEngine.ImpliedMarket.Price[QTMath.MktSideToOtherSide(quote.Side)][0]; double orderPrice = tickRounding * Math.Round(quote.RawPrice / tickRounding); if (tradeSign * (sameSidePrice - orderPrice) > 0) { // We are outside market return(false); } else if (orderPrice >= otherSidePrice) { // order has crossed market. Fill it at market. fill = new Fill(); fill.LocalTime = DateTime.Now; fill.ExchangeTime = DateTime.Now; fill.Price = otherSidePrice; fill.Qty = quote.Qty; return(true); } else { // we are inside the market. fill = new Fill(); fill.LocalTime = DateTime.Now; fill.ExchangeTime = DateTime.Now; fill.Price = orderPrice; fill.Qty = quote.Qty; return(true); } }
// ***************************************************************** // **** Constructors **** // ***************************************************************** // // // #endregion//Constructors #region no Properties // ***************************************************************** // **** Properties **** // ***************************************************************** // // #endregion//Properties #region Public Methods // ***************************************************************** // **** Public Methods **** // ***************************************************************** // // // // ************************************************************* // **** TryFindLegIDWithBestQtyRatio **** // ************************************************************* /// <summary> /// Caller would like to find the leg with the current best qty ratio for a given /// side of the market. If side == Order.BidSide then we are looking for the leg /// with the highest BidQty / AskQty Ratio (lowest AskQty/BidQty Ratio). If we are /// looking at side == Order.AskSide then we are looking for the leg with the highest /// AskQty / BidQty ratio (lower BidQty / AskQty) ratio. /// </summary> /// <param name="legIDs"></param> /// <param name="side"></param> /// <param name="bestLegID"></param> /// <returns>False if a "best" leg id is not found. This has to be a problem with our markets</returns> public bool TryFindLegIDWithBestQtyRatio(List <int> legIDs, int side, out int bestLegID) { bestLegID = -1; int bestQtyRatio = -1; int qtyRatio; int oppSide = QTMath.MktSideToOtherSide(side); for (int leg = 0; leg < legIDs.Count; leg++) { qtyRatio = m_CurveLegs[leg].m_Market.Qty[side][0] / m_CurveLegs[leg].m_Market.Qty[oppSide][0]; if (qtyRatio > bestQtyRatio) { // this leg is our current best qty ratio! bestQtyRatio = qtyRatio; bestLegID = leg; } } return(bestLegID != -1); }
// ***************************************************************** // **** Private Methods **** // ***************************************************************** // // // ********************************************************* // **** IsScratchNeededAtIPrice **** // ********************************************************* /// <summary> /// Caller would like to check if a position from a specific level needs to /// be scratched based on the parameters and our current market state. /// Notes : This could be optimized a bit for speed up purposes! /// </summary> /// <param name="iPrice"></param> /// <returns></returns> private bool IsScratchNeededAtIPrice(int iPrice) { int currentPos; if (!m_IPriceToPosition.TryGetValue(iPrice, out currentPos)) // we have no position to manage here! { return(false); } int posSide = QTMath.MktSignToMktSide(currentPos); // find out which side our position is from int marketIPrice; if (m_IsActive) { // if we are actively scratching // If i am long from the bid, i only care when the bid changes int posSign = QTMath.MktSideToMktSign(posSide); marketIPrice = (int)(m_Market.Price[posSide][0] / m_InstrumentDetails.TickSize); if (iPrice * posSign > marketIPrice * posSign | (marketIPrice == iPrice & m_Market.Qty[posSide][0] < m_ScratchThreshold)) { // market has gone through us, or quantity is now less than our threshold return(true); } } else { // we are passively scratching // if i am long from the bid, i only care when the ask price changes ( i am going to join when price goes sellers) int oppSide = QTMath.MktSideToOtherSide(posSide); int oppSign = QTMath.MktSideToMktSign(oppSide); marketIPrice = (int)(m_Market.Price[oppSide][0] / m_InstrumentDetails.TickSize); if (marketIPrice * oppSign > iPrice * oppSign | (marketIPrice == iPrice & m_Market.Qty[oppSide][0] > m_ScratchThreshold)) { // market has gone through us, or quantity is now less greater than our threshold return(true); } } return(false); }
}//UpdateQuotes(). // // // ***************************************************** // **** Process Synthetic Order() **** // ***************************************************** /// <summary> /// Process fills from Strategy to PricingEngines. /// </summary> /// <param name="syntheticOrder"></param> /// <param name="newFills"></param> /// <returns>True if update required</returns> public override bool ProcessSyntheticOrder(SyntheticOrder syntheticOrder, List <Fill> newFills) { if (newFills == null || newFills.Count == 0) { return(false); } // Collect all fills into work spaces. Log.BeginEntry(LogLevel.Major, "Quote.ProcessSynthOrder: {0} Fills=", ParentStrategy.Name); w_NewFills[0].Clear(); w_NewFills[1].Clear(); foreach (Fill fill in newFills) { int tradeSide = QTMath.MktSignToMktSide(fill.Qty); w_NewFills[tradeSide].Add(fill); m_BuySellQty[tradeSide] += fill.Qty; // this records the raw fills as they come in. Log.AppendEntry(" [{0}]", fill); } int[] position = new int[2]; // this will be updated during allocation of fills. base.m_Position.CopyTo(position, 0); Log.AppendEntry(". "); // Try to cancel fills with undistributed fills. // TODO: Cancel undistributed fills, if any. /* * if ( m_UndistributedFills[0].Count + m_UndistributedFills[1].Count > 0) * { * for (int tradeSide = 0; tradeSide < 2; ++tradeSide) * { * int otherSide = QTMath.MktSideToOtherSide(tradeSide); * if (w_NewFills[tradeSide].Count > 0 && m_UndistributedFills[otherSide].Count > 0 ) * { * Log.AppendEntry(" Canceling with undistributed fills: Not implemented!"); * } * } * } */ // Prepare entry for database write. DateTime localTime = ParentStrategy.StrategyHub.GetLocalTime(); UV.Lib.DatabaseReaderWriters.Queries.FillsQuery query = new Lib.DatabaseReaderWriters.Queries.FillsQuery(); // ----------------------------------------------------- // Pass: distribute fills to stops // ----------------------------------------------------- for (int tradeSide = 0; tradeSide < 2; ++tradeSide) { int exitingSide = QTMath.MktSideToActiveMktSide(tradeSide); if (w_NewFills[tradeSide].Count == 0 || base.m_FillQty[exitingSide].Count == 0) { continue; } List <Quote> exitList = m_QuoteListRecycling.Get(); // get empty list. exitList.Clear(); foreach (KeyValuePair <PricingEngine, int> kv in base.m_FillQty[exitingSide]) { Quote quote; if (m_Quotes[tradeSide].TryGetValue(kv.Key, out quote) && quote.Reason == QuoteReason.Stop && quote.Qty != 0) { exitList.Add(quote); } } if (exitList.Count > 0) { Log.AppendEntry(" Distribute to {0} stop quoters:", exitList.Count); DistributeFillsToQuoters(ref w_NewFills[tradeSide], ref exitList, ref query, ref w_DistributedFills, ref position); Log.AppendEntry(". "); } exitList.Clear(); m_QuoteListRecycling.Recycle(exitList); }//next tradeSide // ----------------------------------------------------- // Pass: distribute fills to quoters who want them. // ----------------------------------------------------- for (int tradeSide = 0; tradeSide < 2; ++tradeSide) { if (w_NewFills[tradeSide].Count == 0) { continue; } int exitingSide = QTMath.MktSideToOtherSide(tradeSide); int tradeSign = QTMath.MktSideToMktSign(tradeSide); List <Quote> exitList = m_QuoteListRecycling.Get(); // get empty lists for entry quotes. List <Quote> entryList = m_QuoteListRecycling.Get(); // and for exit quoters... Log.AppendEntry(" Distribute to working quoters"); List <int> iPriceKeys = new List <int>(m_QuotesByPrice[tradeSide].Keys); int priceLevel = 0; while (w_NewFills[tradeSide].Count > 0 && priceLevel < iPriceKeys.Count) { // On each interation, update our "pos" so we know the remaining qty. int allowedEntryQty = tradeSign * Math.Max(0, m_MaxPosition - Math.Abs(position[tradeSide])); // Load entry/exit quoters for this price level. Log.AppendEntry(" lvl={0}/{1}:", priceLevel, iPriceKeys.Count); entryList.Clear(); exitList.Clear(); List <Quote> quotes = null; if (m_QuotesByPrice[tradeSide].TryGetValue(iPriceKeys[priceLevel], out quotes)) { foreach (Quote quote in quotes) { if (allowedEntryQty != 0 && quote.Reason == QuoteReason.Entry && quote.Qty != 0) { entryList.Add(quote); } else if (base.m_FillQty[exitingSide].ContainsKey(quote.PricingEngine) && quote.Reason == QuoteReason.Exit && quote.Qty != 0) { exitList.Add(quote); } } } if (exitList.Count > 0) { Log.AppendEntry(" Exits ({0}):", exitList.Count); DistributeFillsToQuoters(ref w_NewFills[tradeSide], ref exitList, ref query, ref w_DistributedFills, ref position); } if (entryList.Count > 0) { entryList.Sort(this.QuoteComparerByEngineId); // To better match our backtest, consider sorting entryList by engine names... Log.AppendEntry(" Entries ({0}):", entryList.Count); DistributeFillsToQuoters(ref w_NewFills[tradeSide], ref entryList, ref query, ref w_DistributedFills, ref position); } // priceLevel++; }// next price level // Clean up. entryList.Clear(); exitList.Clear(); m_QuoteListRecycling.Recycle(entryList); m_QuoteListRecycling.Recycle(exitList); Log.AppendEntry(" Finished."); if (w_NewFills[tradeSide].Count > 0) { Log.AppendEntry(" {0} fills remaining.", w_NewFills[tradeSide].Count); } else { Log.AppendEntry(" No fills remain."); } }//tradeSide // ----------------------------------------------------- // Start emergency processing! // ----------------------------------------------------- if (w_NewFills[0].Count > 0 || w_NewFills[1].Count > 0) { Log.AppendEntry(" Process unwanted fills!"); ProcessUnwantedFills(ref w_NewFills, ref w_DistributedFills); } Log.EndEntry(); // end logging for us now, before we call other methods. // ----------------------------------------------------- // Distribute these fills now. // ----------------------------------------------------- if (query != null && query.Count != 0) { ParentStrategy.StrategyHub.RequestDatabaseWrite(query); // submit all the queries } foreach (KeyValuePair <Quote, List <Fill> > kv in w_DistributedFills) { int fillQty = 0; double fillPrice = 0; foreach (Fill fill in kv.Value) { fillQty += fill.Qty; fillPrice = fill.Price; // TODO: this should be ave fill price } int tradeSide = QTMath.MktSignToMktSide(fillQty); int exitSide = QTMath.MktSideToOtherSide(tradeSide); if (fillQty == 0) { continue; } // Update our position counting. int openPos = 0; if (base.m_FillQty[exitSide].TryGetValue(kv.Key.PricingEngine, out openPos)) { // This is an exit (since this PricingEngine has open position on other side of mkt). openPos += fillQty; // update quoter's graph if (m_IsGraphEnabled) { if (exitSide == 0) { // exit long position. m_GraphEngine.AddPoint(m_GraphID, "Long Exit", fillPrice); m_GraphEngine.AddText(m_GraphID, string.Format("{0}", kv.Key.PricingEngine.EngineName), fillPrice + m_TextOffsetTicks * m_QuoteTickSize); } else { // exit short position. m_GraphEngine.AddPoint(m_GraphID, "Short Exit", fillPrice); m_GraphEngine.AddText(m_GraphID, string.Format("{0}", kv.Key.PricingEngine.EngineName), fillPrice - m_TextOffsetTicks * m_QuoteTickSize); } } // Update real position table. if (openPos * fillQty <= 0) { base.m_FillQty[exitSide].Remove(kv.Key.PricingEngine);// complete exit, possibly a side flip } if (openPos != 0) { // There is a new position (on other side of mkt). int posSide = QTMath.MktSignToMktSide(openPos); base.m_FillQty[posSide][kv.Key.PricingEngine] = openPos; } } else { // This is an entry! if (m_IsGraphEnabled) { if (tradeSide == 0) { m_GraphEngine.AddPoint(m_GraphID, "Long Entry", fillPrice); m_GraphEngine.AddText(m_GraphID, string.Format("{0}", kv.Key.PricingEngine.EngineName), fillPrice - m_TextOffsetTicks * m_QuoteTickSize); } else { m_GraphEngine.AddPoint(m_GraphID, "Short Entry", fillPrice); m_GraphEngine.AddText(m_GraphID, string.Format("{0}", kv.Key.PricingEngine.EngineName), fillPrice + m_TextOffsetTicks * m_QuoteTickSize); } } // Update real position table. if (base.m_FillQty[tradeSide].ContainsKey(kv.Key.PricingEngine)) { base.m_FillQty[tradeSide][kv.Key.PricingEngine] += fillQty; // add to this engines position. } else { base.m_FillQty[tradeSide].Add(kv.Key.PricingEngine, fillQty); // store this engines position. } } // Trigger the pricing engine filled event! foreach (Fill fill in kv.Value) { kv.Key.PricingEngine.Filled(fill); } }// next filled Quote. // Update total sum Log.BeginEntry(LogLevel.Major, "Quote.ProcessSynthOrder {0} Summary: ", ParentStrategy.Name); for (int tradeSide = 0; tradeSide < 2; tradeSide++) { // Add up the current position. int pos = 0; foreach (KeyValuePair <PricingEngine, int> kv in base.m_FillQty[tradeSide]) { pos += kv.Value; } base.m_Position[tradeSide] = pos; // Write some logging. Log.AppendEntry(" {0}-side:", QTMath.MktSideToLongString(tradeSide)); Log.AppendEntry(" Pos={0:+0;-0;0}", base.m_Position[tradeSide]); foreach (KeyValuePair <PricingEngine, int> kv in base.m_FillQty[tradeSide]) { Log.AppendEntry(" [{1:+0;-0;0} {0}]", kv.Key.EngineName, kv.Value); } Log.AppendEntry(" TotalQty={0}", m_BuySellQty[tradeSide]); // Log undistributed fills too. if (m_UndistributedFills[tradeSide].Count > 0) { Log.AppendEntry(" Undistributed {0}-fills:", QTMath.MktSideToLongString(tradeSide)); foreach (Fill fill in m_UndistributedFills[tradeSide]) { Log.AppendEntry(" {0}", fill); } } }// next tradeSide Log.AppendEntry(" |MaxPos|={0}.", m_MaxPosition); Log.EndEntry(); // // Clean up work spaces // foreach (KeyValuePair <Quote, List <Fill> > kv in w_DistributedFills) { kv.Value.Clear(); m_FillListRecycling.Recycle(kv.Value); } w_DistributedFills.Clear(); // Quoters and their fills to distribute. return(true); }// ProcessSyntheticOrder()
// // // // #endregion//Constructors #region no Properties // ***************************************************************** // **** Properties **** // ***************************************************************** // // // // // #endregion//Properties #region Public Methods // ***************************************************************** // **** Public Methods **** // ***************************************************************** // // ***************************************** // **** UpdateQuotes **** // ***************************************** /// <summary> /// Called regularly by the StrategyHub after it has allowed PricingEngines to update /// and possibly change their quotes. This method checks for quote changes, and /// sends them to the OrderEngine. /// Whenever the QuoteEngine's parameters are changed (and it would affect how we quote) /// we should call this method fith forceUpdate=true! /// <param name="forceUpdate">set to to force updating each side of quotes, as is done after fills.</param> /// </summary> public override void UpdateQuotes(bool forceUpdate = false) { if (m_IsQuoteEnabled == false) { // TODO: We could check to exit undistributed fills. return; } ValidateQuoteTables(); // Check whether QuoteTickSize has changed. if (ValidatePosition() == false) { this.IsQuoteEnabled = false; return; } DateTime now = ParentStrategy.StrategyHub.GetLocalTime(); List <Fill> fauxFills = null; List <Quote> fauxQuotesFilled = null; // Update graph if (m_IsGraphEnabled && m_FirstPriceEngine != null) { double bid = m_FirstPriceEngine.ImpliedMarket.Price[0][0]; double ask = m_FirstPriceEngine.ImpliedMarket.Price[1][0]; m_GraphEngine.AddPoint(m_GraphID, "Bid", bid); m_GraphEngine.AddPoint(m_GraphID, "Ask", ask); } for (int tradeSide = 0; tradeSide < 2; tradeSide++) { if (m_IsQuoteSideUpdateRequired[tradeSide] == false && forceUpdate == false) { continue; } // // Collect most aggressive non-zero quoters // int tradeSign = QTMath.MktSideToMktSign(tradeSide); int exitSide = QTMath.MktSideToOtherSide(tradeSide); bool isEntryAllowed = tradeSign * base.m_Position[tradeSide] < m_MaxPosition; // true if new entries allowed. int rawIPrice = 0; int realEntryQty = 0; int realExitQty = 0; List <Quote> simQuotes = m_QuoteListRecycling.Get(); simQuotes.Clear(); foreach (KeyValuePair <int, List <Quote> > kv in m_QuotesByPrice[tradeSide]) { foreach (Quote quote in kv.Value) { int qty; if (isEntryAllowed && quote.Reason == QuoteReason.Entry) { realEntryQty += quote.Qty; // collect real entry quote size } else if (base.m_FillQty[exitSide].TryGetValue(quote.PricingEngine, out qty) && qty != 0) { realExitQty += quote.Qty; // collect real exit quantity } else if (quote.Qty != 0) { simQuotes.Add(quote); // collect other quotes, which will be simulated filled. } } rawIPrice = -tradeSign * kv.Key; // price at this level if (realEntryQty != 0 || realExitQty != 0) { break; // stop, once we have found non-empty quote stop. } } // // Send real quotes. // int tradeId; bool isRealQuoteSent = false; int realTotalQty = realExitQty + realEntryQty; if (realTotalQty != 0) { // There is some non-zero (REAL) quantity to quote. // Constrain REAL quotes inside max position allowed: s*qty <= MaxPos - s*Pos int quoteQtyAllowed = Math.Max(0, m_MaxPosition - tradeSign * base.m_Position[tradeSide]); //always positive realTotalQty = tradeSign * Math.Min(quoteQtyAllowed, Math.Abs(realTotalQty)); if (realTotalQty != 0) { // We want to quote a real qty. int orderQty = realTotalQty + m_BuySellQty[tradeSide]; tradeId = ParentStrategy.m_OrderEngine.Quote(tradeSide, rawIPrice * m_QuoteTickSize, orderQty, string.Empty); isRealQuoteSent = true; if (m_IsGraphEnabled) { m_GraphEngine.AddPoint(m_GraphID, QTMath.MktSideToLongString(tradeSide), rawIPrice * m_QuoteTickSize); } } } if (!isRealQuoteSent) { // If in the above, we have not sent a real order, send a zero quote. int orderQty = m_BuySellQty[tradeSide]; tradeId = ParentStrategy.m_OrderEngine.Quote(tradeSide, rawIPrice * m_QuoteTickSize, orderQty, string.Empty); if (m_IsGraphEnabled) { m_GraphEngine.AddPoint(m_GraphID, QTMath.MktSideToLongString(tradeSide), double.NaN); } } // // Simulate quoting. // if (simQuotes.Count > 0) { foreach (Quote quote in simQuotes) { Fill fill; if (quote.Qty != 0 && FillModels.FillModel.TryFill(quote, out fill)) { if (fauxQuotesFilled == null) { // Only make these tables when we need them! fauxQuotesFilled = m_QuoteListRecycling.Get(); fauxFills = m_FillListRecycling.Get(); } fill.LocalTime = now; fill.ExchangeTime = now; fauxFills.Add(fill); fauxQuotesFilled.Add(quote); } } } simQuotes.Clear(); m_QuoteListRecycling.Recycle(simQuotes); m_IsQuoteSideUpdateRequired[tradeSide] = false; }//next side // // Report simulated fills. // if (fauxFills != null && fauxFills.Count > 0) { Log.BeginEntry(LogLevel.Minor, "Quote Simulating fills: "); for (int i = 0; i < fauxFills.Count; ++i) { Fill fill = fauxFills[i]; Quote quote = fauxQuotesFilled[i]; Log.AppendEntry("[{0} filled {1}] ", quote.PricingEngine.EngineName, fill); } Log.EndEntry(); UV.Lib.DatabaseReaderWriters.Queries.FillsQuery query = new Lib.DatabaseReaderWriters.Queries.FillsQuery(); // Process sim fills. for (int i = 0; i < fauxFills.Count; ++i) { Fill fill = fauxFills[i]; Quote quote = fauxQuotesFilled[i]; quote.Qty -= fill.Qty; string msgStr = quote.FillAttribution(); query.AddItemToWrite(ParentStrategy.SqlId, -1, now, m_FauxUser, quote.PricingEngine.EngineName, msgStr, fill.Qty, fill.Price); quote.PricingEngine.Filled(fill); } if (query != null) { ParentStrategy.StrategyHub.RequestDatabaseWrite(query); } fauxFills.Clear(); m_FillListRecycling.Recycle(fauxFills); fauxQuotesFilled.Clear(); m_QuoteListRecycling.Recycle(fauxQuotesFilled); } }//UpdateQuotes().
// // // ********************************************************* // **** TryScratchPosition **** // ********************************************************* /// <summary> /// Called when a market state change triggers a scratch criteria to be met, /// Or after an order change triggers us to attempt and reprocess pending scratch orders /// </summary> /// <param name="iPrice"></param> /// <param name="isFirstAttempt">will be true if market state caused this attempt </param> /// <returns>true if a scratch order has been submitted and not queued</returns> private bool TryScratchPosition(int iPrice, bool isFirstAttempt) { Order scratchOrder; // Our scratch order to be submit or queued int currentPos; if (isFirstAttempt) { if (!m_IPriceToPosition.TryGetValue(iPrice, out currentPos)) { m_Log.NewEntry(LogLevel.Error, "Scratcher:{0} TryScratchPosition failed. iPrice {1} not found in dictionary", this.EngineName, iPrice); return(false); } int qtyToScratch = currentPos * -1; // opposit of qty of position give us scratch qty int scratchSide = QTMath.MktSignToMktSide(qtyToScratch); // opposite side is the scratch side if (m_IPriceToPendingScratchOrder.TryGetValue(iPrice, out scratchOrder)) { // we already have a pending scratch order waiting for submisssion, just add our quantity to it m_ExecutionListener.TryChangeOrderQty(scratchOrder, scratchOrder.OriginalQtyPending + qtyToScratch); } else if (!m_ExecutionListener.TryCreateOrder(m_InstrumentDetails.InstrumentName, scratchSide, iPrice, qtyToScratch, out scratchOrder)) { // order creation failed for some reason, this has to be a logic mistake! m_Log.NewEntry(LogLevel.Error, "Scratcher:{0} TryScratchPosition failed. Order Creation failed", this.EngineName); return(false); } m_IPriceToPosition.Remove(iPrice); // Remove key from dictionary. scratchOrder.OrderReason = OrderReason.Scratch; // tag order with "reason" } else { // this is a second call after we believe we are ready to scratch, check opposing orders again before we submit if (!m_IPriceToPendingScratchOrder.TryGetValue(iPrice, out scratchOrder)) { // something went wrong, log the error m_Log.NewEntry(LogLevel.Error, "Scratcher:{0} TryScratchPosition failed. Unable to find pending scratch order for IPrice {1}", this.EngineName, iPrice); return(false); } } bool isOkayToSubmitScratch = true; if (m_IsActive) { // if we are actively scratching, we could possibly cross ourselves in the market, need to be careful here m_OrderWorkSpaceList.Clear(); // clear order workspace m_OrderEngineOrderBook.GetOrdersByIPrice(QTMath.MktSideToOtherSide(scratchOrder.Side), iPrice, ref m_OrderWorkSpaceList); if (m_OrderWorkSpaceList.Count > 0) { // we have opposing orders that need to be pulled prior to us submitting, arm ourselves and wait for delete ack HashSet <int> orderIdsWaitingDelete; if (!m_IPriceToOppositeOrderIDs.TryGetValue(iPrice, out orderIdsWaitingDelete)) { // we don't have a list yet for this iPrice, create one orderIdsWaitingDelete = new HashSet <int>(); } for (int i = 0; i < m_OrderWorkSpaceList.Count; i++) { // iterate through all pending orders and save their order ids orderIdsWaitingDelete.Add(m_OrderWorkSpaceList[i].Id); // since this is a hash set it will only add unique ids m_OppositeOrderIDToIPrice[m_OrderWorkSpaceList[i].Id] = iPrice; // make sure we map every order to the iprice we care about } m_IPriceToOppositeOrderIDs[iPrice] = orderIdsWaitingDelete; // save our new hash set by iPrice } else { // there is no opposing order, immediately send our scratch order isOkayToSubmitScratch = true; if (!isFirstAttempt) { // we need to cleanup our collection m_IPriceToOppositeOrderIDs.Remove(iPrice); // remove this collection } } } if (isOkayToSubmitScratch) { // we are either passive (there is no possible way we can cross ourselves with a scratch order) or we are active but okay to submit TrySubmitScratchOrder(scratchOrder); return(true); } return(false); }
}//DistributeFillsToQuoters() // // // ************************************************************* // **** Process Unwanted Fills() **** // ************************************************************* protected void ProcessUnwantedFills(ref List <Fill>[] newFills, ref Dictionary <Quote, List <Fill> > distributedFills) { // // Cancel off-setting fills. // if (newFills[0].Count > 0 && newFills[1].Count > 0) { Log.AppendEntry(" Remove offsetting fills:"); while (newFills[0].Count > 0 && newFills[1].Count > 0) { Fill fillLong = newFills[0][0]; newFills[0].RemoveAt(0); Fill fillShort = newFills[1][0]; newFills[1].RemoveAt(0); int netQty = fillLong.Qty + fillShort.Qty; // cancel the first fill in each list. int cancelledQty = Math.Min(Math.Abs(fillLong.Qty), Math.Abs(fillShort.Qty)); Log.AppendEntry(" PnL={0}", cancelledQty * (fillShort.Price - fillLong.Price)); if (netQty > 0) { // Long side will survive somewhat. Fill remainder = Fill.Create(fillLong); remainder.Qty = netQty; newFills[0].Insert(0, remainder); } else if (netQty < 0) { Fill remainder = Fill.Create(fillShort); remainder.Qty = netQty; newFills[1].Insert(0, remainder); } } } // // Pass: distribute fills to anyone with position: "forced exit" // if (newFills[0].Count > 0 || newFills[1].Count > 0) { for (int tradeSide = 0; tradeSide < 2; ++tradeSide) { int exitSide = QTMath.MktSideToOtherSide(tradeSide); if (newFills[tradeSide].Count == 0 || m_FillQty[exitSide].Count == 0) { continue; } int tradeSign = QTMath.MktSideToMktSign(tradeSide); foreach (KeyValuePair <PricingEngine, int> kv in m_FillQty[exitSide]) { Quote quote; List <Fill> fills; int qtyToForce = 0; if (m_Quotes[tradeSide].TryGetValue(kv.Key, out quote) && distributedFills.TryGetValue(quote, out fills)) { // This strategy already has some fills. int fillQty = 0; foreach (Fill fill in fills) { fillQty += fill.Qty; } int finalQty = (kv.Value + fillQty); qtyToForce = Math.Min(0, tradeSign * finalQty); // qty for fill he can still take. } else { qtyToForce = -kv.Value; } if (qtyToForce != 0) { // Pass to him extra fills foreach (Fill fill in newFills[tradeSide]) { } } } }// next side } // // Pass: distribute fills to anyone with a quote! // if (newFills[0].Count > 0 || newFills[1].Count > 0) { for (int tradeSide = 0; tradeSide < 2; ++tradeSide) { if (newFills[tradeSide].Count == 0) { continue; } Log.AppendEntry(" Forcing fills:"); int tradeSign = QTMath.MktSideToMktSign(tradeSide); // Collect all the quotes List <Quote> quotes = m_QuoteListRecycling.Get(); quotes.Clear(); foreach (KeyValuePair <int, List <Quote> > kv in m_QuotesByPrice[tradeSide]) { quotes.AddRange(kv.Value); } int quoteID = 0; int qtyToForce = 1; // TODO: This can be dynamic // Force fills now while (newFills[tradeSide].Count > 0 && quotes.Count > 0) { Quote quote = quotes[quoteID]; Fill origFill = newFills[tradeSide][0]; newFills[tradeSide].RemoveAt(0); Fill fillToDistribute = null; int fillQty = tradeSign * Math.Min(qtyToForce, Math.Abs(origFill.Qty)); if ((origFill.Qty - fillQty) == 0) { // Entire fill is consumed. fillToDistribute = origFill; } else { fillToDistribute = Fill.Create(origFill); fillToDistribute.Qty = fillQty; Fill remainingFill = Fill.Create(origFill); remainingFill.Qty = origFill.Qty - fillQty; newFills[tradeSide].Insert(0, remainingFill); } List <Fill> fills; if (!distributedFills.TryGetValue(quote, out fills)) { // This is this quotes first fill, so create a fill list. fills = new List <Fill>(); distributedFills.Add(quote, fills); } fills.Add(fillToDistribute); Log.AppendEntry(" {0} filled {1}.", quote.PricingEngine.EngineName, fillToDistribute); // increment the while loop! quoteID = (quoteID + 1) % quotes.Count; }//wend // Cleanup. quotes.Clear(); m_QuoteListRecycling.Recycle(quotes); } // next side } // if fills to distribute. // // Failed to distribute fills. // if (newFills[0].Count > 0 || newFills[1].Count > 0) { Log.AppendEntry(" FAILED to distribute fills:"); for (int tradeSide = 0; tradeSide < 2; ++tradeSide) { foreach (Fill fill in newFills[tradeSide]) { m_UndistributedFills[tradeSide].Add(fill); Log.AppendEntry(" {0}", fill); } } } }// ProcessUnwantedFills()