import numpy as np
import scipy as sp
import pandas as pd
import pandas.io.data as web
import matplotlib.pyplot as plt
import scipy.optimize as opt
from pandas.io.pytables import HDFStore
from datetime import datetime, timedelta
from optimizers import *
from Portfolio import Portfolio
import rs

# from scipy import optimize
# from scipy.optimize import basinhopping


class Simulator:  

    def __init__(self, data, date=None, window=2*365, iwindow=120, periods=3, maxAssets=10, options=None, selector=None, repeatopt=3):    
        self.data = data        
        self.market = self.data.assets["^GSPC"]['excess_daily_ret']
        self.optimized = []
        self.options = options        
        self.window = window
        self.iwindow = iwindow
        self.periods = periods
        self.maxAssets = maxAssets
        self.repeatopt = repeatopt
        self.investment = 100000.0 # in $
        self.transaction_cost = 5.0 # in $
        self.portfolio = Portfolio(pd.Series(data.assetList), data, date=date, window=window, maxAssets=maxAssets, investment=self.investment, transaction_cost=self.transaction_cost, selector=selector) #data.assetList        
        self.sim_returns = pd.Series()
        self.result = pd.DataFrame()
        self.portfolios = []
        self.benchmarks = {
            title:pd.Series() for title in ['equal', 'return', 'sharpe', 'sortino', 'market']
        }
        print "Simulation initializated"

    def run(self):
        for i in range(self.periods):        
            # print self.portfolio    

            bestWeights = []
            bestFit = 9999.0
            # find best fit in 3 runs
            for c in range(self.repeatopt):                
                print "\nStarted run ", c
                self.portfolio.optimize(**self.options)
                fit = self.portfolio.utilityFunction()
                print "\nFinished run ", c
                print self.portfolio
                if fit<bestFit: # utility is negative
                    bestFit = fit
                    bestWeights = self.portfolio.portfolio.weights
            
            # set best found weights
            self.portfolio.setWeights(bestWeights)
            print "\nBest utility: ", bestFit
            print self.portfolio
            
            # save list of portfolios
            self.portfolios.append(self.portfolio.portfolio)

            # compute future period returns for this portfolio
            period_returns = self.getCurrentPortfolioFutureReturns()
            # add to simulation
            self.sim_returns = self.sim_returns.append(period_returns, verify_integrity=False)                             
            # compute benchmarks for this period
            self.addPeriodBenchmarks(period_returns)
            
            self.portfolio.shift(self.iwindow)
            print " "
            print 100.0*(i+1)/self.periods , '%'
            print " "


        self.result['sim'] = self.sim_returns        
        self.result = self.result.join(pd.DataFrame(self.benchmarks))
        
        print self.stats()
        
        self.plot()

    def stats(self):        
        self.s = pd.DataFrame()        
        self.s['sortino'] = self.result.apply(self.portfolio.sortino)        
        self.s['mean'] = self.result.apply(np.mean)  
        self.s['std'] = self.result.apply(np.std)  
        self.s['total_return'] = (self.result.cumsum()[-1:]+1).T*100
        self.s['growth_pa'] = ((self.s['total_return']/100)**(1/(self.result.count()/252.0))).T*100-100
        self.s['var'] = self.result.apply(rs.var)* np.sqrt(252/12)*100
        self.s = self.s.sort('sortino', ascending=False) 
        return self.s       

    def hist(self):
        self.sim_returns.hist(bins=50).plot()
        plt.show()

    def getCurrentPortfolioFutureReturns(self):
        period_returns = self.portfolio.getPortfolioFutureReturns(days=self.iwindow)                    
        # decrease equally by transaction costs
        period_costs = 2.0*self.transaction_cost*self.maxAssets
        period_returns = period_returns - period_costs / self.investment / period_returns.count() # sub percentual loss over period        
        return period_returns


    def addPeriodBenchmarks(self, period_returns):

        # add market benchmark
        self.benchmarks['market'] = self.benchmarks['market'].append(self.market.reindex(index=period_returns.index))

        # add equal benchmark
        self.portfolio.setWeights()
        self.benchmarks['equal'] = self.benchmarks['equal'].append(self.getCurrentPortfolioFutureReturns())
                
        # exponential return benchmark
        # self.portfolio.setWeights((self.portfolio.portfolio['mean']*10000)**2)
        # self.benchmarks['exp_return'] = self.benchmarks['exp_return'].append(self.getCurrentPortfolioFutureReturns())

        # sharpe weighted benchmark
        for benchmark in ['mean', 'sharpe', 'sortino']:
            self.portfolio.setWeights(self.portfolio.portfolio[benchmark])
            if benchmark is 'mean':
                benchmark = 'return'
            self.benchmarks[benchmark] = self.benchmarks[benchmark].append(self.getCurrentPortfolioFutureReturns())

    def load(self,case):
        """load case from df"""
        df = pd.HDFStore(case)        
        self.result = df['e']
        self.s = df['s']
        print self.s

    def plot(self):
        (self.result.cumsum()+1).plot()        
        plt.xlabel('date')
        plt.ylabel('return')
        plt.show()