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,353 @@
1
+ """
2
+ Liquidation module
3
+ """
4
+
5
+ from attrs import define, field
6
+ from ddx.common.transactions.cancel import Cancel
7
+ from ddx.common.transactions.event import Event
8
+ from ddx.common.transactions.inner.adl_outcome import AdlOutcome
9
+ from ddx.common.transactions.inner.liquidated_position import LiquidatedPosition
10
+ from ddx.common.transactions.inner.liquidation_entry import LiquidationEntry
11
+ from ddx.common.transactions.inner.liquidation_fill import LiquidationFill
12
+ from ddx.common.transactions.inner.outcome import Outcome
13
+ from ddx._rust.common import ProductSymbol, TokenSymbol
14
+ from ddx._rust.common.enums import OrderSide, PositionSide
15
+ from ddx._rust.common.state import (DerivadexSMT, InsuranceFund, Position,
16
+ Price, Strategy)
17
+ from ddx._rust.common.state.keys import (InsuranceFundKey, PositionKey,
18
+ PriceKey, StrategyKey)
19
+ from ddx._rust.decimal import Decimal
20
+
21
+
22
+ def compute_strategy_total_value(
23
+ strategy: Strategy,
24
+ position_leaves: dict[ProductSymbol, Position],
25
+ prices: dict[ProductSymbol, tuple[PriceKey, Price]],
26
+ ):
27
+ # Compute Strategy total value prior to liquidation
28
+ strategy_total_value = strategy.avail_collateral[TokenSymbol.USDC]
29
+
30
+ for symbol in position_leaves:
31
+ # Obtain latest mark price for the symbol
32
+ mark_price = prices[symbol][1].mark_price
33
+
34
+ # Compute unrealized PNL for Position
35
+ unrealized_pnl = position_leaves[symbol].unrealized_pnl(mark_price)
36
+
37
+ # Adjust the Strategy's total value to account for
38
+ # the Position's unrealized pnl
39
+ strategy_total_value += unrealized_pnl
40
+
41
+ return strategy_total_value
42
+
43
+
44
+ @define
45
+ class Liquidation(Event):
46
+ """
47
+ Defines an Liquidation
48
+
49
+ A Liquidation contains a list of LiquidationEntry objects.
50
+
51
+ Attributes:
52
+ liquidation_entries (list[LiquidationEntry]): A list of LiquidationEntry objects
53
+ request_index (int): Sequenced request index of transaction
54
+ """
55
+
56
+ liquidation_entries: list[LiquidationEntry] = field(eq=set)
57
+ request_index: int = field(default=-1, eq=False)
58
+
59
+ @classmethod
60
+ def decode_value_into_cls(cls, raw_tx_log_event: dict):
61
+ """
62
+ Decode a raw transaction log event (dict) into a Liquidation
63
+ instance.
64
+
65
+ Parameters
66
+ ----------
67
+ raw_tx_log_event : dict
68
+ Raw transaction log event being processed
69
+ """
70
+
71
+ event = raw_tx_log_event["event"]["c"]
72
+ liquidation_tx_event = event["strategies"]
73
+
74
+ return cls(
75
+ [
76
+ LiquidationEntry(
77
+ liquidation_entry["traderAddress"],
78
+ liquidation_entry["strategyIdHash"],
79
+ [
80
+ Cancel(
81
+ ProductSymbol(canceled_order["symbol"]),
82
+ canceled_order["orderHash"],
83
+ Decimal(canceled_order["amount"]),
84
+ raw_tx_log_event["requestIndex"],
85
+ )
86
+ for canceled_order in liquidation_entry["canceledOrders"]
87
+ ],
88
+ [
89
+ (
90
+ ProductSymbol(liquidated_position_key),
91
+ LiquidatedPosition(
92
+ Decimal(liquidated_position_val["amount"]),
93
+ [
94
+ (
95
+ LiquidationFill(
96
+ ProductSymbol(
97
+ trade_outcome["Fill"]["symbol"]
98
+ ),
99
+ trade_outcome["Fill"]["indexPriceHash"],
100
+ trade_outcome["Fill"]["makerOrderHash"],
101
+ Decimal(
102
+ trade_outcome["Fill"][
103
+ "makerOrderRemainingAmount"
104
+ ]
105
+ ),
106
+ Decimal(trade_outcome["Fill"]["amount"]),
107
+ Decimal(trade_outcome["Fill"]["price"]),
108
+ OrderSide(
109
+ trade_outcome["Fill"]["takerSide"]
110
+ ),
111
+ Outcome(
112
+ trade_outcome["Fill"]["makerOutcome"][
113
+ "trader"
114
+ ],
115
+ trade_outcome["Fill"]["makerOutcome"][
116
+ "strategyIdHash"
117
+ ],
118
+ ),
119
+ raw_tx_log_event["timeValue"],
120
+ raw_tx_log_event["requestIndex"],
121
+ )
122
+ if "Fill" in trade_outcome
123
+ else Cancel(
124
+ ProductSymbol(
125
+ trade_outcome["Cancel"]["symbol"]
126
+ ),
127
+ trade_outcome["Cancel"]["orderHash"],
128
+ Decimal(trade_outcome["Cancel"]["amount"]),
129
+ raw_tx_log_event["requestIndex"],
130
+ )
131
+ )
132
+ for trade_outcome in liquidated_position_val[
133
+ "tradeOutcomes"
134
+ ]
135
+ ],
136
+ [
137
+ AdlOutcome(
138
+ adl_outcome["traderAddress"],
139
+ adl_outcome["strategyIdHash"],
140
+ raw_tx_log_event["requestIndex"],
141
+ )
142
+ for adl_outcome in liquidated_position_val[
143
+ "adlOutcomes"
144
+ ]
145
+ ],
146
+ Decimal(liquidated_position_val["newInsuranceFundCap"]),
147
+ raw_tx_log_event["requestIndex"],
148
+ ),
149
+ )
150
+ for (
151
+ liquidated_position_key,
152
+ liquidated_position_val,
153
+ ) in liquidation_entry["positions"]
154
+ ],
155
+ raw_tx_log_event["requestIndex"],
156
+ )
157
+ for liquidation_entry in liquidation_tx_event
158
+ ],
159
+ raw_tx_log_event["requestIndex"],
160
+ )
161
+
162
+ def process_tx(
163
+ self,
164
+ smt: DerivadexSMT,
165
+ **kwargs,
166
+ ):
167
+ """
168
+ Process a Liquidation transaction. A Liquidation is when a
169
+ trader's account is under-collateralized and forcibly closed.
170
+ Their collateral is removed and positions are closed, either
171
+ against the order book with other market participants, or if
172
+ the insurance fund is insufficiently-capitalized, ADL'd vs
173
+ winning traders. A liquidated trader's open orders are canceled
174
+ and the insurance fund is adjusted, along with the relevant
175
+ Stats leaves for the maker traders taking on the liquidated
176
+ position.
177
+
178
+ Parameters
179
+ ----------
180
+ smt: DerivadexSMT
181
+ DerivaDEX Sparse Merkle Tree
182
+ **kwargs
183
+ Additional args specific to Liquidation transactions
184
+ """
185
+
186
+ # Loop through each liquidation entry and process the cancels
187
+ for liquidation_entry in self.liquidation_entries:
188
+ # Loop through the canceled orders to remove them from the
189
+ # SMT
190
+ for cancel_tx in liquidation_entry.canceled_orders:
191
+ cancel_tx.process_tx(smt, **kwargs)
192
+
193
+ # Loop through each liquidation entry and process them individually
194
+ for liquidation_entry in self.liquidation_entries:
195
+ liquidated_strategy_key: StrategyKey = StrategyKey(
196
+ liquidation_entry.trader_address, liquidation_entry.strategy_id_hash
197
+ )
198
+ liquidated_strategy: Strategy = smt.strategy(liquidated_strategy_key)
199
+
200
+ position_leaves_by_symbol = {}
201
+ for symbol, liquidated_position in liquidation_entry.positions:
202
+ liquidated_position_key: PositionKey = PositionKey(
203
+ liquidation_entry.trader_address,
204
+ liquidation_entry.strategy_id_hash,
205
+ symbol,
206
+ )
207
+
208
+ liquidated_position: Position = smt.position(liquidated_position_key)
209
+
210
+ # Store position in dict
211
+ position_leaves_by_symbol[symbol] = liquidated_position
212
+
213
+ # Loop through the positions of the liquidated Strategy
214
+ for symbol, liquidated_position in liquidation_entry.positions:
215
+ # Get collateral for liquidated strategy
216
+ collateral = liquidated_strategy.avail_collateral[TokenSymbol.USDC]
217
+
218
+ liquidated_position_key: PositionKey = PositionKey(
219
+ liquidation_entry.trader_address,
220
+ liquidation_entry.strategy_id_hash,
221
+ symbol,
222
+ )
223
+ liquidated_position_leaf: Position = smt.position(
224
+ liquidated_position_key
225
+ )
226
+
227
+ # Obtain latest mark price
228
+ mark_price = kwargs["latest_price_leaves"][symbol][1].mark_price
229
+
230
+ liquidated_strategy_total_value = compute_strategy_total_value(
231
+ liquidated_strategy,
232
+ position_leaves_by_symbol,
233
+ kwargs["latest_price_leaves"],
234
+ )
235
+
236
+ # Compute the bankruptcy price for the liquidated Position
237
+ bankruptcy_price = mark_price - (
238
+ Decimal("1")
239
+ if liquidated_position_leaf.side == PositionSide.Long
240
+ else Decimal("-1")
241
+ ) * (liquidated_strategy_total_value / liquidated_position_leaf.balance)
242
+
243
+ # Loop through each trade outcome event and process them individually
244
+ for trade_outcome in liquidated_position.trade_outcomes:
245
+ trade_outcome.process_tx(smt, **kwargs)
246
+
247
+ # If we're dealing with a Liquidation fill vs.
248
+ # a cancel...
249
+ if isinstance(trade_outcome, LiquidationFill):
250
+ # Update the collateral's intermediate value
251
+ collateral += (
252
+ trade_outcome.amount
253
+ * liquidated_position_leaf.avg_pnl(bankruptcy_price)
254
+ )
255
+
256
+ # Update liquidated Position's balance by the
257
+ # liquidated amount
258
+ liquidated_position_leaf.balance -= trade_outcome.amount
259
+
260
+ # Loop through each ADL outcome and process them individually
261
+ for adl_outcome in liquidated_position.adl_outcomes:
262
+ adl_position_key: PositionKey = PositionKey(
263
+ adl_outcome.trader_address,
264
+ adl_outcome.strategy_id_hash,
265
+ symbol,
266
+ )
267
+ adl_position: Position = smt.position(adl_position_key)
268
+
269
+ adl_strategy_key: StrategyKey = adl_position_key.as_strategy_key()
270
+ adl_strategy: Strategy = smt.strategy(adl_strategy_key)
271
+
272
+ # Compute ADL amount
273
+ adl_amount = min(
274
+ liquidated_position_leaf.balance, adl_position.balance
275
+ )
276
+
277
+ # Compute ADL'd realized PNL
278
+ adl_realized_pnl = adl_amount * adl_position.avg_pnl(
279
+ bankruptcy_price,
280
+ )
281
+
282
+ # Adjust ADL'd Strategy's free collateral
283
+ adl_strategy.set_avail_collateral(
284
+ TokenSymbol.USDC,
285
+ adl_strategy.avail_collateral[TokenSymbol.USDC]
286
+ + adl_realized_pnl,
287
+ )
288
+
289
+ # Store ADL'd Strategy in the SMT
290
+ smt.store_strategy(
291
+ adl_strategy_key,
292
+ adl_strategy,
293
+ )
294
+
295
+ # Adjust and store ADL'd Position in the SMT
296
+ adl_position.balance -= adl_amount
297
+ smt.store_position(
298
+ adl_position_key,
299
+ adl_position,
300
+ )
301
+
302
+ # Compute liquidated Strategy's realized pnl
303
+ liquidated_realized_pnl = (
304
+ adl_amount
305
+ * liquidated_position_leaf.avg_pnl(
306
+ bankruptcy_price,
307
+ )
308
+ )
309
+
310
+ # Update the collateral's intermediate value
311
+ collateral += liquidated_realized_pnl
312
+
313
+ # Adjust liquidated Position's balance by the
314
+ # ADL'd amount
315
+ liquidated_position_leaf.balance -= adl_amount
316
+
317
+ # Update liquidated Strategy's free collateral
318
+ # with the realized PNL from liquidation fills and ADL's
319
+ liquidated_strategy.set_avail_collateral(
320
+ TokenSymbol.USDC,
321
+ collateral,
322
+ )
323
+
324
+ # Remove Position from the SMT
325
+ smt.store_position(
326
+ liquidated_position_key,
327
+ None,
328
+ )
329
+
330
+ insurance_fund_key: InsuranceFundKey = InsuranceFundKey()
331
+ insurance_fund: InsuranceFund = smt.insurance_fund(insurance_fund_key)
332
+
333
+ # Overwrite the insurance fund with the new insurance fund
334
+ # capitalization.
335
+ insurance_fund[TokenSymbol.USDC] = (
336
+ liquidated_position.new_insurance_fund_cap
337
+ )
338
+
339
+ smt.store_insurance_fund(insurance_fund_key, insurance_fund)
340
+
341
+ # Delete symbol/Position from dict
342
+ del position_leaves_by_symbol[symbol]
343
+
344
+ # Clear out the liquidated Strategy's free collateral and
345
+ # store in the SMT
346
+ liquidated_strategy.set_avail_collateral(
347
+ TokenSymbol.USDC,
348
+ Decimal("0"),
349
+ )
350
+ smt.store_strategy(
351
+ liquidated_strategy_key,
352
+ liquidated_strategy,
353
+ )
@@ -0,0 +1,125 @@
1
+ """
2
+ PartialFill module
3
+ """
4
+
5
+ from attrs import define, field
6
+ from ddx.common.transactions.cancel import Cancel
7
+ from ddx.common.transactions.event import Event
8
+ from ddx.common.transactions.inner.outcome import Outcome
9
+ from ddx.common.transactions.inner.trade_fill import TradeFill
10
+ from ddx.common.transactions.post import Post
11
+ from ddx._rust.common import ProductSymbol
12
+ from ddx._rust.common.enums import OrderSide
13
+ from ddx._rust.common.state import DerivadexSMT
14
+ from ddx._rust.decimal import Decimal
15
+
16
+
17
+ @define
18
+ class PartialFill(Event):
19
+ """
20
+ Defines an PartialFill
21
+
22
+ A PartialFill is a scenario where the taker order has been partially filled
23
+ across 1 or more maker orders and thus has a remaining order that enters the
24
+ book along with any canceled maker orders.
25
+
26
+ Attributes:
27
+ post (Post): A Post object
28
+ trade_outcomes (list[TradeFill | Cancel]): A list of trade outcome objects
29
+ request_index (int): Sequenced request index of transaction
30
+ """
31
+
32
+ post: Post
33
+ trade_outcomes: list[TradeFill | Cancel] = field(eq=set)
34
+ request_index: int = field(default=-1, eq=False)
35
+
36
+ @classmethod
37
+ def decode_value_into_cls(cls, raw_tx_log_event: dict):
38
+ """
39
+ Decode a raw transaction log event (dict) into a PartialFill
40
+ instance.
41
+
42
+ Parameters
43
+ ----------
44
+ raw_tx_log_event : dict
45
+ Raw transaction log event being processed
46
+ """
47
+
48
+ event = raw_tx_log_event["event"]["c"]
49
+ trade_outcomes_tx_event = event["tradeOutcomes"]
50
+
51
+ return cls(
52
+ Post(
53
+ ProductSymbol(event["symbol"]),
54
+ event["orderHash"],
55
+ OrderSide(event["side"]),
56
+ Decimal(event["amount"]),
57
+ Decimal(event["price"]),
58
+ event["traderAddress"],
59
+ event["strategyIdHash"],
60
+ event["bookOrdinal"],
61
+ # NOTE 3503: This is logically identical to the time value in BookOrder, so not repeating it.
62
+ raw_tx_log_event["timeValue"],
63
+ raw_tx_log_event["requestIndex"],
64
+ ),
65
+ [
66
+ (
67
+ TradeFill(
68
+ ProductSymbol(trade_outcome["Fill"]["symbol"]),
69
+ trade_outcome["Fill"]["takerOrderHash"],
70
+ trade_outcome["Fill"]["makerOrderHash"],
71
+ Decimal(trade_outcome["Fill"]["makerOrderRemainingAmount"]),
72
+ Decimal(trade_outcome["Fill"]["amount"]),
73
+ Decimal(trade_outcome["Fill"]["price"]),
74
+ OrderSide(trade_outcome["Fill"]["takerSide"]),
75
+ Outcome(
76
+ trade_outcome["Fill"]["makerOutcome"]["trader"],
77
+ trade_outcome["Fill"]["makerOutcome"]["strategyIdHash"],
78
+ ),
79
+ Outcome(
80
+ trade_outcome["Fill"]["takerOutcome"]["trader"],
81
+ trade_outcome["Fill"]["takerOutcome"]["strategyIdHash"],
82
+ ),
83
+ raw_tx_log_event["timeValue"],
84
+ raw_tx_log_event["requestIndex"],
85
+ )
86
+ if "Fill" in trade_outcome
87
+ else Cancel(
88
+ ProductSymbol(trade_outcome["Cancel"]["symbol"]),
89
+ trade_outcome["Cancel"]["orderHash"],
90
+ Decimal(trade_outcome["Cancel"]["amount"]),
91
+ raw_tx_log_event["requestIndex"],
92
+ )
93
+ )
94
+ for trade_outcome in trade_outcomes_tx_event
95
+ ],
96
+ raw_tx_log_event["requestIndex"],
97
+ )
98
+
99
+ def process_tx(
100
+ self,
101
+ smt: DerivadexSMT,
102
+ **kwargs,
103
+ ):
104
+ """
105
+ Process a PartialFill transaction. A PartialFill consists of
106
+ Fill objects, which will adjust the maker BookOrder leaf in the
107
+ SMT, while also adjusting the Strategy, Position, and Trader
108
+ leaves corresponding to both the maker and the taker. It also
109
+ consists of a Post object, which will be a BookOrder consisting
110
+ of what's left of the partially matched order.
111
+
112
+ Parameters
113
+ ----------
114
+ smt: DerivadexSMT
115
+ DerivaDEX Sparse Merkle Tree
116
+ **kwargs
117
+ Additional args specific to PartialFill transactions
118
+ """
119
+
120
+ # Loop through each trade outcome event and process them individually
121
+ for trade_outcome in self.trade_outcomes:
122
+ trade_outcome.process_tx(smt, **kwargs)
123
+
124
+ # Process the remaining post event
125
+ self.post.process_tx(smt, **kwargs)
@@ -0,0 +1,120 @@
1
+ """
2
+ PnlRealization module
3
+ """
4
+
5
+ from attrs import define, field
6
+ from ddx.common.logging import auditor_logger
7
+ from ddx.common.transactions.event import Event
8
+ from ddx._rust.common import TokenSymbol
9
+ from ddx._rust.common.state import DerivadexSMT, Position
10
+ from ddx._rust.common.state.keys import PositionKey, StrategyKey
11
+ from ddx._rust.decimal import Decimal
12
+
13
+ logger = auditor_logger(__name__)
14
+
15
+
16
+ @define
17
+ class PnlRealization(Event):
18
+ """
19
+ Defines a PnlRealization
20
+
21
+ A PnlRealization is when unrealized pnl is settled/realized for all
22
+ traders' strategies. This will result in a credit/debit for all
23
+ strategies.
24
+
25
+ Attributes:
26
+ settlement_epoch_id (int): Settlement epoch id
27
+ time_value (int): Time value
28
+ request_index (int): Sequenced request index of transaction
29
+ """
30
+
31
+ settlement_epoch_id: int
32
+ time_value: 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 PnlRealization
39
+ instance.
40
+
41
+ Parameters
42
+ ----------
43
+ raw_tx_log_event : dict
44
+ Raw transaction log event being processed
45
+ """
46
+
47
+ event = raw_tx_log_event["event"]["c"]
48
+
49
+ return cls(
50
+ event["settlementEpochId"],
51
+ raw_tx_log_event["timeValue"],
52
+ raw_tx_log_event["requestIndex"],
53
+ )
54
+
55
+ def process_tx(
56
+ self,
57
+ smt: DerivadexSMT,
58
+ **kwargs,
59
+ ):
60
+ """
61
+ Process a PnlRealization transaction. A PnlRealization event
62
+ consists of information relating to when unrealized pnl is
63
+ settled/realized for all traders' strategies. This will result
64
+ in a credit/debit for all strategies. Furthermore, the
65
+ average entry price for any open positions will be set to the
66
+ current mark price.
67
+
68
+ Parameters
69
+ ----------
70
+ smt: DerivadexSMT
71
+ DerivaDEX Sparse Merkle Tree
72
+ **kwargs
73
+ Additional args specific to PnlRealization transactions
74
+ """
75
+
76
+ for (
77
+ position_key,
78
+ position,
79
+ ) in sorted(
80
+ smt.all_positions(),
81
+ key=lambda item: item[0].symbol,
82
+ ):
83
+ mark_price = kwargs["latest_price_leaves"][position_key.symbol][
84
+ 1
85
+ ].mark_price
86
+ unrealized_pnl = position.unrealized_pnl(
87
+ mark_price,
88
+ )
89
+
90
+ # Construct a StrategyKey and corresponding encoded
91
+ # key
92
+ strategy_key: StrategyKey = position_key.as_strategy_key()
93
+
94
+ # Get the Strategy leaf given the key from above
95
+ strategy = smt.strategy(strategy_key)
96
+
97
+ old_balance = strategy.avail_collateral[TokenSymbol.USDC]
98
+ # Credit/debit the trader's Strategy leaf by the
99
+ # unrealized PNL to settle
100
+ strategy.set_avail_collateral(
101
+ TokenSymbol.USDC,
102
+ strategy.avail_collateral[TokenSymbol.USDC] + unrealized_pnl,
103
+ )
104
+ logger.info(
105
+ f"Calculated realized pnl:\n\told balance: {old_balance}\n\tnew balance: {strategy.avail_collateral[TokenSymbol.USDC]}\n\tpnl realized: {strategy.avail_collateral[TokenSymbol.USDC] - old_balance}"
106
+ )
107
+
108
+ smt.store_strategy(
109
+ strategy_key,
110
+ strategy,
111
+ )
112
+
113
+ # Set the Position's average entry price to the mark price
114
+ # since settlement has just taken place
115
+ position.avg_entry_price = mark_price
116
+
117
+ smt.store_position(
118
+ position_key,
119
+ position,
120
+ )
@@ -0,0 +1,72 @@
1
+ """
2
+ Post module
3
+ """
4
+
5
+ from attrs import define, field
6
+ from ddx.common.transactions.event import Event
7
+ from ddx._rust.common import ProductSymbol
8
+ from ddx._rust.common.enums import OrderSide
9
+ from ddx._rust.common.state import BookOrder, DerivadexSMT
10
+ from ddx._rust.common.state.keys import BookOrderKey, StrategyKey
11
+ from ddx._rust.decimal import Decimal
12
+
13
+
14
+ @define
15
+ class Post(Event):
16
+ """
17
+ Defines a Post
18
+
19
+ A Post is an order that enters the order book.
20
+
21
+ Attributes:
22
+ symbol (ProductSymbol): The symbol for the market this order is for.
23
+ order_hash (str): Hexstr representation of the EIP-712 hash of the order
24
+ side (str): Side of order ('Bid', 'Ask')
25
+ amount (Decimal): Amount/size of order
26
+ price (Decimal): Price the order has been placed at
27
+ trader_address (str): The order creator's Ethereum address
28
+ strategy_id_hash (str): The cross-margined strategy ID for which this order belongs
29
+ book_ordinal (int): The numerical sequence-identifying value for an order's insertion into the book
30
+ time_value (int): Time value
31
+ request_index (int): Sequenced request index of transaction
32
+ """
33
+
34
+ symbol: ProductSymbol
35
+ order_hash: str = field(eq=str.lower)
36
+ side: OrderSide
37
+ amount: Decimal
38
+ price: Decimal
39
+ trader_address: str = field(eq=str.lower)
40
+ strategy_id_hash: str = field(eq=str.lower)
41
+ book_ordinal: int
42
+ time_value: int
43
+ request_index: int = field(default=-1, eq=False)
44
+
45
+ def process_tx(
46
+ self,
47
+ smt: DerivadexSMT,
48
+ **kwargs,
49
+ ):
50
+ """
51
+ Process a Post transaction. We will need to create a new
52
+ BookOrder leaf with this information.
53
+
54
+ Parameters
55
+ ----------
56
+ smt: DerivadexSMT
57
+ DerivaDEX Sparse Merkle Tree
58
+ **kwargs
59
+ Additional args specific to PostOrder transactions
60
+ """
61
+
62
+ book_order = BookOrder(
63
+ self.side,
64
+ self.amount,
65
+ self.price,
66
+ self.trader_address,
67
+ self.strategy_id_hash,
68
+ self.book_ordinal,
69
+ self.time_value,
70
+ )
71
+
72
+ smt.store_book_order(BookOrderKey(self.symbol, self.order_hash), book_order)