@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,308 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Funding rate tracker and analyzer.
4
+
5
+ Tracks funding rates across exchanges with:
6
+ - Multi-exchange aggregation
7
+ - Historical averages
8
+ - Arbitrage opportunity detection
9
+ - Sentiment analysis
10
+ """
11
+
12
+ from dataclasses import dataclass
13
+ from decimal import Decimal
14
+ from typing import Dict, List, Optional
15
+ from datetime import datetime
16
+
17
+ from exchange_client import ExchangeClient, FundingRate, Exchange
18
+
19
+
20
+ @dataclass
21
+ class FundingAnalysis:
22
+ """Aggregated funding rate analysis."""
23
+
24
+ symbol: str
25
+ rates: List[FundingRate]
26
+ weighted_avg: float
27
+ annualized_avg: float
28
+ min_rate: FundingRate
29
+ max_rate: FundingRate
30
+ spread: float # Max - min rate
31
+ sentiment: str # "bullish", "bearish", "neutral"
32
+ sentiment_strength: str # "strong", "moderate", "weak"
33
+ arbitrage_opportunity: bool
34
+ arbitrage_spread: float
35
+ timestamp: datetime
36
+
37
+ @property
38
+ def is_extreme(self) -> bool:
39
+ """Check if funding is at extreme levels."""
40
+ return abs(self.weighted_avg) > 0.08 # 0.08% 8-hour
41
+
42
+ @property
43
+ def exchanges_count(self) -> int:
44
+ """Number of exchanges with data."""
45
+ return len(self.rates)
46
+
47
+
48
+ class FundingTracker:
49
+ """
50
+ Tracks and analyzes funding rates across exchanges.
51
+
52
+ Features:
53
+ - Real-time funding aggregation
54
+ - Weighted average calculation
55
+ - Sentiment analysis
56
+ - Arbitrage detection
57
+ """
58
+
59
+ # Funding rate interpretation thresholds
60
+ NEUTRAL_THRESHOLD = 0.005 # Below this is neutral
61
+ MODERATE_THRESHOLD = 0.03 # Below this is moderate
62
+ EXTREME_THRESHOLD = 0.08 # Above this is extreme
63
+
64
+ # Arbitrage minimum spread
65
+ ARB_MIN_SPREAD = 0.02 # 0.02% minimum for arbitrage
66
+
67
+ def __init__(
68
+ self,
69
+ client: Optional[ExchangeClient] = None,
70
+ ):
71
+ """
72
+ Initialize funding tracker.
73
+
74
+ Args:
75
+ client: Exchange client for data fetching
76
+ """
77
+ self.client = client or ExchangeClient(use_mock=True)
78
+
79
+ def analyze(
80
+ self,
81
+ symbol: str,
82
+ exchanges: Optional[List[Exchange]] = None,
83
+ ) -> FundingAnalysis:
84
+ """
85
+ Analyze funding rates for a symbol.
86
+
87
+ Args:
88
+ symbol: Trading symbol (e.g., "BTC")
89
+ exchanges: Exchanges to include
90
+
91
+ Returns:
92
+ FundingAnalysis with all metrics
93
+ """
94
+ # Fetch rates from all exchanges
95
+ rates = self.client.get_all_funding_rates(symbol, exchanges)
96
+
97
+ if not rates:
98
+ raise ValueError(f"No funding data available for {symbol}")
99
+
100
+ # Calculate weighted average (by estimated OI)
101
+ # For simplicity, use equal weights
102
+ avg_rate = sum(float(r.rate) for r in rates) / len(rates)
103
+ avg_annualized = sum(r.annualized for r in rates) / len(rates)
104
+
105
+ # Find min and max
106
+ min_rate = min(rates, key=lambda r: r.rate)
107
+ max_rate = max(rates, key=lambda r: r.rate)
108
+ spread = float(max_rate.rate - min_rate.rate)
109
+
110
+ # Determine sentiment
111
+ sentiment, strength = self._analyze_sentiment(avg_rate)
112
+
113
+ # Check for arbitrage opportunity
114
+ arb_opportunity = spread >= self.ARB_MIN_SPREAD
115
+
116
+ return FundingAnalysis(
117
+ symbol=symbol,
118
+ rates=rates,
119
+ weighted_avg=round(avg_rate, 6),
120
+ annualized_avg=round(avg_annualized, 2),
121
+ min_rate=min_rate,
122
+ max_rate=max_rate,
123
+ spread=round(spread, 6),
124
+ sentiment=sentiment,
125
+ sentiment_strength=strength,
126
+ arbitrage_opportunity=arb_opportunity,
127
+ arbitrage_spread=round(spread, 6),
128
+ timestamp=datetime.now(),
129
+ )
130
+
131
+ def _analyze_sentiment(
132
+ self,
133
+ avg_rate: float,
134
+ ) -> tuple:
135
+ """
136
+ Analyze market sentiment from funding rate.
137
+
138
+ Returns:
139
+ (sentiment, strength) tuple
140
+ """
141
+ abs_rate = abs(avg_rate)
142
+
143
+ # Determine direction
144
+ if avg_rate > self.NEUTRAL_THRESHOLD:
145
+ sentiment = "bullish"
146
+ elif avg_rate < -self.NEUTRAL_THRESHOLD:
147
+ sentiment = "bearish"
148
+ else:
149
+ sentiment = "neutral"
150
+
151
+ # Determine strength
152
+ if abs_rate >= self.EXTREME_THRESHOLD:
153
+ strength = "extreme"
154
+ elif abs_rate >= self.MODERATE_THRESHOLD:
155
+ strength = "strong"
156
+ elif abs_rate >= self.NEUTRAL_THRESHOLD:
157
+ strength = "moderate"
158
+ else:
159
+ strength = "weak"
160
+
161
+ return sentiment, strength
162
+
163
+ def get_arbitrage_opportunities(
164
+ self,
165
+ symbols: List[str],
166
+ min_spread: float = 0.02,
167
+ ) -> List[Dict]:
168
+ """
169
+ Find funding arbitrage opportunities across symbols.
170
+
171
+ Strategy: Long on exchange with negative/low funding,
172
+ Short on exchange with positive/high funding.
173
+
174
+ Args:
175
+ symbols: Symbols to check
176
+ min_spread: Minimum spread to report
177
+
178
+ Returns:
179
+ List of arbitrage opportunities
180
+ """
181
+ opportunities = []
182
+
183
+ for symbol in symbols:
184
+ analysis = self.analyze(symbol)
185
+
186
+ if analysis.spread >= min_spread:
187
+ profit_8h = analysis.spread
188
+ profit_daily = profit_8h * 3
189
+ profit_annual = profit_8h * 365 * 3
190
+
191
+ opportunities.append({
192
+ "symbol": symbol,
193
+ "long_exchange": analysis.min_rate.exchange,
194
+ "long_rate": float(analysis.min_rate.rate),
195
+ "short_exchange": analysis.max_rate.exchange,
196
+ "short_rate": float(analysis.max_rate.rate),
197
+ "spread": analysis.spread,
198
+ "profit_8h_pct": round(profit_8h, 4),
199
+ "profit_daily_pct": round(profit_daily, 4),
200
+ "profit_annual_pct": round(profit_annual, 2),
201
+ })
202
+
203
+ # Sort by spread descending
204
+ opportunities.sort(key=lambda x: x["spread"], reverse=True)
205
+ return opportunities
206
+
207
+ def get_extreme_funding(
208
+ self,
209
+ symbols: List[str],
210
+ threshold: float = 0.08,
211
+ ) -> List[Dict]:
212
+ """
213
+ Find symbols with extreme funding rates.
214
+
215
+ Extreme funding often indicates crowded trades and
216
+ potential mean reversion opportunities.
217
+
218
+ Args:
219
+ symbols: Symbols to check
220
+ threshold: Extreme threshold (default 0.08%)
221
+
222
+ Returns:
223
+ List of extreme funding situations
224
+ """
225
+ extreme = []
226
+
227
+ for symbol in symbols:
228
+ analysis = self.analyze(symbol)
229
+
230
+ if abs(analysis.weighted_avg) >= threshold:
231
+ extreme.append({
232
+ "symbol": symbol,
233
+ "avg_rate": analysis.weighted_avg,
234
+ "annualized": analysis.annualized_avg,
235
+ "sentiment": analysis.sentiment,
236
+ "strength": analysis.sentiment_strength,
237
+ "signal": "short" if analysis.weighted_avg > 0 else "long",
238
+ "signal_reason": "Contrarian: extreme funding often reverts",
239
+ })
240
+
241
+ # Sort by absolute rate descending
242
+ extreme.sort(key=lambda x: abs(x["avg_rate"]), reverse=True)
243
+ return extreme
244
+
245
+
246
+ def demo():
247
+ """Demonstrate funding tracker."""
248
+ tracker = FundingTracker()
249
+
250
+ print("=" * 70)
251
+ print("FUNDING RATE TRACKER")
252
+ print("=" * 70)
253
+
254
+ # Analyze BTC funding
255
+ analysis = tracker.analyze("BTC")
256
+
257
+ print(f"\nšŸ“Š {analysis.symbol} FUNDING ANALYSIS")
258
+ print("-" * 50)
259
+
260
+ print(f"\n{'Exchange':<12} {'Current':>10} {'Annualized':>12} {'Next Payment':>14}")
261
+ print("-" * 50)
262
+
263
+ for rate in sorted(analysis.rates, key=lambda r: r.rate, reverse=True):
264
+ print(
265
+ f"{rate.exchange:<12} "
266
+ f"{float(rate.rate):>+9.4%} "
267
+ f"{rate.annualized:>+11.2f}% "
268
+ f"{rate.time_to_payment_str:>14}"
269
+ )
270
+
271
+ print("-" * 50)
272
+ print(f"\nWeighted Average: {analysis.weighted_avg:+.4%}")
273
+ print(f"Annualized: {analysis.annualized_avg:+.2f}%")
274
+ print(f"Spread (max-min): {analysis.spread:.4%}")
275
+ print(f"\nSentiment: {analysis.sentiment_strength.title()} {analysis.sentiment.title()}")
276
+
277
+ if analysis.is_extreme:
278
+ print(f"\nāš ļø EXTREME FUNDING - Contrarian opportunity")
279
+
280
+ if analysis.arbitrage_opportunity:
281
+ print(f"\nšŸ’° ARBITRAGE OPPORTUNITY")
282
+ print(f" Long on {analysis.min_rate.exchange} ({float(analysis.min_rate.rate):+.4%})")
283
+ print(f" Short on {analysis.max_rate.exchange} ({float(analysis.max_rate.rate):+.4%})")
284
+ print(f" Profit: {analysis.arbitrage_spread:.4%} per 8h")
285
+
286
+ # Check multiple symbols for arbitrage
287
+ print("\n" + "=" * 70)
288
+ print("FUNDING ARBITRAGE SCANNER")
289
+ print("=" * 70)
290
+
291
+ opportunities = tracker.get_arbitrage_opportunities(["BTC", "ETH", "SOL"])
292
+ if opportunities:
293
+ print(f"\n{'Symbol':<8} {'Long On':<12} {'Short On':<12} {'Spread':>8} {'Annual':>10}")
294
+ print("-" * 50)
295
+ for opp in opportunities:
296
+ print(
297
+ f"{opp['symbol']:<8} "
298
+ f"{opp['long_exchange']:<12} "
299
+ f"{opp['short_exchange']:<12} "
300
+ f"{opp['spread']:>7.4%} "
301
+ f"{opp['profit_annual_pct']:>+9.1f}%"
302
+ )
303
+ else:
304
+ print("\nNo arbitrage opportunities found")
305
+
306
+
307
+ if __name__ == "__main__":
308
+ demo()
@@ -0,0 +1,356 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Liquidation monitor and heatmap generator.
4
+
5
+ Tracks liquidations across exchanges with:
6
+ - Real-time liquidation events
7
+ - Liquidation level clustering
8
+ - Cascade risk assessment
9
+ - Heatmap visualization
10
+ """
11
+
12
+ from dataclasses import dataclass
13
+ from decimal import Decimal
14
+ from typing import Dict, List, Optional
15
+ from datetime import datetime, timedelta
16
+
17
+ from exchange_client import (
18
+ ExchangeClient, Liquidation, LiquidationLevel, Exchange
19
+ )
20
+
21
+
22
+ @dataclass
23
+ class LiquidationSummary:
24
+ """Summary of liquidation activity."""
25
+
26
+ symbol: str
27
+ current_price: Decimal
28
+ total_24h_usd: Decimal
29
+ long_liquidations_usd: Decimal
30
+ short_liquidations_usd: Decimal
31
+ largest_single: Liquidation
32
+ recent_liquidations: List[Liquidation]
33
+ long_levels: List[LiquidationLevel]
34
+ short_levels: List[LiquidationLevel]
35
+ cascade_risk: str # "low", "medium", "high", "critical"
36
+ nearest_long_level: Optional[LiquidationLevel]
37
+ nearest_short_level: Optional[LiquidationLevel]
38
+ timestamp: datetime
39
+
40
+
41
+ class LiquidationMonitor:
42
+ """
43
+ Monitors liquidation events and levels.
44
+
45
+ Features:
46
+ - Real-time liquidation tracking
47
+ - Heatmap generation
48
+ - Cascade risk assessment
49
+ - Level clustering
50
+ """
51
+
52
+ # Cascade risk thresholds (USD within 5% of price)
53
+ CRITICAL_THRESHOLD = 500_000_000 # $500M
54
+ HIGH_THRESHOLD = 200_000_000 # $200M
55
+ MEDIUM_THRESHOLD = 100_000_000 # $100M
56
+
57
+ def __init__(
58
+ self,
59
+ client: Optional[ExchangeClient] = None,
60
+ ):
61
+ """
62
+ Initialize liquidation monitor.
63
+
64
+ Args:
65
+ client: Exchange client for data fetching
66
+ """
67
+ self.client = client or ExchangeClient(use_mock=True)
68
+
69
+ def get_summary(
70
+ self,
71
+ symbol: str,
72
+ current_price: Optional[Decimal] = None,
73
+ ) -> LiquidationSummary:
74
+ """
75
+ Get comprehensive liquidation summary.
76
+
77
+ Args:
78
+ symbol: Trading symbol (e.g., "BTC")
79
+ current_price: Current price (fetched if not provided)
80
+
81
+ Returns:
82
+ LiquidationSummary with all metrics
83
+ """
84
+ # Set default price if not provided
85
+ if current_price is None:
86
+ if symbol == "BTC":
87
+ current_price = Decimal("67500")
88
+ elif symbol == "ETH":
89
+ current_price = Decimal("2500")
90
+ else:
91
+ current_price = Decimal("100")
92
+
93
+ # Fetch recent liquidations
94
+ liquidations = self.client.get_recent_liquidations(
95
+ symbol, limit=100, min_value_usd=100000
96
+ )
97
+
98
+ # Fetch liquidation levels
99
+ levels = self.client.get_liquidation_levels(symbol, current_price)
100
+
101
+ # Separate long and short levels
102
+ long_levels = [l for l in levels if l.side == "long"]
103
+ short_levels = [l for l in levels if l.side == "short"]
104
+
105
+ # Sort by distance from current price
106
+ long_levels.sort(key=lambda x: x.price, reverse=True)
107
+ short_levels.sort(key=lambda x: x.price)
108
+
109
+ # Calculate 24h totals
110
+ cutoff = datetime.now() - timedelta(hours=24)
111
+ recent_24h = [l for l in liquidations if l.timestamp > cutoff]
112
+
113
+ total_24h = sum(float(l.value_usd) for l in recent_24h)
114
+ long_24h = sum(float(l.value_usd) for l in recent_24h if l.side == "long")
115
+ short_24h = sum(float(l.value_usd) for l in recent_24h if l.side == "short")
116
+
117
+ # Find largest single liquidation
118
+ largest = max(liquidations, key=lambda x: x.value_usd) if liquidations else None
119
+
120
+ # Find nearest levels
121
+ nearest_long = long_levels[0] if long_levels else None
122
+ nearest_short = short_levels[0] if short_levels else None
123
+
124
+ # Assess cascade risk
125
+ cascade_risk = self._assess_cascade_risk(
126
+ current_price, long_levels, short_levels
127
+ )
128
+
129
+ return LiquidationSummary(
130
+ symbol=symbol,
131
+ current_price=current_price,
132
+ total_24h_usd=Decimal(str(int(total_24h))),
133
+ long_liquidations_usd=Decimal(str(int(long_24h))),
134
+ short_liquidations_usd=Decimal(str(int(short_24h))),
135
+ largest_single=largest,
136
+ recent_liquidations=liquidations[:20],
137
+ long_levels=long_levels,
138
+ short_levels=short_levels,
139
+ cascade_risk=cascade_risk,
140
+ nearest_long_level=nearest_long,
141
+ nearest_short_level=nearest_short,
142
+ timestamp=datetime.now(),
143
+ )
144
+
145
+ def _assess_cascade_risk(
146
+ self,
147
+ current_price: Decimal,
148
+ long_levels: List[LiquidationLevel],
149
+ short_levels: List[LiquidationLevel],
150
+ ) -> str:
151
+ """
152
+ Assess cascade risk based on nearby liquidation levels.
153
+
154
+ Considers liquidations within 5% of current price.
155
+ """
156
+ price = float(current_price)
157
+ lower_bound = price * 0.95
158
+ upper_bound = price * 1.05
159
+
160
+ # Sum liquidations within range
161
+ nearby_value = 0
162
+
163
+ for level in long_levels:
164
+ if float(level.price) >= lower_bound:
165
+ nearby_value += float(level.total_value_usd)
166
+
167
+ for level in short_levels:
168
+ if float(level.price) <= upper_bound:
169
+ nearby_value += float(level.total_value_usd)
170
+
171
+ # Determine risk level
172
+ if nearby_value >= self.CRITICAL_THRESHOLD:
173
+ return "critical"
174
+ elif nearby_value >= self.HIGH_THRESHOLD:
175
+ return "high"
176
+ elif nearby_value >= self.MEDIUM_THRESHOLD:
177
+ return "medium"
178
+ else:
179
+ return "low"
180
+
181
+ def generate_heatmap_data(
182
+ self,
183
+ symbol: str,
184
+ current_price: Decimal,
185
+ levels: int = 5,
186
+ ) -> Dict:
187
+ """
188
+ Generate heatmap visualization data.
189
+
190
+ Args:
191
+ symbol: Trading symbol
192
+ current_price: Current price
193
+ levels: Number of levels above/below
194
+
195
+ Returns:
196
+ Dict with heatmap data for visualization
197
+ """
198
+ summary = self.get_summary(symbol, current_price)
199
+
200
+ heatmap = {
201
+ "symbol": symbol,
202
+ "current_price": float(current_price),
203
+ "long_levels": [],
204
+ "short_levels": [],
205
+ }
206
+
207
+ # Add long levels (below price)
208
+ for level in summary.long_levels[:levels]:
209
+ distance_pct = (float(current_price) - float(level.price)) / float(current_price) * 100
210
+ heatmap["long_levels"].append({
211
+ "price": float(level.price),
212
+ "value_usd": float(level.total_value_usd),
213
+ "distance_pct": round(distance_pct, 1),
214
+ "density": level.density,
215
+ })
216
+
217
+ # Add short levels (above price)
218
+ for level in summary.short_levels[:levels]:
219
+ distance_pct = (float(level.price) - float(current_price)) / float(current_price) * 100
220
+ heatmap["short_levels"].append({
221
+ "price": float(level.price),
222
+ "value_usd": float(level.total_value_usd),
223
+ "distance_pct": round(distance_pct, 1),
224
+ "density": level.density,
225
+ })
226
+
227
+ return heatmap
228
+
229
+ def get_recent_large_liquidations(
230
+ self,
231
+ symbol: str,
232
+ min_value_usd: float = 1_000_000,
233
+ limit: int = 10,
234
+ ) -> List[Dict]:
235
+ """
236
+ Get recent large liquidation events.
237
+
238
+ Args:
239
+ symbol: Trading symbol
240
+ min_value_usd: Minimum liquidation size
241
+ limit: Maximum results
242
+
243
+ Returns:
244
+ List of large liquidations
245
+ """
246
+ liquidations = self.client.get_recent_liquidations(
247
+ symbol, limit=limit * 2, min_value_usd=min_value_usd
248
+ )
249
+
250
+ # Filter by size and limit
251
+ large = [l for l in liquidations if float(l.value_usd) >= min_value_usd]
252
+ large = sorted(large, key=lambda x: x.value_usd, reverse=True)[:limit]
253
+
254
+ return [
255
+ {
256
+ "exchange": l.exchange,
257
+ "side": l.side,
258
+ "price": float(l.price),
259
+ "quantity": float(l.quantity),
260
+ "value_usd": float(l.value_usd),
261
+ "time_ago": self._time_ago(l.timestamp),
262
+ }
263
+ for l in large
264
+ ]
265
+
266
+ def _time_ago(self, dt: datetime) -> str:
267
+ """Format timestamp as time ago string."""
268
+ delta = datetime.now() - dt
269
+ minutes = int(delta.total_seconds() / 60)
270
+
271
+ if minutes < 60:
272
+ return f"{minutes}m ago"
273
+ elif minutes < 1440:
274
+ hours = minutes // 60
275
+ return f"{hours}h ago"
276
+ else:
277
+ days = minutes // 1440
278
+ return f"{days}d ago"
279
+
280
+
281
+ def demo():
282
+ """Demonstrate liquidation monitor."""
283
+ monitor = LiquidationMonitor()
284
+
285
+ print("=" * 70)
286
+ print("LIQUIDATION MONITOR")
287
+ print("=" * 70)
288
+
289
+ # Get BTC liquidation summary
290
+ summary = monitor.get_summary("BTC", Decimal("67500"))
291
+
292
+ print(f"\nšŸ’„ {summary.symbol} LIQUIDATION SUMMARY")
293
+ print(f" Current Price: ${summary.current_price:,}")
294
+ print("-" * 60)
295
+
296
+ # 24h totals
297
+ print(f"\n24h Liquidations:")
298
+ print(f" Total: ${float(summary.total_24h_usd)/1e6:,.1f}M")
299
+ print(f" Longs: ${float(summary.long_liquidations_usd)/1e6:,.1f}M")
300
+ print(f" Shorts: ${float(summary.short_liquidations_usd)/1e6:,.1f}M")
301
+
302
+ # Cascade risk
303
+ risk_emoji = {
304
+ "low": "🟢",
305
+ "medium": "🟔",
306
+ "high": "🟠",
307
+ "critical": "šŸ”“",
308
+ }
309
+ print(f"\nCascade Risk: {risk_emoji[summary.cascade_risk]} {summary.cascade_risk.upper()}")
310
+
311
+ # Heatmap
312
+ print("\n" + "-" * 60)
313
+ print("LIQUIDATION HEATMAP")
314
+ print("-" * 60)
315
+
316
+ print(f"\nLONG LIQUIDATIONS (below ${summary.current_price:,}):")
317
+ for level in summary.long_levels[:4]:
318
+ bar_len = min(int(float(level.total_value_usd) / 10_000_000), 20)
319
+ bar = "ā–ˆ" * bar_len
320
+ density_mark = "āš ļø " if level.density in ["high", "critical"] else ""
321
+ print(
322
+ f" ${float(level.price):>10,.0f} {bar} "
323
+ f"${float(level.total_value_usd)/1e6:.0f}M {density_mark}{level.density.upper()}"
324
+ )
325
+
326
+ print(f"\nSHORT LIQUIDATIONS (above ${summary.current_price:,}):")
327
+ for level in summary.short_levels[:4]:
328
+ bar_len = min(int(float(level.total_value_usd) / 10_000_000), 20)
329
+ bar = "ā–ˆ" * bar_len
330
+ density_mark = "āš ļø " if level.density in ["high", "critical"] else ""
331
+ print(
332
+ f" ${float(level.price):>10,.0f} {bar} "
333
+ f"${float(level.total_value_usd)/1e6:.0f}M {density_mark}{level.density.upper()}"
334
+ )
335
+
336
+ # Recent large liquidations
337
+ print("\n" + "-" * 60)
338
+ print("RECENT LARGE LIQUIDATIONS (>$1M)")
339
+ print("-" * 60)
340
+
341
+ large = monitor.get_recent_large_liquidations("BTC", min_value_usd=1_000_000, limit=5)
342
+ if large:
343
+ print(f"\n{'Exchange':<10} {'Side':<6} {'Price':>12} {'Value':>12} {'When':>10}")
344
+ print("-" * 60)
345
+ for l in large:
346
+ print(
347
+ f"{l['exchange']:<10} "
348
+ f"{l['side']:<6} "
349
+ f"${l['price']:>10,.0f} "
350
+ f"${l['value_usd']/1e6:>10.1f}M "
351
+ f"{l['time_ago']:>10}"
352
+ )
353
+
354
+
355
+ if __name__ == "__main__":
356
+ demo()