Fifty-four bots. One Mac. They trade equities, forex, crypto, and prediction markets around the clock — plus monitoring, data processing, and strategy execution — all managed through Python and macOS's native job scheduler, launchctl.

Not a thought experiment. Not a "how to automate your business" listicle. What you're reading is the actual plumbing behind a live system moving real capital across multiple markets, built by a solo founder who also runs a commercial real estate practice and ships software products on parallel tracks.

Do you actually need Kubernetes, Airflow, or a cloud fleet to automate at scale? Here's the answer I wish someone had given me three years ago. You don't.

Why Python for Everything

Python hit a 26.98% TIOBE Index rating in July 2025 — the highest any language has achieved since the index started in 2001, running 17 percentage points ahead of C++. GitHub's Octoverse 2024 showed it overtaking JavaScript as the most-used language on the platform for the first time, riding a 59% surge in AI contributions.

Honestly? That's not why I chose it. The ecosystem just happens to be absurdly deep for exactly the kind of work these bots do.

What my bots need:

  • Brokerage API clients (Schwab, OANDA, Alpaca)
  • Market data feeds and WebSocket connections
  • Statistical analysis and signal processing
  • HTTP clients for prediction market APIs (Polymarket, Kalshi)
  • Blockchain interaction (Solana via solana-py)
  • Logging, alerting, error recovery

Every single one of those has a mature Python library behind it. PyPI: 600,000+ packages, 2.56 trillion requests in 2025 (per their Year in Review). Not a toy ecosystem.

Could I write faster bots in Go or Rust? Absolutely. But would the development velocity trade-off make sense for a single person juggling 54 strategies across five markets? No chance. Python gets me from idea to deployed bot in an afternoon, sometimes less. If I were chasing sub-millisecond HFT execution, sure, I'd reach for a compiled language — but my strategies live or die by reliability and rapid iteration, not nanosecond latency.

Why launchctl Instead of Cron, Celery, or the Cloud

People are always surprised by this one. No cron. No Celery, no Airflow, no cloud orchestration layer. Just launchd — macOS's native service manager — controlled through launchctl.

launchctl vs. Alternatives

Feature launchctl (macOS) cron (Linux/macOS) systemd (Linux) Celery/Airflow Cloud (Lambda/ECS)
Restart on crash Built-in (KeepAlive) No Yes (Restart=always) Manual config Depends on service
Schedule precision Seconds Minutes Seconds (timers) Seconds Minutes (cron triggers)
Resource limits Process-level None cgroups Application-level Service-level
Log management StandardOutPath/ErrorPath Redirect manually journald Custom CloudWatch/equivalent
Setup complexity XML plist file One-line crontab Unit file Broker + worker + backend IAM + VPC + container + deploy
Monthly cost $0 (local hardware) $0 $0 $0 (self-hosted) $50-500+ at scale
macOS native Yes Deprecated on macOS No No No

Apple deprecated cron on macOS years ago and killed periodic entirely in macOS Sequoia. If you're developing on a Mac — and according to the Stack Overflow Developer Survey 2025, 32.9% of professional developers do — launchctl isn't an option. It's the intended path.

Here's the killer feature, though: KeepAlive. Bot crashes at 3 AM? launchd restarts it before anyone notices. No supervisor process. No watchdog script. No PagerDuty alert yanking me out of sleep. When you're a team of one with no NOC (network operations center), that isn't a nice-to-have — it's what keeps you sane.

The Anatomy of a Bot plist

Every bot lives as a property list (plist) file in ~/Library/LaunchAgents/:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.trading.schwab-scalper</string>
    <key>ProgramArguments</key>
    <array>
        <string>/usr/local/bin/python3</string>
        <string>/Users/zach/bots/schwab-scalper/main.py</string>
    </array>
    <key>KeepAlive</key>
    <true/>
    <key>StandardOutPath</key>
    <string>/Users/zach/trading_logs/schwab-scalper.log</string>
    <key>StandardErrorPath</key>
    <string>/Users/zach/trading_logs/schwab-scalper-error.log</string>
    <key>EnvironmentVariables</key>
    <dict>
        <key>PYTHONUNBUFFERED</key>
        <string>1</string>
    </dict>
</dict>
</plist>

KeepAlive set to true means launchd restarts the process whenever it exits. PYTHONUNBUFFERED=1 ensures log output writes immediately — critical when you're reconstructing a 2 AM crash from your 7 AM log review.

Two commands. That's the entire management interface:

# Load and start a bot
launchctl load ~/Library/LaunchAgents/com.trading.schwab-scalper.plist

# Check running bots
launchctl list | grep com.trading

# Stop and unload
launchctl unload ~/Library/LaunchAgents/com.trading.schwab-scalper.plist

No container registry. No deploy pipeline. No YAML indentation nightmares.

The 54-Bot Architecture

Three tiers.

Tier 1: Core Strategies (5 bots)

These run continuously:

  • schwab-scalper — equity momentum on Schwab
  • oanda-forex — forex pairs on OANDA
  • solana-autobot — on-chain Solana strategies
  • polymarket-trader — prediction market positions
  • autoexit-trader — automated exit management across accounts

Each core bot maintains its own state, manages its own API authentication, writes to its own log file. None of them talk to each other. That's by design — when one breaks, the others don't even flinch.

Tier 2: Arbitrage Bots (45+ bots)

These hunt specific market inefficiencies:

  • Carry trades — interest rate differentials across forex pairs
  • Calendar spreads — exploiting term structure in options and futures
  • Cross-index arbitrage — pricing discrepancies between correlated indices
  • DEX/CEX arbitrage — price gaps between decentralized and centralized exchanges

Each arb bot is a single Python script. Most clock in under 200 lines — check a condition, execute if profitable, sleep. I can read any bot's entire logic in five minutes. That simplicity? Deliberate.

Tier 3: Infrastructure Bots (4 bots)

  • Health checker — pings all broker APIs every 60 seconds, alerts on failure
  • Log rotator — keeps log files from eating the disk
  • Portfolio reconciler — compares expected vs. actual positions across accounts
  • Daily reporter — compiles P&L, sends a summary

Why Not a Monolith?

Tried it once. Built a unified orchestration framework. Huge mistake.

McKinsey says 57% of US work hours could be automated with existing technology (November 2025). When you hear a number like that, the temptation is obvious — one elegant system, one codebase, one deployment. But a monolithic bot framework means a forex bug crashes the equity module. A Solana dependency upgrade poisons OANDA. One process shouldering 54 strategies needs 54 strategies' worth of error handling tangled together in a single repo.

So: separate processes, separate failure domains. When com.trading.arb-carry-17 crashes because OANDA returned something unexpected, the other 53 bots are blissfully unaware. launchd restarts the fallen bot, it picks up where it left off, and I find out over coffee. Not at 3 AM.

What Breaks (and How I Fix It)

I've written about sharing failures honestly. Here are the real ones from running this stack in production.

API Rate Limits

Every brokerage enforces rate limits, and the documentation is rarely accurate. Schwab's published limits and actual limits diverge — I've been throttled at half the documented threshold during volatile sessions. OANDA stays more consistent but still clamps down when volume spikes unexpectedly.

The fix: Exponential backoff on every API call. Fail, wait 1 second, then 2, then 4, capping at 60. After a full minute of continuous failure, log a critical alert and let the main loop proceed. Zero bots have ever crashed on a rate limit since implementing this.

Token Expiration at 2 AM

OAuth (Open Authorization) tokens expire. Brokerage tokens expire. Blockchain RPC endpoints rotate. When a token dies mid-execution, the bot enters a failure loop — retrying the same dead credential until it re-authenticates.

The fix: Pre-flight token refresh at the start of every cycle. Check expiry before making any market calls. Token within 5 minutes of expiry? Refresh proactively. Should've been obvious from day one. Took three failed trades before I wised up.

The Silent Failure Problem

Here's the scariest class of bug: bots that keep running but stop doing anything useful. An exception caught too broadly. A loop spinning forever without placing a single trade. I had one bot run like that for six days. Six full days of nothing, zero alerts, zero indication anything was wrong.

ITIC's 2024 data says 90%+ of businesses estimate downtime at $300,000/hour or more. Scale that down for a solo operation and the dollars shrink — but the detection problem stays exactly the same. Nobody's staring at dashboards.

The fix: Heartbeat monitoring. Every bot writes a timestamp to a shared file every 60 seconds. The health checker reads all heartbeats and flags any timestamp older than 5 minutes. Running process, no fresh heartbeat — silent failure caught. This was the fourth bot I built and it should've been the first.

Disk Space Exhaustion

When 54 bots write logs around the clock, any disk fills eventually. Found this out the hard way on a Saturday — the machine ground to a halt, every bot died simultaneously because none could write to their log files. All at once. Spectacular.

The fix: Log rotation bot plus per-bot size limits. Rotate daily, compress after 3 days, delete after 30. Total log storage stays under 2 GB.

The Economics of Self-Hosted Automation

Running 54 bots locally costs roughly $8/month in electricity. No AWS bill. No Lambda invocations. No egress charges.

What would the cloud equivalent look like?

Component Local (My Setup) AWS Equivalent
Compute Mac Mini (existing hardware) 54 Lambda functions or 3-4 ECS tasks
Scheduling launchctl (free, built-in) EventBridge + Step Functions
Logging Local files + rotation bot CloudWatch Logs ($0.50/GB ingested)
Monitoring Health check bot CloudWatch Alarms + SNS
Secrets Environment variables AWS Secrets Manager ($0.40/secret/month)
Monthly cost ~$8 (electricity) $150-400 (conservative estimate)
Annual cost ~$96 $1,800-4,800

MBO Partners counts 72.7 million independent American workers in 2024, 5.6 million of them earning over $100,000 annually (2024 and 2025 State of Independence Reports). When you're independent, every dollar of infrastructure overhead comes straight out of your margin. Spending $4,800/year on cloud hosting when a Mac Mini handles it natively is, to put it bluntly, bad capital allocation.

The trade-off is real, though. Local means I own the uptime problem entirely — power goes out, everything stops; ISP drops, network-dependent bots fail. I mitigate with a UPS (uninterruptible power supply) for short outages and cellular failover for network, but genuine HA (high availability) would require redundant hardware, and that erases the cost advantage pretty quickly.

Here's my mental model: 99.5% uptime on local hardware beats 99.99% uptime at 50x the cost — as long as you're not serving external customers. My bots trade my money. An occasional four-hour outage during a power grid hiccup? Acceptable risk at this price point.

How I'd Rebuild It Today

Three years into this stack. Here's what I'd change.

Keep: Python, launchctl, separate processes, local execution. Core architecture is right.

Add: A lightweight message queue between bots. Right now they're fully isolated — phenomenal for fault tolerance, awkward for cross-strategy coordination. Redis pub/sub would let bots share market state without coupling their execution. During correlated drawdowns, I've wanted this more than once.

Change: Centralized config. Currently each bot manages its own config file, which means updating API endpoints or risk parameters across all 54 is a slog. A single JSON/TOML config with per-bot overrides would cut that overhead dramatically.

Remove: Hand-written XML plists. Apple's format works fine, but authoring XML by hand for 54 bots? Tedious and error-prone. I'd generate plists from a Python script reading centralized config — turning "add a new arb bot" from a 10-minute manual chore into one command.

Gusto's solopreneurship report: 56% of solopreneurs started since 2020. The tooling for one-person operations has never been more capable. You don't need a DevOps team to run automation at scale. What you need is a clear architecture, boring technology choices, and — maybe hardest of all — the discipline to not make it clever.

Lessons for Other Solo Founders

Three years of 54 bots, distilled:

Start with one bot, not a framework. Write a single script that does one thing well. Get it running reliably with launchctl or cron. Only abstract once you've got three bots sharing patterns. I burned a full month designing an orchestration framework — config inheritance, plugin architectures, dynamic scheduling — before I had anything to orchestrate. Classic over-engineering.

Log everything, read logs daily. Retool's 2022 survey says developers spend 33% of their time on internal tools. For a solo automation operation, the most important internal tool is your logging, full stop. Every bot decision should carry enough context in the log to reconstruct what happened the next morning. Can't figure out why a bot did something from the logs alone? Your logging isn't good enough yet.

Separate processes, shared nothing. ITIC's 2024 data: 1 in 5 small businesses can't survive a single $10,000 incident. Correlated failures — one bug cascading through your entire system — represent the highest-risk scenario for solo operations. Isolated processes eliminate that vector entirely.

Monitor for silence, not just errors. A bot that stops working quietly is far more dangerous than one that crashes loudly. Build heartbeat monitoring from day one. I didn't, and it cost me six days of a bot running dead before I caught it.

Frequently Asked Questions

How much does it cost to run 54 automated bots?

About $8/month in electricity on a Mac Mini. The equivalent AWS setup: $150-400/month for comparable compute, logging, and scheduling. MBO Partners counts 72.7 million independent American workers (2024) — for all of us, infrastructure cost comes straight out of margin. Local hosting wins for non-customer-facing workloads.

Is Python fast enough for automated trading?

For my strategies, yes. The bottleneck isn't Python — it's network latency to broker APIs. My bots operate on timeframes of seconds to hours, not microseconds. Python hit a 26.98% TIOBE rating (July 2025), the highest ever, largely because developer velocity matters more than raw speed for most applications.

Why launchctl instead of Docker or Kubernetes?

Docker and Kubernetes solve problems I don't have. One machine, not a cluster. launchctl is built into macOS, handles process supervision with KeepAlive, and needs nothing beyond an XML file. Stack Overflow's 2025 survey: 32.9% of professional developers use macOS. If you're already on a Mac, launchd is the native, zero-dependency answer.

How do you handle bot failures at 3 AM?

launchd's KeepAlive restarts any crashed bot automatically. For silent failures — bots that run but stop working — I use heartbeat monitoring where each bot writes a timestamp every 60 seconds. A health checker bot alerts if any heartbeat goes stale. I check logs in the morning, not at 3 AM.

Can this approach scale beyond a single machine?

Vertically, yes — a modern Mac handles hundreds of lightweight Python processes. For horizontal scaling, you'd move to Nomad, systemd on Linux, or cloud orchestration. My 54 bots use under 4 GB of RAM combined. I'll hit the practical limit around 200 bots, at which point I'd shard across two machines before touching the cloud.

What's the biggest mistake you made building this system?

Building an orchestration framework before I had anything to orchestrate. Spent a month designing config inheritance, plugin architectures, and dynamic scheduling — before a single profitable bot existed. The first version that actually made money was a cron job and a 47-line Python script. Start with the simplest thing that works.


A one-person automation stack across five markets isn't magic — it's 54 small, boring programs managed by an OS feature that's been stable since 2005. The stack works because it's simple, not despite it. For what actually breaks at the strategy level — backtest-to-live gaps, correlated drawdowns, strategy decay — and what breaks at the system level when everything's automated — those are separate posts. The context engineering layer that orchestrates how AI agents interact with this stack, the agentic engineering methodology that structures each development session, the framework for when to trust AI-generated code, and how agentic AI handles the daily operations all build on this foundation. I apply the same analytical rigor to financial modeling and CRE deal evaluation. For the philosophy behind building this way, see how I build in public. Explore what I'm working on or get in touch.