Skip to content

Commit

Permalink
feat: Migrate Market Details page (#1381)
Browse files Browse the repository at this point in the history
  • Loading branch information
jaredvu authored Dec 19, 2024
1 parent 446c4b0 commit 5cb0dbd
Show file tree
Hide file tree
Showing 9 changed files with 142 additions and 71 deletions.
26 changes: 26 additions & 0 deletions src/abacus-ts/calculators/assets.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { mapValues } from 'lodash';
import { weakMapMemoize } from 'reselect';

import { MetadataServiceAssetInfo, MetadataServiceInfoResponse } from '@/constants/assetMetadata';

export const parseAssetInfo = weakMapMemoize(
(assetInfo: MetadataServiceAssetInfo, assetId: string) => ({
assetId,
name: assetInfo.name,
logo: assetInfo.logo,
urls: {
website: assetInfo.urls.website,
technicalDoc: assetInfo.urls.technical_doc,
cmc: assetInfo.urls.cmc,
},
})
);

export const transformAssetsInfo = (assetsInfo: MetadataServiceInfoResponse | undefined) => {
if (assetsInfo == null) {
return assetsInfo;
}

// Add id field to each assetInfo
return mapValues(assetsInfo, (assetInfo, key) => parseAssetInfo(assetInfo, key));
};
49 changes: 48 additions & 1 deletion src/abacus-ts/calculators/markets.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import { IndexerPerpetualMarketResponseObject } from '@/types/indexer/indexerApiGen';
import BigNumber from 'bignumber.js';
import { mapValues } from 'lodash';
import { weakMapMemoize } from 'reselect';

import { TOKEN_DECIMALS, USD_DECIMALS } from '@/constants/numbers';

import { MaybeBigNumber } from '@/lib/numbers';
import {
getAssetFromMarketId,
getDisplayableAssetFromTicker,
getDisplayableTickerFromMarket,
} from '@/lib/assetUtils';
import { MaybeBigNumber, MustBigNumber } from '@/lib/numbers';

import { MarketsData } from '../rawTypes';
import { MarketInfo, MarketsInfo } from '../summaryTypes';
Expand All @@ -16,9 +22,50 @@ export function calculateAllMarkets(markets: MarketsData | undefined): MarketsIn
return mapValues(markets, calculateMarket);
}

export function getMarketEffectiveInitialMarginForMarket(
market: IndexerPerpetualMarketResponseObject
) {
const initialMarginFraction = MaybeBigNumber(market.initialMarginFraction);
const openInterest = MaybeBigNumber(market.openInterest);
const openInterestLowerCap = MaybeBigNumber(market.openInterestLowerCap);
const openInterestUpperCap = MaybeBigNumber(market.openInterestUpperCap);
const oraclePrice = MaybeBigNumber(market.oraclePrice);

if (initialMarginFraction == null) return null;
if (
oraclePrice == null ||
openInterest == null ||
openInterestLowerCap == null ||
openInterestUpperCap == null
) {
return initialMarginFraction;
}

// if these are equal we can throw an error from dividing by zero
if (openInterestUpperCap.eq(openInterestLowerCap)) {
return initialMarginFraction;
}

const openNotional = openInterest.times(oraclePrice);
const scalingFactor = openNotional
.minus(openInterestLowerCap)
.div(openInterestUpperCap.minus(openInterestLowerCap));
const imfIncrease = scalingFactor.times(MustBigNumber(1).minus(initialMarginFraction));

const effectiveIMF = BigNumber.minimum(
initialMarginFraction.plus(BigNumber.maximum(imfIncrease, 0.0)),
1.0
);
return effectiveIMF;
}

const calculateMarket = weakMapMemoize(
(market: IndexerPerpetualMarketResponseObject): MarketInfo => ({
...market,
assetId: getAssetFromMarketId(market.ticker),
displayableAsset: getDisplayableAssetFromTicker(market.ticker),
effectiveInitialMarginFraction: getMarketEffectiveInitialMarginForMarket(market),
displayableTicker: getDisplayableTickerFromMarket(market.ticker),
stepSizeDecimals: MaybeBigNumber(market.stepSize)?.decimalPlaces() ?? TOKEN_DECIMALS,
tickSizeDecimals: MaybeBigNumber(market.tickSize)?.decimalPlaces() ?? USD_DECIMALS,
})
Expand Down
36 changes: 1 addition & 35 deletions src/abacus-ts/calculators/subaccount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
SubaccountSummaryCore,
SubaccountSummaryDerived,
} from '../summaryTypes';
import { getMarketEffectiveInitialMarginForMarket } from './markets';

export function calculateParentSubaccountPositions(
parent: Omit<ParentSubaccountData, 'live'>,
Expand Down Expand Up @@ -287,38 +288,3 @@ function calculatePositionDerivedExtra(
updatedUnrealizedPnlPercent,
};
}

function getMarketEffectiveInitialMarginForMarket(config: IndexerPerpetualMarketResponseObject) {
const initialMarginFraction = MaybeBigNumber(config.initialMarginFraction);
const openInterest = MaybeBigNumber(config.openInterest);
const openInterestLowerCap = MaybeBigNumber(config.openInterestLowerCap);
const openInterestUpperCap = MaybeBigNumber(config.openInterestUpperCap);
const oraclePrice = MaybeBigNumber(config.oraclePrice);

if (initialMarginFraction == null) return null;
if (
oraclePrice == null ||
openInterest == null ||
openInterestLowerCap == null ||
openInterestUpperCap == null
) {
return initialMarginFraction;
}

// if these are equal we can throw an error from dividing by zero
if (openInterestUpperCap.eq(openInterestLowerCap)) {
return initialMarginFraction;
}

const openNotional = openInterest.times(oraclePrice);
const scalingFactor = openNotional
.minus(openInterestLowerCap)
.div(openInterestUpperCap.minus(openInterestLowerCap));
const imfIncrease = scalingFactor.times(MustBigNumber(1).minus(initialMarginFraction));

const effectiveIMF = BigNumber.minimum(
initialMarginFraction.plus(BigNumber.maximum(imfIncrease, 0.0)),
1.0
);
return effectiveIMF;
}
20 changes: 20 additions & 0 deletions src/abacus-ts/selectors/assets.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { createSelector } from 'reselect';

import { transformAssetsInfo } from '../calculators/assets';
import { selectRawAssetsData } from './base';
import { selectCurrentMarketInfo } from './markets';

export const selectAllAssetsInfo = createSelector([selectRawAssetsData], (assets) =>
transformAssetsInfo(assets)
);

export const selectCurrentMarketAssetInfo = createSelector(
[selectCurrentMarketInfo, selectAllAssetsInfo],
(currentMarketInfo, assets) => {
if (currentMarketInfo == null || assets == null) {
return undefined;
}

return assets[currentMarketInfo.assetId];
}
);
6 changes: 6 additions & 0 deletions src/abacus-ts/selectors/markets.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import { createAppSelector } from '@/state/appTypes';
import { getCurrentMarketId } from '@/state/perpetualsSelectors';

import { calculateAllMarkets } from '../calculators/markets';
import { selectRawMarketsData } from './base';

export const selectAllMarketsInfo = createAppSelector([selectRawMarketsData], (markets) =>
calculateAllMarkets(markets)
);

export const selectCurrentMarketInfo = createAppSelector(
[selectAllMarketsInfo, getCurrentMarketId],
(markets, currentMarketId) => (currentMarketId ? markets?.[currentMarketId] : undefined)
);
4 changes: 4 additions & 0 deletions src/abacus-ts/summaryTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ type ConvertStringToBigNumber<T, K extends SelectStringProperties<T>> = {
};

export type MarketInfo = IndexerPerpetualMarketResponseObject & {
assetId: string;
displayableAsset: string;
displayableTicker: string;
effectiveInitialMarginFraction: BigNumber | null;
stepSizeDecimals: number;
tickSizeDecimals: number;
};
Expand Down
6 changes: 3 additions & 3 deletions src/hooks/useMetadataService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,16 +85,16 @@ export const useMetadataService = () => {
export const useMetadataServiceAssetFromId = (marketId?: string) => {
const metadataServiceData = useMetadataService();

const launchableAsset = useMemo(() => {
if (!metadataServiceData.data || !marketId) {
const asset = useMemo(() => {
if (!Object.keys(metadataServiceData.data).length || !marketId) {
return null;
}

const assetId = getAssetFromMarketId(marketId);
return metadataServiceData.data[assetId];
}, [metadataServiceData.data, marketId]);

return launchableAsset;
return asset;
};

export const useMetadataServiceCandles = (
Expand Down
5 changes: 5 additions & 0 deletions src/lib/assetUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,8 @@ export const getDefaultChainIDFromNetworkType = (networkType: NetworkType): stri
if (networkType === 'cosmos') return 'noble-1';
return undefined;
};

export const getAssetDescriptionStringKeys = (assetId: string) => ({
primary: `APP.__ASSETS.${assetId}.PRIMARY`,
secondary: `APP.__ASSETS.${assetId}.SECONDARY`,
});
61 changes: 29 additions & 32 deletions src/views/MarketDetails/CurrentMarketDetails.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { selectCurrentMarketAssetInfo } from '@/abacus-ts/selectors/assets';
import { selectCurrentMarketInfo } from '@/abacus-ts/selectors/markets';
import { IndexerPerpetualMarketType } from '@/types/indexer/indexerApiGen';
import BigNumber from 'bignumber.js';
import { shallowEqual } from 'react-redux';

import { PerpetualMarketType } from '@/constants/abacus';
import { STRING_KEYS } from '@/constants/localization';

import { useStringGetter } from '@/hooks/useStringGetter';
Expand All @@ -11,56 +13,51 @@ import { DiffOutput } from '@/components/DiffOutput';
import { Output, OutputType } from '@/components/Output';

import { useAppSelector } from '@/state/appTypes';
import { getCurrentMarketAssetData } from '@/state/assetsSelectors';
import { getCurrentMarketData } from '@/state/perpetualsSelectors';

import { getDisplayableAssetFromBaseAsset } from '@/lib/assetUtils';
import { getAssetDescriptionStringKeys } from '@/lib/assetUtils';
import { BIG_NUMBERS } from '@/lib/numbers';
import { orEmptyObj } from '@/lib/typeUtils';

import { MarketDetails } from './MarketDetails';

export const CurrentMarketDetails = () => {
const stringGetter = useStringGetter();
const { configs, displayId } = useAppSelector(getCurrentMarketData, shallowEqual) ?? {};
const { id, name, resources } = useAppSelector(getCurrentMarketAssetData, shallowEqual) ?? {};

if (!configs) return null;
const currentMarketData = useAppSelector(selectCurrentMarketInfo, shallowEqual);
const asset = useAppSelector(selectCurrentMarketAssetInfo);

const {
tickSize,
stepSize,
initialMarginFraction,
displayableAsset,
displayableTicker,
effectiveInitialMarginFraction,
initialMarginFraction,
maintenanceMarginFraction,
minOrderSize,
perpetualMarketType,
stepSizeDecimals,
marketType,
tickSize,
tickSizeDecimals,
} = configs;
stepSize,
stepSizeDecimals,
} = orEmptyObj(currentMarketData);

const {
coinMarketCapsLink,
primaryDescriptionKey,
secondaryDescriptionKey,
websiteLink,
whitepaperLink,
} = resources ?? {};
const { assetId, logo, name, urls } = orEmptyObj(asset);
const { cmc, website, technicalDoc } = orEmptyObj(urls);
const { primary, secondary } = getAssetDescriptionStringKeys(assetId ?? '');

const preferEIMF = Boolean(
effectiveInitialMarginFraction && initialMarginFraction !== effectiveInitialMarginFraction
effectiveInitialMarginFraction &&
initialMarginFraction !== effectiveInitialMarginFraction.toString()
);

const items = [
{
key: 'ticker',
label: stringGetter({ key: STRING_KEYS.TICKER }),
value: displayId,
value: displayableTicker,
},
{
key: 'market-type',
label: stringGetter({ key: STRING_KEYS.TYPE }),
value:
perpetualMarketType === PerpetualMarketType.CROSS
marketType === IndexerPerpetualMarketType.CROSS
? stringGetter({ key: STRING_KEYS.CROSS })
: stringGetter({ key: STRING_KEYS.ISOLATED }),
},
Expand All @@ -86,7 +83,7 @@ export const CurrentMarketDetails = () => {
useGrouping
value={stepSize}
type={OutputType.Asset}
tag={getDisplayableAssetFromBaseAsset(id)}
tag={displayableAsset}
fractionDigits={stepSizeDecimals}
/>
),
Expand All @@ -97,9 +94,9 @@ export const CurrentMarketDetails = () => {
value: (
<Output
useGrouping
value={minOrderSize}
value={stepSize}
type={OutputType.Asset}
tag={getDisplayableAssetFromBaseAsset(id)}
tag={displayableAsset}
fractionDigits={stepSizeDecimals}
/>
),
Expand Down Expand Up @@ -149,11 +146,11 @@ export const CurrentMarketDetails = () => {
return (
<MarketDetails
assetName={name}
assetIcon={{ symbol: id, logoUrl: resources?.imageUrl }}
assetIcon={{ symbol: assetId, logoUrl: logo }}
marketDetailItems={items}
primaryDescription={stringGetter({ key: `APP.${primaryDescriptionKey}` })}
secondaryDescription={stringGetter({ key: `APP.${secondaryDescriptionKey}` })}
urls={{ technicalDoc: whitepaperLink, website: websiteLink, cmc: coinMarketCapsLink }}
primaryDescription={stringGetter({ key: primary })}
secondaryDescription={stringGetter({ key: secondary })}
urls={{ technicalDoc, website, cmc }}
/>
);
};

0 comments on commit 5cb0dbd

Please sign in to comment.