Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
14 changes: 14 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,18 @@ set(POLYMARKET_CLIENT_SOURCES
src/evm_reorg_reconciliation.cpp
src/evm_utils.cpp
src/gamma_market_fetcher.cpp
src/data_analytics_client.cpp
src/data_analytics_parsing.cpp
src/data_analytics_query.cpp
src/data_client.cpp
src/data_leaderboard_client.cpp
src/data_leaderboard_parsing.cpp
src/data_leaderboard_query.cpp
src/data_parsing.cpp
src/data_portfolio_client.cpp
src/data_portfolio_parsing.cpp
src/data_portfolio_query.cpp
src/data_query.cpp
src/gamma_catalog_client.cpp
src/gamma_catalog_parsing.cpp
src/gamma_catalog_query.cpp
Expand All @@ -123,6 +135,8 @@ set(POLYMARKET_CLIENT_SOURCES
src/gamma_query.cpp
src/gamma_social_client.cpp
src/gamma_social_parsing.cpp
src/json_fields.cpp
src/query_builder.cpp
src/geoblock.cpp
src/http_client.cpp
src/http_client_connection.cpp
Expand Down
36 changes: 36 additions & 0 deletions cmake/PolymarketTests.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,42 @@ if(POLYMARKET_CLIENT_BUILD_TESTS)
target_link_libraries(test_gamma_social PRIVATE polymarket::client)
add_test(NAME test_gamma_social COMMAND test_gamma_social)

add_executable(test_data_parsing tests/test_data_parsing.cpp)
target_include_directories(test_data_parsing PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
target_link_libraries(test_data_parsing PRIVATE polymarket::client)
add_test(NAME test_data_parsing COMMAND test_data_parsing)

add_executable(test_data_client tests/test_data_client.cpp)
target_link_libraries(test_data_client PRIVATE polymarket::client)
add_test(NAME test_data_client COMMAND test_data_client)

add_executable(test_data_portfolio_parsing tests/test_data_portfolio_parsing.cpp)
target_include_directories(test_data_portfolio_parsing PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
target_link_libraries(test_data_portfolio_parsing PRIVATE polymarket::client)
add_test(NAME test_data_portfolio_parsing COMMAND test_data_portfolio_parsing)

add_executable(test_data_portfolio_client tests/test_data_portfolio_client.cpp)
target_link_libraries(test_data_portfolio_client PRIVATE polymarket::client)
add_test(NAME test_data_portfolio_client COMMAND test_data_portfolio_client)

add_executable(test_data_analytics_parsing tests/test_data_analytics_parsing.cpp)
target_include_directories(test_data_analytics_parsing PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
target_link_libraries(test_data_analytics_parsing PRIVATE polymarket::client)
add_test(NAME test_data_analytics_parsing COMMAND test_data_analytics_parsing)

add_executable(test_data_analytics_client tests/test_data_analytics_client.cpp)
target_link_libraries(test_data_analytics_client PRIVATE polymarket::client)
add_test(NAME test_data_analytics_client COMMAND test_data_analytics_client)

add_executable(test_data_leaderboard_parsing tests/test_data_leaderboard_parsing.cpp)
target_include_directories(test_data_leaderboard_parsing PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
target_link_libraries(test_data_leaderboard_parsing PRIVATE polymarket::client)
add_test(NAME test_data_leaderboard_parsing COMMAND test_data_leaderboard_parsing)

add_executable(test_data_leaderboard_client tests/test_data_leaderboard_client.cpp)
target_link_libraries(test_data_leaderboard_client PRIVATE polymarket::client)
add_test(NAME test_data_leaderboard_client COMMAND test_data_leaderboard_client)

add_executable(test_clob_pagers tests/test_clob_pagers.cpp)
target_link_libraries(test_clob_pagers PRIVATE polymarket::client)
add_test(NAME test_clob_pagers COMMAND test_clob_pagers)
Expand Down
113 changes: 113 additions & 0 deletions include/polymarket/data_analytics_types.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
#pragma once

#include <nlohmann/json.hpp>

#include <cstdint>
#include <optional>
#include <string>
#include <utility>
#include <vector>

// Data API (/v2) market analytics types, following the official py-sdk models.
// Units and the empty and unset rules are those of data_types.hpp, and `raw`
// holds the whole object as received.
namespace polymarket
{
// A holder of one outcome. The PnL fields are set only when holders are
// requested with include_pnl.
struct DataHolder
{
std::string wallet; // proxy_wallet
std::string asset_id; // CLOB token ID
double amount{0.0}; // shares
std::optional<int> outcome_index;
bool display_username_public{false};
bool verified{false};
std::string name;
std::string pseudonym;
std::string bio;
std::string profile_image;
std::string profile_image_optimized;
std::optional<double> avg_price;
std::optional<double> entry_cost_usdc;
std::optional<double> current_price;
std::optional<double> current_value;
std::optional<double> realized_pnl;
std::optional<double> unrealized_pnl;
std::optional<double> total_pnl;
nlohmann::json raw;
};

// Holders of one outcome asset. A page holds up to the page size per
// outcome, so one asset can span pages; merge groups by `asset_id`.
struct DataAssetHolders
{
std::string asset_id;
std::vector<DataHolder> holders;
};

// Open interest in USDC; an empty `condition_id` is global interest.
struct DataOpenInterest
{
std::string condition_id;
double value{0.0};
nlohmann::json raw;
};

struct DataMarketLiveVolume
{
std::string condition_id; // may be empty
double taker_volume{0.0}; // shares
};

struct DataLiveVolume
{
double taker_volume_total{0.0}; // shares
std::vector<DataMarketLiveVolume> markets;
nlohmann::json raw;
};

// A price in USDC per share; a resolution of 0 is an exact tick.
struct DataPricePoint
{
std::int64_t timestamp{0};
double price{0.0};
std::int64_t resolution_seconds{0};
};

// A market resolution. Times are Unix seconds. `status` is initialized,
// posed, proposed, challenged, reproposed, disputed, active, arbitration
// or resolved; `settlement_time_basis` is managed_proposal_expiration,
// proposal_expiration, liveness or dvm_round_estimate; `market_type` is
// BINARY, INCREMENTAL_NEGRISK or ATOMIC_NEGRISK; `resolution_source` is
// reported or derived; `reporter` is UMA_OO, CHAINLINK or EOA. Oracle
// prices are as reported, with the oracle's "no price" (69) unset, and
// `payouts` are USDC per share for outcomes 0 and 1.
struct DataResolution
{
std::string question_id;
std::string condition_id;
std::string status;
bool extended_review{false};
// Earliest estimated settlement time, not a deadline; it may have passed.
std::optional<std::int64_t> expected_settlement_time;
std::string settlement_time_basis;
bool was_disputed{false};
bool question_rules_updated{false}; // new_version_q
std::optional<double> proposed_price;
std::optional<double> reproposed_price;
std::optional<double> price;
std::string transaction_hash;
std::optional<std::int64_t> log_index;
std::int64_t last_updated_at{0};
std::string market_type;
std::optional<std::pair<double, double>> payouts;
std::string resolution_source;
std::string reporter;
std::optional<bool> was_arbitrated;
std::optional<std::int64_t> resolved_block;
std::optional<std::int64_t> resolved_at;
nlohmann::json raw;
};

} // namespace polymarket
116 changes: 116 additions & 0 deletions include/polymarket/data_client.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
#pragma once

#include "polymarket/data_analytics_types.hpp"
#include "polymarket/data_leaderboard_types.hpp"
#include "polymarket/data_portfolio_types.hpp"
#include "polymarket/data_requests.hpp"
#include "polymarket/data_types.hpp"
#include "polymarket/environment.hpp"
#include "polymarket/http_client.hpp"
#include "polymarket/pagination.hpp"
#include "polymarket/rate_limit.hpp"
#include "polymarket/sdk_error.hpp"

#include <cstddef>
#include <cstdint>
#include <mutex>
#include <optional>
#include <string>
#include <vector>

namespace polymarket
{
// Typed client for the Data API v2 (Environment::data_url). Calls may run
// from several threads; requests on one client are serialized. Invalid
// arguments fail with InvalidArgument before any request. Pagers page by
// server cursor, at most 1000 rows per page unless noted, and borrow this
// client, which must outlive them.
class DataClient
{
public:
explicit DataClient(Environment environment = Environment::production(),
const std::optional<HttpClientOptions> &http_options = std::nullopt);

DataClient(const DataClient &) = delete;
DataClient &operator=(const DataClient &) = delete;

// Trades across Polymarket, or one wallet's with `filter.user`.
Paginator<DataTrade> trades_pager(const DataTradeFilter &filter = {},
std::size_t page_size = 100,
const PageRequest &resume = {});
// A wallet's activity, deposits and withdrawals included.
Paginator<DataActivity> activity_pager(const std::string &user,
const DataActivityFilter &filter = {},
std::size_t page_size = 100,
const PageRequest &resume = {});

Paginator<DataPosition> positions_pager(const DataPositionFilter &filter,
std::size_t page_size = 100,
const PageRequest &resume = {});
// Condition IDs follow DataPositionFilter's rules.
Result<DataPortfolioValue>
get_portfolio_value(const std::string &user,
const std::vector<std::string> &condition_ids = {});
// std::nullopt when the Data API has no stats for the wallet.
Result<std::optional<DataUserStats>> get_user_stats(const std::string &user);
// `interval` and `fidelity` are the DataPnlSeries values, or "" for the
// server default.
Result<DataPnlSeries> get_user_pnl(const std::string &user,
const std::string &interval = "",
const std::string &fidelity = "");
Result<DataUserVolume> get_user_volume(const std::string &user,
const DataTimeRange &range = {});
// The wallet's accounting snapshot as zip archive bytes.
Result<std::string> download_accounting_snapshot(const std::string &user);

// Holders of the markets' outcomes. `condition_ids` is required and
// follows DataPositionFilter's rules; `page_size` applies per outcome.
Paginator<DataAssetHolders> holders_pager(const std::vector<std::string> &condition_ids,
const DataHolderFilter &filter = {},
std::size_t page_size = 100,
const PageRequest &resume = {});
// Global open interest when `condition_ids` is empty.
Result<std::vector<DataOpenInterest>>
get_open_interests(const std::vector<std::string> &condition_ids = {});
// Combined taker volume of the events, which are required.
Result<DataLiveVolume> get_event_live_volume(const std::vector<std::int64_t> &event_ids);
// Prices oldest first, at most 10000 per page.
Paginator<DataPricePoint> price_history_pager(const std::string &asset_id,
const DataPriceHistoryFilter &filter,
std::size_t page_size = 10000,
const PageRequest &resume = {});
// Rows the Data API does not know are left out.
Result<std::vector<DataResolution>> get_resolutions(const DataResolutionFilter &filter);

Paginator<DataTraderEntry>
trader_leaderboard_pager(const DataLeaderboardFilter &filter = {},
std::size_t page_size = 100, const PageRequest &resume = {});
// std::nullopt when the trader is not on the leaderboard.
Result<std::optional<DataTraderStanding>>
get_trader_standing(const std::string &user, const DataLeaderboardFilter &filter = {});
Paginator<DataBiggestWinner> biggest_winners_pager(const DataLeaderboardFilter &filter = {},
std::size_t page_size = 100,
const PageRequest &resume = {});

// Reads that get HTTP 429 are retried after the server's Retry-After
// delay. Defaults to RateLimitRetry{}; std::nullopt disables retries.
void set_rate_limit_retry(std::optional<RateLimitRetry> policy);

const Environment &environment() const { return environment_; }

private:
Environment environment_;
HttpClient http_;
mutable std::mutex rate_limit_mutex_;
std::optional<RateLimitRetry> rate_limit_retry_{RateLimitRetry{}};

HttpResponse get(const std::string &path);
template <typename T, typename BuildQuery, typename ParsePage>
Paginator<T> cursor_pager(const char *endpoint, std::size_t page_size,
std::size_t max_page_size, const PageRequest &resume,
BuildQuery build_query, ParsePage parse_page);
template <typename T, typename BuildPath, typename Parse>
Result<T> fetch(BuildPath build_path, Parse parse);
};

} // namespace polymarket
66 changes: 66 additions & 0 deletions include/polymarket/data_leaderboard_types.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#pragma once

#include <nlohmann/json.hpp>

#include <cstdint>
#include <optional>
#include <string>

// Data API (/v2) leaderboard types, following the official py-sdk models. PnL
// and values are USDC and volume is shares. The empty and unset rules are
// those of data_types.hpp, and `raw` holds the whole object as received.
namespace polymarket
{
// A ranked trader. Ranks can tie and skip.
struct DataTraderEntry
{
std::int64_t rank{0};
std::string wallet; // user_id
double pnl{0.0};
double volume{0.0};
std::string user_name;
std::string profile_image;
std::string x_username;
bool verified{false};
nlohmann::json raw;
};

// One trader's ranks; a rank is unset when the trader is unranked.
struct DataTraderStanding
{
std::string wallet; // user_id
double pnl{0.0};
double volume{0.0};
std::optional<std::int64_t> pnl_rank;
std::optional<std::int64_t> volume_rank;
std::string user_name;
std::string profile_image;
std::string x_username;
bool verified{false};
nlohmann::json raw;
};

// A winning position. `kind` is "market" or "combo". A market winner has
// its condition ID, outcome asset ID and event. A combo winner has the
// common fields, a combo condition ID in its 31-byte form, lowercased, and
// its position ID in `asset_id`; its event fields are empty.
struct DataBiggestWinner
{
std::int64_t rank{0}; // win_rank
std::string kind;
std::string wallet; // user_id
double pnl{0.0};
double initial_value{0.0};
double final_value{0.0};
std::int64_t resolved_at{0}; // Unix seconds
std::string user_name;
std::string profile_image;
std::string event_title;
std::string condition_id;
std::string asset_id; // position_id
std::string event_id;
std::string event_slug;
nlohmann::json raw;
};

} // namespace polymarket
Loading
Loading