@intentsolutionsio/crypto-derivatives-tracker 1.0.0

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.
Files changed (29) hide show
  1. package/.claude-plugin/plugin.json +22 -0
  2. package/LICENSE +21 -0
  3. package/README.md +173 -0
  4. package/agents/derivatives-agent.md +408 -0
  5. package/package.json +43 -0
  6. package/skills/skill-adapter/assets/README.md +6 -0
  7. package/skills/skill-adapter/assets/config-template.json +32 -0
  8. package/skills/skill-adapter/assets/skill-schema.json +28 -0
  9. package/skills/skill-adapter/assets/test-data.json +27 -0
  10. package/skills/skill-adapter/references/README.md +4 -0
  11. package/skills/skill-adapter/references/best-practices.md +69 -0
  12. package/skills/skill-adapter/references/examples.md +73 -0
  13. package/skills/skill-adapter/scripts/README.md +8 -0
  14. package/skills/skill-adapter/scripts/helper-template.sh +42 -0
  15. package/skills/skill-adapter/scripts/validation.sh +32 -0
  16. package/skills/tracking-crypto-derivatives/ARD.md +376 -0
  17. package/skills/tracking-crypto-derivatives/PRD.md +258 -0
  18. package/skills/tracking-crypto-derivatives/SKILL.md +127 -0
  19. package/skills/tracking-crypto-derivatives/config/settings.yaml +152 -0
  20. package/skills/tracking-crypto-derivatives/references/errors.md +224 -0
  21. package/skills/tracking-crypto-derivatives/references/examples.md +460 -0
  22. package/skills/tracking-crypto-derivatives/references/implementation.md +113 -0
  23. package/skills/tracking-crypto-derivatives/scripts/basis_calculator.py +377 -0
  24. package/skills/tracking-crypto-derivatives/scripts/derivatives_tracker.py +579 -0
  25. package/skills/tracking-crypto-derivatives/scripts/formatters.py +459 -0
  26. package/skills/tracking-crypto-derivatives/scripts/funding_tracker.py +308 -0
  27. package/skills/tracking-crypto-derivatives/scripts/liquidation_monitor.py +356 -0
  28. package/skills/tracking-crypto-derivatives/scripts/oi_analyzer.py +338 -0
  29. package/skills/tracking-crypto-derivatives/scripts/options_analyzer.py +373 -0
@@ -0,0 +1,579 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Crypto Derivatives Tracker - Main CLI.
4
+
5
+ Comprehensive derivatives market analysis:
6
+ - Funding rate tracking
7
+ - Open interest analysis
8
+ - Liquidation monitoring
9
+ - Options flow analysis
10
+ - Basis/spread calculations
11
+ - Multi-asset dashboard
12
+
13
+ Usage:
14
+ python derivatives_tracker.py funding BTC
15
+ python derivatives_tracker.py oi BTC --format json
16
+ python derivatives_tracker.py liquidations BTC
17
+ python derivatives_tracker.py options BTC
18
+ python derivatives_tracker.py basis BTC
19
+ python derivatives_tracker.py dashboard BTC ETH
20
+ """
21
+
22
+ import argparse
23
+ import sys
24
+ from decimal import Decimal
25
+ from typing import List, Optional
26
+
27
+ from exchange_client import ExchangeClient
28
+ from funding_tracker import FundingTracker
29
+ from oi_analyzer import OIAnalyzer
30
+ from liquidation_monitor import LiquidationMonitor
31
+ from options_analyzer import OptionsAnalyzer
32
+ from basis_calculator import BasisCalculator
33
+ from formatters import ConsoleFormatter, JSONFormatter, ReportGenerator
34
+
35
+
36
+ def cmd_funding(args):
37
+ """Handle funding subcommand."""
38
+ tracker = FundingTracker()
39
+ console = ConsoleFormatter()
40
+ json_fmt = JSONFormatter()
41
+
42
+ print(console.header(f"{args.symbol} FUNDING RATE ANALYSIS"))
43
+
44
+ analysis = tracker.analyze(args.symbol)
45
+
46
+ if args.format == "json":
47
+ print(json_fmt.funding_report(args.symbol, {
48
+ "weighted_avg": analysis.weighted_avg,
49
+ "annualized_avg": analysis.annualized_avg,
50
+ "spread": analysis.spread,
51
+ "sentiment": analysis.sentiment,
52
+ "sentiment_strength": analysis.sentiment_strength,
53
+ "arbitrage_opportunity": analysis.arbitrage_opportunity,
54
+ "rates": [
55
+ {
56
+ "exchange": r.exchange,
57
+ "rate": float(r.rate),
58
+ "annualized": r.annualized,
59
+ "next_payment": r.time_to_payment_str,
60
+ }
61
+ for r in analysis.rates
62
+ ],
63
+ }))
64
+ return
65
+
66
+ # Console format
67
+ print(f"\n{'Exchange':<12} {'Current':>10} {'Annualized':>12} {'Next Payment':>14}")
68
+ print("-" * 50)
69
+
70
+ for rate in sorted(analysis.rates, key=lambda r: r.rate, reverse=True):
71
+ print(
72
+ f"{rate.exchange:<12} "
73
+ f"{float(rate.rate):>+9.4%} "
74
+ f"{rate.annualized:>+11.2f}% "
75
+ f"{rate.time_to_payment_str:>14}"
76
+ )
77
+
78
+ print("-" * 50)
79
+ print(f"\nWeighted Average: {analysis.weighted_avg:+.4%}")
80
+ print(f"Annualized: {analysis.annualized_avg:+.2f}%")
81
+ print(f"Spread (max-min): {analysis.spread:.4%}")
82
+ print(f"\nSentiment: {console.sentiment_icon(analysis.sentiment)} "
83
+ f"{analysis.sentiment_strength.title()} {analysis.sentiment.title()}")
84
+
85
+ if analysis.is_extreme:
86
+ print(f"\n⚠️ EXTREME FUNDING - Contrarian opportunity")
87
+
88
+ if analysis.arbitrage_opportunity:
89
+ print(f"\n💰 ARBITRAGE OPPORTUNITY")
90
+ print(f" Long on {analysis.min_rate.exchange} ({float(analysis.min_rate.rate):+.4%})")
91
+ print(f" Short on {analysis.max_rate.exchange} ({float(analysis.max_rate.rate):+.4%})")
92
+ print(f" Profit: {analysis.arbitrage_spread:.4%} per 8h")
93
+
94
+
95
+ def cmd_oi(args):
96
+ """Handle open interest subcommand."""
97
+ analyzer = OIAnalyzer()
98
+ console = ConsoleFormatter()
99
+ json_fmt = JSONFormatter()
100
+
101
+ print(console.header(f"{args.symbol} OPEN INTEREST ANALYSIS"))
102
+
103
+ analysis = analyzer.analyze(args.symbol)
104
+
105
+ if args.format == "json":
106
+ print(json_fmt.oi_report(args.symbol, {
107
+ "total_oi_usd": float(analysis.total_oi_usd),
108
+ "total_oi_contracts": float(analysis.total_oi_contracts),
109
+ "avg_change_24h": analysis.avg_change_24h,
110
+ "avg_change_7d": analysis.avg_change_7d,
111
+ "weighted_long_ratio": analysis.weighted_long_ratio,
112
+ "long_percentage": analysis.long_percentage,
113
+ "dominant_exchange": analysis.dominant_exchange,
114
+ "dominant_share": analysis.dominant_share,
115
+ "trend": analysis.trend,
116
+ "trend_strength": analysis.trend_strength,
117
+ "exchanges": [
118
+ {
119
+ "exchange": oi.exchange,
120
+ "oi_usd": float(oi.oi_usd),
121
+ "change_24h": oi.change_24h_pct,
122
+ "change_7d": oi.change_7d_pct,
123
+ "long_ratio": oi.long_ratio,
124
+ }
125
+ for oi in analysis.exchanges
126
+ ],
127
+ }))
128
+ return
129
+
130
+ # Console format
131
+ print(f"\n{'Exchange':<12} {'OI (USD)':>14} {'24h Chg':>10} {'7d Chg':>10} {'Share':>8}")
132
+ print("-" * 60)
133
+
134
+ for oi in sorted(analysis.exchanges, key=lambda x: x.oi_usd, reverse=True):
135
+ share = float(oi.oi_usd) / float(analysis.total_oi_usd) * 100
136
+ print(
137
+ f"{oi.exchange:<12} "
138
+ f"${float(oi.oi_usd)/1e9:>12.2f}B "
139
+ f"{oi.change_24h_pct:>+9.1f}% "
140
+ f"{oi.change_7d_pct:>+9.1f}% "
141
+ f"{share:>7.1f}%"
142
+ )
143
+
144
+ print("-" * 60)
145
+ print(f"\nTotal OI: ${float(analysis.total_oi_usd)/1e9:.2f}B")
146
+ print(f"24h Change: {analysis.avg_change_24h:+.1f}%")
147
+ print(f"7d Change: {analysis.avg_change_7d:+.1f}%")
148
+ print(f"\nLong/Short Ratio: {analysis.weighted_long_ratio:.2f} "
149
+ f"({analysis.long_percentage:.1f}% long)")
150
+ print(f"Trend: {analysis.trend_strength.title()} {analysis.trend.title()}")
151
+ print(f"Dominant Exchange: {analysis.dominant_exchange} ({analysis.dominant_share:.1f}%)")
152
+
153
+ # Check for divergence
154
+ if args.price_change:
155
+ print(console.section("DIVERGENCE ANALYSIS"))
156
+ divergence = analyzer.detect_divergence(args.symbol, args.price_change)
157
+ if divergence:
158
+ print(f"\n🔍 Divergence Detected!")
159
+ print(f" OI: {divergence.oi_direction} ({divergence.oi_change_pct:+.1f}%)")
160
+ print(f" Price: {divergence.price_direction} ({divergence.price_change_pct:+.1f}%)")
161
+ print(f" Signal: {divergence.signal.upper()}")
162
+ print(f" {divergence.description}")
163
+ print(f" Confidence: {divergence.confidence}")
164
+ else:
165
+ print("\nNo significant divergence detected")
166
+
167
+
168
+ def cmd_liquidations(args):
169
+ """Handle liquidations subcommand."""
170
+ monitor = LiquidationMonitor()
171
+ console = ConsoleFormatter()
172
+ json_fmt = JSONFormatter()
173
+
174
+ price = Decimal(str(args.price)) if args.price else None
175
+ summary = monitor.get_summary(args.symbol, price)
176
+
177
+ print(console.header(f"{args.symbol} LIQUIDATION MONITOR"))
178
+
179
+ if args.format == "json":
180
+ print(json_fmt.format({
181
+ "symbol": summary.symbol,
182
+ "current_price": float(summary.current_price),
183
+ "total_24h_usd": float(summary.total_24h_usd),
184
+ "long_liquidations_usd": float(summary.long_liquidations_usd),
185
+ "short_liquidations_usd": float(summary.short_liquidations_usd),
186
+ "cascade_risk": summary.cascade_risk,
187
+ "long_levels": [
188
+ {
189
+ "price": float(l.price),
190
+ "total_value_usd": float(l.total_value_usd),
191
+ "density": l.density,
192
+ }
193
+ for l in summary.long_levels
194
+ ],
195
+ "short_levels": [
196
+ {
197
+ "price": float(l.price),
198
+ "total_value_usd": float(l.total_value_usd),
199
+ "density": l.density,
200
+ }
201
+ for l in summary.short_levels
202
+ ],
203
+ }))
204
+ return
205
+
206
+ print(f"\n Current Price: ${summary.current_price:,}")
207
+ print("-" * 60)
208
+
209
+ # 24h totals
210
+ print(f"\n24h Liquidations:")
211
+ print(f" Total: ${float(summary.total_24h_usd)/1e6:,.1f}M")
212
+ print(f" Longs: ${float(summary.long_liquidations_usd)/1e6:,.1f}M")
213
+ print(f" Shorts: ${float(summary.short_liquidations_usd)/1e6:,.1f}M")
214
+
215
+ # Cascade risk
216
+ print(f"\nCascade Risk: {console.risk_icon(summary.cascade_risk)} "
217
+ f"{summary.cascade_risk.upper()}")
218
+
219
+ # Heatmap
220
+ print(console.section("LIQUIDATION HEATMAP"))
221
+
222
+ print(f"\nLONG LIQUIDATIONS (below ${summary.current_price:,}):")
223
+ for level in summary.long_levels[:4]:
224
+ bar_len = min(int(float(level.total_value_usd) / 10_000_000), 20)
225
+ bar = "█" * bar_len
226
+ density_mark = "⚠️ " if level.density in ["high", "critical"] else ""
227
+ print(
228
+ f" ${float(level.price):>10,.0f} {bar} "
229
+ f"${float(level.total_value_usd)/1e6:.0f}M "
230
+ f"{density_mark}{level.density.upper()}"
231
+ )
232
+
233
+ print(f"\nSHORT LIQUIDATIONS (above ${summary.current_price:,}):")
234
+ for level in summary.short_levels[:4]:
235
+ bar_len = min(int(float(level.total_value_usd) / 10_000_000), 20)
236
+ bar = "█" * bar_len
237
+ density_mark = "⚠️ " if level.density in ["high", "critical"] else ""
238
+ print(
239
+ f" ${float(level.price):>10,.0f} {bar} "
240
+ f"${float(level.total_value_usd)/1e6:.0f}M "
241
+ f"{density_mark}{level.density.upper()}"
242
+ )
243
+
244
+ # Large liquidations
245
+ if args.large:
246
+ print(console.section("RECENT LARGE LIQUIDATIONS"))
247
+ large = monitor.get_recent_large_liquidations(
248
+ args.symbol, min_value_usd=1_000_000, limit=5
249
+ )
250
+ if large:
251
+ print(f"\n{'Exchange':<10} {'Side':<6} {'Price':>12} {'Value':>12} {'When':>10}")
252
+ print("-" * 60)
253
+ for l in large:
254
+ print(
255
+ f"{l['exchange']:<10} "
256
+ f"{l['side']:<6} "
257
+ f"${l['price']:>10,.0f} "
258
+ f"${l['value_usd']/1e6:>10.1f}M "
259
+ f"{l['time_ago']:>10}"
260
+ )
261
+
262
+
263
+ def cmd_options(args):
264
+ """Handle options subcommand."""
265
+ analyzer = OptionsAnalyzer()
266
+ console = ConsoleFormatter()
267
+ json_fmt = JSONFormatter()
268
+
269
+ price = Decimal(str(args.price)) if args.price else None
270
+ analysis = analyzer.analyze(args.symbol, current_price=price)
271
+
272
+ print(console.header(f"{args.symbol} OPTIONS ANALYSIS"))
273
+
274
+ if args.format == "json":
275
+ print(json_fmt.format({
276
+ "symbol": analysis.symbol,
277
+ "expiry": analysis.snapshot.expiry,
278
+ "atm_iv": analysis.snapshot.atm_iv,
279
+ "iv_interpretation": analysis.iv_interpretation,
280
+ "iv_percentile": analysis.iv_percentile,
281
+ "put_call_ratio_volume": analysis.snapshot.put_call_ratio_volume,
282
+ "put_call_ratio_oi": analysis.snapshot.put_call_ratio_oi,
283
+ "sentiment_from_pcr": analysis.sentiment_from_pcr,
284
+ "sentiment_from_skew": analysis.sentiment_from_skew,
285
+ "overall_sentiment": analysis.overall_sentiment,
286
+ "max_pain": float(analysis.snapshot.max_pain),
287
+ "max_pain_distance_pct": analysis.max_pain_distance_pct,
288
+ "expiry_pressure": analysis.expiry_pressure,
289
+ "total_call_oi": float(analysis.snapshot.total_call_oi),
290
+ "total_put_oi": float(analysis.snapshot.total_put_oi),
291
+ }))
292
+ return
293
+
294
+ snap = analysis.snapshot
295
+ print(f"\nExpiry: {snap.expiry}")
296
+ print(f"Exchange: {snap.exchange}")
297
+
298
+ print(f"\nImplied Volatility:")
299
+ print(f" ATM IV: {snap.atm_iv:.1f}%")
300
+ print(f" Interpretation: {analysis.iv_interpretation.upper()}")
301
+ print(f" IV Rank: {analysis.iv_percentile:.0f}th percentile")
302
+
303
+ print(f"\nPut/Call Analysis:")
304
+ print(f" PCR (Volume): {snap.put_call_ratio_volume:.2f}")
305
+ print(f" PCR (OI): {snap.put_call_ratio_oi:.2f}")
306
+ print(f" Sentiment: {console.sentiment_icon(analysis.sentiment_from_pcr)} "
307
+ f"{analysis.sentiment_from_pcr.upper()}")
308
+
309
+ print(f"\nMax Pain:")
310
+ print(f" Price: ${snap.max_pain:,.0f}")
311
+ print(f" Distance: {analysis.max_pain_distance_pct:+.1f}% from current")
312
+
313
+ print(f"\nOpen Interest:")
314
+ print(f" Calls: ${float(snap.total_call_oi)/1e9:.2f}B")
315
+ print(f" Puts: ${float(snap.total_put_oi)/1e9:.2f}B")
316
+
317
+ print(f"\nOverall Sentiment: {console.sentiment_icon(analysis.overall_sentiment)} "
318
+ f"{analysis.overall_sentiment.upper()}")
319
+ print(f"Expiry Pressure: {analysis.expiry_pressure.upper()}")
320
+
321
+ # Max pain levels
322
+ if args.max_pain:
323
+ print(console.section("MAX PAIN BY EXPIRY"))
324
+ levels = analyzer.get_max_pain_levels(args.symbol)
325
+ print(f"\n{'Expiry':<12} {'Max Pain':>12} {'Call OI':>12} {'Put OI':>12} {'PCR':>6}")
326
+ print("-" * 50)
327
+ for lvl in levels:
328
+ print(
329
+ f"{lvl['expiry']:<12} "
330
+ f"${lvl['max_pain']:>10,.0f} "
331
+ f"${lvl['call_oi']/1e9:>10.1f}B "
332
+ f"${lvl['put_oi']/1e9:>10.1f}B "
333
+ f"{lvl['pcr_oi']:>5.2f}"
334
+ )
335
+
336
+ # Options flow
337
+ if args.flow:
338
+ print(console.section("OPTIONS FLOW (Simulated)"))
339
+ flows = analyzer.generate_mock_flow(args.symbol, count=5)
340
+ print(f"\n{'Type':<6} {'Strike':>10} {'Size':>8} {'Premium':>12} {'Interpretation':<25}")
341
+ print("-" * 70)
342
+ for flow in flows:
343
+ print(
344
+ f"{flow.option_type.upper():<6} "
345
+ f"${float(flow.strike):>8,.0f} "
346
+ f"{flow.size_contracts:>8} "
347
+ f"${float(flow.premium_usd):>10,.0f} "
348
+ f"{flow.interpretation:<25}"
349
+ )
350
+
351
+
352
+ def cmd_basis(args):
353
+ """Handle basis subcommand."""
354
+ calc = BasisCalculator()
355
+ console = ConsoleFormatter()
356
+ json_fmt = JSONFormatter()
357
+
358
+ price = Decimal(str(args.price)) if args.price else None
359
+ analysis = calc.analyze(args.symbol, price)
360
+
361
+ print(console.header(f"{args.symbol} BASIS ANALYSIS"))
362
+
363
+ if args.format == "json":
364
+ print(json_fmt.format({
365
+ "symbol": analysis.symbol,
366
+ "spot_price": float(analysis.spot_price),
367
+ "avg_basis_pct": analysis.avg_basis_pct,
368
+ "avg_annualized": analysis.avg_annualized,
369
+ "market_structure": analysis.market_structure,
370
+ "structure_strength": analysis.structure_strength,
371
+ "best_carry_expiry": analysis.best_carry_expiry,
372
+ "best_carry_yield": analysis.best_carry_yield,
373
+ "basis_data": [
374
+ {
375
+ "expiry": b.expiry,
376
+ "futures_price": float(b.futures_price),
377
+ "basis_pct": b.basis_pct,
378
+ "annualized_pct": b.annualized_pct,
379
+ "days_to_expiry": b.days_to_expiry,
380
+ }
381
+ for b in analysis.basis_data
382
+ ],
383
+ }))
384
+ return
385
+
386
+ print(f"\n Spot Price: ${analysis.spot_price:,}")
387
+ print("-" * 60)
388
+
389
+ print(f"\n{'Expiry':<12} {'Futures':>12} {'Basis':>10} {'Annual':>10} {'Days':>6}")
390
+ print("-" * 60)
391
+
392
+ for basis in sorted(analysis.basis_data, key=lambda b: b.days_to_expiry):
393
+ print(
394
+ f"{basis.expiry:<12} "
395
+ f"${float(basis.futures_price):>10,.0f} "
396
+ f"{basis.basis_pct:>+9.2f}% "
397
+ f"{basis.annualized_pct:>+9.1f}% "
398
+ f"{basis.days_to_expiry:>6}"
399
+ )
400
+
401
+ print("-" * 60)
402
+ print(f"\nMarket Structure: {analysis.structure_strength.title()} "
403
+ f"{analysis.market_structure.title()}")
404
+ print(f"Average Basis: {analysis.avg_basis_pct:+.2f}%")
405
+ print(f"Average Annualized: {analysis.avg_annualized:+.1f}%")
406
+ print(f"Best Carry: {analysis.best_carry_expiry} "
407
+ f"({analysis.best_carry_yield:+.1f}% annualized)")
408
+
409
+ # Term structure
410
+ print(console.section("TERM STRUCTURE"))
411
+ structure = calc.get_term_structure(args.symbol, analysis.spot_price)
412
+ for point in structure:
413
+ bar = "+" * min(int(abs(point["annualized_pct"]) / 2), 20)
414
+ direction = "▲" if point["annualized_pct"] > 0 else "▼"
415
+ print(f"{point['expiry']:<12} {direction} {bar} {point['annualized_pct']:+.1f}%")
416
+
417
+ # Carry scanner
418
+ if args.carry:
419
+ print(console.section("CARRY TRADE SCANNER"))
420
+ opportunities = calc.find_carry_opportunities([args.symbol], min_yield=5.0)
421
+ if opportunities:
422
+ print(f"\n{'Expiry':<12} {'Basis':>8} {'Annual':>10} {'Direction':<12}")
423
+ print("-" * 50)
424
+ for opp in opportunities[:5]:
425
+ print(
426
+ f"{opp.expiry:<12} "
427
+ f"{opp.basis_pct:>+7.2f}% "
428
+ f"{opp.annualized_yield:>+9.1f}% "
429
+ f"{opp.direction:<12}"
430
+ )
431
+
432
+ print("\nTop Opportunity:")
433
+ top = opportunities[0]
434
+ print(f" Strategy: {top.strategy}")
435
+ print(f" Risk: {top.risk_notes}")
436
+ else:
437
+ print("\nNo carry opportunities found above threshold")
438
+
439
+
440
+ def cmd_dashboard(args):
441
+ """Handle dashboard subcommand."""
442
+ console = ConsoleFormatter()
443
+ report_gen = ReportGenerator()
444
+
445
+ funding_tracker = FundingTracker()
446
+ oi_analyzer = OIAnalyzer()
447
+ liq_monitor = LiquidationMonitor()
448
+
449
+ print(console.header("CRYPTO DERIVATIVES DASHBOARD"))
450
+
451
+ for symbol in args.symbols:
452
+ print(f"\n{'=' * 70}")
453
+ print(f" {symbol} ".center(70, "="))
454
+ print("=" * 70)
455
+
456
+ try:
457
+ # Funding
458
+ funding = funding_tracker.analyze(symbol)
459
+ print(f"\n📊 FUNDING")
460
+ print(f" Rate: {funding.weighted_avg:+.4%} ({funding.annualized_avg:+.1f}% annual)")
461
+ print(f" Sentiment: {console.sentiment_icon(funding.sentiment)} "
462
+ f"{funding.sentiment.title()}")
463
+
464
+ # OI
465
+ oi = oi_analyzer.analyze(symbol)
466
+ print(f"\n📈 OPEN INTEREST")
467
+ print(f" Total: ${float(oi.total_oi_usd)/1e9:.1f}B ({oi.avg_change_24h:+.1f}% 24h)")
468
+ print(f" Long/Short: {oi.weighted_long_ratio:.2f} ({oi.long_percentage:.0f}% long)")
469
+ print(f" Trend: {oi.trend_strength.title()} {oi.trend.title()}")
470
+
471
+ # Liquidations
472
+ liq = liq_monitor.get_summary(symbol)
473
+ print(f"\n💥 LIQUIDATIONS (24h)")
474
+ print(f" Total: ${float(liq.total_24h_usd)/1e6:.1f}M")
475
+ print(f" Longs: ${float(liq.long_liquidations_usd)/1e6:.1f}M | "
476
+ f"Shorts: ${float(liq.short_liquidations_usd)/1e6:.1f}M")
477
+ print(f" Cascade Risk: {console.risk_icon(liq.cascade_risk)} "
478
+ f"{liq.cascade_risk.upper()}")
479
+
480
+ except Exception as e:
481
+ print(f"\n⚠️ Error fetching data for {symbol}: {e}")
482
+
483
+ print(f"\n{'=' * 70}")
484
+ print(f"Dashboard generated at: {console.H_LINE * 50}")
485
+
486
+
487
+ def main():
488
+ """Main entry point."""
489
+ parser = argparse.ArgumentParser(
490
+ description="Crypto Derivatives Tracker",
491
+ formatter_class=argparse.RawDescriptionHelpFormatter,
492
+ epilog="""
493
+ Examples:
494
+ %(prog)s funding BTC
495
+ %(prog)s oi BTC --price-change 3.5
496
+ %(prog)s liquidations BTC --large
497
+ %(prog)s options BTC --flow --max-pain
498
+ %(prog)s basis BTC --carry
499
+ %(prog)s dashboard BTC ETH SOL
500
+ """,
501
+ )
502
+
503
+ subparsers = parser.add_subparsers(dest="command", help="Available commands")
504
+
505
+ # Funding subcommand
506
+ funding_parser = subparsers.add_parser("funding", help="Analyze funding rates")
507
+ funding_parser.add_argument("symbol", help="Trading symbol (e.g., BTC)")
508
+ funding_parser.add_argument("--format", choices=["console", "json"],
509
+ default="console", help="Output format")
510
+
511
+ # OI subcommand
512
+ oi_parser = subparsers.add_parser("oi", help="Analyze open interest")
513
+ oi_parser.add_argument("symbol", help="Trading symbol")
514
+ oi_parser.add_argument("--price-change", type=float, dest="price_change",
515
+ help="24h price change %% for divergence analysis")
516
+ oi_parser.add_argument("--format", choices=["console", "json"],
517
+ default="console", help="Output format")
518
+
519
+ # Liquidations subcommand
520
+ liq_parser = subparsers.add_parser("liquidations", help="Monitor liquidations")
521
+ liq_parser.add_argument("symbol", help="Trading symbol")
522
+ liq_parser.add_argument("--price", type=float, help="Current price override")
523
+ liq_parser.add_argument("--large", action="store_true",
524
+ help="Show recent large liquidations")
525
+ liq_parser.add_argument("--format", choices=["console", "json"],
526
+ default="console", help="Output format")
527
+
528
+ # Options subcommand
529
+ opt_parser = subparsers.add_parser("options", help="Analyze options market")
530
+ opt_parser.add_argument("symbol", help="Trading symbol")
531
+ opt_parser.add_argument("--price", type=float, help="Current price override")
532
+ opt_parser.add_argument("--max-pain", action="store_true", dest="max_pain",
533
+ help="Show max pain by expiry")
534
+ opt_parser.add_argument("--flow", action="store_true",
535
+ help="Show options flow")
536
+ opt_parser.add_argument("--format", choices=["console", "json"],
537
+ default="console", help="Output format")
538
+
539
+ # Basis subcommand
540
+ basis_parser = subparsers.add_parser("basis", help="Calculate futures basis")
541
+ basis_parser.add_argument("symbol", help="Trading symbol")
542
+ basis_parser.add_argument("--price", type=float, help="Spot price override")
543
+ basis_parser.add_argument("--carry", action="store_true",
544
+ help="Show carry opportunities")
545
+ basis_parser.add_argument("--format", choices=["console", "json"],
546
+ default="console", help="Output format")
547
+
548
+ # Dashboard subcommand
549
+ dash_parser = subparsers.add_parser("dashboard", help="Multi-asset dashboard")
550
+ dash_parser.add_argument("symbols", nargs="+", help="Trading symbols")
551
+
552
+ args = parser.parse_args()
553
+
554
+ if not args.command:
555
+ parser.print_help()
556
+ sys.exit(1)
557
+
558
+ # Dispatch to command handler
559
+ commands = {
560
+ "funding": cmd_funding,
561
+ "oi": cmd_oi,
562
+ "liquidations": cmd_liquidations,
563
+ "options": cmd_options,
564
+ "basis": cmd_basis,
565
+ "dashboard": cmd_dashboard,
566
+ }
567
+
568
+ try:
569
+ commands[args.command](args)
570
+ except KeyboardInterrupt:
571
+ print("\nInterrupted")
572
+ sys.exit(1)
573
+ except Exception as e:
574
+ print(f"\n❌ Error: {e}")
575
+ sys.exit(1)
576
+
577
+
578
+ if __name__ == "__main__":
579
+ main()