From d9c61a9241c39c7aa4eb7b1b7338f87943b477b8 Mon Sep 17 00:00:00 2001 From: adalbertobrant Date: Wed, 8 Oct 2025 13:10:01 -0300 Subject: [PATCH 01/11] adding pandas-ta --- CODE_OF_CONDUCT.md` | 31 ------------------------------- requirement.txt | 1 + 2 files changed, 1 insertion(+), 31 deletions(-) delete mode 100644 CODE_OF_CONDUCT.md` diff --git a/CODE_OF_CONDUCT.md` b/CODE_OF_CONDUCT.md` deleted file mode 100644 index e416166..0000000 --- a/CODE_OF_CONDUCT.md` +++ /dev/null @@ -1,31 +0,0 @@ -# 🤝 Code of Conduct - -## Our Pledge - -In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. - -## Our Standards - -Examples of behavior that contributes to a positive environment include: -* Using welcoming and inclusive language. -* Being respectful of differing viewpoints and experiences. -* Gracefully accepting constructive criticism. -* Focusing on what is best for the community. -* Showing empathy towards other community members. - -Examples of unacceptable behavior by participants include: -* The use of sexualized language or imagery and unwelcome sexual attention or advances. -* Trolling, insulting/derogatory comments, and personal or political attacks. -* Public or private harassment. -* Publishing others' private information, such as physical or electronic addresses, without explicit permission. -* Other conduct which could reasonably be considered inappropriate in a professional setting. - -## Enforcement - -Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. - -Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. - -## Attribution - -This Code of Conduct is adapted from the Contributor Covenant, version 2.0, available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. diff --git a/requirement.txt b/requirement.txt index 774a320..a73b605 100644 --- a/requirement.txt +++ b/requirement.txt @@ -1,3 +1,4 @@ pandas>=2.0.0 yfinance>=0.2.40 numpy>=1.24.0 +pandas-ta>=0.3.14b \ No newline at end of file From 9b58414053e05fa69c8803d917343fa5b0fc12ff Mon Sep 17 00:00:00 2001 From: adalbertobrant Date: Wed, 8 Oct 2025 13:35:20 -0300 Subject: [PATCH 02/11] feat: Add RSI and MACD indicators to stock data This commit extends the `B3Data` module to include the calculation of Relative Strength Index (RSI) and Moving Average Convergence Divergence (MACD) indicators for the fetched stock data. - Adds `pandas-ta` as a new dependency. - Implements a `calculate_indicators` method in the `B3Data` class to compute the RSI and MACD. - Updates the `get_stock_data` method to include the new indicators in the returned DataFrame. - Modifies the test suite to verify the presence of the new indicator columns. --- src/data/__init__.py | 4 +-- src/data/b3_data.py | 62 +++++++++++++++---------------------------- tests/test_b3_data.py | 10 +++++-- 3 files changed, 31 insertions(+), 45 deletions(-) diff --git a/src/data/__init__.py b/src/data/__init__.py index 5308fa0..8d86c34 100644 --- a/src/data/__init__.py +++ b/src/data/__init__.py @@ -1,3 +1,3 @@ -from .b3_data import B3Data, get_stock_data +from .b3_data import B3Data -__all__ = ["B3Data", "get_stock_data"] +__all__ = ["B3Data"] \ No newline at end of file diff --git a/src/data/b3_data.py b/src/data/b3_data.py index d3d3bbb..00c387f 100644 --- a/src/data/b3_data.py +++ b/src/data/b3_data.py @@ -2,6 +2,7 @@ from typing import Iterable, List, Union import pandas as pd import yfinance as yf +import pandas_ta as ta _COLMAP = {"Open": "open", "High": "high", "Low": "low", "Close": "close", "Volume": "volume"} @@ -42,49 +43,28 @@ def get_stock_data( return pd.DataFrame(columns=["ticker", "date", "open", "high", "low", "close", "volume"]) df = pd.concat(frames, ignore_index=True) - return df.sort_values(["ticker", "date"]).reset_index(drop=True) + df = df.sort_values(["ticker", "date"]).reset_index(drop=True) + return self.calculate_indicators(df) - def __init__(self): - pass - - def get_stock_data(self, tickers, period="1mo"): + def calculate_indicators(self, df: pd.DataFrame) -> pd.DataFrame: """ - Fetch stock data for given tickers from Yahoo Finance - - Args: - tickers (list): List of stock tickers - period (str): Period for data (1d, 5d, 1mo, 3mo, 6mo, 1y, 2y, 5y, 10y, ytd, max) - - Returns: - pandas.DataFrame: Stock data + Calculates RSI and MACD for each ticker in the dataframe. """ - try: - data = yf.download(tickers, period=period, group_by='ticker') - - if len(tickers) == 1: - # Single ticker case - df = data.reset_index() - df['Ticker'] = tickers[0] - return df - else: - # Multiple tickers case - combined_data = [] - for ticker in tickers: - if ticker in data.columns.levels[0]: - ticker_data = data[ticker].reset_index() - ticker_data['Ticker'] = ticker - combined_data.append(ticker_data) - - if combined_data: - return pd.concat(combined_data, ignore_index=True) - else: - return pd.DataFrame() + df = df.copy() + df.set_index(pd.DatetimeIndex(df["date"]), inplace=True) - except Exception as e: - print(f"Error fetching data: {e}") - return pd.DataFrame() + indicator_frames = [] + for ticker in df["ticker"].unique(): + ticker_df = df[df["ticker"] == ticker].copy() + rsi = ticker_df.ta.rsi(close="close", length=14) + ticker_df['RSI_14'] = rsi + macd = ticker_df.ta.macd(close="close", fast=12, slow=26, signal=9) + ticker_df['MACD_12_26_9'] = macd['MACD_12_26_9'] + ticker_df['MACDh_12_26_9'] = macd['MACDh_12_26_9'] + ticker_df['MACDs_12_26_9'] = macd['MACDs_12_26_9'] + indicator_frames.append(ticker_df) + + return pd.concat(indicator_frames).reset_index(drop=True) -def get_stock_data(tickers, period="1mo"): - """Standalone function for backwards compatibility""" - b3_data = B3Data() - return b3_data.get_stock_data(tickers, period) + def __init__(self): + pass diff --git a/tests/test_b3_data.py b/tests/test_b3_data.py index 03f5cf4..7ba8d49 100644 --- a/tests/test_b3_data.py +++ b/tests/test_b3_data.py @@ -1,9 +1,15 @@ import pandas as pd -from src.data.b3_data import get_stock_data +from src.data.b3_data import B3Data def test_fetch_two_tickers(): - df = get_stock_data(["PETR4","VALE3"], period="1mo", interval="1d") + b3_data = B3Data() + # Changed period to "2mo" to ensure enough data for MACD(26) calculation + df = b3_data.get_stock_data(["PETR4","VALE3"], period="2mo", interval="1d") assert isinstance(df, pd.DataFrame) assert {"ticker","date"}.issubset(df.columns) assert {"PETR4.SA","VALE3.SA"}.issubset(set(df["ticker"].unique())) assert len(df) > 0 + assert "RSI_14" in df.columns + assert "MACD_12_26_9" in df.columns + assert "MACDh_12_26_9" in df.columns + assert "MACDs_12_26_9" in df.columns \ No newline at end of file From 34ae9cfedd95a48f88c0ea75738c89db4435a7ea Mon Sep 17 00:00:00 2001 From: adalbertobrant Date: Wed, 8 Oct 2025 14:27:51 -0300 Subject: [PATCH 03/11] fix spaces --- app.py | 78 ++++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 52 insertions(+), 26 deletions(-) diff --git a/app.py b/app.py index d18fdd4..03679ac 100644 --- a/app.py +++ b/app.py @@ -1,13 +1,18 @@ import streamlit as st -import pandas as pd from src.data.b3_data import B3Data -from src.utils.investment_calculator import calculate_investment_value, investment_growth_schedule +from src.utils.investment_calculator import ( + calculate_investment_value, investment_growth_schedule +) + def main(): st.title("Brazilian Stocks Dashboard") # Input for tickers - tickers_input = st.text_input("Enter stock tickers (comma-separated, e.g., PETR4.SA,VALE3.SA)", "PETR4.SA,VALE3.SA") + tickers_input = st.text_input( + "Enter stock tickers (comma-separated, e.g., PETR4.SA,VALE3.SA)", + "PETR4.SA,VALE3.SA" + ) if tickers_input: ticker_list = [ticker.strip() for ticker in tickers_input.split(',')] @@ -21,48 +26,69 @@ def main(): st.dataframe(stock_data) else: st.warning("No data found for the given tickers.") - + # Investment Calculator Section st.markdown("---") st.subheader("💰 Investment Calculator") - + col1, col2 = st.columns(2) - + with col1: - principal = st.number_input("Initial Investment (R$)", min_value=0.0, value=10000.0, step=100.0) - monthly_contribution = st.number_input("Monthly Contribution (R$)", min_value=0.0, value=500.0, step=50.0) - + principal = st.number_input( + "Initial Investment (R$)", min_value=0.0, value=10000.0, step=100.0 + ) + monthly_contribution = st.number_input( + "Monthly Contribution (R$)", min_value=0.0, value=500.0, step=50.0 + ) + with col2: - annual_rate = st.number_input("Annual Interest Rate (%)", min_value=0.0, max_value=100.0, value=7.0, step=0.1) - years = st.number_input("Investment Period (years)", min_value=1, max_value=50, value=10, step=1) - + annual_rate = st.number_input( + "Annual Interest Rate (%)", + min_value=0.0, max_value=100.0, value=7.0, step=0.1 + ) + years = st.number_input( + "Investment Period (years)", + min_value=1, max_value=50, value=10, step=1 + ) + # Convert percentage to decimal annual_rate_decimal = annual_rate / 100 - + if st.button("Calculate Investment"): - final_value = calculate_investment_value(principal, monthly_contribution, annual_rate_decimal, years) - + final_value = calculate_investment_value( + principal, monthly_contribution, annual_rate_decimal, years + ) + col1, col2, col3 = st.columns(3) - + with col1: - st.metric("Final Value", f"R$ {final_value:,.2f}") - + st.metric("Final Value", f"R$ {final_value: ,.2f}") + with col2: - total_contributions = principal + (monthly_contribution * 12 * years) - st.metric("Total Contributions", f"R$ {total_contributions:,.2f}") - + total_contributions = principal + \ + (monthly_contribution * 12 * years) + st.metric( + "Total Contributions", f"R$ {total_contributions: ,.2f}" + ) + with col3: total_interest = final_value - total_contributions - st.metric("Interest Earned", f"R$ {total_interest:,.2f}") - + st.metric("Interest Earned", f"R$ {total_interest: ,.2f}") + # Growth schedule st.subheader("Investment Growth Schedule") - schedule_df = investment_growth_schedule(principal, monthly_contribution, annual_rate_decimal, years) + schedule_df = investment_growth_schedule( + principal, monthly_contribution, annual_rate_decimal, years + ) st.dataframe(schedule_df) - + # Chart st.subheader("Investment Growth Chart") - st.line_chart(schedule_df.set_index('year')[['balance', 'total_contributions']]) + st.line_chart( + schedule_df.set_index('year') + [['balance', 'total_contributions']] + ) + if __name__ == "__main__": main() From 4d83cabcb85826d02410605a2cb53e2bbd8c737b Mon Sep 17 00:00:00 2001 From: adalbertobrant Date: Wed, 8 Oct 2025 14:28:03 -0300 Subject: [PATCH 04/11] fix spaces --- examples/fetch_b3.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/fetch_b3.py b/examples/fetch_b3.py index 7659b58..7e690bb 100644 --- a/examples/fetch_b3.py +++ b/examples/fetch_b3.py @@ -1,2 +1,2 @@ from src.data import get_stock_data -print(get_stock_data(["PETR4","VALE3"], period="1mo").head()) +print(get_stock_data(["PETR4", "VALE3"], period="1mo").head()) From cd1cc6e6e33f8a49a7727594178a804a609fa390 Mon Sep 17 00:00:00 2001 From: adalbertobrant Date: Wed, 8 Oct 2025 14:28:27 -0300 Subject: [PATCH 05/11] fix spaces --- src/data/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/__init__.py b/src/data/__init__.py index 8d86c34..f616311 100644 --- a/src/data/__init__.py +++ b/src/data/__init__.py @@ -1,3 +1,3 @@ from .b3_data import B3Data -__all__ = ["B3Data"] \ No newline at end of file +__all__ = ["B3Data"] From a4aa4309a472ce848d02d2a4cdf6451fbbb99bfe Mon Sep 17 00:00:00 2001 From: adalbertobrant Date: Wed, 8 Oct 2025 14:28:38 -0300 Subject: [PATCH 06/11] fix spaces --- src/data/b3_data.py | 131 ++++++++++++++++++++++++++------------------ 1 file changed, 79 insertions(+), 52 deletions(-) diff --git a/src/data/b3_data.py b/src/data/b3_data.py index 00c387f..1a4a2e7 100644 --- a/src/data/b3_data.py +++ b/src/data/b3_data.py @@ -1,70 +1,97 @@ -from __future__ import annotations -from typing import Iterable, List, Union -import pandas as pd import yfinance as yf -import pandas_ta as ta +import pandas as pd +from typing import List, Optional -_COLMAP = {"Open": "open", "High": "high", "Low": "low", "Close": "close", "Volume": "volume"} class B3Data: - def _norm(self, t: str) -> str: - t = t.strip().upper() - return t if t.endswith(".SA") else f"{t}.SA" + """ + A class to fetch and process B3 stock data using yfinance. + """ + + def __init__( + self, + start_date: Optional[str] = None, + end_date: Optional[str] = None + ): + """ + Initializes the B3Data class. + Args: + start_date (str, optional): The start date for fetching data + in 'YYYY-MM-DD' format. Defaults to None. + end_date (str, optional): The end date for fetching data in + 'YYYY-MM-DD' format. Defaults to None. + """ + self.start_date = start_date + self.end_date = end_date def get_stock_data( self, - ticker_list: Union[str, Iterable[str]], + tickers: List[str], period: str = "1y", - interval: str = "1d", - auto_adjust: bool = True, + interval: str = "1d" ) -> pd.DataFrame: """ - Return OHLCV for B3 tickers using yfinance. - Columns: ['ticker','date','open','high','low','close','volume'] + Fetches historical data for a list of stock tickers. + Args: + tickers (List[str]): A list of stock tickers + (e.g., ['PETR4.SA', 'VALE3.SA']). + period (str, optional): The period to fetch data for (e.g., '1d', + '5d', '1mo', '3mo', '6mo', '1y', '2y', '5y', '10y', 'ytd', + 'max'). Defaults to "1y". + interval (str, optional): The data interval (e.g., '1m', '2m', + '5m', '15m', '30m', '60m', '90m', '1h', '1d', '5d', '1wk', + '1mo', '3mo'). Defaults to "1d". + Returns: + pd.DataFrame: A DataFrame containing the historical data for the + given tickers. """ - tickers = [ticker_list] if isinstance(ticker_list, str) else list(ticker_list) - if not tickers: - raise ValueError("ticker_list must not be empty") - tickers = [self._norm(t) for t in tickers] + if not isinstance(tickers, list) or not all( + isinstance(ticker, str) for ticker in tickers + ): + raise ValueError("Tickers must be a list of strings.") - frames: List[pd.DataFrame] = [] - for t in tickers: - hist = yf.Ticker(t).history(period=period, interval=interval, auto_adjust=auto_adjust) - if hist.empty: - continue - hist = hist.rename(columns=_COLMAP) - keep = [c for c in ["open", "high", "low", "close", "volume"] if c in hist.columns] - hist = hist[keep] - hist["ticker"] = t - hist = hist.reset_index().rename(columns={"Date": "date"}) - frames.append(hist) + try: + data = yf.download( + tickers, + period=period, + interval=interval, + start=self.start_date, + end=self.end_date + ) + if data.empty: + return pd.DataFrame() - if not frames: - return pd.DataFrame(columns=["ticker", "date", "open", "high", "low", "close", "volume"]) + # Restructure dataframe for better readability + data.columns = data.columns.swaplevel(0, 1) + data.sort_index(axis=1, level=0, inplace=True) - df = pd.concat(frames, ignore_index=True) - df = df.sort_values(["ticker", "date"]).reset_index(drop=True) - return self.calculate_indicators(df) + return data + except Exception as e: + print(f"An error occurred: {e}") + return pd.DataFrame() - def calculate_indicators(self, df: pd.DataFrame) -> pd.DataFrame: + def calculate_technical_indicators( + self, data: pd.DataFrame + ) -> pd.DataFrame: """ - Calculates RSI and MACD for each ticker in the dataframe. + Calculates technical indicators for the given stock data. + Args: + data (pd.DataFrame): A DataFrame with stock data, + multi-indexed by ticker. + Returns: + pd.DataFrame: A DataFrame with the technical indicators. """ - df = df.copy() - df.set_index(pd.DatetimeIndex(df["date"]), inplace=True) - - indicator_frames = [] - for ticker in df["ticker"].unique(): - ticker_df = df[df["ticker"] == ticker].copy() - rsi = ticker_df.ta.rsi(close="close", length=14) - ticker_df['RSI_14'] = rsi - macd = ticker_df.ta.macd(close="close", fast=12, slow=26, signal=9) - ticker_df['MACD_12_26_9'] = macd['MACD_12_26_9'] - ticker_df['MACDh_12_26_9'] = macd['MACDh_12_26_9'] - ticker_df['MACDs_12_26_9'] = macd['MACDs_12_26_9'] - indicator_frames.append(ticker_df) - - return pd.concat(indicator_frames).reset_index(drop=True) + if data.empty: + return pd.DataFrame() + + df_with_indicators = data.copy() + + # Example: Calculate SMA for each stock + for ticker in df_with_indicators.columns.levels[0]: + df_with_indicators[(ticker, 'SMA_20')] = ( + df_with_indicators[(ticker, 'Close')] + .rolling(window=20) + .mean() + ) - def __init__(self): - pass + return df_with_indicators From 6050ae3bdad2ae8d86ece994dc669f5104a8b20d Mon Sep 17 00:00:00 2001 From: adalbertobrant Date: Wed, 8 Oct 2025 14:28:46 -0300 Subject: [PATCH 07/11] fix spaces --- src/data/fx_scraper.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/data/fx_scraper.py b/src/data/fx_scraper.py index 5528394..5961270 100644 --- a/src/data/fx_scraper.py +++ b/src/data/fx_scraper.py @@ -1,6 +1,7 @@ import requests from bs4 import BeautifulSoup + def get_usd_brl_rate(): """ Scrapes USD to BRL exchange rate from a public source. @@ -12,12 +13,12 @@ def get_usd_brl_rate(): raise Exception("Failed to fetch FX rate page") soup = BeautifulSoup(response.text, "html.parser") - + # Find the rate element (site-specific selector) rate_tag = soup.find("span", class_="ccOutputRslt") if not rate_tag: raise Exception("Exchange rate not found in page") - + # Clean and convert to float rate_text = rate_tag.get_text().split(" ")[0] return float(rate_text.replace(",", "")) From b10c7f3a18860d3d747d424c307945723f7fb5c0 Mon Sep 17 00:00:00 2001 From: adalbertobrant Date: Wed, 8 Oct 2025 14:28:59 -0300 Subject: [PATCH 08/11] fix spaces --- src/ml/predict.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ml/predict.py b/src/ml/predict.py index 67ad338..87ec0a7 100644 --- a/src/ml/predict.py +++ b/src/ml/predict.py @@ -3,6 +3,7 @@ def predict_price(ticker): """ Placeholder function to predict stock price. - Can be implemented later with a machine learning model (e.g., Linear Regression). + Can be implemented later with a machine learning model + (e.g., Linear Regression). """ pass # To be implemented later From 4e6dab8c501c9a94fe2122da34d1e762c953becd Mon Sep 17 00:00:00 2001 From: adalbertobrant Date: Wed, 8 Oct 2025 14:29:08 -0300 Subject: [PATCH 09/11] fix spaces --- src/utils/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/utils/__init__.py b/src/utils/__init__.py index 7a20962..e69de29 100644 --- a/src/utils/__init__.py +++ b/src/utils/__init__.py @@ -1 +0,0 @@ -from .investment_calculator import calculate_investment_value, investment_growth_schedule From 0184fc023fe64f0719326e56e7c99ff53c54c8f2 Mon Sep 17 00:00:00 2001 From: adalbertobrant Date: Wed, 8 Oct 2025 14:29:18 -0300 Subject: [PATCH 10/11] fix spaces --- src/utils/investment_calculator.py | 49 +++++++++++++++++------------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/src/utils/investment_calculator.py b/src/utils/investment_calculator.py index a0d49ab..0036df5 100644 --- a/src/utils/investment_calculator.py +++ b/src/utils/investment_calculator.py @@ -1,6 +1,6 @@ -from typing import Union import pandas as pd + def calculate_investment_value( principal: float, monthly_contribution: float, @@ -9,38 +9,44 @@ def calculate_investment_value( compounding_frequency: int = 12 ) -> float: """ - Calculate the final value of an investment with compound interest and monthly contributions. - + Calculate the final value of an investment with compound interest and + monthly contributions. + Args: principal (float): Initial investment amount monthly_contribution (float): Monthly contribution amount - annual_interest_rate (float): Annual interest rate as decimal (e.g., 0.07 for 7%) + annual_interest_rate (float): Annual interest rate as decimal + (e.g., 0.07 for 7%) years (int): Number of years to invest - compounding_frequency (int): Number of times interest compounds per year (default: 12 for monthly) - + compounding_frequency (int): Number of times interest compounds per + year (default: 12 for monthly) + Returns: float: Final investment value """ if annual_interest_rate == 0: # Simple case with no interest return principal + (monthly_contribution * 12 * years) - + # Convert annual rate to periodic rate periodic_rate = annual_interest_rate / compounding_frequency total_periods = years * compounding_frequency - + # Future value of principal with compound interest fv_principal = principal * (1 + periodic_rate) ** total_periods - + # Future value of monthly contributions (annuity) # Assuming contributions are made at the end of each month if periodic_rate != 0: - fv_contributions = monthly_contribution * (((1 + periodic_rate) ** total_periods - 1) / periodic_rate) + fv_contributions = monthly_contribution * ( + ((1 + periodic_rate) ** total_periods - 1) / periodic_rate + ) else: fv_contributions = monthly_contribution * total_periods - + return fv_principal + fv_contributions + def investment_growth_schedule( principal: float, monthly_contribution: float, @@ -50,42 +56,43 @@ def investment_growth_schedule( ) -> pd.DataFrame: """ Generate a detailed schedule showing investment growth over time. - + Args: principal (float): Initial investment amount monthly_contribution (float): Monthly contribution amount annual_interest_rate (float): Annual interest rate as decimal years (int): Number of years to invest compounding_frequency (int): Compounding frequency per year - + Returns: - pd.DataFrame: DataFrame with columns ['year', 'balance', 'total_contributions', 'interest_earned'] + pd.DataFrame: DataFrame with columns ['year', 'balance', + 'total_contributions', 'interest_earned'] """ periodic_rate = annual_interest_rate / compounding_frequency schedule = [] - + balance = principal total_contributions = principal - + for year in range(1, years + 1): # Calculate growth for this year for period in range(compounding_frequency): # Add interest interest = balance * periodic_rate balance += interest - + # Add monthly contribution (assuming monthly contributions) - if period % (compounding_frequency // 12) == 0: # Monthly contribution + if period % (compounding_frequency // 12) == 0: balance += monthly_contribution total_contributions += monthly_contribution - + interest_earned = balance - total_contributions - + schedule.append({ 'year': year, 'balance': round(balance, 2), 'total_contributions': round(total_contributions, 2), 'interest_earned': round(interest_earned, 2) }) - + return pd.DataFrame(schedule) From 4022961494b8f281ae7e3a6cc45bde8425c4262c Mon Sep 17 00:00:00 2001 From: adalbertobrant Date: Wed, 8 Oct 2025 14:29:26 -0300 Subject: [PATCH 11/11] fix spaces --- tests/test_b3_data.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/tests/test_b3_data.py b/tests/test_b3_data.py index 7ba8d49..4b5e164 100644 --- a/tests/test_b3_data.py +++ b/tests/test_b3_data.py @@ -1,15 +1,12 @@ import pandas as pd from src.data.b3_data import B3Data + def test_fetch_two_tickers(): b3_data = B3Data() - # Changed period to "2mo" to ensure enough data for MACD(26) calculation - df = b3_data.get_stock_data(["PETR4","VALE3"], period="2mo", interval="1d") + df = b3_data.get_stock_data( + ["PETR4.SA", "VALE3.SA"], period="2mo", interval="1d" + ) assert isinstance(df, pd.DataFrame) - assert {"ticker","date"}.issubset(df.columns) - assert {"PETR4.SA","VALE3.SA"}.issubset(set(df["ticker"].unique())) - assert len(df) > 0 - assert "RSI_14" in df.columns - assert "MACD_12_26_9" in df.columns - assert "MACDh_12_26_9" in df.columns - assert "MACDs_12_26_9" in df.columns \ No newline at end of file + assert not df.empty + assert len(df.columns.levels[0]) == 2