Added remaining tests + fixed linting issues

This commit is contained in:
askeluv
2019-02-12 19:34:05 +08:00
parent d7825dd7db
commit 82dffe9d0e
6 changed files with 127 additions and 64 deletions

View File

@@ -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;
}
}
}

View File

@@ -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' })

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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);
}
});
});

View 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);
});
});
});