eagle0504 commited on
Commit
744b222
1 Parent(s): 4232961

Create advisor.py

Browse files
Files changed (1) hide show
  1. utils/advisor.py +452 -0
utils/advisor.py ADDED
@@ -0,0 +1,452 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from datetime import datetime
2
+ from typing import List, Tuple
3
+
4
+ import matplotlib.pyplot as plt
5
+ import numpy as np
6
+ import pandas as pd
7
+ import plotly.express as px
8
+ import plotly.graph_objects as go
9
+ from plotly.subplots import make_subplots
10
+ import streamlit as st
11
+ import yfinance as yf
12
+ from ta.momentum import RSIIndicator
13
+
14
+
15
+ def download_stocks(tickers: List[str]) -> List[pd.DataFrame]:
16
+ """
17
+ Downloads stock data from Yahoo Finance.
18
+
19
+ Args:
20
+ tickers: A list of stock tickers.
21
+
22
+ Returns:
23
+ A list of Pandas DataFrames, one for each stock.
24
+ """
25
+
26
+ # Create a list of DataFrames.
27
+ df_list = []
28
+
29
+ # Iterate over the tickers.
30
+ for ticker in tickers:
31
+ # Download the stock data.
32
+ df = yf.download(ticker)
33
+
34
+ # Add the DataFrame to the list.
35
+ df_list.append(df.tail(255 * 8))
36
+
37
+ return df_list
38
+
39
+
40
+ def plot_mkt_cap(df: pd.DataFrame) -> px.treemap:
41
+ """Takes in a DataFrame of stock information and plots market cap treemap
42
+
43
+ Args:
44
+ df: pandas DataFrame containing the following columns - ticker, sector, market_cap, colors, delta
45
+
46
+ Returns:
47
+ fig : Plotly express treemap figure object showing the market cap and color-coded
48
+ according to the input "colors" column.
49
+ """
50
+ # Build and return the treemap figure
51
+ fig = px.treemap(
52
+ df,
53
+ path=[px.Constant("all"), "sector", "ticker"],
54
+ values="market_cap",
55
+ color="colors",
56
+ hover_data={"delta": ":.2p"},
57
+ )
58
+ return fig
59
+
60
+
61
+ def plot_returns(table: pd.DataFrame) -> plt.Figure:
62
+ """
63
+ This function plots the daily returns of each stock contained in the DataFrame `table`.
64
+
65
+ Returns:
66
+ fig: A `Figure` instance representing the entire figure.
67
+ """
68
+ # Calculate the daily percentage change of all stocks using the `pct_change` method.
69
+ returns = table.pct_change()
70
+
71
+ # Plot each stock's daily returns on the same graph using a for loop and the `plot` method of pyplot object.
72
+ fig, ax = plt.subplots(figsize=(14, 7))
73
+ for c in returns.columns.values:
74
+ ax.plot(returns.index, returns[c], lw=3, alpha=0.8, label=c)
75
+
76
+ # Add legend and y-axis label to the plot.
77
+ ax.legend(loc="upper right", fontsize=12)
78
+ ax.set_ylabel("daily returns")
79
+
80
+ return fig
81
+
82
+
83
+ def portfolio_annualised_performance(
84
+ weights: np.ndarray, mean_returns: np.ndarray, cov_matrix: np.ndarray
85
+ ) -> Tuple[float, float]:
86
+ """
87
+ Given the weights of the assets in the portfolio, their mean returns, and their covariance matrix,
88
+ this function computes and returns the annualized performance of the portfolio in terms of its
89
+ standard deviation (volatility) and expected returns.
90
+
91
+ Args:
92
+ weights (np.ndarray): The weights of the assets in the portfolio.
93
+ Each weight corresponds to the proportion of the investor's total
94
+ investment in the corresponding asset.
95
+
96
+ mean_returns (np.ndarray): The mean (expected) returns of the assets.
97
+
98
+ cov_matrix (np.ndarray): The covariance matrix of the asset returns. Each entry at the
99
+ intersection of a row and a column represents the covariance
100
+ between the returns of the asset corresponding to that row
101
+ and the asset corresponding to that column.
102
+
103
+ Returns:
104
+ Tuple of portfolio volatility (standard deviation) and portfolio expected return, both annualized.
105
+ """
106
+
107
+ # Annualize portfolio returns by summing up the products of the mean returns and weights of each asset and then multiplying by 252
108
+ # (number of trading days in a year)
109
+ returns = np.sum(mean_returns * weights) * 252
110
+
111
+ # Compute portfolio volatility (standard deviation) by dot multiplying the weights transpose and the dot product of covariance matrix
112
+ # and weights. Then take the square root to get the standard deviation and multiply by square root of 252 to annualize it.
113
+ std = np.sqrt(np.dot(weights.T, np.dot(cov_matrix, weights))) * np.sqrt(252)
114
+
115
+ return std, returns
116
+
117
+
118
+ def random_portfolios(
119
+ num_portfolios: int,
120
+ num_weights: int,
121
+ mean_returns: np.ndarray,
122
+ cov_matrix: np.ndarray,
123
+ risk_free_rate: float,
124
+ ) -> Tuple[np.ndarray, List[np.ndarray]]:
125
+ """
126
+ Generate random portfolios and calculate their standard deviation, returns and Sharpe ratio.
127
+
128
+ Args:
129
+ num_portfolios (int): The number of random portfolios to generate.
130
+
131
+ mean_returns (np.ndarray): The mean (expected) returns of the assets.
132
+
133
+ cov_matrix (np.ndarray): The covariance matrix of the asset returns. Each entry at the
134
+ intersection of a row and a column represents the covariance
135
+ between the returns of the asset corresponding to that row
136
+ and the asset corresponding to that column.
137
+
138
+ risk_free_rate (float): The risk-free rate of return.
139
+
140
+ Returns:
141
+ Tuple of results and weights_record.
142
+
143
+ results (np.ndarray): A 3D array with standard deviation, returns and Sharpe ratio of the portfolios.
144
+
145
+ weights_record (List[np.ndarray]): A list with the weights of the assets in each portfolio.
146
+ """
147
+ # Initialize results array with zeros
148
+ results = np.zeros((3, num_portfolios))
149
+
150
+ # Initialize weights record list
151
+ weights_record = []
152
+
153
+ # Loop over the range of num_portfolios
154
+ for i in np.arange(num_portfolios):
155
+ # Generate random weights
156
+ weights = np.random.random(num_weights)
157
+
158
+ # Normalize weights
159
+ weights /= np.sum(weights)
160
+
161
+ # Record weights
162
+ weights_record.append(weights)
163
+
164
+ # Calculate portfolio standard deviation and returns
165
+ portfolio_std_dev, portfolio_return = portfolio_annualised_performance(
166
+ weights, mean_returns, cov_matrix
167
+ )
168
+
169
+ # Store standard deviation, returns and Sharpe ratio in results
170
+ results[0, i] = portfolio_std_dev
171
+ results[1, i] = portfolio_return
172
+ results[2, i] = (portfolio_return - risk_free_rate) / portfolio_std_dev
173
+
174
+ return results, weights_record
175
+
176
+
177
+ def display_simulated_ef_with_random(
178
+ table: pd.DataFrame,
179
+ mean_returns: List[float],
180
+ cov_matrix: np.ndarray,
181
+ num_portfolios: int,
182
+ risk_free_rate: float,
183
+ ) -> plt.Figure:
184
+ """
185
+ This function displays a simulated efficient frontier plot based on randomly generated portfolios with the specified parameters.
186
+
187
+ Args:
188
+ - mean_returns (List): A list of mean returns for each security or asset in the portfolio.
189
+ - cov_matrix (ndarray): A covariance matrix for the securities or assets in the portfolio.
190
+ - num_portfolios (int): The number of random portfolios to generate.
191
+ - risk_free_rate (float): The risk-free rate of return.
192
+
193
+ Returns:
194
+ - fig (plt.Figure): A pyplot figure object
195
+ """
196
+
197
+ # Generate random portfolios using the specified parameters
198
+ results, weights = random_portfolios(
199
+ num_portfolios, len(mean_returns), mean_returns, cov_matrix, risk_free_rate
200
+ )
201
+
202
+ # Find the maximum Sharpe ratio portfolio and the portfolio with minimum volatility
203
+ max_sharpe_idx = np.argmax(results[2])
204
+ sdp, rp = results[0, max_sharpe_idx], results[1, max_sharpe_idx]
205
+
206
+ # Create a DataFrame of the maximum Sharpe ratio allocation
207
+ max_sharpe_allocation = pd.DataFrame(
208
+ weights[max_sharpe_idx], index=table.columns, columns=["allocation"]
209
+ )
210
+ max_sharpe_allocation.allocation = [
211
+ round(i * 100, 2) for i in max_sharpe_allocation.allocation
212
+ ]
213
+ max_sharpe_allocation = max_sharpe_allocation.T
214
+
215
+ # Find index of the portfolio with minimum volatility
216
+ min_vol_idx = np.argmin(results[0])
217
+ sdp_min, rp_min = results[0, min_vol_idx], results[1, min_vol_idx]
218
+
219
+ # Create a DataFrame of the minimum volatility allocation
220
+ min_vol_allocation = pd.DataFrame(
221
+ weights[min_vol_idx], index=table.columns, columns=["allocation"]
222
+ )
223
+ min_vol_allocation.allocation = [
224
+ round(i * 100, 2) for i in min_vol_allocation.allocation
225
+ ]
226
+ min_vol_allocation = min_vol_allocation.T
227
+
228
+ # Generate and plot the efficient frontier
229
+ fig, ax = plt.subplots(figsize=(10, 7))
230
+ ax.scatter(
231
+ results[0, :],
232
+ results[1, :],
233
+ c=results[2, :],
234
+ cmap="YlGnBu",
235
+ marker="o",
236
+ s=10,
237
+ alpha=0.3,
238
+ )
239
+ ax.scatter(sdp, rp, marker="*", color="r", s=500, label="Maximum Sharpe ratio")
240
+ ax.scatter(
241
+ sdp_min, rp_min, marker="*", color="g", s=500, label="Minimum volatility"
242
+ )
243
+ ax.set_title("Simulated Portfolio Optimization based on Efficient Frontier")
244
+ ax.set_xlabel("Annual volatility")
245
+ ax.set_ylabel("Annual returns")
246
+ ax.legend(labelspacing=0.8)
247
+
248
+ return fig, {
249
+ "Annualised Return (efficient portfolio)": round(rp, 2),
250
+ "Annualised Volatility (efficient portfolio)": round(sdp, 2),
251
+ "Max Sharpe Allocation": max_sharpe_allocation,
252
+ "Max Sharpe Allocation in Percentile": max_sharpe_allocation.div(
253
+ max_sharpe_allocation.sum(axis=1), axis=0
254
+ ),
255
+ "Annualised Return (min variance portfolio)": round(rp_min, 2),
256
+ "Annualised Volatility (min variance portfolio)": round(sdp_min, 2),
257
+ "Min Volatility Allocation": min_vol_allocation,
258
+ "Min Volatility Allocation in Percentile": min_vol_allocation.div(
259
+ min_vol_allocation.sum(axis=1), axis=0
260
+ ),
261
+ }
262
+
263
+
264
+ def entry_strategy(
265
+ start_date="2013-01-01",
266
+ end_date="2019-12-6",
267
+ tickers="AAPL",
268
+ thresholds="10, 20, 30",
269
+ buy_threshold=20,
270
+ sell_threshold=80,
271
+ ):
272
+ rsi_threshold_1 = int(thresholds.split(",")[0])
273
+ rsi_threshold_2 = int(thresholds.split(",")[1])
274
+ rsi_threshold_3 = int(thresholds.split(",")[2])
275
+
276
+ # Conditional Buy/Sell => Signals
277
+ stock = yf.download(tickers, start_date, end_date)
278
+ rsiData1 = RSIIndicator(stock["Close"], rsi_threshold_1, True)
279
+ rsiData2 = RSIIndicator(stock["Close"], rsi_threshold_2, True)
280
+ rsiData3 = RSIIndicator(stock["Close"], rsi_threshold_3, True)
281
+
282
+ # Conditional Buy/Sell => Signals
283
+ conditionalBuy1 = np.where(rsiData1.rsi() < buy_threshold, stock["Close"], np.nan)
284
+ conditionalSell1 = np.where(rsiData1.rsi() > sell_threshold, stock["Close"], np.nan)
285
+ conditionalBuy2 = np.where(rsiData2.rsi() < buy_threshold, stock["Close"], np.nan)
286
+ conditionalSell2 = np.where(rsiData2.rsi() > sell_threshold, stock["Close"], np.nan)
287
+ conditionalBuy3 = np.where(rsiData3.rsi() < buy_threshold, stock["Close"], np.nan)
288
+ conditionalSell3 = np.where(rsiData3.rsi() > sell_threshold, stock["Close"], np.nan)
289
+
290
+ # RSI Construction
291
+ stock["RSI1"] = rsiData1.rsi()
292
+ stock["RSI2"] = rsiData2.rsi()
293
+ stock["RSI3"] = rsiData3.rsi()
294
+ stock["RSI1_Buy"] = conditionalBuy1
295
+ stock["RSI1_Sell"] = conditionalSell1
296
+ stock["RSI2_Buy"] = conditionalBuy2
297
+ stock["RSI2_Sell"] = conditionalSell2
298
+ stock["RSI3_Buy"] = conditionalBuy3
299
+ stock["RSI3_Sell"] = conditionalSell3
300
+
301
+ strategy = "RSI"
302
+ title = f"Close Price Buy/Sell Signals using WYN Entry Strategy"
303
+
304
+ fig, axs = plt.subplots(2, sharex=True, figsize=(13, 9))
305
+
306
+ if not stock["RSI1_Buy"].isnull().all():
307
+ axs[0].scatter(
308
+ stock.index,
309
+ stock["RSI1_Buy"],
310
+ color="green",
311
+ label="Buy Signal 1",
312
+ marker="^",
313
+ alpha=1,
314
+ )
315
+ if not stock["RSI1_Sell"].isnull().all():
316
+ axs[0].scatter(
317
+ stock.index,
318
+ stock["RSI1_Sell"],
319
+ color="red",
320
+ label="Sell Signal 1",
321
+ marker="v",
322
+ alpha=1,
323
+ )
324
+ axs[0].plot(stock["Close"], label="Close Price", color="blue", alpha=0.35)
325
+
326
+ if not stock["RSI2_Buy"].isnull().all():
327
+ axs[0].scatter(
328
+ stock.index,
329
+ stock["RSI2_Buy"],
330
+ color="blue",
331
+ label="Buy Signal 2",
332
+ marker="^",
333
+ alpha=1,
334
+ )
335
+ if not stock["RSI2_Sell"].isnull().all():
336
+ axs[0].scatter(
337
+ stock.index,
338
+ stock["RSI2_Sell"],
339
+ color="purple",
340
+ label="Sell Signal 2",
341
+ marker="v",
342
+ alpha=1,
343
+ )
344
+ axs[0].plot(stock["Close"], label="Close Price", color="blue", alpha=0.35)
345
+
346
+ if not stock["RSI3_Buy"].isnull().all():
347
+ axs[0].scatter(
348
+ stock.index,
349
+ stock["RSI3_Buy"],
350
+ color="cyan",
351
+ label="Buy Signal 3",
352
+ marker="^",
353
+ alpha=1,
354
+ )
355
+ if not stock["RSI3_Sell"].isnull().all():
356
+ axs[0].scatter(
357
+ stock.index,
358
+ stock["RSI3_Sell"],
359
+ color="pink",
360
+ label="Sell Signal 3",
361
+ marker="v",
362
+ alpha=1,
363
+ )
364
+ axs[0].plot(stock["Close"], label="Close Price", color="blue", alpha=0.35)
365
+
366
+ # plt.xticks(rotation=45)
367
+ axs[0].set_title(title)
368
+ axs[0].set_xlabel("Date", fontsize=18)
369
+ axs[0].set_ylabel("Close Price", fontsize=18)
370
+ axs[0].legend(loc="upper left")
371
+ axs[0].grid()
372
+
373
+ axs[1].plot(stock["RSI1"], label="RSI", color="green")
374
+ axs[1].plot(stock["RSI2"], label="RSI", color="blue")
375
+ axs[1].plot(stock["RSI3"], label="RSI", color="red")
376
+
377
+ return fig
378
+
379
+
380
+ def entry_strategy_plotly(
381
+ start_date="2013-01-01",
382
+ end_date="2019-12-6",
383
+ tickers="AAPL",
384
+ thresholds="10, 20, 30",
385
+ buy_threshold=20,
386
+ sell_threshold=80,
387
+ ):
388
+ rsi_threshold_1 = int(thresholds.split(",")[0])
389
+ rsi_threshold_2 = int(thresholds.split(",")[1])
390
+ rsi_threshold_3 = int(thresholds.split(",")[2])
391
+
392
+ # Conditional Buy/Sell => Signals
393
+ stock = yf.download(tickers, start_date, end_date)
394
+ rsiData1 = RSIIndicator(stock["Close"], rsi_threshold_1, True)
395
+ rsiData2 = RSIIndicator(stock["Close"], rsi_threshold_2, True)
396
+ rsiData3 = RSIIndicator(stock["Close"], rsi_threshold_3, True)
397
+
398
+ # Conditional Buy/Sell => Signals
399
+ stock["RSI1_Buy"] = np.where(rsiData1.rsi() < buy_threshold, stock["Close"], np.nan)
400
+ stock["RSI1_Sell"] = np.where(rsiData1.rsi() > sell_threshold, stock["Close"], np.nan)
401
+ stock["RSI2_Buy"] = np.where(rsiData2.rsi() < buy_threshold, stock["Close"], np.nan)
402
+ stock["RSI2_Sell"] = np.where(rsiData2.rsi() > sell_threshold, stock["Close"], np.nan)
403
+ stock["RSI3_Buy"] = np.where(rsiData3.rsi() < buy_threshold, stock["Close"], np.nan)
404
+ stock["RSI3_Sell"] = np.where(rsiData3.rsi() > sell_threshold, stock["Close"], np.nan)
405
+
406
+ # Create a subplot figure with secondary Y-axis
407
+ fig = make_subplots(specs=[[{"secondary_y": True}]])
408
+
409
+ # Add traces for close prices
410
+ fig.add_trace(go.Scatter(x=stock.index, y=stock["Close"], name="Close Price", line=dict(color='blue', width=0.5)), secondary_y=False)
411
+
412
+ # Add traces for buy and sell signals
413
+ fig.add_trace(go.Scatter(x=stock.index, y=stock["RSI1_Buy"], mode='markers', name='Buy Signal (light)', marker=dict(color='green', size=6, symbol='triangle-up')), secondary_y=False)
414
+ fig.add_trace(go.Scatter(x=stock.index, y=stock["RSI1_Sell"], mode='markers', name='Sell Signal (light)', marker=dict(color='red', size=6, symbol='triangle-down')), secondary_y=False)
415
+ fig.add_trace(go.Scatter(x=stock.index, y=stock["RSI2_Buy"], mode='markers', name='Buy Signal (medium)', marker=dict(color='blue', size=6, symbol='triangle-up')), secondary_y=False)
416
+ fig.add_trace(go.Scatter(x=stock.index, y=stock["RSI2_Sell"], mode='markers', name='Sell Signal (medium)', marker=dict(color='purple', size=6, symbol='triangle-down')), secondary_y=False)
417
+ fig.add_trace(go.Scatter(x=stock.index, y=stock["RSI3_Buy"], mode='markers', name='Buy Signal (heavy)', marker=dict(color='cyan', size=6, symbol='triangle-up')), secondary_y=False)
418
+ fig.add_trace(go.Scatter(x=stock.index, y=stock["RSI3_Sell"], mode='markers', name='Sell Signal (heavy)', marker=dict(color='pink', size=6, symbol='triangle-down')), secondary_y=False)
419
+
420
+ # Add traces for RSI
421
+ # fig.add_trace(go.Scatter(x=stock.index, y=rsiData1.rsi(), name="RSI 1", line=dict(color='green')), secondary_y=True)
422
+ # fig.add_trace(go.Scatter(x=stock.index, y=rsiData2.rsi(), name="RSI 2", line=dict(color='blue')), secondary_y=True)
423
+ # fig.add_trace(go.Scatter(x=stock.index, y=rsiData3.rsi(), name="RSI 3", line=dict(color='red')), secondary_y=True)
424
+
425
+ # Set figure title, and axis titles
426
+ fig.update_layout(title_text='Close Price Buy/Sell Signals using WYN Entry Strategy')
427
+ fig.update_xaxes(title_text='Date')
428
+ fig.update_yaxes(title_text='<b>Close Price</b>', secondary_y=False)
429
+ fig.update_yaxes(title_text='<b>RSI</b>', secondary_y=True)
430
+
431
+ return fig
432
+
433
+
434
+ def get_stock_info(ticker: str) -> dict:
435
+ # Get More Data:
436
+ tck = yf.Ticker(ticker)
437
+ ALL_DATA = {
438
+ 'get stock info': tck.info,
439
+ 'get historical market data': tck.history(period="max"),
440
+ 'show actions (dividends, splits)': tck.actions,
441
+ 'show dividends': tck.dividends,
442
+ 'show splits': tck.splits,
443
+ 'show financials': [tck.financials, tck.quarterly_financials],
444
+ 'show balance sheet': [tck.balance_sheet, tck.quarterly_balance_sheet],
445
+ 'show cashflow': [tck.cashflow, tck.quarterly_cashflow],
446
+ # 'show earnings': [tck.earnings, tck.quarterly_earnings],
447
+ # 'show sustainability': tck.sustainability,
448
+ # 'show analysts recommendations': tck.recommendations,
449
+ # 'show next event (earnings, etc)': tck.calendar
450
+ }
451
+
452
+ return ALL_DATA