agent-finance-cli 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "agent-finance-cli"
3
- version = "0.1.2"
3
+ version = "0.1.3"
4
4
  edition = "2024"
5
5
  license = "MIT OR Apache-2.0"
6
6
  repository = "https://github.com/M4n5ter/agent-finance-cli"
@@ -30,3 +30,4 @@ scraper = "0.27.0"
30
30
  wreq = { version = "6.0.0-rc.29", default-features = false, features = ["cookies", "deflate", "gzip", "json", "socks", "system-proxy", "tokio-rt", "webpki-roots"] }
31
31
  wreq-util = "3.0.0-rc.12"
32
32
  iana-time-zone = "0.1"
33
+ polymarket_client_sdk_v2 = { version = "*", features = ["clob", "gamma", "data"] }
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  AI Agent-first CLI for no-key financial market data and research context.
4
4
 
5
- `agent-finance` is designed for human-operated AI agents: the CLI can print its own task-specific skills, then provide current quotes, regular/pre/post/overnight session splits, OHLCV history, local indicators, proxy futures data, no-key research payloads, URL text extraction, provider capability notes, polling, and Yahoo WebSocket streams.
5
+ `agent-finance` is designed for human-operated AI agents: the CLI can print its own task-specific skills, then provide current quotes, regular/pre/post/overnight session splits, OHLCV history, local indicators, proxy futures data, prediction-market sentiment, no-key research payloads, URL text extraction, provider capability notes, polling, and Yahoo WebSocket streams.
6
6
 
7
7
  If you are an AI Agent, start here:
8
8
 
@@ -69,6 +69,10 @@ agent-finance read-url "https://www.sec.gov/Archives/edgar/data/0001807794/00016
69
69
  agent-finance search "optical interconnect"
70
70
  agent-finance screen day_gainers
71
71
  agent-finance providers
72
+
73
+ # Prediction-market sentiment and event probabilities.
74
+ agent-finance polymarket search "spacex ipo" --limit 5
75
+ agent-finance polymarket market MARKET_ID_OR_SLUG --json
72
76
  ```
73
77
 
74
78
  ## Agent Skills
@@ -81,6 +85,7 @@ agent-finance skills get core --full
81
85
  agent-finance skills get price
82
86
  agent-finance skills get research-data
83
87
  agent-finance skills get providers
88
+ agent-finance skills get prediction-markets
84
89
  agent-finance skills get history-indicators
85
90
  agent-finance skills get futures
86
91
  ```
@@ -92,6 +97,7 @@ agent-finance skills get futures
92
97
  - `history` defaults to adjusted prices and includes corporate actions unless disabled.
93
98
  - `providers` is the source-of-truth capability matrix. Do not infer coverage from provider names.
94
99
  - Binance futures / TradFi perps are derivative/proxy prices. They are useful for 24h price discovery, but they are not the legal equity, broker fill, or pre-IPO ownership price.
100
+ - Polymarket is a prediction-market sentiment source. Use it for implied probabilities, orderbook, spread, volume, liquidity, open interest, holder preview rows, and probability history. It is not an equity quote, primary-source fact, or confirmation of insider information.
95
101
  - `read-url` is an extraction fallback using direct/Jina/Defuddle readers. It is not a login-capable browser.
96
102
  - Dynamic, login-gated, screenshot-sensitive, or noisy pages should be verified with a real browser tool. `agent-browser` and `opencli` are examples, not dependencies.
97
103
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-finance-cli",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "AI Agent-first CLI for no-key financial market data and research context.",
5
5
  "license": "MIT OR Apache-2.0",
6
6
  "repository": {
@@ -31,11 +31,11 @@
31
31
  "check:npm": "node npm/check-package.js"
32
32
  },
33
33
  "optionalDependencies": {
34
- "agent-finance-cli-darwin-arm64": "0.1.2",
35
- "agent-finance-cli-darwin-x64": "0.1.2",
36
- "agent-finance-cli-linux-arm64": "0.1.2",
37
- "agent-finance-cli-linux-x64": "0.1.2",
38
- "agent-finance-cli-win32-x64": "0.1.2"
34
+ "agent-finance-cli-darwin-arm64": "0.1.3",
35
+ "agent-finance-cli-darwin-x64": "0.1.3",
36
+ "agent-finance-cli-linux-arm64": "0.1.3",
37
+ "agent-finance-cli-linux-x64": "0.1.3",
38
+ "agent-finance-cli-win32-x64": "0.1.3"
39
39
  },
40
40
  "keywords": [
41
41
  "finance",
@@ -10,6 +10,7 @@ agent-finance skills get core
10
10
  agent-finance skills get price
11
11
  agent-finance skills get research-data
12
12
  agent-finance skills get providers
13
+ agent-finance skills get prediction-markets
13
14
  agent-finance skills get history-indicators
14
15
  agent-finance skills get futures
15
16
  ```
@@ -67,8 +68,21 @@ agent-finance futures LITEUSDT --json
67
68
 
68
69
  Use `providers` as the source-of-truth coverage matrix. Binance futures / TradFi perps are derivative/proxy prices, not legal equity or broker-fill prices.
69
70
 
71
+ ## Prediction Markets
72
+
73
+ ```bash
74
+ agent-finance polymarket search "spacex ipo" --limit 5
75
+ agent-finance polymarket search "spcex" --limit 5
76
+ agent-finance polymarket market MARKET_ID_OR_SLUG --json
77
+ agent-finance skills get prediction-markets
78
+ ```
79
+
80
+ Use Polymarket for quantifiable sentiment and event-probability signals. It does not replace SEC/IR/company releases, verified news, or equity quotes.
81
+
70
82
  ## Network and Browser Boundaries
71
83
 
72
84
  The CLI respects `--proxy`, `AGENT_FINANCE_PROXY`, and standard proxy environment variables. It does not hardcode a local proxy.
73
85
 
86
+ Polymarket uses the official SDK by default. When `--proxy` or `--no-proxy` is explicit, it uses public REST fallback through the CLI HTTP stack so those network controls are honored.
87
+
74
88
  `read-url` is a text extraction fallback. For dynamic, login-gated, screenshot-sensitive, or noisy pages, open the original page with an available real browser tool such as agent-browser or opencli.
package/skills/core.md CHANGED
@@ -48,6 +48,14 @@ agent-finance search "optical interconnect"
48
48
  agent-finance screen day_gainers
49
49
  ```
50
50
 
51
+ 5. Prediction-market sentiment:
52
+
53
+ ```bash
54
+ agent-finance polymarket search "spacex ipo" --limit 5
55
+ agent-finance polymarket market MARKET_ID_OR_SLUG
56
+ agent-finance skills get prediction-markets
57
+ ```
58
+
51
59
  ## Rules
52
60
 
53
61
  - Use `price` for the default "what is the current price?" answer.
@@ -55,5 +63,6 @@ agent-finance screen day_gainers
55
63
  - Use both daily and minute history before judging fills, limit-order quality, stop placement, or intraday action.
56
64
  - Use `providers --json` when an Agent needs a machine-readable capability matrix.
57
65
  - Treat Binance futures / TradFi perps as proxy/derivative price discovery only.
66
+ - Treat Polymarket as quantifiable prediction-market sentiment and event-probability evidence only; it is not an equity quote or primary-source fact.
58
67
  - `read-url` is a text extraction fallback, not a real browser. For dynamic, login-gated, screenshot-sensitive, or noisy pages, use an available browser tool such as agent-browser or opencli.
59
68
  - JSON output preserves structured fields for downstream computation. Human output is for quick inspection.
@@ -0,0 +1,47 @@
1
+ # agent-finance prediction-markets skill
2
+
3
+ Use this skill when an AI Agent needs prediction-market sentiment, event probabilities, or "what capital is pricing in" for a public event.
4
+
5
+ ## Commands
6
+
7
+ ```bash
8
+ agent-finance polymarket search "spacex ipo" --limit 5
9
+ agent-finance polymarket search "spcex" --limit 5
10
+ agent-finance polymarket market MARKET_ID_OR_SLUG
11
+ agent-finance polymarket market MARKET_ID_OR_SLUG --json
12
+ ```
13
+
14
+ ## Search Semantics
15
+
16
+ - Treat Polymarket search as public relevance search. Do not describe it as guaranteed fuzzy search.
17
+ - Use multiple query fallbacks for important topics:
18
+ - `spacex`, `space x`, `starship`, `ipo`
19
+ - `nvidia`, `nvda`, product names, regulatory events
20
+ - The CLI locally filters and sorts by active/closed state, volume, liquidity, and market signal strength. Still inspect source URLs and raw JSON for important decisions.
21
+
22
+ ## Interpretation Rules
23
+
24
+ - Polymarket prices are implied probabilities backed by user capital.
25
+ - They are useful as quantifiable sentiment and event-probability evidence.
26
+ - They are not facts, confirmed insider information, legal equity prices, broker-fill prices, or official company disclosures.
27
+ - For investment research, pair this signal with primary sources: SEC filings, IR pages, company releases, earnings calls, and verifiable news.
28
+
29
+ ## Useful Flags
30
+
31
+ ```bash
32
+ agent-finance polymarket search "spacex ipo" --include-closed --min-volume 1000 --json
33
+ agent-finance polymarket market MARKET_ID_OR_SLUG --limit 20 --refresh
34
+ ```
35
+
36
+ - `--include-closed`: include resolved/closed markets for historical expectation checks.
37
+ - `--min-volume`: ignore thin markets.
38
+ - `--refresh`: bypass local cache.
39
+ - `--cache-ttl-seconds`: tune freshness for repeated agent workflows.
40
+ - `--json`: preserve full structured payloads for downstream reasoning.
41
+
42
+ ## Boundaries
43
+
44
+ - This CLI is read-only for Polymarket.
45
+ - It does not accept private keys, derive API keys, place orders, cancel orders, or manage Polymarket positions.
46
+ - Default transport uses the official SDK. Explicit `--proxy` or `--no-proxy` uses public REST fallback through the CLI HTTP stack so those network controls are honored.
47
+ - Holder data is reported as preview rows returned by the API limit, not as a total holder count.
@@ -19,7 +19,8 @@ agent-finance providers --json
19
19
  - Robinhood and CNBC are partial no-key sources; use them as cross-checks, not replacements for official filings or primary disclosures.
20
20
  - Stooq live can provide no-key daily/weekly/monthly history; intraday bulk data requires explicit imported ZIP cache.
21
21
  - Binance futures / TradFi perps are proxy/derivative instruments.
22
+ - Polymarket is a prediction-market sentiment source. Use `polymarket search` and `polymarket market` for implied probability, orderbook, liquidity, OI, holder preview rows, and probability history; do not use it as an equity quote or primary-source fact.
22
23
 
23
24
  ## Browser Boundary
24
25
 
25
- The CLI uses HTTP requests with browser-like TLS behavior where possible, but it is not a full browser. Dynamic, login-gated, screenshot-sensitive, or noisy pages require a real browser tool.
26
+ The CLI uses HTTP requests with browser-like TLS behavior where possible, but it is not a full browser. Dynamic, login-gated, screenshot-sensitive, or noisy pages require a real browser tool. Polymarket uses the official SDK by default; explicit `--proxy` or `--no-proxy` uses public REST fallback through the CLI HTTP stack.
package/src/app.rs CHANGED
@@ -8,8 +8,9 @@ use serde::Serialize;
8
8
 
9
9
  use crate::cli::{
10
10
  Cli, Command, FuturesArgs, HistoryArgs, HistorySession, IndicatorsArgs, NewsArgs, OptionsArgs,
11
- OptionsProvider, Provider, ProviderResearchArgs, ProvidersArgs, ReadUrlArgs, ResearchArgs,
12
- ScreenArgs, SearchArgs, SessionsArgs, StooqArgs, StooqCommand, WatchArgs,
11
+ OptionsProvider, PolymarketArgs, PolymarketCommand, Provider, ProviderResearchArgs,
12
+ ProvidersArgs, ReadUrlArgs, ResearchArgs, ScreenArgs, SearchArgs, SessionsArgs, StooqArgs,
13
+ StooqCommand, WatchArgs,
13
14
  };
14
15
  use crate::http::http_client;
15
16
  use crate::indicators::compute_indicator;
@@ -94,6 +95,9 @@ pub async fn run() -> Result<()> {
94
95
  Command::News(args) => run_news(args, proxy, no_proxy, timeout_seconds, timezone).await,
95
96
  Command::ReadUrl(args) => run_read_url(args, proxy, no_proxy, timeout_seconds).await,
96
97
  Command::Search(args) => run_search(args, proxy, no_proxy, timeout_seconds, timezone).await,
98
+ Command::Polymarket(args) => {
99
+ run_polymarket(args, proxy, no_proxy, timeout_seconds, timezone).await
100
+ }
97
101
  Command::Screen(args) => run_screen(args, proxy, no_proxy, timeout_seconds, timezone).await,
98
102
  Command::Stooq(args) => run_stooq(args, proxy, no_proxy, timeout_seconds).await,
99
103
  Command::Providers(args) => run_providers(args),
@@ -103,6 +107,57 @@ pub async fn run() -> Result<()> {
103
107
  }
104
108
  }
105
109
 
110
+ async fn run_polymarket(
111
+ args: PolymarketArgs,
112
+ proxy: Option<&str>,
113
+ no_proxy: bool,
114
+ timeout_seconds: u64,
115
+ timezone: &str,
116
+ ) -> Result<()> {
117
+ let client = http_client(timeout_seconds, proxy, no_proxy)?;
118
+ let use_http_transport = proxy.is_some() || no_proxy;
119
+ match args.command {
120
+ PolymarketCommand::Search(args) => {
121
+ let options = providers::polymarket::SearchRequestOptions {
122
+ query: args.query,
123
+ limit: args.limit,
124
+ include_closed: args.include_closed,
125
+ min_volume: args.min_volume,
126
+ refresh: args.refresh,
127
+ cache_ttl_seconds: args.cache_ttl_seconds,
128
+ timeout_seconds,
129
+ use_http_transport,
130
+ };
131
+ let report = providers::polymarket::search_report(&client, &options, timezone).await?;
132
+ if args.json {
133
+ println!("{}", serde_json::to_string_pretty(&report)?);
134
+ } else {
135
+ output::print_prediction_search_report(&report);
136
+ }
137
+ Ok(())
138
+ }
139
+ PolymarketCommand::Market(args) => {
140
+ let options = providers::polymarket::MarketRequestOptions {
141
+ identifier: args.identifier,
142
+ limit: args.limit,
143
+ include_closed: args.include_closed,
144
+ min_volume: args.min_volume,
145
+ refresh: args.refresh,
146
+ cache_ttl_seconds: args.cache_ttl_seconds,
147
+ timeout_seconds,
148
+ use_http_transport,
149
+ };
150
+ let report = providers::polymarket::market_report(&client, &options, timezone).await?;
151
+ if args.json {
152
+ println!("{}", serde_json::to_string_pretty(&report)?);
153
+ } else {
154
+ output::print_prediction_market_report(&report);
155
+ }
156
+ Ok(())
157
+ }
158
+ }
159
+ }
160
+
106
161
  fn run_skills(args: crate::cli::SkillsArgs) -> Result<()> {
107
162
  match args.command {
108
163
  crate::cli::SkillsCommand::List => {
package/src/cli.rs CHANGED
@@ -61,6 +61,8 @@ pub enum Command {
61
61
  ReadUrl(ReadUrlArgs),
62
62
  /// Search Yahoo Finance for tickers and news.
63
63
  Search(SearchArgs),
64
+ /// Inspect Polymarket prediction-market sentiment and event-probability signals.
65
+ Polymarket(PolymarketArgs),
64
66
  /// Run Yahoo predefined screeners.
65
67
  Screen(ScreenArgs),
66
68
  /// Inspect or import Stooq bulk historical data packages.
@@ -341,6 +343,67 @@ pub struct SearchArgs {
341
343
  pub json: bool,
342
344
  }
343
345
 
346
+ #[derive(Parser, Debug)]
347
+ pub struct PolymarketArgs {
348
+ #[command(subcommand)]
349
+ pub command: PolymarketCommand,
350
+ }
351
+
352
+ #[derive(Subcommand, Debug)]
353
+ pub enum PolymarketCommand {
354
+ /// Search public Polymarket events and markets by relevance.
355
+ Search(PolymarketSearchArgs),
356
+ /// Inspect one Polymarket market by numeric id or slug.
357
+ Market(PolymarketMarketArgs),
358
+ }
359
+
360
+ #[derive(Parser, Debug)]
361
+ pub struct PolymarketSearchArgs {
362
+ pub query: String,
363
+
364
+ #[arg(long, default_value_t = 8)]
365
+ pub limit: usize,
366
+
367
+ #[arg(long)]
368
+ pub include_closed: bool,
369
+
370
+ #[arg(long)]
371
+ pub min_volume: Option<f64>,
372
+
373
+ #[arg(long)]
374
+ pub refresh: bool,
375
+
376
+ #[arg(long, default_value_t = 900)]
377
+ pub cache_ttl_seconds: u64,
378
+
379
+ #[arg(long)]
380
+ pub json: bool,
381
+ }
382
+
383
+ #[derive(Parser, Debug)]
384
+ pub struct PolymarketMarketArgs {
385
+ pub identifier: String,
386
+
387
+ /// Maximum rows for holder/trade/orderbook detail previews.
388
+ #[arg(long, default_value_t = 10)]
389
+ pub limit: usize,
390
+
391
+ #[arg(long)]
392
+ pub include_closed: bool,
393
+
394
+ #[arg(long)]
395
+ pub min_volume: Option<f64>,
396
+
397
+ #[arg(long)]
398
+ pub refresh: bool,
399
+
400
+ #[arg(long, default_value_t = 900)]
401
+ pub cache_ttl_seconds: u64,
402
+
403
+ #[arg(long)]
404
+ pub json: bool,
405
+ }
406
+
344
407
  #[derive(Parser, Debug)]
345
408
  pub struct ScreenArgs {
346
409
  /// Yahoo predefined screener id, for example most_actives, day_gainers, day_losers.
package/src/model.rs CHANGED
@@ -325,6 +325,89 @@ pub struct SearchReport {
325
325
  pub payload: Value,
326
326
  }
327
327
 
328
+ #[derive(Debug, Clone, Serialize)]
329
+ pub struct PredictionSearchReport {
330
+ pub provider: String,
331
+ pub query: String,
332
+ pub fetched_at_utc: String,
333
+ pub fetched_at_local: String,
334
+ pub cache_status: String,
335
+ pub source_urls: Vec<String>,
336
+ pub interpretation_note: String,
337
+ pub markets: Vec<PredictionMarketSummary>,
338
+ pub payload: Value,
339
+ }
340
+
341
+ #[derive(Debug, Clone, Serialize)]
342
+ pub struct PredictionMarketReport {
343
+ pub provider: String,
344
+ pub identifier: String,
345
+ pub fetched_at_utc: String,
346
+ pub fetched_at_local: String,
347
+ pub cache_status: String,
348
+ pub enrichment_status: String,
349
+ pub enrichment_fetched_at_utc: String,
350
+ pub enrichment_fetched_at_local: String,
351
+ pub source_urls: Vec<String>,
352
+ pub interpretation_note: String,
353
+ pub market: PredictionMarketSummary,
354
+ pub outcomes: Vec<PredictionOutcome>,
355
+ pub price_history: Vec<PredictionPricePoint>,
356
+ pub open_interest: Option<f64>,
357
+ pub holder_preview_count: Option<usize>,
358
+ pub data_errors: BTreeMap<String, String>,
359
+ pub payload: Value,
360
+ }
361
+
362
+ #[derive(Debug, Clone, Serialize)]
363
+ pub struct PredictionMarketSummary {
364
+ pub id: Option<String>,
365
+ pub condition_id: Option<String>,
366
+ pub slug: Option<String>,
367
+ pub event_id: Option<String>,
368
+ pub event_slug: Option<String>,
369
+ pub title: String,
370
+ pub question: Option<String>,
371
+ pub active: Option<bool>,
372
+ pub closed: Option<bool>,
373
+ pub accepting_orders: Option<bool>,
374
+ pub end_time_utc: Option<String>,
375
+ pub end_time_local: Option<String>,
376
+ pub volume: Option<f64>,
377
+ pub volume_24hr: Option<f64>,
378
+ pub liquidity: Option<f64>,
379
+ pub open_interest: Option<f64>,
380
+ pub best_bid: Option<f64>,
381
+ pub best_ask: Option<f64>,
382
+ pub spread: Option<f64>,
383
+ pub last_trade_price: Option<f64>,
384
+ pub one_hour_price_change: Option<f64>,
385
+ pub one_day_price_change: Option<f64>,
386
+ pub one_week_price_change: Option<f64>,
387
+ pub market_url: Option<String>,
388
+ pub outcomes: Vec<PredictionOutcome>,
389
+ }
390
+
391
+ #[derive(Debug, Clone, Serialize)]
392
+ pub struct PredictionOutcome {
393
+ pub label: String,
394
+ pub implied_probability: Option<f64>,
395
+ pub clob_token_id: Option<String>,
396
+ pub best_bid: Option<f64>,
397
+ pub best_ask: Option<f64>,
398
+ pub spread: Option<f64>,
399
+ pub last_trade_price: Option<f64>,
400
+ pub bid_count: usize,
401
+ pub ask_count: usize,
402
+ }
403
+
404
+ #[derive(Debug, Clone, Serialize)]
405
+ pub struct PredictionPricePoint {
406
+ pub time_utc: Option<String>,
407
+ pub time_local: Option<String>,
408
+ pub price: f64,
409
+ }
410
+
328
411
  #[derive(Debug, Clone, Serialize)]
329
412
  pub struct StreamQuote {
330
413
  pub symbol: String,
package/src/output.rs CHANGED
@@ -3,7 +3,8 @@ use std::collections::BTreeMap;
3
3
  use anyhow::Result;
4
4
 
5
5
  use crate::model::{
6
- DerivedIndicator, FuturesStats, HistoryBatch, PricePoint, PriceSummary, ProviderProfile,
6
+ DerivedIndicator, FuturesStats, HistoryBatch, PredictionMarketReport, PredictionMarketSummary,
7
+ PredictionOutcome, PredictionSearchReport, PricePoint, PriceSummary, ProviderProfile,
7
8
  ResearchReport, SearchReport, StreamQuote,
8
9
  };
9
10
  use crate::page_read::PageReadReport;
@@ -331,6 +332,153 @@ pub fn print_search_report(report: &SearchReport, raw: bool) -> Result<()> {
331
332
  Ok(())
332
333
  }
333
334
 
335
+ pub fn print_prediction_search_report(report: &PredictionSearchReport) {
336
+ println!(
337
+ "{} search '{}' fetched={} cache={}",
338
+ report.provider, report.query, report.fetched_at_local, report.cache_status
339
+ );
340
+ println!("{}", report.interpretation_note);
341
+ print_source_urls(&report.source_urls);
342
+ print_prediction_markets(&report.markets);
343
+ }
344
+
345
+ pub fn print_prediction_market_report(report: &PredictionMarketReport) {
346
+ println!(
347
+ "{} market '{}' fetched={} cache={} enrichment={} enrichment_fetched={}",
348
+ report.provider,
349
+ report.identifier,
350
+ report.fetched_at_local,
351
+ report.cache_status,
352
+ report.enrichment_status,
353
+ report.enrichment_fetched_at_local
354
+ );
355
+ println!("{}", report.interpretation_note);
356
+ print_source_urls(&report.source_urls);
357
+ print_prediction_markets(std::slice::from_ref(&report.market));
358
+ println!();
359
+ println!("Outcomes");
360
+ print_prediction_outcomes(&report.outcomes);
361
+ if !report.price_history.is_empty() {
362
+ println!();
363
+ println!("Price history");
364
+ let headers = ["time", "price"];
365
+ let rows = report
366
+ .price_history
367
+ .iter()
368
+ .rev()
369
+ .take(12)
370
+ .map(|point| {
371
+ vec![
372
+ point
373
+ .time_local
374
+ .as_deref()
375
+ .or(point.time_utc.as_deref())
376
+ .unwrap_or("-")
377
+ .to_string(),
378
+ money_value(Some(point.price)),
379
+ ]
380
+ })
381
+ .collect::<Vec<_>>();
382
+ print_table(&headers, &rows);
383
+ }
384
+ if report.open_interest.is_some() || report.holder_preview_count.is_some() {
385
+ println!();
386
+ println!(
387
+ "Data API: open_interest={} holder_preview_rows={}",
388
+ number_value(report.open_interest),
389
+ report
390
+ .holder_preview_count
391
+ .map_or_else(|| "-".to_string(), |value| value.to_string())
392
+ );
393
+ }
394
+ if !report.data_errors.is_empty() {
395
+ println!();
396
+ println!("Partial data errors");
397
+ for (source, error) in &report.data_errors {
398
+ println!("{source}: {error}");
399
+ }
400
+ }
401
+ }
402
+
403
+ fn print_source_urls(urls: &[String]) {
404
+ if urls.is_empty() {
405
+ return;
406
+ }
407
+ println!("Sources:");
408
+ for url in urls {
409
+ println!("- {url}");
410
+ }
411
+ }
412
+
413
+ fn print_prediction_markets(markets: &[PredictionMarketSummary]) {
414
+ if markets.is_empty() {
415
+ println!("No markets matched after local filtering.");
416
+ return;
417
+ }
418
+ let headers = [
419
+ "market", "active", "closed", "prob", "bid", "ask", "spread", "vol24h", "liq", "end",
420
+ ];
421
+ let rows = markets
422
+ .iter()
423
+ .map(|market| {
424
+ vec![
425
+ market.title.clone(),
426
+ bool_text(market.active),
427
+ bool_text(market.closed),
428
+ market
429
+ .outcomes
430
+ .first()
431
+ .and_then(|outcome| outcome.implied_probability)
432
+ .map(pct_from_unit)
433
+ .unwrap_or_else(|| "-".to_string()),
434
+ money_value(market.best_bid),
435
+ money_value(market.best_ask),
436
+ money_value(market.spread),
437
+ number_value(market.volume_24hr),
438
+ number_value(market.liquidity),
439
+ market
440
+ .end_time_local
441
+ .clone()
442
+ .unwrap_or_else(|| "-".to_string()),
443
+ ]
444
+ })
445
+ .collect::<Vec<_>>();
446
+ print_table(&headers, &rows);
447
+ }
448
+
449
+ fn print_prediction_outcomes(outcomes: &[PredictionOutcome]) {
450
+ if outcomes.is_empty() {
451
+ println!("No outcomes found.");
452
+ return;
453
+ }
454
+ let headers = [
455
+ "outcome", "prob", "bid", "ask", "spread", "last", "bids", "asks", "token",
456
+ ];
457
+ let rows = outcomes
458
+ .iter()
459
+ .map(|outcome| {
460
+ vec![
461
+ outcome.label.clone(),
462
+ outcome
463
+ .implied_probability
464
+ .map(pct_from_unit)
465
+ .unwrap_or_else(|| "-".to_string()),
466
+ money_value(outcome.best_bid),
467
+ money_value(outcome.best_ask),
468
+ money_value(outcome.spread),
469
+ money_value(outcome.last_trade_price),
470
+ outcome.bid_count.to_string(),
471
+ outcome.ask_count.to_string(),
472
+ outcome
473
+ .clob_token_id
474
+ .clone()
475
+ .unwrap_or_else(|| "-".to_string()),
476
+ ]
477
+ })
478
+ .collect::<Vec<_>>();
479
+ print_table(&headers, &rows);
480
+ }
481
+
334
482
  pub fn print_provider_profiles(profiles: &[ProviderProfile]) {
335
483
  let headers = [
336
484
  "provider",
@@ -549,6 +697,18 @@ fn number_value(value: Option<f64>) -> String {
549
697
  }
550
698
  }
551
699
 
700
+ fn bool_text(value: Option<bool>) -> String {
701
+ match value {
702
+ Some(true) => "yes".to_string(),
703
+ Some(false) => "no".to_string(),
704
+ None => "-".to_string(),
705
+ }
706
+ }
707
+
708
+ fn pct_from_unit(value: f64) -> String {
709
+ format!("{:.2}%", value * 100.0)
710
+ }
711
+
552
712
  fn pct_value(value: Option<f64>) -> String {
553
713
  match value {
554
714
  Some(value) => format!("{value:+.2}%"),
@@ -211,6 +211,55 @@ pub fn profiles() -> Vec<ProviderProfile> {
211
211
  ],
212
212
  &["Use for extended-hours price checks."],
213
213
  ),
214
+ profile(
215
+ "polymarket",
216
+ false,
217
+ "official-sdk",
218
+ "official-public-api",
219
+ "Prediction-market sentiment, implied probability, orderbook, liquidity, open interest, holder previews, and probability history.",
220
+ &[
221
+ cap(
222
+ "prediction search",
223
+ "yes",
224
+ "Gamma public relevance search for events and markets",
225
+ ),
226
+ cap(
227
+ "market detail",
228
+ "yes",
229
+ "Gamma market metadata and outcome probabilities",
230
+ ),
231
+ cap(
232
+ "orderbook",
233
+ "yes",
234
+ "CLOB public best bid/ask and depth by outcome token",
235
+ ),
236
+ cap(
237
+ "probability history",
238
+ "yes",
239
+ "CLOB prices-history by outcome token",
240
+ ),
241
+ cap(
242
+ "open interest",
243
+ "yes",
244
+ "Data API open interest by condition id",
245
+ ),
246
+ cap(
247
+ "holders",
248
+ "preview",
249
+ "Data API top holder preview rows by condition id; not total holder count",
250
+ ),
251
+ cap("quote", "no", "Prediction prices are not equity quotes"),
252
+ cap(
253
+ "fundamentals",
254
+ "no",
255
+ "Does not replace SEC, IR, or company filings",
256
+ ),
257
+ ],
258
+ &[
259
+ "Use as quantifiable sentiment and event-probability evidence only.",
260
+ "Default transport uses the official SDK; explicit --proxy/--no-proxy uses public REST fallback through the CLI HTTP stack.",
261
+ ],
262
+ ),
214
263
  profile(
215
264
  Provider::BinanceFutures.label(),
216
265
  false,
@@ -7,6 +7,7 @@ use crate::model::{HistoryBatch, Quote};
7
7
  pub mod binance_futures;
8
8
  pub mod capabilities;
9
9
  pub mod cnbc;
10
+ pub mod polymarket;
10
11
  pub mod robinhood;
11
12
  pub mod sec_edgar;
12
13
  pub mod stooq;