- 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>
5.8 KiB
5.8 KiB
Timeframe Filtering Feature
Overview
The timeframe filtering feature allows users to view dashboard metrics and charts for specific date ranges, providing better insights into performance over different time periods.
User Interface
Location
- Dashboard page (DashboardV2 component)
- Dropdown filter positioned at the top of the dashboard, above metrics cards
Available Options
- All Time - Shows all historical data
- Last 30 Days - Shows data from the past 30 days
- Last 90 Days - Shows data from the past 90 days
- Last 180 Days - Shows data from the past 180 days (default for chart)
- Last 1 Year - Shows data from the past 365 days
- Year to Date - Shows data from January 1st of current year to today
What Gets Filtered
Metrics Cards (Top of Dashboard)
When a timeframe is selected, the following metrics are filtered by position open date:
- Total Positions count
- Open Positions count
- Closed Positions count
- Total Realized P&L
- Total Unrealized P&L
- Win Rate percentage
- Average Win amount
- Average Loss amount
- Current Balance (always shows latest)
Balance History Chart
The chart adjusts to show the requested number of days:
- All Time: ~10 years (3650 days)
- Last 30 Days: 30 days
- Last 90 Days: 90 days
- Last 180 Days: 180 days
- Last 1 Year: 365 days
- Year to Date: Dynamic calculation from Jan 1 to today
Implementation Details
Frontend
Component: DashboardV2.tsx
// State management
const [timeframe, setTimeframe] = useState<TimeframeOption>('all');
// Convert timeframe to days for balance history
const getDaysFromTimeframe = (tf: TimeframeOption): number => {
switch (tf) {
case 'last30days': return 30;
case 'last90days': return 90;
// ... etc
}
};
// Get date range for filtering
const { startDate, endDate } = getTimeframeDates(timeframe);
API Calls
-
Overview Stats:
- Endpoint:
GET /analytics/overview/{account_id} - Parameters:
start_date,end_date - Query key includes timeframe for proper caching
- Endpoint:
-
Balance History:
- Endpoint:
GET /analytics/balance-history/{account_id} - Parameters:
days(calculated from timeframe) - Query key includes timeframe for proper caching
- Endpoint:
Backend
Endpoint: analytics_v2.py
@router.get("/overview/{account_id}")
def get_overview(
account_id: int,
refresh_prices: bool = False,
max_api_calls: int = 5,
start_date: Optional[date] = None, # NEW
end_date: Optional[date] = None, # NEW
db: Session = Depends(get_db)
):
# Passes dates to calculator
stats = calculator.calculate_account_stats(
account_id,
update_prices=True,
max_api_calls=max_api_calls,
start_date=start_date,
end_date=end_date
)
Service: performance_calculator_v2.py
def calculate_account_stats(
self,
account_id: int,
update_prices: bool = True,
max_api_calls: int = 10,
start_date = None, # NEW
end_date = None # NEW
) -> Dict:
# Filter positions by open date
query = self.db.query(Position).filter(Position.account_id == account_id)
if start_date:
query = query.filter(Position.open_date >= start_date)
if end_date:
query = query.filter(Position.open_date <= end_date)
positions = query.all()
# ... rest of calculation logic
Filter Logic
Position Filtering
Positions are filtered based on their open_date:
- Only positions opened on or after
start_dateare included - Only positions opened on or before
end_dateare included - Open positions are always included if they match the date criteria
Balance History
The balance history shows account balance at end of each day:
- Calculated from transactions within the specified days
- Does not filter by open date, shows actual historical balances
Caching Strategy
React Query cache keys include timeframe parameters to ensure:
- Different timeframes don't conflict in cache
- Changing timeframes triggers new API calls
- Cache invalidation works correctly
Cache keys:
- Overview:
['analytics', 'overview', accountId, startDate, endDate] - Balance:
['analytics', 'balance-history', accountId, timeframe]
User Experience
Performance
- Balance history queries are fast (no market data needed)
- Overview queries use cached prices by default (fast)
- Users can still trigger price refresh within filtered timeframe
Visual Feedback
- Filter immediately updates both metrics and chart
- Loading states handled by React Query
- Stale data shown while fetching (stale-while-revalidate pattern)
Testing Checklist
- All timeframe options work correctly
- Metrics update when timeframe changes
- Balance history chart adjusts to show correct date range
- "All Time" shows complete data
- Year to Date calculation is accurate
- Filter persists during price refresh
- Cache invalidation works properly
- UI shows loading states appropriately
Future Enhancements
Potential improvements:
- Add custom date range picker
- Compare multiple timeframes side-by-side
- Save preferred timeframe in user settings
- Add timeframe filter to Transactions table
- Add timeframe presets for tax year, quarters
- Export filtered data to CSV
Related Components
TimeframeFilter.tsx- Reusable dropdown componentgetTimeframeDates()- Helper function to convert timeframe to datesTransactionTable.tsx- Already uses timeframe filtering
API Reference
GET /analytics/overview/{account_id}
Query Parameters:
- refresh_prices: boolean (default: false)
- max_api_calls: integer (default: 5)
- start_date: date (optional, format: YYYY-MM-DD)
- end_date: date (optional, format: YYYY-MM-DD)
GET /analytics/balance-history/{account_id}
Query Parameters:
- days: integer (default: 30, max: 3650)