Files
design-vest-mind/机生文档/投资收益记录系统设计.md
2025-11-11 12:56:49 +08:00

21 KiB
Raw Blame History

投资收益记录系统设计文档

一、需求概述

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 取两者低者的实现

策略:保守计算,取两种方法中较低的收益率。

实现逻辑

const timeWeightedReturn = calculateTimeWeightedReturn();
const moneyWeightedReturn = calculateIRR();
const finalReturn = Math.min(timeWeightedReturn, moneyWeightedReturn);

4.4 其他收益率指标

  1. 累计收益率

    累计收益率 = (当前总资产 - 累计投入资金) / 累计投入资金
    
  2. 年化收益率

    年化收益率 = (1 + 累计收益率)^(365 / 投资天数) - 1
    
  3. 当年收益率

    当年收益率 = (当前总资产 - 年初总资产) / 年初总资产
    

五、数据模型设计

5.1 资产账户Account

interface Account {
  id: string;                    // 账户ID
  name: string;                  // 账户名称
  type: 'stock' | 'fund' | 'cash'; // 账户类型
  currency: string;              // 货币类型CNY/USD/HKD
  createdAt: Date;              // 创建时间
  updatedAt: Date;              // 更新时间
}

5.2 持仓Position

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

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

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

interface CashAccount {
  id: string;                    // 现金账户ID
  accountId: string;             // 所属账户ID
  balance: number;               // 余额
  currency: string;              // 货币类型
  interestRate?: number;         // 利率(年化)
  updatedAt: Date;
}

5.5 每日资产快照DailySnapshot

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 成本价计算算法

买入后成本价计算

function calculateCostAfterBuy(originalShares, originalCost, buyShares, buyPrice, fee) {
  const totalCost = (originalShares * originalCost) + (buyShares * buyPrice) + fee;
  const totalShares = originalShares + buyShares;
  return totalCost / totalShares;
}

卖出后成本价计算

// 卖出不改变成本价,只减少份额
function calculateCostAfterSell(originalShares, originalCost, sellShares) {
  // 成本价不变
  return originalCost;
}

拆股后成本价计算

function calculateCostAfterSplit(originalShares, originalCost, splitRatio) {
  // splitRatio: 如 1:2 拆股splitRatio = 2
  const newShares = originalShares * splitRatio;
  const newCost = originalCost / splitRatio;
  return { newShares, newCost };
}

分红后成本价计算

function calculateCostAfterDividend(originalCost, dividendPerShare) {
  // 现金分红:成本价降低
  return originalCost - dividendPerShare;
}

送股后成本价计算

function calculateCostAfterBonus(originalShares, originalCost, bonusRatio) {
  // bonusRatio: 如 10送5bonusRatio = 0.5
  const newShares = originalShares * (1 + bonusRatio);
  const newCost = originalCost / (1 + bonusRatio);
  return { newShares, newCost };
}

6.2 收益率计算算法

时间加权收益率(基金净值法)

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

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计算不是基于交易记录
  • 用户只需记录:什么时候投入多少钱、什么时候提取多少钱
  • 比记录每笔交易简单很多

年化收益率计算

function calculateAnnualizedReturn(totalReturn, days) {
  return Math.pow(1 + totalReturn, 365 / days) - 1;
}

6.3 每日资产更新流程

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年