@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,338 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Open Interest analyzer.
4
+
5
+ Analyzes open interest across exchanges with:
6
+ - Multi-exchange aggregation
7
+ - Trend analysis
8
+ - OI vs price divergence detection
9
+ - Long/short ratio tracking
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, OpenInterest, Exchange
18
+
19
+
20
+ @dataclass
21
+ class OIAnalysis:
22
+ """Aggregated open interest analysis."""
23
+
24
+ symbol: str
25
+ exchanges: List[OpenInterest]
26
+ total_oi_usd: Decimal
27
+ total_oi_contracts: Decimal
28
+ avg_change_24h: float
29
+ avg_change_7d: float
30
+ weighted_long_ratio: float
31
+ dominant_exchange: str
32
+ dominant_share: float
33
+ trend: str # "increasing", "decreasing", "stable"
34
+ trend_strength: str # "strong", "moderate", "weak"
35
+ timestamp: datetime
36
+
37
+ @property
38
+ def is_increasing(self) -> bool:
39
+ """Check if OI is increasing."""
40
+ return self.avg_change_24h > 2.0
41
+
42
+ @property
43
+ def is_decreasing(self) -> bool:
44
+ """Check if OI is decreasing."""
45
+ return self.avg_change_24h < -2.0
46
+
47
+ @property
48
+ def long_percentage(self) -> float:
49
+ """Calculate long percentage from ratio."""
50
+ return self.weighted_long_ratio / (1 + self.weighted_long_ratio) * 100
51
+
52
+
53
+ @dataclass
54
+ class OIDivergence:
55
+ """OI vs Price divergence signal."""
56
+
57
+ symbol: str
58
+ oi_direction: str # "up" or "down"
59
+ price_direction: str # "up" or "down"
60
+ oi_change_pct: float
61
+ price_change_pct: float
62
+ signal: str # "bullish", "bearish", "short_squeeze", "long_liquidation"
63
+ description: str
64
+ confidence: str # "high", "medium", "low"
65
+
66
+
67
+ class OIAnalyzer:
68
+ """
69
+ Analyzes open interest patterns and signals.
70
+
71
+ Features:
72
+ - Multi-exchange aggregation
73
+ - Trend detection
74
+ - Divergence analysis
75
+ - Long/short positioning
76
+ """
77
+
78
+ # Trend thresholds
79
+ STRONG_CHANGE = 10.0 # >10% is strong
80
+ MODERATE_CHANGE = 5.0 # >5% is moderate
81
+
82
+ def __init__(
83
+ self,
84
+ client: Optional[ExchangeClient] = None,
85
+ ):
86
+ """
87
+ Initialize OI analyzer.
88
+
89
+ Args:
90
+ client: Exchange client for data fetching
91
+ """
92
+ self.client = client or ExchangeClient(use_mock=True)
93
+
94
+ def analyze(
95
+ self,
96
+ symbol: str,
97
+ exchanges: Optional[List[Exchange]] = None,
98
+ ) -> OIAnalysis:
99
+ """
100
+ Analyze open interest for a symbol.
101
+
102
+ Args:
103
+ symbol: Trading symbol (e.g., "BTC")
104
+ exchanges: Exchanges to include
105
+
106
+ Returns:
107
+ OIAnalysis with all metrics
108
+ """
109
+ # Fetch OI from all exchanges
110
+ oi_list = self.client.get_all_open_interest(symbol, exchanges)
111
+
112
+ if not oi_list:
113
+ raise ValueError(f"No OI data available for {symbol}")
114
+
115
+ # Calculate totals
116
+ total_usd = sum(float(oi.oi_usd) for oi in oi_list)
117
+ total_contracts = sum(float(oi.oi_contracts) for oi in oi_list)
118
+
119
+ # Calculate weighted averages
120
+ avg_24h = sum(
121
+ oi.change_24h_pct * float(oi.oi_usd) for oi in oi_list
122
+ ) / total_usd
123
+ avg_7d = sum(
124
+ oi.change_7d_pct * float(oi.oi_usd) for oi in oi_list
125
+ ) / total_usd
126
+
127
+ # Weighted long ratio
128
+ weighted_long = sum(
129
+ oi.long_ratio * float(oi.oi_usd) for oi in oi_list
130
+ ) / total_usd
131
+
132
+ # Find dominant exchange
133
+ dominant = max(oi_list, key=lambda x: x.oi_usd)
134
+ dominant_share = float(dominant.oi_usd) / total_usd * 100
135
+
136
+ # Determine trend
137
+ trend, strength = self._analyze_trend(avg_24h, avg_7d)
138
+
139
+ return OIAnalysis(
140
+ symbol=symbol,
141
+ exchanges=oi_list,
142
+ total_oi_usd=Decimal(str(int(total_usd))),
143
+ total_oi_contracts=Decimal(str(int(total_contracts))),
144
+ avg_change_24h=round(avg_24h, 2),
145
+ avg_change_7d=round(avg_7d, 2),
146
+ weighted_long_ratio=round(weighted_long, 2),
147
+ dominant_exchange=dominant.exchange,
148
+ dominant_share=round(dominant_share, 1),
149
+ trend=trend,
150
+ trend_strength=strength,
151
+ timestamp=datetime.now(),
152
+ )
153
+
154
+ def _analyze_trend(
155
+ self,
156
+ change_24h: float,
157
+ change_7d: float,
158
+ ) -> tuple:
159
+ """
160
+ Analyze OI trend from changes.
161
+
162
+ Returns:
163
+ (trend, strength) tuple
164
+ """
165
+ # Determine direction from 24h change
166
+ if change_24h > 2.0:
167
+ trend = "increasing"
168
+ elif change_24h < -2.0:
169
+ trend = "decreasing"
170
+ else:
171
+ trend = "stable"
172
+
173
+ # Determine strength from magnitude
174
+ abs_change = abs(change_24h)
175
+ if abs_change >= self.STRONG_CHANGE:
176
+ strength = "strong"
177
+ elif abs_change >= self.MODERATE_CHANGE:
178
+ strength = "moderate"
179
+ else:
180
+ strength = "weak"
181
+
182
+ return trend, strength
183
+
184
+ def detect_divergence(
185
+ self,
186
+ symbol: str,
187
+ price_change_24h: float,
188
+ ) -> Optional[OIDivergence]:
189
+ """
190
+ Detect OI vs price divergence.
191
+
192
+ Classic interpretation:
193
+ - Rising OI + Rising Price = Strong bullish trend
194
+ - Rising OI + Falling Price = Strong bearish trend
195
+ - Falling OI + Rising Price = Short covering (weak rally)
196
+ - Falling OI + Falling Price = Long liquidation (weak selloff)
197
+
198
+ Args:
199
+ symbol: Trading symbol
200
+ price_change_24h: Price change in last 24h (%)
201
+
202
+ Returns:
203
+ OIDivergence if divergence detected
204
+ """
205
+ analysis = self.analyze(symbol)
206
+ oi_change = analysis.avg_change_24h
207
+
208
+ oi_dir = "up" if oi_change > 0 else "down"
209
+ price_dir = "up" if price_change_24h > 0 else "down"
210
+
211
+ # Determine signal
212
+ if oi_change > 2 and price_change_24h > 2:
213
+ signal = "bullish"
214
+ desc = "Rising OI confirms bullish trend - new longs entering"
215
+ confidence = "high" if oi_change > 5 else "medium"
216
+ elif oi_change > 2 and price_change_24h < -2:
217
+ signal = "bearish"
218
+ desc = "Rising OI during selloff - new shorts entering"
219
+ confidence = "high" if oi_change > 5 else "medium"
220
+ elif oi_change < -2 and price_change_24h > 2:
221
+ signal = "short_squeeze"
222
+ desc = "Falling OI during rally - short covering, may be weak"
223
+ confidence = "medium"
224
+ elif oi_change < -2 and price_change_24h < -2:
225
+ signal = "long_liquidation"
226
+ desc = "Falling OI during selloff - long liquidations, may find support"
227
+ confidence = "medium"
228
+ else:
229
+ return None # No significant divergence
230
+
231
+ return OIDivergence(
232
+ symbol=symbol,
233
+ oi_direction=oi_dir,
234
+ price_direction=price_dir,
235
+ oi_change_pct=oi_change,
236
+ price_change_pct=price_change_24h,
237
+ signal=signal,
238
+ description=desc,
239
+ confidence=confidence,
240
+ )
241
+
242
+ def get_market_share(
243
+ self,
244
+ symbol: str,
245
+ ) -> List[Dict]:
246
+ """
247
+ Get OI market share by exchange.
248
+
249
+ Args:
250
+ symbol: Trading symbol
251
+
252
+ Returns:
253
+ List of exchange market shares
254
+ """
255
+ analysis = self.analyze(symbol)
256
+ total = float(analysis.total_oi_usd)
257
+
258
+ shares = []
259
+ for oi in analysis.exchanges:
260
+ share = float(oi.oi_usd) / total * 100
261
+ shares.append({
262
+ "exchange": oi.exchange,
263
+ "oi_usd": float(oi.oi_usd),
264
+ "share_pct": round(share, 1),
265
+ "change_24h": oi.change_24h_pct,
266
+ "long_ratio": oi.long_ratio,
267
+ })
268
+
269
+ return sorted(shares, key=lambda x: x["oi_usd"], reverse=True)
270
+
271
+
272
+ def demo():
273
+ """Demonstrate OI analyzer."""
274
+ analyzer = OIAnalyzer()
275
+
276
+ print("=" * 70)
277
+ print("OPEN INTEREST ANALYZER")
278
+ print("=" * 70)
279
+
280
+ # Analyze BTC OI
281
+ analysis = analyzer.analyze("BTC")
282
+
283
+ print(f"\n📈 {analysis.symbol} OPEN INTEREST ANALYSIS")
284
+ print("-" * 60)
285
+
286
+ print(f"\n{'Exchange':<12} {'OI (USD)':>14} {'24h Chg':>10} {'7d Chg':>10} {'Share':>8}")
287
+ print("-" * 60)
288
+
289
+ for oi in sorted(analysis.exchanges, key=lambda x: x.oi_usd, reverse=True):
290
+ share = float(oi.oi_usd) / float(analysis.total_oi_usd) * 100
291
+ print(
292
+ f"{oi.exchange:<12} "
293
+ f"${float(oi.oi_usd)/1e9:>12.2f}B "
294
+ f"{oi.change_24h_pct:>+9.1f}% "
295
+ f"{oi.change_7d_pct:>+9.1f}% "
296
+ f"{share:>7.1f}%"
297
+ )
298
+
299
+ print("-" * 60)
300
+ print(f"\nTotal OI: ${float(analysis.total_oi_usd)/1e9:.2f}B")
301
+ print(f"24h Change: {analysis.avg_change_24h:+.1f}%")
302
+ print(f"7d Change: {analysis.avg_change_7d:+.1f}%")
303
+ print(f"\nLong/Short Ratio: {analysis.weighted_long_ratio:.2f} ({analysis.long_percentage:.1f}% long)")
304
+ print(f"Trend: {analysis.trend_strength.title()} {analysis.trend.title()}")
305
+ print(f"Dominant Exchange: {analysis.dominant_exchange} ({analysis.dominant_share:.1f}%)")
306
+
307
+ # Check for divergence
308
+ print("\n" + "=" * 70)
309
+ print("DIVERGENCE ANALYSIS")
310
+ print("=" * 70)
311
+
312
+ # Simulate price change
313
+ price_change = 3.5 # Example: +3.5% price move
314
+ divergence = analyzer.detect_divergence("BTC", price_change)
315
+
316
+ if divergence:
317
+ print(f"\n🔍 Divergence Detected!")
318
+ print(f" OI: {divergence.oi_direction} ({divergence.oi_change_pct:+.1f}%)")
319
+ print(f" Price: {divergence.price_direction} ({divergence.price_change_pct:+.1f}%)")
320
+ print(f" Signal: {divergence.signal.upper()}")
321
+ print(f" {divergence.description}")
322
+ print(f" Confidence: {divergence.confidence}")
323
+ else:
324
+ print("\nNo significant divergence detected")
325
+
326
+ # Market share breakdown
327
+ print("\n" + "=" * 70)
328
+ print("MARKET SHARE BREAKDOWN")
329
+ print("=" * 70)
330
+
331
+ shares = analyzer.get_market_share("BTC")
332
+ for s in shares:
333
+ bar = "█" * int(s["share_pct"] / 2)
334
+ print(f"{s['exchange']:<12} {bar} {s['share_pct']:.1f}%")
335
+
336
+
337
+ if __name__ == "__main__":
338
+ demo()
@@ -0,0 +1,373 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Options market analyzer.
4
+
5
+ Analyzes crypto options markets with:
6
+ - Implied volatility tracking
7
+ - Put/call ratio analysis
8
+ - Max pain calculation
9
+ - Options flow detection
10
+ """
11
+
12
+ from dataclasses import dataclass
13
+ from decimal import Decimal
14
+ from typing import Dict, List, Optional
15
+ from datetime import datetime, date
16
+
17
+ from exchange_client import ExchangeClient, OptionsSnapshot
18
+
19
+
20
+ @dataclass
21
+ class OptionsAnalysis:
22
+ """Comprehensive options analysis."""
23
+
24
+ symbol: str
25
+ snapshot: OptionsSnapshot
26
+ iv_interpretation: str # "high", "normal", "low"
27
+ iv_percentile: float # Estimated percentile (0-100)
28
+ sentiment_from_pcr: str # "bullish", "bearish", "neutral"
29
+ sentiment_from_skew: str # "bullish", "bearish", "neutral"
30
+ overall_sentiment: str
31
+ max_pain_distance_pct: float # Distance from current to max pain
32
+ expiry_pressure: str # "high", "medium", "low"
33
+ timestamp: datetime
34
+
35
+
36
+ @dataclass
37
+ class OptionsFlow:
38
+ """Significant options trade."""
39
+
40
+ symbol: str
41
+ expiry: str
42
+ strike: Decimal
43
+ option_type: str # "call" or "put"
44
+ side: str # "buy" or "sell"
45
+ size_contracts: int
46
+ premium_usd: Decimal
47
+ iv_at_trade: float
48
+ interpretation: str
49
+ timestamp: datetime
50
+
51
+
52
+ class OptionsAnalyzer:
53
+ """
54
+ Analyzes crypto options markets.
55
+
56
+ Features:
57
+ - IV analysis and percentile ranking
58
+ - Put/call ratio interpretation
59
+ - Max pain calculation
60
+ - Flow analysis
61
+ """
62
+
63
+ # IV interpretation thresholds
64
+ HIGH_IV = 70.0
65
+ LOW_IV = 40.0
66
+
67
+ # Put/call interpretation
68
+ BEARISH_PCR = 1.2 # Above this is bearish
69
+ BULLISH_PCR = 0.7 # Below this is bullish
70
+
71
+ def __init__(
72
+ self,
73
+ client: Optional[ExchangeClient] = None,
74
+ ):
75
+ """
76
+ Initialize options analyzer.
77
+
78
+ Args:
79
+ client: Exchange client for data fetching
80
+ """
81
+ self.client = client or ExchangeClient(use_mock=True)
82
+
83
+ def analyze(
84
+ self,
85
+ symbol: str,
86
+ expiry: Optional[str] = None,
87
+ current_price: Optional[Decimal] = None,
88
+ ) -> OptionsAnalysis:
89
+ """
90
+ Analyze options market for a symbol.
91
+
92
+ Args:
93
+ symbol: Trading symbol (e.g., "BTC")
94
+ expiry: Target expiry date (default: nearest)
95
+ current_price: Current spot price
96
+
97
+ Returns:
98
+ OptionsAnalysis with all metrics
99
+ """
100
+ # Fetch options snapshot
101
+ snapshot = self.client.get_options_snapshot(symbol, expiry)
102
+
103
+ if snapshot is None:
104
+ raise ValueError(f"No options data available for {symbol}")
105
+
106
+ # Set default price if not provided
107
+ if current_price is None:
108
+ if symbol == "BTC":
109
+ current_price = Decimal("67500")
110
+ elif symbol == "ETH":
111
+ current_price = Decimal("2500")
112
+ else:
113
+ current_price = Decimal("100")
114
+
115
+ # Interpret IV
116
+ iv_interp, iv_pctl = self._interpret_iv(snapshot.atm_iv, symbol)
117
+
118
+ # Interpret put/call ratio
119
+ pcr_sentiment = self._interpret_pcr(snapshot.put_call_ratio_volume)
120
+
121
+ # Interpret skew (simplified - just using PCR OI as proxy)
122
+ skew_sentiment = self._interpret_pcr(snapshot.put_call_ratio_oi)
123
+
124
+ # Overall sentiment (combine signals)
125
+ overall = self._combine_sentiment(pcr_sentiment, skew_sentiment)
126
+
127
+ # Max pain distance
128
+ max_pain_dist = (
129
+ (float(snapshot.max_pain) - float(current_price))
130
+ / float(current_price) * 100
131
+ )
132
+
133
+ # Expiry pressure (days until expiry)
134
+ expiry_pressure = self._assess_expiry_pressure(snapshot.expiry)
135
+
136
+ return OptionsAnalysis(
137
+ symbol=symbol,
138
+ snapshot=snapshot,
139
+ iv_interpretation=iv_interp,
140
+ iv_percentile=iv_pctl,
141
+ sentiment_from_pcr=pcr_sentiment,
142
+ sentiment_from_skew=skew_sentiment,
143
+ overall_sentiment=overall,
144
+ max_pain_distance_pct=round(max_pain_dist, 2),
145
+ expiry_pressure=expiry_pressure,
146
+ timestamp=datetime.now(),
147
+ )
148
+
149
+ def _interpret_iv(
150
+ self,
151
+ iv: float,
152
+ symbol: str,
153
+ ) -> tuple:
154
+ """
155
+ Interpret implied volatility.
156
+
157
+ Returns:
158
+ (interpretation, estimated_percentile)
159
+ """
160
+ # Adjust thresholds by asset (crypto generally high IV)
161
+ high = self.HIGH_IV
162
+ low = self.LOW_IV
163
+
164
+ if iv >= high:
165
+ interp = "high"
166
+ pctl = min(100, 50 + (iv - high))
167
+ elif iv <= low:
168
+ interp = "low"
169
+ pctl = max(0, 50 - (low - iv))
170
+ else:
171
+ interp = "normal"
172
+ pctl = 50 + ((iv - 55) / 15 * 30) # Scale around 55
173
+
174
+ return interp, round(pctl, 0)
175
+
176
+ def _interpret_pcr(self, pcr: float) -> str:
177
+ """Interpret put/call ratio."""
178
+ if pcr >= self.BEARISH_PCR:
179
+ return "bearish"
180
+ elif pcr <= self.BULLISH_PCR:
181
+ return "bullish"
182
+ else:
183
+ return "neutral"
184
+
185
+ def _combine_sentiment(self, pcr_sent: str, skew_sent: str) -> str:
186
+ """Combine sentiment signals."""
187
+ if pcr_sent == skew_sent:
188
+ return pcr_sent
189
+ elif pcr_sent == "neutral":
190
+ return skew_sent
191
+ elif skew_sent == "neutral":
192
+ return pcr_sent
193
+ else:
194
+ return "mixed"
195
+
196
+ def _assess_expiry_pressure(self, expiry: str) -> str:
197
+ """Assess expiry pressure based on days remaining."""
198
+ try:
199
+ exp_date = datetime.strptime(expiry, "%Y-%m-%d").date()
200
+ days = (exp_date - date.today()).days
201
+
202
+ if days <= 2:
203
+ return "high"
204
+ elif days <= 7:
205
+ return "medium"
206
+ else:
207
+ return "low"
208
+ except Exception:
209
+ return "unknown"
210
+
211
+ def get_max_pain_levels(
212
+ self,
213
+ symbol: str,
214
+ expiries: Optional[List[str]] = None,
215
+ ) -> List[Dict]:
216
+ """
217
+ Get max pain levels for multiple expiries.
218
+
219
+ Args:
220
+ symbol: Trading symbol
221
+ expiries: List of expiry dates
222
+
223
+ Returns:
224
+ List of max pain levels by expiry
225
+ """
226
+ if expiries is None:
227
+ # Default expiries (next few Fridays)
228
+ expiries = ["2025-01-17", "2025-01-24", "2025-01-31"]
229
+
230
+ levels = []
231
+ for exp in expiries:
232
+ try:
233
+ snapshot = self.client.get_options_snapshot(symbol, exp)
234
+ if snapshot:
235
+ levels.append({
236
+ "expiry": exp,
237
+ "max_pain": float(snapshot.max_pain),
238
+ "call_oi": float(snapshot.total_call_oi),
239
+ "put_oi": float(snapshot.total_put_oi),
240
+ "pcr_oi": snapshot.put_call_ratio_oi,
241
+ })
242
+ except Exception:
243
+ continue
244
+
245
+ return levels
246
+
247
+ def generate_mock_flow(
248
+ self,
249
+ symbol: str,
250
+ count: int = 5,
251
+ ) -> List[OptionsFlow]:
252
+ """
253
+ Generate mock options flow data.
254
+
255
+ In production, this would track actual large trades.
256
+ """
257
+ import random
258
+
259
+ if symbol == "BTC":
260
+ base_price = 67500
261
+ elif symbol == "ETH":
262
+ base_price = 2500
263
+ else:
264
+ base_price = 100
265
+
266
+ flows = []
267
+ for i in range(count):
268
+ opt_type = random.choice(["call", "put"])
269
+ # Strikes around current price
270
+ strike = base_price * (1 + random.uniform(-0.15, 0.15))
271
+ strike = round(strike / 1000) * 1000 # Round to nearest 1000
272
+
273
+ size = random.randint(100, 2000)
274
+ premium = size * random.uniform(500, 5000)
275
+
276
+ # Interpretation based on type and likely direction
277
+ if opt_type == "call":
278
+ interp = "Bullish positioning" if random.random() > 0.3 else "Covered call selling"
279
+ else:
280
+ interp = "Bearish bet" if random.random() > 0.5 else "Protective put"
281
+
282
+ flows.append(OptionsFlow(
283
+ symbol=symbol,
284
+ expiry="2025-01-31",
285
+ strike=Decimal(str(int(strike))),
286
+ option_type=opt_type,
287
+ side=random.choice(["buy", "sell"]),
288
+ size_contracts=size,
289
+ premium_usd=Decimal(str(int(premium))),
290
+ iv_at_trade=round(55 + random.uniform(-10, 15), 1),
291
+ interpretation=interp,
292
+ timestamp=datetime.now(),
293
+ ))
294
+
295
+ return sorted(flows, key=lambda x: x.premium_usd, reverse=True)
296
+
297
+
298
+ def demo():
299
+ """Demonstrate options analyzer."""
300
+ analyzer = OptionsAnalyzer()
301
+
302
+ print("=" * 70)
303
+ print("OPTIONS MARKET ANALYZER")
304
+ print("=" * 70)
305
+
306
+ # Analyze BTC options
307
+ analysis = analyzer.analyze("BTC", current_price=Decimal("67500"))
308
+
309
+ print(f"\n📊 {analysis.symbol} OPTIONS ANALYSIS")
310
+ print("-" * 50)
311
+
312
+ snap = analysis.snapshot
313
+ print(f"\nExpiry: {snap.expiry}")
314
+ print(f"Exchange: {snap.exchange}")
315
+
316
+ print(f"\nImplied Volatility:")
317
+ print(f" ATM IV: {snap.atm_iv:.1f}%")
318
+ print(f" Interpretation: {analysis.iv_interpretation.upper()}")
319
+ print(f" IV Rank: {analysis.iv_percentile:.0f}th percentile")
320
+
321
+ print(f"\nPut/Call Analysis:")
322
+ print(f" PCR (Volume): {snap.put_call_ratio_volume:.2f}")
323
+ print(f" PCR (OI): {snap.put_call_ratio_oi:.2f}")
324
+ print(f" Sentiment: {analysis.sentiment_from_pcr.upper()}")
325
+
326
+ print(f"\nMax Pain:")
327
+ print(f" Price: ${snap.max_pain:,.0f}")
328
+ print(f" Distance: {analysis.max_pain_distance_pct:+.1f}% from current")
329
+
330
+ print(f"\nOpen Interest:")
331
+ print(f" Calls: ${float(snap.total_call_oi)/1e9:.2f}B")
332
+ print(f" Puts: ${float(snap.total_put_oi)/1e9:.2f}B")
333
+
334
+ print(f"\nOverall Sentiment: {analysis.overall_sentiment.upper()}")
335
+ print(f"Expiry Pressure: {analysis.expiry_pressure.upper()}")
336
+
337
+ # Max pain levels
338
+ print("\n" + "-" * 50)
339
+ print("MAX PAIN BY EXPIRY")
340
+ print("-" * 50)
341
+
342
+ levels = analyzer.get_max_pain_levels("BTC")
343
+ print(f"\n{'Expiry':<12} {'Max Pain':>12} {'Call OI':>12} {'Put OI':>12} {'PCR':>6}")
344
+ print("-" * 50)
345
+ for lvl in levels:
346
+ print(
347
+ f"{lvl['expiry']:<12} "
348
+ f"${lvl['max_pain']:>10,.0f} "
349
+ f"${lvl['call_oi']/1e9:>10.1f}B "
350
+ f"${lvl['put_oi']/1e9:>10.1f}B "
351
+ f"{lvl['pcr_oi']:>5.2f}"
352
+ )
353
+
354
+ # Options flow
355
+ print("\n" + "-" * 50)
356
+ print("SIGNIFICANT OPTIONS FLOW (Simulated)")
357
+ print("-" * 50)
358
+
359
+ flows = analyzer.generate_mock_flow("BTC", count=5)
360
+ print(f"\n{'Type':<6} {'Strike':>10} {'Size':>8} {'Premium':>12} {'Interpretation':<25}")
361
+ print("-" * 70)
362
+ for flow in flows:
363
+ print(
364
+ f"{flow.option_type.upper():<6} "
365
+ f"${float(flow.strike):>8,.0f} "
366
+ f"{flow.size_contracts:>8} "
367
+ f"${float(flow.premium_usd):>10,.0f} "
368
+ f"{flow.interpretation:<25}"
369
+ )
370
+
371
+
372
+ if __name__ == "__main__":
373
+ demo()