Skip to content

Commit

Permalink
feat(buy/sell panel): support trades that increase available collateral
Browse files Browse the repository at this point in the history
  • Loading branch information
fubar committed May 24, 2024
1 parent 60248ce commit a3b3617
Show file tree
Hide file tree
Showing 2 changed files with 206 additions and 61 deletions.
83 changes: 45 additions & 38 deletions src/orderbook/quantities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,12 +180,12 @@ export function calculateBuySellPanelEstimate(
* Don't limit the amount of available collateral to be spent if limiting by
* desired position qty.
*/
let desiredRemainingAvailableCollateral = BigInt(0);
let desiredRemainingAvailableCollateral2p = BigInt(0);

if (typeof sliderFactor !== 'undefined') {
const sliderFactorInPips = decimalToPip(sliderFactor.toString());

desiredRemainingAvailableCollateral =
desiredRemainingAvailableCollateral2p =
initialAvailableCollateral * (oneInPips - sliderFactorInPips);
}

Expand Down Expand Up @@ -228,16 +228,6 @@ export function calculateBuySellPanelEstimate(
quoteQuantity: additionalPositionCostBasis,
};
}
if (
takerSide === 'buy' ?
indexPrice >= makerOrder.price
: indexPrice <= makerOrder.price
) {
return {
baseQuantity: additionalPositionQty,
quoteQuantity: additionalPositionCostBasis,
};
}
const makerOrderPrice2p = makerOrder.price * oneInPips;

const positionBalance =
Expand All @@ -252,47 +242,63 @@ export function calculateBuySellPanelEstimate(
positionBalance,
});

// Unsigned
const initialMarginRequirementOfPosition2p = multiplyPips(
absBigInt(quoteValueOfPosition2p),
initialMarginFraction,
);

// Signed
const maxBuyingPowerBase =
((-quoteBalance2p -
quoteValueOfPosition2p -
quoteValueOfOtherPositions2p +
heldCollateral2p +
desiredRemainingAvailableCollateral +
initialMarginRequirementOfPosition2p +
initialMarginRequirementOfOtherPositions2p) *
oneInPips) /
(indexPrice2p -
makerOrderPrice2p +
BigInt(takerSide === 'buy' ? -1 : 1) *
indexPrice *
initialMarginFraction);

let maxTakerBaseQty = maxBuyingPowerBase;
let maxTakerBaseQty =
makerOrder.size * BigInt(takerSide === 'buy' ? 1 : -1);

if (
takerSide === 'buy' ?
makerOrder.price >
multiplyPips(indexPrice, oneInPips - initialMarginFraction)
: multiplyPips(indexPrice, oneInPips + initialMarginFraction) >
makerOrder.price
) {
/*
* Trade decreases available collateral; determine the taker's buying
* power (may exceed the maker order size).
*/

// Unsigned
const initialMarginRequirementOfPosition2p = multiplyPips(
absBigInt(quoteValueOfPosition2p),
initialMarginFraction,
);

// Signed
maxTakerBaseQty =
((-quoteBalance2p -
quoteValueOfPosition2p -
quoteValueOfOtherPositions2p +
heldCollateral2p +
desiredRemainingAvailableCollateral2p +
initialMarginRequirementOfPosition2p +
initialMarginRequirementOfOtherPositions2p) *
oneInPips) /
(indexPrice2p -
makerOrderPrice2p +
BigInt(takerSide === 'buy' ? -1 : 1) *
indexPrice *
initialMarginFraction);
}

if (desiredPositionBaseQuantity) {
// Limit base to buying power and desired position qty
// Limit max base to desired position qty and buying power
maxTakerBaseQty =
takerSide === 'buy' ?
minBigInt(
maxBuyingPowerBase,
maxTakerBaseQty,
desiredPositionBaseQuantity - additionalPositionQty,
)
: maxBigInt(
maxBuyingPowerBase,
maxTakerBaseQty,
desiredPositionBaseQuantity - additionalPositionQty,
);
}

let maxTakerQuoteQty = multiplyPips(maxTakerBaseQty, makerOrder.price);

if (desiredPositionQuoteQuantity) {
// Limit quote to buying power and desired position qty
// Limit max quote to desired position qty and buying power
const maxTakerQuoteQtyBefore = maxTakerQuoteQty;
maxTakerQuoteQty =
takerSide === 'buy' ?
Expand Down Expand Up @@ -327,6 +333,7 @@ export function calculateBuySellPanelEstimate(

quoteBalance2p -= tradeBaseQty * makerOrder.price;
}

return {
baseQuantity: additionalPositionQty,
quoteQuantity: additionalPositionCostBasis,
Expand Down
184 changes: 161 additions & 23 deletions src/tests/orderbook/quantities/calculateBuySellPanelEstimate.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import * as chai from 'chai';

import { decimalToPip, pipToDecimal } from '#pipmath';
import {
absBigInt,
decimalToPip,
multiplyPips,
oneInPips,
pipToDecimal,
} from '#pipmath';

import * as orderbook from '#orderbook/index';

Expand Down Expand Up @@ -634,10 +640,84 @@ describe('orderbook/quantities', () => {
);
});

const runIndexPriceMismatchBuyScenario = (arePricesEqual: boolean) => {
const { market, positionInAnotherMarket, heldCollateral, quoteBalance } =
function calculateAvailableCollateral(args: {
heldCollateral: bigint;
market: IDEXMarket;
positionInAnotherMarket: IDEXPosition;
positionQuantity: bigint;
quoteBalance: bigint;
}): bigint {
const {
heldCollateral,
market,
positionInAnotherMarket,
positionQuantity,
quoteBalance,
} = args;

// Signed
const quoteValueOfPosition = multiplyPips(
positionQuantity,
decimalToPip(market.indexPrice),
);

const initialMarginRequirement = multiplyPips(
absBigInt(quoteValueOfPosition),
decimalToPip(market.initialMarginFraction),
);

const accountValue =
quoteBalance +
quoteValueOfPosition +
// Other position has index price 1; see `setUpStandardTestAccount`
decimalToPip(positionInAnotherMarket.quantity);

return (
accountValue -
initialMarginRequirement -
decimalToPip(positionInAnotherMarket.marginRequirement) -
heldCollateral
);
}

const runTradeIncreasesAvailableCollateralBuyScenario = (
runAvailableCollateralStaysTheSameScenario: boolean,
) => {
const { market, positionInAnotherMarket, heldCollateral } =
setUpStandardTestAccount();

// The test account's quote balance equals its available collateral
const quoteBalance = BigInt(1);

expect(
calculateAvailableCollateral({
heldCollateral,
market,
positionInAnotherMarket,
positionQuantity: BigInt(0),
quoteBalance,
}),
).to.eql(BigInt(1));

/*
* Buy trades increase available collateral if
* trade price <= index price * (1 - IMF)
* 0.01 index price * (1 - 0.03 IMF) = 0.0097
*/
const priceThreshold = multiplyPips(
decimalToPip(market.indexPrice),
oneInPips - decimalToPip(market.initialMarginFraction),
);

const orderQty = decimalToPip('1');
const orderPrice =
runAvailableCollateralStaysTheSameScenario ? priceThreshold : (
priceThreshold - BigInt(1)
);

const expectedBaseQty = orderQty;
const expectedQuoteQty = orderPrice; // order price * order qty (1)

expect(
orderbook.calculateBuySellPanelEstimate({
allWalletPositions: [positionInAnotherMarket],
Expand All @@ -646,31 +726,77 @@ describe('orderbook/quantities', () => {
leverageParameters: market,
makerSideOrders: [
{
price: decimalToPip(arePricesEqual ? '0.01' : '0.009'),
size: decimalToPip('123'),
price: orderPrice,
size: orderQty,
},
],
market,
quoteBalance,
sliderFactor: 0.123,
sliderFactor: 0.123, // Should have no effect
takerSide: 'buy',
}),
).to.eql({
baseQuantity: BigInt(0),
quoteQuantity: BigInt(0),
baseQuantity: expectedBaseQty,
quoteQuantity: expectedQuoteQty,
});

expect(
calculateAvailableCollateral({
heldCollateral,
market,
positionInAnotherMarket,
positionQuantity: expectedBaseQty,
quoteBalance: quoteBalance - expectedQuoteQty,
}),
).to.eql(
runAvailableCollateralStaysTheSameScenario ? BigInt(1) : BigInt(2),
);
};

it('should stop matching when the index price exceeds a maker sell price (taker buy)', () =>
runIndexPriceMismatchBuyScenario(false));
it("should allow trades that don't change available collateral (taker buy)", () =>
runTradeIncreasesAvailableCollateralBuyScenario(true));

it('should stop matching when the index price is equal to a maker sell price (taker buy)', () =>
runIndexPriceMismatchBuyScenario(true));
it('should allow trades that increase available collateral (taker buy)', () =>
runTradeIncreasesAvailableCollateralBuyScenario(false));

const runIndexPriceMismatchSellScenario = (arePricesEqual: boolean) => {
const { market, positionInAnotherMarket, heldCollateral, quoteBalance } =
const runTradeIncreasesAvailableCollateralSellScenario = (
runAvailableCollateralStaysTheSameScenario: boolean,
) => {
const { market, positionInAnotherMarket, heldCollateral } =
setUpStandardTestAccount();

// The test account's quote balance equals its available collateral
const quoteBalance = BigInt(1);

expect(
calculateAvailableCollateral({
heldCollateral,
market,
positionInAnotherMarket,
positionQuantity: BigInt(0),
quoteBalance,
}),
).to.eql(BigInt(1));

/*
* Sell trades increase available collateral if
* trade price >= index price * (1 + IMF)
* 0.01 index price * (1 + 0.03 IMF) = 0.0103
*/
const priceThreshold = multiplyPips(
decimalToPip(market.indexPrice),
oneInPips + decimalToPip(market.initialMarginFraction),
);

const orderQty = decimalToPip('1');
const orderPrice =
runAvailableCollateralStaysTheSameScenario ? priceThreshold : (
priceThreshold + BigInt(1)
);

const expectedBaseQty = -orderQty;
const expectedQuoteQty = -orderPrice; // -(order price * order qty (1))

expect(
orderbook.calculateBuySellPanelEstimate({
allWalletPositions: [positionInAnotherMarket],
Expand All @@ -679,25 +805,37 @@ describe('orderbook/quantities', () => {
leverageParameters: market,
makerSideOrders: [
{
price: decimalToPip(arePricesEqual ? '0.01' : '0.011'),
size: decimalToPip('123'),
price: orderPrice,
size: orderQty,
},
],
market,
quoteBalance,
sliderFactor: 0.123,
sliderFactor: 0.123, // Should have no effect
takerSide: 'sell',
}),
).to.eql({
baseQuantity: BigInt(0),
quoteQuantity: BigInt(0),
baseQuantity: expectedBaseQty,
quoteQuantity: expectedQuoteQty,
});

expect(
calculateAvailableCollateral({
heldCollateral,
market,
positionInAnotherMarket,
positionQuantity: expectedBaseQty,
quoteBalance: quoteBalance - expectedQuoteQty,
}),
).to.eql(
runAvailableCollateralStaysTheSameScenario ? BigInt(1) : BigInt(2),
);
};

it('should stop matching when the index price is below a maker buy price (taker sell)', () =>
runIndexPriceMismatchSellScenario(false));
it("should allow trades that don't change available collateral (taker sell)", () =>
runTradeIncreasesAvailableCollateralSellScenario(true));

it('should stop matching when the index price is equal to a maker buy price (taker sell)', () =>
runIndexPriceMismatchSellScenario(true));
it('should allow trades that increase available collateral (taker sell)', () =>
runTradeIncreasesAvailableCollateralSellScenario(false));
});
});

0 comments on commit a3b3617

Please sign in to comment.