Dnes bych se s Vámi rád podělil o to, jak jsem začal sledovat vývoj cen akcií pomocí Zabbixu. Možná na takovou akci existují lepší nástroje, ale když už tu mám můj oblíbený Zabbix, tak proč toho nevyužít.
Tuto věc jsem měl v TO-DO už poměrně dlouho, ale dostal jsem se k tomu až nyní. V první řadě šlo o to, jakým způsobem se budou požadovaná data získávat. K dispozici je více možností, ne však všude a pro vše. Problém může být, že daná burza neposkytuje ať už REST API nebo data v nějakém validním real-time formátu apod. V neposlední řadě je často služba poskytování dat spojená s poplatky. Cena zdarma je vždy vítána, zvlášť pokud člověk chce nejprve něco otestovat.
Jako první možnost jsem zvolil čtení aktuální ceny akcií z webu brokera. Vybral jsem Patria a české akcie. Těch není mnoho a pro daný způsob to tedy není tak náročné řešení.
Vybral jsem si hlavní stránku jedné z akcií a z jejího kódu jsem zjistil, kde se nachází aktuální cena akcie, kterou potřebuji získat.
Vytvořil jsem tedy v Zabbixu hosta a k němu jeden item pro získávání dat z webu pomocí HTTP agenta. Název itemu je název akcie, jako klíč jsem použil ticker a pak jsem přidal url hlavní stránky akcie.
Následně bylo potřeba ještě nastavit preprocessing a nejjednodušší způsob se zdál být použít regulární výraz pro získání většího řetězce, ve kterém se nachází požadovaná hodnota. Po něm dojde k očištění tak, aby zůstala samotná cena a nakonec změna desetinné čárky na tečku kvůli formátu čísla.
Závěrečné rozkopírování pro další akcie z BCPP bylo vcelku rychlé a jednoduché.
Po spuštění monitoringu se dle očekávání data začala sbírat a vypadalo to, že je tato část hotová. Bohužel to bylo pouze zdání, výsledek nebyl úplně pěkný viz. následující obrázek.
Data máme, vše se zdá funguje, ale přeci jen by to chtělo mít graf celistvý bez chybějících dat. Otázka zní, proč tomu tak je.
Ověřil jsem funkčnost monitoringu a dostupnost dat, vše v pořádku. Bylo potřeba počkat na dobu, kdy data nejsou dostupná. Zabbix v danou dobu vykazoval následující chybu:
cannot perform regular expression … match for value of type „none“: pattern does not match.
Podíval jsem se tedy na stránku jedné z akcií a zjistil jsem, že došlo ke změně v kódu a string obsahující cenu se změnil.
Bylo potřeba upravit preprocessing s ohledem na nově zjištěnou skutečnost.
Řešit to úpravou regulárního výrazu a pod. už nebylo vhodné, tak jsem přistoupil na změnu preprocessingu pomocí jediného JavaScriptu, výsledek viz. následující obrázek.
Jako druhou možnost jsem zvolil REST API, což v případě akcií ve světě není takový problém. Vybral jsem si službu Finnhub Stock API zabývající se real-time zprostředkováním dat akcií, měn a kryptoměn. Vše mají dobře zdokumentované a nabízí i variantu zdarma. Ta se zdá plně dostačující pro moje použití. Stačí si vytvořit účet a uživatel dostane klíč/token pro používání REST API. Jediné omezení se týká počtu dotazů, které je limitované na 60 volání REST API za minutu.
To je dost i v případě dotazování se na každou akcii zvlášť, ale to v plánu nemám.
Vytvořil jsem druhého hosta a místo tvorby itemu jako v předchozím případě, jsem šel klasickou cestou tvorby template. Template obsahuje pouze jeden item, jeden discovery item a dvě makra. Item je pro získání hodnot a discovery je pro tvorbu itemu, jeden item na akcii. Item je typu external check, tudíž je použit skript, který zajistí komunikaci a data. Pro discovery a tvorbu itemu jsem opět jako klíč využil ticker a jako název itemu název akcie předaný přes discovery makro.
Co se týká skriptu, použil jsem Python.
Samotný skript je vložen v adresáři externalscripts a je spouštěn s makrem určujícím akcie, které chci sledovat. Makro je jednoduše ve formátu ticker01,ticker02,ticker03 atd. Je potřeba dát si pozor na logiku klíče itemu, protože svádí k tomu, že jednotlivé tickery z makra se použijí jako jednotlivé parametry (script[key,<parameter1>,<parameter2>,…]), ale není tomu tak. Je to jeden jediný parametr. Tomu musí být uzpůsobeno zpracování ve skriptu.
Jako výstup skriptu jsem použil JSON formát.
Test samotného skriptu proběhl v pořádku, nasazení a spuštění monitoringu také. V makru jsem měl pouze několik málo akcií pro samotný test.
Během chvíle se vytvořily itemy a opět vše vypadalo skvěle.
Problém nastal, jakmile jsem chtěl monitorovat více než pět akcií. V tom případě to vždy skončilo na timeout. Prohlédl jsem si znovu uvedená omezení a dokumentaci REST API, ale nenašel jsem nic, co by to vysvětlovalo. Zkusil jsem nastavit větší čas na Timeout parametru Zabbixu i skriptu, ale bohužel bez úspěchu.
Napadlo mě tedy rozdělit dotazování po balíčcích pěti akcií s drobným časovým odstupem. Upravil jsem skript a otestoval. Opět bez úspěchu. Následně jsem už musel trochu víc laborovat vzhledem k tomu, že nejsem kovaný programátor, ale řešení jsem našel. Použil jsem asynchronní a paralelní funkce Pythonu pro potřebné dotazování a následně jsem výstupní data složil do jednoho JSON souboru. Toto řešení už bylo funkční a začal jsem dostávat informace o tolika akciích, kolik jsem chtěl viz. následující obrázek.
Řešení se zdá být dobře funkční, mohu tak sledovat vývoj cen akcií v jednom grafu, nastavit si hlídání různých limitů atd.
Zde přikládám jednotlivé příkazy a nastavení
JavaScript pro preprocessing sběru dat z webu
function (value) { // Define the regex pattern to match both variations of the <td> element with the number const regex = /<td\s+(?:nowrap="nowrap"\s+class="EquityHeaderCellStrong"|class="EquityHeaderCellStrong"\s+nowrap="nowrap")\s*>([0-9]+\s*[0-9]*,[0-9]+)<\/td>/; // Attempt to find the first match in the input value const match = value.match(regex); // If a match is found, process and return the number in the desired format if (match) { // Replace commas with dots for the decimal point and remove spaces return match[1].replace(',', '.').replace(/\s/g, ''); } else { // Return some default value or indication of no match return "No match found"; // Adjust based on your needs } }
Skript pro získávání dat fetch_stock_price.py
#!/usr/bin/env python3 import asyncio import aiohttp import sys import json async def fetch_stock_data(session, url): async with session.get(url) as response: return await response.json() async def main(api_key, stocks): base_url = 'https://finnhub.io/api/v1/quote' async with aiohttp.ClientSession() as session: tasks = [] for stock in stocks[:5]: # Limit to 5 stocks per run full_url = f"{base_url}?symbol={stock}&token={api_key}" tasks.append(fetch_stock_data(session, full_url)) responses = await asyncio.gather(*tasks) stock_data = [{"Stock_name": stocks[i], "Stock_value": response.get('c')} for i, response in enumerate(responses)] print(json.dumps(stock_data, indent=4)) if __name__ == "__main__": if len(sys.argv) < 3: print("Usage: python script.py <API_KEY> 'STOCK_SYMBOL1,STOCK_SYMBOL2,...'") sys.exit(1) api_key = sys.argv[1] stocks = sys.argv[2].split(',') asyncio.run(main(api_key, stocks))
Instalace potřebných balíčků
dnf install python3 pip3 install requests pip3 install aiohttp
Ukázka JSON výstupu
[ { "Stock_name": "IBM", "Stock_value": 179.7 }, { "Stock_name": "INTC", "Stock_value": 43.47 }, { "Stock_name": "CVX", "Stock_value": 155.44 }, { "Stock_name": "AAPL", "Stock_value": 182.32 }, { "Stock_name": "MSFT", "Stock_value": 402.18 } ]
IT architect | Monitoring expert | DB admin | Konzultant