2023-02-21 23:36:59 -08:00
|
|
|
import { BN } from '@coral-xyz/anchor';
|
2022-09-29 06:51:09 -07:00
|
|
|
import { OpenOrders } from '@project-serum/serum';
|
2022-09-23 02:43:26 -07:00
|
|
|
import { expect } from 'chai';
|
2023-02-21 23:36:59 -08:00
|
|
|
import cloneDeep from 'lodash/cloneDeep';
|
|
|
|
import range from 'lodash/range';
|
2023-01-20 05:52:43 -08:00
|
|
|
|
2022-12-16 07:33:37 -08:00
|
|
|
import { I80F48, ONE_I80F48, ZERO_I80F48 } from '../numbers/I80F48';
|
2022-12-02 06:48:43 -08:00
|
|
|
import { BankForHealth, StablePriceModel, TokenIndex } from './bank';
|
2022-09-29 06:51:09 -07:00
|
|
|
import { HealthCache, PerpInfo, Serum3Info, TokenInfo } from './healthCache';
|
|
|
|
import { HealthType, PerpPosition } from './mangoAccount';
|
2022-12-02 06:48:43 -08:00
|
|
|
import { PerpMarket, PerpOrderSide } from './perp';
|
2022-09-29 06:51:09 -07:00
|
|
|
import { MarketIndex } from './serum3';
|
|
|
|
|
|
|
|
function mockBankAndOracle(
|
|
|
|
tokenIndex: TokenIndex,
|
|
|
|
maintWeight: number,
|
|
|
|
initWeight: number,
|
|
|
|
price: number,
|
2022-12-14 00:21:45 -08:00
|
|
|
stablePrice: number,
|
2022-09-29 06:51:09 -07:00
|
|
|
): BankForHealth {
|
|
|
|
return {
|
|
|
|
tokenIndex,
|
|
|
|
maintAssetWeight: I80F48.fromNumber(1 - maintWeight),
|
|
|
|
initAssetWeight: I80F48.fromNumber(1 - initWeight),
|
|
|
|
maintLiabWeight: I80F48.fromNumber(1 + maintWeight),
|
|
|
|
initLiabWeight: I80F48.fromNumber(1 + initWeight),
|
|
|
|
price: I80F48.fromNumber(price),
|
2022-12-14 00:21:45 -08:00
|
|
|
stablePriceModel: { stablePrice: stablePrice } as StablePriceModel,
|
2022-12-08 01:16:06 -08:00
|
|
|
scaledInitAssetWeight: () => I80F48.fromNumber(1 - initWeight),
|
|
|
|
scaledInitLiabWeight: () => I80F48.fromNumber(1 + initWeight),
|
2022-09-29 06:51:09 -07:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function mockPerpMarket(
|
|
|
|
perpMarketIndex: number,
|
2023-01-16 07:49:09 -08:00
|
|
|
maintBaseWeight: number,
|
|
|
|
initBaseWeight: number,
|
2022-12-02 06:48:43 -08:00
|
|
|
baseLotSize: number,
|
|
|
|
price: number,
|
2022-09-29 06:51:09 -07:00
|
|
|
): PerpMarket {
|
|
|
|
return {
|
|
|
|
perpMarketIndex,
|
2023-01-16 07:49:09 -08:00
|
|
|
maintBaseAssetWeight: I80F48.fromNumber(1 - maintBaseWeight),
|
|
|
|
initBaseAssetWeight: I80F48.fromNumber(1 - initBaseWeight),
|
|
|
|
maintBaseLiabWeight: I80F48.fromNumber(1 + maintBaseWeight),
|
|
|
|
initBaseLiabWeight: I80F48.fromNumber(1 + initBaseWeight),
|
2023-02-01 07:15:45 -08:00
|
|
|
maintOverallAssetWeight: I80F48.fromNumber(1 - 0.02),
|
|
|
|
initOverallAssetWeight: I80F48.fromNumber(1 - 0.05),
|
2022-12-02 06:48:43 -08:00
|
|
|
price: I80F48.fromNumber(price),
|
|
|
|
stablePriceModel: { stablePrice: price } as StablePriceModel,
|
2022-09-29 06:51:09 -07:00
|
|
|
quoteLotSize: new BN(100),
|
2022-12-02 06:48:43 -08:00
|
|
|
baseLotSize: new BN(baseLotSize),
|
2022-09-29 06:51:09 -07:00
|
|
|
longFunding: ZERO_I80F48(),
|
|
|
|
shortFunding: ZERO_I80F48(),
|
|
|
|
} as unknown as PerpMarket;
|
|
|
|
}
|
2022-09-23 02:43:26 -07:00
|
|
|
|
|
|
|
describe('Health Cache', () => {
|
2022-09-29 06:51:09 -07:00
|
|
|
it('test_health0', () => {
|
|
|
|
const sourceBank: BankForHealth = mockBankAndOracle(
|
|
|
|
1 as TokenIndex,
|
|
|
|
0.1,
|
|
|
|
0.2,
|
|
|
|
1,
|
2022-12-14 00:21:45 -08:00
|
|
|
1,
|
2022-09-29 06:51:09 -07:00
|
|
|
);
|
|
|
|
const targetBank: BankForHealth = mockBankAndOracle(
|
|
|
|
4 as TokenIndex,
|
|
|
|
0.3,
|
|
|
|
0.5,
|
|
|
|
5,
|
2022-12-14 00:21:45 -08:00
|
|
|
5,
|
2022-09-29 06:51:09 -07:00
|
|
|
);
|
|
|
|
|
|
|
|
const ti1 = TokenInfo.fromBank(sourceBank, I80F48.fromNumber(100));
|
|
|
|
const ti2 = TokenInfo.fromBank(targetBank, I80F48.fromNumber(-10));
|
|
|
|
|
|
|
|
const si1 = Serum3Info.fromOoModifyingTokenInfos(
|
|
|
|
1,
|
|
|
|
ti2,
|
|
|
|
0,
|
|
|
|
ti1,
|
|
|
|
2 as MarketIndex,
|
|
|
|
{
|
|
|
|
quoteTokenTotal: new BN(21),
|
|
|
|
baseTokenTotal: new BN(18),
|
|
|
|
quoteTokenFree: new BN(1),
|
|
|
|
baseTokenFree: new BN(3),
|
|
|
|
referrerRebatesAccrued: new BN(2),
|
|
|
|
} as any as OpenOrders,
|
|
|
|
);
|
|
|
|
|
2022-12-02 06:48:43 -08:00
|
|
|
const pM = mockPerpMarket(9, 0.1, 0.2, 10, targetBank.price.toNumber());
|
2022-09-29 06:51:09 -07:00
|
|
|
const pp = new PerpPosition(
|
|
|
|
pM.perpMarketIndex,
|
2022-12-06 05:14:58 -08:00
|
|
|
0,
|
|
|
|
new BN(0),
|
2022-09-30 06:07:43 -07:00
|
|
|
new BN(3),
|
2022-09-29 06:51:09 -07:00
|
|
|
I80F48.fromNumber(-310),
|
2022-12-06 05:14:58 -08:00
|
|
|
new BN(0),
|
|
|
|
ZERO_I80F48(),
|
|
|
|
ZERO_I80F48(),
|
2022-09-30 06:07:43 -07:00
|
|
|
new BN(7),
|
|
|
|
new BN(11),
|
2022-11-02 05:13:29 -07:00
|
|
|
new BN(1),
|
|
|
|
new BN(2),
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
new BN(0),
|
|
|
|
new BN(0),
|
|
|
|
new BN(0),
|
2022-12-06 05:14:58 -08:00
|
|
|
0,
|
|
|
|
ZERO_I80F48(),
|
2023-01-11 05:32:15 -08:00
|
|
|
ZERO_I80F48(),
|
|
|
|
new BN(0),
|
2023-01-17 05:07:58 -08:00
|
|
|
ZERO_I80F48(),
|
2022-09-29 06:51:09 -07:00
|
|
|
);
|
|
|
|
const pi1 = PerpInfo.fromPerpPosition(pM, pp);
|
|
|
|
|
|
|
|
const hc = new HealthCache([ti1, ti2], [si1], [pi1]);
|
|
|
|
|
|
|
|
// for bank1/oracle1, including open orders (scenario: bids execute)
|
|
|
|
const health1 = (100.0 + 1.0 + 2.0 + (20.0 + 15.0 * 5.0)) * 0.8;
|
|
|
|
// for bank2/oracle2
|
|
|
|
const health2 = (-10.0 + 3.0) * 5.0 * 1.5;
|
|
|
|
// for perp (scenario: bids execute)
|
|
|
|
const health3 =
|
|
|
|
(3.0 + 7.0 + 1.0) * 10.0 * 5.0 * 0.8 +
|
|
|
|
(-310.0 + 2.0 * 100.0 - 7.0 * 10.0 * 5.0);
|
|
|
|
|
|
|
|
const health = hc.health(HealthType.init).toNumber();
|
|
|
|
console.log(
|
2022-12-02 06:48:43 -08:00
|
|
|
` - health ${health
|
2022-09-29 06:51:09 -07:00
|
|
|
.toFixed(3)
|
|
|
|
.padStart(
|
|
|
|
10,
|
|
|
|
)}, case "test that includes all the side values (like referrer_rebates_accrued)"`,
|
|
|
|
);
|
|
|
|
|
|
|
|
expect(health - (health1 + health2 + health3)).lessThan(0.0000001);
|
|
|
|
});
|
|
|
|
|
2022-12-02 06:48:43 -08:00
|
|
|
it('test_health1', (done) => {
|
2022-09-29 06:51:09 -07:00
|
|
|
function testFixture(fixture: {
|
|
|
|
name: string;
|
|
|
|
token1: number;
|
|
|
|
token2: number;
|
|
|
|
token3: number;
|
|
|
|
oo12: [number, number];
|
|
|
|
oo13: [number, number];
|
|
|
|
perp1: [number, number, number, number];
|
|
|
|
expectedHealth: number;
|
|
|
|
}): void {
|
|
|
|
const bank1: BankForHealth = mockBankAndOracle(
|
|
|
|
1 as TokenIndex,
|
|
|
|
0.1,
|
|
|
|
0.2,
|
|
|
|
1,
|
2022-12-14 00:21:45 -08:00
|
|
|
1,
|
2022-09-29 06:51:09 -07:00
|
|
|
);
|
|
|
|
const bank2: BankForHealth = mockBankAndOracle(
|
|
|
|
4 as TokenIndex,
|
|
|
|
0.3,
|
|
|
|
0.5,
|
|
|
|
5,
|
2022-12-14 00:21:45 -08:00
|
|
|
5,
|
2022-09-29 06:51:09 -07:00
|
|
|
);
|
|
|
|
const bank3: BankForHealth = mockBankAndOracle(
|
|
|
|
5 as TokenIndex,
|
|
|
|
0.3,
|
|
|
|
0.5,
|
|
|
|
10,
|
2022-12-14 00:21:45 -08:00
|
|
|
10,
|
2022-09-29 06:51:09 -07:00
|
|
|
);
|
|
|
|
|
|
|
|
const ti1 = TokenInfo.fromBank(bank1, I80F48.fromNumber(fixture.token1));
|
|
|
|
const ti2 = TokenInfo.fromBank(bank2, I80F48.fromNumber(fixture.token2));
|
|
|
|
const ti3 = TokenInfo.fromBank(bank3, I80F48.fromNumber(fixture.token3));
|
|
|
|
|
|
|
|
const si1 = Serum3Info.fromOoModifyingTokenInfos(
|
|
|
|
1,
|
|
|
|
ti2,
|
|
|
|
0,
|
|
|
|
ti1,
|
|
|
|
2 as MarketIndex,
|
|
|
|
{
|
|
|
|
quoteTokenTotal: new BN(fixture.oo12[0]),
|
|
|
|
baseTokenTotal: new BN(fixture.oo12[1]),
|
|
|
|
quoteTokenFree: new BN(0),
|
|
|
|
baseTokenFree: new BN(0),
|
|
|
|
referrerRebatesAccrued: new BN(0),
|
|
|
|
} as any as OpenOrders,
|
|
|
|
);
|
|
|
|
|
|
|
|
const si2 = Serum3Info.fromOoModifyingTokenInfos(
|
|
|
|
2,
|
|
|
|
ti3,
|
|
|
|
0,
|
|
|
|
ti1,
|
|
|
|
2 as MarketIndex,
|
|
|
|
{
|
|
|
|
quoteTokenTotal: new BN(fixture.oo13[0]),
|
|
|
|
baseTokenTotal: new BN(fixture.oo13[1]),
|
|
|
|
quoteTokenFree: new BN(0),
|
|
|
|
baseTokenFree: new BN(0),
|
|
|
|
referrerRebatesAccrued: new BN(0),
|
|
|
|
} as any as OpenOrders,
|
|
|
|
);
|
|
|
|
|
2022-12-02 06:48:43 -08:00
|
|
|
const pM = mockPerpMarket(9, 0.1, 0.2, 10, bank2.price.toNumber());
|
2022-09-29 06:51:09 -07:00
|
|
|
const pp = new PerpPosition(
|
|
|
|
pM.perpMarketIndex,
|
2022-12-06 05:14:58 -08:00
|
|
|
0,
|
|
|
|
new BN(0),
|
2022-09-30 06:07:43 -07:00
|
|
|
new BN(fixture.perp1[0]),
|
2022-09-29 06:51:09 -07:00
|
|
|
I80F48.fromNumber(fixture.perp1[1]),
|
2022-11-02 05:13:29 -07:00
|
|
|
new BN(0),
|
2022-12-06 05:14:58 -08:00
|
|
|
ZERO_I80F48(),
|
|
|
|
ZERO_I80F48(),
|
2022-12-02 06:48:43 -08:00
|
|
|
new BN(fixture.perp1[2]),
|
|
|
|
new BN(fixture.perp1[3]),
|
2022-11-02 05:13:29 -07:00
|
|
|
new BN(0),
|
|
|
|
new BN(0),
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
new BN(0),
|
|
|
|
new BN(0),
|
2022-11-09 00:35:13 -08:00
|
|
|
new BN(0),
|
2022-12-06 05:14:58 -08:00
|
|
|
0,
|
|
|
|
ZERO_I80F48(),
|
2023-01-11 05:32:15 -08:00
|
|
|
ZERO_I80F48(),
|
|
|
|
new BN(0),
|
2023-01-17 05:07:58 -08:00
|
|
|
ZERO_I80F48(),
|
2022-09-29 06:51:09 -07:00
|
|
|
);
|
|
|
|
const pi1 = PerpInfo.fromPerpPosition(pM, pp);
|
|
|
|
|
|
|
|
const hc = new HealthCache([ti1, ti2, ti3], [si1, si2], [pi1]);
|
|
|
|
const health = hc.health(HealthType.init).toNumber();
|
|
|
|
console.log(
|
2023-01-16 07:49:09 -08:00
|
|
|
` - case "${fixture.name}" health ${health
|
|
|
|
.toFixed(3)
|
|
|
|
.padStart(10)}, expected ${fixture.expectedHealth}`,
|
2022-09-29 06:51:09 -07:00
|
|
|
);
|
|
|
|
expect(health - fixture.expectedHealth).lessThan(0.0000001);
|
|
|
|
}
|
|
|
|
|
|
|
|
const basePrice = 5;
|
|
|
|
const baseLotsToQuote = 10.0 * basePrice;
|
|
|
|
|
|
|
|
testFixture({
|
|
|
|
name: '0',
|
|
|
|
token1: 100,
|
|
|
|
token2: -10,
|
|
|
|
token3: 0,
|
|
|
|
oo12: [20, 15],
|
|
|
|
oo13: [0, 0],
|
|
|
|
perp1: [3, -131, 7, 11],
|
|
|
|
expectedHealth:
|
|
|
|
// for token1, including open orders (scenario: bids execute)
|
|
|
|
(100.0 + (20.0 + 15.0 * basePrice)) * 0.8 -
|
|
|
|
// for token2
|
|
|
|
10.0 * basePrice * 1.5 +
|
|
|
|
// for perp (scenario: bids execute)
|
|
|
|
(3.0 + 7.0) * baseLotsToQuote * 0.8 +
|
|
|
|
(-131.0 - 7.0 * baseLotsToQuote),
|
|
|
|
});
|
|
|
|
|
|
|
|
testFixture({
|
|
|
|
name: '1',
|
|
|
|
token1: -100,
|
|
|
|
token2: 10,
|
|
|
|
token3: 0,
|
|
|
|
oo12: [20, 15],
|
|
|
|
oo13: [0, 0],
|
|
|
|
perp1: [-10, -131, 7, 11],
|
|
|
|
expectedHealth:
|
|
|
|
// for token1
|
|
|
|
-100.0 * 1.2 +
|
|
|
|
// for token2, including open orders (scenario: asks execute)
|
|
|
|
(10.0 * basePrice + (20.0 + 15.0 * basePrice)) * 0.5 +
|
|
|
|
// for perp (scenario: asks execute)
|
|
|
|
(-10.0 - 11.0) * baseLotsToQuote * 1.2 +
|
|
|
|
(-131.0 + 11.0 * baseLotsToQuote),
|
|
|
|
});
|
|
|
|
|
|
|
|
testFixture({
|
2023-01-16 07:49:09 -08:00
|
|
|
name: '2: weighted positive perp pnl',
|
2022-09-29 06:51:09 -07:00
|
|
|
token1: 0,
|
|
|
|
token2: 0,
|
|
|
|
token3: 0,
|
|
|
|
oo12: [0, 0],
|
|
|
|
oo13: [0, 0],
|
2023-01-16 07:49:09 -08:00
|
|
|
perp1: [-1, 100, 0, 0],
|
|
|
|
expectedHealth: 0.95 * (100.0 - 1.2 * 1.0 * baseLotsToQuote),
|
2022-09-29 06:51:09 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
testFixture({
|
2023-01-16 07:49:09 -08:00
|
|
|
name: '3: negative perp pnl is not weighted',
|
2022-09-29 06:51:09 -07:00
|
|
|
token1: 0,
|
|
|
|
token2: 0,
|
|
|
|
token3: 0,
|
|
|
|
oo12: [0, 0],
|
|
|
|
oo13: [0, 0],
|
|
|
|
perp1: [1, -100, 0, 0],
|
|
|
|
expectedHealth: -100.0 + 0.8 * 1.0 * baseLotsToQuote,
|
|
|
|
});
|
|
|
|
|
|
|
|
testFixture({
|
2023-01-16 07:49:09 -08:00
|
|
|
name: '4: perp health',
|
2022-09-29 06:51:09 -07:00
|
|
|
token1: 0,
|
|
|
|
token2: 0,
|
|
|
|
token3: 0,
|
|
|
|
oo12: [0, 0],
|
|
|
|
oo13: [0, 0],
|
|
|
|
perp1: [10, 100, 0, 0],
|
2023-01-16 07:49:09 -08:00
|
|
|
expectedHealth: 0.95 * (100.0 + 0.8 * 10.0 * baseLotsToQuote),
|
2022-09-29 06:51:09 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
testFixture({
|
2023-01-16 07:49:09 -08:00
|
|
|
name: '5: perp health',
|
2022-09-29 06:51:09 -07:00
|
|
|
token1: 0,
|
|
|
|
token2: 0,
|
|
|
|
token3: 0,
|
|
|
|
oo12: [0, 0],
|
|
|
|
oo13: [0, 0],
|
|
|
|
perp1: [30, -100, 0, 0],
|
2023-01-16 07:49:09 -08:00
|
|
|
expectedHealth: 0.95 * (-100.0 + 0.8 * 30.0 * baseLotsToQuote),
|
2022-09-29 06:51:09 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
testFixture({
|
|
|
|
name: '6, reserved oo funds',
|
|
|
|
token1: -100,
|
|
|
|
token2: -10,
|
|
|
|
token3: -10,
|
|
|
|
oo12: [1, 1],
|
|
|
|
oo13: [1, 1],
|
2023-01-16 07:49:09 -08:00
|
|
|
perp1: [0, 0, 0, 0],
|
2022-09-29 06:51:09 -07:00
|
|
|
expectedHealth:
|
|
|
|
// tokens
|
|
|
|
-100.0 * 1.2 -
|
|
|
|
10.0 * 5.0 * 1.5 -
|
|
|
|
10.0 * 10.0 * 1.5 +
|
|
|
|
// oo_1_2 (-> token1)
|
|
|
|
(1.0 + 5.0) * 1.2 +
|
|
|
|
// oo_1_3 (-> token1)
|
|
|
|
(1.0 + 10.0) * 1.2,
|
|
|
|
});
|
|
|
|
|
|
|
|
testFixture({
|
|
|
|
name: '7, reserved oo funds cross the zero balance level',
|
|
|
|
token1: -14,
|
|
|
|
token2: -10,
|
|
|
|
token3: -10,
|
|
|
|
oo12: [1, 1],
|
|
|
|
oo13: [1, 1],
|
|
|
|
perp1: [0, 0, 0, 0],
|
|
|
|
expectedHealth:
|
|
|
|
-14.0 * 1.2 -
|
|
|
|
10.0 * 5.0 * 1.5 -
|
|
|
|
10.0 * 10.0 * 1.5 +
|
|
|
|
// oo_1_2 (-> token1)
|
|
|
|
3.0 * 1.2 +
|
|
|
|
3.0 * 0.8 +
|
|
|
|
// oo_1_3 (-> token1)
|
|
|
|
8.0 * 1.2 +
|
|
|
|
3.0 * 0.8,
|
|
|
|
});
|
|
|
|
|
|
|
|
testFixture({
|
|
|
|
name: '8, reserved oo funds in a non-quote currency',
|
|
|
|
token1: -100,
|
|
|
|
token2: -100,
|
|
|
|
token3: -1,
|
|
|
|
oo12: [0, 0],
|
|
|
|
oo13: [10, 1],
|
|
|
|
perp1: [0, 0, 0, 0],
|
|
|
|
expectedHealth:
|
|
|
|
// tokens
|
|
|
|
-100.0 * 1.2 -
|
|
|
|
100.0 * 5.0 * 1.5 -
|
|
|
|
10.0 * 1.5 +
|
|
|
|
// oo_1_3 (-> token3)
|
|
|
|
10.0 * 1.5 +
|
|
|
|
10.0 * 0.5,
|
|
|
|
});
|
|
|
|
|
|
|
|
testFixture({
|
|
|
|
name: '9, like 8 but oo_1_2 flips the oo_1_3 target',
|
|
|
|
token1: -100,
|
|
|
|
token2: -100,
|
|
|
|
token3: -1,
|
|
|
|
oo12: [100, 0],
|
|
|
|
oo13: [10, 1],
|
|
|
|
perp1: [0, 0, 0, 0],
|
|
|
|
expectedHealth:
|
|
|
|
// tokens
|
|
|
|
-100.0 * 1.2 -
|
|
|
|
100.0 * 5.0 * 1.5 -
|
|
|
|
10.0 * 1.5 +
|
|
|
|
// oo_1_2 (-> token1)
|
|
|
|
80.0 * 1.2 +
|
|
|
|
20.0 * 0.8 +
|
|
|
|
// oo_1_3 (-> token1)
|
|
|
|
20.0 * 0.8,
|
|
|
|
});
|
|
|
|
|
2022-12-02 06:48:43 -08:00
|
|
|
done();
|
|
|
|
});
|
2022-09-23 02:43:26 -07:00
|
|
|
|
2022-12-02 06:48:43 -08:00
|
|
|
it('test_max_swap', (done) => {
|
2022-12-14 00:21:45 -08:00
|
|
|
const b0 = mockBankAndOracle(0 as TokenIndex, 0.1, 0.1, 2, 2);
|
|
|
|
const b1 = mockBankAndOracle(1 as TokenIndex, 0.2, 0.2, 3, 3);
|
|
|
|
const b2 = mockBankAndOracle(2 as TokenIndex, 0.3, 0.3, 4, 4);
|
2022-12-02 06:48:43 -08:00
|
|
|
const banks = [b0, b1, b2];
|
2022-09-23 02:43:26 -07:00
|
|
|
const hc = new HealthCache(
|
|
|
|
[
|
2022-12-02 06:48:43 -08:00
|
|
|
TokenInfo.fromBank(b0, I80F48.fromNumber(0)),
|
|
|
|
TokenInfo.fromBank(b1, I80F48.fromNumber(0)),
|
|
|
|
TokenInfo.fromBank(b2, I80F48.fromNumber(0)),
|
2022-09-23 02:43:26 -07:00
|
|
|
],
|
|
|
|
[],
|
|
|
|
[],
|
|
|
|
);
|
|
|
|
|
|
|
|
expect(
|
2022-12-02 06:48:43 -08:00
|
|
|
hc
|
2022-12-16 07:33:37 -08:00
|
|
|
.getMaxSwapSourceForHealthRatio(
|
2022-12-02 06:48:43 -08:00
|
|
|
b0,
|
|
|
|
b1,
|
|
|
|
I80F48.fromNumber(2 / 3),
|
|
|
|
I80F48.fromNumber(50),
|
|
|
|
)
|
|
|
|
.toNumber(),
|
|
|
|
).lessThan(0.0000001);
|
|
|
|
|
|
|
|
function findMaxSwapActual(
|
|
|
|
hc: HealthCache,
|
|
|
|
source: TokenIndex,
|
|
|
|
target: TokenIndex,
|
2022-12-16 07:33:37 -08:00
|
|
|
minValue: number,
|
2022-12-02 06:48:43 -08:00
|
|
|
priceFactor: number,
|
2022-12-16 07:33:37 -08:00
|
|
|
maxSwapFn: (HealthCache) => I80F48,
|
|
|
|
): number[] {
|
2023-01-20 05:52:43 -08:00
|
|
|
const clonedHc: HealthCache = cloneDeep(hc);
|
2022-12-02 06:48:43 -08:00
|
|
|
|
|
|
|
const sourcePrice = clonedHc.tokenInfos[source].prices;
|
|
|
|
const targetPrice = clonedHc.tokenInfos[target].prices;
|
|
|
|
const swapPrice = I80F48.fromNumber(priceFactor)
|
|
|
|
.mul(sourcePrice.oracle)
|
|
|
|
.div(targetPrice.oracle);
|
2022-12-16 07:33:37 -08:00
|
|
|
const sourceAmount = clonedHc.getMaxSwapSourceForHealthFn(
|
2022-12-02 06:48:43 -08:00
|
|
|
banks[source],
|
|
|
|
banks[target],
|
|
|
|
swapPrice,
|
2022-12-16 07:33:37 -08:00
|
|
|
I80F48.fromNumber(minValue),
|
|
|
|
maxSwapFn,
|
2022-12-02 06:48:43 -08:00
|
|
|
);
|
|
|
|
|
2022-12-26 23:26:19 -08:00
|
|
|
function valueForAmount(amount: I80F48): I80F48 {
|
2022-12-16 07:33:37 -08:00
|
|
|
// adjust token balance
|
2023-01-20 05:52:43 -08:00
|
|
|
const clonedHcClone: HealthCache = cloneDeep(clonedHc);
|
2022-12-16 07:33:37 -08:00
|
|
|
clonedHc.tokenInfos[source].balanceNative.isub(amount);
|
|
|
|
clonedHc.tokenInfos[target].balanceNative.iadd(amount.mul(swapPrice));
|
|
|
|
return maxSwapFn(clonedHcClone);
|
|
|
|
}
|
2022-12-02 06:48:43 -08:00
|
|
|
|
2022-12-16 07:33:37 -08:00
|
|
|
return [
|
|
|
|
sourceAmount.toNumber(),
|
|
|
|
valueForAmount(sourceAmount).toNumber(),
|
|
|
|
valueForAmount(sourceAmount.sub(ONE_I80F48())).toNumber(),
|
|
|
|
valueForAmount(sourceAmount.add(ONE_I80F48())).toNumber(),
|
|
|
|
];
|
2022-12-02 06:48:43 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
function checkMaxSwapResult(
|
|
|
|
hc: HealthCache,
|
|
|
|
source: TokenIndex,
|
|
|
|
target: TokenIndex,
|
2022-12-16 07:33:37 -08:00
|
|
|
minValue: number,
|
2022-12-02 06:48:43 -08:00
|
|
|
priceFactor: number,
|
2022-12-16 07:33:37 -08:00
|
|
|
maxSwapFn: (HealthCache) => I80F48,
|
2022-12-02 06:48:43 -08:00
|
|
|
): void {
|
2022-12-16 07:33:37 -08:00
|
|
|
const [sourceAmount, actualValue, minusValue, plusValue] =
|
|
|
|
findMaxSwapActual(hc, source, target, minValue, priceFactor, maxSwapFn);
|
2022-12-02 06:48:43 -08:00
|
|
|
console.log(
|
2022-12-16 07:33:37 -08:00
|
|
|
` -- checking ${source} to ${target} for priceFactor: ${priceFactor}, target: ${minValue} actual: ${minusValue}/${actualValue}/${plusValue}, amount: ${sourceAmount}`,
|
2022-12-02 06:48:43 -08:00
|
|
|
);
|
2022-12-16 07:33:37 -08:00
|
|
|
if (actualValue < minValue) {
|
|
|
|
// check that swapping more would decrease the ratio!
|
|
|
|
expect(plusValue < actualValue);
|
|
|
|
} else {
|
|
|
|
expect(actualValue >= minValue);
|
|
|
|
// either we're within tolerance of the target, or swapping 1 more would
|
|
|
|
// bring us below the target
|
|
|
|
expect(actualValue < minValue + 1 || plusValue < minValue);
|
|
|
|
}
|
2022-12-02 06:48:43 -08:00
|
|
|
}
|
|
|
|
|
2022-12-16 07:33:37 -08:00
|
|
|
function maxSwapFnRatio(hc: HealthCache): I80F48 {
|
|
|
|
return hc.healthRatio(HealthType.init);
|
|
|
|
}
|
|
|
|
|
|
|
|
function maxSwapFn(hc: HealthCache): I80F48 {
|
|
|
|
return hc.health(HealthType.init);
|
|
|
|
}
|
2022-12-02 06:48:43 -08:00
|
|
|
|
2022-12-16 07:33:37 -08:00
|
|
|
for (const fn of [maxSwapFn, maxSwapFnRatio]) {
|
|
|
|
{
|
|
|
|
console.log(' - test 0');
|
|
|
|
// adjust by usdc
|
2023-01-20 05:52:43 -08:00
|
|
|
const clonedHc: HealthCache = cloneDeep(hc);
|
2022-12-16 07:33:37 -08:00
|
|
|
clonedHc.tokenInfos[1].balanceNative.iadd(
|
|
|
|
I80F48.fromNumber(100).div(clonedHc.tokenInfos[1].prices.oracle),
|
|
|
|
);
|
|
|
|
|
|
|
|
for (const priceFactor of [0.1, 0.9, 1.1]) {
|
2023-01-20 05:52:43 -08:00
|
|
|
for (const target of range(1, 100, 1)) {
|
2022-12-16 07:33:37 -08:00
|
|
|
checkMaxSwapResult(
|
|
|
|
clonedHc,
|
|
|
|
0 as TokenIndex,
|
|
|
|
1 as TokenIndex,
|
|
|
|
target,
|
|
|
|
priceFactor,
|
|
|
|
fn,
|
|
|
|
);
|
|
|
|
checkMaxSwapResult(
|
|
|
|
clonedHc,
|
|
|
|
1 as TokenIndex,
|
|
|
|
0 as TokenIndex,
|
|
|
|
target,
|
|
|
|
priceFactor,
|
|
|
|
fn,
|
|
|
|
);
|
|
|
|
checkMaxSwapResult(
|
|
|
|
clonedHc,
|
|
|
|
0 as TokenIndex,
|
|
|
|
2 as TokenIndex,
|
|
|
|
target,
|
|
|
|
priceFactor,
|
|
|
|
fn,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// At this unlikely price it's healthy to swap infinitely
|
|
|
|
expect(function () {
|
|
|
|
findMaxSwapActual(
|
2022-12-02 06:48:43 -08:00
|
|
|
clonedHc,
|
2022-12-14 00:21:45 -08:00
|
|
|
0 as TokenIndex,
|
2022-12-02 06:48:43 -08:00
|
|
|
1 as TokenIndex,
|
2022-12-16 07:33:37 -08:00
|
|
|
50.0,
|
|
|
|
1.5,
|
|
|
|
fn,
|
2022-12-02 06:48:43 -08:00
|
|
|
);
|
2022-12-16 07:33:37 -08:00
|
|
|
}).to.throw('Number out of range');
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
console.log(' - test 1');
|
2023-01-20 05:52:43 -08:00
|
|
|
const clonedHc: HealthCache = cloneDeep(hc);
|
2022-12-16 07:33:37 -08:00
|
|
|
// adjust by usdc
|
|
|
|
clonedHc.tokenInfos[0].balanceNative.iadd(
|
|
|
|
I80F48.fromNumber(-20).div(clonedHc.tokenInfos[0].prices.oracle),
|
|
|
|
);
|
|
|
|
clonedHc.tokenInfos[1].balanceNative.iadd(
|
|
|
|
I80F48.fromNumber(100).div(clonedHc.tokenInfos[1].prices.oracle),
|
|
|
|
);
|
|
|
|
|
|
|
|
for (const priceFactor of [0.1, 0.9, 1.1]) {
|
2023-01-20 05:52:43 -08:00
|
|
|
for (const target of range(1, 100, 1)) {
|
2022-12-16 07:33:37 -08:00
|
|
|
checkMaxSwapResult(
|
|
|
|
clonedHc,
|
|
|
|
0 as TokenIndex,
|
|
|
|
1 as TokenIndex,
|
|
|
|
target,
|
|
|
|
priceFactor,
|
|
|
|
fn,
|
|
|
|
);
|
|
|
|
checkMaxSwapResult(
|
|
|
|
clonedHc,
|
|
|
|
1 as TokenIndex,
|
|
|
|
0 as TokenIndex,
|
|
|
|
target,
|
|
|
|
priceFactor,
|
|
|
|
fn,
|
|
|
|
);
|
|
|
|
checkMaxSwapResult(
|
|
|
|
clonedHc,
|
|
|
|
0 as TokenIndex,
|
|
|
|
2 as TokenIndex,
|
|
|
|
target,
|
|
|
|
priceFactor,
|
|
|
|
fn,
|
|
|
|
);
|
|
|
|
checkMaxSwapResult(
|
|
|
|
clonedHc,
|
|
|
|
2 as TokenIndex,
|
|
|
|
0 as TokenIndex,
|
|
|
|
target,
|
|
|
|
priceFactor,
|
|
|
|
fn,
|
|
|
|
);
|
|
|
|
}
|
2022-12-02 06:48:43 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-16 07:33:37 -08:00
|
|
|
{
|
|
|
|
console.log(' - test 2');
|
2023-01-20 05:52:43 -08:00
|
|
|
const clonedHc: HealthCache = cloneDeep(hc);
|
2022-12-16 07:33:37 -08:00
|
|
|
// adjust by usdc
|
|
|
|
clonedHc.tokenInfos[0].balanceNative.iadd(
|
|
|
|
I80F48.fromNumber(-50).div(clonedHc.tokenInfos[0].prices.oracle),
|
|
|
|
);
|
|
|
|
clonedHc.tokenInfos[1].balanceNative.iadd(
|
|
|
|
I80F48.fromNumber(100).div(clonedHc.tokenInfos[1].prices.oracle),
|
|
|
|
);
|
|
|
|
// possible even though the init ratio is <100
|
|
|
|
checkMaxSwapResult(
|
2022-12-02 06:48:43 -08:00
|
|
|
clonedHc,
|
|
|
|
1 as TokenIndex,
|
2022-12-16 07:33:37 -08:00
|
|
|
0 as TokenIndex,
|
|
|
|
100,
|
|
|
|
1,
|
|
|
|
|
|
|
|
maxSwapFn,
|
2022-12-02 06:48:43 -08:00
|
|
|
);
|
2022-12-16 07:33:37 -08:00
|
|
|
}
|
2022-12-02 06:48:43 -08:00
|
|
|
|
2022-12-16 07:33:37 -08:00
|
|
|
{
|
|
|
|
console.log(' - test 3');
|
2023-01-20 05:52:43 -08:00
|
|
|
const clonedHc: HealthCache = cloneDeep(hc);
|
2022-12-16 07:33:37 -08:00
|
|
|
// adjust by usdc
|
|
|
|
clonedHc.tokenInfos[0].balanceNative.iadd(
|
|
|
|
I80F48.fromNumber(-30).div(clonedHc.tokenInfos[0].prices.oracle),
|
|
|
|
);
|
|
|
|
clonedHc.tokenInfos[1].balanceNative.iadd(
|
|
|
|
I80F48.fromNumber(100).div(clonedHc.tokenInfos[1].prices.oracle),
|
|
|
|
);
|
|
|
|
clonedHc.tokenInfos[2].balanceNative.iadd(
|
|
|
|
I80F48.fromNumber(-30).div(clonedHc.tokenInfos[2].prices.oracle),
|
|
|
|
);
|
2022-12-02 06:48:43 -08:00
|
|
|
|
2022-12-16 07:33:37 -08:00
|
|
|
// swapping with a high ratio advises paying back all liabs
|
|
|
|
// and then swapping even more because increasing assets in 0 has better asset weight
|
2022-12-26 23:26:19 -08:00
|
|
|
const initRatio = clonedHc.healthRatio(HealthType.init).toNumber();
|
2022-12-16 07:33:37 -08:00
|
|
|
const [amount, actualRatio] = findMaxSwapActual(
|
|
|
|
clonedHc,
|
|
|
|
1 as TokenIndex,
|
|
|
|
0 as TokenIndex,
|
|
|
|
100,
|
|
|
|
1,
|
|
|
|
maxSwapFn,
|
|
|
|
);
|
|
|
|
expect(actualRatio / 2.0 > initRatio);
|
|
|
|
expect(amount - 100 / 3).lessThan(1);
|
2022-12-02 06:48:43 -08:00
|
|
|
}
|
|
|
|
|
2022-12-16 07:33:37 -08:00
|
|
|
{
|
|
|
|
console.log(' - test 4');
|
2023-01-20 05:52:43 -08:00
|
|
|
const clonedHc: HealthCache = cloneDeep(hc);
|
2022-12-16 07:33:37 -08:00
|
|
|
// adjust by usdc
|
|
|
|
clonedHc.tokenInfos[0].balanceNative.iadd(
|
|
|
|
I80F48.fromNumber(100).div(clonedHc.tokenInfos[0].prices.oracle),
|
|
|
|
);
|
|
|
|
clonedHc.tokenInfos[1].balanceNative.iadd(
|
|
|
|
I80F48.fromNumber(-2).div(clonedHc.tokenInfos[1].prices.oracle),
|
|
|
|
);
|
|
|
|
clonedHc.tokenInfos[2].balanceNative.iadd(
|
|
|
|
I80F48.fromNumber(-65).div(clonedHc.tokenInfos[2].prices.oracle),
|
|
|
|
);
|
2022-12-02 06:48:43 -08:00
|
|
|
|
2022-12-16 07:33:37 -08:00
|
|
|
const initRatio = clonedHc.healthRatio(HealthType.init);
|
|
|
|
expect(initRatio.toNumber()).greaterThan(3);
|
|
|
|
expect(initRatio.toNumber()).lessThan(4);
|
2022-12-02 06:48:43 -08:00
|
|
|
|
2022-12-16 07:33:37 -08:00
|
|
|
checkMaxSwapResult(
|
|
|
|
clonedHc,
|
|
|
|
0 as TokenIndex,
|
|
|
|
1 as TokenIndex,
|
|
|
|
1,
|
|
|
|
1,
|
|
|
|
maxSwapFn,
|
|
|
|
);
|
|
|
|
checkMaxSwapResult(
|
|
|
|
clonedHc,
|
|
|
|
0 as TokenIndex,
|
|
|
|
1 as TokenIndex,
|
|
|
|
3,
|
|
|
|
1,
|
|
|
|
maxSwapFn,
|
|
|
|
);
|
|
|
|
checkMaxSwapResult(
|
|
|
|
clonedHc,
|
|
|
|
0 as TokenIndex,
|
|
|
|
1 as TokenIndex,
|
|
|
|
4,
|
|
|
|
1,
|
|
|
|
maxSwapFn,
|
|
|
|
);
|
|
|
|
}
|
2022-12-02 06:48:43 -08:00
|
|
|
|
2022-12-16 07:33:37 -08:00
|
|
|
// TODO test 5
|
2022-12-02 06:48:43 -08:00
|
|
|
|
2022-12-16 07:33:37 -08:00
|
|
|
{
|
|
|
|
console.log(' - test 6');
|
2023-01-20 05:52:43 -08:00
|
|
|
const clonedHc: HealthCache = cloneDeep(hc);
|
2022-12-16 07:33:37 -08:00
|
|
|
clonedHc.serum3Infos = [
|
|
|
|
new Serum3Info(
|
|
|
|
I80F48.fromNumber(30 / 3),
|
|
|
|
I80F48.fromNumber(30 / 2),
|
|
|
|
1,
|
|
|
|
0,
|
|
|
|
0 as MarketIndex,
|
|
|
|
),
|
|
|
|
];
|
|
|
|
|
|
|
|
// adjust by usdc
|
|
|
|
clonedHc.tokenInfos[0].balanceNative.iadd(
|
|
|
|
I80F48.fromNumber(-20).div(clonedHc.tokenInfos[0].prices.oracle),
|
|
|
|
);
|
|
|
|
clonedHc.tokenInfos[1].balanceNative.iadd(
|
|
|
|
I80F48.fromNumber(-40).div(clonedHc.tokenInfos[1].prices.oracle),
|
|
|
|
);
|
|
|
|
clonedHc.tokenInfos[2].balanceNative.iadd(
|
|
|
|
I80F48.fromNumber(120).div(clonedHc.tokenInfos[2].prices.oracle),
|
|
|
|
);
|
2022-12-02 06:48:43 -08:00
|
|
|
|
2022-12-16 07:33:37 -08:00
|
|
|
for (const priceFactor of [0.9, 1.1]) {
|
2023-01-20 05:52:43 -08:00
|
|
|
for (const target of range(1, 100, 1)) {
|
2022-12-16 07:33:37 -08:00
|
|
|
checkMaxSwapResult(
|
|
|
|
clonedHc,
|
|
|
|
0 as TokenIndex,
|
|
|
|
1 as TokenIndex,
|
|
|
|
target,
|
|
|
|
priceFactor,
|
|
|
|
fn,
|
|
|
|
);
|
|
|
|
checkMaxSwapResult(
|
|
|
|
clonedHc,
|
|
|
|
1 as TokenIndex,
|
|
|
|
0 as TokenIndex,
|
|
|
|
target,
|
|
|
|
priceFactor,
|
|
|
|
fn,
|
|
|
|
);
|
|
|
|
checkMaxSwapResult(
|
|
|
|
clonedHc,
|
|
|
|
0 as TokenIndex,
|
|
|
|
2 as TokenIndex,
|
|
|
|
target,
|
|
|
|
priceFactor,
|
|
|
|
fn,
|
|
|
|
);
|
|
|
|
checkMaxSwapResult(
|
|
|
|
clonedHc,
|
|
|
|
1 as TokenIndex,
|
|
|
|
2 as TokenIndex,
|
|
|
|
target,
|
|
|
|
priceFactor,
|
|
|
|
fn,
|
|
|
|
);
|
|
|
|
checkMaxSwapResult(
|
|
|
|
clonedHc,
|
|
|
|
2 as TokenIndex,
|
|
|
|
0 as TokenIndex,
|
|
|
|
target,
|
|
|
|
priceFactor,
|
|
|
|
fn,
|
|
|
|
);
|
|
|
|
checkMaxSwapResult(
|
|
|
|
clonedHc,
|
|
|
|
2 as TokenIndex,
|
|
|
|
1 as TokenIndex,
|
|
|
|
target,
|
|
|
|
priceFactor,
|
|
|
|
fn,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-12-02 06:48:43 -08:00
|
|
|
|
2022-12-16 07:33:37 -08:00
|
|
|
{
|
|
|
|
// check starting with negative health but swapping can make it positive
|
|
|
|
console.log(' - test 7');
|
2023-01-20 05:52:43 -08:00
|
|
|
const clonedHc: HealthCache = cloneDeep(hc);
|
2022-12-14 00:21:45 -08:00
|
|
|
|
2022-12-16 07:33:37 -08:00
|
|
|
// adjust by usdc
|
|
|
|
clonedHc.tokenInfos[0].balanceNative.iadd(
|
|
|
|
I80F48.fromNumber(-20).div(clonedHc.tokenInfos[0].prices.oracle),
|
|
|
|
);
|
|
|
|
clonedHc.tokenInfos[1].balanceNative.iadd(
|
|
|
|
I80F48.fromNumber(20).div(clonedHc.tokenInfos[1].prices.oracle),
|
|
|
|
);
|
2022-12-26 23:26:19 -08:00
|
|
|
expect(clonedHc.health(HealthType.init).toNumber() < 0);
|
2022-12-16 07:33:37 -08:00
|
|
|
|
|
|
|
for (const priceFactor of [0.9, 1.1]) {
|
2023-01-20 05:52:43 -08:00
|
|
|
for (const target of range(1, 100, 1)) {
|
2022-12-16 07:33:37 -08:00
|
|
|
checkMaxSwapResult(
|
|
|
|
clonedHc,
|
|
|
|
1 as TokenIndex,
|
|
|
|
0 as TokenIndex,
|
|
|
|
target,
|
|
|
|
priceFactor,
|
|
|
|
fn,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-12-14 00:21:45 -08:00
|
|
|
|
2022-12-16 07:33:37 -08:00
|
|
|
{
|
|
|
|
// check starting with negative health but swapping can't make it positive
|
|
|
|
console.log(' - test 8');
|
2023-01-20 05:52:43 -08:00
|
|
|
const clonedHc: HealthCache = cloneDeep(hc);
|
2022-12-14 00:21:45 -08:00
|
|
|
|
2022-12-16 07:33:37 -08:00
|
|
|
// adjust by usdc
|
|
|
|
clonedHc.tokenInfos[0].balanceNative.iadd(
|
|
|
|
I80F48.fromNumber(-20).div(clonedHc.tokenInfos[0].prices.oracle),
|
|
|
|
);
|
|
|
|
clonedHc.tokenInfos[1].balanceNative.iadd(
|
|
|
|
I80F48.fromNumber(10).div(clonedHc.tokenInfos[1].prices.oracle),
|
|
|
|
);
|
2022-12-26 23:26:19 -08:00
|
|
|
expect(clonedHc.health(HealthType.init).toNumber() < 0);
|
2022-12-16 07:33:37 -08:00
|
|
|
|
|
|
|
for (const priceFactor of [0.9, 1.1]) {
|
2023-01-20 05:52:43 -08:00
|
|
|
for (const target of range(1, 100, 1)) {
|
2022-12-16 07:33:37 -08:00
|
|
|
checkMaxSwapResult(
|
|
|
|
clonedHc,
|
|
|
|
1 as TokenIndex,
|
|
|
|
0 as TokenIndex,
|
|
|
|
target,
|
|
|
|
priceFactor,
|
|
|
|
fn,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-12-14 00:21:45 -08:00
|
|
|
|
2022-12-16 07:33:37 -08:00
|
|
|
{
|
|
|
|
// swap some assets into a zero-asset-weight token
|
|
|
|
console.log(' - test 9');
|
2023-01-20 05:52:43 -08:00
|
|
|
const clonedHc: HealthCache = cloneDeep(hc);
|
2022-12-16 07:33:37 -08:00
|
|
|
|
|
|
|
// adjust by usdc
|
|
|
|
clonedHc.tokenInfos[0].balanceNative.iadd(
|
|
|
|
I80F48.fromNumber(10).div(clonedHc.tokenInfos[0].prices.oracle),
|
|
|
|
);
|
|
|
|
clonedHc.tokenInfos[1].initAssetWeight = ZERO_I80F48();
|
|
|
|
expect(
|
|
|
|
findMaxSwapActual(
|
2022-12-14 00:21:45 -08:00
|
|
|
clonedHc,
|
|
|
|
0 as TokenIndex,
|
|
|
|
1 as TokenIndex,
|
2022-12-16 07:33:37 -08:00
|
|
|
1,
|
|
|
|
1,
|
|
|
|
maxSwapFn,
|
|
|
|
)[0] > 0,
|
|
|
|
);
|
|
|
|
|
|
|
|
for (const priceFactor of [0.9, 1.1]) {
|
2023-01-20 05:52:43 -08:00
|
|
|
for (const target of range(1, 100, 1)) {
|
2022-12-16 07:33:37 -08:00
|
|
|
checkMaxSwapResult(
|
|
|
|
clonedHc,
|
|
|
|
0 as TokenIndex,
|
|
|
|
1 as TokenIndex,
|
|
|
|
target,
|
|
|
|
priceFactor,
|
|
|
|
fn,
|
|
|
|
);
|
|
|
|
}
|
2022-12-14 00:21:45 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-12-02 06:48:43 -08:00
|
|
|
done();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('test_max_perp', (done) => {
|
|
|
|
const baseLotSize = 100;
|
2022-12-14 00:21:45 -08:00
|
|
|
const b0 = mockBankAndOracle(0 as TokenIndex, 0.0, 0.0, 1, 1);
|
2022-12-02 06:48:43 -08:00
|
|
|
const p0 = mockPerpMarket(0, 0.3, 0.3, baseLotSize, 2);
|
|
|
|
const hc = new HealthCache(
|
|
|
|
[TokenInfo.fromBank(b0, I80F48.fromNumber(0))],
|
|
|
|
[],
|
|
|
|
[PerpInfo.emptyFromPerpMarket(p0)],
|
|
|
|
);
|
|
|
|
|
|
|
|
expect(hc.health(HealthType.init).toNumber()).equals(0);
|
2022-09-23 02:43:26 -07:00
|
|
|
|
|
|
|
expect(
|
2022-12-02 06:48:43 -08:00
|
|
|
hc
|
|
|
|
.getMaxPerpForHealthRatio(
|
|
|
|
p0,
|
|
|
|
I80F48.fromNumber(2),
|
|
|
|
PerpOrderSide.bid,
|
|
|
|
I80F48.fromNumber(50),
|
|
|
|
)
|
|
|
|
.toNumber(),
|
|
|
|
).equals(0);
|
|
|
|
|
|
|
|
function findMaxTrade(
|
|
|
|
hc: HealthCache,
|
|
|
|
side: PerpOrderSide,
|
|
|
|
ratio: number,
|
|
|
|
priceFactor: number,
|
|
|
|
): number[] {
|
|
|
|
const prices = hc.perpInfos[0].prices;
|
|
|
|
const tradePrice = I80F48.fromNumber(priceFactor).mul(prices.oracle);
|
|
|
|
const baseLots0 = hc
|
|
|
|
.getMaxPerpForHealthRatio(
|
|
|
|
p0,
|
|
|
|
tradePrice,
|
|
|
|
side,
|
|
|
|
I80F48.fromNumber(ratio),
|
|
|
|
)
|
|
|
|
.toNumber();
|
|
|
|
|
|
|
|
const direction = side == PerpOrderSide.bid ? 1 : -1;
|
|
|
|
|
|
|
|
// compute the health ratio we'd get when executing the trade
|
|
|
|
const baseLots1 = direction * baseLots0;
|
|
|
|
let baseNative = I80F48.fromNumber(baseLots1).mul(
|
|
|
|
I80F48.fromNumber(baseLotSize),
|
|
|
|
);
|
2023-01-20 05:52:43 -08:00
|
|
|
let hcClone: HealthCache = cloneDeep(hc);
|
2022-12-02 06:48:43 -08:00
|
|
|
hcClone.perpInfos[0].baseLots.iadd(new BN(baseLots1));
|
|
|
|
hcClone.perpInfos[0].quote.isub(baseNative.mul(tradePrice));
|
|
|
|
const actualRatio = hcClone.healthRatio(HealthType.init);
|
|
|
|
|
|
|
|
// the ratio for trading just one base lot extra
|
|
|
|
const baseLots2 = direction * (baseLots0 + 1);
|
|
|
|
baseNative = I80F48.fromNumber(baseLots2 * baseLotSize);
|
2023-01-20 05:52:43 -08:00
|
|
|
hcClone = cloneDeep(hc);
|
2022-12-02 06:48:43 -08:00
|
|
|
hcClone.perpInfos[0].baseLots.iadd(new BN(baseLots2));
|
|
|
|
hcClone.perpInfos[0].quote.isub(baseNative.mul(tradePrice));
|
|
|
|
const plusRatio = hcClone.healthRatio(HealthType.init);
|
|
|
|
|
|
|
|
return [baseLots0, actualRatio.toNumber(), plusRatio.toNumber()];
|
|
|
|
}
|
|
|
|
|
|
|
|
function checkMaxTrade(
|
|
|
|
hc: HealthCache,
|
|
|
|
side: PerpOrderSide,
|
|
|
|
ratio: number,
|
|
|
|
priceFactor: number,
|
|
|
|
): void {
|
|
|
|
const [baseLots, actualRatio, plusRatio] = findMaxTrade(
|
|
|
|
hc,
|
|
|
|
side,
|
|
|
|
ratio,
|
|
|
|
priceFactor,
|
|
|
|
);
|
|
|
|
console.log(
|
|
|
|
`checking for price_factor: ${priceFactor}, target ratio ${ratio}: actual ratio: ${actualRatio}, plus ratio: ${plusRatio}, base_lots: ${baseLots}`,
|
|
|
|
);
|
|
|
|
expect(ratio).lessThan(actualRatio);
|
|
|
|
expect(plusRatio - 0.1).lessThanOrEqual(ratio);
|
|
|
|
}
|
|
|
|
|
|
|
|
// adjust token
|
|
|
|
hc.tokenInfos[0].balanceNative.iadd(I80F48.fromNumber(3000));
|
|
|
|
for (const existing of [-5, 0, 3]) {
|
2023-01-20 05:52:43 -08:00
|
|
|
const hcClone: HealthCache = cloneDeep(hc);
|
2022-12-02 06:48:43 -08:00
|
|
|
hcClone.perpInfos[0].baseLots.iadd(new BN(existing));
|
|
|
|
hcClone.perpInfos[0].quote.isub(
|
|
|
|
I80F48.fromNumber(existing * baseLotSize * 2),
|
|
|
|
);
|
|
|
|
for (const side of [PerpOrderSide.bid, PerpOrderSide.ask]) {
|
|
|
|
console.log(
|
|
|
|
`existing ${existing} ${side === PerpOrderSide.bid ? 'bid' : 'ask'}`,
|
|
|
|
);
|
|
|
|
for (const priceFactor of [0.8, 1.0, 1.1]) {
|
2023-01-20 05:52:43 -08:00
|
|
|
for (const ratio of range(1, 101, 1)) {
|
2022-12-02 06:48:43 -08:00
|
|
|
checkMaxTrade(hcClone, side, ratio, priceFactor);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// check some extremely bad prices
|
|
|
|
checkMaxTrade(hc, PerpOrderSide.bid, 50, 2);
|
|
|
|
checkMaxTrade(hc, PerpOrderSide.ask, 50, 0.1);
|
|
|
|
|
|
|
|
// and extremely good prices
|
|
|
|
expect(function () {
|
|
|
|
findMaxTrade(hc, PerpOrderSide.bid, 50, 0.1);
|
|
|
|
}).to.throw();
|
|
|
|
expect(function () {
|
|
|
|
findMaxTrade(hc, PerpOrderSide.ask, 50, 1.5);
|
|
|
|
}).to.throw();
|
|
|
|
|
|
|
|
done();
|
2022-09-23 02:43:26 -07:00
|
|
|
});
|
|
|
|
});
|