ensure that freshly fetched objects have valid prices until we fetch … (#409)

* ensure that freshly fetched objects have valid prices until we fetch oracles again

Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>

* fix

Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>

* fixes from review

Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>

Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>
This commit is contained in:
microwavedcola1 2023-01-20 14:52:43 +01:00 committed by GitHub
parent 8b4a4c82fb
commit bb35aa66dc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 55 additions and 33 deletions

View File

@ -8,6 +8,7 @@ import {
PublicKey,
} from '@solana/web3.js';
import BN from 'bn.js';
import { cloneDeep, merge } from 'lodash';
import { MangoClient } from '../client';
import { OPENBOOK_PROGRAM_ID } from '../constants';
import { Id } from '../ids';
@ -149,10 +150,17 @@ export class Group {
banks = await client.getBanksForGroup(this);
}
const oldbanksMapByTokenIndex = cloneDeep(this.banksMapByTokenIndex);
this.banksMapByName = new Map();
this.banksMapByMint = new Map();
this.banksMapByTokenIndex = new Map();
for (const bank of banks) {
// ensure that freshly fetched banks have valid price until we fetch oracles again
const oldBanks = oldbanksMapByTokenIndex.get(bank.tokenIndex);
if (oldBanks && oldBanks.length > 0) {
merge(bank, oldBanks[0]);
}
const mintId = bank.mint.toString();
if (this.banksMapByMint.has(mintId)) {
this.banksMapByMint.get(mintId)?.push(bank);
@ -258,6 +266,19 @@ export class Group {
perpMarkets = await client.perpGetMarkets(this);
}
// ensure that freshly fetched perp markets have valid price until we fetch oracles again
const oldPerpMarketByMarketIndex = cloneDeep(
this.perpMarketsMapByMarketIndex,
);
for (const perpMarket of perpMarkets) {
const oldPerpMarket = oldPerpMarketByMarketIndex.get(
perpMarket.perpMarketIndex,
);
if (oldPerpMarket) {
merge(perpMarket, oldPerpMarket);
}
}
this.perpMarketsMapByName = new Map(
perpMarkets.map((perpMarket) => [perpMarket.name, perpMarket]),
);

View File

@ -1,7 +1,8 @@
import { BN } from '@project-serum/anchor';
import { OpenOrders } from '@project-serum/serum';
import { expect } from 'chai';
import _ from 'lodash';
import { cloneDeep, range } from 'lodash';
import { I80F48, ONE_I80F48, ZERO_I80F48 } from '../numbers/I80F48';
import { BankForHealth, StablePriceModel, TokenIndex } from './bank';
import { HealthCache, PerpInfo, Serum3Info, TokenInfo } from './healthCache';
@ -439,7 +440,7 @@ describe('Health Cache', () => {
priceFactor: number,
maxSwapFn: (HealthCache) => I80F48,
): number[] {
const clonedHc: HealthCache = _.cloneDeep(hc);
const clonedHc: HealthCache = cloneDeep(hc);
const sourcePrice = clonedHc.tokenInfos[source].prices;
const targetPrice = clonedHc.tokenInfos[target].prices;
@ -456,7 +457,7 @@ describe('Health Cache', () => {
function valueForAmount(amount: I80F48): I80F48 {
// adjust token balance
const clonedHcClone: HealthCache = _.cloneDeep(clonedHc);
const clonedHcClone: HealthCache = cloneDeep(clonedHc);
clonedHc.tokenInfos[source].balanceNative.isub(amount);
clonedHc.tokenInfos[target].balanceNative.iadd(amount.mul(swapPrice));
return maxSwapFn(clonedHcClone);
@ -506,13 +507,13 @@ describe('Health Cache', () => {
{
console.log(' - test 0');
// adjust by usdc
const clonedHc: HealthCache = _.cloneDeep(hc);
const clonedHc: HealthCache = cloneDeep(hc);
clonedHc.tokenInfos[1].balanceNative.iadd(
I80F48.fromNumber(100).div(clonedHc.tokenInfos[1].prices.oracle),
);
for (const priceFactor of [0.1, 0.9, 1.1]) {
for (const target of _.range(1, 100, 1)) {
for (const target of range(1, 100, 1)) {
checkMaxSwapResult(
clonedHc,
0 as TokenIndex,
@ -555,7 +556,7 @@ describe('Health Cache', () => {
{
console.log(' - test 1');
const clonedHc: HealthCache = _.cloneDeep(hc);
const clonedHc: HealthCache = cloneDeep(hc);
// adjust by usdc
clonedHc.tokenInfos[0].balanceNative.iadd(
I80F48.fromNumber(-20).div(clonedHc.tokenInfos[0].prices.oracle),
@ -565,7 +566,7 @@ describe('Health Cache', () => {
);
for (const priceFactor of [0.1, 0.9, 1.1]) {
for (const target of _.range(1, 100, 1)) {
for (const target of range(1, 100, 1)) {
checkMaxSwapResult(
clonedHc,
0 as TokenIndex,
@ -604,7 +605,7 @@ describe('Health Cache', () => {
{
console.log(' - test 2');
const clonedHc: HealthCache = _.cloneDeep(hc);
const clonedHc: HealthCache = cloneDeep(hc);
// adjust by usdc
clonedHc.tokenInfos[0].balanceNative.iadd(
I80F48.fromNumber(-50).div(clonedHc.tokenInfos[0].prices.oracle),
@ -626,7 +627,7 @@ describe('Health Cache', () => {
{
console.log(' - test 3');
const clonedHc: HealthCache = _.cloneDeep(hc);
const clonedHc: HealthCache = cloneDeep(hc);
// adjust by usdc
clonedHc.tokenInfos[0].balanceNative.iadd(
I80F48.fromNumber(-30).div(clonedHc.tokenInfos[0].prices.oracle),
@ -655,7 +656,7 @@ describe('Health Cache', () => {
{
console.log(' - test 4');
const clonedHc: HealthCache = _.cloneDeep(hc);
const clonedHc: HealthCache = cloneDeep(hc);
// adjust by usdc
clonedHc.tokenInfos[0].balanceNative.iadd(
I80F48.fromNumber(100).div(clonedHc.tokenInfos[0].prices.oracle),
@ -701,7 +702,7 @@ describe('Health Cache', () => {
{
console.log(' - test 6');
const clonedHc: HealthCache = _.cloneDeep(hc);
const clonedHc: HealthCache = cloneDeep(hc);
clonedHc.serum3Infos = [
new Serum3Info(
I80F48.fromNumber(30 / 3),
@ -724,7 +725,7 @@ describe('Health Cache', () => {
);
for (const priceFactor of [0.9, 1.1]) {
for (const target of _.range(1, 100, 1)) {
for (const target of range(1, 100, 1)) {
checkMaxSwapResult(
clonedHc,
0 as TokenIndex,
@ -780,7 +781,7 @@ describe('Health Cache', () => {
{
// check starting with negative health but swapping can make it positive
console.log(' - test 7');
const clonedHc: HealthCache = _.cloneDeep(hc);
const clonedHc: HealthCache = cloneDeep(hc);
// adjust by usdc
clonedHc.tokenInfos[0].balanceNative.iadd(
@ -792,7 +793,7 @@ describe('Health Cache', () => {
expect(clonedHc.health(HealthType.init).toNumber() < 0);
for (const priceFactor of [0.9, 1.1]) {
for (const target of _.range(1, 100, 1)) {
for (const target of range(1, 100, 1)) {
checkMaxSwapResult(
clonedHc,
1 as TokenIndex,
@ -808,7 +809,7 @@ describe('Health Cache', () => {
{
// check starting with negative health but swapping can't make it positive
console.log(' - test 8');
const clonedHc: HealthCache = _.cloneDeep(hc);
const clonedHc: HealthCache = cloneDeep(hc);
// adjust by usdc
clonedHc.tokenInfos[0].balanceNative.iadd(
@ -820,7 +821,7 @@ describe('Health Cache', () => {
expect(clonedHc.health(HealthType.init).toNumber() < 0);
for (const priceFactor of [0.9, 1.1]) {
for (const target of _.range(1, 100, 1)) {
for (const target of range(1, 100, 1)) {
checkMaxSwapResult(
clonedHc,
1 as TokenIndex,
@ -836,7 +837,7 @@ describe('Health Cache', () => {
{
// swap some assets into a zero-asset-weight token
console.log(' - test 9');
const clonedHc: HealthCache = _.cloneDeep(hc);
const clonedHc: HealthCache = cloneDeep(hc);
// adjust by usdc
clonedHc.tokenInfos[0].balanceNative.iadd(
@ -855,7 +856,7 @@ describe('Health Cache', () => {
);
for (const priceFactor of [0.9, 1.1]) {
for (const target of _.range(1, 100, 1)) {
for (const target of range(1, 100, 1)) {
checkMaxSwapResult(
clonedHc,
0 as TokenIndex,
@ -918,7 +919,7 @@ describe('Health Cache', () => {
let baseNative = I80F48.fromNumber(baseLots1).mul(
I80F48.fromNumber(baseLotSize),
);
let hcClone: HealthCache = _.cloneDeep(hc);
let hcClone: HealthCache = cloneDeep(hc);
hcClone.perpInfos[0].baseLots.iadd(new BN(baseLots1));
hcClone.perpInfos[0].quote.isub(baseNative.mul(tradePrice));
const actualRatio = hcClone.healthRatio(HealthType.init);
@ -926,7 +927,7 @@ describe('Health Cache', () => {
// the ratio for trading just one base lot extra
const baseLots2 = direction * (baseLots0 + 1);
baseNative = I80F48.fromNumber(baseLots2 * baseLotSize);
hcClone = _.cloneDeep(hc);
hcClone = cloneDeep(hc);
hcClone.perpInfos[0].baseLots.iadd(new BN(baseLots2));
hcClone.perpInfos[0].quote.isub(baseNative.mul(tradePrice));
const plusRatio = hcClone.healthRatio(HealthType.init);
@ -956,7 +957,7 @@ describe('Health Cache', () => {
// adjust token
hc.tokenInfos[0].balanceNative.iadd(I80F48.fromNumber(3000));
for (const existing of [-5, 0, 3]) {
const hcClone: HealthCache = _.cloneDeep(hc);
const hcClone: HealthCache = cloneDeep(hc);
hcClone.perpInfos[0].baseLots.iadd(new BN(existing));
hcClone.perpInfos[0].quote.isub(
I80F48.fromNumber(existing * baseLotSize * 2),
@ -966,7 +967,7 @@ describe('Health Cache', () => {
`existing ${existing} ${side === PerpOrderSide.bid ? 'bid' : 'ask'}`,
);
for (const priceFactor of [0.8, 1.0, 1.1]) {
for (const ratio of _.range(1, 101, 1)) {
for (const ratio of range(1, 101, 1)) {
checkMaxTrade(hcClone, side, ratio, priceFactor);
}
}

View File

@ -1,7 +1,7 @@
import { BN } from '@project-serum/anchor';
import { OpenOrders } from '@project-serum/serum';
import { PublicKey } from '@solana/web3.js';
import _ from 'lodash';
import { cloneDeep } from 'lodash';
import {
HUNDRED_I80F48,
I80F48,
@ -346,7 +346,7 @@ export class HealthCache {
}[],
healthType: HealthType = HealthType.init,
): I80F48 {
const adjustedCache: HealthCache = _.cloneDeep(this);
const adjustedCache: HealthCache = cloneDeep(this);
// HealthCache.logHealthCache('beforeChange', adjustedCache);
for (const change of nativeTokenChanges) {
const bank: Bank = group.getFirstBankByMint(change.mintPk);
@ -423,7 +423,7 @@ export class HealthCache {
serum3Market: Serum3Market,
healthType: HealthType = HealthType.init,
): I80F48 {
const adjustedCache: HealthCache = _.cloneDeep(this);
const adjustedCache: HealthCache = cloneDeep(this);
const quoteIndex = adjustedCache.getOrCreateTokenInfoIndex(quoteBank);
// Move token balance to reserved funds in open orders,
@ -454,7 +454,7 @@ export class HealthCache {
serum3Market: Serum3Market,
healthType: HealthType = HealthType.init,
): I80F48 {
const adjustedCache: HealthCache = _.cloneDeep(this);
const adjustedCache: HealthCache = cloneDeep(this);
const baseIndex = adjustedCache.getOrCreateTokenInfoIndex(baseBank);
// Move token balance to reserved funds in open orders,
@ -521,7 +521,7 @@ export class HealthCache {
price: I80F48,
healthType: HealthType = HealthType.init,
): I80F48 {
const clonedHealthCache: HealthCache = _.cloneDeep(this);
const clonedHealthCache: HealthCache = cloneDeep(this);
const perpInfoIndex =
clonedHealthCache.getOrCreatePerpInfoIndex(perpMarket);
clonedHealthCache.adjustPerpInfo(perpInfoIndex, price, side, baseLots);
@ -781,7 +781,7 @@ export class HealthCache {
const initialRatio = this.healthRatio(HealthType.init);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const healthCacheClone: HealthCache = _.cloneDeep(this);
const healthCacheClone: HealthCache = cloneDeep(this);
const sourceIndex = healthCacheClone.getOrCreateTokenInfoIndex(sourceBank);
const targetIndex = healthCacheClone.getOrCreateTokenInfoIndex(targetBank);
@ -815,7 +815,7 @@ export class HealthCache {
// The maximum will be at one of these points (ignoring serum3 effects).
function cacheAfterSwap(amount: I80F48): HealthCache {
const adjustedCache: HealthCache = _.cloneDeep(healthCacheClone);
const adjustedCache: HealthCache = cloneDeep(healthCacheClone);
// adjustedCache.logHealthCache('beforeSwap', adjustedCache);
// TODO: make a copy of the bank, apply amount, recompute weights,
// and set the new weights on the tokenInfos
@ -912,7 +912,7 @@ export class HealthCache {
side: Serum3Side,
minRatio: I80F48,
): I80F48 {
const healthCacheClone: HealthCache = _.cloneDeep(this);
const healthCacheClone: HealthCache = cloneDeep(this);
const baseIndex = healthCacheClone.getOrCreateTokenInfoIndex(baseBank);
const quoteIndex = healthCacheClone.getOrCreateTokenInfoIndex(quoteBank);
@ -987,7 +987,7 @@ export class HealthCache {
// console.log(` - zeroAmountRatio ${zeroAmountRatio.toLocaleString()}`);
function cacheAfterPlacingOrder(amount: I80F48): HealthCache {
const adjustedCache: HealthCache = _.cloneDeep(healthCacheClone);
const adjustedCache: HealthCache = cloneDeep(healthCacheClone);
// adjustedCache.logHealthCache(` before placing order ${amount}`);
// TODO: there should also be some issue with oracle vs stable price here;
// probably better to pass in not the quote amount but the base or quote native amount
@ -1038,7 +1038,7 @@ export class HealthCache {
side: PerpOrderSide,
minRatio: I80F48,
): I80F48 {
const healthCacheClone: HealthCache = _.cloneDeep(this);
const healthCacheClone: HealthCache = cloneDeep(this);
const initialRatio = this.healthRatio(HealthType.init);
if (initialRatio.lt(ZERO_I80F48())) {
@ -1066,7 +1066,7 @@ export class HealthCache {
}
function cacheAfterTrade(baseLots: BN): HealthCache {
const adjustedCache: HealthCache = _.cloneDeep(healthCacheClone);
const adjustedCache: HealthCache = cloneDeep(healthCacheClone);
// adjustedCache.logHealthCache(' -- before trade');
adjustedCache.adjustPerpInfo(perpInfoIndex, price, side, baseLots);
// adjustedCache.logHealthCache(' -- after trade');