"""
Test and Train ML Algorithm

"""

from analysis.src.labelFunctions import *
from analysis.src.setup import *
from analysis.src.features import *
from analysis.src.fetchData import *
from analysis.src.globals import *

# This function gives us a prediction between [-3,-2,-1,0,1,2,3]
# WE could instead use the probabilities that the classifier provides for each of these
# labels and computed a weighted average signal

def getPredictionsProb(ticker,trainStart,testPeriod,options,supportTickers):
    """This function does something.

	:param name: The name to use
	:type name: None
	:param state: None
	:type state: None
	:returns: None
	:raises: None
	"""
    print('in getPredictionsProb')
    # This function will take in a test period, for each data point in that
    # period it will give us a prediction in [-3,-2,-1,0,1,2,3]


    # It will use the algorithm that is chosen by the user
    algo = options['algo']
    algo_params=options['algo_params']
    testData=getFeatures(ticker,testPeriod[0],testPeriod[1],options,supportTickers)[0]
    # We get the test data points represented using the features

    # For each test data point a classifier will be trained using the
    # training data from trainStart to testDate-1,

    start_date=trainStart
    dates=pd.Series(testData.index)

    predictions=np.empty(dates.size)

    for i in range(dates.size):
        end_date=str(testData.iloc[i,].name)
        testPoint = testData.iloc[i,]
        print (start_date, end_date)
        # This is the training period used for the current testPoint

        clf=algo(**algo_params)
        (features, labels)=getFeatures(ticker,start_date,end_date,options,supportTickers)

        # We use an imputer to fill in missing values if any
        imp=Imputer(missing_values='NaN',strategy='mean',axis=0)
        imp=imp.fit(features)

        features_imp=imp.transform(features)
        clf=clf.fit(features_imp, labels)
        classes=clf.classes_ # This gives the class labels
        prediction_proba=clf.predict_proba(imp.transform(testPoint.reshape(1,-1))) # This is the list of probabilities for each class label
        predictions[i]=np.sum(np.multiply(classes,prediction_proba))

    return predictions

def getPredictions(ticker,trainStart,testPeriod,options,supportTickers):
    """This function does something.

	:param name: The name to use
	:type name: None
	:param state: None
	:type state: None
	:returns: None
	:raises: None
	"""
    #print('in getPredictions')
    # This function will take in a test period, for each data point in that
    # period it will give us a prediction in [-3,-2,-1,0,1,2,3]


    # It will use the algorithm that is chosen by the user
    algo = options['algo']
    algo_params=options['algo_params']
    #print ("obtaining test data in getPredictions")
    testData=getFeatures(ticker,testPeriod[0],testPeriod[1],options,supportTickers)[0]
    #print("test data in getPredictions obtained")
    # We get the test data points represented using the features

    # For each test data point a classifier will be trained using the
    # training data from trainStart to testDate-1,

    start_date=trainStart
    dates=pd.Series(testData.index)

    predictions=np.empty(dates.size)

    for i in range(dates.size):
        end_date=str(testData.iloc[i,].name)
        testPoint = testData.iloc[i,]
        #print (start_date, end_date)
        # This is the training period used for the current testPoint

        clf=algo(**algo_params)

        #print ('getting features for each day'+str(i))
        (features, labels)=getFeatures(ticker,start_date,end_date,options,supportTickers)
        #print ("feature "+str(i)+" obtained")

        # We use an imputer to fill in missing values if any
        imp=Imputer(missing_values='NaN',strategy='mean',axis=0)
        imp=imp.fit(features)
        features_imp = imp.transform(features)
        labels = labels.iloc[:-2]
        #print (features_imp.shape, labels.shape)
        clf=clf.fit(features_imp, labels)
        predictions[i]=(clf.predict(imp.transform(testPoint.reshape(1,-1))))

    return predictions

# We have the predictions by using this algorithm. WE can now run a backtest, ie
# See what the returns and Sharpe Ratio would have been if we used these predictions to
# execute trades

def backtestResults(ticker,trainStart,testPeriod,options,supportTickers,predictFn=getPredictions):
    """This function does something.

	:param name: The name to use
	:type name: None
	:param state: None
	:type state: None
	:returns: None
	:raises: None
	"""
    #print('in backtestResults')
    predictions=predictFn(ticker,trainStart,testPeriod,options,supportTickers)
    #print ('predictions obtained')
    # We also need to get the actual returns
    #print ('get actual returns')
    returns = getAssetReturns(ticker,testPeriod[0],testPeriod[1],options)
    #print ('returns obtained')

    anl_factor=252
    if options["freq"]==1:
        anl_factor=12
    if options["freq"]==2:
        anl_factor=52

    strat_returns=pd.Series(np.multiply(predictions,returns))
    sharpe=np.sqrt(anl_factor)*np.nanmean(strat_returns)/np.nanstd(strat_returns)
    returnSeries=pd.concat([returns,strat_returns],axis=1)
    returnSeries.columns=["Asset_Returns","Strat_Returns"]
    returnSeries.to_csv(globals['outputFilesPath']+'/Returns.csv')
    result=[]
    result.append(sharpe)
    result.append(np.nanmean(strat_returns))
    result.append(np.nanstd(strat_returns))

    strat_returns = strat_returns.values #additional line i put in to convert from series to array for scipy stats below
    result.append(scipy.stats.skew(strat_returns,nan_policy="omit"))
    result.append(float(strat_returns[strat_returns>0].size)/float(strat_returns.size))
    result.append(float(strat_returns[strat_returns < 0].size) / float(strat_returns.size))

    result=pd.DataFrame(result,index=["Sharpe","Mean","Risk","Skew","%up","%Down"])
    print (result)

    return result

# We need this function that can give us the returns for a ticker between 2 dates
# at the specified frequency

def getAssetReturns(ticker,start_date,end_date,options):
    """This function does something.

	:param name: The name to use
	:type name: None
	:param state: None
	:type state: None
	:returns: None
	:raises: None
	"""
    #print('in getAssetReturns')
    # This is just like the features function where we construct all the features
    # We just want to return the Return series instead of all the features

    start_date = datetime.strptime(start_date, "%Y-%m-%d").date()
    end_date = datetime.strptime(end_date, "%Y-%m-%d").date()

    tickerDataRaw = getRawData(ticker, start_date, end_date, options)


    # Now we setup the data at the right frequency
    if options['freq'] > 0:
        offset = options['offset']
        if options['freq'] == 1:

            if offset > 0:
                tickerDataRaw = tickerDataRaw[tickerDataRaw["tDayinMonth"] == int(offset)]
            else:
                tickerDataRaw = tickerDataRaw[tickerDataRaw["tDaysleftMonth"] == int(abs(offset))]


        if options['freq'] == 2:
            tickerDataRaw = tickerDataRaw[tickerDataRaw["tDayinWeek"] == int(abs(offset))]

        # We've filtered the data to the right frequency. For each frequency,
        # The trailing periods used for momentum, jump calculations are different
        # if the frequency is not daily, then recompute the returns

    tickerDataRaw["Return"] = getReturn(tickerDataRaw["Price"])

    return tickerDataRaw["Return"][tickerDataRaw.index>=start_date]

def getPredictionsNN(ticker,trainStart,testPeriod,options,supportTickers):
    """This function does something.

	:param name: The name to use
	:type name: None
	:param state: None
	:type state: None
	:returns: None
	:raises: None
	"""
    print('in getPredictionsNN')
    # This function will take in a test period, for each data point in that
    # period it will give us a prediction in [-3,-2,-1,0,1,2,3]


    # It will use the algorithm that is chosen by the user
    algo = options['algo']
    algo_params=options['algo_params']
    #print ('obtain test data')
    testData=getFeatures(ticker,testPeriod[0],testPeriod[1],options,supportTickers)[0]
    #print (testData.head(2))
    # We get the test data points represented using the features

    # For each test data point a classifier will be trained using the
    # training data from trainStart to testDate-1,

    start_date=trainStart
    dates=pd.Series(testData.index)

    predictions=np.empty(dates.size)

    for i in range(dates.size):
        end_date=str(testData.iloc[i,].name)
        testPoint = testData.iloc[i,].values
        #print (start_date, end_date)
        # This is the training period used for the current testPoint

        clf=algo(**algo_params)
        #print ('get features for each date')
        (features, labels)=getFeatures(ticker,start_date,end_date,options,supportTickers,assignLabels=labelId)
        #print ('features obtined')
        # We use an imputer to fill in missing values if any
        imp=Imputer(missing_values='NaN',strategy='mean',axis=0)
        imp=imp.fit(features)
        #labels = labels.iloc[:-2]
        features_imp=imp.transform(features)
        #print(features_imp.shape)
        #print(labels)
        clf=clf.fit(features_imp, labels)
        predictions[i] = labels3(clf.predict(imp.transform(testPoint.reshape(1, -1))))
        # With the K-Nearest neighbors the prediction will be the average return of the the
        # nearest neighbours. We convert it to a signal in [-3,-2...]

    return predictions














