@@ -135,7 +135,8 @@ describe('tradeMetricsProcessor', () => {
135135 takerOrderBaseAssetAmount : 1 ,
136136 takerOrderCumulativeBaseAssetAmountFilled : 1 ,
137137 takerOrderCumulativeQuoteAssetAmountFilled : 100.1 ,
138- maker : 'good-maker' ,
138+ maker : 'maker-user-account' ,
139+ makerIndicativeKey : 'good-maker' ,
139140 makerOrderId : 2 ,
140141 makerOrderDirection : 'long' ,
141142 makerOrderBaseAssetAmount : 1 ,
@@ -367,6 +368,86 @@ describe('tradeMetricsProcessor', () => {
367368 ) ;
368369 } ) ;
369370
371+ it ( 'emits maker_not_competitive using the indicative maker label when indicative key is provided' , async ( ) => {
372+ const metrics = createMetricSinks ( ) ;
373+ const quoteState = new Map < string , any > ( [
374+ [ 'market_mms_perp_0' , [ 'quoted-maker' ] ] ,
375+ [
376+ 'mm_quotes_v2_perp_0_quoted-maker' ,
377+ {
378+ ts : 1710000000000 ,
379+ quotes : [ { bid_price : 99900000 , bid_size : 1000000000 } ] ,
380+ } ,
381+ ] ,
382+ ] ) ;
383+
384+ const { processFillEvent } = createTradeMetricsProcessor ( {
385+ redisClientPrefix : 'dlob:' ,
386+ indicativeQuoteMaxAgeMs : 1000 ,
387+ indicativeQuotesCacheTtlMs : 250 ,
388+ spotMarketPrecisionResolver : ( ) => undefined ,
389+ publisherRedisClient : {
390+ publish : async ( ) => 1 ,
391+ } ,
392+ indicativeQuotesRedisClient : {
393+ smembers : async ( key ) => quoteState . get ( key ) ?? [ ] ,
394+ get : async ( key ) => quoteState . get ( key ) ,
395+ } ,
396+ metrics,
397+ nowMsProvider : ( ) => 1710000000000 ,
398+ } ) ;
399+
400+ const fillEvent : FillEvent = {
401+ ts : 1710000000000 ,
402+ marketIndex : 0 ,
403+ marketType : 'perp' ,
404+ filler : 'mock-filler' ,
405+ takerFee : 0 ,
406+ makerFee : 0 ,
407+ quoteAssetAmountSurplus : 0 ,
408+ baseAssetAmountFilled : 1 ,
409+ quoteAssetAmountFilled : 100 ,
410+ taker : 'mock-taker' ,
411+ takerOrderId : 1 ,
412+ takerOrderDirection : 'short' ,
413+ takerOrderBaseAssetAmount : 1 ,
414+ takerOrderCumulativeBaseAssetAmountFilled : 1 ,
415+ takerOrderCumulativeQuoteAssetAmountFilled : 100 ,
416+ maker : 'maker-user-account' ,
417+ makerIndicativeKey : 'quoted-maker' ,
418+ makerOrderId : 2 ,
419+ makerOrderDirection : 'long' ,
420+ makerOrderBaseAssetAmount : 1 ,
421+ makerOrderCumulativeBaseAssetAmountFilled : 1 ,
422+ makerOrderCumulativeQuoteAssetAmountFilled : 100 ,
423+ oraclePrice : 100 ,
424+ txSig : 'mock-4' ,
425+ slot : 4 ,
426+ fillRecordId : 4 ,
427+ action : 'fill' ,
428+ actionExplanation : 'none' ,
429+ referrerReward : 0 ,
430+ bitFlags : 0 ,
431+ } ;
432+
433+ await processFillEvent ( fillEvent ) ;
434+
435+ expect ( metrics . indicativeQuoteEvaluationCount . calls ) . toEqual (
436+ expect . arrayContaining ( [
437+ {
438+ value : 1 ,
439+ attributes : {
440+ maker : 'quoted-maker' ,
441+ market_index : 0 ,
442+ market_type : 'perp' ,
443+ side : 'long' ,
444+ result : 'maker_not_competitive' ,
445+ } ,
446+ } ,
447+ ] )
448+ ) ;
449+ } ) ;
450+
370451 it ( 'treats quotes as fresh based on evaluation time rather than fill timestamp' , async ( ) => {
371452 const metrics = createMetricSinks ( ) ;
372453 const quoteState = new Map < string , any > ( [
@@ -456,4 +537,81 @@ describe('tradeMetricsProcessor', () => {
456537 ] )
457538 ) ;
458539 } ) ;
540+
541+ it ( 'matches competitive fills using precomputed indicative key' , async ( ) => {
542+ const metrics = createMetricSinks ( ) ;
543+ const quoteState = new Map < string , any > ( [
544+ [ 'market_mms_perp_0' , [ 'indicative-maker' ] ] ,
545+ [
546+ 'mm_quotes_v2_perp_0_indicative-maker' ,
547+ {
548+ ts : 1710000000000 ,
549+ quotes : [ { bid_price : 100100000 , bid_size : 1000000000 } ] ,
550+ } ,
551+ ] ,
552+ ] ) ;
553+
554+ const { processFillEvent } = createTradeMetricsProcessor ( {
555+ redisClientPrefix : 'dlob:' ,
556+ indicativeQuoteMaxAgeMs : 1000 ,
557+ indicativeQuotesCacheTtlMs : 250 ,
558+ spotMarketPrecisionResolver : ( ) => undefined ,
559+ publisherRedisClient : {
560+ publish : async ( ) => 1 ,
561+ } ,
562+ indicativeQuotesRedisClient : {
563+ smembers : async ( key ) => quoteState . get ( key ) ?? [ ] ,
564+ get : async ( key ) => quoteState . get ( key ) ,
565+ } ,
566+ metrics,
567+ nowMsProvider : ( ) => 1710000000000 ,
568+ } ) ;
569+
570+ const fillEvent : FillEvent = {
571+ ts : 1710000000000 ,
572+ marketIndex : 0 ,
573+ marketType : 'perp' ,
574+ filler : 'mock-filler' ,
575+ takerFee : 0 ,
576+ makerFee : 0 ,
577+ quoteAssetAmountSurplus : 0 ,
578+ baseAssetAmountFilled : 1 ,
579+ quoteAssetAmountFilled : 100.1 ,
580+ taker : 'mock-taker' ,
581+ takerOrderId : 1 ,
582+ takerOrderDirection : 'short' ,
583+ takerOrderBaseAssetAmount : 1 ,
584+ takerOrderCumulativeBaseAssetAmountFilled : 1 ,
585+ takerOrderCumulativeQuoteAssetAmountFilled : 100.1 ,
586+ maker : 'maker-user-account' ,
587+ makerIndicativeKey : 'indicative-maker' ,
588+ makerOrderId : 2 ,
589+ makerOrderDirection : 'long' ,
590+ makerOrderBaseAssetAmount : 1 ,
591+ makerOrderCumulativeBaseAssetAmountFilled : 1 ,
592+ makerOrderCumulativeQuoteAssetAmountFilled : 100.1 ,
593+ oraclePrice : 100 ,
594+ txSig : 'mock-5' ,
595+ slot : 5 ,
596+ fillRecordId : 5 ,
597+ action : 'fill' ,
598+ actionExplanation : 'none' ,
599+ referrerReward : 0 ,
600+ bitFlags : 0 ,
601+ } ;
602+
603+ await processFillEvent ( fillEvent ) ;
604+
605+ expect ( metrics . indicativeCompetitiveFillCount . calls ) . toEqual ( [
606+ {
607+ value : 1 ,
608+ attributes : {
609+ maker : 'indicative-maker' ,
610+ market_index : 0 ,
611+ market_type : 'perp' ,
612+ side : 'long' ,
613+ } ,
614+ } ,
615+ ] ) ;
616+ } ) ;
459617} ) ;
0 commit comments