<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Maciek Lamberski</title>
    <description>Writing about the web and building software.</description>
    <language>en</language>
    <lastBuildDate>Mon, 02 Feb 2026 00:00:00 GMT</lastBuildDate>
    <generator>Feedsmith</generator>
    <atom:link href="https://lamberski.com/feed" rel="self" type="application/rss+xml"/>
    <atom:link href="https://lamberski.com" rel="alternate" type="text/html"/>
    <item>
      <title>Minimalist API for stock prices in Google Sheets #2</title>
      <link>https://lamberski.com/blog/googlefinance-alternative-2</link>
      <guid isPermaLink="true">https://lamberski.com/blog/googlefinance-alternative-2</guid>
      <pubDate>Mon, 02 Feb 2026 00:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>When I initially <a href="/blog/googlefinance-alternative">came up with Stonkista</a>, I was naive to think that Google Sheets would happily welcome my idea with open arms. It didn’t take long to realize I had only replaced <code>#N/A</code> errors with the equally useless <code>Loading…</code> state in cells.</p>
<p>My spreadsheet includes a page that tracks a portfolio in various currencies over the last few years with weekly data points. That&#39;s roughly 270 rows × 5 columns. 1350 cells. And every single one of those cells called the API via <code>IMPORTDATA()</code>. That couldn&#39;t work.</p>
<p>After a bit of research, I found that <b>Google Sheets seems to cap external data calls at around 50 per sheet</b>. So the only viable solution was to load data in batches.</p>
<h2>Batching endpoints</h2>
<p>To make that possible, I added a new type of endpoints that return prices for a given date range.</p>
<pre><code class="language-text">/forex/usd/pln/2021-01-01..2025-12-31
</code></pre>
<p>The response is a list of prices separated by a new line, which Google Sheets conveniently renders as separate rows.</p>
<pre><code class="language-text">3.719628473
3.742906631
3.742885424
3.742885424
3.742885424
3.762350796
3.73821299
3.745310305
3.724625761
…
</code></pre>
<p>That means I can now pull the full dataset with a single formula:</p>
<pre><code class="language-text">=IMPORTDATA(&quot;https://stonkista.com/forex/chf/pln/2021-01-01..&quot; &amp; TEXT(TODAY(),&quot;yyyy-mm-dd&quot;))
</code></pre>
<p>I’ve been testing this for the last few weeks, and so far it has been very reliable. Instead of hundreds of individual requests, I can load a large chunk of historical data at once and chart the portfolio over time.</p>
<p>The main downside is that the sheet still takes a few seconds to fetch everything when the document opens. But that seems to be a general trade-off when importing external data into Google Sheets.</p>
<hr>
<p>For now, I’m calling this version done. It’s stable, it solves the problem I built it for, and it has held up well in my use. The only thing it’s really missing is a small website with documentation and examples so it’s easier for others to use too.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Minimalist API for stock prices in Google Sheets</title>
      <link>https://lamberski.com/blog/googlefinance-alternative</link>
      <guid isPermaLink="true">https://lamberski.com/blog/googlefinance-alternative</guid>
      <pubDate>Mon, 15 Dec 2025 00:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>For a few years now, I&#39;ve been using Google Sheets to manage my investment portfolio. It shows the current state of the investments as well as the historical data on charts, allowing me to see how the internet-informed decisions impacted it over time.</p>
<p>To get the prices of various instruments, I use the built-in <code>GOOGLEFINANCE()</code> function. However, this is not without its cons. First, it does not have all the tickers I need, and is known for <a href="https://issuetracker.google.com/issues/182524129">causing</a> <a href="https://www.bogleheads.org/forum/viewtopic.php?t=415943">problems</a> <a href="https://support.google.com/docs/thread/320455428/googlefinance-not-working">with frequent</a> <a href="https://stackoverflow.com/q/59860596">#N/A errors</a>. This issue is especially annoying when many prices depend on each other. One invalid cell is enough to disrupt the entire calculation or chart. More than once I opened my spreadsheet only to find many of the numbers replaced with errors.</p>
<h2>Looking for an alternative</h2>
<p>I started looking for an alternative which would cover all my needs:</p>
<ul>
<li>support for ETFs, commodities, forex, and crypto,</li>
<li>the ability to get historical data of each instrument.</li>
</ul>
<p>Ideally, the replacement would also be as simple as the solution built into Google Sheets. One function you pass an argument to and get the value in return. No explicit casting to number, no plucking for nested JSON values.</p>
<p>After some research, I found <a href="https://cryptoprices.cc">Crypto Prices</a>. It checks the box for simplicity, but is limited to cryptocurrencies and only current prices. Though not ideal, I really liked the idea of using <code>IMPORTDATA()</code> with API endpoints returning text with the price. Inspired by this simplicity, I decided to build the solution myself.</p>
<h2>Custom solution</h2>
<p>This solution is <strong>Stonkista</strong>. A minimalist API service inspired by Crypto Prices, but designed for broad markets and providing access to historical data. The name is kinda dorky, but I like it! </p>
<p>Getting the prices is a breeze and endpoints pretty self-explanatory. Want the current price of Apple stock? Put this into the cell and that&#39;s it.</p>
<pre><code class="language-text">=IMPORTDATA(&quot;https://stonkista.com/AAPL&quot;)
</code></pre>
<p>Stonkista has no usage limits, no sign ups, no API keys. It&#39;s open source and <a href="https://github.com/macieklamberski/stonkista">available on GitHub</a>.</p>
<h2>Implementation</h2>
<p>First, I needed to find data sources. I wanted free sources with current and historical data, and ideally accessible without any API keys. I ended up choosing:</p>
<ul>
<li><a href="https://www.coingecko.com/api">Coin Gecko API</a> for cryptocurrency prices (also used by Crypto Prices). Can be used either with or without an API key.</li>
<li><a href="https://frankfurter.dev">Frankfurter</a> for currency rates. It provides the official data published by the European Central Bank and includes ~30 commonly traded currencies.</li>
<li><a href="https://finance.yahoo.com">Unofficial Yahoo Finance API</a> for all the other instruments. Though unofficial, it has worked reliably for a long time. There are even Python/JS wrappers for it. I decided to use the API directly.</li>
</ul>
<p>I used my usual go-to stack: Bun, Hono, Drizzle, BullMQ, Redis and Postgres, stitched together with a Docker Compose. Created background jobs to call the APIs periodically and store current prices. I also wrote a script to backfill the database with historical data.</p>
<h3>API design</h3>
<p>I wanted the URLs to be obvious at a glance. Ticker first, then optional modifiers like currency or date:</p>
<pre><code class="language-text">/AAPL              → current price of Apple
/AAPL/EUR          → converted to EUR
/AAPL/2024-01-15   → historical price
/crypto/BTC/PLN    → Bitcoin in PLN
/forex/USD/PLN     → exchange rate
</code></pre>
<p>One quirk I ran into was that many crypto symbols conflict with stock tickers. <code>ETH</code> is both Ethereum and Ethan Allen Interiors, <code>LUNA</code> could be Terra or Luna Innovations. I prefixed crypto endpoints with <code>/crypto/</code> to avoid ambiguity.</p>
<p>Another thing to handle was price holes. Markets don&#39;t trade on weekends and holidays, so instead of returning errors for those dates, the API falls back to the last available price.</p>
<h3>Lazy fetching</h3>
<p>Yahoo Finance has thousands of tickers, so pre-populating the database with all of them wasn&#39;t practical. Instead, when someone requests a ticker that&#39;s not in the database, it gets fetched from Yahoo on demand, including full price history, and cached for future requests. The first request is slower, but all following ones are instant.</p>
<h2>Wrapping up</h2>
<p>My own spreadsheet pulls ~20 tickers with history going back to 2021. No more #N/A cascades. There are limitations, though, where Google Sheets struggles to load many values at once, sometimes taking a long time to populate. I might dig into those in a future post.</p>]]>
      </content:encoded>
    </item>
  </channel>
</rss>
