Repository navigation
Expand file tree
/
Copy pathforex_print.py
More file actions
89 lines (72 loc) · 3.22 KB
/
Copy pathforex_print.py
File metadata and controls
89 lines (72 loc) · 3.22 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
#!/usr/bin/env python3
"""Print currency exchange rates on the S01 thermal printer.
Shows live rates (ECB data via frankfurter.app) from a base currency to the
majors, plus a 30-day trend chart for one pair.
Examples:
python forex_print.py # USD base
python forex_print.py --base EUR
python forex_print.py --base GBP --chart JPY
python forex_print.py --no-print
"""
from __future__ import annotations
import argparse
from datetime import date, timedelta
from pathlib import Path
from PIL import Image, ImageDraw
from print_common import ROOT, Card, font, get_json
API = "https://api.frankfurter.app"
MAJORS = ["EUR", "GBP", "JPY", "CHF", "CAD", "AUD", "CNY", "USD"]
def fmt(r: float) -> str:
return f"{r:.2f}" if r >= 10 else f"{r:.4f}"
def draw_chart(series: list[float], w: int, h: int = 130) -> Image.Image:
img = Image.new("L", (w, h), 255)
d = ImageDraw.Draw(img)
lo, hi = min(series), max(series)
span = (hi - lo) or 1
n = len(series)
pad = 4
pts = [(pad + i / (n - 1) * (w - 2 * pad), pad + (1 - (v - lo) / span) * (h - 2 * pad))
for i, v in enumerate(series)]
d.line(pts, fill=0, width=2, joint="curve")
y0 = pad + (1 - (series[0] - lo) / span) * (h - 2 * pad)
for x in range(0, w, 6):
d.point((x, y0), fill=0)
lx, ly = pts[-1]
d.ellipse((lx - 3, ly - 3, lx + 3, ly + 3), fill=0)
tiny = font(False, 11)
d.text((pad, pad), fmt(hi), fill=0, font=tiny)
d.text((pad, h - 16), fmt(lo), fill=0, font=tiny)
return img
def main() -> int:
parser = argparse.ArgumentParser(description="Print currency exchange rates on the S01 thermal printer.")
parser.add_argument("--base", default="USD", help="Base currency (default: USD).")
parser.add_argument("--chart", default=None, help="Currency to chart vs base (default: EUR, or USD).")
parser.add_argument("--out", type=Path, default=ROOT / "forex.png")
parser.add_argument("--darkness", type=int, choices=range(1, 6), default=3)
parser.add_argument("--bottom-feed", type=int, default=24)
parser.add_argument("--no-print", action="store_true")
args = parser.parse_args()
base = args.base.upper()
targets = [c for c in MAJORS if c != base]
print(f"Fetching rates for {base} ...")
latest = get_json(f"{API}/latest?from={base}&to={','.join(targets)}")
rates = latest["rates"]
chart_to = (args.chart or ("EUR" if base != "EUR" else "USD")).upper()
end = date.fromisoformat(latest["date"])
start = end - timedelta(days=30)
ts = get_json(f"{API}/{start}..{end}?from={base}&to={chart_to}")
series = [v[chart_to] for _, v in sorted(ts["rates"].items())]
card = Card()
card.title("EXCHANGE RATES")
card.heading(f"1 {base} buys", size=15)
card.gap(2).divider()
for cur in targets:
if cur in rates:
card.kv(cur, fmt(rates[cur]))
if series:
card.gap(4).line(f"{base}/{chart_to} - 30 days", size=12, bold=True, center=True)
card.gap(2).image(draw_chart(series, card.inner_w), border=True)
card.footer("frankfurter.app (ECB)", latest["date"])
return card.finish(args.out, args.bottom_feed, args.darkness, do_print=not args.no_print)
if __name__ == "__main__":
raise SystemExit(main())