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/Cargo.lock +3928 -786
- package/Cargo.toml +3 -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 +70 -7
- package/src/cli.rs +67 -4
- package/src/model.rs +83 -0
- package/src/output.rs +181 -8
- 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/src/time.rs +70 -2
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,
|
|
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
|
|
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,
|
|
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()
|
|
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()
|
|
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.
|
|
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,
|