mirror of
https://github.com/discountry/ritmex-bot.git
synced 2026-09-11 17:28:08 +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:
@@ -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