mirror of
https://github.com/discountry/ritmex-bot.git
synced 2026-09-09 08:18:07 +00:00
i18n: route the remaining 300 hardcoded strings through the table
Grid, offset-maker, liquidity-maker and maker-points wrote their trade log and Telegram alerts as Chinese literals, so LANG=en changed the menu but not a single runtime message. src/ now holds zero user-facing Chinese literals. Reuses rather than duplicates: the four engines' subscription boilerplate maps onto the existing log.subscribe.* / log.process.* keys, and the spot-maker wording shared by offset-maker and liquidity-maker became one log.spotMaker.* set instead of two. Exchange gateways were treated differently on purpose: aster's exception and console.error text is developer diagnostics, not UI, and its eight sibling gateways already used English — so those were translated to English rather than added to the table. Adds tests/i18n-coverage.test.ts to hold the line: it fails on any new CJK literal in src/, on a key missing either language, and on a duplicate key. Several existing tests asserted the Chinese literals, which is what let the gap persist; they now assert the resolved key and hold in either language. 214 new keys (300 -> 514). 275 pass; tsc and oxlint clean.
This commit is contained in:
@@ -534,7 +534,7 @@ export class AsterSpotRestClient {
|
||||
try {
|
||||
response = await fetch(url, init);
|
||||
} catch (error) {
|
||||
throw new Error(`[AsterSpotRestClient] 请求失败 ${String(error)}`);
|
||||
throw new Error(`[AsterSpotRestClient] request failed: ${String(error)}`);
|
||||
}
|
||||
const text = await response.text();
|
||||
if (!response.ok) {
|
||||
@@ -546,7 +546,7 @@ export class AsterSpotRestClient {
|
||||
try {
|
||||
return JSON.parse(text) as T;
|
||||
} catch {
|
||||
throw new Error(`[AsterSpotRestClient] 无法解析响应: ${text.slice(0, 200)}`);
|
||||
throw new Error(`[AsterSpotRestClient] could not parse response: ${text.slice(0, 200)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -789,7 +789,7 @@ export class AsterRestClient {
|
||||
try {
|
||||
response = await fetch(url);
|
||||
} catch (error) {
|
||||
throw new Error(`[AsterRestClient] 获取交易规则失败 ${String(error)}`);
|
||||
throw new Error(`[AsterRestClient] failed to fetch exchange info: ${String(error)}`);
|
||||
}
|
||||
const text = await response.text();
|
||||
if (!response.ok) {
|
||||
@@ -798,7 +798,7 @@ export class AsterRestClient {
|
||||
try {
|
||||
return JSON.parse(text) as AsterFuturesExchangeInfo;
|
||||
} catch {
|
||||
throw new Error(`[AsterRestClient] 无法解析交易规则响应: ${text.slice(0, 200)}`);
|
||||
throw new Error(`[AsterRestClient] could not parse exchange info: ${text.slice(0, 200)}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -863,7 +863,7 @@ export class AsterRestClient {
|
||||
try {
|
||||
response = await fetch(url);
|
||||
} catch (error) {
|
||||
throw new Error(`[AsterRestClient] 获取K线失败 ${String(error)}`);
|
||||
throw new Error(`[AsterRestClient] failed to fetch klines: ${String(error)}`);
|
||||
}
|
||||
const text = await response.text();
|
||||
if (!response.ok) {
|
||||
@@ -873,7 +873,7 @@ export class AsterRestClient {
|
||||
const payload = JSON.parse(text) as any[];
|
||||
return payload.map((entry) => fromRestKline(entry, interval, upper));
|
||||
} catch {
|
||||
throw new Error(`[AsterRestClient] 无法解析K线响应: ${text.slice(0, 200)}`);
|
||||
throw new Error(`[AsterRestClient] could not parse klines: ${text.slice(0, 200)}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -892,7 +892,7 @@ export class AsterRestClient {
|
||||
try {
|
||||
response = await fetch(url);
|
||||
} catch (error) {
|
||||
throw new Error(`[AsterRestClient] 获取资金费率失败 ${String(error)}`);
|
||||
throw new Error(`[AsterRestClient] failed to fetch funding rate: ${String(error)}`);
|
||||
}
|
||||
const text = await response.text();
|
||||
if (!response.ok) {
|
||||
@@ -903,7 +903,7 @@ export class AsterRestClient {
|
||||
// The response shape mirrors Binance: { symbol, markPrice, indexPrice, lastFundingRate, nextFundingTime, time }
|
||||
return payload;
|
||||
} catch {
|
||||
throw new Error(`[AsterRestClient] 无法解析资金费率响应: ${text.slice(0, 200)}`);
|
||||
throw new Error(`[AsterRestClient] could not parse funding rate: ${text.slice(0, 200)}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -937,7 +937,7 @@ export class AsterRestClient {
|
||||
try {
|
||||
response = await fetch(url, init);
|
||||
} catch (error) {
|
||||
throw new Error(`[AsterRestClient] 请求失败 ${String(error)}`);
|
||||
throw new Error(`[AsterRestClient] request failed: ${String(error)}`);
|
||||
}
|
||||
const text = await response.text();
|
||||
if (!response.ok) {
|
||||
@@ -946,7 +946,7 @@ export class AsterRestClient {
|
||||
try {
|
||||
return JSON.parse(text) as T;
|
||||
} catch {
|
||||
throw new Error(`[AsterRestClient] 无法解析响应: ${text.slice(0, 200)}`);
|
||||
throw new Error(`[AsterRestClient] could not parse response: ${text.slice(0, 200)}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1037,7 +1037,7 @@ export class AsterPublicStreams {
|
||||
try {
|
||||
payload = JSON.parse(event.data);
|
||||
} catch (error) {
|
||||
console.error("[AsterPublicStreams] 无法解析消息", error, event.data);
|
||||
console.error("[AsterPublicStreams] could not parse message", error, event.data);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
@@ -1198,7 +1198,7 @@ export class AsterUserStream {
|
||||
try {
|
||||
payload = JSON.parse(event.data);
|
||||
} catch (error) {
|
||||
console.error("[AsterUserStream] 无法解析消息", error, event.data);
|
||||
console.error("[AsterUserStream] could not parse message", error, event.data);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
@@ -1509,7 +1509,7 @@ export class AsterGateway {
|
||||
positions = latestPositions;
|
||||
}
|
||||
} catch (positionError) {
|
||||
console.error("[AsterGateway] 刷新持仓失败", positionError);
|
||||
console.error("[AsterGateway] failed to refresh positions", positionError);
|
||||
}
|
||||
const normalizedPositions = clonePositions(positions);
|
||||
const snapshot: AccountSnapshot = {
|
||||
@@ -1521,7 +1521,7 @@ export class AsterGateway {
|
||||
this.accountSnapshot = snapshot;
|
||||
this.accountEvent.emit(snapshot);
|
||||
} catch (error) {
|
||||
console.error("[AsterGateway] 刷新账户信息失败", error);
|
||||
console.error("[AsterGateway] failed to refresh account", error);
|
||||
}
|
||||
try {
|
||||
const orders = await this.rest.getOpenOrders();
|
||||
@@ -1529,7 +1529,7 @@ export class AsterGateway {
|
||||
orders.forEach((order) => mergeOrderSnapshot(this.openOrders, order));
|
||||
this.ordersEvent.emit(Array.from(this.openOrders.values()));
|
||||
} catch (error) {
|
||||
console.error("[AsterGateway] 刷新挂单失败", error);
|
||||
console.error("[AsterGateway] failed to refresh open orders", error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1573,7 +1573,7 @@ export class AsterGateway {
|
||||
this.accountSnapshot = nextSnapshot;
|
||||
this.accountEvent.emit(nextSnapshot);
|
||||
} catch (error) {
|
||||
console.error("[AsterGateway] 同步持仓失败", error);
|
||||
console.error("[AsterGateway] failed to sync positions", error);
|
||||
} finally {
|
||||
this.positionSyncInFlight = false;
|
||||
}
|
||||
@@ -1608,7 +1608,7 @@ export class AsterGateway {
|
||||
try {
|
||||
exchangeInfo = await this.loadExchangeInfo();
|
||||
} catch (error) {
|
||||
console.error("[AsterGateway] 获取交易规则失败", error);
|
||||
console.error("[AsterGateway] failed to fetch exchange info", error);
|
||||
return null;
|
||||
}
|
||||
const symbols = exchangeInfo?.symbols ?? [];
|
||||
|
||||
@@ -177,7 +177,7 @@ function loadSignatureProviderFromEnv(
|
||||
return loaded.default as GrvtSignatureProvider;
|
||||
}
|
||||
console.warn(
|
||||
`[GrvtExchangeAdapter] 模块 ${resolved} 未导出签名函数 (function default export)`
|
||||
`[GrvtExchangeAdapter] module ${resolved} does not export a signing function (function default export)`
|
||||
);
|
||||
} catch (error) {
|
||||
const log = logger ?? ((ctx, err) => console.error(`[GrvtExchangeAdapter] ${ctx}`, err));
|
||||
|
||||
@@ -860,6 +860,525 @@ const translations: Record<string, TranslationEntry> = {
|
||||
zh: "切换逐仓模式失败: {error}",
|
||||
en: "Failed to switch to isolated margin: {error}",
|
||||
},
|
||||
// --- strategy/grid-logic ---
|
||||
"log.grid.entryFilled": {
|
||||
zh: "ENTRY 成交: {side} @ {price} (线 {level})",
|
||||
en: "ENTRY filled: {side} @ {price} (level {level})",
|
||||
},
|
||||
"log.grid.orphanExitFilled": {
|
||||
zh: "孤儿 EXIT 成交: {side} @ {price}",
|
||||
en: "Orphan EXIT filled: {side} @ {price}",
|
||||
},
|
||||
"log.grid.exitFilled": {
|
||||
zh: "EXIT 成交: {side} @ {price} (释放线 {level})",
|
||||
en: "EXIT filled: {side} @ {price} (level {level} released)",
|
||||
},
|
||||
"log.grid.entryCancelled": {
|
||||
zh: "ENTRY 撤销: {side} @ {price} (线 {level})",
|
||||
en: "ENTRY cancelled: {side} @ {price} (level {level})",
|
||||
},
|
||||
"log.grid.exitCancelled": {
|
||||
zh: "EXIT 撤销: {side} @ {price} (线 {level})",
|
||||
en: "EXIT cancelled: {side} @ {price} (level {level})",
|
||||
},
|
||||
"log.grid.orderVanished": {
|
||||
zh: "订单消失待判定: {intent} {side} @ {price}",
|
||||
en: "Order vanished, outcome unknown: {intent} {side} @ {price}",
|
||||
},
|
||||
"log.grid.belowLowerBound": {
|
||||
zh: "价格跌破网格下边界 {pct}%",
|
||||
en: "Price fell {pct}% below the grid's lower bound",
|
||||
},
|
||||
"log.grid.aboveUpperBound": {
|
||||
zh: "价格突破网格上边界 {pct}%",
|
||||
en: "Price rose {pct}% above the grid's upper bound",
|
||||
},
|
||||
"log.grid.coverageAuditClose": {
|
||||
zh: "覆盖审计: 未覆盖 {qty} 且{cause},市价平仓",
|
||||
en: "Coverage audit: {qty} uncovered and {cause}; closing at market",
|
||||
},
|
||||
"log.grid.causeOutOfRange": { zh: "价格已出区间", en: "price left the range" },
|
||||
"log.grid.causeLossExceeded": { zh: "浮亏超限", en: "unrealised loss exceeded the limit" },
|
||||
"log.grid.coverageAuditReason": { zh: "覆盖审计止损", en: "Coverage audit stop" },
|
||||
"log.grid.coverageAuditRepost": {
|
||||
zh: "覆盖审计: 未覆盖 {qty},补挂平仓单 @ {price}",
|
||||
en: "Coverage audit: {qty} uncovered; reposting exit @ {price}",
|
||||
},
|
||||
"log.grid.shiftOutOfRange": {
|
||||
zh: "价格越界,启动移格: {reason}",
|
||||
en: "Price out of range; starting grid shift: {reason}",
|
||||
},
|
||||
"log.grid.shiftAnchorDrift": {
|
||||
zh: "价格偏离锚定价超阈值,启动移格 (anchor={anchor} → {price})",
|
||||
en: "Price drifted past the anchor threshold; starting grid shift (anchor={anchor} → {price})",
|
||||
},
|
||||
"log.grid.adoptOrphanExit": {
|
||||
zh: "收编平仓方向挂单为孤儿 EXIT: {side} @ {price}",
|
||||
en: "Adopted an unattributed exit-side order as orphan EXIT: {side} @ {price}",
|
||||
},
|
||||
"log.grid.cancelUnattributable": {
|
||||
zh: "撤销无法归属的挂单: {side} @ {price}",
|
||||
en: "Cancelling unattributable order: {side} @ {price}",
|
||||
},
|
||||
"log.grid.inflightMatched": {
|
||||
zh: "inflight 归属确认: {intent} {side} @ {price}",
|
||||
en: "In-flight order matched: {intent} {side} @ {price}",
|
||||
},
|
||||
"log.grid.cancelStaleVersion": {
|
||||
zh: "撤销过期网格版本挂单: {clientOrderId}",
|
||||
en: "Cancelling order from a stale grid version: {clientOrderId}",
|
||||
},
|
||||
"log.grid.orphanResidual": {
|
||||
zh: "对账残余孤儿仓位: {qty}",
|
||||
en: "Reconciliation left an orphan position: {qty}",
|
||||
},
|
||||
// --- strategy/grid-engine ---
|
||||
"log.gridEngine.configInvalid": { zh: "配置无效,已暂停网格", en: "Invalid config; grid paused" },
|
||||
"log.gridEngine.wsDisconnected": {
|
||||
zh: "WebSocket 断连 ({symbol}),冻结网格下单",
|
||||
en: "WebSocket disconnected ({symbol}); freezing grid orders",
|
||||
},
|
||||
"log.gridEngine.wsReconnected": {
|
||||
zh: "WebSocket 重连成功 ({symbol}),下一轮执行对账",
|
||||
en: "WebSocket reconnected ({symbol}); reconciling next tick",
|
||||
},
|
||||
"log.gridEngine.loadStateFailed": {
|
||||
zh: "加载网格状态失败: {error}",
|
||||
en: "Failed to load grid state: {error}",
|
||||
},
|
||||
"log.gridEngine.stateRestored": {
|
||||
zh: "已从磁盘恢复网格状态: gridVersion={gridVersion} anchor={anchor} 区间=[{lower}, {upper}]{shift}",
|
||||
en: "Restored grid state from disk: gridVersion={gridVersion} anchor={anchor} range=[{lower}, {upper}]{shift}",
|
||||
},
|
||||
"log.gridEngine.stateRestoredShift": {
|
||||
zh: " 移格续跑({phase})",
|
||||
en: " resuming shift ({phase})",
|
||||
},
|
||||
"log.gridEngine.fingerprintMismatch": {
|
||||
zh: "磁盘网格状态与当前配置指纹不一致,全新建格并执行孤儿扫描",
|
||||
en: "Stored grid state does not match the current config; rebuilding and scanning for orphans",
|
||||
},
|
||||
"log.gridEngine.gridCreated": {
|
||||
zh: "以锚定价 {anchor} 建立网格 ({mode})",
|
||||
en: "Grid created at anchor {anchor} ({mode})",
|
||||
},
|
||||
"log.gridEngine.initFailed": { zh: "网格初始化失败: {error}", en: "Grid init failed: {error}" },
|
||||
"log.gridEngine.reconcileEvent": { zh: "[对账:{source}] {event}", en: "[reconcile:{source}] {event}" },
|
||||
"log.gridEngine.reconcileCancelled": {
|
||||
zh: "[对账:{source}] 撤销 {count} 个无法归属的挂单",
|
||||
en: "[reconcile:{source}] cancelled {count} unattributable orders",
|
||||
},
|
||||
"log.gridEngine.reconcileCancelFailed": {
|
||||
zh: "[对账:{source}] 撤单失败: {error}",
|
||||
en: "[reconcile:{source}] cancel failed: {error}",
|
||||
},
|
||||
"log.gridEngine.reconcileOrdersFailed": {
|
||||
zh: "[对账:{source}] REST 查询挂单失败: {error}",
|
||||
en: "[reconcile:{source}] REST open-order query failed: {error}",
|
||||
},
|
||||
"log.gridEngine.reconcileAccountFailed": {
|
||||
zh: "[对账:{source}] REST 查询账户失败: {error}",
|
||||
en: "[reconcile:{source}] REST account query failed: {error}",
|
||||
},
|
||||
"log.gridEngine.tickFailed": { zh: "网格轮询异常: {error}", en: "Grid tick failed: {error}" },
|
||||
"log.gridEngine.shiftStarting": {
|
||||
zh: "启动智能移格,目标锚定价 {anchor}",
|
||||
en: "Starting grid shift to anchor {anchor}",
|
||||
},
|
||||
"log.gridEngine.orderFeedStalled": {
|
||||
zh: "订单流疑似停滞(下单后长时间未反映),暂停新下单",
|
||||
en: "Order feed looks stalled (placements are not showing up); pausing new orders",
|
||||
},
|
||||
"log.gridEngine.placeFailed": {
|
||||
zh: "挂单失败 ({side} @ {price}): {error}",
|
||||
en: "Failed to place order ({side} @ {price}): {error}",
|
||||
},
|
||||
"log.gridEngine.closeSlippageBlocked": {
|
||||
zh: "市价平仓滑点守卫触发 ({reason}): close={close} mark={mark} 偏离 {pct}% > {limit}%,暂缓",
|
||||
en: "Market close blocked by slippage guard ({reason}): close={close} mark={mark} deviates {pct}% > {limit}%; holding off",
|
||||
},
|
||||
"log.gridEngine.closed": { zh: "市价平仓 {side} {qty} ({reason})", en: "Market close {side} {qty} ({reason})" },
|
||||
"log.gridEngine.closeFailed": {
|
||||
zh: "市价平仓失败 ({reason}): {error}",
|
||||
en: "Market close failed ({reason}): {error}",
|
||||
},
|
||||
"log.gridEngine.shiftCancelRequested": {
|
||||
zh: "移格: 已请求撤销全部挂单",
|
||||
en: "Shift: requested cancellation of all orders",
|
||||
},
|
||||
"log.gridEngine.shiftCancelFailed": { zh: "移格撤单失败: {error}", en: "Shift cancel failed: {error}" },
|
||||
"log.gridEngine.shiftCloseReason": { zh: "移格平仓", en: "Grid shift close" },
|
||||
"log.gridEngine.shiftCloseDeferred": {
|
||||
zh: "移格: 平仓被滑点守卫暂缓,下轮重试",
|
||||
en: "Shift: close deferred by the slippage guard; retrying next tick",
|
||||
},
|
||||
"log.gridEngine.shiftDone": {
|
||||
zh: "移格完成: 新锚定价 {anchor},区间 [{lower}, {upper}],gridVersion={gridVersion}",
|
||||
en: "Shift complete: anchor {anchor}, range [{lower}, {upper}], gridVersion={gridVersion}",
|
||||
},
|
||||
"log.gridEngine.stopCancelledFlat": {
|
||||
zh: "已撤销交易所兜底止损单(仓位归零)",
|
||||
en: "Cancelled the exchange stop order (position is flat)",
|
||||
},
|
||||
"log.gridEngine.stopCancelFailed": {
|
||||
zh: "撤销兜底止损单失败: {error}",
|
||||
en: "Failed to cancel the exchange stop: {error}",
|
||||
},
|
||||
"log.gridEngine.stopCancelStaleFailed": {
|
||||
zh: "撤销旧兜底止损单失败: {error}",
|
||||
en: "Failed to cancel the previous exchange stop: {error}",
|
||||
},
|
||||
"log.gridEngine.stopPlaceFailed": {
|
||||
zh: "挂兜底止损单失败: {error}",
|
||||
en: "Failed to place the exchange stop: {error}",
|
||||
},
|
||||
"log.gridEngine.haltStarting": {
|
||||
zh: "{reason},开始执行撤单与平仓",
|
||||
en: "{reason}; cancelling orders and closing out",
|
||||
},
|
||||
"log.gridEngine.allCancelled": { zh: "已撤销全部网格挂单", en: "Cancelled all grid orders" },
|
||||
"log.gridEngine.cancelAllFailed": {
|
||||
zh: "撤销网格挂单失败: {error}",
|
||||
en: "Failed to cancel grid orders: {error}",
|
||||
},
|
||||
"log.gridEngine.stopCloseDeferred": {
|
||||
zh: "止损平仓被滑点守卫暂缓,下轮重试",
|
||||
en: "Stop close deferred by the slippage guard; retrying next tick",
|
||||
},
|
||||
"log.gridEngine.resumed": {
|
||||
zh: "价格重新回到网格区间,恢复网格运行 (gridVersion={gridVersion})",
|
||||
en: "Price re-entered the grid range; resuming (gridVersion={gridVersion})",
|
||||
},
|
||||
"log.gridEngine.saveStateFailed": {
|
||||
zh: "保存网格状态失败: {error}",
|
||||
en: "Failed to save grid state: {error}",
|
||||
},
|
||||
// --- offset-maker / liquidity-maker (shared wording) ---
|
||||
"log.subscribe.klineFail": { zh: "订阅K线失败: {error}", en: "Failed to subscribe klines: {error}" },
|
||||
"log.process.klineError": { zh: "K线推送处理异常: {error}", en: "Kline update handler error: {error}" },
|
||||
"log.spotMaker.belowMinSellHold": {
|
||||
zh: "现货持仓低于最小卖单量,暂不挂卖单",
|
||||
en: "Spot balance is below the minimum sell size; holding off on sell orders",
|
||||
},
|
||||
"log.spotMaker.belowMinSellSkip": {
|
||||
zh: "现货持仓低于最小卖单量,跳过卖单",
|
||||
en: "Spot balance is below the minimum sell size; skipping the sell order",
|
||||
},
|
||||
"log.spotMaker.buyOnlyOnGreenCandle": {
|
||||
zh: "现货买入仅在1m阳线,当前跳过买单",
|
||||
en: "Spot buys only on a green 1m candle; skipping the buy order",
|
||||
},
|
||||
"log.spotMaker.quoteBalanceShort": {
|
||||
zh: "现货可用报价资产不足,跳过买单",
|
||||
en: "Not enough quote asset available; skipping the buy order",
|
||||
},
|
||||
"log.spotMaker.baseBalanceShort": {
|
||||
zh: "现货可用基础资产不足,跳过卖单",
|
||||
en: "Not enough base asset available; skipping the sell order",
|
||||
},
|
||||
"log.spotMaker.spreadTooTightBuy": {
|
||||
zh: "跳过买单:价差不足以构造maker价格",
|
||||
en: "Skipping the buy order: the spread is too tight for a maker price",
|
||||
},
|
||||
"log.spotMaker.spreadTooTightSell": {
|
||||
zh: "跳过卖单:价差不足以构造maker价格",
|
||||
en: "Skipping the sell order: the spread is too tight for a maker price",
|
||||
},
|
||||
"log.spotMaker.sellBelowMinNotional": {
|
||||
zh: "现货卖单低于最小成交量,跳过挂单等待累积",
|
||||
en: "Sell size is below the venue minimum; waiting to accumulate",
|
||||
},
|
||||
"log.spotMaker.belowMinCloseSkipStop": {
|
||||
zh: "现货持仓低于最小平仓数量,跳过止损检查",
|
||||
en: "Spot position is below the minimum close size; skipping the stop check",
|
||||
},
|
||||
"log.spotMaker.rateLimitCloseMissing": {
|
||||
zh: "限频强制平仓时订单已不存在",
|
||||
en: "Order already gone during the rate-limit forced close",
|
||||
},
|
||||
"log.spotMaker.rateLimitCloseFailed": {
|
||||
zh: "限频强制平仓失败: {error}",
|
||||
en: "Rate-limit forced close failed: {error}",
|
||||
},
|
||||
"log.spotMaker.startupCleanup": { zh: "启动时清理历史挂单", en: "Cancelling stale orders on startup" },
|
||||
"log.spotMaker.startupCleanupGone": {
|
||||
zh: "历史挂单已消失,跳过启动清理",
|
||||
en: "Stale orders already gone; skipping startup cleanup",
|
||||
},
|
||||
"log.spotMaker.startupCancelFailed": {
|
||||
zh: "启动撤单失败: {error}",
|
||||
en: "Startup cancel failed: {error}",
|
||||
},
|
||||
"log.spotMaker.cancelMismatched": {
|
||||
zh: "撤销不匹配订单 {side} @ {price} reduceOnly={reduceOnly}",
|
||||
en: "Cancelling mismatched order {side} @ {price} reduceOnly={reduceOnly}",
|
||||
},
|
||||
"log.spotMaker.cancelAlreadySettled": {
|
||||
zh: "撤销时发现订单已被成交/取消,忽略",
|
||||
en: "Order was already filled or cancelled; ignoring",
|
||||
},
|
||||
"log.spotMaker.cancelFailed": { zh: "撤销订单失败: {error}", en: "Failed to cancel order: {error}" },
|
||||
"log.spotMaker.orderMissingOnCancel": {
|
||||
zh: "订单已不存在,撤销跳过",
|
||||
en: "Order no longer exists; skipping cancel",
|
||||
},
|
||||
"log.spotMaker.dustCloseFailed": {
|
||||
zh: "小额市价平仓失败: {error}",
|
||||
en: "Dust market close failed: {error}",
|
||||
},
|
||||
"log.spotMaker.dustClose": {
|
||||
zh: "小额仓位使用市价平仓 {side} 数量 {qty}",
|
||||
en: "Closing dust position at market: {side} qty {qty}",
|
||||
},
|
||||
"log.spotMaker.placeFailed": {
|
||||
zh: "挂单失败({side} {price}): {error}",
|
||||
en: "Failed to place order ({side} {price}): {error}",
|
||||
},
|
||||
"log.spotMaker.spotStop": {
|
||||
zh: "现货止损,当前仓位={qty} PnL={pnl} USDT",
|
||||
en: "Spot stop-loss: position={qty} PnL={pnl} USDT",
|
||||
},
|
||||
"log.spotMaker.spotStopFailed": { zh: "现货止损失败: {error}", en: "Spot stop-loss failed: {error}" },
|
||||
"log.spotMaker.stopCloseMissing": {
|
||||
zh: "止损平仓时订单已不存在",
|
||||
en: "Order already gone while closing on stop",
|
||||
},
|
||||
"log.spotMaker.stopCloseFailed": { zh: "止损平仓失败: {error}", en: "Stop close failed: {error}" },
|
||||
"log.spotMaker.entryPricePending": {
|
||||
zh: "做市持仓均价未同步,等待账户快照刷新后再执行止损判断",
|
||||
en: "Entry price not synced yet; waiting for an account refresh before evaluating the stop",
|
||||
},
|
||||
"log.spotMaker.stopTriggered": {
|
||||
zh: "触发止损,方向={direction} 当前亏损={pnl} USDT",
|
||||
en: "Stop-loss triggered: direction={direction} loss={pnl} USDT",
|
||||
},
|
||||
"log.spotMaker.updateHandlerError": {
|
||||
zh: "更新回调处理异常: {error}",
|
||||
en: "Update handler error: {error}",
|
||||
},
|
||||
"log.spotMaker.snapshotDispatchError": {
|
||||
zh: "快照或更新分发异常: {error}",
|
||||
en: "Snapshot/update dispatch error: {error}",
|
||||
},
|
||||
"log.offsetMaker.tickFailed": { zh: "偏移做市循环异常: {error}", en: "Offset maker tick failed: {error}" },
|
||||
"log.offsetMaker.imbalanceClose": {
|
||||
zh: "深度极端不平衡({buySum} vs {sellSum}), 市价平仓 {side}",
|
||||
en: "Extreme depth imbalance ({buySum} vs {sellSum}); closing {side} at market",
|
||||
},
|
||||
"log.offsetMaker.imbalanceCloseMissing": {
|
||||
zh: "深度不平衡平仓时订单已不存在",
|
||||
en: "Order already gone during the imbalance close",
|
||||
},
|
||||
"log.offsetMaker.imbalanceCloseFailed": {
|
||||
zh: "深度不平衡平仓失败: {error}",
|
||||
en: "Imbalance close failed: {error}",
|
||||
},
|
||||
"log.liquidityMaker.tickFailed": {
|
||||
zh: "流动性做市循环异常: {error}",
|
||||
en: "Liquidity maker tick failed: {error}",
|
||||
},
|
||||
"log.liquidityMaker.fillDetected": {
|
||||
zh: "检测到成交: {side} {qty} @ {price}",
|
||||
en: "Fill detected: {side} {qty} @ {price}",
|
||||
},
|
||||
"log.liquidityMaker.exitRaisedToBreakeven": {
|
||||
zh: "平仓价调整为入场价+1tick以确保不亏本: {price}",
|
||||
en: "Exit raised to entry+1 tick to stay at or above breakeven: {price}",
|
||||
},
|
||||
"log.liquidityMaker.exitLoweredToBreakeven": {
|
||||
zh: "平仓价调整为入场价-1tick以确保不亏本: {price}",
|
||||
en: "Exit lowered to entry-1 tick to stay at or above breakeven: {price}",
|
||||
},
|
||||
// --- strategy/maker-points-engine ---
|
||||
"log.mp.binanceError": { zh: "Binance {context} 异常: {error}", en: "Binance {context} error: {error}" },
|
||||
"log.mp.binanceDisconnected": { zh: "Binance 深度连接断开", en: "Binance depth feed disconnected" },
|
||||
"log.mp.binanceStale": { zh: "Binance 深度数据过时", en: "Binance depth data is stale" },
|
||||
"log.mp.binanceRecovered": { zh: "Binance 深度连接恢复", en: "Binance depth feed recovered" },
|
||||
"log.mp.wsDisconnected": {
|
||||
zh: "WebSocket 断连 ({symbol}),启动断连保护",
|
||||
en: "WebSocket disconnected ({symbol}); engaging disconnect protection",
|
||||
},
|
||||
"log.mp.wsReconnected": {
|
||||
zh: "WebSocket 重连成功 ({symbol}),开始重连保护流程",
|
||||
en: "WebSocket reconnected ({symbol}); running reconnect protection",
|
||||
},
|
||||
"log.mp.reconnectFoundOrders": {
|
||||
zh: "重连后查询到 {count} 个挂单",
|
||||
en: "Found {count} open orders after reconnecting",
|
||||
},
|
||||
"log.mp.reconnectCancelled": { zh: "重连保护:已取消所有挂单", en: "Reconnect protection: cancelled all orders" },
|
||||
"log.mp.reconnectCancelPartial": {
|
||||
zh: "重连保护:取消挂单未完全成功,将在下次循环重试",
|
||||
en: "Reconnect protection: cancellation incomplete; retrying next tick",
|
||||
},
|
||||
"log.mp.reconnectFailed": { zh: "重连保护流程失败: {error}", en: "Reconnect protection failed: {error}" },
|
||||
"log.mp.closeOnlyEntered": { zh: "进入平仓模式,仅挂 reduce-only", en: "Entered close-only mode; reduce-only quotes" },
|
||||
"log.mp.closeOnlyExited": { zh: "退出平仓模式", en: "Left close-only mode" },
|
||||
"log.mp.depthImbalancePause": {
|
||||
zh: "Binance 深度失衡,暂停 {summary} 挂单",
|
||||
en: "Binance depth imbalance; pausing {summary} quotes",
|
||||
},
|
||||
"log.mp.depthImbalanceResume": { zh: "Binance 深度恢复,继续挂单", en: "Binance depth recovered; resuming quotes" },
|
||||
"log.mp.rateLimited": { zh: "限频触发,暂停挂单: {error}", en: "Rate limited; pausing quotes: {error}" },
|
||||
"log.mp.tickFailed": { zh: "MakerPoints 主循环异常: {error}", en: "MakerPoints tick failed: {error}" },
|
||||
"log.mp.precisionErrorResync": {
|
||||
zh: "检测到精度错误,重新同步: {error}",
|
||||
en: "Precision error detected; resyncing: {error}",
|
||||
},
|
||||
"log.mp.placeFailed": { zh: "挂单失败 {side} @ {price}: {error}", en: "Failed to place {side} @ {price}: {error}" },
|
||||
"log.mp.unexpectedOrders": {
|
||||
zh: "发现 {count} 个未预期挂单,执行强制取消",
|
||||
en: "Found {count} unexpected orders; force-cancelling",
|
||||
},
|
||||
"log.mp.forceCancelled": {
|
||||
zh: "已强制取消所有挂单,重置本地状态",
|
||||
en: "Force-cancelled all orders and reset local state",
|
||||
},
|
||||
"log.mp.verifyOrdersFailed": { zh: "验证挂单状态失败: {error}", en: "Failed to verify order state: {error}" },
|
||||
"log.mp.stopTriggered": {
|
||||
zh: "触发止损: 实时未实现亏损 {pnl} USDT",
|
||||
en: "Stop-loss triggered: live unrealised loss {pnl} USDT",
|
||||
},
|
||||
"log.mp.stopSucceeded": { zh: "止损成功: 仓位已清零", en: "Stop-loss done: position is flat" },
|
||||
"log.mp.stopOrderMissing": {
|
||||
zh: "止损平仓时订单已不存在,继续检查仓位",
|
||||
en: "Order already gone during the stop close; rechecking the position",
|
||||
},
|
||||
"log.mp.stopPrecisionResync": {
|
||||
zh: "止损平仓精度错误,重新同步: {error}",
|
||||
en: "Precision error during the stop close; resyncing: {error}",
|
||||
},
|
||||
"log.mp.stopRetry": {
|
||||
zh: "止损平仓失败 (重试 {attempt}/{max}): {error}",
|
||||
en: "Stop close failed (retry {attempt}/{max}): {error}",
|
||||
},
|
||||
"log.mp.stopRetriesExhausted": {
|
||||
zh: "止损重试已达上限 ({max} 次),请手动检查仓位",
|
||||
en: "Stop retries exhausted ({max}); check the position manually",
|
||||
},
|
||||
"log.mp.updateHandlerError": { zh: "更新监听异常: {error}", en: "Update listener error: {error}" },
|
||||
"log.mp.snapshotError": { zh: "快照生成异常: {error}", en: "Snapshot build error: {error}" },
|
||||
"log.mp.noTargets": { zh: "暂无目标挂单", en: "No target orders" },
|
||||
"log.mp.targets": { zh: "目标挂单: {summary}", en: "Target orders: {summary}" },
|
||||
"log.mp.skipThinDepth": {
|
||||
zh: "跳过 {side} {bps}bps 挂单: 深度 {depth} BTC < {min} BTC",
|
||||
en: "Skipping {side} {bps}bps quote: depth {depth} BTC < {min} BTC",
|
||||
},
|
||||
"log.mp.depthRecovered": {
|
||||
zh: "{side} {bps}bps 深度恢复,继续挂单",
|
||||
en: "{side} {bps}bps depth recovered; resuming quotes",
|
||||
},
|
||||
"log.mp.insufficientBalance": {
|
||||
zh: "余额不足,暂停挂单 {seconds}s: {detail}",
|
||||
en: "Insufficient balance; pausing quotes for {seconds}s: {detail}",
|
||||
},
|
||||
"log.mp.balanceRecovered": { zh: "余额恢复,继续挂单", en: "Balance recovered; resuming quotes" },
|
||||
"log.mp.defenseEntered": {
|
||||
zh: "数据过时检测: {summary},进入防御模式",
|
||||
en: "Stale-data check: {summary}; entering defense mode",
|
||||
},
|
||||
"log.mp.defenseExited": {
|
||||
zh: "数据推送恢复正常,退出防御模式",
|
||||
en: "Data feeds recovered; leaving defense mode",
|
||||
},
|
||||
"log.mp.defenseForceCancelled": {
|
||||
zh: "防御模式: 已强制取消所有挂单",
|
||||
en: "Defense mode: force-cancelled all orders",
|
||||
},
|
||||
"log.mp.defenseCancelPartial": {
|
||||
zh: "防御模式: 取消挂单未完全成功,将继续重试",
|
||||
en: "Defense mode: cancellation incomplete; will retry",
|
||||
},
|
||||
"log.mp.defenseCancelled": { zh: "防御模式: 已取消所有挂单", en: "Defense mode: cancelled all orders" },
|
||||
"log.mp.defenseOrdersGone": { zh: "防御模式: 挂单已不存在", en: "Defense mode: orders already gone" },
|
||||
"log.mp.defenseCancelFailed": {
|
||||
zh: "防御模式取消挂单失败: {error}",
|
||||
en: "Defense mode cancel failed: {error}",
|
||||
},
|
||||
"log.mp.defensePollStarted": {
|
||||
zh: "防御模式: 启动 REST 数据轮询",
|
||||
en: "Defense mode: started REST polling",
|
||||
},
|
||||
"log.mp.defensePollStopped": {
|
||||
zh: "防御模式: 停止 REST 数据轮询",
|
||||
en: "Defense mode: stopped REST polling",
|
||||
},
|
||||
"log.mp.defensePositionStillBad": {
|
||||
zh: "防御模式: 仓位数据仍异常: {issues}",
|
||||
en: "Defense mode: position data still invalid: {issues}",
|
||||
},
|
||||
"log.mp.defenseEmptySnapshot": {
|
||||
zh: "防御模式: REST 获取账户快照为空",
|
||||
en: "Defense mode: REST returned an empty account snapshot",
|
||||
},
|
||||
"log.mp.defenseFoundOrders": {
|
||||
zh: "防御模式: 发现 {count} 个挂单,执行取消",
|
||||
en: "Defense mode: found {count} open orders; cancelling",
|
||||
},
|
||||
"log.mp.defenseQueryFailed": {
|
||||
zh: "防御模式查询挂单失败: {error}",
|
||||
en: "Defense mode open-order query failed: {error}",
|
||||
},
|
||||
"log.mp.defensePollFailed": {
|
||||
zh: "防御模式 REST 轮询失败: {error}",
|
||||
en: "Defense mode REST poll failed: {error}",
|
||||
},
|
||||
"notify.mp.disconnectTitle": { zh: "连接断开", en: "Disconnected" },
|
||||
"notify.mp.disconnectBody": {
|
||||
zh: "WebSocket 断连,正在尝试取消所有挂单",
|
||||
en: "WebSocket disconnected; cancelling all open orders",
|
||||
},
|
||||
"notify.mp.reconnectTitle": { zh: "重连完成", en: "Reconnected" },
|
||||
"notify.mp.reconnectBody": {
|
||||
zh: "WebSocket 重连成功,已清理挂单状态",
|
||||
en: "WebSocket reconnected; order state cleaned up",
|
||||
},
|
||||
"notify.mp.stopTitle": { zh: "止损触发", en: "Stop-loss triggered" },
|
||||
"notify.mp.stopBody": {
|
||||
zh: "实时未实现亏损 {pnl} USDT,强制平仓",
|
||||
en: "Live unrealised loss {pnl} USDT; forcing a close",
|
||||
},
|
||||
"notify.mp.defenseTitle": { zh: "防御模式", en: "Defense mode" },
|
||||
"notify.mp.defenseBody": {
|
||||
zh: "数据推送中断: {summary},已取消所有挂单",
|
||||
en: "Data feed interrupted: {summary}; cancelled all open orders",
|
||||
},
|
||||
"notify.mp.defenseClearedTitle": { zh: "防御模式解除", en: "Defense mode cleared" },
|
||||
"notify.mp.defenseClearedBody": {
|
||||
zh: "数据推送恢复正常,恢复正常交易",
|
||||
en: "Data feeds are healthy again; resuming normal trading",
|
||||
},
|
||||
"notify.mp.openTitle": { zh: "开仓", en: "Position opened" },
|
||||
"notify.mp.closeTitle": { zh: "平仓", en: "Position closed" },
|
||||
"notify.mp.closeTitleTokenExpired": { zh: "Token过期平仓", en: "Token-expiry close" },
|
||||
"notify.mp.increaseTitle": { zh: "加仓", en: "Position increased" },
|
||||
"notify.mp.reduceTitle": { zh: "减仓", en: "Position reduced" },
|
||||
"notify.mp.reverseTitle": { zh: "反向开仓", en: "Position reversed" },
|
||||
"notify.mp.openBody": { zh: "{direction} {qty}", en: "{direction} {qty}" },
|
||||
"notify.mp.closeBody": { zh: "已平仓 {qty} ({direction})", en: "Closed {qty} ({direction})" },
|
||||
"notify.mp.increaseBody": {
|
||||
zh: "{direction} +{delta} → {qty}",
|
||||
en: "{direction} +{delta} → {qty}",
|
||||
},
|
||||
"notify.mp.reduceBody": { zh: "{direction} -{delta} → {qty}", en: "{direction} -{delta} → {qty}" },
|
||||
"notify.mp.reverseBody": { zh: "{transition} {qty}", en: "{transition} {qty}" },
|
||||
"common.direction.longToShort": { zh: "多→空", en: "long → short" },
|
||||
"common.direction.shortToLong": { zh: "空→多", en: "short → long" },
|
||||
// --- strategy/maker-points-defense (stale-reason summary) ---
|
||||
"defense.reason.depth": { zh: "StandX深度({seconds}s)", en: "StandX depth ({seconds}s)" },
|
||||
"defense.reason.account": { zh: "StandX账户({seconds}s)", en: "StandX account ({seconds}s)" },
|
||||
"defense.reason.accountInvalid": {
|
||||
zh: "StandX仓位数据异常({issues})",
|
||||
en: "StandX position data invalid ({issues})",
|
||||
},
|
||||
"defense.reason.rest": { zh: "StandX REST错误({count}次)", en: "StandX REST errors ({count})" },
|
||||
"defense.reason.marginMode": { zh: "保证金模式({mode})", en: "Margin mode ({mode})" },
|
||||
"defense.reason.binanceDepth": { zh: "Binance深度({seconds}s)", en: "Binance depth ({seconds}s)" },
|
||||
"defense.reason.binanceBook": {
|
||||
zh: "Binance簿记异常({reason})",
|
||||
en: "Binance order book unhealthy ({reason})",
|
||||
},
|
||||
"defense.reason.unknown": { zh: "unknown", en: "unknown" },
|
||||
};
|
||||
|
||||
const formatTemplate = (template: string, params: Record<string, unknown>): string => {
|
||||
|
||||
@@ -521,7 +521,7 @@ export class BasisArbEngine {
|
||||
const spotTs = snapshot.spotLastUpdate ?? 0;
|
||||
if (futTs <= readyAt || spotTs <= readyAt) return;
|
||||
const now = this.now();
|
||||
// Use net spread after taker fees to match UI's "扣除 taker 手续费" bp
|
||||
// Use net spread after taker fees to match the bp figure the UI labels as taker-fee-adjusted
|
||||
const spreadBps = snapshot.netSpreadBps;
|
||||
const fundingRate = snapshot.fundingRate;
|
||||
const nextFundingTime = snapshot.nextFundingTime;
|
||||
|
||||
+67
-48
@@ -15,6 +15,7 @@ import {
|
||||
type OrderPendingMap,
|
||||
type OrderTimerMap,
|
||||
} from "../core/order-coordinator";
|
||||
import { t } from "../i18n";
|
||||
import { StrategyEventEmitter } from "./common/event-emitter";
|
||||
import { createPrecisionSyncer, type PrecisionSyncer } from "./common/precision-syncer";
|
||||
import { safeSubscribe, type LogHandler } from "./common/subscriptions";
|
||||
@@ -186,7 +187,7 @@ export class GridEngine {
|
||||
this.configValid = this.validateConfig();
|
||||
this.running = this.configValid;
|
||||
if (!this.configValid) {
|
||||
this.stopReason = "配置无效,已暂停网格";
|
||||
this.stopReason = t("log.gridEngine.configInvalid");
|
||||
this.log("error", this.stopReason);
|
||||
}
|
||||
this.precision = createPrecisionSyncer(this.exchange, this.config, this.config.qtyStep, this.log);
|
||||
@@ -306,15 +307,15 @@ export class GridEngine {
|
||||
this.accountVersion += 1;
|
||||
if (!this.feedArrived.account) {
|
||||
this.feedArrived.account = true;
|
||||
this.log("info", "账户快照已同步");
|
||||
this.log("info", t("log.account.snapshotSynced"));
|
||||
}
|
||||
this.feedStatus.account = true;
|
||||
this.emitUpdate();
|
||||
},
|
||||
this.log,
|
||||
{
|
||||
subscribeFail: (error) => `订阅账户失败: ${extractMessage(error)}`,
|
||||
processFail: (error) => `账户推送处理异常: ${extractMessage(error)}`,
|
||||
subscribeFail: (error) => t("log.subscribe.accountFail", { error: extractMessage(error) }),
|
||||
processFail: (error) => t("log.process.accountError", { error: extractMessage(error) }),
|
||||
}
|
||||
);
|
||||
|
||||
@@ -329,7 +330,7 @@ export class GridEngine {
|
||||
this.ordersFeedLastAt = this.now();
|
||||
if (!this.feedArrived.orders) {
|
||||
this.feedArrived.orders = true;
|
||||
this.log("info", "订单快照已同步");
|
||||
this.log("info", t("log.order.snapshotReturned"));
|
||||
}
|
||||
this.feedStatus.orders = true;
|
||||
void this.attemptInit();
|
||||
@@ -337,8 +338,8 @@ export class GridEngine {
|
||||
},
|
||||
this.log,
|
||||
{
|
||||
subscribeFail: (error) => `订阅订单失败: ${extractMessage(error)}`,
|
||||
processFail: (error) => `订单推送处理异常: ${extractMessage(error)}`,
|
||||
subscribeFail: (error) => t("log.subscribe.orderFail", { error: extractMessage(error) }),
|
||||
processFail: (error) => t("log.process.orderError", { error: extractMessage(error) }),
|
||||
}
|
||||
);
|
||||
|
||||
@@ -348,14 +349,14 @@ export class GridEngine {
|
||||
this.depthSnapshot = depth;
|
||||
if (!this.feedArrived.depth) {
|
||||
this.feedArrived.depth = true;
|
||||
this.log("info", "盘口深度已同步");
|
||||
this.log("info", t("log.depth.ready"));
|
||||
}
|
||||
this.feedStatus.depth = true;
|
||||
},
|
||||
this.log,
|
||||
{
|
||||
subscribeFail: (error) => `订阅深度失败: ${extractMessage(error)}`,
|
||||
processFail: (error) => `深度推送处理异常: ${extractMessage(error)}`,
|
||||
subscribeFail: (error) => t("log.subscribe.depthFail", { error: extractMessage(error) }),
|
||||
processFail: (error) => t("log.process.depthError", { error: extractMessage(error) }),
|
||||
}
|
||||
);
|
||||
|
||||
@@ -366,7 +367,7 @@ export class GridEngine {
|
||||
this.tickerLastAt = this.now();
|
||||
if (!this.feedArrived.ticker) {
|
||||
this.feedArrived.ticker = true;
|
||||
this.log("info", "行情推送已同步");
|
||||
this.log("info", t("log.ticker.ready"));
|
||||
}
|
||||
this.feedStatus.ticker = true;
|
||||
void this.attemptInit();
|
||||
@@ -374,8 +375,8 @@ export class GridEngine {
|
||||
},
|
||||
this.log,
|
||||
{
|
||||
subscribeFail: (error) => `订阅行情失败: ${extractMessage(error)}`,
|
||||
processFail: (error) => `行情推送处理异常: ${extractMessage(error)}`,
|
||||
subscribeFail: (error) => t("log.subscribe.tickerFail", { error: extractMessage(error) }),
|
||||
processFail: (error) => t("log.process.tickerError", { error: extractMessage(error) }),
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -385,11 +386,11 @@ export class GridEngine {
|
||||
this.exchange.onConnectionEvent((event, symbol) => {
|
||||
if (event === "disconnected") {
|
||||
this.frozen = true;
|
||||
this.log("warn", `WebSocket 断连 (${symbol}),冻结网格下单`);
|
||||
this.log("warn", t("log.gridEngine.wsDisconnected", { symbol }));
|
||||
} else if (event === "reconnected") {
|
||||
this.frozen = false;
|
||||
this.restReconcilePending = true;
|
||||
this.log("info", `WebSocket 重连成功 (${symbol}),下一轮执行对账`);
|
||||
this.log("info", t("log.gridEngine.wsReconnected", { symbol }));
|
||||
}
|
||||
this.emitUpdate();
|
||||
});
|
||||
@@ -482,7 +483,7 @@ export class GridEngine {
|
||||
try {
|
||||
stored = await loadGridState(this.config.symbol);
|
||||
} catch (err) {
|
||||
this.log("error", `加载网格状态失败: ${extractMessage(err)}`);
|
||||
this.log("error", t("log.gridEngine.loadStateFailed", { error: extractMessage(err) }));
|
||||
}
|
||||
}
|
||||
const meta = this.stateMeta();
|
||||
@@ -490,20 +491,27 @@ export class GridEngine {
|
||||
this.state = fromStored(stored, this.logicSettings(), price);
|
||||
this.log(
|
||||
"info",
|
||||
`已从磁盘恢复网格状态: gridVersion=${this.state.gridVersion} anchor=${this.state.anchorPrice} ` +
|
||||
`区间=[${this.state.lowerPrice}, ${this.state.upperPrice}]${this.state.shift ? ` 移格续跑(${this.state.shift.phase})` : ""}`
|
||||
t("log.gridEngine.stateRestored", {
|
||||
gridVersion: this.state.gridVersion,
|
||||
anchor: this.state.anchorPrice,
|
||||
lower: this.state.lowerPrice,
|
||||
upper: this.state.upperPrice,
|
||||
shift: this.state.shift
|
||||
? t("log.gridEngine.stateRestoredShift", { phase: this.state.shift.phase })
|
||||
: "",
|
||||
})
|
||||
);
|
||||
} else {
|
||||
if (stored) {
|
||||
this.log("warn", "磁盘网格状态与当前配置指纹不一致,全新建格并执行孤儿扫描");
|
||||
this.log("warn", t("log.gridEngine.fingerprintMismatch"));
|
||||
}
|
||||
this.state = createInitialState(this.logicSettings(), price);
|
||||
this.log("info", `以锚定价 ${this.state.anchorPrice} 建立网格 (${this.tradeMode})`);
|
||||
this.log("info", t("log.gridEngine.gridCreated", { anchor: this.state.anchorPrice, mode: this.tradeMode }));
|
||||
}
|
||||
await this.applyReconcile(this.openOrders, "startup");
|
||||
this.initDone = true;
|
||||
} catch (err) {
|
||||
this.log("error", `网格初始化失败: ${extractMessage(err)}`);
|
||||
this.log("error", t("log.gridEngine.initFailed", { error: extractMessage(err) }));
|
||||
this.initStarted = false;
|
||||
}
|
||||
this.emitUpdate();
|
||||
@@ -523,7 +531,7 @@ export class GridEngine {
|
||||
now: this.now(),
|
||||
});
|
||||
for (const event of result.events) {
|
||||
this.log("info", `[对账:${source}] ${event}`);
|
||||
this.log("info", t("log.gridEngine.reconcileEvent", { source, event }));
|
||||
}
|
||||
if (result.cancelOrderIds.length > 0) {
|
||||
try {
|
||||
@@ -531,10 +539,10 @@ export class GridEngine {
|
||||
symbol: this.config.symbol,
|
||||
orderIdList: result.cancelOrderIds,
|
||||
});
|
||||
this.log("order", `[对账:${source}] 撤销 ${result.cancelOrderIds.length} 个无法归属的挂单`);
|
||||
this.log("order", t("log.gridEngine.reconcileCancelled", { source, count: result.cancelOrderIds.length }));
|
||||
} catch (err) {
|
||||
if (!isUnknownOrderError(err)) {
|
||||
this.log("error", `[对账:${source}] 撤单失败: ${extractMessage(err)}`);
|
||||
this.log("error", t("log.gridEngine.reconcileCancelFailed", { source, error: extractMessage(err) }));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -550,7 +558,7 @@ export class GridEngine {
|
||||
const fetched = await this.exchange.queryOpenOrders();
|
||||
orders = fetched.filter((order) => order.symbol === this.config.symbol);
|
||||
} catch (err) {
|
||||
this.log("error", `[对账:${source}] REST 查询挂单失败: ${extractMessage(err)}`);
|
||||
this.log("error", t("log.gridEngine.reconcileOrdersFailed", { source, error: extractMessage(err) }));
|
||||
}
|
||||
}
|
||||
if (this.exchange.queryAccountSnapshot) {
|
||||
@@ -561,7 +569,7 @@ export class GridEngine {
|
||||
this.accountVersion += 1;
|
||||
}
|
||||
} catch (err) {
|
||||
this.log("error", `[对账:${source}] REST 查询账户失败: ${extractMessage(err)}`);
|
||||
this.log("error", t("log.gridEngine.reconcileAccountFailed", { source, error: extractMessage(err) }));
|
||||
}
|
||||
}
|
||||
if (orders) {
|
||||
@@ -645,7 +653,7 @@ export class GridEngine {
|
||||
this.schedulePersist();
|
||||
}
|
||||
} catch (error) {
|
||||
this.log("error", `网格轮询异常: ${extractMessage(error)}`);
|
||||
this.log("error", t("log.gridEngine.tickFailed", { error: extractMessage(error) }));
|
||||
} finally {
|
||||
this.processing = false;
|
||||
this.emitUpdate();
|
||||
@@ -661,7 +669,7 @@ export class GridEngine {
|
||||
}
|
||||
if (action.kind === "BEGIN_SHIFT") {
|
||||
// 移格标记已由 planTick 写入 state,落盘后由下个 tick 开始执行
|
||||
this.log("warn", `启动智能移格,目标锚定价 ${action.targetAnchor}`);
|
||||
this.log("warn", t("log.gridEngine.shiftStarting", { anchor: action.targetAnchor }));
|
||||
await this.persistNow();
|
||||
return;
|
||||
}
|
||||
@@ -692,7 +700,7 @@ export class GridEngine {
|
||||
) {
|
||||
if (now - this.lastStalenessLogAt > 30_000) {
|
||||
this.lastStalenessLogAt = now;
|
||||
this.log("warn", "订单流疑似停滞(下单后长时间未反映),暂停新下单");
|
||||
this.log("warn", t("log.gridEngine.orderFeedStalled"));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -741,7 +749,7 @@ export class GridEngine {
|
||||
clientOrderId,
|
||||
});
|
||||
} catch (error) {
|
||||
this.log("error", `挂单失败 (${action.side} @ ${action.price}): ${extractMessage(error)}`);
|
||||
this.log("error", t("log.gridEngine.placeFailed", { side: action.side, price: action.price, error: extractMessage(error) }));
|
||||
}
|
||||
state.inflight = null;
|
||||
|
||||
@@ -799,7 +807,13 @@ export class GridEngine {
|
||||
if (pctDiff > limitPct) {
|
||||
this.log(
|
||||
"warn",
|
||||
`市价平仓滑点守卫触发 (${reason}): close=${closeSidePrice} mark=${mark} 偏离 ${(pctDiff * 100).toFixed(2)}% > ${(limitPct * 100).toFixed(2)}%,暂缓`
|
||||
t("log.gridEngine.closeSlippageBlocked", {
|
||||
reason,
|
||||
close: closeSidePrice,
|
||||
mark,
|
||||
pct: (pctDiff * 100).toFixed(2),
|
||||
limit: (limitPct * 100).toFixed(2),
|
||||
})
|
||||
);
|
||||
return false;
|
||||
}
|
||||
@@ -816,10 +830,10 @@ export class GridEngine {
|
||||
},
|
||||
qtyStep: this.config.qtyStep
|
||||
});
|
||||
this.log("close", `市价平仓 ${side} ${qty} (${reason})`);
|
||||
this.log("close", t("log.gridEngine.closed", { side, qty, reason }));
|
||||
return true;
|
||||
} catch (error) {
|
||||
this.log("error", `市价平仓失败 (${reason}): ${extractMessage(error)}`);
|
||||
this.log("error", t("log.gridEngine.closeFailed", { reason, error: extractMessage(error) }));
|
||||
return false;
|
||||
} finally {
|
||||
unlockOperating(this.locks, this.timers, this.pendings, "MARKET");
|
||||
@@ -843,9 +857,9 @@ export class GridEngine {
|
||||
try {
|
||||
await this.exchange.cancelAllOrders({ symbol: this.config.symbol });
|
||||
state.exchangeStop = null;
|
||||
this.log("order", "移格: 已请求撤销全部挂单");
|
||||
this.log("order", t("log.gridEngine.shiftCancelRequested"));
|
||||
} catch (err) {
|
||||
this.log("error", `移格撤单失败: ${extractMessage(err)}`);
|
||||
this.log("error", t("log.gridEngine.shiftCancelFailed", { error: extractMessage(err) }));
|
||||
}
|
||||
} else if (step.kind === "CLOSE_POSITION") {
|
||||
// 平仓单已提交但仓位回报未到时不重复提交
|
||||
@@ -853,19 +867,24 @@ export class GridEngine {
|
||||
this.shiftCloseAccountVersion === this.accountVersion &&
|
||||
this.now() - this.shiftCloseAt < 10_000;
|
||||
if (!awaitingFill) {
|
||||
const done = await this.guardedMarketClose(step.side, step.qty, "移格平仓");
|
||||
const done = await this.guardedMarketClose(step.side, step.qty, t("log.gridEngine.shiftCloseReason"));
|
||||
if (done) {
|
||||
this.shiftCloseAccountVersion = this.accountVersion;
|
||||
this.shiftCloseAt = this.now();
|
||||
} else {
|
||||
this.log("info", "移格: 平仓被滑点守卫暂缓,下轮重试");
|
||||
this.log("info", t("log.gridEngine.shiftCloseDeferred"));
|
||||
}
|
||||
}
|
||||
} else if (step.kind === "REBUILD") {
|
||||
applyRebuild(state, this.logicSettings(), step.anchor);
|
||||
this.log(
|
||||
"info",
|
||||
`移格完成: 新锚定价 ${step.anchor},区间 [${state.lowerPrice.toFixed(4)}, ${state.upperPrice.toFixed(4)}],gridVersion=${state.gridVersion}`
|
||||
t("log.gridEngine.shiftDone", {
|
||||
anchor: step.anchor,
|
||||
lower: state.lowerPrice.toFixed(4),
|
||||
upper: state.upperPrice.toFixed(4),
|
||||
gridVersion: state.gridVersion,
|
||||
})
|
||||
);
|
||||
}
|
||||
this.lastUpdated = this.now();
|
||||
@@ -905,10 +924,10 @@ export class GridEngine {
|
||||
if (live) {
|
||||
try {
|
||||
await this.exchange.cancelOrder({ symbol: this.config.symbol, orderId: existing.orderId });
|
||||
this.log("order", "已撤销交易所兜底止损单(仓位归零)");
|
||||
this.log("order", t("log.gridEngine.stopCancelledFlat"));
|
||||
} catch (err) {
|
||||
if (!isUnknownOrderError(err)) {
|
||||
this.log("error", `撤销兜底止损单失败: ${extractMessage(err)}`);
|
||||
this.log("error", t("log.gridEngine.stopCancelFailed", { error: extractMessage(err) }));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -932,7 +951,7 @@ export class GridEngine {
|
||||
await this.exchange.cancelOrder({ symbol: this.config.symbol, orderId: existing.orderId });
|
||||
} catch (err) {
|
||||
if (!isUnknownOrderError(err)) {
|
||||
this.log("error", `撤销旧兜底止损单失败: ${extractMessage(err)}`);
|
||||
this.log("error", t("log.gridEngine.stopCancelStaleFailed", { error: extractMessage(err) }));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -961,7 +980,7 @@ export class GridEngine {
|
||||
this.schedulePersist();
|
||||
}
|
||||
} catch (err) {
|
||||
this.log("error", `挂兜底止损单失败: ${extractMessage(err)}`);
|
||||
this.log("error", t("log.gridEngine.stopPlaceFailed", { error: extractMessage(err) }));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -972,12 +991,12 @@ export class GridEngine {
|
||||
private async haltGrid(reason: string): Promise<void> {
|
||||
const state = this.state;
|
||||
this.stopReason = reason;
|
||||
this.log("warn", `${reason},开始执行撤单与平仓`);
|
||||
this.log("warn", t("log.gridEngine.haltStarting", { reason }));
|
||||
try {
|
||||
await this.exchange.cancelAllOrders({ symbol: this.config.symbol });
|
||||
this.log("order", "已撤销全部网格挂单");
|
||||
this.log("order", t("log.gridEngine.allCancelled"));
|
||||
} catch (error) {
|
||||
this.log("error", `撤销网格挂单失败: ${extractMessage(error)}`);
|
||||
this.log("error", t("log.gridEngine.cancelAllFailed", { error: extractMessage(error) }));
|
||||
}
|
||||
if (state) state.exchangeStop = null;
|
||||
const qty = this.position.positionAmt;
|
||||
@@ -985,7 +1004,7 @@ export class GridEngine {
|
||||
const closed = await this.guardedMarketClose(qty > 0 ? "SELL" : "BUY", Math.abs(qty), reason);
|
||||
if (!closed) {
|
||||
// 滑点守卫暂缓:保持 running,下个 tick 重新触发层①重试
|
||||
this.log("warn", "止损平仓被滑点守卫暂缓,下轮重试");
|
||||
this.log("warn", t("log.gridEngine.stopCloseDeferred"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -1038,7 +1057,7 @@ export class GridEngine {
|
||||
this.running = true;
|
||||
this.stopReason = null;
|
||||
this.initDone = true;
|
||||
this.log("info", `价格重新回到网格区间,恢复网格运行 (gridVersion=${nextVersion})`);
|
||||
this.log("info", t("log.gridEngine.resumed", { gridVersion: nextVersion }));
|
||||
await this.persistNow();
|
||||
this.start();
|
||||
}
|
||||
@@ -1064,7 +1083,7 @@ export class GridEngine {
|
||||
try {
|
||||
await saveGridState(toStored(state, this.stateMeta(), this.now()));
|
||||
} catch (err) {
|
||||
this.log("error", `保存网格状态失败: ${extractMessage(err)}`);
|
||||
this.log("error", t("log.gridEngine.saveStateFailed", { error: extractMessage(err) }));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { t } from "../i18n";
|
||||
import {
|
||||
ORPHAN_LEVEL,
|
||||
applyRebuild,
|
||||
@@ -552,8 +553,12 @@ describe("resolveAwaiting", () => {
|
||||
describe("checkPriceStop", () => {
|
||||
it("triggers below lower and above upper thresholds", () => {
|
||||
const state = createInitialState(settings, 141.4);
|
||||
expect(checkPriceStop(state, settings, 98.9)).toContain("跌破");
|
||||
expect(checkPriceStop(state, settings, 202.1)).toContain("突破");
|
||||
expect(checkPriceStop(state, settings, 98.9)).toBe(
|
||||
t("log.grid.belowLowerBound", { pct: "1.10" })
|
||||
);
|
||||
expect(checkPriceStop(state, settings, 202.1)).toBe(
|
||||
t("log.grid.aboveUpperBound", { pct: "1.05" })
|
||||
);
|
||||
expect(checkPriceStop(state, settings, 150)).toBeNull();
|
||||
expect(checkPriceStop(state, settings, 99.5)).toBeNull(); // 1% 容忍内
|
||||
});
|
||||
|
||||
+24
-18
@@ -1,3 +1,4 @@
|
||||
import { t } from "../i18n";
|
||||
// 网格纯逻辑:无 I/O、无 Date.now、无 adapter 引用。所有时间通过参数传入。
|
||||
// 引擎每 tick 调 planTick(state, settings, input) 得到 actions,由引擎负责执行。
|
||||
|
||||
@@ -417,11 +418,11 @@ function applyFilled(
|
||||
level.phase = "holding";
|
||||
level.holdQty = qty;
|
||||
delete level.entryOrderId;
|
||||
events.push(`ENTRY 成交: ${intent.side} @ ${intent.price} (线 ${intent.level})`);
|
||||
events.push(t("log.grid.entryFilled", { side: intent.side, price: intent.price, level: intent.level }));
|
||||
}
|
||||
} else {
|
||||
if (intent.level === ORPHAN_LEVEL) {
|
||||
events.push(`孤儿 EXIT 成交: ${intent.side} @ ${intent.price}`);
|
||||
events.push(t("log.grid.orphanExitFilled", { side: intent.side, price: intent.price }));
|
||||
return;
|
||||
}
|
||||
const level = state.levels[intent.level];
|
||||
@@ -429,7 +430,7 @@ function applyFilled(
|
||||
level.phase = "idle";
|
||||
level.holdQty = 0;
|
||||
delete level.exitOrderId;
|
||||
events.push(`EXIT 成交: ${intent.side} @ ${intent.price} (释放线 ${intent.level})`);
|
||||
events.push(t("log.grid.exitFilled", { side: intent.side, price: intent.price, level: intent.level }));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -441,7 +442,7 @@ function applyCanceled(state: GridLogicState, intent: OrderIntentRecord, events:
|
||||
level.phase = "idle";
|
||||
delete level.entryOrderId;
|
||||
}
|
||||
events.push(`ENTRY 撤销: ${intent.side} @ ${intent.price} (线 ${intent.level})`);
|
||||
events.push(t("log.grid.entryCancelled", { side: intent.side, price: intent.price, level: intent.level }));
|
||||
} else {
|
||||
if (intent.level === ORPHAN_LEVEL) return;
|
||||
const level = state.levels[intent.level];
|
||||
@@ -449,7 +450,7 @@ function applyCanceled(state: GridLogicState, intent: OrderIntentRecord, events:
|
||||
level.phase = "holding";
|
||||
delete level.exitOrderId;
|
||||
}
|
||||
events.push(`EXIT 撤销: ${intent.side} @ ${intent.price} (线 ${intent.level})`);
|
||||
events.push(t("log.grid.exitCancelled", { side: intent.side, price: intent.price, level: intent.level }));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -502,7 +503,9 @@ export function processOrderSnapshot(
|
||||
applyCanceled(state, intent, events);
|
||||
} else {
|
||||
setAwaiting(state, intent, input);
|
||||
events.push(`订单消失待判定: ${intent.intent} ${intent.side} @ ${intent.price}`);
|
||||
events.push(
|
||||
t("log.grid.orderVanished", { intent: intent.intent, side: intent.side, price: intent.price })
|
||||
);
|
||||
}
|
||||
state.intents.delete(id);
|
||||
state.seenOrderIds.delete(id);
|
||||
@@ -669,10 +672,10 @@ export function checkPriceStop(
|
||||
const lowerTrigger = state.lowerPrice * (1 - settings.stopLossPct);
|
||||
const upperTrigger = state.upperPrice * (1 + settings.stopLossPct);
|
||||
if (price <= lowerTrigger) {
|
||||
return `价格跌破网格下边界 ${((1 - price / state.lowerPrice) * 100).toFixed(2)}%`;
|
||||
return t("log.grid.belowLowerBound", { pct: ((1 - price / state.lowerPrice) * 100).toFixed(2) });
|
||||
}
|
||||
if (price >= upperTrigger) {
|
||||
return `价格突破网格上边界 ${((price / state.upperPrice - 1) * 100).toFixed(2)}%`;
|
||||
return t("log.grid.aboveUpperBound", { pct: ((price / state.upperPrice - 1) * 100).toFixed(2) });
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -731,17 +734,20 @@ export function auditExitCoverage(
|
||||
state.uncoveredSince = input.now;
|
||||
if (outOfRange || deepLoss) {
|
||||
events.push(
|
||||
`覆盖审计: 未覆盖 ${uncovered.toFixed(6)} 且${outOfRange ? "价格已出区间" : "浮亏超限"},市价平仓`
|
||||
t("log.grid.coverageAuditClose", {
|
||||
qty: uncovered.toFixed(6),
|
||||
cause: outOfRange ? t("log.grid.causeOutOfRange") : t("log.grid.causeLossExceeded"),
|
||||
})
|
||||
);
|
||||
return {
|
||||
uncoveredQty: uncovered,
|
||||
action: { kind: "MARKET_CLOSE", side: exitSide, qty: uncovered, reason: "覆盖审计止损" },
|
||||
action: { kind: "MARKET_CLOSE", side: exitSide, qty: uncovered, reason: t("log.grid.coverageAuditReason") },
|
||||
events,
|
||||
};
|
||||
}
|
||||
// 最近可盈利线补挂孤儿 EXIT
|
||||
const targetPrice = findNearestProfitableExitPrice(state, pos > 0 ? "long" : "short", entry, input.price);
|
||||
events.push(`覆盖审计: 未覆盖 ${uncovered.toFixed(6)},补挂平仓单 @ ${targetPrice}`);
|
||||
events.push(t("log.grid.coverageAuditRepost", { qty: uncovered.toFixed(6), price: targetPrice }));
|
||||
return {
|
||||
uncoveredQty: uncovered,
|
||||
action: {
|
||||
@@ -918,7 +924,7 @@ export function planTick(
|
||||
if (settings.shiftEnabled && !state.shift) {
|
||||
beginShift(state, input.price, input.now);
|
||||
actions.push({ kind: "BEGIN_SHIFT", targetAnchor: input.price });
|
||||
events.push(`价格越界,启动移格: ${stopReason}`);
|
||||
events.push(t("log.grid.shiftOutOfRange", { reason: stopReason }));
|
||||
return { actions, events, stateChanged: true, uncoveredQty: 0 };
|
||||
}
|
||||
actions.push({ kind: "HALT", reason: stopReason });
|
||||
@@ -929,7 +935,7 @@ export function planTick(
|
||||
if (shouldShift(state, settings, input.price, input.now)) {
|
||||
beginShift(state, input.price, input.now);
|
||||
actions.push({ kind: "BEGIN_SHIFT", targetAnchor: input.price });
|
||||
events.push(`价格偏离锚定价超阈值,启动移格 (anchor=${state.anchorPrice} → ${input.price})`);
|
||||
events.push(t("log.grid.shiftAnchorDrift", { anchor: state.anchorPrice, price: input.price }));
|
||||
return { actions, events, stateChanged: true, uncoveredQty: 0 };
|
||||
}
|
||||
|
||||
@@ -1034,11 +1040,11 @@ export function reconcile(
|
||||
gridVersion: state.gridVersion,
|
||||
createdAt: input.now,
|
||||
});
|
||||
events.push(`收编平仓方向挂单为孤儿 EXIT: ${order.side} @ ${order.price}`);
|
||||
events.push(t("log.grid.adoptOrphanExit", { side: order.side, price: order.price }));
|
||||
return;
|
||||
}
|
||||
cancelOrderIds.push(order.orderId);
|
||||
events.push(`撤销无法归属的挂单: ${order.side} @ ${order.price}`);
|
||||
events.push(t("log.grid.cancelUnattributable", { side: order.side, price: order.price }));
|
||||
};
|
||||
|
||||
for (const order of input.activeOrders) {
|
||||
@@ -1079,7 +1085,7 @@ export function reconcile(
|
||||
rec.intent === "ENTRY" ? adoptEntry(order, rec.level, intent) : adoptExit(order, rec.level, intent);
|
||||
state.inflight = null;
|
||||
if (ok) {
|
||||
events.push(`inflight 归属确认: ${rec.intent} ${rec.side} @ ${rec.price}`);
|
||||
events.push(t("log.grid.inflightMatched", { intent: rec.intent, side: rec.side, price: rec.price }));
|
||||
continue;
|
||||
}
|
||||
fallbackAdopt(order, remaining);
|
||||
@@ -1090,7 +1096,7 @@ export function reconcile(
|
||||
if (parsed) {
|
||||
if (parsed.gridVersion != null && parsed.gridVersion !== state.gridVersion) {
|
||||
cancelOrderIds.push(order.orderId);
|
||||
events.push(`撤销过期网格版本挂单: ${order.clientOrderId}`);
|
||||
events.push(t("log.grid.cancelStaleVersion", { clientOrderId: order.clientOrderId }));
|
||||
continue;
|
||||
}
|
||||
const intent: OrderIntentRecord = {
|
||||
@@ -1231,7 +1237,7 @@ export function reconcile(
|
||||
diff = 0;
|
||||
}
|
||||
if (Math.abs(diff) > eps) {
|
||||
events.push(`对账残余孤儿仓位: ${diff.toFixed(6)}`);
|
||||
events.push(t("log.grid.orphanResidual", { qty: diff.toFixed(6) }));
|
||||
// 立即进入层②处置(跳过宽限期)
|
||||
state.uncoveredSince = input.now - 86_400_000;
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import type { MakerEngineSnapshot } from "./maker-engine";
|
||||
import { makeOrderPlan } from "../core/lib/order-plan";
|
||||
import { safeCancelOrder } from "../core/lib/orders";
|
||||
import { RateLimitController } from "../core/lib/rate-limit";
|
||||
import { t } from "../i18n";
|
||||
import { StrategyEventEmitter } from "./common/event-emitter";
|
||||
import { safeSubscribe, type LogHandler } from "./common/subscriptions";
|
||||
import { SessionVolumeTracker } from "./common/session-volume";
|
||||
@@ -233,8 +234,8 @@ export class LiquidityMakerEngine {
|
||||
},
|
||||
log,
|
||||
{
|
||||
subscribeFail: (error) => `订阅账户失败: ${String(error)}`,
|
||||
processFail: (error) => `账户推送处理异常: ${String(error)}`,
|
||||
subscribeFail: (error) => t("log.subscribe.accountFail", { error: String(error) }),
|
||||
processFail: (error) => t("log.process.accountError", { error: String(error) }),
|
||||
}
|
||||
);
|
||||
|
||||
@@ -277,8 +278,8 @@ export class LiquidityMakerEngine {
|
||||
},
|
||||
log,
|
||||
{
|
||||
subscribeFail: (error) => `订阅订单失败: ${String(error)}`,
|
||||
processFail: (error) => `订单推送处理异常: ${String(error)}`,
|
||||
subscribeFail: (error) => t("log.subscribe.orderFail", { error: String(error) }),
|
||||
processFail: (error) => t("log.process.orderError", { error: String(error) }),
|
||||
}
|
||||
);
|
||||
|
||||
@@ -291,8 +292,8 @@ export class LiquidityMakerEngine {
|
||||
},
|
||||
log,
|
||||
{
|
||||
subscribeFail: (error) => `订阅深度失败: ${String(error)}`,
|
||||
processFail: (error) => `深度推送处理异常: ${String(error)}`,
|
||||
subscribeFail: (error) => t("log.subscribe.depthFail", { error: String(error) }),
|
||||
processFail: (error) => t("log.process.depthError", { error: String(error) }),
|
||||
}
|
||||
);
|
||||
|
||||
@@ -305,8 +306,8 @@ export class LiquidityMakerEngine {
|
||||
},
|
||||
log,
|
||||
{
|
||||
subscribeFail: (error) => `订阅Ticker失败: ${String(error)}`,
|
||||
processFail: (error) => `价格推送处理异常: ${String(error)}`,
|
||||
subscribeFail: (error) => t("log.subscribe.tickerFail", { error: String(error) }),
|
||||
processFail: (error) => t("log.process.tickerError", { error: String(error) }),
|
||||
}
|
||||
);
|
||||
|
||||
@@ -325,8 +326,8 @@ export class LiquidityMakerEngine {
|
||||
},
|
||||
log,
|
||||
{
|
||||
subscribeFail: (error) => `订阅K线失败: ${String(error)}`,
|
||||
processFail: (error) => `K线推送处理异常: ${String(error)}`,
|
||||
subscribeFail: (error) => t("log.subscribe.klineFail", { error: String(error) }),
|
||||
processFail: (error) => t("log.process.klineError", { error: String(error) }),
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -364,7 +365,11 @@ export class LiquidityMakerEngine {
|
||||
|
||||
this.tradeLog.push(
|
||||
"order",
|
||||
`检测到成交: ${order.side} ${filledQty.toFixed(6)} @ ${avgPrice.toFixed(this.getPriceDecimals())}`
|
||||
t("log.liquidityMaker.fillDetected", {
|
||||
side: order.side,
|
||||
qty: filledQty.toFixed(6),
|
||||
price: avgPrice.toFixed(this.getPriceDecimals()),
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -487,13 +492,13 @@ export class LiquidityMakerEngine {
|
||||
// 无法卖出,跳过卖单,允许买单累计
|
||||
this.lastSellPriceViable = false;
|
||||
if (!skipSellSide) {
|
||||
this.tradeLog.push("info", "现货持仓低于最小卖单量,暂不挂卖单");
|
||||
this.tradeLog.push("info", t("log.spotMaker.belowMinSellHold"));
|
||||
}
|
||||
}
|
||||
if (!skipBuySide && canEnter) {
|
||||
if (!allowSpotBuy) {
|
||||
if (this.lastBuyPriceViable) {
|
||||
this.tradeLog.push("info", "现货买入仅在1m阳线,当前跳过买单");
|
||||
this.tradeLog.push("info", t("log.spotMaker.buyOnlyOnGreenCandle"));
|
||||
this.lastBuyPriceViable = false;
|
||||
}
|
||||
} else {
|
||||
@@ -510,8 +515,8 @@ export class LiquidityMakerEngine {
|
||||
this.lastBuyPriceViable = false;
|
||||
const reason =
|
||||
buyAmount < EPS && isSpotMarket
|
||||
? "现货可用报价资产不足,跳过买单"
|
||||
: "跳过买单:价差不足以构造maker价格";
|
||||
? t("log.spotMaker.quoteBalanceShort")
|
||||
: t("log.spotMaker.spreadTooTightBuy");
|
||||
this.tradeLog.push("info", reason);
|
||||
}
|
||||
}
|
||||
@@ -524,7 +529,7 @@ export class LiquidityMakerEngine {
|
||||
// 持仓低于最小卖单量,跳过卖单,等待累积
|
||||
if (this.lastSellPriceViable) {
|
||||
this.lastSellPriceViable = false;
|
||||
this.tradeLog.push("info", "现货持仓低于最小卖单量,跳过卖单");
|
||||
this.tradeLog.push("info", t("log.spotMaker.belowMinSellSkip"));
|
||||
}
|
||||
} else {
|
||||
const desiredSellAmount =
|
||||
@@ -542,8 +547,8 @@ export class LiquidityMakerEngine {
|
||||
this.lastSellPriceViable = false;
|
||||
const reason =
|
||||
sellAmount < EPS && isSpotMarket
|
||||
? "现货可用基础资产不足,跳过卖单"
|
||||
: "跳过卖单:价差不足以构造maker价格";
|
||||
? t("log.spotMaker.baseBalanceShort")
|
||||
: t("log.spotMaker.spreadTooTightSell");
|
||||
this.tradeLog.push("info", reason);
|
||||
}
|
||||
}
|
||||
@@ -554,7 +559,7 @@ export class LiquidityMakerEngine {
|
||||
if (!skipBuySide && canEnter) {
|
||||
if (isSpotMarket && !allowSpotBuy) {
|
||||
if (this.lastBuyPriceViable) {
|
||||
this.tradeLog.push("info", "现货买入仅在1m阳线,当前跳过买单");
|
||||
this.tradeLog.push("info", t("log.spotMaker.buyOnlyOnGreenCandle"));
|
||||
this.lastBuyPriceViable = false;
|
||||
}
|
||||
} else if (bidPrice != null) {
|
||||
@@ -567,7 +572,7 @@ export class LiquidityMakerEngine {
|
||||
const baseWallet = balancesForSpot?.baseWallet ?? baseAvail;
|
||||
if (Math.max(baseAvail, baseWallet) + EPS < minSell) {
|
||||
this.lastSellPriceViable = false;
|
||||
this.tradeLog.push("info", "现货持仓低于最小卖单量,跳过卖单");
|
||||
this.tradeLog.push("info", t("log.spotMaker.belowMinSellSkip"));
|
||||
}
|
||||
}
|
||||
if (askPrice != null) {
|
||||
@@ -622,7 +627,7 @@ export class LiquidityMakerEngine {
|
||||
await this.enforceRateLimitStop();
|
||||
this.tradeLog.push("warn", `LiquidityMakerEngine 429: ${String(error)}`);
|
||||
} else {
|
||||
this.tradeLog.push("error", `流动性做市循环异常: ${String(error)}`);
|
||||
this.tradeLog.push("error", t("log.liquidityMaker.tickFailed", { error: String(error) }));
|
||||
}
|
||||
this.emitUpdate();
|
||||
} finally {
|
||||
@@ -679,13 +684,13 @@ export class LiquidityMakerEngine {
|
||||
// 多头平仓:卖价必须 >= 入场价
|
||||
if (targetPrice < entryPrice) {
|
||||
targetPrice = entryPrice + this.precision.priceTick;
|
||||
this.tradeLog.push("info", `平仓价调整为入场价+1tick以确保不亏本: ${targetPrice.toFixed(priceDecimals)}`);
|
||||
this.tradeLog.push("info", t("log.liquidityMaker.exitRaisedToBreakeven", { price: targetPrice.toFixed(priceDecimals) }));
|
||||
}
|
||||
} else {
|
||||
// 空头平仓:买价必须 <= 入场价
|
||||
if (targetPrice > entryPrice) {
|
||||
targetPrice = entryPrice - this.precision.priceTick;
|
||||
this.tradeLog.push("info", `平仓价调整为入场价-1tick以确保不亏本: ${targetPrice.toFixed(priceDecimals)}`);
|
||||
this.tradeLog.push("info", t("log.liquidityMaker.exitLoweredToBreakeven", { price: targetPrice.toFixed(priceDecimals) }));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -727,9 +732,9 @@ export class LiquidityMakerEngine {
|
||||
});
|
||||
} catch (error) {
|
||||
if (isUnknownOrderError(error)) {
|
||||
this.tradeLog.push("order", "限频强制平仓时订单已不存在");
|
||||
this.tradeLog.push("order", t("log.spotMaker.rateLimitCloseMissing"));
|
||||
} else {
|
||||
this.tradeLog.push("error", `限频强制平仓失败: ${String(error)}`);
|
||||
this.tradeLog.push("error", t("log.spotMaker.rateLimitCloseFailed", { error: String(error) }));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -747,18 +752,18 @@ export class LiquidityMakerEngine {
|
||||
unlockOperating(this.locks, this.timers, this.pending, "LIMIT");
|
||||
this.openOrders = [];
|
||||
this.emitUpdate();
|
||||
this.tradeLog.push("order", "启动时清理历史挂单");
|
||||
this.tradeLog.push("order", t("log.spotMaker.startupCleanup"));
|
||||
this.initialOrderResetDone = true;
|
||||
return true;
|
||||
} catch (error) {
|
||||
if (isUnknownOrderError(error)) {
|
||||
this.tradeLog.push("order", "历史挂单已消失,跳过启动清理");
|
||||
this.tradeLog.push("order", t("log.spotMaker.startupCleanupGone"));
|
||||
this.initialOrderResetDone = true;
|
||||
this.openOrders = [];
|
||||
this.emitUpdate();
|
||||
return true;
|
||||
}
|
||||
this.tradeLog.push("error", `启动撤单失败: ${String(error)}`);
|
||||
this.tradeLog.push("error", t("log.spotMaker.startupCancelFailed", { error: String(error) }));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -857,17 +862,21 @@ export class LiquidityMakerEngine {
|
||||
() => {
|
||||
this.tradeLog.push(
|
||||
"order",
|
||||
`撤销不匹配订单 ${order.side} @ ${order.price} reduceOnly=${order.reduceOnly}`
|
||||
t("log.spotMaker.cancelMismatched", {
|
||||
side: order.side,
|
||||
price: order.price,
|
||||
reduceOnly: order.reduceOnly,
|
||||
})
|
||||
);
|
||||
// 保持与原逻辑一致:成功撤销不立即修改本地 openOrders,等待订单流重建
|
||||
},
|
||||
() => {
|
||||
this.tradeLog.push("order", "撤销时发现订单已被成交/取消,忽略");
|
||||
this.tradeLog.push("order", t("log.spotMaker.cancelAlreadySettled"));
|
||||
this.pendingCancelOrders.delete(String(order.orderId));
|
||||
this.openOrders = this.openOrders.filter((existing) => existing.orderId !== order.orderId);
|
||||
},
|
||||
(error) => {
|
||||
this.tradeLog.push("error", `撤销订单失败: ${String(error)}`);
|
||||
this.tradeLog.push("error", t("log.spotMaker.cancelFailed", { error: String(error) }));
|
||||
this.pendingCancelOrders.delete(String(order.orderId));
|
||||
// 避免同一轮内重复操作同一张已出错的本地挂单,直接从本地缓存移除,等待下一次订单推送重建
|
||||
this.openOrders = this.openOrders.filter((existing) => existing.orderId !== order.orderId);
|
||||
@@ -887,7 +896,7 @@ export class LiquidityMakerEngine {
|
||||
// Skip placing sells that would be bumped by venue minimums
|
||||
if (this.lastSellPriceViable) {
|
||||
this.lastSellPriceViable = false;
|
||||
this.tradeLog.push("info", "现货卖单低于最小成交量,跳过挂单等待累积");
|
||||
this.tradeLog.push("info", t("log.spotMaker.sellBelowMinNotional"));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -920,10 +929,10 @@ export class LiquidityMakerEngine {
|
||||
if (isRateLimitError(dustError)) {
|
||||
throw dustError;
|
||||
}
|
||||
this.tradeLog.push("error", `小额市价平仓失败: ${String(dustError)}`);
|
||||
this.tradeLog.push("error", t("log.spotMaker.dustCloseFailed", { error: String(dustError) }));
|
||||
}
|
||||
if (dustClosed) continue;
|
||||
this.tradeLog.push("error", `挂单失败(${target.side} ${target.price}): ${String(error)}`);
|
||||
this.tradeLog.push("error", t("log.spotMaker.placeFailed", { side: target.side, price: target.price, error: String(error) }));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -939,7 +948,7 @@ export class LiquidityMakerEngine {
|
||||
const minStopQty = Number.isFinite(this.precision.minBaseAmount) ? this.precision.minBaseAmount! : null;
|
||||
if (minStopQty != null && minStopQty > 0 && absPosition + EPS < minStopQty) {
|
||||
if (!this.lastSpotStopSkipped) {
|
||||
this.tradeLog.push("info", "现货持仓低于最小平仓数量,跳过止损检查");
|
||||
this.tradeLog.push("info", t("log.spotMaker.belowMinCloseSkipStop"));
|
||||
this.lastSpotStopSkipped = true;
|
||||
}
|
||||
return;
|
||||
@@ -948,7 +957,7 @@ export class LiquidityMakerEngine {
|
||||
const pnl = computePositionPnl(position, bidPrice, askPrice);
|
||||
const triggerStop = shouldStopLoss(position, bidPrice, askPrice, this.config.lossLimit);
|
||||
if (!triggerStop) return;
|
||||
this.tradeLog.push("stop", `现货止损,当前仓位=${absPosition.toFixed(6)} PnL=${pnl.toFixed(4)} USDT`);
|
||||
this.tradeLog.push("stop", t("log.spotMaker.spotStop", { qty: absPosition.toFixed(6), pnl: pnl.toFixed(4) }));
|
||||
try {
|
||||
// 尽力撤销所有未完成挂单,避免锁定基础资产导致余额不足
|
||||
await this.exchange.cancelAllOrders({ symbol: this.config.symbol }).catch(() => {});
|
||||
@@ -967,9 +976,9 @@ export class LiquidityMakerEngine {
|
||||
} catch (error) {
|
||||
if (isRateLimitError(error)) throw error;
|
||||
if (isUnknownOrderError(error)) {
|
||||
this.tradeLog.push("order", "止损平仓时订单已不存在");
|
||||
this.tradeLog.push("order", t("log.spotMaker.stopCloseMissing"));
|
||||
} else {
|
||||
this.tradeLog.push("error", `现货止损失败: ${String(error)}`);
|
||||
this.tradeLog.push("error", t("log.spotMaker.spotStopFailed", { error: String(error) }));
|
||||
}
|
||||
}
|
||||
return;
|
||||
@@ -980,7 +989,7 @@ export class LiquidityMakerEngine {
|
||||
const hasEntryPrice = Number.isFinite(position.entryPrice) && Math.abs(position.entryPrice) > 1e-8;
|
||||
if (!hasEntryPrice) {
|
||||
if (!this.entryPricePendingLogged) {
|
||||
this.tradeLog.push("info", "做市持仓均价未同步,等待账户快照刷新后再执行止损判断");
|
||||
this.tradeLog.push("info", t("log.spotMaker.entryPricePending"));
|
||||
this.entryPricePendingLogged = true;
|
||||
}
|
||||
return;
|
||||
@@ -993,7 +1002,10 @@ export class LiquidityMakerEngine {
|
||||
if (triggerStop) {
|
||||
this.tradeLog.push(
|
||||
"stop",
|
||||
`触发止损,方向=${position.positionAmt > 0 ? "多" : "空"} 当前亏损=${pnl.toFixed(4)} USDT`
|
||||
t("log.spotMaker.stopTriggered", {
|
||||
direction: position.positionAmt > 0 ? t("common.direction.long") : t("common.direction.short"),
|
||||
pnl: pnl.toFixed(4),
|
||||
})
|
||||
);
|
||||
try {
|
||||
await this.flushOrders();
|
||||
@@ -1010,9 +1022,9 @@ export class LiquidityMakerEngine {
|
||||
});
|
||||
} catch (error) {
|
||||
if (isUnknownOrderError(error)) {
|
||||
this.tradeLog.push("order", "止损平仓时订单已不存在");
|
||||
this.tradeLog.push("order", t("log.spotMaker.stopCloseMissing"));
|
||||
} else {
|
||||
this.tradeLog.push("error", `止损平仓失败: ${String(error)}`);
|
||||
this.tradeLog.push("error", t("log.spotMaker.stopCloseFailed", { error: String(error) }));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1031,12 +1043,12 @@ export class LiquidityMakerEngine {
|
||||
// 与原逻辑保持一致:成功撤销不记录日志且不修改本地 openOrders
|
||||
},
|
||||
() => {
|
||||
this.tradeLog.push("order", "订单已不存在,撤销跳过");
|
||||
this.tradeLog.push("order", t("log.spotMaker.orderMissingOnCancel"));
|
||||
this.pendingCancelOrders.delete(String(order.orderId));
|
||||
this.openOrders = this.openOrders.filter((existing) => existing.orderId !== order.orderId);
|
||||
},
|
||||
(error) => {
|
||||
this.tradeLog.push("error", `撤销订单失败: ${String(error)}`);
|
||||
this.tradeLog.push("error", t("log.spotMaker.cancelFailed", { error: String(error) }));
|
||||
this.pendingCancelOrders.delete(String(order.orderId));
|
||||
// 与同步撤单路径保持一致,移除本地异常订单,等待订单流重建
|
||||
this.openOrders = this.openOrders.filter((existing) => existing.orderId !== order.orderId);
|
||||
@@ -1056,10 +1068,10 @@ export class LiquidityMakerEngine {
|
||||
try {
|
||||
const snapshot = this.buildSnapshot();
|
||||
this.events.emit("update", snapshot, (error) => {
|
||||
this.tradeLog.push("error", `更新回调处理异常: ${String(error)}`);
|
||||
this.tradeLog.push("error", t("log.spotMaker.updateHandlerError", { error: String(error) }));
|
||||
});
|
||||
} catch (err) {
|
||||
this.tradeLog.push("error", `快照或更新分发异常: ${String(err)}`);
|
||||
this.tradeLog.push("error", t("log.spotMaker.snapshotDispatchError", { error: String(err) }));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1253,13 +1265,13 @@ export class LiquidityMakerEngine {
|
||||
},
|
||||
qtyStep: this.precision.qtyStep
|
||||
});
|
||||
this.tradeLog.push("order", `小额仓位使用市价平仓 ${target.side} 数量 ${absQty.toFixed(6)}`);
|
||||
this.tradeLog.push("order", t("log.spotMaker.dustClose", { side: target.side, qty: absQty.toFixed(6) }));
|
||||
return true;
|
||||
} catch (closeError) {
|
||||
if (isRateLimitError(closeError)) {
|
||||
throw closeError;
|
||||
}
|
||||
this.tradeLog.push("error", `小额市价平仓失败: ${String(closeError)}`);
|
||||
this.tradeLog.push("error", t("log.spotMaker.dustCloseFailed", { error: String(closeError) }));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
evaluateDefense,
|
||||
type DefenseInputs,
|
||||
} from "./maker-points-defense";
|
||||
import { t } from "../i18n";
|
||||
|
||||
const NOW = 1_700_000_000_000;
|
||||
|
||||
@@ -149,18 +150,18 @@ describe("describeDefenseReasons", () => {
|
||||
restConsecutiveErrors: 4,
|
||||
})
|
||||
);
|
||||
expect(summary).toContain("StandX深度(7s)");
|
||||
expect(summary).toContain("StandX REST错误(4次)");
|
||||
expect(summary).toContain(t("defense.reason.depth", { seconds: 7 }));
|
||||
expect(summary).toContain(t("defense.reason.rest", { count: 4 }));
|
||||
});
|
||||
|
||||
it("falls back to unknown when nothing is flagged", () => {
|
||||
expect(describeDefenseReasons(defenseReasonsFor({}))).toBe("unknown");
|
||||
expect(describeDefenseReasons(defenseReasonsFor({}))).toBe(t("defense.reason.unknown"));
|
||||
});
|
||||
|
||||
it("omits the Binance book reason when there is none", () => {
|
||||
const summary = describeDefenseReasons(
|
||||
defenseReasonsFor({ binanceUnhealthy: true, binanceHealthReason: null })
|
||||
);
|
||||
expect(summary).toBe("unknown");
|
||||
expect(summary).toBe(t("defense.reason.unknown"));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
* so the rule that decides "is our market data trustworthy" can be tested
|
||||
* without a live adapter.
|
||||
*/
|
||||
import { t } from "../i18n";
|
||||
|
||||
/** A feed older than this is considered stale. */
|
||||
export const DATA_STALE_THRESHOLD_MS = 5_000;
|
||||
@@ -164,17 +165,31 @@ export function describeDefenseReasons(reasons: DefenseReasons): string {
|
||||
const items: string[] = [];
|
||||
const seconds = (ms: number) => Math.round(ms / 1000);
|
||||
|
||||
if (reasons.depthStale) items.push(`StandX深度(${seconds(reasons.depthAge)}s)`);
|
||||
if (reasons.accountStale) items.push(`StandX账户(${seconds(reasons.accountAge)}s)`);
|
||||
if (reasons.depthStale) items.push(t("defense.reason.depth", { seconds: seconds(reasons.depthAge) }));
|
||||
if (reasons.accountStale) {
|
||||
items.push(t("defense.reason.account", { seconds: seconds(reasons.accountAge) }));
|
||||
}
|
||||
if (reasons.accountInvalid) {
|
||||
items.push(`StandX仓位数据异常(${reasons.accountIssues.join(",") || "unknown"})`);
|
||||
items.push(
|
||||
t("defense.reason.accountInvalid", {
|
||||
issues: reasons.accountIssues.join(",") || t("defense.reason.unknown"),
|
||||
})
|
||||
);
|
||||
}
|
||||
if (reasons.restUnhealthy) {
|
||||
items.push(t("defense.reason.rest", { count: reasons.restConsecutiveErrors }));
|
||||
}
|
||||
if (reasons.marginModeNotIsolated) {
|
||||
items.push(
|
||||
t("defense.reason.marginMode", { mode: reasons.marginMode ?? t("defense.reason.unknown") })
|
||||
);
|
||||
}
|
||||
if (reasons.binanceStale) {
|
||||
items.push(t("defense.reason.binanceDepth", { seconds: seconds(reasons.binanceAge) }));
|
||||
}
|
||||
if (reasons.restUnhealthy) items.push(`StandX REST错误(${reasons.restConsecutiveErrors}次)`);
|
||||
if (reasons.marginModeNotIsolated) items.push(`保证金模式(${reasons.marginMode ?? "unknown"})`);
|
||||
if (reasons.binanceStale) items.push(`Binance深度(${seconds(reasons.binanceAge)}s)`);
|
||||
if (reasons.binanceUnhealthy && reasons.binanceHealthReason) {
|
||||
items.push(`Binance簿记异常(${reasons.binanceHealthReason})`);
|
||||
items.push(t("defense.reason.binanceBook", { reason: reasons.binanceHealthReason }));
|
||||
}
|
||||
|
||||
return items.length > 0 ? items.join(", ") : "unknown";
|
||||
return items.length > 0 ? items.join(", ") : t("defense.reason.unknown");
|
||||
}
|
||||
|
||||
@@ -234,7 +234,7 @@ export class MakerPointsEngine {
|
||||
: 3,
|
||||
speedMs: 100,
|
||||
logger: (context, error) => {
|
||||
this.tradeLog.push("warn", `Binance ${context} 异常: ${extractMessage(error)}`);
|
||||
this.tradeLog.push("warn", t("log.mp.binanceError", { context, error: extractMessage(error) }));
|
||||
},
|
||||
});
|
||||
this.binanceDepth.onUpdate(() => {
|
||||
@@ -246,13 +246,13 @@ export class MakerPointsEngine {
|
||||
this.binanceDepth.onConnectionChange((state) => {
|
||||
if (state === "disconnected") {
|
||||
this.feedStatus.binance = false;
|
||||
this.tradeLog.push("warn", "Binance 深度连接断开");
|
||||
this.tradeLog.push("warn", t("log.mp.binanceDisconnected"));
|
||||
} else if (state === "stale") {
|
||||
this.feedStatus.binance = false;
|
||||
this.tradeLog.push("warn", "Binance 深度数据过时");
|
||||
this.tradeLog.push("warn", t("log.mp.binanceStale"));
|
||||
} else if (state === "connected") {
|
||||
this.feedStatus.binance = true;
|
||||
this.tradeLog.push("info", "Binance 深度连接恢复");
|
||||
this.tradeLog.push("info", t("log.mp.binanceRecovered"));
|
||||
}
|
||||
this.emitUpdate();
|
||||
});
|
||||
@@ -501,13 +501,13 @@ export class MakerPointsEngine {
|
||||
*/
|
||||
private handleDisconnect(symbol: string): void {
|
||||
this._standxConnectionState = "disconnected";
|
||||
this.tradeLog.push("warn", `WebSocket 断连 (${symbol}),启动断连保护`);
|
||||
this.tradeLog.push("warn", t("log.mp.wsDisconnected", { symbol }));
|
||||
this.notify({
|
||||
type: "token_expired",
|
||||
level: "warn",
|
||||
symbol: this.config.symbol,
|
||||
title: "连接断开",
|
||||
message: "WebSocket 断连,正在尝试取消所有挂单",
|
||||
title: t("notify.mp.disconnectTitle"),
|
||||
message: t("notify.mp.disconnectBody"),
|
||||
details: { symbol },
|
||||
});
|
||||
}
|
||||
@@ -519,26 +519,26 @@ export class MakerPointsEngine {
|
||||
private async handleReconnect(symbol: string): Promise<void> {
|
||||
this._standxConnectionState = "connected";
|
||||
this.reconnectResetPending = true;
|
||||
this.tradeLog.push("info", `WebSocket 重连成功 (${symbol}),开始重连保护流程`);
|
||||
this.tradeLog.push("info", t("log.mp.wsReconnected", { symbol }));
|
||||
|
||||
try {
|
||||
// 查询真实挂单状态
|
||||
if (this.exchange.queryOpenOrders) {
|
||||
const realOrders = await this.exchange.queryOpenOrders();
|
||||
this.tradeLog.push("info", `重连后查询到 ${realOrders.length} 个挂单`);
|
||||
this.tradeLog.push("info", t("log.mp.reconnectFoundOrders", { count: realOrders.length }));
|
||||
|
||||
if (realOrders.length > 0) {
|
||||
// 取消所有挂单
|
||||
if (this.exchange.forceCancelAllOrders) {
|
||||
const success = await this.exchange.forceCancelAllOrders();
|
||||
if (success) {
|
||||
this.tradeLog.push("order", "重连保护:已取消所有挂单");
|
||||
this.tradeLog.push("order", t("log.mp.reconnectCancelled"));
|
||||
} else {
|
||||
this.tradeLog.push("warn", "重连保护:取消挂单未完全成功,将在下次循环重试");
|
||||
this.tradeLog.push("warn", t("log.mp.reconnectCancelPartial"));
|
||||
}
|
||||
} else {
|
||||
await this.exchange.cancelAllOrders({ symbol: this.config.symbol });
|
||||
this.tradeLog.push("order", "重连保护:已取消所有挂单");
|
||||
this.tradeLog.push("order", t("log.mp.reconnectCancelled"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -561,12 +561,12 @@ export class MakerPointsEngine {
|
||||
type: "position_opened",
|
||||
level: "info",
|
||||
symbol: this.config.symbol,
|
||||
title: "重连完成",
|
||||
message: "WebSocket 重连成功,已清理挂单状态",
|
||||
title: t("notify.mp.reconnectTitle"),
|
||||
message: t("notify.mp.reconnectBody"),
|
||||
details: { symbol },
|
||||
});
|
||||
} catch (error) {
|
||||
this.tradeLog.push("error", `重连保护流程失败: ${extractMessage(error)}`);
|
||||
this.tradeLog.push("error", t("log.mp.reconnectFailed", { error: extractMessage(error) }));
|
||||
} finally {
|
||||
this.reconnectResetPending = false;
|
||||
}
|
||||
@@ -682,7 +682,7 @@ export class MakerPointsEngine {
|
||||
absPosition >= closeThreshold - EPS);
|
||||
const prevCloseOnly = this.lastCloseOnly;
|
||||
if (closeOnly !== prevCloseOnly) {
|
||||
this.tradeLog.push("info", closeOnly ? "进入平仓模式,仅挂 reduce-only" : "退出平仓模式");
|
||||
this.tradeLog.push("info", closeOnly ? t("log.mp.closeOnlyEntered") : t("log.mp.closeOnlyExited"));
|
||||
this.lastCloseOnly = closeOnly;
|
||||
}
|
||||
|
||||
@@ -697,9 +697,9 @@ export class MakerPointsEngine {
|
||||
if (skipBuy !== prevSkipBuy || skipSell !== prevSkipSell) {
|
||||
if (skipBuy || skipSell) {
|
||||
const summary = `${skipBuy ? "BUY" : ""}${skipBuy && skipSell ? "/" : ""}${skipSell ? "SELL" : ""}`;
|
||||
this.tradeLog.push("info", `Binance 深度失衡,暂停 ${summary} 挂单`);
|
||||
this.tradeLog.push("info", t("log.mp.depthImbalancePause", { summary }));
|
||||
} else {
|
||||
this.tradeLog.push("info", "Binance 深度恢复,继续挂单");
|
||||
this.tradeLog.push("info", t("log.mp.depthImbalanceResume"));
|
||||
}
|
||||
this.lastSkipBuy = skipBuy;
|
||||
this.lastSkipSell = skipSell;
|
||||
@@ -748,9 +748,9 @@ export class MakerPointsEngine {
|
||||
if (isRateLimitError(error)) {
|
||||
hadRateLimit = true;
|
||||
this.rateLimit.registerRateLimit("maker-points");
|
||||
this.tradeLog.push("warn", `限频触发,暂停挂单: ${extractMessage(error)}`);
|
||||
this.tradeLog.push("warn", t("log.mp.rateLimited", { error: extractMessage(error) }));
|
||||
} else {
|
||||
this.tradeLog.push("error", `MakerPoints 主循环异常: ${extractMessage(error)}`);
|
||||
this.tradeLog.push("error", t("log.mp.tickFailed", { error: extractMessage(error) }));
|
||||
}
|
||||
this.emitUpdate();
|
||||
} finally {
|
||||
@@ -1004,18 +1004,18 @@ export class MakerPointsEngine {
|
||||
unlockOperating(this.locks, this.timers, this.pending, "LIMIT");
|
||||
this.openOrders = [];
|
||||
this.emitUpdate();
|
||||
this.tradeLog.push("order", "启动时清理历史挂单");
|
||||
this.tradeLog.push("order", t("log.spotMaker.startupCleanup"));
|
||||
this.initialOrderResetDone = true;
|
||||
return true;
|
||||
} catch (error) {
|
||||
if (isUnknownOrderError(error)) {
|
||||
this.tradeLog.push("order", "历史挂单已消失,跳过启动清理");
|
||||
this.tradeLog.push("order", t("log.spotMaker.startupCleanupGone"));
|
||||
this.initialOrderResetDone = true;
|
||||
this.openOrders = [];
|
||||
this.emitUpdate();
|
||||
return true;
|
||||
}
|
||||
this.tradeLog.push("error", `启动撤单失败: ${String(error)}`);
|
||||
this.tradeLog.push("error", t("log.spotMaker.startupCancelFailed", { error: String(error) }));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1047,16 +1047,20 @@ export class MakerPointsEngine {
|
||||
() => {
|
||||
this.tradeLog.push(
|
||||
"order",
|
||||
`撤销不匹配订单 ${order.side} @ ${order.price} reduceOnly=${order.reduceOnly}`
|
||||
t("log.spotMaker.cancelMismatched", {
|
||||
side: order.side,
|
||||
price: order.price,
|
||||
reduceOnly: order.reduceOnly,
|
||||
})
|
||||
);
|
||||
},
|
||||
() => {
|
||||
this.tradeLog.push("order", "撤销时发现订单已被成交/取消,忽略");
|
||||
this.tradeLog.push("order", t("log.spotMaker.cancelAlreadySettled"));
|
||||
this.pendingCancelOrders.delete(String(order.orderId));
|
||||
this.openOrders = this.openOrders.filter((existing) => existing.orderId !== order.orderId);
|
||||
},
|
||||
(error) => {
|
||||
this.tradeLog.push("error", `撤销订单失败: ${String(error)}`);
|
||||
this.tradeLog.push("error", t("log.spotMaker.cancelFailed", { error: String(error) }));
|
||||
this.pendingCancelOrders.delete(String(order.orderId));
|
||||
this.openOrders = this.openOrders.filter((existing) => existing.orderId !== order.orderId);
|
||||
}
|
||||
@@ -1096,12 +1100,12 @@ export class MakerPointsEngine {
|
||||
break;
|
||||
}
|
||||
if (isPrecisionError(error)) {
|
||||
this.tradeLog.push("warn", `检测到精度错误,重新同步: ${extractMessage(error)}`);
|
||||
this.tradeLog.push("warn", t("log.mp.precisionErrorResync", { error: extractMessage(error) }));
|
||||
this.precision.refresh();
|
||||
}
|
||||
this.tradeLog.push(
|
||||
"error",
|
||||
`挂单失败 ${target.side} @ ${target.price}: ${extractMessage(error)}`
|
||||
t("log.mp.placeFailed", { side: target.side, price: target.price, error: extractMessage(error) })
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1147,7 +1151,7 @@ export class MakerPointsEngine {
|
||||
if (unexpectedOrders.length > 0) {
|
||||
this.tradeLog.push(
|
||||
"warn",
|
||||
`发现 ${unexpectedOrders.length} 个未预期挂单,执行强制取消`
|
||||
t("log.mp.unexpectedOrders", { count: unexpectedOrders.length })
|
||||
);
|
||||
|
||||
// 强制取消所有挂单
|
||||
@@ -1160,7 +1164,7 @@ export class MakerPointsEngine {
|
||||
// 重置本地状态
|
||||
this.openOrders = [];
|
||||
this.pendingCancelOrders.clear();
|
||||
this.tradeLog.push("order", "已强制取消所有挂单,重置本地状态");
|
||||
this.tradeLog.push("order", t("log.mp.forceCancelled"));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1170,7 +1174,7 @@ export class MakerPointsEngine {
|
||||
this.openOrders = this.openOrders.filter((o) => realOrderIds.has(String(o.orderId)));
|
||||
}
|
||||
} catch (error) {
|
||||
this.tradeLog.push("error", `验证挂单状态失败: ${extractMessage(error)}`);
|
||||
this.tradeLog.push("error", t("log.mp.verifyOrdersFailed", { error: extractMessage(error) }));
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -1207,14 +1211,14 @@ export class MakerPointsEngine {
|
||||
// 不在这里设置冷却期,只有成功平仓后才设置
|
||||
this.tradeLog.push(
|
||||
"stop",
|
||||
`触发止损: 实时未实现亏损 ${realtimePnl.toFixed(4)} USDT`
|
||||
t("log.mp.stopTriggered", { pnl: realtimePnl.toFixed(4) })
|
||||
);
|
||||
this.notify({
|
||||
type: "stop_loss",
|
||||
level: "error",
|
||||
symbol: this.config.symbol,
|
||||
title: "止损触发",
|
||||
message: `实时未实现亏损 ${realtimePnl.toFixed(4)} USDT,强制平仓`,
|
||||
title: t("notify.mp.stopTitle"),
|
||||
message: t("notify.mp.stopBody", { pnl: realtimePnl.toFixed(4) }),
|
||||
details: {
|
||||
side: position.positionAmt > 0 ? "LONG" : "SHORT",
|
||||
size: absPosition,
|
||||
@@ -1242,7 +1246,7 @@ export class MakerPointsEngine {
|
||||
|
||||
// 仓位已清零,止损成功
|
||||
if (currentAbsPosition < EPS) {
|
||||
this.tradeLog.push("stop", "止损成功: 仓位已清零");
|
||||
this.tradeLog.push("stop", t("log.mp.stopSucceeded"));
|
||||
this.stopLossCooldownUntil = Date.now() + STOP_LOSS_COOLDOWN_MS;
|
||||
break;
|
||||
}
|
||||
@@ -1269,12 +1273,12 @@ export class MakerPointsEngine {
|
||||
} catch (error) {
|
||||
retryCount++;
|
||||
if (isUnknownOrderError(error)) {
|
||||
this.tradeLog.push("order", "止损平仓时订单已不存在,继续检查仓位");
|
||||
this.tradeLog.push("order", t("log.mp.stopOrderMissing"));
|
||||
} else if (isPrecisionError(error)) {
|
||||
this.tradeLog.push("warn", `止损平仓精度错误,重新同步: ${extractMessage(error)}`);
|
||||
this.tradeLog.push("warn", t("log.mp.stopPrecisionResync", { error: extractMessage(error) }));
|
||||
this.precision.refresh();
|
||||
} else {
|
||||
this.tradeLog.push("error", `止损平仓失败 (重试 ${retryCount}/${maxRetries}): ${extractMessage(error)}`);
|
||||
this.tradeLog.push("error", t("log.mp.stopRetry", { attempt: retryCount, max: maxRetries, error: extractMessage(error) }));
|
||||
}
|
||||
|
||||
// 失败后等待一段时间再重试
|
||||
@@ -1285,7 +1289,7 @@ export class MakerPointsEngine {
|
||||
}
|
||||
|
||||
if (retryCount >= maxRetries) {
|
||||
this.tradeLog.push("error", `止损重试已达上限 (${maxRetries} 次),请手动检查仓位`);
|
||||
this.tradeLog.push("error", t("log.mp.stopRetriesExhausted", { max: maxRetries }));
|
||||
// 达到重试上限后设置冷却期,避免持续重试
|
||||
this.stopLossCooldownUntil = Date.now() + STOP_LOSS_COOLDOWN_MS;
|
||||
}
|
||||
@@ -1312,12 +1316,12 @@ export class MakerPointsEngine {
|
||||
// No log on successful cancel
|
||||
},
|
||||
() => {
|
||||
this.tradeLog.push("order", "撤销时发现订单已被成交/取消,忽略");
|
||||
this.tradeLog.push("order", t("log.spotMaker.cancelAlreadySettled"));
|
||||
this.pendingCancelOrders.delete(String(order.orderId));
|
||||
this.openOrders = this.openOrders.filter((existing) => existing.orderId !== order.orderId);
|
||||
},
|
||||
(error) => {
|
||||
this.tradeLog.push("error", `撤销订单失败: ${String(error)}`);
|
||||
this.tradeLog.push("error", t("log.spotMaker.cancelFailed", { error: String(error) }));
|
||||
this.pendingCancelOrders.delete(String(order.orderId));
|
||||
this.openOrders = this.openOrders.filter((existing) => existing.orderId !== order.orderId);
|
||||
}
|
||||
@@ -1343,10 +1347,10 @@ export class MakerPointsEngine {
|
||||
try {
|
||||
const snapshot = this.buildSnapshot();
|
||||
this.events.emit("update", snapshot, (error) => {
|
||||
this.tradeLog.push("error", `更新监听异常: ${String(error)}`);
|
||||
this.tradeLog.push("error", t("log.mp.updateHandlerError", { error: String(error) }));
|
||||
});
|
||||
} catch (err) {
|
||||
this.tradeLog.push("error", `快照生成异常: ${String(err)}`);
|
||||
this.tradeLog.push("error", t("log.mp.snapshotError", { error: String(err) }));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1443,7 +1447,7 @@ export class MakerPointsEngine {
|
||||
private logDesiredOrders(desired: DesiredOrder[]): void {
|
||||
if (!desired.length) {
|
||||
if (this.lastDesiredSummary !== "none") {
|
||||
this.tradeLog.push("info", "暂无目标挂单");
|
||||
this.tradeLog.push("info", t("log.mp.noTargets"));
|
||||
this.lastDesiredSummary = "none";
|
||||
}
|
||||
return;
|
||||
@@ -1452,7 +1456,7 @@ export class MakerPointsEngine {
|
||||
.map((order) => `${order.side}@${order.price}${order.reduceOnly ? "(RO)" : ""}`)
|
||||
.join(" | ");
|
||||
if (summary !== this.lastDesiredSummary) {
|
||||
this.tradeLog.push("info", `目标挂单: ${summary}`);
|
||||
this.tradeLog.push("info", t("log.mp.targets", { summary }));
|
||||
this.lastDesiredSummary = summary;
|
||||
}
|
||||
}
|
||||
@@ -1471,7 +1475,7 @@ export class MakerPointsEngine {
|
||||
if (!alreadySkipped) {
|
||||
this.tradeLog.push(
|
||||
"info",
|
||||
`跳过 ${side} ${bps}bps 挂单: 深度 ${depthQty.toFixed(4)} BTC < ${minDepth} BTC`
|
||||
t("log.mp.skipThinDepth", { side, bps, depth: depthQty.toFixed(4), min: minDepth })
|
||||
);
|
||||
this.thinDepthSkipStatus[key] = true;
|
||||
}
|
||||
@@ -1483,7 +1487,7 @@ export class MakerPointsEngine {
|
||||
private resetThinDepthSkip(side: "BUY" | "SELL", bps: number): void {
|
||||
const key = `${side}_${bps}`;
|
||||
if (this.thinDepthSkipStatus[key]) {
|
||||
this.tradeLog.push("info", `${side} ${bps}bps 深度恢复,继续挂单`);
|
||||
this.tradeLog.push("info", t("log.mp.depthRecovered", { side, bps }));
|
||||
this.thinDepthSkipStatus[key] = false;
|
||||
}
|
||||
}
|
||||
@@ -1499,14 +1503,14 @@ export class MakerPointsEngine {
|
||||
this.insufficientBalanceCooldownUntil = now + INSUFFICIENT_BALANCE_COOLDOWN_MS;
|
||||
this.lastInsufficientMessage = detail;
|
||||
const seconds = Math.ceil(INSUFFICIENT_BALANCE_COOLDOWN_MS / 1000);
|
||||
this.tradeLog.push("warn", `余额不足,暂停挂单 ${seconds}s: ${detail}`);
|
||||
this.tradeLog.push("warn", t("log.mp.insufficientBalance", { seconds, detail }));
|
||||
this.insufficientBalanceNotified = true;
|
||||
}
|
||||
|
||||
private applyInsufficientBalanceState(now: number): boolean {
|
||||
const active = now < this.insufficientBalanceCooldownUntil;
|
||||
if (!active && this.insufficientBalanceNotified) {
|
||||
this.tradeLog.push("info", "余额恢复,继续挂单");
|
||||
this.tradeLog.push("info", t("log.mp.balanceRecovered"));
|
||||
this.insufficientBalanceNotified = false;
|
||||
this.lastInsufficientMessage = null;
|
||||
}
|
||||
@@ -1532,8 +1536,11 @@ export class MakerPointsEngine {
|
||||
type: "position_opened",
|
||||
level: "info",
|
||||
symbol: this.config.symbol,
|
||||
title: "开仓",
|
||||
message: `${currentSide === "LONG" ? "做多" : "做空"} ${Math.abs(currentAmt).toFixed(6)}`,
|
||||
title: t("notify.mp.openTitle"),
|
||||
message: t("notify.mp.openBody", {
|
||||
direction: currentSide === "LONG" ? t("trend.label.long") : t("trend.label.short"),
|
||||
qty: Math.abs(currentAmt).toFixed(6),
|
||||
}),
|
||||
details: {
|
||||
side: currentSide,
|
||||
size: Math.abs(currentAmt),
|
||||
@@ -1542,13 +1549,18 @@ export class MakerPointsEngine {
|
||||
});
|
||||
} else if (currentSide === "FLAT" && prevSide !== "FLAT") {
|
||||
const pnl = position.unrealizedProfit;
|
||||
const closeType = this.tokenExpiry.closeOnlyMode ? "Token过期平仓" : "平仓";
|
||||
const closeType = this.tokenExpiry.closeOnlyMode
|
||||
? t("notify.mp.closeTitleTokenExpired")
|
||||
: t("notify.mp.closeTitle");
|
||||
this.notify({
|
||||
type: "position_closed",
|
||||
level: "success",
|
||||
symbol: this.config.symbol,
|
||||
title: closeType,
|
||||
message: `已平仓 ${Math.abs(prevAmt).toFixed(6)} (${prevSide === "LONG" ? "多" : "空"})`,
|
||||
message: t("notify.mp.closeBody", {
|
||||
qty: Math.abs(prevAmt).toFixed(6),
|
||||
direction: prevSide === "LONG" ? t("common.direction.long") : t("common.direction.short"),
|
||||
}),
|
||||
details: {
|
||||
prevSide,
|
||||
closedSize: Math.abs(prevAmt),
|
||||
@@ -1562,8 +1574,12 @@ export class MakerPointsEngine {
|
||||
type: "order_filled",
|
||||
level: "info",
|
||||
symbol: this.config.symbol,
|
||||
title: "加仓",
|
||||
message: `${currentSide === "LONG" ? "做多" : "做空"} +${absChange.toFixed(6)} → ${Math.abs(currentAmt).toFixed(6)}`,
|
||||
title: t("notify.mp.increaseTitle"),
|
||||
message: t("notify.mp.increaseBody", {
|
||||
direction: currentSide === "LONG" ? t("trend.label.long") : t("trend.label.short"),
|
||||
delta: absChange.toFixed(6),
|
||||
qty: Math.abs(currentAmt).toFixed(6),
|
||||
}),
|
||||
details: {
|
||||
side: currentSide,
|
||||
added: absChange,
|
||||
@@ -1575,8 +1591,12 @@ export class MakerPointsEngine {
|
||||
type: "order_filled",
|
||||
level: "info",
|
||||
symbol: this.config.symbol,
|
||||
title: "减仓",
|
||||
message: `${currentSide === "LONG" ? "多" : "空"} -${absChange.toFixed(6)} → ${Math.abs(currentAmt).toFixed(6)}`,
|
||||
title: t("notify.mp.reduceTitle"),
|
||||
message: t("notify.mp.reduceBody", {
|
||||
direction: currentSide === "LONG" ? t("common.direction.long") : t("common.direction.short"),
|
||||
delta: absChange.toFixed(6),
|
||||
qty: Math.abs(currentAmt).toFixed(6),
|
||||
}),
|
||||
details: {
|
||||
side: currentSide,
|
||||
reduced: absChange,
|
||||
@@ -1589,8 +1609,14 @@ export class MakerPointsEngine {
|
||||
type: "position_opened",
|
||||
level: "info",
|
||||
symbol: this.config.symbol,
|
||||
title: "反向开仓",
|
||||
message: `${prevSide === "LONG" ? "多→空" : "空→多"} ${Math.abs(currentAmt).toFixed(6)}`,
|
||||
title: t("notify.mp.reverseTitle"),
|
||||
message: t("notify.mp.reverseBody", {
|
||||
transition:
|
||||
prevSide === "LONG"
|
||||
? t("common.direction.longToShort")
|
||||
: t("common.direction.shortToLong"),
|
||||
qty: Math.abs(currentAmt).toFixed(6),
|
||||
}),
|
||||
details: {
|
||||
prevSide,
|
||||
newSide: currentSide,
|
||||
@@ -1646,7 +1672,7 @@ export class MakerPointsEngine {
|
||||
this.defenseMode = true;
|
||||
const staleSummary = describeDefenseReasons(reasons);
|
||||
|
||||
this.tradeLog.push("warn", `数据过时检测: ${staleSummary},进入防御模式`);
|
||||
this.tradeLog.push("warn", t("log.mp.defenseEntered", { summary: staleSummary }));
|
||||
|
||||
// 发送通知
|
||||
if (!this.defenseModeNotified) {
|
||||
@@ -1654,8 +1680,8 @@ export class MakerPointsEngine {
|
||||
type: "token_expired",
|
||||
level: "warn",
|
||||
symbol: this.config.symbol,
|
||||
title: "防御模式",
|
||||
message: `数据推送中断: ${staleSummary},已取消所有挂单`,
|
||||
title: t("notify.mp.defenseTitle"),
|
||||
message: t("notify.mp.defenseBody", { summary: staleSummary }),
|
||||
details: reasons,
|
||||
});
|
||||
this.defenseModeNotified = true;
|
||||
@@ -1675,14 +1701,14 @@ export class MakerPointsEngine {
|
||||
this.defenseMode = false;
|
||||
this.defenseModeNotified = false;
|
||||
|
||||
this.tradeLog.push("info", "数据推送恢复正常,退出防御模式");
|
||||
this.tradeLog.push("info", t("log.mp.defenseExited"));
|
||||
|
||||
this.notify({
|
||||
type: "position_opened",
|
||||
level: "info",
|
||||
symbol: this.config.symbol,
|
||||
title: "防御模式解除",
|
||||
message: "数据推送恢复正常,恢复正常交易",
|
||||
title: t("notify.mp.defenseClearedTitle"),
|
||||
message: t("notify.mp.defenseClearedBody"),
|
||||
details: {},
|
||||
});
|
||||
|
||||
@@ -1704,13 +1730,13 @@ export class MakerPointsEngine {
|
||||
if (this.exchange.forceCancelAllOrders) {
|
||||
const success = await this.exchange.forceCancelAllOrders();
|
||||
if (success) {
|
||||
this.tradeLog.push("order", "防御模式: 已强制取消所有挂单");
|
||||
this.tradeLog.push("order", t("log.mp.defenseForceCancelled"));
|
||||
} else {
|
||||
this.tradeLog.push("warn", "防御模式: 取消挂单未完全成功,将继续重试");
|
||||
this.tradeLog.push("warn", t("log.mp.defenseCancelPartial"));
|
||||
}
|
||||
} else {
|
||||
await this.exchange.cancelAllOrders({ symbol: this.config.symbol });
|
||||
this.tradeLog.push("order", "防御模式: 已取消所有挂单");
|
||||
this.tradeLog.push("order", t("log.mp.defenseCancelled"));
|
||||
}
|
||||
|
||||
// 重置本地挂单状态
|
||||
@@ -1719,11 +1745,11 @@ export class MakerPointsEngine {
|
||||
unlockOperating(this.locks, this.timers, this.pending, "LIMIT");
|
||||
} catch (error) {
|
||||
if (isUnknownOrderError(error)) {
|
||||
this.tradeLog.push("order", "防御模式: 挂单已不存在");
|
||||
this.tradeLog.push("order", t("log.mp.defenseOrdersGone"));
|
||||
this.openOrders = [];
|
||||
this.pendingCancelOrders.clear();
|
||||
} else {
|
||||
this.tradeLog.push("error", `防御模式取消挂单失败: ${extractMessage(error)}`);
|
||||
this.tradeLog.push("error", t("log.mp.defenseCancelFailed", { error: extractMessage(error) }));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1736,7 +1762,7 @@ export class MakerPointsEngine {
|
||||
if (this.defenseRestPollActive) return;
|
||||
this.defenseRestPollActive = true;
|
||||
|
||||
this.tradeLog.push("info", "防御模式: 启动 REST 数据轮询");
|
||||
this.tradeLog.push("info", t("log.mp.defensePollStarted"));
|
||||
|
||||
const poll = async () => {
|
||||
if (!this.defenseRestPollActive || !this.defenseMode) return;
|
||||
@@ -1748,10 +1774,10 @@ export class MakerPointsEngine {
|
||||
this.applyAccountSnapshot(nextAccount);
|
||||
const health = validateAccountSnapshotForSymbol(nextAccount, this.config.symbol);
|
||||
if (!health.ok) {
|
||||
this.tradeLog.push("warn", `防御模式: 仓位数据仍异常: ${health.issues.join(",")}`);
|
||||
this.tradeLog.push("warn", t("log.mp.defensePositionStillBad", { issues: health.issues.join(",") }));
|
||||
}
|
||||
} else {
|
||||
this.tradeLog.push("warn", "防御模式: REST 获取账户快照为空");
|
||||
this.tradeLog.push("warn", t("log.mp.defenseEmptySnapshot"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1776,11 +1802,11 @@ export class MakerPointsEngine {
|
||||
this.feedStatus.orders = true;
|
||||
|
||||
if (realOrders.length > 0) {
|
||||
this.tradeLog.push("warn", `防御模式: 发现 ${realOrders.length} 个挂单,执行取消`);
|
||||
this.tradeLog.push("warn", t("log.mp.defenseFoundOrders", { count: realOrders.length }));
|
||||
await this.defenseCancelAllOrders();
|
||||
}
|
||||
} catch (error) {
|
||||
this.tradeLog.push("error", `防御模式查询挂单失败: ${extractMessage(error)}`);
|
||||
this.tradeLog.push("error", t("log.mp.defenseQueryFailed", { error: extractMessage(error) }));
|
||||
// 查询失败时仍然尝试撤销所有挂单(宁可多撤,也不遗留)
|
||||
await this.defenseCancelAllOrders();
|
||||
}
|
||||
@@ -1791,7 +1817,7 @@ export class MakerPointsEngine {
|
||||
// 检查止损条件(使用当前账户快照中的数据)
|
||||
// checkStopLoss 会继续运行,使用最后收到的数据进行止损判断
|
||||
} catch (error) {
|
||||
this.tradeLog.push("error", `防御模式 REST 轮询失败: ${extractMessage(error)}`);
|
||||
this.tradeLog.push("error", t("log.mp.defensePollFailed", { error: extractMessage(error) }));
|
||||
}
|
||||
|
||||
// 继续下一次轮询
|
||||
@@ -1813,7 +1839,7 @@ export class MakerPointsEngine {
|
||||
clearTimeout(this.defenseRestPollTimer);
|
||||
this.defenseRestPollTimer = null;
|
||||
}
|
||||
this.tradeLog.push("info", "防御模式: 停止 REST 数据轮询");
|
||||
this.tradeLog.push("info", t("log.mp.defensePollStopped"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ import type { MakerEngineSnapshot } from "./maker-engine";
|
||||
import { makeOrderPlan } from "../core/lib/order-plan";
|
||||
import { safeCancelOrder } from "../core/lib/orders";
|
||||
import { RateLimitController } from "../core/lib/rate-limit";
|
||||
import { t } from "../i18n";
|
||||
import { StrategyEventEmitter } from "./common/event-emitter";
|
||||
import { safeSubscribe, type LogHandler } from "./common/subscriptions";
|
||||
import { SessionVolumeTracker } from "./common/session-volume";
|
||||
@@ -223,8 +224,8 @@ export class OffsetMakerEngine {
|
||||
},
|
||||
log,
|
||||
{
|
||||
subscribeFail: (error) => `订阅账户失败: ${String(error)}`,
|
||||
processFail: (error) => `账户推送处理异常: ${String(error)}`,
|
||||
subscribeFail: (error) => t("log.subscribe.accountFail", { error: String(error) }),
|
||||
processFail: (error) => t("log.process.accountError", { error: String(error) }),
|
||||
}
|
||||
);
|
||||
|
||||
@@ -252,8 +253,8 @@ export class OffsetMakerEngine {
|
||||
},
|
||||
log,
|
||||
{
|
||||
subscribeFail: (error) => `订阅订单失败: ${String(error)}`,
|
||||
processFail: (error) => `订单推送处理异常: ${String(error)}`,
|
||||
subscribeFail: (error) => t("log.subscribe.orderFail", { error: String(error) }),
|
||||
processFail: (error) => t("log.process.orderError", { error: String(error) }),
|
||||
}
|
||||
);
|
||||
|
||||
@@ -266,8 +267,8 @@ export class OffsetMakerEngine {
|
||||
},
|
||||
log,
|
||||
{
|
||||
subscribeFail: (error) => `订阅深度失败: ${String(error)}`,
|
||||
processFail: (error) => `深度推送处理异常: ${String(error)}`,
|
||||
subscribeFail: (error) => t("log.subscribe.depthFail", { error: String(error) }),
|
||||
processFail: (error) => t("log.process.depthError", { error: String(error) }),
|
||||
}
|
||||
);
|
||||
|
||||
@@ -280,8 +281,8 @@ export class OffsetMakerEngine {
|
||||
},
|
||||
log,
|
||||
{
|
||||
subscribeFail: (error) => `订阅Ticker失败: ${String(error)}`,
|
||||
processFail: (error) => `价格推送处理异常: ${String(error)}`,
|
||||
subscribeFail: (error) => t("log.subscribe.tickerFail", { error: String(error) }),
|
||||
processFail: (error) => t("log.process.tickerError", { error: String(error) }),
|
||||
}
|
||||
);
|
||||
|
||||
@@ -300,8 +301,8 @@ export class OffsetMakerEngine {
|
||||
},
|
||||
log,
|
||||
{
|
||||
subscribeFail: (error) => `订阅K线失败: ${String(error)}`,
|
||||
processFail: (error) => `K线推送处理异常: ${String(error)}`,
|
||||
subscribeFail: (error) => t("log.subscribe.klineFail", { error: String(error) }),
|
||||
processFail: (error) => t("log.process.klineError", { error: String(error) }),
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -421,13 +422,13 @@ export class OffsetMakerEngine {
|
||||
// 无法卖出,跳过卖单,允许买单累计
|
||||
this.lastSellPriceViable = false;
|
||||
if (!skipSellSide) {
|
||||
this.tradeLog.push("info", "现货持仓低于最小卖单量,暂不挂卖单");
|
||||
this.tradeLog.push("info", t("log.spotMaker.belowMinSellHold"));
|
||||
}
|
||||
}
|
||||
if (!skipBuySide && canEnter) {
|
||||
if (!allowSpotBuy) {
|
||||
if (this.lastBuyPriceViable) {
|
||||
this.tradeLog.push("info", "现货买入仅在1m阳线,当前跳过买单");
|
||||
this.tradeLog.push("info", t("log.spotMaker.buyOnlyOnGreenCandle"));
|
||||
this.lastBuyPriceViable = false;
|
||||
}
|
||||
} else {
|
||||
@@ -444,8 +445,8 @@ export class OffsetMakerEngine {
|
||||
this.lastBuyPriceViable = false;
|
||||
const reason =
|
||||
buyAmount < EPS && isSpotMarket
|
||||
? "现货可用报价资产不足,跳过买单"
|
||||
: "跳过买单:价差不足以构造maker价格";
|
||||
? t("log.spotMaker.quoteBalanceShort")
|
||||
: t("log.spotMaker.spreadTooTightBuy");
|
||||
this.tradeLog.push("info", reason);
|
||||
}
|
||||
}
|
||||
@@ -456,7 +457,7 @@ export class OffsetMakerEngine {
|
||||
// 持仓低于最小卖单量,跳过卖单,等待累积
|
||||
if (this.lastSellPriceViable) {
|
||||
this.lastSellPriceViable = false;
|
||||
this.tradeLog.push("info", "现货持仓低于最小卖单量,跳过卖单");
|
||||
this.tradeLog.push("info", t("log.spotMaker.belowMinSellSkip"));
|
||||
}
|
||||
} else {
|
||||
const desiredSellAmount =
|
||||
@@ -474,8 +475,8 @@ export class OffsetMakerEngine {
|
||||
this.lastSellPriceViable = false;
|
||||
const reason =
|
||||
sellAmount < EPS && isSpotMarket
|
||||
? "现货可用基础资产不足,跳过卖单"
|
||||
: "跳过卖单:价差不足以构造maker价格";
|
||||
? t("log.spotMaker.baseBalanceShort")
|
||||
: t("log.spotMaker.spreadTooTightSell");
|
||||
this.tradeLog.push("info", reason);
|
||||
}
|
||||
}
|
||||
@@ -485,7 +486,7 @@ export class OffsetMakerEngine {
|
||||
if (!skipBuySide && canEnter) {
|
||||
if (isSpotMarket && !allowSpotBuy) {
|
||||
if (this.lastBuyPriceViable) {
|
||||
this.tradeLog.push("info", "现货买入仅在1m阳线,当前跳过买单");
|
||||
this.tradeLog.push("info", t("log.spotMaker.buyOnlyOnGreenCandle"));
|
||||
this.lastBuyPriceViable = false;
|
||||
}
|
||||
} else if (bidPrice != null) {
|
||||
@@ -500,7 +501,7 @@ export class OffsetMakerEngine {
|
||||
this.sellableBase(balancesForSpot) + EPS < minSell;
|
||||
if (belowMinSell) {
|
||||
this.lastSellPriceViable = false;
|
||||
this.tradeLog.push("info", "现货持仓低于最小卖单量,跳过卖单");
|
||||
this.tradeLog.push("info", t("log.spotMaker.belowMinSellSkip"));
|
||||
} else if (askPrice != null) {
|
||||
desired.push({ side: "SELL", price: askPrice, amount: this.config.tradeAmount, reduceOnly: false });
|
||||
}
|
||||
@@ -543,7 +544,7 @@ export class OffsetMakerEngine {
|
||||
await this.enforceRateLimitStop();
|
||||
this.tradeLog.push("warn", `OffsetMakerEngine 429: ${String(error)}`);
|
||||
} else {
|
||||
this.tradeLog.push("error", `偏移做市循环异常: ${String(error)}`);
|
||||
this.tradeLog.push("error", t("log.offsetMaker.tickFailed", { error: String(error) }));
|
||||
}
|
||||
this.emitUpdate();
|
||||
} finally {
|
||||
@@ -580,9 +581,9 @@ export class OffsetMakerEngine {
|
||||
});
|
||||
} catch (error) {
|
||||
if (isUnknownOrderError(error)) {
|
||||
this.tradeLog.push("order", "限频强制平仓时订单已不存在");
|
||||
this.tradeLog.push("order", t("log.spotMaker.rateLimitCloseMissing"));
|
||||
} else {
|
||||
this.tradeLog.push("error", `限频强制平仓失败: ${String(error)}`);
|
||||
this.tradeLog.push("error", t("log.spotMaker.rateLimitCloseFailed", { error: String(error) }));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -600,18 +601,18 @@ export class OffsetMakerEngine {
|
||||
unlockOperating(this.locks, this.timers, this.pending, "LIMIT");
|
||||
this.openOrders = [];
|
||||
this.emitUpdate();
|
||||
this.tradeLog.push("order", "启动时清理历史挂单");
|
||||
this.tradeLog.push("order", t("log.spotMaker.startupCleanup"));
|
||||
this.initialOrderResetDone = true;
|
||||
return true;
|
||||
} catch (error) {
|
||||
if (isUnknownOrderError(error)) {
|
||||
this.tradeLog.push("order", "历史挂单已消失,跳过启动清理");
|
||||
this.tradeLog.push("order", t("log.spotMaker.startupCleanupGone"));
|
||||
this.initialOrderResetDone = true;
|
||||
this.openOrders = [];
|
||||
this.emitUpdate();
|
||||
return true;
|
||||
}
|
||||
this.tradeLog.push("error", `启动撤单失败: ${String(error)}`);
|
||||
this.tradeLog.push("error", t("log.spotMaker.startupCancelFailed", { error: String(error) }));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -647,7 +648,11 @@ export class OffsetMakerEngine {
|
||||
const closeSidePrice = side === "SELL" ? bid : ask;
|
||||
this.tradeLog.push(
|
||||
"stop",
|
||||
`深度极端不平衡(${buySum.toFixed(4)} vs ${sellSum.toFixed(4)}), 市价平仓 ${side}`
|
||||
t("log.offsetMaker.imbalanceClose", {
|
||||
buySum: buySum.toFixed(4),
|
||||
sellSum: sellSum.toFixed(4),
|
||||
side,
|
||||
})
|
||||
);
|
||||
try {
|
||||
await this.flushOrders();
|
||||
@@ -664,9 +669,9 @@ export class OffsetMakerEngine {
|
||||
});
|
||||
} catch (error) {
|
||||
if (isUnknownOrderError(error)) {
|
||||
this.tradeLog.push("order", "深度不平衡平仓时订单已不存在");
|
||||
this.tradeLog.push("order", t("log.offsetMaker.imbalanceCloseMissing"));
|
||||
} else {
|
||||
this.tradeLog.push("error", `深度不平衡平仓失败: ${String(error)}`);
|
||||
this.tradeLog.push("error", t("log.offsetMaker.imbalanceCloseFailed", { error: String(error) }));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -712,17 +717,21 @@ export class OffsetMakerEngine {
|
||||
() => {
|
||||
this.tradeLog.push(
|
||||
"order",
|
||||
`撤销不匹配订单 ${order.side} @ ${order.price} reduceOnly=${order.reduceOnly}`
|
||||
t("log.spotMaker.cancelMismatched", {
|
||||
side: order.side,
|
||||
price: order.price,
|
||||
reduceOnly: order.reduceOnly,
|
||||
})
|
||||
);
|
||||
// 保持与原逻辑一致:成功撤销不立即修改本地 openOrders,等待订单流重建
|
||||
},
|
||||
() => {
|
||||
this.tradeLog.push("order", "撤销时发现订单已被成交/取消,忽略");
|
||||
this.tradeLog.push("order", t("log.spotMaker.cancelAlreadySettled"));
|
||||
this.pendingCancelOrders.delete(String(order.orderId));
|
||||
this.openOrders = this.openOrders.filter((existing) => existing.orderId !== order.orderId);
|
||||
},
|
||||
(error) => {
|
||||
this.tradeLog.push("error", `撤销订单失败: ${String(error)}`);
|
||||
this.tradeLog.push("error", t("log.spotMaker.cancelFailed", { error: String(error) }));
|
||||
this.pendingCancelOrders.delete(String(order.orderId));
|
||||
// 避免同一轮内重复操作同一张已出错的本地挂单,直接从本地缓存移除,等待下一次订单推送重建
|
||||
this.openOrders = this.openOrders.filter((existing) => existing.orderId !== order.orderId);
|
||||
@@ -742,7 +751,7 @@ export class OffsetMakerEngine {
|
||||
// Skip placing sells that would be bumped by venue minimums
|
||||
if (this.lastSellPriceViable) {
|
||||
this.lastSellPriceViable = false;
|
||||
this.tradeLog.push("info", "现货卖单低于最小成交量,跳过挂单等待累积");
|
||||
this.tradeLog.push("info", t("log.spotMaker.sellBelowMinNotional"));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -775,10 +784,10 @@ export class OffsetMakerEngine {
|
||||
if (isRateLimitError(dustError)) {
|
||||
throw dustError;
|
||||
}
|
||||
this.tradeLog.push("error", `小额市价平仓失败: ${String(dustError)}`);
|
||||
this.tradeLog.push("error", t("log.spotMaker.dustCloseFailed", { error: String(dustError) }));
|
||||
}
|
||||
if (dustClosed) continue;
|
||||
this.tradeLog.push("error", `挂单失败(${target.side} ${target.price}): ${String(error)}`);
|
||||
this.tradeLog.push("error", t("log.spotMaker.placeFailed", { side: target.side, price: target.price, error: String(error) }));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -794,7 +803,7 @@ export class OffsetMakerEngine {
|
||||
const minStopQty = Number.isFinite(this.precision.minBaseAmount) ? this.precision.minBaseAmount! : null;
|
||||
if (minStopQty != null && minStopQty > 0 && absPosition + EPS < minStopQty) {
|
||||
if (!this.lastSpotStopSkipped) {
|
||||
this.tradeLog.push("info", "现货持仓低于最小平仓数量,跳过止损检查");
|
||||
this.tradeLog.push("info", t("log.spotMaker.belowMinCloseSkipStop"));
|
||||
this.lastSpotStopSkipped = true;
|
||||
}
|
||||
return;
|
||||
@@ -803,7 +812,7 @@ export class OffsetMakerEngine {
|
||||
const pnl = computePositionPnl(position, bidPrice, askPrice);
|
||||
const triggerStop = shouldStopLoss(position, bidPrice, askPrice, this.config.lossLimit);
|
||||
if (!triggerStop) return;
|
||||
this.tradeLog.push("stop", `现货止损,当前仓位=${absPosition.toFixed(6)} PnL=${pnl.toFixed(4)} USDT`);
|
||||
this.tradeLog.push("stop", t("log.spotMaker.spotStop", { qty: absPosition.toFixed(6), pnl: pnl.toFixed(4) }));
|
||||
try {
|
||||
// 尽力撤销所有未完成挂单,避免锁定基础资产导致余额不足
|
||||
await this.exchange.cancelAllOrders({ symbol: this.config.symbol }).catch(() => {});
|
||||
@@ -822,9 +831,9 @@ export class OffsetMakerEngine {
|
||||
} catch (error) {
|
||||
if (isRateLimitError(error)) throw error;
|
||||
if (isUnknownOrderError(error)) {
|
||||
this.tradeLog.push("order", "止损平仓时订单已不存在");
|
||||
this.tradeLog.push("order", t("log.spotMaker.stopCloseMissing"));
|
||||
} else {
|
||||
this.tradeLog.push("error", `现货止损失败: ${String(error)}`);
|
||||
this.tradeLog.push("error", t("log.spotMaker.spotStopFailed", { error: String(error) }));
|
||||
}
|
||||
}
|
||||
return;
|
||||
@@ -835,7 +844,7 @@ export class OffsetMakerEngine {
|
||||
const hasEntryPrice = Number.isFinite(position.entryPrice) && Math.abs(position.entryPrice) > 1e-8;
|
||||
if (!hasEntryPrice) {
|
||||
if (!this.entryPricePendingLogged) {
|
||||
this.tradeLog.push("info", "做市持仓均价未同步,等待账户快照刷新后再执行止损判断");
|
||||
this.tradeLog.push("info", t("log.spotMaker.entryPricePending"));
|
||||
this.entryPricePendingLogged = true;
|
||||
}
|
||||
return;
|
||||
@@ -848,7 +857,10 @@ export class OffsetMakerEngine {
|
||||
if (triggerStop) {
|
||||
this.tradeLog.push(
|
||||
"stop",
|
||||
`触发止损,方向=${position.positionAmt > 0 ? "多" : "空"} 当前亏损=${pnl.toFixed(4)} USDT`
|
||||
t("log.spotMaker.stopTriggered", {
|
||||
direction: position.positionAmt > 0 ? t("common.direction.long") : t("common.direction.short"),
|
||||
pnl: pnl.toFixed(4),
|
||||
})
|
||||
);
|
||||
try {
|
||||
await this.flushOrders();
|
||||
@@ -865,9 +877,9 @@ export class OffsetMakerEngine {
|
||||
});
|
||||
} catch (error) {
|
||||
if (isUnknownOrderError(error)) {
|
||||
this.tradeLog.push("order", "止损平仓时订单已不存在");
|
||||
this.tradeLog.push("order", t("log.spotMaker.stopCloseMissing"));
|
||||
} else {
|
||||
this.tradeLog.push("error", `止损平仓失败: ${String(error)}`);
|
||||
this.tradeLog.push("error", t("log.spotMaker.stopCloseFailed", { error: String(error) }));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -886,12 +898,12 @@ export class OffsetMakerEngine {
|
||||
// 与原逻辑保持一致:成功撤销不记录日志且不修改本地 openOrders
|
||||
},
|
||||
() => {
|
||||
this.tradeLog.push("order", "订单已不存在,撤销跳过");
|
||||
this.tradeLog.push("order", t("log.spotMaker.orderMissingOnCancel"));
|
||||
this.pendingCancelOrders.delete(String(order.orderId));
|
||||
this.openOrders = this.openOrders.filter((existing) => existing.orderId !== order.orderId);
|
||||
},
|
||||
(error) => {
|
||||
this.tradeLog.push("error", `撤销订单失败: ${String(error)}`);
|
||||
this.tradeLog.push("error", t("log.spotMaker.cancelFailed", { error: String(error) }));
|
||||
this.pendingCancelOrders.delete(String(order.orderId));
|
||||
// 与同步撤单路径保持一致,移除本地异常订单,等待订单流重建
|
||||
this.openOrders = this.openOrders.filter((existing) => existing.orderId !== order.orderId);
|
||||
@@ -911,10 +923,10 @@ export class OffsetMakerEngine {
|
||||
try {
|
||||
const snapshot = this.buildSnapshot();
|
||||
this.events.emit("update", snapshot, (error) => {
|
||||
this.tradeLog.push("error", `更新回调处理异常: ${String(error)}`);
|
||||
this.tradeLog.push("error", t("log.spotMaker.updateHandlerError", { error: String(error) }));
|
||||
});
|
||||
} catch (err) {
|
||||
this.tradeLog.push("error", `快照或更新分发异常: ${String(err)}`);
|
||||
this.tradeLog.push("error", t("log.spotMaker.snapshotDispatchError", { error: String(err) }));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1117,13 +1129,13 @@ export class OffsetMakerEngine {
|
||||
},
|
||||
qtyStep: this.precision.qtyStep
|
||||
});
|
||||
this.tradeLog.push("order", `小额仓位使用市价平仓 ${target.side} 数量 ${absQty.toFixed(6)}`);
|
||||
this.tradeLog.push("order", t("log.spotMaker.dustClose", { side: target.side, qty: absQty.toFixed(6) }));
|
||||
return true;
|
||||
} catch (closeError) {
|
||||
if (isRateLimitError(closeError)) {
|
||||
throw closeError;
|
||||
}
|
||||
this.tradeLog.push("error", `小额市价平仓失败: ${String(closeError)}`);
|
||||
this.tradeLog.push("error", t("log.spotMaker.dustCloseFailed", { error: String(closeError) }));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user