Skip to content
This repository has been archived by the owner on Jan 18, 2023. It is now read-only.

Commit

Permalink
Brian/new pivot price curve (#306)
Browse files Browse the repository at this point in the history
New price curve with pivot pattern
  • Loading branch information
bweick committed Nov 30, 2018
1 parent 2989720 commit 84a32f4
Show file tree
Hide file tree
Showing 3 changed files with 145 additions and 15 deletions.
Expand Up @@ -78,25 +78,85 @@ contract LinearAuctionPriceCurve {
*
* @param _auctionStartTime Time of auction start
* @param _auctionTimeToPivot Time until auction reaches pivot point
* @param _auctionStartPrice The price to start the auction at
* @param -- Unused auction start price to conform to IAuctionPriceCurve --
* @param _auctionPivotPrice The price at which auction curve changes from linear to exponential
* @return uint256 The auction price numerator
* @return uint256 The auction price denominator
*/
function getCurrentPrice(
uint256 _auctionStartTime,
uint256 _auctionTimeToPivot,
uint256 _auctionStartPrice,
uint256,
uint256 _auctionPivotPrice
)
external
view
returns (uint256, uint256)
{
// Calculate how much time has elapsed since start of auction and divide by
// timeIncrement of 30 seconds, so price changes every 30 seconds
uint256 elapsed = block.timestamp.sub(_auctionStartTime).div(30);
// Calculate how much time has elapsed since start of auction
uint256 elapsed = block.timestamp.sub(_auctionStartTime);

// Initialize numerator and denominator
uint256 priceNumerator = _auctionPivotPrice;
uint256 currentPriceDenominator = priceDenominator;

/*
* This price curve can be broken down into three stages, 1) set up to allow a portion where managers
* have control over the cadence of the auction, and then two more stages that are used to enforce finality
* to the auction. The auction price, p(x), is defined by:
*
* p(x) = (priceNumerator/priceDenominator
*
* In each stage either the priceNumerator or priceDenominator is manipulated to change p(x).The curve shape
* in each stage is defined below.
*
* 1) Managers have the greatest control over stage 1. Here they define a linear curve that starts at zero
* and terminates at the passed pivot price. The length of time it takes for the auction to reach the pivot
* price is defined by the manager too, thus resulting in the following equation for the slope of the line:
*
* PriceNumerator(x) = auctionPivotPrice*(x/auctionTimeToPivot), where x is amount of time from auction start
*
* 2) Stage 2 the protocol takes over to attempt to hasten/guarantee finality, this unfortunately decreases
* the granularity of the auction price changes. In this stage the PriceNumerator remains fixed at the
* auctionPivotPrice. However, the priceDenominator decays at a rate equivalent to 0.1% of the ORIGINAL
* priceDenominator every 30 secs. This leads to the following function relative to time:
*
* PriceDenominator(x) = priceDenominator-(0.01*priceDeonimator*((x-auctionTimeToPivot)/30)), where x is amount
* of time from auction start.
*
* Since we are decaying the denominator the price curve takes on the shape of e^x. Because of the limitations
* of integer math the denominator can only be decayed to 1. Hence in order to maintain simplicity in calculations
* there is a third segment defined below.
*
* 3) The third segment is a simple linear calculation that changes the priceNumerator at the rate of the pivot
* price every 30 seconds and fixes the priceDenominator at 1:
*
* PriceNumerator(x) = auctionPivotPrice + auctionPivotPrice*(x-auctionTimeToPivot-30000), where x is amount of
* time from auction start and 30000 represents the amount of time spent in Stage 2
*/

// If time hasn't passed to pivot use the user-defined curve
if (elapsed <= _auctionTimeToPivot) {
// Calculate the priceNumerator as a linear function of time between 0 and _auctionPivotPrice
priceNumerator = elapsed.mul(_auctionPivotPrice).div(_auctionTimeToPivot);
} else {
// Calculate how many 30 second increments have passed since pivot was reached
uint256 thirtySecondPeriods = elapsed.sub(_auctionTimeToPivot).div(30);

// Because after 1000 thirtySecondPeriods the priceDenominator would be 0 (causes revert)
if (thirtySecondPeriods < 1000) {
// Calculate new denominator where the denominator decays at a rate of 0.1% of the ORIGINAL
// priceDenominator per time increment (hence divide by 1000)
currentPriceDenominator = priceDenominator.sub(thirtySecondPeriods.mul(priceDenominator).div(1000));
} else {
// Once denominator has fully decayed, fix it at 1
currentPriceDenominator = 1;

// Now priceNumerator just changes linearly, but with slope equal to the pivot price
priceNumerator = _auctionPivotPrice.add(_auctionPivotPrice.mul(thirtySecondPeriods.sub(1000)));
}
}

return (elapsed, priceDenominator);
return (priceNumerator, currentPriceDenominator);
}
}
Expand Up @@ -142,9 +142,59 @@ contract('LinearAuctionPriceCurve', accounts => {

const returnedPrice = await subject();

const expectedPrice = timeJump.div(new BigNumber(30));
expect(returnedPrice[0]).to.be.bignumber.equal(expectedPrice);
const expectedPrice = rebalancingWrapper.getExpectedLinearAuctionPrice(
timeJump,
subjectAuctionTimeToPivot,
subjectAuctionPivotPrice,
DEFAULT_AUCTION_PRICE_DENOMINATOR,
);

expect(returnedPrice[0]).to.be.bignumber.equal(expectedPrice.priceNumerator);
expect(returnedPrice[1]).to.be.bignumber.equal(expectedPrice.priceDenominator);
});

it('returns the correct price at the pivot', async () => {
const timeJump = subjectAuctionTimeToPivot;
await blockchain.increaseTimeAsync(timeJump);

const returnedPrice = await subject();

expect(returnedPrice[0]).to.be.bignumber.equal(subjectAuctionPivotPrice);
expect(returnedPrice[1]).to.be.bignumber.equal(DEFAULT_AUCTION_PRICE_DENOMINATOR);
});

it('returns the correct price after the pivot', async () => {
const timeJump = new BigNumber(115000);
await blockchain.increaseTimeAsync(timeJump);

const returnedPrice = await subject();

const expectedPrice = rebalancingWrapper.getExpectedLinearAuctionPrice(
timeJump,
subjectAuctionTimeToPivot,
subjectAuctionPivotPrice,
DEFAULT_AUCTION_PRICE_DENOMINATOR,
);

expect(returnedPrice[0]).to.be.bignumber.equal(expectedPrice.priceNumerator);
expect(returnedPrice[1]).to.be.bignumber.equal(expectedPrice.priceDenominator);
});

it('returns the correct price after denominator hits 1', async () => {
const timeJump = new BigNumber(150000);
await blockchain.increaseTimeAsync(timeJump);

const returnedPrice = await subject();

const expectedPrice = rebalancingWrapper.getExpectedLinearAuctionPrice(
timeJump,
subjectAuctionTimeToPivot,
subjectAuctionPivotPrice,
DEFAULT_AUCTION_PRICE_DENOMINATOR,
);

expect(returnedPrice[0]).to.be.bignumber.equal(expectedPrice.priceNumerator);
expect(returnedPrice[1]).to.be.bignumber.equal(expectedPrice.priceDenominator);
});
});
});
34 changes: 27 additions & 7 deletions utils/rebalancingWrapper.ts
Expand Up @@ -14,7 +14,6 @@ import {
import { BigNumber } from 'bignumber.js';

import {
AUCTION_TIME_INCREMENT,
DEFAULT_GAS,
DEFAULT_REBALANCING_NATURAL_UNIT,
DEFAULT_UNIT_SHARES,
Expand Down Expand Up @@ -444,11 +443,32 @@ export class RebalancingWrapper {

public getExpectedLinearAuctionPrice(
elapsedTime: BigNumber,
curveCoefficient: BigNumber,
auctionStartPrice: BigNumber
): BigNumber {
const elaspedTimeFromStart = elapsedTime.div(AUCTION_TIME_INCREMENT).round(0, 3);
const expectedPrice = curveCoefficient.mul(elaspedTimeFromStart).add(auctionStartPrice);
return expectedPrice;
auctionTimeToPivot: BigNumber,
auctionPivotPrice: BigNumber,
priceDivisor: BigNumber,
): any {
let priceNumerator: BigNumber;
let priceDenominator: BigNumber;
const timeIncrementsToZero = new BigNumber(1000);

if (elapsedTime.lessThanOrEqualTo(auctionTimeToPivot)) {
priceNumerator = elapsedTime.mul(auctionPivotPrice).div(auctionTimeToPivot).round(0, 3);

priceDenominator = priceDivisor;
} else {
const timeIncrements = elapsedTime.sub(auctionTimeToPivot).div(30).round(0, 3);

if (timeIncrements.lessThan(timeIncrementsToZero)) {
priceNumerator = auctionPivotPrice;
priceDenominator = priceDivisor.sub(timeIncrements.mul(priceDivisor).div(1000).round(0, 3));
} else {
priceDenominator = new BigNumber(1);
priceNumerator = auctionPivotPrice.add(auctionPivotPrice.mul(timeIncrements.sub(1000)));
}
}
return {
priceNumerator,
priceDenominator,
};
}
}

0 comments on commit 84a32f4

Please sign in to comment.