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 = "",
)
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).
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).
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: str | 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
|
str | 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: |
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
|
str | 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 |
|---|---|
NotImplementedError
|
If multi-DEX service is not configured |
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 |
|---|---|
NotImplementedError
|
If multi-DEX service is not configured |
macd
¶
macd(
token: str,
fast_period: int = 12,
slow_period: int = 26,
signal_period: int = 9,
timeframe: str | 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
|
str | 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: str | 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
|
str | 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: str | 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
|
str | 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
|
str | 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
|
str | 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
|
str | 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
|
str | 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
|
str | 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
|
str | 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: str | 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
|
str | 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.
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).
_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.
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
|
str | 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
|
str | 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: str | 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
|
str | 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
¶
Pre-populate Stochastic data for a token.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token
|
str
|
Token symbol |
required |
stoch_data
|
StochasticData
|
StochasticData instance |
required |
timeframe
|
str | 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
|
str | 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: str | 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
|
str | 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
|
str | 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
|
str | 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
|
str | None
|
Optional timeframe (None matches any query) |
None
|
Example
market.set_cci("WETH", CCIData( value=Decimal("-120"), ))
set_ichimoku
¶
Pre-populate Ichimoku data for a token.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token
|
str
|
Token symbol |
required |
ichimoku_data
|
IchimokuData
|
IchimokuData instance |
required |
timeframe
|
str | 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_rate
¶
lending_rate(
protocol: str,
token: str,
side: str = "supply",
*,
chain: 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
|
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
¶
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 |
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,
) -> 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.
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 (Morpho cross-asset markets require this). |
None
|
debt_price_usd
|
Decimal | None
|
Optional override for debt-token price (same). |
None
|
Returns:
| Type | Description |
|---|---|
Any
|
PositionHealth with .health_factor (Decimal), .collateral_value_usd, |
Any
|
.debt_value_usd, .max_borrow_usd, .protocol, .market_id. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If health data cannot be retrieved. |
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 |
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
|
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 |
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 |
funding_rate_spread
¶
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 |
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
¶
Get all prices as a dict suitable for IntentCompiler.
Combines pre-populated prices and cached prices from oracle calls. Keys are normalized to uppercase to match Token.symbol (which is always uppercased by Token.post_init). This prevents case-mismatch lookup failures for mixed-case tokens like cbETH, wstETH, crvUSD, sUSDe, etc.
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.
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, sqrtPrice, etc. |
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.
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,
) -> 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
|
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. |
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.
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
|
If no yield aggregator is configured. |
YieldOpportunitiesUnavailableError
|
If data cannot be retrieved. |
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
¶
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')
|
Returns:
| Type | Description |
|---|---|
ILExposure
|
ILExposure with current IL metrics. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If no IL calculator is configured. |
ILExposureUnavailableError
|
If exposure cannot be calculated. |
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: str = "1h",
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
|
str
|
Candle timeframe (1m, 5m, 15m, 1h, 4h, 1d). Default "1h". |
'1h'
|
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: str = "1h",
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
|
str
|
Candle timeframe. Default "1h". |
'1h'
|
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: str = "1h",
limit: int = 100,
quote: str = "USD",
gap_strategy: GapStrategy = "nan",
*,
pool_address: 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
|
str
|
Candle timeframe (1m, 5m, 15m, 1h, 4h, 1d). Default "1h". |
'1h'
|
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
|
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. |
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_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: str | 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 does NOT auto-construct
the live PoolHistoryReader (per VIB-4755 D-4: the cut-over is
gated on VIB-4730 hosted-egress + VIB-4863 TheGraph API key landing).
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).
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.
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: str | 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 does NOT auto-construct
the live PoolHistoryReader (per VIB-4755 D-4: the cut-over is
gated on VIB-4730 hosted-egress + VIB-4863 TheGraph API key landing).
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
¶
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 = "",
)
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).
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.