algo status

This commit is contained in:
2026-05-07 00:25:49 +00:00
parent 99312b768f
commit f45c035ebb
6 changed files with 634 additions and 81 deletions

117
main.py
View File

@@ -39,6 +39,7 @@ LOG_FILEPATH: str = f'{os.getenv(key="LOGS_PATH")}/Fund_Rate_Algo.log'
### Algo Config ###
Config: structs.Algo_Config
Algo_Status: structs.Algo_Status
### Exchanges ###
Aster: structs.Perpetual_Exchange
@@ -57,6 +58,12 @@ Flags = structs.Flags()
### Algo ###
async def output_algo_status(status: str) -> None:
global Algo_Status
Algo_Status.status = status
VAL_KEY.set('algo_status', json.dumps(Algo_Status.model_dump()))
def create_exchange_objs_from_dict(exchanges_dict: dict) -> tuple[structs.Perpetual_Exchange, structs.Perpetual_Exchange]:
Aster = structs.Perpetual_Exchange(
mult = int(exchanges_dict['max_leverage_ast']),
@@ -64,6 +71,7 @@ def create_exchange_objs_from_dict(exchanges_dict: dict) -> tuple[structs.Perpet
rh_asset = exchanges_dict['rh_asset_ast'],
symbol_asset_separator = '',
initial_funding_rate=float(exchanges_dict['funding_rate_ast']),
fund_rate_at_same_time=bool(exchanges_dict['next_funding_at_same_time']),
min_price=float(exchanges_dict['min_price_ast']),
min_order_size=float(exchanges_dict['min_order_size_ast']),
min_lot_size=float(exchanges_dict['min_lot_size_ast']),
@@ -77,6 +85,7 @@ def create_exchange_objs_from_dict(exchanges_dict: dict) -> tuple[structs.Perpet
rh_asset = exchanges_dict['rh_asset_ext'],
symbol_asset_separator = '-',
initial_funding_rate=float(exchanges_dict['funding_rate_ext']),
fund_rate_at_same_time=bool(exchanges_dict['next_funding_at_same_time']),
min_price=float(exchanges_dict['min_price_ext']),
min_order_size=float(exchanges_dict['min_order_size_ext']),
min_lot_size=float(exchanges_dict['min_lot_size_ext']),
@@ -102,6 +111,10 @@ async def symbol_switch(best_symbol_by_exchange_aster: structs.Perpetual_Exchang
logging.info('Balances Flattened - Updating to Trade New Symbols:')
logging.info(f' ASTER.symbol -> {best_symbol_by_exchange_aster.symbol}')
logging.info(f' EXTEND.symbol -> {best_symbol_by_exchange_extend.symbol}')
await aster_cancel_all_orders()
await extend_cancel_all_orders()
Config.Overrides.Flatten_Open_Positions_Opportunistic = False
if Open_Symbols:
logging.info(f'OPEN SYMBOLS: {Open_Symbols}')
@@ -122,9 +135,9 @@ async def symbol_switch(best_symbol_by_exchange_aster: structs.Perpetual_Exchang
VAL_KEY.set(name='fr_algo_working_symbol', value=json.dumps(obj={'ASTER': asdict(obj=Aster), 'EXTEND': asdict(obj=Extend)}))
def calc_fr_minutes_remaining_factor(
min_start_procedure: int = 30,
min_to_end_procedure: int = 7,
factor_exp_pct: float = 0.50
min_start_procedure: int = 15,
min_to_end_procedure: int = 15,
factor_exp_pct: float = 0.25
):
factors = [np.float64(0.00)]
for x in range(min_start_procedure+1,61-min_to_end_procedure):
@@ -229,24 +242,38 @@ def signal_alpha_over_taker(
funding_rate_exch: str,
funding_rate_side: str,
funding_rate: Decimal = Decimal('0.00'),
funding_rate_switch: Decimal = Decimal('0.00'),
taker_fee: Decimal = Decimal(str(0.00025)),
alpha_hurdle_adj: Decimal = Decimal('0.00'),
) -> Signal:
if funding_rate_exch == 'ASTER':
if funding_rate_side == 'BUY':
aster_buy_fund_rate_return = funding_rate*-1
extend_buy_fund_rate_return = funding_rate
else:
aster_buy_fund_rate_return = funding_rate
extend_buy_fund_rate_return = funding_rate*-1
if Config.Overrides.Flatten_Open_Positions_Opportunistic:
if Decimal(str(Aster.notional_position)) > 0:
aster_buy_fund_rate_return = abs(funding_rate_switch) * -1
extend_buy_fund_rate_return = abs(funding_rate_switch)
# funding_rate_exch = 'EXTEND'
# funding_rate_side = 'BUY'
else: # Decimal(str(Aster.notional_position)) < 0:
aster_buy_fund_rate_return = abs(funding_rate_switch)
extend_buy_fund_rate_return = abs(funding_rate_switch) * -1
# funding_rate_exch = 'ASTER'
# funding_rate_side = 'BUY'
else:
if funding_rate_side == 'BUY':
aster_buy_fund_rate_return = funding_rate
extend_buy_fund_rate_return = funding_rate*-1
else:
aster_buy_fund_rate_return = funding_rate*-1
extend_buy_fund_rate_return = funding_rate
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
extend_mid_px: Decimal = ( Decimal(str(extend_ticker_dict['best_ask_px'])) + Decimal(str(extend_ticker_dict['best_bid_px'])) ) / 2
@@ -257,9 +284,19 @@ def signal_alpha_over_taker(
aster_buy_ratio_min_taker_hurdle = ( aster_buy_ratio + aster_buy_fund_rate_return ) - taker_fee - alpha_hurdle_adj
extend_buy_ratio_min_taker_hurdle = ( extend_buy_ratio + extend_buy_fund_rate_return ) - taker_fee - alpha_hurdle_adj
aster_buy_expected_alpha: Decimal = ( aster_buy_ratio_min_taker_hurdle - Decimal(str(Aster.buy_ratio)) ).quantize(Decimal('0.0001'), 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.0001'), 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 $)
# 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'aster_buy_expected_alpha: {aster_buy_ratio_min_taker_hurdle} - {Decimal(str(Aster.buy_ratio))} = {aster_buy_expected_alpha}')
# logging.info(f'extend_buy_expected_alpha: {extend_buy_ratio_min_taker_hurdle} - {Decimal(str(Extend.buy_ratio))} = {extend_buy_expected_alpha}')
# aster_buy_ratio_min_taker_hurdle : ( 0.000886878630659394261895260 + 0.000037000000000000005 ) - 0.00025 - 0.0 = 0.000673878630659394266895260
# extend_buy_ratio_min_taker_hurdle : ( -0.000886878630659394261895260 + -0.000037000000000000005 ) - 0.00025 - 0.0 = -0.001173878630659394266895260
# aster_buy_expected_alpha : 0.000673878630659394266895260 - 0.0014628375 = -0.0007
# extend_buy_expected_alpha : -0.001173878630659394266895260 - -0.0014628375 = 0.0002
if aster_buy_expected_alpha > 0:
signal: bool = True
exchange: str = 'ASTER'
@@ -523,9 +560,11 @@ async def handle_order_updates(exch: str, local_open_orders: list[dict], ws_open
local_open_orders.pop(idx)
# await get_aster_collateral()
if exch=='ASTER':
await aster_cancel_all_orders()
await get_aster_notional_position()
Last_Aster_Fill_Time_Ts = datetime.now().timestamp()*1000
else:
await extend_cancel_all_orders()
await get_extend_notional()
utils.send_tg_alert(f'FR_ALGO - {exch} FILLED ({order_id})')
else:
@@ -772,11 +811,13 @@ async def kill_algo():
await aster_cancel_all_orders()
await extend_cancel_all_orders()
logging.info('ALGO KILL FLAG ACTIVATED; CANCELLING OPEN ORDERS AND SHUTTING DOWN')
await output_algo_status('STOPPED')
raise ValueError('KILL FLAG ACTIVATED')
### ALGO LOOP ###
async def run_algo():
global Config
global Algo_Status
global Aster
global Extend
@@ -846,18 +887,18 @@ async def run_algo():
min_between_fundings = round((abs(aster_fund_rate_time - extend_fund_rate_time) / 1000 / 60))
next_funding_at_same_time = min_between_fundings < 5
def calc_next_net_fund_rate(next_funding_at_same_time: bool) -> tuple[float, str, str]:
def calc_next_net_fund_rate(next_funding_at_same_time: bool, fund_rate_ast: float, fund_rate_ext: float) -> tuple[float, str, str]:
if next_funding_at_same_time:
net_fr = max([aster_fund_rate, extend_fund_rate]) - min([aster_fund_rate, extend_fund_rate])
fr_best_exch = 'ASTER' if max([abs(aster_fund_rate), abs(extend_fund_rate)]) == abs(aster_fund_rate) else 'EXTEND'
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_side = 'BUY' if net_fr < 0 else 'SELL'
return net_fr, fr_best_exch, fr_best_side
else:
fr_best_exch = 'EXTEND'
fr_best_side = 'BUY' if extend_fund_rate < 0 else 'SELL'
return extend_fund_rate, fr_best_exch, fr_best_side
fr_best_side = 'BUY' if fund_rate_ext < 0 else 'SELL'
return fund_rate_ext, fr_best_exch, fr_best_side
next_net_funding_rate, fr_best_exch, fr_best_side = calc_next_net_fund_rate(next_funding_at_same_time)
next_net_funding_rate, fr_best_exch, fr_best_side = calc_next_net_fund_rate(next_funding_at_same_time, fund_rate_ast=aster_fund_rate, fund_rate_ext=extend_fund_rate)
Flags.NET_FUNDING_IS_ZERO = ( next_net_funding_rate >= ( (Config.Config.Min_Fund_Rate_Pct_To_Trade*-1) / 100) ) and ( next_net_funding_rate <= ( Config.Config.Min_Fund_Rate_Pct_To_Trade / 100 ) )
# Tickers
@@ -910,6 +951,11 @@ async def run_algo():
### Decisions ###
fr_factor: Decimal = get_fr_factor_by_minute(time_to_funding_minutes, Funding_Rates_Min_Remaining_Factor_Pcts)
funding_rate_factored = Decimal(str(next_net_funding_rate)) * fr_factor
funding_rate_switch_net, _, _ = calc_next_net_fund_rate(best_symbol_by_exchange_aster.fund_rate_at_same_time, fund_rate_ast=float(best_symbol_by_exchange_aster.initial_funding_rate), fund_rate_ext=float(best_symbol_by_exchange_extend.initial_funding_rate))
funding_rate_switch_net_factored = Decimal(str(funding_rate_switch_net)) * fr_factor
# funding_rate_switch_net_factored = Decimal(str(funding_rate_switch_net)) * Decimal('15')
signal: Signal = signal_alpha_over_taker(
Aster=Aster,
Extend=Extend,
@@ -918,8 +964,10 @@ async def run_algo():
funding_rate_exch=fr_best_exch,
funding_rate_side=fr_best_side,
funding_rate=funding_rate_factored,
funding_rate_switch=funding_rate_switch_net_factored,
alpha_hurdle_adj=Decimal(str(Config.Config.Min_Fund_Rate_Pct_To_Trade)),
)
Algo_Status.expected_alpha = float(signal.expected_alpha)
if signal.signal:
### True signal, standard target
alpha_target_notional = Decimal(str(Config.Config.Max_Target_Notional))
@@ -930,7 +978,6 @@ async def run_algo():
else:
alpha_target_notional = Decimal(str(Aster.notional_position*-1))
### Apply Overrides ###
if signal.exchange == 'ASTER':
aster_tgt = alpha_target_notional
@@ -969,7 +1016,8 @@ async def run_algo():
OUT(f'''
FLIP SIDES FOR TESTING?: {Config.Overrides.Flip_Side_For_Testing}; ASTER ORDER ENABLED? {Config.Overrides.Allow_Ordering_Aster}; EXTEND ORDER ENABLED? {Config.Overrides.Allow_Ordering_Extend}
MKT : Aster: {Aster.symbol:<10} (best: {best_symbol_by_exchange_aster.symbol}) | Extend: {Extend.symbol:<10} (best: {best_symbol_by_exchange_extend.symbol})
MKT : Aster: {Aster.symbol:<10} (best: {best_symbol_by_exchange_aster.symbol}) | Extend: {Extend.symbol:<10} (best: {best_symbol_by_exchange_extend.symbol})
FR SWITCH : {funding_rate_switch_net:.6%} [{funding_rate_switch_net*10_000:.2f}bps] [{funding_rate_switch_net*1_000_000:.0f}pips]
{pd.to_datetime(aster_fund_rate_time, unit='ms')} ({(pd.to_datetime(aster_fund_rate_time, unit='ms')-datetime.now()):}) | {pd.to_datetime(extend_fund_rate_time, unit='ms')} ({(pd.to_datetime(extend_fund_rate_time, unit='ms')-datetime.now()):})
ASTER: {aster_fund_rate:.6%} [{aster_fund_rate*10_000:.2f}bps] [{aster_fund_rate*1_000_000:.0f}pips] | EXTEND: {extend_fund_rate:.6%} [{extend_fund_rate*10_000:.2f}bps] [{extend_fund_rate*1_000_000:.0f}pips]
@@ -982,7 +1030,7 @@ async def run_algo():
ALPHA SIDE : {signal.side:<5} at {signal.exchange}
ALPHA ALPHA : {signal.expected_alpha: .4f} [{signal.expected_alpha*100:.2f}bps] [{signal.expected_alpha*10_000:.2f}pips]; Current {signal.current_ratio:.4f} [{signal.current_ratio*10_000:.2f}scl] {">" if signal.side=='BUY' else "<"} Model {signal.model_ratio:.4f} [{signal.model_ratio*10_000:.2f}scl]
ALPHA ALPHA : {signal.expected_alpha: .6f} [{signal.expected_alpha*100:.2f}bps] [{signal.expected_alpha*10_000:.2f}pips]; Current {signal.current_ratio:.6f} [{signal.current_ratio*10_000:.2f}scl] {">" if signal.side=='BUY' else "<"} Model {signal.model_ratio:.6f} [{signal.model_ratio*10_000:.2f}scl]
ALPHA St.Dev: Buy Ratio Std: {Aster.buy_ratio_std:.4%}
*** ALPHA SIGNAL: {signal.signal} ***
@@ -1131,6 +1179,9 @@ async def run_algo():
cxl_prev_order_id=open_order_id
)
### Output Algo Status ###
await output_algo_status('WORKING')
### CHECK TIME TO FUNDING AND WHETHER TO BE ACTIVE ###
if ( time_to_funding_ms > min_time_to_funding ) and (not Aster_Open_Orders) and (not Extend_Open_Orders):
logging.info(f'Outside action window (minutes) and no active order (sleeping for 5 sec): {pd.to_datetime(time_to_funding_ms, unit='ms').minute} > {pd.to_datetime(min_time_to_funding, unit='ms').minute}')
@@ -1149,11 +1200,13 @@ async def run_algo():
time.sleep(Config.Config.Loop_Sleep_Sec)
except KeyboardInterrupt:
logging.info('CANCELLING OPEN ORDERS')
await output_algo_status('STOPPING')
await kill_algo()
except Exception as e:
logging.error(traceback.format_exc())
logging.critical(f'*** ALGO ENGINE CRASHED: {e}')
logging.info('CANCELLING OPEN ORDERS')
await output_algo_status('STOPPING')
utils.send_tg_alert(f'FR_ALGO_CRASHED: {str(e)}')
await kill_algo()
@@ -1163,12 +1216,12 @@ async def main():
global VAL_KEY
global CON
global Config
global Algo_Status
global Aster
global Extend
global Open_Symbols
global Funding_Rates_Min_Remaining_Factor_Pcts
_, EXTEND_CLIENT = await extend_auth.create_auth_account_and_trading_client()
VAL_KEY = valkey.Valkey(host='localhost', port=6379, db=0, decode_responses=True)
engine = create_async_engine('mysql+asyncmy://root:pwd@localhost/fund_rate')
@@ -1210,6 +1263,12 @@ async def main():
factor_exp_pct = 0.50
)
Algo_Status = structs.Algo_Status(
last_update_ts_ms = int(round(datetime.now().timestamp()*1000, 2)),
status = 'WORKING',
expected_alpha = 0.00,
)
async with engine.connect() as CON:
### ASTER SETUP ###
# await get_aster_collateral()