Last active
November 18, 2023 21:15
-
-
Save mkrcah/5619d9d579ff74bbf2d75a79a3342fb0 to your computer and use it in GitHub Desktop.
find exchange rates (v1)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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