feat: UI稿设计
This commit is contained in:
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` 的持仓价格
|
||||
|
||||
|
||||
Reference in New Issue
Block a user