import pandas as pd
import numpy as np
import scipy.stats as st
DAYS_IN_YEAR=256.0
ROOT_DAYS_IN_YEAR=DAYS_IN_YEAR**.5

useroot=""

def cap_forecast(xrow, capmin,capmax):
    """
    Cap forecasts.
    
    """

    ## Assumes we have a single column    
    x=xrow[0]

    if x<capmin:
        return capmin
    elif x>capmax:
        return capmax
    
    return x

def cap_series(xseries, capmin=-20.0,capmax=20.0):
    """
    Apply capping to each element of a time series
    For a long only investor, replace -20.0 with 0.0

    """
    return xseries.apply(cap_forecast, axis=1, args=(capmin, capmax))

def get_list_code():
    ans=pd.read_csv("%sconfig.csv" % useroot)
    return list(ans.Instrument)

def get_point_sizes():
    ans=pd.read_csv("%sconfig.csv" % useroot)
    psizes=dict([(x[1].Instrument, float(x[1].Pointsize)) for x in ans.iterrows()])
    return psizes


def pd_readcsv(filename):
    """
    Reads the pandas dataframe from a filename, given the index is correctly labelled
    """

    
    ans=pd.read_csv(filename)
    
    ans.index=pd.to_datetime(ans['DATETIME'])
    del ans['DATETIME']
    ans.index.name=None
    return ans

def find_datediff(data_row):
    """
    data differential for a single row
    """
    if np.isnan(data_row.NEAR_MONTH) or np.isnan(data_row.TRADE_MONTH):
        return np.nan
    nearest_dt=pd.to_datetime(str(int(data_row.NEAR_MONTH)), format="%Y%m")
    trade_dt=pd.to_datetime(str(int(data_row.TRADE_MONTH)), format="%Y%m")
    
    distance = trade_dt - nearest_dt
    distance_years=distance.days/365.25
    
    ## if nearder contract is cheaper; price will fall
    price_diff=data_row.NEARER - data_row.TRADED 
    
    return price_diff/distance_years
    


def ewmac_forecast_scalar(Lfast, Lslow):
    """
    Function to return the forecast scalar (table 49 of the book)
    
    Only defined for certain values
    """
    
    
    fsdict=dict(l2_8=10.6, l4_16=7.5, l8_32=5.3, l16_64=3.75, l32_128=2.65, l64_256=1.87)
    
    lkey="l%d_%d" % (Lfast, Lslow)
    
    if lkey in fsdict:
        return fsdict[lkey]
    else:
        print ("Warning: No scalar defined for Lfast=%d, Lslow=%d, using default of 1.0" % (Lfast, Lslow))
        return 1.0

def get_price_for_instrument(code):
    
    filename="%sdata/%s_price.csv" %  (useroot, code)
    price=pd_readcsv(filename)
    return price

def get_carry_data(code):
    
    filename="%sdata/%s_carrydata.csv" % (useroot, code)
    data=pd_readcsv(filename)

    return data

def uniquets(df3):
    """
    Makes x unique
    """
    df3=df3.groupby(level=0).first()
    return df3


def daily_resample(b, a):
    """
    Returns b dataframe resampled to a dataframe index
    
    """
    
    master_index=a.index
    a_daily=a.resample('1D') ## Only want index, fill method is irrelevant
    b=uniquets(b)
    b_daily=b.reindex(a_daily.index, method="ffill", limit=1)
    new_b=b_daily.reindex(master_index, method="ffill", limit=1)
    
    return new_b



def calculate_pandl(position_ts, price_ts, pointsize=1.0):
    rs_positions_ts=daily_resample(position_ts, price_ts).ffill()
    rets=price_ts - price_ts.shift(1)
    local_rets=rs_positions_ts.shift(1)*rets*pointsize

    return local_rets

def annualised_rets(total_rets):
    mean_rets=total_rets.mean(skipna=True)
    annualised_rets=mean_rets*DAYS_IN_YEAR
    return annualised_rets

def annualised_vol(total_rets):
    actual_total_daily_vol=total_rets.std(skipna=True)
    actual_total_annual_vol=actual_total_daily_vol*ROOT_DAYS_IN_YEAR
    return actual_total_annual_vol

def sharpe(total_rets):
    
    sharpe=annualised_rets(total_rets)/annualised_vol(total_rets)
    
    return sharpe

def stack_ts(tslist, start_date=pd.datetime(1970,1,1)):
    
    """
    Take a list of time series, and stack them, generating a new time series
    """
    

    tslist_values=[list(x.iloc[:,0].values) for x in tslist]
    stack_values=sum(tslist_values, [])
    stack_values=[x for x in stack_values if not np.isinf(x)]
    
    stacked=arbitrary_timeindex(stack_values, start_date)
    
    
    return stacked

def slices_for_ts(data, freq="12M"):
    """
    Return date indices for slicing up a data frame
    """
    yridx=list(pd.date_range(start=data.index[0], end=data.index[-1], freq=freq))
    yridx_stub=list(pd.date_range(start=yridx[-1], periods=2, freq=freq))[-1]
    yridx=yridx+[yridx_stub]
    
    return yridx

def break_up_ts(data, freq="12M"):
    """
    Take a data frame and break it into chunks 
    returns a list of data frames
    """
    yridx=slices_for_ts(data, freq)
    
    brokenup=[]
    for idx in range(len(yridx))[1:]:
        brokenup.append(data[yridx[idx-1]:yridx[idx]])
    
    return brokenup
    

def drawdown(x):
    ### Returns a ts of drawdowns for a time series x
    
    ## rolling max with infinite window
    maxx=pd.rolling_max(x, 99999999, min_periods=1)
    return (x - maxx)/maxx

    
    

class account_curve(pd.core.series.Series):
    """
    Inherits from pandas time series to give useful information
    
    Could be in % or GBP terms
    
    Downsamples to daily before doing anything else
    
    Can 
    
    """
    
    
    def new_freq(self, freq):
        ## Set up a new frequency.
        ## Note this will break certain things (eg Sharpe) so be careful
        if freq=="Daily":
            ## we assume we're daily so do nothing
            return self
        if freq=="Weekly":
            return self.cumsum().ffill().resample("W").diff()
        if freq=="Monthly":
            return self.cumsum().ffill().resample("M").diff()
    
    def sharpe(self):
        ## assumes daily returns
        return ROOT_DAYS_IN_YEAR*self.mean()/self.std()
    
    def annstd(self):
        return ROOT_DAYS_IN_YEAR*self.std()
    
    def losses(self):
        x=self.values
        return [z for z in x if z<0]
    
    def gains(self):
        x=self.values
        return [z for z in x if z>0]
    
    def avg_loss(self):
        return np.mean(self.losses())

    def avg_gain(self):
        return np.mean(self.gains())
    
    def drawdown(self):
        ## in case need numerous stats
        if "drawdownacc" not in dir(self):
            setattr(self, "drawdownacc", drawdown(cum_perc(self)))
        return self.drawdownacc
    
    def avg_drawdown(self):
        return self.perc_drawdown(50.0)
    
    def perc_drawdown(self, q):
        dd=self.drawdown()
        return np.percentile(dd, q)

    def worst_drawdown(self):
        dd=self.drawdown()
        return np.nanmin(dd.values)
        
    def time_in_drawdown(self):
        dd=self.drawdown()
        dd=[z for z in dd if not np.isnan(z)]
        in_dd=float(len([z for z in dd if z<0]))
        return in_dd/float(len(dd))
        
    def monthly_returns(self):
        return self.resample("1M", how="sum")
    
    def gaintolossratio(self):
        return self.avg_gain()/-self.avg_loss()
    
    def profitfactor(self):
        return sum(self.gains())/-sum(self.losses())
    
    def hitrate(self):
        no_gains=float(len(self.gains()))
        no_losses=float(len(self.losses()))
        return no_gains/(no_losses+no_gains)
    

def cum_perc(pd_timeseries):
    """
    Cumulate percentage returns for a pandas time series
    """
    
    cum_datalist=[1+x for x in pd_timeseries]
    cum_datalist=pd.TimeSeries(cum_datalist, index=pd_timeseries.index)
    
    
    return cum_datalist.cumprod()


def arbitrary_timeindex(Nperiods, index_start=pd.datetime(2000,1,1)):
    """
    For nice plotting, convert a list of prices or returns into an arbitrary pandas time series
    """    
    
    ans=pd.bdate_range(start=index_start, periods=Nperiods)
    
    return ans


def arbitrary_timeseries(datalist, index_start=pd.datetime(2000,1,1)):
    """
    For nice plotting, convert a list of prices or returns into an arbitrary pandas time series
    """    
    
    ans=pd.TimeSeries(datalist, index=arbitrary_timeindex(len(datalist), index_start))
    
    return ans

def remove_nans_from_list(xlist):
    return [x for x in xlist if not np.isnan(x)]



def autocorr(x, t=1):
    return np.corrcoef(np.array([x[0:len(x)-t], x[t:len(x)]]))[0,1]
