Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions Readme.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,41 @@ pnl = client.polymarket.wallet_profit_and_loss.list(
)
----

=== Matching Markets

Access Matching Markets endpoints via the `matching_markets` namespace.

==== Sports

[API Reference](https://docs.domeapi.io/api-reference/endpoint/get-matching-markets-sports)
Comment thread
bougyman marked this conversation as resolved.
Outdated

List matching markets for a specific sport and date:

[source,ruby]
----
# Using dynamic methods
markets = client.matching_markets.nfl_on(Date.today)
markets = client.matching_markets.nba_on('2023-12-25')

# Using sports_by_date method
markets = client.matching_markets.sports_by_date(sport: 'nfl', date: Date.today)
----

==== Markets

[API Reference](https://docs.domeapi.io/api-reference/endpoint/get-matching-markets)
Comment thread
bougyman marked this conversation as resolved.
Outdated

List matching markets by slug or ticker:

[source,ruby]
----
# By Polymarket slug
markets = client.matching_markets.sports(polymarket_market_slug: 'super-bowl-lviii-winner')

# By Kalshi ticker
markets = client.matching_markets.sports(kalshi_event_ticker: 'SB58')
----

== Development

After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
Expand Down
4 changes: 4 additions & 0 deletions lib/domeapi/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ def polymarket
@polymarket ||= Polymarket::Client.new(clone)
end

def matching_markets
@matching_markets ||= MatchingMarkets.new(self)
end
Comment on lines +31 to +33
Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing test coverage for the new matching_markets method added to the Client class. Similar to the existing test for polymarket on line 32-34, there should be a test verifying that client.matching_markets returns an instance of Rubyists::Domeapi::MatchingMarkets.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot open a new pull request to apply changes based on this feedback

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot open a new pull request to apply changes based on this feedback. Make sure all of your commit messages and PR titles pass the Conventional Commits action check.


private

def full_url(path)
Expand Down
96 changes: 96 additions & 0 deletions lib/domeapi/matching_markets.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# frozen_string_literal: true

module Rubyists
module Domeapi
# Matching Markets API endpoints
class MatchingMarkets
SPORTS = %w[nfl mlb cfb nba nhl cbb].freeze

# Filter for matching markets sports
class SportsFilter < Contract
propertize(%i[sport date])

validation do
# :nocov:
params do
required(:sport).filled(:string, included_in?: SPORTS)
required(:date).filled(:string, format?: /\A\d{4}-\d{2}-\d{2}\z/)
end
# :nocov:
end
end

# Filter for matching markets
class Filter < Contract
propertize(%i[polymarket_market_slug kalshi_event_ticker])

validation do
# :nocov:
params do
optional(:polymarket_market_slug).maybe(Types::PolymarketMarketSlug)
optional(:kalshi_event_ticker).maybe(Types::KalshiEventTicker)
end

rule(:polymarket_market_slug, :kalshi_event_ticker) do
if !values[:polymarket_market_slug] && !values[:kalshi_event_ticker]
key.failure('Either polymarket_market_slug or kalshi_event_ticker must be provided')
end
end
# :nocov:
end
end

attr_reader :client

# @param client [Rubyists::Domeapi::Client]
#
# @return [void]
def initialize(client = Rubyists::Domeapi::Client.new)
@client = client
end

# List matching markets
#
# @param filter [Filter|Hash] Filter options
#
# @return [Hash|Array] resource data
def sports(filter = Filter.new(Filter::Properties.new))
filter = Filter.new(Filter::Properties.new(**filter)) if filter.is_a?(Hash)
raise ArgumentError, filter.errors.full_messages.join(', ') unless filter.validate({})

client.get('matching-markets/sports', params: filter.to_h)
end

# List matching markets for a specific sport and date
#
# @param filter [SportsFilter|Hash] Filter options
#
# @return [Hash|Array] resource data
def sports_by_date(filter = SportsFilter.new(SportsFilter::Properties.new))
filter = prepare_sports_filter(filter)
raise ArgumentError, filter.errors.full_messages.join(', ') unless filter.validate({})

client.get("matching-markets/sports/#{filter.sport}", params: { date: filter.date })
end

SPORTS.each do |sport|
define_method("#{sport}_on") do |date|
sports_by_date(sport: sport, date: date)
end

define_singleton_method("#{sport}_on") do |date|
new.send("#{sport}_on", date)
end
end

private

def prepare_sports_filter(filter)
return filter unless filter.is_a?(Hash)

filter[:date] = filter[:date].strftime('%Y-%m-%d') if filter[:date].respond_to?(:strftime)
SportsFilter.new(SportsFilter::Properties.new(**filter))
end
end
end
end
18 changes: 18 additions & 0 deletions lib/domeapi/types.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# frozen_string_literal: true

require 'dry-types'

module Rubyists
module Domeapi
# Custom types for Domeapi
module Types
include Dry.Types()

# Polymarket slugs are typically kebab-case strings
PolymarketMarketSlug = String.constrained(format: /\A[a-z0-9]+(?:-[a-z0-9]+)+\z/)

# Kalshi event tickers are typically uppercase alphanumeric strings, potentially with dashes
KalshiEventTicker = String.constrained(format: /\A[A-Z0-9]+(?:-[A-Z0-9]+)+\z/)
Comment thread
bougyman marked this conversation as resolved.
Comment thread
bougyman marked this conversation as resolved.
end
end
end
112 changes: 112 additions & 0 deletions test/domeapi/matching_markets_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# frozen_string_literal: true

require_relative '../helper'

describe Rubyists::Domeapi::MatchingMarkets do
let(:client) { Rubyists::Domeapi::Client.new }
let(:matching_markets) { client.matching_markets }

before do
Rubyists::Domeapi.configure do |config|
config.api_key = 'test_api_key'
end
end

describe 'Sports' do
it 'lists matching markets for sports' do
stub_request(:get, 'https://api.domeapi.io/v1/matching-markets/sports')
.with(query: { polymarket_market_slug: 'valid-slug' })
.to_return(status: 200, body: '[]')

response = matching_markets.sports(polymarket_market_slug: 'valid-slug')

_(response).must_equal []
end

it 'lists matching markets for sports with Filter object' do
stub_request(:get, 'https://api.domeapi.io/v1/matching-markets/sports')
.with(query: { polymarket_market_slug: 'valid-slug' })
.to_return(status: 200, body: '[]')

filter = Rubyists::Domeapi::MatchingMarkets::Filter.new(
Rubyists::Domeapi::MatchingMarkets::Filter::Properties.new(polymarket_market_slug: 'valid-slug')
)
response = matching_markets.sports(filter)

_(response).must_equal []
end

it 'validates filter' do
error = _ { matching_markets.sports({}) }.must_raise ArgumentError
_(error.message).must_include 'Either polymarket_market_slug or kalshi_event_ticker must be provided'
end
end

describe 'Sports by Date' do
it 'lists matching markets for sport and date' do
stub_request(:get, 'https://api.domeapi.io/v1/matching-markets/sports/nfl')
.with(query: { date: '2023-01-01' })
.to_return(status: 200, body: '[]')

response = matching_markets.sports_by_date(sport: 'nfl', date: '2023-01-01')

_(response).must_equal []
end

it 'accepts Date object' do
stub_request(:get, 'https://api.domeapi.io/v1/matching-markets/sports/nfl')
.with(query: { date: '2023-01-01' })
.to_return(status: 200, body: '[]')

require 'date'
response = matching_markets.sports_by_date(sport: 'nfl', date: Date.parse('2023-01-01'))

_(response).must_equal []
end

it 'accepts SportsFilter object' do
stub_request(:get, 'https://api.domeapi.io/v1/matching-markets/sports/nfl')
.with(query: { date: '2023-01-01' })
.to_return(status: 200, body: '[]')

filter = Rubyists::Domeapi::MatchingMarkets::SportsFilter.new(
Rubyists::Domeapi::MatchingMarkets::SportsFilter::Properties.new(sport: 'nfl', date: '2023-01-01')
)
response = matching_markets.sports_by_date(filter)

_(response).must_equal []
end

it 'validates sport' do
error = _ { matching_markets.sports_by_date(sport: 'invalid', date: '2023-01-01') }.must_raise ArgumentError
_(error.message).must_include 'Sport must be one of: nfl, mlb, cfb, nba, nhl, cbb'
end

it 'validates date format' do
error = _ { matching_markets.sports_by_date(sport: 'nfl', date: 'invalid') }.must_raise ArgumentError
_(error.message).must_include 'Date is in invalid format'
end
end

describe 'Dynamic methods' do
it 'defines methods for each sport' do
stub_request(:get, 'https://api.domeapi.io/v1/matching-markets/sports/nfl')
.with(query: { date: '2023-01-01' })
.to_return(status: 200, body: '[]')

response = matching_markets.nfl_on('2023-01-01')

_(response).must_equal []
end

it 'defines class methods for each sport' do
stub_request(:get, 'https://api.domeapi.io/v1/matching-markets/sports/nfl')
.with(query: { date: '2023-01-01' })
.to_return(status: 200, body: '[]')

response = Rubyists::Domeapi::MatchingMarkets.nfl_on('2023-01-01')

_(response).must_equal []
end
end
end