Skip to content

Commit 69e1ebb

Browse files
Adds support to Eurex market to DBAG EA (#4151)
* Adds support for Eurex market
1 parent 9121f1b commit 69e1ebb

File tree

6 files changed

+105
-12
lines changed

6 files changed

+105
-12
lines changed

.changeset/shiny-weeks-drive.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@chainlink/deutsche-boerse-adapter': minor
3+
---
4+
5+
Adds support to Eurex market

packages/sources/deutsche-boerse/src/endpoint/lwba.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ import { InputParameters } from '@chainlink/external-adapter-framework/validatio
33
import { config } from '../config'
44
import { lwbaProtobufWsTransport } from '../transport/lwba'
55

6-
export const MARKETS = ['md-xetraetfetp', 'md-tradegate'] as const
6+
export const MARKET_XETRA_ETFETP = 'md-xetraetfetp' as const
7+
export const MARKET_TRADEGATE = 'md-tradegate' as const
8+
export const MARKET_EUREX_MICRO = 'md-microproducts' as const
9+
export const MARKETS = [MARKET_XETRA_ETFETP, MARKET_TRADEGATE, MARKET_EUREX_MICRO] as const
710
export type Market = (typeof MARKETS)[number]
811

912
export const inputParameters = new InputParameters(

packages/sources/deutsche-boerse/src/transport/lwba.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { create, fromBinary, toBinary } from '@bufbuild/protobuf'
22
import { TransportGenerics } from '@chainlink/external-adapter-framework/transports'
33
import { makeLogger } from '@chainlink/external-adapter-framework/util'
44
import { config } from '../config'
5-
import { BaseEndpointTypes, Market, MARKETS } from '../endpoint/lwba'
5+
import { BaseEndpointTypes, Market, MARKET_EUREX_MICRO, MARKETS } from '../endpoint/lwba'
66
import { BaseEndpointTypes as PriceBaseEndpointTypes } from '../endpoint/price'
77
import {
88
RequestSchema,
@@ -17,6 +17,7 @@ import {
1717
decimalToNumber,
1818
hasSingleBidFrame,
1919
hasSingleOfferFrame,
20+
isFutureInstrument,
2021
isSingleTradeFrame,
2122
parseIsin,
2223
pickProviderTime,
@@ -171,7 +172,6 @@ function decodeStreamMessage(buf: Buffer): StreamMessage | null {
171172
return null
172173
}
173174
}
174-
175175
function processMarketData(
176176
md: MarketData,
177177
cache: InstrumentQuoteCache,
@@ -185,18 +185,24 @@ function processMarketData(
185185
logger.warn('Could not parse ISIN from MarketData')
186186
return null
187187
}
188-
const dat: any = (md as MarketData)?.Dat
189-
if (!dat) {
190-
logger.warn('Could not parse MarketData from MarketData.Instrmt')
191-
return null
192-
}
193188

194189
const quote = cache.get(market, isin)
195190
if (!quote) {
196191
logger.debug('Ignoring message for inactive instrument (not in cache)')
197192
return null
198193
}
199194

195+
if (market === MARKET_EUREX_MICRO && !isFutureInstrument(md)) {
196+
logger.debug({ isin, market }, 'Ignoring non-FUT instrument for FUT-only market')
197+
return null
198+
}
199+
200+
const dat: any = (md as MarketData)?.Dat
201+
if (!dat) {
202+
logger.warn('Could not parse MarketData from MarketData.Instrmt')
203+
return null
204+
}
205+
200206
const providerTime = pickProviderTime(dat)
201207

202208
if (isSingleTradeFrame(dat)) {

packages/sources/deutsche-boerse/src/transport/proto-utils.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import Decimal from 'decimal.js'
2-
import type {
3-
Data as DataProto,
4-
Decimal as DecimalProto,
5-
MarketData as MarketDataProto,
2+
import {
3+
Instrument_SecurityType,
4+
type Data as DataProto,
5+
type Decimal as DecimalProto,
6+
type MarketData as MarketDataProto,
67
} from '../gen/md_cef_pb'
78

89
const MAX_SIG_DIGITS = 15
@@ -63,3 +64,8 @@ export function hasSingleBidFrame(dat?: DataProto): boolean {
6364
export function hasSingleOfferFrame(dat?: DataProto): boolean {
6465
return isDecimalPrice(dat?.Offer?.Px)
6566
}
67+
68+
// true if this instrument type is Future
69+
export function isFutureInstrument(md: MarketDataProto): boolean {
70+
return md.Instrmt?.SecTyp === Instrument_SecurityType.FUT
71+
}

packages/sources/deutsche-boerse/test/unit/lwba.test.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { RequestSchema, StreamMessageSchema } from '../../src/gen/client_pb'
55
import {
66
DataSchema,
77
DecimalSchema,
8+
Instrument_SecurityType,
89
MarketDataSchema,
910
type Decimal,
1011
type MarketData,
@@ -18,6 +19,7 @@ const dec = (m: bigint, e: number): Decimal => create(DecimalSchema, { m, e })
1819
type MarketDataInit = MessageInitShape<typeof MarketDataSchema>
1920
const MARKET = 'md-xetraetfetp' as const
2021
const MARKET2 = 'md-tradegate' as const
22+
const MARKET_EUREX = 'md-microproducts'
2123
const ISIN = 'IE00B53L3W79'
2224
const OTHER = 'US0000000001'
2325

@@ -304,3 +306,54 @@ describe('LWBA Metadata Transport', () => {
304306
expect(d.askSize).toBe(0)
305307
})
306308
})
309+
310+
describe('Eurex (md-microproducts) guard', () => {
311+
test('ignores non-future instruments on Eurex', () => {
312+
const t = lwbaProtobufWsTransport as any
313+
const EUREX_ISIN = 'EU000000FNON' // unique to avoid cache interference
314+
315+
// Activate subscription for Eurex stream
316+
t.config.builders.subscribeMessage({ market: MARKET_EUREX, isin: EUREX_ISIN })
317+
318+
// Non-future instrument (no SecTyp or any non-FUT value) → should be ignored
319+
const mdNonFut = create(MarketDataSchema, {
320+
Instrmt: { Sym: EUREX_ISIN }, // SecTyp omitted → NOT FUT
321+
Dat: create(DataSchema, {
322+
Bid: { Px: dec(10000n, -2), Sz: dec(10n, 0) },
323+
Offer: { Px: dec(10100n, -2), Sz: dec(11n, 0) },
324+
Tm: 1_000_000n,
325+
} as any),
326+
} as any)
327+
328+
const out = t.config.handlers.message(makeStreamBuffer(mdNonFut, MARKET_EUREX))
329+
expect(out).toEqual([]) // ignored because not FUT
330+
})
331+
332+
test('processes FUT instruments on Eurex', () => {
333+
const t = lwbaProtobufWsTransport as any
334+
const EUREX_FUT_ISIN = 'EU000000FFUT' // unique to avoid cache interference
335+
336+
// Activate subscription for Eurex stream
337+
t.config.builders.subscribeMessage({ market: MARKET_EUREX, isin: EUREX_FUT_ISIN })
338+
339+
// FUT instrument with complete quote → should emit
340+
const mdFut = create(MarketDataSchema, {
341+
Instrmt: { Sym: EUREX_FUT_ISIN, SecTyp: Instrument_SecurityType.FUT },
342+
Dat: create(DataSchema, {
343+
Bid: { Px: dec(25000n, -2), Sz: dec(5n, 0) }, // 250.00
344+
Offer: { Px: dec(25150n, -2), Sz: dec(7n, 0) }, // 251.50
345+
Tm: 2_000_000n,
346+
} as any),
347+
} as any)
348+
349+
const res = t.config.handlers.message(makeStreamBuffer(mdFut, MARKET_EUREX))
350+
expect(res.length).toBe(1)
351+
352+
const d = res[0].response.data
353+
expect(d.bid).toBe(250)
354+
expect(d.ask).toBe(251.5)
355+
expect(d.mid).toBe(250.75)
356+
expect(d.bidSize).toBe(5)
357+
expect(d.askSize).toBe(7)
358+
})
359+
})

packages/sources/deutsche-boerse/test/unit/proto-utils.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { create } from '@bufbuild/protobuf'
22
import {
33
DataSchema,
44
DecimalSchema,
5+
Instrument_SecurityType,
56
MarketDataSchema,
67
type Data,
78
type Decimal,
@@ -12,6 +13,7 @@ import {
1213
decimalToNumber,
1314
hasSingleBidFrame,
1415
hasSingleOfferFrame,
16+
isFutureInstrument,
1517
isSingleTradeFrame,
1618
parseIsin,
1719
pickProviderTime,
@@ -72,3 +74,21 @@ describe('proto-utils', () => {
7274
expect(hasSingleOfferFrame(datEmpty)).toBe(false)
7375
})
7476
})
77+
78+
test('isFutureInstrument returns true for FUT', () => {
79+
const md: MarketData = create(MarketDataSchema, {
80+
Instrmt: { Sym: 'FUT-ISIN', SecTyp: Instrument_SecurityType.FUT },
81+
} as any)
82+
expect(isFutureInstrument(md)).toBe(true)
83+
})
84+
85+
test('isFutureInstrument returns false when SecTyp is missing or non-FUT', () => {
86+
const mdMissing: MarketData = create(MarketDataSchema, {
87+
Instrmt: { Sym: 'NOSEC-ISIN' }, // no SecTyp
88+
} as any)
89+
const mdOther: MarketData = create(MarketDataSchema, {
90+
Instrmt: { Sym: 'OTHER-ISIN', SecTyp: 0 as any }, // some non-FUT enum
91+
} as any)
92+
expect(isFutureInstrument(mdMissing)).toBe(false)
93+
expect(isFutureInstrument(mdOther)).toBe(false)
94+
})

0 commit comments

Comments
 (0)