mirror of
https://github.com/discountry/ritmex-bot.git
synced 2026-09-09 08:18:07 +00:00
Add immediate reprice logic to MakerPointsEngine
- Enhanced the MakerPointsEngine by introducing a new method `shouldTriggerImmediateReprice` to trigger an immediate tick when the market depth deviates beyond a specified minimum reprice basis points threshold. - Updated the existing depth protection logic to include this new reprice condition. - Added a comprehensive test suite to validate the immediate reprice functionality and its integration with the MakerPoints engine.
This commit is contained in:
@@ -346,7 +346,7 @@ export class MakerPointsEngine {
|
||||
this.lastStandxDepthTime = Date.now();
|
||||
this.feedStatus.depth = true;
|
||||
this.emitUpdate();
|
||||
if (this.shouldTriggerImmediateDepthProtection(depth)) {
|
||||
if (this.shouldTriggerImmediateDepthProtection(depth) || this.shouldTriggerImmediateReprice(depth)) {
|
||||
this.forceTickRequested = true;
|
||||
void this.tick();
|
||||
}
|
||||
@@ -914,6 +914,24 @@ export class MakerPointsEngine {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 当盘口相对上次报价偏移超过 minRepriceBps 时,立即触发一次主循环,优先撤销旧报价。
|
||||
*/
|
||||
private shouldTriggerImmediateReprice(depth: AsterDepth | null): boolean {
|
||||
if (!depth) return false;
|
||||
if (this.defenseMode || this.reconnectResetPending || this.stopLossProcessing) return false;
|
||||
|
||||
const hasActiveEntryOrders = this.openOrders.some(
|
||||
(order) => order.symbol === this.config.symbol && !order.reduceOnly && isOrderActiveStatus(order.status)
|
||||
);
|
||||
if (!hasActiveEntryOrders) return false;
|
||||
|
||||
const { topBid, topAsk } = getTopPrices(depth);
|
||||
if (topBid == null || topAsk == null) return false;
|
||||
|
||||
return this.shouldReprice(topBid, topAsk);
|
||||
}
|
||||
|
||||
private buildCloseOnlyOrders(
|
||||
position: PositionSnapshot,
|
||||
bid1: number,
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
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";
|
||||
|
||||
private depthListeners: Array<(depth: AsterDepth) => void> = [];
|
||||
|
||||
supportsTrailingStops(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
watchAccount(_cb: (snapshot: AsterAccountSnapshot) => void): void {}
|
||||
watchOrders(_cb: (orders: AsterOrder[]) => void): void {}
|
||||
watchTicker(_symbol: string, _cb: (ticker: AsterTicker) => void): void {}
|
||||
watchKlines(_symbol: string, _interval: string, _cb: (klines: AsterKline[]) => void): void {}
|
||||
|
||||
watchDepth(_symbol: string, cb: (depth: AsterDepth) => void): void {
|
||||
this.depthListeners.push(cb);
|
||||
}
|
||||
|
||||
emitDepth(depth: AsterDepth): void {
|
||||
for (const listener of this.depthListeners) {
|
||||
listener(depth);
|
||||
}
|
||||
}
|
||||
|
||||
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 null;
|
||||
}
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
describe("MakerPointsEngine immediate reprice", () => {
|
||||
it("triggers an immediate tick when min reprice bps threshold is reached", () => {
|
||||
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,
|
||||
},
|
||||
];
|
||||
|
||||
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",
|
||||
});
|
||||
|
||||
expect(tickSpy).toHaveBeenCalledTimes(1);
|
||||
engine.stop();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user