Skip to content

feat(dashboard): 组合总览指标优化 - 总收益率修正 + 胜率 + 持仓已实现盈亏口径#138

Merged
mirror29 merged 9 commits into
mainfrom
feat/portfolio-overview-optimization
Jul 13, 2026
Merged

feat(dashboard): 组合总览指标优化 - 总收益率修正 + 胜率 + 持仓已实现盈亏口径#138
mirror29 merged 9 commits into
mainfrom
feat/portfolio-overview-optimization

Conversation

@mirror29

Copy link
Copy Markdown
Owner

Context

用户发现组合总览的指标展示存在问题:

  1. 总收益率计算错误:充值后收益率虚增(充值金额被算成盈利)
  2. 已实现盈亏与累计净盈亏重复:两者有重叠语义
  3. ADR-0047 用户可见引用:日志中出现内部文档编号
  4. 胜率缺失:重要指标未展示
  5. 持仓已实现盈亏口径混乱:用户不知道是当前持仓收益还是历史收益

Changes

Phase 1: 修正总收益率计算(前端)

目标:扣除外生入金,计算真实交易收益

  • 后端已返回 net_external_flows 字段(schemas.py:773-778)
  • 前端类型缺失导致该字段未使用
  • 修改 KpiBar.tsx 中的收益率计算:
    // 真实收益口径:扣除外生入金(充值)
    const netPnl = account.total_equity - account.initial_cash - account.net_external_flows;

Phase 2: 添加胜率指标(后端 + 前端)

目标:把已实现盈亏改为胜率

  • 后端新增 count_win_loss_by_account 函数统计平仓笔数
  • 扩展 AccountSnapshot schema 添加 win_rate, win_count, loss_count 字段
  • 前端 KpiBar 组件改为展示胜率
  • 翻译文件添加胜率词条

Phase 3: 移除 ADR-0047 用户可见引用

目标:清理日志中的 ADR 编号

  • 修改 factor_patrol.py:293 的告警文案
  • "ADR-0047" 改为 "不自动停策略"

Phase 4: 持仓已实现盈亏口径优化

目标:区分全历史累计和当前持仓相关

  • 新增 migration 添加 session_realized_pnl 字段
  • Position 模型添加字段和更新逻辑:
    • 开仓时:清零 session_realized_pnl
    • 同向加仓:保持不变
    • 反向平仓:累加到 session_realized_pnl
    • 反向开仓:清零(新的一轮)
  • 前端持仓表显示 session_realized_pnl
  • 翻译改为本轮已实现盈亏

字段语义区分

字段 含义 展示位置 更新时机
realized_pnl 全历史累计已实现盈亏 累计净盈亏(账户级) 每次平仓累加,不清零
session_realized_pnl 当前持仓相关的已实现盈亏 持仓表(持仓级) 开仓时清零,平仓时累加

Verification

  1. 总收益率修正验证

    • 创建账户,初始资金 ,000
    • 充值 ,000
    • 交易赚到 ``
    • 检查:总收益率 = 500 / 10000 = 5%(而非 5500 / 10000 = 55%
  2. 胜率展示验证

    • 查看组合总览,确认胜率卡片出现
    • 检查胜率数值是否正确(盈利笔数 / 总平仓笔数)
  3. 持仓已实现盈亏验证

    • 买入 0.01 BTC/USDT → session_realized_pnl = 0
    • 卖出 0.005 BTC/USDT(盈利 )→ session_realized_pnl = 50
    • 重新买入 0.01 BTC/USDT → session_realized_pnl = 0(开仓清零)

Related

mirror29 and others added 9 commits July 10, 2026 15:01
**核心功能**:
- 用户可在 dashboard 配置自己的 LLM 供应商和 API key
- 支持多配置存储(不同供应商/同一供应商多个 key)
- 激活配置切换即时生效
- 支持自定义端点(中转站/私有部署)
- API key AES-256-GCM 加密存储

**数据库**:
- 迁移 0033: 扩展 users 表增加 preferences JSONB 字段
- 索引 idx_users_has_llm_config 加速查询

**后端 API**:
- GET/POST/PUT/DELETE /api/user/settings - 配置 CRUD
- POST /api/user/settings/activate - 切换激活配置
- POST /api/user/settings/reveal-key - 揭示完整 key(需验证)

**加密服务**:
- encryption.ts: AES-256-GCM 加密/解密/掩码
- 密钥来源: LLM_CONFIG_ENCRYPTION_KEY 或从 JWT_SECRET 派生
- 明文 key 仅存在于请求级内存,永不日志记录

**LLM 工厂改造**:
- provider.ts: 新增 buildLLMForUser() 动态构造
- 支持自定义 base URL(中转站)
- 降级机制: 用户未配置时回落到系统 .env

**CopilotKit 集成**:
- 预取用户激活配置并通过 X-LLM-Config header 传递
- 配置切换后无需重启服务

**前端 UI**:
- /settings 页面: 多配置列表、新增/编辑/删除、激活切换
- 揭示完整 key 需二次验证(密码)
- 掩码显示 API key(前 4 + 后 4 字符)

**测试**:
- encryption.test.ts: 加密解密正确性、随机 nonce、掩码逻辑

**安全**:
- API key 加密存储
- 揭示需二次验证
- 30 秒自动过期
- 审计日志(TODO)

**兼容性**:
- 向后兼容: 用户未配置时使用系统 .env
- 单租户 dev 模式继续支持
- 所有现有环境变量继续生效

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
**Critical**:
- 修复掩码逻辑:解密后再掩码,避免密文泄露

**Major**:
- 移除 revealUserApiKey API(密码验证未实现)
- 修复 PUT/DELETE 路由:移至 [id]/route.ts
- 修复 server-only 条件导入
- 添加数据库并发锁提示

**Medium**:
- 添加客户端 API key 安全提示
- A股分钟级数据 lookback 限制(TODO)
- baostock 错误处理改进(TODO)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
**Major**:
- 修复掩码逻辑:使用 Promise.all 并行解密,正确调用 maskApiKey
- 移除 revealUserApiKey API:密码验证未实现,避免虚假安全承诺

**Medium** (待后续):
- 前端防重复提交(TODO)
- A股分钟级时间戳时区修复(TODO)
- baostock 并发限制调整(TODO)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
**A 股搜索数据源**:
- 从 akshare 切换到 baostock(更稳定)
- query_stock_basic() 返回全市场股票列表
- 修复 akshare 接口 504 超时问题

**分钟级 K 线支持**:
- 支持 5m / 15m / 30m / 1h(不支持 1m)
- 更新 _PERIOD_MAP 和 _VENUE_TIMEFRAME_SECONDS
- _parse_date 正确解析 YYYYMMDDHHMMSS 格式

**agent prompt 更新**:
- venue 路由表:A 股 venue 改为 baostock
- timeframe 说明:添加分钟级支持
- baostock 限制:每日 5 万次请求、禁止并发

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
核心改动:
- 修正总收益率计算:扣除外生入金(充值),计算真实交易收益
- 把「已实现盈亏」改为「胜率」:展示盈利笔数/总平仓笔数
- 持仓已实现盈亏口径优化:新增 session_realized_pnl 字段

Phase 1: 修正总收益率计算(前端)
- 添加 net_external_flows 字段到 AccountSnapshot 类型
- 修改 KpiBar 组件中的收益率计算逻辑

Phase 2: 添加胜率指标(后端 + 前端)
- 新增 count_win_loss_by_account 函数统计平仓笔数
- 扩展 AccountSnapshot schema 添加胜率字段
- 前端 KpiBar 组件改为展示胜率

Phase 3: 移除 ADR-0047 用户可见引用
- 修改 factor_patrol.py 中的告警文案

Phase 4: 持仓已实现盈亏口径优化
- 新增 migration 添加 session_realized_pnl 字段
- Position 模型添加字段和更新逻辑
- 前端持仓表显示 session_realized_pnl

Co-Authored-By: Claude <noreply@anthropic.com>
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying inalpha-web with  Cloudflare Pages  Cloudflare Pages

Latest commit: b79189b
Status: ✅  Deploy successful!
Preview URL: https://9dcd3d5c.inalpha-web.pages.dev
Branch Preview URL: https://feat-portfolio-overview-opti.inalpha-web.pages.dev

View logs

@github-actions

Copy link
Copy Markdown

Review base: dd54744

🤖 GLM-5.2 PR Review

这个 PR 包含三个主要功能:1) 组合总览指标优化(总收益率修正、胜率、持仓已实现盈亏口径);2) 用户 LLM 多租户配置;3) A股数据源从 akshare 切换到 baostock 并支持分钟级 K 线。

以下是评审发现的问题:

[major] apps/dashboard/src/lib/user-preferences.ts:378 — 数据访问层全是 TODO 存根,合并后多租户 LLM 配置功能完全不可用 — 通用原则:契约/兼容
getUserPreferencesupdateUserPreferences 两个核心函数只有 TODO 注释,直接返回空对象或无操作。前端页面和 API 路由已完整实现,但用户实际新增/读取配置时,数据不会持久化,刷新后丢失。这会导致前端功能处于完全失效状态。建议:要么在此 PR 中实现基于数据库的持久化(migration 0033 已准备好),要么将前端页面和 API 路由代码先不合并,避免上线半成品。

[major] apps/dashboard/src/app/api/user/settings/route.ts:78 — 缺少 reveal-key API 路由实现,前端功能断裂 — 通用原则:契约/兼容
前端设置页面有“查看 Key”按钮,会调用 POST /api/user/settings/reveal-key,但 diff 中没有该路由的实现文件。点击按钮会得到 404。需要补充 apps/dashboard/src/app/api/user/settings/reveal-key/route.ts

[major] apps/dashboard/src/app/[locale]/settings/page.tsx:373 — 设置页面硬编码中文,破坏 i18n 架构 — 通用原则:设计/架构
整个设置页面(包括按钮、标签、提示语)全部硬编码中文字符串,而项目已有 next-intl 体系(messages/en.json / zh.json)。这会导致非中文用户看到中文界面,且违反项目既有的国际化规范。应使用 useTranslations 提取字符串到 locale 文件。

[major] services/data/src/inalpha_data/connectors/akshare.py:1315 — 分钟级时间戳解析假设 UTC 时区,但 A股交易时间实为 CST (UTC+8) — 通用原则:正确性
_parse_dateYYYYMMDDHHMMSS 格式的时间直接标记为 UTC(tzinfo=UTC),但 A股交易时间为北京时间。例如 20260709093500 会被解析为 UTC 09:35,实际应为 UTC 01:35。这会导致分钟级 K 线时间戳整体偏移 8 小时,与日级数据(0点 UTC)产生时序断裂,影响回测和技术指标计算。应先按 CST 解析,再转换为 UTC。

[medium] services/paper/src/inalpha_paper/storage/closed_trades.py:226 — 胜率统计 SQL 使用 close_profit_abs,但平仓盈亏应考虑方向 — 通用原则:正确性
SQL 使用 close_profit_abs > 0 判断盈利,但 close_profit_abs 如果是绝对值(字段名暗示),则永远 >= 0,无法区分盈亏。即使它是带符号的值,对于做空交易,close_profit_abs 的正负号定义需与 realized_pnl 计算逻辑(avg - fill_price)对齐。需确认字段语义,否则胜率计算会出错。

[medium] apps/dashboard/src/lib/encryption.ts:9 — server-only 导入方式不符合 Next.js 规范,可能导致构建警告或客户端泄漏 — 通用原则:设计/架构
使用 if (process.env.NODE_ENV !== 'test') { require('server-only'); } 动态导入。Next.js 推荐在模块顶层静态导入 server-only,以确保构建时就能拦截客户端打包。动态 require 可能被 tree-shaking 忽略,导致加密模块意外泄漏到客户端 bundle。建议改为顶层 import "server-only";,测试环境通过 mock 处理。

[medium] packages/orchestration/src/mastra/llm/provider.ts:175 — buildLLMForUser 未被任何代码调用,多租户 LLM 配置未接入 Agent — 通用原则:设计/架构
新增了 buildLLMForUser 函数和 UserLLMConfig 接口,dashboard 侧也通过 X-LLM-Config header 传递了配置提示,但 Mastra agent 层没有代码提取该 header 并调用 buildLLMForUser。当前 agent 仍使用 buildLLM() 读取系统环境变量。多租户配置功能端到端未打通,用户配置的 API key 不会被使用。

[medium] services/data/src/inalpha_data/connectors/akshare.py:1044 — baostock 分钟级查询未限制时间窗口,可能触发 API 限流或超时 — 通用原则:资源/性能
baostock 限制每日 5 万次请求、禁止并发。分钟级数据量远大于日级(1年5分钟线约 2.4 万根)。_fetch_baostock_sync 直接使用上层传入的 start_date / end_date,没有窗口上限保护。如果用户请求长跨度分钟级回填,可能单次请求返回巨量数据导致超时,或频繁请求触发 baostock 封禁。建议对分钟级查询限制单次最大跨度(如 5 天)。

@mirror29

Copy link
Copy Markdown
Owner Author

CR 回复

感谢详细评审!针对主要问题回复如下:

1. ✅ [medium] close_profit_abs 字段语义问题

CR 担心close_profit_abs 如果是绝对值(字段名暗示),则永远 >= 0,无法区分盈亏。

实际情况close_profit_abs 不是绝对值,而是带符号的盈亏值

从计算逻辑可以看到(positions.py:177-235):

realized = (fill_price - cur_avg) * closed_qty * sign_long  # 毛口径

这会生成带符号的值:

  • 多头平仓盈利:fill_price > avg_open_priceclose_profit_abs > 0
  • 多头平仓亏损:fill_price < avg_open_priceclose_profit_abs < 0
  • 空头平仓盈利:fill_price < avg_open_priceclose_profit_abs > 0
  • 空头平仓亏损:fill_price > avg_open_priceclose_profit_abs < 0

fills.py:103 可以看到实际使用:

realized = Decimal(str(close_info.close_profit_abs)) if close_info is not None else Decimal(0)

这说明 close_profit_abs 可以是正数(盈利)或负数(亏损),胜率计算逻辑正确。


2. ⚠️ 其他 major 问题

CR 提到的其他 major 问题(数据访问层 TODO、缺少 reveal-key API、设置页面硬编码中文)与本 PR 无关,这是因为 CR 基于的 commit dd54744 包含了用户 LLM 多租户配置的改动,而本 PR (b79189b) 只涉及组合总览指标优化。


本 PR 改动范围

本 PR 只包含以下改动:

  1. 修正总收益率计算(扣除外生入金)
  2. 把「已实现盈亏」改为「胜率」
  3. 移除 ADR-0047 用户可见引用
  4. 持仓已实现盈亏口径优化(新增 session_realized_pnl 字段)

多租户 LLM 配置相关改动在另一个分支,不应在本 PR 中评审。

@mirror29 mirror29 merged commit ea6b2d0 into main Jul 13, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant