20241219

Help me refactor the code provided with modules(data, analysis, visualization), just implement the functions provided. Don't add other functions not included in the files provided.

I'll help you refactor the code into a modular structure with three main modules: data, analysis, and visualization. Let me create an artifact for this refactored code.

# data.py
import yfinance as yf
import requests
import pandas as pd
from datetime import datetime
from dateutil.relativedelta import relativedelta

class DataService:
    def __init__(self, api_key="a365bff224a6419fac064dd52e1f80d9"):
        self.API_KEY = api_key
        self.BASE_URL = "https://api.roic.ai/v1/rql"
        self.METRICS = {
            "total revenues": "is_sales_and_services_revenues",
            "operating cash flow": "cf_cash_from_oper",
            "net income": "is_net_income",
            "earnings per share": "eps",
            "operating margin": "oper_margin",
            "capital expenditures": "cf_cap_expenditures",
            "return on invested capital": "return_on_inv_capital",
            "Diluted Weighted Avg Shares": "is_sh_for_diluted_eps"
        }

    def get_historical_data(self, ticker, start_date, end_date):
        """Fetch historical price data using yfinance"""
        ticker_obj = yf.Ticker(ticker)
        df = ticker_obj.history(start=start_date, end=end_date)
        if df.empty:
            raise ValueError(f"No data found for {ticker} in the specified date range")
        df.index = df.index.tz_localize(None)
        return df

    def get_financial_data(self, ticker, metric_description, start_year, end_year):
        """Fetch financial metrics data from ROIC API"""
        metric_field = self.METRICS.get(metric_description.lower())
        if not metric_field:
            print(f"Error: Unknown metric '{metric_description}'")
            return None

        query = f"get({metric_field}(fa_period_reference=range('{start_year}', '{end_year}'))) for('{ticker}')"
        url = f"{self.BASE_URL}?query={query}&apikey={self.API_KEY}"

        try:
            response = requests.get(url)
            response.raise_for_status()

            df = pd.DataFrame(response.json())
            df.columns = df.iloc[0]
            df = df.drop(0).reset_index(drop=True)

            years = df['fiscal_year'].astype(int)
            values = df[metric_field].astype(float)

            return pd.Series(values.values, index=years, name=metric_description)
        except Exception as e:
            print(f"Error fetching {metric_description}: {str(e)}")
            return None

    def get_analysis_dates(self, end_date, lookback_type, lookback_value):
        """Calculate start date based on lookback period"""
        end_dt = datetime.strptime(end_date, "%Y-%m-%d")
        if lookback_type == 'quarters':
            start_dt = end_dt - relativedelta(months=3*lookback_value)
        else:  # days
            start_dt = end_dt - relativedelta(days=lookback_value)
        return start_dt.strftime("%Y-%m-%d")

# analysis.py
import numpy as np
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
from sklearn.metrics import r2_score

class AnalysisService:
    @staticmethod
    def calculate_price_appreciation_pct(current_price, highest_price, lowest_price):
        """Calculate price appreciation percentage relative to range"""
        total_range = highest_price - lowest_price
        if total_range > 0:
            current_from_low = current_price - lowest_price
            return (current_from_low / total_range) * 100
        return 0

    @staticmethod
    def find_crossover_points(dates, series1, series2, prices):
        """Find points where two series cross each other"""
        crossover_points = []
        crossover_values = []
        crossover_directions = []
        crossover_prices = []
        
        s1 = np.array(series1)
        s2 = np.array(series2)
        
        diff = s1 - s2
        for i in range(1, len(diff)):
            if diff[i-1] <= 0 and diff[i] > 0:
                cross_value = (s1[i-1] + s2[i-1]) / 2
                crossover_points.append(dates[i])
                crossover_values.append(cross_value)
                crossover_directions.append('down')
                crossover_prices.append(prices[i])
            elif diff[i-1] >= 0 and diff[i] < 0:
                cross_value = (s1[i-1] + s2[i-1]) / 2
                crossover_points.append(dates[i])
                crossover_values.append(cross_value)
                crossover_directions.append('up')
                crossover_prices.append(prices[i])
        
        return crossover_points, crossover_values, crossover_directions, crossover_prices

    @staticmethod
    def format_regression_equation(coefficients, intercept, max_x):
        """Format regression equation string"""
        terms = []
        if coefficients[2] != 0:
            terms.append(f"{coefficients[2]:.4f}(x/{max_x})²")
        if coefficients[1] != 0:
            sign = "+" if coefficients[1] > 0 else ""
            terms.append(f"{sign}{coefficients[1]:.4f}(x/{max_x})")
        if intercept != 0:
            sign = "+" if intercept > 0 else ""
            terms.append(f"{sign}{intercept:.4f}")
        equation = "ln(y) = " + " ".join(terms)
        return equation

    @staticmethod
    def perform_polynomial_regression(data, future_days=180):
        """Perform polynomial regression analysis"""
        data['Log_Close'] = np.log(data['Close'])
        
        scale = 1
        X = (data.index - data.index[0]).days.values.reshape(-1, 1)
        y = data['Log_Close'].values
        X_scaled = X / (np.max(X) * scale)
        
        poly_features = PolynomialFeatures(degree=2)
        X_poly = poly_features.fit_transform(X_scaled)
        model = LinearRegression()
        model.fit(X_poly, y)
        
        X_future = np.arange(len(data) + future_days).reshape(-1, 1)
        X_future_scaled = X_future / np.max(X) * scale
        X_future_poly = poly_features.transform(X_future_scaled)
        y_pred_log = model.predict(X_future_poly)
        
        y_pred = np.exp(y_pred_log)
        residuals = y - model.predict(X_poly)
        std_dev = np.std(residuals)
        y_pred_upper = np.exp(y_pred_log + 2 * std_dev)
        y_pred_lower = np.exp(y_pred_log - 2 * std_dev)
        
        r2 = r2_score(y, model.predict(X_poly))
        coef = model.coef_
        intercept = model.intercept_
        max_x = np.max(X)
        
        equation = AnalysisService.format_regression_equation(coef, intercept, max_x)
        
        return {
            'predictions': y_pred,
            'upper_band': y_pred_upper,
            'lower_band': y_pred_lower,
            'r2': r2,
            'coefficients': coef,
            'intercept': intercept,
            'std_dev': std_dev,
            'equation': equation,
            'max_x': max_x
        }

    @staticmethod
    def calculate_cagr(first_value, last_value, num_years):
        """Calculate Compound Annual Growth Rate"""
        if first_value <= 0 or last_value <= 0:
            return None
        return (pow(last_value / first_value, 1 / num_years) - 1) * 100

    @staticmethod
    def calculate_growth_rates(df):
        """Calculate period-over-period growth rates"""
        growth_rates = {}
        
        for metric in df.index:
            values = df.loc[metric][:-1]
            if len(values) > 1:
                growth_rates[metric] = []
                for i in range(1, len(values)):
                    prev_val = float(values.iloc[i-1])
                    curr_val = float(values.iloc[i])
                    if prev_val and prev_val != 0:
                        growth = ((curr_val / prev_val) - 1) * 100
                        growth_rates[metric].append(growth)
                    else:
                        growth_rates[metric].append(None)
        
        return growth_rates

# visualization.py
import plotly.graph_objects as go
import pandas as pd

class VisualizationService:
    @staticmethod
    def format_number(x):
        """Format numbers with comprehensive handling"""
        if pd.isna(x) or x is None:
            return "N/A"
        try:
            if abs(x) >= 1_000_000:
                return f"-{abs(x/1_000_000):,.0f}M" if x < 0 else f"{x/1_000_000:,.0f}M"
            else:
                return f"{x:,.2f}"
        except (TypeError, ValueError):
            return "N/A"

    @staticmethod
    def format_growth_values(growth_rates):
        """Format growth rates for display"""
        if not growth_rates:
            return []
        
        metrics = list(growth_rates.keys())
        if not metrics or not growth_rates[metrics[0]]:
            return []
        
        periods = len(growth_rates[metrics[0]])
        formatted_values = [metrics]
        
        for i in range(periods):
            period_values = []
            for metric in metrics:
                value = growth_rates[metric][i]
                if value is None:
                    period_values.append("N/A")
                else:
                    period_values.append(f"{value:+.1f}%" if value != 0 else "0.0%")
            formatted_values.append(period_values)
        
        return formatted_values

    @staticmethod
    def create_financial_metrics_table(df):
        """Create financial metrics tables"""
        if df is None or df.empty:
            return None, None

        formatted_df = df.copy()
        for col in df.columns:
            if col != 'CAGR %':
                formatted_df[col] = formatted_df[col].apply(VisualizationService.format_number)
            else:
                formatted_df[col] = formatted_df[col].apply(
                    lambda x: f"{x:+.2f}" if pd.notna(x) and x is not None else "N/A"
                )

        metrics_table = go.Table(
            domain=dict(x=[0, 1], y=[0.27, 0.37]),
            header=dict(
                values=['<b>Metric</b>'] + [f'<b>{col}</b>' for col in df.columns],
                fill_color='lightgrey',
                align=['left'] + ['center'] * len(df.columns),
                font=dict(size=12)
            ),
            cells=dict(
                values=[
                    formatted_df.index.tolist(),
                    *[formatted_df[col].tolist() for col in formatted_df.columns]
                ],
                align=['left'] + ['center'] * len(df.columns),
                font=dict(size=11),
                fill_color=[
                    ['white'] * len(formatted_df),
                    *[['white'] * len(formatted_df)] * len(df.columns)
                ]
            )
        )
        
        growth_table = None
        if df is not None and not df.empty:
            df_columns = list(df.columns)
            year_columns = df_columns[1:-1]
            growth_rates = AnalysisService.calculate_growth_rates(df)
            if growth_rates:
                formatted_values = VisualizationService.format_growth_values(growth_rates)
                if formatted_values:
                    growth_table = go.Table(
                        domain=dict(x=[0, 1], y=[0.45, 0.53]),
                        header=dict(
                            values=['<b>Metric</b>'] + [f'<b>{df.columns[i]}</b>' 
                                   for i in range(1, len(df.columns)-1)],
                            fill_color='lightgrey',
                            align=['left'] + ['center'] * len(year_columns),
                            font=dict(size=12)
                        ),
                        cells=dict(
                            values=formatted_values,
                            align=['left'] + ['center'] * (len(formatted_values) - 1),
                            font=dict(size=11),
                            fill_color=[
                                ['white'] * len(formatted_values[0]),
                                *[['white'] * len(formatted_values[0])] * (len(formatted_values) - 1)
                            ]
                        )
                    )
        
        return metrics_table, growth_table

I've refactored the code into three main modules:

  1. data.py: Handles all data retrieval and processing

    • Includes the DataService class for fetching historical and financial data
    • Contains methods for date calculations and data formatting
  2. analysis.py: Contains all analytical functions

    • Includes the AnalysisService class for technical analysis
    • Contains methods for regression analysis, crossover detection, and growth calculations
  3. visualization.py: Manages all visualization-related functions

    • Includes the VisualizationService class for creating tables and formatting
    • Contains methods for number formatting and table creation

Key aspects of the refactoring:

The code can be used the same way as before, but now it's more modular and maintainable. Would you like me to explain any specific part of the refactoring or show how to use these modules together?

Q:miss chart builder in visualization.py
Let me add the chart building functionality to the VisualizationService class.

# visualization.py
import plotly.graph_objects as go
import pandas as pd
from datetime import datetime, timedelta
import numpy as np

class VisualizationService:
    # ... (previous methods remain the same) ...

    @staticmethod
    def create_stock_analysis_chart(data, analysis_dates, ratios, prices, appreciation_pcts, 
                                  regression_results, crossover_data, signal_returns, metrics_df, 
                                  total_height=1000):
        """Create the complete stock analysis chart with all components"""
        fig = go.Figure()
        
        # Add price line
        fig.add_trace(
            go.Scatter(
                x=analysis_dates,
                y=prices,
                name='Price (Log Scale)',
                line=dict(color='green', width=3),
                yaxis='y2',
                hovertemplate='<b>Date</b>: %{x}<br>' +
                             '<b>Price</b>: $%{y:.2f}<extra></extra>'
            )
        )
        
        # Add regression line and bands
        future_dates = pd.date_range(
            start=data.index[0],
            periods=len(regression_results['predictions']),
            freq='D'
        )
        
        fig.add_trace(
            go.Scatter(
                x=future_dates,
                y=regression_results['predictions'],
                name='Regression',
                line=dict(color='blue', width=2, dash='dash'),
                yaxis='y2',
                hovertemplate='<b>Date</b>: %{x}<br>' +
                             '<b>Predicted</b>: $%{y:.2f}<extra></extra>'
            )
        )
        
        fig.add_trace(
            go.Scatter(
                x=future_dates,
                y=regression_results['upper_band'],
                name='Upper Band',
                line=dict(color='lightblue', width=1),
                yaxis='y2',
                showlegend=False,
                hovertemplate='<b>Date</b>: %{x}<br>' +
                             '<b>Upper Band</b>: $%{y:.2f}<extra></extra>'
            )
        )
        
        fig.add_trace(
            go.Scatter(
                x=future_dates,
                y=regression_results['lower_band'],
                name='Lower Band',
                fill='tonexty',
                fillcolor='rgba(173, 216, 230, 0.2)',
                line=dict(color='lightblue', width=1),
                yaxis='y2',
                showlegend=False,
                hovertemplate='<b>Date</b>: %{x}<br>' +
                             '<b>Lower Band</b>: $%{y:.2f}<extra></extra>'
            )
        )
        
        # Add retracement ratio line
        fig.add_trace(
            go.Scatter(
                x=analysis_dates,
                y=ratios,
                name='Retracement Ratio',
                line=dict(color='purple', width=1),
                hovertemplate='<b>Date</b>: %{x}<br>' +
                             '<b>Ratio</b>: %{y:.1f}%<extra></extra>'
            )
        )
        
        # Add price position line
        fig.add_trace(
            go.Scatter(
                x=analysis_dates,
                y=appreciation_pcts,
                name='Price Position',
                line=dict(color='orange', width=2, dash='dot'),
                hovertemplate='<b>Date</b>: %{x}<br>' +
                             '<b>Position</b>: %{y:.1f}%<extra></extra>'
            )
        )
        
        # Add crossover points
        if crossover_data[0]:  # If there are crossover points
            dates, values, directions, prices = crossover_data
            for date, value, direction, price in zip(dates, values, directions, prices):
                color = 'green' if direction == 'up' else 'red'
                formatted_date = date.strftime('%Y-%m-%d')
                base_name = 'Bullish Crossover' if direction == 'up' else 'Bearish Crossover'
                detailed_name = f"{base_name} ({formatted_date}, ${price:.2f})"
                
                fig.add_trace(
                    go.Scatter(
                        x=[date],
                        y=[value],
                        mode='markers',
                        name=detailed_name,
                        marker=dict(
                            symbol='star',
                            size=12,
                            color=color,
                            line=dict(width=1, color=color)
                        ),
                        hovertemplate='<b>%{text}</b><br>' +
                                     '<b>Date</b>: %{x}<br>' +
                                     '<b>Value</b>: %{y:.1f}%<br>' +
                                     '<b>Price</b>: $%{customdata:.2f}<extra></extra>',
                        text=[detailed_name],
                        customdata=[price]
                    )
                )

        # Calculate metrics for annotations
        start_price = data['Close'].iloc[0]
        end_price = data['Close'].iloc[-1]
        days = (data.index[-1] - data.index[0]).days
        annual_return = ((end_price / start_price) ** (365 / days) - 1) * 100
        daily_volatility = data['Close'].pct_change().std()
        annualized_volatility = daily_volatility * np.sqrt(252)
        total_return = 0

        if signal_returns:
            returns = [s.get('Trade Return', 0) for s in signal_returns if 'Trade Return' in s]
            if returns:
                total_return = sum(returns)

        # Create Analysis Summary table
        analysis_summary = VisualizationService._create_analysis_summary_table(
            days, end_price, annual_return, daily_volatility, 
            annualized_volatility, regression_results['r2']
        )

        # Create Trading Signal Analysis table
        trading_table = VisualizationService._create_trading_signal_table(signal_returns)

        # Add financial metrics tables if available
        if metrics_df is not None:
            metrics_table, growth_table = VisualizationService.create_financial_metrics_table(metrics_df)
            if metrics_table:
                fig.add_trace(metrics_table)
            if growth_table:
                fig.add_trace(growth_table)

        # Add all tables to figure
        fig.add_trace(analysis_summary)
        fig.add_trace(trading_table)

        # Add horizontal lines at key levels
        fig.add_hline(y=0, line_dash="dash", line_color="gray", opacity=0.5)
        fig.add_hline(y=50, line_dash="dash", line_color="gray", opacity=0.5)
        fig.add_hline(y=100, line_dash="dash", line_color="gray", opacity=0.5)

        # Update layout
        fig.update_layout(
            title=dict(
                text=f'Technical Analysis ({days} Days)',
                x=0.5,
                xanchor='center',
                font=dict(size=24)
            ),
            height=total_height,
            showlegend=True,
            hovermode='x unified',
            grid=dict(rows=2, columns=2, pattern='independent'),
            xaxis=dict(
                title="Date",
                showgrid=True,
                gridwidth=1,
                gridcolor='rgba(128, 128, 128, 0.2)',
                showspikes=True,
                spikesnap='cursor',
                spikemode='across',
                spikethickness=1,
                domain=[0, 0.95]
            ),
            yaxis=dict(
                title="Ratio and Position (%)",
                ticksuffix="%",
                range=[-10, 120],
                showgrid=True,
                gridwidth=1,
                gridcolor='rgba(128, 128, 128, 0.2)',
                showspikes=True,
                spikesnap='cursor',
                spikemode='across',
                spikethickness=1,
                domain=[0.58, 0.98]
            ),
            yaxis2=dict(
                title="Price (Log Scale)",
                overlaying="y",
                side="right",
                type="log",
                showgrid=False,
                showspikes=True,
                spikesnap='cursor',
                spikemode='across',
                spikethickness=1,
                domain=[0.58, 0.98]
            ),
            legend=dict(
                yanchor="top",
                y=0.99,
                xanchor="right",
                x=1.15,
                bgcolor='rgba(255, 255, 255, 0.8)',
                bordercolor='rgba(0, 0, 0, 0.2)',
                borderwidth=1,
                font=dict(size=11)
            ),
            margin=dict(l=50, r=50, t=120, b=50),
            plot_bgcolor='white',
            paper_bgcolor='white'
        )

        # Add annotations
        annotations = VisualizationService._create_chart_annotations(
            start_price, end_price, annual_return, daily_volatility,
            annualized_volatility, regression_results, total_return, signal_returns
        )
        fig.update_layout(annotations=annotations)

        return fig

    @staticmethod
    def _create_analysis_summary_table(days, end_price, annual_return, daily_volatility, 
                                     annualized_volatility, r2):
        """Create the analysis summary table"""
        return go.Table(
            domain=dict(x=[0, 0.45], y=[0.02, 0.22]),
            header=dict(
                values=['<b>Metric</b>', '<b>Value</b>'],
                fill_color='lightgrey',
                align='left',
                font=dict(size=12)
            ),
            cells=dict(
                values=[
                    ['Total Days', 'Current Price', 'Annualized Return', 
                     'Daily Volatility', 'Annual Volatility', 'Regression R²'],
                    [
                        f"{days:,d}",
                        f"${end_price:.2f}",
                        f"{annual_return:.2f}%",
                        f"{daily_volatility:.3f}",
                        f"{annualized_volatility:.3f}",
                        f"{r2:.4f}"
                    ]
                ],
                align='left',
                font=dict(size=11)
            )
        )

    @staticmethod
    def _create_trading_signal_table(signal_returns):
        """Create the trading signal analysis table"""
        if not signal_returns:
            return go.Table(
                domain=dict(x=[0.85, 1], y=[0.6, 0.99]),
                header=dict(
                    values=['<b>Notice</b>'],
                    fill_color='lightgrey',
                    align='left'
                ),
                cells=dict(
                    values=[['No trading signals found in the analysis period']],
                    align='left'
                )
            )

        trades = []
        buy_signal = None
        for signal in signal_returns:
            if signal['Signal'] == 'Buy':
                buy_signal = signal
                if signal['Status'] == 'Open' and 'Trade Return' in signal:
                    trades.append({
                        'Entry Date': signal['Entry Date'].strftime('%Y-%m-%d'),
                        'Entry Price': signal['Entry Price'],
                        'Exit Date': 'Open',
                        'Exit Price': signal['Current Price'],
                        'Return': signal['Trade Return'],
                        'Status': 'Open'
                    })
            elif signal['Signal'] == 'Sell' and buy_signal is not None:
                trades.append({
                    'Entry Date': buy_signal['Entry Date'].strftime('%Y-%m-%d'),
                    'Entry Price': buy_signal['Entry Price'],
                    'Exit Date': signal['Entry Date'].strftime('%Y-%m-%d'),
                    'Exit Price': signal['Entry Price'],
                    'Return': signal['Trade Return'],
                    'Status': 'Closed'
                })
                buy_signal = None

        return go.Table(
            domain=dict(x=[0.55, 1], y=[0.02, 0.22]),
            header=dict(
                values=['<b>Entry Date</b>', '<b>Entry Price</b>', '<b>Exit Date</b>', 
                       '<b>Exit Price</b>', '<b>Return</b>', '<b>Status</b>'],
                fill_color='lightgrey',
                align='left',
                font=dict(size=12)
            ),
            cells=dict(
                values=[
                    [t['Entry Date'] for t in trades],
                    [f"${t['Entry Price']:.2f}" for t in trades],
                    [t['Exit Date'] for t in trades],
                    [f"${t['Exit Price']:.2f}" for t in trades],
                    [f"{t['Return']:.2f}%" for t in trades],
                    [t['Status'] for t in trades]
                ],
                align='left',
                font=dict(size=11)
            )
        )

    @staticmethod
    def _create_chart_annotations(start_price, end_price, annual_return, daily_volatility,
                                annualized_volatility, regression_results, total_return, signal_returns):
        """Create chart annotations"""
        return [
            dict(
                x=0.02,
                y=1.00,
                xref='paper',
                yref='paper',
                text=f'<b>Price Analysis</b><br>' + 
                     f'Start: ${start_price:.2f}<br>' +
                     f'Current: ${end_price:.2f}<br>' +
                     f'Return: {annual_return:.2f}%',
                showarrow=False,
                font=dict(size=12),
                bgcolor='rgba(255, 255, 255, 0.8)',
                bordercolor='rgba(0, 0, 0, 0.2)',
                borderwidth=1,
                align='left'
            ),
            dict(
                x=0.25,
                y=1.00,
                xref='paper',
                yref='paper',
                text=(f'<b>Regression Analysis</b><br>'
                      f'{regression_results["equation"]}<br>'
                      f'R² = {regression_results["r2"]:.4f}'),
                showarrow=False,
                font=dict(size=12),
                bgcolor='rgba(255, 255, 255, 0.8'),
                bordercolor='rgba(0, 0, 0, 0.2)',
                borderwidth=1,
                align='left'
            ),
            dict(
                x=0.48,
                y=1.00,
                xref='paper',
                yref='paper',
                text=(f'<b>Volatility Analysis</b><br>'
                      f'Daily: {daily_volatility:.3f}<br>'
                      f'Annual: {annualized_volatility:.3f}'),
                showarrow=False,
                font=dict(size=12),
                bgcolor='rgba(255, 255, 255, 0.8'),
                bordercolor='rgba(0, 0, 0, 0.2)',
borderwidth=1,
                align='left'
            ),
            dict(
                x=0.71,
                y=1.00,
                xref='paper',
                yref='paper',
                text=(f'<b>Signal Analysis</b><br>'
                      f'Total Return: {total_return:.2f}%<br>'
                      f'Trades: {len([s for s in signal_returns if s["Signal"] == "Buy"])}'),
                showarrow=False,
                font=dict(size=12),
                bgcolor='rgba(255, 255, 255, 0.8'),
                bordercolor='rgba(0, 0, 0, 0.2'),
                borderwidth=1,
                align='left'
            ),
            # Table Headers
            dict(
                x=0.02,
                y=0.23,
                xref='paper',
                yref='paper',
                text='<b>Analysis Summary</b>',
                showarrow=False,
                font=dict(size=14),
                align='left'
            ),
            dict(
                x=0.55,
                y=0.23,
                xref='paper',
                yref='paper',
                text='<b>Trading Signal Analysis</b>',
                showarrow=False,
                font=dict(size=14),
                align='left'
            ),
            dict(
                x=0.02,
                y=0.54,
                xref='paper',
                yref='paper',
                text='<b>Financial Metrics Growth</b>',
                showarrow=False,
                font=dict(size=14),
                align='left'
            ),
            dict(
                x=0.02,
                y=0.38,
                xref='paper',
                yref='paper',
                text='<b>Historical Financial Metrics</b>',
                showarrow=False,
                font=dict(size=14),
                align='left'
            )
        ]

    @staticmethod
    def print_signal_analysis(signals_df):
        """Print detailed analysis of trading signals including open positions"""
        if signals_df.empty:
            print("No trading signals found in the analysis period.")
            return
            
        print("\nTrading Signal Analysis:")
        print("-" * 50)
        
        trades = []
        buy_signal = None
        
        for _, row in signals_df.iterrows():
            if row['Signal'] == 'Buy':
                buy_signal = row
                if row['Status'] == 'Open' and 'Trade Return' in row:
                    trades.append({
                        'Buy Date': row['Entry Date'],
                        'Buy Price': row['Entry Price'],
                        'Sell Date': 'Open',
                        'Sell Price': row['Current Price'],
                        'Return': row['Trade Return'],
                        'Status': 'Open'
                    })
            elif row['Signal'] == 'Sell' and buy_signal is not None:
                trades.append({
                    'Buy Date': buy_signal['Entry Date'],
                    'Buy Price': buy_signal['Entry Price'],
                    'Sell Date': row['Entry Date'],
                    'Sell Price': row['Entry Price'],
                    'Return': row['Trade Return'],
                    'Status': 'Closed'
                })
                buy_signal = None
        
        for i, trade in enumerate(trades, 1):
            print(f"\nTrade {i}:")
            print(f"Buy:  {trade['Buy Date'].strftime('%Y-%m-%d')} at ${trade['Buy Price']:.2f}")
            if trade['Status'] == 'Open':
                print(f"Current Position: OPEN at ${trade['Sell Price']:.2f}")
            else:
                print(f"Sell: {trade['Sell Date'].strftime('%Y-%m-%d')} at ${trade['Sell Price']:.2f}")
            print(f"Return: {trade['Return']:.2f}%")
            print(f"Status: {trade['Status']}")
        
        if trades:
            returns = [trade['Return'] for trade in trades]
            closed_trades = [t for t in trades if t['Status'] == 'Closed']
            open_trades = [t for t in trades if t['Status'] == 'Open']
            
            print("\nSummary Statistics:")
            print("-" * 50)
            print(f"Total Trades: {len(trades)}")
            print(f"Closed Trades: {len(closed_trades)}")
            print(f"Open Trades: {len(open_trades)}")
            print(f"Average Return per Trade: {np.mean(returns):.2f}%")
            print(f"Best Trade: {max(returns):.2f}%")
            print(f"Worst Trade: {min(returns):.2f}%")
            print(f"Win Rate: {len([r for r in returns if r > 0]) / len(returns) * 100:.1f}%")

This completes the VisualizationService class with all the chart building functionality. The class now includes:

  1. Main chart creation method:

    • create_stock_analysis_chart: Creates the complete analysis chart with all components
  2. Helper methods for table creation:

    • _create_analysis_summary_table: Creates the analysis metrics summary table
    • _create_trading_signal_table: Creates the trading signals analysis table
    • _create_chart_annotations: Creates all chart annotations
  3. Signal analysis printing:

    • print_signal_analysis: Prints detailed analysis of trading signals

The visualization module now contains all the necessary functionality to create the complete stock analysis visualization, including:

Would you like me to show how to use this visualization service with the data and analysis services we created earlier?

q:give me the sructure of visualization.py
I'll outline the complete structure of the visualization.py module with its imports, class, and methods.

# visualization.py

# Imports
import plotly.graph_objects as go
import pandas as pd
from datetime import datetime, timedelta
import numpy as np

class VisualizationService:
    """Service class for creating and managing visualizations of stock analysis data"""
    
    # Number Formatting Methods
    @staticmethod
    def format_number(x):
        """Format numbers with comprehensive handling for display"""
        
    @staticmethod
    def format_growth_values(growth_rates):
        """Format growth rates for table display"""
        
    # Table Creation Methods
    @staticmethod
    def create_financial_metrics_table(df):
        """Create financial metrics and growth tables"""
        
    @staticmethod
    def _create_analysis_summary_table(days, end_price, annual_return, daily_volatility, 
                                     annualized_volatility, r2):
        """Create the analysis summary table"""
        
    @staticmethod
    def _create_trading_signal_table(signal_returns):
        """Create the trading signal analysis table"""
        
    # Chart Creation Methods
    @staticmethod
    def create_stock_analysis_chart(data, analysis_dates, ratios, prices, appreciation_pcts, 
                                  regression_results, crossover_data, signal_returns, 
                                  metrics_df, total_height=1000):
        """Create the complete stock analysis chart with all components"""
        
    @staticmethod
    def _create_chart_annotations(start_price, end_price, annual_return, daily_volatility,
                                annualized_volatility, regression_results, total_return, 
                                signal_returns):
        """Create chart annotations"""
        
    # Signal Analysis Methods
    @staticmethod
    def print_signal_analysis(signals_df):
        """Print detailed analysis of trading signals including open positions"""

The structure is organized as follows:

  1. Imports Section

    • plotly.graph_objects for chart creation
    • pandas for data manipulation
    • datetime for date handling
    • numpy for numerical operations
  2. VisualizationService Class

    • Organized into logical groups of static methods
  3. Method Groups:

    • Number Formatting Methods

      • format_number
      • format_growth_values
    • Table Creation Methods

      • create_financial_metrics_table
      • _create_analysis_summary_table
      • _create_trading_signal_table
    • Chart Creation Methods

      • create_stock_analysis_chart
      • _create_chart_annotations
    • Signal Analysis Methods

      • print_signal_analysis

Key features of the structure:

This structure makes the code:

Would you like me to explain any specific part of the structure in more detail?

q:set up the project with bash.
I'll help you set up the project structure with a bash script.

#!/bin/bash

# Project name
PROJECT_NAME="stock_analysis"

# Create main project directory
mkdir -p $PROJECT_NAME
cd $PROJECT_NAME

# Create project structure
mkdir -p src/{data,analysis,visualization}
mkdir -p tests/{data,analysis,visualization}
mkdir -p docs
mkdir -p config

# Create Python package files
touch src/__init__.py
touch src/data/__init__.py
touch src/analysis/__init__.py
touch src/visualization/__init__.py

# Create main module files
cat > src/data/data_service.py << 'EOF'
import yfinance as yf
import requests
import pandas as pd
from datetime import datetime
from dateutil.relativedelta import relativedelta

class DataService:
    def __init__(self, api_key="a365bff224a6419fac064dd52e1f80d9"):
        self.API_KEY = api_key
        self.BASE_URL = "https://api.roic.ai/v1/rql"
        self.METRICS = {
            "total revenues": "is_sales_and_services_revenues",
            "operating cash flow": "cf_cash_from_oper",
            "net income": "is_net_income",
            "earnings per share": "eps",
            "operating margin": "oper_margin",
            "capital expenditures": "cf_cap_expenditures",
            "return on invested capital": "return_on_inv_capital",
            "Diluted Weighted Avg Shares": "is_sh_for_diluted_eps"
        }
EOF

cat > src/analysis/analysis_service.py << 'EOF'
import numpy as np
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
from sklearn.metrics import r2_score

class AnalysisService:
    @staticmethod
    def calculate_price_appreciation_pct(current_price, highest_price, lowest_price):
        """Calculate price appreciation percentage relative to range"""
        total_range = highest_price - lowest_price
        if total_range > 0:
            current_from_low = current_price - lowest_price
            return (current_from_low / total_range) * 100
        return 0
EOF

cat > src/visualization/visualization_service.py << 'EOF'
import plotly.graph_objects as go
import pandas as pd
from datetime import datetime, timedelta
import numpy as np

class VisualizationService:
    @staticmethod
    def format_number(x):
        """Format numbers with comprehensive handling"""
        if pd.isna(x) or x is None:
            return "N/A"
        try:
            if abs(x) >= 1_000_000:
                return f"-{abs(x/1_000_000):,.0f}M" if x < 0 else f"{x/1_000_000:,.0f}M"
            else:
                return f"{x:,.2f}"
        except (TypeError, ValueError):
            return "N/A"
EOF

# Create main application file
cat > src/main.py << 'EOF'
from data.data_service import DataService
from analysis.analysis_service import AnalysisService
from visualization.visualization_service import VisualizationService

def main():
    try:
        data_service = DataService()
        # Add your main application logic here
        print("Stock Analysis Application Started")
    except Exception as e:
        print(f"Error: {str(e)}")

if __name__ == "__main__":
    main()
EOF

# Create requirements file
cat > requirements.txt << 'EOF'
yfinance>=0.2.36
pandas>=2.0.0
numpy>=1.24.0
plotly>=5.18.0
scikit-learn>=1.3.0
requests>=2.31.0
python-dateutil>=2.8.2
EOF

# Create setup file
cat > setup.py << 'EOF'
from setuptools import setup, find_packages

setup(
    name="stock_analysis",
    version="0.1.0",
    packages=find_packages(),
    install_requires=[
        'yfinance>=0.2.36',
        'pandas>=2.0.0',
        'numpy>=1.24.0',
        'plotly>=5.18.0',
        'scikit-learn>=1.3.0',
        'requests>=2.31.0',
        'python-dateutil>=2.8.2',
    ],
    author="Your Name",
    author_email="[email protected]",
    description="A stock analysis package for technical and financial analysis",
    python_requires='>=3.8',
)
EOF

# Create README file
cat > README.md << 'EOF'
# Stock Analysis Project

A comprehensive stock analysis tool that provides technical and financial analysis capabilities.

## Features

- Historical data retrieval
- Technical analysis
- Financial metrics analysis
- Interactive visualizations

## Installation

1. Clone the repository
2. Create a virtual environment:
   ```bash
   python -m venv venv
   source venv/bin/activate  # On Windows: venv\Scripts\activate
  1. Install dependencies:
    pip install -r requirements.txt
    

Usage

from stock_analysis.data import DataService
from stock_analysis.analysis import AnalysisService
from stock_analysis.visualization import VisualizationService

# Initialize services
data_service = DataService()

License

MIT
EOF

Create test files

cat > tests/init.py << 'EOF'

Test package initialization

EOF

Create configuration file

cat > config/config.yml << 'EOF'
api:
roic:
key: "a365bff224a6419fac064dd52e1f80d9"
base_url: "https://api.roic.ai/v1/rql"

analysis:
default_lookback_days: 365
default_crossover_days: 180

visualization:
default_chart_height: 1000
EOF

Create .gitignore

cat > .gitignore << 'EOF'

Python

pycache/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg

Virtual Environment

venv/
ENV/
env/

IDE

.idea/
.vscode/
*.swp
*.swo

OS

.DS_Store
Thumbs.db
EOF

Initialize git repository

git init

Setup virtual environment

python3 -m venv venv
source venv/bin/activate

Install requirements

pip install -r requirements.txt

echo "Project setup completed successfully!"
echo "To start working on the project:"
echo "1. cd $PROJECT_NAME"
echo "2. source venv/bin/activate # On Windows: venv\Scripts\activate"
echo "3. pip install -r requirements.txt"
echo "4. python src/main.py"


To use this setup script:

1. Save it as `setup_project.sh`
2. Make it executable:
```bash
chmod +x setup_project.sh
  1. Run it:
./setup_project.sh

The script will:

  1. Create the project structure with all necessary directories
  2. Create initial Python files with basic code
  3. Set up a virtual environment
  4. Create requirements.txt with necessary dependencies
  5. Initialize a git repository
  6. Create configuration files
  7. Create a README.md with basic documentation

The project structure will look like this:

stock_analysis/
├── config/
│   └── config.yml
├── docs/
├── src/
│   ├── data/
│   │   ├── __init__.py
│   │   └── data_service.py
│   ├── analysis/
│   │   ├── __init__.py
│   │   └── analysis_service.py
│   ├── visualization/
│   │   ├── __init__.py
│   │   └── visualization_service.py
│   ├── __init__.py
│   └── main.py
├── tests/
│   ├── data/
│   ├── analysis/
│   ├── visualization/
│   └── __init__.py
├── requirements.txt
├── setup.py
├── README.md
└── .gitignore

Would you like me to explain any specific part of the setup or add any additional features to the script?