254 lines
9.4 KiB
Python
254 lines
9.4 KiB
Python
import os
|
|
from nicegui import ui, app, html
|
|
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]
|
|
master_data = json.loads(VALKEY.get(name='fr_engine_best_fund_rate_master')) # 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)
|
|
|
|
# ui.query('.q-page').classes('flex flex-col h-screen')
|
|
|
|
# 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=2, rows=2).classes('h-screen w-full flex-grow gap-2 auto-fit '):
|
|
aggrid = ui.aggrid({
|
|
'columnDefs': [
|
|
{'field': 'name', 'editable': True, 'sortable': True},
|
|
{'field': 'age', 'editable': True},
|
|
{'field': 'id'},
|
|
],
|
|
'rowData': [
|
|
{'id': 0, 'name': 'Alice', 'age': 18},
|
|
{'id': 1, 'name': 'Bob', 'age': 21},
|
|
{'id': 2, 'name': 'Carol', 'age': 20},
|
|
],
|
|
'rowSelection': {'mode': 'multiRow'},
|
|
'stopEditingWhenCellsLoseFocus': True,
|
|
}).classes('auto-fit flex-grow w-full col-span-2 md:col-span-1')
|
|
# with ui.element(tag='div').classes('auto-fit flex-grow w-full').style("height:100%; width: 100%;"):
|
|
# with ui.tabs().classes('w-full') as tabs:
|
|
# one = ui.tab('One').classes('auto-fit flex-grow w-full').style("height:100%; width: 100%;")
|
|
# two = ui.tab('Two').classes('auto-fit flex-grow w-full').style("height:100%; width: 100%;")
|
|
# with ui.tab_panels(tabs, value=two).classes('auto-fit flex-grow w-full').style("height:100%; width: 100%;"):
|
|
# with ui.tab_panel(one).classes('auto-fit flex-grow w-full').style("height:100%; width: 100%;"):
|
|
# ui.label('First tab')
|
|
# with ui.tab_panel(two).classes('auto-fit flex-grow w-full').style("height:100%; width: 100%;"):
|
|
ui.html('<div id="tv" style="height:100%; width: 100%;"></div>', sanitize=False).classes('auto-fit flex-grow w-full col-span-2 md:col-span-1')
|
|
ui.run_javascript(f'await create_tv(charts_list={CHARTS}, create_chart_options={CHARTS_OPTIONS});')
|
|
|
|
with ui.element(tag='div').classes('col-span-2').style("height:100%; width: 100%;"):
|
|
with ui.tabs().classes('w-full') as tabs:
|
|
one = ui.tab('One')
|
|
two = ui.tab('Two')
|
|
with ui.tab_panels(tabs, value=two).classes('w-full').style("height:100%; width: 100%;"):
|
|
with ui.tab_panel(one):
|
|
ui.label('First tab')
|
|
with ui.tab_panel(two):
|
|
aggrid_2 = ui.aggrid({
|
|
'columnDefs': [
|
|
{'field': 'name', 'editable': True, 'sortable': True},
|
|
{'field': 'age', 'editable': True},
|
|
{'field': 'id'},
|
|
],
|
|
'rowData': [
|
|
{'id': 0, 'name': 'Alice', 'age': 18},
|
|
{'id': 1, 'name': 'Bob', 'age': 21},
|
|
{'id': 2, 'name': 'Carol', 'age': 20},
|
|
],
|
|
'rowSelection': {'mode': 'multiRow'},
|
|
'stopEditingWhenCellsLoseFocus': True,
|
|
})
|
|
|
|
|
|
|
|
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>
|
|
'''
|
|
)
|
|
|
|
# ui.add_head_html('<meta name="darkreader-lock">')
|
|
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') |