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,5 @@
"""Utility functions and helpers."""
from app.utils.deduplication import generate_transaction_hash
from app.utils.option_parser import parse_option_symbol, OptionInfo
__all__ = ["generate_transaction_hash", "parse_option_symbol", "OptionInfo"]

View File

@@ -0,0 +1,65 @@
"""Transaction deduplication utilities."""
import hashlib
from datetime import date
from decimal import Decimal
from typing import Optional
def generate_transaction_hash(
account_id: int,
run_date: date,
symbol: Optional[str],
action: str,
amount: Optional[Decimal],
quantity: Optional[Decimal],
price: Optional[Decimal],
) -> str:
"""
Generate a unique SHA-256 hash for a transaction to prevent duplicates.
The hash is generated from key transaction attributes that uniquely identify
a transaction: account, date, symbol, action, amount, quantity, and price.
Args:
account_id: Account identifier
run_date: Transaction date
symbol: Trading symbol
action: Transaction action description
amount: Transaction amount
quantity: Number of shares/contracts
price: Price per unit
Returns:
str: 64-character hexadecimal SHA-256 hash
Example:
>>> generate_transaction_hash(
... account_id=1,
... run_date=date(2025, 12, 26),
... symbol="AAPL",
... action="YOU BOUGHT",
... amount=Decimal("-1500.00"),
... quantity=Decimal("10"),
... price=Decimal("150.00")
... )
'a1b2c3d4...'
"""
# Convert values to strings, handling None values
symbol_str = symbol or ""
amount_str = str(amount) if amount is not None else ""
quantity_str = str(quantity) if quantity is not None else ""
price_str = str(price) if price is not None else ""
# Create hash string with pipe delimiter
hash_string = (
f"{account_id}|"
f"{run_date.isoformat()}|"
f"{symbol_str}|"
f"{action}|"
f"{amount_str}|"
f"{quantity_str}|"
f"{price_str}"
)
# Generate SHA-256 hash
return hashlib.sha256(hash_string.encode("utf-8")).hexdigest()

View File

@@ -0,0 +1,91 @@
"""Option symbol parsing utilities."""
import re
from datetime import datetime
from typing import Optional, NamedTuple
from decimal import Decimal
class OptionInfo(NamedTuple):
"""
Parsed option information.
Attributes:
underlying_symbol: Base ticker symbol (e.g., "AAPL")
expiration_date: Option expiration date
option_type: "CALL" or "PUT"
strike_price: Strike price
"""
underlying_symbol: str
expiration_date: datetime
option_type: str
strike_price: Decimal
def parse_option_symbol(option_symbol: str) -> Optional[OptionInfo]:
"""
Parse Fidelity option symbol format into components.
Fidelity format: -SYMBOL + YYMMDD + C/P + STRIKE
Example: -AAPL260116C150 = AAPL Call expiring Jan 16, 2026 at $150 strike
Args:
option_symbol: Fidelity option symbol string
Returns:
OptionInfo object if parsing successful, None otherwise
Examples:
>>> parse_option_symbol("-AAPL260116C150")
OptionInfo(
underlying_symbol='AAPL',
expiration_date=datetime(2026, 1, 16),
option_type='CALL',
strike_price=Decimal('150')
)
>>> parse_option_symbol("-TSLA251219P500")
OptionInfo(
underlying_symbol='TSLA',
expiration_date=datetime(2025, 12, 19),
option_type='PUT',
strike_price=Decimal('500')
)
"""
# Regex pattern: -SYMBOL + YYMMDD + C/P + STRIKE
# Symbol: one or more uppercase letters
# Date: 6 digits (YYMMDD)
# Type: C (call) or P (put)
# Strike: digits with optional decimal point
pattern = r"^-([A-Z]+)(\d{6})([CP])(\d+\.?\d*)$"
match = re.match(pattern, option_symbol)
if not match:
return None
symbol, date_str, option_type, strike_str = match.groups()
# Parse date (YYMMDD format)
try:
# Assume 20XX for years (works until 2100)
year = 2000 + int(date_str[:2])
month = int(date_str[2:4])
day = int(date_str[4:6])
expiration_date = datetime(year, month, day)
except (ValueError, IndexError):
return None
# Parse option type
option_type_full = "CALL" if option_type == "C" else "PUT"
# Parse strike price
try:
strike_price = Decimal(strike_str)
except (ValueError, ArithmeticError):
return None
return OptionInfo(
underlying_symbol=symbol,
expiration_date=expiration_date,
option_type=option_type_full,
strike_price=strike_price,
)