The Primitive Contract
Every analytics primitive in DeFiPy follows the same three-line contract:
primitive = SomePrimitive() # Stateless constructionresult = primitive.apply(lp, *args) # Computationvalue = result.<field> # Typed dataclass accessThis contract is what makes the primitives composable, testable, and exposable as LLM tools without any adapter layer. A quant in a notebook calls AnalyzePosition(...).apply(...) and reads result.il_percentage. The MCP server wrapping that same primitive for an LLM dispatches AnalyzePosition(...).apply(...) and returns result.il_percentage — exactly the same code path. No translation, no branching, no agent-specific glue.
Three invariants hold across all 21 primitives:
- Stateless construction.
SomePrimitive()takes no state that affects the math. Any parameters passed at construction time (e.g.,DetectFeeAnomaly(threshold_bps=10)) are tuning, not state — calling.apply()twice with the same arguments returns the same answer. - Computation at
apply(). All work happens in one call. Primitives do not subscribe, stream, or maintain internal caches. - Typed dataclass return. Every primitive returns a specific result dataclass (
PositionAnalysis,PriceMoveScenario,PoolHealth, etc.) with named fields. Nothing is returned as a raw tuple, a dict, or a string. Callers — human or LLM — can rely on field names.
These invariants aren’t just stylistic. They’re what lets the library serve three audiences with one interface: notebook quants who want typed introspection, LLMs that need JSON-serializable outputs, and agent frameworks that compose primitives into larger tools.
What this section covers
- Agentic Primitives — the 9 categories, each listing the primitives that belong and their call signatures
- Tools Reference —
defipy.toolsmodule: schema emission, registry, binding - Twin Reference —
defipy.twinmodule: provider interface and snapshot types - Result Dataclasses — the full catalog of result dataclasses returned by the primitives