Refactor price calculation in MakerPointsEngine for improved accuracy

- Replaced direct price calculations with a new method `normalizeDepthTargetPrice` to ensure valid target prices for buy and sell orders.
- Updated all instances of price calculations in the MakerPointsEngine to utilize the new normalization method.
- Added boundary tests for `getDepthBetweenPrices` to validate behavior when prices are exactly at the target.
This commit is contained in:
discountry
2026-02-07 23:27:42 +08:00
parent b8942c18e6
commit 6998afebb1
2 changed files with 69 additions and 22 deletions
+32 -22
View File
@@ -772,17 +772,17 @@ export class MakerPointsEngine {
const shouldCheckDepth = minDepth > 0; const shouldCheckDepth = minDepth > 0;
if (!skipBuy) { if (!skipBuy) {
const price = bid1 * (1 - bps / 10000); const targetPrice = this.normalizeDepthTargetPrice(bid1 * (1 - bps / 10000), priceDecimals);
if (Number.isFinite(price) && price > 0) { if (targetPrice != null) {
if (shouldCheckDepth) { if (shouldCheckDepth) {
const depthQty = getDepthBetweenPrices(depth, "BUY", price); const depthQty = getDepthBetweenPrices(depth, "BUY", targetPrice);
if (depthQty < minDepth) { if (depthQty < minDepth) {
this.logThinDepthSkip("BUY", bps, depthQty, minDepth); this.logThinDepthSkip("BUY", bps, depthQty, minDepth);
} else { } else {
this.resetThinDepthSkip("BUY", bps); this.resetThinDepthSkip("BUY", bps);
desired.push({ desired.push({
side: "BUY", side: "BUY",
price: formatPriceToString(price, priceDecimals), price: formatPriceToString(targetPrice, priceDecimals),
amount, amount,
reduceOnly: false, reduceOnly: false,
}); });
@@ -790,7 +790,7 @@ export class MakerPointsEngine {
} else { } else {
desired.push({ desired.push({
side: "BUY", side: "BUY",
price: formatPriceToString(price, priceDecimals), price: formatPriceToString(targetPrice, priceDecimals),
amount, amount,
reduceOnly: false, reduceOnly: false,
}); });
@@ -798,17 +798,17 @@ export class MakerPointsEngine {
} }
} }
if (!skipSell) { if (!skipSell) {
const price = ask1 * (1 + bps / 10000); const targetPrice = this.normalizeDepthTargetPrice(ask1 * (1 + bps / 10000), priceDecimals);
if (Number.isFinite(price) && price > 0) { if (targetPrice != null) {
if (shouldCheckDepth) { if (shouldCheckDepth) {
const depthQty = getDepthBetweenPrices(depth, "SELL", price); const depthQty = getDepthBetweenPrices(depth, "SELL", targetPrice);
if (depthQty < minDepth) { if (depthQty < minDepth) {
this.logThinDepthSkip("SELL", bps, depthQty, minDepth); this.logThinDepthSkip("SELL", bps, depthQty, minDepth);
} else { } else {
this.resetThinDepthSkip("SELL", bps); this.resetThinDepthSkip("SELL", bps);
desired.push({ desired.push({
side: "SELL", side: "SELL",
price: formatPriceToString(price, priceDecimals), price: formatPriceToString(targetPrice, priceDecimals),
amount, amount,
reduceOnly: false, reduceOnly: false,
}); });
@@ -816,7 +816,7 @@ export class MakerPointsEngine {
} else { } else {
desired.push({ desired.push({
side: "SELL", side: "SELL",
price: formatPriceToString(price, priceDecimals), price: formatPriceToString(targetPrice, priceDecimals),
amount, amount,
reduceOnly: false, reduceOnly: false,
}); });
@@ -839,6 +839,7 @@ export class MakerPointsEngine {
): boolean { ): boolean {
const minDepth = this.config.filterMinDepth; const minDepth = this.config.filterMinDepth;
if (minDepth <= 0) return false; if (minDepth <= 0) return false;
const priceDecimals = this.getPriceDecimals();
// 获取启用的所有档位 // 获取启用的所有档位
const targets = buildBpsTargets({ const targets = buildBpsTargets({
@@ -850,11 +851,11 @@ export class MakerPointsEngine {
let changed = false; let changed = false;
for (const bps of targets) { for (const bps of targets) {
const buyPrice = bid1 * (1 - bps / 10000); const buyTargetPrice = this.normalizeDepthTargetPrice(bid1 * (1 - bps / 10000), priceDecimals);
const sellPrice = ask1 * (1 + bps / 10000); const sellTargetPrice = this.normalizeDepthTargetPrice(ask1 * (1 + bps / 10000), priceDecimals);
const buyDepthQty = getDepthBetweenPrices(depth, "BUY", buyPrice); const buyDepthQty = getDepthBetweenPrices(depth, "BUY", buyTargetPrice ?? 0);
const sellDepthQty = getDepthBetweenPrices(depth, "SELL", sellPrice); const sellDepthQty = getDepthBetweenPrices(depth, "SELL", sellTargetPrice ?? 0);
const currentBuyOk = buyDepthQty >= minDepth; const currentBuyOk = buyDepthQty >= minDepth;
const currentSellOk = sellDepthQty >= minDepth; const currentSellOk = sellDepthQty >= minDepth;
@@ -889,15 +890,16 @@ export class MakerPointsEngine {
band10To30: this.config.enableBand10To30, band10To30: this.config.enableBand10To30,
band30To100: this.config.enableBand30To100, band30To100: this.config.enableBand30To100,
}); });
const priceDecimals = this.getPriceDecimals();
for (const bps of targets) { for (const bps of targets) {
const lastStatus = this.lastDepthOkStatus[bps]; const lastStatus = this.lastDepthOkStatus[bps];
if (!lastStatus) continue; if (!lastStatus) continue;
const buyPrice = topBid * (1 - bps / 10000); const buyTargetPrice = this.normalizeDepthTargetPrice(topBid * (1 - bps / 10000), priceDecimals);
const sellPrice = topAsk * (1 + bps / 10000); const sellTargetPrice = this.normalizeDepthTargetPrice(topAsk * (1 + bps / 10000), priceDecimals);
const buyDepthQty = getDepthBetweenPrices(depth, "BUY", buyPrice); const buyDepthQty = getDepthBetweenPrices(depth, "BUY", buyTargetPrice ?? 0);
const sellDepthQty = getDepthBetweenPrices(depth, "SELL", sellPrice); const sellDepthQty = getDepthBetweenPrices(depth, "SELL", sellTargetPrice ?? 0);
const currentBuyOk = buyDepthQty >= minDepth; const currentBuyOk = buyDepthQty >= minDepth;
const currentSellOk = sellDepthQty >= minDepth; const currentSellOk = sellDepthQty >= minDepth;
@@ -1344,6 +1346,13 @@ export class MakerPointsEngine {
return Math.max(0, Math.floor(raw + 1e-9)); return Math.max(0, Math.floor(raw + 1e-9));
} }
private normalizeDepthTargetPrice(price: number, priceDecimals: number): number | null {
if (!Number.isFinite(price) || price <= 0) return null;
const normalized = Number(formatPriceToString(price, priceDecimals));
if (!Number.isFinite(normalized) || normalized <= 0) return null;
return normalized;
}
private emitUpdate(): void { private emitUpdate(): void {
try { try {
const snapshot = this.buildSnapshot(); const snapshot = this.buildSnapshot();
@@ -1398,12 +1407,13 @@ export class MakerPointsEngine {
if (!this.depthSnapshot || topBid == null || topAsk == null) { if (!this.depthSnapshot || topBid == null || topAsk == null) {
return bands; return bands;
} }
const priceDecimals = this.getPriceDecimals();
return bands.map((band) => { return bands.map((band) => {
const buyPrice = topBid * (1 - band.bps / 10000); const buyTargetPrice = this.normalizeDepthTargetPrice(topBid * (1 - band.bps / 10000), priceDecimals);
const sellPrice = topAsk * (1 + band.bps / 10000); const sellTargetPrice = this.normalizeDepthTargetPrice(topAsk * (1 + band.bps / 10000), priceDecimals);
const buyDepth = getDepthBetweenPrices(this.depthSnapshot, "BUY", buyPrice); const buyDepth = getDepthBetweenPrices(this.depthSnapshot, "BUY", buyTargetPrice ?? 0);
const sellDepth = getDepthBetweenPrices(this.depthSnapshot, "SELL", sellPrice); const sellDepth = getDepthBetweenPrices(this.depthSnapshot, "SELL", sellTargetPrice ?? 0);
return { ...band, buyDepth, sellDepth }; return { ...band, buyDepth, sellDepth };
}); });
} }
+37
View File
@@ -0,0 +1,37 @@
import { describe, expect, it } from "vitest";
import { getDepthBetweenPrices } from "../src/utils/price";
import type { AsterDepth } from "../src/exchanges/types";
describe("getDepthBetweenPrices boundary", () => {
it("SELL side excludes quantity exactly at target price", () => {
const depth: AsterDepth = {
lastUpdateId: 1,
bids: [],
asks: [
["69345", "1"],
["69349", "2"],
["69350", "999"],
["69351", "3"],
],
};
const total = getDepthBetweenPrices(depth, "SELL", 69350);
expect(total).toBe(3); // 仅 69345 + 69349
});
it("BUY side excludes quantity exactly at target price", () => {
const depth: AsterDepth = {
lastUpdateId: 1,
bids: [
["69355", "1"],
["69351", "2"],
["69350", "999"],
["69349", "3"],
],
asks: [],
};
const total = getDepthBetweenPrices(depth, "BUY", 69350);
expect(total).toBe(3); // 仅 69355 + 69351
});
});