import numpy as np
import matplotlib.pyplot as plt

import time
def new_timer(id='', disabled=False):
    beg_ts = time.time ()
    
    def finish(id2='', reset=False):
        nonlocal beg_ts
        if disabled: return
        end_ts = time.time ()
        timediff = end_ts - beg_ts
        print("%s%s elapsed time: %f" % (id, id2, timediff))
        if reset: beg_ts = time.time() # needs `nonlocal` defn from Py3
        return timediff

    return finish

"""
Explanation: iPython Cookbook - Monte Carlo Pricing III - European Engine

Pricing a European option with a Monte Carlo engine

Set-up
End of explanation
"""


def payoff_call(k=100):
    def payoff(spot):
        if spot > k:
            return spot - k
        else:
            return 0
    return payoff

def payoff_put(k=100):
    def payoff(spot):
        if spot < k:
            return k - spot
        else:
            return 0
    return payoff

def payoff_fwd(k=100):
    def payoff(spot):
            return spot - k
    return payoff

def payoff_straddle(k=100):
    def payoff(spot):
        if spot < k:
            return k - spot
        else:
            return spot - k
    return payoff

"""
Explanation: Product definitions
We first define a number of different European option payoff structures (ie payoff structures that only depende on the value of the spot at maturity), notable
- call - the right to buy a security at a given strike price
- put - the right to sell a security at a given strike price
- straddle - a call and a put with the same strike
- forward - the obligation to buy a security at a given strike price (not an option)
End of explanation
"""


def calc_factory (N=1000, default_payoff=None):
    
    z = np.random.standard_normal((N))
    
    def run (fwd, sig, mat, payoff=None):
        
        read_timer = new_timer(disabled=True)
        x = fwd * exp(- 0.5 * sig * sig * mat + sig * sqrt(mat) * z)
        read_timer("a")
        if payoff == None:
            payoff = default_payoff
        po = list(map(payoff,x))
        read_timer("b")
        fv = mean(po)
        read_timer("c")
        return fv
    
    return run

calc = calc_factory(N=1000)
calc(fwd=100, sig=0.2, mat=1, payoff=payoff_straddle(100))

"""
Explanation: Model calculator factory
We define a factory function that then provides us with a calculation function for our specific model. The factory function most importantly takes the number of Monte Carlo samples to be drawn N. For convenience it also takes a default payoff function as often the payoff will not be varied over the course of one analysis.
Importantly, the vector of Standard Gaussian random variables z that will be the basis of the Monte Carlo simulation is drawn immediately, and it is remembered in the closure This allows us to run the calculation function (the one returned by the factory) with different parameters but against the same sampling set, thereby greatly reducing Monte Carlo errors.
End of explanation
"""


from scipy.stats import norm
def bscall(fwd=100,strike=100,sig=0.1,mat=1):
    lnfs = log(fwd/strike)
    sig2t = sig*sig*mat
    sigsqrt = sig*sqrt(mat)
    d1 = (lnfs + 0.5 * sig2t)/ sigsqrt
    d2 = (lnfs - 0.5 * sig2t)/ sigsqrt
    fv = fwd * norm.cdf (d1) - strike * norm.cdf (d2)
    return fv

"""
Explanation: We also implement the Black Schole formula for testing the code
End of explanation
"""


strike   = 100
mat      = 1
forward  = 100
vol      = 0.1

call      = payoff_call (k=strike)
put       = payoff_put  (k=strike)
fwd       = payoff_fwd  (k=strike)
straddle  = payoff_straddle (k=strike)

callfv    = calc(fwd=forward, sig=vol, mat=mat, payoff=call)
callfvc   = bscall(fwd=100,strike=100,sig=0.1,mat=1)
putfv     = calc(fwd=forward, sig=vol, mat=mat, payoff=put)
fwdfv     = calc(fwd=forward, sig=vol, mat=mat, payoff=fwd)
stradfv   = calc(fwd=forward, sig=vol, mat=mat, payoff=straddle)
#print ("Call       =  %f", callfv)

"""
print "Call       =  %f" % callfv
print "  check    =  %f" % callfvc
print "Put        =  %f" % putfv
print "Forward    =  %f" % fwdfv
print "  check    =  %f" % (callfv-putfv)
print "Straddle   =  %f" % stradfv
print "  check    =  %f" % (callfv+putfv)
"""

"""
Explanation: Calculation
Single points
We now compute the prices of a number of different options, together with some checks (eg, we verify call-put parity, and the price of the straddle)
End of explanation
"""


forwards = np.linspace(75, 125, 25)

def freeze(thecalc, sig=0.20, mat=1.0, payoff=call):
    def calc1(fwd):
        return thecalc(fwd=fwd, sig=sig, mat=mat, payoff=payoff)
    return calc1
        

#fvs = calc(fwd=forwards, sig=0.20, mat=1.0, payoff=call)
payoff1 = straddle

plt.plot(forwards, list(map (freeze(calc, sig=0.20, mat=2.0, payoff=payoff1), forwards)), label="2y")
plt.plot(forwards, list(map (freeze(calc, sig=0.20, mat=1.0, payoff=payoff1), forwards)), label="1y")
plt.plot(forwards, list(map (freeze(calc, sig=0.20, mat=0.5, payoff=payoff1), forwards)), label="0.5y")
plt.plot(forwards, list(map (freeze(calc, sig=0.20, mat=0.25, payoff=payoff1), forwards)), label="0.25y")
plt.plot(forwards, list(map (freeze(calc, sig=0.20, mat=0.1, payoff=payoff1), forwards)), label="0.1y")
plt.title('Premium of a European Option')
plt.ylabel('forward premium')
plt.xlabel('forward')
plt.legend()
plt.show()

"""
Explanation: Graphs
We have the calc function and we can compute it on a range of values. For this we need to freeze all but one parameters what we do with an ad-hoc closure: the function freeze freezes all parameters of its first argument (which must be a function with the signature of calc) except the paramter fwd. It returns a calculation function that only takes one parameter. This function is then applies to forwards.
TODO Use functools.partial instead of freeze see here
End of explanation
"""


import sys
print(sys.version)

"""
Explanation: Licence and version
(c) Stefan Loesch / oditorium 2014; all rights reserved 
(license)
End of explanation
"""


