Liking cljdoc? Tell your friends :D

clj-yfinance

Clojars Project CI cljdoc badge License

A pure Clojure client for Yahoo Finance — prices, historical OHLCV, dividends, splits, fundamentals, financial statements, analyst estimates, options chains, and more.

No Python bridge. No API key. No external HTTP dependencies. Just Clojure and the java.net.http.HttpClient that ships with JDK 11+.

Why clj-yfinance?

  • Zero-config data access — fetch live prices, historical bars, and company fundamentals with a single function call.
  • Parallel multi-ticker fetching — bounded-concurrency batch requests with per-ticker error handling.
  • Two-tier API — simple functions return data directly; verbose (*) variants return {:ok? :data :error} maps for production error handling.
  • First-class data science integration — built-in support for tech.ml.dataset, tablecloth, Noj, Kindly/Clay, Parquet, and DuckDB.
  • Minimal footprint — one runtime dependency (charred for JSON parsing). Everything else is optional.

At a Glance

CapabilityNamespaceStatus
Current prices (single & batch)clj-yfinance.coreStable
Historical OHLCV barsclj-yfinance.coreStable
Dividends & stock splitsclj-yfinance.coreStable
Ticker metadataclj-yfinance.coreStable
Company fundamentals & profileclj-yfinance.experimental.fundamentalsExperimental
Analyst estimates & recommendationsclj-yfinance.experimental.fundamentalsExperimental
Full income/balance/cash-flow statementsclj-yfinance.experimental.timeseriesExperimental
Earnings report dates & EPS surprisesclj-yfinance.experimental.earningsExperimental
Earnings calendar & dividend datesclj-yfinance.experimental.fundamentalsExperimental
Options chainsclj-yfinance.experimental.optionsExperimental
Dataset conversion (tech.ml.dataset)clj-yfinance.datasetStable
Kindly-tagged datasets (Clay/Portal)clj-yfinance.kindlyStable
Parquet read/writeclj-yfinance.parquetStable
DuckDB SQL queriesclj-yfinance.duckdbStable

Stable features use Yahoo's public chart endpoint, which has been reliable for years.
Experimental features use Yahoo's authenticated quoteSummary, options and visualization endpoints, or its undocumented fundamentals-timeseries endpoint — they work today but may break if Yahoo changes them or their authentication mechanism.

Installation

;; deps.edn
com.github.clojure-finance/clj-yfinance {:mvn/version "0.1.10"}

;; project.clj
[com.github.clojure-finance/clj-yfinance "0.1.10"]

Requires JDK 11+. The only runtime dependency is charred for JSON parsing. All other integrations (datasets, Parquet, DuckDB, Kindly) are optional — add their libraries to your deps only if you use them.


Quick Start

(require '[clj-yfinance.core :as yf])

;; Current price
(yf/fetch-price "AAPL")
;; => 261.05

;; Batch prices in parallel
(yf/fetch-prices ["AAPL" "GOOGL" "MSFT" "0005.HK"])
;; => {"AAPL" 261.05, "GOOGL" 337.28, "MSFT" 428.04, "0005.HK" 59.85}

;; Daily bars for the past year
(yf/fetch-historical "AAPL" :period "1y")
;; => [{:timestamp 1704067200, :open 185.2, :high 186.1, :low 184.0,
;;      :close 185.5, :volume 12345678, :adj-close 185.5} ...]

;; As a typed dataset
(require '[clj-yfinance.dataset :as yfd])
(yfd/historical->dataset "AAPL" :period "1mo")
;; => #tech.v3.dataset [:timestamp :open :high :low :close :volume :adj-close]

Core API

All stable functions live in clj-yfinance.core. Every function has two flavours:

  • Simple (fetch-price, fetch-historical, …) — returns data directly, or nil/[] on failure.
  • Verbose (fetch-price*, fetch-historical*, …) — returns {:ok? true :data …} or {:ok? false :error {…}} for structured error handling.

Prices

;; Single ticker
(yf/fetch-price "AAPL")
;; => 261.05

;; Multiple tickers with bounded concurrency (default 8 threads)
(yf/fetch-prices ["AAPL" "GOOGL" "MSFT" "0005.HK"])
;; => {"AAPL" 261.05, "GOOGL" 337.28, "MSFT" 428.04, "0005.HK" 59.85}

;; Lower concurrency to avoid rate limiting
(yf/fetch-prices large-ticker-list :concurrency 2)

;; Verbose — per-ticker success/failure detail
(yf/fetch-prices* ["AAPL" "INVALID"])
;; => {"AAPL"    {:ok? true  :data 261.05}
;;     "INVALID" {:ok? false :error {:type :http-error :status 404 ...}}}

Historical Data

(import '[java.time Instant Duration])

;; Daily bars for the past month
(yf/fetch-historical "AAPL" :period "1mo")

;; Intraday with a custom date range
(yf/fetch-historical "TSLA"
                     :start (.minus (Instant/now) (Duration/ofDays 7))
                     :interval "1h"
                     :prepost true)   ; include pre/post market
ParameterValues
:period1d 5d 1mo 3mo 6mo 1y 2y 5y 10y ytd max
:interval1m 2m 5m 15m 30m 60m 90m 1h 1d 5d 1wk 1mo 3mo
:start / :endepoch seconds (integer) or java.time.Instant; :start overrides :period
:auto-adjustback-adjust :open/:high/:low/:close by the adjclose/close ratio, like python-yfinance's auto_adjust=True (default true; pass :auto-adjust false for Yahoo's raw, split-adjusted-only OHLC)
:prepostinclude pre/post market data (default false)
:eventsalso return the window's dividends and splits, from the same request (default false; verbose variant only)

Each row is {:timestamp :open :high :low :close :volume :adj-close}. :adj-close is Yahoo's dividend- and split-adjusted close and is always included when available — under the default :auto-adjust true it equals :close by construction; with :auto-adjust false it is the only dividend-adjusted figure in the row. Unlike python-yfinance, the column is never dropped, so datasets, Parquet files and DuckDB tables always have the same shape.

The verbose fetch-historical* also returns the instrument's identity and exchange details as :meta, and with :events true the dividends and splits of the same window as :events (in the shape fetch-dividends-splits returns), so one request gives the bars and everything needed to interpret them:

(def r (yf/fetch-historical* "AAPL" :period "1y" :events true))

(:meta r)
;; => {:symbol "AAPL" :long-name "Apple Inc." :short-name "Apple Inc."
;;     :currency "USD" :exchange-name "NMS" :full-exchange-name "NasdaqGS"
;;     :instrument-type "EQUITY" :first-trade-date 345479400
;;     :timezone "EDT" :exchange-timezone-name "America/New_York"
;;     :gmt-offset -14400 :price-hint 2}

(:events r)
;; => {:dividends {:1786368600 {:amount 0.27 :date 1786368600} ...}   ; 4 in the past year
;;     :splits    {}}

Invalid parameter combinations are caught before any network call and returned as :invalid-opts errors. Technically valid but potentially problematic combinations (e.g. 1m interval over a 30-day range) produce a warning in the :warnings key of the verbose response.

Dividends & Splits

(yf/fetch-dividends-splits "AAPL" :period "10y")
;; => {:dividends {:1699574400 {:amount 0.24 :date 1699574400} ...}
;;     :splits    {:1598832000 {:date 1598832000 :numerator 4.0 :denominator 1.0 :splitRatio "4:1"} ...}}

Events are keyed by a keywordized epoch timestamp (Yahoo's JSON object keys, which need not equal the event date); use each event's :date as the event time. Accepts the same :period, :start, :end options as fetch-historical. Default period is "5y".

Split factors

Yahoo back-adjusts all historical quotes for splits, so share counts recorded before a split no longer match the price series. cumulative-split-factor (pure) and fetch-split-factor (fetching) compute the factor by which such a position's share count must be multiplied (and its per-share cost basis divided) to be consistent with today's quotes:

;; One NVDA share bought before the 2024-06-10 10:1 split is 10 shares today
(yf/fetch-split-factor "NVDA" (Instant/parse "2024-06-01T23:59:59Z"))
;; => 10.0

;; Pure variant, reusing already-fetched events
(let [{:keys [splits]} (yf/fetch-dividends-splits "NVDA" :period "max")]
  (yf/cumulative-split-factor splits (Instant/parse "2024-06-01T23:59:59Z")))
;; => 10.0

Splits dated exactly at the since timestamp are excluded (trades on the effective date are already in post-split units); with date-granularity trade data, pass the end of the trade day.

Ticker Info

(yf/fetch-info "AAPL")
;; => {:symbol "AAPL"
;;     :long-name "Apple Inc."
;;     :currency "USD"
;;     :exchange-name "NMS"
;;     :regular-market-price 261.05
;;     :regular-market-volume 92443408
;;     :fifty-two-week-high 288.62
;;     :fifty-two-week-low 169.21
;;     :timezone "America/New_York"
;;     ...}

Returns identifiers, current price, day/52-week ranges, and exchange info from Yahoo's public chart endpoint. For richer company data (sector, description, officers, P/E), see fetch-company-info in the experimental namespace.

Error Handling

The verbose API provides structured errors with enough context for intelligent retry logic:

(let [result (yf/fetch-price* "AAPL")]
  (if (:ok? result)
    (:data result)
    (case (-> result :error :type)
      :rate-limited  (do (Thread/sleep 5000) (yf/fetch-price "AAPL"))
      :http-error    (println "Bad ticker or endpoint:" (-> result :error :status))
      :parse-error   (println "Yahoo changed their format")
      nil)))

See Error Types Reference for the full list.


Experimental: Fundamentals, Financials & Analyst Data

⚠️ Uses Yahoo's authenticated quoteSummary endpoint. Authentication is fully automatic (cookie + crumb, cached for one hour). No API key required. Works reliably today but Yahoo can change this at any time.

(require '[clj-yfinance.experimental.fundamentals :as yff])
FunctionReturns
fetch-fundamentals / *P/E, market cap, margins, revenue, analyst price targets
fetch-company-info / *Sector, industry, description, employees, officers
fetch-analyst / *EPS/revenue estimates, buy/hold/sell trends, earnings surprises
fetch-financials / *Statement modules of quoteSummary (Yahoo now serves only a few fields here; see Financial Statements)
fetch-calendar / *Upcoming earnings dates, EPS/revenue estimates, ex-dividend date
fetch-quotesummary*Raw access to any quoteSummary module combination
;; Key fundamentals
(yff/fetch-fundamentals "AAPL")
;; => {:financialData      {:currentPrice    {:raw 255.78  :fmt "255.78"}
;;                          :recommendationKey "buy"
;;                          :profitMargins   {:raw 0.27    :fmt "27.04%"}
;;                          :targetMeanPrice {:raw 292.15  :fmt "292.15"} ...}
;;     :defaultKeyStatistics {:beta           {:raw 1.107   :fmt "1.11"}
;;                            :forwardPE      {:raw 27.54   :fmt "27.54"} ...}}

;; Company profile
(yff/fetch-company-info "AAPL")
;; => {:sector "Technology"
;;     :industry "Consumer Electronics"
;;     :fullTimeEmployees 150000
;;     :longBusinessSummary "Apple Inc. designs..."
;;     :companyOfficers [{:name "Mr. Timothy D. Cook" :title "CEO & Director" ...}]
;;     ...}

;; Analyst estimates
(yff/fetch-analyst "AAPL")
;; => {:earningsTrend       {:trend [{:period "0q" :earningsEstimate {:avg {:raw 1.95}} ...}]}
;;     :recommendationTrend {:trend [{:period "0m" :strongBuy 5 :buy 23 :hold 16 :sell 1}]}
;;     :earningsHistory     {:history [{:epsActual {:raw 1.65} :epsEstimate {:raw 1.62}
;;                                      :surprisePercent {:raw 0.0169}} ...]}}

;; Financial statements (annual by default; use :period :quarterly)
(yff/fetch-financials "MSFT")
(yff/fetch-financials "AAPL" :period :quarterly)

;; Upcoming earnings & dividend dates
(yff/fetch-calendar "AAPL")
;; => {:earnings {:earningsDate [{:raw 1777582800 :fmt "2026-04-30"}] ...}
;;     :exDividendDate {:raw 1770595200 :fmt "2026-02-09"} ...}

;; Raw module access
(yff/fetch-quotesummary* "AAPL" "assetProfile,earningsTrend")

Data format note: Yahoo returns numeric values as {:raw <number> :fmt <string>} maps. Use :raw for calculations, :fmt for display.

Session management:

(require '[clj-yfinance.experimental.auth :as auth])
(auth/session-info)     ;; => {:status :active, :age-minutes 12.3, :crumb-present? true}
(auth/force-refresh!)   ;; force a new session

Experimental: Financial Statements

⚠️ Uses Yahoo's undocumented fundamentals-timeseries endpoint (the one behind Yahoo's own financials pages). It needs no cookie or crumb, but Yahoo can change it at any time.

(require '[clj-yfinance.experimental.timeseries :as yfs])
FunctionReturns
fetch-statements / *Income statement, balance sheet and cash flow line items as rows
fetch-timeseries / *Raw access by Yahoo type string (e.g. "quarterlyTotalRevenue")
statement-fieldsThe field catalog: {:income-statement [...] :balance-sheet [...] :cash-flow [...]} (Yahoo names, 375 in all)
field->keywordYahoo name to row keyword: "DilutedEPS" → :diluted-eps, "NetPPE" → :net-ppe
;; Every statement at every frequency (about 1000 Yahoo types, 10 requests)
(yfs/fetch-statements "AAPL")

;; Narrow it down
(yfs/fetch-statements "AAPL" :frequencies [:quarterly] :fields ["TotalRevenue"])
;; => [{:ticker "AAPL" :statement :income-statement :field :total-revenue
;;      :yahoo-field "TotalRevenue" :frequency :quarterly
;;      :period-end "2025-06-30" :period-type "3M" :currency "USD" :value 9.4036E10}
;;     {... :period-end "2025-09-30" ... :value 1.02466E11}
;;     ...]                                             ; 5 quarters

(yfs/fetch-statements "NVDA" :statements [:balance-sheet] :frequencies [:annual])

;; Raw access: rows sorted by :type, then :period-end
(yfs/fetch-timeseries "MSFT" ["quarterlyDilutedEPS" "trailingTotalRevenue"])
;; => [{:ticker "MSFT" :type "quarterlyDilutedEPS" :period-end "2025-09-30"
;;      :period-type "3M" :currency "USD" :value 3.72}
;;     ...
;;     {:ticker "MSFT" :type "trailingTotalRevenue" :period-end "2026-09-30"
;;      :period-type "TTM" :currency "USD" :value 3.46445E11}]
Option (fetch-statements)Values
:frequenciesany of :annual :quarterly :trailing (default all)
:statementsany of :income-statement :balance-sheet :cash-flow (default all)
:fieldsYahoo field names from the catalog; overrides :statements
:start / :endepoch seconds or java.time.Instant (default: all available)

Rows are sorted by statement, field, frequency and period end. Unknown option keys or values (including field names not in the catalog) return :invalid-opts. If any of the underlying requests fails, the whole call fails — the verbose variant never returns partial statements.

What Yahoo returns:

  • Depth: typically 4 annual, 5 quarterly and a few trailing-twelve-month points. Semi-annual reporters have gaps in the quarterly series. There is no trailing balance sheet.
  • Period ends are normalised to month ends: NVDA's fiscal year ending 2026-01-25 is reported as "2026-01-31".
  • Restated for splits: annual and quarterly per-share values use today's share basis — NVDA's FY2024 diluted EPS is 1.19, not the 11.93 reported before its 2024 10:1 split. Trailing points are not always restated (NVDA's trailing diluted EPS at 2024-01-31 is still 11.93).
  • Currency is the reporting currency, which can differ from the trading currency: 0005.HK (traded in HKD) reports in USD, so (yfs/fetch-statements "0005.HK" :frequencies [:annual] :fields ["NetIncome"]) gives :currency "USD".
  • Values are as of today, not as first published: use the report dates below to know when a figure became public.

Experimental: Earnings Dates

⚠️ Uses Yahoo's authenticated quoteSummary and visualization endpoints, with the same session and caveats as fundamentals.

(require '[clj-yfinance.experimental.earnings :as yfe])
FunctionReturns
fetch-earnings-history / *Last four quarters: period end, report date and time, EPS actual vs. estimate
fetch-earnings-dates / *Longer history of earnings announcements with EPS estimate, actual and surprise
(yfe/fetch-earnings-history "AAPL")
;; => [{:ticker "AAPL" :period-end "2025-09-30"
;;      :reported-date "2025-10-30" :reported-at 1761856200   ; 2025-10-30T20:30:00Z
;;      :fiscal-quarter "4Q2025" :calendar-quarter "3Q2025"
;;      :eps-actual 1.85 :eps-estimate 1.76993 :eps-difference 0.08 :surprise-pct 4.52
;;      :currency "USD"}
;;     ...]                                                    ; 4 quarters, oldest first

(yfe/fetch-earnings-dates "AAPL" :limit 200)
;; => [{:ticker "AAPL" :company-name "Apple Inc." :event-name nil
;;      :announced-at "1993-01-16T05:00:00Z" :announced-epoch 727160400 ...}
;;     ...
;;     {:ticker "AAPL" :company-name "Apple Inc." :event-name nil
;;      :announced-at "2025-05-01T20:30:00Z" :announced-epoch 1746131400
;;      :timing "TAS" :eps-estimate 1.63 :eps-actual 1.65 :surprise-pct 1.41
;;      :timezone "EDT" :gmt-offset-ms -14400000}]           ; 127 events

fetch-earnings-history pairs each fiscal period end with the instant its results were published, which is what a point-in-time analysis needs: a figure for the quarter ending 2025-09-30 was not known before 2025-10-30 20:30 UTC. fetch-earnings-dates returns the most recent :limit events (default 100; requests are paged at Yahoo's maximum of 250), sorted oldest first.

  • The EPS figures are the ones analysts estimate (usually adjusted, non-GAAP) and can differ from the diluted EPS of the statements. :currency in the history is the reporting currency.
  • :event-name is nil for plain earnings releases and names other events ("Annual Shareholders Meeting", "Q1 2025 Earnings Call"), whose EPS fields are usually nil. Filter on it if you only want releases.
  • Yahoo's announcement history reaches back to the 1990s for large US companies but currently ends in mid-2025; coverage outside the US is thin (0005.HK has three call events and no EPS). Use fetch-earnings-history for recent quarters.

Experimental: Options Chains

⚠️ Uses Yahoo's authenticated v7 options endpoint. Same session as fundamentals; same caveats apply.

(require '[clj-yfinance.experimental.options :as yfo])

;; Nearest expiry + all available expiration dates
(yfo/fetch-options "AAPL")
;; => {:underlying-symbol "AAPL"
;;     :expiration-dates  [1771372800 1771977600 ...]
;;     :strikes           [195.0 200.0 210.0 ... 335.0]
;;     :calls             [{:contractSymbol "AAPL260218C00210000"
;;                          :strike 210.0 :bid 44.6 :ask 47.6
;;                          :impliedVolatility 1.447 :inTheMoney true ...} ...]
;;     :puts              [{...} ...]}

;; Specific expiry
(yfo/fetch-options "AAPL" :expiration 1771977600)

Each contract includes: :contractSymbol :strike :bid :ask :lastPrice :impliedVolatility :openInterest :volume :inTheMoney :expiration :lastTradeDate :percentChange :change.


Data Science Integrations

Dataset (tech.ml.dataset)

Add techascent/tech.ml.dataset to your deps and use clj-yfinance.dataset:

(require '[clj-yfinance.dataset :as yfd])

(yfd/historical->dataset "AAPL" :period "1mo")
;; => #tech.v3.dataset [:timestamp :open :high :low :close :volume :adj-close]

(yfd/prices->dataset (yf/fetch-prices ["AAPL" "GOOGL" "MSFT"]))
(yfd/multi-ticker->dataset ["AAPL" "GOOGL" "MSFT"] :period "1y")
(yfd/dividends-splits->dataset "AAPL" :period "10y")
(yfd/info->dataset "AAPL")

Column types: timestamps as :int64, prices as :float64, volume as :int64, tickers as :string.

Works directly with tablecloth:

(require '[tablecloth.api :as tc])

(-> (yfd/historical->dataset "AAPL" :period "1y")
    (tc/add-column :returns (fn [ds]
                              (let [c (ds :close)]
                                (map / (rest c) c))))
    (tc/select-columns [:timestamp :close :returns]))

For datasets too large to fit in memory, Clojask can process the data out-of-core via CSV:

(require '[tech.v3.dataset :as ds])
(require '[clojask.dataframe :as ck])
(ds/write! (yfd/multi-ticker->dataset ["AAPL" "GOOGL" "MSFT"] :period "5y") "data.csv")
(def ck-df (ck/dataframe "data.csv"))

Kindly (Clay / Portal)

For auto-rendering as interactive tables in Clay or Portal, use the clj-yfinance.kindly namespace. Same API as clj-yfinance.dataset, but output is tagged with kind/dataset.

Requires org.scicloj/kindly and techascent/tech.ml.dataset.

(require '[clj-yfinance.kindly :as yfk])

(yfk/historical->dataset "AAPL" :period "1mo")
(yfk/prices->dataset (yf/fetch-prices ["AAPL" "GOOGL" "MSFT"]))
(yfk/multi-ticker->dataset ["AAPL" "GOOGL" "MSFT"] :period "1y")
(yfk/dividends-splits->dataset "AAPL" :period "10y")
(yfk/info->dataset "AAPL")

Parquet

Columnar archiving of financial datasets:

Requires com.techascent/tmd-parquet and techascent/tech.ml.dataset.

(require '[clj-yfinance.parquet :as yfp])

;; Save
(yfp/save-historical! "AAPL" "aapl.parquet" :period "5y")
(yfp/save-multi-ticker! ["AAPL" "GOOGL" "MSFT"] "tech.parquet" :period "1y")

;; Load
(yfp/load-historical "aapl.parquet")
(yfp/load-dataset "tech.parquet")

;; Save an already-transformed dataset
(yfp/save-dataset! my-enriched-ds "enriched.parquet")

DuckDB

Run SQL queries over financial datasets using an embedded DuckDB database:

Requires com.techascent/tmducken and techascent/tech.ml.dataset. DuckDB also requires its native shared library (libduckdb, 0.10.1 or later): download it for your platform from the DuckDB releases and set DUCKDB_HOME to the directory containing it.

(require '[clj-yfinance.duckdb :as yf-db])

(def db (yf-db/open-db))                ;; in-memory
(def db (yf-db/open-db "finance.db"))    ;; persistent

;; Load and query
(yf-db/load-historical! db "AAPL" :period "1y")
(yf-db/query db "SELECT * FROM AAPL ORDER BY timestamp DESC LIMIT 5")

(yf-db/load-multi-ticker! db ["AAPL" "GOOGL" "MSFT"] :period "1y")
(yf-db/query db "SELECT ticker, AVG(close) AS avg_close FROM prices GROUP BY ticker ORDER BY avg_close DESC")

;; Load any existing dataset
(yf-db/load-dataset! db my-ds :table-name "enriched")

;; Cleanup
(yf-db/run! db "DROP TABLE IF EXISTS prices")
(yf-db/close! db)

query returns a dataset whose column names are keywords (e.g. :ticker, :avg_close), consistent with the rest of clj-yfinance — so (ds/column result :avg_close) works directly.

Using with Noj

Noj is the Scicloj batteries-included data science toolkit — tablecloth, tableplot, fastmath, Clay, and more in a single tested dependency. clj-yfinance serves as the data acquisition layer. Add org.scicloj/noj alongside clj-yfinance in your deps.

(require '[clj-yfinance.core    :as yf])
(require '[clj-yfinance.dataset :as yfd])
(require '[tablecloth.api       :as tc])
(require '[fastmath.stats       :as stats])
(require '[scicloj.kindly.v4.kind :as kind])
(require '[scicloj.clay.v2.api    :as clay])

;; Fetch → dataset → log-returns → summary stats → visualise
(def prices-ds (yfd/multi-ticker->dataset ["AAPL" "GOOGL" "MSFT"] :period "1y"))

(defn log-returns [ds]
  (tc/add-column ds :log-return
    (fn [rows]
      (let [c (vec (rows :close))]
        (into [nil] (map (fn [a b] (Math/log (/ b a))) c (rest c)))))))

(def returns-ds
  (-> prices-ds
      (tc/group-by :ticker)
      (tc/process #(log-returns %))
      tc/ungroup))

(defn ticker-stats [ds ticker]
  (let [rets (->> (tc/select-rows ds #(= (:ticker %) ticker))
                  :log-return (remove nil?) vec)]
    {:ticker ticker
     :mean     (stats/mean rets)
     :std      (stats/stddev rets)
     :skewness (stats/skewness rets)
     :kurtosis (stats/kurtosis rets)
     :sharpe   (/ (stats/mean rets) (stats/stddev rets))}))

(kind/table (tc/dataset (map #(ticker-stats returns-ds %) ["AAPL" "GOOGL" "MSFT"])))
NeedLibrary (via Noj)
Data wranglingtablecloth
Chartingtableplot (Plotly/Vega-Lite)
Statisticsfastmath
ML / modellingmetamorph.ml
Notebook renderingClay + Kindly

Demo Notebook (Clay)

The examples/finance_demo.clj notebook demonstrates the full pipeline — fetching, transforming with tablecloth, and rendering interactive charts with tableplot.

Covers: current prices, historical OHLCV, log-returns, multi-ticker datasets, closing price charts, normalised performance (indexed to 100), rolling volatility, returns distributions, dividend history, and ticker info comparison.

Running

From a clone of this repository, start a REPL with the :clay alias (which bundles Clay, tablecloth, tableplot and tech.ml.dataset) and evaluate the file in your editor:

clojure -M:clay:nrepl

Clay integrates with Calva (VS Code), CIDER (Emacs), and Cursive (IntelliJ). When you evaluate a form, Clay opens http://localhost:1971/ and updates live.

To render the entire notebook to a static HTML file:

(require '[scicloj.clay.v2.api :as clay])
(clay/make! {:source-path "examples/finance_demo.clj"})

Error Types Reference

Core Errors

:rate-limited · :http-error · :api-error · :parse-error · :connection-error · :missing-price · :missing-data · :missing-metadata · :no-data · :invalid-opts · :timeout · :execution-error · :interrupted · :exception

Experimental Errors (in addition to core)

ErrorCause
:auth-failedCookie/crumb refresh failed after retry
:session-failedSession could not initialise (network issue)
:request-failedNetwork error during authenticated request
:missing-dataYahoo returned a response with no usable result
:invalid-optsInvalid option value (e.g. bad :period for fetch-financials)

Caveats

  • No built-in caching — every call hits the network. Add core.memoize or similar at the application level.
  • No built-in rate limiting — aggressive parallel use triggers 429 errors. Use :concurrency on fetch-prices and retry logic via the verbose API.
  • Unofficial API — Yahoo does not publicly document these endpoints. Check their Terms of Service for commercial use.
  • Financial statement coverage — Yahoo's quoteSummary statement modules (fetch-financials) now carry only a few fields; use clj-yfinance.experimental.timeseries for full statements. Yahoo keeps only a few years of history there.

Alternative Data Providers

For reference, commercial providers worth knowing about (no affiliation; pricing subject to change):

  • Alpha Vantage — Free tier; premium from ~$49.99/mo. Solid fundamentals and time series.
  • Financial Modeling Prep — Free basic tier. 100+ endpoints, 70k+ securities, 30+ years of data.
  • Massive (formerly Polygon.io) — Free basic; Starter $29/mo. Professional-grade market data and options.
  • Finnhub — Free tier with generous limits. Real-time REST/WebSocket, global coverage.
  • EOD Historical Data — Free tier (20 calls/day); from $19.99/mo. 60+ exchanges, student discounts.
  • Marketstack — Free tier (100 req/mo); from ~$9.99/mo. 500k+ tickers, 15+ years historical.
  • Twelve Data — Stocks, forex, crypto, ETFs; 100k+ symbols.

Changelog

See CHANGELOG.md.

License

Apache-2.0 — see LICENSE. Artifacts up to and including 0.1.7 were published under EPL-2.0.

Can you improve this documentation?Edit on GitHub

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
←Move to previous article
→Move to next article
Ctrl+/Jump to the search field
× close