Market Snapshot¶
almanak.framework.market
¶
Canonical MarketSnapshot package — VIB-4062.
The single source of truth for the strategy-facing market-data interface. All
strategies and runtime surfaces consume MarketSnapshot through this
package; legacy import paths under almanak.framework.strategies and
almanak.framework.data.market_snapshot are removed in commit 6 of the
VIB-4062 migration.
Public surface:
MarketSnapshot— the only concrete snapshot class.MarketSnapshotBuilder— builder with factories per runtime surface.MultiChainMarketSnapshot— type alias forMarketSnapshot(see PRD §4.2).- Typed errors —
MarketSnapshotError,ChainNotConfiguredError,AmbiguousChainError,PriceUnavailableError, … - Typed return models —
TokenBalance,PriceData,RSIData,MACDData, …
AmbiguousChainError
¶
Bases: MarketSnapshotError
A multi-chain snapshot was queried with chain=None.
Raised when len(self.chains) > 1 and no explicit chain= was given.
Multi-chain callers must be explicit; default-to-primary policy hides
chain-routing bugs (PRD §4.2 R2, Codex finding HIGH).
ChainNotConfiguredError
¶
Bases: MarketSnapshotError
A specific chain= argument was supplied but is not in chains.
Raised in BOTH single-chain (mismatch with the only configured chain) and multi-chain (chain not in the configured set) cases. The runner has no recovery path — strategy code must update its chain handling.
IndicatorUnavailableError
¶
MarketSnapshotError
¶
Bases: Exception
Base class for typed snapshot errors.
Subclasses carry structured fields. Stringifying gives a stable shape
for log lines: "<ClassName>(<key>=<value>, …): <reason>".
For backward compat with the legacy data-layer error idiom, positional
arguments are accepted as (chain, reason) or just (reason,);
callers using kwargs (the canonical post-VIB-4062 form) continue to work.
StaleDataError
¶
ADXData
dataclass
¶
ADXData(
adx: Decimal,
plus_di: Decimal,
minus_di: Decimal,
period: int = 14,
trend_threshold: Decimal = Decimal("25"),
)
ADX (Average Directional Index) data for a token.
ATRData
dataclass
¶
ATRData(
value: Decimal,
value_percent: Decimal = Decimal("0"),
period: int = 14,
volatility_threshold: Decimal = Decimal("5.0"),
)
ATR (Average True Range) data for a token.
BollingerBandsData
dataclass
¶
BollingerBandsData(
upper_band: Decimal,
middle_band: Decimal,
lower_band: Decimal,
bandwidth: Decimal = Decimal("0"),
percent_b: Decimal = Decimal("0.5"),
period: int = 20,
std_dev: float = 2.0,
)
Bollinger Bands data for a token.
CCIData
dataclass
¶
CCIData(
value: Decimal,
period: int = 20,
upper_level: Decimal = Decimal("100"),
lower_level: Decimal = Decimal("-100"),
)
CCI (Commodity Channel Index) data for a token.
IchimokuData
dataclass
¶
IchimokuData(
tenkan_sen: Decimal,
kijun_sen: Decimal,
senkou_span_a: Decimal,
senkou_span_b: Decimal,
current_price: Decimal = Decimal("0"),
tenkan_period: int = 9,
kijun_period: int = 26,
senkou_b_period: int = 52,
)
Ichimoku Cloud data for a token.
IndicatorProvider
¶
IndicatorProvider(
macd: Callable[..., MACDData] | None = None,
bollinger: Callable[..., BollingerBandsData]
| None = None,
stochastic: Callable[..., StochasticData] | None = None,
atr: Callable[..., ATRData] | None = None,
sma: Callable[..., MAData] | None = None,
ema: Callable[..., MAData] | None = None,
adx: Callable[..., ADXData] | None = None,
obv: Callable[..., OBVData] | None = None,
cci: Callable[..., CCIData] | None = None,
ichimoku: Callable[..., IchimokuData] | None = None,
)
Provider that wraps all indicator calculators for synchronous access.
Each attribute corresponds to a MarketSnapshot accessor. The runner creates this once and injects it into the strategy, so all indicators work out of the box without strategy authors managing calculators.
MACDData
dataclass
¶
MACDData(
macd_line: Decimal,
signal_line: Decimal,
histogram: Decimal,
fast_period: int = 12,
slow_period: int = 26,
signal_period: int = 9,
)
MACD data for a token.
MAData
dataclass
¶
MAData(
value: Decimal,
ma_type: str = "SMA",
period: int = 20,
current_price: Decimal = Decimal("0"),
)
Moving Average data for a token.
OBVData
dataclass
¶
OBV (On-Balance Volume) data for a token.
PriceData
dataclass
¶
PriceData(
price: Decimal,
price_24h_ago: Decimal = Decimal("0"),
change_24h_pct: Decimal = Decimal("0"),
high_24h: Decimal = Decimal("0"),
low_24h: Decimal = Decimal("0"),
timestamp: datetime = (lambda: datetime.now(UTC))(),
source: str = "",
confidence: str = "HIGH",
)
Price data for a token.
Includes source (the named provider that produced the datum) so
accounting writers can stamp transaction_ledger.price_inputs_json with
the real provider name (VIB-3889).
ReferenceMarketStatus
¶
Bases: StrEnum
Session state originated by the gateway's reference-market calendar.
ReferencePriceData
dataclass
¶
ReferencePriceData(
instrument: str,
quote: str,
chain: str,
price: Decimal | None,
confidence: float | None,
source: str,
observed_at: datetime | None,
stale: bool,
market_status: ReferenceMarketStatus,
market_status_as_of: datetime | None,
market_status_source: str,
reason: str = "",
)
Exact-provider reference price with freshness and session provenance.
trade_block_reason
¶
trade_block_reason(
*,
max_age_seconds: int,
min_confidence: float = 0.0,
now: datetime | None = None,
) -> str | None
Return None only when this observation is safe for a new trade.
is_tradeable
¶
is_tradeable(
*,
max_age_seconds: int,
min_confidence: float = 0.0,
now: datetime | None = None,
) -> bool
Whether every fail-closed reference-price gate passes.
RSIData
dataclass
¶
RSIData(
value: Decimal,
period: int = 14,
overbought: Decimal = Decimal("70"),
oversold: Decimal = Decimal("30"),
)
RSI (Relative Strength Index) data for a token.
StochasticData
dataclass
¶
StochasticData(
k_value: Decimal,
d_value: Decimal,
k_period: int = 14,
d_period: int = 3,
overbought: Decimal = Decimal("80"),
oversold: Decimal = Decimal("20"),
)
Stochastic Oscillator data for a token.
TokenBalance
dataclass
¶
Balance information for a single token.
Comparisons delegate to the balance field (native units).
PriceLookupResult
dataclass
¶
The value, raw record, and precedence branch selected by a lookup.
PriceStore
¶
Quote-aware prices keyed by token identity, with legacy symbol aliases.
Address-shaped inputs always land in _by_identity under
TokenRef.identity_key. Symbol-only inputs are retained in
_by_symbol as a compatibility alias because a symbol does not contain
enough information to invent an address identity.
put
¶
put(
token: str | TokenRef,
*,
chain: str,
quote: str,
price: Decimal,
data: T,
symbol: str | None = None,
) -> StoredPrice[T]
Insert or replace one record and return its canonical form.
records
¶
Return all records for quote once, in deterministic insertion order.
has_unambiguous_symbol_alias
¶
Whether record.symbol names exactly this identity in its quote lane.
StoredPrice
dataclass
¶
StoredPrice(
price: Decimal,
quote: str,
data: T,
chain: str,
identity_key: PriceIdentity | None = None,
symbol: str | None = None,
)
One quote-aware price record held by :class:PriceStore.
MarketSnapshot
¶
MarketSnapshot(
chain: str | None = None,
wallet_address: str = "",
price_oracle: PriceOracle | None = None,
rsi_provider: RSIProvider | None = None,
balance_provider: BalanceProvider | None = None,
timestamp: datetime | None = None,
wallet_activity_provider: WalletActivityProvider
| None = None,
prediction_provider: Any | None = None,
indicator_provider: IndicatorProvider | None = None,
multi_dex_service: Any | None = None,
rate_monitor: Any | None = None,
funding_rate_provider: Any | None = None,
gateway_client: Any | None = None,
default_timeframe: OHLCVTimeframe | None = None,
runtime_surface: str = "unit_test",
chains: tuple[str, ...] | list[str] | None = None,
gas_oracle: Any | None = None,
aave_health_factor_provider: Any | None = None,
pool_reader_registry: Any | None = None,
pool_reader: Any | None = None,
price_aggregator: Any | None = None,
ohlcv_router: Any | None = None,
ohlcv_module: Any | None = None,
pool_history_reader: Any | None = None,
liquidity_depth_reader: Any | None = None,
slippage_estimator: Any | None = None,
pool_analytics_reader: Any | None = None,
yield_aggregator: Any | None = None,
il_calculator: Any | None = None,
volatility_calculator: Any | None = None,
risk_calculator: Any | None = None,
rate_history_reader: Any | None = None,
solana_lst_provider: Any | None = None,
data_router: Any | None = None,
)
Helper class providing market data access for strategy decisions.
MarketSnapshot provides a simple interface for strategies to access: - Token prices - RSI values - Wallet balances - Position information
The snapshot is populated with data at the start of each iteration, allowing strategies to make decisions based on current market conditions.
Example
def decide(self, market: MarketSnapshot) -> Optional[Intent]: # Get ETH price eth_price = market.price("ETH")
# Get RSI
rsi = market.rsi("ETH", period=14)
if rsi.is_oversold:
return Intent.swap("USDC", "ETH", amount_usd=Decimal("1000"))
# Check balance
balance = market.balance("USDC")
if balance.balance_usd < Decimal("100"):
return Intent.hold(reason="Insufficient balance")
return Intent.hold()
Initialize market snapshot.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
chain
|
str | None
|
Chain name (e.g., "arbitrum", "ethereum") |
None
|
wallet_address
|
str
|
Wallet address for balance queries |
''
|
price_oracle
|
PriceOracle | None
|
Function to fetch prices (token, quote) -> price |
None
|
rsi_provider
|
RSIProvider | None
|
Function to calculate RSI (token, period[, timeframe=]) -> RSIData |
None
|
balance_provider
|
BalanceProvider | None
|
Function to fetch balances (token) -> TokenBalance |
None
|
timestamp
|
datetime | None
|
Snapshot timestamp (defaults to now) |
None
|
wallet_activity_provider
|
WalletActivityProvider | None
|
Provider for leader wallet activity signals |
None
|
prediction_provider
|
Any | None
|
PredictionProvider for prediction market data |
None
|
indicator_provider
|
IndicatorProvider | None
|
IndicatorProvider for calculator-backed TA indicators |
None
|
multi_dex_service
|
Any | None
|
MultiDexService for cross-DEX price comparison |
None
|
rate_monitor
|
Any | None
|
RateMonitor instance for lending rate queries |
None
|
funding_rate_provider
|
Any | None
|
GatewayFundingRateProvider for perpetual funding rate queries |
None
|
gateway_client
|
Any | None
|
Connected GatewayClient for gateway-routed on-chain reads (used by position_health and other methods that need eth_call). |
None
|
default_timeframe
|
OHLCVTimeframe | None
|
Default OHLCV timeframe from strategy config (e.g., "15m", "1h"). Used as the default for all indicator methods (rsi, macd, sma, etc.) when no explicit timeframe is passed. Falls back to DEFAULT_TIMEFRAME if not set. |
None
|
chain
property
¶
Get the (primary) chain name.
For multi-chain snapshots this returns the first chain in
self.chains. Strategy code that cares about the active chain in
a multi-chain context must pass chain= explicitly to each method
(PRD §4.2).
chains
property
¶
Configured chain set (PRD §4.2).
Single-chain snapshots return (self.chain,). Multi-chain snapshots
— folded in from MultiChainMarketSnapshot in commit 3 — return all
configured chains. Multi-chain helpers consult len(self.chains)
to decide whether chain=None defaults to the primary or raises
AmbiguousChainError.
fork_rpc_url
property
¶
Get the Anvil fork RPC URL for on-chain reads (paper trading only).
Returns the fork's JSON-RPC endpoint when running in paper trading mode, allowing strategies to perform protocol-level reads directly against the fork. Returns None when not in paper trading mode.
VIB-1956: Enables strategies to do protocol-level reads (e.g., Aave getReserveData, DEX pool state) during paper trading.
WARNING: This is a paper-trading-only escape hatch. In production, this returns None. Do NOT gate trading logic on fork_rpc_url availability — strategies that behave differently based on this property will diverge between paper trading and production.
fork_block
property
¶
Get the current fork block number (paper trading only).
prices
property
¶
Hybrid dict-callable accessor for prices.
Two usage shapes (both supported, see ALM-2696 deferred-batch NOTE):
``market.prices.get("WETH")`` — legacy dict-style access on the
internal ``_prices`` map (used by uniswap_rsi-style backtest
adapters that probe ``hasattr(market, "prices")``).
``market.prices(["WETH", "USDC"], chain="arbitrum")`` — batch
fetch via ``price()``, returning ``{symbol: Decimal}``. Per-symbol
failures are logged and skipped.
Returning a hybrid accessor preserves both contracts; making
prices a plain method broke hasattr+get callers
('function' object has no attribute 'get'), and making it a
plain dict broke the batch-fetch tests.
price
¶
Get the price of a token.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token
|
str
|
Token symbol (e.g., "ETH", "WBTC") |
required |
quote
|
str
|
Quote currency (default "USD") |
'USD'
|
chain
|
str | None
|
Optional chain override. |
None
|
Returns:
| Type | Description |
|---|---|
Decimal
|
Token price in quote currency |
Raises:
| Type | Description |
|---|---|
AmbiguousChainError
|
If chain is None on a multi-chain snapshot. |
ChainNotConfiguredError
|
If chain is not in this snapshot's configured chains AND the oracle cannot route by chain. |
ValueError
|
If price cannot be determined. |
price_data
¶
Get full price data for a token.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token
|
str
|
Token symbol |
required |
quote
|
str
|
Quote currency (default "USD") |
'USD'
|
chain
|
str | None
|
Optional chain override (keyword-only, PRD §4.2 R1). |
None
|
Returns:
| Type | Description |
|---|---|
PriceData
|
PriceData with current price and historical data |
Raises:
| Type | Description |
|---|---|
ChainNotConfiguredError / AmbiguousChainError
|
same rules as :meth: |
reference_price
¶
reference_price(
instrument: str,
chain: str | None = None,
*,
quote: str = "USD",
) -> ReferencePriceData
Read one gateway-verified non-crypto reference price.
This surface is deliberately separate from :meth:price: it selects a
single catalogued provider feed, preserves that feed's updatedAt,
and carries an explicit market-session status. Transport, unavailable,
malformed, stale, closed, and unknown states all remain representable so
callers can fail closed through :meth:ReferencePriceData.is_tradeable.
pt_price
¶
pt_price(
symbol: str,
chain: str | None = None,
maturity: int | None = None,
*,
quote: str = "USD",
) -> PtPriceData
Get the USD price of a Pendle PT/YT by symbol (VIB-5311).
Strategy-safe, typed pass-through of the gateway PT/YT-USD price
authority (GetPtPrice, VIB-5309/5310). The gateway composes
price = pt_to_sy_rate × underlying/USD (VIB-5407 — the discounted
market mark, not the accounting-asset rate), sources both legs, and
originates the confidence band + staleness; this method performs NO
on-chain reads and NO composition — it only maps the wire contract to
the framework ValueConfidence vocabulary (design spine §1/§2).
Empty ≠ Zero: an expected-unpriceable PT (no composition leg) returns
price=None + confidence=UNAVAILABLE — NEVER Decimal("0") —
and never raises. A genuinely unmeasured price is distinguished at the
wire level by availability (UNMEASURED / ERRORED / UNSPECIFIED),
not by an empty string.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
symbol
|
str
|
Canonical PT/YT symbol — the identity + join + FIFO-match key (spine §3.1). Case-insensitive at the gateway. |
required |
chain
|
str | None
|
Chain name. |
None
|
maturity
|
int | None
|
Optional maturity hint as a Unix timestamp in SECONDS.
Config is maturity-less; |
None
|
quote
|
str
|
Quote currency (default |
'USD'
|
Returns:
| Type | Description |
|---|---|
PtPriceData
|
PtPriceData. |
PtPriceData
|
when the gateway reports the PT as unmeasured / errored, or on an |
PtPriceData
|
old gateway that does not set |
Raises:
| Type | Description |
|---|---|
AmbiguousChainError / ChainNotConfiguredError
|
per :meth: |
PriceUnavailableError
|
when no connected |
rsi
¶
Get RSI (Relative Strength Index) for a token.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token
|
str
|
Token symbol |
required |
period
|
int
|
RSI calculation period (default 14) |
14
|
timeframe
|
OHLCVTimeframe | None
|
OHLCV candle timeframe. Defaults to strategy's data_granularity config, or "4h" if not configured. |
None
|
Returns:
| Type | Description |
|---|---|
RSIData
|
RSIData with current RSI value and signal |
Raises:
| Type | Description |
|---|---|
ValueError
|
If RSI cannot be calculated |
price_across_dexs
¶
price_across_dexs(
token_in: str,
token_out: str,
amount: Decimal,
dexs: list[str] | None = None,
) -> Any
Get prices from multiple DEXs for comparison.
Fetches quotes from all configured DEXs and returns a comparison of prices and execution details.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token_in
|
str
|
Input token symbol (e.g., "USDC", "WETH") |
required |
token_out
|
str
|
Output token symbol (e.g., "WETH", "USDC") |
required |
amount
|
Decimal
|
Input amount (human-readable) |
required |
dexs
|
list[str] | None
|
DEXs to query (default: all available on chain) |
None
|
Returns:
| Type | Description |
|---|---|
Any
|
MultiDexPriceResult with quotes from each DEX |
Raises:
| Type | Description |
|---|---|
ValueError
|
If multi-DEX service is not configured (recorded as a critical data failure; standard data-unavailable catches apply) |
best_dex_price
¶
best_dex_price(
token_in: str,
token_out: str,
amount: Decimal,
dexs: list[str] | None = None,
) -> Any
Get the best DEX for a trade.
Compares prices from all configured DEXs and returns the one with the highest output amount (best execution).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token_in
|
str
|
Input token symbol (e.g., "USDC", "WETH") |
required |
token_out
|
str
|
Output token symbol (e.g., "WETH", "USDC") |
required |
amount
|
Decimal
|
Input amount (human-readable) |
required |
dexs
|
list[str] | None
|
DEXs to compare (default: all available on chain) |
None
|
Returns:
| Type | Description |
|---|---|
Any
|
BestDexResult with the best DEX and quote |
Raises:
| Type | Description |
|---|---|
ValueError
|
If multi-DEX service is not configured (recorded as a critical data failure; standard data-unavailable catches apply) |
macd
¶
macd(
token: str,
fast_period: int = 12,
slow_period: int = 26,
signal_period: int = 9,
timeframe: OHLCVTimeframe | None = None,
) -> MACDData
Get MACD (Moving Average Convergence Divergence) for a token.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token
|
str
|
Token symbol |
required |
fast_period
|
int
|
Fast EMA period (default 12) |
12
|
slow_period
|
int
|
Slow EMA period (default 26) |
26
|
signal_period
|
int
|
Signal EMA period (default 9) |
9
|
timeframe
|
OHLCVTimeframe | None
|
OHLCV candle timeframe. Defaults to strategy's data_granularity config, or "4h" if not configured. |
None
|
Returns:
| Type | Description |
|---|---|
MACDData
|
MACDData with MACD line, signal line, and histogram |
Raises:
| Type | Description |
|---|---|
ValueError
|
If MACD data is not available |
Example
macd = market.macd("WETH") if macd.is_bullish_crossover: return Intent.swap("USDC", "WETH", amount_usd=Decimal("100"))
bollinger_bands
¶
bollinger_bands(
token: str,
period: int = 20,
std_dev: float = 2.0,
timeframe: OHLCVTimeframe | None = None,
) -> BollingerBandsData
Get Bollinger Bands for a token.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token
|
str
|
Token symbol |
required |
period
|
int
|
SMA period (default 20) |
20
|
std_dev
|
float
|
Standard deviation multiplier (default 2.0) |
2.0
|
timeframe
|
OHLCVTimeframe | None
|
OHLCV candle timeframe. Defaults to strategy's data_granularity config, or "4h" if not configured. |
None
|
Returns:
| Type | Description |
|---|---|
BollingerBandsData
|
BollingerBandsData with upper, middle, lower bands and position metrics |
Raises:
| Type | Description |
|---|---|
ValueError
|
If Bollinger Bands data is not available |
Example
bb = market.bollinger_bands("WETH") if bb.is_oversold: return Intent.swap("USDC", "WETH", amount_usd=Decimal("100"))
stochastic
¶
stochastic(
token: str,
k_period: int = 14,
d_period: int = 3,
timeframe: OHLCVTimeframe | None = None,
) -> StochasticData
Get Stochastic Oscillator for a token.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token
|
str
|
Token symbol |
required |
k_period
|
int
|
%K period (default 14) |
14
|
d_period
|
int
|
%D period (default 3) |
3
|
timeframe
|
OHLCVTimeframe | None
|
OHLCV candle timeframe. Defaults to strategy's data_granularity config, or "4h" if not configured. |
None
|
Returns:
| Type | Description |
|---|---|
StochasticData
|
StochasticData with %K and %D values |
Raises:
| Type | Description |
|---|---|
ValueError
|
If Stochastic data is not available |
Example
stoch = market.stochastic("WETH") if stoch.is_oversold and stoch.k_value > stoch.d_value: return Intent.swap("USDC", "WETH", amount_usd=Decimal("100"))
atr
¶
Get ATR (Average True Range) for a token.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token
|
str
|
Token symbol |
required |
period
|
int
|
ATR period (default 14) |
14
|
timeframe
|
OHLCVTimeframe | None
|
OHLCV candle timeframe. Defaults to strategy's data_granularity config, or "4h" if not configured. |
None
|
Returns:
| Type | Description |
|---|---|
ATRData
|
ATRData with ATR value and volatility assessment |
Raises:
| Type | Description |
|---|---|
ValueError
|
If ATR data is not available |
Example
atr = market.atr("WETH") if atr.is_low_volatility: # Safe to trade return Intent.swap("USDC", "WETH", amount_usd=Decimal("100"))
sma
¶
Get Simple Moving Average for a token.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token
|
str
|
Token symbol |
required |
period
|
int
|
SMA period (default 20) |
20
|
timeframe
|
OHLCVTimeframe | None
|
OHLCV candle timeframe. Defaults to strategy's data_granularity config, or "4h" if not configured. |
None
|
Returns:
| Type | Description |
|---|---|
MAData
|
MAData with SMA value |
Raises:
| Type | Description |
|---|---|
ValueError
|
If SMA data is not available |
Example
sma = market.sma("WETH", period=50) if sma.is_price_above: print("Bullish - price above 50 SMA")
ema
¶
Get Exponential Moving Average for a token.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token
|
str
|
Token symbol |
required |
period
|
int
|
EMA period (default 12) |
12
|
timeframe
|
OHLCVTimeframe | None
|
OHLCV candle timeframe. Defaults to strategy's data_granularity config, or "4h" if not configured. |
None
|
Returns:
| Type | Description |
|---|---|
MAData
|
MAData with EMA value |
Raises:
| Type | Description |
|---|---|
ValueError
|
If EMA data is not available |
Example
ema_12 = market.ema("WETH", period=12) ema_26 = market.ema("WETH", period=26) if ema_12.value > ema_26.value: print("Golden cross - bullish")
adx
¶
Get ADX (Average Directional Index) for a token.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token
|
str
|
Token symbol |
required |
period
|
int
|
ADX period (default 14) |
14
|
timeframe
|
OHLCVTimeframe | None
|
OHLCV candle timeframe. Defaults to strategy's data_granularity config, or "4h" if not configured. |
None
|
Returns:
| Type | Description |
|---|---|
ADXData
|
ADXData with ADX, +DI, and -DI values |
Raises:
| Type | Description |
|---|---|
ValueError
|
If ADX data is not available |
Example
adx = market.adx("WETH") if adx.is_uptrend: return Intent.swap("USDC", "WETH", amount_usd=Decimal("100"))
obv
¶
Get OBV (On-Balance Volume) for a token.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token
|
str
|
Token symbol |
required |
signal_period
|
int
|
OBV signal line period (default 21) |
21
|
timeframe
|
OHLCVTimeframe | None
|
OHLCV candle timeframe. Defaults to strategy's data_granularity config, or "4h" if not configured. |
None
|
Returns:
| Type | Description |
|---|---|
OBVData
|
OBVData with OBV and signal line values |
Raises:
| Type | Description |
|---|---|
ValueError
|
If OBV data is not available |
Example
obv = market.obv("WETH") if obv.is_bullish: return Intent.swap("USDC", "WETH", amount_usd=Decimal("100"))
cci
¶
Get CCI (Commodity Channel Index) for a token.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token
|
str
|
Token symbol |
required |
period
|
int
|
CCI period (default 20) |
20
|
timeframe
|
OHLCVTimeframe | None
|
OHLCV candle timeframe. Defaults to strategy's data_granularity config, or "4h" if not configured. |
None
|
Returns:
| Type | Description |
|---|---|
CCIData
|
CCIData with CCI value and overbought/oversold status |
Raises:
| Type | Description |
|---|---|
ValueError
|
If CCI data is not available |
Example
cci = market.cci("WETH") if cci.is_oversold: return Intent.swap("USDC", "WETH", amount_usd=Decimal("100"))
ichimoku
¶
ichimoku(
token: str,
tenkan_period: int = 9,
kijun_period: int = 26,
senkou_b_period: int = 52,
timeframe: OHLCVTimeframe | None = None,
) -> IchimokuData
Get Ichimoku Cloud data for a token.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token
|
str
|
Token symbol |
required |
tenkan_period
|
int
|
Conversion line period (default 9) |
9
|
kijun_period
|
int
|
Base line period (default 26) |
26
|
senkou_b_period
|
int
|
Leading span B period (default 52) |
52
|
timeframe
|
OHLCVTimeframe | None
|
OHLCV candle timeframe. Defaults to strategy's data_granularity config, or "4h" if not configured. |
None
|
Returns:
| Type | Description |
|---|---|
IchimokuData
|
IchimokuData with all Ichimoku components |
Raises:
| Type | Description |
|---|---|
ValueError
|
If Ichimoku data is not available |
Example
ich = market.ichimoku("WETH") if ich.is_bullish_crossover and ich.is_above_cloud: return Intent.swap("USDC", "WETH", amount_usd=Decimal("100"))
invalidate_balance
¶
Evict any memoized wallet balance for token so the next
:meth:balance call re-queries the provider.
Sequential execution lanes mutate wallet balances mid-snapshot: a
teardown staircase's REPAY consumes the debt token before a later
amount="all" sweep resolves against this same snapshot, and the
stale memo then over-resolves by exactly the repaid amount (compile
fails on insufficient balance). The gateway-level cache is already
invalidated by the commit pipeline after each intent — this clears the
snapshot-level memo so the fresh value is actually read.
No-op on provider-less snapshots (paper / dry-run inject simulated balances directly into the memo maps): with no provider there is no fresher source to re-query, so evicting would turn every subsequent read into a ValueError instead of serving the (correct, simulated) memo.
invalidate_balances
¶
Evict ALL memoized wallet balances so the next :meth:balance call
re-queries the provider. Plural, whole-snapshot sibling of
:meth:invalidate_balance.
Used by post-teardown verification (VIB-5523): when a fresh snapshot cannot be built and the pre-execution snapshot is reused for the post-closure chain re-read, the position-health memo is evicted but the memoized wallet balances would otherwise still serve pre-unwind values. Clearing them forces every subsequent balance read to reflect live on-chain state.
No-op on provider-less snapshots (paper / dry-run inject simulated
balances directly into the memo maps): with no provider there is no
fresher source to re-query, so clearing would turn every subsequent
read into a ValueError instead of serving the (correct, simulated) memo
— same rationale as :meth:invalidate_balance.
balance
¶
balance(
token: str,
protocol: str | None = None,
*,
chain: str | None = None,
price: Decimal | None = None,
) -> TokenBalance
Get wallet balance for a token.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token
|
str
|
Token symbol |
required |
protocol
|
str | None
|
Optional protocol name for variant disambiguation (VIB-3138).
When set, resolves generic symbols to the protocol's preferred
variant via the connector's |
None
|
chain
|
str | None
|
Optional chain override (keyword-only, PRD §4.2 R1). Required
on multi-chain snapshots; on single-chain it must match
|
None
|
price
|
Decimal | None
|
Optional already-known USD price (keyword-only, VIB-4843
FR-5003). When supplied, |
None
|
Returns:
| Type | Description |
|---|---|
TokenBalance
|
TokenBalance with current balance (and |
TokenBalance
|
|
Raises:
| Type | Description |
|---|---|
ChainNotConfiguredError / AmbiguousChainError
|
same rules as :meth: |
ValueError
|
If balance cannot be determined. |
clear_critical_data_failures
¶
Clear all tracked critical data failures.
Called by the runner after pre-warming the price cache and before strategy.decide() runs, so that pre-warm failures (which are retried inside decide()) do not incorrectly poison the HOLD-escalation check.
has_critical_data_failures
¶
Return True when this snapshot observed any critical data failures.
critical_data_failure_count
¶
Number of currently tracked critical failures for this snapshot.
classify_critical_data_failures
¶
Classify observed data failures as transient, permanent, or mixed.
summarize_critical_data_failures
¶
Create a concise summary for logs/lifecycle error messages.
is_quiet_pool_hold
¶
Liveness backstop for a DEX pool with no recent swaps.
Returns True iff every recorded critical data failure is a DEX quiet-pool staleness miss and the affected token is still priceable from the 24/7 aggregated oracle.
Rationale: a DEX pool that simply hasn't traded recently returns stale
(not absent) trade-derived OHLCV, so swap-based indicators (RSI, MACD, …)
can't be computed. But the asset itself is continuously priceable — the
pool is alive, just quiet. Holding through that is correct and must not
be escalated to a DATA_ERROR (which trips the circuit breaker on a
live pool). A genuinely dead/unreachable feed has no oracle price and
stays critical, so the dead-pool guard is preserved.
The check is conservative: any non-quiet-pool failure, any unparseable detail, or any token that is not priceable returns False (escalate).
balance_usd
¶
Get wallet balance in USD terms.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token
|
str
|
Token symbol |
required |
protocol
|
str | None
|
Optional protocol for variant disambiguation (see |
None
|
Returns:
| Type | Description |
|---|---|
Decimal
|
Balance in USD |
collateral_value_usd
¶
Get the USD value of a given amount of collateral.
Convenience helper for perp position sizing. Multiplies the given amount by the token's current price.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token
|
str
|
Token symbol (e.g., "WETH", "USDC", "WBTC") |
required |
amount
|
Decimal
|
Token amount in human-readable units (not wei) |
required |
Returns:
| Type | Description |
|---|---|
Decimal
|
USD value as a Decimal |
total_portfolio_usd
¶
Calculate total portfolio value in USD across all known balances.
Sums balance_usd for all tokens in pre-populated balances and
cached balances (tokens queried via balance() in this snapshot).
Wallet semantics on every surface: value held inside open positions
(LP / perp / lending) is not a wallet balance and is not included —
live totals only see what the balance provider reports, and the
backtest bridge mirrors that (blueprint 31 §4.1).
_balance_cache keys are either the bare symbol (legacy single-arg
provider path) or f"{symbol}@{chain}" (multi-chain path). Strip
the chain suffix before dedup so a token populated via both
set_balance(symbol, tb) (which writes both maps) is counted once.
Keys registered via _register_cash_mirror_keys (backtest bridge)
all expose the same underlying cash source and contribute exactly
once, no matter how many exposure keys carry the face value.
set_price
¶
Pre-populate price for a token.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token
|
str
|
Token symbol |
required |
price_value
|
Decimal
|
Price value in USD |
required |
set_price_data
¶
set_price_data(
token: str,
price_data_or_chain: PriceData | str,
price_data_or_quote: PriceData | str | None = None,
quote: str = "USD",
chain: str | None = None,
) -> None
Pre-populate enriched price data for a token (useful for testing).
Accepts both call shapes during the VIB-4062 transition:
set_price_data(token, price_data, quote="USD", chain=None)— canonical strategy-layer shape.set_price_data(token, chain, price_data, quote="USD")— legacy multichain shape; chain is the second positional argument.
The ambiguity is resolved by inspecting the type of the second
argument: PriceData → canonical; str → legacy multichain.
set_balance
¶
set_balance(
token: str,
balance_data_or_chain: Any,
balance_data: TokenBalance | None = None,
) -> None
Pre-populate balance for a token.
Two call shapes:
- Canonical (post-VIB-4062):
set_balance(token, balance_data). - Legacy multichain:
set_balance(token, chain, balance_data)— kept so multi-chain runners (runner_teardown's simulated-balances injector, paper-engine cross-chain seeding) keep working.
In the multichain shape, _balances (the chain-agnostic short cache)
is updated only when chain == self._chain so cross-chain reads
stay distinct.
set_rsi
¶
Pre-populate RSI for a token.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token
|
str
|
Token symbol |
required |
rsi_data
|
RSIData
|
RSI data |
required |
timeframe
|
OHLCVTimeframe | None
|
OHLCV timeframe this data was computed from (None matches any) |
None
|
set_macd
¶
Pre-populate MACD data for a token.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token
|
str
|
Token symbol |
required |
macd_data
|
MACDData
|
MACDData instance |
required |
timeframe
|
OHLCVTimeframe | None
|
OHLCV timeframe this data was computed from (None matches any) |
None
|
Example
market.set_macd("WETH", MACDData( macd_line=Decimal("0.5"), signal_line=Decimal("0.3"), histogram=Decimal("0.2"), ))
set_bollinger_bands
¶
set_bollinger_bands(
token: str,
bb_data: BollingerBandsData,
timeframe: OHLCVTimeframe | None = None,
) -> None
Pre-populate Bollinger Bands data for a token.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token
|
str
|
Token symbol |
required |
bb_data
|
BollingerBandsData
|
BollingerBandsData instance |
required |
timeframe
|
OHLCVTimeframe | None
|
OHLCV timeframe this data was computed from (None matches any) |
None
|
Example
market.set_bollinger_bands("WETH", BollingerBandsData( upper_band=Decimal("3100"), middle_band=Decimal("3000"), lower_band=Decimal("2900"), percent_b=Decimal("0.5"), ))
set_stochastic
¶
set_stochastic(
token: str,
stoch_data: StochasticData,
timeframe: OHLCVTimeframe | None = None,
) -> None
Pre-populate Stochastic data for a token.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token
|
str
|
Token symbol |
required |
stoch_data
|
StochasticData
|
StochasticData instance |
required |
timeframe
|
OHLCVTimeframe | None
|
OHLCV timeframe this data was computed from (None matches any) |
None
|
Example
market.set_stochastic("WETH", StochasticData( k_value=Decimal("25"), d_value=Decimal("30"), ))
set_atr
¶
Pre-populate ATR data for a token.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token
|
str
|
Token symbol |
required |
atr_data
|
ATRData
|
ATRData instance |
required |
timeframe
|
OHLCVTimeframe | None
|
OHLCV timeframe this data was computed from (None matches any) |
None
|
Example
market.set_atr("WETH", ATRData( value=Decimal("50"), value_percent=Decimal("2.5"), ))
set_ma
¶
set_ma(
token: str,
ma_data: MAData,
ma_type: str = "SMA",
period: int = 20,
timeframe: OHLCVTimeframe | None = None,
) -> None
Pre-populate Moving Average data for a token.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token
|
str
|
Token symbol |
required |
ma_data
|
MAData
|
MAData instance |
required |
ma_type
|
str
|
Type of MA ("SMA" or "EMA") |
'SMA'
|
period
|
int
|
MA period |
20
|
timeframe
|
OHLCVTimeframe | None
|
OHLCV timeframe this data was computed from (None matches any) |
None
|
Example
market.set_ma("WETH", MAData( value=Decimal("3000"), ma_type="SMA", period=20, current_price=Decimal("3050"), ), ma_type="SMA", period=20)
set_adx
¶
Pre-populate ADX data for a token.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token
|
str
|
Token symbol |
required |
adx_data
|
ADXData
|
ADXData instance |
required |
timeframe
|
OHLCVTimeframe | None
|
Optional timeframe (None matches any query) |
None
|
Example
market.set_adx("WETH", ADXData( adx=Decimal("30"), plus_di=Decimal("25"), minus_di=Decimal("15"), ))
set_obv
¶
Pre-populate OBV data for a token.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token
|
str
|
Token symbol |
required |
obv_data
|
OBVData
|
OBVData instance |
required |
timeframe
|
OHLCVTimeframe | None
|
Optional timeframe (None matches any query) |
None
|
Example
market.set_obv("WETH", OBVData( obv=Decimal("1000000"), signal_line=Decimal("950000"), ))
set_cci
¶
Pre-populate CCI data for a token.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token
|
str
|
Token symbol |
required |
cci_data
|
CCIData
|
CCIData instance |
required |
timeframe
|
OHLCVTimeframe | None
|
Optional timeframe (None matches any query) |
None
|
Example
market.set_cci("WETH", CCIData( value=Decimal("-120"), ))
set_ichimoku
¶
set_ichimoku(
token: str,
ichimoku_data: IchimokuData,
timeframe: OHLCVTimeframe | None = None,
) -> None
Pre-populate Ichimoku data for a token.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token
|
str
|
Token symbol |
required |
ichimoku_data
|
IchimokuData
|
IchimokuData instance |
required |
timeframe
|
OHLCVTimeframe | None
|
Optional timeframe (None matches any query) |
None
|
Example
market.set_ichimoku("WETH", IchimokuData( tenkan_sen=Decimal("3050"), kijun_sen=Decimal("3000"), senkou_span_a=Decimal("3025"), senkou_span_b=Decimal("2950"), current_price=Decimal("3100"), ))
lending_markets
¶
lending_markets(
protocol: str,
*,
collateral: str | None = None,
loan: str | None = None,
lltv_bps: int | None = None,
chain: str | None = None,
page_size: int | None = None,
) -> list[LendingMarketInfo]
List candidate lending markets for protocol (VIB-5985).
Returns EVERY catalog candidate matching the filters — the gateway never
ranks or auto-picks. Purpose is verification / re-check of a market
universe at build or boot time, NOT per-iteration market selection: the
returned records are verified=False / source="curated_catalog".
Call :meth:lending_market to on-chain-VERIFY a chosen market_id
before pinning it into a config (Morpho is permissionless — a same-pair
market can carry a hostile oracle/IRM). An empty list means zero
candidates matched; the caller MUST treat that as an error to act on, not
a silent continue. Chain reads happen entirely gateway-side.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
protocol
|
str
|
Connector slug (e.g. |
required |
collateral
|
str | None
|
Optional collateral filter — symbol OR address (resolved to an address gateway-side before matching). |
None
|
loan
|
str | None
|
Optional loan/borrow-token filter — symbol OR address. |
None
|
lltv_bps
|
int | None
|
Optional exact LLTV filter in basis points (9150 = 91.5%). |
None
|
chain
|
str | None
|
Optional chain override; defaults to the snapshot's chain. |
None
|
page_size
|
int | None
|
Optional page size hint for the underlying RPC (the accessor transparently follows pagination and returns ALL matches regardless). |
None
|
Raises:
| Type | Description |
|---|---|
LendingMarketResolutionError
|
no connected gateway client, RPC transport failure, or an unsupported protocol/chain. |
lending_market
¶
Verify one exact market_id on-chain (VIB-5985).
The gateway reads idToMarketParams(market_id) on-chain, recomputes the
market id from the returned params and compares — only a match returns a
record, always verified=True / source="onchain_verify". Use this
to re-check a configured market_id at boot (config still PINS the
market identity; this does not pick one). A recompute mismatch, a
non-existent market, or an unsupported protocol/chain each raise
LendingMarketResolutionError — a market id is NEVER returned unverified.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
protocol
|
str
|
Connector slug (e.g. |
required |
market_id
|
str
|
Exact bytes32 hex market id to verify. |
required |
chain
|
str | None
|
Optional chain override; defaults to the snapshot's chain. |
None
|
Raises:
| Type | Description |
|---|---|
LendingMarketResolutionError
|
transport failure, unsupported protocol/chain, market not found, or verification mismatch. |
lending_rate
¶
lending_rate(
protocol: str,
token: str,
side: str = "supply",
*,
chain: str | None = None,
market_id: str | None = None,
) -> Any
Get the lending rate for a specific protocol and token.
Canonical strategy-side accessor for live lending rates (VIB-4859 / W7).
Delegates to the gateway's RateHistoryService.GetLendingRateCurrent
RPC via :class:almanak.framework.data.rates.RateMonitor — all HTTP /
Web3 egress for rate lookups happens server-side via
:class:GatewayLendingRateHistoryCapability implementations on the
corresponding connectors.
Use this in preference to the deprecated
:class:almanak.framework.data.rates.RateMonitor direct surface
(caller migration tracked in VIB-4869).
Resolution order:
- Pre-populated cache (
set_lending_rate(...)) — hit first for strategies that inject rates synthetically (backtests / tests). - The constructor-injected
rate_monitor(legacy path), if set. - A lazily-constructed default
RateMonitor(calls the gateway).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
protocol
|
str
|
Protocol identifier ( |
required |
token
|
str
|
Token symbol (e.g. |
required |
side
|
str
|
Rate side — |
'supply'
|
chain
|
str | None
|
Optional chain override (keyword-only, PRD §4.2 R1). When omitted the snapshot's resolved chain is used. |
None
|
market_id
|
str | None
|
Optional market scoping for ISOLATED-market lenders (Morpho Blue — a bytes32 market id). Keyword-only. Pass this whenever you mean a specific market (VIB-5729).
On an isolated-market venue A market-scoped call is accounting-grade: it is verified against the gateway's echo and NEVER degrades to a placeholder constant, so it either returns the requested market's measured rate or raises. |
None
|
Returns:
| Type | Description |
|---|---|
Any
|
class: |
Any
|
|
Raises:
| Type | Description |
|---|---|
ChainNotConfiguredError / AmbiguousChainError
|
same rules as :meth: |
ValueError
|
If the gateway returns no rate and no fallback is configured. |
Example
rate = market.lending_rate("aave_v3", "USDC", "supply") print(f"Aave USDC Supply APY: {rate.apy_percent:.2f}%")
best_lending_rate
¶
best_lending_rate(
token: str,
side: str = "supply",
protocols: list[str] | None = None,
*,
chain: str | None = None,
) -> Any
Get the best lending rate for a token across protocols.
Canonical strategy-side accessor for the best live lending rate
(VIB-4869), symmetric with :meth:lending_rate. For supply rates,
returns highest APY; for borrow rates, returns lowest APY.
Resolution order mirrors :meth:lending_rate:
- The constructor-injected
rate_monitor(legacy path), if set — preserves test surfaces that mock the monitor. - A lazily-constructed framework-internal
RateMonitor(_internal=True) that fans out to the gateway. This keeps direct instantiations (README-style flows, unit / backtest harnesses callingcreate_market_snapshot()directly) working without an injected monitor, instead of unconditionally raising.
As with :meth:lending_rate, the lazy lane queries the gateway
directly and raises loudly when the gateway is unreachable rather
than silently substituting placeholder rates — surfacing hardcoded
numbers for a strategy that asked for the best live rate is a bigger
footgun than raising.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token
|
str
|
Token symbol (e.g., "USDC", "WETH") |
required |
side
|
str
|
Rate side - "supply" or "borrow" (default "supply") |
'supply'
|
protocols
|
list[str] | None
|
Protocols to compare (default: all available on chain) |
None
|
chain
|
str | None
|
Optional chain override (keyword-only). When omitted the snapshot's resolved chain is used. |
None
|
Returns:
| Type | Description |
|---|---|
Any
|
BestRateResult with best_rate, all_rates, etc. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If no monitor is injected and the gateway is unavailable, or if the best-rate lookup otherwise fails. |
Example
result = market.best_lending_rate("USDC", "supply") if result.best_rate: print(f"Best: {result.best_rate.protocol} at {result.best_rate.apy_percent:.2f}%")
set_lending_rate
¶
set_lending_rate(
protocol: str,
token: str,
side: str,
rate: Any,
market_id: str | None = None,
) -> None
Pre-populate a lending rate for a protocol/token/side.
Useful for backtesting and testing where you want to inject known rates without needing a live RateMonitor.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
protocol
|
str
|
Protocol identifier (e.g., "aave_v3") |
required |
token
|
str
|
Token symbol (e.g., "USDC") |
required |
side
|
str
|
Rate side ("supply" or "borrow") |
required |
rate
|
Any
|
LendingRate dataclass instance |
required |
market_id
|
str | None
|
Optional market scoping, matching :meth: |
None
|
Example
from almanak.framework.data.rates import LendingRate market.set_lending_rate("aave_v3", "USDC", "supply", LendingRate( protocol="aave_v3", token="USDC", side="supply", apy_ray=Decimal("0"), apy_percent=Decimal("4.25"), ))
position_health
¶
position_health(
protocol: str,
market_id: str,
rpc_url: str | None = None,
collateral_price_usd: Decimal | None = None,
debt_price_usd: Decimal | None = None,
ref: LendingPositionRef | None = None,
) -> Any
Get health factor for a lending position.
Reads on-chain position data and computes the health factor for Aave V3, Morpho Blue, or Compound V3 positions. Uses gateway-routed eth_calls when a gateway_client was wired into this MarketSnapshot.
In backtests (ALM-2943) the engine wires a simulated position view and
health is served from the sim's own tracked lending positions — the
same collateral/debt/threshold plane the liquidation guard marks each
tick — with no gateway involved. No position for the protocol returns
the empty-account shape (no debt ⇒ Infinity health factor);
genuinely uncomputable reads refuse with a decision-input ledger entry.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
protocol
|
str
|
"aave_v3", "morpho_blue", or "compound_v3" |
required |
market_id
|
str
|
Protocol-specific market identifier. For Aave V3 this is informational (one pool per chain). For Morpho Blue this is the bytes32 market id. For Compound V3 this is the Comet market key (e.g. "usdc", "weth"). |
required |
rpc_url
|
str | None
|
Optional explicit RPC URL. Strategies should leave this None so the gateway-routed path is used. Paper-trading code may pass a local anvil URL. |
None
|
collateral_price_usd
|
Decimal | None
|
Optional override for collateral price on
cross-asset markets. When both overrides are omitted, the
framework defaults to the market's own liquidation oracle
(exact, e.g. Morpho Blue), then to the wired price oracle; it
fails closed when neither answers. Supply both overrides or
neither — a partial pair raises. The result's |
None
|
debt_price_usd
|
Decimal | None
|
Optional override for debt-token price (same). |
None
|
ref
|
LendingPositionRef | None
|
Optional typed :class: |
None
|
Returns:
| Type | Description |
|---|---|
Any
|
PositionHealth with .health_factor (Decimal), .collateral_value_usd, |
Any
|
.debt_value_usd, .max_borrow_usd, .protocol, .market_id, |
Any
|
.price_source. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If health data cannot be retrieved. |
perp_positions
¶
Read the wallet's measured perp positions for protocol.
Returns the protocol-neutral :class:PerpsReadResult. ok=True with
an empty tuple is measured flat; ok=False is an unavailable read and
must never be interpreted as zero exposure. On a live snapshot all RPC
access is routed through the snapshot's gateway client; on a backtest
snapshot with a bound simulated position view the read is served from
the engine's own book (observation parity) and no gateway call is made.
block_timestamp
¶
Return the latest measured block timestamp through the gateway.
None means the chain clock is unmeasured. Callers must not substitute
wall time for venue rules that are defined in terms of block.timestamp.
lp_position_value
¶
lp_position_value(
position_id: str,
protocol: str,
pool_address: str | None = None,
*,
token0_symbol: str | None = None,
token1_symbol: str | None = None,
) -> Any
Value an open concentrated-liquidity LP position live on-chain.
Sibling of :meth:position_health: builds an LPPositionReader from
this snapshot's gateway client and reuses the SAME shared repricing
engine (lp_repricer.reprice_lp_position) the portfolio valuer uses —
no bespoke tick / liquidity math. Reads positions(tokenId) and the
pool slot0, prices each token via this snapshot's oracle
(self.price), and adds uncollected fees.
Scope: this is the concentrated-liquidity / V3-family valuation path
(Slipstream, Uniswap V3). The "shares one engine with the portfolio
valuer" guarantee holds for that family; the valuer's own
family-dispatch routes V4 / Curve / fungible-LP positions through
specialized readers this method does not invoke. For a non-CL protocol
the underlying position-manager role resolves to None and this
returns None (fail-closed, Empty ≠ Zero) rather than a wrong number.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
position_id
|
str
|
The NFT |
required |
protocol
|
str
|
Protocol slug (e.g. |
required |
pool_address
|
str | None
|
The pool contract address for the exact-price
|
None
|
token0_symbol / token1_symbol
|
Optional symbol hints threaded into
|
required |
Returns:
| Type | Description |
|---|---|
Any
|
|
Any
|
|
Any
|
when the position cannot be measured (Empty ≠ Zero — an unmeasured |
Any
|
read is NEVER a fabricated |
Any
|
(zero liquidity, zero fees) returns an all-zero measured result. |
Any
|
In backtests (ALM-2943) the engine's own tracked LP position is |
Any
|
served from the portfolio marker's repricing plane; an unknown |
Any
|
|
Any
|
ledger entry instead of returning a silent |
invalidate_position_health
¶
Evict memoized position_health results so the next call re-reads on-chain.
position_health memoizes per (protocol, market_id, rpc_url,
collateral_price, debt_price) for the snapshot's lifetime. Teardown
verification reuses the pre-execution snapshot to confirm POST-closure
state; without eviction the post-WITHDRAW read serves the PRE-WITHDRAW
health and falsely reports a zeroed lending position still open
(VIB-5523). Mirrors :meth:invalidate_balance.
Cache keys are populated from the raw protocol / market_id a
caller passes to :meth:position_health (no normalization at seed
time), so an invalidation argument whose casing differs from the
seeded key — mixed-case protocol slugs, checksummed vs lowercase hex
market_id — would otherwise miss and leave the stale memo in place
(defeating the VIB-5523 fix). Compare both sides case-insensitively so
eviction always hits regardless of casing on either side.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
protocol
|
str | None
|
When given, evict only entries for this protocol. |
None
|
market_id
|
str | None
|
When given (with |
None
|
set_position_health
¶
Pre-populate position health for tests and backtesting.
Useful where you want to inject a known PositionHealth without needing a live gateway_client. Mirrors set_lending_rate.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
protocol
|
str
|
Protocol identifier (e.g., "aave_v3") |
required |
market_id
|
str
|
Protocol-specific market identifier |
required |
health
|
Any
|
PositionHealth dataclass instance |
required |
lending_position_balances
¶
lending_position_balances(
protocol: str,
token: str,
*,
market_id: str | None = None,
chain: str | None = None,
) -> tuple[int | None, int | None]
Per-reserve on-chain supply (aToken) and debt balances, in the reserve's native wei.
Reads the wallet's actual on-chain position for one lending reserve via
the connector-owned balance readers (balance_readers.get_reader_for_protocol,
gateway-routed eth_call — no direct network). This is the
un-conflated counterpart to :meth:position_health: position_health
returns the account-level USD aggregate (getUserAccountData.totalCollateralBase
— the borrow-capacity figure, which is 0 for an LTV=0 / isolation /
non-collateral supply even when the deposit is fully withdrawable), whereas
this returns the literal aToken balance, so a teardown can tell an empty
supply (skip) from a non-collateral supply with a positive balance (keep)
without conflating the two (VIB-5468 / VIB-5484).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
protocol
|
str
|
Lending protocol identifier (e.g. |
required |
token
|
str
|
Reserve asset symbol or address. |
required |
market_id
|
str | None
|
Optional protocol market id (e.g. the Compound Comet key); ignored by whole-pool protocols (Aave family). |
None
|
chain
|
str | None
|
Chain override; defaults to the snapshot's pinned chain. |
None
|
Returns:
| Type | Description |
|---|---|
int | None
|
|
int | None
|
is unmeasured (no gateway, unresolvable token, unsupported protocol, |
tuple[int | None, int | None]
|
or the read failed). Empty ≠ Zero: |
tuple[int | None, int | None]
|
fabricated |
tuple[int | None, int | None]
|
|
tuple[int | None, int | None]
|
|
tuple[int | None, int | None]
|
the |
aave_health_factor
¶
Aave V3 account health factor for chain via the wired provider.
Returns None when no aave_health_factor_provider was wired into
this snapshot (the default multi-chain CLI path wires only a balance
provider) OR when the provider reports no live Aave position. Callers
treat None as "no confirmed live position" and must not infer a
healthy or unhealthy position from it. This mirrors the
None-on-no-provider convention of :meth:prediction_price /
:meth:wallet_activity so decide() can branch with if hf is
None (see docs/internal/blueprints/01-data-layer.md
§MarketSnapshot — provider-driven methods).
The provider (an AaveHealthFactorProvider — a
(chain) -> Decimal | None callable) owns any gateway-routed read;
this accessor is pure delegation and opens no network connection
itself, so it does not cross the gateway boundary. Exceptions raised by
the provider propagate unchanged: a gateway failure must surface, never
be silently coerced to None and mistaken for "no position" — the
leverage-stacking guard in cross-chain strategies depends on that
distinction.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
chain
|
str | None
|
Chain to query (keyword-only, PRD §4.2). Required on a
multi-chain snapshot; on a single-chain snapshot it must match
|
None
|
In backtests (ALM-2943) the engine wires the simulated position view
as the provider, so None truthfully means "the sim holds no Aave
position" and an open sim position serves its computed health factor
(Infinity when there is no debt). When NO provider is wired, the
None is additionally recorded in the decision-input ledger so a
hollow run can name it — the return contract is unchanged.
Returns:
| Type | Description |
|---|---|
Decimal | None
|
The Aave V3 health factor as a |
Decimal | None
|
provider is wired / no live position exists. |
funding_rate
¶
Get the current funding rate for a perpetual market on a specific venue.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
venue
|
str
|
Venue identifier (e.g., "gmx_v2", "hyperliquid") |
required |
market
|
str
|
Market symbol (e.g., "ETH-USD") |
required |
market_address
|
str
|
Exact market address for dynamically discovered venues |
''
|
Returns:
| Type | Description |
|---|---|
FundingRate
|
FundingRate dataclass with rate_hourly, rate_8h, rate_annualized, etc. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If no funding rate provider is configured or venue is unsupported |
perp_mark_price
¶
perp_mark_price(
venue: str,
market: str,
*,
index_token_address: str | None = None,
chain: str | None = None,
) -> Decimal
Return a trustworthy mark for one perpetual market.
EVM venues must provide index_token_address and therefore use the
ordinary address-native price plane. Addressless venues (currently
HyperCore) use their market-scoped mark. A live funding response that
declares is_live_data=False is rejected even when it contains a
positive placeholder mark. Backtests instead read the tick's already
seeded venue-candle price, so funding fallbacks never become prices.
funding_rate_spread
¶
funding_rate_spread(
market: str,
venue_a: str,
venue_b: str,
market_address: str = "",
) -> FundingRateSpread
Get the funding rate spread between two venues.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
market
|
str
|
Market symbol (e.g., "ETH-USD") |
required |
venue_a
|
str
|
First venue identifier |
required |
venue_b
|
str
|
Second venue identifier |
required |
market_address
|
str
|
Exact market address for dynamically discovered venues |
''
|
Returns:
| Type | Description |
|---|---|
FundingRateSpread
|
FundingRateSpread dataclass with spread_hourly, spread_annualized, rate_a, rate_b |
Raises:
| Type | Description |
|---|---|
ValueError
|
If no funding rate provider is configured or venue is unsupported |
wallet_activity
¶
wallet_activity(
leader_address: str | None = None,
action_types: list[str] | None = None,
min_usd_value: Decimal | None = None,
protocols: list[str] | None = None,
) -> list
Get leader wallet activity signals for copy trading.
Returns filtered signals from the WalletActivityProvider. If no provider is configured, returns an empty list (graceful degradation).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
leader_address
|
str | None
|
Filter by specific leader wallet address |
None
|
action_types
|
list[str] | None
|
Filter by action types (e.g., ["SWAP"]) |
None
|
min_usd_value
|
Decimal | None
|
Minimum USD value filter |
None
|
protocols
|
list[str] | None
|
Filter by protocol names (e.g., ["uniswap_v3"]) |
None
|
Returns:
| Type | Description |
|---|---|
list
|
List of CopySignal objects matching the filters |
prediction_price
¶
Get current price for a prediction market outcome.
Convenience method that extracts the YES or NO price from a market.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
market_id
|
str
|
Prediction market ID or URL slug |
required |
outcome
|
str
|
"YES" or "NO" |
required |
Returns:
| Type | Description |
|---|---|
Decimal | None
|
Current price as Decimal (0.01 to 0.99), or None if unavailable |
Example
yes_price = market.prediction_price("btc-100k", "YES") if yes_price is not None and yes_price < Decimal("0.3"): return BuyIntent(...)
get_price_oracle_dict
¶
Return the derived USD-only compatibility view for legacy consumers.
The authoritative cache is :class:PriceStore, keyed by
TokenRef.identity_key plus quote. This method deliberately exports
only quote=USD records. Address identities render as
chain:address; symbol aliases are additive on the snapshot's primary
chain. Off-primary symbol-only records also render as
chain:SYMBOL because they have no address identity to fall back
to; a bare alias is added when that symbol is unambiguous across the
snapshot. Solana mint casing is preserved.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
with_sources
|
bool
|
When |
False
|
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Flat |
dict[str, Any]
|
|
gas_price
¶
Get current gas price for a chain (data-layer port).
Returns GasPrice from the configured _gas_oracle (provider
attribute). Caches per _gas_cache_ttl_seconds (default 12s).
Raises:
| Type | Description |
|---|---|
GasUnavailableError
|
If the gas price cannot be fetched. |
ValueError
|
If no gas oracle is configured. |
estimate_swap_gas_cost_usd
¶
Estimate USD cost of a typical DEX swap on the given chain.
Returns Decimal("0") when the gas oracle is unconfigured or the
underlying estimated_cost_usd is zero — strategy authors who need
a fail-closed gate should call gas_price() directly and handle
the typed errors.
is_trade_worthwhile
¶
is_trade_worthwhile(
amount_usd: Decimal,
chain: str | None = None,
max_gas_ratio: Decimal = Decimal("0.05"),
) -> bool
Whether amount_usd is worth paying gas for on chain.
Fail-open: if a gas estimate cannot be obtained (oracle missing,
returns 0, or raises GasUnavailableError), returns True so the
strategy is not silently halted by a transient infrastructure issue.
pool_price
¶
Get the live price from an on-chain DEX pool.
Reads slot0() from the pool contract and decodes sqrtPriceX96 into a human-readable price. Classification is EXECUTION_GRADE (fail-closed, no off-chain fallback).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pool_address
|
str
|
Pool contract address. |
required |
chain
|
str | None
|
Chain name. Defaults to this snapshot's chain. |
None
|
Returns:
| Type | Description |
|---|---|
DataEnvelope[PoolPrice]
|
DataEnvelope[PoolPrice] with provenance metadata. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If no pool reader registry is configured. |
PoolPriceUnavailableError
|
If the pool price cannot be retrieved. |
pool_price_by_pair
¶
pool_price_by_pair(
token_a: str,
token_b: str,
chain: str | None = None,
protocol: str | None = None,
fee_tier: int = 3000,
) -> DataEnvelope[PoolPrice]
Get the live pool price for a token pair.
Resolves the pool address for the given pair and reads the price.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token_a
|
str
|
Token A symbol or address. |
required |
token_b
|
str
|
Token B symbol or address. |
required |
chain
|
str | None
|
Chain name. Defaults to this snapshot's chain. |
None
|
protocol
|
str | None
|
Protocol name (e.g. "uniswap_v3"). If None, tries all registered protocols. |
None
|
fee_tier
|
int
|
Fee tier in basis points (default 3000 = 0.3%). |
3000
|
Returns:
| Type | Description |
|---|---|
DataEnvelope[PoolPrice]
|
DataEnvelope[PoolPrice] with provenance metadata. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If no pool reader registry is configured. |
PoolPriceUnavailableError
|
If the pool cannot be found or the price cannot be read. |
pool_reserves
¶
Get DEX pool reserves and state.
Fetches the current state of a DEX liquidity pool from the blockchain.
The pool's AMM shape is auto-detected: Uniswap-V3-style concentrated
liquidity pools (dex="uniswap_v3"), Solidly classic pools —
Aerodrome / Velodrome volatile and stable (dex="solidly_v2") — and
plain V2 pairs (dex="uniswap_v2"; fee_tier is None there — V2
fees are not on-chain readable) are all supported.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pool_address
|
str
|
Pool contract address. |
required |
chain
|
str | None
|
Chain identifier. Defaults to this snapshot's chain. |
None
|
Returns:
| Type | Description |
|---|---|
PoolReserves
|
PoolReserves dataclass with reserves, fee tier, and (for V3-shaped |
PoolReserves
|
pools) sqrtPrice / tick / liquidity. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If no pool reader is configured. |
PoolReservesUnavailableError
|
If pool data cannot be retrieved. |
twap
¶
twap(
token_pair: str | Instrument,
chain: str | None = None,
window_seconds: int = 300,
pool_address: str | None = None,
protocol: str = "uniswap_v3",
token0_decimals: int | None = None,
token1_decimals: int | None = None,
) -> DataEnvelope[AggregatedPrice]
Get the time-weighted average price (TWAP) for a token pair.
Uses the Uniswap V3 oracle's observe() function to compute the TWAP over the specified time window. Classification: EXECUTION_GRADE (fail-closed, no off-chain fallback).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token_pair
|
str | Instrument
|
Token pair as "BASE/QUOTE" string or Instrument. |
required |
chain
|
str | None
|
Chain name. Defaults to this snapshot's chain. |
None
|
window_seconds
|
int
|
Time window in seconds (default 300 = 5 min). |
300
|
pool_address
|
str | None
|
Explicit pool address. If None, resolves from pair. |
None
|
protocol
|
str
|
Protocol to use (default "uniswap_v3"). |
'uniswap_v3'
|
token0_decimals
|
int | None
|
Decimals for token0. If omitted and
|
None
|
token1_decimals
|
int | None
|
Decimals for token1. Same fallback as above. |
None
|
Returns:
| Type | Description |
|---|---|
DataEnvelope[AggregatedPrice]
|
DataEnvelope[AggregatedPrice] with TWAP price and provenance. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If no price aggregator is configured, or if token
decimals cannot be derived for an explicit |
PoolPriceUnavailableError
|
If TWAP cannot be calculated. |
lwap
¶
lwap(
token_pair: str | Instrument,
chain: str | None = None,
fee_tiers: list[int] | None = None,
protocols: list[str] | None = None,
) -> DataEnvelope[AggregatedPrice]
Get the liquidity-weighted average price (LWAP) for a token pair.
Reads live prices from all known pools for the pair, filters by minimum liquidity, and computes a liquidity-weighted average.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token_pair
|
str | Instrument
|
Token pair as "BASE/QUOTE" string or Instrument. |
required |
chain
|
str | None
|
Chain name. Defaults to this snapshot's chain. |
None
|
fee_tiers
|
list[int] | None
|
Fee tiers to search (default: [100, 500, 3000, 10000]). |
None
|
protocols
|
list[str] | None
|
Protocols to search (default: all registered for chain). |
None
|
Returns:
| Type | Description |
|---|---|
DataEnvelope[AggregatedPrice]
|
DataEnvelope[AggregatedPrice] with LWAP price and provenance. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If no price aggregator is configured. |
PoolPriceUnavailableError
|
If LWAP cannot be calculated. |
pool_history
¶
pool_history(
pool_address: str,
chain: str | None = None,
start_date: datetime | None = None,
end_date: datetime | None = None,
resolution: str = "1h",
*,
protocol: str,
) -> DataEnvelope[list[PoolSnapshot]]
Get historical pool state snapshots for backtesting and analytics.
Live runners route through the gateway's PoolHistoryService:
all upstream provider egress happens gateway-side
and the envelope's meta.source names the provider that actually
served the rows. Backtest surfaces inject NullPoolHistoryReader
so the call fails deterministically under replay.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pool_address
|
str
|
Pool contract address. |
required |
chain
|
str | None
|
Chain name. Defaults to this snapshot's chain. |
None
|
start_date
|
datetime | None
|
Start of the history window. Defaults to 90 days
before |
None
|
end_date
|
datetime | None
|
End of the history window. Defaults to the snapshot's
iteration |
None
|
resolution
|
str
|
"1h" / "4h" / "1d". Default "1h". |
'1h'
|
protocol
|
str
|
REQUIRED keyword-only. Protocol slug — e.g.
|
required |
Returns:
| Type | Description |
|---|---|
DataEnvelope[list[PoolSnapshot]]
|
DataEnvelope[list[PoolSnapshot]] with INFORMATIONAL classification. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If no pool history reader is configured. |
PoolHistoryUnavailableError
|
If historical data cannot be retrieved. |
TypeError
|
If |
liquidity_depth
¶
Get tick-level liquidity depth for a concentrated-liquidity pool.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pool_address
|
str
|
Pool contract address. |
required |
chain
|
str | None
|
Chain name. Defaults to this snapshot's chain. |
None
|
Returns:
| Type | Description |
|---|---|
DataEnvelope[LiquidityDepth]
|
DataEnvelope[LiquidityDepth] with tick-level liquidity data. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If no liquidity depth reader is configured. |
LiquidityDepthUnavailableError
|
If liquidity data cannot be read. |
estimate_slippage
¶
estimate_slippage(
token_in: str,
token_out: str,
amount: Decimal,
chain: str | None = None,
protocol: str | None = None,
pool_address: str | None = None,
fee_tier: int | None = None,
) -> DataEnvelope[SlippageEstimate]
Estimate price impact and slippage for a potential swap.
Simulates the swap through tick ranges using actual on-chain liquidity.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token_in
|
str
|
Input token symbol or address. |
required |
token_out
|
str
|
Output token symbol or address. |
required |
amount
|
Decimal
|
Amount of token_in to swap (human-readable units). |
required |
chain
|
str | None
|
Chain name. Defaults to this snapshot's chain. |
None
|
protocol
|
str | None
|
Protocol name. Auto-detected if None. |
None
|
pool_address
|
str | None
|
Exact pool to simulate. Pass the same address used in
|
None
|
fee_tier
|
int | None
|
Exact pool discriminator when |
None
|
Returns:
| Type | Description |
|---|---|
DataEnvelope[SlippageEstimate]
|
DataEnvelope[SlippageEstimate] with price impact data. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If no slippage estimator is configured. |
SlippageEstimateUnavailableError
|
If slippage cannot be estimated. |
pool_analytics
¶
pool_analytics(
pool_address: str,
chain: str | None = None,
protocol: str | None = None,
) -> DataEnvelope[PoolAnalytics]
Get real-time analytics for a pool (TVL, volume, fee APR/APY).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pool_address
|
str
|
Pool contract address. |
required |
chain
|
str | None
|
Chain name. Defaults to this snapshot's chain. |
None
|
protocol
|
str | None
|
Optional protocol hint (e.g. "uniswap_v3"). |
None
|
Returns:
| Type | Description |
|---|---|
DataEnvelope[PoolAnalytics]
|
DataEnvelope[PoolAnalytics] with INFORMATIONAL classification. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If no pool analytics reader is configured. |
PoolAnalyticsUnavailableError
|
If analytics cannot be retrieved. |
best_pool
¶
best_pool(
token_a: str,
token_b: str,
chain: str | None = None,
metric: str = "fee_apr",
protocols: list[str] | None = None,
) -> DataEnvelope[PoolAnalyticsResult]
Find the best pool for a token pair based on a metric.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token_a
|
str
|
First token symbol. |
required |
token_b
|
str
|
Second token symbol. |
required |
chain
|
str | None
|
Chain name. Defaults to this snapshot's chain. |
None
|
metric
|
str
|
"fee_apr" / "fee_apy" / "tvl_usd" / "volume_24h_usd". |
'fee_apr'
|
protocols
|
list[str] | None
|
Optional list of protocols to filter by. |
None
|
Returns:
| Type | Description |
|---|---|
DataEnvelope[PoolAnalyticsResult]
|
DataEnvelope[PoolAnalyticsResult] with the best pool. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If no pool analytics reader is configured. |
PoolAnalyticsUnavailableError
|
If no pools found or all providers fail. |
token_pools
¶
token_pools(
token: str,
chain: str | None = None,
*,
min_liquidity_usd: Decimal | float | None = None,
allow_fallback_provider: bool = True,
) -> DataEnvelope[TokenPools]
List the venues where token trades, deepest first (VIB-6599).
The viability check that belongs BEFORE an intent: a signal whose venue does not exist, or exists with $84 of daily volume, is rejectable in one call. Without it that same fact surfaces three layers down as an oracle-divergence refusal that reads like a data-quality bug.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token
|
str
|
Token symbol or address. Symbols resolve through the static registry (no egress); pass an address for tail tokens the registry does not carry. |
required |
chain
|
str | None
|
Chain name. Defaults to this snapshot's chain. |
None
|
min_liquidity_usd
|
Decimal | float | None
|
Optional floor on |
None
|
allow_fallback_provider
|
bool
|
Permit the gateway's keyless fallback when the primary provider is unavailable (default True: without it a gateway with no CoinGecko key returns nothing at all). The fallback engages ONLY after the primary fails, so a configured gateway behaves identically either way. |
True
|
Returns:
| Type | Description |
|---|---|
DataEnvelope[TokenPools]
|
|
DataEnvelope[TokenPools]
|
descending with unmeasured reserves last. An EMPTY tuple is a |
DataEnvelope[TokenPools]
|
measured "no venues on this chain", not a failure. |
DataEnvelope[TokenPools]
|
Do NOT match |
DataEnvelope[TokenPools]
|
|
DataEnvelope[TokenPools]
|
first entry as the deepest venue that EXISTS unless |
DataEnvelope[TokenPools]
|
|
DataEnvelope[TokenPools]
|
"deepest I can see". |
Raises:
| Type | Description |
|---|---|
ValueError
|
If no pool analytics reader is configured, the token
cannot be resolved to an address, or |
PoolAnalyticsUnavailableError
|
If the venue list cannot be fetched. |
yield_opportunities
¶
yield_opportunities(
token: str,
chains: list[str] | None = None,
min_tvl: float = 100000,
sort_by: str = "apy",
) -> DataEnvelope[list[YieldOpportunity]]
Find yield opportunities for a token across protocols and chains.
.. warning::
Not gateway-served yet. No runtime surface wires a yield
aggregator: the gateway proto has no Yield service, and the
framework YieldAggregator performs direct HTTP egress, which
the gateway boundary forbids in the strategy container. Until a
gateway Yield service exists, calling this on a runner-built
snapshot raises ValueError naming the live alternatives:
pool_analytics() / best_pool() (LP APY + TVL),
lending_rate() / lending_rate_history() (lending APRs),
and funding_rate() / funding_rate_history() (perp
funding). Tests / research harnesses may inject an aggregator
via the yield_aggregator constructor parameter.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token
|
str
|
Token symbol. |
required |
chains
|
list[str] | None
|
Optional list of chains to filter. None means all. |
None
|
min_tvl
|
float
|
Minimum TVL in USD. Default $100k. |
100000
|
sort_by
|
str
|
"apy" / "tvl" / "risk_score". Default "apy". |
'apy'
|
Returns:
| Type | Description |
|---|---|
DataEnvelope[list[YieldOpportunity]]
|
DataEnvelope[list[YieldOpportunity]] sorted by chosen metric. |
Raises:
| Type | Description |
|---|---|
ValueError
|
Always, on runner-built snapshots — no yield aggregator is configured (capability not gateway-served yet; see warning above). |
YieldOpportunitiesUnavailableError
|
If an injected aggregator fails to retrieve data. |
lending_rate_history
¶
lending_rate_history(
protocol: str,
token: str,
chain: str | None = None,
days: int = 90,
) -> DataEnvelope[list[LendingRateSnapshot]]
Get historical lending rate snapshots for backtesting.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
protocol
|
str
|
Lending protocol (e.g. "aave_v3"). |
required |
token
|
str
|
Token symbol. |
required |
chain
|
str | None
|
Chain name. Defaults to this snapshot's chain. |
None
|
days
|
int
|
Number of days of history. Default 90. |
90
|
Returns:
| Type | Description |
|---|---|
DataEnvelope[list[LendingRateSnapshot]]
|
DataEnvelope[list[LendingRateSnapshot]] sorted ascending. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If no rate history reader is configured. |
LendingRateHistoryUnavailableError
|
If data cannot be retrieved. |
funding_rate_history
¶
funding_rate_history(
venue: str, market_symbol: str, hours: int = 168
) -> DataEnvelope[list[FundingRateSnapshot]]
Get historical funding rate snapshots for backtesting.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
venue
|
str
|
Perps venue (e.g. "hyperliquid"). |
required |
market_symbol
|
str
|
Market symbol (e.g. "ETH-USD"). |
required |
hours
|
int
|
Number of hours of history. Default 168 (7 days). |
168
|
Returns:
| Type | Description |
|---|---|
DataEnvelope[list[FundingRateSnapshot]]
|
DataEnvelope[list[FundingRateSnapshot]] sorted ascending. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If no rate history reader is configured. |
FundingRateHistoryUnavailableError
|
If data cannot be retrieved. |
il_exposure
¶
il_exposure(
position_id: str,
fees_earned: Decimal = Decimal("0"),
*,
default: _DefaultT = _NO_DEFAULT,
) -> ILExposure | _DefaultT
Get impermanent loss exposure for a tracked LP position.
Requires an ILCalculator with the position already registered via
add_position().
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
position_id
|
str
|
Unique identifier for the LP position. |
required |
fees_earned
|
Decimal
|
Optional fees earned (for net PnL). |
Decimal('0')
|
default
|
_DefaultT
|
Soft-fallback value (VIB-5153 / ALM-2814). When supplied
— including |
_NO_DEFAULT
|
Returns:
| Type | Description |
|---|---|
ILExposure | _DefaultT
|
ILExposure with current IL metrics, or |
ILExposure | _DefaultT
|
the exposure cannot be computed. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If no IL calculator is configured (regardless of
|
ILExposureUnavailableError
|
If exposure cannot be calculated and no
|
projected_il
¶
projected_il(
token_a: str,
token_b: str,
price_change_pct: Decimal,
weight_a: Decimal = Decimal("0.5"),
weight_b: Decimal = Decimal("0.5"),
) -> ProjectedILResult
Project impermanent loss for a hypothetical price change.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token_a
|
str
|
Symbol of token A (the volatile token). |
required |
token_b
|
str
|
Symbol of token B (often a stablecoin). |
required |
price_change_pct
|
Decimal
|
Price change percentage (50 for +50%). |
required |
weight_a
|
Decimal
|
Weight of token A in the pool (default 0.5). |
Decimal('0.5')
|
weight_b
|
Decimal
|
Weight of token B in the pool (default 0.5). |
Decimal('0.5')
|
Returns:
| Type | Description |
|---|---|
ProjectedILResult
|
ProjectedILResult with il_ratio / il_percent / il_bps. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If no IL calculator is configured or invalid args. |
realized_vol
¶
realized_vol(
token: str,
window_days: int = 30,
timeframe: OHLCVTimeframe = OHLCVTimeframe.ONE_HOUR,
estimator: str = "close_to_close",
*,
ohlcv_limit: int | None = None,
) -> DataEnvelope[VolatilityResult]
Calculate realized volatility for a token.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token
|
str
|
Token symbol. |
required |
window_days
|
int
|
Lookback window in calendar days. Default 30. |
30
|
timeframe
|
OHLCVTimeframe
|
Candle timeframe (1m, 5m, 15m, 1h, 4h, 1d). Default "1h". |
ONE_HOUR
|
estimator
|
str
|
"close_to_close" (default) or "parkinson". |
'close_to_close'
|
ohlcv_limit
|
int | None
|
Override for number of candles to fetch. |
None
|
Returns:
| Type | Description |
|---|---|
DataEnvelope[VolatilityResult]
|
DataEnvelope[VolatilityResult] with INFORMATIONAL classification. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If no volatility calculator is configured. |
VolatilityUnavailableError
|
If volatility cannot be calculated. |
vol_cone
¶
vol_cone(
token: str,
windows: list[int] | None = None,
timeframe: OHLCVTimeframe = OHLCVTimeframe.ONE_HOUR,
estimator: str = "close_to_close",
*,
ohlcv_limit: int | None = None,
) -> DataEnvelope[VolConeResult]
Compute volatility cone: current vol vs historical percentile.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token
|
str
|
Token symbol. |
required |
windows
|
list[int] | None
|
Lookback windows in days. Default [7, 14, 30, 90]. |
None
|
timeframe
|
OHLCVTimeframe
|
Candle timeframe. Default "1h". |
ONE_HOUR
|
estimator
|
str
|
"close_to_close" or "parkinson". |
'close_to_close'
|
ohlcv_limit
|
int | None
|
Override for number of candles to fetch. |
None
|
Returns:
| Type | Description |
|---|---|
DataEnvelope[VolConeResult]
|
DataEnvelope[VolConeResult] with INFORMATIONAL classification. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If no volatility calculator is configured. |
VolConeUnavailableError
|
If vol cone cannot be calculated. |
portfolio_risk
¶
portfolio_risk(
pnl_series: list[float],
total_value_usd: Decimal | None = None,
return_interval: str = "1d",
risk_free_rate: Decimal = Decimal("0"),
var_method: str = "parametric",
timestamps: list[datetime] | None = None,
benchmark_eth_returns: list[float] | None = None,
benchmark_btc_returns: list[float] | None = None,
) -> DataEnvelope[PortfolioRisk]
Calculate portfolio risk metrics from a PnL return series.
Computes Sharpe ratio, Sortino ratio, VaR, CVaR, and drawdown.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pnl_series
|
list[float]
|
Periodic returns as fractions (0.01 = 1% gain). |
required |
total_value_usd
|
Decimal | None
|
Current portfolio value in USD. |
None
|
return_interval
|
str
|
Periodicity of returns (1d, 1h, etc.). |
'1d'
|
risk_free_rate
|
Decimal
|
Risk-free rate per period as a decimal. |
Decimal('0')
|
var_method
|
str
|
"parametric" / "historical" / "cornish_fisher". |
'parametric'
|
timestamps
|
list[datetime] | None
|
Optional timestamps for each return. |
None
|
benchmark_eth_returns
|
list[float] | None
|
Optional ETH returns for beta. |
None
|
benchmark_btc_returns
|
list[float] | None
|
Optional BTC returns for beta. |
None
|
Returns:
| Type | Description |
|---|---|
DataEnvelope[PortfolioRisk]
|
DataEnvelope[PortfolioRisk] with INFORMATIONAL classification. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If no risk calculator is configured. |
PortfolioRiskUnavailableError
|
If risk metrics cannot be calculated. |
rolling_sharpe
¶
rolling_sharpe(
pnl_series: list[float],
window_days: int = 30,
return_interval: str = "1d",
risk_free_rate: Decimal = Decimal("0"),
timestamps: list[datetime] | None = None,
) -> DataEnvelope[RollingSharpeResult]
Compute rolling Sharpe ratio over a PnL series.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pnl_series
|
list[float]
|
Periodic returns as fractions. |
required |
window_days
|
int
|
Rolling window in days. Default 30. |
30
|
return_interval
|
str
|
Periodicity of returns. |
'1d'
|
risk_free_rate
|
Decimal
|
Risk-free rate per period. |
Decimal('0')
|
timestamps
|
list[datetime] | None
|
Optional timestamps aligned with pnl_series. |
None
|
Returns:
| Type | Description |
|---|---|
DataEnvelope[RollingSharpeResult]
|
DataEnvelope[RollingSharpeResult] with INFORMATIONAL classification. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If no risk calculator is configured. |
RollingSharpeUnavailableError
|
If rolling Sharpe cannot be computed. |
ohlcv
¶
ohlcv(
token: str | Instrument,
timeframe: OHLCVTimeframe = OHLCVTimeframe.ONE_HOUR,
limit: int = 100,
quote: str = "USD",
gap_strategy: GapStrategy = "nan",
*,
pool_address: str | None = None,
data_source: str | None = None,
) -> pd.DataFrame
Get OHLCV (candlestick) data for a token.
Routes through ohlcv_router (preferred) or legacy ohlcv_module.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token
|
str | Instrument
|
Token symbol, "BASE/QUOTE" string, or Instrument. |
required |
timeframe
|
OHLCVTimeframe
|
Candle timeframe (1m, 5m, 15m, 1h, 4h, 1d). Default "1h". |
ONE_HOUR
|
limit
|
int
|
Maximum number of candles. Default 100. |
100
|
quote
|
str
|
Quote currency. Default "USD". |
'USD'
|
gap_strategy
|
GapStrategy
|
"nan" / "ffill" / "drop". Default "nan". |
'nan'
|
pool_address
|
str | None
|
Explicit pool address for DEX providers. |
None
|
data_source
|
str | None
|
Explicit provider override (e.g. |
None
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
pandas DataFrame with columns timestamp, open, high, low, close, volume. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If no OHLCV module/router is configured, or
|
OHLCVUnavailableError
|
If OHLCV data cannot be retrieved. |
health
¶
Get a health report for all registered data providers.
Returns:
| Type | Description |
|---|---|
HealthReport
|
HealthReport with per-source health, cache stats, overall status. |
prediction
¶
Get prediction market data.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
market_id
|
str
|
Prediction market ID or URL slug. |
required |
Returns:
| Type | Description |
|---|---|
PredictionMarket
|
PredictionMarket with full market details. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If no prediction provider is configured. |
PredictionUnavailableError
|
If market data cannot be retrieved. |
prediction_positions
¶
Get all open prediction market positions.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
market_id
|
str | None
|
Optional market ID or slug to filter by. |
None
|
Returns:
| Type | Description |
|---|---|
list[PredictionPosition]
|
List of PredictionPosition objects. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If no prediction provider is configured. |
PredictionUnavailableError
|
If positions cannot be retrieved. |
prediction_orders
¶
Get all open prediction market orders.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
market_id
|
str | None
|
Optional market ID or slug to filter by. |
None
|
Returns:
| Type | Description |
|---|---|
list[PredictionOrder]
|
List of PredictionOrder objects. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If no prediction provider is configured. |
PredictionUnavailableError
|
If orders cannot be retrieved. |
lst_exchange_rate
¶
Get Solana LST exchange rate vs SOL.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
symbol
|
str
|
LST symbol (e.g. "jitoSOL", "mSOL", "bSOL", "INF"). |
required |
Returns:
| Type | Description |
|---|---|
LSTExchangeRate
|
LSTExchangeRate with rate vs SOL and APY data. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If no LST provider is configured. |
LSTDataUnavailableError
|
If data cannot be retrieved. |
lst_all_rates
¶
Get exchange rates for all tracked Solana LSTs.
Returns:
| Type | Description |
|---|---|
dict[str, LSTExchangeRate]
|
dict mapping symbol -> LSTExchangeRate. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If no LST provider is configured. |
LSTDataUnavailableError
|
If data cannot be retrieved. |
pt_position_health
¶
pt_position_health(
morpho_market_id: str,
principal_token_market_address: str | None = None,
rpc_url: str | None = None,
collateral_price_usd: Decimal | None = None,
debt_price_usd: Decimal | None = None,
*,
principal_token_protocol: str | None = None,
pendle_market_address: str | None = None,
) -> PTPositionHealth
Get extended health data for a PT-collateral position.
Combines Morpho Blue position data with principal-token market metrics (implied APY, maturity risk) for comprehensive risk assessment.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
morpho_market_id
|
str
|
Morpho Blue market ID. |
required |
principal_token_market_address
|
str | None
|
Principal-token market address for the PT collateral. |
None
|
rpc_url
|
str | None
|
RPC endpoint (uses gateway-routed path when None). |
None
|
collateral_price_usd
|
Decimal | None
|
Override for PT collateral price. |
None
|
debt_price_usd
|
Decimal | None
|
Override for debt token price. |
None
|
principal_token_protocol
|
str | None
|
Optional connector protocol key. When omitted, the sole registered principal-token reader is used for backward compatibility. |
None
|
pendle_market_address
|
str | None
|
Deprecated alias for
|
None
|
Returns:
| Type | Description |
|---|---|
PTPositionHealth
|
PTPositionHealth with Morpho + principal-token risk metrics. |
Raises:
| Type | Description |
|---|---|
HealthUnavailableError
|
If health data cannot be retrieved or
if neither a connected |
seed_price
¶
Seed a scalar price into the snapshot's cache.
Goes through the same internal chokepoint as set_price so the
validation behavior is identical.
seed_price_data
¶
seed_price_data(
token: str,
data: PriceData,
*,
quote: str = "USD",
chain: str | None = None,
) -> None
Seed a full PriceData record (with source / timestamp / block).
chain is keyword-only and lets multi-chain tests / backtests seed
different PriceData for the same symbol on different chains —
matches the legacy multichain set_price_data shape.
seed_balance
¶
Seed a TokenBalance for a token.
seed_balance_usd_unmeasured
¶
Seed a token amount whose USD value was not measured.
Public seeding counterpart to provider results that supply a measured
token amount but no trustworthy USD mark. balance_usd remains the
Decimal("0") unmeasured sentinel until a caller supplies a price or
warms the price cache.
seed_rsi
¶
Seed an RSIData instance (timeframe-keyed).
MarketSnapshotBuilder
¶
Factory class — never instantiated. All entry points are classmethods.
for_strategy_runner
classmethod
¶
for_strategy_runner(
*,
strategy: Any,
runtime_context: Any | None = None,
gateway_client: Any | None = None,
chain: str | None = None,
wallet_address: str | None = None,
runtime_surface: str | None = None,
chains: tuple[str, ...] | list[str] | None = None,
multi_chain_price_oracle: Any | None = None,
multi_chain_balance_provider: Any | None = None,
aave_health_factor_provider: Any | None = None,
default_timeframe: OHLCVTimeframe | None = None,
) -> MarketSnapshot
Build a snapshot for the live / hosted runner.
Pulls providers off the strategy's wired services (price oracle,
balance provider, indicator provider, …) and wraps the gateway client
into the snapshot. Returns a snapshot with
runtime_surface="local_sdk" or "hosted" based on
runtime_context (the caller resolves which is which — the snapshot
does not read env vars).
For multi-chain strategies the caller passes chains= and the
multi_chain_* providers; the resulting snapshot has the same
canonical class as a single-chain build.
for_pnl_backtest_state
classmethod
¶
Build a snapshot wired to a PnL backtest engine's state.
The state object exposes price_oracle / balance_provider /
indicator_provider interfaces; the builder forwards them as-is.
VIB-4727: backtest factories inject NullPoolAnalyticsReader so
any market.pool_analytics(...) call raises DataSourceUnavailable
deterministically. Live gateway HTTP at backtest time would break
reproducibility — strategies must take a deterministic code path
(static fee assumption, fixture-backed analytics, or HOLD) inside
backtests.
VIB-4728 / POOL-7 (VIB-4755) extends the same pattern to pool
history: NullPoolHistoryReader is injected for the same
determinism reason. for_strategy_runner wires the live
PoolHistoryReader (VIB-4757); the Null reader here keeps
backtests deterministic regardless.
for_paper_fork
classmethod
¶
for_paper_fork(
*,
chain: str,
wallet_address: str,
fork_manager: Any,
gateway_client: Any | None = None,
) -> MarketSnapshot
Build a snapshot for paper-trading on an Anvil fork.
Strategies must NOT see fork_rpc_url directly — the fork-aware
market service adapters consume it internally. PRD §4.7.
for_http_backtest_spec
classmethod
¶
Build a snapshot for the HTTP-backtest service path.
seeded
classmethod
¶
seeded(
*,
chain: str = DEFAULT_CHAIN,
wallet_address: str = "0x" + "0" * 40,
prices: Mapping[str, Decimal] | None = None,
price_data: Mapping[str, PriceData] | None = None,
balances: Mapping[str, TokenBalance] | None = None,
indicators: Mapping[str, Any] | None = None,
timestamp: Any | None = None,
) -> MarketSnapshot
Build a snapshot pre-seeded for unit tests.
Goes through the public seed_* API on the snapshot — never writes
private cache attributes directly (PRD §5.6).
lookup_price
¶
lookup_price(
prices: PriceStore[Any] | Mapping[Any, Any] | None,
*,
token: str | TokenRef | None = None,
chain: str | None = None,
address: str | None = None,
symbol: str | None = None,
aliases: Iterable[str] = (),
quote: str = "USD",
peg: Decimal | None = None,
infer_symbol_from_address: bool = True,
) -> PriceLookupResult | None
Resolve one price with the SDK-wide deterministic precedence contract.
Precedence is:
- exact typed
(chain, address)identity; - legacy
chain:addresscompatibility key; - legacy bare address (accepted only with an explicit chain);
- explicit or safely inferred symbol aliases;
- an explicit caller-supplied peg.
Generic mappings are the USD-only compatibility surface. Non-USD quotes
therefore resolve only from :class:PriceStore, never by relabelling a USD
scalar under another quote.
Overview¶
almanak.framework.market is the canonical home for MarketSnapshot — the
strategy-facing market-data interface. It replaces the two legacy locations
(almanak.framework.strategies.intent_strategy.MarketSnapshot and
almanak.framework.data.market_snapshot.MarketSnapshot) that silently
diverged before VIB-4062.
The snapshot clock¶
MarketSnapshot.timestamp is the strategy's only legitimate clock. It is
a tz-aware datetime: real time on the live surface, simulated time in
backtests. Any time arithmetic that feeds a decision — cooldowns, cadences,
daily counters — must be measured against it; datetime.now() inside
decide() or on_intent_executed() mixes clocks and silently breaks every
backtest (a 24h wall-clock cooldown never expires when weeks of simulated
time replay in minutes).
def decide(self, market: MarketSnapshot) -> Intent:
now = market.timestamp
if self._last_trade_ts and now - self._last_trade_ts < self._cooldown:
return Intent.hold(reason="cooldown")
...
on_intent_executed() has no snapshot parameter — capture market.timestamp
in decide() and reuse the captured value when stamping fill state there.
See Time in Strategies for the
full pattern.
Builder factories¶
almanak.framework.market.builders.MarketSnapshotBuilder
¶
Factory class — never instantiated. All entry points are classmethods.
for_strategy_runner
classmethod
¶
for_strategy_runner(
*,
strategy: Any,
runtime_context: Any | None = None,
gateway_client: Any | None = None,
chain: str | None = None,
wallet_address: str | None = None,
runtime_surface: str | None = None,
chains: tuple[str, ...] | list[str] | None = None,
multi_chain_price_oracle: Any | None = None,
multi_chain_balance_provider: Any | None = None,
aave_health_factor_provider: Any | None = None,
default_timeframe: OHLCVTimeframe | None = None,
) -> MarketSnapshot
Build a snapshot for the live / hosted runner.
Pulls providers off the strategy's wired services (price oracle,
balance provider, indicator provider, …) and wraps the gateway client
into the snapshot. Returns a snapshot with
runtime_surface="local_sdk" or "hosted" based on
runtime_context (the caller resolves which is which — the snapshot
does not read env vars).
For multi-chain strategies the caller passes chains= and the
multi_chain_* providers; the resulting snapshot has the same
canonical class as a single-chain build.
for_pnl_backtest_state
classmethod
¶
Build a snapshot wired to a PnL backtest engine's state.
The state object exposes price_oracle / balance_provider /
indicator_provider interfaces; the builder forwards them as-is.
VIB-4727: backtest factories inject NullPoolAnalyticsReader so
any market.pool_analytics(...) call raises DataSourceUnavailable
deterministically. Live gateway HTTP at backtest time would break
reproducibility — strategies must take a deterministic code path
(static fee assumption, fixture-backed analytics, or HOLD) inside
backtests.
VIB-4728 / POOL-7 (VIB-4755) extends the same pattern to pool
history: NullPoolHistoryReader is injected for the same
determinism reason. for_strategy_runner wires the live
PoolHistoryReader (VIB-4757); the Null reader here keeps
backtests deterministic regardless.
for_paper_fork
classmethod
¶
for_paper_fork(
*,
chain: str,
wallet_address: str,
fork_manager: Any,
gateway_client: Any | None = None,
) -> MarketSnapshot
Build a snapshot for paper-trading on an Anvil fork.
Strategies must NOT see fork_rpc_url directly — the fork-aware
market service adapters consume it internally. PRD §4.7.
for_http_backtest_spec
classmethod
¶
Build a snapshot for the HTTP-backtest service path.
seeded
classmethod
¶
seeded(
*,
chain: str = DEFAULT_CHAIN,
wallet_address: str = "0x" + "0" * 40,
prices: Mapping[str, Decimal] | None = None,
price_data: Mapping[str, PriceData] | None = None,
balances: Mapping[str, TokenBalance] | None = None,
indicators: Mapping[str, Any] | None = None,
timestamp: Any | None = None,
) -> MarketSnapshot
Build a snapshot pre-seeded for unit tests.
Goes through the public seed_* API on the snapshot — never writes
private cache attributes directly (PRD §5.6).
Typed errors¶
almanak.framework.market.errors
¶
Typed errors for VIB-4062 MarketSnapshot.
Every error inherits from MarketSnapshotError and carries
source/chain/instrument/severity/retryability metadata so the runner can
classify the failure without parsing exception messages (PRD §4.4).
The snapshot always raises typed errors on missing/stale/invalid data and records the failure in its critical-data-failure register before the exception bubbles up. The runner/runtime decides halt-vs-continue. The snapshot is mode-agnostic.
MarketSnapshotError
¶
Bases: Exception
Base class for typed snapshot errors.
Subclasses carry structured fields. Stringifying gives a stable shape
for log lines: "<ClassName>(<key>=<value>, …): <reason>".
For backward compat with the legacy data-layer error idiom, positional
arguments are accepted as (chain, reason) or just (reason,);
callers using kwargs (the canonical post-VIB-4062 form) continue to work.
ChainNotConfiguredError
¶
Bases: MarketSnapshotError
A specific chain= argument was supplied but is not in chains.
Raised in BOTH single-chain (mismatch with the only configured chain) and multi-chain (chain not in the configured set) cases. The runner has no recovery path — strategy code must update its chain handling.
AmbiguousChainError
¶
Bases: MarketSnapshotError
A multi-chain snapshot was queried with chain=None.
Raised when len(self.chains) > 1 and no explicit chain= was given.
Multi-chain callers must be explicit; default-to-primary policy hides
chain-routing bugs (PRD §4.2 R2, Codex finding HIGH).
StaleDataError
¶
IndicatorUnavailableError
¶
LendingMarketResolutionError
¶
Bases: MarketSnapshotError
Verified lending-market resolution failed (VIB-5985).
Raised by MarketSnapshot.lending_markets / lending_market for a
transport failure, an unsupported protocol/chain, a not-found market, or —
critically — an on-chain verification MISMATCH. Not retryable: a mismatch or
unsupported protocol is a permanent contract error the caller must resolve,
not transiently retry. Zero candidates from lending_markets is NOT an
error (an empty list is returned); this error is for the fail-closed cases.
CriticalDataFailureRecord
dataclass
¶
CriticalDataFailureRecord(
error_type: str,
severity: str,
retryable: bool,
chain: str | None,
instrument: str | None,
reason: str,
)
One entry in MarketSnapshot's critical-data-failure register.
Written from inside the snapshot's _record_critical_failure chokepoint;
read from outside via the public has_critical_data_failures /
critical_data_failure_count / classify_critical_data_failures /
summarize_critical_data_failures accessors.
HOLD contract for data-unavailable errors¶
Some MarketSnapshot accessors call out to off-chain services through
the gateway (e.g. pool_analytics(...), which routes to
PoolAnalyticsService over gRPC). When the gateway is unreachable, the
strategy container has no fallback, so the accessor raises a typed
error such as PoolAnalyticsUnavailableError whose __cause__ chain
preserves the underlying DataSourceUnavailable.
The runner's classify_failure walks __cause__ to depth 8 and treats
DataSourceUnavailable as DATA_UNAVAILABLE, which the iteration
loop interprets as HOLD-worthy. Strategy authors must either let
these errors propagate, or catch them and explicitly return
Intent.hold(...). A bare except (swallowing the typed error
without re-raising or returning HOLD) breaks the runner's HOLD
inference and the strategy will appear to "succeed with no signal"
while losing the safety contract.
def decide(self, market: MarketSnapshot) -> Intent:
try:
analytics = market.pool_analytics(pool_address, protocol="uniswap_v3")
except PoolAnalyticsUnavailableError:
# Correct: surface as HOLD so the runner's data-unavailable path fires.
return Intent.hold(reason="pool analytics unavailable")
# ... use analytics
The same HOLD contract applies to market.pool_history(...) — backed by
PoolHistoryReader over the gateway-side PoolHistoryService (VIB-4728).
In a backtest the injected NullPoolHistoryReader raises
DataSourceUnavailable("backtest") on every call; in a live run a gateway
outage or "pool not found across all providers" surfaces as the same
typed error. The same catch-and-HOLD or let-it-propagate discipline
applies:
def decide(self, market: MarketSnapshot) -> Intent:
try:
history = market.pool_history(
pool_address,
chain=self.chain,
start_date=self.lookback_start,
resolution="1h",
protocol="uniswap_v3",
)
except DataSourceUnavailable:
# Correct: HOLD on backtest or gateway-down; do NOT default to zeros.
return Intent.hold(reason="pool history unavailable")
# ... use history.value (list[PoolSnapshot])
Return-type DTOs¶
almanak.framework.market.models
¶
Typed return models for VIB-4062 MarketSnapshot.
Public surface for TokenBalance, PriceData, RSIData, MACDData,
all *Data indicator types, and config dataclasses.
This module is intentionally lean (PRD §4.8) — only stdlib imports at
module level. The legacy locations
almanak.framework.strategies.strategy_models and
almanak.framework.strategies.indicator_models re-export from this module
during the VIB-4062 transition; commit 6 deletes those re-export shims.
If you are adding a new typed return model for MarketSnapshot, put it
HERE — not in strategies/ or data/. The AST uniqueness gate (PRD §5.1)
enforces this.
TokenBalance
dataclass
¶
Balance information for a single token.
Comparisons delegate to the balance field (native units).
PriceData
dataclass
¶
PriceData(
price: Decimal,
price_24h_ago: Decimal = Decimal("0"),
change_24h_pct: Decimal = Decimal("0"),
high_24h: Decimal = Decimal("0"),
low_24h: Decimal = Decimal("0"),
timestamp: datetime = (lambda: datetime.now(UTC))(),
source: str = "",
confidence: str = "HIGH",
)
Price data for a token.
Includes source (the named provider that produced the datum) so
accounting writers can stamp transaction_ledger.price_inputs_json with
the real provider name (VIB-3889).
ReferenceMarketStatus
¶
Bases: StrEnum
Session state originated by the gateway's reference-market calendar.
ReferencePriceData
dataclass
¶
ReferencePriceData(
instrument: str,
quote: str,
chain: str,
price: Decimal | None,
confidence: float | None,
source: str,
observed_at: datetime | None,
stale: bool,
market_status: ReferenceMarketStatus,
market_status_as_of: datetime | None,
market_status_source: str,
reason: str = "",
)
Exact-provider reference price with freshness and session provenance.
trade_block_reason
¶
trade_block_reason(
*,
max_age_seconds: int,
min_confidence: float = 0.0,
now: datetime | None = None,
) -> str | None
Return None only when this observation is safe for a new trade.
is_tradeable
¶
is_tradeable(
*,
max_age_seconds: int,
min_confidence: float = 0.0,
now: datetime | None = None,
) -> bool
Whether every fail-closed reference-price gate passes.
PtPriceData
dataclass
¶
PtPriceData(
symbol: str,
chain: str,
price: Decimal | None,
confidence: ValueConfidence,
underlying_price: Decimal | None = None,
pt_to_asset_rate: Decimal | None = None,
days_to_maturity: int | None = None,
maturity_ts: int | None = None,
source: str = "",
stale: bool = False,
raw_confidence: float | None = None,
timestamp: datetime | None = None,
)
Typed, strategy-safe result of MarketSnapshot.pt_price (VIB-5311).
A pure pass-through of the gateway PT/YT-USD price contract
(PtPriceResponse, VIB-5309/5310) — the gateway is the single price
authority that composes price = pt_to_sy_rate × underlying/USD (VIB-5407
— the discounted market mark, not the accounting-asset rate) and
originates confidence + staleness. This object carries that one number to
every consumer (valuation, dashboard, Accountant Test) so two tiles can
never disagree (design spine §0/§1).
Empty ≠ Zero (spine §3): an unmeasured price is price=None +
confidence=UNAVAILABLE — NEVER Decimal("0"). The composition legs
(underlying_price, pt_to_asset_rate) are None when the gateway
did not emit them, never Decimal("0").
confidence combines the gateway's coarse band with its stale flag
into the framework ValueConfidence vocabulary (the band alone lacks
STALE — see PtPriceConfidenceBand). Confidence only ever degrades
downstream; a consumer must not upgrade it (spine §3.4).
is_available
property
¶
True when a usable price was measured (price present + not UNAVAILABLE).
is_stale
property
¶
True when the mark is stale — for a strategy to gate on before trusting it (VIB-5312).
Combines BOTH staleness signals so a caller never has to (and never
silently drops one): the gateway's raw stale flag OR a
confidence == STALE that the band+stale combination already produced
(MarketSnapshot.pt_price). They normally agree; OR-ing them is
fail-safe — a mark flagged stale by either signal is treated as stale.
Note an AVAILABLE price can still be stale (the mark exists but is old);
is_available and not is_stale is the "fresh, trustable mark" gate.
LendingMarketInfo
dataclass
¶
LendingMarketInfo(
protocol: str,
chain: str,
market_id: str,
kind: str,
collateral_token: str,
collateral_symbol: str,
loan_token: str,
loan_symbol: str,
lltv_bps: int,
oracle: str,
irm: str,
verified: bool,
source: str,
)
Typed result of MarketSnapshot.lending_markets / lending_market (VIB-5985).
A pure pass-through of the gateway LendingMarket contract. Immutable
market params only (tokens, oracle, IRM, LLTV) — live supply/borrow state is
out of scope for this surface. This exists to VERIFY / re-check a configured
market_id at build or boot time, NOT to pick a market per iteration:
lending_markets returns catalog CANDIDATES (verified=False) and
lending_market returns one on-chain-VERIFIED market (verified=True).
Empty ≠ Zero: an unresolvable symbol/address field is "" (the gateway
never fabricates it), never a placeholder. verified is True ONLY
after the gateway read idToMarketParams and the recomputed id matched.
Attributes:
| Name | Type | Description |
|---|---|---|
kind |
str
|
Market topology — |
source |
str
|
Provenance — |
lltv_percent
property
¶
Liquidation LTV as a percentage (9150 bps → Decimal('91.5')).
RSIData
dataclass
¶
RSIData(
value: Decimal,
period: int = 14,
overbought: Decimal = Decimal("70"),
oversold: Decimal = Decimal("30"),
)
RSI (Relative Strength Index) data for a token.
MACDData
dataclass
¶
MACDData(
macd_line: Decimal,
signal_line: Decimal,
histogram: Decimal,
fast_period: int = 12,
slow_period: int = 26,
signal_period: int = 9,
)
MACD data for a token.
BollingerBandsData
dataclass
¶
BollingerBandsData(
upper_band: Decimal,
middle_band: Decimal,
lower_band: Decimal,
bandwidth: Decimal = Decimal("0"),
percent_b: Decimal = Decimal("0.5"),
period: int = 20,
std_dev: float = 2.0,
)
Bollinger Bands data for a token.
StochasticData
dataclass
¶
StochasticData(
k_value: Decimal,
d_value: Decimal,
k_period: int = 14,
d_period: int = 3,
overbought: Decimal = Decimal("80"),
oversold: Decimal = Decimal("20"),
)
Stochastic Oscillator data for a token.
ATRData
dataclass
¶
ATRData(
value: Decimal,
value_percent: Decimal = Decimal("0"),
period: int = 14,
volatility_threshold: Decimal = Decimal("5.0"),
)
ATR (Average True Range) data for a token.
MAData
dataclass
¶
MAData(
value: Decimal,
ma_type: str = "SMA",
period: int = 20,
current_price: Decimal = Decimal("0"),
)
Moving Average data for a token.
ADXData
dataclass
¶
ADXData(
adx: Decimal,
plus_di: Decimal,
minus_di: Decimal,
period: int = 14,
trend_threshold: Decimal = Decimal("25"),
)
ADX (Average Directional Index) data for a token.
OBVData
dataclass
¶
OBV (On-Balance Volume) data for a token.
CCIData
dataclass
¶
CCIData(
value: Decimal,
period: int = 20,
upper_level: Decimal = Decimal("100"),
lower_level: Decimal = Decimal("-100"),
)
CCI (Commodity Channel Index) data for a token.
IchimokuData
dataclass
¶
IchimokuData(
tenkan_sen: Decimal,
kijun_sen: Decimal,
senkou_span_a: Decimal,
senkou_span_b: Decimal,
current_price: Decimal = Decimal("0"),
tenkan_period: int = 9,
kijun_period: int = 26,
senkou_b_period: int = 52,
)
Ichimoku Cloud data for a token.
IndicatorProvider
¶
IndicatorProvider(
macd: Callable[..., MACDData] | None = None,
bollinger: Callable[..., BollingerBandsData]
| None = None,
stochastic: Callable[..., StochasticData] | None = None,
atr: Callable[..., ATRData] | None = None,
sma: Callable[..., MAData] | None = None,
ema: Callable[..., MAData] | None = None,
adx: Callable[..., ADXData] | None = None,
obv: Callable[..., OBVData] | None = None,
cci: Callable[..., CCIData] | None = None,
ichimoku: Callable[..., IchimokuData] | None = None,
)
Provider that wraps all indicator calculators for synchronous access.
Each attribute corresponds to a MarketSnapshot accessor. The runner creates this once and injects it into the strategy, so all indicators work out of the box without strategy authors managing calculators.
Provider Protocols (sync adapters)¶
almanak.framework.market.services
¶
Provider Protocol layer for VIB-4062 MarketSnapshot.
PRD §4.3 — every builder factory normalizes async data-layer providers into
the canonical sync service Protocols at the builder boundary.
MarketSnapshot itself never sees an async provider, never decides whether
to await or not, never re-implements the sync bridge.
Production sync adapters wrap the async data-layer providers (under
framework/data); the wrap centralizes the sync bridge logic in
framework/market/sync_bridge.py.