diff --git a/src/commands/events.rs b/src/commands/events.rs index d001210..040ca76 100644 --- a/src/commands/events.rs +++ b/src/commands/events.rs @@ -1,9 +1,11 @@ use anyhow::Result; +use chrono::{DateTime, Utc}; use clap::{Args, Subcommand}; use polymarket_client_sdk::gamma::{ self, types::request::{EventByIdRequest, EventBySlugRequest, EventTagsRequest, EventsRequest}, }; +use rust_decimal::Decimal; use super::is_numeric_id; use crate::output::OutputFormat; @@ -47,6 +49,38 @@ pub enum EventsCommand { /// Filter by tag slug (e.g. "politics", "crypto") #[arg(long)] tag: Option, + + /// Minimum trading volume (e.g. 1000000) + #[arg(long)] + volume_min: Option, + + /// Maximum trading volume + #[arg(long)] + volume_max: Option, + + /// Minimum liquidity + #[arg(long)] + liquidity_min: Option, + + /// Maximum liquidity + #[arg(long)] + liquidity_max: Option, + + /// Only events starting after this date (e.g. 2026-03-01T00:00:00Z) + #[arg(long)] + start_date_min: Option>, + + /// Only events starting before this date + #[arg(long)] + start_date_max: Option>, + + /// Only events ending after this date + #[arg(long)] + end_date_min: Option>, + + /// Only events ending before this date + #[arg(long)] + end_date_max: Option>, }, /// Get a single event by ID or slug @@ -72,6 +106,14 @@ pub async fn execute(client: &gamma::Client, args: EventsArgs, output: OutputFor order, ascending, tag, + volume_min, + volume_max, + liquidity_min, + liquidity_max, + start_date_min, + start_date_max, + end_date_min, + end_date_max, } => { let resolved_closed = closed.or_else(|| active.map(|a| !a)); @@ -83,6 +125,14 @@ pub async fn execute(client: &gamma::Client, args: EventsArgs, output: OutputFor .maybe_tag_slug(tag) // EventsRequest::order is Vec; into_iter on Option yields 0 or 1 items. .order(order.into_iter().collect()) + .maybe_volume_min(volume_min) + .maybe_volume_max(volume_max) + .maybe_liquidity_min(liquidity_min) + .maybe_liquidity_max(liquidity_max) + .maybe_start_date_min(start_date_min) + .maybe_start_date_max(start_date_max) + .maybe_end_date_min(end_date_min) + .maybe_end_date_max(end_date_max) .build(); let events = client.events(&request).await?; diff --git a/src/commands/markets.rs b/src/commands/markets.rs index 2544d18..67f6e63 100644 --- a/src/commands/markets.rs +++ b/src/commands/markets.rs @@ -1,4 +1,5 @@ use anyhow::Result; +use chrono::{DateTime, Utc}; use clap::{Args, Subcommand}; use polymarket_client_sdk::gamma::{ self, @@ -10,6 +11,7 @@ use polymarket_client_sdk::gamma::{ response::Market, }, }; +use rust_decimal::Decimal; use super::is_numeric_id; use crate::output::OutputFormat; @@ -49,6 +51,42 @@ pub enum MarketsCommand { /// Sort ascending instead of descending #[arg(long)] ascending: bool, + + /// Minimum trading volume (e.g. 1000000) + #[arg(long)] + volume_min: Option, + + /// Maximum trading volume + #[arg(long)] + volume_max: Option, + + /// Minimum liquidity + #[arg(long)] + liquidity_min: Option, + + /// Maximum liquidity + #[arg(long)] + liquidity_max: Option, + + /// Only markets starting after this date (e.g. 2026-03-01T00:00:00Z) + #[arg(long)] + start_date_min: Option>, + + /// Only markets starting before this date + #[arg(long)] + start_date_max: Option>, + + /// Only markets ending after this date + #[arg(long)] + end_date_min: Option>, + + /// Only markets ending before this date + #[arg(long)] + end_date_max: Option>, + + /// Filter by tag ID + #[arg(long)] + tag: Option, }, /// Get a single market by ID or slug @@ -87,6 +125,15 @@ pub async fn execute( offset, order, ascending, + volume_min, + volume_max, + liquidity_min, + liquidity_max, + start_date_min, + start_date_max, + end_date_min, + end_date_max, + tag, } => { let resolved_closed = closed.or_else(|| active.map(|a| !a)); @@ -96,6 +143,15 @@ pub async fn execute( .maybe_offset(offset) .maybe_order(order) .ascending(ascending) + .maybe_volume_num_min(volume_min) + .maybe_volume_num_max(volume_max) + .maybe_liquidity_num_min(liquidity_min) + .maybe_liquidity_num_max(liquidity_max) + .maybe_start_date_min(start_date_min) + .maybe_start_date_max(start_date_max) + .maybe_end_date_min(end_date_min) + .maybe_end_date_max(end_date_max) + .maybe_tag_id(tag) .build(); let markets = client.markets(&request).await?; diff --git a/src/main.rs b/src/main.rs index 2abb55f..6ec0c67 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,6 +26,10 @@ pub(crate) struct Cli { /// Signature type: eoa, proxy, or gnosis-safe #[arg(long, global = true)] signature_type: Option, + + /// Comma-separated list of fields to include in JSON output (e.g. question,volume_num,slug) + #[arg(long, global = true, value_delimiter = ',')] + fields: Option>, } #[derive(Subcommand)] @@ -81,6 +85,9 @@ async fn main() -> ExitCode { #[allow(clippy::too_many_lines)] pub(crate) async fn run(cli: Cli) -> anyhow::Result<()> { + // set (or clear) the JSON field filter for this invocation + output::set_json_fields(cli.fields); + // Lazy-init so we only pay for the client we actually use. let gamma = std::cell::LazyCell::new(polymarket_client_sdk::gamma::Client::default); let data = std::cell::LazyCell::new(polymarket_client_sdk::data::Client::default); diff --git a/src/output/mod.rs b/src/output/mod.rs index 05de836..75d55ce 100644 --- a/src/output/mod.rs +++ b/src/output/mod.rs @@ -11,13 +11,25 @@ pub(crate) mod series; pub(crate) mod sports; pub(crate) mod tags; +use std::sync::RwLock; + use chrono::{DateTime, Utc}; use polymarket_client_sdk::types::Decimal; use rust_decimal::prelude::ToPrimitive; +use serde_json::Value; use tabled::Table; use tabled::settings::object::Columns; use tabled::settings::{Modify, Style, Width}; +/// field names to keep in JSON output; updated per invocation via --fields +static JSON_FIELDS: RwLock>> = RwLock::new(None); + +pub(crate) fn set_json_fields(fields: Option>) { + if let Ok(mut guard) = JSON_FIELDS.write() { + *guard = fields; + } +} + pub(crate) const DASH: &str = "—"; #[derive(Clone, Copy, Debug, clap::ValueEnum)] @@ -63,10 +75,37 @@ pub(crate) fn active_status(closed: Option, active: Option) -> &'sta } pub(crate) fn print_json(data: &(impl serde::Serialize + ?Sized)) -> anyhow::Result<()> { - println!("{}", serde_json::to_string_pretty(data)?); + let guard = JSON_FIELDS.read().unwrap(); + if let Some(fields) = guard.as_deref() { + // only convert to Value when filtering — preserves key order in the common case + let value = serde_json::to_value(data)?; + println!( + "{}", + serde_json::to_string_pretty(&filter_fields(value, fields))? + ); + } else { + println!("{}", serde_json::to_string_pretty(data)?); + } Ok(()) } +/// keep only the requested keys from an object or each object in an array +fn filter_fields(value: Value, fields: &[String]) -> Value { + match value { + Value::Array(arr) => { + Value::Array(arr.into_iter().map(|v| filter_fields(v, fields)).collect()) + } + Value::Object(map) => { + let filtered = map + .into_iter() + .filter(|(k, _)| fields.iter().any(|f| f == k)) + .collect(); + Value::Object(filtered) + } + other => other, + } +} + pub(crate) fn print_error(error: &anyhow::Error, format: OutputFormat) { match format { OutputFormat::Json => { @@ -185,4 +224,36 @@ mod tests { fn format_decimal_just_below_million_uses_k() { assert_eq!(format_decimal(dec!(999_999)), "$1000.0K"); } + + #[test] + fn filter_fields_keeps_only_requested_keys() { + let obj = serde_json::json!({"a": 1, "b": 2, "c": 3}); + let fields = vec!["a".into(), "c".into()]; + let result = filter_fields(obj, &fields); + assert_eq!(result, serde_json::json!({"a": 1, "c": 3})); + } + + #[test] + fn filter_fields_applies_to_each_array_element() { + let arr = serde_json::json!([{"a": 1, "b": 2}, {"a": 3, "b": 4}]); + let fields = vec!["a".into()]; + let result = filter_fields(arr, &fields); + assert_eq!(result, serde_json::json!([{"a": 1}, {"a": 3}])); + } + + #[test] + fn filter_fields_returns_empty_object_when_no_match() { + let obj = serde_json::json!({"a": 1, "b": 2}); + let fields = vec!["z".into()]; + let result = filter_fields(obj, &fields); + assert_eq!(result, serde_json::json!({})); + } + + #[test] + fn filter_fields_passes_through_non_object_values() { + let val = serde_json::json!(42); + let fields = vec!["a".into()]; + let result = filter_fields(val, &fields); + assert_eq!(result, serde_json::json!(42)); + } }