` 到页面渲染的完整实现)
TradingAgents-CN 仪表板模拟交易账户卡片实战指南从paperApi.getAccount()到页面渲染的完整实现【免费下载链接】TradingAgents-CN基于多智能体LLM的中文金融交易框架 - TradingAgents中文增强版项目地址: https://gitcode.com/GitHub_Trending/tr/TradingAgents-CN导读本文围绕 TradingAgents-CN 仪表板Dashboard页面中模拟交易账户信息卡片这一前端功能的完整实现展开讲解如何在前端仪表板的自选股卡片下方嵌入账户摘要卡片现金、持仓市值、总资产、已实现盈亏并串联前端 API 封装、页面生命周期加载、金额格式化与盈亏配色以及后端/api/paper/account多货币账户聚合接口的源码级数据链路。读完本文你将掌握在 Vue 3 Element Plus 项目中新增一个数据卡片组件的标准套路并理解模拟交易账户数据从前端请求到后端 MongoDB 聚合、再到页面渲染的完整闭环。背景为什么要在仪表板放一张模拟交易账户卡片TradingAgents-CN 是一个基于多智能体 LLM 的中文金融交易框架。除了让 LLM 智能体分析师、研究员、交易员等对股票进行分析与辩论之外项目还内置了**模拟交易Paper Trading**能力用户在真实行情驱动下以虚拟资金下单、持仓、查看盈亏用于验证分析结论而不承担真实风险。在早期版本中模拟交易账户信息只存在于独立的模拟交易页面路由/paper见 frontend/src/router/index.ts。用户想查看账户状态必须先离开仪表板跳转过去。而仪表板是用户登录后最先看到的页面承载着快速分析我的自选股市场快讯等高频入口。因此本功能的目标很明确在仪表板自选股卡片下方新增一张模拟交易账户卡片让用户无需跳转即可快速查看四项关键账户指标指标含义现金cash账户可用现金余额持仓市值positions_value全部持仓按最新价计算的市值之和总资产equity现金 持仓市值即账户净资产已实现盈亏realized_pnl卖出操作实现的累计盈亏该功能对应文档为 docs/frontend/DASHBOARD_PAPER_TRADING.md本文将以该文档为主体骨架结合仓库中的真实源码frontend/src/views/Dashboard/index.vue、frontend/src/api/paper.ts、app/routers/paper.py进行源码级深化。前端 API 封装paperApi.getAccount()的数据契约在写页面之前先看数据从哪来。仓库中已经封装好了模拟交易相关的 API 模块 frontend/src/api/paper.ts其中与仪表板卡片直接相关的是export const paperApi { async getAccount() { return ApiClient.getGetAccountResponse(/api/paper/account) }, // 下单、持仓、订单、重置等接口 }getAccount()请求后端GET /api/paper/account返回类型定义如下注意其中CurrencyAmount | number的联合类型export interface CurrencyAmount { CNY: number HKD: number USD: number } export interface PaperAccountSummary { cash: CurrencyAmount | number // 支持新旧格式 realized_pnl: CurrencyAmount | number // 支持新旧格式 positions_value: CurrencyAmount equity: CurrencyAmount | number // 支持新旧格式 updated_at?: string } export interface GetAccountResponse { account: PaperAccountSummary positions: PaperPositionItem[] }这里需要特别说明CurrencyAmount | number的设计意图后端账户结构经历了从单一现金标量到多货币对象的演进。新版本账户的cash、equity、realized_pnl均为{ CNY, HKD, USD }对象但数据库中仍可能存在旧版标量结构。因此类型声明用联合类型兼容两种格式这正是后端在 app/routers/paper.py 中做账户结构迁移把标量迁移为多货币对象对应的前端契约。仪表板卡片渲染时通过getCurrencyAmount()辅助函数做兜底取值见下文保证新旧数据都能正确显示。页面实现在自选股卡片下方嵌入账户卡片模板结构卡片 账户信息 空状态仪表板主文件 frontend/src/views/Dashboard/index.vue 采用 Vue 3script setup语法 Element Plus 组件库。在右侧栏我的自选股卡片el-card classfavorites-card之后新增模拟交易账户卡片源码 L221-L292!-- 模拟交易账户 -- el-card classpaper-trading-card stylemargin-top: 24px; template #header div classcard-header span模拟交易账户/span el-button typetext sizesmall clickgoToPaperTrading 查看详情 el-iconArrowRight //el-icon /el-button /div /template div v-ifpaperAccount classpaper-account-info div classaccount-item div classaccount-label现金/div div classaccount-value¥{{ formatMoney(paperAccount.cash) }}/div /div div classaccount-item div classaccount-label持仓市值/div div classaccount-value¥{{ formatMoney(paperAccount.positions_value) }}/div /div div classaccount-item div classaccount-label总资产/div div classaccount-value primary¥{{ formatMoney(paperAccount.equity) }}/div /div div classaccount-item div classaccount-label已实现盈亏/div div classaccount-value :classgetPnlClass(paperAccount.realized_pnl) {{ paperAccount.realized_pnl 0 ? : }}¥{{ formatMoney(paperAccount.realized_pnl) }} /div /div /div div v-else classempty-state el-icon classempty-iconInfoFilled //el-icon p暂无账户信息/p el-button typeprimary sizesmall clickgoToPaperTrading 查看模拟交易 /el-button /div /el-card模板设计有三个要点v-if / v-else双态paperAccount有值时渲染四行账户信息为null时渲染空状态图标 暂无账户信息 引导按钮保证接口失败或账户异常时页面不出现空白。头部右侧查看详情按钮点击后通过goToPaperTrading()跳转到/paper模拟交易页面形成概览 → 详情的导航闭环。已实现盈亏的符号前缀{{ realized_pnl 0 ? : }}保证正盈亏带号配合颜色类直观表达盈亏方向。实现事实较原文档的增强仓库实际落地的版本已支持多市场分账户展示——当后端返回多货币对象时卡片会按 A股账户 / 港股账户 / 美股账户分组渲染源码 L232-L283并通过getCurrencyAmount()取值、按币种显示¥/HK$/$前缀。这是对原文档单币种示例的演进详见下文多货币适配小节。脚本逻辑加载、跳转与格式化页面脚本部分源码 L301-L582的核心逻辑如下import { paperApi, type PaperAccountSummary } from /api/paper // 模拟交易账户数据 const paperAccount refPaperAccountSummary | null(null) // 加载模拟交易账户信息 const loadPaperAccount async () { try { const response await paperApi.getAccount() if (response.success response.data) { paperAccount.value response.data.account } } catch (error) { console.error(加载模拟交易账户失败:, error) paperAccount.value null } } // 跳转到模拟交易页面 const goToPaperTrading () { router.push(/paper) } // 格式化金额添加千分位分隔符 const formatMoney (value: number) { return value.toFixed(2).replace(/\B(?(\d{3})(?!\d))/g, ,) } // 获取盈亏样式类 const getPnlClass (pnl: number) { if (pnl 0) return price-up // 红色 if (pnl 0) return price-down // 绿色 return price-neutral // 灰色 }在生命周期钩子中与其他仪表板数据并行加载源码 L573-L582onMounted(async () { await loadFavoriteStocks() await loadRecentAnalyses() await loadMarketNews() await loadPaperAccount() // 新增加载模拟交易账户 })关键细节说明refPaperAccountSummary | null初始为null以触发空状态分支加载成功赋值失败置null天然支持模板的v-else。response.success response.data双重校验与项目统一 API 响应封装ApiResponse保持一致兼容不同返回结构。formatMoney的正则toFixed(2)先固定两位小数再通过\B(?(\d{3})(?!\d))在千位前插入逗号。例如2329863.00 → 2,329,863.00。盈亏配色price-up红/price-down绿/price-neutral灰与 A 股红涨绿跌习惯一致Element Plus 主题色之外的业务语义色。样式卡片布局与状态色样式部分源码 L917-L996在.dashboard作用域下定义.paper-trading-card.paper-trading-card { .card-header { display: flex; justify-content: space-between; align-items: center; } .paper-account-info { display: flex; flex-direction: column; gap: 16px; .account-item { display: flex; justify-content: space-between; align-items: center; padding: 12px; background-color: var(--el-fill-color-lighter); border-radius: 8px; .account-label { font-size: 14px; color: var(--el-text-color-regular); } .account-value { font-size: 16px; font-weight: 600; color: var(--el-text-color-primary); .primary { color: var(--el-color-primary); // 总资产蓝色高亮 font-size: 18px; } .price-up { color: #f56c6c; } // 盈利红 .price-down { color: #67c23a; } // 亏损绿 .price-neutral { color: var(--el-text-color-regular); } // 持平灰 } } } .empty-state { text-align: center; padding: 20px 0; .empty-icon { font-size: 48px; color: var(--el-text-color-placeholder); margin-bottom: 12px; } p { color: var(--el-text-color-secondary); margin-bottom: 16px; } } }视觉规范要点总资产使用primary类蓝色--el-color-primary 更大字号18px在四项指标中突出净资产这一核心数字账户行背景--el-fill-color-lighter卡片内二级层次感与自选股列表的纯文本区分CSS 变量体系颜色全部走 Element Plus Design Tokens--el-*深色/浅色主题下自动适配。多货币适配源码增强点实际仓库代码在账户行之上增加了account-section分组源码 L232-L283按币种渲染三组账户取值辅助函数如下const getCurrencyAmount ( amount: number | { CNY: number; HKD: number; USD: number } | undefined, currency: CNY | HKD | USD, fallback 0 ): number { if (typeof amount number) return amount // 旧格式直接返回标量 return amount?.[currency] ?? fallback // 新格式按币种取值 }渲染时按条件分节!-- A股账户 -- div classaccount-section div classaccount-section-title A股账户/div div classaccount-item div classaccount-label现金/div div classaccount-value¥{{ formatMoney(getCurrencyAmount(paperAccount.cash, CNY)) }}/div /div ... /div !-- 港股账户cash 为对象且存在 HKD 字段时显示 -- div classaccount-section v-iftypeof paperAccount.cash ! number paperAccount.cash?.HKD ! undefined ...HK$ 前缀... /div !-- 美股账户cash 为对象且存在 USD 字段时显示 -- div classaccount-section v-iftypeof paperAccount.cash ! number paperAccount.cash?.USD ! undefined ...$ 前缀... /div这样单币种旧账户只显示 A 股一行多货币账户自动扩展为三组无需改动模板主干。后端支撑/api/paper/account的多货币账户聚合仪表板卡片显示的四项数据全部来自后端 app/routers/paper.py 中的GET /paper/account接口源码 L270-L341。理解该接口才能知道前端拿到的cash / positions_value / equity / realized_pnl究竟如何计算。账户的创建与旧数据迁移接口首先调用_get_or_create_account()源码 L65-L115按user_id在 MongoDBpaper_accounts集合中查找账户不存在则创建新账户初始资金按市场配置INITIAL_CASH_BY_MARKET { CNY: 1_000_000.0, # A股100万人民币 HKD: 1_000_000.0, # 港股100万港币 USD: 100_000.0 # 美股10万美元 }新账户结构包含多货币cash、多货币realized_pnl与账户设置auto_currency_conversion、default_market。如果数据库中残留旧版标量结构会在这里自动迁移为多货币对象——这就是前端PaperAccountSummary需要CurrencyAmount | number联合类型、以及getCurrencyAmount()兜底的根因。持仓市值与总资产的计算接口从paper_positions集合读取全部持仓对每只持仓调用_get_last_price()源码 L194-L260获取最新价按币种累加市值再与现金汇总positions_value_by_currency {CNY: 0.0, HKD: 0.0, USD: 0.0} for p in positions: code, market, currency p.get(code), p.get(market, CN), p.get(currency, CNY) qty int(p.get(quantity, 0)) last await _get_last_price(code, market) # 最新价 mkt_value round((last or 0.0) * qty, 2) # 持仓市值 positions_value_by_currency[currency] mkt_value ...价格获取策略_get_last_priceA 股优先查market_quotes集合的close字段失败回退stock_basic_info的current_price港股/美股通过app.services.foreign_stock_service.ForeignStockService.get_quote()获取行情。最终汇总响应体源码 L321-L341summary { cash: {CNY: ..., HKD: ..., USD: ...}, realized_pnl: {CNY: ..., HKD: ..., USD: ...}, positions_value: positions_value_by_currency, # 持仓市值 equity: { # 总资产 现金 持仓市值分币种 CNY: round(cash[CNY] positions_value_by_currency[CNY], 2), HKD: round(cash[HKD] positions_value_by_currency[HKD], 2), USD: round(cash[USD] positions_value_by_currency[USD], 2), }, updated_at: acc.get(updated_at), } return ok({account: summary, positions: detailed_positions})由此可得仪表板四项指标的来源映射仪表板字段后端来源计算方式现金summary.cash账户cash含已扣除的手续费与买入金额持仓市值summary.positions_valueΣ(最新价 × 持仓数量)总资产summary.equitycash positions_value按币种已实现盈亏summary.realized_pnl卖出时(卖出价 - 均价) × 数量的累计源码 L464完整数据流结合前后端源码仪表板卡片的数据流可归纳为1. 页面加载 ↓ 2. onMounted() 调用 loadPaperAccount() (frontend/src/views/Dashboard/index.vue#L550-L560) ↓ 3. paperApi.getAccount() (frontend/src/api/paper.ts) ↓ GET /api/paper/account 4. 后端聚合账户信息 (app/routers/paper.py#L270-L341) - _get_or_create_account() 获取/创建账户含旧数据迁移 - 遍历持仓_get_last_price() 取最新价累加持仓市值 - 汇总 cash / positions_value / equity / realized_pnl ↓ 返回 { account: {...}, positions: [...] } 5. 前端校验 response.success response.data ↓ 6. 赋值 paperAccount.value ↓ 7. 模板 v-ifpaperAccount 渲染账户卡片含多货币分节功能展示与页面布局仪表板整体布局卡片插入后仪表板右侧栏自上而下为我的自选股 → 模拟交易账户新增→ 多数据源同步 → 市场快讯。原文档给出了完整布局示意┌──────────────────────────────┬──────────────────────────────────────┐ │ 快速操作 │ 我的自选股 │ │ │ ┌──────────────────────────────────┐ │ │ [单股分析] │ │ 300750 宁德时代 ¥402.00 5.68%│ │ │ [批量分析] │ │ 601288 农业银行 ¥6.67 0.00%│ │ │ [股票筛选] │ └──────────────────────────────────┘ │ │ [任务中心] │ │ │ │ 模拟交易账户 │ │ 最近分析 │ ┌──────────────────────────────────┐ │ │ ┌──────────────────────────┐ │ │ 现金 ¥2,329,863.00 │ │ │ │ 601288 农业银行 已完成 │ │ │ 持仓市值 ¥1,002,160.00 │ │ │ │ 601398 工商银行 已完成 │ │ │ 总资产 ¥7,691,970.00 │ │ │ └──────────────────────────┘ │ │ 已实现盈亏 ¥0.00 │ │ │ │ └──────────────────────────────────┘ │ │ │ │ │ │ 多数据源同步 │ │ │ 市场快讯 │ └──────────────────────────────┴──────────────────────────────────────┘账户信息卡片与空状态卡片本体样式为头部左侧标题模拟交易账户、右侧查看详情 →跳转按钮主体四行账户项其中总资产蓝色高亮、已实现盈亏按红/绿/灰着色┌─────────────────────────────────────┐ │ 模拟交易账户 [查看详情 →] │ ├─────────────────────────────────────┤ │ 现金 ¥2,329,863.00 │ │ 持仓市值 ¥1,002,160.00 │ │ 总资产 ¥7,691,970.00 (蓝色)│ │ 已实现盈亏 ¥0.00 (红/绿)│ └─────────────────────────────────────┘当接口失败或账户为空时渲染空状态居中图标 暂无账户信息 主色查看模拟交易按钮点击同样跳转/paper保证异常场景下仍提供明确的操作出口。测试与验收步骤参照原文档并对应真实代码可按以下六组用例验收测试 1正常显示。启动前端开发服务器默认http://localhost:5173登录后进入仪表板路由/dashboard确认自选股卡片下方出现模拟交易账户卡片四项指标均带千分位分隔符、总资产蓝色高亮、盈亏带颜色。测试 2金额格式化。校验formatMoney输出2329863.00 → ¥2,329,863.00 1002160.00 → ¥1,002,160.00 7691970.00 → ¥7,691,970.00测试 3盈亏颜色。getPnlClass对正数返回price-up红#f56c6c、负数返回price-down绿#67c23a、零返回price-neutral灰。测试 4跳转功能。点击查看详情应跳转到/paper对应路由PaperTrading见 frontend/src/router/index.ts。测试 5空状态。可临时中断后端或使接口抛错确认显示图标 暂无账户信息 查看模拟交易按钮且按钮可跳转。测试 6响应式。调整浏览器窗口至窄屏max-width: 768px确认卡片布局正常、内容不溢出移动端下卡片随列宽自然堆叠源码 L999-L1031 的媒体查询。后续优化方向原文档同时给出了可选的增强路线可作为功能迭代清单实时更新用setInterval每 30 秒自动轮询loadPaperAccount()让卡片反映最新成交与行情注意组件卸载时清理定时器持仓概览展示持仓数量与浮动盈亏后端GetAccountResponse.positions已返回unrealized_pnl前端可直接消费见 frontend/src/api/paper.ts收益率增加总收益率行复用getPnlClass着色图表展示用el-progress按positions_value / equity展示持仓占比直观呈现资产分布。其中持仓概览与收益率所需数据持仓市值、浮动盈亏后端接口已具备前端无需改动 API 层即可扩展是性价比最高的下一步。总结本文基于 docs/frontend/DASHBOARD_PAPER_TRADING.md 的功能设计结合仓库真实源码完整还原了仪表板模拟交易账户卡片的实现前端层面el-card双态模板 loadPaperAccount()生命周期加载 formatMoney/getPnlClass工具函数 SCSS 视觉规范构成一个可复用的账户摘要卡片模式实际实现还演进出了 A/H/US 多货币分节展示契约层面PaperAccountSummary的CurrencyAmount | number联合类型向后兼容旧账户数据getCurrencyAmount()兜底取值后端层面GET /api/paper/account通过_get_or_create_account()含旧数据迁移、持仓遍历 _get_last_price()行情获取按币种聚合出cash / positions_value / equity / realized_pnl再由ok()统一包装返回。该功能的实现路径API 封装 → 页面组件 → 生命周期加载 → 格式化工具 → 样式是 TradingAgents-CN 前端新增数据卡片的通用范式可推广到仪表板其他信息模块的扩展中。相关文档与源码索引功能设计文档docs/frontend/DASHBOARD_PAPER_TRADING.md仪表板页面实现frontend/src/views/Dashboard/index.vue模拟交易 API 封装frontend/src/api/paper.ts模拟交易后端接口app/routers/paper.py模拟交易路由配置frontend/src/router/index.ts【免费下载链接】TradingAgents-CN基于多智能体LLM的中文金融交易框架 - TradingAgents中文增强版项目地址: https://gitcode.com/GitHub_Trending/tr/TradingAgents-CN创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考