mirror of
https://github.com/discountry/ritmex-bot.git
synced 2026-09-11 17:28:08 +00:00
refactor(core): give order functions a parameter object
placeOrder took 13 positional arguments; the other five order functions took 9-13. The leading six — adapter, symbol, openOrders, locks, timers, pendings — were the same values at all 36 call sites, and every engine spelled them out again for each order it placed. Introduce Parameter Object: OrderContext holds what is fixed for an engine's lifetime (exposed once via a lazily-built this.orderContext), and each function takes a named request. A wrong argument order is now a compile error rather than a silently misrouted order. The type change surfaced dead weight: placeOrder's opts.priceTick was never read by its body, yet five engines passed it. Removed. Also finishes the PrecisionSyncer migration — grid-engine was the ninth copy and was missed last round, so it still carried the uncleared retry timer. Extract Function: normalizeQuantity replaces the round-down-but-never-to-zero block that appeared in all five order functions. 250 pass; tsc and oxlint clean.
This commit is contained in:
+46
-81
@@ -10,11 +10,13 @@ import {
|
||||
placeOrder,
|
||||
placeStopLossOrder,
|
||||
unlockOperating,
|
||||
type OrderContext,
|
||||
type OrderLockMap,
|
||||
type OrderPendingMap,
|
||||
type OrderTimerMap,
|
||||
} from "../core/order-coordinator";
|
||||
import { StrategyEventEmitter } from "./common/event-emitter";
|
||||
import { createPrecisionSyncer, type PrecisionSyncer } from "./common/precision-syncer";
|
||||
import { safeSubscribe, type LogHandler } from "./common/subscriptions";
|
||||
import { clearGridState, loadGridState, saveGridState } from "./common/grid-storage";
|
||||
import {
|
||||
@@ -170,7 +172,7 @@ export class GridEngine {
|
||||
private savePending = false;
|
||||
private uncoveredQty = 0;
|
||||
private desiredOrders: DesiredGridOrder[] = [];
|
||||
private precisionSync: Promise<void> | null = null;
|
||||
private readonly precision: PrecisionSyncer;
|
||||
|
||||
constructor(
|
||||
private readonly config: GridConfig,
|
||||
@@ -187,11 +189,25 @@ export class GridEngine {
|
||||
this.stopReason = "配置无效,已暂停网格";
|
||||
this.log("error", this.stopReason);
|
||||
}
|
||||
this.syncPrecision();
|
||||
this.precision = createPrecisionSyncer(this.exchange, this.config, this.config.qtyStep, this.log);
|
||||
this.precision.start();
|
||||
this.bootstrap();
|
||||
this.setupConnectionProtection();
|
||||
}
|
||||
|
||||
/** Bundles the fixed order-routing state; rebuilt lazily on first use. */
|
||||
private get orderContext(): OrderContext {
|
||||
return (this.orderContextCache ??= {
|
||||
adapter: this.exchange,
|
||||
symbol: this.config.symbol,
|
||||
locks: this.locks,
|
||||
timers: this.timers,
|
||||
pendings: this.pendings,
|
||||
log: (type, detail) => this.tradeLog.push(type, detail),
|
||||
});
|
||||
}
|
||||
private orderContextCache: OrderContext | null = null;
|
||||
|
||||
start(): void {
|
||||
if (this.timer || !this.running) {
|
||||
if (!this.timer && !this.running) {
|
||||
@@ -209,6 +225,7 @@ export class GridEngine {
|
||||
clearInterval(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
this.precision.stop();
|
||||
}
|
||||
|
||||
on(event: GridEvent, listener: GridListener): void {
|
||||
@@ -277,37 +294,6 @@ export class GridEngine {
|
||||
// Precision sync
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
private syncPrecision(): void {
|
||||
if (this.precisionSync) return;
|
||||
const getPrecision = this.exchange.getPrecision?.bind(this.exchange);
|
||||
if (!getPrecision) return;
|
||||
this.precisionSync = getPrecision()
|
||||
.then((precision) => {
|
||||
if (!precision) return;
|
||||
let updated = false;
|
||||
if (Number.isFinite(precision.priceTick) && precision.priceTick > 0) {
|
||||
if (Math.abs(precision.priceTick - this.config.priceTick) > 1e-12) {
|
||||
this.config.priceTick = precision.priceTick;
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
if (Number.isFinite(precision.qtyStep) && precision.qtyStep > 0) {
|
||||
if (Math.abs(precision.qtyStep - this.config.qtyStep) > 1e-12) {
|
||||
this.config.qtyStep = precision.qtyStep;
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
if (updated) {
|
||||
this.log("info", `已同步交易精度: priceTick=${precision.priceTick} qtyStep=${precision.qtyStep}`);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
this.log("error", `同步精度失败: ${extractMessage(error)}`);
|
||||
this.precisionSync = null;
|
||||
setTimeout(() => this.syncPrecision(), 2000);
|
||||
});
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Feed subscriptions / connection events
|
||||
// -----------------------------------------------------------------------
|
||||
@@ -744,26 +730,16 @@ export class GridEngine {
|
||||
const ordersVersionBeforePlace = this.ordersVersion;
|
||||
try {
|
||||
this.lastLimitAttemptAt = now;
|
||||
placed = await placeOrder(
|
||||
this.exchange,
|
||||
this.config.symbol,
|
||||
this.openOrders,
|
||||
this.locks,
|
||||
this.timers,
|
||||
this.pendings,
|
||||
action.side,
|
||||
action.price,
|
||||
action.qty,
|
||||
this.log,
|
||||
isEntry ? false : this.config.useReduceOnlyForExit,
|
||||
undefined,
|
||||
{
|
||||
priceTick: this.config.priceTick,
|
||||
qtyStep: this.config.qtyStep,
|
||||
skipDedupe: true,
|
||||
clientOrderId,
|
||||
}
|
||||
);
|
||||
placed = await placeOrder(this.orderContext, {
|
||||
openOrders: this.openOrders,
|
||||
side: action.side,
|
||||
price: action.price,
|
||||
amount: action.qty,
|
||||
reduceOnly: isEntry ? false : this.config.useReduceOnlyForExit,
|
||||
qtyStep: this.config.qtyStep,
|
||||
skipDedupe: true,
|
||||
clientOrderId,
|
||||
});
|
||||
} catch (error) {
|
||||
this.log("error", `挂单失败 (${action.side} @ ${action.price}): ${extractMessage(error)}`);
|
||||
}
|
||||
@@ -829,23 +805,17 @@ export class GridEngine {
|
||||
}
|
||||
}
|
||||
try {
|
||||
await marketClose(
|
||||
this.exchange,
|
||||
this.config.symbol,
|
||||
this.openOrders,
|
||||
this.locks,
|
||||
this.timers,
|
||||
this.pendings,
|
||||
side,
|
||||
qty,
|
||||
this.log,
|
||||
{
|
||||
await marketClose(this.orderContext, {
|
||||
openOrders: this.openOrders,
|
||||
side: side,
|
||||
quantity: qty,
|
||||
guard: {
|
||||
markPrice: mark,
|
||||
expectedPrice: Number.isFinite(closeSidePrice) ? closeSidePrice : null,
|
||||
maxPct: limitPct > 0 ? limitPct : undefined,
|
||||
},
|
||||
{ qtyStep: this.config.qtyStep }
|
||||
);
|
||||
qtyStep: this.config.qtyStep
|
||||
});
|
||||
this.log("close", `市价平仓 ${side} ${qty} (${reason})`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
@@ -971,21 +941,16 @@ export class GridEngine {
|
||||
|
||||
const lastPrice = Number(this.tickerSnapshot?.lastPrice);
|
||||
try {
|
||||
const placed = await placeStopLossOrder(
|
||||
this.exchange,
|
||||
this.config.symbol,
|
||||
this.openOrders,
|
||||
this.locks,
|
||||
this.timers,
|
||||
this.pendings,
|
||||
desired.side,
|
||||
desired.stopPrice,
|
||||
Math.abs(this.position.positionAmt),
|
||||
Number.isFinite(lastPrice) ? lastPrice : price,
|
||||
this.log,
|
||||
undefined,
|
||||
{ priceTick: this.config.priceTick, qtyStep: this.config.qtyStep }
|
||||
);
|
||||
const placed = await placeStopLossOrder(this.orderContext, {
|
||||
openOrders: this.openOrders,
|
||||
side: desired.side,
|
||||
stopPrice: desired.stopPrice,
|
||||
quantity: Math.abs(this.position.positionAmt),
|
||||
lastPrice: Number.isFinite(lastPrice) ? lastPrice : price,
|
||||
guard: undefined,
|
||||
priceTick: this.config.priceTick,
|
||||
qtyStep: this.config.qtyStep
|
||||
});
|
||||
if (placed?.orderId != null) {
|
||||
state.exchangeStop = {
|
||||
orderId: String(placed.orderId),
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
placeTrailingStopOrder,
|
||||
unlockOperating,
|
||||
} from "../core/order-coordinator";
|
||||
import type { OrderLockMap, OrderPendingMap, OrderTimerMap } from "../core/order-coordinator";
|
||||
import type { OrderContext, OrderLockMap, OrderPendingMap, OrderTimerMap } from "../core/order-coordinator";
|
||||
import { createTradeLog, type TradeLogEntry } from "../logging/trade-log";
|
||||
import { extractMessage, isUnknownOrderError } from "../utils/errors";
|
||||
import { formatPriceToString } from "../utils/math";
|
||||
@@ -76,6 +76,19 @@ export class GuardianEngine {
|
||||
this.bootstrap();
|
||||
}
|
||||
|
||||
/** Bundles the fixed order-routing state; rebuilt lazily on first use. */
|
||||
private get orderContext(): OrderContext {
|
||||
return (this.orderContextCache ??= {
|
||||
adapter: this.exchange,
|
||||
symbol: this.config.symbol,
|
||||
locks: this.locks,
|
||||
timers: this.timers,
|
||||
pendings: this.pending,
|
||||
log: (type, detail) => this.tradeLog.push(type, detail),
|
||||
});
|
||||
}
|
||||
private orderContextCache: OrderContext | null = null;
|
||||
|
||||
start(): void {
|
||||
if (this.timer) return;
|
||||
this.timer = setInterval(() => {
|
||||
@@ -390,24 +403,19 @@ export class GuardianEngine {
|
||||
if (quantity <= minQty) {
|
||||
return;
|
||||
}
|
||||
await placeStopLossOrder(
|
||||
this.exchange,
|
||||
this.config.symbol,
|
||||
this.openOrders,
|
||||
this.locks,
|
||||
this.timers,
|
||||
this.pending,
|
||||
side,
|
||||
stopPrice,
|
||||
quantity,
|
||||
lastPrice,
|
||||
(type, detail) => this.tradeLog.push(type, detail),
|
||||
{
|
||||
await placeStopLossOrder(this.orderContext, {
|
||||
openOrders: this.openOrders,
|
||||
side: side,
|
||||
stopPrice: stopPrice,
|
||||
quantity: quantity,
|
||||
lastPrice: lastPrice,
|
||||
guard: {
|
||||
markPrice: position.markPrice,
|
||||
maxPct: this.config.maxCloseSlippagePct,
|
||||
},
|
||||
{ priceTick: this.config.priceTick, qtyStep: this.config.qtyStep }
|
||||
);
|
||||
priceTick: this.config.priceTick,
|
||||
qtyStep: this.config.qtyStep
|
||||
});
|
||||
this.lastStopAttempt.side = side;
|
||||
this.lastStopAttempt.price = stopPrice;
|
||||
this.lastStopAttempt.at = now;
|
||||
@@ -449,24 +457,19 @@ export class GuardianEngine {
|
||||
if (quantity <= minQty) {
|
||||
return;
|
||||
}
|
||||
const order = await placeStopLossOrder(
|
||||
this.exchange,
|
||||
this.config.symbol,
|
||||
this.openOrders,
|
||||
this.locks,
|
||||
this.timers,
|
||||
this.pending,
|
||||
side,
|
||||
nextStopPrice,
|
||||
quantity,
|
||||
lastPrice,
|
||||
(type, detail) => this.tradeLog.push(type, detail),
|
||||
{
|
||||
const order = await placeStopLossOrder(this.orderContext, {
|
||||
openOrders: this.openOrders,
|
||||
side: side,
|
||||
stopPrice: nextStopPrice,
|
||||
quantity: quantity,
|
||||
lastPrice: lastPrice,
|
||||
guard: {
|
||||
markPrice: position.markPrice,
|
||||
maxPct: this.config.maxCloseSlippagePct,
|
||||
},
|
||||
{ priceTick: this.config.priceTick, qtyStep: this.config.qtyStep }
|
||||
);
|
||||
priceTick: this.config.priceTick,
|
||||
qtyStep: this.config.qtyStep
|
||||
});
|
||||
if (order) {
|
||||
this.tradeLog.push(
|
||||
"stop",
|
||||
@@ -484,24 +487,19 @@ export class GuardianEngine {
|
||||
if (quantity <= minQty) {
|
||||
return;
|
||||
}
|
||||
const restored = await placeStopLossOrder(
|
||||
this.exchange,
|
||||
this.config.symbol,
|
||||
this.openOrders,
|
||||
this.locks,
|
||||
this.timers,
|
||||
this.pending,
|
||||
side,
|
||||
Number.isFinite(existingStopPrice) ? existingStopPrice : nextStopPrice,
|
||||
quantity,
|
||||
lastPrice,
|
||||
(type, detail) => this.tradeLog.push(type, detail),
|
||||
{
|
||||
const restored = await placeStopLossOrder(this.orderContext, {
|
||||
openOrders: this.openOrders,
|
||||
side: side,
|
||||
stopPrice: Number.isFinite(existingStopPrice) ? existingStopPrice : nextStopPrice,
|
||||
quantity: quantity,
|
||||
lastPrice: lastPrice,
|
||||
guard: {
|
||||
markPrice: position.markPrice,
|
||||
maxPct: this.config.maxCloseSlippagePct,
|
||||
},
|
||||
{ priceTick: this.config.priceTick, qtyStep: this.config.qtyStep }
|
||||
);
|
||||
priceTick: this.config.priceTick,
|
||||
qtyStep: this.config.qtyStep
|
||||
});
|
||||
if (restored && Number.isFinite(existingStopPrice)) {
|
||||
this.tradeLog.push(
|
||||
"order",
|
||||
@@ -525,24 +523,19 @@ export class GuardianEngine {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await placeTrailingStopOrder(
|
||||
this.exchange,
|
||||
this.config.symbol,
|
||||
this.openOrders,
|
||||
this.locks,
|
||||
this.timers,
|
||||
this.pending,
|
||||
side,
|
||||
activationPrice,
|
||||
quantity,
|
||||
this.config.trailingCallbackRate,
|
||||
(type, detail) => this.tradeLog.push(type, detail),
|
||||
{
|
||||
await placeTrailingStopOrder(this.orderContext, {
|
||||
openOrders: this.openOrders,
|
||||
side: side,
|
||||
activationPrice: activationPrice,
|
||||
quantity: quantity,
|
||||
callbackRate: this.config.trailingCallbackRate,
|
||||
guard: {
|
||||
markPrice: getPosition(this.accountSnapshot, this.config.symbol).markPrice,
|
||||
maxPct: this.config.maxCloseSlippagePct,
|
||||
},
|
||||
{ priceTick: this.config.priceTick, qtyStep: this.config.qtyStep }
|
||||
);
|
||||
priceTick: this.config.priceTick,
|
||||
qtyStep: this.config.qtyStep
|
||||
});
|
||||
} catch (err) {
|
||||
this.tradeLog.push("error", t("log.guardian.trailingFail", { error: String(err) }));
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
placeOrder,
|
||||
unlockOperating,
|
||||
} from "../core/order-coordinator";
|
||||
import type { OrderLockMap, OrderPendingMap, OrderTimerMap } from "../core/order-coordinator";
|
||||
import type { OrderContext, OrderLockMap, OrderPendingMap, OrderTimerMap } from "../core/order-coordinator";
|
||||
import type { MakerEngineSnapshot } from "./maker-engine";
|
||||
import { makeOrderPlan } from "../core/lib/order-plan";
|
||||
import { safeCancelOrder } from "../core/lib/orders";
|
||||
@@ -151,6 +151,19 @@ export class LiquidityMakerEngine {
|
||||
this.bootstrap();
|
||||
}
|
||||
|
||||
/** Bundles the fixed order-routing state; rebuilt lazily on first use. */
|
||||
private get orderContext(): OrderContext {
|
||||
return (this.orderContextCache ??= {
|
||||
adapter: this.exchange,
|
||||
symbol: this.config.symbol,
|
||||
locks: this.locks,
|
||||
timers: this.timers,
|
||||
pendings: this.pending,
|
||||
log: (type, detail) => this.tradeLog.push(type, detail),
|
||||
});
|
||||
}
|
||||
private orderContextCache: OrderContext | null = null;
|
||||
|
||||
start(): void {
|
||||
if (this.timer) return;
|
||||
this.timer = setInterval(() => {
|
||||
@@ -698,17 +711,11 @@ export class LiquidityMakerEngine {
|
||||
const closeBidPrice = topBid != null ? formatPriceToString(topBid, priceDecimals) : null;
|
||||
const closeAskPrice = topAsk != null ? formatPriceToString(topAsk, priceDecimals) : null;
|
||||
try {
|
||||
await marketClose(
|
||||
this.exchange,
|
||||
this.config.symbol,
|
||||
this.openOrders,
|
||||
this.locks,
|
||||
this.timers,
|
||||
this.pending,
|
||||
side,
|
||||
absPosition,
|
||||
(type, detail) => this.tradeLog.push(type, detail),
|
||||
{
|
||||
await marketClose(this.orderContext, {
|
||||
openOrders: this.openOrders,
|
||||
side: side,
|
||||
quantity: absPosition,
|
||||
guard: {
|
||||
markPrice: position.markPrice,
|
||||
expectedPrice:
|
||||
side === "SELL"
|
||||
@@ -716,8 +723,8 @@ export class LiquidityMakerEngine {
|
||||
: (closeBidPrice != null ? Number(closeBidPrice) : null),
|
||||
maxPct: this.config.maxCloseSlippagePct,
|
||||
},
|
||||
{ qtyStep: this.precision.qtyStep }
|
||||
);
|
||||
qtyStep: this.precision.qtyStep
|
||||
});
|
||||
} catch (error) {
|
||||
if (isUnknownOrderError(error)) {
|
||||
this.tradeLog.push("order", "限频强制平仓时订单已不存在");
|
||||
@@ -886,27 +893,18 @@ export class LiquidityMakerEngine {
|
||||
}
|
||||
try {
|
||||
const reduceOnlyFlag = this.marketType === "spot" ? false : target.reduceOnly;
|
||||
await placeOrder(
|
||||
this.exchange,
|
||||
this.config.symbol,
|
||||
this.openOrders,
|
||||
this.locks,
|
||||
this.timers,
|
||||
this.pending,
|
||||
target.side,
|
||||
target.price, // 已经是字符串价格
|
||||
target.amount,
|
||||
(type, detail) => this.tradeLog.push(type, detail),
|
||||
reduceOnlyFlag,
|
||||
{
|
||||
await placeOrder(this.orderContext, {
|
||||
openOrders: this.openOrders,
|
||||
side: target.side,
|
||||
price: target.price,
|
||||
amount: target.amount,
|
||||
reduceOnly: reduceOnlyFlag,
|
||||
guard: {
|
||||
markPrice: this.getPositionSnapshot().markPrice,
|
||||
maxPct: this.config.maxCloseSlippagePct,
|
||||
},
|
||||
{
|
||||
priceTick: this.precision.priceTick,
|
||||
qtyStep: this.precision.qtyStep,
|
||||
}
|
||||
);
|
||||
qtyStep: this.precision.qtyStep
|
||||
});
|
||||
// Record last placed entry order timing and price
|
||||
if (!target.reduceOnly) {
|
||||
this.lastEntryOrderBySide[target.side] = { price: target.price, ts: Date.now() };
|
||||
@@ -955,23 +953,17 @@ export class LiquidityMakerEngine {
|
||||
// 尽力撤销所有未完成挂单,避免锁定基础资产导致余额不足
|
||||
await this.exchange.cancelAllOrders({ symbol: this.config.symbol }).catch(() => {});
|
||||
await this.flushOrders();
|
||||
await marketClose(
|
||||
this.exchange,
|
||||
this.config.symbol,
|
||||
this.openOrders,
|
||||
this.locks,
|
||||
this.timers,
|
||||
this.pending,
|
||||
"SELL",
|
||||
absPosition,
|
||||
(type, detail) => this.tradeLog.push(type, detail),
|
||||
{
|
||||
await marketClose(this.orderContext, {
|
||||
openOrders: this.openOrders,
|
||||
side: "SELL",
|
||||
quantity: absPosition,
|
||||
guard: {
|
||||
markPrice: position.markPrice,
|
||||
expectedPrice: bidPrice || null,
|
||||
maxPct: this.config.maxCloseSlippagePct,
|
||||
},
|
||||
{ qtyStep: this.precision.qtyStep }
|
||||
);
|
||||
qtyStep: this.precision.qtyStep
|
||||
});
|
||||
} catch (error) {
|
||||
if (isRateLimitError(error)) throw error;
|
||||
if (isUnknownOrderError(error)) {
|
||||
@@ -1005,23 +997,17 @@ export class LiquidityMakerEngine {
|
||||
);
|
||||
try {
|
||||
await this.flushOrders();
|
||||
await marketClose(
|
||||
this.exchange,
|
||||
this.config.symbol,
|
||||
this.openOrders,
|
||||
this.locks,
|
||||
this.timers,
|
||||
this.pending,
|
||||
position.positionAmt > 0 ? "SELL" : "BUY",
|
||||
absPosition,
|
||||
(type, detail) => this.tradeLog.push(type, detail),
|
||||
{
|
||||
await marketClose(this.orderContext, {
|
||||
openOrders: this.openOrders,
|
||||
side: position.positionAmt > 0 ? "SELL" : "BUY",
|
||||
quantity: absPosition,
|
||||
guard: {
|
||||
markPrice: position.markPrice,
|
||||
expectedPrice: Number(position.positionAmt > 0 ? bidPrice : askPrice) || null,
|
||||
maxPct: this.config.maxCloseSlippagePct,
|
||||
},
|
||||
{ qtyStep: this.precision.qtyStep }
|
||||
);
|
||||
qtyStep: this.precision.qtyStep
|
||||
});
|
||||
} catch (error) {
|
||||
if (isUnknownOrderError(error)) {
|
||||
this.tradeLog.push("order", "止损平仓时订单已不存在");
|
||||
@@ -1253,17 +1239,11 @@ export class LiquidityMakerEngine {
|
||||
if (absQty < EPS) return false;
|
||||
const { topBid, topAsk } = getTopPrices(this.depthSnapshot);
|
||||
try {
|
||||
await marketClose(
|
||||
this.exchange,
|
||||
this.config.symbol,
|
||||
this.openOrders,
|
||||
this.locks,
|
||||
this.timers,
|
||||
this.pending,
|
||||
target.side,
|
||||
absQty,
|
||||
(type, detail) => this.tradeLog.push(type, detail),
|
||||
{
|
||||
await marketClose(this.orderContext, {
|
||||
openOrders: this.openOrders,
|
||||
side: target.side,
|
||||
quantity: absQty,
|
||||
guard: {
|
||||
markPrice: position.markPrice,
|
||||
expectedPrice:
|
||||
target.side === "SELL"
|
||||
@@ -1271,8 +1251,8 @@ export class LiquidityMakerEngine {
|
||||
: (topAsk != null ? Number(topAsk) : null),
|
||||
maxPct: this.config.maxCloseSlippagePct,
|
||||
},
|
||||
{ qtyStep: this.precision.qtyStep }
|
||||
);
|
||||
qtyStep: this.precision.qtyStep
|
||||
});
|
||||
this.tradeLog.push("order", `小额仓位使用市价平仓 ${target.side} 数量 ${absQty.toFixed(6)}`);
|
||||
return true;
|
||||
} catch (closeError) {
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
placeOrder,
|
||||
unlockOperating,
|
||||
} from "../core/order-coordinator";
|
||||
import type { OrderLockMap, OrderPendingMap, OrderTimerMap } from "../core/order-coordinator";
|
||||
import type { OrderContext, OrderLockMap, OrderPendingMap, OrderTimerMap } from "../core/order-coordinator";
|
||||
import { makeOrderPlan } from "../core/lib/order-plan";
|
||||
import { safeCancelOrder } from "../core/lib/orders";
|
||||
import { RateLimitController } from "../core/lib/rate-limit";
|
||||
@@ -127,6 +127,19 @@ export class MakerEngine {
|
||||
this.bootstrap();
|
||||
}
|
||||
|
||||
/** Bundles the fixed order-routing state; rebuilt lazily on first use. */
|
||||
private get orderContext(): OrderContext {
|
||||
return (this.orderContextCache ??= {
|
||||
adapter: this.exchange,
|
||||
symbol: this.config.symbol,
|
||||
locks: this.locks,
|
||||
timers: this.timers,
|
||||
pendings: this.pending,
|
||||
log: (type, detail) => this.tradeLog.push(type, detail),
|
||||
});
|
||||
}
|
||||
private orderContextCache: OrderContext | null = null;
|
||||
|
||||
start(): void {
|
||||
if (this.timer) return;
|
||||
this.timer = setInterval(() => {
|
||||
@@ -439,27 +452,18 @@ export class MakerEngine {
|
||||
if (!target) continue;
|
||||
if (target.amount < EPS) continue;
|
||||
try {
|
||||
await placeOrder(
|
||||
this.exchange,
|
||||
this.config.symbol,
|
||||
this.openOrders,
|
||||
this.locks,
|
||||
this.timers,
|
||||
this.pending,
|
||||
target.side,
|
||||
target.price, // 已经是字符串价格
|
||||
target.amount,
|
||||
(type, detail) => this.tradeLog.push(type, detail),
|
||||
target.reduceOnly,
|
||||
{
|
||||
await placeOrder(this.orderContext, {
|
||||
openOrders: this.openOrders,
|
||||
side: target.side,
|
||||
price: target.price,
|
||||
amount: target.amount,
|
||||
reduceOnly: target.reduceOnly,
|
||||
guard: {
|
||||
markPrice: getPosition(this.accountSnapshot, this.config.symbol).markPrice,
|
||||
maxPct: this.config.maxCloseSlippagePct,
|
||||
},
|
||||
{
|
||||
priceTick: this.precision.priceTick,
|
||||
qtyStep: this.precision.qtyStep,
|
||||
}
|
||||
);
|
||||
qtyStep: this.precision.qtyStep
|
||||
});
|
||||
} catch (error) {
|
||||
if (isInsufficientBalanceError(error)) {
|
||||
this.registerInsufficientBalance(error);
|
||||
@@ -507,23 +511,17 @@ export class MakerEngine {
|
||||
);
|
||||
try {
|
||||
await this.flushOrders();
|
||||
await marketClose(
|
||||
this.exchange,
|
||||
this.config.symbol,
|
||||
this.openOrders,
|
||||
this.locks,
|
||||
this.timers,
|
||||
this.pending,
|
||||
position.positionAmt > 0 ? "SELL" : "BUY",
|
||||
absPosition,
|
||||
(type, detail) => this.tradeLog.push(type, detail),
|
||||
{
|
||||
await marketClose(this.orderContext, {
|
||||
openOrders: this.openOrders,
|
||||
side: position.positionAmt > 0 ? "SELL" : "BUY",
|
||||
quantity: absPosition,
|
||||
guard: {
|
||||
markPrice: position.markPrice,
|
||||
expectedPrice: Number(closeSidePrice) || null,
|
||||
maxPct: this.config.maxCloseSlippagePct,
|
||||
},
|
||||
{ qtyStep: this.precision.qtyStep }
|
||||
);
|
||||
qtyStep: this.precision.qtyStep
|
||||
});
|
||||
} catch (error) {
|
||||
if (isUnknownOrderError(error)) {
|
||||
this.tradeLog.push("order", t("log.maker.stopOrderMissing"));
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
placeOrder,
|
||||
unlockOperating,
|
||||
} from "../core/order-coordinator";
|
||||
import type { OrderLockMap, OrderPendingMap, OrderTimerMap } from "../core/order-coordinator";
|
||||
import type { OrderContext, OrderLockMap, OrderPendingMap, OrderTimerMap } from "../core/order-coordinator";
|
||||
import { makeOrderPlan } from "../core/lib/order-plan";
|
||||
import { safeCancelOrder } from "../core/lib/orders";
|
||||
import { RateLimitController } from "../core/lib/rate-limit";
|
||||
@@ -244,6 +244,19 @@ export class MakerPointsEngine {
|
||||
this.bootstrap();
|
||||
}
|
||||
|
||||
/** Bundles the fixed order-routing state; rebuilt lazily on first use. */
|
||||
private get orderContext(): OrderContext {
|
||||
return (this.orderContextCache ??= {
|
||||
adapter: this.exchange,
|
||||
symbol: this.config.symbol,
|
||||
locks: this.locks,
|
||||
timers: this.timers,
|
||||
pendings: this.pending,
|
||||
log: (type, detail) => this.tradeLog.push(type, detail),
|
||||
});
|
||||
}
|
||||
private orderContextCache: OrderContext | null = null;
|
||||
|
||||
start(): void {
|
||||
if (this.timer) return;
|
||||
// 初始化数据时间戳
|
||||
@@ -1046,26 +1059,17 @@ export class MakerPointsEngine {
|
||||
: target.side === "BUY"
|
||||
? priceNum - 1
|
||||
: priceNum + 1;
|
||||
await placeOrder(
|
||||
this.exchange,
|
||||
this.config.symbol,
|
||||
this.openOrders,
|
||||
this.locks,
|
||||
this.timers,
|
||||
this.pending,
|
||||
target.side,
|
||||
target.price,
|
||||
target.amount,
|
||||
(type, detail) => this.tradeLog.push(type, detail),
|
||||
target.reduceOnly,
|
||||
undefined,
|
||||
{
|
||||
priceTick: this.precision.priceTick,
|
||||
qtyStep: this.precision.qtyStep,
|
||||
skipDedupe: true,
|
||||
slPrice,
|
||||
}
|
||||
);
|
||||
await placeOrder(this.orderContext, {
|
||||
openOrders: this.openOrders,
|
||||
side: target.side,
|
||||
price: target.price,
|
||||
amount: target.amount,
|
||||
reduceOnly: target.reduceOnly,
|
||||
guard: undefined,
|
||||
qtyStep: this.precision.qtyStep,
|
||||
skipDedupe: true,
|
||||
slPrice
|
||||
});
|
||||
} catch (error) {
|
||||
if (isInsufficientBalanceError(error)) {
|
||||
this.registerInsufficientBalance(error);
|
||||
@@ -1231,19 +1235,13 @@ export class MakerPointsEngine {
|
||||
await this.flushOrders();
|
||||
|
||||
// 执行市价平仓
|
||||
await marketClose(
|
||||
this.exchange,
|
||||
this.config.symbol,
|
||||
this.openOrders,
|
||||
this.locks,
|
||||
this.timers,
|
||||
this.pending,
|
||||
side,
|
||||
currentAbsPosition,
|
||||
(type, detail) => this.tradeLog.push(type, detail),
|
||||
undefined,
|
||||
{ qtyStep: this.precision.qtyStep }
|
||||
);
|
||||
await marketClose(this.orderContext, {
|
||||
openOrders: this.openOrders,
|
||||
side: side,
|
||||
quantity: currentAbsPosition,
|
||||
guard: undefined,
|
||||
qtyStep: this.precision.qtyStep
|
||||
});
|
||||
|
||||
// 等待一小段时间让账户数据更新
|
||||
await this.sleep(STOP_LOSS_RETRY_INTERVAL_MS);
|
||||
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
placeOrder,
|
||||
unlockOperating,
|
||||
} from "../core/order-coordinator";
|
||||
import type { OrderLockMap, OrderPendingMap, OrderTimerMap } from "../core/order-coordinator";
|
||||
import type { OrderContext, OrderLockMap, OrderPendingMap, OrderTimerMap } from "../core/order-coordinator";
|
||||
import type { MakerEngineSnapshot } from "./maker-engine";
|
||||
import { makeOrderPlan } from "../core/lib/order-plan";
|
||||
import { safeCancelOrder } from "../core/lib/orders";
|
||||
@@ -141,6 +141,19 @@ export class OffsetMakerEngine {
|
||||
this.bootstrap();
|
||||
}
|
||||
|
||||
/** Bundles the fixed order-routing state; rebuilt lazily on first use. */
|
||||
private get orderContext(): OrderContext {
|
||||
return (this.orderContextCache ??= {
|
||||
adapter: this.exchange,
|
||||
symbol: this.config.symbol,
|
||||
locks: this.locks,
|
||||
timers: this.timers,
|
||||
pendings: this.pending,
|
||||
log: (type, detail) => this.tradeLog.push(type, detail),
|
||||
});
|
||||
}
|
||||
private orderContextCache: OrderContext | null = null;
|
||||
|
||||
start(): void {
|
||||
if (this.timer) return;
|
||||
this.timer = setInterval(() => {
|
||||
@@ -551,17 +564,11 @@ export class OffsetMakerEngine {
|
||||
const closeBidPrice = topBid != null ? formatPriceToString(topBid, priceDecimals) : null;
|
||||
const closeAskPrice = topAsk != null ? formatPriceToString(topAsk, priceDecimals) : null;
|
||||
try {
|
||||
await marketClose(
|
||||
this.exchange,
|
||||
this.config.symbol,
|
||||
this.openOrders,
|
||||
this.locks,
|
||||
this.timers,
|
||||
this.pending,
|
||||
side,
|
||||
absPosition,
|
||||
(type, detail) => this.tradeLog.push(type, detail),
|
||||
{
|
||||
await marketClose(this.orderContext, {
|
||||
openOrders: this.openOrders,
|
||||
side: side,
|
||||
quantity: absPosition,
|
||||
guard: {
|
||||
markPrice: position.markPrice,
|
||||
expectedPrice:
|
||||
side === "SELL"
|
||||
@@ -569,8 +576,8 @@ export class OffsetMakerEngine {
|
||||
: (closeBidPrice != null ? Number(closeBidPrice) : null),
|
||||
maxPct: this.config.maxCloseSlippagePct,
|
||||
},
|
||||
{ qtyStep: this.precision.qtyStep }
|
||||
);
|
||||
qtyStep: this.precision.qtyStep
|
||||
});
|
||||
} catch (error) {
|
||||
if (isUnknownOrderError(error)) {
|
||||
this.tradeLog.push("order", "限频强制平仓时订单已不存在");
|
||||
@@ -644,23 +651,17 @@ export class OffsetMakerEngine {
|
||||
);
|
||||
try {
|
||||
await this.flushOrders();
|
||||
await marketClose(
|
||||
this.exchange,
|
||||
this.config.symbol,
|
||||
this.openOrders,
|
||||
this.locks,
|
||||
this.timers,
|
||||
this.pending,
|
||||
side,
|
||||
absPosition,
|
||||
(type, detail) => this.tradeLog.push(type, detail),
|
||||
{
|
||||
await marketClose(this.orderContext, {
|
||||
openOrders: this.openOrders,
|
||||
side: side,
|
||||
quantity: absPosition,
|
||||
guard: {
|
||||
markPrice: position.markPrice,
|
||||
expectedPrice: Number(closeSidePrice) || null,
|
||||
maxPct: this.config.maxCloseSlippagePct,
|
||||
},
|
||||
{ qtyStep: this.precision.qtyStep }
|
||||
);
|
||||
qtyStep: this.precision.qtyStep
|
||||
});
|
||||
} catch (error) {
|
||||
if (isUnknownOrderError(error)) {
|
||||
this.tradeLog.push("order", "深度不平衡平仓时订单已不存在");
|
||||
@@ -747,27 +748,18 @@ export class OffsetMakerEngine {
|
||||
}
|
||||
try {
|
||||
const reduceOnlyFlag = this.marketType === "spot" ? false : target.reduceOnly;
|
||||
await placeOrder(
|
||||
this.exchange,
|
||||
this.config.symbol,
|
||||
this.openOrders,
|
||||
this.locks,
|
||||
this.timers,
|
||||
this.pending,
|
||||
target.side,
|
||||
target.price, // 已经是字符串价格
|
||||
target.amount,
|
||||
(type, detail) => this.tradeLog.push(type, detail),
|
||||
reduceOnlyFlag,
|
||||
{
|
||||
await placeOrder(this.orderContext, {
|
||||
openOrders: this.openOrders,
|
||||
side: target.side,
|
||||
price: target.price,
|
||||
amount: target.amount,
|
||||
reduceOnly: reduceOnlyFlag,
|
||||
guard: {
|
||||
markPrice: this.getPositionSnapshot().markPrice,
|
||||
maxPct: this.config.maxCloseSlippagePct,
|
||||
},
|
||||
{
|
||||
priceTick: this.precision.priceTick,
|
||||
qtyStep: this.precision.qtyStep,
|
||||
}
|
||||
);
|
||||
qtyStep: this.precision.qtyStep
|
||||
});
|
||||
// Record last placed entry order timing and price
|
||||
if (!target.reduceOnly) {
|
||||
this.lastEntryOrderBySide[target.side] = { price: target.price, ts: Date.now() };
|
||||
@@ -816,23 +808,17 @@ export class OffsetMakerEngine {
|
||||
// 尽力撤销所有未完成挂单,避免锁定基础资产导致余额不足
|
||||
await this.exchange.cancelAllOrders({ symbol: this.config.symbol }).catch(() => {});
|
||||
await this.flushOrders();
|
||||
await marketClose(
|
||||
this.exchange,
|
||||
this.config.symbol,
|
||||
this.openOrders,
|
||||
this.locks,
|
||||
this.timers,
|
||||
this.pending,
|
||||
"SELL",
|
||||
absPosition,
|
||||
(type, detail) => this.tradeLog.push(type, detail),
|
||||
{
|
||||
await marketClose(this.orderContext, {
|
||||
openOrders: this.openOrders,
|
||||
side: "SELL",
|
||||
quantity: absPosition,
|
||||
guard: {
|
||||
markPrice: position.markPrice,
|
||||
expectedPrice: bidPrice || null,
|
||||
maxPct: this.config.maxCloseSlippagePct,
|
||||
},
|
||||
{ qtyStep: this.precision.qtyStep }
|
||||
);
|
||||
qtyStep: this.precision.qtyStep
|
||||
});
|
||||
} catch (error) {
|
||||
if (isRateLimitError(error)) throw error;
|
||||
if (isUnknownOrderError(error)) {
|
||||
@@ -866,23 +852,17 @@ export class OffsetMakerEngine {
|
||||
);
|
||||
try {
|
||||
await this.flushOrders();
|
||||
await marketClose(
|
||||
this.exchange,
|
||||
this.config.symbol,
|
||||
this.openOrders,
|
||||
this.locks,
|
||||
this.timers,
|
||||
this.pending,
|
||||
position.positionAmt > 0 ? "SELL" : "BUY",
|
||||
absPosition,
|
||||
(type, detail) => this.tradeLog.push(type, detail),
|
||||
{
|
||||
await marketClose(this.orderContext, {
|
||||
openOrders: this.openOrders,
|
||||
side: position.positionAmt > 0 ? "SELL" : "BUY",
|
||||
quantity: absPosition,
|
||||
guard: {
|
||||
markPrice: position.markPrice,
|
||||
expectedPrice: Number(position.positionAmt > 0 ? bidPrice : askPrice) || null,
|
||||
maxPct: this.config.maxCloseSlippagePct,
|
||||
},
|
||||
{ qtyStep: this.precision.qtyStep }
|
||||
);
|
||||
qtyStep: this.precision.qtyStep
|
||||
});
|
||||
} catch (error) {
|
||||
if (isUnknownOrderError(error)) {
|
||||
this.tradeLog.push("order", "止损平仓时订单已不存在");
|
||||
@@ -1123,17 +1103,11 @@ export class OffsetMakerEngine {
|
||||
if (absQty < EPS) return false;
|
||||
const { topBid, topAsk } = getTopPrices(this.depthSnapshot);
|
||||
try {
|
||||
await marketClose(
|
||||
this.exchange,
|
||||
this.config.symbol,
|
||||
this.openOrders,
|
||||
this.locks,
|
||||
this.timers,
|
||||
this.pending,
|
||||
target.side,
|
||||
absQty,
|
||||
(type, detail) => this.tradeLog.push(type, detail),
|
||||
{
|
||||
await marketClose(this.orderContext, {
|
||||
openOrders: this.openOrders,
|
||||
side: target.side,
|
||||
quantity: absQty,
|
||||
guard: {
|
||||
markPrice: position.markPrice,
|
||||
expectedPrice:
|
||||
target.side === "SELL"
|
||||
@@ -1141,8 +1115,8 @@ export class OffsetMakerEngine {
|
||||
: (topAsk != null ? Number(topAsk) : null),
|
||||
maxPct: this.config.maxCloseSlippagePct,
|
||||
},
|
||||
{ qtyStep: this.precision.qtyStep }
|
||||
);
|
||||
qtyStep: this.precision.qtyStep
|
||||
});
|
||||
this.tradeLog.push("order", `小额仓位使用市价平仓 ${target.side} 数量 ${absQty.toFixed(6)}`);
|
||||
return true;
|
||||
} catch (closeError) {
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { ExchangeAdapter } from "../exchanges/adapter";
|
||||
import type { AccountSnapshot, Depth, Order, Ticker } from "../exchanges/types";
|
||||
import { createTradeLog, type TradeLogEntry } from "../logging/trade-log";
|
||||
import { marketClose, placeMarketOrder, placeStopLossOrder, unlockOperating } from "../core/order-coordinator";
|
||||
import type { OrderLockMap, OrderPendingMap, OrderTimerMap } from "../core/order-coordinator";
|
||||
import type { OrderContext, OrderLockMap, OrderPendingMap, OrderTimerMap } from "../core/order-coordinator";
|
||||
import { extractMessage, isRateLimitError, isUnknownOrderError } from "../utils/errors";
|
||||
import { getPosition, type PositionSnapshot } from "../utils/strategy";
|
||||
import { computePositionPnl } from "../utils/pnl";
|
||||
@@ -136,6 +136,19 @@ export class SwingEngine {
|
||||
this.bootstrap();
|
||||
}
|
||||
|
||||
/** Bundles the fixed order-routing state; rebuilt lazily on first use. */
|
||||
private get orderContext(): OrderContext {
|
||||
return (this.orderContextCache ??= {
|
||||
adapter: this.exchange,
|
||||
symbol: this.config.symbol,
|
||||
locks: this.locks,
|
||||
timers: this.timers,
|
||||
pendings: this.pending,
|
||||
log: (type, detail) => this.tradeLog.push(type, detail),
|
||||
});
|
||||
}
|
||||
private orderContextCache: OrderContext | null = null;
|
||||
|
||||
start(): void {
|
||||
if (this.timer) return;
|
||||
this.timer = setInterval(() => {
|
||||
@@ -348,24 +361,18 @@ export class SwingEngine {
|
||||
if (Math.abs(position.positionAmt) > EPS) {
|
||||
return;
|
||||
}
|
||||
await placeMarketOrder(
|
||||
this.exchange,
|
||||
this.config.symbol,
|
||||
this.openOrders,
|
||||
this.locks,
|
||||
this.timers,
|
||||
this.pending,
|
||||
side,
|
||||
this.config.tradeAmount,
|
||||
(type, detail) => this.tradeLog.push(type, detail),
|
||||
false,
|
||||
{
|
||||
await placeMarketOrder(this.orderContext, {
|
||||
openOrders: this.openOrders,
|
||||
side: side,
|
||||
amount: this.config.tradeAmount,
|
||||
reduceOnly: false,
|
||||
guard: {
|
||||
markPrice: position.markPrice,
|
||||
expectedPrice: Number(this.tickerSnapshot?.lastPrice) || null,
|
||||
maxPct: this.config.maxCloseSlippagePct,
|
||||
},
|
||||
{ qtyStep: this.config.qtyStep }
|
||||
);
|
||||
qtyStep: this.config.qtyStep
|
||||
});
|
||||
this.tradeLog.push("open", `${reason}: ${side} (market)`);
|
||||
} catch (err) {
|
||||
this.tradeLog.push("error", `Open failed: ${extractMessage(err)}`);
|
||||
@@ -380,23 +387,17 @@ export class SwingEngine {
|
||||
side === "SELL"
|
||||
? Number(this.depthSnapshot?.bids?.[0]?.[0])
|
||||
: Number(this.depthSnapshot?.asks?.[0]?.[0]);
|
||||
await marketClose(
|
||||
this.exchange,
|
||||
this.config.symbol,
|
||||
this.openOrders,
|
||||
this.locks,
|
||||
this.timers,
|
||||
this.pending,
|
||||
side,
|
||||
Math.abs(position.positionAmt),
|
||||
(type, detail) => this.tradeLog.push(type, detail),
|
||||
{
|
||||
await marketClose(this.orderContext, {
|
||||
openOrders: this.openOrders,
|
||||
side: side,
|
||||
quantity: Math.abs(position.positionAmt),
|
||||
guard: {
|
||||
markPrice: position.markPrice,
|
||||
expectedPrice: Number.isFinite(expected) ? expected : Number(this.tickerSnapshot?.lastPrice) || null,
|
||||
maxPct: this.config.maxCloseSlippagePct,
|
||||
},
|
||||
{ qtyStep: this.config.qtyStep }
|
||||
);
|
||||
qtyStep: this.config.qtyStep
|
||||
});
|
||||
this.tradeLog.push("close", `${reason}: ${side} (market close)`);
|
||||
} catch (err) {
|
||||
if (isUnknownOrderError(err)) {
|
||||
@@ -459,24 +460,19 @@ export class SwingEngine {
|
||||
|
||||
try {
|
||||
const qty = Math.abs(position.positionAmt);
|
||||
await placeStopLossOrder(
|
||||
this.exchange,
|
||||
this.config.symbol,
|
||||
this.openOrders,
|
||||
this.locks,
|
||||
this.timers,
|
||||
this.pending,
|
||||
stopSide,
|
||||
stopPrice,
|
||||
qty,
|
||||
lastPrice,
|
||||
(type, detail) => this.tradeLog.push(type, detail),
|
||||
{
|
||||
await placeStopLossOrder(this.orderContext, {
|
||||
openOrders: this.openOrders,
|
||||
side: stopSide,
|
||||
stopPrice: stopPrice,
|
||||
quantity: qty,
|
||||
lastPrice: lastPrice,
|
||||
guard: {
|
||||
markPrice: position.markPrice,
|
||||
maxPct: this.config.maxCloseSlippagePct,
|
||||
},
|
||||
{ priceTick: this.config.priceTick, qtyStep: this.config.qtyStep }
|
||||
);
|
||||
priceTick: this.config.priceTick,
|
||||
qtyStep: this.config.qtyStep
|
||||
});
|
||||
this.lastStopAttempt = { side: stopSide, price: stopPrice, at: Date.now() };
|
||||
} catch (err) {
|
||||
this.lastStopAttempt = { side: stopSide, price: stopPrice, at: Date.now() };
|
||||
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
placeTrailingStopOrder,
|
||||
unlockOperating,
|
||||
} from "../core/order-coordinator";
|
||||
import type { OrderLockMap, OrderPendingMap, OrderTimerMap } from "../core/order-coordinator";
|
||||
import type { OrderContext, OrderLockMap, OrderPendingMap, OrderTimerMap } from "../core/order-coordinator";
|
||||
import { extractMessage, isUnknownOrderError } from "../utils/errors";
|
||||
import { formatPriceToString } from "../utils/math";
|
||||
import { createTradeLog, type TradeLogEntry } from "../logging/trade-log";
|
||||
@@ -139,6 +139,19 @@ export class TrendEngine {
|
||||
this.bootstrap();
|
||||
}
|
||||
|
||||
/** Bundles the fixed order-routing state; rebuilt lazily on first use. */
|
||||
private get orderContext(): OrderContext {
|
||||
return (this.orderContextCache ??= {
|
||||
adapter: this.exchange,
|
||||
symbol: this.config.symbol,
|
||||
locks: this.locks,
|
||||
timers: this.timers,
|
||||
pendings: this.pending,
|
||||
log: (type, detail) => this.tradeLog.push(type, detail),
|
||||
});
|
||||
}
|
||||
private orderContextCache: OrderContext | null = null;
|
||||
|
||||
start(): void {
|
||||
if (this.timer) return;
|
||||
this.timer = setInterval(() => {
|
||||
@@ -490,24 +503,18 @@ export class TrendEngine {
|
||||
|
||||
private async submitMarketOrder(side: "BUY" | "SELL", price: number, reason: string): Promise<void> {
|
||||
try {
|
||||
await placeMarketOrder(
|
||||
this.exchange,
|
||||
this.config.symbol,
|
||||
this.openOrders,
|
||||
this.locks,
|
||||
this.timers,
|
||||
this.pending,
|
||||
side,
|
||||
this.config.tradeAmount,
|
||||
(type, detail) => this.tradeLog.push(type, detail),
|
||||
false,
|
||||
{
|
||||
await placeMarketOrder(this.orderContext, {
|
||||
openOrders: this.openOrders,
|
||||
side: side,
|
||||
amount: this.config.tradeAmount,
|
||||
reduceOnly: false,
|
||||
guard: {
|
||||
markPrice: getPosition(this.accountSnapshot, this.config.symbol).markPrice,
|
||||
expectedPrice: Number(this.tickerSnapshot?.lastPrice) || null,
|
||||
maxPct: this.config.maxCloseSlippagePct,
|
||||
},
|
||||
{ qtyStep: this.config.qtyStep }
|
||||
);
|
||||
qtyStep: this.config.qtyStep
|
||||
});
|
||||
this.tradeLog.push("open", `${reason}: ${side} @ ${price}`);
|
||||
this.lastOpenPlan = { side, price };
|
||||
} catch (err) {
|
||||
@@ -748,17 +755,11 @@ export class TrendEngine {
|
||||
return { closed: false, pnl };
|
||||
}
|
||||
}
|
||||
await marketClose(
|
||||
this.exchange,
|
||||
this.config.symbol,
|
||||
this.openOrders,
|
||||
this.locks,
|
||||
this.timers,
|
||||
this.pending,
|
||||
direction === "long" ? "SELL" : "BUY",
|
||||
Math.abs(position.positionAmt),
|
||||
(type, detail) => this.tradeLog.push(type, detail),
|
||||
{
|
||||
await marketClose(this.orderContext, {
|
||||
openOrders: this.openOrders,
|
||||
side: direction === "long" ? "SELL" : "BUY",
|
||||
quantity: Math.abs(position.positionAmt),
|
||||
guard: {
|
||||
markPrice: getPosition(this.accountSnapshot, this.config.symbol).markPrice,
|
||||
expectedPrice: Number(
|
||||
direction === "long"
|
||||
@@ -767,8 +768,8 @@ export class TrendEngine {
|
||||
) || null,
|
||||
maxPct: this.config.maxCloseSlippagePct,
|
||||
},
|
||||
{ qtyStep: this.config.qtyStep }
|
||||
);
|
||||
qtyStep: this.config.qtyStep
|
||||
});
|
||||
result.closed = true;
|
||||
this.tradeLog.push("close", t("log.trend.stopClose", { side: direction === "long" ? "SELL" : "BUY" }));
|
||||
// 记录止损时间以便短期内抑制再次入场
|
||||
@@ -811,24 +812,19 @@ export class TrendEngine {
|
||||
if (quantity <= minQty) {
|
||||
return;
|
||||
}
|
||||
await placeStopLossOrder(
|
||||
this.exchange,
|
||||
this.config.symbol,
|
||||
this.openOrders,
|
||||
this.locks,
|
||||
this.timers,
|
||||
this.pending,
|
||||
side,
|
||||
stopPrice,
|
||||
quantity,
|
||||
lastPrice,
|
||||
(type, detail) => this.tradeLog.push(type, detail),
|
||||
{
|
||||
await placeStopLossOrder(this.orderContext, {
|
||||
openOrders: this.openOrders,
|
||||
side: side,
|
||||
stopPrice: stopPrice,
|
||||
quantity: quantity,
|
||||
lastPrice: lastPrice,
|
||||
guard: {
|
||||
markPrice: position.markPrice,
|
||||
maxPct: this.config.maxCloseSlippagePct,
|
||||
},
|
||||
{ priceTick: this.config.priceTick, qtyStep: this.config.qtyStep }
|
||||
);
|
||||
priceTick: this.config.priceTick,
|
||||
qtyStep: this.config.qtyStep
|
||||
});
|
||||
this.lastStopAttempt = { side, price: stopPrice, at: Date.now() };
|
||||
} catch (err) {
|
||||
this.tradeLog.push("error", t("log.trend.placeStopFail", { error: String(err) }));
|
||||
@@ -871,24 +867,19 @@ export class TrendEngine {
|
||||
if (quantity <= minQty) {
|
||||
return;
|
||||
}
|
||||
const order = await placeStopLossOrder(
|
||||
this.exchange,
|
||||
this.config.symbol,
|
||||
this.openOrders,
|
||||
this.locks,
|
||||
this.timers,
|
||||
this.pending,
|
||||
side,
|
||||
nextStopPrice,
|
||||
quantity,
|
||||
lastPrice,
|
||||
(type, detail) => this.tradeLog.push(type, detail),
|
||||
{
|
||||
const order = await placeStopLossOrder(this.orderContext, {
|
||||
openOrders: this.openOrders,
|
||||
side: side,
|
||||
stopPrice: nextStopPrice,
|
||||
quantity: quantity,
|
||||
lastPrice: lastPrice,
|
||||
guard: {
|
||||
markPrice: position.markPrice,
|
||||
maxPct: this.config.maxCloseSlippagePct,
|
||||
},
|
||||
{ priceTick: this.config.priceTick, qtyStep: this.config.qtyStep }
|
||||
);
|
||||
priceTick: this.config.priceTick,
|
||||
qtyStep: this.config.qtyStep
|
||||
});
|
||||
if (order) {
|
||||
this.tradeLog.push(
|
||||
"stop",
|
||||
@@ -914,24 +905,19 @@ export class TrendEngine {
|
||||
(side === "SELL" && existingStopPrice >= lastPrice) ||
|
||||
(side === "BUY" && existingStopPrice <= lastPrice);
|
||||
if (!restoreInvalid) {
|
||||
const restored = await placeStopLossOrder(
|
||||
this.exchange,
|
||||
this.config.symbol,
|
||||
this.openOrders,
|
||||
this.locks,
|
||||
this.timers,
|
||||
this.pending,
|
||||
side,
|
||||
existingStopPrice,
|
||||
quantity,
|
||||
lastPrice,
|
||||
(t, d) => this.tradeLog.push(t, d),
|
||||
{
|
||||
const restored = await placeStopLossOrder(this.orderContext, {
|
||||
openOrders: this.openOrders,
|
||||
side: side,
|
||||
stopPrice: existingStopPrice,
|
||||
quantity: quantity,
|
||||
lastPrice: lastPrice,
|
||||
guard: {
|
||||
markPrice: position.markPrice,
|
||||
maxPct: this.config.maxCloseSlippagePct,
|
||||
},
|
||||
{ priceTick: this.config.priceTick, qtyStep: this.config.qtyStep }
|
||||
);
|
||||
priceTick: this.config.priceTick,
|
||||
qtyStep: this.config.qtyStep
|
||||
});
|
||||
if (restored) {
|
||||
this.tradeLog.push(
|
||||
"order",
|
||||
@@ -959,24 +945,19 @@ export class TrendEngine {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await placeTrailingStopOrder(
|
||||
this.exchange,
|
||||
this.config.symbol,
|
||||
this.openOrders,
|
||||
this.locks,
|
||||
this.timers,
|
||||
this.pending,
|
||||
side,
|
||||
activationPrice,
|
||||
quantity,
|
||||
this.config.trailingCallbackRate,
|
||||
(type, detail) => this.tradeLog.push(type, detail),
|
||||
{
|
||||
await placeTrailingStopOrder(this.orderContext, {
|
||||
openOrders: this.openOrders,
|
||||
side: side,
|
||||
activationPrice: activationPrice,
|
||||
quantity: quantity,
|
||||
callbackRate: this.config.trailingCallbackRate,
|
||||
guard: {
|
||||
markPrice: getPosition(this.accountSnapshot, this.config.symbol).markPrice,
|
||||
maxPct: this.config.maxCloseSlippagePct,
|
||||
},
|
||||
{ priceTick: this.config.priceTick, qtyStep: this.config.qtyStep }
|
||||
);
|
||||
priceTick: this.config.priceTick,
|
||||
qtyStep: this.config.qtyStep
|
||||
});
|
||||
} catch (err) {
|
||||
this.tradeLog.push("error", t("log.trend.trailingFail", { error: String(err) }));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user