from turtle import width import gradio as gr import yfinance as yf from retry import retry from tqdm.auto import tqdm import pandas as pd import time import mplfinance as mpf from pypfopt import risk_models, expected_returns from pypfopt import plotting from pypfopt import EfficientFrontier, objective_functions import numpy as np from warnings import filterwarnings filterwarnings("ignore") data = pd.DataFrame() def load_data(ticker_list="", start_year=2021): data = yf.download( list(map(lambda x: x.strip().upper(), ticker_list.split(','))), start=f'{start_year}-01-01', interval='1d', keepna=True, period="5y", rounding=True, )['Close'].reset_index().fillna(method='ffill') data.Date = pd.to_datetime(data.Date) return data def _prep_evaluation_report( hist_data, backtest_days, investment_size, weights ): allocation_df = pd.DataFrame({k: [weights[k]] for k in weights}).T testing_data = hist_data[-backtest_days:].copy() allocation_df.columns = ['weights'] init_df = testing_data.iloc[0] end_df = testing_data.iloc[-1] allocation_df = allocation_df.merge( init_df, left_index=True, right_index=True) allocation_df = allocation_df.merge( end_df, left_index=True, right_index=True) allocation_df.columns = ['weights', 'cost_price', 'sell_price'] allocation_df['amount_allocation'] = allocation_df.weights * \ investment_size allocation_df['unit_pnl'] = allocation_df.sell_price - \ allocation_df.cost_price allocation_df['units'] = allocation_df.weights * \ investment_size // allocation_df.cost_price allocation_df['value'] = allocation_df.units * allocation_df.cost_price allocation_df['pnl'] = allocation_df.units * allocation_df.unit_pnl profit = np.round(allocation_df.pnl.sum(), 2) cagr = np.round( ( ( (investment_size + profit)/investment_size )**( 1/(backtest_days/365) ) - 1 )*100, 2 ) instrumnets = testing_data.columns.tolist() unit_map = allocation_df['units'].to_dict() for instrument in instrumnets: testing_data[instrument] -= testing_data[instrument].iloc[0] testing_data[instrument] *= unit_map.get(instrument, 0) return ( allocation_df.round(2).reset_index(), profit, cagr, gr.BarPlot( pd.DataFrame( testing_data.sum(axis=1), columns=['Portfolio Value'] ).reset_index(), x='Date', y='Portfolio Value', ), ) def re_optimize( hist_data: pd.DataFrame, backtest_days: int, investment_size: int ): _, allocation_df, _ = find_weights( hist_data, backtest_days, investment_size ) return find_weights( hist_data.drop( columns=allocation_df[allocation_df.units == 0]['index'] ), backtest_days, investment_size ) def find_weights( hist_data: pd.DataFrame, backtest_days: int, investment_size: int ): training_data = hist_data[:-backtest_days].copy().set_index( 'Date' ).fillna(0).astype(float) mu = expected_returns.capm_return(training_data) S = risk_models.semicovariance(training_data) initial_weights = np.array( [1/len(training_data.columns)] * len(training_data.columns)) ef = EfficientFrontier(mu, S) ef.add_objective( objective_functions.transaction_cost, w_prev=initial_weights, k=0.02 ) ef.add_objective(objective_functions.L2_reg) ef.max_sharpe() weights = ef.clean_weights() ( expected_annual_return, annual_volatility, sharpe_ratio ) = ef.portfolio_performance() ( pnl_df, profit, cagr, pnl_graph ) = _prep_evaluation_report( hist_data.set_index('Date'), backtest_days, investment_size, weights ) return ( f''' > ## Portfolio Performance |Expected Annual Return|Annual Volatility|Sharpe Ratio|Profit|Profit %|CAGR| |:---:|:---:|:---:|:---:|:---:|:---:| |{expected_annual_return*100:.2f}|{annual_volatility*100:.2f}%|{sharpe_ratio:.2f}|{profit:.2f}|{profit/investment_size*100:.2f}%|{cagr:.2f}|''', pnl_df.sort_values('pnl', ascending=False), pnl_graph ) with gr.Blocks(title='Portfolio Optimizer') as demo: # with gr.Blocks() as demo: gr.Label('Portfolio Optimizer') with gr.Accordion('About'): gr.Markdown( ''' # Portfolio Optimizer This is a simple portfolio optimizer that uses the Efficient Frontier to optimize the portfolio weights. The optimizer uses the past `N` years of data to optimize the portfolio weights. The optimizer also provides a backtest of the portfolio for the last `X` days. The optimizer also provides a PnL graph for the backtest period. > `Note`: This optimizer does not claim any future outlook or guarantee any returns. This is just for educational purposes and uses [PyPortfolioOpt](https://pyportfolioopt.readthedocs.io/) as an optimizer. ## How to use the optimizer? 1. Enter the TickerCodes separated by a comma. 2. Select the number of days for backtesting. 3. Select the investment amount. 4. Select the start year for analysis. 5. Click on `Load Data` to load the data. 6. Click on `Optimize` to optimize the portfolio weights. The optimizer uses yFinance to fetch the data. Kindly use the TickerCodes that are available on `Yahoo Finance`. ''' ) with gr.Row(): with gr.Column(): stocks = gr.Textbox( label="TickerCodes", show_label=True, interactive=True ) backtest_days = gr.Slider( minimum=1, maximum=200, value=90, label="Backtest Days", interactive=True ) investment_size = gr.Slider( minimum=10_000, maximum=10_00_000, value=100_000, interactive=True, step=10_000, label="Investment Amount" ) start_year = gr.Slider( minimum=2020, maximum=2024, interactive=True, value=2021, label="Analysis Start Year" ) with gr.Row(): fetch_data = gr.Button("Load Data") # optimize = gr.Button("Optimize") optimize = gr.Button("Optimize") with gr.Row(): with gr.Column(): pnl_img = gr.BarPlot( title='PnL Graph', # interactive=True, # height=400, ) performance = gr.Markdown() pnl_statement = gr.DataFrame( pd.DataFrame(columns=[ 'instrument', 'weights', 'cost_price', 'sell_price', 'amount_allocation', 'unit_pnl', 'units', 'value', 'pnl' ])) historical_data = gr.DataFrame( pd.DataFrame(columns=['Date', 'Close']) ) fetch_data.click( load_data, [stocks, start_year], [historical_data] ) # optimize.click( # find_weights, # [historical_data, backtest_days, investment_size], # [performance, pnl_statement] # ) optimize.click( re_optimize, [historical_data, backtest_days, investment_size], [performance, pnl_statement, pnl_img] ) gr.Examples( examples=[ [','.join( ['ADANIENT.BO', 'ADANIPORTS.NS', 'APOLLOHOSP.NS', 'ASIANPAINT.NS', 'AXISBANK.BO', 'BAJAJ-AUTO.NS', 'BAJAJFINSV.NS', 'BAJFINANCE.NS', 'BHARTIARTL.BO', 'BPCL.NS', 'BRITANNIA.NS', 'CIPLA.NS', 'COALINDIA.NS', 'DIVISLAB.NS', 'DRREDDY.BO', 'EICHERMOT.BO', 'GRASIM.NS', 'HCLTECH.NS', 'HDFCBANK.NS', 'HDFCLIFE.BO', 'HEROMOTOCO.BO', 'HINDALCO.BO', 'HINDUNILVR.BO', 'ICICIBANK.NS', 'INDUSINDBK.NS', 'INFY.NS', 'ITC.NS', 'JSWSTEEL.BO', 'KOTAKBANK.BO', 'LT.BO', 'LTIM.BO', 'M&M.NS', 'MARUTI.NS', 'NTPC.BO', 'ONGC.NS', 'POWERGRID.BO', 'RELIANCE.NS', 'SBILIFE.BO', 'SBIN.NS', 'SHRIRAMFIN.BO', 'SUNPHARMA.NS', 'TATACONSUM.BO', 'TATAMOTORS.BO', 'TATASTEEL.BO', 'TCS.NS', 'TECHM.BO', 'TITAN.BO', 'ULTRACEMCO.NS', 'WIPRO.BO' ] ), 90, 100000, 2021 ] ], inputs=[stocks, backtest_days, investment_size, start_year] ) demo.launch(debug=True, )