Fixed many bugs.
This commit is contained in:
1 parent
ad924ca5c7
commit
174319293c
10 files changed
+222
-89
No files matched your search
+2
-1
@@ -19,5 +19,6 @@
|
|||||||
:plugins [[lein-midje "3.2.1"]]
|
:plugins [[lein-midje "3.2.1"]]
|
||||||
:profiles {
|
:profiles {
|
||||||
:uberjar {:aot :all}
|
:uberjar {:aot :all}
|
||||||
:dev {:dependencies [[midje "1.9.1"]]}})
|
:dev {:jvm-opts ["-XX:-OmitStackTraceInFastThrow"]
|
||||||
|
:dependencies [[midje "1.9.1"]]}})
|
||||||
|
|
||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
(ns book.cli
|
(ns book.cli
|
||||||
(:require [book.match :refer [sell?]]
|
(:require [book.match :refer [sell?]]
|
||||||
[book.util :refer :all]
|
[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
|
[book.order] ;; for spec side effect
|
||||||
[slingshot.slingshot :refer [try+ throw+]]
|
[slingshot.slingshot :refer [try+ throw+]]
|
||||||
[clojure.edn :as edn]
|
[clojure.edn :as edn]
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
(defn -main [& args]
|
(defn -main [& args]
|
||||||
(let [initial-book empty-book
|
(let [initial-book empty-book
|
||||||
incoming-order-channel (chan)
|
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 "trade > " trades-channel))
|
||||||
(go (print-chan "order > " new-book-order-channel))
|
(go (print-chan "order > " new-book-order-channel))
|
||||||
(loop [order (read-order)]
|
(loop [order (read-order)]
|
||||||
|
|||||||
+28
-22
@@ -4,37 +4,43 @@
|
|||||||
create-sell-book
|
create-sell-book
|
||||||
match-order-in-book]]
|
match-order-in-book]]
|
||||||
[book.util :refer :all]
|
[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)})
|
(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)
|
(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)]
|
;ff (println ">>=" same-type-book (type same-type-book));;
|
||||||
(when (seq trades) (doseq [trade trades] (>!! trades-channel trade)))
|
;ff (println ">>=" leftover-order (type leftover-order))
|
||||||
(when leftover-order (>!! new-book-order-channel 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]))
|
[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]
|
(defn process-sell-order-2 [{sell-book :sell buy-book :buy} order]
|
||||||
(let [[new-buy-book new-sell-book] (process-abstract-sell order buy-book sell-book trades-channel new-book-order-channel)]
|
(let [[new-buy-book new-sell-book new-order trades] (process-abstract order buy-book sell-book)
|
||||||
{:sell new-sell-book :buy new-buy-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]
|
(defn process-buy-order-2 [{sell-book :sell buy-book :buy} order]
|
||||||
(let [[new-sell-book new-buy-book] (process-abstract-sell order sell-book buy-book trades-channel new-book-order-channel)]
|
(let [[new-sell-book new-buy-book new-order trades] (process-abstract order sell-book buy-book)
|
||||||
{:sell new-sell-book :buy new-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]
|
(defn process-order-2 [book order]
|
||||||
(loop [order (<!! orders-channel)
|
(if (sell? order)
|
||||||
book master-book]
|
(process-sell-order-2 book order)
|
||||||
(let [process-order (if (sell? order) process-sell-order process-buy-order)
|
(process-buy-order-2 book order)))
|
||||||
new-book (process-order order book trades-channel new-book-order-channel)]
|
|
||||||
(recur (<!! orders-channel) new-book))))
|
|
||||||
|
|
||||||
|
(defn process-orders-2 [initial-book incoming-order-channel]
|
||||||
(defn create-exchange-for-security [initial-book incoming-order-channel]
|
|
||||||
(let [trades-channel (chan)
|
(let [trades-channel (chan)
|
||||||
new-book-order-channel (chan)]
|
new-book-order-channel (chan)]
|
||||||
(go (process-orders initial-book incoming-order-channel trades-channel new-book-order-channel))
|
(go (loop [order (<! incoming-order-channel) book initial-book]
|
||||||
[trades-channel new-book-order-channel]))
|
(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]
|
(defn compute-partial-order [order trade]
|
||||||
(assoc order :quantity (- (:quantity order) (:book.order/quantity trade 0))))
|
(assoc order :quantity (- (:quantity order) (:book.order/quantity trade 0))))
|
||||||
|
|
||||||
(defn compute-new-book-after-trades [sell-book trades]
|
(defn- drop-in-sorted-list [n sorted-list]
|
||||||
(let [new-book (drop (-> trades count dec) sell-book)
|
(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)]
|
possible-partial-trade (last trades)]
|
||||||
(if (partial-trade? possible-partial-trade (first new-book))
|
(if (partial-trade? possible-partial-trade (first new-book))
|
||||||
(conj (rest new-book) (compute-partial-order (first new-book) possible-partial-trade))
|
(conj (rest new-book) (compute-partial-order (first new-book) possible-partial-trade))
|
||||||
@@ -93,20 +99,3 @@
|
|||||||
new-buy-order (leftover-order order trades)]
|
new-buy-order (leftover-order order trades)]
|
||||||
[trades new-book new-buy-order]))
|
[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)
|
200)
|
||||||
status))
|
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}]
|
(defn build-response [request {status :status body :body :as response}]
|
||||||
(let [[content-type serializer] (get-best-serializer request)]
|
(let [[content-type serializer] (get-best-serializer request)]
|
||||||
{:body (serializer response)
|
{:body (serializer (if (nil? body) response body))
|
||||||
:headers {"Content-Type" content-type}
|
:headers {"Content-Type" content-type}
|
||||||
:status (get-status status request)}))
|
:status (get-status status request)}))
|
||||||
+89
-23
@@ -1,14 +1,15 @@
|
|||||||
(ns book.web
|
(ns book.web
|
||||||
(:require [book.exchange :refer [empty-book]]
|
(: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.route :refer [not-found]]
|
||||||
[compojure.handler :refer [api]]
|
[compojure.handler :refer [api]]
|
||||||
[compojure.core :refer :all]
|
[compojure.core :refer :all]
|
||||||
[book.order] ;; for spec side effect
|
[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]
|
[clojure.spec.alpha :as s]
|
||||||
[slingshot.slingshot :refer [try+ throw+]]
|
[slingshot.slingshot :refer [try+ throw+]]
|
||||||
[cheshire.core :refer [generate-string]]
|
[clojure.core.async :refer [chan go go-loop close! <! >!! sub]]
|
||||||
[clojure.edn :as edn]))
|
[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]
|
(fn [request]
|
||||||
(build-response request {:securities (keys books)})))
|
(build-response request {:securities (keys exchange-apis)})))
|
||||||
|
|
||||||
(defn get-security [books]
|
|
||||||
(fn [{{sec-id-str :sec-id} :route-params body :body :as request}]))
|
|
||||||
|
|
||||||
(defn validate-order [asked-order]
|
(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))
|
(if (not (s/valid? :book.order/order asked-order))
|
||||||
(throw+ {:status :4XX :headers {"Content-Type" "application/json"} :body "foo"}))
|
(throw+ {:error-type :4XX
|
||||||
(assoc asked-order :book.order/id (swap! order-id inc)))
|
: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-order [body]
|
(defn get-body [body]
|
||||||
(try+
|
(try+
|
||||||
(-> body clojure.java.io/reader slurp edn/read-string)
|
(-> body clojure.java.io/reader slurp edn/read-string)
|
||||||
(catch Exception e
|
(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}]
|
(fn [{{sec-id-str :sec-id} :route-params body :body :as request}]
|
||||||
(try+
|
(try+
|
||||||
(let [asked-order (get-order body)
|
(let [asked-order (get-body body)
|
||||||
order (validate-order asked-order)
|
exchange-api (get-exchange-api (keyword sec-id-str) exchange-apis)
|
||||||
sec-id (keyword sec-id-str)
|
incoming-order-channel (:incoming-order-channel exchange-api)
|
||||||
book (get books sec-id)]
|
order (validate-order asked-order (:sec-id exchange-api))]
|
||||||
(build-response request {:order order :book book}))
|
(>!! incoming-order-channel order)
|
||||||
|
(build-response request order))
|
||||||
(catch [:error-type :4XX] e (build-response request e))
|
(catch [:error-type :4XX] e (build-response request e))
|
||||||
(catch [:error-type :5XX] 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
|
(routes
|
||||||
(context "/api/1" []
|
(context "/api/1" []
|
||||||
(GET "/" [] api-description)
|
(GET "/" [] api-description)
|
||||||
(GET "/securities" [] (securities books))
|
(GET "/securities" [] (securities exchange-apis))
|
||||||
(GET "/securities/:sec-id" [] (get-security books))
|
(GET "/securities/:sec-id" [] (get-security exchange-apis))
|
||||||
(POST "/securities/:sec-id" [] (create-order books)))
|
(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")))
|
(not-found "not found")))
|
||||||
|
|
||||||
|
|
||||||
(defn -main [& args]
|
(defn -main [& args]
|
||||||
(let [books {:foo empty-book :bar empty-book}
|
(let [exchange-apis {:foo (create-exchange-api-for :foo) :bar (create-exchange-api-for :bar)}
|
||||||
api-routes (create-routes books)]
|
api-routes (create-routes exchange-apis)]
|
||||||
(run-server (api api-routes) {:port 8080})))
|
(run-server (api api-routes) {:port 8080})))
|
||||||
@@ -13,11 +13,11 @@ public class PersistentSortedList extends Obj implements Iterable, RandomAccess,
|
|||||||
List, Counted{
|
List, Counted{
|
||||||
final private PersistentTreeMap impl;
|
final private PersistentTreeMap impl;
|
||||||
final private int _count;
|
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;
|
private int _hasheq = 0;
|
||||||
|
|
||||||
public static PersistentSortedList create(ISeq items){
|
public static PersistentSortedList create(ISeq items){
|
||||||
PersistentSortedList ret = EMPTY;
|
PersistentSortedList ret = EMPTY_DEFAULT_COMPARATOR;
|
||||||
for(; items != null; items = items.next()){
|
for(; items != null; items = items.next()){
|
||||||
ret = ret.cons(items.first());
|
ret = ret.cons(items.first());
|
||||||
}
|
}
|
||||||
@@ -52,7 +52,9 @@ public class PersistentSortedList extends Obj implements Iterable, RandomAccess,
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IPersistentCollection empty() {
|
public IPersistentCollection empty() {
|
||||||
return PersistentSortedList.EMPTY;
|
return new PersistentSortedList(null,
|
||||||
|
new PersistentTreeMap(null, this.impl.comp),
|
||||||
|
0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -142,7 +144,7 @@ public class PersistentSortedList extends Obj implements Iterable, RandomAccess,
|
|||||||
@Override
|
@Override
|
||||||
public ISeq more() {
|
public ISeq more() {
|
||||||
ISeq s = next();
|
ISeq s = next();
|
||||||
return s == null ? PersistentSortedList.EMPTY : s;
|
return s == null ? (PersistentSortedList) empty() : s;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+59
-17
@@ -1,28 +1,70 @@
|
|||||||
(ns book.exchange-test
|
(ns book.exchange-test
|
||||||
|
(:import [book.PersistentSortedList])
|
||||||
(:require [midje.sweet :refer :all]
|
(:require [midje.sweet :refer :all]
|
||||||
[book.exchange :refer :all]
|
[book.exchange :refer :all]
|
||||||
[book.match :refer [create-buy-book
|
[book.match :refer [create-buy-book
|
||||||
create-sell-book]]
|
create-sell-book]]))
|
||||||
[clojure.core.async :refer [chan go >!! <!!]]))
|
|
||||||
|
(def PersistentSortedList? (comp #(= % book.PersistentSortedList) type))
|
||||||
|
|
||||||
(facts "the exchange as an whole"
|
(facts "the exchange as an whole"
|
||||||
(fact "sell order followed by a buy order trivialities "
|
(fact "process-sell-order should work when book empty"
|
||||||
(let [incoming-order-channel (chan)
|
(let [book empty-book
|
||||||
trades-channel (chan)
|
order {:type :sell :quantity 10 :price 2 :book.order/id 1}
|
||||||
new-book-order-channel (chan)
|
expt-new-sell-book [order]
|
||||||
master-book {:sell (create-sell-book) :buy (create-buy-book)}]
|
[{new-sell-book :sell new-buy-book :buy} new-order new-trades] (process-sell-order-2 book order)]
|
||||||
(go (process-orders master-book incoming-order-channel trades-channel new-book-order-channel))
|
new-sell-book => expt-new-sell-book
|
||||||
(>!! incoming-order-channel {:type :sell :quantity 10 :price 2})
|
new-buy-book => empty?
|
||||||
(<!! new-book-order-channel) => {:type :sell :quantity 10 :price 2}
|
new-order => order
|
||||||
(>!! incoming-order-channel {:type :buy :quantity 20 :price 2})
|
new-trades => empty?))
|
||||||
(<!! trades-channel) => #:book.order{:book-id nil
|
|
||||||
:order-id nil
|
|
||||||
:price 2
|
|
||||||
:quantity 10
|
|
||||||
:type :sell}
|
|
||||||
(<!! new-book-order-channel) => {:type :buy :quantity 10 :price 2})))
|
|
||||||
|
|
||||||
|
(fact "process-sell-order should work when there is a buy order that matches"
|
||||||
|
(let [sell-book (create-sell-book)
|
||||||
|
buy-book (create-buy-book {:type :buy :quantity 10 :price 3 :book.order/id 2})
|
||||||
|
book {:sell sell-book :buy buy-book}
|
||||||
|
order {:type :sell :quantity 4 :price 2 :book.order/id 3}
|
||||||
|
expt-new-buy-book [{:price 3 :quantity 6 :type :buy :book.order/id 2}]
|
||||||
|
expt-new-trades [#:book.order{:book-id 2 :order-id 3 :price 3 :quantity 4 :type :buy}]
|
||||||
|
[{new-sell-book :sell new-buy-book :buy} new-order new-trades] (process-order-2 book order)]
|
||||||
|
new-sell-book => empty?
|
||||||
|
new-buy-book => expt-new-buy-book
|
||||||
|
new-order => nil?
|
||||||
|
new-trades => expt-new-trades))
|
||||||
|
|
||||||
|
(fact "process-buy-order should work when book empty"
|
||||||
|
(let [book empty-book
|
||||||
|
order {:type :buy :quantity 10 :price 2 :book.order/id 1}
|
||||||
|
expt-new-sell-book [order]
|
||||||
|
[{new-sell-book :sell new-buy-book :buy} new-order new-trades] (process-order-2 book order)]
|
||||||
|
new-sell-book => empty?
|
||||||
|
new-buy-book => expt-new-sell-book
|
||||||
|
new-order => order
|
||||||
|
new-trades => empty?))
|
||||||
|
|
||||||
|
(fact "process-buy-order should work when there is a buy order that matches"
|
||||||
|
(let [sell-book (create-sell-book {:type :sell :quantity 10 :price 3 :book.order/id 2})
|
||||||
|
buy-book (create-buy-book)
|
||||||
|
book {:sell sell-book :buy buy-book}
|
||||||
|
order {:type :buy :quantity 4 :price 4 :book.order/id 3}
|
||||||
|
expt-new-sell-book [{:price 3 :quantity 6 :type :sell :book.order/id 2}]
|
||||||
|
expt-new-trades [#:book.order{:book-id 2 :order-id 3 :price 3 :quantity 4 :type :sell}]
|
||||||
|
[{new-sell-book :sell new-buy-book :buy} new-order new-trades] (process-order-2 book order)]
|
||||||
|
new-sell-book => expt-new-sell-book
|
||||||
|
new-order => nil?
|
||||||
|
new-trades => expt-new-trades))
|
||||||
|
|
||||||
|
(fact "process-order total dance"
|
||||||
|
(let [book empty-book
|
||||||
|
order-1 {:type :buy :quantity 10 :price 100 :book.order/id 7}
|
||||||
|
order-2 {:type :buy :quantity 15 :price 99 :book.order/id 8}
|
||||||
|
order-3 {:type :sell :quantity 17 :price 99 :book.order/id 9}
|
||||||
|
order-4 {:type :sell :quantity 1 :price 102 :book.order/id 10}
|
||||||
|
expt-book-4 {:buy [{:price 99 :quantity 8 :type :buy :book.order/id 8}]
|
||||||
|
:sell [{:price 102 :quantity 1 :type :sell :book.order/id 10}]}
|
||||||
|
[book-1 _ _] (process-order-2 book order-1)
|
||||||
|
[book-2 _ _] (process-order-2 book-1 order-2)
|
||||||
|
[book-3 _ _] (process-order-2 book-2 order-3)
|
||||||
|
[book-4 _ _] (process-order-2 book-3 order-4)]
|
||||||
|
book-4 => expt-book-4)))
|
||||||
|
|
||||||
|
|
||||||
@@ -2,6 +2,9 @@
|
|||||||
(:require [midje.sweet :refer :all]
|
(:require [midje.sweet :refer :all]
|
||||||
[book.match :refer :all]))
|
[book.match :refer :all]))
|
||||||
|
|
||||||
|
(def PersistentSortedList? (comp #(= % book.PersistentSortedList) type))
|
||||||
|
|
||||||
|
|
||||||
(facts "matching buy order in sell book"
|
(facts "matching buy order in sell book"
|
||||||
(fact "Simplest case I."
|
(fact "Simplest case I."
|
||||||
(let [sell-book (create-sell-book {:type :sell :quantity 10 :price 2 :book.order/id 101})
|
(let [sell-book (create-sell-book {:type :sell :quantity 10 :price 2 :book.order/id 101})
|
||||||
@@ -102,3 +105,20 @@
|
|||||||
trades => [#:book.order{:quantity 2 :price 14 :book-id 116 :order-id 118 :type :buy}
|
trades => [#:book.order{:quantity 2 :price 14 :book-id 116 :order-id 118 :type :buy}
|
||||||
#:book.order{:quantity 8 :price 13 :book-id 117 :order-id 118 :type :buy}]
|
#:book.order{:quantity 8 :price 13 :book-id 117 :order-id 118 :type :buy}]
|
||||||
leftover-order => {:type :sell :quantity 11 :price 13 :book.order/id 118})))
|
leftover-order => {:type :sell :quantity 11 :price 13 :book.order/id 118})))
|
||||||
|
|
||||||
|
(facts "We should write tests for each bug found"
|
||||||
|
(fact "book type should always be book.PersistentSortedList"
|
||||||
|
(let [book (create-buy-book)
|
||||||
|
order {:type :sell :quantity 21 :price 13 :book.order/id 118}
|
||||||
|
[_ new-book _] (match-order-in-book book order)]
|
||||||
|
(count new-book) => 0
|
||||||
|
(type new-book) => book.PersistentSortedList)))
|
||||||
|
;(fact "should not fuck up"
|
||||||
|
; (let [order-1 {:type :buy :quantity 4 :price 4 :book.order/id 5}
|
||||||
|
; order-2 {:type :sell :quantity 2 :price 3 :book.order/id 6}
|
||||||
|
; book (create-buy-book order-1)
|
||||||
|
; expt-trade #:book.order{}
|
||||||
|
; [trades new-book new-order] (match-order-in-book book order-2)]
|
||||||
|
; trades => 1
|
||||||
|
; new-order => nil?
|
||||||
|
; new-book => {})))
|
||||||
@@ -30,6 +30,9 @@
|
|||||||
(fact "drop"
|
(fact "drop"
|
||||||
(drop 2 (sorted-list 1 2 3)) => (sorted-list 3)
|
(drop 2 (sorted-list 1 2 3)) => (sorted-list 3)
|
||||||
(drop 3 (sorted-list 1 2 3)) => (list))
|
(drop 3 (sorted-list 1 2 3)) => (list))
|
||||||
|
(fact "empty"
|
||||||
|
(empty (sorted-list 2 3 4)) => (sorted-list)
|
||||||
|
(into (empty (sorted-list-by #(- %2 %1) 2 3 4)) [20 30]) => (sorted-list-by #(- %2 %1) 20 30))
|
||||||
(fact "reduce"
|
(fact "reduce"
|
||||||
(reduce + (sorted-list 1 2 3)) => 6
|
(reduce + (sorted-list 1 2 3)) => 6
|
||||||
(reduce + 10 (sorted-list 1 2 3)) => 16)
|
(reduce + 10 (sorted-list 1 2 3)) => 16)
|
||||||
|
|||||||
Reference in new issue
Block a user