Skip to content

Commit 2fe9f0e

Browse files
authored
DxFeed only return O (#4774)
* DxFeed only return O * Comments
1 parent 1a5f573 commit 2fe9f0e

File tree

6 files changed

+130
-34
lines changed

6 files changed

+130
-34
lines changed

.changeset/yellow-laws-repeat.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@chainlink/dxfeed-adapter': minor
3+
---
4+
5+
Only use O exchange for overnight

packages/sources/dxfeed/src/endpoint/stock-quotes.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ export const stockQuotesInputParameters = new InputParameters({
1616
description: 'If true, throw error if bid/ask volume is invalid',
1717
type: 'boolean',
1818
},
19+
isOvernight: {
20+
default: false,
21+
description: 'If true, only accept O exchange codes for bid and ask',
22+
type: 'boolean',
23+
},
1924
})
2025

2126
export type BaseEndpointTypes = {

packages/sources/dxfeed/src/transport/stock-quotes.ts

Lines changed: 39 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { makeLogger } from '@chainlink/external-adapter-framework/util'
1+
import { makeLogger, splitArrayIntoChunks } from '@chainlink/external-adapter-framework/util'
22
import { BaseEndpointTypes } from '../endpoint/stock-quotes'
33
import { buildWsTransport } from './ws'
44

@@ -8,9 +8,11 @@ const logger = makeLogger('StockQuotesTransport')
88
// "bidPrice","bidSize","askTime","askExchangeCode","askPrice","askSize"]
99
const eventSymbolIndex = 0
1010
const bidTimeIndex = 4
11+
const bidExchangeCodeIndex = 5
1112
const bidPriceIndex = 6
1213
const bidSizeIndex = 7
1314
const askTimeIndex = 8
15+
const askExchangeCodeIndex = 9
1416
const askPriceIndex = 10
1517
const askSizeIndex = 11
1618
const dataLength = 12
@@ -30,28 +32,28 @@ export const transport = buildWsTransport<BaseEndpointTypes>(
3032
return []
3133
}
3234

33-
return Array.from({ length: data.length / dataLength }, (_, i) => i * dataLength)
34-
.map((i) => generateResponse(data, i))
35-
.flat()
35+
return splitArrayIntoChunks(data, dataLength).flatMap((chunk) => generateResponse(chunk))
3636
},
3737
)
3838

39-
const generateResponse = (data: (string | number)[], i: number) => {
40-
const bidVolume = Number(data[i + bidSizeIndex])
41-
const askVolume = Number(data[i + askSizeIndex])
39+
const generateResponse = (data: (string | number)[]) => {
40+
const bidVolume = Number(data[bidSizeIndex])
41+
const askVolume = Number(data[askSizeIndex])
4242
const invalidVolume = Number.isNaN(Number(bidVolume)) || Number.isNaN(Number(askVolume))
43-
const bidPrice = Number(data[i + bidPriceIndex])
44-
const askPrice = Number(data[i + askPriceIndex])
43+
const bidPrice = Number(data[bidPriceIndex])
44+
const askPrice = Number(data[askPriceIndex])
4545

46-
const params = { base: data[i + eventSymbolIndex].toString() }
46+
const params = { base: data[eventSymbolIndex].toString() }
4747
if (Number.isNaN(bidPrice) || Number.isNaN(askPrice)) {
4848
const response = {
4949
statusCode: 502,
5050
errorMessage: `Bid price: ${bidPrice} or Ask price: ${askPrice} for ${params.base} is invalid.`,
5151
}
5252
return [
53-
{ params: { ...params, requireVolume: false }, response },
54-
{ params: { ...params, requireVolume: true }, response },
53+
{ params: { ...params, requireVolume: false, isOvernight: true }, response },
54+
{ params: { ...params, requireVolume: true, isOvernight: true }, response },
55+
{ params: { ...params, requireVolume: false, isOvernight: false }, response },
56+
{ params: { ...params, requireVolume: true, isOvernight: false }, response },
5557
]
5658
}
5759

@@ -75,23 +77,35 @@ const generateResponse = (data: (string | number)[], i: number) => {
7577
ask_volume: askVolume,
7678
},
7779
timestamps: {
78-
providerIndicatedTimeUnixMs: Math.max(
79-
Number(data[i + bidTimeIndex]),
80-
Number(data[i + askTimeIndex]),
81-
),
80+
providerIndicatedTimeUnixMs: Math.max(Number(data[bidTimeIndex]), Number(data[askTimeIndex])),
8281
},
8382
}
8483

85-
return [
86-
{ params: { ...params, requireVolume: false }, response },
84+
const requireVolumeResponse = invalidVolume
85+
? {
86+
statusCode: 502,
87+
errorMessage: `Bid volume: ${bidVolume} or Ask volume: ${askVolume} for ${params.base} is invalid.`,
88+
}
89+
: response
90+
91+
const responses = [
92+
{ params: { ...params, requireVolume: false, isOvernight: false }, response },
8793
{
88-
params: { ...params, requireVolume: true },
89-
response: invalidVolume
90-
? {
91-
statusCode: 502,
92-
errorMessage: `Bid volume: ${bidVolume} or Ask volume: ${askVolume} for ${params.base} is invalid.`,
93-
}
94-
: response,
94+
params: { ...params, requireVolume: true, isOvernight: false },
95+
response: requireVolumeResponse,
9596
},
9697
]
98+
99+
if (data[bidExchangeCodeIndex] === 'O' && data[askExchangeCodeIndex] === 'O') {
100+
responses.push({
101+
params: { ...params, requireVolume: false, isOvernight: true },
102+
response,
103+
})
104+
responses.push({
105+
params: { ...params, requireVolume: true, isOvernight: true },
106+
response: requireVolumeResponse,
107+
})
108+
}
109+
110+
return responses
97111
}

packages/sources/dxfeed/test/integration/__snapshots__/adapter-ws.test.ts.snap

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,44 @@ exports[`websocket quote endpoint error when data length is not valid 1`] = `
3333
}
3434
`;
3535

36+
exports[`websocket quote endpoint return O when isOvernight 1`] = `
37+
{
38+
"data": {
39+
"ask_price": 172,
40+
"ask_volume": 100,
41+
"bid_price": 170,
42+
"bid_volume": 148,
43+
"mid_price": 171,
44+
},
45+
"result": null,
46+
"statusCode": 200,
47+
"timestamps": {
48+
"providerDataReceivedUnixMs": 1018,
49+
"providerDataStreamEstablishedUnixMs": 1010,
50+
"providerIndicatedTimeUnixMs": 1670868378000,
51+
},
52+
}
53+
`;
54+
55+
exports[`websocket quote endpoint return X when not isOvernight 1`] = `
56+
{
57+
"data": {
58+
"ask_price": 172.1,
59+
"ask_volume": 100.1,
60+
"bid_price": 170.1,
61+
"bid_volume": 148.1,
62+
"mid_price": 171.1,
63+
},
64+
"result": null,
65+
"statusCode": 200,
66+
"timestamps": {
67+
"providerDataReceivedUnixMs": 1018,
68+
"providerDataStreamEstablishedUnixMs": 1010,
69+
"providerIndicatedTimeUnixMs": 1670868378000,
70+
},
71+
}
72+
`;
73+
3674
exports[`websocket quote endpoint should return ask when bid is 0 1`] = `
3775
{
3876
"data": {

packages/sources/dxfeed/test/integration/adapter-ws.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ describe('websocket', () => {
3939
base: 'MULTI_2',
4040
endpoint: 'stock_quotes',
4141
}
42+
const quoteOXData = {
43+
base: 'OX',
44+
endpoint: 'stock_quotes',
45+
}
4246

4347
beforeAll(async () => {
4448
oldEnv = JSON.parse(JSON.stringify(process.env))
@@ -173,5 +177,15 @@ describe('websocket', () => {
173177
})
174178
expect(response.json()).toMatchSnapshot()
175179
})
180+
181+
it('return O when isOvernight', async () => {
182+
const response = await testAdapter.request({ ...quoteOXData, isOvernight: true })
183+
expect(response.json()).toMatchSnapshot()
184+
})
185+
186+
it('return X when not isOvernight', async () => {
187+
const response = await testAdapter.request({ ...quoteOXData })
188+
expect(response.json()).toMatchSnapshot()
189+
})
176190
})
177191
})

packages/sources/dxfeed/test/integration/fixtures.ts

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ export const mockWebSocketServer = (URL: string): MockWebsocketServer => {
5757
{
5858
data: [
5959
'Quote',
60-
['TSLA', 0, 0, 0, 1670868378000, 'V', 170.0, 148.0, 1670868370000, 'V', 172.0, 100.0],
60+
['TSLA', 0, 0, 0, 1670868378000, 'O', 170.0, 148.0, 1670868370000, 'O', 172.0, 100.0],
6161
],
6262
channel: '/service/data',
6363
},
@@ -67,8 +67,8 @@ export const mockWebSocketServer = (URL: string): MockWebsocketServer => {
6767
data: [
6868
'Quote',
6969
[
70-
...['MULTI_1', 1, 2, 3, 4, 'V', 5, 6, 7, 'V', 8, 9],
71-
...['MULTI_2', 10, 11, 12, 13, 'V', 14, 15, 16, 'V', 17, 18],
70+
...['MULTI_1', 1, 2, 3, 4, 'O', 5, 6, 7, 'O', 8, 9],
71+
...['MULTI_2', 10, 11, 12, 13, 'O', 14, 15, 16, 'O', 17, 18],
7272
],
7373
],
7474
channel: '/service/data',
@@ -78,7 +78,7 @@ export const mockWebSocketServer = (URL: string): MockWebsocketServer => {
7878
{
7979
data: [
8080
'Quote',
81-
['NO_BID', 0, 0, 0, 1670868378000, 'V', 0, 148.0, 1670868370000, 'V', 172.0, 100.0],
81+
['NO_BID', 0, 0, 0, 1670868378000, 'O', 0, 148.0, 1670868370000, 'O', 172.0, 100.0],
8282
],
8383
channel: '/service/data',
8484
},
@@ -87,7 +87,7 @@ export const mockWebSocketServer = (URL: string): MockWebsocketServer => {
8787
{
8888
data: [
8989
'Quote',
90-
['NO_ASK', 0, 0, 0, 1670868378000, 'V', 170.0, 148.0, 1670868370000, 'V', 0, 100.0],
90+
['NO_ASK', 0, 0, 0, 1670868378000, 'O', 170.0, 148.0, 1670868370000, 'O', 0, 100.0],
9191
],
9292
channel: '/service/data',
9393
},
@@ -96,7 +96,7 @@ export const mockWebSocketServer = (URL: string): MockWebsocketServer => {
9696
{
9797
data: [
9898
'Quote',
99-
['NO_VOLUME', 0, 0, 0, 1670868378000, 'V', 170.0, 'NaN', 1670868370000, 'V', 172.0, 'NaN'],
99+
['NO_VOLUME', 0, 0, 0, 1670868378000, 'O', 170.0, 'NaN', 1670868370000, 'O', 172.0, 'NaN'],
100100
],
101101
channel: '/service/data',
102102
},
@@ -105,7 +105,7 @@ export const mockWebSocketServer = (URL: string): MockWebsocketServer => {
105105
{
106106
data: [
107107
'Quote',
108-
['INVALID_DATA', 0, 0, 0, 1670868378000, 'V', 170.0, 148.0, 1670868370000, 'V', 0],
108+
['INVALID_DATA', 0, 0, 0, 1670868378000, 'O', 170.0, 148.0, 1670868370000, 'O', 0],
109109
],
110110
channel: '/service/data',
111111
},
@@ -114,7 +114,7 @@ export const mockWebSocketServer = (URL: string): MockWebsocketServer => {
114114
{
115115
data: [
116116
'Quote',
117-
['NULL_BID', 0, 0, 0, 1670868378000, 'V', 'NaN', 148.0, 1670868370000, 'V', 172.0, 100.0],
117+
['NULL_BID', 0, 0, 0, 1670868378000, 'O', 'NaN', 148.0, 1670868370000, 'O', 172.0, 100.0],
118118
],
119119
channel: '/service/data',
120120
},
@@ -123,7 +123,25 @@ export const mockWebSocketServer = (URL: string): MockWebsocketServer => {
123123
{
124124
data: [
125125
'Quote',
126-
['NULL_ASK', 0, 0, 0, 1670868378000, 'V', 170.0, 148.0, 1670868370000, 'V', 'NaN', 100.0],
126+
['NULL_ASK', 0, 0, 0, 1670868378000, 'O', 170.0, 148.0, 1670868370000, 'O', 'NaN', 100.0],
127+
],
128+
channel: '/service/data',
129+
},
130+
]
131+
const quoteReponseInO = [
132+
{
133+
data: [
134+
'Quote',
135+
['OX', 0, 0, 0, 1670868378000, 'O', 170.0, 148.0, 1670868370000, 'O', 172.0, 100.0],
136+
],
137+
channel: '/service/data',
138+
},
139+
]
140+
const quoteReponseInX = [
141+
{
142+
data: [
143+
'Quote',
144+
['OX', 0, 0, 0, 1670868378000, 'O', 170.1, 148.1, 1670868370000, 'X', 172.1, 100.1],
127145
],
128146
channel: '/service/data',
129147
},
@@ -316,6 +334,8 @@ export const mockWebSocketServer = (URL: string): MockWebsocketServer => {
316334
socket.send(JSON.stringify(invalidQuoteReponse))
317335
socket.send(JSON.stringify(nullBidQuoteResponse))
318336
socket.send(JSON.stringify(nullAskQuoteResponse))
337+
socket.send(JSON.stringify(quoteReponseInO))
338+
socket.send(JSON.stringify(quoteReponseInX))
319339
socket.send(JSON.stringify(tradeResponse))
320340
socket.send(JSON.stringify(tradeResponseIgnored))
321341
socket.send(JSON.stringify(tradeResponseOvernight))

0 commit comments

Comments
 (0)