Skip to content
This repository was archived by the owner on Oct 15, 2024. It is now read-only.

Commit 540f92e

Browse files
authored
feat: Depth Chart API endpoint (#290)
* feat: Depth Chart API endpoint * added tests * fix excessive accumulation when multiple prices land in the same bucket * Update to latest asset-swapper * refactor and accumulate sources over buckets
1 parent 5736c74 commit 540f92e

10 files changed

Lines changed: 718 additions & 6 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@
128128
},
129129
"dependencies": {
130130
"@0x/assert": "^3.0.4",
131-
"@0x/asset-swapper": "0xProject/gitpkg-registry#0x-asset-swapper-v4.6.0-9a16f5736",
131+
"@0x/asset-swapper": "0xProject/gitpkg-registry#0x-asset-swapper-v4.6.0-ae2a6fb68",
132132
"@0x/connect": "^6.0.4",
133133
"@0x/contract-addresses": "^4.11.0",
134134
"@0x/contract-wrappers": "^13.7.0",

src/config.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ export const ASSET_SWAPPER_MARKET_ORDERS_V0_OPTS: Partial<SwapQuoteRequestOpts>
330330
feeSchedule: FEE_SCHEDULE_V0,
331331
gasSchedule: GAS_SCHEDULE_V0,
332332
shouldBatchBridgeOrders: true,
333-
runLimit: 2 ** 13,
333+
runLimit: 2 ** 8,
334334
};
335335

336336
export const GAS_SCHEDULE_V1: FeeSchedule = {
@@ -356,7 +356,7 @@ export const ASSET_SWAPPER_MARKET_ORDERS_V1_OPTS: Partial<SwapQuoteRequestOpts>
356356
feeSchedule: FEE_SCHEDULE_V1,
357357
gasSchedule: GAS_SCHEDULE_V1,
358358
shouldBatchBridgeOrders: false,
359-
runLimit: 2 ** 13,
359+
runLimit: 2 ** 8,
360360
};
361361

362362
export const SAMPLER_OVERRIDES: SamplerOverrides | undefined = (() => {

src/constants.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,8 @@ export const GST2_WALLET_ADDRESSES = {
8989
[ChainId.Kovan]: NULL_ADDRESS,
9090
[ChainId.Ganache]: NULL_ADDRESS,
9191
};
92+
93+
// Market Depth
94+
export const MARKET_DEPTH_MAX_SAMPLES = 50;
95+
export const MARKET_DEPTH_DEFAULT_DISTRIBUTION = 1.05;
96+
export const MARKET_DEPTH_END_PRICE_SLIPPAGE_PERC = 20;

src/handlers/swap_handlers.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@ import * as express from 'express';
44
import * as HttpStatus from 'http-status-codes';
55

66
import { CHAIN_ID } from '../config';
7-
import { DEFAULT_QUOTE_SLIPPAGE_PERCENTAGE, SWAP_DOCS_URL } from '../constants';
7+
import {
8+
DEFAULT_QUOTE_SLIPPAGE_PERCENTAGE,
9+
MARKET_DEPTH_DEFAULT_DISTRIBUTION,
10+
MARKET_DEPTH_MAX_SAMPLES,
11+
SWAP_DOCS_URL,
12+
} from '../constants';
813
import {
914
InternalServerError,
1015
RevertAPIError,
@@ -125,6 +130,27 @@ export class SwapHandlers {
125130
const records = await this._swapService.getTokenPricesAsync(baseAsset, unitAmount);
126131
res.status(HttpStatus.OK).send({ records });
127132
}
133+
134+
public async getMarketDepthAsync(req: express.Request, res: express.Response): Promise<void> {
135+
const makerToken = getTokenMetadataIfExists(req.query.buyToken as string, CHAIN_ID);
136+
const takerToken = getTokenMetadataIfExists(req.query.sellToken as string, CHAIN_ID);
137+
const response = await this._swapService.calculateMarketDepthAsync({
138+
buyToken: makerToken,
139+
sellToken: takerToken,
140+
sellAmount: new BigNumber(req.query.sellAmount as string),
141+
// tslint:disable-next-line:radix custom-no-magic-numbers
142+
numSamples: req.query.numSamples ? parseInt(req.query.numSamples as string) : MARKET_DEPTH_MAX_SAMPLES,
143+
sampleDistributionBase: req.query.sampleDistributionBase
144+
? parseFloat(req.query.sampleDistributionBase as string)
145+
: MARKET_DEPTH_DEFAULT_DISTRIBUTION,
146+
excludedSources:
147+
req.query.excludedSources === undefined
148+
? []
149+
: parseUtils.parseStringArrForERC20BridgeSources((req.query.excludedSources as string).split(',')),
150+
});
151+
res.status(HttpStatus.OK).send({ ...response, buyToken: makerToken, sellToken: takerToken });
152+
}
153+
128154
private async _calculateSwapQuoteAsync(
129155
params: GetSwapQuoteRequestParams,
130156
swapVersion: SwapVersion,

src/routers/swap_router.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export function createSwapRouter(swapService: SwapService): express.Router {
1212
router.get('/v0', asyncHandler(SwapHandlers.rootAsync.bind(SwapHandlers)));
1313
router.get('/v0/prices', asyncHandler(handlers.getTokenPricesAsync.bind(handlers)));
1414
router.get('/v0/tokens', asyncHandler(handlers.getSwapTokensAsync.bind(handlers)));
15+
router.get('/v0/depth', asyncHandler(handlers.getMarketDepthAsync.bind(handlers)));
1516

1617
router.get('/v0/quote', asyncHandler(handlers.getSwapQuoteAsync.bind(handlers, SwapVersion.V0)));
1718
router.get('/v0/price', asyncHandler(handlers.getSwapPriceAsync.bind(handlers, SwapVersion.V0)));

src/services/swap_service.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { SwapQuoteRequestOpts, SwapQuoterOpts } from '@0x/asset-swapper/lib/src/
1212
import { ContractAddresses, getContractAddressesForChainOrThrow } from '@0x/contract-addresses';
1313
import { ERC20TokenContract, WETH9Contract } from '@0x/contract-wrappers';
1414
import { assetDataUtils, SupportedProvider } from '@0x/order-utils';
15+
import { MarketOperation } from '@0x/types';
1516
import { BigNumber, decodeThrownErrorAsRevertError, RevertError } from '@0x/utils';
1617
import { TxData, Web3Wrapper } from '@0x/web3-wrapper';
1718
import * as _ from 'lodash';
@@ -38,6 +39,8 @@ import { InsufficientFundsError } from '../errors';
3839
import { logger } from '../logger';
3940
import { TokenMetadatasForChains } from '../token_metadatas_for_networks';
4041
import {
42+
BucketedPriceDepth,
43+
CalaculateMarketDepthParams,
4144
CalculateSwapQuoteParams,
4245
GetSwapQuoteResponse,
4346
GetTokenPricesResponse,
@@ -46,6 +49,7 @@ import {
4649
SwapVersion,
4750
TokenMetadata,
4851
} from '../types';
52+
import { marketDepthUtils } from '../utils/market_depth_utils';
4953
import { createResultCache, ResultCache } from '../utils/result_cache';
5054
import { serviceUtils } from '../utils/service_utils';
5155
import { getTokenMetadataIfExists } from '../utils/token_metadata_utils';
@@ -269,6 +273,58 @@ export class SwapService {
269273
return prices;
270274
}
271275

276+
public async calculateMarketDepthAsync(
277+
params: CalaculateMarketDepthParams,
278+
): Promise<{
279+
asks: { depth: BucketedPriceDepth[] };
280+
bids: { depth: BucketedPriceDepth[] };
281+
}> {
282+
const { buyToken, sellToken, sellAmount, numSamples, sampleDistributionBase, excludedSources } = params;
283+
const marketDepth = await this._swapQuoter.getBidAskLiquidityForMakerTakerAssetPairAsync(
284+
buyToken.tokenAddress,
285+
sellToken.tokenAddress,
286+
sellAmount,
287+
{
288+
numSamples,
289+
excludedSources: [...(excludedSources || []), ERC20BridgeSource.MultiBridge],
290+
sampleDistributionBase,
291+
},
292+
);
293+
294+
const maxEndSlippagePercentage = 20;
295+
const scalePriceByDecimals = (priceDepth: BucketedPriceDepth[]) =>
296+
priceDepth.map(b => ({
297+
...b,
298+
price: b.price.times(new BigNumber(10).pow(sellToken.decimals - buyToken.decimals)),
299+
}));
300+
const askDepth = scalePriceByDecimals(
301+
marketDepthUtils.calculateDepthForSide(
302+
marketDepth.asks,
303+
MarketOperation.Sell,
304+
numSamples * 2,
305+
sampleDistributionBase,
306+
maxEndSlippagePercentage,
307+
),
308+
);
309+
const bidDepth = scalePriceByDecimals(
310+
marketDepthUtils.calculateDepthForSide(
311+
marketDepth.bids,
312+
MarketOperation.Buy,
313+
numSamples * 2,
314+
sampleDistributionBase,
315+
maxEndSlippagePercentage,
316+
),
317+
);
318+
return {
319+
// We're buying buyToken and SELLING sellToken (DAI) (50k)
320+
// Price goes from HIGH to LOW
321+
asks: { depth: askDepth },
322+
// We're BUYING sellToken (DAI) (50k) and selling buyToken
323+
// Price goes from LOW to HIGH
324+
bids: { depth: bidDepth },
325+
};
326+
}
327+
272328
private async _getSwapQuoteForWethAsync(
273329
params: CalculateSwapQuoteParams,
274330
isUnwrap: boolean,

src/types.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -613,4 +613,20 @@ export interface HttpServiceConfig {
613613
meshHttpUri?: string;
614614
metaTxnRateLimiters?: MetaTransactionRateLimitConfig;
615615
}
616+
617+
export interface CalaculateMarketDepthParams {
618+
buyToken: TokenMetadata;
619+
sellToken: TokenMetadata;
620+
sellAmount: BigNumber;
621+
numSamples: number;
622+
sampleDistributionBase: number;
623+
excludedSources?: ERC20BridgeSource[];
624+
}
625+
626+
export interface BucketedPriceDepth {
627+
cumulative: BigNumber;
628+
price: BigNumber;
629+
bucket: number;
630+
bucketTotal: BigNumber;
631+
}
616632
// tslint:disable-line:max-file-line-count

0 commit comments

Comments
 (0)