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>
3.7 KiB
3.7 KiB
| id | title | type | status | priority | epic | project | version | created_date | updated_date |
|---|---|---|---|---|---|---|---|---|---|
| ET-PFM-006 | Motor de Reportes Fiscales | Technical Specification | Done | Alta | OQI-008 | trading-platform | 1.0.0 | 2025-12-05 | 2026-01-04 |
ET-PFM-006: Motor de Reportes Fiscales
Épica: OQI-008 - Portfolio Manager Versión: 1.0 Fecha: 2025-12-05 Estado: Planificado
Servicios de Cálculo Fiscal
@Injectable()
export class TaxReportService {
async generateCapitalGainsReport(
userId: string,
year: number
): Promise<CapitalGainsReport> {
const sales = await this.getSalesForYear(userId, year);
const gains: CapitalGain[] = [];
let shortTermTotal = 0;
let longTermTotal = 0;
for (const sale of sales) {
const costBasis = await this.getCostBasis(sale, 'FIFO');
const gain = sale.total - costBasis.totalCost;
const holdingDays = this.calculateHoldingPeriod(
costBasis.purchaseDate,
sale.executedAt
);
const isLongTerm = holdingDays > 365;
gains.push({
symbol: sale.symbol,
purchaseDate: costBasis.purchaseDate,
saleDate: sale.executedAt,
quantity: sale.quantity,
costBasis: costBasis.totalCost,
proceeds: sale.total,
gain,
holdingPeriod: isLongTerm ? 'long' : 'short',
});
if (isLongTerm) {
longTermTotal += gain;
} else {
shortTermTotal += gain;
}
}
return {
year,
gains,
summary: {
shortTerm: {
gains: gains.filter(g => g.holdingPeriod === 'short' && g.gain > 0)
.reduce((sum, g) => sum + g.gain, 0),
losses: gains.filter(g => g.holdingPeriod === 'short' && g.gain < 0)
.reduce((sum, g) => sum + g.gain, 0),
net: shortTermTotal,
},
longTerm: {
gains: gains.filter(g => g.holdingPeriod === 'long' && g.gain > 0)
.reduce((sum, g) => sum + g.gain, 0),
losses: gains.filter(g => g.holdingPeriod === 'long' && g.gain < 0)
.reduce((sum, g) => sum + g.gain, 0),
net: longTermTotal,
},
total: shortTermTotal + longTermTotal,
},
};
}
async generateDividendReport(
userId: string,
year: number
): Promise<DividendReport> {
const dividends = await this.getDividendsForYear(userId, year);
return {
year,
dividends: dividends.map(d => ({
date: d.executedAt,
symbol: d.symbol,
amount: d.total,
type: d.metadata?.qualified ? 'qualified' : 'ordinary',
})),
summary: {
qualified: dividends
.filter(d => d.metadata?.qualified)
.reduce((sum, d) => sum + d.total, 0),
ordinary: dividends
.filter(d => !d.metadata?.qualified)
.reduce((sum, d) => sum + d.total, 0),
total: dividends.reduce((sum, d) => sum + d.total, 0),
},
};
}
}
Tax-Loss Harvesting (Premium)
async getTaxLossOpportunities(
userId: string
): Promise<TaxLossOpportunity[]> {
const positions = await this.portfolioService.getPositions(userId);
const ytdGains = await this.getYTDRealizedGains(userId);
const opportunities = positions
.filter(p => p.pnl < 0)
.map(p => ({
symbol: p.symbol,
unrealizedLoss: p.pnl,
estimatedTaxSavings: Math.abs(p.pnl) * 0.25, // ~25% tax rate
washSaleStatus: this.checkWashSale(p.symbol, userId),
canHarvest: this.checkWashSale(p.symbol, userId) === 'clear',
}))
.filter(o => o.canHarvest);
return opportunities;
}
Referencias
Especificación técnica - Sistema NEXUS