feat: 更新设计文档
This commit is contained in:
18
我编写的文档/投资记录-产品设计.md
Normal file
18
我编写的文档/投资记录-产品设计.md
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# 投资记录模块-产品设计
|
||||||
|
## 核心设计思路
|
||||||
|
1. 把所有的表更分为两种:`用户驱动(主动)` 和 `系统驱动(被动)`
|
||||||
|
2. 系统驱动包含:现金分红、送股、拆股、汇率变动等
|
||||||
|
3. 用户驱动包含:初始买入、追加买入、卖出;
|
||||||
|
4. 所有的用户驱动,都只需要变更最终的持仓成本和最新的持仓份额,这两项。避免其他复杂的操作
|
||||||
|
5. 每次用户主动变更,记录成本价和份额的同时,还需要完成如下记录:
|
||||||
|
- 反向计算本次交易股价和份额,并记录;
|
||||||
|
- 统计最新的份额和净值,并记录
|
||||||
|
- 同时引导填下投资复盘和思考。
|
||||||
|
6. 系统驱动的变更:(万一无法实现,可以降级为用户驱动变更)
|
||||||
|
- 分红:收盘后获取每股分红金额,最新成本价=原成本价 - 分红,市场价逻辑保持不变(使用不复权的股价)
|
||||||
|
- 送股、拆股等都变更最新的成本价和份额,并记录。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 想法
|
||||||
|
- 私密分享:可以将自己的交易计划和复盘,通过小程序私密分享-分享给其他人,这样即保障了裂变属性,有增加了隐私安全。
|
||||||
865
机生文档/NestJS与PostgreSQL集成方案.md
Normal file
865
机生文档/NestJS与PostgreSQL集成方案.md
Normal file
@@ -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<string, any>;
|
||||||
|
|
||||||
|
@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<Transaction>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async create(createTransactionDto: CreateTransactionDto): Promise<Transaction> {
|
||||||
|
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<Transaction> {
|
||||||
|
return await this.transactionRepository.findOne({
|
||||||
|
where: { id, userId },
|
||||||
|
relations: ['account', 'position', 'tradingPlan'],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async findByDateRange(
|
||||||
|
userId: number,
|
||||||
|
startDate: Date,
|
||||||
|
endDate: Date,
|
||||||
|
): Promise<Transaction[]> {
|
||||||
|
return await this.transactionRepository.find({
|
||||||
|
where: {
|
||||||
|
userId,
|
||||||
|
date: Between(startDate, endDate),
|
||||||
|
},
|
||||||
|
order: { date: 'DESC' },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(id: number, userId: number, updateData: Partial<Transaction>) {
|
||||||
|
await this.transactionRepository.update({ id, userId }, updateData);
|
||||||
|
return this.findOne(id, userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
async remove(id: number, userId: number): Promise<void> {
|
||||||
|
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<Transaction>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
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<void> {
|
||||||
|
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<void> {
|
||||||
|
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年
|
||||||
|
|
||||||
1527
机生文档/PostgreSQL使用文档.md
Normal file
1527
机生文档/PostgreSQL使用文档.md
Normal file
File diff suppressed because it is too large
Load Diff
409
机生文档/产品需求文档-去除空格.md
Normal file
409
机生文档/产品需求文档-去除空格.md
Normal file
@@ -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月*
|
||||||
|
|
||||||
|
*文档状态:待评审*
|
||||||
@@ -1,11 +1,8 @@
|
|||||||
# 思投录 (VestMind) 产品需求文档
|
|
||||||
|
|
||||||
## 1. 产品概述
|
## 1. 产品概述
|
||||||
|
|
||||||
### 1.1 产品定位
|
### 1.1 产品定位
|
||||||
**产品名称**:思投录 (VestMind)
|
**产品名称**:思投录 (VestMind)
|
||||||
**产品定位**:投资决策与复盘工具
|
**产品定位**:投资决策与复盘工具
|
||||||
**产品愿景**:让每笔投资都经得起思考
|
**产品愿景**:让每笔投资都经得起思考
|
||||||
**目标用户**:个人投资者、价值投资者、投资新手
|
**目标用户**:个人投资者、价值投资者、投资新手
|
||||||
|
|
||||||
### 1.2 产品价值
|
### 1.2 产品价值
|
||||||
@@ -21,7 +18,6 @@
|
|||||||
- **数据驱动**:基于数据做决策
|
- **数据驱动**:基于数据做决策
|
||||||
|
|
||||||
## 2. 功能架构
|
## 2. 功能架构
|
||||||
|
|
||||||
### 2.1 核心功能模块
|
### 2.1 核心功能模块
|
||||||
```
|
```
|
||||||
思投录
|
思投录
|
||||||
@@ -37,44 +33,40 @@
|
|||||||
- **P2**:高级工具、分享功能
|
- **P2**:高级工具、分享功能
|
||||||
|
|
||||||
## 3. 页面详细设计
|
## 3. 页面详细设计
|
||||||
|
|
||||||
### 3.1 首页/持仓页面
|
### 3.1 首页/持仓页面
|
||||||
|
|
||||||
#### 3.1.1 页面概述
|
#### 3.1.1 页面概述
|
||||||
用户进入应用后的主页面,展示整体投资概况和持仓详情。
|
用户进入应用后的主页面,展示整体投资概况和持仓详情。
|
||||||
|
|
||||||
#### 3.1.2 页面布局
|
#### 3.1.2 页面布局
|
||||||
```
|
```
|
||||||
┌─────────────────────────┐
|
┌─────────────────────────┐
|
||||||
│ 思投录 (顶部导航) │
|
│ 思投录 (顶部导航) │
|
||||||
│ 让每笔投资都经得起思考 │
|
│ 让每笔投资都经得起思考 │
|
||||||
├─────────────────────────┤
|
├─────────────────────────┤
|
||||||
│ 持仓概览卡片 │
|
│ 持仓概览卡片 │
|
||||||
│ ┌─────────────────────┐ │
|
│ ┌─────────────────────┐ │
|
||||||
│ │ 总资产: ¥128,450.00 │ │
|
│ │ 总资产: ¥128,450.00 │ │
|
||||||
│ │ 今日收益: +¥1,250 │ │
|
│ │ 今日收益: +¥1,250 │ │
|
||||||
│ │ 总收益率: +12.5% │ │
|
│ │ 总收益率: +12.5% │ │
|
||||||
│ └─────────────────────┘ │
|
│ └─────────────────────┘ │
|
||||||
├─────────────────────────┤
|
├─────────────────────────┤
|
||||||
│ 持仓列表 │
|
│ 持仓列表 │
|
||||||
│ ┌─────────────────────┐ │
|
│ ┌─────────────────────┐ │
|
||||||
│ │ 贵州茅台 600519 │ │
|
│ │ 贵州茅台 600519 │ │
|
||||||
│ │ 100股 ¥1,850.00 │ │
|
│ │ 100股 ¥1,850.00 │ │
|
||||||
│ │ +¥2,500.00 (+15.6%) │ │
|
│ │ +¥2,500.00 (+15.6%) │ │
|
||||||
│ └─────────────────────┘ │
|
│ └─────────────────────┘ │
|
||||||
│ ┌─────────────────────┐ │
|
│ ┌─────────────────────┐ │
|
||||||
│ │ 腾讯控股 00700 │ │
|
│ │ 腾讯控股 00700 │ │
|
||||||
│ │ 200股 ¥320.00 │ │
|
│ │ 200股 ¥320.00 │ │
|
||||||
│ │ -¥800.00 (-1.2%) │ │
|
│ │ -¥800.00 (-1.2%) │ │
|
||||||
│ └─────────────────────┘ │
|
│ └─────────────────────┘ │
|
||||||
├─────────────────────────┤
|
├─────────────────────────┤
|
||||||
│ 底部导航栏 │
|
│ 底部导航栏 │
|
||||||
│ [持仓] [计划] [记录] [我的] │
|
│ [持仓] [计划] [记录] [我的] │
|
||||||
└─────────────────────────┘
|
└─────────────────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 3.1.3 功能需求
|
#### 3.1.3 功能需求
|
||||||
|
|
||||||
**持仓概览卡片**
|
**持仓概览卡片**
|
||||||
- 显示总资产金额
|
- 显示总资产金额
|
||||||
- 显示今日收益(正负用颜色区分)
|
- 显示今日收益(正负用颜色区分)
|
||||||
@@ -102,48 +94,46 @@
|
|||||||
- 滑动删除持仓(需确认)
|
- 滑动删除持仓(需确认)
|
||||||
|
|
||||||
### 3.2 交易计划页面
|
### 3.2 交易计划页面
|
||||||
|
|
||||||
#### 3.2.1 页面概述
|
#### 3.2.1 页面概述
|
||||||
帮助用户制定和执行交易计划,实现"计划你的交易,交易你的计划"。
|
帮助用户制定和执行交易计划,实现"计划你的交易,交易你的计划"。
|
||||||
|
|
||||||
#### 3.2.2 页面布局
|
#### 3.2.2 页面布局
|
||||||
```
|
```
|
||||||
┌─────────────────────────┐
|
┌─────────────────────────┐
|
||||||
│ 交易计划 (顶部导航) │
|
│ 交易计划 (顶部导航) │
|
||||||
│ [+ 新建] │
|
│ [+ 新建] │
|
||||||
├─────────────────────────┤
|
├─────────────────────────┤
|
||||||
│ 计划列表 │
|
│ 计划列表 │
|
||||||
│ ┌─────────────────────┐ │
|
│ ┌─────────────────────┐ │
|
||||||
│ │ 招商银行 600036 │ │
|
│ │ 招商银行 600036 │ │
|
||||||
│ │ 状态: 进行中 │ │
|
│ │ 状态: 进行中 │ │
|
||||||
│ │ 目标价格: ¥45.00 │ │
|
│ │ 目标价格: ¥45.00 │ │
|
||||||
│ │ 计划金额: ¥10,000 │ │
|
│ │ 计划金额: ¥10,000 │ │
|
||||||
│ │ 截止时间: 2024-03-15│ │
|
│ │ 截止时间: 2024-03-15│ │
|
||||||
│ │ ████████░░ 60% │ │
|
│ │ ████████░░ 60% │ │
|
||||||
│ └─────────────────────┘ │
|
│ └─────────────────────┘ │
|
||||||
│ ┌─────────────────────┐ │
|
│ ┌─────────────────────┐ │
|
||||||
│ │ 中国平安 601318 │ │
|
│ │ 中国平安 601318 │ │
|
||||||
│ │ 状态: 已完成 │ │
|
│ │ 状态: 已完成 │ │
|
||||||
│ │ 目标价格: ¥55.00 │ │
|
│ │ 目标价格: ¥55.00 │ │
|
||||||
│ │ 计划金额: ¥15,000 │ │
|
│ │ 计划金额: ¥15,000 │ │
|
||||||
│ │ 截止时间: 2024-04-20│ │
|
│ │ 截止时间: 2024-04-20│ │
|
||||||
│ │ ██████████ 100% │ │
|
│ │ ██████████ 100% │ │
|
||||||
│ └─────────────────────┘ │
|
│ └─────────────────────┘ │
|
||||||
├─────────────────────────┤
|
├─────────────────────────┤
|
||||||
│ 底部导航栏 │
|
│ 底部导航栏 │
|
||||||
│ [持仓] [计划] [记录] [我的] │
|
│ [持仓] [计划] [记录] [我的] │
|
||||||
└─────────────────────────┘
|
└─────────────────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 3.2.3 功能需求
|
#### 3.2.3 功能需求
|
||||||
|
|
||||||
**计划列表**
|
**计划列表**
|
||||||
- 显示计划状态(进行中/已完成/已取消)
|
- 显示计划状态(进行中/已完成/已取消)
|
||||||
- 显示股票信息、目标价格
|
- 显示股票信息、目标价格
|
||||||
- 显示计划金额、截止时间
|
- 显示计划金额、截止时间
|
||||||
- 显示完成进度条
|
- 显示完成进度条
|
||||||
- 支持点击查看计划详情
|
- 支持点击查看计划详情
|
||||||
|
|
||||||
**新建计划功能**
|
**新建计划功能**
|
||||||
- 股票选择(名称、代码)
|
- 股票选择(名称、代码)
|
||||||
- 市场选择(A股/港股/美股)
|
- 市场选择(A股/港股/美股)
|
||||||
@@ -157,7 +147,7 @@
|
|||||||
- 到达目标价格提醒
|
- 到达目标价格提醒
|
||||||
- 支持手动标记完成
|
- 支持手动标记完成
|
||||||
- 支持从计划跳转到交易记录
|
- 支持从计划跳转到交易记录
|
||||||
- 支持修改计划参数
|
- 支持修改计划参数
|
||||||
|
|
||||||
#### 3.2.4 交互需求
|
#### 3.2.4 交互需求
|
||||||
- 点击计划项查看详情
|
- 点击计划项查看详情
|
||||||
@@ -166,52 +156,48 @@
|
|||||||
- 支持计划搜索和筛选
|
- 支持计划搜索和筛选
|
||||||
|
|
||||||
### 3.3 交易记录/复盘页面
|
### 3.3 交易记录/复盘页面
|
||||||
|
|
||||||
#### 3.3.1 页面概述
|
#### 3.3.1 页面概述
|
||||||
记录每笔交易的详细信息,通过时间线展示交易历史和思考过程。
|
记录每笔交易的详细信息,通过时间线展示交易历史和思考过程。
|
||||||
|
|
||||||
#### 3.3.2 页面布局
|
#### 3.3.2 页面布局
|
||||||
```
|
```
|
||||||
┌─────────────────────────┐
|
┌─────────────────────────┐
|
||||||
│ 交易记录 (顶部导航) │
|
│ 交易记录 (顶部导航) │
|
||||||
│ [+ 记录] │
|
│ [+ 记录] │
|
||||||
├─────────────────────────┤
|
├─────────────────────────┤
|
||||||
│ 时间线 │
|
│ 时间线 │
|
||||||
│ ┌─────────────────────┐ │
|
│ ┌─────────────────────┐ │
|
||||||
│ │ 2024-01-15 │ │
|
│ │ 2024-01-15 │ │
|
||||||
│ │ ┌─────────────────┐ │ │
|
│ │ ┌─────────────────┐ │ │
|
||||||
│ │ │ 贵州茅台 600519 │ │ │
|
│ │ │ 贵州茅台 600519 │ │ │
|
||||||
│ │ │ 买入 100股 │ │ │
|
│ │ │ 买入 100股 │ │ │
|
||||||
│ │ │ 价格: ¥1,600.00 │ │ │
|
│ │ │ 价格: ¥1,600.00 │ │ │
|
||||||
│ │ │ ┌─────────────┐ │ │ │
|
│ │ │ ┌─────────────┐ │ │ │
|
||||||
│ │ │ │ 交易思考: │ │ │ │
|
│ │ │ │ 交易思考: │ │ │ │
|
||||||
│ │ │ │ 基于茅台品牌 │ │ │ │
|
│ │ │ │ 基于茅台品牌 │ │ │ │
|
||||||
│ │ │ │ 价值和长期增 │ │ │ │
|
│ │ │ │ 价值和长期增 │ │ │ │
|
||||||
│ │ │ │ 长潜力... │ │ │ │
|
│ │ │ │ 长潜力... │ │ │ │
|
||||||
│ │ │ └─────────────┘ │ │ │
|
│ │ │ └─────────────┘ │ │ │
|
||||||
│ │ └─────────────────┘ │ │
|
│ │ └─────────────────┘ │ │
|
||||||
│ └─────────────────────┘ │
|
│ └─────────────────────┘ │
|
||||||
│ ┌─────────────────────┐ │
|
│ ┌─────────────────────┐ │
|
||||||
│ │ 2024-01-10 │ │
|
│ │ 2024-01-10 │ │
|
||||||
│ │ ┌─────────────────┐ │ │
|
│ │ ┌─────────────────┐ │ │
|
||||||
│ │ │ 比亚迪 002594 │ │ │
|
│ │ │ 比亚迪 002594 │ │ │
|
||||||
│ │ │ 卖出 200股 │ │ │
|
│ │ │ 卖出 200股 │ │ │
|
||||||
│ │ │ 价格: ¥280.00 │ │ │
|
│ │ │ 价格: ¥280.00 │ │ │
|
||||||
│ │ │ ┌─────────────┐ │ │ │
|
│ │ │ ┌─────────────┐ │ │ │
|
||||||
│ │ │ │ 交易思考: │ │ │ │
|
│ │ │ │ 交易思考: │ │ │ │
|
||||||
│ │ │ │ 新能源汽车行 │ │ │ │
|
│ │ │ │ 新能源汽车行 │ │ │ │
|
||||||
│ │ │ │ 业竞争加剧...│ │ │ │
|
│ │ │ │ 业竞争加剧...│ │ │ │
|
||||||
│ │ │ └─────────────┘ │ │ │
|
│ │ │ └─────────────┘ │ │ │
|
||||||
│ │ └─────────────────┘ │ │
|
│ │ └─────────────────┘ │ │
|
||||||
│ └─────────────────────┘ │
|
│ └─────────────────────┘ │
|
||||||
├─────────────────────────┤
|
├─────────────────────────┤
|
||||||
│ 底部导航栏 │
|
│ 底部导航栏 │
|
||||||
│ [持仓] [计划] [记录] [我的] │
|
│ [持仓] [计划] [记录] [我的] │
|
||||||
└─────────────────────────┘
|
└─────────────────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 3.3.3 功能需求
|
#### 3.3.3 功能需求
|
||||||
|
|
||||||
**时间线展示**
|
**时间线展示**
|
||||||
- 按时间倒序显示交易记录
|
- 按时间倒序显示交易记录
|
||||||
- 显示交易日期
|
- 显示交易日期
|
||||||
@@ -226,18 +212,18 @@
|
|||||||
- 交易价格输入
|
- 交易价格输入
|
||||||
- 交易思考记录(必填)
|
- 交易思考记录(必填)
|
||||||
- 支持从计划跳转记录
|
- 支持从计划跳转记录
|
||||||
|
|
||||||
**复盘功能**
|
**复盘功能**
|
||||||
- 定期弹出复盘提醒
|
- 定期弹出复盘提醒
|
||||||
- 支持为历史交易添加复盘
|
- 支持为历史交易添加复盘
|
||||||
- 复盘内容记录
|
- 复盘内容记录
|
||||||
- 复盘时间记录
|
- 复盘时间记录
|
||||||
|
|
||||||
**分享功能**(可选)
|
**分享功能**(可选)
|
||||||
- 支持分享单笔交易
|
- 支持分享单笔交易
|
||||||
- 支持分享交易时间线
|
- 支持分享交易时间线
|
||||||
- 隐私设置控制
|
- 隐私设置控制
|
||||||
|
|
||||||
#### 3.3.4 交互需求
|
#### 3.3.4 交互需求
|
||||||
- 点击交易记录查看详情
|
- 点击交易记录查看详情
|
||||||
- 长按显示操作菜单(编辑/删除/复盘)
|
- 长按显示操作菜单(编辑/删除/复盘)
|
||||||
@@ -246,53 +232,50 @@
|
|||||||
- 支持按时间范围筛选
|
- 支持按时间范围筛选
|
||||||
|
|
||||||
### 3.4 我的工具页面
|
### 3.4 我的工具页面
|
||||||
|
|
||||||
#### 3.4.1 页面概述
|
#### 3.4.1 页面概述
|
||||||
提供各种投资工具和计算器,帮助用户做出更好的投资决策。
|
提供各种投资工具和计算器,帮助用户做出更好的投资决策。
|
||||||
|
|
||||||
#### 3.4.2 页面布局
|
#### 3.4.2 页面布局
|
||||||
```
|
```
|
||||||
┌─────────────────────────┐
|
┌─────────────────────────┐
|
||||||
│ 我的工具 (顶部导航) │
|
│ 我的工具 (顶部导航) │
|
||||||
├─────────────────────────┤
|
├─────────────────────────┤
|
||||||
│ 工具网格 │
|
│ 工具网格 │
|
||||||
│ ┌─────────┐ ┌─────────┐ │
|
│ ┌─────────┐ ┌─────────┐ │
|
||||||
│ │ ✅ │ │ 🧮 │ │
|
│ │ ✅ │ │ 🧮 │ │
|
||||||
│ │投资检查清单│ │复利计算器│ │
|
│ │投资检查清单│ │复利计算器│ │
|
||||||
│ │买入卖出检查│ │计算未来收益│ │
|
│ │买入卖出检查│ │计算未来收益│ │
|
||||||
│ └─────────┘ └─────────┘ │
|
│ └─────────┘ └─────────┘ │
|
||||||
│ ┌─────────┐ ┌─────────┐ │
|
│ ┌─────────┐ ┌─────────┐ │
|
||||||
│ │ 📈 │ │ 🎯 │ │
|
│ │ 📈 │ │ 🎯 │ │
|
||||||
│ │ 估值工具 │ │ 自由目标 │ │
|
│ │ 估值工具 │ │ 自由目标 │ │
|
||||||
│ │企业价值评估│ │财务自由规划│ │
|
│ │企业价值评估│ │财务自由规划│ │
|
||||||
│ └─────────┘ └─────────┘ │
|
│ └─────────┘ └─────────┘ │
|
||||||
├─────────────────────────┤
|
├─────────────────────────┤
|
||||||
│ 用户信息 │
|
│ 用户信息 │
|
||||||
│ ┌─────────────────────┐ │
|
│ ┌─────────────────────┐ │
|
||||||
│ │ 头像 | 用户名 │ │
|
│ │ 头像 | 用户名 │ │
|
||||||
│ │ 投资天数: 365天 │ │
|
│ │ 投资天数: 365天 │ │
|
||||||
│ │ 总交易次数: 25次 │ │
|
│ │ 总交易次数: 25次 │ │
|
||||||
│ └─────────────────────┘ │
|
│ └─────────────────────┘ │
|
||||||
├─────────────────────────┤
|
├─────────────────────────┤
|
||||||
│ 底部导航栏 │
|
│ 底部导航栏 │
|
||||||
│ [持仓] [计划] [记录] [我的] │
|
│ [持仓] [计划] [记录] [我的] │
|
||||||
└─────────────────────────┘
|
└─────────────────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 3.4.3 功能需求
|
#### 3.4.3 功能需求
|
||||||
|
|
||||||
**投资检查清单**
|
**投资检查清单**
|
||||||
- 买入检查清单
|
- 买入检查清单
|
||||||
- 企业基本面是否优秀?
|
- 企业基本面是否优秀?
|
||||||
- 估值是否合理?
|
- 估值是否合理?
|
||||||
- 行业前景如何?
|
- 行业前景如何?
|
||||||
- 管理层是否可信?
|
- 管理层是否可信?
|
||||||
- 现金流是否健康?
|
- 现金流是否健康?
|
||||||
- 卖出检查清单
|
- 卖出检查清单
|
||||||
- 基本面是否恶化?
|
- 基本面是否恶化?
|
||||||
- 估值是否过高?
|
- 估值是否过高?
|
||||||
- 是否有更好的投资机会?
|
- 是否有更好的投资机会?
|
||||||
- 是否需要资金配置?
|
- 是否需要资金配置?
|
||||||
- 支持在创建交易计划时自动弹出
|
- 支持在创建交易计划时自动弹出
|
||||||
|
|
||||||
**复利计算器**
|
**复利计算器**
|
||||||
@@ -305,15 +288,15 @@
|
|||||||
|
|
||||||
**估值工具**
|
**估值工具**
|
||||||
- 老唐估值法
|
- 老唐估值法
|
||||||
- 净利润输入
|
- 净利润输入
|
||||||
- 无风险收益率设置
|
- 无风险收益率设置
|
||||||
- 合理PE倍数计算
|
- 合理PE倍数计算
|
||||||
- 估值结果输出
|
- 估值结果输出
|
||||||
- 现金流折现法
|
- 现金流折现法
|
||||||
- 自由现金流输入
|
- 自由现金流输入
|
||||||
- 增长率设置
|
- 增长率设置
|
||||||
- 折现率设置
|
- 折现率设置
|
||||||
- 估值结果输出
|
- 估值结果输出
|
||||||
|
|
||||||
**自由目标**
|
**自由目标**
|
||||||
- 目标资产设置
|
- 目标资产设置
|
||||||
@@ -330,7 +313,6 @@
|
|||||||
- 支持历史记录查看
|
- 支持历史记录查看
|
||||||
|
|
||||||
## 4. 技术需求
|
## 4. 技术需求
|
||||||
|
|
||||||
### 4.1 平台支持
|
### 4.1 平台支持
|
||||||
- **移动端APP**:iOS、Android
|
- **移动端APP**:iOS、Android
|
||||||
- **小程序**:微信小程序
|
- **小程序**:微信小程序
|
||||||
@@ -347,7 +329,6 @@
|
|||||||
- 支持离线使用基础功能
|
- 支持离线使用基础功能
|
||||||
|
|
||||||
## 5. 用户体验需求
|
## 5. 用户体验需求
|
||||||
|
|
||||||
### 5.1 易用性
|
### 5.1 易用性
|
||||||
- 界面简洁直观
|
- 界面简洁直观
|
||||||
- 操作流程简单
|
- 操作流程简单
|
||||||
@@ -359,7 +340,7 @@
|
|||||||
- 支持颜色对比度调节
|
- 支持颜色对比度调节
|
||||||
- 支持语音输入
|
- 支持语音输入
|
||||||
- 支持键盘导航
|
- 支持键盘导航
|
||||||
|
|
||||||
### 5.3 个性化
|
### 5.3 个性化
|
||||||
- 主题颜色自定义
|
- 主题颜色自定义
|
||||||
- 功能模块自定义
|
- 功能模块自定义
|
||||||
@@ -367,7 +348,6 @@
|
|||||||
- 数据展示个性化
|
- 数据展示个性化
|
||||||
|
|
||||||
## 6. 安全需求
|
## 6. 安全需求
|
||||||
|
|
||||||
### 6.1 数据安全
|
### 6.1 数据安全
|
||||||
- 本地数据加密存储
|
- 本地数据加密存储
|
||||||
- 网络传输加密
|
- 网络传输加密
|
||||||
@@ -381,7 +361,6 @@
|
|||||||
- 安全日志记录
|
- 安全日志记录
|
||||||
|
|
||||||
## 7. 运营需求
|
## 7. 运营需求
|
||||||
|
|
||||||
### 7.1 数据统计
|
### 7.1 数据统计
|
||||||
- 用户行为分析
|
- 用户行为分析
|
||||||
- 功能使用统计
|
- 功能使用统计
|
||||||
@@ -395,7 +374,6 @@
|
|||||||
- 功能建议收集
|
- 功能建议收集
|
||||||
|
|
||||||
## 8. 开发计划
|
## 8. 开发计划
|
||||||
|
|
||||||
### 8.1 版本规划
|
### 8.1 版本规划
|
||||||
- **V1.0**:基础功能(持仓、记录)
|
- **V1.0**:基础功能(持仓、记录)
|
||||||
- **V1.1**:交易计划功能
|
- **V1.1**:交易计划功能
|
||||||
@@ -408,9 +386,8 @@
|
|||||||
- 开发实现:8周
|
- 开发实现:8周
|
||||||
- 测试优化:2周
|
- 测试优化:2周
|
||||||
- 上线发布:1周
|
- 上线发布:1周
|
||||||
|
|
||||||
## 9. 成功指标
|
## 9. 成功指标
|
||||||
|
|
||||||
### 9.1 用户指标
|
### 9.1 用户指标
|
||||||
- 日活跃用户数
|
- 日活跃用户数
|
||||||
- 用户留存率
|
- 用户留存率
|
||||||
@@ -425,6 +402,8 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
*本文档版本:V1.0*
|
*本文档版本:V1.0*
|
||||||
*最后更新:2024年1月*
|
|
||||||
*文档状态:待评审*
|
*最后更新:2024年1月*
|
||||||
|
|
||||||
|
*文档状态:待评审*
|
||||||
11
机生文档/命名问题.md
Normal file
11
机生文档/命名问题.md
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# 应用如何命名
|
||||||
|
## 参考命名
|
||||||
|
- 投资复盘笔记
|
||||||
|
|
||||||
|
|
||||||
|
## 审核是描述
|
||||||
|
这只是一个个人投资交易的记录与复盘工具
|
||||||
|
|
||||||
|
考虑更稳妥的命名:如果希望最大化降低审核风险,可以考虑对名称进行微调,使其更偏向“个人工具”属性,同时保留核心含义。例如:
|
||||||
|
- 增加个人化前缀/后缀:比如“我的复盘笔记”、“知行投资笔记”。
|
||||||
|
- 使用更中性的词汇:例如“资产轨迹日记”、“收益账本与思考”。
|
||||||
732
机生文档/投资收益记录系统设计.md
Normal file
732
机生文档/投资收益记录系统设计.md
Normal file
@@ -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年
|
||||||
|
|
||||||
832
机生文档/数据库设计文档.md
Normal file
832
机生文档/数据库设计文档.md
Normal file
@@ -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+
|
||||||
|
|
||||||
419
机生文档/设计优化评估-简化交易记录.md
Normal file
419
机生文档/设计优化评估-简化交易记录.md
Normal file
@@ -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年
|
||||||
|
**评估结论**:✅ 推荐采用新设计,但需要补充资金流和快照功能
|
||||||
|
|
||||||
Reference in New Issue
Block a user