Skip to content

Commit 9692869

Browse files
committed
fix(trading): stop_orders/cancel_order 覆盖 OKX conditional 类条件单
OKX 实盘 e2e 测出: set_stop 在 OKX 挂的止盈止损是 ordType=conditional 类算法单,但 stop_orders 旧逻辑先查 trigger 类、'第一次返回空就 return',读不到 conditional → 误报'无条件单'(单子其实挂上了)。 cancel_order 的 OKX 全撤同样漏 conditional 类。 抽出 fetchConditionalOrders(ex,exchange,symbol): 合并查询各 ordType(OKX 额外查 conditional/oco/ trigger/move_order_stop)、去重、只在全部查询都报错时才抛 —— 不再首空即返。stop_orders 与 cancel_order(无 id 全撤的 OKX/HL 分支)都改用它。 真实 OKX round-trip 验证: 开多→set_stop(2 条 conditional)→ stop_orders 查到 2 条(修复前为 0)→ cancel_order 撤掉 2/2 → 平仓 → 持仓 0、条件单 0。
1 parent 71abee7 commit 9692869

2 files changed

Lines changed: 62 additions & 14 deletions

File tree

skills/aicoin-account/scripts/exchange.mjs

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,25 @@ function closeParamsFor(exchange, marketType, pos) {
250250
return out;
251251
}
252252

253+
// 查"条件/算法单"(止盈止损/触发/追踪),合并各 ordType 再去重 —— OKX 的止盈止损是 conditional 类,
254+
// 与 trigger 类**分开存**,必须各类都查再合并,不能"第一次返回空就 return"(否则 OKX 的 set_stop
255+
// 单会被漏掉:实测 stop_orders 旧逻辑先查 trigger 返回空就 return,读不到 conditional 类的 SL/TP)。
256+
async function fetchConditionalOrders(ex, exchange, symbol) {
257+
const variants = [{ trigger: true }, { stop: true }];
258+
if (exchange === 'okx') variants.push({ ordType: 'conditional' }, { ordType: 'oco' }, { ordType: 'trigger' }, { ordType: 'move_order_stop' });
259+
const seen = new Map();
260+
let any = false, lastErr = null;
261+
for (const extra of variants) {
262+
try {
263+
const os = await ex.fetchOpenOrders(symbol, undefined, undefined, extra);
264+
any = true;
265+
for (const o of (os || [])) if (o && o.id != null) seen.set(o.id, o);
266+
} catch (e) { lastErr = e; }
267+
}
268+
if (!any) throw lastErr || new Error('无法查询条件单');
269+
return [...seen.values()];
270+
}
271+
253272
cli({
254273
exchanges: async () => ({
255274
supported: SUPPORTED.map(id => {
@@ -778,12 +797,8 @@ cli({
778797
// 列出条件单/算法委托(止盈止损等)。OKX 等的算法单不在普通 open_orders 里,用这个查。
779798
stop_orders: async ({ exchange, symbol, market_type }) => {
780799
const ex = await getExchange(exchange, market_type || 'swap');
781-
let lastErr = null;
782-
for (const extra of [{ trigger: true }, { stop: true }]) {
783-
try { return await ex.fetchOpenOrders(symbol, undefined, undefined, extra); }
784-
catch (e) { lastErr = e; }
785-
}
786-
throw new Error(`查询条件单失败: ${lastErr?.message || lastErr}。部分交易所的算法/条件单需在交易所 APP 的「条件委托」栏查看。`);
800+
try { return await fetchConditionalOrders(ex, exchange, symbol); }
801+
catch (e) { throw new Error(`查询条件单失败: ${e?.message || e}。部分交易所的算法/条件单需在交易所 APP 的「条件委托」栏查看。`); }
787802
},
788803
funding_rate: async ({ exchange, symbol, market_type }) => {
789804
const ex = await getExchange(exchange, market_type || 'swap', true);
@@ -843,7 +858,16 @@ cli({
843858
try { out.conditional = await ex.cancelAllOrders(symbol, { stop: true }); } catch (e) { out.conditional = { skipped: String(e).slice(0, 120) }; }
844859
} else {
845860
try { out.regular = await cancelByFetch(); } catch (e) { out.regular = { error: String(e).slice(0, 160) }; }
846-
try { out.conditional = await cancelByFetch({ stop: true }); } catch (e) { out.conditional = { skipped: String(e).slice(0, 120) }; }
861+
// 条件单: 用合并查询(覆盖 OKX conditional 类,不漏)再逐个撤
862+
try {
863+
const conds = await fetchConditionalOrders(ex, exchange, symbol);
864+
const res = [];
865+
for (const o of conds) {
866+
try { await ex.cancelOrder(o.id, symbol, { stop: true }); res.push({ id: o.id, ok: true }); }
867+
catch (e) { res.push({ id: o.id, ok: false, error: String(e).slice(0, 100) }); }
868+
}
869+
out.conditional = { canceled: res.filter((r) => r.ok).length, total: res.length, detail: res };
870+
} catch (e) { out.conditional = { skipped: String(e).slice(0, 120) }; }
847871
}
848872
return out;
849873
},

skills/aicoin-trading/scripts/exchange.mjs

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,25 @@ function closeParamsFor(exchange, marketType, pos) {
250250
return out;
251251
}
252252

253+
// 查"条件/算法单"(止盈止损/触发/追踪),合并各 ordType 再去重 —— OKX 的止盈止损是 conditional 类,
254+
// 与 trigger 类**分开存**,必须各类都查再合并,不能"第一次返回空就 return"(否则 OKX 的 set_stop
255+
// 单会被漏掉:实测 stop_orders 旧逻辑先查 trigger 返回空就 return,读不到 conditional 类的 SL/TP)。
256+
async function fetchConditionalOrders(ex, exchange, symbol) {
257+
const variants = [{ trigger: true }, { stop: true }];
258+
if (exchange === 'okx') variants.push({ ordType: 'conditional' }, { ordType: 'oco' }, { ordType: 'trigger' }, { ordType: 'move_order_stop' });
259+
const seen = new Map();
260+
let any = false, lastErr = null;
261+
for (const extra of variants) {
262+
try {
263+
const os = await ex.fetchOpenOrders(symbol, undefined, undefined, extra);
264+
any = true;
265+
for (const o of (os || [])) if (o && o.id != null) seen.set(o.id, o);
266+
} catch (e) { lastErr = e; }
267+
}
268+
if (!any) throw lastErr || new Error('无法查询条件单');
269+
return [...seen.values()];
270+
}
271+
253272
cli({
254273
exchanges: async () => ({
255274
supported: SUPPORTED.map(id => {
@@ -778,12 +797,8 @@ cli({
778797
// 列出条件单/算法委托(止盈止损等)。OKX 等的算法单不在普通 open_orders 里,用这个查。
779798
stop_orders: async ({ exchange, symbol, market_type }) => {
780799
const ex = await getExchange(exchange, market_type || 'swap');
781-
let lastErr = null;
782-
for (const extra of [{ trigger: true }, { stop: true }]) {
783-
try { return await ex.fetchOpenOrders(symbol, undefined, undefined, extra); }
784-
catch (e) { lastErr = e; }
785-
}
786-
throw new Error(`查询条件单失败: ${lastErr?.message || lastErr}。部分交易所的算法/条件单需在交易所 APP 的「条件委托」栏查看。`);
800+
try { return await fetchConditionalOrders(ex, exchange, symbol); }
801+
catch (e) { throw new Error(`查询条件单失败: ${e?.message || e}。部分交易所的算法/条件单需在交易所 APP 的「条件委托」栏查看。`); }
787802
},
788803
funding_rate: async ({ exchange, symbol, market_type }) => {
789804
const ex = await getExchange(exchange, market_type || 'swap', true);
@@ -843,7 +858,16 @@ cli({
843858
try { out.conditional = await ex.cancelAllOrders(symbol, { stop: true }); } catch (e) { out.conditional = { skipped: String(e).slice(0, 120) }; }
844859
} else {
845860
try { out.regular = await cancelByFetch(); } catch (e) { out.regular = { error: String(e).slice(0, 160) }; }
846-
try { out.conditional = await cancelByFetch({ stop: true }); } catch (e) { out.conditional = { skipped: String(e).slice(0, 120) }; }
861+
// 条件单: 用合并查询(覆盖 OKX conditional 类,不漏)再逐个撤
862+
try {
863+
const conds = await fetchConditionalOrders(ex, exchange, symbol);
864+
const res = [];
865+
for (const o of conds) {
866+
try { await ex.cancelOrder(o.id, symbol, { stop: true }); res.push({ id: o.id, ok: true }); }
867+
catch (e) { res.push({ id: o.id, ok: false, error: String(e).slice(0, 100) }); }
868+
}
869+
out.conditional = { canceled: res.filter((r) => r.ok).length, total: res.length, detail: res };
870+
} catch (e) { out.conditional = { skipped: String(e).slice(0, 120) }; }
847871
}
848872
return out;
849873
},

0 commit comments

Comments
 (0)