code.vegaprotocol.io/vega@v0.79.0/core/products/future.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 products
    17  
    18  import (
    19  	"context"
    20  
    21  	"code.vegaprotocol.io/vega/core/datasource"
    22  	dscommon "code.vegaprotocol.io/vega/core/datasource/common"
    23  	"code.vegaprotocol.io/vega/core/datasource/spec"
    24  	"code.vegaprotocol.io/vega/core/types"
    25  	"code.vegaprotocol.io/vega/libs/num"
    26  	"code.vegaprotocol.io/vega/logging"
    27  	datapb "code.vegaprotocol.io/vega/protos/vega/data/v1"
    28  	snapshotpb "code.vegaprotocol.io/vega/protos/vega/snapshot/v1"
    29  
    30  	"github.com/pkg/errors"
    31  )
    32  
    33  var (
    34  	// ErrDataSourceSpecAndBindingAreRequired is returned when the definition of the
    35  	// data source spec or its binding is missing from the future definition.
    36  	ErrDataSourceSpecAndBindingAreRequired = errors.New("a data source spec and spec binding are required")
    37  
    38  	// ErrDataSourceSettlementDataNotSet is returned when the data source has not set the settlement data.
    39  	ErrDataSourceSettlementDataNotSet = errors.New("settlement data is not set")
    40  
    41  	// ErrSettlementDataDecimalsNotSupportedByAsset is returned when the decimal data decimal places
    42  	// are more than the asset decimals.
    43  	ErrSettlementDataDecimalsNotSupportedByAsset = errors.New("settlement data decimals not suported by market asset")
    44  )
    45  
    46  // Future represent a Future as describe by the market framework.
    47  type Future struct {
    48  	log                        *logging.Logger
    49  	SettlementAsset            string
    50  	QuoteName                  string
    51  	oracle                     terminatingOracle
    52  	tradingTerminationListener func(context.Context, bool)
    53  	settlementDataListener     func(context.Context, *num.Numeric)
    54  	assetDP                    uint32
    55  }
    56  
    57  func (_ Future) GetCurrentPeriod() uint64 { return 0 }
    58  
    59  func (f *Future) UnsubscribeTradingTerminated(ctx context.Context) {
    60  	f.log.Info("unsubscribed trading terminated for", logging.String("quote-name", f.QuoteName))
    61  	f.oracle.unsubTerm(ctx)
    62  }
    63  
    64  func (f *Future) UnsubscribeSettlementData(ctx context.Context) {
    65  	f.log.Info("unsubscribed trading settlement data for", logging.String("quote-name", f.QuoteName))
    66  	f.oracle.unsubSettle(ctx)
    67  }
    68  
    69  func (f *Future) Unsubscribe(ctx context.Context) {
    70  	f.UnsubscribeTradingTerminated(ctx)
    71  	f.UnsubscribeSettlementData(ctx)
    72  }
    73  
    74  func (f *Future) SubmitDataPoint(_ context.Context, _ *num.Uint, _ int64) error {
    75  	return nil
    76  }
    77  
    78  func (f *Future) UpdateAuctionState(_ context.Context, _ bool) {
    79  }
    80  
    81  func (f *Future) GetMarginIncrease(_ int64) num.Decimal {
    82  	return num.DecimalZero()
    83  }
    84  
    85  func (f *Future) NotifyOnDataSourcePropagation(listener func(context.Context, *num.Uint)) {
    86  	f.log.Panic("not implemented")
    87  }
    88  
    89  func (f *Future) NotifyOnSettlementData(listener func(context.Context, *num.Numeric)) {
    90  	f.settlementDataListener = listener
    91  }
    92  
    93  func (f *Future) NotifyOnTradingTerminated(listener func(context.Context, bool)) {
    94  	f.tradingTerminationListener = listener
    95  }
    96  
    97  func (f *Future) RestoreSettlementData(settleData *num.Numeric) {
    98  	f.oracle.data.settlData = settleData
    99  }
   100  
   101  func (f *Future) ScaleSettlementDataToDecimalPlaces(price *num.Numeric, dp uint32) (*num.Uint, error) {
   102  	if !price.SupportDecimalPlaces(int64(dp)) {
   103  		return nil, ErrSettlementDataDecimalsNotSupportedByAsset
   104  	}
   105  
   106  	settlDataDecimals := int64(f.oracle.binding.settlementDecimals)
   107  	return price.ScaleTo(settlDataDecimals, int64(dp))
   108  }
   109  
   110  // Settle a position against the future.
   111  func (f *Future) Settle(entryPriceInAsset, settlementData *num.Uint, netFractionalPosition num.Decimal) (amt *types.FinancialAmount, neg bool, rounding num.Decimal, err error) {
   112  	amount, neg := settlementData.Delta(settlementData, entryPriceInAsset)
   113  	// Make sure net position is positive
   114  	if netFractionalPosition.IsNegative() {
   115  		netFractionalPosition = netFractionalPosition.Neg()
   116  		neg = !neg
   117  	}
   118  
   119  	if f.log.IsDebug() {
   120  		f.log.Debug("settlement",
   121  			logging.String("entry-price-in-asset", entryPriceInAsset.String()),
   122  			logging.String("settlement-data-in-asset", settlementData.String()),
   123  			logging.String("net-fractional-position", netFractionalPosition.String()),
   124  			logging.String("amount-in-decimal", netFractionalPosition.Mul(amount.ToDecimal()).String()),
   125  			logging.String("amount-in-uint", amount.String()),
   126  		)
   127  	}
   128  	a, rem := num.UintFromDecimalWithFraction(netFractionalPosition.Mul(amount.ToDecimal()))
   129  
   130  	return &types.FinancialAmount{
   131  		Asset:  f.SettlementAsset,
   132  		Amount: a,
   133  	}, neg, rem, nil
   134  }
   135  
   136  // Value - returns the nominal value of a unit given a current mark price.
   137  func (f *Future) Value(markPrice *num.Uint) (*num.Uint, error) {
   138  	return markPrice.Clone(), nil
   139  }
   140  
   141  // IsTradingTerminated - returns true when the oracle has signalled terminated market.
   142  func (f *Future) IsTradingTerminated() bool {
   143  	return f.oracle.data.IsTradingTerminated()
   144  }
   145  
   146  // GetAsset return the asset used by the future.
   147  func (f *Future) GetAsset() string {
   148  	return f.SettlementAsset
   149  }
   150  
   151  func (f *Future) updateTradingTerminated(ctx context.Context, data dscommon.Data) error {
   152  	if f.log.GetLevel() == logging.DebugLevel {
   153  		f.log.Debug("new oracle data received", data.Debug()...)
   154  	}
   155  
   156  	tradingTerminated, err := data.GetBoolean(f.oracle.binding.terminationProperty)
   157  
   158  	return f.setTradingTerminated(ctx, tradingTerminated, err)
   159  }
   160  
   161  func (f *Future) updateTradingTerminatedByTimestamp(ctx context.Context, data dscommon.Data) error {
   162  	if f.log.GetLevel() == logging.DebugLevel {
   163  		f.log.Debug("new oracle data received", data.Debug()...)
   164  	}
   165  
   166  	var tradingTerminated bool
   167  	var err error
   168  
   169  	if _, err = data.GetTimestamp(spec.BuiltinTimestamp); err == nil {
   170  		// we have received a trading termination timestamp from the internal vega time oracle
   171  		tradingTerminated = true
   172  	}
   173  
   174  	return f.setTradingTerminated(ctx, tradingTerminated, err)
   175  }
   176  
   177  func (f *Future) setTradingTerminated(ctx context.Context, tradingTerminated bool, dataErr error) error {
   178  	if dataErr != nil {
   179  		f.log.Error(
   180  			"could not parse the property acting as trading Terminated",
   181  			logging.Error(dataErr),
   182  		)
   183  		return dataErr
   184  	}
   185  
   186  	f.oracle.data.tradingTerminated = tradingTerminated
   187  	if f.tradingTerminationListener != nil {
   188  		f.tradingTerminationListener(ctx, tradingTerminated)
   189  	}
   190  	return nil
   191  }
   192  
   193  func (f *Future) updateSettlementData(ctx context.Context, data dscommon.Data) error {
   194  	if f.log.GetLevel() == logging.DebugLevel {
   195  		f.log.Debug("new oracle data received", data.Debug()...)
   196  	}
   197  
   198  	odata := &oracleData{
   199  		settlData: &num.Numeric{},
   200  	}
   201  	switch f.oracle.binding.settlementType {
   202  	case datapb.PropertyKey_TYPE_DECIMAL:
   203  		settlDataAsDecimal, err := data.GetDecimal(f.oracle.binding.settlementProperty)
   204  		if err != nil {
   205  			f.log.Error(
   206  				"could not parse decimal type property acting as settlement data",
   207  				logging.Error(err),
   208  			)
   209  			return err
   210  		}
   211  
   212  		odata.settlData.SetDecimal(&settlDataAsDecimal)
   213  
   214  	default:
   215  		settlDataAsUint, err := data.GetUint(f.oracle.binding.settlementProperty)
   216  		if err != nil {
   217  			f.log.Error(
   218  				"could not parse integer type property acting as settlement data",
   219  				logging.Error(err),
   220  			)
   221  			return err
   222  		}
   223  
   224  		odata.settlData.SetUint(settlDataAsUint)
   225  	}
   226  
   227  	f.oracle.data.settlData = odata.settlData
   228  	if f.settlementDataListener != nil {
   229  		f.settlementDataListener(ctx, odata.settlData)
   230  	}
   231  
   232  	if f.log.GetLevel() == logging.DebugLevel {
   233  		f.log.Debug(
   234  			"future settlement data updated",
   235  			logging.String("settlementData", f.oracle.data.settlData.String()),
   236  		)
   237  	}
   238  
   239  	return nil
   240  }
   241  
   242  func (f *Future) Serialize() *snapshotpb.Product {
   243  	return &snapshotpb.Product{}
   244  }
   245  
   246  func (f *Future) Update(ctx context.Context, pp interface{}, oe OracleEngine) error {
   247  	ff, ok := pp.(*types.InstrumentFuture)
   248  	if !ok {
   249  		f.log.Panic("attempting to update a future into something else")
   250  	}
   251  
   252  	cfg := ff.Future
   253  
   254  	// unsubscribe the old data sources
   255  	f.oracle.unsubSettle(ctx)
   256  	f.oracle.unsubTerm(ctx)
   257  
   258  	oracle, err := newFutureOracle(cfg)
   259  	if err != nil {
   260  		return err
   261  	}
   262  
   263  	// subscribe to new
   264  	// Oracle spec for settlement data.
   265  	osForSettle, err := spec.New(*datasource.SpecFromDefinition(*cfg.DataSourceSpecForSettlementData.Data))
   266  	if err != nil {
   267  		return err
   268  	}
   269  	osForTerm, err := spec.New(*datasource.SpecFromDefinition(*cfg.DataSourceSpecForTradingTermination.Data))
   270  	if err != nil {
   271  		return err
   272  	}
   273  	tradingTerminationCb := f.updateTradingTerminated
   274  	if oracle.binding.terminationType == datapb.PropertyKey_TYPE_TIMESTAMP {
   275  		tradingTerminationCb = f.updateTradingTerminatedByTimestamp
   276  	}
   277  	if err := oracle.bindAll(ctx, oe, osForSettle, osForTerm, f.updateSettlementData, tradingTerminationCb); err != nil {
   278  		return err
   279  	}
   280  
   281  	f.oracle = oracle
   282  	return nil
   283  }
   284  
   285  func (f *Future) GetData(t int64) *types.ProductData {
   286  	return nil
   287  }
   288  
   289  func NewFuture(ctx context.Context, log *logging.Logger, f *types.Future, oe OracleEngine, assetDP uint32) (*Future, error) {
   290  	if f.DataSourceSpecForSettlementData == nil || f.DataSourceSpecForTradingTermination == nil || f.DataSourceSpecBinding == nil {
   291  		return nil, ErrDataSourceSpecAndBindingAreRequired
   292  	}
   293  
   294  	oracle, err := newFutureOracle(f)
   295  	if err != nil {
   296  		return nil, err
   297  	}
   298  
   299  	future := &Future{
   300  		log:             log,
   301  		SettlementAsset: f.SettlementAsset,
   302  		QuoteName:       f.QuoteName,
   303  		assetDP:         assetDP,
   304  	}
   305  
   306  	// Oracle spec for settlement data.
   307  	osForSettle, err := spec.New(*datasource.SpecFromDefinition(*f.DataSourceSpecForSettlementData.Data))
   308  	if err != nil {
   309  		return nil, err
   310  	}
   311  	osForTerm, err := spec.New(*datasource.SpecFromDefinition(*f.DataSourceSpecForTradingTermination.Data))
   312  	if err != nil {
   313  		return nil, err
   314  	}
   315  	tradingTerminationCb := future.updateTradingTerminated
   316  	if oracle.binding.terminationType == datapb.PropertyKey_TYPE_TIMESTAMP {
   317  		tradingTerminationCb = future.updateTradingTerminatedByTimestamp
   318  	}
   319  	if err := oracle.bindAll(ctx, oe, osForSettle, osForTerm, future.updateSettlementData, tradingTerminationCb); err != nil {
   320  		return nil, err
   321  	}
   322  	future.oracle = oracle // ensure the oracle on future is not an old copy
   323  
   324  	if log.IsDebug() {
   325  		log.Debug("future subscribed to oracle engine for settlement data",
   326  			logging.Uint64("subscription ID", uint64(future.oracle.settlementSubscriptionID)),
   327  		)
   328  		log.Debug("future subscribed to oracle engine for market termination event",
   329  			logging.Uint64("subscription ID", uint64(future.oracle.terminationSubscriptionID)),
   330  		)
   331  	}
   332  
   333  	return future, nil
   334  }