Merge pull request #953 from 0xProject/feature/order-utils/order-sorting-utils
[order-utils] Add rate and sorting utilities
This commit is contained in:
@@ -18,6 +18,10 @@
|
||||
},
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
},
|
||||
{
|
||||
"note": "Added rateUtils and sortingUtils",
|
||||
"pr": 953
|
||||
}
|
||||
],
|
||||
"timestamp": 1534210131
|
||||
|
||||
@@ -25,3 +25,5 @@ export { EIP712Utils } from './eip712_utils';
|
||||
export { OrderValidationUtils } from './order_validation_utils';
|
||||
export { ExchangeTransferSimulator } from './exchange_transfer_simulator';
|
||||
export { marketUtils } from './market_utils';
|
||||
export { rateUtils } from './rate_utils';
|
||||
export { sortingUtils } from './sorting_utils';
|
||||
|
||||
48
packages/order-utils/src/rate_utils.ts
Normal file
48
packages/order-utils/src/rate_utils.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { schemas } from '@0xproject/json-schemas';
|
||||
import { Order } from '@0xproject/types';
|
||||
import { BigNumber } from '@0xproject/utils';
|
||||
|
||||
import { assert } from './assert';
|
||||
import { constants } from './constants';
|
||||
|
||||
export const rateUtils = {
|
||||
/**
|
||||
* Takes an order and calculates the fee adjusted rate (takerAsset/makerAsset) by calculating how much takerAsset
|
||||
* is required to cover the fees (feeRate * takerFee), adding the takerAssetAmount and dividing by makerAssetAmount
|
||||
* @param order An object that conforms to the order interface
|
||||
* @param feeRate The market rate of ZRX denominated in takerAssetAmount
|
||||
* (ex. feeRate is 0.1 takerAsset/ZRX if it takes 1 unit of takerAsset to buy 10 ZRX)
|
||||
* Defaults to 0
|
||||
* @return The rate (takerAsset/makerAsset) of the order adjusted for fees
|
||||
*/
|
||||
getFeeAdjustedRateOfOrder(order: Order, feeRate: BigNumber = constants.ZERO_AMOUNT): BigNumber {
|
||||
assert.doesConformToSchema('order', order, schemas.orderSchema);
|
||||
assert.isBigNumber('feeRate', feeRate);
|
||||
assert.assert(
|
||||
feeRate.gte(constants.ZERO_AMOUNT),
|
||||
`Expected feeRate: ${feeRate} to be greater than or equal to 0`,
|
||||
);
|
||||
const takerAssetAmountNeededToPayForFees = order.takerFee.mul(feeRate);
|
||||
const totalTakerAssetAmount = takerAssetAmountNeededToPayForFees.plus(order.takerAssetAmount);
|
||||
const rate = totalTakerAssetAmount.div(order.makerAssetAmount);
|
||||
return rate;
|
||||
},
|
||||
/**
|
||||
* Takes a fee order (makerAssetData corresponds to ZRX and takerAssetData corresponds to WETH) and calculates
|
||||
* the fee adjusted rate (WETH/ZRX) by dividing the takerAssetAmount by the makerAmount minus the takerFee
|
||||
* @param feeOrder An object that conforms to the order interface
|
||||
* @return The rate (WETH/ZRX) of the fee order adjusted for fees
|
||||
*/
|
||||
getFeeAdjustedRateOfFeeOrder(feeOrder: Order): BigNumber {
|
||||
assert.doesConformToSchema('feeOrder', feeOrder, schemas.orderSchema);
|
||||
const zrxAmountAfterFees = feeOrder.makerAssetAmount.sub(feeOrder.takerFee);
|
||||
assert.assert(
|
||||
zrxAmountAfterFees.greaterThan(constants.ZERO_AMOUNT),
|
||||
`Expected takerFee: ${JSON.stringify(feeOrder.takerFee)} to be less than makerAssetAmount: ${JSON.stringify(
|
||||
feeOrder.makerAssetAmount,
|
||||
)}`,
|
||||
);
|
||||
const rate = feeOrder.takerAssetAmount.div(zrxAmountAfterFees);
|
||||
return rate;
|
||||
},
|
||||
};
|
||||
54
packages/order-utils/src/sorting_utils.ts
Normal file
54
packages/order-utils/src/sorting_utils.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { schemas } from '@0xproject/json-schemas';
|
||||
import { Order } from '@0xproject/types';
|
||||
import { BigNumber } from '@0xproject/utils';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { assert } from './assert';
|
||||
import { constants } from './constants';
|
||||
import { rateUtils } from './rate_utils';
|
||||
|
||||
export const sortingUtils = {
|
||||
/**
|
||||
* Takes an array of orders and sorts them by takerAsset/makerAsset rate in ascending order (best rate first).
|
||||
* Adjusts the rate of each order according to the feeRate and takerFee for that order.
|
||||
* @param orders An array of objects that extend the Order interface. All orders should specify ZRX as
|
||||
* the makerAsset and WETH as the takerAsset.
|
||||
* @param feeRate The market rate of ZRX denominated in takerAssetAmount
|
||||
* (ex. feeRate is 0.1 takerAsset/ZRX if it takes 1 unit of takerAsset to buy 10 ZRX)
|
||||
* Defaults to 0
|
||||
* @return The input orders sorted by rate in ascending order
|
||||
*/
|
||||
sortOrdersByFeeAdjustedRate<T extends Order>(orders: T[], feeRate: BigNumber = constants.ZERO_AMOUNT): T[] {
|
||||
assert.doesConformToSchema('orders', orders, schemas.ordersSchema);
|
||||
assert.isBigNumber('feeRate', feeRate);
|
||||
const rateCalculator = (order: Order) => rateUtils.getFeeAdjustedRateOfOrder(order, feeRate);
|
||||
const sortedOrders = sortOrders(orders, rateCalculator);
|
||||
return sortedOrders;
|
||||
},
|
||||
/**
|
||||
* Takes an array of fee orders (makerAssetData corresponds to ZRX and takerAssetData corresponds to WETH)
|
||||
* and sorts them by rate in ascending order (best rate first). Adjusts the rate according to the takerFee.
|
||||
* @param feeOrders An array of objects that extend the Order interface. All orders should specify ZRX as
|
||||
* the makerAsset and WETH as the takerAsset.
|
||||
* @return The input orders sorted by rate in ascending order
|
||||
*/
|
||||
sortFeeOrdersByFeeAdjustedRate(feeOrders: Order[]): Order[] {
|
||||
assert.doesConformToSchema('feeOrders', feeOrders, schemas.ordersSchema);
|
||||
const rateCalculator = rateUtils.getFeeAdjustedRateOfFeeOrder.bind(rateUtils);
|
||||
const sortedOrders = sortOrders(feeOrders, rateCalculator);
|
||||
return sortedOrders;
|
||||
},
|
||||
};
|
||||
|
||||
type RateCalculator = (order: Order) => BigNumber;
|
||||
|
||||
// takes an array of orders, copies them, and sorts the copy based on the rate definition provided by rateCalculator
|
||||
function sortOrders<T extends Order>(orders: T[], rateCalculator: RateCalculator): T[] {
|
||||
const copiedOrders = _.cloneDeep(orders);
|
||||
copiedOrders.sort((firstOrder, secondOrder) => {
|
||||
const firstOrderRate = rateCalculator(firstOrder);
|
||||
const secondOrderRate = rateCalculator(secondOrder);
|
||||
return firstOrderRate.comparedTo(secondOrderRate);
|
||||
});
|
||||
return copiedOrders;
|
||||
}
|
||||
55
packages/order-utils/test/rate_utils_test.ts
Normal file
55
packages/order-utils/test/rate_utils_test.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { BigNumber } from '@0xproject/utils';
|
||||
import * as chai from 'chai';
|
||||
import 'mocha';
|
||||
|
||||
import { rateUtils } from '../src';
|
||||
|
||||
import { chaiSetup } from './utils/chai_setup';
|
||||
import { testOrderFactory } from './utils/test_order_factory';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
|
||||
describe('rateUtils', () => {
|
||||
const testOrder = testOrderFactory.generateTestSignedOrder({
|
||||
makerAssetAmount: new BigNumber(100),
|
||||
takerAssetAmount: new BigNumber(100),
|
||||
takerFee: new BigNumber(20),
|
||||
});
|
||||
describe('#getFeeAdjustedRateOfOrder', () => {
|
||||
it('throws when feeRate is less than zero', async () => {
|
||||
const feeRate = new BigNumber(-1);
|
||||
expect(() => rateUtils.getFeeAdjustedRateOfOrder(testOrder, feeRate)).to.throw(
|
||||
'Expected feeRate: -1 to be greater than or equal to 0',
|
||||
);
|
||||
});
|
||||
it('correctly calculates fee adjusted rate when feeRate is provided', async () => {
|
||||
const feeRate = new BigNumber(2); // ZRX costs 2 units of takerAsset per 1 unit of ZRX
|
||||
const feeAdjustedRate = rateUtils.getFeeAdjustedRateOfOrder(testOrder, feeRate);
|
||||
// the order actually takes 100 + (2 * 20) takerAsset units to fill 100 units of makerAsset
|
||||
expect(feeAdjustedRate).to.bignumber.equal(new BigNumber(1.4));
|
||||
});
|
||||
it('correctly calculates fee adjusted rate when no feeRate is provided', async () => {
|
||||
const feeAdjustedRate = rateUtils.getFeeAdjustedRateOfOrder(testOrder);
|
||||
// because no feeRate was provided we just assume 0 fees
|
||||
// the order actually takes 100 takerAsset units to fill 100 units of makerAsset
|
||||
expect(feeAdjustedRate).to.bignumber.equal(new BigNumber(1));
|
||||
});
|
||||
});
|
||||
describe('#getFeeAdjustedRateOfFeeOrder', () => {
|
||||
it('throws when takerFee exceeds makerAssetAmount', async () => {
|
||||
const badOrder = testOrderFactory.generateTestSignedOrder({
|
||||
makerAssetAmount: new BigNumber(100),
|
||||
takerFee: new BigNumber(101),
|
||||
});
|
||||
expect(() => rateUtils.getFeeAdjustedRateOfFeeOrder(badOrder)).to.throw(
|
||||
'Expected takerFee: "101" to be less than makerAssetAmount: "100"',
|
||||
);
|
||||
});
|
||||
it('correctly calculates fee adjusted rate', async () => {
|
||||
const feeAdjustedRate = rateUtils.getFeeAdjustedRateOfFeeOrder(testOrder);
|
||||
// the order actually takes 100 takerAsset units to fill (100 - 20) units of makerAsset
|
||||
expect(feeAdjustedRate).to.bignumber.equal(new BigNumber(1.25));
|
||||
});
|
||||
});
|
||||
});
|
||||
67
packages/order-utils/test/sorting_utils_test.ts
Normal file
67
packages/order-utils/test/sorting_utils_test.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { BigNumber } from '@0xproject/utils';
|
||||
import * as chai from 'chai';
|
||||
import 'mocha';
|
||||
|
||||
import { sortingUtils } from '../src';
|
||||
|
||||
import { chaiSetup } from './utils/chai_setup';
|
||||
import { testOrderFactory } from './utils/test_order_factory';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
|
||||
describe('sortingUtils', () => {
|
||||
describe('#sortOrdersByFeeAdjustedRate', () => {
|
||||
const feeRate = new BigNumber(1); // ZRX costs 1 unit of takerAsset per 1 unit of ZRX
|
||||
// rate: 2 takerAsset / makerAsset
|
||||
const testOrder1 = testOrderFactory.generateTestSignedOrder({
|
||||
makerAssetAmount: new BigNumber(100),
|
||||
takerAssetAmount: new BigNumber(200),
|
||||
});
|
||||
// rate: 1 takerAsset / makerAsset
|
||||
const testOrder2 = testOrderFactory.generateTestSignedOrder({
|
||||
makerAssetAmount: new BigNumber(100),
|
||||
takerAssetAmount: new BigNumber(100),
|
||||
});
|
||||
// rate: 2.5 takerAsset / makerAsset
|
||||
const testOrder3 = testOrderFactory.generateTestSignedOrder({
|
||||
makerAssetAmount: new BigNumber(100),
|
||||
takerAssetAmount: new BigNumber(200),
|
||||
takerFee: new BigNumber(50),
|
||||
});
|
||||
it('correctly sorts by fee adjusted rate when feeRate is Provided', async () => {
|
||||
const orders = [testOrder1, testOrder2, testOrder3];
|
||||
const sortedOrders = sortingUtils.sortOrdersByFeeAdjustedRate(orders, feeRate);
|
||||
expect(sortedOrders).to.deep.equal([testOrder2, testOrder1, testOrder3]);
|
||||
});
|
||||
it('correctly sorts by fee adjusted rate when no feeRate is Provided', async () => {
|
||||
const orders = [testOrder1, testOrder2, testOrder3];
|
||||
const sortedOrders = sortingUtils.sortOrdersByFeeAdjustedRate(orders);
|
||||
expect(sortedOrders).to.deep.equal([testOrder2, testOrder1, testOrder3]);
|
||||
});
|
||||
});
|
||||
describe('#sortFeeOrdersByFeeAdjustedRate', () => {
|
||||
// rate: 200 takerAsset / makerAsset
|
||||
const testOrder1 = testOrderFactory.generateTestSignedOrder({
|
||||
makerAssetAmount: new BigNumber(100),
|
||||
takerAssetAmount: new BigNumber(200),
|
||||
takerFee: new BigNumber(99),
|
||||
});
|
||||
// rate: 1 takerAsset / makerAsset
|
||||
const testOrder2 = testOrderFactory.generateTestSignedOrder({
|
||||
makerAssetAmount: new BigNumber(100),
|
||||
takerAssetAmount: new BigNumber(100),
|
||||
});
|
||||
// rate: 4 takerAsset / makerAsset
|
||||
const testOrder3 = testOrderFactory.generateTestSignedOrder({
|
||||
makerAssetAmount: new BigNumber(100),
|
||||
takerAssetAmount: new BigNumber(200),
|
||||
takerFee: new BigNumber(50),
|
||||
});
|
||||
it('correctly sorts by fee adjusted rate', async () => {
|
||||
const orders = [testOrder1, testOrder2, testOrder3];
|
||||
const sortedOrders = sortingUtils.sortFeeOrdersByFeeAdjustedRate(orders);
|
||||
expect(sortedOrders).to.deep.equal([testOrder2, testOrder3, testOrder1]);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user