File size: 20,372 Bytes
cf8654f
 
 
 
 
 
 
 
 
 
 
 
 
bd13973
 
 
cf8654f
 
 
 
 
 
 
 
 
f382b03
cf8654f
 
 
 
 
 
 
 
47d9d9d
cf8654f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d6722d1
 
cf8654f
 
 
 
 
 
 
1ae4c6b
 
 
cf8654f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2f29283
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cf8654f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3514602
cf8654f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2670a18
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cf8654f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47d9d9d
f9c308a
 
 
 
 
21f83f6
dfb854a
f9c308a
 
eea824e
f9c308a
cf8654f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3514602
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
import os
from datetime import datetime
from typing import List, Tuple

import matplotlib.pyplot as plt
import numpy as np
import openai
import pandas as pd
import plotly.express as px
import streamlit as st
import yfinance as yf
from ta.momentum import RSIIndicator

from utils.advisor import *
from utils.advisor import entry_strategy_plotly, get_stock_info
from utils.my_openai import call_chatcompletion

# Set the OpenAI API key from Streamlit secrets
openai.api_key = os.environ["OPENAI_API_KEY"]

# Set up Title
st.set_page_config(page_title="WYN AI", page_icon="💹", layout="wide")

st.markdown(
    f"""
        <h1 style='text-align: center;'>W.Y.N. Portfolio Management 🤖</h1>
    """,
    unsafe_allow_html=True,
)

# Set up Sidebar
st.sidebar.title("Sidebar")
option = st.sidebar.selectbox(
    "Which strategy do you want to see?",
    ("--", "Portfolio Management", "Entry Strategy", "Interactive Timing Strategy"),
)
# st.sidebar.write("You selected:", option)

# More sidebar
if option == "Portfolio Management":
    stocks = st.sidebar.text_input(
        "Enter stocks (sep. by comma and space, e.g. ', ')",
        "AAPL, META, TSLA, AMZN, AMD, NVDA, TSM, MSFT, GOOGL, NFLX",
    )
    # st.sidebar.write("Discalimer: The first 10 are held by Yiqiao Yin.")
    start_datetime = st.sidebar.date_input("Start date", datetime(2012, 1, 1))
    end_datetime = st.sidebar.date_input("End date", datetime.today())
    st.sidebar.write(
        "Range selected: from ",
        str(start_datetime).split(" ")[0],
        " to ",
        str(end_datetime).split(" ")[0],
    )
    num_portfolios = st.sidebar.select_slider(
        "Select total number of portfolios to similuate",
        value=5000,
        options=[10, 100, 200, 500, 1000, 2000, 5000, 8000, 10000],
    )
    risk_free_rate = st.sidebar.select_slider(
        "Select simulated risk-free rate",
        value=0.01,
        options=[0.005, 0.01, 0.015, 0.02, 0.025, 0.03, 0.035, 0.04, 0.045, 0.05],
    )
    with st.sidebar:
        with st.form(key="my_form"):
            submit_button = st.form_submit_button(label="Submit!")
elif option == "Entry Strategy":
    start_datetime = st.sidebar.date_input("Start date", datetime(2010, 1, 1))
    end_datetime = st.sidebar.date_input("End date", datetime.today())
    this_stock = st.sidebar.text_input("Enter a ticker of a stock you like:", "AAPL")
    rsi_thresholds = st.sidebar.text_input(
        "Enter 3 integers for number of past days to construct RSI (sep. by comma and space):",
        "12, 26, 50",
    )
    thresholds_values = st.sidebar.slider(
        "Select a range of values to infer margin of error", 0.0, 100.0, (25.0, 75.0)
    )
    with st.sidebar:
        with st.form("Submit"):
            submit_button = st.form_submit_button(label="Submit!")
else:
    st.sidebar.write("Please pick an option from above.")

# Credits
st.sidebar.markdown(
    "© [Yiqiao Yin](https://www.y-yin.io/) | [LinkedIn](https://www.linkedin.com/in/yiqiaoyin/) | [YouTube](https://youtube.com/YiqiaoYin/)"
)
st.sidebar.success(
    "On a macro level, I'd adivse to run this app, which is [Momentum Strategy](https://huggingface.co/spaces/eagle0504/Momentum-Strategy-Screener), first. Then take the selected stocks to the second app, [Modern Portfolio Theory Demo](https://huggingface.co/spaces/eagle0504/MPT-Demo), and run again to assess reward-risk ratio."
)


# Content starts here
if option == "Portfolio Management":
    if submit_button:
        col1, col2 = st.columns(2)

        # List `stocks` is a string of comma-separated stock symbols
        stocks = stocks.split(", ")

        with col1:
            # Get the list of stocks data using the `download_stocks` function
            list_of_stocks = download_stocks(stocks)
            st.sidebar.success("Downloading latest stock data successfully!")

            # Create a DataFrame object from the closing prices of all stocks
            try:
                # Assuming list_of_stocks is the list of stock data (each item is a DataFrame with 'Close' column)
                table_data = [list_of_stocks[j]["Close"] for j in range(len(list_of_stocks))]
                
                # Check if the data is 2D (even though it's 1D data in each row, we need to check the overall shape)
                if len(table_data) > 0 and isinstance(table_data[0], pd.Series):
                    table_data = np.array(table_data).T  # Transpose to make sure it's 2D
                
                # Create the DataFrame with proper shape
                table = pd.DataFrame(table_data)

                # Set the column names to be the stock symbols
                table.columns = stocks

            except ValueError as e:
                st.error(f"Error while creating the DataFrame: {e}")
                # Gracefully handle the error by showing a fallback or empty DataFrame
                table = pd.DataFrame(columns=stocks)
                st.warning("An issue occurred while processing stock data, defaulting to an empty table.")\

            # Set the column names to be the stocks symbols
            table.columns = stocks

            # Filter by date range selected by user
            df = table
            new_index = [df.index[t].date() for t in range(len(df.index))]
            check1 = tuple(
                [new_index[t] >= start_datetime for t in range(len(new_index))]
            )
            check2 = tuple(
                [new_index[t] <= end_datetime for t in range(len(new_index))]
            )
            final_idx = [check1[t] and check2[t] for t in range(len(new_index))]
            filtered_df = df[final_idx]
            if filtered_df.shape[0] > 100:
                st.sidebar.success("Data filtered by date range selected by user.")
                table = filtered_df
            else:
                st.warning(
                    "Date range by user not valid, default range (past 2 years) is used."
                )
                table = table.tail(255 * 2)

            # Start new section: Time-series Plot
            st.markdown(
                f"""
                    <h4 style='text-align: left;'>Time Series Plot of Daily Returns</h4>
                """,
                unsafe_allow_html=True,
            )
            return_figure = plot_returns(table=table)
            st.write(
                f"""
                Plot daily returns of the stocks selected: {stocks}
            """
            )
            st.pyplot(return_figure, use_container_width=True)

            # Start new section: MPT
            st.markdown(
                f"""
                    <h4 style='text-align: center;'>Modern Portfolio Theory</h4>
                """,
                unsafe_allow_html=True,
            )
            st.markdown(
                r"""
                Among the large cap stocks, I trade a long run reversal strategy, and hence the visualization of returns from time-series plot and MPT.
                The philosophy comes from the famous [Carhart 4-Factor](https://en.wikipedia.org/wiki/Carhart_four-factor-model)
                model and the reversal strategy is captured using the 4th factor 'UMD'. If interested, one can 
                trace the algorithm proposed from this [paper](https://onlinelibrary.wiley.com/doi/10.1111/j.1540-6261.1997.tb03808.x).
                """
            )
            st.warning("What is Efficient Frontier?")
            with st.expander("Expand/collapse for references:"):
                st.markdown(
                    r"""
                    The efficient frontier is a concept in Modern Portfolio Theory. It is the set of optimal portfolios that offer the highest expected return for a defined level of risk or the lowest risk for a given level of expected return.
        
                    Mathematically, the efficient frontier is the solution to the following optimization problem:
        
                    Minimize:
                    $$ \sigma_p = \sqrt{w^T\Sigma w} $$
                    Subject to:
                    $$ R_p = w^T \mu $$
        
                    Where:
        
                    - $w$ is a vector of portfolio weights.
                    - $\Sigma$ is the covariance matrix of asset returns.
                    - $\mu$ is the vector of expected asset returns.
                    - $\sigma_p$ is the portfolio standard deviation (risk).
                    - $R_p$ is the portfolio expected return.
                    Here, $w^T$ denotes the transpose of $w$. The symbol $\sqrt{w^T\Sigma w}$ represents the standard deviation (volatility) of the portfolio returns, which is a measure of risk. The equation $R_p = w^T \mu$ states that the expected return of the portfolio should be equal to the portfolio weights times the expected returns of the individual assets.
        
                    Note: This is the simplified version of the efficient frontier. In practice, one might consider additional constraints such as no short-selling (i.e., weights must be non-negative) or a requirement that all weights sum to one.
                """
                )

            st.success("Efficient portfolio shows us what to buy (not when to buy). 💡")
            st.warning(
                "Bias (1): The time of entry is a trade secret and is completely decided by me based on personal experience. So far, I have not been able to replicate the timing strategy."
            )
            st.warning(
                "Bias (2): Though stocks are presented above, the weights decided by Mr. Yin is drastically different from the above allocation. The reason is I tend to hold the stocks that have gone up a lot and I don't like selling high growth positions for tax purposes."
            )
            st.warning(
                "Bias (3): The initial stock pool construction is also not reproducible. I mostly pick stocks from large cap brackets but occasionally break his own rules. Empirically speaking, I tend to construct my portfolio using 90-10 rule for equity and cash holdings. In the 90 percent, I have a 80-20 rule with 80 percent in large cap and 20 percent in small cap."
            )
            st.warning(
                "Bias (4): Typically asset pricing models tend to balance portfolios on a fixed time window, e.g. once a month etc.. I do not strictly follow this rule. My entry points are random because stock price is stochastic and bottoms usually appear stochasticly."
            )

        with col2:
            returns = table.pct_change()
            mean_returns = returns.mean()
            cov_matrix = returns.cov()
            eff_front_figure, some_data = display_simulated_ef_with_random(
                table, mean_returns, cov_matrix, num_portfolios, risk_free_rate
            )

            # Start new section: Efficient Portfolio
            st.markdown(
                f"""
                    <h4 style='text-align: center;'>Efficient Portfolio:</h4>
                """,
                unsafe_allow_html=True,
            )
            st.write(
                f"Annualised <span style='color:green'>Return</span> (efficient portfolio): {some_data['Annualised Return (efficient portfolio)']}",
                unsafe_allow_html=True,
            )
            st.write(
                f"Annualised <span style='color:red'>Volatility</span> (efficient portfolio): {some_data['Annualised Volatility (efficient portfolio)']}",
                unsafe_allow_html=True,
            )
            sharpe_ratio_for_eff_port = np.round(
                some_data["Annualised Return (efficient portfolio)"]
                / some_data["Annualised Volatility (efficient portfolio)"],
                3,
            )
            st.write(
                f"Annualised <span style='color:blue'>Sharpe Ratio</span> (efficient portfolio): {sharpe_ratio_for_eff_port}",
                unsafe_allow_html=True,
            )
            lower_bound_eff_port = np.round(
                some_data["Annualised Return (efficient portfolio)"]
                - 2 * some_data["Annualised Volatility (efficient portfolio)"],
                3,
            )
            upper_bound_eff_port = np.round(
                some_data["Annualised Return (efficient portfolio)"]
                + 2 * some_data["Annualised Volatility (efficient portfolio)"],
                3,
            )
            st.write(
                f"Annualised <span style='color:orange'>return confidence Interval</span> (efficient portfolio): [{lower_bound_eff_port}, {upper_bound_eff_port}]",
                unsafe_allow_html=True,
            )
            # st.write(f"Max Sharpe Allocation:")
            # st.table(some_data["Max Sharpe Allocation"])
            st.write(f"Max Sharpe Allocation in Percentile:")
            st.table(some_data["Max Sharpe Allocation in Percentile"])

            # Start new section: Min Variance Portfolio
            st.markdown(
                f"""
                    <h4 style='text-align: center;'>Min Variance Portfolio:</h4>
                """,
                unsafe_allow_html=True,
            )
            st.write(
                f"Annualised <span style='color:green'>Return</span> (min. variance portfolio): {some_data['Annualised Return (min variance portfolio)']}",
                unsafe_allow_html=True,
            )
            st.write(
                f"Annualised <span style='color:red'>Volatility</span> (min. variance portfolio): {some_data['Annualised Volatility (min variance portfolio)']}",
                unsafe_allow_html=True,
            )
            sharpe_ratio_for_mv_port = np.round(
                some_data["Annualised Return (min variance portfolio)"]
                / some_data["Annualised Volatility (min variance portfolio)"],
                3,
            )
            st.write(
                f"Annualised <span style='color:blue'>Sharpe Ratio</span> (efficient portfolio): {sharpe_ratio_for_mv_port}",
                unsafe_allow_html=True,
            )
            lower_bound_mv_port = np.round(
                some_data["Annualised Return (min variance portfolio)"]
                - 2 * some_data["Annualised Volatility (min variance portfolio)"],
                3,
            )
            upper_bound_mv_port = np.round(
                some_data["Annualised Return (min variance portfolio)"]
                + 2 * some_data["Annualised Volatility (min variance portfolio)"],
                3,
            )
            st.write(
                f"Annualised <span style='color:orange'>return confidence Interval</span> (min variance portfolio): [{lower_bound_mv_port}, {upper_bound_mv_port}]",
                unsafe_allow_html=True,
            )
            # st.write(f"Min Volatility Allocation:")
            # st.table(some_data["Min Volatility Allocation"])
            st.write(f"Min Volatility Allocation in Percentile:")
            st.table(some_data["Min Volatility Allocation in Percentile"])
            st.pyplot(eff_front_figure)

    else:
        st.warning("Please select an option and click the submit button!")
elif option == "Entry Strategy":
    if submit_button:
        tab1, tab2, tab3 = st.tabs(
            ["Overview", "Buy/Sell Signal (Interactive)", "Basic Stock Info"]
        )

        # Get data
        all_info = get_stock_info(this_stock)

        with tab1:
            st.markdown(
                r"""
                The Relative Strength Index ([RSI](https://www.investopedia.com/terms/r/rsi.asp)) 
                is a momentum oscillator that determines the pace and variation of security prices.
                It is usually depicted graphically and oscillates on a scale of zero to 100.

                The RSI oscillates on a scale of zero to 100. Low RSI levels, below 30, generate 
                buy signals and indicate an oversold or undervalued condition. High RSI levels, 
                above 70, generate sell signals and suggest that a security is overbought or 
                overvalued. A reading of 50 denotes a neutral level or balance between bullish 
                and bearish positions.

                We allow users to select the number of days for RSI (this is one of the input 
                text area on the left sidebar). We also allow users to select the range to 
                measure margin of error, e.g. default values are (20, 80).
                """
            )
            # Start new section: Entry Strategy
            st.markdown(
                f"""
                    <h4 style='text-align: center;'>Entry Strategy:</h4>
                """,
                unsafe_allow_html=True,
            )
            st.write(f"Pick a stock you like and review entry strategy.")
            entry_plot = entry_strategy(
                start_date=str(start_datetime).split(" ")[0],
                end_date=str(end_datetime).split(" ")[0],
                tickers=this_stock,
                thresholds=rsi_thresholds,
                buy_threshold=thresholds_values[0],
                sell_threshold=thresholds_values[1],
            )
            st.pyplot(entry_plot)
            st.success("Entry Strategy teacheds Mr. Yin when to buy. 💡")

        with tab2:
            fig = entry_strategy_plotly(
                start_date=str(start_datetime).split(" ")[0],
                end_date=str(end_datetime).split(" ")[0],
                tickers=this_stock,
                thresholds=rsi_thresholds,
                buy_threshold=thresholds_values[0],
                sell_threshold=thresholds_values[1],
            )
            st.plotly_chart(fig, theme="streamlit", use_container_width=True)

        with tab3:
            st.warning(
                "Please be patient with us. Our generative AI robot is preparing the info for you!"
            )
            the_stock_info = all_info["get stock info"]
            assistant_prompt = {
                "role": "assistant",
                "content": "You are a helpful AI assistant for the user.",
            }
            result = []
            result.append(assistant_prompt)
            prompt = f"""
                Write a mardown file based on {the_stock_info}
            """
            user_prompt = {"role": "user", "content": prompt}
            result.append(user_prompt)
            try:
                response = call_chatcompletion(result)
                st.markdown(response)
            except:
                st.warning(
                    "OpenAI limit reached. We'll manually pull summarized information for you."
                )
                df = []
                for key, value in the_stock_info.items():
                    df.append([key, value])
                df = pd.DataFrame(df)
                st.table(df)
            st.success(
                "We used Yahoo Finance to acquire data and OpenAI's ChatGPT to create this file!"
            )

        st.warning(
            "Note (1): The entry strategy presented above simulates largely what Mr. Yin is executing, but the number of days and thresholds are not reproducible and these parameters are largely based on experience."
        )

        st.warning(
            "Note (2): Mr. Yin currently doesn't execute any exit strategy. Holding a stock is like marriage. Mr. Yin does not believe in short term profits and it certainly does not fulfil fiduciary by his experience. For starters, think about the tax you pay."
        )
    else:
        st.warning("Please select an option and click the submit button!")
elif option == "Interactive Timing Strategy":
    # Use st.components.v1.html to render the iframe
    st.components.v1.html(
        """
        <iframe
            src="https://eagle0504-technical-trader.hf.space"
            frameborder="0"
            style="width: 100%; height: 900px;"
        ></iframe>
        """,
        height=1000,  # Set the height to match the iframe's height style
    )
else:
    st.warning("Please select an option and click the submit button!")


# Credit
def current_year():
    now = datetime.now()
    return now.year


# Example usage:
current_year = current_year()  # This will print the current year
st.markdown(
    f"""
        <h6 style='text-align: left;'>Copyright © 2010-{current_year} Present Yiqiao Yin</h6>
    """,
    unsafe_allow_html=True,
)