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:
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* TradeSync —— 交易记录本地同步(R-009 / 迭代 07,2026-09-01)
|
||||
*
|
||||
* 职责:把 QMT Bridge 当日交易数据(委托 + 成交)定时同步落 SQLite(trade_orders + trade_fills)。
|
||||
* - 启动预热:插件启动即同步一次(今日数据入库);
|
||||
* - 定时同步:60s 轮询拉当日 orders+trades → UPSERT(幂等,状态覆盖更新);
|
||||
* - 失败容忍:单次同步失败只记日志,不中断定时循环。
|
||||
*
|
||||
* 数据来源:QmtBridgeRestDataSource.getOrders() / getTrades()(仅当日,QMT 无历史接口)。
|
||||
* 存储:DataStore.upsertTradeOrders / upsertTradeFills(委托 SqliteStore,UPSERT 幂等)。
|
||||
*/
|
||||
|
||||
const SYNC_INTERVAL_MS = 60 * 1000; // 60s
|
||||
|
||||
export class TradeSync {
|
||||
/**
|
||||
* @param {object} opts
|
||||
* @param {object} opts.runtime { dataSource, storage } —— dataSource 拉当日数据,storage 落库
|
||||
* @param {object} [opts.logger]
|
||||
*/
|
||||
constructor({ runtime, logger } = {}) {
|
||||
this.runtime = runtime;
|
||||
this.logger = logger;
|
||||
this.timer = null;
|
||||
this.mounted = false;
|
||||
this.syncing = false;
|
||||
}
|
||||
|
||||
/** 启动:立即同步一次(预热)+ 定时同步 */
|
||||
start() {
|
||||
if (this.mounted) return;
|
||||
this.mounted = true;
|
||||
this.syncNow().catch((e) => {
|
||||
this.logger?.warn?.('[one-divine-lot] TradeSync 启动预热失败: ' + (e?.message ?? e));
|
||||
});
|
||||
this.timer = setInterval(() => {
|
||||
this.syncNow().catch((e) => {
|
||||
this.logger?.debug?.('[one-divine-lot] TradeSync 定时同步失败: ' + (e?.message ?? e));
|
||||
});
|
||||
}, SYNC_INTERVAL_MS);
|
||||
this.logger?.info?.('[one-divine-lot] TradeSync 启动(60s 定时同步交易记录)');
|
||||
}
|
||||
|
||||
/** 停止(插件释放时) */
|
||||
stop() {
|
||||
this.mounted = false;
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步一次:拉当日 orders+trades → UPSERT 落库(幂等)
|
||||
* @returns {Promise<{orders: number, fills: number} | null>} 写入条数;数据源不可用返回 null
|
||||
*/
|
||||
async syncNow() {
|
||||
if (!this.mounted || this.syncing) return null;
|
||||
const { dataSource, storage } = this.runtime;
|
||||
if (!dataSource || !storage) return null;
|
||||
this.syncing = true;
|
||||
try {
|
||||
const [orders, fills] = await Promise.all([
|
||||
dataSource.getOrders({}).catch((e) => {
|
||||
this.logger?.debug?.('[one-divine-lot] TradeSync 拉委托失败: ' + (e?.message ?? e));
|
||||
return null;
|
||||
}),
|
||||
dataSource.getTrades({}).catch((e) => {
|
||||
this.logger?.debug?.('[one-divine-lot] TradeSync 拉成交失败: ' + (e?.message ?? e));
|
||||
return null;
|
||||
}),
|
||||
]);
|
||||
const nOrders = Array.isArray(orders) ? await storage.upsertTradeOrders(orders) : 0;
|
||||
const nFills = Array.isArray(fills) ? await storage.upsertTradeFills(fills) : 0;
|
||||
if (nOrders > 0 || nFills > 0) {
|
||||
this.logger?.debug?.('[one-divine-lot] TradeSync 同步完成: 委托 ' + nOrders + ' / 成交 ' + nFills);
|
||||
}
|
||||
return { orders: nOrders, fills: nFills };
|
||||
} finally {
|
||||
this.syncing = false;
|
||||
}
|
||||
}
|
||||
|
||||
/** 手动触发同步(api 层可调用;返回最新同步结果) */
|
||||
async sync() {
|
||||
return this.syncNow();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user