tickflow-assist 0.3.6 → 0.3.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -42
- package/dist/analysis/types/composite-analysis.d.ts +27 -0
- package/dist/background/realtime-monitor.worker.d.ts +1 -1
- package/dist/background/realtime-monitor.worker.js +3 -4
- package/dist/bootstrap.js +24 -4
- package/dist/config/tickflow-access.d.ts +2 -1
- package/dist/config/tickflow-access.js +10 -3
- package/dist/dev/run-monitor-loop.js +0 -1
- package/dist/dev/tickflow-assist-cli.js +4 -3
- package/dist/dev/validate-mx-search.js +10 -2
- package/dist/plugin-commands.js +27 -0
- package/dist/plugin.js +4 -6
- package/dist/prompts/analysis/kline-analysis-user-prompt.js +2 -1
- package/dist/prompts/analysis/post-close-review-user-prompt.js +40 -1
- package/dist/prompts/analysis/pre-market-brief-prompt.d.ts +3 -1
- package/dist/prompts/analysis/pre-market-brief-prompt.js +8 -3
- package/dist/services/industry-peer-service.d.ts +9 -0
- package/dist/services/industry-peer-service.js +152 -0
- package/dist/services/jin10-flash-monitor-service.js +2 -1
- package/dist/services/monitor-service.d.ts +1 -1
- package/dist/services/monitor-service.js +21 -26
- package/dist/services/mx-search-service.d.ts +8 -1
- package/dist/services/mx-search-service.js +400 -10
- package/dist/services/post-close-review-service.d.ts +11 -4
- package/dist/services/post-close-review-service.js +113 -10
- package/dist/services/pre-market-brief-service.js +500 -42
- package/dist/services/tickflow-client.d.ts +4 -1
- package/dist/services/tickflow-client.js +32 -0
- package/dist/services/tickflow-universe-service.d.ts +26 -0
- package/dist/services/tickflow-universe-service.js +213 -0
- package/dist/services/watchlist-profile-service.d.ts +4 -1
- package/dist/services/watchlist-profile-service.js +58 -29
- package/dist/services/watchlist-service.d.ts +5 -1
- package/dist/services/watchlist-service.js +9 -4
- package/dist/storage/repositories/universe-membership-repo.d.ts +11 -0
- package/dist/storage/repositories/universe-membership-repo.js +38 -0
- package/dist/storage/repositories/universe-repo.d.ts +17 -0
- package/dist/storage/repositories/universe-repo.js +62 -0
- package/dist/storage/schemas.d.ts +2 -0
- package/dist/storage/schemas.js +13 -0
- package/dist/tools/add-stock.tool.d.ts +2 -1
- package/dist/tools/add-stock.tool.js +10 -1
- package/dist/tools/eastmoney-watchlist.tool.d.ts +31 -0
- package/dist/tools/eastmoney-watchlist.tool.js +294 -0
- package/dist/tools/mx-data.tool.d.ts +8 -0
- package/dist/tools/mx-data.tool.js +94 -0
- package/dist/tools/mx-select-stock.tool.js +6 -2
- package/dist/tools/query-database.tool.js +6 -0
- package/dist/tools/refresh-watchlist-profiles.tool.d.ts +2 -1
- package/dist/tools/refresh-watchlist-profiles.tool.js +11 -1
- package/dist/tools/screen-stock-candidates.tool.d.ts +34 -0
- package/dist/tools/screen-stock-candidates.tool.js +477 -0
- package/dist/tools/test-alert.tool.js +56 -19
- package/dist/types/mx-data.d.ts +23 -0
- package/dist/types/mx-data.js +1 -0
- package/dist/types/mx-select-stock.d.ts +1 -0
- package/dist/types/mx-self-select.d.ts +30 -0
- package/dist/types/mx-self-select.js +1 -0
- package/dist/types/tickflow.d.ts +12 -0
- package/dist/utils/tickflow-quote.d.ts +5 -0
- package/dist/utils/tickflow-quote.js +31 -0
- package/openclaw.plugin.json +83 -6
- package/package.json +6 -6
- package/skills/stock-analysis/SKILL.md +39 -20
- package/skills/usage-help/SKILL.md +33 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { formatConfigEnvFallback } from "../config/env.js";
|
|
2
|
+
import { normalizeSymbol } from "../utils/symbol.js";
|
|
2
3
|
export class MxSearchServiceError extends Error {
|
|
3
4
|
constructor(message) {
|
|
4
5
|
super(message);
|
|
@@ -15,12 +16,12 @@ export class MxApiService {
|
|
|
15
16
|
isConfigured() {
|
|
16
17
|
return Boolean(this.apiBaseUrl.trim() && this.apiKey.trim());
|
|
17
18
|
}
|
|
18
|
-
getConfigurationError() {
|
|
19
|
+
getConfigurationError(featureName = "mx_search") {
|
|
19
20
|
if (!this.apiBaseUrl.trim()) {
|
|
20
|
-
return
|
|
21
|
+
return `${featureName} 未配置接口地址,请设置 mxSearchApiUrl 或环境变量 ${formatConfigEnvFallback("mxSearchApiUrl")}`;
|
|
21
22
|
}
|
|
22
23
|
if (!this.apiKey.trim()) {
|
|
23
|
-
return
|
|
24
|
+
return `${featureName} 未配置 API Key,请设置插件配置 mxSearchApiKey 或环境变量 ${formatConfigEnvFallback("mxSearchApiKey")}`;
|
|
24
25
|
}
|
|
25
26
|
return null;
|
|
26
27
|
}
|
|
@@ -40,8 +41,40 @@ export class MxApiService {
|
|
|
40
41
|
}, "mx_select_stock");
|
|
41
42
|
return normalizeMxSelectStockResult(json);
|
|
42
43
|
}
|
|
44
|
+
async queryData(toolQuery) {
|
|
45
|
+
const normalizedQuery = toolQuery.trim();
|
|
46
|
+
if (!normalizedQuery) {
|
|
47
|
+
throw new MxSearchServiceError("mx_data requires query");
|
|
48
|
+
}
|
|
49
|
+
const json = await this.postJson("query", { toolQuery: normalizedQuery }, "mx_data");
|
|
50
|
+
const apiError = extractApiError(json);
|
|
51
|
+
if (apiError) {
|
|
52
|
+
throw new MxSearchServiceError(`mx_data 返回错误: ${apiError.code ?? "UNKNOWN"} ${apiError.message ?? ""}`.trim());
|
|
53
|
+
}
|
|
54
|
+
return normalizeMxDataResult(json);
|
|
55
|
+
}
|
|
56
|
+
async getSelfSelectWatchlist() {
|
|
57
|
+
const json = await this.postJson("self-select/get", {}, "mx_zixuan");
|
|
58
|
+
const apiError = extractApiError(json);
|
|
59
|
+
if (apiError) {
|
|
60
|
+
throw new MxSearchServiceError(`mx_zixuan 返回错误: ${apiError.code ?? "UNKNOWN"} ${apiError.message ?? ""}`.trim());
|
|
61
|
+
}
|
|
62
|
+
return normalizeMxSelfSelectResult(json);
|
|
63
|
+
}
|
|
64
|
+
async manageSelfSelect(query) {
|
|
65
|
+
const normalizedQuery = query.trim();
|
|
66
|
+
if (!normalizedQuery) {
|
|
67
|
+
throw new MxSearchServiceError("mx_zixuan requires query");
|
|
68
|
+
}
|
|
69
|
+
const json = await this.postJson("self-select/manage", { query: normalizedQuery }, "mx_zixuan");
|
|
70
|
+
const apiError = extractApiError(json);
|
|
71
|
+
if (apiError) {
|
|
72
|
+
throw new MxSearchServiceError(`mx_zixuan 返回错误: ${apiError.code ?? "UNKNOWN"} ${apiError.message ?? ""}`.trim());
|
|
73
|
+
}
|
|
74
|
+
return normalizeMxSelfSelectManageResult(json, normalizedQuery);
|
|
75
|
+
}
|
|
43
76
|
async postJson(endpoint, body, toolName) {
|
|
44
|
-
const configError = this.getConfigurationError();
|
|
77
|
+
const configError = this.getConfigurationError(toolName);
|
|
45
78
|
if (configError) {
|
|
46
79
|
throw new MxSearchServiceError(configError);
|
|
47
80
|
}
|
|
@@ -65,7 +98,7 @@ export class MxSearchService extends MxApiService {
|
|
|
65
98
|
function buildMxEndpointUrl(baseUrl, endpoint) {
|
|
66
99
|
const trimmedBase = baseUrl.trim().replace(/\/+$/, "");
|
|
67
100
|
const normalizedEndpoint = endpoint.replace(/^\/+/, "");
|
|
68
|
-
const normalizedBase = trimmedBase.replace(/\/(news-search|stock-screen)$/i, "");
|
|
101
|
+
const normalizedBase = trimmedBase.replace(/\/(news-search|stock-screen|query|self-select\/get|self-select\/manage)$/i, "");
|
|
69
102
|
if (trimmedBase.endsWith(`/${normalizedEndpoint}`)) {
|
|
70
103
|
return trimmedBase;
|
|
71
104
|
}
|
|
@@ -196,7 +229,7 @@ function toNullableString(value) {
|
|
|
196
229
|
const text = value.trim();
|
|
197
230
|
return text || null;
|
|
198
231
|
}
|
|
199
|
-
function normalizeMxSelectStockResult(value) {
|
|
232
|
+
export function normalizeMxSelectStockResult(value) {
|
|
200
233
|
const root = asRecord(value);
|
|
201
234
|
const data = asRecord(root.data);
|
|
202
235
|
const nestedData = asRecord(data.data);
|
|
@@ -205,17 +238,42 @@ function normalizeMxSelectStockResult(value) {
|
|
|
205
238
|
const businessCode = toNullableString(allResults.code) ?? toNullableString(nestedData.responseCode) ?? toNullableString(data.code);
|
|
206
239
|
const rawTotalCondition = normalizeCondition(allResults.totalCondition);
|
|
207
240
|
const flatTotalCondition = toNullableString(nestedData.totalCondition);
|
|
241
|
+
const structuredColumns = normalizeColumns(result.columns);
|
|
242
|
+
const structuredRows = normalizeDataList(result.dataList);
|
|
243
|
+
const partialTable = structuredRows.length === 0
|
|
244
|
+
? parseMarkdownTable(toNullableString(nestedData.partialResults) ?? toNullableString(allResults.partialResults))
|
|
245
|
+
: null;
|
|
246
|
+
const fallbackColumns = partialTable
|
|
247
|
+
? partialTable.fieldnames.map((fieldname) => ({
|
|
248
|
+
title: fieldname,
|
|
249
|
+
key: fieldname,
|
|
250
|
+
dateMsg: null,
|
|
251
|
+
sortable: false,
|
|
252
|
+
sortWay: null,
|
|
253
|
+
redGreenAble: false,
|
|
254
|
+
unit: null,
|
|
255
|
+
dataType: "String",
|
|
256
|
+
}))
|
|
257
|
+
: [];
|
|
258
|
+
const dataList = structuredRows.length > 0 ? structuredRows : (partialTable?.rows ?? []);
|
|
259
|
+
const columns = structuredColumns.length > 0 ? structuredColumns : fallbackColumns;
|
|
260
|
+
const dataSource = structuredRows.length > 0
|
|
261
|
+
? "dataList"
|
|
262
|
+
: partialTable && partialTable.rows.length > 0
|
|
263
|
+
? "partialResults"
|
|
264
|
+
: "none";
|
|
208
265
|
return {
|
|
209
266
|
status: toNullableNumber(root.status),
|
|
210
267
|
message: toNullableString(root.message),
|
|
211
268
|
code: businessCode,
|
|
212
269
|
msg: toNullableString(data.message) ?? toNullableString(data.msg),
|
|
213
270
|
resultType: toNullableNumber(result.resultType) ?? toNullableNumber(nestedData.resultType),
|
|
214
|
-
total: toSafeNumber(result.total),
|
|
215
|
-
totalRecordCount: toSafeNumber(result.totalRecordCount),
|
|
271
|
+
total: toSafeNumber(result.total) || dataList.length,
|
|
272
|
+
totalRecordCount: toSafeNumber(result.totalRecordCount) || dataList.length,
|
|
216
273
|
parserText: toNullableString(nestedData.parserText),
|
|
217
|
-
|
|
218
|
-
|
|
274
|
+
dataSource,
|
|
275
|
+
columns,
|
|
276
|
+
dataList,
|
|
219
277
|
responseConditionList: normalizeConditions(allResults.responseConditionList ?? nestedData.responseConditionList),
|
|
220
278
|
totalCondition: rawTotalCondition ??
|
|
221
279
|
(flatTotalCondition
|
|
@@ -226,6 +284,338 @@ function normalizeMxSelectStockResult(value) {
|
|
|
226
284
|
: null),
|
|
227
285
|
};
|
|
228
286
|
}
|
|
287
|
+
export function normalizeMxDataResult(value) {
|
|
288
|
+
const root = asRecord(value);
|
|
289
|
+
const data = asRecord(root.data);
|
|
290
|
+
const nestedData = asRecord(data.data);
|
|
291
|
+
const searchResult = asRecord(nestedData.searchDataResultDTO ?? data.searchDataResultDTO);
|
|
292
|
+
const dtoList = Array.isArray(searchResult.dataTableDTOList)
|
|
293
|
+
? searchResult.dataTableDTOList
|
|
294
|
+
: Array.isArray(searchResult.rawDataTableDTOList)
|
|
295
|
+
? searchResult.rawDataTableDTOList
|
|
296
|
+
: [];
|
|
297
|
+
const tables = [];
|
|
298
|
+
const conditionParts = [];
|
|
299
|
+
for (const [index, item] of dtoList.entries()) {
|
|
300
|
+
if (typeof item !== "object" || item === null) {
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
const dto = item;
|
|
304
|
+
const condition = toNullableString(dto.condition);
|
|
305
|
+
const entityName = toNullableString(dto.entityName);
|
|
306
|
+
if (condition) {
|
|
307
|
+
conditionParts.push(`[${entityName ?? `表${index + 1}`}]\n${condition}`);
|
|
308
|
+
}
|
|
309
|
+
const table = normalizeMxDataTable(dto, index);
|
|
310
|
+
if (table.rows.length === 0) {
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
tables.push(table);
|
|
314
|
+
}
|
|
315
|
+
return {
|
|
316
|
+
status: toNullableNumber(root.status),
|
|
317
|
+
message: toNullableString(root.message),
|
|
318
|
+
questionId: toNullableString(searchResult.questionId),
|
|
319
|
+
entityTags: normalizeMxDataEntityTags(searchResult.entityTagDTOList ?? data.entityTagDTOList),
|
|
320
|
+
conditionParts,
|
|
321
|
+
tables,
|
|
322
|
+
totalRows: tables.reduce((sum, table) => sum + table.rows.length, 0),
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
function normalizeMxDataTable(dto, index) {
|
|
326
|
+
const title = toNullableString(dto.title) ??
|
|
327
|
+
toNullableString(dto.inputTitle) ??
|
|
328
|
+
toNullableString(dto.entityName) ??
|
|
329
|
+
`表${index + 1}`;
|
|
330
|
+
const tableValue = dto.table ?? dto.rawTable;
|
|
331
|
+
const { rows, fieldnames } = mxDataTableToRows(tableValue, dto.nameMap, dto.indicatorOrder, toNullableString(dto.entityName) ?? "指标", dto);
|
|
332
|
+
return {
|
|
333
|
+
title,
|
|
334
|
+
code: toNullableString(dto.code),
|
|
335
|
+
entityName: toNullableString(dto.entityName),
|
|
336
|
+
rows,
|
|
337
|
+
fieldnames,
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
function mxDataTableToRows(tableValue, nameMapValue, indicatorOrderValue, entityName, block) {
|
|
341
|
+
if (Array.isArray(tableValue)) {
|
|
342
|
+
return genericRowsToNamedRows(tableValue, nameMapValue);
|
|
343
|
+
}
|
|
344
|
+
if (typeof tableValue !== "object" || tableValue === null) {
|
|
345
|
+
return { rows: [], fieldnames: [] };
|
|
346
|
+
}
|
|
347
|
+
const table = tableValue;
|
|
348
|
+
const nameMap = normalizeStringMap(nameMapValue);
|
|
349
|
+
const headers = Array.isArray(table.headName) ? table.headName : [];
|
|
350
|
+
const order = orderedMxDataKeys(table, indicatorOrderValue);
|
|
351
|
+
const codeMap = normalizeStringMap(block.returnCodeMap ?? block.returnCodeNameMap ?? block.codeMap);
|
|
352
|
+
if (headers.length > 0) {
|
|
353
|
+
const dateColumn = nameMap.get("headNameSub") || nameMap.get("headName") || "date";
|
|
354
|
+
const fieldnames = [
|
|
355
|
+
dateColumn,
|
|
356
|
+
...order
|
|
357
|
+
.map((key) => formatMxDataIndicatorLabel(key, nameMap, codeMap))
|
|
358
|
+
.filter(Boolean),
|
|
359
|
+
];
|
|
360
|
+
const rows = headers.map((header, rowIndex) => {
|
|
361
|
+
const row = {
|
|
362
|
+
[dateColumn]: flattenMxValue(header),
|
|
363
|
+
};
|
|
364
|
+
for (const key of order) {
|
|
365
|
+
const label = formatMxDataIndicatorLabel(key, nameMap, codeMap);
|
|
366
|
+
if (!label) {
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
const values = table[key];
|
|
370
|
+
const cell = Array.isArray(values) ? values[rowIndex] : rowIndex === 0 ? values : "";
|
|
371
|
+
row[label] = flattenMxValue(cell);
|
|
372
|
+
}
|
|
373
|
+
return row;
|
|
374
|
+
});
|
|
375
|
+
return { rows, fieldnames };
|
|
376
|
+
}
|
|
377
|
+
const fieldnames = [entityName, "value"];
|
|
378
|
+
const rows = order
|
|
379
|
+
.map((key) => {
|
|
380
|
+
const label = formatMxDataIndicatorLabel(key, nameMap, codeMap);
|
|
381
|
+
if (!label) {
|
|
382
|
+
return null;
|
|
383
|
+
}
|
|
384
|
+
return {
|
|
385
|
+
[fieldnames[0]]: label,
|
|
386
|
+
[fieldnames[1]]: flattenMxValue(table[key]),
|
|
387
|
+
};
|
|
388
|
+
})
|
|
389
|
+
.filter((row) => row != null);
|
|
390
|
+
return { rows, fieldnames };
|
|
391
|
+
}
|
|
392
|
+
function genericRowsToNamedRows(value, nameMapValue) {
|
|
393
|
+
const records = value.filter((item) => typeof item === "object" && item !== null);
|
|
394
|
+
if (records.length === 0) {
|
|
395
|
+
return { rows: [], fieldnames: [] };
|
|
396
|
+
}
|
|
397
|
+
const nameMap = normalizeStringMap(nameMapValue);
|
|
398
|
+
const keys = Object.keys(records[0] ?? {});
|
|
399
|
+
const fieldnames = keys.map((key) => nameMap.get(key) || key);
|
|
400
|
+
const rows = records.map((record) => {
|
|
401
|
+
const row = {};
|
|
402
|
+
for (const key of keys) {
|
|
403
|
+
row[nameMap.get(key) || key] = flattenMxValue(record[key]);
|
|
404
|
+
}
|
|
405
|
+
return row;
|
|
406
|
+
});
|
|
407
|
+
return { rows, fieldnames };
|
|
408
|
+
}
|
|
409
|
+
function orderedMxDataKeys(table, indicatorOrderValue) {
|
|
410
|
+
const dataKeys = Object.keys(table).filter((key) => key !== "headName");
|
|
411
|
+
const ordered = [];
|
|
412
|
+
const seen = new Set();
|
|
413
|
+
const indicatorOrder = Array.isArray(indicatorOrderValue) ? indicatorOrderValue.map((item) => String(item)) : [];
|
|
414
|
+
for (const key of indicatorOrder) {
|
|
415
|
+
if (dataKeys.includes(key) && !seen.has(key)) {
|
|
416
|
+
ordered.push(key);
|
|
417
|
+
seen.add(key);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
for (const key of dataKeys) {
|
|
421
|
+
if (!seen.has(key)) {
|
|
422
|
+
ordered.push(key);
|
|
423
|
+
seen.add(key);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
return ordered;
|
|
427
|
+
}
|
|
428
|
+
function formatMxDataIndicatorLabel(key, nameMap, codeMap) {
|
|
429
|
+
const mapped = nameMap.get(key) ?? codeMap.get(key);
|
|
430
|
+
if (mapped) {
|
|
431
|
+
return mapped;
|
|
432
|
+
}
|
|
433
|
+
return /^\d+$/.test(key) ? "" : key;
|
|
434
|
+
}
|
|
435
|
+
function normalizeMxDataEntityTags(value) {
|
|
436
|
+
if (!Array.isArray(value)) {
|
|
437
|
+
return [];
|
|
438
|
+
}
|
|
439
|
+
return value
|
|
440
|
+
.filter((item) => typeof item === "object" && item !== null)
|
|
441
|
+
.map((item) => ({
|
|
442
|
+
fullName: toNullableString(item.fullName),
|
|
443
|
+
secuCode: toNullableString(item.secuCode),
|
|
444
|
+
marketChar: toNullableString(item.marketChar),
|
|
445
|
+
entityTypeName: toNullableString(item.entityTypeName),
|
|
446
|
+
className: toNullableString(item.className),
|
|
447
|
+
}));
|
|
448
|
+
}
|
|
449
|
+
function parseMarkdownTable(value) {
|
|
450
|
+
const text = String(value ?? "").trim();
|
|
451
|
+
if (!text) {
|
|
452
|
+
return null;
|
|
453
|
+
}
|
|
454
|
+
const lines = text
|
|
455
|
+
.split(/\r?\n/)
|
|
456
|
+
.map((line) => line.trim())
|
|
457
|
+
.filter(Boolean);
|
|
458
|
+
if (lines.length === 0) {
|
|
459
|
+
return null;
|
|
460
|
+
}
|
|
461
|
+
const header = splitMarkdownCells(lines[0] ?? "");
|
|
462
|
+
if (header.length === 0) {
|
|
463
|
+
return null;
|
|
464
|
+
}
|
|
465
|
+
const dataStart = lines[1] && /^[\s|:-]+$/.test(lines[1]) ? 2 : 1;
|
|
466
|
+
const rows = [];
|
|
467
|
+
for (const line of lines.slice(dataStart)) {
|
|
468
|
+
const cells = splitMarkdownCells(line);
|
|
469
|
+
if (cells.length === 0) {
|
|
470
|
+
continue;
|
|
471
|
+
}
|
|
472
|
+
const row = {};
|
|
473
|
+
for (const [index, fieldname] of header.entries()) {
|
|
474
|
+
row[fieldname] = cells[index] ?? "";
|
|
475
|
+
}
|
|
476
|
+
rows.push(row);
|
|
477
|
+
}
|
|
478
|
+
return rows.length > 0 ? { rows, fieldnames: header } : null;
|
|
479
|
+
}
|
|
480
|
+
function splitMarkdownCells(line) {
|
|
481
|
+
return line
|
|
482
|
+
.split("|")
|
|
483
|
+
.map((cell) => cell.trim())
|
|
484
|
+
.filter((cell, index, cells) => {
|
|
485
|
+
const isEdge = (index === 0 || index === cells.length - 1) && cell === "";
|
|
486
|
+
return !isEdge;
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
function normalizeStringMap(value) {
|
|
490
|
+
const map = new Map();
|
|
491
|
+
if (Array.isArray(value)) {
|
|
492
|
+
value.forEach((item, index) => {
|
|
493
|
+
const text = flattenMxValue(item);
|
|
494
|
+
if (text) {
|
|
495
|
+
map.set(String(index), text);
|
|
496
|
+
}
|
|
497
|
+
});
|
|
498
|
+
return map;
|
|
499
|
+
}
|
|
500
|
+
if (typeof value !== "object" || value === null) {
|
|
501
|
+
return map;
|
|
502
|
+
}
|
|
503
|
+
for (const [key, raw] of Object.entries(value)) {
|
|
504
|
+
const text = flattenMxValue(raw);
|
|
505
|
+
if (text) {
|
|
506
|
+
map.set(key, text);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
return map;
|
|
510
|
+
}
|
|
511
|
+
function flattenMxValue(value) {
|
|
512
|
+
if (value == null) {
|
|
513
|
+
return "";
|
|
514
|
+
}
|
|
515
|
+
if (typeof value === "object") {
|
|
516
|
+
return JSON.stringify(value);
|
|
517
|
+
}
|
|
518
|
+
return String(value);
|
|
519
|
+
}
|
|
520
|
+
function normalizeMxSelfSelectResult(value) {
|
|
521
|
+
const root = asRecord(value);
|
|
522
|
+
const data = asRecord(root.data);
|
|
523
|
+
const nestedData = asRecord(data.data);
|
|
524
|
+
const allResults = asRecord(data.allResults ?? nestedData.allResults);
|
|
525
|
+
const result = asRecord(allResults.result ?? data.result ?? nestedData.result);
|
|
526
|
+
const rows = normalizeDataList(result.dataList ?? allResults.dataList ?? data.dataList ?? nestedData.dataList);
|
|
527
|
+
return {
|
|
528
|
+
status: toNullableNumber(root.status),
|
|
529
|
+
code: toNullableString(root.code) ?? toNullableString(allResults.code) ?? toNullableString(nestedData.responseCode),
|
|
530
|
+
message: toNullableString(root.message) ?? toNullableString(data.message) ?? toNullableString(data.msg),
|
|
531
|
+
columns: normalizeSelfSelectColumns(result.columns ?? allResults.columns ?? data.columns ?? nestedData.columns),
|
|
532
|
+
stocks: rows
|
|
533
|
+
.map((row) => normalizeSelfSelectStock(row))
|
|
534
|
+
.filter((item) => item != null),
|
|
535
|
+
raw: value,
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
function normalizeMxSelfSelectManageResult(value, query) {
|
|
539
|
+
const root = asRecord(value);
|
|
540
|
+
const data = asRecord(root.data);
|
|
541
|
+
const nestedData = asRecord(data.data);
|
|
542
|
+
const allResults = asRecord(data.allResults ?? nestedData.allResults);
|
|
543
|
+
return {
|
|
544
|
+
status: toNullableNumber(root.status),
|
|
545
|
+
code: toNullableString(root.code) ?? toNullableString(allResults.code) ?? toNullableString(nestedData.responseCode),
|
|
546
|
+
message: toNullableString(root.message) ??
|
|
547
|
+
toNullableString(data.message) ??
|
|
548
|
+
toNullableString(data.msg) ??
|
|
549
|
+
toNullableString(allResults.message) ??
|
|
550
|
+
"已完成",
|
|
551
|
+
query,
|
|
552
|
+
raw: value,
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
function normalizeSelfSelectColumns(value) {
|
|
556
|
+
if (!Array.isArray(value)) {
|
|
557
|
+
return [];
|
|
558
|
+
}
|
|
559
|
+
return value
|
|
560
|
+
.filter((item) => typeof item === "object" && item !== null)
|
|
561
|
+
.map((item) => ({
|
|
562
|
+
title: String(item.title ?? item.name ?? item.key ?? ""),
|
|
563
|
+
key: String(item.key ?? ""),
|
|
564
|
+
}))
|
|
565
|
+
.filter((item) => item.key);
|
|
566
|
+
}
|
|
567
|
+
function normalizeSelfSelectStock(value) {
|
|
568
|
+
const rawSymbol = pickFirstString(value, [
|
|
569
|
+
"SECURITY_CODE",
|
|
570
|
+
"SECUCODE",
|
|
571
|
+
"SECURITYCODE",
|
|
572
|
+
"secuCode",
|
|
573
|
+
"symbol",
|
|
574
|
+
"code",
|
|
575
|
+
]) ?? null;
|
|
576
|
+
const symbol = normalizeSelfSelectSymbol(rawSymbol);
|
|
577
|
+
if (!symbol) {
|
|
578
|
+
return null;
|
|
579
|
+
}
|
|
580
|
+
return {
|
|
581
|
+
symbol,
|
|
582
|
+
rawSymbol,
|
|
583
|
+
name: pickFirstString(value, [
|
|
584
|
+
"SECURITY_SHORT_NAME",
|
|
585
|
+
"SECURITY_NAME_ABBR",
|
|
586
|
+
"SECURITY_NAME",
|
|
587
|
+
"secuName",
|
|
588
|
+
"name",
|
|
589
|
+
]) ?? symbol,
|
|
590
|
+
latestPrice: pickFirstCell(value, ["NEWEST_PRICE", "LATEST_PRICE", "price"]),
|
|
591
|
+
changePercent: pickFirstCell(value, ["CHG", "CHANGE_PERCENT", "pctChg"]),
|
|
592
|
+
changeAmount: pickFirstCell(value, ["PCHG", "CHANGE", "change"]),
|
|
593
|
+
turnoverRate: pickFirstCell(value, ["010000_TURNOVER_RATE", "TURNOVER_RATE", "turnoverRate"]),
|
|
594
|
+
volumeRatio: pickFirstCell(value, ["010000_LIANGBI", "VOLUME_RATIO", "volumeRatio"]),
|
|
595
|
+
raw: value,
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
function normalizeSelfSelectSymbol(value) {
|
|
599
|
+
const text = String(value ?? "").trim().toUpperCase();
|
|
600
|
+
if (!text) {
|
|
601
|
+
return null;
|
|
602
|
+
}
|
|
603
|
+
const direct = text.match(/^\d{6}\.(SH|SZ|BJ)$/);
|
|
604
|
+
if (direct) {
|
|
605
|
+
return text;
|
|
606
|
+
}
|
|
607
|
+
const digits = text.match(/\d{6}/)?.[0];
|
|
608
|
+
return digits ? normalizeSymbol(digits) : null;
|
|
609
|
+
}
|
|
610
|
+
function pickFirstCell(value, keys) {
|
|
611
|
+
for (const key of keys) {
|
|
612
|
+
const candidate = value[key];
|
|
613
|
+
if (candidate != null && String(candidate).trim()) {
|
|
614
|
+
return String(candidate).trim();
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
return null;
|
|
618
|
+
}
|
|
229
619
|
function normalizeColumns(value) {
|
|
230
620
|
if (!Array.isArray(value)) {
|
|
231
621
|
return [];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { WatchlistItem } from "../types/domain.js";
|
|
2
2
|
import { CompositeAnalysisOrchestrator } from "../analysis/orchestrators/composite-analysis.orchestrator.js";
|
|
3
|
-
import type { CompositeAnalysisResult, PostCloseReviewResult, PriorKeyLevelValidationContext } from "../analysis/types/composite-analysis.js";
|
|
3
|
+
import type { CompositeAnalysisResult, IndustryPeerContext, PostCloseReviewResult, PriorKeyLevelValidationContext } from "../analysis/types/composite-analysis.js";
|
|
4
4
|
import { PostCloseReviewTask } from "../analysis/tasks/post-close-review.task.js";
|
|
5
5
|
import { WatchlistService } from "./watchlist-service.js";
|
|
6
6
|
import { AnalysisService } from "./analysis-service.js";
|
|
@@ -10,6 +10,11 @@ import { KlinesRepository } from "../storage/repositories/klines-repo.js";
|
|
|
10
10
|
import { IntradayKlinesRepository } from "../storage/repositories/intraday-klines-repo.js";
|
|
11
11
|
import { Jin10FlashDeliveryRepository } from "../storage/repositories/jin10-flash-delivery-repo.js";
|
|
12
12
|
import { Jin10FlashRepository } from "../storage/repositories/jin10-flash-repo.js";
|
|
13
|
+
import { IndustryPeerService } from "./industry-peer-service.js";
|
|
14
|
+
interface ReviewMarketSummary {
|
|
15
|
+
latestClose: number | null;
|
|
16
|
+
dailyChangePct: number | null;
|
|
17
|
+
}
|
|
13
18
|
export interface PostCloseReviewRunResult {
|
|
14
19
|
overviewMessage: string;
|
|
15
20
|
detailMessages: string[];
|
|
@@ -26,7 +31,8 @@ export declare class PostCloseReviewService {
|
|
|
26
31
|
private readonly intradayKlinesRepository;
|
|
27
32
|
private readonly flashDeliveryRepository;
|
|
28
33
|
private readonly flashRepository;
|
|
29
|
-
|
|
34
|
+
private readonly industryPeerService;
|
|
35
|
+
constructor(watchlistService: WatchlistService, compositeAnalysisOrchestrator: CompositeAnalysisOrchestrator, analysisService: AnalysisService, postCloseReviewTask: PostCloseReviewTask, keyLevelsRepository: KeyLevelsRepository, keyLevelsHistoryRepository: KeyLevelsHistoryRepository, klinesRepository: KlinesRepository, intradayKlinesRepository: IntradayKlinesRepository, flashDeliveryRepository: Jin10FlashDeliveryRepository, flashRepository: Jin10FlashRepository, industryPeerService: IndustryPeerService);
|
|
30
36
|
run(): Promise<PostCloseReviewRunResult>;
|
|
31
37
|
private persistReview;
|
|
32
38
|
private persistFallbackCompositeReview;
|
|
@@ -36,5 +42,6 @@ export declare class PostCloseReviewService {
|
|
|
36
42
|
private formatDetailMessage;
|
|
37
43
|
private formatFailureMessage;
|
|
38
44
|
}
|
|
39
|
-
export declare function formatPostCloseReviewDetailMessage(item: WatchlistItem, validation: PriorKeyLevelValidationContext, review: PostCloseReviewResult): string;
|
|
40
|
-
export declare function formatPostCloseReviewFailureMessage(item: WatchlistItem, errorMessage: string, compositeResult: CompositeAnalysisResult | null): string;
|
|
45
|
+
export declare function formatPostCloseReviewDetailMessage(item: WatchlistItem, validation: PriorKeyLevelValidationContext, review: PostCloseReviewResult, marketSummary?: ReviewMarketSummary | null, peerContext?: IndustryPeerContext | null): string;
|
|
46
|
+
export declare function formatPostCloseReviewFailureMessage(item: WatchlistItem, errorMessage: string, compositeResult: CompositeAnalysisResult | null, marketSummary?: ReviewMarketSummary | null): string;
|
|
47
|
+
export {};
|