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

# Phoenix Market Parameters

> Phoenix Markets: Risk, Leverage, and Parameters

export const API_URL = "https://perp-api.phoenix.trade/exchange/markets";
export const FALLBACK_RETRY_MS = 10_000;
export const MARKET_ORDER = {
  SOL: 0,
  BTC: 1,
  ETH: 2
};

export const formatNumber = value => Number(value).toLocaleString("en-US");

export const toFixedDigits = decimals => {
  const digits = Number(decimals);
  if (!Number.isFinite(digits)) return 0;
  return Math.min(100, Math.max(0, Math.trunc(digits)));
};

export const baseLotsToBase = (baseLots, decimals) => Number(baseLots) / Math.pow(10, decimals);

export const formatBaseUnits = (baseLots, decimals) => {
  const value = baseLotsToBase(baseLots, decimals);
  const trimmed = parseFloat(value.toFixed(toFixedDigits(decimals)));
  if (trimmed >= 1000) return trimmed.toLocaleString("en-US");
  return String(trimmed);
};

export const formatBaseLotSize = decimals => (1 / Math.pow(10, decimals)).toFixed(toFixedDigits(decimals));

export const formatRiskPercent = rawValue => rawValue.toFixed(2) + "%";

export const formatTickSize = (tickSize, decimals) => {
  const price = tickSize * Math.pow(10, decimals - 6);
  return "$" + price.toFixed(6);
};

export const logoUriForMarket = market => (market.logoUri ?? market.metadata?.logoUri) ?? null;

export const AssetLogo = ({market, size = 20}) => {
  const logoUri = logoUriForMarket(market);
  if (!logoUri) return null;
  return <img src={logoUri} alt={`${market.symbol} logo`} width={size} height={size} style={{
    borderRadius: "50%",
    flex: "0 0 auto",
    objectFit: "contain",
    verticalAlign: "middle"
  }} />;
};

export const MarketLabel = ({market, logoSize = 20}) => <span style={{
  display: "inline-flex",
  alignItems: "center",
  gap: "8px"
}}>
    <AssetLogo market={market} size={logoSize} />
    <span>{market.symbol}</span>
  </span>;

export const fetchMarketConfigs = async () => {
  const res = await fetch(API_URL, {
    cache: "no-store"
  });
  if (!res.ok) throw new Error("API returned " + res.status + " for " + API_URL);
  const json = await res.json();
  if (!Array.isArray(json)) throw new Error("Unexpected response shape from " + API_URL);
  return json;
};

export const SummaryTable = ({markets}) => <table style={{
  width: "100%",
  tableLayout: "auto"
}}>
    <thead>
      <tr>
        <th style={{
  textAlign: "left"
}}>Market</th>
        <th style={{
  textAlign: "left"
}}>Base Lot Size</th>
        <th style={{
  textAlign: "left"
}}>Tick Size</th>
        <th style={{
  textAlign: "left"
}}>OI Cap (Base)</th>
      </tr>
    </thead>
    <tbody>
      {markets.map(m => <tr key={m.symbol}>
          <td><MarketLabel market={m} /></td>
          <td>{formatBaseLotSize(m.baseLotsDecimals)}</td>
          <td>{formatTickSize(m.tickSize, m.baseLotsDecimals)}</td>
          <td>
            {formatNumber(baseLotsToBase(m.openInterestCapBaseLots, m.baseLotsDecimals))}{" "}
            {m.symbol}
          </td>
        </tr>)}
    </tbody>
  </table>;

export const MarketSection = ({m}) => {
  const decimals = m.baseLotsDecimals;
  return <>
      <h2>
        <MarketLabel market={m} logoSize={28} />
      </h2>
      <h3>Market Parameters</h3>
      <p>Leverage tiers scale required collateral as position size grows.</p>
      <table>
        <thead>
          <tr>
            <th style={{
    textAlign: "left"
  }}>Field</th>
            <th style={{
    textAlign: "left"
  }}>Value</th>
          </tr>
        </thead>
        <tbody>
          <tr><td>Market</td><td><MarketLabel market={m} /></td></tr>
          <tr><td>Asset ID</td><td>{m.assetId}</td></tr>
          <tr><td>Base Lot Decimals</td><td>{decimals}</td></tr>
          <tr>
            <td>Minimum Trade Size (1 base lot)</td>
            <td>{formatBaseLotSize(decimals)} base units</td>
          </tr>
          <tr>
            <td>Tick Size</td>
            <td>{formatTickSize(m.tickSize, decimals)} per base unit</td>
          </tr>
          <tr><td>Isolated Only</td><td>{m.isolatedOnly ? "Yes" : "No"}</td></tr>
          <tr>
            <td>Open Interest Cap</td>
            <td>{formatNumber(baseLotsToBase(m.openInterestCapBaseLots, decimals))} {m.symbol}</td>
          </tr>
          <tr>
            <td>Max Liquidation Size</td>
            <td>{formatBaseUnits(m.maxLiquidationSizeBaseLots, decimals)} {m.symbol}</td>
          </tr>
          <tr><td>Funding Period</td><td>{formatNumber(m.fundingPeriodSeconds)} seconds</td></tr>
          <tr><td>Funding Interval</td><td>{formatNumber(m.fundingIntervalSeconds)} seconds</td></tr>
          <tr>
            <td>Max Funding Rate per Period</td>
            <td>
              {formatRiskPercent(m.maxFundingRatePerIntervalPercentage * 24)} (based on current
              mark price)
            </td>
          </tr>
          <tr>
            <td>Max Funding Rate per Interval</td>
            <td>
              {formatRiskPercent(m.maxFundingRatePerIntervalPercentage)} (based on current mark
              price)
            </td>
          </tr>
          <tr><td>Maintenance Risk Factor</td><td>{formatRiskPercent(m.riskFactors.maintenance)}</td></tr>
          <tr><td>Backstop Risk Factor</td><td>{formatRiskPercent(m.riskFactors.backstop)}</td></tr>
          <tr><td>High Risk Factor</td><td>{formatRiskPercent(m.riskFactors.highRisk)}</td></tr>
          <tr><td>Cancel Order Risk Factor</td><td>{formatRiskPercent(m.riskFactors.cancelOrder)}</td></tr>
          <tr><td>UPnL Risk (Trading)</td><td>{formatRiskPercent(m.riskFactors.upnl)}</td></tr>
          <tr><td>UPnL Risk (Withdrawals)</td><td>{formatRiskPercent(m.riskFactors.upnlForWithdrawals)}</td></tr>
          <tr><td>Maker Fee</td><td>{formatRiskPercent(m.makerFee * 100)}</td></tr>
          <tr><td>Taker Fee</td><td>{formatRiskPercent(m.takerFee * 100)}</td></tr>
          <tr><td>Market Status</td><td>{m.marketStatus}</td></tr>
          <tr><td>Market Account</td><td><code>{m.marketPubkey}</code></td></tr>
          <tr><td>Spline Account</td><td><code>{m.splinePubkey}</code></td></tr>
        </tbody>
      </table>

      <h3>Leverage Tiers</h3>
      <p>Leverage tiers scale required collateral as position size grows.</p>
      <table>
        <thead>
          <tr>
            <th style={{
    textAlign: "left"
  }}>Tier</th>
            <th style={{
    textAlign: "left"
  }}>Max Leverage</th>
            <th style={{
    textAlign: "left"
  }}>Max Size (Base Units)</th>
            <th style={{
    textAlign: "left"
  }}>Limit Order Risk</th>
          </tr>
        </thead>
        <tbody>
          {m.leverageTiers.map((tier, i) => <tr key={i}>
              <td>{i + 1}</td>
              <td>{tier.maxLeverage}x</td>
              <td>{formatBaseUnits(tier.maxSizeBaseLots, decimals)} {m.symbol}</td>
              <td>{formatRiskPercent(tier.limitOrderRiskFactor)}</td>
            </tr>)}
        </tbody>
      </table>
    </>;
};

export const MarketParametersPage = () => {
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);
  const [loading, setLoading] = useState(true);
  const [usingFallback, setUsingFallback] = useState(false);
  useEffect(() => {
    let cancelled = false;
    let retryTimeout = null;
    const loadMarketConfigs = async () => {
      try {
        const json = await fetchMarketConfigs();
        if (cancelled) return;
        setData(json);
        setError(null);
        setUsingFallback(false);
        setLoading(false);
      } catch (err) {
        if (cancelled) return;
        setData(fallbackMarkets);
        setError(err.message);
        setUsingFallback(true);
        setLoading(false);
        retryTimeout = setTimeout(loadMarketConfigs, FALLBACK_RETRY_MS);
      }
    };
    loadMarketConfigs();
    return () => {
      cancelled = true;
      if (retryTimeout) clearTimeout(retryTimeout);
    };
  }, []);
  if (loading) {
    return <div style={{
      display: "flex",
      alignItems: "center",
      gap: "8px",
      padding: "24px 0"
    }}>
        <svg width="16" height="16" viewBox="0 0 24 24" style={{
      animation: "spin 1s linear infinite"
    }}>
          <style>{`@keyframes spin { to { transform: rotate(360deg); } }`}</style>
          <circle cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="3" fill="none" opacity="0.2" />
          <path d="M12 2a10 10 0 0 1 10 10" stroke="currentColor" strokeWidth="3" fill="none" strokeLinecap="round" />
        </svg>
        <span>Loading market parameters…</span>
      </div>;
  }
  if (error && !data) return <p>Failed to load market parameters: {error}</p>;
  const markets = [...data].filter(market => market.marketStatus === "active").sort((a, b) => {
    const orderDiff = (MARKET_ORDER[a.symbol] ?? 99) - (MARKET_ORDER[b.symbol] ?? 99);
    if (orderDiff !== 0) return orderDiff;
    return a.symbol.localeCompare(b.symbol);
  });
  return <>
      {usingFallback ? <p style={{
    padding: "12px 16px",
    border: "1px solid rgba(255, 160, 110, 0.35)",
    borderRadius: "12px",
    background: "rgba(255, 160, 110, 0.08)"
  }}>
          Live market parameters are temporarily unavailable. Showing fallback values and retrying.
        </p> : null}
      <h2>Summary</h2>
      <SummaryTable markets={markets} />
      {markets.map(m => <MarketSection key={m.symbol} m={m} />)}
    </>;
};

<MarketParametersPage />
