refactor algo orchestrator and hedge ratio bug fix

This commit is contained in:
2026-04-29 16:18:42 +00:00
parent 8f3f7c6667
commit dc3409ac40
14 changed files with 1475 additions and 11128 deletions

172
engine_best_funding_rate.py Normal file
View File

@@ -0,0 +1,172 @@
import asyncio
import json
import logging
import os
import time
import traceback
from dataclasses import dataclass, field
from datetime import datetime
from typing import AsyncContextManager
import pandas as pd
import requests
import valkey
from dotenv import load_dotenv
# from sqlalchemy.ext.asyncio import create_async_engine
### Structs ###
@dataclass(kw_only=False)
class Asset_Leverage:
exchange: str
lh_asset: str
rh_asset: str
max_leverage: int
max_notional: float
# max_leverage_notional: list = field(default_factory=list)
### MANUAL LEVERAGE DATA ###
LEVERAGE_BY_EXCH: list[Asset_Leverage] = [
Asset_Leverage('ASTER', 'BTC' , 'USDT', 150, 300_000), Asset_Leverage('EXTEND', 'BTC' , 'USD', 50, 4_000_000),
Asset_Leverage('ASTER', 'ETH' , 'USDT', 150, 300_000), Asset_Leverage('EXTEND', 'ETH' , 'USD', 50, 4_000_000),
Asset_Leverage('ASTER', 'LIT' , 'USDT', 50 , 2_500 ), Asset_Leverage('EXTEND', 'LIT' , 'USD', 25, 400_000 ),
Asset_Leverage('ASTER', 'CHIP' , 'USDT', 50 , 5_000 ), Asset_Leverage('EXTEND', 'CHIP' , 'USD', 5 , 100_000 ),
Asset_Leverage('ASTER', 'XAG' , 'USDT', 100, 50_000 ), Asset_Leverage('EXTEND', 'XAG' , 'USD', 10, 1_000_000),
Asset_Leverage('ASTER', '4' , 'USDT', 50 , 5_000 ), Asset_Leverage('EXTEND', '4' , 'USD', 5 , 100_000 ),
Asset_Leverage('ASTER', 'XPT' , 'USDT', 3 , 30_000 ), Asset_Leverage('EXTEND', 'XPT' , 'USD', 5 , 1_000_000),
Asset_Leverage('ASTER', 'XMR' , 'USDT', 50 , 10_000 ), Asset_Leverage('EXTEND', 'XMR' , 'USD', 25, 400_000 ),
Asset_Leverage('ASTER', 'WLFI' , 'USDT', 25 , 104_869), Asset_Leverage('EXTEND', 'WLFI' , 'USD', 10, 250_000 ),
Asset_Leverage('ASTER', 'TRUMP', 'USDT', 50 , 5_567 ), Asset_Leverage('EXTEND', 'TRUMP', 'USD', 25, 400_000 ),
Asset_Leverage('ASTER', 'INIT' , 'USDT', 50 , 5_000 ), Asset_Leverage('EXTEND', 'INIT' , 'USD', 5 , 100_000 ),
Asset_Leverage('ASTER', 'ZORA' , 'USDT', 5 , 100_000), Asset_Leverage('EXTEND', 'ZORA' , 'USD', 5 , 100_000 ),
Asset_Leverage('ASTER', 'ZEC' , 'USDT', 75 , 6_250 ), Asset_Leverage('EXTEND', 'ZEC' , 'USD', 10, 250_000 ),
]
df_leverage_by_exch = pd.DataFrame(LEVERAGE_BY_EXCH)
### Database ###
# CON: AsyncContextManager | None = None
VAL_KEY = None
VK_OUT = 'fr_engine_best_fund_rate_output'
### Logging ###
load_dotenv()
LOG_FILEPATH: str = os.getenv("LOGS_PATH") + '/Fund_Rate_Engine_BFR.log'
### CONSTANTS ###
LOOP_SLEEP_SEC: int = 5
REFRESH_MKT_INFO_EVERY_SEC: int = 90
REFRESH_MKT_VOLUME_EVERY_SEC: int = 30
### GLOBALS ###
Mkt_Info_Last_Refresh_TS_ms: int
Mkt_Volume_Last_Refresh_TS_ms: int
### Funcs - Load Data ###
def get_extended_markets_info() -> pd.DataFrame:
global Mkt_Info_Last_Refresh_TS_ms
r = json.loads(requests.get('https://api.starknet.extended.exchange/api/v1/info/markets').text)
df = pd.DataFrame(r['data'])
df['funding_rate'] = df['marketStats'].apply(lambda x: x.get('fundingRate',{}))
df['funding_rate_ts'] = df['marketStats'].apply(lambda x: x.get('nextFundingRate',{}))
df['min_order_size'] = df['tradingConfig'].apply(lambda x: x.get('minOrderSize',{}))
df['min_price_change'] = df['tradingConfig'].apply(lambda x: x.get('minPriceChange',{}))
df['max_leverage'] = df['tradingConfig'].apply(lambda x: x.get('maxLeverage',{}))
Mkt_Info_Last_Refresh_TS_ms = round(datetime.now().timestamp() * 1000)
print('Extend markets info refreshed successfully')
return df
def load_aster_current_fr() -> pd.DataFrame:
df = pd.DataFrame(json.loads(VAL_KEY.get('fund_rate_aster_all')))
df = df[['s','E','r','T']].rename({'s':'symbol','E':'funding_rate_updated_ts_ms','r':'funding_rate','T':'next_funding_ts'}, axis=1)
df['funding_rate_updated_dt'] = pd.to_datetime(df['funding_rate_updated_ts_ms'], unit='ms')
df['funding_rate'] = df['funding_rate'].astype(float)
df['time_delta_to_next_funding'] = pd.to_datetime(df['next_funding_ts'], unit='ms') - pd.Timestamp.now()
return df
def load_extend_current_fr(df_mkt_stats: pd.DataFrame) -> pd.DataFrame:
df = pd.DataFrame(json.loads(VAL_KEY.get('fund_rate_extended_all')))
df = df[['symbol','funding_rate_updated_ts_ms','funding_rate']]
df['funding_rate_updated_dt'] = pd.to_datetime(df['funding_rate_updated_ts_ms'], unit='ms')
df['funding_rate'] = df['funding_rate'].astype(float)
df = df.merge(df_mkt_stats[['name','assetName','status', 'funding_rate_ts','max_leverage']].rename({'name':'symbol','funding_rate_ts':'next_funding_ts'}, axis=1), on='symbol', how='left')
df = df.loc[df['status']=='ACTIVE',:]
df['USDT_Symbol'] = df['assetName'] + 'USDT'
df['time_delta_to_next_funding'] = pd.to_datetime(df['next_funding_ts'], unit='ms') - pd.Timestamp.now()
return df
async def loop() -> None:
global Mkt_Info_Last_Refresh_TS_ms
df_extend_mkt_stats = get_extended_markets_info()
try:
while True:
ts_arrival = round(datetime.now().timestamp() * 1000)
if ( ts_arrival - Mkt_Info_Last_Refresh_TS_ms ) > ( REFRESH_MKT_INFO_EVERY_SEC * 1000 ):
df_extend_mkt_stats = get_extended_markets_info()
df_aster_fr = load_aster_current_fr()
df_extend_fr = load_extend_current_fr(df_mkt_stats=df_extend_mkt_stats)
df_comb_fr = df_extend_fr.merge(df_aster_fr, left_on='USDT_Symbol', right_on='symbol', how='inner', suffixes=('_ext', '_ast'))
df_comb_fr['next_funding_at_same_time'] = (abs(df_comb_fr['time_delta_to_next_funding_ext'].dt.total_seconds() - df_comb_fr['time_delta_to_next_funding_ast'].dt.total_seconds()) / 60) < 1
df_comb_fr['net_funding_rate'] = (df_comb_fr[['funding_rate_ext', 'funding_rate_ast']].max(axis=1) - df_comb_fr[['funding_rate_ext', 'funding_rate_ast']].min(axis=1)).where(df_comb_fr['next_funding_at_same_time'], df_comb_fr['funding_rate_ext'])
df_comb_fr['net_funding_rate_abs'] = df_comb_fr['net_funding_rate'].abs()
### NET MULT ###
df_comb_fr = df_comb_fr.merge(df_leverage_by_exch.loc[df_leverage_by_exch['exchange']=='EXTEND'], left_on='assetName', right_on='lh_asset').merge(df_leverage_by_exch.loc[df_leverage_by_exch['exchange']=='ASTER'], left_on='assetName', right_on='lh_asset', suffixes=('_ext', '_ast'))
df_comb_fr['net_mult'] = 1 / ( ( 0.5 / df_comb_fr['max_leverage_ext'] ) + ( 0.5 / df_comb_fr['max_leverage_ast'] ) )
df_comb_fr['net_mult'] = df_comb_fr['net_mult'].round(2)
df_comb_fr['net_mult_x_net_fr_abs'] = df_comb_fr['net_funding_rate_abs'] * df_comb_fr['net_mult']
df_best_fr_rate = df_comb_fr[['symbol_ext','symbol_ast','net_mult_x_net_fr_abs','net_funding_rate_abs','net_funding_rate','next_funding_at_same_time']].sort_values(by='net_mult_x_net_fr_abs', ascending=False).reset_index(drop=True)
best_next_funding_pair = {'symbol_aster':df_best_fr_rate['symbol_ast'][0],'symbol_extended':df_best_fr_rate['symbol_ext'][0]}
VAL_KEY.set(VK_OUT, json.dumps(best_next_funding_pair))
print(best_next_funding_pair)
time.sleep(LOOP_SLEEP_SEC)
continue
except valkey.exceptions.ConnectionError as e:
logging.info(f"Could not connect to Valkey. Please check the publish server is up; {e}")
except KeyboardInterrupt:
logging.info('ORCHESTRATOR SHUTTING DOWN...')
except Exception as e:
logging.error(traceback.format_exc())
logging.critical(f'*** ORCHESTRATOR CRASHED: {e}')
### STARTUP ###
async def main() -> None:
global VAL_KEY
# global CON
VAL_KEY = valkey.Valkey(host='localhost', port=6379, db=0, decode_responses=True)
# engine = create_async_engine('mysql+asyncmy://root:pwd@localhost/fund_rate')
await loop()
if __name__ == '__main__':
START_TIME = round(datetime.now().timestamp()*1000)
logging.info(f'Log FilePath: {LOG_FILEPATH}')
logging.basicConfig(
force=True,
filename=LOG_FILEPATH,
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
filemode='w'
)
logging.info(f"STARTED: {START_TIME}")
asyncio.run(main())