@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,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()
|