public event DecodeStatusEventHandler DecodeStarted = delegate { }; // Initializing to the empty anonymous method will ensure that the event is never null, even if no methods are subscribed to it. /// <summary> /// Provides methods and properties for decoding FIX messages. /// </summary> public Decoder() { trailer = new Trailer(); header = new Header(tag, value, trailer, this); xupdate = new MarketDataIncrementalRefresh(tag, value, trailer, this); logout = new Logout(tag, value, trailer); logon = new Logon(tag, value, trailer); vupdate = new MarketDataRequest(tag, value, trailer); fupdate = new SecurityStatus(tag, value, trailer); dupdate = new SecurityDefinition(tag, value, trailer); rupdate = new QuoteRequest(tag, value, trailer); wupdate = new MarketDataSnapshotFullRefresh(tag, value, trailer); message = new Message(); }
/// <summary> /// Applies the current update to the limit order book. /// </summary> /// <param name="header"></param> /// <param name="update"></param> public void Build(Header header, MarketDataIncrementalRefresh.RepeatingGroup update) { if (update.MDUpdateAction == MDUpdateAction.New) { New(header, update); } else if (update.MDUpdateAction == MDUpdateAction.Change) { Change(header, update); } else if (update.MDUpdateAction == MDUpdateAction.Delete) { Delete(header, update); } UpdateTime = header.SendingTime; }
// NOTE: In the context of the CME's data platform, a DELETE OrderAction means the deletion of an entire price level // from the book, not simply the deletion of a single order. Deletions of single // orders (while the price level remains intact) will be communicated via the // CHANGE OrderAction. /// <summary> /// Removes a price level from the limit order book. /// </summary> /// <param name="header"></param> /// <param name="update"></param> private void Delete(Header header, MarketDataIncrementalRefresh.RepeatingGroup update) { int level = update.MDPriceLevel; if (update.MDEntryType == MDEntryType.Bid) { // TO DO: Find a better way to implement below. //if (Bid[level].Price != update.MDEntryPx) //{ // throw new InvalidOperationException("Rebuild operation is attempting to delete a price level (" + update.MDEntryPx + ") that does not exist in the order book!"); //} ShiftDepthLevelsUp(Bid, header.SendingTime, from: level, to: Depth); if (level != Depth) return; Bid[level].Delete(header.SendingTime); } else if (update.MDEntryType == MDEntryType.Offer) { // TO DO: Find a better way to implement below. //if (Ask[level].Price != update.MDEntryPx) //{ // throw new InvalidOperationException("Rebuild operation is attempting to delete a price level (" + update.MDEntryPx + ") that does not exist in the order book!"); //} ShiftDepthLevelsUp(Ask, header.SendingTime, from: level, to: Depth); if (level != Depth) return; Ask[level].Delete(header.SendingTime); } }
// NOTE: In the context of the CME's data platform, a CHANGE OrderAction means a change to the aggregate size // of a pre-existing level. It DOES NOT mean a change to the price of a resting order. // A change to the price of a resting order would be communicated via a DELETE OrderAction // at the old price, followed immediately by a NEW OrderAction at the new price. /// <summary> /// Modifies a price level which is already displayed in the limit order book. /// </summary> /// <param name="header"></param> /// <param name="update"></param> private void Change(Header header, MarketDataIncrementalRefresh.RepeatingGroup update) { int level = update.MDPriceLevel; if (update.MDEntryType == MDEntryType.Bid) { if (Bid[level].Price == null) { New(header, update); return; } Bid[level].Size = update.MDEntrySize; Bid[level].UpdateTime = header.SendingTime; Bid[level].NumberOfOrders = update.NumberOfOrders; } else if (update.MDEntryType == MDEntryType.Offer) { if (Ask[level].Price == null) { New(header, update); return; } Ask[level].Size = update.MDEntrySize; Ask[level].UpdateTime = header.SendingTime; Ask[level].NumberOfOrders = update.NumberOfOrders; } }
// NOTE: In the context of the CME's data platform, a NEW OrderAction means the addition of an entire price level // to the book, not simply the addition of a single order. Additions of single // orders (to a pre-existing price level) will be communicated via the // CHANGE OrderAction. /// <summary> /// Inserts a new price level into the limit order book. /// </summary> /// <param name="header"></param> /// <param name="update"></param> private void New(Header header, MarketDataIncrementalRefresh.RepeatingGroup update) { int level = update.MDPriceLevel; if (update.MDEntryType == MDEntryType.Bid) { ShiftDepthLevelsDown(Bid, from: Depth, to: level); Bid[level].Price = update.MDEntryPx; Bid[level].Size = update.MDEntrySize; Bid[level].UpdateTime = header.SendingTime; Bid[level].NumberOfOrders = update.NumberOfOrders; } else if (update.MDEntryType == MDEntryType.Offer) { ShiftDepthLevelsDown(Ask, from: Depth, to: level); Ask[level].Price = update.MDEntryPx; Ask[level].Size = update.MDEntrySize; Ask[level].UpdateTime = header.SendingTime; Ask[level].NumberOfOrders = update.NumberOfOrders; } }
public event UpdateEventHandler QuoteParsed = delegate { }; // Initializing to the empty anonymous method will ensure that the event is never null, even if no methods are subscribed to it. /// <summary> /// Wrapper around the 'TradeParsed' event, called when a trade update has been parsed. /// </summary> /// <param name="update"></param> internal void OnTradeParsed(MarketDataIncrementalRefresh.RepeatingGroup update) { UpdateEventHandler invoker = TradeParsed; invoker(update); }
private int GetContractIndex(MarketDataIncrementalRefresh.RepeatingGroup update) { int contractHash = update.SecurityDesc.GetId(); return instrumentCollection[0].PertinentContracts(currentTime).IndexOf(contractHash); }
private void BuildOrderBook(MarketDataIncrementalRefresh.RepeatingGroup update) { if (update.QuoteCondition != QuoteCondition.Implied) { OrderBooks[orderBookNumber].Build(header, update); } else if (update.QuoteCondition == QuoteCondition.Implied) { ImpliedOrderBooks[orderBookNumber].Build(header, update); } }
/// <summary> /// Uses various elements of a given message to determine whether or not a quote is valid. /// </summary> /// <param name="update"></param> /// <returns></returns> protected virtual bool IsValidQuote(MarketDataIncrementalRefresh.RepeatingGroup update) { return update.QuoteCondition != QuoteCondition.ExchangeBest; }
/// <summary> /// Uses various elements of a given message to determine whether or not a trade is valid. /// </summary> /// <param name="update"></param> /// <returns></returns> protected virtual bool IsValidTrade(MarketDataIncrementalRefresh.RepeatingGroup update) { return update.QuoteCondition != QuoteCondition.ExchangeBest && update.TradeCondition != TradeCondition.PriceCalculatedByGlobex; }
/// <summary> /// Called when a quote has been decoded from a market data incremental refresh FIX message. /// </summary> /// <param name="update"></param> protected virtual void OnQuoteParsed(MarketDataIncrementalRefresh.RepeatingGroup update) { if (!IsValidQuote(update)) { return; } int contractIndex = GetContractIndex(update); if (contractIndex == NOT_FOUND) { return; } orderBookNumber = contractIndex; messageContainsValidQuote = true; BuildOrderBook(update); }
/// <summary> /// Called when a trade has been decoded from a market data incremental refresh FIX message. /// </summary> /// <param name="update"></param> protected virtual void OnTradeParsed(MarketDataIncrementalRefresh.RepeatingGroup update) { if (!IsValidTrade(update)) { return; } int contractIndex = GetContractIndex(update); if (contractIndex == NOT_FOUND) { return; } messageContainsValidTrade = true; OrderBooks[contractIndex].CommitTrade(update.MDEntryPx, currentTime, update.TradeVolume); }