mirror of
https://github.com/discountry/ritmex-bot.git
synced 2026-09-11 09:18:08 +00:00
Enhance account snapshot handling and staleness checks in MakerPointsEngine
- Updated `emitAccountSnapshot` method in `StandxGateway` to accept an optional `updateTime` parameter, allowing for more accurate timestamping. - Introduced logic to determine the appropriate `updateTime` based on the latest position or balance data. - Added `time` property to `StandxPosition` interface for improved timestamp management. - Implemented `applyAccountSnapshot` method in `MakerPointsEngine` to streamline account snapshot processing and ensure accurate time tracking. - Added tests to validate the behavior of account staleness checks and defense mode activation based on account data freshness.
This commit is contained in:
@@ -1398,7 +1398,7 @@ export class StandxGateway {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private emitAccountSnapshot(): void {
|
private emitAccountSnapshot(updateTime?: number): void {
|
||||||
const positions = Array.from(this.positions.values());
|
const positions = Array.from(this.positions.values());
|
||||||
const assets = Array.from(this.balances.values());
|
const assets = Array.from(this.balances.values());
|
||||||
const totalWalletBalance = assets.reduce((sum, asset) => sum + Number(asset.walletBalance ?? 0), 0);
|
const totalWalletBalance = assets.reduce((sum, asset) => sum + Number(asset.walletBalance ?? 0), 0);
|
||||||
@@ -1410,7 +1410,7 @@ export class StandxGateway {
|
|||||||
canTrade: true,
|
canTrade: true,
|
||||||
canDeposit: true,
|
canDeposit: true,
|
||||||
canWithdraw: true,
|
canWithdraw: true,
|
||||||
updateTime: Date.now(),
|
updateTime: typeof updateTime === "number" && Number.isFinite(updateTime) && updateTime > 0 ? updateTime : Date.now(),
|
||||||
totalWalletBalance: String(totalWalletBalance || 0),
|
totalWalletBalance: String(totalWalletBalance || 0),
|
||||||
totalUnrealizedProfit: String(totalUnrealizedProfit || 0),
|
totalUnrealizedProfit: String(totalUnrealizedProfit || 0),
|
||||||
positions,
|
positions,
|
||||||
@@ -1433,10 +1433,13 @@ export class StandxGateway {
|
|||||||
this.requestJson<StandxBalanceSnapshot>("/api/query_balance", { method: "GET" }),
|
this.requestJson<StandxBalanceSnapshot>("/api/query_balance", { method: "GET" }),
|
||||||
this.requestJson<StandxPosition[]>("/api/query_positions", { method: "GET" }),
|
this.requestJson<StandxPosition[]>("/api/query_positions", { method: "GET" }),
|
||||||
]);
|
]);
|
||||||
|
let restSnapshotTime = 0;
|
||||||
if (Array.isArray(positions)) {
|
if (Array.isArray(positions)) {
|
||||||
for (const position of positions) {
|
for (const position of positions) {
|
||||||
const mapped = this.mapPosition(position);
|
const mapped = this.mapPosition(position);
|
||||||
this.positions.set(mapped.symbol, mapped);
|
this.positions.set(mapped.symbol, mapped);
|
||||||
|
const positionTime = toTimestamp(position.time ?? position.updated_at);
|
||||||
|
restSnapshotTime = Math.max(restSnapshotTime, positionTime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (balance) {
|
if (balance) {
|
||||||
@@ -1445,12 +1448,12 @@ export class StandxGateway {
|
|||||||
asset: token,
|
asset: token,
|
||||||
walletBalance: String(balance.balance ?? "0"),
|
walletBalance: String(balance.balance ?? "0"),
|
||||||
availableBalance: String(balance.cross_available ?? balance.balance ?? "0"),
|
availableBalance: String(balance.cross_available ?? balance.balance ?? "0"),
|
||||||
updateTime: Date.now(),
|
updateTime: restSnapshotTime > 0 ? restSnapshotTime : Date.now(),
|
||||||
unrealizedProfit: String(balance.upnl ?? "0"),
|
unrealizedProfit: String(balance.upnl ?? "0"),
|
||||||
};
|
};
|
||||||
this.balances.set(token, asset);
|
this.balances.set(token, asset);
|
||||||
}
|
}
|
||||||
this.emitAccountSnapshot();
|
this.emitAccountSnapshot(restSnapshotTime > 0 ? restSnapshotTime : undefined);
|
||||||
return this.accountSnapshot;
|
return this.accountSnapshot;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger("accountSnapshot", error);
|
this.logger("accountSnapshot", error);
|
||||||
@@ -1679,7 +1682,7 @@ export class StandxGateway {
|
|||||||
entryPrice: String(data.entry_price ?? "0"),
|
entryPrice: String(data.entry_price ?? "0"),
|
||||||
unrealizedProfit: String(data.upnl ?? "0"),
|
unrealizedProfit: String(data.upnl ?? "0"),
|
||||||
positionSide: "BOTH",
|
positionSide: "BOTH",
|
||||||
updateTime: toTimestamp(data.updated_at),
|
updateTime: toTimestamp(data.time ?? data.updated_at),
|
||||||
leverage: data.leverage ? String(data.leverage) : undefined,
|
leverage: data.leverage ? String(data.leverage) : undefined,
|
||||||
marginType: data.margin_mode,
|
marginType: data.margin_mode,
|
||||||
liquidationPrice: data.liq_price ? String(data.liq_price) : undefined,
|
liquidationPrice: data.liq_price ? String(data.liq_price) : undefined,
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ export interface StandxPosition {
|
|||||||
leverage?: string;
|
leverage?: string;
|
||||||
liq_price?: string;
|
liq_price?: string;
|
||||||
margin_mode?: string;
|
margin_mode?: string;
|
||||||
|
time?: string;
|
||||||
updated_at?: string;
|
updated_at?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -95,10 +95,11 @@ const STOP_LOSS_CHECK_INTERVAL_MS = 250; // 止损检查最大间隔
|
|||||||
const STOP_LOSS_RETRY_INTERVAL_MS = 500; // 止损失败后重试间隔
|
const STOP_LOSS_RETRY_INTERVAL_MS = 500; // 止损失败后重试间隔
|
||||||
const DATA_STALE_THRESHOLD_MS = 5_000; // 数据过时阈值(5秒)
|
const DATA_STALE_THRESHOLD_MS = 5_000; // 数据过时阈值(5秒)
|
||||||
const DEFENSE_MODE_CHECK_INTERVAL_MS = 1000; // 防御模式检查间隔
|
const DEFENSE_MODE_CHECK_INTERVAL_MS = 1000; // 防御模式检查间隔
|
||||||
const ACCOUNT_DATA_STALE_THRESHOLD_MS = 20_000; // 账户数据长期无更新阈值(StandX 有 REST 兜底,长期无更新通常意味着异常)
|
const ACCOUNT_DATA_STALE_THRESHOLD_MS = 20_000; // 账户数据长期无更新阈值(会先尝试通过 REST 补拉验证,不直接进入防御模式)
|
||||||
const STANDX_REST_ERROR_DEFENSE_THRESHOLD = 3;
|
const STANDX_REST_ERROR_DEFENSE_THRESHOLD = 3;
|
||||||
const STANDX_MARGIN_MODE_CHECK_INTERVAL_MS = 500;
|
const STANDX_MARGIN_MODE_CHECK_INTERVAL_MS = 500;
|
||||||
const STANDX_MARGIN_MODE_MAX_ATTEMPTS = 10;
|
const STANDX_MARGIN_MODE_MAX_ATTEMPTS = 10;
|
||||||
|
const ACCOUNT_STALE_REST_PROBE_MIN_INTERVAL_MS = 5_000;
|
||||||
|
|
||||||
export class MakerPointsEngine {
|
export class MakerPointsEngine {
|
||||||
private accountSnapshot: AsterAccountSnapshot | null = null;
|
private accountSnapshot: AsterAccountSnapshot | null = null;
|
||||||
@@ -177,6 +178,9 @@ export class MakerPointsEngine {
|
|||||||
private lastStandxDepthTime = 0;
|
private lastStandxDepthTime = 0;
|
||||||
private lastStandxAccountTime = 0;
|
private lastStandxAccountTime = 0;
|
||||||
private lastBinanceDepthTime = 0;
|
private lastBinanceDepthTime = 0;
|
||||||
|
private accountStaleRestProbeInFlight: Promise<void> | null = null;
|
||||||
|
private accountStaleRestProbeLastAttempt = 0;
|
||||||
|
private accountStaleRestProbeConsecutiveFailures = 0;
|
||||||
// 防御模式状态
|
// 防御模式状态
|
||||||
private defenseMode = false;
|
private defenseMode = false;
|
||||||
private defenseModeNotified = false;
|
private defenseModeNotified = false;
|
||||||
@@ -289,17 +293,7 @@ export class MakerPointsEngine {
|
|||||||
safeSubscribe<AsterAccountSnapshot>(
|
safeSubscribe<AsterAccountSnapshot>(
|
||||||
this.exchange.watchAccount.bind(this.exchange),
|
this.exchange.watchAccount.bind(this.exchange),
|
||||||
(snapshot) => {
|
(snapshot) => {
|
||||||
this.accountSnapshot = snapshot;
|
this.applyAccountSnapshot(snapshot);
|
||||||
this.lastStandxAccountTime = Date.now();
|
|
||||||
const totalUnrealized = Number(snapshot.totalUnrealizedProfit ?? "0");
|
|
||||||
if (Number.isFinite(totalUnrealized)) {
|
|
||||||
this.accountUnrealized = totalUnrealized;
|
|
||||||
}
|
|
||||||
const position = getPosition(snapshot, this.config.symbol);
|
|
||||||
this.sessionVolume.update(position, this.getReferencePrice());
|
|
||||||
this.detectPositionChange(position);
|
|
||||||
this.feedStatus.account = true;
|
|
||||||
this.emitUpdate();
|
|
||||||
},
|
},
|
||||||
log,
|
log,
|
||||||
{
|
{
|
||||||
@@ -370,6 +364,51 @@ export class MakerPointsEngine {
|
|||||||
this.setupConnectionProtection();
|
this.setupConnectionProtection();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private applyAccountSnapshot(snapshot: AsterAccountSnapshot): void {
|
||||||
|
this.accountSnapshot = snapshot;
|
||||||
|
// StandX: WS 推送使用本地接收时间戳;REST 快照使用响应里的 time 字段映射到 snapshot.updateTime
|
||||||
|
this.lastStandxAccountTime =
|
||||||
|
this.exchange.id === "standx" && Number.isFinite(snapshot.updateTime) && snapshot.updateTime > 0
|
||||||
|
? snapshot.updateTime
|
||||||
|
: Date.now();
|
||||||
|
this.accountStaleRestProbeConsecutiveFailures = 0;
|
||||||
|
const totalUnrealized = Number(snapshot.totalUnrealizedProfit ?? "0");
|
||||||
|
if (Number.isFinite(totalUnrealized)) {
|
||||||
|
this.accountUnrealized = totalUnrealized;
|
||||||
|
}
|
||||||
|
const position = getPosition(snapshot, this.config.symbol);
|
||||||
|
this.sessionVolume.update(position, this.getReferencePrice());
|
||||||
|
this.detectPositionChange(position);
|
||||||
|
this.feedStatus.account = true;
|
||||||
|
this.emitUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
private maybeProbeStandxAccountSnapshot(now: number): void {
|
||||||
|
if (this.exchange.id !== "standx") return;
|
||||||
|
if (!this.exchange.queryAccountSnapshot) return;
|
||||||
|
if (this.defenseMode) return;
|
||||||
|
if (this.accountStaleRestProbeInFlight) return;
|
||||||
|
if (this.accountStaleRestProbeLastAttempt > 0 && now - this.accountStaleRestProbeLastAttempt < ACCOUNT_STALE_REST_PROBE_MIN_INTERVAL_MS) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.accountStaleRestProbeLastAttempt = now;
|
||||||
|
|
||||||
|
this.accountStaleRestProbeInFlight = (async () => {
|
||||||
|
try {
|
||||||
|
const next = await this.exchange.queryAccountSnapshot?.();
|
||||||
|
if (next) {
|
||||||
|
this.applyAccountSnapshot(next);
|
||||||
|
} else {
|
||||||
|
this.accountStaleRestProbeConsecutiveFailures += 1;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
this.accountStaleRestProbeConsecutiveFailures += 1;
|
||||||
|
} finally {
|
||||||
|
this.accountStaleRestProbeInFlight = null;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置连接保护机制
|
* 设置连接保护机制
|
||||||
* 监听断连/重连事件,实现保护逻辑
|
* 监听断连/重连事件,实现保护逻辑
|
||||||
@@ -1616,8 +1655,16 @@ export class MakerPointsEngine {
|
|||||||
const standxDepthStale = this.lastStandxDepthTime > 0 && (now - this.lastStandxDepthTime) > DATA_STALE_THRESHOLD_MS;
|
const standxDepthStale = this.lastStandxDepthTime > 0 && (now - this.lastStandxDepthTime) > DATA_STALE_THRESHOLD_MS;
|
||||||
const binanceStale = this.lastBinanceDepthTime > 0 && (now - this.lastBinanceDepthTime) > DATA_STALE_THRESHOLD_MS;
|
const binanceStale = this.lastBinanceDepthTime > 0 && (now - this.lastBinanceDepthTime) > DATA_STALE_THRESHOLD_MS;
|
||||||
|
|
||||||
|
const standxAccountAge = this.lastStandxAccountTime > 0 ? now - this.lastStandxAccountTime : 0;
|
||||||
|
const standxAccountStaleByAge = this.lastStandxAccountTime > 0 && standxAccountAge > ACCOUNT_DATA_STALE_THRESHOLD_MS;
|
||||||
|
if (standxAccountStaleByAge) {
|
||||||
|
this.maybeProbeStandxAccountSnapshot(now);
|
||||||
|
}
|
||||||
const standxAccountStale =
|
const standxAccountStale =
|
||||||
this.lastStandxAccountTime > 0 && (now - this.lastStandxAccountTime) > ACCOUNT_DATA_STALE_THRESHOLD_MS;
|
standxAccountStaleByAge &&
|
||||||
|
// WS 推送间隔可能较长,先给 REST 补拉一次机会;只有补拉失败后才进入防御模式
|
||||||
|
this.accountStaleRestProbeConsecutiveFailures > 0 &&
|
||||||
|
this.accountStaleRestProbeInFlight == null;
|
||||||
const accountHealth = validateAccountSnapshotForSymbol(this.accountSnapshot, this.config.symbol);
|
const accountHealth = validateAccountSnapshotForSymbol(this.accountSnapshot, this.config.symbol);
|
||||||
const accountInvalid = this.accountSnapshot != null && !accountHealth.ok;
|
const accountInvalid = this.accountSnapshot != null && !accountHealth.ok;
|
||||||
const standxRestUnhealthy =
|
const standxRestUnhealthy =
|
||||||
@@ -1647,7 +1694,7 @@ export class MakerPointsEngine {
|
|||||||
marginMode,
|
marginMode,
|
||||||
standxDepthAge: this.lastStandxDepthTime > 0 ? now - this.lastStandxDepthTime : 0,
|
standxDepthAge: this.lastStandxDepthTime > 0 ? now - this.lastStandxDepthTime : 0,
|
||||||
binanceAge: this.lastBinanceDepthTime > 0 ? now - this.lastBinanceDepthTime : 0,
|
binanceAge: this.lastBinanceDepthTime > 0 ? now - this.lastBinanceDepthTime : 0,
|
||||||
standxAccountAge: this.lastStandxAccountTime > 0 ? now - this.lastStandxAccountTime : 0,
|
standxAccountAge,
|
||||||
accountIssues: accountInvalid ? accountHealth.issues : [],
|
accountIssues: accountInvalid ? accountHealth.issues : [],
|
||||||
});
|
});
|
||||||
} else if (!shouldDefend && this.defenseMode) {
|
} else if (!shouldDefend && this.defenseMode) {
|
||||||
@@ -1797,9 +1844,7 @@ export class MakerPointsEngine {
|
|||||||
if (this.exchange.queryAccountSnapshot) {
|
if (this.exchange.queryAccountSnapshot) {
|
||||||
const nextAccount = await this.exchange.queryAccountSnapshot();
|
const nextAccount = await this.exchange.queryAccountSnapshot();
|
||||||
if (nextAccount) {
|
if (nextAccount) {
|
||||||
this.accountSnapshot = nextAccount;
|
this.applyAccountSnapshot(nextAccount);
|
||||||
this.lastStandxAccountTime = Date.now();
|
|
||||||
this.feedStatus.account = true;
|
|
||||||
const health = validateAccountSnapshotForSymbol(nextAccount, this.config.symbol);
|
const health = validateAccountSnapshotForSymbol(nextAccount, this.config.symbol);
|
||||||
if (!health.ok) {
|
if (!health.ok) {
|
||||||
this.tradeLog.push("warn", `防御模式: 仓位数据仍异常: ${health.issues.join(",")}`);
|
this.tradeLog.push("warn", `防御模式: 仓位数据仍异常: ${health.issues.join(",")}`);
|
||||||
@@ -1884,9 +1929,7 @@ export class MakerPointsEngine {
|
|||||||
for (let attempt = 0; attempt < STANDX_MARGIN_MODE_MAX_ATTEMPTS; attempt++) {
|
for (let attempt = 0; attempt < STANDX_MARGIN_MODE_MAX_ATTEMPTS; attempt++) {
|
||||||
const next = await queryAccount();
|
const next = await queryAccount();
|
||||||
if (next) {
|
if (next) {
|
||||||
this.accountSnapshot = next;
|
this.applyAccountSnapshot(next);
|
||||||
this.lastStandxAccountTime = Date.now();
|
|
||||||
this.feedStatus.account = true;
|
|
||||||
}
|
}
|
||||||
const mode = this.getStandxMarginMode(this.accountSnapshot);
|
const mode = this.getStandxMarginMode(this.accountSnapshot);
|
||||||
if (mode === "isolated") {
|
if (mode === "isolated") {
|
||||||
|
|||||||
@@ -0,0 +1,131 @@
|
|||||||
|
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";
|
||||||
|
|
||||||
|
class StubAdapter implements ExchangeAdapter {
|
||||||
|
id = "standx";
|
||||||
|
accountSnapshot: AsterAccountSnapshot | null = null;
|
||||||
|
|
||||||
|
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 this.accountSnapshot;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("MakerPointsEngine defense-mode account staleness", () => {
|
||||||
|
it("does not enter defense mode for ~21s StandX account gap (REST probe succeeds)", async () => {
|
||||||
|
vi.useFakeTimers();
|
||||||
|
vi.setSystemTime(new Date("2026-01-24T15:20:00.000Z"));
|
||||||
|
const adapter = new StubAdapter();
|
||||||
|
adapter.accountSnapshot = {
|
||||||
|
canTrade: true,
|
||||||
|
canDeposit: true,
|
||||||
|
canWithdraw: true,
|
||||||
|
updateTime: Date.now(),
|
||||||
|
totalWalletBalance: "0",
|
||||||
|
totalUnrealizedProfit: "0",
|
||||||
|
positions: [],
|
||||||
|
assets: [],
|
||||||
|
marketType: "perp",
|
||||||
|
};
|
||||||
|
const engine = new MakerPointsEngine(
|
||||||
|
{
|
||||||
|
symbol: "BTC-USD",
|
||||||
|
perOrderAmount: 0.01,
|
||||||
|
closeThreshold: 0,
|
||||||
|
stopLossUsd: 1,
|
||||||
|
refreshIntervalMs: 500,
|
||||||
|
maxLogEntries: 10,
|
||||||
|
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
|
||||||
|
);
|
||||||
|
|
||||||
|
const now = Date.now();
|
||||||
|
(engine as any).lastStandxDepthTime = now;
|
||||||
|
(engine as any).lastBinanceDepthTime = now;
|
||||||
|
(engine as any).lastStandxAccountTime = now - 21_000;
|
||||||
|
|
||||||
|
(engine as any).checkDataStaleAndDefense();
|
||||||
|
expect((engine as any).defenseMode).toBe(false);
|
||||||
|
await vi.runAllTimersAsync();
|
||||||
|
engine.stop();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("enters defense mode if StandX account REST probe fails", async () => {
|
||||||
|
vi.useFakeTimers();
|
||||||
|
vi.setSystemTime(new Date("2026-01-24T15:20:00.000Z"));
|
||||||
|
const adapter = new StubAdapter();
|
||||||
|
adapter.accountSnapshot = null;
|
||||||
|
const engine = new MakerPointsEngine(
|
||||||
|
{
|
||||||
|
symbol: "BTC-USD",
|
||||||
|
perOrderAmount: 0.01,
|
||||||
|
closeThreshold: 0,
|
||||||
|
stopLossUsd: 1,
|
||||||
|
refreshIntervalMs: 500,
|
||||||
|
maxLogEntries: 10,
|
||||||
|
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
|
||||||
|
);
|
||||||
|
|
||||||
|
const now = Date.now();
|
||||||
|
(engine as any).lastStandxDepthTime = now;
|
||||||
|
(engine as any).lastBinanceDepthTime = now;
|
||||||
|
(engine as any).lastStandxAccountTime = now - 121_000;
|
||||||
|
|
||||||
|
(engine as any).checkDataStaleAndDefense();
|
||||||
|
expect((engine as any).defenseMode).toBe(false);
|
||||||
|
|
||||||
|
await vi.runAllTimersAsync();
|
||||||
|
vi.advanceTimersByTime(1000);
|
||||||
|
(engine as any).checkDataStaleAndDefense();
|
||||||
|
expect((engine as any).defenseMode).toBe(true);
|
||||||
|
engine.stop();
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user