diff --git a/.gitignore b/.gitignore index e25ba41..1511d86 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ pom.xml.asc .idea/ book.iml skirmish.iml +full-stack diff --git a/README.md b/README.md index ebed2b2..18f4e06 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,14 @@ -# book +# Skrimish + +![logo](doc/logo.svg "Skrimish") + +A stock exchange simulator. The scope of this project is not yet defined but it should +allow end user(s?) to exchange securities via the common exchange rules. -FIXME: description ## Installation +Okay, this should be easy but it is hard... -Download from http://example.com/FIXME. ## Usage @@ -12,25 +16,11 @@ FIXME: explanation $ java -jar book-0.1.0-standalone.jar [args] -## Options -FIXME: listing of options this app accepts. - -## Examples - -... - -### Bugs - -... - -### Any Other Sections -### That You Think -### Might be Useful ## License -Copyright © 2018 FIXME +Copyright © 2018 didier amyot Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version. diff --git a/doc/logo.svg b/doc/logo.svg new file mode 100644 index 0000000..1c7137e --- /dev/null +++ b/doc/logo.svg @@ -0,0 +1,7 @@ + + + + + Svg Vector Icons : http://www.onlinewebfonts.com/icon + + \ No newline at end of file diff --git a/src/skirmish/exchange.clj b/src/skirmish/exchange.clj index e832dd7..34c133c 100644 --- a/src/skirmish/exchange.clj +++ b/src/skirmish/exchange.clj @@ -6,7 +6,47 @@ [skirmish.util :refer :all] [clojure.core.async :refer [chan go >! ! new-book-order-channel new-order)) - (doseq [trade new-trades] (>! trades-channel trade)) + (doseq [trade new-trades] (>! trades-channel trade)) ;; TODO: user clojure.core.async/onto-chan (recur (= (:price book-order) (:price order))) -(defn buy-order-fullfillable? [book-order order] +(defn- buy-order-fullfillable? [book-order order] (<= (:price book-order) (:price order))) -(defn create-sell-trade-factory [order-to-fulfill] +(defn- create-sell-trade-factory [order-to-fulfill] (fn [trade book-order] (let [already-fulfilled (:book.order/already-fulfilled trade 0) qtn-to-fulfill (min (:quantity book-order) (- (:quantity order-to-fulfill) already-fulfilled)) @@ -44,7 +44,7 @@ :order-id (:book.order/id order-to-fulfill) :book-id (:book.order/id book-order)})))) -(defn create-buy-trade-factory [order-to-fulfill] +(defn- create-buy-trade-factory [order-to-fulfill] (fn [trade book-order] (let [already-fulfilled (:book.order/already-fulfilled trade 0) qtn-to-fulfill (min (:quantity book-order) (- (:quantity order-to-fulfill) already-fulfilled)) @@ -57,21 +57,21 @@ :order-id (:book.order/id order-to-fulfill) :book-id (:book.order/id book-order)})))) -(defn create-trade-factory [order-to-fulfill] +(defn- create-trade-factory [order-to-fulfill] (if (sell? order-to-fulfill) (create-sell-trade-factory order-to-fulfill) (create-buy-trade-factory order-to-fulfill))) -(defn create-trades [order-to-fulfill sell-book] +(defn- create-trades [order-to-fulfill sell-book] (let [create-trade (create-trade-factory order-to-fulfill) trades (reductions create-trade {} sell-book) ret (rest (take-while some? trades))] (map #(dissoc % :book.order/already-fulfilled) ret))) -(defn partial-trade? [trade order] +(defn- partial-trade? [trade order] (not= (:quantity order) (:book.order/quantity trade))) -(defn compute-partial-order [order trade] +(defn- compute-partial-order [order trade] (assoc order :quantity (- (:quantity order) (:book.order/quantity trade 0)))) (defn- drop-in-sorted-list [n sorted-list] @@ -80,22 +80,25 @@ (<= (count sorted-list) n) (empty sorted-list) :else (drop n sorted-list)));; -(defn compute-new-book-after-trades [book trades] +(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)) (rest new-book)))) -(defn leftover-order [order trades] +(defn- leftover-order [order trades] (let [traded-quantity (reduce + (map :book.order/quantity trades)) leftover-quantity (- (:quantity order) traded-quantity)] (when (pos? leftover-quantity) (assoc order :quantity leftover-quantity)))) (defn match-order-in-book [book order] + "Matches the order within the book. Returns the applied trades, + the resulting book and left over order if the input order has not + been entirely fulfilled." (let [trades (create-trades order book) new-book (compute-new-book-after-trades book trades) - new-buy-order (leftover-order order trades)] - [trades new-book new-buy-order])) + leftover-order (leftover-order order trades)] + [trades new-book leftover-order])) diff --git a/src/skirmish/order.clj b/src/skirmish/order.clj index 604b4da..a505369 100644 --- a/src/skirmish/order.clj +++ b/src/skirmish/order.clj @@ -11,6 +11,17 @@ (s/def :book.order/price pos-int?) (s/def :book.order/quantity pos-int?) (s/def :book.order/id pos-int?) +(s/def :book.order/status #{:inserted ;; maybe called acknklowdlge. + :partially-matched + :totally-matched + :inserted-into-book + :partially-fulfilled + :totally-fulfilled + :withdrawn}) ;; for instance the order has been canceled + + + +(s/def :book.order/status-dual-id :book.order/id) (s/def :book.order/order (s/keys :req-un [:book.order/type :book.order/price :book.order/quantity] :opt [:book.order/id])) ;; A trade is defined by the bellow map... diff --git a/test/skirmish/exchange_test.clj b/test/skirmish/exchange_test.clj index 22752b2..2464364 100644 --- a/test/skirmish/exchange_test.clj +++ b/test/skirmish/exchange_test.clj @@ -3,7 +3,8 @@ (:require [midje.sweet :refer :all] [skirmish.exchange :refer :all] [skirmish.match :refer [create-buy-book - create-sell-book]])) + create-sell-book + match-order-in-book]])) (def PersistentSortedList? (comp #(= % skirmish.PersistentSortedList) type)) @@ -21,7 +22,7 @@ (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} + book {:sell sell-book :buy buy-book :id 0} 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}] @@ -44,7 +45,7 @@ (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} + book {:sell sell-book :buy buy-book :id 0} 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}] @@ -60,11 +61,22 @@ 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}]} + :sell [{:price 102 :quantity 1 :type :sell :book.order/id 10}] + :id 4} [book-1 _ _] (process-order book order-1) [book-2 _ _] (process-order book-1 order-2) [book-3 _ _] (process-order book-2 order-3) [book-4 _ _] (process-order book-3 order-4)] book-4 => expt-book-4))) +(facts "get-processed-orders" + (fact "ss" + (let [sell-book (create-buy-book {:type :buy :quantity 2 :price 14 :book.order/id 116} + {:type :buy :quantity 8 :price 13 :book.order/id 117}) + order {:type :sell :quantity 21 :price 13 :book.order/id 118} + [trades new-book leftover-order] (match-order-in-book sell-book order) + processed-order (get-processed-orders order leftover-order trades sell-book)] + processed-order => seq)));;; + +