axis-synome 0.1.0.dev30__tar.gz → 0.1.0.dev32__tar.gz
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.
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/PKG-INFO +1 -1
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/src/axis_synome/_version.py +2 -2
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/src/axis_synome/spec/asc/entities/tokens.py +4 -0
- axis_synome-0.1.0.dev32/src/axis_synome/spec/asc/entities/types.py +23 -0
- axis_synome-0.1.0.dev32/src/axis_synome/spec/risk_capital/financial_rrc/__init__.py +5 -0
- axis_synome-0.1.0.dev32/src/axis_synome/spec/risk_capital/financial_rrc/entities.py +35 -0
- axis_synome-0.1.0.dev32/src/axis_synome/spec/risk_capital/financial_rrc/perpetual_positions.py +507 -0
- axis_synome-0.1.0.dev32/src/axis_synome/spec_support/pendle_validation.py +52 -0
- axis_synome-0.1.0.dev32/src/axis_synome/spec_support/validated_dataclass.py +50 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/src/axis_synome.egg-info/PKG-INFO +1 -1
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/src/axis_synome.egg-info/SOURCES.txt +6 -0
- axis_synome-0.1.0.dev32/tests/axis_synome/risk_capital/financial_rrc/test_perpetual_positions.py +460 -0
- axis_synome-0.1.0.dev32/tests/axis_synome/suraf/suraf_client/__init__.py +0 -0
- axis_synome-0.1.0.dev30/src/axis_synome/spec/asc/entities/types.py +0 -15
- axis_synome-0.1.0.dev30/src/axis_synome/spec_support/validated_dataclass.py +0 -36
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/.flake8 +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/README.md +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/WRITING_SPECS.md +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/pyproject.toml +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/setup.cfg +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/src/axis_synome/__init__.py +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/src/axis_synome/spec/__init__.py +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/src/axis_synome/spec/asc/README.md +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/src/axis_synome/spec/asc/entities/__init__.py +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/src/axis_synome/spec/asc/entities/assets.py +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/src/axis_synome/spec/asc/entities/assets_by_prime.py +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/src/axis_synome/spec/asc/entities/networks.py +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/src/axis_synome/spec/asc/entities/primes.py +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/src/axis_synome/spec/asc/entities/protocol_sets.py +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/src/axis_synome/spec/asc/formulas/asc.py +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/src/axis_synome/spec/asc/formulas/asc_collateral_ratio.py +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/src/axis_synome/spec/asc/formulas/asc_incentive.py +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/src/axis_synome/spec/asc/formulas/dab.py +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/src/axis_synome/spec/asc/formulas/latent_asc.py +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/src/axis_synome/spec/asc/formulas/ratio_latent_asc.py +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/src/axis_synome/spec/asc/formulas/resting_asc.py +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/src/axis_synome/spec/codegen_test/entities/agents.py +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/src/axis_synome/spec/crypto_lending/__init__.py +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/src/axis_synome/spec/crypto_lending/formulas/__init__.py +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/src/axis_synome/spec/crypto_lending/formulas/lif.py +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/src/axis_synome/spec/risk_capital/__init__.py +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/src/axis_synome/spec/risk_capital/formulas/__init__.py +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/src/axis_synome/spec/risk_capital/formulas/required_risk_capital.py +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/src/axis_synome/spec/suraf/README.md +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/src/axis_synome/spec/suraf/entities/assessor_score.py +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/src/axis_synome/spec/suraf/entities/assets.py +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/src/axis_synome/spec/suraf/entities/mappings.py +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/src/axis_synome/spec/suraf/formulas/crr.py +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/src/axis_synome/spec/suraf/formulas/scoring.py +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/src/axis_synome/spec_support/__init__.py +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/src/axis_synome/spec_support/evm_address.py +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/src/axis_synome/spec_support/runtime/__init__.py +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/src/axis_synome/spec_support/runtime/base.py +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/src/axis_synome/spec_support/runtime/math.py +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/src/axis_synome/spec_support/runtime/reference.py +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/src/axis_synome/spec_support/validated_str.py +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/src/axis_synome/spec_validator/__init__.py +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/src/axis_synome/spec_validator/checker.py +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/src/axis_synome/spec_validator/flake8_plugin.py +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/src/axis_synome/spec_validator/python_subset.py +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/src/axis_synome.egg-info/dependency_links.txt +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/src/axis_synome.egg-info/entry_points.txt +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/src/axis_synome.egg-info/requires.txt +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/src/axis_synome.egg-info/top_level.txt +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/tests/axis_synome/asc/__init__.py +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/tests/axis_synome/asc/conftest.py +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/tests/axis_synome/asc/mocks.py +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/tests/axis_synome/asc/test_asc.py +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/tests/axis_synome/asc/test_asc_collateral_ratio.py +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/tests/axis_synome/asc/test_asc_incentive.py +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/tests/axis_synome/asc/test_dab.py +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/tests/axis_synome/asc/test_evm_address.py +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/tests/axis_synome/asc/test_latent_asc.py +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/tests/axis_synome/asc/test_prime_agent_data_validation.py +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/tests/axis_synome/asc/test_ratio_latent_asc.py +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/tests/axis_synome/asc/test_resting_asc.py +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/tests/axis_synome/risk_capital/__init__.py +0 -0
- {axis_synome-0.1.0.dev30/tests/axis_synome/risk_capital/formulas → axis_synome-0.1.0.dev32/tests/axis_synome/risk_capital/financial_rrc}/__init__.py +0 -0
- {axis_synome-0.1.0.dev30/tests/axis_synome/spec_support → axis_synome-0.1.0.dev32/tests/axis_synome/risk_capital/formulas}/__init__.py +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/tests/axis_synome/risk_capital/formulas/test_loss_given_default.py +0 -0
- {axis_synome-0.1.0.dev30/tests/axis_synome/spec_support/runtime → axis_synome-0.1.0.dev32/tests/axis_synome/spec_support}/__init__.py +0 -0
- {axis_synome-0.1.0.dev30/tests/axis_synome/suraf → axis_synome-0.1.0.dev32/tests/axis_synome/spec_support/runtime}/__init__.py +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/tests/axis_synome/spec_support/runtime/test_base.py +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/tests/axis_synome/spec_support/runtime/test_math.py +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/tests/axis_synome/spec_validator/test_checker.py +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/tests/axis_synome/spec_validator/test_flake8_plugin.py +0 -0
- {axis_synome-0.1.0.dev30/tests/axis_synome/suraf/entities → axis_synome-0.1.0.dev32/tests/axis_synome/suraf}/__init__.py +0 -0
- {axis_synome-0.1.0.dev30/tests/axis_synome/suraf/formulas → axis_synome-0.1.0.dev32/tests/axis_synome/suraf/entities}/__init__.py +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/tests/axis_synome/suraf/entities/test_assessor_score.py +0 -0
- {axis_synome-0.1.0.dev30/tests/axis_synome/suraf/suraf_client → axis_synome-0.1.0.dev32/tests/axis_synome/suraf/formulas}/__init__.py +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/tests/axis_synome/suraf/formulas/test_crr.py +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/tests/axis_synome/suraf/formulas/test_scoring.py +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/tests/axis_synome/suraf/static/aave_ausdc/v1/crr_mapping.csv +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/tests/axis_synome/suraf/static/aave_ausdc/v1/penalty.csv +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/tests/axis_synome/suraf/static/aave_ausdc/v1/scorecards/Assessor_1_scores.csv +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/tests/axis_synome/suraf/static/aave_ausdc/v1/scorecards/Assessor_2_scores.csv +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/tests/axis_synome/suraf/static/aave_ausdc/v1/scorecards/Assessor_3_scores.csv +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/tests/axis_synome/suraf/static/aave_ausdc/v1/weights.csv +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/tests/axis_synome/suraf/suraf_client/suraf_client.py +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/tests/axis_synome/suraf/suraf_client/test_suraf_client.py +0 -0
- {axis_synome-0.1.0.dev30 → axis_synome-0.1.0.dev32}/uv.lock +0 -0
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.1.0.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 1, 0, '
|
|
21
|
+
__version__ = version = '0.1.0.dev32'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 1, 0, 'dev32')
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from axis_synome.spec.asc.entities.assets import Asset
|
|
2
|
+
from axis_synome.spec_support.pendle_validation import validate_pendle_token_has_expiration
|
|
3
|
+
from axis_synome.spec_support.validated_dataclass import validated_dataclass
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@validated_dataclass(validators=[validate_pendle_token_has_expiration])
|
|
7
|
+
class Position:
|
|
8
|
+
"""A single holding of a token in a protocol on a network.
|
|
9
|
+
|
|
10
|
+
This is the main computational unit for all ASC / DAB formulas.
|
|
11
|
+
|
|
12
|
+
``pt_expiration_time`` is meaningful only for Pendle Ethena PT holdings
|
|
13
|
+
(PT-USDe / PT-sUSDe). The ``validate_pendle_token_has_expiration``
|
|
14
|
+
validator (wired via the decorator since the spec subset disallows
|
|
15
|
+
methods on dataclasses) enforces at construction time that Pendle tokens
|
|
16
|
+
carry a non-``None`` expiration and other tokens do not.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
asset: Asset
|
|
20
|
+
value_usd: float
|
|
21
|
+
pool_paired_with_usds: bool | None = None
|
|
22
|
+
fee_tier: float | None = None
|
|
23
|
+
pt_expiration_time: int | None = None
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Entities for Financial RRC perpetual-position deployment events."""
|
|
2
|
+
|
|
3
|
+
from axis_synome.spec.asc.entities.primes import PrimeAgent
|
|
4
|
+
from axis_synome.spec_support.validated_dataclass import validated_dataclass
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@validated_dataclass
|
|
8
|
+
class RiskAdvisorApproval:
|
|
9
|
+
"""Core Council Risk Advisor approval for a Superstate deployment."""
|
|
10
|
+
|
|
11
|
+
approved_amount_usds: float
|
|
12
|
+
approval_timestamp: int
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@validated_dataclass
|
|
16
|
+
class SuperstateDeployment:
|
|
17
|
+
"""A Prime Agent's capital deployment to Superstate (umbrella type)."""
|
|
18
|
+
|
|
19
|
+
agent: PrimeAgent
|
|
20
|
+
amount_usds: float
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@validated_dataclass
|
|
24
|
+
class SuperstateInitialDeployment(SuperstateDeployment):
|
|
25
|
+
"""A Prime Agent's first capital deployment to Superstate."""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@validated_dataclass
|
|
29
|
+
class SuperstateSubsequentDeployment(SuperstateDeployment):
|
|
30
|
+
"""A Prime Agent's subsequent (post-first) Superstate deployment.
|
|
31
|
+
|
|
32
|
+
Each subsequent deployment requires explicit Risk Advisor approval.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
approval: RiskAdvisorApproval
|
axis_synome-0.1.0.dev32/src/axis_synome/spec/risk_capital/financial_rrc/perpetual_positions.py
ADDED
|
@@ -0,0 +1,507 @@
|
|
|
1
|
+
"""Perpetual Positions Financial RRC — Near-Term Treatment.
|
|
2
|
+
|
|
3
|
+
Near-term Instance Financial CRR rules for perpetual-position exposures.
|
|
4
|
+
Covers Ethena (direct, indirect, Pendle) and Superstate (USCC) exposure
|
|
5
|
+
types, their capital requirement ratios, aggregate exposure limits, and
|
|
6
|
+
deployment limits.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from enum import Enum
|
|
10
|
+
from typing import Final, cast
|
|
11
|
+
|
|
12
|
+
from axis_synome.spec.asc.entities.primes import PrimeAgent
|
|
13
|
+
from axis_synome.spec.asc.entities.protocol_sets import Protocol
|
|
14
|
+
from axis_synome.spec.asc.entities.tokens import (
|
|
15
|
+
DIRECT_ETHENA_TOKENS,
|
|
16
|
+
PENDLE_ETHENA_TOKENS,
|
|
17
|
+
)
|
|
18
|
+
from axis_synome.spec.asc.entities.types import Position
|
|
19
|
+
from axis_synome.spec.risk_capital.financial_rrc.entities import (
|
|
20
|
+
SuperstateInitialDeployment,
|
|
21
|
+
SuperstateSubsequentDeployment,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
# ---------------------------------------------------------------------------
|
|
25
|
+
# Eligible Prime Agents for Ethena exposures
|
|
26
|
+
# ---------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class EthenaEligiblePrimeAgent(Enum):
|
|
30
|
+
"""Prime Agents eligible to hold Ethena exposures.
|
|
31
|
+
|
|
32
|
+
Membership derived from per-agent Ethena Exposure Limits: only agents
|
|
33
|
+
with a non-zero limit are included.
|
|
34
|
+
|
|
35
|
+
:source_uuid: 8e120edf-c87b-4d99-8f2a-65fb49bcc3b7
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
SPARK = PrimeAgent.SPARK.value
|
|
39
|
+
GROVE = PrimeAgent.GROVE.value
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# ---------------------------------------------------------------------------
|
|
43
|
+
# Ethena CRR parameters
|
|
44
|
+
# ---------------------------------------------------------------------------
|
|
45
|
+
|
|
46
|
+
DIRECT_ETHENA_CRR: Final[float] = 0.03
|
|
47
|
+
"""Instance Financial CRR for direct USDe and sUSDe holdings.
|
|
48
|
+
|
|
49
|
+
:source_uuid: e0fa035c-e8f3-4cd2-8ca1-a6afbd1825eb
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
# is required before these CRRs can be applied to typed positions.
|
|
53
|
+
|
|
54
|
+
INDIRECT_ETHENA_ETHENA_COLLATERAL_CRR: Final[float] = 0.03
|
|
55
|
+
"""Instance Financial CRR for Ethena-related assets lent against Ethena-related collateral.
|
|
56
|
+
|
|
57
|
+
:source_uuid: cfa615fb-9927-4059-873a-7c824a517835
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
INDIRECT_ETHENA_NON_ETHENA_COLLATERAL_CRR: Final[float] = 0.04
|
|
61
|
+
"""Instance Financial CRR for non-Ethena assets lent against Ethena-related collateral.
|
|
62
|
+
|
|
63
|
+
:source_uuid: dadc95a6-c8ef-4abb-b9a5-d51bf2c0bf29
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
# ---------------------------------------------------------------------------
|
|
67
|
+
# Pendle Ethena CRR parameters
|
|
68
|
+
# ---------------------------------------------------------------------------
|
|
69
|
+
|
|
70
|
+
PENDLE_ETHENA_INITIAL_CRR: Final[float] = 0.10
|
|
71
|
+
"""Instance Financial CRR for Pendle Ethena exposures at exactly six months to maturity.
|
|
72
|
+
|
|
73
|
+
:source_uuid: 5d2d2430-2fd5-4418-a688-e8f091eb44b9
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
PENDLE_ETHENA_FINAL_CRR: Final[float] = DIRECT_ETHENA_CRR
|
|
77
|
+
"""Instance Financial CRR at PT maturity, equal to the Direct Ethena Exposures CRR
|
|
78
|
+
since the PT is convertible into the underlying asset.
|
|
79
|
+
|
|
80
|
+
:source_uuid: 900ecd08-64b7-4f37-8539-f4e822f11d98
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
PENDLE_ETHENA_MATURITY_THRESHOLD_SECONDS: Final[int] = 180 * 24 * 60 * 60
|
|
84
|
+
"""Six-month maturity threshold in seconds (180 days).
|
|
85
|
+
|
|
86
|
+
:source_uuid: 6ed19cc0-5447-4df9-a9c1-45c8730f5f44
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
PENDLE_ETHENA_TOTAL_DECAY: Final[float] = PENDLE_ETHENA_INITIAL_CRR - PENDLE_ETHENA_FINAL_CRR
|
|
90
|
+
"""Total CRR decay from initial value to final value over the six-month window.
|
|
91
|
+
|
|
92
|
+
:source_uuid: c5530d25-b8ef-4ebe-9b38-7b2a29014ff3
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
PENDLE_ETHENA_TOTAL_DURATION_SECONDS: Final[int] = PENDLE_ETHENA_MATURITY_THRESHOLD_SECONDS
|
|
96
|
+
"""Duration in seconds of the time-decay window (six months).
|
|
97
|
+
|
|
98
|
+
:source_uuid: 0e21a4c4-cd66-4ace-9dcd-6015fc9e4966
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
PENDLE_ETHENA_DECAY_RATE_PER_SECOND: Final[float] = (
|
|
102
|
+
PENDLE_ETHENA_TOTAL_DECAY / PENDLE_ETHENA_TOTAL_DURATION_SECONDS
|
|
103
|
+
)
|
|
104
|
+
"""CRR decay rate per second over the six-month window.
|
|
105
|
+
|
|
106
|
+
:source_uuid: cda560ff-5b25-44e1-bad4-e6ccfefdf7eb
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
PENDLE_ETHENA_CONCENTRATION_LIMIT: Final[float] = 0.20
|
|
110
|
+
"""Maximum fraction of total Allocation System Investments that may be in
|
|
111
|
+
Pendle Ethena exposures before 100% CRR applies.
|
|
112
|
+
|
|
113
|
+
:source_uuid: 6f850537-5e8a-4e57-95c8-f57a099ed8f3
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
# ---------------------------------------------------------------------------
|
|
117
|
+
# Superstate CRR parameters
|
|
118
|
+
# ---------------------------------------------------------------------------
|
|
119
|
+
|
|
120
|
+
SUPERSTATE_CRR: Final[float] = 0.045
|
|
121
|
+
"""Instance Financial CRR for Superstate (USCC) exposures.
|
|
122
|
+
|
|
123
|
+
:source_uuid: ffca1065-7f92-4815-8a65-52bdbc82c558
|
|
124
|
+
"""
|
|
125
|
+
|
|
126
|
+
# ---------------------------------------------------------------------------
|
|
127
|
+
# Aggregate exposure limit parameters
|
|
128
|
+
# ---------------------------------------------------------------------------
|
|
129
|
+
|
|
130
|
+
ETHENA_AGGREGATE_EXPOSURE_LIMIT: Final[float] = 1_300_000_000.0
|
|
131
|
+
"""Maximum Ethena Aggregate Exposure across all Prime Agents, in USDS.
|
|
132
|
+
|
|
133
|
+
:source_uuid: 176c8562-848c-4868-8490-8e64da24adcd
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
SUPERSTATE_AGGREGATE_EXPOSURE_LIMIT: Final[float] = 500_000_000.0
|
|
137
|
+
"""Maximum Superstate Aggregate Exposure across all Prime Agents, in USDS.
|
|
138
|
+
|
|
139
|
+
:source_uuid: ea606bf7-6dbf-41d2-a993-79b66c56b7c2
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
# ---------------------------------------------------------------------------
|
|
143
|
+
# Superstate deployment limit parameters
|
|
144
|
+
# ---------------------------------------------------------------------------
|
|
145
|
+
|
|
146
|
+
SUPERSTATE_INITIAL_DEPLOYMENT_LIMIT: Final[float] = 20_000_000.0
|
|
147
|
+
"""Maximum first deployment of capital into Superstate exposures, in USDS.
|
|
148
|
+
|
|
149
|
+
:source_uuid: 3fd57a2e-3e2a-41c1-a9f7-cfbe58799837
|
|
150
|
+
"""
|
|
151
|
+
|
|
152
|
+
SUPERSTATE_SUBSEQUENT_DEPLOYMENT_LIMIT: Final[float] = 50_000_000.0
|
|
153
|
+
"""Maximum per-deployment amount for subsequent Superstate deployments, in USDS.
|
|
154
|
+
Each subsequent deployment requires Core Council Risk Advisor approval.
|
|
155
|
+
|
|
156
|
+
:source_uuid: e36d7537-95f8-4156-955f-bc74d37935d8
|
|
157
|
+
"""
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
# ---------------------------------------------------------------------------
|
|
161
|
+
# Pendle Ethena CRR formulas
|
|
162
|
+
# ---------------------------------------------------------------------------
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def pendle_ethena_crr_at_maturity_leq_six_months(
|
|
166
|
+
current_time: int,
|
|
167
|
+
start_time: int,
|
|
168
|
+
) -> float:
|
|
169
|
+
"""CRR = max(finalValue, initialValue - (currentTime - startTime) * decayRatePerSecond).
|
|
170
|
+
|
|
171
|
+
Clamped at the final CRR so a PT past expiration returns the at-maturity
|
|
172
|
+
value rather than a negative number.
|
|
173
|
+
|
|
174
|
+
:source_uuid: 6ed19cc0-5447-4df9-a9c1-45c8730f5f44
|
|
175
|
+
"""
|
|
176
|
+
elapsed: int = current_time - start_time
|
|
177
|
+
return max(
|
|
178
|
+
PENDLE_ETHENA_FINAL_CRR,
|
|
179
|
+
PENDLE_ETHENA_INITIAL_CRR - (elapsed * PENDLE_ETHENA_DECAY_RATE_PER_SECOND),
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def pendle_ethena_crr(
|
|
184
|
+
current_time: int,
|
|
185
|
+
expiration_time: int,
|
|
186
|
+
) -> float:
|
|
187
|
+
"""Returns 1.0 (100% Instance Total CRR) when maturity exceeds six months,
|
|
188
|
+
otherwise the linearly decaying CRR.
|
|
189
|
+
|
|
190
|
+
:source_uuid: 4094c159-9132-454a-81be-361a461b5098
|
|
191
|
+
"""
|
|
192
|
+
time_to_maturity: int = expiration_time - current_time
|
|
193
|
+
start_time: int = expiration_time - PENDLE_ETHENA_MATURITY_THRESHOLD_SECONDS
|
|
194
|
+
return (
|
|
195
|
+
1.0
|
|
196
|
+
if time_to_maturity > PENDLE_ETHENA_MATURITY_THRESHOLD_SECONDS
|
|
197
|
+
else pendle_ethena_crr_at_maturity_leq_six_months(current_time, start_time)
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
# ---------------------------------------------------------------------------
|
|
202
|
+
# Position classification predicates
|
|
203
|
+
# ---------------------------------------------------------------------------
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def is_direct_ethena_position(
|
|
207
|
+
position: Position,
|
|
208
|
+
) -> bool:
|
|
209
|
+
"""True when the position's token is a direct Ethena token.
|
|
210
|
+
|
|
211
|
+
:source_uuid: e0fa035c-e8f3-4cd2-8ca1-a6afbd1825eb
|
|
212
|
+
"""
|
|
213
|
+
return position.asset.token in DIRECT_ETHENA_TOKENS
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def is_pendle_ethena_position(
|
|
217
|
+
position: Position,
|
|
218
|
+
) -> bool:
|
|
219
|
+
"""True when the position's token is a Pendle Ethena token.
|
|
220
|
+
|
|
221
|
+
:source_uuid: 4094c159-9132-454a-81be-361a461b5098
|
|
222
|
+
"""
|
|
223
|
+
return position.asset.token in PENDLE_ETHENA_TOKENS
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def is_ethena_position(
|
|
227
|
+
position: Position,
|
|
228
|
+
) -> bool:
|
|
229
|
+
"""True when the position is either a direct or a Pendle Ethena holding.
|
|
230
|
+
|
|
231
|
+
:source_uuid: 7ce05a43-e3ec-4c54-a11e-30e56526cfdd
|
|
232
|
+
"""
|
|
233
|
+
return is_direct_ethena_position(position) or is_pendle_ethena_position(position)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def is_superstate_position(
|
|
237
|
+
position: Position,
|
|
238
|
+
) -> bool:
|
|
239
|
+
"""True when the position's protocol is Superstate.
|
|
240
|
+
|
|
241
|
+
:source_uuid: ffca1065-7f92-4815-8a65-52bdbc82c558
|
|
242
|
+
"""
|
|
243
|
+
return position.asset.protocol == Protocol.SUPERSTATE
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def is_recognised_perpetual_position(
|
|
247
|
+
position: Position,
|
|
248
|
+
) -> bool:
|
|
249
|
+
"""True when the position falls under one of the perpetual-position CRRs.
|
|
250
|
+
|
|
251
|
+
Defensive predicate for callers. The aggregator silently contributes 0
|
|
252
|
+
for positions that aren't recognised perpetual-position holdings (the
|
|
253
|
+
spec subset disallows ``raise`` in function bodies, so loud rejection
|
|
254
|
+
must happen caller-side); use ``all(map(is_recognised_perpetual_position, positions))``
|
|
255
|
+
on the input list before calling ``instance_perpetual_rrc_usd`` to catch
|
|
256
|
+
grouping errors.
|
|
257
|
+
|
|
258
|
+
:source_uuid: 69fac7fa-6168-4b74-99cc-28b557826556
|
|
259
|
+
"""
|
|
260
|
+
return (
|
|
261
|
+
is_direct_ethena_position(position)
|
|
262
|
+
or is_pendle_ethena_position(position)
|
|
263
|
+
or is_superstate_position(position)
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
# ---------------------------------------------------------------------------
|
|
268
|
+
# Pendle Ethena portfolio CRR formulas
|
|
269
|
+
# ---------------------------------------------------------------------------
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def pendle_ethena_aggregate_exposure(
|
|
273
|
+
pendle_positions: list[Position],
|
|
274
|
+
) -> float:
|
|
275
|
+
"""Sum of value_usd across all Pendle Ethena positions.
|
|
276
|
+
|
|
277
|
+
:source_uuid: 6f850537-5e8a-4e57-95c8-f57a099ed8f3
|
|
278
|
+
"""
|
|
279
|
+
return sum(p.value_usd for p in pendle_positions)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def pendle_ethena_concentration_breached(
|
|
283
|
+
pendle_positions: list[Position],
|
|
284
|
+
total_allocation_investments: float,
|
|
285
|
+
) -> bool:
|
|
286
|
+
"""True when Pendle Ethena exposure exceeds the concentration limit.
|
|
287
|
+
|
|
288
|
+
:source_uuid: 6f850537-5e8a-4e57-95c8-f57a099ed8f3
|
|
289
|
+
"""
|
|
290
|
+
aggregate: float = pendle_ethena_aggregate_exposure(pendle_positions)
|
|
291
|
+
concentration: float = aggregate / total_allocation_investments
|
|
292
|
+
return concentration > PENDLE_ETHENA_CONCENTRATION_LIMIT
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def pendle_ethena_position_effective_crr(
|
|
296
|
+
position: Position,
|
|
297
|
+
pendle_positions: list[Position],
|
|
298
|
+
total_allocation_investments: float,
|
|
299
|
+
current_time: int,
|
|
300
|
+
) -> float:
|
|
301
|
+
"""Per-position Pendle Ethena CRR with portfolio-level concentration override.
|
|
302
|
+
|
|
303
|
+
Applies 100% Instance Total CRR if the portfolio concentration is breached;
|
|
304
|
+
otherwise returns the time-decay CRR for this position's expiration.
|
|
305
|
+
|
|
306
|
+
The ``cast(int, ...)`` at the read site narrows ``int | None`` to ``int``
|
|
307
|
+
for the type checker. This is safe because the ``Position`` validator
|
|
308
|
+
guarantees Pendle tokens carry a non-``None`` ``pt_expiration_time``; we
|
|
309
|
+
use ``cast`` rather than ``assert`` because the spec subset disallows
|
|
310
|
+
``assert`` (see ``spec_validator/checker.py``).
|
|
311
|
+
|
|
312
|
+
:source_uuid: 6f850537-5e8a-4e57-95c8-f57a099ed8f3
|
|
313
|
+
"""
|
|
314
|
+
breached: bool = pendle_ethena_concentration_breached(
|
|
315
|
+
pendle_positions, total_allocation_investments
|
|
316
|
+
)
|
|
317
|
+
base_crr: float = pendle_ethena_crr(current_time, cast(int, position.pt_expiration_time))
|
|
318
|
+
return 1.0 if breached else base_crr
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
# ---------------------------------------------------------------------------
|
|
322
|
+
# Aggregate exposure formulas
|
|
323
|
+
# ---------------------------------------------------------------------------
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def ethena_exposure_limit(
|
|
327
|
+
_agent: EthenaEligiblePrimeAgent,
|
|
328
|
+
) -> float:
|
|
329
|
+
"""Each eligible Prime Agent receives one half of the aggregate limit.
|
|
330
|
+
|
|
331
|
+
:source_uuid: 8e120edf-c87b-4d99-8f2a-65fb49bcc3b7
|
|
332
|
+
"""
|
|
333
|
+
return ETHENA_AGGREGATE_EXPOSURE_LIMIT / 2.0
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
# ---------------------------------------------------------------------------
|
|
337
|
+
# Boolean constraints — Ethena
|
|
338
|
+
# ---------------------------------------------------------------------------
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def ethena_exposure(
|
|
342
|
+
positions: list[Position],
|
|
343
|
+
) -> float:
|
|
344
|
+
"""Sum of value_usd across all direct and Pendle Ethena positions.
|
|
345
|
+
|
|
346
|
+
:source_uuid: 642b6bee-9702-4339-bda3-3a35d025bbcc
|
|
347
|
+
"""
|
|
348
|
+
return sum(p.value_usd for p in positions if is_ethena_position(p))
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def ethena_exposure_within_limit(
|
|
352
|
+
agent: EthenaEligiblePrimeAgent,
|
|
353
|
+
positions: list[Position],
|
|
354
|
+
) -> bool:
|
|
355
|
+
"""Constraint: agent's Ethena exposure must not exceed its limit.
|
|
356
|
+
|
|
357
|
+
No Prime Agent may make an investment that would cause its Ethena Exposures
|
|
358
|
+
to exceed its Ethena Exposure Limit.
|
|
359
|
+
|
|
360
|
+
:source_uuid: 50854ba8-c788-4df5-9c70-b6f070a28bfd
|
|
361
|
+
"""
|
|
362
|
+
return ethena_exposure(positions) <= ethena_exposure_limit(agent)
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def ethena_aggregate_within_limit(
|
|
366
|
+
positions: list[Position],
|
|
367
|
+
) -> bool:
|
|
368
|
+
"""Constraint: total Ethena exposure across all Prime Agents.
|
|
369
|
+
|
|
370
|
+
Ethena Aggregate Exposure must not exceed the aggregate limit.
|
|
371
|
+
|
|
372
|
+
:source_uuid: 176c8562-848c-4868-8490-8e64da24adcd
|
|
373
|
+
"""
|
|
374
|
+
return ethena_exposure(positions) <= ETHENA_AGGREGATE_EXPOSURE_LIMIT
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
# ---------------------------------------------------------------------------
|
|
378
|
+
# Boolean constraints — Superstate
|
|
379
|
+
# ---------------------------------------------------------------------------
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def superstate_exposure(
|
|
383
|
+
positions: list[Position],
|
|
384
|
+
) -> float:
|
|
385
|
+
"""Sum of value_usd across all Superstate positions.
|
|
386
|
+
|
|
387
|
+
:source_uuid: 6cf2221f-214c-4bc2-8de1-b0d6adb2a327
|
|
388
|
+
"""
|
|
389
|
+
return sum(p.value_usd for p in positions if is_superstate_position(p))
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def superstate_aggregate_within_limit(
|
|
393
|
+
positions: list[Position],
|
|
394
|
+
) -> bool:
|
|
395
|
+
"""Constraint: total Superstate exposure across all Prime Agents.
|
|
396
|
+
|
|
397
|
+
No Prime Agent may make an investment that would cause the Superstate
|
|
398
|
+
Aggregate Exposure to exceed the limit.
|
|
399
|
+
|
|
400
|
+
:source_uuid: a75e7eea-7567-4f5c-aba4-6d41fb6732dd
|
|
401
|
+
"""
|
|
402
|
+
return superstate_exposure(positions) <= SUPERSTATE_AGGREGATE_EXPOSURE_LIMIT
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
def superstate_initial_deployment_compliant(
|
|
406
|
+
deployment: SuperstateInitialDeployment,
|
|
407
|
+
) -> bool:
|
|
408
|
+
"""Constraint: a Prime Agent's first Superstate deployment.
|
|
409
|
+
|
|
410
|
+
Initial deployment into Superstate must not exceed the limit.
|
|
411
|
+
|
|
412
|
+
:source_uuid: 3fd57a2e-3e2a-41c1-a9f7-cfbe58799837
|
|
413
|
+
"""
|
|
414
|
+
return deployment.amount_usds <= SUPERSTATE_INITIAL_DEPLOYMENT_LIMIT
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
def superstate_subsequent_deployment_approved(
|
|
418
|
+
deployment: SuperstateSubsequentDeployment,
|
|
419
|
+
) -> bool:
|
|
420
|
+
"""Sub-predicate: approval covers the deployment amount.
|
|
421
|
+
|
|
422
|
+
:source_uuid: e36d7537-95f8-4156-955f-bc74d37935d8
|
|
423
|
+
"""
|
|
424
|
+
return deployment.approval.approved_amount_usds >= deployment.amount_usds
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
def superstate_subsequent_deployment_compliant(
|
|
428
|
+
deployment: SuperstateSubsequentDeployment,
|
|
429
|
+
) -> bool:
|
|
430
|
+
"""Constraint: each subsequent Superstate deployment by a Prime Agent.
|
|
431
|
+
|
|
432
|
+
Each subsequent Superstate deployment must not exceed the limit.
|
|
433
|
+
Additionally requires approval from the Core Council Risk Advisor.
|
|
434
|
+
|
|
435
|
+
:source_uuid: e36d7537-95f8-4156-955f-bc74d37935d8
|
|
436
|
+
"""
|
|
437
|
+
return (
|
|
438
|
+
deployment.amount_usds <= SUPERSTATE_SUBSEQUENT_DEPLOYMENT_LIMIT
|
|
439
|
+
and superstate_subsequent_deployment_approved(deployment)
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
# ---------------------------------------------------------------------------
|
|
444
|
+
# Instance Financial RRC aggregator
|
|
445
|
+
# ---------------------------------------------------------------------------
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
def instance_perpetual_rrc_usd(
|
|
449
|
+
positions: list[Position],
|
|
450
|
+
portfolio: list[Position],
|
|
451
|
+
total_allocation_investments: float,
|
|
452
|
+
current_time: int,
|
|
453
|
+
) -> float:
|
|
454
|
+
"""Sum of per-category capital charges for perpetual-position exposures.
|
|
455
|
+
|
|
456
|
+
Signature follows the unified per-Instance contract proposed in
|
|
457
|
+
``align_rrc.md`` §1: take everything needed for one Allocation Instance
|
|
458
|
+
and return one USD float.
|
|
459
|
+
|
|
460
|
+
Parameters:
|
|
461
|
+
|
|
462
|
+
- ``positions``: holdings in this Allocation Instance. The token /
|
|
463
|
+
``pt_expiration_time`` invariant is enforced at ``Position`` construction
|
|
464
|
+
(see ``spec_support.pendle_validation``), so the misclassification cases
|
|
465
|
+
that previously needed runtime guards here can no longer reach this
|
|
466
|
+
function. Partition is by token predicate ``is_pendle_ethena_position``.
|
|
467
|
+
- ``portfolio``: reserved for future portfolio-context rules (e.g. rules
|
|
468
|
+
that look at non-Pendle holdings as portfolio context for perpetual-
|
|
469
|
+
position constraints). **Currently unused.** Included now for forward
|
|
470
|
+
compatibility with ``align_rrc.md`` §1 so the four RRC modules adopt a
|
|
471
|
+
common shape; the only portfolio-context check today is the Pendle
|
|
472
|
+
concentration limit, whose denominator is ``total_allocation_investments``
|
|
473
|
+
per Atlas A.3.2.2.1.1.1.2.1.1.1.3.3 ("their total Allocation System
|
|
474
|
+
Investments"), not a sum over ``portfolio``.
|
|
475
|
+
- ``total_allocation_investments``: the Prime Agent's total Allocation
|
|
476
|
+
System Investments, used as the Pendle concentration denominator.
|
|
477
|
+
- ``current_time``: reference timestamp (seconds) for Pendle time-decay.
|
|
478
|
+
|
|
479
|
+
The aggregator silently contributes 0 for any position that isn't a
|
|
480
|
+
recognised perpetual-position holding. The spec subset disallows
|
|
481
|
+
``raise`` in function bodies, so loud rejection of caller-side grouping
|
|
482
|
+
errors lives in ``is_recognised_perpetual_position``: callers should run
|
|
483
|
+
``all(map(is_recognised_perpetual_position, positions))`` before calling
|
|
484
|
+
this aggregator to catch non-perpetual holdings (USDC, ETH, lending
|
|
485
|
+
positions, etc.) that would otherwise silently dilute the charge.
|
|
486
|
+
|
|
487
|
+
:source_uuid: 69fac7fa-6168-4b74-99cc-28b557826556
|
|
488
|
+
"""
|
|
489
|
+
_ = portfolio # reserved for future portfolio-context rules; see docstring
|
|
490
|
+
|
|
491
|
+
pendle_subset: list[Position] = [p for p in positions if is_pendle_ethena_position(p)]
|
|
492
|
+
non_pendle_subset: list[Position] = [p for p in positions if not is_pendle_ethena_position(p)]
|
|
493
|
+
|
|
494
|
+
direct_charge: float = sum(
|
|
495
|
+
p.value_usd * DIRECT_ETHENA_CRR for p in non_pendle_subset if is_direct_ethena_position(p)
|
|
496
|
+
)
|
|
497
|
+
superstate_charge: float = sum(
|
|
498
|
+
p.value_usd * SUPERSTATE_CRR for p in non_pendle_subset if is_superstate_position(p)
|
|
499
|
+
)
|
|
500
|
+
pendle_charge: float = sum(
|
|
501
|
+
p.value_usd
|
|
502
|
+
* pendle_ethena_position_effective_crr(
|
|
503
|
+
p, pendle_subset, total_allocation_investments, current_time
|
|
504
|
+
)
|
|
505
|
+
for p in pendle_subset
|
|
506
|
+
)
|
|
507
|
+
return direct_charge + superstate_charge + pendle_charge
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Cross-field validator for Pendle Ethena tokens and pt_expiration_time.
|
|
2
|
+
|
|
3
|
+
Pendle Ethena PT holdings (PT-USDe / PT-sUSDe) carry a fixed maturity, and the
|
|
4
|
+
Instance Financial CRR for these holdings is a function of time-to-maturity
|
|
5
|
+
(Atlas A.3.2.2.1.1.1.2.1.1.1.3.2). Other tokens have no concept of expiration.
|
|
6
|
+
|
|
7
|
+
This helper enforces both directions of the constraint at construction time:
|
|
8
|
+
|
|
9
|
+
- A Pendle Ethena token must carry a non-``None`` ``pt_expiration_time``.
|
|
10
|
+
- A non-Pendle token must not carry a ``pt_expiration_time``.
|
|
11
|
+
|
|
12
|
+
Catching this at ``Position`` construction (rather than at the perpetual-RRC
|
|
13
|
+
aggregator) means a misclassification fails at the test or data-loading site,
|
|
14
|
+
not deep inside the capital calculation where the symptom would be a silent
|
|
15
|
+
0% CRR or a meaningless time-decay applied to an unrelated asset.
|
|
16
|
+
|
|
17
|
+
Lives under ``spec_support`` rather than ``spec`` because it must be able to
|
|
18
|
+
``raise`` (the spec subset / AXS plugin disallows ``raise`` in spec code) and
|
|
19
|
+
because it is wired into ``Position`` via the ``validated_dataclass``
|
|
20
|
+
decorator rather than as a method on the dataclass itself (the spec subset
|
|
21
|
+
also disallows methods on dataclasses).
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from axis_synome.spec.asc.entities.tokens import PENDLE_ETHENA_TOKENS
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def validate_pendle_token_has_expiration(position) -> None:
|
|
28
|
+
"""Enforce the token / pt_expiration_time invariant on a Position.
|
|
29
|
+
|
|
30
|
+
Raises ``ValueError`` if:
|
|
31
|
+
|
|
32
|
+
- ``position.asset.token`` is a Pendle Ethena token and
|
|
33
|
+
``position.pt_expiration_time`` is ``None``.
|
|
34
|
+
- ``position.asset.token`` is not a Pendle Ethena token and
|
|
35
|
+
``position.pt_expiration_time`` is set.
|
|
36
|
+
"""
|
|
37
|
+
token = position.asset.token
|
|
38
|
+
pt_expiration_time = position.pt_expiration_time
|
|
39
|
+
is_pendle = token in PENDLE_ETHENA_TOKENS
|
|
40
|
+
has_expiration = pt_expiration_time is not None
|
|
41
|
+
|
|
42
|
+
if is_pendle and not has_expiration:
|
|
43
|
+
raise ValueError(
|
|
44
|
+
f"Pendle Ethena token {token} requires pt_expiration_time; "
|
|
45
|
+
"PT holdings carry a fixed maturity that the time-decay CRR depends on."
|
|
46
|
+
)
|
|
47
|
+
if not is_pendle and has_expiration:
|
|
48
|
+
raise ValueError(
|
|
49
|
+
f"Token {token} is not a Pendle Ethena token but pt_expiration_time "
|
|
50
|
+
f"is set to {pt_expiration_time}; pt_expiration_time is meaningful "
|
|
51
|
+
"only for PT-USDe / PT-sUSDe holdings."
|
|
52
|
+
)
|