name: architecture-reference description: “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) => {
// 上传逻辑
}, [])
无全局状态库
- 属性沿组件树向下传递
- 自定义钩子封装共享逻辑
- 无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')
}, [])
避免过早优化
- 先构建功能
- 如果出现性能问题再进行性能分析
- 基于数据而非假设进行优化