diff --git a/我编写的文档/投资记录-产品设计.md b/我编写的文档/投资记录-产品设计.md new file mode 100644 index 0000000..1e9e066 --- /dev/null +++ b/我编写的文档/投资记录-产品设计.md @@ -0,0 +1,18 @@ +# 投资记录模块-产品设计 +## 核心设计思路 +1. 把所有的表更分为两种:`用户驱动(主动)` 和 `系统驱动(被动)` +2. 系统驱动包含:现金分红、送股、拆股、汇率变动等 +3. 用户驱动包含:初始买入、追加买入、卖出; +4. 所有的用户驱动,都只需要变更最终的持仓成本和最新的持仓份额,这两项。避免其他复杂的操作 +5. 每次用户主动变更,记录成本价和份额的同时,还需要完成如下记录: + - 反向计算本次交易股价和份额,并记录; + - 统计最新的份额和净值,并记录 + - 同时引导填下投资复盘和思考。 +6. 系统驱动的变更:(万一无法实现,可以降级为用户驱动变更) + - 分红:收盘后获取每股分红金额,最新成本价=原成本价 - 分红,市场价逻辑保持不变(使用不复权的股价) + - 送股、拆股等都变更最新的成本价和份额,并记录。 + + + +## 想法 +- 私密分享:可以将自己的交易计划和复盘,通过小程序私密分享-分享给其他人,这样即保障了裂变属性,有增加了隐私安全。 \ No newline at end of file diff --git a/机生文档/NestJS与PostgreSQL集成方案.md b/机生文档/NestJS与PostgreSQL集成方案.md new file mode 100644 index 0000000..8f9dde3 --- /dev/null +++ b/机生文档/NestJS与PostgreSQL集成方案.md @@ -0,0 +1,865 @@ +# NestJS 与 PostgreSQL 集成方案 + +## 一、兼容性说明 + +### ✅ PostgreSQL 完全支持 NestJS + +**简短回答**:PostgreSQL 与 NestJS **完全兼容**,并且是 NestJS 官方推荐和广泛使用的数据库组合。 + +### 1.1 官方支持 + +NestJS 官方文档中明确支持 PostgreSQL,主要通过以下 ORM 库: + +1. **TypeORM** - NestJS 官方推荐,最常用 +2. **Prisma** - 现代化 ORM,类型安全 +3. **Sequelize** - 成熟的 ORM 库 +4. **Knex.js** - SQL 查询构建器 +5. **原生 pg 驱动** - 直接使用 PostgreSQL 驱动 + +### 1.2 为什么选择 NestJS + PostgreSQL + +1. **完美兼容**:NestJS 的依赖注入和模块化架构与 PostgreSQL 配合良好 +2. **TypeScript 支持**:两者都原生支持 TypeScript,类型安全 +3. **生态成熟**:有丰富的库和工具支持 +4. **性能优秀**:PostgreSQL 性能强大,NestJS 框架高效 +5. **开发体验好**:代码生成、迁移工具完善 + +--- + +## 二、推荐方案对比 + +### 2.1 TypeORM(最推荐) + +**优势:** +- ✅ NestJS 官方文档示例主要使用 TypeORM +- ✅ 装饰器语法,与 NestJS 风格一致 +- ✅ 支持数据库迁移(Migration) +- ✅ 支持实体关系映射(ORM) +- ✅ 支持事务管理 +- ✅ 支持查询构建器 + +**适用场景:** +- 需要完整的 ORM 功能 +- 团队熟悉装饰器语法 +- 需要数据库迁移管理 + +### 2.2 Prisma(现代化选择) + +**优势:** +- ✅ 类型安全,TypeScript 支持最好 +- ✅ 代码生成,自动生成类型定义 +- ✅ 迁移工具强大 +- ✅ 性能优秀 +- ✅ 开发体验极佳 + +**适用场景:** +- 重视类型安全 +- 需要快速开发 +- 团队喜欢现代化工具 + +### 2.3 Sequelize(成熟稳定) + +**优势:** +- ✅ 非常成熟,生态丰富 +- ✅ 文档完善 +- ✅ 支持多种数据库 + +**劣势:** +- ⚠️ TypeScript 支持不如 Prisma +- ⚠️ 语法相对传统 + +--- + +## 三、TypeORM 集成方案(推荐) + +### 3.1 安装依赖 + +```bash +npm install @nestjs/typeorm typeorm pg +npm install --save-dev @types/pg +``` + +### 3.2 项目结构 + +``` +src/ +├── app.module.ts +├── database/ +│ ├── database.module.ts +│ └── database.config.ts +├── users/ +│ ├── entities/ +│ │ └── user.entity.ts +│ ├── users.module.ts +│ ├── users.service.ts +│ └── users.controller.ts +├── accounts/ +│ ├── entities/ +│ │ └── account.entity.ts +│ └── ... +└── transactions/ + ├── entities/ + │ └── transaction.entity.ts + └── ... +``` + +### 3.3 配置数据库连接 + +#### database/database.config.ts + +```typescript +import { TypeOrmModuleOptions } from '@nestjs/typeorm'; +import { ConfigService } from '@nestjs/config'; + +export const getDatabaseConfig = ( + configService: ConfigService, +): TypeOrmModuleOptions => ({ + type: 'postgres', + host: configService.get('DB_HOST', 'localhost'), + port: configService.get('DB_PORT', 5432), + username: configService.get('DB_USERNAME', 'postgres'), + password: configService.get('DB_PASSWORD', 'password'), + database: configService.get('DB_NAME', 'vestmind'), + entities: [__dirname + '/../**/*.entity{.ts,.js}'], + synchronize: configService.get('NODE_ENV') !== 'production', // 生产环境设为 false + logging: configService.get('NODE_ENV') === 'development', + migrations: [__dirname + '/../migrations/**/*{.ts,.js}'], + migrationsRun: false, + ssl: configService.get('DB_SSL', false), +}); +``` + +#### database/database.module.ts + +```typescript +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { ConfigModule, ConfigService } from '@nestjs/config'; +import { getDatabaseConfig } from './database.config'; + +@Module({ + imports: [ + TypeOrmModule.forRootAsync({ + imports: [ConfigModule], + useFactory: getDatabaseConfig, + inject: [ConfigService], + }), + ], +}) +export class DatabaseModule {} +``` + +#### app.module.ts + +```typescript +import { Module } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; +import { DatabaseModule } from './database/database.module'; +import { UsersModule } from './users/users.module'; +import { AccountsModule } from './accounts/accounts.module'; +import { TransactionsModule } from './transactions/transactions.module'; + +@Module({ + imports: [ + ConfigModule.forRoot({ + isGlobal: true, + envFilePath: '.env', + }), + DatabaseModule, + UsersModule, + AccountsModule, + TransactionsModule, + ], +}) +export class AppModule {} +``` + +### 3.4 实体定义示例 + +#### users/entities/user.entity.ts + +```typescript +import { + Entity, + PrimaryGeneratedColumn, + Column, + CreateDateColumn, + UpdateDateColumn, + OneToMany, +} from 'typeorm'; +import { Account } from '../../accounts/entities/account.entity'; + +@Entity('users') +export class User { + @PrimaryGeneratedColumn('increment') + id: number; + + @Column({ type: 'varchar', length: 50, unique: true }) + username: string; + + @Column({ type: 'varchar', length: 100, unique: true, nullable: true }) + email: string; + + @Column({ type: 'varchar', length: 20, unique: true, nullable: true }) + phone: string; + + @Column({ type: 'varchar', length: 255 }) + passwordHash: string; + + @Column({ type: 'varchar', length: 50, nullable: true }) + nickname: string; + + @Column({ type: 'varchar', length: 255, nullable: true }) + avatarUrl: string; + + @Column({ type: 'varchar', length: 20, default: 'active' }) + status: string; + + @CreateDateColumn() + createdAt: Date; + + @UpdateDateColumn() + updatedAt: Date; + + @Column({ type: 'timestamp', nullable: true }) + lastLoginAt: Date; + + // 关系 + @OneToMany(() => Account, (account) => account.user) + accounts: Account[]; +} +``` + +#### accounts/entities/account.entity.ts + +```typescript +import { + Entity, + PrimaryGeneratedColumn, + Column, + CreateDateColumn, + UpdateDateColumn, + ManyToOne, + OneToMany, + JoinColumn, +} from 'typeorm'; +import { User } from '../../users/entities/user.entity'; +import { Position } from '../../positions/entities/position.entity'; +import { Transaction } from '../../transactions/entities/transaction.entity'; + +@Entity('accounts') +export class Account { + @PrimaryGeneratedColumn('increment') + id: number; + + @Column({ type: 'int' }) + userId: number; + + @Column({ type: 'varchar', length: 100 }) + name: string; + + @Column({ type: 'varchar', length: 20 }) + type: string; // stock, fund, cash, mixed + + @Column({ type: 'varchar', length: 10, default: 'CNY' }) + currency: string; + + @Column({ type: 'text', nullable: true }) + description: string; + + @Column({ type: 'varchar', length: 20, default: 'active' }) + status: string; + + @CreateDateColumn() + createdAt: Date; + + @UpdateDateColumn() + updatedAt: Date; + + // 关系 + @ManyToOne(() => User, (user) => user.accounts) + @JoinColumn({ name: 'userId' }) + user: User; + + @OneToMany(() => Position, (position) => position.account) + positions: Position[]; + + @OneToMany(() => Transaction, (transaction) => transaction.account) + transactions: Transaction[]; +} +``` + +#### transactions/entities/transaction.entity.ts + +```typescript +import { + Entity, + PrimaryGeneratedColumn, + Column, + CreateDateColumn, + UpdateDateColumn, + ManyToOne, + JoinColumn, + Index, +} from 'typeorm'; +import { User } from '../../users/entities/user.entity'; +import { Account } from '../../accounts/entities/account.entity'; +import { Position } from '../../positions/entities/position.entity'; +import { TradingPlan } from '../../trading-plans/entities/trading-plan.entity'; + +@Entity('transactions') +@Index(['userId', 'date', 'createdAt']) +@Index(['accountId']) +@Index(['positionId']) +export class Transaction { + @PrimaryGeneratedColumn('increment') + id: number; + + @Column({ type: 'int' }) + userId: number; + + @Column({ type: 'int' }) + accountId: number; + + @Column({ type: 'int', nullable: true }) + positionId: number; + + @Column({ type: 'int', nullable: true }) + tradingPlanId: number; + + @Column({ type: 'varchar', length: 20 }) + type: string; // buy, sell, dividend, split, bonus, rights, deposit, withdraw + + @Column({ type: 'date' }) + date: Date; + + @Column({ type: 'varchar', length: 20, nullable: true }) + symbol: string; + + @Column({ type: 'varchar', length: 100, nullable: true }) + name: string; + + @Column({ type: 'varchar', length: 20, nullable: true }) + market: string; + + @Column({ type: 'decimal', precision: 18, scale: 4, nullable: true }) + shares: number; + + @Column({ type: 'decimal', precision: 18, scale: 4, nullable: true }) + price: number; + + @Column({ type: 'decimal', precision: 18, scale: 2 }) + amount: number; + + @Column({ type: 'decimal', precision: 18, scale: 2, default: 0 }) + fee: number; + + @Column({ type: 'varchar', length: 10, default: 'CNY' }) + currency: string; + + @Column({ type: 'decimal', precision: 10, scale: 6, default: 1 }) + exchangeRate: number; + + @Column({ type: 'jsonb', nullable: true }) + metadata: Record; + + @CreateDateColumn() + createdAt: Date; + + @UpdateDateColumn() + updatedAt: Date; + + // 关系 + @ManyToOne(() => User) + @JoinColumn({ name: 'userId' }) + user: User; + + @ManyToOne(() => Account) + @JoinColumn({ name: 'accountId' }) + account: Account; + + @ManyToOne(() => Position, { nullable: true }) + @JoinColumn({ name: 'positionId' }) + position: Position; + + @ManyToOne(() => TradingPlan, { nullable: true }) + @JoinColumn({ name: 'tradingPlanId' }) + tradingPlan: TradingPlan; +} +``` + +### 3.5 Service 使用示例 + +#### transactions/transactions.service.ts + +```typescript +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository, Between } from 'typeorm'; +import { Transaction } from './entities/transaction.entity'; +import { CreateTransactionDto } from './dto/create-transaction.dto'; + +@Injectable() +export class TransactionsService { + constructor( + @InjectRepository(Transaction) + private transactionRepository: Repository, + ) {} + + async create(createTransactionDto: CreateTransactionDto): Promise { + const transaction = this.transactionRepository.create(createTransactionDto); + return await this.transactionRepository.save(transaction); + } + + async findAll(userId: number, page: number = 1, limit: number = 20) { + const [data, total] = await this.transactionRepository.findAndCount({ + where: { userId }, + relations: ['account', 'position', 'tradingPlan'], + order: { date: 'DESC', createdAt: 'DESC' }, + skip: (page - 1) * limit, + take: limit, + }); + + return { + data, + total, + page, + limit, + totalPages: Math.ceil(total / limit), + }; + } + + async findOne(id: number, userId: number): Promise { + return await this.transactionRepository.findOne({ + where: { id, userId }, + relations: ['account', 'position', 'tradingPlan'], + }); + } + + async findByDateRange( + userId: number, + startDate: Date, + endDate: Date, + ): Promise { + return await this.transactionRepository.find({ + where: { + userId, + date: Between(startDate, endDate), + }, + order: { date: 'DESC' }, + }); + } + + async update(id: number, userId: number, updateData: Partial) { + await this.transactionRepository.update({ id, userId }, updateData); + return this.findOne(id, userId); + } + + async remove(id: number, userId: number): Promise { + await this.transactionRepository.delete({ id, userId }); + } +} +``` + +### 3.6 Module 配置 + +#### transactions/transactions.module.ts + +```typescript +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { TransactionsService } from './transactions.service'; +import { TransactionsController } from './transactions.controller'; +import { Transaction } from './entities/transaction.entity'; + +@Module({ + imports: [TypeOrmModule.forFeature([Transaction])], + controllers: [TransactionsController], + providers: [TransactionsService], + exports: [TransactionsService], +}) +export class TransactionsModule {} +``` + +### 3.7 事务处理示例 + +```typescript +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { DataSource } from 'typeorm'; +import { Transaction } from './entities/transaction.entity'; +import { Position } from '../positions/entities/position.entity'; +import { CashAccount } from '../cash-accounts/entities/cash-account.entity'; + +@Injectable() +export class TradingService { + constructor( + private dataSource: DataSource, + @InjectRepository(Transaction) + private transactionRepository: Repository, + ) {} + + async buyStock(buyDto: CreateBuyDto) { + const queryRunner = this.dataSource.createQueryRunner(); + await queryRunner.connect(); + await queryRunner.startTransaction(); + + try { + // 1. 创建交易记录 + const transaction = this.transactionRepository.create({ + ...buyDto, + type: 'buy', + }); + await queryRunner.manager.save(transaction); + + // 2. 更新持仓 + const position = await queryRunner.manager.findOne(Position, { + where: { accountId: buyDto.accountId, symbol: buyDto.symbol }, + }); + + if (position) { + // 更新现有持仓 + const newShares = position.shares + buyDto.shares; + const newCostPrice = + (position.shares * position.costPrice + + buyDto.shares * buyDto.price + + buyDto.fee) / + newShares; + position.shares = newShares; + position.costPrice = newCostPrice; + await queryRunner.manager.save(position); + } else { + // 创建新持仓 + const newPosition = queryRunner.manager.create(Position, { + accountId: buyDto.accountId, + symbol: buyDto.symbol, + name: buyDto.name, + market: buyDto.market, + shares: buyDto.shares, + costPrice: buyDto.price, + currentPrice: buyDto.price, + }); + await queryRunner.manager.save(newPosition); + } + + // 3. 更新现金账户 + const cashAccount = await queryRunner.manager.findOne(CashAccount, { + where: { accountId: buyDto.accountId }, + }); + cashAccount.balance -= buyDto.amount + buyDto.fee; + await queryRunner.manager.save(cashAccount); + + await queryRunner.commitTransaction(); + return transaction; + } catch (error) { + await queryRunner.rollbackTransaction(); + throw error; + } finally { + await queryRunner.release(); + } + } +} +``` + +--- + +## 四、Prisma 集成方案(备选) + +### 4.1 安装依赖 + +```bash +npm install @prisma/client +npm install -D prisma +npx prisma init +``` + +### 4.2 Prisma Schema 示例 + +#### prisma/schema.prisma + +```prisma +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +model User { + id BigInt @id @default(autoincrement()) + username String @unique @db.VarChar(50) + email String? @unique @db.VarChar(100) + phone String? @unique @db.VarChar(20) + passwordHash String @db.VarChar(255) + nickname String? @db.VarChar(50) + avatarUrl String? @db.VarChar(255) + status String @default("active") @db.VarChar(20) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + lastLoginAt DateTime? + + accounts Account[] + transactions Transaction[] + + @@map("users") +} + +model Account { + id BigInt @id @default(autoincrement()) + userId BigInt + name String @db.VarChar(100) + type String @db.VarChar(20) + currency String @default("CNY") @db.VarChar(10) + description String? + status String @default("active") @db.VarChar(20) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + positions Position[] + transactions Transaction[] + + @@map("accounts") +} + +model Transaction { + id BigInt @id @default(autoincrement()) + userId BigInt + accountId BigInt + positionId BigInt? + tradingPlanId BigInt? + type String @db.VarChar(20) + date DateTime @db.Date + symbol String? @db.VarChar(20) + name String? @db.VarChar(100) + market String? @db.VarChar(20) + shares Decimal? @db.Decimal(18, 4) + price Decimal? @db.Decimal(18, 4) + amount Decimal @db.Decimal(18, 2) + fee Decimal @default(0) @db.Decimal(18, 2) + currency String @default("CNY") @db.VarChar(10) + exchangeRate Decimal @default(1) @db.Decimal(10, 6) + metadata Json? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + account Account @relation(fields: [accountId], references: [id], onDelete: Cascade) + + @@index([userId, date, createdAt]) + @@index([accountId]) + @@index([positionId]) + @@map("transactions") +} +``` + +### 4.3 Prisma Service + +#### prisma/prisma.service.ts + +```typescript +import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common'; +import { PrismaClient } from '@prisma/client'; + +@Injectable() +export class PrismaService + extends PrismaClient + implements OnModuleInit, OnModuleDestroy +{ + async onModuleInit() { + await this.$connect(); + } + + async onModuleDestroy() { + await this.$disconnect(); + } +} +``` + +--- + +## 五、环境配置 + +### 5.1 .env 文件 + +```env +# 数据库配置 +DB_HOST=localhost +DB_PORT=5432 +DB_USERNAME=postgres +DB_PASSWORD=your_password +DB_NAME=vestmind +DB_SSL=false + +# 或者使用连接字符串(Prisma) +DATABASE_URL="postgresql://postgres:password@localhost:5432/vestmind?schema=public" + +# 应用配置 +NODE_ENV=development +PORT=3000 +``` + +### 5.2 package.json 脚本 + +```json +{ + "scripts": { + "start": "nest start", + "start:dev": "nest start --watch", + "start:prod": "node dist/main", + "typeorm": "typeorm-ts-node-commonjs", + "migration:generate": "typeorm-ts-node-commonjs migration:generate", + "migration:run": "typeorm-ts-node-commonjs migration:run", + "migration:revert": "typeorm-ts-node-commonjs migration:revert", + "prisma:generate": "prisma generate", + "prisma:migrate": "prisma migrate dev", + "prisma:studio": "prisma studio" + } +} +``` + +--- + +## 六、数据库迁移 + +### 6.1 TypeORM 迁移 + +#### 创建迁移 + +```bash +npm run typeorm migration:generate -- -n CreateUsersTable +``` + +#### 迁移文件示例 + +```typescript +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class CreateUsersTable1234567890 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + CREATE TABLE users ( + id BIGSERIAL PRIMARY KEY, + username VARCHAR(50) UNIQUE NOT NULL, + email VARCHAR(100) UNIQUE, + password_hash VARCHAR(255) NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP + ); + `); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP TABLE users;`); + } +} +``` + +### 6.2 Prisma 迁移 + +```bash +# 创建迁移 +npx prisma migrate dev --name init + +# 应用迁移 +npx prisma migrate deploy + +# 查看迁移状态 +npx prisma migrate status +``` + +--- + +## 七、性能优化建议 + +### 7.1 连接池配置 + +```typescript +// TypeORM +{ + type: 'postgres', + // ... 其他配置 + extra: { + max: 20, // 最大连接数 + min: 5, // 最小连接数 + idleTimeoutMillis: 30000, + connectionTimeoutMillis: 2000, + }, +} +``` + +### 7.2 查询优化 + +```typescript +// 使用 select 只查询需要的字段 +const users = await userRepository.find({ + select: ['id', 'username', 'email'], +}); + +// 使用 relations 预加载关联数据 +const transaction = await transactionRepository.findOne({ + where: { id }, + relations: ['account', 'position'], +}); + +// 使用 QueryBuilder 进行复杂查询 +const result = await transactionRepository + .createQueryBuilder('transaction') + .leftJoinAndSelect('transaction.account', 'account') + .where('transaction.userId = :userId', { userId }) + .andWhere('transaction.date BETWEEN :start AND :end', { start, end }) + .orderBy('transaction.date', 'DESC') + .getMany(); +``` + +--- + +## 八、总结 + +### 8.1 推荐方案 + +**对于您的项目,推荐使用 TypeORM + PostgreSQL:** + +1. ✅ NestJS 官方推荐,文档完善 +2. ✅ 装饰器语法,与 NestJS 风格一致 +3. ✅ 支持复杂的实体关系 +4. ✅ 支持事务管理 +5. ✅ 迁移工具完善 + +### 8.2 快速开始 + +```bash +# 1. 创建 NestJS 项目 +npm i -g @nestjs/cli +nest new vest-mind-backend + +# 2. 安装依赖 +cd vest-mind-backend +npm install @nestjs/typeorm typeorm pg +npm install --save-dev @types/pg + +# 3. 配置数据库连接 +# 参考上面的 database.module.ts + +# 4. 创建实体 +nest g module users +nest g service users +nest g controller users + +# 5. 运行项目 +npm run start:dev +``` + +### 8.3 参考资料 + +- [NestJS 官方文档 - TypeORM](https://docs.nestjs.com/techniques/database) +- [TypeORM 官方文档](https://typeorm.io/) +- [PostgreSQL 官方文档](https://www.postgresql.org/docs/) + +--- + +**文档版本**:v1.0 +**创建日期**:2024年 + diff --git a/机生文档/PostgreSQL使用文档.md b/机生文档/PostgreSQL使用文档.md new file mode 100644 index 0000000..76e63ec --- /dev/null +++ b/机生文档/PostgreSQL使用文档.md @@ -0,0 +1,1527 @@ +# PostgreSQL 使用文档 + +## 目录 + +1. [安装 PostgreSQL](#一安装-postgresql) +2. [基本操作](#二基本操作) +3. [数据库操作](#三数据库操作) +4. [表操作](#四表操作) +5. [数据操作(CRUD)](#五数据操作crud) +6. [索引操作](#六索引操作) +7. [高级查询](#七高级查询) +8. [事务管理](#八事务管理) +9. [常用函数](#九常用函数) +10. [实用技巧](#十实用技巧) + +--- + +## 一、安装 PostgreSQL + +### 1.1 macOS 安装 + +#### 方法一:使用 Homebrew(推荐) + +```bash +# 安装 Homebrew(如果还没有) +/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + +# 安装 PostgreSQL +brew install postgresql@15 + +# 启动 PostgreSQL 服务 +brew services start postgresql@15 + +# 或者手动启动 +pg_ctl -D /usr/local/var/postgresql@15 start +``` + +#### 方法二:使用 Postgres.app(图形化工具) + +1. 访问 https://postgresapp.com/ +2. 下载并安装 Postgres.app +3. 打开应用,点击 "Initialize" 初始化数据库 + +#### 方法三:使用官方安装包 + +1. 访问 https://www.postgresql.org/download/macosx/ +2. 下载官方安装包 +3. 按照安装向导完成安装 + +### 1.2 Linux 安装 + +#### Ubuntu/Debian + +```bash +# 更新包列表 +sudo apt update + +# 安装 PostgreSQL +sudo apt install postgresql postgresql-contrib + +# 启动服务 +sudo systemctl start postgresql + +# 设置开机自启 +sudo systemctl enable postgresql + +# 查看服务状态 +sudo systemctl status postgresql +``` + +#### CentOS/RHEL/Fedora + +```bash +# CentOS/RHEL 7+ +sudo yum install postgresql-server postgresql-contrib + +# 或者使用 dnf (Fedora/CentOS 8+) +sudo dnf install postgresql-server postgresql-contrib + +# 初始化数据库 +sudo postgresql-setup --initdb + +# 启动服务 +sudo systemctl start postgresql +sudo systemctl enable postgresql +``` + +#### Arch Linux + +```bash +# 安装 +sudo pacman -S postgresql + +# 初始化数据库 +sudo -u postgres initdb -D /var/lib/postgres/data + +# 启动服务 +sudo systemctl start postgresql +sudo systemctl enable postgresql +``` + +### 1.3 验证安装 + +```bash +# 检查 PostgreSQL 版本 +psql --version + +# 或者 +postgres --version + +# 检查服务状态(Linux) +sudo systemctl status postgresql +``` + +### 1.4 初始配置 + +#### 设置 postgres 用户密码 + +```bash +# 切换到 postgres 用户(Linux) +sudo -u postgres psql + +# 或者直接连接(macOS) +psql -U postgres + +# 在 psql 中设置密码 +ALTER USER postgres PASSWORD 'your_password'; +``` + +#### 创建新用户和数据库 + +```bash +# 使用 postgres 用户登录 +psql -U postgres + +# 创建新用户 +CREATE USER myuser WITH PASSWORD 'mypassword'; + +# 创建数据库 +CREATE DATABASE mydb OWNER myuser; + +# 授予权限 +GRANT ALL PRIVILEGES ON DATABASE mydb TO myuser; + +# 退出 +\q +``` + +--- + +## 二、基本操作 + +### 2.1 连接数据库 + +```bash +# 使用默认用户连接默认数据库 +psql + +# 指定用户和数据库 +psql -U username -d database_name + +# 指定主机和端口 +psql -h localhost -p 5432 -U username -d database_name + +# 使用连接字符串 +psql postgresql://username:password@localhost:5432/database_name +``` + +### 2.2 psql 常用命令 + +```sql +-- 查看所有数据库 +\l +-- 或者 +\list + +-- 连接到指定数据库 +\c database_name +-- 或者 +\connect database_name + +-- 查看当前数据库 +SELECT current_database(); + +-- 查看所有表 +\dt + +-- 查看表结构 +\d table_name + +-- 查看表详细信息(包括索引、约束等) +\d+ table_name + +-- 查看所有用户 +\du + +-- 查看当前用户 +SELECT current_user; + +-- 查看帮助 +\? +-- 查看 SQL 命令帮助 +\h +-- 查看特定命令帮助 +\h SELECT + +-- 退出 psql +\q +-- 或者 +exit + +-- 清屏 +\! clear -- Linux +\! cls -- Windows + +-- 执行外部 SQL 文件 +\i /path/to/file.sql + +-- 将查询结果导出到文件 +\o /path/to/output.txt +SELECT * FROM users; +\o + +-- 显示执行时间 +\timing + +-- 显示查询结果(表格格式) +\x -- 切换显示模式(横向/纵向) + +-- 查看命令历史 +\s + +-- 保存命令历史到文件 +\s /path/to/history.sql +``` + +--- + +## 三、数据库操作 + +### 3.1 创建数据库 + +```sql +-- 基本语法 +CREATE DATABASE database_name; + +-- 指定所有者 +CREATE DATABASE database_name OWNER username; + +-- 指定编码 +CREATE DATABASE database_name + WITH ENCODING 'UTF8'; + +-- 完整示例 +CREATE DATABASE vestmind + OWNER myuser + ENCODING 'UTF8' + LC_COLLATE 'zh_CN.UTF-8' + LC_CTYPE 'zh_CN.UTF-8' + TEMPLATE template0; +``` + +### 3.2 查看数据库 + +```sql +-- 查看所有数据库 +SELECT datname FROM pg_database; + +-- 查看数据库详细信息 +\l +-- 或者 +\list + +-- 查看当前数据库 +SELECT current_database(); +``` + +### 3.3 删除数据库 + +```sql +-- 删除数据库(需要先断开所有连接) +DROP DATABASE database_name; + +-- 强制删除(终止所有连接) +-- 首先终止所有连接 +SELECT pg_terminate_backend(pid) +FROM pg_stat_activity +WHERE datname = 'database_name'; + +-- 然后删除 +DROP DATABASE database_name; +``` + +### 3.4 修改数据库 + +```sql +-- 重命名数据库 +ALTER DATABASE old_name RENAME TO new_name; + +-- 修改所有者 +ALTER DATABASE database_name OWNER TO new_owner; + +-- 修改连接限制 +ALTER DATABASE database_name WITH CONNECTION LIMIT 10; +``` + +### 3.5 备份和恢复 + +```bash +# 备份数据库 +pg_dump -U username -d database_name > backup.sql + +# 备份为自定义格式(压缩) +pg_dump -U username -d database_name -F c -f backup.dump + +# 备份所有数据库 +pg_dumpall -U username > all_databases.sql + +# 恢复数据库 +psql -U username -d database_name < backup.sql + +# 恢复自定义格式备份 +pg_restore -U username -d database_name backup.dump + +# 只恢复表结构 +pg_dump -U username -d database_name -s > schema_only.sql + +# 只恢复数据 +pg_dump -U username -d database_name -a > data_only.sql +``` + +--- + +## 四、表操作 + +### 4.1 创建表 + +#### 基本语法 + +```sql +CREATE TABLE table_name ( + column1 datatype constraint, + column2 datatype constraint, + ... +); +``` + +#### 数据类型 + +**数值类型:** +```sql +SMALLINT -- 2字节,-32768 到 32767 +INTEGER -- 4字节,-2147483648 到 2147483647 +BIGINT -- 8字节,大整数 +DECIMAL(p,s) -- 精确数值,p=精度,s=小数位数 +NUMERIC(p,s) -- 同 DECIMAL +REAL -- 4字节,单精度浮点数 +DOUBLE PRECISION -- 8字节,双精度浮点数 +SERIAL -- 自增整数(1 到 2147483647) +BIGSERIAL -- 大自增整数 +``` + +**字符串类型:** +```sql +VARCHAR(n) -- 可变长度字符串,最大n字符 +CHAR(n) -- 固定长度字符串,n字符 +TEXT -- 无限长度字符串 +``` + +**日期时间类型:** +```sql +DATE -- 日期(年-月-日) +TIME -- 时间(时:分:秒) +TIMESTAMP -- 日期和时间 +TIMESTAMPTZ -- 带时区的时间戳 +INTERVAL -- 时间间隔 +``` + +**布尔类型:** +```sql +BOOLEAN -- true/false +``` + +**JSON 类型:** +```sql +JSON -- JSON 数据 +JSONB -- 二进制 JSON(推荐,性能更好) +``` + +#### 约束 + +```sql +-- 主键约束 +id INTEGER PRIMARY KEY +-- 或者 +id INTEGER, +PRIMARY KEY (id) + +-- 外键约束 +user_id INTEGER REFERENCES users(id) +-- 或者 +user_id INTEGER, +FOREIGN KEY (user_id) REFERENCES users(id) + +-- 唯一约束 +email VARCHAR(100) UNIQUE +-- 或者 +email VARCHAR(100), +UNIQUE (email) + +-- 非空约束 +name VARCHAR(50) NOT NULL + +-- 检查约束 +age INTEGER CHECK (age > 0 AND age < 150) + +-- 默认值 +created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + +-- 自增(使用 SERIAL) +id SERIAL PRIMARY KEY +``` + +#### 完整示例 + +```sql +-- 创建用户表 +CREATE TABLE users ( + id BIGSERIAL PRIMARY KEY, + username VARCHAR(50) UNIQUE NOT NULL, + email VARCHAR(100) UNIQUE, + phone VARCHAR(20) UNIQUE, + password_hash VARCHAR(255) NOT NULL, + nickname VARCHAR(50), + avatar_url VARCHAR(255), + status VARCHAR(20) DEFAULT 'active', + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + last_login_at TIMESTAMP, + CHECK (status IN ('active', 'inactive', 'deleted')) +); + +-- 创建账户表 +CREATE TABLE accounts ( + id BIGSERIAL PRIMARY KEY, + user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE, + name VARCHAR(100) NOT NULL, + type VARCHAR(20) NOT NULL, + currency VARCHAR(10) NOT NULL DEFAULT 'CNY', + description TEXT, + status VARCHAR(20) DEFAULT 'active', + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + CHECK (type IN ('stock', 'fund', 'cash', 'mixed')), + CHECK (status IN ('active', 'archived', 'deleted')) +); + +-- 创建持仓表 +CREATE TABLE positions ( + id BIGSERIAL PRIMARY KEY, + account_id BIGINT NOT NULL REFERENCES accounts(id) ON DELETE CASCADE, + symbol VARCHAR(20) NOT NULL, + name VARCHAR(100) NOT NULL, + market VARCHAR(20) NOT NULL, + 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', + status VARCHAR(20) DEFAULT 'active', + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + UNIQUE(account_id, symbol, market), + CHECK (shares >= 0), + CHECK (status IN ('active', 'suspended', 'delisted')) +); +``` + +### 4.2 查看表 + +```sql +-- 查看所有表 +\dt + +-- 查看表结构 +\d table_name + +-- 查看表详细信息 +\d+ table_name + +-- 查看表的所有列 +SELECT column_name, data_type, is_nullable, column_default +FROM information_schema.columns +WHERE table_name = 'users'; + +-- 查看表的约束 +SELECT constraint_name, constraint_type +FROM information_schema.table_constraints +WHERE table_name = 'users'; +``` + +### 4.3 修改表 + +```sql +-- 添加列 +ALTER TABLE table_name +ADD COLUMN column_name datatype; + +-- 示例 +ALTER TABLE users +ADD COLUMN age INTEGER; + +-- 删除列 +ALTER TABLE table_name +DROP COLUMN column_name; + +-- 修改列类型 +ALTER TABLE table_name +ALTER COLUMN column_name TYPE new_datatype; + +-- 示例 +ALTER TABLE users +ALTER COLUMN age TYPE SMALLINT; + +-- 重命名列 +ALTER TABLE table_name +RENAME COLUMN old_name TO new_name; + +-- 添加约束 +ALTER TABLE table_name +ADD CONSTRAINT constraint_name constraint_definition; + +-- 示例:添加外键 +ALTER TABLE accounts +ADD CONSTRAINT fk_user +FOREIGN KEY (user_id) REFERENCES users(id); + +-- 删除约束 +ALTER TABLE table_name +DROP CONSTRAINT constraint_name; + +-- 重命名表 +ALTER TABLE old_table_name +RENAME TO new_table_name; + +-- 设置列的默认值 +ALTER TABLE table_name +ALTER COLUMN column_name SET DEFAULT default_value; + +-- 删除列的默认值 +ALTER TABLE table_name +ALTER COLUMN column_name DROP DEFAULT; + +-- 设置列非空 +ALTER TABLE table_name +ALTER COLUMN column_name SET NOT NULL; + +-- 取消列非空 +ALTER TABLE table_name +ALTER COLUMN column_name DROP NOT NULL; +``` + +### 4.4 删除表 + +```sql +-- 删除表 +DROP TABLE table_name; + +-- 删除表(如果存在) +DROP TABLE IF EXISTS table_name; + +-- 级联删除(删除表及其依赖对象) +DROP TABLE table_name CASCADE; + +-- 清空表数据(保留表结构) +TRUNCATE TABLE table_name; + +-- 清空表数据并重置自增序列 +TRUNCATE TABLE table_name RESTART IDENTITY; +``` + +--- + +## 五、数据操作(CRUD) + +### 5.1 插入数据(INSERT) + +#### 基本语法 + +```sql +-- 插入单行数据 +INSERT INTO table_name (column1, column2, ...) +VALUES (value1, value2, ...); + +-- 插入多行数据 +INSERT INTO table_name (column1, column2, ...) +VALUES + (value1, value2, ...), + (value3, value4, ...), + (value5, value6, ...); + +-- 插入所有列(按表定义顺序) +INSERT INTO table_name +VALUES (value1, value2, ...); + +-- 从查询结果插入 +INSERT INTO table_name (column1, column2, ...) +SELECT column1, column2, ... +FROM other_table +WHERE condition; +``` + +#### 示例 + +```sql +-- 插入用户 +INSERT INTO users (username, email, password_hash, nickname) +VALUES ('john_doe', 'john@example.com', 'hashed_password', 'John'); + +-- 插入多用户 +INSERT INTO users (username, email, password_hash, nickname) +VALUES + ('alice', 'alice@example.com', 'hash1', 'Alice'), + ('bob', 'bob@example.com', 'hash2', 'Bob'), + ('charlie', 'charlie@example.com', 'hash3', 'Charlie'); + +-- 使用 RETURNING 返回插入的数据 +INSERT INTO users (username, email, password_hash) +VALUES ('david', 'david@example.com', 'hash4') +RETURNING id, username, created_at; + +-- 插入时处理冲突(ON CONFLICT) +INSERT INTO users (username, email, password_hash) +VALUES ('john_doe', 'john@example.com', 'new_hash') +ON CONFLICT (username) +DO UPDATE SET + email = EXCLUDED.email, + password_hash = EXCLUDED.password_hash, + updated_at = CURRENT_TIMESTAMP; +``` + +### 5.2 查询数据(SELECT) + +#### 基本语法 + +```sql +-- 查询所有列 +SELECT * FROM table_name; + +-- 查询指定列 +SELECT column1, column2, ... FROM table_name; + +-- 条件查询 +SELECT * FROM table_name WHERE condition; + +-- 排序 +SELECT * FROM table_name ORDER BY column_name ASC/DESC; + +-- 限制结果数量 +SELECT * FROM table_name LIMIT number; + +-- 跳过结果 +SELECT * FROM table_name OFFSET number; + +-- 组合使用 +SELECT * FROM table_name +WHERE condition +ORDER BY column_name DESC +LIMIT 10 OFFSET 20; +``` + +#### 示例 + +```sql +-- 查询所有用户 +SELECT * FROM users; + +-- 查询指定列 +SELECT id, username, email, created_at FROM users; + +-- 条件查询 +SELECT * FROM users WHERE status = 'active'; + +-- 多条件查询 +SELECT * FROM users +WHERE status = 'active' AND created_at > '2024-01-01'; + +-- 模糊查询 +SELECT * FROM users WHERE username LIKE 'john%'; + +-- IN 查询 +SELECT * FROM users WHERE id IN (1, 2, 3, 4, 5); + +-- BETWEEN 查询 +SELECT * FROM users +WHERE created_at BETWEEN '2024-01-01' AND '2024-12-31'; + +-- 排序 +SELECT * FROM users ORDER BY created_at DESC; + +-- 多列排序 +SELECT * FROM users +ORDER BY status ASC, created_at DESC; + +-- 限制数量 +SELECT * FROM users LIMIT 10; + +-- 分页查询 +SELECT * FROM users +ORDER BY id +LIMIT 10 OFFSET 0; -- 第1页 +SELECT * FROM users +ORDER BY id +LIMIT 10 OFFSET 10; -- 第2页 + +-- 去重 +SELECT DISTINCT status FROM users; + +-- 聚合函数 +SELECT + COUNT(*) as total_users, + COUNT(DISTINCT status) as status_count, + MAX(created_at) as latest_user, + MIN(created_at) as earliest_user +FROM users; + +-- 分组查询 +SELECT status, COUNT(*) as count +FROM users +GROUP BY status; + +-- HAVING 子句 +SELECT status, COUNT(*) as count +FROM users +GROUP BY status +HAVING COUNT(*) > 10; +``` + +### 5.3 更新数据(UPDATE) + +#### 基本语法 + +```sql +UPDATE table_name +SET column1 = value1, column2 = value2, ... +WHERE condition; +``` + +#### 示例 + +```sql +-- 更新单列 +UPDATE users +SET nickname = 'Johnny' +WHERE id = 1; + +-- 更新多列 +UPDATE users +SET + nickname = 'Johnny', + email = 'newemail@example.com', + updated_at = CURRENT_TIMESTAMP +WHERE id = 1; + +-- 使用表达式更新 +UPDATE positions +SET current_price = current_price * 1.1 +WHERE symbol = '600519'; + +-- 基于子查询更新 +UPDATE accounts +SET status = 'archived' +WHERE user_id IN ( + SELECT id FROM users WHERE status = 'deleted' +); + +-- 更新时返回结果 +UPDATE users +SET nickname = 'NewName' +WHERE id = 1 +RETURNING id, username, nickname; +``` + +### 5.4 删除数据(DELETE) + +#### 基本语法 + +```sql +DELETE FROM table_name WHERE condition; +``` + +#### 示例 + +```sql +-- 删除指定记录 +DELETE FROM users WHERE id = 1; + +-- 删除多条记录 +DELETE FROM users WHERE status = 'deleted'; + +-- 删除所有记录(危险!) +DELETE FROM table_name; + +-- 使用子查询删除 +DELETE FROM accounts +WHERE user_id IN ( + SELECT id FROM users WHERE status = 'deleted' +); + +-- 删除时返回结果 +DELETE FROM users +WHERE id = 1 +RETURNING id, username; +``` + +### 5.5 连接查询(JOIN) + +```sql +-- 内连接 +SELECT u.username, a.name as account_name +FROM users u +INNER JOIN accounts a ON u.id = a.user_id; + +-- 左连接 +SELECT u.username, a.name as account_name +FROM users u +LEFT JOIN accounts a ON u.id = a.user_id; + +-- 右连接 +SELECT u.username, a.name as account_name +FROM users u +RIGHT JOIN accounts a ON u.id = a.user_id; + +-- 全外连接 +SELECT u.username, a.name as account_name +FROM users u +FULL OUTER JOIN accounts a ON u.id = a.user_id; + +-- 多表连接 +SELECT + u.username, + a.name as account_name, + p.symbol, + p.shares +FROM users u +INNER JOIN accounts a ON u.id = a.user_id +INNER JOIN positions p ON a.id = p.account_id +WHERE u.status = 'active'; +``` + +### 5.6 子查询 + +```sql +-- 标量子查询 +SELECT + username, + (SELECT COUNT(*) FROM accounts WHERE user_id = users.id) as account_count +FROM users; + +-- EXISTS 子查询 +SELECT * FROM users u +WHERE EXISTS ( + SELECT 1 FROM accounts a WHERE a.user_id = u.id +); + +-- IN 子查询 +SELECT * FROM users +WHERE id IN ( + SELECT DISTINCT user_id FROM accounts +); + +-- 相关子查询 +SELECT + u.*, + (SELECT COUNT(*) FROM accounts WHERE user_id = u.id) as account_count +FROM users u; +``` + +--- + +## 六、索引操作 + +### 6.1 索引的作用 + +- 提高查询速度 +- 加速排序和分组 +- 强制唯一性约束 +- 加速表连接 + +### 6.2 创建索引 + +#### 基本语法 + +```sql +-- 创建单列索引 +CREATE INDEX index_name ON table_name (column_name); + +-- 创建多列索引 +CREATE INDEX index_name ON table_name (column1, column2, ...); + +-- 创建唯一索引 +CREATE UNIQUE INDEX index_name ON table_name (column_name); + +-- 创建部分索引(带条件) +CREATE INDEX index_name ON table_name (column_name) +WHERE condition; + +-- 创建表达式索引 +CREATE INDEX index_name ON table_name (expression); +``` + +#### 示例 + +```sql +-- 基本索引 +CREATE INDEX idx_users_email ON users (email); + +-- 唯一索引 +CREATE UNIQUE INDEX idx_users_username ON users (username); + +-- 多列索引 +CREATE INDEX idx_transactions_user_date ON transactions (user_id, date); + +-- 降序索引 +CREATE INDEX idx_users_created_desc ON users (created_at DESC); + +-- 部分索引(只索引活跃用户) +CREATE INDEX idx_users_active_email ON users (email) +WHERE status = 'active'; + +-- 表达式索引 +CREATE INDEX idx_users_lower_email ON users (LOWER(email)); + +-- 文本搜索索引(GIN) +CREATE INDEX idx_transaction_thoughts_content +ON transaction_thoughts +USING gin(to_tsvector('jiebacfg', content)); +``` + +### 6.3 查看索引 + +```sql +-- 查看表的所有索引 +\d table_name + +-- 或者 +\di + +-- 查看索引详细信息 +SELECT + indexname, + indexdef +FROM pg_indexes +WHERE tablename = 'users'; + +-- 查看索引使用情况 +SELECT + schemaname, + tablename, + indexname, + idx_scan as index_scans, + idx_tup_read as tuples_read, + idx_tup_fetch as tuples_fetched +FROM pg_stat_user_indexes +WHERE tablename = 'users'; +``` + +### 6.4 删除索引 + +```sql +-- 删除索引 +DROP INDEX index_name; + +-- 删除索引(如果存在) +DROP INDEX IF EXISTS index_name; + +-- 级联删除 +DROP INDEX index_name CASCADE; +``` + +### 6.5 重建索引 + +```sql +-- 重建索引(释放空间,提高性能) +REINDEX INDEX index_name; + +-- 重建表的所有索引 +REINDEX TABLE table_name; + +-- 重建数据库的所有索引 +REINDEX DATABASE database_name; +``` + +### 6.6 索引类型 + +```sql +-- B-tree 索引(默认,最常用) +CREATE INDEX idx_name ON table_name (column_name); + +-- Hash 索引(只支持等值查询) +CREATE INDEX idx_name ON table_name USING HASH (column_name); + +-- GIN 索引(全文搜索、数组、JSONB) +CREATE INDEX idx_name ON table_name USING GIN (jsonb_column); + +-- GiST 索引(几何数据、全文搜索) +CREATE INDEX idx_name ON table_name USING GIST (geometry_column); + +-- BRIN 索引(大表,有序数据) +CREATE INDEX idx_name ON table_name USING BRIN (column_name); +``` + +### 6.7 索引维护 + +```sql +-- 分析表(更新统计信息,帮助查询优化器) +ANALYZE table_name; + +-- 分析所有表 +ANALYZE; + +-- 查看索引大小 +SELECT + pg_size_pretty(pg_relation_size('index_name')) as index_size; + +-- 查看表和索引总大小 +SELECT + pg_size_pretty(pg_total_relation_size('table_name')) as total_size; +``` + +--- + +## 七、高级查询 + +### 7.1 窗口函数 + +```sql +-- ROW_NUMBER:行号 +SELECT + id, + username, + ROW_NUMBER() OVER (ORDER BY created_at) as row_num +FROM users; + +-- RANK:排名(相同值相同排名,跳过后续排名) +SELECT + id, + username, + RANK() OVER (ORDER BY created_at DESC) as rank +FROM users; + +-- DENSE_RANK:密集排名(相同值相同排名,不跳过) +SELECT + id, + username, + DENSE_RANK() OVER (ORDER BY created_at DESC) as dense_rank +FROM users; + +-- 分组窗口函数 +SELECT + user_id, + date, + amount, + SUM(amount) OVER (PARTITION BY user_id ORDER BY date) as running_total +FROM transactions; +``` + +### 7.2 公共表表达式(CTE) + +```sql +-- 基本 CTE +WITH active_users AS ( + SELECT * FROM users WHERE status = 'active' +) +SELECT * FROM active_users; + +-- 递归 CTE +WITH RECURSIVE category_tree AS ( + -- 基础查询 + SELECT id, name, parent_id, 1 as level + FROM categories + WHERE parent_id IS NULL + + UNION ALL + + -- 递归查询 + SELECT c.id, c.name, c.parent_id, ct.level + 1 + FROM categories c + INNER JOIN category_tree ct ON c.parent_id = ct.id +) +SELECT * FROM category_tree; +``` + +### 7.3 集合操作 + +```sql +-- UNION:合并结果(去重) +SELECT column1 FROM table1 +UNION +SELECT column1 FROM table2; + +-- UNION ALL:合并结果(不去重) +SELECT column1 FROM table1 +UNION ALL +SELECT column1 FROM table2; + +-- INTERSECT:交集 +SELECT column1 FROM table1 +INTERSECT +SELECT column1 FROM table2; + +-- EXCEPT:差集 +SELECT column1 FROM table1 +EXCEPT +SELECT column1 FROM table2; +``` + +### 7.4 条件表达式 + +```sql +-- CASE 表达式 +SELECT + username, + CASE + WHEN status = 'active' THEN '活跃' + WHEN status = 'inactive' THEN '非活跃' + ELSE '未知' + END as status_text +FROM users; + +-- COALESCE:返回第一个非空值 +SELECT + username, + COALESCE(nickname, username) as display_name +FROM users; + +-- NULLIF:如果两个值相等返回 NULL +SELECT NULLIF(column1, column2) FROM table_name; + +-- GREATEST/LEAST:返回最大/最小值 +SELECT + GREATEST(price1, price2, price3) as max_price, + LEAST(price1, price2, price3) as min_price +FROM products; +``` + +--- + +## 八、事务管理 + +### 8.1 事务基本概念 + +事务的 ACID 特性: +- **原子性(Atomicity)**:事务中的所有操作要么全部成功,要么全部失败 +- **一致性(Consistency)**:事务执行前后数据库保持一致状态 +- **隔离性(Isolation)**:并发事务之间相互隔离 +- **持久性(Durability)**:事务提交后,数据永久保存 + +### 8.2 事务操作 + +```sql +-- 开始事务 +BEGIN; +-- 或者 +START TRANSACTION; + +-- 提交事务 +COMMIT; + +-- 回滚事务 +ROLLBACK; + +-- 保存点 +SAVEPOINT savepoint_name; + +-- 回滚到保存点 +ROLLBACK TO SAVEPOINT savepoint_name; + +-- 释放保存点 +RELEASE SAVEPOINT savepoint_name; +``` + +### 8.3 事务示例 + +```sql +-- 基本事务 +BEGIN; +INSERT INTO users (username, email, password_hash) +VALUES ('user1', 'user1@example.com', 'hash1'); +INSERT INTO accounts (user_id, name, type) +VALUES (currval('users_id_seq'), '主账户', 'mixed'); +COMMIT; + +-- 带错误处理的事务 +BEGIN; +INSERT INTO users (username, email, password_hash) +VALUES ('user2', 'user2@example.com', 'hash2'); +-- 如果出错,自动回滚 +SAVEPOINT before_account; +INSERT INTO accounts (user_id, name, type) +VALUES (currval('users_id_seq'), '主账户', 'mixed'); +-- 如果账户创建失败,回滚到保存点 +ROLLBACK TO SAVEPOINT before_account; +COMMIT; +``` + +### 8.4 事务隔离级别 + +```sql +-- 设置事务隔离级别 +SET TRANSACTION ISOLATION LEVEL READ COMMITTED; + +-- 查看当前隔离级别 +SHOW transaction_isolation; + +-- 隔离级别: +-- READ UNCOMMITTED(PostgreSQL 不支持) +-- READ COMMITTED(默认) +-- REPEATABLE READ +-- SERIALIZABLE +``` + +--- + +## 九、常用函数 + +### 9.1 字符串函数 + +```sql +-- 连接字符串 +SELECT CONCAT('Hello', ' ', 'World'); +SELECT 'Hello' || ' ' || 'World'; + +-- 长度 +SELECT LENGTH('Hello'); +SELECT CHAR_LENGTH('Hello'); + +-- 大小写转换 +SELECT UPPER('hello'); +SELECT LOWER('HELLO'); + +-- 截取 +SELECT SUBSTRING('Hello World', 1, 5); +SELECT LEFT('Hello World', 5); +SELECT RIGHT('Hello World', 5); + +-- 替换 +SELECT REPLACE('Hello World', 'World', 'PostgreSQL'); + +-- 去除空格 +SELECT TRIM(' Hello '); +SELECT LTRIM(' Hello'); +SELECT RTRIM('Hello '); + +-- 分割字符串 +SELECT SPLIT_PART('a,b,c', ',', 2); -- 返回 'b' +``` + +### 9.2 数值函数 + +```sql +-- 绝对值 +SELECT ABS(-10); + +-- 四舍五入 +SELECT ROUND(3.14159, 2); -- 3.14 + +-- 向上取整 +SELECT CEIL(3.14); -- 4 + +-- 向下取整 +SELECT FLOOR(3.14); -- 3 + +-- 幂运算 +SELECT POWER(2, 3); -- 8 + +-- 平方根 +SELECT SQRT(16); -- 4 + +-- 随机数 +SELECT RANDOM(); +SELECT FLOOR(RANDOM() * 100) + 1; -- 1-100 随机整数 +``` + +### 9.3 日期时间函数 + +```sql +-- 当前日期时间 +SELECT CURRENT_DATE; +SELECT CURRENT_TIME; +SELECT CURRENT_TIMESTAMP; +SELECT NOW(); + +-- 提取日期部分 +SELECT EXTRACT(YEAR FROM CURRENT_TIMESTAMP); +SELECT EXTRACT(MONTH FROM CURRENT_TIMESTAMP); +SELECT EXTRACT(DAY FROM CURRENT_TIMESTAMP); + +-- 日期加减 +SELECT CURRENT_DATE + INTERVAL '1 day'; +SELECT CURRENT_DATE + INTERVAL '1 month'; +SELECT CURRENT_DATE - INTERVAL '1 year'; + +-- 日期格式化 +SELECT TO_CHAR(CURRENT_TIMESTAMP, 'YYYY-MM-DD HH24:MI:SS'); +SELECT TO_CHAR(CURRENT_DATE, 'YYYY年MM月DD日'); + +-- 日期差 +SELECT CURRENT_DATE - '2024-01-01'::DATE; +SELECT AGE('2024-12-31'::DATE, '2024-01-01'::DATE); +``` + +### 9.4 聚合函数 + +```sql +-- 计数 +SELECT COUNT(*) FROM users; +SELECT COUNT(DISTINCT status) FROM users; + +-- 求和 +SELECT SUM(amount) FROM transactions; + +-- 平均值 +SELECT AVG(price) FROM positions; + +-- 最大值/最小值 +SELECT MAX(created_at) FROM users; +SELECT MIN(created_at) FROM users; + +-- 分组聚合 +SELECT + status, + COUNT(*) as count, + AVG(EXTRACT(YEAR FROM AGE(CURRENT_DATE, created_at))) as avg_age +FROM users +GROUP BY status; +``` + +### 9.5 JSON 函数 + +```sql +-- 创建 JSON +SELECT '{"name": "John", "age": 30}'::JSON; +SELECT JSON_BUILD_OBJECT('name', 'John', 'age', 30); + +-- 提取 JSON 值 +SELECT '{"name": "John"}'::JSON->>'name'; +SELECT '{"user": {"name": "John"}}'::JSON->'user'->>'name'; + +-- JSON 数组 +SELECT JSON_ARRAY_LENGTH('[1,2,3]'::JSON); + +-- JSONB 操作符 +SELECT '{"name": "John"}'::JSONB @> '{"name": "John"}'::JSONB; -- 包含 +SELECT '{"name": "John"}'::JSONB ? 'name'; -- 键存在 +``` + +--- + +## 十、实用技巧 + +### 10.1 性能优化 + +```sql +-- 使用 EXPLAIN 分析查询计划 +EXPLAIN SELECT * FROM users WHERE email = 'test@example.com'; + +-- 详细分析(包含实际执行时间) +EXPLAIN ANALYZE SELECT * FROM users WHERE email = 'test@example.com'; + +-- 查看慢查询 +SELECT + query, + calls, + total_time, + mean_time +FROM pg_stat_statements +ORDER BY mean_time DESC +LIMIT 10; +``` + +### 10.2 数据导入导出 + +```sql +-- 导出为 CSV +COPY users TO '/tmp/users.csv' WITH CSV HEADER; + +-- 从 CSV 导入 +COPY users FROM '/tmp/users.csv' WITH CSV HEADER; + +-- 导出为文本 +COPY users TO '/tmp/users.txt'; + +-- 从文本导入 +COPY users FROM '/tmp/users.txt'; +``` + +### 10.3 查看数据库信息 + +```sql +-- 查看数据库大小 +SELECT pg_size_pretty(pg_database_size('database_name')); + +-- 查看表大小 +SELECT pg_size_pretty(pg_total_relation_size('table_name')); + +-- 查看所有表大小 +SELECT + schemaname, + tablename, + pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) AS size +FROM pg_tables +WHERE schemaname = 'public' +ORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC; + +-- 查看连接数 +SELECT count(*) FROM pg_stat_activity; + +-- 查看当前连接 +SELECT * FROM pg_stat_activity; +``` + +### 10.4 权限管理 + +```sql +-- 授予权限 +GRANT SELECT, INSERT, UPDATE ON table_name TO username; +GRANT ALL PRIVILEGES ON DATABASE database_name TO username; + +-- 撤销权限 +REVOKE SELECT ON table_name FROM username; + +-- 查看权限 +\dp table_name +``` + +### 10.5 序列操作 + +```sql +-- 查看当前序列值 +SELECT currval('users_id_seq'); + +-- 查看下一个序列值 +SELECT nextval('users_id_seq'); + +-- 设置序列值 +SELECT setval('users_id_seq', 100); + +-- 重置序列 +ALTER SEQUENCE users_id_seq RESTART WITH 1; +``` + +### 10.6 常用配置 + +```sql +-- 查看配置 +SHOW ALL; +SHOW shared_buffers; +SHOW max_connections; + +-- 设置配置(会话级别) +SET work_mem = '256MB'; + +-- 查看时区 +SHOW timezone; +SET timezone = 'Asia/Shanghai'; +``` + +--- + +## 十一、常见问题 + +### 11.1 连接问题 + +```bash +# 问题:无法连接到数据库 +# 解决:检查 PostgreSQL 服务是否运行 +sudo systemctl status postgresql # Linux +brew services list # macOS + +# 问题:认证失败 +# 解决:检查 pg_hba.conf 配置文件 +sudo nano /etc/postgresql/15/main/pg_hba.conf +``` + +### 11.2 性能问题 + +```sql +-- 问题:查询慢 +-- 解决:创建索引 +CREATE INDEX idx_column ON table_name (column_name); + +-- 问题:表膨胀 +-- 解决:VACUUM +VACUUM ANALYZE table_name; + +-- 问题:统计信息过期 +-- 解决:更新统计信息 +ANALYZE table_name; +``` + +### 11.3 数据备份恢复 + +```bash +# 定期备份脚本 +#!/bin/bash +DATE=$(date +%Y%m%d_%H%M%S) +pg_dump -U postgres database_name > /backup/db_$DATE.sql + +# 自动清理旧备份(保留30天) +find /backup -name "db_*.sql" -mtime +30 -delete +``` + +--- + +## 十二、参考资源 + +- **官方文档**:https://www.postgresql.org/docs/ +- **中文文档**:https://www.postgresql.org/docs/current/index.html +- **psql 命令参考**:https://www.postgresql.org/docs/current/app-psql.html +- **SQL 语法参考**:https://www.postgresql.org/docs/current/sql.html + +--- + +**文档版本**:v1.0 +**创建日期**:2024年 +**适用版本**:PostgreSQL 12+ + diff --git a/机生文档/产品需求文档-去除空格.md b/机生文档/产品需求文档-去除空格.md new file mode 100644 index 0000000..efb6b05 --- /dev/null +++ b/机生文档/产品需求文档-去除空格.md @@ -0,0 +1,409 @@ +## 1. 产品概述 +### 1.1 产品定位 +**产品名称**:思投录 (VestMind) +**产品定位**:投资决策与复盘工具 +**产品愿景**:让每笔投资都经得起思考 +**目标用户**:个人投资者、价值投资者、投资新手 + +### 1.2 产品价值 +- 帮助用户建立系统化的投资决策流程 +- 通过复盘机制提升投资决策质量 +- 提供专业的投资工具和检查清单 +- 培养理性投资思维,避免冲动交易 + +### 1.3 设计原则 +- **紫色主题**:营造安静思考的投资氛围 +- **移动端优先**:适配APP和小程序 +- **简洁易用**:降低使用门槛 +- **数据驱动**:基于数据做决策 + +## 2. 功能架构 +### 2.1 核心功能模块 +``` +思投录 +├── 持仓管理 +├── 交易计划 +├── 交易记录/复盘 +└── 我的工具 +``` + +### 2.2 功能优先级 +- **P0**:持仓管理、交易记录 +- **P1**:交易计划、基础工具 +- **P2**:高级工具、分享功能 + +## 3. 页面详细设计 +### 3.1 首页/持仓页面 +#### 3.1.1 页面概述 +用户进入应用后的主页面,展示整体投资概况和持仓详情。 +#### 3.1.2 页面布局 +``` +┌─────────────────────────┐ +│ 思投录 (顶部导航) │ +│ 让每笔投资都经得起思考 │ +├─────────────────────────┤ +│ 持仓概览卡片 │ +│ ┌─────────────────────┐ │ +│ │ 总资产: ¥128,450.00 │ │ +│ │ 今日收益: +¥1,250 │ │ +│ │ 总收益率: +12.5% │ │ +│ └─────────────────────┘ │ +├─────────────────────────┤ +│ 持仓列表 │ +│ ┌─────────────────────┐ │ +│ │ 贵州茅台 600519 │ │ +│ │ 100股 ¥1,850.00 │ │ +│ │ +¥2,500.00 (+15.6%) │ │ +│ └─────────────────────┘ │ +│ ┌─────────────────────┐ │ +│ │ 腾讯控股 00700 │ │ +│ │ 200股 ¥320.00 │ │ +│ │ -¥800.00 (-1.2%) │ │ +│ └─────────────────────┘ │ +├─────────────────────────┤ +│ 底部导航栏 │ +│ [持仓] [计划] [记录] [我的] │ +└─────────────────────────┘ +``` + +#### 3.1.3 功能需求 +**持仓概览卡片** +- 显示总资产金额 +- 显示今日收益(正负用颜色区分) +- 显示总收益率 +- 支持点击查看详细统计 + +**持仓列表** +- 显示股票名称、代码 +- 显示持股数量、当前价格 +- 显示盈亏金额和收益率 +- 支持点击查看单只股票详情 +- 支持添加新持仓 + +**添加持仓功能** +- 股票名称输入 +- 股票代码输入 +- 持股数量输入 +- 成本价格输入 +- 仓位上限设置(预警功能) + +#### 3.1.4 交互需求 +- 下拉刷新更新数据 +- 长按持仓项显示操作菜单 +- 点击"+"按钮添加新持仓 +- 滑动删除持仓(需确认) + +### 3.2 交易计划页面 +#### 3.2.1 页面概述 +帮助用户制定和执行交易计划,实现"计划你的交易,交易你的计划"。 + +#### 3.2.2 页面布局 +``` +┌─────────────────────────┐ +│ 交易计划 (顶部导航) │ +│ [+ 新建] │ +├─────────────────────────┤ +│ 计划列表 │ +│ ┌─────────────────────┐ │ +│ │ 招商银行 600036 │ │ +│ │ 状态: 进行中 │ │ +│ │ 目标价格: ¥45.00 │ │ +│ │ 计划金额: ¥10,000 │ │ +│ │ 截止时间: 2024-03-15│ │ +│ │ ████████░░ 60% │ │ +│ └─────────────────────┘ │ +│ ┌─────────────────────┐ │ +│ │ 中国平安 601318 │ │ +│ │ 状态: 已完成 │ │ +│ │ 目标价格: ¥55.00 │ │ +│ │ 计划金额: ¥15,000 │ │ +│ │ 截止时间: 2024-04-20│ │ +│ │ ██████████ 100% │ │ +│ └─────────────────────┘ │ +├─────────────────────────┤ +│ 底部导航栏 │ +│ [持仓] [计划] [记录] [我的] │ +└─────────────────────────┘ +``` + +#### 3.2.3 功能需求 +**计划列表** +- 显示计划状态(进行中/已完成/已取消) +- 显示股票信息、目标价格 +- 显示计划金额、截止时间 +- 显示完成进度条 +- 支持点击查看计划详情 + +**新建计划功能** +- 股票选择(名称、代码) +- 市场选择(A股/港股/美股) +- 目标价格设置 +- 截止时间设置 +- 投资金额或股份数选择 +- 分步买入设置(默认3步) +- 每步买入价格设置 + +**计划执行** +- 到达目标价格提醒 +- 支持手动标记完成 +- 支持从计划跳转到交易记录 +- 支持修改计划参数 + +#### 3.2.4 交互需求 +- 点击计划项查看详情 +- 长按显示操作菜单(编辑/删除/完成) +- 滑动标记为完成 +- 支持计划搜索和筛选 + +### 3.3 交易记录/复盘页面 +#### 3.3.1 页面概述 +记录每笔交易的详细信息,通过时间线展示交易历史和思考过程。 +#### 3.3.2 页面布局 +``` +┌─────────────────────────┐ +│ 交易记录 (顶部导航) │ +│ [+ 记录] │ +├─────────────────────────┤ +│ 时间线 │ +│ ┌─────────────────────┐ │ +│ │ 2024-01-15 │ │ +│ │ ┌─────────────────┐ │ │ +│ │ │ 贵州茅台 600519 │ │ │ +│ │ │ 买入 100股 │ │ │ +│ │ │ 价格: ¥1,600.00 │ │ │ +│ │ │ ┌─────────────┐ │ │ │ +│ │ │ │ 交易思考: │ │ │ │ +│ │ │ │ 基于茅台品牌 │ │ │ │ +│ │ │ │ 价值和长期增 │ │ │ │ +│ │ │ │ 长潜力... │ │ │ │ +│ │ │ └─────────────┘ │ │ │ +│ │ └─────────────────┘ │ │ +│ └─────────────────────┘ │ +│ ┌─────────────────────┐ │ +│ │ 2024-01-10 │ │ +│ │ ┌─────────────────┐ │ │ +│ │ │ 比亚迪 002594 │ │ │ +│ │ │ 卖出 200股 │ │ │ +│ │ │ 价格: ¥280.00 │ │ │ +│ │ │ ┌─────────────┐ │ │ │ +│ │ │ │ 交易思考: │ │ │ │ +│ │ │ │ 新能源汽车行 │ │ │ │ +│ │ │ │ 业竞争加剧...│ │ │ │ +│ │ │ └─────────────┘ │ │ │ +│ │ └─────────────────┘ │ │ +│ └─────────────────────┘ │ +├─────────────────────────┤ +│ 底部导航栏 │ +│ [持仓] [计划] [记录] [我的] │ +└─────────────────────────┘ +``` +#### 3.3.3 功能需求 +**时间线展示** +- 按时间倒序显示交易记录 +- 显示交易日期 +- 显示交易类型(买入/卖出) +- 显示股票信息、数量、价格 +- 显示交易思考内容 + +**记录交易功能** +- 交易类型选择(买入/卖出) +- 股票信息输入 +- 交易数量输入 +- 交易价格输入 +- 交易思考记录(必填) +- 支持从计划跳转记录 + +**复盘功能** +- 定期弹出复盘提醒 +- 支持为历史交易添加复盘 +- 复盘内容记录 +- 复盘时间记录 + +**分享功能**(可选) +- 支持分享单笔交易 +- 支持分享交易时间线 +- 隐私设置控制 + +#### 3.3.4 交互需求 +- 点击交易记录查看详情 +- 长按显示操作菜单(编辑/删除/复盘) +- 支持交易记录搜索 +- 支持按股票筛选 +- 支持按时间范围筛选 + +### 3.4 我的工具页面 +#### 3.4.1 页面概述 +提供各种投资工具和计算器,帮助用户做出更好的投资决策。 +#### 3.4.2 页面布局 +``` +┌─────────────────────────┐ +│ 我的工具 (顶部导航) │ +├─────────────────────────┤ +│ 工具网格 │ +│ ┌─────────┐ ┌─────────┐ │ +│ │ ✅ │ │ 🧮 │ │ +│ │投资检查清单│ │复利计算器│ │ +│ │买入卖出检查│ │计算未来收益│ │ +│ └─────────┘ └─────────┘ │ +│ ┌─────────┐ ┌─────────┐ │ +│ │ 📈 │ │ 🎯 │ │ +│ │ 估值工具 │ │ 自由目标 │ │ +│ │企业价值评估│ │财务自由规划│ │ +│ └─────────┘ └─────────┘ │ +├─────────────────────────┤ +│ 用户信息 │ +│ ┌─────────────────────┐ │ +│ │ 头像 | 用户名 │ │ +│ │ 投资天数: 365天 │ │ +│ │ 总交易次数: 25次 │ │ +│ └─────────────────────┘ │ +├─────────────────────────┤ +│ 底部导航栏 │ +│ [持仓] [计划] [记录] [我的] │ +└─────────────────────────┘ +``` + +#### 3.4.3 功能需求 +**投资检查清单** +- 买入检查清单 +- 企业基本面是否优秀? +- 估值是否合理? +- 行业前景如何? +- 管理层是否可信? +- 现金流是否健康? +- 卖出检查清单 +- 基本面是否恶化? +- 估值是否过高? +- 是否有更好的投资机会? +- 是否需要资金配置? +- 支持在创建交易计划时自动弹出 + +**复利计算器** +- 初始金额输入 +- 每年投入金额输入 +- 年复合增长率设置 +- 投资年限设置 +- 计算总投入、最终金额、总收益 +- 生成收益曲线图 + +**估值工具** +- 老唐估值法 +- 净利润输入 +- 无风险收益率设置 +- 合理PE倍数计算 +- 估值结果输出 +- 现金流折现法 +- 自由现金流输入 +- 增长率设置 +- 折现率设置 +- 估值结果输出 + +**自由目标** +- 目标资产设置 +- 当前资产输入 +- 年复合增长率设置 +- 每年投入金额设置 +- 计算达成时间 +- 与持仓数据联动 + +#### 3.4.4 交互需求 +- 点击工具卡片打开对应工具 +- 工具界面支持数据输入和计算 +- 支持结果保存和分享 +- 支持历史记录查看 + +## 4. 技术需求 +### 4.1 平台支持 +- **移动端APP**:iOS、Android +- **小程序**:微信小程序 +- **响应式设计**:适配不同屏幕尺寸 + +### 4.2 数据存储 +- 本地存储:用户数据、设置 +- 云端同步:多设备数据同步 +- 数据备份:定期备份重要数据 + +### 4.3 性能要求 +- 页面加载时间 < 2秒 +- 操作响应时间 < 500ms +- 支持离线使用基础功能 + +## 5. 用户体验需求 +### 5.1 易用性 +- 界面简洁直观 +- 操作流程简单 +- 新手引导完善 +- 错误提示友好 + +### 5.2 可访问性 +- 支持字体大小调节 +- 支持颜色对比度调节 +- 支持语音输入 +- 支持键盘导航 + +### 5.3 个性化 +- 主题颜色自定义 +- 功能模块自定义 +- 提醒设置个性化 +- 数据展示个性化 + +## 6. 安全需求 +### 6.1 数据安全 +- 本地数据加密存储 +- 网络传输加密 +- 用户隐私保护 +- 数据访问权限控制 + +### 6.2 功能安全 +- 重要操作二次确认 +- 数据删除保护 +- 异常操作监控 +- 安全日志记录 + +## 7. 运营需求 +### 7.1 数据统计 +- 用户行为分析 +- 功能使用统计 +- 性能监控 +- 错误日志收集 + +### 7.2 用户反馈 +- 意见反馈入口 +- 问题报告功能 +- 用户满意度调查 +- 功能建议收集 + +## 8. 开发计划 +### 8.1 版本规划 +- **V1.0**:基础功能(持仓、记录) +- **V1.1**:交易计划功能 +- **V1.2**:工具集成 +- **V2.0**:高级功能和优化 + +### 8.2 里程碑 +- 需求确认:1周 +- UI设计:2周 +- 开发实现:8周 +- 测试优化:2周 +- 上线发布:1周 + +## 9. 成功指标 +### 9.1 用户指标 +- 日活跃用户数 +- 用户留存率 +- 功能使用率 +- 用户满意度 + +### 9.2 产品指标 +- 交易记录完成率 +- 计划执行率 +- 工具使用频率 +- 数据准确性 + +--- + +*本文档版本:V1.0* + +*最后更新:2024年1月* + +*文档状态:待评审* \ No newline at end of file diff --git a/产品需求文档.md b/机生文档/产品需求文档.md similarity index 58% rename from 产品需求文档.md rename to 机生文档/产品需求文档.md index 63ef2a6..efb6b05 100644 --- a/产品需求文档.md +++ b/机生文档/产品需求文档.md @@ -1,11 +1,8 @@ -# 思投录 (VestMind) 产品需求文档 - ## 1. 产品概述 - ### 1.1 产品定位 -**产品名称**:思投录 (VestMind) -**产品定位**:投资决策与复盘工具 -**产品愿景**:让每笔投资都经得起思考 +**产品名称**:思投录 (VestMind) +**产品定位**:投资决策与复盘工具 +**产品愿景**:让每笔投资都经得起思考 **目标用户**:个人投资者、价值投资者、投资新手 ### 1.2 产品价值 @@ -21,7 +18,6 @@ - **数据驱动**:基于数据做决策 ## 2. 功能架构 - ### 2.1 核心功能模块 ``` 思投录 @@ -37,44 +33,40 @@ - **P2**:高级工具、分享功能 ## 3. 页面详细设计 - ### 3.1 首页/持仓页面 - #### 3.1.1 页面概述 用户进入应用后的主页面,展示整体投资概况和持仓详情。 - #### 3.1.2 页面布局 ``` ┌─────────────────────────┐ -│ 思投录 (顶部导航) │ -│ 让每笔投资都经得起思考 │ +│ 思投录 (顶部导航) │ +│ 让每笔投资都经得起思考 │ ├─────────────────────────┤ -│ 持仓概览卡片 │ -│ ┌─────────────────────┐ │ -│ │ 总资产: ¥128,450.00 │ │ -│ │ 今日收益: +¥1,250 │ │ -│ │ 总收益率: +12.5% │ │ -│ └─────────────────────┘ │ +│ 持仓概览卡片 │ +│ ┌─────────────────────┐ │ +│ │ 总资产: ¥128,450.00 │ │ +│ │ 今日收益: +¥1,250 │ │ +│ │ 总收益率: +12.5% │ │ +│ └─────────────────────┘ │ ├─────────────────────────┤ -│ 持仓列表 │ -│ ┌─────────────────────┐ │ -│ │ 贵州茅台 600519 │ │ -│ │ 100股 ¥1,850.00 │ │ -│ │ +¥2,500.00 (+15.6%) │ │ -│ └─────────────────────┘ │ -│ ┌─────────────────────┐ │ -│ │ 腾讯控股 00700 │ │ -│ │ 200股 ¥320.00 │ │ -│ │ -¥800.00 (-1.2%) │ │ -│ └─────────────────────┘ │ +│ 持仓列表 │ +│ ┌─────────────────────┐ │ +│ │ 贵州茅台 600519 │ │ +│ │ 100股 ¥1,850.00 │ │ +│ │ +¥2,500.00 (+15.6%) │ │ +│ └─────────────────────┘ │ +│ ┌─────────────────────┐ │ +│ │ 腾讯控股 00700 │ │ +│ │ 200股 ¥320.00 │ │ +│ │ -¥800.00 (-1.2%) │ │ +│ └─────────────────────┘ │ ├─────────────────────────┤ -│ 底部导航栏 │ +│ 底部导航栏 │ │ [持仓] [计划] [记录] [我的] │ └─────────────────────────┘ ``` #### 3.1.3 功能需求 - **持仓概览卡片** - 显示总资产金额 - 显示今日收益(正负用颜色区分) @@ -102,48 +94,46 @@ - 滑动删除持仓(需确认) ### 3.2 交易计划页面 - #### 3.2.1 页面概述 帮助用户制定和执行交易计划,实现"计划你的交易,交易你的计划"。 #### 3.2.2 页面布局 ``` ┌─────────────────────────┐ -│ 交易计划 (顶部导航) │ -│ [+ 新建] │ +│ 交易计划 (顶部导航) │ +│ [+ 新建] │ ├─────────────────────────┤ -│ 计划列表 │ -│ ┌─────────────────────┐ │ -│ │ 招商银行 600036 │ │ -│ │ 状态: 进行中 │ │ -│ │ 目标价格: ¥45.00 │ │ -│ │ 计划金额: ¥10,000 │ │ -│ │ 截止时间: 2024-03-15│ │ -│ │ ████████░░ 60% │ │ -│ └─────────────────────┘ │ -│ ┌─────────────────────┐ │ -│ │ 中国平安 601318 │ │ -│ │ 状态: 已完成 │ │ -│ │ 目标价格: ¥55.00 │ │ -│ │ 计划金额: ¥15,000 │ │ -│ │ 截止时间: 2024-04-20│ │ -│ │ ██████████ 100% │ │ -│ └─────────────────────┘ │ +│ 计划列表 │ +│ ┌─────────────────────┐ │ +│ │ 招商银行 600036 │ │ +│ │ 状态: 进行中 │ │ +│ │ 目标价格: ¥45.00 │ │ +│ │ 计划金额: ¥10,000 │ │ +│ │ 截止时间: 2024-03-15│ │ +│ │ ████████░░ 60% │ │ +│ └─────────────────────┘ │ +│ ┌─────────────────────┐ │ +│ │ 中国平安 601318 │ │ +│ │ 状态: 已完成 │ │ +│ │ 目标价格: ¥55.00 │ │ +│ │ 计划金额: ¥15,000 │ │ +│ │ 截止时间: 2024-04-20│ │ +│ │ ██████████ 100% │ │ +│ └─────────────────────┘ │ ├─────────────────────────┤ -│ 底部导航栏 │ +│ 底部导航栏 │ │ [持仓] [计划] [记录] [我的] │ └─────────────────────────┘ ``` #### 3.2.3 功能需求 - **计划列表** - 显示计划状态(进行中/已完成/已取消) - 显示股票信息、目标价格 - 显示计划金额、截止时间 - 显示完成进度条 - 支持点击查看计划详情 - + **新建计划功能** - 股票选择(名称、代码) - 市场选择(A股/港股/美股) @@ -157,7 +147,7 @@ - 到达目标价格提醒 - 支持手动标记完成 - 支持从计划跳转到交易记录 -- 支持修改计划参数 +- 支持修改计划参数 #### 3.2.4 交互需求 - 点击计划项查看详情 @@ -166,52 +156,48 @@ - 支持计划搜索和筛选 ### 3.3 交易记录/复盘页面 - #### 3.3.1 页面概述 记录每笔交易的详细信息,通过时间线展示交易历史和思考过程。 - #### 3.3.2 页面布局 ``` ┌─────────────────────────┐ -│ 交易记录 (顶部导航) │ -│ [+ 记录] │ +│ 交易记录 (顶部导航) │ +│ [+ 记录] │ ├─────────────────────────┤ -│ 时间线 │ -│ ┌─────────────────────┐ │ -│ │ 2024-01-15 │ │ -│ │ ┌─────────────────┐ │ │ -│ │ │ 贵州茅台 600519 │ │ │ -│ │ │ 买入 100股 │ │ │ -│ │ │ 价格: ¥1,600.00 │ │ │ -│ │ │ ┌─────────────┐ │ │ │ -│ │ │ │ 交易思考: │ │ │ │ -│ │ │ │ 基于茅台品牌 │ │ │ │ -│ │ │ │ 价值和长期增 │ │ │ │ -│ │ │ │ 长潜力... │ │ │ │ -│ │ │ └─────────────┘ │ │ │ -│ │ └─────────────────┘ │ │ -│ └─────────────────────┘ │ -│ ┌─────────────────────┐ │ -│ │ 2024-01-10 │ │ -│ │ ┌─────────────────┐ │ │ -│ │ │ 比亚迪 002594 │ │ │ -│ │ │ 卖出 200股 │ │ │ -│ │ │ 价格: ¥280.00 │ │ │ -│ │ │ ┌─────────────┐ │ │ │ -│ │ │ │ 交易思考: │ │ │ │ -│ │ │ │ 新能源汽车行 │ │ │ │ -│ │ │ │ 业竞争加剧...│ │ │ │ -│ │ │ └─────────────┘ │ │ │ -│ │ └─────────────────┘ │ │ -│ └─────────────────────┘ │ +│ 时间线 │ +│ ┌─────────────────────┐ │ +│ │ 2024-01-15 │ │ +│ │ ┌─────────────────┐ │ │ +│ │ │ 贵州茅台 600519 │ │ │ +│ │ │ 买入 100股 │ │ │ +│ │ │ 价格: ¥1,600.00 │ │ │ +│ │ │ ┌─────────────┐ │ │ │ +│ │ │ │ 交易思考: │ │ │ │ +│ │ │ │ 基于茅台品牌 │ │ │ │ +│ │ │ │ 价值和长期增 │ │ │ │ +│ │ │ │ 长潜力... │ │ │ │ +│ │ │ └─────────────┘ │ │ │ +│ │ └─────────────────┘ │ │ +│ └─────────────────────┘ │ +│ ┌─────────────────────┐ │ +│ │ 2024-01-10 │ │ +│ │ ┌─────────────────┐ │ │ +│ │ │ 比亚迪 002594 │ │ │ +│ │ │ 卖出 200股 │ │ │ +│ │ │ 价格: ¥280.00 │ │ │ +│ │ │ ┌─────────────┐ │ │ │ +│ │ │ │ 交易思考: │ │ │ │ +│ │ │ │ 新能源汽车行 │ │ │ │ +│ │ │ │ 业竞争加剧...│ │ │ │ +│ │ │ └─────────────┘ │ │ │ +│ │ └─────────────────┘ │ │ +│ └─────────────────────┘ │ ├─────────────────────────┤ -│ 底部导航栏 │ +│ 底部导航栏 │ │ [持仓] [计划] [记录] [我的] │ └─────────────────────────┘ ``` - #### 3.3.3 功能需求 - **时间线展示** - 按时间倒序显示交易记录 - 显示交易日期 @@ -226,18 +212,18 @@ - 交易价格输入 - 交易思考记录(必填) - 支持从计划跳转记录 - + **复盘功能** - 定期弹出复盘提醒 - 支持为历史交易添加复盘 - 复盘内容记录 - 复盘时间记录 - + **分享功能**(可选) - 支持分享单笔交易 - 支持分享交易时间线 - 隐私设置控制 - + #### 3.3.4 交互需求 - 点击交易记录查看详情 - 长按显示操作菜单(编辑/删除/复盘) @@ -246,53 +232,50 @@ - 支持按时间范围筛选 ### 3.4 我的工具页面 - #### 3.4.1 页面概述 提供各种投资工具和计算器,帮助用户做出更好的投资决策。 - #### 3.4.2 页面布局 ``` ┌─────────────────────────┐ -│ 我的工具 (顶部导航) │ +│ 我的工具 (顶部导航) │ ├─────────────────────────┤ -│ 工具网格 │ -│ ┌─────────┐ ┌─────────┐ │ -│ │ ✅ │ │ 🧮 │ │ -│ │投资检查清单│ │复利计算器│ │ -│ │买入卖出检查│ │计算未来收益│ │ -│ └─────────┘ └─────────┘ │ -│ ┌─────────┐ ┌─────────┐ │ -│ │ 📈 │ │ 🎯 │ │ -│ │ 估值工具 │ │ 自由目标 │ │ -│ │企业价值评估│ │财务自由规划│ │ -│ └─────────┘ └─────────┘ │ +│ 工具网格 │ +│ ┌─────────┐ ┌─────────┐ │ +│ │ ✅ │ │ 🧮 │ │ +│ │投资检查清单│ │复利计算器│ │ +│ │买入卖出检查│ │计算未来收益│ │ +│ └─────────┘ └─────────┘ │ +│ ┌─────────┐ ┌─────────┐ │ +│ │ 📈 │ │ 🎯 │ │ +│ │ 估值工具 │ │ 自由目标 │ │ +│ │企业价值评估│ │财务自由规划│ │ +│ └─────────┘ └─────────┘ │ ├─────────────────────────┤ -│ 用户信息 │ -│ ┌─────────────────────┐ │ -│ │ 头像 | 用户名 │ │ -│ │ 投资天数: 365天 │ │ -│ │ 总交易次数: 25次 │ │ -│ └─────────────────────┘ │ +│ 用户信息 │ +│ ┌─────────────────────┐ │ +│ │ 头像 | 用户名 │ │ +│ │ 投资天数: 365天 │ │ +│ │ 总交易次数: 25次 │ │ +│ └─────────────────────┘ │ ├─────────────────────────┤ -│ 底部导航栏 │ +│ 底部导航栏 │ │ [持仓] [计划] [记录] [我的] │ └─────────────────────────┘ ``` - + #### 3.4.3 功能需求 - **投资检查清单** - 买入检查清单 - - 企业基本面是否优秀? - - 估值是否合理? - - 行业前景如何? - - 管理层是否可信? - - 现金流是否健康? +- 企业基本面是否优秀? +- 估值是否合理? +- 行业前景如何? +- 管理层是否可信? +- 现金流是否健康? - 卖出检查清单 - - 基本面是否恶化? - - 估值是否过高? - - 是否有更好的投资机会? - - 是否需要资金配置? +- 基本面是否恶化? +- 估值是否过高? +- 是否有更好的投资机会? +- 是否需要资金配置? - 支持在创建交易计划时自动弹出 **复利计算器** @@ -305,15 +288,15 @@ **估值工具** - 老唐估值法 - - 净利润输入 - - 无风险收益率设置 - - 合理PE倍数计算 - - 估值结果输出 +- 净利润输入 +- 无风险收益率设置 +- 合理PE倍数计算 +- 估值结果输出 - 现金流折现法 - - 自由现金流输入 - - 增长率设置 - - 折现率设置 - - 估值结果输出 +- 自由现金流输入 +- 增长率设置 +- 折现率设置 +- 估值结果输出 **自由目标** - 目标资产设置 @@ -330,7 +313,6 @@ - 支持历史记录查看 ## 4. 技术需求 - ### 4.1 平台支持 - **移动端APP**:iOS、Android - **小程序**:微信小程序 @@ -347,7 +329,6 @@ - 支持离线使用基础功能 ## 5. 用户体验需求 - ### 5.1 易用性 - 界面简洁直观 - 操作流程简单 @@ -359,7 +340,7 @@ - 支持颜色对比度调节 - 支持语音输入 - 支持键盘导航 - + ### 5.3 个性化 - 主题颜色自定义 - 功能模块自定义 @@ -367,7 +348,6 @@ - 数据展示个性化 ## 6. 安全需求 - ### 6.1 数据安全 - 本地数据加密存储 - 网络传输加密 @@ -381,7 +361,6 @@ - 安全日志记录 ## 7. 运营需求 - ### 7.1 数据统计 - 用户行为分析 - 功能使用统计 @@ -395,7 +374,6 @@ - 功能建议收集 ## 8. 开发计划 - ### 8.1 版本规划 - **V1.0**:基础功能(持仓、记录) - **V1.1**:交易计划功能 @@ -408,9 +386,8 @@ - 开发实现:8周 - 测试优化:2周 - 上线发布:1周 - + ## 9. 成功指标 - ### 9.1 用户指标 - 日活跃用户数 - 用户留存率 @@ -425,6 +402,8 @@ --- -*本文档版本:V1.0* -*最后更新:2024年1月* -*文档状态:待评审* +*本文档版本:V1.0* + +*最后更新:2024年1月* + +*文档状态:待评审* \ No newline at end of file diff --git a/机生文档/命名问题.md b/机生文档/命名问题.md new file mode 100644 index 0000000..627b169 --- /dev/null +++ b/机生文档/命名问题.md @@ -0,0 +1,11 @@ +# 应用如何命名 +## 参考命名 +- 投资复盘笔记 + + +## 审核是描述 +这只是一个个人投资交易的记录与复盘工具 + +考虑更稳妥的命名:如果希望最大化降低审核风险,可以考虑对名称进行微调,使其更偏向“个人工具”属性,同时保留核心含义。例如: +- 增加个人化前缀/后缀:比如“我的复盘笔记”、“知行投资笔记”。 +- 使用更中性的词汇:例如“资产轨迹日记”、“收益账本与思考”。 \ No newline at end of file diff --git a/机生文档/投资收益记录系统设计.md b/机生文档/投资收益记录系统设计.md new file mode 100644 index 0000000..678a963 --- /dev/null +++ b/机生文档/投资收益记录系统设计.md @@ -0,0 +1,732 @@ +# 投资收益记录系统设计文档 + +## 一、需求概述 + +### 1.1 核心目标 +开发一个自动化的投资收益记录应用,**用户只需直接修改持仓的成本价和份额**,系统自动更新市场价格、计算收益和收益率,无需用户定期手动更新资产金额。 + +**设计原则:** +- **被动变更(分红、拆股、送股)**:系统自动完成 +- **主动变更(买入、卖出、追加)**:用户直接修改成本价和份数,无需记录每次交易细节 +- **适用场景**:多券商用户汇总统计,不是替代券商系统 + +### 1.2 支持的资产类型 +- **股票**:支持A股、港股、美股等 +- **基金**:支持各类基金产品 +- **现金**:证券账户留存现金、分红现金等 + +### 1.3 核心功能 +- 自动更新市场价格(每日收盘后) +- 自动计算总资产、总收益 +- 自动计算累计收益率、年化收益率、当年收益率 +- 支持多种交易场景处理 +- 使用基金净值法和资金加权法计算收益率(取两者低者) + +--- + +## 二、设计分析与优化 + +### 2.1 用户提出的设计分析 + +#### ✅ 正确的设计点 + +1. **拆股处理** + - 自动变更股份数和成本价 + - **实现方式**:股份数 = 原股份数 × 拆股比例,成本价 = 原成本价 ÷ 拆股比例 + - **示例**:10股拆成20股(1:2),成本价从100元变为50元 + +2. **分红处理** + - 股票成本价减去每股分红得到最新成本价 + - **实现方式**:新成本价 = 原成本价 - 每股分红金额 + - **注意**:分红需要同时记录到现金账户 + +3. **买入/卖出处理** + - 变更持仓份额和成本价 + - **实现方式**: + - **买入**:新成本价 = (原成本价 × 原份额 + 买入价 × 买入份额) / (原份额 + 买入份额) + - **卖出**:只减少份额,成本价不变(先进先出或加权平均) + +4. **提现处理** + - 变更现金账户金额 + - **实现方式**:现金余额 = 原余额 - 提现金额 + +#### ⚠️ 需要补充和优化的点 + +1. **分红处理需要完善** + - 需要区分现金分红和股票分红(送股) + - 现金分红:成本价降低,现金账户增加 + - 股票分红(送股):股份数增加,成本价降低 + - **公式**:新成本价 = (原成本价 × 原股份数) / (原股份数 + 送股数) + +2. **买入/卖出成本价计算** + - 需要考虑交易费用(佣金、印花税等) + - **买入成本价** = (买入价 × 买入份额 + 交易费用) / 买入份额 + - **卖出**:需要计算已实现收益,并更新持仓成本价 + +3. **基金的特殊处理** + - 基金分红通常有现金分红和红利再投资两种方式 + - 需要支持基金份额的自动调整 + +--- + +## 三、遗漏场景分析 + +### 3.1 必须处理的场景 + +1. **送股(股票分红)** + - 场景:公司送股,如10送5 + - 处理:股份数增加,成本价降低 + - 公式:新股份数 = 原股份数 × (1 + 送股比例),新成本价 = 原成本价 / (1 + 送股比例) + +2. **配股** + - 场景:公司配股,需要用户决定是否参与 + - 处理:如果参与,需要记录配股价格和数量,重新计算成本价 + +3. **转增股本** + - 场景:资本公积转增股本 + - 处理:类似送股,股份数增加,成本价降低 + +4. **股票合并(并股)** + - 场景:如10股合并为1股 + - 处理:股份数减少,成本价提高 + - 公式:新股份数 = 原股份数 / 合并比例,新成本价 = 原成本价 × 合并比例 + +5. **现金分红再投资** + - 场景:用户选择将分红现金再次买入股票 + - 处理:需要记录一笔买入交易 + +6. **基金分红再投资** + - 场景:基金分红选择红利再投资 + - 处理:基金份额增加,成本价不变 + +7. **股票退市/摘牌** + - 场景:股票退市,无法获取市场价格 + - 处理:需要标记为退市状态,使用最后已知价格或用户手动设置 + +8. **股票停牌** + - 场景:股票长期停牌 + - 处理:使用停牌前最后价格,标记停牌状态 + +9. **汇率变动(港股、美股)** + - 场景:持有港股、美股,汇率变动影响资产价值 + - 处理:需要记录买入时的汇率,计算时使用当前汇率 + +10. **交易费用** + - 场景:买入/卖出都有交易费用 + - 处理:买入时计入成本,卖出时从收益中扣除 + +11. **现金账户利息** + - 场景:证券账户现金可能产生利息 + - 处理:定期(如每月)更新现金余额,增加利息收入 + +12. **资产转移** + - 场景:从一个账户转移到另一个账户 + - 处理:需要支持多账户管理 + +### 3.2 可选处理的场景 + +1. **股票期权/权证** +2. **可转债** +3. **分级基金** +4. **ETF套利** + +--- + +## 四、收益率计算方法 + +### 4.1 基金净值法(时间加权收益率) + +**原理**:消除资金流入流出的影响,只反映投资能力。 + +**计算公式**: +``` +收益率 = (期末净值 - 期初净值) / 期初净值 +``` + +**每日净值计算**: +``` +当日净值 = (总资产价值) / (累计投入资金) +``` + +**累计净值**: +``` +累计净值 = 1 × (1 + r1) × (1 + r2) × ... × (1 + rn) +``` + +### 4.2 资金加权法(内部收益率 IRR) + +**原理**:考虑资金流入流出的时间点,计算真实的投资回报率。 + +**计算公式**: +``` +NPV = Σ(CFt / (1 + IRR)^t) = 0 +``` +其中: +- CFt:第t期的现金流(正数表示投入,负数表示提取) +- IRR:内部收益率 + +**实现方式**: +- 使用迭代法(如牛顿法)求解IRR +- 或使用Excel的XIRR函数(考虑具体日期) + +### 4.3 取两者低者的实现 + +**策略**:保守计算,取两种方法中较低的收益率。 + +**实现逻辑**: +```javascript +const timeWeightedReturn = calculateTimeWeightedReturn(); +const moneyWeightedReturn = calculateIRR(); +const finalReturn = Math.min(timeWeightedReturn, moneyWeightedReturn); +``` + +### 4.4 其他收益率指标 + +1. **累计收益率** + ``` + 累计收益率 = (当前总资产 - 累计投入资金) / 累计投入资金 + ``` + +2. **年化收益率** + ``` + 年化收益率 = (1 + 累计收益率)^(365 / 投资天数) - 1 + ``` + +3. **当年收益率** + ``` + 当年收益率 = (当前总资产 - 年初总资产) / 年初总资产 + ``` + +--- + +## 五、数据模型设计 + +### 5.1 资产账户(Account) + +```typescript +interface Account { + id: string; // 账户ID + name: string; // 账户名称 + type: 'stock' | 'fund' | 'cash'; // 账户类型 + currency: string; // 货币类型(CNY/USD/HKD) + createdAt: Date; // 创建时间 + updatedAt: Date; // 更新时间 +} +``` + +### 5.2 持仓(Position) + +```typescript +interface Position { + id: string; // 持仓ID + accountId: string; // 所属账户ID + symbol: string; // 股票/基金代码 + name: string; // 股票/基金名称 + shares: number; // 持仓份额 + costPrice: number; // 成本价(每股/每份) + currentPrice: number; // 当前价格 + market: string; // 市场(A股/港股/美股) + currency: string; // 货币类型 + status: 'active' | 'suspended' | 'delisted'; // 状态 + createdAt: Date; + updatedAt: Date; +} +``` + +### 5.3 持仓变更记录(PositionChange) + +```typescript +interface PositionChange { + id: string; // 变更ID + positionId: string; // 持仓ID + changeDate: Date; // 变更日期 + changeType: 'manual' | 'buy' | 'sell' | 'auto'; // 变更类型 + beforeShares: number; // 变更前份数 + beforeCostPrice: number; // 变更前成本价 + afterShares: number; // 变更后份数 + afterCostPrice: number; // 变更后成本价 + notes?: string; // 备注/思考 + createdAt: Date; +} +``` + +### 5.3.1 资金变动记录(CashFlow)- 用于计算 IRR + +```typescript +interface CashFlow { + id: string; // 资金流ID + accountId: string; // 账户ID + flowDate: Date; // 资金变动日期 + flowType: 'deposit' | 'withdraw' | 'dividend' | 'interest'; // 类型 + amount: number; // 金额(正数表示投入,负数表示提取) + currency: string; // 货币类型 + notes?: string; // 备注 + createdAt: Date; +} +``` + +**说明:** +- 不再需要详细的交易记录表 +- 持仓变更记录只记录成本价和份数的变化,不记录交易细节 +- 资金变动记录用于计算 IRR,用户只需记录投入/提取的时间和金额 + +### 5.4 现金账户(CashAccount) + +```typescript +interface CashAccount { + id: string; // 现金账户ID + accountId: string; // 所属账户ID + balance: number; // 余额 + currency: string; // 货币类型 + interestRate?: number; // 利率(年化) + updatedAt: Date; +} +``` + +### 5.5 每日资产快照(DailySnapshot) + +```typescript +interface DailySnapshot { + id: string; // 快照ID + date: Date; // 日期 + totalAsset: number; // 总资产 + totalCost: number; // 总成本(累计投入) + totalProfit: number; // 总收益 + netValue: number; // 单位净值(总资产/总成本) + timeWeightedReturn: number; // 时间加权收益率 + moneyWeightedReturn: number; // 资金加权收益率(基于资金流计算) + finalReturn: number; // 最终收益率(取两者低者) + annualizedReturn: number; // 年化收益率 + yearToDateReturn: number; // 当年收益率 + positions: Position[]; // 持仓明细(JSON格式) + cashAccounts: CashAccount[]; // 现金账户明细(JSON格式) +} +``` + +**说明:** +- 系统每日自动生成资产快照 +- 用于计算时间加权收益率(基金净值法) +- 不依赖交易记录,只依赖持仓和资产快照 + +--- + +## 六、核心算法设计 + +### 6.1 成本价计算算法 + +#### 买入后成本价计算 +```javascript +function calculateCostAfterBuy(originalShares, originalCost, buyShares, buyPrice, fee) { + const totalCost = (originalShares * originalCost) + (buyShares * buyPrice) + fee; + const totalShares = originalShares + buyShares; + return totalCost / totalShares; +} +``` + +#### 卖出后成本价计算 +```javascript +// 卖出不改变成本价,只减少份额 +function calculateCostAfterSell(originalShares, originalCost, sellShares) { + // 成本价不变 + return originalCost; +} +``` + +#### 拆股后成本价计算 +```javascript +function calculateCostAfterSplit(originalShares, originalCost, splitRatio) { + // splitRatio: 如 1:2 拆股,splitRatio = 2 + const newShares = originalShares * splitRatio; + const newCost = originalCost / splitRatio; + return { newShares, newCost }; +} +``` + +#### 分红后成本价计算 +```javascript +function calculateCostAfterDividend(originalCost, dividendPerShare) { + // 现金分红:成本价降低 + return originalCost - dividendPerShare; +} +``` + +#### 送股后成本价计算 +```javascript +function calculateCostAfterBonus(originalShares, originalCost, bonusRatio) { + // bonusRatio: 如 10送5,bonusRatio = 0.5 + const newShares = originalShares * (1 + bonusRatio); + const newCost = originalCost / (1 + bonusRatio); + return { newShares, newCost }; +} +``` + +### 6.2 收益率计算算法 + +#### 时间加权收益率(基金净值法) +```javascript +function calculateTimeWeightedReturn(snapshots) { + let cumulativeReturn = 1; + + for (let i = 1; i < snapshots.length; i++) { + const prevSnapshot = snapshots[i - 1]; + const currSnapshot = snapshots[i]; + + // 计算期间收益率 + const periodReturn = (currSnapshot.totalAsset - prevSnapshot.totalAsset) / prevSnapshot.totalAsset; + cumulativeReturn *= (1 + periodReturn); + } + + return cumulativeReturn - 1; +} +``` + +#### 资金加权收益率(IRR) +```javascript +function calculateIRR(cashFlows) { + // cashFlows: 资金变动记录数组 + // 正数:投入资金(deposit) + // 负数:提取资金(withdraw) + // 最后一条:当前资产价值(负数,表示"提取") + + // 构建现金流数组 + const flows = cashFlows.map(cf => ({ + date: cf.flowDate, + amount: cf.amount + })); + + // 添加当前资产价值(作为最后一笔现金流) + const currentAsset = calculateTotalAsset(); + flows.push({ + date: new Date(), + amount: -currentAsset // 负数表示"提取" + }); + + function npv(rate) { + let sum = 0; + const baseDate = flows[0].date; + for (let i = 0; i < flows.length; i++) { + const days = (flows[i].date - baseDate) / (1000 * 60 * 60 * 24); + sum += flows[i].amount / Math.pow(1 + rate, days / 365); + } + return sum; + } + + // 使用二分法求解 + let low = -0.99; + let high = 10; + let mid; + + for (let i = 0; i < 100; i++) { + mid = (low + high) / 2; + const npvValue = npv(mid); + + if (Math.abs(npvValue) < 0.0001) { + return mid; + } + + if (npvValue > 0) { + low = mid; + } else { + high = mid; + } + } + + return mid; +} +``` + +**说明:** +- 基于资金变动记录(CashFlow)计算,不是基于交易记录 +- 用户只需记录:什么时候投入多少钱、什么时候提取多少钱 +- 比记录每笔交易简单很多 + +#### 年化收益率计算 +```javascript +function calculateAnnualizedReturn(totalReturn, days) { + return Math.pow(1 + totalReturn, 365 / days) - 1; +} +``` + +### 6.3 每日资产更新流程 + +```javascript +async function updateDailyAssets(date) { + // 1. 获取所有持仓 + const positions = await getActivePositions(); + + // 2. 更新每个持仓的市场价格 + for (const position of positions) { + const currentPrice = await fetchMarketPrice(position.symbol, position.market, date); + position.currentPrice = currentPrice; + position.updatedAt = date; + } + + // 3. 计算总资产 + const totalAsset = calculateTotalAsset(positions, cashAccounts); + + // 4. 计算总成本 + const totalCost = calculateTotalCost(positions, cashAccounts); + + // 5. 计算总收益 + const totalProfit = totalAsset - totalCost; + + // 6. 计算收益率 + const timeWeightedReturn = calculateTimeWeightedReturn(snapshots); + const moneyWeightedReturn = calculateIRR(cashFlows, dates); + const finalReturn = Math.min(timeWeightedReturn, moneyWeightedReturn); + + // 7. 保存资产快照 + await saveAssetSnapshot({ + date, + totalAsset, + totalCost, + totalProfit, + timeWeightedReturn, + moneyWeightedReturn, + finalReturn, + positions, + cashAccounts + }); +} +``` + +--- + +## 七、交易场景处理流程 + +### 7.1 买入/卖出股票/基金(简化操作) + +**新设计:用户直接修改持仓** + +``` +用户操作: +1. 打开持仓列表,找到对应股票 +2. 点击"编辑"或"调整" +3. 直接修改: + - 成本价(如:从 100 改为 95,表示加仓后新的加权成本价) + - 份数(如:从 100 股改为 150 股) +4. 可选:添加备注/思考(如:"2024年1月加仓") +5. 保存 + +系统处理: +1. 记录变更前状态(用于变更历史) +2. 更新持仓(成本价、份数) +3. 记录持仓变更记录(简化版,不记录交易细节) +4. 更新总资产和收益率 +5. 生成每日资产快照(用于收益率计算) +``` + +**优势:** +- ✅ 操作简单,只需修改两个数字 +- ✅ 适合多券商用户快速汇总 +- ✅ 用户可以根据券商系统的成本价直接输入 +- ✅ 不需要记录每笔交易的费用、时间等细节 + +**注意事项:** +- 用户需要自己计算加权平均成本价(或使用系统提供的计算器) +- 系统会记录变更历史,但不会记录详细的交易信息 + +### 7.3 拆股处理 + +``` +1. 系统检测或用户输入:拆股比例(如 1:2) +2. 系统处理: + - 自动更新股份数 = 原股份数 × 拆股比例 + - 自动更新成本价 = 原成本价 ÷ 拆股比例 + - 记录拆股交易记录 +3. 更新总资产(总价值不变) +``` + +### 7.4 现金分红处理 + +``` +1. 系统检测或用户输入:每股分红金额 +2. 系统处理: + - 计算分红总额 = 每股分红 × 持仓份额 + - 更新成本价 = 原成本价 - 每股分红 + - 增加现金账户余额(分红总额) + - 记录分红交易记录 +3. 更新总资产和收益率 +``` + +### 7.5 送股处理 + +``` +1. 系统检测或用户输入:送股比例(如 10送5,比例为0.5) +2. 系统处理: + - 更新股份数 = 原股份数 × (1 + 送股比例) + - 更新成本价 = 原成本价 ÷ (1 + 送股比例) + - 记录送股交易记录 +3. 更新总资产(总价值不变) +``` + +### 7.6 提现处理 + +``` +1. 用户输入:提现金额 +2. 系统处理: + - 检查现金账户余额是否足够 + - 减少现金账户余额 + - 记录提现交易记录 +3. 更新总资产 +``` + +--- + +## 八、系统架构设计 + +### 8.1 技术栈建议 + +**前端**: +- React / Vue.js +- TypeScript +- 状态管理:Redux / Zustand +- UI框架:Ant Design / Material-UI + +**后端**: +- Node.js / Python +- 数据库:PostgreSQL / MySQL +- 缓存:Redis + +**数据获取**: +- 股票价格API:腾讯财经、新浪财经、Yahoo Finance +- 基金净值API:天天基金、晨星 + +### 8.2 模块划分 + +1. **资产管理模块** + - 账户管理 + - 持仓管理 + - 现金账户管理 + +2. **交易处理模块** + - 买入/卖出处理 + - 分红处理 + - 拆股/送股处理 + - 交易记录管理 + +3. **价格更新模块** + - 定时任务(每日收盘后更新) + - 价格数据获取 + - 价格数据缓存 + +4. **收益计算模块** + - 时间加权收益率计算 + - 资金加权收益率计算 + - 年化收益率计算 + - 资产快照生成 + +5. **数据统计模块** + - 总资产统计 + - 收益统计 + - 收益率统计 + - 持仓分析 + +### 8.3 数据存储设计 + +**表结构**: +- accounts(账户表) +- positions(持仓表) +- transactions(交易记录表) +- cash_accounts(现金账户表) +- asset_snapshots(资产快照表) +- market_prices(市场价格表,用于缓存) + +--- + +## 九、实现建议 + +### 9.1 开发优先级 + +**第一阶段(MVP)**: +1. 基本的资产账户管理 +2. 持仓管理(买入/卖出) +3. 每日价格更新 +4. 基本的收益计算(累计收益率) + +**第二阶段**: +1. 拆股、分红、送股处理 +2. 时间加权收益率和资金加权收益率计算 +3. 年化收益率计算 +4. 资产快照功能 + +**第三阶段**: +1. 多账户管理 +2. 多货币支持 +3. 高级统计和分析 +4. 数据导出功能 + +### 9.2 注意事项 + +1. **数据准确性** + - 价格数据需要可靠的数据源 + - 交易记录需要完整保存,不可修改 + - 成本价计算需要精确 + +2. **性能优化** + - 价格数据缓存 + - 资产快照定期生成,避免实时计算 + - 收益率计算优化(IRR计算可能较慢) + +3. **用户体验** + - 交易记录操作简单 + - 自动处理拆股、分红等场景 + - 清晰的收益展示 + +4. **数据备份** + - 定期备份交易记录 + - 支持数据导出 + +--- + +## 十、总结 + +### 10.1 设计要点 + +1. ✅ **用户只需直接修改持仓**:买入/卖出时直接修改成本价和份数,无需记录交易细节 +2. ✅ **自动处理特殊事件**:拆股、分红、送股等被动变更由系统自动处理 +3. ✅ **自动更新市场价格**:每日收盘后自动更新,无需用户手动输入 +4. ✅ **简化操作流程**:适合多券商用户快速汇总统计,不是替代券商系统 +5. ✅ **保守的收益率计算**:使用时间加权(基于资产快照)和资金加权(基于资金流)两种方法,取较低者 +6. ✅ **完整的收益统计**:累计收益率、年化收益率、当年收益率等 +7. ✅ **支持计划和复盘**:通过持仓思考记录实现,关联持仓而非交易 + +### 10.2 需要补充的场景 + +1. 送股(股票分红) +2. 配股 +3. 转增股本 +4. 股票合并 +5. 汇率变动(港股、美股) +6. 交易费用处理 +7. 现金账户利息 +8. 股票停牌/退市处理 + +### 10.3 实现建议 + +- 采用模块化设计,便于扩展 +- 持仓变更记录不可修改,保证数据可追溯性 +- 每日自动生成资产快照,用于收益率计算 +- 提供成本价计算器工具,帮助用户计算加权平均成本价 +- 支持批量导入功能,可以从券商系统导出后导入 +- 提供清晰的数据展示和统计 +- 持仓思考记录支持计划和复盘功能 + +### 10.4 数据模型总结 + +**核心表:** +- `positions` - 持仓表(用户直接修改成本价和份数) +- `position_changes` - 持仓变更记录(系统自动记录变更历史) +- `cash_flows` - 资金变动记录(用户记录投入/提取,用于 IRR 计算) +- `daily_snapshots` - 每日资产快照(系统自动生成,用于时间加权收益率) +- `position_thoughts` - 持仓思考记录(用户记录思考,用于计划和复盘) + +**不再需要:** +- ~~详细的交易记录表~~(改为简化的持仓变更记录) + +--- + +**文档版本**:v1.0 +**创建日期**:2024年 +**最后更新**:2024年 + diff --git a/机生文档/数据库设计文档.md b/机生文档/数据库设计文档.md new file mode 100644 index 0000000..706effe --- /dev/null +++ b/机生文档/数据库设计文档.md @@ -0,0 +1,832 @@ +# 思投录数据库设计文档 + +## 一、数据库选型分析 + +### 1.1 业务特点分析 + +根据业务需求,系统需要处理以下类型的数据: + +1. **结构化数据** + - 用户账户、持仓、交易记录 + - 交易计划、计划步骤 + - 资产净值、收益率等数值数据 + +2. **时间序列数据** + - 每日单位净值记录 + - 资产快照(每日) + - 价格历史数据 + +3. **文本内容数据** + - 交易思考(交易记录时填写) + - 复盘内容(定期复盘时填写) + - 计划思考(创建计划时填写) + +4. **关联关系** + - 交易记录与持仓的关联 + - 交易计划与交易记录的关联 + - 复盘与交易记录的关联 + - 计划步骤与计划的关联 + +5. **查询需求** + - 复杂的时间范围查询 + - 多表关联查询 + - 聚合统计查询 + - 时间线查询(按时间排序) + +### 1.2 数据库选型推荐 + +#### 推荐方案:PostgreSQL + +**推荐理由:** + +1. **关系型数据库优势** + - ✅ 数据结构清晰,关系明确 + - ✅ 支持复杂查询和统计(JOIN、聚合函数) + - ✅ 支持事务,保证数据一致性 + - ✅ ACID特性,数据可靠性高 + +2. **PostgreSQL特有优势** + - ✅ 支持JSON/JSONB类型,可灵活存储思考内容等文本 + - ✅ 支持数组类型,适合存储计划步骤等 + - ✅ 强大的时间序列查询能力 + - ✅ 支持全文搜索(可用于搜索思考内容) + - ✅ 性能优秀,适合中小型应用 + - ✅ 开源免费,社区活跃 + +3. **其他考虑** + - 如果团队更熟悉MySQL,MySQL 8.0+ 也是不错的选择 + - 对于时间序列数据,可以考虑用TimescaleDB(基于PostgreSQL的扩展) + +#### 备选方案:MySQL 8.0+ + +**适用场景:** +- 团队更熟悉MySQL +- 需要更好的云服务支持(如阿里云RDS) +- 数据量不是特别大 + +**MySQL优势:** +- ✅ 生态成熟,工具丰富 +- ✅ 云服务支持好 +- ✅ 性能稳定 +- ⚠️ JSON支持不如PostgreSQL强大 +- ⚠️ 复杂查询性能略逊于PostgreSQL + +#### 不推荐方案 + +- **MongoDB等NoSQL**:虽然可以存储JSON,但关联查询复杂,不适合这种关系明确的数据结构 +- **Redis**:只适合缓存,不适合持久化存储 +- **SQLite**:适合单机应用,不支持多用户并发 + +### 1.3 最终推荐 + +**推荐使用 PostgreSQL 14+** + +理由: +1. 数据结构关系明确,适合关系型数据库 +2. JSONB类型可以灵活存储思考内容 +3. 强大的查询能力,适合复杂统计 +4. 时间序列查询性能好 +5. 支持全文搜索,方便搜索思考内容 + +--- + +## 二、数据库表设计 + +### 2.1 表结构总览 + +``` +用户相关 +├── users (用户表) +└── user_settings (用户设置表) + +账户相关 +├── accounts (账户表) +├── positions (持仓表) +├── cash_accounts (现金账户表) +└── position_warnings (持仓预警表) + +交易相关 +├── transactions (交易记录表) +├── transaction_thoughts (交易思考表) +└── transaction_reviews (交易复盘表) + +计划相关 +├── trading_plans (交易计划表) +├── plan_steps (计划步骤表) +└── plan_thoughts (计划思考表) + +净值相关 +├── net_value_snapshots (净值快照表) +└── daily_net_values (每日净值表) + +市场数据 +└── market_prices (市场价格表) +``` + +### 2.2 详细表结构设计 + +#### 2.2.1 用户相关表 + +##### users (用户表) + +```sql +CREATE TABLE users ( + id BIGSERIAL PRIMARY KEY, + username VARCHAR(50) UNIQUE NOT NULL, + email VARCHAR(100) UNIQUE, + phone VARCHAR(20) UNIQUE, + password_hash VARCHAR(255) NOT NULL, + nickname VARCHAR(50), + 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) DEFAULT 'active' -- active, inactive, deleted +); + +CREATE INDEX idx_users_email ON users(email); +CREATE INDEX idx_users_phone ON users(phone); +CREATE INDEX idx_users_status ON users(status); +``` + +##### user_settings (用户设置表) + +```sql +CREATE TABLE user_settings ( + id BIGSERIAL PRIMARY KEY, + user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE, + setting_key VARCHAR(50) NOT NULL, + setting_value TEXT, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + UNIQUE(user_id, setting_key) +); + +CREATE INDEX idx_user_settings_user_id ON user_settings(user_id); +``` + +**常用设置项:** +- `position_limit_per_stock`: 单只股票仓位上限(百分比) +- `currency`: 默认货币 +- `theme`: 主题设置 +- `notification_enabled`: 是否启用通知 + +#### 2.2.2 账户相关表 + +##### accounts (账户表) + +```sql +CREATE TABLE accounts ( + id BIGSERIAL PRIMARY KEY, + user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE, + name VARCHAR(100) NOT NULL, + type VARCHAR(20) NOT NULL, -- stock, fund, cash, mixed + currency VARCHAR(10) NOT NULL DEFAULT 'CNY', -- CNY, USD, HKD + description TEXT, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + status VARCHAR(20) DEFAULT 'active' -- active, archived, deleted +); + +CREATE INDEX idx_accounts_user_id ON accounts(user_id); +CREATE INDEX idx_accounts_status ON accounts(status); +``` + +##### positions (持仓表) + +```sql +CREATE TABLE positions ( + id BIGSERIAL PRIMARY KEY, + account_id BIGINT NOT NULL REFERENCES accounts(id) ON DELETE CASCADE, + symbol VARCHAR(20) NOT NULL, -- 股票/基金代码 + name VARCHAR(100) NOT NULL, -- 股票/基金名称 + market VARCHAR(20) NOT NULL, -- 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', + status VARCHAR(20) DEFAULT 'active', -- active, suspended, delisted + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + UNIQUE(account_id, symbol, market) -- 同一账户同一股票只能有一条持仓 +); + +CREATE INDEX idx_positions_account_id ON positions(account_id); +CREATE INDEX idx_positions_symbol ON positions(symbol); +CREATE INDEX idx_positions_status ON positions(status); +CREATE INDEX idx_positions_updated_at ON positions(updated_at); +``` + +##### cash_accounts (现金账户表) + +```sql +CREATE TABLE cash_accounts ( + id BIGSERIAL PRIMARY KEY, + account_id BIGINT NOT NULL REFERENCES accounts(id) ON DELETE CASCADE, + balance DECIMAL(18, 2) NOT NULL DEFAULT 0, -- 余额 + currency VARCHAR(10) NOT NULL DEFAULT 'CNY', + interest_rate DECIMAL(5, 4) DEFAULT 0, -- 年化利率 + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + UNIQUE(account_id, currency) -- 同一账户同一货币只能有一条记录 +); + +CREATE INDEX idx_cash_accounts_account_id ON cash_accounts(account_id); +``` + +##### position_warnings (持仓预警表) + +```sql +CREATE TABLE position_warnings ( + id BIGSERIAL PRIMARY KEY, + user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE, + position_id BIGINT NOT NULL REFERENCES positions(id) ON DELETE CASCADE, + warning_type VARCHAR(20) NOT NULL, -- position_limit, price_alert + threshold_value DECIMAL(18, 4), -- 阈值 + is_triggered BOOLEAN DEFAULT FALSE, + triggered_at TIMESTAMP, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX idx_position_warnings_user_id ON position_warnings(user_id); +CREATE INDEX idx_position_warnings_position_id ON position_warnings(position_id); +``` + +#### 2.2.3 交易相关表 + +##### transactions (交易记录表) + +```sql +CREATE TABLE transactions ( + id BIGSERIAL PRIMARY KEY, + user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE, + account_id BIGINT NOT NULL REFERENCES accounts(id) ON DELETE CASCADE, + position_id BIGINT REFERENCES positions(id) ON DELETE SET NULL, -- 可为空,因为可能是现金交易 + trading_plan_id BIGINT REFERENCES trading_plans(id) ON DELETE SET NULL, -- 关联交易计划 + type VARCHAR(20) NOT NULL, -- buy, sell, dividend, split, bonus, rights, deposit, withdraw + date DATE NOT NULL, -- 交易日期 + symbol VARCHAR(20), -- 股票/基金代码 + name VARCHAR(100), -- 股票/基金名称 + market VARCHAR(20), -- 市场 + shares DECIMAL(18, 4), -- 交易份额 + price DECIMAL(18, 4), -- 交易价格 + amount DECIMAL(18, 2) NOT NULL, -- 交易金额(正数表示收入,负数表示支出) + fee DECIMAL(18, 2) DEFAULT 0, -- 交易费用 + currency VARCHAR(10) NOT NULL DEFAULT 'CNY', + exchange_rate DECIMAL(10, 6) DEFAULT 1, -- 汇率(用于多货币) + metadata JSONB, -- 存储额外信息,如拆股比例、分红金额等 + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX idx_transactions_user_id ON transactions(user_id); +CREATE INDEX idx_transactions_account_id ON transactions(account_id); +CREATE INDEX idx_transactions_position_id ON transactions(position_id); +CREATE INDEX idx_transactions_trading_plan_id ON transactions(trading_plan_id); +CREATE INDEX idx_transactions_date ON transactions(date); +CREATE INDEX idx_transactions_type ON transactions(type); +CREATE INDEX idx_transactions_symbol ON transactions(symbol); +CREATE INDEX idx_transactions_date_desc ON transactions(date DESC); -- 用于时间线查询 +``` + +**metadata字段示例:** +```json +{ + "split_ratio": 2, // 拆股比例 + "dividend_per_share": 0.5, // 每股分红 + "bonus_ratio": 0.5, // 送股比例 + "rights_price": 10.5, // 配股价格 + "rights_shares": 100 // 配股数量 +} +``` + +##### transaction_thoughts (交易思考表) + +```sql +CREATE TABLE transaction_thoughts ( + id BIGSERIAL PRIMARY KEY, + transaction_id BIGINT NOT NULL REFERENCES transactions(id) ON DELETE CASCADE, + content TEXT NOT NULL, -- 思考内容 + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + UNIQUE(transaction_id) -- 每笔交易只能有一条思考 +); + +CREATE INDEX idx_transaction_thoughts_transaction_id ON transaction_thoughts(transaction_id); +CREATE INDEX idx_transaction_thoughts_content ON transaction_thoughts USING gin(to_tsvector('jiebacfg', content)); -- 全文搜索索引(需要安装pg_trgm扩展) +``` + +##### transaction_reviews (交易复盘表) + +```sql +CREATE TABLE transaction_reviews ( + id BIGSERIAL PRIMARY KEY, + transaction_id BIGINT NOT NULL REFERENCES transactions(id) ON DELETE CASCADE, + review_type VARCHAR(20) DEFAULT 'manual', -- manual, scheduled + content TEXT NOT NULL, -- 复盘内容 + review_date DATE NOT NULL, -- 复盘日期 + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX idx_transaction_reviews_transaction_id ON transaction_reviews(transaction_id); +CREATE INDEX idx_transaction_reviews_review_date ON transaction_reviews(review_date); +CREATE INDEX idx_transaction_reviews_content ON transaction_reviews USING gin(to_tsvector('jiebacfg', content)); -- 全文搜索索引 +``` + +**说明:** +- 一笔交易可以有多次复盘(不同时间点) +- `review_type`: manual(手动复盘)、scheduled(定期提醒复盘) + +#### 2.2.4 计划相关表 + +##### trading_plans (交易计划表) + +```sql +CREATE TABLE trading_plans ( + id BIGSERIAL PRIMARY KEY, + user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE, + symbol VARCHAR(20) NOT NULL, -- 股票代码 + name VARCHAR(100) NOT NULL, -- 股票名称 + market VARCHAR(20) NOT NULL, -- 市场 + target_price DECIMAL(18, 4) NOT NULL, -- 目标价格 + target_amount DECIMAL(18, 2), -- 目标金额 + target_shares DECIMAL(18, 4), -- 目标份额(金额和份额二选一) + deadline DATE, -- 截止日期 + status VARCHAR(20) DEFAULT 'pending', -- pending, in_progress, completed, cancelled + progress DECIMAL(5, 2) DEFAULT 0, -- 完成进度(百分比) + current_price DECIMAL(18, 4), -- 当前价格(用于提醒) + is_price_alert_enabled BOOLEAN DEFAULT TRUE, -- 是否启用价格提醒 + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + completed_at TIMESTAMP +); + +CREATE INDEX idx_trading_plans_user_id ON trading_plans(user_id); +CREATE INDEX idx_trading_plans_status ON trading_plans(status); +CREATE INDEX idx_trading_plans_deadline ON trading_plans(deadline); +CREATE INDEX idx_trading_plans_symbol ON trading_plans(symbol); +``` + +##### plan_steps (计划步骤表) + +```sql +CREATE TABLE plan_steps ( + id BIGSERIAL PRIMARY KEY, + trading_plan_id BIGINT NOT NULL REFERENCES trading_plans(id) ON DELETE CASCADE, + step_order INTEGER NOT NULL, -- 步骤顺序(1, 2, 3...) + target_price DECIMAL(18, 4) NOT NULL, -- 该步骤的目标价格 + target_amount DECIMAL(18, 2), -- 该步骤的目标金额 + target_shares DECIMAL(18, 4), -- 该步骤的目标份额 + status VARCHAR(20) DEFAULT 'pending', -- pending, completed + completed_at TIMESTAMP, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + UNIQUE(trading_plan_id, step_order) +); + +CREATE INDEX idx_plan_steps_trading_plan_id ON plan_steps(trading_plan_id); +CREATE INDEX idx_plan_steps_status ON plan_steps(status); +``` + +##### plan_thoughts (计划思考表) + +```sql +CREATE TABLE plan_thoughts ( + id BIGSERIAL PRIMARY KEY, + trading_plan_id BIGINT NOT NULL REFERENCES trading_plans(id) ON DELETE CASCADE, + content TEXT NOT NULL, -- 思考内容 + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + UNIQUE(trading_plan_id) -- 每个计划只能有一条思考 +); + +CREATE INDEX idx_plan_thoughts_trading_plan_id ON plan_thoughts(trading_plan_id); +CREATE INDEX idx_plan_thoughts_content ON plan_thoughts USING gin(to_tsvector('jiebacfg', content)); -- 全文搜索索引 +``` + +#### 2.2.5 净值相关表 + +##### net_value_snapshots (净值快照表) + +```sql +CREATE TABLE net_value_snapshots ( + id BIGSERIAL PRIMARY KEY, + user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE, + account_id BIGINT REFERENCES accounts(id) ON DELETE CASCADE, -- 可为空,表示总账户 + snapshot_date DATE NOT NULL, -- 快照日期 + total_asset DECIMAL(18, 2) NOT NULL, -- 总资产 + total_cost DECIMAL(18, 2) NOT NULL, -- 总成本(累计投入) + total_profit DECIMAL(18, 2) NOT NULL, -- 总收益 + net_value DECIMAL(18, 6) NOT NULL, -- 单位净值 + cumulative_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, -- 现金明细 + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + UNIQUE(user_id, account_id, snapshot_date) -- 同一用户同一账户同一日期只能有一条快照 +); + +CREATE INDEX idx_net_value_snapshots_user_id ON net_value_snapshots(user_id); +CREATE INDEX idx_net_value_snapshots_account_id ON net_value_snapshots(account_id); +CREATE INDEX idx_net_value_snapshots_date ON net_value_snapshots(snapshot_date); +CREATE INDEX idx_net_value_snapshots_user_date ON net_value_snapshots(user_id, snapshot_date DESC); +``` + +**positions_data字段示例:** +```json +[ + { + "position_id": 1, + "symbol": "600519", + "name": "贵州茅台", + "shares": 100, + "cost_price": 1600, + "current_price": 1850, + "market_value": 185000, + "profit": 25000 + } +] +``` + +##### daily_net_values (每日净值表) + +```sql +CREATE TABLE daily_net_values ( + id BIGSERIAL PRIMARY KEY, + user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE, + account_id BIGINT REFERENCES accounts(id) ON DELETE CASCADE, -- 可为空,表示总账户 + value_date DATE NOT NULL, -- 净值日期 + net_value DECIMAL(18, 6) NOT NULL, -- 单位净值 + total_asset DECIMAL(18, 2) NOT NULL, -- 总资产 + total_cost DECIMAL(18, 2) NOT NULL, -- 总成本 + daily_return DECIMAL(10, 6), -- 日收益率 + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + UNIQUE(user_id, account_id, value_date) +); + +CREATE INDEX idx_daily_net_values_user_id ON daily_net_values(user_id); +CREATE INDEX idx_daily_net_values_account_id ON daily_net_values(account_id); +CREATE INDEX idx_daily_net_values_date ON daily_net_values(value_date); +CREATE INDEX idx_daily_net_values_user_date ON daily_net_values(user_id, account_id, value_date DESC); -- 用于净值曲线查询 +``` + +#### 2.2.6 市场数据表 + +##### market_prices (市场价格表) + +```sql +CREATE TABLE market_prices ( + id BIGSERIAL PRIMARY KEY, + symbol VARCHAR(20) NOT NULL, + market VARCHAR(20) NOT NULL, + price_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(18, 2), -- 成交额 + currency VARCHAR(10) DEFAULT 'CNY', + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + UNIQUE(symbol, market, price_date) +); + +CREATE INDEX idx_market_prices_symbol ON market_prices(symbol, market); +CREATE INDEX idx_market_prices_date ON market_prices(price_date); +CREATE INDEX idx_market_prices_symbol_date ON market_prices(symbol, market, price_date DESC); +``` + +--- + +## 三、关键业务逻辑实现 + +### 3.1 基金净值法计算 + +#### 3.1.1 每日净值计算流程 + +```sql +-- 1. 计算当日总资产 +-- 总资产 = 所有持仓市值 + 现金余额 + +-- 2. 计算累计投入资金 +-- 累计投入 = 所有买入交易的金额总和 - 所有卖出交易的金额总和 + 初始资金 + +-- 3. 计算单位净值 +-- 单位净值 = 总资产 / 累计投入资金 + +-- 4. 计算收益率 +-- 累计收益率 = (当前净值 - 初始净值) / 初始净值 +-- 日收益率 = (今日净值 - 昨日净值) / 昨日净值 +``` + +#### 3.1.2 净值计算SQL示例 + +```sql +-- 计算用户某账户的当日净值 +WITH asset_value AS ( + -- 计算持仓市值 + SELECT + COALESCE(SUM(p.shares * p.current_price), 0) as position_value + FROM positions p + WHERE p.account_id = :account_id + AND p.status = 'active' +), +cash_value AS ( + -- 计算现金余额 + SELECT + COALESCE(SUM(c.balance), 0) as cash_balance + FROM cash_accounts c + WHERE c.account_id = :account_id +), +total_cost AS ( + -- 计算累计投入 + SELECT + COALESCE(SUM( + CASE + WHEN t.type IN ('buy', 'deposit') THEN t.amount + WHEN t.type IN ('sell', 'withdraw') THEN -t.amount + ELSE 0 + END + ), 0) as total_invested + FROM transactions t + WHERE t.account_id = :account_id +) +SELECT + (av.position_value + cv.cash_balance) as total_asset, + tc.total_invested, + CASE + WHEN tc.total_invested > 0 + THEN (av.position_value + cv.cash_balance) / tc.total_invested + ELSE 1.0 + END as net_value +FROM asset_value av, cash_value cv, total_cost tc; +``` + +### 3.2 交易记录与思考关联查询 + +```sql +-- 查询交易记录及其思考(时间线) +SELECT + t.id, + t.date, + t.type, + t.symbol, + t.name, + t.shares, + t.price, + t.amount, + tt.content as thought_content, + tt.created_at as thought_created_at +FROM transactions t +LEFT JOIN transaction_thoughts tt ON t.id = tt.transaction_id +WHERE t.user_id = :user_id +ORDER BY t.date DESC, t.created_at DESC +LIMIT :limit OFFSET :offset; +``` + +### 3.3 交易复盘查询 + +```sql +-- 查询交易及其所有复盘记录 +SELECT + t.id, + t.date, + t.symbol, + t.name, + t.type, + tr.id as review_id, + tr.review_date, + tr.content as review_content, + tr.created_at as review_created_at +FROM transactions t +LEFT JOIN transaction_reviews tr ON t.id = tr.transaction_id +WHERE t.user_id = :user_id + AND t.id = :transaction_id +ORDER BY tr.review_date DESC; +``` + +### 3.4 交易计划进度更新 + +```sql +-- 更新计划进度 +UPDATE trading_plans tp +SET + progress = ( + SELECT + CASE + WHEN tp.target_amount > 0 + THEN LEAST(100, (COALESCE(SUM(CASE WHEN t.type = 'buy' THEN t.amount ELSE 0 END), 0) / tp.target_amount) * 100) + WHEN tp.target_shares > 0 + THEN LEAST(100, (COALESCE(SUM(CASE WHEN t.type = 'buy' THEN t.shares ELSE 0 END), 0) / tp.target_shares) * 100) + ELSE 0 + END + FROM transactions t + WHERE t.trading_plan_id = tp.id + ), + status = CASE + WHEN progress >= 100 THEN 'completed' + WHEN progress > 0 THEN 'in_progress' + ELSE 'pending' + END, + updated_at = CURRENT_TIMESTAMP +WHERE tp.id = :plan_id; +``` + +--- + +## 四、索引优化建议 + +### 4.1 必须创建的索引 + +1. **外键索引**:所有外键字段都应创建索引 +2. **查询字段索引**:经常用于WHERE、JOIN、ORDER BY的字段 +3. **时间字段索引**:date、created_at等时间字段 + +### 4.2 全文搜索索引 + +对于思考内容、复盘内容等文本字段,使用PostgreSQL的全文搜索功能: + +```sql +-- 安装扩展(需要管理员权限) +CREATE EXTENSION IF NOT EXISTS pg_trgm; +CREATE EXTENSION IF NOT EXISTS jieba; -- 中文分词(如果可用) + +-- 创建全文搜索索引 +CREATE INDEX idx_transaction_thoughts_content_search +ON transaction_thoughts USING gin(to_tsvector('jiebacfg', content)); +``` + +### 4.3 复合索引 + +根据查询模式创建复合索引: + +```sql +-- 用户时间线查询(最常用) +CREATE INDEX idx_transactions_user_date ON transactions(user_id, date DESC, created_at DESC); + +-- 净值查询 +CREATE INDEX idx_daily_net_values_user_account_date ON daily_net_values(user_id, account_id, value_date DESC); + +-- 持仓查询 +CREATE INDEX idx_positions_account_status ON positions(account_id, status); +``` + +--- + +## 五、数据维护建议 + +### 5.1 定期任务 + +1. **每日净值计算** + - 每日收盘后(如18:00)自动计算并保存净值 + - 更新持仓的当前价格 + +2. **价格数据更新** + - 每日更新市场价格表 + - 清理过期的价格数据(保留最近2年) + +3. **计划提醒** + - 检查到达目标价格的计划 + - 发送提醒通知 + +4. **复盘提醒** + - 定期(如每月)提醒用户进行复盘 + +### 5.2 数据清理 + +```sql +-- 清理过期的市场价格数据(保留最近2年) +DELETE FROM market_prices +WHERE price_date < CURRENT_DATE - INTERVAL '2 years'; + +-- 归档已删除的数据(软删除的数据) +-- 可以考虑定期归档到历史表 +``` + +### 5.3 数据备份 + +1. **定期全量备份**:每日备份 +2. **增量备份**:每小时备份事务日志 +3. **重要数据备份**:交易记录、思考内容等关键数据单独备份 + +--- + +## 六、性能优化建议 + +### 6.1 查询优化 + +1. **分页查询**:所有列表查询都应使用LIMIT和OFFSET +2. **避免N+1查询**:使用JOIN一次性获取关联数据 +3. **使用EXPLAIN分析**:定期分析慢查询 + +### 6.2 缓存策略 + +1. **净值数据缓存**:最近30天的净值数据可以缓存 +2. **持仓数据缓存**:当前持仓数据可以缓存,更新时失效 +3. **市场价格缓存**:使用Redis缓存常用股票的最新价格 + +### 6.3 读写分离 + +如果数据量大,可以考虑: +- 主库:写操作 +- 从库:读操作(查询、统计) + +--- + +## 七、数据迁移方案 + +### 7.1 初始化数据 + +```sql +-- 创建初始用户 +INSERT INTO users (username, password_hash, nickname) +VALUES ('admin', 'hashed_password', '管理员'); + +-- 创建初始账户 +INSERT INTO accounts (user_id, name, type, currency) +VALUES (1, '主账户', 'mixed', 'CNY'); + +-- 设置初始净值 +INSERT INTO daily_net_values (user_id, account_id, value_date, net_value, total_asset, total_cost) +VALUES (1, 1, CURRENT_DATE, 1.0, 0, 0); +``` + +### 7.2 数据导入 + +如果从其他系统迁移数据,需要: +1. 清洗数据格式 +2. 按依赖关系顺序导入(先用户,再账户,再交易) +3. 重新计算净值历史 + +--- + +## 八、安全考虑 + +### 8.1 数据安全 + +1. **敏感数据加密**:密码使用bcrypt等加密 +2. **SQL注入防护**:使用参数化查询 +3. **权限控制**:用户只能访问自己的数据 + +### 8.2 数据完整性 + +1. **外键约束**:确保数据关联正确 +2. **唯一约束**:防止重复数据 +3. **检查约束**:确保数据有效性 + +```sql +-- 示例:检查约束 +ALTER TABLE transactions +ADD CONSTRAINT check_transaction_amount +CHECK (amount != 0); + +ALTER TABLE positions +ADD CONSTRAINT check_shares_positive +CHECK (shares >= 0); +``` + +--- + +## 九、总结 + +### 9.1 数据库选型 + +**推荐使用 PostgreSQL 14+** + +优势: +- ✅ 关系型数据库,结构清晰 +- ✅ JSONB支持,灵活存储思考内容 +- ✅ 强大的查询能力 +- ✅ 全文搜索支持 +- ✅ 时间序列查询性能好 + +### 9.2 核心表设计 + +1. **用户体系**:users, user_settings +2. **账户体系**:accounts, positions, cash_accounts +3. **交易体系**:transactions, transaction_thoughts, transaction_reviews +4. **计划体系**:trading_plans, plan_steps, plan_thoughts +5. **净值体系**:daily_net_values, net_value_snapshots +6. **市场数据**:market_prices + +### 9.3 关键特性 + +1. **基金净值法**:通过daily_net_values表记录每日净值 +2. **思考记录**:transaction_thoughts和plan_thoughts表 +3. **复盘功能**:transaction_reviews表支持多次复盘 +4. **时间线查询**:通过索引优化时间线查询性能 + +### 9.4 后续优化 + +1. 根据实际使用情况调整索引 +2. 监控慢查询,持续优化 +3. 考虑使用TimescaleDB处理时间序列数据 +4. 根据数据量考虑分表策略 + +--- + +**文档版本**:v1.0 +**创建日期**:2024年 +**数据库版本**:PostgreSQL 14+ + diff --git a/机生文档/设计优化评估-简化交易记录.md b/机生文档/设计优化评估-简化交易记录.md new file mode 100644 index 0000000..b7182fc --- /dev/null +++ b/机生文档/设计优化评估-简化交易记录.md @@ -0,0 +1,419 @@ +# 设计优化评估:简化交易记录方案 + +## 一、优化方案概述 + +### 1.1 核心设计思路 + +**原设计(记录每次交易):** +- 用户每次买入/卖出都需要记录:价格、份额、费用、日期 +- 系统自动计算加权平均成本价 +- 保留完整的交易历史记录 + +**新设计(直接修改持仓):** +- 被动变更(分红、拆股、送股):系统自动完成 ✅ +- 主动变更(买入、卖出、追加买入):用户直接修改成本价和份数 +- 不记录每次交易的详细历史 + +### 1.2 用户场景分析 + +**目标用户:多券商用户** +- 在多个券商都有账户 +- 需要汇总查看整体持仓和收益 +- 不需要替代券商系统,只是汇总统计 + +**核心需求:** +- 简单快速更新持仓 +- 自动计算收益和收益率 +- 未来支持计划和复盘功能 + +--- + +## 二、方案评估 + +### 2.1 ✅ 优势分析 + +#### 1. **操作简单直接** +``` +原设计:买入 → 输入价格、份额、费用 → 系统计算成本价 +新设计:直接修改成本价和份数 → 完成 +``` +- ✅ 操作步骤减少 50% 以上 +- ✅ 学习成本低,上手快 +- ✅ 适合快速更新多券商汇总数据 + +#### 2. **符合多券商场景** +- ✅ 用户已经在券商系统完成交易,这里只是汇总 +- ✅ 不需要重复录入交易细节 +- ✅ 可以定期(如每周/每月)批量更新持仓 + +#### 3. **降低维护成本** +- ✅ 不需要记录每次交易的费用、时间等细节 +- ✅ 减少数据录入错误 +- ✅ 减少系统复杂度 + +#### 4. **灵活性更高** +- ✅ 用户可以手动调整成本价(如考虑交易费用后的实际成本) +- ✅ 可以快速修正错误 +- ✅ 支持"模糊"记录(不需要精确到每笔交易) + +### 2.2 ⚠️ 潜在问题分析 + +#### 1. **收益率计算准确性** + +**问题:** +- 基金净值法需要知道每次资金流入流出的时间点 +- 资金加权法(IRR)需要完整的现金流记录 +- 如果只有持仓快照,无法准确计算时间加权收益率 + +**影响评估:** +- ⚠️ **时间加权收益率**:需要每日资产快照,如果只有持仓数据,可以计算,但精度可能受影响 +- ⚠️ **资金加权收益率(IRR)**:需要现金流记录,如果只记录持仓,无法计算 +- ✅ **累计收益率**:可以计算(当前资产 - 累计投入) +- ✅ **年化收益率**:可以计算(基于累计收益率) + +**解决方案:** +- 保留"资金变动记录"(不是交易记录,而是资金流入流出) +- 记录:日期、金额、类型(投入/提取) +- 这样可以计算 IRR,同时保持操作简单 + +#### 2. **计划和复盘功能的影响** + +**问题:** +- PRD 中提到需要记录"交易思考"和"复盘" +- 如果只有持仓快照,如何关联思考和复盘? + +**影响评估:** +- ⚠️ 无法关联到具体的某笔交易 +- ✅ 可以关联到持仓(某只股票的整体思考) +- ✅ 可以记录时间点的思考(如"2024年1月加仓茅台") + +**解决方案:** +- 持仓级别的思考记录(不是交易级别) +- 时间线记录(记录某个时间点的持仓变化和思考) +- 支持"持仓变更记录"(记录成本价和份数的变化,但不记录交易细节) + +#### 3. **数据可追溯性** + +**问题:** +- 如果只记录当前持仓,无法追溯历史 +- 无法回答"什么时候买入的?"、"买入价格是多少?" + +**影响评估:** +- ⚠️ 失去详细的交易历史 +- ✅ 可以保留持仓变更历史(成本价和份数的变化) +- ✅ 对于多券商汇总场景,这个需求可能不是核心 + +**解决方案:** +- 保留"持仓变更记录"(简化版) + - 日期 + - 变更类型(手动调整/买入/卖出) + - 变更前:成本价、份数 + - 变更后:成本价、份数 + - 可选:思考/备注 + +#### 4. **成本价计算的准确性** + +**问题:** +- 用户手动输入成本价,可能不准确 +- 多笔买入的加权平均成本价需要用户自己计算 + +**影响评估:** +- ⚠️ 用户需要自己计算加权平均成本价 +- ⚠️ 可能输入错误 +- ✅ 但用户可以根据券商系统的成本价直接输入 + +**解决方案:** +- 提供"成本价计算器"工具 +- 支持批量导入(从券商系统导出后导入) +- 提供成本价验证提示 + +--- + +## 三、优化后的设计方案 + +### 3.1 数据模型调整 + +#### 持仓表(Position)- 保持不变 +```typescript +interface Position { + id: string; + accountId: string; + symbol: string; + name: string; + shares: number; // 持仓份额 + costPrice: number; // 成本价(用户直接修改) + currentPrice: number; // 当前价格(系统自动更新) + market: string; + currency: string; + status: string; + createdAt: Date; + updatedAt: Date; +} +``` + +#### 持仓变更记录表(PositionChange)- 新增 +```typescript +interface PositionChange { + id: string; + positionId: string; + changeDate: Date; // 变更日期 + changeType: 'manual' | 'buy' | 'sell' | 'auto'; // 变更类型 + beforeShares: number; // 变更前份数 + beforeCostPrice: number; // 变更前成本价 + afterShares: number; // 变更后份数 + afterCostPrice: number; // 变更后成本价 + notes?: string; // 备注/思考 + createdAt: Date; +} +``` + +#### 资金变动记录表(CashFlow)- 新增(用于计算 IRR) +```typescript +interface CashFlow { + id: string; + accountId: string; + flowDate: Date; // 资金变动日期 + flowType: 'deposit' | 'withdraw' | 'dividend' | 'interest'; // 类型 + amount: number; // 金额(正数表示投入,负数表示提取) + currency: string; + notes?: string; // 备注 + createdAt: Date; +} +``` + +#### 持仓思考表(PositionThought)- 新增(用于计划和复盘) +```typescript +interface PositionThought { + id: string; + positionId: string; + thoughtDate: Date; // 思考日期 + thoughtType: 'plan' | 'review' | 'note'; // 类型 + content: string; // 思考内容 + createdAt: Date; + updatedAt: Date; +} +``` + +### 3.2 操作流程设计 + +#### 买入/卖出操作(简化版) + +``` +用户操作: +1. 打开持仓列表 +2. 点击"编辑"或"调整" +3. 直接修改: + - 成本价(如:从 100 改为 95,表示加仓后新的加权成本价) + - 份数(如:从 100 股改为 150 股) +4. 可选:添加备注/思考 +5. 保存 + +系统处理: +1. 记录变更前状态 +2. 更新持仓(成本价、份数) +3. 记录持仓变更记录 +4. 更新总资产和收益率 +``` + +#### 被动变更(系统自动) + +``` +分红/拆股/送股: +1. 系统检测或用户触发 +2. 自动计算新的成本价和份数 +3. 更新持仓 +4. 记录持仓变更记录(changeType = 'auto') +``` + +### 3.3 收益率计算调整 + +#### 时间加权收益率(基金净值法) + +**方案:基于每日资产快照** +```javascript +// 需要每日记录资产快照 +interface DailySnapshot { + date: Date; + totalAsset: number; // 总资产 + totalCost: number; // 总成本(累计投入) + netValue: number; // 单位净值 +} + +// 计算方式 +function calculateTimeWeightedReturn(snapshots) { + let cumulativeReturn = 1; + for (let i = 1; i < snapshots.length; i++) { + const periodReturn = (snapshots[i].netValue - snapshots[i-1].netValue) / snapshots[i-1].netValue; + cumulativeReturn *= (1 + periodReturn); + } + return cumulativeReturn - 1; +} +``` + +**关键点:** +- ✅ 需要每日资产快照(系统自动生成) +- ✅ 不依赖交易记录,只依赖资产快照 +- ✅ 可以准确计算 + +#### 资金加权收益率(IRR) + +**方案:基于资金变动记录** +```javascript +// 使用 CashFlow 记录 +function calculateIRR(cashFlows) { + // cashFlows: 资金流入流出记录 + // 正数:投入资金 + // 负数:提取资金 + // 最后一条:当前资产价值(负数,表示"提取") + + // 使用二分法或牛顿法求解 IRR +} +``` + +**关键点:** +- ✅ 需要记录资金变动(不是交易记录) +- ✅ 用户只需要记录:什么时候投入多少钱、什么时候提取多少钱 +- ✅ 比记录每笔交易简单很多 + +--- + +## 四、方案对比总结 + +### 4.1 功能对比 + +| 功能 | 原设计(记录交易) | 新设计(直接修改) | 评估 | +|------|------------------|-------------------|------| +| 操作复杂度 | 高(每次交易需录入) | 低(直接修改) | ✅ 新设计更简单 | +| 数据准确性 | 高(系统计算) | 中(用户输入) | ⚠️ 需要验证 | +| 交易历史 | 完整 | 简化(变更记录) | ⚠️ 失去细节 | +| 收益率计算 | 精确 | 可接受 | ✅ 通过快照和资金流可计算 | +| 计划和复盘 | 可关联交易 | 可关联持仓 | ✅ 两种都支持 | +| 多券商场景 | 适合 | 更适合 | ✅ 新设计更适合 | + +### 4.2 适用场景分析 + +**新设计更适合:** +- ✅ 多券商用户汇总统计 +- ✅ 不需要详细交易历史的用户 +- ✅ 希望快速更新持仓的用户 +- ✅ 主要关注收益统计,不关注交易细节 + +**原设计更适合:** +- ✅ 需要完整交易历史的用户 +- ✅ 需要精确计算每笔交易收益的用户 +- ✅ 单券商用户(可以对接券商API) + +### 4.3 推荐方案 + +**推荐采用新设计(直接修改持仓),但需要补充:** + +1. ✅ **保留持仓变更记录**(简化版,不记录交易细节) +2. ✅ **保留资金变动记录**(用于计算 IRR) +3. ✅ **每日资产快照**(用于计算时间加权收益率) +4. ✅ **持仓思考记录**(用于计划和复盘) + +**这样既简化了操作,又保证了核心功能的实现。** + +--- + +## 五、最终建议 + +### 5.1 核心设计原则 + +1. **被动变更 = 系统自动** + - 分红、拆股、送股等由系统自动处理 + - 用户只需确认或触发 + +2. **主动变更 = 直接修改持仓** + - 买入/卖出:直接修改成本价和份数 + - 可选:记录变更备注/思考 + - 系统记录变更历史(简化版) + +3. **资金变动 = 单独记录** + - 记录资金投入/提取(不是交易) + - 用于计算 IRR + - 操作简单(只需记录日期和金额) + +4. **思考记录 = 关联持仓** + - 不是关联交易,而是关联持仓 + - 支持时间线展示 + - 支持计划和复盘 + +### 5.2 数据模型建议 + +**核心表:** +- `positions` - 持仓表(用户直接修改) +- `position_changes` - 持仓变更记录(系统自动记录) +- `cash_flows` - 资金变动记录(用户记录投入/提取) +- `daily_snapshots` - 每日资产快照(系统自动生成) +- `position_thoughts` - 持仓思考记录(用户记录思考) + +**移除或简化:** +- ~~`transactions`~~ - 不再需要详细的交易记录表 +- 或者保留但改为可选(高级用户可以使用) + +### 5.3 用户体验优化 + +1. **提供成本价计算器** + - 用户输入多笔买入价格和份额 + - 系统计算加权平均成本价 + - 用户可以直接复制使用 + +2. **支持批量导入** + - 从券商系统导出持仓 + - 批量导入到系统 + - 减少手动输入 + +3. **变更历史展示** + - 展示持仓变更时间线 + - 显示每次变更的成本价和份数变化 + - 支持添加思考/备注 + +4. **智能提示** + - 成本价变化异常时提示 + - 份数变化异常时提示 + - 帮助用户发现输入错误 + +--- + +## 六、结论 + +### 6.1 方案评估结果 + +**✅ 新设计(直接修改持仓)更适合您的场景** + +**理由:** +1. ✅ 操作简单,符合多券商汇总场景 +2. ✅ 核心功能(收益计算)可以通过资产快照和资金流实现 +3. ✅ 计划和复盘功能可以通过持仓思考记录实现 +4. ✅ 用户体验更好,上手更快 + +**需要注意:** +1. ⚠️ 需要补充资金变动记录(用于 IRR 计算) +2. ⚠️ 需要每日资产快照(用于时间加权收益率) +3. ⚠️ 需要持仓变更记录(用于追溯和思考关联) + +### 6.2 实施建议 + +**第一阶段:核心功能** +- 持仓直接编辑(成本价、份数) +- 持仓变更记录 +- 每日资产快照 +- 基础收益计算 + +**第二阶段:完善功能** +- 资金变动记录 +- 时间加权收益率和 IRR 计算 +- 持仓思考记录 + +**第三阶段:高级功能** +- 计划和复盘 +- 批量导入 +- 成本价计算器 + +--- + +**文档版本**:v1.0 +**创建日期**:2024年 +**评估结论**:✅ 推荐采用新设计,但需要补充资金流和快照功能 +