feat: UI稿设计
This commit is contained in:
1626
src/investment-record-v2.html
Normal file
1626
src/investment-record-v2.html
Normal file
File diff suppressed because it is too large
Load Diff
1308
src/investment-record.html
Normal file
1308
src/investment-record.html
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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';
|
||||
```
|
||||
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
# 投资记录模块-产品设计
|
||||
## 一、核心设计思路
|
||||
### 1.1 持仓变更设计
|
||||
1. 把所有的表更分为两种:`用户驱动(主动)` 和 `系统驱动(被动)`
|
||||
2. 系统驱动包含:现金分红、送股、拆股、汇率变动等
|
||||
3. 用户驱动包含:初始买入、追加买入、卖出;
|
||||
4. 所有的用户驱动,都只需要变更最终的持仓成本和最新的持仓份额,这两项。避免其他复杂的操作
|
||||
5. 每次用户主动变更,记录成本价和份额的同时,还需要完成如下记录:
|
||||
- 反向计算本次交易股价和份额,并记录;
|
||||
- 统计最新的份额和净值,并记录
|
||||
- 同时引导填下投资复盘和思考。
|
||||
6. 系统驱动的变更:(万一无法实现,可以降级为用户驱动变更)
|
||||
- 分红:收盘后获取每股分红金额,最新成本价=原成本价 - 分红,市场价逻辑保持不变(使用不复权的股价)
|
||||
- 送股、拆股等都变更最新的成本价和份额,并记录。
|
||||
|
||||
### 1.2 收益记录设计
|
||||
1. 使用基金净值法(时间加权收益率)来统计收益;
|
||||
2. 每次主动和被动变更,重新计算总体的资产金额和份额,记录到 daily_snapshots 表。
|
||||
3. 忘记记录的情况:如果用户忘记记录当日交易,过一段时间后再来记录,需要删除期间的快照数据,并保留期间的交易数据。然后删除期间的快照数据,重新生成。
|
||||
|
||||
## 想法
|
||||
- 私密分享:可以将自己的交易计划和复盘,通过小程序私密分享-分享给其他人,这样即保障了裂变属性,有增加了隐私安全。
|
||||
93
我编写的文档/投资记录模块-产品设计.md
Normal file
93
我编写的文档/投资记录模块-产品设计.md
Normal file
@@ -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、市场。
|
||||
之后展示成本价输入框和份数输入框。(输入时要考虑便捷性)
|
||||
|
||||
基金:
|
||||
不展示搜索框,仅展示输入框,需要用户自己输入。
|
||||
之后展示成本价输入框和份数输入框。(输入时要考虑便捷性)
|
||||
|
||||
现金和其他:
|
||||
不展示搜索框,仅展示输入框,需要用户自己输入资产名称。
|
||||
直接输入金额即可。
|
||||
|
||||
确认按钮:点击确认,提交数据到后台,按钮居中展示。
|
||||
|
||||
**变更资产页**
|
||||
最上面不需要在选择资产类型,下边的展示和`新增资产页` 一样。
|
||||
|
||||
|
||||
854
我编写的文档/数据库设计.md
854
我编写的文档/数据库设计.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` 的持仓价格
|
||||
|
||||
|
||||
26
我编写的文档/整体产品设计思路.md
Normal file
26
我编写的文档/整体产品设计思路.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# 整体产品设计思路
|
||||
---
|
||||
## 心智设计
|
||||
### 应用名思考
|
||||
- 投小记-记录、计划、复盘你的投资
|
||||
|
||||
投小记 - 专业投资记账助手,助您记录每一笔交易、制定投资计划、进行投资复盘,轻松管理股票、基金等资产收益。
|
||||
|
||||
### 页面Title设计
|
||||
每个页面除了标题外,下边附带一句话
|
||||
首页 - 买股票就是买公司
|
||||
交易计划 - 计划你的交易,交易你的计划
|
||||
复盘页 - 回顾过去是为了更好应对将来 ??
|
||||
|
||||
|
||||
## 页面设计原则
|
||||
### 配置方案
|
||||
主题色:`#8b5cf6`,紫色主题
|
||||
|
||||
### 设计原则
|
||||
- 简洁又不失个性
|
||||
- 体现 安静与思考 原则
|
||||
-
|
||||
|
||||
## 想法
|
||||
- 私密分享:可以将自己的交易计划和复盘,通过小程序私密分享-分享给其他人,这样即保障了裂变属性,有增加了隐私安全。
|
||||
6
我编写的文档/计划模块-产品设计.md
Normal file
6
我编写的文档/计划模块-产品设计.md
Normal file
@@ -0,0 +1,6 @@
|
||||
# 计划模块-产品设计
|
||||
|
||||
## 二、交易计划模块
|
||||
### 1. 创建计划
|
||||
核心思想:计划应当和估值合并,计划依赖于估值。
|
||||
1. 交易计划列表页:
|
||||
Reference in New Issue
Block a user