ddx-python 1.0.4__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 (106) hide show
  1. ddx/.gitignore +1 -0
  2. ddx/__init__.py +58 -0
  3. ddx/_rust/__init__.pyi +2685 -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 +23 -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 +526 -0
  17. ddx/auditor/README.md +32 -0
  18. ddx/auditor/__init__.py +0 -0
  19. ddx/auditor/auditor_driver.py +1043 -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 +141 -0
  24. ddx/common/logging.py +184 -0
  25. ddx/common/market_aware_account.py +259 -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 +96 -0
  37. ddx/common/transactions/event.py +48 -0
  38. ddx/common/transactions/fee_distribution.py +119 -0
  39. ddx/common/transactions/funding.py +292 -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 +232 -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 +292 -0
  50. ddx/common/transactions/insurance_fund_update.py +138 -0
  51. ddx/common/transactions/insurance_fund_withdraw.py +100 -0
  52. ddx/common/transactions/liquidation.py +353 -0
  53. ddx/common/transactions/partial_fill.py +125 -0
  54. ddx/common/transactions/pnl_realization.py +120 -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 +97 -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 +158 -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 +131 -0
  64. ddx/common/transactions/withdraw.py +90 -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 +270 -0
  69. ddx/models/__init__.py +0 -0
  70. ddx/models/base.py +132 -0
  71. ddx/py.typed +0 -0
  72. ddx/realtime_client/__init__.py +2 -0
  73. ddx/realtime_client/config.py +2 -0
  74. ddx/realtime_client/models/__init__.py +611 -0
  75. ddx/realtime_client/realtime_client.py +646 -0
  76. ddx/rest_client/__init__.py +0 -0
  77. ddx/rest_client/clients/__init__.py +0 -0
  78. ddx/rest_client/clients/base_client.py +60 -0
  79. ddx/rest_client/clients/market_client.py +1243 -0
  80. ddx/rest_client/clients/on_chain_client.py +439 -0
  81. ddx/rest_client/clients/signed_client.py +292 -0
  82. ddx/rest_client/clients/system_client.py +843 -0
  83. ddx/rest_client/clients/trade_client.py +357 -0
  84. ddx/rest_client/constants/__init__.py +0 -0
  85. ddx/rest_client/constants/endpoints.py +66 -0
  86. ddx/rest_client/contracts/__init__.py +0 -0
  87. ddx/rest_client/contracts/checkpoint/__init__.py +560 -0
  88. ddx/rest_client/contracts/ddx/__init__.py +1949 -0
  89. ddx/rest_client/contracts/dummy_token/__init__.py +1014 -0
  90. ddx/rest_client/contracts/i_collateral/__init__.py +1414 -0
  91. ddx/rest_client/contracts/i_stake/__init__.py +696 -0
  92. ddx/rest_client/exceptions/__init__.py +0 -0
  93. ddx/rest_client/exceptions/exceptions.py +32 -0
  94. ddx/rest_client/http/__init__.py +0 -0
  95. ddx/rest_client/http/http_client.py +336 -0
  96. ddx/rest_client/models/__init__.py +0 -0
  97. ddx/rest_client/models/market.py +693 -0
  98. ddx/rest_client/models/signed.py +61 -0
  99. ddx/rest_client/models/system.py +311 -0
  100. ddx/rest_client/models/trade.py +185 -0
  101. ddx/rest_client/utils/__init__.py +0 -0
  102. ddx/rest_client/utils/encryption_utils.py +26 -0
  103. ddx/utils/__init__.py +0 -0
  104. ddx_python-1.0.4.dist-info/METADATA +63 -0
  105. ddx_python-1.0.4.dist-info/RECORD +106 -0
  106. ddx_python-1.0.4.dist-info/WHEEL +5 -0
@@ -0,0 +1,96 @@
1
+ """
2
+ DisasterRecovery module
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
9
+ from ddx._rust.common.state.keys import StrategyKey
10
+ from ddx._rust.decimal import Decimal
11
+
12
+
13
+ @define
14
+ class DisasterRecovery(Event):
15
+ """
16
+ Defines a DisasterRecovery
17
+
18
+ A DisasterRecovery is when the system is wound down in an extreme recovery scenario.
19
+
20
+ Attributes:
21
+ request_index (int): Sequenced request index of transaction
22
+ """
23
+
24
+ request_index: int = field(default=-1, eq=False)
25
+
26
+ @classmethod
27
+ def decode_value_into_cls(cls, raw_tx_log_event: dict):
28
+ """
29
+ Decode a raw transaction log event (dict) into a DisasterRecovery
30
+ instance.
31
+
32
+ Parameters
33
+ ----------
34
+ raw_tx_log_event : dict
35
+ Raw transaction log event being processed
36
+ """
37
+
38
+ return cls(
39
+ raw_tx_log_event["requestIndex"],
40
+ )
41
+
42
+ def process_tx(
43
+ self,
44
+ smt: DerivadexSMT,
45
+ **kwargs,
46
+ ):
47
+ """
48
+ Process a DisasterRecovery transaction.
49
+
50
+ Parameters
51
+ ----------
52
+ smt: DerivadexSMT
53
+ DerivaDEX Sparse Merkle Tree
54
+ **kwargs
55
+ Additional args specific to DisasterRecovery transactions
56
+ """
57
+
58
+ raise NotImplementedError()
59
+
60
+ # sorted_positions = sorted(
61
+ # smt.all_positions(),
62
+ # key=lambda item: item[1].unrealized_pnl(
63
+ # kwargs["latest_price_leaves"][item[0].symbol][1].mark_price
64
+ # ),
65
+ # reverse=True,
66
+ # )
67
+ #
68
+ # for position_key, position in sorted_positions:
69
+ # unrealized_pnl = position.unrealized_pnl(
70
+ # kwargs["latest_price_leaves"][position_key.symbol][1].mark_price
71
+ # )
72
+ #
73
+ # strategy_key: StrategyKey = position_key.as_strategy_key()
74
+ # # Get the Strategy leaf given the key from above
75
+ # strategy = smt.strategy(strategy_key)
76
+ #
77
+ # # Credit/debit the trader's Strategy leaf by the
78
+ # # unrealized PNL to settle
79
+ # update_avail_collateral(
80
+ # strategy,
81
+ # TokenSymbol.USDC,
82
+ # strategy.avail_collateral[TokenSymbol.USDC] + unrealized_pnl,
83
+ # )
84
+ #
85
+ # smt.store_strategy(
86
+ # strategy_key,
87
+ # strategy,
88
+ # )
89
+ #
90
+ # smt.store_position(
91
+ # position_key,
92
+ # None,
93
+ # )
94
+ #
95
+ # for book_order_key, _ in smt.all_book_orders():
96
+ # smt.store_book_order_by_key(book_order_key, None)
@@ -0,0 +1,48 @@
1
+ """
2
+ Event module
3
+ """
4
+
5
+ from abc import ABC, abstractmethod
6
+
7
+ from ddx._rust.common.state import DerivadexSMT
8
+
9
+
10
+ class Event(ABC):
11
+ """
12
+ An Event class from which all Transaction classes inherit
13
+ """
14
+
15
+ @classmethod
16
+ def decode_value_into_cls(cls, raw_tx_log_event: dict):
17
+ """
18
+ Decode a raw transaction log event (dict) into an instance of
19
+ the class.
20
+
21
+ Parameters
22
+ ----------
23
+ raw_tx_log_event : dict
24
+ Raw transaction log event being processed
25
+ """
26
+
27
+ raise NotImplementedError()
28
+
29
+ @abstractmethod
30
+ def process_tx(self, smt: DerivadexSMT, **kwargs):
31
+ """
32
+ Process transaction log event by modifying the SMT state
33
+ and emitting any corresponding events to the Trader when
34
+ appropriate.
35
+
36
+ Parameters
37
+ ----------
38
+ smt: DerivadexSMT
39
+ DerivaDEX Sparse Merkle Tree
40
+ trader_auditor_queue : asyncio.Queue
41
+ Queue for sending events from the Auditor to the Trader
42
+ suppress_trader_queue: bool
43
+ Suppress trader queue messages
44
+ **kwargs
45
+ Additional args specific to various transaction types
46
+ """
47
+
48
+ raise NotImplementedError()
@@ -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,292 @@
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.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.liquidation_fill import LiquidationFill
17
+ from ddx.common.transactions.inner.outcome import Outcome
18
+ from ddx.common.transactions.liquidation import Liquidation
19
+ from ddx._rust.common import ProductSymbol, TokenSymbol
20
+ from ddx._rust.common.enums import OrderSide, PositionSide
21
+ from ddx._rust.common.state import DerivadexSMT
22
+ from ddx._rust.common.state.keys import StrategyKey
23
+ from ddx._rust.decimal import Decimal
24
+
25
+ logger = logging.getLogger(__name__)
26
+
27
+
28
+ def get_funding_rate(
29
+ smt: DerivadexSMT,
30
+ funding_period: int,
31
+ symbol: ProductSymbol,
32
+ ) -> Optional[Decimal]:
33
+ """
34
+ Get the projected funding rate for the upcoming funding
35
+ distribution pay period for a given symbol
36
+
37
+ Parameters
38
+ ----------
39
+ smt: DerivadexSMT
40
+ DerivaDEX Sparse Merkle Tree
41
+ funding_period : int
42
+ Funding period for retrieving price leaves
43
+ symbol : str
44
+ Market symbol
45
+ """
46
+
47
+ if not symbol.is_perpetual():
48
+ return None
49
+
50
+ # Get price leaves for symbol
51
+ prices = get_prices_for_symbol_and_duration(smt, symbol, funding_period)
52
+ logger.debug(f"Last {funding_period} price leaves: {prices}")
53
+
54
+ # Compute average premium rate across all price checkpoints
55
+ avg_premium_rate = np.mean(
56
+ [
57
+ price_value.mark_price_metadata.ema / price_value.index_price
58
+ for _, price_value in prices
59
+ ]
60
+ )
61
+
62
+ # Any values between [-0.0005, 0.0005] => 0
63
+ unclamped_funding_rate = max(Decimal("0.0005"), avg_premium_rate) + min(
64
+ Decimal("-0.0005"), avg_premium_rate
65
+ )
66
+
67
+ # Cap the funding rate bounds to [-0.005, 0.005]
68
+ return min(Decimal("0.005"), max(Decimal("-0.005"), unclamped_funding_rate))
69
+
70
+
71
+ @define
72
+ class Funding(Event):
73
+ """
74
+ Defines a Funding
75
+
76
+ A Funding is when a there is a funding rate distribution.
77
+
78
+ Attributes:
79
+ settlement_epoch_id (int): The epoch id for the funding event.
80
+ liquidation (Liquidation): Liquidations
81
+ time_value (int): Time value
82
+ request_index (int): Sequenced request index of transaction
83
+ """
84
+
85
+ settlement_epoch_id: int
86
+ liquidation: Liquidation
87
+ time_value: int
88
+ request_index: int = field(default=-1, eq=False)
89
+
90
+ @classmethod
91
+ def decode_value_into_cls(cls, raw_tx_log_event: dict):
92
+ """
93
+ Decode a raw transaction log event (dict) into a Funding
94
+ instance.
95
+
96
+ Parameters
97
+ ----------
98
+ raw_tx_log_event : dict
99
+ Raw transaction log event being processed
100
+ """
101
+
102
+ funding_tx_event = raw_tx_log_event["event"]["c"]
103
+ liquidation_tx_event = funding_tx_event["liquidations"]
104
+
105
+ return cls(
106
+ funding_tx_event["settlementEpochId"],
107
+ Liquidation(
108
+ [
109
+ LiquidationEntry(
110
+ liquidation_entry["traderAddress"],
111
+ liquidation_entry["strategyIdHash"],
112
+ [
113
+ Cancel(
114
+ ProductSymbol(canceled_order["symbol"]),
115
+ canceled_order["orderHash"],
116
+ Decimal(canceled_order["amount"]),
117
+ raw_tx_log_event["requestIndex"],
118
+ )
119
+ for canceled_order in liquidation_entry["canceledOrders"]
120
+ ],
121
+ [
122
+ (
123
+ ProductSymbol(liquidated_position_key),
124
+ LiquidatedPosition(
125
+ Decimal(liquidated_position_val["amount"]),
126
+ [
127
+ (
128
+ LiquidationFill(
129
+ ProductSymbol(
130
+ trade_outcome["Fill"]["symbol"]
131
+ ),
132
+ trade_outcome["Fill"]["indexPriceHash"],
133
+ trade_outcome["Fill"]["makerOrderHash"],
134
+ Decimal(
135
+ trade_outcome["Fill"][
136
+ "makerOrderRemainingAmount"
137
+ ]
138
+ ),
139
+ Decimal(
140
+ trade_outcome["Fill"]["amount"]
141
+ ),
142
+ Decimal(trade_outcome["Fill"]["price"]),
143
+ OrderSide(
144
+ trade_outcome["Fill"]["takerSide"]
145
+ ),
146
+ Outcome(
147
+ trade_outcome["Fill"][
148
+ "makerOutcome"
149
+ ]["trader"],
150
+ trade_outcome["Fill"][
151
+ "makerOutcome"
152
+ ]["strategyIdHash"],
153
+ ),
154
+ raw_tx_log_event["timeValue"],
155
+ raw_tx_log_event["requestIndex"],
156
+ )
157
+ if "Fill" in trade_outcome
158
+ else Cancel(
159
+ ProductSymbol(
160
+ trade_outcome["Cancel"]["symbol"]
161
+ ),
162
+ trade_outcome["Cancel"]["orderHash"],
163
+ Decimal(
164
+ trade_outcome["Cancel"]["amount"]
165
+ ),
166
+ raw_tx_log_event["requestIndex"],
167
+ )
168
+ )
169
+ for trade_outcome in liquidated_position_val[
170
+ "tradeOutcomes"
171
+ ]
172
+ ],
173
+ [
174
+ AdlOutcome(
175
+ adl_outcome["traderAddress"],
176
+ adl_outcome["strategyIdHash"],
177
+ raw_tx_log_event["requestIndex"],
178
+ )
179
+ for adl_outcome in liquidated_position_val[
180
+ "adlOutcomes"
181
+ ]
182
+ ],
183
+ Decimal(
184
+ liquidated_position_val["newInsuranceFundCap"]
185
+ ),
186
+ raw_tx_log_event["requestIndex"],
187
+ ),
188
+ )
189
+ for (
190
+ liquidated_position_key,
191
+ liquidated_position_val,
192
+ ) in liquidation_entry["positions"]
193
+ ],
194
+ raw_tx_log_event["requestIndex"],
195
+ )
196
+ for liquidation_entry in liquidation_tx_event
197
+ ],
198
+ raw_tx_log_event["requestIndex"],
199
+ ),
200
+ raw_tx_log_event["timeValue"],
201
+ raw_tx_log_event["requestIndex"],
202
+ )
203
+
204
+ def process_tx(
205
+ self,
206
+ smt: DerivadexSMT,
207
+ **kwargs,
208
+ ):
209
+ """
210
+ Process a Funding transaction. A Funding event consists of
211
+ consists of information relating to funding rate-related
212
+ distributions. All open positions will result in traders
213
+ either paying or receiving a USDC debit/credit to their
214
+ avaiable collateral as a function of the funding rate (given the
215
+ Price leaves in the SMT at this time) and their
216
+ position notional (given the latest mark price).
217
+
218
+ Parameters
219
+ ----------
220
+ smt: DerivadexSMT
221
+ DerivaDEX Sparse Merkle Tree
222
+ **kwargs
223
+ Additional args specific to Funding transactions
224
+ """
225
+
226
+ funding_strategies = {}
227
+
228
+ # Loop through the funding rate symbols and values as specified
229
+ # in the transaction
230
+ for funding_rate_symbol in sorted(kwargs["latest_price_leaves"]):
231
+ funding_rate = get_funding_rate(
232
+ smt, kwargs["funding_period"], funding_rate_symbol
233
+ )
234
+
235
+ if funding_rate is not None and funding_rate != Decimal("0"):
236
+ # If funding rate is non-zero, handle funding payments
237
+
238
+ # Obtain latest mark price
239
+ mark_price = kwargs["latest_price_leaves"][funding_rate_symbol][
240
+ 1
241
+ ].mark_price
242
+
243
+ # Loop through each open position
244
+ for (
245
+ position_key,
246
+ position,
247
+ ) in smt.all_positions_for_symbol(funding_rate_symbol):
248
+ # Compute the funding payment for the trader. When
249
+ # the funding rate is positive, long traders will
250
+ # pay and short traders will receive payments. When
251
+ # the funding rate is negative, long traders will
252
+ # receive payments and short traders will pay.
253
+ funding_delta = (
254
+ (
255
+ Decimal("-1.0")
256
+ if position.side == PositionSide.Long
257
+ else Decimal("1.0")
258
+ )
259
+ * funding_rate
260
+ * position.balance
261
+ * mark_price
262
+ )
263
+
264
+ # Construct a StrategyKey and corresponding encoded
265
+ # key
266
+ strategy_key: StrategyKey = position_key.as_strategy_key()
267
+
268
+ funding_strategies[strategy_key] = (
269
+ funding_strategies[strategy_key] + funding_delta
270
+ if strategy_key in funding_strategies
271
+ else funding_delta
272
+ )
273
+
274
+ for strategy_key, funding_delta in funding_strategies.items():
275
+ strategy = smt.strategy(strategy_key)
276
+
277
+ # Credit/debit the trader's Strategy leaf by the
278
+ # funding delta from above
279
+ strategy.set_avail_collateral(
280
+ TokenSymbol.USDC,
281
+ strategy.avail_collateral[TokenSymbol.USDC] + funding_delta,
282
+ )
283
+
284
+ # Update the SMT with the H256 repr of the key and
285
+ # the Strategy leaf
286
+ smt.store_strategy(
287
+ strategy_key,
288
+ strategy,
289
+ )
290
+
291
+ # Process liquidation
292
+ 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)