-
Notifications
You must be signed in to change notification settings - Fork 19
/
pricelevel.go
241 lines (204 loc) · 5.79 KB
/
pricelevel.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
// Copyright (c) 2022 Gobalsky Labs Limited
//
// Use of this software is governed by the Business Source License included
// in the LICENSE.VEGA file and at https://www.mariadb.com/bsl11.
//
// Change Date: 18 months from the later of the date of the first publicly
// available Distribution of this version of the repository, and 25 June 2022.
//
// On the date above, in accordance with the Business Source License, use
// of this software will be governed by version 3 or later of the GNU General
// Public License.
package matching
import (
"errors"
"fmt"
"code.vegaprotocol.io/vega/core/types"
"code.vegaprotocol.io/vega/libs/num"
"code.vegaprotocol.io/vega/logging"
)
var (
// ErrWashTrade signals an attempt to a wash trade from a party.
ErrWashTrade = errors.New("party attempted to submit wash trade")
ErrFOKNotFilled = errors.New("FOK order could not be fully filled")
)
// PriceLevel represents all the Orders placed at a given price.
type PriceLevel struct {
price *num.Uint
orders []*types.Order
volume uint64
}
// NewPriceLevel instantiate a new PriceLevel.
func NewPriceLevel(price *num.Uint) *PriceLevel {
return &PriceLevel{
price: price,
orders: []*types.Order{},
}
}
func (l *PriceLevel) reduceVolume(reduceBy uint64) {
l.volume -= reduceBy
}
func (l *PriceLevel) getOrdersByParty(partyID string) []*types.Order {
ret := []*types.Order{}
for _, o := range l.orders {
if o.Party == partyID {
ret = append(ret, o)
}
}
return ret
}
func (l *PriceLevel) addOrder(o *types.Order) {
// add orders to slice of orders on this price level
l.orders = append(l.orders, o)
l.volume += o.Remaining
}
func (l *PriceLevel) removeOrder(index int) {
// decrease total volume
l.volume -= l.orders[index].Remaining
// remove the orders at index
copy(l.orders[index:], l.orders[index+1:])
l.orders = l.orders[:len(l.orders)-1]
}
// fakeUncross - this updates a copy of the order passed to it, the copied order is returned.
func (l *PriceLevel) fakeUncross(o *types.Order, checkWashTrades bool) (agg *types.Order, trades []*types.Trade, err error) {
// work on a copy of the order, so we can submit it a second time
// after we've done the price monitoring and fees checks
cpy := *o
agg = &cpy
if len(l.orders) == 0 {
return
}
for _, order := range l.orders {
if checkWashTrades {
if order.Party == agg.Party {
err = ErrWashTrade
return
}
}
// Get size and make newTrade
size := l.getVolumeAllocation(agg, order)
if size <= 0 {
panic("Trade.size > order.remaining")
}
// New Trade
trade := newTrade(agg, order, size)
// Update Remaining for both aggressive and passive
agg.Remaining -= size
// Update trades
trades = append(trades, trade)
// Exit when done
if agg.Remaining == 0 {
break
}
}
return agg, trades, err
}
func (l *PriceLevel) uncross(agg *types.Order, checkWashTrades bool) (filled bool, trades []*types.Trade, impactedOrders []*types.Order, err error) {
// for some reason sometimes it seems the pricelevels are not deleted when getting empty
// no big deal, just return early
if len(l.orders) <= 0 {
return
}
var (
toRemove []int
removed int
)
// l.orders is always sorted by timestamps, that is why when iterating we always start from the beginning
for i, order := range l.orders {
// prevent wash trade
if checkWashTrades {
if order.Party == agg.Party {
err = ErrWashTrade
break
}
}
// Get size and make newTrade
size := l.getVolumeAllocation(agg, order)
if size <= 0 {
panic("Trade.size > order.remaining")
}
// New Trade
trade := newTrade(agg, order, size)
// Update Remaining for both aggressive and passive
agg.Remaining -= size
order.Remaining -= size
l.volume -= size
// Schedule order for deletion
if order.Remaining == 0 {
toRemove = append(toRemove, i)
}
// Update trades
trades = append(trades, trade)
impactedOrders = append(impactedOrders, order)
// Exit when done
if agg.Remaining == 0 {
break
}
}
// FIXME(jeremy): these need to be optimized, we can make a single copy
// just by keep the index of the last order which is to remove as they
// are all order, then just copy the second part of the slice in the actual s[0]
if len(toRemove) > 0 {
for _, idx := range toRemove {
copy(l.orders[idx-removed:], l.orders[idx-removed+1:])
removed++
}
l.orders = l.orders[:len(l.orders)-removed]
}
return agg.Remaining == 0, trades, impactedOrders, err
}
func (l *PriceLevel) getVolumeAllocation(agg, pass *types.Order) uint64 {
return min(agg.Remaining, pass.Remaining)
}
// Returns the min of 2 uint64s.
func min(x, y uint64) uint64 {
if y < x {
return y
}
return x
}
// Returns the max of 2 uint64s.
func max(x, y uint64) uint64 {
if x > y {
return x
}
return y
}
// Creates a trade of a given size between two orders and updates the order details.
func newTrade(agg, pass *types.Order, size uint64) *types.Trade {
var buyer, seller *types.Order
if agg.Side == types.SideBuy {
buyer = agg
seller = pass
} else {
buyer = pass
seller = agg
}
if agg.Side == pass.Side {
panic(fmt.Sprintf("agg.side == pass.side (agg: %v, pass: %v)", agg, pass))
}
return &types.Trade{
Type: types.TradeTypeDefault,
MarketID: agg.MarketID,
Price: pass.Price.Clone(),
MarketPrice: pass.OriginalPrice.Clone(),
Size: size,
Aggressor: agg.Side,
Buyer: buyer.Party,
Seller: seller.Party,
Timestamp: agg.CreatedAt,
}
}
func (l PriceLevel) print(log *logging.Logger) {
log.Debug(fmt.Sprintf("priceLevel: %d\n", l.price))
for _, o := range l.orders {
var side string
if o.Side == types.SideBuy {
side = "BUY"
} else {
side = "SELL"
}
log.Debug(fmt.Sprintf(" %s %s @%d size=%d R=%d Type=%d T=%d %s\n",
o.Party, side, o.Price, o.Size, o.Remaining, o.TimeInForce, o.CreatedAt, o.ID))
}
}