Skip to content

Instantly share code, notes, and snippets.

@mkrcah
Last active November 18, 2023 21:15
Show Gist options
  • Save mkrcah/5619d9d579ff74bbf2d75a79a3342fb0 to your computer and use it in GitHub Desktop.
Save mkrcah/5619d9d579ff74bbf2d75a79a3342fb0 to your computer and use it in GitHub Desktop.
find exchange rates (v1)
(ns find-exchange-rate
"Design objectives:
- need deterministic exchange rates
- need only eur->czk for now, don't see use-cases for other pairs right now
- don't need any performance or persistence, in-memory is fine for now"
(:require [clj-http.client :as client]
[tick.core :as t]
[clojure.tools.logging :as log]
[medley.core :as m]
[clojure.test :refer [deftest is]]))
(def ^:private eur-czk (atom {}))
(defn- kw->date [kw] (t/date (name kw)))
(defn- date->kw [date] (keyword (str date)))
(defn- fetch-ecb-rates-from-public-api [{:keys [from to] :as dates}]
{:pre [(keyword? from)
(keyword? to)
(<= (t/between (t/date (name from)) (t/date (name to)) :days) 90)]} ; api requirements
(let [url (str "http://api.frankfurter.app/" (name from) ".." (name to))]
(log/info "Fetching exchange rates from Frankfurter" dates)
(client/get url
{:accept :json
:as :json
:query-params {:to "CZK"
:from "EUR"}
:throw-exceptions true}))) ; FIXME - unhappy path
(defn- response->rates [res]
(let [rates (-> res :body :rates)]
(m/map-vals :CZK rates)))
(defn- fetch-ecb-rates [date]
(let [from (t/<< (t/date (name date)) (t/of-days 3))
to (t/>> from (t/of-days 70))
rates (-> (fetch-ecb-rates-from-public-api {:from (keyword (str from))
:to (keyword (str to))})
(response->rates))]
(log/debug "Fetched" (count rates) "rates")
rates))
(defn find-eur-czk-ecb-rate
"Find EUR-CZK rate according to European Central Bank (ECB) for a given day"
[date-kw] {:pre [(keyword? date-kw)
(t/<= (kw->date date-kw) (t/today))]
:post [(number? %)]}
(let [date (kw->date date-kw)
latest-day-with-opened-exchange (condp = (t/day-of-week date)
t/SUNDAY (t/<< date (t/of-days 2))
t/SATURDAY (t/<< date (t/of-days 1))
date)]
(or (get @eur-czk (date->kw latest-day-with-opened-exchange))
(let [_ (log/debug "Exchange rate not found in storage, fetching")
rates (fetch-ecb-rates (date->kw latest-day-with-opened-exchange))]
(swap! eur-czk into rates)
(get @eur-czk (date->kw latest-day-with-opened-exchange))))))
(defn find-eur-czk-approx-ib-rate [date-kw]
"Get an approximation of EUR/CZK rate on InteractiveBrokers"
(+ (find-eur-czk-ecb-rate date-kw) 0.10))
; FIXME - would be great to have some kind of a strong component test here, that is:
; - start with empty storage
; - have the API call ready
; - ensure the find-rate works as expected
; - check https://www.youtube.com/watch?v=GOnQzzi8WIc, e.g wiremock
(deftest exch-rate-tests
(is (= {:2023-10-05 24.53} (response->rates {:body {:rates {:2023-10-05 {:CZK 24.53}}}})))
(is (= :2023-10-01 (date->kw (kw->date :2023-10-01)))))
(comment
(find-eur-czk-approx-ib-rate :2022-12-31)
(find-eur-czk-ecb-rate :2022-11-08))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment