tatanka: Add orderbook.
WIP
https://github.com/decred/dcrdex/blob/master/tatanka/spec/meshdex.md contains most of the guidelines we have so far and should be read first.
Some more points about orders from matrix: Lot size is defined per-order based on the user's desired maximum fee exposure, and must be a power of 2. Rate step is always 1 atom / atom equivalent All orders are limit orders, so no marketBuyBuffer is required All orders will have an expiration time shortly into the future, and must be refreshed by the orderer Probably no epochs
The server does not have anything to do with the orderbook, it is only kept by clients. This issue currently aims to figure out the protocol to use to keep clients synchronized.
User requests orderbook per market they are interested in and subscribes to those. They are also expected to participate in sharing all order info on those markets.
Clients can request all order ids or individual orders from other clients. When requesting all orders, it's just the order ids, no info. They may be divided into several sends.
orderbook res
type OrderBook struct {
Page uint64 `json:"page"` // from 0
NPages uin64 `json:"npages"`
OrderIDs []order.ID `json:"orderids"`
}
On the subscription channel, only individual order changes are broadcast. Orders can be created or updated. updated includes qty changing/cancellation and the timeout being extended. Messages for orders are signed with the private key matching the user's PeerID or ignored. The signature should be passed along with the update and saved with it. Update requests should contain all info so that a listener could eventually know the whole book contents after all timeouts have happened.
Quantity can be moved up or down, no need to add settled amount. Cancel is inferred by quantity set to zero. There's no difference between canceling an order and declaring everything settled.
order update
type OrderUpdate struct {
Sig []byte `json:"sig"`
Order *tanka.Order `json:"order"` // updated status
}
Multiple orders (up to some limit) can also be requested. In that case they would be requested by id and they would get a slice of OrderUpdate from the user.
When a user decides to match an order, they send messages directly to that peer and start negotiation. The maker will update their order if the taker meets their requirements.
Some thoughts:
- Things we may want to check on order updates: signature, lotSize is ~divisible by~ a power of two, expiration is not over some time or in the past
- We want to check user reputations at some point, maybe for every time expiration is updated? We maybe should save it along with the order. I don't guess we would share it though with the match? The user will want to ask the tatanka servers I guess.
- Do we want to tell users to prefer older orders on the books? imo it's impossible for us to police and we shouldn't worry about it. It's impossible to police because maybe a user is avoiding another one based on local reputation. I don't think we can prove or disprove some arbitrary reason for choosing an order. But should we match based on oldest with bisonw? Or match randomly just because? I think probably we look at order price/qty first, ofc, then out of those we look at reputation next, then we just go with the oldest order that satisfies our needs. I think we would prefer a newer order over two older orders with different users, as that means less transactions for us. However I could see how that might seem "unfair" and maybe leave some traders with orders that never get filled if they are too small. I guess the answer for that maker is to make the order cheaper, or become a taker.
- A user will miss some order updates. Maybe they should periodically ask some users for their orders list to see if they are missing some.
- Expired orders are immediately forgotten. If requests come for an order we don't have, we assume it expired. We do need to save data for candles, but this should be another subscription and issue I think.
- Only users with interest in a market subscribe to it, and only those users can be asked for orders. Is this good enough, or do all clients need to worry about all markets so that they can distribute data? Probably not?
- All this means that taker orders are never on the book.
- If two makers create orders that would match, that is on them to notice and work it out with one becoming the taker. In that case the "taker" would be on the books in a way, but we wouldn't know which was doing which when the trade happens.
Copying my chat comment: "latency will be a key issue but I think can assume within 1s most will have heard a broadcast" Maybe this is a good assumption to work with
User requests orderbook per market they are interested in and subscribes to those. They are also expected to participate in sharing all order info on those markets.
How about when the user subscribes to the orderbook (subject), the mesh will send a new_subscriber broadcast to other subscribers, and the other subscribers who have standing orders can message the user.
lotSize is divisible by two
Lot size should be a power of 2, e.g. 2^N for some integer N.
From the user's perspective, lot size is presented as maximum fee exposure, which is translated to lot size behind the scenes. The variable lot size also means we need to introduce some conception of "visibility", since some orders on the book may not be compatible with the user's current setting. We want to track those orders anyway, so that the user can modify their setting without having to resync the book. This visibility will be a challenge, because say a user has a fee exposure setting that translates to a lot size of 100, while an order on the books has a lot size of 50, but has 2 lots available. We could still match that order, but with the stipulation that we must match both lots.
We want to check user reputations at some point, maybe for every time expiration is updated? We maybe should save it along with the match. I don't guess we would share it though with the match? The user will want to ask the tatanka servers I guess.
Yeah this will be interesting to figure out. We should talk about the concept of "funded messages" or "funded broadcasts", where the server validates the funding backing an order, and attaches that validation, in some form, to the message. They could also attach the users current reputation to the message.
What the book interface could look like.
func New(market string) OrderBooker
type OrderBooker interface {
Pages() []OrderBookPage
Order(id OrderID) tanka.Order
Orders(id []OrderID) []tanka.Order
FindOrders(filter OrderFilter) []tanka.Order
Add(*tanka.Order)
Update(*tanka.OrderUpdate)
Delete(id OrderID)
}
I think pages would be just the order ids. Maybe it's silly at this point to expect a lot, but maybe pages should take a function that does something with each page, in case there are too many to keep in memory... Same for FindOrders.
It is added, however unused yet.