/** * 服务端 API:交易记录域(R-007,2026-09-01) * * 端点: * orders → 当日委托(支持 code/status 过滤) * trades → 当日成交 * trading-dates → 交易日历(日期导航) * trades/history → 本地 SQLite 历史查询(R-009:时间段/code/策略/方向过滤) * trades/by-holding → 按 holding_id 查委托汇总(R-010:策略持仓行展开) * * 数据流:前端轮询 → /odl/api/orders|trades → dataSource.getOrders/getTrades * (服务端直连 QMT Bridge REST,技术约束-003) */ import { getStrategies } from '../settings.js'; /** 交易记录端点方法表 */ export const TRADE_METHODS = new Set([ 'orders', 'trades', 'trading-dates', 'trades/history', // R-009:本地 SQLite 历史查询(策略过滤) 'orders/attribution-candidates', // R-009:委托归属候选列表(该 code 当前持仓策略) 'orders/set-attribution', // R-009:手动设置委托归属 'trades/by-holding', // R-010:按 holding_id 查委托汇总(策略持仓行展开) ]); /** * 处理交易记录端点 * @param {string} method * @param {object} args * @param {object} runtime { dataSource } */ export async function handleTrade(method, args, { dataSource, storage, settings }) { switch (method) { case 'orders': { const orders = await dataSource.getOrders({ code: args.code, status: args.status, start: args.start, end: args.end, }); // R-009:今日实时委托附加【持久化归属】(本地库中用户已设置的 strategy_id/holding_id) // 未设置为 null(前端显示「未关联」,用户可手动设置);候选由 orders/attribution-candidates 提供 if (storage && Array.isArray(orders) && orders.length > 0) { const ids = orders.map((o) => o.orderId).filter(Boolean); const ph = ids.map(() => '?').join(','); const rows = storage.sqlite.db.prepare( 'SELECT order_id, strategy_id, holding_id FROM trade_orders WHERE order_id IN (' + ph + ')' ).all(...ids); const attr = new Map(rows.map((r) => [r.order_id, { strategyId: r.strategy_id ?? null, holdingId: r.holding_id ?? null }])); return orders.map((o) => { const a = attr.get(o.orderId) ?? { strategyId: null, holdingId: null }; return { ...o, strategyId: a.strategyId, holdingId: a.holdingId }; }); } return orders; } case 'trades': return await dataSource.getTrades({ start: args.start, end: args.end, }); case 'trading-dates': return await dataSource.getTradingDates({ start: args.start, end: args.end, }); case 'orders/attribution-candidates': { // R-009:委托归属候选(该 code 当前持仓策略,Q3 全手动选) if (!storage) throw Object.assign(new Error('本地存储不可用'), { code: 'storage-unavailable' }); const list = await storage.getTradeAttributionCandidates(args.code); // 附策略名(settings 的策略 name,便于前端展示) const nameMap = new Map((getStrategies(settings) || []).map((s) => [s.id, s.name])); return list.map((c) => ({ ...c, strategyName: nameMap.get(c.strategyId) ?? c.strategyId })); } case 'orders/set-attribution': { // R-009:手动设置委托归属(Q4 可随时改,以最终为准) if (!storage) throw Object.assign(new Error('本地存储不可用'), { code: 'storage-unavailable' }); const ok = await storage.setTradeOrderAttribution(args.orderId, args.strategyId ?? null, args.holdingId ?? null); return { ok }; } case 'trades/by-holding': { // R-010:按 holding_id 查委托汇总(策略持仓行展开) if (!storage) throw Object.assign(new Error('本地存储不可用'), { code: 'storage-unavailable' }); return await storage.getTradeOrdersByHolding(args.holdingId); } case 'trades/history': { // R-009:本地 SQLite 历史查询(时间段/code/策略/方向过滤) const opts = { start: args.start, end: args.end, code: args.code, strategyId: args.strategyId, direction: args.direction, }; if (!storage) { throw Object.assign(new Error('本地存储不可用'), { code: 'storage-unavailable' }); } const [orders, fills] = await Promise.all([ storage.getTradeOrderHistory(opts), storage.getTradeFillHistory(opts), ]); return { orders, fills }; } default: throw Object.assign(new Error('unknown trade method: ' + method), { code: 'not-found' }); } }