# Omniledger 架构与开发记忆 (Memory) ## 修复 calculateAssetMetrics 结果的汇率双重标准解析错误,重构 Live Overview 聚合基准 (Task 59) - **核心认知纠正:** `calculateAssetMetrics` 引擎产出的所有数据(`marketValue`, `accumulatedPnl`, `floatingPnl`, `dilutedCost` 等)全都是原始基础币种 (Base Currency)!绝对不存在"部分已经是 CNY"的情况。 - 修复 `src/actions/snapshots.ts` 的 `reconstructPortfolioHistory()`:之前错误地将 `cnyPrice`(已折算的人民币价格)传入引擎,却只对 `totalInvested` 乘汇率、对 `marketValue` 不乘,形成"双标幻觉"。现改为传入原始币种价格 `priceStr`,然后无例外地将引擎输出的所有金额字段统一乘以 `fxRate` 得到 CNY,确保 `posValueCny` 和 `posCostCny` 使用同一套汇率折算基准。 - 修复 `src/actions/portfolio.ts` 的 `getPortfolioSummary()`:废弃旧的 `cnyValue`/`pnlCny` 求和逻辑(旧逻辑基于 `calculateCnyValueFromPrice` 双路径计算,与引擎输出存在语义差异),改为从 `getPortfolioPositions()` 返回的 `marketValueCny`/`accumulatedPnlCny`/`floatingPnlCny` 字段在内存中累加,确保大盘汇总与底层明细使用同一套计算源(单一事实来源)。 - 同步修复 `recordDailySnapshot()`:`totalValueCny` 改为累加 `marketValueCny`,`totalCostCny` 改为 `marketValueCny - accumulatedPnlCny` 推导,与 `getPortfolioSummary` 保持一致。 - 彻底覆盖历史快照:重新执行 `reconstructPortfolioHistory()`,成功重构 1247 天历史快照数据,消除之前因混用汇率导致的错乱快照。 ## 全局修复多币种聚合漏洞,强制叠加汇率乘数 (Task 58) - 修复了跨币种资产直接相加导致的盈亏总额失真问题:USD 盈利未乘以 ~7.23 汇率被当作 CNY 计算,HKD 亏损同理。 - 在 `src/actions/portfolio.ts` 的 `getPortfolioPositions()` 中,对每个资产获取 `exchangeRate`(Base Currency → CNY),将财务引擎 (`calculateAssetMetrics`) 产出的所有绝对金额字段(`marketValue`、`floatingPnl`、`accumulatedPnl`、`dilutedCost`)乘以汇率,映射为 `Cny` 结尾的新字段,确保 Dashboard 列表中的 CNY 聚合数据精确。 - 在 `src/actions/snapshots.ts` 的 `reconstructPortfolioHistory()` 中,修复时光机逐日汇总逻辑:`metrics.marketValue` 已使用 CNY 价格计算可直接取用,`metrics.totalInvested` 基于原始币种价格需乘以 `exchangeRate` 折算为 CNY,确保历史成本曲线正确。 - 重新触发时光机清洗,成功重构 1247 天历史快照数据。 ## 重构历史快照生成逻辑,消除新旧算法断层 (Task 57) - 将时光机重构逻辑全面接入 finance utils 引擎,清洗历史脏快照,消除新旧算法迭代导致的本金曲线断层。 - 在 `src/utils/finance.ts` 的 `calculateAssetMetrics` 返回值中新增 `totalInvested` 字段,直接输出真实投入本金(含手续费),避免通过 `marketValue - accumulatedPnl` 间接推导导致的精度损失。 - 在 `src/actions/snapshots.ts` 中废弃 `reconstructPortfolioHistory()` 的旧版 Day-by-Day 加减法逻辑,改为:对每一天 `currentDate`,获取该资产在 `currentDate` 及之前的所有交易流水 `historicalTx` 和历史收盘价 `historicalPrice`(断点结转),调用 `calculateAssetMetrics(historicalTx, historicalPrice)` 获取 `metrics.marketValue` 和 `metrics.totalInvested`,分别累加为当天的 `totalValueCny` 和 `totalCostCny`。 - 重构后的 `reconstructPortfolioHistory()` 执行第一步调用 `db.delete(portfolioSnapshots)` 彻底清空旧的脏快照,然后从第一笔交易开始用新算法逐天重新生成,确保历史成本曲线平滑过渡、数值一致。 ## 基础设施与底层架构 - 完成根目录的 Next.js 初始化、基础依赖安装与环境变量配置。 - 完成基于单例模式的数据库连接配置,并设定 Drizzle 迁移工具。 - 修复网络连接,成功将 tables 推送至 PostgreSQL 数据库。 - 统一规范 Git 全量提交机制 (`git add -A`),确立了严谨的代码版本控制防腐层。 ## 数据库设计 (Schema) - 成功定义资产枚举与 `assets` 表,支持跨资产标识。 - 完成核心 `transactions` (交易流水) 表的建立,并严格运用了 `numeric(36,18)` 的高精度配置。 - `assets` 表完成多次业务演进:新增 `latestPrice` (支持现价追踪)、`exchange` (显式交易所绑定) 以及 `name` (中文名称解析) 字段。 - `exchange_rates` (汇率表) 已建立,支持联合主键与跨币种交叉汇率架构。 - **引入 `portfolio_snapshots` 表**:用于每日记录投资组合快照,字段包括 `date` (唯一日期)、`total_value_cny` (当日总市值)、`total_cost_cny` (当日总投入本金),为历史净值走势图奠定底层数据结构。 - **新增 `asset_prices_history` 表**:用于存储手动导入的每日标的价格,字段包括 `assetId` (关联资产)、`price` (当日收盘价/净值,numeric(36,18))、`date` (YYYY-MM-DD 格式)、`createdAt`,并对 `(assetId, date)` 建立联合唯一索引,为手动导入历史净值提供底层 Upsert 支持。 ## 核心业务与服务端逻辑 (Server Actions) - 完成高精度交易流水与资产的 Server Actions 开发,成功实现字符串级别的高精度防腐层拦截(基于 Zod & Big.js)。 - 补全资产与流水的全栈增删改查 (CRUD) 操作,`createTransaction` 现已支持根据 `exchange` 自动判定并锁定 `txCurrency`。 - **估值与 P&L 引擎:** 完成底层估值引擎升级,打通交叉汇率换算逻辑;实现原币种 (Native) 与本位币 (CNY Base) 双轨制的历史成本追溯与真实盈亏 (P&L) 计算引擎。 - **快照记录引擎:** 新增 `src/actions/snapshots.ts`,`recordDailySnapshot()` 函数基于 `getPortfolioPositions()` 实时计算总市值与总成本,使用 `Asia/Shanghai` 时区获取当日日期,执行 Upsert 逻辑确保每天仅存一条记录;`getSnapshots()` 支持按日期范围与数量限制查询历史快照数据。 ## 外部行情接口与网络 (Market Data Engines) - **股票行情引擎:** 彻底抛弃低效海外接口,自主研发智能路由接入腾讯财经 (`qt.gtimg.cn`) 极速接口。引入原生 `ArrayBuffer` 与 `TextDecoder(gbk)` 彻底解决历史中文乱码问题,实现沪、深、港、美四大市场毫秒级实时同步。 - **Crypto 行情引擎:** 接入币安 (Binance) 公共 API,构建全市场(股票+加密货币)双轨双擎驱动架构。 - **网络防腐层:** 引入 `undici` 底层网络库并配置 `setGlobalDispatcher`,成功突破境内防火墙对境外 API 的直连封锁 (Timeout)。 ## 前端架构与 UI/UX 体验 - 完成 shadcn/ui 初始化,集成 next-themes 支持暗黑模式,并拉取核心组件库。 - 完成 `/dashboard` 基础布局架构,接管根路由。 - 打通 `/dashboard/assets` 与 `/dashboard/transactions` 页面前后端数据流转,修复早期录入与 404 缺陷。 - 完成 UI 层高精度数据格式化,针对不同资产类型实现动态精度展示,清理因数据库 `numeric` 导致的尾随零问题。 - 引入 `recharts` 图表引擎,构建了基于实时 CNY 估值的资产分布环形图。 - 实装基于 Recharts 的历史净值面积图 (NetWorthChart),支持总市值与投入本金的双轨趋势对比。 - 优化表单交互:实装了交易所与币种的智能联动逻辑,并运用 `disabled` 属性实现了表单字段的只读防腐锁定。 ## UX 与全局交互 (UI/UX) - 引入 `sonner` 构建全局 Toast 消息通知系统,覆盖行情同步、CRUD 操作的成功与异常提示。 - 重构 `` 并将其提升至 Dashboard 首页,实现总资产大盘的全局一键实盘刷新。 ## 修复记录 - 解决了日期选择控件的时区偏移 Bug,确保全球通用:在 `src/libs/utils.ts` 中重写 `formatDateForDatetimeLocal()` 与 `parseDateTimeLocalToUTC_v2()` 函数,采用 `Intl.DateTimeFormat` 动态获取 `Asia/Shanghai` 时区偏移量,确保 UTC 时间到本地时间的双向转换精确无误,修复了用户选 10 点展示为 2 点的问题。修正了前端数据格式化逻辑,在 `src/lib/formatters.ts` 中增加空值/NaN 兜底处理,在 `src/app/dashboard/page.tsx` 中将平均成本与摊薄成本的显示条件从 `.gt(0)` 改为 `.ne(0)`,支持英特尔负成本等极端场景下的精确数字展示。 ## 资产分布图表按市场维度升级 (Task 32) - 优化资产分布图表,升级为按市场维度聚合展示,并增强了 Tooltip 的颜色指代与明细交互。 - 在 `portfolio.ts` 中新增 `getMarketFromExchange()` 函数,将资产按交易所归类为 A股 (SSE/SZSE)、港股 (HKEX)、美股 (US)、虚拟币 (CRYPTO)。 - 新增 `marketAllocation` 聚合数据,按市场维度汇总 `totalCnyValue` 并计算占比,自动过滤已清仓资产。 - 升级 `AllocationChart` 组件:数据源改为市场聚合数据,为各市场设定固定品牌色(A股红、港股黄、美股蓝、虚拟币绿),并自定义 Tooltip 渲染内容,悬停时清晰展示 `[市场名称] [对应颜色块] [CNY 金额] [占比%]`。 ## 盈亏引擎重构 (Task 31) - 重构盈亏计算引擎,支持已实现盈亏统计:交易按时间正序处理,SELL 时基于当时平均成本计算该笔卖出的利润并累加至 `realizedPnlCny`。 - 引入摊薄成本与平均成本双重指标:`avgCost = totalBuyCost / totalBuyQuantity`(平均成本);`dilutedCost = (totalBuyCost - realizedPnlCny) / currentQuantity`(摊薄成本,已实现盈亏会摊低持仓成本)。 - 新增持仓天数统计:`holdingDays = today - 第一次 BUY 的日期`(基于上海时区)。 - Dashboard 首页总览区分展示『持仓盈亏 (Unrealized P&L)』和『总盈亏 (Total P&L)』。 - 持仓卡片新增『平均成本』、『摊薄成本』和『持仓天数』展示。 ## 分紅業務邏輯與成本算法修復 (Task 33) - 重構了分紅的會計處理邏輯,將其正確計入已實現盈虧:DIVIDEND 不再增加持倉數量,而是按 `quantity * price * exchangeRate` 計算分紅金額並累加至 `realizedPnlCny`。 - 新增 `totalDividendCny` 字段追蹤累計分紅金額。 - 修正攤薄成本算法:`dilutedCost = (totalBuyCostCny - realizedPnlCny - totalDividendCny) / currentQuantity`,確保極端情況下攤薄成本為負數時精確返回負數,絕不兜底為 0。 - 平均成本 `avgCost = totalBuyCostCny / totalBuyQuantity` 保持不變,僅在遍歷結束後計算。 ## 修復平均成本顯示 Bug與分紅獨立統計 (Task 35) - 修復了平均成本在前端顯示為空的問題:`totalBuyQuantity` 在 BUY 交易處理中從未累加,導致 `avgCost` 計算時除數為零而永遠返回 0。現在在 BUY 時正確執行 `totalBuyQuantity += quantity`。 - 將分紅邏輯從已實現盈虧中剝離,建立獨立的累計分紅統計維度:新增 `accumulatedDividendsCny` 字段,DIVIDEND 交易不再混入 `realizedPnlCny`,而是獨立累加至 `accumulatedDividendsCny`。 - 重新定義總盈虧公式:`totalPnlCny = unrealizedPnlCny + realizedPnlCny + accumulatedDividendsCny`,確保分紅有獨立的統計維度且不會干擾平均成本計算。 ## 升級 Dashboard 資產卡片 UI (Task 36) - 升級了 Dashboard 資產卡片 UI,新增累計分紅展示,並優化了成本數據的格式化判斷邏輯。 - 修復了 `avgCostFormatted` 的判空邏輯,將 `Big.eq(0)` 修正為 `Big.eq('0')`,確保當 `pos.avgCost` 存在且不為 0 時能正確格式化,不再顯示 `¥-`。 - 在資產卡片中新增「累計分紅」行,展示 `accumulatedDividendsCny` 數據,保持與其它 CNY 數據一致的 `opacity-50` 樣式。 ## 修復幣種符號映射 Bug 與引擎數據擴充 (Task 40) - 修復了币种符号映射错乱 Bug:`getCurrencySymbol()` 中 CNY 和 HKD 被错误地映射为 `HK$`,现修正为 USD→$、HKD→HK$、CNY→¥。 - 在 portfolio 引擎中新增了单资产的流水明细数组:`Position` 接口新增 `transactions` 字段,`getPortfolioPositions()` 按资产名下发排序后的原始交易流水,支持前端展开查看。 ## 流水交易類型中文化映射 (Task 42a) - 在 `app/dashboard/page.tsx` 中新增 `txTypeMap` 字典,將 `BUY`/`SELL`/`DIVIDEND`/`AIRDROP` 映射為對應中文(買入/賣出/分紅/空投)。 - 流水明細子表格中渲染 `tx.txType` 的邏輯替換為 `{txTypeMap[tx.txType] || tx.txType}`,保留原始值兜底。 ## 利用 Big.js 剥离流水明细无意义尾随零 (Task 42b) - 利用 `Big.js` 剥离了流水明细中无意义的尾随零,提升了高精度数据的可读性。 - 在 `app/dashboard/page.tsx` 的流水明細子表格中,将 `tx.quantity`、`tx.price`、`tx.fee` 的渲染逻辑改为 `new Big(value).toString()`,安全剥离因数据库 `numeric(36,18)` 配置导致的如 `0.041000000000000000` 这类冗余尾随零。 ## 盈亏红绿视觉规范 (Task 42c) - 依据中文金融习惯(红涨绿跌),规范了盈亏数值的颜色与正负号显示。 - 移除了 `formatPnl()` 函数及概览行内硬编码拼接的 `+` 号前缀,正收益直接展示数值,负收益保留原生 `-` 号。 - 统一颜色逻辑:值 `> 0` 应用 `text-red-500`(红色),值 `< 0` 应用 `text-green-500`(绿色),值 `=== 0` 使用默认文字颜色。 - 括号内的百分比同步遵循相同逻辑,格式如 `$2447.48 (114.20%)`。 ## 修复快照读取引擎中的 Drizzle 语法错误 - 修复快照读取引擎中的 Drizzle 语法错误,全面改用类型安全的 desc 和 gte 操作符进行查询。 - 在 `src/actions/snapshots.ts` 中引入 `desc` 与 `gte` 操作符,彻底替换原始 SQL 模板拼接(`sql`"${date}" DESC``),消除 `ReferenceError: date is not defined` 运行时错误。 - 使用 `desc(portfolioSnapshots.date)` 实现降序排列,使用 `gte(portfolioSnapshots.date, startDate)` 实现日期范围过滤,并添加 `.$dynamic()` 支持动态条件拼接。 ## 持倉引擎 Native 幣種算法重構 (Task 38) - 重構底層盈虧引擎,全面轉向 Native 原生幣種計算,新增浮動/累計盈虧及百分比指標。 - 徹底分離 Native 與 CNY 計算:單隻股票的成本與盈虧全部改用 Native (原幣種) 進行計算。 - 新增 Native 成本指標:`totalBuyCostNative` (總買入成本)、`realizedPnlNative` (已實現盈虧)、`accumulatedDividendsNative` (累計分紅)。 - 新增 Native 成本均價:`avgCostNative = totalBuyCostNative / totalBuyQuantity`、`dilutedCostNative = (totalBuyCostNative - realizedPnlNative - accumulatedDividendsNative) / currentQuantity`。 - 新增浮動盈虧指標:`marketValueNative = latestPrice * currentQuantity`、`floatingPnlNative = marketValueNative - (avgCostNative * currentQuantity)`、`floatingPnlPercent = floatingPnlNative / (avgCostNative * currentQuantity) * 100`。 - 新增累計盈虧指標:`cumulativePnlNative = floatingPnlNative + realizedPnlNative + accumulatedDividendsNative`、`cumulativePnlPercent = cumulativePnlNative / totalBuyCostNative * 100`。 - SELL 交易的已實現盈虧計算從 CNY 基準改為 Native 基準,CNY 計算保留用於前端兼容展示。 ## Dashboard 流水下鑽明細與行內 CRUD (Task 41b) - 完成 Dashboard 流水下鑽功能,支持在資產列表中直接查看、修改和刪除歷史交易流水。 - 在主行 `TableRow` 下方,根據 `expandedIds[pos.assetId]` 條件渲染第二個子行,使用 `` 確保子行佔滿整行寬度。 - 構建流水明細次級表格:遍歷 `pos.transactions` 數組,表頭為「交易日期 | 類型 | 價格/數量 | 手續費 | 備註 | 操作」,精確渲染每筆交易的歷史數據。 - 實裝 `UpdateTransactionDialog` 組件:「修改」按鈕打開彈窗並回顯該筆流水數據(數量、價格、手續費、幣種、執行時間),提交後調用 `updateTransaction` Action 並刷新頁面。 - 實裝行內刪除功能:「刪除」按鈕彈出確認對話框,確認後調用 `deleteTransaction` Action 並刷新頁面。 - 在 `portfolio.ts` 的 `TransactionRecord` 接口中補充 `id` 和 `fee` 字段,並在下發數據時包含完整的流水 ID,以支持行內 CRUD 操作。 - UI 優化:為展開的子表格添加左側彩色邊框 (`border-l-4 border-primary/20`) 與左側縮進 (`ml-2`) 增強層級感;展開/收起按鈕文字根據狀態動態切換(「收起」/「展開」)。 ## Dashboard 表格化基礎重構 (Task 41) - Dashboard 完成表格化基礎重構,徹底移除原有的卡片網格佈局,改用 shadcn/ui Table 組件構建高密度專業券商風格主表。 - 實裝了基於 ID 的行展開狀態管理邏輯:`useState>` 控制每行的展開/收起狀態,點擊行或展開按鈕觸發 `toggleExpand`。 - 表頭嚴格對齊專業標準:名稱/代碼 | 現價 | 市值 | 持倉 | 攤薄/成本 | 浮動盈虧 | 累計盈虧 | 操作。 - 操作列包含展開/收起指示器與「添加」按鈕,點擊打開 `AddTransactionDialog` 並預選對應資產。 - 展開行內嵌交易記錄子表格,展示每筆交易的類型、數量、價格、手續費、幣種與日期。 - 重構 `AddTransactionDialog` 組件支持外部控制開關(`open`/`onOpenChange`/`defaultAssetId` props),同時保持向後兼容內部狀態管理模式。 - Dashboard 頁面轉換為客戶端組件,使用 `useEffect` 在客戶端加載組合數據。 ## 全面重構資產展示 UI (Task 39) - UI 全面升級,復刻專業券商級數據排版,合併攤薄/成本,引入原生幣種盈虧百分比展示。 - 徹底清理所有帶 (CNY) 和 (USD) 混雜的舊布局,所有 Native 金額根據 `baseCurrency` 渲染正確貨幣符號(USD→$、CNY/HKD→HK$、JPY→¥)。 - 資產卡片全新字段:現價、市值、持倉、攤薄/成本(合併為 `[dilutedCostNative] / [avgCostNative]` 格式)、浮動盈虧(帶百分比)、累計盈虧(帶百分比)、持倉天數。 - 盈虧顏色遵循中國市場慣例:大於 0 顯示紅色,小於 0 顯示綠色。 - 所有百分比保留 2 位小數,0 值正常顯示 `0.00`。 - 卡片佈局優化為響應式 `grid-cols-1 md:grid-cols-2 lg:grid-cols-3`。 ## 基於文本域粘貼的歷史價格批量導入功能 (Bulk Import, Task 49) - 開發了基於文本域粘貼的歷史價格批量導入功能,支持從 Excel 快速複製錄入每日淨價。 - 在 `src/actions/market.ts` 中新增 `importHistoricalPrices(assetId, data)` Server Action,遍歷數據數組對 `assetPricesHistory` 表執行批量 Upsert(基於 `(assetId, date)` 聯合唯一索引的 `onConflictDoUpdate`),衝突時用新價格覆蓋舊價格。 - 前端 `app/dashboard/page.tsx` 的持倉明細表「操作」列與展開區域均新增【導入價格】按鈕。 - 點擊按鈕彈出 Dialog,內含 `