investdesk-quotes 0.3.0 → 0.5.0
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/dist/feishu/base.js +16 -2
- package/dist/mcp/tools.js +21 -0
- package/dist/quotes/eastmoney.js +5 -2
- package/dist/quotes/index.js +2 -0
- package/dist/sync.js +2 -0
- package/package.json +2 -2
package/dist/feishu/base.js
CHANGED
|
@@ -58,6 +58,9 @@ export async function listHoldings() {
|
|
|
58
58
|
unitCost: toNumber(record.单位成本),
|
|
59
59
|
price: toNumber(record.最新价格),
|
|
60
60
|
marketValue: toNumber(record.最新市值),
|
|
61
|
+
previousPrice: toNumber(record.昨收价),
|
|
62
|
+
changePct: toNumber(record['日涨跌幅(%)']),
|
|
63
|
+
dailyPnl: toNumber(record.日盈亏),
|
|
61
64
|
}));
|
|
62
65
|
}
|
|
63
66
|
/** 读取「最新持仓」并转换为取价目标 */
|
|
@@ -80,9 +83,20 @@ export async function listPlatformNames() {
|
|
|
80
83
|
}
|
|
81
84
|
return map;
|
|
82
85
|
}
|
|
83
|
-
/**
|
|
86
|
+
/**
|
|
87
|
+
* 把行情写回「最新持仓」:
|
|
88
|
+
* 最新价格 / 昨收价 / 日涨跌幅(%)
|
|
89
|
+
* 「最新市值」「日盈亏」是公式字段,写完后自动重算。
|
|
90
|
+
*/
|
|
84
91
|
export async function updateLatestPrices(updates) {
|
|
85
92
|
if (updates.length === 0)
|
|
86
93
|
return 0;
|
|
87
|
-
return getBackend().batchUpdateRecords(HOLDINGS_TABLE, updates.map((update) =>
|
|
94
|
+
return getBackend().batchUpdateRecords(HOLDINGS_TABLE, updates.map((update) => {
|
|
95
|
+
const fields = { 最新价格: update.price };
|
|
96
|
+
if (update.previousPrice != null)
|
|
97
|
+
fields.昨收价 = update.previousPrice;
|
|
98
|
+
if (update.changePct != null)
|
|
99
|
+
fields['日涨跌幅(%)'] = update.changePct;
|
|
100
|
+
return { recordId: update.recordId, fields };
|
|
101
|
+
}));
|
|
88
102
|
}
|
package/dist/mcp/tools.js
CHANGED
|
@@ -128,6 +128,27 @@ async function portfolioOverview(args) {
|
|
|
128
128
|
const totalPnl = totalValue - totalCost;
|
|
129
129
|
const totalRate = totalCost > 0 ? `${((totalPnl / totalCost) * 100).toFixed(2)}%` : '-';
|
|
130
130
|
lines.push(`| **合计** | **${rows.length}** | **${formatNumber(totalValue)}** | **${formatNumber(totalCost)}** | **${formatNumber(totalPnl)}** | **${totalRate}** |`);
|
|
131
|
+
// 今日维度:只有同步过昨收价的记录才有日盈亏
|
|
132
|
+
const withDaily = rows.filter((row) => row.dailyPnl !== null && (row.quantity ?? 0) > 0);
|
|
133
|
+
if (withDaily.length > 0) {
|
|
134
|
+
const dailyTotal = withDaily.reduce((sum, row) => sum + (row.dailyPnl ?? 0), 0);
|
|
135
|
+
const yesterdayValue = totalValue - dailyTotal;
|
|
136
|
+
const dailyRate = yesterdayValue > 0 ? `${((dailyTotal / yesterdayValue) * 100).toFixed(2)}%` : '-';
|
|
137
|
+
lines.push('', '## 今日', '', '| 指标 | 值 |', '|---|---:|');
|
|
138
|
+
lines.push(`| 今日盈亏 | ${formatNumber(dailyTotal)} |`);
|
|
139
|
+
lines.push(`| 今日涨幅 | ${dailyRate} |`);
|
|
140
|
+
lines.push(`| 覆盖标的 | ${withDaily.length} 只有持仓数量 |`);
|
|
141
|
+
const movers = [...withDaily].sort((a, b) => (b.dailyPnl ?? 0) - (a.dailyPnl ?? 0));
|
|
142
|
+
const gainers = movers.filter((row) => (row.dailyPnl ?? 0) > 0).slice(0, 3);
|
|
143
|
+
const losers = movers.filter((row) => (row.dailyPnl ?? 0) < 0).slice(-3).reverse();
|
|
144
|
+
if (gainers.length > 0) {
|
|
145
|
+
lines.push('', '**今日贡献**:' + gainers.map((r) => `${r.code} ${r.name} ${formatNumber(r.dailyPnl ?? 0)}(${r.changePct?.toFixed(2) ?? '-'}%)`).join(';'));
|
|
146
|
+
}
|
|
147
|
+
if (losers.length > 0) {
|
|
148
|
+
lines.push('', '**今日拖累**:' + losers.map((r) => `${r.code} ${r.name} ${formatNumber(r.dailyPnl ?? 0)}(${r.changePct?.toFixed(2) ?? '-'}%)`).join(';'));
|
|
149
|
+
}
|
|
150
|
+
lines.push('', '> 场内为当日收盘价;场外基金为 T 日净值(QDII 更晚),「今日」为混合时点。');
|
|
151
|
+
}
|
|
131
152
|
lines.push('', `## 市值前 ${top} 只`, '', '| 代码 | 名称 | 平台 | 市值 | 盈亏 | 收益率 |', '|---|---|---|---:|---:|---:|');
|
|
132
153
|
const sorted = [...rows].sort((a, b) => b.marketValue - a.marketValue).slice(0, top);
|
|
133
154
|
for (const row of sorted) {
|
package/dist/quotes/eastmoney.js
CHANGED
|
@@ -12,7 +12,7 @@ const DEFAULT_TIMEOUT_MS = 10_000;
|
|
|
12
12
|
* 网络或接口异常则抛错,由调用方重试。
|
|
13
13
|
*/
|
|
14
14
|
export async function fetchFundNav(code, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
15
|
-
const url = `${ENDPOINT}?fundCode=${encodeURIComponent(code)}&pageIndex=1&pageSize=
|
|
15
|
+
const url = `${ENDPOINT}?fundCode=${encodeURIComponent(code)}&pageIndex=1&pageSize=2`;
|
|
16
16
|
const controller = new AbortController();
|
|
17
17
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
18
18
|
try {
|
|
@@ -26,16 +26,19 @@ export async function fetchFundNav(code, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
|
26
26
|
if (!res.ok)
|
|
27
27
|
throw new Error(`东财净值 HTTP ${res.status}`);
|
|
28
28
|
const payload = (await res.json());
|
|
29
|
-
const
|
|
29
|
+
const rows = payload.Data?.LSJZList ?? [];
|
|
30
|
+
const row = rows[0];
|
|
30
31
|
if (!row?.DWJZ || !row.FSRQ)
|
|
31
32
|
return null;
|
|
32
33
|
const nav = Number(row.DWJZ);
|
|
33
34
|
if (!Number.isFinite(nav) || nav <= 0)
|
|
34
35
|
return null;
|
|
35
36
|
const pct = Number(row.JZZZL);
|
|
37
|
+
const prevNav = Number(rows[1]?.DWJZ);
|
|
36
38
|
return {
|
|
37
39
|
code,
|
|
38
40
|
nav,
|
|
41
|
+
previousNav: Number.isFinite(prevNav) && prevNav > 0 ? prevNav : null,
|
|
39
42
|
date: row.FSRQ,
|
|
40
43
|
changePct: Number.isFinite(pct) ? pct : null,
|
|
41
44
|
};
|
package/dist/quotes/index.js
CHANGED
|
@@ -87,6 +87,7 @@ export async function fetchQuotes(targets, options = {}) {
|
|
|
87
87
|
name: target.name || q.name,
|
|
88
88
|
price: q.price,
|
|
89
89
|
changePct: q.changePct,
|
|
90
|
+
previousPrice: Number.isFinite(q.prevClose) && q.prevClose > 0 ? q.prevClose : null,
|
|
90
91
|
tradeTime: q.tradeTime,
|
|
91
92
|
source: 'tencent',
|
|
92
93
|
});
|
|
@@ -125,6 +126,7 @@ export async function fetchQuotes(targets, options = {}) {
|
|
|
125
126
|
name: target.name,
|
|
126
127
|
price: nav.nav,
|
|
127
128
|
changePct: nav.changePct,
|
|
129
|
+
previousPrice: nav.previousNav,
|
|
128
130
|
tradeTime: nav.date,
|
|
129
131
|
source: 'eastmoney-fund',
|
|
130
132
|
});
|
package/dist/sync.js
CHANGED
|
@@ -20,6 +20,8 @@ export async function syncPortfolioPrices(options = {}) {
|
|
|
20
20
|
const updates = [...byRecord.entries()].map(([recordId, quote]) => ({
|
|
21
21
|
recordId,
|
|
22
22
|
price: quote.price,
|
|
23
|
+
previousPrice: quote.previousPrice,
|
|
24
|
+
changePct: quote.changePct,
|
|
23
25
|
}));
|
|
24
26
|
const written = await updateLatestPrices(updates);
|
|
25
27
|
return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "investdesk-quotes",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "A 股与场外公募基金行情查询 + 飞书多维表格持仓读写的 MCP 服务。可装进豆包工作 / Claude Desktop 等支持 MCP 的 AI 宿主。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -43,4 +43,4 @@
|
|
|
43
43
|
"@types/node": "^26.6.5",
|
|
44
44
|
"typescript": "^7.0.2"
|
|
45
45
|
}
|
|
46
|
-
}
|
|
46
|
+
}
|