ddx-python 1.0.5__cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. ddx/.gitignore +1 -0
  2. ddx/__init__.py +58 -0
  3. ddx/_rust/__init__.pyi +2009 -0
  4. ddx/_rust/common/__init__.pyi +17 -0
  5. ddx/_rust/common/accounting.pyi +6 -0
  6. ddx/_rust/common/enums.pyi +3 -0
  7. ddx/_rust/common/requests/__init__.pyi +21 -0
  8. ddx/_rust/common/requests/intents.pyi +19 -0
  9. ddx/_rust/common/specs.pyi +17 -0
  10. ddx/_rust/common/state/__init__.pyi +41 -0
  11. ddx/_rust/common/state/keys.pyi +29 -0
  12. ddx/_rust/common/transactions.pyi +7 -0
  13. ddx/_rust/decimal.pyi +3 -0
  14. ddx/_rust/h256.pyi +3 -0
  15. ddx/_rust.abi3.so +0 -0
  16. ddx/app_config/ethereum/addresses.json +541 -0
  17. ddx/auditor/README.md +32 -0
  18. ddx/auditor/__init__.py +0 -0
  19. ddx/auditor/auditor_driver.py +1034 -0
  20. ddx/auditor/websocket_message.py +54 -0
  21. ddx/common/__init__.py +0 -0
  22. ddx/common/epoch_params.py +28 -0
  23. ddx/common/fill_context.py +144 -0
  24. ddx/common/item_utils.py +38 -0
  25. ddx/common/logging.py +184 -0
  26. ddx/common/market_specs.py +64 -0
  27. ddx/common/trade_mining_params.py +19 -0
  28. ddx/common/transaction_utils.py +85 -0
  29. ddx/common/transactions/__init__.py +0 -0
  30. ddx/common/transactions/advance_epoch.py +91 -0
  31. ddx/common/transactions/advance_settlement_epoch.py +63 -0
  32. ddx/common/transactions/all_price_checkpoints.py +84 -0
  33. ddx/common/transactions/cancel.py +76 -0
  34. ddx/common/transactions/cancel_all.py +88 -0
  35. ddx/common/transactions/complete_fill.py +103 -0
  36. ddx/common/transactions/disaster_recovery.py +97 -0
  37. ddx/common/transactions/event.py +48 -0
  38. ddx/common/transactions/fee_distribution.py +119 -0
  39. ddx/common/transactions/funding.py +294 -0
  40. ddx/common/transactions/futures_expiry.py +123 -0
  41. ddx/common/transactions/genesis.py +108 -0
  42. ddx/common/transactions/inner/__init__.py +0 -0
  43. ddx/common/transactions/inner/adl_outcome.py +25 -0
  44. ddx/common/transactions/inner/fill.py +227 -0
  45. ddx/common/transactions/inner/liquidated_position.py +41 -0
  46. ddx/common/transactions/inner/liquidation_entry.py +41 -0
  47. ddx/common/transactions/inner/liquidation_fill.py +118 -0
  48. ddx/common/transactions/inner/outcome.py +32 -0
  49. ddx/common/transactions/inner/trade_fill.py +125 -0
  50. ddx/common/transactions/insurance_fund_update.py +142 -0
  51. ddx/common/transactions/insurance_fund_withdraw.py +99 -0
  52. ddx/common/transactions/liquidation.py +357 -0
  53. ddx/common/transactions/partial_fill.py +125 -0
  54. ddx/common/transactions/pnl_realization.py +122 -0
  55. ddx/common/transactions/post.py +72 -0
  56. ddx/common/transactions/post_order.py +95 -0
  57. ddx/common/transactions/price_checkpoint.py +96 -0
  58. ddx/common/transactions/signer_registered.py +62 -0
  59. ddx/common/transactions/specs_update.py +61 -0
  60. ddx/common/transactions/strategy_update.py +156 -0
  61. ddx/common/transactions/tradable_product_update.py +98 -0
  62. ddx/common/transactions/trade_mining.py +147 -0
  63. ddx/common/transactions/trader_update.py +105 -0
  64. ddx/common/transactions/withdraw.py +91 -0
  65. ddx/common/transactions/withdraw_ddx.py +74 -0
  66. ddx/common/utils.py +176 -0
  67. ddx/config.py +17 -0
  68. ddx/derivadex_client.py +254 -0
  69. ddx/py.typed +0 -0
  70. ddx/realtime_client/__init__.py +2 -0
  71. ddx/realtime_client/config.py +2 -0
  72. ddx/realtime_client/logs/pytest.log +0 -0
  73. ddx/realtime_client/models/__init__.py +683 -0
  74. ddx/realtime_client/realtime_client.py +567 -0
  75. ddx/rest_client/__init__.py +0 -0
  76. ddx/rest_client/clients/__init__.py +0 -0
  77. ddx/rest_client/clients/base_client.py +60 -0
  78. ddx/rest_client/clients/market_client.py +1241 -0
  79. ddx/rest_client/clients/on_chain_client.py +432 -0
  80. ddx/rest_client/clients/signed_client.py +301 -0
  81. ddx/rest_client/clients/system_client.py +843 -0
  82. ddx/rest_client/clients/trade_client.py +335 -0
  83. ddx/rest_client/constants/__init__.py +0 -0
  84. ddx/rest_client/constants/endpoints.py +67 -0
  85. ddx/rest_client/contracts/__init__.py +0 -0
  86. ddx/rest_client/contracts/checkpoint/__init__.py +560 -0
  87. ddx/rest_client/contracts/ddx/__init__.py +1949 -0
  88. ddx/rest_client/contracts/dummy_token/__init__.py +1014 -0
  89. ddx/rest_client/contracts/i_collateral/__init__.py +1414 -0
  90. ddx/rest_client/contracts/i_stake/__init__.py +696 -0
  91. ddx/rest_client/exceptions/__init__.py +0 -0
  92. ddx/rest_client/exceptions/exceptions.py +32 -0
  93. ddx/rest_client/http/__init__.py +0 -0
  94. ddx/rest_client/http/http_client.py +305 -0
  95. ddx/rest_client/models/__init__.py +0 -0
  96. ddx/rest_client/models/market.py +683 -0
  97. ddx/rest_client/models/signed.py +60 -0
  98. ddx/rest_client/models/system.py +390 -0
  99. ddx/rest_client/models/trade.py +140 -0
  100. ddx/rest_client/utils/__init__.py +0 -0
  101. ddx/rest_client/utils/encryption_utils.py +26 -0
  102. ddx_python-1.0.5.dist-info/METADATA +63 -0
  103. ddx_python-1.0.5.dist-info/RECORD +104 -0
  104. ddx_python-1.0.5.dist-info/WHEEL +4 -0
@@ -0,0 +1,119 @@
1
+ """
2
+ FeeDistribution
3
+ """
4
+
5
+ from attrs import define, field
6
+ from ddx.common.transactions.event import Event
7
+ from ddx._rust.common import TokenSymbol
8
+ from ddx._rust.common.state import DerivadexSMT, EpochMetadata, Trader
9
+ from ddx._rust.common.state.keys import EpochMetadataKey, TraderKey
10
+ from ddx._rust.decimal import Decimal
11
+
12
+
13
+ @define
14
+ class FeeDistribution(Event):
15
+ """
16
+ Defines a FeeDistribution
17
+
18
+ A FeeDistribution is an update to a set of custodians' DDX
19
+ balances.
20
+
21
+ Attributes:
22
+ custodians (list[str]): Operator custodians
23
+ bonds (list[Decimal]): Operator bonds
24
+ submitter (str): Checkpoint submitter address
25
+ epoch_id (int): Epoch id
26
+ request_index (int): Sequenced request index of transaction
27
+ """
28
+
29
+ custodians: list[str] = field(eq=lambda x: set(map(str.lower, x)))
30
+ bonds: list[Decimal] = field(eq=set)
31
+ submitter: str = field(eq=str.lower)
32
+ epoch_id: int
33
+ request_index: int = field(default=-1, eq=False)
34
+
35
+ @classmethod
36
+ def decode_value_into_cls(cls, raw_tx_log_event: dict):
37
+ """
38
+ Decode a raw transaction log event (dict) into a
39
+ FeeDistribution instance.
40
+
41
+ Parameters
42
+ ----------
43
+ raw_tx_log_event : dict
44
+ Raw transaction log event being processed
45
+ """
46
+
47
+ fee_distribution_event = raw_tx_log_event["event"]["c"]
48
+
49
+ return cls(
50
+ fee_distribution_event["custodians"],
51
+ [Decimal(bond) for bond in fee_distribution_event["bonds"]],
52
+ fee_distribution_event["submitter"],
53
+ fee_distribution_event["epochId"],
54
+ raw_tx_log_event["requestIndex"],
55
+ )
56
+
57
+ def process_tx(
58
+ self,
59
+ smt: DerivadexSMT,
60
+ **kwargs,
61
+ ):
62
+ """
63
+ Process a FeeDistribution transaction - there shouldn't be any changes to the SMT
64
+
65
+ Parameters
66
+ ----------
67
+ smt: DerivadexSMT
68
+ DerivaDEX Sparse Merkle Tree
69
+ **kwargs
70
+ Additional args specific to FeeDistribution transactions
71
+ """
72
+
73
+ # Sum and delete all epoch metadatas up to the last checkpoint/fee distribution
74
+ epoch_metadatas = sorted(
75
+ filter(
76
+ lambda epoch_metadata_key: epoch_metadata_key[0].epoch_id
77
+ <= self.epoch_id,
78
+ smt.all_epoch_metadatas(),
79
+ ),
80
+ key=lambda epoch_metadata_key: epoch_metadata_key[0].epoch_id,
81
+ )
82
+ accumulated_ddx = Decimal("0")
83
+ for epoch_metadata_key, epoch_metadata in epoch_metadatas:
84
+ accumulated_ddx += epoch_metadata.ddx_fee_pool
85
+ smt.store_epoch_metadata(epoch_metadata_key, None)
86
+
87
+ if accumulated_ddx != Decimal("0"):
88
+ total_distributed_fees = Decimal("0")
89
+ distro_per_custodian = accumulated_ddx / Decimal(str(len(self.custodians)))
90
+
91
+ for custodian in self.custodians:
92
+ trader_key: TraderKey = TraderKey(custodian)
93
+ trader = smt.trader(trader_key)
94
+ if trader is None:
95
+ # Initialize a new Trader Leaf
96
+ trader = Trader.default()
97
+
98
+ old_balance = trader.avail_ddx_balance
99
+ trader.avail_ddx_balance = (
100
+ old_balance + distro_per_custodian
101
+ ).recorded_amount()
102
+ if trader.avail_ddx_balance != old_balance:
103
+ smt.store_trader(trader_key, trader)
104
+
105
+ total_distributed_fees += trader.avail_ddx_balance - old_balance
106
+
107
+ dust = accumulated_ddx - total_distributed_fees
108
+
109
+ trader_key: TraderKey = TraderKey(self.submitter)
110
+ trader = smt.trader(trader_key)
111
+ if trader is None:
112
+ # Initialize a new Trader Leaf
113
+ trader = Trader.default()
114
+
115
+ old_balance = trader.avail_ddx_balance
116
+ trader.avail_ddx_balance += dust
117
+
118
+ if trader.avail_ddx_balance != old_balance:
119
+ smt.store_trader(trader_key, trader)
@@ -0,0 +1,294 @@
1
+ """
2
+ Funding module
3
+ """
4
+
5
+ import logging
6
+ from typing import Optional
7
+
8
+ import numpy as np
9
+ from attrs import define, field
10
+ from ddx.common.item_utils import update_avail_collateral
11
+ from ddx.common.transaction_utils import get_prices_for_symbol_and_duration
12
+ from ddx.common.transactions.cancel import Cancel
13
+ from ddx.common.transactions.event import Event
14
+ from ddx.common.transactions.inner.adl_outcome import AdlOutcome
15
+ from ddx.common.transactions.inner.liquidated_position import LiquidatedPosition
16
+ from ddx.common.transactions.inner.liquidation_entry import LiquidationEntry
17
+ from ddx.common.transactions.inner.liquidation_fill import LiquidationFill
18
+ from ddx.common.transactions.inner.outcome import Outcome
19
+ from ddx.common.transactions.liquidation import Liquidation
20
+ from ddx._rust.common import ProductSymbol, TokenSymbol
21
+ from ddx._rust.common.enums import OrderSide, PositionSide
22
+ from ddx._rust.common.state import DerivadexSMT
23
+ from ddx._rust.common.state.keys import StrategyKey
24
+ from ddx._rust.decimal import Decimal
25
+
26
+ logger = logging.getLogger(__name__)
27
+
28
+
29
+ def get_funding_rate(
30
+ smt: DerivadexSMT,
31
+ funding_period: int,
32
+ symbol: ProductSymbol,
33
+ ) -> Optional[Decimal]:
34
+ """
35
+ Get the projected funding rate for the upcoming funding
36
+ distribution pay period for a given symbol
37
+
38
+ Parameters
39
+ ----------
40
+ smt: DerivadexSMT
41
+ DerivaDEX Sparse Merkle Tree
42
+ funding_period : int
43
+ Funding period for retrieving price leaves
44
+ symbol : str
45
+ Market symbol
46
+ """
47
+
48
+ if not symbol.is_perpetual():
49
+ return None
50
+
51
+ # Get price leaves for symbol
52
+ prices = get_prices_for_symbol_and_duration(smt, symbol, funding_period)
53
+ logger.debug(f"Last {funding_period} price leaves: {prices}")
54
+
55
+ # Compute average premium rate across all price checkpoints
56
+ avg_premium_rate = np.mean(
57
+ [
58
+ price_value.mark_price_metadata.ema / price_value.index_price
59
+ for _, price_value in prices
60
+ ]
61
+ )
62
+
63
+ # Any values between [-0.0005, 0.0005] => 0
64
+ unclamped_funding_rate = max(Decimal("0.0005"), avg_premium_rate) + min(
65
+ Decimal("-0.0005"), avg_premium_rate
66
+ )
67
+
68
+ # Cap the funding rate bounds to [-0.005, 0.005]
69
+ return min(Decimal("0.005"), max(Decimal("-0.005"), unclamped_funding_rate))
70
+
71
+
72
+ @define
73
+ class Funding(Event):
74
+ """
75
+ Defines a Funding
76
+
77
+ A Funding is when a there is a funding rate distribution.
78
+
79
+ Attributes:
80
+ settlement_epoch_id (int): The epoch id for the funding event.
81
+ liquidation (Liquidation): Liquidations
82
+ time_value (int): Time value
83
+ request_index (int): Sequenced request index of transaction
84
+ """
85
+
86
+ settlement_epoch_id: int
87
+ liquidation: Liquidation
88
+ time_value: int
89
+ request_index: int = field(default=-1, eq=False)
90
+
91
+ @classmethod
92
+ def decode_value_into_cls(cls, raw_tx_log_event: dict):
93
+ """
94
+ Decode a raw transaction log event (dict) into a Funding
95
+ instance.
96
+
97
+ Parameters
98
+ ----------
99
+ raw_tx_log_event : dict
100
+ Raw transaction log event being processed
101
+ """
102
+
103
+ funding_tx_event = raw_tx_log_event["event"]["c"]
104
+ liquidation_tx_event = funding_tx_event["liquidations"]
105
+
106
+ return cls(
107
+ funding_tx_event["settlementEpochId"],
108
+ Liquidation(
109
+ [
110
+ LiquidationEntry(
111
+ liquidation_entry["traderAddress"],
112
+ liquidation_entry["strategyIdHash"],
113
+ [
114
+ Cancel(
115
+ ProductSymbol(canceled_order["symbol"]),
116
+ canceled_order["orderHash"],
117
+ Decimal(canceled_order["amount"]),
118
+ raw_tx_log_event["requestIndex"],
119
+ )
120
+ for canceled_order in liquidation_entry["canceledOrders"]
121
+ ],
122
+ [
123
+ (
124
+ ProductSymbol(liquidated_position_key),
125
+ LiquidatedPosition(
126
+ Decimal(liquidated_position_val["amount"]),
127
+ [
128
+ (
129
+ LiquidationFill(
130
+ ProductSymbol(
131
+ trade_outcome["Fill"]["symbol"]
132
+ ),
133
+ trade_outcome["Fill"]["indexPriceHash"],
134
+ trade_outcome["Fill"]["makerOrderHash"],
135
+ Decimal(
136
+ trade_outcome["Fill"][
137
+ "makerOrderRemainingAmount"
138
+ ]
139
+ ),
140
+ Decimal(
141
+ trade_outcome["Fill"]["amount"]
142
+ ),
143
+ Decimal(trade_outcome["Fill"]["price"]),
144
+ OrderSide(
145
+ trade_outcome["Fill"]["takerSide"]
146
+ ),
147
+ Outcome(
148
+ trade_outcome["Fill"][
149
+ "makerOutcome"
150
+ ]["trader"],
151
+ trade_outcome["Fill"][
152
+ "makerOutcome"
153
+ ]["strategyIdHash"],
154
+ ),
155
+ raw_tx_log_event["timeValue"],
156
+ raw_tx_log_event["requestIndex"],
157
+ )
158
+ if "Fill" in trade_outcome
159
+ else Cancel(
160
+ ProductSymbol(
161
+ trade_outcome["Cancel"]["symbol"]
162
+ ),
163
+ trade_outcome["Cancel"]["orderHash"],
164
+ Decimal(
165
+ trade_outcome["Cancel"]["amount"]
166
+ ),
167
+ raw_tx_log_event["requestIndex"],
168
+ )
169
+ )
170
+ for trade_outcome in liquidated_position_val[
171
+ "tradeOutcomes"
172
+ ]
173
+ ],
174
+ [
175
+ AdlOutcome(
176
+ adl_outcome["traderAddress"],
177
+ adl_outcome["strategyIdHash"],
178
+ raw_tx_log_event["requestIndex"],
179
+ )
180
+ for adl_outcome in liquidated_position_val[
181
+ "adlOutcomes"
182
+ ]
183
+ ],
184
+ Decimal(
185
+ liquidated_position_val["newInsuranceFundCap"]
186
+ ),
187
+ raw_tx_log_event["requestIndex"],
188
+ ),
189
+ )
190
+ for (
191
+ liquidated_position_key,
192
+ liquidated_position_val,
193
+ ) in liquidation_entry["positions"]
194
+ ],
195
+ raw_tx_log_event["requestIndex"],
196
+ )
197
+ for liquidation_entry in liquidation_tx_event
198
+ ],
199
+ raw_tx_log_event["requestIndex"],
200
+ ),
201
+ raw_tx_log_event["timeValue"],
202
+ raw_tx_log_event["requestIndex"],
203
+ )
204
+
205
+ def process_tx(
206
+ self,
207
+ smt: DerivadexSMT,
208
+ **kwargs,
209
+ ):
210
+ """
211
+ Process a Funding transaction. A Funding event consists of
212
+ consists of information relating to funding rate-related
213
+ distributions. All open positions will result in traders
214
+ either paying or receiving a USDC debit/credit to their
215
+ avaiable collateral as a function of the funding rate (given the
216
+ Price leaves in the SMT at this time) and their
217
+ position notional (given the latest mark price).
218
+
219
+ Parameters
220
+ ----------
221
+ smt: DerivadexSMT
222
+ DerivaDEX Sparse Merkle Tree
223
+ **kwargs
224
+ Additional args specific to Funding transactions
225
+ """
226
+
227
+ funding_strategies = {}
228
+
229
+ # Loop through the funding rate symbols and values as specified
230
+ # in the transaction
231
+ for funding_rate_symbol in sorted(kwargs["latest_price_leaves"]):
232
+ funding_rate = get_funding_rate(
233
+ smt, kwargs["funding_period"], funding_rate_symbol
234
+ )
235
+
236
+ if funding_rate is not None and funding_rate != Decimal("0"):
237
+ # If funding rate is non-zero, handle funding payments
238
+
239
+ # Obtain latest mark price
240
+ mark_price = kwargs["latest_price_leaves"][funding_rate_symbol][
241
+ 1
242
+ ].mark_price
243
+
244
+ # Loop through each open position
245
+ for (
246
+ position_key,
247
+ position,
248
+ ) in smt.all_positions_for_symbol(funding_rate_symbol):
249
+ # Compute the funding payment for the trader. When
250
+ # the funding rate is positive, long traders will
251
+ # pay and short traders will receive payments. When
252
+ # the funding rate is negative, long traders will
253
+ # receive payments and short traders will pay.
254
+ funding_delta = (
255
+ (
256
+ Decimal("-1.0")
257
+ if position.side == PositionSide.Long
258
+ else Decimal("1.0")
259
+ )
260
+ * funding_rate
261
+ * position.balance
262
+ * mark_price
263
+ )
264
+
265
+ # Construct a StrategyKey and corresponding encoded
266
+ # key
267
+ strategy_key: StrategyKey = position_key.as_strategy_key()
268
+
269
+ funding_strategies[strategy_key] = (
270
+ funding_strategies[strategy_key] + funding_delta
271
+ if strategy_key in funding_strategies
272
+ else funding_delta
273
+ )
274
+
275
+ for strategy_key, funding_delta in funding_strategies.items():
276
+ strategy = smt.strategy(strategy_key)
277
+
278
+ # Credit/debit the trader's Strategy leaf by the
279
+ # funding delta from above
280
+ update_avail_collateral(
281
+ strategy,
282
+ TokenSymbol.USDC,
283
+ strategy.avail_collateral[TokenSymbol.USDC] + funding_delta,
284
+ )
285
+
286
+ # Update the SMT with the H256 repr of the key and
287
+ # the Strategy leaf
288
+ smt.store_strategy(
289
+ strategy_key,
290
+ strategy,
291
+ )
292
+
293
+ # Process liquidation
294
+ self.liquidation.process_tx(smt, **kwargs)
@@ -0,0 +1,123 @@
1
+ """
2
+ FuturesExpiry module
3
+ """
4
+
5
+ from typing import Optional
6
+
7
+ import numpy as np
8
+ from attrs import define, field
9
+ from ddx.common.logging import auditor_logger
10
+ from ddx.common.transaction_utils import get_prices_for_symbol_and_duration
11
+ from ddx.common.transactions.cancel import Cancel
12
+ from ddx.common.transactions.event import Event
13
+ from ddx.common.transactions.inner.adl_outcome import AdlOutcome
14
+ from ddx.common.transactions.inner.liquidated_position import LiquidatedPosition
15
+ from ddx.common.transactions.inner.liquidation_entry import LiquidationEntry
16
+ from ddx.common.transactions.inner.outcome import Outcome
17
+ from ddx.common.transactions.liquidation import Liquidation
18
+ from ddx._rust.common import ProductSymbol
19
+ from ddx._rust.common.enums import OrderSide
20
+ from ddx._rust.common.specs import Quarter
21
+ from ddx._rust.common.state import DerivadexSMT, Position
22
+ from ddx._rust.common.state.keys import PositionKey, StrategyKey
23
+ from ddx._rust.decimal import Decimal
24
+
25
+ logger = auditor_logger(__name__)
26
+
27
+
28
+ @define(hash=True)
29
+ class FuturesExpiry(Event):
30
+ """
31
+ Defines a FuturesExpiry
32
+
33
+ A FuturesExpiry is when all futures of a fixed duration expire.
34
+ This will result in a credit/debit for all strategies.
35
+
36
+ Attributes:
37
+ settlement_epoch_id (int): Settlement epoch id
38
+ quarter (Quarter): Quarter of the futures expired
39
+ time_value (int): Time value
40
+ request_index (int): Sequenced request index of transaction
41
+ """
42
+
43
+ settlement_epoch_id: int
44
+ quarter: Quarter
45
+ time_value: int
46
+ request_index: int = field(default=-1, eq=False, hash=False)
47
+
48
+ @classmethod
49
+ def decode_value_into_cls(cls, raw_tx_log_event: dict):
50
+ """
51
+ Decode a raw transaction log event (dict) into a FuturesExpiry
52
+ instance.
53
+
54
+ Parameters
55
+ ----------
56
+ raw_tx_log_event : dict
57
+ Raw transaction log event being processed
58
+ """
59
+
60
+ futures_expiry_tx_event = raw_tx_log_event["event"]["c"]
61
+
62
+ return cls(
63
+ futures_expiry_tx_event["settlementEpochId"],
64
+ Quarter(futures_expiry_tx_event["quarter"]),
65
+ raw_tx_log_event["timeValue"],
66
+ raw_tx_log_event["requestIndex"],
67
+ )
68
+
69
+ def process_tx(
70
+ self,
71
+ smt: DerivadexSMT,
72
+ **kwargs,
73
+ ):
74
+ """
75
+ Process a FuturesExpiry transaction. A FuturesExpiry event
76
+ consists of information relating to when unrealized pnl is
77
+ settled/realized for all traders' strategies. This will result
78
+ in a credit/debit for all strategies. Furthermore, the
79
+ average entry price for any open positions will be set to the
80
+ current mark price.
81
+
82
+ Parameters
83
+ ----------
84
+ smt: DerivadexSMT
85
+ DerivaDEX Sparse Merkle Tree
86
+ **kwargs
87
+ Additional args specific to FuturesExpiry transactions
88
+ """
89
+
90
+ # Close all open orders
91
+ book_order_leaves: list[tuple[BookOrderKey, BookOrder]] = [
92
+ (book_order_key, book_order)
93
+ for book_order_key, book_order in smt.all_book_orders()
94
+ if (quarter := book_order_key.symbol.futures_quarter()) is not None
95
+ and quarter == self.quarter
96
+ ]
97
+
98
+ for book_order_key, book_order in book_order_leaves:
99
+ cancel = Cancel(
100
+ book_order_key.symbol,
101
+ book_order_key.order_hash,
102
+ book_order.amount,
103
+ self.request_index,
104
+ )
105
+ cancel.process_tx(smt)
106
+
107
+ # Close all open positions for the expiring quarter
108
+ relevant_positions: list[tuple[PositionKey, Position]] = [
109
+ (position_key, position)
110
+ for position_key, position in sorted(
111
+ smt.all_positions(),
112
+ key=lambda item: item[0].symbol,
113
+ )
114
+ if (quarter := position_key.symbol.futures_quarter()) is not None
115
+ and quarter == self.quarter
116
+ ]
117
+
118
+ for position_key, position in relevant_positions:
119
+ # Set position balances to zero
120
+ position.balance = Decimal(0)
121
+
122
+ # Store the position
123
+ smt.store_position(position_key, position)
@@ -0,0 +1,108 @@
1
+ """
2
+ Genesis module
3
+ """
4
+
5
+ from typing import Optional
6
+
7
+ from attrs import define, field
8
+ from ddx.common.logging import auditor_logger
9
+ from ddx.common.transactions.event import Event
10
+ from ddx._rust.common.specs import SpecsKind
11
+ from ddx._rust.common.state import Balance, DerivadexSMT
12
+ from ddx._rust.common.state.keys import InsuranceFundKey, SpecsKey
13
+ from ddx._rust.decimal import Decimal
14
+
15
+ logger = auditor_logger(__name__)
16
+
17
+
18
+ @define
19
+ class Genesis(Event):
20
+ """
21
+ Defines a Genesis
22
+
23
+ A Genesis is a non-transitioning transaction that indicates
24
+ the start of the first epoch.
25
+
26
+ Attributes:
27
+ state_root_hash (str): State root hash at time of marker
28
+ request_index (int): Sequenced request index of transaction
29
+ """
30
+
31
+ state_root_hash: Optional[str] = field(eq=str.lower)
32
+ request_index: int = field(default=-1, eq=False)
33
+
34
+ @classmethod
35
+ def decode_value_into_cls(cls, raw_tx_log_event: dict):
36
+ """
37
+ Decode a raw transaction log event (dict) into a Genesis
38
+ instance.
39
+
40
+ Parameters
41
+ ----------
42
+ raw_tx_log_event : dict
43
+ Raw transaction log event being processed
44
+ """
45
+
46
+ genesis_tx_event = raw_tx_log_event["event"]["c"]
47
+
48
+ return cls(
49
+ genesis_tx_event.get("stateRootHash"),
50
+ raw_tx_log_event["requestIndex"],
51
+ )
52
+
53
+ def process_tx(
54
+ self,
55
+ **kwargs,
56
+ ):
57
+ """
58
+ Process an EpochMarker transaction of type Genesis. This
59
+ indicates the very first event in the transaction log, although
60
+ it is not state-transitioning in the way typical transactions
61
+ are.
62
+
63
+ Parameters
64
+ ----------
65
+ **kwargs
66
+ Additional args specific to Genesis transactions
67
+ """
68
+
69
+ if (
70
+ collaterals := kwargs["genesis_params"]["Genesis"].get("insuranceFundCap")
71
+ ) is not None:
72
+ insurance_fund_balance = Balance.new_from_many(collaterals)
73
+ else:
74
+ insurance_fund_balance = Balance.default()
75
+
76
+ ddx_fee_pool = kwargs["genesis_params"]["Genesis"].get("ddxFeePool")
77
+ if ddx_fee_pool is None:
78
+ ddx_fee_pool = Decimal("0")
79
+
80
+ specs = {}
81
+ for key, val in kwargs["genesis_params"]["Genesis"]["specs"].items():
82
+ if key.startswith("SINGLENAMEPERP"):
83
+ kind = SpecsKind.SingleNamePerpetual
84
+ elif key.startswith("INDEXFUNDPERP"):
85
+ kind = SpecsKind.IndexFundPerpetual
86
+ elif key.startswith("QUARTERLYEXPIRYFUTURE"):
87
+ kind = SpecsKind.QuarterlyExpiryFuture
88
+ elif key.startswith("GATEWAY"):
89
+ kind = SpecsKind.MarketGateway
90
+ else:
91
+ raise Exception("Invalid spec in Genesis params")
92
+
93
+ specs[SpecsKey(kind, key.split("-")[1])] = val
94
+
95
+ logger.info(
96
+ f"Initializing SMT from genesis:\n\tInsurance fund: {insurance_fund_balance}\n\tDDX fee pool: {ddx_fee_pool}\n\tSpecs: {specs}\n\tCurrent datetime: {kwargs['current_time']}"
97
+ )
98
+ smt = DerivadexSMT.from_genesis(
99
+ insurance_fund_balance, ddx_fee_pool, specs, kwargs["current_time"]
100
+ )
101
+ kwargs["smt"](kwargs["auditor_instance"], smt)
102
+
103
+ # Set the expected epoch ID to be 1 and the expected tx ordinal
104
+ # to be -1, because we immediately increment this by 1, thus
105
+ # setting it to 0, which will be the first tx ordinal of the
106
+ # next epoch
107
+ kwargs["expected_epoch_id"](kwargs["auditor_instance"], 1)
108
+ kwargs["expected_tx_ordinal"](kwargs["auditor_instance"], -1)
File without changes
@@ -0,0 +1,25 @@
1
+ """
2
+ AdlOutcome module
3
+ """
4
+
5
+ from attrs import define, field
6
+ from ddx._rust.decimal import Decimal
7
+
8
+
9
+ @define(hash=True)
10
+ class AdlOutcome:
11
+ """
12
+ Defines an AdlOutcome
13
+
14
+ An AdlOutcome is a scenario where a strategy has been auto-deleveraged
15
+ due to a liquidation.
16
+
17
+ Attributes:
18
+ trader_address (str): Auto-deleveraged trader's ethereum address
19
+ strategy_id_hash (str): Auto-deleveraged strategy ID hash
20
+ request_index (int): Sequenced request index of transaction
21
+ """
22
+
23
+ trader_address: str = field(eq=str.lower)
24
+ strategy_id_hash: str = field(eq=str.lower)
25
+ request_index: int = field(default=-1, eq=False, hash=False)