PortfolioBuddy2架构参考Skill architecture-reference

Portfolio Buddy 2 是一个用于量化交易分析和投资组合管理的Web应用程序。本技能文档提供了该项目的完整架构参考,包括组件层次结构、关键钩子(如useMetrics、usePortfolio)、实用函数(如dataUtils.ts中的指标计算和数据处理)、数据流、状态管理策略以及性能优化模式。它详细说明了如何添加新功能、重构大型组件(如PortfolioSection),并强调了TypeScript最佳实践以避免使用any类型。该文档是开发人员添加功能、修改组件、理解数据流或新成员熟悉代码库的必备指南。关键词:量化交易、投资组合分析、React架构、TypeScript、Chart.js、性能优化、代码重构、数据流、钩子模式。

前端开发 0 次安装 2 次浏览 更新于 2/28/2026

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

添加新功能

新指标计算

  1. 将计算逻辑添加到 dataUtils.calculateMetrics()
  2. 更新 calculateMetrics() 中的返回类型
  3. MetricsTable.tsx 添加列
  4. 如有需要,更新 useSorting.ts 中的排序逻辑
  5. 使用样本数据进行测试

示例: 索提诺比率在提交 258ba3a 和 9f25040 中添加

新图表组件

  1. src/components/ 中创建组件
  2. 使用 Chart.js(不是 Recharts - 未使用)
  3. 导入所需的图表类型和插件:
    import { Line } from 'react-chartjs-2'
    import { Chart, registerables } from 'chart.js'
    import zoomPlugin from 'chartjs-plugin-zoom'
    
  4. 连接到 useMetricsusePortfolio 以获取数据
  5. 添加到 App.tsx 中的相应部分

新钩子

  1. src/hooks/use[Feature].ts 中创建
  2. 遵循命名约定:use 前缀,驼峰式
  3. 返回具有清晰属性名称的对象
  4. 对所有类型使用 TypeScript(避免 any
  5. 为复杂逻辑添加 JSDoc 注释

Chart.js 架构

当前设置

  • : Chart.js 4.x(不是 Recharts)
  • React 包装器: react-chartjs-2
  • 使用的插件:
    • chartjs-plugin-zoom(平移和缩放)
    • chartjs-plugin-annotation(趋势线、标记)
    • chartjs-adapter-date-fns(时间刻度)

图表使用位置

  1. PortfolioSection: 权益曲线折线图
  2. CorrelationHeatmap: 相关性矩阵热图
  3. CustomTooltip: 图表的共享工具提示组件

Recharts 注意

⚠️ Recharts 已安装但从未导入 - 应移除(11.5KB 浪费)

组件大小指南

目标:最多 200 行

当前违规:

  • ❌ PortfolioSection.tsx: 591 行(限制的 295%) - 高优先级重构
  • ❌ App.tsx: 351 行(限制的 175%)
  • ❌ MetricsTable.tsx: 242 行(限制的 121%) - 已从 350 行改进

重构策略

对于 PortfolioSection (591 行):

  1. 将权益图表提取到 EquityChartSection.tsx
  2. 将统计信息提取到 PortfolioStats.tsx
  3. 将合约控件提取到 ContractControls.tsx
  4. 在主组件中仅保留编排逻辑

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')
}, [])

避免过早优化

  • 先构建功能
  • 如果出现性能问题再进行性能分析
  • 根据数据而非假设进行优化