Add site extractors for two prediction market platforms using their public REST APIs (no browser/Playwright dependency needed). Polymarket: search via Gamma API, get events/markets by slug/ID/URL. Handles inconsistent JSON encoding (stringified arrays). Kalshi: get markets/events by ticker/URL, search via client-side filtering of open events (no text search API available). Both include CLI tools and comprehensive test suites using httptest. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
137 lines
3.3 KiB
Go
137 lines
3.3 KiB
Go
package polymarket
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
)
|
|
|
|
// Market represents a single prediction market on Polymarket.
|
|
type Market struct {
|
|
ID string
|
|
Question string
|
|
Slug string
|
|
Description string
|
|
Outcomes []string
|
|
OutcomePrices []float64 // probabilities 0-1
|
|
Volume float64
|
|
Volume24h float64
|
|
Liquidity float64
|
|
EndDate string
|
|
Active bool
|
|
Closed bool
|
|
BestBid float64
|
|
BestAsk float64
|
|
LastPrice float64
|
|
}
|
|
|
|
// URL returns the Polymarket web URL for this market.
|
|
func (m Market) URL() string {
|
|
if m.Slug == "" {
|
|
return ""
|
|
}
|
|
return "https://polymarket.com/event/" + m.Slug
|
|
}
|
|
|
|
// Event represents a prediction market event on Polymarket, which may contain
|
|
// one or more individual markets.
|
|
type Event struct {
|
|
ID string
|
|
Title string
|
|
Slug string
|
|
Description string
|
|
Markets []Market
|
|
Volume float64
|
|
Volume24h float64
|
|
Liquidity float64
|
|
Active bool
|
|
Closed bool
|
|
}
|
|
|
|
// URL returns the Polymarket web URL for this event.
|
|
func (e Event) URL() string {
|
|
if e.Slug == "" {
|
|
return ""
|
|
}
|
|
return "https://polymarket.com/event/" + e.Slug
|
|
}
|
|
|
|
// Config holds configuration for the Polymarket extractor.
|
|
type Config struct {
|
|
// BaseURL overrides the Gamma API base URL. Leave empty for the default.
|
|
BaseURL string
|
|
|
|
// HTTPClient overrides the HTTP client used for API requests. Leave nil for default.
|
|
HTTPClient *http.Client
|
|
}
|
|
|
|
// DefaultConfig is the default configuration.
|
|
var DefaultConfig = Config{}
|
|
|
|
func (c Config) validate() Config {
|
|
return c
|
|
}
|
|
|
|
// Search finds events matching the given query string.
|
|
func (c Config) Search(ctx context.Context, query string) ([]Event, error) {
|
|
c = c.validate()
|
|
return c.searchAPI(ctx, query)
|
|
}
|
|
|
|
// GetEvent retrieves an event by its slug or numeric ID.
|
|
func (c Config) GetEvent(ctx context.Context, slugOrID string) (*Event, error) {
|
|
c = c.validate()
|
|
|
|
// Try slug first (most common from URLs)
|
|
ev, err := c.getEventBySlug(ctx, slugOrID)
|
|
if err == nil {
|
|
return ev, nil
|
|
}
|
|
|
|
// Fall back to numeric ID
|
|
ev, idErr := c.getEventByID(ctx, slugOrID)
|
|
if idErr == nil {
|
|
return ev, nil
|
|
}
|
|
|
|
return nil, fmt.Errorf("getting event %q: slug lookup: %w", slugOrID, err)
|
|
}
|
|
|
|
// GetMarket retrieves a single market by its slug.
|
|
func (c Config) GetMarket(ctx context.Context, slug string) (*Market, error) {
|
|
c = c.validate()
|
|
return c.getMarketBySlug(ctx, slug)
|
|
}
|
|
|
|
// GetEventFromURL extracts the event slug from a Polymarket URL and retrieves
|
|
// the event. If the URL points to a specific market within an event, the full
|
|
// event is still returned.
|
|
func (c Config) GetEventFromURL(ctx context.Context, rawURL string) (*Event, error) {
|
|
c = c.validate()
|
|
|
|
_, slug, _, err := parsePolymarketURL(rawURL)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return c.getEventBySlug(ctx, slug)
|
|
}
|
|
|
|
// Convenience functions using DefaultConfig.
|
|
|
|
func Search(ctx context.Context, query string) ([]Event, error) {
|
|
return DefaultConfig.Search(ctx, query)
|
|
}
|
|
|
|
func GetEvent(ctx context.Context, slugOrID string) (*Event, error) {
|
|
return DefaultConfig.GetEvent(ctx, slugOrID)
|
|
}
|
|
|
|
func GetMarket(ctx context.Context, slug string) (*Market, error) {
|
|
return DefaultConfig.GetMarket(ctx, slug)
|
|
}
|
|
|
|
func GetEventFromURL(ctx context.Context, rawURL string) (*Event, error) {
|
|
return DefaultConfig.GetEventFromURL(ctx, rawURL)
|
|
}
|