# 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",
)