trading-platform-frontend-v2/src/services/portfolio.service.ts
Adrian Flores Cortes b8a7cbe691 [OQI-008] feat: Add Portfolio Manager frontend module
- Created portfolio.service.ts with API client functions
- Created AllocationChart component (donut chart)
- Created AllocationTable component (detailed positions)
- Created RebalanceCard component (rebalancing recommendations)
- Created GoalCard component (financial goal progress)
- Created PortfolioDashboard page (main dashboard)
- Created CreatePortfolio page (new portfolio form)
- Created CreateGoal page (new goal form)
- Updated App.tsx with portfolio routes

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 08:31:26 -06:00

255 lines
6.9 KiB
TypeScript

/**
* Portfolio Service
* Client for connecting to the Portfolio API
*/
const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:3080';
// ============================================================================
// Types
// ============================================================================
export type RiskProfile = 'conservative' | 'moderate' | 'aggressive';
export interface Portfolio {
id: string;
userId: string;
name: string;
riskProfile: RiskProfile;
allocations: PortfolioAllocation[];
totalValue: number;
totalCost: number;
unrealizedPnl: number;
unrealizedPnlPercent: number;
realizedPnl: number;
lastRebalanced: string | null;
createdAt: string;
updatedAt: string;
}
export interface PortfolioAllocation {
id: string;
portfolioId: string;
asset: string;
targetPercent: number;
currentPercent: number;
quantity: number;
value: number;
cost: number;
pnl: number;
pnlPercent: number;
deviation: number;
}
export interface PortfolioGoal {
id: string;
userId: string;
name: string;
targetAmount: number;
currentAmount: number;
targetDate: string;
monthlyContribution: number;
progress: number;
projectedCompletion: string | null;
status: 'on_track' | 'at_risk' | 'behind';
createdAt: string;
updatedAt: string;
}
export interface RebalanceRecommendation {
asset: string;
currentPercent: number;
targetPercent: number;
action: 'buy' | 'sell' | 'hold';
amount: number;
amountUSD: number;
priority: 'high' | 'medium' | 'low';
}
export interface PortfolioStats {
totalValue: number;
dayChange: number;
dayChangePercent: number;
weekChange: number;
weekChangePercent: number;
monthChange: number;
monthChangePercent: number;
allTimeChange: number;
allTimeChangePercent: number;
bestPerformer: { asset: string; change: number };
worstPerformer: { asset: string; change: number };
}
export interface CreatePortfolioInput {
name: string;
riskProfile: RiskProfile;
initialValue?: number;
}
export interface CreateGoalInput {
name: string;
targetAmount: number;
targetDate: string;
monthlyContribution: number;
}
// ============================================================================
// Portfolio API Functions
// ============================================================================
/**
* Get user's portfolios
*/
export async function getUserPortfolios(): Promise<Portfolio[]> {
const response = await fetch(`${API_URL}/api/v1/portfolio`, {
credentials: 'include',
});
if (!response.ok) throw new Error('Failed to fetch portfolios');
const data = await response.json();
return data.data || data;
}
/**
* Get portfolio by ID
*/
export async function getPortfolio(portfolioId: string): Promise<Portfolio> {
const response = await fetch(`${API_URL}/api/v1/portfolio/${portfolioId}`, {
credentials: 'include',
});
if (!response.ok) throw new Error('Failed to fetch portfolio');
const data = await response.json();
return data.data || data;
}
/**
* Create a new portfolio
*/
export async function createPortfolio(input: CreatePortfolioInput): Promise<Portfolio> {
const response = await fetch(`${API_URL}/api/v1/portfolio`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify(input),
});
if (!response.ok) throw new Error('Failed to create portfolio');
const data = await response.json();
return data.data || data;
}
/**
* Update portfolio allocations
*/
export async function updateAllocations(
portfolioId: string,
allocations: { asset: string; targetPercent: number }[]
): Promise<Portfolio> {
const response = await fetch(`${API_URL}/api/v1/portfolio/${portfolioId}/allocations`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ allocations }),
});
if (!response.ok) throw new Error('Failed to update allocations');
const data = await response.json();
return data.data || data;
}
/**
* Get rebalancing recommendations
*/
export async function getRebalanceRecommendations(
portfolioId: string
): Promise<RebalanceRecommendation[]> {
const response = await fetch(`${API_URL}/api/v1/portfolio/${portfolioId}/rebalance`, {
credentials: 'include',
});
if (!response.ok) throw new Error('Failed to fetch recommendations');
const data = await response.json();
return data.data || data;
}
/**
* Execute rebalancing
*/
export async function executeRebalance(portfolioId: string): Promise<Portfolio> {
const response = await fetch(`${API_URL}/api/v1/portfolio/${portfolioId}/rebalance`, {
method: 'POST',
credentials: 'include',
});
if (!response.ok) throw new Error('Failed to execute rebalance');
const data = await response.json();
return data.data || data;
}
/**
* Get portfolio statistics
*/
export async function getPortfolioStats(portfolioId: string): Promise<PortfolioStats> {
const response = await fetch(`${API_URL}/api/v1/portfolio/${portfolioId}/stats`, {
credentials: 'include',
});
if (!response.ok) throw new Error('Failed to fetch stats');
const data = await response.json();
return data.data || data;
}
// ============================================================================
// Goals API Functions
// ============================================================================
/**
* Get user's goals
*/
export async function getUserGoals(): Promise<PortfolioGoal[]> {
const response = await fetch(`${API_URL}/api/v1/portfolio/goals`, {
credentials: 'include',
});
if (!response.ok) throw new Error('Failed to fetch goals');
const data = await response.json();
return data.data || data;
}
/**
* Create a new goal
*/
export async function createGoal(input: CreateGoalInput): Promise<PortfolioGoal> {
const response = await fetch(`${API_URL}/api/v1/portfolio/goals`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify(input),
});
if (!response.ok) throw new Error('Failed to create goal');
const data = await response.json();
return data.data || data;
}
/**
* Update goal progress
*/
export async function updateGoalProgress(
goalId: string,
currentAmount: number
): Promise<PortfolioGoal> {
const response = await fetch(`${API_URL}/api/v1/portfolio/goals/${goalId}/progress`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ currentAmount }),
});
if (!response.ok) throw new Error('Failed to update goal');
const data = await response.json();
return data.data || data;
}
/**
* Delete a goal
*/
export async function deleteGoal(goalId: string): Promise<void> {
const response = await fetch(`${API_URL}/api/v1/portfolio/goals/${goalId}`, {
method: 'DELETE',
credentials: 'include',
});
if (!response.ok) throw new Error('Failed to delete goal');
}