├── .gitignore ├── README.md ├── api └── match │ └── v1 │ ├── match.pb.go │ ├── match.proto │ └── match_grpc.pb.go ├── cmd ├── main.go └── match │ ├── wire.go │ └── wire_gen.go ├── example └── main.go ├── go.mod ├── go.sum ├── internal ├── match │ ├── match_test.go │ ├── orderbook.go │ ├── pool.go │ ├── types.go │ └── wire.go ├── server │ ├── grpc.go │ └── wire.go └── status │ ├── status.go │ ├── sysSignalHandle.go │ └── wire.go ├── models ├── order.go └── trade.go ├── mq ├── imq.go ├── wire.go └── yourMq.go ├── pqueue ├── skiplist │ ├── skipList.go │ ├── skipListDesc.go │ ├── skipList_test.go │ └── skipNode.go └── types.go ├── test └── app_test.go └── utils ├── string.go └── time.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Reference https://github.com/github/gitignore/blob/master/Go.gitignore 2 | # Binaries for programs and plugins 3 | *.exe 4 | *.exe~ 5 | *.dll 6 | *.so 7 | *.dylib 8 | 9 | # Test binary, built with `go test -c` 10 | *.test 11 | 12 | # Output of the go coverage tool, specifically when used with LiteIDE 13 | *.out 14 | 15 | # Dependency directories (remove the comment below to include it) 16 | vendor/ 17 | 18 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 19 | *.o 20 | *.a 21 | *.so 22 | 23 | # OS General 24 | Thumbs.db 25 | .DS_Store 26 | 27 | # project 28 | *.cert 29 | *.key 30 | *.log 31 | bin/ 32 | 33 | # Develop tools 34 | .vscode/ 35 | .idea/ 36 | *.swp 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lightning-engine 2 | 3 | 一套高性能的、纯内存撮合的数字货币交易所撮合系统。lightning-engine未使用redis等其他组件来辅助撮合,而是专门针对撮合系统,实现了升序和降序排列的跳表。从性能上来讲,lightning-engine要远大于基于redis其他撮合系统。 4 | 5 | lightning-engine通过gRPC接收请求(挂单、撤单等),挂单时只接收撮合需要的订单字段(价格、数量、类型、方向等,其他业务相关字段需上游服务缓存)。lightning-engine专注于盘口管理和撮合订单,具体的业务处理应由下游服务处理(补全订单字段落盘、成交单处理、用户资金处理等)。lightning-engine将成交单(包括撤销单)推送到MQ,下游服务订阅相应的Topic,进行相应的处理。 6 | 7 | ## 订单类型 8 | 9 | | 订单类型 | 描述 | lightning-engine | lightning-engine-pro | 10 | |--------|-----------------------|------------------|------------------| 11 | | GTC限价单 | 成交的部分立即成交,不能成交的部分挂在盘口 | 支持 | 支持 | 12 | | IOC限价单 | 成交的部分立即成交,不能成交的部分撤销 | 支持 | 支持 | 13 | | FOK限价单 | 若不能完全成交,则全部撤销 | 支持 | 支持 | 14 | | 市价单 | 以对手价成交,不能成交的部分,撤销 | 支持 | 支持 | 15 | 16 | ## 接口 17 | 18 | | 接口 | 版本 | lightning-engine | lightning-engine-pro | 19 | |------|-----|----------------|----------------------| 20 | | 挂单 | v1 | 支持 | 支持 | 21 | | 撤单 | v1 | 支持 | 支持 | 22 | | 查询深度 | v1 | 支持 | 支持 | 23 | 24 | ## example使用 25 | 26 | ```shell 27 | go run example/main.go 28 | ☁ lightning-engine [master] ⚡ go run example/main.go 29 | 2024/04/22 18:04:22 [RPC] :8080 30 | 2024/04/22 18:04:22 启动监听终端信号成功 31 | send buy Result:{Msg:"success"} 32 | send sell Result:{Msg:"success"} 33 | 2024/04/22 18:04:23 成交单: [{Id:1713780263144 Pair:BTC-USDT MakerId:1 TakerId:2 MakerUser:2 TakerUser:2 Price:21000 Amount:1 TakerOrderSide:sell TakerOrderType:limit TakerTimeInForce:GTC Ts:1713780263144}] 34 | send buy Result:{Msg:"success"} 35 | 2024/04/22 18:04:24 成交单: [{Id:1713780264146 Pair:BTC-USDT MakerId:1 TakerId:2 MakerUser:2 TakerUser:2 Price:21000 Amount:1 TakerOrderSide:sell TakerOrderType:limit TakerTimeInForce:GTC Ts:1713780264146}] 36 | send sell Result:{Msg:"success"} 37 | send buy Result:{Msg:"success"} 38 | 2024/04/22 18:04:25 成交单: [{Id:1713780265147 Pair:BTC-USDT MakerId:1 TakerId:2 MakerUser:2 TakerUser:2 Price:21000 Amount:1 TakerOrderSide:sell TakerOrderType:limit TakerTimeInForce:GTC Ts:1713780265147}] 39 | send sell Result:{Msg:"success"} 40 | send buy Result:{Msg:"success"} 41 | 2024/04/22 18:04:26 成交单: [{Id:1713780266149 Pair:BTC-USDT MakerId:1 TakerId:2 MakerUser:2 TakerUser:2 Price:21000 Amount:1 TakerOrderSide:sell TakerOrderType:limit TakerTimeInForce:GTC Ts:1713780266149}] 42 | send sell Result:{Msg:"success"} 43 | ^C2024/04/22 18:04:26 handle signal: interrupt 44 | 2024/04/22 18:04:26 正在安全退出服务... 45 | 2024/04/22 18:04:26 安全退出完成 46 | ``` 47 | 48 | 49 | ## Contact Us 50 | 51 | 个人邮箱: xiiiew@qq.com -------------------------------------------------------------------------------- /api/match/v1/match.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.28.0 4 | // protoc v3.19.4 5 | // source: api/match/v1/match.proto 6 | 7 | package v1 8 | 9 | import ( 10 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 11 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 12 | reflect "reflect" 13 | sync "sync" 14 | ) 15 | 16 | const ( 17 | // Verify that this generated code is sufficiently up-to-date. 18 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 19 | // Verify that runtime/protoimpl is sufficiently up-to-date. 20 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 21 | ) 22 | 23 | type ReplyResult struct { 24 | state protoimpl.MessageState 25 | sizeCache protoimpl.SizeCache 26 | unknownFields protoimpl.UnknownFields 27 | 28 | Code int32 `protobuf:"varint,1,opt,name=Code,proto3" json:"Code,omitempty"` 29 | Msg string `protobuf:"bytes,2,opt,name=Msg,proto3" json:"Msg,omitempty"` 30 | } 31 | 32 | func (x *ReplyResult) Reset() { 33 | *x = ReplyResult{} 34 | if protoimpl.UnsafeEnabled { 35 | mi := &file_api_match_v1_match_proto_msgTypes[0] 36 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 37 | ms.StoreMessageInfo(mi) 38 | } 39 | } 40 | 41 | func (x *ReplyResult) String() string { 42 | return protoimpl.X.MessageStringOf(x) 43 | } 44 | 45 | func (*ReplyResult) ProtoMessage() {} 46 | 47 | func (x *ReplyResult) ProtoReflect() protoreflect.Message { 48 | mi := &file_api_match_v1_match_proto_msgTypes[0] 49 | if protoimpl.UnsafeEnabled && x != nil { 50 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 51 | if ms.LoadMessageInfo() == nil { 52 | ms.StoreMessageInfo(mi) 53 | } 54 | return ms 55 | } 56 | return mi.MessageOf(x) 57 | } 58 | 59 | // Deprecated: Use ReplyResult.ProtoReflect.Descriptor instead. 60 | func (*ReplyResult) Descriptor() ([]byte, []int) { 61 | return file_api_match_v1_match_proto_rawDescGZIP(), []int{0} 62 | } 63 | 64 | func (x *ReplyResult) GetCode() int32 { 65 | if x != nil { 66 | return x.Code 67 | } 68 | return 0 69 | } 70 | 71 | func (x *ReplyResult) GetMsg() string { 72 | if x != nil { 73 | return x.Msg 74 | } 75 | return "" 76 | } 77 | 78 | type Order struct { 79 | state protoimpl.MessageState 80 | sizeCache protoimpl.SizeCache 81 | unknownFields protoimpl.UnknownFields 82 | 83 | Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` // 订单id 84 | UserId int64 `protobuf:"varint,2,opt,name=userId,proto3" json:"userId,omitempty"` // 用户id 85 | Pair string `protobuf:"bytes,3,opt,name=pair,proto3" json:"pair,omitempty"` // 交易对 86 | Price string `protobuf:"bytes,4,opt,name=price,proto3" json:"price,omitempty"` // 价格 87 | Amount string `protobuf:"bytes,5,opt,name=amount,proto3" json:"amount,omitempty"` // 数量 88 | Side string `protobuf:"bytes,6,opt,name=side,proto3" json:"side,omitempty"` // 订单方向 buy/sell 89 | Type string `protobuf:"bytes,7,opt,name=type,proto3" json:"type,omitempty"` // 订单类型 limit/market 90 | TimeInForce string `protobuf:"bytes,8,opt,name=timeInForce,proto3" json:"timeInForce,omitempty"` // 订单有效时间,type为limit时才生效 GTC/IOC/FOK 91 | } 92 | 93 | func (x *Order) Reset() { 94 | *x = Order{} 95 | if protoimpl.UnsafeEnabled { 96 | mi := &file_api_match_v1_match_proto_msgTypes[1] 97 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 98 | ms.StoreMessageInfo(mi) 99 | } 100 | } 101 | 102 | func (x *Order) String() string { 103 | return protoimpl.X.MessageStringOf(x) 104 | } 105 | 106 | func (*Order) ProtoMessage() {} 107 | 108 | func (x *Order) ProtoReflect() protoreflect.Message { 109 | mi := &file_api_match_v1_match_proto_msgTypes[1] 110 | if protoimpl.UnsafeEnabled && x != nil { 111 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 112 | if ms.LoadMessageInfo() == nil { 113 | ms.StoreMessageInfo(mi) 114 | } 115 | return ms 116 | } 117 | return mi.MessageOf(x) 118 | } 119 | 120 | // Deprecated: Use Order.ProtoReflect.Descriptor instead. 121 | func (*Order) Descriptor() ([]byte, []int) { 122 | return file_api_match_v1_match_proto_rawDescGZIP(), []int{1} 123 | } 124 | 125 | func (x *Order) GetId() string { 126 | if x != nil { 127 | return x.Id 128 | } 129 | return "" 130 | } 131 | 132 | func (x *Order) GetUserId() int64 { 133 | if x != nil { 134 | return x.UserId 135 | } 136 | return 0 137 | } 138 | 139 | func (x *Order) GetPair() string { 140 | if x != nil { 141 | return x.Pair 142 | } 143 | return "" 144 | } 145 | 146 | func (x *Order) GetPrice() string { 147 | if x != nil { 148 | return x.Price 149 | } 150 | return "" 151 | } 152 | 153 | func (x *Order) GetAmount() string { 154 | if x != nil { 155 | return x.Amount 156 | } 157 | return "" 158 | } 159 | 160 | func (x *Order) GetSide() string { 161 | if x != nil { 162 | return x.Side 163 | } 164 | return "" 165 | } 166 | 167 | func (x *Order) GetType() string { 168 | if x != nil { 169 | return x.Type 170 | } 171 | return "" 172 | } 173 | 174 | func (x *Order) GetTimeInForce() string { 175 | if x != nil { 176 | return x.TimeInForce 177 | } 178 | return "" 179 | } 180 | 181 | type AddOrderRequest struct { 182 | state protoimpl.MessageState 183 | sizeCache protoimpl.SizeCache 184 | unknownFields protoimpl.UnknownFields 185 | 186 | Order *Order `protobuf:"bytes,1,opt,name=Order,proto3" json:"Order,omitempty"` 187 | } 188 | 189 | func (x *AddOrderRequest) Reset() { 190 | *x = AddOrderRequest{} 191 | if protoimpl.UnsafeEnabled { 192 | mi := &file_api_match_v1_match_proto_msgTypes[2] 193 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 194 | ms.StoreMessageInfo(mi) 195 | } 196 | } 197 | 198 | func (x *AddOrderRequest) String() string { 199 | return protoimpl.X.MessageStringOf(x) 200 | } 201 | 202 | func (*AddOrderRequest) ProtoMessage() {} 203 | 204 | func (x *AddOrderRequest) ProtoReflect() protoreflect.Message { 205 | mi := &file_api_match_v1_match_proto_msgTypes[2] 206 | if protoimpl.UnsafeEnabled && x != nil { 207 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 208 | if ms.LoadMessageInfo() == nil { 209 | ms.StoreMessageInfo(mi) 210 | } 211 | return ms 212 | } 213 | return mi.MessageOf(x) 214 | } 215 | 216 | // Deprecated: Use AddOrderRequest.ProtoReflect.Descriptor instead. 217 | func (*AddOrderRequest) Descriptor() ([]byte, []int) { 218 | return file_api_match_v1_match_proto_rawDescGZIP(), []int{2} 219 | } 220 | 221 | func (x *AddOrderRequest) GetOrder() *Order { 222 | if x != nil { 223 | return x.Order 224 | } 225 | return nil 226 | } 227 | 228 | type AddOrderReply struct { 229 | state protoimpl.MessageState 230 | sizeCache protoimpl.SizeCache 231 | unknownFields protoimpl.UnknownFields 232 | 233 | Result *ReplyResult `protobuf:"bytes,1,opt,name=Result,proto3" json:"Result,omitempty"` 234 | } 235 | 236 | func (x *AddOrderReply) Reset() { 237 | *x = AddOrderReply{} 238 | if protoimpl.UnsafeEnabled { 239 | mi := &file_api_match_v1_match_proto_msgTypes[3] 240 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 241 | ms.StoreMessageInfo(mi) 242 | } 243 | } 244 | 245 | func (x *AddOrderReply) String() string { 246 | return protoimpl.X.MessageStringOf(x) 247 | } 248 | 249 | func (*AddOrderReply) ProtoMessage() {} 250 | 251 | func (x *AddOrderReply) ProtoReflect() protoreflect.Message { 252 | mi := &file_api_match_v1_match_proto_msgTypes[3] 253 | if protoimpl.UnsafeEnabled && x != nil { 254 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 255 | if ms.LoadMessageInfo() == nil { 256 | ms.StoreMessageInfo(mi) 257 | } 258 | return ms 259 | } 260 | return mi.MessageOf(x) 261 | } 262 | 263 | // Deprecated: Use AddOrderReply.ProtoReflect.Descriptor instead. 264 | func (*AddOrderReply) Descriptor() ([]byte, []int) { 265 | return file_api_match_v1_match_proto_rawDescGZIP(), []int{3} 266 | } 267 | 268 | func (x *AddOrderReply) GetResult() *ReplyResult { 269 | if x != nil { 270 | return x.Result 271 | } 272 | return nil 273 | } 274 | 275 | type CancelOrderRequest struct { 276 | state protoimpl.MessageState 277 | sizeCache protoimpl.SizeCache 278 | unknownFields protoimpl.UnknownFields 279 | 280 | Pair string `protobuf:"bytes,1,opt,name=Pair,proto3" json:"Pair,omitempty"` 281 | Id string `protobuf:"bytes,2,opt,name=Id,proto3" json:"Id,omitempty"` 282 | } 283 | 284 | func (x *CancelOrderRequest) Reset() { 285 | *x = CancelOrderRequest{} 286 | if protoimpl.UnsafeEnabled { 287 | mi := &file_api_match_v1_match_proto_msgTypes[4] 288 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 289 | ms.StoreMessageInfo(mi) 290 | } 291 | } 292 | 293 | func (x *CancelOrderRequest) String() string { 294 | return protoimpl.X.MessageStringOf(x) 295 | } 296 | 297 | func (*CancelOrderRequest) ProtoMessage() {} 298 | 299 | func (x *CancelOrderRequest) ProtoReflect() protoreflect.Message { 300 | mi := &file_api_match_v1_match_proto_msgTypes[4] 301 | if protoimpl.UnsafeEnabled && x != nil { 302 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 303 | if ms.LoadMessageInfo() == nil { 304 | ms.StoreMessageInfo(mi) 305 | } 306 | return ms 307 | } 308 | return mi.MessageOf(x) 309 | } 310 | 311 | // Deprecated: Use CancelOrderRequest.ProtoReflect.Descriptor instead. 312 | func (*CancelOrderRequest) Descriptor() ([]byte, []int) { 313 | return file_api_match_v1_match_proto_rawDescGZIP(), []int{4} 314 | } 315 | 316 | func (x *CancelOrderRequest) GetPair() string { 317 | if x != nil { 318 | return x.Pair 319 | } 320 | return "" 321 | } 322 | 323 | func (x *CancelOrderRequest) GetId() string { 324 | if x != nil { 325 | return x.Id 326 | } 327 | return "" 328 | } 329 | 330 | type CancelOrderReply struct { 331 | state protoimpl.MessageState 332 | sizeCache protoimpl.SizeCache 333 | unknownFields protoimpl.UnknownFields 334 | 335 | Result *ReplyResult `protobuf:"bytes,1,opt,name=Result,proto3" json:"Result,omitempty"` 336 | } 337 | 338 | func (x *CancelOrderReply) Reset() { 339 | *x = CancelOrderReply{} 340 | if protoimpl.UnsafeEnabled { 341 | mi := &file_api_match_v1_match_proto_msgTypes[5] 342 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 343 | ms.StoreMessageInfo(mi) 344 | } 345 | } 346 | 347 | func (x *CancelOrderReply) String() string { 348 | return protoimpl.X.MessageStringOf(x) 349 | } 350 | 351 | func (*CancelOrderReply) ProtoMessage() {} 352 | 353 | func (x *CancelOrderReply) ProtoReflect() protoreflect.Message { 354 | mi := &file_api_match_v1_match_proto_msgTypes[5] 355 | if protoimpl.UnsafeEnabled && x != nil { 356 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 357 | if ms.LoadMessageInfo() == nil { 358 | ms.StoreMessageInfo(mi) 359 | } 360 | return ms 361 | } 362 | return mi.MessageOf(x) 363 | } 364 | 365 | // Deprecated: Use CancelOrderReply.ProtoReflect.Descriptor instead. 366 | func (*CancelOrderReply) Descriptor() ([]byte, []int) { 367 | return file_api_match_v1_match_proto_rawDescGZIP(), []int{5} 368 | } 369 | 370 | func (x *CancelOrderReply) GetResult() *ReplyResult { 371 | if x != nil { 372 | return x.Result 373 | } 374 | return nil 375 | } 376 | 377 | var File_api_match_v1_match_proto protoreflect.FileDescriptor 378 | 379 | var file_api_match_v1_match_proto_rawDesc = []byte{ 380 | 0x0a, 0x18, 0x61, 0x70, 0x69, 0x2f, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x2f, 0x76, 0x31, 0x2f, 0x6d, 381 | 0x61, 0x74, 0x63, 0x68, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0c, 0x61, 0x70, 0x69, 0x2e, 382 | 0x6d, 0x61, 0x74, 0x63, 0x68, 0x2e, 0x76, 0x31, 0x22, 0x33, 0x0a, 0x0b, 0x52, 0x65, 0x70, 0x6c, 383 | 0x79, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x43, 0x6f, 0x64, 0x65, 0x18, 384 | 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x4d, 385 | 0x73, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x4d, 0x73, 0x67, 0x22, 0xbb, 0x01, 386 | 0x0a, 0x05, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 387 | 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 388 | 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 389 | 0x12, 0x0a, 0x04, 0x70, 0x61, 0x69, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 390 | 0x61, 0x69, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 391 | 0x28, 0x09, 0x52, 0x05, 0x70, 0x72, 0x69, 0x63, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 392 | 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 393 | 0x74, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 394 | 0x04, 0x73, 0x69, 0x64, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 395 | 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x74, 0x69, 0x6d, 396 | 0x65, 0x49, 0x6e, 0x46, 0x6f, 0x72, 0x63, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 397 | 0x74, 0x69, 0x6d, 0x65, 0x49, 0x6e, 0x46, 0x6f, 0x72, 0x63, 0x65, 0x22, 0x3c, 0x0a, 0x0f, 0x41, 398 | 0x64, 0x64, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x29, 399 | 0x0a, 0x05, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 400 | 0x61, 0x70, 0x69, 0x2e, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x72, 0x64, 401 | 0x65, 0x72, 0x52, 0x05, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x22, 0x42, 0x0a, 0x0d, 0x41, 0x64, 0x64, 402 | 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x31, 0x0a, 0x06, 0x52, 0x65, 403 | 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x61, 0x70, 0x69, 404 | 0x2e, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x52, 405 | 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x38, 0x0a, 406 | 0x12, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 407 | 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x61, 0x69, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 408 | 0x09, 0x52, 0x04, 0x50, 0x61, 0x69, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x64, 0x18, 0x02, 0x20, 409 | 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x64, 0x22, 0x45, 0x0a, 0x10, 0x43, 0x61, 0x6e, 0x63, 0x65, 410 | 0x6c, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x31, 0x0a, 0x06, 0x52, 411 | 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x61, 0x70, 412 | 0x69, 0x2e, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x70, 0x6c, 0x79, 413 | 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x32, 0xab, 414 | 0x01, 0x0a, 0x0c, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 415 | 0x48, 0x0a, 0x08, 0x41, 0x64, 0x64, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x1d, 0x2e, 0x61, 0x70, 416 | 0x69, 0x2e, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x64, 0x4f, 0x72, 417 | 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x61, 0x70, 0x69, 418 | 0x2e, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x64, 0x4f, 0x72, 0x64, 419 | 0x65, 0x72, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x51, 0x0a, 0x0b, 0x43, 0x61, 0x6e, 420 | 0x63, 0x65, 0x6c, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x20, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x6d, 421 | 0x61, 0x74, 0x63, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x4f, 0x72, 422 | 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x61, 0x70, 0x69, 423 | 0x2e, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 424 | 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x42, 0x21, 0x0a, 0x0c, 425 | 0x61, 0x70, 0x69, 0x2e, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x2e, 0x76, 0x31, 0x50, 0x01, 0x5a, 0x0f, 426 | 0x61, 0x70, 0x69, 0x2f, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x2f, 0x76, 0x31, 0x3b, 0x76, 0x31, 0x62, 427 | 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 428 | } 429 | 430 | var ( 431 | file_api_match_v1_match_proto_rawDescOnce sync.Once 432 | file_api_match_v1_match_proto_rawDescData = file_api_match_v1_match_proto_rawDesc 433 | ) 434 | 435 | func file_api_match_v1_match_proto_rawDescGZIP() []byte { 436 | file_api_match_v1_match_proto_rawDescOnce.Do(func() { 437 | file_api_match_v1_match_proto_rawDescData = protoimpl.X.CompressGZIP(file_api_match_v1_match_proto_rawDescData) 438 | }) 439 | return file_api_match_v1_match_proto_rawDescData 440 | } 441 | 442 | var file_api_match_v1_match_proto_msgTypes = make([]protoimpl.MessageInfo, 6) 443 | var file_api_match_v1_match_proto_goTypes = []interface{}{ 444 | (*ReplyResult)(nil), // 0: api.match.v1.ReplyResult 445 | (*Order)(nil), // 1: api.match.v1.Order 446 | (*AddOrderRequest)(nil), // 2: api.match.v1.AddOrderRequest 447 | (*AddOrderReply)(nil), // 3: api.match.v1.AddOrderReply 448 | (*CancelOrderRequest)(nil), // 4: api.match.v1.CancelOrderRequest 449 | (*CancelOrderReply)(nil), // 5: api.match.v1.CancelOrderReply 450 | } 451 | var file_api_match_v1_match_proto_depIdxs = []int32{ 452 | 1, // 0: api.match.v1.AddOrderRequest.Order:type_name -> api.match.v1.Order 453 | 0, // 1: api.match.v1.AddOrderReply.Result:type_name -> api.match.v1.ReplyResult 454 | 0, // 2: api.match.v1.CancelOrderReply.Result:type_name -> api.match.v1.ReplyResult 455 | 2, // 3: api.match.v1.MatchService.AddOrder:input_type -> api.match.v1.AddOrderRequest 456 | 4, // 4: api.match.v1.MatchService.CancelOrder:input_type -> api.match.v1.CancelOrderRequest 457 | 3, // 5: api.match.v1.MatchService.AddOrder:output_type -> api.match.v1.AddOrderReply 458 | 5, // 6: api.match.v1.MatchService.CancelOrder:output_type -> api.match.v1.CancelOrderReply 459 | 5, // [5:7] is the sub-list for method output_type 460 | 3, // [3:5] is the sub-list for method input_type 461 | 3, // [3:3] is the sub-list for extension type_name 462 | 3, // [3:3] is the sub-list for extension extendee 463 | 0, // [0:3] is the sub-list for field type_name 464 | } 465 | 466 | func init() { file_api_match_v1_match_proto_init() } 467 | func file_api_match_v1_match_proto_init() { 468 | if File_api_match_v1_match_proto != nil { 469 | return 470 | } 471 | if !protoimpl.UnsafeEnabled { 472 | file_api_match_v1_match_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 473 | switch v := v.(*ReplyResult); i { 474 | case 0: 475 | return &v.state 476 | case 1: 477 | return &v.sizeCache 478 | case 2: 479 | return &v.unknownFields 480 | default: 481 | return nil 482 | } 483 | } 484 | file_api_match_v1_match_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { 485 | switch v := v.(*Order); i { 486 | case 0: 487 | return &v.state 488 | case 1: 489 | return &v.sizeCache 490 | case 2: 491 | return &v.unknownFields 492 | default: 493 | return nil 494 | } 495 | } 496 | file_api_match_v1_match_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { 497 | switch v := v.(*AddOrderRequest); i { 498 | case 0: 499 | return &v.state 500 | case 1: 501 | return &v.sizeCache 502 | case 2: 503 | return &v.unknownFields 504 | default: 505 | return nil 506 | } 507 | } 508 | file_api_match_v1_match_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { 509 | switch v := v.(*AddOrderReply); i { 510 | case 0: 511 | return &v.state 512 | case 1: 513 | return &v.sizeCache 514 | case 2: 515 | return &v.unknownFields 516 | default: 517 | return nil 518 | } 519 | } 520 | file_api_match_v1_match_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { 521 | switch v := v.(*CancelOrderRequest); i { 522 | case 0: 523 | return &v.state 524 | case 1: 525 | return &v.sizeCache 526 | case 2: 527 | return &v.unknownFields 528 | default: 529 | return nil 530 | } 531 | } 532 | file_api_match_v1_match_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { 533 | switch v := v.(*CancelOrderReply); i { 534 | case 0: 535 | return &v.state 536 | case 1: 537 | return &v.sizeCache 538 | case 2: 539 | return &v.unknownFields 540 | default: 541 | return nil 542 | } 543 | } 544 | } 545 | type x struct{} 546 | out := protoimpl.TypeBuilder{ 547 | File: protoimpl.DescBuilder{ 548 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 549 | RawDescriptor: file_api_match_v1_match_proto_rawDesc, 550 | NumEnums: 0, 551 | NumMessages: 6, 552 | NumExtensions: 0, 553 | NumServices: 1, 554 | }, 555 | GoTypes: file_api_match_v1_match_proto_goTypes, 556 | DependencyIndexes: file_api_match_v1_match_proto_depIdxs, 557 | MessageInfos: file_api_match_v1_match_proto_msgTypes, 558 | }.Build() 559 | File_api_match_v1_match_proto = out.File 560 | file_api_match_v1_match_proto_rawDesc = nil 561 | file_api_match_v1_match_proto_goTypes = nil 562 | file_api_match_v1_match_proto_depIdxs = nil 563 | } 564 | -------------------------------------------------------------------------------- /api/match/v1/match.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package api.match.v1; 4 | 5 | option go_package = "api/match/v1;v1"; 6 | option java_multiple_files = true; 7 | option java_package = "api.match.v1"; 8 | 9 | service MatchService { 10 | rpc AddOrder(AddOrderRequest)returns(AddOrderReply){} 11 | rpc CancelOrder(CancelOrderRequest)returns(CancelOrderReply){} 12 | } 13 | 14 | message ReplyResult{ 15 | int32 Code = 1; 16 | string Msg = 2; 17 | } 18 | 19 | message Order { 20 | string id = 1;// 订单id 21 | int64 userId = 2;// 用户id 22 | string pair = 3;// 交易对 23 | string price = 4;// 价格 24 | string amount = 5;// 数量 25 | string side = 6;// 订单方向 buy/sell 26 | string type = 7;// 订单类型 limit/market 27 | string timeInForce = 8;// 订单有效时间,type为limit时才生效 GTC/IOC/FOK 28 | } 29 | 30 | message AddOrderRequest{ 31 | Order Order = 1; 32 | } 33 | 34 | message AddOrderReply{ 35 | ReplyResult Result = 1; 36 | } 37 | 38 | message CancelOrderRequest{ 39 | string Pair = 1; 40 | string Id = 2; 41 | } 42 | 43 | message CancelOrderReply{ 44 | ReplyResult Result = 1; 45 | } -------------------------------------------------------------------------------- /api/match/v1/match_grpc.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go-grpc. DO NOT EDIT. 2 | // versions: 3 | // - protoc-gen-go-grpc v1.2.0 4 | // - protoc v3.19.4 5 | // source: api/match/v1/match.proto 6 | 7 | package v1 8 | 9 | import ( 10 | context "context" 11 | grpc "google.golang.org/grpc" 12 | codes "google.golang.org/grpc/codes" 13 | status "google.golang.org/grpc/status" 14 | ) 15 | 16 | // This is a compile-time assertion to ensure that this generated file 17 | // is compatible with the grpc package it is being compiled against. 18 | // Requires gRPC-Go v1.32.0 or later. 19 | const _ = grpc.SupportPackageIsVersion7 20 | 21 | // MatchServiceClient is the client API for MatchService service. 22 | // 23 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. 24 | type MatchServiceClient interface { 25 | AddOrder(ctx context.Context, in *AddOrderRequest, opts ...grpc.CallOption) (*AddOrderReply, error) 26 | CancelOrder(ctx context.Context, in *CancelOrderRequest, opts ...grpc.CallOption) (*CancelOrderReply, error) 27 | } 28 | 29 | type matchServiceClient struct { 30 | cc grpc.ClientConnInterface 31 | } 32 | 33 | func NewMatchServiceClient(cc grpc.ClientConnInterface) MatchServiceClient { 34 | return &matchServiceClient{cc} 35 | } 36 | 37 | func (c *matchServiceClient) AddOrder(ctx context.Context, in *AddOrderRequest, opts ...grpc.CallOption) (*AddOrderReply, error) { 38 | out := new(AddOrderReply) 39 | err := c.cc.Invoke(ctx, "/api.match.v1.MatchService/AddOrder", in, out, opts...) 40 | if err != nil { 41 | return nil, err 42 | } 43 | return out, nil 44 | } 45 | 46 | func (c *matchServiceClient) CancelOrder(ctx context.Context, in *CancelOrderRequest, opts ...grpc.CallOption) (*CancelOrderReply, error) { 47 | out := new(CancelOrderReply) 48 | err := c.cc.Invoke(ctx, "/api.match.v1.MatchService/CancelOrder", in, out, opts...) 49 | if err != nil { 50 | return nil, err 51 | } 52 | return out, nil 53 | } 54 | 55 | // MatchServiceServer is the server API for MatchService service. 56 | // All implementations must embed UnimplementedMatchServiceServer 57 | // for forward compatibility 58 | type MatchServiceServer interface { 59 | AddOrder(context.Context, *AddOrderRequest) (*AddOrderReply, error) 60 | CancelOrder(context.Context, *CancelOrderRequest) (*CancelOrderReply, error) 61 | mustEmbedUnimplementedMatchServiceServer() 62 | } 63 | 64 | // UnimplementedMatchServiceServer must be embedded to have forward compatible implementations. 65 | type UnimplementedMatchServiceServer struct { 66 | } 67 | 68 | func (UnimplementedMatchServiceServer) AddOrder(context.Context, *AddOrderRequest) (*AddOrderReply, error) { 69 | return nil, status.Errorf(codes.Unimplemented, "method AddOrder not implemented") 70 | } 71 | func (UnimplementedMatchServiceServer) CancelOrder(context.Context, *CancelOrderRequest) (*CancelOrderReply, error) { 72 | return nil, status.Errorf(codes.Unimplemented, "method CancelOrder not implemented") 73 | } 74 | func (UnimplementedMatchServiceServer) mustEmbedUnimplementedMatchServiceServer() {} 75 | 76 | // UnsafeMatchServiceServer may be embedded to opt out of forward compatibility for this service. 77 | // Use of this interface is not recommended, as added methods to MatchServiceServer will 78 | // result in compilation errors. 79 | type UnsafeMatchServiceServer interface { 80 | mustEmbedUnimplementedMatchServiceServer() 81 | } 82 | 83 | func RegisterMatchServiceServer(s grpc.ServiceRegistrar, srv MatchServiceServer) { 84 | s.RegisterService(&MatchService_ServiceDesc, srv) 85 | } 86 | 87 | func _MatchService_AddOrder_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 88 | in := new(AddOrderRequest) 89 | if err := dec(in); err != nil { 90 | return nil, err 91 | } 92 | if interceptor == nil { 93 | return srv.(MatchServiceServer).AddOrder(ctx, in) 94 | } 95 | info := &grpc.UnaryServerInfo{ 96 | Server: srv, 97 | FullMethod: "/api.match.v1.MatchService/AddOrder", 98 | } 99 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 100 | return srv.(MatchServiceServer).AddOrder(ctx, req.(*AddOrderRequest)) 101 | } 102 | return interceptor(ctx, in, info, handler) 103 | } 104 | 105 | func _MatchService_CancelOrder_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 106 | in := new(CancelOrderRequest) 107 | if err := dec(in); err != nil { 108 | return nil, err 109 | } 110 | if interceptor == nil { 111 | return srv.(MatchServiceServer).CancelOrder(ctx, in) 112 | } 113 | info := &grpc.UnaryServerInfo{ 114 | Server: srv, 115 | FullMethod: "/api.match.v1.MatchService/CancelOrder", 116 | } 117 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 118 | return srv.(MatchServiceServer).CancelOrder(ctx, req.(*CancelOrderRequest)) 119 | } 120 | return interceptor(ctx, in, info, handler) 121 | } 122 | 123 | // MatchService_ServiceDesc is the grpc.ServiceDesc for MatchService service. 124 | // It's only intended for direct use with grpc.RegisterService, 125 | // and not to be introspected or modified (even as a copy) 126 | var MatchService_ServiceDesc = grpc.ServiceDesc{ 127 | ServiceName: "api.match.v1.MatchService", 128 | HandlerType: (*MatchServiceServer)(nil), 129 | Methods: []grpc.MethodDesc{ 130 | { 131 | MethodName: "AddOrder", 132 | Handler: _MatchService_AddOrder_Handler, 133 | }, 134 | { 135 | MethodName: "CancelOrder", 136 | Handler: _MatchService_CancelOrder_Handler, 137 | }, 138 | }, 139 | Streams: []grpc.StreamDesc{}, 140 | Metadata: "api/match/v1/match.proto", 141 | } 142 | -------------------------------------------------------------------------------- /cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "google.golang.org/grpc" 5 | pb "lightning-engine/api/match/v1" 6 | "lightning-engine/cmd/match" 7 | "log" 8 | "net" 9 | ) 10 | 11 | func main() { 12 | pairs := []string{"BTC-USDT", "ETH-USDT"} 13 | app, cleanup, err := match.WireApp(pairs) 14 | if err != nil { 15 | panic(err) 16 | } 17 | defer cleanup() 18 | 19 | go app.SysSignalHandle.Begin() 20 | 21 | lis, err := net.Listen("tcp", ":8080") 22 | if err != nil { 23 | log.Fatalf("failed to listen: %v", err) 24 | } 25 | grpcServer := grpc.NewServer() 26 | pb.RegisterMatchServiceServer(grpcServer, app.Server) 27 | log.Println("[RPC] :8080") 28 | grpcServer.Serve(lis) 29 | select {} 30 | } 31 | -------------------------------------------------------------------------------- /cmd/match/wire.go: -------------------------------------------------------------------------------- 1 | //go:build wireinject 2 | // +build wireinject 3 | 4 | package match 5 | 6 | import ( 7 | "github.com/google/wire" 8 | "lightning-engine/internal/match" 9 | "lightning-engine/internal/server" 10 | "lightning-engine/internal/status" 11 | "lightning-engine/mq" 12 | ) 13 | 14 | func wireApp(pair []string) (*App, func(), error) { 15 | panic(wire.Build(match.ProviderSet, server.ProviderSet, status.ProviderSet, mq.ProviderSet, newApp)) 16 | } 17 | -------------------------------------------------------------------------------- /cmd/match/wire_gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by Wire. DO NOT EDIT. 2 | 3 | //go:generate go run github.com/google/wire/cmd/wire 4 | //go:build !wireinject 5 | // +build !wireinject 6 | 7 | package match 8 | 9 | import ( 10 | "lightning-engine/internal/match" 11 | "lightning-engine/internal/server" 12 | "lightning-engine/internal/status" 13 | "lightning-engine/mq" 14 | ) 15 | 16 | type App struct { 17 | status *status.Status 18 | Server *server.Server 19 | SysSignalHandle *status.SysSignalHandle 20 | } 21 | 22 | func newApp(st *status.Status, se *server.Server, ss *status.SysSignalHandle) *App { 23 | return &App{ 24 | status: st, 25 | Server: se, 26 | SysSignalHandle: ss, 27 | } 28 | } 29 | 30 | // Injectors from wire.go: 31 | func WireApp(pair []string) (*App, func(), error) { 32 | statusStatus := status.NewStatus() 33 | imq := mq.NewYourMq() 34 | matchPool, err := match.NewMatchPool(statusStatus, pair, imq) 35 | if err != nil { 36 | return nil, nil, err 37 | } 38 | serverServer := server.NewServer(statusStatus, matchPool) 39 | sysSignalHandle := status.NewSysSignalHandle(statusStatus) 40 | mainApp := newApp(statusStatus, serverServer, sysSignalHandle) 41 | return mainApp, func() { 42 | }, nil 43 | } 44 | -------------------------------------------------------------------------------- /example/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "google.golang.org/grpc" 7 | "google.golang.org/grpc/credentials/insecure" 8 | pb "lightning-engine/api/match/v1" 9 | "lightning-engine/cmd/match" 10 | "log" 11 | "net" 12 | "time" 13 | ) 14 | 15 | func main() { 16 | // run server 17 | go server() 18 | // run client 19 | clientServer() 20 | } 21 | 22 | func server() { 23 | 24 | pairs := []string{"BTC-USDT", "ETH-USDT"} 25 | app, cleanup, err := match.WireApp(pairs) 26 | if err != nil { 27 | panic(err) 28 | } 29 | defer cleanup() 30 | 31 | go app.SysSignalHandle.Begin() 32 | 33 | lis, err := net.Listen("tcp", ":8080") 34 | if err != nil { 35 | log.Fatalf("failed to listen: %v", err) 36 | } 37 | grpcServer := grpc.NewServer() 38 | pb.RegisterMatchServiceServer(grpcServer, app.Server) 39 | log.Println("[RPC] :8080") 40 | grpcServer.Serve(lis) 41 | select {} 42 | } 43 | 44 | func clientServer() { 45 | conn, _ := grpc.Dial("localhost:8080", grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock()) 46 | client := pb.NewMatchServiceClient(conn) 47 | 48 | for { 49 | sendBuy(client) 50 | sendSell(client) 51 | time.Sleep(1 * time.Second) 52 | } 53 | } 54 | 55 | func sendBuy(client pb.MatchServiceClient) { 56 | req := &pb.AddOrderRequest{Order: &pb.Order{ 57 | Id: "1", 58 | UserId: 2, 59 | Pair: "BTC-USDT", 60 | Price: "21000", 61 | Amount: "2", 62 | Side: "buy", 63 | Type: "limit", 64 | TimeInForce: "GTC", 65 | }} 66 | reply, err := client.AddOrder(context.Background(), req) 67 | fmt.Println("send buy", reply, err) 68 | } 69 | 70 | func sendSell(client pb.MatchServiceClient) { 71 | req := &pb.AddOrderRequest{Order: &pb.Order{ 72 | Id: "2", 73 | UserId: 2, 74 | Pair: "BTC-USDT", 75 | Price: "21000", 76 | Amount: "1", 77 | Side: "sell", 78 | Type: "limit", 79 | TimeInForce: "GTC", 80 | }} 81 | reply, err := client.AddOrder(context.Background(), req) 82 | fmt.Println("send sell", reply, err) 83 | } 84 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module lightning-engine 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/google/wire v0.5.0 7 | github.com/shopspring/decimal v1.3.1 8 | google.golang.org/grpc v1.45.0 9 | google.golang.org/protobuf v1.26.0 10 | ) 11 | 12 | require ( 13 | github.com/golang/protobuf v1.5.2 // indirect 14 | golang.org/x/net v0.0.0-20200822124328-c89045814202 // indirect 15 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd // indirect 16 | golang.org/x/text v0.3.0 // indirect 17 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect 18 | ) 19 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 4 | github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= 5 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 6 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 7 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 8 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 9 | github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 10 | github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= 11 | github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 12 | github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 13 | github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 14 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 15 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 16 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 17 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 18 | github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= 19 | github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= 20 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 21 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 22 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 23 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 24 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 25 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 26 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 27 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 28 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 29 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 30 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 31 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 32 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 33 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 34 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 35 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 36 | github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= 37 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 38 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 39 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 40 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 41 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 42 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 43 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 44 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 45 | github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= 46 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 47 | github.com/google/wire v0.5.0 h1:I7ELFeVBr3yfPIcc8+MWvrjk+3VjbcSzoXm3JVa+jD8= 48 | github.com/google/wire v0.5.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU= 49 | github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= 50 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 51 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 52 | github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= 53 | github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= 54 | github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= 55 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 56 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 57 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 58 | go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= 59 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 60 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 61 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 62 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 63 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 64 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 65 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 66 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 67 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 68 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 69 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 70 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 71 | golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= 72 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 73 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 74 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 75 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 76 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 77 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 78 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 79 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 80 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 81 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 82 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= 83 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 84 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 85 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 86 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 87 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 88 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 89 | golang.org/x/tools v0.0.0-20190422233926-fe54fb35175b/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 90 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 91 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 92 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 93 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 94 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 95 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 96 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 97 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 98 | google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 99 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= 100 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 101 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 102 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 103 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 104 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 105 | google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= 106 | google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= 107 | google.golang.org/grpc v1.45.0 h1:NEpgUqV3Z+ZjkqMsxMg11IaDrXY4RY6CQukSGK0uI1M= 108 | google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= 109 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 110 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 111 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 112 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 113 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 114 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 115 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 116 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 117 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 118 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 119 | google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= 120 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 121 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 122 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 123 | gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 124 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 125 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 126 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 127 | -------------------------------------------------------------------------------- /internal/match/match_test.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "fmt" 5 | "github.com/shopspring/decimal" 6 | "lightning-engine/internal/status" 7 | "lightning-engine/models" 8 | "lightning-engine/mq" 9 | "lightning-engine/utils" 10 | "strconv" 11 | "testing" 12 | ) 13 | 14 | var ( 15 | mp *MatchPool 16 | pairs = []string{"BTC/USDT", "ETH/USDT"} 17 | pair = "BTC/USDT" 18 | ) 19 | 20 | func TestMain(m *testing.M) { 21 | mp, _ = NewMatchPool(status.NewStatus(), pairs, &mq.YourMq{}) 22 | m.Run() 23 | } 24 | 25 | func PrintMP() { 26 | fmt.Println("************** orderbook **************") 27 | // ask 28 | ask := make([]string, 0) 29 | first := mp.pool[pair].ask.First() 30 | for first != nil { 31 | ask = append(ask, fmt.Sprintf("%s\t\t%s\n", first.Score(), first.Value().GetAmount())) 32 | first = first.Next(0) 33 | } 34 | for i := len(ask) - 1; i >= 0; i-- { 35 | fmt.Print(ask[i]) 36 | } 37 | fmt.Println("--------------------") 38 | 39 | // bid 40 | first = mp.pool[pair].bid.First() 41 | for first != nil { 42 | fmt.Printf("%s\t\t%s\n", first.Score(), first.Value().GetAmount()) 43 | first = first.Next(0) 44 | } 45 | } 46 | func TestOrderbookGTC(t *testing.T) { 47 | TestOrderbook_AddBidLimitGTC(t) 48 | TestOrderbook_AddAskLimitGTC(t) 49 | PrintMP() 50 | } 51 | func TestOrderbookIOC(t *testing.T) { 52 | //TestOrderbook_AddAskLimitGTC(t) 53 | //TestOrderbook_AddBidLimitIOC(t) 54 | 55 | TestOrderbook_AddBidLimitGTC(t) 56 | TestOrderbook_AddAskLimitIOC(t) 57 | PrintMP() 58 | } 59 | func TestOrderbookFOK(t *testing.T) { 60 | //TestOrderbook_AddAskLimitGTC(t) 61 | //TestOrderbook_AddBidLimitFOK(t) 62 | 63 | TestOrderbook_AddBidLimitGTC(t) 64 | TestOrderbook_AddAskLimitFOK(t) 65 | PrintMP() 66 | } 67 | func TestOrderbookMarket(t *testing.T) { 68 | //TestOrderbook_AddAskLimitGTC(t) 69 | //TestOrderbook_AddBidMarket(t) 70 | 71 | TestOrderbook_AddBidLimitGTC(t) 72 | TestOrderbook_AddAskMarket(t) 73 | PrintMP() 74 | } 75 | func TestOrderbook_AddBidLimitGTC(t *testing.T) { 76 | for i := 100; i < 120; i++ { 77 | order := &models.Order{ 78 | Id: strconv.Itoa(i), 79 | UserId: 1, 80 | Pair: pair, 81 | Price: decimal.NewFromInt(int64(i)), 82 | Amount: decimal.NewFromInt(int64(i)), 83 | Side: models.Buy, 84 | Type: models.Limit, 85 | TimeInForce: models.TimeInForceGTC, 86 | } 87 | err := mp.pool[pair].Add(order) 88 | if err != nil { 89 | t.Error(err) 90 | } 91 | } 92 | } 93 | func TestOrderbook_AddAskLimitGTC(t *testing.T) { 94 | for i := 110; i < 141; i++ { 95 | order := &models.Order{ 96 | Id: strconv.Itoa(i), 97 | UserId: 2, 98 | Pair: pair, 99 | Price: decimal.NewFromInt(int64(i)), 100 | Amount: decimal.NewFromInt(int64(i)), 101 | Side: models.Sell, 102 | Type: models.Limit, 103 | TimeInForce: models.TimeInForceGTC, 104 | } 105 | err := mp.pool[pair].Add(order) 106 | if err != nil { 107 | t.Error(err) 108 | } 109 | } 110 | } 111 | func TestOrderbook_AddBidLimitIOC(t *testing.T) { 112 | order := &models.Order{ 113 | Id: "100", 114 | UserId: 1, 115 | Pair: pair, 116 | Price: decimal.NewFromInt(500), 117 | Amount: decimal.NewFromInt(110), 118 | Side: models.Buy, 119 | Type: models.Limit, 120 | TimeInForce: models.TimeInForceIOC, 121 | } 122 | err := mp.pool[pair].Add(order) 123 | if err != nil { 124 | t.Error(err) 125 | } 126 | } 127 | func TestOrderbook_AddAskLimitIOC(t *testing.T) { 128 | order := &models.Order{ 129 | Id: "100", 130 | UserId: 1, 131 | Pair: pair, 132 | Price: decimal.NewFromInt(110), 133 | Amount: decimal.NewFromInt(110000), 134 | Side: models.Sell, 135 | Type: models.Limit, 136 | TimeInForce: models.TimeInForceIOC, 137 | } 138 | err := mp.pool[pair].Add(order) 139 | if err != nil { 140 | t.Error(err) 141 | } 142 | } 143 | func TestOrderbook_AddBidLimitFOK(t *testing.T) { 144 | order := &models.Order{ 145 | Id: "100", 146 | UserId: 1, 147 | Pair: pair, 148 | Price: decimal.NewFromInt(110), 149 | Amount: decimal.NewFromInt(111), 150 | Side: models.Buy, 151 | Type: models.Limit, 152 | TimeInForce: models.TimeInForceFOK, 153 | } 154 | err := mp.pool[pair].Add(order) 155 | if err != nil { 156 | t.Error(err) 157 | } 158 | } 159 | func TestOrderbook_AddAskLimitFOK(t *testing.T) { 160 | order := &models.Order{ 161 | Id: "100", 162 | UserId: 1, 163 | Pair: pair, 164 | Price: decimal.NewFromInt(110), 165 | Amount: decimal.NewFromInt(120), 166 | Side: models.Sell, 167 | Type: models.Limit, 168 | TimeInForce: models.TimeInForceFOK, 169 | } 170 | err := mp.pool[pair].Add(order) 171 | if err != nil { 172 | t.Error(err) 173 | } 174 | } 175 | func TestOrderbook_AddBidMarket(t *testing.T) { 176 | order := &models.Order{ 177 | Id: "100", 178 | UserId: 1, 179 | Pair: pair, 180 | Price: decimal.Zero, 181 | Amount: decimal.NewFromInt(10), 182 | Side: models.Buy, 183 | Type: models.Market, 184 | TimeInForce: "", 185 | } 186 | err := mp.pool[pair].Add(order) 187 | if err != nil { 188 | t.Error(err) 189 | } 190 | } 191 | func TestOrderbook_AddAskMarket(t *testing.T) { 192 | order := &models.Order{ 193 | Id: "100", 194 | UserId: 1, 195 | Pair: pair, 196 | Price: decimal.Zero, 197 | Amount: decimal.NewFromInt(10), 198 | Side: models.Sell, 199 | Type: models.Market, 200 | TimeInForce: "", 201 | } 202 | err := mp.pool[pair].Add(order) 203 | if err != nil { 204 | t.Error(err) 205 | } 206 | } 207 | func TestOrderbook_Cancel(t *testing.T) { 208 | for i := 100; i < 120; i++ { 209 | order := &models.Order{ 210 | Id: strconv.Itoa(i), 211 | UserId: 1, 212 | Pair: pair, 213 | Price: decimal.NewFromInt(int64(i)), 214 | Amount: decimal.NewFromInt(int64(i)), 215 | Side: models.Buy, 216 | Type: models.Limit, 217 | TimeInForce: models.TimeInForceGTC, 218 | } 219 | err := mp.pool[pair].Add(order) 220 | if err != nil { 221 | t.Error(err) 222 | } 223 | } 224 | 225 | err := mp.pool[pair].Cancel("100") 226 | if err != nil { 227 | t.Error(err) 228 | } 229 | PrintMP() 230 | } 231 | 232 | // 测试撮合性能,需注释推送成交(改成了异步,所以时间很短,但不是真实时间) 233 | func TestOrderbook_TS_ADD_MATCH(t *testing.T) { 234 | size := 100000 235 | orders := make([]models.Order, size) 236 | for i := 0; i < size; i++ { 237 | order := models.Order{ 238 | Id: strconv.Itoa(i), 239 | UserId: 1, 240 | Pair: pair, 241 | Price: decimal.NewFromInt(int64(i)), 242 | Amount: decimal.NewFromInt(100000), 243 | Side: models.Buy, 244 | Type: models.Limit, 245 | TimeInForce: models.TimeInForceGTC, 246 | } 247 | orders[i] = order 248 | } 249 | begin := utils.NowUnixMilli() 250 | for _, order := range orders { 251 | mp.pool[pair].Add(&order) 252 | } 253 | ts1 := utils.NowUnixMilli() - begin 254 | t.Logf("插入%d条数据: %dms", size, ts1) 255 | 256 | for i := 0; i < size; i++ { 257 | order := models.Order{ 258 | Id: strconv.Itoa(i), 259 | UserId: 1, 260 | Pair: pair, 261 | Price: decimal.NewFromInt(1), 262 | Amount: decimal.NewFromFloat(100000), 263 | Side: models.Sell, 264 | Type: models.Limit, 265 | TimeInForce: models.TimeInForceGTC, 266 | } 267 | orders[i] = order 268 | } 269 | begin = utils.NowUnixMilli() 270 | for _, order := range orders { 271 | mp.pool[pair].Add(&order) 272 | } 273 | ts2 := utils.NowUnixMilli() - begin 274 | t.Logf("撮合%d条数据: %dms", size, ts2) 275 | } 276 | 277 | // 测试撤单性能,需注释推送成交(改成了异步,所以时间很短,但不是真实时间) 278 | func TestOrderbook_TS_ADD_CANCEL(t *testing.T) { 279 | size := 100000 280 | orders := make([]models.Order, size) 281 | for i := 0; i < size; i++ { 282 | order := models.Order{ 283 | Id: strconv.Itoa(i), 284 | UserId: 1, 285 | Pair: pair, 286 | Price: decimal.NewFromInt(int64(i)), 287 | Amount: decimal.NewFromInt(100000), 288 | Side: models.Buy, 289 | Type: models.Limit, 290 | TimeInForce: models.TimeInForceGTC, 291 | } 292 | orders[i] = order 293 | } 294 | begin := utils.NowUnixMilli() 295 | for _, order := range orders { 296 | mp.pool[pair].Add(&order) 297 | } 298 | ts1 := utils.NowUnixMilli() - begin 299 | t.Logf("插入%d条数据: %dms", size, ts1) 300 | 301 | begin = utils.NowUnixMilli() 302 | for i := 0; i < size; i++ { 303 | mp.pool[pair].Cancel(strconv.Itoa(i)) 304 | } 305 | ts2 := utils.NowUnixMilli() - begin 306 | t.Logf("撤销%d条数据: %dms", size, ts2) 307 | } 308 | -------------------------------------------------------------------------------- /internal/match/orderbook.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/shopspring/decimal" 7 | "lightning-engine/internal/status" 8 | "lightning-engine/models" 9 | "lightning-engine/mq" 10 | "lightning-engine/pqueue/skiplist" 11 | "lightning-engine/utils" 12 | "time" 13 | ) 14 | 15 | // Orderbook 盘口订单簿 16 | type Orderbook struct { 17 | pair string 18 | bid *skiplist.SkipListDesc // bid从大到小排列 19 | ask *skiplist.SkipList // ask从小到大排列 20 | 21 | mBid map[string]decimal.Decimal // bid订单id对应的score 22 | mAsk map[string]decimal.Decimal // ask订单id对应的score 23 | 24 | mq mq.IMQ 25 | chAdd chan models.Order // order channel 异步顺序处理订单 26 | chCancel chan string // order_id channel 异步顺序处理订单 27 | status *status.Status // 程序退出状态 28 | } 29 | 30 | func NewOrderbook(status *status.Status, pair string, mq mq.IMQ) (*Orderbook, error) { 31 | if mq == nil { 32 | return nil, ErrMq 33 | } 34 | bid, err := skiplist.NewSkipListDesc() 35 | if err != nil { 36 | return nil, err 37 | } 38 | ask, err := skiplist.NewSkipList() 39 | if err != nil { 40 | return nil, err 41 | } 42 | return &Orderbook{ 43 | pair: pair, 44 | bid: bid, 45 | ask: ask, 46 | mBid: make(map[string]decimal.Decimal), 47 | mAsk: make(map[string]decimal.Decimal), 48 | mq: mq, 49 | chAdd: make(chan models.Order, 1000000), 50 | chCancel: make(chan string, 1000000), 51 | status: status, 52 | }, nil 53 | } 54 | 55 | // Add 异步挂单 56 | func (ob *Orderbook) Add(order *models.Order) error { 57 | ob.status.Add(1) 58 | defer ob.status.Done() 59 | select { 60 | case ob.chAdd <- *order: 61 | return nil 62 | case <-time.After(time.Second): 63 | return ErrTimeout 64 | case <-ob.status.Context().Done(): 65 | return ErrClosed 66 | } 67 | } 68 | 69 | // Cancel 异步撤单 70 | func (ob *Orderbook) Cancel(id string) error { 71 | ob.status.Add(1) 72 | defer ob.status.Done() 73 | select { 74 | case ob.chCancel <- id: 75 | return nil 76 | case <-time.After(time.Second): 77 | return ErrTimeout 78 | case <-ob.status.Context().Done(): 79 | return ErrClosed 80 | } 81 | } 82 | 83 | // Begin 开始撮合 84 | func (ob *Orderbook) Begin() { 85 | defer ob.status.Done() 86 | for { 87 | select { 88 | case order := <-ob.chAdd: 89 | ob.add(order) 90 | case orderId := <-ob.chCancel: 91 | ob.cancel(orderId) 92 | case <-ob.status.Context().Done(): 93 | return 94 | } 95 | } 96 | } 97 | 98 | // add 挂单 99 | func (ob *Orderbook) add(order models.Order) error { 100 | switch order.Side { 101 | case models.Buy: 102 | return ob.addBid(order) 103 | case models.Sell: 104 | return ob.addAsk(order) 105 | } 106 | return ErrOrderSide 107 | } 108 | 109 | // addBid 挂bid 110 | func (ob *Orderbook) addBid(order models.Order) error { 111 | switch order.Type { 112 | case models.Limit: 113 | return ob.addBidLimit(order) 114 | case models.Market: 115 | return ob.addBidMarket(order) 116 | } 117 | return ErrOrderType 118 | } 119 | 120 | // addAsk 挂ask 121 | func (ob *Orderbook) addAsk(order models.Order) error { 122 | switch order.Type { 123 | case models.Limit: 124 | return ob.addAskLimit(order) 125 | case models.Market: 126 | return ob.addAskMarket(order) 127 | } 128 | return ErrOrderType 129 | } 130 | 131 | // addBidLimit 挂bid限价单 132 | func (ob *Orderbook) addBidLimit(order models.Order) error { 133 | switch order.TimeInForce { 134 | case models.TimeInForceGTC: 135 | return ob.addBidLimitGTC(order) 136 | case models.TimeInForceIOC: 137 | return ob.addBidLimitIOC(order) 138 | case models.TimeInForceFOK: 139 | return ob.addBidLimitFOK(order) 140 | } 141 | return ErrOrderTimeInForce 142 | } 143 | 144 | // addAskLimit 挂ask限价单 145 | func (ob *Orderbook) addAskLimit(order models.Order) error { 146 | switch order.TimeInForce { 147 | case models.TimeInForceGTC: 148 | return ob.addAskLimitGTC(order) 149 | case models.TimeInForceIOC: 150 | return ob.addAskLimitIOC(order) 151 | case models.TimeInForceFOK: 152 | return ob.addAskLimitFOK(order) 153 | } 154 | return ErrOrderTimeInForce 155 | } 156 | 157 | // addBidMarket 挂bid市价单 158 | func (ob *Orderbook) addBidMarket(order models.Order) error { 159 | trades := make([]models.Trade, 0) 160 | first := &skiplist.SkipListNode{} 161 | 162 | // order.Amount > 0 163 | for ob.ask.First() != nil && order.Amount.GreaterThan(decimal.Zero) { 164 | first = ob.ask.First() 165 | if first.Value().GetAmount().GreaterThanOrEqual(order.Amount) { // ask.first.Amount >= order.Amount 166 | trade := models.Trade{ 167 | Id: utils.GenTradeId(), 168 | Pair: order.Pair, 169 | MakerId: first.Value().GetId(), 170 | TakerId: order.Id, 171 | MakerUser: first.Value().GetUserId(), 172 | TakerUser: order.UserId, 173 | Price: first.Score().String(), 174 | Amount: order.Amount.String(), 175 | TakerOrderSide: order.Side, 176 | TakerOrderType: order.Type, 177 | TakerTimeInForce: order.TimeInForce, 178 | Ts: utils.NowUnixMilli(), 179 | } 180 | trades = append(trades, trade) 181 | 182 | // 判断first剩余数量 183 | amount := first.Value().GetAmount().Sub(order.Amount) 184 | order.Amount = order.Amount.Sub(order.Amount) 185 | if amount.GreaterThan(decimal.Zero) { // 剩余数量 > 0 186 | first.Value().SetAmount(amount) 187 | } else { // 剩余数量 <= 0 188 | ob.ask.Delete(first.Score(), first.Value().GetId()) 189 | } 190 | } else { // ask.first.Amount < order.Amount 191 | trade := models.Trade{ 192 | Id: utils.GenTradeId(), 193 | Pair: order.Pair, 194 | MakerId: first.Value().GetId(), 195 | TakerId: order.Id, 196 | MakerUser: first.Value().GetUserId(), 197 | TakerUser: order.UserId, 198 | Price: first.Score().String(), 199 | Amount: first.Value().GetAmount().String(), 200 | TakerOrderSide: order.Side, 201 | TakerOrderType: order.Type, 202 | TakerTimeInForce: order.TimeInForce, 203 | Ts: utils.NowUnixMilli(), 204 | } 205 | trades = append(trades, trade) 206 | order.Amount = order.Amount.Sub(first.Value().GetAmount()) 207 | 208 | // 删除first 209 | ob.ask.Delete(first.Score(), first.Value().GetId()) 210 | } 211 | } 212 | 213 | // 判断order是否完全成交 214 | if order.Amount.GreaterThan(decimal.Zero) { 215 | trade := models.Trade{ 216 | Id: utils.GenTradeId(), 217 | Pair: order.Pair, 218 | MakerId: order.Id, 219 | TakerId: order.Id, 220 | MakerUser: order.UserId, 221 | TakerUser: order.UserId, 222 | Price: order.Price.String(), 223 | Amount: order.Amount.String(), 224 | TakerOrderSide: order.Side, 225 | TakerOrderType: models.Cancel, 226 | TakerTimeInForce: order.TimeInForce, 227 | Ts: utils.NowUnixMilli(), 228 | } 229 | trades = append(trades, trade) 230 | } 231 | 232 | if len(trades) > 0 { 233 | ob.PushTrades(trades...) 234 | } 235 | return nil 236 | } 237 | 238 | // addAskMarket 挂ask市价单 239 | func (ob *Orderbook) addAskMarket(order models.Order) error { 240 | trades := make([]models.Trade, 0) 241 | first := &skiplist.SkipListNode{} 242 | 243 | // order.Amount > 0 244 | for ob.bid.First() != nil && order.Amount.GreaterThan(decimal.Zero) { 245 | first = ob.bid.First() 246 | if first.Value().GetAmount().GreaterThanOrEqual(order.Amount) { // bid.first.Amount >= order.Amount 247 | trade := models.Trade{ 248 | Id: utils.GenTradeId(), 249 | Pair: order.Pair, 250 | MakerId: first.Value().GetId(), 251 | TakerId: order.Id, 252 | MakerUser: first.Value().GetUserId(), 253 | TakerUser: order.UserId, 254 | Price: first.Score().String(), 255 | Amount: order.Amount.String(), 256 | TakerOrderSide: order.Side, 257 | TakerOrderType: order.Type, 258 | TakerTimeInForce: order.TimeInForce, 259 | Ts: utils.NowUnixMilli(), 260 | } 261 | trades = append(trades, trade) 262 | 263 | // 判断first剩余数量 264 | amount := first.Value().GetAmount().Sub(order.Amount) 265 | order.Amount = order.Amount.Sub(order.Amount) 266 | if amount.GreaterThan(decimal.Zero) { // 剩余数量 > 0 267 | first.Value().SetAmount(amount) 268 | } else { // 剩余数量 <= 0 269 | ob.bid.Delete(first.Score(), first.Value().GetId()) 270 | } 271 | } else { // bid.first.Amount < order.Amount 272 | trade := models.Trade{ 273 | Id: utils.GenTradeId(), 274 | Pair: order.Pair, 275 | MakerId: first.Value().GetId(), 276 | TakerId: order.Id, 277 | MakerUser: first.Value().GetUserId(), 278 | TakerUser: order.UserId, 279 | Price: first.Score().String(), 280 | Amount: first.Value().GetAmount().String(), 281 | TakerOrderSide: order.Side, 282 | TakerOrderType: order.Type, 283 | TakerTimeInForce: order.TimeInForce, 284 | Ts: utils.NowUnixMilli(), 285 | } 286 | trades = append(trades, trade) 287 | order.Amount = order.Amount.Sub(first.Value().GetAmount()) 288 | 289 | // 删除first 290 | ob.bid.Delete(first.Score(), first.Value().GetId()) 291 | } 292 | } 293 | 294 | // 判断order是否完全成交 295 | if order.Amount.GreaterThan(decimal.Zero) { 296 | trade := models.Trade{ 297 | Id: utils.GenTradeId(), 298 | Pair: order.Pair, 299 | MakerId: order.Id, 300 | TakerId: order.Id, 301 | MakerUser: order.UserId, 302 | TakerUser: order.UserId, 303 | Price: order.Price.String(), 304 | Amount: order.Amount.String(), 305 | TakerOrderSide: order.Side, 306 | TakerOrderType: models.Cancel, 307 | TakerTimeInForce: order.TimeInForce, 308 | Ts: utils.NowUnixMilli(), 309 | } 310 | trades = append(trades, trade) 311 | } 312 | 313 | if len(trades) > 0 { 314 | ob.PushTrades(trades...) 315 | } 316 | return nil 317 | } 318 | 319 | // addBidLimitGTC 挂bid限价GTC订单 320 | func (ob *Orderbook) addBidLimitGTC(order models.Order) error { 321 | trades := make([]models.Trade, 0) 322 | first := &skiplist.SkipListNode{} 323 | 324 | // ask.first.Score <= order.Price && order.Amount > 0 325 | for ob.ask.First() != nil && ob.ask.First().Score().LessThanOrEqual(order.Price) && order.Amount.GreaterThan(decimal.Zero) { 326 | first = ob.ask.First() 327 | if first.Value().GetAmount().GreaterThanOrEqual(order.Amount) { // ask.first.Amount >= order.Amount 328 | trade := models.Trade{ 329 | Id: utils.GenTradeId(), 330 | Pair: order.Pair, 331 | MakerId: first.Value().GetId(), 332 | TakerId: order.Id, 333 | MakerUser: first.Value().GetUserId(), 334 | TakerUser: order.UserId, 335 | Price: first.Score().String(), 336 | Amount: order.Amount.String(), 337 | TakerOrderSide: order.Side, 338 | TakerOrderType: order.Type, 339 | TakerTimeInForce: order.TimeInForce, 340 | Ts: utils.NowUnixMilli(), 341 | } 342 | trades = append(trades, trade) 343 | 344 | // 判断first剩余数量 345 | amount := first.Value().GetAmount().Sub(order.Amount) 346 | order.Amount = order.Amount.Sub(order.Amount) 347 | if amount.GreaterThan(decimal.Zero) { // 剩余数量 > 0 348 | first.Value().SetAmount(amount) 349 | } else { // 剩余数量 <= 0 350 | ob.ask.Delete(first.Score(), first.Value().GetId()) 351 | } 352 | } else { // ask.first.Amount < order.Amount 353 | trade := models.Trade{ 354 | Id: utils.GenTradeId(), 355 | Pair: order.Pair, 356 | MakerId: first.Value().GetId(), 357 | TakerId: order.Id, 358 | MakerUser: first.Value().GetUserId(), 359 | TakerUser: order.UserId, 360 | Price: first.Score().String(), 361 | Amount: first.Value().GetAmount().String(), 362 | TakerOrderSide: order.Side, 363 | TakerOrderType: order.Type, 364 | TakerTimeInForce: order.TimeInForce, 365 | Ts: utils.NowUnixMilli(), 366 | } 367 | trades = append(trades, trade) 368 | order.Amount = order.Amount.Sub(first.Value().GetAmount()) 369 | 370 | // 删除first 371 | ob.ask.Delete(first.Score(), first.Value().GetId()) 372 | } 373 | } 374 | 375 | // 判断order是否完全成交 376 | if order.Amount.GreaterThan(decimal.Zero) { 377 | ob.bid.Insert(order.Price, &order) 378 | ob.mBid[order.Id] = order.Price 379 | } 380 | 381 | if len(trades) > 0 { 382 | ob.PushTrades(trades...) 383 | } 384 | return nil 385 | } 386 | 387 | // addAskLimitGTC 挂ask限价GTC订单 388 | func (ob *Orderbook) addAskLimitGTC(order models.Order) error { 389 | trades := make([]models.Trade, 0) 390 | first := &skiplist.SkipListNode{} 391 | 392 | // bid.first.Score >= order.Price && order.Amount > 0 393 | for ob.bid.First() != nil && ob.bid.First().Score().GreaterThanOrEqual(order.Price) && order.Amount.GreaterThan(decimal.Zero) { 394 | first = ob.bid.First() 395 | if first.Value().GetAmount().GreaterThanOrEqual(order.Amount) { // bid.first.Amount >= order.Amount 396 | trade := models.Trade{ 397 | Id: utils.GenTradeId(), 398 | Pair: order.Pair, 399 | MakerId: first.Value().GetId(), 400 | TakerId: order.Id, 401 | MakerUser: first.Value().GetUserId(), 402 | TakerUser: order.UserId, 403 | Price: first.Score().String(), 404 | Amount: order.Amount.String(), 405 | TakerOrderSide: order.Side, 406 | TakerOrderType: order.Type, 407 | TakerTimeInForce: order.TimeInForce, 408 | Ts: utils.NowUnixMilli(), 409 | } 410 | trades = append(trades, trade) 411 | 412 | // 判断first剩余数量 413 | amount := first.Value().GetAmount().Sub(order.Amount) 414 | order.Amount = order.Amount.Sub(order.Amount) 415 | if amount.GreaterThan(decimal.Zero) { // 剩余数量 > 0 416 | first.Value().SetAmount(amount) 417 | } else { // 剩余数量 <= 0 418 | ob.bid.Delete(first.Score(), first.Value().GetId()) 419 | } 420 | } else { // bid.first.Amount < order.Amount 421 | trade := models.Trade{ 422 | Id: utils.GenTradeId(), 423 | Pair: order.Pair, 424 | MakerId: first.Value().GetId(), 425 | TakerId: order.Id, 426 | MakerUser: first.Value().GetUserId(), 427 | TakerUser: order.UserId, 428 | Price: first.Score().String(), 429 | Amount: first.Value().GetAmount().String(), 430 | TakerOrderSide: order.Side, 431 | TakerOrderType: order.Type, 432 | TakerTimeInForce: order.TimeInForce, 433 | Ts: utils.NowUnixMilli(), 434 | } 435 | trades = append(trades, trade) 436 | order.Amount = order.Amount.Sub(first.Value().GetAmount()) 437 | 438 | // 删除first 439 | ob.bid.Delete(first.Score(), first.Value().GetId()) 440 | } 441 | } 442 | 443 | // 判断order是否完全成交 444 | if order.Amount.GreaterThan(decimal.Zero) { 445 | ob.ask.Insert(order.Price, &order) 446 | ob.mAsk[order.Id] = order.Price 447 | } 448 | 449 | if len(trades) > 0 { 450 | ob.PushTrades(trades...) 451 | } 452 | return nil 453 | } 454 | 455 | // addBidLimitIOC 挂bid限价IOC订单 456 | func (ob *Orderbook) addBidLimitIOC(order models.Order) error { 457 | trades := make([]models.Trade, 0) 458 | first := &skiplist.SkipListNode{} 459 | 460 | // ask.first.Score <= order.Price && order.Amount > 0 461 | for ob.ask.First() != nil && ob.ask.First().Score().LessThanOrEqual(order.Price) && order.Amount.GreaterThan(decimal.Zero) { 462 | first = ob.ask.First() 463 | if first.Value().GetAmount().GreaterThanOrEqual(order.Amount) { // ask.first.Amount >= order.Amount 464 | trade := models.Trade{ 465 | Id: utils.GenTradeId(), 466 | Pair: order.Pair, 467 | MakerId: first.Value().GetId(), 468 | TakerId: order.Id, 469 | MakerUser: first.Value().GetUserId(), 470 | TakerUser: order.UserId, 471 | Price: first.Score().String(), 472 | Amount: order.Amount.String(), 473 | TakerOrderSide: order.Side, 474 | TakerOrderType: order.Type, 475 | TakerTimeInForce: order.TimeInForce, 476 | Ts: utils.NowUnixMilli(), 477 | } 478 | trades = append(trades, trade) 479 | 480 | // 判断first剩余数量 481 | amount := first.Value().GetAmount().Sub(order.Amount) 482 | order.Amount = order.Amount.Sub(order.Amount) 483 | if amount.GreaterThan(decimal.Zero) { // 剩余数量 > 0 484 | first.Value().SetAmount(amount) 485 | } else { // 剩余数量 <= 0 486 | ob.ask.Delete(first.Score(), first.Value().GetId()) 487 | } 488 | } else { // ask.first.Amount < order.Amount 489 | trade := models.Trade{ 490 | Id: utils.GenTradeId(), 491 | Pair: order.Pair, 492 | MakerId: first.Value().GetId(), 493 | TakerId: order.Id, 494 | MakerUser: first.Value().GetUserId(), 495 | TakerUser: order.UserId, 496 | Price: first.Score().String(), 497 | Amount: first.Value().GetAmount().String(), 498 | TakerOrderSide: order.Side, 499 | TakerOrderType: order.Type, 500 | TakerTimeInForce: order.TimeInForce, 501 | Ts: utils.NowUnixMilli(), 502 | } 503 | trades = append(trades, trade) 504 | order.Amount = order.Amount.Sub(first.Value().GetAmount()) 505 | 506 | // 删除first 507 | ob.ask.Delete(first.Score(), first.Value().GetId()) 508 | } 509 | } 510 | 511 | // 判断order是否完全成交 512 | if order.Amount.GreaterThan(decimal.Zero) { 513 | trade := models.Trade{ 514 | Id: utils.GenTradeId(), 515 | Pair: order.Pair, 516 | MakerId: order.Id, 517 | TakerId: order.Id, 518 | MakerUser: order.UserId, 519 | TakerUser: order.UserId, 520 | Price: order.Price.String(), 521 | Amount: order.Amount.String(), 522 | TakerOrderSide: order.Side, 523 | TakerOrderType: models.Cancel, 524 | TakerTimeInForce: order.TimeInForce, 525 | Ts: utils.NowUnixMilli(), 526 | } 527 | trades = append(trades, trade) 528 | } 529 | 530 | if len(trades) > 0 { 531 | ob.PushTrades(trades...) 532 | } 533 | return nil 534 | } 535 | 536 | // addAskLimitIOC 挂ask限价IOC订单 537 | func (ob *Orderbook) addAskLimitIOC(order models.Order) error { 538 | trades := make([]models.Trade, 0) 539 | first := &skiplist.SkipListNode{} 540 | 541 | // bid.first.Score >= order.Price && order.Amount > 0 542 | for ob.bid.First() != nil && ob.bid.First().Score().GreaterThanOrEqual(order.Price) && order.Amount.GreaterThan(decimal.Zero) { 543 | first = ob.bid.First() 544 | if first.Value().GetAmount().GreaterThanOrEqual(order.Amount) { // bid.first.Amount >= order.Amount 545 | trade := models.Trade{ 546 | Id: utils.GenTradeId(), 547 | Pair: order.Pair, 548 | MakerId: first.Value().GetId(), 549 | TakerId: order.Id, 550 | MakerUser: first.Value().GetUserId(), 551 | TakerUser: order.UserId, 552 | Price: first.Score().String(), 553 | Amount: order.Amount.String(), 554 | TakerOrderSide: order.Side, 555 | TakerOrderType: order.Type, 556 | TakerTimeInForce: order.TimeInForce, 557 | Ts: utils.NowUnixMilli(), 558 | } 559 | trades = append(trades, trade) 560 | 561 | // 判断first剩余数量 562 | amount := first.Value().GetAmount().Sub(order.Amount) 563 | order.Amount = order.Amount.Sub(order.Amount) 564 | if amount.GreaterThan(decimal.Zero) { // 剩余数量 > 0 565 | first.Value().SetAmount(amount) 566 | } else { // 剩余数量 <= 0 567 | ob.bid.Delete(first.Score(), first.Value().GetId()) 568 | } 569 | } else { // bid.first.Amount < order.Amount 570 | trade := models.Trade{ 571 | Id: utils.GenTradeId(), 572 | Pair: order.Pair, 573 | MakerId: first.Value().GetId(), 574 | TakerId: order.Id, 575 | MakerUser: first.Value().GetUserId(), 576 | TakerUser: order.UserId, 577 | Price: first.Score().String(), 578 | Amount: first.Value().GetAmount().String(), 579 | TakerOrderSide: order.Side, 580 | TakerOrderType: order.Type, 581 | TakerTimeInForce: order.TimeInForce, 582 | Ts: utils.NowUnixMilli(), 583 | } 584 | trades = append(trades, trade) 585 | order.Amount = order.Amount.Sub(first.Value().GetAmount()) 586 | 587 | // 删除first 588 | ob.bid.Delete(first.Score(), first.Value().GetId()) 589 | } 590 | } 591 | 592 | // 判断order是否完全成交 593 | if order.Amount.GreaterThan(decimal.Zero) { 594 | trade := models.Trade{ 595 | Id: utils.GenTradeId(), 596 | Pair: order.Pair, 597 | MakerId: order.Id, 598 | TakerId: order.Id, 599 | MakerUser: order.UserId, 600 | TakerUser: order.UserId, 601 | Price: order.Price.String(), 602 | Amount: order.Amount.String(), 603 | TakerOrderSide: order.Side, 604 | TakerOrderType: models.Cancel, 605 | TakerTimeInForce: order.TimeInForce, 606 | Ts: utils.NowUnixMilli(), 607 | } 608 | trades = append(trades, trade) 609 | } 610 | 611 | if len(trades) > 0 { 612 | ob.PushTrades(trades...) 613 | } 614 | return nil 615 | } 616 | 617 | // addBidLimitFOK 挂bid限价FOK订单 618 | func (ob *Orderbook) addBidLimitFOK(order models.Order) error { 619 | trades := make([]models.Trade, 0) 620 | first := &skiplist.SkipListNode{} 621 | 622 | // 判断能否全部成交 623 | amount := order.Amount 624 | first = ob.ask.First() 625 | for first != nil && first.Score().LessThanOrEqual(order.Price) && amount.GreaterThan(decimal.Zero) { 626 | if first.Value().GetAmount().GreaterThanOrEqual(order.Amount) { // ask.first.Amount >= order.Amount 627 | amount = amount.Sub(amount) 628 | } else { 629 | amount = amount.Sub(first.Value().GetAmount()) 630 | first = first.Next(0) 631 | } 632 | } 633 | if amount.GreaterThan(decimal.Zero) { // 剩余数量 > 0, 撤销 634 | trade := models.Trade{ 635 | Id: utils.GenTradeId(), 636 | Pair: order.Pair, 637 | MakerId: order.Id, 638 | TakerId: order.Id, 639 | MakerUser: order.UserId, 640 | TakerUser: order.UserId, 641 | Price: order.Price.String(), 642 | Amount: order.Amount.String(), 643 | TakerOrderSide: order.Side, 644 | TakerOrderType: models.Cancel, 645 | TakerTimeInForce: order.TimeInForce, 646 | Ts: utils.NowUnixMilli(), 647 | } 648 | trades = append(trades, trade) 649 | ob.PushTrades(trades...) 650 | return nil 651 | } 652 | 653 | // ask.first.Score <= order.Price && order.Amount > 0 654 | for ob.ask.First() != nil && ob.ask.First().Score().LessThanOrEqual(order.Price) && order.Amount.GreaterThan(decimal.Zero) { 655 | first = ob.ask.First() 656 | if first.Value().GetAmount().GreaterThanOrEqual(order.Amount) { // ask.first.Amount >= order.Amount 657 | trade := models.Trade{ 658 | Id: utils.GenTradeId(), 659 | Pair: order.Pair, 660 | MakerId: first.Value().GetId(), 661 | TakerId: order.Id, 662 | MakerUser: first.Value().GetUserId(), 663 | TakerUser: order.UserId, 664 | Price: first.Score().String(), 665 | Amount: order.Amount.String(), 666 | TakerOrderSide: order.Side, 667 | TakerOrderType: order.Type, 668 | TakerTimeInForce: order.TimeInForce, 669 | Ts: utils.NowUnixMilli(), 670 | } 671 | trades = append(trades, trade) 672 | 673 | // 判断first剩余数量 674 | amount := first.Value().GetAmount().Sub(order.Amount) 675 | order.Amount = order.Amount.Sub(order.Amount) 676 | if amount.GreaterThan(decimal.Zero) { // 剩余数量 > 0 677 | first.Value().SetAmount(amount) 678 | } else { // 剩余数量 <= 0 679 | ob.ask.Delete(first.Score(), first.Value().GetId()) 680 | } 681 | } else { // ask.first.Amount < order.Amount 682 | trade := models.Trade{ 683 | Id: utils.GenTradeId(), 684 | Pair: order.Pair, 685 | MakerId: first.Value().GetId(), 686 | TakerId: order.Id, 687 | MakerUser: first.Value().GetUserId(), 688 | TakerUser: order.UserId, 689 | Price: first.Score().String(), 690 | Amount: first.Value().GetAmount().String(), 691 | TakerOrderSide: order.Side, 692 | TakerOrderType: order.Type, 693 | TakerTimeInForce: order.TimeInForce, 694 | Ts: utils.NowUnixMilli(), 695 | } 696 | trades = append(trades, trade) 697 | order.Amount = order.Amount.Sub(first.Value().GetAmount()) 698 | 699 | // 删除first 700 | ob.ask.Delete(first.Score(), first.Value().GetId()) 701 | } 702 | } 703 | 704 | // 判断order是否完全成交 705 | if order.Amount.GreaterThan(decimal.Zero) { 706 | trade := models.Trade{ 707 | Id: utils.GenTradeId(), 708 | Pair: order.Pair, 709 | MakerId: order.Id, 710 | TakerId: order.Id, 711 | MakerUser: order.UserId, 712 | TakerUser: order.UserId, 713 | Price: order.Price.String(), 714 | Amount: order.Amount.String(), 715 | TakerOrderSide: order.Side, 716 | TakerOrderType: models.Cancel, 717 | TakerTimeInForce: order.TimeInForce, 718 | Ts: utils.NowUnixMilli(), 719 | } 720 | trades = append(trades, trade) 721 | } 722 | 723 | if len(trades) > 0 { 724 | ob.PushTrades(trades...) 725 | } 726 | return nil 727 | } 728 | 729 | // addAskLimitFOK 挂ask限价FOK订单 730 | func (ob *Orderbook) addAskLimitFOK(order models.Order) error { 731 | trades := make([]models.Trade, 0) 732 | first := &skiplist.SkipListNode{} 733 | 734 | // 判断能否全部成交 735 | amount := order.Amount 736 | first = ob.bid.First() 737 | for first != nil && first.Score().GreaterThanOrEqual(order.Price) && amount.GreaterThan(decimal.Zero) { 738 | if first.Value().GetAmount().GreaterThanOrEqual(order.Amount) { // bid.first.Amount >= order.Amount 739 | amount = amount.Sub(amount) 740 | } else { 741 | amount = amount.Sub(first.Value().GetAmount()) 742 | first = first.Next(0) 743 | } 744 | } 745 | if amount.GreaterThan(decimal.Zero) { // 剩余数量 > 0, 撤销 746 | trade := models.Trade{ 747 | Id: utils.GenTradeId(), 748 | Pair: order.Pair, 749 | MakerId: order.Id, 750 | TakerId: order.Id, 751 | MakerUser: order.UserId, 752 | TakerUser: order.UserId, 753 | Price: order.Price.String(), 754 | Amount: order.Amount.String(), 755 | TakerOrderSide: order.Side, 756 | TakerOrderType: models.Cancel, 757 | TakerTimeInForce: order.TimeInForce, 758 | Ts: utils.NowUnixMilli(), 759 | } 760 | trades = append(trades, trade) 761 | ob.PushTrades(trades...) 762 | return nil 763 | } 764 | 765 | // bid.first.Score >= order.Price && order.Amount > 0 766 | for ob.bid.First() != nil && ob.bid.First().Score().GreaterThanOrEqual(order.Price) && order.Amount.GreaterThan(decimal.Zero) { 767 | first = ob.bid.First() 768 | if first.Value().GetAmount().GreaterThanOrEqual(order.Amount) { // bid.first.Amount >= order.Amount 769 | trade := models.Trade{ 770 | Id: utils.GenTradeId(), 771 | Pair: order.Pair, 772 | MakerId: first.Value().GetId(), 773 | TakerId: order.Id, 774 | MakerUser: first.Value().GetUserId(), 775 | TakerUser: order.UserId, 776 | Price: first.Score().String(), 777 | Amount: order.Amount.String(), 778 | TakerOrderSide: order.Side, 779 | TakerOrderType: order.Type, 780 | TakerTimeInForce: order.TimeInForce, 781 | Ts: utils.NowUnixMilli(), 782 | } 783 | trades = append(trades, trade) 784 | 785 | // 判断first剩余数量 786 | amount := first.Value().GetAmount().Sub(order.Amount) 787 | order.Amount = order.Amount.Sub(order.Amount) 788 | if amount.GreaterThan(decimal.Zero) { // 剩余数量 > 0 789 | first.Value().SetAmount(amount) 790 | } else { // 剩余数量 <= 0 791 | ob.bid.Delete(first.Score(), first.Value().GetId()) 792 | } 793 | } else { // bid.first.Amount < order.Amount 794 | trade := models.Trade{ 795 | Id: utils.GenTradeId(), 796 | Pair: order.Pair, 797 | MakerId: first.Value().GetId(), 798 | TakerId: order.Id, 799 | MakerUser: first.Value().GetUserId(), 800 | TakerUser: order.UserId, 801 | Price: first.Score().String(), 802 | Amount: first.Value().GetAmount().String(), 803 | TakerOrderSide: order.Side, 804 | TakerOrderType: order.Type, 805 | TakerTimeInForce: order.TimeInForce, 806 | Ts: utils.NowUnixMilli(), 807 | } 808 | trades = append(trades, trade) 809 | order.Amount = order.Amount.Sub(first.Value().GetAmount()) 810 | 811 | // 删除first 812 | ob.bid.Delete(first.Score(), first.Value().GetId()) 813 | } 814 | } 815 | 816 | // 判断order是否完全成交 817 | if order.Amount.GreaterThan(decimal.Zero) { 818 | trade := models.Trade{ 819 | Id: utils.GenTradeId(), 820 | Pair: order.Pair, 821 | MakerId: order.Id, 822 | TakerId: order.Id, 823 | MakerUser: order.UserId, 824 | TakerUser: order.UserId, 825 | Price: order.Price.String(), 826 | Amount: order.Amount.String(), 827 | TakerOrderSide: order.Side, 828 | TakerOrderType: models.Cancel, 829 | TakerTimeInForce: order.TimeInForce, 830 | Ts: utils.NowUnixMilli(), 831 | } 832 | trades = append(trades, trade) 833 | } 834 | 835 | if len(trades) > 0 { 836 | ob.PushTrades(trades...) 837 | } 838 | return nil 839 | } 840 | 841 | // cancel 撤单 842 | func (ob *Orderbook) cancel(id string) error { 843 | ob.status.Add(1) 844 | defer ob.status.Done() 845 | if score, ok := ob.mBid[id]; ok { 846 | return ob.cancelBid(score, id) 847 | } else if score, ok := ob.mAsk[id]; ok { 848 | return ob.cancelAsk(score, id) 849 | } else { 850 | return ErrOrderId 851 | } 852 | } 853 | 854 | // cancelBid 撤销bid 855 | func (ob *Orderbook) cancelBid(score decimal.Decimal, id string) error { 856 | node, _ := ob.bid.Find(score, id) 857 | if node == nil { 858 | return ErrOrderId 859 | } 860 | 861 | order, ok := node.Value().(*models.Order) 862 | if !ok { 863 | return errors.New(fmt.Sprintf("node value cannot convert to Order.")) 864 | } 865 | 866 | ob.bid.Delete(score, id) 867 | trade := models.Trade{ 868 | Id: utils.GenTradeId(), 869 | Pair: order.Pair, 870 | MakerId: order.Id, 871 | TakerId: order.Id, 872 | MakerUser: order.UserId, 873 | TakerUser: order.UserId, 874 | Price: order.Price.String(), 875 | Amount: order.Amount.String(), 876 | TakerOrderSide: order.Side, 877 | TakerOrderType: models.Cancel, 878 | TakerTimeInForce: order.TimeInForce, 879 | Ts: utils.NowUnixMilli(), 880 | } 881 | ob.PushTrades(trade) 882 | delete(ob.mBid, id) 883 | return nil 884 | } 885 | 886 | // cancelAsk 撤销ask 887 | func (ob *Orderbook) cancelAsk(score decimal.Decimal, id string) error { 888 | node, _ := ob.ask.Find(score, id) 889 | if node == nil { 890 | return ErrOrderId 891 | } 892 | 893 | order, ok := node.Value().(*models.Order) 894 | if !ok { 895 | return errors.New(fmt.Sprintf("node value cannot convert to Order.")) 896 | } 897 | 898 | ob.ask.Delete(score, id) 899 | trade := models.Trade{ 900 | Id: utils.GenTradeId(), 901 | Pair: order.Pair, 902 | MakerId: order.Id, 903 | TakerId: order.Id, 904 | MakerUser: order.UserId, 905 | TakerUser: order.UserId, 906 | Price: order.Price.String(), 907 | Amount: order.Amount.String(), 908 | TakerOrderSide: order.Side, 909 | TakerOrderType: models.Cancel, 910 | TakerTimeInForce: order.TimeInForce, 911 | Ts: utils.NowUnixMilli(), 912 | } 913 | ob.PushTrades(trade) 914 | delete(ob.mAsk, id) 915 | return nil 916 | } 917 | 918 | // PushTrades 推送成交单 919 | func (ob *Orderbook) PushTrades(trades ...models.Trade) { 920 | ob.mq.PushTrade(trades...) 921 | } 922 | -------------------------------------------------------------------------------- /internal/match/pool.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "lightning-engine/internal/status" 5 | "lightning-engine/models" 6 | "lightning-engine/mq" 7 | ) 8 | 9 | // MatchPool 撮合池 10 | type MatchPool struct { 11 | pool map[string]*Orderbook 12 | } 13 | 14 | func NewMatchPool(status *status.Status, pairs []string, mq mq.IMQ) (*MatchPool, error) { 15 | mp := MatchPool{} 16 | mp.pool = make(map[string]*Orderbook) 17 | for _, p := range pairs { 18 | ob, err := NewOrderbook(status, p, mq) 19 | if err != nil { 20 | return nil, err 21 | } 22 | status.Add(1) 23 | go ob.Begin() 24 | mp.pool[p] = ob 25 | } 26 | return &mp, nil 27 | } 28 | 29 | // AddOrder 挂单 30 | func (mp *MatchPool) AddOrder(order *models.Order) error { 31 | if _, ok := mp.pool[order.Pair]; !ok { 32 | return ErrPair 33 | } 34 | return mp.pool[order.Pair].Add(order) 35 | } 36 | 37 | // CancelOrder 撤单 38 | func (mp *MatchPool) CancelOrder(pair string, id string) error { 39 | if _, ok := mp.pool[pair]; !ok { 40 | return ErrPair 41 | } 42 | return mp.pool[pair].Cancel(id) 43 | } 44 | -------------------------------------------------------------------------------- /internal/match/types.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import "errors" 4 | 5 | var ( 6 | ErrMq = errors.New("mq cannot nil") 7 | ErrTimeout = errors.New("timeout") 8 | ErrClosed = errors.New("match server closed") 9 | ErrOrderSide = errors.New("order side error (buy/sell)") 10 | ErrOrderType = errors.New("order type error (limit/market)") 11 | ErrOrderTimeInForce = errors.New("order timeInForce error (GTC/IOC/FOK)") 12 | ErrOrderId = errors.New("order id error") 13 | ErrPair = errors.New("pair error") 14 | ) 15 | -------------------------------------------------------------------------------- /internal/match/wire.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import "github.com/google/wire" 4 | 5 | var ProviderSet = wire.NewSet(NewMatchPool) 6 | -------------------------------------------------------------------------------- /internal/server/grpc.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | "github.com/shopspring/decimal" 6 | pb "lightning-engine/api/match/v1" 7 | "lightning-engine/internal/match" 8 | "lightning-engine/internal/status" 9 | "lightning-engine/models" 10 | ) 11 | 12 | type Server struct { 13 | pb.UnimplementedMatchServiceServer 14 | pool *match.MatchPool 15 | status *status.Status 16 | } 17 | 18 | func NewServer(status *status.Status, pool *match.MatchPool) *Server { 19 | return &Server{ 20 | pool: pool, 21 | status: status, 22 | } 23 | } 24 | 25 | func (s *Server) AddOrder(ctx context.Context, in *pb.AddOrderRequest) (*pb.AddOrderReply, error) { 26 | price, err := decimal.NewFromString(in.Order.Price) 27 | if err != nil { 28 | return &pb.AddOrderReply{Result: &pb.ReplyResult{Code: 400, Msg: "price error"}}, err 29 | } 30 | amount, err := decimal.NewFromString(in.Order.Amount) 31 | if err != nil { 32 | return &pb.AddOrderReply{Result: &pb.ReplyResult{Code: 400, Msg: "amount error"}}, err 33 | } 34 | order := &models.Order{ 35 | Id: in.Order.Id, 36 | UserId: in.Order.UserId, 37 | Pair: in.Order.Pair, 38 | Price: price, 39 | Amount: amount, 40 | Side: in.Order.Side, 41 | Type: in.Order.Type, 42 | TimeInForce: in.Order.TimeInForce, 43 | } 44 | err = s.pool.AddOrder(order) 45 | if err != nil { 46 | return &pb.AddOrderReply{Result: &pb.ReplyResult{Code: 400, Msg: err.Error()}}, err 47 | } 48 | return &pb.AddOrderReply{Result: &pb.ReplyResult{Code: 0, Msg: "success"}}, nil 49 | } 50 | 51 | // CancelOrder 撤单 52 | func (s *Server) CancelOrder(ctx context.Context, in *pb.CancelOrderRequest) (*pb.CancelOrderReply, error) { 53 | err := s.pool.CancelOrder(in.Pair, in.Id) 54 | if err != nil { 55 | return &pb.CancelOrderReply{Result: &pb.ReplyResult{Code: 400, Msg: err.Error()}}, err 56 | } 57 | return &pb.CancelOrderReply{Result: &pb.ReplyResult{Code: 0, Msg: "success"}}, nil 58 | } 59 | -------------------------------------------------------------------------------- /internal/server/wire.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import "github.com/google/wire" 4 | 5 | var ProviderSet = wire.NewSet(NewServer) 6 | -------------------------------------------------------------------------------- /internal/status/status.go: -------------------------------------------------------------------------------- 1 | package status 2 | 3 | import ( 4 | "context" 5 | "sync" 6 | ) 7 | 8 | // 服务运行状态 9 | type Status struct { 10 | wg sync.WaitGroup // 等待所有goroutine安全退出 11 | ctx context.Context 12 | cancel func() 13 | } 14 | 15 | func NewStatus() *Status { 16 | ctx, cancel := context.WithCancel(context.Background()) 17 | return &Status{ 18 | wg: sync.WaitGroup{}, 19 | ctx: ctx, 20 | cancel: cancel, 21 | } 22 | } 23 | 24 | // Context context 25 | func (self *Status) Context() context.Context { 26 | return self.ctx 27 | } 28 | 29 | // Stop 退出程序 30 | func (self *Status) Stop() { 31 | self.cancel() 32 | } 33 | 34 | // Add 添加退出等待 35 | func (self *Status) Add(delta int) { 36 | self.wg.Add(delta) 37 | } 38 | 39 | // Done 减少退出等待 40 | func (self *Status) Done() { 41 | self.wg.Done() 42 | } 43 | 44 | // Wait 等待执行完成 45 | func (self *Status) Wait() { 46 | self.wg.Wait() 47 | } 48 | -------------------------------------------------------------------------------- /internal/status/sysSignalHandle.go: -------------------------------------------------------------------------------- 1 | package status 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "os/signal" 8 | "syscall" 9 | ) 10 | 11 | type SysSignalHandle struct { 12 | status *Status 13 | } 14 | 15 | func NewSysSignalHandle(status *Status) *SysSignalHandle { 16 | return &SysSignalHandle{status: status} 17 | } 18 | 19 | // Begin 监听退出信号 20 | func (h *SysSignalHandle) Begin() { 21 | ss := newSignalSet() 22 | 23 | ss.register(syscall.SIGINT, h.dealSysSignal) 24 | ss.register(syscall.SIGTERM, h.dealSysSignal) 25 | ss.register(syscall.SIGQUIT, h.dealSysSignal) 26 | 27 | c := make(chan os.Signal) 28 | var sigs []os.Signal 29 | for sig := range ss.m { 30 | sigs = append(sigs, sig) 31 | } 32 | signal.Notify(c, sigs...) 33 | 34 | log.Println("启动监听终端信号成功") 35 | for { 36 | sig := <-c 37 | if err := ss.handle(sig, nil); err != nil { 38 | log.Println("unknown signal received: ", sig) 39 | } 40 | } 41 | } 42 | 43 | // 信号处理 44 | func (h *SysSignalHandle) dealSysSignal(s os.Signal, arg interface{}) { 45 | msg := fmt.Sprintf("handle signal: %v", s) 46 | log.Println(msg) 47 | log.Println("正在安全退出服务...") 48 | h.status.Stop() 49 | h.status.Wait() 50 | 51 | log.Println("安全退出完成") 52 | os.Exit(0) 53 | } 54 | 55 | type signalHandler func(s os.Signal, arg interface{}) 56 | 57 | type signalSet struct { 58 | m map[os.Signal]signalHandler 59 | } 60 | 61 | func newSignalSet() *signalSet { 62 | ss := new(signalSet) 63 | ss.m = make(map[os.Signal]signalHandler) 64 | return ss 65 | } 66 | 67 | func (self *signalSet) register(s os.Signal, handler signalHandler) { 68 | self.m[s] = handler 69 | } 70 | 71 | func (self *signalSet) handle(sig os.Signal, arg interface{}) error { 72 | if _, ok := self.m[sig]; ok { 73 | self.m[sig](sig, arg) 74 | return nil 75 | } 76 | return fmt.Errorf("no handler available for signal %v", sig) 77 | } 78 | -------------------------------------------------------------------------------- /internal/status/wire.go: -------------------------------------------------------------------------------- 1 | package status 2 | 3 | import "github.com/google/wire" 4 | 5 | var ProviderSet = wire.NewSet(NewStatus, NewSysSignalHandle) 6 | -------------------------------------------------------------------------------- /models/order.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "github.com/shopspring/decimal" 5 | ) 6 | 7 | const ( 8 | Buy = "buy" 9 | Sell = "sell" 10 | 11 | Limit = "limit" 12 | Market = "market" 13 | Cancel = "cancel" 14 | 15 | TimeInForceGTC = "GTC" // 订单一直有效,知道被成交或者取消 16 | TimeInForceIOC = "IOC" // 无法立即成交的部分就撤销 17 | TimeInForceFOK = "FOK" // 无法全部立即成交就撤销 18 | ) 19 | 20 | // Order 订单, 实现INodeValue接口,存放在节点中 21 | type Order struct { 22 | Id string `json:"i"` // 订单id 23 | UserId int64 `json:"u"` // 用户id 24 | Pair string `json:"P"` // 交易对 25 | Price decimal.Decimal `json:"p"` // 价格 26 | Amount decimal.Decimal `json:"a"` // 数量 27 | Side string `json:"s"` // 订单方向 buy/sell 28 | Type string `json:"t"` // 订单类型 limit/market 29 | TimeInForce string `json:"f"` // 订单有效时间,type为limit时才生效 GTC/IOC/FOK 30 | } 31 | 32 | func (o *Order) GetId() string { 33 | return o.Id 34 | } 35 | 36 | func (o *Order) GetAmount() decimal.Decimal { 37 | return o.Amount 38 | } 39 | 40 | func (o *Order) GetUserId() int64 { 41 | return o.UserId 42 | } 43 | 44 | func (o *Order) SetAmount(d decimal.Decimal) { 45 | o.Amount = d 46 | } 47 | -------------------------------------------------------------------------------- /models/trade.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type Trade struct { 4 | Id string `json:"i"` // 成交单id 5 | Pair string `json:"P"` // 交易对 6 | MakerId string `json:"mi"` // maker订单id 7 | TakerId string `json:"ti"` // taker订单id 8 | MakerUser int64 `json:"mu"` // maker用户id 9 | TakerUser int64 `json:"tu"` // taker用户id 10 | Price string `json:"p"` // 成交价 11 | Amount string `json:"a"` // 成交数量 12 | TakerOrderSide string `json:"s"` // taker订单方向 buy/sell 13 | TakerOrderType string `json:"t"` // taker订单类型 limit/market/cancel 14 | TakerTimeInForce string `json:"f"` // taker订单有效时间,type为limit时才生效 GTC/IOC/FOK 15 | Ts int64 `json:"ts"` // 成交时间 16 | } 17 | -------------------------------------------------------------------------------- /mq/imq.go: -------------------------------------------------------------------------------- 1 | package mq 2 | 3 | import "lightning-engine/models" 4 | 5 | // IMQ 6 | // 消息队列接口,撮合引擎只撮合盘口订单,成交单需推送到消息队列,下游服务消费队列,并进行业务处理。 7 | // 下游处理包括不限于:落盘成交单、落盘委托单、用户资金操作、k线处理等。 8 | // 需根据对应项目使用的消息队列,编写对应的实现类。 9 | type IMQ interface { 10 | PushTrade(...models.Trade) // 推送成交单。注:成交单包括已取消的委托单,需要特殊处理。 11 | } 12 | -------------------------------------------------------------------------------- /mq/wire.go: -------------------------------------------------------------------------------- 1 | package mq 2 | 3 | import "github.com/google/wire" 4 | 5 | var ProviderSet = wire.NewSet(NewYourMq) 6 | -------------------------------------------------------------------------------- /mq/yourMq.go: -------------------------------------------------------------------------------- 1 | package mq 2 | 3 | import ( 4 | "lightning-engine/models" 5 | "log" 6 | ) 7 | 8 | type YourMq struct { 9 | } 10 | 11 | func NewYourMq() IMQ { 12 | return &YourMq{} 13 | } 14 | 15 | func (mq *YourMq) PushTrade(trades ...models.Trade) { 16 | // 根据自己使用的队列,实现IMQ接口相应的方法 17 | log.Printf("成交单: %+v\n", trades) 18 | } 19 | -------------------------------------------------------------------------------- /pqueue/skiplist/skipList.go: -------------------------------------------------------------------------------- 1 | package skiplist 2 | 3 | import ( 4 | "errors" 5 | "github.com/shopspring/decimal" 6 | "lightning-engine/pqueue" 7 | "math/rand" 8 | "time" 9 | ) 10 | 11 | const ( 12 | SKIPLIST_DEFAULT_MAX_LEVEL = 36 13 | SKIPLIST_DEFAULT_P = 0.25 // 跳表加一层索引的概率 14 | ) 15 | 16 | // SkipList 跳表,从小到大排序 17 | type SkipList struct { 18 | // 头节点,尾节点 19 | head, tail *SkipListNode 20 | // node总数 21 | size int64 22 | // 当前最高level 23 | level int 24 | // 最多有多少level 25 | maxLevel int 26 | } 27 | 28 | func NewSkipList(options ...Option) (*SkipList, error) { 29 | rand.Seed(time.Now().UnixNano()) 30 | skipList := &SkipList{ 31 | head: nil, 32 | tail: nil, 33 | size: 0, 34 | level: 1, 35 | maxLevel: SKIPLIST_DEFAULT_MAX_LEVEL, 36 | } 37 | 38 | for _, option := range options { 39 | if err := option(skipList); err != nil { 40 | return nil, err 41 | } 42 | } 43 | 44 | skipList.head = NewSkipListNode(skipList.maxLevel, decimal.NewFromInt(0), nil) 45 | 46 | return skipList, nil 47 | } 48 | 49 | // Option 可选参数设置 50 | type Option func(skipList *SkipList) error 51 | 52 | // MaxLevel 设置MaxLevel 53 | func MaxLevel(maxLevel int) Option { 54 | return func(skipList *SkipList) error { 55 | if maxLevel <= 0 { 56 | return errors.New("SkipList max_level must gte than 1") 57 | } 58 | skipList.maxLevel = maxLevel 59 | return nil 60 | } 61 | } 62 | 63 | // Insert 插入节点 64 | func (list *SkipList) Insert(score decimal.Decimal, value pqueue.INodeValue) *SkipListNode { 65 | rank := make([]int64, list.maxLevel) // 新增节点每一层上是第几个节点 66 | update := make([]*SkipListNode, list.maxLevel) // 新增节点每一层的上一个节点 67 | p := list.head 68 | for i := list.level - 1; i >= 0; i-- { 69 | if i == list.level-1 { 70 | rank[i] = 0 71 | } else { 72 | rank[i] = rank[i+1] 73 | } 74 | // 下个节点存在,并且下个节点的score小于等于score时(score相同,按时间排序) 75 | for p.Next(i) != nil && p.Next(i).score.LessThanOrEqual(score) { 76 | rank[i] += p.level[i].span 77 | p = p.Next(i) 78 | } 79 | update[i] = p 80 | } 81 | 82 | level := list.randLevel() 83 | 84 | if level > list.level { 85 | for i := list.level; i < level; i++ { 86 | rank[i] = 0 87 | update[i] = list.head 88 | update[i].SetSpan(i, list.size) 89 | } 90 | list.level = level 91 | } 92 | newNode := NewSkipListNode(level, score, value) 93 | 94 | for i := 0; i < level; i++ { 95 | newNode.SetNext(i, update[i].Next(i)) 96 | update[i].SetNext(i, newNode) 97 | 98 | newNode.SetSpan(i, update[i].Span(i)-(rank[0]-rank[i])) 99 | update[i].SetSpan(i, rank[0]-rank[i]+1) 100 | } 101 | 102 | // 处理新增节点的span 103 | for i := level; i < list.level; i++ { 104 | update[i].level[i].span++ 105 | } 106 | // 处理新增节点的后退指针 107 | if update[0] == list.head { 108 | newNode.backward = nil 109 | } else { 110 | newNode.backward = update[0] 111 | } 112 | // 判断新插入的节点是不是最后一个节点 113 | if newNode.Next(0) != nil { 114 | newNode.Next(0).backward = newNode 115 | } else { 116 | list.tail = newNode 117 | } 118 | list.size++ 119 | return newNode 120 | } 121 | 122 | // randLevel 随机索引层数 123 | func (list *SkipList) randLevel() int { 124 | level := 1 125 | for rand.Int31n(100) < int32(100*SKIPLIST_DEFAULT_P) && level < list.maxLevel { 126 | level++ 127 | } 128 | return level 129 | } 130 | 131 | // Find 查找节点,并返回路径 132 | func (list *SkipList) Find(score decimal.Decimal, id string) (*SkipListNode, []*SkipListNode) { 133 | update := make([]*SkipListNode, list.maxLevel) 134 | 135 | p := list.head 136 | for i := list.level - 1; i >= 0; i-- { 137 | for p.Next(i) != nil && p.Next(i).score.LessThan(score) { 138 | p = p.Next(i) 139 | } 140 | update[i] = p 141 | } 142 | 143 | // 遍历最后一层,比较id 144 | for p.Next(0) != nil && p.Next(0).score.LessThanOrEqual(score) { 145 | p = p.Next(0) 146 | if p.score.Equal(score) && p.value.GetId() == id { 147 | break 148 | } 149 | update[0] = p 150 | } 151 | 152 | if p.score.Equal(score) && p.value.GetId() == id { 153 | return p, update 154 | } 155 | return nil, nil 156 | } 157 | 158 | // Update 更新节点值 159 | func (list *SkipList) Update(score decimal.Decimal, id string, value pqueue.INodeValue) *SkipListNode { 160 | node, _ := list.Find(score, id) 161 | node.value = value 162 | return node 163 | } 164 | 165 | // Delete 删除节点 166 | func (list *SkipList) Delete(score decimal.Decimal, id string) { 167 | node, update := list.Find(score, id) 168 | if node == nil || node == list.head { 169 | return 170 | } 171 | 172 | for i := 0; i < list.level; i++ { 173 | if update[i].Next(i) == node { 174 | // 修改span 175 | update[i].SetSpan(i, update[i].Span(i)+node.Span(i)-1) 176 | // 删除节点 177 | update[i].SetNext(i, node.Next(i)) 178 | } else { 179 | update[i].level[i].span-- 180 | } 181 | } 182 | 183 | // 处理node的后指针 184 | if node.Next(0) == nil { 185 | list.tail = update[0] 186 | } else { 187 | node.Next(0).backward = update[0] 188 | } 189 | 190 | // 处理删掉的是最高level的情况 191 | for list.level > 1 && list.head.Next(list.level-1) == nil { 192 | list.level-- 193 | } 194 | list.size-- 195 | } 196 | 197 | // First 获取第一个节点 198 | func (list *SkipList) First() *SkipListNode { 199 | if list.size == 0 { 200 | return nil 201 | } 202 | return list.head.Next(0) 203 | } 204 | -------------------------------------------------------------------------------- /pqueue/skiplist/skipListDesc.go: -------------------------------------------------------------------------------- 1 | package skiplist 2 | 3 | import ( 4 | "github.com/shopspring/decimal" 5 | "lightning-engine/pqueue" 6 | ) 7 | 8 | // SkipListDesc 倒叙跳表,从大到小排序 9 | type SkipListDesc struct { 10 | *SkipList 11 | } 12 | 13 | func NewSkipListDesc(options ...Option) (*SkipListDesc, error) { 14 | skipListDesc := &SkipListDesc{} 15 | skipList, err := NewSkipList(options...) 16 | if err != nil { 17 | return nil, err 18 | } 19 | skipListDesc.SkipList = skipList 20 | return skipListDesc, err 21 | } 22 | 23 | // Insert 插入节点 24 | func (list *SkipListDesc) Insert(score decimal.Decimal, value pqueue.INodeValue) *SkipListNode { 25 | rank := make([]int64, list.maxLevel) // 新增节点每一层上是第几个节点 26 | update := make([]*SkipListNode, list.maxLevel) // 新增节点每一层的上一个节点 27 | p := list.head 28 | for i := list.level - 1; i >= 0; i-- { 29 | if i == list.level-1 { 30 | rank[i] = 0 31 | } else { 32 | rank[i] = rank[i+1] 33 | } 34 | // 下个节点存在,并且下个节点的score大于等于score时(score相同,按时间排序) 35 | for p.Next(i) != nil && p.Next(i).score.GreaterThanOrEqual(score) { 36 | rank[i] += p.level[i].span 37 | p = p.Next(i) 38 | } 39 | update[i] = p 40 | } 41 | 42 | level := list.randLevel() 43 | 44 | if level > list.level { 45 | for i := list.level; i < level; i++ { 46 | rank[i] = 0 47 | update[i] = list.head 48 | update[i].SetSpan(i, list.size) 49 | } 50 | list.level = level 51 | } 52 | newNode := NewSkipListNode(level, score, value) 53 | 54 | for i := 0; i < level; i++ { 55 | newNode.SetNext(i, update[i].Next(i)) 56 | update[i].SetNext(i, newNode) 57 | 58 | newNode.SetSpan(i, update[i].Span(i)-(rank[0]-rank[i])) 59 | update[i].SetSpan(i, rank[0]-rank[i]+1) 60 | } 61 | 62 | // 处理新增节点的span 63 | for i := level; i < list.level; i++ { 64 | update[i].level[i].span++ 65 | } 66 | // 处理新增节点的后退指针 67 | if update[0] == list.head { 68 | newNode.backward = nil 69 | } else { 70 | newNode.backward = update[0] 71 | } 72 | // 判断新插入的节点是不是最后一个节点 73 | if newNode.Next(0) != nil { 74 | newNode.Next(0).backward = newNode 75 | } else { 76 | list.tail = newNode 77 | } 78 | list.size++ 79 | return newNode 80 | } 81 | 82 | // Find 查找节点,并返回路径 83 | func (list *SkipListDesc) Find(score decimal.Decimal, id string) (*SkipListNode, []*SkipListNode) { 84 | update := make([]*SkipListNode, list.maxLevel) 85 | 86 | p := list.head 87 | for i := list.level - 1; i >= 0; i-- { 88 | for p.Next(i) != nil && p.Next(i).score.GreaterThan(score) { 89 | p = p.Next(i) 90 | } 91 | update[i] = p 92 | } 93 | 94 | // 遍历最后一层,比较id 95 | for p.Next(0) != nil && p.Next(0).score.GreaterThanOrEqual(score) { 96 | p = p.Next(0) 97 | if p.score.Equal(score) && p.value.GetId() == id { 98 | break 99 | } 100 | update[0] = p 101 | } 102 | 103 | if p.score.Equal(score) && p.value.GetId() == id { 104 | return p, update 105 | } 106 | return nil, nil 107 | } 108 | 109 | // Delete 删除节点 110 | func (list *SkipListDesc) Delete(score decimal.Decimal, id string) { 111 | node, update := list.Find(score, id) 112 | if node == nil || node == list.head { 113 | return 114 | } 115 | 116 | for i := 0; i < list.level; i++ { 117 | if update[i].Next(i) == node { 118 | // 修改span 119 | update[i].SetSpan(i, update[i].Span(i)+node.Span(i)-1) 120 | // 删除节点 121 | update[i].SetNext(i, node.Next(i)) 122 | } else { 123 | update[i].level[i].span-- 124 | } 125 | } 126 | 127 | // 处理node的后指针 128 | if node.Next(0) == nil { 129 | list.tail = update[0] 130 | } else { 131 | node.Next(0).backward = update[0] 132 | } 133 | 134 | // 处理删掉的是最高level的情况 135 | for list.level > 1 && list.head.Next(list.level-1) == nil { 136 | list.level-- 137 | } 138 | list.size-- 139 | } 140 | -------------------------------------------------------------------------------- /pqueue/skiplist/skipList_test.go: -------------------------------------------------------------------------------- 1 | package skiplist 2 | 3 | import ( 4 | "github.com/shopspring/decimal" 5 | "strconv" 6 | "testing" 7 | ) 8 | 9 | type entrust struct { 10 | Id string 11 | Amount decimal.Decimal 12 | } 13 | 14 | func (e *entrust) GetId() string { 15 | return e.Id 16 | } 17 | 18 | func (e *entrust) GetAmount() decimal.Decimal { 19 | return e.Amount 20 | } 21 | 22 | var skiplist *SkipList 23 | var skiplistDesc *SkipListDesc 24 | 25 | func TestMain(m *testing.M) { 26 | skiplist, _ = NewSkipList() 27 | for i := 0; i < 100; i++ { 28 | e := &entrust{ 29 | Id: strconv.Itoa(i), 30 | Amount: decimal.NewFromInt(int64(i)), 31 | } 32 | skiplist.Insert(decimal.NewFromInt(int64(i)), e) 33 | } 34 | skiplistDesc, _ = NewSkipListDesc() 35 | for i := 0; i < 100; i++ { 36 | e := &entrust{ 37 | Id: strconv.Itoa(i), 38 | Amount: decimal.NewFromInt(int64(i)), 39 | } 40 | skiplistDesc.Insert(decimal.NewFromInt(int64(i)), e) 41 | } 42 | m.Run() 43 | } 44 | 45 | func TestSkipList_Insert(t *testing.T) { 46 | skiplist, _ := NewSkipList() 47 | 48 | for i := 0; i < 100000; i++ { 49 | e := &entrust{ 50 | Id: "1", 51 | Amount: decimal.Decimal{}, 52 | } 53 | skiplist.Insert(decimal.NewFromInt(int64(i)), e) 54 | } 55 | t.Logf("size: %d, level: %d\n", skiplist.size, skiplist.level) 56 | } 57 | 58 | func TestSkipList_Find(t *testing.T) { 59 | success := 0 60 | failed := 0 61 | for i := 0; i < 100000; i++ { 62 | node, _ := skiplist.Find(decimal.NewFromInt(int64(i)), strconv.Itoa(i)) 63 | if node == nil || !node.score.Equal(decimal.NewFromInt(int64(i))) { 64 | failed++ 65 | } else { 66 | success++ 67 | } 68 | } 69 | t.Logf("success:%d failed:%d", success, failed) 70 | } 71 | 72 | func TestRange(t *testing.T) { 73 | p := skiplist.head 74 | for p != nil { 75 | p = p.Next(0) 76 | } 77 | } 78 | 79 | func TestSkipList_Delete(t *testing.T) { 80 | for i := 0; i < 100; i++ { 81 | skiplist.Delete(decimal.NewFromInt(int64(i)), strconv.Itoa(i)) 82 | } 83 | if skiplist.size > 0 || skiplist.level > 1 { 84 | t.Error("error", skiplist.size, skiplist.level) 85 | } else { 86 | t.Logf("success") 87 | } 88 | } 89 | 90 | func TestSkipListDesc_Delete(t *testing.T) { 91 | for i := 0; i < 100; i++ { 92 | skiplistDesc.Delete(decimal.NewFromInt(int64(i)), strconv.Itoa(i)) 93 | } 94 | if skiplistDesc.size > 0 || skiplistDesc.level > 1 { 95 | t.Error("error", skiplistDesc.size, skiplistDesc.level) 96 | } else { 97 | t.Logf("success") 98 | } 99 | } 100 | 101 | func TestSkipList_First(t *testing.T) { 102 | f1 := skiplist.First() 103 | f2 := skiplistDesc.First() 104 | t.Logf("f1.first = %s, f2.first = %s", f1.Value().GetAmount(), f2.Value().GetAmount()) 105 | } 106 | -------------------------------------------------------------------------------- /pqueue/skiplist/skipNode.go: -------------------------------------------------------------------------------- 1 | package skiplist 2 | 3 | import ( 4 | "github.com/shopspring/decimal" 5 | "lightning-engine/pqueue" 6 | ) 7 | 8 | type SkipListLevel struct { 9 | // 向前指针 10 | forward *SkipListNode 11 | // 到下一个node的距离 12 | span int64 13 | } 14 | 15 | type SkipListNode struct { 16 | // 向后指针 17 | backward *SkipListNode 18 | // 索引 19 | level []SkipListLevel 20 | // 存储的值,需要实现INodeValue接口 21 | value pqueue.INodeValue 22 | // 用于排序,使用高精度 23 | score decimal.Decimal 24 | } 25 | 26 | func NewSkipListNode(level int, score decimal.Decimal, value pqueue.INodeValue) *SkipListNode { 27 | return &SkipListNode{ 28 | backward: nil, 29 | level: make([]SkipListLevel, level), 30 | value: value, 31 | score: score, 32 | } 33 | } 34 | 35 | // Next 第i层的下个元素 36 | func (node *SkipListNode) Next(i int) *SkipListNode { 37 | return node.level[i].forward 38 | } 39 | 40 | // SetNext 设置第i层的下个元素 41 | func (node *SkipListNode) SetNext(i int, next *SkipListNode) { 42 | node.level[i].forward = next 43 | } 44 | 45 | // Span 第层的span值 46 | func (node *SkipListNode) Span(i int) int64 { 47 | return node.level[i].span 48 | } 49 | 50 | // SetSpan 设置第i层的span 51 | func (node *SkipListNode) SetSpan(i int, span int64) { 52 | node.level[i].span = span 53 | } 54 | 55 | // Pre 上一个元素 56 | func (node *SkipListNode) Pre() *SkipListNode { 57 | return node.backward 58 | } 59 | 60 | // Value 获取节点值 61 | func (node *SkipListNode) Value() pqueue.INodeValue { 62 | return node.value 63 | } 64 | 65 | // Score 获取节点分数 66 | func (node *SkipListNode) Score() decimal.Decimal { 67 | return node.score 68 | } 69 | -------------------------------------------------------------------------------- /pqueue/types.go: -------------------------------------------------------------------------------- 1 | package pqueue 2 | 3 | import "github.com/shopspring/decimal" 4 | 5 | // INodeValue 节点存储的值 6 | type INodeValue interface { 7 | // GetId 返回节点值的唯一ID 8 | GetId() string 9 | // GetAmount 返回节点值的数量 10 | GetAmount() decimal.Decimal 11 | // GetUserId 返回节点值的用户id 12 | GetUserId() int64 13 | // SetAmount 更新节点值的数量 14 | SetAmount(decimal.Decimal) 15 | } 16 | -------------------------------------------------------------------------------- /test/app_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "google.golang.org/grpc" 7 | "google.golang.org/grpc/credentials/insecure" 8 | pb "lightning-engine/api/match/v1" 9 | "testing" 10 | ) 11 | 12 | var client pb.MatchServiceClient 13 | 14 | func TestMain(m *testing.M) { 15 | conn, _ := grpc.Dial("localhost:8080", grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock()) 16 | client = pb.NewMatchServiceClient(conn) 17 | m.Run() 18 | } 19 | 20 | func TestAdd(t *testing.T) { 21 | req := &pb.AddOrderRequest{Order: &pb.Order{ 22 | Id: "1", 23 | UserId: 2, 24 | Pair: "BTC-USDT", 25 | Price: "21000", 26 | Amount: "2", 27 | Side: "buy", 28 | Type: "limit", 29 | TimeInForce: "GTC", 30 | }} 31 | reply, err := client.AddOrder(context.Background(), req) 32 | 33 | req = &pb.AddOrderRequest{Order: &pb.Order{ 34 | Id: "2", 35 | UserId: 2, 36 | Pair: "BTC-USDT", 37 | Price: "21000", 38 | Amount: "1", 39 | Side: "sell", 40 | Type: "limit", 41 | TimeInForce: "GTC", 42 | }} 43 | reply, err = client.AddOrder(context.Background(), req) 44 | fmt.Println(reply, err) 45 | } 46 | func TestCancel(t *testing.T) { 47 | req := &pb.CancelOrderRequest{ 48 | Pair: "BTC-USDT", 49 | Id: "1", 50 | } 51 | reply, err := client.CancelOrder(context.Background(), req) 52 | fmt.Println(reply, err) 53 | } 54 | -------------------------------------------------------------------------------- /utils/string.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "strconv" 5 | ) 6 | 7 | // GenTradeId 生成成交单id 8 | func GenTradeId() string { 9 | return strconv.Itoa(int(NowUnixMilli())) 10 | } 11 | -------------------------------------------------------------------------------- /utils/time.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "time" 4 | 5 | // NowUnixMilli 当前毫秒时间戳 6 | func NowUnixMilli() int64 { 7 | return time.Now().UnixMilli() 8 | } 9 | --------------------------------------------------------------------------------