diff --git a/.env.example b/.env.example index 84d7b8c..ed94485 100644 --- a/.env.example +++ b/.env.example @@ -18,7 +18,7 @@ STANDX_SYMBOL=BTC-USD # STANDX_REQUEST_PRIVATE_KEY= # Token expiry configuration (recommended method: creation date + validity days) # Get these values when generating API token at https://standx.com/user/session -# STANDX_TOKEN_CREATE_DATE=2025-01-15 # Token creation date (YYYY-MM-DD format) +# STANDX_TOKEN_CREATE_DATE=2026-01-15 # Token creation date (YYYY-MM-DD format) # STANDX_TOKEN_VALIDITY_DAYS=30 # Token validity period in days # Legacy method: direct expiry timestamp (Unix seconds) # STANDX_TOKEN_EXPIRY=1737092800 diff --git a/docs/standx/maker-points-guide.md b/docs/standx/maker-points-guide.md index 427e4e1..2ad021b 100644 --- a/docs/standx/maker-points-guide.md +++ b/docs/standx/maker-points-guide.md @@ -72,7 +72,7 @@ https://standx.com/user/session 你会看到类似这样的信息: - **Token**(很长一串以 eyJ 开头的字符串) - **Ed25519 Private Key**(Base58 格式的私钥,类似 `HdsyJD7oWgT756124j3taSPGv...`) -- **创建日期**(例如:2025-01-15) +- **创建日期**(例如:2026-01-15) - **有效期天数**(例如:30 天) > 🔴 **请把这些值复制保存下来!** @@ -140,8 +140,8 @@ MAKER_POINTS_BAND_30_100=true # ===== Token 过期时间配置(推荐配置) ===== # 填写你创建 API Token 时显示的创建日期和有效期天数 -# 创建日期格式:YYYY-MM-DD(例如:2025-01-15) -STANDX_TOKEN_CREATE_DATE=2025-01-15 +# 创建日期格式:YYYY-MM-DD(例如:2026-01-15) +STANDX_TOKEN_CREATE_DATE=2026-01-15 # 有效期天数(例如:30) STANDX_TOKEN_VALIDITY_DAYS=30 @@ -159,7 +159,7 @@ STANDX_TOKEN_VALIDITY_DAYS=30 假设你生成的 API Token 信息是: - Token: `eyJhbGciOiJFUzI1NiIsImtpZCI6IlhnaEJQSVNuN0RQVHlMcWJtLUVHVkVhOU1lMFpwdU9iMk1Qc2gtbUFlencifQ...` - Ed25519 Private Key: `HdsyJD7oWgT756124j3taSPGv17vo5u7FafDq3vrun4f` -- 创建日期: `2025-01-15` +- 创建日期: `2026-01-15` - 有效期: `30` 天 那么你的 `.env` 应该这样写: @@ -176,7 +176,7 @@ MAKER_POINTS_MIN_REPRICE_BPS=3 MAKER_POINTS_BAND_0_10=true MAKER_POINTS_BAND_10_30=true MAKER_POINTS_BAND_30_100=true -STANDX_TOKEN_CREATE_DATE=2025-01-15 +STANDX_TOKEN_CREATE_DATE=2026-01-15 STANDX_TOKEN_VALIDITY_DAYS=30 # TELEGRAM_BOT_TOKEN=你的BotToken # TELEGRAM_CHAT_ID=你的ChatID @@ -237,13 +237,13 @@ bun run pm2:start:maker-points ```bash # 创建日期(格式:YYYY-MM-DD) -STANDX_TOKEN_CREATE_DATE=2025-01-15 +STANDX_TOKEN_CREATE_DATE=2026-01-15 # 有效期天数 STANDX_TOKEN_VALIDITY_DAYS=30 ``` **示例计算:** -- 创建日期:2025-01-15 +- 创建日期:2026-01-15 - 有效期:30 天 - 过期时间:2025-02-14 00:00:00 UTC diff --git a/src/core/order-coordinator.ts b/src/core/order-coordinator.ts index 16fa766..253bd21 100644 --- a/src/core/order-coordinator.ts +++ b/src/core/order-coordinator.ts @@ -132,6 +132,8 @@ type PlaceOrderOptions = { priceTick: number; qtyStep: number; skipDedupe?: boolean; + slPrice?: number; + tpPrice?: number; }; export async function placeOrder( @@ -176,9 +178,11 @@ export async function placeOrder( timeInForce: reduceOnly ? "GTC" : "GTX", reduceOnly: reduceOnly ? true : undefined, closePosition, + slPrice: opts?.slPrice, + tpPrice: opts?.tpPrice, }); pendings[type] = String(order.orderId); - log("order", `挂限价单: ${side} @ ${priceNum} 数量 ${quantity} reduceOnly=${reduceOnly}`); + log("order", `挂限价单: ${side} @ ${priceNum} 数量 ${quantity} reduceOnly=${reduceOnly}${opts?.slPrice ? ` sl=${opts.slPrice}` : ""}`); return order; } catch (err) { unlockOperating(locks, timers, pendings, type); diff --git a/src/exchanges/order-schema.ts b/src/exchanges/order-schema.ts index 5712af4..0c787ed 100644 --- a/src/exchanges/order-schema.ts +++ b/src/exchanges/order-schema.ts @@ -13,6 +13,9 @@ export interface BaseOrderIntent { export interface LimitOrderIntent extends BaseOrderIntent { price: number; + // StandX TPSL 参数 + slPrice?: number; // 止损价格 + tpPrice?: number; // 止盈价格 } export interface MarketOrderIntent extends BaseOrderIntent { diff --git a/src/exchanges/standx/gateway.ts b/src/exchanges/standx/gateway.ts index 23ca04f..977cd5d 100644 --- a/src/exchanges/standx/gateway.ts +++ b/src/exchanges/standx/gateway.ts @@ -799,6 +799,13 @@ export class StandxGateway { } payload.price = price; } + // StandX TPSL 参数 + if (params.slPrice != null && Number.isFinite(params.slPrice)) { + payload.sl_price = toDecimalString(params.slPrice); + } + if (params.tpPrice != null && Number.isFinite(params.tpPrice)) { + payload.tp_price = toDecimalString(params.tpPrice); + } const response = await this.requestJson<{ code?: number; message?: string; request_id?: string }>( "/api/new_order", { @@ -863,6 +870,12 @@ export class StandxGateway { }; const handleError = (error: unknown) => { this.logger("marketWs", error); + // 如果连接从未成功建立(握手失败),需要清理并重连 + // 因为某些 WebSocket 实现在握手失败时可能不触发 close 事件 + if (this.marketWs && !this.marketWsReady) { + this.marketWs = null; + this.scheduleReconnect(); + } }; if ("addEventListener" in this.marketWs && typeof this.marketWs.addEventListener === "function") { @@ -886,8 +899,10 @@ export class StandxGateway { private scheduleReconnect(): void { if (this.marketReconnectTimer) return; + this.logDebug("scheduling reconnect in 2s"); this.marketReconnectTimer = setTimeout(() => { this.marketReconnectTimer = null; + this.logDebug("attempting reconnect"); this.connectMarketWs(); }, WS_RECONNECT_DELAY); } @@ -1182,9 +1197,8 @@ export class StandxGateway { const order = this.mapOrder(raw); mergeOrderSnapshot(this.openOrders, order); } - if (orders.length) { - this.emitOrders(); - } + // 无论是否有挂单都触发推送,确保上层状态更新 + this.emitOrders(); } catch (error) { this.logger("openOrders", error); } @@ -1509,6 +1523,33 @@ export class StandxGateway { // 停止断连保护重试 this.stopDisconnectCancelRetry(); + // 清空本地挂单状态(重连后需要重新同步) + this.openOrders.clear(); + + // 主动触发一次数据推送,确保上层 feedStatus 能更新 + this.emitOrders(); + this.emitAccountSnapshot(); + + // 主动刷新账户和挂单数据 + void this.refreshAccountSnapshot(); + + // 获取当前订阅的 symbols 并刷新数据 + const subscribedSymbols = new Set(); + for (const key of this.subscriptions) { + const [, symbol] = key.split(":"); + if (symbol) subscribedSymbols.add(symbol); + } + if (this.disconnectedSymbol) { + subscribedSymbols.add(this.disconnectedSymbol); + } + + // 刷新每个 symbol 的数据 + for (const symbol of subscribedSymbols) { + void this.refreshOpenOrders(symbol); + void this.fetchDepthSnapshot(symbol).catch((e) => this.logger("depthSnapshot", e)); + void this.fetchTickerSnapshot(symbol).catch((e) => this.logger("tickerSnapshot", e)); + } + // 触发重连事件 for (const listener of this.connectionListeners) { try { diff --git a/src/exchanges/standx/order.ts b/src/exchanges/standx/order.ts index 3017d45..92cc14c 100644 --- a/src/exchanges/standx/order.ts +++ b/src/exchanges/standx/order.ts @@ -34,6 +34,8 @@ export async function createLimitOrder(intent: LimitOrderIntent): Promise