From 33b5d72461d34415a2e44b17e97cf993bc354dab Mon Sep 17 00:00:00 2001 From: R524809 Date: Wed, 12 Nov 2025 18:06:09 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20UI=E7=A8=BF=E8=AE=BE=E8=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/investment-record-v2.html | 1626 +++++++++++++++++++++++++ src/investment-record.html | 1308 ++++++++++++++++++++ 我编写的文档/付费订阅数据库设计.md | 31 + 我编写的文档/投资记录-产品设计.md | 22 - 我编写的文档/投资记录模块-产品设计.md | 93 ++ 我编写的文档/数据库设计.md | 854 ++++++++----- 我编写的文档/整体产品设计思路.md | 26 + 我编写的文档/计划模块-产品设计.md | 6 + 8 files changed, 3606 insertions(+), 360 deletions(-) create mode 100644 src/investment-record-v2.html create mode 100644 src/investment-record.html delete mode 100644 我编写的文档/投资记录-产品设计.md create mode 100644 我编写的文档/投资记录模块-产品设计.md create mode 100644 我编写的文档/整体产品设计思路.md create mode 100644 我编写的文档/计划模块-产品设计.md diff --git a/src/investment-record-v2.html b/src/investment-record-v2.html new file mode 100644 index 0000000..91fc89e --- /dev/null +++ b/src/investment-record-v2.html @@ -0,0 +1,1626 @@ + + + + + + + 投资记录 - 买股票就是买公司 + + + + + + + + +
+ + +
+
+
+ +
+ 头像 +
+ + +
+
+
+
总资产
+
¥128,500
+
+
+ 0 + 100万 +
+
+
+
+ + +
+
+
累计收益率
+
+28.5%
+
+
+
年化收益率
+
+15.7%
+
+
+
+ + +
+ +
+
+

总资产趋势

+
+
+
+ + + + +
+
+
+
+ + +
+
+

累计收益率

+
+
+
+ + + + +
+
+
+
+
+ + +
+
+

持仓分布

+
+
+
+
+
+ + +
+
+

我的持仓

+ +
+
+ +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/src/investment-record.html b/src/investment-record.html new file mode 100644 index 0000000..f4a2e3d --- /dev/null +++ b/src/investment-record.html @@ -0,0 +1,1308 @@ + + + + + + + 投资记录 - 买股票就是买公司 + + + + + +
+ + + + +
+
+
张三
+
记账: 300天
+
+
+
¥128,500.00
+
+ 上一个交易日收益: + +¥1,200.00 +
+
+ 累计收益: + +¥28,500.00 +
+
+ 累计收益率: + +28.52% +
+
+ 年化收益率: + +15.68% +
+
+
+ + +
+ +
+
+
总资产趋势
+
+ + + + +
+
+
+
+ + +
+
+
累计收益率
+
+ + + + +
+
+
+
+
+ + +
+
+
持仓分布
+
+
+
+ + +
+
+

我的持仓

+ +
+
+ +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/我编写的文档/付费订阅数据库设计.md b/我编写的文档/付费订阅数据库设计.md index d503bde..2337e07 100644 --- a/我编写的文档/付费订阅数据库设计.md +++ b/我编写的文档/付费订阅数据库设计.md @@ -480,3 +480,34 @@ COMMIT; - 支付相关数据需要加密存储 - 交易号等敏感信息需要脱敏处理 +7. **持仓自动价格更新功能联动** + - 用户购买订阅后,需要批量更新该用户所有持仓的 `auto_price_update = true` + - 用户订阅过期或取消后,需要批量更新该用户所有持仓的 `auto_price_update = false` + - 建议在订阅状态变更时,同步更新 positions 表的 auto_price_update 字段 + + **示例SQL:** + ```sql + -- 用户购买订阅后,启用自动价格更新 + UPDATE positions + SET auto_price_update = true + WHERE user_id = :user_id + AND asset_type IN ('stock', 'fund', 'bond'); + + -- 用户订阅过期后,禁用自动价格更新 + UPDATE positions + SET auto_price_update = false + WHERE user_id = :user_id; + + -- 查询付费用户的股票持仓(用于自动价格更新) + SELECT + p.position_id, + p.symbol, + p.market, + p.asset_type, + p.current_price + FROM positions p + WHERE p.auto_price_update = true + AND p.asset_type = 'stock' + AND p.status = 'active'; + ``` + diff --git a/我编写的文档/投资记录-产品设计.md b/我编写的文档/投资记录-产品设计.md deleted file mode 100644 index 8762416..0000000 --- a/我编写的文档/投资记录-产品设计.md +++ /dev/null @@ -1,22 +0,0 @@ -# 投资记录模块-产品设计 -## 一、核心设计思路 -### 1.1 持仓变更设计 -1. 把所有的表更分为两种:`用户驱动(主动)` 和 `系统驱动(被动)` -2. 系统驱动包含:现金分红、送股、拆股、汇率变动等 -3. 用户驱动包含:初始买入、追加买入、卖出; -4. 所有的用户驱动,都只需要变更最终的持仓成本和最新的持仓份额,这两项。避免其他复杂的操作 -5. 每次用户主动变更,记录成本价和份额的同时,还需要完成如下记录: - - 反向计算本次交易股价和份额,并记录; - - 统计最新的份额和净值,并记录 - - 同时引导填下投资复盘和思考。 -6. 系统驱动的变更:(万一无法实现,可以降级为用户驱动变更) - - 分红:收盘后获取每股分红金额,最新成本价=原成本价 - 分红,市场价逻辑保持不变(使用不复权的股价) - - 送股、拆股等都变更最新的成本价和份额,并记录。 - -### 1.2 收益记录设计 -1. 使用基金净值法(时间加权收益率)来统计收益; -2. 每次主动和被动变更,重新计算总体的资产金额和份额,记录到 daily_snapshots 表。 -3. 忘记记录的情况:如果用户忘记记录当日交易,过一段时间后再来记录,需要删除期间的快照数据,并保留期间的交易数据。然后删除期间的快照数据,重新生成。 - -## 想法 -- 私密分享:可以将自己的交易计划和复盘,通过小程序私密分享-分享给其他人,这样即保障了裂变属性,有增加了隐私安全。 \ No newline at end of file diff --git a/我编写的文档/投资记录模块-产品设计.md b/我编写的文档/投资记录模块-产品设计.md new file mode 100644 index 0000000..56f7dfb --- /dev/null +++ b/我编写的文档/投资记录模块-产品设计.md @@ -0,0 +1,93 @@ +# 投资记录模块-产品设计 + +## 一、投资记录设计思路 +职责清晰:系统负责所有可规则化、基于市场公开事件的处理(被动变更);用户只负责输入自己主动发起的行为结果(主动变更)。 +输入极简:对于任何一笔交易,用户最核心、最确定的输入就是最终的持仓成本和最新的持仓份额。让用户只修改这两项,避免了理解复杂规则(如加权平均计算)的负担,也减少了输入错误的可能性。 +输入极简:对于任何一笔交易,用户最核心、最确定的输入就是最终的持仓成本和最新的持仓份额。让用户只修改这两项,避免了理解复杂规则(如加权平均计算)的负担,也减少了输入错误的可能性。 + +### 1.1 持仓变更设计 +1. 把所有的表更分为两种:`用户驱动(主动)` 和 `系统驱动(被动)` +2. 系统驱动包含:现金分红、送股、拆股、汇率变动等 +3. 用户驱动包含:初始买入、追加买入、卖出; +4. 所有的用户驱动,都只需要变更最终的持仓成本和最新的持仓份额,这两项。避免其他复杂的操作 +5. 每次用户主动变更,记录成本价和份额的同时,还需要完成如下记录: + - 反向计算本次交易股价和份额,并记录; + - 统计最新的份额和净值,并记录 + - 同时引导填下投资复盘和思考。 +6. 系统驱动的变更:(万一无法实现,可以降级为用户驱动变更) + - 分红:收盘后获取每股分红金额,最新成本价 = 原成本价 - 分红,市场价逻辑保持不变(使用不复权的股价) + - 送股、拆股等都变更最新的成本价和份额,并记录。 + +### 1.2 收益记录设计 +1. 使用基金净值法(时间加权收益率)来统计收益; +2. 每次主动和被动变更,重新计算总体的资产金额和份额,记录到 daily_snapshots 表。 +3. 忘记记录的情况:如果用户忘记记录当日交易,过一段时间后再来记录,需要删除期间的快照数据,并保留期间的交易数据。然后删除期间的快照数据,重新生成。 + +### 1.3 系统驱动变价记录逻辑 +1. 首先需要有一个表记录所有股票的基本信息,方便用户在输入code或股票名是进行模糊匹配。这个数据因为不常变更,可以放在CDN上。 +2. 每日定时把所有用户需要自动变价的持仓(股票/基金)汇总,在收盘后查询这些股票的最新市价、市值、市盈率等信息(所有用的是持仓最新价格应该依赖这个表,减少直接查询外部证券接口的次数) +3. 更新完股票的最新市价后,在定时更新用户持仓表中的持仓数据、收益数据,对应 positions 和 asset_snapshots 表。 + + +## 二、持仓页面详细设计 + +### 页面概述 +页面整体展示三部分内容: +- 资产概览、 +- 资产和收益图表(累计资产折线图、收益率折线图)、 +- 持仓百分比图 +- 我的持仓列表 + +### 功能需求 +#### 资产概览 +以卡片形式展示资产概览,左上角显示用户昵称,下边显示记账时长(例如 记账:300天),右侧从上到下分别展示总金额(大字红色展示)、上一个交易日收益、累计收益、累计收益率、年化收益率。 + +#### 收益图表 +- 第一个图表:默认展示从记账以来每日总金额的折线图,可以切换时间(近5日、本月、近一个月、近一年、记账以来、自定义) +- 第二个图表:默认展示从记账以来每日累计收益率,可以切换不同时间段(近5日、本月、近一个月、近一年、记账以来、自定义) +(PS: 你在输出设计稿的时候,需要使用 Echarts 来模拟) + +#### 持仓百分比 +使用环图,展示各个持仓资产项的金额百分比 +(PS: 你在输出设计稿的时候,需要使用 Echarts 来模拟) + +#### 我的持仓列表 +**展示形式** +title 左侧展示 “我的持仓”,右侧展示一个 圆形的加号(添加资产项) 按钮,可以添加新的资产项。 +以卡片形式展示 资产项: +- 左侧依次展示:公司简称(大号字体)、股票代码、市场、持股市场、证券公司简称(这一块你要帮我看看如何排列会更好) +- 中间展示:持股数、持股时长。 +- 右侧展示:从上到下依次展示昨日市价、盈亏金额、收益率。 +- 右小角展示一个 “更新资产”(名称可以你再帮我想一下) 的按钮,点击从底部弹出Popup页(变更资产页) + +**操作形式** +- 点击 圆形的“添加资产项”按钮,从底部弹出 新增资产页。 +- 点击 资产项卡片总的“更新资产”的按钮,从底部弹出 变更资产页 + + +## 三、新增资产页/变更资产页 +这个页面有两种形态,新增和变更,差别是新增最上面有搜索框,变更没有搜索框。都是冲底部弹出,再上滑,可以变成全屏页面。 + +**新增资产页** +最上面可以选择资产类型,分别是:股票、基金、现金、其他 + +股票: +先展示 搜索框,用户可以输入code或股票名称,自动联想,用户选择后确认股票。 +然后选择券商,下拉列表展示。 +选择后,之后依次展示股票名称、股票code、市场。 +之后展示成本价输入框和份数输入框。(输入时要考虑便捷性) + +基金: +不展示搜索框,仅展示输入框,需要用户自己输入。 +之后展示成本价输入框和份数输入框。(输入时要考虑便捷性) + +现金和其他: +不展示搜索框,仅展示输入框,需要用户自己输入资产名称。 +直接输入金额即可。 + +确认按钮:点击确认,提交数据到后台,按钮居中展示。 + +**变更资产页** +最上面不需要在选择资产类型,下边的展示和`新增资产页` 一样。 + + diff --git a/我编写的文档/数据库设计.md b/我编写的文档/数据库设计.md index bc2f9c7..7d45ba0 100644 --- a/我编写的文档/数据库设计.md +++ b/我编写的文档/数据库设计.md @@ -7,10 +7,15 @@ 用户相关 ├── users (用户表) -持仓相关 +基础数据相关 ├── brokers (券商表) +├── stock_info (股票基本信息表) +└── stock_daily_price (股票每日收盘价表) + +账户相关 ├── positions (持仓表) -└── position_price_plans (持仓价格目标表) +├── asset_snapshots (资产快照表) +└── position_price_plans (持仓价格计划表) ``` ## 用户相关表 @@ -100,337 +105,9 @@ COMMENT ON COLUMN users.last_login_at IS '最后登录时间'; ``` -### daily_snapshots 表设计 -**表结构** - -| 字段名 | 数据类型 | 约束 | 说明 | -|--------|---------|------|------| -| id | BIGSERIAL | PRIMARY KEY | 快照ID,自增 | -| user_id | BIGINT | NOT NULL, FOREIGN KEY | 用户ID,关联 users 表 | -| date | DATE | NOT NULL | 快照日期 | -| total_asset | DECIMAL(18, 2) | NOT NULL | 总资产(所有持仓市值 + 现金余额) | -| total_invested | DECIMAL(18, 2) | NOT NULL | 累计投入金额(所有投入的资金总和) | -| time_weighted_return | DECIMAL(10, 6) | NOT NULL | 时间加权收益率(累计收益率) | -| annualized_return | DECIMAL(10, 6) | | 年化收益率 | -| year_to_date_return | DECIMAL(10, 6) | | 当年收益率(年初至今) | -| positions_data | JSONB | | 持仓明细快照(JSON格式,可选) | -| cash_data | JSONB | | 现金账户明细快照(JSON格式,可选) | -| created_at | TIMESTAMP | NOT NULL, DEFAULT CURRENT_TIMESTAMP | 创建时间 | -| UNIQUE(user_id, date) | | | 同一用户同一日期只能有一条快照 | - -**创建语句** - -```sql -CREATE TABLE daily_snapshots ( - id BIGSERIAL PRIMARY KEY, - user_id BIGINT NOT NULL REFERENCES users(user_id) ON DELETE CASCADE, - date DATE NOT NULL, - total_asset DECIMAL(18, 2) NOT NULL, - total_invested DECIMAL(18, 2) NOT NULL, - time_weighted_return DECIMAL(10, 6) NOT NULL DEFAULT 0, - annualized_return DECIMAL(10, 6), - year_to_date_return DECIMAL(10, 6), - positions_data JSONB, - cash_data JSONB, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - UNIQUE(user_id, date) -); - --- 创建索引 -CREATE INDEX idx_daily_snapshots_user_id ON daily_snapshots(user_id); -CREATE INDEX idx_daily_snapshots_date ON daily_snapshots(date); -CREATE INDEX idx_daily_snapshots_user_date ON daily_snapshots(user_id, date DESC); - --- 添加注释 -COMMENT ON TABLE daily_snapshots IS '每日资产快照表,用于基金净值法计算收益率'; -COMMENT ON COLUMN daily_snapshots.total_asset IS '总资产:所有持仓市值 + 现金余额'; -COMMENT ON COLUMN daily_snapshots.total_invested IS '累计投入金额:所有投入的资金总和'; -COMMENT ON COLUMN daily_snapshots.time_weighted_return IS '时间加权收益率(累计收益率)'; -COMMENT ON COLUMN daily_snapshots.positions_data IS '持仓明细快照,JSON格式存储'; -COMMENT ON COLUMN daily_snapshots.cash_data IS '现金账户明细快照,JSON格式存储'; -``` - -#### 基础字段计算 - -**total_asset(总资产)** -``` -total_asset = Σ(持仓市值) + 现金余额 - -其中: -- 持仓市值 = 持仓份额 × 当前价格 -- 现金余额 = 所有现金账户余额之和 -``` - -**total_invested(累计投入金额)** -``` -total_invested = 初始投入 + 后续投入 - 提取金额 - -说明: -- 初始投入:账户创建时的初始资金 -- 后续投入:用户手动记录的资金投入 -- 提取金额:用户提取的资金(提现等) -- 注意:买入股票的资金不算"投入",因为只是资产形式转换 -``` - -#### 净值计算(不存储,查询时计算) - -**net_value(单位净值)** -```sql --- 查询时计算 -net_value = total_asset / NULLIF(total_invested, 0) - --- 示例 SQL -SELECT - date, - total_asset, - total_invested, - total_asset / NULLIF(total_invested, 0) as net_value -FROM daily_snapshots -WHERE user_id = :user_id -ORDER BY date; -``` - -**total_profit(总收益)** -```sql --- 查询时计算 -total_profit = total_asset - total_invested - --- 示例 SQL -SELECT - date, - total_asset, - total_invested, - total_asset - total_invested as total_profit -FROM daily_snapshots -WHERE user_id = :user_id -ORDER BY date; -``` - -#### 收益率计算 - -**time_weighted_return(时间加权收益率)** - -基于基金净值法,通过相邻日期的净值变化计算: - -```sql --- 计算逻辑 --- 1. 计算每日净值 -net_value_today = total_asset_today / total_invested_today -net_value_yesterday = total_asset_yesterday / total_invested_yesterday - --- 2. 计算日收益率 -daily_return = (net_value_today - net_value_yesterday) / net_value_yesterday - --- 3. 累计收益率(复利) -time_weighted_return = (1 + r1) × (1 + r2) × ... × (1 + rn) - 1 - --- 实际存储时,存储累计收益率 -``` - -**annualized_return(年化收益率)** - -```sql --- 计算逻辑 -annualized_return = (1 + time_weighted_return)^(365 / 投资天数) - 1 - --- 投资天数 = 当前日期 - 首次投入日期 -``` - -**year_to_date_return(当年收益率)** - -```sql --- 计算逻辑 --- 1. 获取年初快照 -year_start_snapshot = SELECT * FROM daily_snapshots - WHERE user_id = :user_id - AND date = DATE_TRUNC('year', CURRENT_DATE) - --- 2. 计算当年收益率 -year_to_date_return = (current_net_value - year_start_net_value) / year_start_net_value - --- 如果没有年初快照,使用年初第一个快照 -``` - -#### 快照数据生成规则 - -**positions_data(持仓明细快照)** - -```json -[ - { - "position_id": 1, - "symbol": "600519", - "name": "贵州茅台", - "shares": 100, - "cost_price": 1600.00, - "current_price": 1850.00, - "market_value": 185000.00, - "profit": 25000.00 - }, - { - "position_id": 2, - "symbol": "00700", - "name": "腾讯控股", - "shares": 200, - "cost_price": 320.00, - "current_price": 300.00, - "market_value": 60000.00, - "profit": -4000.00 - } -] -``` - -**cash_data(现金账户明细快照)** - -```json -[ - { - "account_id": 1, - "currency": "CNY", - "balance": 50000.00 - }, - { - "account_id": 2, - "currency": "USD", - "balance": 1000.00, - "exchange_rate": 7.2, - "balance_cny": 7200.00 - } -] -``` - -#### 每日快照生成流程 - -```sql --- 伪代码流程 -1. 获取用户所有持仓 - SELECT * FROM positions WHERE user_id = :user_id AND status = 'active' - -2. 获取用户所有现金账户 - SELECT * FROM cash_accounts WHERE account_id IN (用户账户列表) - -3. 计算总资产 - total_asset = Σ(持仓市值) + Σ(现金余额) - -4. 计算累计投入(从资金变动记录表获取) - total_invested = SELECT SUM(amount) FROM cash_flows - WHERE user_id = :user_id AND flow_type = 'deposit' - - SELECT SUM(amount) FROM cash_flows - WHERE user_id = :user_id AND flow_type = 'withdraw' - -5. 计算时间加权收益率 - - 获取昨日快照 - - 计算净值变化 - - 更新累计收益率 - -6. 计算年化收益率和当年收益率 - -7. 生成持仓和现金明细快照(JSON格式) - -8. 插入或更新快照记录 - INSERT INTO daily_snapshots (...) - ON CONFLICT (user_id, date) DO UPDATE SET ... -``` - -#### 时间加权收益率更新逻辑 - -```sql --- 获取昨日快照 -WITH yesterday_snapshot AS ( - SELECT * FROM daily_snapshots - WHERE user_id = :user_id - AND date = CURRENT_DATE - INTERVAL '1 day' -), -today_data AS ( - SELECT - :total_asset as total_asset, - :total_invested as total_invested -) --- 计算今日净值 -SELECT - (today_data.total_asset / NULLIF(today_data.total_invested, 0)) as today_net_value, - (yesterday_snapshot.total_asset / NULLIF(yesterday_snapshot.total_invested, 0)) as yesterday_net_value -FROM today_data, yesterday_snapshot; - --- 计算日收益率 -daily_return = (today_net_value - yesterday_net_value) / yesterday_net_value - --- 更新累计收益率 -new_time_weighted_return = (1 + yesterday_snapshot.time_weighted_return) × (1 + daily_return) - 1 -``` -#### 查询用户净值曲线 - -```sql -SELECT - date, - total_asset, - total_invested, - total_asset / NULLIF(total_invested, 0) as net_value, - total_asset - total_invested as total_profit, - time_weighted_return, - annualized_return -FROM daily_snapshots -WHERE user_id = :user_id -ORDER BY date DESC -LIMIT 365; -- 最近一年 -``` - -#### 查询收益率统计 - -```sql -SELECT - date, - time_weighted_return * 100 as return_rate_percent, - annualized_return * 100 as annualized_return_percent, - year_to_date_return * 100 as ytd_return_percent -FROM daily_snapshots -WHERE user_id = :user_id -ORDER BY date DESC -LIMIT 30; -- 最近30天 -``` - -#### 查询持仓明细历史 - -```sql -SELECT - date, - positions_data -FROM daily_snapshots -WHERE user_id = :user_id - AND positions_data IS NOT NULL -ORDER BY date DESC -LIMIT 10; -``` - -#### 注意事项 - -1. **数据一致性** - - `total_invested` 必须大于 0,否则净值计算会出错 - - 使用 `NULLIF(total_invested, 0)` 避免除零错误 - -2. **性能优化** - - 每日快照在收盘后批量生成(如 18:00) - - 使用索引加速查询(user_id, date) - - positions_data 和 cash_data 使用 JSONB 类型,支持高效查询 - -3. **数据完整性** - - 确保每日都有快照(即使没有交易) - - 如果某日没有快照,使用最近一次快照的数据 - -4. **净值计算** - - 净值不存储,查询时计算,保证数据一致性 - - 总收益也不存储,查询时计算 - -5. **收益率计算** - - 时间加权收益率需要每日更新 - - 年化收益率需要定期重新计算(因为投资天数在变化) - - 当年收益率需要每年重置计算基准 - ---- - -## 持仓相关表设计 +## 基础数据相关表设计 ### brokers 券商表 @@ -528,6 +205,238 @@ INSERT INTO brokers (broker_code, broker_name, region, sort_order) VALUES --- +### stock_info 股票基本信息表 + +**表结构** + +| 字段名 | 数据类型 | 约束 | 说明 | +|--------|---------|------|------| +| id | BIGSERIAL | PRIMARY KEY | 主键ID,自增 | +| stock_code | VARCHAR(20) | NOT NULL | 股票代码(如:600519, 00700.HK) | +| stock_name | VARCHAR(100) | NOT NULL | 股票名称 | +| market | VARCHAR(20) | NOT NULL | 市场标识(A股: sh/sz/bj, 港股: hk, 美股: us等) | +| full_name | VARCHAR(200) | | 公司全称 | +| industry | VARCHAR(100) | | 所属行业 | +| listing_date | DATE | | 上市日期 | +| status | VARCHAR(20) | NOT NULL, DEFAULT 'active', CHECK | 状态:active(正常)/suspended(停牌)/delisted(退市) | +| created_at | TIMESTAMP | NOT NULL, DEFAULT CURRENT_TIMESTAMP | 创建时间 | +| updated_at | TIMESTAMP | NOT NULL, DEFAULT CURRENT_TIMESTAMP | 更新时间 | +| UNIQUE(stock_code, market) | | | 同一市场股票代码唯一 | + +**创建语句** + +```sql +CREATE TABLE stock_info ( + id BIGSERIAL PRIMARY KEY, + stock_code VARCHAR(20) NOT NULL, + stock_name VARCHAR(100) NOT NULL, + market VARCHAR(20) NOT NULL, + full_name VARCHAR(200), + industry VARCHAR(100), + listing_date DATE, + status VARCHAR(20) NOT NULL DEFAULT 'active', + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + -- 同一市场股票代码唯一 + UNIQUE(stock_code, market), + CONSTRAINT check_stock_status CHECK (status IN ('active', 'suspended', 'delisted')) +); + +-- 创建索引 +CREATE INDEX idx_stock_info_code ON stock_info(stock_code); +CREATE INDEX idx_stock_info_market ON stock_info(market); +CREATE INDEX idx_stock_info_name ON stock_info(stock_name); +CREATE INDEX idx_stock_info_status ON stock_info(status); +CREATE INDEX idx_stock_info_code_market ON stock_info(stock_code, market); +-- 全文搜索索引(用于股票名称模糊匹配,需要先启用 pg_trgm 扩展) +-- CREATE EXTENSION IF NOT EXISTS pg_trgm; +-- CREATE INDEX idx_stock_info_name_trgm ON stock_info USING gin(stock_name gin_trgm_ops); + +-- 创建触发器自动更新 updated_at +CREATE TRIGGER update_stock_info_updated_at + BEFORE UPDATE ON stock_info + FOR EACH ROW + EXECUTE FUNCTION update_updated_at_column(); + +-- 添加注释 +COMMENT ON TABLE stock_info IS '股票基本信息表,存储所有市场的股票基本静态信息'; +COMMENT ON COLUMN stock_info.stock_code IS '股票代码,如:600519(A股)、00700.HK(港股)、AAPL(美股)'; +COMMENT ON COLUMN stock_info.stock_name IS '股票名称,用于显示和搜索'; +COMMENT ON COLUMN stock_info.market IS '市场标识:sh(上海)/sz(深圳)/bj(北京)/hk(香港)/us(美国)等'; +COMMENT ON COLUMN stock_info.status IS '状态:active(正常交易)/suspended(停牌)/delisted(退市)'; +``` + +**说明:** +- 此表存储所有市场的股票基本信息,用于用户输入时的模糊匹配 +- 数据不常变更,可以定期从外部数据源同步 +- 支持全文搜索,方便用户通过股票名称快速查找 + +--- + +### stock_daily_price 股票每日收盘价表 + +**表结构** + +| 字段名 | 数据类型 | 约束 | 说明 | +|--------|---------|------|------| +| id | BIGSERIAL | PRIMARY KEY | 主键ID,自增 | +| stock_code | VARCHAR(20) | NOT NULL | 股票代码 | +| market | VARCHAR(20) | NOT NULL | 市场标识 | +| trade_date | DATE | NOT NULL | 交易日期 | +| open_price | DECIMAL(18, 4) | | 开盘价 | +| close_price | DECIMAL(18, 4) | NOT NULL | 收盘价 | +| high_price | DECIMAL(18, 4) | | 最高价 | +| low_price | DECIMAL(18, 4) | | 最低价 | +| volume | BIGINT | | 成交量(单位:手) | +| amount | DECIMAL(20, 2) | | 成交额(单位:元) | +| change_amount | DECIMAL(18, 4) | | 涨跌额 | +| change_percent | DECIMAL(10, 6) | | 涨跌幅(%) | +| turnover_rate | DECIMAL(10, 6) | | 换手率(%) | +| pe_ratio | DECIMAL(12, 4) | | 市盈率 | +| pb_ratio | DECIMAL(12, 4) | | 市净率 | +| market_cap | DECIMAL(20, 2) | | 总市值(单位:元) | +| created_at | TIMESTAMP | NOT NULL, DEFAULT CURRENT_TIMESTAMP | 创建时间 | +| UNIQUE(stock_code, market, trade_date) | | | 同一股票同一日期只能有一条记录 | + +**创建语句** + +```sql +CREATE TABLE stock_daily_price ( + id BIGSERIAL PRIMARY KEY, + stock_code VARCHAR(20) NOT NULL, + market VARCHAR(20) NOT NULL, + trade_date DATE NOT NULL, + open_price DECIMAL(18, 4), + close_price DECIMAL(18, 4) NOT NULL, + high_price DECIMAL(18, 4), + low_price DECIMAL(18, 4), + volume BIGINT, + amount DECIMAL(20, 2), + change_amount DECIMAL(18, 4), + change_percent DECIMAL(10, 6), + turnover_rate DECIMAL(10, 6), + pe_ratio DECIMAL(12, 4), + pb_ratio DECIMAL(12, 4), + market_cap DECIMAL(20, 2), + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + -- 同一股票同一日期只能有一条记录 + UNIQUE(stock_code, market, trade_date), + -- 外键关联股票基本信息表(可选,如果数据源可靠可以不加) + CONSTRAINT fk_stock_price_info FOREIGN KEY (stock_code, market) + REFERENCES stock_info(stock_code, market) ON DELETE CASCADE +); + +-- 创建索引 +CREATE INDEX idx_stock_daily_price_code ON stock_daily_price(stock_code); +CREATE INDEX idx_stock_daily_price_market ON stock_daily_price(market); +CREATE INDEX idx_stock_daily_price_date ON stock_daily_price(trade_date); +CREATE INDEX idx_stock_daily_price_code_market ON stock_daily_price(stock_code, market); +CREATE INDEX idx_stock_daily_price_code_date ON stock_daily_price(stock_code, market, trade_date DESC); +-- 用于查询最新价格的索引 +CREATE INDEX idx_stock_daily_price_latest ON stock_daily_price(stock_code, market, trade_date DESC); + +-- 添加注释 +COMMENT ON TABLE stock_daily_price IS '股票每日收盘价表,存储所有市场的股票每日收盘价及相关交易数据'; +COMMENT ON COLUMN stock_daily_price.stock_code IS '股票代码,关联 stock_info 表'; +COMMENT ON COLUMN stock_daily_price.close_price IS '收盘价,用于更新持仓的 current_price'; +COMMENT ON COLUMN stock_daily_price.trade_date IS '交易日期,用于查询历史价格'; +COMMENT ON COLUMN stock_daily_price.market_cap IS '总市值,单位:元'; +``` + +**使用说明:** + +1. **查询最新价格(用于更新持仓)** +```sql +-- 查询指定股票的最新收盘价 +SELECT + stock_code, + market, + close_price, + trade_date +FROM stock_daily_price +WHERE stock_code = :stock_code + AND market = :market +ORDER BY trade_date DESC +LIMIT 1; + +-- 批量查询多个股票的最新价格 +SELECT DISTINCT ON (stock_code, market) + stock_code, + market, + close_price, + trade_date +FROM stock_daily_price +WHERE (stock_code, market) IN ( + ('600519', 'sh'), + ('00700', 'hk'), + ('AAPL', 'us') +) +ORDER BY stock_code, market, trade_date DESC; +``` + +2. **查询历史价格(用于图表展示)** +```sql +-- 查询指定股票的历史价格 +SELECT + trade_date, + open_price, + close_price, + high_price, + low_price, + volume, + change_percent +FROM stock_daily_price +WHERE stock_code = :stock_code + AND market = :market + AND trade_date >= :start_date +ORDER BY trade_date; +``` + +3. **每日价格更新流程** +```sql +-- 步骤1:从外部数据源获取最新价格数据 +-- 步骤2:批量插入或更新价格数据 +INSERT INTO stock_daily_price ( + stock_code, market, trade_date, open_price, close_price, + high_price, low_price, volume, amount, change_amount, + change_percent, turnover_rate, pe_ratio, pb_ratio, market_cap +) +VALUES (...) +ON CONFLICT (stock_code, market, trade_date) +DO UPDATE SET + open_price = EXCLUDED.open_price, + close_price = EXCLUDED.close_price, + high_price = EXCLUDED.high_price, + low_price = EXCLUDED.low_price, + volume = EXCLUDED.volume, + amount = EXCLUDED.amount, + change_amount = EXCLUDED.change_amount, + change_percent = EXCLUDED.change_percent, + turnover_rate = EXCLUDED.turnover_rate, + pe_ratio = EXCLUDED.pe_ratio, + pb_ratio = EXCLUDED.pb_ratio, + market_cap = EXCLUDED.market_cap; + +-- 步骤3:更新持仓表的 current_price(仅更新 auto_price_update = true 的持仓) +UPDATE positions p +SET current_price = ( + SELECT close_price + FROM stock_daily_price sdp + WHERE sdp.stock_code = p.symbol + AND sdp.market = p.market + ORDER BY sdp.trade_date DESC + LIMIT 1 +), +updated_at = CURRENT_TIMESTAMP +WHERE p.auto_price_update = true + AND p.asset_type IN ('stock', 'fund') + AND p.status = 'active'; +``` + +--- + +## 账户相关表设计 + ### positions 持仓表 记录用户持仓情况,包含数量、成本价、最新市场价、券商等 **表结构** @@ -546,6 +455,7 @@ INSERT INTO brokers (broker_code, broker_name, region, sort_order) VALUES | current_price | DECIMAL(18, 4) | | 最新市场价(系统自动更新) | | currency | VARCHAR(10) | NOT NULL, DEFAULT 'CNY' | 货币类型 | | exchange_rate | DECIMAL(10, 6) | DEFAULT 1 | 汇率(用于多货币) | +| auto_price_update | BOOLEAN | NOT NULL, DEFAULT false | 是否自动更新价格(付费用户功能) | | status | VARCHAR(20) | NOT NULL, DEFAULT 'active', CHECK | 状态:active/suspended/delisted | | created_at | TIMESTAMP | NOT NULL, DEFAULT CURRENT_TIMESTAMP | 创建时间 | | updated_at | TIMESTAMP | NOT NULL, DEFAULT CURRENT_TIMESTAMP | 更新时间 | @@ -566,6 +476,7 @@ CREATE TABLE positions ( current_price DECIMAL(18, 4), currency VARCHAR(10) NOT NULL DEFAULT 'CNY', exchange_rate DECIMAL(10, 6) DEFAULT 1, + auto_price_update BOOLEAN NOT NULL DEFAULT false, status VARCHAR(20) NOT NULL DEFAULT 'active', created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, @@ -585,6 +496,8 @@ CREATE INDEX idx_positions_asset_type ON positions(asset_type); CREATE INDEX idx_positions_status ON positions(status); CREATE INDEX idx_positions_user_status ON positions(user_id, status); CREATE INDEX idx_positions_user_broker ON positions(user_id, broker_id); +CREATE INDEX idx_positions_auto_price_update ON positions(auto_price_update); +CREATE INDEX idx_positions_auto_price_asset ON positions(auto_price_update, asset_type, status); CREATE INDEX idx_positions_updated_at ON positions(updated_at); -- 创建触发器自动更新 updated_at @@ -600,12 +513,258 @@ COMMENT ON COLUMN positions.asset_type IS '资产类型:stock(股票)/fund(基 COMMENT ON COLUMN positions.symbol IS '资产代码,如股票代码600519、基金代码等'; COMMENT ON COLUMN positions.shares IS '持仓份额/数量,股票为股数,基金为份数,现金为金额'; COMMENT ON COLUMN positions.cost_price IS '成本价,用户直接修改,系统不自动计算'; -COMMENT ON COLUMN positions.current_price IS '最新市场价,系统每日自动更新'; +COMMENT ON COLUMN positions.current_price IS '最新市场价,系统每日自动更新(仅auto_price_update=true的持仓)'; COMMENT ON COLUMN positions.exchange_rate IS '汇率,用于多货币资产,如港股、美股'; +COMMENT ON COLUMN positions.auto_price_update IS '是否自动更新价格,true表示系统每日自动更新市场价格(付费用户功能)'; ``` --- + +### asset_snapshots (资产快照表) +**表结构(新版)** + +| 字段名 | 数据类型 | 约束 | 说明 | +|---------------------|----------------|----------------------------------|---------------------------------------------------------| +| id | BIGSERIAL | PRIMARY KEY | 快照ID,自增 | +| user_id | BIGINT | NOT NULL, FOREIGN KEY | 用户ID,关联 users 表 | +| snapshot_date | DATE | NOT NULL | 快照日期 | +| total_asset | DECIMAL(18,2) | NOT NULL | 总资产(所有持仓市值 + 现金余额) | +| total_invested | DECIMAL(18,2) | NOT NULL | 累计投入金额(初始投入 + 后续投入 - 提取金额) | +| time_weighted_return| DECIMAL(12,8) | | 时间加权收益率(复利累计收益率) | +| annualized_return | DECIMAL(10,6) | | 年化收益率 | +| year_to_date_return | DECIMAL(10,6) | | 当年收益率(年初至今) | +| positions_data | JSONB | | 持仓明细快照(JSON格式,可选) | +| cash_data | JSONB | | 现金账户明细快照(JSON格式,可选) | +| created_at | TIMESTAMP | NOT NULL, DEFAULT CURRENT_TIMESTAMP | 创建时间 | +| updated_at | TIMESTAMP | NOT NULL, DEFAULT CURRENT_TIMESTAMP | 更新时间 | +| UNIQUE(user_id, snapshot_date) | | | 同一用户同一日期只能有一条快照 | + +**创建语句** + +```sql +CREATE TABLE asset_snapshots ( + id BIGSERIAL PRIMARY KEY, + user_id BIGINT NOT NULL REFERENCES users(user_id) ON DELETE CASCADE, + snapshot_date DATE NOT NULL, + total_asset DECIMAL(18,2) NOT NULL, + total_invested DECIMAL(18,2) NOT NULL, + time_weighted_return DECIMAL(12,8), + annualized_return DECIMAL(10,6), + year_to_date_return DECIMAL(10,6), + positions_data JSONB, + cash_data JSONB, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + UNIQUE(user_id, snapshot_date) +); +``` + +-- 创建索引 +```sql +CREATE INDEX idx_as_user_id ON asset_snapshots(user_id); +CREATE INDEX idx_as_snapshot_date ON asset_snapshots(snapshot_date); +CREATE INDEX idx_as_user_date ON asset_snapshots(user_id, snapshot_date DESC); +``` + +-- 添加注释 +```sql +COMMENT ON TABLE asset_snapshots IS '每日资产快照表,记录总资产、累计投入、收益率等,用于计算净值、收益'; +COMMENT ON COLUMN asset_snapshots.user_id IS '用户ID,关联 users 表'; +COMMENT ON COLUMN asset_snapshots.snapshot_date IS '快照日期'; +COMMENT ON COLUMN asset_snapshots.total_asset IS '总资产:所有持仓市值 + 现金余额'; +COMMENT ON COLUMN asset_snapshots.total_invested IS '累计投入金额:初始资金 + 后续投入 - 提取金额'; +COMMENT ON COLUMN asset_snapshots.time_weighted_return IS '时间加权收益率(复利累计收益率)'; +COMMENT ON COLUMN asset_snapshots.annualized_return IS '年化收益率'; +COMMENT ON COLUMN asset_snapshots.year_to_date_return IS '当年收益率'; +COMMENT ON COLUMN asset_snapshots.positions_data IS '持仓明细快照,JSON格式存储'; +COMMENT ON COLUMN asset_snapshots.cash_data IS '现金账户明细快照,JSON格式存储'; +COMMENT ON COLUMN asset_snapshots.created_at IS '快照创建时间'; +COMMENT ON COLUMN asset_snapshots.updated_at IS '快照更新时间'; +``` + +#### 字段计算说明与用法 + +**total_asset(总资产)** +``` +total_asset = Σ(持仓市值) + 现金余额 +其中: +- 持仓市值 = 持仓份额 × 当前价格 +- 现金余额 = 所有现金账户余额之和 +``` + +**total_invested(累计投入金额)** +``` +total_invested = 初始投入 + 后续投入 - 提取金额 +说明: +- 初始投入:账户创建时的初始资金 +- 后续投入:用户手动记录的资金投入 +- 提取金额:用户提取的资金(提现、转出) +- 注意:买入证券只是资金流转,非"投入" +``` + +**net_value(单位净值)及 total_profit(总收益)** +- 两者不再存储字段,需通过查询实时计算(避免冗余数据和不一致): + +```sql +-- 单位净值(需要时动态计算) +net_value = total_asset / NULLIF(total_invested, 0) + +-- 总收益(需要时动态计算) +total_profit = total_asset - total_invested +``` + +**时间加权收益率(time_weighted_return)逻辑** + +```sql +-- 1. 计算每日净值 +net_value_today = total_asset_today / NULLIF(total_invested_today, 0) +net_value_yesterday = total_asset_yesterday / NULLIF(total_invested_yesterday, 0) + +-- 2. 日收益率 +daily_return = (net_value_today - net_value_yesterday) / NULLIF(net_value_yesterday, 0) + +-- 3. 累计收益率(复利) +new_twr = (1 + 昨日 time_weighted_return) × (1 + daily_return) - 1 +``` + +**年化收益率(annualized_return)** +```sql +annualized_return = (1 + time_weighted_return) ^ (365 / 投资天数) - 1 +``` + +**当年收益率(year_to_date_return)** +```sql +-- 获取年初快照,计算净值 +year_to_date_return = (current_net_value - year_start_net_value) / year_start_net_value +``` + +#### 数据结构示例 + +**positions_data(持仓明细快照)** +```json +[ + { + "position_id": 1, + "symbol": "600519", + "name": "贵州茅台", + "shares": 100, + "cost_price": 1600.00, + "current_price": 1850.00, + "market_value": 185000.00, + "profit": 25000.00 + } +] +``` + +**cash_data(现金账户明细快照)** +```json +[ + { + "account_id": 1, + "currency": "CNY", + "balance": 50000.00 + } +] +``` + +#### 每日快照主要流程(伪代码) + +```sql +1. 获取用户所有持仓 + SELECT * FROM positions WHERE user_id = :user_id AND status = 'active'; + +2. 获取用户现金账户 + SELECT * FROM cash_accounts WHERE user_id = :user_id; + +3. 计算总资产 + total_asset = Σ(持仓市值) + Σ(现金余额) + +4. 汇总累计投入 + total_invested = + SELECT COALESCE(SUM(amount),0) FROM cash_flows + WHERE user_id = :user_id AND flow_type = 'deposit' + - SELECT COALESCE(SUM(amount),0) FROM cash_flows + WHERE user_id = :user_id AND flow_type = 'withdraw' + +5. 计算净值、总收益(不入库,仅查询时用) + +6. 计算时间加权收益率 + - 获取昨日快照 + - 计算净值变化 + - 复利累计 + +7. 计算年化和当年收益率 + +8. 组装positions_data, cash_data(JSONB) + +9. 插入或更新快照 + INSERT INTO asset_snapshots (...) + ON CONFLICT (user_id, snapshot_date) DO UPDATE SET ... +``` + +#### 查询与统计示例 + +**查询用户净值曲线** + +```sql +SELECT + snapshot_date, + total_asset, + total_invested, + (total_asset / NULLIF(total_invested, 0)) AS net_value, + (total_asset - total_invested) AS total_profit, + time_weighted_return, + annualized_return +FROM asset_snapshots +WHERE user_id = :user_id +ORDER BY snapshot_date DESC +LIMIT 365; -- 最近一年 +``` + +**查询收益率统计** + +```sql +SELECT + snapshot_date, + time_weighted_return * 100 as return_rate_percent, + annualized_return * 100 as annualized_return_percent, + year_to_date_return * 100 as ytd_return_percent +FROM asset_snapshots +WHERE user_id = :user_id +ORDER BY snapshot_date DESC +LIMIT 30; +``` + +**查询持仓明细历史** + +```sql +SELECT + snapshot_date, + positions_data +FROM asset_snapshots +WHERE user_id = :user_id + AND positions_data IS NOT NULL +ORDER BY snapshot_date DESC +LIMIT 10; +``` + +#### 注意事项 + +1. **数据一致性** + - total_invested 必须大于 0,否则净值计算会除零出错 + - 使用 NULLIF 防止除零 +2. **性能优化** + - 定时批量生成快照 + - 索引:user_id, snapshot_date + - JSONB 字段支持结构化、高效存储 +3. **数据完整性** + - 每日有且仅有一条快照,断档时补最近快照 +4. **净值及收益实时计算,不入库**,仅金额、收益率相关存库 +5. **收益率需每日、每年维护和更新** + +--- + ### position_price_plans 持仓价格计划表 **表结构** @@ -672,9 +831,9 @@ COMMENT ON COLUMN position_price_plans.step_order IS '步骤顺序,默认3个 --- -## 持仓表使用说明 +### 持仓表使用说明 -### 1. 资产类型说明 +#### 1. 资产类型说明 **stock(股票)** - symbol: 股票代码(如:600519) @@ -700,7 +859,7 @@ COMMENT ON COLUMN position_price_plans.step_order IS '步骤顺序,默认3个 - shares: 债券数量 - cost_price: 每张成本价 -### 2. 价格计划使用示例 +#### 2. 价格计划使用示例 **创建买入计划(3个买点)** ```sql @@ -822,7 +981,7 @@ WHERE p.user_id = :user_id AND p.status = 'active'; ``` -### 3. 持仓更新流程 +#### 3. 持仓更新流程 **用户修改持仓(主动变更)** ```sql @@ -841,16 +1000,28 @@ WHERE position_id = :position_id **系统更新市场价格(被动变更)** ```sql --- 每日收盘后自动更新市场价格 +-- 每日收盘后自动更新市场价格(仅更新启用自动更新的持仓) UPDATE positions SET current_price = :market_price, updated_at = CURRENT_TIMESTAMP WHERE status = 'active' + AND auto_price_update = true AND asset_type IN ('stock', 'fund', 'bond'); + +-- 查询需要自动更新价格的持仓(用于批量更新) +SELECT + p.position_id, + p.symbol, + p.market, + p.asset_type +FROM positions p +WHERE p.status = 'active' + AND p.auto_price_update = true + AND p.asset_type IN ('stock', 'fund', 'bond'); ``` -### 4. 注意事项 +#### 4. 注意事项 1. **唯一性约束** - 同一用户同一券商同一资产(user_id + broker_id + symbol + market + asset_type)只能有一条持仓 @@ -883,3 +1054,10 @@ WHERE status = 'active' - 买入:current_price <= plan_price - 卖出:current_price >= plan_price +6. **自动价格更新功能** + - `auto_price_update` 字段控制是否启用自动价格更新 + - 付费用户创建持仓时,自动设置为 true + - 用户订阅过期时,批量更新为 false + - 未付费用户创建持仓时,默认为 false + - 系统每日收盘后,仅更新 `auto_price_update = true` 的持仓价格 + diff --git a/我编写的文档/整体产品设计思路.md b/我编写的文档/整体产品设计思路.md new file mode 100644 index 0000000..36bbf21 --- /dev/null +++ b/我编写的文档/整体产品设计思路.md @@ -0,0 +1,26 @@ +# 整体产品设计思路 +--- +## 心智设计 +### 应用名思考 +- 投小记-记录、计划、复盘你的投资 + +投小记 - 专业投资记账助手,助您记录每一笔交易、制定投资计划、进行投资复盘,轻松管理股票、基金等资产收益。 + +### 页面Title设计 +每个页面除了标题外,下边附带一句话 +首页 - 买股票就是买公司 +交易计划 - 计划你的交易,交易你的计划 +复盘页 - 回顾过去是为了更好应对将来 ?? + + +## 页面设计原则 +### 配置方案 +主题色:`#8b5cf6`,紫色主题 + +### 设计原则 +- 简洁又不失个性 +- 体现 安静与思考 原则 +- + +## 想法 +- 私密分享:可以将自己的交易计划和复盘,通过小程序私密分享-分享给其他人,这样即保障了裂变属性,有增加了隐私安全。 \ No newline at end of file diff --git a/我编写的文档/计划模块-产品设计.md b/我编写的文档/计划模块-产品设计.md new file mode 100644 index 0000000..864e0e7 --- /dev/null +++ b/我编写的文档/计划模块-产品设计.md @@ -0,0 +1,6 @@ +# 计划模块-产品设计 + +## 二、交易计划模块 +### 1. 创建计划 +核心思想:计划应当和估值合并,计划依赖于估值。 +1. 交易计划列表页: \ No newline at end of file