yfinance-exporter 1.0.0__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,61 @@
1
+ Metadata-Version: 2.1
2
+ Name: yfinance-exporter
3
+ Version: 1.0.0
4
+ Summary:
5
+ Author: François Schmidts
6
+ Author-email: francois@schmidts.fr
7
+ Requires-Python: >=3.12,<4.0
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.12
10
+ Requires-Dist: prometheus-client (>=0.21.1,<0.22.0)
11
+ Requires-Dist: the-conf (>=1.0.4,<2.0.0)
12
+ Requires-Dist: yfinance (>=0.2.51,<0.3.0)
13
+ Description-Content-Type: text/markdown
14
+
15
+ [![PyPI - Version](https://img.shields.io/pypi/v/yfinance-exporter)](https://pypi.org/project/yfinance-exporter/) [![Docker Image Version](https://img.shields.io/docker/v/jaesivsm/yfinance-exporter)](https://hub.docker.com/r/jaesivsm/yfinance-exporter/tags)
16
+
17
+ # YFinance Exporter
18
+
19
+
20
+ ## Metrics served
21
+
22
+
23
+ ```json
24
+ {
25
+ "stocks": [
26
+ {"isin": "FR0000120073", "name": "AIR LIQUIDE", "ycode": "AI.PA"}
27
+ ]
28
+ }
29
+ ```
30
+
31
+ ## Running it
32
+
33
+ For the next few bits of code, we'll suppose you have a working configuration above in `~/.config/yfinance-exporter.json`.
34
+
35
+ ### ... with python:
36
+
37
+ ```shell
38
+ pip install yfinance-exporter
39
+ python -m yfinance_exporter
40
+ ```
41
+
42
+ ### ... with docker:
43
+
44
+ ```shell
45
+ docker run -v ~/.config/:/etc/yfinance-exporter/:ro -p 9100:9100 yfinance-exporter:main
46
+ ```
47
+
48
+ You'll then be able retrieve some values:
49
+
50
+ ```shell
51
+ curl localhost:9100/metrics
52
+
53
+ # HELP yfinance_exporter
54
+ # TYPE yfinance_exporter gauge
55
+ yfinance_exporter{status="loop"} 1.0
56
+ yfinance_exporter{status="loop-time"} 10.179875
57
+ yfinance_exporter{status="ok-stock"} 45.0
58
+ yfinance_exporter{status="ko-stock"} 2.0
59
+ [...]
60
+ ```
61
+
@@ -0,0 +1,4 @@
1
+ yfinance_exporter.py,sha256=HSPavKv4kyGfO9oPUNP1eSDLbJOTg1nHlDTI35SxKNA,3305
2
+ yfinance_exporter-1.0.0.dist-info/METADATA,sha256=YmLQllXUmCFf4HAnOf0cvp8ZG7bCHWyPmTTvpYpiJCI,1525
3
+ yfinance_exporter-1.0.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
4
+ yfinance_exporter-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: poetry-core 1.9.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
yfinance_exporter.py ADDED
@@ -0,0 +1,127 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import logging
4
+
5
+ from prometheus_client import Gauge, start_http_server
6
+ from datetime import datetime
7
+ import time
8
+ from the_conf import TheConf
9
+ from yfinance import Ticker
10
+
11
+ metaconf = {
12
+ "source_order": ["files"],
13
+ "config_files": [
14
+ "~/.config/yfinance-exporter.json",
15
+ "/etc/yfinance-exporter/yfinance-exporter.json",
16
+ ],
17
+ "parameters": [
18
+ {
19
+ "type": "list",
20
+ "stocks": [
21
+ {"name": {"type": str}},
22
+ {"isin": {"type": str}},
23
+ {"ycode": {"type": str}},
24
+ ],
25
+ },
26
+ {
27
+ "loop": [
28
+ {"interval": {"type": int, "default": 240}},
29
+ ]
30
+ },
31
+ {
32
+ "prometheus": [
33
+ {"port": {"type": int, "default": 9100}},
34
+ {"namespace": {"type": str, "default": ""}},
35
+ ]
36
+ },
37
+ {"logging": [{"level": {"default": "WARNING"}}]},
38
+ ],
39
+ }
40
+ conf = TheConf(metaconf)
41
+ logger = logging.getLogger("yfinance-exporter")
42
+ try:
43
+ logger.setLevel(getattr(logging, conf.logging.level))
44
+ logger.addHandler(logging.StreamHandler())
45
+ except AttributeError as error:
46
+ raise AttributeError(
47
+ f"{conf.logging.level} isn't accepted, only DEBUG, INFO, WARNING, "
48
+ "ERROR and FATAL are accepted"
49
+ ) from error
50
+
51
+ YFINANCE_EXPORTER = Gauge(
52
+ "yfinance_exporter",
53
+ "",
54
+ ["status"],
55
+ namespace=conf.prometheus.namespace,
56
+ )
57
+ STOCK = Gauge(
58
+ "financial_positions",
59
+ "",
60
+ [
61
+ "bank",
62
+ "account_type",
63
+ "account_name",
64
+ "account_id",
65
+ "line_name",
66
+ "line_id",
67
+ "value_type", # par-value, shares-value, gain, gain-percent, quantity
68
+ ],
69
+ namespace=conf.prometheus.namespace,
70
+ )
71
+
72
+
73
+ def collect(stock):
74
+ logger.debug("Collecting for %r", stock.name)
75
+ labels = [
76
+ stock.ycode.split(".")[1] if "." in stock.ycode else "",
77
+ "stocks",
78
+ "market",
79
+ "market",
80
+ stock.name,
81
+ stock.isin,
82
+ "par-value",
83
+ ]
84
+ ticker = Ticker(stock.ycode)
85
+ try:
86
+ value = ticker.fast_info["last_price"]
87
+ except KeyError:
88
+ value = None
89
+ if not isinstance(value, (int, float)):
90
+ try:
91
+ STOCK.remove(*labels)
92
+ except KeyError:
93
+ pass
94
+ return False
95
+ STOCK.labels(*labels).set(value)
96
+ return True
97
+
98
+
99
+ def main():
100
+ YFINANCE_EXPORTER.labels("loop-count").set(0)
101
+ while True:
102
+ start = datetime.now()
103
+
104
+ results = {"ok-stock": 0, "ko-stock": 0}
105
+ for stock in conf.stocks:
106
+ if collect(stock):
107
+ results["ok-stock"] += 1
108
+ else:
109
+ results["ko-stock"] += 1
110
+
111
+ exec_interval = (datetime.now() - start).total_seconds()
112
+ YFINANCE_EXPORTER.labels("loop-duration-second").set(exec_interval)
113
+ YFINANCE_EXPORTER.labels("loop-count").inc()
114
+ for result, count in results.items():
115
+ YFINANCE_EXPORTER.labels(result).set(count)
116
+
117
+ interval = conf.loop.interval - exec_interval
118
+ if interval > 0:
119
+ time.sleep(interval)
120
+
121
+
122
+ if __name__ == "__main__":
123
+ logger.info(
124
+ "Starting yfinance exporter with %d stocks to watch", len(conf.stocks)
125
+ )
126
+ start_http_server(conf.prometheus.port)
127
+ main()