-
Notifications
You must be signed in to change notification settings - Fork 84
/
order.go
804 lines (670 loc) · 21.6 KB
/
order.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
// This code is available on the terms of the project LICENSE.md file,
// also available online at https://blueoakcouncil.org/license/1.0.0.
// Package order defines the Order and Match types used throughout the DEX.
package order
import (
"database/sql"
"database/sql/driver"
"encoding/binary"
"encoding/hex"
"encoding/json"
"fmt"
"sync"
"time"
"decred.org/dcrdex/dex/encode"
"decred.org/dcrdex/server/account"
"github.com/decred/dcrd/crypto/blake256"
)
// Several types including OrderID and Commitment are defined as a Blake256
// hash. Define an internal hash type and hashSize for convenience.
const hashSize = blake256.Size // 32
type hash = [hashSize]byte
// OrderIDSize defines the length in bytes of an OrderID.
const OrderIDSize = hashSize
// OrderID is the unique identifier for each order. It is defined as the
// Blake256 hash of the serialized order.
type OrderID hash
// IDFromHex decodes an OrderID from a hexadecimal string.
func IDFromHex(sid string) (OrderID, error) {
if len(sid) > OrderIDSize*2 {
return OrderID{}, fmt.Errorf("invalid order ID. too long %d > %d", len(sid), OrderIDSize*2)
}
oidB, err := hex.DecodeString(sid)
if err != nil {
return OrderID{}, fmt.Errorf("order ID decode error: %w", err)
}
var oid OrderID
copy(oid[OrderIDSize-len(oidB):], oidB)
return oid, nil
}
// String returns a hexadecimal representation of the OrderID. String implements
// fmt.Stringer.
func (oid OrderID) String() string {
return hex.EncodeToString(oid[:])
}
// MarshalJSON satisfies the json.Marshaller interface, and will marshal the
// id to a hex string.
func (oid OrderID) MarshalJSON() ([]byte, error) {
return json.Marshal(oid.String())
}
// Bytes returns the order ID as a []byte.
func (oid OrderID) Bytes() []byte {
return oid[:]
}
// Value implements the sql/driver.Valuer interface.
func (oid OrderID) Value() (driver.Value, error) {
return oid[:], nil // []byte
}
// Scan implements the sql.Scanner interface.
func (oid *OrderID) Scan(src interface{}) error {
switch src := src.(type) {
case []byte:
copy(oid[:], src)
return nil
//case string:
// case nil:
// *oid = nil
// return nil
}
return fmt.Errorf("cannot convert %T to OrderID", src)
}
var zeroOrderID OrderID
// IsZero returns true if the order ID is zeros.
func (oid OrderID) IsZero() bool {
return oid == zeroOrderID
}
// OrderType distinguishes the different kinds of orders (e.g. limit, market,
// cancel).
type OrderType uint8
// The different OrderType values.
const (
UnknownOrderType OrderType = iota
LimitOrderType
MarketOrderType
CancelOrderType
)
// Value implements the sql/driver.Valuer interface.
func (ot OrderType) Value() (driver.Value, error) {
return int64(ot), nil
}
// Scan implements the sql.Scanner interface.
func (ot *OrderType) Scan(src interface{}) error {
// Use sql.(NullInt32).Scan because it uses the unexported
// sql.convertAssignRows to coerce compatible types.
v := new(sql.NullInt32)
if err := v.Scan(src); err != nil {
return err
}
*ot = OrderType(v.Int32)
return nil
}
// String returns a string representation of the OrderType.
func (ot OrderType) String() string {
switch ot {
case LimitOrderType:
return "limit"
case MarketOrderType:
return "market"
case CancelOrderType:
return "cancel"
default:
return "unknown"
}
}
// TimeInForce indicates how limit order execution is to be handled. That is,
// when the order is not immediately matched during processing of the order's
// epoch, the order may become a standing order or be revoked without a fill.
type TimeInForce uint8
// The TimeInForce is either ImmediateTiF, which prevents the order from
// becoming a standing order if there is no match during epoch processing, or
// StandingTiF, which allows limit orders to enter the order book if not
// immediately matched during epoch processing.
const (
ImmediateTiF TimeInForce = iota
StandingTiF
)
// String satisfies the Stringer interface.
func (t TimeInForce) String() string {
switch t {
case ImmediateTiF:
return "immediate"
case StandingTiF:
return "standing"
}
return fmt.Sprintf("unknown (%d)", t)
}
// Order specifies the methods required for a type to function as a DEX order.
// See the concrete implementations of MarketOrder, LimitOrder, and CancelOrder.
type Order interface {
// Prefix returns the order *Prefix.
Prefix() *Prefix
// Trade returns the order *Trade if a limit or market order, else nil.
Trade() *Trade
// ID computes the Order's ID from its serialization. Serialization is
// detailed in the 'Client Order Management' section of the DEX
// specification.
ID() OrderID
// UID gives the string representation of the order ID. It is named to
// reflect the intent of providing a unique identifier.
UID() string
// User gives the user's account ID.
User() account.AccountID
// Serialize marshals the order. Serialization is detailed in the 'Client
// Order Management' section of the DEX specification.
Serialize() []byte
// Type indicates the Order's type (e.g. LimitOrder, MarketOrder, etc.).
Type() OrderType
// Time returns the Order's server time in milliseconds, when it was received by the server.
Time() int64
// SetTime sets the ServerTime field of the prefix.
SetTime(time.Time)
// Base returns the unique integer identifier of the base asset as defined
// in the asset package.
Base() uint32
// Quote returns the unique integer identifier of the quote asset as defined
// in the asset package.
Quote() uint32
// Commitment returns the order's preimage commitment.
Commitment() Commitment
}
// zeroTime is the Unix time for a Time where IsZero() == true.
var zeroTime = unixMilli(time.Time{})
// An order's ID is computed as the Blake-256 hash of the serialized order.
func calcOrderID(order Order) OrderID {
sTime := order.Time()
if sTime == zeroTime {
panic("Order's ServerTime is unset")
}
return blake256.Sum256(order.Serialize())
}
// CoinID should be used to wrap a []byte so that it may be used as a map key.
type CoinID []byte
func (c CoinID) String() string {
return hex.EncodeToString(c)
}
// CommitmentSize is the length of the Commitment, a 32-byte Blake-256 hash
// according to the DEX specification.
const CommitmentSize = hashSize
// Commitment is the Blake-256 hash of the Preimage.
type Commitment hash
var zeroCommit Commitment
// IsZero indicates if the Commitment is the zero-value for the type.
func (c *Commitment) IsZero() bool {
return *c == zeroCommit
}
// Value implements the sql/driver.Valuer interface. The zero-value Commitment
// returns nil rather than a byte slice of zeros.
func (c Commitment) Value() (driver.Value, error) {
if c.IsZero() {
return nil, nil // nil => NULL
}
return c[:], nil // []byte => BYTEA
}
// Scan implements the sql.Scanner interface. NULL table values are scanned as
// the zero-value Commitment.
func (c *Commitment) Scan(src interface{}) error {
switch src := src.(type) {
case []byte:
copy(c[:], src)
return nil
case nil: // NULL in the table
*c = Commitment{}
return nil
}
return fmt.Errorf("cannot convert %T to Commitment", src)
}
// String returns a hexadecimal representation of the Commitment. String
// implements fmt.Stringer.
func (c Commitment) String() string {
return hex.EncodeToString(c[:])
}
// PreimageSize defines the length of the preimage, which is a 32-byte value
// according to the DEX specification.
const PreimageSize = 32
// Preimage represents the 32-byte preimage as a byte slice.
type Preimage [PreimageSize]byte
// Commit computes the preimage commitment as the Blake-256 hash of the
// Preimage.
func (pi *Preimage) Commit() Commitment {
return blake256.Sum256(pi[:])
}
// Value implements the sql/driver.Valuer interface.
func (pi Preimage) Value() (driver.Value, error) {
return pi[:], nil // []byte
}
// Scan implements the sql.Scanner interface.
func (pi *Preimage) Scan(src interface{}) error {
switch src := src.(type) {
case []byte:
copy(pi[:], src)
return nil
case nil: // NULL in the table
*pi = Preimage{}
return nil
}
return fmt.Errorf("cannot convert %T to Preimage", src)
}
// IsZero checks if the Preimage is the zero Preimage.
func (pi *Preimage) IsZero() bool {
return *pi == Preimage{}
}
// Prefix is the order prefix containing data fields common to all orders.
type Prefix struct {
AccountID account.AccountID
BaseAsset uint32
QuoteAsset uint32
OrderType OrderType
ClientTime time.Time
ServerTime time.Time
Commit Commitment
id *OrderID // cache of the order's OrderID
}
// P is an alias for Prefix. Embedding with the alias allows us to define a
// method on the interface called Prefix that returns the *Prefix.
type P = Prefix
func (p *Prefix) Prefix() *Prefix {
return p
}
// PrefixLen is the length in bytes of the serialized order Prefix.
const PrefixLen = account.HashSize + 4 + 4 + 1 + 8 + 8 + CommitmentSize
// serializeSize returns the length of the serialized order Prefix.
func (p *Prefix) serializeSize() int {
return PrefixLen
}
// Time returns the order prefix's server time as a UNIX epoch time in
// milliseconds.
func (p *Prefix) Time() int64 {
return unixMilli(p.ServerTime)
}
// SetTime sets the order prefix's server time.
func (p *Prefix) SetTime(t time.Time) {
p.ServerTime = t.UTC()
// SetTime should only ever be called once in practice, but in case it is
// necessary to restamp the ServerTime, clear any computed OrderID.
p.id = nil
}
// User gives the user's account ID.
func (p *Prefix) User() account.AccountID {
return p.AccountID
}
// Serialize marshals the Prefix into a []byte.
func (p *Prefix) Serialize() []byte {
b := make([]byte, PrefixLen)
// account ID
offset := len(p.AccountID)
copy(b[:offset], p.AccountID[:])
// base asset
binary.BigEndian.PutUint32(b[offset:offset+4], p.BaseAsset)
offset += 4
// quote asset
binary.BigEndian.PutUint32(b[offset:offset+4], p.QuoteAsset)
offset += 4
// order type (e.g. market, limit, cancel)
b[offset] = uint8(p.OrderType)
offset++
// client time
binary.BigEndian.PutUint64(b[offset:offset+8], unixMilliU(p.ClientTime))
offset += 8
// server time
binary.BigEndian.PutUint64(b[offset:offset+8], unixMilliU(p.ServerTime))
offset += 8
// commitment
copy(b[offset:offset+CommitmentSize], p.Commit[:])
return b
}
// Base returns the base asset integer ID.
func (p *Prefix) Base() uint32 {
return p.BaseAsset
}
// Quote returns the quote asset integer ID.
func (p *Prefix) Quote() uint32 {
return p.QuoteAsset
}
// Type returns the order type.
func (p *Prefix) Type() OrderType {
return p.OrderType
}
// Commitment returns the order Commitment.
func (p *Prefix) Commitment() Commitment {
return p.Commit
}
// Trade is information about a trade-type order. Both limit and market orders
// are trade-type orders.
type Trade struct {
Coins []CoinID
Sell bool
Quantity uint64
Address string
// FillAmt is not part of the order's serialization.
fillAmtMtx sync.RWMutex
FillAmt uint64 // use Filled and AddFill methods for thread-safe access
}
// Copy makes a shallow copy of a Trade. This is useful when attempting to
// assign a newly-created trade to an order's field without a linter warning
// about copying a mutex (e.g. MarketOrder{T: *aNewTrade.Copy()}).
func (t *Trade) Copy() *Trade {
return &Trade{
Coins: t.Coins, // shallow
Sell: t.Sell,
Quantity: t.Quantity,
Address: t.Address,
FillAmt: t.FillAmt,
}
}
// T is an alias for Trade. Embedding with the alias allows us to define a
// method on the interface called Trade that returns the *Trade.
type T = Trade
// Trade returns a pointer to the orders embedded Trade.
func (t *Trade) Trade() *Trade {
return t
}
// Remaining returns the remaining order amount.
func (t *Trade) Remaining() uint64 {
t.fillAmtMtx.RLock()
defer t.fillAmtMtx.RUnlock()
return t.Quantity - t.FillAmt
}
// Filled returns the filled amount.
func (t *Trade) Filled() uint64 {
t.fillAmtMtx.RLock()
defer t.fillAmtMtx.RUnlock()
return t.FillAmt
}
// AddFill increases the filled amount.
func (t *Trade) AddFill(amt uint64) {
t.fillAmtMtx.Lock()
t.FillAmt += amt
t.fillAmtMtx.Unlock()
}
// SetFill sets the filled amount.
func (t *Trade) SetFill(amt uint64) {
t.fillAmtMtx.Lock()
t.FillAmt = amt
t.fillAmtMtx.Unlock()
}
// SwapAddress returns the order's payment address.
func (t *Trade) SwapAddress() string {
return t.Address
}
// FromAccount is the account that the order originates from. Only useful for
// account-based assets. Use of this method assumes that account coin has
// already been added.
func (t *Trade) FromAccount() string {
if len(t.Coins) == 0 {
return "no coins?"
}
return string(t.Coins[0])
}
// ToAccount is the account that the order pays to. Only useful for
// account-based assets.
func (t *Trade) ToAccount() string {
return t.Address
}
// BaseAccount is the account address associated with the base asset for the
// order. Only useful for account-based assets. Use of this method assumes that
// account coin has already been added (when sell = true).
func (t *Trade) BaseAccount() string {
if t.Sell {
return t.FromAccount()
}
return t.ToAccount()
}
// QuoteAccount is the account address associated with the quote asset for the
// order. Only useful for account-based assets. Use of this method assumes that
// account coin has already been added (when sell = false).
func (t *Trade) QuoteAccount() string {
if t.Sell {
return t.ToAccount()
}
return t.FromAccount()
}
// serializeSize returns the length of the serialized Trade.
func (t *Trade) serializeSize() int {
// Compute the size of the serialized Coin IDs.
var coinSz int
for _, coinID := range t.Coins {
coinSz += len(coinID)
// TODO: ensure all Coin IDs have the same size, indicating the same asset?
}
// The serialized order includes a byte for coin count, but this is implicit
// in coin slice length.
return 1 + coinSz + 1 + 8 + len(t.Address)
}
// Serialize marshals the Trade into a []byte.
func (t *Trade) Serialize() []byte {
b := make([]byte, t.serializeSize())
offset := 0
// Coin count
b[offset] = uint8(len(t.Coins))
offset++
// Coins
for _, coinID := range t.Coins {
coinSz := len(coinID)
copy(b[offset:offset+coinSz], coinID)
offset += coinSz
}
// order side
var side uint8
if t.Sell {
side = 1
}
b[offset] = side
offset++
// order quantity
binary.BigEndian.PutUint64(b[offset:offset+8], t.Quantity)
offset += 8
// client address for received funds
copy(b[offset:offset+len(t.Address)], []byte(t.Address))
return b
}
// MarketOrder defines a market order in terms of a Prefix and the order
// details, including the backing Coins, the order direction/side, order
// quantity, and the address where the matched client will send funds. The order
// quantity is in atoms of the base asset, and must be an integral multiple of
// the asset's lot size, except for Market buy orders when it is in units of the
// quote asset and is not bound by integral lot size multiple constraints.
type MarketOrder struct {
P
T
}
// ID computes the order ID.
func (o *MarketOrder) ID() OrderID {
if o.id != nil {
return *o.id
}
id := calcOrderID(o)
o.id = &id
return id
}
// UID computes the order ID, returning the string representation.
func (o *MarketOrder) UID() string {
return o.ID().String()
}
// String is the same as UID. It is defined to satisfy Stringer.
func (o *MarketOrder) String() string {
return o.UID()
}
// serializeSize returns the length of the serialized MarketOrder.
func (o *MarketOrder) serializeSize() int {
// The serialized order includes a byte for coin count, but this is implicit
// in coin slice length.
return o.P.serializeSize() + o.T.serializeSize()
}
// Serialize marshals the LimitOrder into a []byte.
func (o *MarketOrder) Serialize() []byte {
b := make([]byte, o.serializeSize())
// Prefix and data common with MarketOrder
offset := o.P.serializeSize()
copy(b[:offset], o.P.Serialize())
tradeLen := o.T.serializeSize()
copy(b[offset:offset+tradeLen], o.T.Serialize())
return b
}
// Ensure MarketOrder is an Order.
var _ Order = (*MarketOrder)(nil)
// LimitOrder defines a limit order in terms of a MarketOrder and limit-specific
// data including rate (price) and time in force.
type LimitOrder struct {
P
T
Rate uint64 // price as atoms of quote asset, applied per 1e8 units of the base asset
Force TimeInForce
}
// ID computes the order ID.
func (o *LimitOrder) ID() OrderID {
if o.id != nil {
return *o.id
}
id := calcOrderID(o)
o.id = &id
return id
}
// UID computes the order ID, returning the string representation.
func (o *LimitOrder) UID() string {
return o.ID().String()
}
// String is the same as UID. It is defined to satisfy Stringer.
func (o *LimitOrder) String() string {
return o.UID()
}
// serializeSize returns the length of the serialized LimitOrder.
func (o *LimitOrder) serializeSize() int {
return o.P.serializeSize() + o.T.serializeSize() + 8 + 1
}
// Serialize marshals the LimitOrder into a []byte.
func (o *LimitOrder) Serialize() []byte {
b := make([]byte, o.serializeSize())
// Prefix and data common with MarketOrder
offset := o.P.serializeSize()
copy(b[:offset], o.P.Serialize())
tradeLen := o.T.serializeSize()
copy(b[offset:offset+tradeLen], o.T.Serialize())
offset += tradeLen
// Price rate in atoms of quote asset
binary.BigEndian.PutUint64(b[offset:offset+8], o.Rate)
offset += 8
// Time in force
b[offset] = uint8(o.Force)
return b
}
// Ensure LimitOrder is an Order.
var _ Order = (*LimitOrder)(nil)
// Price returns the limit order's price rate.
func (o *LimitOrder) Price() uint64 {
return o.Rate
}
// CancelOrder defines a cancel order in terms of an order Prefix and the ID of
// the order to be canceled.
type CancelOrder struct {
P
TargetOrderID OrderID
}
// ID computes the order ID.
func (o *CancelOrder) ID() OrderID {
if o.id != nil {
return *o.id
}
id := calcOrderID(o)
o.id = &id
return id
}
// UID computes the order ID, returning the string representation.
func (o *CancelOrder) UID() string {
return o.ID().String()
}
// Trade returns a pointer to the orders embedded Trade.
func (o *CancelOrder) Trade() *Trade {
return nil
}
// String is the same as UID. It is defined to satisfy Stringer.
func (o *CancelOrder) String() string {
return o.UID()
}
// serializeSize returns the length of the serialized CancelOrder.
func (o *CancelOrder) serializeSize() int {
return o.P.serializeSize() + OrderIDSize
}
// Serialize marshals the CancelOrder into a []byte.
func (o *CancelOrder) Serialize() []byte {
return append(o.P.Serialize(), o.TargetOrderID[:]...)
}
// Ensure CancelOrder is an Order.
var _ Order = (*CancelOrder)(nil)
// ValidateOrder ensures that the order with the given status for the specified
// market is sensible. The ServerTime may not be set yet, so the OrderID cannot
// be computed.
func ValidateOrder(ord Order, status OrderStatus, lotSize uint64) error {
if ord.Base() == ord.Quote() {
return fmt.Errorf("same asset specified for base and quote")
}
// Each order type has different rules about status and lot size.
switch ot := ord.(type) {
case *MarketOrder:
// Market orders OK statuses: epoch and executed (NOT booked or
// canceled).
switch status {
case OrderStatusEpoch, OrderStatusExecuted, OrderStatusRevoked:
default:
return fmt.Errorf("invalid market order status %d -> %s", status, status)
}
if ot.OrderType != MarketOrderType {
return fmt.Errorf("market order has wrong order type %d -> %s", ot.OrderType, ot.OrderType)
}
// Market sell orders must respect lot size. Market buy orders must be
// of an amount sufficiently buffered beyond the minimum standing sell
// order's lot cost, but that is enforced by the order router.
if ot.Sell && (ot.Quantity%lotSize != 0 || ot.Remaining()%lotSize != 0) {
return fmt.Errorf("market sell order fails lot size requirement %d %% %d = %d", ot.Quantity, lotSize, ot.Quantity%lotSize)
}
case *CancelOrder:
// Cancel order OK statuses: epoch, executed (NOT booked or canceled),
// and revoked. Revoked status indicates the cancel order is
// server-generated and corresponds to a revoked trade order.
switch status {
case OrderStatusEpoch, OrderStatusExecuted, OrderStatusRevoked: // orderStatusFailed if we decide to export that
default:
return fmt.Errorf("invalid cancel order status %d -> %s", status, status)
}
if ot.OrderType != CancelOrderType {
return fmt.Errorf("cancel order has wrong order type %d -> %s", ot.OrderType, ot.OrderType)
}
case *LimitOrder:
// Limit order OK statuses: epoch, booked, executed, and canceled (same
// as market plus booked).
switch status {
case OrderStatusEpoch, OrderStatusExecuted, OrderStatusRevoked:
case OrderStatusBooked, OrderStatusCanceled:
// Immediate time in force limit orders may not be canceled, and may
// not be in the order book.
if ot.Force == ImmediateTiF {
return fmt.Errorf("invalid immediate limit order status %d -> %s", status, status)
}
default:
return fmt.Errorf("invalid limit order status %d -> %s", status, status)
}
if ot.OrderType != LimitOrderType {
return fmt.Errorf("limit order has wrong order type %d -> %s", ot.OrderType, ot.OrderType)
}
// All limit orders must respect lot size.
if ot.Quantity%lotSize != 0 || ot.Remaining()%lotSize != 0 {
return fmt.Errorf("limit order fails lot size requirement %d %% %d = %d", ot.Quantity, lotSize, ot.Quantity%lotSize)
}
default:
// cannot validate an unknown order type
return fmt.Errorf("unknown order type")
}
return nil
}
// ExtractAddress extracts the address from the order. If the order is a cancel
// order, an empty string is returned.
func ExtractAddress(ord Order) string {
trade := ord.Trade()
if trade == nil {
return ""
}
return trade.Address
}
// Some commonly used time transformations.
var unixMilli = encode.UnixMilli
var unixMilliU = encode.UnixMilliU
var unixTimeMilli = encode.UnixTimeMilli