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 }