import pandas_datareader.data as pdr
import numpy as np
import datetime
import copy
import pandas as pd
import matplotlib.pyplot as plt
'''
Funtions for backtesting: 
'''

def CAGR(DF):
    df = DF.copy()
    # df['mon_ret'] = DF['Adj Close'].pct_change()
    df['cum_return'] = (1 + df['mon_ret']).cumprod() 
    n = len(df) / 12
    CAGR = (df['cum_return'].tolist()[-1]) ** (1 / n) - 1
    return CAGR

# cagr = CAGR(DF)

def volatility(DF):
    '''
    function to calculate annualized volatility of a trading strategy

    Parameters
    ----------
    DF : TYPE
        DESCRIPTION.

    Returns
    -------
    vol : TYPE
        DESCRIPTION.

    '''
    df = DF.copy()
    vol = df['mon_ret'].std() * np.sqrt(12)
    return vol

# rf: risk free return
def sharpe(DF, rf):
    '''
    Function to calculate sharpe ratio; rf is the risk free rate

    Parameters
    ----------
    DF : TYPE
        DESCRIPTION.
    rf : TYPE
        DESCRIPTION.

    Returns
    -------
    sr : TYPE
        DESCRIPTION.

    '''
    df = DF.copy()
    sr = (CAGR(df) - rf) / volatility(df)
    return sr

# rf = 0.022 # risk free return 

# sharpe_res = sharpe(ohlcv, rf)


def sortino(DF, rf):
    '''
    Function to calculate sortino ratio

    Parameters
    ----------
    DF : TYPE
        DESCRIPTION.
    rf : TYPE
        DESCRIPTION.

    Returns
    -------
    sr : TYPE
        DESCRIPTION.

    '''
    df = DF.copy()
    df['daily_ret'] = DF['Adj Close'].pct_change()
    neg_vol = df[df['daily_ret'] < 0]['daily_ret'].std() * np.sqrt(12) 
    sr = (CAGR(df) - rf) / neg_vol
    return sr

# sortino_res = sortino(ohlcv, rf)


def max_dd(DF):
    '''
    function to calculate max drawdown

    Parameters
    ----------
    DF : TYPE
        DESCRIPTION.

    Returns
    -------
    max_dd : TYPE
        DESCRIPTION.

    '''
    df = DF.copy()
    df['cum_return'] = (1 + df['mon_ret']).cumprod()
    df['cum_roll_max'] = df['cum_return'].cummax()
    df['drawdown'] = df['cum_roll_max'] - df['cum_return']
    df['drawdown_pct'] = df['drawdown'] / df['cum_roll_max']
    max_dd = df['drawdown'].max()
    return max_dd
    
# max_dd_res = max_dd(ohlcv)

# ohlcv['Adj Close'].plot()
    
def calmar(DF):
    '''
    This function calculate the Calmar ratio
    
    * Calmar Ratio is the ratio of CAGR and Max drawdown and it's measure of risk adjusted return.
    '''
    df = DF.copy() 
    clmr = CAGR(df) / max_dd(df)
    return clmr

# calmar_res = calmar(ohlcv)

# Every stock in DJI
tickers = ['MMM', 'AXP', 'AAPL', 'BA', 'CAT', 'CVX', 'CSCO', 'KO', 'DOW', 'XOM', 'GS', 'HD',
           'IBM', 'INTC', 'JNJ', 'JPM', 'MCD', 'MRK', 'MSFT', 'NKE', 'PFE', 'PG', 'TRV',
           'UTX', 'UNH', 'VZ', 'V', 'WMT', 'DIS']

ohlc_mon = {} # Directory with ohlc value for each stock
attempt = 0 # Init passthrough variable
drop = [] # initializeing list to store tickers whose close price was sucessfuly extracted
while len(tickers) != 0 and attempt <= 5:
    tikers = [j for j in tickers if j not in drop]  # Removing stocks whose data has been extracted
    for i in range(len(tickers)):
        try:
            ohlc_mon[tickers[i]] = pdr.get_data_yahoo(tickers[i], datetime.date.today() - datetime.timedelta(1825), datetime.date.today(), interval='m')
            ohlc_mon[tickers[i]].dropna(inplace=True)
            drop.append(tickers[i])
        except:
            print(tickers[i], ' : fail to fetch data ... retrying')
            continue
    attempt += 1
    
tickers = ohlc_mon.keys()

########################### Backtesting #######################################

# calculating monthly return for each stock and consolidating return info by stock in a separate df
ohlc_dict = copy.deepcopy(ohlc_mon)
return_df = pd.DataFrame()
for ticker in tickers:
    print('calculating monthly return for ', ticker)
    ohlc_dict[ticker]['mon_ret'] = ohlc_dict[ticker]['Adj Close'].pct_change()
    return_df[ticker] = ohlc_dict[ticker]['mon_ret']


# function to calculate portfolio return iteratively
return_df
m = 6
x = 3
def pflio(return_df, m, x):
    '''
    

    Parameters
    ----------
    return_df : Dataframe
        dataframe with monthly return info for all stocks
    m : int
        number of stocks in the portfolio
    x : int
        number of underperforming stocks to be removed from portfolio monthly

    Returns
    -------
    None.

    '''
    df = return_df.copy()
    portfolio = []
    monthly_ret = [0]
    for i in range(1, len(df)):
        if len(portfolio) > 0:
            monthly_ret.append(df[portfolio].iloc[i,:].mean())
            bad_stocks = df[portfolio].iloc[i,:].sort_values(ascending=True)[:x].index.values.tolist()
            portfolio = [stock for stock in portfolio if stock not in bad_stocks]
        fill = m - len(portfolio)
        new_picks = df[[t for t in tickers if t not in portfolio]].iloc[i,:].sort_values(ascending=False)[:fill].index.values.tolist()
        portfolio = portfolio + new_picks
        print(portfolio)
    monthly_ret_df = pd.DataFrame(np.array(monthly_ret), columns=['mon_ret'])
    return monthly_ret_df
            
# Calculating overall strategy's KPI's
portfolio_df = pflio(return_df, 6, 3)
p_sharpe = sharpe(portfolio_df, 0.025)
p_mdd = max_dd(portfolio_df)

# Calculating KPIs for index by and hold strategy over the same period
DJI = pdr.get_data_yahoo('^DJI', datetime.date.today() - datetime.timedelta(1825), datetime.date.today(), interval='m')
DJI['mon_ret'] = DJI['Adj Close'].pct_change()
dji_cagr = CAGR(DJI)
dji_sharpe = sharpe(DJI, 0.025)
dji_max_dd = max_dd(DJI)

# Visualization
flg, ax = plt.subplots()
plt.plot((1 + portfolio_df).cumprod())
plt.plot((1+DJI['mon_ret'][2:].reset_index(drop=True)).cumprod())
plt.title('Index Return vs Strategy Return')
plt.ylabel('cumulative return')
plt.xlabel('month')
ax.legend(['Strategy Return', 'Index Return'])






