code.vegaprotocol.io/vega@v0.79.0/datanode/entities/margin_levels.go (about)

     1  // Copyright (C) 2023 Gobalsky Labs Limited
     2  //
     3  // This program is free software: you can redistribute it and/or modify
     4  // it under the terms of the GNU Affero General Public License as
     5  // published by the Free Software Foundation, either version 3 of the
     6  // License, or (at your option) any later version.
     7  //
     8  // This program is distributed in the hope that it will be useful,
     9  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    10  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11  // GNU Affero General Public License for more details.
    12  //
    13  // You should have received a copy of the GNU Affero General Public License
    14  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    15  
    16  package entities
    17  
    18  import (
    19  	"context"
    20  	"encoding/json"
    21  	"fmt"
    22  	"time"
    23  
    24  	v2 "code.vegaprotocol.io/vega/protos/data-node/api/v2"
    25  	"code.vegaprotocol.io/vega/protos/vega"
    26  
    27  	"github.com/shopspring/decimal"
    28  )
    29  
    30  type MarginLevels struct {
    31  	AccountID              AccountID
    32  	OrderMarginAccountID   AccountID
    33  	MaintenanceMargin      decimal.Decimal
    34  	SearchLevel            decimal.Decimal
    35  	InitialMargin          decimal.Decimal
    36  	CollateralReleaseLevel decimal.Decimal
    37  	OrderMargin            decimal.Decimal
    38  	Timestamp              time.Time
    39  	TxHash                 TxHash
    40  	VegaTime               time.Time
    41  	MarginMode             MarginMode
    42  	MarginFactor           decimal.Decimal
    43  }
    44  
    45  func MarginLevelsFromProto(ctx context.Context, margin *vega.MarginLevels, accountSource AccountSource, txHash TxHash, vegaTime time.Time) (MarginLevels, error) {
    46  	var (
    47  		maintenanceMargin, searchLevel, initialMargin, collateralReleaseLevel, orderMargin decimal.Decimal
    48  		err                                                                                error
    49  	)
    50  	marginFactor := decimal.NewFromInt32(0)
    51  	if len(margin.MarginFactor) > 0 {
    52  		marginFactor, err = decimal.NewFromString(margin.MarginFactor)
    53  		if err != nil {
    54  			return MarginLevels{}, fmt.Errorf("failed to obtain margin factor for margin level: %w", err)
    55  		}
    56  	}
    57  	marginMode := MarginModeCrossMargin
    58  	if margin.MarginMode == vega.MarginMode_MARGIN_MODE_ISOLATED_MARGIN {
    59  		marginMode = MarginMode(margin.MarginMode)
    60  	}
    61  
    62  	marginAccount, err := GetAccountFromMarginLevel(ctx, margin, accountSource, txHash, vegaTime)
    63  	if err != nil {
    64  		return MarginLevels{}, fmt.Errorf("failed to obtain account for margin level: %w", err)
    65  	}
    66  
    67  	orderMarginAccount, err := GetAccountFromOrderMarginLevel(ctx, margin, accountSource, txHash, vegaTime)
    68  	var orderMarginAccountID AccountID
    69  	if margin.MarginMode == vega.MarginMode_MARGIN_MODE_ISOLATED_MARGIN && err != nil {
    70  		return MarginLevels{}, fmt.Errorf("failed to obtain account for order margin level: %w", err)
    71  	} else {
    72  		orderMarginAccountID = orderMarginAccount.ID
    73  	}
    74  
    75  	if maintenanceMargin, err = decimal.NewFromString(margin.MaintenanceMargin); err != nil {
    76  		return MarginLevels{}, fmt.Errorf("invalid maintenance margin: %w", err)
    77  	}
    78  
    79  	if searchLevel, err = decimal.NewFromString(margin.SearchLevel); err != nil {
    80  		return MarginLevels{}, fmt.Errorf("invalid search level: %w", err)
    81  	}
    82  
    83  	if initialMargin, err = decimal.NewFromString(margin.InitialMargin); err != nil {
    84  		return MarginLevels{}, fmt.Errorf("invalid initial margin: %w", err)
    85  	}
    86  
    87  	if collateralReleaseLevel, err = decimal.NewFromString(margin.CollateralReleaseLevel); err != nil {
    88  		return MarginLevels{}, fmt.Errorf("invalid collateralReleaseLevel: %w", err)
    89  	}
    90  
    91  	if len(margin.OrderMargin) == 0 {
    92  		orderMargin = decimal.NewFromInt32(0)
    93  	} else if orderMargin, err = decimal.NewFromString(margin.OrderMargin); err != nil {
    94  		return MarginLevels{}, fmt.Errorf("invalid orderMarginLevel: %w", err)
    95  	}
    96  
    97  	return MarginLevels{
    98  		AccountID:              marginAccount.ID,
    99  		OrderMarginAccountID:   orderMarginAccountID,
   100  		MaintenanceMargin:      maintenanceMargin,
   101  		SearchLevel:            searchLevel,
   102  		InitialMargin:          initialMargin,
   103  		CollateralReleaseLevel: collateralReleaseLevel,
   104  		OrderMargin:            orderMargin,
   105  		Timestamp:              time.Unix(0, vegaTime.UnixNano()),
   106  		TxHash:                 txHash,
   107  		VegaTime:               vegaTime,
   108  		MarginMode:             marginMode,
   109  		MarginFactor:           marginFactor,
   110  	}, nil
   111  }
   112  
   113  func GetAccountFromMarginLevel(ctx context.Context, margin *vega.MarginLevels, accountSource AccountSource, txHash TxHash, vegaTime time.Time) (Account, error) {
   114  	marginAccount := Account{
   115  		ID:       "",
   116  		PartyID:  PartyID(margin.PartyId),
   117  		AssetID:  AssetID(margin.Asset),
   118  		MarketID: MarketID(margin.MarketId),
   119  		Type:     vega.AccountType_ACCOUNT_TYPE_MARGIN,
   120  		TxHash:   txHash,
   121  		VegaTime: vegaTime,
   122  	}
   123  
   124  	err := accountSource.Obtain(ctx, &marginAccount)
   125  	return marginAccount, err
   126  }
   127  
   128  func GetAccountFromOrderMarginLevel(ctx context.Context, margin *vega.MarginLevels, accountSource AccountSource, txHash TxHash, vegaTime time.Time) (Account, error) {
   129  	orderMarginAccount := Account{
   130  		ID:       "",
   131  		PartyID:  PartyID(margin.PartyId),
   132  		AssetID:  AssetID(margin.Asset),
   133  		MarketID: MarketID(margin.MarketId),
   134  		Type:     vega.AccountType_ACCOUNT_TYPE_ORDER_MARGIN,
   135  		TxHash:   txHash,
   136  		VegaTime: vegaTime,
   137  	}
   138  
   139  	err := accountSource.Obtain(ctx, &orderMarginAccount)
   140  	return orderMarginAccount, err
   141  }
   142  
   143  func (ml *MarginLevels) ToProto(ctx context.Context, accountSource AccountSource) (*vega.MarginLevels, error) {
   144  	marginAccount, err := accountSource.GetByID(ctx, ml.AccountID)
   145  	if err != nil {
   146  		return nil, fmt.Errorf("getting from account for transfer proto:%w", err)
   147  	}
   148  
   149  	return &vega.MarginLevels{
   150  		MaintenanceMargin:      ml.MaintenanceMargin.String(),
   151  		SearchLevel:            ml.SearchLevel.String(),
   152  		InitialMargin:          ml.InitialMargin.String(),
   153  		CollateralReleaseLevel: ml.CollateralReleaseLevel.String(),
   154  		OrderMargin:            ml.OrderMargin.String(),
   155  		PartyId:                marginAccount.PartyID.String(),
   156  		MarketId:               marginAccount.MarketID.String(),
   157  		Asset:                  marginAccount.AssetID.String(),
   158  		Timestamp:              ml.Timestamp.UnixNano(),
   159  		MarginMode:             vega.MarginMode(ml.MarginMode),
   160  		MarginFactor:           ml.MarginFactor.String(),
   161  	}, nil
   162  }
   163  
   164  func (ml MarginLevels) Cursor() *Cursor {
   165  	cursor := MarginCursor{
   166  		VegaTime:  ml.VegaTime,
   167  		AccountID: ml.AccountID,
   168  	}
   169  	return NewCursor(cursor.String())
   170  }
   171  
   172  func (ml MarginLevels) ToProtoEdge(input ...any) (*v2.MarginEdge, error) {
   173  	if len(input) != 2 {
   174  		return nil, fmt.Errorf("expected account source and context argument")
   175  	}
   176  
   177  	ctx, ok := input[0].(context.Context)
   178  	if !ok {
   179  		return nil, fmt.Errorf("first argument must be a context.Context, got: %v", input[0])
   180  	}
   181  
   182  	as, ok := input[1].(AccountSource)
   183  	if !ok {
   184  		return nil, fmt.Errorf("second argument must be an AccountSource, got: %v", input[1])
   185  	}
   186  
   187  	mlProto, err := ml.ToProto(ctx, as)
   188  	if err != nil {
   189  		return nil, err
   190  	}
   191  
   192  	return &v2.MarginEdge{
   193  		Node:   mlProto,
   194  		Cursor: ml.Cursor().Encode(),
   195  	}, nil
   196  }
   197  
   198  type MarginLevelsKey struct {
   199  	AccountID AccountID
   200  	VegaTime  time.Time
   201  }
   202  
   203  func (ml MarginLevels) Key() MarginLevelsKey {
   204  	return MarginLevelsKey{ml.AccountID, ml.VegaTime}
   205  }
   206  
   207  func (ml MarginLevels) ToRow() []interface{} {
   208  	return []interface{}{
   209  		ml.AccountID, ml.OrderMarginAccountID, ml.Timestamp, ml.MaintenanceMargin,
   210  		ml.SearchLevel, ml.InitialMargin, ml.CollateralReleaseLevel, ml.OrderMargin, ml.TxHash, ml.VegaTime,
   211  		ml.MarginMode, ml.MarginFactor,
   212  	}
   213  }
   214  
   215  var MarginLevelsColumns = []string{
   216  	"account_id", "order_margin_account_id", "timestamp", "maintenance_margin",
   217  	"search_level", "initial_margin", "collateral_release_level", "order_margin", "tx_hash",
   218  	"vega_time", "margin_mode", "margin_factor",
   219  }
   220  
   221  type MarginCursor struct {
   222  	VegaTime  time.Time
   223  	AccountID AccountID
   224  }
   225  
   226  func (mc MarginCursor) String() string {
   227  	bs, err := json.Marshal(mc)
   228  	if err != nil {
   229  		// This should never happen
   230  		panic(fmt.Errorf("failed to marshal margin cursor: %w", err))
   231  	}
   232  	return string(bs)
   233  }
   234  
   235  func (mc *MarginCursor) Parse(cursorString string) error {
   236  	if cursorString == "" {
   237  		return nil
   238  	}
   239  	return json.Unmarshal([]byte(cursorString), mc)
   240  }