Create advisor.py
Browse files- 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
|