trading-cli 0.3.0__py3-none-any.whl

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.
@@ -0,0 +1,2215 @@
1
+ """Polymarket command group — prediction market trading, browsing, and on-chain ops.
2
+
3
+ Mirrors the full Polymarket CLI feature set: markets, events, CLOB trading,
4
+ CTF token operations, bridging, and portfolio data.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import click
10
+
11
+ from ..client import FastBooksClient
12
+ from ..output import emit, emit_error, format_table, require_experimental
13
+
14
+
15
+ # ══════════════════════════════════════════════════════════════════════════
16
+ # Root group
17
+ # ══════════════════════════════════════════════════════════════════════════
18
+
19
+ @click.group()
20
+ @click.pass_context
21
+ def polymarket(ctx):
22
+ """[experimental] Polymarket prediction markets — browse, trade, manage positions.
23
+
24
+ NOTE: requires the Polymarket router, not enabled on standard FastBooks
25
+ deployments. Gated behind FASTBOOKS_EXPERIMENTAL=1.
26
+
27
+ Browse markets and events without a wallet. Trading requires a Polygon
28
+ wallet private key (--private-key or POLYMARKET_PRIVATE_KEY env var).
29
+
30
+ Examples:
31
+ fastbooks polymarket markets list --limit 10
32
+ fastbooks polymarket markets search "bitcoin"
33
+ fastbooks polymarket clob book TOKEN_ID
34
+ fastbooks polymarket clob create-order --token TOKEN --side buy --price 0.50 --size 10
35
+ """
36
+ require_experimental(ctx, "polymarket")
37
+
38
+
39
+ # ══════════════════════════════════════════════════════════════════════════
40
+ # Markets (read-only, no wallet needed)
41
+ # ══════════════════════════════════════════════════════════════════════════
42
+
43
+ @polymarket.group("markets")
44
+ def pm_markets():
45
+ """Browse and search prediction markets."""
46
+ pass
47
+
48
+
49
+ @pm_markets.command("list")
50
+ @click.option("--limit", "-n", type=int, default=20, help="Number of markets.")
51
+ @click.option("--offset", type=int, default=0, help="Pagination offset.")
52
+ @click.option("--active/--no-active", default=None, help="Filter by active status.")
53
+ @click.option("--closed/--no-closed", default=None, help="Filter by closed status.")
54
+ @click.option("--order", type=str, default=None, help="Sort field (e.g. volume_num).")
55
+ @click.option("--ascending", is_flag=True, help="Sort ascending.")
56
+ @click.pass_context
57
+ def pm_markets_list(ctx, limit, offset, active, closed, order, ascending):
58
+ """List prediction markets.
59
+
60
+ Examples:
61
+ fastbooks polymarket markets list --limit 10
62
+ fastbooks polymarket markets list --active --order volume_num
63
+ fastbooks polymarket markets list --closed --limit 50 --offset 25
64
+ """
65
+ client: FastBooksClient = ctx.obj
66
+ try:
67
+ data = client.polymarket_markets_list(
68
+ limit=limit, offset=offset, active=active,
69
+ closed=closed, order=order, ascending=ascending,
70
+ )
71
+ emit(ctx, data, lambda d: _format_markets_list(d))
72
+ except Exception as e:
73
+ emit_error(ctx, str(e))
74
+ ctx.exit(1)
75
+
76
+
77
+ @pm_markets.command("get")
78
+ @click.argument("id_or_slug")
79
+ @click.pass_context
80
+ def pm_markets_get(ctx, id_or_slug):
81
+ """Get a single market by ID or slug.
82
+
83
+ Examples:
84
+ fastbooks polymarket markets get 12345
85
+ fastbooks polymarket markets get will-trump-win
86
+ """
87
+ client: FastBooksClient = ctx.obj
88
+ try:
89
+ data = client.polymarket_market_get(id_or_slug)
90
+ emit(ctx, data, lambda d: _format_market_detail(d))
91
+ except Exception as e:
92
+ emit_error(ctx, str(e))
93
+ ctx.exit(1)
94
+
95
+
96
+ @pm_markets.command("search")
97
+ @click.argument("query")
98
+ @click.option("--limit", "-n", type=int, default=10, help="Max results.")
99
+ @click.pass_context
100
+ def pm_markets_search(ctx, query, limit):
101
+ """Search markets by keyword.
102
+
103
+ Examples:
104
+ fastbooks polymarket markets search "bitcoin"
105
+ fastbooks polymarket markets search "election" --limit 20
106
+ """
107
+ client: FastBooksClient = ctx.obj
108
+ try:
109
+ data = client.polymarket_market_search(query, limit)
110
+ emit(ctx, data, lambda d: _format_markets_list(d))
111
+ except Exception as e:
112
+ emit_error(ctx, str(e))
113
+ ctx.exit(1)
114
+
115
+
116
+ @pm_markets.command("tags")
117
+ @click.argument("market_id")
118
+ @click.pass_context
119
+ def pm_markets_tags(ctx, market_id):
120
+ """Get tags for a market.
121
+
122
+ Example: fastbooks polymarket markets tags 12345
123
+ """
124
+ client: FastBooksClient = ctx.obj
125
+ try:
126
+ data = client.polymarket_market_tags(market_id)
127
+ emit(ctx, data)
128
+ except Exception as e:
129
+ emit_error(ctx, str(e))
130
+ ctx.exit(1)
131
+
132
+
133
+ # ══════════════════════════════════════════════════════════════════════════
134
+ # Events
135
+ # ══════════════════════════════════════════════════════════════════════════
136
+
137
+ @polymarket.group("events")
138
+ def pm_events():
139
+ """Browse events (groups of related markets)."""
140
+ pass
141
+
142
+
143
+ @pm_events.command("list")
144
+ @click.option("--limit", "-n", type=int, default=20)
145
+ @click.option("--offset", type=int, default=0)
146
+ @click.option("--tag", type=str, default=None, help="Filter by tag (e.g. politics, crypto).")
147
+ @click.option("--active/--no-active", default=None)
148
+ @click.option("--closed/--no-closed", default=None)
149
+ @click.pass_context
150
+ def pm_events_list(ctx, limit, offset, tag, active, closed):
151
+ """List events with optional filters.
152
+
153
+ Examples:
154
+ fastbooks polymarket events list --limit 10
155
+ fastbooks polymarket events list --tag politics --active
156
+ """
157
+ client: FastBooksClient = ctx.obj
158
+ try:
159
+ data = client.polymarket_events_list(limit=limit, offset=offset, tag=tag, active=active, closed=closed)
160
+ emit(ctx, data, lambda d: _format_events_list(d))
161
+ except Exception as e:
162
+ emit_error(ctx, str(e))
163
+ ctx.exit(1)
164
+
165
+
166
+ @pm_events.command("get")
167
+ @click.argument("event_id")
168
+ @click.pass_context
169
+ def pm_events_get(ctx, event_id):
170
+ """Get event details by ID.
171
+
172
+ Example: fastbooks polymarket events get 500
173
+ """
174
+ client: FastBooksClient = ctx.obj
175
+ try:
176
+ data = client.polymarket_event_get(event_id)
177
+ emit(ctx, data, lambda d: _format_event_detail(d))
178
+ except Exception as e:
179
+ emit_error(ctx, str(e))
180
+ ctx.exit(1)
181
+
182
+
183
+ @pm_events.command("tags")
184
+ @click.argument("event_id")
185
+ @click.pass_context
186
+ def pm_events_tags(ctx, event_id):
187
+ """Get tags for an event.
188
+
189
+ Example: fastbooks polymarket events tags 500
190
+ """
191
+ client: FastBooksClient = ctx.obj
192
+ try:
193
+ data = client.polymarket_event_tags(event_id)
194
+ emit(ctx, data)
195
+ except Exception as e:
196
+ emit_error(ctx, str(e))
197
+ ctx.exit(1)
198
+
199
+
200
+ # ══════════════════════════════════════════════════════════════════════════
201
+ # Tags
202
+ # ══════════════════════════════════════════════════════════════════════════
203
+
204
+ @polymarket.group("tags")
205
+ @click.pass_context
206
+ def pm_tags(ctx):
207
+ """Browse and navigate market tags."""
208
+ pass
209
+
210
+
211
+ @pm_tags.command("list")
212
+ @click.pass_context
213
+ def pm_tags_list(ctx):
214
+ """List all available market tags.
215
+
216
+ Example: fastbooks polymarket tags list
217
+ """
218
+ client: FastBooksClient = ctx.obj
219
+ try:
220
+ data = client.polymarket_tags_list()
221
+ emit(ctx, data)
222
+ except Exception as e:
223
+ emit_error(ctx, str(e))
224
+ ctx.exit(1)
225
+
226
+
227
+ @pm_tags.command("get")
228
+ @click.argument("id_or_slug")
229
+ @click.pass_context
230
+ def pm_tags_get(ctx, id_or_slug):
231
+ """Get a single tag by ID or slug.
232
+
233
+ Example: fastbooks polymarket tags get politics
234
+ """
235
+ client: FastBooksClient = ctx.obj
236
+ try:
237
+ data = client.polymarket_tag_get(id_or_slug)
238
+ emit(ctx, data)
239
+ except Exception as e:
240
+ emit_error(ctx, str(e))
241
+ ctx.exit(1)
242
+
243
+
244
+ @pm_tags.command("related")
245
+ @click.argument("id_or_slug")
246
+ @click.pass_context
247
+ def pm_tags_related(ctx, id_or_slug):
248
+ """Get related tags.
249
+
250
+ Example: fastbooks polymarket tags related politics
251
+ """
252
+ client: FastBooksClient = ctx.obj
253
+ try:
254
+ data = client.polymarket_tags_related(id_or_slug)
255
+ emit(ctx, data)
256
+ except Exception as e:
257
+ emit_error(ctx, str(e))
258
+ ctx.exit(1)
259
+
260
+
261
+ # ══════════════════════════════════════════════════════════════════════════
262
+ # Series (recurring events)
263
+ # ══════════════════════════════════════════════════════════════════════════
264
+
265
+ @polymarket.group("series")
266
+ def pm_series():
267
+ """Browse recurring event series."""
268
+ pass
269
+
270
+
271
+ @pm_series.command("list")
272
+ @click.option("--limit", "-n", type=int, default=20)
273
+ @click.option("--offset", type=int, default=0)
274
+ @click.pass_context
275
+ def pm_series_list(ctx, limit, offset):
276
+ """List event series.
277
+
278
+ Example: fastbooks polymarket series list --limit 10
279
+ """
280
+ client: FastBooksClient = ctx.obj
281
+ try:
282
+ data = client.polymarket_series_list(limit, offset)
283
+ emit(ctx, data)
284
+ except Exception as e:
285
+ emit_error(ctx, str(e))
286
+ ctx.exit(1)
287
+
288
+
289
+ @pm_series.command("get")
290
+ @click.argument("series_id")
291
+ @click.pass_context
292
+ def pm_series_get(ctx, series_id):
293
+ """Get a series by ID.
294
+
295
+ Example: fastbooks polymarket series get 42
296
+ """
297
+ client: FastBooksClient = ctx.obj
298
+ try:
299
+ data = client.polymarket_series_get(series_id)
300
+ emit(ctx, data)
301
+ except Exception as e:
302
+ emit_error(ctx, str(e))
303
+ ctx.exit(1)
304
+
305
+
306
+ # ══════════════════════════════════════════════════════════════════════════
307
+ # Comments
308
+ # ══════════════════════════════════════════════════════════════════════════
309
+
310
+ @polymarket.group("comments")
311
+ def pm_comments():
312
+ """Browse comments on markets and events."""
313
+ pass
314
+
315
+
316
+ @pm_comments.command("list")
317
+ @click.option("--entity-type", required=True, type=click.Choice(["event", "market", "series"]))
318
+ @click.option("--entity-id", required=True, help="Entity ID.")
319
+ @click.pass_context
320
+ def pm_comments_list(ctx, entity_type, entity_id):
321
+ """List comments for an entity.
322
+
323
+ Example: fastbooks polymarket comments list --entity-type event --entity-id 500
324
+ """
325
+ client: FastBooksClient = ctx.obj
326
+ try:
327
+ data = client.polymarket_comments_list(entity_type, entity_id)
328
+ emit(ctx, data)
329
+ except Exception as e:
330
+ emit_error(ctx, str(e))
331
+ ctx.exit(1)
332
+
333
+
334
+ @pm_comments.command("get")
335
+ @click.argument("comment_id")
336
+ @click.pass_context
337
+ def pm_comments_get(ctx, comment_id):
338
+ """Get a comment by ID.
339
+
340
+ Example: fastbooks polymarket comments get abc123
341
+ """
342
+ client: FastBooksClient = ctx.obj
343
+ try:
344
+ data = client.polymarket_comment_get(comment_id)
345
+ emit(ctx, data)
346
+ except Exception as e:
347
+ emit_error(ctx, str(e))
348
+ ctx.exit(1)
349
+
350
+
351
+ @pm_comments.command("by-user")
352
+ @click.argument("address")
353
+ @click.pass_context
354
+ def pm_comments_by_user(ctx, address):
355
+ """Get comments by a user address.
356
+
357
+ Example: fastbooks polymarket comments by-user 0xf5E6...
358
+ """
359
+ client: FastBooksClient = ctx.obj
360
+ try:
361
+ data = client.polymarket_comments_by_user(address)
362
+ emit(ctx, data)
363
+ except Exception as e:
364
+ emit_error(ctx, str(e))
365
+ ctx.exit(1)
366
+
367
+
368
+ # ══════════════════════════════════════════════════════════════════════════
369
+ # Profiles
370
+ # ══════════════════════════════════════════════════════════════════════════
371
+
372
+ @polymarket.group("profiles")
373
+ def pm_profiles():
374
+ """View public user profiles."""
375
+ pass
376
+
377
+
378
+ @pm_profiles.command("get")
379
+ @click.argument("address")
380
+ @click.pass_context
381
+ def pm_profiles_get(ctx, address):
382
+ """Get a public profile by wallet address.
383
+
384
+ Example: fastbooks polymarket profiles get 0xf5E6...
385
+ """
386
+ client: FastBooksClient = ctx.obj
387
+ try:
388
+ data = client.polymarket_profile_get(address)
389
+ emit(ctx, data)
390
+ except Exception as e:
391
+ emit_error(ctx, str(e))
392
+ ctx.exit(1)
393
+
394
+
395
+ # ══════════════════════════════════════════════════════════════════════════
396
+ # Sports
397
+ # ══════════════════════════════════════════════════════════════════════════
398
+
399
+ @polymarket.group("sports")
400
+ def pm_sports():
401
+ """Sports metadata: leagues, teams, market types."""
402
+ pass
403
+
404
+
405
+ @pm_sports.command("list")
406
+ @click.pass_context
407
+ def pm_sports_list(ctx):
408
+ """List supported sports.
409
+
410
+ Example: fastbooks polymarket sports list
411
+ """
412
+ client: FastBooksClient = ctx.obj
413
+ try:
414
+ data = client.polymarket_sports_list()
415
+ emit(ctx, data)
416
+ except Exception as e:
417
+ emit_error(ctx, str(e))
418
+ ctx.exit(1)
419
+
420
+
421
+ @pm_sports.command("market-types")
422
+ @click.pass_context
423
+ def pm_sports_market_types(ctx):
424
+ """List valid sports market types.
425
+
426
+ Example: fastbooks polymarket sports market-types
427
+ """
428
+ client: FastBooksClient = ctx.obj
429
+ try:
430
+ data = client.polymarket_sports_market_types()
431
+ emit(ctx, data)
432
+ except Exception as e:
433
+ emit_error(ctx, str(e))
434
+ ctx.exit(1)
435
+
436
+
437
+ @pm_sports.command("teams")
438
+ @click.option("--league", required=True, help="League name (e.g. NFL, NBA, MLB).")
439
+ @click.option("--limit", "-n", type=int, default=50)
440
+ @click.pass_context
441
+ def pm_sports_teams(ctx, league, limit):
442
+ """List teams in a league.
443
+
444
+ Example: fastbooks polymarket sports teams --league NFL --limit 32
445
+ """
446
+ client: FastBooksClient = ctx.obj
447
+ try:
448
+ data = client.polymarket_sports_teams(league, limit)
449
+ emit(ctx, data)
450
+ except Exception as e:
451
+ emit_error(ctx, str(e))
452
+ ctx.exit(1)
453
+
454
+
455
+ # ══════════════════════════════════════════════════════════════════════════
456
+ # Wallet Management
457
+ # ══════════════════════════════════════════════════════════════════════════
458
+
459
+ @polymarket.group("wallet")
460
+ def pm_wallet():
461
+ """Manage Polymarket wallet (create, import, show, reset).
462
+
463
+ The wallet private key is used to sign orders and on-chain transactions.
464
+ Stored in ~/.config/polymarket/config.json.
465
+ """
466
+ pass
467
+
468
+
469
+ @pm_wallet.command("create")
470
+ @click.option("--signature-type", type=click.Choice(["proxy", "eoa", "gnosis-safe"]), default="proxy")
471
+ @click.pass_context
472
+ def pm_wallet_create(ctx, signature_type):
473
+ """Generate a new random wallet.
474
+
475
+ Creates a new private key and saves it to config.
476
+
477
+ Example: fastbooks polymarket wallet create
478
+ """
479
+ client: FastBooksClient = ctx.obj
480
+ try:
481
+ data = client.polymarket_wallet_create(signature_type)
482
+ emit(ctx, data, lambda d: _format_wallet_info(d))
483
+ except Exception as e:
484
+ emit_error(ctx, str(e))
485
+ ctx.exit(1)
486
+
487
+
488
+ @pm_wallet.command("import")
489
+ @click.argument("private_key")
490
+ @click.option("--signature-type", type=click.Choice(["proxy", "eoa", "gnosis-safe"]), default="proxy")
491
+ @click.pass_context
492
+ def pm_wallet_import(ctx, private_key, signature_type):
493
+ """Import an existing private key.
494
+
495
+ Example: fastbooks polymarket wallet import 0xabc123...
496
+ """
497
+ client: FastBooksClient = ctx.obj
498
+ try:
499
+ data = client.polymarket_wallet_import(private_key, signature_type)
500
+ emit(ctx, data, lambda d: _format_wallet_info(d))
501
+ except Exception as e:
502
+ emit_error(ctx, str(e))
503
+ ctx.exit(1)
504
+
505
+
506
+ @pm_wallet.command("address")
507
+ @click.pass_context
508
+ def pm_wallet_address(ctx):
509
+ """Print the configured wallet address.
510
+
511
+ Example: fastbooks polymarket wallet address
512
+ """
513
+ client: FastBooksClient = ctx.obj
514
+ try:
515
+ data = client.polymarket_wallet_address()
516
+ emit(ctx, data)
517
+ except Exception as e:
518
+ emit_error(ctx, str(e))
519
+ ctx.exit(1)
520
+
521
+
522
+ @pm_wallet.command("show")
523
+ @click.pass_context
524
+ def pm_wallet_show(ctx):
525
+ """Show full wallet info (address, source, config path).
526
+
527
+ Example: fastbooks polymarket wallet show
528
+ """
529
+ client: FastBooksClient = ctx.obj
530
+ try:
531
+ data = client.polymarket_wallet_show()
532
+ emit(ctx, data, lambda d: _format_wallet_info(d))
533
+ except Exception as e:
534
+ emit_error(ctx, str(e))
535
+ ctx.exit(1)
536
+
537
+
538
+ @pm_wallet.command("reset")
539
+ @click.option("--confirm/--no-confirm", default=True)
540
+ @click.pass_context
541
+ def pm_wallet_reset(ctx, confirm):
542
+ """Delete wallet config (prompts for confirmation).
543
+
544
+ Example: fastbooks polymarket wallet reset
545
+ """
546
+ if confirm and not ctx.find_root().params.get("json_output", False):
547
+ if not click.confirm(" Delete Polymarket wallet config?"):
548
+ click.echo(" Cancelled.")
549
+ return
550
+ client: FastBooksClient = ctx.obj
551
+ try:
552
+ data = client.polymarket_wallet_reset()
553
+ emit(ctx, data, lambda d: " Wallet config deleted.")
554
+ except Exception as e:
555
+ emit_error(ctx, str(e))
556
+ ctx.exit(1)
557
+
558
+
559
+ # ══════════════════════════════════════════════════════════════════════════
560
+ # CLOB — Order Book & Prices (read-only, no wallet)
561
+ # ══════════════════════════════════════════════════════════════════════════
562
+
563
+ @polymarket.group("clob")
564
+ def pm_clob():
565
+ """CLOB order book: prices, books, trading, and account queries.
566
+
567
+ Read-only commands (book, price, midpoint, spread) work without a wallet.
568
+ Trading commands require --private-key or POLYMARKET_PRIVATE_KEY.
569
+ """
570
+ pass
571
+
572
+
573
+ @pm_clob.command("book")
574
+ @click.argument("token_id")
575
+ @click.pass_context
576
+ def pm_clob_book(ctx, token_id):
577
+ """Get order book for a token.
578
+
579
+ Example: fastbooks polymarket clob book 48331043336612883...
580
+ """
581
+ client: FastBooksClient = ctx.obj
582
+ try:
583
+ data = client.polymarket_clob_book(token_id)
584
+ emit(ctx, data, lambda d: _format_clob_book(d))
585
+ except Exception as e:
586
+ emit_error(ctx, str(e))
587
+ ctx.exit(1)
588
+
589
+
590
+ @pm_clob.command("price")
591
+ @click.argument("token_id")
592
+ @click.option("--side", type=click.Choice(["buy", "sell"]), default="buy", help="Price side.")
593
+ @click.pass_context
594
+ def pm_clob_price(ctx, token_id, side):
595
+ """Get best price for a token.
596
+
597
+ Example: fastbooks polymarket clob price TOKEN_ID --side buy
598
+ """
599
+ client: FastBooksClient = ctx.obj
600
+ try:
601
+ data = client.polymarket_clob_price(token_id, side)
602
+ emit(ctx, data)
603
+ except Exception as e:
604
+ emit_error(ctx, str(e))
605
+ ctx.exit(1)
606
+
607
+
608
+ @pm_clob.command("midpoint")
609
+ @click.argument("token_id")
610
+ @click.pass_context
611
+ def pm_clob_midpoint(ctx, token_id):
612
+ """Get midpoint price for a token.
613
+
614
+ Example: fastbooks polymarket clob midpoint TOKEN_ID
615
+ """
616
+ client: FastBooksClient = ctx.obj
617
+ try:
618
+ data = client.polymarket_clob_midpoint(token_id)
619
+ emit(ctx, data)
620
+ except Exception as e:
621
+ emit_error(ctx, str(e))
622
+ ctx.exit(1)
623
+
624
+
625
+ @pm_clob.command("spread")
626
+ @click.argument("token_id")
627
+ @click.pass_context
628
+ def pm_clob_spread(ctx, token_id):
629
+ """Get bid-ask spread for a token.
630
+
631
+ Example: fastbooks polymarket clob spread TOKEN_ID
632
+ """
633
+ client: FastBooksClient = ctx.obj
634
+ try:
635
+ data = client.polymarket_clob_spread(token_id)
636
+ emit(ctx, data)
637
+ except Exception as e:
638
+ emit_error(ctx, str(e))
639
+ ctx.exit(1)
640
+
641
+
642
+ @pm_clob.command("last-trade")
643
+ @click.argument("token_id")
644
+ @click.pass_context
645
+ def pm_clob_last_trade(ctx, token_id):
646
+ """Get last trade for a token.
647
+
648
+ Example: fastbooks polymarket clob last-trade TOKEN_ID
649
+ """
650
+ client: FastBooksClient = ctx.obj
651
+ try:
652
+ data = client.polymarket_clob_last_trade(token_id)
653
+ emit(ctx, data)
654
+ except Exception as e:
655
+ emit_error(ctx, str(e))
656
+ ctx.exit(1)
657
+
658
+
659
+ @pm_clob.command("price-history")
660
+ @click.argument("token_id")
661
+ @click.option("--interval", type=click.Choice(["1m", "1h", "6h", "1d", "1w", "max"]), default="1d")
662
+ @click.option("--fidelity", type=int, default=30, help="Number of data points.")
663
+ @click.pass_context
664
+ def pm_clob_price_history(ctx, token_id, interval, fidelity):
665
+ """Get price history for a token.
666
+
667
+ Examples:
668
+ fastbooks polymarket clob price-history TOKEN_ID --interval 1d --fidelity 30
669
+ fastbooks polymarket clob price-history TOKEN_ID --interval 1h
670
+ """
671
+ client: FastBooksClient = ctx.obj
672
+ try:
673
+ data = client.polymarket_clob_price_history(token_id, interval, fidelity)
674
+ emit(ctx, data, lambda d: _format_price_history(d))
675
+ except Exception as e:
676
+ emit_error(ctx, str(e))
677
+ ctx.exit(1)
678
+
679
+
680
+ @pm_clob.command("clob-markets")
681
+ @click.pass_context
682
+ def pm_clob_markets(ctx):
683
+ """List all CLOB markets.
684
+
685
+ Example: fastbooks polymarket clob clob-markets
686
+ """
687
+ client: FastBooksClient = ctx.obj
688
+ try:
689
+ data = client.polymarket_clob_markets()
690
+ emit(ctx, data)
691
+ except Exception as e:
692
+ emit_error(ctx, str(e))
693
+ ctx.exit(1)
694
+
695
+
696
+ @pm_clob.command("clob-market")
697
+ @click.argument("condition_id")
698
+ @click.pass_context
699
+ def pm_clob_market(ctx, condition_id):
700
+ """Get CLOB market info by condition ID.
701
+
702
+ Example: fastbooks polymarket clob clob-market 0xABC123...
703
+ """
704
+ client: FastBooksClient = ctx.obj
705
+ try:
706
+ data = client.polymarket_clob_market(condition_id)
707
+ emit(ctx, data)
708
+ except Exception as e:
709
+ emit_error(ctx, str(e))
710
+ ctx.exit(1)
711
+
712
+
713
+ # ── CLOB batch queries (read-only) ───────────────────────────────────
714
+
715
+ @pm_clob.command("ok")
716
+ @click.pass_context
717
+ def pm_clob_ok(ctx):
718
+ """Check CLOB API health.
719
+
720
+ Example: fastbooks polymarket clob ok
721
+ """
722
+ client: FastBooksClient = ctx.obj
723
+ try:
724
+ data = client.polymarket_clob_ok()
725
+ emit(ctx, data)
726
+ except Exception as e:
727
+ emit_error(ctx, str(e))
728
+ ctx.exit(1)
729
+
730
+
731
+ @pm_clob.command("books")
732
+ @click.argument("token_ids", metavar="TOKEN_IDS")
733
+ @click.pass_context
734
+ def pm_clob_books(ctx, token_ids):
735
+ """Get order books for multiple tokens (comma-separated).
736
+
737
+ Example: fastbooks polymarket clob books "TOKEN1,TOKEN2"
738
+ """
739
+ client: FastBooksClient = ctx.obj
740
+ try:
741
+ data = client.polymarket_clob_books(token_ids)
742
+ emit(ctx, data)
743
+ except Exception as e:
744
+ emit_error(ctx, str(e))
745
+ ctx.exit(1)
746
+
747
+
748
+ @pm_clob.command("batch-prices")
749
+ @click.argument("token_ids", metavar="TOKEN_IDS")
750
+ @click.option("--side", type=click.Choice(["buy", "sell"]), default="buy")
751
+ @click.pass_context
752
+ def pm_clob_batch_prices(ctx, token_ids, side):
753
+ """Get prices for multiple tokens (comma-separated).
754
+
755
+ Example: fastbooks polymarket clob batch-prices "TOKEN1,TOKEN2" --side buy
756
+ """
757
+ client: FastBooksClient = ctx.obj
758
+ try:
759
+ data = client.polymarket_clob_batch_prices(token_ids, side)
760
+ emit(ctx, data)
761
+ except Exception as e:
762
+ emit_error(ctx, str(e))
763
+ ctx.exit(1)
764
+
765
+
766
+ @pm_clob.command("midpoints")
767
+ @click.argument("token_ids", metavar="TOKEN_IDS")
768
+ @click.pass_context
769
+ def pm_clob_midpoints(ctx, token_ids):
770
+ """Get midpoints for multiple tokens (comma-separated).
771
+
772
+ Example: fastbooks polymarket clob midpoints "TOKEN1,TOKEN2"
773
+ """
774
+ client: FastBooksClient = ctx.obj
775
+ try:
776
+ data = client.polymarket_clob_midpoints(token_ids)
777
+ emit(ctx, data)
778
+ except Exception as e:
779
+ emit_error(ctx, str(e))
780
+ ctx.exit(1)
781
+
782
+
783
+ @pm_clob.command("spreads")
784
+ @click.argument("token_ids", metavar="TOKEN_IDS")
785
+ @click.pass_context
786
+ def pm_clob_spreads(ctx, token_ids):
787
+ """Get spreads for multiple tokens (comma-separated).
788
+
789
+ Example: fastbooks polymarket clob spreads "TOKEN1,TOKEN2"
790
+ """
791
+ client: FastBooksClient = ctx.obj
792
+ try:
793
+ data = client.polymarket_clob_spreads(token_ids)
794
+ emit(ctx, data)
795
+ except Exception as e:
796
+ emit_error(ctx, str(e))
797
+ ctx.exit(1)
798
+
799
+
800
+ @pm_clob.command("last-trades")
801
+ @click.argument("token_ids", metavar="TOKEN_IDS")
802
+ @click.pass_context
803
+ def pm_clob_last_trades(ctx, token_ids):
804
+ """Get last trades for multiple tokens (comma-separated).
805
+
806
+ Example: fastbooks polymarket clob last-trades "TOKEN1,TOKEN2"
807
+ """
808
+ client: FastBooksClient = ctx.obj
809
+ try:
810
+ data = client.polymarket_clob_last_trades(token_ids)
811
+ emit(ctx, data)
812
+ except Exception as e:
813
+ emit_error(ctx, str(e))
814
+ ctx.exit(1)
815
+
816
+
817
+ @pm_clob.command("tick-size")
818
+ @click.argument("token_id")
819
+ @click.pass_context
820
+ def pm_clob_tick_size(ctx, token_id):
821
+ """Get tick size for a token.
822
+
823
+ Example: fastbooks polymarket clob tick-size TOKEN_ID
824
+ """
825
+ client: FastBooksClient = ctx.obj
826
+ try:
827
+ data = client.polymarket_clob_tick_size(token_id)
828
+ emit(ctx, data)
829
+ except Exception as e:
830
+ emit_error(ctx, str(e))
831
+ ctx.exit(1)
832
+
833
+
834
+ @pm_clob.command("fee-rate")
835
+ @click.argument("token_id")
836
+ @click.pass_context
837
+ def pm_clob_fee_rate(ctx, token_id):
838
+ """Get fee rate (in bps) for a token.
839
+
840
+ Example: fastbooks polymarket clob fee-rate TOKEN_ID
841
+ """
842
+ client: FastBooksClient = ctx.obj
843
+ try:
844
+ data = client.polymarket_clob_fee_rate(token_id)
845
+ emit(ctx, data)
846
+ except Exception as e:
847
+ emit_error(ctx, str(e))
848
+ ctx.exit(1)
849
+
850
+
851
+ @pm_clob.command("neg-risk")
852
+ @click.argument("token_id")
853
+ @click.pass_context
854
+ def pm_clob_neg_risk(ctx, token_id):
855
+ """Get negative risk status for a token.
856
+
857
+ Example: fastbooks polymarket clob neg-risk TOKEN_ID
858
+ """
859
+ client: FastBooksClient = ctx.obj
860
+ try:
861
+ data = client.polymarket_clob_neg_risk(token_id)
862
+ emit(ctx, data)
863
+ except Exception as e:
864
+ emit_error(ctx, str(e))
865
+ ctx.exit(1)
866
+
867
+
868
+ @pm_clob.command("time")
869
+ @click.pass_context
870
+ def pm_clob_time(ctx):
871
+ """Get CLOB server time.
872
+
873
+ Example: fastbooks polymarket clob time
874
+ """
875
+ client: FastBooksClient = ctx.obj
876
+ try:
877
+ data = client.polymarket_clob_time()
878
+ emit(ctx, data)
879
+ except Exception as e:
880
+ emit_error(ctx, str(e))
881
+ ctx.exit(1)
882
+
883
+
884
+ @pm_clob.command("geoblock")
885
+ @click.pass_context
886
+ def pm_clob_geoblock(ctx):
887
+ """Check geoblock status.
888
+
889
+ Example: fastbooks polymarket clob geoblock
890
+ """
891
+ client: FastBooksClient = ctx.obj
892
+ try:
893
+ data = client.polymarket_clob_geoblock()
894
+ emit(ctx, data)
895
+ except Exception as e:
896
+ emit_error(ctx, str(e))
897
+ ctx.exit(1)
898
+
899
+
900
+ # ── CLOB trading (authenticated) ─────────────────────────────────────
901
+
902
+ @pm_clob.command("create-order")
903
+ @click.option("--token", required=True, help="Token ID to trade.")
904
+ @click.option("--side", type=click.Choice(["buy", "sell"]), required=True)
905
+ @click.option("--price", type=float, required=True, help="Limit price (0-1).")
906
+ @click.option("--size", type=float, required=True, help="Number of shares.")
907
+ @click.option("--order-type", type=click.Choice(["GTC", "FOK", "GTD", "FAK"]), default="GTC")
908
+ @click.option("--private-key", envvar="POLYMARKET_PRIVATE_KEY", default=None)
909
+ @click.option("--confirm/--no-confirm", default=True)
910
+ @click.pass_context
911
+ def pm_clob_create_order(ctx, token, side, price, size, order_type, private_key, confirm):
912
+ """Place a limit order on Polymarket.
913
+
914
+ Examples:
915
+ fastbooks polymarket clob create-order --token TOKEN --side buy --price 0.50 --size 10
916
+ fastbooks polymarket clob create-order --token TOKEN --side sell --price 0.80 --size 5 --order-type FOK
917
+ """
918
+ client: FastBooksClient = ctx.obj
919
+ if not private_key:
920
+ emit_error(ctx, "Wallet required. Use --private-key or set POLYMARKET_PRIVATE_KEY.")
921
+ ctx.exit(1)
922
+ return
923
+
924
+ if confirm and not ctx.find_root().params.get("json_output", False):
925
+ click.echo(f"\n Polymarket LIMIT {side.upper()} {size} shares @ ${price:.2f} ({order_type})")
926
+ click.echo(f" Token: {token[:20]}...")
927
+ if not click.confirm("\n Execute?"):
928
+ click.echo(" Cancelled.")
929
+ return
930
+
931
+ try:
932
+ data = client.polymarket_create_order(
933
+ token_id=token, side=side, price=price, size=size,
934
+ order_type=order_type, private_key=private_key,
935
+ )
936
+ emit(ctx, data, lambda d: _format_order_result(d))
937
+ except Exception as e:
938
+ emit_error(ctx, str(e))
939
+ ctx.exit(1)
940
+
941
+
942
+ @pm_clob.command("market-order")
943
+ @click.option("--token", required=True, help="Token ID to trade.")
944
+ @click.option("--side", type=click.Choice(["buy", "sell"]), required=True)
945
+ @click.option("--amount", type=float, required=True, help="Amount in USD to spend.")
946
+ @click.option("--private-key", envvar="POLYMARKET_PRIVATE_KEY", default=None)
947
+ @click.option("--confirm/--no-confirm", default=True)
948
+ @click.pass_context
949
+ def pm_clob_market_order(ctx, token, side, amount, private_key, confirm):
950
+ """Place a market order on Polymarket.
951
+
952
+ Examples:
953
+ fastbooks polymarket clob market-order --token TOKEN --side buy --amount 5
954
+ fastbooks polymarket clob market-order --token TOKEN --side sell --amount 10
955
+ """
956
+ client: FastBooksClient = ctx.obj
957
+ if not private_key:
958
+ emit_error(ctx, "Wallet required. Use --private-key or set POLYMARKET_PRIVATE_KEY.")
959
+ ctx.exit(1)
960
+ return
961
+
962
+ if confirm and not ctx.find_root().params.get("json_output", False):
963
+ click.echo(f"\n Polymarket MARKET {side.upper()} ${amount:.2f}")
964
+ click.echo(f" Token: {token[:20]}...")
965
+ if not click.confirm("\n Execute?"):
966
+ click.echo(" Cancelled.")
967
+ return
968
+
969
+ try:
970
+ data = client.polymarket_market_order(
971
+ token_id=token, side=side, amount=amount, private_key=private_key,
972
+ )
973
+ emit(ctx, data, lambda d: _format_order_result(d))
974
+ except Exception as e:
975
+ emit_error(ctx, str(e))
976
+ ctx.exit(1)
977
+
978
+
979
+ @pm_clob.command("cancel")
980
+ @click.argument("order_id")
981
+ @click.option("--private-key", envvar="POLYMARKET_PRIVATE_KEY", default=None)
982
+ @click.pass_context
983
+ def pm_clob_cancel(ctx, order_id, private_key):
984
+ """Cancel a specific order.
985
+
986
+ Example: fastbooks polymarket clob cancel ORDER_ID
987
+ """
988
+ client: FastBooksClient = ctx.obj
989
+ if not private_key:
990
+ emit_error(ctx, "Wallet required.")
991
+ ctx.exit(1)
992
+ return
993
+ try:
994
+ data = client.polymarket_cancel_order(order_id, private_key)
995
+ emit(ctx, data, lambda d: f" Order {order_id} cancelled.")
996
+ except Exception as e:
997
+ emit_error(ctx, str(e))
998
+ ctx.exit(1)
999
+
1000
+
1001
+ @pm_clob.command("cancel-all")
1002
+ @click.option("--private-key", envvar="POLYMARKET_PRIVATE_KEY", default=None)
1003
+ @click.option("--confirm/--no-confirm", default=True)
1004
+ @click.pass_context
1005
+ def pm_clob_cancel_all(ctx, private_key, confirm):
1006
+ """Cancel all open orders.
1007
+
1008
+ Example: fastbooks polymarket clob cancel-all
1009
+ """
1010
+ client: FastBooksClient = ctx.obj
1011
+ if not private_key:
1012
+ emit_error(ctx, "Wallet required.")
1013
+ ctx.exit(1)
1014
+ return
1015
+ if confirm and not ctx.find_root().params.get("json_output", False):
1016
+ if not click.confirm(" Cancel ALL open orders?"):
1017
+ click.echo(" Cancelled.")
1018
+ return
1019
+ try:
1020
+ data = client.polymarket_cancel_all(private_key)
1021
+ emit(ctx, data, lambda d: " All orders cancelled.")
1022
+ except Exception as e:
1023
+ emit_error(ctx, str(e))
1024
+ ctx.exit(1)
1025
+
1026
+
1027
+ @pm_clob.command("cancel-market")
1028
+ @click.argument("condition_id")
1029
+ @click.option("--private-key", envvar="POLYMARKET_PRIVATE_KEY", default=None)
1030
+ @click.pass_context
1031
+ def pm_clob_cancel_market(ctx, condition_id, private_key):
1032
+ """Cancel all orders for a specific market.
1033
+
1034
+ Example: fastbooks polymarket clob cancel-market 0xCONDITION...
1035
+ """
1036
+ client: FastBooksClient = ctx.obj
1037
+ if not private_key:
1038
+ emit_error(ctx, "Wallet required.")
1039
+ ctx.exit(1)
1040
+ return
1041
+ try:
1042
+ data = client.polymarket_cancel_market(condition_id, private_key)
1043
+ emit(ctx, data, lambda d: f" Orders for market {condition_id[:16]}... cancelled.")
1044
+ except Exception as e:
1045
+ emit_error(ctx, str(e))
1046
+ ctx.exit(1)
1047
+
1048
+
1049
+ @pm_clob.command("cancel-orders")
1050
+ @click.argument("order_ids", metavar="ORDER_IDS")
1051
+ @click.option("--private-key", envvar="POLYMARKET_PRIVATE_KEY", default=None)
1052
+ @click.pass_context
1053
+ def pm_clob_cancel_orders(ctx, order_ids, private_key):
1054
+ """Cancel multiple orders (comma-separated IDs).
1055
+
1056
+ Example: fastbooks polymarket clob cancel-orders "ORDER1,ORDER2"
1057
+ """
1058
+ client: FastBooksClient = ctx.obj
1059
+ if not private_key:
1060
+ emit_error(ctx, "Wallet required.")
1061
+ ctx.exit(1)
1062
+ return
1063
+ try:
1064
+ data = client.polymarket_cancel_orders(order_ids, private_key)
1065
+ emit(ctx, data, lambda d: f" Orders cancelled.")
1066
+ except Exception as e:
1067
+ emit_error(ctx, str(e))
1068
+ ctx.exit(1)
1069
+
1070
+
1071
+ @pm_clob.command("post-orders")
1072
+ @click.option("--tokens", required=True, help="Token IDs (comma-separated).")
1073
+ @click.option("--side", type=click.Choice(["buy", "sell"]), required=True)
1074
+ @click.option("--prices", required=True, help="Prices (comma-separated).")
1075
+ @click.option("--sizes", required=True, help="Sizes (comma-separated).")
1076
+ @click.option("--order-type", type=click.Choice(["GTC", "FOK", "GTD", "FAK"]), default="GTC")
1077
+ @click.option("--private-key", envvar="POLYMARKET_PRIVATE_KEY", default=None)
1078
+ @click.option("--confirm/--no-confirm", default=True)
1079
+ @click.pass_context
1080
+ def pm_clob_post_orders(ctx, tokens, side, prices, sizes, order_type, private_key, confirm):
1081
+ """Post multiple orders at once.
1082
+
1083
+ Example: fastbooks polymarket clob post-orders --tokens "T1,T2" --side buy --prices "0.40,0.60" --sizes "10,10"
1084
+ """
1085
+ client: FastBooksClient = ctx.obj
1086
+ if not private_key:
1087
+ emit_error(ctx, "Wallet required.")
1088
+ ctx.exit(1)
1089
+ return
1090
+ if confirm and not ctx.find_root().params.get("json_output", False):
1091
+ click.echo(f"\n Polymarket BATCH {side.upper()} ({order_type})")
1092
+ click.echo(f" Tokens: {tokens}")
1093
+ click.echo(f" Prices: {prices}")
1094
+ click.echo(f" Sizes: {sizes}")
1095
+ if not click.confirm("\n Execute all?"):
1096
+ click.echo(" Cancelled.")
1097
+ return
1098
+ try:
1099
+ data = client.polymarket_post_orders(tokens, side, prices, sizes, order_type, private_key)
1100
+ emit(ctx, data)
1101
+ except Exception as e:
1102
+ emit_error(ctx, str(e))
1103
+ ctx.exit(1)
1104
+
1105
+
1106
+ # ── CLOB account queries (authenticated) ─────────────────────────────
1107
+
1108
+ @pm_clob.command("orders")
1109
+ @click.option("--market", type=str, default=None, help="Filter by condition ID.")
1110
+ @click.option("--private-key", envvar="POLYMARKET_PRIVATE_KEY", default=None)
1111
+ @click.pass_context
1112
+ def pm_clob_orders(ctx, market, private_key):
1113
+ """List your open orders.
1114
+
1115
+ Examples:
1116
+ fastbooks polymarket clob orders
1117
+ fastbooks polymarket clob orders --market 0xCONDITION...
1118
+ """
1119
+ client: FastBooksClient = ctx.obj
1120
+ if not private_key:
1121
+ emit_error(ctx, "Wallet required.")
1122
+ ctx.exit(1)
1123
+ return
1124
+ try:
1125
+ data = client.polymarket_orders(market, private_key)
1126
+ emit(ctx, data, lambda d: _format_orders_list(d))
1127
+ except Exception as e:
1128
+ emit_error(ctx, str(e))
1129
+ ctx.exit(1)
1130
+
1131
+
1132
+ @pm_clob.command("trades")
1133
+ @click.option("--private-key", envvar="POLYMARKET_PRIVATE_KEY", default=None)
1134
+ @click.pass_context
1135
+ def pm_clob_trades(ctx, private_key):
1136
+ """List your recent trades.
1137
+
1138
+ Example: fastbooks polymarket clob trades
1139
+ """
1140
+ client: FastBooksClient = ctx.obj
1141
+ if not private_key:
1142
+ emit_error(ctx, "Wallet required.")
1143
+ ctx.exit(1)
1144
+ return
1145
+ try:
1146
+ data = client.polymarket_trades(private_key)
1147
+ emit(ctx, data, lambda d: _format_trades_list(d))
1148
+ except Exception as e:
1149
+ emit_error(ctx, str(e))
1150
+ ctx.exit(1)
1151
+
1152
+
1153
+ @pm_clob.command("balance")
1154
+ @click.option("--asset-type", type=click.Choice(["collateral", "conditional"]), default="collateral")
1155
+ @click.option("--token", type=str, default=None, help="Token ID (for conditional balance).")
1156
+ @click.option("--private-key", envvar="POLYMARKET_PRIVATE_KEY", default=None)
1157
+ @click.pass_context
1158
+ def pm_clob_balance(ctx, asset_type, token, private_key):
1159
+ """Check your Polymarket balance.
1160
+
1161
+ Examples:
1162
+ fastbooks polymarket clob balance --asset-type collateral
1163
+ fastbooks polymarket clob balance --asset-type conditional --token TOKEN_ID
1164
+ """
1165
+ client: FastBooksClient = ctx.obj
1166
+ if not private_key:
1167
+ emit_error(ctx, "Wallet required.")
1168
+ ctx.exit(1)
1169
+ return
1170
+ try:
1171
+ data = client.polymarket_balance(asset_type, token, private_key)
1172
+ emit(ctx, data, lambda d: _format_balance(d))
1173
+ except Exception as e:
1174
+ emit_error(ctx, str(e))
1175
+ ctx.exit(1)
1176
+
1177
+
1178
+ @pm_clob.command("order")
1179
+ @click.argument("order_id")
1180
+ @click.option("--private-key", envvar="POLYMARKET_PRIVATE_KEY", default=None)
1181
+ @click.pass_context
1182
+ def pm_clob_order_get(ctx, order_id, private_key):
1183
+ """Get a single order by ID.
1184
+
1185
+ Example: fastbooks polymarket clob order ORDER_ID
1186
+ """
1187
+ client: FastBooksClient = ctx.obj
1188
+ if not private_key:
1189
+ emit_error(ctx, "Wallet required.")
1190
+ ctx.exit(1)
1191
+ return
1192
+ try:
1193
+ data = client.polymarket_order_get(order_id, private_key)
1194
+ emit(ctx, data, lambda d: _format_order_result(d))
1195
+ except Exception as e:
1196
+ emit_error(ctx, str(e))
1197
+ ctx.exit(1)
1198
+
1199
+
1200
+ @pm_clob.command("update-balance")
1201
+ @click.option("--asset-type", type=click.Choice(["collateral", "conditional"]), default="collateral")
1202
+ @click.option("--token", type=str, default=None, help="Token ID (for conditional).")
1203
+ @click.option("--private-key", envvar="POLYMARKET_PRIVATE_KEY", default=None)
1204
+ @click.pass_context
1205
+ def pm_clob_update_balance(ctx, asset_type, token, private_key):
1206
+ """Refresh balance from on-chain.
1207
+
1208
+ Example: fastbooks polymarket clob update-balance --asset-type collateral
1209
+ """
1210
+ client: FastBooksClient = ctx.obj
1211
+ if not private_key:
1212
+ emit_error(ctx, "Wallet required.")
1213
+ ctx.exit(1)
1214
+ return
1215
+ try:
1216
+ data = client.polymarket_update_balance(asset_type, token, private_key)
1217
+ emit(ctx, data)
1218
+ except Exception as e:
1219
+ emit_error(ctx, str(e))
1220
+ ctx.exit(1)
1221
+
1222
+
1223
+ # ── CLOB rewards & API keys (authenticated) ──────────────────────────
1224
+
1225
+ @pm_clob.command("rewards")
1226
+ @click.option("--date", required=True, help="Date (YYYY-MM-DD).")
1227
+ @click.option("--private-key", envvar="POLYMARKET_PRIVATE_KEY", default=None)
1228
+ @click.pass_context
1229
+ def pm_clob_rewards(ctx, date, private_key):
1230
+ """List earnings for a date.
1231
+
1232
+ Example: fastbooks polymarket clob rewards --date 2024-06-15
1233
+ """
1234
+ client: FastBooksClient = ctx.obj
1235
+ if not private_key:
1236
+ emit_error(ctx, "Wallet required.")
1237
+ ctx.exit(1)
1238
+ return
1239
+ try:
1240
+ data = client.polymarket_rewards(date, private_key)
1241
+ emit(ctx, data)
1242
+ except Exception as e:
1243
+ emit_error(ctx, str(e))
1244
+ ctx.exit(1)
1245
+
1246
+
1247
+ @pm_clob.command("earnings")
1248
+ @click.option("--date", required=True, help="Date (YYYY-MM-DD).")
1249
+ @click.option("--private-key", envvar="POLYMARKET_PRIVATE_KEY", default=None)
1250
+ @click.pass_context
1251
+ def pm_clob_earnings(ctx, date, private_key):
1252
+ """Get total earnings for a date.
1253
+
1254
+ Example: fastbooks polymarket clob earnings --date 2024-06-15
1255
+ """
1256
+ client: FastBooksClient = ctx.obj
1257
+ if not private_key:
1258
+ emit_error(ctx, "Wallet required.")
1259
+ ctx.exit(1)
1260
+ return
1261
+ try:
1262
+ data = client.polymarket_earnings(date, private_key)
1263
+ emit(ctx, data)
1264
+ except Exception as e:
1265
+ emit_error(ctx, str(e))
1266
+ ctx.exit(1)
1267
+
1268
+
1269
+ @pm_clob.command("earnings-markets")
1270
+ @click.option("--date", required=True, help="Date (YYYY-MM-DD).")
1271
+ @click.option("--private-key", envvar="POLYMARKET_PRIVATE_KEY", default=None)
1272
+ @click.pass_context
1273
+ def pm_clob_earnings_markets(ctx, date, private_key):
1274
+ """Get earnings with market configs for a date.
1275
+
1276
+ Example: fastbooks polymarket clob earnings-markets --date 2024-06-15
1277
+ """
1278
+ client: FastBooksClient = ctx.obj
1279
+ if not private_key:
1280
+ emit_error(ctx, "Wallet required.")
1281
+ ctx.exit(1)
1282
+ return
1283
+ try:
1284
+ data = client.polymarket_earnings_markets(date, private_key)
1285
+ emit(ctx, data)
1286
+ except Exception as e:
1287
+ emit_error(ctx, str(e))
1288
+ ctx.exit(1)
1289
+
1290
+
1291
+ @pm_clob.command("reward-percentages")
1292
+ @click.pass_context
1293
+ def pm_clob_reward_percentages(ctx):
1294
+ """Get reward percentages.
1295
+
1296
+ Example: fastbooks polymarket clob reward-percentages
1297
+ """
1298
+ client: FastBooksClient = ctx.obj
1299
+ try:
1300
+ data = client.polymarket_reward_percentages()
1301
+ emit(ctx, data)
1302
+ except Exception as e:
1303
+ emit_error(ctx, str(e))
1304
+ ctx.exit(1)
1305
+
1306
+
1307
+ @pm_clob.command("current-rewards")
1308
+ @click.pass_context
1309
+ def pm_clob_current_rewards(ctx):
1310
+ """List current reward programs.
1311
+
1312
+ Example: fastbooks polymarket clob current-rewards
1313
+ """
1314
+ client: FastBooksClient = ctx.obj
1315
+ try:
1316
+ data = client.polymarket_current_rewards()
1317
+ emit(ctx, data)
1318
+ except Exception as e:
1319
+ emit_error(ctx, str(e))
1320
+ ctx.exit(1)
1321
+
1322
+
1323
+ @pm_clob.command("market-reward")
1324
+ @click.argument("condition_id")
1325
+ @click.pass_context
1326
+ def pm_clob_market_reward(ctx, condition_id):
1327
+ """Get reward details for a market.
1328
+
1329
+ Example: fastbooks polymarket clob market-reward 0xCONDITION...
1330
+ """
1331
+ client: FastBooksClient = ctx.obj
1332
+ try:
1333
+ data = client.polymarket_market_reward(condition_id)
1334
+ emit(ctx, data)
1335
+ except Exception as e:
1336
+ emit_error(ctx, str(e))
1337
+ ctx.exit(1)
1338
+
1339
+
1340
+ @pm_clob.command("order-scoring")
1341
+ @click.argument("order_id")
1342
+ @click.option("--private-key", envvar="POLYMARKET_PRIVATE_KEY", default=None)
1343
+ @click.pass_context
1344
+ def pm_clob_order_scoring(ctx, order_id, private_key):
1345
+ """Check if an order is scoring rewards.
1346
+
1347
+ Example: fastbooks polymarket clob order-scoring ORDER_ID
1348
+ """
1349
+ client: FastBooksClient = ctx.obj
1350
+ try:
1351
+ data = client.polymarket_order_scoring(order_id, private_key)
1352
+ emit(ctx, data)
1353
+ except Exception as e:
1354
+ emit_error(ctx, str(e))
1355
+ ctx.exit(1)
1356
+
1357
+
1358
+ @pm_clob.command("orders-scoring")
1359
+ @click.argument("order_ids", metavar="ORDER_IDS")
1360
+ @click.option("--private-key", envvar="POLYMARKET_PRIVATE_KEY", default=None)
1361
+ @click.pass_context
1362
+ def pm_clob_orders_scoring(ctx, order_ids, private_key):
1363
+ """Check multiple orders for reward scoring (comma-separated).
1364
+
1365
+ Example: fastbooks polymarket clob orders-scoring "ORDER1,ORDER2"
1366
+ """
1367
+ client: FastBooksClient = ctx.obj
1368
+ try:
1369
+ data = client.polymarket_orders_scoring(order_ids, private_key)
1370
+ emit(ctx, data)
1371
+ except Exception as e:
1372
+ emit_error(ctx, str(e))
1373
+ ctx.exit(1)
1374
+
1375
+
1376
+ @pm_clob.command("api-keys")
1377
+ @click.option("--private-key", envvar="POLYMARKET_PRIVATE_KEY", default=None)
1378
+ @click.pass_context
1379
+ def pm_clob_api_keys(ctx, private_key):
1380
+ """List your API keys.
1381
+
1382
+ Example: fastbooks polymarket clob api-keys
1383
+ """
1384
+ client: FastBooksClient = ctx.obj
1385
+ if not private_key:
1386
+ emit_error(ctx, "Wallet required.")
1387
+ ctx.exit(1)
1388
+ return
1389
+ try:
1390
+ data = client.polymarket_api_keys(private_key)
1391
+ emit(ctx, data)
1392
+ except Exception as e:
1393
+ emit_error(ctx, str(e))
1394
+ ctx.exit(1)
1395
+
1396
+
1397
+ @pm_clob.command("create-api-key")
1398
+ @click.option("--private-key", envvar="POLYMARKET_PRIVATE_KEY", default=None)
1399
+ @click.pass_context
1400
+ def pm_clob_create_api_key(ctx, private_key):
1401
+ """Create or derive a new API key.
1402
+
1403
+ Example: fastbooks polymarket clob create-api-key
1404
+ """
1405
+ client: FastBooksClient = ctx.obj
1406
+ if not private_key:
1407
+ emit_error(ctx, "Wallet required.")
1408
+ ctx.exit(1)
1409
+ return
1410
+ try:
1411
+ data = client.polymarket_create_api_key(private_key)
1412
+ emit(ctx, data)
1413
+ except Exception as e:
1414
+ emit_error(ctx, str(e))
1415
+ ctx.exit(1)
1416
+
1417
+
1418
+ @pm_clob.command("delete-api-key")
1419
+ @click.option("--private-key", envvar="POLYMARKET_PRIVATE_KEY", default=None)
1420
+ @click.option("--confirm/--no-confirm", default=True)
1421
+ @click.pass_context
1422
+ def pm_clob_delete_api_key(ctx, private_key, confirm):
1423
+ """Delete current API key.
1424
+
1425
+ Example: fastbooks polymarket clob delete-api-key
1426
+ """
1427
+ client: FastBooksClient = ctx.obj
1428
+ if not private_key:
1429
+ emit_error(ctx, "Wallet required.")
1430
+ ctx.exit(1)
1431
+ return
1432
+ if confirm and not ctx.find_root().params.get("json_output", False):
1433
+ if not click.confirm(" Delete API key?"):
1434
+ click.echo(" Cancelled.")
1435
+ return
1436
+ try:
1437
+ data = client.polymarket_delete_api_key(private_key)
1438
+ emit(ctx, data, lambda d: " API key deleted.")
1439
+ except Exception as e:
1440
+ emit_error(ctx, str(e))
1441
+ ctx.exit(1)
1442
+
1443
+
1444
+ @pm_clob.command("account-status")
1445
+ @click.option("--private-key", envvar="POLYMARKET_PRIVATE_KEY", default=None)
1446
+ @click.pass_context
1447
+ def pm_clob_account_status(ctx, private_key):
1448
+ """Check account status.
1449
+
1450
+ Example: fastbooks polymarket clob account-status
1451
+ """
1452
+ client: FastBooksClient = ctx.obj
1453
+ if not private_key:
1454
+ emit_error(ctx, "Wallet required.")
1455
+ ctx.exit(1)
1456
+ return
1457
+ try:
1458
+ data = client.polymarket_account_status(private_key)
1459
+ emit(ctx, data)
1460
+ except Exception as e:
1461
+ emit_error(ctx, str(e))
1462
+ ctx.exit(1)
1463
+
1464
+
1465
+ @pm_clob.command("notifications")
1466
+ @click.option("--private-key", envvar="POLYMARKET_PRIVATE_KEY", default=None)
1467
+ @click.pass_context
1468
+ def pm_clob_notifications(ctx, private_key):
1469
+ """List notifications.
1470
+
1471
+ Example: fastbooks polymarket clob notifications
1472
+ """
1473
+ client: FastBooksClient = ctx.obj
1474
+ if not private_key:
1475
+ emit_error(ctx, "Wallet required.")
1476
+ ctx.exit(1)
1477
+ return
1478
+ try:
1479
+ data = client.polymarket_notifications(private_key)
1480
+ emit(ctx, data)
1481
+ except Exception as e:
1482
+ emit_error(ctx, str(e))
1483
+ ctx.exit(1)
1484
+
1485
+
1486
+ @pm_clob.command("delete-notifications")
1487
+ @click.argument("notification_ids", metavar="NOTIFICATION_IDS")
1488
+ @click.option("--private-key", envvar="POLYMARKET_PRIVATE_KEY", default=None)
1489
+ @click.pass_context
1490
+ def pm_clob_delete_notifications(ctx, notification_ids, private_key):
1491
+ """Delete notifications (comma-separated IDs).
1492
+
1493
+ Example: fastbooks polymarket clob delete-notifications "NOTIF1,NOTIF2"
1494
+ """
1495
+ client: FastBooksClient = ctx.obj
1496
+ if not private_key:
1497
+ emit_error(ctx, "Wallet required.")
1498
+ ctx.exit(1)
1499
+ return
1500
+ try:
1501
+ data = client.polymarket_delete_notifications(notification_ids, private_key)
1502
+ emit(ctx, data, lambda d: " Notifications deleted.")
1503
+ except Exception as e:
1504
+ emit_error(ctx, str(e))
1505
+ ctx.exit(1)
1506
+
1507
+
1508
+ # ══════════════════════════════════════════════════════════════════════════
1509
+ # Data — on-chain portfolio & leaderboards (public, no wallet)
1510
+ # ══════════════════════════════════════════════════════════════════════════
1511
+
1512
+ @polymarket.group("data")
1513
+ def pm_data():
1514
+ """On-chain data: positions, portfolio value, trades, leaderboards."""
1515
+ pass
1516
+
1517
+
1518
+ @pm_data.command("positions")
1519
+ @click.argument("address")
1520
+ @click.pass_context
1521
+ def pm_data_positions(ctx, address):
1522
+ """View positions for a wallet address.
1523
+
1524
+ Example: fastbooks polymarket data positions 0xWALLET_ADDRESS
1525
+ """
1526
+ client: FastBooksClient = ctx.obj
1527
+ try:
1528
+ data = client.polymarket_data_positions(address)
1529
+ emit(ctx, data, lambda d: _format_positions(d))
1530
+ except Exception as e:
1531
+ emit_error(ctx, str(e))
1532
+ ctx.exit(1)
1533
+
1534
+
1535
+ @pm_data.command("value")
1536
+ @click.argument("address")
1537
+ @click.pass_context
1538
+ def pm_data_value(ctx, address):
1539
+ """Get portfolio value for a wallet address.
1540
+
1541
+ Example: fastbooks polymarket data value 0xWALLET_ADDRESS
1542
+ """
1543
+ client: FastBooksClient = ctx.obj
1544
+ try:
1545
+ data = client.polymarket_data_value(address)
1546
+ emit(ctx, data)
1547
+ except Exception as e:
1548
+ emit_error(ctx, str(e))
1549
+ ctx.exit(1)
1550
+
1551
+
1552
+ @pm_data.command("trades")
1553
+ @click.argument("address")
1554
+ @click.option("--limit", "-n", type=int, default=50)
1555
+ @click.pass_context
1556
+ def pm_data_trades(ctx, address, limit):
1557
+ """View trade history for a wallet address.
1558
+
1559
+ Example: fastbooks polymarket data trades 0xWALLET_ADDRESS --limit 20
1560
+ """
1561
+ client: FastBooksClient = ctx.obj
1562
+ try:
1563
+ data = client.polymarket_data_trades(address, limit)
1564
+ emit(ctx, data, lambda d: _format_trades_list(d))
1565
+ except Exception as e:
1566
+ emit_error(ctx, str(e))
1567
+ ctx.exit(1)
1568
+
1569
+
1570
+ @pm_data.command("leaderboard")
1571
+ @click.option("--period", type=click.Choice(["day", "week", "month", "all"]), default="month")
1572
+ @click.option("--order-by", type=click.Choice(["pnl", "volume", "markets_traded"]), default="pnl")
1573
+ @click.option("--limit", "-n", type=int, default=20)
1574
+ @click.pass_context
1575
+ def pm_data_leaderboard(ctx, period, order_by, limit):
1576
+ """View the Polymarket leaderboard.
1577
+
1578
+ Examples:
1579
+ fastbooks polymarket data leaderboard --period month --order-by pnl
1580
+ fastbooks polymarket data leaderboard --period week --limit 10
1581
+ """
1582
+ client: FastBooksClient = ctx.obj
1583
+ try:
1584
+ data = client.polymarket_data_leaderboard(period, order_by, limit)
1585
+ emit(ctx, data, lambda d: _format_leaderboard(d))
1586
+ except Exception as e:
1587
+ emit_error(ctx, str(e))
1588
+ ctx.exit(1)
1589
+
1590
+
1591
+ @pm_data.command("closed-positions")
1592
+ @click.argument("address")
1593
+ @click.option("--limit", "-n", type=int, default=25)
1594
+ @click.option("--offset", type=int, default=0)
1595
+ @click.pass_context
1596
+ def pm_data_closed_positions(ctx, address, limit, offset):
1597
+ """View closed positions for a wallet.
1598
+
1599
+ Example: fastbooks polymarket data closed-positions 0xWALLET
1600
+ """
1601
+ client: FastBooksClient = ctx.obj
1602
+ try:
1603
+ data = client.polymarket_data_closed_positions(address, limit, offset)
1604
+ emit(ctx, data, lambda d: _format_positions(d))
1605
+ except Exception as e:
1606
+ emit_error(ctx, str(e))
1607
+ ctx.exit(1)
1608
+
1609
+
1610
+ @pm_data.command("traded")
1611
+ @click.argument("address")
1612
+ @click.pass_context
1613
+ def pm_data_traded(ctx, address):
1614
+ """Get count of unique markets traded by a wallet.
1615
+
1616
+ Example: fastbooks polymarket data traded 0xWALLET
1617
+ """
1618
+ client: FastBooksClient = ctx.obj
1619
+ try:
1620
+ data = client.polymarket_data_traded(address)
1621
+ emit(ctx, data)
1622
+ except Exception as e:
1623
+ emit_error(ctx, str(e))
1624
+ ctx.exit(1)
1625
+
1626
+
1627
+ @pm_data.command("activity")
1628
+ @click.argument("address")
1629
+ @click.option("--limit", "-n", type=int, default=25)
1630
+ @click.option("--offset", type=int, default=0)
1631
+ @click.pass_context
1632
+ def pm_data_activity(ctx, address, limit, offset):
1633
+ """View on-chain activity for a wallet.
1634
+
1635
+ Example: fastbooks polymarket data activity 0xWALLET
1636
+ """
1637
+ client: FastBooksClient = ctx.obj
1638
+ try:
1639
+ data = client.polymarket_data_activity(address, limit, offset)
1640
+ emit(ctx, data)
1641
+ except Exception as e:
1642
+ emit_error(ctx, str(e))
1643
+ ctx.exit(1)
1644
+
1645
+
1646
+ @pm_data.command("holders")
1647
+ @click.argument("condition_id")
1648
+ @click.option("--limit", "-n", type=int, default=10)
1649
+ @click.pass_context
1650
+ def pm_data_holders(ctx, condition_id, limit):
1651
+ """Get top token holders for a market.
1652
+
1653
+ Example: fastbooks polymarket data holders 0xCONDITION_ID
1654
+ """
1655
+ client: FastBooksClient = ctx.obj
1656
+ try:
1657
+ data = client.polymarket_data_holders(condition_id, limit)
1658
+ emit(ctx, data)
1659
+ except Exception as e:
1660
+ emit_error(ctx, str(e))
1661
+ ctx.exit(1)
1662
+
1663
+
1664
+ @pm_data.command("open-interest")
1665
+ @click.argument("condition_id")
1666
+ @click.pass_context
1667
+ def pm_data_open_interest(ctx, condition_id):
1668
+ """Get open interest for a market.
1669
+
1670
+ Example: fastbooks polymarket data open-interest 0xCONDITION_ID
1671
+ """
1672
+ client: FastBooksClient = ctx.obj
1673
+ try:
1674
+ data = client.polymarket_data_open_interest(condition_id)
1675
+ emit(ctx, data)
1676
+ except Exception as e:
1677
+ emit_error(ctx, str(e))
1678
+ ctx.exit(1)
1679
+
1680
+
1681
+ @pm_data.command("volume")
1682
+ @click.argument("event_id")
1683
+ @click.pass_context
1684
+ def pm_data_volume(ctx, event_id):
1685
+ """Get live volume for an event.
1686
+
1687
+ Example: fastbooks polymarket data volume 12345
1688
+ """
1689
+ client: FastBooksClient = ctx.obj
1690
+ try:
1691
+ data = client.polymarket_data_volume(event_id)
1692
+ emit(ctx, data)
1693
+ except Exception as e:
1694
+ emit_error(ctx, str(e))
1695
+ ctx.exit(1)
1696
+
1697
+
1698
+ @pm_data.command("builder-leaderboard")
1699
+ @click.option("--period", type=click.Choice(["day", "week", "month", "all"]), default="month")
1700
+ @click.option("--limit", "-n", type=int, default=25)
1701
+ @click.option("--offset", type=int, default=0)
1702
+ @click.pass_context
1703
+ def pm_data_builder_leaderboard(ctx, period, limit, offset):
1704
+ """View builder leaderboard.
1705
+
1706
+ Example: fastbooks polymarket data builder-leaderboard --period week
1707
+ """
1708
+ client: FastBooksClient = ctx.obj
1709
+ try:
1710
+ data = client.polymarket_data_builder_leaderboard(period, limit, offset)
1711
+ emit(ctx, data)
1712
+ except Exception as e:
1713
+ emit_error(ctx, str(e))
1714
+ ctx.exit(1)
1715
+
1716
+
1717
+ @pm_data.command("builder-volume")
1718
+ @click.option("--period", type=click.Choice(["day", "week", "month", "all"]), default="month")
1719
+ @click.pass_context
1720
+ def pm_data_builder_volume(ctx, period):
1721
+ """Get builder volume time-series.
1722
+
1723
+ Example: fastbooks polymarket data builder-volume --period month
1724
+ """
1725
+ client: FastBooksClient = ctx.obj
1726
+ try:
1727
+ data = client.polymarket_data_builder_volume(period)
1728
+ emit(ctx, data)
1729
+ except Exception as e:
1730
+ emit_error(ctx, str(e))
1731
+ ctx.exit(1)
1732
+
1733
+
1734
+ # ══════════════════════════════════════════════════════════════════════════
1735
+ # CTF — Conditional Token Framework (on-chain, authenticated)
1736
+ # ══════════════════════════════════════════════════════════════════════════
1737
+
1738
+ @polymarket.group("ctf")
1739
+ def pm_ctf():
1740
+ """CTF token operations: split, merge, redeem conditional tokens on-chain.
1741
+
1742
+ Requires MATIC for gas on Polygon. Amounts are in USDC.
1743
+ """
1744
+ pass
1745
+
1746
+
1747
+ @pm_ctf.command("split")
1748
+ @click.option("--condition", required=True, help="Condition ID.")
1749
+ @click.option("--amount", type=float, required=True, help="Amount in USDC to split.")
1750
+ @click.option("--private-key", envvar="POLYMARKET_PRIVATE_KEY", default=None)
1751
+ @click.option("--confirm/--no-confirm", default=True)
1752
+ @click.pass_context
1753
+ def pm_ctf_split(ctx, condition, amount, private_key, confirm):
1754
+ """Split USDC into YES/NO conditional tokens.
1755
+
1756
+ Example: fastbooks polymarket ctf split --condition 0xCOND... --amount 10
1757
+ """
1758
+ client: FastBooksClient = ctx.obj
1759
+ if not private_key:
1760
+ emit_error(ctx, "Wallet required.")
1761
+ ctx.exit(1)
1762
+ return
1763
+ if confirm and not ctx.find_root().params.get("json_output", False):
1764
+ click.echo(f"\n Split ${amount} USDC into YES/NO tokens")
1765
+ click.echo(f" Condition: {condition[:20]}...")
1766
+ if not click.confirm("\n Execute? (requires MATIC for gas)"):
1767
+ click.echo(" Cancelled.")
1768
+ return
1769
+ try:
1770
+ data = client.polymarket_ctf_split(condition, amount, private_key)
1771
+ emit(ctx, data, lambda d: f" Split complete. Tx: {d.get('txHash', '?')}")
1772
+ except Exception as e:
1773
+ emit_error(ctx, str(e))
1774
+ ctx.exit(1)
1775
+
1776
+
1777
+ @pm_ctf.command("merge")
1778
+ @click.option("--condition", required=True, help="Condition ID.")
1779
+ @click.option("--amount", type=float, required=True, help="Amount to merge back to USDC.")
1780
+ @click.option("--private-key", envvar="POLYMARKET_PRIVATE_KEY", default=None)
1781
+ @click.option("--confirm/--no-confirm", default=True)
1782
+ @click.pass_context
1783
+ def pm_ctf_merge(ctx, condition, amount, private_key, confirm):
1784
+ """Merge YES/NO tokens back into USDC.
1785
+
1786
+ Example: fastbooks polymarket ctf merge --condition 0xCOND... --amount 10
1787
+ """
1788
+ client: FastBooksClient = ctx.obj
1789
+ if not private_key:
1790
+ emit_error(ctx, "Wallet required.")
1791
+ ctx.exit(1)
1792
+ return
1793
+ if confirm and not ctx.find_root().params.get("json_output", False):
1794
+ click.echo(f"\n Merge ${amount} YES/NO tokens back to USDC")
1795
+ if not click.confirm("\n Execute?"):
1796
+ click.echo(" Cancelled.")
1797
+ return
1798
+ try:
1799
+ data = client.polymarket_ctf_merge(condition, amount, private_key)
1800
+ emit(ctx, data, lambda d: f" Merge complete. Tx: {d.get('txHash', '?')}")
1801
+ except Exception as e:
1802
+ emit_error(ctx, str(e))
1803
+ ctx.exit(1)
1804
+
1805
+
1806
+ @pm_ctf.command("redeem")
1807
+ @click.option("--condition", required=True, help="Condition ID (resolved market).")
1808
+ @click.option("--private-key", envvar="POLYMARKET_PRIVATE_KEY", default=None)
1809
+ @click.option("--confirm/--no-confirm", default=True)
1810
+ @click.pass_context
1811
+ def pm_ctf_redeem(ctx, condition, private_key, confirm):
1812
+ """Redeem winning tokens after market resolution.
1813
+
1814
+ Example: fastbooks polymarket ctf redeem --condition 0xCOND...
1815
+ """
1816
+ client: FastBooksClient = ctx.obj
1817
+ if not private_key:
1818
+ emit_error(ctx, "Wallet required.")
1819
+ ctx.exit(1)
1820
+ return
1821
+ if confirm and not ctx.find_root().params.get("json_output", False):
1822
+ click.echo(f"\n Redeem winning tokens for condition {condition[:20]}...")
1823
+ if not click.confirm("\n Execute?"):
1824
+ click.echo(" Cancelled.")
1825
+ return
1826
+ try:
1827
+ data = client.polymarket_ctf_redeem(condition, private_key)
1828
+ emit(ctx, data, lambda d: f" Redemption complete. Tx: {d.get('txHash', '?')}")
1829
+ except Exception as e:
1830
+ emit_error(ctx, str(e))
1831
+ ctx.exit(1)
1832
+
1833
+
1834
+ @pm_ctf.command("redeem-neg-risk")
1835
+ @click.option("--condition", required=True, help="Condition ID.")
1836
+ @click.option("--amounts", required=True, help="Amounts (comma-separated).")
1837
+ @click.option("--private-key", envvar="POLYMARKET_PRIVATE_KEY", default=None)
1838
+ @click.option("--confirm/--no-confirm", default=True)
1839
+ @click.pass_context
1840
+ def pm_ctf_redeem_neg_risk(ctx, condition, amounts, private_key, confirm):
1841
+ """Redeem neg-risk positions.
1842
+
1843
+ Example: fastbooks polymarket ctf redeem-neg-risk --condition 0xCOND... --amounts "10,5"
1844
+ """
1845
+ client: FastBooksClient = ctx.obj
1846
+ if not private_key:
1847
+ emit_error(ctx, "Wallet required.")
1848
+ ctx.exit(1)
1849
+ return
1850
+ if confirm and not ctx.find_root().params.get("json_output", False):
1851
+ click.echo(f"\n Redeem neg-risk for {condition[:20]}... amounts: {amounts}")
1852
+ if not click.confirm("\n Execute?"):
1853
+ click.echo(" Cancelled.")
1854
+ return
1855
+ try:
1856
+ data = client.polymarket_ctf_redeem_neg_risk(condition, amounts, private_key)
1857
+ emit(ctx, data, lambda d: f" Neg-risk redemption complete. Tx: {d.get('txHash', '?')}")
1858
+ except Exception as e:
1859
+ emit_error(ctx, str(e))
1860
+ ctx.exit(1)
1861
+
1862
+
1863
+ @pm_ctf.command("condition-id")
1864
+ @click.option("--oracle", required=True, help="Oracle address.")
1865
+ @click.option("--question", required=True, help="Question ID.")
1866
+ @click.option("--outcomes", required=True, type=int, help="Number of outcomes.")
1867
+ @click.pass_context
1868
+ def pm_ctf_condition_id(ctx, oracle, question, outcomes):
1869
+ """Calculate condition ID (read-only).
1870
+
1871
+ Example: fastbooks polymarket ctf condition-id --oracle 0xORACLE... --question 0xQUESTION... --outcomes 2
1872
+ """
1873
+ client: FastBooksClient = ctx.obj
1874
+ try:
1875
+ data = client.polymarket_ctf_condition_id(oracle, question, outcomes)
1876
+ emit(ctx, data)
1877
+ except Exception as e:
1878
+ emit_error(ctx, str(e))
1879
+ ctx.exit(1)
1880
+
1881
+
1882
+ @pm_ctf.command("collection-id")
1883
+ @click.option("--condition", required=True, help="Condition ID.")
1884
+ @click.option("--index-set", required=True, type=int, help="Index set.")
1885
+ @click.option("--parent-collection", default=None, help="Parent collection ID.")
1886
+ @click.pass_context
1887
+ def pm_ctf_collection_id(ctx, condition, index_set, parent_collection):
1888
+ """Calculate collection ID (read-only).
1889
+
1890
+ Example: fastbooks polymarket ctf collection-id --condition 0xCOND... --index-set 1
1891
+ """
1892
+ client: FastBooksClient = ctx.obj
1893
+ try:
1894
+ data = client.polymarket_ctf_collection_id(condition, index_set, parent_collection)
1895
+ emit(ctx, data)
1896
+ except Exception as e:
1897
+ emit_error(ctx, str(e))
1898
+ ctx.exit(1)
1899
+
1900
+
1901
+ @pm_ctf.command("position-id")
1902
+ @click.option("--collateral", required=True, help="Collateral address.")
1903
+ @click.option("--collection", required=True, help="Collection ID.")
1904
+ @click.pass_context
1905
+ def pm_ctf_position_id(ctx, collateral, collection):
1906
+ """Calculate position ID (read-only).
1907
+
1908
+ Example: fastbooks polymarket ctf position-id --collateral 0xUSDC... --collection 0xCOLL...
1909
+ """
1910
+ client: FastBooksClient = ctx.obj
1911
+ try:
1912
+ data = client.polymarket_ctf_position_id(collateral, collection)
1913
+ emit(ctx, data)
1914
+ except Exception as e:
1915
+ emit_error(ctx, str(e))
1916
+ ctx.exit(1)
1917
+
1918
+
1919
+ # ══════════════════════════════════════════════════════════════════════════
1920
+ # Approve — contract approvals
1921
+ # ══════════════════════════════════════════════════════════════════════════
1922
+
1923
+ @polymarket.group("approve")
1924
+ def pm_approve():
1925
+ """Manage Polymarket contract approvals (ERC-20 & ERC-1155)."""
1926
+ pass
1927
+
1928
+
1929
+ @pm_approve.command("check")
1930
+ @click.option("--address", type=str, default=None, help="Address to check (defaults to your wallet).")
1931
+ @click.pass_context
1932
+ def pm_approve_check(ctx, address):
1933
+ """Check current contract approval status.
1934
+
1935
+ Example: fastbooks polymarket approve check
1936
+ """
1937
+ client: FastBooksClient = ctx.obj
1938
+ try:
1939
+ data = client.polymarket_approve_check(address)
1940
+ emit(ctx, data)
1941
+ except Exception as e:
1942
+ emit_error(ctx, str(e))
1943
+ ctx.exit(1)
1944
+
1945
+
1946
+ @pm_approve.command("set")
1947
+ @click.option("--private-key", envvar="POLYMARKET_PRIVATE_KEY", default=None)
1948
+ @click.option("--confirm/--no-confirm", default=True)
1949
+ @click.pass_context
1950
+ def pm_approve_set(ctx, private_key, confirm):
1951
+ """Approve all Polymarket contracts (sends on-chain transactions).
1952
+
1953
+ Requires MATIC for gas. Sends 6 approval transactions.
1954
+
1955
+ Example: fastbooks polymarket approve set
1956
+ """
1957
+ client: FastBooksClient = ctx.obj
1958
+ if not private_key:
1959
+ emit_error(ctx, "Wallet required.")
1960
+ ctx.exit(1)
1961
+ return
1962
+ if confirm and not ctx.find_root().params.get("json_output", False):
1963
+ click.echo("\n This will send 6 approval transactions on Polygon.")
1964
+ click.echo(" Requires MATIC for gas fees.")
1965
+ if not click.confirm("\n Proceed?"):
1966
+ click.echo(" Cancelled.")
1967
+ return
1968
+ try:
1969
+ data = client.polymarket_approve_set(private_key)
1970
+ emit(ctx, data, lambda d: " All contracts approved.")
1971
+ except Exception as e:
1972
+ emit_error(ctx, str(e))
1973
+ ctx.exit(1)
1974
+
1975
+
1976
+ # ══════════════════════════════════════════════════════════════════════════
1977
+ # Bridge — deposit assets from other chains
1978
+ # ══════════════════════════════════════════════════════════════════════════
1979
+
1980
+ @polymarket.group("bridge")
1981
+ def pm_bridge():
1982
+ """Bridge assets into Polymarket from other chains."""
1983
+ pass
1984
+
1985
+
1986
+ @pm_bridge.command("deposit")
1987
+ @click.argument("address")
1988
+ @click.pass_context
1989
+ def pm_bridge_deposit(ctx, address):
1990
+ """Get deposit addresses for a wallet (EVM, Solana, Bitcoin).
1991
+
1992
+ Example: fastbooks polymarket bridge deposit 0xWALLET...
1993
+ """
1994
+ client: FastBooksClient = ctx.obj
1995
+ try:
1996
+ data = client.polymarket_bridge_deposit(address)
1997
+ emit(ctx, data)
1998
+ except Exception as e:
1999
+ emit_error(ctx, str(e))
2000
+ ctx.exit(1)
2001
+
2002
+
2003
+ @pm_bridge.command("supported-assets")
2004
+ @click.pass_context
2005
+ def pm_bridge_supported_assets(ctx):
2006
+ """List supported chains and tokens for bridging.
2007
+
2008
+ Example: fastbooks polymarket bridge supported-assets
2009
+ """
2010
+ client: FastBooksClient = ctx.obj
2011
+ try:
2012
+ data = client.polymarket_bridge_supported_assets()
2013
+ emit(ctx, data)
2014
+ except Exception as e:
2015
+ emit_error(ctx, str(e))
2016
+ ctx.exit(1)
2017
+
2018
+
2019
+ @pm_bridge.command("status")
2020
+ @click.argument("deposit_address")
2021
+ @click.pass_context
2022
+ def pm_bridge_status(ctx, deposit_address):
2023
+ """Check deposit status.
2024
+
2025
+ Example: fastbooks polymarket bridge status 0xDEPOSIT_ADDRESS
2026
+ """
2027
+ client: FastBooksClient = ctx.obj
2028
+ try:
2029
+ data = client.polymarket_bridge_status(deposit_address)
2030
+ emit(ctx, data)
2031
+ except Exception as e:
2032
+ emit_error(ctx, str(e))
2033
+ ctx.exit(1)
2034
+
2035
+
2036
+ # ══════════════════════════════════════════════════════════════════════════
2037
+ # Formatters
2038
+ # ══════════════════════════════════════════════════════════════════════════
2039
+
2040
+ def _format_markets_list(data) -> str:
2041
+ items = data if isinstance(data, list) else data.get("data", data.get("markets", []))
2042
+ if not isinstance(items, list) or not items:
2043
+ return str(data)
2044
+ return format_table(
2045
+ items[:30],
2046
+ ["question", "outcomePrices", "volume", "liquidity", "active"],
2047
+ ["Question", "Price (Yes/No)", "Volume", "Liquidity", "Active"],
2048
+ )
2049
+
2050
+
2051
+ def _format_market_detail(data: dict) -> str:
2052
+ d = data.get("data", data)
2053
+ lines = [
2054
+ "",
2055
+ f" {d.get('question', '?')}",
2056
+ f" ──────────────────────────────────",
2057
+ ]
2058
+ if d.get("outcomePrices"):
2059
+ prices = d["outcomePrices"]
2060
+ if isinstance(prices, list) and len(prices) >= 2:
2061
+ lines.append(f" Yes: {prices[0]} | No: {prices[1]}")
2062
+ for field in ["volume", "liquidity", "startDate", "endDate", "active", "closed"]:
2063
+ if field in d:
2064
+ lines.append(f" {field.replace('_', ' ').title()}: {d[field]}")
2065
+ if d.get("description"):
2066
+ lines.append(f"\n {d['description'][:200]}")
2067
+ return "\n".join(lines)
2068
+
2069
+
2070
+ def _format_events_list(data) -> str:
2071
+ items = data if isinstance(data, list) else data.get("data", data.get("events", []))
2072
+ if not isinstance(items, list) or not items:
2073
+ return str(data)
2074
+ return format_table(
2075
+ items[:30],
2076
+ ["title", "volume", "liquidity", "active", "marketsCount"],
2077
+ ["Event", "Volume", "Liquidity", "Active", "Markets"],
2078
+ )
2079
+
2080
+
2081
+ def _format_event_detail(data: dict) -> str:
2082
+ d = data.get("data", data)
2083
+ lines = [
2084
+ "",
2085
+ f" {d.get('title', '?')}",
2086
+ f" ──────────────────────────────────",
2087
+ ]
2088
+ for field in ["volume", "liquidity", "active", "closed", "startDate", "endDate"]:
2089
+ if field in d:
2090
+ lines.append(f" {field.title()}: {d[field]}")
2091
+ markets = d.get("markets", [])
2092
+ if markets:
2093
+ lines.append(f"\n Markets ({len(markets)}):")
2094
+ for m in markets[:10]:
2095
+ q = m.get("question", "?")
2096
+ p = m.get("outcomePrices", ["?", "?"])
2097
+ lines.append(f" • {q} [{p[0] if isinstance(p, list) else p}]")
2098
+ return "\n".join(lines)
2099
+
2100
+
2101
+ def _format_clob_book(data: dict) -> str:
2102
+ lines = []
2103
+ ob = data.get("data", data)
2104
+ lines.append("")
2105
+ if ob.get("market"):
2106
+ lines.append(f" Market: {ob['market']}")
2107
+ lines.append("")
2108
+ asks = ob.get("asks", [])[:8]
2109
+ bids = ob.get("bids", [])[:8]
2110
+ lines.append(" ASKS:")
2111
+ for entry in reversed(asks):
2112
+ price = entry.get("price", entry[0] if isinstance(entry, list) else "?")
2113
+ size = entry.get("size", entry[1] if isinstance(entry, list) else "?")
2114
+ lines.append(f" {price:>10} {size:>12}")
2115
+ lines.append(" ─────────────────────────")
2116
+ lines.append(" BIDS:")
2117
+ for entry in bids:
2118
+ price = entry.get("price", entry[0] if isinstance(entry, list) else "?")
2119
+ size = entry.get("size", entry[1] if isinstance(entry, list) else "?")
2120
+ lines.append(f" {price:>10} {size:>12}")
2121
+ return "\n".join(lines)
2122
+
2123
+
2124
+ def _format_price_history(data) -> str:
2125
+ items = data if isinstance(data, list) else data.get("history", data.get("data", []))
2126
+ if not isinstance(items, list) or not items:
2127
+ return str(data)
2128
+ return format_table(
2129
+ items[:30],
2130
+ ["t", "p"],
2131
+ ["Time", "Price"],
2132
+ )
2133
+
2134
+
2135
+ def _format_order_result(data: dict) -> str:
2136
+ d = data.get("data", data)
2137
+ lines = ["\n Polymarket Order"]
2138
+ if d.get("orderID") or d.get("id"):
2139
+ lines.append(f" ID: {d.get('orderID', d.get('id', '?'))}")
2140
+ if d.get("status"):
2141
+ lines.append(f" Status: {d['status']}")
2142
+ if d.get("side"):
2143
+ lines.append(f" Side: {d['side']}")
2144
+ if d.get("price"):
2145
+ lines.append(f" Price: {d['price']}")
2146
+ if d.get("size") or d.get("original_size"):
2147
+ lines.append(f" Size: {d.get('size', d.get('original_size', '?'))}")
2148
+ return "\n".join(lines)
2149
+
2150
+
2151
+ def _format_orders_list(data) -> str:
2152
+ items = data if isinstance(data, list) else data.get("data", data.get("orders", []))
2153
+ if not isinstance(items, list) or not items:
2154
+ return " No open orders."
2155
+ return format_table(
2156
+ items[:30],
2157
+ ["id", "side", "price", "original_size", "size_matched", "status", "market"],
2158
+ ["ID", "Side", "Price", "Size", "Filled", "Status", "Market"],
2159
+ )
2160
+
2161
+
2162
+ def _format_trades_list(data) -> str:
2163
+ items = data if isinstance(data, list) else data.get("data", data.get("trades", []))
2164
+ if not isinstance(items, list) or not items:
2165
+ return " No trades."
2166
+ return format_table(
2167
+ items[:30],
2168
+ ["id", "side", "price", "size", "market", "timestamp"],
2169
+ ["ID", "Side", "Price", "Size", "Market", "Time"],
2170
+ )
2171
+
2172
+
2173
+ def _format_balance(data: dict) -> str:
2174
+ d = data.get("data", data)
2175
+ lines = ["\n Polymarket Balance"]
2176
+ if isinstance(d, dict):
2177
+ for k, v in d.items():
2178
+ lines.append(f" {k}: {v}")
2179
+ else:
2180
+ lines.append(f" {d}")
2181
+ return "\n".join(lines)
2182
+
2183
+
2184
+ def _format_positions(data) -> str:
2185
+ items = data if isinstance(data, list) else data.get("data", data.get("positions", []))
2186
+ if not isinstance(items, list) or not items:
2187
+ return " No positions."
2188
+ return format_table(
2189
+ items[:30],
2190
+ ["market", "outcome", "size", "avgPrice", "currentPrice", "pnl"],
2191
+ ["Market", "Outcome", "Size", "Avg Price", "Current", "PnL"],
2192
+ )
2193
+
2194
+
2195
+ def _format_leaderboard(data) -> str:
2196
+ items = data if isinstance(data, list) else data.get("data", data.get("leaderboard", []))
2197
+ if not isinstance(items, list) or not items:
2198
+ return " No leaderboard data."
2199
+ return format_table(
2200
+ items[:20],
2201
+ ["rank", "address", "pnl", "volume", "marketsTraded"],
2202
+ ["Rank", "Address", "PnL", "Volume", "Markets"],
2203
+ )
2204
+
2205
+
2206
+ def _format_wallet_info(data: dict) -> str:
2207
+ d = data.get("data", data)
2208
+ lines = ["\n Polymarket Wallet"]
2209
+ for field in ["address", "proxyAddress", "signatureType", "source", "configPath"]:
2210
+ if field in d:
2211
+ lines.append(f" {field}: {d[field]}")
2212
+ if d.get("privateKey"):
2213
+ key = d["privateKey"]
2214
+ lines.append(f" privateKey: {key[:6]}...{key[-4:]}")
2215
+ return "\n".join(lines)