#!/usr/bin/env python
# coding: utf-8

import numpy as np
import abc
from scipy.optimize import brentq
from scipy.integrate import quad
from scipy.special import erf
from functools import partial
from functools import lru_cache
from ito_diffusions import SABR
from volmodels import Vol_model, ONE_BP

# ## SABR generic class
# Possible SABR calculations include :
# * Hagan normal implied vol
# * Hagan lognormal implied
# * Monte Carlo
class SABR_base_model(Vol_model):
    """Generic class for SABR model
    """
    def __init__(self,
                 beta: float=1.0,
                 vov: float=1.0,
                 rho: float=0.0,
                 ATM=None,
                 sigma_0=None,
                 f=None,
                 T_expiry: float=1.0,
                 vol_type=None,
                 marking_mode: str='ATM',
                 logmoneyness_lo=None,
                 logmoneyness_hi=None,
                 K_lo=None,
                 K_hi=None,
                 strike_type: str='logmoneyness',
                 n_strikes: int=50
                ) -> None:
        """Implementation of SABR implied vol and approximated price formula.
        beta, vov and rho are marked.
        One can either mark the ATM vol and the forward and solve for sigma_0,
        or mark sigma_0 directly.
        """
        super().__init__(f=f,
                         T_expiry=T_expiry,
                         vol_type=vol_type,
                         logmoneyness_lo=logmoneyness_lo,
                         logmoneyness_hi=logmoneyness_hi,
                         K_lo=K_lo,
                         K_hi=K_hi,
                         strike_type=strike_type,
                         n_strikes=n_strikes
                        )
        self._beta = beta
        self._vov = vov
        self._rho = rho

        # ATM or sigma_0
        self._marking_mode = marking_mode

        self._ATM = ATM
        self._sigma_0 = sigma_0

        if self._marking_mode == 'ATM' and self._ATM == None:
            raise NameError( "ATM must be marked in ATM marking mode" )
        elif self._marking_mode == 'sigma_0' and self._sigma_0 == None:
            raise NameError( "sigma_0 must be marked in sigma_0 marking mode" )

    def __str__(self):
        return r'T_expiry={}y, $\beta$={:.2f}, vov={:.2%}, $\rho$={:.2%}, f={:.2%}, $\sigma_0$={:.2%}'    .format(self.T_expiry, self.beta, self.vov, self.rho, self.f, self.sigma_0)

    @property
    def beta(self):
        return self._beta
    @beta.setter
    def beta(self, new_beta):
        self._beta = new_beta

    @property
    def rho(self):
        return self._rho
    @rho.setter
    def rho(self, new_rho):
        self._rho = new_rho

    @property
    def vov(self):
        return self._vov
    @vov.setter
    def vov(self, new_vov):
        self._vov = new_vov

    @property
    def ATM_LN(self):
        if self.vol_type == 'LN':
            return self.ATM
        elif self.vol_type == 'N':
            return self.ATM/self.f
    @property
    def ATM_N(self):
        if self.vol_type == 'N':
            return self.ATM
        elif self.vol_type == 'LN':
            return self.ATM*self.f

    @property
    def ATM(self):
        if self.marking_mode == 'ATM':
            return self._ATM
        elif self.marking_mode == 'sigma_0':
            return self.ATM_func(self.sigma_0)
    @ATM.setter
    def ATM(self, new_ATM):
        if self.marking_mode == 'sigma_0':
            raise NameError( "Impossible to mark ATM in sigma_0 marking mode" )
        elif self.marking_mode == 'ATM':
            self._ATM = new_ATM

    @property
    def sigma_0(self):
        if self.marking_mode == 'ATM':
            if self.vol_type == 'LN':
                try:
                    return brentq(lambda sigma_0: self.ATM_func(sigma_0) - self.ATM,                                  self.ATM * (self.f ** (1-self.beta))-0.1,                                  self.ATM * (self.f ** (1-self.beta))+0.1)
                except:
                    return self.ATM * (self.f ** (1-self.beta))
            elif self.vol_type == 'N':
                try:
                    return brentq(lambda sigma_0: self.ATM_func(sigma_0) - self.ATM,                                  self.ATM * (self.f ** (-self.beta))-0.1,                                  self.ATM * (self.f ** (-self.beta))+0.1)
                except:
                    return self.ATM * (self.f ** (-self.beta))
        elif self.marking_mode == 'sigma_0':
            return self._sigma_0
    @sigma_0.setter
    def sigma_0(self, new_sigma_0):
        if self.marking_mode == "ATM":
            raise NameError( "Impossible to mark sigma_0 in ATM marking mode" )
        elif self.marking_mode == 'sigma_0':
            self._sigma_0 = new_sigma_0

    @property
    def marking_mode(self):
        if self._marking_mode not in ['ATM', 'sigma_0']:
            raise NameError( "Unsupported marking mode : {}".format(self._marking_mode) )
        else:
            return self._marking_mode
    @marking_mode.setter
    def marking_mode(self, new_marking_mode):
        self._marking_mode = new_marking_mode

    def ATM_LN_func(self, sigma_0):
        """Reduction of Hagan's lognormal formula when K --> f
        Used to calibrate sigma_0 to marked ATM vol
        """
        return sigma_0/(self.f**(1-self.beta))*    (1+(((1-self.beta)**2)/24*sigma_0**2/(self.f**(2-2*self.beta))     +0.25*self.rho*self.beta*self.vov*sigma_0/(self.f**(1-self.beta))     +(2-3*self.rho**2)/24*self.vov**2)*self.T_expiry)

    def ATM_N_func(self, sigma_0):
        """Reduction of Hagan's normal formula when K --> f
        Used to calibrate sigma_0 to marked ATM vol
        """
        return sigma_0 * (self.f**self.beta)*    (1+     ((-self.beta*(2-self.beta)*sigma_0**2)/(24*self.f**(2-2*self.beta))     +0.25*self.rho*self.vov*self.beta*sigma_0/(self.f**(1-self.beta))     +(2-3*self.rho**2)/24*self.vov**2)*self.T_expiry)

    def ATM_func(self, sigma_0):
        if self.vol_type == 'LN':
            return self.ATM_LN_func(sigma_0)
        elif self.vol_type == 'N':
            return self.ATM_N_func(sigma_0)

    @abc.abstractmethod
    def smile_func(self, K):
        pass

# ### Hagan lognormal (Black) implied vol expansion
class SABR_Hagan_LN(SABR_base_model):
    """SABR model using lognormal implied vol quoting from Hagan's formula
    """
    def __init__(self,
                 beta: float=1.0,
                 vov: float=1.0,
                 rho: float=0.0,
                 ATM=None,
                 sigma_0=None,
                 f=None,
                 T_expiry: float=1.0,
                 marking_mode: str='ATM',
                 logmoneyness_lo=None,
                 logmoneyness_hi=None,
                 K_lo=None,
                 K_hi=None,
                 strike_type: str='logmoneyness',
                 n_strikes: int=50,
                ) -> None:
        super().__init__(beta=beta,
                         vov=vov,
                         rho=rho,
                         ATM=ATM,
                         sigma_0=sigma_0,
                         f=f,
                         T_expiry=T_expiry,
                         vol_type='LN',
                         marking_mode=marking_mode,
                         logmoneyness_lo=logmoneyness_lo,
                         logmoneyness_hi=logmoneyness_hi,
                         K_lo=K_lo,
                         K_hi=K_hi,
                         strike_type=strike_type,
                         n_strikes=n_strikes
                        )

    def __str__(self):
        return r'$T_expiry={}y, \beta$={:.2f}, vov={:.2%}, $\rho$={:.2%}, ATM={:.2%}, f={:.2%}, $\sigma_0$={:.2%}'    .format(self.T_expiry, self.beta, self.vov, self.rho, self.ATM, self.f, self.sigma_0)

    @property
    def model_name(self):
        return 'SABR_Hagan_LN'

    def smile_func(self, K):
        """Hagan lognormal smile approximation around ATM as written in the
        original 'Managing Smile Risk' paper (A.69c)
        K: strike
        """
        if np.abs(K-self.f)<ONE_BP/100:
            return self.ATM
        else:
            log_moneyness = np.log(self.f/K)
            zeta = self.vov/self.sigma_0*(self.f*K)**((1-self.beta)/2)*log_moneyness
            q_zeta = 1-2*self.rho*zeta+zeta**2
            x_zeta = np.log((np.sqrt(q_zeta)-self.rho+zeta)/(1-self.rho))
            f_avg = np.sqrt(self.f*K)
            return self.sigma_0/                                (f_avg**(1-self.beta)*(1                                    +((1-self.beta)**2)/24*log_moneyness**2                                   +((1-self.beta)**4)/1920*log_moneyness**4))*    (zeta/x_zeta)*    (1+(((1-self.beta)**2)/24*self.sigma_0**2/(f_avg**(2-2*self.beta))     +0.25*self.rho*self.beta*self.vov*self.sigma_0/(f_avg**(1-self.beta))     +(2-3*self.rho**2)/24*self.vov**2)*self.T_expiry)

# ### Hagan normal implied vol expansion
class SABR_Hagan_N(SABR_base_model):
    """SABR model using normal implied vol quoting from Hagan's formula
    """
    def __init__(self,
                 beta: float=1.0,
                 vov: float=1.0,
                 rho: float=0.0,
                 ATM=None,
                 sigma_0=None,
                 f=None,
                 T_expiry:float =1.0,
                 marking_mode: str='ATM',
                 logmoneyness_lo=None,
                 logmoneyness_hi=None,
                 K_lo=None,
                 K_hi=None,
                 strike_type: str='logmoneyness',
                 n_strikes: int=50,
                ):
        super().__init__(beta=beta,
                         vov=vov,
                         rho=rho,
                         ATM=ATM,
                         sigma_0=sigma_0,
                         f=f,
                         T_expiry=T_expiry,
                         vol_type='N',
                         marking_mode=marking_mode,
                         logmoneyness_lo=logmoneyness_lo,
                         logmoneyness_hi=logmoneyness_hi,
                         K_lo=K_lo,
                         K_hi=K_hi,
                         strike_type=strike_type,
                         n_strikes=n_strikes
                        )

    def __str__(self):
        return r'T_expiry={}y, $\beta$={:.2f}, vov={:.2%}, $\rho$={:.2%}, ATM={:.2f}bps, f={:.2%}, $\sigma_0$={:.2%}'    .format(self.T_expiry, self.beta, self.vov, self.rho, self.ATM/ONE_BP, self.f, self.sigma_0)

    @property
    def model_name(self):
        return 'SABR_Hagan_N'

    def smile_func(self, K):
        """Hagan normal smile approximation around ATM as written in the
        original 'Managing Smile Risk' paper (A.69a)
        K: strike
        """
        if np.abs(K-self.f)<ONE_BP/100:
            return self.ATM
        else:
            f_avg = np.sqrt(self.f*K)
            log_moneyness = np.log(self.f/K)
            zeta = self.vov/self.sigma_0*(f_avg)**(1-self.beta)*log_moneyness
            q_zeta = 1-2*self.rho*zeta+zeta**2
            x_zeta = np.log((np.sqrt(q_zeta)-self.rho+zeta)/(1-self.rho))
            g = -self.beta*(2-self.beta)/(24*(f_avg)**(2-2*self.beta))
            return self.sigma_0*(f_avg)**(self.beta)*        (1+1/24*log_moneyness**2+1/1920*log_moneyness**4)/        (1         +((1-self.beta)**2)/24*log_moneyness**2         +((1-self.beta)**4)/1920*log_moneyness**4)*    (zeta/x_zeta)*    (1+     (g*self.sigma_0**2      +0.25*self.rho*self.vov*self.sigma_0*self.beta/(f_avg**(1-1*self.beta))      +(2-3*self.rho**2)/24*self.vov**2)*self.T_expiry)

    def smile_func_full(self, K):
        """Hagan normal smile approximation around ATM as written in the
        original 'Managing Smile Risk' paper (A.67a)
        K: strike
        """
        if np.abs(K-self.f)<ONE_BP/100:
            return self.ATM
        else:
            #local_vol_int = (self.f**(1-self.beta)-K**(1-self.beta))/(1-self.beta)
            f_avg = np.sqrt(self.f*K)
            #f_avg = 0.5*(self.f+K)
            zeta = self.vov/self.sigma_0*(self.f-K)/(f_avg**self.beta)
            q_zeta = 1-2*self.rho*zeta+zeta**2
            x_zeta = np.log((np.sqrt(q_zeta)-self.rho+zeta)/(1-self.rho))
            g = -self.beta*(2-self.beta)/(24*(f_avg)**(2-2*self.beta))
            return self.sigma_0*(1-self.beta)*(self.f-K)        /(self.f**(1-self.beta)-K**(1-self.beta))*(zeta/x_zeta)*        (1+         (g*self.sigma_0**2+0.25*self.rho*self.vov*self.sigma_0*self.beta/(f_avg**(1-self.beta))          +(2-3*self.rho**2)/24*self.vov**2)         *self.T_expiry)

# Monte Carlo pricer, by discretizing the SABR SDEs
class SABR_MC(SABR_base_model):
    """SABR model with Monte Carlo pricing
    """
    def __init__(self,
                 beta: float=1.0,
                 vov: float=1.0,
                 rho: float=0.0,
                 ATM=None,
                 sigma_0=None,
                 f=None,
                 T_expiry: float=1.0,
                 vol_type: str='LN',
                 marking_mode: str='ATM',
                 logmoneyness_lo=None,
                 logmoneyness_hi=None,
                 K_lo=None,
                 K_hi=None,
                 strike_type: str='logmoneyness',
                 n_strikes: int=50,
                 n_sim: int = int(1e3),
                 scheme_steps: int=int(1e2)
                ) -> None:
        super().__init__(beta=beta,
                         vov=vov,
                         rho=rho,
                         ATM=ATM,
                         sigma_0=sigma_0,
                         f=f,
                         T_expiry=T_expiry,
                         vol_type=vol_type,
                         marking_mode=marking_mode,
                         logmoneyness_lo=logmoneyness_lo,
                         logmoneyness_hi=logmoneyness_hi,
                         K_lo=K_lo,
                         K_hi=K_hi,
                         strike_type=strike_type,
                         n_strikes=n_strikes
                        )
        self._n_sim = n_sim
        self._scheme_steps = scheme_steps

    @property
    def model_name(self):
        return 'SABR_Hagan_MC'

    @property
    def n_sim(self):
        return self._n_sim
    @n_sim.setter
    def n_sim(self, new_n_sim):
        self._n_sim = new_n_sim

    @property
    def scheme_steps(self):
        return self._scheme_steps
    @scheme_steps.setter
    def scheme_steps(self, new_scheme_steps):
        self._scheme_steps = new_scheme_steps

    @property
    def MC_generator(self):
        return SABR(x0=[self.f, self.sigma_0], T=self.T_expiry,                        scheme_steps=self._scheme_steps, keys=['F', 'vol'],                        beta=self.beta, vov=self.vov, rho=self.rho,                        barrier=[0,None], barrier_condition='absorb')

    @property
    @lru_cache(maxsize=None)
    def MC_paths(self):
        gen = self.MC_generator
        F_terminal_samples = []
        for i in range(self.n_sim):
            df = gen.simulate()
            F_terminal_samples.append(df['F'].iloc[-1])
        return F_terminal_samples

    def clear_MC_cache(self):
        self.MC_paths.cache_clear()

    def payoff_helper(self, payoff):
        """Returns a lambda with the specification of the payoff name
        """
        if payoff == 'Call':
            return lambda X, K: max(X-K,0)
        elif payoff == 'Put':
            return lambda X, K: max(K-X, 0)
        else:
            raise NameError( "Unsupported payoff name : {}".format(payoff) )

    def option_price(self, K, payoff='Call', regen_paths=False):
        """Returns the call/put price estimated from Monte Carlo simulations
        of the spot/vol SABR dynamics
        """
        payoff_func = self.payoff_helper(payoff)
        payoff_sims = []

        if regen_paths:
            gen = self.MC_generator
            for i in range(self.n_sim):
                df = gen.simulate()
                F_terminal = df['F'].iloc[-1]
                payoff_sims.append(payoff_func(F_terminal, K))
        else:
            F_terminal_samples = self.MC_paths
            for F_terminal in F_terminal_samples:
                payoff_sims.append(payoff_func(F_terminal, K))
        return np.mean(payoff_sims)

    def smile_func(self, K):
        """Implied vol smile converted from option Monte Carlo prices
        K: strike
        """
        if K <= self.f:
            payoff = 'Put'
        else:
            payoff = 'Call'

        price = self.option_price(K, payoff=payoff)
        return self.IV.vol_from_price(price, self.f, K, self.T_expiry, payoff=payoff)

# Normal SABR
# SABR formula for $\beta=0$ with an adjusted $\sigma_0$ to account for the actual local vol in the model.
class SABR_Goes_Normal(SABR_base_model):
    """SABR model with beta = 0 and ajusted sigma_0 to account for the actual local vol.
    Lognormal quoting.
    """
    def __init__(self,
                 beta: float=1.0,
                 vov: float=1.0,
                 rho: float=0.0,
                 ATM=None,
                 sigma_0=None,
                 f=None,
                 T_expiry: float=1.0,
                 vol_type: str='LN',
                 marking_mode: str='ATM',
                 logmoneyness_lo=None,
                 logmoneyness_hi=None,
                 K_lo=None,
                 K_hi=None,
                 strike_type: str='logmoneyness',
                 n_strikes: int=50,
                 n_integral: int=1
                ) -> None:
        super().__init__(beta=beta,
                         vov=vov,
                         rho=rho,
                         ATM=ATM,
                         sigma_0=sigma_0,
                         f=f,
                         T_expiry=T_expiry,
                         vol_type=vol_type,
                         marking_mode=marking_mode,
                         logmoneyness_lo=logmoneyness_lo,
                         logmoneyness_hi=logmoneyness_hi,
                         K_lo=K_lo,
                         K_hi=K_hi,
                         strike_type=strike_type,
                         n_strikes=n_strikes
                        )
        # number of discretization step of the integral in the approximation formula
        self._n_integral = n_integral

    @property
    def n_integral(self):
        return self._n_integral
    @n_integral.setter
    def n_integral(self, new_n_integral):
        self._n_integral = new_n_integral

    @property
    def step_integral(self):
        return self.T_expiry/self._n_integral

    def sigma_0_effective(self, K):
        """Adjusted initial vol to match first order of ATM expansion when using
        normal SABR as base model
        """
        if np.abs(K-self.f)<ONE_BP/100:
            return self.sigma_0*(self.f**self.beta)
        else:
            return self.sigma_0*(1-self.beta)*(K-self.f)/(K**(1-self.beta)-self.f**(1-self.beta))

    def vov_scaled(self, K):
        """Helper function for the call/put price approximation
        """
        return self.vov/self.sigma_0_effective(K)

    def q(self, z, K):
        """Helper function for the call/put price approximation
        """
        return 1-2*self.rho*self.vov_scaled(K)*z+(self.vov_scaled(K)**2)*(z**2)

    def J(self, x, K):
        """Helper function for the call/put price approximation
        """
        X_0 = self.f-K
        num = np.sqrt(self.q(x, K))-self.rho+self.vov_scaled(K)*x
        denom = np.sqrt(self.q(X_0, K))-self.rho+self.vov_scaled(K)*X_0
        return 1/(self.vov)*np.log(num/denom)

    def dJ(self, x, K):
        """Helper function for the call/put price approximation
        """
        return 1/(self.sigma_0_effective(K)*np.sqrt(self.q(x, K)))

    def local_time(self, t, x, K=None):
        """Density of (S_t-K)/(alpha_t) at x under the stochastic vol measure
        """
        return 1/np.sqrt(2*np.pi*t)*self.dJ(x, K)*np.exp(-self.J(x, K)**2/(2*t))

    def integrand(self, K, t, x):
        """Integrand to compute E[J^-1(W_t)], used as an adjustment for the missing drift
        """
        return self.local_time(t, x, K)*x

    def option_price(self, K, payoff='Call'):
        """Returns the call/put price approximation derived for normal SABR
        in "SABR Goes Normal".
        """
        if payoff == "Call":
            return self.call_price(K)
        elif payoff == "Put":
            return self.call_price(K) + (K-self.f)

    def int_local_time(self, t, K):
        """Explicit form for the integral of the local time between 0 and t
        """
        dJ = self.dJ(0, K)
        J = self.J(0, K)
        return dJ*np.exp(-J**2/(2*t))*np.sqrt(2/np.pi)*np.sqrt(t)    +dJ*J*(np.erf(J/(np.sqrt(2*t)))-1)

    def call_price(self, K):
        """Returns the call price approximation derived from normal SABR
        """
        X_0 = self.f-K
        intrinsic_value = max(X_0, 0)

        #s = self.int_local_time(self.T_expiry, K)

        s = 0
        time_steps = [t*self.step_integral for t in range(self.n_integral)]
        for t in time_steps:
            t_next = t + self.step_integral
            t_mid = 0.5*(t+t_next)

            f = partial( self.integrand, K, t_mid )
            L = X_0 - quad(f, -np.inf, np.inf, limit=10)[0]
            s += self.local_time(t_mid, -L, K=K)*self.step_integral
        return intrinsic_value+0.5*self.sigma_0_effective(K)**2*s

class SABR_Goes_Normal_LN(SABR_Goes_Normal):
    """SABR model with beta = 0 and ajusted sigma_0 to account for the actual local vol.
    Lognormal quoting.
    """
    def __init__(self,
                 beta: float=1.0,
                 vov: float=1.0,
                 rho: float=0.0,
                 ATM=None,
                 sigma_0=None,
                 f=None,
                 T_expiry: float=1.0,
                 marking_mode: str='ATM',
                 logmoneyness_lo=None,
                 logmoneyness_hi=None,
                 K_lo=None,
                 K_hi=None,
                 strike_type: str='logmoneyness',
                 n_strikes: int=50,
                 n_integral: int=1
                ) -> None:
        super().__init__(beta=beta,
                         vov=vov,
                         rho=rho,
                         ATM=ATM,
                         sigma_0=sigma_0,
                         f=f,
                         T_expiry=T_expiry,
                         vol_type='LN',
                         marking_mode=marking_mode,
                         logmoneyness_lo=logmoneyness_lo,
                         logmoneyness_hi=logmoneyness_hi,
                         K_lo=K_lo,
                         K_hi=K_hi,
                         strike_type=strike_type,
                         n_strikes=n_strikes,
                         n_integral=n_integral
                        )

    @property
    def model_name(self):
        return 'SABR_Goes_Normal_LN'

    def smile_func(self, K):
        """Hagan normal smile approximation around ATM for beta = 0 as written in the
        original 'Managing Smile Risk' paper (A.70b)
        K: strike
        """
        b_0 = self.sigma_0_effective(K)
        if np.abs(K-self.f)<ONE_BP/100:
            return b_0/self.f*(1                               + ((b_0**2)/(24*self.f**2) + (2-3*self.rho**2)/24*self.vov**2)                               *self.T_expiry)
        else:
            log_moneyness = np.log(self.f/K)
            f_avg = np.sqrt(self.f*K)
            zeta = self.vov/b_0*f_avg*log_moneyness
            q_zeta = 1-2*self.rho*zeta+zeta**2
            x_zeta = np.log((np.sqrt(q_zeta)-self.rho+zeta)/(1-self.rho))

            return b_0*log_moneyness/(self.f-K)*zeta/x_zeta*                (1 + ((b_0**2)/(24*self.f*K) + (2-3*self.rho**2)/24*self.vov**2)*self.T_expiry)

class SABR_Goes_Normal_N(SABR_Goes_Normal):
    """SABR model with beta = 0 and ajusted sigma_0 to account for the actual local vol.
    Normal quoting.
    """
    def __init__(self,
                 beta: float=1.0,
                 vov: float=1.0,
                 rho: float=0.0,
                 ATM=None,
                 sigma_0=None,
                 f=None,
                 T_expiry: float=1.0,
                 marking_mode: str='ATM',
                 logmoneyness_lo=None,
                 logmoneyness_hi=None,
                 K_lo=None,
                 K_hi=None,
                 strike_type: str='logmoneyness',
                 n_strikes: int=50,
                 n_integral: int=1
                ) -> None:
        super().__init__(beta=beta,
                         vov=vov,
                         rho=rho,
                         ATM=ATM,
                         sigma_0=sigma_0,
                         f=f,
                         T_expiry=T_expiry,
                         vol_type='N',
                         marking_mode=marking_mode,
                         logmoneyness_lo=logmoneyness_lo,
                         logmoneyness_hi=logmoneyness_hi,
                         K_lo=K_lo,
                         K_hi=K_hi,
                         strike_type=strike_type,
                         n_strikes=n_strikes,
                         n_integral=n_integral,
                        )

    @property
    def model_name(self):
        return 'SABR_Goes_Normal_N'

    def smile_func(self, K):
        """Hagan normal smile approximation around ATM for beta = 0 as written in the
        original 'Managing Smile Risk' paper (A.70a)
        K: strike
        """
        b_0 = self.sigma_0_effective(K)
        if np.abs(K-self.f)<ONE_BP/100:
            return b_0*(1                        + ((2-3*self.rho**2)/24*self.vov**2)*self.T_expiry)
        else:
            log_moneyness = np.log(self.f/K)
            f_avg = np.sqrt(self.f*K)
            zeta = self.vov/b_0*f_avg*log_moneyness
            q_zeta = 1-2*self.rho*zeta+zeta**2
            x_zeta = np.log((np.sqrt(q_zeta)-self.rho+zeta)/(1-self.rho))

            return b_0*zeta/x_zeta*        (1+((2-3*self.rho**2)/24*self.vov**2)*self.T_expiry)


from advent.utils import filetostringlist
from typing import List
from collections import Counter

def enumerate_list(inputList: List[str]) -> dict:
    elements = {}
    for line in inputList:
        for idx, val in enumerate(line):
            elements.setdefault(idx, [])
            elements[idx].append(val)
    return elements

def get_most_common(inputList: List[str], reverse: bool = False) -> str:
    elements = enumerate_list(inputList)
    binary = ""
    for idx in elements:
        counter = Counter(elements[idx])
        try:
            if counter.most_common(2)[0][1] == counter.most_common(2)[1][1]:
                binary += str(1)
                continue
        except IndexError:
            binary += str(counter.most_common(1)[0][0])
            continue
        binary += str(counter.most_common(1)[0][0])

    if reverse:
        return reverse_binary(binary)
    else:
        return binary

def reverse_binary(inputString: str) -> str:
    return inputString.replace("1", "2").replace("0", "1").replace("2", "0")

def life_support_rating(inputList: List[str], reverse: bool) -> str:
    newlist = inputList
    gammaRate = get_most_common(newlist, reverse)
    for i in range(len(gammaRate)):
        gammaRate = get_most_common(newlist, reverse)
        removals = []
        for row in newlist:
            if row[i] != gammaRate[i]:
                removals.append(row)
        for row in removals:
            newlist.remove(row)
        if len(newlist) == 1:
            return str(newlist[0])

def multiply(binarya: str, binaryb: str) -> int:
    return int(int(binarya, 2) * int(binaryb, 2))

def calculate(inputList: List[str]) -> int:
    gammaRate = get_most_common(inputList)
    epsilonRate = get_most_common(inputList, True)

    return multiply(gammaRate, epsilonRate)

if __name__ == "__main__":
    inputList = filetostringlist("advent/three/input.txt")
    print(calculate(inputList))

    oxygenRate = life_support_rating(filetostringlist("advent/three/input.txt"), False)
    cosRate = life_support_rating(filetostringlist("advent/three/input.txt"), True)
    print(multiply(oxygenRate, cosRate))


# -*- coding: utf-8 -*-

import cv2
import numpy as np
from matplotlib import pyplot as plt
import functions as f

import sys
sys.path.append('MIDI/src/midiutil')

from MidiFile3 import MIDIFile

tabla_sol = [77,76,74,72,71,69,67,65,64,62,60,59,57,55,53,52,50]
kernel = np.ones((2,2),np.uint8)
MyMIDI = MIDIFile(1)

def line_separator(img,color):
    sx,sy = np.size(img,0),np.size(img,1)
    c = 0
    ca =[]
    for i in range(0,sx):
        temp = False
        for j in range(0,sy):
            if(img[i][j] != color):
                temp = True
        if temp == True:
            c = c+1
        elif(c != 0):
            ca.append([c,i-c,i])
            c = 0
    return ca

def ar_line_separator(ar,color):
    ans = []
    for i in range(0,len(ar)):
        ans.append(line_separator(ar[i],255))
    return ans

def separator_blocks(img,cord):
    images = []
    for i in range(0,np.size(cord,0)):
        #For each one
        csize = cord[i][0]
        sy = np.size(img,1)
        cstart = cord[i][1]
        temp = np.zeros([csize,sy],dtype=np.uint8)
        for n in range(0,csize):
            for m in range(0,sy):
                temp[n][m] = img[n + cstart][m]
        temp = agrandar(temp,30,30)
        images.append(temp)
    return images

def ar_separator_blocks(ar,cords):
    ans = []
    for i in range(0,len(ar)):
        ans.append(separator_blocks(ar[i],cords[i]))

    return ans
def EliminateLines(img):
    im = np.copy(img)
    sx = int(im.shape[0])
    sy = int(im.shape[1])
    #Another Tresshold
    for i in range(0,sx):
        for j in range(0,sy):
            if(im[i][j]>=210):
                im[i][j] = 255
            else:
                im[i][j] = 0
    im = ~im
    imgFinal = np.copy(im)
    (rows, cols) = im.shape
    estructura = np.array([[1],
              [1],
             [1]])
    #Falta por convertir a manual
    imgFinal = cv2.erode(imgFinal, estructura, iterations = 1);
    imgFinal = cv2.dilate(imgFinal, estructura, iterations = 1);
    imgFinal = cv2.bitwise_not(imgFinal)
    return imgFinal

"""
test = f.loadimg("test.png")
f.show(test)
test = f.to_grey(test)
test = cv2.adaptiveThreshold(test,255,cv2.ADAPTIVE_THRESH_MEAN_C,cv2.THRESH_BINARY,15,-2)
f.saveimg(test)
data = line_separator(test)
result = separator_blocks(test,data)
#mg(test)
"""
#Demo1
#Demo

def get_body(image):
    gray = cv2.medianBlur(image, 5)
    dst2 = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2)
    return dst2

def transpose_all(ar):
    for i in range(0,np.size(ar,0)):
        ar[i] = cv2.transpose(ar[i])

    return ar

def transponse_arr_all(ar):
    for i in range(0,len(ar)):
        ar[i]= transpose_all(ar[i])
    return ar

#test = f.tresshold(test,100)

#test = cv2.adaptiveThreshold(test,255,cv2.ADAPTIVE_THRESH_MEAN_C,cv2.THRESH_BINARY,15,-2)

"""
test = f.loadimg("test2.jpg")
test = f.to_grey(test)
f.saveimg(test,"imagen.png")
f.show(test)

clear = EliminateLines(test)
clear = f.tresshold(clear,50)
data = line_separator(test,255)

result_1 = separator_blocks(clear,data)
result_2 = transpose_all(result_1)
result_3 = ar_line_separator(result_2,255)
result_4 = ar_separator_blocks(result_2,result_3)
result_5 = transponse_arr_all(result_4)
"""

def agrandar(image,pixeles,n):
    sx,sy = np.size(image,0), np.size(image,1)
    img = np.ones([ sx+ pixeles + n,sy + pixeles + n],dtype = np.uint8) * 255
    for i in range(0,sx):
        for j in range(0,sy):
            img[i + 1][j + 1] = image[i][j]
    return img

def show_circles(image):
    img = np.copy(image)
    img = f.tresshold(img,200)
    #img = cv2.resize(img,[np.size(img,0)*2,np.size(img,1)*2])
    cimg = cv2.cvtColor(img,cv2.COLOR_GRAY2BGR)
    circles = cv2.HoughCircles(img,cv2.HOUGH_GRADIENT,1,1,param1=1,param2=7,minRadius=1,maxRadius=40)
    circles = np.uint8(np.around(circles))
    for i in circles[0,:]:

       cv2.circle(cimg,(i[0],i[1]),2,(0,0,255),3,0)
    f.showc(cimg)
    pass

def delete_borders(image):
    data = line_separator(np.transpose(agrandar(image,2,2)),255)
    data = data[0]
    new_image = np.ones([np.size(image,0),data[0]],dtype = np.uint8) * 255
    for i in range(0,np.size(image,0)):
        for j in  range(0,data[0]):
            new_image[i][j] = image[i][j+data[1]]
    return new_image

def fcorchea(image):
    e = False
    total = 0
    image = agrandar(image,2,2)
    image = cv2.dilate(image,kernel)
    image = delete_borders(image)
    l1,l2,l3,l4 = 0,0,0,0
    sx, sy = np.size(image,0), np.size(image,1)
    for i in range(0,int(sx/2)):
        for j in range(0,int(sy/2)):
            if(image[i][j] < 10):
                l1 = l1 + 1
        for j in range(int(sy/2),sy):
            if(image[i][j] < 10):
                l2 = l2 + 1
    for i in range(int(sx/2),sx):
        for j in range(0,int(sy/2)):
            if(image[i][j] < 10):
                l3 = l3 + 1
                image[i][j] = 0
        for j in range(int(sy/2),sy):
            if(image[i][j] < 10):
                l4 = l4 + 1

    answe = [l1,l2,l3,l4]
    total = l1 + l2 + l3 + l4
    answe = np.sort(answe)
    sup = answe[3]
    inf = answe[0]
    if(answe[0] < 5):
        sup = answe[3]
        inf = answe[1]
        med = answe[2]
        val = sup/max(1,inf)
        if(val > 3):
            e = True
    elif((inf/sup)<1.3 and (inf/sup)>0.8):
        e = False
    if(answe[1] <5):
        e = False
    return e

def Erosion(im,pixel,mascara):
    val = False
    for i in range(0,1):
        for j in range(0,mascara.shape[0]):
            if im[pixel[0]+i][pixel[1]+j]==mascara[j]:
                val = True
            else:
                val = False
                break
        if(val == False):
            break
    if(val == False):
        im[pixel[0]][pixel[1]] = 255

def Dilatacion(im,pixel,mascara):
    for i in range(0,1):
        for j in range(0,mascara.shape[0]):
            im[pixel[0]+i][pixel[1]+j]=mascara[j]

def BorrarLineas(img, mascara):
    im = np.copy(img)
    sx = int(im.shape[0])
    sy = int(im.shape[1])
    for i in range(0,sx):
        for j in range(0,sy):
            if(im[i][j]>=210):
                im[i][j] = 255
            else:
                im[i][j] = 0
    for i in range(0,im.shape[0]):
        for j in range(0,im.shape[1]):
            if(im[i][j]==0 and i+4<=im.shape[0] and j+4<=im.shape[1]):
                pixel = [i,j]
                Erosion(im,pixel,mascara)

    im2 = np.copy(im)
    for i in range(0,im.shape[0]):
        for j in range(0,im.shape[1]):
            if(im2[i][j]==0 and i+4<=im.shape[0] and j+4<=im.shape[1]):
                pixel = [i,j]
                Dilatacion(im,pixel,mascara)
    return im

def eliminar_repetidas(arr):
    ans = np.copy(arr)
    to_delete = []
    rango = 3
    sz = np.size(arr,0)
    for i in range(sz):
        for n in range(i + 1,sz):
            a = abs(arr[i][0] - arr[n][0])
            b = abs(arr[i][1] - arr[n][1])
            if((a < rango) and (b < rango)):
                to_delete.append(n)
    to_delete = np.unique(to_delete)
    ans= np.delete(arr,to_delete,0)
    return ans

def templates(imag, templ, templPos, tipo):
    imagen_gray = f.to_grey(imag)
    w, h = templ.shape[::-1]
    res = cv2.matchTemplate(imagen_gray, templ ,cv2.TM_CCOEFF_NORMED)
    threshold = 0.7
    loc = np.where(res >= threshold)

    for pt in zip(*loc[::-1]):
        cv2.rectangle(imag, pt, (pt[0] + w, pt[1] + h), (0,0,255), 2)
        temp = [pt[0],pt[1],tipo,w,h] #1 es cambiado por tipo
        templPos.append(temp)

    return imag, templPos

def ConseguirLineas(imag):
    masca = np.zeros([int(np.size(imag,1)/2)],dtype = np.uint8)
    #masca = np.array([(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)])
    imag = f.to_grey(imag)
    imag = BorrarLineas(imag,masca)
    f.saveimg(imag,"lineas.png")
    lineaPos = np.zeros([20])
    lineaCon = 0
    adi = 0
    LineasPos = []
    for i in range(0,imag.shape[0]):
        if(imag[i][100]+adi==0):
            lineaPos[lineaCon] = i
            lineaCon += 1
            adi=2
            LineasPos.append(i)
        else:
            adi=0

    lineaInicial = LineasPos[0]
    dist = LineasPos[1]-LineasPos[0]

    return lineaInicial, dist

def check_corchea(img,posx,dist):
    value = False
    sx = np.size(img,0)
    sy = int(dist)
    temp = np.ones([sx,sy],dtype = np.uint8)
    for i in range(0,sx):
        for j in range(0,sy):
            temp[i][j] = img[i][posx + j]
    temp = agrandar(temp,1,1)

    temp = delete_borders(temp)
    temp = np.transpose(delete_borders(np.transpose(temp)))
    value = fcorchea(temp)
    return value

def change_to_corc(arr,image,black):
    for i in range(0,np.size(arr,0)):
        if(check_corchea(image,arr[i][0],arr[i][3]*1.4)):
            arr[i][2] = 5
    pass

def decide_position(arr,lin,size):
    for i in range(0,np.size(arr,0)):
        temp = ((arr[i][1] - lin + 2))/size
        next_temp = ((arr[i][1]+arr[i][3] - lin))/size
        if(np.floor(next_temp) == np.ceil(temp)):#se le da un margen de error
            arr[i][4] = 2*(np.floor(temp)) + 1
        else:
            arr[i][4] = np.floor(temp)*2 + 2

    pass

def newNote(time, duration, pitch):
    track = 0
    MyMIDI.addTrackName(track,time,"Sample Track")
    MyMIDI.addTempo(track,time, 120)
    channel = 0
    volume = 100
    MyMIDI.addNote(track,channel,pitch,time,duration,volume)

def make_midi(arr):
    time = 0
    for i in range(1,np.size(arr,0)):
        tempo = float(arr[i][2]) / 10.0

        frecuencia = tabla_sol[arr[i][4]]
        newNote(time,tempo,frecuencia)
        time = time + tempo
    pass

def song(f_lista):
    tiempo = 0
    newNote(0,f_lista[0][1],f_lista[0][0])
    for i in range(1,len(f_lista)):

        newNote(tiempo,f_lista[i][1],f_lista[i][0])
        tiempo = tiempo + f_lista[i-1][1]
    pass

def get_dataset(imagen):
    linea, distancia = ConseguirLineas(imagen)
    negra = cv2.imread('templates/b1.png',0)
    blanca = cv2.imread('templates/w1.png',0)
    blanca2 = cv2.imread('templates/w2.png',0)
    sol = cv2.imread('templates/sol.png',0)
    fa = cv2.imread('templates/clavefa.jpg',0)
    redonda = cv2.imread('templates/r1.png',0)
    redonda2 = cv2.imread('templates/r2.png',0)
    tata = f.to_grey(imagen)
    tata = EliminateLines(tata)
    NegraPos = []
    BlancaPos = []
    RedondaPos = []
    SolPos = []
    FaPos = []

    imagen, NegraPos = templates(imagen, negra, NegraPos,10)
    imagen, BlancaPos = templates(imagen, blanca, BlancaPos,20)
    imagen, BlancaPos = templates(imagen, blanca2, BlancaPos,20)
    imagen, SolPos = templates(imagen, sol, SolPos,50)
    imagen, RedondaPos = templates(imagen, redonda, RedondaPos, 40)
    imagen, RedondaPos = templates(imagen, redonda2, RedondaPos, 40)
    imagen, FaPos = templates(imagen, fa, FaPos,60)

    NegraPos = eliminar_repetidas(NegraPos)
    change_to_corc(NegraPos,tata,negra)
    BlancaPos = eliminar_repetidas(BlancaPos)
    SolPos = eliminar_repetidas(SolPos)
    NegraPos = sorted(NegraPos,key=lambda order:order[0],reverse = False)
    if(np.size(SolPos,0) > 0):
        dataset = np.concatenate((SolPos,NegraPos),axis = 0)
    if(np.size(BlancaPos,0) > 0):
        dataset = np.concatenate((dataset, BlancaPos),axis=0)

    dataset = np.array(sorted(dataset,key=lambda order:order[0],reverse = False))
    decide_position(dataset,linea,distancia)
    return dataset

def demo():

    image = cv2.imread("maria.jpg")
    tata = f.to_grey(image)
    tata = EliminateLines(tata)
    f.saveimg(tata,"qw.png")

    image = f.to_grey(image)
    clear = EliminateLines(image)
    clear = f.tresshold(clear,50)
    data = line_separator(clear,255)
    result = separator_blocks(image,data)
    datasets = []
    for i in range(0,np.size(result,0)):
        result[i] = agrandar(result[i],15,15)
        datasets.append(get_dataset(result[i]))

    for i in range(1,np.size(datasets,0)):
        dataset = np.concatenate((datasets[i-1],datasets[i]),axis = 0)
    #dataset = get_dataset(result[1])
    make_midi(dataset)
    binfile = open("FINAL2.mid", 'wb')
    MyMIDI.writeFile(binfile)
    binfile.close()

    cv2.imwrite('res2.png',image)

    pass

demo()


import vtk
class PTVisualizer:

    tree = vtk.vtkTree()
    view = vtk.vtkGraphLayoutView()
    graph = vtk.vtkMutableDirectedGraph()

    text_actor = vtk.vtkTextActor()
    text_widget = vtk.vtk.vtk.vtkTextWidget()

    # store data and mapping
    node_vert_map = {}
    vert_node_map = {}
    node_data = {}

    file = None

    def __init__(self):
        # self.tree.a
        self.text_actor.GetTextProperty().SetColor((0, 0, 1))
        self.text_actor.SetTextScaleModeToProp()

        self.view.AddRepresentationFromInput(self.tree)
        # view.SetGlyphType(vtk.vtkGraphToGlyphs.SPHERE)

        self.view.SetInteractionModeTo3D()
        self.view.SetLayoutStrategyToCone()

        self.view.SetColorVertices(True)
        self.view.SetEdgeSelection(False)

        # theme = vtk.vtkViewTheme.CreateOceanTheme()
        # theme.SetLineWidth(4)
        # theme.SetPointSize(32)
        # self.view.ApplyViewTheme(theme)
        # theme.FastDelete()

        rep = self.view.GetRepresentation(0)
        link = rep.GetAnnotationLink()

        self.view.GetRenderWindow().SetSize(600, 600)

        v1 = self.graph.AddVertex()
        v2 = self.graph.AddVertex()
        v3 = self.graph.AddVertex()
        v4 = self.graph.AddVertex()

        print(v1,",",v2,",",v3,",",v4)

        self.graph.AddEdge(v1,v2)
        self.graph.AddEdge(v1,v3)
        self.graph.AddEdge(v2,v4)

        self.vertexColors = vtk.vtkIntArray()
        self.vertexColors.SetNumberOfComponents(1)
        self.vertexColors.SetName("color")
        self.lookup = vtk.vtkLookupTable()
        self.lookup.Build()

        self.graph.GetVertexData().AddArray(self.vertexColors)
        self.view.SetVertexColorArrayName("color")
        self.view.ColorVerticesOn()
        theme = vtk.vtkViewTheme.CreateOceanTheme()
        theme.SetLineWidth(1)
        theme.SetPointSize(20)
        theme.SetPointLookupTable(self.lookup)
        self.view.ApplyViewTheme(theme)

        self.lookup.SetNumberOfTableValues(4)
        self.lookup.SetTableValue(0,1.0,0.0,0.0)    # red
        self.lookup.SetTableValue(1,0.0,1.0,0.0)    # green
        self.lookup.SetTableValue(2,0.0,0.0,1.0)    # blue
        self.lookup.SetTableValue(3,1.0,1.0,1.0)    # white

        self.vertexColors.InsertNextValue(0)
        self.vertexColors.InsertNextValue(1)
        self.vertexColors.InsertNextValue(2)
        self.vertexColors.InsertNextValue(3)

        self.tree.CheckedShallowCopy(self.graph)

        self.vertexColors.InsertValue(v2, 2)
        self.tree.CheckedShallowCopy(self.graph)

        self.vertexColors1 = vtk.vtkIntArray()
        self.vertexColors1.SetNumberOfComponents(1)
        self.vertexColors1.SetName("color")
        self.vertexColors1.InsertValue(v1,0)
        self.vertexColors1.InsertValue(v2,2)
        self.vertexColors1.InsertValue(v3,1)
        self.vertexColors1.InsertValue(v4,3)
        self.graph.GetVertexData().AddArray(self.vertexColors1)
        self.view.SetVertexColorArrayName("color")

        # updating colors of each vertex
        self.vertexColors.InsertValue(v2,v2)
        self.lookup.SetNumberOfTableValues(4)
        self.lookup.SetTableValue(0,1.0,1.0,0.0)
        self.lookup.SetTableValue(1,1.0,0.0,0.0)
        self.lookup.SetTableValue(2,0.0,1.0,0.0)
        self.lookup.SetTableValue(3,1.0,1.0,1.0)
        self.tree.CheckedShallowCopy(self.graph)

        v5 = self.graph.AddVertex()
        print(v5)
        self.vertexColors1.InsertValue(v5, v5)
        self.lookup.SetNumberOfTableValues(5)
        self.lookup.SetTableValue(0,1.0,1.0,0.0)
        self.lookup.SetTableValue(1,1.0,0.0,0.0)
        self.lookup.SetTableValue(2,0.0,1.0,0.0)
        self.lookup.SetTableValue(3,1.0,1.0,1.0)
        self.lookup.SetTableValue(4,0.5,0.8,0.5)

        self.graph.AddEdge(v1, v5)
        self.tree.CheckedShallowCopy(self.graph)
        self.graph.RemoveVertex(v5)
        self.tree.CheckedShallowCopy(self.graph)
        v6 = self.graph.AddVertex()
        self.vertexColors1.InsertValue(v6, v6)
        print(v6)
        self.graph.AddEdge(v1, v6)
        self.tree.CheckedShallowCopy(self.graph)
        def selection(obj, e):
            # print('selection triggered')
            sel = obj.GetCurrentSelection()
            selvs = sel.GetNode(0).GetSelectionList()
            print('selected', selvs.GetNumberOfTuples(),'nodes')
            for idx in range(selvs.GetNumberOfTuples()):
                print('node', selvs.GetValue(idx))

        self.view.GetRepresentation(0).GetAnnotationLink().AddObserver("AnnotationChangedEvent", selection)

    def show(self):
        camera = self.view.GetRenderer().GetActiveCamera()
        camera.SetPosition(0, 1, 0)
        camera.SetFocalPoint(0, 0, 0)
        camera.SetViewUp(0, 0, 1)

        self.view.ResetCamera()
        self.view.GetInteractor().Initialize()
        self.view.Render()
        self.view.GetInteractor().Start()

if __name__ == '__main__':
    vis = PTVisualizer()
    # vis.load('graphs_json/digraph.txt')
    vis.show()


import glob
import os
import json
classes = []
input_dir = "C:/Users/mohfi/OneDrive/Desktop/Garbage-Image/train/labels/"
output_dir = "C:/Users/mohfi/OneDrive/Desktop/Garbage-Image/train/label/"
image_dir = "C:/Users/mohfi/OneDrive/Desktop/Garbage-Image/train/images/"
os.mkdir(output_dir)
files = glob.glob(os.path.join(input_dir, '*.txt'))
# loop through each
for fil in files:
    basename = os.path.basename(fil)
    filename = os.path.splitext(basename)[0]
    # check if the label contains the corresponding image file
    if not os.path.exists(os.path.join(image_dir, f"{filename}.jpg")):
        print(f"{filename} image does not exist!")
        continue
    result = []
    tree = open(str(fil)).readlines()
    for lines in tree:
        lines=list(lines)
        lines[0]=0
        lines = ''.join(map(str, lines))
        result.append(lines)
    label = 'Garbage'
    if label not in classes:
        classes.append(label)
    classes = ''.join(map(str, classes))
    if result:
        with open(os.path.join(output_dir, f"{filename}.txt"), "w", encoding="utf-8") as f:
            f.write("".join(result))
with open(os.path.join(output_dir,'classes.txt'), 'w', encoding='utf8') as f:
    f.write(json.dumps(classes))


'''시총 1위에서 4위까지 종목으로 포트폴리오 구성후 효율적 투자선을 구함- 5장에서 개발한 MarketDB클래스 일별 시세조회 API를 사용
샤프지수 현대 포트폴리오 이론중에서 매우 중요 = (포트폴리오 예상 수익률 - 무위험률)/수익률의 표준편차
샤프지수를 이용하면 20,000개 포트폴리오 중에서 측정된 위험 단위당 수익이 제일 높은 포트폴리오를 구할수 있다.
예를들어  예상수익률이 7%이고 수익률의 표준편차가 5%인 경우 샤프지수는 7/5=1.4가 된다.
샤프지수가 높을수록 위험에 대한 보상이 더 크다
이전 Monte_Carlo_Strategy 코드에서 수정'''

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from stockdata_analysis import Analyzer

mk = Analyzer.MarketDB()
stocks = ['삼성전자', 'SK하이닉스', '현대자동차', 'NAVER']
df = pd.DataFrame()
for s in stocks:
    df[s] = mk.get_daily_price(s, '2016-01-04', '2018-04-27')['close']

daily_ret = df.pct_change()
# 수익률을 비교하려면 종가 대신 일간 변동률로 비교해야 하기 때문에 pct_change()함수 사용해서 일간 변동률을 구한다.
annual_ret = daily_ret.mean() * 252  # 일간 변동률에 평균갑세 252를 곱해서 연간 수익률을 구함 (252는 미국의 1년 평균 개장일)
daily_cov = daily_ret.cov()  # 일간 리스크는 cov() 함수를 이용하여 일간 변동률의 공분산 구함
annual_cov = daily_cov * 252  # 년간 공분산을 구함

port_ret = []
port_risk = []  # 시총 상위 4종목 비중을 다르게 해 포트폴리오 20,000개 생성, 포트폴리오 수익률,리스크,종목 비중을 저장할 각 리스트 생성
port_weights = []
sharpe_ratio = []
# Monte_Carlo_Strategy 코드에서 추가된 부분임

# 몬테카를로 시뮬레이션 : 매우 많은 난수를 이용해 함수의 값을 확률적으로 계산하는 것
# 몬테카를로 시뮬레이션을 이용하여 포트폴리오 20,000개를 생상한 후 각각의 포트폴리오별 수익률, 리스크, 종목비중을 데이터프레임으로
# 구하는 코드임
for _ in range(20000):  # 포트폴리오 20,000개 생성 반복횟수를 사용할일이 없으면 관습적으로 _변수에 할당
    weights = np.random.random(len(stocks))
    # random() 함수를 이용해 포트폴리오 20,000개를 4종목 비중이 다르게 했다. 4개의 랜덤 숫자로 구성된 배열을 생성
    weights /= np.sum(weights)  # 램덤 숫자를 램덤 숫자의 총합으로 나눠 4종목 비중의 합이 1이 되도록 조정

    returns = np.dot(weights, annual_ret)
    # 랜덤하게 생성한 종목별 비중 배열과 종목별 연간 수익률을 곱해 해당 포트폴리오 전체 수익률(returns)을 구한다
    risk = np.sqrt(np.dot(weights.T, np.dot(annual_cov, weights)))

    port_ret.append(returns)  # 포트폴리오 수익률, 리스크, 종목별 비중을 각각의 리스트에 추가
    port_risk.append(risk)
    port_weights.append(weights)
    sharpe_ratio.append(returns / risk)  # 포트폴리오 수익류을 리스크로 나눈 값을 위에 만든 샤프 지수 리스트에 추가

portfolio = {'Returns': port_ret, 'Risk': port_risk, 'Sharpe': sharpe_ratio}  # Monte_Carlo_Strategy 코드에서 추가된 부분임
for i, s in enumerate(stocks):
    portfolio[s] = [weight[i] for weight in port_weights]  # portfolio 딕셔너리에 비중값을 추가
df = pd.DataFrame(portfolio)
df = df[['Returns', 'Risk', 'Sharpe'] + [s for s in stocks]]  # 샤프지수 칼럼을 데이트프레임에 추가
# 최종 생성된 df 데이터프레임을 출력하면, 시총 상위 4 종목의 보유 비율에 따라 포트폴리오 20,000개가 각기 다른 리스크와
# 예상 수익률을 가지는 것을 확인

max_sharpe = df.loc[df['Sharpe'] == df['Sharpe'].max()]  # 샤프지수 칼럼에서 샤프지수값이 제일 큰행을 max_sharpe 로 정함
min_risk = df.loc[df['Risk'] == df['Risk'].min()]  # 리스크 칼럼에서 리스크값이 제일 작은 행을 min_risk 로 정함

df.plot.scatter(x='Risk', y='Returns', c='Sharpe', cmap='viridis', edgecolors='k', figsize=(11,7), grid=True)
# 샤프지수에 따라 컬러맵을 viridis, 테두리는 검정(k)로 표시
plt.scatter(x=max_sharpe['Risk'], y=max_sharpe['Returns'], c='r', marker='*', s=300)
# 샤프지수가 가장 큰 포트폴리오를 300 크기의 붉은 별포로 표시
plt.scatter(x=min_risk['Risk'], y=min_risk['Returns'], c='r', marker='X', s=200)
# 리스크가 제일 작은 포트폴리오를 200크기의 붉은 엑스표로 표시
plt.title('Portfolio Optimization')
plt.xlabel('Risk')
plt.ylabel('Expected Returns')
plt.show()
# df 데이터 프레임을 산점도로 출력.  x축 해당 포트폴리오의 리스크, y축은 예상 수익률

''' 표시된 산점도에서 리스크당 수익률이 가장 큰 포트폴리오는 별표로 표시된 포트폴리오다.'''


# numList = [88,69,76,10,83,33]
from functools import reduce #dont forget to import it

numList = []
print("Enter 6 numbers to list: ")
for i in range(0,6):
    temp = int(input())
    numList.append(temp)

print("The original list is: ",numList)

newList = [3*x for x in numList ]
print("The new list after multiplying by 3: ",newList)

sum1 = reduce(lambda x,y: x+y, numList)
sum2 = reduce(lambda x,y: x+y,newList)

print("The sum of original list is : ",sum1)
print("The sum of new list is : ",sum2)


# -*- coding: utf-8 -*-
"""
@author: slawler
"""
import pandas as pd
import h5py
import requests
import json
from datetime import datetime
from collections import OrderedDict, defaultdict
import string
import requests
from bs4 import BeautifulSoup
import matplotlib.pyplot as plt
import os
from pathlib import Path
from scipy.integrate import trapz, cumtrapz, simps
import numpy as np
import os
from glob import glob
from IPython.display import Markdown, display

#-----------------------------------------------------------------------------#
#--Notebook Functions
#-----------------------------------------------------------------------------#

def printbold(string):
    # Jupyter Notebook function to print bold
    display(Markdown('**'+string+'**'))

#-----------------------------------------------------------------------------#
#--Data Initialization & Manipulation Functions
#-----------------------------------------------------------------------------#

def initialize_test_case(plot=False):
    #--Read in gage data for the Michigan Test Case
    data_dir = os.path.join(str(Path(os.getcwd()).parents[0]),'sample_data')
    tsvs = glob(os.path.join(data_dir, '04119000*'))
    printbold("Read in USGS Gage Records")
    df = MergeDatsets(tsvs)
    return df, data_dir

def initialize(plot=False):
    #--Read in gage data for this region (gages = 015*)
    data_dir = os.path.join(str(Path(os.getcwd()).parents[0]),'sample_data')
    tsvs = glob(os.path.join(data_dir, '015*'))
    metadata = os.path.join(data_dir ,'gage_metadata.tsv')
    df = MergeDatsets(tsvs)
    return df, data_dir

def MergeDatsets(tsvs):
    #--Read in all tsvs and create a single dataframe (pass a list of tsvs)
    f = tsvs[0]           # Used the first data file to initialize the dataframe
    flow_type = f[-9:].split('.tsv')[0]
    print(os.path.basename(f))

    if flow_type == '60_dv':
        df = read_usgs_60_dv(f)

    elif flow_type == '60_iv':
        df = read_usgs_60_iv(f)

    elif flow_type == '65_iv':
        df = read_usgs_65_iv(f)

    else:
        print("Error")

    for f in tsvs[1:]:
        print(os.path.basename(f))
        data = f[-9:].split('.tsv')[0]
        gage =os.path.basename(f).split('.')[0]

        if data == '60_dv':
            df1 = read_usgs_60_dv(f)
            df = pd.merge(df, df1, how='outer', left_index=True, right_index=True)

        elif data == '60_iv':
            df1 = read_usgs_60_iv(f)
            df = pd.merge(df, df1, how='outer', left_index=True, right_index=True)

        elif data == '65_iv':
            df1 = read_usgs_65_iv(f)
            df = pd.merge(df, df1, how='outer', left_index=True, right_index=True)

        else:
            continue

    return df

def read_usgs_60_dv(f):
    #--Read in to dataframe, and reformat daily flow records
    gage = os.path.basename(f).split('.')[0]
    df = pd.read_csv(f, sep='\t')
    df = df.set_index(pd.DatetimeIndex(df['Date'])+ pd.Timedelta('12 hours'))
    df.rename(columns = {'Flow':gage}, inplace=True)
    df.drop(labels=['Date', 'agency_cd', 'site_no', 'Flow_cd'], axis = 1, inplace=True)
    return df

def read_usgs_60_iv(f):
    #--Read in to dataframe, and reformat instantaneous flow records
    gage = os.path.basename(f).split('.')[0]
    df = pd.read_csv(f, sep='\t')
    df = df.set_index(pd.DatetimeIndex(df['dateTime']))
    df.rename(columns = {'Flow_Inst':gage}, inplace=True)
    df.drop(axis=1,labels =['dateTime','agency_cd','site_no', 'Flow_Inst_cd', 'tz_cd'], inplace=True)
    return df

def read_usgs_65_iv(f):
    #--Read in to dataframe, and reformat instantaneous stage records
    gage = os.path.basename(f).split('.')[0]
    df = pd.read_csv(f, sep='\t')
    df = df.set_index(pd.DatetimeIndex(df['dateTime']))
    df.rename(columns = {'GH_Inst':gage}, inplace=True)
    df.drop(axis=1,labels =['dateTime','agency_cd','site_no', 'GH_Inst_cd', 'tz_cd'], inplace=True)
    return df

#-----------------------------------------------------------------------------#
#--USGS API-like Functions
#-----------------------------------------------------------------------------#

def Get_USGS_Peaks(gage):
    #--Get peaks for a gage, may be sensitive to skiprows value. (check if superceded by GetPKFQ below)
    url = 'https://nwis.waterdata.usgs.gov/ny/nwis/peak?site_no={}&agency_cd=USGS&format=rdb'.format(gage)
    df = pd.read_csv(url, skiprows=64, sep = '\t')
    df.drop(0, axis=0, inplace=True)
    y = df['peak_va'].astype(float)
    x = df['peak_dt']
    x = pd.to_datetime(x, format= '%Y-%m-%d',  errors='coerce')
    y.index = x
    return y

def GetPKFQ(gage):
    #--Need to test this function (see Get_USGS_Peaks)
    url = 'https://nwis.waterdata.usgs.gov/ny/nwis/peak?site_no={}&agency_cd=USGS&format=hn2'.format(gage)
    pkf = pd.read_csv(url)
    pkf.to_csv('return_periods\\{}.pkf'.format(gage), sep='\t', index=False)
    print('{} Data Saved in return_periods'.format(gage))

def GotoUSGS(state):
    #--Pass state initials (e.g. 'NY'), function opens gage catalogue in browser
    #--URL stirng broken into parts for ease of reading in text editor
    part1 = 'https://waterdata.usgs.gov/nwis/uv?referred_module=sw&state_cd={}'.format(state)
    part2 = '&site_tp_cd=OC&site_tp_cd=OC-CO&site_tp_cd=ES&site_tp_cd=LK&site_tp_cd=ST&'
    part3 = 'site_tp_cd=ST-CA&site_tp_cd=ST-DCH&site_tp_cd=ST-TS&format=station_list'
    url = part1 + part2 + part3
    print("\nCLICK HERE FOR USGS GAGES: \n", url)
    print("\nCLICK HERE FOR MAP: \n", 'https://maps.waterdata.usgs.gov/mapper/index.html')

#-----------------------------------------------------------------------------#
#--Math Functions
#-----------------------------------------------------------------------------#

def Q_to_Stage(usgs_gage_id, poly_order = 3):
    #--Use USGS rating curve to convert flow to stage (where available)
    url = r'https://waterdata.usgs.gov/nwisweb/get_ratings?site_no={}&file_type=exsa'.format(usgs_gage_id)
    cols = ['INDEP', 'SHIFT','DEP','STOR']
    usgs_data = pd.read_csv(url, skiprows = 38, sep = '\t', names = cols)

    xs = np.array(usgs_data['DEP'])
    ys = np.array(usgs_data['INDEP'])

    coefs    = np.polyfit(xs,ys,poly_order)
    polynomial = np.poly1d(coefs)

    return polynomial

def IntegrateHydrograph(timeseries, da_sqft, method = 'trapezoid'):
    from scipy import integrate
    #--Integrate the volume under a hydrograph
    '''Flow should be given in units of cfs, index should be time series'''
    data = timeseries.resample('1S').asfreq().interpolate()
    da_acres = 640*da_sqft

    if method == 'simpson':
        volume_acre_inches = volume*0.00027548209085905
        inches = volume_acre_inches/da_acres
        volume = integrate.simps(np.array(data.values), x=None, dx=1, axis=0, even='avg')
    else:
        volume = trapz(np.array(data.values), x=None, dx=1.0, axis=0)
        volume_acre_inches = volume*0.00027548209085905
        inches = volume_acre_inches/da_acres

    return round(float(inches), 2)

def GetBreachFlow(base_hydrograph, site, rasdata, station, breach_point, breach_elev, data_dir, date_int=6, poly_order = 3):
    #--Calls ComputeWeirFlow to calculate weir flow & plot (would benefit from partitioning into simpler functions)
    df = GetRasData(rasdata, station)

    # Plot a Rating Curve using Stage & Flow data
    StageDischargePlot(df, figsize=(5,3))
    polyfit  = np.polyfit(df['flow'],df['stage'],poly_order)
    polyfitline = np.poly1d(polyfit)
    hydro =pd.DataFrame(base_hydrograph)
    colname = hydro.columns
    hydro.rename(columns ={colname[0]:'Base Hydrograph'}, inplace=True)
    stage = plotcomp(hydro, polyfitline,breach_elev)
    df_weir = ComputeWeirFlow(stage, breach_elev, date_int)
    output_csv = os.path.join(data_dir, '{}_BreachData_{}_location_{}.tsv'.format(site, station, breach_point))
    df_weir.to_csv(output_csv, sep = '\t')

    printbold('\nInflow Data for Breach Location: ')
    print(output_csv)

def ComputeWeirFlow(df, breach_height, date_int, weir_coeff=2.0, breach_length=250, plot=True):
    #--Compute & plot weir flow at given HEC-RAS cross section, return a plot-series
    import matplotlib.dates as mdates
    min_stage = float(df.stage.min())
    df['head'] = df['stage']-breach_height
    df = df.query('head > 0 or head == 0')
    df['weir_flow'] = weir_coeff*breach_length*df['head']**(2/3)
    if plot:
        f, (ax1, ax2, ax3) = plt.subplots(1,3, figsize = (12,5))

        ax1.plot(df['stage'],color = 'green',label = 'River Stage (ft) at Breach Location')
        ax1.grid()
        ax1.legend(loc= 'best',  fontsize='x-small')

        ax2.set_title('Normalized Hydrographs based on Breach Elevation \n')
        ax2.plot(df['head'],color = 'green',label = 'Head (ft) at Breach Location')
        ax2.grid()
        ax2.legend(loc= 'best',  fontsize='x-small')

        ax3.plot(df['weir_flow'],color = 'blue',label = 'Weir Flow (cfs)')
        ax3.grid()
        ax3.legend(loc= 'best',  fontsize='small')

        days = mdates.HourLocator(interval = date_int)   # every year
        dateFmt = mdates.DateFormatter('%Y-%m-%d :%H:%M')
        ax1.xaxis.set_major_formatter(dateFmt)
        ax1.xaxis.set_major_locator(days)
        ax2.xaxis.set_major_formatter(dateFmt)
        ax2.xaxis.set_major_locator(days)
        ax3.xaxis.set_major_formatter(dateFmt)
        ax3.xaxis.set_major_locator(days)

        f.autofmt_xdate()

    return pd.DataFrame(df)

#-----------------------------------------------------------------------------#
#--HEC-RAS UTILS
#-----------------------------------------------------------------------------#

def GetRasData(hdf_plan_file, station):
    #--Read HEC-RAS hdf plan file, return dataframe with flow & calculated water surface elevation at a given station
    with h5py.File(hdf_plan_file ,'r') as hf:
        #--Paths should be the same in any hdf plan file created by HEC-RAS up to 5.0.3
        xs = hf.get('/Results/Steady/Output/Geometry Info/Cross Section Only')[:]
        xs = pd.DataFrame([x.astype(str).split(' ') for x in xs], columns=['River', 'Reach', 'Station'])

        flows = hf.get('/Results/Steady/Output/Output Blocks/Base Output/Steady Profiles/Cross Sections/Flow')[:]
        flows = pd.DataFrame(flows)

        stages = hf.get('/Results/Steady/Output/Output Blocks/Base Output/Steady Profiles/Cross Sections/Water Surface')[:]
        stages = pd.DataFrame(stages)

    idx = xs[xs['Station'] == str(station)].index[0]
    q = flows[idx]
    s = stages[idx]

    df = pd.DataFrame({'flow':q, 'stage':s})
    df.name = station
    df.sort_values(by='flow', inplace=True)
    printbold('RAS Data for XS {}'.format(df.name))
    return df

def GetRasUnsteadyFlow(hdf_plan_file):
    #--Read in unsteady flow data from HEC-RAS plan to dataframe
    with h5py.File(hdf_plan_file ,'r') as hf:
        qs = hf.get('/Event Conditions/Unsteady/Boundary Conditions/Flow Hydrographs/')
        df=pd.DataFrame()
        for q in qs:
            flow_name = q.split(': ')[-1]
            print(flow_name)
            ats =  qs[q].attrs
            items = ats.items
            for item in items():

                if item[0] == 'Start Date':
                    start = item[1]
                    start = start.astype(str)
                    if start.split(' ')[-1][:4] == '2400': #HEC-RAS likes to end the day with 2400...confuses python!
                        s = start.replace('2400', '0000')
                    start = datetime.strptime(s, '%d%b%Y %H%M')
                    #print('start', s)

                elif item[0] == 'End Date' :
                    end= item[1]
                    end = end.astype(str)
                    if end.split(' ')[-1][:4] == '2400':
                        e = end.replace('2400', '0000')
                    end = datetime.strptime(e, '%d%b%Y %H%M')
                    #print('end',e )
                else:
                    continue

            flow = qs[q][:]
            flow = flow[:,1]
            steps = len(flow) -1
            freq = (end-start)/steps
            idx = pd.date_range(start,end, freq=freq)
            df[flow_name] = flow
        df = df.set_index(pd.DatetimeIndex(idx))
        return df

def GetRasWSE(hdf_plan_file, profile):
    with h5py.File(hdf_plan_file ,'r') as hf:
        xs = hf.get('/Results/Steady/Output/Geometry Info/Cross Section Only')[:]
        xs = pd.DataFrame([x.astype(str).split(' ') for x in xs], columns=['River', 'Reach', 'Station'])

        profiles = hf.get('/Results/Steady/Output/Output Blocks/Base Output/Steady Profiles/Profile Names')[:]
        profiles = pd.DataFrame({'p': profiles[:]})
        profiles['p'] = profiles['p'].str.decode("utf-8")
        idx = profiles[profiles['p'] == profile].index[0]

        stages = hf.get('/Results/Steady/Output/Output Blocks/Base Output/Steady Profiles/Cross Sections/Water Surface')[:]
        stages = pd.DataFrame(stages)
        stages = pd.DataFrame({profile: stages.iloc[0]})

    df = pd.merge(xs, stages, left_index=True, right_index=True, how = 'inner')
    return df

#-----------------------------------------------------------------------------#
#--Plotting Functions
#-----------------------------------------------------------------------------#

def PlotPeak(df, peaks, rank, gage, spline=False, days=10):
    epsilon = pd.Timedelta('{} day'.format(days))
    peak = peaks.index[rank]
    start = peak - epsilon
    stop = peak + epsilon
    start = start.strftime(format = '%Y-%m-%d')
    stop = stop.strftime(format = '%Y-%m-%d')
    if spline:
        data = df[gage][start:stop].interpolate(method='spline', order=2)
    else:
        data = df[gage][start:stop].interpolate()
    plot = data.plot(figsize=(10,2), grid='on', title = gage, marker='o', markersize= 1.5)
    return plot

def PlotPeaks(df, gage):
    ax1 = plt.subplot(111)
    df.sort_values(axis=0, ascending=False, inplace=True)
    plt.scatter(df.index , df, color = 'b', marker = 'o', facecolors='none', s=20)
    plt.title(gage.split('_')[0] + ' Peak Flows')
    plt.grid()

def PlotMultiPeak(df, peaks, rank, gage, spline=False, days=10):
    epsilon = pd.Timedelta('{} day'.format(days))
    peak = peaks.index[rank]
    start = peak - epsilon
    stop = peak + epsilon
    start = start.strftime(format = '%Y-%m-%d')
    stop = stop.strftime(format = '%Y-%m-%d')
    if spline:
        data = df[gage][start:stop].interpolate(method='spline', order=2)
    else:
        data = df[gage][start:stop].interpolate()
    plot = data.plot(figsize=(10,2), grid='on', title = gage, marker='o', markersize= 1.5)
    plot.set_xlim(pd.datetime(1900, 1, 1) , pd.datetime(2020, 1, 1))
    return plot

def StageDischargePlot(df, figsize = (2,3)):
    x, y = df['flow'], df['stage']
    f, (ax) = plt.subplots()
    ax.plot(x,y)
    ax.grid()
    ax.set_ylabel('Stage (ft)')
    ax.set_xlabel('Flow (cfs)')
    ax.set_xlim(0, x.max()*1.1)
    ax.set_title('Rating Curve for XS {}'.format(df.name))
    f.set_size_inches(figsize)
    f.autofmt_xdate()
    plt.tight_layout(pad=0.4, w_pad=0.5, h_pad=1.0)

'''
!NEED TO UPDATE FUNCTION USING IntegrateHydrograph Approach
def PlotCumIntegral(instantaneous, daily, units = 60):

    #--Plot Cumlative flow (integrated)
    f, ax = plt.subplots(figsize=(10,2))
    ax.set_title('Cumulative Trapezoid')

    x = np.array(daily)*60
    ct = cumtrapz(x)
    ax.plot(ct, label = 'daily')

    x = np.array(instantaneous)*60
    ct = cumtrapz(x)
    ax.plot(ct, label = 'inst')

    handles, labels = ax.get_legend_handles_labels()
    ax.legend(handles, labels)
    #plt.legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.)
    plt.grid()
'''

#-----------------------------------------------------------------------------#
#--REGION SPECIFIC FUNCTIONS (copied from ny_clean_nb.py, version control cleanup)
#
#--Functions created on the fly with a specific set of variables (i.e. gage, flow type, date, etc.)
#--TODO: Clean up functions so that they can be used universally.
#-----------------------------------------------------------------------------#

def Daily_vs_Instant(df):
    print('\n')
    printbold("Interpolated daily flows result in a lower peak flow, but a greater total volume of water:")

    # Daily Plot
    gage = '01509000_00060_dv'
    start, stop = '2005-03-15','2005-04-10'
    adj_start, adj_stop = '2005-03-29','2005-04-10'

    daily_peak= df[gage][start:stop].copy()
    daily_peak= daily_peak[adj_start:adj_stop].resample('60S').asfreq()
    daily_peak= daily_peak.interpolate(method = 'spline', order = 2)
    daily_peak = daily_peak['2005-04-02':'2005-04-05 12:00']
    daily_peak.name = 'Daily Flow'
    daily_peak.plot(figsize=(10,2), grid='on')

    # Instantaneous Plot
    gage = '01509000_00060_iv'
    inst_peak= df[gage][start:stop].copy()
    inst_peak= inst_peak[adj_start:adj_stop].resample('60S').asfreq()

    inst_peak= inst_peak.interpolate(method = 'spline', order = 3)
    inst_peak = inst_peak['2005-04-02':'2005-04-05 12:00']
    inst_peak.name = 'Instantaneous Flow'
    inst_peak.plot(figsize=(10,2), grid='on')

    plt.legend()
    plt.title('USGS Gage {}'.format(gage.split('_')[0]))
    print('Maximum Daily Flow = {} cfs'.format(round(daily_peak.max(),0)))
    print('Maximum Inst Flow = {} cfs'.format(round(inst_peak.max(),0)))

    return daily_peak, inst_peak

def Stretched_Daily_vs_Instant(df):
    print('\n')
    printbold('Results from Stretched Daily flows compared with Instantaneous Records')
    # Daily Plot
    gage = '01509000_00060_dv'
    start, stop = '2005-03-15','2005-04-10'
    adj_start, adj_stop = '2005-03-29','2005-04-10'

    #--Replace the daily mean on the day of peak flow, with the peak from the instantaneous record
    daily_peak= df[gage][start:stop].copy()
    daily_peak['2005-04-03 12:00:00'] = 14200.0
    daily_peak= daily_peak[adj_start:adj_stop].resample('60S').asfreq()
    daily_peak= daily_peak.interpolate(method = 'spline', order = 2)
    daily_peak = daily_peak['2005-04-02':'2005-04-05 12:00']
    daily_peak.name = 'Daily Flow, Stretched to Peak'
    daily_peak.plot(figsize=(10,2), grid='on')

    # Instantaneous Plot
    gage = '01509000_00060_iv'
    inst_peak= df[gage][start:stop].copy()
    inst_peak= inst_peak[adj_start:adj_stop].resample('60S').asfreq()
    inst_peak= inst_peak.interpolate(method = 'spline', order = 3)
    inst_peak = inst_peak['2005-04-02':'2005-04-05 12:00']
    inst_peak.name = 'InstantaneousFlow'
    inst_peak.plot(figsize=(10,2), grid='on')

    plt.title('{}'.format(gage.split('_')[0]))
    plt.legend()

    print('Maximum Daily Flow (stretched) = {} cfs'.format(round(daily_peak.max(),0)))
    print('Maximum Inst Flow = {} cfs'.format(round(inst_peak.max(),0)))
    return daily_peak, inst_peak

def Stretched_Daily_100yr(df, plot = False):
    print('\n')

    # Daily Plot
    gage = '01509000_00060_dv'
    start, stop = '2005-04-01','2005-04-07'

    #--Replace the daily mean on the day of peak flow, with the peak from the instantaneous record
    daily_peak= df[gage][start:stop].copy()
    daily_peak['2005-04-03 12:00:00'] = 20960.0
    daily_peak= daily_peak.resample('15T').asfreq().interpolate(method = 'spline', order = 2)
    daily_peak.name = '1 Pct'
    daily_peak = daily_peak['2005-04-02':'2005-04-05 12:00']

    # Instantaneous Plot
    gage = '01509000_00060_iv'
    inst_peak= df[gage][start:stop].copy().interpolate(method = 'spline', order =2)
    inst_peak.name = 'Base Hydrograph'
    inst_peak = inst_peak['2005-04-02':'2005-04-05 12:00']
    if plot == True:
        daily_peak.plot(figsize=(8,6), grid='on',color = 'blue')
        inst_peak.plot(figsize=(8,6), grid='on', color = 'green')

        plt.title('{}'.format(gage.split('_')[0]))
        plt.legend(loc=0)
        printbold("Hydrograph properties:")
        print('1 Percent Peak Flow = {} cfs'.format(round(daily_peak.max(),0)))
        print('Maximum Inst Flow = {} cfs'.format(round(inst_peak.max(),0)))
    return daily_peak, inst_peak

#FINAL HYDROGRAPH:
def Hydrograph_100yr(df, peak):
    print('\n')
    printbold('Historic Hydrograph Adapted to 100yr Peak Flow')
    # Daily Plot
    gage = '01509000_00060_dv'
    start, stop = '2005-03-15','2005-04-10'
    adj_start, adj_stop = '2005-03-29','2005-04-10'

    #--Replace the daily mean on the day of peak flow, with the peak from the instantaneous record
    daily_peak= df[gage][start:stop].copy()
    daily_peak['2005-04-03 12:00:00'] = peak
    daily_peak= daily_peak[adj_start:adj_stop].resample('60S').asfreq()
    daily_peak= daily_peak.interpolate(method = 'spline', order = 2)
    daily_peak = daily_peak[3600:-3600]
    daily_peak.name = 'Daily Values, Stretched to Inst. Peak'
    daily_peak.plot(figsize=(12,3), grid='on')

    plt.title('{}'.format(gage.split('_')[0]))
    plt.legend(location = 0)

    return pd.DataFrame(daily_peak)

def plotcomp(hydro, rawfitline, breach_stage=0):
    stage = rawfitline(hydro['Base Hydrograph'])
    f, (ax, ax1) = plt.subplots(1,2, figsize = (10,3))
    ax.plot(hydro.index, hydro['Base Hydrograph'], label = '1 Pct Flow (cfs)')
    ax.legend(loc='best',  fontsize='small')
    ax.grid()

    ax1.plot(hydro.index, stage, label = 'Flood Stage (ft)')
    ax1.axhline(breach_stage, color = 'red', label = 'Breach Height')
    ax1.legend(loc='best',  fontsize='small')
    ax1.grid()

    #--Format the Plots
    import matplotlib.dates as mdates
    ax.set_title('Flow Hydrograph \n (input from smoothed storm )')
    days = mdates.DayLocator()   # every year
    dateFmt = mdates.DateFormatter('%Y-%m-%d')
    ax.xaxis.set_major_formatter(dateFmt)
    ax.xaxis.set_major_locator(days)

    ax1.set_title('Stage Hydrograph \n(output using rating curve above)')
    ax1.xaxis.set_major_locator(days)
    ax1.xaxis.set_major_formatter(dateFmt)
    f.autofmt_xdate()

    stage = pd.DataFrame( stage, index = hydro.index)
    stage.rename(columns = {0: 'stage'}, inplace=True)
    return stage

def init_base_hydro(gage_data, peak=14209.0, pct_1=20960.0):
    pct_1_peak, inst_peak  = Stretched_Daily_100yr(gage_data, plot = False)
    stretch_1pct = pct_1/peak
    smooth_storm_resample = inst_peak.resample('30T').mean()
    smooth_storm_1pct = smooth_storm_resample*stretch_1pct
    #print('Factor: \n','\t1 Percent\t{}'.format(stretch_1pct))

    f, ax = plt.subplots()
    ax.plot(smooth_storm_1pct,color = 'green',label = 'Base Hydrograph from Gage, stretched to 1-Pct')
    ax.grid()
    ax.legend(loc='best')
    title = '{} - {}'.format(smooth_storm_1pct.index[0],smooth_storm_1pct.index[-1])
    ax.set_title(title)

    f.set_size_inches(6,4)
    f.autofmt_xdate()
    return smooth_storm_1pct

def smooth_base_hydro(smooth_storm_1pct):
    # 1. Make a copy of the unsmoothed hydrograph:
    final_hydrograph  = smooth_storm_1pct.copy()

    #2. Replace the dates of the portions needing to be smoothed with NaNs
    final_hydrograph['2005-04-02 00:00':'2005-04-03 00:00'] = np.nan #

    #3. Set the first data point to the desired starting value
    # Note: Typically the starting and ending values will be the same, for this example the min works
    final_hydrograph['2005-04-02 00:00':'2005-04-02 00:00'] = float(final_hydrograph.min())

    #4. Add more points as needed to smooth the curve
    # Note: This is an iterative process, using a (factor*min) appproach is used here for speed of iteration
    final_hydrograph['2005-04-02 06:00':'2005-04-02 07:00'] = float(final_hydrograph.min()*1.05)
    final_hydrograph['2005-04-02 14:00':'2005-04-02 15:00'] = float(final_hydrograph.min()*1.25)

    #5. Smooth the iterated points
    smooth_storm = final_hydrograph.interpolate(how = 'polynomial', order=3)

    f, ax = plt.subplots()
    ax.plot(smooth_storm,color = 'green',label = 'Smoothed Storm')
    ax.grid()
    ax.legend(loc= 'best',  fontsize='small')

    f.set_size_inches(6,4)
    f.autofmt_xdate()
    return smooth_storm


import requests
import json
import re
from scrapy.selector import Selector

def parse_latlon_from_url(map_url: str):
    coords_re = re.compile(r'\!2d([^!]+)\!3d([^!]+)')
    return list(map(float, coords_re.search(map_url).group(1, 2)))

def working_time_parse_func(span_list):
    morning_time_list = span_list[0].replace('.', ':').split(' ')
    evening_time_list = span_list[1].replace('.', ':').split(' ')
    mon_thu_time = 'mon-thu ' + morning_time_list[2] + '-' + morning_time_list[4] + ' ' \
        + evening_time_list[2] + '-' + evening_time_list[4]
    fri_time = 'fri ' + morning_time_list[2] + '-' + morning_time_list[4] + ' ' \
        + evening_time_list[2] + '-' + evening_time_list[-3]
    return [mon_thu_time, fri_time]

main_response = requests.get('https://oriencoop.cl/sucursales.htm')
main_list = []

pages = [st.split('/')[-1] for st in Selector(
    text=main_response.text).xpath("//ul[@class='sub-menu']/li/a/@href").extract()]

for page in pages:

    response = requests.get('https://oriencoop.cl/sucursales/' + page)
    print('Collecting data from ' + page + ' page...')

    div = Selector(text=response.text).xpath(
        "//div[@class='s-dato']/p").extract()
    mapa_url = Selector(text=response.text).xpath(
        "//div[@class='s-mapa']/iframe/@src").get()

    data = dict()

    time_span_list = Selector(text=div[3]).xpath("//span/text()").extract()

    data['address'] = Selector(text=div[0]).xpath("//span/text()").get()
    data['latlon'] = parse_latlon_from_url(mapa_url)
    data['name'] = Selector(text=response.text).xpath(
        '//div[@class="s-dato"]/h3/text()').get()
    data['phones'] = [
        Selector(text=div[1]).xpath("//span/text()").get(),
        *Selector(text=response.text).xpath("//li[@class='call']/a/text()").extract()
    ]
    data['working_hours'] = working_time_parse_func(time_span_list)
    main_list.append(data)

print(json.dumps(main_list, indent=4, ensure_ascii=False))


from skimage.exposure import rescale_intensity
import numpy as np
import cv2
import matplotlib.pyplot as plt

#Kazım Muhammet Temiz 17050111021
#Mert Rıza Karadeniz 17050111054
#Nevzat Buğrahan Türk 17050111036

def convolve(source_image, kernel):
    # Kaynak resmin ve matrix kernelin spatial çözünürlüğünü al
    (iH, iW) = source_image.shape[:2]
    (kH, kW) = kernel.shape[:2]

    # Çıktı resim için yer açıyoruz ve kenarları padliyoruz
    # output bizim resmimizle aynı çözünürlüğe sahip ama her pixel 0 grayscale
    pad = (kW - 1) // 2
    source_image = cv2.copyMakeBorder(source_image, pad, pad, pad, pad,
                                      cv2.BORDER_REPLICATE)
    output = np.zeros((iH, iW), dtype=float)

    # matrixi her pikselle çarp ve topla
    for y in np.arange(pad, iH + pad):
        for x in np.arange(pad, iW + pad):
            # resimde matrixi çarpacağımız 3x3 alanı/pixelleri seçiyoruz
            # 0 olma ihtimaline karşı +1 eklendi
            pixels = source_image[y - pad:y + pad + 1, x - pad:x + pad + 1]

            # seçtiğimiz pixelleri kernel matriximizle çarpıyoruz
            k = (pixels * kernel).sum()

            # Output resmin aynı koordinatlı yerine bulduğumuz değeri yazıyoruz
            output[y - pad, x - pad] = k

    # Output resimdeki değerler 255i geçebileceğinden rescale ediyoruz
    output = rescale_intensity(output, in_range=(0, 255))
    output = (output * 255).astype("uint8")

    return output

sharpen = np.array((
    [-1, -1, -1],
    [-1, 9, -1],
    [-1, -1, -1]))

img = cv2.imread("question_2.tif")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

print("Applying spatial sharpening filter")
Result = convolve(gray, sharpen)

display = plt.figure()
display.add_subplot(1,2, 1)
plt.imshow(img,"gray"), plt.title("Original")
display.add_subplot(1,2, 2)
plt.imshow(Result,"gray"), plt.title("Spatial Sharpening Filter")
plt.show(block=True)


import torch
import torch.nn as nn
from torch.autograd import Variable
from torch import optim
import torch.nn.functional as F

import math
import time
import os
import argparse

import sconce

# Parse command line arguments
argparser = argparse.ArgumentParser()
argparser.add_argument('--n_epochs', type=int, default=200)
argparser.add_argument('--n_iters', type=int, default=200)
argparser.add_argument('--hidden_size', type=int, default=100)
argparser.add_argument('--n_layers', type=int, default=2)
argparser.add_argument('--dropout_p', type=float, default=0.1)
argparser.add_argument('--learning_rate', type=float, default=0.001)
args = argparser.parse_args()

job = sconce.Job('seq2seq-intent-parsing', vars(args))
job.log_every = args.n_iters * 10

from data import *
from model import *
from evaluate import *

# # Training

def train(input_variable, target_variable):
    encoder_optimizer.zero_grad()
    decoder_optimizer.zero_grad()

    input_length = input_variable.size()[0]
    target_length = target_variable.size()[0]

    encoder_outputs, encoder_hidden = encoder(input_variable)

    decoder_input = Variable(torch.LongTensor([[SOS_token]]))
    decoder_hidden = encoder_hidden

    loss = 0

    for di in range(target_length):
        decoder_output, decoder_hidden, decoder_attention = decoder(decoder_input, decoder_hidden, encoder_outputs)
        loss += criterion(decoder_output[0], target_variable[di])
        decoder_input = target_variable[di] # Teacher forcing

    loss.backward()

    encoder_optimizer.step()
    decoder_optimizer.step()

    return loss.data[0] / target_length

def save_model(model, filename):
    torch.save(model, filename)
    print('Saved %s as %s' % (model.__class__.__name__, filename))

def save():
    save_model(encoder, 'seq2seq-encoder.pt')
    save_model(decoder, 'seq2seq-decoder.pt')

encoder = EncoderRNN(input_lang.size, args.hidden_size)
decoder = AttnDecoderRNN('dot', args.hidden_size, output_lang.size, args.n_layers, dropout_p=args.dropout_p)

encoder_optimizer = optim.Adam(encoder.parameters(), lr=args.learning_rate)
decoder_optimizer = optim.Adam(decoder.parameters(), lr=args.learning_rate)
criterion = nn.NLLLoss()

try:
    print("Training for %d epochs..." % args.n_epochs)

    for epoch in range(args.n_epochs):
        training_pairs = generate_training_pairs(args.n_iters)

        for i in range(args.n_iters):
            input_variable = training_pairs[i][0]
            target_variable = training_pairs[i][1]
            loss = train(input_variable, target_variable)

            job.record((args.n_iters * epoch) + i, loss)

        evaluate_tests(encoder, decoder)

    print("Saving...")
    save()

except KeyboardInterrupt:
    print("Saving before quit...")
    save()