Compare commits

..
29 Commits
Author SHA1 Message Date
dodox ecb387a467 ECB chnaged their API without telling anyone... (Ok, its an internal API, but I still dont like it!) 2024-01-14 16:58:36 +01:00
dodox 89636040c6 Merge branch 'master' of dominik.roth.ml:dodox/meuro 2021-10-30 18:21:33 +02:00
dodox 201c112184 Fixed README typo 2021-10-30 18:21:09 +02:00
dodox eb4aad68fc Fixed README typo 2021-10-30 18:18:55 +02:00
dodox 178f1a169d README++ 2021-10-30 18:18:22 +02:00
dodox 9ad1111055 Removed uneccesary code and more comments 2021-10-30 18:12:53 +02:00
dodox 87789e8500 README: Better description of the cache 2021-10-30 18:01:50 +02:00
dodox 69c6ee4cd0 Prettier code 2021-10-30 17:56:30 +02:00
dodox 59bab52160 Better README 2021-10-30 17:52:53 +02:00
dodox 8147f64b4b Updated README 2021-10-30 17:52:13 +02:00
dodox 338e9af870 Changed Heading-sizes in README 2021-10-30 17:46:34 +02:00
dodox 32a15c8353 Better printing: Now with , for every 3-group 2021-10-30 17:39:56 +02:00
dodox d87d260a1c Bug Fix: Exchange-Rate for future years was wrong 2021-10-30 17:37:27 +02:00
dodox 2de965414a Better icon 2021-10-30 14:43:16 +02:00
dodox d95bb4e78a Added Month-Name example to README 2021-10-30 14:36:32 +02:00
dodox afeee78836 Fixed Bug: Currencly removal from string could destroy date 2021-10-30 14:35:03 +02:00
dodox 58cf8809ad Describing the cache in the README 2021-10-30 14:24:52 +02:00
dodox 872df67776 Changed cache workings to allow better controll when used as lib 2021-10-30 14:19:50 +02:00
dodox 237940861f Included a cache 2021-10-30 14:18:14 +02:00
dodox 5702811203 Fixed huge bug: Calcs were wrong 2021-10-30 13:12:36 +02:00
dodox 33de47dde2 New example in README 2021-10-30 13:10:22 +02:00
dodox c690c495f5 Now with an icon! 2021-10-30 13:07:57 +02:00
dodox 08b6ec53fd Updated README 2021-10-30 12:50:22 +02:00
dodox aa659bd383 ignore ',' in cli-input (allow e.g. 50,000€) 2021-10-30 12:49:57 +02:00
dodox 4946d65fc7 Fixed README 2021-10-30 12:42:03 +02:00
dodox 43c0f52c59 Updated README (describes CLI-Interface) 2021-10-30 12:40:37 +02:00
dodox e9fc21f404 Better printing 2021-10-30 11:50:07 +02:00
dodox 3e8512ae65 Added cli interface which understand human language 2021-10-30 11:45:25 +02:00
dodox 0632833bb9 made meuro.py executable 2021-10-30 10:57:43 +02:00
4 changed files with 165 additions and 29 deletions
+1
View File
@@ -1 +1,2 @@
__pycache__ __pycache__
cache.json
+24 -2
View File
@@ -1,5 +1,7 @@
# Meuro # Meuro
![Logo Missing](./icon_meuro.svg)
Prices in euros from different times can not be compared without taking inflation into account. Prices in euros from different times can not be compared without taking inflation into account.
This also negatively impacts peoples abilities to gauge their own financial prosperity because what they conceive as a raise might not even cover they loss in purchase-power due to inflation. This also negatively impacts peoples abilities to gauge their own financial prosperity because what they conceive as a raise might not even cover they loss in purchase-power due to inflation.
To fix this issue, I present: To fix this issue, I present:
@@ -8,7 +10,23 @@ To fix this issue, I present:
Meuro's worth is equivalent to that of the euro on January 1st 2001 00:00 GMT. Meuro's worth is equivalent to that of the euro on January 1st 2001 00:00 GMT.
(Yes, thats 2001 and not 2000 because the first millenium started in the year 1 and so the third one started in 2001. I do realize that this is stupid, but that's not my fault and all the people that celebrated the beginning of the new century at silvester 2000 should technically have waited another year.) (Yes, thats 2001 and not 2000 because the first millenium started in the year 1 and so the third one started in 2001. I do realize that this is stupid, but that's not my fault and all the people that celebrated the beginning of the new century at silvester 2000 should technically have waited another year.)
## This repo ## This repo
What you find in this repo is a small python-libary that connects to the ecb to get historic inflation-data and computes the conversion-factor between € and µ for any given date (after Jan 1st 2001). What you find in this repo is a small python-libary that connects to the ecb to get historic inflation-data and computes the conversion-factor between € and µ for any given date after Jan 1st 2001 (cli-interface only works before Jan 1st 10000).
## Usage
You probably want to use this tool using the cli-interface which tries to understand requests in human language.
### Examples:
#### ./meuro.py What is 200 eur in meuro
#### ./meuro.py I want to know how much 2000€ from 11.11.2011 are in meuro
#### ./meuro.py How much meuro are 12 euro from January 2010
#### ./meuro.py 50,000€ 10.10.2010
#### ./meuro.py 1000 meur 2055
#### ./meuro.py 3.14E 01.02.2003 12:34
### For best Results:
To reference the currencies use €, eur, euro, e, E or µ, meu, meur, meuro, m, M.
Currencies should be an integer or have 2 digits after the point (.).
Dates should always be dd.mm.YYYY or dd.mm.yy.
If no date is supplied (or understood) the current date is used.
## Python-API
You can also import this libary from another python file
##### The provided functions are: ##### The provided functions are:
#### exchangeRate(date=None) #### exchangeRate(date=None)
Gives you the exchange rate between euros and meuros for the given date. Gives you the exchange rate between euros and meuros for the given date.
@@ -17,8 +35,12 @@ Converts the given amount of euros to meuros for the given date. (wholeCents mea
#### meuroToEuro(meur, date=None, wholeCents=True) #### meuroToEuro(meur, date=None, wholeCents=True)
Converts the given amount of meuros to euros for the given date. (wholeCents means it rounds to two decimal places) Converts the given amount of meuros to euros for the given date. (wholeCents means it rounds to two decimal places)
#### liveValue(eur, interval=10) #### liveValue(eur, interval=10)
Print the current amount of meuros for the given amunt of euros every interval-seconds. Comes in handy when you want to watch your life savings slowly fade away thanks to inflation. Print the current amount of meuros for the given amount of euros every interval-seconds. Comes in handy when you want to watch your life savings slowly fade away thanks to inflation.
##### When the date is set to None the current date is used. Otherwise a datetime-object is expected ##### When the date is set to None the current date is used. Otherwise a datetime-object is expected
### Cache
Because we dont want to annoy the ecb, we cache their data while we are running and for up to an hour on disc.
#### reload(maxCacheSeconds=3600)
Will reload the cache if it is older than maxCacheSeconds. Gets called once in the background the first time you call any other function.
## Caveats ## Caveats
The ecb only gives an estimate for the inflation of the last month and no data for the current month. The ecb only gives an estimate for the inflation of the last month and no data for the current month.
This libary just uses this estimate and assumes a yearly-inflation-rate of the current month (and all others months without data) of 2% (which is the stated goal-inflation-rate of the ecb). This libary just uses this estimate and assumes a yearly-inflation-rate of the current month (and all others months without data) of 2% (which is the stated goal-inflation-rate of the ecb).
+58
View File
@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 13.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 14948) -->
<svg
version="1.2"
id="Ebene_1"
x="0px"
y="0px"
width="194.44644"
height="194.44644"
viewBox="0 0 194.44645 194.44644"
xml:space="preserve"
sodipodi:docname="icon_meuro.svg"
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs11" /><sodipodi:namedview
id="namedview9"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:snap-object-midpoints="false"
inkscape:snap-others="true"
inkscape:snap-nodes="true"
inkscape:zoom="1.589075"
inkscape:cx="-84.325789"
inkscape:cy="19.193557"
inkscape:window-width="1251"
inkscape:window-height="991"
inkscape:window-x="26"
inkscape:window-y="23"
inkscape:window-maximized="0"
inkscape:current-layer="Ebene_1" />
<circle
style="fill:#f6d70d;fill-opacity:1;stroke:#743f15;stroke-width:10.6445;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path191"
cx="97.223221"
cy="97.223221"
r="91.90097" /><text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:165.553px;line-height:1.25;font-family:sans-serif;fill:#743f15;fill-opacity:1;stroke:none;stroke-width:4.13882"
x="44.520176"
y="126.10342"
id="text4082"><tspan
sodipodi:role="line"
id="tspan4080"
x="44.520176"
y="126.10342"
style="fill:#743f15;fill-opacity:1;stroke-width:4.13882">µ</tspan></text></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

Regular → Executable
+81 -26
View File
@@ -1,31 +1,21 @@
import requests #!/usr/bin/python3
import requests, json, os.path
from datetime import datetime from datetime import datetime
from dateutil import relativedelta from dateutil import relativedelta
from dateutil import parser as dparser
from dateutil.parser._parser import ParserError as DateParserError
from collections import defaultdict
# Ugly code thats outside of any function # Our inflation-table as a global-var
url = 'https://sdw.ecb.europa.eu/quickviewexport.do?SERIES_KEY=122.ICP.M.U2.N.000000.4.ANR&type=csv' _years = None
resp = requests.get(url)
lines = resp.text.split('\n')[6:]
years = {} # Will later contain the monthly factor of inflation (~1.0016667) for every month
for line in lines:
vals = line.split(',')
year = int(vals[0][:4])
month = datetime.strptime(vals[0][4:],'%b').month
inflation = float(vals[1])
if not year in years:
years[year] = {}
years[year][month] = 1 + (inflation/100)/12
for year in years:
months = years[year]
for month in range(1,13):
if month not in months:
years[year][month] = 1 + 0.02/12 # Lets say the ECB archives their target
# Gives you the exchange rate between euros and meuros for the given date. # Gives you the exchange rate between euros and meuros for the given date.
# (Should always be a float < 1) # (Should always be a float < 1)
# date should be either a datetime-object or None (= current date) # date should be either a datetime-object or None (= current date)
def exchangeRate(date=None): def exchangeRate(date=None):
global _years
if _years==None:
reload()
if date==None: if date==None:
date = datetime.now() date = datetime.now()
month, year = date.month, date.year month, year = date.month, date.year
@@ -34,32 +24,33 @@ def exchangeRate(date=None):
akk = 1 akk = 1
for fullYear in range(2001, year): for fullYear in range(2001, year):
yearlyInf = 1 yearlyInf = 1
for m in years[fullYear]: for m in _years[fullYear]:
monthlyInf = years[fullYear][m] monthlyInf = _years[fullYear][m]
yearlyInf *= monthlyInf yearlyInf *= monthlyInf
akk *= yearlyInf akk *= yearlyInf
for fullMonth in range(1, month): for fullMonth in range(1, month):
monthlyInf = years[year][fullMonth] monthlyInf = _years[year][fullMonth]
akk *= monthlyInf akk *= monthlyInf
beginningOfMonth = date.replace(hour=0, minute=0, second=0, microsecond=0, day=1) beginningOfMonth = date.replace(hour=0, minute=0, second=0, microsecond=0, day=1)
endOfMonth = beginningOfMonth + relativedelta.relativedelta(months=1) endOfMonth = beginningOfMonth + relativedelta.relativedelta(months=1)
fracOfMonth = (date - beginningOfMonth).total_seconds() / (endOfMonth - beginningOfMonth).total_seconds() fracOfMonth = (date - beginningOfMonth).total_seconds() / (endOfMonth - beginningOfMonth).total_seconds()
inflationThisMonth = 1 + fracOfMonth * (years[year][month]-1) inflationThisMonth = 1 + fracOfMonth * (_years[year][month]-1)
akk *= inflationThisMonth akk *= inflationThisMonth
return 1/akk return 1/akk
# Converts the given amount of euros to meuros for the given date. (wholeCents means it rounds to two decimal places) # Converts the given amount of euros to meuros for the given date. (wholeCents means it rounds to two decimal places)
# date should be either a datetime-object or None (= current date) # date should be either a datetime-object or None (= current date)
def euroToMeuro(eur,date=None,wholeCents=True): def euroToMeuro(eur,date=None,wholeCents=True):
return round(eur * factorForDate(date),[64,2][wholeCents]) return round(eur * exchangeRate(date),[64,2][wholeCents])
# Converts the given amount of meuros to euros for the given date. (wholeCents means it rounds to two decimal places) # Converts the given amount of meuros to euros for the given date. (wholeCents means it rounds to two decimal places)
# date should be either a datetime-object or None (= current date) # date should be either a datetime-object or None (= current date)
def meuroToEuro(meur,date=None,wholeCents=True): def meuroToEuro(meur,date=None,wholeCents=True):
return round(meur / factorForDate(date),[64,2][wholeCents]) return round(meur / exchangeRate(date),[64,2][wholeCents])
# Print the current amount of meuros for the given amunt of euros every <interval>-seconds. # Print the current amount of meuros for the given amunt of euros every <interval>-seconds.
# Comes in handy when you want to watch your life savings slowly fade away thanks to inflation. # Comes in handy when you want to watch your life savings slowly fade away thanks to inflation.
@@ -68,3 +59,67 @@ def liveValue(eur,interval=10):
while True: while True:
print(str(eur*exchangeRate())+'µ') print(str(eur*exchangeRate())+'µ')
time.sleep(interval) time.sleep(interval)
def _extractDate(s):
try:
return dparser.parse(s, fuzzy=True, dayfirst=True)
except DateParserError:
return datetime.now()
def reload(maxCacheSeconds=3600):
global _years
if os.path.isfile('cache.json'):
with open('cache.json', 'r') as f:
cacheUpdate, cacheYears = json.loads(f.read())
if (datetime.now() - dparser.isoparse(cacheUpdate)).total_seconds() < maxCacheSeconds:
# JSON does not allow integers as keys; so we convert them back here
cacheYears = {int(y):{int(m):float(n) for m,n in ms.items()} for y,ms in cacheYears.items()}
# For every month we dont have data for, we assume the ECB nailed their 2%-target
_years = defaultdict(lambda: {m:1 + 0.02/12 for m in range(1,13)}, cacheYears)
return
_years = _loadYearsTableWeb()
def _loadYearsTableWeb():
print('[i] Fetching new data from ECB-Servers...')
#url = 'https://sdw.ecb.europa.eu/quickviewexport.do?SERIES_KEY=122.ICP.M.U2.N.000000.4.ANR&type=csv'
#resp = requests.get(url)
url = 'https://data.ecb.europa.eu/download-api/csv/wide'
resp = requests.post(url, {'serieskeys': ['ICP.M.U2.N.000000.4.ANR']})
lines = resp.text.split('\n')[6:]
years = defaultdict(lambda: {m:1 + 0.02/12 for m in range(1,13)})
for line in lines:
vals = line.split(',')
year = int(vals[0][:4])
month = datetime.strptime(vals[0][4:],'%b').month
inflation = float(vals[1])
years[year][month] = 1 + (inflation/100)/12
with open('cache.json', 'w') as f:
f.write(json.dumps([datetime.now().isoformat(),years]))
return years
def cliInterface():
import sys, re
arg = " ".join(sys.argv[1:]).replace(',','')
reFromEur = re.compile(r'(\d+(.\d\d)?)\W?(€|e|E)')
reFromMeur = re.compile(r'(\d+(.\d\d)?)\W?(μ|m|M)')
if (m := reFromEur.search(arg))!=None:
eurS = m.groups()[0]
date = _extractDate(arg[:m.span()[0]] + '-' + arg[m.span()[1]:])
eur = float(eurS)
meur = euroToMeuro(eur, date)
print('Exchange rate for '+date.strftime("%d.%m.%Y at %H:%M")+':')
print(f'{eur:,.2f}€ = {meur:,.2f}µ')
elif (m:= reFromMeur.search(arg))!=None:
meurS = m.groups()[0]
date = _extractDate(arg[:m.span()[0]] + '-' + arg[m.span()[1]:])
meur = float(meurS)
eur = meuroToEuro(meur, date)
print('Exchange rate for '+date.strftime("%d.%m.%Y at %H:%M")+':')
print(f'{meur:,.2f}µ = {eur:,.2f}')
else:
print('[!] Unable to parse input')
if __name__=='__main__':
cliInterface()