Every serious trader I know has some version of the same problem: market opens at 6:30am PT, but the real action starts before that. Pre-market movers, overnight news, earnings revisions, options flow — by the time you sit down with coffee, the setup has already changed. I wanted a briefing on my phone before I was fully awake. So I built one.

The system runs every weekday at 5am PT via macOS launchd. It pulls pre-market data from the Alpaca API, feeds it to Claude claude-haiku-4-5, and iMessages me a structured briefing covering my open positions, the day's key levels, and any unusual options flow. It has been running for three months. Here is how I built it and what I learned.

The Architecture

The stack is deliberately simple. Three components, each doing one thing well:

Data Layer
Alpaca API
Intelligence
Claude Haiku
Scheduler
launchd plist

Alpaca gives you market data and portfolio state through a clean REST API. The free tier covers everything I need: account positions, current quotes, and a basic options flow endpoint. I use the paper trading account for this, not live — the briefing is read-only, so there is no risk of accidental orders.

Claude claude-haiku-4-5 is the analysis layer. I send it a JSON payload of my positions, their pre-market changes, key levels I have set in a config file, and any options sweep activity above a dollar threshold. It outputs a structured briefing in plain language. Haiku is fast and cheap — each morning run costs roughly $0.003 in API tokens. That is pennies per month.

launchd is macOS's built-in job scheduler, similar to cron but more reliable for desktop machines. A plist file tells it when to run my Python script. The critical detail is that launchd fires even if you were asleep — unlike cron, it does not miss jobs if the system is in a low-power state.

The Plist Setup

This is the piece most tutorials skip. Getting launchd right took me two debugging sessions.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC ...>
<plist version="1.0">
<dict>
  <key>Label</key>
  <string>com.sachin.portfolio-monitor</string>

  <key>ProgramArguments</key>
  <array>
    <string>/usr/local/bin/python3.12</string>
    <string>/Users/sachinrai/portfolio-monitor.py</string>
  </array>

  <key>StartCalendarInterval</key>
  <dict>
    <key>Hour</key><integer>5</integer>
    <key>Minute</key><integer>0</integer>
  </dict>

  <key>StandardOutPath</key>
  <string>/tmp/portfolio-monitor.log</string>

  <key>StandardErrorPath</key>
  <string>/tmp/portfolio-monitor-err.log</string>

  <key>EnvironmentVariables</key>
  <dict>
    <key>ALPACA_API_KEY</key>
    <string>LOADED_FROM_KEYCHAIN_AT_RUNTIME</string>
  </dict>
</dict>
</plist>

The key lesson: never store API keys in the plist file. I load them from macOS Keychain at runtime using the security find-generic-password CLI command inside the Python script itself. The plist stays clean and safe to version-control.

What the Briefing Contains

I spent three weeks iterating on the Claude prompt until the output was genuinely useful — not just data I could read myself, but actual analysis. The final briefing has four sections:

  • Position delta summary — each open position with its pre-market change, P&L, and whether it crossed a key level overnight.
  • Top movers scan — the three biggest pre-market moves in my watchlist with a one-line reason (earnings, news, sector rotation).
  • Options flow alerts — any unusual sweeps above $50K notional in names I track. This is the section I read first.
  • One-line market tone — Claude's read on whether the pre-market setup is risk-on, risk-off, or neutral, and why.

The market tone line is the most useful thing in the briefing. On mornings where I would have sat down and immediately traded, seeing "risk-off — VIX up 8%, NQ futures -0.9%, dollar strengthening" has stopped me from forcing setups that were not there.

What Breaks

Three months of daily runs taught me exactly where this system is fragile.

Sleep mode is the first killer. macOS will not fire a launchd job if the machine is asleep and "Power Nap" is disabled in System Settings. I learned this the hard way on a morning when I had put the Mac Mini to sleep to conserve power. Now I have a script that checks whether the monitor fired by 5:15am and sends me a watchdog alert if it did not.

API rate limits are the second issue. Alpaca's free tier has a burst limit, and if you fire multiple requests in the same second — which a naive sequential script will do — you hit a 429. I added a 1-second sleep between each data fetch call. Ugly but it works.

Pre-market data gaps are the third problem. Alpaca's pre-market quotes are only available from 4am ET. If the plist fires at 5am PT (which is 8am ET — well within pre-market hours), you are fine. But the first time I ran this on a holiday, Alpaca returned empty quotes for everything. Claude dutifully reported "no significant pre-market movement" because there was no data, not because markets were calm. I added a data quality check that verifies at least five positions have valid quotes before passing to Claude.

Results After 3 Months

The briefing has fired 61 out of 63 trading days (one failure was the sleep mode issue, one was an API outage). The average delivery time is 12 seconds after the scheduled fire time.

More importantly: it has changed how I start my trading day. I no longer open ThinkorSwim half-awake and start clicking. I have a context-loaded mental map of the day before I touch the platform. On at least four occasions, the options flow alert flagged something I would have missed that turned into a significant intraday move.

The total build time was one weekend. The total ongoing cost is under $2/month in API tokens. The return on investment, measured in fewer impulsive morning trades alone, has been well worth it.

If you want to build something similar, the full code is not public yet — I am cleaning it up. But the architecture is straightforward enough that you could replicate it from this post. The hardest part is not the code. It is writing a Claude prompt that produces briefings you actually trust.