Files
one_divine_lot/src/api/index.js
T
kyugao 51c70c48fb 迭代14: 策略tab历史持仓展示(R-016)+ 今日已清仓默认层
- R-016 Q1-Q7: 两个策略 tab title 旁「历史持仓」开关 + 清仓范围筛选(近一周默认/1月/3月/半年/1年)
  - SqliteStore.getHoldingsHistory(strategy_id + closed_at 非空 + sinceMs,closed_at DESC,只读)
  - DataStore 门面透传 + strategy-holdings/history 端点(name 兜底 + 参数校验)
  - 前端: 开关/范围下拉/历史行灰显+「已清仓」徽标/展开复用 R-010(rowKey 唯一化)
- R-016 二轮补充(Q8-Q9 老师拍板): 今日已清仓默认层
  - range=today = 本地自然日 00:00 起(特判零点,不落回溯毫秒档)
  - 当天已清仓的持仓默认恒显示、不受「历史持仓」开关控制(开关只控今天之前)
  - rowsToRender 分层合成 + holdingId 去重;标题计数不含已清仓行;零写路径变更
- 回归: test-r016-history 24/24 + 存量回归全绿
2026-09-07 18:15:07 +08:00

102 lines
4.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 服务端 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 }
* strategy-holdings/history → 某策略已清仓历史持仓 { strategyId, range }R-016
* unallocated → 未分配持仓
* summary → 分仓摘要
* add-shares → 添加份额 { code, strategyId, shares }
* move-all-shares → 全部移入 { code, strategyId }
* remove-shares → 移出份额 { code, strategyId, shares }
* 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, storage, tradeSync, positionSync }) {
const runtime = { ctx, manager, settings, dataSource, marketHub, marketFeed, qmtHealthMonitor, storage, tradeSync, positionSync };
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/*)');
}