# Live market ticker

Build a snapshot-then-stream Go market monitor with trades, candles, staleness detection, and clean shutdown.

Build a public market monitor that starts from REST state, follows realtime trades, confirms candle availability, and fails visibly when its stream becomes stale. This pattern is useful both for a terminal ticker and as the market-data health input to a trading system.

> **Public and non-mutating**
>
> No API key is required. Empty trade or candle arrays are valid on a quiet devnet market; distinguish an empty successful response from a transport failure.

1. Create the client and load catalogs

   ```go
   client, err := polyester.New(polyester.Config{HydrateCatalogs: true})
   if err != nil { log.Fatal(err) }
   defer client.Close()

   ctx, cancel := context.WithCancel(context.Background())
   defer cancel()
   if err := client.WaitForCatalogs(ctx); err != nil { log.Fatal(err) }
   ```

   Catalog hydration resolves symbols to engine IDs and attaches the correct quantity scale to public trades.

2. Bootstrap from recent trades and candles

   ```go
   symbol := "BTC-USDT"
   recent, err := client.MarketData.GetTrades(ctx, &symbol, nil, 20, nil)
   if err != nil { log.Fatal(err) }
   for _, trade := range recent.Trades {
       fmt.Println(trade.MatchID, trade.Side, trade.Price, trade.Qty, trade.TsNs)
   }

   candles, err := client.Candles.GetCandles(
       ctx, &symbol, nil, "1m", 50, nil, nil, true,
   )
   if err != nil { log.Fatal(err) }
   if len(candles.Candles) > 0 {
       latest := candles.Candles[0]
       fmt.Println(latest.TsSec, latest.Close, latest.Volume)
   }
   ```

   REST bootstrap prevents a blank screen while waiting for the next trade. Keep the match ID and timestamp of the newest row so consumers can deduplicate a reconnect overlap. Candle rows are newest-first, and an included open candle is prepended. For indicators, request closed candles and reverse or sort the rows by `TsSec` into chronological order first.

3. Follow the public trade tape

   ```go
   sub, err := client.MarketData.SubscribeTrades(ctx, &symbol, nil)
   if err != nil { log.Fatal(err) }
   defer sub.Close()

   select {
   case trade, ok := <-sub.Messages():
       if !ok {
           if err := sub.Err(); err != nil { log.Fatal(err) }
           break
       }
       fmt.Println(trade.MatchID, trade.Side, trade.Price, trade.Qty, trade.TsNs)
   case <-time.After(30 * time.Second):
       log.Print("no trade in 30s; market may be quiet or stream may be stale")
   case <-ctx.Done():
       break
   }
   ```

   `SubscribeTrades` completes its handshake before returning. A closed message channel must always be followed by `sub.Err()`; queue overflow is terminal and requires a fresh subscription.

4. Add candle updates

   ```go
   candleSub, err := client.Candles.SubscribeCandles(ctx, &symbol, nil, "1m")
   if err != nil { log.Fatal(err) }
   defer candleSub.Close()

   if candle, ok := <-candleSub.Messages(); ok {
       fmt.Println(candle.TsSec, candle.Open, candle.High, candle.Low, candle.Close)
   }
   if err := candleSub.Err(); err != nil { log.Print(err) }
   ```

   `models.Candle` does not expose `IsClosed`. Request REST candles with `includeIncomplete=false` for indicators that must not repaint. For the stream, treat a `TsSec` bucket as forming until the next bucket begins.

5. Operate and shut down

   Use `signal.NotifyContext` for `SIGINT` and `SIGTERM`. Track a last-event timestamp separately for trades and candles. A quiet market can legitimately produce no events, so verify staleness with a bounded REST read before declaring the websocket unhealthy. Cancel the context, close each subscription, and finally close the client.

## API map

| Method                         | Important inputs                              | Use                                |
| ------------------------------ | --------------------------------------------- | ---------------------------------- |
| `MarketData.GetTrades`         | symbol/ID, limit, page token                  | Initial tape and reconnect overlap |
| `MarketData.SubscribeTrades`   | symbol/ID                                     | Live public executions             |
| `Candles.GetCandles`           | symbol/ID, timeframe, bounds, incomplete flag | Historical/bootstrap OHLCV         |
| `Candles.GetCurrentCandle`     | symbol/ID, timeframe                          | Lightweight health check           |
| `Candles.SubscribeCandles`     | symbol/ID, timeframe                          | Forming and closed candle updates  |
| `Orderbook.CreateSubscription` | symbol, depth, bucket, callbacks              | Sequence-checked bid/ask state     |

## Tested source

Example `01` performs live REST market reads and example `04` runs the public trade stream in [`polyester-examples-go`](https://github.com/Fabric-Labs/polyester-examples-go). The example suite compiles against the exact `v0.1.0a25` commit; the REST path and candle-backed RSI dry-run were executed on devnet.

## Related

- [Streaming](https://testnet.polyester.com/docs/sdk/go/guides/streaming)
- [Market data](https://testnet.polyester.com/docs/sdk/go/guides/market-data)
- [Candles](https://testnet.polyester.com/docs/sdk/go/reference/candles)
- [Public trades](https://testnet.polyester.com/docs/sdk/go/reference/public-trades)
