Compare commits
No commits in common. "b7a24b48fd87f1eeeecaa5d618fd6d28d86789e7" and "cb5132f2c15d94ff35eb967b677be2278500db05" have entirely different histories.
b7a24b48fd
...
cb5132f2c1
@ -108,14 +108,6 @@
|
|||||||
color: var(--color);
|
color: var(--color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-success {
|
|
||||||
color: var(--fg-success);
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-error {
|
|
||||||
color: var(--fg-error);
|
|
||||||
}
|
|
||||||
|
|
||||||
.hitbox {
|
.hitbox {
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
margin: -15px;
|
margin: -15px;
|
||||||
@ -132,8 +124,7 @@
|
|||||||
color: var(--fg-success);
|
color: var(--fg-success);
|
||||||
}
|
}
|
||||||
|
|
||||||
.neon.success:hover,
|
.neon.success:hover, .neon.success.active {
|
||||||
.neon.success.active {
|
|
||||||
background-color: var(--fg-success);
|
background-color: var(--fg-success);
|
||||||
color: var(--white);
|
color: var(--white);
|
||||||
}
|
}
|
||||||
@ -143,8 +134,7 @@
|
|||||||
color: var(--fg-error);
|
color: var(--fg-error);
|
||||||
}
|
}
|
||||||
|
|
||||||
.neon.error:hover,
|
.neon.error:hover, .neon.error.active {
|
||||||
.neon.error.active {
|
|
||||||
background-color: var(--fg-error);
|
background-color: var(--fg-error);
|
||||||
color: var(--white);
|
color: var(--white);
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@ package handler
|
|||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
|
||||||
"git.ekzyis.com/ekzyis/delphi.market/lib/lmsr"
|
|
||||||
"git.ekzyis.com/ekzyis/delphi.market/server/router/context"
|
"git.ekzyis.com/ekzyis/delphi.market/server/router/context"
|
||||||
"git.ekzyis.com/ekzyis/delphi.market/server/router/pages"
|
"git.ekzyis.com/ekzyis/delphi.market/server/router/pages"
|
||||||
"git.ekzyis.com/ekzyis/delphi.market/types"
|
"git.ekzyis.com/ekzyis/delphi.market/types"
|
||||||
@ -16,29 +15,16 @@ func HandleIndex(sc context.Context) echo.HandlerFunc {
|
|||||||
db = sc.Db
|
db = sc.Db
|
||||||
ctx = c.Request().Context()
|
ctx = c.Request().Context()
|
||||||
rows *sql.Rows
|
rows *sql.Rows
|
||||||
markets []types.Market
|
|
||||||
err error
|
err error
|
||||||
|
markets []types.Market
|
||||||
)
|
)
|
||||||
|
|
||||||
if rows, err = db.QueryContext(ctx, ""+
|
if rows, err = db.QueryContext(ctx, ""+
|
||||||
"WITH lmsr AS ("+
|
|
||||||
" SELECT "+
|
|
||||||
" o.market_id, "+
|
|
||||||
" COALESCE(SUM(o.quantity) FILTER(WHERE o.outcome = 0), 0) AS q1, "+
|
|
||||||
" COALESCE(SUM(o.quantity) FILTER(WHERE o.outcome = 1), 0) AS q2, "+
|
|
||||||
" COALESCE(SUM(i.msats_received), 0) / 1000 AS volume "+
|
|
||||||
" FROM orders o "+
|
|
||||||
" JOIN invoices i ON o.invoice_id = i.id "+
|
|
||||||
" WHERE i.confirmed_at IS NOT NULL "+
|
|
||||||
" GROUP BY o.market_id"+
|
|
||||||
")"+
|
|
||||||
"SELECT m.id, m.question, m.description, m.created_at, m.end_date, "+
|
"SELECT m.id, m.question, m.description, m.created_at, m.end_date, "+
|
||||||
"m.lmsr_b, l.q1, l.q2, l.volume, "+
|
|
||||||
"u.id, u.name, u.created_at, u.ln_pubkey, u.nostr_pubkey, u.msats "+
|
"u.id, u.name, u.created_at, u.ln_pubkey, u.nostr_pubkey, u.msats "+
|
||||||
"FROM markets m "+
|
"FROM markets m "+
|
||||||
"JOIN users u ON m.user_id = u.id "+
|
"JOIN users u ON m.user_id = u.id "+
|
||||||
"JOIN invoices i ON m.invoice_id = i.id "+
|
"JOIN invoices i ON m.invoice_id = i.id "+
|
||||||
"JOIN lmsr l ON m.id = l.market_id "+
|
|
||||||
"WHERE i.confirmed_at IS NOT NULL"); err != nil {
|
"WHERE i.confirmed_at IS NOT NULL"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -46,16 +32,12 @@ func HandleIndex(sc context.Context) echo.HandlerFunc {
|
|||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var m types.Market
|
var m types.Market
|
||||||
var u types.User
|
var u types.User
|
||||||
var l types.LMSR
|
|
||||||
if err = rows.Scan(
|
if err = rows.Scan(
|
||||||
&m.Id, &m.Question, &m.Description, &m.CreatedAt, &m.EndDate,
|
&m.Id, &m.Question, &m.Description, &m.CreatedAt, &m.EndDate,
|
||||||
&l.B, &l.Q1, &l.Q2, &m.Volume,
|
|
||||||
&u.Id, &u.Name, &u.CreatedAt, &u.LnPubkey, &u.NostrPubkey, &u.Msats); err != nil {
|
&u.Id, &u.Name, &u.CreatedAt, &u.LnPubkey, &u.NostrPubkey, &u.Msats); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
m.User = u
|
m.User = u
|
||||||
m.Pyes = lmsr.Quote(l.B, l.Q2, l.Q1, 1)
|
|
||||||
|
|
||||||
markets = append(markets, m)
|
markets = append(markets, m)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,51 +101,27 @@ func HandleMarket(sc context.Context) echo.HandlerFunc {
|
|||||||
var (
|
var (
|
||||||
db = sc.Db
|
db = sc.Db
|
||||||
ctx = c.Request().Context()
|
ctx = c.Request().Context()
|
||||||
|
|
||||||
// session user
|
|
||||||
u = types.User{}
|
u = types.User{}
|
||||||
|
|
||||||
// market id
|
|
||||||
id = c.Param("id")
|
id = c.Param("id")
|
||||||
|
|
||||||
// quantity of shares user entered into form
|
|
||||||
quantity = c.QueryParam("q")
|
quantity = c.QueryParam("q")
|
||||||
|
|
||||||
// quantity as number
|
|
||||||
q int64
|
q int64
|
||||||
|
|
||||||
// current market
|
|
||||||
m = types.Market{}
|
m = types.Market{}
|
||||||
|
|
||||||
// market founder
|
|
||||||
mU = types.User{}
|
mU = types.User{}
|
||||||
|
|
||||||
// market LMSR data
|
|
||||||
l = types.LMSR{}
|
l = types.LMSR{}
|
||||||
|
|
||||||
// total price for current quantity of shares in sats
|
|
||||||
total float64
|
total float64
|
||||||
|
quote0 = types.MarketQuote{}
|
||||||
// market quotes
|
quote1 = types.MarketQuote{}
|
||||||
quoteNo = types.MarketQuote{}
|
uQ0 int
|
||||||
quoteYes = types.MarketQuote{}
|
uQ1 int
|
||||||
|
|
||||||
// how many shares the user already holds
|
|
||||||
uQuantityNo int
|
|
||||||
uQuantityYes int
|
|
||||||
rows *sql.Rows
|
rows *sql.Rows
|
||||||
|
p0 []types.MarketPoint
|
||||||
// chart data
|
p1 []types.MarketPoint
|
||||||
lineNo []types.Point
|
|
||||||
lineYes []types.Point
|
|
||||||
|
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
|
|
||||||
if c.Get("session") != nil {
|
if c.Get("session") != nil {
|
||||||
u = c.Get("session").(types.User)
|
u = c.Get("session").(types.User)
|
||||||
} else {
|
} else {
|
||||||
// unauthenticated user
|
|
||||||
u.Id = -1
|
u.Id = -1
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -173,7 +149,6 @@ func HandleMarket(sc context.Context) echo.HandlerFunc {
|
|||||||
|
|
||||||
if err = db.QueryRowContext(ctx, ""+
|
if err = db.QueryRowContext(ctx, ""+
|
||||||
"SELECT "+
|
"SELECT "+
|
||||||
"COALESCE(SUM(i.msats_received), 0) / 1000 AS volume, "+
|
|
||||||
"COALESCE(SUM(o.quantity) FILTER(WHERE o.outcome = 0), 0) AS q1, "+
|
"COALESCE(SUM(o.quantity) FILTER(WHERE o.outcome = 0), 0) AS q1, "+
|
||||||
"COALESCE(SUM(o.quantity) FILTER(WHERE o.outcome = 1), 0) AS q2, "+
|
"COALESCE(SUM(o.quantity) FILTER(WHERE o.outcome = 1), 0) AS q2, "+
|
||||||
"COALESCE(SUM(o.quantity) FILTER(WHERE o.outcome = 0 AND o.user_id = $2), 0) AS uq1, "+
|
"COALESCE(SUM(o.quantity) FILTER(WHERE o.outcome = 0 AND o.user_id = $2), 0) AS uq1, "+
|
||||||
@ -192,16 +167,14 @@ func HandleMarket(sc context.Context) echo.HandlerFunc {
|
|||||||
// but this isn't sybil resistant.
|
// but this isn't sybil resistant.
|
||||||
//
|
//
|
||||||
// For now, we will ignore pending orders.
|
// For now, we will ignore pending orders.
|
||||||
"WHERE o.market_id = $1 AND i.confirmed_at IS NOT NULL", id, u.Id).
|
"WHERE o.market_id = $1 AND i.confirmed_at IS NOT NULL", id, u.Id).Scan(
|
||||||
Scan(&m.Volume, &l.Q1, &l.Q2, &uQuantityNo, &uQuantityYes); err != nil {
|
&l.Q1, &l.Q2, &uQ0, &uQ1); err != nil {
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
return echo.NewHTTPError(http.StatusNotFound)
|
return echo.NewHTTPError(http.StatusNotFound)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
m.Pyes = lmsr.Quote(l.B, l.Q2, l.Q1, 1)
|
|
||||||
|
|
||||||
if rows, err = db.QueryContext(ctx, ""+
|
if rows, err = db.QueryContext(ctx, ""+
|
||||||
"SELECT created_at, quote(b, q0, q1, 1) AS p0, quote(b, q1, q0, 1) AS p1 "+
|
"SELECT created_at, quote(b, q0, q1, 1) AS p0, quote(b, q1, q0, 1) AS p1 "+
|
||||||
"FROM ( "+
|
"FROM ( "+
|
||||||
@ -230,12 +203,12 @@ func HandleMarket(sc context.Context) echo.HandlerFunc {
|
|||||||
if err = rows.Scan(&createdAt, &_p0, &_p1); err != nil {
|
if err = rows.Scan(&createdAt, &_p0, &_p1); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
lineNo = append(lineNo, types.Point{X: createdAt, Y: _p0})
|
p0 = append(p0, types.MarketPoint{X: createdAt, Y: _p0})
|
||||||
lineYes = append(lineYes, types.Point{X: createdAt, Y: _p1})
|
p1 = append(p1, types.MarketPoint{X: createdAt, Y: _p1})
|
||||||
}
|
}
|
||||||
|
|
||||||
total = lmsr.Quote(l.B, l.Q1, l.Q2, int(q))
|
total = lmsr.Quote(l.B, l.Q1, l.Q2, int(q))
|
||||||
quoteNo = types.MarketQuote{
|
quote0 = types.MarketQuote{
|
||||||
Outcome: 0,
|
Outcome: 0,
|
||||||
AvgPrice: total / float64(q),
|
AvgPrice: total / float64(q),
|
||||||
TotalPrice: total,
|
TotalPrice: total,
|
||||||
@ -243,7 +216,7 @@ func HandleMarket(sc context.Context) echo.HandlerFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
total = lmsr.Quote(l.B, l.Q2, l.Q1, int(q))
|
total = lmsr.Quote(l.B, l.Q2, l.Q1, int(q))
|
||||||
quoteYes = types.MarketQuote{
|
quote1 = types.MarketQuote{
|
||||||
Outcome: 1,
|
Outcome: 1,
|
||||||
AvgPrice: total / float64(q),
|
AvgPrice: total / float64(q),
|
||||||
TotalPrice: total,
|
TotalPrice: total,
|
||||||
@ -252,9 +225,9 @@ func HandleMarket(sc context.Context) echo.HandlerFunc {
|
|||||||
|
|
||||||
return pages.Market(
|
return pages.Market(
|
||||||
m,
|
m,
|
||||||
lineNo, lineYes,
|
p0, p1,
|
||||||
quoteNo, quoteYes,
|
quote0, quote1,
|
||||||
uQuantityNo, uQuantityYes).Render(context.RenderContext(sc, c), c.Response().Writer)
|
uQ0, uQ1).Render(context.RenderContext(sc, c), c.Response().Writer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -266,48 +239,24 @@ func HandleOrder(sc context.Context) echo.HandlerFunc {
|
|||||||
tx *sql.Tx
|
tx *sql.Tx
|
||||||
ctx = c.Request().Context()
|
ctx = c.Request().Context()
|
||||||
u = c.Get("session").(types.User)
|
u = c.Get("session").(types.User)
|
||||||
|
|
||||||
// market id
|
|
||||||
id = c.Param("id")
|
id = c.Param("id")
|
||||||
|
|
||||||
// how many shares user wants to buy
|
|
||||||
quantity = c.FormValue("q")
|
quantity = c.FormValue("q")
|
||||||
// quantity as number
|
|
||||||
q int64
|
|
||||||
|
|
||||||
// on which outcome user wants to bet
|
|
||||||
outcome = c.FormValue("o")
|
outcome = c.FormValue("o")
|
||||||
// outcome as id
|
q int64
|
||||||
o int64
|
o int64
|
||||||
|
|
||||||
// selected market
|
|
||||||
m = types.Market{}
|
m = types.Market{}
|
||||||
|
|
||||||
// market founder
|
|
||||||
mU = types.User{}
|
mU = types.User{}
|
||||||
|
|
||||||
// market LMSR data
|
|
||||||
l = types.LMSR{}
|
l = types.LMSR{}
|
||||||
|
|
||||||
// total price as returned by LMSR for given quantity
|
|
||||||
totalF float64
|
totalF float64
|
||||||
// total rounded to msats
|
|
||||||
total int
|
total int
|
||||||
|
|
||||||
// invoice data
|
|
||||||
hash lntypes.Hash
|
hash lntypes.Hash
|
||||||
paymentRequest string
|
paymentRequest string
|
||||||
expiry = int64(60)
|
expiry = int64(60)
|
||||||
expiresAt = time.Now().Add(time.Second * time.Duration(expiry))
|
expiresAt = time.Now().Add(time.Second * time.Duration(expiry))
|
||||||
invoiceId int
|
invoiceId int
|
||||||
invDescription string
|
invDescription string
|
||||||
|
|
||||||
// id of created order
|
|
||||||
orderId int
|
orderId int
|
||||||
|
|
||||||
// QR component during render
|
|
||||||
qr templ.Component
|
qr templ.Component
|
||||||
|
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -36,8 +36,8 @@ templ Index(markets []types.Market) {
|
|||||||
<a href={ templ.SafeURL(fmt.Sprintf("/market/%d", m.Id)) }>{ m.Question }</a>
|
<a href={ templ.SafeURL(fmt.Sprintf("/market/%d", m.Id)) }>{ m.Question }</a>
|
||||||
<div class="text-small text-muted">{ m.User.Name } / { humanize.Time(m.CreatedAt) } / { humanize.Time(m.EndDate) }</div>
|
<div class="text-small text-muted">{ m.User.Name } / { humanize.Time(m.CreatedAt) } / { humanize.Time(m.EndDate) }</div>
|
||||||
</span>
|
</span>
|
||||||
<span class={ fmt.Sprintf("%s %s", "px-3 border-b border-muted pb-3 mt-3 flex", colorize(m.Pyes)) }><div class="self-center">{ fmt.Sprintf("%.2f%%", m.Pyes * 100) }</div></span>
|
<span class="px-3 border-b border-muted pb-3 mt-3 flex"><div class="self-center">51%</div></span>
|
||||||
<span class="pe-3 border-b border-muted pb-3 mt-3 flex"><div class="self-center">{ fmt.Sprintf("%s", humanizeRound(m.Volume)) }</div></span>
|
<span class="pe-3 border-b border-muted pb-3 mt-3 flex"><div class="self-center">0</div></span>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
} else {
|
} else {
|
||||||
@ -92,25 +92,6 @@ func minDate() string {
|
|||||||
return time.Now().Add(24 * time.Hour).Format("2006-01-02")
|
return time.Now().Add(24 * time.Hour).Format("2006-01-02")
|
||||||
}
|
}
|
||||||
|
|
||||||
func humanizeRound(f float64) string {
|
|
||||||
if f > 1_000_000 {
|
|
||||||
return fmt.Sprintf("%.2fm", f/1_000_000)
|
|
||||||
} else if f > 1_000 {
|
|
||||||
return fmt.Sprintf("%.2fk", f/1_000)
|
|
||||||
} else {
|
|
||||||
return fmt.Sprintf("%.2f", f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func colorize(f float64) string {
|
|
||||||
if f > 0.5 {
|
|
||||||
return "text-success"
|
|
||||||
} else {
|
|
||||||
return "text-error"
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func tabStyle(path string, tab string) string {
|
func tabStyle(path string, tab string) string {
|
||||||
class := "!no-underline"
|
class := "!no-underline"
|
||||||
if path == tab {
|
if path == tab {
|
||||||
|
@ -7,7 +7,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// TODO: Add countdown? Use or at least show somewhere precise timestamps?
|
// TODO: Add countdown? Use or at least show somewhere precise timestamps?
|
||||||
templ Market(m types.Market, p0 []types.Point, p1 []types.Point, quoteNo types.MarketQuote, quoteYes types.MarketQuote, uQuantityNo int, uQuantityYes int) {
|
templ Market(m types.Market, p0 []types.MarketPoint, p1 []types.MarketPoint, q0 types.MarketQuote, q1 types.MarketQuote, uQ0 int, uQ1 int) {
|
||||||
<html>
|
<html>
|
||||||
@components.Head()
|
@components.Head()
|
||||||
<body
|
<body
|
||||||
@ -54,10 +54,10 @@ templ Market(m types.Market, p0 []types.Point, p1 []types.Point, quoteNo types.M
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="mx-auto my-5" x-show="outcome === 1">
|
<div class="mx-auto my-5" x-show="outcome === 1">
|
||||||
@components.MarketForm(m, 1, quoteYes, uQuantityYes)
|
@components.MarketForm(m, 1, q1, uQ1)
|
||||||
</div>
|
</div>
|
||||||
<div class="mx-auto my-5" x-show="outcome === 0">
|
<div class="mx-auto my-5" x-show="outcome === 0">
|
||||||
@components.MarketForm(m, 0, quoteNo, uQuantityNo)
|
@components.MarketForm(m, 0, q0, uQ0)
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -36,9 +36,6 @@ type Market struct {
|
|||||||
Description string
|
Description string
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
EndDate time.Time
|
EndDate time.Time
|
||||||
Pyes float64
|
|
||||||
// market volume in sats
|
|
||||||
Volume float64
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type LMSR struct {
|
type LMSR struct {
|
||||||
@ -54,7 +51,7 @@ type MarketQuote struct {
|
|||||||
Reward float64
|
Reward float64
|
||||||
}
|
}
|
||||||
|
|
||||||
type Point struct {
|
type MarketPoint struct {
|
||||||
X time.Time
|
X time.Time
|
||||||
Y float64
|
Y float64
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user