feat: pool market info, use market data as source of current funding period startTime

This commit is contained in:
Bartłomiej Głownia 2023-12-20 09:40:05 +01:00 committed by Matthew Russell
parent 59b2f75b13
commit 5d792d2458
No known key found for this signature in database
7 changed files with 144 additions and 67 deletions

View File

@ -15,10 +15,10 @@ import {
getDataSourceSpecForSettlementSchedule, getDataSourceSpecForSettlementSchedule,
isMarketInAuction, isMarketInAuction,
marketInfoProvider, marketInfoProvider,
useFundingPeriodsQuery,
useFundingRate, useFundingRate,
useMarketTradingMode, useMarketTradingMode,
useExternalTwap, useExternalTwap,
useFundingPeriodStartTime,
} from '@vegaprotocol/markets'; } from '@vegaprotocol/markets';
import { MarketState as State } from '@vegaprotocol/types'; import { MarketState as State } from '@vegaprotocol/types';
import { HeaderStat } from '../../components/header'; import { HeaderStat } from '../../components/header';
@ -212,16 +212,9 @@ const useEvery = (marketId: string) => {
}; };
const useStartTime = (marketId: string) => { const useStartTime = (marketId: string) => {
const { data: fundingPeriods } = useFundingPeriodsQuery({ const { data: startTime } = useFundingPeriodStartTime(marketId);
variables: { if (startTime) {
marketId: marketId, return fromNanoSeconds(startTime).getTime();
pagination: { first: 1 },
},
});
const node = fundingPeriods?.fundingPeriods.edges?.[0]?.node;
let startTime: number | undefined = undefined;
if (node && node.startTime && !node.endTime) {
startTime = fromNanoSeconds(node.startTime).getTime();
} }
return startTime; return startTime;
}; };

View File

@ -7,6 +7,7 @@ import type {
FetchResult, FetchResult,
ErrorPolicy, ErrorPolicy,
ApolloQueryResult, ApolloQueryResult,
QueryOptions,
} from '@apollo/client'; } from '@apollo/client';
import type { GraphQLErrors } from '@apollo/client/errors'; import type { GraphQLErrors } from '@apollo/client/errors';
import type { Subscription } from 'zen-observable-ts'; import type { Subscription } from 'zen-observable-ts';
@ -158,6 +159,7 @@ interface DataProviderParams<
}; };
fetchPolicy?: FetchPolicy; fetchPolicy?: FetchPolicy;
resetDelay?: number; resetDelay?: number;
pollInterval?: number;
additionalContext?: Record<string, unknown>; additionalContext?: Record<string, unknown>;
errorPolicyGuard?: (graphqlErrors: GraphQLErrors) => boolean; errorPolicyGuard?: (graphqlErrors: GraphQLErrors) => boolean;
getQueryVariables?: (variables: Variables) => QueryVariables; getQueryVariables?: (variables: Variables) => QueryVariables;
@ -198,6 +200,7 @@ function makeDataProviderInternal<
errorPolicyGuard, errorPolicyGuard,
getQueryVariables, getQueryVariables,
getSubscriptionVariables, getSubscriptionVariables,
pollInterval,
}: DataProviderParams< }: DataProviderParams<
QueryData, QueryData,
Data, Data,
@ -222,6 +225,7 @@ function makeDataProviderInternal<
let client: ApolloClient<object>; let client: ApolloClient<object>;
let subscription: Subscription[] | undefined; let subscription: Subscription[] | undefined;
let pageInfo: PageInfo | null = null; let pageInfo: PageInfo | null = null;
let watchQuerySubscription: Subscription | null = null;
// notify single callback about current state, delta is passes optionally only if notify was invoked onNext // notify single callback about current state, delta is passes optionally only if notify was invoked onNext
const notify = ( const notify = (
@ -243,12 +247,10 @@ function makeDataProviderInternal<
callbacks.forEach((callback) => notify(callback, updateData)); callbacks.forEach((callback) => notify(callback, updateData));
}; };
const call = ( const getQueryOptions = (
pagination?: Pagination, pagination?: Pagination,
policy?: ErrorPolicy policy?: ErrorPolicy
): Promise<ApolloQueryResult<QueryData>> => ): QueryOptions<OperationVariables, QueryData> => ({
client
.query<QueryData>({
query, query,
variables: { variables: {
...(getQueryVariables ? getQueryVariables(variables) : variables), ...(getQueryVariables ? getQueryVariables(variables) : variables),
@ -263,14 +265,82 @@ function makeDataProviderInternal<
fetchPolicy: fetchPolicy || 'no-cache', fetchPolicy: fetchPolicy || 'no-cache',
context: additionalContext, context: additionalContext,
errorPolicy: policy || 'none', errorPolicy: policy || 'none',
}) pollInterval,
});
const onNext = (res: ApolloQueryResult<QueryData>) => {
data = getData(res.data, variables);
if (data && pagination) {
if (!(data instanceof Array)) {
throw new Error(
'data needs to be instance of Edge[] when using pagination'
);
}
pageInfo = pagination.getPageInfo(res.data);
}
// if there was some updates received from subscription during initial query loading apply them on just received data
if (update && data && updateQueue && updateQueue.length > 0) {
while (updateQueue.length) {
const delta = updateQueue.shift();
if (delta) {
setData(update(data, delta, reload, variables));
}
}
}
loaded = true;
};
const onError = (e: Error) => {
if (isNotFoundGraphQLError(e, ['party'])) {
data = getData(null, variables);
loaded = true;
return;
}
// if error will occur data provider stops subscription
error = e;
subscriptionUnsubscribe();
};
const onComplete = (isUpdate?: boolean) => {
loading = false;
notifyAll({ isUpdate });
};
const callWatchQuery = (pagination?: Pagination, policy?: ErrorPolicy) => {
let onNextCalled = false;
try {
watchQuerySubscription = client
.watchQuery(getQueryOptions(pagination, policy))
.subscribe(
(res) => {
onNext(res);
onComplete(onNextCalled);
onNextCalled = true;
},
(error) => {
onError(error as Error);
onComplete();
}
);
} catch (e) {
onError(e as Error);
onComplete();
}
};
const callQuery = (
pagination?: Pagination,
policy?: ErrorPolicy
): Promise<ApolloQueryResult<QueryData>> =>
client
.query<QueryData>(getQueryOptions(pagination, policy))
.catch((err) => { .catch((err) => {
if ( if (
err.graphQLErrors && err.graphQLErrors &&
errorPolicyGuard && errorPolicyGuard &&
errorPolicyGuard(err.graphQLErrors) errorPolicyGuard(err.graphQLErrors)
) { ) {
return call(pagination, 'ignore'); return callQuery(pagination, 'ignore');
} else { } else {
throw err; throw err;
} }
@ -294,7 +364,7 @@ function makeDataProviderInternal<
} }
} }
const res = await call(paginationVariables); const res = await callQuery(paginationVariables);
const insertionData = getData(res.data, variables); const insertionData = getData(res.data, variables);
const insertionPageInfo = pagination.getPageInfo(res.data); const insertionPageInfo = pagination.getPageInfo(res.data);
@ -329,7 +399,7 @@ function makeDataProviderInternal<
variables, variables,
fetchPolicy, fetchPolicy,
}) })
.subscribe(onNext, onError) .subscribe(subscriptionOnNext, subscriptionOnError)
); );
}; };
@ -347,39 +417,16 @@ function makeDataProviderInternal<
const paginationVariables = pagination const paginationVariables = pagination
? { first: pagination.first } ? { first: pagination.first }
: undefined; : undefined;
try { if (pollInterval) {
const res = await call(paginationVariables); callWatchQuery();
data = getData(res.data, variables);
if (data && pagination) {
if (!(data instanceof Array)) {
throw new Error(
'data needs to be instance of Edge[] when using pagination'
);
}
pageInfo = pagination.getPageInfo(res.data);
}
// if there was some updates received from subscription during initial query loading apply them on just received data
if (update && data && updateQueue && updateQueue.length > 0) {
while (updateQueue.length) {
const delta = updateQueue.shift();
if (delta) {
setData(update(data, delta, reload, variables));
}
}
}
loaded = true;
} catch (e) {
if (isNotFoundGraphQLError(e as Error, ['party'])) {
data = getData(null, variables);
loaded = true;
return; return;
} }
// if error will occur data provider stops subscription try {
error = e as Error; onNext(await callQuery(paginationVariables));
subscriptionUnsubscribe(); } catch (e) {
onError(e as Error);
} finally { } finally {
loading = false; onComplete(isUpdate);
notifyAll({ isUpdate });
} }
}; };
@ -399,7 +446,7 @@ function makeDataProviderInternal<
} }
}; };
const onNext = ({ const subscriptionOnNext = ({
data: subscriptionData, data: subscriptionData,
}: FetchResult<SubscriptionData>) => { }: FetchResult<SubscriptionData>) => {
if (!subscriptionData || !getDelta || !update) { if (!subscriptionData || !getDelta || !update) {
@ -418,7 +465,7 @@ function makeDataProviderInternal<
} }
}; };
const onError = (e: Error) => { const subscriptionOnError = (e: Error) => {
error = e; error = e;
subscriptionUnsubscribe(); subscriptionUnsubscribe();
notifyAll(); notifyAll();
@ -442,6 +489,9 @@ function makeDataProviderInternal<
}; };
const reset = () => { const reset = () => {
if (watchQuerySubscription) {
watchQuerySubscription.unsubscribe();
}
subscriptionUnsubscribe(); subscriptionUnsubscribe();
initialized = false; initialized = false;
data = null; data = null;

View File

@ -3,23 +3,23 @@ import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client'; import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client'; import * as Apollo from '@apollo/client';
const defaultOptions = {} as const; const defaultOptions = {} as const;
export type MarketDataUpdateFieldsFragment = { __typename?: 'ObservableMarketData', marketId: string, auctionEnd?: string | null, auctionStart?: string | null, bestBidPrice: string, bestBidVolume: string, bestOfferPrice: string, bestOfferVolume: string, bestStaticBidPrice: string, bestStaticBidVolume: string, bestStaticOfferPrice: string, bestStaticOfferVolume: string, indicativePrice: string, indicativeVolume: string, marketState: Types.MarketState, marketTradingMode: Types.MarketTradingMode, markPrice: string, midPrice: string, openInterest: string, staticMidPrice: string, suppliedStake?: string | null, targetStake?: string | null, trigger: Types.AuctionTrigger, lastTradedPrice: string, productData?: { __typename?: 'PerpetualData', fundingRate?: string | null, fundingPayment?: string | null, externalTwap?: string | null, internalTwap?: string | null } | null, priceMonitoringBounds?: Array<{ __typename?: 'PriceMonitoringBounds', minValidPrice: string, maxValidPrice: string, referencePrice: string, trigger: { __typename?: 'PriceMonitoringTrigger', horizonSecs: number, probability: number, auctionExtensionSecs: number } }> | null }; export type MarketDataUpdateFieldsFragment = { __typename?: 'ObservableMarketData', marketId: string, auctionEnd?: string | null, auctionStart?: string | null, bestBidPrice: string, bestBidVolume: string, bestOfferPrice: string, bestOfferVolume: string, bestStaticBidPrice: string, bestStaticBidVolume: string, bestStaticOfferPrice: string, bestStaticOfferVolume: string, indicativePrice: string, indicativeVolume: string, marketState: Types.MarketState, marketTradingMode: Types.MarketTradingMode, markPrice: string, midPrice: string, openInterest: string, staticMidPrice: string, suppliedStake?: string | null, targetStake?: string | null, trigger: Types.AuctionTrigger, lastTradedPrice: string, productData?: { __typename?: 'PerpetualData', fundingRate?: string | null, fundingPayment?: string | null, externalTwap?: string | null, internalTwap?: string | null, seqNum: number, startTime: any } | null, priceMonitoringBounds?: Array<{ __typename?: 'PriceMonitoringBounds', minValidPrice: string, maxValidPrice: string, referencePrice: string, trigger: { __typename?: 'PriceMonitoringTrigger', horizonSecs: number, probability: number, auctionExtensionSecs: number } }> | null };
export type MarketDataUpdateSubscriptionVariables = Types.Exact<{ export type MarketDataUpdateSubscriptionVariables = Types.Exact<{
marketId: Types.Scalars['ID']; marketId: Types.Scalars['ID'];
}>; }>;
export type MarketDataUpdateSubscription = { __typename?: 'Subscription', marketsData: Array<{ __typename?: 'ObservableMarketData', marketId: string, auctionEnd?: string | null, auctionStart?: string | null, bestBidPrice: string, bestBidVolume: string, bestOfferPrice: string, bestOfferVolume: string, bestStaticBidPrice: string, bestStaticBidVolume: string, bestStaticOfferPrice: string, bestStaticOfferVolume: string, indicativePrice: string, indicativeVolume: string, marketState: Types.MarketState, marketTradingMode: Types.MarketTradingMode, markPrice: string, midPrice: string, openInterest: string, staticMidPrice: string, suppliedStake?: string | null, targetStake?: string | null, trigger: Types.AuctionTrigger, lastTradedPrice: string, productData?: { __typename?: 'PerpetualData', fundingRate?: string | null, fundingPayment?: string | null, externalTwap?: string | null, internalTwap?: string | null } | null, priceMonitoringBounds?: Array<{ __typename?: 'PriceMonitoringBounds', minValidPrice: string, maxValidPrice: string, referencePrice: string, trigger: { __typename?: 'PriceMonitoringTrigger', horizonSecs: number, probability: number, auctionExtensionSecs: number } }> | null }> }; export type MarketDataUpdateSubscription = { __typename?: 'Subscription', marketsData: Array<{ __typename?: 'ObservableMarketData', marketId: string, auctionEnd?: string | null, auctionStart?: string | null, bestBidPrice: string, bestBidVolume: string, bestOfferPrice: string, bestOfferVolume: string, bestStaticBidPrice: string, bestStaticBidVolume: string, bestStaticOfferPrice: string, bestStaticOfferVolume: string, indicativePrice: string, indicativeVolume: string, marketState: Types.MarketState, marketTradingMode: Types.MarketTradingMode, markPrice: string, midPrice: string, openInterest: string, staticMidPrice: string, suppliedStake?: string | null, targetStake?: string | null, trigger: Types.AuctionTrigger, lastTradedPrice: string, productData?: { __typename?: 'PerpetualData', fundingRate?: string | null, fundingPayment?: string | null, externalTwap?: string | null, internalTwap?: string | null, seqNum: number, startTime: any } | null, priceMonitoringBounds?: Array<{ __typename?: 'PriceMonitoringBounds', minValidPrice: string, maxValidPrice: string, referencePrice: string, trigger: { __typename?: 'PriceMonitoringTrigger', horizonSecs: number, probability: number, auctionExtensionSecs: number } }> | null }> };
export type MarketDataFieldsFragment = { __typename?: 'MarketData', auctionEnd?: string | null, auctionStart?: string | null, bestBidPrice: string, bestBidVolume: string, bestOfferPrice: string, bestOfferVolume: string, bestStaticBidPrice: string, bestStaticBidVolume: string, bestStaticOfferPrice: string, bestStaticOfferVolume: string, indicativePrice: string, indicativeVolume: string, marketState: Types.MarketState, marketTradingMode: Types.MarketTradingMode, markPrice: string, midPrice: string, openInterest: string, staticMidPrice: string, suppliedStake?: string | null, targetStake?: string | null, trigger: Types.AuctionTrigger, lastTradedPrice: string, market: { __typename?: 'Market', id: string }, productData?: { __typename?: 'PerpetualData', fundingRate?: string | null, fundingPayment?: string | null, externalTwap?: string | null, internalTwap?: string | null } | null, priceMonitoringBounds?: Array<{ __typename?: 'PriceMonitoringBounds', minValidPrice: string, maxValidPrice: string, referencePrice: string, trigger: { __typename?: 'PriceMonitoringTrigger', horizonSecs: number, probability: number, auctionExtensionSecs: number } }> | null }; export type MarketDataFieldsFragment = { __typename?: 'MarketData', auctionEnd?: string | null, auctionStart?: string | null, bestBidPrice: string, bestBidVolume: string, bestOfferPrice: string, bestOfferVolume: string, bestStaticBidPrice: string, bestStaticBidVolume: string, bestStaticOfferPrice: string, bestStaticOfferVolume: string, indicativePrice: string, indicativeVolume: string, marketState: Types.MarketState, marketTradingMode: Types.MarketTradingMode, markPrice: string, midPrice: string, openInterest: string, staticMidPrice: string, suppliedStake?: string | null, targetStake?: string | null, trigger: Types.AuctionTrigger, lastTradedPrice: string, market: { __typename?: 'Market', id: string }, productData?: { __typename?: 'PerpetualData', fundingRate?: string | null, fundingPayment?: string | null, externalTwap?: string | null, internalTwap?: string | null, seqNum: number, startTime: any } | null, priceMonitoringBounds?: Array<{ __typename?: 'PriceMonitoringBounds', minValidPrice: string, maxValidPrice: string, referencePrice: string, trigger: { __typename?: 'PriceMonitoringTrigger', horizonSecs: number, probability: number, auctionExtensionSecs: number } }> | null };
export type MarketDataQueryVariables = Types.Exact<{ export type MarketDataQueryVariables = Types.Exact<{
marketId: Types.Scalars['ID']; marketId: Types.Scalars['ID'];
}>; }>;
export type MarketDataQuery = { __typename?: 'Query', marketsConnection?: { __typename?: 'MarketConnection', edges: Array<{ __typename?: 'MarketEdge', node: { __typename?: 'Market', data?: { __typename?: 'MarketData', auctionEnd?: string | null, auctionStart?: string | null, bestBidPrice: string, bestBidVolume: string, bestOfferPrice: string, bestOfferVolume: string, bestStaticBidPrice: string, bestStaticBidVolume: string, bestStaticOfferPrice: string, bestStaticOfferVolume: string, indicativePrice: string, indicativeVolume: string, marketState: Types.MarketState, marketTradingMode: Types.MarketTradingMode, markPrice: string, midPrice: string, openInterest: string, staticMidPrice: string, suppliedStake?: string | null, targetStake?: string | null, trigger: Types.AuctionTrigger, lastTradedPrice: string, market: { __typename?: 'Market', id: string }, productData?: { __typename?: 'PerpetualData', fundingRate?: string | null, fundingPayment?: string | null, externalTwap?: string | null, internalTwap?: string | null } | null, priceMonitoringBounds?: Array<{ __typename?: 'PriceMonitoringBounds', minValidPrice: string, maxValidPrice: string, referencePrice: string, trigger: { __typename?: 'PriceMonitoringTrigger', horizonSecs: number, probability: number, auctionExtensionSecs: number } }> | null } | null } }> } | null }; export type MarketDataQuery = { __typename?: 'Query', marketsConnection?: { __typename?: 'MarketConnection', edges: Array<{ __typename?: 'MarketEdge', node: { __typename?: 'Market', data?: { __typename?: 'MarketData', auctionEnd?: string | null, auctionStart?: string | null, bestBidPrice: string, bestBidVolume: string, bestOfferPrice: string, bestOfferVolume: string, bestStaticBidPrice: string, bestStaticBidVolume: string, bestStaticOfferPrice: string, bestStaticOfferVolume: string, indicativePrice: string, indicativeVolume: string, marketState: Types.MarketState, marketTradingMode: Types.MarketTradingMode, markPrice: string, midPrice: string, openInterest: string, staticMidPrice: string, suppliedStake?: string | null, targetStake?: string | null, trigger: Types.AuctionTrigger, lastTradedPrice: string, market: { __typename?: 'Market', id: string }, productData?: { __typename?: 'PerpetualData', fundingRate?: string | null, fundingPayment?: string | null, externalTwap?: string | null, internalTwap?: string | null, seqNum: number, startTime: any } | null, priceMonitoringBounds?: Array<{ __typename?: 'PriceMonitoringBounds', minValidPrice: string, maxValidPrice: string, referencePrice: string, trigger: { __typename?: 'PriceMonitoringTrigger', horizonSecs: number, probability: number, auctionExtensionSecs: number } }> | null } | null } }> } | null };
export const MarketDataUpdateFieldsFragmentDoc = gql` export const MarketDataUpdateFieldsFragmentDoc = gql`
fragment MarketDataUpdateFields on ObservableMarketData { fragment MarketDataUpdateFields on ObservableMarketData {
@ -40,6 +40,8 @@ export const MarketDataUpdateFieldsFragmentDoc = gql`
fundingPayment fundingPayment
externalTwap externalTwap
internalTwap internalTwap
seqNum
startTime
} }
} }
indicativePrice indicativePrice
@ -87,6 +89,8 @@ export const MarketDataFieldsFragmentDoc = gql`
fundingPayment fundingPayment
externalTwap externalTwap
internalTwap internalTwap
seqNum
startTime
} }
} }
indicativePrice indicativePrice

View File

@ -34,6 +34,7 @@ export const marketInfoProvider = makeDataProvider<
query: MarketInfoDocument, query: MarketInfoDocument,
getData, getData,
errorPolicyGuard: marketDataErrorPolicyGuard, errorPolicyGuard: marketDataErrorPolicyGuard,
pollInterval: 5000,
}); });
export const marketInfoWithDataProvider = makeDerivedDataProvider< export const marketInfoWithDataProvider = makeDerivedDataProvider<

View File

@ -124,6 +124,16 @@ export const marketTradingModeProvider = makeDerivedDataProvider<
(parts[0] as ReturnType<typeof getData>)?.marketTradingMode (parts[0] as ReturnType<typeof getData>)?.marketTradingMode
); );
export const fundingRateStartTimeProvider = makeDerivedDataProvider<
NonNullable<MarketDataFieldsFragment['productData']>['startTime'] | undefined,
never,
MarketDataQueryVariables
>(
[marketDataProvider],
(parts, variables, prevData) =>
(parts[0] as ReturnType<typeof getData>)?.productData?.startTime
);
export const marketStateProvider = makeDerivedDataProvider< export const marketStateProvider = makeDerivedDataProvider<
MarketDataFieldsFragment['marketState'] | undefined, MarketDataFieldsFragment['marketState'] | undefined,
never, never,
@ -189,6 +199,17 @@ export const useMarketTradingMode = (marketId?: string, skip?: boolean) => {
}); });
}; };
export const useFundingPeriodStartTime = (
marketId?: string,
skip?: boolean
) => {
return useDataProvider({
dataProvider: fundingRateStartTimeProvider,
variables: { marketId: marketId || '' },
skip: skip || !marketId,
});
};
export const useMarketState = (marketId?: string, skip?: boolean) => { export const useMarketState = (marketId?: string, skip?: boolean) => {
return useDataProvider({ return useDataProvider({
dataProvider: marketStateProvider, dataProvider: marketStateProvider,

View File

@ -16,6 +16,8 @@ fragment MarketDataUpdateFields on ObservableMarketData {
fundingPayment fundingPayment
externalTwap externalTwap
internalTwap internalTwap
seqNum
startTime
} }
} }
indicativePrice indicativePrice
@ -68,6 +70,8 @@ fragment MarketDataFields on MarketData {
fundingPayment fundingPayment
externalTwap externalTwap
internalTwap internalTwap
seqNum
startTime
} }
} }
indicativePrice indicativePrice

View File

@ -3683,6 +3683,10 @@ export type PerpetualData = {
fundingRate?: Maybe<Scalars['String']>; fundingRate?: Maybe<Scalars['String']>;
/** Time-weighted average price calculated from data points for this period from the internal data source. */ /** Time-weighted average price calculated from data points for this period from the internal data source. */
internalTwap?: Maybe<Scalars['String']>; internalTwap?: Maybe<Scalars['String']>;
/** Funding period sequence number */
seqNum: Scalars['Int'];
/** Time at which the funding period started */
startTime: Scalars['Timestamp'];
}; };
export type PerpetualProduct = { export type PerpetualProduct = {