# app.py import ast from datetime import datetime import streamlit as st import plotly.graph_objects as go import pandas as pd from utils.helper import * # english def main_algo_trader(): # Front-end Design # st.set_page_config(layout="wide") st.write("# Welcome to Algorithmic Trading! A Quick Implementationđź‘‹") with st.sidebar: with st.expander("Expand/Collapse"): st.markdown( r""" The following app is a simple demonstration of the growth stock strategy. For simplicity, we assume our research team hand over a pool of stocks. Amongst the pool of stocks, we can do the following: - use `yfinance` library to download stock data live (for the sake of speed, please start with time frame "1mo"); - every period (time frame is a tuning parameter), we balance our portfolio (equal weight) by holding the top n stocks (n can be top quintile/quartile of stocks); """ ) # Main inputs tickers = st.text_input( "Enter tickers (comma-separated):", "MSFT, AAPL, NVDA, GOOG, AMZN, META, LLY, AVGO, TSLA, JPM, V, WMT, UNH, MA, PG, HD, JNJ, ORCL, MRK, COST, ABBV, BAC, CRM, AMD, NFLX, ACN, ADBE, DIS, TMO, WFC, MCD, CSCO, ABT, QCOM, INTC, INTU, IBM, AMAT, CMCSA, AXP, PFE, NOW, AMGN, MU", ) start_date = st.sidebar.date_input("Start date", pd.to_datetime("2001-01-01")) end_date = st.sidebar.date_input( "End date", pd.to_datetime(datetime.now().strftime("%Y-%m-%d")) ) time_frame = st.sidebar.selectbox( "Select Time Frame:", [ "1mo", "3mo", ], ) top_n = st.sidebar.number_input("Top n stocks", min_value=1, value=3) height_of_graph = st.sidebar.number_input( "Height of the plot", min_value=500, value=750 ) # Process inputs tickers_list = [ticker.strip() for ticker in tickers.split(",")] # Run analysis on button click if st.button("Run Analysis"): with st.spinner("Downloading data and calculating returns..."): stock_data = download_stock_data( tickers_list, start_date.strftime("%Y-%m-%d"), end_date.strftime("%Y-%m-%d"), w=time_frame, ) returns_data = create_portfolio_and_calculate_returns(stock_data, top_n) benchmark_sharpe_ratio = ( returns_data["benchmark"].mean() / returns_data["benchmark"].std() ) portfolio_sharpe_ratio = ( returns_data["portfolio_returns"].mean() / returns_data["portfolio_returns"].std() ) # Data for plotting df = returns_data[ ["rolling_benchmark", "rolling_portfolio_returns", "portfolio_history"] ] df.index = pd.to_datetime(df.index, unit="ms") # Create download file @st.cache_data def convert_df(df): # IMPORTANT: Cache the conversion to prevent computation on every rerun return df.to_csv().encode("utf-8") csv = convert_df(df) # Create plot fig = go.Figure() fig.add_trace( go.Scatter( x=df.index, y=df["rolling_benchmark"], mode="lines", name="Rolling Benchmark", ) ) fig.add_trace( go.Scatter( x=df.index, y=df["rolling_portfolio_returns"], mode="lines", name="Rolling Portfolio Returns", ) ) for date, stocks in df["portfolio_history"].items(): fig.add_shape( type="line", x0=date, y0=0, x1=date, y1=0, line=dict(color="RoyalBlue", width=1), ) fig.add_annotation( x=date, y=0.5, text=str(stocks), showarrow=False, yshift=10, textangle=-90, font=dict(size=15), # You can adjust the size as needed ) # Calculate means and standard deviations benchmark_mean = returns_data["benchmark"].mean() benchmark_std = returns_data["benchmark"].std() portfolio_mean = returns_data["portfolio_returns"].mean() portfolio_std = returns_data["portfolio_returns"].std() # Update title text with additional information if time_frame == "1mo": some_n_based_on_time_frame = 12 in_a_year = 1000 * (1 + portfolio_mean) ** (some_n_based_on_time_frame) in_50_years = 1000 * (1 + portfolio_mean) ** ( some_n_based_on_time_frame * 50 ) else: some_n_based_on_time_frame = 4 in_a_year = 1000 * (1 + portfolio_mean) ** (some_n_based_on_time_frame) in_50_years = 1000 * (1 + portfolio_mean) ** ( some_n_based_on_time_frame * 50 ) title_text = ( f"Performance:
" f"Benchmark Sharpe Ratio = {benchmark_sharpe_ratio:.3f}, " f"Portfolio Sharpe Ratio = {portfolio_sharpe_ratio:.3f}, " f"based on time frame: {time_frame}
" f"Benchmark => Mean: {benchmark_mean:.4f}, Std: {benchmark_std:.4f}; " f"Portfolio => Mean: {portfolio_mean:.4f}, Std: {portfolio_std:.4f}
" f"---
" f"This may or may not be a small number, let's check under the following cases 1) 12-mon, and 2) 50-year returns on $1,000 USD:
" f"$1,000*(1+{portfolio_mean:.4f})^({some_n_based_on_time_frame})={in_a_year},
" f"$1,000*(1+{portfolio_mean:.4f})^({some_n_based_on_time_frame}*50)={in_50_years}." ) curr_max_num = max( df.rolling_benchmark.max(), df.rolling_portfolio_returns.max() ) fig.update_layout( title=title_text, xaxis_title="Date", yaxis_title="Value", yaxis=dict(range=[0, curr_max_num * 1.1]), legend=dict( orientation="h", x=0.5, y=-0.4, xanchor="center", yanchor="bottom" ), height=height_of_graph, ) st.plotly_chart(fig, use_container_width=True) # Post-analysis col1, col2 = st.columns(2) with col1: # Checkpoint: ask user whether they want portfolio weights if csv: try: recent_selected_stocks = df["portfolio_history"][-1] recent_selected_stocks = ", ".join(recent_selected_stocks) st.success( f"The algorithm suggests to hold the following stocks for the current month (equally weighted): {recent_selected_stocks}" ) except: st.warning( "Oops! No data found due during API calls. Please refresh the screen and rerun the simulation." ) with col2: # Download st.download_button( label="Download data as CSV", data=csv, file_name=f"history_{end_date}.csv", mime="text/csv", )