Clearing 清算模組
Clearing 清算模組

Clearing 清算模組

Tags
Golang
System Design
Maching System
Date
Mar 31, 2024
notion image
撮合模組撮合後,雙方資產還沒有實際交換,訂單也還沒更新,必須再透過清算模組進行處理,故注入資產模組與訂單模組
type clearingUseCase struct { userAssetUseCase domain.UserAssetUseCase orderUseCase domain.OrderUseCase // another code... }
清算模組需實作的method不多,只需實作ClearMatchResult(),將MatchResult帶入,資產的轉換結果生成TransferResult提供給下游系統使用
type ClearingUseCase interface { ClearMatchResult(ctx context.Context, matchResult *MatchResult) (*TransferResult, error) // another code... }
將taker訂單更新,並依照taker的方向進行清算,以下分別介紹
func (c *clearingUseCase) ClearMatchResult(ctx context.Context, matchResult *domain.MatchResult) (*domain.TransferResult, error) { // 新增`transferResult`紀錄資產轉換結果 transferResult := new(domain.TransferResult) // 更新taker在訂單模組的狀態 taker := matchResult.TakerOrder if err := c.orderUseCase.UpdateOrder(ctx, taker.ID, taker.UnfilledQuantity, taker.Status, taker.UpdatedAt); err != nil { return nil, errors.Wrap(err, "update order failed") } switch matchResult.TakerOrder.Direction { // taker是賣單的情形 case domain.DirectionSell: // another code... // taker是買單的情形 case domain.DirectionBuy: // another code... default: return nil, errors.New("unknown direction") } return transferResult, nil }
如果taker是賣單,依照MatchResultMatchDetails逐一將maker訂單更新:
  • 將taker賣單凍結的數量轉換給maker,數量是matched
  • 將maker買單凍結的資產轉換給taker,金額是(maker price)*matched
並把taker跟maker最後的資產紀錄在TransferResult
// taker是賣單的情形 case domain.DirectionSell: // 依照`MatchDetails`逐一拿出撮合maker的細節來處理 for _, matchDetail := range matchResult.MatchDetails { // 更新maker在訂單模組的狀態 maker := matchDetail.MakerOrder if err := c.orderUseCase.UpdateOrder(ctx, maker.ID, maker.UnfilledQuantity, maker.Status, maker.UpdatedAt); err != nil { return nil, errors.Wrap(err, "update order failed") } matched := matchDetail.Quantity // 將taker賣單凍結的數量轉換給maker,數量是matched transferResultOne, err := c.userAssetUseCase.TransferFrozenToAvailable(ctx, taker.UserID, maker.UserID, c.baseCurrencyID, matched) if err != nil { return nil, errors.Wrap(err, "transfer failed") } // 將轉換結果紀錄 transferResult.UserAssets = append(transferResult.UserAssets, transferResultOne.UserAssets...) // 將maker買單凍結的資產轉換給taker,金額是(maker price)*matched transferResultTwo, err := c.userAssetUseCase.TransferFrozenToAvailable(ctx, maker.UserID, taker.UserID, c.quoteCurrencyID, maker.Price.Mul(matched)) if err != nil { return nil, errors.Wrap(err, "transfer failed") } // 將轉換結果紀錄 transferResult.UserAssets = append(transferResult.UserAssets, transferResultTwo.UserAssets...) // 如果maker買單數量減至0,則移除訂單 if maker.UnfilledQuantity.IsZero() { if err := c.orderUseCase.RemoveOrder(ctx, maker.ID); err != nil { return nil, errors.Wrap(err, "remove failed") } } } // 如果taker買單數量減至0,則移除訂單 if taker.UnfilledQuantity.IsZero() { if err := c.orderUseCase.RemoveOrder(ctx, taker.ID); err != nil { return nil, errors.Wrap(err, "remove failed") } }
如果taker是買單,則相反:
  • 將taker買單凍結的資產轉換給maker,金額是(maker price)*matched
  • 將maker賣單凍結的資產轉換給taker,數量是matched
需注意的是,如果taker是買單,凍結的資產金額是(taker price)*matched,須退還(taker price-maker price)*matched金額給taker。
例如10usdt price數量3btc的買單,需凍結30usdt,如果撮合到1usdt price數量2btc的賣單,撮合到2btc,taker理應用1*2=2usdt會獲得2btc,須退還(10-1)*2=18usdt給taker
// taker是買單的情形 case domain.DirectionBuy: // 依照`MatchDetails`逐一拿出撮合maker的細節來處理 for _, matchDetail := range matchResult.MatchDetails { // 更新maker在訂單模組的狀態 maker := matchDetail.MakerOrder if err := c.orderUseCase.UpdateOrder(ctx, maker.ID, maker.UnfilledQuantity, maker.Status, maker.UpdatedAt); err != nil { return nil, errors.Wrap(err, "update order failed") } matched := matchDetail.Quantity // taker買單的價格如果高於maker賣單,則多出來的價格須退還給taker,金額是(taker price-maker price)*matched // 退還給taker不需紀錄在`transferResult`,因為後續taker還會從maker拿到資產,taker資產的結果只需紀錄最後一個就可以了 if taker.Price.Cmp(maker.Price) > 0 { unfreezeQuote := taker.Price.Sub(maker.Price).Mul(matched) _, err := c.userAssetUseCase.Unfreeze(ctx, taker.UserID, c.quoteCurrencyID, unfreezeQuote) if err != nil { return nil, errors.Wrap(err, "unfreeze taker failed") } } // 將taker買單凍結的資產轉換給maker,金額是(maker price)*matched transferResultOne, err := c.userAssetUseCase.TransferFrozenToAvailable(ctx, taker.UserID, maker.UserID, c.quoteCurrencyID, maker.Price.Mul(matched)) if err != nil { return nil, errors.Wrap(err, "transfer failed") } // 將轉換結果紀錄 transferResult.UserAssets = append(transferResult.UserAssets, transferResultOne.UserAssets...) // 將maker賣單凍結的資產轉換給taker,數量是matched transferResultTwo, err := c.userAssetUseCase.TransferFrozenToAvailable(ctx, maker.UserID, taker.UserID, c.baseCurrencyID, matched) if err != nil { return nil, errors.Wrap(err, "transfer failed") } // 將轉換結果紀錄 transferResult.UserAssets = append(transferResult.UserAssets, transferResultTwo.UserAssets...) // 如果maker買單數量減至0,則移除訂單 if maker.UnfilledQuantity.IsZero() { if err := c.orderUseCase.RemoveOrder(ctx, maker.ID); err != nil { return nil, errors.Wrap(err, "remove maker order failed, maker order id: "+strconv.Itoa(maker.ID)) } } } // 如果taker買單數量減至0,則移除訂單 if taker.UnfilledQuantity.IsZero() { if err := c.orderUseCase.RemoveOrder(ctx, taker.ID); err != nil { return nil, errors.Wrap(err, "remove taker order failed") } }
最後的TransferResult我們將傳送給下游模組進行處理。