Initial release v1.1.0

- Complete MVP for tracking Fidelity brokerage account performance
- Transaction import from CSV with deduplication
- Automatic FIFO position tracking with options support
- Real-time P&L calculations with market data caching
- Dashboard with timeframe filtering (30/90/180 days, 1 year, YTD, all time)
- Docker-based deployment with PostgreSQL backend
- React/TypeScript frontend with TailwindCSS
- FastAPI backend with SQLAlchemy ORM

Features:
- Multi-account support
- Import via CSV upload or filesystem
- Open and closed position tracking
- Balance history charting
- Performance analytics and metrics
- Top trades analysis
- Responsive UI design

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Chris
2026-01-22 14:27:43 -05:00
commit eea4469095
90 changed files with 14513 additions and 0 deletions

View File

@@ -0,0 +1,151 @@
"""Account management API endpoints."""
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.orm import Session
from typing import List
from app.api.deps import get_db
from app.models import Account
from app.schemas import AccountCreate, AccountUpdate, AccountResponse
router = APIRouter()
@router.post("", response_model=AccountResponse, status_code=status.HTTP_201_CREATED)
def create_account(account: AccountCreate, db: Session = Depends(get_db)):
"""
Create a new brokerage account.
Args:
account: Account creation data
db: Database session
Returns:
Created account
Raises:
HTTPException: If account number already exists
"""
# Check if account number already exists
existing = (
db.query(Account)
.filter(Account.account_number == account.account_number)
.first()
)
if existing:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Account with number {account.account_number} already exists",
)
# Create new account
db_account = Account(**account.model_dump())
db.add(db_account)
db.commit()
db.refresh(db_account)
return db_account
@router.get("", response_model=List[AccountResponse])
def list_accounts(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
"""
List all accounts.
Args:
skip: Number of records to skip
limit: Maximum number of records to return
db: Database session
Returns:
List of accounts
"""
accounts = db.query(Account).offset(skip).limit(limit).all()
return accounts
@router.get("/{account_id}", response_model=AccountResponse)
def get_account(account_id: int, db: Session = Depends(get_db)):
"""
Get account by ID.
Args:
account_id: Account ID
db: Database session
Returns:
Account details
Raises:
HTTPException: If account not found
"""
account = db.query(Account).filter(Account.id == account_id).first()
if not account:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Account {account_id} not found",
)
return account
@router.put("/{account_id}", response_model=AccountResponse)
def update_account(
account_id: int, account_update: AccountUpdate, db: Session = Depends(get_db)
):
"""
Update account details.
Args:
account_id: Account ID
account_update: Updated account data
db: Database session
Returns:
Updated account
Raises:
HTTPException: If account not found
"""
db_account = db.query(Account).filter(Account.id == account_id).first()
if not db_account:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Account {account_id} not found",
)
# Update fields
update_data = account_update.model_dump(exclude_unset=True)
for field, value in update_data.items():
setattr(db_account, field, value)
db.commit()
db.refresh(db_account)
return db_account
@router.delete("/{account_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_account(account_id: int, db: Session = Depends(get_db)):
"""
Delete an account and all associated data.
Args:
account_id: Account ID
db: Database session
Raises:
HTTPException: If account not found
"""
db_account = db.query(Account).filter(Account.id == account_id).first()
if not db_account:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Account {account_id} not found",
)
db.delete(db_account)
db.commit()