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:
parent
8b4a4c82fb
commit
bb35aa66dc
|
@ -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]),
|
||||
);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
|
|
Loading…
Reference in New Issue