Add whale activity plotting and alert processing to ChartView
- Introduced a new subplot for whale activity in ChartView (WIP) - Implemented alert queue processing to handle whale alerts. - Updated WhalesWatcher to support alert queue integration.
This commit is contained in:
parent
389cd7c919
commit
fcc0342fd8
165
ChartView.py
165
ChartView.py
@ -8,9 +8,11 @@ from matplotlib.dates import date2num
|
||||
import threading
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
import queue
|
||||
from datetime import datetime
|
||||
|
||||
class ChartView:
|
||||
def __init__(self, exchange_ids, symbol='BTC/USDT', timeframe='1m', limit=100):
|
||||
def __init__(self, exchange_ids, symbol='BTC/USDT', timeframe='1m', limit=100, alert_queue=None):
|
||||
self.exchange_ids = exchange_ids
|
||||
self.symbol = symbol
|
||||
self.timeframe = timeframe
|
||||
@ -19,10 +21,16 @@ class ChartView:
|
||||
self.fig = None
|
||||
self.ax1 = None
|
||||
self.ax2 = None
|
||||
self.ax_whales = None # New axis for whale activity
|
||||
self.animation = None
|
||||
self.current_exchange_id = exchange_ids[0]
|
||||
self.root = None
|
||||
self.canvas = None
|
||||
self.alert_queue = alert_queue
|
||||
self.whale_markers = []
|
||||
|
||||
# Maintain a dict of whale alerts by exchange
|
||||
self.whale_alerts = {exchange_id: [] for exchange_id in exchange_ids}
|
||||
|
||||
# Create exchanges dictionary to avoid reconnecting each time
|
||||
self.exchanges = {}
|
||||
@ -69,7 +77,12 @@ class ChartView:
|
||||
if df is not None:
|
||||
self.ax1.clear()
|
||||
self.ax2.clear()
|
||||
self.ax_whales.clear()
|
||||
|
||||
# Process any pending whale alerts from the queue
|
||||
self.process_whale_queue()
|
||||
|
||||
# Plot the chart
|
||||
mpf.plot(df, type='candle', ax=self.ax1, volume=self.ax2,
|
||||
style='yahoo', xrotation=0, ylabel='Price',
|
||||
ylabel_lower='Volume', show_nontrading=False)
|
||||
@ -77,9 +90,149 @@ class ChartView:
|
||||
latest_price = df['close'].iloc[-1]
|
||||
self.ax1.set_title(f'{self.symbol} on {self.current_exchange_id} - Last: ${latest_price:.2f}')
|
||||
|
||||
# Plot whale activity on the dedicated subplot
|
||||
self.plot_whale_activity(df)
|
||||
|
||||
# Refresh the canvas
|
||||
self.canvas.draw()
|
||||
|
||||
def process_whale_queue(self):
|
||||
"""Process any new whale alerts from the queue"""
|
||||
if self.alert_queue is None:
|
||||
return
|
||||
|
||||
# Process all available alerts
|
||||
while not self.alert_queue.empty():
|
||||
try:
|
||||
alert = self.alert_queue.get_nowait()
|
||||
exchange_id = alert['exchange']
|
||||
|
||||
# Add timestamp if not present
|
||||
if 'timestamp' not in alert:
|
||||
alert['timestamp'] = datetime.now().isoformat()
|
||||
|
||||
# Store the alert with the appropriate exchange
|
||||
if exchange_id in self.whale_alerts:
|
||||
self.whale_alerts[exchange_id].append(alert)
|
||||
print(f"New whale alert for {exchange_id}: {alert['type']} ${alert['value_usd']:,.2f}")
|
||||
|
||||
self.alert_queue.task_done()
|
||||
except queue.Empty:
|
||||
break
|
||||
except Exception as e:
|
||||
print(f"Error processing whale alert from queue: {str(e)}")
|
||||
if self.alert_queue is not None:
|
||||
self.alert_queue.task_done()
|
||||
|
||||
def plot_whale_activity(self, df):
|
||||
"""Plot whale activity on the dedicated whale subplot"""
|
||||
if self.current_exchange_id not in self.whale_alerts:
|
||||
self.ax_whales.set_ylabel('Whale Activity')
|
||||
return
|
||||
|
||||
# Get the whale alerts for current exchange
|
||||
exchange_alerts = self.whale_alerts[self.current_exchange_id]
|
||||
|
||||
if not exchange_alerts:
|
||||
self.ax_whales.set_ylabel('Whale Activity')
|
||||
return
|
||||
|
||||
try:
|
||||
# Create a dataframe from the whale alerts
|
||||
alerts_df = pd.DataFrame(exchange_alerts)
|
||||
|
||||
# Handle the timestamp conversion carefully
|
||||
alerts_df['timestamp'] = pd.to_datetime(alerts_df['timestamp'])
|
||||
|
||||
# Check if the dataframe timestamps have timezone info
|
||||
has_tz = False
|
||||
if not alerts_df.empty:
|
||||
has_tz = alerts_df['timestamp'].iloc[0].tzinfo is not None
|
||||
|
||||
# Get the start and end time of the current chart
|
||||
start_time = df.index[0]
|
||||
end_time = df.index[-1]
|
||||
|
||||
# Convert all timestamps to naive (remove timezone info) for comparison
|
||||
# First create a copy of the timestamp column
|
||||
alerts_df['plot_timestamp'] = alerts_df['timestamp']
|
||||
|
||||
# If timestamps have timezone, convert them to naive by replacing with their UTC equivalent
|
||||
if has_tz:
|
||||
alerts_df['plot_timestamp'] = alerts_df['timestamp'].dt.tz_localize(None)
|
||||
else:
|
||||
# If timestamps are naive, assume they're in local time and adjust by GMT+8 offset
|
||||
alerts_df['plot_timestamp'] = alerts_df['timestamp'] - pd.Timedelta(hours=8)
|
||||
|
||||
# Filter to only include alerts in the visible time range
|
||||
visible_alerts = alerts_df[
|
||||
(alerts_df['plot_timestamp'] >= start_time) &
|
||||
(alerts_df['plot_timestamp'] <= end_time)
|
||||
]
|
||||
|
||||
if visible_alerts.empty:
|
||||
self.ax_whales.set_ylabel('Whale Activity')
|
||||
return
|
||||
|
||||
# Create two separate series for buy and sell orders
|
||||
buy_orders = visible_alerts[visible_alerts['type'] == 'bid'].copy()
|
||||
sell_orders = visible_alerts[visible_alerts['type'] == 'ask'].copy()
|
||||
|
||||
# Draw the buy orders as green bars going up
|
||||
if not buy_orders.empty:
|
||||
buy_values = buy_orders['value_usd'] / 1_000_000 # Convert to millions
|
||||
self.ax_whales.bar(buy_orders['plot_timestamp'], buy_values,
|
||||
color='green', alpha=0.6, width=pd.Timedelta(minutes=1))
|
||||
|
||||
# Draw the sell orders as red bars going down
|
||||
if not sell_orders.empty:
|
||||
sell_values = -1 * sell_orders['value_usd'] / 1_000_000 # Convert to millions and make negative
|
||||
self.ax_whales.bar(sell_orders['plot_timestamp'], sell_values,
|
||||
color='red', alpha=0.6, width=pd.Timedelta(minutes=1))
|
||||
|
||||
# Format the whale activity subplot
|
||||
self.ax_whales.set_ylabel('Whale Activity ($M)')
|
||||
|
||||
# Add zero line
|
||||
self.ax_whales.axhline(y=0, color='black', linestyle='-', alpha=0.3)
|
||||
|
||||
# Set y-axis limits with some padding
|
||||
all_values = visible_alerts['value_usd'] / 1_000_000
|
||||
if len(all_values) > 0:
|
||||
max_val = all_values.max() * 1.1
|
||||
self.ax_whales.set_ylim(-max_val, max_val)
|
||||
|
||||
# Align the x-axis with the price chart
|
||||
self.ax_whales.sharex(self.ax1)
|
||||
|
||||
# Add text labels for significant whale activity
|
||||
for idx, row in visible_alerts.iterrows():
|
||||
value_millions = row['value_usd'] / 1_000_000
|
||||
sign = 1 if row['type'] == 'bid' else -1
|
||||
position = sign * value_millions
|
||||
if abs(value_millions) > max(all_values) * 0.3: # Only label significant activity
|
||||
self.ax_whales.text(
|
||||
row['plot_timestamp'], position * 1.05,
|
||||
f"${abs(value_millions):.1f}M",
|
||||
ha='center', va='bottom' if sign > 0 else 'top',
|
||||
fontsize=8, color='green' if sign > 0 else 'red'
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"Error plotting whale activity: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
self.ax_whales.set_ylabel('Whale Activity - Error plotting data')
|
||||
|
||||
def parse_timeframe_to_minutes(self, timeframe):
|
||||
"""Convert timeframe string to minutes"""
|
||||
if timeframe.endswith('m'):
|
||||
return int(timeframe[:-1])
|
||||
elif timeframe.endswith('h'):
|
||||
return int(timeframe[:-1]) * 60
|
||||
elif timeframe.endswith('d'):
|
||||
return int(timeframe[:-1]) * 1440
|
||||
return 1 # default to 1 minute
|
||||
|
||||
def on_exchange_change(self, event=None):
|
||||
selected_exchange = self.exchange_var.get()
|
||||
if selected_exchange != self.current_exchange_id:
|
||||
@ -102,10 +255,14 @@ class ChartView:
|
||||
exchange_dropdown.pack(side=tk.LEFT, padx=(0, 10))
|
||||
exchange_dropdown.bind("<<ComboboxSelected>>", self.on_exchange_change)
|
||||
|
||||
# Create the figure
|
||||
# Create the figure with three subplots
|
||||
self.fig = plt.Figure(figsize=(12, 8), dpi=100)
|
||||
self.ax1 = self.fig.add_subplot(6, 1, (1, 4))
|
||||
self.ax2 = self.fig.add_subplot(6, 1, (5, 6), sharex=self.ax1)
|
||||
|
||||
# Adjust the subplot grid to add whale activity panel
|
||||
# Price chart (60%), Volume (20%), Whale Activity (20%)
|
||||
self.ax1 = self.fig.add_subplot(5, 1, (1, 3)) # Price chart - 3/5 of the space
|
||||
self.ax2 = self.fig.add_subplot(5, 1, 4, sharex=self.ax1) # Volume - 1/5 of the space
|
||||
self.ax_whales = self.fig.add_subplot(5, 1, 5, sharex=self.ax1) # Whale activity - 1/5 of the space
|
||||
|
||||
# Create the canvas to display the figure
|
||||
self.canvas = FigureCanvasTkAgg(self.fig, master=self.root)
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import pandas as pd
|
||||
import time
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timezone
|
||||
import logging
|
||||
import ccxt
|
||||
from sqlalchemy import create_engine, Column, Integer, String, Float, DateTime
|
||||
@ -21,12 +21,12 @@ class Alert(Base):
|
||||
timestamp = Column(DateTime)
|
||||
|
||||
class WhalesWatcher:
|
||||
def __init__(self):
|
||||
def __init__(self, alert_queue=None):
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
filename='logs/whales_watcher.log',
|
||||
filemode='a' # Append mode
|
||||
filemode='a'
|
||||
)
|
||||
|
||||
console_handler = logging.StreamHandler()
|
||||
@ -38,10 +38,11 @@ class WhalesWatcher:
|
||||
|
||||
self.exchanges = {}
|
||||
self.symbol = 'BTC/USDT'
|
||||
self.threshold = 1000000
|
||||
self.threshold = 100000
|
||||
self.check_interval = 5
|
||||
self.large_orders = []
|
||||
self.is_running = False
|
||||
self.alert_queue = alert_queue
|
||||
|
||||
self.logger.info("WhalesWatcher initialized with symbol: %s and threshold: $%d", self.symbol, self.threshold)
|
||||
|
||||
@ -88,6 +89,9 @@ class WhalesWatcher:
|
||||
|
||||
whale_orders = []
|
||||
|
||||
# Get current time in UTC
|
||||
current_time_utc = datetime.now().astimezone().astimezone(timezone.utc)
|
||||
|
||||
for bid in order_book['bids']:
|
||||
if (len(bid) == 3):
|
||||
price, amount, timestamp = bid
|
||||
@ -100,7 +104,7 @@ class WhalesWatcher:
|
||||
'price': price,
|
||||
'amount': amount,
|
||||
'value_usd': value,
|
||||
'timestamp': datetime.now().isoformat()
|
||||
'timestamp': current_time_utc.isoformat()
|
||||
})
|
||||
|
||||
for ask in order_book['asks']:
|
||||
@ -115,7 +119,7 @@ class WhalesWatcher:
|
||||
'price': price,
|
||||
'amount': amount,
|
||||
'value_usd': value,
|
||||
'timestamp': datetime.now().isoformat()
|
||||
'timestamp': current_time_utc.isoformat()
|
||||
})
|
||||
|
||||
return whale_orders
|
||||
@ -154,7 +158,7 @@ class WhalesWatcher:
|
||||
self.logger.info(f"Saved whale activity data to {filename}")
|
||||
|
||||
def log_alert(self, order):
|
||||
"""Log the alert to the database."""
|
||||
"""Log the alert to the database and alert queue."""
|
||||
session = self.Session()
|
||||
alert = Alert(
|
||||
exchange=order['exchange'],
|
||||
@ -167,6 +171,10 @@ class WhalesWatcher:
|
||||
session.add(alert)
|
||||
session.commit()
|
||||
session.close()
|
||||
|
||||
# Add to queue if available
|
||||
if self.alert_queue is not None:
|
||||
self.alert_queue.put(order)
|
||||
|
||||
def run(self):
|
||||
"""Run the whale watcher continuously."""
|
||||
|
||||
@ -1,23 +1,27 @@
|
||||
from WhalesWatcher import WhalesWatcher
|
||||
from ChartView import ChartView
|
||||
import threading
|
||||
import queue
|
||||
|
||||
def start_whales_watcher():
|
||||
whales_watcher = WhalesWatcher()
|
||||
def start_whales_watcher(alert_queue):
|
||||
whales_watcher = WhalesWatcher(alert_queue)
|
||||
whales_watcher.run()
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Create a queue for passing whale alerts between threads
|
||||
whale_alert_queue = queue.Queue()
|
||||
|
||||
# Start WhalesWatcher in a separate thread
|
||||
# watcher_thread = threading.Thread(target=start_whales_watcher)
|
||||
# watcher_thread.daemon = True
|
||||
watcher_thread = threading.Thread(target=start_whales_watcher, args=(whale_alert_queue,))
|
||||
watcher_thread.daemon = True
|
||||
# Start the watcher thread
|
||||
# watcher_thread.start()
|
||||
watcher_thread.start()
|
||||
|
||||
# Define exchanges for the chart
|
||||
exchange_ids = ['binance', 'kraken', 'coinbase'] # Add more exchanges as needed
|
||||
exchange_ids = ['binance', 'kraken', 'coinbase', 'bitfinex', 'huobi', 'kucoin', 'bybit', 'okx', 'gateio', 'mexc']
|
||||
|
||||
# Start the chart viewer
|
||||
chart_view = ChartView(exchange_ids)
|
||||
# Start the chart viewer with alert queue
|
||||
chart_view = ChartView(exchange_ids, alert_queue=whale_alert_queue)
|
||||
|
||||
# Run the GUI (this will block until the window is closed)
|
||||
chart_view.start_gui()
|
||||
Loading…
x
Reference in New Issue
Block a user