<jupyter_start><jupyter_text>Need to define inputs (type: pd.Dataframe):1. expRtns: "LIBOR rates & forecasted returns"2. cov: "covariance based on historical data"3. mu_f: "US treasury yield"<jupyter_code>def Portfolio_stats(weights: "porportions of capital", 
                    expRtns: "LIBOR rates & forecasted returns", 
                    cov: "covariance based on historical data", 
                    mu_f: "US treasury yield") -> "portfolio returns,variance,volatility,sharpe_ratio":
    varP = np.dot(weights.T, np.dot(cov,weights))
    volP = np.sqrt(varP)
    rtnP = np.sum(weights*expRtns)
    sharpeP = (rtnP-mu_f)/volP
    return rtnP, varP, volP, sharpeP
def negative_sharpe(weights):
    return -Portfolio_stats(weights,expRtns,cov,mu_f)[3]
def returns_from_prices(prices, log_returns=False):
    """
    Calculate the returns given prices.
    :param prices: adjusted (daily) closing prices of the asset, each row is a
                   date and each column is a ticker/id.
    :type prices: pd.DataFrame
    :param log_returns: whether to compute using log returns
    :type log_returns: bool, defaults to False
    :return: (daily) returns
    :rtype: pd.DataFrame
    """
    if log_returns:
        return np.log(prices).diff().dropna(how="all")
    else:
        return prices.pct_change().dropna(how="all")
        
def _pair_exp_cov(X, Y, span=180):
    """
    Calculate the exponential covariance between two timeseries of returns.
    :param X: first time series of returns
    :type X: pd.Series
    :param Y: second time series of returns
    :type Y: pd.Series
    :param span: the span of the exponential weighting function, defaults to 180
    :type span: int, optional
    :return: the exponential covariance between X and Y
    :rtype: float
    """
    covariation = (X - X.mean()) * (Y - Y.mean())
    # Exponentially weight the covariation and take the mean
    if span < 10:
        warnings.warn("it is recommended to use a higher span, e.g 30 days")
    return covariation.ewm(span=span).mean().iloc[-1]     

def exp_cov(prices, returns_data=False, span=180, frequency=252, **kwargs):
    """
    Estimate the exponentially-weighted covariance matrix, which gives
    greater weight to more recent data.
    :param prices: adjusted closing prices of the asset, each row is a date
                   and each column is a ticker/id.
    :type prices: pd.DataFrame
    :param returns_data: if true, the first argument is returns instead of prices.
    :type returns_data: bool, defaults to False.
    :param span: the span of the exponential weighting function, defaults to 180
    :type span: int, optional
    :param frequency: number of time periods in a year, defaults to 252 (the number
                      of trading days in a year)
    :type frequency: int, optional
    :return: annualised estimate of exponential covariance matrix
    :rtype: pd.DataFrame
    """
    if not isinstance(prices, pd.DataFrame):
        warnings.warn("data is not in a dataframe", RuntimeWarning)
        prices = pd.DataFrame(prices)
    assets = prices.columns
    if returns_data:
        returns = prices
    else:
        returns = returns_from_prices(prices)
    N = len(assets)

    # Loop over matrix, filling entries with the pairwise exp cov
    S = np.zeros((N, N))
    for i in range(N):
        for j in range(i, N):
            S[i, j] = S[j, i] = _pair_exp_cov(
                returns.iloc[:, i], returns.iloc[:, j], span
            )
    cov = pd.DataFrame(S * frequency, columns=assets, index=assets)
    
    return cov

start_date = '2015-09-01'
end_date = '2016-09-01' # hypothetically the current time
ahead_date = '2017-09-01'

symbols = ['AAPL', 'GS', 'GC=F', 'GE']

# historical data: to be used to compute variance
df = pd_data.DataReader(symbols,'yahoo',start_date,end_date)['Adj Close'] 
df = df.dropna()

# one-year-ahead data: to be used as the (fake) forecasted return
df2 = pd_data.DataReader(symbols,'yahoo',end_date,ahead_date)['Adj Close'] 
df2 = df2.dropna()
# import LIBOR rates data
LIBOR_rates = pd.read_csv("LIBOR_USD.csv")
LIBOR_rates
# convert to datetime
LIBOR_rates['Date'] = pd.to_datetime(LIBOR_rates['Date'],format="%d.%m.%Y")
# LIBOR rates between start date & end date
Rates = LIBOR_rates.loc[ (LIBOR_rates['Date'] >= start_date) & (LIBOR_rates['Date'] <= end_date) ]
Rates.sort_values(by='Date',ascending=True,inplace=True)
Rates.set_index('Date', inplace=True)
Rates<jupyter_output>C:\Users\YH\Anaconda3\lib\site-packages\ipykernel_launcher.py:3: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  This is separate from the ipykernel package so we can avoid doing imports until<jupyter_text>LIBOR rates are quoted as annual interest rates (in %). To get daily percentage change, need to divide by 252 (trading days in a year).  Compute covariance (historical data)<jupyter_code>df_all = df.copy()
df_all['Cash'] = Rates['ON']/100/252 # short-term interest rates (OverNight LIBOR)
df_all = df_all.dropna()
dfrtn_all = df_all.pct_change().dropna() # the percentage (not log) returns of stocks
cov = exp_cov(dfrtn_all, returns_data = True) #covariance of percentage returns
cov<jupyter_output><empty_output><jupyter_text>Compute expected returns (one-year-ahead data)<jupyter_code># expected returns: current short-term rate for cash, and (heuristically) the one-year-ahead average percentage returns for stocks
expRtns = df2.pct_change().mean(axis=0)
expRtns['Cash'] = Rates['ON'][end_date]/100/252
expRtns<jupyter_output><empty_output><jupyter_text>Use LIBOR rate for short-term risk-free rate => for daily expected return of cash Tenor of rate = rebalancing period  Use Treasury yield for long-term risk-free rate => for performance measure, Sharpe ratio, etc. Tenor of rate = portfolio holding period<jupyter_code>TreasuryYields = pd.read_csv('USTREASURY_YIELD.csv')
TreasuryYields
TreasuryYields['Date']= pd.to_datetime(TreasuryYields['Date'],format="%Y-%m-%d")
TreasuryYields
# Treasury yield at current time
# assuming portfolio holding period = 3 years
mu_f = TreasuryYields.loc[TreasuryYields['Date']==end_date]['3 YR'].values[0]/100/252
mu_f<jupyter_output><empty_output><jupyter_text>Compute optimal weights<jupyter_code># total number of stocks (+cash)
nn = len(expRtns)

# Initialise weights
w0 = [1.0/nn for i in range(nn)]
w0 = np.array(w0)

# Constraints on weights
cons = ({'type':'eq','fun': lambda x: np.sum(x)-1}) #add up to 1
bnds = tuple((0,1) for x in range(nn)) #only between 0 and 1, i.e. no short-selling

# Maximise sharpes ratio
opts = sco.minimize(negative_sharpe, w0 , method = 'SLSQP', bounds= bnds, constraints = cons)

# Optimal weights
w_opt = opts['x'].round(3)
w_opt<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.11164274322169059, 'fraction_numerical': 0.013774104683195593, 'mean_word_length': 3.9412607449856734, 'pattern_counts': {'":': 1, '<': 22, '<?xml version=': 0, '>': 24, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '5', 'dir': 'jupyter-structured-clean-dedup', 'id': '491385', 'license': 'Apache-2.0', 'n_tokens_mistral': 2374, 'n_tokens_neox': 2239, 'n_words': 684, 'path': 'Backtesting - Max Sharpe Strategy.ipynb', 'repo_name': 'algotradingsoc/trading_simulator', 'size': '6897'}