added testing to market utils for market sell

This commit is contained in:
David Sun
2019-06-27 10:23:25 -07:00
parent 6691f490bc
commit 3e2dbfc83c
4 changed files with 200 additions and 9 deletions

View File

@@ -73,6 +73,8 @@ export {
TransferType,
FindFeeOrdersThatCoverFeesForTargetOrdersOpts,
FindOrdersThatCoverMakerAssetFillAmountOpts,
FindOrdersThatCoverTakerAssetFillAmountOpts,
FeeOrdersAndRemainingFeeAmount,
OrdersAndRemainingFillAmount,
OrdersAndRemainingTakerFillAmount,
OrdersAndRemainingMakerFillAmount,
} from './types';

View File

@@ -5,12 +5,16 @@ import * as _ from 'lodash';
import { assert } from './assert';
import { constants } from './constants';
import {
orderCalculationUtils,
} from './order_calculation_utils';
import {
FeeOrdersAndRemainingFeeAmount,
FindFeeOrdersThatCoverFeesForTargetOrdersOpts,
FindOrdersThatCoverMakerAssetFillAmountOpts,
FindOrdersThatCoverTakerAssetFillAmountOpts,
OrdersAndRemainingFillAmount,
OrdersAndRemainingMakerFillAmount,
OrdersAndRemainingTakerFillAmount,
} from './types';
export const marketUtils = {
@@ -18,10 +22,61 @@ export const marketUtils = {
orders: T[],
takerAssetFillAmount: BigNumber,
opts?: FindOrdersThatCoverTakerAssetFillAmountOpts,
): OrdersAndRemainingFillAmount<T> {
): OrdersAndRemainingTakerFillAmount<T> {
assert.doesConformToSchema('orders', orders, schemas.ordersSchema);
assert.isValidBaseUnitAmount('takerAssetFillAmount', takerAssetFillAmount);
// try to get remainingFillableMakerAssetAmounts from opts, if it's not there, use makerAssetAmount values from orders
const remainingFillableTakerAssetAmounts = _.get(
opts,
'remainingFillableTakerAssetAmounts',
_.map(orders, order => order.takerAssetAmount),
) as BigNumber[];
_.forEach(remainingFillableTakerAssetAmounts, (amount, index) =>
assert.isValidBaseUnitAmount(`remainingFillableTakerAssetAmount[${index}]`, amount),
);
assert.assert(
orders.length === remainingFillableTakerAssetAmounts.length,
'Expected orders.length to equal opts.remainingFillableMakerAssetAmounts.length',
);
// try to get slippageBufferAmount from opts, if it's not there, default to 0
const slippageBufferAmount = _.get(opts, 'slippageBufferAmount', constants.ZERO_AMOUNT) as BigNumber;
assert.isValidBaseUnitAmount('opts.slippageBufferAmount', slippageBufferAmount);
// calculate total amount of makerAsset needed to be filled
const totalFillAmount = takerAssetFillAmount.plus(slippageBufferAmount);
// iterate through the orders input from left to right until we have enough makerAsset to fill totalFillAmount
const result = _.reduce(
orders,
({ resultOrders, remainingFillAmount, ordersRemainingFillableTakerAssetAmounts }, order, index) => {
if (remainingFillAmount.isLessThanOrEqualTo(constants.ZERO_AMOUNT)) {
return {
resultOrders,
remainingFillAmount: constants.ZERO_AMOUNT,
ordersRemainingFillableTakerAssetAmounts,
};
} else {
const takerAssetAmountAvailable = remainingFillableTakerAssetAmounts[index];
const shouldIncludeOrder = takerAssetAmountAvailable.gt(constants.ZERO_AMOUNT);
// if there is no makerAssetAmountAvailable do not append order to resultOrders
// if we have exceeded the total amount we want to fill set remainingFillAmount to 0
return {
resultOrders: shouldIncludeOrder ? _.concat(resultOrders, order) : resultOrders,
ordersRemainingFillableTakerAssetAmounts: shouldIncludeOrder
? _.concat(ordersRemainingFillableTakerAssetAmounts, takerAssetAmountAvailable)
: ordersRemainingFillableTakerAssetAmounts,
remainingFillAmount: BigNumber.max(
constants.ZERO_AMOUNT,
remainingFillAmount.minus(takerAssetAmountAvailable),
),
};
}
},
{
resultOrders: [] as T[],
remainingFillAmount: totalFillAmount,
ordersRemainingFillableTakerAssetAmounts: [] as BigNumber[],
},
);
return result;
},
/**
* Takes an array of orders and returns a subset of those orders that has enough makerAssetAmount
@@ -37,7 +92,7 @@ export const marketUtils = {
orders: T[],
makerAssetFillAmount: BigNumber,
opts?: FindOrdersThatCoverMakerAssetFillAmountOpts,
): OrdersAndRemainingFillAmount<T> {
): OrdersAndRemainingMakerFillAmount<T> {
assert.doesConformToSchema('orders', orders, schemas.ordersSchema);
assert.isValidBaseUnitAmount('makerAssetFillAmount', makerAssetFillAmount);
// try to get remainingFillableMakerAssetAmounts from opts, if it's not there, use makerAssetAmount values from orders

View File

@@ -34,19 +34,18 @@ export interface CreateOrderOpts {
*/
export interface FindOrdersThatCoverMakerAssetFillAmountOpts {
remainingFillableMakerAssetAmounts?: BigNumber[];
remainingFillableTakerAssetAmounts?: BigNumber[];
slippageBufferAmount?: BigNumber;
}
/**
* remainingFillableTakerAssetAmount: An array of BigNumbers corresponding to the `orders` parameter.
* remainingFillableMakerAssetAmount: An array of BigNumbers corresponding to the `orders` parameter.
* You can use `OrderStateUtils` `@0x/order-utils` to perform blockchain lookups for these values.
* Defaults to `makerAssetAmount` values from the orders param.
* slippageBufferAmount: An additional amount of makerAsset to be covered by the result in case of trade collisions or partial fills.
* Defaults to 0
*/
export interface FindOrdersThatCoverTakerAssetFillAmountOpts {
remainingFillableMakerAssetAmounts?: BigNumber[];
remainingFillableTakerAssetAmounts?: BigNumber[];
slippageBufferAmount?: BigNumber;
}
@@ -72,8 +71,14 @@ export interface FeeOrdersAndRemainingFeeAmount<T> {
remainingFeeAmount: BigNumber;
}
export interface OrdersAndRemainingFillAmount<T> {
export interface OrdersAndRemainingMakerFillAmount<T> {
resultOrders: T[];
ordersRemainingFillableMakerAssetAmounts: BigNumber[];
remainingFillAmount: BigNumber;
}
export interface OrdersAndRemainingTakerFillAmount<T> {
resultOrders: T[];
ordersRemainingFillableTakerAssetAmounts: BigNumber[];
remainingFillAmount: BigNumber;
}

View File

@@ -13,6 +13,135 @@ const expect = chai.expect;
// tslint:disable: no-unused-expression
describe('marketUtils', () => {
describe('#findOrdersThatCoverTakerAssetFillAmount', () => {
describe('no orders', () => {
it('returns empty and unchanged remainingFillAmount', async () => {
const fillAmount = new BigNumber(10);
const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverTakerAssetFillAmount(
[],
fillAmount,
);
expect(resultOrders).to.be.empty;
expect(remainingFillAmount).to.be.bignumber.equal(fillAmount);
});
});
describe('orders are completely fillable', () => {
// generate three signed orders each with 10 units of makerAsset, 30 total
const takerAssetAmount = new BigNumber(10);
const inputOrders = testOrderFactory.generateTestSignedOrders(
{
takerAssetAmount,
},
3,
);
it('returns input orders and zero remainingFillAmount when input exactly matches requested fill amount', async () => {
// try to fill 20 units of makerAsset
// include 10 units of slippageBufferAmount
const fillAmount = new BigNumber(20);
const slippageBufferAmount = new BigNumber(10);
const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverTakerAssetFillAmount(
inputOrders,
fillAmount,
{
slippageBufferAmount,
},
);
expect(resultOrders).to.be.deep.equal(inputOrders);
expect(remainingFillAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
});
it('returns input orders and zero remainingFillAmount when input has more than requested fill amount', async () => {
// try to fill 15 units of makerAsset
// include 10 units of slippageBufferAmount
const fillAmount = new BigNumber(15);
const slippageBufferAmount = new BigNumber(10);
const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverTakerAssetFillAmount(
inputOrders,
fillAmount,
{
slippageBufferAmount,
},
);
expect(resultOrders).to.be.deep.equal(inputOrders);
expect(remainingFillAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
});
it('returns input orders and non-zero remainingFillAmount when input has less than requested fill amount', async () => {
// try to fill 30 units of makerAsset
// include 5 units of slippageBufferAmount
const fillAmount = new BigNumber(30);
const slippageBufferAmount = new BigNumber(5);
const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverTakerAssetFillAmount(
inputOrders,
fillAmount,
{
slippageBufferAmount,
},
);
expect(resultOrders).to.be.deep.equal(inputOrders);
expect(remainingFillAmount).to.be.bignumber.equal(new BigNumber(5));
});
it('returns first order and zero remainingFillAmount when requested fill amount is exactly covered by the first order', async () => {
// try to fill 10 units of makerAsset
const fillAmount = new BigNumber(10);
const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverTakerAssetFillAmount(
inputOrders,
fillAmount,
);
expect(resultOrders).to.be.deep.equal([inputOrders[0]]);
expect(remainingFillAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
});
it('returns first two orders and zero remainingFillAmount when requested fill amount is over covered by the first two order', async () => {
// try to fill 15 units of makerAsset
const fillAmount = new BigNumber(15);
const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverTakerAssetFillAmount(
inputOrders,
fillAmount,
);
expect(resultOrders).to.be.deep.equal([inputOrders[0], inputOrders[1]]);
expect(remainingFillAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
});
});
describe('orders are partially fillable', () => {
// generate three signed orders each with 10 units of makerAsset, 30 total
const takerAssetAmount = new BigNumber(10);
const inputOrders = testOrderFactory.generateTestSignedOrders(
{
takerAssetAmount,
},
3,
);
// generate remainingFillableMakerAssetAmounts that cover different partial fill scenarios
// 1. order is completely filled already
// 2. order is partially fillable
// 3. order is completely fillable
const remainingFillableTakerAssetAmounts = [constants.ZERO_AMOUNT, new BigNumber(5), takerAssetAmount];
it('returns last two orders and non-zero remainingFillAmount when trying to fill original takerAssetAmounts', async () => {
// try to fill 30 units of takerAsset
const fillAmount = new BigNumber(30);
const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverTakerAssetFillAmount(
inputOrders,
fillAmount,
{
remainingFillableTakerAssetAmounts,
},
);
expect(resultOrders).to.be.deep.equal([inputOrders[1], inputOrders[2]]);
expect(remainingFillAmount).to.be.bignumber.equal(new BigNumber(15));
});
it('returns last two orders and zero remainingFillAmount when trying to fill exactly takerAssetAmounts remaining', async () => {
// try to fill 15 units of takerAsset
const fillAmount = new BigNumber(15);
const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverTakerAssetFillAmount(
inputOrders,
fillAmount,
{
remainingFillableTakerAssetAmounts,
},
);
expect(resultOrders).to.be.deep.equal([inputOrders[1], inputOrders[2]]);
expect(remainingFillAmount).to.be.bignumber.equal(new BigNumber(0));
});
});
});
describe('#findOrdersThatCoverMakerAssetFillAmount', () => {
describe('no orders', () => {
it('returns empty and unchanged remainingFillAmount', async () => {