fix(connectors): stamp order books with the venue's event timestamp; … - #73
Conversation
…add {{metric}} webhook placeholder
The five exchange libraries all deliver a genuine venue event timestamp on
the book stream (Binance E, Coinbase per-entry event_time, Kraken book-v2
timestamp, KuCoin time, Bitfinex DataTime) and every connector discarded
it, stamping LastUpdated from local socket receive time. Each connector
gains one ResolveBookTimestamp decision point: venue event time in local
kind, or null when the frame carries none -- a default(DateTime) stamp
would fabricate a colossal latency spike, and receive time must never
masquerade as exchange time. Binance's EventTime clobber is deleted and
its freshness warn keys on ReceiveTime.
TriggerEngine: the {{metric}} placeholder the RestApiAction docs promised
now substitutes with the firing metric; {{plugin}} keeps its historical
output so saved templates render unchanged; help text and the seeded
template updated.
There was a problem hiding this comment.
Pull request overview
This PR updates multiple market connector plugins to stamp order books using the exchange-provided event timestamp (when available) instead of local receive time, and extends TriggerEngine webhook templating to support a documented {{metric}} placeholder while preserving legacy {{plugin}} behavior.
Changes:
- Market connectors: propagate venue event timestamps into the order book
LastUpdatedfield (nullable when the feed frame provides no usable timestamp). - TriggerEngine: add/seed
{{metric}}in webhook body templates and keep{{plugin}}substitution compatible with existing saved templates.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| VisualHFT.Plugins/MarketConnectors.KuCoin/KuCoinPlugin.cs | Buffer and book update pipeline now carries nullable venue timestamps and resolves KuCoin frame time. |
| VisualHFT.Plugins/MarketConnectors.Kraken/KrakenPlugin.cs | Uses Kraken book-v2 frame timestamp as the book stamp (nullable when absent) while keeping freshness checks on receive time. |
| VisualHFT.Plugins/MarketConnectors.Coinbase/CoinbasePlugin.cs | Resolves Coinbase per-entry event_time into a nullable frame timestamp for stamping. |
| VisualHFT.Plugins/MarketConnectors.Bitfinex/BitfinexPlugin.cs | Uses Bitfinex wrapper DataTime (when present) as the venue timestamp for book updates. |
| VisualHFT.Plugins/MarketConnectors.Binance/BinancePlugin.cs | Stops overwriting EventTime, adds a single timestamp decision point, and stamps LastUpdated with venue time (nullable). |
| TriggerEngine/View/TriggerSettingAddOrUpdate.xaml | Updates UI help text to document the available webhook placeholders including {{metric}}. |
| TriggerEngine/View/AddAPISetting.xaml.cs | Seeds the default webhook body template to use {{metric}}. |
| TriggerEngine/TriggerEngineService.cs | Implements {{metric}} substitution while keeping {{plugin}} output stable for older templates. |
Suppressed comments (5)
VisualHFT.Plugins/MarketConnectors.Coinbase/CoinbasePlugin.cs:609
- Same issue as above:
ServerTimeStamp = ts ?? DateTime.Nowintroduces a second wall-clock read inside the same frame. Preferts ?? nowfor consistency and to reduce overhead.
IsBid = true,
LocalTimeStamp = now,
ServerTimeStamp = ts ?? DateTime.Now,
Symbol = symbol
});
VisualHFT.Plugins/MarketConnectors.Coinbase/CoinbasePlugin.cs:626
- Same issue as above: use the already-cached
nowvalue whentsis null so timestamps are consistent within the frame and you avoid multipleDateTime.Nowcalls.
IsBid = false,
LocalTimeStamp = now,
ServerTimeStamp = ts ?? DateTime.Now,
Symbol = symbol
});
VisualHFT.Plugins/MarketConnectors.Coinbase/CoinbasePlugin.cs:638
- Same issue as above:
ServerTimeStampshould use the cachednowwhentsis null to avoid inconsistent timestamps and extra wall-clock reads.
IsBid = false,
LocalTimeStamp = now,
ServerTimeStamp = ts ?? DateTime.Now,
Symbol = symbol
});
VisualHFT.Plugins/MarketConnectors.KuCoin/KuCoinPlugin.cs:779
- Same as above: avoid multiple
DateTime.Nowreads for a single ask-level update by cachingnowand usingts ?? now. This keeps timestamps within the same frame consistent and reduces overhead.
else
local_lob.AddOrUpdateLevel(false, string.Empty, (double)item.Price, (double)item.Quantity, DateTime.Now, ts ?? DateTime.Now);
}
VisualHFT.Plugins/MarketConnectors.Bitfinex/BitfinexPlugin.cs:715
- Same issue as the delete path: cache one
nowper delta soLocalTimeStampandts-null fallback forServerTimeStampare consistent, and avoid duplicateDateTime.Nowcalls.
var delta = new DeltaBookItem()
{
Price = (double)lob_update.Price,
Size = (double)Math.Abs(lob_update.Quantity),
IsBid = isBid,
LocalTimeStamp = DateTime.Now,
ServerTimeStamp = ts ?? DateTime.Now,
Symbol = local_lob.Symbol,
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| else | ||
| { | ||
| restApiAction.BodyTemplate = "{\r\n\r\n\"plugin\":\"{{plugin}}\",\r\n\"value\":\"{{value}}\",\r\n\"timestamp\":\"{{timestamp}}\", \r\n}"; | ||
| restApiAction.BodyTemplate = "{\r\n\r\n\"metric\":\"{{metric}}\",\r\n\"value\":\"{{value}}\",\r\n\"timestamp\":\"{{timestamp}}\", \r\n}"; |
| IsBid = true, | ||
| LocalTimeStamp = now, | ||
| ServerTimeStamp = ts, | ||
| ServerTimeStamp = ts ?? DateTime.Now, | ||
| Symbol = symbol | ||
| }); |
| new HelperCustomQueue<Tuple<DateTime?, string, CoinbaseOrderBookUpdate>>( | ||
| $"<Tuple<DateTime, string, CoinbaseStreamOrderBookChanged>>_{this.Name.Replace(" Plugin", "")}", | ||
| eventBuffers_onReadAction, eventBuffers_onErrorAction)); |
| _eventBuffers.Add(symbol, | ||
| new HelperCustomQueue<Tuple<DateTime, string, KucoinStreamOrderBook>>( | ||
| new HelperCustomQueue<Tuple<DateTime?, string, KucoinStreamOrderBook>>( | ||
| $"<Tuple<DateTime, string, KucoinStreamOrderBookChanged>>_{this.Name.Replace(" Plugin", "")}", | ||
| eventBuffers_onReadAction, eventBuffers_onErrorAction)); |
| else | ||
| local_lob.AddOrUpdateLevel(true, string.Empty, (double)item.Price, (double)item.Quantity, DateTime.Now, ts); | ||
| local_lob.AddOrUpdateLevel(true, string.Empty, (double)item.Price, (double)item.Quantity, DateTime.Now, ts ?? DateTime.Now); | ||
| } |
| var delta = new DeltaBookItem() | ||
| { | ||
| Price = (double)lob_update.Price, | ||
| Size = (double)Math.Abs(lob_update.Quantity), | ||
| IsBid = isBid, | ||
| LocalTimeStamp = DateTime.Now, | ||
| ServerTimeStamp = ts, | ||
| ServerTimeStamp = ts ?? DateTime.Now, | ||
| Symbol = local_lob.Symbol, |
…add {{metric}} webhook placeholder
The five exchange libraries all deliver a genuine venue event timestamp on the book stream (Binance E, Coinbase per-entry event_time, Kraken book-v2 timestamp, KuCoin time, Bitfinex DataTime) and every connector discarded it, stamping LastUpdated from local socket receive time. Each connector gains one ResolveBookTimestamp decision point: venue event time in local kind, or null when the frame carries none -- a default(DateTime) stamp would fabricate a colossal latency spike, and receive time must never masquerade as exchange time. Binance's EventTime clobber is deleted and its freshness warn keys on ReceiveTime.
TriggerEngine: the {{metric}} placeholder the RestApiAction docs promised now substitutes with the firing metric; {{plugin}} keeps its historical output so saved templates render unchanged; help text and the seeded template updated.