200 lines
7.0 KiB
Python
200 lines
7.0 KiB
Python
|
|
"""
|
||
|
|
Modular Chart System for Crypto Trading Bot Dashboard
|
||
|
|
|
||
|
|
This package provides a flexible, strategy-driven chart system that supports:
|
||
|
|
- Technical indicator overlays (SMA, EMA, Bollinger Bands)
|
||
|
|
- Subplot management (RSI, MACD)
|
||
|
|
- Strategy-specific configurations
|
||
|
|
- Future bot signal integration
|
||
|
|
|
||
|
|
Main Components:
|
||
|
|
- ChartBuilder: Main orchestrator for chart creation
|
||
|
|
- Layer System: Modular rendering components
|
||
|
|
- Configuration System: Strategy-driven chart configs
|
||
|
|
"""
|
||
|
|
|
||
|
|
from .builder import ChartBuilder
|
||
|
|
from .utils import (
|
||
|
|
validate_market_data,
|
||
|
|
prepare_chart_data,
|
||
|
|
get_indicator_colors
|
||
|
|
)
|
||
|
|
|
||
|
|
# Version information
|
||
|
|
__version__ = "0.1.0"
|
||
|
|
__package_name__ = "charts"
|
||
|
|
|
||
|
|
# Public API exports
|
||
|
|
__all__ = [
|
||
|
|
"ChartBuilder",
|
||
|
|
"validate_market_data",
|
||
|
|
"prepare_chart_data",
|
||
|
|
"get_indicator_colors",
|
||
|
|
"create_candlestick_chart",
|
||
|
|
"create_strategy_chart",
|
||
|
|
"get_supported_symbols",
|
||
|
|
"get_supported_timeframes",
|
||
|
|
"get_market_statistics",
|
||
|
|
"check_data_availability",
|
||
|
|
"create_data_status_indicator",
|
||
|
|
"create_error_chart"
|
||
|
|
]
|
||
|
|
|
||
|
|
def create_candlestick_chart(symbol: str, timeframe: str, days_back: int = 7, **kwargs):
|
||
|
|
"""
|
||
|
|
Convenience function to create a basic candlestick chart.
|
||
|
|
|
||
|
|
Args:
|
||
|
|
symbol: Trading pair (e.g., 'BTC-USDT')
|
||
|
|
timeframe: Timeframe (e.g., '1h', '1d')
|
||
|
|
days_back: Number of days to look back
|
||
|
|
**kwargs: Additional parameters for chart customization
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
Plotly Figure object
|
||
|
|
"""
|
||
|
|
builder = ChartBuilder()
|
||
|
|
return builder.create_candlestick_chart(symbol, timeframe, days_back, **kwargs)
|
||
|
|
|
||
|
|
def create_strategy_chart(symbol: str, timeframe: str, strategy_name: str, **kwargs):
|
||
|
|
"""
|
||
|
|
Convenience function to create a strategy-specific chart.
|
||
|
|
|
||
|
|
Args:
|
||
|
|
symbol: Trading pair
|
||
|
|
timeframe: Timeframe
|
||
|
|
strategy_name: Name of the strategy configuration
|
||
|
|
**kwargs: Additional parameters
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
Plotly Figure object with strategy indicators
|
||
|
|
"""
|
||
|
|
builder = ChartBuilder()
|
||
|
|
return builder.create_strategy_chart(symbol, timeframe, strategy_name, **kwargs)
|
||
|
|
|
||
|
|
def get_supported_symbols():
|
||
|
|
"""Get list of symbols that have data in the database."""
|
||
|
|
builder = ChartBuilder()
|
||
|
|
candles = builder.fetch_market_data("BTC-USDT", "1m", days_back=1) # Test query
|
||
|
|
if candles:
|
||
|
|
from database.operations import get_database_operations
|
||
|
|
from utils.logger import get_logger
|
||
|
|
logger = get_logger("charts_symbols")
|
||
|
|
|
||
|
|
try:
|
||
|
|
db = get_database_operations(logger)
|
||
|
|
with db.market_data.get_session() as session:
|
||
|
|
from sqlalchemy import text
|
||
|
|
result = session.execute(text("SELECT DISTINCT symbol FROM market_data ORDER BY symbol"))
|
||
|
|
return [row[0] for row in result]
|
||
|
|
except Exception:
|
||
|
|
pass
|
||
|
|
|
||
|
|
return ['BTC-USDT', 'ETH-USDT'] # Fallback
|
||
|
|
|
||
|
|
def get_supported_timeframes():
|
||
|
|
"""Get list of timeframes that have data in the database."""
|
||
|
|
builder = ChartBuilder()
|
||
|
|
candles = builder.fetch_market_data("BTC-USDT", "1m", days_back=1) # Test query
|
||
|
|
if candles:
|
||
|
|
from database.operations import get_database_operations
|
||
|
|
from utils.logger import get_logger
|
||
|
|
logger = get_logger("charts_timeframes")
|
||
|
|
|
||
|
|
try:
|
||
|
|
db = get_database_operations(logger)
|
||
|
|
with db.market_data.get_session() as session:
|
||
|
|
from sqlalchemy import text
|
||
|
|
result = session.execute(text("SELECT DISTINCT timeframe FROM market_data ORDER BY timeframe"))
|
||
|
|
return [row[0] for row in result]
|
||
|
|
except Exception:
|
||
|
|
pass
|
||
|
|
|
||
|
|
return ['5s', '1m', '15m', '1h'] # Fallback
|
||
|
|
|
||
|
|
def get_market_statistics(symbol: str, timeframe: str = "1h"):
|
||
|
|
"""Calculate market statistics from recent data."""
|
||
|
|
builder = ChartBuilder()
|
||
|
|
candles = builder.fetch_market_data(symbol, timeframe, days_back=1)
|
||
|
|
|
||
|
|
if not candles:
|
||
|
|
return {'Price': 'N/A', '24h Change': 'N/A', '24h Volume': 'N/A', 'High 24h': 'N/A', 'Low 24h': 'N/A'}
|
||
|
|
|
||
|
|
import pandas as pd
|
||
|
|
df = pd.DataFrame(candles)
|
||
|
|
latest = df.iloc[-1]
|
||
|
|
current_price = float(latest['close'])
|
||
|
|
|
||
|
|
# Calculate 24h change
|
||
|
|
if len(df) > 1:
|
||
|
|
price_24h_ago = float(df.iloc[0]['open'])
|
||
|
|
change_percent = ((current_price - price_24h_ago) / price_24h_ago) * 100
|
||
|
|
else:
|
||
|
|
change_percent = 0
|
||
|
|
|
||
|
|
from .utils import format_price, format_volume
|
||
|
|
return {
|
||
|
|
'Price': format_price(current_price, decimals=2),
|
||
|
|
'24h Change': f"{'+' if change_percent >= 0 else ''}{change_percent:.2f}%",
|
||
|
|
'24h Volume': format_volume(df['volume'].sum()),
|
||
|
|
'High 24h': format_price(df['high'].max(), decimals=2),
|
||
|
|
'Low 24h': format_price(df['low'].min(), decimals=2)
|
||
|
|
}
|
||
|
|
|
||
|
|
def check_data_availability(symbol: str, timeframe: str):
|
||
|
|
"""Check data availability for a symbol and timeframe."""
|
||
|
|
from datetime import datetime, timezone, timedelta
|
||
|
|
from database.operations import get_database_operations
|
||
|
|
from utils.logger import get_logger
|
||
|
|
|
||
|
|
try:
|
||
|
|
logger = get_logger("charts_data_check")
|
||
|
|
db = get_database_operations(logger)
|
||
|
|
latest_candle = db.market_data.get_latest_candle(symbol, timeframe)
|
||
|
|
|
||
|
|
if latest_candle:
|
||
|
|
latest_time = latest_candle['timestamp']
|
||
|
|
time_diff = datetime.now(timezone.utc) - latest_time.replace(tzinfo=timezone.utc)
|
||
|
|
|
||
|
|
return {
|
||
|
|
'has_data': True,
|
||
|
|
'latest_timestamp': latest_time,
|
||
|
|
'time_since_last': time_diff,
|
||
|
|
'is_recent': time_diff < timedelta(hours=1),
|
||
|
|
'message': f"Latest data: {latest_time.strftime('%Y-%m-%d %H:%M:%S UTC')}"
|
||
|
|
}
|
||
|
|
else:
|
||
|
|
return {
|
||
|
|
'has_data': False,
|
||
|
|
'latest_timestamp': None,
|
||
|
|
'time_since_last': None,
|
||
|
|
'is_recent': False,
|
||
|
|
'message': f"No data available for {symbol} {timeframe}"
|
||
|
|
}
|
||
|
|
except Exception as e:
|
||
|
|
return {
|
||
|
|
'has_data': False,
|
||
|
|
'latest_timestamp': None,
|
||
|
|
'time_since_last': None,
|
||
|
|
'is_recent': False,
|
||
|
|
'message': f"Error checking data: {str(e)}"
|
||
|
|
}
|
||
|
|
|
||
|
|
def create_data_status_indicator(symbol: str, timeframe: str):
|
||
|
|
"""Create a data status indicator for the dashboard."""
|
||
|
|
status = check_data_availability(symbol, timeframe)
|
||
|
|
|
||
|
|
if status['has_data']:
|
||
|
|
if status['is_recent']:
|
||
|
|
icon, color, status_text = "🟢", "#27ae60", "Real-time Data"
|
||
|
|
else:
|
||
|
|
icon, color, status_text = "🟡", "#f39c12", "Delayed Data"
|
||
|
|
else:
|
||
|
|
icon, color, status_text = "🔴", "#e74c3c", "No Data"
|
||
|
|
|
||
|
|
return f'<span style="color: {color}; font-weight: bold;">{icon} {status_text}</span><br><small>{status["message"]}</small>'
|
||
|
|
|
||
|
|
def create_error_chart(error_message: str):
|
||
|
|
"""Create an error chart with error message."""
|
||
|
|
builder = ChartBuilder()
|
||
|
|
return builder._create_error_chart(error_message)
|