import pandas as pd
from sets import Set

from app.processor.strategy.entity.StrategyCalculatorDataBean import *
from app.model.BlackScholesCalculator import *
from app.entity.ConstantValue import *


class StrategyCalculatorProcessor:
    @staticmethod
    # calculate by TTM
    def calculateOptions(strategyCalculatorDataBean):
        bs = BlackScholesCalculator()

        market = strategyCalculatorDataBean.marketInfo
        scenario = strategyCalculatorDataBean.scenario
        portfolio = strategyCalculatorDataBean.portfolio
        
        price = market.stockPrice
        dividendYield = market.dividendYield
        riskfreeRate = market.riskfreeRate
        optionMultiplier = market.optionMultiplier
        
        for posi in portfolio:
            tradeDirection = 1. if posi.buyOrSell == ConstantValue.BUY else -1.
            
            if posi.callOrPutOrStock == ConstantValue.STOCK:
                posi.value = tradeDirection * posi.quantity * price
                posi.delta = posi.value
            else:
                expiry = posi.expiryDays / 365.
                    
                if expiry <= 0:
                    posi.value = optionMultiplier * tradeDirection * posi.quantity * \
                            StrategyCalculatorProcessor.getProfitValue(posi.callOrPutOrStock, price, posi.strike)
                    posi.delta = 0
                    posi.gamma = 0 
                    posi.theta = 0
                    posi.vega = 0
                    posi.rho = 0
                else:
                    posi.value = optionMultiplier * tradeDirection * posi.quantity * bs.getValue(posi.callOrPutOrStock, price, posi.strike, posi.volatility, expiry, dividendYield, riskfreeRate)
                    posi.delta = optionMultiplier * tradeDirection * posi.quantity * bs.getDelta(posi.callOrPutOrStock, price, posi.strike, posi.volatility, expiry, dividendYield, riskfreeRate)
                    posi.delta *= price
                    posi.gamma = optionMultiplier * tradeDirection * posi.quantity * bs.getGamma(price, posi.strike, posi.volatility, expiry, dividendYield, riskfreeRate)
                    posi.gamma = posi.gamma * 0.5 * price * price 
                    posi.theta = optionMultiplier * tradeDirection * posi.quantity * bs.getTheta(posi.callOrPutOrStock, price, posi.strike, posi.volatility, expiry, dividendYield, riskfreeRate)
                    posi.vega = optionMultiplier * tradeDirection * posi.quantity * bs.getVega(price, posi.strike, posi.volatility, expiry, dividendYield, riskfreeRate)
                    posi.rho = optionMultiplier * tradeDirection * posi.quantity * bs.getRho(posi.callOrPutOrStock, price, posi.strike, posi.volatility, expiry, dividendYield, riskfreeRate)
            
            posi.cost = -1. * posi.value
        
    @staticmethod
    def calculateOptionsByDaysElapsed(strategyCalculatorDataBean):
        bs = BlackScholesCalculator()

        market = strategyCalculatorDataBean.marketInfo
        scenario = strategyCalculatorDataBean.scenario
        portfolio = strategyCalculatorDataBean.scenarioResults
        
        price = scenario.stockPrice
        if price <= 0:
            scenario.stockPrice = market.stockPrice
            price = market.stockPrice

        daysFromToday = scenario.daysFromToday
        volatilityShift = scenario.volatility
        riskfreeRate = scenario.riskfreeRate
        dividendYield = scenario.dividendYield
        
        # limited to the minimum expiry days
        minExpiryDays = 0
        for posi in portfolio:
            if posi.callOrPutOrStock == ConstantValue.STOCK:
                continue
            if minExpiryDays == 0 or minExpiryDays > posi.expiryDays:
                minExpiryDays = posi.expiryDays
        daysFromToday = min(daysFromToday, minExpiryDays)
        scenario.daysFromToday = daysFromToday

        optionMultiplier = market.optionMultiplier
        
        # limited to the minimum expiry days
        minExpiryDays = -1.
        for posi in portfolio:
            if posi.callOrPutOrStock == ConstantValue.STOCK:
                continue
            if minExpiryDays < 0 or minExpiryDays > posi.expiryDays:
                minExpiryDays = posi.expiryDays
        
        for posi in portfolio:
            tradeDirection = 1. if posi.buyOrSell == ConstantValue.BUY else -1.
            
            if posi.callOrPutOrStock == ConstantValue.STOCK:
                posi.cost = tradeDirection * posi.quantity * market.stockPrice
            else:
                posi.cost = optionMultiplier * tradeDirection * posi.quantity * \
                        bs.getValue(posi.callOrPutOrStock, market.stockPrice, posi.strike, posi.volatility, max(posi.expiryDays, 0.0001) / 365., market.dividendYield, market.riskfreeRate)
            posi.cost *= -1.

            if posi.callOrPutOrStock == ConstantValue.STOCK:
                posi.value = tradeDirection * posi.quantity * price
                posi.delta = posi.value
            else:
                volatility = posi.volatility + volatilityShift
                volatility = max(volatility, 0.0001)
                posi.volatility = volatility
                
                expiry = (posi.expiryDays - daysFromToday) / 365.
                
                if expiry <= 0:
                    posi.value = optionMultiplier * tradeDirection * posi.quantity * \
                            StrategyCalculatorProcessor.getProfitValue(posi.callOrPutOrStock, price, posi.strike)
                    posi.delta = 0
                    posi.gamma = 0 
                    posi.theta = 0
                    posi.vega = 0
                    posi.rho = 0
                else:
                    posi.value = optionMultiplier * tradeDirection * posi.quantity * bs.getValue(posi.callOrPutOrStock, price, posi.strike, volatility, expiry, dividendYield, riskfreeRate)
                    posi.delta = optionMultiplier * tradeDirection * posi.quantity * bs.getDelta(posi.callOrPutOrStock, price, posi.strike, volatility, expiry, dividendYield, riskfreeRate)
                    posi.delta *= price
                    posi.gamma = optionMultiplier * tradeDirection * posi.quantity * bs.getGamma(price, posi.strike, volatility, expiry, dividendYield, riskfreeRate)
                    posi.gamma = posi.gamma * 0.5 * price * price 
                    posi.theta = optionMultiplier * tradeDirection * posi.quantity * bs.getTheta(posi.callOrPutOrStock, price, posi.strike, volatility, expiry, dividendYield, riskfreeRate)
                    posi.vega = optionMultiplier * tradeDirection * posi.quantity * bs.getVega(price, posi.strike, volatility, expiry, dividendYield, riskfreeRate)
                    posi.rho = optionMultiplier * tradeDirection * posi.quantity * bs.getRho(posi.callOrPutOrStock, price, posi.strike, volatility, expiry, dividendYield, riskfreeRate)
            
        
    
    @staticmethod
    def calculateChart(strategyCalculatorDataBean):
        bs = BlackScholesCalculator()

        market = strategyCalculatorDataBean.marketInfo
        scenario = strategyCalculatorDataBean.scenario
        portfolio = strategyCalculatorDataBean.portfolio
        
        optionMultiplier = market.optionMultiplier

        daysFromToday = scenario.daysFromToday
        volatilityShift = scenario.volatility
        plRiskfreeRate = scenario.riskfreeRate
        
        # x-axis range
        stockPrice = scenario.stockPrice
        if stockPrice <= 0:
            stockPrice = market.stockPrice
        
        if strategyCalculatorDataBean.axisMin > 0:
            plMin = strategyCalculatorDataBean.axisMin
        else:
            plMin = stockPrice * 0.1
        if strategyCalculatorDataBean.axisMax > 0:
            plMax = strategyCalculatorDataBean.axisMax
        else:
            plMax = stockPrice * 2

        plMin = max(0.01, round(plMin, 2))
        
        priceStep = (float(plMax) - float(plMin)) / 36.
        priceStep = max(0.01, round(priceStep, 2))
        
        plMax = max(plMax, plMin + priceStep * 35.)
        
        # set Min and Max
        strategyCalculatorDataBean.axisMin = plMin
        strategyCalculatorDataBean.axisMax = plMax

        # limited to the minimum expiry days
        minExpiryDays = 0
        for posi in portfolio:
            if posi.callOrPutOrStock == ConstantValue.STOCK:
                continue
            if minExpiryDays == 0 or minExpiryDays > posi.expiryDays:
                minExpiryDays = posi.expiryDays
        daysFromToday = min(daysFromToday, minExpiryDays)
        scenario.daysFromToday = daysFromToday

        # series title
        if daysFromToday == 0:
            currentLabel = 'Today'
        else:
            strLabel = "{0:.0f}".format(daysFromToday)
            currentLabel = 'In ' + strLabel + '-days'
        
        dfcolumns = ['Stock Price', currentLabel, 'At Expiry', 'Time Value']
        results = pd.DataFrame([], columns=dfcolumns)
        
        xAxisSet = Set([])

        plCurrent = plMin
        while (plCurrent <= plMax):
            xAxisSet.add(plCurrent)
            plCurrent += priceStep

        for posi in portfolio:
            if posi.callOrPutOrStock != ConstantValue.STOCK:
                xAxisSet.add(posi.strike)
        
        xAxisList = list(xAxisSet)
        xAxisList.sort()
        #print 'xAxisList', xAxisList
        
        
        plCurrent = plMin
        #while (plCurrent <= plMax):
        for plCurrent in xAxisList:
            todayValue = 0.
            expiryValue = 0.
            totalPremium = 0.
        
            for posi in portfolio:
                tradeDirection = 1. if posi.buyOrSell == ConstantValue.BUY else -1.
                
                quantity = posi.quantity
                volatility = posi.volatility + volatilityShift
                volatility = max(volatility, 0.0001)
                
                expiryYears = (posi.expiryDays - daysFromToday) / 365.
                
                #totalPremium += posi.premium * tradeDirection * quantity
                if posi.callOrPutOrStock == ConstantValue.STOCK:
                    posCost = tradeDirection * posi.quantity * market.stockPrice
                else:
                    posCost = optionMultiplier * tradeDirection * posi.quantity * \
                        bs.getValue(posi.callOrPutOrStock, market.stockPrice, posi.strike, posi.volatility, max(posi.expiryDays, 0.0001) / 365., market.dividendYield, market.riskfreeRate)
                posCost *= -1.
                totalPremium += posCost
                
                if posi.callOrPutOrStock == ConstantValue.STOCK:
                    theTodayValue = plCurrent
                    theExpiryValue = plCurrent
                else:
                    if expiryYears > 0:
                        theTodayValue = optionMultiplier * bs.getValue(posi.callOrPutOrStock,
                                plCurrent,
                                posi.strike,
                                volatility,
                                expiryYears,
                                market.dividendYield,
                                plRiskfreeRate)
                    else:
                        theTodayValue = optionMultiplier * \
                                StrategyCalculatorProcessor.getProfitValue(posi.callOrPutOrStock, plCurrent, posi.strike)
                                
                    if posi.expiryDays > minExpiryDays:
                        theExpiryValue = optionMultiplier * bs.getValue(posi.callOrPutOrStock,
                                plCurrent,
                                posi.strike,
                                volatility,
                                (posi.expiryDays - minExpiryDays) / 365.,
                                market.dividendYield,
                                plRiskfreeRate)
                    else:
                        theExpiryValue = optionMultiplier * StrategyCalculatorProcessor.getProfitValue(
                                posi.callOrPutOrStock,
                                plCurrent,
                                posi.strike)
            
                todayValue += theTodayValue * tradeDirection * quantity
                expiryValue += theExpiryValue * tradeDirection * quantity
            
            theResult = pd.DataFrame([[plCurrent, todayValue + totalPremium, expiryValue + totalPremium, 0.]], columns=dfcolumns)
            #theResult = pd.DataFrame([[plCurrent, todayValue, expiryValue, 0.]], columns=dfcolumns)
            
            results = results.append(theResult)
        
            #plCurrent += priceStep
        
        results['Time Value'] = results[currentLabel] - results['At Expiry']
        
        chartValueDict = {'chartTitles':dfcolumns, 'chartValues': results}
        return ChartDataBean(**chartValueDict)

    @staticmethod
    def getProfitValue(optionType, stockPrice, strike):
        if optionType == ConstantValue.STOCK:
            return stockPrice
        elif optionType == ConstantValue.CALL:
            return max(stockPrice - strike, 0.)
        else:
            return max(strike - stockPrice, 0.)
        
 
