9b719f13ba
- 新增 SqliteStore(node:sqlite 封装):strategy_holdings 持仓生命周期表 + market_quotes_cache 行情缓存表 - DataStore 改造为门面委托 SqliteStore,保留兼容 API,启动检测自动迁移(幂等) - PositionManager 适配单票生命周期(openHolding/addShares/reduceShares/closeHolding),清仓转历史保留 - 新增一次性迁移脚本 scripts/migrate-json-to-sqlite.mjs + pnpm migrate 命令 - 修复 addToStrategy 语义 Bug(新增量 vs 绝对目标量),真实 API CRUD 回归通过 - 移除策略 tab 一键清零按钮及 remove-all-shares 端点(clearStrategyShares 保留供删除策略联动) - 插件 dispose 补充 storage.close() 关闭 SQLite 连接
96 lines
4.0 KiB
JavaScript
96 lines
4.0 KiB
JavaScript
/**
|
||
* 神之一手(one_divine_lot)DSH 插件入口
|
||
*
|
||
* 迭代 01:分仓管理工具
|
||
* - S2 数据源直连 QMT Bridge REST(技术约束-003)
|
||
* - S3 设置管理(策略=标签 + 显示开关)
|
||
* - S4 分仓数据本地存储
|
||
* - S5 分仓逻辑(份额分配/查询)
|
||
* - S6 服务端 HTTP API(webServer /odl/api/*)
|
||
* - S7 客户端 tab 展示(客户端插件)
|
||
*
|
||
* 依据:docs/02-计划/计划-分仓管理工具.md、docs/04-迭代记录/01-分仓管理工具/
|
||
* 设计约束:技术约束-001/003、产品约束-001/002/003
|
||
*/
|
||
|
||
import { QmtBridgeRestDataSource } from './component/QmtBridgeRestDataSource.js';
|
||
import { registerSettings, resolveStartupConnection } from './settings.js';
|
||
import { DataStore } from './component/DataStore.js';
|
||
import { PositionManager } from './component/PositionManager.js';
|
||
import { registerApi } from './api/index.js';
|
||
import { MarketDataHub } from './component/MarketDataHub.js';
|
||
import { MarketFeed } from './component/MarketFeed.js';
|
||
import { QmtHealthMonitor } from './component/QmtHealthMonitor.js';
|
||
|
||
const name = 'one-divine-lot';
|
||
|
||
// 依赖服务:settings(S3)、webServer(S6 api)
|
||
const inject = ['settings', 'webServer'];
|
||
|
||
async function apply(ctx, config) {
|
||
const logger = ctx.logger;
|
||
|
||
// S3: 设置管理(策略=标签 + 显示开关 + QMT 连接配置)
|
||
const settings = registerSettings(ctx, config);
|
||
|
||
// S2: 数据源(REST 直连 QMT Bridge)
|
||
// R-004(技术约束-008):启动激活地址 = 默认配置 → 上次激活 → 第一条;列表为空回退 cordis qmtBaseUrl(Q4 不迁移)
|
||
const settingsScope = settings;
|
||
let startup;
|
||
try {
|
||
startup = await resolveStartupConnection(settingsScope, config);
|
||
} catch (e) {
|
||
logger.warn('[one-divine-lot] 启动连接解析失败,回退 cordis qmtBaseUrl: ' + e.message);
|
||
startup = { connection: null, baseUrl: config?.qmtBaseUrl ?? null, source: 'cordis-fallback' };
|
||
}
|
||
const dataSource = new QmtBridgeRestDataSource({
|
||
baseUrl: startup.baseUrl ?? config?.qmtBaseUrl,
|
||
timeoutMs: config?.qmtTimeoutMs,
|
||
});
|
||
logger.info(
|
||
'[one-divine-lot] QMT 连接激活: ' + (startup.connection ? startup.connection.name + ' (' + startup.connection.baseUrl + ')' : String(startup.baseUrl ?? '(未配置)')) +
|
||
' [来源: ' + startup.source + ']'
|
||
);
|
||
|
||
// S4: 分仓数据本地存储(R-006:JSON data store,策略为中心)
|
||
const storage = new DataStore({
|
||
dataDir: config?.dataDir,
|
||
});
|
||
|
||
// R-005:行情数据(MarketDataHub 缓存 + MarketFeed 获取,页面轮询读取)
|
||
const marketHub = new MarketDataHub({ logger, dataStore: storage });
|
||
// 启动时从磁盘加载行情缓存(首屏快速展示,不依赖实盘订阅)
|
||
marketHub.warmup().catch((e) => logger.warn('[one-divine-lot] 行情预热失败: ' + e.message));
|
||
const marketFeed = new MarketFeed({ hub: marketHub, runtime: { settings, dataSource }, logger });
|
||
marketFeed.start(startup.baseUrl ?? config?.qmtBaseUrl);
|
||
|
||
// QMT 连接健康检查(服务端定时探测 + 缓存,2026-09-01)
|
||
const qmtHealthMonitor = new QmtHealthMonitor({ runtime: { settings, dataSource }, logger });
|
||
qmtHealthMonitor.start();
|
||
|
||
// S5: 分仓逻辑(份额分配/查询)
|
||
const manager = new PositionManager({ dataSource, storage });
|
||
|
||
// S6: 服务端 HTTP API(R-004:注入 dataSource 以编排激活热切换)
|
||
registerApi(ctx, { manager, settings, dataSource, marketHub, marketFeed, qmtHealthMonitor });
|
||
|
||
// 启动时可用性检查(日志,不阻塞)
|
||
dataSource.isAvailable().then((ok) => {
|
||
logger.info(`[one-divine-lot] QMT Bridge REST 可用: ${ok}`);
|
||
}).catch((e) => {
|
||
logger.warn(`[one-divine-lot] QMT Bridge REST 检查失败: ${e.message}`);
|
||
});
|
||
|
||
ctx.effect(() => {
|
||
logger.info('[one-divine-lot] 插件已加载(S1-S6 就绪)');
|
||
return () => {
|
||
marketFeed.stop();
|
||
qmtHealthMonitor.stop();
|
||
storage.close(); // 关闭 SQLite 连接(迭代 06)
|
||
logger.info('[one-divine-lot] 插件已释放');
|
||
};
|
||
}, 'one-divine-lot.dispose');
|
||
}
|
||
|
||
export { name, inject, apply };
|