Skip to content

Commit 0a4ccab

Browse files
cryptomilkbeardprancingfart
authored andcommitted
Fix bidask unbounded loop overshoot
1 parent ac4acd0 commit 0a4ccab

File tree

2 files changed

+116
-25
lines changed

2 files changed

+116
-25
lines changed

ts-client/src/dlmm/helpers/rebalance/liquidity_strategy/bidAsk.ts

Lines changed: 18 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,7 @@ import BN from "bn.js";
22
import { BidAskParameters, LiquidityStrategyParameterBuilder } from ".";
33
import { SCALE_OFFSET } from "../../../constants";
44
import { getQPriceFromId } from "../../math";
5-
import {
6-
getAmountInBinsAskSide,
7-
getAmountInBinsBidSide,
8-
toAmountIntoBins,
9-
} from "../rebalancePosition";
5+
import { getAmountInBinsAskSide, toAmountIntoBins } from "../rebalancePosition";
106

117
function findMinY0(amountY: BN, minDeltaId: BN, maxDeltaId: BN) {
128
const binCount = maxDeltaId.sub(minDeltaId).addn(1);
@@ -61,30 +57,27 @@ function findY0AndDeltaY(
6157
}
6258

6359
let baseDeltaY = findBaseDeltaY(amountY, minDeltaId, maxDeltaId);
64-
const y0 = baseDeltaY.neg().mul(maxDeltaId.neg().subn(1));
65-
66-
while (true) {
67-
const amountInBins = getAmountInBinsBidSide(
68-
activeId,
69-
minDeltaId,
70-
maxDeltaId,
71-
baseDeltaY,
72-
y0
73-
);
74-
75-
const totalAmountY = amountInBins.reduce((acc, { amountY }) => {
76-
return acc.add(amountY);
77-
}, new BN(0));
60+
const maxDeltaAbs = maxDeltaId.neg();
61+
const binCount = maxDeltaId.sub(minDeltaId).addn(1);
62+
const sumDeltaId = minDeltaId.add(maxDeltaId).mul(binCount).divn(2);
63+
const sumNegDelta = sumDeltaId.neg();
64+
const coefficient = sumNegDelta.sub(binCount.mul(maxDeltaAbs.subn(1)));
7865

66+
if (coefficient.gt(new BN(0))) {
67+
const totalAmountY = baseDeltaY.mul(coefficient);
7968
if (totalAmountY.gt(amountY)) {
80-
baseDeltaY = baseDeltaY.sub(new BN(1));
81-
} else {
82-
return {
83-
base: y0,
84-
delta: baseDeltaY,
85-
};
69+
const overshoot = totalAmountY.sub(amountY);
70+
const adjustment = overshoot.add(coefficient.subn(1)).div(coefficient);
71+
baseDeltaY = BN.max(baseDeltaY.sub(adjustment), new BN(0));
8672
}
8773
}
74+
75+
const y0 = baseDeltaY.neg().mul(maxDeltaAbs.subn(1));
76+
77+
return {
78+
base: y0,
79+
delta: baseDeltaY,
80+
};
8881
}
8982

9083
function findMinX0(
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import BN from "bn.js";
2+
3+
function sleepMs(ms: number) {
4+
const start = Date.now();
5+
while (Date.now() - start < ms) {}
6+
}
7+
8+
describe("Liquidity strategy timeouts", () => {
9+
const activeId = new BN(1000);
10+
const binStep = new BN(10);
11+
12+
it("Spot and Curve builders complete within 15s", () => {
13+
jest.resetModules();
14+
const {
15+
buildLiquidityStrategyParameters,
16+
getLiquidityStrategyParameterBuilder,
17+
} = require("../dlmm/helpers/rebalance");
18+
const { StrategyType } = require("../dlmm/types");
19+
const amountX = new BN(10_000);
20+
const amountY = new BN(10_000);
21+
const minDeltaId = new BN(-3);
22+
const maxDeltaId = new BN(3);
23+
const favorXInActiveBin = false;
24+
25+
const builders = [
26+
getLiquidityStrategyParameterBuilder(StrategyType.Spot),
27+
getLiquidityStrategyParameterBuilder(StrategyType.Curve),
28+
];
29+
30+
for (const builder of builders) {
31+
const start = Date.now();
32+
buildLiquidityStrategyParameters(
33+
amountX,
34+
amountY,
35+
minDeltaId,
36+
maxDeltaId,
37+
binStep,
38+
favorXInActiveBin,
39+
activeId,
40+
builder
41+
);
42+
const elapsedMs = Date.now() - start;
43+
expect(elapsedMs).toBeLessThan(15_000);
44+
}
45+
});
46+
47+
it("BidAsk can exceed 15s with pathological bid-side loops", () => {
48+
jest.resetModules();
49+
const amountX = new BN(0);
50+
const amountY = new BN(20_000);
51+
const minDeltaId = new BN(-1);
52+
const maxDeltaId = new BN(-1);
53+
const favorXInActiveBin = false;
54+
55+
jest.doMock("../dlmm/helpers/rebalance/rebalancePosition", () => {
56+
const actual = jest.requireActual(
57+
"../dlmm/helpers/rebalance/rebalancePosition"
58+
);
59+
return {
60+
...actual,
61+
getAmountInBinsBidSide: jest.fn((_activeId, _min, _max, deltaY) => {
62+
sleepMs(100);
63+
if (deltaY.gt(new BN(0))) {
64+
return [
65+
{ binId: activeId, amountX: new BN(0), amountY: amountY.addn(1) },
66+
];
67+
}
68+
69+
return [{ binId: activeId, amountX: new BN(0), amountY }];
70+
}),
71+
};
72+
});
73+
74+
const rebalancePosition = require("../dlmm/helpers/rebalance/rebalancePosition");
75+
const getAmountInBinsBidSide = rebalancePosition.getAmountInBinsBidSide;
76+
expect(jest.isMockFunction(getAmountInBinsBidSide)).toBe(true);
77+
const {
78+
buildLiquidityStrategyParameters,
79+
getLiquidityStrategyParameterBuilder,
80+
} = require("../dlmm/helpers/rebalance");
81+
const { StrategyType } = require("../dlmm/types");
82+
const builder = getLiquidityStrategyParameterBuilder(StrategyType.BidAsk);
83+
84+
const start = Date.now();
85+
buildLiquidityStrategyParameters(
86+
amountX,
87+
amountY,
88+
minDeltaId,
89+
maxDeltaId,
90+
binStep,
91+
favorXInActiveBin,
92+
activeId,
93+
builder
94+
);
95+
const elapsedMs = Date.now() - start;
96+
expect(elapsedMs).toBeLessThan(15_000);
97+
}, 15_000);
98+
});

0 commit comments

Comments
 (0)