From c777626bc98205dc778e2f87c6295b94f9695d04 Mon Sep 17 00:00:00 2001 From: "Tj (bougyman) Vanderpoel" Date: Mon, 5 Jan 2026 10:21:40 -0600 Subject: [PATCH 1/3] feat: adds event listing and searching --- lib/kalshi/client.rb | 8 +++- lib/kalshi/endpoint.rb | 13 +++++++ lib/kalshi/events/client.rb | 26 +++++++++++++ lib/kalshi/events/list.rb | 37 ++++++++++++++++++ lib/kalshi/events/multivariate.rb | 28 ++++++++++++++ lib/kalshi/listable.rb | 11 ------ test/kalshi/client_test.rb | 8 ++++ test/kalshi/events/client_test.rb | 50 +++++++++++++++++++++++++ test/kalshi/events/multivariate_test.rb | 30 +++++++++++++++ 9 files changed, 199 insertions(+), 12 deletions(-) create mode 100644 lib/kalshi/events/client.rb create mode 100644 lib/kalshi/events/list.rb create mode 100644 lib/kalshi/events/multivariate.rb create mode 100644 test/kalshi/events/client_test.rb create mode 100644 test/kalshi/events/multivariate_test.rb diff --git a/lib/kalshi/client.rb b/lib/kalshi/client.rb index 20ca3a6..05cf35d 100644 --- a/lib/kalshi/client.rb +++ b/lib/kalshi/client.rb @@ -57,10 +57,16 @@ def search @search ||= Search::Client.new(clone) end + def events + @events ||= Events::Client.new(clone) + end + private def full_url(path) - File.join(*[base_url, prefix, path].compact) + parts = [base_url, prefix, path].compact + parts.reject!(&:empty?) + File.join(*parts) end def handle_response(response) diff --git a/lib/kalshi/endpoint.rb b/lib/kalshi/endpoint.rb index 81ed09a..698912e 100644 --- a/lib/kalshi/endpoint.rb +++ b/lib/kalshi/endpoint.rb @@ -6,6 +6,19 @@ module Kalshi class Endpoint attr_reader :client + class << self + attr_accessor :endpoint_path + + # Set the endpoint path for the resource + # + # @param path [String] API endpoint path + # + # @return [String] endpoint path + def kalshi_path(path) + self.endpoint_path = path + end + end + # Initialize the Endpoint # # @param client [Client] The Kalshi client diff --git a/lib/kalshi/events/client.rb b/lib/kalshi/events/client.rb new file mode 100644 index 0000000..45e0e0d --- /dev/null +++ b/lib/kalshi/events/client.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module Rubyists + module Kalshi + module Events + # Events API Client + class Client < ApiClient + def list(...) + List.new(client).list(...) + end + + def fetch(...) + List.new(client).fetch(...) + end + + def metadata(...) + List.new(client).metadata(...) + end + + def multivariate + Multivariate.new(client) + end + end + end + end +end diff --git a/lib/kalshi/events/list.rb b/lib/kalshi/events/list.rb new file mode 100644 index 0000000..0ed4c81 --- /dev/null +++ b/lib/kalshi/events/list.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +module Rubyists + module Kalshi + module Events + # Events API endpoint + class List < Kalshi::Endpoint + include Kalshi::Listable + + kalshi_path '' + + # Filter for Kalshi events list + class Filter < Kalshi::Contract + propertize(%i[limit cursor status series_ticker with_nested_markets]) + + validation do + params do + optional(:limit).maybe(:integer) + optional(:cursor).maybe(:string) + optional(:status).maybe(:string) + optional(:series_ticker).maybe(:string) + optional(:with_nested_markets).maybe(:bool) + end + end + end + + def fetch(event_ticker) + client.get(event_ticker) + end + + def metadata(event_ticker) + client.get("#{event_ticker}/metadata") + end + end + end + end +end diff --git a/lib/kalshi/events/multivariate.rb b/lib/kalshi/events/multivariate.rb new file mode 100644 index 0000000..a28bf0b --- /dev/null +++ b/lib/kalshi/events/multivariate.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module Rubyists + module Kalshi + module Events + # Multivariate Events API endpoint + class Multivariate < Kalshi::Endpoint + include Kalshi::Listable + + kalshi_path 'multivariate' + + # Filter for Kalshi multivariate events list + class Filter < Kalshi::Contract + propertize(%i[limit cursor series_ticker collection_ticker]) + + validation do + params do + optional(:limit).maybe(:integer) + optional(:cursor).maybe(:string) + optional(:series_ticker).maybe(:string) + optional(:collection_ticker).maybe(:string) + end + end + end + end + end + end +end diff --git a/lib/kalshi/listable.rb b/lib/kalshi/listable.rb index 5ea4306..280a4e3 100644 --- a/lib/kalshi/listable.rb +++ b/lib/kalshi/listable.rb @@ -10,17 +10,6 @@ def self.included(base) # Class methods for Listable module ClassMethods - attr_accessor :endpoint_path - - # Set the endpoint path for the resource - # - # @param path [String] API endpoint path - # - # @return [String] endpoint path - def kalshi_path(path) - self.endpoint_path = path - end - def list(...) new.list(...) end diff --git a/test/kalshi/client_test.rb b/test/kalshi/client_test.rb index 82de174..06eb7ae 100644 --- a/test/kalshi/client_test.rb +++ b/test/kalshi/client_test.rb @@ -62,6 +62,14 @@ assert_instance_of Rubyists::Kalshi::Search::Client, client.search end end + + describe '#events' do + it 'returns an Events::Client instance' do + client = Rubyists::Kalshi::Client.new + + assert_instance_of Rubyists::Kalshi::Events::Client, client.events + end + end end describe Rubyists::Kalshi do diff --git a/test/kalshi/events/client_test.rb b/test/kalshi/events/client_test.rb new file mode 100644 index 0000000..68d518f --- /dev/null +++ b/test/kalshi/events/client_test.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require_relative '../../helper' + +describe Rubyists::Kalshi::Events::Client do + let(:client) { Rubyists::Kalshi::Client.new } + let(:events_client) { client.events } + let(:base_url) { Rubyists::Kalshi.config.base_url } + + describe '#list' do + it 'fetches the events list' do + stub_request(:get, "#{base_url}/events") + .to_return(status: 200, body: '{"events": []}', headers: { 'Content-Type' => 'application/json' }) + + response = events_client.list + + assert_equal({ events: [] }, response) + end + end + + describe '#fetch' do + it 'fetches a specific event by ticker' do + ticker = 'KX-EVENT' + stub_request(:get, "#{base_url}/events/#{ticker}") + .to_return(status: 200, body: '{"event": {}}', headers: { 'Content-Type' => 'application/json' }) + + response = events_client.fetch(ticker) + + assert_equal({ event: {} }, response) + end + end + + describe '#metadata' do + it 'fetches event metadata' do + ticker = 'KX-EVENT' + stub_request(:get, "#{base_url}/events/#{ticker}/metadata") + .to_return(status: 200, body: '{"metadata": {}}', headers: { 'Content-Type' => 'application/json' }) + + response = events_client.metadata(ticker) + + assert_equal({ metadata: {} }, response) + end + end + + describe '#multivariate' do + it 'returns a Multivariate instance' do + assert_instance_of Rubyists::Kalshi::Events::Multivariate, events_client.multivariate + end + end +end diff --git a/test/kalshi/events/multivariate_test.rb b/test/kalshi/events/multivariate_test.rb new file mode 100644 index 0000000..5dd4ca6 --- /dev/null +++ b/test/kalshi/events/multivariate_test.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require_relative '../../helper' + +describe Rubyists::Kalshi::Events::Multivariate do + let(:client) { Rubyists::Kalshi::Client.new } + let(:multivariate) { client.events.multivariate } + let(:base_url) { Rubyists::Kalshi.config.base_url } + + describe '#list' do + it 'fetches the multivariate events list' do + stub_request(:get, "#{base_url}/events/multivariate") + .to_return(status: 200, body: '{"events": []}', headers: { 'Content-Type' => 'application/json' }) + + response = multivariate.list + + assert_equal({ events: [] }, response) + end + + it 'fetches the multivariate events list with filters' do + stub_request(:get, "#{base_url}/events/multivariate") + .with(query: { series_ticker: 'KX-SERIES' }) + .to_return(status: 200, body: '{"events": []}', headers: { 'Content-Type' => 'application/json' }) + + response = multivariate.list(series_ticker: 'KX-SERIES') + + assert_equal({ events: [] }, response) + end + end +end From cf1a7282d9b85acd5c0a8e8926a915b023d7eb57 Mon Sep 17 00:00:00 2001 From: "Tj (bougyman) Vanderpoel" Date: Mon, 5 Jan 2026 13:14:26 -0600 Subject: [PATCH 2/3] adds series path and events lookup --- lib/kalshi/client.rb | 4 ++ lib/kalshi/events/client.rb | 4 ++ lib/kalshi/market/client.rb | 2 +- lib/kalshi/series/client.rb | 18 ++++++++ lib/kalshi/series/event_candlesticks.rb | 34 ++++++++++++++ .../series/forecast_percentile_history.rb | 36 +++++++++++++++ .../market_candlesticks.rb} | 4 +- test/kalshi/client_test.rb | 8 ++++ test/kalshi/market/candlesticks_test.rb | 4 +- test/kalshi/market/client_test.rb | 2 +- test/kalshi/series/client_test.rb | 21 +++++++++ test/kalshi/series/event_candlesticks_test.rb | 39 ++++++++++++++++ .../forecast_percentile_history_test.rb | 46 +++++++++++++++++++ 13 files changed, 216 insertions(+), 6 deletions(-) create mode 100644 lib/kalshi/series/client.rb create mode 100644 lib/kalshi/series/event_candlesticks.rb create mode 100644 lib/kalshi/series/forecast_percentile_history.rb rename lib/kalshi/{market/candlesticks.rb => series/market_candlesticks.rb} (91%) create mode 100644 test/kalshi/series/client_test.rb create mode 100644 test/kalshi/series/event_candlesticks_test.rb create mode 100644 test/kalshi/series/forecast_percentile_history_test.rb diff --git a/lib/kalshi/client.rb b/lib/kalshi/client.rb index 05cf35d..0d6a0a7 100644 --- a/lib/kalshi/client.rb +++ b/lib/kalshi/client.rb @@ -61,6 +61,10 @@ def events @events ||= Events::Client.new(clone) end + def series + @series ||= Series::Client.new(clone) + end + private def full_url(path) diff --git a/lib/kalshi/events/client.rb b/lib/kalshi/events/client.rb index 45e0e0d..aa1135b 100644 --- a/lib/kalshi/events/client.rb +++ b/lib/kalshi/events/client.rb @@ -20,6 +20,10 @@ def metadata(...) def multivariate Multivariate.new(client) end + + def candlesticks + Rubyists::Kalshi::Series::EventCandlesticks.new(client) + end end end end diff --git a/lib/kalshi/market/client.rb b/lib/kalshi/market/client.rb index 1c41fe3..81c5725 100644 --- a/lib/kalshi/market/client.rb +++ b/lib/kalshi/market/client.rb @@ -32,7 +32,7 @@ def trades end def candlesticks - @candlesticks ||= Candlesticks.new(client) + @candlesticks ||= Rubyists::Kalshi::Series::MarketCandlesticks.new(client) end end end diff --git a/lib/kalshi/series/client.rb b/lib/kalshi/series/client.rb new file mode 100644 index 0000000..8b1bce2 --- /dev/null +++ b/lib/kalshi/series/client.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module Rubyists + module Kalshi + module Series + # Series API Client + class Client < ApiClient + def event_candlesticks + EventCandlesticks.new(client) + end + + def forecast_percentile_history + ForecastPercentileHistory.new(client) + end + end + end + end +end diff --git a/lib/kalshi/series/event_candlesticks.rb b/lib/kalshi/series/event_candlesticks.rb new file mode 100644 index 0000000..bd209b2 --- /dev/null +++ b/lib/kalshi/series/event_candlesticks.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module Rubyists + module Kalshi + module Series + # Event Candlesticks API endpoint + class EventCandlesticks < Kalshi::Endpoint + # Filter for Event Candlesticks + class EventFilter < Kalshi::Contract + propertize(%i[series_ticker ticker start_ts end_ts period_interval]) + + validation do + params do + required(:series_ticker).filled(:string) + required(:ticker).filled(:string) + required(:start_ts).filled(:integer) + required(:end_ts).filled(:integer) + required(:period_interval).filled(:integer) + end + end + end + + def fetch(params) + filter = EventFilter.new(EventFilter::Properties.new(**params)) + raise ArgumentError, filter.errors.full_messages.join(', ') unless filter.validate({}) + + path = "#{filter.series_ticker}/events/#{filter.ticker}/candlesticks" + query_params = filter.to_h.slice('start_ts', 'end_ts', 'period_interval') + client.get(path, params: query_params) + end + end + end + end +end diff --git a/lib/kalshi/series/forecast_percentile_history.rb b/lib/kalshi/series/forecast_percentile_history.rb new file mode 100644 index 0000000..8c272d6 --- /dev/null +++ b/lib/kalshi/series/forecast_percentile_history.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +module Rubyists + module Kalshi + module Series + # Event Forecast Percentile History API endpoint + class ForecastPercentileHistory < Kalshi::Endpoint + # Filter for Event Forecast Percentile History + class EventFilter < Kalshi::Contract + propertize(%i[series_ticker ticker percentiles start_ts end_ts period_interval]) + + validation do + params do + required(:series_ticker).filled(:string) + required(:ticker).filled(:string) + required(:percentiles).filled(:array) + required(:start_ts).filled(:integer) + required(:end_ts).filled(:integer) + required(:period_interval).filled(:integer) + end + end + end + + def fetch(params) + filter = EventFilter.new(EventFilter::Properties.new(**params)) + raise ArgumentError, filter.errors.full_messages.join(', ') unless filter.validate({}) + + path = "#{filter.series_ticker}/events/#{filter.ticker}/forecast_percentile_history" + query_params = filter.to_h.slice('start_ts', 'end_ts', 'period_interval') + query_params[:percentiles] = filter.percentiles.join(',') + client.get(path, params: query_params) + end + end + end + end +end diff --git a/lib/kalshi/market/candlesticks.rb b/lib/kalshi/series/market_candlesticks.rb similarity index 91% rename from lib/kalshi/market/candlesticks.rb rename to lib/kalshi/series/market_candlesticks.rb index f0a1214..20f7b51 100644 --- a/lib/kalshi/market/candlesticks.rb +++ b/lib/kalshi/series/market_candlesticks.rb @@ -2,9 +2,9 @@ module Rubyists module Kalshi - module Market + module Series # Candlesticks API endpoint - class Candlesticks < Kalshi::Endpoint + class MarketCandlesticks < Kalshi::Endpoint def fetch(series_ticker:, ticker:, start_ts:, end_ts:, period_interval:) path = "series/#{series_ticker}/markets/#{ticker}/candlesticks" params = { start_ts:, end_ts:, period_interval: } diff --git a/test/kalshi/client_test.rb b/test/kalshi/client_test.rb index 06eb7ae..a2ce258 100644 --- a/test/kalshi/client_test.rb +++ b/test/kalshi/client_test.rb @@ -70,6 +70,14 @@ assert_instance_of Rubyists::Kalshi::Events::Client, client.events end end + + describe '#series' do + it 'returns a Series::Client instance' do + client = Rubyists::Kalshi::Client.new + + assert_instance_of Rubyists::Kalshi::Series::Client, client.series + end + end end describe Rubyists::Kalshi do diff --git a/test/kalshi/market/candlesticks_test.rb b/test/kalshi/market/candlesticks_test.rb index f849bcb..de599b3 100644 --- a/test/kalshi/market/candlesticks_test.rb +++ b/test/kalshi/market/candlesticks_test.rb @@ -2,9 +2,9 @@ require_relative '../../helper' -describe Rubyists::Kalshi::Market::Candlesticks do +describe Rubyists::Kalshi::Series::MarketCandlesticks do let(:client) { Rubyists::Kalshi::Client.new } - let(:candlesticks) { Rubyists::Kalshi::Market::Candlesticks.new(client) } + let(:candlesticks) { client.market.candlesticks } let(:base_url) { Rubyists::Kalshi.config.base_url } describe '#fetch' do diff --git a/test/kalshi/market/client_test.rb b/test/kalshi/market/client_test.rb index cfc2115..6f0ba7f 100644 --- a/test/kalshi/market/client_test.rb +++ b/test/kalshi/market/client_test.rb @@ -58,7 +58,7 @@ describe '#candlesticks' do it 'returns a Candlesticks instance' do - assert_instance_of Rubyists::Kalshi::Market::Candlesticks, market_client.candlesticks + assert_instance_of Rubyists::Kalshi::Series::MarketCandlesticks, market_client.candlesticks end it 'memoizes the instance' do diff --git a/test/kalshi/series/client_test.rb b/test/kalshi/series/client_test.rb new file mode 100644 index 0000000..66f02b6 --- /dev/null +++ b/test/kalshi/series/client_test.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require_relative '../../helper' + +describe Rubyists::Kalshi::Series::Client do + let(:client) { Rubyists::Kalshi::Client.new } + let(:series_client) { client.series } + let(:base_url) { Rubyists::Kalshi.config.base_url } + + describe '#event_candlesticks' do + it 'returns a EventCandlesticks instance' do + assert_instance_of Rubyists::Kalshi::Series::EventCandlesticks, series_client.event_candlesticks + end + end + + describe '#forecast_percentile_history' do + it 'returns a ForecastPercentileHistory instance' do + assert_instance_of Rubyists::Kalshi::Series::ForecastPercentileHistory, series_client.forecast_percentile_history + end + end +end diff --git a/test/kalshi/series/event_candlesticks_test.rb b/test/kalshi/series/event_candlesticks_test.rb new file mode 100644 index 0000000..ff9079d --- /dev/null +++ b/test/kalshi/series/event_candlesticks_test.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +require_relative '../../helper' + +describe Rubyists::Kalshi::Series::EventCandlesticks do + let(:client) { Rubyists::Kalshi::Client.new } + let(:candlesticks) { client.series.event_candlesticks } + let(:base_url) { Rubyists::Kalshi.config.base_url } + + describe '#fetch' do + it 'fetches event candlesticks' do + series_ticker = 'KX-SERIES' + ticker = 'KX-EVENT' + start_ts = 1_600_000_000 + end_ts = 1_600_000_060 + period_interval = 1 + + stub_request(:get, "#{base_url}/series/#{series_ticker}/events/#{ticker}/candlesticks") + .with(query: { start_ts: start_ts, end_ts: end_ts, period_interval: period_interval }) + .to_return(status: 200, body: '{"market_candlesticks": []}', headers: { 'Content-Type' => 'application/json' }) + + response = candlesticks.fetch( + series_ticker: series_ticker, + ticker: ticker, + start_ts: start_ts, + end_ts: end_ts, + period_interval: period_interval + ) + + assert_equal({ market_candlesticks: [] }, response) + end + + it 'raises ArgumentError for invalid params' do + assert_raises(ArgumentError) do + candlesticks.fetch(series_ticker: 'KX-SERIES') + end + end + end +end diff --git a/test/kalshi/series/forecast_percentile_history_test.rb b/test/kalshi/series/forecast_percentile_history_test.rb new file mode 100644 index 0000000..ce59099 --- /dev/null +++ b/test/kalshi/series/forecast_percentile_history_test.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require_relative '../../helper' + +describe Rubyists::Kalshi::Series::ForecastPercentileHistory do + let(:client) { Rubyists::Kalshi::Client.new } + let(:history) { client.series.forecast_percentile_history } + let(:base_url) { Rubyists::Kalshi.config.base_url } + + describe '#fetch' do + it 'fetches forecast percentile history' do + series_ticker = 'KX-SERIES' + ticker = 'KX-EVENT' + percentiles = [50, 99] + start_ts = 1_600_000_000 + end_ts = 1_600_000_060 + period_interval = 1 + + stub_request(:get, "#{base_url}/series/#{series_ticker}/events/#{ticker}/forecast_percentile_history") + .with(query: { + percentiles: '50,99', + start_ts: start_ts, + end_ts: end_ts, + period_interval: period_interval + }) + .to_return(status: 200, body: '{"forecast_history": []}', headers: { 'Content-Type' => 'application/json' }) + + response = history.fetch( + series_ticker: series_ticker, + ticker: ticker, + percentiles: percentiles, + start_ts: start_ts, + end_ts: end_ts, + period_interval: period_interval + ) + + assert_equal({ forecast_history: [] }, response) + end + + it 'raises ArgumentError for invalid params' do + assert_raises(ArgumentError) do + history.fetch(series_ticker: 'KX-SERIES') + end + end + end +end From 0239da3c3c69ec4e2d38bc149bf9862a519bf11f Mon Sep 17 00:00:00 2001 From: "Tj (bougyman) Vanderpoel" Date: Mon, 5 Jan 2026 13:14:26 -0600 Subject: [PATCH 3/3] feat: adds series path and events lookup --- lib/kalshi/client.rb | 4 ++ lib/kalshi/events/client.rb | 4 ++ lib/kalshi/market/client.rb | 2 +- lib/kalshi/series/client.rb | 18 ++++++++ lib/kalshi/series/event_candlesticks.rb | 34 ++++++++++++++ .../series/forecast_percentile_history.rb | 36 +++++++++++++++ .../market_candlesticks.rb} | 4 +- test/kalshi/client_test.rb | 8 ++++ test/kalshi/market/candlesticks_test.rb | 4 +- test/kalshi/market/client_test.rb | 2 +- test/kalshi/series/client_test.rb | 21 +++++++++ test/kalshi/series/event_candlesticks_test.rb | 39 ++++++++++++++++ .../forecast_percentile_history_test.rb | 46 +++++++++++++++++++ 13 files changed, 216 insertions(+), 6 deletions(-) create mode 100644 lib/kalshi/series/client.rb create mode 100644 lib/kalshi/series/event_candlesticks.rb create mode 100644 lib/kalshi/series/forecast_percentile_history.rb rename lib/kalshi/{market/candlesticks.rb => series/market_candlesticks.rb} (91%) create mode 100644 test/kalshi/series/client_test.rb create mode 100644 test/kalshi/series/event_candlesticks_test.rb create mode 100644 test/kalshi/series/forecast_percentile_history_test.rb diff --git a/lib/kalshi/client.rb b/lib/kalshi/client.rb index 05cf35d..0d6a0a7 100644 --- a/lib/kalshi/client.rb +++ b/lib/kalshi/client.rb @@ -61,6 +61,10 @@ def events @events ||= Events::Client.new(clone) end + def series + @series ||= Series::Client.new(clone) + end + private def full_url(path) diff --git a/lib/kalshi/events/client.rb b/lib/kalshi/events/client.rb index 45e0e0d..aa1135b 100644 --- a/lib/kalshi/events/client.rb +++ b/lib/kalshi/events/client.rb @@ -20,6 +20,10 @@ def metadata(...) def multivariate Multivariate.new(client) end + + def candlesticks + Rubyists::Kalshi::Series::EventCandlesticks.new(client) + end end end end diff --git a/lib/kalshi/market/client.rb b/lib/kalshi/market/client.rb index 1c41fe3..81c5725 100644 --- a/lib/kalshi/market/client.rb +++ b/lib/kalshi/market/client.rb @@ -32,7 +32,7 @@ def trades end def candlesticks - @candlesticks ||= Candlesticks.new(client) + @candlesticks ||= Rubyists::Kalshi::Series::MarketCandlesticks.new(client) end end end diff --git a/lib/kalshi/series/client.rb b/lib/kalshi/series/client.rb new file mode 100644 index 0000000..8b1bce2 --- /dev/null +++ b/lib/kalshi/series/client.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module Rubyists + module Kalshi + module Series + # Series API Client + class Client < ApiClient + def event_candlesticks + EventCandlesticks.new(client) + end + + def forecast_percentile_history + ForecastPercentileHistory.new(client) + end + end + end + end +end diff --git a/lib/kalshi/series/event_candlesticks.rb b/lib/kalshi/series/event_candlesticks.rb new file mode 100644 index 0000000..bd209b2 --- /dev/null +++ b/lib/kalshi/series/event_candlesticks.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module Rubyists + module Kalshi + module Series + # Event Candlesticks API endpoint + class EventCandlesticks < Kalshi::Endpoint + # Filter for Event Candlesticks + class EventFilter < Kalshi::Contract + propertize(%i[series_ticker ticker start_ts end_ts period_interval]) + + validation do + params do + required(:series_ticker).filled(:string) + required(:ticker).filled(:string) + required(:start_ts).filled(:integer) + required(:end_ts).filled(:integer) + required(:period_interval).filled(:integer) + end + end + end + + def fetch(params) + filter = EventFilter.new(EventFilter::Properties.new(**params)) + raise ArgumentError, filter.errors.full_messages.join(', ') unless filter.validate({}) + + path = "#{filter.series_ticker}/events/#{filter.ticker}/candlesticks" + query_params = filter.to_h.slice('start_ts', 'end_ts', 'period_interval') + client.get(path, params: query_params) + end + end + end + end +end diff --git a/lib/kalshi/series/forecast_percentile_history.rb b/lib/kalshi/series/forecast_percentile_history.rb new file mode 100644 index 0000000..8c272d6 --- /dev/null +++ b/lib/kalshi/series/forecast_percentile_history.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +module Rubyists + module Kalshi + module Series + # Event Forecast Percentile History API endpoint + class ForecastPercentileHistory < Kalshi::Endpoint + # Filter for Event Forecast Percentile History + class EventFilter < Kalshi::Contract + propertize(%i[series_ticker ticker percentiles start_ts end_ts period_interval]) + + validation do + params do + required(:series_ticker).filled(:string) + required(:ticker).filled(:string) + required(:percentiles).filled(:array) + required(:start_ts).filled(:integer) + required(:end_ts).filled(:integer) + required(:period_interval).filled(:integer) + end + end + end + + def fetch(params) + filter = EventFilter.new(EventFilter::Properties.new(**params)) + raise ArgumentError, filter.errors.full_messages.join(', ') unless filter.validate({}) + + path = "#{filter.series_ticker}/events/#{filter.ticker}/forecast_percentile_history" + query_params = filter.to_h.slice('start_ts', 'end_ts', 'period_interval') + query_params[:percentiles] = filter.percentiles.join(',') + client.get(path, params: query_params) + end + end + end + end +end diff --git a/lib/kalshi/market/candlesticks.rb b/lib/kalshi/series/market_candlesticks.rb similarity index 91% rename from lib/kalshi/market/candlesticks.rb rename to lib/kalshi/series/market_candlesticks.rb index f0a1214..20f7b51 100644 --- a/lib/kalshi/market/candlesticks.rb +++ b/lib/kalshi/series/market_candlesticks.rb @@ -2,9 +2,9 @@ module Rubyists module Kalshi - module Market + module Series # Candlesticks API endpoint - class Candlesticks < Kalshi::Endpoint + class MarketCandlesticks < Kalshi::Endpoint def fetch(series_ticker:, ticker:, start_ts:, end_ts:, period_interval:) path = "series/#{series_ticker}/markets/#{ticker}/candlesticks" params = { start_ts:, end_ts:, period_interval: } diff --git a/test/kalshi/client_test.rb b/test/kalshi/client_test.rb index 06eb7ae..a2ce258 100644 --- a/test/kalshi/client_test.rb +++ b/test/kalshi/client_test.rb @@ -70,6 +70,14 @@ assert_instance_of Rubyists::Kalshi::Events::Client, client.events end end + + describe '#series' do + it 'returns a Series::Client instance' do + client = Rubyists::Kalshi::Client.new + + assert_instance_of Rubyists::Kalshi::Series::Client, client.series + end + end end describe Rubyists::Kalshi do diff --git a/test/kalshi/market/candlesticks_test.rb b/test/kalshi/market/candlesticks_test.rb index f849bcb..de599b3 100644 --- a/test/kalshi/market/candlesticks_test.rb +++ b/test/kalshi/market/candlesticks_test.rb @@ -2,9 +2,9 @@ require_relative '../../helper' -describe Rubyists::Kalshi::Market::Candlesticks do +describe Rubyists::Kalshi::Series::MarketCandlesticks do let(:client) { Rubyists::Kalshi::Client.new } - let(:candlesticks) { Rubyists::Kalshi::Market::Candlesticks.new(client) } + let(:candlesticks) { client.market.candlesticks } let(:base_url) { Rubyists::Kalshi.config.base_url } describe '#fetch' do diff --git a/test/kalshi/market/client_test.rb b/test/kalshi/market/client_test.rb index cfc2115..6f0ba7f 100644 --- a/test/kalshi/market/client_test.rb +++ b/test/kalshi/market/client_test.rb @@ -58,7 +58,7 @@ describe '#candlesticks' do it 'returns a Candlesticks instance' do - assert_instance_of Rubyists::Kalshi::Market::Candlesticks, market_client.candlesticks + assert_instance_of Rubyists::Kalshi::Series::MarketCandlesticks, market_client.candlesticks end it 'memoizes the instance' do diff --git a/test/kalshi/series/client_test.rb b/test/kalshi/series/client_test.rb new file mode 100644 index 0000000..66f02b6 --- /dev/null +++ b/test/kalshi/series/client_test.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require_relative '../../helper' + +describe Rubyists::Kalshi::Series::Client do + let(:client) { Rubyists::Kalshi::Client.new } + let(:series_client) { client.series } + let(:base_url) { Rubyists::Kalshi.config.base_url } + + describe '#event_candlesticks' do + it 'returns a EventCandlesticks instance' do + assert_instance_of Rubyists::Kalshi::Series::EventCandlesticks, series_client.event_candlesticks + end + end + + describe '#forecast_percentile_history' do + it 'returns a ForecastPercentileHistory instance' do + assert_instance_of Rubyists::Kalshi::Series::ForecastPercentileHistory, series_client.forecast_percentile_history + end + end +end diff --git a/test/kalshi/series/event_candlesticks_test.rb b/test/kalshi/series/event_candlesticks_test.rb new file mode 100644 index 0000000..ff9079d --- /dev/null +++ b/test/kalshi/series/event_candlesticks_test.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +require_relative '../../helper' + +describe Rubyists::Kalshi::Series::EventCandlesticks do + let(:client) { Rubyists::Kalshi::Client.new } + let(:candlesticks) { client.series.event_candlesticks } + let(:base_url) { Rubyists::Kalshi.config.base_url } + + describe '#fetch' do + it 'fetches event candlesticks' do + series_ticker = 'KX-SERIES' + ticker = 'KX-EVENT' + start_ts = 1_600_000_000 + end_ts = 1_600_000_060 + period_interval = 1 + + stub_request(:get, "#{base_url}/series/#{series_ticker}/events/#{ticker}/candlesticks") + .with(query: { start_ts: start_ts, end_ts: end_ts, period_interval: period_interval }) + .to_return(status: 200, body: '{"market_candlesticks": []}', headers: { 'Content-Type' => 'application/json' }) + + response = candlesticks.fetch( + series_ticker: series_ticker, + ticker: ticker, + start_ts: start_ts, + end_ts: end_ts, + period_interval: period_interval + ) + + assert_equal({ market_candlesticks: [] }, response) + end + + it 'raises ArgumentError for invalid params' do + assert_raises(ArgumentError) do + candlesticks.fetch(series_ticker: 'KX-SERIES') + end + end + end +end diff --git a/test/kalshi/series/forecast_percentile_history_test.rb b/test/kalshi/series/forecast_percentile_history_test.rb new file mode 100644 index 0000000..ce59099 --- /dev/null +++ b/test/kalshi/series/forecast_percentile_history_test.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require_relative '../../helper' + +describe Rubyists::Kalshi::Series::ForecastPercentileHistory do + let(:client) { Rubyists::Kalshi::Client.new } + let(:history) { client.series.forecast_percentile_history } + let(:base_url) { Rubyists::Kalshi.config.base_url } + + describe '#fetch' do + it 'fetches forecast percentile history' do + series_ticker = 'KX-SERIES' + ticker = 'KX-EVENT' + percentiles = [50, 99] + start_ts = 1_600_000_000 + end_ts = 1_600_000_060 + period_interval = 1 + + stub_request(:get, "#{base_url}/series/#{series_ticker}/events/#{ticker}/forecast_percentile_history") + .with(query: { + percentiles: '50,99', + start_ts: start_ts, + end_ts: end_ts, + period_interval: period_interval + }) + .to_return(status: 200, body: '{"forecast_history": []}', headers: { 'Content-Type' => 'application/json' }) + + response = history.fetch( + series_ticker: series_ticker, + ticker: ticker, + percentiles: percentiles, + start_ts: start_ts, + end_ts: end_ts, + period_interval: period_interval + ) + + assert_equal({ forecast_history: [] }, response) + end + + it 'raises ArgumentError for invalid params' do + assert_raises(ArgumentError) do + history.fetch(series_ticker: 'KX-SERIES') + end + end + end +end