"""Chart plotting utilities for creating standard, beautiful OHLC charts.""" import pandas as pd import matplotlib.pyplot as plt from typing import Optional, Tuple import logging logger = logging.getLogger(__name__) def plot_ohlc( df: pd.DataFrame, title: Optional[str] = None, volume: bool = True, figsize: Tuple[int, int] = (14, 8), style: str = 'seaborn-v0_8-darkgrid', **kwargs ) -> plt.Figure: """Create a beautiful standard OHLC candlestick chart. This is a convenience function that generates a professional-looking candlestick chart with consistent styling across all generated charts. It uses mplfinance with seaborn aesthetics for a polished appearance. Args: df: pandas DataFrame with DatetimeIndex and columns: open, high, low, close, volume title: Optional chart title. If None, uses symbol from chart context volume: Whether to include volume subplot (default: True) figsize: Figure size as (width, height) in inches (default: (14, 8)) style: Base matplotlib style to use (default: 'seaborn-v0_8-darkgrid') **kwargs: Additional arguments to pass to mplfinance.plot() Returns: matplotlib.figure.Figure: The created figure object Example: ```python # Basic usage in analyze_chart_data script fig = plot_ohlc(df, title='BTC/USDT 15min') # Customize with additional indicators fig = plot_ohlc(df, volume=True, title='Price Action') # Add custom overlays after calling plot_ohlc df['SMA20'] = df['close'].rolling(20).mean() fig = plot_ohlc(df, title='With SMA') # Note: For mplfinance overlays, use the mav or addplot parameters ``` Note: The DataFrame must have a DatetimeIndex and the standard OHLCV columns. Column names should be lowercase: open, high, low, close, volume """ try: import mplfinance as mpf except ImportError: raise ImportError( "mplfinance is required for plot_ohlc(). " "Install it with: pip install mplfinance" ) # Validate DataFrame structure required_cols = ['open', 'high', 'low', 'close'] missing_cols = [col for col in required_cols if col not in df.columns] if missing_cols: raise ValueError( f"DataFrame missing required columns: {missing_cols}. " f"Required: {required_cols}" ) if not isinstance(df.index, pd.DatetimeIndex): raise ValueError( "DataFrame must have a DatetimeIndex. " "Convert with: df.index = pd.to_datetime(df.index)" ) # Ensure volume column exists for volume plot if volume and 'volume' not in df.columns: logger.warning("volume=True but 'volume' column not found in DataFrame. Disabling volume.") volume = False # Create custom style with seaborn aesthetics # Using a professional color scheme: green for up candles, red for down candles mc = mpf.make_marketcolors( up='#26a69a', # Teal green (calmer than bright green) down='#ef5350', # Coral red (softer than pure red) edge='inherit', # Match candle color for edges wick='inherit', # Match candle color for wicks volume='in', # Volume bars colored by price direction alpha=0.9 # Slight transparency for elegance ) s = mpf.make_mpf_style( base_mpf_style='charles', # Clean base style marketcolors=mc, rc={ 'font.size': 10, 'axes.labelsize': 11, 'axes.titlesize': 12, 'xtick.labelsize': 9, 'ytick.labelsize': 9, 'legend.fontsize': 10, 'figure.facecolor': '#f0f0f0', 'axes.facecolor': '#ffffff', 'axes.grid': True, 'grid.alpha': 0.3, 'grid.linestyle': '--', } ) # Prepare plot parameters plot_params = { 'type': 'candle', 'style': s, 'volume': volume, 'figsize': figsize, 'tight_layout': True, 'returnfig': True, 'warn_too_much_data': 1000, # Warn if > 1000 candles for performance } # Add title if provided if title: plot_params['title'] = title # Merge any additional kwargs plot_params.update(kwargs) # Create the plot logger.info( f"Creating OHLC chart with {len(df)} candles, " f"date range: {df.index.min()} to {df.index.max()}, " f"volume: {volume}" ) fig, axes = mpf.plot(df, **plot_params) return fig def add_indicators_to_plot( df: pd.DataFrame, indicators: dict, **plot_kwargs ) -> plt.Figure: """Create an OHLC chart with technical indicators overlaid. This extends plot_ohlc() to include common technical indicators using mplfinance's addplot functionality for proper overlay on candlestick charts. Args: df: pandas DataFrame with OHLCV data and indicator columns indicators: Dictionary mapping indicator names to parameters Example: { 'SMA_20': {'color': 'blue', 'width': 1.5}, 'EMA_50': {'color': 'orange', 'width': 1.5} } **plot_kwargs: Additional arguments for plot_ohlc() Returns: matplotlib.figure.Figure: The created figure object Example: ```python # Calculate indicators df['SMA_20'] = df['close'].rolling(20).mean() df['SMA_50'] = df['close'].rolling(50).mean() # Plot with indicators fig = add_indicators_to_plot( df, indicators={ 'SMA_20': {'color': 'blue', 'width': 1.5, 'label': '20 SMA'}, 'SMA_50': {'color': 'red', 'width': 1.5, 'label': '50 SMA'} }, title='BTC/USDT with Moving Averages' ) ``` """ try: import mplfinance as mpf except ImportError: raise ImportError( "mplfinance is required. Install it with: pip install mplfinance" ) # Build addplot list for indicators addplots = [] for indicator_col, params in indicators.items(): if indicator_col not in df.columns: logger.warning(f"Indicator column '{indicator_col}' not found in DataFrame. Skipping.") continue color = params.get('color', 'blue') width = params.get('width', 1.0) panel = params.get('panel', 0) # 0 = main panel with candles ylabel = params.get('ylabel', '') addplots.append( mpf.make_addplot( df[indicator_col], color=color, width=width, panel=panel, ylabel=ylabel ) ) # Pass addplot to plot_ohlc via kwargs if addplots: plot_kwargs['addplot'] = addplots return plot_ohlc(df, **plot_kwargs) # Convenience presets for common chart types def plot_price_volume(df: pd.DataFrame, title: Optional[str] = None) -> plt.Figure: """Create a standard price + volume chart.""" return plot_ohlc(df, title=title, volume=True, figsize=(14, 8)) def plot_price_only(df: pd.DataFrame, title: Optional[str] = None) -> plt.Figure: """Create a price-only candlestick chart without volume.""" return plot_ohlc(df, title=title, volume=False, figsize=(14, 6))