Уведомления об изменении цен на бирже Bybit в приложении Flex на Python

GetCoder.ru
Изображение статьи

Создание пользовательского интерфейса для отображения детальной информация по торговым парам с биржи Bybit в приложении Flet Python.

В продолжении статьи Приложение на Flet Python для отображения актуальных цен с биржи Bybit мы добавили возможность получать системные уведомления об изменении цены. Теперь при нажатии на торговую пару открывается сцена с информацией по каждой торговой паре и возможностью включить получение уведомлений.
  • Процент изменнеия цены (float)
  • Интервал проверки цены (в секундах)
  • Выкл. / Вкл. получение уведомлений

Для удобства можно добавить храниение состояния торговой пары в БД с помошью SQLite. Подробнее можно ознакомиться в статье Работа с базой данных SQLite в Python: примеры использования

Или можно хранить даные о состоянии в файле. Подробнее можно ознакомиться в статье Рекурсивное создание директорий и сохранение данных в файл на Python

Установка

Прежде чем мы начнем, убедитесь, что у вас установлены необходимые библиотеки:

  • copy
pip install websocket-client requests flet pip plyer

Код

  • copy
# -*- coding: utf-8 -*- import flet as ft import time, websocket, json, threading, requests, re from functools import partial import plyer sybolsList = {} class bybit(): def __init__(self): try: self.mTickers = {} self.ticker_socket = {} self.start() except Exception as e: print(e) # Данные от сокета def on_message(self, _wsa, wsData, prm): varData = json.loads(wsData) if 'op' in varData: if varData['op'] == 'subscribe' and varData['success'] == False: mPairs = re.findall(r'\[([^]]*)\]', varData['ret_msg']) for elem in mPairs: del(self.ticker_socket[prm['key']]['listPair'][elem]) if len(self.ticker_socket[prm['key']]['listPair']) == 0: self.ticker_socket[prm['key']]['exit'] = 'true' self.ticker_socket[prm['key']]['status'] = 'off' _wsa.close() else: self.mTickers[varData['data']['symbol']] = varData['data'] # Отлов ошибок/выключения сокета def on_close(self, _wsa, wsData, prm): _wsa.close() self.ticker_socket[prm['key']]['status'] = 'off' # Открытие соединения def on_open(self, _wsa, prm): try: args = list(self.ticker_socket[prm['key']]['listPair'].values()) _wsa.send(json.dumps({"op": "subscribe", "args": args})) except Exception as e: _wsa.close() # Запуск сокета def runWebSockPl(self, key): while True: try: wss = 'wss://stream.bybit.com/v5/public/spot' wsa = websocket.WebSocketApp(wss, on_open = partial(self.on_open, prm={'key': key}), on_message = partial(self.on_message, prm={'key': key}), on_error = partial(self.on_close, prm={'key': key}), on_close = partial(self.on_close, prm={'key': key})) self.ticker_socket[key]['connect'] = wsa self.ticker_socket[key]['status'] = 'on' print(u'Установлено соединение Web Socket ключ '+key) wsa.run_forever() if self.ticker_socket[key]['exit'] == 'true': break raise Exception('Exit') except Exception as e: print(u'Разрыв соединения Web Socket ключ '+key) self.ticker_socket[key]['status'] = 'off' time.sleep(10) def start(self): response = requests.get("https://api.bybit.com/v5/market/instruments-info?category=spot") data = response.json() i = 1 listPair = {} for elem in data['result']['list']: listPair['tickers.'+elem['symbol']] = 'tickers.'+elem['symbol'] if len(listPair) >= 10: key = str(i) self.ticker_socket[key] = {'connect': '', 'status': 'off', 'exit': 'false', 'listPair': listPair} listPair = {} ws = threading.Thread(target=self.runWebSockPl, kwargs={'key': key}, daemon=True) ws.start() i = i + 1 if len(listPair) > 0: key = str(i) self.ticker_socket[key] = {'connect': '', 'status': 'off', 'exit': 'false', 'listPair': listPair} ws = threading.Thread(target=self.runWebSockPl, kwargs={'key': key}, daemon=True) ws.start() def main(page: ft.Page): # Обновление торговых пар def update_data_pair(): while True: time.sleep(1) for keyElem, dataElem in sybolsList.items(): sybolsList[keyElem]['container'].bgcolor = None det_title_con.bgcolor = None for keyElem, dataElem in bb.mTickers.items(): if keyElem in sybolsList: if mPage['detailed']['container'].visible == True: if det_title.value == keyElem: det_lastPrice.value = dataElem['lastPrice'] det_highPrice24h.value = dataElem['highPrice24h'] det_lowPrice24h.value = dataElem['lowPrice24h'] det_prevPrice24h.value = dataElem['prevPrice24h'] det_volume24h.value = dataElem['volume24h'] det_turnover24h.value = dataElem['turnover24h'] if float(dataElem['lastPrice']) > float(sybolsList[keyElem]['lastPrice']): det_title_con.bgcolor = ft.colors.GREEN_700 if float(dataElem['lastPrice']) < float(sybolsList[keyElem]['lastPrice']): det_title_con.bgcolor = ft.colors.RED sybolsList[keyElem]['text'].value = keyElem+' '+str(dataElem['lastPrice']) if float(dataElem['lastPrice']) > float(sybolsList[keyElem]['lastPrice']): sybolsList[keyElem]['container'].bgcolor = ft.colors.GREEN_700 if float(dataElem['lastPrice']) < float(sybolsList[keyElem]['lastPrice']): sybolsList[keyElem]['container'].bgcolor = ft.colors.RED sybolsList[keyElem]['lastPrice'] = float(dataElem['lastPrice']) if sybolsList[keyElem]['alarm']['status'] == True and int(time.time()) >= sybolsList[keyElem]['alarm']['nextCheck']: sybolsList[keyElem]['alarm']['nextCheck'] = int(time.time()) + sybolsList[det_title.value]['alarm']['interval'] percentage_change = ((float(sybolsList[keyElem]['lastPrice']) - float(sybolsList[keyElem]['alarm']['price'])) / float(sybolsList[keyElem]['alarm']['price'])) * 100 if percentage_change >= sybolsList[keyElem]['alarm']['percent']: position = 'увеличилась' if float(sybolsList[keyElem]['lastPrice']) > float(sybolsList[keyElem]['alarm']['price']) else 'уменьшилась' text_msg = 'Цена '+position+' на '+str(sybolsList[keyElem]['alarm']['percent'])+'%' plyer.notification.notify(message = text_msg, app_name='Bybit - торговые пары', title='Торговая пара '+str(keyElem)) sybolsList[keyElem]['alarm']['price'] = sybolsList[keyElem]['lastPrice'] page.update() # Нижнее меню def bottom_menu(e): if e.control.selected_index == 0: mPage['main']['container'].visible = True mPage['detailed']['container'].visible = False formSearch.visible = True page.update() # Детальная информация def detailed_open(symbol): if symbol in bb.mTickers: det_title.value = symbol det_lastPrice.value = bb.mTickers[symbol]['lastPrice'] det_highPrice24h.value = bb.mTickers[symbol]['highPrice24h'] det_lowPrice24h.value = bb.mTickers[symbol]['lowPrice24h'] det_prevPrice24h.value = bb.mTickers[symbol]['prevPrice24h'] det_volume24h.value = bb.mTickers[symbol]['volume24h'] det_turnover24h.value = bb.mTickers[symbol]['turnover24h'] tf_percent.value = sybolsList[det_title.value]['alarm']['percent'] tf_interval.value = sybolsList[det_title.value]['alarm']['interval'] sw_start_alarm.value = sybolsList[det_title.value]['alarm']['status'] mPage['detailed']['container'].visible = True mPage['main']['container'].visible = False formSearch.visible = False page.update() # Филтрация списка def filter_proccess(e): serchVar = search.value.upper() for keyElem, dataElem in sybolsList.items(): sybolsList[keyElem]['container'].visible = False if serchVar in keyElem: sybolsList[keyElem]['container'].visible = True page.update() # Отмена фильтрации def clear_filter(e): search.value = '' for keyElem, dataElem in sybolsList.items(): sybolsList[keyElem]['container'].visible = True page.update() # Обновление конфига уведомления def proccess_upd_alarm(e): sybolsList[det_title.value]['alarm']['percent'] = float(tf_percent.value) sybolsList[det_title.value]['alarm']['interval'] = int(tf_interval.value) sybolsList[det_title.value]['alarm']['status'] = sw_start_alarm.value sybolsList[det_title.value]['alarm']['nextCheck'] = int(time.time()) sybolsList[det_title.value]['alarm']['price'] = sybolsList[det_title.value]['lastPrice'] # Генерация главной страницы def main_page(): response = requests.get("https://api.bybit.com/v5/market/instruments-info?category=spot") data = response.json() lastPrice = 0.00000000 rv = ft.ResponsiveRow([]) for elem in data['result']['list']: var_text = ft.Text(elem['symbol']+' '+str(lastPrice)) var_elem = ft.Container( ft.CupertinoButton( content=var_text, opacity_on_click=0.3, on_click=lambda e, symbol=elem['symbol']: detailed_open(symbol), color="black" ), padding=5, col={"sm": 6, "md": 4, "xl": 2}, border = ft.border.all(1, ft.colors.PRIMARY) ) sybolsList[elem['symbol']] = {'container': var_elem, 'text': var_text, 'lastPrice': lastPrice, 'alarm': {'price': 0, 'percent': 0, 'interval': 0, 'status': False, 'nextCheck': False}} rv.controls.append(var_elem) mPage['main']['container'].controls.append(rv) page.update() # Деальная информация det_title = ft.Text("--/--", size = 50, weight = ft.FontWeight.BOLD) det_title_con = ft.Container(det_title,padding = 10, alignment=ft.alignment.center, border = ft.border.all(1, ft.colors.PRIMARY)) det_lastPrice = ft.Text("0.00000000", size = 20, weight = ft.FontWeight.BOLD) det_highPrice24h = ft.Text("0.00000000", size = 20, weight = ft.FontWeight.BOLD) det_lowPrice24h = ft.Text("0.00000000", size = 20, weight = ft.FontWeight.BOLD) det_prevPrice24h = ft.Text("0.00000000", size = 20, weight = ft.FontWeight.BOLD) det_volume24h = ft.Text("0.00000000", size = 20, weight = ft.FontWeight.BOLD) det_turnover24h = ft.Text("0.00000000", size = 20, weight = ft.FontWeight.BOLD) # Форма детальная информация con_det = ft.Container( ft.Column([ ft.ResponsiveRow([ft.Text("Последняя цена: ", size = 20), det_lastPrice]), ft.ResponsiveRow([ft.Text("Самая высокая цена за 24 часа: ", size = 20), det_highPrice24h]), ft.ResponsiveRow([ft.Text("Самая низкая цена за 24 часа: ", size = 20), det_lowPrice24h]), ft.ResponsiveRow([ft.Text("Процентное изменение рыночной цены: ", size = 20), det_prevPrice24h]), ft.ResponsiveRow([ft.Text("Объем за 24 часа: ", size = 20), det_volume24h]), ft.ResponsiveRow([ft.Text("Оборот за 24 часа: ", size = 20), det_turnover24h]) ]), col={"sm": 6, "md": 6, "xl": 4}, padding = 10, alignment=ft.alignment.center, border = ft.border.all(1, ft.colors.PRIMARY) ) # Форма включение уведомлений tf_percent = ft.TextField(label="Процент изменения цены", value="0.5", border_color = ft.colors.PRIMARY) tf_interval = ft.TextField(label="Интервал проверки цены (в секундах)", value="5", border_color = ft.colors.PRIMARY) sw_start_alarm = ft.Switch(label="Выкл. / Вкл.") btn_upd_alarm = ft.CupertinoFilledButton( content=ft.Text("Применить"), opacity_on_click = 0.3, on_click = proccess_upd_alarm, ) con_alarm = ft.Container( ft.Column([ tf_percent, tf_interval, sw_start_alarm, btn_upd_alarm ]), col={"sm": 6, "md": 6, "xl": 4}, padding = 10, alignment=ft.alignment.center, border = ft.border.all(1, ft.colors.PRIMARY) ) rvd = ft.ResponsiveRow([con_det, con_alarm]) mPage = { 'main': {'container': ft.ListView(expand = 1, spacing = 10, padding = 10)}, 'detailed': {'container': ft.ListView(expand = 1, spacing = 10, padding = 10)} } mPage['detailed']['container'].controls.append(det_title_con) mPage['detailed']['container'].controls.append(rvd) mPage['main']['container'].visible = True mPage['detailed']['container'].visible = False # Фильтр search = ft.TextField(label="Поиск", on_change=filter_proccess) searchContainer = ft.Container(search ,padding=10) btnFilter = ft.IconButton(ft.icons.FILTER_ALT_OFF, icon_size=30,tooltip = "Отмена фильтра", on_click = clear_filter) formSearch = ft.Row([searchContainer, btnFilter]) # Верхнее меню page.appbar = ft.AppBar( title=ft.Text("Bybit - торговые пары"), center_title=False, bgcolor=ft.colors.SURFACE_VARIANT, actions=[formSearch], ) # Нижнее меню page.navigation_bar = ft.CupertinoNavigationBar( bgcolor=ft.colors.SURFACE_VARIANT, on_change=lambda e: bottom_menu(e), destinations=[ft.NavigationBarDestination(icon=ft.icons.HOME_ROUNDED, label="Главная")] ) page.add(mPage['main']['container'], mPage['detailed']['container']) # Генерация главной страницы ws = threading.Thread(target = main_page, daemon=True) ws.start() # Запуск ws bb = bybit() # Запуск обновления торговых пар ws = threading.Thread(target = update_data_pair, daemon=True) ws.start() ft.app(target=main)
Исходный код

Заключение

В этом примере мы продемонстрировали, как можно реализовать получение системных уведомлений от приложения. Надеюсь, эта статья была полезной для вас.

  • 12.10.2024
  • 39
  • 0

Уведомления об изменении цен на бирже Bybit в приложении Flex на Python

В продолжении статьи Приложение на Flet Python для отображения актуальных цен с биржи Bybit мы добавили возможность получать системные уведомления об изменении цены. Теперь при нажатии на торговую пару открывается сцена с информацией по каждой торговой паре и возможностью включить получение уведомлений.
  • Процент изменнеия цены (float)
  • Интервал проверки цены (в секундах)
  • Выкл. / Вкл. получение уведомлений

Для удобства можно добавить храниение состояния торговой пары в БД с помошью SQLite. Подробнее можно ознакомиться в статье Работа с базой данных SQLite в Python: примеры использования

Или можно хранить даные о состоянии в файле. Подробнее можно ознакомиться в статье Рекурсивное создание директорий и сохранение данных в файл на Python

Установка

Прежде чем мы начнем, убедитесь, что у вас установлены необходимые библиотеки:

  • copy
pip install websocket-client requests flet pip plyer

Код

  • copy
# -*- coding: utf-8 -*- import flet as ft import time, websocket, json, threading, requests, re from functools import partial import plyer sybolsList = {} class bybit(): def __init__(self): try: self.mTickers = {} self.ticker_socket = {} self.start() except Exception as e: print(e) # Данные от сокета def on_message(self, _wsa, wsData, prm): varData = json.loads(wsData) if 'op' in varData: if varData['op'] == 'subscribe' and varData['success'] == False: mPairs = re.findall(r'\[([^]]*)\]', varData['ret_msg']) for elem in mPairs: del(self.ticker_socket[prm['key']]['listPair'][elem]) if len(self.ticker_socket[prm['key']]['listPair']) == 0: self.ticker_socket[prm['key']]['exit'] = 'true' self.ticker_socket[prm['key']]['status'] = 'off' _wsa.close() else: self.mTickers[varData['data']['symbol']] = varData['data'] # Отлов ошибок/выключения сокета def on_close(self, _wsa, wsData, prm): _wsa.close() self.ticker_socket[prm['key']]['status'] = 'off' # Открытие соединения def on_open(self, _wsa, prm): try: args = list(self.ticker_socket[prm['key']]['listPair'].values()) _wsa.send(json.dumps({"op": "subscribe", "args": args})) except Exception as e: _wsa.close() # Запуск сокета def runWebSockPl(self, key): while True: try: wss = 'wss://stream.bybit.com/v5/public/spot' wsa = websocket.WebSocketApp(wss, on_open = partial(self.on_open, prm={'key': key}), on_message = partial(self.on_message, prm={'key': key}), on_error = partial(self.on_close, prm={'key': key}), on_close = partial(self.on_close, prm={'key': key})) self.ticker_socket[key]['connect'] = wsa self.ticker_socket[key]['status'] = 'on' print(u'Установлено соединение Web Socket ключ '+key) wsa.run_forever() if self.ticker_socket[key]['exit'] == 'true': break raise Exception('Exit') except Exception as e: print(u'Разрыв соединения Web Socket ключ '+key) self.ticker_socket[key]['status'] = 'off' time.sleep(10) def start(self): response = requests.get("https://api.bybit.com/v5/market/instruments-info?category=spot") data = response.json() i = 1 listPair = {} for elem in data['result']['list']: listPair['tickers.'+elem['symbol']] = 'tickers.'+elem['symbol'] if len(listPair) >= 10: key = str(i) self.ticker_socket[key] = {'connect': '', 'status': 'off', 'exit': 'false', 'listPair': listPair} listPair = {} ws = threading.Thread(target=self.runWebSockPl, kwargs={'key': key}, daemon=True) ws.start() i = i + 1 if len(listPair) > 0: key = str(i) self.ticker_socket[key] = {'connect': '', 'status': 'off', 'exit': 'false', 'listPair': listPair} ws = threading.Thread(target=self.runWebSockPl, kwargs={'key': key}, daemon=True) ws.start() def main(page: ft.Page): # Обновление торговых пар def update_data_pair(): while True: time.sleep(1) for keyElem, dataElem in sybolsList.items(): sybolsList[keyElem]['container'].bgcolor = None det_title_con.bgcolor = None for keyElem, dataElem in bb.mTickers.items(): if keyElem in sybolsList: if mPage['detailed']['container'].visible == True: if det_title.value == keyElem: det_lastPrice.value = dataElem['lastPrice'] det_highPrice24h.value = dataElem['highPrice24h'] det_lowPrice24h.value = dataElem['lowPrice24h'] det_prevPrice24h.value = dataElem['prevPrice24h'] det_volume24h.value = dataElem['volume24h'] det_turnover24h.value = dataElem['turnover24h'] if float(dataElem['lastPrice']) > float(sybolsList[keyElem]['lastPrice']): det_title_con.bgcolor = ft.colors.GREEN_700 if float(dataElem['lastPrice']) < float(sybolsList[keyElem]['lastPrice']): det_title_con.bgcolor = ft.colors.RED sybolsList[keyElem]['text'].value = keyElem+' '+str(dataElem['lastPrice']) if float(dataElem['lastPrice']) > float(sybolsList[keyElem]['lastPrice']): sybolsList[keyElem]['container'].bgcolor = ft.colors.GREEN_700 if float(dataElem['lastPrice']) < float(sybolsList[keyElem]['lastPrice']): sybolsList[keyElem]['container'].bgcolor = ft.colors.RED sybolsList[keyElem]['lastPrice'] = float(dataElem['lastPrice']) if sybolsList[keyElem]['alarm']['status'] == True and int(time.time()) >= sybolsList[keyElem]['alarm']['nextCheck']: sybolsList[keyElem]['alarm']['nextCheck'] = int(time.time()) + sybolsList[det_title.value]['alarm']['interval'] percentage_change = ((float(sybolsList[keyElem]['lastPrice']) - float(sybolsList[keyElem]['alarm']['price'])) / float(sybolsList[keyElem]['alarm']['price'])) * 100 if percentage_change >= sybolsList[keyElem]['alarm']['percent']: position = 'увеличилась' if float(sybolsList[keyElem]['lastPrice']) > float(sybolsList[keyElem]['alarm']['price']) else 'уменьшилась' text_msg = 'Цена '+position+' на '+str(sybolsList[keyElem]['alarm']['percent'])+'%' plyer.notification.notify(message = text_msg, app_name='Bybit - торговые пары', title='Торговая пара '+str(keyElem)) sybolsList[keyElem]['alarm']['price'] = sybolsList[keyElem]['lastPrice'] page.update() # Нижнее меню def bottom_menu(e): if e.control.selected_index == 0: mPage['main']['container'].visible = True mPage['detailed']['container'].visible = False formSearch.visible = True page.update() # Детальная информация def detailed_open(symbol): if symbol in bb.mTickers: det_title.value = symbol det_lastPrice.value = bb.mTickers[symbol]['lastPrice'] det_highPrice24h.value = bb.mTickers[symbol]['highPrice24h'] det_lowPrice24h.value = bb.mTickers[symbol]['lowPrice24h'] det_prevPrice24h.value = bb.mTickers[symbol]['prevPrice24h'] det_volume24h.value = bb.mTickers[symbol]['volume24h'] det_turnover24h.value = bb.mTickers[symbol]['turnover24h'] tf_percent.value = sybolsList[det_title.value]['alarm']['percent'] tf_interval.value = sybolsList[det_title.value]['alarm']['interval'] sw_start_alarm.value = sybolsList[det_title.value]['alarm']['status'] mPage['detailed']['container'].visible = True mPage['main']['container'].visible = False formSearch.visible = False page.update() # Филтрация списка def filter_proccess(e): serchVar = search.value.upper() for keyElem, dataElem in sybolsList.items(): sybolsList[keyElem]['container'].visible = False if serchVar in keyElem: sybolsList[keyElem]['container'].visible = True page.update() # Отмена фильтрации def clear_filter(e): search.value = '' for keyElem, dataElem in sybolsList.items(): sybolsList[keyElem]['container'].visible = True page.update() # Обновление конфига уведомления def proccess_upd_alarm(e): sybolsList[det_title.value]['alarm']['percent'] = float(tf_percent.value) sybolsList[det_title.value]['alarm']['interval'] = int(tf_interval.value) sybolsList[det_title.value]['alarm']['status'] = sw_start_alarm.value sybolsList[det_title.value]['alarm']['nextCheck'] = int(time.time()) sybolsList[det_title.value]['alarm']['price'] = sybolsList[det_title.value]['lastPrice'] # Генерация главной страницы def main_page(): response = requests.get("https://api.bybit.com/v5/market/instruments-info?category=spot") data = response.json() lastPrice = 0.00000000 rv = ft.ResponsiveRow([]) for elem in data['result']['list']: var_text = ft.Text(elem['symbol']+' '+str(lastPrice)) var_elem = ft.Container( ft.CupertinoButton( content=var_text, opacity_on_click=0.3, on_click=lambda e, symbol=elem['symbol']: detailed_open(symbol), color="black" ), padding=5, col={"sm": 6, "md": 4, "xl": 2}, border = ft.border.all(1, ft.colors.PRIMARY) ) sybolsList[elem['symbol']] = {'container': var_elem, 'text': var_text, 'lastPrice': lastPrice, 'alarm': {'price': 0, 'percent': 0, 'interval': 0, 'status': False, 'nextCheck': False}} rv.controls.append(var_elem) mPage['main']['container'].controls.append(rv) page.update() # Деальная информация det_title = ft.Text("--/--", size = 50, weight = ft.FontWeight.BOLD) det_title_con = ft.Container(det_title,padding = 10, alignment=ft.alignment.center, border = ft.border.all(1, ft.colors.PRIMARY)) det_lastPrice = ft.Text("0.00000000", size = 20, weight = ft.FontWeight.BOLD) det_highPrice24h = ft.Text("0.00000000", size = 20, weight = ft.FontWeight.BOLD) det_lowPrice24h = ft.Text("0.00000000", size = 20, weight = ft.FontWeight.BOLD) det_prevPrice24h = ft.Text("0.00000000", size = 20, weight = ft.FontWeight.BOLD) det_volume24h = ft.Text("0.00000000", size = 20, weight = ft.FontWeight.BOLD) det_turnover24h = ft.Text("0.00000000", size = 20, weight = ft.FontWeight.BOLD) # Форма детальная информация con_det = ft.Container( ft.Column([ ft.ResponsiveRow([ft.Text("Последняя цена: ", size = 20), det_lastPrice]), ft.ResponsiveRow([ft.Text("Самая высокая цена за 24 часа: ", size = 20), det_highPrice24h]), ft.ResponsiveRow([ft.Text("Самая низкая цена за 24 часа: ", size = 20), det_lowPrice24h]), ft.ResponsiveRow([ft.Text("Процентное изменение рыночной цены: ", size = 20), det_prevPrice24h]), ft.ResponsiveRow([ft.Text("Объем за 24 часа: ", size = 20), det_volume24h]), ft.ResponsiveRow([ft.Text("Оборот за 24 часа: ", size = 20), det_turnover24h]) ]), col={"sm": 6, "md": 6, "xl": 4}, padding = 10, alignment=ft.alignment.center, border = ft.border.all(1, ft.colors.PRIMARY) ) # Форма включение уведомлений tf_percent = ft.TextField(label="Процент изменения цены", value="0.5", border_color = ft.colors.PRIMARY) tf_interval = ft.TextField(label="Интервал проверки цены (в секундах)", value="5", border_color = ft.colors.PRIMARY) sw_start_alarm = ft.Switch(label="Выкл. / Вкл.") btn_upd_alarm = ft.CupertinoFilledButton( content=ft.Text("Применить"), opacity_on_click = 0.3, on_click = proccess_upd_alarm, ) con_alarm = ft.Container( ft.Column([ tf_percent, tf_interval, sw_start_alarm, btn_upd_alarm ]), col={"sm": 6, "md": 6, "xl": 4}, padding = 10, alignment=ft.alignment.center, border = ft.border.all(1, ft.colors.PRIMARY) ) rvd = ft.ResponsiveRow([con_det, con_alarm]) mPage = { 'main': {'container': ft.ListView(expand = 1, spacing = 10, padding = 10)}, 'detailed': {'container': ft.ListView(expand = 1, spacing = 10, padding = 10)} } mPage['detailed']['container'].controls.append(det_title_con) mPage['detailed']['container'].controls.append(rvd) mPage['main']['container'].visible = True mPage['detailed']['container'].visible = False # Фильтр search = ft.TextField(label="Поиск", on_change=filter_proccess) searchContainer = ft.Container(search ,padding=10) btnFilter = ft.IconButton(ft.icons.FILTER_ALT_OFF, icon_size=30,tooltip = "Отмена фильтра", on_click = clear_filter) formSearch = ft.Row([searchContainer, btnFilter]) # Верхнее меню page.appbar = ft.AppBar( title=ft.Text("Bybit - торговые пары"), center_title=False, bgcolor=ft.colors.SURFACE_VARIANT, actions=[formSearch], ) # Нижнее меню page.navigation_bar = ft.CupertinoNavigationBar( bgcolor=ft.colors.SURFACE_VARIANT, on_change=lambda e: bottom_menu(e), destinations=[ft.NavigationBarDestination(icon=ft.icons.HOME_ROUNDED, label="Главная")] ) page.add(mPage['main']['container'], mPage['detailed']['container']) # Генерация главной страницы ws = threading.Thread(target = main_page, daemon=True) ws.start() # Запуск ws bb = bybit() # Запуск обновления торговых пар ws = threading.Thread(target = update_data_pair, daemon=True) ws.start() ft.app(target=main)
Исходный код

Заключение

В этом примере мы продемонстрировали, как можно реализовать получение системных уведомлений от приложения. Надеюсь, эта статья была полезной для вас.