
import math

from scipy.stats import norm
from app.entity.ConstantValue import ConstantValue
from sympy.polys.polyoptions import OptionType


class BlackScholesCalculator:
    def getD1(self, stockPrice, strike, volatility, expiryYears, dividendYield, riskfreeRate):
        d1 = (math.log(stockPrice / strike, math.e) + (riskfreeRate - dividendYield + math.pow(volatility, 2) / 2.) * expiryYears) / \
                (volatility * math.sqrt(expiryYears));
        return d1
    
    def getD2(self, d1, volatility, expiryYears):
        d2 = d1 - volatility * math.sqrt(expiryYears)
        return d2
    
    def getValue(self, optionType, stockPrice, strike, volatility, expiryYears, dividendYield, riskfreeRate):
        d1 = self.getD1(stockPrice, strike, volatility, expiryYears, dividendYield, riskfreeRate)
        d2 = self.getD2(d1, volatility, expiryYears)
        
        if (optionType == ConstantValue.CALL):
            premium = stockPrice * math.exp(-1. * dividendYield * expiryYears) * norm.cdf(d1) - \
                    strike * math.exp(-1. * riskfreeRate * expiryYears) * norm.cdf(d2);
        else:
            premium = strike * math.exp(-1. * riskfreeRate * expiryYears) * norm.cdf(-d2) - \
                    stockPrice * math.exp(-1. * dividendYield * expiryYears) * norm.cdf(-d1);
                    
        return premium
    
    def getDelta(self, optionType, stockPrice, strike, volatility, expiryYears, dividendYield, riskfreeRate):
        d1 = self.getD1(stockPrice, strike, volatility, expiryYears, dividendYield, riskfreeRate);
        if (optionType == ConstantValue.CALL):
            delta = math.exp(-1. * dividendYield * expiryYears) * norm.cdf(d1)
        else:
            delta = -1. * math.exp(-1. * dividendYield * expiryYears) * norm.cdf(-d1)
        
        return delta
    
    def getGamma(self, stockPrice, strike, volatility, expiryYears, dividendYield, riskfreeRate):
        d1 = self.getD1(stockPrice, strike, volatility, expiryYears, dividendYield, riskfreeRate)
        gamma = math.exp(-1. * dividendYield * expiryYears) * norm.pdf(d1) / \
                (stockPrice * volatility * math.sqrt(expiryYears))
        return gamma
    
    def getVega(self, stockPrice, strike, volatility, expiryYears, dividendYield, riskfreeRate):
        d1 = self.getD1(stockPrice, strike, volatility, expiryYears, dividendYield, riskfreeRate);
        vega = stockPrice * math.exp(-1. * dividendYield * expiryYears) * norm.pdf(d1) * math.sqrt(expiryYears);
        return vega / 100.
    
    def getTheta(self, optionType, stockPrice, strike, volatility, expiryYears, dividendYield, riskfreeRate):
        d1 = self.getD1(stockPrice, strike, volatility, expiryYears, dividendYield, riskfreeRate);
        d2 = self.getD2(d1, volatility, expiryYears)
        
        phase1 = stockPrice * math.exp(-1. * dividendYield * expiryYears) * volatility * norm.pdf(d1) /\
                (2. * math.sqrt(expiryYears))
        
        if (optionType == ConstantValue.CALL):
            phase2 = riskfreeRate * strike * math.exp(-1. * riskfreeRate * expiryYears) * norm.cdf(d2)
            phase3 = dividendYield * stockPrice * math.exp(-1. * dividendYield * expiryYears) * norm.cdf(d1)
            
            theta = -phase1 - phase2 + phase3
        else:
            phase2 = riskfreeRate * strike * math.exp(-1. * riskfreeRate * expiryYears) * norm.cdf(-d2)
            phase3 = dividendYield * stockPrice * math.exp(-1. * dividendYield * expiryYears) * norm.cdf(-d1)

            theta = -phase1 + phase2 - phase3
        return theta / 365.
    
    def getRho(self, type, stockPrice, strike, volatility, expiryYears, dividendYield, riskfreeRate):
        d1 = self.getD1(stockPrice, strike, volatility, expiryYears, dividendYield, riskfreeRate);
        d2 = self.getD2(d1, volatility, expiryYears)
        if (type == ConstantValue.CALL):
            rho = strike * expiryYears * math.exp(-riskfreeRate * expiryYears) * norm.cdf(d2)
        else:
            rho = -1. * strike * expiryYears * math.exp(-riskfreeRate * expiryYears) * norm.cdf(-d2)
        
        return rho / 100.
        