Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

multi: market overview and price feed #1232

Merged
merged 5 commits into from
Oct 19, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions client/core/bookie.go
Original file line number Diff line number Diff line change
Expand Up @@ -876,6 +876,46 @@ func (dc *dexConnection) refreshServerConfig() error {
return nil
}

// subPriceFeed subscribes to the price_feed notification feed and primes the
// initial prices.
func (dc *dexConnection) subPriceFeed() {
var spots map[string]*msgjson.Spot
err := sendRequest(dc.WsConn, msgjson.PriceFeedRoute, nil, &spots, DefaultResponseTimeout)
if err != nil {
var msgErr *msgjson.Error
// Ignore old servers' errors.
if !errors.As(err, &msgErr) || msgErr.Code != msgjson.UnknownMessageType {
dc.log.Errorf("unable to fetch market overview: %w", err)
}
return
}

dc.spotsMtx.Lock()
dc.spots = spots
buck54321 marked this conversation as resolved.
Show resolved Hide resolved
dc.spotsMtx.Unlock()

dc.notify(newSpotPriceNote(dc.acct.host, spots))
}

// handlePriceUpdateNote handles the price_update note that is part of the
// price feed.
func handlePriceUpdateNote(_ *Core, dc *dexConnection, msg *msgjson.Message) error {
spot := new(msgjson.Spot)
if err := msg.Unmarshal(spot); err != nil {
return fmt.Errorf("error unmarshaling price update: %v", err)
}
mktName, err := dex.MarketName(spot.BaseID, spot.QuoteID)
if err != nil {
return fmt.Errorf("error parsing market for base = %d, quote = %d: %v", spot.BaseID, spot.QuoteID, err)
}
dc.spotsMtx.Lock()
dc.spots[mktName] = spot
dc.spotsMtx.Unlock()

dc.notify(newSpotPriceNote(dc.acct.host, map[string]*msgjson.Spot{mktName: spot}))
return nil
}

// handleUnbookOrderMsg is called when an unbook_order notification is
// received.
func handleUnbookOrderMsg(_ *Core, dc *dexConnection, msg *msgjson.Message) error {
Expand Down
16 changes: 16 additions & 0 deletions client/core/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,9 @@ type dexConnection struct {
pendingFee *pendingFeeState

reportingConnects uint32

spotsMtx sync.RWMutex
spots map[string]*msgjson.Spot
}

// DefaultResponseTimeout is the default timeout for responses after a request is
Expand Down Expand Up @@ -209,6 +212,13 @@ func (dc *dexConnection) marketMap() map[string]*Market {
marketMap[mktID] = mkt
}

// Populate spots.
dc.spotsMtx.RLock()
for mktID, mkt := range marketMap {
mkt.SpotPrice = dc.spots[mktID]
}
dc.spotsMtx.RUnlock()

return marketMap
}

Expand Down Expand Up @@ -4938,6 +4948,7 @@ func (c *Core) connectDEX(acctInfo *db.AccountInfo, temporary ...bool) (*dexConn
trades: make(map[order.OrderID]*trackedTrade),
apiVer: -1,
reportingConnects: reporting,
spots: make(map[string]*msgjson.Spot),
// On connect, must set: cfg, epoch, and assets.
}

Expand Down Expand Up @@ -4998,6 +5009,8 @@ func (c *Core) connectDEX(acctInfo *db.AccountInfo, temporary ...bool) (*dexConn
}
// handleConnectEvent sets dc.connected, even on first connect

go dc.subPriceFeed()

if listen {
c.log.Infof("Connected to DEX server at %s and listening for messages.", host)
} else {
Expand Down Expand Up @@ -5029,6 +5042,8 @@ func (c *Core) handleReconnect(host string) {
return
}

go dc.subPriceFeed()

if !dc.acct.locked() && dc.acct.feePaid() {
err = c.authDEX(dc)
if err != nil {
Expand Down Expand Up @@ -5275,6 +5290,7 @@ var noteHandlers = map[string]routeHandler{
msgjson.BookOrderRoute: handleBookOrderMsg,
msgjson.EpochOrderRoute: handleEpochOrderMsg,
msgjson.UnbookOrderRoute: handleUnbookOrderMsg,
msgjson.PriceUpdateRoute: handlePriceUpdateNote,
msgjson.UpdateRemainingRoute: handleUpdateRemainingMsg,
msgjson.EpochReportRoute: handleEpochReportMsg,
msgjson.SuspensionRoute: handleTradeSuspensionMsg,
Expand Down
1 change: 1 addition & 0 deletions client/core/core_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ func testDexConnection(ctx context.Context, crypter *tCrypter) (*dexConnection,
apiVer: serverdex.PreAPIVersion,
connected: 1,
reportingConnects: 1,
spots: make(map[string]*msgjson.Spot),
}, conn, acct
}

Expand Down
19 changes: 19 additions & 0 deletions client/core/notification.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"decred.org/dcrdex/client/db"
"decred.org/dcrdex/dex"
"decred.org/dcrdex/dex/msgjson"
)

// Notifications should use the following note type strings.
Expand All @@ -19,6 +20,7 @@ const (
NoteTypeEpoch = "epoch"
NoteTypeConnEvent = "conn"
NoteTypeBalance = "balance"
NoteTypeSpots = "spots"
NoteTypeWalletConfig = "walletconfig"
NoteTypeWalletState = "walletstate"
NoteTypeServerNotify = "notify"
Expand Down Expand Up @@ -354,6 +356,23 @@ func newBalanceNote(assetID uint32, bal *WalletBalance) *BalanceNote {
}
}

// SpotPriceNote is a notification of an update to the market's spot price.
type SpotPriceNote struct {
db.Notification
Host string `json:"host"`
Spots map[string]*msgjson.Spot `json:"spots"`
}

const TopicSpotsUpdate = "SpotsUpdate"
buck54321 marked this conversation as resolved.
Show resolved Hide resolved

func newSpotPriceNote(host string, spots map[string]*msgjson.Spot) *SpotPriceNote {
return &SpotPriceNote{
Notification: db.NewNotification(NoteTypeSpots, TopicSpotsUpdate, "", "", db.Data),
Host: host,
Spots: spots,
}
}

// DEXAuthNote is a notification regarding individual DEX authentication status.
type DEXAuthNote struct {
db.Notification
Expand Down
23 changes: 12 additions & 11 deletions client/core/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -372,17 +372,18 @@ func coreOrderFromTrade(ord order.Order, metaData *db.OrderMetaData) *Order {

// Market is market info.
type Market struct {
Name string `json:"name"`
BaseID uint32 `json:"baseid"`
BaseSymbol string `json:"basesymbol"`
QuoteID uint32 `json:"quoteid"`
QuoteSymbol string `json:"quotesymbol"`
LotSize uint64 `json:"lotsize"`
RateStep uint64 `json:"ratestep"`
EpochLen uint64 `json:"epochlen"`
StartEpoch uint64 `json:"startepoch"`
MarketBuyBuffer float64 `json:"buybuffer"`
Orders []*Order `json:"orders"`
Name string `json:"name"`
BaseID uint32 `json:"baseid"`
BaseSymbol string `json:"basesymbol"`
QuoteID uint32 `json:"quoteid"`
QuoteSymbol string `json:"quotesymbol"`
LotSize uint64 `json:"lotsize"`
RateStep uint64 `json:"ratestep"`
EpochLen uint64 `json:"epochlen"`
StartEpoch uint64 `json:"startepoch"`
MarketBuyBuffer float64 `json:"buybuffer"`
Orders []*Order `json:"orders"`
SpotPrice *msgjson.Spot `json:"spot"`
}

// BaseContractLocked is the amount of base asset locked in un-redeemed
Expand Down
35 changes: 35 additions & 0 deletions client/webserver/live_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,9 @@ func mkMrkt(base, quote string) *core.Market {
marketStats[mktID] = [2]float64{midGap, maxQty}
}

rate := uint64(rand.Intn(1e3)) * rateStep
change24 := rand.Float64()*0.3 - .15

return &core.Market{
Name: fmt.Sprintf("%s_%s", base, quote),
BaseID: baseID,
Expand All @@ -164,6 +167,15 @@ func mkMrkt(base, quote string) *core.Market {
MarketBuyBuffer: rand.Float64() + 1,
EpochLen: uint64(epochDuration.Milliseconds()),
Orders: userOrders(mktID),
SpotPrice: &msgjson.Spot{
Stamp: encode.UnixMilliU(time.Now()),
BaseID: baseID,
QuoteID: quoteID,
Rate: rate,
// BookVolume: ,
Change24: change24,
// Vol24: ,
},
}
}

Expand Down Expand Up @@ -1214,13 +1226,36 @@ out:
quoteConnected = true
}
c.mtx.RUnlock()

if c.dexAddr == "" {
continue
}

c.noteFeed <- &core.EpochNotification{
Host: dexAddr,
MarketID: mktID,
Notification: db.NewNotification(core.NoteTypeEpoch, core.TopicEpoch, "", "", db.Data),
Epoch: getEpoch(),
}

rateStep := tExchanges[dexAddr].Markets[mktID].RateStep
rate := uint64(rand.Intn(1e3)) * rateStep
change24 := rand.Float64()*0.3 - .15

c.noteFeed <- &core.SpotPriceNote{
Host: dexAddr,
Notification: db.NewNotification(core.NoteTypeSpots, core.TopicSpotsUpdate, "", "", db.Data),
Spots: map[string]*msgjson.Spot{mktID: &msgjson.Spot{
Stamp: encode.UnixMilliU(time.Now()),
BaseID: baseID,
QuoteID: quoteID,
Rate: rate,
// BookVolume: ,
Change24: change24,
// Vol24: ,
}},
}

// randomize the balance
if baseID != 141 && baseConnected { // komodo unsupported
c.noteFeed <- randomBalanceNote(baseID)
Expand Down
1 change: 0 additions & 1 deletion client/webserver/site/src/css/main.scss
Original file line number Diff line number Diff line change
Expand Up @@ -427,7 +427,6 @@ div.form-closer {

.micro-icon {
position: relative;
bottom: 2px;
}

#tooltip {
Expand Down
60 changes: 50 additions & 10 deletions client/webserver/site/src/css/market.scss
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
div.marketlist {
width: 175px;
// width: 175px;
border-right: 1px solid #626262;
z-index: 99;
background-color: #dbdbdb;
min-width: 175px;

.selected {
border-style: solid none solid none;
Expand All @@ -13,31 +14,70 @@ div.marketlist {
.header {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 100%;
}

.xc:not(:first-child) .header {
padding-top: 15px;
}

.grey {
color: #555;
}
}

div.marketrow {
font-size: 17px;
padding: 0.3em;
font-family: 'demi-sans', sans-serif;
font-size: 16px;
padding: 3px 4px;
cursor: pointer;
}
white-space: nowrap;
display: flex;
flex-direction: column;
align-items: stretch;
margin: 3px;
background-color: #34383e;
border-radius: 3px;
// border: 1px solid #7775;

div.marketrow:hover,
div.marketrow.selected {
background-color: #f0f7f7;
& > div {
display: flex;
align-items: center;
flex-direction: row;
line-height: 1;
padding: 2px 0;
}

&:hover,
&.selected {
padding: 2px 3px;
border: 1px solid #7a97;
}

span.upgreen {
color: #35b97c;
}

span.downred {
color: #c33b3b;
}

span.pct-change {
min-width: 50px;
text-align: right;
}
}

label.market-search {
display: flex;
justify-content: space-between;
align-items: center;
align-items: stretch;
margin: 1px;
background-color: white;

input {
border: none;
width: 150px;
max-width: 75px;
}

span {
Expand Down
13 changes: 4 additions & 9 deletions client/webserver/site/src/css/market_dark.scss
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,12 @@ body.dark {
}

div.marketlist {
background-color: #232428;
background-color: #1c1c1f;
border-right-color: black;
}

div.marketrow:hover,
div.marketrow.selected {
background-color: #1d2936;
.grey {
color: #999;
}
}

label.market-search {
Expand Down Expand Up @@ -66,10 +65,6 @@ body.dark {
border-radius: 3px;
}

.selected {
border-style: none;
}

.brdrleft {
border-left: 1px solid black;
}
Expand Down
Loading