├── .vscode └── launch.json ├── README.md ├── cmd ├── root.go └── start.go ├── common ├── constant.go └── signal.go ├── dump ├── BTC-USDT │ ├── asks.json │ └── bids.json ├── ETH-USDT │ ├── asks.json │ └── bids.json └── pairs.json ├── engine ├── engine.go └── stored.go ├── go.mod ├── go.sum ├── grpc ├── implement.go ├── middleware.go ├── order.pb.go ├── order.proto └── server.go ├── main.go ├── models ├── deep.go ├── order.go ├── sort.go └── trade.go ├── orders_test.json └── pool ├── cmp.go ├── match.go ├── pool.go ├── pools_test.go └── snapshot.go /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "main 入口调试", 6 | "type": "go", 7 | "request": "launch", 8 | "mode": "auto", 9 | "program": "main.go", 10 | "args": ["start","grpc","BTC-USDT"] 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # transaction-matching-engine 2 | 基于内存的高性能数字货币交易所撮合引擎核心 [ 异步挂单, 盘口深度快照, 最新成交价推送 ] 3 | 1. 使用grpc作为服务端框架,传输更加高效。 4 | 2. 数据结构方面,实现了自定义跳表,数据操作也更加高效。 5 | 3. 系统启动和终止都将与持久化数据交互,存储或者读取,保证了系统安全启动与退出。 6 | 7 | - 挂单:采用了异步通知,引擎只返回当前挂单成功或者失败。其实也可以将FOK,IOC类型订单做成类似深度一样的同步通知,但是效率会降低,调用侧平均等待时间会加长。【使用者可根据自己需要自行修改,示例如盘口深度查询】 8 | - 盘口深度:查询采用了同步通知,同步开销比较大,虽然加了盘口深度快照,但涉及到快照更新,由于单跳表只能单线程操作,所以会影响到挂单效率。 9 | - 成交推送:为了降低服务耦合,提高撮合引擎性能,使用者须将成交接入消息中间件。以订阅成交消息推送。【使用者也可根据自己需要自定义增加 grpc stream流接口,实时推送成交至业务端,与websocket类似】 10 | 11 | 12 | ### 挂单 13 | 14 | 订单类型|描述|限价单|市价单(以对手价成交) 15 | -|-|-|- 16 | IOC|无法立即成交的部分就撤销,订单在失效前会尽量多的成交|支持|支持 17 | FOK|无法全部立即成交就撤销,如果无法全部成交,订单会失效|支持|支持 18 | GTC|订单会一直有效,直到被成交或者取消|支持|支持 19 | 20 | ### 盘口 21 | 22 | 档位数据: [][3]string{用户id,价格,数量} 23 | 24 | ### 使用 25 | 26 | - start grpc 后跟交易对参数 27 | ``` 28 | go run main.go start grpc BTC-USDT,ETH-USDT 29 | ``` 30 | 31 | - dump 文件内配置交易对,和执行程序同目录下,dump文件内,pairs.json文件内配置交易对`[BTC-USDT,ETH-USDT]`,然后直接启动 32 | 33 | ``` 34 | go run main.go start grpc 35 | ``` 36 | 37 | ### 项目结构 38 | 39 | ``` 40 | ├─cmd 启动命令相关 41 | ├─common 安全退出与系统运行时参数 42 | ├─dump 持久化数据文件 43 | │ │ ├─pairs.json 快捷启动配置 44 | │ ├─BTC-USDT 具体交易对持久化示例1 45 | │ │ ├─asks.json 卖盘数据 46 | │ │ ├─bids.json 买盘数据 47 | │ └─ETH-USDT 具体交易对持久化示例2 48 | ├─engine 撮合引擎与持久化 49 | ├─grpc grpc对外服务实现 50 | ├─models 数据模型 51 | └─pool 撮合池 52 | ``` 53 | 54 | ### orders_test.json 订单类型测试数据 55 | 56 | - 相同的 id 表示为并行测试数据 如 `[1,2,3(1),3(2)]` 表示测试顺序有2条,分别为 `1,2,3(1)` 和 `1,2,3(2)` 57 | 58 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | var rootCmd = &cobra.Command{ 10 | Use: "transaction-matching-engine", 11 | Short: "虚拟货币交易撮合引擎", 12 | Run: func(cmd *cobra.Command, args []string) { 13 | cmd.Help() 14 | os.Exit(0) 15 | }, 16 | } 17 | 18 | func Execute() { 19 | if err := rootCmd.Execute(); err != nil { 20 | rootCmd.Help() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /cmd/start.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | "transaction-matching-engine/engine" 8 | "transaction-matching-engine/grpc" 9 | 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | func init() { 14 | rootCmd.AddCommand(start) 15 | start.AddCommand(startGrpcCmd) 16 | } 17 | 18 | var ( 19 | start = &cobra.Command{ 20 | Use: "start", 21 | Short: "启动服务", 22 | Run: func(cmd *cobra.Command, args []string) { 23 | cmd.Help() 24 | os.Exit(0) 25 | }, 26 | } 27 | 28 | startGrpcCmd = &cobra.Command{ 29 | Use: "grpc", 30 | Short: "启动grpc服务", 31 | PreRun: preServerRun, 32 | Run: func(cmd *cobra.Command, args []string) { 33 | var pairs []string 34 | if len(args) > 0 { 35 | pairs = strings.Split(strings.ToUpper(args[0]), ",") 36 | } else { 37 | pairs = engine.ReadPairs() 38 | if len(pairs) == 0 { 39 | panic("转储的交易对文件不存在或无交易对") 40 | } 41 | } 42 | engine.Load(pairs) 43 | grpc.Run(pairs) 44 | }, 45 | } 46 | ) 47 | 48 | // 服务启动前参数检查,数据载入 49 | func preServerRun(cmd *cobra.Command, args []string) { 50 | if len(args) == 0 { 51 | fmt.Println("未在启动参数指定交易对,例: [start x服务 BTC-USDT] \r\n交易对将从配置文件加载...") 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /common/constant.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "errors" 5 | "sync/atomic" 6 | ) 7 | 8 | var ( 9 | orderAtomicSource int32 = 0 //订单原子自增量 10 | ServerCancelErr error = errors.New("server done") //系统正在关闭 11 | OrderHandleTimeoutErr error = errors.New("order handle timeout") //订单处理超时 12 | NotOpenMatchPoolErr error = errors.New("this pair not open the match pool") //未配置交易撮合池 13 | ) 14 | 15 | const ( 16 | int32MaxValueRotation int32 = 1 << 20 //自增归零量 //1048576 fmt.Printf("%07d", 10) 补齐 17 | // 订单类型基础 18 | TypeOrderCancel string = "cancel" 19 | SideOrderBuy string = "buy" 20 | SideOrderSell string = "sell" 21 | TypeOrderLimit string = "limit" 22 | TypeOrderMarket string = "market" 23 | TimeInForceGTC string = "GTC" 24 | TimeInForceIOC string = "IOC" 25 | TimeInForceFOK string = "FOK" 26 | ) 27 | 28 | //获取原子自增值 29 | func GetAtomicIncrSeq() int { 30 | atomic.CompareAndSwapInt32(&orderAtomicSource, int32MaxValueRotation, 0) 31 | return int(atomic.AddInt32(&orderAtomicSource, 1)) 32 | } 33 | -------------------------------------------------------------------------------- /common/signal.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "os/signal" 8 | "sync" 9 | "syscall" 10 | ) 11 | 12 | // 系统安全退出 13 | var ServerStatus *serverStatus 14 | 15 | func init() { 16 | ctx, cancelFunc := context.WithCancel(context.Background()) 17 | ServerStatus = &serverStatus{ 18 | ctx: ctx, 19 | cancelFunc: cancelFunc, 20 | exitSignal: make(chan os.Signal, 1), 21 | wg: &sync.WaitGroup{}, 22 | } 23 | signal.Notify(ServerStatus.exitSignal, syscall.SIGINT, syscall.SIGKILL, syscall.SIGTERM) 24 | go ServerStatus.waitSignal() 25 | } 26 | 27 | // 系统状态 28 | type serverStatus struct { 29 | ctx context.Context 30 | cancelFunc context.CancelFunc 31 | exitSignal chan os.Signal 32 | wg *sync.WaitGroup 33 | } 34 | 35 | // 等待操作系统信号,并发出退出信号 36 | func (ss *serverStatus) waitSignal() { 37 | ss.wg.Add(1) 38 | defer ss.wg.Done() 39 | <-ss.exitSignal 40 | fmt.Println("系统收到退出信号...") 41 | ss.cancelFunc() 42 | } 43 | 44 | // 获取系统全局context 45 | func (ss *serverStatus) Context() context.Context { 46 | return ss.ctx 47 | } 48 | 49 | func (ss *serverStatus) Add(delta int) { 50 | ss.wg.Add(delta) 51 | } 52 | 53 | func (ss *serverStatus) Done() { 54 | ss.wg.Done() 55 | } 56 | 57 | // 等待安全退出 58 | func (ss *serverStatus) Wait() { 59 | ss.wg.Wait() 60 | fmt.Println("系统已退出.") 61 | } 62 | -------------------------------------------------------------------------------- /dump/BTC-USDT/asks.json: -------------------------------------------------------------------------------- 1 | [{"id":"2","user_id":"1","pair":"BTC-USDT","price":"1100","amount":"8","type":"limit","side":"sell","time_in_force":"GTC","time_unix_milli":1}] -------------------------------------------------------------------------------- /dump/BTC-USDT/bids.json: -------------------------------------------------------------------------------- 1 | [{"id":"1","user_id":"1","pair":"BTC-USDT","price":"1000","amount":"10","type":"limit","side":"buy","time_in_force":"GTC","time_unix_milli":1}] -------------------------------------------------------------------------------- /dump/ETH-USDT/asks.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /dump/ETH-USDT/bids.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /dump/pairs.json: -------------------------------------------------------------------------------- 1 | ["ETH-USDT","BTC-USDT"] -------------------------------------------------------------------------------- /engine/engine.go: -------------------------------------------------------------------------------- 1 | package engine 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strings" 7 | "sync" 8 | "transaction-matching-engine/common" 9 | "transaction-matching-engine/models" 10 | "transaction-matching-engine/pool" 11 | ) 12 | 13 | var ( 14 | once = sync.Once{} 15 | matchEngine *MatchEngine 16 | ) 17 | 18 | // 撮合引擎 19 | type MatchEngine struct { 20 | pools map[string]*pool.MatchPool //每个交易对一个撮合池 21 | } 22 | 23 | func GetMatchEngine(pairs []string) *MatchEngine { 24 | once.Do(func() { 25 | matchEngine = &MatchEngine{ 26 | pools: make(map[string]*pool.MatchPool), 27 | } 28 | for _, pair := range pairs { 29 | if _, ok := matchEngine.pools[pair]; !ok { 30 | matchEngine.pools[pair] = pool.NewMatchPool() 31 | } 32 | } 33 | matchEngine.printPairs() 34 | matchEngine.subscribeTrade() 35 | }) 36 | return matchEngine 37 | } 38 | 39 | // 输出所有的交易对 40 | func (me *MatchEngine) printPairs() { 41 | pairs := make([]string, 0, len(me.pools)) 42 | for pair := range me.pools { 43 | pairs = append(pairs, pair) 44 | } 45 | fmt.Printf("撮合池内支持的交易对: [%s]\n", strings.Join(pairs, ",")) 46 | } 47 | 48 | // 订阅成交 //推送至消息队列供业务测消费&生成k线 --- 49 | func (me *MatchEngine) subscribeTrade() { 50 | for pair, mp := range me.pools { 51 | go func(pair string, mp *pool.MatchPool) { 52 | for trade := range mp.Output() { 53 | fmt.Printf("新的成交!交易对:%s\r\n详细信息:%+v\n", pair, trade) 54 | /* 55 | TODO:接入业务系统消息推送 56 | */ 57 | 58 | common.ServerStatus.Done() 59 | } 60 | }(pair, mp) 61 | } 62 | } 63 | 64 | // 挂单 65 | func (me *MatchEngine) AddOrder(ctx context.Context, order *models.Order) error { 66 | if pool, ok := me.pools[order.Pair]; ok { 67 | return pool.Input(ctx, order) 68 | } 69 | fmt.Println("[挂单]异常订单,id: ", order.Id, " 交易对: ", order.Pair) 70 | return common.NotOpenMatchPoolErr 71 | } 72 | 73 | // 撤单 74 | func (me *MatchEngine) CancelOrder(ctx context.Context, order *models.Order) error { 75 | if pool, ok := me.pools[order.Pair]; ok { 76 | return pool.Input(ctx, order) 77 | } 78 | fmt.Println("[撤单]异常订单,id: ", order.Id, " 交易对: ", order.Pair) 79 | return common.NotOpenMatchPoolErr 80 | } 81 | 82 | // 查询深度 83 | func (me *MatchEngine) QueryDeep(pair string) ([][3]string, [][3]string, error) { 84 | if pool, ok := me.pools[pair]; ok { 85 | bids, asks := pool.QueryDeep(pair) 86 | return bids, asks, nil 87 | } 88 | fmt.Println("[查询深度]异常交易对: ", pair) 89 | return nil, nil, common.NotOpenMatchPoolErr 90 | } 91 | -------------------------------------------------------------------------------- /engine/stored.go: -------------------------------------------------------------------------------- 1 | package engine 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/fs" 7 | "os" 8 | "strings" 9 | "transaction-matching-engine/models" 10 | ) 11 | 12 | var ( 13 | filePathPrefix = "./dump/" 14 | bidsName = "/bids.json" 15 | asksName = "/asks.json" 16 | pairName = "pairs.json" 17 | ) 18 | 19 | //序列化 20 | func serialize(data interface{}) []byte { 21 | bts, _ := json.Marshal(data) 22 | return bts 23 | } 24 | 25 | //反序列化 26 | func deserialize(bts []byte) []*models.Order { 27 | data := []*models.Order{} 28 | json.Unmarshal(bts, &data) 29 | return data 30 | } 31 | 32 | //存储 文件存在就删除了重新创建 33 | func Dump() { 34 | eg := GetMatchEngine(nil) 35 | os.MkdirAll(filePathPrefix, fs.ModeDir) 36 | pairs := []string{} //记录本次启动的pairs 37 | for pair, pool := range eg.pools { 38 | pairs = append(pairs, pair) 39 | bids, asks := pool.GetOrders() 40 | os.MkdirAll(filePathPrefix+pair, fs.ModeDir) 41 | save(filePathPrefix+pair+bidsName, bids) 42 | save(filePathPrefix+pair+asksName, asks) 43 | } 44 | save(filePathPrefix+pairName, pairs) 45 | } 46 | 47 | //存储 48 | func save(filePath string, data interface{}) { 49 | os.Remove(filePath) 50 | file, err := os.Create(filePath) 51 | if err != nil { 52 | panic("创建文件失败" + err.Error()) 53 | } 54 | file.Write(serialize(data)) 55 | } 56 | 57 | //加载 只加载pairs内的文件 58 | func Load(pairs []string) { 59 | files, err := os.ReadDir(filePathPrefix) 60 | if err != nil { 61 | if os.IsNotExist(err) { 62 | return 63 | } 64 | panic(filePathPrefix + "内的dump文件读取异常:" + err.Error()) 65 | } 66 | if len(files) == 0 { 67 | return 68 | } 69 | eg := GetMatchEngine(pairs) 70 | needPair := map[string]int{} 71 | for k := range pairs { 72 | needPair[pairs[k]]++ 73 | } 74 | for _, info := range files { 75 | if info.IsDir() { 76 | needPair[info.Name()]++ 77 | } 78 | } 79 | //包含 启动pair && 文件存在 的才载入 80 | for pair, count := range needPair { 81 | if count == 2 { 82 | eg.pools[pair].SetOrders(readOrders(filePathPrefix+pair+bidsName), readOrders(filePathPrefix+pair+asksName)) 83 | fmt.Println("成功加载文件:", pair) 84 | } 85 | } 86 | } 87 | 88 | //读取存储的订单数据 89 | func readOrders(filePath string) []*models.Order { 90 | bts, err := os.ReadFile(filePath) 91 | if err != nil { 92 | panic(filePath + "读取文件失败" + err.Error()) 93 | } 94 | data := []*models.Order{} 95 | err = json.Unmarshal(bts, &data) 96 | if err != nil { 97 | panic(filePath + "解析文件内容失败" + filePath) 98 | } 99 | return data 100 | } 101 | 102 | //记录上次启动的pair 103 | func ReadPairs() []string { 104 | filePath := filePathPrefix + pairName 105 | data := []string{} 106 | bts, err := os.ReadFile(filePath) 107 | if err != nil { 108 | fmt.Println(filePath + "读取文件失败" + err.Error()) 109 | return data 110 | } 111 | err = json.Unmarshal(bts, &data) 112 | if err != nil { 113 | panic(filePath + "解析文件内容失败" + filePath) 114 | } 115 | for k := range data { 116 | data[k] = strings.ToUpper(data[k]) 117 | } 118 | return data 119 | } 120 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module transaction-matching-engine 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 7 | github.com/shopspring/decimal v1.3.1 8 | github.com/spf13/cobra v1.5.0 9 | github.com/yantao1995/ds v1.1.1 10 | google.golang.org/grpc v1.53.0 11 | google.golang.org/protobuf v1.28.1 12 | ) 13 | 14 | require ( 15 | github.com/golang/protobuf v1.5.2 // indirect 16 | github.com/inconshreveable/mousetrap v1.0.0 // indirect 17 | github.com/spf13/pflag v1.0.5 // indirect 18 | golang.org/x/net v0.7.0 // indirect 19 | golang.org/x/sys v0.5.0 // indirect 20 | golang.org/x/text v0.7.0 // indirect 21 | google.golang.org/genproto v0.0.0-20230301171018-9ab4bdc49ad5 // indirect 22 | ) 23 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 4 | github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= 5 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 6 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 7 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 8 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 9 | github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 10 | github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= 11 | github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 12 | github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 13 | github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 14 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 15 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 16 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 17 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 18 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 19 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 20 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 21 | github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= 22 | github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= 23 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 24 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 25 | github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 26 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 27 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 28 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 29 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 30 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 31 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 32 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 33 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 34 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 35 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 36 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 37 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 38 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 39 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 40 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 41 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 42 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 43 | github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= 44 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 45 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 46 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 47 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 48 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 49 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 50 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 51 | github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= 52 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 53 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 54 | github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= 55 | github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= 56 | github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= 57 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= 58 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 59 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 60 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 61 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 62 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 63 | github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= 64 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 65 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 66 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 67 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 68 | github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= 69 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 70 | github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= 71 | github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= 72 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 73 | github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= 74 | github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= 75 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 76 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 77 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 78 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 79 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 80 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= 81 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 82 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 83 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 84 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 85 | github.com/yantao1995/ds v1.1.1 h1:dVwHgUPbhEl/fHrvekg9GkEOj3YRBef0sfhZ6/C/JCQ= 86 | github.com/yantao1995/ds v1.1.1/go.mod h1:Yel+hmq4pbk0VW7MTqXOmD20Jjm2X9tDNJQMKxjZunM= 87 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 88 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 89 | go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= 90 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 91 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 92 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 93 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 94 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 95 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 96 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 97 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 98 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 99 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 100 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 101 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 102 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 103 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 104 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 105 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 106 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 107 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 108 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 109 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 110 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 111 | golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI= 112 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 113 | golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= 114 | golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 115 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 116 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 117 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 118 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 119 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 120 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 121 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 122 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 123 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 124 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 125 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 126 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 127 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 128 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 129 | golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 h1:myAQVi0cGEoqQVR5POX+8RR2mrocKqNN1hmeMqhX27k= 130 | golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 131 | golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= 132 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 133 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 134 | golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= 135 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 136 | golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= 137 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 138 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 139 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 140 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 141 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 142 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 143 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 144 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 145 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 146 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 147 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 148 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 149 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 150 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 151 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 152 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 153 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 154 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 155 | google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 156 | google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 157 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= 158 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 159 | google.golang.org/genproto v0.0.0-20230301171018-9ab4bdc49ad5 h1:/cadn7taPtPlCgiWNetEPsle7jgnlad2R7gR5MXB6dM= 160 | google.golang.org/genproto v0.0.0-20230301171018-9ab4bdc49ad5/go.mod h1:TvhZT5f700eVlTNwND1xoEZQeWTB2RY/65kplwl/bFA= 161 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 162 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 163 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 164 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 165 | google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= 166 | google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= 167 | google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= 168 | google.golang.org/grpc v1.48.0 h1:rQOsyJ/8+ufEDJd/Gdsz7HG220Mh9HAhFHRGnIjda0w= 169 | google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= 170 | google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc= 171 | google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= 172 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 173 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 174 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 175 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 176 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 177 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 178 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 179 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 180 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 181 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 182 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 183 | google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 184 | google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= 185 | google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 186 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 187 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 188 | gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 189 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 190 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 191 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 192 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 193 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 194 | -------------------------------------------------------------------------------- /grpc/implement.go: -------------------------------------------------------------------------------- 1 | package grpc 2 | 3 | import ( 4 | context "context" 5 | "encoding/json" 6 | "strings" 7 | "time" 8 | "transaction-matching-engine/common" 9 | "transaction-matching-engine/engine" 10 | "transaction-matching-engine/models" 11 | ) 12 | 13 | // 交易对不区分大小写 14 | type implementedMatchServiceServer struct { 15 | me *engine.MatchEngine 16 | timeout time.Duration 17 | } 18 | 19 | // 参数由业务侧校验 20 | func NewImplementedMatchServiceServer(pairs []string) *implementedMatchServiceServer { 21 | return &implementedMatchServiceServer{ 22 | me: engine.GetMatchEngine(pairs), 23 | timeout: time.Second, 24 | } 25 | } 26 | 27 | func (im *implementedMatchServiceServer) handleErr(err error) *CommonResponse { 28 | resp := &CommonResponse{} 29 | if err != nil { 30 | switch err { 31 | case common.ServerCancelErr, common.OrderHandleTimeoutErr: 32 | resp.Code = 500 33 | default: 34 | resp.Code = 400 35 | } 36 | resp.Msg = err.Error() 37 | } 38 | return resp 39 | } 40 | 41 | func (im *implementedMatchServiceServer) AddOrder(ctx context.Context, req *AddOrderRequest) (*CommonResponse, error) { 42 | order := &models.Order{ 43 | Id: req.GetId(), 44 | UserId: req.GetUserId(), 45 | Pair: strings.ToUpper(req.GetPair()), 46 | Price: req.GetPrice(), 47 | Amount: req.GetAmount(), 48 | Type: req.GetType(), 49 | Side: req.GetSide(), 50 | TimeInForce: req.GetTimeInForce(), 51 | TimeUnixMilli: req.GetTimeUnixMilli(), 52 | } 53 | ctxTimeout, cancel := context.WithTimeout(ctx, im.timeout) 54 | defer cancel() 55 | return im.handleErr(im.me.AddOrder(ctxTimeout, order)), nil 56 | } 57 | 58 | func (im *implementedMatchServiceServer) CancelOrder(ctx context.Context, req *CancelOrderRequest) (*CommonResponse, error) { 59 | order := &models.Order{ 60 | Id: req.GetId(), 61 | Pair: strings.ToUpper(req.GetPair()), 62 | } 63 | ctxTimeout, cancel := context.WithTimeout(ctx, im.timeout) 64 | defer cancel() 65 | return im.handleErr(im.me.CancelOrder(ctxTimeout, order)), nil 66 | } 67 | 68 | func (im *implementedMatchServiceServer) QueryDeep(ctx context.Context, req *QueryDeepRequest) (*CommonResponse, error) { 69 | req.Pair = strings.ToUpper(req.GetPair()) 70 | bids, asks, err := im.me.QueryDeep(req.GetPair()) 71 | resp := im.handleErr(err) 72 | if err == nil { 73 | data := models.Deep{ 74 | Pair: req.GetPair(), 75 | TimeUnixMilli: time.Now().UnixMilli(), 76 | Bids: bids, 77 | Asks: asks, 78 | } 79 | resp.Data, _ = json.Marshal(data) 80 | } 81 | return resp, nil 82 | } 83 | -------------------------------------------------------------------------------- /grpc/middleware.go: -------------------------------------------------------------------------------- 1 | package grpc 2 | 3 | import ( 4 | context "context" 5 | 6 | grpc "google.golang.org/grpc" 7 | codes "google.golang.org/grpc/codes" 8 | status "google.golang.org/grpc/status" 9 | ) 10 | 11 | func loggerInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { 12 | 13 | resp, err := handler(ctx, req) 14 | // log.InfoWithFields( 15 | // "Request_Response", 16 | // log.Fields{ 17 | // "method": info.FullMethod, 18 | // "req": common.PrintJson(req), 19 | // "resp": common.PrintJson(resp), 20 | // }, 21 | // ) 22 | return resp, err 23 | } 24 | 25 | func recoverInterceptor(p interface{}) (err error) { 26 | // log.ErrorWithFields( 27 | // "recover", 28 | // log.Fields{ 29 | // "err": p, 30 | // }, 31 | // ) 32 | return status.Errorf(codes.Unknown, "panic triggered: %v", p) 33 | } 34 | -------------------------------------------------------------------------------- /grpc/order.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.26.0 4 | // protoc v3.21.1 5 | // source: grpc/order.proto 6 | 7 | package grpc 8 | 9 | import ( 10 | context "context" 11 | grpc "google.golang.org/grpc" 12 | codes "google.golang.org/grpc/codes" 13 | status "google.golang.org/grpc/status" 14 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 15 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 16 | reflect "reflect" 17 | sync "sync" 18 | ) 19 | 20 | const ( 21 | // Verify that this generated code is sufficiently up-to-date. 22 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 23 | // Verify that runtime/protoimpl is sufficiently up-to-date. 24 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 25 | ) 26 | 27 | type AddOrderRequest struct { 28 | state protoimpl.MessageState 29 | sizeCache protoimpl.SizeCache 30 | unknownFields protoimpl.UnknownFields 31 | 32 | Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` 33 | UserId string `protobuf:"bytes,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` 34 | Pair string `protobuf:"bytes,3,opt,name=pair,proto3" json:"pair,omitempty"` 35 | Price string `protobuf:"bytes,4,opt,name=price,proto3" json:"price,omitempty"` 36 | Amount string `protobuf:"bytes,5,opt,name=amount,proto3" json:"amount,omitempty"` 37 | Type string `protobuf:"bytes,6,opt,name=type,proto3" json:"type,omitempty"` 38 | Side string `protobuf:"bytes,7,opt,name=side,proto3" json:"side,omitempty"` 39 | TimeInForce string `protobuf:"bytes,8,opt,name=time_in_force,json=timeInForce,proto3" json:"time_in_force,omitempty"` 40 | TimeUnixMilli int64 `protobuf:"varint,9,opt,name=time_unix_milli,json=timeUnixMilli,proto3" json:"time_unix_milli,omitempty"` 41 | } 42 | 43 | func (x *AddOrderRequest) Reset() { 44 | *x = AddOrderRequest{} 45 | if protoimpl.UnsafeEnabled { 46 | mi := &file_grpc_order_proto_msgTypes[0] 47 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 48 | ms.StoreMessageInfo(mi) 49 | } 50 | } 51 | 52 | func (x *AddOrderRequest) String() string { 53 | return protoimpl.X.MessageStringOf(x) 54 | } 55 | 56 | func (*AddOrderRequest) ProtoMessage() {} 57 | 58 | func (x *AddOrderRequest) ProtoReflect() protoreflect.Message { 59 | mi := &file_grpc_order_proto_msgTypes[0] 60 | if protoimpl.UnsafeEnabled && x != nil { 61 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 62 | if ms.LoadMessageInfo() == nil { 63 | ms.StoreMessageInfo(mi) 64 | } 65 | return ms 66 | } 67 | return mi.MessageOf(x) 68 | } 69 | 70 | // Deprecated: Use AddOrderRequest.ProtoReflect.Descriptor instead. 71 | func (*AddOrderRequest) Descriptor() ([]byte, []int) { 72 | return file_grpc_order_proto_rawDescGZIP(), []int{0} 73 | } 74 | 75 | func (x *AddOrderRequest) GetId() string { 76 | if x != nil { 77 | return x.Id 78 | } 79 | return "" 80 | } 81 | 82 | func (x *AddOrderRequest) GetUserId() string { 83 | if x != nil { 84 | return x.UserId 85 | } 86 | return "" 87 | } 88 | 89 | func (x *AddOrderRequest) GetPair() string { 90 | if x != nil { 91 | return x.Pair 92 | } 93 | return "" 94 | } 95 | 96 | func (x *AddOrderRequest) GetPrice() string { 97 | if x != nil { 98 | return x.Price 99 | } 100 | return "" 101 | } 102 | 103 | func (x *AddOrderRequest) GetAmount() string { 104 | if x != nil { 105 | return x.Amount 106 | } 107 | return "" 108 | } 109 | 110 | func (x *AddOrderRequest) GetType() string { 111 | if x != nil { 112 | return x.Type 113 | } 114 | return "" 115 | } 116 | 117 | func (x *AddOrderRequest) GetSide() string { 118 | if x != nil { 119 | return x.Side 120 | } 121 | return "" 122 | } 123 | 124 | func (x *AddOrderRequest) GetTimeInForce() string { 125 | if x != nil { 126 | return x.TimeInForce 127 | } 128 | return "" 129 | } 130 | 131 | func (x *AddOrderRequest) GetTimeUnixMilli() int64 { 132 | if x != nil { 133 | return x.TimeUnixMilli 134 | } 135 | return 0 136 | } 137 | 138 | type CancelOrderRequest struct { 139 | state protoimpl.MessageState 140 | sizeCache protoimpl.SizeCache 141 | unknownFields protoimpl.UnknownFields 142 | 143 | Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` 144 | Pair string `protobuf:"bytes,2,opt,name=pair,proto3" json:"pair,omitempty"` 145 | } 146 | 147 | func (x *CancelOrderRequest) Reset() { 148 | *x = CancelOrderRequest{} 149 | if protoimpl.UnsafeEnabled { 150 | mi := &file_grpc_order_proto_msgTypes[1] 151 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 152 | ms.StoreMessageInfo(mi) 153 | } 154 | } 155 | 156 | func (x *CancelOrderRequest) String() string { 157 | return protoimpl.X.MessageStringOf(x) 158 | } 159 | 160 | func (*CancelOrderRequest) ProtoMessage() {} 161 | 162 | func (x *CancelOrderRequest) ProtoReflect() protoreflect.Message { 163 | mi := &file_grpc_order_proto_msgTypes[1] 164 | if protoimpl.UnsafeEnabled && x != nil { 165 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 166 | if ms.LoadMessageInfo() == nil { 167 | ms.StoreMessageInfo(mi) 168 | } 169 | return ms 170 | } 171 | return mi.MessageOf(x) 172 | } 173 | 174 | // Deprecated: Use CancelOrderRequest.ProtoReflect.Descriptor instead. 175 | func (*CancelOrderRequest) Descriptor() ([]byte, []int) { 176 | return file_grpc_order_proto_rawDescGZIP(), []int{1} 177 | } 178 | 179 | func (x *CancelOrderRequest) GetId() string { 180 | if x != nil { 181 | return x.Id 182 | } 183 | return "" 184 | } 185 | 186 | func (x *CancelOrderRequest) GetPair() string { 187 | if x != nil { 188 | return x.Pair 189 | } 190 | return "" 191 | } 192 | 193 | type CommonResponse struct { 194 | state protoimpl.MessageState 195 | sizeCache protoimpl.SizeCache 196 | unknownFields protoimpl.UnknownFields 197 | 198 | Code int32 `protobuf:"varint,1,opt,name=code,proto3" json:"code,omitempty"` 199 | Msg string `protobuf:"bytes,2,opt,name=msg,proto3" json:"msg,omitempty"` 200 | Data []byte `protobuf:"bytes,3,opt,name=data,proto3" json:"data,omitempty"` 201 | } 202 | 203 | func (x *CommonResponse) Reset() { 204 | *x = CommonResponse{} 205 | if protoimpl.UnsafeEnabled { 206 | mi := &file_grpc_order_proto_msgTypes[2] 207 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 208 | ms.StoreMessageInfo(mi) 209 | } 210 | } 211 | 212 | func (x *CommonResponse) String() string { 213 | return protoimpl.X.MessageStringOf(x) 214 | } 215 | 216 | func (*CommonResponse) ProtoMessage() {} 217 | 218 | func (x *CommonResponse) ProtoReflect() protoreflect.Message { 219 | mi := &file_grpc_order_proto_msgTypes[2] 220 | if protoimpl.UnsafeEnabled && x != nil { 221 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 222 | if ms.LoadMessageInfo() == nil { 223 | ms.StoreMessageInfo(mi) 224 | } 225 | return ms 226 | } 227 | return mi.MessageOf(x) 228 | } 229 | 230 | // Deprecated: Use CommonResponse.ProtoReflect.Descriptor instead. 231 | func (*CommonResponse) Descriptor() ([]byte, []int) { 232 | return file_grpc_order_proto_rawDescGZIP(), []int{2} 233 | } 234 | 235 | func (x *CommonResponse) GetCode() int32 { 236 | if x != nil { 237 | return x.Code 238 | } 239 | return 0 240 | } 241 | 242 | func (x *CommonResponse) GetMsg() string { 243 | if x != nil { 244 | return x.Msg 245 | } 246 | return "" 247 | } 248 | 249 | func (x *CommonResponse) GetData() []byte { 250 | if x != nil { 251 | return x.Data 252 | } 253 | return nil 254 | } 255 | 256 | type QueryDeepRequest struct { 257 | state protoimpl.MessageState 258 | sizeCache protoimpl.SizeCache 259 | unknownFields protoimpl.UnknownFields 260 | 261 | Pair string `protobuf:"bytes,1,opt,name=pair,proto3" json:"pair,omitempty"` 262 | } 263 | 264 | func (x *QueryDeepRequest) Reset() { 265 | *x = QueryDeepRequest{} 266 | if protoimpl.UnsafeEnabled { 267 | mi := &file_grpc_order_proto_msgTypes[3] 268 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 269 | ms.StoreMessageInfo(mi) 270 | } 271 | } 272 | 273 | func (x *QueryDeepRequest) String() string { 274 | return protoimpl.X.MessageStringOf(x) 275 | } 276 | 277 | func (*QueryDeepRequest) ProtoMessage() {} 278 | 279 | func (x *QueryDeepRequest) ProtoReflect() protoreflect.Message { 280 | mi := &file_grpc_order_proto_msgTypes[3] 281 | if protoimpl.UnsafeEnabled && x != nil { 282 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 283 | if ms.LoadMessageInfo() == nil { 284 | ms.StoreMessageInfo(mi) 285 | } 286 | return ms 287 | } 288 | return mi.MessageOf(x) 289 | } 290 | 291 | // Deprecated: Use QueryDeepRequest.ProtoReflect.Descriptor instead. 292 | func (*QueryDeepRequest) Descriptor() ([]byte, []int) { 293 | return file_grpc_order_proto_rawDescGZIP(), []int{3} 294 | } 295 | 296 | func (x *QueryDeepRequest) GetPair() string { 297 | if x != nil { 298 | return x.Pair 299 | } 300 | return "" 301 | } 302 | 303 | var File_grpc_order_proto protoreflect.FileDescriptor 304 | 305 | var file_grpc_order_proto_rawDesc = []byte{ 306 | 0x0a, 0x10, 0x67, 0x72, 0x70, 0x63, 0x2f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 307 | 0x74, 0x6f, 0x22, 0xf0, 0x01, 0x0a, 0x0f, 0x41, 0x64, 0x64, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 308 | 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 309 | 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 310 | 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 311 | 0x12, 0x0a, 0x04, 0x70, 0x61, 0x69, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 312 | 0x61, 0x69, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 313 | 0x28, 0x09, 0x52, 0x05, 0x70, 0x72, 0x69, 0x63, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 314 | 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 315 | 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 316 | 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x64, 0x65, 0x18, 0x07, 0x20, 317 | 0x01, 0x28, 0x09, 0x52, 0x04, 0x73, 0x69, 0x64, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x74, 0x69, 0x6d, 318 | 0x65, 0x5f, 0x69, 0x6e, 0x5f, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 319 | 0x52, 0x0b, 0x74, 0x69, 0x6d, 0x65, 0x49, 0x6e, 0x46, 0x6f, 0x72, 0x63, 0x65, 0x12, 0x26, 0x0a, 320 | 0x0f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x75, 0x6e, 0x69, 0x78, 0x5f, 0x6d, 0x69, 0x6c, 0x6c, 0x69, 321 | 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x74, 0x69, 0x6d, 0x65, 0x55, 0x6e, 0x69, 0x78, 322 | 0x4d, 0x69, 0x6c, 0x6c, 0x69, 0x22, 0x38, 0x0a, 0x12, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x4f, 323 | 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 324 | 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x70, 325 | 0x61, 0x69, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x69, 0x72, 0x22, 326 | 0x4a, 0x0a, 0x0e, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 327 | 0x65, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 328 | 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x73, 0x67, 0x18, 0x02, 0x20, 0x01, 329 | 0x28, 0x09, 0x52, 0x03, 0x6d, 0x73, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 330 | 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x26, 0x0a, 0x10, 0x51, 331 | 0x75, 0x65, 0x72, 0x79, 0x44, 0x65, 0x65, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 332 | 0x12, 0x0a, 0x04, 0x70, 0x61, 0x69, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 333 | 0x61, 0x69, 0x72, 0x32, 0xa3, 0x01, 0x0a, 0x0c, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x53, 0x65, 0x72, 334 | 0x76, 0x69, 0x63, 0x65, 0x12, 0x2d, 0x0a, 0x08, 0x41, 0x64, 0x64, 0x4f, 0x72, 0x64, 0x65, 0x72, 335 | 0x12, 0x10, 0x2e, 0x41, 0x64, 0x64, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 336 | 0x73, 0x74, 0x1a, 0x0f, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 337 | 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x0b, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x4f, 0x72, 0x64, 338 | 0x65, 0x72, 0x12, 0x13, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x4f, 0x72, 0x64, 0x65, 0x72, 339 | 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0f, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 340 | 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x09, 0x51, 0x75, 0x65, 0x72, 341 | 0x79, 0x44, 0x65, 0x65, 0x70, 0x12, 0x11, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x44, 0x65, 0x65, 342 | 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0f, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 343 | 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x08, 0x5a, 0x06, 0x2e, 0x2f, 0x67, 344 | 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 345 | } 346 | 347 | var ( 348 | file_grpc_order_proto_rawDescOnce sync.Once 349 | file_grpc_order_proto_rawDescData = file_grpc_order_proto_rawDesc 350 | ) 351 | 352 | func file_grpc_order_proto_rawDescGZIP() []byte { 353 | file_grpc_order_proto_rawDescOnce.Do(func() { 354 | file_grpc_order_proto_rawDescData = protoimpl.X.CompressGZIP(file_grpc_order_proto_rawDescData) 355 | }) 356 | return file_grpc_order_proto_rawDescData 357 | } 358 | 359 | var file_grpc_order_proto_msgTypes = make([]protoimpl.MessageInfo, 4) 360 | var file_grpc_order_proto_goTypes = []interface{}{ 361 | (*AddOrderRequest)(nil), // 0: AddOrderRequest 362 | (*CancelOrderRequest)(nil), // 1: CancelOrderRequest 363 | (*CommonResponse)(nil), // 2: CommonResponse 364 | (*QueryDeepRequest)(nil), // 3: QueryDeepRequest 365 | } 366 | var file_grpc_order_proto_depIdxs = []int32{ 367 | 0, // 0: MatchService.AddOrder:input_type -> AddOrderRequest 368 | 1, // 1: MatchService.CancelOrder:input_type -> CancelOrderRequest 369 | 3, // 2: MatchService.QueryDeep:input_type -> QueryDeepRequest 370 | 2, // 3: MatchService.AddOrder:output_type -> CommonResponse 371 | 2, // 4: MatchService.CancelOrder:output_type -> CommonResponse 372 | 2, // 5: MatchService.QueryDeep:output_type -> CommonResponse 373 | 3, // [3:6] is the sub-list for method output_type 374 | 0, // [0:3] is the sub-list for method input_type 375 | 0, // [0:0] is the sub-list for extension type_name 376 | 0, // [0:0] is the sub-list for extension extendee 377 | 0, // [0:0] is the sub-list for field type_name 378 | } 379 | 380 | func init() { file_grpc_order_proto_init() } 381 | func file_grpc_order_proto_init() { 382 | if File_grpc_order_proto != nil { 383 | return 384 | } 385 | if !protoimpl.UnsafeEnabled { 386 | file_grpc_order_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 387 | switch v := v.(*AddOrderRequest); i { 388 | case 0: 389 | return &v.state 390 | case 1: 391 | return &v.sizeCache 392 | case 2: 393 | return &v.unknownFields 394 | default: 395 | return nil 396 | } 397 | } 398 | file_grpc_order_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { 399 | switch v := v.(*CancelOrderRequest); i { 400 | case 0: 401 | return &v.state 402 | case 1: 403 | return &v.sizeCache 404 | case 2: 405 | return &v.unknownFields 406 | default: 407 | return nil 408 | } 409 | } 410 | file_grpc_order_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { 411 | switch v := v.(*CommonResponse); i { 412 | case 0: 413 | return &v.state 414 | case 1: 415 | return &v.sizeCache 416 | case 2: 417 | return &v.unknownFields 418 | default: 419 | return nil 420 | } 421 | } 422 | file_grpc_order_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { 423 | switch v := v.(*QueryDeepRequest); i { 424 | case 0: 425 | return &v.state 426 | case 1: 427 | return &v.sizeCache 428 | case 2: 429 | return &v.unknownFields 430 | default: 431 | return nil 432 | } 433 | } 434 | } 435 | type x struct{} 436 | out := protoimpl.TypeBuilder{ 437 | File: protoimpl.DescBuilder{ 438 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 439 | RawDescriptor: file_grpc_order_proto_rawDesc, 440 | NumEnums: 0, 441 | NumMessages: 4, 442 | NumExtensions: 0, 443 | NumServices: 1, 444 | }, 445 | GoTypes: file_grpc_order_proto_goTypes, 446 | DependencyIndexes: file_grpc_order_proto_depIdxs, 447 | MessageInfos: file_grpc_order_proto_msgTypes, 448 | }.Build() 449 | File_grpc_order_proto = out.File 450 | file_grpc_order_proto_rawDesc = nil 451 | file_grpc_order_proto_goTypes = nil 452 | file_grpc_order_proto_depIdxs = nil 453 | } 454 | 455 | // Reference imports to suppress errors if they are not otherwise used. 456 | var _ context.Context 457 | var _ grpc.ClientConnInterface 458 | 459 | // This is a compile-time assertion to ensure that this generated file 460 | // is compatible with the grpc package it is being compiled against. 461 | const _ = grpc.SupportPackageIsVersion6 462 | 463 | // MatchServiceClient is the client API for MatchService service. 464 | // 465 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. 466 | type MatchServiceClient interface { 467 | AddOrder(ctx context.Context, in *AddOrderRequest, opts ...grpc.CallOption) (*CommonResponse, error) 468 | CancelOrder(ctx context.Context, in *CancelOrderRequest, opts ...grpc.CallOption) (*CommonResponse, error) 469 | QueryDeep(ctx context.Context, in *QueryDeepRequest, opts ...grpc.CallOption) (*CommonResponse, error) 470 | } 471 | 472 | type matchServiceClient struct { 473 | cc grpc.ClientConnInterface 474 | } 475 | 476 | func NewMatchServiceClient(cc grpc.ClientConnInterface) MatchServiceClient { 477 | return &matchServiceClient{cc} 478 | } 479 | 480 | func (c *matchServiceClient) AddOrder(ctx context.Context, in *AddOrderRequest, opts ...grpc.CallOption) (*CommonResponse, error) { 481 | out := new(CommonResponse) 482 | err := c.cc.Invoke(ctx, "/MatchService/AddOrder", in, out, opts...) 483 | if err != nil { 484 | return nil, err 485 | } 486 | return out, nil 487 | } 488 | 489 | func (c *matchServiceClient) CancelOrder(ctx context.Context, in *CancelOrderRequest, opts ...grpc.CallOption) (*CommonResponse, error) { 490 | out := new(CommonResponse) 491 | err := c.cc.Invoke(ctx, "/MatchService/CancelOrder", in, out, opts...) 492 | if err != nil { 493 | return nil, err 494 | } 495 | return out, nil 496 | } 497 | 498 | func (c *matchServiceClient) QueryDeep(ctx context.Context, in *QueryDeepRequest, opts ...grpc.CallOption) (*CommonResponse, error) { 499 | out := new(CommonResponse) 500 | err := c.cc.Invoke(ctx, "/MatchService/QueryDeep", in, out, opts...) 501 | if err != nil { 502 | return nil, err 503 | } 504 | return out, nil 505 | } 506 | 507 | // MatchServiceServer is the server API for MatchService service. 508 | type MatchServiceServer interface { 509 | AddOrder(context.Context, *AddOrderRequest) (*CommonResponse, error) 510 | CancelOrder(context.Context, *CancelOrderRequest) (*CommonResponse, error) 511 | QueryDeep(context.Context, *QueryDeepRequest) (*CommonResponse, error) 512 | } 513 | 514 | // UnimplementedMatchServiceServer can be embedded to have forward compatible implementations. 515 | type UnimplementedMatchServiceServer struct { 516 | } 517 | 518 | func (*UnimplementedMatchServiceServer) AddOrder(context.Context, *AddOrderRequest) (*CommonResponse, error) { 519 | return nil, status.Errorf(codes.Unimplemented, "method AddOrder not implemented") 520 | } 521 | func (*UnimplementedMatchServiceServer) CancelOrder(context.Context, *CancelOrderRequest) (*CommonResponse, error) { 522 | return nil, status.Errorf(codes.Unimplemented, "method CancelOrder not implemented") 523 | } 524 | func (*UnimplementedMatchServiceServer) QueryDeep(context.Context, *QueryDeepRequest) (*CommonResponse, error) { 525 | return nil, status.Errorf(codes.Unimplemented, "method QueryDeep not implemented") 526 | } 527 | 528 | func RegisterMatchServiceServer(s *grpc.Server, srv MatchServiceServer) { 529 | s.RegisterService(&_MatchService_serviceDesc, srv) 530 | } 531 | 532 | func _MatchService_AddOrder_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 533 | in := new(AddOrderRequest) 534 | if err := dec(in); err != nil { 535 | return nil, err 536 | } 537 | if interceptor == nil { 538 | return srv.(MatchServiceServer).AddOrder(ctx, in) 539 | } 540 | info := &grpc.UnaryServerInfo{ 541 | Server: srv, 542 | FullMethod: "/MatchService/AddOrder", 543 | } 544 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 545 | return srv.(MatchServiceServer).AddOrder(ctx, req.(*AddOrderRequest)) 546 | } 547 | return interceptor(ctx, in, info, handler) 548 | } 549 | 550 | func _MatchService_CancelOrder_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 551 | in := new(CancelOrderRequest) 552 | if err := dec(in); err != nil { 553 | return nil, err 554 | } 555 | if interceptor == nil { 556 | return srv.(MatchServiceServer).CancelOrder(ctx, in) 557 | } 558 | info := &grpc.UnaryServerInfo{ 559 | Server: srv, 560 | FullMethod: "/MatchService/CancelOrder", 561 | } 562 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 563 | return srv.(MatchServiceServer).CancelOrder(ctx, req.(*CancelOrderRequest)) 564 | } 565 | return interceptor(ctx, in, info, handler) 566 | } 567 | 568 | func _MatchService_QueryDeep_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 569 | in := new(QueryDeepRequest) 570 | if err := dec(in); err != nil { 571 | return nil, err 572 | } 573 | if interceptor == nil { 574 | return srv.(MatchServiceServer).QueryDeep(ctx, in) 575 | } 576 | info := &grpc.UnaryServerInfo{ 577 | Server: srv, 578 | FullMethod: "/MatchService/QueryDeep", 579 | } 580 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 581 | return srv.(MatchServiceServer).QueryDeep(ctx, req.(*QueryDeepRequest)) 582 | } 583 | return interceptor(ctx, in, info, handler) 584 | } 585 | 586 | var _MatchService_serviceDesc = grpc.ServiceDesc{ 587 | ServiceName: "MatchService", 588 | HandlerType: (*MatchServiceServer)(nil), 589 | Methods: []grpc.MethodDesc{ 590 | { 591 | MethodName: "AddOrder", 592 | Handler: _MatchService_AddOrder_Handler, 593 | }, 594 | { 595 | MethodName: "CancelOrder", 596 | Handler: _MatchService_CancelOrder_Handler, 597 | }, 598 | { 599 | MethodName: "QueryDeep", 600 | Handler: _MatchService_QueryDeep_Handler, 601 | }, 602 | }, 603 | Streams: []grpc.StreamDesc{}, 604 | Metadata: "grpc/order.proto", 605 | } 606 | -------------------------------------------------------------------------------- /grpc/order.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option go_package = "./grpc"; // protoc --go_out=plugins=grpc:. ./grpc/*.proto 4 | 5 | message AddOrderRequest { 6 | string id = 1; 7 | string user_id = 2; 8 | string pair = 3; 9 | string price = 4; 10 | string amount = 5; 11 | string type = 6; 12 | string side = 7; 13 | string time_in_force = 8; 14 | int64 time_unix_milli = 9; 15 | } 16 | 17 | message CancelOrderRequest { 18 | string id = 1; 19 | string pair = 2; 20 | } 21 | 22 | message CommonResponse { 23 | int32 code = 1; 24 | string msg = 2; 25 | bytes data = 3; 26 | } 27 | 28 | message QueryDeepRequest { 29 | string pair = 1; 30 | } 31 | 32 | service MatchService { 33 | rpc AddOrder (AddOrderRequest) returns (CommonResponse); 34 | rpc CancelOrder (CancelOrderRequest) returns (CommonResponse); 35 | rpc QueryDeep (QueryDeepRequest) returns (CommonResponse); 36 | } -------------------------------------------------------------------------------- /grpc/server.go: -------------------------------------------------------------------------------- 1 | package grpc 2 | 3 | import ( 4 | context "context" 5 | "fmt" 6 | "net" 7 | "transaction-matching-engine/common" 8 | 9 | grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" 10 | grpc_recovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery" 11 | "google.golang.org/grpc" 12 | ) 13 | 14 | type grpcServer struct { 15 | gs *grpc.Server 16 | ctx context.Context 17 | cancelFunc context.CancelFunc 18 | } 19 | 20 | func Run(pairs []string) { 21 | common.ServerStatus.Add(1) 22 | defer common.ServerStatus.Done() 23 | 24 | opts := []grpc.ServerOption{ 25 | grpc_middleware.WithUnaryServerChain( 26 | grpc_recovery.UnaryServerInterceptor([]grpc_recovery.Option{ 27 | grpc_recovery.WithRecoveryHandler(recoverInterceptor)}...), 28 | loggerInterceptor, 29 | ), 30 | } 31 | 32 | ctx, cancelFunc := context.WithCancel(common.ServerStatus.Context()) 33 | 34 | server := &grpcServer{ 35 | gs: grpc.NewServer(opts...), 36 | ctx: ctx, 37 | cancelFunc: cancelFunc, 38 | } 39 | 40 | lst, err := net.Listen("tcp", ":6666") 41 | if err != nil { 42 | panic("rpc listen err:" + err.Error()) 43 | } 44 | 45 | RegisterMatchServiceServer(server.gs, NewImplementedMatchServiceServer(pairs)) 46 | 47 | go gracefulStop(server) 48 | 49 | fmt.Println("rpc running...") 50 | if err := server.gs.Serve(lst); err != nil { 51 | panic("rpc Serve err:" + err.Error()) 52 | } 53 | fmt.Println("rpc stopped.") 54 | } 55 | 56 | func gracefulStop(server *grpcServer) { 57 | <-server.ctx.Done() 58 | server.gs.GracefulStop() 59 | } 60 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "transaction-matching-engine/cmd" 5 | "transaction-matching-engine/common" 6 | "transaction-matching-engine/engine" 7 | ) 8 | 9 | func main() { 10 | cmd.Execute() 11 | common.ServerStatus.Wait() 12 | engine.Dump() 13 | } 14 | -------------------------------------------------------------------------------- /models/deep.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type Deep struct { 4 | Pair string `json:"pair"` 5 | TimeUnixMilli int64 `json:"time_unix_milli"` 6 | Bids [][3]string `json:"bids"` 7 | Asks [][3]string `json:"asks"` 8 | } 9 | -------------------------------------------------------------------------------- /models/order.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | /* 4 | 业务系统需要校验参数的合法性,需要保证同一交易对下不会出现相同的id 5 | 6 | GTC 成交为止 :订单会一直有效,直到被成交或者取消。 7 | IOC 无法立即成交的部分就撤销 : 订单在失效前会尽量多的成交。 8 | FOK 无法全部立即成交就撤销 : 如果无法全部成交,订单会失效。 9 | 10 | */ 11 | 12 | //订单挂单 订单撤销仅会传Id/Pair过来,会重复使用该结构,但会修改 TimeInForce = CANCEL 13 | type Order struct { 14 | Id string `json:"id"` //订单唯一标识 15 | UserId string `json:"user_id"` //用户id 16 | Pair string `json:"pair"` //交易对 作为多个撮合池的唯一标识 17 | Price string `json:"price"` //价格 (市价单无price) 18 | Amount string `json:"amount"` //数量 19 | Type string `json:"type"` //订单类型 [limit,market] limit 限价单 market市价单 [market市价单走IOC成交] 20 | Side string `json:"side"` //订单方向 [buy,sell] buy 买单 sell 卖单 21 | TimeInForce string `json:"time_in_force"` //订单有效期 [GTC,IOC,FOK] 说明见注释 限价单必传,市价单不传 (默认GTC) 22 | TimeUnixMilli int64 `json:"time_unix_milli"` //下单时间戳 毫秒 23 | } 24 | -------------------------------------------------------------------------------- /models/sort.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "github.com/shopspring/decimal" 4 | 5 | /* 6 | 可自定义增加排序逻辑 7 | 例如:需要增加大客户优先级,则可以修改 SortKey 的排序优先级 8 | 增加字段 UserId 9 | 使用map存储 [UserId: 大客户分数] 如: m = map[10001:100,10002:99] 10 | asks与bids中调用Compare,增加分值比较逻辑即可: m[userId1] <=> m[userId2] 11 | */ 12 | 13 | // 跳表排序key ,优先级分别为 price,TimeUnixMilli,amount,id 14 | type SortKey struct { 15 | Price decimal.Decimal //价格 根据 asks/bids 类型升降排序 16 | TimeUnixMilli int64 //下单时间 时间早的排前面 17 | //Amount decimal.Decimal //数量 数量大的排前面 【因为成交会导致数量变化,生成的排序key会变化,不使用数量作为key】 18 | Id string //订单唯一标识 增加订单号比较,保证排序key不重复 19 | } 20 | -------------------------------------------------------------------------------- /models/trade.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | /* 4 | maker 订单 : 盘口订单为maker订单;成交时以maker订单价格成交 5 | taker 订单 : 与盘口订单成交部分为taker订单,无法成交部分转入盘口时,将由taker转为maker 6 | */ 7 | 8 | //成交/撤销 推送 分别至不同队列 9 | type Trade struct { 10 | Id string `json:"id"` //成交id 11 | Pair string `json:"pair"` //交易对 作为多个撮合池的唯一标识 12 | TakerUserId string `json:"taker_user_id"` //taker用户id 13 | TakerOrderId string `json:"taker_order_id"` //taker订单id 14 | MakerUserId string `json:"maker_user_id"` //taker用户id 15 | MakerOrderId string `json:"maker_order_id"` //maker订单id 16 | Price string `json:"price"` //价格 17 | Amount string `json:"amount"` //数量 18 | TakerOrderType string `json:"taker_order_type"` //taker订单类型 [limit,market] limit 限价单 market市价单 19 | MakerOrderType string `json:"maker_order_type"` //maker订单类型 [limit,market] limit 限价单 market市价单 20 | TakerOrderSide string `json:"taker_order_side"` //taker订单方向 [buy,sell] buy 买单 sell 卖单 21 | MakerOrderSide string `json:"maker_order_side"` //maker订单方向 [buy,sell] buy 买单 sell 卖单 22 | TimeUnixMilli int64 `json:"time_unix_milli"` //成交时间戳 毫秒 23 | Type string `json:"type"` //订单的成交类型 [sell,buy,cancel] (cancel数据均在taker,虽然包含maker撤单) 24 | } 25 | -------------------------------------------------------------------------------- /orders_test.json: -------------------------------------------------------------------------------- 1 | { 2 | "GTC":[{ 3 | "id": "1", 4 | "userId": "1", 5 | "pair": "BTC-USDT", 6 | "price": "1000", 7 | "amount": "10", 8 | "type": "limit", 9 | "side": "buy", 10 | "timeInForce": "GTC", 11 | "timeUnixMilli": "1" 12 | },{ 13 | "id": "2", 14 | "userId": "1", 15 | "pair": "BTC-USDT", 16 | "price": "1100", 17 | "amount": "12", 18 | "type": "limit", 19 | "side": "sell", 20 | "timeInForce": "GTC", 21 | "timeUnixMilli": "1" 22 | },{ 23 | "id": "3", 24 | "userId": "1", 25 | "pair": "BTC-USDT", 26 | "price": "1300", 27 | "amount": "4", 28 | "type": "limit", 29 | "side": "buy", 30 | "timeInForce": "GTC", 31 | "timeUnixMilli": "1" 32 | },{ 33 | "id": "4", 34 | "userId": "1", 35 | "pair": "BTC-USDT", 36 | "price": "800", 37 | "amount": "12", 38 | "type": "limit", 39 | "side": "sell", 40 | "timeInForce": "GTC", 41 | "timeUnixMilli": "1" 42 | },{ 43 | "id": "5", 44 | "userId": "1", 45 | "pair": "BTC-USDT", 46 | "price": "2000", 47 | "amount": "11", 48 | "type": "limit", 49 | "side": "buy", 50 | "timeInForce": "GTC", 51 | "timeUnixMilli": "1" 52 | },{ 53 | "id": "5", 54 | "userId": "1", 55 | "pair": "BTC-USDT", 56 | "price": "", 57 | "amount": "11", 58 | "type": "market", 59 | "side": "buy", 60 | "timeInForce": "GTC", 61 | "timeUnixMilli": "1" 62 | }], 63 | "IOC":[{ 64 | "id": "1", 65 | "userId": "1", 66 | "pair": "BTC-USDT", 67 | "price": "1000", 68 | "amount": "10", 69 | "type": "limit", 70 | "side": "buy", 71 | "timeInForce": "GTC", 72 | "timeUnixMilli": "1" 73 | },{ 74 | "id": "2", 75 | "userId": "1", 76 | "pair": "BTC-USDT", 77 | "price": "2000", 78 | "amount": "8", 79 | "type": "limit", 80 | "side": "buy", 81 | "timeInForce": "GTC", 82 | "timeUnixMilli": "1" 83 | },{ 84 | "id": "3", 85 | "userId": "1", 86 | "pair": "BTC-USDT", 87 | "price": "800", 88 | "amount": "17", 89 | "type": "limit", 90 | "side": "sell", 91 | "timeInForce": "IOC", 92 | "timeUnixMilli": "1" 93 | },{ 94 | "id": "3", 95 | "userId": "1", 96 | "pair": "BTC-USDT", 97 | "price": "", 98 | "amount": "17", 99 | "type": "market", 100 | "side": "sell", 101 | "timeInForce": "IOC", 102 | "timeUnixMilli": "1" 103 | },{ 104 | "id": "3", 105 | "userId": "1", 106 | "pair": "BTC-USDT", 107 | "price": "800", 108 | "amount": "19", 109 | "type": "limit", 110 | "side": "sell", 111 | "timeInForce": "IOC", 112 | "timeUnixMilli": "1" 113 | },{ 114 | "id": "3", 115 | "userId": "1", 116 | "pair": "BTC-USDT", 117 | "price": "", 118 | "amount": "19", 119 | "type": "market", 120 | "side": "sell", 121 | "timeInForce": "IOC", 122 | "timeUnixMilli": "1" 123 | }], 124 | "FOK":[{ 125 | "id": "1", 126 | "userId": "1", 127 | "pair": "BTC-USDT", 128 | "price": "1000", 129 | "amount": "10", 130 | "type": "limit", 131 | "side": "buy", 132 | "timeInForce": "GTC", 133 | "timeUnixMilli": "1" 134 | },{ 135 | "id": "2", 136 | "userId": "1", 137 | "pair": "BTC-USDT", 138 | "price": "2000", 139 | "amount": "8", 140 | "type": "limit", 141 | "side": "buy", 142 | "timeInForce": "GTC", 143 | "timeUnixMilli": "1" 144 | },{ 145 | "id": "3", 146 | "userId": "1", 147 | "pair": "BTC-USDT", 148 | "price": "800", 149 | "amount": "17", 150 | "type": "limit", 151 | "side": "sell", 152 | "timeInForce": "FOK", 153 | "timeUnixMilli": "1" 154 | },{ 155 | "id": "3", 156 | "userId": "1", 157 | "pair": "BTC-USDT", 158 | "price": "", 159 | "amount": "17", 160 | "type": "market", 161 | "side": "sell", 162 | "timeInForce": "FOK", 163 | "timeUnixMilli": "1" 164 | },{ 165 | "id": "3", 166 | "userId": "1", 167 | "pair": "BTC-USDT", 168 | "price": "800", 169 | "amount": "19", 170 | "type": "limit", 171 | "side": "sell", 172 | "timeInForce": "FOK", 173 | "timeUnixMilli": "1" 174 | },{ 175 | "id": "3", 176 | "userId": "1", 177 | "pair": "BTC-USDT", 178 | "price": "", 179 | "amount": "19", 180 | "type": "market", 181 | "side": "sell", 182 | "timeInForce": "FOK", 183 | "timeUnixMilli": "1" 184 | }] 185 | } -------------------------------------------------------------------------------- /pool/cmp.go: -------------------------------------------------------------------------------- 1 | package pool 2 | 3 | import "transaction-matching-engine/models" 4 | 5 | /* 6 | 排序优先级: 7 | 价格 8 | 时间 [ 早 -> 晚 ] 9 | // 数量 [ 少 -> 多 ] //能保证池内数量按序消耗之后仍然保持 [少->多] 的顺序而不用重新排序 【因为成交会导致数量变化,生成的排序key会变化,不使用数量作为key】 10 | id [ 小 -> 大 ] id肯定不相等,所以不会出现相等的key 11 | */ 12 | 13 | /* 14 | 卖单,跳表价格升序 15 | */ 16 | 17 | type asksCmp struct { 18 | val int //decimal比较结果 19 | ak, bk *models.SortKey //排序key 20 | } 21 | 22 | // 实现比较接口 23 | func (asks *asksCmp) Compare(a, b interface{}) int { 24 | asks.ak, asks.bk = a.(*models.SortKey), b.(*models.SortKey) 25 | if asks.val = asks.ak.Price.Cmp(asks.bk.Price); asks.val != 0 { //价格降序 26 | return asks.val 27 | } 28 | if asks.ak.TimeUnixMilli < asks.bk.TimeUnixMilli { 29 | return -1 30 | } else if asks.ak.TimeUnixMilli > asks.bk.TimeUnixMilli { 31 | return 1 32 | } 33 | // if asks.val = asks.ak.Amount.Cmp(asks.bk.Amount); asks.val != 0 { 34 | // return asks.val 35 | // } 36 | if asks.ak.Id < asks.bk.Id { 37 | return -1 38 | } else if asks.ak.Id > asks.bk.Id { 39 | return 1 40 | } 41 | return 0 42 | } 43 | 44 | /* 45 | 买单,跳表价格降序 46 | */ 47 | 48 | type bidsCmp struct { 49 | val int //decimal比较结果 50 | ak, bk *models.SortKey //排序key 51 | } 52 | 53 | // 实现比较接口 54 | func (bids *bidsCmp) Compare(a, b interface{}) int { 55 | bids.ak, bids.bk = a.(*models.SortKey), b.(*models.SortKey) 56 | if bids.val = bids.bk.Price.Cmp(bids.ak.Price); bids.val != 0 { //价格降序 57 | return bids.val 58 | } 59 | if bids.ak.TimeUnixMilli < bids.bk.TimeUnixMilli { 60 | return -1 61 | } else if bids.ak.TimeUnixMilli > bids.bk.TimeUnixMilli { 62 | return 1 63 | } 64 | // if bids.val = bids.ak.Amount.Cmp(bids.bk.Amount); bids.val != 0 { 65 | // return bids.val 66 | // } 67 | if bids.ak.Id < bids.bk.Id { 68 | return -1 69 | } else if bids.ak.Id > bids.bk.Id { 70 | return 1 71 | } 72 | return 0 73 | } 74 | -------------------------------------------------------------------------------- /pool/match.go: -------------------------------------------------------------------------------- 1 | package pool 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "time" 7 | "transaction-matching-engine/common" 8 | "transaction-matching-engine/models" 9 | 10 | "github.com/shopspring/decimal" 11 | ) 12 | 13 | // 撮合池 14 | type MatchPool struct { 15 | orderMap map[string]*models.Order //存储订单池内的订单,撤销订单时 16 | orderChan chan *models.Order //订单输入 17 | bids, asks *pool //买卖盘订单池 18 | tradeChan chan *models.Trade //订单成交 19 | ctx context.Context 20 | cancelFunc context.CancelFunc 21 | deepSnapshot *deepSnapshot 22 | } 23 | 24 | func NewMatchPool() *MatchPool { 25 | ctx, cancelFunc := context.WithCancel(common.ServerStatus.Context()) 26 | matchPool := &MatchPool{ 27 | orderMap: make(map[string]*models.Order), 28 | orderChan: make(chan *models.Order, 10000), 29 | bids: NewPool(&bidsCmp{}), 30 | asks: NewPool(&asksCmp{}), 31 | tradeChan: make(chan *models.Trade, 10000), 32 | ctx: ctx, 33 | cancelFunc: cancelFunc, 34 | deepSnapshot: newDeepSnapshot(), 35 | } 36 | go matchPool.listenSignal() 37 | go matchPool.run() 38 | return matchPool 39 | } 40 | 41 | /* 42 | 业务相关 单线程模式 43 | */ 44 | 45 | // 接收退出信号 46 | func (m *MatchPool) listenSignal() { 47 | <-m.ctx.Done() 48 | close(m.orderChan) 49 | } 50 | 51 | // 运行 52 | func (m *MatchPool) run() { 53 | common.ServerStatus.Add(1) 54 | defer common.ServerStatus.Done() 55 | for orderPtr := range m.orderChan { 56 | m.deepSnapshot.queryLock.Lock() 57 | m.deepSnapshot.hasNewOrder = true 58 | switch orderPtr.TimeInForce { 59 | case common.TimeInForceFOK: 60 | m.orderFOK(orderPtr) 61 | case common.TimeInForceIOC: 62 | m.orderIOC(orderPtr) 63 | case common.TimeInForceGTC: 64 | m.orderGTC(orderPtr) 65 | default: //默认Cancel 66 | m.orderCancel(orderPtr) 67 | } 68 | m.deepSnapshot.queryLock.Unlock() 69 | } 70 | } 71 | 72 | // 查询深度 73 | func (m *MatchPool) QueryDeep(pair string) ([][3]string, [][3]string) { 74 | if m.deepSnapshot.IsNeedUpdate() { 75 | m.deepSnapshot.queryLock.Lock() 76 | if m.deepSnapshot.IsNeedUpdate() { //可能已经被上一个请求更新了 77 | m.deepSnapshot.Update(m.GetOrders()) 78 | } 79 | m.deepSnapshot.queryLock.Unlock() 80 | } 81 | return m.deepSnapshot.GetSnapshot() 82 | } 83 | 84 | // 订单输入 异步 85 | func (m *MatchPool) Input(ctx context.Context, order *models.Order) error { 86 | select { 87 | case <-m.ctx.Done(): 88 | return common.ServerCancelErr 89 | default: 90 | select { 91 | case m.orderChan <- order: 92 | return nil 93 | case <-ctx.Done(): 94 | return common.OrderHandleTimeoutErr 95 | } 96 | } 97 | } 98 | 99 | // 成交输出 异步 100 | func (m *MatchPool) Output() <-chan *models.Trade { 101 | return m.tradeChan 102 | } 103 | 104 | // FOK 无法全部立即成交就撤销 : 如果无法全部成交,订单会失效。 105 | func (m *MatchPool) orderFOK(order *models.Order) { 106 | rival := m.bids 107 | if order.Side == common.SideOrderBuy { 108 | rival = m.asks 109 | } 110 | needAmount, _ := decimal.NewFromString(order.Amount) 111 | canDealAmount := m.getCanDealAmount(rival, order, order.Type) 112 | 113 | //撤单 114 | if needAmount.Cmp(canDealAmount) == 1 { // need > can 115 | m.handleTrade(order, nil, nil, common.TypeOrderCancel, 1) 116 | return 117 | } 118 | 119 | //成交 120 | for ; needAmount.Cmp(decimal.Zero) == 1; needAmount, _ = decimal.NewFromString(order.Amount) { 121 | m.handleTrade(order, rival.GetDepthData(1), rival, order.Side, 1) 122 | } 123 | } 124 | 125 | // IOC 无法立即成交的部分就撤销 : 订单在失效前会尽量多的成交。 126 | func (m *MatchPool) orderIOC(order *models.Order) { 127 | rival := m.bids 128 | if order.Side == common.SideOrderBuy { 129 | rival = m.asks 130 | } 131 | needAmount, _ := decimal.NewFromString(order.Amount) 132 | canDealAmount := m.getCanDealAmount(rival, order, order.Type) 133 | cancelAmount := decimal.Zero 134 | 135 | //计算需要撤销的数量 136 | if needAmount.Cmp(canDealAmount) == 1 { // need > can 137 | cancelAmount = needAmount.Sub(canDealAmount) 138 | } 139 | 140 | //计算需要成交的数量并成交 141 | if canDealAmount.Cmp(decimal.Zero) == 1 { //能成交的数量大于0 142 | 143 | if canDealAmount.Cmp(needAmount) == -1 { //重新生成数量 144 | order.Amount = canDealAmount.String() 145 | needAmount, _ = decimal.NewFromString(order.Amount) 146 | } 147 | 148 | for ; needAmount.Cmp(decimal.Zero) == 1; needAmount, _ = decimal.NewFromString(order.Amount) { 149 | m.handleTrade(order, rival.GetDepthData(1), rival, order.Side, 1) 150 | } 151 | } 152 | 153 | //撤单 154 | if cancelAmount.Cmp(decimal.Zero) == 1 { 155 | order.Amount = cancelAmount.String() 156 | m.handleTrade(order, nil, nil, common.TypeOrderCancel, 1) 157 | } 158 | } 159 | 160 | // GTC 成交为止 :订单会一直有效,直到被成交或者取消。 161 | func (m *MatchPool) orderGTC(order *models.Order) { 162 | self, rival := m.asks, m.bids 163 | if order.Side == common.SideOrderBuy { 164 | self, rival = m.bids, m.asks 165 | } 166 | needAmount, _ := decimal.NewFromString(order.Amount) 167 | canDealAmount := m.getCanDealAmount(rival, order, order.Type) 168 | inputAmount := decimal.Zero 169 | 170 | //计算无法成交的数量 171 | if needAmount.Cmp(canDealAmount) == 1 { // need >= can 172 | inputAmount = needAmount.Sub(canDealAmount) 173 | } 174 | 175 | //计算需要成交的数量并成交 176 | if canDealAmount.Cmp(decimal.Zero) == 1 { //能成交的数量 > 0 177 | 178 | if canDealAmount.Cmp(needAmount) == -1 { //重新生成数量 179 | order.Amount = canDealAmount.String() 180 | needAmount, _ = decimal.NewFromString(order.Amount) 181 | } 182 | 183 | for ; needAmount.Cmp(decimal.Zero) == 1; needAmount, _ = decimal.NewFromString(order.Amount) { 184 | m.handleTrade(order, rival.GetDepthData(1), rival, order.Side, 1) 185 | } 186 | } 187 | 188 | if inputAmount.Cmp(decimal.Zero) == 1 { 189 | order.Amount = inputAmount.String() //重新生成数量 190 | if order.Type == common.TypeOrderLimit { //限价单进入撮合池 191 | self.Insert(order) 192 | m.orderMap[order.Id] = order 193 | } else { //市价单撤单 194 | m.handleTrade(order, nil, nil, common.TypeOrderCancel, 1) 195 | } 196 | } 197 | } 198 | 199 | // Cancel订单 200 | func (m *MatchPool) orderCancel(order *models.Order) { 201 | order, ok := m.orderMap[order.Id] 202 | if ok { 203 | self := m.asks 204 | if order.Side == common.SideOrderBuy { 205 | self = m.bids 206 | } 207 | m.handleTrade(order, nil, self, common.TypeOrderCancel, 1) 208 | delete(m.orderMap, order.Id) 209 | self.DeleteByOrder(order) 210 | } 211 | } 212 | 213 | // 获取能成交的数量 side,price为taker的状态 214 | func (m *MatchPool) getCanDealAmount(p *pool, order *models.Order, orderType string) decimal.Decimal { 215 | canDeal := decimal.NewFromInt(0) 216 | orderPrice, _ := decimal.NewFromString(order.Price) 217 | orderAmount, _ := decimal.NewFromString(order.Amount) 218 | for rk := 1; rk <= p.GetOrderDepth() && orderAmount.Cmp(canDeal) == 1; rk++ { 219 | data := p.GetDepthData(rk) 220 | currentPrice, _ := decimal.NewFromString(data.Price) 221 | currentAmount, _ := decimal.NewFromString(data.Amount) 222 | if orderType == common.TypeOrderLimit && //限价单需要判断价格 不满足的条件 223 | ((order.Side == common.SideOrderBuy && orderPrice.Cmp(currentPrice) == -1) || //买价 小于 卖一档价 224 | (order.Side == common.SideOrderSell && orderPrice.Cmp(currentPrice) == 1)) { //卖价 大于 买一档价 225 | return canDeal 226 | } 227 | canDeal = canDeal.Add(currentAmount) 228 | } 229 | return canDeal 230 | } 231 | 232 | // 处理成交/撤销 trade pl为对手盘订单池 233 | func (m *MatchPool) handleTrade(taker, maker *models.Order, pl *pool, tradeType string, rk int) { 234 | nowUnixMilli := time.Now().UnixMilli() 235 | trade := &models.Trade{ 236 | Id: fmt.Sprintf("%d%07d", nowUnixMilli, common.GetAtomicIncrSeq()), 237 | Pair: taker.Pair, 238 | TakerUserId: taker.UserId, 239 | TakerOrderId: taker.Id, 240 | Price: taker.Price, 241 | Amount: taker.Amount, 242 | TakerOrderType: taker.Type, 243 | TakerOrderSide: taker.Side, 244 | TimeUnixMilli: nowUnixMilli, 245 | Type: tradeType, 246 | } 247 | if tradeType != common.TypeOrderCancel { 248 | trade.Price = maker.Price 249 | trade.MakerUserId = maker.UserId 250 | trade.MakerOrderId = maker.Id 251 | trade.MakerOrderType = maker.Type 252 | trade.MakerOrderSide = maker.Side 253 | //计算amount 254 | takerAmountDecimal, _ := decimal.NewFromString(taker.Amount) 255 | makerAmountDecimal, _ := decimal.NewFromString(maker.Amount) 256 | if takerAmountDecimal.Cmp(makerAmountDecimal) > -1 { // taker >= maker 257 | taker.Amount = takerAmountDecimal.Sub(makerAmountDecimal).String() 258 | trade.Amount = maker.Amount 259 | delete(m.orderMap, maker.Id) 260 | pl.DeleteByDepth(rk) 261 | } else { // taker < maker 262 | taker.Amount = "0" 263 | maker.Amount = makerAmountDecimal.Sub(takerAmountDecimal).String() 264 | m.orderMap[maker.Id] = maker 265 | pl.UpdateDataByDepth(rk, maker) 266 | } 267 | } 268 | common.ServerStatus.Add(1) 269 | m.tradeChan <- trade 270 | } 271 | 272 | /* 273 | 持久化相关 274 | */ 275 | 276 | // 获取订单池 (bids,asks) 277 | func (m *MatchPool) GetOrders() ([]*models.Order, []*models.Order) { 278 | return m.bids.GetAllOrders(), m.asks.GetAllOrders() 279 | } 280 | 281 | // 写入订单池 282 | func (m *MatchPool) SetOrders(bids, asks []*models.Order) { 283 | for k := range bids { 284 | m.bids.Insert(bids[k]) 285 | m.orderMap[bids[k].Id] = bids[k] 286 | } 287 | for k := range asks { 288 | m.asks.Insert(asks[k]) 289 | m.orderMap[asks[k].Id] = asks[k] 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /pool/pool.go: -------------------------------------------------------------------------------- 1 | package pool 2 | 3 | import ( 4 | "transaction-matching-engine/models" 5 | 6 | "github.com/shopspring/decimal" 7 | "github.com/yantao1995/ds/skiplist" 8 | ) 9 | 10 | // 订单池 11 | type pool struct { 12 | sl *skiplist.SkipList 13 | } 14 | 15 | func NewPool(cmp skiplist.CompareAble) *pool { 16 | sl, _ := skiplist.New(cmp, skiplist.WithAllowTheSameKey(false)) 17 | return &pool{ 18 | sl: sl, 19 | } 20 | } 21 | 22 | // 生成订单排序key 23 | func (p *pool) generateSortKey(order *models.Order) *models.SortKey { 24 | price, _ := decimal.NewFromString(order.Price) 25 | //amount, _ := decimal.NewFromString(order.Amount) 26 | return &models.SortKey{ 27 | Price: price, 28 | TimeUnixMilli: order.TimeUnixMilli, 29 | // Amount: amount, 30 | Id: order.Id, 31 | } 32 | } 33 | 34 | // 写入订单池 35 | func (p *pool) Insert(order *models.Order) { 36 | p.sl.Insert(p.generateSortKey(order), order) 37 | } 38 | 39 | // 获取订单池内订单深度 40 | func (p *pool) GetOrderDepth() int { 41 | return p.sl.GetLength() 42 | } 43 | 44 | // 更新指定档位的数据 45 | func (p *pool) UpdateDataByDepth(rk int, order *models.Order) bool { 46 | if rk <= p.GetOrderDepth() { 47 | return p.sl.UpdateByRank(rk, order) 48 | } 49 | return false 50 | } 51 | 52 | // 删除指定档位的数据 53 | func (p *pool) DeleteByDepth(rk int) bool { 54 | if rk <= p.GetOrderDepth() { 55 | return p.sl.DeleteByRank(rk) 56 | } 57 | return false 58 | } 59 | 60 | // 删除指定订单 61 | func (p *pool) DeleteByOrder(order *models.Order) bool { 62 | if p.GetOrderDepth() > 0 { 63 | return p.sl.DeleteBatchByKey(p.generateSortKey(order)) 64 | } 65 | return false 66 | } 67 | 68 | // 获取订单池指定档位的数据 69 | func (p *pool) GetDepthData(rk int) *models.Order { 70 | if rk <= p.GetOrderDepth() { 71 | return p.sl.GetByRank(rk).(*models.Order) 72 | } 73 | return nil 74 | } 75 | 76 | // 获取订单池内所有订单 77 | func (p *pool) GetAllOrders() []*models.Order { 78 | all := p.sl.GetByRankRange(1, p.sl.GetLength()) 79 | orders := make([]*models.Order, len(all)) 80 | for k := range all { 81 | orders[k] = all[k].(*models.Order) 82 | } 83 | return orders 84 | } 85 | -------------------------------------------------------------------------------- /pool/pools_test.go: -------------------------------------------------------------------------------- 1 | package pool 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "testing" 7 | "time" 8 | "transaction-matching-engine/models" 9 | ) 10 | 11 | func TestGTCPool(t *testing.T) { 12 | mpl := NewMatchPool() 13 | ctx, cancel := context.WithCancel(context.Background()) 14 | defer cancel() 15 | order := &models.Order{ 16 | Id: "1", 17 | UserId: "1", 18 | Pair: "BTC-USDT", 19 | Price: "1000", 20 | Amount: "100", 21 | Type: "limit", 22 | Side: "buy", 23 | TimeInForce: "GTC", 24 | TimeUnixMilli: time.Now().UnixMilli(), 25 | } 26 | mpl.Input(ctx, order) 27 | order = &models.Order{ 28 | Id: "2", 29 | UserId: "1", 30 | Pair: "BTC-USDT", 31 | Price: "2000", 32 | Amount: "120", 33 | Type: "limit", 34 | Side: "buy", 35 | TimeInForce: "GTC", 36 | TimeUnixMilli: time.Now().UnixMilli(), 37 | } 38 | mpl.Input(ctx, order) 39 | order = &models.Order{ 40 | Id: "3", 41 | UserId: "2", 42 | Pair: "BTC-USDT", 43 | Price: "1500", 44 | Amount: "100", 45 | Type: "limit", 46 | Side: "sell", 47 | TimeInForce: "GTC", 48 | TimeUnixMilli: time.Now().UnixMilli(), 49 | } 50 | mpl.Input(ctx, order) 51 | order = &models.Order{ 52 | Id: "4", 53 | UserId: "2", 54 | Pair: "BTC-USDT", 55 | Price: "900", 56 | Amount: "30", 57 | Type: "limit", 58 | Side: "sell", 59 | TimeInForce: "GTC", 60 | TimeUnixMilli: time.Now().UnixMilli(), 61 | } 62 | mpl.Input(ctx, order) 63 | ch := mpl.Output() 64 | for trade := range ch { 65 | fmt.Printf("成交:%+v\n", trade) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /pool/snapshot.go: -------------------------------------------------------------------------------- 1 | package pool 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | "transaction-matching-engine/models" 7 | ) 8 | 9 | /* 10 | 快照惰性更新,有请求时,判断上次更新时间,如果时间差大于当前计算出的最大时间差,则更新 11 | 12 | 快照更新时间 计算公式 13 | update Millisecond = snapshotUpdateTimeMillisecondBase + ( len(orders) / increaseFactor ) 14 | 每 increaseFactor 个订单,增加更新时间1毫秒 15 | 16 | 快照结构, 返回u方便业务对量化帐号进行筛选 17 | [[u,p,a]] 18 | [[用户id,价格,数量]] 19 | */ 20 | 21 | //盘口深度快照 22 | type deepSnapshot struct { 23 | queryLock *sync.Mutex //保证更新深度和挂单不冲突 24 | bidsDeepSnapshot, asksDeepSnapshot [][3]string //订单深度快照 25 | lastUpdateTimeMilli int64 //上次更新时间 毫秒 26 | snapshotUpdateTimeMillisecondBase int64 //订单快照更新基础时间 27 | increaseFactor int64 //更新时间递增因子 28 | hasNewOrder bool //这段时间有新订单进入 如果一段时间无新订单则不更新 29 | } 30 | 31 | func newDeepSnapshot() *deepSnapshot { 32 | return &deepSnapshot{ 33 | queryLock: &sync.Mutex{}, 34 | bidsDeepSnapshot: [][3]string{}, 35 | asksDeepSnapshot: [][3]string{}, 36 | lastUpdateTimeMilli: 0, 37 | snapshotUpdateTimeMillisecondBase: 200, 38 | increaseFactor: 10000, 39 | hasNewOrder: true, 40 | } 41 | } 42 | 43 | //根据上次更新时间判断当前是否需要更新 44 | func (d *deepSnapshot) IsNeedUpdate() bool { 45 | return d.hasNewOrder && 46 | time.Now().UnixMilli()-d.lastUpdateTimeMilli > d.snapshotUpdateTimeMillisecondBase+ 47 | int64(len(d.bidsDeepSnapshot)+len(d.asksDeepSnapshot))/d.increaseFactor 48 | } 49 | 50 | //更新快照 51 | func (d *deepSnapshot) Update(bids, asks []*models.Order) { 52 | d.bidsDeepSnapshot = d.bidsDeepSnapshot[:0] 53 | for k := range bids { 54 | d.bidsDeepSnapshot = append(d.bidsDeepSnapshot, [3]string{bids[k].UserId, bids[k].Price, bids[k].Amount}) 55 | } 56 | d.asksDeepSnapshot = d.asksDeepSnapshot[:0] 57 | for k := range asks { 58 | d.asksDeepSnapshot = append(d.asksDeepSnapshot, [3]string{asks[k].UserId, asks[k].Price, asks[k].Amount}) 59 | } 60 | d.lastUpdateTimeMilli = time.Now().UnixMilli() 61 | d.hasNewOrder = false 62 | } 63 | 64 | //获取快照 65 | func (d *deepSnapshot) GetSnapshot() ([][3]string, [][3]string) { 66 | return d.bidsDeepSnapshot, d.asksDeepSnapshot 67 | } 68 | --------------------------------------------------------------------------------