In [1]:
###############################
# Alunos
# André Ferreira Bem Silva
# Fernando D'Império
# Marcos Vinício Siqueira
###############################
# Neste arquivo estão contidos os três
# exercícios da mina de ouro
###############################
import math
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import random
import seaborn as sns
import scipy as sp
from scipy.stats import norm
In [2]:
class GoldMineExerciseBase(object):
def __init__(self):
#Problem constants
self._gold_price = 400
self._gold_cost = 300
self._gold_volatility = 0.3
self._half_square_gold_volatility = 0.5 * self._gold_volatility**2
self._gold_extraction_quantity = 10_000
self._risk_free_interest_rate = 0.1
self._number_of_years = 10
def _calc_results_for_year(self, gold_price_last_year, years_delta):
return gold_price_last_year * math.exp((self._risk_free_interest_rate - self._half_square_gold_volatility) * years_delta + self._gold_volatility * math.sqrt(years_delta) * norm.ppf(np.random.uniform()))
def run_mine_year_simulation(self):
gold_prices = [self._gold_price for i in range(0, self._number_of_years)]
for i in range(1, self._number_of_years):
gold_prices[i] = self._calc_results_for_year(gold_price_last_year = gold_prices[i - 1], years_delta = 1)
return gold_prices
def _calculate_gold_quantity(self, gold_price):
return 0 if gold_price < self._gold_cost else self._gold_extraction_quantity
def calc_year_risk_from_index(self, i):
return math.exp(-(i % self._number_of_years) * self._risk_free_interest_rate)
def add_profit_and_npv_to_df(self, simulations_df):
simulations_df['profit'] = (simulations_df['gold_price'] - simulations_df['gold_cost']) * simulations_df['gold_quantity']
#simulations_df['npv'] = simulations_df.year.map(lambda i: simulations_df.iloc[i]['profit'] * self.calc_year_risk_from_index(i))
simulations_df['npv'] = simulations_df['profit'] * simulations_df['year'].map(lambda year: self.calc_year_risk_from_index(year))
class GoldMineExercise1(GoldMineExerciseBase):
def __init__(self):
GoldMineExerciseBase.__init__(self)
def _create_simulation(self, simulation_index):
gold_prices = self.run_mine_year_simulation()
output_rows = []
for i in range(0, self._number_of_years):
gold_price = gold_prices[i]
output_rows.append({
'gold_price': gold_price,
'gold_cost': self._gold_cost,
'gold_quantity': self._calculate_gold_quantity(gold_price),
'simulation': simulation_index,
'year': i
})
return pd.DataFrame(output_rows)
def create_simulations(self, number_of_simulations):
simulation_df = pd.DataFrame(columns = ['gold_price', 'gold_cost', 'gold_quantity', 'simulation', 'year'])
return pd.concat(
[self._create_simulation(simulation_index) for simulation_index in range(0, number_of_simulations)],
ignore_index = True
)
In [3]:
# Creates object to solve exercise 1
first_exercise = GoldMineExercise1()
simulations_df = first_exercise.create_simulations(50_000)
#Creates new columns to solve the problems
first_exercise.add_profit_and_npv_to_df(simulations_df)
simulations_df
Out[3]:
In [4]:
simulations_npv = simulations_df.groupby(['simulation'])['npv'].agg(sum)
# Solution for Exercise 1
print('Mean value = ', round(np.mean(simulations_npv), 2), ' Median value = ', round(np.median(simulations_npv), 2))
In [5]:
ax = sns.distplot(simulations_npv)
plt.xlabel('Net Present Value')
plt.ylabel('# Ocurrences')
Out[5]:
In [6]:
class GoldMineExercise2(GoldMineExerciseBase):
def __init__(self):
GoldMineExerciseBase.__init__(self)
self._gold_expansion_cost = 320
self._gold_enhanced_extraction = 15_000
self._expansion_total_cost = 8_000_000
def add_build_costs_if_building_expansion(self, expand_now, expanded):
return self._expansion_total_cost if expand_now != expanded else 0
def expand_this_year_flag(self, cut_expand, gold_price):
return cut_expand > gold_price
def calc_gold_cost_expanded_or_not(self, expanded):
return self._gold_expansion_cost if expanded else self._gold_cost
def calc_gold_quantity_expanded_or_not(self, expanded):
return self._gold_extraction_quantity if expanded else self._gold_enhanced_extraction
def _create_simulation(self, simulation_index):
simulation_columns = [
'simulation', 'year', 'gold_price', 'gold_cost', 'gold_quantity',
'expanded', 'build_costs', 'cut_expand'
]
simulation_df = pd.DataFrame(columns = simulation_columns)
output_rows = [{
'year': 0,
'simulation': simulation_index,
'gold_price': self._gold_price,
'gold_cost': self._gold_cost,
'gold_quantity': self._gold_extraction_quantity,
'expanded': False,
'cut_expand': 0,
'build_costs' : 0,
}]
gold_year_prices = self.run_mine_year_simulation()
for i in range(1, self._number_of_years):
previous_year = i - 1
cut_expand = random.randint(256, 2080)
expanded = self.expand_this_year_flag(gold_year_prices[i], cut_expand) or output_rows[previous_year]['expanded']
build_costs = self.add_build_costs_if_building_expansion(expanded, output_rows[previous_year]['expanded'])
output_rows.append({
'gold_price': gold_year_prices[i],
'gold_cost': self.calc_gold_cost_expanded_or_not(output_rows[previous_year]['expanded']),
'cut_expand': cut_expand,
'expanded': expanded,
'build_costs': build_costs,
'simulation': simulation_index,
'gold_quantity': self.calc_gold_quantity_expanded_or_not(output_rows[previous_year]['expanded']),
'year': i
})
return pd.DataFrame(output_rows)
def create_simulations(self, number_of_simulations):
simulations = [self._create_simulation(i) for i in range(0, number_of_simulations)]
return pd.concat(simulations, ignore_index = True)
def add_profit_and_npv_to_df(self, simulations_df):
GoldMineExerciseBase.add_profit_and_npv_to_df(self, simulations_df)
simulations_df['f_profit'] = (simulations_df['gold_price'] - simulations_df['gold_cost']) * simulations_df['gold_quantity'] - simulations_df['build_costs']
simulations_df['f_npv'] = simulations_df['f_profit'] * simulations_df['year'].map(lambda year: self.calc_year_risk_from_index(year))
In [7]:
second_exercise = GoldMineExercise2()
simulations2_df = second_exercise.create_simulations(50_000)
second_exercise.add_profit_and_npv_to_df(simulations2_df)
simulations2_df
Out[7]:
In [8]:
simulations2_npv = simulations2_df.groupby(['simulation'])['f_npv'].agg(sum)
# Solution for Exercise 2
print('Mean value = ', round(np.mean(simulations2_npv), 2), ' Median value = ', round(np.median(simulations2_npv), 2))
In [9]:
ax = sns.distplot(simulations2_npv)
plt.xlabel('Net Present Value')
plt.ylabel('# Ocurrences')
Out[9]:
In [10]:
class GoldMineExercise3(GoldMineExerciseBase):
def __init__(self):
GoldMineExerciseBase.__init__(self)
self._gold_cost = 250
self._gold_extraction_fixed_costs = 1_000_000
self._mine_startup_cost = 2_000_000
self._mine_shutdown_cost = 1_500_000
self._number_of_years = 20
def _create_simulation(self, simulation_index):
simulation_columns = [
'simulation', 'year', 'gold_price', 'gold_cost', 'gold_quantity',
'opened_mine', 'open_cut', 'closed_mine', 'close_cut', 'mine_is_open'
]
simulation_df = pd.DataFrame(columns = simulation_columns)
gold_year_prices = self.run_mine_year_simulation()
output_rows = []
for i in range(0, self._number_of_years):
mine_is_open = self._is_mine_open_flag(output_rows, i)
gold_price = gold_year_prices[i]
output_rows.append({
'year': i,
'simulation': simulation_index,
'gold_price': gold_price,
'gold_cost': self._gold_cost,
'gold_quantity': self._gold_extraction_quantity if mine_is_open else 0,
'mine_is_open': mine_is_open,
'closed_mine': self._close_mine_flag(random.randint(0, 2000), gold_price, mine_is_open),
'opened_mine': self._open_mine_flag(random.randint(0, 300), gold_price, mine_is_open)
})
return pd.DataFrame(output_rows)
def _calc_status_based_costs(self, mine_is_open, close_flag, open_flag):
return (0 if mine_is_open else self._gold_extraction_fixed_costs) + (self._mine_shutdown_cost if close_flag else 0) + (self._mine_startup_cost if open_flag else 0)
def _previous_base_cash_flow_or_zero(self, output_rows, year):
0 if year == 0 else output_rows[year]['base_cash_flow']
def _is_mine_open_flag(self, output_rows, year):
return True if year == 0 else (output_rows[year - 1]['mine_is_open'] and not output_rows[year - 1]['closed_mine']) or output_rows[year - 1]['opened_mine']
def _open_mine_flag(self, cut_open, gold_price, is_open_flag):
return not is_open_flag and gold_price > cut_open
def _close_mine_flag(self, cut_close, gold_price, is_open_flag):
return is_open_flag and gold_price <= cut_close
def create_simulations(self, number_of_simulations):
simulations = [self._create_simulation(i) for i in range(0, number_of_simulations)]
return pd.concat(simulations, ignore_index = True)
def add_profit_and_npv_to_df(self, simulations_df):
cash_flow_multiplier = simulations_df['mine_is_open'].map(lambda mine_is_open: 1 if mine_is_open else 0)
base_cash_flow = (simulations_df['gold_price'] - simulations_df['gold_cost']) * simulations_df['gold_quantity'] * cash_flow_multiplier
fixed_costs = simulations_df.year.map(lambda i: self._calc_status_based_costs(simulations_df.iloc[i]['mine_is_open'], simulations_df.iloc[i]['closed_mine'], simulations_df.iloc[i]['opened_mine']))
simulations_df['fixed_costs'] = fixed_costs
simulations_df['profit'] = base_cash_flow - fixed_costs
simulations_df['npv'] = simulations_df['profit'] * simulations_df['year'].map(lambda year: self.calc_year_risk_from_index(year))
In [11]:
third_exercise = GoldMineExercise3()
simulations3_df = third_exercise.create_simulations(50_000)
#third_exercise.add_profit_and_npv_to_df(simulations3_df)
third_exercise.add_profit_and_npv_to_df(simulations3_df)
simulations3_df
Out[11]:
In [12]:
simulations3_npv = simulations3_df.groupby(['simulation'])['npv'].agg(sum)
# Solution for Exercise 3
print('Mean value = ', round(np.mean(simulations3_npv), 2), ' Median value = ', round(np.median(simulations3_npv), 2))
In [13]:
ax = sns.distplot(simulations3_npv)
plt.xlabel('Net Present Value')
plt.ylabel('# Ocurrences')
Out[13]: