mirror of
https://github.com/discountry/ritmex-bot.git
synced 2026-09-10 08:48:07 +00:00
优化做市引擎、偏移做市引擎和趋势引擎的订单管理逻辑,将待取消订单的类型从数字更改为字符串,确保在处理订单时的类型一致性。同时,增强风险检查逻辑,确保在平仓时使用正确的价格,提升交易策略的稳定性和准确性。
This commit is contained in:
@@ -6,7 +6,7 @@ export async function safeCancelOrder(
|
||||
exchange: ExchangeAdapter,
|
||||
symbol: string,
|
||||
order: AsterOrder,
|
||||
onResolved: (orderId: number) => void,
|
||||
onResolved: (orderId: number | string) => void,
|
||||
onUnknown: () => void,
|
||||
onError: (err: unknown) => void
|
||||
): Promise<void> {
|
||||
@@ -19,4 +19,3 @@ export async function safeCancelOrder(
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
+18
-16
@@ -60,7 +60,7 @@ export class MakerEngine {
|
||||
private readonly locks: OrderLockMap = {};
|
||||
private readonly timers: OrderTimerMap = {};
|
||||
private readonly pending: OrderPendingMap = {};
|
||||
private readonly pendingCancelOrders = new Set<number>();
|
||||
private readonly pendingCancelOrders = new Set<string>();
|
||||
|
||||
private readonly tradeLog: ReturnType<typeof createTradeLog>;
|
||||
private readonly listeners = new Map<MakerEvent, Set<MakerListener>>();
|
||||
@@ -145,7 +145,7 @@ export class MakerEngine {
|
||||
this.openOrders = Array.isArray(orders)
|
||||
? orders.filter((order) => order.type !== "MARKET" && order.symbol === this.config.symbol)
|
||||
: [];
|
||||
const currentIds = new Set(this.openOrders.map((order) => order.orderId));
|
||||
const currentIds = new Set(this.openOrders.map((order) => String(order.orderId)));
|
||||
for (const id of Array.from(this.pendingCancelOrders)) {
|
||||
if (!currentIds.has(id)) {
|
||||
this.pendingCancelOrders.delete(id);
|
||||
@@ -246,6 +246,8 @@ export class MakerEngine {
|
||||
return;
|
||||
}
|
||||
|
||||
const closeBidPrice = roundDownToTick(topBid, this.config.priceTick);
|
||||
const closeAskPrice = roundDownToTick(topAsk, this.config.priceTick);
|
||||
const bidPrice = roundDownToTick(topBid - this.config.bidOffset, this.config.priceTick);
|
||||
const askPrice = roundDownToTick(topAsk + this.config.askOffset, this.config.priceTick);
|
||||
const position = getPosition(this.accountSnapshot, this.config.symbol);
|
||||
@@ -261,14 +263,14 @@ export class MakerEngine {
|
||||
}
|
||||
} else {
|
||||
const closeSide: "BUY" | "SELL" = position.positionAmt > 0 ? "SELL" : "BUY";
|
||||
const closePrice = closeSide === "SELL" ? askPrice : bidPrice;
|
||||
const closePrice = closeSide === "SELL" ? closeAskPrice : closeBidPrice;
|
||||
desired.push({ side: closeSide, price: closePrice, amount: absPosition, reduceOnly: true });
|
||||
}
|
||||
|
||||
this.desiredOrders = desired;
|
||||
this.updateSessionVolume(position);
|
||||
await this.syncOrders(desired);
|
||||
await this.checkRisk(position, bidPrice, askPrice);
|
||||
await this.checkRisk(position, closeBidPrice, closeAskPrice);
|
||||
this.emitUpdate();
|
||||
} catch (error) {
|
||||
if (isRateLimitError(error)) {
|
||||
@@ -291,9 +293,9 @@ export class MakerEngine {
|
||||
if (Math.abs(position.positionAmt) < EPS) return;
|
||||
const { topBid, topAsk } = getTopPrices(this.depthSnapshot);
|
||||
if (topBid == null || topAsk == null) return;
|
||||
const bidPrice = roundDownToTick(topBid - this.config.bidOffset, this.config.priceTick);
|
||||
const askPrice = roundDownToTick(topAsk + this.config.askOffset, this.config.priceTick);
|
||||
await this.checkRisk(position, bidPrice, askPrice);
|
||||
const closeBidPrice = roundDownToTick(topBid, this.config.priceTick);
|
||||
const closeAskPrice = roundDownToTick(topAsk, this.config.priceTick);
|
||||
await this.checkRisk(position, closeBidPrice, closeAskPrice);
|
||||
await this.flushOrders();
|
||||
}
|
||||
|
||||
@@ -328,12 +330,12 @@ export class MakerEngine {
|
||||
|
||||
private async syncOrders(targets: DesiredOrder[]): Promise<void> {
|
||||
const tolerance = this.config.priceChaseThreshold;
|
||||
const availableOrders = this.openOrders.filter((o) => !this.pendingCancelOrders.has(o.orderId));
|
||||
const availableOrders = this.openOrders.filter((o) => !this.pendingCancelOrders.has(String(o.orderId)));
|
||||
const { toCancel, toPlace } = makeOrderPlan(availableOrders, targets, tolerance);
|
||||
|
||||
for (const order of toCancel) {
|
||||
if (this.pendingCancelOrders.has(order.orderId)) continue;
|
||||
this.pendingCancelOrders.add(order.orderId);
|
||||
if (this.pendingCancelOrders.has(String(order.orderId))) continue;
|
||||
this.pendingCancelOrders.add(String(order.orderId));
|
||||
await safeCancelOrder(
|
||||
this.exchange,
|
||||
this.config.symbol,
|
||||
@@ -346,12 +348,12 @@ export class MakerEngine {
|
||||
},
|
||||
() => {
|
||||
this.tradeLog.push("order", "撤销时发现订单已被成交/取消,忽略");
|
||||
this.pendingCancelOrders.delete(order.orderId);
|
||||
this.pendingCancelOrders.delete(String(order.orderId));
|
||||
this.openOrders = this.openOrders.filter((existing) => existing.orderId !== order.orderId);
|
||||
},
|
||||
(error) => {
|
||||
this.tradeLog.push("error", `撤销订单失败: ${String(error)}`);
|
||||
this.pendingCancelOrders.delete(order.orderId);
|
||||
this.pendingCancelOrders.delete(String(order.orderId));
|
||||
this.openOrders = this.openOrders.filter((existing) => existing.orderId !== order.orderId);
|
||||
}
|
||||
);
|
||||
@@ -440,8 +442,8 @@ export class MakerEngine {
|
||||
private async flushOrders(): Promise<void> {
|
||||
if (!this.openOrders.length) return;
|
||||
for (const order of this.openOrders) {
|
||||
if (this.pendingCancelOrders.has(order.orderId)) continue;
|
||||
this.pendingCancelOrders.add(order.orderId);
|
||||
if (this.pendingCancelOrders.has(String(order.orderId))) continue;
|
||||
this.pendingCancelOrders.add(String(order.orderId));
|
||||
await safeCancelOrder(
|
||||
this.exchange,
|
||||
this.config.symbol,
|
||||
@@ -451,12 +453,12 @@ export class MakerEngine {
|
||||
},
|
||||
() => {
|
||||
this.tradeLog.push("order", "订单已不存在,撤销跳过");
|
||||
this.pendingCancelOrders.delete(order.orderId);
|
||||
this.pendingCancelOrders.delete(String(order.orderId));
|
||||
this.openOrders = this.openOrders.filter((existing) => existing.orderId !== order.orderId);
|
||||
},
|
||||
(error) => {
|
||||
this.tradeLog.push("error", `撤销订单失败: ${String(error)}`);
|
||||
this.pendingCancelOrders.delete(order.orderId);
|
||||
this.pendingCancelOrders.delete(String(order.orderId));
|
||||
this.openOrders = this.openOrders.filter((existing) => existing.orderId !== order.orderId);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -54,7 +54,7 @@ export class OffsetMakerEngine {
|
||||
private readonly locks: OrderLockMap = {};
|
||||
private readonly timers: OrderTimerMap = {};
|
||||
private readonly pending: OrderPendingMap = {};
|
||||
private readonly pendingCancelOrders = new Set<number>();
|
||||
private readonly pendingCancelOrders = new Set<string>();
|
||||
|
||||
private readonly tradeLog: ReturnType<typeof createTradeLog>;
|
||||
private readonly listeners = new Map<MakerEvent, Set<MakerListener>>();
|
||||
@@ -145,7 +145,7 @@ export class OffsetMakerEngine {
|
||||
this.openOrders = Array.isArray(orders)
|
||||
? orders.filter((order) => order.type !== "MARKET" && order.symbol === this.config.symbol)
|
||||
: [];
|
||||
const currentIds = new Set(this.openOrders.map((order) => order.orderId));
|
||||
const currentIds = new Set(this.openOrders.map((order) => String(order.orderId)));
|
||||
for (const id of Array.from(this.pendingCancelOrders)) {
|
||||
if (!currentIds.has(id)) {
|
||||
this.pendingCancelOrders.delete(id);
|
||||
@@ -259,6 +259,8 @@ export class OffsetMakerEngine {
|
||||
return;
|
||||
}
|
||||
|
||||
const closeBidPrice = roundDownToTick(topBid!, this.config.priceTick);
|
||||
const closeAskPrice = roundDownToTick(topAsk!, this.config.priceTick);
|
||||
const bidPrice = roundDownToTick(topBid! - this.config.bidOffset, this.config.priceTick);
|
||||
const askPrice = roundDownToTick(topAsk! + this.config.askOffset, this.config.priceTick);
|
||||
const absPosition = Math.abs(position.positionAmt);
|
||||
@@ -275,14 +277,14 @@ export class OffsetMakerEngine {
|
||||
}
|
||||
} else {
|
||||
const closeSide: "BUY" | "SELL" = position.positionAmt > 0 ? "SELL" : "BUY";
|
||||
const closePrice = closeSide === "SELL" ? askPrice : bidPrice;
|
||||
const closePrice = closeSide === "SELL" ? closeAskPrice : closeBidPrice;
|
||||
desired.push({ side: closeSide, price: closePrice, amount: absPosition, reduceOnly: true });
|
||||
}
|
||||
|
||||
this.desiredOrders = desired;
|
||||
this.updateSessionVolume(position);
|
||||
await this.syncOrders(desired);
|
||||
await this.checkRisk(position, bidPrice, askPrice);
|
||||
await this.checkRisk(position, closeBidPrice, closeAskPrice);
|
||||
this.emitUpdate();
|
||||
} catch (error) {
|
||||
if (isRateLimitError(error)) {
|
||||
@@ -306,6 +308,9 @@ export class OffsetMakerEngine {
|
||||
await this.flushOrders();
|
||||
const absPosition = Math.abs(position.positionAmt);
|
||||
const side: "BUY" | "SELL" = position.positionAmt > 0 ? "SELL" : "BUY";
|
||||
const { topBid, topAsk } = getTopPrices(this.depthSnapshot);
|
||||
const closeBidPrice = topBid != null ? roundDownToTick(topBid, this.config.priceTick) : null;
|
||||
const closeAskPrice = topAsk != null ? roundDownToTick(topAsk, this.config.priceTick) : null;
|
||||
try {
|
||||
await marketClose(
|
||||
this.exchange,
|
||||
@@ -319,7 +324,10 @@ export class OffsetMakerEngine {
|
||||
(type, detail) => this.tradeLog.push(type, detail),
|
||||
{
|
||||
markPrice: position.markPrice,
|
||||
expectedPrice: Number(side === "SELL" ? this.depthSnapshot?.bids?.[0]?.[0] : this.depthSnapshot?.asks?.[0]?.[0]) || null,
|
||||
expectedPrice:
|
||||
side === "SELL"
|
||||
? (closeAskPrice != null ? Number(closeAskPrice) : null)
|
||||
: (closeBidPrice != null ? Number(closeBidPrice) : null),
|
||||
maxPct: this.config.maxCloseSlippagePct,
|
||||
}
|
||||
);
|
||||
@@ -423,12 +431,12 @@ export class OffsetMakerEngine {
|
||||
|
||||
private async syncOrders(targets: DesiredOrder[]): Promise<void> {
|
||||
const tolerance = this.config.priceChaseThreshold;
|
||||
const availableOrders = this.openOrders.filter((o) => !this.pendingCancelOrders.has(o.orderId));
|
||||
const availableOrders = this.openOrders.filter((o) => !this.pendingCancelOrders.has(String(o.orderId)));
|
||||
const { toCancel, toPlace } = makeOrderPlan(availableOrders, targets, tolerance);
|
||||
|
||||
for (const order of toCancel) {
|
||||
if (this.pendingCancelOrders.has(order.orderId)) continue;
|
||||
this.pendingCancelOrders.add(order.orderId);
|
||||
if (this.pendingCancelOrders.has(String(order.orderId))) continue;
|
||||
this.pendingCancelOrders.add(String(order.orderId));
|
||||
await safeCancelOrder(
|
||||
this.exchange,
|
||||
this.config.symbol,
|
||||
@@ -442,12 +450,12 @@ export class OffsetMakerEngine {
|
||||
},
|
||||
() => {
|
||||
this.tradeLog.push("order", "撤销时发现订单已被成交/取消,忽略");
|
||||
this.pendingCancelOrders.delete(order.orderId);
|
||||
this.pendingCancelOrders.delete(String(order.orderId));
|
||||
this.openOrders = this.openOrders.filter((existing) => existing.orderId !== order.orderId);
|
||||
},
|
||||
(error) => {
|
||||
this.tradeLog.push("error", `撤销订单失败: ${String(error)}`);
|
||||
this.pendingCancelOrders.delete(order.orderId);
|
||||
this.pendingCancelOrders.delete(String(order.orderId));
|
||||
// 避免同一轮内重复操作同一张已出错的本地挂单,直接从本地缓存移除,等待下一次订单推送重建
|
||||
this.openOrders = this.openOrders.filter((existing) => existing.orderId !== order.orderId);
|
||||
}
|
||||
@@ -534,8 +542,8 @@ export class OffsetMakerEngine {
|
||||
private async flushOrders(): Promise<void> {
|
||||
if (!this.openOrders.length) return;
|
||||
for (const order of this.openOrders) {
|
||||
if (this.pendingCancelOrders.has(order.orderId)) continue;
|
||||
this.pendingCancelOrders.add(order.orderId);
|
||||
if (this.pendingCancelOrders.has(String(order.orderId))) continue;
|
||||
this.pendingCancelOrders.add(String(order.orderId));
|
||||
await safeCancelOrder(
|
||||
this.exchange,
|
||||
this.config.symbol,
|
||||
@@ -545,12 +553,12 @@ export class OffsetMakerEngine {
|
||||
},
|
||||
() => {
|
||||
this.tradeLog.push("order", "订单已不存在,撤销跳过");
|
||||
this.pendingCancelOrders.delete(order.orderId);
|
||||
this.pendingCancelOrders.delete(String(order.orderId));
|
||||
this.openOrders = this.openOrders.filter((existing) => existing.orderId !== order.orderId);
|
||||
},
|
||||
(error) => {
|
||||
this.tradeLog.push("error", `撤销订单失败: ${String(error)}`);
|
||||
this.pendingCancelOrders.delete(order.orderId);
|
||||
this.pendingCancelOrders.delete(String(order.orderId));
|
||||
// 与同步撤单路径保持一致,移除本地异常订单,等待订单流重建
|
||||
this.openOrders = this.openOrders.filter((existing) => existing.orderId !== order.orderId);
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ export class TrendEngine {
|
||||
private prevPositionAmt = 0;
|
||||
private initializedPosition = false;
|
||||
private cancelAllRequested = false;
|
||||
private readonly pendingCancelOrders = new Set<number>();
|
||||
private readonly pendingCancelOrders = new Set<string>();
|
||||
private readonly rateLimit: RateLimitController;
|
||||
|
||||
// 控制入场频率:同一分钟内最多入场一次
|
||||
@@ -166,7 +166,7 @@ export class TrendEngine {
|
||||
this.openOrders = Array.isArray(orders)
|
||||
? orders.filter((order) => order.type !== "MARKET" && order.symbol === this.config.symbol)
|
||||
: [];
|
||||
const currentIds = new Set(this.openOrders.map((order) => order.orderId));
|
||||
const currentIds = new Set(this.openOrders.map((order) => String(order.orderId)));
|
||||
for (const id of Array.from(this.pendingCancelOrders)) {
|
||||
if (!currentIds.has(id)) {
|
||||
this.pendingCancelOrders.delete(id);
|
||||
@@ -588,17 +588,18 @@ export class TrendEngine {
|
||||
try {
|
||||
if (this.openOrders.length > 0) {
|
||||
const orderIdList = this.openOrders.map((order) => order.orderId);
|
||||
const orderIdSet = new Set(orderIdList.map(String));
|
||||
try {
|
||||
await this.exchange.cancelOrders({ symbol: this.config.symbol, orderIdList });
|
||||
orderIdList.forEach((id) => this.pendingCancelOrders.add(id));
|
||||
orderIdSet.forEach((id) => this.pendingCancelOrders.add(id));
|
||||
} catch (err) {
|
||||
if (isUnknownOrderError(err)) {
|
||||
this.tradeLog.push("order", "止损前撤单发现订单已不存在");
|
||||
// 清理本地缓存,避免重复对同一订单执行撤单
|
||||
for (const id of orderIdList) {
|
||||
for (const id of orderIdSet) {
|
||||
this.pendingCancelOrders.delete(id);
|
||||
}
|
||||
this.openOrders = this.openOrders.filter((o) => !orderIdList.includes(o.orderId));
|
||||
this.openOrders = this.openOrders.filter((o) => !orderIdSet.has(String(o.orderId)));
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user