Portfolio API
The Portfolio API provides comprehensive portfolio management functionality, allowing users to track stock holdings, analyze performance, and manage investments.
Base URL
All portfolio endpoints are prefixed with:
Authentication
All portfolio endpoints require authentication. Include the Bearer token in the Authorization header:
Data Models
Portfolio Response
{
"stocks": [
{
"symbol": "AAPL",
"name": "Apple Inc.",
"shares": 10.5,
"averagePrice": 150.25,
"currentPrice": 175.80,
"totalValue": 1845.90,
"gain": 268.28,
"gainPercent": 17.02
}
],
"totalValue": 25430.50,
"dailyChange": 1250.75,
"dailyChangePercent": 5.17
}
Portfolio Stock
Field | Type | Description |
---|---|---|
symbol |
string | Stock ticker symbol (e.g., "AAPL") |
name |
string | Company name |
shares |
number | Number of shares owned |
averagePrice |
number | Average purchase price per share |
currentPrice |
number | Current market price per share |
totalValue |
number | Current total value (shares × currentPrice) |
gain |
number | Total gain/loss in dollars |
gainPercent |
number | Total gain/loss as percentage |
Endpoints
Get Portfolio
Retrieve the user's complete portfolio with real-time market data.
Response
Status Code: 200 OK
{
"stocks": [
{
"symbol": "AAPL",
"name": "Apple Inc.",
"shares": 10,
"averagePrice": 150.00,
"currentPrice": 175.50,
"totalValue": 1755.00,
"gain": 255.00,
"gainPercent": 17.00
},
{
"symbol": "GOOGL",
"name": "Alphabet Inc.",
"shares": 5,
"averagePrice": 2800.00,
"currentPrice": 2950.25,
"totalValue": 14751.25,
"gain": 751.25,
"gainPercent": 5.37
}
],
"totalValue": 16506.25,
"dailyChange": 425.75,
"dailyChangePercent": 2.65
}
Error Responses
404 Not Found
500 Internal Server Error
Add Stock to Portfolio
Add a new stock to the portfolio or increase an existing position.
Request Body
Field | Type | Required | Description |
---|---|---|---|
symbol |
string | Yes | Stock ticker symbol (e.g., "AAPL") |
shares |
number | Yes | Number of shares to add (> 0) |
price |
number | Yes | Purchase price per share (> 0) |
Response
Status Code: 200 OK
Error Responses
400 Bad Request - Invalid stock symbol
422 Unprocessable Entity - Validation errors
{
"detail": [
{
"loc": ["body", "shares"],
"msg": "ensure this value is greater than 0",
"type": "value_error.number.not_gt",
"ctx": {"limit_value": 0}
}
]
}
500 Internal Server Error
Update Stock in Portfolio
Update the number of shares or average price for an existing holding.
Path Parameters
Parameter | Type | Description |
---|---|---|
symbol |
string | Stock ticker symbol to update |
Request Body
Field | Type | Required | Description |
---|---|---|---|
shares |
number | No | New total number of shares (> 0) |
average_price |
number | No | New average price per share (> 0) |
Note: At least one field must be provided.
Response
Status Code: 200 OK
Error Responses
404 Not Found
422 Unprocessable Entity
{
"detail": [
{
"loc": ["body", "shares"],
"msg": "ensure this value is greater than 0",
"type": "value_error.number.not_gt",
"ctx": {"limit_value": 0}
}
]
}
500 Internal Server Error
Remove Stock from Portfolio
Remove a specified number of shares from an existing holding.
Path Parameters
Parameter | Type | Description |
---|---|---|
symbol |
string | Stock ticker symbol to remove shares from |
Request Body
Field | Type | Required | Description |
---|---|---|---|
shares |
number | Yes | Number of shares to remove (> 0) |
Note: If the number of shares to remove equals or exceeds the current holding, the entire position will be removed from the portfolio.
Response
Status Code: 200 OK
Error Responses
404 Not Found
422 Unprocessable Entity
{
"detail": [
{
"loc": ["body", "shares"],
"msg": "ensure this value is greater than 0",
"type": "value_error.number.not_gt",
"ctx": {"limit_value": 0}
}
]
}
500 Internal Server Error
Examples
Complete Portfolio Management Workflow
1. Get Current Portfolio
curl -X GET "http://localhost:8000/api/v1/portfolio/" \
-H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
2. Add New Stock
curl -X POST "http://localhost:8000/api/v1/portfolio/stocks" \
-H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..." \
-H "Content-Type: application/json" \
-d '{
"symbol": "AAPL",
"shares": 10,
"price": 150.00
}'
3. Update Existing Position
curl -X PUT "http://localhost:8000/api/v1/portfolio/stocks/AAPL" \
-H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..." \
-H "Content-Type: application/json" \
-d '{
"shares": 15
}'
4. Partial Sale
curl -X DELETE "http://localhost:8000/api/v1/portfolio/stocks/AAPL" \
-H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..." \
-H "Content-Type: application/json" \
-d '{
"shares": 5
}'
JavaScript/TypeScript Examples
Using Fetch API
// Get portfolio
async function getPortfolio() {
const response = await fetch('/api/v1/portfolio/', {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
if (!response.ok) {
throw new Error(`Error ${response.status}: ${response.statusText}`);
}
return await response.json();
}
// Add stock to portfolio
async function addStock(symbol, shares, price) {
const response = await fetch('/api/v1/portfolio/stocks', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ symbol, shares, price })
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail);
}
return await response.json();
}
Using Axios
import axios from 'axios';
const api = axios.create({
baseURL: '/api/v1',
headers: {
'Authorization': `Bearer ${token}`
}
});
// Get portfolio
const portfolio = await api.get('/portfolio/');
// Add stock
const result = await api.post('/portfolio/stocks', {
symbol: 'AAPL',
shares: 10,
price: 150.00
});
// Update stock
await api.put('/portfolio/stocks/AAPL', {
shares: 15
});
// Remove shares
await api.delete('/portfolio/stocks/AAPL', {
data: { shares: 5 }
});
Python Examples
import httpx
import asyncio
async def get_portfolio(token: str):
async with httpx.AsyncClient() as client:
response = await client.get(
"http://localhost:8000/api/v1/portfolio/",
headers={"Authorization": f"Bearer {token}"}
)
response.raise_for_status()
return response.json()
async def add_stock(token: str, symbol: str, shares: float, price: float):
async with httpx.AsyncClient() as client:
response = await client.post(
"http://localhost:8000/api/v1/portfolio/stocks",
headers={"Authorization": f"Bearer {token}"},
json={"symbol": symbol, "shares": shares, "price": price}
)
response.raise_for_status()
return response.json()
# Usage
portfolio = await get_portfolio(token)
result = await add_stock(token, "AAPL", 10, 150.00)
Business Logic
Portfolio Calculations
Average Price Calculation
When adding shares to an existing position, the average price is calculated using a weighted average:
new_average_price = (existing_shares * existing_avg_price + new_shares * new_price) / (existing_shares + new_shares)
Gain/Loss Calculation
- Gain/Loss ($):
(current_price - average_price) * shares
- Gain/Loss (%):
((current_price - average_price) / average_price) * 100
Portfolio Totals
- Total Value: Sum of all individual stock values
- Daily Change: Sum of all individual stock daily changes
- Daily Change %:
(daily_change / (total_value - daily_change)) * 100
Stock Symbol Validation
Before adding a stock to the portfolio, the system validates that the symbol exists by: 1. Checking with the market data service 2. Ensuring the symbol returns valid price data 3. Rejecting invalid or non-existent symbols
Error Handling
The API implements comprehensive error handling: - Validation Errors: Invalid input data (negative shares, invalid symbols) - Business Logic Errors: Stock not found in portfolio - System Errors: Database connection issues, external API failures - Authentication Errors: Invalid or expired tokens
Performance Considerations
- Portfolio data includes real-time stock prices
- Market data is cached for 60 seconds to reduce API calls
- Database queries are optimized with proper indexing
- Bulk operations are supported for better performance
Rate Limiting
Portfolio endpoints are subject to rate limiting: - Read Operations: 100 requests per minute - Write Operations: 50 requests per minute
Related APIs
- Market Data API - Get real-time stock prices and data
- Authentication API - User authentication and token management
- User API - User profile and preferences management
Testing
Unit Tests
Portfolio endpoints are covered by comprehensive unit tests:
# Run portfolio-specific tests
uv run pytest tests/unit/test_services/test_portfolio_service.py -v
# Run portfolio API tests
uv run pytest tests/integration/test_api/test_portfolio.py -v
Test Data
Test portfolios can be created using factories:
from tests.factories import UserFactory, PortfolioFactory, HoldingFactory
user = UserFactory.create()
portfolio = PortfolioFactory.create(user=user)
holding = HoldingFactory.create(portfolio=portfolio, symbol="AAPL")
For more information about testing, see the Testing Guide.