@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.
- package/.claude-plugin/plugin.json +22 -0
- package/LICENSE +21 -0
- package/README.md +173 -0
- package/agents/derivatives-agent.md +408 -0
- package/package.json +43 -0
- package/skills/skill-adapter/assets/README.md +6 -0
- package/skills/skill-adapter/assets/config-template.json +32 -0
- package/skills/skill-adapter/assets/skill-schema.json +28 -0
- package/skills/skill-adapter/assets/test-data.json +27 -0
- package/skills/skill-adapter/references/README.md +4 -0
- package/skills/skill-adapter/references/best-practices.md +69 -0
- package/skills/skill-adapter/references/examples.md +73 -0
- package/skills/skill-adapter/scripts/README.md +8 -0
- package/skills/skill-adapter/scripts/helper-template.sh +42 -0
- package/skills/skill-adapter/scripts/validation.sh +32 -0
- package/skills/tracking-crypto-derivatives/ARD.md +376 -0
- package/skills/tracking-crypto-derivatives/PRD.md +258 -0
- package/skills/tracking-crypto-derivatives/SKILL.md +127 -0
- package/skills/tracking-crypto-derivatives/config/settings.yaml +152 -0
- package/skills/tracking-crypto-derivatives/references/errors.md +224 -0
- package/skills/tracking-crypto-derivatives/references/examples.md +460 -0
- package/skills/tracking-crypto-derivatives/references/implementation.md +113 -0
- package/skills/tracking-crypto-derivatives/scripts/basis_calculator.py +377 -0
- package/skills/tracking-crypto-derivatives/scripts/derivatives_tracker.py +579 -0
- package/skills/tracking-crypto-derivatives/scripts/formatters.py +459 -0
- package/skills/tracking-crypto-derivatives/scripts/funding_tracker.py +308 -0
- package/skills/tracking-crypto-derivatives/scripts/liquidation_monitor.py +356 -0
- package/skills/tracking-crypto-derivatives/scripts/oi_analyzer.py +338 -0
- 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()
|