Show order book + focus on binary market
This commit is contained in:
parent
8eecf1a981
commit
76309c4153
8
init.sql
8
init.sql
@ -31,10 +31,6 @@ CREATE TABLE orders(
|
|||||||
pubkey TEXT NOT NULL REFERENCES users(pubkey),
|
pubkey TEXT NOT NULL REFERENCES users(pubkey),
|
||||||
side ORDER_SIDE NOT NULL,
|
side ORDER_SIDE NOT NULL,
|
||||||
quantity BIGINT NOT NULL,
|
quantity BIGINT NOT NULL,
|
||||||
price BIGINT NOT NULL
|
price BIGINT NOT NULL,
|
||||||
);
|
order_id UUID REFERENCES orders(id)
|
||||||
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)
|
|
||||||
);
|
);
|
||||||
|
@ -1,83 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<title>delphi.market</title>
|
|
||||||
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
|
|
||||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
|
|
||||||
<link rel="manifest" href="/site.webmanifest" />
|
|
||||||
<link rel="stylesheet" href="/index.css" />
|
|
||||||
<link rel="stylesheet" href="/market.css" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
<meta name="theme-color" content="#091833" />
|
|
||||||
{{ if eq .ENV "development" }}
|
|
||||||
<script defer src="/hotreload.js"></script>
|
|
||||||
{{ end }}
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<header class="flex flex-row text-center justify-center pt-1">
|
|
||||||
<nav>
|
|
||||||
<a href="/">home</a>
|
|
||||||
{{ if .session }}
|
|
||||||
<form action='/logout' method='post'>
|
|
||||||
<button type='submit'>logout</button>
|
|
||||||
</form>
|
|
||||||
{{ else }} <a href="/login">login</a> {{ end }}
|
|
||||||
</nav>
|
|
||||||
</header>
|
|
||||||
<div class="container flex flex-column text-center">
|
|
||||||
<code>
|
|
||||||
<strong>
|
|
||||||
<pre>
|
|
||||||
_ _
|
|
||||||
_ __ ___ __ _ _ __| | _____| |_
|
|
||||||
| '_ ` _ \ / _` | '__| |/ / _ \ __|
|
|
||||||
| | | | | | (_| | | | < __/ |_
|
|
||||||
|_| |_| |_|\__,_|_| |_|\_\___|\__|</pre>
|
|
||||||
</strong>
|
|
||||||
</code>
|
|
||||||
<div class="font-mono mb-1">{{.Description}}</div>
|
|
||||||
<div class="flex justify-start mb-1">
|
|
||||||
<a class="mx-1 selected" href="/market/{{.Id}}">Orders</a>
|
|
||||||
<a class="mx-1" href="/market/{{.Id}}/trade">Trade</a>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-row justify-center mb-1">
|
|
||||||
{{ range .Shares }}
|
|
||||||
<a class="mx-1 {{ if eq $.ShareId .Id }}selected{{end}}"
|
|
||||||
href="/market/{{$.Id}}/{{.Id}}">{{.Description}}</a>
|
|
||||||
{{ end }}
|
|
||||||
</div>
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<th class="align-left">Quantity</th>
|
|
||||||
<th class="align-right">Price</th>
|
|
||||||
<th class="align-left">Price</th>
|
|
||||||
<th class="align-right">Quantity</th>
|
|
||||||
</tr>
|
|
||||||
{{ range .OrderBook }}
|
|
||||||
<tr>
|
|
||||||
<td style="width: auto" class="align-left">{{.BuyQuantity}}</td>
|
|
||||||
<td style="width: 50%">
|
|
||||||
<div class="flex">
|
|
||||||
<span style="width: {{ sub 100 .BuyQuantity}}%"></span>
|
|
||||||
<span style="width: {{.BuyQuantity}}%" class="align-right yes">{{.BuyPrice}}</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td style="width: 50%">
|
|
||||||
<div class="flex">
|
|
||||||
<span style="width: {{.SellQuantity}}%" class="align-left no">{{.SellPrice}}</span>
|
|
||||||
<span style="width: {{ sub 100 .SellQuantity}}%"></span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td style="width: auto" class="align-right">{{.SellQuantity}}</td>
|
|
||||||
</tr>
|
|
||||||
{{ end }}
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
<script src="/order.js"></script>
|
|
||||||
|
|
||||||
</html>
|
|
@ -1,89 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<title>delphi.market</title>
|
|
||||||
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
|
|
||||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
|
|
||||||
<link rel="manifest" href="/site.webmanifest" />
|
|
||||||
<link rel="stylesheet" href="/index.css" />
|
|
||||||
<link rel="stylesheet" href="/market.css" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
<meta name="theme-color" content="#091833" />
|
|
||||||
{{ if eq .ENV "development" }}
|
|
||||||
<script defer src="/hotreload.js"></script>
|
|
||||||
{{ end }}
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<header class="flex flex-row text-center justify-center pt-1">
|
|
||||||
<nav>
|
|
||||||
<a href="/">home</a>
|
|
||||||
{{ if .session }}
|
|
||||||
<form action='/logout' method='post'>
|
|
||||||
<button type='submit'>logout</button>
|
|
||||||
</form>
|
|
||||||
{{ else }} <a href="/login">login</a> {{ end }}
|
|
||||||
</nav>
|
|
||||||
</header>
|
|
||||||
<div class="container flex flex-column text-center">
|
|
||||||
<code>
|
|
||||||
<strong>
|
|
||||||
<pre>
|
|
||||||
_ _
|
|
||||||
_ __ ___ __ _ _ __| | _____| |_
|
|
||||||
| '_ ` _ \ / _` | '__| |/ / _ \ __|
|
|
||||||
| | | | | | (_| | | | < __/ |_
|
|
||||||
|_| |_| |_|\__,_|_| |_|\_\___|\__|</pre>
|
|
||||||
</strong>
|
|
||||||
</code>
|
|
||||||
<div class="font-mono mb-1">{{.Description}}</div>
|
|
||||||
<div class="flex justify-start mb-1">
|
|
||||||
<a class="mx-1" href="/market/{{.Id}}">Orders</a>
|
|
||||||
<a class="mx-1 selected" href="/market/{{.Id}}/trade">Trade</a>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-row justify-center mb-1">
|
|
||||||
{{ range .Shares }} {{ if eq .Description "YES" }}
|
|
||||||
<button id="yes-order" class="order-button sx-1 yes">YES</button>
|
|
||||||
{{ else }}
|
|
||||||
<button id="no-order" class="order-button sx-1 no">NO</button>
|
|
||||||
{{ end }}
|
|
||||||
{{ end }}
|
|
||||||
</div>
|
|
||||||
{{ range .Shares }} {{ if eq .Description "YES" }}
|
|
||||||
<form id="yes-form" class="order-form" hidden action="/api/market/order" method="post">
|
|
||||||
<button id="yes-buy" type="button" class="order-button yes w-100p selected">BUY</button>
|
|
||||||
<button id="yes-sell" type="button" class="order-button no w-100p">SELL</button>
|
|
||||||
<input id="market-id" hidden name="market_id" value="{{$.Id}}" />
|
|
||||||
<input id="yes-share" hidden name="share_id" value="{{.Id}}" />
|
|
||||||
<input id="yes-side" hidden name="side" value="BUY" />
|
|
||||||
<label>shares</label>
|
|
||||||
<input id="yes-quantity" type="number" name="quantity" placeholder="quantity" />
|
|
||||||
<label>price [sats]</label>
|
|
||||||
<input id="yes-cost" type="number" name="cost" disabled />
|
|
||||||
<label id="yes-submit-label">BUY YES shares</label>
|
|
||||||
<button type="submit">SUBMIT</button>
|
|
||||||
</form>
|
|
||||||
{{ else }}
|
|
||||||
<form id="no-form" class="order-form" hidden action="/api/market/order" method="post">
|
|
||||||
<button id="no-buy" type="button" class="order-button yes w-100p selected">BUY</button>
|
|
||||||
<button id="no-sell" type="button" class="order-button no w-100p">SELL</button>
|
|
||||||
<input id="market-id" hidden name="market_id" value="{{$.Id}}" />
|
|
||||||
<input id="no-share" hidden name="share_id" value="{{.Id}}" />
|
|
||||||
<input id="no-side" hidden name="side" value="BUY" />
|
|
||||||
<label>shares</label>
|
|
||||||
<input id="no-quantity" type="number" name="quantity" placeholder="quantity" />
|
|
||||||
<label>price [sats]</label>
|
|
||||||
<input id="no-cost" type="number" name="cost" disabled />
|
|
||||||
<label id="no-submit-label">BUY NO shares</label>
|
|
||||||
<button type="submit">SUBMIT</button>
|
|
||||||
</form>
|
|
||||||
{{ end }}
|
|
||||||
{{ end }}
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
<script src="/trade.js"></script>
|
|
||||||
|
|
||||||
</html>
|
|
118
pages/market.html
Normal file
118
pages/market.html
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>delphi.market</title>
|
||||||
|
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
|
||||||
|
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
|
||||||
|
<link rel="manifest" href="/site.webmanifest" />
|
||||||
|
<link rel="stylesheet" href="/index.css" />
|
||||||
|
<link rel="stylesheet" href="/market.css" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<meta name="theme-color" content="#091833" />
|
||||||
|
{{ if eq .ENV "development" }}
|
||||||
|
<script defer src="/hotreload.js"></script>
|
||||||
|
{{ end }}
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<header class="flex flex-row text-center justify-center pt-1">
|
||||||
|
<nav>
|
||||||
|
<a href="/">home</a>
|
||||||
|
{{ if .session }}
|
||||||
|
<form action='/logout' method='post'>
|
||||||
|
<button type='submit'>logout</button>
|
||||||
|
</form>
|
||||||
|
{{ else }} <a href="/login">login</a> {{ end }}
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
<div class="container flex flex-column text-center">
|
||||||
|
<code>
|
||||||
|
<strong>
|
||||||
|
<pre>
|
||||||
|
_ _
|
||||||
|
_ __ ___ __ _ _ __| | _____| |_
|
||||||
|
| '_ ` _ \ / _` | '__| |/ / _ \ __|
|
||||||
|
| | | | | | (_| | | | < __/ |_
|
||||||
|
|_| |_| |_|\__,_|_| |_|\_\___|\__|</pre>
|
||||||
|
</strong>
|
||||||
|
</code>
|
||||||
|
<div class="font-mono mb-1">{{.Description}}</div>
|
||||||
|
<div class="font-mono mb-1"><strong>Order Book</strong></div>
|
||||||
|
<table class="mb-1">
|
||||||
|
<tr>
|
||||||
|
<th class="align-left">BUY YES</th>
|
||||||
|
<th class="align-center">SELL</th>
|
||||||
|
<th class="align-right">BUY NO</th>
|
||||||
|
</tr>
|
||||||
|
{{ range .Orders }}
|
||||||
|
<tr>
|
||||||
|
{{ if and (eq .ShareId $.YesShare.Id) (eq .Side "BUY") }}
|
||||||
|
<td>
|
||||||
|
<div class="flex yes">
|
||||||
|
<span class="align-left">YES</span>
|
||||||
|
<span style="width: 100%" class="align-right">{{.Quantity}} @ {{.Price}}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
{{ else }}
|
||||||
|
<td></td>
|
||||||
|
{{ end }}
|
||||||
|
{{ if and (eq .ShareId $.YesShare.Id) (eq .Side "SELL") }}
|
||||||
|
<td>
|
||||||
|
<div class="flex no" style="width: 100%">
|
||||||
|
<span class="align-left">YES</span>
|
||||||
|
<span style="width: 100%" class="align-right">{{.Quantity}} @ {{.Price}}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
{{ else }}
|
||||||
|
<td></td>
|
||||||
|
{{ end }}
|
||||||
|
{{ if and (eq .ShareId $.NoShare.Id) (eq .Side "SELL") }}
|
||||||
|
<td>
|
||||||
|
<div class="flex no">
|
||||||
|
<span class="align-left">NO</span>
|
||||||
|
<span style="width: 100%" class="align-right">{{.Quantity}} @ {{.Price}}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
{{ else }}
|
||||||
|
<td></td>
|
||||||
|
{{ end }}
|
||||||
|
{{ if and (eq .ShareId $.NoShare.Id) (eq .Side "BUY") }}
|
||||||
|
<td>
|
||||||
|
<div class="flex yes">
|
||||||
|
<span class="align-left">NO</span>
|
||||||
|
<span style="width: 100%" class="align-right">{{.Quantity}} @ {{.Price}}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
{{ else }}
|
||||||
|
<td></td>
|
||||||
|
{{ end }}
|
||||||
|
</tr>
|
||||||
|
{{ end }}
|
||||||
|
</table>
|
||||||
|
<hr />
|
||||||
|
<div class="font-mono mb-1"><strong>Order Form</strong></div>
|
||||||
|
<form id="form" class="order-form" hidden action="/api/market/{{$.Id}}/order" method="post">
|
||||||
|
<button id="buy" type="button" class="order-button yes w-100p selected">BUY</button>
|
||||||
|
<button id="sell" type="button" class="order-button no w-100p">SELL</button>
|
||||||
|
<input id="market-id" hidden name="market_id" value="{{$.Id}}" />
|
||||||
|
<input id="side" hidden name="side" value="BUY" />
|
||||||
|
<label>share</label>
|
||||||
|
<select name="share_id">
|
||||||
|
<option value="{{.YesShare.Id}}">YES</option>
|
||||||
|
<option value="{{.NoShare.Id}}">NO</option>
|
||||||
|
</select>
|
||||||
|
<label>quantity</label>
|
||||||
|
<input id="quantity" type="number" name="quantity" placeholder="quantity" />
|
||||||
|
<label>price [sats]</label>
|
||||||
|
<input id="price" type="number" name="price" placeholder="price"/>
|
||||||
|
<label id="submit-label"></label>
|
||||||
|
<button type="submit">SUBMIT</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
<script src="/order.js"></script>
|
||||||
|
|
||||||
|
</html>
|
@ -131,6 +131,12 @@ ul {
|
|||||||
.mb-1 {
|
.mb-1 {
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
}
|
}
|
||||||
|
.mb-s {
|
||||||
|
margin-bottom: 0.2em;
|
||||||
|
}
|
||||||
|
.mr-s {
|
||||||
|
margin-right: 0.2em;
|
||||||
|
}
|
||||||
.pt-1 {
|
.pt-1 {
|
||||||
padding-top: 1em;
|
padding-top: 1em;
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.order-form {
|
.order-form {
|
||||||
display: none;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr;
|
||||||
grid-template-rows: 1fr 1fr;
|
grid-template-rows: 1fr 1fr;
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
.align-left {
|
.align-left {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.align-center {
|
.align-center {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.align-right {
|
.align-right {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
@ -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')
|
||||||
|
}
|
131
public/trade.js
131
public/trade.js
@ -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)
|
|
@ -28,7 +28,7 @@ type LnAuthResponse struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Session struct {
|
type Session struct {
|
||||||
pubkey string
|
Pubkey string
|
||||||
}
|
}
|
||||||
|
|
||||||
func lnAuth() (*LnAuth, error) {
|
func lnAuth() (*LnAuth, error) {
|
||||||
|
43
src/db.go
43
src/db.go
@ -68,50 +68,19 @@ func (db *DB) FetchShares(marketId int, shares *[]Share) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *DB) FetchOrderBook(shareId string, orderBook *[]OrderBookEntry) error {
|
func (db *DB) FetchOrders(marketId int, orders *[]Order) error {
|
||||||
rows, err := db.Query(""+
|
rows, err := db.Query(""+
|
||||||
"SELECT share_id, side, price, SUM(quantity)"+
|
"SELECT id, share_id, pubkey, side, quantity, price, order_id FROM orders "+
|
||||||
"FROM orders WHERE share_id = $1"+
|
"WHERE share_id = ANY(SELECT id FROM shares WHERE market_id = $1) "+
|
||||||
"GROUP BY (share_id, side, price)"+
|
"ORDER BY price DESC", marketId)
|
||||||
"ORDER BY share_id DESC, side DESC, price DESC", shareId)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
buyOrders := []Order{}
|
|
||||||
sellOrders := []Order{}
|
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var order Order
|
var order Order
|
||||||
rows.Scan(&order.ShareId, &order.Side, &order.Price, &order.Quantity)
|
rows.Scan(&order.Id, &order.ShareId, &order.Pubkey, &order.Side, &order.Quantity, &order.Price, &order.OrderId)
|
||||||
if order.Side == "BUY" {
|
*orders = append(*orders, order)
|
||||||
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,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"log"
|
||||||
"math"
|
"math"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -23,23 +23,13 @@ type Share struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Order struct {
|
type Order struct {
|
||||||
|
Session
|
||||||
|
Id string
|
||||||
ShareId string
|
ShareId string
|
||||||
Side string
|
Side string
|
||||||
Price int
|
Price int
|
||||||
Quantity int
|
Quantity int
|
||||||
}
|
OrderId string
|
||||||
|
|
||||||
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"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func costFunction(b float64, q1 float64, q2 float64) float64 {
|
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)
|
return costFunction(b, fq1+fdq1, fq2) - costFunction(b, fq1, fq2)
|
||||||
}
|
}
|
||||||
|
|
||||||
func trades(c echo.Context) error {
|
func order(c echo.Context) error {
|
||||||
marketId, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
// TODO: implement POST /market/:id/order
|
||||||
if err != nil {
|
return echo.NewHTTPError(http.StatusMethodNotAllowed, "Method Not Allowed")
|
||||||
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 orders(c echo.Context) error {
|
func market(c echo.Context) error {
|
||||||
marketId, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
marketId, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return echo.NewHTTPError(http.StatusBadRequest, "Bad Request")
|
return echo.NewHTTPError(http.StatusBadRequest, "Bad Request")
|
||||||
}
|
}
|
||||||
shareId := c.Param("sid")
|
|
||||||
|
|
||||||
var market Market
|
var market Market
|
||||||
if err = db.FetchMarket(int(marketId), &market); err == sql.ErrNoRows {
|
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 {
|
if err = db.FetchShares(market.Id, &shares); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if shareId == "" {
|
var orders []Order
|
||||||
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("/market/%d/%s", market.Id, shares[0].Id))
|
if err = db.FetchOrders(market.Id, &orders); err != nil {
|
||||||
}
|
|
||||||
var orderBook []OrderBookEntry
|
|
||||||
if err = db.FetchOrderBook(shareId, &orderBook); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
data := map[string]any{
|
data := map[string]any{
|
||||||
@ -112,9 +78,11 @@ func orders(c echo.Context) error {
|
|||||||
"ENV": ENV,
|
"ENV": ENV,
|
||||||
"Id": market.Id,
|
"Id": market.Id,
|
||||||
"Description": market.Description,
|
"Description": market.Description,
|
||||||
"ShareId": shareId,
|
// shares are sorted by description in descending order
|
||||||
"Shares": shares,
|
// that's how we know that YES must be the first share
|
||||||
"OrderBook": orderBook,
|
"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)
|
||||||
}
|
}
|
||||||
|
@ -63,9 +63,8 @@ func main() {
|
|||||||
e.GET("/api/login", verifyLogin)
|
e.GET("/api/login", verifyLogin)
|
||||||
e.GET("/api/session", checkSession)
|
e.GET("/api/session", checkSession)
|
||||||
e.POST("/logout", logout)
|
e.POST("/logout", logout)
|
||||||
e.GET("/market/:id", sessionGuard(orders))
|
e.GET("/market/:id", sessionGuard(market))
|
||||||
e.GET("/market/:id/:sid", sessionGuard(orders))
|
e.POST("/market/:id/order", sessionGuard(order))
|
||||||
e.GET("/market/:id/trade", sessionGuard(trades))
|
|
||||||
e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
|
e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
|
||||||
Format: "${time_custom} ${method} ${uri} ${status}\n",
|
Format: "${time_custom} ${method} ${uri} ${status}\n",
|
||||||
CustomTimeFormat: "2006-01-02 15:04:05.00000-0700",
|
CustomTimeFormat: "2006-01-02 15:04:05.00000-0700",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user