going somewhere

This commit is contained in:
didier amyot committed 2018-11-12 14:50:56 -05:00
1 parent b972e6e69f
commit 10b054db25
31 files changed
+621 -302

No files matched your search

+4
View File
@@ -1,3 +1,7 @@
; This is the service that broadcast market data to the
; world. The users need to expect websocket and REST api.
(ns skirmish.bookkeeper.core
(:require [org.httpkit.server :refer [run-server]]
[compojure.handler :refer [api]]
+44 -7
View File
@@ -4,15 +4,50 @@
[datomic.api :as d]
[slingshot.slingshot :refer [try+ throw+]]
[skirmish.rest.util :refer [build-response not-found-resp get-body]]
[taoensso.timbre :as timbre]))
[taoensso.timbre :as timbre]
[skirmish.routes.core :as routes]
[clj-http.client :as http-client]))
(defn create-user [conn]
(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)
amount (generate-amount-for-new-user seed)
payload [amount (str account-id)]]
(timbre/info "Creating new account " account-id " with ammount " amount " of " default-sec-id)
(timbre/info url)
(http-client/post url {:body (str payload)})
(create-account account-id default-sec-id)))
(defn create-user-seed-from-email [email]
(let [hasher (java.security.MessageDigest/getInstance "SHA-256")
salt (. "this should be private and set in config" getBytes "utf-8")
msg (. email getBytes "utf-8")
_ (doto hasher (.update salt) (.update msg))
hash (. hasher digest)]
(int (mod (new BigInteger 1 hash) (biginteger Integer/MAX_VALUE)))))
(defn create-user [conn default-sec-id]
(fn [{body :body :as request}]
(try+
(let [email (-> body get-body :email)]
(timbre/info "Creating new user" email)
@(d/transact conn [[:skirmish.broker.user/create-user email]])
(let [email (-> body get-body :email)
_ (timbre/info "Creating new user" email)
seed (create-user-seed-from-email email)
account (create-user-account seed default-sec-id)]
@(d/transact conn [[:skirmish.broker.user/create-user email [account]]])
(build-response request {:status 201 :headers {"location" (str "/api/1/users/" email)}}))
(catch [:error-type :4XX] e (build-response request e))
(catch [:error-type :5XX] e (build-response request e)))))
@@ -24,6 +59,7 @@
ret (map #(assoc % :skirmish.broker.account/url (url %)) accounts)]
(assoc user :skirmish.broker.user/accounts ret)))
(defn get-user [conn]
(fn [{{email :email} :route-params :as request}]
(try+
@@ -54,14 +90,15 @@
(catch [:error-type :4XX] e (build-response request e))
(catch [:error-type :5XX] e (build-response request e)))))
(defn get-account [conn]
(fn [email account]))
(defn create-routes [conn]
(defn create-routes [conn config]
(routes
(context "/api/1" []
(POST "/users" [] (create-user conn))
(POST "/users" [] (create-user conn (:default-sec-id config)))
(GET "/users" [] (get-all-users conn))
(GET "/users/:email" [email] (get-user conn))
(GET "/users/:email/accounts/:account" [email account] (get-account conn)))
+3 -1
View File
@@ -1,3 +1,5 @@
; The backend of the skirmish web user interface.
(ns skirmish.broker.core
(:require [org.httpkit.server :refer [run-server]]
[compojure.handler :refer [api]]
@@ -11,7 +13,7 @@
(defn -main [config securities-api exchange-apis]
(let [webserver-config (:api-broker config)
conn (create-connection (-> webserver-config :datomic :transactor-url))
api-routes (create-routes conn)]
api-routes (create-routes conn webserver-config)]
(timbre/info "Running skirmish.broker.core server with config: " webserver-config)
(run-server
(-> api-routes api cors-middleware)
+2 -3
View File
@@ -8,12 +8,11 @@
:db/doc "Create a new user in the system."
:db/fn (d/function {:lang "clojure"
:requires ['[slingshot.slingshot]]
:params '[db email]
:params '[db email accounts]
:code '(let [existing-user (d/pull db '[:db/id] [:skirmish.broker.user/email email])]
;:skirmish.broker.user/accounts []])]
(when (-> existing-user :db/id some?)
(slingshot.slingshot/throw+ {:error-type :4XX :status 400 :body {:error "A user with this email already exists."}}))
[#:skirmish.broker.user{:email email}])})}])
[#:skirmish.broker.user{:email email :accounts accounts}])})}])
(def accounts-schema [
-104
View File
@@ -1,104 +0,0 @@
(ns skirmish.cli.benchmark
(:require [clojure.math.combinatorics :refer [cartesian-product]]
[clojure.core.async :refer [go chan <! >! close! to-chan close!] :as async]
[gniazdo.core :as ws]
[clj-http.client :as client]
[com.stuartsierra.frequencies :as freq]
[clojure.edn :as edn]))
(def ids (atom 0))
(defn web-socket-to-channel [url output-ch]
(let [on-receive #(go (>! output-ch %))
headers {"accept" "application/edn"}]
(ws/connect url :headers headers :on-receive on-receive)))
(defn create-orders-channel [securities]
(let [output (chan 1 (map edn/read-string))
url #(format "ws://localhost:%s/api/1/securities/%s/orders" 8080 %)
xf (comp (map :sec-id)
(map name)
(map url)
(map #(web-socket-to-channel % output)))]
(doall (sequence xf securities))
(async/pub output :book.order/id)))
(defn create-requests-payload-for-security [i security id]
(let [rd (new java.util.Random i)
rand-bool (= (mod (. rd nextInt) 2) 0) ;; java random number generator REALLY sucks
type (if rand-bool :sell :buy)
price (+ (* 3 (. rd nextGaussian)) 30)
int-price (int (* price 100))
quantity (reduce + 1 (map #(. rd nextInt (inc %)) (range 100)))]
{:type type :quantity quantity :price int-price :security security :benchmark/id id}))
(defn create-requests-for-security [[i security]]
(let [req-id (swap! ids inc)
payload (create-requests-payload-for-security i (str (:sec-id security)) req-id)
sec-id-str (-> security :sec-id name)
params {:throw-exceptions false :content-type :edn :headers {"accept" "application/edn"}}
url (format "http://localhost:%s/api/1/securities/%s" 8080 sec-id-str)]
(fn []
(let [now (System/nanoTime)]
(client/post url (assoc params :form-params (assoc payload :benchmark/request-time now)))))))
(defn make-requests [n securities]
(map create-requests-for-security (cartesian-product (range n) securities)))
(defn no-more-then-n-simultaneously [f input-chan n]
(let [buffer (chan)]
(go (async/pipeline n buffer (map f) input-chan))
buffer))
(defn decode-response [response]
(let [now (System/nanoTime)
ret {}
ret (assoc ret :status (:status response))
ret (assoc ret :order (edn/read-string (:body response)))
ret (assoc ret :duration (/ (- now (get-in ret [:order :benchmark/request-time])) (Math/pow 10 9)))]
ret))
(defn benchmark [requests orders]
(let [decoded-responses (chan 1 (map decode-response))
responses (no-more-then-n-simultaneously #(%) (to-chan requests) 10)]
(go (async/pipe responses decoded-responses))
decoded-responses))
(defn compute-result [results new-result]
(let [status-code (:status new-result)
duration (:duration new-result)
old-status-code-count (get-in results [:status status-code] 0)
ret results
ret (assoc-in ret [:status status-code] (inc old-status-code-count))
ret (assoc ret :durations (conj (:durations ret) duration))]
ret))
(defn pp-durations [durations]
(let [freq-map (frequencies durations)]
(print (freq/stats freq-map))))
(defn display-results [results]
(println results)
(println "number of request: " (:n results))
(println "status map: " (:status results))
(pp-durations (:durations results)))
(defn -main [config exchange-apis]
(let [benchmark-config (:benchmark config)
requests (-> benchmark-config :number-of-requests-per-securities (make-requests (:securities config)))
orders (create-orders-channel (:securities config))
results (benchmark requests orders)]
(display-results (async/<!! (async/reduce compute-result {} results)))))
-1
View File
@@ -1 +0,0 @@
(ns skirmish.cli.core)
-56
View File
@@ -1,56 +0,0 @@
(ns skirmish.cli.exchange
(:require [skirmish.exchange.match :refer [sell?]]
[skirmish.util :refer :all]
[skirmish.exchange.exchange :refer [empty-book create-exchange-for-security]]
[skirmish.order] ;; for spec side effect
[slingshot.slingshot :refer [try+ throw+]]
[clojure.edn :as edn]
[clojure.core.async :refer [chan go >!! <!!]]
[clojure.spec.alpha :as s]))
(def incoming-order-channel (chan))
;(def trades-channel (chan))
;
;(def new-book-order-channel (chan))
(defn print-chan [prefix c]
(loop [shit (<!! c)]
(println prefix shit)
(recur (<!! c))))
;(def master-book {:sell (create-sell-book)
; :buy (create-buy-book)})
;;; repl things.
(defn read-order []
(try
(print "$ ")
(let [order (edn/read-string (read-line))
valid? (s/valid? :book.order/order order)]
(if valid? order nil))
(catch Exception e nil)))
(def order-ids (atom -1))
(defn add-id-to-order [order]
(assoc order :book.order/id (swap! order-ids inc)))
(defn -main [& args]
(let [initial-book empty-book
incoming-order-channel (chan)
[trades-channel new-book-order-channel] (create-exchange-for-security initial-book incoming-order-channel)]
(go (print-chan "trade > " trades-channel))
(go (print-chan "order > " new-book-order-channel))
(loop [order (read-order)]
(if (some? order)
(do
(let [order-with-id (add-id-to-order order)]
(>!! incoming-order-channel order-with-id)
(println "\n\norder accepted with id: " (:book.order/id order-with-id))))
(println "invalid order"))
(recur (read-order)))))
+1 -3
View File
@@ -1,7 +1,5 @@
(ns skirmish.core
(:require [skirmish.cli.core]
[skirmish.cli.benchmark]
[skirmish.exchange.core]
(:require [skirmish.exchange.core]
[skirmish.wui.server]
[skirmish.bookkeeper.core]
[skirmish.exchange-gateway.core]
-3
View File
@@ -106,6 +106,3 @@
(<! orders-copy)
(<! trades-copy)
(recur (<! new-order-publisher) new-book)))))
+5 -1
View File
@@ -153,6 +153,11 @@
(defn emit-securities [conn]
"Emit securities in the specified wallet. This is the only way to
create securities.
payload
[:amount :acount]"
(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)))]
@@ -184,7 +189,6 @@
(defn create-routes-for-ledger [conn]
(compojure/routes
;; (routes/get-context :ledger)
(routes/context :ledger []
(routes/GET :ledger/home [] get-api-description)
(routes/GET :ledger/accounts [] (get-available-accounts conn))
+2 -1
View File
@@ -26,7 +26,8 @@
(head)
[:body.body-container
mount-target
(include-js "/js/app.js")]))
(include-js "/js/app.js")
(include-js "https://sidecar.gitter.im/dist/sidecar.v1.js")]))
(defn loading-page []
(html5
@@ -1,14 +1,10 @@
(ns skirmish.wui.components.home-page
(:require [cljs-http.client :as http]
[reagent.core :refer [atom]]
[skirmish.wui.util :refer [email?]]
[cljs.core.async :refer [<!] :refer-macros [go]]
[secretary.core :as secretary]))
(defn email? [s]
(let [email-pattern #"^([a-zA-Z0-9_\-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$"]
(->> s str (re-matches email-pattern) some?)))
(defn create-user [email]
(go
(let [url "http://localhost:1605/api/1/users"
@@ -0,0 +1,69 @@
(ns skirmish.wui.components.otc-page
(:require [reagent.core :refer [atom]]
[skirmish.wui.util :refer [email?]]))
(defn set-amount [state event]
(let [inserted-value (-> event .-target .-value)
new-value (first (re-find #"^\d+(\.\d*)?" inserted-value))]
(swap! state #(assoc % :value new-value))))
(defn set-email [state event]
(let [new-email (-> event .-target .-value)]
(swap! state #(assoc % :email new-email))))
(defn set-sec-id [state event]
(let [sec-id (-> event .-target .-value)]
(swap! state #(assoc % :sec-id sec-id))))
(defn ready-to-transfer? [state]
(and (-> state :value js/parseInt pos?)
(-> state :email email?)))
(defn direct-send-confirmation []
[:div.model
[:div.modal-background]
[:div.modal-content
[:div.modal-card
[:header.modal-card-head
[:p.modal-card-title "Modal title"]
[:button.delete]]]]
[:a.button.modal-close.is-large {:aria-label "close"} "dslkds"]])
(defn direct-send []
(let [state (atom {})]
(fn []
[:div
[:div.content
[:p "Send securities to another person."]
[:pre (str @state)]]
[:form.form
[:div.field.has-addons.has-addons-centered
[:div.control
[:span.select
[:select {:value (:sec-id @state)
:on-change (partial set-sec-id state)}
[:option {:value :foo :selected true} "damp"]
[:option {:value :bar } "blah blah blah"]]]]
[:div.control
[:input.input {:type "text"
:value (:value @state)
:on-change (partial set-amount state)
:placeholder "110.90"}]]
[:div.control
[:input.input {:type "text"
:value (:email @state)
:on-change (partial set-email state)
:placeholder "boris.johnson@redneck.co.uk"}]]
[:a.button.is-primary {:style {:marginLeft "1em"}
:disabled (-> @state ready-to-transfer? not)} "transfer"]]]])))
(defn page [state]
[:div
;[direct-send-confirmation]
[:div.content
[:h5 "Do transaction without passing by the exchange."]]
[direct-send]])
@@ -23,8 +23,9 @@
accounts (map :body (<! (async/into [] ar)))]
(reset! a-accounts accounts))))
(defn shit [urls accounts]
(defn page-wrapper [accounts]
[:div.content
[:h5 "View all the assets you own."]
[make-portfolio-summary-table accounts]])
@@ -32,4 +33,4 @@
(let [urls (map :skirmish.broker.account/url accounts)
a-accounts (atom [])]
(get-accounts-information urls a-accounts)
#(shit urls @a-accounts)))
#(page-wrapper @a-accounts)))
@@ -49,7 +49,9 @@
(defn create-currencies-table [securities]
[:div
[:div.content
[:h3 "Currencies"]]
[:h3 "Currencies"]
[:p "A " [:em "currency"] " is currency that pays interest rate in
its own nomination."]]
[:table.table.is-hoverable.is-fullwidth
table-head
[:tbody
@@ -58,7 +60,11 @@
(defn create-equities-table [securities]
[:div
[:div.content
[:h3 "Equities"]]
[:h3 "Equities"]
[:p "An " [:em "equity"] " is a type of security that pays dividends. In
real life, an equity (or stock) is a share in a company ownership.
When the company make profits, it shares the its profits between its stock
holder in a pro rata fashion."]]
[:table.table.is-hoverable.is-fullwidth
table-head
[:tbody
@@ -76,4 +82,7 @@
#(create-securities-table @securities)))
(defn page [state]
(security-table))
[:div
[:div.content
[:h5 "View the available securities for trading."]]
[security-table]])
@@ -6,12 +6,29 @@
(def done {:style {:text-decoration "line-through"}})
(defn filter-out-done [n]
(let [state (volatile! n)]
(fn [item]
(when (= (second item) done)
(vswap! state dec))
(or (not= (second item) done) (neg? @state)))))
(defn todo-list [& items]
(let [filter-fn (filter-out-done (- (count items) 20))
item-to-show (filter filter-fn items)]
[:ol
(for [item item-to-show]
item)]))
(defn page []
[:div.content
[:h5 "This still to do in this project. This is a jira for the poor (or for the geek)."]
[:ol
[:li "Broker"
[:p "the brooker should use the exchange-gateway and ledge as apis. Nothing else."]
[:ol
[todo-list
[:li done "implement basic login in the wui"]
[:li done "implement basic web server"]
[:li done "implement stupid login on server side."]
@@ -22,14 +39,18 @@
[:li done "make the " [:a {:href "/portfolio"} "portfolio"] " page representing the use accounts"]
[:li done "make a somewhat pretty " [:a {:href "/securities/dapm"} "security"] " page"]
[:li done "create two stocks"]
[:li on-it "add stocks to didier@gmail.com"]
[:li on-it "make something for urls on the backend"]
[:li done "add stocks to didier@gmail.com"]
[:li done "make something for urls on the backend"]
[:li on-it "add a wui for OTC"]
[:li "fix the stupid chat. This is an important feature for OTC users."]
[:li "make something that allows to send securities from one user to another"]
[:li "move 'routes' in 'rest'"]
[:li "add a little bit of professionalism on top of the broker api"]
[:li "make the api support transit and edn"]
[:li "implements interest rate in ledger."]
[:li "Make sure the ledger can have accounts"]
[:li "when a new user signs in, he should receive some sort of money"]
[:li "\u22EE"]
[:li done "when a new user signs in, he should receive some sort of money"]
[:li "implement send securities to friends"]
[:li "implement buy/sell securities."]
[:li "Use ledger as source of through in each sub server."]
[:li "Update web ui for integrating securities with ledger."]
+3 -2
View File
@@ -37,8 +37,9 @@
[:div.navbar-start
[:a.navbar-item.is-tab {:href "/" :class (top-bar-class state :home)} "Home"]
[:a.navbar-item.is-tab {:href "/securities" :class (top-bar-class state :securities)} "Securities"]
[:a.navbar-item.is-tab {:href "/create-order" :class (top-bar-class state :create-order)} "Create order"]
[:a.navbar-item.is-tab {:href "/portfolio" :class (top-bar-class state :view-portfolio)} "View Portfolio"]
[:a.navbar-item.is-tab {:href "/create-order" :class (top-bar-class state :create-order)} "Create Order"]
[:a.navbar-item.is-tab {:href "/otc" :class (top-bar-class state :otc)} "Over The Counter"]
[:a.navbar-item.is-tab {:href "/portfolio" :class (top-bar-class state :view-portfolio)} "Portfolio"]
[:a.navbar-item.is-tab {:href "/todo" :class (top-bar-class state :todo)} "Todo"]]
[:div.navbar-end
[:a.navbar-item {} "Skirmish Shitty Exchange"]
+7
View File
@@ -53,6 +53,12 @@
(session/put! :current-page #'pages/portfolio-page)
(swap! state #(into % {:page :portfolio})))
(secretary/defroute "/otc" []
(session/put! :current-page #'pages/otc-page)
(swap! state #(into % {:page :otc})))
(secretary/defroute "/create-order" []
(session/put! :current-page #'pages/create-order-page)
(swap! state #(into % {:page :create-order})))
@@ -77,6 +83,7 @@
[(session/get :current-page) @state]]]]])
(defn mount-root []
(reagent/render [current-page] (.getElementById js/document "app")))
+4 -1
View File
@@ -4,7 +4,8 @@
[skirmish.wui.components.portfolio-page]
[skirmish.wui.components.create-order-page]
[skirmish.wui.components.securities-page]
[skirmish.wui.components.security-page]))
[skirmish.wui.components.security-page]
[skirmish.wui.components.otc-page]))
(def home-page skirmish.wui.components.home-page/page)
@@ -17,3 +18,5 @@
(def portfolio-page skirmish.wui.components.portfolio-page/page)
(def security-page skirmish.wui.components.security-page/page)
(def otc-page skirmish.wui.components.otc-page/page)
+6 -1
View File
@@ -26,4 +26,9 @@
(if (<= dec 0)
(pp-int n)
(let [i (int (Math/pow 10 dec))]
(str (pp-int (quot n i)) "," (right-pad (str (mod n i)) "0" dec))))))
(str (pp-int (quot n i)) "," (right-pad (str (mod n i)) "0" dec))))))
(defn email? [s]
(let [email-pattern #"^([a-zA-Z0-9_\-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$"]
(->> s str (re-matches email-pattern) some?)))
+4
View File
@@ -25,6 +25,10 @@ $table-background-color: $grey-dark;
$table-cell-heading-color: $grey-darker;
$table-row-hover-background-color: $black-ter;
// box
$box-background-color: $grey;
$box-color: $white;
$box-shadow: 0 2px 3px $grey-light, 0 0 0 1px $grey-light;// $red;
@import "../../node_modules/bulma/sass/base/_all";