# 投资收益记录系统设计文档 ## 一、需求概述 ### 1.1 核心目标 开发一个自动化的投资收益记录应用,**用户只需直接修改持仓的成本价和份额**,系统自动更新市场价格、计算收益和收益率,无需用户定期手动更新资产金额。 **设计原则:** - **被动变更(分红、拆股、送股)**:系统自动完成 - **主动变更(买入、卖出、追加)**:用户直接修改成本价和份数,无需记录每次交易细节 - **适用场景**:多券商用户汇总统计,不是替代券商系统 ### 1.2 支持的资产类型 - **股票**:支持A股、港股、美股等 - **基金**:支持各类基金产品 - **现金**:证券账户留存现金、分红现金等 ### 1.3 核心功能 - 自动更新市场价格(每日收盘后) - 自动计算总资产、总收益 - 自动计算累计收益率、年化收益率、当年收益率 - 支持多种交易场景处理 - 使用基金净值法和资金加权法计算收益率(取两者低者) --- ## 二、设计分析与优化 ### 2.1 用户提出的设计分析 #### ✅ 正确的设计点 1. **拆股处理** - 自动变更股份数和成本价 - **实现方式**:股份数 = 原股份数 × 拆股比例,成本价 = 原成本价 ÷ 拆股比例 - **示例**:10股拆成20股(1:2),成本价从100元变为50元 2. **分红处理** - 股票成本价减去每股分红得到最新成本价 - **实现方式**:新成本价 = 原成本价 - 每股分红金额 - **注意**:分红需要同时记录到现金账户 3. **买入/卖出处理** - 变更持仓份额和成本价 - **实现方式**: - **买入**:新成本价 = (原成本价 × 原份额 + 买入价 × 买入份额) / (原份额 + 买入份额) - **卖出**:只减少份额,成本价不变(先进先出或加权平均) 4. **提现处理** - 变更现金账户金额 - **实现方式**:现金余额 = 原余额 - 提现金额 #### ⚠️ 需要补充和优化的点 1. **分红处理需要完善** - 需要区分现金分红和股票分红(送股) - 现金分红:成本价降低,现金账户增加 - 股票分红(送股):股份数增加,成本价降低 - **公式**:新成本价 = (原成本价 × 原股份数) / (原股份数 + 送股数) 2. **买入/卖出成本价计算** - 需要考虑交易费用(佣金、印花税等) - **买入成本价** = (买入价 × 买入份额 + 交易费用) / 买入份额 - **卖出**:需要计算已实现收益,并更新持仓成本价 3. **基金的特殊处理** - 基金分红通常有现金分红和红利再投资两种方式 - 需要支持基金份额的自动调整 --- ## 三、遗漏场景分析 ### 3.1 必须处理的场景 1. **送股(股票分红)** - 场景:公司送股,如10送5 - 处理:股份数增加,成本价降低 - 公式:新股份数 = 原股份数 × (1 + 送股比例),新成本价 = 原成本价 / (1 + 送股比例) 2. **配股** - 场景:公司配股,需要用户决定是否参与 - 处理:如果参与,需要记录配股价格和数量,重新计算成本价 3. **转增股本** - 场景:资本公积转增股本 - 处理:类似送股,股份数增加,成本价降低 4. **股票合并(并股)** - 场景:如10股合并为1股 - 处理:股份数减少,成本价提高 - 公式:新股份数 = 原股份数 / 合并比例,新成本价 = 原成本价 × 合并比例 5. **现金分红再投资** - 场景:用户选择将分红现金再次买入股票 - 处理:需要记录一笔买入交易 6. **基金分红再投资** - 场景:基金分红选择红利再投资 - 处理:基金份额增加,成本价不变 7. **股票退市/摘牌** - 场景:股票退市,无法获取市场价格 - 处理:需要标记为退市状态,使用最后已知价格或用户手动设置 8. **股票停牌** - 场景:股票长期停牌 - 处理:使用停牌前最后价格,标记停牌状态 9. **汇率变动(港股、美股)** - 场景:持有港股、美股,汇率变动影响资产价值 - 处理:需要记录买入时的汇率,计算时使用当前汇率 10. **交易费用** - 场景:买入/卖出都有交易费用 - 处理:买入时计入成本,卖出时从收益中扣除 11. **现金账户利息** - 场景:证券账户现金可能产生利息 - 处理:定期(如每月)更新现金余额,增加利息收入 12. **资产转移** - 场景:从一个账户转移到另一个账户 - 处理:需要支持多账户管理 ### 3.2 可选处理的场景 1. **股票期权/权证** 2. **可转债** 3. **分级基金** 4. **ETF套利** --- ## 四、收益率计算方法 ### 4.1 基金净值法(时间加权收益率) **原理**:消除资金流入流出的影响,只反映投资能力。 **计算公式**: ``` 收益率 = (期末净值 - 期初净值) / 期初净值 ``` **每日净值计算**: ``` 当日净值 = (总资产价值) / (累计投入资金) ``` **累计净值**: ``` 累计净值 = 1 × (1 + r1) × (1 + r2) × ... × (1 + rn) ``` ### 4.2 资金加权法(内部收益率 IRR) **原理**:考虑资金流入流出的时间点,计算真实的投资回报率。 **计算公式**: ``` NPV = Σ(CFt / (1 + IRR)^t) = 0 ``` 其中: - CFt:第t期的现金流(正数表示投入,负数表示提取) - IRR:内部收益率 **实现方式**: - 使用迭代法(如牛顿法)求解IRR - 或使用Excel的XIRR函数(考虑具体日期) ### 4.3 取两者低者的实现 **策略**:保守计算,取两种方法中较低的收益率。 **实现逻辑**: ```javascript const timeWeightedReturn = calculateTimeWeightedReturn(); const moneyWeightedReturn = calculateIRR(); const finalReturn = Math.min(timeWeightedReturn, moneyWeightedReturn); ``` ### 4.4 其他收益率指标 1. **累计收益率** ``` 累计收益率 = (当前总资产 - 累计投入资金) / 累计投入资金 ``` 2. **年化收益率** ``` 年化收益率 = (1 + 累计收益率)^(365 / 投资天数) - 1 ``` 3. **当年收益率** ``` 当年收益率 = (当前总资产 - 年初总资产) / 年初总资产 ``` --- ## 五、数据模型设计 ### 5.1 资产账户(Account) ```typescript interface Account { id: string; // 账户ID name: string; // 账户名称 type: 'stock' | 'fund' | 'cash'; // 账户类型 currency: string; // 货币类型(CNY/USD/HKD) createdAt: Date; // 创建时间 updatedAt: Date; // 更新时间 } ``` ### 5.2 持仓(Position) ```typescript interface Position { id: string; // 持仓ID accountId: string; // 所属账户ID symbol: string; // 股票/基金代码 name: string; // 股票/基金名称 shares: number; // 持仓份额 costPrice: number; // 成本价(每股/每份) currentPrice: number; // 当前价格 market: string; // 市场(A股/港股/美股) currency: string; // 货币类型 status: 'active' | 'suspended' | 'delisted'; // 状态 createdAt: Date; updatedAt: Date; } ``` ### 5.3 持仓变更记录(PositionChange) ```typescript interface PositionChange { id: string; // 变更ID positionId: string; // 持仓ID changeDate: Date; // 变更日期 changeType: 'manual' | 'buy' | 'sell' | 'auto'; // 变更类型 beforeShares: number; // 变更前份数 beforeCostPrice: number; // 变更前成本价 afterShares: number; // 变更后份数 afterCostPrice: number; // 变更后成本价 notes?: string; // 备注/思考 createdAt: Date; } ``` ### 5.3.1 资金变动记录(CashFlow)- 用于计算 IRR ```typescript interface CashFlow { id: string; // 资金流ID accountId: string; // 账户ID flowDate: Date; // 资金变动日期 flowType: 'deposit' | 'withdraw' | 'dividend' | 'interest'; // 类型 amount: number; // 金额(正数表示投入,负数表示提取) currency: string; // 货币类型 notes?: string; // 备注 createdAt: Date; } ``` **说明:** - 不再需要详细的交易记录表 - 持仓变更记录只记录成本价和份数的变化,不记录交易细节 - 资金变动记录用于计算 IRR,用户只需记录投入/提取的时间和金额 ### 5.4 现金账户(CashAccount) ```typescript interface CashAccount { id: string; // 现金账户ID accountId: string; // 所属账户ID balance: number; // 余额 currency: string; // 货币类型 interestRate?: number; // 利率(年化) updatedAt: Date; } ``` ### 5.5 每日资产快照(DailySnapshot) ```typescript interface DailySnapshot { id: string; // 快照ID date: Date; // 日期 totalAsset: number; // 总资产 totalCost: number; // 总成本(累计投入) totalProfit: number; // 总收益 netValue: number; // 单位净值(总资产/总成本) timeWeightedReturn: number; // 时间加权收益率 moneyWeightedReturn: number; // 资金加权收益率(基于资金流计算) finalReturn: number; // 最终收益率(取两者低者) annualizedReturn: number; // 年化收益率 yearToDateReturn: number; // 当年收益率 positions: Position[]; // 持仓明细(JSON格式) cashAccounts: CashAccount[]; // 现金账户明细(JSON格式) } ``` **说明:** - 系统每日自动生成资产快照 - 用于计算时间加权收益率(基金净值法) - 不依赖交易记录,只依赖持仓和资产快照 --- ## 六、核心算法设计 ### 6.1 成本价计算算法 #### 买入后成本价计算 ```javascript function calculateCostAfterBuy(originalShares, originalCost, buyShares, buyPrice, fee) { const totalCost = (originalShares * originalCost) + (buyShares * buyPrice) + fee; const totalShares = originalShares + buyShares; return totalCost / totalShares; } ``` #### 卖出后成本价计算 ```javascript // 卖出不改变成本价,只减少份额 function calculateCostAfterSell(originalShares, originalCost, sellShares) { // 成本价不变 return originalCost; } ``` #### 拆股后成本价计算 ```javascript function calculateCostAfterSplit(originalShares, originalCost, splitRatio) { // splitRatio: 如 1:2 拆股,splitRatio = 2 const newShares = originalShares * splitRatio; const newCost = originalCost / splitRatio; return { newShares, newCost }; } ``` #### 分红后成本价计算 ```javascript function calculateCostAfterDividend(originalCost, dividendPerShare) { // 现金分红:成本价降低 return originalCost - dividendPerShare; } ``` #### 送股后成本价计算 ```javascript function calculateCostAfterBonus(originalShares, originalCost, bonusRatio) { // bonusRatio: 如 10送5,bonusRatio = 0.5 const newShares = originalShares * (1 + bonusRatio); const newCost = originalCost / (1 + bonusRatio); return { newShares, newCost }; } ``` ### 6.2 收益率计算算法 #### 时间加权收益率(基金净值法) ```javascript function calculateTimeWeightedReturn(snapshots) { let cumulativeReturn = 1; for (let i = 1; i < snapshots.length; i++) { const prevSnapshot = snapshots[i - 1]; const currSnapshot = snapshots[i]; // 计算期间收益率 const periodReturn = (currSnapshot.totalAsset - prevSnapshot.totalAsset) / prevSnapshot.totalAsset; cumulativeReturn *= (1 + periodReturn); } return cumulativeReturn - 1; } ``` #### 资金加权收益率(IRR) ```javascript function calculateIRR(cashFlows) { // cashFlows: 资金变动记录数组 // 正数:投入资金(deposit) // 负数:提取资金(withdraw) // 最后一条:当前资产价值(负数,表示"提取") // 构建现金流数组 const flows = cashFlows.map(cf => ({ date: cf.flowDate, amount: cf.amount })); // 添加当前资产价值(作为最后一笔现金流) const currentAsset = calculateTotalAsset(); flows.push({ date: new Date(), amount: -currentAsset // 负数表示"提取" }); function npv(rate) { let sum = 0; const baseDate = flows[0].date; for (let i = 0; i < flows.length; i++) { const days = (flows[i].date - baseDate) / (1000 * 60 * 60 * 24); sum += flows[i].amount / Math.pow(1 + rate, days / 365); } return sum; } // 使用二分法求解 let low = -0.99; let high = 10; let mid; for (let i = 0; i < 100; i++) { mid = (low + high) / 2; const npvValue = npv(mid); if (Math.abs(npvValue) < 0.0001) { return mid; } if (npvValue > 0) { low = mid; } else { high = mid; } } return mid; } ``` **说明:** - 基于资金变动记录(CashFlow)计算,不是基于交易记录 - 用户只需记录:什么时候投入多少钱、什么时候提取多少钱 - 比记录每笔交易简单很多 #### 年化收益率计算 ```javascript function calculateAnnualizedReturn(totalReturn, days) { return Math.pow(1 + totalReturn, 365 / days) - 1; } ``` ### 6.3 每日资产更新流程 ```javascript async function updateDailyAssets(date) { // 1. 获取所有持仓 const positions = await getActivePositions(); // 2. 更新每个持仓的市场价格 for (const position of positions) { const currentPrice = await fetchMarketPrice(position.symbol, position.market, date); position.currentPrice = currentPrice; position.updatedAt = date; } // 3. 计算总资产 const totalAsset = calculateTotalAsset(positions, cashAccounts); // 4. 计算总成本 const totalCost = calculateTotalCost(positions, cashAccounts); // 5. 计算总收益 const totalProfit = totalAsset - totalCost; // 6. 计算收益率 const timeWeightedReturn = calculateTimeWeightedReturn(snapshots); const moneyWeightedReturn = calculateIRR(cashFlows, dates); const finalReturn = Math.min(timeWeightedReturn, moneyWeightedReturn); // 7. 保存资产快照 await saveAssetSnapshot({ date, totalAsset, totalCost, totalProfit, timeWeightedReturn, moneyWeightedReturn, finalReturn, positions, cashAccounts }); } ``` --- ## 七、交易场景处理流程 ### 7.1 买入/卖出股票/基金(简化操作) **新设计:用户直接修改持仓** ``` 用户操作: 1. 打开持仓列表,找到对应股票 2. 点击"编辑"或"调整" 3. 直接修改: - 成本价(如:从 100 改为 95,表示加仓后新的加权成本价) - 份数(如:从 100 股改为 150 股) 4. 可选:添加备注/思考(如:"2024年1月加仓") 5. 保存 系统处理: 1. 记录变更前状态(用于变更历史) 2. 更新持仓(成本价、份数) 3. 记录持仓变更记录(简化版,不记录交易细节) 4. 更新总资产和收益率 5. 生成每日资产快照(用于收益率计算) ``` **优势:** - ✅ 操作简单,只需修改两个数字 - ✅ 适合多券商用户快速汇总 - ✅ 用户可以根据券商系统的成本价直接输入 - ✅ 不需要记录每笔交易的费用、时间等细节 **注意事项:** - 用户需要自己计算加权平均成本价(或使用系统提供的计算器) - 系统会记录变更历史,但不会记录详细的交易信息 ### 7.3 拆股处理 ``` 1. 系统检测或用户输入:拆股比例(如 1:2) 2. 系统处理: - 自动更新股份数 = 原股份数 × 拆股比例 - 自动更新成本价 = 原成本价 ÷ 拆股比例 - 记录拆股交易记录 3. 更新总资产(总价值不变) ``` ### 7.4 现金分红处理 ``` 1. 系统检测或用户输入:每股分红金额 2. 系统处理: - 计算分红总额 = 每股分红 × 持仓份额 - 更新成本价 = 原成本价 - 每股分红 - 增加现金账户余额(分红总额) - 记录分红交易记录 3. 更新总资产和收益率 ``` ### 7.5 送股处理 ``` 1. 系统检测或用户输入:送股比例(如 10送5,比例为0.5) 2. 系统处理: - 更新股份数 = 原股份数 × (1 + 送股比例) - 更新成本价 = 原成本价 ÷ (1 + 送股比例) - 记录送股交易记录 3. 更新总资产(总价值不变) ``` ### 7.6 提现处理 ``` 1. 用户输入:提现金额 2. 系统处理: - 检查现金账户余额是否足够 - 减少现金账户余额 - 记录提现交易记录 3. 更新总资产 ``` --- ## 八、系统架构设计 ### 8.1 技术栈建议 **前端**: - React / Vue.js - TypeScript - 状态管理:Redux / Zustand - UI框架:Ant Design / Material-UI **后端**: - Node.js / Python - 数据库:PostgreSQL / MySQL - 缓存:Redis **数据获取**: - 股票价格API:腾讯财经、新浪财经、Yahoo Finance - 基金净值API:天天基金、晨星 ### 8.2 模块划分 1. **资产管理模块** - 账户管理 - 持仓管理 - 现金账户管理 2. **交易处理模块** - 买入/卖出处理 - 分红处理 - 拆股/送股处理 - 交易记录管理 3. **价格更新模块** - 定时任务(每日收盘后更新) - 价格数据获取 - 价格数据缓存 4. **收益计算模块** - 时间加权收益率计算 - 资金加权收益率计算 - 年化收益率计算 - 资产快照生成 5. **数据统计模块** - 总资产统计 - 收益统计 - 收益率统计 - 持仓分析 ### 8.3 数据存储设计 **表结构**: - accounts(账户表) - positions(持仓表) - transactions(交易记录表) - cash_accounts(现金账户表) - asset_snapshots(资产快照表) - market_prices(市场价格表,用于缓存) --- ## 九、实现建议 ### 9.1 开发优先级 **第一阶段(MVP)**: 1. 基本的资产账户管理 2. 持仓管理(买入/卖出) 3. 每日价格更新 4. 基本的收益计算(累计收益率) **第二阶段**: 1. 拆股、分红、送股处理 2. 时间加权收益率和资金加权收益率计算 3. 年化收益率计算 4. 资产快照功能 **第三阶段**: 1. 多账户管理 2. 多货币支持 3. 高级统计和分析 4. 数据导出功能 ### 9.2 注意事项 1. **数据准确性** - 价格数据需要可靠的数据源 - 交易记录需要完整保存,不可修改 - 成本价计算需要精确 2. **性能优化** - 价格数据缓存 - 资产快照定期生成,避免实时计算 - 收益率计算优化(IRR计算可能较慢) 3. **用户体验** - 交易记录操作简单 - 自动处理拆股、分红等场景 - 清晰的收益展示 4. **数据备份** - 定期备份交易记录 - 支持数据导出 --- ## 十、总结 ### 10.1 设计要点 1. ✅ **用户只需直接修改持仓**:买入/卖出时直接修改成本价和份数,无需记录交易细节 2. ✅ **自动处理特殊事件**:拆股、分红、送股等被动变更由系统自动处理 3. ✅ **自动更新市场价格**:每日收盘后自动更新,无需用户手动输入 4. ✅ **简化操作流程**:适合多券商用户快速汇总统计,不是替代券商系统 5. ✅ **保守的收益率计算**:使用时间加权(基于资产快照)和资金加权(基于资金流)两种方法,取较低者 6. ✅ **完整的收益统计**:累计收益率、年化收益率、当年收益率等 7. ✅ **支持计划和复盘**:通过持仓思考记录实现,关联持仓而非交易 ### 10.2 需要补充的场景 1. 送股(股票分红) 2. 配股 3. 转增股本 4. 股票合并 5. 汇率变动(港股、美股) 6. 交易费用处理 7. 现金账户利息 8. 股票停牌/退市处理 ### 10.3 实现建议 - 采用模块化设计,便于扩展 - 持仓变更记录不可修改,保证数据可追溯性 - 每日自动生成资产快照,用于收益率计算 - 提供成本价计算器工具,帮助用户计算加权平均成本价 - 支持批量导入功能,可以从券商系统导出后导入 - 提供清晰的数据展示和统计 - 持仓思考记录支持计划和复盘功能 ### 10.4 数据模型总结 **核心表:** - `positions` - 持仓表(用户直接修改成本价和份数) - `position_changes` - 持仓变更记录(系统自动记录变更历史) - `cash_flows` - 资金变动记录(用户记录投入/提取,用于 IRR 计算) - `daily_snapshots` - 每日资产快照(系统自动生成,用于时间加权收益率) - `position_thoughts` - 持仓思考记录(用户记录思考,用于计划和复盘) **不再需要:** - ~~详细的交易记录表~~(改为简化的持仓变更记录) --- **文档版本**:v1.0 **创建日期**:2024年 **最后更新**:2024年