Spaces:
Running
Running
| import pandas as pd | |
| import numpy as np | |
| import os | |
| import io | |
| from datetime import datetime | |
| from datetime import timedelta | |
| import holoviews as hvs | |
| import panel as pn | |
| import hvplot as hv | |
| import hvplot.pandas | |
| import yfinance as yf | |
| from dotenv import load_dotenv | |
| # from datasets import load_dataset | |
| from utils import load_hf_dataset # ,upload_to_hf_dataset, download_from_hf_dataset | |
| pn.extension("bokeh", template="bootstrap") | |
| def _extract_raw_data(ticker): | |
| df = yf.Ticker(ticker) | |
| df = df.history(period="1y", interval="1d").reset_index() | |
| df['SMA50'] = df['Close'].rolling(window=50).mean() | |
| df['SMA200'] = df['Close'].rolling(window=200).mean() | |
| return df | |
| def _transform_data(raw_data: pd.DataFrame): | |
| data = ( | |
| raw_data[["Date", "Open", "High", "Low", "Close", "Volume" ,"SMA50", "SMA200"]] | |
| .copy(deep=True) | |
| .rename( | |
| columns={ | |
| "Date": "time", | |
| "Open": "open", | |
| "High": "high", | |
| "Low": "low", | |
| "Close": "close", | |
| "Volume": "volume", | |
| } | |
| ) | |
| ) | |
| t_delta = timedelta(hours=1) | |
| data["time_start"] = data.time - 9 * t_delta # rectangles start | |
| data["time_end"] = data.time + 9 * t_delta # rectangles end | |
| data["positive"] = ((data.close - data.open) > 0).astype(int) | |
| return data | |
| def make_candle_stick(ticker): | |
| raw_data = _extract_raw_data(ticker=ticker) | |
| data = _transform_data(raw_data=raw_data) | |
| _delta = np.median(np.diff(data.time)) | |
| candlestick = hvs.Segments( | |
| data, kdims=["time", "low", "time", "high"] | |
| ) * hvs.Rectangles( | |
| data, kdims=["time_start", "open", "time_end", "close"], vdims=["positive"] | |
| ) * hvs.Curve( | |
| data, kdims=["time"], vdims=[ "SMA50"] | |
| ) | |
| candlestick = candlestick.redim.label(Low="Values") | |
| candlechart = pn.Column( | |
| candlestick.opts( | |
| hvs.opts.Rectangles( | |
| color="positive", cmap=["red", "green"], responsive=True | |
| ), | |
| hvs.opts.Segments( | |
| color="black", height=400, responsive=True, show_grid=True | |
| ), | |
| ), | |
| data.hvplot( | |
| x="time", y="volume", kind="line", responsive=True, height=200 | |
| ).opts(show_grid=True), | |
| ) | |
| # data.hvplot(y="volume", kind="bar", responsive=True, height=200) ) | |
| return candlechart | |
| # Function to convert DataFrame to CSV | |
| def get_csv(df): | |
| sio = io.StringIO() | |
| df.to_csv(sio, index=False) | |
| sio.seek(0) | |
| return sio | |
| # Function to convert the 'Ticker' column to a comma-separated string in a text file | |
| def get_text(df): | |
| tickers = df["Ticker"].tolist() | |
| tickers_str = ",".join(tickers) | |
| sio = io.StringIO() | |
| sio.write(tickers_str) | |
| sio.seek(0) | |
| return sio | |
| # Load environment variables from .env file | |
| load_dotenv() | |
| # Get the Hugging Face API token from the environment; either set in .env file or in the environment directly in GitHub | |
| HF_TOKEN = os.getenv("HF_TOKEN") | |
| # Get the name of the GuruFocus dataset for TradingView, Finviz, MarketBeat and TipRanks to read from | |
| dataset_name_BeatingMarket_input = os.getenv("dataset_name_BeatingMarket_input") | |
| dataset_name_TradingView_input = os.getenv("dataset_name_TradingView_input") | |
| dataset_name_GuruFocus_input = os.getenv("dataset_name_GuruFocus_input") | |
| dataset_name_FinViz_input = os.getenv("dataset_name_FinViz_input") | |
| dataset_name_TipRanks_input = os.getenv("dataset_name_TipRanks_input") | |
| def get_tradingview(current_datetime): | |
| # Load lastest TradingView DataSet from HuggingFace Dataset which is always america.csv | |
| # download_from_hf_dataset("america.csv", dataset_name_TradingView_input, HF_TOKEN) | |
| return load_hf_dataset("america.csv", HF_TOKEN, dataset_name_TradingView_input) | |
| def get_marketbeat(current_datetime): | |
| return load_hf_dataset( | |
| "beatingmarket.csv", HF_TOKEN, dataset_name_BeatingMarket_input | |
| ) | |
| def get_gurufocus(current_datetime): | |
| # Load lastest GuruFocus DataSet from HuggingFace Dataset which is always GuruFocus_merged.csv | |
| # daily_gurufocus_DF = load_dataset(dataset_name_GuruFocus_output , data_files="GuruFocus_merged.csv", split="train" , token=HF_TOKEN ).to_pandas() | |
| return load_hf_dataset( | |
| "GuruFocus_merged.csv", HF_TOKEN, dataset_name_GuruFocus_input | |
| ) | |
| def get_finviz(current_datetime): | |
| # Load lastest FinViz DataSet from HuggingFace Dataset which is always FinViz.csv | |
| return load_hf_dataset("FinViz.csv", HF_TOKEN, dataset_name_FinViz_input) | |
| def get_tipranks(current_datetime): | |
| # Load lastest TipRanks DataSet from HuggingFace Dataset which is always tipranks.csv | |
| return load_hf_dataset("tipranks.csv", HF_TOKEN, dataset_name_TipRanks_input) | |
| # Reading gurufocus,finviz from crawling pipelines with GitHub Action | |
| current_datetime = datetime.now().strftime("%Y-%m-%d") | |
| daily_gurufocus_DF = get_gurufocus(current_datetime) | |
| daily_finviz_DF = get_finviz(current_datetime) | |
| daily_marketbeat = get_marketbeat(current_datetime) | |
| monthly_tiprank_DF = get_tipranks(current_datetime) | |
| # TipRanks | |
| monthly_tiprank_DF = monthly_tiprank_DF[["Ticker", "SmartScore"]] | |
| monthly_tiprank_DF["SmartScore"] = monthly_tiprank_DF["SmartScore"].fillna(-1).astype(float) | |
| # FinViz | |
| daily_finviz_DF["FinVizPrice"] = ( | |
| pd.to_numeric(daily_finviz_DF["Price"], errors="coerce").fillna(0).astype(float) | |
| ) | |
| daily_finviz_DF["FinVizTarget"] = ( | |
| pd.to_numeric(daily_finviz_DF["Target Price"], errors="coerce") | |
| .fillna(0) | |
| .astype(float) | |
| ) | |
| daily_finviz_DF["FinVizTargetpercent"] = ( | |
| 100 | |
| * (daily_finviz_DF["FinVizTarget"] - daily_finviz_DF["FinVizPrice"]) | |
| / daily_finviz_DF["FinVizPrice"] | |
| ).round(2) | |
| daily_finviz_DF_filtered = daily_finviz_DF[ | |
| ["Ticker", "Price", "FinVizTarget", "FinVizTargetpercent"] | |
| ] | |
| # Merging tipranks with Gurufocus | |
| DFgurufocus = daily_gurufocus_DF[ | |
| ["Ticker", "GFValue", "Market Capitalization", "Sector", "Industry"] | |
| ] # , 'GFValuediff']] | |
| DFmerge_tipranks_gurufocus = DFgurufocus.merge(monthly_tiprank_DF) | |
| # Merging Finviz with Merged last one | |
| DFmerge_tipranks_gurufocus = DFmerge_tipranks_gurufocus.merge(daily_finviz_DF_filtered) | |
| # ['Ticker', 'AverageTarget', 'MaxTarget', 'LowTarget', 'CurrentPrice'] | |
| daily_marketbeat["MarketBeat"] = ( | |
| (daily_marketbeat["MaxTarget"] > daily_marketbeat["CurrentPrice"]) | |
| & (daily_marketbeat["LowTarget"] > daily_marketbeat["CurrentPrice"]) | |
| & (daily_marketbeat["AverageTarget"] > daily_marketbeat["CurrentPrice"]) | |
| ).astype(int) | |
| # Merging daily_marketbeat with Merged last one | |
| print(80 * "&") | |
| print(DFmerge_tipranks_gurufocus.columns) | |
| print(daily_marketbeat.columns) | |
| # DFmerge_tipranks_gurufocus = DFmerge_tipranks_gurufocus.merge(daily_marketbeat) | |
| DFmerge_tipranks_gurufocus = DFmerge_tipranks_gurufocus.merge( | |
| daily_marketbeat, how="left", on="Ticker" | |
| ) | |
| if ( | |
| "Price" in DFmerge_tipranks_gurufocus.columns | |
| and "GFValue" in DFmerge_tipranks_gurufocus.columns | |
| ): | |
| DFmerge_tipranks_gurufocus["GFValuepercent"] = ( | |
| 100 | |
| * (DFmerge_tipranks_gurufocus["GFValue"] - DFmerge_tipranks_gurufocus["Price"]) | |
| / DFmerge_tipranks_gurufocus["Price"] | |
| ) | |
| DFmerge_tipranks_gurufocus["GFValuepercent"] = DFmerge_tipranks_gurufocus[ | |
| "GFValuepercent" | |
| ].round(2) | |
| DFmerge_tipranks_gurufocus["Market Capitalization"] = ( | |
| DFmerge_tipranks_gurufocus["Market Capitalization"] / 1e9 | |
| ) | |
| DFmerge_tipranks_gurufocus["MarketCap"] = DFmerge_tipranks_gurufocus[ | |
| "Market Capitalization" | |
| ].round(1) | |
| DFmerge_tipranks_gurufocus = DFmerge_tipranks_gurufocus.drop( | |
| columns=["Market Capitalization"] | |
| ) | |
| DFmerge_tipranks_gurufocus = DFmerge_tipranks_gurufocus[ | |
| [ | |
| "Ticker", | |
| "Sector", | |
| "Industry", | |
| "MarketCap", | |
| "SmartScore", | |
| "Price", | |
| "GFValue", | |
| "GFValuepercent", | |
| "FinVizTarget", | |
| "FinVizTargetpercent", | |
| "MarketBeat", | |
| ] | |
| ] | |
| # widget | |
| tickername = pn.state.location.query_params.get("ticker", "No ticker provided") | |
| if tickername == "No ticker provided": | |
| ticker = pn.widgets.AutocompleteInput( | |
| name="Ticker", | |
| options=list(DFmerge_tipranks_gurufocus.Ticker), | |
| placeholder="Write Ticker here همین جا", | |
| value="ALL", | |
| restrict=False, | |
| ) | |
| else: | |
| ticker = pn.widgets.AutocompleteInput( | |
| name="Ticker", | |
| options=list(DFmerge_tipranks_gurufocus.Ticker), | |
| placeholder="Write Ticker here همین جا", | |
| value=f"{tickername}", | |
| restrict=False, | |
| ) | |
| SmartScore = pn.widgets.EditableRangeSlider( | |
| name="Smart Score", start=-1, end=10, value=(-1, 10), step=1 | |
| ) | |
| Industry = pn.widgets.CheckBoxGroup( | |
| name="Select Industry", | |
| value=list(set(DFmerge_tipranks_gurufocus.Industry)), | |
| options=list(set(DFmerge_tipranks_gurufocus.Industry)), | |
| inline=True, | |
| ) | |
| Sector = pn.widgets.MultiSelect( | |
| name="Select Sector", | |
| value=list(set(DFmerge_tipranks_gurufocus.Sector)), | |
| options=list(set(DFmerge_tipranks_gurufocus.Sector)), | |
| ) # , inline=False) | |
| GFValuepercent = pn.widgets.FloatSlider( | |
| name="GF Value %", start=-100, end=100, step=1, value=12.0 | |
| ) | |
| FinVizTargetpercent = pn.widgets.FloatSlider( | |
| name="FinViz Target %", start=-100, end=100, step=1, value=12.0 | |
| ) | |
| MarketCap = pn.widgets.FloatSlider( | |
| name="Market Capital (B$)", start=0, end=1000, step=1, value=10 | |
| ) | |
| MarketBeat_radio_group = pn.widgets.RadioButtonGroup( | |
| name="MarketBeat", | |
| options=["Default Targets", "Above all Targets"], | |
| button_type="success", | |
| value="Above all Targets", | |
| ) | |
| # Create a file download link | |
| download_button_GuruFocus = pn.widgets.FileDownload( | |
| filename=f"GuruFocus_merged_{current_datetime}.csv", | |
| callback=pn.bind(get_csv, daily_gurufocus_DF), | |
| button_type="primary", | |
| label="Download GuruFocus", | |
| width=300, | |
| ) | |
| download_button_FinViz = pn.widgets.FileDownload( | |
| filename=f"FinViz_{current_datetime}.csv", | |
| callback=pn.bind(get_csv, daily_finviz_DF), | |
| button_type="primary", | |
| label="Download FinViz", | |
| width=300, | |
| ) | |
| download_button_MarketBeat = pn.widgets.FileDownload( | |
| filename=f"MarketBeat_{current_datetime}.csv", | |
| callback=pn.bind(get_csv, daily_marketbeat), | |
| button_type="warning", | |
| label="Download MarketBeat", | |
| width=300, | |
| ) | |
| download_button_TipRanks = pn.widgets.FileDownload( | |
| filename=f"TipRanks_{current_datetime}.csv", | |
| callback=pn.bind(get_csv, monthly_tiprank_DF), | |
| button_type="warning", | |
| label="Download TipRanks", | |
| width=300, | |
| ) | |
| def get_DF_filter( | |
| DF, ticker, SmartScore, GFValuepercent, FinVizTargetpercent, Sector, MarketCap | |
| ): | |
| return DF.query( | |
| "SmartScore>=@SmartScore[0] & SmartScore <= @SmartScore[1] & GFValuepercent>=@GFValuepercent & FinVizTargetpercent>@FinVizTargetpercent & Sector in @Sector & MarketCap>@MarketCap" | |
| ) | |
| download_button_Filter = pn.widgets.FileDownload( | |
| filename=f"Filter_{current_datetime}.csv", | |
| callback=pn.bind( | |
| get_csv, | |
| pn.bind( | |
| get_DF_filter, | |
| DF=DFmerge_tipranks_gurufocus, | |
| ticker=ticker, | |
| SmartScore=SmartScore, | |
| GFValuepercent=GFValuepercent, | |
| FinVizTargetpercent=FinVizTargetpercent, | |
| Sector=Sector, | |
| MarketCap=MarketCap, | |
| ), | |
| ), | |
| button_type="danger", | |
| label="Download your Filter", | |
| width=300, | |
| ) | |
| download_button_watchlist = pn.widgets.FileDownload( | |
| filename=f"watchlist_{current_datetime}.txt", | |
| callback=pn.bind( | |
| get_text, | |
| pn.bind( | |
| get_DF_filter, | |
| DF=DFmerge_tipranks_gurufocus, | |
| ticker=ticker, | |
| SmartScore=SmartScore, | |
| GFValuepercent=GFValuepercent, | |
| FinVizTargetpercent=FinVizTargetpercent, | |
| Sector=Sector, | |
| MarketCap=MarketCap, | |
| ), | |
| ), | |
| button_type="success", | |
| label="Download Watchlist", | |
| width=300, | |
| ) | |
| png_pane = pn.pane.GIF( | |
| "https://cdn.dribbble.com/userupload/23265870/file/original-619180a5b218d92d799a2d60617a09fb.gif", | |
| width=300, | |
| ) # , height=150) | |
| png_pane2 = pn.pane.GIF( | |
| "https://www.mywallstreetadvisor.com/_next/image?url=%2FMyWallStreetAdvisorLOGO.png&w=64&q=75", | |
| width=50, | |
| ) # , height=150) | |
| pn.extension('echarts') | |
| def get_gauge(text, value, low, high): | |
| if value: | |
| return pn.indicators.Gauge( | |
| name=f'{text}', value=value, bounds=(low, high), format='{value}', | |
| colors=[(0.2, 'red'), (0.8, 'gold'), (1, 'green')], width=300, height=300) | |
| def get_DF( | |
| DF, | |
| ticker, | |
| SmartScore, | |
| GFValuepercent, | |
| FinVizTargetpercent, | |
| Sector, | |
| MarketCap, | |
| MarketBeat, | |
| ): | |
| if ticker and ticker != "ALL": | |
| table1 = pn.widgets.Tabulator( | |
| DF.query("Ticker == @ticker"), height=200, widths=200, show_index=False | |
| ) | |
| table1.disabled = True | |
| chart1 = make_candle_stick(ticker) | |
| url_button_finviz = pn.widgets.Button( | |
| name=f"{ticker} on FinViz", button_type="primary", width=250 | |
| ) | |
| url_button_finviz.js_on_click( | |
| code=f"window.open('https://finviz.com/quote.ashx?t={ticker}&p=d#', '_blank')" | |
| ) | |
| url_button_benzinga = pn.widgets.Button( | |
| name=f"{ticker} on Benzinga", button_type="primary", width=250 | |
| ) | |
| url_button_benzinga.js_on_click( | |
| code=f"window.open('https://www.benzinga.com/quote/{ticker}', '_blank')" | |
| ) | |
| url_button_tipranks = pn.widgets.Button( | |
| name=f"{ticker} on TipRanks", button_type="warning", width=250 | |
| ) | |
| url_button_tipranks.js_on_click( | |
| code=f"window.open('https://www.tipranks.com/stocks/{ticker}/forecast', '_blank')" | |
| ) | |
| url_button_gurufocus = pn.widgets.Button( | |
| name=f"{ticker} on GuruFocus", button_type="warning", width=250 | |
| ) | |
| url_button_gurufocus.js_on_click( | |
| code=f"window.open('https://gurufocus.com/stock/{ticker}', '_blank')" | |
| ) | |
| url_button_barchart = pn.widgets.Button( | |
| name=f"{ticker} on BarChart", button_type="success", width=250 | |
| ) | |
| url_button_barchart.js_on_click( | |
| code=f"window.open('https://www.barchart.com/stocks/quotes/{ticker}/expected-move', '_blank')" | |
| ) | |
| url_button_optionchart = pn.widgets.Button( | |
| name=f"{ticker} on optionchart", button_type="danger", width=250 | |
| ) | |
| url_button_optionchart.js_on_click( | |
| code=f"window.open('https://optioncharts.io/options/{ticker}', '_blank')" | |
| ) | |
| url_button_tradingview = pn.widgets.Button( | |
| name=f"{ticker} on tradingview", button_type="light", width=250 | |
| ) | |
| url_button_tradingview.js_on_click( | |
| code=f"window.open('https://www.tradingview.com/symbols/NASDAQ-{ticker}/technicals/', '_blank')" | |
| ) | |
| url_button_marketwatch = pn.widgets.Button( | |
| name=f"{ticker} on marketwatch", button_type="danger", width=250 | |
| ) | |
| url_button_marketwatch.js_on_click( | |
| code=f"window.open('https://www.marketwatch.com/investing/stock/{ticker}', '_blank')" | |
| ) | |
| url_button_chartmill_TA = pn.widgets.Button( | |
| name=f"{ticker} on ChartMill TA", button_type="danger", width=250 | |
| ) | |
| url_button_chartmill_TA.js_on_click( | |
| code=f"window.open('https://www.chartmill.com/stock/quote/{ticker}/technical-analysis', '_blank')" | |
| ) | |
| url_button_chartmill_FA = pn.widgets.Button( | |
| name=f"{ticker} on ChartMill FA", button_type="danger", width=250 | |
| ) | |
| url_button_chartmill_FA.js_on_click( | |
| code=f"window.open('https://www.chartmill.com/stock/quote/{ticker}/fundamental-analysis', '_blank')" | |
| ) | |
| url_button_seekingalpha = pn.widgets.Button( | |
| name=f"{ticker} on seekingalpha", button_type="light", width=250 | |
| ) | |
| url_button_seekingalpha.js_on_click( | |
| code=f"window.open('https://seekingalpha.com/symbol/{ticker}', '_blank')" | |
| ) | |
| return pn.Column( | |
| table1, | |
| chart1, | |
| pn.Column( | |
| pn.Row( | |
| url_button_finviz, | |
| url_button_benzinga, | |
| url_button_gurufocus, | |
| url_button_tipranks, | |
| url_button_barchart, | |
| url_button_optionchart, | |
| url_button_tradingview, | |
| url_button_marketwatch, | |
| ), | |
| pn.Row( | |
| url_button_chartmill_TA, | |
| url_button_chartmill_FA, | |
| url_button_seekingalpha | |
| ), | |
| pn.Row( | |
| get_gauge('SmartScore', float(DF.query('Ticker == @ticker').SmartScore.iloc[0]), 0, 10), | |
| get_gauge('GuruFocus %', float(DF.query('Ticker == @ticker').GFValuepercent.iloc[0]), -100, 100), | |
| get_gauge('FinViz %', float(DF.query('Ticker == @ticker').FinVizTargetpercent.iloc[0]), -100, 100), | |
| align="center") ) | |
| ) | |
| else: | |
| if MarketBeat == "Above all Targets": | |
| tabulator = pn.widgets.Tabulator( | |
| DF.query( | |
| "SmartScore>=@SmartScore[0] & SmartScore <= @SmartScore[1] & GFValuepercent>=@GFValuepercent & FinVizTargetpercent>@FinVizTargetpercent & Sector in @Sector & MarketCap>@MarketCap & MarketBeat==1" | |
| ), | |
| height=800, | |
| widths=200, | |
| show_index=False, | |
| theme="modern", | |
| ) | |
| else: | |
| tabulator = pn.widgets.Tabulator( | |
| DF.query( | |
| "SmartScore>=@SmartScore[0] & SmartScore <= @SmartScore[1] & GFValuepercent>=@GFValuepercent & FinVizTargetpercent>@FinVizTargetpercent & Sector in @Sector & MarketCap>@MarketCap" | |
| ), | |
| height=800, | |
| widths=200, | |
| show_index=False, | |
| theme="modern", | |
| ) | |
| tabulator.disabled = True | |
| return tabulator | |
| pn.extension("tabulator") | |
| bound_plot = pn.bind( | |
| get_DF, | |
| DF=DFmerge_tipranks_gurufocus, | |
| ticker=ticker, | |
| SmartScore=SmartScore, | |
| GFValuepercent=GFValuepercent, | |
| FinVizTargetpercent=FinVizTargetpercent, | |
| Sector=Sector, | |
| MarketCap=MarketCap, | |
| MarketBeat=MarketBeat_radio_group, | |
| ) | |
| pn.Column( | |
| pn.Row( | |
| pn.Column( | |
| pn.Row(png_pane2, align="center"), | |
| ticker, | |
| SmartScore, | |
| GFValuepercent, | |
| FinVizTargetpercent, | |
| MarketCap, | |
| pn.Row(MarketBeat_radio_group, align="center"), | |
| png_pane, | |
| download_button_GuruFocus, | |
| download_button_FinViz, | |
| download_button_MarketBeat, | |
| download_button_TipRanks, | |
| download_button_Filter, | |
| download_button_watchlist, | |
| ), | |
| pn.Row(bound_plot, align="center"), | |
| ) | |
| ).servable( | |
| title="Fair Value Ranking - Merged Gurufocus & Tipranks & FinViz & MorningStars & MarketBeat" | |
| ) | |