Fixed many bugs.
This commit is contained in:
1 parent
ad924ca5c7
commit
174319293c
10 files changed
+227
-94
No files matched your search
+2
-2
@@ -1,7 +1,7 @@
|
||||
(ns book.cli
|
||||
(:require [book.match :refer [sell?]]
|
||||
[book.util :refer :all]
|
||||
[book.exchange :refer [empty-book process-orders create-exchange-for-security]]
|
||||
[book.exchange :refer [empty-book create-exchange-for-security-2]]
|
||||
[book.order] ;; for spec side effect
|
||||
[slingshot.slingshot :refer [try+ throw+]]
|
||||
[clojure.edn :as edn]
|
||||
@@ -42,7 +42,7 @@
|
||||
(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)]
|
||||
[trades-channel new-book-order-channel] (create-exchange-for-security-2 initial-book incoming-order-channel)]
|
||||
(go (print-chan "trade > " trades-channel))
|
||||
(go (print-chan "order > " new-book-order-channel))
|
||||
(loop [order (read-order)]
|
||||
|
||||
+28
-22
@@ -4,37 +4,43 @@
|
||||
create-sell-book
|
||||
match-order-in-book]]
|
||||
[book.util :refer :all]
|
||||
[clojure.core.async :refer [chan go >!! <!!]]))
|
||||
[clojure.core.async :refer [chan go >!! <! <!! >! pub]]))
|
||||
|
||||
(def empty-book {:sell (create-sell-book) :buy (create-buy-book)})
|
||||
|
||||
|
||||
(defn process-abstract-sell [order reference-book same-type-book trades-channel new-book-order-channel]
|
||||
(defn process-abstract [order reference-book same-type-book]
|
||||
(let [[trades new-reference-book leftover-order] (match-order-in-book reference-book order)
|
||||
new-same-type-book (if leftover-order (conj same-type-book leftover-order) leftover-order)]
|
||||
(when (seq trades) (doseq [trade trades] (>!! trades-channel trade)))
|
||||
(when leftover-order (>!! new-book-order-channel leftover-order))
|
||||
[new-reference-book new-same-type-book]))
|
||||
;ff (println ">>=" same-type-book (type same-type-book));;
|
||||
;ff (println ">>=" leftover-order (type leftover-order))
|
||||
new-same-type-book (if leftover-order (conj same-type-book leftover-order) same-type-book)]
|
||||
[new-reference-book new-same-type-book leftover-order trades]))
|
||||
|
||||
|
||||
(defn process-sell-order [order {sell-book :sell buy-book :buy} trades-channel new-book-order-channel]
|
||||
(let [[new-buy-book new-sell-book] (process-abstract-sell order buy-book sell-book trades-channel new-book-order-channel)]
|
||||
{:sell new-sell-book :buy new-buy-book}))
|
||||
(defn process-sell-order-2 [{sell-book :sell buy-book :buy} order]
|
||||
(let [[new-buy-book new-sell-book new-order trades] (process-abstract order buy-book sell-book)
|
||||
new-book {:sell new-sell-book :buy new-buy-book}]
|
||||
[new-book new-order trades]))
|
||||
|
||||
(defn process-buy-order [order {sell-book :sell buy-book :buy} trades-channel new-book-order-channel]
|
||||
(let [[new-sell-book new-buy-book] (process-abstract-sell order sell-book buy-book trades-channel new-book-order-channel)]
|
||||
{:sell new-sell-book :buy new-buy-book}))
|
||||
(defn process-buy-order-2 [{sell-book :sell buy-book :buy} order]
|
||||
(let [[new-sell-book new-buy-book new-order trades] (process-abstract order sell-book buy-book)
|
||||
new-book {:sell new-sell-book :buy new-buy-book}]
|
||||
[new-book new-order trades]))
|
||||
|
||||
(defn process-orders [master-book orders-channel trades-channel new-book-order-channel]
|
||||
(loop [order (<!! orders-channel)
|
||||
book master-book]
|
||||
(let [process-order (if (sell? order) process-sell-order process-buy-order)
|
||||
new-book (process-order order book trades-channel new-book-order-channel)]
|
||||
(recur (<!! orders-channel) new-book))))
|
||||
(defn process-order-2 [book order]
|
||||
(if (sell? order)
|
||||
(process-sell-order-2 book order)
|
||||
(process-buy-order-2 book order)))
|
||||
|
||||
|
||||
(defn create-exchange-for-security [initial-book incoming-order-channel]
|
||||
(defn process-orders-2 [initial-book incoming-order-channel]
|
||||
(let [trades-channel (chan)
|
||||
new-book-order-channel (chan)]
|
||||
(go (process-orders initial-book incoming-order-channel trades-channel new-book-order-channel))
|
||||
[trades-channel new-book-order-channel]))
|
||||
(go (loop [order (<! incoming-order-channel) book initial-book]
|
||||
(let [[new-book new-order new-trades] (process-order-2 book order)]
|
||||
(when (not (nil? new-order)) (>! new-book-order-channel new-order))
|
||||
(doseq [trade new-trades] (>! trades-channel trade))
|
||||
(recur (<!! incoming-order-channel) new-book))))
|
||||
[(pub trades-channel :book.order/type) (pub new-book-order-channel :type)]))
|
||||
|
||||
(def create-exchange-for-security-2 process-orders-2)
|
||||
|
||||
+8
-19
@@ -74,8 +74,14 @@
|
||||
(defn compute-partial-order [order trade]
|
||||
(assoc order :quantity (- (:quantity order) (:book.order/quantity trade 0))))
|
||||
|
||||
(defn compute-new-book-after-trades [sell-book trades]
|
||||
(let [new-book (drop (-> trades count dec) sell-book)
|
||||
(defn- drop-in-sorted-list [n sorted-list]
|
||||
(cond
|
||||
(<= n 0) sorted-list
|
||||
(<= (count sorted-list) n) (empty sorted-list)
|
||||
:else (drop n sorted-list)));;
|
||||
|
||||
(defn compute-new-book-after-trades [book trades]
|
||||
(let [new-book (drop-in-sorted-list (-> trades count dec) book)
|
||||
possible-partial-trade (last trades)]
|
||||
(if (partial-trade? possible-partial-trade (first new-book))
|
||||
(conj (rest new-book) (compute-partial-order (first new-book) possible-partial-trade))
|
||||
@@ -93,20 +99,3 @@
|
||||
new-buy-order (leftover-order order trades)]
|
||||
[trades new-book new-buy-order]))
|
||||
|
||||
;(defn place-buy-order [book order]
|
||||
; (match-buy-order-in-sell-book (:sell book) order)
|
||||
; (println :bingo)) ;; should return multiple "trade" and a new book
|
||||
;
|
||||
;(defn place-order [book order]
|
||||
; (if (buy? order)
|
||||
; (place-buy-order book order)))
|
||||
;
|
||||
;
|
||||
;(defn -main
|
||||
; [& args]
|
||||
; (let [order (-> a+rgs first edn/read-string)]
|
||||
; (if (not (s/valid? :book.order/order order))
|
||||
; (println "invalid order :( ")
|
||||
; (println (place-order book order)))))
|
||||
;
|
||||
;
|
||||
@@ -36,8 +36,12 @@
|
||||
200)
|
||||
status))
|
||||
|
||||
(defn build-response-body [request body]
|
||||
(let [[_ serializer] (get-best-serializer request)]
|
||||
(serializer body)))
|
||||
|
||||
(defn build-response [request {status :status body :body :as response}]
|
||||
(let [[content-type serializer] (get-best-serializer request)]
|
||||
{:body (serializer response)
|
||||
{:body (serializer (if (nil? body) response body))
|
||||
:headers {"Content-Type" content-type}
|
||||
:status (get-status status request)}))
|
||||
+93
-27
@@ -1,14 +1,15 @@
|
||||
(ns book.web
|
||||
(:require [book.exchange :refer [empty-book]]
|
||||
[org.httpkit.server :refer [run-server]]
|
||||
[org.httpkit.server :refer [run-server with-channel send! close on-close]]
|
||||
[compojure.route :refer [not-found]]
|
||||
[compojure.handler :refer [api]]
|
||||
[compojure.core :refer :all]
|
||||
[book.order] ;; for spec side effect
|
||||
[book.rest.util :refer [build-response]]
|
||||
[book.exchange :refer [create-exchange-for-security-2]]
|
||||
[book.rest.util :refer [build-response build-response-body]]
|
||||
[clojure.spec.alpha :as s]
|
||||
[slingshot.slingshot :refer [try+ throw+]]
|
||||
[cheshire.core :refer [generate-string]]
|
||||
[clojure.core.async :refer [chan go go-loop close! <! >!! sub]]
|
||||
[clojure.edn :as edn]))
|
||||
|
||||
|
||||
@@ -29,49 +30,114 @@
|
||||
;;
|
||||
|
||||
|
||||
(defn securities [books]
|
||||
(defn create-exchange-api-for [sec-id]
|
||||
(let [initial-book empty-book
|
||||
incoming-order-channel (chan)
|
||||
[trades-publisher new-book-order-publisher] (create-exchange-for-security-2 initial-book incoming-order-channel)]
|
||||
{:sec-id sec-id
|
||||
:incoming-order-channel incoming-order-channel
|
||||
:trades-publisher trades-publisher
|
||||
:new-book-order-publisher new-book-order-publisher}))
|
||||
|
||||
|
||||
(defn get-new-order-channel [exchange-apis sec-id]
|
||||
(let [exchange (get exchange-apis sec-id)
|
||||
new-book-order-channel (:new-book-order-publisher exchange)
|
||||
ret (chan)]
|
||||
(sub new-book-order-channel :sell ret)
|
||||
(sub new-book-order-channel :buy ret)
|
||||
ret))
|
||||
|
||||
|
||||
(defn get-new-trades-channel [exchange-apis sec-id]
|
||||
(let [exchange (get exchange-apis sec-id)
|
||||
new-trades-channel (:trades-publisher exchange)
|
||||
ret (chan)]
|
||||
(sub new-trades-channel :sell ret)
|
||||
(sub new-trades-channel :buy ret)
|
||||
ret))
|
||||
|
||||
|
||||
(defn securities [exchange-apis]
|
||||
(fn [request]
|
||||
(build-response request {:securities (keys books)})))
|
||||
|
||||
(defn get-security [books]
|
||||
(fn [{{sec-id-str :sec-id} :route-params body :body :as request}]))
|
||||
|
||||
(defn validate-order [asked-order]
|
||||
(if (not (s/valid? :book.order/order asked-order))
|
||||
(throw+ {:status :4XX :headers {"Content-Type" "application/json"} :body "foo"}))
|
||||
(assoc asked-order :book.order/id (swap! order-id inc)))
|
||||
(build-response request {:securities (keys exchange-apis)})))
|
||||
|
||||
|
||||
(defn get-order [body]
|
||||
(defn get-security [exchange-apis]
|
||||
(fn [{{sec-id-str :sec-id} :route-params body :body :as request}]
|
||||
(build-response request {:security {:sec-id sec-id-str}})))
|
||||
|
||||
|
||||
(defn validate-order [asked-order sec-id]
|
||||
(let [now (System/currentTimeMillis)]
|
||||
(if (not (s/valid? :book.order/order asked-order))
|
||||
(throw+ {:error-type :4XX
|
||||
:status 400
|
||||
:body {:error "Invalid order."
|
||||
:spec (s/explain-data :book.order/order asked-order)}}))
|
||||
(into asked-order {:book.order/id (swap! order-id inc)
|
||||
:book.order/sec-id sec-id
|
||||
:book.order/entry-timestamp now})))
|
||||
|
||||
|
||||
(defn get-body [body]
|
||||
(try+
|
||||
(-> body clojure.java.io/reader slurp edn/read-string)
|
||||
(catch Exception e
|
||||
(throw+ {:error-type :4XX :status 400 :body {:error "Invalid body format. Body must be edn format."}}))))
|
||||
(throw+ {:error-type :4XX :status 400 :body {:error "Invalid body format. Body must be edn format."}}))))
|
||||
|
||||
|
||||
(defn create-order [books]
|
||||
(defn get-exchange-api [sec-id exchange-apis]
|
||||
(if (contains? exchange-apis sec-id)
|
||||
(get exchange-apis sec-id)
|
||||
(throw+ {:error-type :4XX :status 404 :body {:error "Security not available."}})))
|
||||
|
||||
|
||||
(defn create-order [exchange-apis]
|
||||
(fn [{{sec-id-str :sec-id} :route-params body :body :as request}]
|
||||
(try+
|
||||
(let [asked-order (get-order body)
|
||||
order (validate-order asked-order)
|
||||
sec-id (keyword sec-id-str)
|
||||
book (get books sec-id)]
|
||||
(build-response request {:order order :book book}))
|
||||
(let [asked-order (get-body body)
|
||||
exchange-api (get-exchange-api (keyword sec-id-str) exchange-apis)
|
||||
incoming-order-channel (:incoming-order-channel exchange-api)
|
||||
order (validate-order asked-order (:sec-id exchange-api))]
|
||||
(>!! incoming-order-channel order)
|
||||
(build-response request order))
|
||||
(catch [:error-type :4XX] e (build-response request e))
|
||||
(catch [:error-type :5XX] e (build-response request e)))))
|
||||
|
||||
|
||||
(defn create-routes [books]
|
||||
(defn chan-to-websocket [request chan ws-chan]
|
||||
(on-close ws-chan (fn [status] (close! chan)))
|
||||
(go-loop []
|
||||
(when-let [e (<! chan)]
|
||||
(send! ws-chan (build-response-body request e))
|
||||
(recur))))
|
||||
|
||||
(defn listen-to-orders [exchange-apis]
|
||||
(fn [{{sec-id-str :sec-id} :route-params :as request}]
|
||||
(with-channel request ws-chan
|
||||
(let [order-channel (get-new-order-channel exchange-apis (keyword sec-id-str))]
|
||||
(chan-to-websocket request order-channel ws-chan)))))
|
||||
|
||||
(defn listen-to-trades [exchange-apis]
|
||||
(fn [{{sec-id-str :sec-id} :route-params :as request}]
|
||||
(with-channel request ws-chan
|
||||
(let [order-channel (get-new-trades-channel exchange-apis (keyword sec-id-str))]
|
||||
(chan-to-websocket request order-channel ws-chan)))))
|
||||
|
||||
(defn create-routes [exchange-apis]
|
||||
(routes
|
||||
(context "/api/1" []
|
||||
(GET "/" [] api-description)
|
||||
(GET "/securities" [] (securities books))
|
||||
(GET "/securities/:sec-id" [] (get-security books))
|
||||
(POST "/securities/:sec-id" [] (create-order books)))
|
||||
(GET "/securities" [] (securities exchange-apis))
|
||||
(GET "/securities/:sec-id" [] (get-security exchange-apis))
|
||||
(POST "/securities/:sec-id" [] (create-order exchange-apis))
|
||||
(GET "/securities/:sec-id/trades" [] (listen-to-trades exchange-apis))
|
||||
(GET "/securities/:sec-id/orders" [] (listen-to-orders exchange-apis)))
|
||||
(not-found "not found")))
|
||||
|
||||
|
||||
(defn -main [& args]
|
||||
(let [books {:foo empty-book :bar empty-book}
|
||||
api-routes (create-routes books)]
|
||||
(let [exchange-apis {:foo (create-exchange-api-for :foo) :bar (create-exchange-api-for :bar)}
|
||||
api-routes (create-routes exchange-apis)]
|
||||
(run-server (api api-routes) {:port 8080})))
|
||||
@@ -13,11 +13,11 @@ public class PersistentSortedList extends Obj implements Iterable, RandomAccess,
|
||||
List, Counted{
|
||||
final private PersistentTreeMap impl;
|
||||
final private int _count;
|
||||
static public final PersistentSortedList EMPTY = new PersistentSortedList(null, PersistentTreeMap.EMPTY, 0);
|
||||
static private final PersistentSortedList EMPTY_DEFAULT_COMPARATOR = new PersistentSortedList(null, PersistentTreeMap.EMPTY, 0);
|
||||
private int _hasheq = 0;
|
||||
|
||||
public static PersistentSortedList create(ISeq items){
|
||||
PersistentSortedList ret = EMPTY;
|
||||
PersistentSortedList ret = EMPTY_DEFAULT_COMPARATOR;
|
||||
for(; items != null; items = items.next()){
|
||||
ret = ret.cons(items.first());
|
||||
}
|
||||
@@ -52,7 +52,9 @@ public class PersistentSortedList extends Obj implements Iterable, RandomAccess,
|
||||
|
||||
@Override
|
||||
public IPersistentCollection empty() {
|
||||
return PersistentSortedList.EMPTY;
|
||||
return new PersistentSortedList(null,
|
||||
new PersistentTreeMap(null, this.impl.comp),
|
||||
0);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -142,7 +144,7 @@ public class PersistentSortedList extends Obj implements Iterable, RandomAccess,
|
||||
@Override
|
||||
public ISeq more() {
|
||||
ISeq s = next();
|
||||
return s == null ? PersistentSortedList.EMPTY : s;
|
||||
return s == null ? (PersistentSortedList) empty() : s;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
Reference in new issue
Block a user