Bullish integration#65
Conversation
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <copilot@github.com>
…evert it here too i think
|
Sorry, ignore previous comments. Those were no approved. |
|
|
||
| export class BullishTradesMapper implements Mapper<'bullish', Trade> { | ||
| canHandle(message: BullishMessage): message is BullishAnonymousTradeUpdateMessage { | ||
| return message.dataType === 'V1TAAnonymousTradeUpdate' && (message.type === 'snapshot' || message.type === 'update') |
There was a problem hiding this comment.
we need to skip snapshots (old not real-time trades)
| } | ||
|
|
||
| private mapLevels(levels: string[]): BookPriceLevel[] { | ||
| return levels.reduce<BookPriceLevel[]>((result, value, index) => { |
There was a problem hiding this comment.
depending on the perf characteristics, but if below code is meaningfully faster I'd use it instead even if it's uglier, but it's perf is +-10% the same it's fine to keep the reduce:
const result = new Array<BookPriceLevel>(levels.length / 2)
for (let i = 0, j = 0; i < levels.length; i += 2, j++) {
result[j] = { price: Number(levels[i]), amount: Number(levels[i + 1]) }
}
return result|
|
||
| export class BullishBookChangeMapper implements Mapper<'bullish', BookChange> { | ||
| canHandle(message: BullishMessage): message is BullishLevel2Message { | ||
| return message.dataType === 'V1TALevel2' && (message.type === 'snapshot' || message.type === 'update') |
There was a problem hiding this comment.
do we need to check snapshot and update type here? are other values possible in the exchange contract?
| const timestamp = new Date(message.data.updatedAtDatetime) | ||
| this.indexPrices.set(message.data.assetSymbol, { price, timestamp }) | ||
|
|
||
| for (const symbol of this.derivativeSymbolsByIndexAsset.get(message.data.assetSymbol) ?? []) { |
There was a problem hiding this comment.
V1TAIndexPrice should only update cached index price state, and not emit ticker message, same for options
| id: trade.tradeId, | ||
| price: Number(trade.price), | ||
| amount: Number(trade.quantity), | ||
| side: trade.side.toLowerCase() as 'buy' | 'sell', |
There was a problem hiding this comment.
Bullish side should not be copied directly into Tardis trade.side.
Bullish exposes two separate fields on trades:
side— the side of the order represented by the trade rowisTaker— whether that represented order was the taker
Tardis has a different normalized contract: trade.side is the taker/aggressor side. That means the mapper must answer “did the taker buy or sell?”, not just “what side did Bullish put in side?”
{
"symbol": "AUSDUSDC",
"tradeId": "100120000008559857",
"price": "1.0000",
"quantity": "1.92651930",
"side": "SELL",
"isTaker": false,
"createdAtDatetime": "2026-05-04T17:23:16.671Z"
}If we copy side: "SELL" directly, Tardis emits:
{ "side": "sell" }But isTaker: false means the represented SELL order was the maker/resting side. The taker was on the opposite side, so the normalized Tardis trade side should be:
{ "side": "buy" }The mapping rule should therefore be:
function mapBullishTradeSide(side: 'BUY' | 'SELL', isTaker: boolean): 'buy' | 'sell' {
if (isTaker) {
return side === 'BUY' ? 'buy' : 'sell'
}
return side === 'BUY' ? 'sell' : 'buy'
}So if we ignore isTaker, trades where Bullish reports the maker side will be normalized with the wrong aggressor direction.
Tests should cover ideally:
side=BUY, isTaker=true -> buy
side=SELL, isTaker=true -> sell
side=BUY, isTaker=false -> sell
side=SELL, isTaker=false -> buy
There was a problem hiding this comment.
i understand, thanks, done
| pendingTickerInfo.updateLastPrice(asNumberIfValid(message.data.last)) | ||
| pendingTickerInfo.updateMarkPrice(asNumberIfValid(message.data.markPrice)) | ||
| pendingTickerInfo.updateFundingRate(asNumberIfValid(message.data.fundingRate)) | ||
| pendingTickerInfo.updateOpenInterest(asNumberIfValid(message.data.openInterest)) |
There was a problem hiding this comment.
Do not use asNumberIfValid where 0 is a valid Bullish ticker value
asNumberIfValid returns undefined for zero. That drops real Bullish values from normalized output.
In V1TATickerResponse messages, zero appears as a valid value for:
openInterestfundingRate- option
markPrice - option greeks:
delta,gamma,theta,vega
Example:
{
"symbol": "BTC-USDC-20260515-150000-C",
"markPrice": "0.0000",
"openInterest": "0.00000000",
"delta": "0.00000000",
"gamma": "0.00000000",
"theta": "0.0000",
"vega": "0.0000",
"impliedVolatility": "0.6182"
}These values should normalize to 0, not undefined.
This is risky for derivative tickers because PendingTickerInfoHelper ignores undefined updates. If a field changes from non-zero to zero, parsing zero as undefined can leave the previous non-zero value cached and emit stale data.
Use a zero-preserving parser for:
- derivative ticker
fundingRate - derivative ticker
openInterest - option summary
openInterest - option summary
markPrice - option summary
delta - option summary
gamma - option summary
theta - option summary
vega
No description provided.