DEV Community

Sam Hartley
Sam Hartley

Posted on

I Wanted Real-Time Stock Charts on My Garmin Watch. Monkey C Had Other Plans.

I Wanted Real-Time Stock Charts on My Garmin Watch. Monkey C Had Other Plans.

I wanted stock prices on my wrist. Not a notification — an actual chart. A little sparkline showing me if Bitcoin was crashing while I was making coffee. If Tesla was tanking during my run. Something I could glance at without pulling out my phone.

So I built it. And it almost broke me.

The Fantasy vs. The Reality

In my head, this was simple:

  1. Garmin has a watch face SDK
  2. The SDK can make web requests
  3. I pull stock data from an API
  4. I draw some lines
  5. Done

Reality check #1: Garmin watch faces are written in Monkey C. A language that — as far as I can tell — exists specifically to make mobile developers cry. No npm. No package manager. No Stack Overflow threads that actually answer your question. Just you, the docs, and a simulator that lies to you.

The Constraints Hit Fast

I have a Garmin Venu 2 Plus. Beautiful AMOLED display, 416×416 resolution, looks great. Here's what they don't advertise: 124KB total memory for your watch face. That's not a typo. One hundred and twenty-four kilobytes.

To put that in perspective: the JSON response from a stock API is often 50-80KB. My watch face needs to:

  • Store 5 tickers with 24 hours of price data each
  • Draw 5 sparkline charts
  • Render text labels
  • Handle weather data
  • Not crash

All in 124KB. I spent my first two days just figuring out what I could fit.

The Font Problem (AKA My First Mental Break)

I needed custom text. The watch face shows ticker symbols, prices, time, dates. Garmin has something called VectorFont that sounds perfect — scalable text that looks crisp at any size.

It doesn't exist on the Venu 2 Plus.

So I built my own. I call it PrimitiveFont and it's exactly what it sounds like: every single character is drawn using drawLine() calls on a 5×7 grid. The letter "A" is a collection of line segments. So is "$". So is every number.

Here's what the letter "A" looks like in my font definition:

// "A" on a 5×7 grid — just drawLine() calls
CHAR_A = [0,6, 0,2,  0,2, 2,0,  2,0, 4,2,  4,2, 4,6,  0,4, 4,4]
Enter fullscreen mode Exit fullscreen mode

It's 21KB for the full alphabet including bold, italic, underline, and — the thing I'm most proud of — arc text that curves along the watch's circular bezel. Because if you're going to build a font renderer from line segments, you might as well make it curve.

The API Situation

I chose Twelve Data for stock and crypto prices. Free tier: 800 calls per day. My math: 5 symbols × 96 fetches/day (every 15 minutes) = 480 calls. Comfortable margin.

But here's the thing nobody tells you: the Garmin simulator cannot make web requests. At all. You code the API call, you run the simulator, you get... nothing. To test a web request, you need a real phone connected via Bluetooth, with the Garmin Connect app running as a proxy, and the watch face actually installed on real hardware.

My testing loop became:

  1. Write code
  2. Build and push to watch
  3. Wait for 15-minute background interval
  4. Check if data appeared
  5. It didn't
  6. Guess why
  7. Repeat

I spent three evenings on this. Three. The fix was adding a phone proxy layer that Garmin provides but barely documents. The phone makes the HTTP request, passes the JSON through to the watch face, and the watch parses it in a 64KB background service sandbox that is completely separate from the main app's memory.

Yes, 64KB. For parsing JSON. I learned to extract exactly what I needed and discard everything else immediately.

The Sparklines

The whole point was the charts. I wanted 5 polar sparklines arranged in a ring around the watch face — each showing 24 hours of price movement. Green if the asset was up, red if down.

Drawing this in Monkey C meant:

  • Converting price data to angles (0° to 360° around the circle)
  • Drawing line segments between consecutive data points
  • Color-coding based on daily change
  • Keeping all coordinate math in integers (no floating-point performance hit)

It works. It actually looks pretty good. But getting there meant hand-tuning every pixel calculation because there's no charting library. No drawSparkline(). Just drawLine(x1, y1, x2, y2) and a lot of trigonometry I thought I'd never use again.

The Metadata Discovery

Early on, I hardcoded label mappings. "XAU/USD" → "GOLD". "BTC/USD" → "BITCOIN". Worked fine until I wanted to add Ethereum, then silver, then a random stock.

Then I noticed the Twelve Data API returns metadata with every response:

"meta": {
    "symbol": "XAU/USD",
    "currency_base": "Gold Spot",
    "type": "Precious Metal"
}
Enter fullscreen mode Exit fullscreen mode

Now labels generate themselves. Stocks use the symbol. Crypto uses the base currency name. Commodities get cleaned up automatically. I can add any ticker without touching the code.

Small win. Felt huge after the font nightmare.

What Actually Works

After all that pain, here's what's running on my wrist right now:

  • ⏱️ Time, date, heart rate, steps, floors climbed
  • 📊 5 sparkline charts showing 24h price data for configurable assets
  • 🔄 Auto-updates every 15 minutes via Twelve Data
  • 🌤️ 6-hour weather forecast with custom icons
  • ⚙️ Fully configurable tickers through the Garmin Connect app

Default watchlist: AAPL, TSLA, Gold, BTC/USD, ETH/USD. I change them depending on what I'm watching.

What I'd Do Differently

Start with a simpler device. The Venu 2 Plus is beautiful but the memory limits are brutal. A Fenix or Forerunner with more resources would have saved me weeks.

Read the simulator docs first. I wasted days assuming the simulator was a reasonable testing environment. It's not. It's a layout previewer. Real testing requires real hardware.

Don't build your own font. Just... don't. If VectorFont isn't available on your target device, pick a different device. I learned a lot but I wouldn't wish it on anyone.

Use the API metadata from day one. Hardcoding is always a trap. APIs give you labels, use them.

The Honest Verdict

Is this watch face useful? Honestly... sort of. I glance at it more than I expected. During market hours, it's genuinely handy to see BTC movement without unlocking my phone. During runs, it's just pretty data I ignore.

Was it worth the effort? As a product — maybe not. As a learning experience — absolutely. I now know more about constrained embedded development than any web project could have taught me. 124KB makes you care about every byte.

Would I build another Garmin app? Ask me in six months when I've forgotten the pain.


Sam Hartley is a solo dev who apparently enjoys suffering through proprietary SDKs. When he's not fighting Monkey C, he writes about local AI setups and home lab automation.

Custom dev work on Fiverr
Follow along on Telegram

Top comments (0)