File size: 4,990 Bytes
63f48ef
0d78d1c
63f48ef
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0d78d1c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2c93b94
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0d78d1c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
import pandas as pd
from typing import Tuple

import re

_NEG_COLOR = "red"

def format_large_number(n, decimals=2):
    if n >= 1e12:
        return f'{n / 1e12:.{decimals}f} T'
    elif n >= 1e9:
        return f'{n / 1e9:.{decimals}f} B'
    elif n >= 1e6:
        return f'{n / 1e6:.{decimals}f} M'
    else:
        return str(n)

def format_results(df: pd.DataFrame, rename_columns: dict) -> pd.DataFrame:
    # Índice 100
    if "ind_sust" in df.columns:
        df["ind_sust"] = df["ind_sust"].apply(lambda x: "-" if pd.isna(x) else int(round(x * 100, 0)))
    # 1 decimal
    for col in ["trailingPE", "beta"]:
        if col in df.columns:
            df[col] = df[col].apply(lambda x: "-" if pd.isna(x) else f"{x:.1f}")

    # 2 decimales
    if "Search dist." in df.columns:
        df["Search dist."] = df["Search dist."].apply(lambda n: "-" if pd.isna(n) else f"{n:.2f}")

    # Cantidades monetarias grandes
    if "marketCap" in df.columns:
        df["marketCap"] = df["marketCap"].apply(lambda n: "-" if pd.isna(n) else format_large_number(n, 1))
    # Porcentajes 1 decimal
    for col in ["ret_365", "revenueGrowth"]:
        if col in df.columns:
            df[col] = df[col].apply(lambda x: "-" if pd.isna(x) or x == 0 else f"{(x * 100):.1f}%")
    # Porcentajes 1 decimal (porcentaje numérico en fuente)
    for col in ["dividendYield"]:
        if col in df.columns:
            df[col] = df[col].apply(lambda x: "-" if pd.isna(x) else f"{round(x, 1)}%")
    # Volatilidad
    if "vol_365" in df.columns:
        df["vol_365"] = df["vol_365"].apply(lambda x: "-" if pd.isna(x) or x == 0 else f"{x:.4f}")

    # Devolvemos el dataframe con los nombres de columnas renombrados
    return df.rename(columns=rename_columns)


def random_ticker(df: pd.DataFrame) -> str:
    return df["ticker"].sample(n=1).values[0]

def styler_negative_red(df: pd.DataFrame, cols: list[str] | None = None):
    """
    Returns a Styler that paints negative numeric values in *cols*.
    Columns absent in *df* are ignored.
    """
    cols = [c for c in (cols or df.columns) if c in df.columns]

    def _style(v):
        try:
            num = float(re.sub(r"[ %,TMB]", "", str(v)))
            if num < 0:
                return f"color:{_NEG_COLOR}"
        except ValueError:
            pass
        return ""

    return df.style.applymap(_style, subset=cols)

def get_company_info(
    maestro: pd.DataFrame,
    ticker: str,
    rename_columns: dict
) -> Tuple[str, str, pd.DataFrame]:
    """
    Returns the company name, longBusinessSummary, and a DataFrame
    of all other fields for the given ticker.
    """
    company = maestro[maestro["ticker"] == ticker]
    if company.empty:
        return ticker, "No data available.", pd.DataFrame()

    # extract name & summary
    name = company["security"].iloc[0] if "security" in company.columns else ticker
    summary = company["longBusinessSummary"].iloc[0] if "longBusinessSummary" in company.columns else ""

    # build details table
    details = company.drop(columns=["longBusinessSummary"], errors="ignore").iloc[0]
    df = pd.DataFrame({
        "Field": details.index.tolist(),
        "Value": details.values.tolist()
    })
    df["Field"] = df["Field"].map(lambda c: rename_columns.get(c, c))

    # Round _norm fields to 3 decimal places
    for i, field in enumerate(df["Field"]):
        if field.endswith("_norm"):
            value = df.iloc[i]["Value"]
            if isinstance(value, (int, float)) and not pd.isna(value):
                df.iloc[i, df.columns.get_loc("Value")] = round(value, 3)
        # Process numeric fields using format_results function
        # Extract numeric fields (excluding already processed _norm fields)
        numeric_fields = []
        numeric_values = []
        numeric_indices = []
        
        for i, (display_field, value) in enumerate(zip(df["Field"], df["Value"])):
            if not display_field.endswith("_norm") and isinstance(value, (int, float)) and not pd.isna(value):
                # Get original field name using inverse rename dictionary
                orig_field = next((k for k, v in rename_columns.items() if v == display_field), display_field)
                numeric_fields.append(orig_field)
                numeric_values.append(value)
                numeric_indices.append(i)
        
        if numeric_fields:
            # Create a single-row dataframe with original field names
            temp_df = pd.DataFrame([numeric_values], columns=numeric_fields)
            
            # Apply format_results function
            formatted_df = format_results(temp_df, rename_columns)
            
            # Put formatted values back into the original dataframe
            for i, field in zip(numeric_indices, numeric_fields):
                display_field = rename_columns.get(field, field)
                df.iloc[i, df.columns.get_loc("Value")] = formatted_df.iloc[0][display_field]
        

    return name, summary, df