Skip to content

Instantly share code, notes, and snippets.

@schcriher
Created November 13, 2023 01:34
Show Gist options
  • Save schcriher/7855364aa1ac1b6675c96e69dfd56d0c to your computer and use it in GitHub Desktop.
Save schcriher/7855364aa1ac1b6675c96e69dfd56d0c to your computer and use it in GitHub Desktop.
peso_por_dolar.py
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
# Copyright (C) 2023 Schmidt Cristian Hernán
# Este script descarga el histórico de los valores del precio del dólar en la
# República Argentina y los gráfica, agregando además los cambios de presidentes
# que hubieron en el rango de los valores disponibles.
#
# Se almacenan además los datos en un archivo csv y se guarda la imagen en formato
# png y svg. Los tres archivos en la misma carpeta donde donde esté este archivo,
# cada uno con su respectiva extensión.
import os
import sys
import math
import json
import time
import urllib
import datetime
try:
import requests
import bs4
import pandas
import matplotlib
matplotlib.use('GTK3Agg')
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
except ImportError:
print('Debe tener instalado: requests bs4 pandas matplotlib\n')
print('Ejecute: python3 -m pip install --user requests bs4 pandas matplotlib')
sys.exit(-1)
pandas.plotting.register_matplotlib_converters()
BASE = os.path.splitext(__file__)[0]
CSV = BASE + '.csv'
SVG = BASE + '.svg'
PNG = BASE + '.png'
TMP = BASE + '.tmp'
url = 'https://www.bcra.gob.ar/PublicacionesEstadisticas/Principales_variables_datos.asp'
now = datetime.datetime.now()
ini = '2010-06-01' # límite del servidor (url)
fin = now.strftime('%Y-%m-%d')
tmp_exists = os.path.exists(TMP)
if tmp_exists and time.time() - os.path.getmtime(TMP) < 3600: # cache de 1 hora
with open(TMP, mode='r') as fid:
head, _x, _y = json.load(fid)
else:
if tmp_exists:
os.remove(TMP)
data = {# Extraído del form de la pagina (url)
'fecha_desde': ini,
'fecha_hasta': fin,
'primeravez': '1',
'serie': '7927',
'serie1': '0',
'serie2': '0',
'serie3': '0',
'serie4': '0',
'detalle': 'Tipo de Cambio Minorista ($ por US$) Comunicación B 9791',
}
response = requests.post(url, data=data)
response.encoding = response.apparent_encoding
html = bs4.BeautifulSoup(response.text, 'html.parser')
table = html.find(name='table', attrs={'class': 'table-BCRA'})
head, *rows = table.findAll(name='tr')
head = [c.string for c in head.findAll(name='th')]
_x = []
_y = []
for row in rows:
cell = row.findAll(name='td')
_x.append(cell[0].string.strip())
_y.append(cell[1].string.replace(',', '.').strip())
with open(TMP, mode='w') as fid:
json.dump((head, _x, _y), fid, indent=4)
_xx = [datetime.datetime.strptime(i, '%d/%m/%Y') for i in _x]
_yy = [float(i) for i in _y]
df = pandas.Series(data=_yy, index=_xx, name=r'Valor ARS/USD')
plt.plot(df, 'k-', linewidth=0.8) # Dólar oficial SIN impuestos
# https://www.argentina.gob.ar/normativa/buscar
# IMPUETO PAIS
# Ley 27541, art 39 → 30%, vigencia 2019-12-24
# https://www.argentina.gob.ar/normativa/nacional/ley-27541-333564
# https://servicios.infoleg.gob.ar/infolegInternet/verNorma.do?id=333564
# ADELANTO DE GANANCIAS
# Resolución General 4815/2020, art 5 → 35%, vigencia 2020-09-16
# https://www.argentina.gob.ar/normativa/nacional/resoluci%C3%B3n-4815-2020-342273
# https://www.afip.gob.ar/regimen-devolucion-percepciones/percepcion/que-es.asp
# Resolución General 5393/2023, art 2 → 45%, vigencia 2023-07-26
# https://www.argentina.gob.ar/normativa/nacional/resoluci%C3%B3n-5393-2023-387216
# Resolución General 5430/2023, art 1 → 45% y 25% (70%), vigencia 2023-10-10
# http://biblioteca.afip.gob.ar/dcp/REAG01005430_2023_10_09
df_base = df.copy()
mod1 = ('2019-12-26', 'Ley 27541\nart 39') # "26" siguiente día hábil
mod2 = ('2020-09-16', 'RG 4815\nart 5')
mod3 = ('2023-07-26', 'RG 5393\nart 2')
mod4 = ('2023-10-10', 'RG 5430\nart 1')
df[mod1[0]:] = df_base[mod1[0]:] * 1.30 # Impuesto PAIS
df[mod2[0]:] = df_base[mod2[0]:] * 1.65 # Impuesto PAIS + adelanto de ganancias
df[mod3[0]:] = df_base[mod3[0]:] * 1.75 # Impuesto PAIS + adelanto de ganancias
df[mod4[0]:] = df_base[mod4[0]:] * 2.00 # Impuesto PAIS + adelanto de ganancias
plt.plot(df, 'k-', linewidth=1.5) # Dólar oficial CON impuestos (para compra de billetes)
events = (
None,
'2011',
('2011-08-14', 'Primarias', 'dotted'),
('2011-10-23', 'Generales', 'dashed'),
('2011-12-10', 'Cristina Fernández', 'solid'),
None,
'2015',
('2015-08-09', 'Primarias', 'dotted'),
('2015-10-25', 'Generales', 'dashed'),
('2015-11-22', 'Balotaje', 'dashdot'),
('2015-12-10', 'Mauricio Macri', 'solid'),
None,
'2019',
('2019-08-11', 'Primarias', 'dotted'),
('2019-10-27', 'Generales', 'dashed'),
('2019-12-10', 'Alberto Fernández', 'solid'),
None,
('2022-08-03', 'Asume Sergio Massa como Ministro de Economía', 'solid', 'blue'),
None,
'2023',
('2023-08-13', 'Primarias', 'dotted'),
('2023-10-22', 'Generales', 'dashed'),
('2023-11-19', 'Balotaje', 'dashdot'),
('2023-12-10', '?', 'solid'),
)
xmin = df.keys()[0]
xmax = df.keys()[-1]
xspan = xmax - xmin
ymean = df.mean()
ymin = df.min()
ymax = df.max()
yspan = ymax - ymin
handles = []
for data in events:
if data is None:
handles.append(mpatches.Patch(alpha=0))
elif isinstance(data, str):
handles.append(mpatches.Patch(alpha=0, label=data))
else:
date, text, linestyle, *color = data
color = color[0] if color else 'green'
date = datetime.datetime.strptime(date, '%Y-%m-%d')
handles.extend(plt.plot([date, date], [ymin, ymax],
alpha=0.5, linewidth=1, color=color,
linestyle=linestyle, label=text))
def including_delta(plt, tini, tfin, df):
dat = df[tini:tfin]
x = dat.index.mean().date()
y1 = float(dat[0])
y2 = float(dat[-1])
d = y2 - y1
p = y2 / y1 * 100
yoffset = yspan / 33
if d > ymean:
va = 'center'
ym = y1 + d / 2
elif y1 > ymean and y2 > ymean:
va = 'top'
ym = y1 - yoffset
else:
va = 'bottom'
ym = y2 + yoffset
plt.annotate('', xy=(x, y1), xytext=(x, y2),
arrowprops=dict(arrowstyle='|-|', connectionstyle='arc3', color='red'))
code = '$\$\,{:.2f}$\n$~{:.0f}\,\%$'
params = {'color': 'red', 'fontsize': 10, 'ha': 'center', 'va': va,
'bbox': {'boxstyle': 'round', 'fc': 'white', 'ec': 'red'}}
plt.text(x, ym, code.format(d, p), **params)
including_delta(plt, '2011-12-10', '2015-12-10', df)
including_delta(plt, '2015-12-10', '2019-12-10', df)
including_delta(plt, '2019-12-10', fin, df)
#for fecha, texto in (mod1, mod2, mod3, mod4):
# x = datetime.datetime.strptime(fecha, '%Y-%m-%d')
# y1 = df[:x][-2]
# y2 = df[:x][-1]
# ym = (y2 + y1) / 2
# plt.annotate(texto, xy=(x, ym), xytext=(-3, 3),
# fontsize='x-small', ha='center', va='center',
# textcoords='offset fontsize', arrowprops={'arrowstyle': '->', 'color': 'indigo'},
# bbox={'boxstyle': 'round', 'fc': 'white', 'lw': 1, 'ec': 'indigo'})
point = '2020-12-21'
x = datetime.datetime.strptime(point, '%Y-%m-%d')
y = df_base[point]
plt.annotate('Sin impuestos', xy=(x, y), xytext=(0, -3),
fontsize='x-small', ha='center', va='center',
textcoords='offset fontsize', arrowprops={'arrowstyle': '->'},
bbox={'boxstyle': 'round', 'fc': 'white', 'lw': 0})
plt.xlabel(head[0])
plt.ylabel(head[1])
ini = ini.replace('-', '/')
fin = fin.replace('-', '/')
plt.title('Valor del dólar oficial (mas impuestos) • {} ─ {} • {}'.format(
ini, fin, urllib.parse.urlsplit(url).netloc))
plt.legend(handles=handles, loc='upper left', fontsize='x-small', frameon=False)
plt.text(0.01, 0.01, 'Schcriher',
fontsize='x-small', ha='left', va='bottom',
transform=plt.gca().transAxes,
bbox={'boxstyle': 'round', 'fc': 'white', 'lw': 0})
plt.text(0.99, 0.01, 'Datos obtenidos el ' + now.strftime('%Y-%m-%d %H:%M'),
fontsize='x-small', ha='right', va='bottom',
transform=plt.gca().transAxes,
bbox={'boxstyle': 'round', 'fc': 'white', 'lw': 0})
plt.tight_layout()
fig = plt.gcf()
fig.set_size_inches(9, 6, forward=True)
fig.set_dpi(90)
df.to_csv(CSV, sep=',', encoding='utf-8', index_label=head[0], header=True)
plt.savefig(PNG, format='png')
plt.savefig(SVG, format='svg')
plt.show()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment