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.
@@ -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) => ({ recordId: update.recordId, fields: { 最新价格: update.price } })));
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) {
@@ -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=1`;
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 row = payload.Data?.LSJZList?.[0];
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
  };
@@ -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.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
+ }