Skip to content

Commit a37bb09

Browse files
Feat/OPDATA-4170 multi market status (#4076)
* OPDATA-4170 multi-market-status functionality, added market options to finnhub-secondary and tradinghours * add changeset * review fixes * remove incorrect comments --------- Co-authored-by: app-token-issuer-data-feeds[bot] <134377064+app-token-issuer-data-feeds[bot]@users.noreply.github.com>
1 parent 8dfac32 commit a37bb09

File tree

18 files changed

+614
-257
lines changed

18 files changed

+614
-257
lines changed

.changeset/poor-keys-protect.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
'@chainlink/market-status-adapter': minor
3+
'@chainlink/finnhub-secondary-adapter': patch
4+
'@chainlink/tradinghours-adapter': patch
5+
'@chainlink/finnhub-adapter': patch
6+
---
7+
8+
multi-market-status endpoint support, added additional markets to tradinghours and finnhub

packages/composites/market-status/src/config/adapters.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,24 @@ export const marketAdapters: Record<string, { primary: AdapterName; secondary: A
2020
primary: 'TRADINGHOURS',
2121
secondary: 'FINNHUB_SECONDARY',
2222
},
23+
lse: {
24+
primary: 'TRADINGHOURS',
25+
secondary: 'FINNHUB_SECONDARY',
26+
},
27+
xetra: {
28+
primary: 'TRADINGHOURS',
29+
secondary: 'FINNHUB_SECONDARY',
30+
},
31+
six: {
32+
primary: 'TRADINGHOURS',
33+
secondary: 'FINNHUB_SECONDARY',
34+
},
35+
euronext_milan: {
36+
primary: 'TRADINGHOURS',
37+
secondary: 'FINNHUB_SECONDARY',
38+
},
39+
euronext_paris: {
40+
primary: 'TRADINGHOURS',
41+
secondary: 'FINNHUB_SECONDARY',
42+
},
2343
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import {
2+
MarketStatusEndpointGenerics,
3+
MarketStatusResultResponse,
4+
} from '@chainlink/external-adapter-framework/adapter'
5+
import { config } from '../config'
6+
7+
export type CompositeMarketStatusResultResponse = MarketStatusResultResponse & {
8+
Data: {
9+
source?: string
10+
}
11+
}
12+
13+
export type BaseMarketStatusEndpointTypes = MarketStatusEndpointGenerics & {
14+
Response: CompositeMarketStatusResultResponse
15+
Settings: typeof config.settings
16+
}
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
export { marketStatusEndpoint as marketStatus } from './market-status'
1+
export { endpoint as marketStatus } from './market-status'
2+
export { endpoint as multiMarketStatus } from './multi-market-status'

packages/composites/market-status/src/endpoint/market-status.ts

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,18 @@
11
import {
22
MarketStatusEndpoint,
3-
MarketStatusResultResponse,
43
marketStatusEndpointInputParametersDefinition,
54
} from '@chainlink/external-adapter-framework/adapter'
65
import { InputParameters } from '@chainlink/external-adapter-framework/validation'
7-
8-
import { config } from '../config'
96
import { transport } from '../transport/market-status'
7+
import { BaseMarketStatusEndpointTypes } from './common'
108

119
export const inputParameters = new InputParameters(marketStatusEndpointInputParametersDefinition)
1210

13-
export type CompositeMarketStatusResultResponse = MarketStatusResultResponse & {
14-
Data: {
15-
source?: string
16-
}
17-
}
18-
19-
export type BaseEndpointTypes = {
11+
export type MarketStatusEndpointTypes = BaseMarketStatusEndpointTypes & {
2012
Parameters: typeof inputParameters.definition
21-
Response: CompositeMarketStatusResultResponse
22-
Settings: typeof config.settings
2313
}
2414

25-
export const marketStatusEndpoint = new MarketStatusEndpoint({
15+
export const endpoint = new MarketStatusEndpoint({
2616
name: 'market-status',
2717
transport,
2818
inputParameters,
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import {
2+
AdapterEndpoint,
3+
marketStatusEndpointInputParametersDefinition,
4+
} from '@chainlink/external-adapter-framework/adapter'
5+
import { InputParameters } from '@chainlink/external-adapter-framework/validation'
6+
import { transport } from '../transport/multi-market-status'
7+
import { BaseMarketStatusEndpointTypes } from './common'
8+
9+
const modeOptions = ['any', 'all']
10+
11+
export const inputParameters = new InputParameters({
12+
...marketStatusEndpointInputParametersDefinition,
13+
openMode: {
14+
description:
15+
'If `any`, returns OPEN if any market is open. If `all`, only returns OPEN if all markets are open.',
16+
options: modeOptions,
17+
default: 'any',
18+
type: 'string',
19+
},
20+
closedMode: {
21+
description:
22+
'If `any`, returns CLOSED if any market is closed. If `all`, only returns CLOSED if all markets are closed. Processed after `openMode`.',
23+
options: modeOptions,
24+
default: 'all',
25+
type: 'string',
26+
},
27+
})
28+
29+
export type MultiMarketStatusEndpointTypes = BaseMarketStatusEndpointTypes & {
30+
Parameters: typeof inputParameters.definition
31+
}
32+
33+
export const endpoint = new AdapterEndpoint({
34+
name: 'multi-market-status',
35+
transport,
36+
inputParameters,
37+
})

packages/composites/market-status/src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { expose, ServerInstance } from '@chainlink/external-adapter-framework'
22
import { Adapter } from '@chainlink/external-adapter-framework/adapter'
33
import { config } from './config'
4-
import { marketStatus } from './endpoint'
4+
import { marketStatus, multiMarketStatus } from './endpoint'
55

66
export const adapter = new Adapter({
77
name: 'MARKET_STATUS',
8-
endpoints: [marketStatus],
8+
endpoints: [marketStatus, multiMarketStatus],
99
defaultEndpoint: marketStatus.name,
1010
config,
1111
})
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import {
2+
EndpointContext,
3+
MarketStatus,
4+
MarketStatusResultResponse,
5+
} from '@chainlink/external-adapter-framework/adapter'
6+
import { TransportDependencies } from '@chainlink/external-adapter-framework/transports'
7+
import { SubscriptionTransport } from '@chainlink/external-adapter-framework/transports/abstract/subscription'
8+
import { makeLogger, sleep } from '@chainlink/external-adapter-framework/util'
9+
import { Requester } from '@chainlink/external-adapter-framework/util/requester'
10+
import { AdapterResponse } from '@chainlink/external-adapter-framework/util/types'
11+
12+
import { AdapterName } from '../config/adapters'
13+
import { BaseMarketStatusEndpointTypes } from '../endpoint/common'
14+
import { inputParameters } from '../endpoint/market-status'
15+
16+
const logger = makeLogger('BaseMarketStatusTransport')
17+
18+
type MarketStatusResult = {
19+
marketStatus: MarketStatus
20+
providerIndicatedTimeUnixMs: number
21+
source?: AdapterName
22+
}
23+
24+
type RequestParams = typeof inputParameters.validated
25+
26+
export abstract class BaseMarketStatusTransport<
27+
T extends BaseMarketStatusEndpointTypes,
28+
> extends SubscriptionTransport<BaseMarketStatusEndpointTypes> {
29+
requester!: Requester
30+
31+
async initialize(
32+
dependencies: TransportDependencies<BaseMarketStatusEndpointTypes>,
33+
adapterSettings: BaseMarketStatusEndpointTypes['Settings'],
34+
endpointName: string,
35+
transportName: string,
36+
): Promise<void> {
37+
await super.initialize(dependencies, adapterSettings, endpointName, transportName)
38+
this.requester = dependencies.requester
39+
}
40+
41+
async backgroundHandler(context: EndpointContext<T>, entries: RequestParams[]) {
42+
await Promise.all(entries.map(async (param) => this.handleRequest(context, param)))
43+
await sleep(context.adapterSettings.BACKGROUND_EXECUTE_MS)
44+
}
45+
46+
async handleRequest(context: EndpointContext<T>, param: RequestParams) {
47+
const requestedAt = Date.now()
48+
49+
let response: AdapterResponse<T['Response']>
50+
try {
51+
const result = await this._handleRequest(context, param)
52+
response = {
53+
data: {
54+
result: result.marketStatus,
55+
source: result.source,
56+
},
57+
result: result.marketStatus,
58+
statusCode: 200,
59+
timestamps: {
60+
providerDataRequestedUnixMs: requestedAt,
61+
providerDataReceivedUnixMs: Date.now(),
62+
providerIndicatedTimeUnixMs: result.providerIndicatedTimeUnixMs,
63+
},
64+
}
65+
} catch (e) {
66+
const errorMessage = e instanceof Error ? e.message : `Unknown error occurred: ${e}`
67+
logger.error(e, errorMessage)
68+
response = {
69+
statusCode: 502,
70+
errorMessage,
71+
timestamps: {
72+
providerDataRequestedUnixMs: requestedAt,
73+
providerDataReceivedUnixMs: Date.now(),
74+
providerIndicatedTimeUnixMs: undefined,
75+
},
76+
}
77+
}
78+
await this.responseCache.write(this.name, [{ params: param, response }])
79+
}
80+
81+
abstract _handleRequest(
82+
context: EndpointContext<T>,
83+
param: RequestParams,
84+
): Promise<MarketStatusResult>
85+
86+
async sendAdapterRequest(
87+
context: EndpointContext<T>,
88+
adapterName: AdapterName,
89+
market: string,
90+
): Promise<MarketStatusResult> {
91+
const key = `${market}:${adapterName}`
92+
const config = {
93+
method: 'POST',
94+
baseURL: context.adapterSettings[`${adapterName}_ADAPTER_URL`],
95+
data: {
96+
data: {
97+
endpoint: 'market-status',
98+
market,
99+
},
100+
},
101+
}
102+
103+
try {
104+
const resp = await this.requester.request<AdapterResponse<MarketStatusResultResponse>>(
105+
key,
106+
config,
107+
)
108+
if (resp.response.status === 200) {
109+
return {
110+
marketStatus: resp.response.data?.result ?? MarketStatus.UNKNOWN,
111+
providerIndicatedTimeUnixMs:
112+
resp.response.data?.timestamps?.providerIndicatedTimeUnixMs ?? Date.now(),
113+
source: adapterName,
114+
}
115+
} else {
116+
logger.error(
117+
`Request to ${adapterName} for market ${market} got status ${resp.response.status}: ${resp.response.data}`,
118+
)
119+
}
120+
} catch (e) {
121+
logger.error(`Request to ${adapterName} for market ${market} failed: ${e}`)
122+
}
123+
124+
return {
125+
marketStatus: MarketStatus.UNKNOWN,
126+
providerIndicatedTimeUnixMs: Date.now(),
127+
}
128+
}
129+
130+
getSubscriptionTtlFromConfig(adapterSettings: BaseMarketStatusEndpointTypes['Settings']): number {
131+
return adapterSettings.WARMUP_SUBSCRIPTION_TTL
132+
}
133+
}

0 commit comments

Comments
 (0)