Skip to content

Commit

Permalink
multi: encode recent matches to preserve order
Browse files Browse the repository at this point in the history
This encoding preserves order. Matches are only binned if they
occur sequentially.
  • Loading branch information
buck54321 committed Sep 28, 2022
1 parent a0ee939 commit cc2830a
Show file tree
Hide file tree
Showing 10 changed files with 92 additions and 98 deletions.
16 changes: 8 additions & 8 deletions client/core/bookie.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,13 +192,12 @@ func (b *bookie) logEpochReport(note *msgjson.EpochReportNote) error {
}

marketID := marketName(b.base, b.quote)
b.SetMatchesSummary(note.MatchesSummarySell, note.EndStamp, true)
b.SetMatchesSummary(note.MatchesSummaryBuy, note.EndStamp, false)
if note.MatchesSummaryBuy != nil || note.MatchesSummarySell != nil {
matchSummaries := b.AddRecentMatches(note.MatchSummary, note.EndStamp)
if len(note.MatchSummary) > 0 {
b.send(&BookUpdate{
Action: EpochMatchSummary,
MarketID: marketID,
Payload: b.GetMatchesSummary(),
Payload: matchSummaries,
})
}
for durStr, cache := range b.candleCaches {
Expand Down Expand Up @@ -365,9 +364,10 @@ func (b *bookie) send(u *BookUpdate) {
func (b *bookie) book() *OrderBook {
buys, sells, epoch := b.Orders()
return &OrderBook{
Buys: b.translateBookSide(buys),
Sells: b.translateBookSide(sells),
Epoch: b.translateBookSide(epoch),
Buys: b.translateBookSide(buys),
Sells: b.translateBookSide(sells),
Epoch: b.translateBookSide(epoch),
RecentMatches: b.RecentMatches(),
}
}

Expand Down Expand Up @@ -911,7 +911,7 @@ func (dc *dexConnection) refreshServerConfig() error {

assets, epochs, err := generateDEXMaps(dc.acct.host, cfg)
if err != nil {
return fmt.Errorf("Inconsistent 'config' response: %w", err)
return fmt.Errorf("inconsistent 'config' response: %w", err)
}

// Update dc.{marketMap,epoch,assets}
Expand Down
3 changes: 3 additions & 0 deletions client/core/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"decred.org/dcrdex/client/asset"
"decred.org/dcrdex/client/comms"
"decred.org/dcrdex/client/db"
"decred.org/dcrdex/client/orderbook"
"decred.org/dcrdex/dex"
"decred.org/dcrdex/dex/calc"
"decred.org/dcrdex/dex/candles"
Expand Down Expand Up @@ -567,6 +568,8 @@ type OrderBook struct {
Sells []*MiniOrder `json:"sells"`
Buys []*MiniOrder `json:"buys"`
Epoch []*MiniOrder `json:"epoch"`
// RecentMatches is a cache of up to 100 recent matches for a market.
RecentMatches []*orderbook.MatchSummary `json:"recentMatches"`
}

// MarketOrderBook is used as the BookUpdate's Payload with the FreshBookAction.
Expand Down
80 changes: 37 additions & 43 deletions client/orderbook/orderbook.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"fmt"
"sync"
"sync/atomic"
"time"

"decred.org/dcrdex/dex"
"decred.org/dcrdex/dex/msgjson"
Expand Down Expand Up @@ -55,11 +54,14 @@ type rateSell struct {
sell bool
}

// MatchSummary summarizes one or more consecutive matches at a given rate and
// buy/sell direction. Consecutive matches of the same rate and direction are
// binned by the server.
type MatchSummary struct {
Rate uint64 `json:"rate"`
Qty uint64 `json:"qty"`
Age time.Time `json:"age"`
Sell bool `json:"sell"`
Rate uint64 `json:"rate"`
Qty uint64 `json:"qty"`
Stamp uint64 `json:"stamp"`
Sell bool `json:"sell"`
}

// OrderBook represents a client tracked order book.
Expand Down Expand Up @@ -616,57 +618,49 @@ func (ob *OrderBook) BestFillMarketBuy(qty, lotSize uint64) ([]*Fill, bool) {
return ob.sells.bestFill(qty, true, lotSize)
}

// SetMatchesSummary set matches summary. If the matches summary length grows
// bigger than 100, it will slice out the ones first added.
func (ob *OrderBook) SetMatchesSummary(matches map[uint64]uint64, ts uint64, sell bool) {
// AddRecentMatches adds the recent matches. If the recent matches cache length
// grows bigger than 100, it will slice out the ones first added.
func (ob *OrderBook) AddRecentMatches(matches [][2]int64, ts uint64) []*MatchSummary {
if matches == nil {
return
}
newMatchesSummary := make([]*MatchSummary, len(matches))
i := 0
for rate, qty := range matches {
newMatchesSummary[i] = &MatchSummary{
Rate: rate,
Qty: qty,
Age: time.UnixMilli(int64(ts)),
Sell: sell,
return nil
}
newMatches := make([]*MatchSummary, len(matches))
for i, m := range matches {
rate, qty := m[0], m[1]
// negative qty means maker is a sell
sell := true
if qty < 0 {
qty *= -1
sell = false
}
newMatches[i] = &MatchSummary{
Rate: uint64(rate),
Qty: uint64(qty),
Stamp: ts,
Sell: sell,
}
i++
}

// Put the newest first.
for i, j := 0, len(newMatches)-1; i < j; i, j = i+1, j-1 {
newMatches[i], newMatches[j] = newMatches[j], newMatches[i]
}

ob.matchSummaryMtx.Lock()
defer ob.matchSummaryMtx.Unlock()
if ob.matchesSummary == nil {
ob.matchesSummary = newMatchesSummary
return
}
ob.matchesSummary = append(newMatchesSummary, ob.matchesSummary...)
maxLength := 100
ob.matchesSummary = append(newMatches, ob.matchesSummary...)
const maxLength = 100
// if ob.matchesSummary length is greater than max length, we slice the array
// to maxLength, removing values first added.
if len(ob.matchesSummary) > maxLength {
ob.matchesSummary = ob.matchesSummary[:maxLength]
}
return newMatches
}

// GetMatchesSummary returns a deep copy of MatchesSummary
func (ob *OrderBook) GetMatchesSummary() []MatchSummary {
// RecentMatches returns up to 100 recent matches, newest first.
func (ob *OrderBook) RecentMatches() []*MatchSummary {
ob.matchSummaryMtx.Lock()
defer ob.matchSummaryMtx.Unlock()
matchesSummary := make([]MatchSummary, len(ob.matchesSummary))
for i := 0; i < len(ob.matchesSummary); i++ {
matchesSummary[i] = ob.matchesSummary[i].copy()
}
return matchesSummary
}

// makes a copy of a MatchSummary
func (ob *MatchSummary) copy() MatchSummary {
matchSummary := MatchSummary{
Rate: ob.Rate,
Qty: ob.Qty,
Age: ob.Age,
Sell: ob.Sell,
}
return matchSummary
return ob.matchesSummary
}
4 changes: 2 additions & 2 deletions client/webserver/site/src/html/bodybuilder.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<link rel="icon" href="/img/favicon.png?v=AK4XS4">
<meta name="description" content="Decred DEX Client Web Portal">
<title>{{.Title}}</title>
<link href="/css/style.css?v=RQIW8192" rel="stylesheet">
<link href="/css/style.css?v=RQIW8193" rel="stylesheet">
</head>
<body {{if .UserInfo.DarkMode}} class="dark"{{end}}>
<div class="popup-notes" id="popupNotes">
Expand Down Expand Up @@ -86,7 +86,7 @@
{{end}}

{{define "bottom"}}
<script src="/js/entry.js?v=RQIW8192"></script>
<script src="/js/entry.js?v=RQIW8193"></script>
</body>
</html>
{{end}}
19 changes: 7 additions & 12 deletions client/webserver/site/src/js/markets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -751,7 +751,6 @@ export default class MarketsPage extends BasePage {
this.maxEstimateTimer = null
}
const mktId = marketID(baseCfg.symbol, quoteCfg.symbol)
this.setToRecentMatches(dex.markets[mktId].recentmatches)
this.market = {
dex: dex,
sid: mktId, // A string market identifier used by the DEX.
Expand Down Expand Up @@ -780,7 +779,6 @@ export default class MarketsPage extends BasePage {
if (dex.candleDurs.indexOf(this.candleDur) === -1) this.candleDur = dex.candleDurs[0]
this.loadCandles()
}
this.refreshRecentMatchesTable()
this.setLoaderMsgVisibility()
this.setRegistrationStatusVisibility()
this.resolveOrderFormVisibility()
Expand Down Expand Up @@ -1087,6 +1085,8 @@ export default class MarketsPage extends BasePage {
return
}
this.depthChart.set(this.book, cfg.lotsize, cfg.ratestep, baseUnitInfo, quoteUnitInfo)
this.recentMatches = data.book.recentMatches ?? []
this.refreshRecentMatchesTable()
}

/*
Expand Down Expand Up @@ -1384,7 +1384,7 @@ export default class MarketsPage extends BasePage {
}

handleEpochMatchSummary (data: BookUpdate) {
this.setToRecentMatches(data.payload)
this.addRecentMatches(data.payload)
this.refreshRecentMatchesTable()
}

Expand Down Expand Up @@ -1892,7 +1892,7 @@ export default class MarketsPage extends BasePage {
case 'qty':
return (a: RecentMatch, b: RecentMatch) => this.recentMatchesSortDirection * (a.qty - b.qty)
case 'age':
return (a: RecentMatch, b:RecentMatch) => this.recentMatchesSortDirection * (a.age.getTime() - b.age.getTime())
return (a: RecentMatch, b:RecentMatch) => this.recentMatchesSortDirection * (a.stamp - b.stamp)
}
}

Expand All @@ -1910,20 +1910,15 @@ export default class MarketsPage extends BasePage {
updateDataCol(row, 'rate', Doc.formatCoinValue(match.rate / this.market.rateConversionFactor))
// change rate color based if is sell or not.
updateDataCol(row, 'qty', Doc.formatCoinValue(match.qty, this.market.baseUnitInfo))
updateDataCol(row, 'age', match.age.toLocaleTimeString())
updateDataCol(row, 'age', new Date(match.stamp).toLocaleTimeString())
row.classList.add(match.sell ? 'sellcolor' : 'buycolor')

page.recentMatchesLiveList.append(row)
}
}

setToRecentMatches (matches: RecentMatch[]) {
// if does not have new matches, just skip.
if (!matches) return
for (let i = 0; i < matches.length; i++) {
matches[i].age = new Date(matches[i].age)
}
this.recentMatches = matches
addRecentMatches (matches: RecentMatch[]) {
this.recentMatches = [...matches, ...this.recentMatches].slice(0, 100)
}

setBalanceVisibility () {
Expand Down
4 changes: 2 additions & 2 deletions client/webserver/site/src/js/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ export interface Market {
buybuffer: number
orders: Order[]
spot: Spot
recentmatches: RecentMatch[]
}

export interface Order {
Expand Down Expand Up @@ -319,7 +318,7 @@ export interface OrderNote extends CoreNote {
export interface RecentMatch {
rate: number
qty: number
age: Date
stamp: number
sell: boolean
}

Expand Down Expand Up @@ -468,6 +467,7 @@ export interface CoreOrderBook {
sells: MiniOrder[]
buys: MiniOrder[]
epoch: MiniOrder[]
recentMatches: RecentMatch[]
}

export interface MarketOrderBook {
Expand Down
6 changes: 3 additions & 3 deletions dex/msgjson/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -1177,9 +1177,9 @@ type EpochReportNote struct {
Epoch uint64 `json:"epoch"`
BaseFeeRate uint64 `json:"baseFeeRate"`
QuoteFeeRate uint64 `json:"quoteFeeRate"`
// quantity of matches by rate: [rate]qty
MatchesSummaryBuy map[uint64]uint64 `json:"matchesSummaryBuy"`
MatchesSummarySell map[uint64]uint64 `json:"matchesSummarySell"`
// MatchSummary: [rate, quantity]. Quantity is signed. Negative means that
// the maker was a sell order.
MatchSummary [][2]int64 `json:"matchSummary"`
Candle
}

Expand Down
18 changes: 8 additions & 10 deletions server/market/bookrouter.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,14 +104,13 @@ type sigDataEpochOrder sigDataOrder
type sigDataUpdateRemaining sigDataOrder

type sigDataEpochReport struct {
epochIdx int64
epochDur int64
stats *matcher.MatchCycleStats
spot *msgjson.Spot
baseFeeRate uint64
quoteFeeRate uint64
matchesSummaryBuy map[uint64]uint64
matchesSummarySell map[uint64]uint64
epochIdx int64
epochDur int64
stats *matcher.MatchCycleStats
spot *msgjson.Spot
baseFeeRate uint64
quoteFeeRate uint64
matches [][2]int64
}

type sigDataNewEpoch struct {
Expand Down Expand Up @@ -430,8 +429,7 @@ out:
StartRate: stats.StartRate,
EndRate: stats.EndRate,
},
MatchesSummaryBuy: sigData.matchesSummaryBuy,
MatchesSummarySell: sigData.matchesSummarySell,
MatchSummary: sigData.matches,
}

case sigDataEpochOrder:
Expand Down
34 changes: 19 additions & 15 deletions server/market/market.go
Original file line number Diff line number Diff line change
Expand Up @@ -2599,33 +2599,37 @@ func (m *Market) processReadyEpoch(epoch *readyEpoch, notifyChan chan<- *updateS
log.Errorf("Error updating API data collector: %v", err)
}

basicMatchesSell := make(map[uint64]uint64)
basicMatchesBuy := make(map[uint64]uint64)
matchReport := make([][2]int64, 0, len(matches))
var lastRate uint64
var lastSide bool
for _, matchSet := range matches {
for _, match := range matchSet.Matches() {
// skip cancel order types
if _, ok := match.Taker.(*order.CancelOrder); ok {
t := match.Taker.Trade()
if t == nil {
continue
}
if match.Taker.Trade() != nil && match.Taker.Trade().Sell {
basicMatchesSell[match.Rate] += match.Quantity
if match.Rate != lastRate || t.Sell != lastSide {
matchReport = append(matchReport, [2]int64{int64(match.Rate), 0})
lastRate, lastSide = match.Rate, t.Sell
}
if t.Sell {
matchReport[len(matchReport)-1][1] += int64(match.Quantity)
} else {
basicMatchesBuy[match.Rate] += match.Quantity
matchReport[len(matchReport)-1][1] -= int64(match.Quantity)
}
}
}
// Send "epoch_report" notifications.
notifyChan <- &updateSignal{
action: epochReportAction,
data: sigDataEpochReport{
epochIdx: epoch.Epoch,
epochDur: epoch.Duration,
spot: spot,
stats: stats,
baseFeeRate: feeRateBase,
quoteFeeRate: feeRateQuote,
matchesSummaryBuy: basicMatchesBuy,
matchesSummarySell: basicMatchesSell,
epochIdx: epoch.Epoch,
epochDur: epoch.Duration,
spot: spot,
stats: stats,
baseFeeRate: feeRateBase,
quoteFeeRate: feeRateQuote,
matches: matchReport,
},
}

Expand Down

0 comments on commit cc2830a

Please sign in to comment.