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,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)
|