# Market data

> Query markets, events, sports, and competitions.

Source: https://docs.stxapp.io/sdks/csharp/markets/

Market data lives behind two services:

- **`STXMarketService`**: individual markets and market counts
- **`STXEventService`**: events (a match/game), with its markets attached

## List markets

```csharp
var markets = serviceProvider.GetRequiredService<STXMarketService>();

var resp = await markets.GetMarketInfosWithCountAsync();

foreach (var m in resp.MarketInfos)
{
    Console.WriteLine($"{m.MarketId}  {m.Status}  {m.Title}");
}
```

`GetMarketInfosWithCountAsync` returns `STXMarketInfosWithCountResponse<STXMarketInfo>`: the full market object, plus `Count` and `Cursor` for paging. For lightweight lookups by ID, use the `Short` variant:

```csharp
var ids = new[] { "mkt_abc", "mkt_def" };
var short_ = await markets.GetShortMarketInfosWithCountAsync(ids);
```

## Pagination & counts

`GetMarketInfosWithCountAsync` returns one page plus `Count` (the total matching the filter *before* pagination) and `Cursor`, an opaque pointer to the next page:

```csharp
var page = await markets.GetMarketInfosWithCountAsync(new STXMarketInfosFilter
{
    Status     = [STXMarketInfosStatus.OPEN],
    Trading    = STXMarketInfosTrading.TRUE,
    Pagination = new STXKeysetPagination { Limit = 100 },
});

Console.WriteLine($"{page.MarketInfos.Count}/{page.Count}");
```

Pass `Cursor` back to fetch the next page. The walk ends when the server stops returning a cursor, not when a page comes back short. A last page that exactly fills `Limit` still carries a cursor, and the request after it returns no markets and no cursor. Treat an empty cursor as the end too: sent back, it reads to the server as "no cursor" and restarts you at page one.

```csharp
string cursor = null;

do
{
    var page = await markets.GetMarketInfosWithCountAsync(new STXMarketInfosFilter
    {
        Pagination = new STXKeysetPagination { Cursor = cursor, Limit = 100 },
    });

    foreach (var m in page.MarketInfos) { /* … */ }

    cursor = page.Cursor;
}
while (!string.IsNullOrEmpty(cursor));
```

## Fetch every market

:::caution
**A single response carries a limited number of markets, so one call is not guaranteed to return everything.** Ask for no page size and the server applies its own default; ask for one larger than the server's maximum and the request is rejected outright with `Cannot request more than N records per page`. Both the default and the maximum are per-environment configuration, so neither is a number to hard-code against.

`Count` always reports the true total, so **whenever `MarketInfos.Count` is smaller than `Count`, you are looking at a partial set**. Because it is the total that grows, a call that returns everything today can start coming back short later without anything about your code changing. Walking the cursor is what makes a result complete, and the helpers below do it for you.
:::

The paging helpers run that loop for you. `GetAllOpenMarketInfosAsync` fetches every market currently open:

```csharp
var open = await markets.GetAllOpenMarketInfosAsync();

Console.WriteLine($"{open.Count} open markets");
```

:::caution
**`MarketIds` bypasses the rest of the filter.** When it is set the server fetches exactly those markets by id and ignores status, sports, trading and paging, so `GetAllOpenMarketInfosAsync(new STXMarketInfosFilter { MarketIds = [...] })` returns those markets whatever their status, open or not. Filter the result yourself if you combine the two.
:::

:::caution
`OPEN` is a **lifecycle** filter, not a literal status match: it returns markets whose `Status` is `suspended` as well as `open`, and the suspended share can be a large fraction of the result. If you need only tradeable markets, filter on `Status` afterwards:

```csharp
var tradeable = open.Where(m => m.Status == STXMarketStatus.open).ToList();
```
:::

`Status` is overridden and, as in every paged call, `Limit` and `Pagination` are ignored. The rest of the filter is honoured, so the walk can be narrowed:

```csharp
var openSoccer = await markets.GetAllOpenMarketInfosAsync(new STXMarketInfosFilter
{
    Sports = ["Soccer"],
});
```

Use `GetAllMarketInfosAsync` to walk any filter without forcing a status, and `GetMarketInfoPagesAsync` to stream page by page instead of buffering the whole result set. This is worth it for broad filters, which can run to tens of thousands of markets:

```csharp
await foreach (var page in markets.GetMarketInfoPagesAsync<STXMarketInfo>(filter, pageSize: 500))
{
    foreach (var m in page) { /* … */ }
}
```

:::note
`Limit` and `Pagination` on the filter are ignored by the paging helpers; the `pageSize` argument sets the page. Keep it within the server's maximum page size: exceeding it fails the request rather than returning a smaller page. The filter you pass is never modified. Markets are created and change status continuously, so a walk returns each market as it was when its page was read, not an instantaneous snapshot of the whole set.
:::

### Cost, and how to cut it

A walk covers every matching market, so the projection you ask for decides what it costs. Asking for the complete `STXMarketInfo` across a large market set moves a great deal more data and takes correspondingly longer than asking for a handful of fields (on a mature environment the difference is about an order of magnitude in both), and it holds every fully-populated market object in memory at once.

The generic overload builds its GraphQL selection from `T`, so a type carrying only the fields you actually use is the largest saving available here:

```csharp
public class MarketRow
{
    public Guid MarketId { get; set; }
    public string Symbol { get; set; }
    public string GroupingId { get; set; }
    public string Title { get; set; }
}

var rows = await markets.GetAllMarketInfosAsync<MarketRow>();
```

Before reaching for the full set, check whether you need it: the bulk of a mature environment is `resulted` history. If what you want is the live board, `GetAllOpenMarketInfosAsync()` returns a far smaller set for a fraction of the cost.

## Filter

`STXMarketInfosFilter` combines the common filter fields:

| Field | Type | |
|---|---|---|
| `Status` | `IEnumerable<STXMarketInfosStatus>` | One or more statuses, e.g. `[STXMarketInfosStatus.OPEN]` |
| `Trading` | `STXMarketInfosTrading?` | `TRUE` / `FALSE` |
| `Sports` | `IEnumerable<string>` | Sport filter (`["Basketball"]`, `["Baseball"]`, …) |
| `Competitions` | `IEnumerable<string>` | Competition filter (`["NBA"]`, `["MLB"]`, …) |
| `EventIds` | `IEnumerable<string>` | Only markets for the given events |
| `MarketIds` | `IEnumerable<string>` | Only the given markets |
| `Pagination` | `STXKeysetPagination` | Cursor and page size (see above) |
| `Limit` | `int?` | Caps a single response; not a substitute for paging |
| `KeywordRegex` | `string` | Regex match on market keywords |
| `Featured` | `STXFeatured?` | Featured markets |
| `SortBy` | `STXMarketInfosSortBy` | Field and direction |
| `Stat` | `STXPslStats?` | Player-stat markets |

:::note
These are collections, not single values: `Sports`, not `Sport`. There is no `Offset`: paging is cursor-based through `Pagination`.
:::

## Sports & competitions

```csharp
var catalog = await markets.GetSportAndCompetitionsAsync();

foreach (var sport in catalog)
{
    Console.WriteLine($"{sport.Sport}");
    foreach (var c in sport.Competitions)
        Console.WriteLine($"  {c}");
}
```

## Events

`STXEventService` bundles an event with its markets: one call instead of one-markets-per-event:

```csharp
var events = serviceProvider.GetRequiredService<STXEventService>();

var resp = await events.GetEventInfosAsync(new STXEventInfosFilter
{
    Sport = "basketball",
    EventStatusFilter = new[]
    {
        STXEventStatus.scheduled,
        STXEventStatus.live,
    },
});

foreach (var ev in resp.EventInfos)
{
    Console.WriteLine($"{ev.EventId}  {ev.Title}  ({ev.MarketInfos.Count} markets)");
}
```

## Real-time updates

For live price ticks on open markets, subscribe via the `STXMarketChannel` websocket wrapper (see [WebSockets → Market updates](/sdks/csharp/websockets#market-data)). REST polling is fine for snapshots; channels are required for latency-sensitive use cases.

## See also

- [Reference → Market data](/sdks/csharp/reference/market-data/): full method signatures
- `STX.Sdk.Enums`: all filter values, listed by your IDE's completion
