feat(交易记录): SQLite 本地存储 + 手动归属(迭代 07,R-009)
- trade_orders + trade_fills 两表(委托/成交,holding_id/strategy_id 手动归属) - TradeSync 定时同步(启动预热 + 60s UPSERT,不覆盖归属列) - 手动设置归属(candidates 候选 + set-attribution 端点 + 前端归属下拉) - 历史查询 trades/history + 策略过滤 - QMT code 归一化(补后缀)+ tradeDate 兜底 insertDate
This commit is contained in:
+64
-3
@@ -5,16 +5,24 @@
|
||||
* 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 查委托汇总(策略持仓行展开)
|
||||
]);
|
||||
|
||||
/**
|
||||
@@ -23,15 +31,31 @@ export const TRADE_METHODS = new Set([
|
||||
* @param {object} args
|
||||
* @param {object} runtime { dataSource }
|
||||
*/
|
||||
export async function handleTrade(method, args, { dataSource }) {
|
||||
export async function handleTrade(method, args, { dataSource, storage, settings }) {
|
||||
switch (method) {
|
||||
case 'orders':
|
||||
return await dataSource.getOrders({
|
||||
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,
|
||||
@@ -42,6 +66,43 @@ export async function handleTrade(method, args, { dataSource }) {
|
||||
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' });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user