1064 lines
38 KiB
Markdown
1064 lines
38 KiB
Markdown
# 数据库设计文档
|
||
|
||
## 表结构总览
|
||
数据库使用 PostgreSQL
|
||
|
||
```
|
||
用户相关
|
||
├── users (用户表)
|
||
|
||
基础数据相关
|
||
├── brokers (券商表)
|
||
├── stock_info (股票基本信息表)
|
||
└── stock_daily_price (股票每日收盘价表)
|
||
|
||
账户相关
|
||
├── positions (持仓表)
|
||
├── 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 | 用户名(可选,用于账号密码登录) |
|
||
| 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 |
|
||
|
||
**创建语句**
|
||
|
||
```sql
|
||
CREATE TABLE users (
|
||
user_id BIGSERIAL PRIMARY KEY,
|
||
open_id VARCHAR(100) UNIQUE,
|
||
union_id VARCHAR(100) UNIQUE,
|
||
username VARCHAR(100) UNIQUE,
|
||
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',
|
||
-- 约束:至少要有一种登录方式
|
||
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_status CHECK (status IN ('active', 'inactive', 'deleted'))
|
||
);
|
||
|
||
-- 创建索引
|
||
CREATE INDEX idx_users_open_id ON users(open_id);
|
||
CREATE INDEX idx_users_union_id ON users(union_id);
|
||
CREATE INDEX idx_users_username ON users(username);
|
||
CREATE INDEX idx_users_email ON users(email);
|
||
CREATE INDEX idx_users_phone ON users(phone);
|
||
CREATE INDEX idx_users_status ON users(status);
|
||
CREATE INDEX idx_users_last_login_at ON users(last_login_at);
|
||
|
||
-- 创建自动更新 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 users
|
||
FOR EACH ROW
|
||
EXECUTE FUNCTION update_updated_at_column();
|
||
|
||
-- 添加注释
|
||
COMMENT ON TABLE users IS '用户表,支持微信登录和账号密码登录';
|
||
COMMENT ON COLUMN users.user_id IS '用户ID,主键,自增';
|
||
COMMENT ON COLUMN users.open_id IS '微信 openId,用于小程序/公众号登录,唯一';
|
||
COMMENT ON COLUMN users.union_id IS '微信 unionId,用于跨应用统一标识,唯一';
|
||
COMMENT ON COLUMN users.username IS '用户名,用于账号密码登录,唯一,可选';
|
||
COMMENT ON COLUMN users.email IS '邮箱,用于邮箱登录,唯一,可选';
|
||
COMMENT ON COLUMN users.phone IS '手机号,用于手机号登录,唯一,可选';
|
||
COMMENT ON COLUMN users.nickname IS '用户昵称,显示名称';
|
||
COMMENT ON COLUMN users.avatar_url IS '头像URL';
|
||
COMMENT ON COLUMN users.status IS '用户状态:active(活跃)/inactive(非活跃)/deleted(已删除)';
|
||
COMMENT ON COLUMN users.created_at IS '创建时间,自动设置';
|
||
COMMENT ON COLUMN users.updated_at IS '更新时间,自动更新';
|
||
COMMENT ON COLUMN users.last_login_at IS '最后登录时间';
|
||
```
|
||
|
||
|
||
|
||
|
||
## 基础数据相关表设计
|
||
|
||
### brokers 券商表
|
||
|
||
**表结构**
|
||
|
||
| 字段名 | 数据类型 | 约束 | 说明 |
|
||
|--------|---------|------|------|
|
||
| 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 brokers (
|
||
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_brokers_code ON brokers(broker_code);
|
||
CREATE INDEX idx_brokers_name ON brokers(broker_name);
|
||
CREATE INDEX idx_brokers_region ON brokers(region);
|
||
CREATE INDEX idx_brokers_active ON brokers(is_active);
|
||
CREATE INDEX idx_brokers_sort ON brokers(sort_order);
|
||
CREATE INDEX idx_brokers_region_active ON brokers(region, is_active);
|
||
|
||
-- 创建触发器自动更新 updated_at
|
||
CREATE TRIGGER update_brokers_updated_at
|
||
BEFORE UPDATE ON brokers
|
||
FOR EACH ROW
|
||
EXECUTE FUNCTION update_updated_at_column();
|
||
|
||
-- 添加注释
|
||
COMMENT ON TABLE brokers IS '券商表,记录所有可用的券商信息';
|
||
COMMENT ON COLUMN brokers.broker_code IS '券商代码,用于系统识别,如:HTZQ(华泰证券)';
|
||
COMMENT ON COLUMN brokers.broker_name IS '券商名称,显示给用户的名称,如:华泰证券';
|
||
COMMENT ON COLUMN brokers.region IS '地区/国家代码:CN(中国)/US(美国)/HK(香港)/SG(新加坡)/JP(日本)/UK(英国)/AU(澳大利亚)/CA(加拿大)/OTHER(其他)';
|
||
```
|
||
|
||
**示例数据**
|
||
|
||
```sql
|
||
-- 中国券商
|
||
INSERT INTO brokers (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 brokers (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 brokers (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 | 股票代码 |
|
||
| 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 持仓表
|
||
记录用户持仓情况,包含数量、成本价、最新市场价、券商等
|
||
**表结构**
|
||
|
||
| 字段名 | 数据类型 | 约束 | 说明 |
|
||
|--------|---------|------|------|
|
||
| position_id | BIGSERIAL | PRIMARY KEY | 持仓ID,自增 |
|
||
| user_id | BIGINT | NOT NULL, FOREIGN KEY | 用户ID,关联 users 表 |
|
||
| 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) | | 最新市场价(系统自动更新) |
|
||
| 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 users(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),
|
||
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.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 持仓价格计划表
|
||
|
||
**表结构**
|
||
|
||
| 字段名 | 数据类型 | 约束 | 说明 |
|
||
|--------|---------|------|------|
|
||
| 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;
|
||
|
||
-- 记录持仓变更历史(需要在 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` 的持仓价格
|
||
|