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.
- ddx/.gitignore +1 -0
- ddx/__init__.py +58 -0
- ddx/_rust/__init__.pyi +2685 -0
- ddx/_rust/common/__init__.pyi +17 -0
- ddx/_rust/common/accounting.pyi +6 -0
- ddx/_rust/common/enums.pyi +3 -0
- ddx/_rust/common/requests/__init__.pyi +23 -0
- ddx/_rust/common/requests/intents.pyi +19 -0
- ddx/_rust/common/specs.pyi +17 -0
- ddx/_rust/common/state/__init__.pyi +41 -0
- ddx/_rust/common/state/keys.pyi +29 -0
- ddx/_rust/common/transactions.pyi +7 -0
- ddx/_rust/decimal.pyi +3 -0
- ddx/_rust/h256.pyi +3 -0
- ddx/_rust.abi3.so +0 -0
- ddx/app_config/ethereum/addresses.json +526 -0
- ddx/auditor/README.md +32 -0
- ddx/auditor/__init__.py +0 -0
- ddx/auditor/auditor_driver.py +1043 -0
- ddx/auditor/websocket_message.py +54 -0
- ddx/common/__init__.py +0 -0
- ddx/common/epoch_params.py +28 -0
- ddx/common/fill_context.py +141 -0
- ddx/common/logging.py +184 -0
- ddx/common/market_aware_account.py +259 -0
- ddx/common/market_specs.py +64 -0
- ddx/common/trade_mining_params.py +19 -0
- ddx/common/transaction_utils.py +85 -0
- ddx/common/transactions/__init__.py +0 -0
- ddx/common/transactions/advance_epoch.py +91 -0
- ddx/common/transactions/advance_settlement_epoch.py +63 -0
- ddx/common/transactions/all_price_checkpoints.py +84 -0
- ddx/common/transactions/cancel.py +76 -0
- ddx/common/transactions/cancel_all.py +88 -0
- ddx/common/transactions/complete_fill.py +103 -0
- ddx/common/transactions/disaster_recovery.py +96 -0
- ddx/common/transactions/event.py +48 -0
- ddx/common/transactions/fee_distribution.py +119 -0
- ddx/common/transactions/funding.py +292 -0
- ddx/common/transactions/futures_expiry.py +123 -0
- ddx/common/transactions/genesis.py +108 -0
- ddx/common/transactions/inner/__init__.py +0 -0
- ddx/common/transactions/inner/adl_outcome.py +25 -0
- ddx/common/transactions/inner/fill.py +232 -0
- ddx/common/transactions/inner/liquidated_position.py +41 -0
- ddx/common/transactions/inner/liquidation_entry.py +41 -0
- ddx/common/transactions/inner/liquidation_fill.py +118 -0
- ddx/common/transactions/inner/outcome.py +32 -0
- ddx/common/transactions/inner/trade_fill.py +292 -0
- ddx/common/transactions/insurance_fund_update.py +138 -0
- ddx/common/transactions/insurance_fund_withdraw.py +100 -0
- ddx/common/transactions/liquidation.py +353 -0
- ddx/common/transactions/partial_fill.py +125 -0
- ddx/common/transactions/pnl_realization.py +120 -0
- ddx/common/transactions/post.py +72 -0
- ddx/common/transactions/post_order.py +95 -0
- ddx/common/transactions/price_checkpoint.py +97 -0
- ddx/common/transactions/signer_registered.py +62 -0
- ddx/common/transactions/specs_update.py +61 -0
- ddx/common/transactions/strategy_update.py +158 -0
- ddx/common/transactions/tradable_product_update.py +98 -0
- ddx/common/transactions/trade_mining.py +147 -0
- ddx/common/transactions/trader_update.py +131 -0
- ddx/common/transactions/withdraw.py +90 -0
- ddx/common/transactions/withdraw_ddx.py +74 -0
- ddx/common/utils.py +176 -0
- ddx/config.py +17 -0
- ddx/derivadex_client.py +270 -0
- ddx/models/__init__.py +0 -0
- ddx/models/base.py +132 -0
- ddx/py.typed +0 -0
- ddx/realtime_client/__init__.py +2 -0
- ddx/realtime_client/config.py +2 -0
- ddx/realtime_client/models/__init__.py +611 -0
- ddx/realtime_client/realtime_client.py +646 -0
- ddx/rest_client/__init__.py +0 -0
- ddx/rest_client/clients/__init__.py +0 -0
- ddx/rest_client/clients/base_client.py +60 -0
- ddx/rest_client/clients/market_client.py +1243 -0
- ddx/rest_client/clients/on_chain_client.py +439 -0
- ddx/rest_client/clients/signed_client.py +292 -0
- ddx/rest_client/clients/system_client.py +843 -0
- ddx/rest_client/clients/trade_client.py +357 -0
- ddx/rest_client/constants/__init__.py +0 -0
- ddx/rest_client/constants/endpoints.py +66 -0
- ddx/rest_client/contracts/__init__.py +0 -0
- ddx/rest_client/contracts/checkpoint/__init__.py +560 -0
- ddx/rest_client/contracts/ddx/__init__.py +1949 -0
- ddx/rest_client/contracts/dummy_token/__init__.py +1014 -0
- ddx/rest_client/contracts/i_collateral/__init__.py +1414 -0
- ddx/rest_client/contracts/i_stake/__init__.py +696 -0
- ddx/rest_client/exceptions/__init__.py +0 -0
- ddx/rest_client/exceptions/exceptions.py +32 -0
- ddx/rest_client/http/__init__.py +0 -0
- ddx/rest_client/http/http_client.py +336 -0
- ddx/rest_client/models/__init__.py +0 -0
- ddx/rest_client/models/market.py +693 -0
- ddx/rest_client/models/signed.py +61 -0
- ddx/rest_client/models/system.py +311 -0
- ddx/rest_client/models/trade.py +185 -0
- ddx/rest_client/utils/__init__.py +0 -0
- ddx/rest_client/utils/encryption_utils.py +26 -0
- ddx/utils/__init__.py +0 -0
- ddx_python-1.0.4.dist-info/METADATA +63 -0
- ddx_python-1.0.4.dist-info/RECORD +106 -0
- 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)
|