Calculate min and max rates in buy quote

This commit is contained in:
fragosti 2018-09-19 15:58:30 +02:00
parent 93f7e33f6a
commit 60e2dfdbda
3 changed files with 70 additions and 33 deletions

View File

@ -1,5 +1,6 @@
import { marketUtils } from '@0xproject/order-utils'; import { marketUtils } from '@0xproject/order-utils';
import { BigNumber } from '@0xproject/utils'; import { BigNumber } from '@0xproject/utils';
import * as _ from 'lodash';
import { constants } from '../constants'; import { constants } from '../constants';
import { AssetBuyerError, AssetBuyerOrdersAndFillableAmounts, BuyQuote } from '../types'; import { AssetBuyerError, AssetBuyerOrdersAndFillableAmounts, BuyQuote } from '../types';
@ -18,41 +19,64 @@ export const buyQuoteCalculator = {
remainingFillableFeeAmounts, remainingFillableFeeAmounts,
} = ordersAndFillableAmounts; } = ordersAndFillableAmounts;
const slippageBufferAmount = assetBuyAmount.mul(slippagePercentage).round(); const slippageBufferAmount = assetBuyAmount.mul(slippagePercentage).round();
const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount( const {
orders, resultOrders,
assetBuyAmount, remainingFillAmount,
{ ordersRemainingFillableMakerAssetAmounts,
} = marketUtils.findOrdersThatCoverMakerAssetFillAmount(orders, assetBuyAmount, {
remainingFillableMakerAssetAmounts, remainingFillableMakerAssetAmounts,
slippageBufferAmount, slippageBufferAmount,
}, });
);
if (remainingFillAmount.gt(constants.ZERO_AMOUNT)) { if (remainingFillAmount.gt(constants.ZERO_AMOUNT)) {
throw new Error(AssetBuyerError.InsufficientAssetLiquidity); throw new Error(AssetBuyerError.InsufficientAssetLiquidity);
} }
// TODO: optimization // TODO: optimization
// update this logic to find the minimum amount of feeOrders to cover the worst case as opposed to // update this logic to find the minimum amount of feeOrders to cover the worst case as opposed to
// finding order that cover all fees, this will help with estimating ETH and minimizing gas usage // finding order that cover all fees, this will help with estimating ETH and minimizing gas usage
const { resultFeeOrders, remainingFeeAmount } = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders( const {
resultOrders, resultFeeOrders,
feeOrders, remainingFeeAmount,
{ feeOrdersRemainingFillableMakerAssetAmounts,
} = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders(resultOrders, feeOrders, {
remainingFillableMakerAssetAmounts, remainingFillableMakerAssetAmounts,
remainingFillableFeeAmounts, remainingFillableFeeAmounts,
}, });
);
if (remainingFeeAmount.gt(constants.ZERO_AMOUNT)) { if (remainingFeeAmount.gt(constants.ZERO_AMOUNT)) {
throw new Error(AssetBuyerError.InsufficientZrxLiquidity); throw new Error(AssetBuyerError.InsufficientZrxLiquidity);
} }
const assetData = orders[0].makerAssetData; const assetData = orders[0].makerAssetData;
// TODO: critical
// calculate minRate and maxRate by calculating min and max eth usage and then dividing into // calculate minRate and maxRate by calculating min and max eth usage and then dividing into
// assetBuyAmount to get assetData / WETH, needs to take into account feePercentage as well // assetBuyAmount to get assetData / WETH, needs to take into account feePercentage as well
// minEthAmount = (sum(takerAssetAmount[i]) until sum(makerAssetAmount[i]) >= assetBuyAmount ) * (1 + feePercentage)
// maxEthAmount = (sum(takerAssetAmount[i]) until i == orders.length) * (1 + feePercentage)
const allOrders = _.concat(resultOrders, resultFeeOrders);
const allRemainingAmounts = _.concat(
ordersRemainingFillableMakerAssetAmounts,
feeOrdersRemainingFillableMakerAssetAmounts,
);
let minEthAmount = constants.ZERO_AMOUNT;
let maxEthAmount = constants.ZERO_AMOUNT;
let cumulativeMakerAmount = constants.ZERO_AMOUNT;
_.forEach(allOrders, (order, index) => {
const remainingFillableMakerAssetAmount = allRemainingAmounts[index];
const orderRate = order.takerAssetAmount.div(order.makerAssetAmount);
const claimableTakerAssetAmount = orderRate.mul(remainingFillableMakerAssetAmount);
// taker asset is always assumed to be WETH
maxEthAmount = maxEthAmount.plus(claimableTakerAssetAmount);
if (cumulativeMakerAmount.lessThan(assetBuyAmount)) {
minEthAmount = minEthAmount.plus(claimableTakerAssetAmount);
}
cumulativeMakerAmount = cumulativeMakerAmount.plus(remainingFillableMakerAssetAmount);
});
const feeAdjustedMinRate = minEthAmount.mul(feePercentage + 1).div(assetBuyAmount);
const feeAdjustedMaxRate = minEthAmount.mul(feePercentage + 1).div(assetBuyAmount);
return { return {
assetData, assetData,
orders: resultOrders, orders: resultOrders,
feeOrders: resultFeeOrders, feeOrders: resultFeeOrders,
minRate: constants.ZERO_AMOUNT, minRate: feeAdjustedMinRate,
maxRate: constants.ZERO_AMOUNT, maxRate: feeAdjustedMaxRate,
assetBuyAmount, assetBuyAmount,
feePercentage, feePercentage,
}; };

View File

@ -51,17 +51,23 @@ export const marketUtils = {
// iterate through the orders input from left to right until we have enough makerAsset to fill totalFillAmount // iterate through the orders input from left to right until we have enough makerAsset to fill totalFillAmount
const result = _.reduce( const result = _.reduce(
orders, orders,
({ resultOrders, remainingFillAmount }, order, index) => { ({ resultOrders, remainingFillAmount, ordersRemainingFillableMakerAssetAmounts }, order, index) => {
if (remainingFillAmount.lessThanOrEqualTo(constants.ZERO_AMOUNT)) { if (remainingFillAmount.lessThanOrEqualTo(constants.ZERO_AMOUNT)) {
return { resultOrders, remainingFillAmount: constants.ZERO_AMOUNT }; return {
resultOrders,
remainingFillAmount: constants.ZERO_AMOUNT,
ordersRemainingFillableMakerAssetAmounts,
};
} else { } else {
const makerAssetAmountAvailable = remainingFillableMakerAssetAmounts[index]; const makerAssetAmountAvailable = remainingFillableMakerAssetAmounts[index];
const shouldIncludeOrder = makerAssetAmountAvailable.gt(constants.ZERO_AMOUNT);
// if there is no makerAssetAmountAvailable do not append order to resultOrders // 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 // if we have exceeded the total amount we want to fill set remainingFillAmount to 0
return { return {
resultOrders: makerAssetAmountAvailable.gt(constants.ZERO_AMOUNT) resultOrders: shouldIncludeOrder ? _.concat(resultOrders, order) : resultOrders,
? _.concat(resultOrders, order) ordersRemainingFillableMakerAssetAmounts: shouldIncludeOrder
: resultOrders, ? _.concat(ordersRemainingFillableMakerAssetAmounts, makerAssetAmountAvailable)
: ordersRemainingFillableMakerAssetAmounts,
remainingFillAmount: BigNumber.max( remainingFillAmount: BigNumber.max(
constants.ZERO_AMOUNT, constants.ZERO_AMOUNT,
remainingFillAmount.minus(makerAssetAmountAvailable), remainingFillAmount.minus(makerAssetAmountAvailable),
@ -69,7 +75,11 @@ export const marketUtils = {
}; };
} }
}, },
{ resultOrders: [] as T[], remainingFillAmount: totalFillAmount }, {
resultOrders: [] as T[],
remainingFillAmount: totalFillAmount,
ordersRemainingFillableMakerAssetAmounts: [] as BigNumber[],
},
); );
return result; return result;
}, },
@ -133,17 +143,18 @@ export const marketUtils = {
}, },
constants.ZERO_AMOUNT, constants.ZERO_AMOUNT,
); );
const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount( const {
feeOrders, resultOrders,
totalFeeAmount, remainingFillAmount,
{ ordersRemainingFillableMakerAssetAmounts,
} = marketUtils.findOrdersThatCoverMakerAssetFillAmount(feeOrders, totalFeeAmount, {
remainingFillableMakerAssetAmounts: remainingFillableFeeAmounts, remainingFillableMakerAssetAmounts: remainingFillableFeeAmounts,
slippageBufferAmount, slippageBufferAmount,
}, });
);
return { return {
resultFeeOrders: resultOrders, resultFeeOrders: resultOrders,
remainingFeeAmount: remainingFillAmount, remainingFeeAmount: remainingFillAmount,
feeOrdersRemainingFillableMakerAssetAmounts: ordersRemainingFillableMakerAssetAmounts,
}; };
// TODO: add more orders here to cover rounding // TODO: add more orders here to cover rounding
// https://github.com/0xProject/0x-protocol-specification/blob/master/v2/forwarding-contract-specification.md#over-buying-zrx // https://github.com/0xProject/0x-protocol-specification/blob/master/v2/forwarding-contract-specification.md#over-buying-zrx

View File

@ -72,10 +72,12 @@ export interface FindFeeOrdersThatCoverFeesForTargetOrdersOpts {
export interface FeeOrdersAndRemainingFeeAmount<T> { export interface FeeOrdersAndRemainingFeeAmount<T> {
resultFeeOrders: T[]; resultFeeOrders: T[];
feeOrdersRemainingFillableMakerAssetAmounts: BigNumber[];
remainingFeeAmount: BigNumber; remainingFeeAmount: BigNumber;
} }
export interface OrdersAndRemainingFillAmount<T> { export interface OrdersAndRemainingFillAmount<T> {
resultOrders: T[]; resultOrders: T[];
ordersRemainingFillableMakerAssetAmounts: BigNumber[];
remainingFillAmount: BigNumber; remainingFillAmount: BigNumber;
} }