Stock League - Serverpod Hackathon Submission

Inspiration

I pick stocks. I think I'm good at it. But I had no way to prove it.

Tracking picks in a spreadsheet? Boring. Comparing performance against friends? Required manual work. The picks get forgotten. The bragging rights never materialize.

When I saw the Serverpod hackathon, it clicked: I could finally build the app I'd been wanting: A hackathon for stocks. Create a challenge, pick 5 stocks, compete with friends on real returns. Simple, competitive, fun.


What it does

Stock League turns stock picking into a social competition:

  • Create Challenges - Set a start date, duration, and invite friends
  • Pick 5 Stocks - Everyone locks in their picks before the challenge begins
  • Real-time Leaderboard - Track portfolio returns as prices update throughout the day
  • Bragging Rights - Winner gets eternal glory (and screenshot-worthy proof)

No fake money. No complex trading mechanics. Just pure stock-picking skill, measured against your friends + worldwide.

The Math Behind Rankings

Each user's portfolio return is calculated as the average return across their 5 picks:

$$R_{portfolio} = \frac{1}{n} \sum_{i=1}^{n} \frac{P_{current,i} - P_{start,i}}{P_{start,i}} \times 100$$

Where:

  • $P_{start,i}$ = Price of stock $i$ when challenge started (locked via Future Call)
  • $P_{current,i}$ = Current price of stock $i$
  • $n$ = Number of stocks picked (5)

How we built it

Frontend: Flutter for a smooth cross-platform experience. Custom motion system with haptics for that satisfying tap feedback.

Backend: Serverpod running on Serverpod Cloud. PostgreSQL for persistence.

The Secret Sauce — Future Calls: Each challenge has its own timeline. I needed:

  • Start prices captured exactly when a challenge begins (not at midnight)
  • Leaderboards recalculated during market hours
  • Final rankings frozen when challenges complete

Serverpod's Future Calls handled this perfectly. I schedule lifecycle events per challenge, and they fire precisely when needed:

// Schedule challenge activation at exact start time
await session.futureCall(
  'activateChallenge',
  challenge.startDate,
  {'challengeId': challenge.id},
);

No cron hacks. No polling loops. Clean, event-driven automation.


Challenges we ran into

1. Smart Fetching

My first version fetched prices for all stocks on every update:

$$API_{calls} = |S_{all}| \approx 500+$$

Refactored to only fetch distinct stocks users actually picked:

$$API_{calls} = |S_{picked}| = \bigcup_{u \in U} picks(u) \approx 50$$

A 10x reduction in API calls while maintaining complete data coverage.

2. Price Capture Timing

Stock prices must be captured at market open ($t = 09:30$ ET), not midnight. Each challenge $C_i$ has a unique start time $t_{start,i}$. Future Calls let me schedule precisely:

$$\forall C_i : \text{schedule}(\texttt{captureStartPrices}, t_{start,i})$$

3. Leaderboard Performance

Naive approach—loop through users, calculate returns individually:

$$O(u \times s)$$ where $u$ = users, $s$ = stocks per user

Optimized to batch SQL with window functions:

RANK() OVER (ORDER BY portfolio_return DESC)

Result: $O(1)$ query regardless of user count. Instant leaderboards.

4. API Rate Limits

Alpha Vantage limit: 5 requests/minute. With smart caching:

$$\text{Cache TTL} = 30s, \quad \text{Effective calls} = \frac{|S_{picked}|}{12} \text{ per hour}$$

Stays well under quota while keeping data fresh.


Accomplishments that we're proud of

  • Future Calls in production — Not just a demo, but powering real challenge lifecycles with precise timing
  • Active challenges running (one dedicated to ServerPod) — My friends and I actually use this. The group chat trash talk has never been better
  • Sub-second leaderboard updates — Batch SQL ranking: $O(1)$ query time
  • Full-stack Dart — Shared models between Flutter and Serverpod eliminated serialization bugs

What we learned

  1. Future Calls > Cron for dynamic scheduling — When events don't follow a fixed schedule, Future Calls are the right abstraction. Game-changer.

  2. Start simple, optimize later — First version: $O(n^2)$. Production version: $O(1)$. Real usage teaches you where to optimize.

  3. Serverpod's full-stack Dart is underrated — Same language, same models, same types. The productivity boost compounds daily.

  4. Build for yourself first — The best features came from my own frustration using the app.


What's next for Stock League

Feature Description
Public Challenges Join challenges with friends and strangers, compete globally
Themed Challenges "Tech stocks only" or "Dividend kings" constraints
Historical Replays Backtest: "What if you picked in March 2020?"
Push Notifications Personalized Alert

Built With

Share this project:

Updates