feat(R-013+重构): 策略自定义字段需求文档 + 结构归类优化

需求与文档:
- R-013 策略自定义字段配置(定义随策略 configSchema 存 settings、值落
  strategy_holdings.values JSON、类型化文本/数字/布尔/枚举)定稿并进入
  PLAN-012 / 迭代 11(含目标数据模型 + 端到端示例 + 技术方案 + 验收标准)
- R-011(Tab设置)/ R-012(UI主题适配)归档至 已完成/,索引标记已归档
- 沉淀约束: 产品约束-010 / 技术约束-015 / 技术约束-016 / UI约束-005

结构重构(技术约束-016):
- src/component/ 平铺还原为语义分域目录: data-source/ storage/ position/
  market/ trades/(git rename 保留历史)
- 清理死代码: 删除 AllocationStorage.js(0 引用)、DataStore
  setDataset/removeDataset(无调用方)
- 同步更新 src/index.js / scripts/*.mjs / tsdown.config.ts 引用
- typecheck + build + test-r009 回归 14/14 通过
This commit is contained in:
2026-09-02 15:38:51 +08:00
parent f1e7e785a1
commit a4902bb730
31 changed files with 509 additions and 198 deletions
+88
View File
@@ -0,0 +1,88 @@
/**
* TradeSync —— 交易记录本地同步(R-009 / 迭代 072026-09-01
*
* 职责:把 QMT Bridge 当日交易数据(委托 + 成交)定时同步落 SQLitetrade_orders + trade_fills)。
* - 启动预热:插件启动即同步一次(今日数据入库);
* - 定时同步:60s 轮询拉当日 orders+trades → UPSERT(幂等,状态覆盖更新);
* - 失败容忍:单次同步失败只记日志,不中断定时循环。
*
* 数据来源:QmtBridgeRestDataSource.getOrders() / getTrades()(仅当日,QMT 无历史接口)。
* 存储:DataStore.upsertTradeOrders / upsertTradeFills(委托 SqliteStoreUPSERT 幂等)。
*/
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();
}
}