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.
Create the client and load catalogs
client, err := polyester.New(polyester.Config{
Environment: &polyester.TestnetEnvironment,
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.
Bootstrap from recent trades and candles
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.
Follow the public trade tape
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.
Add candle updates
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.
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
MarketData.GetTrades(symbol/ID, limit, page token): Initial tape and reconnect overlapMarketData.SubscribeTrades(symbol/ID): Live public executionsCandles.GetCandles(symbol/ID, timeframe, bounds, incomplete flag): Historical/bootstrap OHLCVCandles.GetCurrentCandle(symbol/ID, timeframe): Lightweight health checkCandles.SubscribeCandles(symbol/ID, timeframe): Forming and closed candle updatesOrderbook.CreateSubscription(symbol, depth, bucket, callbacks): Sequence-checked bid/ask state
The snippets on this page compile against polyester-sdk-go v0.6.0.