update switching fr logic for expected alpha

This commit is contained in:
2026-05-07 20:59:47 +00:00
parent 1bbb4797ce
commit e4bdb3f6a0
6 changed files with 122 additions and 60 deletions

View File

@@ -2,7 +2,7 @@
"cells": [ "cells": [
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 7, "execution_count": 1,
"id": "d1eed397", "id": "d1eed397",
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
@@ -22,7 +22,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 8, "execution_count": 2,
"id": "c6151613", "id": "c6151613",
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
@@ -32,7 +32,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 9, "execution_count": null,
"id": "d83c61e5", "id": "d83c61e5",
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
@@ -48,6 +48,7 @@
} }
], ],
"source": [ "source": [
"\n",
"config_update = {\n", "config_update = {\n",
" # 'Config': {\n", " # 'Config': {\n",
" # 'Price_Worsener_Aster': 0,\n", " # 'Price_Worsener_Aster': 0,\n",
@@ -57,15 +58,15 @@
" # },\n", " # },\n",
" 'Logging': {\n", " 'Logging': {\n",
" 'Log_Summary_Each_Loop': False,\n", " 'Log_Summary_Each_Loop': False,\n",
" 'Print_Summary_Each_Loop': True,\n", " 'Print_Summary_Each_Loop': False,\n",
" },\n",
" 'Overrides': {\n",
" 'Allow_Ordering_Aster': True,\n",
" 'Allow_Ordering_Extend': True,\n",
" 'Allow_Symbol_Change': True,\n",
" # 'Flatten_Open_Positions': False,\n",
" # 'Flatten_Open_Positions_Opportunistic': False,\n",
" },\n", " },\n",
" # 'Overrides': {\n",
" # 'Allow_Ordering_Aster': True,\n",
" # 'Allow_Ordering_Extend': True,\n",
" # 'Allow_Symbol_Change': True,\n",
" # # 'Flatten_Open_Positions': False,\n",
" # # 'Flatten_Open_Positions_Opportunistic': False,\n",
" # },\n",
"}\n", "}\n",
"VAL_KEY.publish('fr_orchestrator_input', json.dumps(config_update))" "VAL_KEY.publish('fr_orchestrator_input', json.dumps(config_update))"
] ]

View File

@@ -1,5 +1,5 @@
{ {
"Updated_Timestamp": 1778133411421, "Updated_Timestamp": 1778178429902,
"Config": { "Config": {
"Loop_Sleep_Sec": 0.0, "Loop_Sleep_Sec": 0.0,
"Max_Order_Over_Notional_Ratio": 1.05, "Max_Order_Over_Notional_Ratio": 1.05,

View File

@@ -204,8 +204,8 @@ async def loop() -> None:
df_best_fr_rate['last_trade_ts_dt_ast'] = pd.to_datetime(df_best_fr_rate['last_trade_ts_ast'], unit='ms') df_best_fr_rate['last_trade_ts_dt_ast'] = pd.to_datetime(df_best_fr_rate['last_trade_ts_ast'], unit='ms')
df_best_fr_rate['last_trade_ts_dt_ext'] = pd.to_datetime(df_best_fr_rate['last_trade_ts_ext'], unit='ms') df_best_fr_rate['last_trade_ts_dt_ext'] = pd.to_datetime(df_best_fr_rate['last_trade_ts_ext'], unit='ms')
df_best_fr_rate = df_best_fr_rate.loc[( (datetime.now().timestamp()*1000 )-df_best_fr_rate['last_trade_ts_ast']) < (3*60*1000) ] df_best_fr_rate = df_best_fr_rate.loc[( (datetime.now().timestamp()*1000 )-df_best_fr_rate['last_trade_ts_ast']) < (5*60*1000) ] # Last traded in 3min
df_best_fr_rate = df_best_fr_rate.loc[( (datetime.now().timestamp()*1000 )-df_best_fr_rate['last_trade_ts_ext']) < (15*60*1000) ] # df_best_fr_rate = df_best_fr_rate.loc[( (datetime.now().timestamp()*1000 )-df_best_fr_rate['last_trade_ts_ext']) < (15*60*1000) ] # Last traded in 15min
# print(df_best_fr_rate.columns) # print(df_best_fr_rate.columns)
# print(df_best_fr_rate.iloc[0]) # print(df_best_fr_rate.iloc[0])

61
main.py
View File

@@ -249,33 +249,34 @@ def signal_alpha_over_taker(
alpha_hurdle_adj: Decimal = Decimal('0.00'), alpha_hurdle_adj: Decimal = Decimal('0.00'),
) -> Signal: ) -> Signal:
if funding_rate_exch == 'ASTER':
if funding_rate_side == 'BUY':
aster_buy_fund_rate_return = abs(funding_rate)
extend_buy_fund_rate_return = abs(funding_rate) * -1
else:
aster_buy_fund_rate_return = abs(funding_rate) * -1
extend_buy_fund_rate_return = abs(funding_rate)
else: # funding_rate_exch == 'EXTEND':
if funding_rate_side == 'BUY':
aster_buy_fund_rate_return = abs(funding_rate) * -1
extend_buy_fund_rate_return = abs(funding_rate)
else:
aster_buy_fund_rate_return = abs(funding_rate)
extend_buy_fund_rate_return = abs(funding_rate) * -1
if Config.Overrides.Flatten_Open_Positions_Opportunistic: if Config.Overrides.Flatten_Open_Positions_Opportunistic:
if Decimal(str(Aster.notional_position)) > 0: if Decimal(str(Aster.notional_position)) > 0:
aster_buy_fund_rate_return = abs(funding_rate_switch) * -1 aster_buy_fund_rate_return = aster_buy_fund_rate_return
extend_buy_fund_rate_return = abs(funding_rate_switch) extend_buy_fund_rate_return = max([extend_buy_fund_rate_return, abs(funding_rate_switch)])
# funding_rate_exch = 'EXTEND' # funding_rate_exch = 'EXTEND'
# funding_rate_side = 'BUY' # funding_rate_side = 'BUY'
else: # Decimal(str(Aster.notional_position)) < 0: else: # Decimal(str(Aster.notional_position)) < 0:
aster_buy_fund_rate_return = abs(funding_rate_switch) aster_buy_fund_rate_return = max([aster_buy_fund_rate_return, abs(funding_rate_switch)])
extend_buy_fund_rate_return = abs(funding_rate_switch) * -1 extend_buy_fund_rate_return = extend_buy_fund_rate_return
# funding_rate_exch = 'ASTER' # funding_rate_exch = 'ASTER'
# funding_rate_side = 'BUY' # funding_rate_side = 'BUY'
else:
if funding_rate_exch == 'ASTER':
if funding_rate_side == 'BUY':
aster_buy_fund_rate_return = abs(funding_rate)
extend_buy_fund_rate_return = abs(funding_rate) * -1
else:
aster_buy_fund_rate_return = abs(funding_rate) * -1
extend_buy_fund_rate_return = abs(funding_rate)
else: # funding_rate_exch == 'EXTEND':
if funding_rate_side == 'BUY':
aster_buy_fund_rate_return = abs(funding_rate) * -1
extend_buy_fund_rate_return = abs(funding_rate)
else:
aster_buy_fund_rate_return = abs(funding_rate)
extend_buy_fund_rate_return = abs(funding_rate) * -1
aster_mid_px: Decimal = ( Decimal(str(aster_ticker_dict['best_ask_px'])) + Decimal(str(aster_ticker_dict['best_bid_px'])) ) / 2 aster_mid_px: Decimal = ( Decimal(str(aster_ticker_dict['best_ask_px'])) + Decimal(str(aster_ticker_dict['best_bid_px'])) ) / 2
extend_mid_px: Decimal = ( Decimal(str(extend_ticker_dict['best_ask_px'])) + Decimal(str(extend_ticker_dict['best_bid_px'])) ) / 2 extend_mid_px: Decimal = ( Decimal(str(extend_ticker_dict['best_ask_px'])) + Decimal(str(extend_ticker_dict['best_bid_px'])) ) / 2
@@ -288,7 +289,8 @@ def signal_alpha_over_taker(
aster_buy_expected_alpha: Decimal = ( aster_buy_ratio_min_taker_hurdle - Decimal(str(Aster.buy_ratio)) ).quantize(Decimal('0.000001'), rounding='ROUND_DOWN') # Decimal Price % Diff (x Qty = Alpha $) aster_buy_expected_alpha: Decimal = ( aster_buy_ratio_min_taker_hurdle - Decimal(str(Aster.buy_ratio)) ).quantize(Decimal('0.000001'), rounding='ROUND_DOWN') # Decimal Price % Diff (x Qty = Alpha $)
extend_buy_expected_alpha: Decimal = ( extend_buy_ratio_min_taker_hurdle - Decimal(str(Extend.buy_ratio)) ).quantize(Decimal('0.000001'), rounding='ROUND_DOWN') # Decimal Price % Diff (x Qty = Alpha $) extend_buy_expected_alpha: Decimal = ( extend_buy_ratio_min_taker_hurdle - Decimal(str(Extend.buy_ratio)) ).quantize(Decimal('0.000001'), rounding='ROUND_DOWN') # Decimal Price % Diff (x Qty = Alpha $)
best_expected_alpha = max([aster_buy_expected_alpha, extend_buy_expected_alpha])
# logging.info(f'aster_buy_ratio_min_taker_hurdle: ( {aster_buy_ratio} - {aster_buy_fund_rate_return} ) - {taker_fee} - {alpha_hurdle_adj} = {aster_buy_ratio_min_taker_hurdle}') # logging.info(f'aster_buy_ratio_min_taker_hurdle: ( {aster_buy_ratio} - {aster_buy_fund_rate_return} ) - {taker_fee} - {alpha_hurdle_adj} = {aster_buy_ratio_min_taker_hurdle}')
# logging.info(f'extend_buy_ratio_min_taker_hurdle: ( {extend_buy_ratio} - {extend_buy_fund_rate_return} ) - {taker_fee} - {alpha_hurdle_adj} = {extend_buy_ratio_min_taker_hurdle}') # logging.info(f'extend_buy_ratio_min_taker_hurdle: ( {extend_buy_ratio} - {extend_buy_fund_rate_return} ) - {taker_fee} - {alpha_hurdle_adj} = {extend_buy_ratio_min_taker_hurdle}')
# logging.info(f'aster_buy_expected_alpha: {aster_buy_ratio_min_taker_hurdle} - {Decimal(str(Aster.buy_ratio))} = {aster_buy_expected_alpha}') # logging.info(f'aster_buy_expected_alpha: {aster_buy_ratio_min_taker_hurdle} - {Decimal(str(Aster.buy_ratio))} = {aster_buy_expected_alpha}')
@@ -299,7 +301,7 @@ def signal_alpha_over_taker(
# aster_buy_expected_alpha : 0.000673878630659394266895260 - 0.0014628375 = -0.0007 # aster_buy_expected_alpha : 0.000673878630659394266895260 - 0.0014628375 = -0.0007
# extend_buy_expected_alpha : -0.001173878630659394266895260 - -0.0014628375 = 0.0002 # extend_buy_expected_alpha : -0.001173878630659394266895260 - -0.0014628375 = 0.0002
if aster_buy_expected_alpha > 0: if ( aster_buy_expected_alpha > 0 ) and ( best_expected_alpha == aster_buy_expected_alpha ):
signal: bool = True signal: bool = True
exchange: str = 'ASTER' exchange: str = 'ASTER'
side: str = 'BUY' side: str = 'BUY'
@@ -307,7 +309,7 @@ def signal_alpha_over_taker(
expected_alpha: Decimal = aster_buy_expected_alpha expected_alpha: Decimal = aster_buy_expected_alpha
model_ratio: Decimal = Decimal(str(Aster.buy_ratio)) model_ratio: Decimal = Decimal(str(Aster.buy_ratio))
current_ratio: Decimal = aster_buy_ratio_min_taker_hurdle current_ratio: Decimal = aster_buy_ratio_min_taker_hurdle
elif extend_buy_expected_alpha > 0: elif ( extend_buy_expected_alpha > 0 ) and ( best_expected_alpha == extend_buy_expected_alpha ):
signal: bool = True signal: bool = True
exchange: str = 'EXTEND' exchange: str = 'EXTEND'
side: str = 'BUY' side: str = 'BUY'
@@ -316,7 +318,7 @@ def signal_alpha_over_taker(
model_ratio: Decimal = Decimal(str(Extend.buy_ratio)) model_ratio: Decimal = Decimal(str(Extend.buy_ratio))
current_ratio: Decimal = extend_buy_ratio_min_taker_hurdle current_ratio: Decimal = extend_buy_ratio_min_taker_hurdle
else: else:
if max([aster_buy_expected_alpha,extend_buy_expected_alpha]) == aster_buy_expected_alpha: if best_expected_alpha == aster_buy_expected_alpha:
signal: bool = False signal: bool = False
exchange: str = 'ASTER' exchange: str = 'ASTER'
side: str = 'BUY' side: str = 'BUY'
@@ -877,7 +879,7 @@ async def run_algo():
aster_fund_rate = aster_fund_rate * -1 aster_fund_rate = aster_fund_rate * -1
extend_fund_rate = extend_fund_rate * -1 extend_fund_rate = extend_fund_rate * -1
aster_fund_rate_time = float(aster_fund_rate_dict.get('next_funding_time_ts_ms', 0)) aster_fund_rate_time = max([float(aster_fund_rate_dict.get('next_funding_time_ts_ms', 0)), 0])
aster_fund_rate_time = aster_fund_rate_time+(60*60*1000) if aster_fund_rate_time < (datetime.now().timestamp()*1000) else aster_fund_rate_time aster_fund_rate_time = aster_fund_rate_time+(60*60*1000) if aster_fund_rate_time < (datetime.now().timestamp()*1000) else aster_fund_rate_time
extend_fund_rate_time = max([float(extend_fund_rate_dict.get('next_funding_time_ts_ms', 0)), 0]) extend_fund_rate_time = max([float(extend_fund_rate_dict.get('next_funding_time_ts_ms', 0)), 0])
@@ -893,7 +895,8 @@ async def run_algo():
if next_funding_at_same_time: if next_funding_at_same_time:
net_fr = max([fund_rate_ast, fund_rate_ext]) - min([fund_rate_ast, fund_rate_ext]) net_fr = max([fund_rate_ast, fund_rate_ext]) - min([fund_rate_ast, fund_rate_ext])
fr_best_exch = 'ASTER' if max([abs(fund_rate_ast), abs(fund_rate_ext)]) == abs(fund_rate_ast) else 'EXTEND' fr_best_exch = 'ASTER' if max([abs(fund_rate_ast), abs(fund_rate_ext)]) == abs(fund_rate_ast) else 'EXTEND'
fr_best_side = 'BUY' if net_fr < 0 else 'SELL' fr_best_rate = fund_rate_ast if max([abs(fund_rate_ast), abs(fund_rate_ext)]) == abs(fund_rate_ast) else fund_rate_ext
fr_best_side = 'BUY' if fr_best_rate < 0 else 'SELL'
return net_fr, fr_best_exch, fr_best_side return net_fr, fr_best_exch, fr_best_side
else: else:
fr_best_exch = 'EXTEND' fr_best_exch = 'EXTEND'
@@ -1261,11 +1264,7 @@ async def main():
VAL_KEY.set(name='fr_orchestrator_output', value=json.dumps(obj=Config.model_dump())) VAL_KEY.set(name='fr_orchestrator_output', value=json.dumps(obj=Config.model_dump()))
VAL_KEY.set(name='fr_algo_working_symbol', value=json.dumps(obj={'ASTER': asdict(obj=Aster), 'EXTEND': asdict(obj=Extend)})) VAL_KEY.set(name='fr_algo_working_symbol', value=json.dumps(obj={'ASTER': asdict(obj=Aster), 'EXTEND': asdict(obj=Extend)}))
Funding_Rates_Min_Remaining_Factor_Pcts = calc_fr_minutes_remaining_factor( Funding_Rates_Min_Remaining_Factor_Pcts = calc_fr_minutes_remaining_factor()
min_start_procedure = 30,
min_to_end_procedure = 7,
factor_exp_pct = 0.50
)
Algo_Status = structs.Algo_Status( Algo_Status = structs.Algo_Status(
last_update_ts_ms = int(round(datetime.now().timestamp()*1000, 2)), last_update_ts_ms = int(round(datetime.now().timestamp()*1000, 2)),

75
ng.py
View File

@@ -1,5 +1,5 @@
import os import os
from nicegui import ui, app from nicegui import ui, app, html
from sqlalchemy import create_engine from sqlalchemy import create_engine
# import requests # import requests
import json import json
@@ -127,7 +127,8 @@ def update_body_scroll(e=None, bool_override=False):
async def update_tv(): async def update_tv():
series_update_aster_tob = json.loads(VALKEY.get('fut_ticker_aster')) # ty:ignore[invalid-argument-type] 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_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] 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_aster_tob = round( ( series_update_aster_tob['timestamp_transaction'] / 1000 ) , 2)
timestamp_extend_tob = round( ( series_update_extend_tob['timestamp_msg'] / 1000 ) , 2) timestamp_extend_tob = round( ( series_update_extend_tob['timestamp_msg'] / 1000 ) , 2)
@@ -172,18 +173,66 @@ async def rt_chart_page():
LOOKBACK = app.storage.user.get('lookback', LOOKBACK) LOOKBACK = app.storage.user.get('lookback', LOOKBACK)
timer = ui.timer(REFRESH_INTERVAL_RT_SEC, update_tv) timer = ui.timer(REFRESH_INTERVAL_RT_SEC, update_tv)
with ui.row(): # ui.query('.q-page').classes('flex flex-col h-screen')
with ui.column():
ui.switch('☸︎', value=ALLOW_BODY_SCROLL, on_change=lambda e: update_body_scroll(e)) # with ui.row():
with ui.column(): # with ui.column():
ui.switch('▶️', value=True).bind_value_to(timer, 'active') # ui.switch('☸︎', value=ALLOW_BODY_SCROLL, on_change=lambda e: update_body_scroll(e))
with ui.column().style('position: absolute; right: 20px; font-family: monospace; align-self: center;'): # with ui.column():
ui.label('Atwater Trading - Funding Rate') # 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.grid(columns=2, rows=2).classes('h-screen w-full flex-grow gap-2 auto-fit '):
with ui.card().tight().classes('w-full col-span-full no-shadow border border-black-200').style('overflow: auto;'): aggrid = ui.aggrid({
ui.html('<div id="tv" style="width:100%; height:800px;"></div>', sanitize=False).classes('w-full') 'columnDefs': [
ui.run_javascript(f'await create_tv(charts_list={CHARTS}, create_chart_options={CHARTS_OPTIONS});') {'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(): 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')) app.add_static_files(max_cache_age=0, url_path='/static', local_directory=os.path.join(os.path.dirname(__file__), 'nicegui_modules/static'))

View File

@@ -36,10 +36,12 @@ async function update_tv(data_list, lookback_max_points) {
async function create_tv(charts_list, create_chart_options) { async function create_tv(charts_list, create_chart_options) {
const container = document.getElementById('tv');
if (create_chart_options.crosshair == 'NORMAL') { if (create_chart_options.crosshair == 'NORMAL') {
create_chart_options.crosshair = { mode: LightweightCharts.CrosshairMode.Normal } create_chart_options.crosshair = { mode: LightweightCharts.CrosshairMode.Normal }
}; };
window.chart = LightweightCharts.createChart(document.getElementById('tv'), create_chart_options); window.chart = LightweightCharts.createChart(container, create_chart_options);
window.charts_arr = []; window.charts_arr = [];
charts_list.forEach(function (item, index) { charts_list.forEach(function (item, index) {
@@ -64,7 +66,18 @@ async function create_tv(charts_list, create_chart_options) {
}); });
window.chart.timeScale().fitContent(); window.chart.timeScale().fitContent();
// Handle responsiveness: Resize chart when container size changes
const resizeObserver = new ResizeObserver(entries => {{
for (let entry of entries) {{
window.chart.applyOptions({
width: entry.contentRect.width,
height: entry.contentRect.height
});
}}
}});
resizeObserver.observe(container);
console.log("TV Created!") console.log("TV Created!")
// window.midPriceLine_Config = { // window.midPriceLine_Config = {