feat(maker-points): enhance configuration for maker points bands

Added new configuration options for maker points bands, including target distances and maximum distance limits. Updated the logic to handle these configurations, ensuring backward compatibility with existing defaults. Enhanced documentation and tests to cover the new features and ensure correct functionality across the system.
This commit is contained in:
discountry
2026-08-18 15:16:49 +08:00
parent f3a96886ac
commit 85954461f3
16 changed files with 1085 additions and 306 deletions
@@ -72,7 +72,13 @@ describe("MakerPointsEngine Binance depth health defense", () => {
band0To10Amount: 0.01,
band10To30Amount: 0.01,
band30To100Amount: 0.01,
band0To10Bps: 9,
band10To30Bps: 29,
band30To100Bps: 40,
maxDistanceBps: 95,
minRepriceBps: 3,
bandRepriceRatio: 0.15,
slOffsetBps: 2,
enableBinanceDepthCancel: true,
filterMinDepth: 0,
},
@@ -136,7 +142,13 @@ describe("MakerPointsEngine Binance depth health defense", () => {
band0To10Amount: 0.01,
band10To30Amount: 0.01,
band30To100Amount: 0.01,
band0To10Bps: 9,
band10To30Bps: 29,
band30To100Bps: 40,
maxDistanceBps: 95,
minRepriceBps: 3,
bandRepriceRatio: 0.15,
slOffsetBps: 2,
enableBinanceDepthCancel: true,
filterMinDepth: 0,
},
+68 -1
View File
@@ -1,4 +1,4 @@
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { resolveSymbolFromEnv } from "../src/config";
const ORIGINAL_ENV = { ...process.env };
@@ -72,3 +72,70 @@ describe("resolveSymbolFromEnv", () => {
expect(resolveSymbolFromEnv("ondoperp")).toBe("ETH-USD.P");
});
});
describe("makerPointsConfig defaults", () => {
async function loadConfig(env: Record<string, string> = {}) {
for (const key of Object.keys(process.env)) {
if (key.startsWith("MAKER_POINTS_")) delete process.env[key];
}
process.env.EXCHANGE = "standx";
Object.assign(process.env, env);
vi.resetModules();
return (await import("../src/config")).makerPointsConfig;
}
it("runs on sane defaults when none of the new vars are set", async () => {
const config = await loadConfig();
expect(config.band0To10Bps).toBe(9);
expect(config.band10To30Bps).toBe(29);
expect(config.band30To100Bps).toBe(40);
expect(config.maxDistanceBps).toBe(95);
expect(config.minRepriceBps).toBe(3);
expect(config.bandRepriceRatio).toBe(0.15);
expect(config.slOffsetBps).toBe(2);
for (const [key, value] of Object.entries(config)) {
if (typeof value === "number") {
expect(Number.isFinite(value), `${key} must be finite`).toBe(true);
}
}
});
it("falls back to defaults for unparseable values", async () => {
const config = await loadConfig({
MAKER_POINTS_BAND_0_10_BPS: "abc",
MAKER_POINTS_BAND_REPRICE_RATIO: "",
MAKER_POINTS_SL_OFFSET_BPS: "not-a-number",
});
expect(config.band0To10Bps).toBe(9);
expect(config.bandRepriceRatio).toBe(0.15);
expect(config.slOffsetBps).toBe(2);
});
it("never lets the distance cap sit inside an enabled band", async () => {
// 否则夹回会把挂单推向盘口,正好是最容易成交的方向
const config = await loadConfig({
MAKER_POINTS_MAX_DISTANCE_BPS: "20",
MAKER_POINTS_BAND_30_100_BPS: "60",
});
expect(config.maxDistanceBps).toBe(60);
});
it("ignores a disabled band when widening the cap", async () => {
const config = await loadConfig({
MAKER_POINTS_MAX_DISTANCE_BPS: "20",
MAKER_POINTS_BAND_30_100: "false",
MAKER_POINTS_BAND_30_100_BPS: "60",
});
expect(config.maxDistanceBps).toBe(29);
});
it("caps the distance at the zero-points cliff", async () => {
const config = await loadConfig({ MAKER_POINTS_MAX_DISTANCE_BPS: "500" });
expect(config.maxDistanceBps).toBe(100);
});
});
@@ -45,7 +45,13 @@ describe("MakerPointsEngine Binance depth monitor config", () => {
band0To10Amount: 0.01,
band10To30Amount: 0.01,
band30To100Amount: 0.01,
band0To10Bps: 9,
band10To30Bps: 29,
band30To100Bps: 40,
maxDistanceBps: 95,
minRepriceBps: 3,
bandRepriceRatio: 0.15,
slOffsetBps: 2,
enableBinanceDepthCancel: true,
filterMinDepth: 0,
},
@@ -79,7 +85,13 @@ describe("MakerPointsEngine Binance depth monitor config", () => {
band0To10Amount: 0.01,
band10To30Amount: 0.01,
band30To100Amount: 0.01,
band0To10Bps: 9,
band10To30Bps: 29,
band30To100Bps: 40,
maxDistanceBps: 95,
minRepriceBps: 3,
bandRepriceRatio: 0.15,
slOffsetBps: 2,
enableBinanceDepthCancel: true,
binanceDepthWindowBps: 7,
binanceDepthImbalanceRatio: 11,
@@ -67,7 +67,13 @@ describe("MakerPointsEngine defense-mode account staleness", () => {
band0To10Amount: 0.01,
band10To30Amount: 0.01,
band30To100Amount: 0.01,
band0To10Bps: 9,
band10To30Bps: 29,
band30To100Bps: 40,
maxDistanceBps: 95,
minRepriceBps: 3,
bandRepriceRatio: 0.15,
slOffsetBps: 2,
enableBinanceDepthCancel: false,
filterMinDepth: 0,
},
@@ -107,7 +113,13 @@ describe("MakerPointsEngine defense-mode account staleness", () => {
band0To10Amount: 0.01,
band10To30Amount: 0.01,
band30To100Amount: 0.01,
band0To10Bps: 9,
band10To30Bps: 29,
band30To100Bps: 40,
maxDistanceBps: 95,
minRepriceBps: 3,
bandRepriceRatio: 0.15,
slOffsetBps: 2,
enableBinanceDepthCancel: false,
filterMinDepth: 0,
},
+12
View File
@@ -85,7 +85,13 @@ describe("MakerPointsEngine defense-mode REST polling", () => {
band0To10Amount: 0.01,
band10To30Amount: 0.01,
band30To100Amount: 0.01,
band0To10Bps: 9,
band10To30Bps: 29,
band30To100Bps: 40,
maxDistanceBps: 95,
minRepriceBps: 3,
bandRepriceRatio: 0.15,
slOffsetBps: 2,
enableBinanceDepthCancel: false,
filterMinDepth: 0,
},
@@ -139,7 +145,13 @@ describe("MakerPointsEngine defense-mode REST polling", () => {
band0To10Amount: 0.01,
band10To30Amount: 0.01,
band30To100Amount: 0.01,
band0To10Bps: 9,
band10To30Bps: 29,
band30To100Bps: 40,
maxDistanceBps: 95,
minRepriceBps: 3,
bandRepriceRatio: 0.15,
slOffsetBps: 2,
enableBinanceDepthCancel: false,
filterMinDepth: 0,
},
@@ -67,7 +67,13 @@ describe("MakerPointsEngine immediate depth protection", () => {
band0To10Amount: 0.01,
band10To30Amount: 0.01,
band30To100Amount: 0.01,
band0To10Bps: 9,
band10To30Bps: 29,
band30To100Bps: 40,
maxDistanceBps: 95,
minRepriceBps: 3,
bandRepriceRatio: 0.15,
slOffsetBps: 2,
enableBinanceDepthCancel: false,
filterMinDepth: 10,
},
+99 -59
View File
@@ -45,70 +45,110 @@ afterEach(() => {
vi.useRealTimers();
});
function buildEngine(adapter: StubAdapter, restingBuyPrice: string): MakerPointsEngine {
const engine = new MakerPointsEngine(
{
symbol: "BTC-USD",
perOrderAmount: 0.01,
closeThreshold: 0,
stopLossUsd: 1,
refreshIntervalMs: 10_000,
maxLogEntries: 20,
maxCloseSlippagePct: 0.05,
priceTick: 0.1,
qtyStep: 0.001,
enableBand0To10: true,
enableBand10To30: false,
enableBand30To100: false,
band0To10Amount: 0.01,
band10To30Amount: 0.01,
band30To100Amount: 0.01,
band0To10Bps: 9,
band10To30Bps: 29,
band30To100Bps: 40,
maxDistanceBps: 95,
minRepriceBps: 3,
bandRepriceRatio: 0.15,
slOffsetBps: 2,
enableBinanceDepthCancel: false,
filterMinDepth: 0,
},
adapter
);
(engine as any).feedStatus = { account: true, depth: true, ticker: true, orders: true, binance: true };
(engine as any).initialOrderSnapshotReady = true;
(engine as any).defenseMode = false;
(engine as any).reconnectResetPending = false;
(engine as any).stopLossProcessing = false;
(engine as any).openOrders = [
{
orderId: 1,
clientOrderId: "entry-order",
symbol: "BTC-USD",
side: "BUY",
type: "LIMIT",
status: "NEW",
price: restingBuyPrice,
origQty: "0.01",
executedQty: "0",
stopPrice: "0",
time: Date.now(),
updateTime: Date.now(),
reduceOnly: false,
closePosition: false,
},
];
return engine;
}
// bid1 99.9 / ask1 100.9 → 中值 100.40-10 档目标 9 bps,容差 max(3, 9×0.15)=3
// 所以保留窗口是距中值 612 bps,即 100.28100.34
const DEPTH = {
lastUpdateId: 1,
bids: [["99.9", "1"]] as Array<[string, string]>,
asks: [["100.9", "1"]] as Array<[string, string]>,
eventTime: Date.now(),
symbol: "BTC-USD",
};
describe("MakerPointsEngine immediate reprice", () => {
it("triggers an immediate tick when min reprice bps threshold is reached", () => {
it("leaves a quote alone while it is still inside its band tolerance", () => {
vi.useFakeTimers();
const adapter = new StubAdapter();
const engine = new MakerPointsEngine(
{
symbol: "BTC-USD",
perOrderAmount: 0.01,
closeThreshold: 0,
stopLossUsd: 1,
refreshIntervalMs: 10_000,
maxLogEntries: 20,
maxCloseSlippagePct: 0.05,
priceTick: 0.1,
qtyStep: 0.001,
enableBand0To10: true,
enableBand10To30: false,
enableBand30To100: false,
band0To10Amount: 0.01,
band10To30Amount: 0.01,
band30To100Amount: 0.01,
minRepriceBps: 3,
enableBinanceDepthCancel: false,
filterMinDepth: 0,
},
adapter
);
(engine as any).feedStatus = { account: true, depth: true, ticker: true, orders: true, binance: true };
(engine as any).initialOrderSnapshotReady = true;
(engine as any).defenseMode = false;
(engine as any).reconnectResetPending = false;
(engine as any).stopLossProcessing = false;
(engine as any).lastQuoteBid1 = 100;
(engine as any).lastQuoteAsk1 = 101;
(engine as any).openOrders = [
{
orderId: 1,
clientOrderId: "entry-order",
symbol: "BTC-USD",
side: "BUY",
type: "LIMIT",
status: "NEW",
price: "99.0",
origQty: "0.01",
executedQty: "0",
stopPrice: "0",
time: Date.now(),
updateTime: Date.now(),
reduceOnly: false,
closePosition: false,
},
];
// 100.31 距中值 8.96 bps,仍在 9±3 内 —— 不该撤挂,订单得以跨过 3 秒计分门槛
const engine = buildEngine(adapter, "100.31");
const tickSpy = vi.spyOn(engine as any, "tick").mockResolvedValue(undefined);
adapter.emitDepth({
lastUpdateId: 1,
bids: [["99.9", "1"]],
asks: [["100.9", "1"]],
eventTime: Date.now(),
symbol: "BTC-USD",
});
adapter.emitDepth(DEPTH);
expect(tickSpy).not.toHaveBeenCalled();
engine.stop();
});
it("triggers an immediate tick once the quote drifts out of every band", () => {
vi.useFakeTimers();
const adapter = new StubAdapter();
// 100.25 距中值 14.94 bps,已经掉出 9±3
const engine = buildEngine(adapter, "100.25");
const tickSpy = vi.spyOn(engine as any, "tick").mockResolvedValue(undefined);
adapter.emitDepth(DEPTH);
expect(tickSpy).toHaveBeenCalledTimes(1);
engine.stop();
});
it("measures drift against mark price rather than the book mid", () => {
vi.useFakeTimers();
const adapter = new StubAdapter();
// 同一张单:按中值 100.4 算是安全的,但 mark 已经跌到 100.0
// 买单实际挂在 mark 上方 31 bps,随时会被吃 —— 必须立即重挂
const engine = buildEngine(adapter, "100.31");
(engine as any).tickerSnapshot = { symbol: "BTC-USD", markPrice: "100.0" };
const tickSpy = vi.spyOn(engine as any, "tick").mockResolvedValue(undefined);
adapter.emitDepth(DEPTH);
expect(tickSpy).toHaveBeenCalledTimes(1);
engine.stop();
+12
View File
@@ -81,7 +81,13 @@ describe("MakerPointsEngine StandX isolated margin guard", () => {
band0To10Amount: 0.01,
band10To30Amount: 0.01,
band30To100Amount: 0.01,
band0To10Bps: 9,
band10To30Bps: 29,
band30To100Bps: 40,
maxDistanceBps: 95,
minRepriceBps: 3,
bandRepriceRatio: 0.15,
slOffsetBps: 2,
enableBinanceDepthCancel: false,
filterMinDepth: 0,
},
@@ -146,7 +152,13 @@ describe("MakerPointsEngine StandX isolated margin guard", () => {
band0To10Amount: 0.01,
band10To30Amount: 0.01,
band30To100Amount: 0.01,
band0To10Bps: 9,
band10To30Bps: 29,
band30To100Bps: 40,
maxDistanceBps: 95,
minRepriceBps: 3,
bandRepriceRatio: 0.15,
slOffsetBps: 2,
enableBinanceDepthCancel: false,
filterMinDepth: 0,
},