From 76309c4153dee2ebdf4f79828f55f08c082cf5b2 Mon Sep 17 00:00:00 2001 From: ekzyis Date: Sat, 9 Sep 2023 22:52:51 +0200 Subject: [PATCH] Show order book + focus on binary market --- init.sql | 8 +-- pages/bmarket_order.html | 83 ------------------------- pages/bmarket_trade.html | 89 -------------------------- pages/market.html | 118 +++++++++++++++++++++++++++++++++++ public/index.css | 6 ++ public/market.css | 2 +- public/order.css | 10 +-- public/order.js | 16 +++++ public/trade.js | 131 --------------------------------------- src/auth.go | 2 +- src/db.go | 43 ++----------- src/market.go | 64 +++++-------------- src/server.go | 5 +- 13 files changed, 173 insertions(+), 404 deletions(-) delete mode 100644 pages/bmarket_order.html delete mode 100644 pages/bmarket_trade.html create mode 100644 pages/market.html delete mode 100644 public/trade.js diff --git a/init.sql b/init.sql index 814133e..14c4e7f 100644 --- a/init.sql +++ b/init.sql @@ -31,10 +31,6 @@ CREATE TABLE orders( pubkey TEXT NOT NULL REFERENCES users(pubkey), side ORDER_SIDE NOT NULL, quantity BIGINT NOT NULL, - price BIGINT NOT NULL -); -CREATE TABLE trades( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - order_id_1 UUID NOT NULL REFERENCES orders(id), - order_id_2 UUID NOT NULL REFERENCES orders(id) + price BIGINT NOT NULL, + order_id UUID REFERENCES orders(id) ); diff --git a/pages/bmarket_order.html b/pages/bmarket_order.html deleted file mode 100644 index 563c6f6..0000000 --- a/pages/bmarket_order.html +++ /dev/null @@ -1,83 +0,0 @@ - - - - - delphi.market - - - - - - - - - - {{ if eq .ENV "development" }} - - {{ end }} - - - -
- -
-
- - -
-                      _        _   
- _ __ ___   __ _ _ __| | _____| |_ 
-| '_ ` _ \ / _` | '__| |/ / _ \ __|
-| | | | | | (_| | |  |   <  __/ |_ 
-|_| |_| |_|\__,_|_|  |_|\_\___|\__|
-
-
-
{{.Description}}
-
- Orders - Trade -
-
- {{ range .Shares }} - {{.Description}} - {{ end }} -
- - - - - - - - {{ range .OrderBook }} - - - - - - - {{ end }} -
QuantityPricePriceQuantity
{{.BuyQuantity}} -
- - {{.BuyPrice}} -
-
-
- {{.SellPrice}} - -
-
{{.SellQuantity}}
-
- - - - \ No newline at end of file diff --git a/pages/bmarket_trade.html b/pages/bmarket_trade.html deleted file mode 100644 index e6071da..0000000 --- a/pages/bmarket_trade.html +++ /dev/null @@ -1,89 +0,0 @@ - - - - - delphi.market - - - - - - - - - - {{ if eq .ENV "development" }} - - {{ end }} - - - -
- -
-
- - -
-                      _        _   
- _ __ ___   __ _ _ __| | _____| |_ 
-| '_ ` _ \ / _` | '__| |/ / _ \ __|
-| | | | | | (_| | |  |   <  __/ |_ 
-|_| |_| |_|\__,_|_|  |_|\_\___|\__|
-
-
-
{{.Description}}
-
- Orders - Trade -
-
- {{ range .Shares }} {{ if eq .Description "YES" }} - - {{ else }} - - {{ end }} - {{ end }} -
- {{ range .Shares }} {{ if eq .Description "YES" }} - - {{ else }} - - {{ end }} - {{ end }} -
- - - - \ No newline at end of file diff --git a/pages/market.html b/pages/market.html new file mode 100644 index 0000000..b66c4c0 --- /dev/null +++ b/pages/market.html @@ -0,0 +1,118 @@ + + + + + delphi.market + + + + + + + + + + {{ if eq .ENV "development" }} + + {{ end }} + + + +
+ +
+
+ + +
+                      _        _   
+ _ __ ___   __ _ _ __| | _____| |_ 
+| '_ ` _ \ / _` | '__| |/ / _ \ __|
+| | | | | | (_| | |  |   <  __/ |_ 
+|_| |_| |_|\__,_|_|  |_|\_\___|\__|
+
+
+
{{.Description}}
+
Order Book
+ + + + + + + {{ range .Orders }} + + {{ if and (eq .ShareId $.YesShare.Id) (eq .Side "BUY") }} + + {{ else }} + + {{ end }} + {{ if and (eq .ShareId $.YesShare.Id) (eq .Side "SELL") }} + + {{ else }} + + {{ end }} + {{ if and (eq .ShareId $.NoShare.Id) (eq .Side "SELL") }} + + {{ else }} + + {{ end }} + {{ if and (eq .ShareId $.NoShare.Id) (eq .Side "BUY") }} + + {{ else }} + + {{ end }} + + {{ end }} +
BUY YESSELLBUY NO
+
+ YES + {{.Quantity}} @ {{.Price}} +
+
+
+ YES + {{.Quantity}} @ {{.Price}} +
+
+
+ NO + {{.Quantity}} @ {{.Price}} +
+
+
+ NO + {{.Quantity}} @ {{.Price}} +
+
+
+
Order Form
+ +
+ + + + \ No newline at end of file diff --git a/public/index.css b/public/index.css index 31022ec..bdff7b2 100644 --- a/public/index.css +++ b/public/index.css @@ -131,6 +131,12 @@ ul { .mb-1 { margin-bottom: 1em; } +.mb-s { + margin-bottom: 0.2em; +} +.mr-s { + margin-right: 0.2em; +} .pt-1 { padding-top: 1em; } diff --git a/public/market.css b/public/market.css index 7847bfc..187bf0b 100644 --- a/public/market.css +++ b/public/market.css @@ -6,7 +6,7 @@ } .order-form { - display: none; + display: grid; grid-template-columns: 1fr 1fr; grid-template-rows: 1fr 1fr; } diff --git a/public/order.css b/public/order.css index dd089d2..db829a4 100644 --- a/public/order.css +++ b/public/order.css @@ -1,11 +1,11 @@ .align-left { text-align: left; - } +} - .align-center { +.align-center { text-align: center; - } +} - .align-right { +.align-right { text-align: right; - } \ No newline at end of file +} \ No newline at end of file diff --git a/public/order.js b/public/order.js index e69de29..80e77f9 100644 --- a/public/order.js +++ b/public/order.js @@ -0,0 +1,16 @@ +const marketId = document.querySelector("#market-id").value + +const buyBtn = document.querySelector("#buy") +const sellBtn = document.querySelector("#sell") +const sideInput = document.querySelector("#side") + +buyBtn.onclick = function (e) { + buyBtn.classList.add("selected") + sellBtn.classList.remove("selected") + sideInput.setAttribute("value", "BUY") +} +sellBtn.onclick = function(e) { + buyBtn.classList.remove("selected") + sellBtn.classList.add("selected") + sideInput.setAttribute("value", 'SELL') +} diff --git a/public/trade.js b/public/trade.js deleted file mode 100644 index 32d54f5..0000000 --- a/public/trade.js +++ /dev/null @@ -1,131 +0,0 @@ -const marketId = document.querySelector("#market-id").value -const yesShareId = document.querySelector("#yes-share").value -const noShareId = document.querySelector("#no-share").value - -const yesOrderBtn = document.querySelector("#yes-order") -const yesForm = document.querySelector("#yes-form") -const yesBuyBtn = document.querySelector("#yes-buy") -const yesSellBtn = document.querySelector("#yes-sell") -const yesSideInput = document.querySelector("#yes-side") -const yesQuantityInput = document.querySelector("#yes-quantity") -const yesCostDisplay = document.querySelector("#yes-cost") -const yesCostLabel = document.querySelector("#yes-cost-label") -const yesSubmitLabel = document.querySelector("#yes-submit-label") - -const noOrderBtn = document.querySelector("#no-order") -const noForm = document.querySelector("#no-form") -const noBuyBtn = document.querySelector("#no-buy") -const noSellBtn = document.querySelector("#no-sell") -const noSideInput = document.querySelector("#no-side") -const noQuantityInput = document.querySelector("#no-quantity") -const noCostDisplay = document.querySelector("#no-cost") -const noCostLabel = document.querySelector("#no-cost-label") -const noSubmitLabel = document.querySelector("#no-submit-label") - -function resetInputs() { - yesQuantityInput.value = undefined - yesCostDisplay.value = undefined - noQuantityInput.value = undefined - noCostDisplay.value = undefined -} - -function toggleYesForm() { - resetInputs() - if (yesOrderBtn.classList.contains("selected")) { - yesOrderBtn.classList.remove("selected") - yesForm.style.display = "none" - } - else { - yesOrderBtn.classList.add("selected") - yesForm.style.display = "grid" - } - noOrderBtn.classList.remove("selected") - noForm.style.display = "none" -} -yesOrderBtn.onclick = toggleYesForm -toggleYesForm() - -function toggleNoForm() { - resetInputs() - if (noOrderBtn.classList.contains("selected")) { - noOrderBtn.classList.remove("selected") - noForm.style.display = "none" - } else { - noOrderBtn.classList.add("selected") - noForm.style.display = "grid" - } - yesOrderBtn.classList.remove("selected") - yesForm.style.display = "none" -} -noOrderBtn.onclick = toggleNoForm - -function showBuyForm() { - resetInputs() - yesBuyBtn.classList.add("selected") - yesSellBtn.classList.remove("selected") - yesSubmitLabel.textContent = 'BUY YES shares' - yesSideInput.value = "BUY" - - noBuyBtn.classList.add("selected") - noSellBtn.classList.remove("selected") - noSubmitLabel.textContent = 'BUY NO shares' - noSideInput.value = "BUY" -} -function showSellForm() { - resetInputs() - yesBuyBtn.classList.remove("selected") - yesSellBtn.classList.add("selected") - yesSubmitLabel.textContent = 'SELL NO shares' - yesSideInput.value = "SELL" - - noBuyBtn.classList.remove("selected") - noSellBtn.classList.add("selected") - noSubmitLabel.textContent = 'SELL YES shares' - noSideInput.value = "SELL" -} -yesBuyBtn.onclick = showBuyForm -yesSellBtn.onclick = showSellForm -noBuyBtn.onclick = showBuyForm -noSellBtn.onclick = showSellForm - -function debounce(ms) { - let debounceTimeout = null - return function (fn, ...args) { - return function (e) { - if (debounceTimeout) { - clearTimeout(debounceTimeout) - } - debounceTimeout = setTimeout(() => { - fn(...args)(e) - debounceTimeout = null - }, ms) - } - } -} - -function updatePrice(marketId, shareId) { - return async function (e) { - const quantity = parseInt(e.target.value, 10) - const body = { - share_id: shareId, - quantity, - side: yesSideInput.value - } - const rBody = await fetch(`/api/market/${marketId}/cost`, { - method: "POST", - headers: { - "Content-type": "application/json" - }, - body: JSON.stringify(body) - }) - .then(r => r.json()) - .catch((err) => { - console.error(err); - return null - }) - if (!rBody) return null; - yesCostDisplay.value = parseFloat(Math.abs(rBody.cost)).toFixed(3) - } -} -// yesQuantityInput.oninput = debounce(250)(updatePrice, marketId, yesShareId) -// noQuantityInput.onchange = debounce(250)(updatePrice, marketId, noShareId) diff --git a/src/auth.go b/src/auth.go index 764a36c..20e14af 100644 --- a/src/auth.go +++ b/src/auth.go @@ -28,7 +28,7 @@ type LnAuthResponse struct { } type Session struct { - pubkey string + Pubkey string } func lnAuth() (*LnAuth, error) { diff --git a/src/db.go b/src/db.go index b453e54..9b03fd0 100644 --- a/src/db.go +++ b/src/db.go @@ -68,50 +68,19 @@ func (db *DB) FetchShares(marketId int, shares *[]Share) error { return nil } -func (db *DB) FetchOrderBook(shareId string, orderBook *[]OrderBookEntry) error { +func (db *DB) FetchOrders(marketId int, orders *[]Order) error { rows, err := db.Query(""+ - "SELECT share_id, side, price, SUM(quantity)"+ - "FROM orders WHERE share_id = $1"+ - "GROUP BY (share_id, side, price)"+ - "ORDER BY share_id DESC, side DESC, price DESC", shareId) + "SELECT id, share_id, pubkey, side, quantity, price, order_id FROM orders "+ + "WHERE share_id = ANY(SELECT id FROM shares WHERE market_id = $1) "+ + "ORDER BY price DESC", marketId) if err != nil { return err } defer rows.Close() - buyOrders := []Order{} - sellOrders := []Order{} for rows.Next() { var order Order - rows.Scan(&order.ShareId, &order.Side, &order.Price, &order.Quantity) - if order.Side == "BUY" { - buyOrders = append(buyOrders, Order{Price: order.Price, Quantity: order.Quantity}) - } else { - sellOrders = append(sellOrders, Order{Price: order.Price, Quantity: order.Quantity}) - } - } - buySum := 0 - sellSum := 0 - for i := 0; i < Max(len(buyOrders), len(sellOrders)); i++ { - buyPrice, buyQuantity, sellQuantity, sellPrice := 0, 0, 0, 0 - if i < len(buyOrders) { - buyPrice = buyOrders[i].Price - buyQuantity = buySum + buyOrders[i].Quantity - } - if i < len(sellOrders) { - sellPrice = sellOrders[i].Price - sellQuantity = sellSum + sellOrders[i].Quantity - } - buySum += buyQuantity - sellSum += sellQuantity - *orderBook = append( - *orderBook, - OrderBookEntry{ - BuyQuantity: buyQuantity, - BuyPrice: buyPrice, - SellPrice: sellPrice, - SellQuantity: sellQuantity, - }, - ) + rows.Scan(&order.Id, &order.ShareId, &order.Pubkey, &order.Side, &order.Quantity, &order.Price, &order.OrderId) + *orders = append(*orders, order) } return nil } diff --git a/src/market.go b/src/market.go index 6aee60a..42d63a7 100644 --- a/src/market.go +++ b/src/market.go @@ -2,7 +2,7 @@ package main import ( "database/sql" - "fmt" + "log" "math" "net/http" "strconv" @@ -23,23 +23,13 @@ type Share struct { } type Order struct { + Session + Id string ShareId string Side string Price int Quantity int -} - -type OrderBookEntry struct { - BuyQuantity int - BuyPrice int - SellPrice int - SellQuantity int -} - -type MarketDataRequest struct { - ShareId string `json:"share_id"` - OrderSide string `json:"side"` - Quantity int `json:"quantity"` + OrderId string } func costFunction(b float64, q1 float64, q2 float64) float64 { @@ -57,37 +47,16 @@ func BinaryLMSR(invariant int, funding int, q1 int, q2 int, dq1 int) float64 { return costFunction(b, fq1+fdq1, fq2) - costFunction(b, fq1, fq2) } -func trades(c echo.Context) error { - marketId, err := strconv.ParseInt(c.Param("id"), 10, 64) - if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, "Bad Request") - } - var market Market - if err = db.FetchMarket(int(marketId), &market); err == sql.ErrNoRows { - return echo.NewHTTPError(http.StatusNotFound, "Not Found") - } else if err != nil { - return err - } - var shares []Share - if err = db.FetchShares(market.Id, &shares); err != nil { - return err - } - data := map[string]any{ - "session": c.Get("session"), - "ENV": ENV, - "Id": market.Id, - "Description": market.Description, - "Shares": shares, - } - return c.Render(http.StatusOK, "bmarket_trade.html", data) +func order(c echo.Context) error { + // TODO: implement POST /market/:id/order + return echo.NewHTTPError(http.StatusMethodNotAllowed, "Method Not Allowed") } -func orders(c echo.Context) error { +func market(c echo.Context) error { marketId, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, "Bad Request") } - shareId := c.Param("sid") var market Market if err = db.FetchMarket(int(marketId), &market); err == sql.ErrNoRows { @@ -100,11 +69,8 @@ func orders(c echo.Context) error { if err = db.FetchShares(market.Id, &shares); err != nil { return err } - if shareId == "" { - c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("/market/%d/%s", market.Id, shares[0].Id)) - } - var orderBook []OrderBookEntry - if err = db.FetchOrderBook(shareId, &orderBook); err != nil { + var orders []Order + if err = db.FetchOrders(market.Id, &orders); err != nil { return err } data := map[string]any{ @@ -112,9 +78,11 @@ func orders(c echo.Context) error { "ENV": ENV, "Id": market.Id, "Description": market.Description, - "ShareId": shareId, - "Shares": shares, - "OrderBook": orderBook, + // shares are sorted by description in descending order + // that's how we know that YES must be the first share + "YesShare": shares[0], + "NoShare": shares[1], + "Orders": orders, } - return c.Render(http.StatusOK, "bmarket_order.html", data) + return c.Render(http.StatusOK, "market.html", data) } diff --git a/src/server.go b/src/server.go index 3ca0057..4b5495d 100644 --- a/src/server.go +++ b/src/server.go @@ -63,9 +63,8 @@ func main() { e.GET("/api/login", verifyLogin) e.GET("/api/session", checkSession) e.POST("/logout", logout) - e.GET("/market/:id", sessionGuard(orders)) - e.GET("/market/:id/:sid", sessionGuard(orders)) - e.GET("/market/:id/trade", sessionGuard(trades)) + e.GET("/market/:id", sessionGuard(market)) + e.POST("/market/:id/order", sessionGuard(order)) e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{ Format: "${time_custom} ${method} ${uri} ${status}\n", CustomTimeFormat: "2006-01-02 15:04:05.00000-0700",