feat: 迭代03-05 交易记录接入、行情实时、QMT连接配置、data store 标准化

- R-004 QMT连接配置:多配置管理 + 会话头部快捷切换(03-QMT连接配置)
- R-005 WS盘中价格实时更新:服务端中转+缓存+前端轮询(04-WS盘中价格实时更新)
- R-006 策略数据 JSON data store 标准化(store.json/market.json/schema.json)
- R-007 交易记录接入:当日订单+成交单表合并(委托主行+展开成交明细)、时间段查询(今日/本周/本月+手动起止)、费用计算(佣金费率万m_dCommission+最低5元、印花税卖出万5、过户费双向万0.1)
- 架构治理:api 按领域拆分、component 目录、QmtHealthMonitor
This commit is contained in:
2026-09-01 13:21:49 +08:00
parent 5a723d514e
commit bb3f8bd28d
57 changed files with 5244 additions and 397 deletions
+102
View File
@@ -0,0 +1,102 @@
/**
* 服务端 HTTP API 入口(2026-08-31 从 api.js 按领域拆分)
*
* 结构:
* src/api/
* ├── index.js # 路由入口:合并各领域 + 公共处理(405/404/500
* ├── common.js # 公共工具(writeJson/readJsonBody/resolveActiveBaseUrl
* ├── positions.js # 持仓域(positions
* ├── strategies.js # 策略/份额/tabs 域
* ├── qmt-connections.js # QMT 连接配置域(含热切换编排)
* └── market.js # 行情域(market-snapshot
*
* 端点(POST /odl/api/<method>body = { args: {...} }):
* positions → 全量持仓
* strategies → 策略列表(含 visible)
* strategies/update → 更新策略列表(整表)
* strategies/add → 新增策略 { name }
* strategies/remove → 删除策略 { strategyId },联动清份额
* strategies/move → 排序 { strategyId, dir }
* strategy-positions → 某策略持仓 { strategyId }
* unallocated → 未分配持仓
* summary → 分仓摘要
* add-shares → 添加份额 { code, strategyId, shares }
* move-all-shares → 全部移入 { code, strategyId }
* remove-shares → 移出份额 { code, strategyId, shares }
* remove-all-shares → 一键清零 { strategyId }
* tabs / tabs/update → 通用 tab 显隐配置
* qmt-connections → QMT 连接配置(R-004
* qmt-connections/* → 增删改/激活/默认/测试/active-host/订阅代理
* market-snapshot → 按 code 查询行情缓存(R-005
*/
import { API_PREFIX, writeJson, readJsonBody } from './common.js';
import { POSITION_METHODS, handlePosition } from './positions.js';
import { STRATEGY_METHODS, handleStrategy } from './strategies.js';
import { QMT_METHODS, handleQmt } from './qmt-connections.js';
import { MARKET_METHODS, handleMarket } from './market.js';
import { TRADE_METHODS, handleTrade } from './trades.js';
/** 全部端点方法表(合并各领域) */
const METHODS = new Set([
...POSITION_METHODS,
...STRATEGY_METHODS,
...QMT_METHODS,
...MARKET_METHODS,
...TRADE_METHODS,
]);
/** 领域分发:method → handler */
const HANDLERS = [
{ set: POSITION_METHODS, handle: handlePosition },
{ set: STRATEGY_METHODS, handle: handleStrategy },
{ set: QMT_METHODS, handle: handleQmt },
{ set: MARKET_METHODS, handle: handleMarket },
{ set: TRADE_METHODS, handle: handleTrade },
];
/**
* 注册 HTTP API
* @param {import('@deepseek-ai/cordis').Context} ctx
* @param {object} runtime { manager, settings, dataSource, marketCache }
*/
export function registerApi(ctx, { manager, settings, dataSource, marketHub, marketFeed, qmtHealthMonitor }) {
const runtime = { ctx, manager, settings, dataSource, marketHub, marketFeed, qmtHealthMonitor };
ctx.effect(() => ctx.webServer.register({
kind: 'prefix',
path: '/odl/api',
handler: async (req, res) => {
if (req.method !== 'POST') {
writeJson(res, 405, { ok: false, error: { code: 'method-error', message: 'method not allowed' } });
return;
}
const pathname = new URL(req.url ?? '/', 'http://dsh.internal').pathname;
const method = pathname.startsWith(API_PREFIX) ? pathname.slice(API_PREFIX.length) : void 0;
if (!method || !METHODS.has(method)) {
writeJson(res, 404, { ok: false, error: { code: 'not-found', message: 'unknown method: ' + method } });
return;
}
try {
const body = await readJsonBody(req);
const args = body?.args ?? {};
// 按领域分发
let value;
for (const { set, handle } of HANDLERS) {
if (set.has(method)) {
value = await handle(method, args, runtime);
break;
}
}
writeJson(res, 200, { ok: true, value });
} catch (error) {
writeJson(res, 500, {
ok: false,
error: { code: error?.code ?? 'internal', message: error?.message ?? String(error), details: {} },
});
}
},
}), 'one-divine-lot.api');
ctx.logger?.info('[one-divine-lot] HTTP API 已注册 (/odl/api/*)');
}