Skip to main content

Description

Retrieves real-time market prices for specified cryptocurrency symbols. Used to calculate unrealized P&L, display current market values, and update position valuations in real-time.

Input Schema

symbols
string[]
Array of cryptocurrency trading pair symbols to fetch prices for (e.g., ["BTC-USD", "ETH-USD", "SOL-USD"]). If omitted or empty, returns prices for all available symbols.

TypeScript Type

type CryptoPricesInput = {
  symbols?: string[];
};

Output Schema

prices
CryptoPrice[]
Array of cryptocurrency price objects.

TypeScript Type

type CryptoPricesResponse = {
  prices: {
    symbol: string;
    price: number;
    message?: string;
  }[];
};

Example Usage

Basic Query

import { useQuery } from "@tanstack/react-query";
import { orpc } from "@/server/orpc/client";

function CryptoPrices() {
  const { data, isLoading } = useQuery(
    orpc.trading.getCryptoPrices.queryOptions({
      input: {
        symbols: ["BTC-USD", "ETH-USD", "SOL-USD"]
      }
    })
  );

  if (isLoading) return <div>Loading prices...</div>;

  return (
    <div>
      <h3>Current Prices</h3>
      <ul>
        {data?.prices.map((crypto) => (
          <li key={crypto.symbol}>
            {crypto.symbol}: ${crypto.price.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
            {crypto.message && <span style={{ color: 'orange' }}> - {crypto.message}</span>}
          </li>
        ))}
      </ul>
    </div>
  );
}

Real-time Price Ticker

import { useQuery } from "@tanstack/react-query";
import { orpc } from "@/server/orpc/client";

function PriceTicker() {
  // Poll every 2 seconds for real-time updates
  const { data } = useQuery({
    ...orpc.trading.getCryptoPrices.queryOptions({
      input: {
        symbols: ["BTC-USD", "ETH-USD"]
      }
    }),
    refetchInterval: 2000,
    staleTime: 1000,
  });

  return (
    <div style={{ display: 'flex', gap: '2rem', padding: '1rem', backgroundColor: '#000' }}>
      {data?.prices.map((crypto) => (
        <div key={crypto.symbol} style={{ color: '#fff' }}>
          <div style={{ fontSize: '0.875rem', opacity: 0.7 }}>{crypto.symbol}</div>
          <div style={{ fontSize: '1.5rem', fontWeight: 'bold' }}>
            ${crypto.price.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
          </div>
        </div>
      ))}
    </div>
  );
}

Calculate Position Values

import { useQuery } from "@tanstack/react-query";
import { orpc } from "@/server/orpc/client";

function PositionValuation() {
  const positionsQuery = useQuery(
    orpc.trading.getPositions.queryOptions({ input: {} })
  );

  // Extract unique symbols from positions
  const symbols = Array.from(
    new Set(
      positionsQuery.data?.positions.flatMap(account => 
        account.positions.map(pos => pos.symbol)
      ) ?? []
    )
  );

  const pricesQuery = useQuery(
    orpc.trading.getCryptoPrices.queryOptions({
      input: { symbols }
    })
  );

  if (positionsQuery.isLoading || pricesQuery.isLoading) {
    return <div>Loading...</div>;
  }

  // Create price lookup map
  const priceMap = new Map(
    pricesQuery.data?.prices.map(p => [p.symbol, p.price]) ?? []
  );

  return (
    <div>
      <h3>Position Valuations</h3>
      {positionsQuery.data?.positions.map(account => (
        <div key={account.modelId}>
          <h4>{account.modelName}</h4>
          {account.positions.map((pos, idx) => {
            const currentPrice = priceMap.get(pos.symbol) ?? 0;
            const value = pos.quantity * currentPrice;
            const pnl = pos.side === 'long'
              ? (currentPrice - pos.entryPrice) * pos.quantity
              : (pos.entryPrice - currentPrice) * pos.quantity;
            
            return (
              <div key={`${pos.symbol}-${idx}`}>
                <p>
                  {pos.symbol}: {pos.quantity.toFixed(4)} × ${currentPrice.toFixed(2)} = 
                  <strong> ${value.toFixed(2)}</strong>
                  <span style={{ color: pnl >= 0 ? 'green' : 'red', marginLeft: '1rem' }}>
                    ({pnl >= 0 ? '+' : ''}${pnl.toFixed(2)})
                  </span>
                </p>
              </div>
            );
          })}
        </div>
      ))}
    </div>
  );
}

Price Alert System

import { useQuery } from "@tanstack/react-query";
import { orpc } from "@/server/orpc/client";
import { useEffect, useState } from "react";

type PriceAlert = {
  symbol: string;
  threshold: number;
  condition: 'above' | 'below';
};

function PriceAlerts() {
  const [alerts, setAlerts] = useState<PriceAlert[]>([
    { symbol: 'BTC-USD', threshold: 100000, condition: 'above' },
    { symbol: 'ETH-USD', threshold: 3000, condition: 'below' },
  ]);
  
  const [triggered, setTriggered] = useState<string[]>([]);

  const { data } = useQuery({
    ...orpc.trading.getCryptoPrices.queryOptions({
      input: {
        symbols: alerts.map(a => a.symbol)
      }
    }),
    refetchInterval: 5000,
  });

  useEffect(() => {
    if (!data?.prices) return;

    const newTriggered: string[] = [];
    
    alerts.forEach(alert => {
      const price = data.prices.find(p => p.symbol === alert.symbol);
      if (!price) return;

      const isTriggered = alert.condition === 'above'
        ? price.price > alert.threshold
        : price.price < alert.threshold;

      if (isTriggered) {
        const alertKey = `${alert.symbol}-${alert.threshold}-${alert.condition}`;
        if (!triggered.includes(alertKey)) {
          newTriggered.push(alertKey);
          // Trigger notification
          console.log(`🚨 ALERT: ${alert.symbol} is ${alert.condition} ${alert.threshold}!`);
        }
      }
    });

    if (newTriggered.length > 0) {
      setTriggered(prev => [...prev, ...newTriggered]);
    }
  }, [data, alerts, triggered]);

  return (
    <div>
      <h3>Price Alerts</h3>
      {alerts.map((alert, idx) => {
        const price = data?.prices.find(p => p.symbol === alert.symbol);
        return (
          <div key={idx}>
            <p>
              {alert.symbol}: ${price?.price.toFixed(2) ?? 'N/A'} 
              {alert.condition === 'above' ? ' > ' : ' < '}
              ${alert.threshold.toFixed(2)}
            </p>
          </div>
        );
      })}
    </div>
  );
}

Fetch All Prices

import { useQuery } from "@tanstack/react-query";
import { orpc } from "@/server/orpc/client";

function AllCryptoPrices() {
  // Omit symbols parameter to fetch all available prices
  const { data, isLoading } = useQuery(
    orpc.trading.getCryptoPrices.queryOptions({
      input: {}
    })
  );

  if (isLoading) return <div>Loading all prices...</div>;

  // Sort by price descending
  const sortedPrices = [...(data?.prices ?? [])].sort((a, b) => b.price - a.price);

  return (
    <div>
      <h3>All Cryptocurrency Prices</h3>
      <p>Total: {sortedPrices.length} symbols</p>
      <table>
        <thead>
          <tr>
            <th>Symbol</th>
            <th>Price (USD)</th>
          </tr>
        </thead>
        <tbody>
          {sortedPrices.map((crypto) => (
            <tr key={crypto.symbol}>
              <td>{crypto.symbol}</td>
              <td>${crypto.price.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

Implementation Notes

  • Normalization: Input symbols are normalized using parseSymbols() before fetching (handles various formats).
  • Error Handling: Returns empty array (prices: []) on fetch failure instead of throwing an error.
  • Data Source: Prices are fetched from @/server/features/trading/queries.server.fetchCryptoPrices().
  • Caching: Recommended to use short staleTime (1-5 seconds) for near real-time updates.

Build docs developers (and LLMs) love