test new feature
Browse files- utils/helper.py +208 -0
- utils/ui_helper.py +9 -0
utils/helper.py
CHANGED
@@ -1,5 +1,7 @@
|
|
1 |
# helper.py
|
2 |
import yfinance as yf
|
|
|
|
|
3 |
import pandas as pd
|
4 |
from typing import List, Tuple, Dict
|
5 |
|
@@ -23,6 +25,31 @@ def download_stock_data(
|
|
23 |
return data["Adj Close"]
|
24 |
|
25 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
26 |
def create_portfolio_and_calculate_returns(
|
27 |
stock_data: pd.DataFrame, top_n: int
|
28 |
) -> pd.DataFrame:
|
@@ -75,3 +102,184 @@ def create_portfolio_and_calculate_returns(
|
|
75 |
).cumprod()
|
76 |
|
77 |
return new_returns_data
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
# helper.py
|
2 |
import yfinance as yf
|
3 |
+
import matplotlib.pyplot as plt
|
4 |
+
import numpy as np
|
5 |
import pandas as pd
|
6 |
from typing import List, Tuple, Dict
|
7 |
|
|
|
25 |
return data["Adj Close"]
|
26 |
|
27 |
|
28 |
+
def download_stocks(tickers: List[str]) -> List[pd.DataFrame]:
|
29 |
+
"""
|
30 |
+
Downloads stock data from Yahoo Finance.
|
31 |
+
|
32 |
+
Args:
|
33 |
+
tickers: A list of stock tickers.
|
34 |
+
|
35 |
+
Returns:
|
36 |
+
A list of Pandas DataFrames, one for each stock.
|
37 |
+
"""
|
38 |
+
|
39 |
+
# Create a list of DataFrames.
|
40 |
+
df_list = []
|
41 |
+
|
42 |
+
# Iterate over the tickers.
|
43 |
+
for ticker in tickers:
|
44 |
+
# Download the stock data.
|
45 |
+
df = yf.download(ticker)
|
46 |
+
|
47 |
+
# Add the DataFrame to the list.
|
48 |
+
df_list.append(df.tail(255 * 8))
|
49 |
+
|
50 |
+
return df_list
|
51 |
+
|
52 |
+
|
53 |
def create_portfolio_and_calculate_returns(
|
54 |
stock_data: pd.DataFrame, top_n: int
|
55 |
) -> pd.DataFrame:
|
|
|
102 |
).cumprod()
|
103 |
|
104 |
return new_returns_data
|
105 |
+
|
106 |
+
|
107 |
+
def portfolio_annualised_performance(
|
108 |
+
weights: np.ndarray, mean_returns: np.ndarray, cov_matrix: np.ndarray
|
109 |
+
) -> Tuple[float, float]:
|
110 |
+
"""
|
111 |
+
Given the weights of the assets in the portfolio, their mean returns, and their covariance matrix,
|
112 |
+
this function computes and returns the annualized performance of the portfolio in terms of its
|
113 |
+
standard deviation (volatility) and expected returns.
|
114 |
+
|
115 |
+
Args:
|
116 |
+
weights (np.ndarray): The weights of the assets in the portfolio.
|
117 |
+
Each weight corresponds to the proportion of the investor's total
|
118 |
+
investment in the corresponding asset.
|
119 |
+
|
120 |
+
mean_returns (np.ndarray): The mean (expected) returns of the assets.
|
121 |
+
|
122 |
+
cov_matrix (np.ndarray): The covariance matrix of the asset returns. Each entry at the
|
123 |
+
intersection of a row and a column represents the covariance
|
124 |
+
between the returns of the asset corresponding to that row
|
125 |
+
and the asset corresponding to that column.
|
126 |
+
|
127 |
+
Returns:
|
128 |
+
Tuple of portfolio volatility (standard deviation) and portfolio expected return, both annualized.
|
129 |
+
"""
|
130 |
+
|
131 |
+
# Annualize portfolio returns by summing up the products of the mean returns and weights of each asset and then multiplying by 252
|
132 |
+
# (number of trading days in a year)
|
133 |
+
returns = np.sum(mean_returns * weights) * 252
|
134 |
+
|
135 |
+
# Compute portfolio volatility (standard deviation) by dot multiplying the weights transpose and the dot product of covariance matrix
|
136 |
+
# and weights. Then take the square root to get the standard deviation and multiply by square root of 252 to annualize it.
|
137 |
+
std = np.sqrt(np.dot(weights.T, np.dot(cov_matrix, weights))) * np.sqrt(252)
|
138 |
+
|
139 |
+
return std, returns
|
140 |
+
|
141 |
+
|
142 |
+
def random_portfolios(
|
143 |
+
num_portfolios: int,
|
144 |
+
num_weights: int,
|
145 |
+
mean_returns: np.ndarray,
|
146 |
+
cov_matrix: np.ndarray,
|
147 |
+
risk_free_rate: float,
|
148 |
+
) -> Tuple[np.ndarray, List[np.ndarray]]:
|
149 |
+
"""
|
150 |
+
Generate random portfolios and calculate their standard deviation, returns and Sharpe ratio.
|
151 |
+
|
152 |
+
Args:
|
153 |
+
num_portfolios (int): The number of random portfolios to generate.
|
154 |
+
|
155 |
+
mean_returns (np.ndarray): The mean (expected) returns of the assets.
|
156 |
+
|
157 |
+
cov_matrix (np.ndarray): The covariance matrix of the asset returns. Each entry at the
|
158 |
+
intersection of a row and a column represents the covariance
|
159 |
+
between the returns of the asset corresponding to that row
|
160 |
+
and the asset corresponding to that column.
|
161 |
+
|
162 |
+
risk_free_rate (float): The risk-free rate of return.
|
163 |
+
|
164 |
+
Returns:
|
165 |
+
Tuple of results and weights_record.
|
166 |
+
|
167 |
+
results (np.ndarray): A 3D array with standard deviation, returns and Sharpe ratio of the portfolios.
|
168 |
+
|
169 |
+
weights_record (List[np.ndarray]): A list with the weights of the assets in each portfolio.
|
170 |
+
"""
|
171 |
+
# Initialize results array with zeros
|
172 |
+
results = np.zeros((3, num_portfolios))
|
173 |
+
|
174 |
+
# Initialize weights record list
|
175 |
+
weights_record = []
|
176 |
+
|
177 |
+
# Loop over the range of num_portfolios
|
178 |
+
for i in np.arange(num_portfolios):
|
179 |
+
# Generate random weights
|
180 |
+
weights = np.random.random(num_weights)
|
181 |
+
|
182 |
+
# Normalize weights
|
183 |
+
weights /= np.sum(weights)
|
184 |
+
|
185 |
+
# Record weights
|
186 |
+
weights_record.append(weights)
|
187 |
+
|
188 |
+
# Calculate portfolio standard deviation and returns
|
189 |
+
portfolio_std_dev, portfolio_return = portfolio_annualised_performance(
|
190 |
+
weights, mean_returns, cov_matrix
|
191 |
+
)
|
192 |
+
|
193 |
+
# Store standard deviation, returns and Sharpe ratio in results
|
194 |
+
results[0, i] = portfolio_std_dev
|
195 |
+
results[1, i] = portfolio_return
|
196 |
+
results[2, i] = (portfolio_return - risk_free_rate) / portfolio_std_dev
|
197 |
+
|
198 |
+
return results, weights_record
|
199 |
+
|
200 |
+
|
201 |
+
def display_simulated_ef_with_random(
|
202 |
+
table: pd.DataFrame,
|
203 |
+
mean_returns: List[float],
|
204 |
+
cov_matrix: np.ndarray,
|
205 |
+
num_portfolios: int,
|
206 |
+
risk_free_rate: float,
|
207 |
+
) -> plt.Figure:
|
208 |
+
"""
|
209 |
+
This function displays a simulated efficient frontier plot based on randomly generated portfolios with the specified parameters.
|
210 |
+
|
211 |
+
Args:
|
212 |
+
- mean_returns (List): A list of mean returns for each security or asset in the portfolio.
|
213 |
+
- cov_matrix (ndarray): A covariance matrix for the securities or assets in the portfolio.
|
214 |
+
- num_portfolios (int): The number of random portfolios to generate.
|
215 |
+
- risk_free_rate (float): The risk-free rate of return.
|
216 |
+
|
217 |
+
Returns:
|
218 |
+
- fig (plt.Figure): A pyplot figure object
|
219 |
+
"""
|
220 |
+
|
221 |
+
# Generate random portfolios using the specified parameters
|
222 |
+
results, weights = random_portfolios(
|
223 |
+
num_portfolios, len(mean_returns), mean_returns, cov_matrix, risk_free_rate
|
224 |
+
)
|
225 |
+
|
226 |
+
# Find the maximum Sharpe ratio portfolio and the portfolio with minimum volatility
|
227 |
+
max_sharpe_idx = np.argmax(results[2])
|
228 |
+
sdp, rp = results[0, max_sharpe_idx], results[1, max_sharpe_idx]
|
229 |
+
|
230 |
+
# Create a DataFrame of the maximum Sharpe ratio allocation
|
231 |
+
max_sharpe_allocation = pd.DataFrame(
|
232 |
+
weights[max_sharpe_idx], index=table.columns, columns=["allocation"]
|
233 |
+
)
|
234 |
+
max_sharpe_allocation.allocation = [
|
235 |
+
round(i * 100, 2) for i in max_sharpe_allocation.allocation
|
236 |
+
]
|
237 |
+
max_sharpe_allocation = max_sharpe_allocation.T
|
238 |
+
|
239 |
+
# Find index of the portfolio with minimum volatility
|
240 |
+
min_vol_idx = np.argmin(results[0])
|
241 |
+
sdp_min, rp_min = results[0, min_vol_idx], results[1, min_vol_idx]
|
242 |
+
|
243 |
+
# Create a DataFrame of the minimum volatility allocation
|
244 |
+
min_vol_allocation = pd.DataFrame(
|
245 |
+
weights[min_vol_idx], index=table.columns, columns=["allocation"]
|
246 |
+
)
|
247 |
+
min_vol_allocation.allocation = [
|
248 |
+
round(i * 100, 2) for i in min_vol_allocation.allocation
|
249 |
+
]
|
250 |
+
min_vol_allocation = min_vol_allocation.T
|
251 |
+
|
252 |
+
# Generate and plot the efficient frontier
|
253 |
+
fig, ax = plt.subplots(figsize=(10, 7))
|
254 |
+
ax.scatter(
|
255 |
+
results[0, :],
|
256 |
+
results[1, :],
|
257 |
+
c=results[2, :],
|
258 |
+
cmap="YlGnBu",
|
259 |
+
marker="o",
|
260 |
+
s=10,
|
261 |
+
alpha=0.3,
|
262 |
+
)
|
263 |
+
ax.scatter(sdp, rp, marker="*", color="r", s=500, label="Maximum Sharpe ratio")
|
264 |
+
ax.scatter(
|
265 |
+
sdp_min, rp_min, marker="*", color="g", s=500, label="Minimum volatility"
|
266 |
+
)
|
267 |
+
ax.set_title("Simulated Portfolio Optimization based on Efficient Frontier")
|
268 |
+
ax.set_xlabel("Annual volatility")
|
269 |
+
ax.set_ylabel("Annual returns")
|
270 |
+
ax.legend(labelspacing=0.8)
|
271 |
+
|
272 |
+
return fig, {
|
273 |
+
"Annualised Return": round(rp, 2),
|
274 |
+
"Annualised Volatility": round(sdp, 2),
|
275 |
+
"Max Sharpe Allocation": max_sharpe_allocation,
|
276 |
+
"Max Sharpe Allocation in Percentile": max_sharpe_allocation.div(
|
277 |
+
max_sharpe_allocation.sum(axis=1), axis=0
|
278 |
+
),
|
279 |
+
"Annualised Return": round(rp_min, 2),
|
280 |
+
"Annualised Volatility": round(sdp_min, 2),
|
281 |
+
"Min Volatility Allocation": min_vol_allocation,
|
282 |
+
"Min Volatility Allocation in Percentile": min_vol_allocation.div(
|
283 |
+
min_vol_allocation.sum(axis=1), axis=0
|
284 |
+
),
|
285 |
+
}
|
utils/ui_helper.py
CHANGED
@@ -77,6 +77,7 @@ def main_algo_trader():
|
|
77 |
return df.to_csv().encode("utf-8")
|
78 |
|
79 |
csv = convert_df(df)
|
|
|
80 |
|
81 |
# Create plot
|
82 |
fig = go.Figure()
|
@@ -171,6 +172,14 @@ def main_algo_trader():
|
|
171 |
mime="text/csv",
|
172 |
)
|
173 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
174 |
|
175 |
# chinese
|
176 |
def main_algo_trader_chinese():
|
|
|
77 |
return df.to_csv().encode("utf-8")
|
78 |
|
79 |
csv = convert_df(df)
|
80 |
+
recent_selected_stocks = df['portfolio_history'][-1]
|
81 |
|
82 |
# Create plot
|
83 |
fig = go.Figure()
|
|
|
172 |
mime="text/csv",
|
173 |
)
|
174 |
|
175 |
+
# Checkpoint: ask user whether they want portfolio weights
|
176 |
+
if csv:
|
177 |
+
agree = st.sidebar.checkbox('Check the box if you want the weights.')
|
178 |
+
|
179 |
+
if agree:
|
180 |
+
#TODO
|
181 |
+
st.markdown(recent_selected_stocks)
|
182 |
+
|
183 |
|
184 |
# chinese
|
185 |
def main_algo_trader_chinese():
|