优化做市引擎、偏移做市引擎和趋势引擎的订单管理逻辑,将待取消订单的类型从数字更改为字符串,确保在处理订单时的类型一致性。同时,增强风险检查逻辑,确保在平仓时使用正确的价格,提升交易策略的稳定性和准确性。

This commit is contained in:
discountry
2025-09-27 17:05:51 +08:00
parent dde7ab71a9
commit 0bb22d891f
9 changed files with 217 additions and 92 deletions
+1 -2
View File
@@ -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
View File
@@ -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);
}
);
+22 -14
View File
@@ -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);
}
+6 -5
View File
@@ -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;
}