From f09c26c2c04d99e9a91bffa62aebaa8d3ed62a18 Mon Sep 17 00:00:00 2001 From: Charlie <99198652+cdummett@users.noreply.github.com> Date: Fri, 12 Jul 2024 15:52:55 +0100 Subject: [PATCH] feat: capped futures (#682) * feat: implement capped futures * feat: restructure configs for capped futures * fix: correct oracle decimal places * fix: ensure finalise transactions * chore: run make black --- vega_sim/api/market.py | 39 ++++++- vega_sim/builders/governance.py | 2 + vega_sim/builders/markets.py | 15 +++ vega_sim/configs/agents.py | 41 ++++--- vega_sim/configs/research/__init__.py | 4 +- vega_sim/configs/research/fcap/ENGFRA_POS.py | 108 +++++++++++++++++ vega_sim/configs/research/fcap/ENGFRA_RES.py | 109 ++++++++++++++++++ vega_sim/configs/research/fcap/__init__.py | 3 + .../research/{future => futr}/BTCUSDT.py | 0 .../research/{future => futr}/ETHUSDT.py | 0 .../research/{future => futr}/__init__.py | 0 .../research/{perpetual => perp}/BTCUSDT.py | 0 .../research/{perpetual => perp}/ETHUSDT.py | 0 .../research/{perpetual => perp}/__init__.py | 0 vega_sim/environment/environment.py | 8 ++ vega_sim/scenario/benchmark/registry.py | 44 ++----- vega_sim/scenario/fuzzing/registry.py | 20 +++- 17 files changed, 335 insertions(+), 58 deletions(-) create mode 100644 vega_sim/configs/research/fcap/ENGFRA_POS.py create mode 100644 vega_sim/configs/research/fcap/ENGFRA_RES.py create mode 100644 vega_sim/configs/research/fcap/__init__.py rename vega_sim/configs/research/{future => futr}/BTCUSDT.py (100%) rename vega_sim/configs/research/{future => futr}/ETHUSDT.py (100%) rename vega_sim/configs/research/{future => futr}/__init__.py (100%) rename vega_sim/configs/research/{perpetual => perp}/BTCUSDT.py (100%) rename vega_sim/configs/research/{perpetual => perp}/ETHUSDT.py (100%) rename vega_sim/configs/research/{perpetual => perp}/__init__.py (100%) diff --git a/vega_sim/api/market.py b/vega_sim/api/market.py index 11dd34dad..3b63d16d5 100644 --- a/vega_sim/api/market.py +++ b/vega_sim/api/market.py @@ -780,6 +780,7 @@ class FutureProduct(Config): "settlement_data_property": "future.settlement", "trading_termination_property": "future.termination", }, + "cap": None, } } @@ -806,6 +807,7 @@ def load(self, opt: Optional[Union[dict, str]] = None): if config["data_source_spec_binding"] is not None else None ) + self.cap = FutureCap(config["cap"]) if config["cap"] is not None else None def build(self, oracle_pubkey: str): if None in [ @@ -825,9 +827,42 @@ def build(self, oracle_pubkey: str): oracle_pubkey=oracle_pubkey ), data_source_spec_binding=self.data_source_spec_binding.build(), + cap=self.cap.build() if self.cap is not None else None, ) return proto + def is_capped(self): + return self.cap is not None + + def is_binary(self): + if not self.is_capped(): + return False + return self.cap.binary_settlement == True + + +class FutureCap(Config): + OPTS = { + "default": { + "max_price": 1, + "binary_settlement": False, + "fully_collateralised": False, + } + } + + def load(self, opt: Optional[Union[dict, str]] = None): + config = super().load(opt=opt) + + self.max_price = config["max_price"] + self.binary_settlement = config["binary_settlement"] + self.fully_collateralised = config["fully_collateralised"] + + def build(self): + return build.markets.future_cap( + max_price=self.max_price, + binary_settlement=self.binary_settlement, + fully_collateralised=self.fully_collateralised, + ) + class PerpetualProduct(Config): OPTS = { @@ -1069,7 +1104,9 @@ def build(self, oracle_pubkey: str): key=build.data.spec.property_key( name=filter["key"]["name"], type=filter["key"]["type"], - number_decimal_places=filter.get("number_decimal_places", None), + number_decimal_places=filter["key"].get( + "number_decimal_places", None + ), ), conditions=[ build.data.spec.condition( diff --git a/vega_sim/builders/governance.py b/vega_sim/builders/governance.py index 5d3dcbd99..dba68ca01 100644 --- a/vega_sim/builders/governance.py +++ b/vega_sim/builders/governance.py @@ -202,6 +202,7 @@ def future_product( data_source_spec_for_settlement_data: vega_protos.data_source.DataSourceDefinition, data_source_spec_for_trading_termination: vega_protos.data_source.DataSourceDefinition, data_source_spec_binding: vega_protos.markets.DataSourceSpecToFutureBinding, + cap: Optional[vega_protos.markets.FutureCap] = None, ) -> vega_protos.governance.FutureProduct: return vega_protos.governance.FutureProduct( settlement_asset=settlement_asset, @@ -209,6 +210,7 @@ def future_product( data_source_spec_for_settlement_data=data_source_spec_for_settlement_data, data_source_spec_for_trading_termination=data_source_spec_for_trading_termination, data_source_spec_binding=data_source_spec_binding, + cap=cap, ) diff --git a/vega_sim/builders/markets.py b/vega_sim/builders/markets.py index 526083432..130f239a4 100644 --- a/vega_sim/builders/markets.py +++ b/vega_sim/builders/markets.py @@ -27,6 +27,7 @@ def future_product( data_source_spec_for_settlement_data: vega_protos.data_source.DataSourceDefinition, data_source_spec_for_trading_termination: vega_protos.data_source.DataSourceDefinition, data_source_spec_binding: vega_protos.markets.DataSourceSpecToFutureBinding, + cap: Optional[vega_protos.markets.FutureCap] = None, ) -> vega_protos.markets.Future: return vega_protos.markets.Future( settlement_asset=settlement_asset, @@ -34,6 +35,7 @@ def future_product( data_source_spec_for_settlement_data=data_source_spec_for_settlement_data, data_source_spec_for_trading_termination=data_source_spec_for_trading_termination, data_source_spec_binding=data_source_spec_binding, + cap=cap, ) @@ -47,6 +49,19 @@ def data_source_spec_to_future_binding( ) +@raise_custom_build_errors +def future_cap( + max_price: float, + binary_settlement: bool, + fully_collateralised: bool, +) -> vega_protos.markets.FutureCap: + return vega_protos.markets.FutureCap( + max_price=str(max_price), + binary_settlement=binary_settlement, + fully_collateralised=fully_collateralised, + ) + + # TODO: Implement build methods for spot markets @raise_custom_build_errors def spot() -> vega_protos.markets.Spot: diff --git a/vega_sim/configs/agents.py b/vega_sim/configs/agents.py index 81af397e7..54372d2a0 100644 --- a/vega_sim/configs/agents.py +++ b/vega_sim/configs/agents.py @@ -4,6 +4,7 @@ from typing import Optional, Union, Dict, Iterable from vega_sim.api.market import MarketConfig, SpotMarketConfig +from vega_sim.api.helpers import num_from_padded_int from vega_sim.environment.agent import StateAgentWithWallet from vega_sim.network_service import VegaServiceNetwork from vega_sim.null_service import VegaServiceNull @@ -134,24 +135,36 @@ def step(self, vega_state): ) def finalise(self): - if self.is_future: + if self.is_spot: + return + # Submit termination data + self.vega.submit_oracle_data( + key_name=self.key_name, + wallet_name=self.wallet_name, + name=self.termination_oracle, + type=protos.vega.data.v1.spec.PropertyKey.Type.TYPE_BOOLEAN, + value=True, + ) + for name, number_decimal_places in self.data_oracles.items(): + settlement_price = self.price + if self.is_future and self.market_config.instrument.future.is_capped(): + max_price = num_from_padded_int( + self.market_config.instrument.future.cap.max_price, + self.market_config.decimal_places, + ) + settlement_price = min(settlement_price, max_price) + if self.market_config.instrument.future.is_binary(): + normalised_price = settlement_price / max_price + rounded_price = round(normalised_price * 2) / 2 + settlement_price = rounded_price * max_price self.vega.submit_oracle_data( key_name=self.key_name, wallet_name=self.wallet_name, - name=self.termination_oracle, - type=protos.vega.data.v1.spec.PropertyKey.Type.TYPE_BOOLEAN, - value=True, + name=name, + type=protos.vega.data.v1.spec.PropertyKey.Type.TYPE_INTEGER, + value=settlement_price, + decimals=number_decimal_places, ) - if self.is_future or self.is_perpetual: - for name, number_decimal_places in self.data_oracles.items(): - self.vega.submit_oracle_data( - key_name=self.key_name, - wallet_name=self.wallet_name, - name=name, - type=protos.vega.data.v1.spec.PropertyKey.Type.TYPE_INTEGER, - value=self.price, - decimals=number_decimal_places, - ) def __find_or_create_asset(self, symbol: str): # Check if asset exists and create it if not diff --git a/vega_sim/configs/research/__init__.py b/vega_sim/configs/research/__init__.py index 91be46f8d..c942b1b3a 100644 --- a/vega_sim/configs/research/__init__.py +++ b/vega_sim/configs/research/__init__.py @@ -1,3 +1,3 @@ -from . import spot, future, perpetual, ESHRUSDT, HLPUSDT, SPOT +from . import fcap, spot, futr, perp, ESHRUSDT, HLPUSDT, SPOT -__all__ = ["spot", "future", "perpetual", "ESHRUSDT", "HLPUSDT", "SPOT"] +__all__ = ["fcap", "spot", "futr", "perp", "ESHRUSDT", "HLPUSDT", "SPOT"] diff --git a/vega_sim/configs/research/fcap/ENGFRA_POS.py b/vega_sim/configs/research/fcap/ENGFRA_POS.py new file mode 100644 index 000000000..6c5889685 --- /dev/null +++ b/vega_sim/configs/research/fcap/ENGFRA_POS.py @@ -0,0 +1,108 @@ +"""ENGFRA_POS.py + +Market config for a capped future market. Market setup as a market for +taking bets on the final possession for a team in a football game. +""" + +from vega_sim.api.market import MarketConfig + +CONFIG = MarketConfig( + { + "decimalPlaces": "4", + "positionDecimalPlaces": "1", + "tickSize": "1", + "instrument": { + "code": "ENGvFRA-POS/USDT-FCAP", + "name": "Euro 2024 England vs. France (Final Possession) / Tether USD (Capped Future)", + "future": { + "settlementAsset": "bf1e88d19db4b3ca0d1d5bdb73718a01686b18cf731ca26adedf3c8b83802bba", + "quoteName": "USDT", + "dataSourceSpecForSettlementData": { + "external": { + "oracle": { + "signers": [{"pubKey": {"key": None}}], + "filters": [ + { + "key": { + "name": "prices.engfra.value", + "type": "TYPE_INTEGER", + "numberDecimalPlaces": "18", + }, + "conditions": [ + { + "operator": "OPERATOR_GREATER_THAN", + "value": "0", + }, + ], + } + ], + } + } + }, + "dataSourceSpecForTradingTermination": { + "external": { + "oracle": { + "signers": [{"pubKey": {"key": None}}], + "filters": [ + { + "key": { + "name": "termination", + "type": "TYPE_BOOLEAN", + }, + "conditions": [ + {"operator": "OPERATOR_EQUALS", "value": "true"} + ], + } + ], + } + } + }, + "dataSourceSpecBinding": { + "settlementDataProperty": "prices.engfra.value", + "tradingTerminationProperty": "termination", + }, + # "cap": { + # "maxPrice": 1000000, + # "binarySettlement": False, + # "fullyCollateralised": True, + # }, + }, + }, + "metadata": [ + "quote:USDT", + "class:fcap", + "future", + "sector:defi", + ], + "priceMonitoringParameters": {"triggers": []}, + "liquidityMonitoringParameters": { + "targetStakeParameters": {"timeWindow": "3600", "scalingFactor": 0.05}, + "triggeringRatio": "", + "auctionExtension": "0", + }, + "logNormal": { + "riskAversionParameter": 0.000001, + "tau": 0.000003995, + "params": {"mu": 0, "r": 0, "sigma": 1}, + }, + "linearSlippageFactor": "0.001", + "quadraticSlippageFactor": "", + "liquiditySlaParameters": { + "priceRange": "0.03", + "commitmentMinTimeFraction": "0.75", + "performanceHysteresisEpochs": "1", + "slaCompetitionFactor": "0.8", + }, + "liquidityFeeSettings": {"method": "METHOD_MARGINAL_COST"}, + "liquidationStrategy": { + "disposalTimeStep": "1", + "disposalFraction": "1", + "fullDisposalSize": "1000000", + "maxFractionConsumed": "0.1", + "disposalSlippageRange": "0.03", + }, + "markPriceConfiguration": { + "compositePriceType": "COMPOSITE_PRICE_TYPE_LAST_TRADE", + }, + } +) diff --git a/vega_sim/configs/research/fcap/ENGFRA_RES.py b/vega_sim/configs/research/fcap/ENGFRA_RES.py new file mode 100644 index 000000000..f54ef0ece --- /dev/null +++ b/vega_sim/configs/research/fcap/ENGFRA_RES.py @@ -0,0 +1,109 @@ +"""ENGFRA.py + +Market config for a fully collateralised binary settlement, capped +future market. Market setup as a market for taking bets on the result +of a football game. +""" + +from vega_sim.api.market import MarketConfig + +CONFIG = MarketConfig( + { + "decimalPlaces": "4", + "positionDecimalPlaces": "1", + "tickSize": "1", + "instrument": { + "code": "ENGvFRA-RES/USDT-FCAP", + "name": "Euro 2024 England vs. France (Final Result) / Tether USD (Capped Future)", + "future": { + "settlementAsset": "bf1e88d19db4b3ca0d1d5bdb73718a01686b18cf731ca26adedf3c8b83802bba", + "quoteName": "USDT", + "dataSourceSpecForSettlementData": { + "external": { + "oracle": { + "signers": [{"pubKey": {"key": None}}], + "filters": [ + { + "key": { + "name": "prices.engfra.value", + "type": "TYPE_INTEGER", + "numberDecimalPlaces": "18", + }, + "conditions": [ + { + "operator": "OPERATOR_GREATER_THAN", + "value": "0", + }, + ], + } + ], + } + } + }, + "dataSourceSpecForTradingTermination": { + "external": { + "oracle": { + "signers": [{"pubKey": {"key": None}}], + "filters": [ + { + "key": { + "name": "termination", + "type": "TYPE_BOOLEAN", + }, + "conditions": [ + {"operator": "OPERATOR_EQUALS", "value": "true"} + ], + } + ], + } + } + }, + "dataSourceSpecBinding": { + "settlementDataProperty": "prices.engfra.value", + "tradingTerminationProperty": "termination", + }, + "cap": { + "maxPrice": 10000, + "binarySettlement": True, + "fullyCollateralised": True, + }, + }, + }, + "metadata": [ + "quote:USDT", + "class:fcap", + "future", + "sector:defi", + ], + "priceMonitoringParameters": {"triggers": []}, + "liquidityMonitoringParameters": { + "targetStakeParameters": {"timeWindow": "3600", "scalingFactor": 0.05}, + "triggeringRatio": "", + "auctionExtension": "0", + }, + "logNormal": { + "riskAversionParameter": 0.000001, + "tau": 0.000003995, + "params": {"mu": 0, "r": 0, "sigma": 1}, + }, + "linearSlippageFactor": "0.001", + "quadraticSlippageFactor": "", + "liquiditySlaParameters": { + "priceRange": "0.03", + "commitmentMinTimeFraction": "0.75", + "performanceHysteresisEpochs": "1", + "slaCompetitionFactor": "0.8", + }, + "liquidityFeeSettings": {"method": "METHOD_MARGINAL_COST"}, + "liquidationStrategy": { + "disposalTimeStep": "1", + "disposalFraction": "1", + "fullDisposalSize": "1000000", + "maxFractionConsumed": "0.1", + "disposalSlippageRange": "0.03", + }, + "markPriceConfiguration": { + "compositePriceType": "COMPOSITE_PRICE_TYPE_LAST_TRADE", + }, + } +) diff --git a/vega_sim/configs/research/fcap/__init__.py b/vega_sim/configs/research/fcap/__init__.py new file mode 100644 index 000000000..4cbf15348 --- /dev/null +++ b/vega_sim/configs/research/fcap/__init__.py @@ -0,0 +1,3 @@ +from . import ENGFRA_POS, ENGFRA_RES + +__all__ = ["ENGFRA_RES", "ENGFRA_POS"] diff --git a/vega_sim/configs/research/future/BTCUSDT.py b/vega_sim/configs/research/futr/BTCUSDT.py similarity index 100% rename from vega_sim/configs/research/future/BTCUSDT.py rename to vega_sim/configs/research/futr/BTCUSDT.py diff --git a/vega_sim/configs/research/future/ETHUSDT.py b/vega_sim/configs/research/futr/ETHUSDT.py similarity index 100% rename from vega_sim/configs/research/future/ETHUSDT.py rename to vega_sim/configs/research/futr/ETHUSDT.py diff --git a/vega_sim/configs/research/future/__init__.py b/vega_sim/configs/research/futr/__init__.py similarity index 100% rename from vega_sim/configs/research/future/__init__.py rename to vega_sim/configs/research/futr/__init__.py diff --git a/vega_sim/configs/research/perpetual/BTCUSDT.py b/vega_sim/configs/research/perp/BTCUSDT.py similarity index 100% rename from vega_sim/configs/research/perpetual/BTCUSDT.py rename to vega_sim/configs/research/perp/BTCUSDT.py diff --git a/vega_sim/configs/research/perpetual/ETHUSDT.py b/vega_sim/configs/research/perp/ETHUSDT.py similarity index 100% rename from vega_sim/configs/research/perpetual/ETHUSDT.py rename to vega_sim/configs/research/perp/ETHUSDT.py diff --git a/vega_sim/configs/research/perpetual/__init__.py b/vega_sim/configs/research/perp/__init__.py similarity index 100% rename from vega_sim/configs/research/perpetual/__init__.py rename to vega_sim/configs/research/perp/__init__.py diff --git a/vega_sim/environment/environment.py b/vega_sim/environment/environment.py index 573388c34..4828de7e6 100644 --- a/vega_sim/environment/environment.py +++ b/vega_sim/environment/environment.py @@ -289,6 +289,10 @@ def _run( ) for agent in self.agents: agent.finalise() + # Forward a number of transactions so finalise transactions can occur + for _ in range(60): + vega.wait_fn(1) + vega.wait_for_total_catchup() vega.wait_for_core_catchup() vega.wait_for_datanode_sync() @@ -521,6 +525,10 @@ def _run( raise (e) else: logging.warning(msg) + # Forward a number of transactions so finalise transactions can occur + for _ in range(60): + vega.wait_fn(1) + vega.wait_for_total_catchup() if pause_at_completion: input( "Environment run completed. Pausing to allow inspection of state." diff --git a/vega_sim/scenario/benchmark/registry.py b/vega_sim/scenario/benchmark/registry.py index d90911ee3..070de3b3b 100644 --- a/vega_sim/scenario/benchmark/registry.py +++ b/vega_sim/scenario/benchmark/registry.py @@ -137,58 +137,28 @@ ) ], ), - "research-ESHRUSDT": BenchmarkScenario( + "research-fcap-ENGFRA_POS": BenchmarkScenario( block_length_seconds=1, step_length_seconds=30, benchmark_configs=[ BenchmarkConfig( - market_config=configs.research.ESHRUSDT.CONFIG, - initial_price=1, + market_config=configs.research.fcap.ENGFRA_POS.CONFIG, + initial_price=50, annualised_volatility=10, notional_trade_volume=100, - ) - ], - ), - "research": BenchmarkScenario( - block_length_seconds=1, - step_length_seconds=30, - benchmark_configs=[ - BenchmarkConfig( - market_config=configs.research.SPOT.CONFIG, - initial_price=70000, - annualised_volatility=2, - notional_trade_volume=100, - ), - BenchmarkConfig( - market_config=configs.mainnet.EGLPUSDT.CONFIG, - initial_price=70000, - annualised_volatility=2, - notional_trade_volume=100, ), ], ), - "research-HLPUSDT": BenchmarkScenario( + "research-fcap-ENGFRA_RES": BenchmarkScenario( block_length_seconds=1, step_length_seconds=30, benchmark_configs=[ BenchmarkConfig( - market_config=configs.research.HLPUSDT.CONFIG, - initial_price=1, + market_config=configs.research.fcap.ENGFRA_RES.CONFIG, + initial_price=0.5, annualised_volatility=10, notional_trade_volume=100, - ) - ], - ), - "research-SPOT": BenchmarkScenario( - block_length_seconds=1, - step_length_seconds=30, - benchmark_configs=[ - BenchmarkConfig( - market_config=configs.research.SPOT.CONFIG, - initial_price=70000, - annualised_volatility=2, - notional_trade_volume=100, - ) + ), ], ), } diff --git a/vega_sim/scenario/fuzzing/registry.py b/vega_sim/scenario/fuzzing/registry.py index 72d49aa5a..b0e3bc3e4 100644 --- a/vega_sim/scenario/fuzzing/registry.py +++ b/vega_sim/scenario/fuzzing/registry.py @@ -27,29 +27,41 @@ notional_trade_volume=100, ), BenchmarkConfig( - market_config=configs.research.future.ETHUSDT.CONFIG, + market_config=configs.research.futr.ETHUSDT.CONFIG, initial_price=4000, annualised_volatility=2, notional_trade_volume=100, ), BenchmarkConfig( - market_config=configs.research.future.BTCUSDT.CONFIG, + market_config=configs.research.futr.BTCUSDT.CONFIG, initial_price=80000, annualised_volatility=2, notional_trade_volume=100, ), BenchmarkConfig( - market_config=configs.research.perpetual.ETHUSDT.CONFIG, + market_config=configs.research.perp.ETHUSDT.CONFIG, initial_price=4000, annualised_volatility=2, notional_trade_volume=100, ), BenchmarkConfig( - market_config=configs.research.perpetual.BTCUSDT.CONFIG, + market_config=configs.research.perp.BTCUSDT.CONFIG, initial_price=80000, annualised_volatility=2, notional_trade_volume=100, ), + BenchmarkConfig( + market_config=configs.research.fcap.ENGFRA_POS.CONFIG, + initial_price=50, + annualised_volatility=20, + notional_trade_volume=100, + ), + BenchmarkConfig( + market_config=configs.research.fcap.ENGFRA_RES.CONFIG, + initial_price=0.5, + annualised_volatility=20, + notional_trade_volume=100, + ), ], initial_network_parameters={"validators.epoch.length": "10m"}, ),