diff --git a/packages/pipeline/src/data_sources/slippage/index.ts b/packages/pipeline/src/data_sources/slippage/index.ts index 57bb2cc4a2..65bb8fe046 100644 --- a/packages/pipeline/src/data_sources/slippage/index.ts +++ b/packages/pipeline/src/data_sources/slippage/index.ts @@ -1,22 +1,22 @@ -import { fetchAsync, logUtils } from '@0x/utils'; +import { fetchAsync } from '@0x/utils'; const EDPS_BASE_URL = 'https://ethereum-dex-prices-service.production.airswap.io'; -const PRICE_BASE_URL = 'https://min-api.cryptocompare.com/data/price?tsyms=USD' +const PRICE_BASE_URL = 'https://min-api.cryptocompare.com/data/price?tsyms=USD'; -export type EdpsResponse = EdpsWrapper[] +export type EdpsResponse = EdpsWrapper[]; export interface EdpsWrapper { - [key: string]: EdpsExchange + [key: string]: EdpsExchange; } export interface EdpsExchange { - exchangeName: string, - totalPrice: number, - tokenAmount: number, - tokenSymbol: string, - avgPrice: number, - timestamp: number, - error: string + exchangeName: string; + totalPrice: number; + tokenAmount: number; + tokenSymbol: string; + avgPrice: number; + timestamp: number; + error: string; } export interface PriceResponse { @@ -34,23 +34,22 @@ export class EdpsSource { const resp = await fetchAsync(edpsUrl); const respJson: EdpsResponse = await resp.json(); const allExchanges = new Map(); - for (let entry of respJson) { - for (let key in entry) { + for (const entry of respJson) { + for (const key of Object.keys(entry)) { allExchanges.set(key, entry[key]); } } return allExchanges; } -} -export class PriceSource { /** - * Call CryptoCompare Price API to get USD price of token. + * + * Call price API to fetch USD price for symbol. */ public async getUsdPriceAsync(symbol: string): Promise { - const priceUrl = `${PRICE_BASE_URL}&fsym=${symbol}` + const priceUrl = `${PRICE_BASE_URL}&fsym=${symbol}`; const resp = await fetchAsync(priceUrl); const respJson: PriceResponse = await resp.json(); return respJson.USD; } -} \ No newline at end of file +} diff --git a/packages/pipeline/src/entities/slippage.ts b/packages/pipeline/src/entities/slippage.ts index d046f382b5..bf330151a2 100644 --- a/packages/pipeline/src/entities/slippage.ts +++ b/packages/pipeline/src/entities/slippage.ts @@ -1,5 +1,7 @@ -import { BigNumber } from '@0x/utils'; import { Column, Entity, PrimaryColumn } from 'typeorm'; + +import { BigNumber } from '@0x/utils'; + import { bigNumberTransformer, numberToBigIntTransformer } from '../utils'; @Entity({ name: 'slippage', schema: 'raw' }) diff --git a/packages/pipeline/src/parsers/slippage/index.ts b/packages/pipeline/src/parsers/slippage/index.ts index e38fb4c6a7..fb3e3cd2fc 100644 --- a/packages/pipeline/src/parsers/slippage/index.ts +++ b/packages/pipeline/src/parsers/slippage/index.ts @@ -1,34 +1,34 @@ import { BigNumber } from '@0x/utils'; -import * as R from 'ramda'; import { EdpsExchange } from '../../data_sources/slippage'; import { Slippage } from '../../entities'; -import { symbol } from 'prop-types'; /** * Calculates slippage and returns Slippage entity. - * - * @param usdAmount - * @param exchange - * @param buyEdps - * @param sellEdps + * + * @param usdAmount Amount to buy/sell in USD. + * @param exchange Exchange where we are calculating slippage. + * @param buyEdps Ethereum DEX price service object for buy side. + * @param sellEdps Ethereum DEX price service object for sell side. + * */ - export function calculateSlippage(usdAmount: number, exchange: string, - buyEdps: Map, sellEdps: Map) { - const b = buyEdps.get(exchange); - const s = sellEdps.get(exchange); - const slippage = new Slippage(); - if (b && s && b.avgPrice && s.avgPrice) { - slippage.observedTimestamp = b.timestamp; - slippage.symbol = b.tokenSymbol; - slippage.exchange = exchange; - slippage.usdAmount = new BigNumber(usdAmount); - slippage.tokenAmount = new BigNumber(Number(b.tokenAmount)); // API returns a string - slippage.avgPriceInEthBuy = new BigNumber(b.avgPrice); - slippage.avgPriceInEthSell = new BigNumber(s.avgPrice); - slippage.slippage = new BigNumber((b.avgPrice - s.avgPrice) / b.avgPrice); - - } - return slippage; +export function calculateSlippage(usdAmount: number, + exchange: string, + buyEdps: Map, + sellEdps: Map): Slippage { + const b = buyEdps.get(exchange); + const s = sellEdps.get(exchange); + const slippage = new Slippage(); + if (b && s && b.avgPrice && s.avgPrice) { + slippage.observedTimestamp = b.timestamp; + slippage.symbol = b.tokenSymbol; + slippage.exchange = exchange; + slippage.usdAmount = new BigNumber(usdAmount); + slippage.tokenAmount = new BigNumber(Number(b.tokenAmount)); // API returns a string + slippage.avgPriceInEthBuy = new BigNumber(b.avgPrice); + slippage.avgPriceInEthSell = new BigNumber(s.avgPrice); + slippage.slippage = new BigNumber((b.avgPrice - s.avgPrice) / b.avgPrice); } + return slippage; +} diff --git a/packages/pipeline/src/scripts/pull_slippage.ts b/packages/pipeline/src/scripts/pull_slippage.ts index 9411e51389..cb82a5ac96 100644 --- a/packages/pipeline/src/scripts/pull_slippage.ts +++ b/packages/pipeline/src/scripts/pull_slippage.ts @@ -1,19 +1,20 @@ -import * as R from 'ramda'; -import { Connection, ConnectionOptions, createConnection, Repository, PromiseUtils, AdvancedConsoleLogger } from 'typeorm'; +import { Connection, ConnectionOptions, createConnection } from 'typeorm'; + import { logUtils } from '@0x/utils'; -import { EdpsExchange, EdpsSource, PriceResponse, PriceSource } from '../data_sources/slippage'; -import { handleError } from '../utils'; -import { string, number } from 'prop-types'; -import { calculateSlippage } from '../parsers/slippage'; + +import { EdpsSource} from '../data_sources/slippage'; import { Slippage } from '../entities'; import * as ormConfig from '../ormconfig'; +import { calculateSlippage } from '../parsers/slippage'; +import { handleError } from '../utils'; // Number of orders to save at once. const BATCH_SAVE_SIZE = 1000; // USD amounts for slippage depths +// tslint:disable-next-line:custom-no-magic-numbers const USD_AMOUNTS = [10, 100, 1000, 10000]; - + // TODO: fetch from database const TOKENS = ['BAT', 'DAI', 'FUN', 'MANA', 'OMG', 'REP', 'TUSD', 'ZRX', 'MKR', 'BNB', 'USDC']; @@ -21,30 +22,28 @@ let connection: Connection; (async () => { connection = await createConnection(ormConfig as ConnectionOptions); - const priceSource = new PriceSource(); const edpsSource = new EdpsSource(); logUtils.log('Fetching slippage records'); - let nestedSlippages: Slippage[][][] = await Promise.all(await TOKENS.map(async (symbol) => { - const usdPrice = await priceSource.getUsdPriceAsync(symbol); - return Promise.all(USD_AMOUNTS.map(async (usdAmount) => { + const nestedSlippages: Slippage[][][] = await Promise.all(TOKENS.map(async symbol => { + const usdPrice = await edpsSource.getUsdPriceAsync(symbol); + return Promise.all(USD_AMOUNTS.map(async usdAmount => { const amount = usdAmount / usdPrice; const buyEdps = await edpsSource.getEdpsAsync('buy', symbol, amount); const sellEdps = await edpsSource.getEdpsAsync('sell', symbol, amount); - const slippages = Array.from(buyEdps.keys()).map((exchange) => { + return Array.from(buyEdps.keys()).map(exchange => { const slippage: Slippage = calculateSlippage(usdAmount, exchange, buyEdps, sellEdps); return slippage; }); - return slippages; })); })); - let slippagesWithEmptyRecords = await nestedSlippages + const slippagesWithEmptyRecords = nestedSlippages .reduce((acc, val) => acc.concat(val)) .reduce((acc, val) => acc.concat(val)); - let slippages = slippagesWithEmptyRecords.filter((slippage) => slippage.observedTimestamp) + const slippages = slippagesWithEmptyRecords.filter(slippage => slippage.observedTimestamp); const SlippageRepository = connection.getRepository(Slippage); logUtils.log(`Saving ${slippages.length} records to database`); await SlippageRepository.save(slippages, { chunk: Math.ceil(slippages.length / BATCH_SAVE_SIZE) }); - logUtils.log("Done"); + logUtils.log('Done'); process.exit(0); -})().catch(handleError); \ No newline at end of file +})().catch(handleError); diff --git a/packages/pipeline/test/entities/slippage_test.ts b/packages/pipeline/test/entities/slippage_test.ts index 5f95e28c5e..0dc655adeb 100644 --- a/packages/pipeline/test/entities/slippage_test.ts +++ b/packages/pipeline/test/entities/slippage_test.ts @@ -1,6 +1,5 @@ import { BigNumber } from '@0x/utils'; import 'mocha'; -import * as R from 'ramda'; import 'reflect-metadata'; import { Slippage } from '../../src/entities'; @@ -19,7 +18,7 @@ const slippage = { tokenAmount: new BigNumber(25), avgPriceInEthBuy: new BigNumber(0.0022), avgPriceInEthSell: new BigNumber(0.002), - slippage: new BigNumber(0.01) + slippage: new BigNumber(0.01), }; // tslint:disable:custom-no-magic-numbers @@ -28,8 +27,8 @@ describe('Slippage entity', () => { const connection = await createDbConnectionOnceAsync(); const slippages = [slippage]; const slippageRepository = connection.getRepository(Slippage); - for (const slippage of slippages) { - await testSaveAndFindEntityAsync(slippageRepository, slippage); + for (const slippageRecord of slippages) { + await testSaveAndFindEntityAsync(slippageRepository, slippageRecord); } }); }); diff --git a/packages/pipeline/test/parsers/slippage/index_test.ts b/packages/pipeline/test/parsers/slippage/index_test.ts new file mode 100644 index 0000000000..44e2adcf73 --- /dev/null +++ b/packages/pipeline/test/parsers/slippage/index_test.ts @@ -0,0 +1,64 @@ +import { BigNumber } from '@0x/utils'; +import * as chai from 'chai'; +import 'mocha'; + +import { EdpsExchange } from '../../../src/data_sources/slippage'; +import { Slippage } from '../../../src/entities'; +import { calculateSlippage } from '../../../src/parsers/slippage'; +import { chaiSetup } from '../../utils/chai_setup'; + +chaiSetup.configure(); +const expect = chai.expect; + +// tslint:disable:custom-no-magic-numbers +describe('slippage', () => { + describe('calculateSlippage', () => { + it('calculates slippage correctly', () => { + + const exchange = 'Radar Relay'; + const ts = 1549961441473; + const symbol = 'DAI'; + const amount = 1000; + const buyPrice = 10; + const sellPrice = 9; + const expectedSlippage = 0.1; + + const buyEdps = new Map(); + const buyOrder: EdpsExchange = { + exchangeName: exchange, + totalPrice: buyPrice, + tokenAmount: amount, + tokenSymbol: symbol, + avgPrice: buyPrice / amount, + timestamp: ts, + error: '', + }; + buyEdps.set(exchange, buyOrder); + + const sellEdps = new Map(); + const sellOrder: EdpsExchange = { + exchangeName: exchange, + totalPrice: sellPrice, + tokenAmount: amount, + tokenSymbol: symbol, + avgPrice: sellPrice / amount, + timestamp: ts, + error: '', + }; + sellEdps.set(exchange, sellOrder); + const expected = new Slippage(); + expected.observedTimestamp = ts; + expected.symbol = symbol; + expected.exchange = exchange; + expected.usdAmount = new BigNumber(amount); + expected.tokenAmount = new BigNumber(amount); // API returns a string + expected.avgPriceInEthBuy = new BigNumber(buyPrice / amount); + expected.avgPriceInEthSell = new BigNumber(sellPrice / amount); + expected.slippage = new BigNumber(0.1); + + const actual = calculateSlippage(amount, exchange, buyEdps, sellEdps); + const actualSlippage: BigNumber = actual.slippage ? actual.slippage : new BigNumber(0); + expect(actualSlippage.toNumber()).to.be.closeTo(expectedSlippage, 0.0001); + }); + }); +});