21 KiB
21 KiB
投资收益记录系统设计文档
一、需求概述
1.1 核心目标
开发一个自动化的投资收益记录应用,用户只需直接修改持仓的成本价和份额,系统自动更新市场价格、计算收益和收益率,无需用户定期手动更新资产金额。
设计原则:
- 被动变更(分红、拆股、送股):系统自动完成
- 主动变更(买入、卖出、追加):用户直接修改成本价和份数,无需记录每次交易细节
- 适用场景:多券商用户汇总统计,不是替代券商系统
1.2 支持的资产类型
- 股票:支持A股、港股、美股等
- 基金:支持各类基金产品
- 现金:证券账户留存现金、分红现金等
1.3 核心功能
- 自动更新市场价格(每日收盘后)
- 自动计算总资产、总收益
- 自动计算累计收益率、年化收益率、当年收益率
- 支持多种交易场景处理
- 使用基金净值法和资金加权法计算收益率(取两者低者)
二、设计分析与优化
2.1 用户提出的设计分析
✅ 正确的设计点
-
拆股处理
- 自动变更股份数和成本价
- 实现方式:股份数 = 原股份数 × 拆股比例,成本价 = 原成本价 ÷ 拆股比例
- 示例:10股拆成20股(1:2),成本价从100元变为50元
-
分红处理
- 股票成本价减去每股分红得到最新成本价
- 实现方式:新成本价 = 原成本价 - 每股分红金额
- 注意:分红需要同时记录到现金账户
-
买入/卖出处理
- 变更持仓份额和成本价
- 实现方式:
- 买入:新成本价 = (原成本价 × 原份额 + 买入价 × 买入份额) / (原份额 + 买入份额)
- 卖出:只减少份额,成本价不变(先进先出或加权平均)
-
提现处理
- 变更现金账户金额
- 实现方式:现金余额 = 原余额 - 提现金额
⚠️ 需要补充和优化的点
-
分红处理需要完善
- 需要区分现金分红和股票分红(送股)
- 现金分红:成本价降低,现金账户增加
- 股票分红(送股):股份数增加,成本价降低
- 公式:新成本价 = (原成本价 × 原股份数) / (原股份数 + 送股数)
-
买入/卖出成本价计算
- 需要考虑交易费用(佣金、印花税等)
- 买入成本价 = (买入价 × 买入份额 + 交易费用) / 买入份额
- 卖出:需要计算已实现收益,并更新持仓成本价
-
基金的特殊处理
- 基金分红通常有现金分红和红利再投资两种方式
- 需要支持基金份额的自动调整
三、遗漏场景分析
3.1 必须处理的场景
-
送股(股票分红)
- 场景:公司送股,如10送5
- 处理:股份数增加,成本价降低
- 公式:新股份数 = 原股份数 × (1 + 送股比例),新成本价 = 原成本价 / (1 + 送股比例)
-
配股
- 场景:公司配股,需要用户决定是否参与
- 处理:如果参与,需要记录配股价格和数量,重新计算成本价
-
转增股本
- 场景:资本公积转增股本
- 处理:类似送股,股份数增加,成本价降低
-
股票合并(并股)
- 场景:如10股合并为1股
- 处理:股份数减少,成本价提高
- 公式:新股份数 = 原股份数 / 合并比例,新成本价 = 原成本价 × 合并比例
-
现金分红再投资
- 场景:用户选择将分红现金再次买入股票
- 处理:需要记录一笔买入交易
-
基金分红再投资
- 场景:基金分红选择红利再投资
- 处理:基金份额增加,成本价不变
-
股票退市/摘牌
- 场景:股票退市,无法获取市场价格
- 处理:需要标记为退市状态,使用最后已知价格或用户手动设置
-
股票停牌
- 场景:股票长期停牌
- 处理:使用停牌前最后价格,标记停牌状态
-
汇率变动(港股、美股)
- 场景:持有港股、美股,汇率变动影响资产价值
- 处理:需要记录买入时的汇率,计算时使用当前汇率
-
交易费用
- 场景:买入/卖出都有交易费用
- 处理:买入时计入成本,卖出时从收益中扣除
-
现金账户利息
- 场景:证券账户现金可能产生利息
- 处理:定期(如每月)更新现金余额,增加利息收入
-
资产转移
- 场景:从一个账户转移到另一个账户
- 处理:需要支持多账户管理
3.2 可选处理的场景
- 股票期权/权证
- 可转债
- 分级基金
- 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 + 累计收益率)^(365 / 投资天数) - 1 -
当年收益率
当年收益率 = (当前总资产 - 年初总资产) / 年初总资产
五、数据模型设计
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送5,bonusRatio = 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 模块划分
-
资产管理模块
- 账户管理
- 持仓管理
- 现金账户管理
-
交易处理模块
- 买入/卖出处理
- 分红处理
- 拆股/送股处理
- 交易记录管理
-
价格更新模块
- 定时任务(每日收盘后更新)
- 价格数据获取
- 价格数据缓存
-
收益计算模块
- 时间加权收益率计算
- 资金加权收益率计算
- 年化收益率计算
- 资产快照生成
-
数据统计模块
- 总资产统计
- 收益统计
- 收益率统计
- 持仓分析
8.3 数据存储设计
表结构:
- accounts(账户表)
- positions(持仓表)
- transactions(交易记录表)
- cash_accounts(现金账户表)
- asset_snapshots(资产快照表)
- market_prices(市场价格表,用于缓存)
九、实现建议
9.1 开发优先级
第一阶段(MVP):
- 基本的资产账户管理
- 持仓管理(买入/卖出)
- 每日价格更新
- 基本的收益计算(累计收益率)
第二阶段:
- 拆股、分红、送股处理
- 时间加权收益率和资金加权收益率计算
- 年化收益率计算
- 资产快照功能
第三阶段:
- 多账户管理
- 多货币支持
- 高级统计和分析
- 数据导出功能
9.2 注意事项
-
数据准确性
- 价格数据需要可靠的数据源
- 交易记录需要完整保存,不可修改
- 成本价计算需要精确
-
性能优化
- 价格数据缓存
- 资产快照定期生成,避免实时计算
- 收益率计算优化(IRR计算可能较慢)
-
用户体验
- 交易记录操作简单
- 自动处理拆股、分红等场景
- 清晰的收益展示
-
数据备份
- 定期备份交易记录
- 支持数据导出
十、总结
10.1 设计要点
- ✅ 用户只需直接修改持仓:买入/卖出时直接修改成本价和份数,无需记录交易细节
- ✅ 自动处理特殊事件:拆股、分红、送股等被动变更由系统自动处理
- ✅ 自动更新市场价格:每日收盘后自动更新,无需用户手动输入
- ✅ 简化操作流程:适合多券商用户快速汇总统计,不是替代券商系统
- ✅ 保守的收益率计算:使用时间加权(基于资产快照)和资金加权(基于资金流)两种方法,取较低者
- ✅ 完整的收益统计:累计收益率、年化收益率、当年收益率等
- ✅ 支持计划和复盘:通过持仓思考记录实现,关联持仓而非交易
10.2 需要补充的场景
- 送股(股票分红)
- 配股
- 转增股本
- 股票合并
- 汇率变动(港股、美股)
- 交易费用处理
- 现金账户利息
- 股票停牌/退市处理
10.3 实现建议
- 采用模块化设计,便于扩展
- 持仓变更记录不可修改,保证数据可追溯性
- 每日自动生成资产快照,用于收益率计算
- 提供成本价计算器工具,帮助用户计算加权平均成本价
- 支持批量导入功能,可以从券商系统导出后导入
- 提供清晰的数据展示和统计
- 持仓思考记录支持计划和复盘功能
10.4 数据模型总结
核心表:
positions- 持仓表(用户直接修改成本价和份数)position_changes- 持仓变更记录(系统自动记录变更历史)cash_flows- 资金变动记录(用户记录投入/提取,用于 IRR 计算)daily_snapshots- 每日资产快照(系统自动生成,用于时间加权收益率)position_thoughts- 持仓思考记录(用户记录思考,用于计划和复盘)
不再需要:
详细的交易记录表(改为简化的持仓变更记录)
文档版本:v1.0
创建日期:2024年
最后更新:2024年