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.lock +3920 -779
- package/Cargo.toml +2 -1
- package/README.md +7 -1
- package/package.json +6 -6
- package/skills/core-full.md +14 -0
- package/skills/core.md +9 -0
- package/skills/prediction-markets.md +47 -0
- package/skills/providers.md +2 -1
- package/src/app.rs +57 -2
- package/src/cli.rs +63 -0
- package/src/model.rs +83 -0
- package/src/output.rs +161 -1
- package/src/providers/capabilities.rs +49 -0
- package/src/providers/mod.rs +1 -0
- package/src/providers/polymarket.rs +1071 -0
- package/src/skills.rs +7 -1
package/Cargo.toml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "agent-finance-cli"
|
|
3
|
-
version = "0.1.
|
|
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.
|
|
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.
|
|
35
|
-
"agent-finance-cli-darwin-x64": "0.1.
|
|
36
|
-
"agent-finance-cli-linux-arm64": "0.1.
|
|
37
|
-
"agent-finance-cli-linux-x64": "0.1.
|
|
38
|
-
"agent-finance-cli-win32-x64": "0.1.
|
|
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",
|
package/skills/core-full.md
CHANGED
|
@@ -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.
|
package/skills/providers.md
CHANGED
|
@@ -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,
|
|
12
|
-
ScreenArgs, SearchArgs, SessionsArgs, StooqArgs,
|
|
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,
|
|
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,
|