diff --git a/datomic/start.sh b/datomic/start.sh old mode 100644 new mode 100755 diff --git a/project.clj b/project.clj index cd52a05..01107c9 100644 --- a/project.clj +++ b/project.clj @@ -4,7 +4,6 @@ :license {:name "Eclipse Public License" :url "http://www.eclipse.org/legal/epl-v10.html"} - :resource-paths ["resources" "target/cljsbuild"] @@ -32,6 +31,7 @@ [orchestra "2017.11.12-1"] [cheshire "5.8.0"] [com.stuartsierra/frequencies "0.1.0"] + [rm-hull/infix "0.3.3"] ;; wui [ring-server "0.5.0"] @@ -126,8 +126,7 @@ :sass {:source-paths ["src/sass"] :target-path "resources/public/css"} - :profiles {:dev {:repl-options {:init-ns skirmish.wui.repl - :nrepl-middleware [cemerick.piggieback/wrap-cljs-repl]} + :profiles {:dev { :java-source-paths ["src/java"] :dependencies [[midje "1.9.9"] diff --git a/resources/config.edn b/resources/config.edn index b00e21e..3251266 100644 --- a/resources/config.edn +++ b/resources/config.edn @@ -1,12 +1,12 @@ { ;; Specify which middleware to use, it will default to :chan. - :middleware :kafka + :middleware :chan ;; If set to true, orchestra will instrument all the spec'ed functions :instrument-spec true :datomic - {:transactor-url "datomic:free://localhost:4334/skirmish.ledger"} + {:transactor-url "datomic:free://localhost:4334/skirmish.ledger?password=datomicpassword"} ;; all the kafka configuration goes bellow. :kafka @@ -39,7 +39,7 @@ :max-body 524288 ;; 512kb, small but enough :max-ws 524288 :default-sec-id :dapm - :datomic {:transactor-url "datomic:free://localhost:4334/skirmish.broker"}} + :datomic {:transactor-url "datomic:free://localhost:4334/skirmish.broker?password=datomicpassword"}} :wui {:port #long #or [#env WUI_PORT 3000]}} diff --git a/src/clj/skirmish/broker/broker.clj b/src/clj/skirmish/broker/broker.clj index c637e6e..3a34161 100644 --- a/src/clj/skirmish/broker/broker.clj +++ b/src/clj/skirmish/broker/broker.clj @@ -8,19 +8,16 @@ [skirmish.routes.core :as routes] [clj-http.client :as http-client])) - (defn generate-amount-for-new-user [seed] (let [random (new java.util.Random seed) random-fn #(. random nextInt 20000)] (* 100 (reduce + (take 100 (repeatedly random-fn)))))) - (defn create-account [account-id sec-id] #:skirmish.broker.account{:id account-id :sec-id sec-id :private-key (str account-id)}) - (defn create-user-account [seed default-sec-id] (let [url (routes/get-path-for :ledger/security {:sec-id (name default-sec-id)}) account-id (java.util.UUID/randomUUID) diff --git a/src/clj/skirmish/broker/db.clj b/src/clj/skirmish/broker/db.clj index 04cbf40..06b27eb 100644 --- a/src/clj/skirmish/broker/db.clj +++ b/src/clj/skirmish/broker/db.clj @@ -54,7 +54,7 @@ (concat accounts-schema))) (defn create-connection [uri] - (let [new-db? (d/create-database uri) + (let [_ (d/create-database uri) conn (d/connect uri)] @(d/transact conn schema) conn)) diff --git a/src/clj/skirmish/core.clj b/src/clj/skirmish/core.clj index 58801d7..2c32032 100644 --- a/src/clj/skirmish/core.clj +++ b/src/clj/skirmish/core.clj @@ -36,7 +36,8 @@ exchange-apis (create-exchange-apis-for config securities-api)] (if (= (count target-fns) 1) ((first target-fns) config securities-api exchange-apis) - (run-all-targets-in-separate-threads target-fns config securities-api exchange-apis)))) + (run-all-targets-in-separate-threads target-fns config securities-api exchange-apis)) + (skirmish.util/block-thread))) (defn -main [& argv] (let [config (-> "config.edn" clojure.java.io/resource read-config) @@ -47,7 +48,7 @@ (if (seq target-fns) (run-all-targets target-fns config) (do - (timbre/info argv) - (timbre/info "target" target-fns "not found") - (timbre/info "all known targets are...") - (timbre/info (keys targets)))))) \ No newline at end of file + (timbre/warn argv) + (timbre/warn "target" target-fns "not found") + (timbre/warn "all known targets are...") + (timbre/warn (keys targets)))))) \ No newline at end of file diff --git a/src/clj/skirmish/ledger/accounts.clj b/src/clj/skirmish/ledger/accounts.clj index 5e11933..cbea7cd 100644 --- a/src/clj/skirmish/ledger/accounts.clj +++ b/src/clj/skirmish/ledger/accounts.clj @@ -82,7 +82,12 @@ :db/ident :skirmish.security/interest-rate :db/valueType :db.type/long :db/cardinality :db.cardinality/one - :db/doc "The interest-rate payed by the security as a multiple of 0.01% (100 is 1% interest rate)."}]) + :db/doc "The daily interest-rate payed by the security as a multiple of 0.01% (100 is 1% interest rate)."} + { + :db/ident :skirmish.security/inflation + :db/valueType :db.type/long + :db/cardinality :db.cardinality/one + :db/doc "The daily inflation as a multiple of 0.01% (100 is 1%)."}]) (def ledger-schema [ @@ -120,7 +125,8 @@ :emitter "dapm private key" :number-of-decimal 2 :symbol "რ" - :interest-rate 10} + :interest-rate (* 100 10) + :inflation (* 100 9)} apbm #:skirmish.security{:sec-id :apbm :name "apache beam" :type :equity diff --git a/src/clj/skirmish/ledger/core.clj b/src/clj/skirmish/ledger/core.clj index 141e21c..9b0de05 100644 --- a/src/clj/skirmish/ledger/core.clj +++ b/src/clj/skirmish/ledger/core.clj @@ -12,12 +12,86 @@ [taoensso.timbre :as timbre] [compojure.core :as compojure] [clojure.spec.alpha :as s] + [infix.macros :refer [infix]] [ring.middleware.cors :refer [wrap-cors]])) ;; the ledger is the central + + +(defn compute-simple-face-value [amount daily-interest-rate deposit-date value-date] + (let [min-in-milliseconds (infix 60 * 1000) + minute-interest-rate (infix daily-interest-rate / (24 * 60. * 10000.)) + ite (quot (- (. value-date getTime) (. deposit-date getTime)) min-in-milliseconds) + base (infix 1 + minute-interest-rate)] + (infix amount * base ** ite))) + +(defn- in-interval? [[_ start end] x] + (let [x-time (. x getTime)] + (and (<= (. start getTime) x-time) + (< x-time (. end getTime))))) + +(defn- larger-then-interval? [[ _ start end] x] + (and (<= (. x getTime) (. end getTime)) + (<= (. x getTime) (. start getTime)))) + + +(defn- split-interest-rate-intervals [interest-rates-intervals deposit-date value-date] + (let [xf (comp (drop-while #((comp not in-interval?) % deposit-date)) + (take-while #((comp not larger-then-interval?) % value-date))) + ret (into [] xf interest-rates-intervals) + ret (assoc-in ret [0 1] deposit-date) + ret (assoc-in ret [(-> ret count dec) 2] value-date)] + ret)) + +(defn- find-interest-rate-for [interest-rates-intervals d] + (if (in-interval? (first interest-rates-intervals) d) + (ffirst interest-rates-intervals) + (recur (rest interest-rates-intervals) d))) + + +(defn split-interest-rates [interest-rates deposit-date value-date] + (let [interest-rates (into [[0 (new java.util.Date 0)]] interest-rates) + interest-rates (conj interest-rates [0 (new java.util.Date Long/MAX_VALUE)]) + interest-rates-intervals (partition 2 1 interest-rates) + interest-rates-intervals (map (juxt ffirst (comp second first) (comp second second)) interest-rates-intervals)] + (if (= deposit-date value-date) + [[(find-interest-rate-for interest-rates-intervals deposit-date) deposit-date value-date]] + (split-interest-rate-intervals interest-rates-intervals deposit-date value-date)))) + +(defn compute-face-value [amount interest-rates deposit-date value-date] + (loop [interest-rates (split-interest-rates interest-rates deposit-date value-date) + amount amount] + (let [[interest-rate start end] (first interest-rates) + amount (compute-simple-face-value amount interest-rate start end)] + (if (empty? (rest interest-rates)) + (long amount) + (recur (rest interest-rates) amount))))) + + +(defn get-amount-in-account [db account-id] + (let [[amount sec-id date] (d/q '[:find [?amount ?sec ?v] + :in $ ?account-id + :where [?e :skirmish.ledger/owner-public-key ?account-id] + [?e :skirmish.ledger/amount ?amount ?tx] + [?e :skirmish.ledger/sec ?sec] + [?tx :db/txInstant ?v]] db account-id) + history (d/history db) + interest-rates (d/q '[:find ?interest-rate ?v + :in $ ?eid + :where [?eid :skirmish.security/interest-rate ?interest-rate ?tx] + [?tx :db/txInstant ?v]] + history sec-id) + face-value (compute-face-value amount + date + (sort-by second interest-rates) + (new java.util.Date))] + {:value face-value + :sec-id sec-id + :account-public-key (:skirmish.ledger/owner-public-key 333)})) + ;; an :account-id is the public key of a -;; an :account-secret is an :acount-id private key +;; an :account-secret is an :account-id private key (defn get-account-body [request account sec] (if (nil? sec) @@ -29,7 +103,6 @@ ret {:account beautify-account :sec-url sec-url}] (build-response request ret)))) - (defn get-account [conn] "Returns the value of an account." (fn [{{account-id :account-id} :route-params :as request}] @@ -74,12 +147,12 @@ name :skirmish.security/name number-of-decimal :skirmish.security/number-of-decimal type_ :skirmish.security/type} e - volume (d/q '[:find (sum ?amount) . - :in $ ?id - :where [?e :skirmish.ledger/sec ?id] - [?e :skirmish.ledger/amount ?amount]] + volume (d/q '[:find (sum ?amount) . + :in $ ?id + :where [?e :skirmish.ledger/sec ?id] + [?e :skirmish.ledger/amount ?amount]] - db id)] + db id)] {:sec-id sec-id :interest-rate interest-rate :volume (or volume 0) @@ -157,7 +230,7 @@ create securities. payload - [:amount :acount]" + [:amount :account]" (fn [{{sec-id-str :sec-id} :route-params body :body :as request}] (try+ (let [emit-request (-> body get-body (get-emit-securities-request (keyword sec-id-str)))] @@ -171,7 +244,7 @@ (let [sec-id->url #(routes/get-path-for :ledger/account {:account-id (name %)}) to-security-description #(hash-map :account-public-key % :url (sec-id->url %)) xf (comp cat (map to-security-description)) - securities-info (sequence xf accounts)];; + securities-info (sequence xf accounts)] {:accounts securities-info})) diff --git a/src/clj/skirmish/util.clj b/src/clj/skirmish/util.clj index 63ebfe4..1cc7eaf 100644 --- a/src/clj/skirmish/util.clj +++ b/src/clj/skirmish/util.clj @@ -1,6 +1,6 @@ (ns skirmish.util (:require [clojure.core.async :as async]) - (:import (skirmish PersistentSortedList))) + (:import [ skirmish PersistentSortedList])) (defn sorted-list [& keys] (PersistentSortedList/create (seq keys))) diff --git a/src/cljs/skirmish/wui/components/home_page.cljs b/src/cljs/skirmish/wui/components/home_page.cljs index 8fdc27e..7d1294c 100644 --- a/src/cljs/skirmish/wui/components/home_page.cljs +++ b/src/cljs/skirmish/wui/components/home_page.cljs @@ -38,6 +38,8 @@ (defn page [state] [:div [:section.section - [:div.container - [:img {:src "/images/amsterdam.svg"}] + [:div.columns + [:div.container + [:img {:src "/images/amsterdam.svg"}]]] + [:div.columns.is-centered [login state]]]]) \ No newline at end of file diff --git a/test/clj/skirmish/ledger/core_test.clj b/test/clj/skirmish/ledger/core_test.clj new file mode 100644 index 0000000..eb27f93 --- /dev/null +++ b/test/clj/skirmish/ledger/core_test.clj @@ -0,0 +1,6 @@ +(ns skirmish.ledger.core-test + (:require [clojure.test :refer :all]) + (:require [skirmish.ledger.core :refer [get-amount-in-account]])) + +(deftest get-amount-in-account-test) + diff --git a/test/clj/skirmish/ledger/test_core.clj b/test/clj/skirmish/ledger/test_core.clj index 38c2161..5ed67c0 100644 --- a/test/clj/skirmish/ledger/test_core.clj +++ b/test/clj/skirmish/ledger/test_core.clj @@ -6,6 +6,7 @@ [skirmish.ledger.core :refer :all] [skirmish.rest.util :refer [edn->stream]] [clojure.spec.alpha :as s] + [infix.macros :refer [infix]] [skirmish.ledger.accounts :refer [create-connection schema]])) (defn spec-check [spec] @@ -99,14 +100,14 @@ request {:route-params {:sec-id :new-currency} :headers {"accept" "application/edn"}} resp ((get-security conn) request)] resp => (spec-check ::ok) - (body resp) => {:sec-id :new-currency - :interest-rate 100 - :name nil - :description nil + (body resp) => {:sec-id :new-currency + :interest-rate 100 + :name nil + :description nil :number-of-decimal nil - :symbol nil - :type nil - :volume 0})) + :symbol nil + :type nil + :volume 0})) (fact "we should not be able to get-security if it does not exists" (let [conn (create-conn) @@ -140,8 +141,8 @@ (facts "about the emitting securities" (fact "should not work if invalid request" (let [conn (create-some-securities (create-conn)) - req-1 {:body (edn->stream [10 :not-a-string]) :route-params {:sec-id "sec-1"} :headers {"accept" "application/edn"}} - req-2 {:body (edn->stream [-10 "foo"]) :route-params {:sec-id "sec-1"} :headers {"accept" "application/edn"}} + req-1 {:body (edn->stream [10 :not-a-string]) :route-params {:sec-id "sec-1"} :headers {"accept" "application/edn"}} + req-2 {:body (edn->stream [-10 "foo"]) :route-params {:sec-id "sec-1"} :headers {"accept" "application/edn"}} req-3 {:body (edn->stream [90 "account-id"]) :route-params {:sec-id "invalid-sec-id"} :headers {"accept" "application/edn"}}] ((emit-securities conn) req-1) => (spec-check ::status-400) ((emit-securities conn) req-2) => (spec-check ::status-400) @@ -175,7 +176,7 @@ {:account-public-key "2-sec-1" :url "http://localhost:1604/api/1/ledger/accounts/2-sec-1"} {:account-public-key "2-sec-2" :url "http://localhost:1604/api/1/ledger/accounts/2-sec-2"}]] ((get-available-accounts conn) req) => (spec-check ::ok) - (-> ((get-available-accounts conn) req) body :accounts sec->hash-set) => (sec->hash-set expt))) + (-> ((get-available-accounts conn) req) body :accounts sec->hash-set) => (sec->hash-set expt))) (fact "we should be able to get account" (let [conn (create-some-securities-and-accounts (create-conn)) @@ -198,9 +199,59 @@ ((get-account conn) req) => (spec-check ::status-404)))) +(facts "about interest computation" + (fact "about very basic interest rates" + (let [amount 1000 + interest-rate (* 10 100) ;; daily 10% interest rates + negative-interest-rate (- interest-rate) + start #inst"2020-09-05T00:00:00.000-00:00" + start+1d #inst"2020-09-06T00:00:00.000-00:00" + start+2d #inst"2020-09-07T00:00:00.000-00:00"] + (compute-simple-face-value amount interest-rate start start) => (roughly amount) + (compute-simple-face-value amount interest-rate start start+1d) => (roughly 1105.1670808) + (compute-simple-face-value amount interest-rate start start+2d) => (roughly 1221.3942766) + (compute-simple-face-value amount negative-interest-rate start start+2d) => (roughly 818.7250672) + (compute-simple-face-value amount 0 start start+2d) => (roughly amount))) + (fact "split-interest-rates" + (let [d-1d #inst"2020-09-04T00:00:00.000-00:00" + d-½d #inst"2020-09-04T12:00:00.000-00:00" + d #inst"2020-09-05T00:00:00.000-00:00" + d+½d #inst"2020-09-05T12:00:00.000-00:00" + d+1d #inst"2020-09-06T00:00:00.000-00:00" + d+1½d #inst"2020-09-06T12:00:00.000-00:00" + d+2d #inst"2020-09-07T00:00:00.000-00:00" + d+2½d #inst"2020-09-07T12:00:00.000-00:00" + d+3d #inst"2020-09-08T00:00:00.000-00:00" + d+3½d #inst"2020-09-08T12:00:00.000-00:00";; + interest-rates [[10 d] [15 d+1d] [20 d+2d]]] + (split-interest-rates interest-rates d+½d d+3d) => [[10 d+½d d+1d] [15 d+1d d+2d] [20 d+2d d+3d]] + (split-interest-rates interest-rates d+3d d+3½d) => [[20 d+3d d+3½d]] + (split-interest-rates interest-rates d-1d d-½d) => [[0 d-1d d-½d]] + (split-interest-rates interest-rates d d+½d) => [[10 d d+½d]] + (split-interest-rates interest-rates d+2½d d+2½d) => [[20 d+2½d d+2½d]])) + (fact "compute-face-value" + (let [d-1d #inst"2020-09-04T00:00:00.000-00:00" + d-½d #inst"2020-09-04T12:00:00.000-00:00" + d #inst"2020-09-05T00:00:00.000-00:00" + d+½d #inst"2020-09-05T12:00:00.000-00:00" + d+1d #inst"2020-09-06T00:00:00.000-00:00" + d+1½d #inst"2020-09-06T12:00:00.000-00:00" + d+2d #inst"2020-09-07T00:00:00.000-00:00" + d+2½d #inst"2020-09-07T12:00:00.000-00:00" + d+3d #inst"2020-09-08T00:00:00.000-00:00" + d+3½d #inst"2020-09-08T12:00:00.000-00:00" + amount 10000000 + interest-rates [[10 d] [15 d+1d] [20 d+2d]]] + (compute-face-value amount interest-rates d+½d d+½d) => amount + (compute-face-value amount interest-rates d-1d d-½d) => amount + (compute-face-value amount interest-rates d-1d d+3d) => 10045101))) + + ;(split-interest-rates interest-rates d+3d d+½d) => [[10 d d+½d]]))) + + +;; TOD + -;; TODO ;; 4. transfer sec from one account to the other. -;; 5. account for interest