import os from nicegui import ui, app from sqlalchemy import create_engine # import requests import json # import time # import re import valkey import asyncio from datetime import datetime from dataclasses import dataclass, field # from random import random # from nicegui_modules import data # from nicegui_modules import ui_components ALLOW_BODY_SCROLL: bool = True LOOKBACK: int = 60 LOOKBACK_RT_TV_MAX_POINTS: int = 3000 # REFRESH_INTERVAL_SEC: float = 10 REFRESH_INTERVAL_RT_SEC: float = 1/10 ENGINE = create_engine('mysql+pymysql://root:pwd@localhost/fund_rate') VALKEY = valkey.Valkey(host='localhost', port=6379, db=0, decode_responses=True) CHARTS = [ { 'type': 'AREA', 'autoscaleInfoProvider': False, 'data': [], 'options': { 'color': '#94fcdf', 'priceScaleId': 'right', 'topColor': '#94fcdf', 'bottomColor': 'rgba(112, 249, 210, 0.28)', 'invertFilledArea': True } }, { 'type': 'AREA', 'autoscaleInfoProvider': False, 'data': [], 'options': { 'color': '#dd7525', 'priceScaleId': 'right', 'topColor': '#94fcdf', 'bottomColor': 'rgba(249, 167, 112, 0.28)', 'invertFilledArea': False }, }, { 'type': 'LINE', 'autoscaleInfoProvider': [-0.1, 0.1], 'data': [], 'options': { 'color': '#ea0707', 'priceScaleId': 'left', }, }, { 'type': 'LINE', 'autoscaleInfoProvider': False, 'data': [], 'options': { 'color': '#009b12', 'priceScaleId': 'left', }, }, { 'type': 'LINE', 'autoscaleInfoProvider': False, 'data': [], 'options': { 'color': '#ffffff', 'priceScaleId': 'left', }, }, ] CHARTS_OPTIONS = { 'crosshair': 'NORMAL', 'autoSize': True, 'toolbox': True, 'timeScale': { 'timeVisible': True, # // Shows HH:mm on x-axis 'secondsVisible': True # // Optional: show seconds }, 'rightPriceScale': { 'visible': True, 'autoScale': True }, 'leftPriceScale': { 'visible': True }, 'layout': { 'background': { 'type': 'solid', 'color': '#222' }, 'textColor': '#DDD', }, 'grid': { 'vertLines': { 'color': '#e1e1e1', # // Set vertical line color 'visible': True, 'style': 2, # // 0: Solid, 1: Dashed, 2: Dotted, 3: LargeDashed, 4: SparseDotted }, 'horzLines': { 'color': '#e1e1e1', # // Set horizontal line color 'visible': True, 'style': 2, }, } } ### Utils ### def update_body_scroll(e=None, bool_override=False): if e is None: if bool_override: ui.query('body').style('height: 100%; overflow-y: auto;') else: ui.query('body').style('height: 100%; overflow-y: hidden;') else: if e.value: ui.query('body').style('height: 100%; overflow-y: auto;') else: ui.query('body').style('height: 100%; overflow-y: hidden;') ### Callbacks ### async def update_tv(): series_update_aster_tob = json.loads(VALKEY.get('fut_ticker_aster')) # ty:ignore[invalid-argument-type] series_update_extend_tob = json.loads(VALKEY.get('fut_ticker_extended')) # ty:ignore[invalid-argument-type] series_update_algo_status = json.loads(VALKEY.get('algo_status')) # ty:ignore[invalid-argument-type] timestamp_aster_tob = round( ( series_update_aster_tob['timestamp_transaction'] / 1000 ) , 2) timestamp_extend_tob = round( ( series_update_extend_tob['timestamp_msg'] / 1000 ) , 2) timestamp_algo_status = round( ( series_update_algo_status['last_update_ts_ms'] / 1000 ) , 2) value_aster_tob = ( float(series_update_aster_tob['best_ask_px']) + float(series_update_aster_tob['best_bid_px']) ) / 2 value_extend_tob = ( float(series_update_extend_tob['best_ask_px']) + float(series_update_extend_tob['best_bid_px']) ) / 2 value_algo_model_ratio = float(series_update_algo_status['model_ratio'])*1_000 value_algo_current_ratio = float(series_update_algo_status['current_ratio'])*1_000 value_algo_expected_alpha = float(series_update_algo_status['expected_alpha'])*1_000 data_list = [ { 'timestamp': timestamp_aster_tob, 'value': value_aster_tob, }, { 'timestamp': timestamp_extend_tob, 'value': value_extend_tob, }, { 'timestamp': timestamp_algo_status, 'value': value_algo_model_ratio, }, { 'timestamp': timestamp_algo_status, 'value': value_algo_current_ratio, }, { 'timestamp': timestamp_algo_status, 'value': value_algo_expected_alpha, }, ] ui.run_javascript(f'await update_tv(data_list={data_list}, lookback_max_points={LOOKBACK_RT_TV_MAX_POINTS});') ### Pages ### async def rt_chart_page(): global LOOKBACK LOOKBACK = app.storage.user.get('lookback', LOOKBACK) timer = ui.timer(REFRESH_INTERVAL_RT_SEC, update_tv) with ui.row(): with ui.column(): ui.switch('☸︎', value=ALLOW_BODY_SCROLL, on_change=lambda e: update_body_scroll(e)) with ui.column(): ui.switch('▶️', value=True).bind_value_to(timer, 'active') with ui.column().style('position: absolute; right: 20px; font-family: monospace; align-self: center;'): ui.label('Atwater Trading - Funding Rate') with ui.grid(columns=16).classes('w-full gap-0 auto-fit'): with ui.card().tight().classes('w-full col-span-full no-shadow border border-black-200').style('overflow: auto;'): ui.html('
', sanitize=False).classes('w-full') ui.run_javascript(f'await create_tv(charts_list={CHARTS}, create_chart_options={CHARTS_OPTIONS});') def root(): app.add_static_files(max_cache_age=0, url_path='/static', local_directory=os.path.join(os.path.dirname(__file__), 'nicegui_modules/static')) ui.add_head_html(''' ''' ) # ui.add_head_html('') update_body_scroll(bool_override=ALLOW_BODY_SCROLL) ui.sub_pages({ '/': rt_chart_page, }).classes('w-full') ui.run(root, storage_secret="123ABC", reload=True, dark=True, title='Atwater Trading')