axis-synome 0.1.0.dev31__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.
Files changed (101) hide show
  1. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/PKG-INFO +1 -1
  2. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/src/axis_synome/_version.py +2 -2
  3. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/src/axis_synome/spec/asc/entities/tokens.py +4 -0
  4. axis_synome-0.1.0.dev32/src/axis_synome/spec/asc/entities/types.py +23 -0
  5. axis_synome-0.1.0.dev32/src/axis_synome/spec/risk_capital/financial_rrc/__init__.py +5 -0
  6. axis_synome-0.1.0.dev32/src/axis_synome/spec/risk_capital/financial_rrc/entities.py +35 -0
  7. axis_synome-0.1.0.dev32/src/axis_synome/spec/risk_capital/financial_rrc/perpetual_positions.py +507 -0
  8. axis_synome-0.1.0.dev32/src/axis_synome/spec_support/pendle_validation.py +52 -0
  9. axis_synome-0.1.0.dev32/src/axis_synome/spec_support/validated_dataclass.py +50 -0
  10. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/src/axis_synome.egg-info/PKG-INFO +1 -1
  11. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/src/axis_synome.egg-info/SOURCES.txt +6 -0
  12. axis_synome-0.1.0.dev32/tests/axis_synome/risk_capital/financial_rrc/test_perpetual_positions.py +460 -0
  13. axis_synome-0.1.0.dev32/tests/axis_synome/suraf/suraf_client/__init__.py +0 -0
  14. axis_synome-0.1.0.dev31/src/axis_synome/spec/asc/entities/types.py +0 -15
  15. axis_synome-0.1.0.dev31/src/axis_synome/spec_support/validated_dataclass.py +0 -36
  16. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/.flake8 +0 -0
  17. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/README.md +0 -0
  18. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/WRITING_SPECS.md +0 -0
  19. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/pyproject.toml +0 -0
  20. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/setup.cfg +0 -0
  21. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/src/axis_synome/__init__.py +0 -0
  22. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/src/axis_synome/spec/__init__.py +0 -0
  23. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/src/axis_synome/spec/asc/README.md +0 -0
  24. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/src/axis_synome/spec/asc/entities/__init__.py +0 -0
  25. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/src/axis_synome/spec/asc/entities/assets.py +0 -0
  26. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/src/axis_synome/spec/asc/entities/assets_by_prime.py +0 -0
  27. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/src/axis_synome/spec/asc/entities/networks.py +0 -0
  28. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/src/axis_synome/spec/asc/entities/primes.py +0 -0
  29. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/src/axis_synome/spec/asc/entities/protocol_sets.py +0 -0
  30. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/src/axis_synome/spec/asc/formulas/asc.py +0 -0
  31. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/src/axis_synome/spec/asc/formulas/asc_collateral_ratio.py +0 -0
  32. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/src/axis_synome/spec/asc/formulas/asc_incentive.py +0 -0
  33. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/src/axis_synome/spec/asc/formulas/dab.py +0 -0
  34. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/src/axis_synome/spec/asc/formulas/latent_asc.py +0 -0
  35. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/src/axis_synome/spec/asc/formulas/ratio_latent_asc.py +0 -0
  36. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/src/axis_synome/spec/asc/formulas/resting_asc.py +0 -0
  37. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/src/axis_synome/spec/codegen_test/entities/agents.py +0 -0
  38. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/src/axis_synome/spec/crypto_lending/__init__.py +0 -0
  39. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/src/axis_synome/spec/crypto_lending/formulas/__init__.py +0 -0
  40. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/src/axis_synome/spec/crypto_lending/formulas/lif.py +0 -0
  41. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/src/axis_synome/spec/risk_capital/__init__.py +0 -0
  42. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/src/axis_synome/spec/risk_capital/formulas/__init__.py +0 -0
  43. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/src/axis_synome/spec/risk_capital/formulas/required_risk_capital.py +0 -0
  44. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/src/axis_synome/spec/suraf/README.md +0 -0
  45. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/src/axis_synome/spec/suraf/entities/assessor_score.py +0 -0
  46. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/src/axis_synome/spec/suraf/entities/assets.py +0 -0
  47. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/src/axis_synome/spec/suraf/entities/mappings.py +0 -0
  48. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/src/axis_synome/spec/suraf/formulas/crr.py +0 -0
  49. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/src/axis_synome/spec/suraf/formulas/scoring.py +0 -0
  50. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/src/axis_synome/spec_support/__init__.py +0 -0
  51. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/src/axis_synome/spec_support/evm_address.py +0 -0
  52. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/src/axis_synome/spec_support/runtime/__init__.py +0 -0
  53. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/src/axis_synome/spec_support/runtime/base.py +0 -0
  54. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/src/axis_synome/spec_support/runtime/math.py +0 -0
  55. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/src/axis_synome/spec_support/runtime/reference.py +0 -0
  56. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/src/axis_synome/spec_support/validated_str.py +0 -0
  57. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/src/axis_synome/spec_validator/__init__.py +0 -0
  58. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/src/axis_synome/spec_validator/checker.py +0 -0
  59. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/src/axis_synome/spec_validator/flake8_plugin.py +0 -0
  60. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/src/axis_synome/spec_validator/python_subset.py +0 -0
  61. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/src/axis_synome.egg-info/dependency_links.txt +0 -0
  62. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/src/axis_synome.egg-info/entry_points.txt +0 -0
  63. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/src/axis_synome.egg-info/requires.txt +0 -0
  64. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/src/axis_synome.egg-info/top_level.txt +0 -0
  65. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/tests/axis_synome/asc/__init__.py +0 -0
  66. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/tests/axis_synome/asc/conftest.py +0 -0
  67. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/tests/axis_synome/asc/mocks.py +0 -0
  68. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/tests/axis_synome/asc/test_asc.py +0 -0
  69. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/tests/axis_synome/asc/test_asc_collateral_ratio.py +0 -0
  70. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/tests/axis_synome/asc/test_asc_incentive.py +0 -0
  71. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/tests/axis_synome/asc/test_dab.py +0 -0
  72. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/tests/axis_synome/asc/test_evm_address.py +0 -0
  73. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/tests/axis_synome/asc/test_latent_asc.py +0 -0
  74. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/tests/axis_synome/asc/test_prime_agent_data_validation.py +0 -0
  75. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/tests/axis_synome/asc/test_ratio_latent_asc.py +0 -0
  76. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/tests/axis_synome/asc/test_resting_asc.py +0 -0
  77. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/tests/axis_synome/risk_capital/__init__.py +0 -0
  78. {axis_synome-0.1.0.dev31/tests/axis_synome/risk_capital/formulas → axis_synome-0.1.0.dev32/tests/axis_synome/risk_capital/financial_rrc}/__init__.py +0 -0
  79. {axis_synome-0.1.0.dev31/tests/axis_synome/spec_support → axis_synome-0.1.0.dev32/tests/axis_synome/risk_capital/formulas}/__init__.py +0 -0
  80. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/tests/axis_synome/risk_capital/formulas/test_loss_given_default.py +0 -0
  81. {axis_synome-0.1.0.dev31/tests/axis_synome/spec_support/runtime → axis_synome-0.1.0.dev32/tests/axis_synome/spec_support}/__init__.py +0 -0
  82. {axis_synome-0.1.0.dev31/tests/axis_synome/suraf → axis_synome-0.1.0.dev32/tests/axis_synome/spec_support/runtime}/__init__.py +0 -0
  83. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/tests/axis_synome/spec_support/runtime/test_base.py +0 -0
  84. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/tests/axis_synome/spec_support/runtime/test_math.py +0 -0
  85. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/tests/axis_synome/spec_validator/test_checker.py +0 -0
  86. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/tests/axis_synome/spec_validator/test_flake8_plugin.py +0 -0
  87. {axis_synome-0.1.0.dev31/tests/axis_synome/suraf/entities → axis_synome-0.1.0.dev32/tests/axis_synome/suraf}/__init__.py +0 -0
  88. {axis_synome-0.1.0.dev31/tests/axis_synome/suraf/formulas → axis_synome-0.1.0.dev32/tests/axis_synome/suraf/entities}/__init__.py +0 -0
  89. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/tests/axis_synome/suraf/entities/test_assessor_score.py +0 -0
  90. {axis_synome-0.1.0.dev31/tests/axis_synome/suraf/suraf_client → axis_synome-0.1.0.dev32/tests/axis_synome/suraf/formulas}/__init__.py +0 -0
  91. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/tests/axis_synome/suraf/formulas/test_crr.py +0 -0
  92. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/tests/axis_synome/suraf/formulas/test_scoring.py +0 -0
  93. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/tests/axis_synome/suraf/static/aave_ausdc/v1/crr_mapping.csv +0 -0
  94. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/tests/axis_synome/suraf/static/aave_ausdc/v1/penalty.csv +0 -0
  95. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/tests/axis_synome/suraf/static/aave_ausdc/v1/scorecards/Assessor_1_scores.csv +0 -0
  96. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/tests/axis_synome/suraf/static/aave_ausdc/v1/scorecards/Assessor_2_scores.csv +0 -0
  97. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/tests/axis_synome/suraf/static/aave_ausdc/v1/scorecards/Assessor_3_scores.csv +0 -0
  98. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/tests/axis_synome/suraf/static/aave_ausdc/v1/weights.csv +0 -0
  99. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/tests/axis_synome/suraf/suraf_client/suraf_client.py +0 -0
  100. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/tests/axis_synome/suraf/suraf_client/test_suraf_client.py +0 -0
  101. {axis_synome-0.1.0.dev31 → axis_synome-0.1.0.dev32}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: axis-synome
3
- Version: 0.1.0.dev31
3
+ Version: 0.1.0.dev32
4
4
  Summary: Axis specification modules (entities, formulas, validators)
5
5
  Author-email: Archon Tech <hello@archontech.ai>
6
6
  License: MIT
@@ -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.dev31'
22
- __version_tuple__ = version_tuple = (0, 1, 0, 'dev31')
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
@@ -56,3 +56,7 @@ class Token(Enum):
56
56
 
57
57
 
58
58
  CASH_STABLECOINS = {Token.USDC, Token.USDS, Token.DAI, Token.USDT}
59
+
60
+ DIRECT_ETHENA_TOKENS = {Token.USDE, Token.S_USDE}
61
+
62
+ PENDLE_ETHENA_TOKENS = {Token.PT_USDE, Token.PT_S_USDE}
@@ -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,5 @@
1
+ """Instance Financial Required Risk Capital.
2
+
3
+ Asset-class-specific risk models for computing the Financial RRC
4
+ component of each Allocation System Instance.
5
+ """
@@ -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
@@ -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
+ )