├── .github └── workflows │ ├── codeql-analysis.yml │ ├── go.yml │ └── greetings.yml ├── .gitignore ├── Dockerfile ├── Makefile ├── README.md ├── engine.proto ├── engine ├── cancel_limit_order.go ├── cancel_limit_order_test.go ├── order.go ├── order_book.go ├── order_book_test.go ├── order_node.go ├── order_node_test.go ├── order_test.go ├── order_type.go ├── order_type_test.go ├── process_limit_order.go ├── process_limit_order_test.go ├── process_market_order.go ├── process_market_order_test.go ├── side.go ├── side_test.go └── trade.go ├── engineGrpc └── engine.pb.go ├── go.mod ├── go.sum ├── main.go ├── server └── engine.go └── util └── bigdecimal.go /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ "master" ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ "master" ] 20 | schedule: 21 | - cron: '31 14 * * 1' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'go' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v3 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v2 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | 52 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 53 | # queries: security-extended,security-and-quality 54 | 55 | 56 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 57 | # If this step fails, then you should remove it and run the build manually (see below) 58 | - name: Autobuild 59 | uses: github/codeql-action/autobuild@v2 60 | 61 | # ℹ️ Command-line programs to run using the OS shell. 62 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 63 | 64 | # If the Autobuild fails above, remove it and uncomment the following three lines. 65 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 66 | 67 | # - run: | 68 | # echo "Run, Build Application using script" 69 | # ./location_of_script_within_repo/buildscript.sh 70 | 71 | - name: Perform CodeQL Analysis 72 | uses: github/codeql-action/analyze@v2 73 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | 11 | build: 12 | name: Build 13 | runs-on: ubuntu-latest 14 | steps: 15 | 16 | - name: Set up Go 1.x 17 | uses: actions/setup-go@v2 18 | with: 19 | go-version: ^1.13 20 | id: go 21 | 22 | - name: Check out code into the Go module directory 23 | uses: actions/checkout@v2 24 | 25 | - name: Get dependencies 26 | run: | 27 | go get -v -t -d ./... 28 | if [ -f Gopkg.toml ]; then 29 | curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh 30 | dep ensure 31 | fi 32 | 33 | - name: Build 34 | run: go build -v . 35 | 36 | - name: Test 37 | run: go test -v ./engine 38 | -------------------------------------------------------------------------------- /.github/workflows/greetings.yml: -------------------------------------------------------------------------------- 1 | name: Greetings 2 | 3 | on: [pull_request, issues] 4 | 5 | jobs: 6 | greeting: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/first-interaction@v1 10 | with: 11 | repo-token: ${{ secrets.GITHUB_TOKEN }} 12 | issue-message: 'Message that will be displayed on users'' first issue' 13 | pr-message: 'Message that will be displayed on users'' first pr' 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | 17 | test* 18 | 19 | main/ -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:alpine as builder 2 | 3 | ENV GO111MODULE=on \ 4 | CGO_ENABLED=0 \ 5 | GOOS=linux \ 6 | GOARCH=amd64 7 | 8 | WORKDIR /dist 9 | 10 | # Copy and download dependency using go mod 11 | COPY go.mod . 12 | COPY go.sum . 13 | RUN go mod download 14 | 15 | # Copy the code into the container 16 | COPY . . 17 | 18 | # Build the application 19 | RUN go build -o main . 20 | 21 | FROM gcr.io/distroless/static-debian10 22 | 23 | COPY --from=builder /dist/main . 24 | 25 | EXPOSE 9099 26 | # Command to run when starting the container 27 | CMD ["./main"] -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: protos 2 | 3 | protos: 4 | protoc -I ./ ./engine.proto --go_out=plugins=grpc:./engineGrpc -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Superfast Matching Engine 2 | Improved matching engine written in Go (Golang) 3 | 4 | 5 | ## Installation 6 | 7 | ```javascript 8 | go get github.com/Pantelwar/matching-engine/engine 9 | ``` 10 | -------------------------------------------------------------------------------- /engine.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | service Engine { 4 | rpc Process(Order) returns (OutputOrders); 5 | rpc ProcessMarket(Order) returns (OutputOrders); 6 | rpc Cancel(Order) returns (Order); 7 | rpc FetchBook(BookInput) returns (BookOutput); 8 | } 9 | 10 | message Order { 11 | Side Type = 1 [json_name = "type"]; 12 | string ID = 2 [json_name = "id"]; 13 | string Amount = 3 [json_name = "amount"]; 14 | string Price = 4 [json_name = "price"]; 15 | string Pair = 5 [json_name = "pair"]; 16 | } 17 | 18 | message OutputOrders { 19 | string OrdersProcessed = 1 [json_name = "orders_processed"]; 20 | string PartialOrder = 2 [json_name = "partial_order"]; 21 | } 22 | 23 | enum Side { 24 | buy = 0; 25 | sell = 1; 26 | } 27 | 28 | message BookInput { 29 | string pair = 1; 30 | int64 limit = 2; 31 | } 32 | 33 | message BookArray { 34 | repeated string price_amount = 1; 35 | } 36 | 37 | message BookOutput { 38 | repeated BookArray Buys = 1; 39 | repeated BookArray Sells = 2; 40 | } -------------------------------------------------------------------------------- /engine/cancel_limit_order.go: -------------------------------------------------------------------------------- 1 | package engine 2 | 3 | // CancelOrder remove the order from book and returns 4 | func (ob *OrderBook) CancelOrder(id string) *Order { 5 | ob.mutex.Lock() 6 | orderNode := ob.orders[id] 7 | ob.mutex.Unlock() 8 | 9 | if orderNode == nil { 10 | return nil 11 | } 12 | 13 | for i, order := range orderNode.Orders { 14 | if order.ID == id { 15 | // ob.orders[id].addOrder(*order) 16 | // ob.removeOrder(order, i) 17 | orderNode.removeOrder(i) 18 | // orderNode.updateVolume(-order.Amount) 19 | // orderNode.Orders = append(orderNode.Orders[:i], orderNode.Orders[i+1:]...) 20 | if len(orderNode.Orders) == 0 { 21 | ob.removeOrder(order) 22 | } 23 | ob.mutex.Lock() 24 | delete(ob.orders, id) 25 | ob.mutex.Unlock() 26 | return order 27 | } 28 | } 29 | return nil 30 | } 31 | -------------------------------------------------------------------------------- /engine/cancel_limit_order_test.go: -------------------------------------------------------------------------------- 1 | package engine 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestCancelOrder(t *testing.T) { 8 | var tests = []struct { 9 | input *Order 10 | }{ 11 | {NewOrder("b1", Buy, DecimalBig("5.0"), DecimalBig("7000.0"))}, 12 | {NewOrder("b2", Buy, DecimalBig("10.0"), DecimalBig("6000.0"))}, 13 | {NewOrder("b3", Buy, DecimalBig("11.0"), DecimalBig("7000.0"))}, 14 | {NewOrder("b4", Buy, DecimalBig("1.0"), DecimalBig("7000.0"))}, 15 | {NewOrder("s1", Sell, DecimalBig("5.0"), DecimalBig("8000.0"))}, 16 | {NewOrder("s2", Sell, DecimalBig("10.0"), DecimalBig("9000.0"))}, 17 | {NewOrder("s3", Sell, DecimalBig("11.0"), DecimalBig("9000.0"))}, 18 | {NewOrder("s4", Sell, DecimalBig("1.0"), DecimalBig("7500.0"))}, 19 | } 20 | ob := NewOrderBook() 21 | 22 | for _, tt := range tests { 23 | if tt.input.Type == Buy { 24 | ob.addBuyOrder(*tt.input) 25 | } else { 26 | ob.addSellOrder(*tt.input) 27 | } 28 | } 29 | 30 | on := ob.orders[tests[4].input.ID] 31 | 32 | order := ob.CancelOrder("s1") 33 | 34 | for _, o := range on.Orders { 35 | if o.ID == tests[4].input.ID { 36 | t.Fatal("Order is not removed from the OrderNode") 37 | } 38 | } 39 | 40 | if order == nil { 41 | t.Fatal("Order is not removed") 42 | } 43 | 44 | err := ob.removeOrder(order) 45 | if err == nil { 46 | t.Fatal("Order is not removed from Tree of Orderbook") 47 | } 48 | 49 | if ob.orders[order.ID] != nil { 50 | t.Fatal("Order is not removed from \"orders\" of Orderbook") 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /engine/order.go: -------------------------------------------------------------------------------- 1 | package engine 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "strconv" 8 | 9 | "github.com/Pantelwar/matching-engine/util" 10 | ) 11 | 12 | // Order describes the struct of the order 13 | type Order struct { 14 | Amount *util.StandardBigDecimal `json:"amount"` // validate:"gt=0"` 15 | Price *util.StandardBigDecimal `json:"price"` // validate:"gt=0"` 16 | ID string `json:"id"` // validate:"required"` 17 | Type Side `json:"type"` // validate:"side_validate"` 18 | } 19 | 20 | // func sideValidation(fl validator.FieldLevel) bool { 21 | // if fl.Field().Interface() != Buy && fl.Field().Interface() != Sell { 22 | // return false 23 | // } 24 | // return true 25 | // } 26 | 27 | // NewOrder returns *Order 28 | func NewOrder(id string, orderType Side, amount, price *util.StandardBigDecimal) *Order { 29 | o := &Order{ID: id, Type: orderType, Amount: amount, Price: price} 30 | return o 31 | } 32 | 33 | // FromJSON create the Order struct from json string 34 | func (order *Order) FromJSON(msg []byte) error { 35 | err := json.Unmarshal(msg, order) 36 | if err != nil { 37 | return err 38 | } 39 | return nil 40 | } 41 | 42 | // ToJSON returns json string of the order 43 | func (order *Order) ToJSON() ([]byte, error) { 44 | str, err := json.Marshal(order) 45 | return str, err 46 | } 47 | 48 | // String implements Stringer interface 49 | func (order *Order) String() string { 50 | amount := order.Amount.Float64() 51 | price := order.Price.Float64() 52 | 53 | return fmt.Sprintf("\"%s\":\n\tside: %v\n\tquantity: %s\n\tprice: %s\n", order.ID, order.Type, strconv.FormatFloat(amount, 'f', -1, 64), strconv.FormatFloat(price, 'f', -1, 64)) 54 | } 55 | 56 | // UnmarshalJSON implements json.Unmarshaler interface 57 | func (order *Order) UnmarshalJSON(data []byte) error { 58 | obj := struct { 59 | Type Side `json:"type"` // validate:"side_validate"` 60 | ID string `json:"id"` // validate:"required"` 61 | Amount string `json:"amount"` // validate:"required"` 62 | Price string `json:"price"` // validate:"required"` 63 | }{} 64 | 65 | if err := json.Unmarshal(data, &obj); err != nil { 66 | fmt.Println("Damn errr", err) 67 | return err 68 | } 69 | 70 | if obj.ID == "" { 71 | return errors.New("ID is not present") 72 | } 73 | if obj.Type == "" { 74 | return errors.New("invalid order type") 75 | } 76 | 77 | var err error 78 | order.Price, err = util.NewDecimalFromString(obj.Price) //.Quantize(8) 79 | if err != nil { 80 | fmt.Println("price", order.Price, err.Error()) 81 | return errors.New("invalid order price") 82 | } 83 | order.Amount, err = util.NewDecimalFromString(obj.Amount) //.Quantize(8) 84 | if err != nil { 85 | return errors.New("invalid order amount") 86 | } 87 | 88 | order.Type = obj.Type 89 | order.ID = obj.ID 90 | 91 | price := order.Price.Float64() 92 | if price <= 0 { 93 | return errors.New("Order price should be greater than zero") 94 | } 95 | amount := order.Amount.Float64() 96 | if amount <= 0 { 97 | return errors.New("Order amount should be greater than zero") 98 | } 99 | return nil 100 | } 101 | 102 | // MarshalJSON implements json.Marshaler interface 103 | func (order *Order) MarshalJSON() ([]byte, error) { 104 | return json.Marshal( 105 | &struct { 106 | Type string `json:"type"` 107 | ID string `json:"id"` 108 | Amount string `json:"amount"` 109 | Price string `json:"price"` 110 | }{ 111 | Type: order.Type.String(), 112 | ID: order.ID, 113 | Amount: order.Amount.String(), 114 | Price: order.Price.String(), 115 | }, 116 | ) 117 | } 118 | -------------------------------------------------------------------------------- /engine/order_book.go: -------------------------------------------------------------------------------- 1 | package engine 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "math" 8 | "strconv" 9 | "sync" 10 | 11 | "github.com/Pantelwar/binarytree" 12 | "github.com/Pantelwar/matching-engine/util" 13 | ) 14 | 15 | // OrderBook type 16 | type OrderBook struct { 17 | BuyTree *binarytree.BinaryTree 18 | SellTree *binarytree.BinaryTree 19 | orderLimitRange int 20 | orders map[string]*OrderNode // orderID -> *Order (*list.Element.Value.(*Order)) 21 | mutex *sync.Mutex 22 | } 23 | 24 | // Book ... 25 | type Book struct { 26 | Buys []orderinfo `json:"buys"` 27 | Sells []orderinfo `json:"sells"` 28 | } 29 | 30 | type orderinfo struct { 31 | Price *util.StandardBigDecimal `json:"price"` 32 | Amount *util.StandardBigDecimal `json:"amount"` 33 | } 34 | 35 | // MarshalJSON implements json.Marshaler interface 36 | func (ob *OrderBook) MarshalJSON() ([]byte, error) { 37 | buys := []orderinfo{} 38 | // var orderSideBuy []string 39 | ob.BuyTree.Root.InOrderTraverse(func(i float64) { 40 | // result = append(result, fmt.Sprintf("%#v", i)) 41 | // fmt.Println("Key", i) 42 | node := ob.BuyTree.Root.SearchSubTree(i) 43 | node.Data.(*OrderType).Tree.Root.InOrderTraverse(func(i float64) { 44 | // result = append(result, fmt.Sprintf("%#v", i)) 45 | // fmt.Println(" value", i) 46 | var b orderinfo 47 | // res := fmt.Sprintf("%#f -> ", i) 48 | b.Price = util.NewDecimalFromFloat(i) 49 | subNode := node.Data.(*OrderType).Tree.Root.SearchSubTree(i) 50 | // fmt.Printf("subnode: %#v\n", subNode) 51 | // fmt.Printf("volume:%#v, %#v\n\n", subNode.Data.(*OrderNode).Volume, len(subNode.Data.(*OrderNode).Orders)) 52 | // res += fmt.Sprintf("%#f", subNode.Data.(*OrderNode).Volume) 53 | b.Amount = subNode.Data.(*OrderNode).Volume 54 | // fmt.Println("res b", res) 55 | // orderSideBuy = append(orderSideBuy, res) 56 | buys = append(buys, b) 57 | }) 58 | }) 59 | 60 | sells := []orderinfo{} 61 | ob.SellTree.Root.InOrderTraverse(func(i float64) { 62 | // result = append(result, fmt.Sprintf("%#v", i)) 63 | // fmt.Println("Key lol", i) 64 | node := ob.SellTree.Root.SearchSubTree(i) 65 | node.Data.(*OrderType).Tree.Root.InOrderTraverse(func(i float64) { 66 | // result = append(result, fmt.Sprintf("%#v", i)) 67 | // fmt.Println(" value", i) 68 | var b orderinfo 69 | // res := fmt.Sprintf("%#f -> ", i) 70 | b.Price = util.NewDecimalFromFloat(i) 71 | subNode := node.Data.(*OrderType).Tree.Root.SearchSubTree(i) 72 | // fmt.Printf("subnode: %#v\n", subNode) 73 | // fmt.Printf("volume:%#v, %#v\n\n", subNode.Data.(*OrderNode).Volume, len(subNode.Data.(*OrderNode).Orders)) 74 | // res += fmt.Sprintf("%#f", subNode.Data.(*OrderNode).Volume) 75 | // fmt.Println("res", res) 76 | b.Amount = subNode.Data.(*OrderNode).Volume 77 | // fmt.Println("res b", res) 78 | // orderSideBuy = append(orderSideBuy, res) 79 | buys = append(sells, b) 80 | }) 81 | }) 82 | 83 | // res := ob.GetOrders() 84 | return json.Marshal( 85 | &Book{ 86 | Buys: buys, 87 | Sells: sells, 88 | }, 89 | ) 90 | } 91 | 92 | // BookArray ... 93 | type BookArray struct { 94 | Buys [][]string `json:"buys"` 95 | Sells [][]string `json:"sells"` 96 | } 97 | 98 | // GetOrders implements json.Marshaler interface 99 | func (ob *OrderBook) GetOrders(limit int64) *BookArray { 100 | buys := [][]string{} 101 | ob.BuyTree.Root.InReverseOrderTraverse(func(i float64) { 102 | node := ob.BuyTree.Root.SearchSubTree(i) 103 | node.Data.(*OrderType).Tree.Root.InReverseOrderTraverse(func(i float64) { 104 | if int64(len(buys)) >= limit && limit != 0 { 105 | return 106 | } 107 | var b []string 108 | subNode := node.Data.(*OrderType).Tree.Root.SearchSubTree(i) 109 | if subNode != nil { 110 | price := strconv.FormatFloat(i, 'f', -1, 64) 111 | b = append(b, price) 112 | 113 | amount := subNode.Data.(*OrderNode).Volume 114 | b = append(b, amount.String()) 115 | buys = append(buys, b) 116 | } 117 | }) 118 | }) 119 | 120 | sells := [][]string{} 121 | ob.SellTree.Root.InOrderTraverse(func(i float64) { 122 | node := ob.SellTree.Root.SearchSubTree(i) 123 | node.Data.(*OrderType).Tree.Root.InOrderTraverse(func(i float64) { 124 | if int64(len(sells)) >= limit && limit != 0 { 125 | return 126 | } 127 | var b []string 128 | subNode := node.Data.(*OrderType).Tree.Root.SearchSubTree(i) 129 | if subNode != nil { 130 | price := strconv.FormatFloat(i, 'f', -1, 64) 131 | b = append(b, price) 132 | 133 | amount := subNode.Data.(*OrderNode).Volume 134 | b = append(b, amount.String()) 135 | sells = append(sells, b) 136 | } 137 | }) 138 | }) 139 | 140 | // res := ob.GetOrders() 141 | return &BookArray{ 142 | Buys: buys, 143 | Sells: sells, 144 | } 145 | } 146 | 147 | // String implements Stringer interface 148 | func (ob *OrderBook) String() string { 149 | result := "" 150 | var orderSideSell []string 151 | ob.SellTree.Root.InOrderTraverse(func(i float64) { 152 | // result = append(result, fmt.Sprintf("%#v", i)) 153 | // fmt.Println("Key lol", i) 154 | node := ob.SellTree.Root.SearchSubTree(i) 155 | node.Data.(*OrderType).Tree.Root.InOrderTraverse(func(i float64) { 156 | // result = append(result, fmt.Sprintf("%#v", i)) 157 | // fmt.Println(" value", i) 158 | res := strconv.FormatFloat(i, 'f', -1, 64) + " -> " 159 | subNode := node.Data.(*OrderType).Tree.Root.SearchSubTree(i) 160 | if subNode != nil { 161 | // fmt.Printf("subnode: %#v\n", subNode) 162 | // fmt.Printf("volume:%#v, %#v\n\n", subNode.Data.(*OrderNode).Volume, len(subNode.Data.(*OrderNode).Orders)) 163 | vol := subNode.Data.(*OrderNode).Volume.Float64() 164 | res += strconv.FormatFloat(vol, 'f', -1, 64) //subNode.Data.(*OrderNode).Volume.String() // strings.Trim(subNode.Data.(*OrderNode).Volume.String(), "0") 165 | // fmt.Println("res", res) 166 | orderSideSell = append(orderSideSell, res) 167 | } 168 | }) 169 | }) 170 | // fmt.Println() 171 | // fmt.Println("sell orders") 172 | sells := "" 173 | for _, o := range orderSideSell { 174 | // fmt.Println(o) 175 | sells = o + "\n" + sells 176 | } 177 | result = sells + "------------------------------------------\n" 178 | 179 | var orderSideBuy []string 180 | ob.BuyTree.Root.InOrderTraverse(func(i float64) { 181 | // result = append(result, fmt.Sprintf("%#v", i)) 182 | // fmt.Println("Key", i) 183 | node := ob.BuyTree.Root.SearchSubTree(i) 184 | node.Data.(*OrderType).Tree.Root.InOrderTraverse(func(i float64) { 185 | // result = append(result, fmt.Sprintf("%#v", i)) 186 | // fmt.Println(" value", i) 187 | res := strconv.FormatFloat(i, 'f', -1, 64) + " -> " 188 | subNode := node.Data.(*OrderType).Tree.Root.SearchSubTree(i) 189 | if subNode != nil { 190 | // fmt.Printf("subnode: %#v\n", subNode) 191 | vol := subNode.Data.(*OrderNode).Volume.Float64() 192 | res += strconv.FormatFloat(vol, 'f', -1, 64) //subNode.Data.(*OrderNode).Volume.String() // strings.Trim(subNode.Data.(*OrderNode).Volume.String(), "0") 193 | // fmt.Println("res b", res) 194 | orderSideBuy = append(orderSideBuy, res) 195 | } 196 | }) 197 | }) 198 | // fmt.Println() 199 | // fmt.Println("buy orders") 200 | buys := "" 201 | for _, o := range orderSideBuy { 202 | // fmt.Println(o) 203 | buys = o + "\n" + buys 204 | } 205 | result += buys 206 | return result 207 | } 208 | 209 | // NewOrderBook Returns new order book 210 | func NewOrderBook() *OrderBook { 211 | bTree := binarytree.NewBinaryTree() 212 | sTree := binarytree.NewBinaryTree() 213 | bTree.ToggleSplay(true) 214 | sTree.ToggleSplay(true) 215 | 216 | return &OrderBook{ 217 | BuyTree: bTree, 218 | SellTree: sTree, 219 | orderLimitRange: 200000000, 220 | orders: make(map[string]*OrderNode), 221 | mutex: &sync.Mutex{}, 222 | } 223 | } 224 | 225 | // addBuyOrder a buy order to the order book 226 | func (ob *OrderBook) addBuyOrder(order Order) { 227 | orderPrice := order.Price.Float64() 228 | startPoint := float64(int(math.Ceil(orderPrice)) / ob.orderLimitRange * ob.orderLimitRange) 229 | endPoint := startPoint + float64(ob.orderLimitRange) 230 | searchNodePrice := (startPoint + endPoint) / 2 231 | // fmt.Println("search node", startPoint, searchNodePrice, endPoint) 232 | node := ob.BuyTree.Root.SearchSubTree(searchNodePrice) 233 | var orderNode *OrderNode 234 | if node != nil { 235 | // fmt.Println("slab found", order.Price) 236 | subTree := node.Data.(*OrderType) 237 | subTreeNode := subTree.Tree.Root.SearchSubTree(orderPrice) 238 | if subTreeNode != nil { 239 | // order.Amount += node.Datorder.Priceorder.Pricea.(Order).Amount 240 | // fmt.Println("node found", order.Price) 241 | // subTreeNode.Data.(*OrderNode).updateVolume(order.Amount) 242 | subTreeNode.Data.(*OrderNode).addOrder(order) 243 | orderNode = subTreeNode.Data.(*OrderNode) 244 | } else { 245 | // fmt.Println("not found", order.Price) 246 | orderNode, _ = subTree.AddOrderInQueue(order) 247 | } 248 | // return 249 | } else { 250 | // fmt.Println("adding new slab", order.Price) 251 | orderTypeObj := NewOrderType(order.Type) 252 | orderNode, _ = orderTypeObj.AddOrderInQueue(order) 253 | ob.BuyTree.Insert(searchNodePrice, orderTypeObj) 254 | } 255 | // fmt.Println("ors", orderNode) 256 | ob.mutex.Lock() 257 | ob.orders[order.ID] = orderNode 258 | ob.mutex.Unlock() 259 | } 260 | 261 | // addSellOrder a buy order to the order book 262 | func (ob *OrderBook) addSellOrder(order Order) { 263 | orderPrice := order.Price.Float64() 264 | startPoint := float64(int(math.Ceil(orderPrice)) / ob.orderLimitRange * ob.orderLimitRange) 265 | endPoint := startPoint + float64(ob.orderLimitRange) 266 | searchNodePrice := (startPoint + endPoint) / 2 267 | // fmt.Println("search node", startPoint, searchNodePrice, endPoint) 268 | node := ob.SellTree.Root.SearchSubTree(searchNodePrice) 269 | var orderNode *OrderNode 270 | if node != nil { 271 | // fmt.Println("slab found", order.Price) 272 | subTree := node.Data.(*OrderType) 273 | subTreeNode := subTree.Tree.Root.SearchSubTree(orderPrice) 274 | if subTreeNode != nil { 275 | // order.Amount += node.Datorder.Priceorder.Pricea.(Order).Amount 276 | // fmt.Println("node found", order.Price) 277 | // subTreeNode.Data.(*OrderNode).updateVolume(order.Amount) 278 | subTreeNode.Data.(*OrderNode).addOrder(order) 279 | orderNode = subTreeNode.Data.(*OrderNode) 280 | } else { 281 | // fmt.Println("not found", order.Price) 282 | orderNode, _ = subTree.AddOrderInQueue(order) 283 | } 284 | // return 285 | } else { 286 | // fmt.Println("adding new slab", order.Price) 287 | orderTypeObj := NewOrderType(order.Type) 288 | orderNode, _ = orderTypeObj.AddOrderInQueue(order) 289 | ob.SellTree.Insert(searchNodePrice, orderTypeObj) 290 | } 291 | ob.mutex.Lock() 292 | ob.orders[order.ID] = orderNode 293 | ob.mutex.Unlock() 294 | } 295 | 296 | func (ob *OrderBook) removeBuyNode(key float64) error { 297 | node := ob.BuyTree.Root.Remove(key) 298 | ob.BuyTree.Root = node 299 | return nil 300 | } 301 | 302 | func (ob *OrderBook) removeSellNode(key float64) error { 303 | node := ob.SellTree.Root.Remove(key) 304 | ob.SellTree.Root = node 305 | return nil 306 | } 307 | 308 | func (ob *OrderBook) removeOrder(order *Order) error { 309 | orderPrice := order.Price.Float64() 310 | startPoint := float64(int(math.Ceil(orderPrice)) / ob.orderLimitRange * ob.orderLimitRange) 311 | endPoint := startPoint + float64(ob.orderLimitRange) 312 | searchNodePrice := (startPoint + endPoint) / 2 313 | // fmt.Println("search node", startPoint, searchNodePrice, endPoint) 314 | var node *binarytree.BinaryNode 315 | if order.Type == Buy { 316 | node = ob.BuyTree.Root.SearchSubTree(searchNodePrice) 317 | } else { 318 | node = ob.SellTree.Root.SearchSubTree(searchNodePrice) 319 | } 320 | if node != nil { 321 | // fmt.Println("slab found", order.Price) 322 | subTree := node.Data.(*OrderType) 323 | subTreeNode := subTree.Tree.Root.SearchSubTree(orderPrice) 324 | if subTreeNode != nil { 325 | fmt.Println("Found node to remove") 326 | // subTreeNode.Data.(*OrderNode).updateVolume(-order.Amount) 327 | // subTreeNode.Data.(*OrderNode).Orders = append(subTreeNode.Data.(*OrderNode).Orders[:index], subTreeNode.Data.(*OrderNode).Orders[index+1:]...) 328 | // if len(subTreeNode.Data.(*OrderNode).Orders) == 0 { 329 | // orderNode = nil 330 | n := subTree.Tree.Root.Remove(orderPrice) 331 | subTree.Tree.Root = n 332 | // } 333 | } else { 334 | return errors.New("no Order found") 335 | // fmt.Println("nothing") 336 | } 337 | // return 338 | } else { 339 | return errors.New("no Order found") 340 | } 341 | return nil 342 | } 343 | -------------------------------------------------------------------------------- /engine/order_book_test.go: -------------------------------------------------------------------------------- 1 | package engine 2 | 3 | import ( 4 | "math" 5 | "testing" 6 | 7 | "github.com/Pantelwar/binarytree" 8 | "github.com/Pantelwar/matching-engine/util" 9 | ) 10 | 11 | func TestNewOrderBook(t *testing.T) { 12 | t.Log(NewOrderBook()) 13 | } 14 | 15 | func DecimalBig(val string) *util.StandardBigDecimal { 16 | a, _ := util.NewDecimalFromString(val) 17 | return a 18 | } 19 | 20 | func TestAddOrderInBook(t *testing.T) { 21 | var tests = []struct { 22 | input *Order 23 | }{ 24 | {NewOrder("b1", Buy, DecimalBig("5.0"), DecimalBig("7000.0"))}, 25 | {NewOrder("s2", Sell, DecimalBig("10.0"), DecimalBig("7000.0"))}, 26 | {NewOrder("s3", Sell, DecimalBig("10.0"), DecimalBig("7000.0"))}, 27 | {NewOrder("b4", Buy, DecimalBig("1.0"), DecimalBig("7000.0"))}, 28 | } 29 | 30 | ob := NewOrderBook() 31 | 32 | for _, tt := range tests { 33 | if tt.input.Type == Buy { 34 | ob.addBuyOrder(*tt.input) 35 | } else { 36 | ob.addSellOrder(*tt.input) 37 | } 38 | 39 | if ob.orders[tt.input.ID] == nil { 40 | t.Fatal("Order should be pushed in orders array") 41 | } 42 | 43 | price := tt.input.Price.Float64() 44 | startPoint := float64(int(math.Ceil(price)) / ob.orderLimitRange * ob.orderLimitRange) 45 | endPoint := startPoint + float64(ob.orderLimitRange) 46 | searchNodePrice := (startPoint + endPoint) / 2 47 | 48 | var node *binarytree.BinaryNode 49 | if tt.input.Type == Buy { 50 | node = ob.BuyTree.Root.SearchSubTree(searchNodePrice) 51 | } else { 52 | node = ob.SellTree.Root.SearchSubTree(searchNodePrice) 53 | } 54 | 55 | if node == nil { 56 | t.Fatal("Order should be present in tree") 57 | } 58 | } 59 | } 60 | 61 | func TestRemoveOrderNodeFromBook(t *testing.T) { 62 | var tests = []struct { 63 | input *Order 64 | }{ 65 | {NewOrder("b1", Buy, DecimalBig("5.0"), DecimalBig("7000.0"))}, 66 | {NewOrder("s2", Sell, DecimalBig("10.0"), DecimalBig("7000.0"))}, 67 | {NewOrder("s3", Sell, DecimalBig("10.0"), DecimalBig("7000.0"))}, 68 | {NewOrder("b4", Buy, DecimalBig("1.0"), DecimalBig("7000.0"))}, 69 | } 70 | ob := NewOrderBook() 71 | 72 | for _, tt := range tests { 73 | if tt.input.Type == Buy { 74 | ob.addBuyOrder(*tt.input) 75 | } else { 76 | ob.addSellOrder(*tt.input) 77 | } 78 | } 79 | 80 | price := tests[0].input.Price.Float64() 81 | startPoint := float64(int(math.Ceil(price)) / ob.orderLimitRange * ob.orderLimitRange) 82 | endPoint := startPoint + float64(ob.orderLimitRange) 83 | searchNodePrice := (startPoint + endPoint) / 2 84 | 85 | ob.removeBuyNode(searchNodePrice) 86 | 87 | var node *binarytree.BinaryNode 88 | node = ob.BuyTree.Root.SearchSubTree(searchNodePrice) 89 | 90 | if node != nil { 91 | t.Fatal("Buy Mid Price should be get removed from tree") 92 | } 93 | 94 | price = tests[1].input.Price.Float64() 95 | startPoint = float64(int(math.Ceil(price)) / ob.orderLimitRange * ob.orderLimitRange) 96 | endPoint = startPoint + float64(ob.orderLimitRange) 97 | searchNodePrice = (startPoint + endPoint) / 2 98 | 99 | ob.removeSellNode(searchNodePrice) 100 | 101 | node = ob.SellTree.Root.SearchSubTree(searchNodePrice) 102 | 103 | if node != nil { 104 | t.Fatal("Sell Mid Price should be get removed from tree") 105 | } 106 | } 107 | 108 | func TestRemoveOrderFromBook(t *testing.T) { 109 | var tests = []struct { 110 | input *Order 111 | }{ 112 | {NewOrder("b1", Buy, DecimalBig("5.0"), DecimalBig("7000.0"))}, 113 | {NewOrder("s2", Sell, DecimalBig("10.0"), DecimalBig("7000.0"))}, 114 | {NewOrder("s3", Sell, DecimalBig("10.0"), DecimalBig("7000.0"))}, 115 | {NewOrder("b4", Buy, DecimalBig("1.0"), DecimalBig("7000.0"))}, 116 | } 117 | ob := NewOrderBook() 118 | 119 | for _, tt := range tests { 120 | if tt.input.Type == Buy { 121 | ob.addBuyOrder(*tt.input) //, orderPrice) 122 | } else { 123 | ob.addSellOrder(*tt.input) //, orderPrice) 124 | } 125 | } 126 | 127 | err := ob.removeOrder(tests[0].input) 128 | if err != nil { 129 | t.Fatal("Order is not removed") 130 | } 131 | 132 | err = ob.removeOrder(tests[0].input) 133 | if err == nil { 134 | t.Fatal("The response should be order not found") 135 | } 136 | } 137 | 138 | func TestString(t *testing.T) { 139 | var tests = []struct { 140 | input []*Order 141 | output string 142 | }{ 143 | { 144 | []*Order{ 145 | NewOrder("b1", Buy, DecimalBig("5.0"), DecimalBig("7000.0")), 146 | NewOrder("b2", Buy, DecimalBig("10.0"), DecimalBig("7000.0")), 147 | }, 148 | `------------------------------------------ 149 | 7000 -> 15 150 | `}, 151 | { 152 | []*Order{ 153 | NewOrder("b1", Buy, DecimalBig("5.0"), DecimalBig("7000.0")), 154 | NewOrder("b2", Buy, DecimalBig("10.0"), DecimalBig("8000.0")), 155 | }, 156 | `------------------------------------------ 157 | 8000 -> 10 158 | 7000 -> 5 159 | `}, 160 | { 161 | []*Order{ 162 | NewOrder("s1", Sell, DecimalBig("5.0"), DecimalBig("7000.0")), 163 | NewOrder("s2", Sell, DecimalBig("10.0"), DecimalBig("7000.0")), 164 | }, 165 | `7000 -> 15 166 | ------------------------------------------ 167 | `}, 168 | { 169 | []*Order{ 170 | NewOrder("s1", Sell, DecimalBig("5.0"), DecimalBig("7000.0")), 171 | NewOrder("s2", Sell, DecimalBig("10.0"), DecimalBig("8000.0")), 172 | }, 173 | `8000 -> 10 174 | 7000 -> 5 175 | ------------------------------------------ 176 | `}, 177 | { 178 | []*Order{ 179 | NewOrder("s1", Sell, DecimalBig("5.0"), DecimalBig("7000.0")), 180 | NewOrder("b2", Buy, DecimalBig("10.0"), DecimalBig("6000.0")), 181 | NewOrder("s3", Sell, DecimalBig("1.0"), DecimalBig("8000.0")), 182 | NewOrder("b4", Buy, DecimalBig("2.0"), DecimalBig("6500.0")), 183 | }, 184 | `8000 -> 1 185 | 7000 -> 5 186 | ------------------------------------------ 187 | 6500 -> 2 188 | 6000 -> 10 189 | `}, 190 | { 191 | []*Order{ 192 | NewOrder("s1", Sell, DecimalBig("5.134"), DecimalBig("7000.0")), 193 | NewOrder("b2", Buy, DecimalBig("10.134"), DecimalBig("6000.0")), 194 | NewOrder("s3", Sell, DecimalBig("1.32"), DecimalBig("7000.0")), 195 | NewOrder("b4", Buy, DecimalBig("2.1278"), DecimalBig("6000.0")), 196 | }, 197 | `7000 -> 6.454 198 | ------------------------------------------ 199 | 6000 -> 12.2618 200 | `}, 201 | } 202 | 203 | for _, tt := range tests { 204 | ob := NewOrderBook() 205 | for _, o := range tt.input { 206 | ob.Process(*o) 207 | } 208 | 209 | if tt.output != ob.String() { 210 | t.Fatalf("Book prints incorrect (have: \n%s, \nwant: \n%s\n)", ob.String(), tt.output) 211 | } 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /engine/order_node.go: -------------------------------------------------------------------------------- 1 | package engine 2 | 3 | import ( 4 | "github.com/Pantelwar/matching-engine/util" 5 | ) 6 | 7 | // OrderNode ... 8 | type OrderNode struct { 9 | Orders []*Order `json:"orders"` 10 | Volume *util.StandardBigDecimal `json:"volume"` 11 | } 12 | 13 | // NewOrderNode returns new OrderNode struct 14 | func NewOrderNode() *OrderNode { 15 | vol, _ := util.NewDecimalFromString("0.0") 16 | return &OrderNode{Orders: []*Order{}, Volume: vol} 17 | } 18 | 19 | // addOrder adds order to node 20 | func (on *OrderNode) addOrder(order Order) { 21 | found := false 22 | for _, o := range on.Orders { 23 | if o.ID == order.ID { 24 | if o.Amount != order.Amount { 25 | on.updateVolume(o.Amount.Neg()) 26 | o.Amount = order.Amount 27 | o.Price = order.Price 28 | on.updateVolume(o.Amount) 29 | } 30 | found = true 31 | break 32 | } 33 | } 34 | if !found { 35 | on.updateVolume(order.Amount) 36 | on.Orders = append(on.Orders, &order) 37 | } 38 | // fmt.Printf("on.ORderNode: %v", on.Orders) 39 | } 40 | 41 | // updateVolume updates volume 42 | func (on *OrderNode) updateVolume(value *util.StandardBigDecimal) { 43 | on.Volume = on.Volume.Add(value) 44 | // fmt.Println("onVolume", on.Volume) 45 | } 46 | 47 | // removeOrder removes order from OrderNode array 48 | func (on *OrderNode) removeOrder(index int) { 49 | on.updateVolume(on.Orders[index].Amount.Neg()) 50 | on.Orders = append(on.Orders[:index], on.Orders[index+1:]...) 51 | } 52 | 53 | // // MarshalJSON implements json.Marshaler interface 54 | // func (on *OrderNode) MarshalJSON() ([]byte, error) { 55 | 56 | // } 57 | -------------------------------------------------------------------------------- /engine/order_node_test.go: -------------------------------------------------------------------------------- 1 | package engine 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestNewOrderNode(t *testing.T) { 8 | t.Log(NewOrderNode()) 9 | } 10 | 11 | func TestAddOrderInNode(t *testing.T) { 12 | var tests = []struct { 13 | input *Order 14 | }{ 15 | {NewOrder("b1", Buy, DecimalBig("5.0"), DecimalBig("7000.0"))}, 16 | {NewOrder("b2", Buy, DecimalBig("10.0"), DecimalBig("7000.0"))}, 17 | {NewOrder("b3", Buy, DecimalBig("11.0"), DecimalBig("7000.0"))}, 18 | {NewOrder("b4", Buy, DecimalBig("1.0"), DecimalBig("7000.0"))}, 19 | } 20 | on := NewOrderNode() 21 | volume := DecimalBig("0.0") 22 | for _, tt := range tests { 23 | on.addOrder(*tt.input) 24 | volume = volume.Add(tt.input.Amount) 25 | } 26 | 27 | if len(on.Orders) != len(tests) { 28 | t.Fatalf("Invalid order length (have: %d, want: %d", len(on.Orders), len(tests)) 29 | } 30 | 31 | if on.Volume.Cmp(volume) != 0 { 32 | t.Fatalf("Invalid order volume (have: %s, want: %s", on.Volume.String(), volume.String()) 33 | } 34 | } 35 | 36 | func TestRemoveOrderFromNode(t *testing.T) { 37 | var tests = []struct { 38 | input *Order 39 | }{ 40 | {NewOrder("b1", Buy, DecimalBig("5.0"), DecimalBig("7000.0"))}, 41 | {NewOrder("b2", Buy, DecimalBig("10.0"), DecimalBig("7000.0"))}, 42 | {NewOrder("b3", Buy, DecimalBig("11.0"), DecimalBig("7000.0"))}, 43 | {NewOrder("b4", Buy, DecimalBig("1.0"), DecimalBig("7000.0"))}, 44 | } 45 | on := NewOrderNode() 46 | volume := DecimalBig("0.0") 47 | for _, tt := range tests { 48 | on.addOrder(*tt.input) 49 | volume = volume.Add(tt.input.Amount) 50 | } 51 | 52 | on.removeOrder(0) 53 | volume = volume.Sub(tests[0].input.Amount) 54 | 55 | if len(on.Orders) != len(tests)-1 { 56 | t.Fatalf("Invalid order length (have: %d, want: %d", len(on.Orders), len(tests)) 57 | } 58 | 59 | if on.Volume.Cmp(volume) != 0 { 60 | t.Fatalf("Invalid order volume (have: %s, want: %s", on.Volume.String(), volume.String()) 61 | } 62 | } 63 | 64 | func TestUpdateVolume(t *testing.T) { 65 | var tests = []struct { 66 | input *Order 67 | }{ 68 | {NewOrder("b1", Buy, DecimalBig("5.0"), DecimalBig("7000.0"))}, 69 | {NewOrder("b2", Buy, DecimalBig("10.0"), DecimalBig("7000.0"))}, 70 | {NewOrder("b3", Buy, DecimalBig("11.0"), DecimalBig("7000.0"))}, 71 | {NewOrder("b4", Buy, DecimalBig("1.0"), DecimalBig("7000.0"))}, 72 | } 73 | on := NewOrderNode() 74 | volume := DecimalBig("0.0") 75 | for _, tt := range tests { 76 | on.updateVolume(tt.input.Amount) 77 | volume = volume.Add(tt.input.Amount) 78 | } 79 | 80 | if on.Volume.Cmp(volume) != 0 { 81 | t.Fatalf("Invalid order volume (have: %s, want: %s", on.Volume.String(), volume.String()) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /engine/order_test.go: -------------------------------------------------------------------------------- 1 | package engine 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestNewOrder(t *testing.T) { 9 | t.Log(NewOrder("b1", Sell, DecimalBig("5.0"), DecimalBig("7000.0"))) 10 | } 11 | 12 | func TestToJSON(t *testing.T) { 13 | var tests = []struct { 14 | input *Order 15 | output string 16 | }{ 17 | {NewOrder("b1", Buy, DecimalBig("5.0"), DecimalBig("7000.0")), `{"type":"buy","id":"b1","amount":"5.0","price":"7000.0"}`}, 18 | {NewOrder("s1", Sell, DecimalBig("5.0"), DecimalBig("7000.0")), `{"type":"sell","id":"s1","amount":"5.0","price":"7000.0"}`}, 19 | } 20 | 21 | for _, tt := range tests { 22 | result, _ := tt.input.ToJSON() 23 | 24 | if string(result) != tt.output { 25 | t.Fatalf("Unable to marshal json (have: %s, want: %s)\n", string(result), tt.output) 26 | } 27 | } 28 | } 29 | 30 | func TestFromJSON(t *testing.T) { 31 | var tests = []struct { 32 | input string 33 | err string 34 | message string 35 | }{ 36 | {"{\"amount\":\"5.0\",\"price\":\"7000.0\",\"id\":\"b1\",\"type\":\"buy\"}", "", "JSON should be approved"}, 37 | 38 | {"{}", "err", "Empty JSON should not be passed"}, 39 | {"{\"price\":\"0.0\",\"id\":\"b1\",\"type\":\"buy\"}", "err", "Check for amount key"}, 40 | {"{\"amount\":\"5.0\",\"id\":\"b1\",\"type\":\"buy\"}", "err", "Check for price key"}, 41 | {"{\"amount\":\"5.0\",\"price\":\"7000.0\",\"type\":\"buy\"}", "err", "Check for id key"}, 42 | {"{\"amount\":\"5.0\",\"price\":\"7000.0\",\"id\":\"b1\"}", "err", "Check for type key"}, 43 | 44 | {"{\"amount\":\"0.0\",\"price\":\"7000.0\",\"id\":\"b1\",\"type\":\"buy\"}", "err", "Check for valid amount"}, 45 | {"{\"amount\":\"5.0\",\"price\":\"0.0\",\"id\":\"b1\",\"type\":\"buy\"}", "err", "Check for valid price"}, 46 | 47 | {"{\"amount\":\"5.0\",\"price\":\"7000.0\",\"id\":\"b1\",\"type\":\"random\"}", "err", "Check for valid type"}, 48 | {"{\"amount\":\"random\",\"price\":\"0.0\",\"id\":\"b1\",\"type\":\"buy\"}", "err", "Check for valid amount"}, 49 | {"{\"amount\":\"0.0\",\"price\":\"random\",\"id\":\"b1\",\"type\":\"buy\"}", "err", "Check for valid price"}, 50 | } 51 | for _, tt := range tests { 52 | t.Run(tt.input, func(t *testing.T) { 53 | order := &Order{} 54 | err := order.FromJSON([]byte(tt.input)) 55 | fmt.Println("error:", err) 56 | if tt.err == "" && err == nil { 57 | t.Log("Successfully detecting error") 58 | } else if tt.err != "" && err != nil { 59 | t.Log("Successful detection of json") 60 | } else { 61 | t.Fatal(tt.message) 62 | } 63 | }) 64 | } 65 | } 66 | 67 | func TestOrderString(t *testing.T) { 68 | var tests = []struct { 69 | input *Order 70 | output string 71 | }{ 72 | { 73 | NewOrder("b1", Buy, DecimalBig("5.0"), DecimalBig("7000.0")), 74 | `"b1": 75 | side: buy 76 | quantity: 5 77 | price: 7000 78 | `}, 79 | { 80 | NewOrder("s1", Sell, DecimalBig("5.124"), DecimalBig("9000.0")), 81 | `"s1": 82 | side: sell 83 | quantity: 5.124 84 | price: 9000 85 | `}, 86 | } 87 | for _, tt := range tests { 88 | o := tt.input.String() 89 | if tt.output != o { 90 | t.Fatalf("Book prints incorrect (have: \n%s, \nwant: \n%s\n)", o, tt.output) 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /engine/order_type.go: -------------------------------------------------------------------------------- 1 | package engine 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/Pantelwar/binarytree" 7 | ) 8 | 9 | // OrderType defines tree side 10 | type OrderType struct { 11 | Tree *binarytree.BinaryTree 12 | Type Side `json:"type"` 13 | } 14 | 15 | // NewOrderType returns OrderType struct 16 | func NewOrderType(orderSide Side) *OrderType { 17 | bTree := binarytree.NewBinaryTree() 18 | bTree.ToggleSplay(true) 19 | return &OrderType{Tree: bTree, Type: orderSide} 20 | } 21 | 22 | // AddOrderInQueue adds order to the tree 23 | func (ot *OrderType) AddOrderInQueue(order Order) (*OrderNode, error) { 24 | if ot.Type != order.Type { 25 | return nil, errors.New("invalid order type") 26 | } 27 | orderNode := NewOrderNode() 28 | orderNode.Orders = append(orderNode.Orders, &order) 29 | orderNode.Volume = order.Amount 30 | orderPrice := order.Price.Float64() 31 | ot.Tree.Insert(orderPrice, orderNode) 32 | return orderNode, nil 33 | } 34 | -------------------------------------------------------------------------------- /engine/order_type_test.go: -------------------------------------------------------------------------------- 1 | package engine 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestNewOrderType(t *testing.T) { 9 | t.Log(NewOrderType("sell")) 10 | } 11 | 12 | func TestAddOrderInQueue(t *testing.T) { 13 | var tests = []struct { 14 | input *Order 15 | err bool 16 | }{ 17 | {NewOrder("b1", Buy, DecimalBig("5.0"), DecimalBig("7000.0")), true}, 18 | {NewOrder("s2", Sell, DecimalBig("10.0"), DecimalBig("7000.0")), false}, 19 | {NewOrder("b3", Buy, DecimalBig("11.0"), DecimalBig("8000.0")), true}, 20 | {NewOrder("s4", Sell, DecimalBig("2.0"), DecimalBig("10000.0")), false}, 21 | } 22 | ot := NewOrderType("sell") 23 | for _, tt := range tests { 24 | on, err := ot.AddOrderInQueue(*tt.input) 25 | if tt.err { 26 | if err == nil { 27 | t.Fatalf("Cannot append %s order under %s order type", tt.input.Type, ot.Type) 28 | } 29 | continue 30 | } 31 | fmt.Println("on", on, err) 32 | if on.Volume != tt.input.Amount { 33 | t.Fatalf("Volume update failure (have: %s, want: %s)", on.Volume.String(), tt.input.Amount.String()) 34 | } 35 | if len(on.Orders) != 1 { 36 | t.Fatalf("Order length update failure (have: %d, want: 1)", len(on.Orders)) 37 | } 38 | } 39 | 40 | price := tests[1].input.Price.Float64() 41 | node := ot.Tree.Root.SearchSubTree(price) 42 | if node == nil { 43 | t.Fatal("There should exists a node in orderType.Tree") 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /engine/process_limit_order.go: -------------------------------------------------------------------------------- 1 | package engine 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/Pantelwar/binarytree" 7 | "github.com/Pantelwar/matching-engine/util" 8 | ) 9 | 10 | var decimalZero, _ = util.NewDecimalFromString("0.0") 11 | 12 | // Process executes limit process 13 | func (ob *OrderBook) Process(order Order) ([]*Order, *Order) { 14 | if order.Type == Buy { 15 | // return ob.processOrderB(order) 16 | return ob.commonProcess(order, ob.SellTree, ob.addBuyOrder, ob.removeSellNode) 17 | } 18 | // return ob.processOrderS(order) 19 | return ob.commonProcess(order, ob.BuyTree, ob.addSellOrder, ob.removeBuyNode) 20 | } 21 | 22 | func (ob *OrderBook) commonProcess(order Order, tree *binarytree.BinaryTree, add func(Order), remove func(float64) error) ([]*Order, *Order) { 23 | var maxNode *binarytree.BinaryNode 24 | if order.Type == Sell { 25 | maxNode = tree.Max() 26 | } else { 27 | maxNode = tree.Min() 28 | } 29 | if maxNode == nil { 30 | // fmt.Println("adding node pending", order.Price) 31 | add(order) 32 | return nil, nil 33 | } 34 | // fmt.Println("maxNode", maxNode.Key, maxNode.Data.(*OrderType).Tree.Root.Key) 35 | count := 0 36 | noMoreOrders := false 37 | var allOrdersProcessed []*Order 38 | var partialOrder *Order 39 | orderOriginalAmount := order.Amount 40 | for maxNode == nil || order.Amount.Cmp(decimalZero) == 1 { 41 | count++ 42 | if order.Type == Sell { 43 | maxNode = tree.Max() 44 | } else { 45 | maxNode = tree.Min() 46 | } 47 | if maxNode == nil || noMoreOrders { 48 | if order.Amount.Cmp(decimalZero) == 1 { 49 | // fmt.Println("adding sell node pending") 50 | add(order) 51 | break 52 | } else { 53 | break 54 | } 55 | } 56 | // fmt.Println("maxNode in", maxNode.Key, maxNode.Data.(*OrderType).Tree.Root.Key) 57 | 58 | // var t []Trade 59 | var ordersProcessed []*Order 60 | noMoreOrders, ordersProcessed, partialOrder = ob.processLimit(&order, partialOrder, maxNode.Data.(*OrderType).Tree, orderOriginalAmount) //, orderPrice) 61 | fmt.Printf("\npartialOrder in between: %#v\n", partialOrder) 62 | fmt.Printf("noMoreOrders: %#v\n\n", noMoreOrders) 63 | allOrdersProcessed = append(allOrdersProcessed, ordersProcessed...) 64 | // trades = append(trades, t...) 65 | 66 | if maxNode.Data.(*OrderType).Tree.Root == nil { 67 | // node := remove(maxNode.Key) 68 | // // node := ob.removeBuyNode(maxNode.Key) 69 | // tree.Root = node 70 | // fmt.Println("removing", maxNode.Key) 71 | remove(maxNode.Key) 72 | } 73 | } 74 | 75 | // return trades, allOrdersProcessed, partialOrder 76 | // if partialOrder.Amount == nil { 77 | // partialOrder = nil 78 | // } 79 | fmt.Printf("partialOrder final: %#v\n", partialOrder) 80 | 81 | return allOrdersProcessed, partialOrder 82 | } 83 | 84 | func (ob *OrderBook) processLimit(order, partialOrder *Order, tree *binarytree.BinaryTree, orderOriginalAmount *util.StandardBigDecimal) (bool, []*Order, *Order) { 85 | orderPrice := order.Price.Float64() 86 | var maxNode *binarytree.BinaryNode 87 | if order.Type == Sell { 88 | maxNode = tree.Max() 89 | } else { 90 | maxNode = tree.Min() 91 | } 92 | noMoreOrders := false 93 | var ordersProcessed []*Order 94 | // var partialOrder *Order 95 | fmt.Printf("partialOrder start: %#v\n", partialOrder) 96 | 97 | // var partialOrder *Order 98 | if maxNode == nil { 99 | // return trades, noMoreOrders, nil, nil 100 | return noMoreOrders, nil, nil 101 | } 102 | // countAdd := 0.0 103 | for maxNode == nil || order.Amount.Cmp(decimalZero) == 1 { 104 | if order.Type == Sell { 105 | maxNode = tree.Max() 106 | } else { 107 | maxNode = tree.Min() 108 | } 109 | // fmt.Println("maxNode more in", maxNode.Key, maxNode.Data.(*OrderNode).Orders) 110 | if maxNode == nil || noMoreOrders { 111 | if order.Amount.Cmp(decimalZero) == 1 { 112 | partialOrder = NewOrder(order.ID, order.Type, order.Amount, order.Price) 113 | break 114 | } else { 115 | // partialOrder = nil 116 | break 117 | } 118 | } 119 | if order.Type == Sell { 120 | if orderPrice > maxNode.Key { 121 | fmt.Println("adding sellnode directly") 122 | noMoreOrders = true 123 | // return trades, noMoreOrders, nil, nil 124 | return noMoreOrders, ordersProcessed, partialOrder 125 | } 126 | } else { 127 | if orderPrice < maxNode.Key { 128 | fmt.Println("adding buynode directly") 129 | noMoreOrders = true 130 | // return trades, noMoreOrders, nil, nil 131 | return noMoreOrders, ordersProcessed, partialOrder 132 | } 133 | } 134 | 135 | nodeData := maxNode.Data.(*OrderNode) //([]*Order) 136 | nodeOrders := nodeData.Orders //([]*Order) 137 | countMatch := 0 138 | // fmt.Println("length of nodeOrders", len(nodeOrders)) 139 | for _, ele := range nodeOrders { 140 | if order.Type == Sell { 141 | if ele.Price.Cmp(order.Price) == -1 { 142 | noMoreOrders = true 143 | break 144 | } 145 | } else { 146 | if ele.Price.Cmp(order.Price) == 1 { 147 | noMoreOrders = true 148 | break 149 | } 150 | } 151 | 152 | // countAdd += ele.Amount 153 | // fmt.Println(ele.Price, order.Price, ele.Amount, order.Amount, ele.Amount.Cmp(order.Amount)) 154 | if ele.Amount.Cmp(order.Amount) == 1 { 155 | nodeData.updateVolume(order.Amount.Neg()) 156 | // trades = append(trades, Trade{BuyOrderID: ele.ID, SellOrderID: order.ID, Amount: order.Amount, Price: ele.Price}) 157 | 158 | amount := ele.Amount.Sub(order.Amount) 159 | // amount = math.Floor(amount*100000000) / 100000000 160 | ele.Amount = amount 161 | 162 | partialOrder = NewOrder(ele.ID, ele.Type, ele.Amount, ele.Price) 163 | ordersProcessed = append(ordersProcessed, NewOrder(order.ID, order.Type, orderOriginalAmount, order.Price)) 164 | 165 | maxNode.SetData(nodeData) 166 | 167 | order.Amount, _ = util.NewDecimalFromString("0.0") 168 | noMoreOrders = true 169 | break 170 | } else if ele.Amount.Cmp(order.Amount) == 0 { 171 | nodeData.updateVolume(order.Amount.Neg()) 172 | 173 | ordersProcessed = append(ordersProcessed, NewOrder(ele.ID, ele.Type, ele.Amount, ele.Price)) 174 | ordersProcessed = append(ordersProcessed, NewOrder(order.ID, order.Type, orderOriginalAmount, order.Price)) 175 | partialOrder = nil 176 | countMatch++ 177 | // trades = append(trades, Trade{BuyOrderID: ele.ID, SellOrderID: order.ID, Amount: order.Amount, Price: ele.Price}) 178 | 179 | order.Amount, _ = util.NewDecimalFromString("0.0") 180 | // orderComplete = true 181 | 182 | // ele.Amount = 0 183 | ob.mutex.Lock() 184 | delete(ob.orders, ele.ID) 185 | ob.mutex.Unlock() 186 | 187 | break 188 | } else { 189 | countMatch++ 190 | 191 | amount := order.Amount.Sub(ele.Amount) 192 | 193 | partialOrder = NewOrder(order.ID, order.Type, amount, order.Price) 194 | 195 | ordersProcessed = append(ordersProcessed, NewOrder(ele.ID, ele.Type, ele.Amount, ele.Price)) 196 | 197 | nodeData.updateVolume(ele.Amount.Neg()) 198 | 199 | // trades = append(trades, Trade{BuyOrderID: ele.ID, SellOrderID: order.ID, Amount: ele.Amount, Price: ele.Price}) 200 | 201 | order.Amount = order.Amount.Sub(ele.Amount) 202 | ob.mutex.Lock() 203 | delete(ob.orders, ele.ID) 204 | ob.mutex.Unlock() 205 | } 206 | } 207 | 208 | if len(nodeOrders) == countMatch { 209 | node := tree.Root.Remove(maxNode.Key) // ob.removeBuyNode(maxNode.Key, buyTree) 210 | // fmt.Printf("node removed: %#v %#v\n", node, maxNode) 211 | tree.Root = node 212 | } 213 | 214 | nodeData.Orders = nodeOrders[countMatch:] 215 | maxNode.SetData(nodeData) 216 | } 217 | // return trades, noMoreOrders, ordersProcessed, partialOrder 218 | // fmt.Printf("partialOrder: %#v\n", partialOrder) 219 | // fmt.Printf("order.Amount: %#v\n", order.Amount.String()) 220 | return noMoreOrders, ordersProcessed, partialOrder 221 | } 222 | -------------------------------------------------------------------------------- /engine/process_limit_order_test.go: -------------------------------------------------------------------------------- 1 | package engine 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestProcessLimitOrder(t *testing.T) { 9 | var tests = []struct { 10 | bookGen []*Order 11 | input *Order 12 | processedOrder []*Order 13 | partialOrder *Order 14 | }{ 15 | { 16 | []*Order{ 17 | NewOrder("b1", Buy, DecimalBig("5.0"), DecimalBig("7000.0")), 18 | }, 19 | NewOrder("s2", Sell, DecimalBig("5.0"), DecimalBig("8000.0")), 20 | []*Order{}, 21 | nil, 22 | }, 23 | { 24 | []*Order{ 25 | NewOrder("s2", Sell, DecimalBig("5.0"), DecimalBig("8000.0")), 26 | }, 27 | NewOrder("b1", Buy, DecimalBig("5.0"), DecimalBig("7000.0")), 28 | []*Order{}, 29 | nil, 30 | }, 31 | //////////////////////////////////////////////////////////////////////// 32 | { 33 | []*Order{ 34 | NewOrder("b1", Buy, DecimalBig("5.0"), DecimalBig("7000.0")), 35 | }, 36 | NewOrder("s2", Sell, DecimalBig("5.0"), DecimalBig("7000.0")), 37 | []*Order{ 38 | NewOrder("b1", Buy, DecimalBig("5.0"), DecimalBig("7000.0")), 39 | NewOrder("s2", Sell, DecimalBig("5.0"), DecimalBig("7000.0")), 40 | }, 41 | nil, 42 | }, 43 | { 44 | []*Order{ 45 | NewOrder("s1", Sell, DecimalBig("5.0"), DecimalBig("7000.0")), 46 | }, 47 | NewOrder("b2", Buy, DecimalBig("5.0"), DecimalBig("7000.0")), 48 | []*Order{ 49 | NewOrder("s1", Sell, DecimalBig("5.0"), DecimalBig("7000.0")), 50 | NewOrder("b2", Buy, DecimalBig("5.0"), DecimalBig("7000.0")), 51 | }, 52 | nil, 53 | }, 54 | //////////////////////////////////////////////////////////////////////// 55 | { 56 | []*Order{ 57 | NewOrder("b1", Buy, DecimalBig("5.0"), DecimalBig("7000.0")), 58 | }, 59 | NewOrder("s2", Sell, DecimalBig("1.0"), DecimalBig("7000.0")), 60 | []*Order{ 61 | NewOrder("s2", Sell, DecimalBig("1.0"), DecimalBig("7000.0")), 62 | }, 63 | NewOrder("b1", Buy, DecimalBig("4.0"), DecimalBig("7000.0")), 64 | }, 65 | { 66 | []*Order{ 67 | NewOrder("s1", Sell, DecimalBig("5.0"), DecimalBig("7000.0")), 68 | }, 69 | NewOrder("b2", Buy, DecimalBig("1.0"), DecimalBig("7000.0")), 70 | []*Order{ 71 | NewOrder("b2", Buy, DecimalBig("1.0"), DecimalBig("7000.0")), 72 | }, 73 | NewOrder("s1", Sell, DecimalBig("4.0"), DecimalBig("7000.0")), 74 | }, 75 | //////////////////////////////////////////////////////////////////////// 76 | { 77 | []*Order{ 78 | NewOrder("b1", Buy, DecimalBig("5.0"), DecimalBig("7000.0")), 79 | }, 80 | NewOrder("s2", Sell, DecimalBig("1.0"), DecimalBig("6000.0")), 81 | []*Order{ 82 | NewOrder("s2", Sell, DecimalBig("1.0"), DecimalBig("6000.0")), 83 | }, 84 | NewOrder("b1", Buy, DecimalBig("4.0"), DecimalBig("7000.0")), 85 | }, 86 | 87 | { 88 | []*Order{ 89 | NewOrder("s1", Sell, DecimalBig("5.0"), DecimalBig("7000.0")), 90 | }, 91 | NewOrder("b2", Buy, DecimalBig("1.0"), DecimalBig("8000.0")), 92 | []*Order{ 93 | NewOrder("b2", Buy, DecimalBig("1.0"), DecimalBig("8000.0")), 94 | }, 95 | NewOrder("s1", Sell, DecimalBig("4.0"), DecimalBig("7000.0")), 96 | }, 97 | //////////////////////////////////////////////////////////////////////// 98 | { 99 | []*Order{ 100 | NewOrder("b1", Buy, DecimalBig("1.0"), DecimalBig("7000.0")), 101 | NewOrder("b2", Buy, DecimalBig("1.0"), DecimalBig("6000.0")), 102 | }, 103 | NewOrder("s3", Sell, DecimalBig("2.0"), DecimalBig("6000.0")), 104 | []*Order{ 105 | NewOrder("b1", Buy, DecimalBig("1.0"), DecimalBig("7000.0")), 106 | NewOrder("b2", Buy, DecimalBig("1.0"), DecimalBig("6000.0")), 107 | NewOrder("s3", Sell, DecimalBig("2.0"), DecimalBig("6000.0")), 108 | }, 109 | nil, 110 | }, 111 | //////////////////////////////////////////////////////////////////////// 112 | { 113 | []*Order{ 114 | NewOrder("b1", Buy, DecimalBig("1.0"), DecimalBig("7000.0")), 115 | NewOrder("b2", Buy, DecimalBig("2.0"), DecimalBig("6000.0")), 116 | }, 117 | NewOrder("s3", Sell, DecimalBig("2.0"), DecimalBig("6000.0")), 118 | []*Order{ 119 | NewOrder("b1", Buy, DecimalBig("1.0"), DecimalBig("7000.0")), 120 | NewOrder("s3", Sell, DecimalBig("2.0"), DecimalBig("6000.0")), 121 | }, 122 | NewOrder("b2", Buy, DecimalBig("1.0"), DecimalBig("6000.0")), 123 | }, 124 | 125 | ////////////////////////////////////////////////////////////////////// 126 | { 127 | []*Order{ 128 | NewOrder("b1", Buy, DecimalBig("5.0"), DecimalBig("7000.0")), 129 | }, 130 | NewOrder("s2", Sell, DecimalBig("6.0"), DecimalBig("6000.0")), 131 | []*Order{ 132 | NewOrder("b1", Buy, DecimalBig("5.0"), DecimalBig("7000.0")), 133 | }, 134 | NewOrder("s2", Sell, DecimalBig("1.0"), DecimalBig("6000.0")), 135 | }, 136 | { 137 | []*Order{ 138 | NewOrder("s1", Sell, DecimalBig("5.0"), DecimalBig("7000.0")), 139 | }, 140 | NewOrder("b2", Buy, DecimalBig("6.0"), DecimalBig("8000.0")), 141 | []*Order{ 142 | NewOrder("s1", Sell, DecimalBig("5.0"), DecimalBig("7000.0")), 143 | }, 144 | NewOrder("b2", Buy, DecimalBig("1.0"), DecimalBig("8000.0")), 145 | }, 146 | { 147 | []*Order{ 148 | NewOrder("b1", Buy, DecimalBig("10.0"), DecimalBig("74.0")), 149 | NewOrder("b2", Buy, DecimalBig("10.0"), DecimalBig("75.0")), 150 | NewOrder("b4", Buy, DecimalBig("10.0"), DecimalBig("770.0")), 151 | NewOrder("b3", Buy, DecimalBig("10.0"), DecimalBig("760.0")), 152 | }, 153 | NewOrder("s1", Sell, DecimalBig("21.0"), DecimalBig("770.0")), 154 | []*Order{ 155 | NewOrder("b4", Buy, DecimalBig("10.0"), DecimalBig("770.0")), 156 | }, 157 | // nil, 158 | NewOrder("s1", Sell, DecimalBig("11.0"), DecimalBig("770.0")), 159 | }, 160 | 161 | { 162 | []*Order{ 163 | // NewOrder("b1", Buy, DecimalBig("10.0"), DecimalBig("74.0")), 164 | // NewOrder("b2", Buy, DecimalBig("10.0"), DecimalBig("75.0")), 165 | NewOrder("s3", Sell, DecimalBig("10.0"), DecimalBig("760.0")), 166 | NewOrder("s4", Sell, DecimalBig("10.0"), DecimalBig("770.0")), 167 | }, 168 | NewOrder("b1", Buy, DecimalBig("20.0"), DecimalBig("760.0")), 169 | []*Order{ 170 | NewOrder("s3", Sell, DecimalBig("10.0"), DecimalBig("760.0")), 171 | }, 172 | // nil, 173 | NewOrder("b1", Buy, DecimalBig("10.0"), DecimalBig("760.0")), 174 | }, 175 | 176 | //////////////////////////////////////////////////////////////////////// 177 | { 178 | []*Order{ 179 | // NewOrder("b1", Buy, DecimalBig("10.0"), DecimalBig("74.0")), 180 | // NewOrder("b2", Buy, DecimalBig("10.0"), DecimalBig("75.0")), 181 | NewOrder("s1", Sell, DecimalBig("0.001"), DecimalBig("4000000.00")), 182 | NewOrder("s2", Sell, DecimalBig("0.001"), DecimalBig("3990000.00")), 183 | }, 184 | NewOrder("b1", Buy, DecimalBig("0.2"), DecimalBig("3990000.00")), 185 | []*Order{ 186 | NewOrder("s2", Sell, DecimalBig("0.001"), DecimalBig("3990000.00")), 187 | }, 188 | // nil, 189 | NewOrder("b1", Buy, DecimalBig("0.199"), DecimalBig("3990000.00")), 190 | }, 191 | 192 | //////////////////////////////////////////////////////////////////////// 193 | 194 | { 195 | []*Order{ 196 | // NewOrder("b1", Buy, DecimalBig("10.0"), DecimalBig("74.0")), 197 | // NewOrder("b2", Buy, DecimalBig("10.0"), DecimalBig("75.0")), 198 | NewOrder("b1", Buy, DecimalBig("0.001"), DecimalBig("4000000.00")), 199 | NewOrder("b2", Buy, DecimalBig("0.001"), DecimalBig("3990000.00")), 200 | }, 201 | NewOrder("s1", Sell, DecimalBig("0.2"), DecimalBig("4000000.00")), 202 | []*Order{ 203 | NewOrder("b1", Buy, DecimalBig("0.001"), DecimalBig("4000000.00")), 204 | }, 205 | // nil, 206 | NewOrder("s1", Sell, DecimalBig("0.199"), DecimalBig("4000000.00")), 207 | }, 208 | 209 | //////////////////////////////////////////////////////////////////////// 210 | 211 | { 212 | []*Order{ 213 | // NewOrder("b1", Buy, DecimalBig("10.0"), DecimalBig("74.0")), 214 | // NewOrder("b2", Buy, DecimalBig("10.0"), DecimalBig("75.0")), 215 | NewOrder("b1", Buy, DecimalBig("0.2"), DecimalBig("4200000.00")), 216 | NewOrder("b2", Buy, DecimalBig("0.001"), DecimalBig("4100000.00")), 217 | }, 218 | NewOrder("s1", Sell, DecimalBig("0.001"), DecimalBig("4200000.00")), 219 | []*Order{ 220 | NewOrder("s1", Sell, DecimalBig("0.001"), DecimalBig("4200000.00")), 221 | }, 222 | // nil, 223 | NewOrder("b1", Buy, DecimalBig("0.199"), DecimalBig("4200000.00")), 224 | }, 225 | 226 | //////////////////////////////////////////////////////////////////////// 227 | 228 | } 229 | 230 | for i, tt := range tests { 231 | ob := NewOrderBook() 232 | 233 | // Order book generation. 234 | for _, o := range tt.bookGen { 235 | ob.Process(*o) 236 | } 237 | 238 | fmt.Println("before:", ob) 239 | processedOrder, partialOrder := ob.Process(*tt.input) 240 | fmt.Println("result ", i, processedOrder, partialOrder) 241 | fmt.Println("after:", ob) 242 | for i, po := range processedOrder { 243 | if po.String() != tt.processedOrder[i].String() { 244 | fmt.Println(*po, *tt.processedOrder[i], *po == *tt.processedOrder[i]) 245 | fmt.Println(len(po.String()), len((tt.processedOrder[i].String()))) 246 | t.Fatalf("Incorrect processedOrder: (have: \n%s\n, want: \n%s\n)", processedOrder, tt.processedOrder) 247 | } 248 | } 249 | 250 | // fmt.Println("tt.partialOrder", tt.partialOrder) 251 | // fmt.Println("partialOrder", partialOrder) 252 | if tt.partialOrder == nil { 253 | if partialOrder != tt.partialOrder { 254 | // fmt.Println(len(partialOrder.String()), len((tt.partialOrder.String()))) 255 | t.Fatalf("Incorrect partialOrder: (have: \n%s\n, want: \n%s)", partialOrder, tt.partialOrder) 256 | } 257 | } else { 258 | if partialOrder == nil { 259 | t.Fatalf("Incorrect partialOrder: (have: \n%s\n, want: \n%s)", partialOrder, tt.partialOrder) 260 | } else { 261 | if partialOrder.String() != tt.partialOrder.String() { 262 | // fmt.Println(len(partialOrder.String()), len((tt.partialOrder.String()))) 263 | t.Fatalf("Incorrect partialOrder: (have: \n%s\n, want: \n%s)", partialOrder, tt.partialOrder) 264 | } 265 | } 266 | } 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /engine/process_market_order.go: -------------------------------------------------------------------------------- 1 | package engine 2 | 3 | import ( 4 | "github.com/Pantelwar/binarytree" 5 | "github.com/Pantelwar/matching-engine/util" 6 | ) 7 | 8 | // ProcessMarket executes limit process 9 | func (ob *OrderBook) ProcessMarket(order Order) ([]*Order, *Order) { 10 | if order.Type == Buy { 11 | // return ob.processOrderB(order) 12 | return ob.commonProcessMarket(order, ob.SellTree, ob.addBuyOrder, ob.removeSellNode) 13 | } 14 | // return ob.processOrderS(order) 15 | return ob.commonProcessMarket(order, ob.BuyTree, ob.addSellOrder, ob.removeBuyNode) 16 | } 17 | 18 | func (ob *OrderBook) commonProcessMarket(order Order, tree *binarytree.BinaryTree, add func(Order), remove func(float64) error) ([]*Order, *Order) { 19 | var maxNode *binarytree.BinaryNode 20 | if order.Type == Sell { 21 | maxNode = tree.Max() 22 | } else { 23 | maxNode = tree.Min() 24 | } 25 | if maxNode == nil { 26 | // add(order) 27 | return nil, nil 28 | } 29 | count := 0 30 | noMoreOrders := false 31 | var allOrdersProcessed []*Order 32 | var partialOrder *Order 33 | orderOriginalAmount := order.Amount 34 | for maxNode == nil || order.Amount.Cmp(decimalZero) == 1 { 35 | count++ 36 | if order.Type == Sell { 37 | maxNode = tree.Max() 38 | } else { 39 | maxNode = tree.Min() 40 | } 41 | if maxNode == nil || noMoreOrders { 42 | if order.Amount.Cmp(decimalZero) == 1 { 43 | allOrdersProcessed = append(allOrdersProcessed, NewOrder(order.ID, order.Type, orderOriginalAmount, decimalZero)) 44 | } 45 | break 46 | } 47 | 48 | // var t []Trade 49 | var ordersProcessed []*Order 50 | noMoreOrders, ordersProcessed, partialOrder = ob.processLimitMarket(&order, maxNode.Data.(*OrderType).Tree, orderOriginalAmount) //, orderPrice) 51 | allOrdersProcessed = append(allOrdersProcessed, ordersProcessed...) 52 | // trades = append(trades, t...) 53 | 54 | if maxNode.Data.(*OrderType).Tree.Root == nil { 55 | // node := remove(maxNode.Key) 56 | // // node := ob.removeBuyNode(maxNode.Key) 57 | // tree.Root = node 58 | remove(maxNode.Key) 59 | } 60 | } 61 | 62 | // return trades, allOrdersProcessed, partialOrder 63 | return allOrdersProcessed, partialOrder 64 | } 65 | 66 | func (ob *OrderBook) processLimitMarket(order *Order, tree *binarytree.BinaryTree, orderOriginalAmount *util.StandardBigDecimal) (bool, []*Order, *Order) { 67 | // orderPrice, _ := order.Price.Float64() 68 | var maxNode *binarytree.BinaryNode 69 | if order.Type == Sell { 70 | maxNode = tree.Max() 71 | } else { 72 | maxNode = tree.Min() 73 | } 74 | noMoreOrders := false 75 | var ordersProcessed []*Order 76 | var partialOrder *Order 77 | if maxNode == nil { 78 | // return trades, noMoreOrders, nil, nil 79 | return noMoreOrders, nil, nil 80 | } 81 | // countAdd := 0.0 82 | for maxNode == nil || order.Amount.Cmp(decimalZero) == 1 { 83 | if order.Type == Sell { 84 | maxNode = tree.Max() 85 | } else { 86 | maxNode = tree.Min() 87 | } 88 | if maxNode == nil || noMoreOrders { 89 | break 90 | // if order.Amount.Cmp(decimalZero) == 1 { 91 | // // fmt.Println("inserting", noMoreOrders) 92 | // // ordersProcessed = append(ordersProcessed, NewOrder(order.ID, order.Type, orderOriginalAmount, decimalZero)) 93 | // // partialOrder = NewOrder(order.ID, order.Type, order.Amount, order.Price) 94 | // break 95 | // } else { 96 | // break 97 | // } 98 | } 99 | // if order.Type == Sell { 100 | // if orderPrice > maxNode.Key { 101 | // // fmt.Println("adding sellnode directly") 102 | // noMoreOrders = true 103 | // // return trades, noMoreOrders, nil, nil 104 | // return noMoreOrders, nil, nil 105 | // } 106 | // } else { 107 | // if orderPrice < maxNode.Key { 108 | // // fmt.Println("adding buynode directly") 109 | // noMoreOrders = true 110 | // // return trades, noMoreOrders, nil, nil 111 | // return noMoreOrders, nil, nil 112 | // } 113 | // } 114 | 115 | nodeData := maxNode.Data.(*OrderNode) //([]*Order) 116 | nodeOrders := nodeData.Orders //([]*Order) 117 | countMatch := 0 118 | for _, ele := range nodeOrders { 119 | // if order.Type == Sell { 120 | // if ele.Price.Cmp(order.Price) == -1 { 121 | // noMoreOrders = true 122 | // break 123 | // } 124 | // } else { 125 | // if ele.Price.Cmp(order.Price) == 1 { 126 | // noMoreOrders = true 127 | // break 128 | // } 129 | // } 130 | 131 | // countAdd += ele.Amount 132 | // fmt.Println(ele.Price, ele.Amount, order.Amount, ele.Amount.Cmp(order.Amount)) 133 | if ele.Amount.Cmp(order.Amount) == 1 { 134 | nodeData.updateVolume(order.Amount.Neg()) 135 | // trades = append(trades, Trade{BuyOrderID: ele.ID, SellOrderID: order.ID, Amount: order.Amount, Price: ele.Price}) 136 | 137 | amount := ele.Amount.Sub(order.Amount) 138 | // amount = math.Floor(amount*100000000) / 100000000 139 | ele.Amount = amount 140 | 141 | partialOrder = NewOrder(ele.ID, ele.Type, ele.Amount, ele.Price) 142 | ordersProcessed = append(ordersProcessed, NewOrder(order.ID, order.Type, orderOriginalAmount, decimalZero)) 143 | 144 | maxNode.SetData(nodeData) 145 | 146 | order.Amount, _ = util.NewDecimalFromString("0.0") 147 | noMoreOrders = true 148 | break 149 | } 150 | if ele.Amount.Cmp(order.Amount) == 0 { 151 | nodeData.updateVolume(order.Amount.Neg()) 152 | 153 | ordersProcessed = append(ordersProcessed, NewOrder(ele.ID, ele.Type, ele.Amount, ele.Price)) 154 | ordersProcessed = append(ordersProcessed, NewOrder(order.ID, order.Type, orderOriginalAmount, decimalZero)) 155 | 156 | countMatch++ 157 | // trades = append(trades, Trade{BuyOrderID: ele.ID, SellOrderID: order.ID, Amount: order.Amount, Price: ele.Price}) 158 | 159 | order.Amount, _ = util.NewDecimalFromString("0.0") 160 | // orderComplete = true 161 | 162 | // ele.Amount = 0 163 | ob.mutex.Lock() 164 | delete(ob.orders, ele.ID) 165 | ob.mutex.Unlock() 166 | 167 | break 168 | } else { 169 | countMatch++ 170 | 171 | ordersProcessed = append(ordersProcessed, NewOrder(ele.ID, ele.Type, ele.Amount, ele.Price)) 172 | 173 | nodeData.updateVolume(ele.Amount.Neg()) 174 | 175 | // trades = append(trades, Trade{BuyOrderID: ele.ID, SellOrderID: order.ID, Amount: ele.Amount, Price: ele.Price}) 176 | 177 | order.Amount = order.Amount.Sub(ele.Amount) 178 | ob.mutex.Lock() 179 | delete(ob.orders, ele.ID) 180 | ob.mutex.Unlock() 181 | } 182 | } 183 | 184 | if len(nodeOrders) == countMatch { 185 | node := tree.Root.Remove(maxNode.Key) // ob.removeBuyNode(maxNode.Key, buyTree) 186 | // fmt.Printf("node removed: %#v %#v\n", node, maxNode) 187 | tree.Root = node 188 | } 189 | 190 | nodeData.Orders = nodeOrders[countMatch:] 191 | maxNode.SetData(nodeData) 192 | } 193 | // return trades, noMoreOrders, ordersProcessed, partialOrder 194 | return noMoreOrders, ordersProcessed, partialOrder 195 | } 196 | -------------------------------------------------------------------------------- /engine/process_market_order_test.go: -------------------------------------------------------------------------------- 1 | package engine 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | // var decimalZero, _ = util.NewDecimalFromString("0.0") 9 | 10 | func TestProcessMarketOrder(t *testing.T) { 11 | var tests = []struct { 12 | bookGen []*Order 13 | input *Order 14 | processedOrder []*Order 15 | partialOrder *Order 16 | book string 17 | }{ 18 | // //////////////////////////////////////////////////////////////////////// 19 | // { 20 | // []*Order{}, 21 | // NewOrder("b1", Buy, DecimalBig("2.0"), DecimalBig("7000.0")), 22 | // []*Order{}, 23 | // nil, 24 | // `------------------------------------------ 25 | // `}, 26 | // //////////////////////////////////////////////////////////////////////// 27 | // { 28 | // []*Order{}, 29 | // NewOrder("s1", Sell, DecimalBig("2.0"), DecimalBig("7000.0")), 30 | // []*Order{}, 31 | // nil, 32 | // `------------------------------------------ 33 | // `}, 34 | { 35 | []*Order{ 36 | NewOrder("b1", Buy, DecimalBig("5.0"), DecimalBig("7000.0")), 37 | }, 38 | NewOrder("s2", Sell, DecimalBig("5.0"), DecimalBig("8000.0")), 39 | []*Order{ 40 | NewOrder("b1", Buy, DecimalBig("5.0"), DecimalBig("7000.0")), 41 | NewOrder("s2", Sell, DecimalBig("5.0"), decimalZero), 42 | }, 43 | nil, 44 | `------------------------------------------ 45 | `}, 46 | // { 47 | // []*Order{ 48 | // NewOrder("s2", Sell, DecimalBig("5.0"), DecimalBig("8000.0")), 49 | // }, 50 | // NewOrder("b1", Buy, DecimalBig("5.0"), DecimalBig("7000.0")), 51 | // []*Order{ 52 | // NewOrder("s2", Sell, DecimalBig("5.0"), DecimalBig("8000.0")), 53 | // NewOrder("b1", Buy, DecimalBig("5.0"), decimalZero), 54 | // }, 55 | // nil, 56 | // `------------------------------------------ 57 | // `}, 58 | // ////////////////////////////////////////////////////////////////////// 59 | // { 60 | // []*Order{ 61 | // NewOrder("b1", Buy, DecimalBig("5.0"), DecimalBig("7000.0")), 62 | // }, 63 | // NewOrder("s2", Sell, DecimalBig("5.0"), DecimalBig("7000.0")), 64 | // []*Order{ 65 | // NewOrder("b1", Buy, DecimalBig("5.0"), DecimalBig("7000.0")), 66 | // NewOrder("s2", Sell, DecimalBig("5.0"), decimalZero), 67 | // }, 68 | // nil, 69 | // `------------------------------------------ 70 | // `}, 71 | // { 72 | // []*Order{ 73 | // NewOrder("s1", Sell, DecimalBig("5.0"), DecimalBig("7000.0")), 74 | // }, 75 | // NewOrder("b2", Buy, DecimalBig("5.0"), DecimalBig("7000.0")), 76 | // []*Order{ 77 | // NewOrder("s1", Sell, DecimalBig("5.0"), DecimalBig("7000.0")), 78 | // NewOrder("b2", Buy, DecimalBig("5.0"), decimalZero), 79 | // }, 80 | // nil, 81 | // `------------------------------------------ 82 | // `}, 83 | // ////////////////////////////////////////////////////////////////////// 84 | // { 85 | // []*Order{ 86 | // NewOrder("b1", Buy, DecimalBig("5.0"), DecimalBig("7000.0")), 87 | // }, 88 | // NewOrder("s2", Sell, DecimalBig("1.0"), DecimalBig("7000.0")), 89 | // []*Order{ 90 | // NewOrder("s2", Sell, DecimalBig("1.0"), decimalZero), 91 | // }, 92 | // NewOrder("b1", Buy, DecimalBig("4.0"), DecimalBig("7000.0")), 93 | // `------------------------------------------ 94 | // 7000 -> 4 95 | // `}, 96 | // { 97 | // []*Order{ 98 | // NewOrder("s1", Sell, DecimalBig("5.0"), DecimalBig("7000.0")), 99 | // }, 100 | // NewOrder("b2", Buy, DecimalBig("1.0"), DecimalBig("7000.0")), 101 | // []*Order{ 102 | // NewOrder("b2", Buy, DecimalBig("1.0"), decimalZero), 103 | // }, 104 | // NewOrder("s1", Sell, DecimalBig("4.0"), DecimalBig("7000.0")), 105 | // `7000 -> 4 106 | // ------------------------------------------ 107 | // `}, 108 | // ////////////////////////////////////////////////////////////////////// 109 | // { 110 | // []*Order{ 111 | // NewOrder("b1", Buy, DecimalBig("5.0"), DecimalBig("7000.0")), 112 | // }, 113 | // NewOrder("s2", Sell, DecimalBig("1.0"), DecimalBig("6000.0")), 114 | // []*Order{ 115 | // NewOrder("s2", Sell, DecimalBig("1.0"), decimalZero), 116 | // }, 117 | // NewOrder("b1", Buy, DecimalBig("4.0"), DecimalBig("7000.0")), 118 | // `------------------------------------------ 119 | // 7000 -> 4 120 | // `}, 121 | // { 122 | // []*Order{ 123 | // NewOrder("s1", Sell, DecimalBig("5.0"), DecimalBig("7000.0")), 124 | // }, 125 | // NewOrder("b2", Buy, DecimalBig("1.0"), DecimalBig("8000.0")), 126 | // []*Order{ 127 | // NewOrder("b2", Buy, DecimalBig("1.0"), decimalZero), 128 | // }, 129 | // NewOrder("s1", Sell, DecimalBig("4.0"), DecimalBig("7000.0")), 130 | // `7000 -> 4 131 | // ------------------------------------------ 132 | // `}, 133 | 134 | // //////////////////////////////////////////////////////////////////////// 135 | // { 136 | // []*Order{ 137 | // NewOrder("b1", Buy, DecimalBig("1.0"), DecimalBig("7000.0")), 138 | // NewOrder("b2", Buy, DecimalBig("1.0"), DecimalBig("6000.0")), 139 | // }, 140 | // NewOrder("s3", Sell, DecimalBig("2.0"), DecimalBig("6000.0")), 141 | // []*Order{ 142 | // NewOrder("b1", Buy, DecimalBig("1.0"), DecimalBig("7000.0")), 143 | // NewOrder("b2", Buy, DecimalBig("1.0"), DecimalBig("6000.0")), 144 | // NewOrder("s3", Sell, DecimalBig("2.0"), decimalZero), 145 | // }, 146 | // nil, 147 | // `------------------------------------------ 148 | // `}, 149 | // //////////////////////////////////////////////////////////////////////// 150 | // { 151 | // []*Order{ 152 | // NewOrder("b1", Buy, DecimalBig("1.0"), DecimalBig("7000.0")), 153 | // NewOrder("b2", Buy, DecimalBig("2.0"), DecimalBig("6000.0")), 154 | // }, 155 | // NewOrder("s3", Sell, DecimalBig("2.0"), DecimalBig("6000.0")), 156 | // []*Order{ 157 | // NewOrder("b1", Buy, DecimalBig("1.0"), DecimalBig("7000.0")), 158 | // NewOrder("s3", Sell, DecimalBig("2.0"), decimalZero), 159 | // }, 160 | // NewOrder("b2", Buy, DecimalBig("1.0"), DecimalBig("6000.0")), 161 | // `------------------------------------------ 162 | // 6000 -> 1 163 | // `}, 164 | ////////////////////////////////////////////////////////////////////// 165 | { 166 | []*Order{ 167 | NewOrder("b1", Buy, DecimalBig("5.0"), DecimalBig("7000.0")), 168 | }, 169 | NewOrder("s2", Sell, DecimalBig("6.0"), DecimalBig("6000.0")), 170 | []*Order{ 171 | NewOrder("b1", Buy, DecimalBig("5.0"), DecimalBig("7000.0")), 172 | NewOrder("s2", Sell, DecimalBig("6.0"), decimalZero), 173 | }, 174 | nil, 175 | `------------------------------------------ 176 | `}, 177 | { 178 | []*Order{ 179 | NewOrder("s1", Sell, DecimalBig("5.0"), DecimalBig("7000.0")), 180 | }, 181 | NewOrder("b2", Buy, DecimalBig("6.0"), DecimalBig("8000.0")), 182 | []*Order{ 183 | NewOrder("s1", Sell, DecimalBig("5.0"), DecimalBig("7000.0")), 184 | NewOrder("b2", Buy, DecimalBig("6.0"), decimalZero), 185 | }, 186 | nil, 187 | `------------------------------------------ 188 | `}, 189 | 190 | { 191 | []*Order{ 192 | // NewOrder("b1", Buy, DecimalBig("10.0"), DecimalBig("74.0")), 193 | // NewOrder("b2", Buy, DecimalBig("10.0"), DecimalBig("75.0")), 194 | NewOrder("b1", Buy, DecimalBig("0.001"), DecimalBig("4000000.00")), 195 | NewOrder("b2", Buy, DecimalBig("0.001"), DecimalBig("3990000.00")), 196 | }, 197 | NewOrder("s1", Sell, DecimalBig("0.2"), DecimalBig("4000000.00")), 198 | []*Order{ 199 | NewOrder("b1", Buy, DecimalBig("0.001"), DecimalBig("4000000.00")), 200 | NewOrder("b2", Buy, DecimalBig("0.001"), DecimalBig("3990000.00")), 201 | NewOrder("s1", Sell, DecimalBig("0.2"), decimalZero), 202 | }, 203 | nil, 204 | "", 205 | }, 206 | 207 | //////////////////////////////////////////////////////////////////////// 208 | 209 | //////////////////////////////////////////////////////////////////////// 210 | 211 | { 212 | []*Order{ 213 | // NewOrder("b1", Buy, DecimalBig("10.0"), DecimalBig("74.0")), 214 | // NewOrder("b2", Buy, DecimalBig("10.0"), DecimalBig("75.0")), 215 | NewOrder("b1", Buy, DecimalBig("0.2"), DecimalBig("4200000.00")), 216 | NewOrder("b2", Buy, DecimalBig("0.001"), DecimalBig("4100000.00")), 217 | }, 218 | NewOrder("s1", Sell, DecimalBig("0.001"), DecimalBig("4200000.00")), 219 | []*Order{ 220 | NewOrder("s1", Sell, DecimalBig("0.001"), decimalZero), 221 | }, 222 | // nil, 223 | NewOrder("b1", Buy, DecimalBig("0.199"), DecimalBig("4200000.00")), 224 | "", 225 | }, 226 | 227 | //////////////////////////////////////////////////////////////////////// 228 | 229 | } 230 | 231 | for i, tt := range tests { 232 | ob := NewOrderBook() 233 | 234 | // Order book generation. 235 | for _, o := range tt.bookGen { 236 | ob.Process(*o) 237 | } 238 | 239 | fmt.Println("before:", ob) 240 | processedOrder, partialOrder := ob.ProcessMarket(*tt.input) 241 | fmt.Println("result ", i, processedOrder, partialOrder) 242 | fmt.Println("after:", ob) 243 | if len(processedOrder) != len(tt.processedOrder) { 244 | t.Fatalf("Incorrect processedOrder: (have: \n%s\n, want: \n%s\n)", processedOrder, tt.processedOrder) 245 | } 246 | for i, po := range processedOrder { 247 | if po.String() != tt.processedOrder[i].String() { 248 | fmt.Println(*po, *tt.processedOrder[i], *po == *tt.processedOrder[i]) 249 | fmt.Println(len(po.String()), len((tt.processedOrder[i].String()))) 250 | t.Fatalf("Incorrect processedOrder: (have: \n%s\n, want: \n%s\n)", processedOrder, tt.processedOrder) 251 | } 252 | } 253 | 254 | // if ob.String() != tt.book { 255 | // // fmt.Println(len(partialOrder.String()), len((tt.partialOrder.String()))) 256 | // t.Fatalf("Incorrect book: (have: \n%s\n, want: \n%s)", ob.String(), tt.book) 257 | // } 258 | 259 | if tt.partialOrder == nil { 260 | if partialOrder != tt.partialOrder { 261 | // fmt.Println(len(partialOrder.String()), len((tt.partialOrder.String()))) 262 | t.Fatalf("Incorrect partialOrder: (have: \n%s\n, want: \n%s)", partialOrder, tt.partialOrder) 263 | } 264 | } else { 265 | if partialOrder.String() != tt.partialOrder.String() { 266 | // fmt.Println(len(partialOrder.String()), len((tt.partialOrder.String()))) 267 | t.Fatalf("Incorrect partialOrder: (have: \n%s\n, want: \n%s)", partialOrder, tt.partialOrder) 268 | } 269 | } 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /engine/side.go: -------------------------------------------------------------------------------- 1 | package engine 2 | 3 | import ( 4 | "encoding/json" 5 | "reflect" 6 | ) 7 | 8 | // Side of the order 9 | type Side string 10 | 11 | // Sell (asks) or Buy (bids) 12 | const ( 13 | Buy Side = "buy" 14 | Sell Side = "sell" 15 | ) 16 | 17 | // MarshalJSON implements json.Marshaler interface 18 | func (s Side) MarshalJSON() ([]byte, error) { 19 | return []byte(`"` + s.String() + `"`), nil 20 | } 21 | 22 | // UnmarshalJSON implements interface for json unmarshal 23 | func (s *Side) UnmarshalJSON(data []byte) error { 24 | switch string(data) { 25 | case `"buy"`: 26 | *s = Buy 27 | case `"sell"`: 28 | *s = Sell 29 | default: 30 | return &json.UnsupportedValueError{ 31 | Value: reflect.New(reflect.TypeOf(data)), 32 | Str: string(data), 33 | } 34 | } 35 | 36 | return nil 37 | } 38 | 39 | // String implements Stringer interface 40 | func (s Side) String() string { 41 | if s == Buy { 42 | return "buy" 43 | } else if s == Sell { 44 | return "sell" 45 | } 46 | return "" 47 | } 48 | -------------------------------------------------------------------------------- /engine/side_test.go: -------------------------------------------------------------------------------- 1 | package engine 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | ) 7 | 8 | type testSide struct { 9 | Type Side `json:"type"` 10 | } 11 | 12 | func TestSideUnmarshal(t *testing.T) { 13 | var tests = []struct { 14 | input string 15 | err string 16 | message string 17 | }{ 18 | {"{\"type\":\"buy\"}", "", "JSON should be approved"}, 19 | {"{\"type\":\"sell\"}", "", "JSON should be approved"}, 20 | {"{}", "err", "Empty JSON should not be passed"}, 21 | {"\"type\":\"random\"}", "err", "type should be either buy or sell"}, 22 | } 23 | 24 | for _, tt := range tests { 25 | t.Run(tt.input, func(t *testing.T) { 26 | side := testSide{} 27 | err := json.Unmarshal([]byte(tt.input), &side) 28 | if tt.err == "" && err == nil { 29 | t.Log("Successfully detecting error") 30 | } else if tt.err != "" && err != nil { 31 | t.Log("Successful detection of json") 32 | } else { 33 | if tt.err != "" && side.Type == "" { 34 | t.Log("Successful detecting of empty json") 35 | } else { 36 | t.Fatal(tt.message) 37 | } 38 | } 39 | }) 40 | } 41 | } 42 | 43 | func TestSideMarshal(t *testing.T) { 44 | var tests = []struct { 45 | input Side 46 | output string 47 | }{ 48 | {Buy, "\"buy\""}, 49 | {Sell, "\"sell\""}, 50 | } 51 | 52 | for _, tt := range tests { 53 | output, _ := json.Marshal(tt.input) 54 | if string(output) != tt.output { 55 | t.Fatalf("Marshal error: (have: %s, want: %s\n", string(output), tt.output) 56 | } 57 | } 58 | } 59 | 60 | func TestSideString(t *testing.T) { 61 | var tests = []struct { 62 | input Side 63 | output string 64 | }{ 65 | {Buy, "buy"}, 66 | {Sell, "sell"}, 67 | } 68 | 69 | for _, tt := range tests { 70 | output := tt.input.String() 71 | if string(output) != tt.output { 72 | t.Fatalf("String error: (have: %s, want: %s)\n", string(output), tt.output) 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /engine/trade.go: -------------------------------------------------------------------------------- 1 | package engine 2 | 3 | import "encoding/json" 4 | 5 | // Trade describes the trade structure 6 | type Trade struct { 7 | BuyOrderID string `json:"buy_order_id"` 8 | SellOrderID string `json:"sell_order_id"` 9 | Amount float64 `json:"amount"` 10 | Price float64 `json:"price"` 11 | } 12 | 13 | // FromJSON create the Trade struct from json string 14 | func (trade *Trade) FromJSON(msg []byte) error { 15 | return json.Unmarshal(msg, trade) 16 | } 17 | 18 | // ToJSON returns json string of the Trade 19 | func (trade *Trade) ToJSON() []byte { 20 | str, _ := json.Marshal(trade) 21 | return str 22 | } 23 | -------------------------------------------------------------------------------- /engineGrpc/engine.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // source: engine.proto 3 | 4 | package engine 5 | 6 | import ( 7 | context "context" 8 | fmt "fmt" 9 | proto "github.com/golang/protobuf/proto" 10 | grpc "google.golang.org/grpc" 11 | codes "google.golang.org/grpc/codes" 12 | status "google.golang.org/grpc/status" 13 | math "math" 14 | ) 15 | 16 | // Reference imports to suppress errors if they are not otherwise used. 17 | var _ = proto.Marshal 18 | var _ = fmt.Errorf 19 | var _ = math.Inf 20 | 21 | // This is a compile-time assertion to ensure that this generated file 22 | // is compatible with the proto package it is being compiled against. 23 | // A compilation error at this line likely means your copy of the 24 | // proto package needs to be updated. 25 | const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package 26 | 27 | type Side int32 28 | 29 | const ( 30 | Side_buy Side = 0 31 | Side_sell Side = 1 32 | ) 33 | 34 | var Side_name = map[int32]string{ 35 | 0: "buy", 36 | 1: "sell", 37 | } 38 | 39 | var Side_value = map[string]int32{ 40 | "buy": 0, 41 | "sell": 1, 42 | } 43 | 44 | func (x Side) String() string { 45 | return proto.EnumName(Side_name, int32(x)) 46 | } 47 | 48 | func (Side) EnumDescriptor() ([]byte, []int) { 49 | return fileDescriptor_770b178c3aab763f, []int{0} 50 | } 51 | 52 | type Order struct { 53 | Type Side `protobuf:"varint,1,opt,name=Type,json=type,proto3,enum=Side" json:"Type,omitempty"` 54 | ID string `protobuf:"bytes,2,opt,name=ID,json=id,proto3" json:"ID,omitempty"` 55 | Amount string `protobuf:"bytes,3,opt,name=Amount,json=amount,proto3" json:"Amount,omitempty"` 56 | Price string `protobuf:"bytes,4,opt,name=Price,json=price,proto3" json:"Price,omitempty"` 57 | Pair string `protobuf:"bytes,5,opt,name=Pair,json=pair,proto3" json:"Pair,omitempty"` 58 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 59 | XXX_unrecognized []byte `json:"-"` 60 | XXX_sizecache int32 `json:"-"` 61 | } 62 | 63 | func (m *Order) Reset() { *m = Order{} } 64 | func (m *Order) String() string { return proto.CompactTextString(m) } 65 | func (*Order) ProtoMessage() {} 66 | func (*Order) Descriptor() ([]byte, []int) { 67 | return fileDescriptor_770b178c3aab763f, []int{0} 68 | } 69 | 70 | func (m *Order) XXX_Unmarshal(b []byte) error { 71 | return xxx_messageInfo_Order.Unmarshal(m, b) 72 | } 73 | func (m *Order) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 74 | return xxx_messageInfo_Order.Marshal(b, m, deterministic) 75 | } 76 | func (m *Order) XXX_Merge(src proto.Message) { 77 | xxx_messageInfo_Order.Merge(m, src) 78 | } 79 | func (m *Order) XXX_Size() int { 80 | return xxx_messageInfo_Order.Size(m) 81 | } 82 | func (m *Order) XXX_DiscardUnknown() { 83 | xxx_messageInfo_Order.DiscardUnknown(m) 84 | } 85 | 86 | var xxx_messageInfo_Order proto.InternalMessageInfo 87 | 88 | func (m *Order) GetType() Side { 89 | if m != nil { 90 | return m.Type 91 | } 92 | return Side_buy 93 | } 94 | 95 | func (m *Order) GetID() string { 96 | if m != nil { 97 | return m.ID 98 | } 99 | return "" 100 | } 101 | 102 | func (m *Order) GetAmount() string { 103 | if m != nil { 104 | return m.Amount 105 | } 106 | return "" 107 | } 108 | 109 | func (m *Order) GetPrice() string { 110 | if m != nil { 111 | return m.Price 112 | } 113 | return "" 114 | } 115 | 116 | func (m *Order) GetPair() string { 117 | if m != nil { 118 | return m.Pair 119 | } 120 | return "" 121 | } 122 | 123 | type OutputOrders struct { 124 | OrdersProcessed string `protobuf:"bytes,1,opt,name=OrdersProcessed,json=orders_processed,proto3" json:"OrdersProcessed,omitempty"` 125 | PartialOrder string `protobuf:"bytes,2,opt,name=PartialOrder,json=partial_order,proto3" json:"PartialOrder,omitempty"` 126 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 127 | XXX_unrecognized []byte `json:"-"` 128 | XXX_sizecache int32 `json:"-"` 129 | } 130 | 131 | func (m *OutputOrders) Reset() { *m = OutputOrders{} } 132 | func (m *OutputOrders) String() string { return proto.CompactTextString(m) } 133 | func (*OutputOrders) ProtoMessage() {} 134 | func (*OutputOrders) Descriptor() ([]byte, []int) { 135 | return fileDescriptor_770b178c3aab763f, []int{1} 136 | } 137 | 138 | func (m *OutputOrders) XXX_Unmarshal(b []byte) error { 139 | return xxx_messageInfo_OutputOrders.Unmarshal(m, b) 140 | } 141 | func (m *OutputOrders) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 142 | return xxx_messageInfo_OutputOrders.Marshal(b, m, deterministic) 143 | } 144 | func (m *OutputOrders) XXX_Merge(src proto.Message) { 145 | xxx_messageInfo_OutputOrders.Merge(m, src) 146 | } 147 | func (m *OutputOrders) XXX_Size() int { 148 | return xxx_messageInfo_OutputOrders.Size(m) 149 | } 150 | func (m *OutputOrders) XXX_DiscardUnknown() { 151 | xxx_messageInfo_OutputOrders.DiscardUnknown(m) 152 | } 153 | 154 | var xxx_messageInfo_OutputOrders proto.InternalMessageInfo 155 | 156 | func (m *OutputOrders) GetOrdersProcessed() string { 157 | if m != nil { 158 | return m.OrdersProcessed 159 | } 160 | return "" 161 | } 162 | 163 | func (m *OutputOrders) GetPartialOrder() string { 164 | if m != nil { 165 | return m.PartialOrder 166 | } 167 | return "" 168 | } 169 | 170 | type BookInput struct { 171 | Pair string `protobuf:"bytes,1,opt,name=pair,proto3" json:"pair,omitempty"` 172 | Limit int64 `protobuf:"varint,2,opt,name=limit,proto3" json:"limit,omitempty"` 173 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 174 | XXX_unrecognized []byte `json:"-"` 175 | XXX_sizecache int32 `json:"-"` 176 | } 177 | 178 | func (m *BookInput) Reset() { *m = BookInput{} } 179 | func (m *BookInput) String() string { return proto.CompactTextString(m) } 180 | func (*BookInput) ProtoMessage() {} 181 | func (*BookInput) Descriptor() ([]byte, []int) { 182 | return fileDescriptor_770b178c3aab763f, []int{2} 183 | } 184 | 185 | func (m *BookInput) XXX_Unmarshal(b []byte) error { 186 | return xxx_messageInfo_BookInput.Unmarshal(m, b) 187 | } 188 | func (m *BookInput) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 189 | return xxx_messageInfo_BookInput.Marshal(b, m, deterministic) 190 | } 191 | func (m *BookInput) XXX_Merge(src proto.Message) { 192 | xxx_messageInfo_BookInput.Merge(m, src) 193 | } 194 | func (m *BookInput) XXX_Size() int { 195 | return xxx_messageInfo_BookInput.Size(m) 196 | } 197 | func (m *BookInput) XXX_DiscardUnknown() { 198 | xxx_messageInfo_BookInput.DiscardUnknown(m) 199 | } 200 | 201 | var xxx_messageInfo_BookInput proto.InternalMessageInfo 202 | 203 | func (m *BookInput) GetPair() string { 204 | if m != nil { 205 | return m.Pair 206 | } 207 | return "" 208 | } 209 | 210 | func (m *BookInput) GetLimit() int64 { 211 | if m != nil { 212 | return m.Limit 213 | } 214 | return 0 215 | } 216 | 217 | type BookArray struct { 218 | PriceAmount []string `protobuf:"bytes,1,rep,name=price_amount,json=priceAmount,proto3" json:"price_amount,omitempty"` 219 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 220 | XXX_unrecognized []byte `json:"-"` 221 | XXX_sizecache int32 `json:"-"` 222 | } 223 | 224 | func (m *BookArray) Reset() { *m = BookArray{} } 225 | func (m *BookArray) String() string { return proto.CompactTextString(m) } 226 | func (*BookArray) ProtoMessage() {} 227 | func (*BookArray) Descriptor() ([]byte, []int) { 228 | return fileDescriptor_770b178c3aab763f, []int{3} 229 | } 230 | 231 | func (m *BookArray) XXX_Unmarshal(b []byte) error { 232 | return xxx_messageInfo_BookArray.Unmarshal(m, b) 233 | } 234 | func (m *BookArray) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 235 | return xxx_messageInfo_BookArray.Marshal(b, m, deterministic) 236 | } 237 | func (m *BookArray) XXX_Merge(src proto.Message) { 238 | xxx_messageInfo_BookArray.Merge(m, src) 239 | } 240 | func (m *BookArray) XXX_Size() int { 241 | return xxx_messageInfo_BookArray.Size(m) 242 | } 243 | func (m *BookArray) XXX_DiscardUnknown() { 244 | xxx_messageInfo_BookArray.DiscardUnknown(m) 245 | } 246 | 247 | var xxx_messageInfo_BookArray proto.InternalMessageInfo 248 | 249 | func (m *BookArray) GetPriceAmount() []string { 250 | if m != nil { 251 | return m.PriceAmount 252 | } 253 | return nil 254 | } 255 | 256 | type BookOutput struct { 257 | Buys []*BookArray `protobuf:"bytes,1,rep,name=Buys,json=buys,proto3" json:"Buys,omitempty"` 258 | Sells []*BookArray `protobuf:"bytes,2,rep,name=Sells,json=sells,proto3" json:"Sells,omitempty"` 259 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 260 | XXX_unrecognized []byte `json:"-"` 261 | XXX_sizecache int32 `json:"-"` 262 | } 263 | 264 | func (m *BookOutput) Reset() { *m = BookOutput{} } 265 | func (m *BookOutput) String() string { return proto.CompactTextString(m) } 266 | func (*BookOutput) ProtoMessage() {} 267 | func (*BookOutput) Descriptor() ([]byte, []int) { 268 | return fileDescriptor_770b178c3aab763f, []int{4} 269 | } 270 | 271 | func (m *BookOutput) XXX_Unmarshal(b []byte) error { 272 | return xxx_messageInfo_BookOutput.Unmarshal(m, b) 273 | } 274 | func (m *BookOutput) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 275 | return xxx_messageInfo_BookOutput.Marshal(b, m, deterministic) 276 | } 277 | func (m *BookOutput) XXX_Merge(src proto.Message) { 278 | xxx_messageInfo_BookOutput.Merge(m, src) 279 | } 280 | func (m *BookOutput) XXX_Size() int { 281 | return xxx_messageInfo_BookOutput.Size(m) 282 | } 283 | func (m *BookOutput) XXX_DiscardUnknown() { 284 | xxx_messageInfo_BookOutput.DiscardUnknown(m) 285 | } 286 | 287 | var xxx_messageInfo_BookOutput proto.InternalMessageInfo 288 | 289 | func (m *BookOutput) GetBuys() []*BookArray { 290 | if m != nil { 291 | return m.Buys 292 | } 293 | return nil 294 | } 295 | 296 | func (m *BookOutput) GetSells() []*BookArray { 297 | if m != nil { 298 | return m.Sells 299 | } 300 | return nil 301 | } 302 | 303 | func init() { 304 | proto.RegisterEnum("Side", Side_name, Side_value) 305 | proto.RegisterType((*Order)(nil), "Order") 306 | proto.RegisterType((*OutputOrders)(nil), "OutputOrders") 307 | proto.RegisterType((*BookInput)(nil), "BookInput") 308 | proto.RegisterType((*BookArray)(nil), "BookArray") 309 | proto.RegisterType((*BookOutput)(nil), "BookOutput") 310 | } 311 | 312 | func init() { 313 | proto.RegisterFile("engine.proto", fileDescriptor_770b178c3aab763f) 314 | } 315 | 316 | var fileDescriptor_770b178c3aab763f = []byte{ 317 | // 384 bytes of a gzipped FileDescriptorProto 318 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x52, 0x4d, 0x6f, 0xd4, 0x30, 319 | 0x10, 0x25, 0x89, 0x93, 0x92, 0xd9, 0x6c, 0x59, 0x8d, 0x10, 0x72, 0x7b, 0x40, 0x21, 0x20, 0xb4, 320 | 0x70, 0xc8, 0x61, 0x11, 0x3f, 0xa0, 0xe5, 0x43, 0xea, 0x01, 0x1a, 0xa5, 0x9c, 0x59, 0x79, 0x13, 321 | 0x0b, 0xac, 0x66, 0x13, 0xcb, 0x71, 0x0e, 0xf9, 0x1b, 0xfc, 0x62, 0xe4, 0x71, 0x58, 0x84, 0x50, 322 | 0x4f, 0xf1, 0x3c, 0xbf, 0x99, 0x79, 0xef, 0xc5, 0x90, 0xc9, 0xfe, 0x87, 0xea, 0x65, 0xa9, 0xcd, 323 | 0x60, 0x87, 0xc2, 0x42, 0x7c, 0x6b, 0x5a, 0x69, 0xf0, 0x02, 0xd8, 0xb7, 0x59, 0x4b, 0x1e, 0xe4, 324 | 0xc1, 0xf6, 0x7c, 0x17, 0x97, 0x77, 0xaa, 0x95, 0x35, 0xb3, 0xb3, 0x96, 0x78, 0x0e, 0xe1, 0xcd, 325 | 0x47, 0x1e, 0xe6, 0xc1, 0x36, 0xad, 0x43, 0xd5, 0xe2, 0x33, 0x48, 0xae, 0x8e, 0xc3, 0xd4, 0x5b, 326 | 0x1e, 0x11, 0x96, 0x08, 0xaa, 0xf0, 0x29, 0xc4, 0x95, 0x51, 0x8d, 0xe4, 0x8c, 0xe0, 0x58, 0xbb, 327 | 0x02, 0x11, 0x58, 0x25, 0x94, 0xe1, 0x31, 0x81, 0x4c, 0x0b, 0x65, 0x8a, 0xef, 0x90, 0xdd, 0x4e, 328 | 0x56, 0x4f, 0x96, 0x76, 0x8f, 0xf8, 0x06, 0x9e, 0xf8, 0x53, 0x65, 0x86, 0x46, 0x8e, 0xa3, 0x6c, 329 | 0x49, 0x47, 0x5a, 0x6f, 0x06, 0x82, 0xf7, 0xfa, 0x0f, 0x8e, 0x2f, 0x21, 0xab, 0x84, 0xb1, 0x4a, 330 | 0x74, 0xd4, 0xb1, 0xc8, 0x5a, 0x6b, 0x8f, 0xed, 0x89, 0x5f, 0xbc, 0x87, 0xf4, 0x7a, 0x18, 0xee, 331 | 0x6f, 0x7a, 0x3d, 0x59, 0x27, 0xc0, 0x2d, 0x5d, 0x26, 0xd2, 0xd9, 0x49, 0xed, 0xd4, 0x51, 0x59, 332 | 0x6a, 0x8f, 0x6a, 0x5f, 0x14, 0xa5, 0x6f, 0xbb, 0x32, 0x46, 0xcc, 0xf8, 0x02, 0x32, 0x32, 0xb0, 333 | 0xf7, 0xee, 0x78, 0x90, 0x47, 0xdb, 0xb4, 0x5e, 0x11, 0xe6, 0xed, 0x17, 0x5f, 0x01, 0x1c, 0xdf, 334 | 0x5b, 0xc1, 0xe7, 0xc0, 0xae, 0xa7, 0x79, 0x24, 0xe2, 0x6a, 0x07, 0xe5, 0x69, 0x54, 0xcd, 0x0e, 335 | 0xd3, 0x3c, 0x62, 0x0e, 0xf1, 0x9d, 0xec, 0xba, 0x91, 0x87, 0xff, 0x11, 0xe2, 0xd1, 0x5d, 0xbc, 336 | 0xbd, 0x00, 0xe6, 0x62, 0xc7, 0x33, 0x88, 0x0e, 0xd3, 0xbc, 0x79, 0x84, 0x8f, 0x81, 0xb9, 0x9b, 337 | 0x4d, 0xb0, 0xfb, 0x15, 0x40, 0xf2, 0x89, 0x7e, 0x1c, 0xe6, 0x70, 0xb6, 0xc4, 0x84, 0x49, 0x49, 338 | 0x21, 0x5c, 0xae, 0xcb, 0x7f, 0xe2, 0x7c, 0x0d, 0xeb, 0x85, 0xf1, 0x45, 0x98, 0x7b, 0x69, 0x1f, 339 | 0xe2, 0x71, 0x48, 0x3e, 0x88, 0xbe, 0x91, 0xdd, 0x89, 0xb0, 0x7c, 0xf1, 0x15, 0xa4, 0x9f, 0xa5, 340 | 0x6d, 0x7e, 0x3a, 0x89, 0xe8, 0x95, 0x52, 0x98, 0x97, 0xab, 0xf2, 0xaf, 0xe3, 0x43, 0x42, 0x6f, 341 | 0xe8, 0xdd, 0xef, 0x00, 0x00, 0x00, 0xff, 0xff, 0x8d, 0x39, 0x65, 0xfd, 0x53, 0x02, 0x00, 0x00, 342 | } 343 | 344 | // Reference imports to suppress errors if they are not otherwise used. 345 | var _ context.Context 346 | var _ grpc.ClientConnInterface 347 | 348 | // This is a compile-time assertion to ensure that this generated file 349 | // is compatible with the grpc package it is being compiled against. 350 | const _ = grpc.SupportPackageIsVersion6 351 | 352 | // EngineClient is the client API for Engine service. 353 | // 354 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. 355 | type EngineClient interface { 356 | Process(ctx context.Context, in *Order, opts ...grpc.CallOption) (*OutputOrders, error) 357 | ProcessMarket(ctx context.Context, in *Order, opts ...grpc.CallOption) (*OutputOrders, error) 358 | Cancel(ctx context.Context, in *Order, opts ...grpc.CallOption) (*Order, error) 359 | FetchBook(ctx context.Context, in *BookInput, opts ...grpc.CallOption) (*BookOutput, error) 360 | } 361 | 362 | type engineClient struct { 363 | cc grpc.ClientConnInterface 364 | } 365 | 366 | func NewEngineClient(cc grpc.ClientConnInterface) EngineClient { 367 | return &engineClient{cc} 368 | } 369 | 370 | func (c *engineClient) Process(ctx context.Context, in *Order, opts ...grpc.CallOption) (*OutputOrders, error) { 371 | out := new(OutputOrders) 372 | err := c.cc.Invoke(ctx, "/Engine/Process", in, out, opts...) 373 | if err != nil { 374 | return nil, err 375 | } 376 | return out, nil 377 | } 378 | 379 | func (c *engineClient) ProcessMarket(ctx context.Context, in *Order, opts ...grpc.CallOption) (*OutputOrders, error) { 380 | out := new(OutputOrders) 381 | err := c.cc.Invoke(ctx, "/Engine/ProcessMarket", in, out, opts...) 382 | if err != nil { 383 | return nil, err 384 | } 385 | return out, nil 386 | } 387 | 388 | func (c *engineClient) Cancel(ctx context.Context, in *Order, opts ...grpc.CallOption) (*Order, error) { 389 | out := new(Order) 390 | err := c.cc.Invoke(ctx, "/Engine/Cancel", in, out, opts...) 391 | if err != nil { 392 | return nil, err 393 | } 394 | return out, nil 395 | } 396 | 397 | func (c *engineClient) FetchBook(ctx context.Context, in *BookInput, opts ...grpc.CallOption) (*BookOutput, error) { 398 | out := new(BookOutput) 399 | err := c.cc.Invoke(ctx, "/Engine/FetchBook", in, out, opts...) 400 | if err != nil { 401 | return nil, err 402 | } 403 | return out, nil 404 | } 405 | 406 | // EngineServer is the server API for Engine service. 407 | type EngineServer interface { 408 | Process(context.Context, *Order) (*OutputOrders, error) 409 | ProcessMarket(context.Context, *Order) (*OutputOrders, error) 410 | Cancel(context.Context, *Order) (*Order, error) 411 | FetchBook(context.Context, *BookInput) (*BookOutput, error) 412 | } 413 | 414 | // UnimplementedEngineServer can be embedded to have forward compatible implementations. 415 | type UnimplementedEngineServer struct { 416 | } 417 | 418 | func (*UnimplementedEngineServer) Process(ctx context.Context, req *Order) (*OutputOrders, error) { 419 | return nil, status.Errorf(codes.Unimplemented, "method Process not implemented") 420 | } 421 | func (*UnimplementedEngineServer) ProcessMarket(ctx context.Context, req *Order) (*OutputOrders, error) { 422 | return nil, status.Errorf(codes.Unimplemented, "method ProcessMarket not implemented") 423 | } 424 | func (*UnimplementedEngineServer) Cancel(ctx context.Context, req *Order) (*Order, error) { 425 | return nil, status.Errorf(codes.Unimplemented, "method Cancel not implemented") 426 | } 427 | func (*UnimplementedEngineServer) FetchBook(ctx context.Context, req *BookInput) (*BookOutput, error) { 428 | return nil, status.Errorf(codes.Unimplemented, "method FetchBook not implemented") 429 | } 430 | 431 | func RegisterEngineServer(s *grpc.Server, srv EngineServer) { 432 | s.RegisterService(&_Engine_serviceDesc, srv) 433 | } 434 | 435 | func _Engine_Process_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 436 | in := new(Order) 437 | if err := dec(in); err != nil { 438 | return nil, err 439 | } 440 | if interceptor == nil { 441 | return srv.(EngineServer).Process(ctx, in) 442 | } 443 | info := &grpc.UnaryServerInfo{ 444 | Server: srv, 445 | FullMethod: "/Engine/Process", 446 | } 447 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 448 | return srv.(EngineServer).Process(ctx, req.(*Order)) 449 | } 450 | return interceptor(ctx, in, info, handler) 451 | } 452 | 453 | func _Engine_ProcessMarket_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 454 | in := new(Order) 455 | if err := dec(in); err != nil { 456 | return nil, err 457 | } 458 | if interceptor == nil { 459 | return srv.(EngineServer).ProcessMarket(ctx, in) 460 | } 461 | info := &grpc.UnaryServerInfo{ 462 | Server: srv, 463 | FullMethod: "/Engine/ProcessMarket", 464 | } 465 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 466 | return srv.(EngineServer).ProcessMarket(ctx, req.(*Order)) 467 | } 468 | return interceptor(ctx, in, info, handler) 469 | } 470 | 471 | func _Engine_Cancel_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 472 | in := new(Order) 473 | if err := dec(in); err != nil { 474 | return nil, err 475 | } 476 | if interceptor == nil { 477 | return srv.(EngineServer).Cancel(ctx, in) 478 | } 479 | info := &grpc.UnaryServerInfo{ 480 | Server: srv, 481 | FullMethod: "/Engine/Cancel", 482 | } 483 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 484 | return srv.(EngineServer).Cancel(ctx, req.(*Order)) 485 | } 486 | return interceptor(ctx, in, info, handler) 487 | } 488 | 489 | func _Engine_FetchBook_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 490 | in := new(BookInput) 491 | if err := dec(in); err != nil { 492 | return nil, err 493 | } 494 | if interceptor == nil { 495 | return srv.(EngineServer).FetchBook(ctx, in) 496 | } 497 | info := &grpc.UnaryServerInfo{ 498 | Server: srv, 499 | FullMethod: "/Engine/FetchBook", 500 | } 501 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 502 | return srv.(EngineServer).FetchBook(ctx, req.(*BookInput)) 503 | } 504 | return interceptor(ctx, in, info, handler) 505 | } 506 | 507 | var _Engine_serviceDesc = grpc.ServiceDesc{ 508 | ServiceName: "Engine", 509 | HandlerType: (*EngineServer)(nil), 510 | Methods: []grpc.MethodDesc{ 511 | { 512 | MethodName: "Process", 513 | Handler: _Engine_Process_Handler, 514 | }, 515 | { 516 | MethodName: "ProcessMarket", 517 | Handler: _Engine_ProcessMarket_Handler, 518 | }, 519 | { 520 | MethodName: "Cancel", 521 | Handler: _Engine_Cancel_Handler, 522 | }, 523 | { 524 | MethodName: "FetchBook", 525 | Handler: _Engine_FetchBook_Handler, 526 | }, 527 | }, 528 | Streams: []grpc.StreamDesc{}, 529 | Metadata: "engine.proto", 530 | } 531 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/Pantelwar/matching-engine 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/Pantelwar/binarytree v1.0.0 7 | github.com/golang/protobuf v1.5.2 8 | github.com/shopspring/decimal v1.3.1 9 | google.golang.org/grpc v1.48.0 10 | ) 11 | 12 | require ( 13 | golang.org/x/net v0.0.0-20201021035429-f5854403a974 // indirect 14 | golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 // indirect 15 | golang.org/x/text v0.3.3 // indirect 16 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect 17 | google.golang.org/protobuf v1.27.1 // 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/Pantelwar/binarytree v1.0.0 h1:MIRuFcHokAcG3Xe6Hyc3akQZbrOdovf7Ypk0WdncbvE= 5 | github.com/Pantelwar/binarytree v1.0.0/go.mod h1:/NCD1RUdwk0zIggyYp8iXo7QaNu8tyZGSTBjJn86V+E= 6 | github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= 7 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 8 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 9 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 10 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 11 | github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 12 | github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= 13 | github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 14 | github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 15 | github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 16 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 17 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 18 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 19 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 20 | github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= 21 | github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= 22 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 23 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 24 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 25 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 26 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 27 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 28 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 29 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 30 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 31 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 32 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 33 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 34 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 35 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 36 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 37 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 38 | github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= 39 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 40 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 41 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 42 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 43 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 44 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 45 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 46 | github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= 47 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 48 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 72 | golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI= 73 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 74 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 75 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 76 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 77 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 78 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 79 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 80 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 81 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 82 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 83 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 84 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 85 | golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 h1:myAQVi0cGEoqQVR5POX+8RR2mrocKqNN1hmeMqhX27k= 86 | golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 87 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 88 | golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= 89 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 90 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 91 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 92 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 93 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 94 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 95 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 96 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 97 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 98 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 99 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 100 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 101 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 102 | google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 103 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= 104 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 105 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 106 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 107 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 108 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 109 | google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= 110 | google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= 111 | google.golang.org/grpc v1.48.0 h1:rQOsyJ/8+ufEDJd/Gdsz7HG220Mh9HAhFHRGnIjda0w= 112 | google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= 113 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 114 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 115 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 116 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 117 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 118 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 119 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 120 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 121 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 122 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 123 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 124 | google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= 125 | google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 126 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 127 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 128 | gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 129 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 130 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 131 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 132 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "os" 7 | 8 | engineGrpc "github.com/Pantelwar/matching-engine/engineGrpc" 9 | "github.com/Pantelwar/matching-engine/server" 10 | 11 | "google.golang.org/grpc" 12 | "google.golang.org/grpc/reflection" 13 | ) 14 | 15 | const ( 16 | port = ":9000" 17 | ) 18 | 19 | func main() { 20 | gs := grpc.NewServer() 21 | cs := server.NewEngine() 22 | engineGrpc.RegisterEngineServer(gs, cs) 23 | 24 | reflection.Register(gs) 25 | 26 | l, err := net.Listen("tcp", port) 27 | if err != nil { 28 | e := fmt.Errorf("Unable to listen server, err: %v", err) 29 | fmt.Println(e) 30 | os.Exit(1) 31 | } 32 | fmt.Printf("grpc server listening to %s\n", port) 33 | gs.Serve(l) 34 | } 35 | -------------------------------------------------------------------------------- /server/engine.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | 9 | "github.com/Pantelwar/matching-engine/engine" 10 | engineGrpc "github.com/Pantelwar/matching-engine/engineGrpc" 11 | "github.com/Pantelwar/matching-engine/util" 12 | ) 13 | 14 | // Engine ... 15 | type Engine struct { 16 | book map[string]*engine.OrderBook 17 | } 18 | 19 | // NewEngine returns Engine object 20 | func NewEngine() *Engine { 21 | return &Engine{book: map[string]*engine.OrderBook{}} 22 | } 23 | 24 | // Process implements EngineServer interface 25 | func (e *Engine) Process(ctx context.Context, req *engineGrpc.Order) (*engineGrpc.OutputOrders, error) { 26 | bigZero, _ := util.NewDecimalFromString("0.0") 27 | orderString := fmt.Sprintf("{\"id\":\"%s\", \"type\": \"%s\", \"amount\": \"%s\", \"price\": \"%s\" }", req.GetID(), req.GetType(), req.GetAmount(), req.GetPrice()) 28 | 29 | var order engine.Order 30 | // decode the message 31 | fmt.Println("Orderstring =: ", orderString) 32 | err := order.FromJSON([]byte(orderString)) 33 | if err != nil { 34 | fmt.Println("JSON Parse Error =: ", err) 35 | return nil, err 36 | } 37 | 38 | if order.Amount.Cmp(bigZero) == 0 || order.Price.Cmp(bigZero) == 0 { 39 | fmt.Println("Invalid JSON") 40 | return nil, errors.New("Invalid JSON") 41 | } 42 | 43 | if req.GetPair() == "" { 44 | fmt.Println("Invalid pair") 45 | return nil, errors.New("Invalid pair") 46 | } 47 | 48 | var pairBook *engine.OrderBook 49 | if val, ok := e.book[req.GetPair()]; ok { 50 | pairBook = val 51 | } else { 52 | pairBook = engine.NewOrderBook() 53 | e.book[req.GetPair()] = pairBook 54 | } 55 | 56 | ordersProcessed, partialOrder := pairBook.Process(order) 57 | 58 | ordersProcessedString, err := json.Marshal(ordersProcessed) 59 | 60 | // if order.Type.String() == "sell" { 61 | fmt.Println("pair:", req.GetPair()) 62 | fmt.Println(pairBook) 63 | // } 64 | 65 | if err != nil { 66 | fmt.Println("Marshal error", err) 67 | return nil, err 68 | } 69 | 70 | if partialOrder != nil { 71 | var partialOrderString []byte 72 | partialOrderString, err = json.Marshal(partialOrder) 73 | if err != nil { 74 | fmt.Println("partialOrderString Marshal error", err) 75 | return nil, err 76 | } 77 | return &engineGrpc.OutputOrders{OrdersProcessed: string(ordersProcessedString), PartialOrder: string(partialOrderString)}, nil 78 | } 79 | return &engineGrpc.OutputOrders{OrdersProcessed: string(ordersProcessedString), PartialOrder: "null"}, nil 80 | } 81 | 82 | // Cancel implements EngineServer interface 83 | func (e *Engine) Cancel(ctx context.Context, req *engineGrpc.Order) (*engineGrpc.Order, error) { 84 | order := &engine.Order{ID: req.GetID()} 85 | 86 | if order.ID == "" { 87 | fmt.Println("Invalid JSON") 88 | return nil, errors.New("Invalid JSON") 89 | } 90 | 91 | if req.GetPair() == "" { 92 | fmt.Println("Invalid pair") 93 | return nil, errors.New("Invalid pair") 94 | } 95 | 96 | var pairBook *engine.OrderBook 97 | if val, ok := e.book[req.GetPair()]; ok { 98 | pairBook = val 99 | } else { 100 | pairBook = engine.NewOrderBook() 101 | e.book[req.GetPair()] = pairBook 102 | } 103 | 104 | order = pairBook.CancelOrder(order.ID) 105 | 106 | fmt.Println("pair:", req.GetPair()) 107 | fmt.Println(pairBook) 108 | 109 | if order == nil { 110 | return nil, errors.New("NoOrderPresent") 111 | } 112 | 113 | orderEngine := &engineGrpc.Order{} 114 | 115 | orderEngine.ID = order.ID 116 | orderEngine.Amount = order.Amount.String() 117 | orderEngine.Price = order.Price.String() 118 | orderEngine.Type = engineGrpc.Side(engineGrpc.Side_value[order.Type.String()]) 119 | 120 | return orderEngine, nil 121 | } 122 | 123 | // ProcessMarket implements EngineServer interface 124 | func (e *Engine) ProcessMarket(ctx context.Context, req *engineGrpc.Order) (*engineGrpc.OutputOrders, error) { 125 | bigZero, _ := util.NewDecimalFromString("0.0") 126 | orderString := fmt.Sprintf("{\"id\":\"%s\", \"type\": \"%s\", \"amount\": \"%s\", \"price\": \"%s\" }", req.GetID(), req.GetType(), req.GetAmount(), req.GetPrice()) 127 | 128 | var order engine.Order 129 | // decode the message 130 | // fmt.Println("Orderstring =: ", orderString) 131 | err := order.FromJSON([]byte(orderString)) 132 | if err != nil { 133 | fmt.Println("JSON Parse Error =: ", err) 134 | return nil, err 135 | } 136 | 137 | if order.Amount.Cmp(bigZero) == 0 { 138 | fmt.Println("Invalid JSON") 139 | return nil, errors.New("Invalid JSON") 140 | } 141 | 142 | if req.GetPair() == "" { 143 | fmt.Println("Invalid pair") 144 | return nil, errors.New("Invalid pair") 145 | } 146 | 147 | var pairBook *engine.OrderBook 148 | if val, ok := e.book[req.GetPair()]; ok { 149 | pairBook = val 150 | } else { 151 | pairBook = engine.NewOrderBook() 152 | e.book[req.GetPair()] = pairBook 153 | } 154 | 155 | ordersProcessed, partialOrder := pairBook.ProcessMarket(order) 156 | 157 | ordersProcessedString, err := json.Marshal(ordersProcessed) 158 | 159 | // if order.Type.String() == "sell" { 160 | fmt.Println("pair:", req.GetPair()) 161 | fmt.Println(pairBook) 162 | // } 163 | 164 | if err != nil { 165 | return nil, err 166 | } 167 | 168 | if partialOrder != nil { 169 | var partialOrderString []byte 170 | partialOrderString, err = json.Marshal(partialOrder) 171 | return &engineGrpc.OutputOrders{OrdersProcessed: string(ordersProcessedString), PartialOrder: string(partialOrderString)}, nil 172 | } 173 | return &engineGrpc.OutputOrders{OrdersProcessed: string(ordersProcessedString), PartialOrder: "null"}, nil 174 | } 175 | 176 | // FetchBook implements EngineServer interface 177 | func (e *Engine) FetchBook(ctx context.Context, req *engineGrpc.BookInput) (*engineGrpc.BookOutput, error) { 178 | if req.GetPair() == "" { 179 | fmt.Println("Invalid pair") 180 | return nil, errors.New("Invalid pair") 181 | } 182 | 183 | var pairBook *engine.OrderBook 184 | if val, ok := e.book[req.GetPair()]; ok { 185 | pairBook = val 186 | } else { 187 | return nil, errors.New("Invalid pair") 188 | } 189 | 190 | fmt.Println(pairBook) 191 | book := pairBook.GetOrders(req.GetLimit()) 192 | 193 | result := &engineGrpc.BookOutput{Buys: []*engineGrpc.BookArray{}, Sells: []*engineGrpc.BookArray{}} 194 | 195 | for _, buy := range book.Buys { 196 | arr := &engineGrpc.BookArray{PriceAmount: []string{}} 197 | 198 | bodyBytes, err := json.Marshal(buy) 199 | if err != nil { 200 | fmt.Println("1", err) 201 | return &engineGrpc.BookOutput{Buys: []*engineGrpc.BookArray{}, Sells: []*engineGrpc.BookArray{}}, nil 202 | } 203 | 204 | err = json.Unmarshal(bodyBytes, &arr.PriceAmount) 205 | if err != nil { 206 | fmt.Println("2", err) 207 | return &engineGrpc.BookOutput{Buys: []*engineGrpc.BookArray{}, Sells: []*engineGrpc.BookArray{}}, nil 208 | } 209 | 210 | result.Buys = append(result.Buys, arr) 211 | } 212 | 213 | for _, sell := range book.Sells { 214 | arr := &engineGrpc.BookArray{PriceAmount: []string{}} 215 | 216 | bodyBytes, err := json.Marshal(sell) 217 | if err != nil { 218 | fmt.Println("json.Marshal Error", err) 219 | return &engineGrpc.BookOutput{Buys: []*engineGrpc.BookArray{}, Sells: []*engineGrpc.BookArray{}}, nil 220 | } 221 | 222 | err = json.Unmarshal(bodyBytes, &arr.PriceAmount) 223 | if err != nil { 224 | fmt.Println("json.Unmarshal Error", err) 225 | return &engineGrpc.BookOutput{Buys: []*engineGrpc.BookArray{}, Sells: []*engineGrpc.BookArray{}}, nil 226 | } 227 | 228 | result.Sells = append(result.Sells, arr) 229 | } 230 | return result, nil 231 | } 232 | -------------------------------------------------------------------------------- /util/bigdecimal.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | d "github.com/shopspring/decimal" 5 | ) 6 | 7 | // StandardBigDecimal initializes the standard big decimcal 8 | type StandardBigDecimal struct { 9 | d.Decimal 10 | } 11 | 12 | // NewLogger initializes the standard logger 13 | func NewDecimalFromString(str string) (*StandardBigDecimal, error) { 14 | var err error 15 | s := &StandardBigDecimal{d.Decimal{}} 16 | s.Decimal, err = d.NewFromString(str) 17 | return s, err 18 | } 19 | 20 | // NewLogger initializes the standard logger 21 | func NewDecimalFromFloat(str float64) *StandardBigDecimal { 22 | s := &StandardBigDecimal{d.Decimal{}} 23 | s.Decimal = d.NewFromFloat(str) 24 | return s 25 | } 26 | 27 | func (s *StandardBigDecimal) Add(other *StandardBigDecimal) *StandardBigDecimal { 28 | bigDecimal := s.Decimal.Add(other.Decimal) 29 | return &StandardBigDecimal{bigDecimal} 30 | } 31 | 32 | func (s *StandardBigDecimal) Sub(other *StandardBigDecimal) *StandardBigDecimal { 33 | bigDecimal := s.Decimal.Sub(other.Decimal) 34 | return &StandardBigDecimal{bigDecimal} 35 | } 36 | 37 | func (s *StandardBigDecimal) Mul(other *StandardBigDecimal) *StandardBigDecimal { 38 | bigDecimal := s.Decimal.Mul(other.Decimal) 39 | return &StandardBigDecimal{bigDecimal} 40 | } 41 | 42 | func (s *StandardBigDecimal) Div(other *StandardBigDecimal) *StandardBigDecimal { 43 | bigDecimal := s.Decimal.Div(other.Decimal) 44 | return &StandardBigDecimal{bigDecimal} 45 | } 46 | 47 | func (s *StandardBigDecimal) Cmp(other *StandardBigDecimal) int { 48 | return s.Decimal.Cmp(other.Decimal) 49 | } 50 | 51 | func (s *StandardBigDecimal) Neg() *StandardBigDecimal { 52 | bigDecimal := s.Decimal.Neg() 53 | return &StandardBigDecimal{bigDecimal} 54 | } 55 | 56 | func (s *StandardBigDecimal) String() string { 57 | return s.Decimal.String() 58 | } 59 | 60 | func (s *StandardBigDecimal) Float64() float64 { 61 | floatValue, _ := s.Decimal.Float64() 62 | return floatValue 63 | } 64 | --------------------------------------------------------------------------------