2026-04-25 23:43:28 +00:00
|
|
|
import os
|
|
|
|
|
from nicegui import ui, app
|
|
|
|
|
from sqlalchemy import create_engine
|
2026-05-06 16:20:06 +00:00
|
|
|
# import requests
|
2026-04-25 23:43:28 +00:00
|
|
|
import json
|
2026-05-06 16:20:06 +00:00
|
|
|
# import time
|
|
|
|
|
# import re
|
2026-04-25 23:43:28 +00:00
|
|
|
import valkey
|
2026-05-07 00:25:49 +00:00
|
|
|
import asyncio
|
2026-05-06 16:20:06 +00:00
|
|
|
# import datetime as dt
|
|
|
|
|
# from random import random
|
|
|
|
|
# from nicegui_modules import data
|
|
|
|
|
# from nicegui_modules import ui_components
|
2026-04-25 23:43:28 +00:00
|
|
|
|
2026-05-06 16:20:06 +00:00
|
|
|
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/30
|
|
|
|
|
|
|
|
|
|
ENGINE = create_engine('mysql+pymysql://root:pwd@localhost/fund_rate')
|
2026-05-07 00:25:49 +00:00
|
|
|
VALKEY = valkey.Valkey(host='localhost', port=6379, db=0, decode_responses=True)
|
2026-04-25 23:43:28 +00:00
|
|
|
|
2026-05-06 16:20:06 +00:00
|
|
|
### 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():
|
2026-05-07 00:25:49 +00:00
|
|
|
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_arrival'] / 1000 ) , 2)
|
|
|
|
|
timestamp_extend_tob = round( ( series_update_extend_tob['timestamp_arrival'] / 1000 ) , 2)
|
|
|
|
|
timestamp_algo_status = round( ( series_update_algo_status['timestamp_arrival'] / 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_expected_alpha = float(series_update_algo_status['price'])
|
2026-05-06 16:20:06 +00:00
|
|
|
|
|
|
|
|
data_dict = {
|
2026-05-07 00:25:49 +00:00
|
|
|
'timestamp': timestamp_aster_tob,
|
|
|
|
|
'timestamp_b': timestamp_extend_tob,
|
|
|
|
|
'timestamp_c': timestamp_algo_status,
|
|
|
|
|
'value': value_aster_tob,
|
|
|
|
|
'value_b': value_extend_tob,
|
|
|
|
|
'value_c': value_algo_expected_alpha,
|
|
|
|
|
'target': series_update_algo_status['target_price'],
|
2026-05-06 16:20:06 +00:00
|
|
|
'LOOKBACK_RT_TV_MAX_POINTS': LOOKBACK_RT_TV_MAX_POINTS,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ui.run_javascript(f'await update_tv(data_dict={data_dict});')
|
2026-04-25 23:43:28 +00:00
|
|
|
|
|
|
|
|
|
2026-05-06 16:20:06 +00:00
|
|
|
### 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: Orderbook')
|
|
|
|
|
|
|
|
|
|
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('<div id="tv" style="width:100%; height:800px;"></div>', sanitize=False).classes('w-full')
|
|
|
|
|
ui.run_javascript('await create_tv();')
|
|
|
|
|
|
2026-04-25 23:43:28 +00:00
|
|
|
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('''
|
|
|
|
|
<meta name="darkreader-lock">
|
|
|
|
|
<link rel="stylesheet" type="text/css" href="/static/styles.css">
|
|
|
|
|
<script type="text/javascript" src="https://unpkg.com/lightweight-charts/dist/lightweight-charts.standalone.production.js"></script>
|
|
|
|
|
<script src="/static/script.js"></script>
|
|
|
|
|
'''
|
|
|
|
|
)
|
2026-05-06 16:20:06 +00:00
|
|
|
|
2026-04-25 23:43:28 +00:00
|
|
|
# ui.add_head_html('<meta name="darkreader-lock">')
|
2026-05-06 16:20:06 +00:00
|
|
|
update_body_scroll(bool_override=ALLOW_BODY_SCROLL)
|
2026-04-25 23:43:28 +00:00
|
|
|
|
|
|
|
|
ui.sub_pages({
|
2026-05-06 16:20:06 +00:00
|
|
|
'/': rt_chart_page,
|
2026-04-25 23:43:28 +00:00
|
|
|
}).classes('w-full')
|
|
|
|
|
|
2026-05-06 16:20:06 +00:00
|
|
|
ui.run(root, storage_secret="123ABC", reload=True, dark=True, title='Atwater Trading')
|