Changes include: - Updated architecture documentation - Enhanced module definitions (OQI-001 to OQI-008) - ML integration documentation updates - Trading strategies documentation - Orchestration and inventory updates - Docker configuration updates 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
4.0 KiB
4.0 KiB
| id | title | type | status | priority | epic | project | version | created_date | updated_date |
|---|---|---|---|---|---|---|---|---|---|
| ET-PFM-007 | Motor de Metas de Inversión | Technical Specification | Done | Alta | OQI-008 | trading-platform | 1.0.0 | 2025-12-05 | 2026-01-04 |
ET-PFM-007: Motor de Metas de Inversión
Épica: OQI-008 - Portfolio Manager Versión: 1.0 Fecha: 2025-12-05 Estado: Planificado
Modelo de Datos
@Entity('investment_goals')
export class InvestmentGoal {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ name: 'user_id' })
userId: string;
@Column({ length: 100 })
name: string;
@Column({
type: 'enum',
enum: ['retirement', 'home', 'education', 'emergency', 'custom'],
})
type: GoalType;
@Column('decimal', { precision: 18, scale: 2, name: 'target_amount' })
targetAmount: number;
@Column('decimal', { precision: 18, scale: 2, name: 'current_amount' })
currentAmount: number;
@Column('decimal', { precision: 18, scale: 2, name: 'monthly_contribution' })
monthlyContribution: number;
@Column({ name: 'target_date' })
targetDate: Date;
@Column('decimal', { precision: 5, scale: 2, name: 'expected_return' })
expectedReturn: number;
@Column({
type: 'enum',
enum: ['on_track', 'ahead', 'behind'],
default: 'on_track',
})
status: GoalStatus;
@Column({ name: 'linked_account_id', nullable: true })
linkedAccountId: string;
@CreateDateColumn({ name: 'created_at' })
createdAt: Date;
@UpdateDateColumn({ name: 'updated_at' })
updatedAt: Date;
}
Servicio de Proyección
@Injectable()
export class GoalProjectionService {
calculateProjection(goal: InvestmentGoal): GoalProjection {
const monthsRemaining = this.getMonthsRemaining(goal.targetDate);
const monthlyReturn = goal.expectedReturn / 12 / 100;
// Future Value con contribuciones mensuales
// FV = PV(1+r)^n + PMT × ((1+r)^n - 1) / r
const futureValue =
goal.currentAmount * Math.pow(1 + monthlyReturn, monthsRemaining) +
goal.monthlyContribution *
((Math.pow(1 + monthlyReturn, monthsRemaining) - 1) / monthlyReturn);
const progressPercent = (goal.currentAmount / goal.targetAmount) * 100;
const projectedPercent = (futureValue / goal.targetAmount) * 100;
return {
currentProgress: progressPercent,
projectedValue: futureValue,
projectedProgress: projectedPercent,
shortfall: Math.max(0, goal.targetAmount - futureValue),
status: this.determineStatus(projectedPercent),
scenarios: this.calculateScenarios(goal, monthsRemaining),
};
}
calculateScenarios(
goal: InvestmentGoal,
months: number
): GoalScenarios {
return {
optimistic: this.projectWithReturn(goal, months, goal.expectedReturn + 3),
base: this.projectWithReturn(goal, months, goal.expectedReturn),
pessimistic: this.projectWithReturn(goal, months, goal.expectedReturn - 3),
};
}
calculateRequiredContribution(goal: InvestmentGoal): number {
const months = this.getMonthsRemaining(goal.targetDate);
const monthlyReturn = goal.expectedReturn / 12 / 100;
// PMT = (FV - PV(1+r)^n) × r / ((1+r)^n - 1)
const fvOfCurrent = goal.currentAmount * Math.pow(1 + monthlyReturn, months);
const remaining = goal.targetAmount - fvOfCurrent;
const factor = (Math.pow(1 + monthlyReturn, months) - 1) / monthlyReturn;
return remaining / factor;
}
}
API Endpoints
| Method | Endpoint | Descripción |
|---|---|---|
| GET | /api/goals |
Lista de metas |
| POST | /api/goals |
Crear meta |
| GET | /api/goals/:id |
Detalle de meta |
| PUT | /api/goals/:id |
Actualizar meta |
| DELETE | /api/goals/:id |
Eliminar meta |
| GET | /api/goals/:id/projection |
Proyección de meta |
| POST | /api/goals/:id/simulate |
Simular escenarios |
Referencias
Especificación técnica - Sistema NEXUS