mirror of
https://github.com/discountry/ritmex-bot.git
synced 2026-09-09 08:18:07 +00:00
Enhance Binance depth health monitoring and defense mode logic (#21)
- Updated translations for Binance depth status messages to include depth window information. - Modified `MakerPointsEngine` to incorporate health checks for the Binance depth tracker, including handling of unhealthy states. - Improved defense mode activation logic to respond to Binance depth health status, ensuring appropriate logging and notifications. - Added integration tests for defense mode behavior based on Binance depth health, validating transitions into and out of defense mode. - Refactored `BinanceDepthTracker` to support health checks and improved connection management.
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import type { ExchangeAdapter } from "../src/exchanges/adapter";
|
||||
import type { AsterAccountSnapshot, AsterDepth, AsterKline, AsterOrder, AsterTicker } from "../src/exchanges/types";
|
||||
import { MakerPointsEngine } from "../src/strategy/maker-points-engine";
|
||||
|
||||
const ORIGINAL_FETCH = globalThis.fetch;
|
||||
|
||||
class StubAdapter implements ExchangeAdapter {
|
||||
id = "standx";
|
||||
|
||||
supportsTrailingStops(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
watchAccount(_cb: (snapshot: AsterAccountSnapshot) => void): void {}
|
||||
watchOrders(_cb: (orders: AsterOrder[]) => void): void {}
|
||||
watchDepth(_symbol: string, _cb: (depth: AsterDepth) => void): void {}
|
||||
watchTicker(_symbol: string, _cb: (ticker: AsterTicker) => void): void {}
|
||||
watchKlines(_symbol: string, _interval: string, _cb: (klines: AsterKline[]) => void): void {}
|
||||
|
||||
async createOrder(): Promise<AsterOrder> {
|
||||
throw new Error("not implemented");
|
||||
}
|
||||
|
||||
async cancelOrder(): Promise<void> {}
|
||||
async cancelOrders(): Promise<void> {}
|
||||
async cancelAllOrders(): Promise<void> {}
|
||||
|
||||
async queryAccountSnapshot(): Promise<AsterAccountSnapshot | null> {
|
||||
return {
|
||||
canTrade: true,
|
||||
canDeposit: true,
|
||||
canWithdraw: true,
|
||||
updateTime: Date.now(),
|
||||
totalWalletBalance: "0",
|
||||
totalUnrealizedProfit: "0",
|
||||
positions: [],
|
||||
assets: [],
|
||||
marketType: "perp",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
globalThis.fetch = ORIGINAL_FETCH;
|
||||
vi.restoreAllMocks();
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
describe("MakerPointsEngine Binance depth health defense", () => {
|
||||
it("enters defense mode when Binance depth tracker is unhealthy", () => {
|
||||
vi.useFakeTimers();
|
||||
globalThis.fetch = vi.fn(async () => {
|
||||
throw new Error("network blocked in test");
|
||||
}) as any;
|
||||
|
||||
const engine = new MakerPointsEngine(
|
||||
{
|
||||
symbol: "BTC-USD",
|
||||
perOrderAmount: 0.01,
|
||||
closeThreshold: 0,
|
||||
stopLossUsd: 1,
|
||||
refreshIntervalMs: 500,
|
||||
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: true,
|
||||
filterMinDepth: 0,
|
||||
},
|
||||
new StubAdapter()
|
||||
);
|
||||
|
||||
const now = Date.now();
|
||||
(engine as any).lastStandxDepthTime = now;
|
||||
(engine as any).lastStandxAccountTime = now;
|
||||
(engine as any).lastBinanceDepthTime = now;
|
||||
|
||||
(engine as any).binanceDepth = {
|
||||
getHealth: () => ({
|
||||
started: true,
|
||||
connected: true,
|
||||
orderBookReady: false,
|
||||
restHealthy: false,
|
||||
healthy: false,
|
||||
reason: "orderbook_not_ready",
|
||||
lastEventAt: now,
|
||||
lastSnapshotAt: 0,
|
||||
lastRestSyncAt: 0,
|
||||
localLastUpdateId: 0,
|
||||
}),
|
||||
stop: () => {},
|
||||
};
|
||||
|
||||
(engine as any).checkDataStaleAndDefense();
|
||||
expect((engine as any).defenseMode).toBe(true);
|
||||
|
||||
const logs = ((engine as any).tradeLog.all() as Array<{ detail: string }>).map((entry) => entry.detail);
|
||||
expect(logs.some((detail) => detail.includes("Binance簿记异常(orderbook_not_ready)"))).toBe(true);
|
||||
|
||||
engine.stop();
|
||||
});
|
||||
|
||||
it("exits defense mode after Binance depth health recovers", () => {
|
||||
vi.useFakeTimers();
|
||||
globalThis.fetch = vi.fn(async () => {
|
||||
throw new Error("network blocked in test");
|
||||
}) as any;
|
||||
|
||||
const engine = new MakerPointsEngine(
|
||||
{
|
||||
symbol: "BTC-USD",
|
||||
perOrderAmount: 0.01,
|
||||
closeThreshold: 0,
|
||||
stopLossUsd: 1,
|
||||
refreshIntervalMs: 500,
|
||||
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: true,
|
||||
filterMinDepth: 0,
|
||||
},
|
||||
new StubAdapter()
|
||||
);
|
||||
|
||||
const now = Date.now();
|
||||
(engine as any).lastStandxDepthTime = now;
|
||||
(engine as any).lastStandxAccountTime = now;
|
||||
(engine as any).lastBinanceDepthTime = now;
|
||||
|
||||
let unhealthy = true;
|
||||
(engine as any).binanceDepth = {
|
||||
getHealth: () => ({
|
||||
started: true,
|
||||
connected: true,
|
||||
orderBookReady: !unhealthy,
|
||||
restHealthy: !unhealthy,
|
||||
healthy: !unhealthy,
|
||||
reason: unhealthy ? "orderbook_not_ready" : null,
|
||||
lastEventAt: now,
|
||||
lastSnapshotAt: now,
|
||||
lastRestSyncAt: now,
|
||||
localLastUpdateId: unhealthy ? 0 : 100,
|
||||
}),
|
||||
stop: () => {},
|
||||
};
|
||||
|
||||
(engine as any).checkDataStaleAndDefense();
|
||||
expect((engine as any).defenseMode).toBe(true);
|
||||
|
||||
unhealthy = false;
|
||||
(engine as any).checkDataStaleAndDefense();
|
||||
expect((engine as any).defenseMode).toBe(false);
|
||||
|
||||
engine.stop();
|
||||
});
|
||||
});
|
||||
@@ -94,6 +94,8 @@ describe("MakerPointsEngine defense-mode REST polling", () => {
|
||||
(engine as any).enterDefenseMode({
|
||||
standxDepthStale: true,
|
||||
binanceStale: false,
|
||||
binanceUnhealthy: false,
|
||||
binanceHealthReason: null,
|
||||
standxAccountStale: false,
|
||||
accountInvalid: false,
|
||||
standxRestUnhealthy: false,
|
||||
@@ -146,6 +148,8 @@ describe("MakerPointsEngine defense-mode REST polling", () => {
|
||||
(engine as any).enterDefenseMode({
|
||||
standxDepthStale: true,
|
||||
binanceStale: false,
|
||||
binanceUnhealthy: false,
|
||||
binanceHealthReason: null,
|
||||
standxAccountStale: false,
|
||||
accountInvalid: false,
|
||||
standxRestUnhealthy: false,
|
||||
|
||||
Reference in New Issue
Block a user