> ## Documentation Index
> Fetch the complete documentation index at: https://tetrafi.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Pricing Unlisted Pairs

> Derive a cross rate for pairs the stream doesn't quote directly, by bridging two legs through a shared token.

The stream won't quote every combination directly. Want WETH/WBTC when only WETH/USDC and WBTC/USDC exist? Bridge the two: derive a **synthetic cross rate** through the token both pairs share.

That derived number is a pre-trade filter - a cheap way to decide whether the route deserves a firm RFQ quote at all.

<Info>
  **Typical scenario:** a WETH/WBTC swap lands on your desk, and no direct pair exists on the stream - but both legs against USDC do. Deriving the cross rate from streamed depth tells you whether the route is competitive *before* you spend a firm quote on it.
</Info>

## Two Legs, One Rate

To price a WBTC purchase paid in WETH when only the USDC pairs stream, split the trade conceptually:

1. **Leg one: WETH out** - your WETH sells into the WETH/USDC bids
2. **Leg two: WBTC in** - the resulting USDC buys from the WBTC/USDC asks

Dividing the two per-leg [VWAP estimates](/price-api/guides/vwap-estimation) yields the effective WETH/WBTC rate. Per-leg VWAP - not top-of-book - matters here because the two books rarely carry the same depth shape at your size.

### The Recipe

<Steps>
  <Step title="Map what's streaming">
    Index every pair in the snapshot, then intersect: which quote tokens do both of your base tokens trade against? (`find_common_quotes` below.)
  </Step>

  <Step title="Choose your bridge">
    Several candidates? Take the one with real depth on *both* sides - in practice that's usually a major stablecoin, but let the data decide.
  </Step>

  <Step title="Price each leg on its own">
    Run the [VWAP walk](/price-api/guides/vwap-estimation) per leg: bids for the leg where you're selling, asks where you're buying.
  </Step>

  <Step title="Divide">
    Leg-one VWAP over leg-two VWAP - that quotient is your size-aware cross rate.
  </Step>
</Steps>

## Discovering Bridge Tokens

Every pair on the network rides the same stream, so bridge discovery is a set intersection over the snapshot you already have.

Don't bake assumptions in: which tokens serve as common quotes differs chain to chain. Stablecoins show up often, but the snapshot itself is the only reliable answer.

```python theme={null} theme={null}
def find_common_quotes(
    pairs: dict[tuple[str, str], dict],
    base_a: str,
    base_b: str,
) -> list[str]:
    """
    Intersect the quote sides of two base tokens.

    pairs  -- snapshot index keyed by (base_addr, quote_addr).
    base_a -- first base token address.
    base_b -- second base token address.

    Yields every quote token address the two bases both trade against -
    each one is a candidate bridge.
    """
    quotes_a = {q for (b, q) in pairs if b == base_a}
    quotes_b = {q for (b, q) in pairs if b == base_b}
    return list(quotes_a & quotes_b)
```

## Deriving the Cross Rate

With a bridge chosen, price both legs and take the ratio:

```python theme={null} theme={null}
def estimate_synthetic_price(
    leg1_levels: list[tuple[float, float]],
    leg2_levels: list[tuple[float, float]],
    target_notional: float,
    direction: str,  # "buy" or "sell" (relative to the synthetic pair)
) -> tuple[float, float, float]:
    """
    Bridge two VWAP legs into one cross rate.

    Buying base_b with base_a (say, WBTC paid in WETH):
      leg 1 sells base_a into its bids; leg 2 buys base_b from its asks.
    Selling base_b for base_a: the same shape with the roles swapped.

    target_notional is expressed in bridge-token terms (e.g. USDC).
    Returns (cross_rate, leg1_vwap, leg2_vwap); zeros mean a leg had no depth.
    """
    if direction == "buy":
        # leg 1: base_a -> bridge, into the bids
        leg1_vwap, leg1_unfilled = estimate_vwap(leg1_levels, target_notional, "sell")
        # leg 2: bridge -> base_b, from the asks
        leg2_vwap, leg2_unfilled = estimate_vwap(leg2_levels, target_notional, "buy")
    else:
        # leg 1: base_b -> bridge, into the bids
        leg1_vwap, leg1_unfilled = estimate_vwap(leg1_levels, target_notional, "sell")
        # leg 2: bridge -> base_a, from the asks
        leg2_vwap, leg2_unfilled = estimate_vwap(leg2_levels, target_notional, "buy")

    if leg1_vwap == 0 or leg2_vwap == 0:
        return 0.0, 0.0, 0.0

    synthetic_price = leg1_vwap / leg2_vwap
    return synthetic_price, leg1_vwap, leg2_vwap
```

## On the Live Stream

Plugged into the WebSocket connection from the [Quickstart](/price-api/quickstart), the whole pipeline re-derives the cross rate on every snapshot:

```python theme={null} theme={null}
import asyncio

import httpx
import websockets

from tetrafi_pb2 import TetraFiPricingUpdate  # type: ignore

BASE_A = "WETH"  # what you're paying with
BASE_B = "WBTC"  # what you're after
TARGET_NOTIONAL = 250_000  # price a $250k clip

NETWORK = "ethereum"
CHAIN_ID = 1
API_KEY = "tfk_live_..."

WSS_URL = (
    f"wss://api.tetrafi.io/api/v1/ws"
    f"?token={API_KEY}"
    f"&format=protobuf"
    f"&network={NETWORK}"
)

def address_to_hex(b: bytes) -> str:
    return "0x" + b.hex()

def to_levels(flat: list[float]) -> list[tuple[float, float]]:
    it = iter(flat)
    return list(zip(it, it, strict=True))

def estimate_vwap(
    levels: list[tuple[float, float]], target_notional: float, intent: str
) -> tuple[float, float]:
    sorted_levels = sorted(levels, key=lambda lv: lv[0], reverse=(intent == "sell"))

    remaining = target_notional
    total_base = 0.0
    total_quote = 0.0

    for price, size in sorted_levels:
        if remaining <= 0:
            break

        level_notional = price * size
        fill_notional = min(level_notional, remaining)
        fill_base = fill_notional / price

        total_quote += fill_notional
        total_base += fill_base
        remaining -= fill_notional

    if total_base == 0:
        return 0.0, target_notional

    return total_quote / total_base, remaining

def find_common_quotes(
    pairs: dict[tuple[str, str], dict], base_a: str, base_b: str
) -> list[str]:
    quotes_a = {q for (b, q) in pairs if b == base_a}
    quotes_b = {q for (b, q) in pairs if b == base_b}
    return list(quotes_a & quotes_b)

def estimate_synthetic_price(
    leg1_levels: list[tuple[float, float]],
    leg2_levels: list[tuple[float, float]],
    target_notional: float,
    direction: str,
) -> tuple[float, float, float]:
    if direction == "buy":
        leg1_vwap, _ = estimate_vwap(leg1_levels, target_notional, "sell")
        leg2_vwap, _ = estimate_vwap(leg2_levels, target_notional, "buy")
    else:
        leg1_vwap, _ = estimate_vwap(leg1_levels, target_notional, "sell")
        leg2_vwap, _ = estimate_vwap(leg2_levels, target_notional, "buy")

    if leg1_vwap == 0 or leg2_vwap == 0:
        return 0.0, 0.0, 0.0

    return leg1_vwap / leg2_vwap, leg1_vwap, leg2_vwap

# Symbol -> address resolution via the tradable-pair list
resp = httpx.get("https://api.tetrafi.io/api/v1/pairs",
                 headers={"X-API-Key": API_KEY}, timeout=10.0)
tokens = {}
for p in resp.json()["pairs"]:
    for side in (p["input"], p["output"]):
        if side["chainId"] == CHAIN_ID:
            tokens[side["symbol"]] = side
addr_a = tokens[BASE_A]["address"].lower()
addr_b = tokens[BASE_B]["address"].lower()

async def main():
    async with websockets.connect(
        WSS_URL,
        ping_interval=20,
        ping_timeout=10,
        max_size=2**21,
    ) as ws:
        print(f"Streaming - deriving {BASE_A}/{BASE_B} through a bridge token\n")

        async for blob in ws:
            update = TetraFiPricingUpdate()
            update.ParseFromString(blob)

            # Index this snapshot's pairs
            pair_map: dict[tuple[str, str], dict] = {}
            for pair in update.pairs:
                base_hex = address_to_hex(pair.base).lower()
                quote_hex = address_to_hex(pair.quote).lower()
                pair_map[(base_hex, quote_hex)] = {
                    "bids": to_levels(list(pair.bids)),
                    "asks": to_levels(list(pair.asks)),
                }

            # Which bridges are available right now?
            common = find_common_quotes(pair_map, addr_a, addr_b)
            if not common:
                continue

            quote_addr = common[0]  # simplest choice; prefer deepest in production
            leg1 = pair_map.get((addr_a, quote_addr))
            leg2 = pair_map.get((addr_b, quote_addr))

            if not leg1 or not leg2:
                continue

            synthetic, vwap_a, vwap_b = estimate_synthetic_price(
                leg1["bids"], leg2["asks"], TARGET_NOTIONAL, "buy"
            )

            if synthetic > 0:
                print(
                    f"  {BASE_A}/{BASE_B} derived (via bridge):  {synthetic:.6f}\n"
                    f"    Leg 1 ({BASE_A}/bridge) VWAP:  {vwap_a:.2f}\n"
                    f"    Leg 2 ({BASE_B}/bridge) VWAP:  {vwap_b:.2f}\n"
                )
asyncio.run(main())
```

A \$250,000 WETH/WBTC derivation prints along these lines:

```json theme={null} theme={null}
{
  "pair": "WETH/WBTC",
  "target_notional": 250000,
  "synthetic_price": 0.032551,
  "leg1": {
    "pair": "WETH/bridge",
    "vwap": 2411.63
  },
  "leg2": {
    "pair": "WBTC/bridge",
    "vwap": 74088.91
  }
}
```

<Warning>
  A derived cross rate inherits the uncertainty of **both** legs - it's an indicative screen, and the firm quote that follows can differ. Never treat it as executable.
</Warning>

## Watch-outs

* **Per-leg VWAP or nothing.** The two books almost never share a depth profile, so top-of-book ratios mislead exactly when size matters - blend each leg at your true notional.
* **A thin leg poisons the whole number.** Any unfilled remainder on either side makes the cross rate unreliable at that size; treat it as no-quote.
* **Bridge quality is depth on both sides.** Given multiple shared tokens, the right bridge is the one that absorbs your size twice - once per leg.
* **Rediscover bridges from the data.** The set of shared quote tokens shifts by chain and by snapshot; recompute it rather than pinning yesterday's answer.
