mirror of
https://github.com/discountry/ritmex-bot.git
synced 2026-09-11 17:28:08 +00:00
feat: 更新网格引擎,增强即时平仓订单处理逻辑,添加减仓订单支持以优化利润捕获
This commit is contained in:
+53
-22
@@ -100,12 +100,11 @@ export class GridEngine {
|
|||||||
private static readonly DISAPPEAR_REOPEN_COOLDOWN_MS = 2000;
|
private static readonly DISAPPEAR_REOPEN_COOLDOWN_MS = 2000;
|
||||||
// Track levels awaiting classification after disappearance until next account snapshot confirms
|
// Track levels awaiting classification after disappearance until next account snapshot confirms
|
||||||
private readonly awaitingClassification = new Map<number, { accountVerAtStart: number; absAtStart: number }>();
|
private readonly awaitingClassification = new Map<number, { accountVerAtStart: number; absAtStart: number }>();
|
||||||
private static readonly AWAIT_CLASSIFICATION_TIMEOUT_MS = 2500;
|
|
||||||
private sidesLocked = false;
|
private sidesLocked = false;
|
||||||
private startupCleaned = false;
|
private startupCleaned = false;
|
||||||
private initialCloseHandled = false;
|
private initialCloseHandled = false;
|
||||||
private lastAbsPositionAmt = 0;
|
private lastAbsPositionAmt = 0;
|
||||||
private immediateCloseToPlace: Array<{ sourceLevel: number; targetLevel: number; side: "BUY" | "SELL"; price: string }> = [];
|
private immediateCloseToPlace: Array<{ sourceLevel: number; targetLevel: number; side: "BUY" | "SELL"; price: string; reduceOnly: boolean }> = [];
|
||||||
|
|
||||||
private accountSnapshot: AsterAccountSnapshot | null = null;
|
private accountSnapshot: AsterAccountSnapshot | null = null;
|
||||||
private depthSnapshot: AsterDepth | null = null;
|
private depthSnapshot: AsterDepth | null = null;
|
||||||
@@ -483,6 +482,18 @@ export class GridEngine {
|
|||||||
currentOrderIdsByKey.set(key, set);
|
currentOrderIdsByKey.set(key, set);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Build active order key counts (open and reduce-only) for placement and disappearance handling
|
||||||
|
const activeKeyCounts = new Map<string, number>();
|
||||||
|
for (const o of activeOrders) {
|
||||||
|
const k = this.getOrderKey(o.side, this.normalizePrice(o.price), false);
|
||||||
|
activeKeyCounts.set(k, (activeKeyCounts.get(k) ?? 0) + 1);
|
||||||
|
}
|
||||||
|
const activeKeyCountsRO = new Map<string, number>();
|
||||||
|
for (const o of activeOrders) {
|
||||||
|
const k = this.getOrderKey(o.side, this.normalizePrice(o.price), true);
|
||||||
|
activeKeyCountsRO.set(k, (activeKeyCountsRO.get(k) ?? 0) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
for (const prevKey of prevKeys) {
|
for (const prevKey of prevKeys) {
|
||||||
if (currentKeys.has(prevKey)) continue;
|
if (currentKeys.has(prevKey)) continue;
|
||||||
const meta = prevMeta.get(prevKey);
|
const meta = prevMeta.get(prevKey);
|
||||||
@@ -504,6 +515,20 @@ export class GridEngine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (handledByCloseTracking) continue;
|
if (handledByCloseTracking) continue;
|
||||||
|
// Proactively queue a reduce-only close at target to safely capture profit if the disappearance was a fill
|
||||||
|
if (meta) {
|
||||||
|
const target0 = this.levelMeta[meta.level]?.closeTarget;
|
||||||
|
if (target0 != null) {
|
||||||
|
const priceStr0 = this.formatPrice(this.gridLevels[target0]!);
|
||||||
|
const side0: "BUY" | "SELL" = meta.side === "BUY" ? "SELL" : "BUY";
|
||||||
|
const roKey = this.getOrderKey(side0, priceStr0, true);
|
||||||
|
const roCount = activeKeyCountsRO.get(roKey) ?? 0;
|
||||||
|
if (roCount < 1) {
|
||||||
|
this.immediateCloseToPlace.push({ sourceLevel: meta.level, targetLevel: target0, side: side0, price: priceStr0, reduceOnly: true });
|
||||||
|
this.closeKeyBySourceLevel.set(meta.level, roKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// Use order records to classify disappearance: filled vs canceled
|
// Use order records to classify disappearance: filled vs canceled
|
||||||
if (meta) {
|
if (meta) {
|
||||||
const prevIds = this.lastOrderIdsByKey.get(prevKey);
|
const prevIds = this.lastOrderIdsByKey.get(prevKey);
|
||||||
@@ -527,19 +552,21 @@ export class GridEngine {
|
|||||||
if (classified === "filled") {
|
if (classified === "filled") {
|
||||||
if (meta.side === "BUY") this.pendingLongLevels.add(meta.level);
|
if (meta.side === "BUY") this.pendingLongLevels.add(meta.level);
|
||||||
else this.pendingShortLevels.add(meta.level);
|
else this.pendingShortLevels.add(meta.level);
|
||||||
// Immediately queue a close at the mapped target to ensure it is placed this tick
|
// ensure reduce-only close mapping exists for disappearance tracking (already queued above)
|
||||||
const target = this.levelMeta[meta.level]?.closeTarget;
|
|
||||||
if (target != null) {
|
|
||||||
const priceStr = this.formatPrice(this.gridLevels[target]!);
|
|
||||||
const side: "BUY" | "SELL" = meta.side === "BUY" ? "SELL" : "BUY";
|
|
||||||
this.immediateCloseToPlace.push({ sourceLevel: meta.level, targetLevel: target, side, price: priceStr });
|
|
||||||
// also map source->close key for disappearance tracking
|
|
||||||
this.closeKeyBySourceLevel.set(meta.level, this.getOrderKey(side, priceStr, false));
|
|
||||||
}
|
|
||||||
} else if (classified === "canceled") {
|
} else if (classified === "canceled") {
|
||||||
// no action; allow immediate re-open (do not block)
|
// no action; allow immediate re-open (do not block)
|
||||||
this.awaitingClassification.delete(meta.level);
|
this.awaitingClassification.delete(meta.level);
|
||||||
this.levelReopenBlockUntil.delete(meta.level);
|
this.levelReopenBlockUntil.delete(meta.level);
|
||||||
|
// clear any reduce-only mapping queued earlier
|
||||||
|
const target = this.levelMeta[meta.level]?.closeTarget;
|
||||||
|
if (target != null) {
|
||||||
|
const priceStr = this.formatPrice(this.gridLevels[target]!);
|
||||||
|
const side: "BUY" | "SELL" = meta.side === "BUY" ? "SELL" : "BUY";
|
||||||
|
const roKey = this.getOrderKey(side, priceStr, true);
|
||||||
|
if (this.closeKeyBySourceLevel.get(meta.level) === roKey) {
|
||||||
|
this.closeKeyBySourceLevel.delete(meta.level);
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// unknown: defer decision until we see a newer account snapshot
|
// unknown: defer decision until we see a newer account snapshot
|
||||||
this.awaitingClassification.set(meta.level, { accountVerAtStart: this.accountVersion, absAtStart: this.lastAbsPositionAmt });
|
this.awaitingClassification.set(meta.level, { accountVerAtStart: this.accountVersion, absAtStart: this.lastAbsPositionAmt });
|
||||||
@@ -577,19 +604,15 @@ export class GridEngine {
|
|||||||
const activeKeySet = new Set(
|
const activeKeySet = new Set(
|
||||||
activeOrders.map((o) => this.getOrderKey(o.side, this.normalizePrice(o.price), o.reduceOnly === true))
|
activeOrders.map((o) => this.getOrderKey(o.side, this.normalizePrice(o.price), o.reduceOnly === true))
|
||||||
);
|
);
|
||||||
const activeKeyCounts = new Map<string, number>();
|
// activeKeyCounts and activeKeyCountsRO already computed above
|
||||||
for (const o of activeOrders) {
|
|
||||||
const k = this.getOrderKey(o.side, this.normalizePrice(o.price), false);
|
|
||||||
activeKeyCounts.set(k, (activeKeyCounts.get(k) ?? 0) + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// First, place any immediate close orders queued by fresh fills
|
// First, place any immediate close orders queued by fresh fills
|
||||||
if (this.immediateCloseToPlace.length) {
|
if (this.immediateCloseToPlace.length) {
|
||||||
for (const item of this.immediateCloseToPlace) {
|
for (const item of this.immediateCloseToPlace) {
|
||||||
const key = this.getOrderKey(item.side, item.price, false);
|
const key = this.getOrderKey(item.side, item.price, item.reduceOnly);
|
||||||
const count = activeKeyCounts.get(key) ?? 0;
|
const count = item.reduceOnly ? (activeKeyCountsRO.get(key) ?? 0) : (activeKeyCounts.get(key) ?? 0);
|
||||||
if (count < 2) {
|
if (item.reduceOnly ? count < 1 : count < 2) {
|
||||||
desired.push({ level: item.targetLevel, side: item.side, price: item.price, amount: this.config.orderSize, reduceOnly: false });
|
desired.push({ level: item.targetLevel, side: item.side, price: item.price, amount: this.config.orderSize, reduceOnly: item.reduceOnly });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// clear queue regardless to avoid duplicates next tick
|
// clear queue regardless to avoid duplicates next tick
|
||||||
@@ -625,8 +648,12 @@ export class GridEngine {
|
|||||||
const target = this.levelMeta[source]?.closeTarget;
|
const target = this.levelMeta[source]?.closeTarget;
|
||||||
if (target == null) continue;
|
if (target == null) continue;
|
||||||
const priceStr = this.formatPrice(this.gridLevels[target]!);
|
const priceStr = this.formatPrice(this.gridLevels[target]!);
|
||||||
|
const closeKeyRO = this.getOrderKey("SELL", priceStr, true);
|
||||||
|
if ((activeKeyCountsRO.get(closeKeyRO) ?? 0) < 1) {
|
||||||
|
desired.push({ level: target, side: "SELL", price: priceStr, amount: this.config.orderSize, reduceOnly: true });
|
||||||
|
}
|
||||||
const closeKey = this.getOrderKey("SELL", priceStr, false);
|
const closeKey = this.getOrderKey("SELL", priceStr, false);
|
||||||
if ((activeKeyCounts.get(closeKey) ?? 0) < 2) {
|
if ((activeKeyCounts.get(closeKey) ?? 0) < 1) {
|
||||||
desired.push({ level: target, side: "SELL", price: priceStr, amount: this.config.orderSize, reduceOnly: false });
|
desired.push({ level: target, side: "SELL", price: priceStr, amount: this.config.orderSize, reduceOnly: false });
|
||||||
} else {
|
} else {
|
||||||
// Map existing identical open as the close so disappearance clears pending
|
// Map existing identical open as the close so disappearance clears pending
|
||||||
@@ -639,8 +666,12 @@ export class GridEngine {
|
|||||||
const target = this.levelMeta[source]?.closeTarget;
|
const target = this.levelMeta[source]?.closeTarget;
|
||||||
if (target == null) continue;
|
if (target == null) continue;
|
||||||
const priceStr = this.formatPrice(this.gridLevels[target]!);
|
const priceStr = this.formatPrice(this.gridLevels[target]!);
|
||||||
|
const closeKeyRO = this.getOrderKey("BUY", priceStr, true);
|
||||||
|
if ((activeKeyCountsRO.get(closeKeyRO) ?? 0) < 1) {
|
||||||
|
desired.push({ level: target, side: "BUY", price: priceStr, amount: this.config.orderSize, reduceOnly: true });
|
||||||
|
}
|
||||||
const closeKey = this.getOrderKey("BUY", priceStr, false);
|
const closeKey = this.getOrderKey("BUY", priceStr, false);
|
||||||
if ((activeKeyCounts.get(closeKey) ?? 0) < 2) {
|
if ((activeKeyCounts.get(closeKey) ?? 0) < 1) {
|
||||||
desired.push({ level: target, side: "BUY", price: priceStr, amount: this.config.orderSize, reduceOnly: false });
|
desired.push({ level: target, side: "BUY", price: priceStr, amount: this.config.orderSize, reduceOnly: false });
|
||||||
} else {
|
} else {
|
||||||
if (!this.closeKeyBySourceLevel.has(source)) {
|
if (!this.closeKeyBySourceLevel.has(source)) {
|
||||||
|
|||||||
Reference in New Issue
Block a user