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

# Build a Portfolio Tracker

> Combine live prices with your users' holdings to calculate real-time portfolio value and P&L.

This guide shows how to build a portfolio tracker that fetches live prices for a set of stock holdings and calculates current value, cost basis, and unrealized gain or loss — the same core logic NGN Market uses in its own dashboard.

**Endpoints used:**

* `GET /companies` (Free plan) — bulk price fetch for all holdings at once
* `GET /companies/:symbol` (Starter plan) — single stock quote when needed

## How it works

1. Store each holding as `{ symbol, quantity, avg_cost }` — either in a database or client-side
2. Fetch current prices for all held symbols in one call
3. Calculate position value and P\&L per stock
4. Sum up for the total portfolio view

## Step 1: Define your holdings

```javascript JavaScript theme={null}
const holdings = [
  { symbol: 'DANGCEM',   quantity: 500,  avg_cost: 285.00 },
  { symbol: 'GTCO',      quantity: 2000, avg_cost: 55.40  },
  { symbol: 'AIRTELAFRI',quantity: 300,  avg_cost: 1820.00},
  { symbol: 'MTNN',      quantity: 800,  avg_cost: 198.50 },
  { symbol: 'ZENITHBANK',quantity: 1500, avg_cost: 34.20  },
];
```

```python Python theme={null}
holdings = [
    {'symbol': 'DANGCEM',    'quantity': 500,  'avg_cost': 285.00},
    {'symbol': 'GTCO',       'quantity': 2000, 'avg_cost': 55.40 },
    {'symbol': 'AIRTELAFRI', 'quantity': 300,  'avg_cost': 1820.00},
    {'symbol': 'MTNN',       'quantity': 800,  'avg_cost': 198.50},
    {'symbol': 'ZENITHBANK', 'quantity': 1500, 'avg_cost': 34.20 },
]
```

## Step 2: Fetch live prices for all holdings

Fetch the full company list and filter to your held symbols — one API call covers the whole portfolio.

<CodeGroup>
  ```javascript JavaScript theme={null}
  const API_KEY = 'ngm_live_YOUR_KEY';

  async function fetchPrices(symbols) {
    const res = await fetch(
      'https://api.ngnmarket.com/v1/companies?limit=200',
      { headers: { Authorization: `Bearer ${API_KEY}` } }
    );
    const { data } = await res.json();

    // Build a symbol → price map for fast lookup
    return Object.fromEntries(
      data.companies
        .filter(c => symbols.includes(c.symbol))
        .map(c => [c.symbol, c])
    );
  }
  ```

  ```python Python theme={null}
  import requests

  API_KEY = 'ngm_live_YOUR_KEY'

  def fetch_prices(symbols):
      res = requests.get(
          'https://api.ngnmarket.com/v1/companies',
          params={'limit': 200},
          headers={'Authorization': f'Bearer {API_KEY}'},
      )
      companies = res.json()['data']['companies']
      return {c['symbol']: c for c in companies if c['symbol'] in symbols}
  ```
</CodeGroup>

## Step 3: Calculate P\&L for each position

<CodeGroup>
  ```javascript JavaScript theme={null}
  function calcPortfolio(holdings, priceMap) {
    let totalCost         = 0;
    let totalCurrentValue = 0;

    const positions = holdings.map(({ symbol, quantity, avg_cost }) => {
      const stock         = priceMap[symbol];
      const current_price = stock?.current_price ?? avg_cost;
      const cost_basis    = quantity * avg_cost;
      const current_value = quantity * current_price;
      const gain_loss     = current_value - cost_basis;
      const gain_loss_pct = (gain_loss / cost_basis) * 100;

      totalCost         += cost_basis;
      totalCurrentValue += current_value;

      return {
        symbol,
        company_name:   stock?.company_name ?? symbol,
        quantity,
        avg_cost,
        current_price,
        cost_basis,
        current_value,
        gain_loss,
        gain_loss_pct,
        last_updated: stock?.last_updated,
      };
    });

    return {
      positions,
      summary: {
        total_cost:          totalCost,
        total_current_value: totalCurrentValue,
        total_gain_loss:     totalCurrentValue - totalCost,
        total_gain_loss_pct: ((totalCurrentValue - totalCost) / totalCost) * 100,
      },
    };
  }
  ```

  ```python Python theme={null}
  def calc_portfolio(holdings, price_map):
      total_cost          = 0
      total_current_value = 0
      positions = []

      for h in holdings:
          symbol      = h['symbol']
          quantity    = h['quantity']
          avg_cost    = h['avg_cost']
          stock       = price_map.get(symbol, {})
          cur_price   = stock.get('current_price', avg_cost)
          cost_basis  = quantity * avg_cost
          cur_value   = quantity * cur_price
          gain_loss   = cur_value - cost_basis
          gl_pct      = (gain_loss / cost_basis) * 100

          total_cost          += cost_basis
          total_current_value += cur_value

          positions.append({
              'symbol':        symbol,
              'company_name':  stock.get('company_name', symbol),
              'quantity':      quantity,
              'avg_cost':      avg_cost,
              'current_price': cur_price,
              'cost_basis':    cost_basis,
              'current_value': cur_value,
              'gain_loss':     gain_loss,
              'gain_loss_pct': gl_pct,
              'last_updated':  stock.get('last_updated'),
          })

      return {
          'positions': positions,
          'summary': {
              'total_cost':          total_cost,
              'total_current_value': total_current_value,
              'total_gain_loss':     total_current_value - total_cost,
              'total_gain_loss_pct': ((total_current_value - total_cost) / total_cost) * 100,
          },
      }
  ```
</CodeGroup>

## Step 4: Put it all together

<CodeGroup>
  ```javascript JavaScript theme={null}
  const symbols  = holdings.map(h => h.symbol);
  const priceMap = await fetchPrices(symbols);
  const { positions, summary } = calcPortfolio(holdings, priceMap);

  console.log('Portfolio summary:');
  console.log(`  Cost basis:     ₦${summary.total_cost.toLocaleString()}`);
  console.log(`  Current value:  ₦${summary.total_current_value.toLocaleString()}`);
  console.log(`  Unrealized P&L: ₦${summary.total_gain_loss.toLocaleString()} (${summary.total_gain_loss_pct.toFixed(2)}%)`);

  console.log('\nPositions:');
  positions.forEach(p => {
    const sign = p.gain_loss >= 0 ? '+' : '';
    console.log(
      `  ${p.symbol.padEnd(12)} ₦${p.current_price.toFixed(2).padStart(8)}  ` +
      `${sign}₦${p.gain_loss.toLocaleString()} (${sign}${p.gain_loss_pct.toFixed(2)}%)`
    );
  });
  ```

  ```python Python theme={null}
  symbols   = [h['symbol'] for h in holdings]
  price_map = fetch_prices(symbols)
  result    = calc_portfolio(holdings, price_map)
  summary   = result['summary']

  print('Portfolio summary:')
  print(f"  Cost basis:     ₦{summary['total_cost']:,.2f}")
  print(f"  Current value:  ₦{summary['total_current_value']:,.2f}")
  print(f"  Unrealized P&L: ₦{summary['total_gain_loss']:,.2f} ({summary['total_gain_loss_pct']:.2f}%)")

  print('\nPositions:')
  for p in result['positions']:
      sign = '+' if p['gain_loss'] >= 0 else ''
      print(f"  {p['symbol']:<12} ₦{p['current_price']:>8.2f}  "
            f"{sign}₦{p['gain_loss']:,.2f} ({sign}{p['gain_loss_pct']:.2f}%)")
  ```
</CodeGroup>

### Sample output

```
Portfolio summary:
  Cost basis:     ₦19,275,000.00
  Current value:  ₦21,043,500.00
  Unrealized P&L: ₦1,768,500.00 (+9.17%)

Positions:
  DANGCEM      ₦  302.50  +₦8,750.00 (+6.14%)
  GTCO         ₦   62.10  +₦13,400.00 (+12.10%)
  AIRTELAFRI   ₦ 1910.00  +₦27,000.00 (+4.95%)
  MTNN         ₦  205.30  +₦5,440.00 (+3.42%)
  ZENITHBANK   ₦   37.80  +₦5,400.00 (+10.53%)
```

## React component

```tsx React theme={null}
import { useEffect, useState } from 'react';

const API_KEY  = 'ngm_live_YOUR_KEY';
const INTERVAL = 5 * 60 * 1000; // refresh every 5 minutes

const HOLDINGS = [
  { symbol: 'DANGCEM',    quantity: 500,  avg_cost: 285.00 },
  { symbol: 'GTCO',       quantity: 2000, avg_cost: 55.40  },
  { symbol: 'AIRTELAFRI', quantity: 300,  avg_cost: 1820.00},
];

async function loadPortfolio() {
  const res = await fetch('https://api.ngnmarket.com/v1/companies?limit=200', {
    headers: { Authorization: `Bearer ${API_KEY}` },
  });
  const { data } = await res.json();
  const priceMap = Object.fromEntries(data.companies.map(c => [c.symbol, c]));

  return HOLDINGS.map(({ symbol, quantity, avg_cost }) => {
    const stock         = priceMap[symbol] ?? {};
    const current_price = stock.current_price ?? avg_cost;
    const cost_basis    = quantity * avg_cost;
    const current_value = quantity * current_price;
    const gain_loss     = current_value - cost_basis;
    return { symbol, company_name: stock.company_name, quantity, avg_cost,
             current_price, cost_basis, current_value, gain_loss,
             gain_loss_pct: (gain_loss / cost_basis) * 100 };
  });
}

export function PortfolioTracker() {
  const [positions, setPositions] = useState([]);

  useEffect(() => {
    loadPortfolio().then(setPositions);
    const t = setInterval(() => loadPortfolio().then(setPositions), INTERVAL);
    return () => clearInterval(t);
  }, []);

  const total = positions.reduce((acc, p) => ({
    cost: acc.cost + p.cost_basis,
    value: acc.value + p.current_value,
  }), { cost: 0, value: 0 });

  const totalGL    = total.value - total.cost;
  const totalGLPct = total.cost ? (totalGL / total.cost) * 100 : 0;

  return (
    <div>
      <h2>Portfolio — ₦{total.value.toLocaleString(undefined, { maximumFractionDigits: 2 })}</h2>
      <p style={{ color: totalGL >= 0 ? 'green' : 'red' }}>
        {totalGL >= 0 ? '+' : ''}₦{totalGL.toLocaleString(undefined, { maximumFractionDigits: 2 })} ({totalGLPct.toFixed(2)}%)
      </p>
      <table>
        <thead>
          <tr><th>Symbol</th><th>Qty</th><th>Avg cost</th><th>Price</th><th>Value</th><th>P&L</th></tr>
        </thead>
        <tbody>
          {positions.map(p => (
            <tr key={p.symbol}>
              <td>{p.symbol}</td>
              <td>{p.quantity}</td>
              <td>₦{p.avg_cost.toFixed(2)}</td>
              <td>₦{p.current_price.toFixed(2)}</td>
              <td>₦{p.current_value.toLocaleString(undefined, { maximumFractionDigits: 2 })}</td>
              <td style={{ color: p.gain_loss >= 0 ? 'green' : 'red' }}>
                {p.gain_loss >= 0 ? '+' : ''}₦{p.gain_loss.toLocaleString(undefined, { maximumFractionDigits: 2 })} ({p.gain_loss_pct.toFixed(2)}%)
              </td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}
```

## Tips

**Store holdings server-side.** Keeping `avg_cost` and `quantity` in your own database means you can calculate P\&L for any user without exposing API keys to the client.

**Refresh on the same cadence as the ticker.** Prices update every 20 minutes during trading hours — a 5-minute poll interval is a good balance between freshness and quota usage.

**Handle missing prices gracefully.** If a symbol isn't in the response (e.g. suspended stock), fall back to `avg_cost` so the position still appears with a 0% change rather than crashing.

<CardGroup cols={2}>
  <Card title="Companies list reference" icon="book" href="/api-reference/companies/list">
    All fields returned by `GET /companies`
  </Card>

  <Card title="Live price ticker guide" icon="bolt" href="/guides/live-price-ticker">
    How to keep prices updating automatically
  </Card>
</CardGroup>
