agent-finance-cli 0.1.1 → 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/src/output.rs CHANGED
@@ -3,10 +3,12 @@ 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;
11
+ use crate::time::utc_to_local;
10
12
 
11
13
  pub fn print_price_summary(summary: &PriceSummary, show_all: bool) {
12
14
  println!(
@@ -72,7 +74,7 @@ pub fn print_price_summary(summary: &PriceSummary, show_all: bool) {
72
74
  }
73
75
  }
74
76
 
75
- pub fn print_history_table(history: &HistoryBatch) {
77
+ pub fn print_history_table(history: &HistoryBatch, timezone: &str) {
76
78
  println!(
77
79
  "{} history via {} interval={} adjustment={} actions={} repair_requested={} repair_applied={}",
78
80
  history.symbol,
@@ -101,7 +103,7 @@ pub fn print_history_table(history: &HistoryBatch) {
101
103
  .iter()
102
104
  .map(|bar| {
103
105
  vec![
104
- bar.open_time.clone(),
106
+ local_or_original(&bar.open_time, timezone),
105
107
  money_value(bar.open),
106
108
  money_value(bar.high),
107
109
  money_value(bar.low),
@@ -157,10 +159,12 @@ pub fn print_indicator_table(indicators: &[DerivedIndicator], errors: &BTreeMap<
157
159
  print_table(&headers, &rows);
158
160
  }
159
161
 
160
- pub fn print_futures_stats(stats: &FuturesStats) {
162
+ pub fn print_futures_stats(stats: &FuturesStats, timezone: &str) {
161
163
  println!(
162
164
  "{} futures stats via {} fetched_at={}",
163
- stats.symbol, stats.provider, stats.fetched_at_utc
165
+ stats.symbol,
166
+ stats.provider,
167
+ local_or_original(&stats.fetched_at_utc, timezone)
164
168
  );
165
169
  if let Some(ticker) = stats.ticker_24h.as_ref() {
166
170
  println!(
@@ -182,14 +186,14 @@ pub fn print_futures_stats(stats: &FuturesStats) {
182
186
  money_value(mark.mark_price),
183
187
  money_value(mark.index_price),
184
188
  pct_value(mark.last_funding_rate.map(|value| value * 100.0)),
185
- mark.next_funding_time.as_deref().unwrap_or("-")
189
+ local_or_original_optional(mark.next_funding_time.as_deref(), timezone)
186
190
  );
187
191
  }
188
192
  if let Some(open_interest) = stats.open_interest.as_ref() {
189
193
  println!(
190
194
  "open_interest: {} time={}",
191
195
  number_value(open_interest.open_interest),
192
- open_interest.time.as_deref().unwrap_or("-")
196
+ local_or_original_optional(open_interest.time.as_deref(), timezone)
193
197
  );
194
198
  }
195
199
  if !stats.funding_rates.is_empty() {
@@ -200,7 +204,7 @@ pub fn print_futures_stats(stats: &FuturesStats) {
200
204
  .iter()
201
205
  .map(|row| {
202
206
  vec![
203
- row.funding_time.clone().unwrap_or_else(|| "-".to_string()),
207
+ local_or_original_optional(row.funding_time.as_deref(), timezone),
204
208
  pct_value(row.funding_rate.map(|value| value * 100.0)),
205
209
  money_value(row.mark_price),
206
210
  ]
@@ -328,6 +332,153 @@ pub fn print_search_report(report: &SearchReport, raw: bool) -> Result<()> {
328
332
  Ok(())
329
333
  }
330
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
+
331
482
  pub fn print_provider_profiles(profiles: &[ProviderProfile]) {
332
483
  let headers = [
333
484
  "provider",
@@ -512,6 +663,16 @@ where
512
663
  .join(" ")
513
664
  }
514
665
 
666
+ fn local_or_original_optional(value: Option<&str>, timezone: &str) -> String {
667
+ value
668
+ .map(|value| local_or_original(value, timezone))
669
+ .unwrap_or_else(|| "-".to_string())
670
+ }
671
+
672
+ fn local_or_original(value: &str, timezone: &str) -> String {
673
+ utc_to_local(Some(value), timezone).unwrap_or_else(|| value.to_string())
674
+ }
675
+
515
676
  fn money_value(value: Option<f64>) -> String {
516
677
  match value {
517
678
  Some(value) => format!("${value:.2}"),
@@ -536,6 +697,18 @@ fn number_value(value: Option<f64>) -> String {
536
697
  }
537
698
  }
538
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
+
539
712
  fn pct_value(value: Option<f64>) -> String {
540
713
  match value {
541
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;