How to fetch and interpret Pyth Network oracle data (price, confidence, EMA) in TypeScript?

23 hours ago 2
ARTICLE AD BOX

Pyth Network price feeds return raw integers with a separate exponent, plus confidence and EMA fields that most tutorials skip over. Here's how to work with all of them.

1. Interfaces

Define types matching the Hermes API response:

interface PythPriceData { price: string; conf: string; expo: number; publish_time: number; ema_price: string; ema_conf: string; } interface PythPriceUpdate { id: string; price: PythPriceData; ema_price: PythPriceData; }

2. Fetching current and historical prices

Pyth's Hermes API has two key endpoints — latest price and historical price at a specific Unix timestamp:

const HERMES_BASE = "https://hermes.pyth.network"; // Current price async function fetchCurrentPrice(feedId: string): Promise<PythPriceUpdate> { const res = await fetch( `${HERMES_BASE}/v2/updates/price/latest?ids[]=${feedId}&parsed=true` ); if (!res.ok) throw new Error("Failed to fetch price"); const data = await res.json(); return data.parsed[0]; } // Historical price at a specific timestamp async function fetchHistoricalPrice( feedId: string, timestamp: number ): Promise<PythPriceUpdate> { const res = await fetch( `${HERMES_BASE}/v2/updates/price/${timestamp}?ids[]=${feedId}&parsed=true` ); if (!res.ok) throw new Error(`Failed to fetch historical price (${res.status})`); const data = await res.json(); if (!data.parsed || data.parsed.length === 0) { throw new Error("No price data returned for this timestamp"); } return data.parsed[0]; }

You can find feed IDs for 500+ assets at pyth.network/developers/price-feed-ids. For example, ETH/USD is 0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace.

3. Parsing raw values

The critical part most people miss — Pyth returns prices as raw integers. You must apply the exponent:

function parsePythPrice(price: string, expo: number): number { return Number(price) * Math.pow(10, expo); } // Example: price "239151300000" with expo -8 // = 239151300000 * 10^(-8) // = 2391.513 (USD)

This applies to all numeric fields: price, conf, ema_price, and ema_conf.

4. Calculating market condition metrics

Once parsed, you can derive useful metrics from the confidence and EMA fields:

function analyzeTradeConditions(priceUpdate: PythPriceUpdate) { const expo = priceUpdate.price.expo; const price = parsePythPrice(priceUpdate.price.price, expo); const conf = parsePythPrice(priceUpdate.price.conf, expo); const emaPrice = parsePythPrice(priceUpdate.ema_price.price, expo); const emaConf = parsePythPrice(priceUpdate.ema_price.conf, expo); // 1. Confidence as % of price — how uncertain the market is // < 0.1% = very tight, stable market // > 0.5% = wide spread, high uncertainty const confPctOfPrice = (conf / price) * 100; // 2. EMA divergence — is price above or below the trend? // Positive = price above EMA (bullish momentum) // Negative = price below EMA (bearish momentum) const emaDivergence = ((price - emaPrice) / emaPrice) * 100; // 3. EMA confidence ratio — is market stability improving or worsening? // > 1 = EMA confidence wider than spot (conditions deteriorating) // < 1 = EMA confidence tighter than spot (conditions improving) const emaConfRatio = emaConf / conf; return { price, conf, emaPrice, emaConf, confPctOfPrice, emaDivergence, emaConfRatio, }; }

5. Putting it all together

async function assessTrade(feedId: string, entryTimestamp: number) { const [entryData, currentData] = await Promise.all([ fetchHistoricalPrice(feedId, entryTimestamp), fetchCurrentPrice(feedId), ]); const entry = analyzeTradeConditions(entryData); const current = analyzeTradeConditions(currentData); const pnlPct = ((current.price - entry.price) / entry.price) * 100; console.log(`Entry price: $${entry.price.toFixed(2)}`); console.log(`Current price: $${current.price.toFixed(2)}`); console.log(`P&L: ${pnlPct > 0 ? "+" : ""}${pnlPct.toFixed(2)}%`); console.log(`Entry confidence: ±${entry.confPctOfPrice.toFixed(3)}% of price`); console.log(`EMA divergence at entry: ${entry.emaDivergence.toFixed(2)}%`); console.log(`EMA conf ratio: ${entry.emaConfRatio.toFixed(2)}`); } // Example: assess an ETH trade opened on March 20, 2024 const ETH_FEED = "0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace"; assessTrade(ETH_FEED, 1710892800);

Key takeaways

Always apply the exponent — raw Pyth values are useless without it Confidence interval tells you market uncertainty at the moment of the price update. Wide confidence = disagreement among data publishers EMA fields give you trend context without needing to fetch historical candles No API key needed for Hermes — it's a free public endpoint Historical data availability starts around November 2023

I used this approach in The Market Witness (live demo), a project that turns Pyth oracle data into courtroom evidence for judging trades.

Read Entire Article