# 个人投资持仓管理系统 - 需求规格说明书 > 文档版本:v1.0.0 > 日期:2026-04-12 > 状态:已完成 --- ## 1. 产品概述 ### 1.1 产品名称 个人投资持仓管理系统(Stock Portfolio Manager) ### 1.2 产品简介 现代化、全面化的个人投资组合管理平台,支持多市场(美股、A股、港股、加密货币)统一管理,实时获取行情数据,自动计算持仓成本与盈亏。 ### 1.3 目标用户 - 拥有多个市场投资账户的个人投资者 - 需要统一管理分散在不同平台的投资组合 - 希望实时了解总体资产配置和盈亏状况 --- ## 2. 技术架构 ### 2.1 技术栈 | 层级 | 技术 | 版本 | |------|------|------| | 前端框架 | Next.js | 16 | | UI 框架 | React | 19 | | 类型系统 | TypeScript | 5 | | 样式方案 | Tailwind CSS | 4 | | 组件库 | shadcn/ui | - | | 图表库 | Recharts | - | | 后端框架 | Next.js API Routes | - | | ORM | Prisma | - | | 数据库 | PostgreSQL | 16 | ### 2.2 系统架构 ``` ┌─────────────────────────────────────────────────────┐ │ 前端 (Next.js) │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │ Dashboard │ │ 持仓明细 │ │ 交易流水 │ │ 分析 │ │ │ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ │ └───────┼────────────┼────────────┼────────────┼───────┘ │ │ │ │ └────────────┴─────┬──────┴────────────┘ │ REST API ┌──────────────────┼──────────────────┐ │ │ │ ┌────▼────┐ ┌──────▼──────┐ ┌──────▼──────┐ │ Accounts │ │ Transactions │ │ Positions │ │ API │ │ API │ │ API │ └────┬────┘ └──────┬──────┘ └──────┬──────┘ │ │ │ ┌────▼──────────────────▼──────────────────▼────┐ │ Prisma ORM │ └────────────────────┬─────────────────────────┘ │ ┌────────────────────▼─────────────────────────┐ │ PostgreSQL Database │ │ User │ Account │ Security │ Transaction │ │ │ Position │ ExchangeRate │ └──────────────────────────────────────────────┘ ``` ### 2.3 目录结构 ``` stock-portfolio/ ├── prisma/ │ ├── schema.prisma # 数据模型定义 │ └── seed.ts # 初始化数据 ├── src/ │ ├── app/ │ │ ├── page.tsx # 主页面(Dashboard) │ │ └── api/ # API 路由 │ │ ├── accounts/ │ │ ├── transactions/ │ │ ├── positions/ │ │ ├── securities/ │ │ ├── exchange-rates/ │ │ ├── dashboard/ │ │ │ ├── stats/ │ │ │ └── analytics/ │ │ └── import/ │ ├── components/ # UI 组件 │ ├── lib/ │ │ ├── api.ts # API 调用封装 │ │ ├── prisma.ts # Prisma 客户端 │ │ └── import-export.ts │ └── types/ # TypeScript 类型定义 ├── .env # 环境变量 └── README.md ``` --- ## 3. 功能规格 ### 3.1 市场与账户 #### 3.1.1 支持的市场 | 市场代码 | 市场名称 | 结算货币 | 示例证券 | |----------|----------|----------|----------| | US | 美股 | USD | AAPL, GOOGL, MSFT | | CN | A股 | CNY | 600690, 159235 | | HK | 港股 | HKD | 00700, 09868 | | CRYPTO | 加密货币 | USDT | BTC, ETH | #### 3.1.2 账户管理 - 每个市场对应一个默认账户 - 账户信息包含:名称、市场类型、基准货币、余额 - 创建账户时自动设置对应市场的基准货币 ### 3.2 证券管理 #### 3.2.1 证券数据结构 | 字段 | 类型 | 说明 | |------|------|------| | symbol | String | 证券代码(唯一) | | name | String | 证券名称 | | market | MarketType | 市场类型 | | currency | String | 结算货币 | | lotSize | Int | 每手股数(港股=100,A股=100,美股=1) | | priceDecimals | Int | 价格精度 | | qtyDecimals | Int | 数量精度(股票=0,数字货币=8) | | isCrypto | Boolean | 是否为加密货币 | #### 3.2.2 预置证券 | 代码 | 名称 | 市场 | 货币 | |------|------|------|------| | 00700 | 腾讯控股 | HK | HKD | | 09868 | 小鹏汽车 | HK | HKD | | 09988 | 阿里巴巴 | HK | HKD | | AAPL | Apple Inc. | US | USD | | MSFT | Microsoft Corp. | US | USD | | NVDA | NVIDIA Corp. | US | USD | | GOOGL | Alphabet Inc. | US | USD | | INTC | Intel Corp. | US | USD | | 600690 | 海尔智家 | CN | CNY | | 159235 | 中证现金流ETF | CN | CNY | | BTC | Bitcoin | CRYPTO | USDT | | ETH | Ethereum | CRYPTO | USDT | ### 3.3 交易类型 | 类型代码 | 中文名称 | 说明 | 账户影响 | 持仓影响 | |----------|----------|------|----------|----------| | BUY | 买入 | 购买证券 | 扣减余额 | 增加持仓 | | SELL | 卖出 | 卖出证券 | 增加余额 | 减少持仓 | | DEPOSIT | 入金 | 资金转入 | 增加余额 | 无 | | WITHDRAW | 出金 | 资金转出 | 扣减余额 | 无 | | DIVIDEND | 分红 | 现金分红 | 增加余额 | 无 | | INTEREST | 利息 | 利息收入 | 增加余额 | 无 | | FEE | 费用 | 手续费支出 | 扣减余额 | 无 | ### 3.4 行情数据 #### 3.4.1 数据源 使用腾讯行情接口获取实时价格: - 接口地址:`https://qt.gtimg.cn/q=` - 支持批量查询,用逗号分隔 #### 3.4.2 代码格式转换 用户输入证券代码时,系统自动转换为腾讯接口格式: | 市场 | 用户输入 | 腾讯接口格式 | |------|----------|--------------| | 港股 | 09868 | r_hk09868 | | A股上海 | sh600690 或 600690 | sh600690 | | A股深圳 | sz159235 或 159235 | sz159235 | | 美股 | GOOGL | s_usGOOGL | #### 3.4.3 解析字段(多市场差异化索引) 腾讯行情各市场返回字段索引不同,必须根据市场前缀分别解析: | 市场 | 前缀 | 名称索引 | 当前价索引 | 涨跌额索引 | 涨跌幅索引 | |------|------|---------|-----------|-----------|-----------| | 美股 | `s_us` | 1 | 3 | **4** | **5** | | 港股 | `r_hk` | 1 | 3 | **31** | **32** | | A股(上海) | `sh` | 1 | 3 | **31** | **32** | | A股(深圳) | `sz` | 1 | 3 | **31** | **32** | **返回数据示例:** ``` # 美股 (s_us) - 涨跌额在索引4,涨跌幅在索引5 v_s_usGOOG="200~Alphabet-C~GOOG.OQ~315.72~-0.65~-0.21~..." # 港股 (r_hk) - 涨跌额在索引31,涨跌幅在索引32 v_r_hk09868="100~小鹏集团-W~09868~67.600~...~2026/04/13 16:08:56~0.600~0.90~..." # A股 (sh) - 涨跌额在索引31,涨跌幅在索引32 v_sh600690="1~海尔智家~600690~...~20260413161426~-0.14~-0.67~..." ``` **异常处理策略:** 1. 当 Security 表中存在证券记录时,优先使用 Security 表中的名称 2. 当 Security 表中无记录时,使用腾讯行情返回的名称(索引1) 3. 当前价获取失败时,降级使用持仓的平均成本价(avgCost) 4. 涨跌数据获取失败时,降级通过 `当前价 - 昨收价` 计算 5. **GBK 中文解码**:腾讯 API 返回 GBK 编码数据,必须使用 `arrayBuffer() + TextDecoder('gbk')` 解码,禁止直接使用 `text()` ### 3.5 持仓计算 #### 3.5.1 成本计算 - 平均成本法 ``` 平均成本 = (Σ买入金额) / 总数量 = (Q1×P1 + Q2×P2 + ...) / (Q1 + Q2 + ...) ``` #### 3.5.2 市值计算 ``` 市值 = 持仓数量 × 当前价格 ``` #### 3.5.3 盈亏计算 ``` 浮动盈亏 = 市值 - 成本基数 = (数量 × 当前价) - (数量 × 平均成本) 浮动盈亏率 = (浮动盈亏 / 成本基数) × 100% ``` #### 3.5.4 货币转换 系统接入 **JisuAPI** (`https://api.jisuapi.com/exchange/convert`) 获取实时汇率。 **汇率获取策略:** 1. 优先使用 JisuAPI 实时汇率 2. 缓存周期:1 小时(防止 API 调用频率限制) 3. 降级机制:JisuAPI 失败时使用默认固定汇率 **默认固定汇率(降级用):** | 从货币 | 到 USD 汇率 | |--------|-------------| | USD | 1 | | CNY | 0.137 | | HKD | 0.129 | | USDT | 1 | **汇率转换公式:** ``` 目标货币金额 = 源货币金额 × 汇率 示例:1000 CNY × 0.137 = 137 USD ``` ### 3.6 货币显示 #### 3.6.1 显示货币选择 顶部导航栏提供显示货币切换器: - CNY(人民币) - USD(美元) - HKD(港币) #### 3.6.2 持仓显示规则 持仓明细和分析页面根据市场显示对应货币: - 港股持仓 → HKD - A股持仓 → CNY - 美股持仓 → USD - 加密货币持仓 → USDT #### 3.6.3 总资产显示 总资产、总盈亏等汇总数据根据用户选择的显示货币进行转换: ``` 显示金额 = USD金额 × 显示货币汇率 ``` **资产配置环形图联动:** - 环形图的市值数值、Tooltip 提示金额、图例金额均随全局显示货币(CNY/USD/HKD)实时联动转换 - 使用 `useMemo` 缓存计算结果,避免重复渲染 ### 3.7 数据导入导出 #### 3.7.1 CSV 导出 支持导出: - 交易记录(transactions) - 持仓明细(positions) 导出字段: - 交易记录:时间、类型、证券代码、数量、价格、金额、手续费、备注 - 持仓明细:证券代码、名称、市场、数量、成本价、当前价、市值、盈亏 #### 3.7.2 CSV 导入 导入交易记录: - 支持批量导入 - 自动验证数据格式 - 按账户批量创建交易 导入模板包含字段: - type, symbol, quantity, price, amount, fee, currency, notes, executedAt --- ## 4. 页面与界面 ### 4.1 顶部导航栏 ``` ┌─────────────────────────────────────────────────────────────────┐ │ [💰] 投资持仓管理 [CNY▼] [账户▼] [导入] [导出] [+记录交易] │ └─────────────────────────────────────────────────────────────────┘ ``` 功能: - Logo 和标题 - 显示货币选择(下拉) - 账户选择(下拉) - 导入按钮 - 导出按钮 - 记录交易按钮 ### 4.2 资产概览卡片 四个卡片并排显示: 1. **总资产** - 渐变蓝背景,显示总市值和成本 2. **浮动盈亏** - 渐变绿背景,显示盈亏金额和百分比 3. **持仓市值** - 渐变紫背景,显示持仓数量和总市值 4. **账户数量** - 渐变橙背景,显示市场数量 ### 4.3 持仓分析卡片 展示前4个持仓的概览: - 证券代码、图标和**证券名称** - 市值(使用对应市场货币) - 盈亏金额和百分比 ### 4.3.1 证券名称显示 在以下位置均显示证券代码和对应名称: | 位置 | 显示内容 | |------|----------| | 持仓分析卡片 | 代码 + 名称(truncate) | | 资产分布条形图 | 代码 + 名称(truncate) | | 盈亏排行榜 | 代码 + 名称(truncate,最大宽度120px) | 证券名称从数据库 Security 表中获取,与腾讯行情接口解析的实时价格配合使用。 ### 4.4 标签页 #### 4.4.1 持仓明细标签页 表格列:证券 | 市场 | 数量 | 成本价 | 当前价 | 市值 | 盈亏 | 操作 功能: - 按市场货币显示价格和市值 - 显示涨跌百分比 - 支持删除持仓(卖出全部) #### 4.4.2 交易流水标签页 表格列:时间 | 类型 | 证券(名称+代码) | 数量 | 价格 | 金额 | 账户 | 操作 **证券列显示:** - 上行:证券名称(加粗) - 下行:证券代码(灰色小字) 功能: - 支持编辑交易记录 - 支持删除交易记录 - 支持导出 CSV - 后端通过关联查询附加证券名称 #### 4.4.3 分析标签页 包含两个卡片: 1. **资产配置环形图** - 由后端实时聚合持仓市值数据,按市场(美股/港股/A股/加密)分组显示占比 2. **盈亏排行** - 列表显示按盈亏排序的持仓 **资产配置数据来源:** - 后端 API `/api/dashboard/analytics` 返回 `marketDistribution` 字段 - 数据结构:`{ name: string, value: number, percent: number }` - 使用 Recharts `` 渲染环形图 ### 4.5 对话框 #### 4.5.1 记录交易对话框 字段: - 账户选择 - 交易类型(买入/卖出/入金/出金/分红) - 证券代码搜索(自动补全) - 数量(买入/卖出时显示) - 价格(买入/卖出时显示) - 成交总额(自动计算) - 手续费 - 交易时间 - 备注 - 确认按钮 #### 4.5.2 交易确认对话框 显示交易摘要: - 账户信息 - 交易类型和证券 - 成交总额 - 手续费 - 交易时间 #### 4.5.3 编辑交易对话框 与记录交易类似,但预填充现有数据。 #### 4.5.4 删除确认对话框 显示待删除项目详情,确认后执行删除。 --- ## 5. API 规格 ### 5.1 账户 API #### GET /api/accounts 获取所有账户列表 Response: ```json { "id": "string", "name": "string", "marketType": "US|CN|HK|CRYPTO", "baseCurrency": "string", "balance": "number" } ``` ### 5.2 持仓 API #### GET /api/positions 获取持仓列表 Query: `?accountId=xxx` Response: ```json { "symbol": "string", "quantity": "string", "averageCost": "string", "currency": "string", "accountName": "string", "marketType": "string" } ``` ### 5.3 证券 API #### GET /api/securities 获取证券列表 Query: `?market=US&search=AAPL` #### POST /api/securities 创建证券 Body: ```json { "symbol": "string", "name": "string", "market": "US|CN|HK|CRYPTO", "currency": "string", "lotSize": 100, "priceDecimals": 2, "qtyDecimals": 0, "isCrypto": false } ``` ### 5.4 交易 API #### GET /api/transactions 获取交易流水 Query: `?accountId=xxx&page=1&limit=20` Response: ```json { "data": [...], "pagination": { "page": 1, "limit": 20, "total": 100, "totalPages": 5 } } ``` #### POST /api/transactions 创建交易 Body: ```json { "accountId": "string", "type": "BUY|SELL|DEPOSIT|WITHDRAW|DIVIDEND", "symbol": "string|null", "quantity": "number|null", "price": "number|null", "amount": "number", "fee": "number", "currency": "string", "notes": "string|null", "executedAt": "ISO8601" } ``` #### PATCH /api/transactions 更新交易 Body: ```json { "id": "string", "type": "string", "symbol": "string|null", "quantity": "number|null", "price": "number|null", "amount": "number|null", "fee": "number|null", "currency": "string|null", "notes": "string|null", "executedAt": "string|null" } ``` #### DELETE /api/transactions?id=xxx 删除交易 ### 5.5 行情 API #### GET /api/dashboard/analytics 获取持仓分析和汇总 Response: ```json { "prices": { "AAPL": { "price": 150.00, "change": 2.50, "changePercent": 1.69 } }, "positions": [ { "symbol": "AAPL", "name": "Apple Inc.", "marketType": "US", "quantity": 10, "avgCost": 145.00, "currentPrice": 150.00, "change": 2.50, "changePercent": 1.69, "costBasis": 1450.00, "costBasisUSD": 1450.00, "marketValue": 1500.00, "marketValueUSD": 1500.00, "pnl": 50.00, "pnlPercent": 3.45, "pnlUSD": 50.00, "currency": "USD" } ], "summary": { "totalCostBasis": 10000.00, "totalMarketValue": 10500.00, "totalPnL": 500.00, "totalPnLPercent": 5.00, "positionCount": 3 }, "byMarket": { "US": { "totalCost": 5000, "totalValue": 5500, "totalPnL": 500 }, "CN": { "totalCost": 3000, "totalValue": 3000, "totalPnL": 0 } } } ``` --- ## 6. 数据库模型 ### 6.1 User(用户) ```prisma model User { id String @id @default(cuid()) email String @unique name String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt accounts Account[] } ``` ### 6.2 Account(账户) ```prisma model Account { id String @id @default(cuid()) userId String user User @relation(...) name String marketType MarketType baseCurrency String balance Decimal @default(0) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt transactions Transaction[] positions Position[] } enum MarketType { US // 美股 CN // A股 HK // 港股 CRYPTO // 加密货币 } ``` ### 6.3 Security(证券) ```prisma model Security { id String @id @default(cuid()) symbol String @unique name String market MarketType currency String lotSize Int @default(1) priceDecimals Int @default(2) qtyDecimals Int @default(0) isCrypto Boolean @default(false) } ``` ### 6.4 Transaction(交易流水) ```prisma model Transaction { id String @id @default(cuid()) accountId String account Account @relation(...) type TransactionType symbol String? quantity Decimal? price Decimal? amount Decimal fee Decimal @default(0) networkFee Decimal? currency String exchangeRate Decimal? notes String? executedAt DateTime } enum TransactionType { DEPOSIT // 入金 WITHDRAW // 出金 BUY // 买入 SELL // 卖出 DIVIDEND // 分红 INTEREST // 利息 FEE // 费用 } ``` ### 6.5 Position(持仓) ```prisma model Position { id String @id @default(cuid()) accountId String account Account @relation(...) symbol String quantity Decimal averageCost Decimal currency String updatedAt DateTime @updatedAt @@unique([accountId, symbol]) } ``` ### 6.6 ExchangeRate(汇率) ```prisma model ExchangeRate { id String @id @default(cuid()) fromCurrency String toCurrency String rate Decimal effectiveDate DateTime @@unique([fromCurrency, toCurrency, effectiveDate]) } ``` --- ## 7. 汇率配置 ### 7.1 初始汇率(以 USD 为基准) | 从货币 | 到 USD 汇率 | |--------|-------------| | USD | 1.000 | | CNY | 0.137 | | HKD | 0.129 | | USDT | 1.000 | ### 7.2 货币转换公式 ``` amountInTargetCurrency = amountInUSD × targetCurrencyRate 示例: 100 USD → CNY = 100 × 7.24 = 724 CNY 100 HKD → USD = 100 × 0.129 = 12.9 USD ``` --- ## 8. 非功能性需求 ### 8.1 性能 - 页面加载时间 < 2秒 - API 响应时间 < 500ms - 股价缓存时间 60 秒 ### 8.2 兼容性 - 支持 Chrome、Firefox、Safari、Edge 最新版本 - 响应式设计,支持桌面和移动端 ### 8.3 数据安全 - 所有敏感配置通过环境变量管理 - 数据库连接使用密码认证 --- ## 9. 版本历史 | 版本 | 日期 | 说明 | |------|------|------| | v1.0.0 | 2026-04-12 | 初始版本,完成核心功能 | --- ## 10. 附录 ### 10.1 环境变量 ```env DATABASE_URL="postgresql://user:password@host:5432/database" ALPHA_VANTAGE_API_KEY="your_api_key" ``` ### 10.2 腾讯行情接口示例 **港股(小鹏汽车):** ``` GET https://qt.gtimg.cn/q=r_hk09868 Response: v_r_hk09868="100~小鹏汽车-W~09868~67.000~66.950~..." ``` **A股(海尔智家):** ``` GET https://qt.gtimg.cn/q=sh600690 Response: v_sh600690="1~海尔智家~600690~20.88~20.75~20.79~..." ``` **美股(Google):** ``` GET https://qt.gtimg.cn/q=s_usGOOG Response: v_s_usGOOG="200~Alphabet-C~GOOG.OQ~315.72~-0.65~-0.21~..." ``` ### 10.3 批量查询示例 ``` GET https://qt.gtimg.cn/q=r_hk09868,sh600690,sz159235,s_usGOOG ```