1477 lines
55 KiB
Markdown
1477 lines
55 KiB
Markdown
# 数据库设计文档
|
||
|
||
## 表结构总览
|
||
数据库使用 PostgreSQL
|
||
|
||
```
|
||
用户相关
|
||
├── user (用户表)
|
||
|
||
基础数据相关
|
||
├── broker (券商表)
|
||
├── stock_info (股票基本信息表)
|
||
└── stock_daily_price (股票每日收盘价表)
|
||
|
||
财务报表相关(见 财务报表模块-产品与数据库设计.md)
|
||
├── financial_report (报表主表)
|
||
├── financial_report_line (行项目表)
|
||
├── financial_report_stats (预计算统计表)
|
||
└── financial_item_category (自定义分类表,可选)
|
||
|
||
账户相关
|
||
├── positions (持仓表)
|
||
├── position_changes (持仓变更记录表)
|
||
├── asset_snapshots (资产快照表)
|
||
└── position_price_plans (持仓价格计划表)
|
||
```
|
||
|
||
## 用户相关表
|
||
### users 用户表
|
||
|
||
**表结构**
|
||
|
||
| 字段名 | 数据类型 | 约束 | 说明 |
|
||
|--------|---------|------|------|
|
||
| user_id | BIGSERIAL | PRIMARY KEY | 用户ID,自增 |
|
||
| open_id | VARCHAR(100) | UNIQUE | 可选,微信体系的 openId(小程序/公众号) |
|
||
| union_id | VARCHAR(100) | UNIQUE | 可选,微信体系的 unionId(跨应用统一标识) |
|
||
| username | VARCHAR(100) | UNIQUE | 用户名(必选,用于账号密码登录) |
|
||
| password_hash | VARCHAR(255) | | 密码哈希值(bcrypt加密,仅用户名登录时需要) |
|
||
| email | VARCHAR(100) | UNIQUE | 邮箱(可选,用于邮箱登录) |
|
||
| phone | VARCHAR(20) | UNIQUE | 电话号码(可选,用于手机号登录) |
|
||
| nickname | VARCHAR(100) | | 用户昵称(显示名称) |
|
||
| avatar_url | VARCHAR(255) | | 头像URL |
|
||
| created_at | TIMESTAMP | NOT NULL, DEFAULT CURRENT_TIMESTAMP | 创建时间 |
|
||
| updated_at | TIMESTAMP | NOT NULL, DEFAULT CURRENT_TIMESTAMP | 修改时间(自动更新) |
|
||
| last_login_at | TIMESTAMP | | 最后登录时间 |
|
||
| status | VARCHAR(20) | NOT NULL, DEFAULT 'active', CHECK | 状态:active/inactive/deleted |
|
||
| role | VARCHAR(20) | NOT NULL, DEFAULT 'user', CHECK | 用户角色:user(普通用户)/admin(管理员)/super_admin(超级管理员) |
|
||
|
||
**创建语句**
|
||
|
||
```sql
|
||
CREATE TABLE user (
|
||
user_id BIGSERIAL PRIMARY KEY,
|
||
open_id VARCHAR(100) UNIQUE,
|
||
union_id VARCHAR(100) UNIQUE,
|
||
username VARCHAR(100) UNIQUE,
|
||
password_hash VARCHAR(255),
|
||
email VARCHAR(100) UNIQUE,
|
||
phone VARCHAR(20) UNIQUE,
|
||
nickname VARCHAR(100),
|
||
avatar_url VARCHAR(255),
|
||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
last_login_at TIMESTAMP,
|
||
status VARCHAR(20) NOT NULL DEFAULT 'active',
|
||
role VARCHAR(20) NOT NULL DEFAULT 'user',
|
||
-- 约束:至少要有一种登录方式
|
||
CONSTRAINT check_login_method CHECK (
|
||
open_id IS NOT NULL OR
|
||
username IS NOT NULL OR
|
||
email IS NOT NULL OR
|
||
phone IS NOT NULL
|
||
),
|
||
-- 约束:如果使用用户名登录,则必须有密码
|
||
CONSTRAINT check_username_password CHECK (
|
||
username IS NULL OR password_hash IS NOT NULL
|
||
),
|
||
-- 约束:状态值限制
|
||
CONSTRAINT check_status CHECK (status IN ('active', 'inactive', 'deleted')),
|
||
-- 约束:角色值限制
|
||
CONSTRAINT check_role CHECK (role IN ('user', 'admin', 'super_admin'))
|
||
);
|
||
|
||
-- 创建索引
|
||
CREATE INDEX idx_users_open_id ON user(open_id);
|
||
CREATE INDEX idx_users_union_id ON user(union_id);
|
||
CREATE INDEX idx_users_username ON user(username);
|
||
CREATE INDEX idx_users_email ON user(email);
|
||
CREATE INDEX idx_users_phone ON user(phone);
|
||
CREATE INDEX idx_users_status ON user(status);
|
||
CREATE INDEX idx_users_last_login_at ON user(last_login_at);
|
||
CREATE INDEX idx_users_role ON user(role);
|
||
|
||
-- 创建自动更新 updated_at 的触发器
|
||
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
||
RETURNS TRIGGER AS $$
|
||
BEGIN
|
||
NEW.updated_at = CURRENT_TIMESTAMP;
|
||
RETURN NEW;
|
||
END;
|
||
$$ language 'plpgsql';
|
||
|
||
CREATE TRIGGER update_users_updated_at
|
||
BEFORE UPDATE ON user
|
||
FOR EACH ROW
|
||
EXECUTE FUNCTION update_updated_at_column();
|
||
|
||
-- 添加注释
|
||
COMMENT ON TABLE user IS '用户表,支持微信登录和账号密码登录';
|
||
COMMENT ON COLUMN user.user_id IS '用户ID,主键,自增';
|
||
COMMENT ON COLUMN user.open_id IS '微信 openId,用于小程序/公众号登录,唯一';
|
||
COMMENT ON COLUMN user.union_id IS '微信 unionId,用于跨应用统一标识,唯一';
|
||
COMMENT ON COLUMN user.username IS '用户名,用于账号密码登录,唯一,可选';
|
||
COMMENT ON COLUMN user.password_hash IS '密码哈希值,使用 bcrypt 算法加密存储,仅当使用用户名登录时必填';
|
||
COMMENT ON COLUMN user.email IS '邮箱,用于邮箱登录,唯一,可选';
|
||
COMMENT ON COLUMN user.phone IS '手机号,用于手机号登录,唯一,可选';
|
||
COMMENT ON COLUMN user.nickname IS '用户昵称,显示名称';
|
||
COMMENT ON COLUMN user.avatar_url IS '头像URL';
|
||
COMMENT ON COLUMN user.status IS '用户状态:active(活跃)/inactive(非活跃)/deleted(已删除)';
|
||
COMMENT ON COLUMN user.role IS '用户角色:user(普通用户)/admin(管理员)/super_admin(超级管理员)';
|
||
COMMENT ON COLUMN user.created_at IS '创建时间,自动设置';
|
||
COMMENT ON COLUMN user.updated_at IS '更新时间,自动更新';
|
||
COMMENT ON COLUMN user.last_login_at IS '最后登录时间';
|
||
```
|
||
|
||
#### 密码存储说明
|
||
|
||
**密码安全规范:**
|
||
1. **加密算法**:使用 `bcrypt` 算法对密码进行哈希加密(推荐使用 cost factor 10-12)
|
||
2. **存储字段**:密码哈希值存储在 `password_hash` 字段中,**绝不存储明文密码**
|
||
3. **字段长度**:`VARCHAR(255)` 足够存储 bcrypt 哈希值(通常为 60 字符)
|
||
4. **约束规则**:
|
||
- 如果用户设置了 `username`,则必须设置 `password_hash`
|
||
- 微信登录用户不需要密码(`password_hash` 可为 NULL)
|
||
- 密码字段允许为 NULL,支持多种登录方式
|
||
|
||
**密码验证流程:**
|
||
```sql
|
||
-- 用户登录时验证密码
|
||
-- 1. 根据用户名查询用户
|
||
SELECT user_id, password_hash, status
|
||
FROM user
|
||
WHERE username = :username AND status = 'active';
|
||
|
||
-- 2. 在应用层使用 bcrypt 验证密码
|
||
-- bcrypt.compare(用户输入的明文密码, 数据库中的 password_hash)
|
||
|
||
-- 3. 验证成功后更新最后登录时间
|
||
UPDATE user
|
||
SET last_login_at = CURRENT_TIMESTAMP
|
||
WHERE user_id = :user_id;
|
||
```
|
||
|
||
**密码设置/修改流程:**
|
||
```sql
|
||
-- 注册时设置密码(应用层先使用 bcrypt 加密)
|
||
INSERT INTO user (username, password_hash, nickname, ...)
|
||
VALUES (:username, :password_hash_bcrypt, :nickname, ...);
|
||
|
||
-- 修改密码(应用层先使用 bcrypt 加密新密码)
|
||
UPDATE user
|
||
SET password_hash = :new_password_hash_bcrypt,
|
||
updated_at = CURRENT_TIMESTAMP
|
||
WHERE user_id = :user_id;
|
||
|
||
-- 重置密码(忘记密码场景)
|
||
UPDATE user
|
||
SET password_hash = :new_password_hash_bcrypt,
|
||
updated_at = CURRENT_TIMESTAMP
|
||
WHERE username = :username OR email = :email;
|
||
```
|
||
|
||
**注意事项:**
|
||
- 密码哈希值一旦生成,无法逆向还原为明文密码
|
||
- 忘记密码时,只能通过重置密码流程(发送验证码到邮箱/手机)来设置新密码
|
||
- 建议实现密码强度验证(长度、复杂度等)在应用层完成
|
||
- 建议实现登录失败次数限制,防止暴力破解
|
||
|
||
|
||
|
||
|
||
## 基础数据相关表设计
|
||
|
||
### broker 券商表
|
||
|
||
**表结构**
|
||
|
||
| 字段名 | 数据类型 | 约束 | 说明 |
|
||
|--------|---------|------|------|
|
||
| broker_id | BIGSERIAL | PRIMARY KEY | 券商ID,自增 |
|
||
| broker_code | VARCHAR(50) | NOT NULL | 券商代码(如:HTZQ、ZSZQ等) |
|
||
| broker_name | VARCHAR(100) | NOT NULL | 券商名称(如:华泰证券、招商证券等) |
|
||
| region | VARCHAR(50) | NOT NULL, DEFAULT 'CN' | 地区/国家(如:CN/US/HK等) |
|
||
| sort_order | INTEGER | DEFAULT 0 | 排序顺序 |
|
||
| is_active | BOOLEAN | NOT NULL, DEFAULT true | 是否启用 |
|
||
| created_at | TIMESTAMP | NOT NULL, DEFAULT CURRENT_TIMESTAMP | 创建时间 |
|
||
| updated_at | TIMESTAMP | NOT NULL, DEFAULT CURRENT_TIMESTAMP | 更新时间 |
|
||
| UNIQUE(broker_code, region) | | | 同一地区券商代码唯一 |
|
||
| UNIQUE(broker_name, region) | | | 同一地区券商名称唯一 |
|
||
|
||
**创建语句**
|
||
|
||
```sql
|
||
CREATE TABLE broker (
|
||
broker_id BIGSERIAL PRIMARY KEY,
|
||
broker_code VARCHAR(50) NOT NULL,
|
||
broker_name VARCHAR(100) NOT NULL,
|
||
region VARCHAR(50) NOT NULL DEFAULT 'CN',
|
||
sort_order INTEGER DEFAULT 0,
|
||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
-- 同一地区券商代码唯一(不同地区可以有相同代码)
|
||
UNIQUE(broker_code, region),
|
||
-- 同一地区券商名称唯一(不同地区可以有相同名称)
|
||
UNIQUE(broker_name, region),
|
||
CONSTRAINT check_region CHECK (region IN ('CN', 'US', 'HK', 'SG', 'JP', 'UK', 'AU', 'CA', 'OTHER'))
|
||
);
|
||
|
||
-- 创建索引
|
||
CREATE INDEX idx_broker_code ON broker(broker_code);
|
||
CREATE INDEX idx_broker_name ON broker(broker_name);
|
||
CREATE INDEX idx_broker_region ON broker(region);
|
||
CREATE INDEX idx_broker_active ON broker(is_active);
|
||
CREATE INDEX idx_broker_sort ON broker(sort_order);
|
||
CREATE INDEX idx_broker_region_active ON broker(region, is_active);
|
||
|
||
-- 创建触发器自动更新 updated_at
|
||
CREATE TRIGGER update_broker_updated_at
|
||
BEFORE UPDATE ON broker
|
||
FOR EACH ROW
|
||
EXECUTE FUNCTION update_updated_at_column();
|
||
|
||
-- 添加注释
|
||
COMMENT ON TABLE broker IS '券商表,记录所有可用的券商信息';
|
||
COMMENT ON COLUMN broker.broker_code IS '券商代码,用于系统识别,如:HTZQ(华泰证券)';
|
||
COMMENT ON COLUMN broker.broker_name IS '券商名称,显示给用户的名称,如:华泰证券';
|
||
COMMENT ON COLUMN broker.region IS '地区/国家代码:CN(中国)/US(美国)/HK(香港)/SG(新加坡)/JP(日本)/UK(英国)/AU(澳大利亚)/CA(加拿大)/OTHER(其他)';
|
||
```
|
||
|
||
**示例数据**
|
||
|
||
```sql
|
||
-- 中国券商
|
||
INSERT INTO broker (broker_code, broker_name, region, sort_order) VALUES
|
||
('HTZQ', '华泰证券', 'CN', 1),
|
||
('ZSZQ', '招商证券', 'CN', 2),
|
||
('ZXZQ', '中信证券', 'CN', 3),
|
||
('GJZQ', '国金证券', 'CN', 4),
|
||
('DFZQ', '东方证券', 'CN', 5),
|
||
('GTZQ', '国泰君安', 'CN', 6),
|
||
('HXZQ', '华西证券', 'CN', 7),
|
||
('ZJZQ', '中金公司', 'CN', 8),
|
||
('PZQ', '平安证券', 'CN', 9),
|
||
('GFZQ', '广发证券', 'CN', 10);
|
||
|
||
-- 美国券商
|
||
INSERT INTO broker (broker_code, broker_name, region, sort_order) VALUES
|
||
('SCHW', 'Charles Schwab', 'US', 1),
|
||
('FID', 'Fidelity', 'US', 2),
|
||
('IB', 'Interactive Brokers', 'US', 3),
|
||
('TD', 'TD Ameritrade', 'US', 4),
|
||
('ETRADE', 'E*TRADE', 'US', 5);
|
||
|
||
-- 香港券商
|
||
INSERT INTO broker (broker_code, broker_name, region, sort_order) VALUES
|
||
('FUTU', '富途证券', 'HK', 1),
|
||
('TIGER', '老虎证券', 'HK', 2),
|
||
('HSBC', '汇丰银行', 'HK', 3),
|
||
('CITI', '花旗银行', 'HK', 4);
|
||
|
||
-- 其他地区券商示例
|
||
INSERT INTO brokers (broker_code, broker_name, region, sort_order) VALUES
|
||
('DBS', '星展银行', 'SG', 1),
|
||
('NOMURA', '野村证券', 'JP', 1);
|
||
```
|
||
|
||
---
|
||
|
||
### 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 | 股票代码 |
|
||
| stock_name | VARCHAR(100) | 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) | | 涨跌幅(%) |
|
||
| change_percent_60day | DECIMAL(10, 6) | | 60日涨跌幅(%) |
|
||
| change_percent_ytd | 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,
|
||
stock_name VARCHAR(100) 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),
|
||
change_percent_60day DECIMAL(10, 6),
|
||
change_percent_ytd 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.change_percent_60day IS '60日涨跌幅(%)';
|
||
COMMENT ON COLUMN stock_daily_price.change_percent_ytd 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, change_percent_60day, change_percent_ytd,
|
||
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,
|
||
change_percent_60day = EXCLUDED.change_percent_60day,
|
||
change_percent_ytd = EXCLUDED.change_percent_ytd,
|
||
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';
|
||
```
|
||
|
||
---
|
||
|
||
## 财务报表相关表设计
|
||
|
||
> 详细产品与设计说明见:[财务报表模块-产品与数据库设计.md](./财务报表模块-产品与数据库设计.md)
|
||
|
||
### financial_report 报表主表
|
||
|
||
| 字段名 | 数据类型 | 约束 | 说明 |
|
||
|--------|---------|------|------|
|
||
| id | BIGSERIAL | PRIMARY KEY | 主键 |
|
||
| stock_code | VARCHAR(20) | NOT NULL | 股票代码,关联 stock_info |
|
||
| market | VARCHAR(20) | NOT NULL | 市场,关联 stock_info |
|
||
| report_date | DATE | NOT NULL | 报告期截止日 |
|
||
| period_type | VARCHAR(20) | NOT NULL | annual / quarterly |
|
||
| statement_type | VARCHAR(30) | NOT NULL | balance_sheet / income_statement / cash_flow |
|
||
| source_file_name | VARCHAR(255) | | 来源文件名 |
|
||
| created_at | TIMESTAMP | NOT NULL, DEFAULT CURRENT_TIMESTAMP | 创建时间 |
|
||
| updated_at | TIMESTAMP | NOT NULL, DEFAULT CURRENT_TIMESTAMP | 更新时间 |
|
||
|
||
```sql
|
||
CREATE TABLE financial_report (
|
||
id BIGSERIAL PRIMARY KEY,
|
||
stock_code VARCHAR(20) NOT NULL,
|
||
market VARCHAR(20) NOT NULL,
|
||
report_date DATE NOT NULL,
|
||
period_type VARCHAR(20) NOT NULL,
|
||
statement_type VARCHAR(30) NOT NULL,
|
||
source_file_name VARCHAR(255),
|
||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
UNIQUE(stock_code, market, report_date, statement_type),
|
||
CONSTRAINT fk_financial_report_stock FOREIGN KEY (stock_code, market)
|
||
REFERENCES stock_info(stock_code, market) ON DELETE CASCADE,
|
||
CONSTRAINT check_period_type CHECK (period_type IN ('annual', 'quarterly')),
|
||
CONSTRAINT check_statement_type CHECK (statement_type IN ('balance_sheet', 'income_statement', 'cash_flow'))
|
||
);
|
||
|
||
CREATE INDEX idx_financial_report_stock_market ON financial_report(stock_code, market);
|
||
CREATE INDEX idx_financial_report_report_date ON financial_report(report_date);
|
||
CREATE INDEX idx_financial_report_statement_type ON financial_report(statement_type);
|
||
|
||
CREATE TRIGGER update_financial_report_updated_at
|
||
BEFORE UPDATE ON financial_report
|
||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||
|
||
COMMENT ON TABLE financial_report IS '财务报表主表:公司+报告期+表型';
|
||
COMMENT ON COLUMN financial_report.statement_type IS 'balance_sheet(资产负债表)/income_statement(利润表)/cash_flow(现金流量表)';
|
||
```
|
||
|
||
### financial_report_line 行项目表
|
||
|
||
| 字段名 | 数据类型 | 约束 | 说明 |
|
||
|--------|---------|------|------|
|
||
| id | BIGSERIAL | PRIMARY KEY | 主键 |
|
||
| report_id | BIGINT | NOT NULL, FK | 所属报表 |
|
||
| item_name | VARCHAR(200) | NOT NULL | 科目名称(原文) |
|
||
| item_code | VARCHAR(50) | | 科目编码(可选) |
|
||
| value | DECIMAL(24, 6) | | 数值 |
|
||
| unit | VARCHAR(20) | | 单位 |
|
||
| row_order | INT | DEFAULT 0 | 行顺序 |
|
||
| created_at | TIMESTAMP | NOT NULL, DEFAULT CURRENT_TIMESTAMP | 创建时间 |
|
||
|
||
```sql
|
||
CREATE TABLE financial_report_line (
|
||
id BIGSERIAL PRIMARY KEY,
|
||
report_id BIGINT NOT NULL REFERENCES financial_report(id) ON DELETE CASCADE,
|
||
item_name VARCHAR(200) NOT NULL,
|
||
item_code VARCHAR(50),
|
||
value DECIMAL(24, 6),
|
||
unit VARCHAR(20),
|
||
row_order INT DEFAULT 0,
|
||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||
);
|
||
|
||
CREATE INDEX idx_financial_report_line_report_id ON financial_report_line(report_id);
|
||
CREATE INDEX idx_financial_report_line_item_name ON financial_report_line(report_id, item_name);
|
||
|
||
COMMENT ON TABLE financial_report_line IS '财务报表行项目,存原始科目名与数值,支持多市场科目差异';
|
||
```
|
||
|
||
### financial_report_stats 预计算统计表
|
||
|
||
| 字段名 | 数据类型 | 约束 | 说明 |
|
||
|--------|---------|------|------|
|
||
| id | BIGSERIAL | PRIMARY KEY | 主键 |
|
||
| report_id | BIGINT | NOT NULL, FK | 所属报表 |
|
||
| stat_key | VARCHAR(80) | NOT NULL | 如 total_assets, net_profit, roe |
|
||
| stat_value | DECIMAL(24, 6) | | 统计值 |
|
||
| dimension | VARCHAR(50) | | 可选维度 |
|
||
| created_at | TIMESTAMP | NOT NULL, DEFAULT CURRENT_TIMESTAMP | 创建时间 |
|
||
| updated_at | TIMESTAMP | NOT NULL, DEFAULT CURRENT_TIMESTAMP | 更新时间 |
|
||
|
||
```sql
|
||
CREATE TABLE financial_report_stats (
|
||
id BIGSERIAL PRIMARY KEY,
|
||
report_id BIGINT NOT NULL REFERENCES financial_report(id) ON DELETE CASCADE,
|
||
stat_key VARCHAR(80) NOT NULL,
|
||
stat_value DECIMAL(24, 6),
|
||
dimension VARCHAR(50),
|
||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
UNIQUE(report_id, stat_key, dimension)
|
||
);
|
||
|
||
CREATE INDEX idx_financial_report_stats_report_id ON financial_report_stats(report_id);
|
||
CREATE INDEX idx_financial_report_stats_stat_key ON financial_report_stats(stat_key);
|
||
|
||
CREATE TRIGGER update_financial_report_stats_updated_at
|
||
BEFORE UPDATE ON financial_report_stats
|
||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||
|
||
COMMENT ON TABLE financial_report_stats IS '报表预计算统计项,上传时计算并写入';
|
||
```
|
||
|
||
### financial_item_category 自定义分类表(可选)
|
||
|
||
| 字段名 | 数据类型 | 约束 | 说明 |
|
||
|--------|---------|------|------|
|
||
| id | BIGSERIAL | PRIMARY KEY | 主键 |
|
||
| user_id | BIGINT | FK user, 可选 | 多用户时按用户隔离 |
|
||
| market | VARCHAR(20) | NOT NULL | 市场 |
|
||
| statement_type | VARCHAR(30) | NOT NULL | 表型 |
|
||
| item_name_pattern | VARCHAR(200) | NOT NULL | 科目名或匹配模式 |
|
||
| category_slug | VARCHAR(60) | NOT NULL | 分类标签,如 cash_like, operating |
|
||
| sort_order | INT | DEFAULT 0 | 展示顺序 |
|
||
| created_at | TIMESTAMP | NOT NULL, DEFAULT CURRENT_TIMESTAMP | 创建时间 |
|
||
| updated_at | TIMESTAMP | NOT NULL, DEFAULT CURRENT_TIMESTAMP | 更新时间 |
|
||
|
||
```sql
|
||
CREATE TABLE financial_item_category (
|
||
id BIGSERIAL PRIMARY KEY,
|
||
user_id BIGINT REFERENCES user(user_id) ON DELETE CASCADE,
|
||
market VARCHAR(20) NOT NULL,
|
||
statement_type VARCHAR(30) NOT NULL,
|
||
item_name_pattern VARCHAR(200) NOT NULL,
|
||
category_slug VARCHAR(60) NOT NULL,
|
||
sort_order INT DEFAULT 0,
|
||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||
);
|
||
|
||
CREATE INDEX idx_financial_item_category_user_market ON financial_item_category(user_id, market, statement_type);
|
||
|
||
CREATE TRIGGER update_financial_item_category_updated_at
|
||
BEFORE UPDATE ON financial_item_category
|
||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||
|
||
COMMENT ON TABLE financial_item_category IS '科目名到自定义分类的映射,用于按类现金/投资资产等聚合展示';
|
||
```
|
||
|
||
---
|
||
|
||
## 账户相关表设计
|
||
|
||
### positions 持仓表
|
||
记录用户持仓情况,包含数量、成本价、最新市场价、券商等
|
||
**表结构**
|
||
|
||
| 字段名 | 数据类型 | 约束 | 说明 |
|
||
|--------|---------|------|------|
|
||
| position_id | BIGSERIAL | PRIMARY KEY | 持仓ID,自增 |
|
||
| user_id | BIGINT | NOT NULL, FOREIGN KEY | 用户ID,关联 user 表 |
|
||
| broker_id | BIGINT | NOT NULL, FOREIGN KEY | 券商ID,关联 brokers 表 |
|
||
| asset_type | VARCHAR(20) | NOT NULL, CHECK | 资产类型:stock/fund/cash/bond |
|
||
| symbol | VARCHAR(50) | NOT NULL | 资产代码(股票代码、基金代码等) |
|
||
| name | VARCHAR(100) | NOT NULL | 资产名称 |
|
||
| market | VARCHAR(20) | | 市场(A股/港股/美股等) |
|
||
| shares | DECIMAL(18, 4) | NOT NULL, DEFAULT 0 | 持仓份额/数量 |
|
||
| cost_price | DECIMAL(18, 4) | NOT NULL | 成本价(每股/每份) |
|
||
| current_price | DECIMAL(18, 4) | | 最新市场价(系统自动更新) |
|
||
| previous_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 | 更新时间 |
|
||
|
||
**创建语句**
|
||
|
||
```sql
|
||
CREATE TABLE positions (
|
||
position_id BIGSERIAL PRIMARY KEY,
|
||
user_id BIGINT NOT NULL REFERENCES user(user_id) ON DELETE CASCADE,
|
||
broker_id BIGINT NOT NULL REFERENCES brokers(broker_id),
|
||
asset_type VARCHAR(20) NOT NULL,
|
||
symbol VARCHAR(50) NOT NULL,
|
||
name VARCHAR(100) NOT NULL,
|
||
market VARCHAR(20),
|
||
shares DECIMAL(18, 4) NOT NULL DEFAULT 0,
|
||
cost_price DECIMAL(18, 4) NOT NULL,
|
||
current_price DECIMAL(18, 4),
|
||
previous_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,
|
||
CONSTRAINT check_asset_type CHECK (asset_type IN ('stock', 'fund', 'cash', 'bond', 'other')),
|
||
CONSTRAINT check_status CHECK (status IN ('active', 'suspended', 'delisted')),
|
||
CONSTRAINT check_shares_non_negative CHECK (shares >= 0),
|
||
CONSTRAINT check_cost_price_positive CHECK (cost_price > 0),
|
||
-- 同一用户同一券商同一资产只能有一条持仓(通过 user_id + broker_id + symbol + market + asset_type 唯一)
|
||
UNIQUE(user_id, broker_id, symbol, market, asset_type)
|
||
);
|
||
|
||
-- 创建索引
|
||
CREATE INDEX idx_positions_user_id ON positions(user_id);
|
||
CREATE INDEX idx_positions_broker_id ON positions(broker_id);
|
||
CREATE INDEX idx_positions_symbol ON positions(symbol);
|
||
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
|
||
CREATE TRIGGER update_positions_updated_at
|
||
BEFORE UPDATE ON positions
|
||
FOR EACH ROW
|
||
EXECUTE FUNCTION update_updated_at_column();
|
||
|
||
-- 添加注释
|
||
COMMENT ON TABLE positions IS '持仓表,记录用户的资产持仓';
|
||
COMMENT ON COLUMN positions.broker_id IS '券商ID,关联 brokers 表,支持多券商';
|
||
COMMENT ON COLUMN positions.asset_type IS '资产类型:stock(股票)/fund(基金)/cash(现金)/bond(国债)/other(其他)';
|
||
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 '最新市场价,系统每日自动更新(仅auto_price_update=true的持仓)';
|
||
COMMENT ON COLUMN positions.previous_price IS '上一次的价格,用户手动更新价格时自动保存上一次价格,用于前端显示红绿色(上涨/下跌)';
|
||
COMMENT ON COLUMN positions.exchange_rate IS '汇率,用于多货币资产,如港股、美股';
|
||
COMMENT ON COLUMN positions.auto_price_update IS '是否自动更新价格,true表示系统每日自动更新市场价格(付费用户功能)';
|
||
```
|
||
|
||
**示例数据**
|
||
|
||
```sql
|
||
-- 假设 user_id = 1,broker_id 1=华泰证券(HTZQ),broker_id 4=富途证券(FUTU),broker_id 3=Interactive Brokers(IB)
|
||
-- 插入三条持仓数据
|
||
|
||
-- 1. A股股票:贵州茅台(上海市场)
|
||
INSERT INTO positions (
|
||
user_id, broker_id, asset_type, symbol, name, market,
|
||
shares, cost_price, current_price, previous_price,
|
||
currency, exchange_rate, auto_price_update, status
|
||
) VALUES (
|
||
1, 1, 'stock', '600519', '贵州茅台', 'sh',
|
||
100.0000, 1600.0000, 1850.0000, 1800.0000,
|
||
'CNY', 1.000000, false, 'active'
|
||
);
|
||
|
||
-- 2. 港股股票:腾讯控股(香港市场)
|
||
INSERT INTO positions (
|
||
user_id, broker_id, asset_type, symbol, name, market,
|
||
shares, cost_price, current_price, previous_price,
|
||
currency, exchange_rate, auto_price_update, status
|
||
) VALUES (
|
||
1, 4, 'stock', '00700', '腾讯控股', 'hk',
|
||
200.0000, 320.0000, 350.0000, 340.0000,
|
||
'HKD', 0.920000, false, 'active'
|
||
);
|
||
|
||
-- 3. 美股股票:苹果公司(美国市场)
|
||
INSERT INTO positions (
|
||
user_id, broker_id, asset_type, symbol, name, market,
|
||
shares, cost_price, current_price, previous_price,
|
||
currency, exchange_rate, auto_price_update, status
|
||
) VALUES (
|
||
1, 3, 'stock', 'AAPL', '苹果公司', 'us',
|
||
50.0000, 150.0000, 175.0000, 170.0000,
|
||
'USD', 7.200000, false, 'active'
|
||
);
|
||
```
|
||
|
||
---
|
||
|
||
### position_changes 持仓变更记录表
|
||
|
||
记录每次持仓变更的详细信息,用于展示持仓变更历史。
|
||
|
||
**表结构**
|
||
|
||
| 字段名 | 数据类型 | 约束 | 说明 |
|
||
|--------|---------|------|------|
|
||
| change_id | BIGSERIAL | PRIMARY KEY | 变更记录ID,自增 |
|
||
| position_id | BIGINT | NOT NULL, FOREIGN KEY | 持仓ID,关联 positions 表 |
|
||
| change_date | DATE | NOT NULL | 变更日期 |
|
||
| change_type | VARCHAR(20) | NOT NULL, CHECK | 变更类型:buy(买入)/sell(卖出)/auto(系统自动) |
|
||
| before_shares | DECIMAL(18, 4) | NOT NULL | 变更前份额/数量 |
|
||
| before_cost_price | DECIMAL(18, 4) | NOT NULL | 变更前成本价 |
|
||
| after_shares | DECIMAL(18, 4) | NOT NULL | 变更后份额/数量 |
|
||
| after_cost_price | DECIMAL(18, 4) | NOT NULL | 变更后成本价 |
|
||
| notes | TEXT | | 备注/思考 |
|
||
| created_at | TIMESTAMP | NOT NULL, DEFAULT CURRENT_TIMESTAMP | 创建时间 |
|
||
|
||
**创建语句**
|
||
|
||
```sql
|
||
CREATE TABLE position_changes (
|
||
change_id BIGSERIAL PRIMARY KEY,
|
||
position_id BIGINT NOT NULL REFERENCES positions(position_id) ON DELETE CASCADE,
|
||
change_date DATE NOT NULL,
|
||
change_type VARCHAR(20) NOT NULL,
|
||
before_shares DECIMAL(18, 4) NOT NULL,
|
||
before_cost_price DECIMAL(18, 4) NOT NULL,
|
||
after_shares DECIMAL(18, 4) NOT NULL,
|
||
after_cost_price DECIMAL(18, 4) NOT NULL,
|
||
notes TEXT,
|
||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
CONSTRAINT check_change_type CHECK (change_type IN ('buy', 'sell', 'auto'))
|
||
);
|
||
|
||
-- 创建索引
|
||
CREATE INDEX idx_position_changes_position_id ON position_changes(position_id);
|
||
CREATE INDEX idx_position_changes_change_date ON position_changes(change_date);
|
||
CREATE INDEX idx_position_changes_position_date ON position_changes(position_id, change_date DESC);
|
||
|
||
-- 添加注释
|
||
COMMENT ON TABLE position_changes IS '持仓变更记录表,记录每次持仓变更的详细信息,用于展示持仓变更历史';
|
||
COMMENT ON COLUMN position_changes.position_id IS '持仓ID,关联 positions 表';
|
||
COMMENT ON COLUMN position_changes.change_date IS '变更日期';
|
||
COMMENT ON COLUMN position_changes.change_type IS '变更类型:buy(买入)/sell(卖出)/auto(系统自动,如分红、拆股等)';
|
||
COMMENT ON COLUMN position_changes.before_shares IS '变更前份额/数量';
|
||
COMMENT ON COLUMN position_changes.before_cost_price IS '变更前成本价';
|
||
COMMENT ON COLUMN position_changes.after_shares IS '变更后份额/数量';
|
||
COMMENT ON COLUMN position_changes.after_cost_price IS '变更后成本价';
|
||
COMMENT ON COLUMN position_changes.notes IS '备注/思考记录';
|
||
```
|
||
|
||
**使用说明:**
|
||
|
||
1. **记录买入变更**
|
||
```sql
|
||
-- 用户买入时,记录变更前后状态
|
||
INSERT INTO position_changes (
|
||
position_id, change_date, change_type,
|
||
before_shares, before_cost_price,
|
||
after_shares, after_cost_price,
|
||
notes
|
||
)
|
||
VALUES (
|
||
:position_id, :change_date, 'buy',
|
||
:before_shares, :before_cost_price,
|
||
:after_shares, :after_cost_price,
|
||
:notes
|
||
);
|
||
```
|
||
|
||
2. **记录卖出变更**
|
||
```sql
|
||
-- 用户卖出时,记录变更前后状态
|
||
INSERT INTO position_changes (
|
||
position_id, change_date, change_type,
|
||
before_shares, before_cost_price,
|
||
after_shares, after_cost_price,
|
||
notes
|
||
)
|
||
VALUES (
|
||
:position_id, :change_date, 'sell',
|
||
:before_shares, :before_cost_price,
|
||
:after_shares, :after_cost_price,
|
||
:notes
|
||
);
|
||
```
|
||
|
||
3. **记录系统自动变更**
|
||
```sql
|
||
-- 系统自动变更(分红、拆股、送股等)
|
||
INSERT INTO position_changes (
|
||
position_id, change_date, change_type,
|
||
before_shares, before_cost_price,
|
||
after_shares, after_cost_price,
|
||
notes
|
||
)
|
||
VALUES (
|
||
:position_id, :change_date, 'auto',
|
||
:before_shares, :before_cost_price,
|
||
:after_shares, :after_cost_price,
|
||
'分红/拆股/送股等系统自动变更'
|
||
);
|
||
```
|
||
|
||
4. **查询持仓变更历史**
|
||
```sql
|
||
-- 查询指定持仓的所有变更记录
|
||
SELECT
|
||
change_id,
|
||
change_date,
|
||
change_type,
|
||
before_shares,
|
||
before_cost_price,
|
||
after_shares,
|
||
after_cost_price,
|
||
notes,
|
||
created_at
|
||
FROM position_changes
|
||
WHERE position_id = :position_id
|
||
ORDER BY change_date DESC, created_at DESC;
|
||
```
|
||
|
||
---
|
||
|
||
|
||
### asset_snapshots (资产快照表)
|
||
**表结构(新版)**
|
||
|
||
| 字段名 | 数据类型 | 约束 | 说明 |
|
||
|---------------------|----------------|----------------------------------|---------------------------------------------------------|
|
||
| id | BIGSERIAL | PRIMARY KEY | 快照ID,自增 |
|
||
| user_id | BIGINT | NOT NULL, FOREIGN KEY | 用户ID,关联 user 表 |
|
||
| 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 user(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,关联 user 表';
|
||
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 持仓价格计划表
|
||
|
||
**表结构**
|
||
|
||
| 字段名 | 数据类型 | 约束 | 说明 |
|
||
|--------|---------|------|------|
|
||
| plan_id | BIGSERIAL | PRIMARY KEY | 计划ID,自增 |
|
||
| position_id | BIGINT | NOT NULL, FOREIGN KEY | 持仓ID,关联 positions 表 |
|
||
| action_type | VARCHAR(20) | NOT NULL, CHECK | 操作类型:buy/sell |
|
||
| plan_price | DECIMAL(18, 4) | NOT NULL | 计划价格 |
|
||
| plan_shares | DECIMAL(18, 4) | | 计划份额(可选) |
|
||
| plan_amount | DECIMAL(18, 2) | | 计划金额(可选,与份额二选一) |
|
||
| step_order | INTEGER | NOT NULL, DEFAULT 1 | 步骤顺序(1, 2, 3...) |
|
||
| is_completed | BOOLEAN | NOT NULL, DEFAULT false | 是否已完成 |
|
||
| completed_at | TIMESTAMP | | 完成时间 |
|
||
| notes | TEXT | | 备注 |
|
||
| created_at | TIMESTAMP | NOT NULL, DEFAULT CURRENT_TIMESTAMP | 创建时间 |
|
||
| updated_at | TIMESTAMP | NOT NULL, DEFAULT CURRENT_TIMESTAMP | 更新时间 |
|
||
|
||
**创建语句**
|
||
|
||
```sql
|
||
CREATE TABLE position_price_plans (
|
||
plan_id BIGSERIAL PRIMARY KEY,
|
||
position_id BIGINT NOT NULL REFERENCES positions(position_id) ON DELETE CASCADE,
|
||
action_type VARCHAR(20) NOT NULL,
|
||
plan_price DECIMAL(18, 4) NOT NULL,
|
||
plan_shares DECIMAL(18, 4),
|
||
plan_amount DECIMAL(18, 2),
|
||
step_order INTEGER NOT NULL DEFAULT 1,
|
||
is_completed BOOLEAN NOT NULL DEFAULT false,
|
||
completed_at TIMESTAMP,
|
||
notes TEXT,
|
||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
CONSTRAINT check_action_type CHECK (action_type IN ('buy', 'sell')),
|
||
CONSTRAINT check_plan_price_positive CHECK (plan_price > 0),
|
||
CONSTRAINT check_step_order_positive CHECK (step_order > 0),
|
||
-- 同一持仓同一操作类型同一顺序只能有一个计划
|
||
UNIQUE(position_id, action_type, step_order)
|
||
);
|
||
|
||
-- 创建索引
|
||
CREATE INDEX idx_position_price_plans_position_id ON position_price_plans(position_id);
|
||
CREATE INDEX idx_position_price_plans_action_type ON position_price_plans(action_type);
|
||
CREATE INDEX idx_position_price_plans_completed ON position_price_plans(is_completed);
|
||
CREATE INDEX idx_position_price_plans_position_action ON position_price_plans(position_id, action_type, step_order);
|
||
|
||
-- 创建触发器自动更新 updated_at
|
||
CREATE TRIGGER update_position_price_plans_updated_at
|
||
BEFORE UPDATE ON position_price_plans
|
||
FOR EACH ROW
|
||
EXECUTE FUNCTION update_updated_at_column();
|
||
|
||
-- 添加注释
|
||
COMMENT ON TABLE position_price_plans IS '持仓价格计划表,记录买入和卖出计划价格';
|
||
COMMENT ON COLUMN position_price_plans.plan_id IS '计划ID,主键,自增';
|
||
COMMENT ON COLUMN position_price_plans.action_type IS '操作类型:buy(买入)/sell(卖出)';
|
||
COMMENT ON COLUMN position_price_plans.plan_price IS '计划价格,当市场价格达到此价格时触发提醒';
|
||
COMMENT ON COLUMN position_price_plans.plan_shares IS '计划份额,计划买入/卖出的数量';
|
||
COMMENT ON COLUMN position_price_plans.plan_amount IS '计划金额,计划买入/卖出的金额(与份额二选一)';
|
||
COMMENT ON COLUMN position_price_plans.step_order IS '步骤顺序,默认3个买点和3个卖点(1, 2, 3)';
|
||
```
|
||
|
||
---
|
||
|
||
### 持仓表使用说明
|
||
|
||
#### 1. 资产类型说明
|
||
|
||
**stock(股票)**
|
||
- symbol: 股票代码(如:600519)
|
||
- market: 市场(A股/港股/美股)
|
||
- shares: 股数
|
||
- cost_price: 每股成本价
|
||
|
||
**fund(基金)**
|
||
- symbol: 基金代码
|
||
- market: 市场(如:场内/场外)
|
||
- shares: 基金份数
|
||
- cost_price: 每份成本价
|
||
|
||
**cash(现金)**
|
||
- symbol: 可自定义(如:CASH_CNY)
|
||
- name: 现金账户名称
|
||
- shares: 现金余额(金额)
|
||
- cost_price: 固定为1(现金无成本价概念)
|
||
|
||
**bond(国债)**
|
||
- symbol: 债券代码
|
||
- market: 市场
|
||
- shares: 债券数量
|
||
- cost_price: 每张成本价
|
||
|
||
#### 2. 价格计划使用示例
|
||
|
||
**创建买入计划(3个买点)**
|
||
```sql
|
||
-- 为持仓创建3个买入计划
|
||
INSERT INTO position_price_plans (position_id, action_type, plan_price, plan_shares, step_order)
|
||
VALUES
|
||
(1, 'buy', 100.00, 100, 1), -- 第一个买点:100元买入100股
|
||
(1, 'buy', 95.00, 100, 2), -- 第二个买点:95元买入100股
|
||
(1, 'buy', 90.00, 100, 3); -- 第三个买点:90元买入100股
|
||
```
|
||
|
||
**创建卖出计划(3个卖点)**
|
||
```sql
|
||
-- 为持仓创建3个卖出计划
|
||
INSERT INTO position_price_plans (position_id, action_type, plan_price, plan_shares, step_order)
|
||
VALUES
|
||
(1, 'sell', 120.00, 50, 1), -- 第一个卖点:120元卖出50股
|
||
(1, 'sell', 130.00, 50, 2), -- 第二个卖点:130元卖出50股
|
||
(1, 'sell', 150.00, 50, 3); -- 第三个卖点:150元卖出50股
|
||
```
|
||
|
||
**查询持仓及其价格计划**
|
||
```sql
|
||
-- 查询持仓及其所有价格计划(包含券商信息)
|
||
SELECT
|
||
p.position_id,
|
||
b.broker_name,
|
||
p.name,
|
||
p.shares,
|
||
p.cost_price,
|
||
p.current_price,
|
||
t.action_type,
|
||
t.plan_price,
|
||
t.plan_shares,
|
||
t.step_order,
|
||
t.is_completed
|
||
FROM positions p
|
||
INNER JOIN brokers b ON p.broker_id = b.broker_id
|
||
LEFT JOIN position_price_plans t ON p.position_id = t.position_id
|
||
WHERE p.user_id = :user_id
|
||
AND p.status = 'active'
|
||
ORDER BY b.sort_order, b.broker_name, p.position_id, t.action_type, t.step_order;
|
||
|
||
-- 按券商汇总查询持仓
|
||
SELECT
|
||
b.broker_name,
|
||
COUNT(*) as position_count,
|
||
SUM(p.shares * p.current_price) as total_value
|
||
FROM positions p
|
||
INNER JOIN brokers b ON p.broker_id = b.broker_id
|
||
WHERE p.user_id = :user_id
|
||
AND p.status = 'active'
|
||
GROUP BY b.broker_id, b.broker_name
|
||
ORDER BY b.sort_order;
|
||
```
|
||
|
||
**查询可用券商列表**
|
||
```sql
|
||
-- 查询所有启用的券商(用于下拉选择)
|
||
SELECT
|
||
broker_id,
|
||
broker_code,
|
||
broker_name,
|
||
region
|
||
FROM brokers
|
||
WHERE is_active = true
|
||
ORDER BY region, sort_order, broker_name;
|
||
|
||
-- 按地区查询券商
|
||
SELECT
|
||
broker_id,
|
||
broker_code,
|
||
broker_name,
|
||
region
|
||
FROM brokers
|
||
WHERE is_active = true
|
||
AND region = 'CN' -- 查询中国券商
|
||
ORDER BY sort_order, broker_name;
|
||
|
||
-- 查询所有地区列表
|
||
SELECT DISTINCT region
|
||
FROM brokers
|
||
WHERE is_active = true
|
||
ORDER BY region;
|
||
```
|
||
|
||
**检查价格计划是否触发**
|
||
```sql
|
||
-- 查询已达到计划价格但未完成的买入计划
|
||
SELECT
|
||
p.position_id,
|
||
p.name,
|
||
p.current_price,
|
||
t.plan_price,
|
||
t.plan_shares,
|
||
t.step_order
|
||
FROM positions p
|
||
INNER JOIN position_price_plans t ON p.position_id = t.position_id
|
||
WHERE p.user_id = :user_id
|
||
AND t.action_type = 'buy'
|
||
AND t.is_completed = false
|
||
AND p.current_price <= t.plan_price
|
||
AND p.status = 'active';
|
||
|
||
-- 查询已达到计划价格但未完成的卖出计划
|
||
SELECT
|
||
p.position_id,
|
||
p.name,
|
||
p.current_price,
|
||
t.plan_price,
|
||
t.plan_shares,
|
||
t.step_order
|
||
FROM positions p
|
||
INNER JOIN position_price_plans t ON p.position_id = t.position_id
|
||
WHERE p.user_id = :user_id
|
||
AND t.action_type = 'sell'
|
||
AND t.is_completed = false
|
||
AND p.current_price >= t.plan_price
|
||
AND p.status = 'active';
|
||
```
|
||
|
||
#### 3. 持仓更新流程
|
||
|
||
**用户修改持仓(主动变更)**
|
||
```sql
|
||
-- 用户直接修改成本价和份数
|
||
UPDATE positions
|
||
SET
|
||
cost_price = :new_cost_price,
|
||
shares = :new_shares,
|
||
notes = :notes,
|
||
updated_at = CURRENT_TIMESTAMP
|
||
WHERE position_id = :position_id
|
||
AND user_id = :user_id;
|
||
|
||
-- 用户手动更新价格时,将当前价格保存到 previous_price
|
||
UPDATE positions
|
||
SET
|
||
previous_price = current_price, -- 先将当前价格保存为上一次价格
|
||
current_price = :new_price, -- 更新为新价格
|
||
updated_at = CURRENT_TIMESTAMP
|
||
WHERE position_id = :position_id
|
||
AND user_id = :user_id;
|
||
|
||
-- 记录持仓变更历史到 position_changes 表
|
||
```
|
||
|
||
**系统更新市场价格(被动变更)**
|
||
```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. 注意事项
|
||
|
||
1. **唯一性约束**
|
||
- 同一用户同一券商同一资产(user_id + broker_id + symbol + market + asset_type)只能有一条持仓
|
||
- 支持用户在不同券商持有同一资产(如:华泰证券持有茅台、招商证券也持有茅台)
|
||
- 避免同一券商重复持仓
|
||
|
||
2. **券商管理**
|
||
- 通过 brokers 表统一管理券商信息
|
||
- 用户添加持仓时从券商列表中选择,保证数据一致性
|
||
- 支持自定义券商(可以添加用户自定义券商)
|
||
- 通过 sort_order 控制券商显示顺序
|
||
|
||
2. **价格计划管理**
|
||
- 默认支持3个买点和3个卖点
|
||
- 可以通过 step_order 扩展更多计划
|
||
- 完成后的计划可以保留作为历史记录
|
||
|
||
3. **现金资产处理**
|
||
- 现金的 cost_price 固定为1
|
||
- shares 字段存储现金余额
|
||
- current_price 也为1(现金无价格波动)
|
||
|
||
4. **多货币支持**
|
||
- 通过 exchange_rate 字段处理汇率
|
||
- 计算总资产时需要统一货币单位
|
||
|
||
5. **价格计划提醒**
|
||
- 需要定时任务检查价格计划
|
||
- 当 current_price 达到 plan_price 时发送提醒
|
||
- 买入:current_price <= plan_price
|
||
- 卖出:current_price >= plan_price
|
||
|
||
6. **自动价格更新功能**
|
||
- `auto_price_update` 字段控制是否启用自动价格更新
|
||
- 付费用户创建持仓时,自动设置为 true
|
||
- 用户订阅过期时,批量更新为 false
|
||
- 未付费用户创建持仓时,默认为 false
|
||
- 系统每日收盘后,仅更新 `auto_price_update = true` 的持仓价格
|
||
|