Created
November 18, 2023 21:16
-
-
Save mkrcah/32bbd41b3da95853474d92d8649b36bc to your computer and use it in GitHub Desktop.
find-exchange-rates (v2)
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 exchange-rates.find-exchange-rate | |
(:require [clj-http.client :as client] | |
[tick.core :as t] | |
[clojure.tools.logging :as log] | |
[clojure.spec.alpha :as s] | |
[exchange-rates.rates-repo :as repo] | |
[exchange-rates.value-objects :as vo]) | |
(:use [clojure.tools.trace])) | |
; alternatively use http://fx.modfin.se/ | |
(defn- fetch-from-public-api | |
[pair period] | |
{:pre [(s/valid? ::vo/pair pair) | |
(<= (t/between (:start period) (:end period) :days) 90)]} | |
(let [url (str "http://api.frankfurter.app/" (:start period) ".." (:end period))] | |
(log/info "Fetching exchange rates from Frankfurter" period) | |
(client/get url | |
{:accept :json | |
:as :json | |
:query-params {:to (:quote pair) | |
:from (:base pair)} | |
:throw-exceptions true}))) ; FIXME - handle path | |
(defn- fetching-period [date] | |
"Prepare start and end date for fetching to save API requests" | |
{:start (t/<< date (t/of-days 3)) | |
:end (t/>> date (t/of-days 70))}) | |
(defn- latest-day-with-open-exchange | |
"Find the latest day before a given day when the exchange was opened" | |
[date] | |
(condp = (t/day-of-week date) | |
t/SUNDAY (t/<< date (t/of-days 2)) | |
t/SATURDAY (t/<< date (t/of-days 1)) | |
date)) | |
(defn- in-past? [date] | |
(t/<= (t/date date) (t/today))) | |
(defn- response->rates | |
"Extract rates from the API response and convert | |
to the model expected by the repository" | |
[pair api-response] | |
(let [rates (-> api-response | |
:body | |
:rates)] | |
(into {} (map (fn [[k v]] [(name k) (get v (keyword (:quote pair)))]) rates)))) | |
(defn by-date | |
"For a given date and currency pair, find European Central Bank exchange rate" | |
[pair requested-date-str] | |
{:pre [(in-past? (t/date requested-date-str)) | |
(s/valid? ::vo/pair pair)] | |
:post [(or (nil? %) (number? %))]} | |
(let [actual-date (latest-day-with-open-exchange (t/date requested-date-str)) | |
actual-date-str (str actual-date)] | |
(or (repo/get-by-date pair actual-date-str) | |
(do | |
(log/debug "Exchange rate not found in storage, fetching") | |
(-> actual-date | |
fetching-period | |
((partial fetch-from-public-api pair)) | |
((partial response->rates pair)) | |
((partial repo/store pair))) | |
(repo/get-by-date pair actual-date-str))))) | |
(comment | |
(def EUR-CZK {:base "EUR" :quote "CZK"}) | |
(do | |
(def api-response | |
{:body {:rates {:2023-01-24 {:CZK 23.874}, | |
:2023-01-25 {:CZK 23.809}}}}) | |
(response->rates EUR-CZK api-response) | |
; fake fetch | |
(defn fetch-from-public-api [pair period] | |
api-response) | |
(by-date EUR-CZK "2023-01-24")) | |
(by-date EUR-CZK "2023-11-11") | |
(latest-day-with-open-exchange (t/date "2023-01-15"))) | |
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 exchange-rates.rates-repo | |
(:require [clojure.spec.alpha :as s] | |
[clojure.tools.logging :as log] | |
[exchange-rates.value-objects :as vo] | |
[tick.core :as t] | |
[clojure.test :refer [is]]) | |
(:use [clojure.tools.trace])) | |
(def ^:private stored-rates (atom {})) | |
(defn store [pair rates] | |
{:pre [(is (s/valid? ::vo/pair pair)) | |
(is (s/valid? (s/map-of string? double?) rates))]} | |
(log/debug "Storing" (count rates) (vo/pair->str pair) "rate(s)") | |
(swap! stored-rates into {pair rates})) | |
(defn get-by-date [pair date-str] | |
{:pre [(is (s/valid? ::vo/pair pair)) | |
(is (string? date-str))]} | |
(log/debug "Getting" (vo/pair->str pair) "rate for" (str date-str)) | |
(get (get @stored-rates pair) date-str)) | |
(comment | |
(def CZK-EUR {:base "CZK" :quote "EUR"}) | |
(s/explain ::vo/pair CZK-EUR) | |
(def today (str (t/date))) | |
(get-by-date CZK-EUR today) | |
(store CZK-EUR {today 13.4}) | |
(get-by-date CZK-EUR today)) |
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 exchange-rates.value-objects | |
(:require [clojure.spec.alpha :as s])) | |
(s/def ::currency-code | |
(s/and string? #(re-matches #"^[A-Z]{3}$" %))) | |
(s/def ::base ::currency-code) | |
(s/def ::quote ::currency-code) | |
(s/def ::pair | |
(s/keys :req-un [::base ::quote])) | |
(defn pair->str [pair] | |
{:pre [(s/valid? ::pair pair)]} | |
(str (:base pair) "-" (:quote pair))) | |
(comment | |
(s/valid? ::pair | |
{:base "EUR" | |
:quote "CZK"}) | |
(s/valid? ::pair | |
{:base :EUR | |
:quote :CZK}) | |
(def CZK-EUR {:base "CZK" :quote "EUR"}) | |
(pair->str CZK-EUR)) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment