199 lines
No EOL
6.6 KiB
Python
199 lines
No EOL
6.6 KiB
Python
import asyncio
|
|
import json
|
|
from datetime import datetime
|
|
from websockets import connect
|
|
import random
|
|
|
|
from rich.console import Console
|
|
from rich.table import Table
|
|
from rich.panel import Panel
|
|
from rich.live import Live
|
|
from rich.layout import Layout
|
|
from rich.text import Text
|
|
from rich.align import Align
|
|
from rich import box
|
|
|
|
|
|
console = Console()
|
|
symbols = ['btcusdt', 'ethusdt', 'solusdt', 'bnbusdt', 'dogeusdt', 'wifusdt']
|
|
websocket_url_base = 'wss://fstream.binance.com/ws/'
|
|
|
|
# Store latest data for each symbol
|
|
funding_data = {symbol: None for symbol in symbols}
|
|
print_lock = asyncio.Lock()
|
|
|
|
def get_funding_style(yearly_rate):
|
|
"""Return style based on funding rate"""
|
|
if yearly_rate > 50:
|
|
return "bold white on red"
|
|
elif yearly_rate > 30:
|
|
return "bold black on yellow"
|
|
elif yearly_rate > 5:
|
|
return "bold white on blue"
|
|
elif yearly_rate < -10:
|
|
return "bold white on green"
|
|
else:
|
|
return "bold black on bright_green"
|
|
|
|
def create_funding_table():
|
|
"""Create a rich table with current funding data"""
|
|
table = Table(
|
|
title="🚀 Binance Perpetual Funding Rates",
|
|
title_style="bold cyan",
|
|
box=box.ROUNDED,
|
|
show_header=True,
|
|
header_style="bold magenta"
|
|
)
|
|
|
|
table.add_column("Symbol", style="cyan", width=12)
|
|
table.add_column("Funding Rate", justify="right", width=15)
|
|
table.add_column("Yearly Rate", justify="right", width=15)
|
|
table.add_column("Status", justify="center", width=20)
|
|
table.add_column("Last Update", style="dim", width=12)
|
|
|
|
for symbol in symbols:
|
|
data = funding_data[symbol]
|
|
if data:
|
|
symbol_display = data['symbol_display']
|
|
funding_rate = data['funding_rate']
|
|
yearly_rate = data['yearly_rate']
|
|
last_update = data['last_update']
|
|
|
|
# Create status indicator
|
|
if yearly_rate > 30:
|
|
status = Text("🔥 HIGH", style="bold red")
|
|
elif yearly_rate > 5:
|
|
status = Text("📈 POSITIVE", style="bold yellow")
|
|
elif yearly_rate < -10:
|
|
status = Text("📉 NEGATIVE", style="bold green")
|
|
else:
|
|
status = Text("😐 NEUTRAL", style="dim")
|
|
|
|
# Style the rates
|
|
funding_text = Text(f"{funding_rate:.4f}%", style=get_funding_style(yearly_rate))
|
|
yearly_text = Text(f"{yearly_rate:+.2f}%", style=get_funding_style(yearly_rate))
|
|
|
|
table.add_row(
|
|
symbol_display,
|
|
funding_text,
|
|
yearly_text,
|
|
status,
|
|
last_update
|
|
)
|
|
else:
|
|
table.add_row(
|
|
symbol.replace('usdt', '').upper(),
|
|
Text("Loading...", style="dim"),
|
|
Text("Loading...", style="dim"),
|
|
Text("⏳ WAITING", style="dim"),
|
|
Text("--:--:--", style="dim")
|
|
)
|
|
|
|
return table
|
|
|
|
def create_layout():
|
|
"""Create the main layout"""
|
|
layout = Layout()
|
|
|
|
# Header
|
|
header = Panel(
|
|
Align.center(
|
|
Text("Binance Funding Rate Monitor", style="bold white"),
|
|
vertical="middle"
|
|
),
|
|
height=3,
|
|
style="blue"
|
|
)
|
|
|
|
# Main table
|
|
table = create_funding_table()
|
|
|
|
# Footer with info
|
|
footer_text = Text()
|
|
footer_text.append("🟥 >50% ", style="bold white on red")
|
|
footer_text.append("🟨 >30% ", style="bold black on yellow")
|
|
footer_text.append("🟦 >5% ", style="bold white on blue")
|
|
footer_text.append("🟩 <-10% ", style="bold white on green")
|
|
footer_text.append("💚 Normal", style="bold black on bright_green")
|
|
|
|
footer = Panel(
|
|
Align.center(footer_text),
|
|
title="Legend",
|
|
height=3,
|
|
style="dim"
|
|
)
|
|
|
|
layout.split_column(
|
|
Layout(header, name="header", size=3),
|
|
Layout(table, name="main"),
|
|
Layout(footer, name="footer", size=3)
|
|
)
|
|
|
|
return layout
|
|
|
|
|
|
async def binance_funding_stream(symbol):
|
|
"""Connect to Binance WebSocket and stream funding data"""
|
|
global funding_data, print_lock
|
|
|
|
websocket_url = f'{websocket_url_base}{symbol}@markPrice'
|
|
|
|
while True: # Reconnection loop
|
|
try:
|
|
async with connect(websocket_url) as websocket:
|
|
while True:
|
|
try:
|
|
message = await websocket.recv()
|
|
data = json.loads(message)
|
|
|
|
event_time = datetime.fromtimestamp(data['E'] / 1000).strftime("%H:%M:%S")
|
|
symbol_display = data['s'].replace('USDT', '').upper()
|
|
funding_rate = float(data['r']) * 100 # Convert to percentage
|
|
yearly_funding_rate = funding_rate * 3 * 365 # 3 times per day, 365 days
|
|
|
|
async with print_lock:
|
|
funding_data[symbol] = {
|
|
'symbol_display': symbol_display,
|
|
'funding_rate': funding_rate,
|
|
'yearly_rate': yearly_funding_rate,
|
|
'last_update': event_time
|
|
}
|
|
|
|
except json.JSONDecodeError:
|
|
continue
|
|
except Exception as e:
|
|
console.print(f"[red]Error processing data for {symbol}: {e}[/red]")
|
|
break
|
|
|
|
except Exception as e:
|
|
console.print(f"[red]Connection error for {symbol}: {e}. Retrying in 5 seconds...[/red]")
|
|
await asyncio.sleep(5)
|
|
|
|
|
|
async def display_loop():
|
|
"""Main display loop using Rich Live"""
|
|
with Live(create_layout(), refresh_per_second=2, screen=True) as live:
|
|
while True:
|
|
live.update(create_layout())
|
|
await asyncio.sleep(0.5)
|
|
|
|
async def main():
|
|
"""Main function to run all tasks"""
|
|
console.print("[bold green]🚀 Starting Binance Funding Rate Monitor...[/bold green]")
|
|
console.print("[yellow]Press Ctrl+C to exit[/yellow]\n")
|
|
|
|
# Start WebSocket streams
|
|
stream_tasks = [binance_funding_stream(symbol) for symbol in symbols]
|
|
|
|
# Start display
|
|
display_task = display_loop()
|
|
|
|
# Run all tasks
|
|
try:
|
|
await asyncio.gather(*stream_tasks, display_task)
|
|
except KeyboardInterrupt:
|
|
console.print("\n[bold red]Shutting down...[/bold red]")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
asyncio.run(main()) |