axis-synome 0.1.dev184__tar.gz → 0.1.dev185__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.dev184 → axis_synome-0.1.dev185}/PKG-INFO +1 -1
- {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/WRITING_SPECS.md +163 -114
- {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/_version.py +2 -2
- {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec/asc/formulas/asc.py +2 -19
- axis_synome-0.1.dev185/src/axis_synome/spec/asc/formulas/asc_collateral_ratio.py +36 -0
- {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec/asc/formulas/asc_incentive.py +4 -12
- axis_synome-0.1.dev185/src/axis_synome/spec/asc/formulas/dab.py +24 -0
- {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec/asc/formulas/latent_asc.py +3 -12
- {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec/asc/formulas/ratio_latent_asc.py +8 -22
- {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec/asc/formulas/resting_asc.py +3 -10
- {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec/crypto_lending/formulas/lif.py +6 -21
- {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec/risk_capital/formulas/required_risk_capital.py +3 -11
- {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec_validator/checker.py +31 -0
- axis_synome-0.1.dev184/src/axis_synome/spec/asc/formulas/asc_collateral_ratio.py +0 -53
- axis_synome-0.1.dev184/src/axis_synome/spec/asc/formulas/dab.py +0 -30
- {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/.flake8 +0 -0
- {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/.gitignore +0 -0
- {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/README.md +0 -0
- {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/pyproject.toml +0 -0
- {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/__init__.py +0 -0
- {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec/__init__.py +0 -0
- {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec/asc/README.md +0 -0
- {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec/asc/entities/__init__.py +0 -0
- {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec/asc/entities/assets.py +0 -0
- {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec/asc/entities/assets_by_prime.py +0 -0
- {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec/asc/entities/networks.py +0 -0
- {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec/asc/entities/primes.py +0 -0
- {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec/asc/entities/protocol_sets.py +0 -0
- {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec/asc/entities/tokens.py +0 -0
- {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec/asc/entities/types.py +0 -0
- {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec/codegen_test/entities/agents.py +0 -0
- {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec/crypto_lending/__init__.py +0 -0
- {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec/crypto_lending/formulas/__init__.py +0 -0
- {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec/risk_capital/__init__.py +0 -0
- {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec/risk_capital/formulas/__init__.py +0 -0
- {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec_support/__init__.py +0 -0
- {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec_support/evm_address.py +0 -0
- {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec_support/metadata.py +0 -0
- {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec_support/validated_dataclass.py +0 -0
- {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec_support/validated_str.py +0 -0
- {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec_validator/__init__.py +0 -0
- {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec_validator/flake8_plugin.py +0 -0
- {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec_validator/python_subset.py +0 -0
- {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/tests/axis_synome/asc/__init__.py +0 -0
- {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/tests/axis_synome/asc/conftest.py +0 -0
- {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/tests/axis_synome/asc/mocks.py +0 -0
- {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/tests/axis_synome/asc/test_asc.py +0 -0
- {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/tests/axis_synome/asc/test_asc_collateral_ratio.py +0 -0
- {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/tests/axis_synome/asc/test_asc_incentive.py +0 -0
- {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/tests/axis_synome/asc/test_asc_spec_client_parity.py +0 -0
- {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/tests/axis_synome/asc/test_dab.py +0 -0
- {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/tests/axis_synome/asc/test_evm_address.py +0 -0
- {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/tests/axis_synome/asc/test_latent_asc.py +0 -0
- {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/tests/axis_synome/asc/test_prime_agent_data_validation.py +0 -0
- {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/tests/axis_synome/asc/test_ratio_latent_asc.py +0 -0
- {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/tests/axis_synome/asc/test_resting_asc.py +0 -0
- {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/tests/axis_synome/risk_capital/__init__.py +0 -0
- {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/tests/axis_synome/risk_capital/formulas/__init__.py +0 -0
- {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/tests/axis_synome/risk_capital/formulas/test_loss_given_default.py +0 -0
- {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/tests/axis_synome/spec_validator/test_checker.py +0 -0
- {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/tests/axis_synome/spec_validator/test_flake8_plugin.py +0 -0
- {axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: axis-synome
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.dev185
|
|
4
4
|
Summary: Axis specification modules (entities, formulas, validators)
|
|
5
5
|
Project-URL: Repository, https://github.com/archon-research/next-gen-atlas
|
|
6
6
|
Project-URL: Documentation, https://github.com/archon-research/next-gen-atlas
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# How to Write Axis Specs
|
|
2
2
|
|
|
3
|
-
This guide distills the principles and conventions used in `
|
|
3
|
+
This guide distills the principles and conventions used in `python/axis_synome/src/axis_synome/spec`. It is expected that new specs follow these
|
|
4
4
|
principles as much as possible. The purpose of the principles and conventions is ensure that specs remain parseable, convertible to (SymPy-backed) mathematical expressions, and that they become easy to read and navigate.
|
|
5
5
|
|
|
6
6
|
This is to facilitate the following goals, paraphrased from the [Axis Synome TechSpec](https://www.notion.so/Axis-Synome-Tech-Spec-30ab87693a5a8024ab3fe29ba5c3421c?source=copy_link):
|
|
@@ -17,10 +17,9 @@ This is to facilitate the following goals, paraphrased from the [Axis Synome Tec
|
|
|
17
17
|
Specs encode Atlas calculation rules directly in typed Python.
|
|
18
18
|
They are declarative, deterministic, side‑effect‑free computations; no I/O or mutable state.
|
|
19
19
|
|
|
20
|
-
They are organized into the `
|
|
21
|
-
generally correspond to risk calculations for specific asset types (applicable to Prime agents), but this is not a fully enforced/enforceable dichotomy. Each of these submodules contains formulas:
|
|
20
|
+
They are organized into the `axis_synome.spec` namespace which contains a submodule/subdirectory `entities/` that defines dataclasses and Enums for domain inputs and constants. Many of these correspond to or stem from Atlas abstractions (typically, A6). In addition there are submodules grouped by calculation concepts (Atlas‑style, e.g., capital composition, credit risk, exposure transformation/mitigation). Asset classes live in `entities/` rather than as top‑level spec organization. This structure is pragmatic and reversible. Each of these submodules contains formulas:
|
|
22
21
|
|
|
23
|
-
- Formulas are pure functions over entities that specify computations
|
|
22
|
+
- Formulas are pure functions over entities that specify computations.
|
|
24
23
|
- Each set of formulas that corresponds to a specific risk calculation, e.g., for a specific asset class is organized into its own namespace.
|
|
25
24
|
- Functions are kept small; compose into top‑level results.
|
|
26
25
|
- Independently testable and extractor‑friendly.
|
|
@@ -47,27 +46,47 @@ Relations are traversable: clients and tooling can evaluate predicates across th
|
|
|
47
46
|
|
|
48
47
|
## Building Blocks
|
|
49
48
|
|
|
50
|
-
###
|
|
49
|
+
### Documentation and Provenance
|
|
51
50
|
|
|
52
|
-
|
|
51
|
+
- Prefer function docstrings for descriptive text. Put the human‑readable description of what a formula does into the function’s docstring. Docstrings are code‑adjacent, IDE/hover friendly, and can be used to generate documentation.
|
|
52
|
+
- As functions are also math formulas and documentation from docstrs will also be used for the mathematical exposition, avoid Python specific terminology.
|
|
53
|
+
- Avoid duplicating long‑form descriptions in metadata.
|
|
54
|
+
- Put Atlas provenance in docstrings using reST-style fields such as `:source_uuid: ...`.
|
|
55
|
+
- In `python/axis_synome/src/**/formulas/*.py`, public functions are treated as formulas by convention, so plain return types like `-> float` or `-> bool` are preferred.
|
|
56
|
+
- Outside formula modules, docstring fields such as `:source_uuid:` provide explicit opt-in for formula discovery.
|
|
57
|
+
- For constants and parameters, use `Final[...]` and trailing string literal docstrings.
|
|
58
|
+
- Avoid per‑formula READMEs. Prefer generating documentation from docstrings to keep content in sync with code.
|
|
53
59
|
|
|
54
|
-
|
|
60
|
+
Examples:
|
|
55
61
|
|
|
56
62
|
```python
|
|
57
|
-
(
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
63
|
+
def asc(prime: EligiblePrimeAgentASC, positions: list[Position]) -> float:
|
|
64
|
+
"""
|
|
65
|
+
Total Actively Stabilizing Capital for a prime.
|
|
66
|
+
|
|
67
|
+
Sum of resting and latent ASC for a given prime over its positions.
|
|
68
|
+
|
|
69
|
+
:source_uuid: 62495dee-8d2a-45d4-87c4-01150e3db3c8
|
|
70
|
+
"""
|
|
71
|
+
...
|
|
72
|
+
|
|
64
73
|
|
|
65
|
-
|
|
74
|
+
# If no corresponding Atlas section exists, just omit the marker and keep the docstring:
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def asc(prime: EligiblePrimeAgentASC, positions: list[Position]) -> float:
|
|
78
|
+
"""
|
|
79
|
+
Total Actively Stabilizing Capital for a prime.
|
|
80
|
+
|
|
81
|
+
Sum of resting and latent ASC for a given prime over its positions.
|
|
82
|
+
"""
|
|
83
|
+
...
|
|
84
|
+
```
|
|
66
85
|
|
|
67
86
|
### Parameters
|
|
68
87
|
|
|
69
|
-
- Use `Final[
|
|
70
|
-
- Parameter names
|
|
88
|
+
- Use `Final[T]` for governance‑set constants.
|
|
89
|
+
- Parameter names plus structured docstring markers like `:source_uuid:` provide machine‑readable identities used in the graph, audits, and codegen. Place human‑readable descriptions in trailing constant docstrings.
|
|
71
90
|
|
|
72
91
|
### Units
|
|
73
92
|
|
|
@@ -81,25 +100,38 @@ For instance, a float-compatible `usd` type may be introduced (this is used in s
|
|
|
81
100
|
|
|
82
101
|
### Parameters
|
|
83
102
|
|
|
84
|
-
- Represent global
|
|
85
|
-
- Use `typing.Final`
|
|
103
|
+
- Represent global parameters as module‑level, annotated constants.
|
|
104
|
+
- Use `typing.Final` as the constant marker and place descriptive text and Atlas provenance in the trailing string literal docstring.
|
|
86
105
|
- When parameters vary per entity (e.g., per Prime), model them as fields on the entity dataclass with validation (e.g., `pydantic.dataclasses.dataclass` + `Field`).
|
|
106
|
+
- If a constant has no relevant Atlas section, just omit the `:source_uuid:` marker and provide the human description as the trailing string literal docstring.
|
|
87
107
|
|
|
88
108
|
Example:
|
|
89
109
|
|
|
90
110
|
```python
|
|
91
|
-
from typing import
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
111
|
+
from typing import Final
|
|
112
|
+
|
|
113
|
+
MAX_LATENT_ASC: Final[usd] = 0.25 # usd is a floating point value compatible with Python float
|
|
114
|
+
"""Maximum latent ASC fraction.
|
|
115
|
+
|
|
116
|
+
:source_uuid: 5e300cdb-b221-4b6f-9c4a-11502133a1f9
|
|
117
|
+
"""
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Constant descriptions (docstrings)
|
|
121
|
+
|
|
122
|
+
- For constants, place the human‑readable description as a bare string literal immediately following the assignment. The parser treats this trailing string as the constant’s “docstring” and extracts it into the graph.
|
|
123
|
+
- Keep `source_uuid` and other structured fields in the trailing docstring using markers such as `:source_uuid: ...`.
|
|
124
|
+
|
|
125
|
+
Example:
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
from typing import Final
|
|
129
|
+
|
|
130
|
+
DAB_REQUIRED_PCT: Final[float] = 0.25
|
|
131
|
+
"""Required DAB as a fraction of total ASC.
|
|
132
|
+
|
|
133
|
+
:source_uuid: 1e129119-a2ce-4978-b235-c50f2a1c5e2e
|
|
134
|
+
"""
|
|
103
135
|
```
|
|
104
136
|
|
|
105
137
|
---
|
|
@@ -137,10 +169,16 @@ class Position:
|
|
|
137
169
|
Note that all entity fields are typed to a closed set, the set of allowed values are in each case
|
|
138
170
|
a fixed set instiantiated objects. `value_usd` is additionally restricted with an annotation ensuring that only positive values are supplied.
|
|
139
171
|
|
|
172
|
+
#### Entities Layout (Formula‑Local vs Common)
|
|
173
|
+
|
|
174
|
+
- Start formula‑local: keep entities within each formula’s package initially (`axis/<formula>/entities/`).
|
|
175
|
+
- Uplift to shared only when needed: introduce shared entities at top level (`axis/entities/`) when the same entities are used by multiple formulas and sharing materially reduces duplication. A pragmatic threshold is when a third, independent formula reuses the same entities.
|
|
176
|
+
- Don’t add empty common folders: only create and populate shared folders when you actually have shared entities.
|
|
177
|
+
- Moving entities is a breaking change: import paths change when uplifting to shared. Provide re‑exports where feasible during transitions and bump the major version when breaking import paths.
|
|
140
178
|
### Formulas
|
|
141
179
|
|
|
142
180
|
- Formula functions are pure: `(inputs) -> output`, but process inputs to intermediate values by calling other formula functions (or by utilizing a restricted set of built-in functions and external functions)
|
|
143
|
-
-
|
|
181
|
+
- In formula modules, prefer plain return types and put structured provenance such as `source_uuid` in the docstring. Outside formula modules, docstring fields such as `:source_uuid:` provide explicit opt-in.
|
|
144
182
|
- They accept closed‑world Enums to encode eligibility and preconditions; use union types for overlapping eligible sets when needed.
|
|
145
183
|
- Compose helpers; keep inclusion/exclusion criteria explicit in docstrings.
|
|
146
184
|
|
|
@@ -206,6 +244,7 @@ def asc(prime: EligiblePrimeAgentASC, positions: list[Position]) -> float:
|
|
|
206
244
|
"""
|
|
207
245
|
Total Actively Stabilizing Capital for a prime.
|
|
208
246
|
|
|
247
|
+
Computes the sum of resting and latent ASC for a given prime over its positions.
|
|
209
248
|
"""
|
|
210
249
|
# Enum values are concrete PrimeAgent instances
|
|
211
250
|
resting_asc_usd: float = resting_asc(positions)
|
|
@@ -219,14 +258,8 @@ Each of those assignments induces a formula definition, `formula_id(resting_asc_
|
|
|
219
258
|
|
|
220
259
|
Similarly, the return statement induces a formula definition, `formula_id(asc) = formula_id(resting_asc_usd) + formula_id(latent_asc_usd)`.
|
|
221
260
|
|
|
222
|
-
In
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
```python
|
|
226
|
-
def asc(prime: EligiblePrimeAgentASC, positions: list[Position]) -> Annotated[float, Metadata(...)]:
|
|
227
|
-
):
|
|
228
|
-
...
|
|
229
|
-
```
|
|
261
|
+
In formula modules, the function can just return a plain scalar type and carry provenance in the
|
|
262
|
+
docstring, e.g. `def asc(...) -> float: ...`.
|
|
230
263
|
|
|
231
264
|
#### Boolean functions as constraints
|
|
232
265
|
|
|
@@ -236,7 +269,9 @@ Boolean constraint functions that take closed‑world sets (e.g., `EligiblePrime
|
|
|
236
269
|
Specification functions that take one or more closed-world Entity argument are constraints that should hold true for **all** combinations of the matching inputs to the functions.
|
|
237
270
|
|
|
238
271
|
```python
|
|
239
|
-
def compliant_prime(
|
|
272
|
+
def compliant_prime(
|
|
273
|
+
prime: EligiblePrimeAgentASC,
|
|
274
|
+
) -> bool: ...
|
|
240
275
|
```
|
|
241
276
|
|
|
242
277
|
Multi-input constraints are evaluated across the Cartesian product of their closed-world domains:
|
|
@@ -247,32 +282,43 @@ For instance, we might have a constraint that two distinct primes do not share a
|
|
|
247
282
|
def portfolio_diversification(
|
|
248
283
|
prime_a: PrimeAgentAll,
|
|
249
284
|
prime_b: PrimeAgentAll,
|
|
250
|
-
) ->
|
|
251
|
-
"""Verifies that no two distinct primes hold the same asset above a threshold.
|
|
285
|
+
) -> bool:
|
|
286
|
+
"""Verifies that no two distinct primes hold the same asset above a threshold.
|
|
287
|
+
|
|
288
|
+
:source_uuid: 62495dee-8d2a-45d4-87c4-01150e3db3c8
|
|
289
|
+
"""
|
|
252
290
|
return prime_a == prime_b or not (asset in prime_a.assets and asset in prime_b.assets)
|
|
253
291
|
```
|
|
254
292
|
|
|
255
293
|
Assuming that `PrimeAgentAll` is an Enum consisting of prime agent instances, tooling evaluates this constraint across the Cartesian product `PrimeAgentAll × PrimeAgentAll`, ensuring the predicate holds for all combinations where prime_a ≠ prime_b.
|
|
256
294
|
|
|
257
295
|
|
|
296
|
+
### Testing
|
|
297
|
+
|
|
298
|
+
- Happy‑path tests only (internal initially): provide one simple, end‑to‑end test per formula demonstrating basic execution flow. Avoid asserting on highly specific numerical guarantees unless essential; prefer sanity checks that exercise the graph and opaque boundaries.
|
|
299
|
+
- Location: keep tests under `python/axis_synome/tests/` organized by concept/module. Treat these as internal by default; they can be published later based on demand.
|
|
300
|
+
- Declarative first: specs are declarative and amenable to static verification; tests complement by documenting usage and catching regressions in opaque integrations.
|
|
301
|
+
|
|
302
|
+
#### Reference Implementations for Opaque Functions
|
|
303
|
+
|
|
304
|
+
- Opaque functions (e.g., ARIMA) are too complex to fully specify as pure math. To execute happy‑path tests, provide a minimal “test math engine” or reference implementation used only by tests.
|
|
305
|
+
- Do not publish as an official runtime dependency initially. If users request a ready‑made engine, consider publishing a separate package later.
|
|
306
|
+
- Multiple valid approaches may exist; tests should pick one concrete implementation as an example without constraining others.
|
|
307
|
+
|
|
308
|
+
|
|
258
309
|
## Starter Template
|
|
259
310
|
|
|
260
311
|
Start a new spec module with (simplified example; in real modules, entities typically live in their own package folder):
|
|
261
312
|
|
|
262
313
|
```python
|
|
263
314
|
# parameters.py
|
|
264
|
-
from typing import
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
description="Breach threshold in USD",
|
|
272
|
-
source="Atlas Ref",
|
|
273
|
-
),
|
|
274
|
-
]
|
|
275
|
-
] = 1_000_000.0
|
|
315
|
+
from typing import Final
|
|
316
|
+
|
|
317
|
+
THRESHOLD: Final[float] = 1_000_000.0
|
|
318
|
+
"""Example threshold constant.
|
|
319
|
+
|
|
320
|
+
:source_uuid: 62495dee-8d2a-45d4-87c4-01150e3db3c8
|
|
321
|
+
"""
|
|
276
322
|
```
|
|
277
323
|
|
|
278
324
|
```python
|
|
@@ -300,8 +346,6 @@ class ItemSet(Enum):
|
|
|
300
346
|
|
|
301
347
|
```python
|
|
302
348
|
# formulas.py
|
|
303
|
-
from typing import Annotated
|
|
304
|
-
from axis_synome.spec_support.metadata import Metadata
|
|
305
349
|
from .parameters import THRESHOLD
|
|
306
350
|
from .entities.items import ItemSet, EligibleOwner
|
|
307
351
|
|
|
@@ -326,14 +370,11 @@ def owner_item_value(owner: EligibleOwner, item: ItemSet) -> float:
|
|
|
326
370
|
|
|
327
371
|
def total_value(
|
|
328
372
|
owner: EligibleOwner,
|
|
329
|
-
) ->
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
source="Atlas Ref",
|
|
335
|
-
),
|
|
336
|
-
]:
|
|
373
|
+
) -> float:
|
|
374
|
+
"""Total value for an eligible owner.
|
|
375
|
+
|
|
376
|
+
:source_uuid: DOC-EXAMPLE-UUID
|
|
377
|
+
"""
|
|
337
378
|
# Computes over the closed‑world ItemSet domain
|
|
338
379
|
return sum(owner_item_value(owner, it) for it in ItemSet)
|
|
339
380
|
|
|
@@ -350,7 +391,7 @@ def threshold_satisfied(owner: EligibleOwner) -> bool:
|
|
|
350
391
|
- Aggregators: `sum`, `min`, `max`, `any`, `all` over generator expressions.
|
|
351
392
|
- Generator expressions and simple `range(...)` where needed.
|
|
352
393
|
- Selected `math.*` (e.g., `log`, `exp`, `sqrt`, `pow`).
|
|
353
|
-
- Imports from `
|
|
394
|
+
- Imports from `axis_synome.spec`, `synome`, `typing`, `dataclasses`, `enum`, `pydantic`, and bare `math`.
|
|
354
395
|
|
|
355
396
|
- Avoid / Disallowed
|
|
356
397
|
- Mutation, I/O, networking, side effects; exceptions and `try/except`.
|
|
@@ -398,7 +439,7 @@ The parser extracts formulas from typed Python by walking the function AST and l
|
|
|
398
439
|
- Dict literals and dict comprehensions are disallowed in formulas. Set comprehensions are not currently supported by the parser; if used, they will not be converted and may fallback to uninterpreted forms.
|
|
399
440
|
- Exceptions and side effects are out of scope by design.
|
|
400
441
|
|
|
401
|
-
|
|
442
|
+
Formula extraction: in `python/axis_synome/src/**/formulas/*.py`, the parser treats public functions as formulas by convention and reads `source_uuid` from docstring fields such as `:source_uuid: ...`. Outside formula modules, those docstring fields remain the explicit opt-in. Prefer putting descriptions in docstrings; units are not required and can be documented there as needed.
|
|
402
443
|
|
|
403
444
|
---
|
|
404
445
|
|
|
@@ -408,7 +449,7 @@ Use the Makefile targets to run checks consistently:
|
|
|
408
449
|
|
|
409
450
|
- Lint (ruff): `make lint` → `uv run ruff check .`
|
|
410
451
|
- Format (ruff): `make format` or `make format-check`
|
|
411
|
-
- Specs lint (flake8 in specs): `make lint-specs` (runs from `
|
|
452
|
+
- Specs lint (flake8 in specs): `make lint-specs` (runs from `python/axis_synome/`)
|
|
412
453
|
- Type check (Ty): `make typecheck` (runs `ty check` for `src/` and `explorations/`)
|
|
413
454
|
- All checks: `make check-all`
|
|
414
455
|
|
|
@@ -425,19 +466,19 @@ Use the Makefile targets to run checks consistently:
|
|
|
425
466
|
- Run: `make lint` for checks, `make fix` to auto-fix, `make format` for formatting.
|
|
426
467
|
|
|
427
468
|
- Flake8 (spec-subset validator)
|
|
428
|
-
- In `
|
|
469
|
+
- In `python/axis_synome/`, Flake8 runs a custom plugin that emits AXS00x errors when a file uses constructs outside the allowed subset defined in `axis_synome.spec_validator.python_subset`.
|
|
429
470
|
- Examples of disallowed patterns: nested functions in formulas, methods in dataclasses/Enums, non-whitelisted imports, f-strings, dict/set comprehensions, `try/except`, `yield`, walrus `:=`, unapproved builtins.
|
|
430
471
|
- Only spec files are validated by the plugin; editor integrations surface these as AXS diagnostics.
|
|
431
|
-
- Run: `make lint-specs` (executes from `
|
|
472
|
+
- Run: `make lint-specs` (executes from `python/axis_synome/`).
|
|
432
473
|
|
|
433
474
|
- Ty (static type checking)
|
|
434
475
|
- Validates function signatures, dataclass fields, and return types across runtime code and specs. Types must line up; otherwise the graph edges inferred from signatures become unsound.
|
|
435
|
-
- Encourages narrow, precise types on inputs/outputs (e.g., closed-world Enums, `Final`,
|
|
476
|
+
- Encourages narrow, precise types on inputs/outputs (e.g., closed-world Enums, `Final`, and precise scalar return types).
|
|
436
477
|
- Run: `make typecheck`.
|
|
437
478
|
|
|
438
479
|
### How Checks Align With The Parser
|
|
439
480
|
|
|
440
|
-
- Single source of truth for the subset. The same allowlist powering the Flake8 plugin (`
|
|
481
|
+
- Single source of truth for the subset. The same allowlist powering the Flake8 plugin (`axis_synome.spec_validator.python_subset`) is used by the parser when walking function ASTs to produce SymPy. If Flake8 passes, the parser will not encounter unexpected statement/expression kinds.
|
|
441
482
|
- Safe constructs are lowered deterministically. Comprehensions, boolean ops, comparisons, `sum/min/max`, selected `math.*`, and simple conditionals become SymPy expressions or uninterpreted functions with clear bounds.
|
|
442
483
|
- Disallowed constructs block extraction early. If you see an `AXS001 ... is not allowed` error, change the code to an allowed equivalent (e.g., use a generator expression and `sum(...)` instead of a loop; prefer ternary `x if c else y` over complex branching inside expressions).
|
|
443
484
|
|
|
@@ -445,14 +486,14 @@ Use the Makefile targets to run checks consistently:
|
|
|
445
486
|
|
|
446
487
|
- Signatures define edges. A formula like `def asc(prime: EligiblePrimeAgentASC, positions: list[Position]) -> float` induces edges from each `EligiblePrimeAgentASC` member to `asc`, and from any sub-formulas it calls to `asc`.
|
|
447
488
|
- Closed-world Enums enable universal and product quantification. Boolean constraints over Enums are checked over all members or Cartesian products without execution, thanks to types.
|
|
448
|
-
- Entities and parameters carry semantics. Dataclass fields typed to domain-specific classes and
|
|
489
|
+
- Entities and parameters carry semantics. Dataclass fields typed to domain-specific classes and typed constants such as `Final[T]` provide machine-readable identities; `source_uuid` comes from docstring markers such as `:source_uuid:` and is used in graph labeling, audits, and downstream codegen. Place long‑form descriptions in docstrings.
|
|
449
490
|
- Type checking safeguards graph integrity. If you widen a type (e.g., `Any` or a loose `str`) you reduce the graph’s precision; Ty will highlight mismatches so specs remain traversable and analyzable.
|
|
450
491
|
|
|
451
492
|
### Typical Violations And Fixes
|
|
452
493
|
|
|
453
494
|
- For-loops inside formulas → Replace with generator expressions plus `sum(...)`, `any(...)`, or `all(...)`.
|
|
454
495
|
- Dict/set comprehensions → Move structure-building to entities or use lists/tuples; keep formula bodies expression-centric.
|
|
455
|
-
- Unapproved builtins/imports → Use only builtins in the allowlist and imports under permitted prefixes (`
|
|
496
|
+
- Unapproved builtins/imports → Use only builtins in the allowlist and imports under permitted prefixes (`axis_synome.spec`, `typing`, `dataclasses`, `enum`, `pydantic`, `synome`, or `math`).
|
|
456
497
|
- Methods in dataclasses/Enums → Move logic into free functions; limit classes to annotated fields (dataclasses) or constant members (Enums).
|
|
457
498
|
- Missing/loose types → Add precise argument and return annotations; prefer closed-world Enums where policy implies eligibility.
|
|
458
499
|
|
|
@@ -483,12 +524,11 @@ These hypothetical examples show how mathematical expression/equations can be co
|
|
|
483
524
|
$\mathrm{PortfolioUSD} = H_{\mathrm{USDC}} + H_{\mathrm{USDT}} + H_{\mathrm{USDS}} + H_{\mathrm{DAI}}$
|
|
484
525
|
|
|
485
526
|
```python
|
|
486
|
-
def portfolio_usd(
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
]:
|
|
527
|
+
def portfolio_usd(h_usdc: float, h_usdt: float, h_usds: float, h_dai: float) -> float:
|
|
528
|
+
"""Total USD across stablecoin holdings.
|
|
529
|
+
|
|
530
|
+
:source_uuid: DOC-EXAMPLE-UUID
|
|
531
|
+
"""
|
|
492
532
|
holdings = [("USDC", h_usdc), ("USDT", h_usdt), ("USDS", h_usds), ("DAI", h_dai)]
|
|
493
533
|
total: float = sum(value for _, value in holdings)
|
|
494
534
|
return total
|
|
@@ -501,23 +541,21 @@ $\mathrm{margin} = \dfrac{\mathrm{assetsUSD}}{\mathrm{debtUSD}} - \mathrm{minRat
|
|
|
501
541
|
$\mathrm{collateral\_ratio\_satisfied} \iff \mathrm{margin} \ge 0$
|
|
502
542
|
|
|
503
543
|
```python
|
|
504
|
-
def collateral_ratio_margin(
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
]:
|
|
544
|
+
def collateral_ratio_margin(assets_usd: float, debt_usd: float, min_ratio: float) -> float:
|
|
545
|
+
"""Collateral ratio minus minimum.
|
|
546
|
+
|
|
547
|
+
:source_uuid: DOC-EXAMPLE-UUID
|
|
548
|
+
"""
|
|
510
549
|
ratio: float = assets_usd / debt_usd
|
|
511
550
|
margin: float = ratio - min_ratio
|
|
512
551
|
return margin
|
|
513
552
|
|
|
514
553
|
|
|
515
|
-
def collateral_ratio_satisfied(
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
]:
|
|
554
|
+
def collateral_ratio_satisfied(assets_usd: float, debt_usd: float, min_ratio: float) -> bool:
|
|
555
|
+
"""True when collateral ratio meets or exceeds the minimum.
|
|
556
|
+
|
|
557
|
+
:source_uuid: DOC-EXAMPLE-UUID
|
|
558
|
+
"""
|
|
521
559
|
return collateral_ratio_margin(assets_usd, debt_usd, min_ratio) >= 0.0
|
|
522
560
|
```
|
|
523
561
|
|
|
@@ -531,10 +569,11 @@ $\mathrm{haircut}(\sigma_{30\mathrm{d}}) = \begin{cases}
|
|
|
531
569
|
```python
|
|
532
570
|
def volatility_haircut(
|
|
533
571
|
vol_30d: float,
|
|
534
|
-
) ->
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
572
|
+
) -> float:
|
|
573
|
+
"""Haircut by 30d volatility threshold.
|
|
574
|
+
|
|
575
|
+
:source_uuid: DOC-EXAMPLE-UUID
|
|
576
|
+
"""
|
|
538
577
|
return 0.02 if vol_30d < 0.2 else 0.05
|
|
539
578
|
```
|
|
540
579
|
|
|
@@ -547,10 +586,11 @@ $w_{\mathrm{PSM}}=U_{\mathrm{PSM}}/T,\quad w_{\mathrm{Curve}}=U_{\mathrm{Curve}}
|
|
|
547
586
|
$\max\{w_{\mathrm{PSM}}, w_{\mathrm{Curve}}, w_{\mathrm{Uni}}\} \le \mathrm{cap}$
|
|
548
587
|
|
|
549
588
|
```python
|
|
550
|
-
def max_venue_concentration(psm_usd: float, curve_usd: float, uni_usd: float, cap: float) ->
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
589
|
+
def max_venue_concentration(psm_usd: float, curve_usd: float, uni_usd: float, cap: float) -> bool:
|
|
590
|
+
"""True when the maximum venue share is at or below the cap.
|
|
591
|
+
|
|
592
|
+
:source_uuid: DOC-EXAMPLE-UUID
|
|
593
|
+
"""
|
|
554
594
|
total: float = psm_usd + curve_usd + uni_usd
|
|
555
595
|
w_psm: float = psm_usd / total
|
|
556
596
|
w_curve: float = curve_usd / total
|
|
@@ -564,9 +604,9 @@ def max_venue_concentration(psm_usd: float, curve_usd: float, uni_usd: float, ca
|
|
|
564
604
|
- Use types (dataclasses, Enums, precise signatures) to encode policy and traversable relations.
|
|
565
605
|
|
|
566
606
|
- Minimal template
|
|
567
|
-
- Parameters: `Final[
|
|
607
|
+
- Parameters: `Final[T]` for governance‑set constants, with provenance in trailing docstrings.
|
|
568
608
|
- Entities: frozen dataclasses and closed‑world Enums of concrete instances.
|
|
569
|
-
- Formulas: pure functions over these types with precise return
|
|
609
|
+
- Formulas: pure functions over these types with precise return types; put provenance in docstrings.
|
|
570
610
|
|
|
571
611
|
- Do / Don’t (subset cheat‑sheet)
|
|
572
612
|
- Do: use generator expressions + `sum/min/max/any/all`, ternary `x if c else y`, selected `math.*`, and composition of small helpers.
|
|
@@ -584,12 +624,11 @@ def max_venue_concentration(psm_usd: float, curve_usd: float, uni_usd: float, ca
|
|
|
584
624
|
$\mathrm{PV} = \sum_{i=0}^{n-1} \dfrac{\mathrm{CF}_i}{(1+r)^{i+1}}$
|
|
585
625
|
|
|
586
626
|
```python
|
|
587
|
-
def present_value(
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
]:
|
|
627
|
+
def present_value(cashflows: list[float], r: float) -> float:
|
|
628
|
+
"""Present value of discounted cash flows.
|
|
629
|
+
|
|
630
|
+
:source_uuid: DOC-EXAMPLE-UUID
|
|
631
|
+
"""
|
|
593
632
|
n: int = len(cashflows)
|
|
594
633
|
return sum(cashflows[i] / (1.0 + r) ** (i + 1) for i in range(n))
|
|
595
634
|
```
|
|
@@ -599,12 +638,11 @@ def present_value(
|
|
|
599
638
|
$\forall i \in \{0,\dots,n-1\}:\; p_i \ge \tau$
|
|
600
639
|
|
|
601
640
|
```python
|
|
602
|
-
def all_positions_above(
|
|
603
|
-
positions
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
]:
|
|
641
|
+
def all_positions_above(positions: list[float], threshold: float) -> bool:
|
|
642
|
+
"""All positions above a threshold.
|
|
643
|
+
|
|
644
|
+
:source_uuid: DOC-EXAMPLE-UUID
|
|
645
|
+
"""
|
|
608
646
|
return all(p >= threshold for p in positions)
|
|
609
647
|
```
|
|
610
648
|
|
|
@@ -632,3 +670,14 @@ Use only when the operation cannot be expressed in the allowed, parser‑friendl
|
|
|
632
670
|
- Keep wrappers zero‑logic (no exceptions/branching); just delegate to `get_engine()`.
|
|
633
671
|
- Use precise, stable types and consistent snake_case names.
|
|
634
672
|
- Briefly document new opaque functions in `opaque/README.md` (purpose, signature, runtime notes).
|
|
673
|
+
|
|
674
|
+
## Atlas Integration
|
|
675
|
+
|
|
676
|
+
- Keep Atlas UUIDs in docstrings using `:source_uuid: ...` when available. If no Atlas section applies, omit the marker entirely.
|
|
677
|
+
- Prefer docstrings in specs as the authoritative descriptive source. Atlas Markdown pages can be “hollowed out” to minimal anchors that link back to Synome specs and reuse docstrings when possible. This approach avoids duplicate, drifting descriptions. Pending Atlas Access team alignment.
|
|
678
|
+
|
|
679
|
+
## Versioning and Backwards Compatibility
|
|
680
|
+
|
|
681
|
+
- Early phase: limited backwards‑compatibility promises. We may iterate quickly across major versions.
|
|
682
|
+
- Breaking changes (e.g., moving entities from formula‑local to shared `axis/entities/`, changing import paths) require a major version bump.
|
|
683
|
+
- Where feasible, provide temporary re‑exports/shims to ease upgrades, but prefer clarity over long‑term indirection.
|
|
@@ -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.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 1, '
|
|
21
|
+
__version__ = version = '0.1.dev185'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 1, 'dev185')
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -1,31 +1,14 @@
|
|
|
1
|
-
from typing import Annotated
|
|
2
|
-
|
|
3
1
|
from axis_synome.spec.asc.entities.primes import EligiblePrimeAgentASC
|
|
4
2
|
from axis_synome.spec.asc.entities.types import Position
|
|
5
3
|
from axis_synome.spec.asc.formulas.latent_asc import latent_asc
|
|
6
4
|
from axis_synome.spec.asc.formulas.resting_asc import resting_asc
|
|
7
|
-
from axis_synome.spec_support.metadata import Metadata
|
|
8
|
-
|
|
9
|
-
BLAH = 5
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
"""Seconds to wait before the connection times out."""
|
|
13
|
-
|
|
14
|
-
"hello"
|
|
15
5
|
|
|
16
6
|
|
|
17
|
-
def asc(
|
|
18
|
-
prime: EligiblePrimeAgentASC, positions: list[Position]
|
|
19
|
-
) -> Annotated[
|
|
20
|
-
float,
|
|
21
|
-
Metadata(
|
|
22
|
-
description="Total Actively Stabilizing Capital for a prime: resting ASC plus latent ASC.",
|
|
23
|
-
source_uuid="62495dee-8d2a-45d4-87c4-01150e3db3c8",
|
|
24
|
-
),
|
|
25
|
-
]:
|
|
7
|
+
def asc(prime: EligiblePrimeAgentASC, positions: list[Position]) -> float:
|
|
26
8
|
"""
|
|
27
9
|
Total Actively Stabilizing Capital for a prime.
|
|
28
10
|
|
|
11
|
+
:source_uuid: 62495dee-8d2a-45d4-87c4-01150e3db3c8
|
|
29
12
|
"""
|
|
30
13
|
resting_asc_usd: float = resting_asc(positions)
|
|
31
14
|
latent_asc_usd: float = latent_asc(prime, positions)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from axis_synome.spec.asc.entities.primes import EligiblePrimeAgentASC
|
|
2
|
+
from axis_synome.spec.asc.entities.types import Position
|
|
3
|
+
from axis_synome.spec.asc.formulas.latent_asc import latent_asc
|
|
4
|
+
from axis_synome.spec.asc.formulas.resting_asc import resting_asc
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def collateral_portfolio(
|
|
8
|
+
positions: list[Position],
|
|
9
|
+
) -> float:
|
|
10
|
+
"""Total Collateral Portfolio value for a prime: sum of all positions.
|
|
11
|
+
|
|
12
|
+
:source_uuid: 64e1390f-68a1-43ec-87a8-8ae7b990f7ec
|
|
13
|
+
"""
|
|
14
|
+
return sum(p.value_usd for p in positions)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def asc_collateral_ratio(prime: EligiblePrimeAgentASC, positions: list[Position]) -> float:
|
|
18
|
+
"""ASC collateral ratio slack.
|
|
19
|
+
|
|
20
|
+
Returns the arithmetic result unconditionally. A negative value means
|
|
21
|
+
ASC is below the required minimum fraction of the Collateral Portfolio.
|
|
22
|
+
|
|
23
|
+
:source_uuid: 475fe222-9e4a-4e9d-9be6-a7a424ce02f8
|
|
24
|
+
"""
|
|
25
|
+
agent = prime.value
|
|
26
|
+
asc_usd: float = resting_asc(positions) + latent_asc(prime, positions)
|
|
27
|
+
portfolio_usd: float = collateral_portfolio(positions)
|
|
28
|
+
return asc_usd - portfolio_usd * agent.min_pct_asc_of_collateral
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def asc_collateral_ratio_satisfied(prime: EligiblePrimeAgentASC, positions: list[Position]) -> bool:
|
|
32
|
+
"""Boolean ASC collateral ratio compliance predicate. True iff asc_collateral_ratio >= 0.
|
|
33
|
+
|
|
34
|
+
:source_uuid: 475fe222-9e4a-4e9d-9be6-a7a424ce02f8
|
|
35
|
+
"""
|
|
36
|
+
return asc_collateral_ratio(prime, positions) >= 0.0
|
{axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec/asc/formulas/asc_incentive.py
RENAMED
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
from typing import Annotated
|
|
2
|
-
|
|
3
1
|
from axis_synome.spec.asc.entities.primes import EligiblePrimeAgentASC
|
|
4
2
|
from axis_synome.spec.asc.entities.types import Position
|
|
5
3
|
from axis_synome.spec.asc.formulas.latent_asc import latent_asc
|
|
6
4
|
from axis_synome.spec.asc.formulas.resting_asc import resting_asc
|
|
7
|
-
from axis_synome.spec_support.metadata import Metadata
|
|
8
5
|
|
|
9
6
|
# base_float and treasury_bill_rate are both expressed as params.
|
|
10
7
|
|
|
@@ -14,15 +11,10 @@ def asc_incentive(
|
|
|
14
11
|
positions: list[Position],
|
|
15
12
|
base_rate: float,
|
|
16
13
|
treasury_bill_rate: float,
|
|
17
|
-
) ->
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
source_uuid="693330d6-9072-4054-bd61-d788537e34e8",
|
|
22
|
-
),
|
|
23
|
-
]:
|
|
24
|
-
"""
|
|
25
|
-
ASC incentive payment
|
|
14
|
+
) -> float:
|
|
15
|
+
"""ASC incentive payment: eligible ASC times (base_rate - treasury_bill_rate).
|
|
16
|
+
|
|
17
|
+
:source_uuid: 693330d6-9072-4054-bd61-d788537e34e8
|
|
26
18
|
"""
|
|
27
19
|
eligible_asc_usd: float = resting_asc(positions) + latent_asc(prime, positions)
|
|
28
20
|
return eligible_asc_usd * (base_rate - treasury_bill_rate)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from typing import Final
|
|
2
|
+
|
|
3
|
+
from axis_synome.spec.asc.entities.primes import EligiblePrimeAgentASC
|
|
4
|
+
from axis_synome.spec.asc.entities.types import Position
|
|
5
|
+
from axis_synome.spec.asc.formulas.latent_asc import latent_asc
|
|
6
|
+
from axis_synome.spec.asc.formulas.resting_asc import resting_asc
|
|
7
|
+
|
|
8
|
+
DAB_REQUIRED_PCT: Final[float] = 0.25
|
|
9
|
+
"""Required DAB as a fraction of total ASC.
|
|
10
|
+
|
|
11
|
+
:source_uuid: 1e129119-a2ce-4978-b235-c50f2a1c5e2e
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def dab_required(
|
|
16
|
+
prime: EligiblePrimeAgentASC,
|
|
17
|
+
positions: list[Position],
|
|
18
|
+
) -> float:
|
|
19
|
+
"""Required DAB: DAB_REQUIRED_PCT of ASC.
|
|
20
|
+
|
|
21
|
+
:source_uuid: 1e129119-a2ce-4978-b235-c50f2a1c5e2e
|
|
22
|
+
"""
|
|
23
|
+
asc_usd = resting_asc(positions) + latent_asc(prime, positions)
|
|
24
|
+
return asc_usd * DAB_REQUIRED_PCT
|
{axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec/asc/formulas/latent_asc.py
RENAMED
|
@@ -1,21 +1,10 @@
|
|
|
1
|
-
from typing import Annotated
|
|
2
|
-
|
|
3
1
|
from axis_synome.spec.asc.entities.primes import EligiblePrimeAgentASC
|
|
4
2
|
from axis_synome.spec.asc.entities.protocol_sets import LENDING_PROTOCOLS, POOL_PROTOCOLS
|
|
5
3
|
from axis_synome.spec.asc.entities.tokens import CASH_STABLECOINS
|
|
6
4
|
from axis_synome.spec.asc.entities.types import Position
|
|
7
|
-
from axis_synome.spec_support.metadata import Metadata
|
|
8
5
|
|
|
9
6
|
|
|
10
|
-
def latent_asc(
|
|
11
|
-
prime: EligiblePrimeAgentASC, positions: list[Position]
|
|
12
|
-
) -> Annotated[
|
|
13
|
-
float,
|
|
14
|
-
Metadata(
|
|
15
|
-
description="Latent ASC: stabilizing capital not immediately liquid, held by a prime.",
|
|
16
|
-
source_uuid="35ce6b38-9fc1-456e-93da-10ab1468a8bf",
|
|
17
|
-
),
|
|
18
|
-
]:
|
|
7
|
+
def latent_asc(prime: EligiblePrimeAgentASC, positions: list[Position]) -> float:
|
|
19
8
|
"""Latent ASC for a prime: which is the stabilizing capital not immediately liquid.
|
|
20
9
|
|
|
21
10
|
Includes:
|
|
@@ -25,6 +14,8 @@ def latent_asc(
|
|
|
25
14
|
|
|
26
15
|
Pool positions paired with USDS are excluded (those contribute to
|
|
27
16
|
RestingASC).
|
|
17
|
+
|
|
18
|
+
:source_uuid: 35ce6b38-9fc1-456e-93da-10ab1468a8bf
|
|
28
19
|
"""
|
|
29
20
|
agent = prime.value
|
|
30
21
|
return sum(
|
|
@@ -1,25 +1,16 @@
|
|
|
1
|
-
from typing import Annotated
|
|
2
|
-
|
|
3
1
|
from axis_synome.spec.asc.entities.primes import EligiblePrimeAgentASC
|
|
4
2
|
from axis_synome.spec.asc.entities.types import Position
|
|
5
3
|
from axis_synome.spec.asc.formulas.latent_asc import latent_asc
|
|
6
4
|
from axis_synome.spec.asc.formulas.resting_asc import resting_asc
|
|
7
|
-
from axis_synome.spec_support.metadata import Metadata
|
|
8
5
|
|
|
9
6
|
|
|
10
|
-
def ratio_latent_asc(
|
|
11
|
-
prime: EligiblePrimeAgentASC, positions: list[Position]
|
|
12
|
-
) -> Annotated[
|
|
13
|
-
float,
|
|
14
|
-
Metadata(
|
|
15
|
-
description="Latent ASC ratio slack: permitted latent cap minus actual latent ASC. Must be >= 0 for compliance.",
|
|
16
|
-
source_uuid="5e300cdb-b221-4b6f-9c4a-11502133a1f9",
|
|
17
|
-
),
|
|
18
|
-
]:
|
|
7
|
+
def ratio_latent_asc(prime: EligiblePrimeAgentASC, positions: list[Position]) -> float:
|
|
19
8
|
"""Latent ASC ratio slack.
|
|
20
9
|
|
|
21
10
|
Returns the arithmetic result unconditionally. A negative value means
|
|
22
11
|
LatentASC exceeds the permitted fraction of total ASC.
|
|
12
|
+
|
|
13
|
+
:source_uuid: 5e300cdb-b221-4b6f-9c4a-11502133a1f9
|
|
23
14
|
"""
|
|
24
15
|
agent = prime.value
|
|
25
16
|
latent_usd: float = latent_asc(prime, positions)
|
|
@@ -27,14 +18,9 @@ def ratio_latent_asc(
|
|
|
27
18
|
return total_usd * agent.max_pct_latent_asc - latent_usd
|
|
28
19
|
|
|
29
20
|
|
|
30
|
-
def latent_asc_ratio_satisfied(
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
description="True when latent ASC is within the permitted fraction of total ASC.",
|
|
36
|
-
source_uuid="5e300cdb-b221-4b6f-9c4a-11502133a1f9",
|
|
37
|
-
),
|
|
38
|
-
]:
|
|
39
|
-
"""Boolean latent ASC ratio compliance predicate. True iff ratio_latent_asc >= 0."""
|
|
21
|
+
def latent_asc_ratio_satisfied(prime: EligiblePrimeAgentASC, positions: list[Position]) -> bool:
|
|
22
|
+
"""Boolean latent ASC ratio compliance predicate. True iff ratio_latent_asc >= 0.
|
|
23
|
+
|
|
24
|
+
:source_uuid: 5e300cdb-b221-4b6f-9c4a-11502133a1f9
|
|
25
|
+
"""
|
|
40
26
|
return ratio_latent_asc(prime, positions) >= 0.0
|
{axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec/asc/formulas/resting_asc.py
RENAMED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
from typing import Annotated
|
|
2
|
-
|
|
3
1
|
from axis_synome.spec.asc.entities.networks import L2_NETWORKS
|
|
4
2
|
from axis_synome.spec.asc.entities.protocol_sets import (
|
|
5
3
|
GUNI_PROTOCOLS,
|
|
@@ -10,18 +8,11 @@ from axis_synome.spec.asc.entities.protocol_sets import (
|
|
|
10
8
|
)
|
|
11
9
|
from axis_synome.spec.asc.entities.tokens import CASH_STABLECOINS, Token
|
|
12
10
|
from axis_synome.spec.asc.entities.types import Position
|
|
13
|
-
from axis_synome.spec_support.metadata import Metadata
|
|
14
11
|
|
|
15
12
|
|
|
16
13
|
def resting_asc(
|
|
17
14
|
positions: list[Position],
|
|
18
|
-
) ->
|
|
19
|
-
float,
|
|
20
|
-
Metadata(
|
|
21
|
-
description="Resting ASC: immediately liquid stabilizing capital held by a prime.",
|
|
22
|
-
source_uuid="4e8cd2d1-4c74-49fd-b3fe-f8b6ccc1a79f",
|
|
23
|
-
),
|
|
24
|
-
]:
|
|
15
|
+
) -> float:
|
|
25
16
|
"""Resting ASC for a prime: immediately liquid stabilizing capital.
|
|
26
17
|
|
|
27
18
|
Includes:
|
|
@@ -31,6 +22,8 @@ def resting_asc(
|
|
|
31
22
|
- USDC in G-UNI at eligible fee tiers (0.01% = 0.0001, 0.05% = 0.0005)
|
|
32
23
|
|
|
33
24
|
All positions are assumed to belong to this prime.
|
|
25
|
+
|
|
26
|
+
:source_uuid: 4e8cd2d1-4c74-49fd-b3fe-f8b6ccc1a79f
|
|
34
27
|
"""
|
|
35
28
|
return sum(
|
|
36
29
|
p.value_usd
|
|
@@ -7,33 +7,18 @@ following the Morpho protocol specification.
|
|
|
7
7
|
Reference: https://docs.morpho.org/learn/concepts/liquidation/
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
|
-
from typing import
|
|
10
|
+
from typing import Final
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
BETA: Final[float] = 0.3
|
|
13
|
+
"""Weight parameter for the LLTV in the LIF denominator calculation."""
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
Metadata(
|
|
17
|
-
description="Morpho liquidation curve steepness parameter",
|
|
18
|
-
),
|
|
19
|
-
] = 0.3
|
|
20
|
-
|
|
21
|
-
M: Annotated[
|
|
22
|
-
float,
|
|
23
|
-
Metadata(
|
|
24
|
-
description="Maximum liquidation incentive factor",
|
|
25
|
-
),
|
|
26
|
-
] = 1.15
|
|
15
|
+
M: Final[float] = 1.15
|
|
16
|
+
"""Maximum liquidation incentive factor cap."""
|
|
27
17
|
|
|
28
18
|
|
|
29
19
|
def lif(
|
|
30
20
|
lltv: float,
|
|
31
|
-
) ->
|
|
32
|
-
float,
|
|
33
|
-
Metadata(
|
|
34
|
-
description="Liquidation incentive factor for a given LLTV",
|
|
35
|
-
),
|
|
36
|
-
]:
|
|
21
|
+
) -> float:
|
|
37
22
|
"""
|
|
38
23
|
Calculate the liquidation incentive factor (LIF) for a given LLTV.
|
|
39
24
|
|
|
@@ -7,22 +7,12 @@ occurs at a given slippage, following the Atlas specification.
|
|
|
7
7
|
Reference: A.3.2.2.1.1.1.1.1.2 — Calculate Loss Given Default
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
|
-
from typing import Annotated
|
|
11
|
-
|
|
12
|
-
from axis_synome.spec_support.metadata import Metadata
|
|
13
|
-
|
|
14
10
|
|
|
15
11
|
def loss_given_default(
|
|
16
12
|
liquidation_penalty: float,
|
|
17
13
|
slippage: float,
|
|
18
14
|
liquidation_threshold: float,
|
|
19
|
-
) ->
|
|
20
|
-
float,
|
|
21
|
-
Metadata(
|
|
22
|
-
description="Fractional loss on a collateral position at a given slippage",
|
|
23
|
-
source_uuid="c9bd4928-d054-4e89-9a98-720c439b0db3",
|
|
24
|
-
),
|
|
25
|
-
]:
|
|
15
|
+
) -> float:
|
|
26
16
|
"""
|
|
27
17
|
Calculate the loss given default for a collateral position.
|
|
28
18
|
|
|
@@ -34,6 +24,8 @@ def loss_given_default(
|
|
|
34
24
|
liquidation_threshold: LT, debt as fraction of collateral at liquidation [0, 1]
|
|
35
25
|
|
|
36
26
|
Returns a value in [0, 1]. Zero means no loss; 1 means total loss.
|
|
27
|
+
|
|
28
|
+
:source_uuid: c9bd4928-d054-4e89-9a98-720c439b0db3
|
|
37
29
|
"""
|
|
38
30
|
recovery: float = (1.0 - liquidation_penalty) * (1.0 - slippage) / liquidation_threshold
|
|
39
31
|
return max(0.0, min(1.0, 1.0 - recovery))
|
|
@@ -49,6 +49,8 @@ class SpecChecker(ast.NodeVisitor):
|
|
|
49
49
|
# ------------------------------------------------------------------
|
|
50
50
|
|
|
51
51
|
def visit_Module(self, node: ast.Module) -> None:
|
|
52
|
+
# Validate trailing string literal constant docstrings where present.
|
|
53
|
+
self._validate_constant_docstrings(node)
|
|
52
54
|
# Pre-scan for imported names for shadowing detection
|
|
53
55
|
for stmt in node.body:
|
|
54
56
|
if isinstance(stmt, (ast.Import, ast.ImportFrom)):
|
|
@@ -68,6 +70,35 @@ class SpecChecker(ast.NodeVisitor):
|
|
|
68
70
|
else:
|
|
69
71
|
self.visit(stmt)
|
|
70
72
|
|
|
73
|
+
def _validate_constant_docstrings(self, node: ast.Module) -> None:
|
|
74
|
+
"""Validate trailing string literal docstrings for documented constants."""
|
|
75
|
+
body = list(getattr(node, "body", []))
|
|
76
|
+
for i, stmt in enumerate(body):
|
|
77
|
+
target: ast.Name | None = None
|
|
78
|
+
if isinstance(stmt, ast.AnnAssign) and isinstance(stmt.target, ast.Name):
|
|
79
|
+
target = stmt.target
|
|
80
|
+
elif (
|
|
81
|
+
isinstance(stmt, ast.Assign)
|
|
82
|
+
and len(stmt.targets) == 1
|
|
83
|
+
and isinstance(stmt.targets[0], ast.Name)
|
|
84
|
+
):
|
|
85
|
+
target = stmt.targets[0]
|
|
86
|
+
|
|
87
|
+
if target is None or not target.id.isupper():
|
|
88
|
+
continue
|
|
89
|
+
|
|
90
|
+
if i + 1 < len(body):
|
|
91
|
+
nxt = body[i + 1]
|
|
92
|
+
if isinstance(nxt, ast.Expr) and isinstance(
|
|
93
|
+
getattr(nxt, "value", None), ast.Constant
|
|
94
|
+
):
|
|
95
|
+
val = nxt.value
|
|
96
|
+
if isinstance(getattr(val, "value", None), str) and not str(val.value).strip():
|
|
97
|
+
self._error(
|
|
98
|
+
stmt,
|
|
99
|
+
f"Constant '{target.id}' trailing string literal docstring must not be empty",
|
|
100
|
+
)
|
|
101
|
+
|
|
71
102
|
# ------------------------------------------------------------------
|
|
72
103
|
# Function definitions
|
|
73
104
|
# ------------------------------------------------------------------
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
from typing import Annotated
|
|
2
|
-
|
|
3
|
-
from axis_synome.spec.asc.entities.primes import EligiblePrimeAgentASC
|
|
4
|
-
from axis_synome.spec.asc.entities.types import Position
|
|
5
|
-
from axis_synome.spec.asc.formulas.latent_asc import latent_asc
|
|
6
|
-
from axis_synome.spec.asc.formulas.resting_asc import resting_asc
|
|
7
|
-
from axis_synome.spec_support.metadata import Metadata
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def collateral_portfolio(
|
|
11
|
-
positions: list[Position],
|
|
12
|
-
) -> Annotated[
|
|
13
|
-
float,
|
|
14
|
-
Metadata(
|
|
15
|
-
description="Total Collateral Portfolio value for a prime: sum of all position values.",
|
|
16
|
-
source_uuid="64e1390f-68a1-43ec-87a8-8ae7b990f7ec",
|
|
17
|
-
),
|
|
18
|
-
]:
|
|
19
|
-
"""Total Collateral Portfolio value for a prime: sum of all positions."""
|
|
20
|
-
return sum(p.value_usd for p in positions)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def asc_collateral_ratio(
|
|
24
|
-
prime: EligiblePrimeAgentASC, positions: list[Position]
|
|
25
|
-
) -> Annotated[
|
|
26
|
-
float,
|
|
27
|
-
Metadata(
|
|
28
|
-
description="ASC collateral ratio slack: ASC minus the required minimum fraction of the Collateral Portfolio. Must be >= 0 for compliance.",
|
|
29
|
-
source_uuid="475fe222-9e4a-4e9d-9be6-a7a424ce02f8",
|
|
30
|
-
),
|
|
31
|
-
]:
|
|
32
|
-
"""ASC collateral ratio slack.
|
|
33
|
-
|
|
34
|
-
Returns the arithmetic result unconditionally. A negative value means
|
|
35
|
-
ASC is below the required minimum fraction of the Collateral Portfolio.
|
|
36
|
-
"""
|
|
37
|
-
agent = prime.value
|
|
38
|
-
asc_usd: float = resting_asc(positions) + latent_asc(prime, positions)
|
|
39
|
-
portfolio_usd: float = collateral_portfolio(positions)
|
|
40
|
-
return asc_usd - portfolio_usd * agent.min_pct_asc_of_collateral
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def asc_collateral_ratio_satisfied(
|
|
44
|
-
prime: EligiblePrimeAgentASC, positions: list[Position]
|
|
45
|
-
) -> Annotated[
|
|
46
|
-
bool,
|
|
47
|
-
Metadata(
|
|
48
|
-
description="True when ASC meets or exceeds the required minimum fraction of the Collateral Portfolio.",
|
|
49
|
-
source_uuid="475fe222-9e4a-4e9d-9be6-a7a424ce02f8",
|
|
50
|
-
),
|
|
51
|
-
]:
|
|
52
|
-
"""Boolean ASC collateral ratio compliance predicate. True iff asc_collateral_ratio >= 0."""
|
|
53
|
-
return asc_collateral_ratio(prime, positions) >= 0.0
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
from typing import Annotated
|
|
2
|
-
|
|
3
|
-
from axis_synome.spec.asc.entities.primes import EligiblePrimeAgentASC
|
|
4
|
-
from axis_synome.spec.asc.entities.types import Position
|
|
5
|
-
from axis_synome.spec.asc.formulas.latent_asc import latent_asc
|
|
6
|
-
from axis_synome.spec.asc.formulas.resting_asc import resting_asc
|
|
7
|
-
from axis_synome.spec_support.metadata import Metadata
|
|
8
|
-
|
|
9
|
-
DAB_REQUIRED_PCT: Annotated[
|
|
10
|
-
float,
|
|
11
|
-
Metadata(
|
|
12
|
-
description="Required DAB as a fraction of total ASC.",
|
|
13
|
-
source_uuid="1e129119-a2ce-4978-b235-c50f2a1c5e2e",
|
|
14
|
-
),
|
|
15
|
-
] = 0.25
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def dab_required(
|
|
19
|
-
prime: EligiblePrimeAgentASC,
|
|
20
|
-
positions: list[Position],
|
|
21
|
-
) -> Annotated[
|
|
22
|
-
float,
|
|
23
|
-
Metadata(
|
|
24
|
-
description="Required DAB: DAB_REQUIRED_PCT of total ASC.",
|
|
25
|
-
source_uuid="1e129119-a2ce-4978-b235-c50f2a1c5e2e",
|
|
26
|
-
),
|
|
27
|
-
]:
|
|
28
|
-
"""Required DAB: DAB_REQUIRED_PCT of ASC."""
|
|
29
|
-
asc_usd = resting_asc(positions) + latent_asc(prime, positions)
|
|
30
|
-
return asc_usd * DAB_REQUIRED_PCT
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec/asc/entities/__init__.py
RENAMED
|
File without changes
|
{axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec/asc/entities/assets.py
RENAMED
|
File without changes
|
|
File without changes
|
{axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec/asc/entities/networks.py
RENAMED
|
File without changes
|
{axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec/asc/entities/primes.py
RENAMED
|
File without changes
|
{axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec/asc/entities/protocol_sets.py
RENAMED
|
File without changes
|
{axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec/asc/entities/tokens.py
RENAMED
|
File without changes
|
{axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec/asc/entities/types.py
RENAMED
|
File without changes
|
|
File without changes
|
{axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec/crypto_lending/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec/risk_capital/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec_support/evm_address.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec_support/validated_str.py
RENAMED
|
File without changes
|
{axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec_validator/__init__.py
RENAMED
|
File without changes
|
{axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec_validator/flake8_plugin.py
RENAMED
|
File without changes
|
{axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/src/axis_synome/spec_validator/python_subset.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/tests/axis_synome/asc/test_asc_collateral_ratio.py
RENAMED
|
File without changes
|
{axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/tests/axis_synome/asc/test_asc_incentive.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/tests/axis_synome/asc/test_ratio_latent_asc.py
RENAMED
|
File without changes
|
|
File without changes
|
{axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/tests/axis_synome/risk_capital/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{axis_synome-0.1.dev184 → axis_synome-0.1.dev185}/tests/axis_synome/spec_validator/test_checker.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|