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