b3526c7693
- trade_orders + trade_fills 两表(委托/成交,holding_id/strategy_id 手动归属) - TradeSync 定时同步(启动预热 + 60s UPSERT,不覆盖归属列) - 手动设置归属(candidates 候选 + set-attribution 端点 + 前端归属下拉) - 历史查询 trades/history + 策略过滤 - QMT code 归一化(补后缀)+ tradeDate 兜底 insertDate
109 lines
4.6 KiB
JavaScript
109 lines
4.6 KiB
JavaScript
/**
|
||
* 服务端 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' });
|
||
}
|
||
} |