trading-platform/docs/02-definicion-modulos/OQI-004-investment-accounts/especificaciones/ET-INV-002-api.md
rckrdmrd c1b5081208 feat(ml): Complete FASE 11 - BTCUSD update and comprehensive documentation alignment
ML Engine Updates:
- Updated BTCUSD with Polygon API data (2024-2025): 215,699 new records
- Re-trained all ML models: Attention (R²: 0.223), Base, Metamodel (87.3% confidence)
- Backtest results: +176.71R profit with aggressive_filter strategy

Documentation Consolidation:
- Created docs/99-analisis/_MAP.md index with 13 new analysis documents
- Consolidated inventories: removed duplicates from orchestration/inventarios/
- Updated ML_INVENTORY.yml with BTCUSD metrics and training results
- Added execution reports: FASE11-BTCUSD, correction issues, alignment validation

Architecture & Integration:
- Updated all module documentation with NEXUS v3.4 frontmatter
- Fixed _MAP.md indexes across all folders
- Updated orchestration plans and traces

Files: 229 changed, 5064 insertions(+), 1872 deletions(-)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-07 09:31:29 -06:00

1293 lines
34 KiB
Markdown

---
id: "ET-INV-002"
title: "API REST Investment Accounts"
type: "Technical Specification"
status: "Done"
priority: "Alta"
epic: "OQI-004"
project: "trading-platform"
version: "1.0.0"
created_date: "2025-12-05"
updated_date: "2026-01-04"
---
# ET-INV-002: API REST Investment Accounts
**Epic:** OQI-004 Cuentas de Inversión
**Versión:** 1.0
**Fecha:** 2025-12-05
**Responsable:** Requirements-Analyst
---
## 1. Descripción
Define los endpoints REST para la gestión completa de cuentas de inversión:
- CRUD de productos de inversión
- Gestión de cuentas de inversión
- Depósitos y retiros
- Consulta de portfolio y performance
- Administración de solicitudes de retiro
---
## 2. Arquitectura de API
```
┌─────────────────────────────────────────────────────────────────┐
│ Investment API Layer │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Routes │────►│ Controllers │────►│ Services │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │ │ │ │
│ │ │ ▼ │
│ │ │ ┌──────────────┐ │
│ │ │ │ DB │ │
│ │ │ │ (Postgres) │ │
│ │ │ └──────────────┘ │
│ │ │ │
│ │ ▼ │
│ │ ┌──────────────┐ │
│ │ │ Middlewares │ │
│ │ │ - Auth │ │
│ │ │ - Validate │ │
│ │ │ - RateLimit │ │
│ │ └──────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ Integration │ │
│ │ - Stripe │ │
│ │ - ML Engine │ │
│ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
```
---
## 3. Endpoints Especificados
### 3.1 Productos de Inversión
#### GET `/api/v1/investment/products`
Lista todos los productos de inversión disponibles.
**Request:**
```http
GET /api/v1/investment/products
Authorization: Bearer {token}
Query Parameters:
- status: string (optional) - 'active', 'paused', 'closed'
- agent_type: string (optional) - 'swing', 'day', 'scalping', 'arbitrage'
- risk_level: string (optional) - 'low', 'medium', 'high', 'very_high'
- limit: number (optional, default: 20)
- offset: number (optional, default: 0)
```
**Response 200:**
```json
{
"success": true,
"data": {
"products": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Swing Trader Pro",
"description": "Agente de swing trading con estrategias de mediano plazo",
"agent_type": "swing",
"min_investment": 500.00,
"max_investment": null,
"performance_fee_percentage": 20.00,
"target_annual_return": 35.50,
"risk_level": "medium",
"status": "active",
"is_accepting_new_investors": true,
"total_aum": 125000.00,
"total_investors": 45,
"created_at": "2025-01-15T10:00:00Z",
"updated_at": "2025-01-20T14:30:00Z"
}
],
"pagination": {
"total": 5,
"limit": 20,
"offset": 0,
"has_more": false
}
}
}
```
**Errors:**
- `401 Unauthorized`: Token inválido o expirado
- `500 Internal Server Error`: Error en el servidor
---
#### GET `/api/v1/investment/products/:id`
Obtiene detalles de un producto específico.
**Request:**
```http
GET /api/v1/investment/products/550e8400-e29b-41d4-a716-446655440000
Authorization: Bearer {token}
```
**Response 200:**
```json
{
"success": true,
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Swing Trader Pro",
"description": "Agente de swing trading con estrategias de mediano plazo",
"agent_type": "swing",
"min_investment": 500.00,
"max_investment": null,
"performance_fee_percentage": 20.00,
"target_annual_return": 35.50,
"risk_level": "medium",
"ml_agent_id": "ml-agent-swing-001",
"ml_config": {
"trading_pairs": ["BTC/USD", "ETH/USD"],
"max_position_size": 0.1,
"stop_loss_percentage": 2.5
},
"status": "active",
"is_accepting_new_investors": true,
"total_aum": 125000.00,
"total_investors": 45,
"created_at": "2025-01-15T10:00:00Z",
"updated_at": "2025-01-20T14:30:00Z"
}
}
```
**Errors:**
- `404 Not Found`: Producto no encontrado
---
#### POST `/api/v1/investment/products` (Admin Only)
Crea un nuevo producto de inversión.
**Request:**
```http
POST /api/v1/investment/products
Authorization: Bearer {admin_token}
Content-Type: application/json
{
"name": "Scalping Expert",
"description": "Agente de scalping de alta frecuencia",
"agent_type": "scalping",
"min_investment": 2000.00,
"max_investment": 50000.00,
"performance_fee_percentage": 30.00,
"target_annual_return": 60.00,
"risk_level": "very_high",
"ml_agent_id": "ml-agent-scalping-001",
"ml_config": {
"timeframe": "1m",
"max_trades_per_day": 100
}
}
```
**Response 201:**
```json
{
"success": true,
"data": {
"id": "650e8400-e29b-41d4-a716-446655440001",
"name": "Scalping Expert",
"status": "active",
"created_at": "2025-01-21T09:00:00Z"
}
}
```
**Errors:**
- `400 Bad Request`: Validación fallida
- `403 Forbidden`: No es administrador
- `409 Conflict`: `ml_agent_id` ya existe
---
### 3.2 Cuentas de Inversión
#### GET `/api/v1/investment/accounts`
Lista las cuentas de inversión del usuario autenticado.
**Request:**
```http
GET /api/v1/investment/accounts
Authorization: Bearer {token}
Query Parameters:
- status: string (optional) - 'active', 'paused', 'closed'
- product_id: string (optional)
```
**Response 200:**
```json
{
"success": true,
"data": {
"accounts": [
{
"id": "750e8400-e29b-41d4-a716-446655440000",
"product_id": "550e8400-e29b-41d4-a716-446655440000",
"product_name": "Swing Trader Pro",
"current_balance": 5250.75,
"initial_investment": 5000.00,
"total_deposited": 5000.00,
"total_withdrawn": 0.00,
"total_profit_distributed": 250.75,
"total_return_percentage": 5.015,
"annualized_return_percentage": 35.10,
"status": "active",
"opened_at": "2025-01-10T08:00:00Z"
}
],
"summary": {
"total_invested": 5000.00,
"total_current_value": 5250.75,
"total_profit": 250.75,
"total_return_percentage": 5.015
}
}
}
```
---
#### GET `/api/v1/investment/accounts/:id`
Obtiene detalles de una cuenta específica.
**Request:**
```http
GET /api/v1/investment/accounts/750e8400-e29b-41d4-a716-446655440000
Authorization: Bearer {token}
```
**Response 200:**
```json
{
"success": true,
"data": {
"account": {
"id": "750e8400-e29b-41d4-a716-446655440000",
"product_id": "550e8400-e29b-41d4-a716-446655440000",
"current_balance": 5250.75,
"initial_investment": 5000.00,
"total_deposited": 5000.00,
"total_withdrawn": 0.00,
"total_profit_distributed": 250.75,
"total_return_percentage": 5.015,
"annualized_return_percentage": 35.10,
"status": "active",
"opened_at": "2025-01-10T08:00:00Z"
},
"product": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Swing Trader Pro",
"agent_type": "swing",
"risk_level": "medium",
"performance_fee_percentage": 20.00
},
"recent_transactions": [
{
"id": "850e8400-e29b-41d4-a716-446655440000",
"type": "deposit",
"amount": 5000.00,
"status": "completed",
"created_at": "2025-01-10T08:00:00Z"
}
]
}
}
```
**Errors:**
- `403 Forbidden`: Cuenta no pertenece al usuario
- `404 Not Found`: Cuenta no encontrada
---
#### POST `/api/v1/investment/accounts`
Crea una nueva cuenta de inversión (depósito inicial).
**Request:**
```http
POST /api/v1/investment/accounts
Authorization: Bearer {token}
Content-Type: application/json
{
"product_id": "550e8400-e29b-41d4-a716-446655440000",
"initial_investment": 5000.00,
"payment_method_id": "pm_1234567890abcdef"
}
```
**Response 201:**
```json
{
"success": true,
"data": {
"account_id": "750e8400-e29b-41d4-a716-446655440000",
"payment_intent": {
"id": "pi_1234567890abcdef",
"client_secret": "pi_1234567890abcdef_secret_xyz",
"status": "requires_confirmation"
}
}
}
```
**Errors:**
- `400 Bad Request`: Validación fallida (ej: monto menor a min_investment)
- `409 Conflict`: Usuario ya tiene cuenta en este producto
---
### 3.3 Depósitos
#### POST `/api/v1/investment/accounts/:id/deposit`
Realiza un depósito adicional a una cuenta existente.
**Request:**
```http
POST /api/v1/investment/accounts/750e8400-e29b-41d4-a716-446655440000/deposit
Authorization: Bearer {token}
Content-Type: application/json
{
"amount": 1000.00,
"payment_method_id": "pm_1234567890abcdef"
}
```
**Response 200:**
```json
{
"success": true,
"data": {
"transaction_id": "850e8400-e29b-41d4-a716-446655440001",
"payment_intent": {
"id": "pi_9876543210abcdef",
"client_secret": "pi_9876543210abcdef_secret_abc",
"status": "requires_confirmation"
}
}
}
```
**Errors:**
- `400 Bad Request`: Monto inválido
- `403 Forbidden`: Cuenta no pertenece al usuario
- `404 Not Found`: Cuenta no encontrada
- `409 Conflict`: Cuenta no está activa
---
### 3.4 Retiros
#### POST `/api/v1/investment/accounts/:id/withdraw`
Crea una solicitud de retiro.
**Request:**
```http
POST /api/v1/investment/accounts/750e8400-e29b-41d4-a716-446655440000/withdraw
Authorization: Bearer {token}
Content-Type: application/json
{
"amount": 500.00,
"withdrawal_method": "bank_transfer",
"destination_details": {
"bank_name": "Bank of America",
"account_number": "****1234",
"routing_number": "026009593",
"account_holder_name": "John Doe"
}
}
```
**Response 201:**
```json
{
"success": true,
"data": {
"withdrawal_request_id": "950e8400-e29b-41d4-a716-446655440000",
"status": "pending",
"amount": 500.00,
"estimated_processing_time": "2-5 business days",
"requested_at": "2025-01-21T10:00:00Z"
}
}
```
**Errors:**
- `400 Bad Request`: Monto excede balance disponible
- `403 Forbidden`: Cuenta no pertenece al usuario
- `409 Conflict`: Ya existe solicitud de retiro pendiente
---
#### GET `/api/v1/investment/withdrawal-requests`
Lista las solicitudes de retiro del usuario.
**Request:**
```http
GET /api/v1/investment/withdrawal-requests
Authorization: Bearer {token}
Query Parameters:
- status: string (optional) - 'pending', 'approved', 'rejected', 'completed'
- limit: number (optional, default: 20)
- offset: number (optional, default: 0)
```
**Response 200:**
```json
{
"success": true,
"data": {
"requests": [
{
"id": "950e8400-e29b-41d4-a716-446655440000",
"account_id": "750e8400-e29b-41d4-a716-446655440000",
"amount": 500.00,
"withdrawal_method": "bank_transfer",
"status": "pending",
"requested_at": "2025-01-21T10:00:00Z"
}
],
"pagination": {
"total": 1,
"limit": 20,
"offset": 0
}
}
}
```
---
### 3.5 Portfolio y Performance
#### GET `/api/v1/investment/portfolio`
Obtiene el resumen completo del portfolio del usuario.
**Request:**
```http
GET /api/v1/investment/portfolio
Authorization: Bearer {token}
```
**Response 200:**
```json
{
"success": true,
"data": {
"summary": {
"total_invested": 10000.00,
"total_current_value": 10850.50,
"total_profit": 850.50,
"total_return_percentage": 8.505,
"annualized_return_percentage": 42.50
},
"accounts": [
{
"account_id": "750e8400-e29b-41d4-a716-446655440000",
"product_name": "Swing Trader Pro",
"agent_type": "swing",
"current_balance": 5250.75,
"invested": 5000.00,
"profit": 250.75,
"return_percentage": 5.015,
"allocation_percentage": 48.40
},
{
"account_id": "750e8400-e29b-41d4-a716-446655440001",
"product_name": "Day Trader Elite",
"agent_type": "day",
"current_balance": 5599.75,
"invested": 5000.00,
"profit": 599.75,
"return_percentage": 11.995,
"allocation_percentage": 51.60
}
],
"allocation_by_risk": {
"low": 0.00,
"medium": 48.40,
"high": 51.60,
"very_high": 0.00
}
}
}
```
---
#### GET `/api/v1/investment/accounts/:id/performance`
Obtiene el historial de performance de una cuenta.
**Request:**
```http
GET /api/v1/investment/accounts/750e8400-e29b-41d4-a716-446655440000/performance
Authorization: Bearer {token}
Query Parameters:
- period: string - 'week', 'month', 'quarter', 'year', 'all'
- start_date: string (optional) - ISO 8601 date
- end_date: string (optional) - ISO 8601 date
```
**Response 200:**
```json
{
"success": true,
"data": {
"account_id": "750e8400-e29b-41d4-a716-446655440000",
"period": "month",
"performance": [
{
"date": "2025-01-10",
"opening_balance": 5000.00,
"closing_balance": 5025.50,
"daily_return": 25.50,
"daily_return_percentage": 0.51,
"cumulative_return": 25.50,
"cumulative_return_percentage": 0.51
},
{
"date": "2025-01-11",
"opening_balance": 5025.50,
"closing_balance": 5075.25,
"daily_return": 49.75,
"daily_return_percentage": 0.99,
"cumulative_return": 75.25,
"cumulative_return_percentage": 1.505
}
],
"statistics": {
"total_days": 30,
"winning_days": 22,
"losing_days": 8,
"best_day_return": 125.50,
"worst_day_return": -45.20,
"average_daily_return": 8.35,
"volatility": 2.15
}
}
}
```
---
### 3.6 Transacciones
#### GET `/api/v1/investment/transactions`
Lista todas las transacciones del usuario.
**Request:**
```http
GET /api/v1/investment/transactions
Authorization: Bearer {token}
Query Parameters:
- account_id: string (optional)
- type: string (optional) - 'deposit', 'withdrawal', 'profit_distribution', 'fee'
- status: string (optional) - 'pending', 'completed', 'failed', 'cancelled'
- start_date: string (optional)
- end_date: string (optional)
- limit: number (optional, default: 50)
- offset: number (optional, default: 0)
```
**Response 200:**
```json
{
"success": true,
"data": {
"transactions": [
{
"id": "850e8400-e29b-41d4-a716-446655440000",
"account_id": "750e8400-e29b-41d4-a716-446655440000",
"type": "deposit",
"status": "completed",
"amount": 5000.00,
"balance_before": 0.00,
"balance_after": 5000.00,
"created_at": "2025-01-10T08:00:00Z",
"processed_at": "2025-01-10T08:05:00Z"
}
],
"pagination": {
"total": 15,
"limit": 50,
"offset": 0
}
}
}
```
---
## 4. Implementación Backend
### 4.1 Estructura de Archivos
```
src/
├── modules/
│ └── investment/
│ ├── investment.routes.ts
│ ├── investment.controller.ts
│ ├── investment.service.ts
│ ├── investment.repository.ts
│ ├── investment.validators.ts
│ └── investment.types.ts
├── middlewares/
│ ├── auth.middleware.ts
│ ├── validate.middleware.ts
│ └── rate-limit.middleware.ts
└── utils/
├── errors.ts
└── response.ts
```
### 4.2 Routes
```typescript
// src/modules/investment/investment.routes.ts
import { Router } from 'express';
import { InvestmentController } from './investment.controller';
import { authenticate, requireAdmin } from '../../middlewares/auth.middleware';
import { validate } from '../../middlewares/validate.middleware';
import { rateLimit } from '../../middlewares/rate-limit.middleware';
import {
createProductSchema,
createAccountSchema,
depositSchema,
withdrawalSchema,
} from './investment.validators';
const router = Router();
const controller = new InvestmentController();
// Products
router.get('/products', authenticate, controller.getProducts);
router.get('/products/:id', authenticate, controller.getProductById);
router.post('/products', authenticate, requireAdmin, validate(createProductSchema), controller.createProduct);
router.patch('/products/:id', authenticate, requireAdmin, controller.updateProduct);
// Accounts
router.get('/accounts', authenticate, controller.getAccounts);
router.get('/accounts/:id', authenticate, controller.getAccountById);
router.post('/accounts', authenticate, validate(createAccountSchema), rateLimit(5, 3600), controller.createAccount);
// Deposits
router.post('/accounts/:id/deposit', authenticate, validate(depositSchema), rateLimit(10, 3600), controller.deposit);
// Withdrawals
router.post('/accounts/:id/withdraw', authenticate, validate(withdrawalSchema), rateLimit(3, 3600), controller.withdraw);
router.get('/withdrawal-requests', authenticate, controller.getWithdrawalRequests);
router.patch('/withdrawal-requests/:id', authenticate, requireAdmin, controller.updateWithdrawalRequest);
// Portfolio
router.get('/portfolio', authenticate, controller.getPortfolio);
router.get('/accounts/:id/performance', authenticate, controller.getPerformance);
// Transactions
router.get('/transactions', authenticate, controller.getTransactions);
export default router;
```
### 4.3 Controller
```typescript
// src/modules/investment/investment.controller.ts
import { Request, Response, NextFunction } from 'express';
import { InvestmentService } from './investment.service';
import { successResponse, errorResponse } from '../../utils/response';
export class InvestmentController {
private service: InvestmentService;
constructor() {
this.service = new InvestmentService();
}
getProducts = async (req: Request, res: Response, next: NextFunction) => {
try {
const { status, agent_type, risk_level, limit = 20, offset = 0 } = req.query;
const result = await this.service.getProducts({
status: status as string,
agent_type: agent_type as string,
risk_level: risk_level as string,
limit: Number(limit),
offset: Number(offset),
});
return successResponse(res, result, 200);
} catch (error) {
next(error);
}
};
getProductById = async (req: Request, res: Response, next: NextFunction) => {
try {
const { id } = req.params;
const product = await this.service.getProductById(id);
if (!product) {
return errorResponse(res, 'Product not found', 404);
}
return successResponse(res, product, 200);
} catch (error) {
next(error);
}
};
createAccount = async (req: Request, res: Response, next: NextFunction) => {
try {
const userId = req.user!.id;
const { product_id, initial_investment, payment_method_id } = req.body;
const result = await this.service.createAccount({
user_id: userId,
product_id,
initial_investment,
payment_method_id,
});
return successResponse(res, result, 201);
} catch (error) {
next(error);
}
};
deposit = async (req: Request, res: Response, next: NextFunction) => {
try {
const userId = req.user!.id;
const { id: accountId } = req.params;
const { amount, payment_method_id } = req.body;
const result = await this.service.deposit({
user_id: userId,
account_id: accountId,
amount,
payment_method_id,
});
return successResponse(res, result, 200);
} catch (error) {
next(error);
}
};
withdraw = async (req: Request, res: Response, next: NextFunction) => {
try {
const userId = req.user!.id;
const { id: accountId } = req.params;
const { amount, withdrawal_method, destination_details } = req.body;
const result = await this.service.createWithdrawalRequest({
user_id: userId,
account_id: accountId,
amount,
withdrawal_method,
destination_details,
});
return successResponse(res, result, 201);
} catch (error) {
next(error);
}
};
getPortfolio = async (req: Request, res: Response, next: NextFunction) => {
try {
const userId = req.user!.id;
const portfolio = await this.service.getPortfolio(userId);
return successResponse(res, portfolio, 200);
} catch (error) {
next(error);
}
};
getPerformance = async (req: Request, res: Response, next: NextFunction) => {
try {
const userId = req.user!.id;
const { id: accountId } = req.params;
const { period, start_date, end_date } = req.query;
const performance = await this.service.getPerformance({
user_id: userId,
account_id: accountId,
period: period as string,
start_date: start_date as string,
end_date: end_date as string,
});
return successResponse(res, performance, 200);
} catch (error) {
next(error);
}
};
// ... otros métodos
}
```
### 4.4 Service
```typescript
// src/modules/investment/investment.service.ts
import { InvestmentRepository } from './investment.repository';
import { StripeService } from '../payments/stripe.service';
import { AppError } from '../../utils/errors';
import { CreateAccountDto, DepositDto, WithdrawalDto } from './investment.types';
export class InvestmentService {
private repository: InvestmentRepository;
private stripeService: StripeService;
constructor() {
this.repository = new InvestmentRepository();
this.stripeService = new StripeService();
}
async getProducts(filters: any) {
const { products, total } = await this.repository.getProducts(filters);
return {
products,
pagination: {
total,
limit: filters.limit,
offset: filters.offset,
has_more: total > filters.offset + filters.limit,
},
};
}
async createAccount(data: CreateAccountDto) {
// Validar producto
const product = await this.repository.getProductById(data.product_id);
if (!product) {
throw new AppError('Product not found', 404);
}
if (!product.is_accepting_new_investors) {
throw new AppError('Product is not accepting new investors', 400);
}
// Validar monto mínimo
if (data.initial_investment < product.min_investment) {
throw new AppError(
`Minimum investment is ${product.min_investment}`,
400
);
}
// Validar monto máximo
if (
product.max_investment &&
data.initial_investment > product.max_investment
) {
throw new AppError(
`Maximum investment is ${product.max_investment}`,
400
);
}
// Verificar si ya existe cuenta
const existingAccount = await this.repository.getAccountByUserAndProduct(
data.user_id,
data.product_id
);
if (existingAccount) {
throw new AppError('Account already exists for this product', 409);
}
// Crear Payment Intent en Stripe
const paymentIntent = await this.stripeService.createPaymentIntent({
amount: data.initial_investment,
currency: 'usd',
payment_method: data.payment_method_id,
metadata: {
type: 'investment_deposit',
product_id: data.product_id,
user_id: data.user_id,
},
});
// Crear cuenta (pendiente de confirmación de pago)
const account = await this.repository.createAccount({
user_id: data.user_id,
product_id: data.product_id,
initial_investment: data.initial_investment,
});
// Crear transacción pendiente
await this.repository.createTransaction({
account_id: account.id,
user_id: data.user_id,
type: 'deposit',
amount: data.initial_investment,
balance_before: 0,
stripe_payment_intent_id: paymentIntent.id,
status: 'pending',
});
return {
account_id: account.id,
payment_intent: {
id: paymentIntent.id,
client_secret: paymentIntent.client_secret,
status: paymentIntent.status,
},
};
}
async deposit(data: DepositDto) {
// Validar cuenta
const account = await this.repository.getAccountById(data.account_id);
if (!account) {
throw new AppError('Account not found', 404);
}
if (account.user_id !== data.user_id) {
throw new AppError('Forbidden', 403);
}
if (account.status !== 'active') {
throw new AppError('Account is not active', 409);
}
// Crear Payment Intent
const paymentIntent = await this.stripeService.createPaymentIntent({
amount: data.amount,
currency: 'usd',
payment_method: data.payment_method_id,
metadata: {
type: 'investment_deposit',
account_id: data.account_id,
user_id: data.user_id,
},
});
// Crear transacción pendiente
const transaction = await this.repository.createTransaction({
account_id: data.account_id,
user_id: data.user_id,
type: 'deposit',
amount: data.amount,
balance_before: account.current_balance,
stripe_payment_intent_id: paymentIntent.id,
status: 'pending',
});
return {
transaction_id: transaction.id,
payment_intent: {
id: paymentIntent.id,
client_secret: paymentIntent.client_secret,
status: paymentIntent.status,
},
};
}
async getPortfolio(userId: string) {
const accounts = await this.repository.getAccountsByUser(userId);
const totalInvested = accounts.reduce((sum, acc) => sum + acc.total_deposited, 0);
const totalCurrentValue = accounts.reduce((sum, acc) => sum + acc.current_balance, 0);
const totalProfit = totalCurrentValue - totalInvested;
const totalReturnPercentage = totalInvested > 0 ? (totalProfit / totalInvested) * 100 : 0;
// Calcular allocación por riesgo
const allocationByRisk = accounts.reduce((acc, account) => {
const percentage = (account.current_balance / totalCurrentValue) * 100;
acc[account.product.risk_level] = (acc[account.product.risk_level] || 0) + percentage;
return acc;
}, {} as Record<string, number>);
return {
summary: {
total_invested: totalInvested,
total_current_value: totalCurrentValue,
total_profit: totalProfit,
total_return_percentage: totalReturnPercentage,
},
accounts: accounts.map((acc) => ({
account_id: acc.id,
product_name: acc.product.name,
agent_type: acc.product.agent_type,
current_balance: acc.current_balance,
invested: acc.total_deposited,
profit: acc.current_balance - acc.total_deposited,
return_percentage: acc.total_return_percentage,
allocation_percentage: (acc.current_balance / totalCurrentValue) * 100,
})),
allocation_by_risk: allocationByRisk,
};
}
// ... otros métodos
}
```
---
## 5. Validaciones
### 5.1 Schemas Zod
```typescript
// src/modules/investment/investment.validators.ts
import { z } from 'zod';
export const createProductSchema = z.object({
name: z.string().min(3).max(100),
description: z.string().optional(),
agent_type: z.enum(['swing', 'day', 'scalping', 'arbitrage']),
min_investment: z.number().positive(),
max_investment: z.number().positive().optional(),
performance_fee_percentage: z.number().min(0).max(100),
target_annual_return: z.number().optional(),
risk_level: z.enum(['low', 'medium', 'high', 'very_high']),
ml_agent_id: z.string().min(1),
ml_config: z.record(z.any()).optional(),
});
export const createAccountSchema = z.object({
product_id: z.string().uuid(),
initial_investment: z.number().positive(),
payment_method_id: z.string().min(1),
});
export const depositSchema = z.object({
amount: z.number().positive(),
payment_method_id: z.string().min(1),
});
export const withdrawalSchema = z.object({
amount: z.number().positive(),
withdrawal_method: z.enum(['bank_transfer', 'stripe_payout']),
destination_details: z.record(z.any()),
});
```
---
## 6. Seguridad
### 6.1 Rate Limiting
```typescript
// Límites por endpoint
const RATE_LIMITS = {
createAccount: { max: 5, window: 3600 }, // 5 cuentas/hora
deposit: { max: 10, window: 3600 }, // 10 depósitos/hora
withdraw: { max: 3, window: 3600 }, // 3 retiros/hora
};
```
### 6.2 Autenticación
- Todos los endpoints requieren JWT válido
- Endpoints de admin requieren rol `admin`
- Verificación de ownership para acceso a cuentas
---
## 7. Configuración
### 7.1 Variables de Entorno
```bash
# API
PORT=3000
API_PREFIX=/api/v1
# Investment
INVESTMENT_MIN_DEPOSIT=50.00
INVESTMENT_MIN_WITHDRAWAL=50.00
INVESTMENT_MAX_WITHDRAWAL_PENDING=5
# Rate Limits
RATE_LIMIT_WINDOW_MS=3600000
RATE_LIMIT_MAX_REQUESTS=100
```
---
## 8. Testing
### 8.1 Test de Endpoints
```typescript
// tests/investment/accounts.test.ts
import request from 'supertest';
import app from '../../src/app';
describe('Investment Accounts API', () => {
let authToken: string;
let productId: string;
beforeAll(async () => {
// Setup: autenticar y crear producto
authToken = await getAuthToken();
productId = await createTestProduct();
});
describe('POST /api/v1/investment/accounts', () => {
it('should create new account with valid data', async () => {
const response = await request(app)
.post('/api/v1/investment/accounts')
.set('Authorization', `Bearer ${authToken}`)
.send({
product_id: productId,
initial_investment: 5000,
payment_method_id: 'pm_test_123',
});
expect(response.status).toBe(201);
expect(response.body.success).toBe(true);
expect(response.body.data).toHaveProperty('account_id');
expect(response.body.data.payment_intent).toHaveProperty('client_secret');
});
it('should reject investment below minimum', async () => {
const response = await request(app)
.post('/api/v1/investment/accounts')
.set('Authorization', `Bearer ${authToken}`)
.send({
product_id: productId,
initial_investment: 50, // Menor al mínimo
payment_method_id: 'pm_test_123',
});
expect(response.status).toBe(400);
});
it('should reject duplicate account', async () => {
// Intentar crear segunda cuenta en mismo producto
const response = await request(app)
.post('/api/v1/investment/accounts')
.set('Authorization', `Bearer ${authToken}`)
.send({
product_id: productId,
initial_investment: 5000,
payment_method_id: 'pm_test_123',
});
expect(response.status).toBe(409);
});
});
describe('GET /api/v1/investment/portfolio', () => {
it('should return user portfolio', async () => {
const response = await request(app)
.get('/api/v1/investment/portfolio')
.set('Authorization', `Bearer ${authToken}`);
expect(response.status).toBe(200);
expect(response.body.data).toHaveProperty('summary');
expect(response.body.data).toHaveProperty('accounts');
expect(response.body.data.summary).toHaveProperty('total_invested');
});
});
});
```
---
## 9. Documentación OpenAPI
```yaml
openapi: 3.0.0
info:
title: Trading Platform - Investment API
version: 1.0.0
description: API para gestión de cuentas de inversión
paths:
/api/v1/investment/products:
get:
summary: Lista productos de inversión
tags: [Products]
security:
- bearerAuth: []
responses:
'200':
description: Lista de productos
content:
application/json:
schema:
$ref: '#/components/schemas/ProductsResponse'
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
schemas:
ProductsResponse:
type: object
properties:
success:
type: boolean
data:
type: object
properties:
products:
type: array
items:
$ref: '#/components/schemas/Product'
```
---
## 10. Referencias
- Stripe Payment Intents API
- Express.js Best Practices
- Zod Validation Library
- PostgreSQL Transaction Management