#!/usr/bin/python3

"""
    The number, 197, is called a circular prime because all rotations of the digits: 197, 971, and 719, are themselves prime.
There are thirteen such primes below 100: 2, 3, 5, 7, 11, 13, 17, 31, 37, 71, 73, 79, and 97.
How many circular primes are there below one million?
"""

import numpy as np

def isprime(num: int) -> bool:
    for i in range(2, int(np.sqrt(num))+1):
        if num%i == 0:
            return False
    return True

def rotate(num: int) -> set:
    rot = {num}
    length = len(str(num))
    k = 0
    while k < length:
        tmp = list(str(num))
        dig = tmp[0]
        tmp[:] = tmp[1:]
        tmp.append(dig)
        num = ''.join(tmp)
        rot.add(int(num))
        k = k + 1

    return rot

def euler35() -> int:
    tot = 0
    c_primes = [2]
    flag = False
    for i in range(3, 10**6, 2):
        if isprime(i):
            flag = True
            tmp = set()
            cps = rotate(i)
            for x in cps:
                if isprime(x):
                    tmp.add(x)
                else:
                    flag = False
                    break
        if flag:
            c_primes.extend(list(tmp))

    return len(set(c_primes))

tot = euler35()
print(tot)


# -*- coding: utf-8 -*-
"""
Created on Wed Nov  3 13:06:11 2021

@author: Oliver
"""
import numpy as np
from scipy.integrate import quad
import matplotlib.pyplot as plt

"""__________________REFERENCE SOLUTION________________"""
"""DONE"""

def integrand(x):
    return x**2+4*x-12
ans, err = quad(integrand, -10,10)
def integral(x):
    return 1/3*x**3+2*x**2-12*x
x= np.linspace(1,1000,8000)
plt.plot(x,integral(x),'g',label ='reference')
plt.legend()
plt.show()
print(f'The analytical solution to the integral is: {ans}')
print()

# Excercise 1: Implement the Midpoint/rectangular Rule code and execute
# it to integrate the function x^2+4x-12 in the domain -10<x<10

"""______________________________________________________________________"""
def calculate_dx(a,b,n):
    return (b-a)/float(n)
"""DONE"""

def rect_rule(f,a,b,n):
    total = 0.0
    dx = calculate_dx(a,b,n)
    t = []
    for i in range(0,n):
        total += abs(f((a+(i*dx))))
        t.append(total)
    plt.plot(range(n),t)
    plt.show()
    return dx*total

def f(x):
    return x**2+4*x-12

print(f' Excercise 1, midpoint/rectangular rule result: {rect_rule(f,-10,10,10000)}')

print(f'Analitycal - midpoint/rectangular rule result: {abs(rect_rule(f,-10,10,10000) - ans)}')

print(f'Relative error midpoint/rectangular result: {abs(ans - rect_rule(f,-10,10,10000) )/abs(rect_rule(f,-10,10,10000))}')
print()

"""______________________________________________________________________"""

#Excercise 2: Implement this trapezoid Rule code and execute it to integrate
# the function x**2 +4*x-12 in the domain -10<x<10 (enter inputs first)
"""NOT DONE PLOTTING"""
def trapz(f,a,b,N=50):
    x = np.linspace(a,b,N) # N+1 points make N subintervals
    print(x)
    y = f(x)
    y_right = y[1:] # right endpoints
    y_left = y[:-1] # left endpoints
    total = []
    for i in range(N+1,1,-1):
        dx = (b-a)/i
        Z = (dx/2) *np.sum(y_right+y_left)
        total.append(Z)
    plt.plot(x,total,label = 'traps')
    plt.legend()
    plt.show()
    # while i <= N:
    #     k += 2
    #     g.append((dx/2)*np.sum(f(k)+f(k+1))
    # plt.plot(range(N),g)
    # plt.show
    return Z

a= -10
b= 10
n= 10000
print(f' The trapezoidal rule result is: {trapz(f,a,b,n)}')

print(f'Analitycal - trapezoidal rule: {abs(trapz(f,a,b,n) - ans)}')

print(f'Relative error trapezoidal rule result: {abs(ans - trapz(f,a,b,n) )/abs(trapz(f,a,b,n))}')
print()

"""______________________________________________________________________"""

# Excercise 3: Implement this Simpson's One Third Rule code and execute it
# to integrate the funciton x**2+4x-12 in the domain -10<x<10
"""NOT DONE PLOTTING"""
def simps(f,a,b,N=50):
    if N % 2 == 1:
        raise ValueError("N must be an even integer")
    t=[]
    x = np.linspace(a,b,N+1)
    y = f(x)
    for i in range(1,N+2,1):
        dx = (b-a)/i
        S = dx/3 * np.sum(y[0:-1:2] + 4*y[1::2] + y[2::2]) # s[i:j:k] - "slice of s from i to j with step k
        t.append(S)
    plt.plot(x,t)
    plt.show()
    return S # y[2::2] - start at the 2nd element and skip through in steps of 2 each time

f = lambda x: x**2+4*x-12
solution = simps(f,-10,10,10000)

print(f' The simpson one third rule solution: {solution}')

print(f'Analitycal - simpson one third rule: {abs(solution - ans)}')

print(f'Relative error simpson one third rule result: {abs(ans - solution )/abs(solution)}')
print()

"""______________________________________________________________________"""

# Exercise 4: Implement this Simpson’s three eightths Rule code and execute it to
# integrate the function x2 +4x – 12 in the domain -10 < x < 10 (enter inputs first).
"""DONE"""
def func(x):
    return abs(x**2+4*x-12)

def calculate(lower_limit, upper_limit, interval_limit ):
    interval_size = (float(upper_limit - lower_limit) / interval_limit)
    sum = func(lower_limit) + func(upper_limit);
    # Calculates value till integral limit
    n=0
    k = []
    t = []
    for i in range(1, interval_limit ):
        if (i % 3 == 0):
            k.append(n)
            n +=1
            sum = sum + 2 * func(lower_limit + i * interval_size)
            t.append(sum)
        else:
            sum = sum + 3 * func(lower_limit + i * interval_size)
    plt.plot(k,t,'r')
    return ((float( 3 * interval_size) / 8 ) * sum )

# driver function

interval_limit = 10000
lower_limit = -10
upper_limit = 10
integral_res = calculate(lower_limit, upper_limit, interval_limit)

# rounding the final answer to 6 decimal places

print (f' The simpson three eigthths rule solution: {round(integral_res, 6)}')

print(f'Analitycal - three eightths rule: {abs(round(integral_res, 6) - ans)}')

print(f'Relative error simpson three eightths rule result: {abs(ans - round(integral_res, 6) )/abs(round(integral_res, 6))}')

# Plot the evolution of the integral value as a function of the number of
# integration intervals for each technique (Hint: you will have to modify each
# code to run for different values of N and plot the integral obtained for each
# run). Use arrays to store values and matplotlib to plot Integral v. N).


import math
import decimal as dec
import numpy as np

class MethodResult:
    number_of_steps = 0
    root = 0

def f1(x):
    return dec.Decimal(math.cos(x) * math.cosh(x))

def f1der(x):
    return dec.Decimal(math.cos(x) * math.sinh(x) - math.sin(x) * math.cosh(x))

def f2(x):
    if math.isclose(x, 0.0, abs_tol=1e-9):
        return dec.Decimal(10000000)
    else:
        return dec.Decimal(1/x) - dec.Decimal(math.tan(x))

def f2der(x):
    return dec.Decimal(-1/x**2) - dec.Decimal(1 / math.cos(x)**2)

def f3(x):
    return dec.Decimal(math.pow(2, -x) + math.pow(math.e, x) + 2*math.cos(x)) - 6

def f3der(x):
    return dec.Decimal(math.pow(math.e, x) - math.pow(2, -x) * math.log(2, math.e) - 2 * math.sin(x))

def f4(x):
    return dec.Decimal(x**3 - 3 * x + 1)

def f4der(x):
    return dec.Decimal(3 * x**2 - 3)

def bisection(function, float_precision, result_precision, a, b):
    f_a = function(a)
    f_b = function(b)

    if np.sign(f_a) == np.sign(f_b):
        print("Function has same signs at " + a + " and " + b + "!")
        return None

    dec.getcontext().prec = float_precision
    error = dec.Decimal(b) - dec.Decimal(a)
    number_of_steps = 0

    while not math.isclose(error, 0, abs_tol=result_precision):
        error = dec.Decimal(b) - dec.Decimal(a)
        error = error / 2
        c = dec.Decimal(a) + error
        f_c = function(c)

        number_of_steps += 1

        if math.isclose(error, 0, abs_tol=result_precision):
            result = MethodResult()
            result.number_of_steps = number_of_steps
            result.root = c
            return result

        if np.sign(f_a) != np.sign(f_c):
            b = c
        else:
            a = c
            f_a = f_c

def Newton_method(function, function_derivative, float_precision, max_iterations, result_precision, x):
    dec.getcontext().prec = float_precision
    x = dec.Decimal(x)

    number_of_steps = 0

    while (not math.isclose(function(x), 0, abs_tol=result_precision)) and (number_of_steps < max_iterations):
        x = x - function(x) / function_derivative(x)
        number_of_steps += 1

    if number_of_steps == max_iterations:
        print("Maximum number of iterations achieved!")
        result = MethodResult()
        result.number_of_steps = number_of_steps
        result.root = x
        return result

    result = MethodResult()
    result.number_of_steps = number_of_steps
    result.root = x
    return result

def secant_method(function, float_precision, max_iterations, result_precision, a, b):
    f_a = function(a)
    f_b = function(b)

    if np.sign(f_a) == np.sign(f_b):
        print("Function has same signs at " + a + " and " + b + "!")
        return None

    dec.getcontext().prec = float_precision

    a_n = dec.Decimal(a)
    b_n = dec.Decimal(b)

    root = a_n - f_a * (b_n - a_n) / (f_b - f_a)
    f_root = function(root)

    number_of_steps = 1

    while (not math.isclose(f_root, 0, abs_tol=result_precision)) and (number_of_steps < max_iterations):
        f_a = function(a_n)
        f_b = function(b_n)

        root = a_n - f_a * (b_n - a_n) / (f_b - f_a)
        f_root = function(root)

        number_of_steps += 1

        if np.sign(f_a) != np.sign(f_root):
            b_n = root
        elif np.sign(f_b) != np.sign(f_root):
            a_n = root
        elif math.isclose(f_root, 0, abs_tol=result_precision):
            result = MethodResult()
            result.number_of_steps = number_of_steps
            result.root = root
            return result

    if number_of_steps == max_iterations:
        print("Maximum number of iterations achieved!")
        result = MethodResult()
        result.number_of_steps = number_of_steps
        result.root = root
        return result

    result = MethodResult()
    result.number_of_steps = number_of_steps
    result.root = root
    return result

for i in [math.pow(10, -7), math.pow(10, -15), math.pow(10, -33)]:
    bisection_result = bisection(f1, 16, 1e-8, 1.5*math.pi, 2*math.pi)
    Newton_method_result = Newton_method(f1, f1der, 16, 50, 1e-8, (1.5*math.pi+2*math.pi)/2)
    secant_result = secant_method(f1, 16, 50, 1e-8, 1.5*math.pi, 2*math.pi)

    print("Function f1, precision : " + str(i))
    print(str(bisection_result.root) + " " + str(bisection_result.number_of_steps))
    print(str(Newton_method_result.root) + " " + str(Newton_method_result.number_of_steps))
    print(str(secant_result.root) + " " + str(secant_result.number_of_steps))

    print("")

    bisection_result = bisection(f2, 16, 1e-8, 0, math.pi/2)
    Newton_method_result = Newton_method(f2, f2der, 16, 50, 1e-8, math.pi/4)
    secant_result = secant_method(f2, 16, 50, 1e-8, 0, math.pi/2)

    print("Function f2, precision : " + str(i))
    print(str(bisection_result.root) + " " + str(bisection_result.number_of_steps))
    print(str(Newton_method_result.root) + " " + str(Newton_method_result.number_of_steps))
    print(str(secant_result.root) + " " + str(secant_result.number_of_steps))

    print("")

    bisection_result = bisection(f3, 16, 1e-8, 1, 3)
    Newton_method_result = Newton_method(f3, f3der, 16, 50, 1e-8, 2)
    secant_result = secant_method(f3, 16, 50, 1e-8, 1, 3)

    print("Function f3, precision : " + str(i))
    print(str(bisection_result.root) + " " + str(bisection_result.number_of_steps))
    print(str(Newton_method_result.root) + " " + str(Newton_method_result.number_of_steps))
    print(str(secant_result.root) + " " + str(secant_result.number_of_steps))

    print("\n")


# -*- coding: utf-8 -*-
"""
Created on Wed Feb  3 16:08:35 2021

@author: LI Zhengyang
"""

"""
Cutting Stock Problem
The length of a raw pipe is 218cm. Now the client wants 44 pipes of 81cm, 3 pipes of 70cm, 48 pipes of 68cm, what is the best cutting plan?

This problem can be formulated as an mixed integer linear programming (MILP) model:
    parameter:
        x(ni) the number of times item i is cut on roll n.
        y(n) = 1 if roll n is cut.
        l(i) the length of pipe i.
        L the length of a raw pipe.
        b(i) the required number of i type pipe.
    model:
        min = sum_n [ y(n) ]
        s.t.
        sum_n [ x(ni) ] >= b(i)                   for i in I (Set of different kinds of pipes)
        sum_i [ l(i) * x(ni) ] <= L * y(n)        for n in N (Set of stocks)
        x(ni) are integers
        y(n) are binaries
"""

import sys
import numpy as np
import gurobipy as gp
from gurobipy import GRB
import time

timeStart = time.time()

class Material:
    def __init__(self, name, length, num):
        self.name = name
        self.length = length
        self.num = num

class Demand:
    def __init__(self, name, length, num):
        self.name = name
        self.length = length
        self.num = num

matSet = {}
demSet = {}
for i in range(1,201):
    matSet[i] = Material('pipe', 218, 1)
demSet[1] = Demand('pipe1', 81, 44)
demSet[2] = Demand('pipe2', 70, 3)
demSet[3] = Demand('pipe3', 68, 48)

def solver():
    m = gp.Model("MIP")
    x = m.addVars( demSet, matSet, vtype='I', name="x")
    y = m.addVars( matSet, vtype='B', name='y')
    m.setObjective(gp.quicksum(y[mat] for mat in matSet), GRB.MINIMIZE)
    m.addConstrs(gp.quicksum(x.select(dem,'*')) >= demSet[dem].num for dem in demSet.keys())
    m.addConstrs(gp.quicksum(demSet[dem].length * x[dem,mat] for dem in demSet) <= y[mat] * matSet[mat].length for mat in matSet.keys())
    m.optimize()
    sol = m.x
    return m.objVal

obj = solver()
print('Obj = ',obj)
# print('Cutting plan (each column is a plan):')
# print(A)
# print('Number of implementation times for each cutting plan:')
# print(sol)

timeEnd = time.time()
print('Optimal cutting plan found !')
print('running time =', timeEnd-timeStart)


import numpy as np
import math
def getCircleOfBestFitCenter(xList, yList):
    inputList = []
    dList = []
    for i in range(0, len(xList)):
        inputList.append([xList[i], yList[i], 1])
        dList.append([(xList[i] ** 2) + (yList[i] ** 2)])

    inputMatrix = np.array(inputList)
    dMatrix = np.array(dList)

    outputMatrix = (np.linalg.pinv(inputMatrix)) @ dMatrix

    xCenter = outputMatrix[0]/2
    yCenter = outputMatrix[1]/2

    # rSquared = outputMatrix[2] - (xCenter ** 2) - (yCenter ** 2)
    rSquared = math.sqrt(((4 * outputMatrix[2]) + (outputMatrix[0] ** 2) + (outputMatrix[1] ** 2)))/2
    centers = [[xCenter, yCenter, rSquared]]

    return centers

xList = [205.26037743043992, 203.23717997069133, 201.3400034980003, 202.16121837617698]

yList = [95.39624792384349, 62.42071196788877, 84.03825895054133, 73.17519781157725]

circle = getCircleOfBestFitCenter(xList, yList)

print(circle)

# print("X: " + str(convertedCoordinates[0]) + " Y: " + str(convertedCoordinates[1]) + " Z: " + str(convertedCoordinates[2]))


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Visualizing the Mandelbrot Set Using Python by Blake Sanie, Nov 29, 2020.
https://medium.com/swlh/visualizing-the-mandelbrot-set-using-python-50-lines-f6aa5a05cf0f

Use Python Numba for C-like performance, Jan 11, 2022.
Part 1 of 4 Just-In-Time (JIT) Compilation.
"""

from PIL import Image
from os.path import exists
from numba import njit, uint8
from timeit import default_timer as timer
import colorsys, os, sys
import numpy as np

# frame parameters
width = 700 # pixels
x, y = -0.5, 0.0
xRange = 3.4
aspectRatio = 4 / 3
precision = 500

height = round(width / aspectRatio)
yRange = xRange / aspectRatio
minX = x - xRange / 2
maxX = x + xRange / 2
minY = y - yRange / 2
maxY = y + yRange / 2

jit_start = timer()

# JIT hsv_to_rgb (Note: return type UniTuple(f8,3) or None)
hsv_to_rgb = njit('(f8, f8, f8)', nogil=True)(colorsys.hsv_to_rgb)

@njit('UniTuple(u1,3)(f8, f8, f8, f8)', nogil=True)
def powerColor(distance, exp, const, scale):
    color = distance**exp
    r,g,b = hsv_to_rgb(const + scale * color, 1 - 0.6 * color, 0.9)
    r = uint8(r * 255 + 0.5)
    g = uint8(g * 255 + 0.5)
    b = uint8(b * 255 + 0.5)
    return r,g,b

@njit('void(u1[:,:,:])', nogil=True)
def mandel(pixels):
    for row in range(height):
        for col in range(width):
            x = minX + col * xRange / width
            y = maxY - row * yRange / height
            oldX = x
            oldY = y
            for i in range(precision + 1):
                a = x*x - y*y # real component of z^2
                b = 2 * x * y # imaginary component of z^2
                x = a + oldX  # real component of new z
                y = b + oldY  # imaginary component of new z
                if x*x + y*y > 4:
                    break
            if i < precision:
                distance = (i + 1) / (precision + 1)
                # Numpy ndarray is [row, col] versus Pillow Image [col, row].
                pixels[row, col] = powerColor(distance, 0.2, 0.27, 1.0)

def save_image(pixels, filename, show_image=False):
    height, width, dim = pixels.shape
    pixels = pixels.reshape((height * width * dim,))

    img = Image.frombuffer("RGB", (width, height), pixels, "raw", "RGB", 0, 1)
    img.save(filename)
    print(f"image saved as {filename}")

    if show_image:
        if sys.platform == "darwin":
            os.system(f"open {filename}")
        elif sys.platform.startswith("linux") and exists("/usr/bin/eog"):
            os.system(f"eog {filename}")

if __name__ == '__main__':
    print("     jit time {:.3f} seconds".format(timer() - jit_start))
    pixels = np.empty((height, width, 3), dtype=np.uint8)

    s = timer()
    mandel(pixels)
    print("   mandelbrot {:.3f} seconds".format(timer() - s))

    save_image(pixels, "img1.png", show_image=False)


import sympy as sp, math as m

t, x = sp.symbols('t x')
sp.init_printing(use_unicode=True)

#f=sp.Lambda(x, x*sp.cos(x)-x**2*sp.sin(x))
#f=sp.Lambda(x, sp.cos(x))
#f=sp.Lambda(x, 1/(1+x**2))
f=sp.Lambda(x, x**2*sp.exp(x))

"""
def f(x):
    #return x*m.cos(x)-x**2*m.sin(x)
    return m.cos(x)
"""

def legendre(f, raices_legendre):

    n = len(raices_legendre)-1

    l = [1]*(n+1)

    result = 0

    for i in range(n+1):
        for j in range(n+1):
            if i != j:
                l[i]*=sp.poly((x-raices_legendre[j])/(raices_legendre[i]-raices_legendre[j]))

        #print("alpha_"+str(i)+"="+str((l[i].integrate()(1)-l[i].integrate()(-1))))

        result += (l[i].integrate()(1)-l[i].integrate()(-1))*f(raices_legendre[i])

    return result

def gaussiana(f, a, b, n, raices_legendre):

    g=sp.Lambda(t, f(((b-a)*t+b+a)/2))

    return legendre(g, raices_legendre)*(b-a)/2

raices_legendre1=[-0.5773502692, 0.5773502692]
raices_legendre2=[-0.7745966692, 0, 0.7745966692]
raices_legendre3=[-0.8611363116, -0.3399810436, 0.3399810436, 0.8611363116]
raices_legendre4=[-0.9061798459, -0.5384693101, 0, 0.5384693101, 0.9061798459]

raices_legendre=raices_legendre3

n = len(raices_legendre)-1
a, b = 0, 1/2

result = gaussiana(f, a, b, n, raices_legendre)

print(result)

# Compruebo exactitud
"""
p = sp.Poly(1,x)

for i in range(2*n+3): # Todos los errores deberian dar 0 menos el último
    #print(p)
    r = gaussiana(p, -1, 1, n, raices_legendre)
    print("Grado "+str(i)+" Error =", abs(r-p.integrate()(1)+p.integrate()(-1)))
    p*=sp.poly(x)
"""

# Ej 22
"""
fa=sp.Lambda(x, x**2*sp.log(x))
fb=sp.Lambda(x, x**3*sp.exp(-x))
fc=sp.Lambda(x, 3*x/(x**2-4))
fd=sp.Lambda(x, sp.cos(x)*sp.exp(3*x))

print("a) " + str(gaussiana(fa, 1, 1.5, 2, raices_legendre2)))
print("Error: " + str(abs(0.19225935773048122-gaussiana(fa, 1, 1.5, 2, raices_legendre2))))
print("b) " + str(sp.N(gaussiana(fb, 0, 1, 2, raices_legendre2))))
print("Error " + str(abs(0.11392894084187483-sp.N(gaussiana(fb, 0, 1, 2, raices_legendre2)))))
print("c) " + str(gaussiana(fc, 1, 1.8, 2, raices_legendre2)))
print("Error: " + str(abs(-2.059573828864272-gaussiana(fc, 1, 1.8, 2, raices_legendre2))))
print("d) " + str(gaussiana(fd, 0, m.pi/4, 2, raices_legendre2)))
print("Error " + str(abs(2.6841954140755666-gaussiana(fd, 0, m.pi/4, 2, raices_legendre2))))
"""


from __future__ import print_function

import datetime
import numpy as np
import pandas as pd
#import pandas.io.data as web
import pandas_datareader as web

def annualised_sharpe(returns, N=364):
    """
    Calculate the annualised Sharpe ratio of a returns stream
    based on a number of trading periods, N. N defaults to 252,
    which then assumes a stream of daily returns.
    The function assumes that the returns are the excess of
    those compared to a benchmark.
    """
    return np.sqrt(N) * returns.mean() / returns.std()

def equity_sharpe(ticker):
    """
    Calculates the annualised Sharpe ratio based on the daily
    returns of an equity ticker symbol listed in Google Finance.
    The dates have been hardcoded here for brevity.
    """
    start = datetime.datetime(2000,1,1)
    end = datetime.datetime(2013,1,1)

    # Obtain the equities daily historic data for the desired time period
    # and add to a pandas DataFrame
    pdf = web.DataReader(ticker, 'google', start, end)

    # Use the percentage change method to easily calculate daily returns
    pdf['daily_ret'] = pdf['Close'].pct_change()

    # Assume an average annual risk-free rate over the period of 5%
    pdf['excess_daily_ret'] = pdf['daily_ret'] - 0.05/252

    # Return the annualised Sharpe ratio based on the excess daily returns
    return annualised_sharpe(pdf['excess_daily_ret'])

def market_neutral_sharpe(ticker, benchmark):
    """
    Calculates the annualised Sharpe ratio of a market
    neutral long/short strategy inolving the long of 'ticker'
    with a corresponding short of the 'benchmark'.
    """
    start = datetime.datetime(2000, 1, 1)
    end = datetime.datetime(2013, 1, 1)

    # Get historic data for both a symbol/ticker and a benchmark ticker
    # The dates have been hardcoded, but you can modify them as you see fit!
    tick = web.DataReader(ticker, 'google', start, end)
    bench = web.DataReader(benchmark, 'google', start, end)

    # Calculate the percentage returns on each of the time series
    tick['daily_ret'] = tick['Close'].pct_change()
    bench['daily_ret'] = bench['Close'].pct_change()

    # Create a new DataFrame to store the strategy information
    # The net returns are (long - short)/2, since there is twice
    # the trading capital for this strategy
    strat = pd.DataFrame(index=tick.index)
    strat['net_ret'] = (tick['daily_ret'] - bench['daily_ret'])/2.0

    # Return the annualised Sharpe ratio for this strategy
    return annualised_sharpe(strat['net_ret'])

if __name__ == "__main__":
    print(
        "Google Sharpe Ratio: %s" %
        equity_sharpe('GOOG')
    )
    print(
        "Google Market Neutral Sharpe Ratio: %s" %
        market_neutral_sharpe('GOOG', 'SPY')
    )


import numpy as np
import numba
import random

def calc_pi(N):
    M = 0
    for i in range(N):
        # Simulate impact coordinates
        x = random.uniform(-1, 1)
        y = random.uniform(-1, 1)

        # True if impact happens inside the circle
        if x**2 + y**2 < 1.0:
            M += 1
    return 4 * M / N

def calc_pi_numpy(N):
    # Simulate impact coordinates
    pts = np.random.uniform(-1, 1, (2, N))
    # Count number of impacts inside the circle
    M = np.count_nonzero((pts**2).sum(axis=0) < 1)
    return 4 * M / N

@numba.jit
def sum_range_numba(a: int):
    """Compute the sum of the numbers in the range [0, a)."""
    x = 0
    for i in range(a):
        x += i
    return x

@numba.jit
def calc_pi_numba(N):
    M = 0
    for i in range(N):
        # Simulate impact coordinates
        x = random.uniform(-1, 1)
        y = random.uniform(-1, 1)

        # True if impact happens inside the circle
        if x**2 + y**2 < 1.0:
            M += 1
    return 4 * M / N

def test_calc_pi():
    assert round(calc_pi(10**6)) == 3

def test_calc_pi_numpy():
    assert round(calc_pi_numpy(10**6)) == 3

def test_sum_range_numba():
    for n in np.random.randint(1000, 10000, size=10):
        assert sum_range_numba(n) == (n * (n - 1)) // 2

def test_calc_pi_numba():
    assert round(calc_pi_numba(10**6)) == 3


# Noise reduction method
#
# utilizing "Gradual Release of Sensitive Data under Differential Privacy"
# by Fragkiskos Koufogiannis, Shuo Han, and George J. Pappas

# Background:
# We wish to release a function f of a private database, say D.
# The true output is a numpy array M = f(D).
# Each entry of M has "sensitivity" s meaning that if D changes to a
# neighboring database D', then the corresponding entry of M' = f(D')
# changes by at most s.
#
# We use differential privacy to release private versions of M.
# The output is a sequence of versions of M that become more accurate
# and less private.
#
# Given M, sensitivity, and a INCREASING list of epsilons eps_1, ..., eps_T
# 1. Construct a random walk starting from M, so that at step t, the {i,j}
#    entry is distributed as M_{ij} + Laplace(1 / eps_{T-t}).
# 2. Return the list of resulting matrices IN REVERSE ORDER of the walk,
#    i.e. from most noisy (most private) to least noisy (least private).
# 3. In particular, releasing all of the first t matrices in the list
#    is eps_t private, for each t, because:
#    - releasing the t-th matrix in the list is eps_t private
#    - all previous matrices are post-processings of this (by the random walk)

import numpy as np

# Helper function
# filter by returning each entry of matrix independently with probability 1 - prob
# and 0 with probability prob
def do_filt(prob, matrix):
  f = lambda x: 0 if np.random.random() <= prob else x
  return np.vectorize(f)(matrix)

# Main function
# Input:
#   numpy array M whose privacy is to be protected
#   sensitivity of the each entry of M
#   a INCREASING list of epsilons
#
# Output: a list of Mhat matrices approximating M, where releasing the first t
# matrices is eps_t private
def gen_list(M, sensitivity, eps_list):
  try:
    steps = len(eps_list)
  except:
    steps = eps_list.shape[0]
  shape = M.shape
  rev_eps_list = eps_list[::-1]
  noise_list = [np.random.laplace(scale=sensitivity/eps, size=shape) for eps in rev_eps_list]

  # first step, just add the noise to M
  walk = [M + noise_list[0]]

  # other steps, add the noise of each entry with only a certain probability
  filt_probs = [ (rev_eps_list[j] / rev_eps_list[j-1])**2 for j in range(1, steps) ]
  walk_steps = [do_filt(p, noise_list[1+j]) for j,p in enumerate(filt_probs)]

  for j in range(1, len(rev_eps_list)):
    walk.append(walk[j-1] + walk_steps[j-1])
  walk.reverse()
  return np.array(walk)


import numpy as np

import matplotlib.pyplot as plt

def gini(p):
    return p * (1 - p) + (1 - p) * (1 - (1 - p))

def entropy(p):
    return -(p * np.log2(p) + (1 - p) * np.log2(1 - p))

def error(p):
    return 1 - np.max([p, 1 - p])

x = np.arange(0.0, 1.0, 0.01)

ent = [entropy(p) if p != 0 else None for p in x]

# Scaled entropy
sc_ent = [e * 0.5 if e else None for e in ent]

err = [error(i) for i in x]

fig = plt.figure()
ax = plt.subplot(111)

for i, lab, ls, c, in zip([ent, sc_ent, gini(x), err],
                          ['Entropy', 'Entropy (scaled)',
                           'Gini impurity',
                           'Misclassification error'],
                          ['-', '-', '--', '-.'],
                          ['black', 'lightgray',
                           'red', 'green', 'cyan']):
    line = ax.plot(x, i, label=lab, linestyle=ls, lw=2, color=c)

ax.legend(loc='upper center', bbox_to_anchor=(0.5, 1.15), ncol=5,
          fancybox=True, shadow=False)

ax.axhline(y=0.5, linewidth=1, color='k', linestyle='--')
ax.axhline(y=1.0, linewidth=1, color='k', linestyle='--')

plt.ylim([0, 1.1])
plt.xlabel('p(i=1)')
plt.ylabel('impurity index')
plt.tight_layout()
plt.show()


"""
Linear Regression it assumes a linear relationship between dependent variable(y) and independent variable(X).

So the value of y can be calculated  using a linear combination of input variable X.

Very similar to how the equation of a straight line takes a linear relation between x-axis and y-axis as

y = m(x) + c

where:
    m -> slope of the line
    c -> y-intercept

we can also write equation for predicting value as

y = b0 + b1 * X

where b0 and b1 are the coefficients that we need to estimate from the training data.

Steps:
    1. Calculate the Mean and Variance
    2. Calculate covariance
    3. Estimate coefficients
    4. Make predictions
    5. Evaluate algorithm
"""
# imports
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
from sklearn.model_selection import train_test_split
import random
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error, r2_score

random.seed(1)

# plt.scatter(X, y)
# plt.show()
# after looking at the scatter plot you can see there is a linear relation between X and  y.

# Step - 1 : Calculating Mean and variance of both X and y.

def mean(values):
    return sum(values) / float(len(values))

def variance(values, mean):
    return sum((np.array(values) - mean) ** 2) / float(len(values))

# Step -2 : Calculating Covariance of X and y

def covariance(x, mean_x, y, mean_y):
    x = np.array(x)
    y = np.array(y)
    return sum((x - mean_x) * (y - mean_y)) / float(len(x))

# Step - 3 :  Estimate coefficients
def coefficient(X, y):
    mean_x, mean_y = mean(X), mean(y)
    b1 = covariance(X, mean_x, y, mean_y) / variance(X, mean_x)
    b0 = mean_y - b1 * mean_x
    return [b0, b1]

def split_data(X, y, test_size=0.2):
    return train_test_split(X, y, test_size=test_size, random_state=42)

# Step - 4 : Make predictions
def simple_linear_regression(X_train, y_train, X_test):
    b0, b1 = coefficient(X_train, y_train)
    predictions = b0 + b1 * X_test
    return predictions

def rmse_metric(actual, predicted):
    actual = np.array(actual)
    predicted = np.array(predicted)
    prediction_error = actual - predicted
    rmse = np.sqrt(sum(prediction_error ** 2) / float(len(actual)))
    return rmse

def evaluate_algorithm(X, y, algorithm, split_size):
    X_train, X_test, y_train, y_test = split_data(X, y, split_size)
    predicted = algorithm(X_train, y_train, X_test)
    rmse = rmse_metric(y_test, predicted)
    return rmse

#
# print("X-stats : mean= %0.3f variance= %0.3f " % (mean_x, var_x))
# print("y-stats : mean= %0.3f variance= %0.3f " % (mean_y, var_y))
# print("Covariance of X, y : %0.3f" % (covarxy))
# print("coefficients  b0=%0.3f,. b1=%0.3f " % (b0, b1))

df = pd.read_excel(io="insurance.xls", encoding='ascii')
X = df["X"]
y = df["Y"]
test_size = 0.2

b0, b1 = coefficient(X, y)
y_hat = b0 + b1 * X

# plt.scatter(X, y, label="Data points")
# plt.plot(X, y_hat, color='#00ff00', label="LinearRegression")
# plt.legend()
# plt.show()

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=test_size, random_state=42)

def rSquare(X_train, X_test, y_train, y_test, algorithm):
    predicted = algorithm(X_train, y_train, X_test)
    y_mean_line = mean(y_test)
    squared_error_regr = sum((predicted - y_test)**2)
    squared_error_ymean = sum((y_test - y_mean_line)**2)
    return 1 - squared_error_regr / squared_error_ymean

rmse = evaluate_algorithm(X, y, simple_linear_regression, test_size)
print("RMSE from Math : %0.3f" % (rmse))
print("Rsquare error from Math: %0.3f " % (rSquare(X_train, X_test, y_train, y_test, simple_linear_regression)))

lr = LinearRegression()
lr.fit(np.array(X_train).reshape(-1, 1), y_train)
y_pred = lr.predict(np.array(X_test).reshape(-1, 1))
print("RMSE from sklearn ", np.sqrt(mean_squared_error(y_test, y_pred)))
print("Rsquare error : ",r2_score(y_test, y_pred))