Added remaining tests + fixed linting issues
This commit is contained in:
@@ -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<string, EdpsExchange>();
|
||||
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<number> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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' })
|
||||
|
@@ -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<string, EdpsExchange>, sellEdps: Map<string, EdpsExchange>) {
|
||||
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<string, EdpsExchange>,
|
||||
sellEdps: Map<string, EdpsExchange>): 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;
|
||||
}
|
||||
|
@@ -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);
|
||||
})().catch(handleError);
|
||||
|
@@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
64
packages/pipeline/test/parsers/slippage/index_test.ts
Normal file
64
packages/pipeline/test/parsers/slippage/index_test.ts
Normal file
@@ -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<string, EdpsExchange>();
|
||||
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<string, EdpsExchange>();
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
Reference in New Issue
Block a user