@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,459 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Output formatters for derivatives tracker.
4
+
5
+ Provides consistent formatting for:
6
+ - Console output (tables, charts)
7
+ - JSON export
8
+ - Report generation
9
+ """
10
+
11
+ import json
12
+ from dataclasses import asdict
13
+ from datetime import datetime
14
+ from decimal import Decimal
15
+ from typing import Any, Dict, List, Optional, Union
16
+
17
+
18
+ class DecimalEncoder(json.JSONEncoder):
19
+ """JSON encoder that handles Decimal types."""
20
+
21
+ def default(self, obj):
22
+ if isinstance(obj, Decimal):
23
+ return float(obj)
24
+ if isinstance(obj, datetime):
25
+ return obj.isoformat()
26
+ return super().default(obj)
27
+
28
+
29
+ def format_currency(value: Union[float, Decimal], decimals: int = 0) -> str:
30
+ """Format value as currency."""
31
+ val = float(value)
32
+ if abs(val) >= 1e9:
33
+ return f"${val/1e9:.1f}B"
34
+ elif abs(val) >= 1e6:
35
+ return f"${val/1e6:.1f}M"
36
+ elif abs(val) >= 1e3:
37
+ return f"${val/1e3:.1f}K"
38
+ else:
39
+ return f"${val:,.{decimals}f}"
40
+
41
+
42
+ def format_percent(value: float, decimals: int = 2, sign: bool = True) -> str:
43
+ """Format value as percentage."""
44
+ if sign:
45
+ return f"{value:+.{decimals}f}%"
46
+ else:
47
+ return f"{value:.{decimals}f}%"
48
+
49
+
50
+ def format_time_ago(dt: datetime) -> str:
51
+ """Format datetime as time ago string."""
52
+ delta = datetime.now() - dt
53
+ minutes = int(delta.total_seconds() / 60)
54
+
55
+ if minutes < 1:
56
+ return "just now"
57
+ elif minutes < 60:
58
+ return f"{minutes}m ago"
59
+ elif minutes < 1440:
60
+ hours = minutes // 60
61
+ return f"{hours}h ago"
62
+ else:
63
+ days = minutes // 1440
64
+ return f"{days}d ago"
65
+
66
+
67
+ class ConsoleFormatter:
68
+ """
69
+ Formats data for console output.
70
+
71
+ Features:
72
+ - ASCII tables
73
+ - Bar charts
74
+ - Color indicators
75
+ - Consistent widths
76
+ """
77
+
78
+ # Box drawing characters
79
+ H_LINE = "─"
80
+ V_LINE = "│"
81
+ CROSS = "┼"
82
+ TOP_LEFT = "┌"
83
+ TOP_RIGHT = "┐"
84
+ BOT_LEFT = "└"
85
+ BOT_RIGHT = "┘"
86
+
87
+ # Sentiment indicators
88
+ BULLISH = "🟢"
89
+ BEARISH = "🔴"
90
+ NEUTRAL = "🟡"
91
+ MIXED = "⚪"
92
+
93
+ # Risk indicators
94
+ RISK_CRITICAL = "🔴"
95
+ RISK_HIGH = "🟠"
96
+ RISK_MEDIUM = "🟡"
97
+ RISK_LOW = "🟢"
98
+
99
+ def __init__(self, width: int = 70):
100
+ """Initialize formatter with terminal width."""
101
+ self.width = width
102
+
103
+ def header(self, title: str, char: str = "=") -> str:
104
+ """Create a centered header."""
105
+ return f"{char * self.width}\n{title.center(self.width)}\n{char * self.width}"
106
+
107
+ def subheader(self, title: str, char: str = "-") -> str:
108
+ """Create a subheader."""
109
+ return f"{char * self.width}\n{title}\n{char * self.width}"
110
+
111
+ def section(self, title: str) -> str:
112
+ """Create a section divider."""
113
+ return f"\n{self.H_LINE * self.width}\n{title}\n{self.H_LINE * self.width}"
114
+
115
+ def sentiment_icon(self, sentiment: str) -> str:
116
+ """Get icon for sentiment."""
117
+ icons = {
118
+ "bullish": self.BULLISH,
119
+ "bearish": self.BEARISH,
120
+ "neutral": self.NEUTRAL,
121
+ "mixed": self.MIXED,
122
+ }
123
+ return icons.get(sentiment.lower(), self.NEUTRAL)
124
+
125
+ def risk_icon(self, risk: str) -> str:
126
+ """Get icon for risk level."""
127
+ icons = {
128
+ "critical": self.RISK_CRITICAL,
129
+ "high": self.RISK_HIGH,
130
+ "medium": self.RISK_MEDIUM,
131
+ "low": self.RISK_LOW,
132
+ }
133
+ return icons.get(risk.lower(), self.RISK_LOW)
134
+
135
+ def bar(
136
+ self,
137
+ value: float,
138
+ max_value: float,
139
+ width: int = 20,
140
+ char: str = "█",
141
+ ) -> str:
142
+ """Create a horizontal bar."""
143
+ if max_value <= 0:
144
+ return ""
145
+ fill = min(int(value / max_value * width), width)
146
+ return char * fill
147
+
148
+ def format_funding_table(
149
+ self,
150
+ rates: List[Dict],
151
+ ) -> str:
152
+ """Format funding rates as table."""
153
+ lines = []
154
+ lines.append(f"{'Exchange':<12} {'Current':>10} {'Annualized':>12} {'Next Payment':>14}")
155
+ lines.append("-" * 50)
156
+
157
+ for rate in sorted(rates, key=lambda r: r.get("rate", 0), reverse=True):
158
+ lines.append(
159
+ f"{rate['exchange']:<12} "
160
+ f"{rate['rate']:>+9.4%} "
161
+ f"{rate['annualized']:>+11.1f}% "
162
+ f"{rate.get('next_payment', 'N/A'):>14}"
163
+ )
164
+
165
+ return "\n".join(lines)
166
+
167
+ def format_oi_table(
168
+ self,
169
+ exchanges: List[Dict],
170
+ total: float,
171
+ ) -> str:
172
+ """Format open interest as table."""
173
+ lines = []
174
+ lines.append(f"{'Exchange':<12} {'OI (USD)':>14} {'24h Chg':>10} {'7d Chg':>10} {'Share':>8}")
175
+ lines.append("-" * 60)
176
+
177
+ for ex in sorted(exchanges, key=lambda x: x.get("oi_usd", 0), reverse=True):
178
+ share = ex.get("oi_usd", 0) / total * 100 if total > 0 else 0
179
+ lines.append(
180
+ f"{ex['exchange']:<12} "
181
+ f"{format_currency(ex.get('oi_usd', 0)):>14} "
182
+ f"{ex.get('change_24h', 0):>+9.1f}% "
183
+ f"{ex.get('change_7d', 0):>+9.1f}% "
184
+ f"{share:>7.1f}%"
185
+ )
186
+
187
+ return "\n".join(lines)
188
+
189
+ def format_liquidation_heatmap(
190
+ self,
191
+ levels: List[Dict],
192
+ side: str,
193
+ max_value: float,
194
+ ) -> str:
195
+ """Format liquidation levels as visual heatmap."""
196
+ lines = []
197
+
198
+ for level in levels:
199
+ bar_len = min(int(level.get("value_usd", 0) / 10_000_000), 20)
200
+ bar = "█" * bar_len
201
+ density = level.get("density", "low")
202
+ marker = "⚠️ " if density in ["high", "critical"] else ""
203
+
204
+ lines.append(
205
+ f" ${float(level.get('price', 0)):>10,.0f} {bar} "
206
+ f"{format_currency(level.get('value_usd', 0))} "
207
+ f"{marker}{density.upper()}"
208
+ )
209
+
210
+ return "\n".join(lines)
211
+
212
+ def format_options_summary(
213
+ self,
214
+ analysis: Dict,
215
+ ) -> str:
216
+ """Format options analysis summary."""
217
+ lines = []
218
+
219
+ lines.append(f"Implied Volatility:")
220
+ lines.append(f" ATM IV: {analysis.get('atm_iv', 0):.1f}%")
221
+ lines.append(f" Interpretation: {analysis.get('iv_interpretation', 'unknown').upper()}")
222
+ lines.append(f" IV Rank: {analysis.get('iv_percentile', 50):.0f}th percentile")
223
+
224
+ lines.append(f"\nPut/Call Analysis:")
225
+ lines.append(f" PCR (Volume): {analysis.get('pcr_volume', 0):.2f}")
226
+ lines.append(f" PCR (OI): {analysis.get('pcr_oi', 0):.2f}")
227
+ lines.append(f" Sentiment: {analysis.get('pcr_sentiment', 'neutral').upper()}")
228
+
229
+ lines.append(f"\nMax Pain:")
230
+ lines.append(f" Price: ${analysis.get('max_pain', 0):,.0f}")
231
+ lines.append(f" Distance: {analysis.get('max_pain_distance', 0):+.1f}% from current")
232
+
233
+ return "\n".join(lines)
234
+
235
+ def format_basis_term_structure(
236
+ self,
237
+ structure: List[Dict],
238
+ ) -> str:
239
+ """Format term structure as visual chart."""
240
+ lines = []
241
+
242
+ for point in structure:
243
+ annual = point.get("annualized_pct", 0)
244
+ bar = "+" * min(int(abs(annual) / 2), 20)
245
+ direction = "▲" if annual > 0 else "▼"
246
+ lines.append(
247
+ f"{point.get('expiry', 'N/A'):<12} {direction} {bar} "
248
+ f"{annual:+.1f}%"
249
+ )
250
+
251
+ return "\n".join(lines)
252
+
253
+
254
+ class JSONFormatter:
255
+ """
256
+ Formats data for JSON export.
257
+
258
+ Features:
259
+ - Clean JSON structure
260
+ - Decimal handling
261
+ - Datetime serialization
262
+ - Nested object support
263
+ """
264
+
265
+ def __init__(self, indent: int = 2):
266
+ """Initialize formatter."""
267
+ self.indent = indent
268
+
269
+ def format(self, data: Any) -> str:
270
+ """Format any data as JSON string."""
271
+ return json.dumps(
272
+ self._prepare(data),
273
+ cls=DecimalEncoder,
274
+ indent=self.indent,
275
+ )
276
+
277
+ def _prepare(self, obj: Any) -> Any:
278
+ """Prepare object for JSON serialization."""
279
+ if hasattr(obj, "__dataclass_fields__"):
280
+ # Dataclass - convert to dict
281
+ return {k: self._prepare(v) for k, v in asdict(obj).items()}
282
+ elif isinstance(obj, dict):
283
+ return {k: self._prepare(v) for k, v in obj.items()}
284
+ elif isinstance(obj, (list, tuple)):
285
+ return [self._prepare(item) for item in obj]
286
+ elif isinstance(obj, Decimal):
287
+ return float(obj)
288
+ elif isinstance(obj, datetime):
289
+ return obj.isoformat()
290
+ else:
291
+ return obj
292
+
293
+ def funding_report(
294
+ self,
295
+ symbol: str,
296
+ analysis: Dict,
297
+ ) -> str:
298
+ """Generate funding rate JSON report."""
299
+ report = {
300
+ "report_type": "funding_rates",
301
+ "symbol": symbol,
302
+ "generated_at": datetime.now().isoformat(),
303
+ "data": self._prepare(analysis),
304
+ }
305
+ return self.format(report)
306
+
307
+ def oi_report(
308
+ self,
309
+ symbol: str,
310
+ analysis: Dict,
311
+ ) -> str:
312
+ """Generate open interest JSON report."""
313
+ report = {
314
+ "report_type": "open_interest",
315
+ "symbol": symbol,
316
+ "generated_at": datetime.now().isoformat(),
317
+ "data": self._prepare(analysis),
318
+ }
319
+ return self.format(report)
320
+
321
+ def derivatives_dashboard(
322
+ self,
323
+ symbol: str,
324
+ funding: Dict,
325
+ oi: Dict,
326
+ liquidations: Dict,
327
+ options: Optional[Dict] = None,
328
+ basis: Optional[Dict] = None,
329
+ ) -> str:
330
+ """Generate complete derivatives dashboard JSON."""
331
+ report = {
332
+ "report_type": "derivatives_dashboard",
333
+ "symbol": symbol,
334
+ "generated_at": datetime.now().isoformat(),
335
+ "funding": self._prepare(funding),
336
+ "open_interest": self._prepare(oi),
337
+ "liquidations": self._prepare(liquidations),
338
+ }
339
+
340
+ if options:
341
+ report["options"] = self._prepare(options)
342
+ if basis:
343
+ report["basis"] = self._prepare(basis)
344
+
345
+ return self.format(report)
346
+
347
+
348
+ class ReportGenerator:
349
+ """
350
+ Generates formatted reports combining multiple data sources.
351
+ """
352
+
353
+ def __init__(self):
354
+ """Initialize report generator."""
355
+ self.console = ConsoleFormatter()
356
+ self.json_fmt = JSONFormatter()
357
+
358
+ def derivatives_summary(
359
+ self,
360
+ symbol: str,
361
+ funding: Dict,
362
+ oi: Dict,
363
+ liquidations: Dict,
364
+ format: str = "console",
365
+ ) -> str:
366
+ """
367
+ Generate derivatives market summary.
368
+
369
+ Args:
370
+ symbol: Trading symbol
371
+ funding: Funding rate analysis
372
+ oi: Open interest analysis
373
+ liquidations: Liquidation summary
374
+ format: Output format ("console" or "json")
375
+
376
+ Returns:
377
+ Formatted summary string
378
+ """
379
+ if format == "json":
380
+ return self.json_fmt.derivatives_dashboard(
381
+ symbol, funding, oi, liquidations
382
+ )
383
+
384
+ # Console format
385
+ lines = []
386
+ lines.append(self.console.header(f"{symbol} DERIVATIVES SUMMARY"))
387
+
388
+ # Funding section
389
+ lines.append(f"\n📊 FUNDING RATES")
390
+ lines.append(f" Weighted Average: {format_percent(funding.get('weighted_avg', 0) * 100, 4)}")
391
+ lines.append(f" Annualized: {format_percent(funding.get('annualized_avg', 0), 1)}")
392
+ lines.append(f" Sentiment: {funding.get('sentiment', 'unknown').upper()}")
393
+
394
+ # OI section
395
+ lines.append(f"\n📈 OPEN INTEREST")
396
+ lines.append(f" Total: {format_currency(oi.get('total_oi_usd', 0))}")
397
+ lines.append(f" 24h Change: {format_percent(oi.get('avg_change_24h', 0), 1)}")
398
+ lines.append(f" Trend: {oi.get('trend', 'unknown').title()}")
399
+
400
+ # Liquidations section
401
+ lines.append(f"\n💥 LIQUIDATIONS")
402
+ lines.append(f" 24h Total: {format_currency(liquidations.get('total_24h_usd', 0))}")
403
+ lines.append(f" Longs: {format_currency(liquidations.get('long_liquidations_usd', 0))}")
404
+ lines.append(f" Shorts: {format_currency(liquidations.get('short_liquidations_usd', 0))}")
405
+ risk = liquidations.get('cascade_risk', 'low')
406
+ lines.append(f" Cascade Risk: {self.console.risk_icon(risk)} {risk.upper()}")
407
+
408
+ lines.append(f"\n{self.console.H_LINE * self.console.width}")
409
+ lines.append(f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
410
+
411
+ return "\n".join(lines)
412
+
413
+
414
+ def demo():
415
+ """Demonstrate formatters."""
416
+ console = ConsoleFormatter()
417
+ json_fmt = JSONFormatter()
418
+
419
+ print(console.header("FORMATTER DEMO"))
420
+
421
+ # Currency formatting
422
+ print("\nCurrency Formatting:")
423
+ print(f" $1,234,567,890 → {format_currency(1234567890)}")
424
+ print(f" $12,345,678 → {format_currency(12345678)}")
425
+ print(f" $123,456 → {format_currency(123456)}")
426
+
427
+ # Percent formatting
428
+ print("\nPercent Formatting:")
429
+ print(f" 5.5 → {format_percent(5.5)}")
430
+ print(f" -3.25 → {format_percent(-3.25)}")
431
+ print(f" 0.02 → {format_percent(0.02, 4)}")
432
+
433
+ # Bars
434
+ print("\nBar Charts:")
435
+ print(f" 100/100: {console.bar(100, 100)}")
436
+ print(f" 50/100: {console.bar(50, 100)}")
437
+ print(f" 25/100: {console.bar(25, 100)}")
438
+
439
+ # Sentiment icons
440
+ print("\nSentiment Icons:")
441
+ for sent in ["bullish", "bearish", "neutral", "mixed"]:
442
+ print(f" {sent}: {console.sentiment_icon(sent)}")
443
+
444
+ # JSON export
445
+ print("\n" + console.section("JSON EXPORT"))
446
+ sample_data = {
447
+ "symbol": "BTC",
448
+ "price": Decimal("67500.50"),
449
+ "timestamp": datetime.now(),
450
+ "metrics": {
451
+ "funding": 0.01,
452
+ "oi": Decimal("15000000000"),
453
+ }
454
+ }
455
+ print(json_fmt.format(sample_data))
456
+
457
+
458
+ if __name__ == "__main__":
459
+ demo()