name: 架构参考 描述: “Portfolio Buddy 2 项目结构快速参考。适用于:添加新功能、修改现有组件、理解数据流或新成员熟悉代码库。包含组件层次结构、钩子模式和实用函数。”
Portfolio Buddy 2 - 架构参考
组件层次结构
App.tsx (351 行)
├── Header
│ └── 应用标题和品牌标识
├── UploadSection
│ ├── 文件上传至 Supabase
│ ├── CSV 解析和验证
│ └── 错误处理
├── ErrorList
│ └── 显示解析/验证错误
├── UploadedFilesList
│ └── 成功上传的文件列表
├── AnalyticsControls
│ ├── 切换指标视图
│ ├── 切换投资组合视图
│ └── 切换相关性视图
├── PortfolioSection (591 行 - 需要重构!)
│ ├── usePortfolio 钩子(日期过滤)
│ ├── useContractMultipliers 钩子
│ ├── Chart.js 权益曲线
│ ├── 投资组合统计
│ ├── ContractInput 组件
│ ├── MasterContractControl
│ └── MetricsTable 集成
├── CorrelationSection
│ ├── CorrelationHeatmap (Chart.js)
│ ├── 斯皮尔曼相关性
│ └── 皮尔逊相关性
├── MetricsTable (242 行)
│ ├── useMetrics 钩子
│ ├── useSorting 钩子(高级多列排序)
│ ├── SortableHeader 组件
│ └── 选择状态管理
└── SessionComplete
└── 完成 UI/消息
关键钩子
useMetrics
位置: src/hooks/useMetrics.ts 目的: 根据上传的投资组合数据计算交易指标 功能:
- 计算夏普比率、索提诺比率、最大回撤、年复合增长率、胜率等。
- 记忆化计算以提高性能
- 优雅处理空/无效数据
用法:
const { metrics, isCalculating } = useMetrics(portfolioData, riskFreeRate)
返回:
metrics: 按策略计算的指标数组isCalculating: 布尔加载状态
注意: 包含 4 处 TypeScript any 违规(技术债务)
usePortfolio
位置: src/hooks/usePortfolio.ts 目的: 管理具有日期范围过滤功能的投资组合数据 功能:
- 解析 CSV 交易数据
- 按日期范围(开始/结束日期)过滤
- 构建权益曲线
- 聚合日收益率
用法:
const {
portfolioData,
filteredData,
dateRange,
setDateRange
} = usePortfolio(uploadedFiles)
近期添加: 日期范围过滤(提交 258ba3a)
注意: 包含 11 处 TypeScript any 违规(技术债务)
useContractMultipliers
位置: src/hooks/useContractMultipliers.ts 目的: 管理期货交易的合约乘数 功能:
- 按策略跟踪合约规模
- 将乘数应用于指标
- 主控件可一次性设置所有合约
用法:
const {
multipliers,
setMultiplier,
setAllMultipliers,
getAdjustedMetrics
} = useContractMultipliers(strategies)
useSorting
位置: src/hooks/useSorting.ts 目的: 用于 MetricsTable 的高级多列排序 功能:
- 按优先级对多列进行排序
- 切换升序/降序
- 每种数据类型的自定义比较逻辑
用法:
const {
sortedData,
sortColumn,
sortDirection,
handleSort
} = useSorting(data, defaultColumn)
实用函数
dataUtils.ts
位置: src/utils/dataUtils.ts 包含核心函数:
CSV 与数据处理:
parseCSV(file)- 使用 PapaParse 解析 CSV 文件processCurrencyColumns(data)- 清理货币值($,逗号)parseFilenameComponents(filename)- 从文件名中提取符号/方向/策略getDisplayName(symbol, direction, strategy)- 格式化显示名称normalizeDate(date)- 将日期标准化为 UTC 午夜getDateKey(date)- 将日期转换为 YYYY-MM-DD 字符串键
指标计算:
calculateMetrics(data, filename)- 计算策略级别的交易指标- 净利润、毛利润/亏损
- 盈利因子、胜率
- 平均盈利/亏损、期望值
- 最大回撤(来自权益曲线)
- 年复合增长率等效值(annualGrowthRate)
- 总交易数、盈利/亏损交易数
- 注意: 不计算夏普或索提诺比率(这些在 PortfolioSection.tsx 中)
getAdjustedMetrics(metrics, multiplier)- 将合约乘数应用于指标
风险调整后指标 (PortfolioSection.tsx):
- 夏普比率 (第 533 行): 内联计算为
(annualGrowthRate / 100) / (maxDrawdown / startingCapital) - 索提诺比率 (第 133-158 行): 使用下行偏差内联计算,使用无风险利率状态
相关性分析:
buildCorrelationMatrix(strategies)- 构建斯皮尔曼相关性矩阵calculatePearsonCorrelation(returns1, returns2)- 皮尔逊相关系数calculateRanks(values)- 斯皮尔曼相关性的排名计算
交易计算:
getMarginRate(symbol)- 按符号获取保证金要求calculateEquityCurve(trades)- 构建累积权益曲线calculateDailyReturns(equity)- 根据权益曲线计算日收益率
格式化:
formatNumber(value, decimals)- 格式化带小数的数字formatCurrency(value)- 格式化为货币($X,XXX.XX)formatPercent(value)- 格式化为百分比(X.XX%)
注意: 包含 1 处 TypeScript any 违规(技术债务)
数据流
上传与处理流程
1. 用户通过 UploadSection 上传 CSV
↓
2. parseCSV() 提取交易数据
↓
3. processCurrencyColumns() 清理数据
↓
4. 文件上传至 Supabase 存储
↓
5. usePortfolio 钩子获取并聚合数据
↓
6. 应用日期范围过滤器(如果已设置)
↓
7. useMetrics 计算所有指标
↓
8. MetricsTable 显示结果
合约乘数流程
1. 用户在 ContractInput 中输入合约规模
↓
2. useContractMultipliers 存储值
↓
3. getAdjustedMetrics() 应用乘数
↓
4. 调整后的指标显示在 MetricsTable 中
↓
5. 投资组合图表随调整后的值更新
排序流程
1. 用户点击 SortableHeader
↓
2. useSorting 更新排序列/方向
↓
3. 应用自定义比较逻辑
↓
4. MetricsTable 重新渲染排序后的数据
相关性流程
1. 用户在 MetricsTable 中选择资产
↓
2. 选择状态传递给 CorrelationSection
↓
3. buildCorrelationMatrix() 计算相关性
↓
4. CorrelationHeatmap 渲染 Chart.js 热图
↓
5. 同时显示斯皮尔曼和皮尔逊相关性
状态管理
纯 React 钩子(无 Zustand/TanStack Query)
- 本地组件状态 →
useState - 派生状态 →
useMemo - 稳定回调 →
useCallback - 值的引用 →
useRef
示例模式:
const [data, setData] = useState<Trade[]>([])
const metrics = useMemo(() => calculateMetrics(data), [data])
const handleUpload = useCallback((file: File) => {
// 上传逻辑
}, [])
无全局状态库
- 通过组件树向下传递 Props
- 自定义钩子封装共享逻辑
- 无 Redux、Zustand 或 Jotai
添加新功能
新指标计算
- 将计算逻辑添加到
dataUtils.calculateMetrics() - 更新
calculateMetrics()中的返回类型 - 向
MetricsTable.tsx添加列 - 如有需要,更新
useSorting.ts中的排序逻辑 - 使用样本数据进行测试
示例: 索提诺比率在提交 258ba3a 和 9f25040 中添加
新图表组件
- 在
src/components/中创建组件 - 使用 Chart.js(不是 Recharts - 未使用)
- 导入所需的图表类型和插件:
import { Line } from 'react-chartjs-2' import { Chart, registerables } from 'chart.js' import zoomPlugin from 'chartjs-plugin-zoom' - 连接到
useMetrics或usePortfolio以获取数据 - 添加到
App.tsx中的相应部分
新钩子
- 在
src/hooks/use[Feature].ts中创建 - 遵循命名约定:
use前缀,驼峰式 - 返回具有清晰属性名称的对象
- 对所有类型使用 TypeScript(避免
any) - 为复杂逻辑添加 JSDoc 注释
Chart.js 架构
当前设置
- 库: Chart.js 4.x(不是 Recharts)
- React 包装器: react-chartjs-2
- 使用的插件:
- chartjs-plugin-zoom(平移和缩放)
- chartjs-plugin-annotation(趋势线、标记)
- chartjs-adapter-date-fns(时间刻度)
图表使用位置
- PortfolioSection: 权益曲线折线图
- CorrelationHeatmap: 相关性矩阵热图
- CustomTooltip: 图表的共享工具提示组件
Recharts 注意
⚠️ Recharts 已安装但从未导入 - 应移除(11.5KB 浪费)
组件大小指南
目标:最多 200 行
当前违规:
- ❌ PortfolioSection.tsx: 591 行(限制的 295%) - 高优先级重构
- ❌ App.tsx: 351 行(限制的 175%)
- ❌ MetricsTable.tsx: 242 行(限制的 121%) - 已从 350 行改进
重构策略
对于 PortfolioSection (591 行):
- 将权益图表提取到
EquityChartSection.tsx - 将统计信息提取到
PortfolioStats.tsx - 将合约控件提取到
ContractControls.tsx - 在主组件中仅保留编排逻辑
TypeScript 模式
数据结构的接口
interface Trade {
date: Date
symbol: string
pnl: number
// ...
}
interface Metric {
name: string
sharpe: number
sortino: number
// ...
}
避免 any 类型
当前违规(共 15 处) - 详见 portfolio-context 技能
首选方法:
// 错误
const data: any = parseData()
// 正确
interface ParsedData {
trades: Trade[]
errors: string[]
}
const data: ParsedData = parseData()
性能模式
使用 useMemo 进行记忆化
// 昂贵的相关性计算
const correlationMatrix = useMemo(
() => buildCorrelationMatrix(selectedStrategies),
[selectedStrategies]
)
使用 useCallback 实现稳定回调
const handleSort = useCallback((column: string) => {
setSortColumn(column)
setSortDirection(prev => prev === 'asc' ? 'desc' : 'asc')
}, [])
避免过早优化
- 先构建功能
- 如果出现性能问题再进行性能分析
- 根据数据而非假设进行优化