2026-04-21 20:22:33 +00:00
import asyncio
import json
import logging
import math
import os
import time
import traceback
2026-04-24 07:29:26 +00:00
from dataclasses import asdict , dataclass , field
2026-04-21 20:22:33 +00:00
from datetime import datetime , timezone
2026-04-24 07:29:26 +00:00
from decimal import ROUND_DOWN , Decimal
2026-04-21 20:22:33 +00:00
from typing import AsyncContextManager
2026-04-24 07:29:26 +00:00
from typing import Any
2026-04-21 20:22:33 +00:00
import numpy as np
import pandas as pd
import requests
2026-04-24 07:29:26 +00:00
2026-04-21 20:22:33 +00:00
# import talib
import valkey
2026-04-24 07:29:26 +00:00
from dotenv import load_dotenv
2026-04-21 20:22:33 +00:00
from sqlalchemy import text
from sqlalchemy . ext . asyncio import create_async_engine
2026-04-24 07:29:26 +00:00
from x10 . models . order import OrderSide
import modules . utils as utils
2026-04-23 06:39:51 +00:00
import modules . aster_auth as aster_auth
import modules . extended_auth as extend_auth
2026-04-21 20:22:33 +00:00
### Database ###
2026-04-23 06:39:51 +00:00
EXTEND_CLIENT = None
2026-04-21 20:22:33 +00:00
CON : AsyncContextManager | None = None
VAL_KEY = None
### Logging ###
load_dotenv ( )
LOG_FILEPATH : str = os . getenv ( " LOGS_PATH " ) + ' /Fund_Rate_Algo.log '
2026-04-23 06:39:51 +00:00
### CONSTANTS ###
2026-04-25 03:40:47 +00:00
ASTER_ALLOW_ORDERING : bool = False
EXTEND_ALLOW_ORDERING : bool = False
2026-04-24 07:29:26 +00:00
LOOP_SLEEP_SEC = 1
PRICE_WORSENER_ASTER = 0.00
PRICE_WORSENER_EXTEND = 0.0
MIN_TIME_TO_FUNDING : int = 1000 * 60 * 7 # 5 minutes.
2026-04-23 06:39:51 +00:00
ASTER_LH_ASSET : str = ' ETH '
ASTER_RH_ASSET : str = ' USDT '
ASTER_TICKER : str = ASTER_LH_ASSET + ASTER_RH_ASSET
EXTEND_LH_ASSET : str = ' ETH '
EXTEND_RH_ASSET : str = ' USD '
EXTEND_TICKER : str = EXTEND_LH_ASSET + ' - ' + EXTEND_RH_ASSET
TARGET_OPEN_CASH_POSITION : float = 10 # Each side (alpha and hedge)
### GLOBALS ###
ASTER_MULT = 150
EXTEND_MULT = 50
MAX_TARGET_NOTIONAL = min ( [ ASTER_MULT , EXTEND_MULT ] ) * TARGET_OPEN_CASH_POSITION
ASTER_MIN_ORDER_QTY = 0.001
EXTEND_MIN_ORDER_QTY = 0.01
ASTER_AVAIL_COLLATERAL = 0
EXTEND_AVAIL_COLLATERAL = 0
2026-04-23 17:33:01 +00:00
ASTER_NOTIONAL_POSITION = 0
EXTEND_NOTIONAL_POSITION = 0
2026-04-23 06:39:51 +00:00
ASTER_OPEN_ORDERS = [ ]
EXTEND_OPEN_ORDERS = [ ]
2026-04-23 17:33:01 +00:00
# ASTER_OPEN_POSITIONS = []
# EXTEND_OPEN_POSITIONS = []
2026-04-24 07:29:26 +00:00
@dataclass ( kw_only = True )
class Valkey_Stream :
channel : str
data : Any = None
none_fill : Any = None
async def update ( self ) :
r = VAL_KEY . get ( self . channel )
self . data = json . loads ( r ) if r is not None else self . none_fill
@dataclass ( kw_only = True )
class Position :
market : str
notional : float
qty : float
@dataclass ( kw_only = True )
class Open_Positions :
Valkey : Valkey_Stream
Positions : list [ Position ] = field ( default_factory = list )
async def update ( self ) - > None :
self . Valkey = await self . Valkey . update ( )
### Collateral ###
@dataclass ( kw_only = True )
class Asset :
symbol : str
balance : float
2026-04-25 03:40:47 +00:00
# min_order_qty: float
2026-04-24 07:29:26 +00:00
@dataclass ( kw_only = True )
class Collateral :
Valkey : Valkey_Stream
# Last_Updated_Ts_Ms: int
# Last_Pulled_Ts_Ms: int
Assets : list [ Asset ] = field ( default_factory = list )
async def update ( self ) - > None :
self . Valkey = await self . Valkey . update ( )
### Orders ###
@dataclass ( kw_only = True )
class Order :
symbol : str
order_id : str
client_order_id : str
side : str
order_type : str
original_qty : float
original_price : float
order_status : str
last_filled_qty : float
last_filled_price : float
commission : float
trade_is_maker : bool
@dataclass ( kw_only = True )
class Order_Updates :
# Last_Updated_Ts_Ms: int
# Last_Pulled_Ts_Ms: int
Valkey : Valkey_Stream
Orders : list [ Order ] = field ( default_factory = list )
async def update ( self ) - > None :
self . Valkey = await self . Valkey . update ( )
### Funding Rate ###
@dataclass ( kw_only = True )
class Funding_Rate :
# Last_Updated_Ts_Ms: int
# Last_Pulled_Ts_Ms: int
Valkey : Valkey_Stream
timestamp_arrival : int
timestamp_msg : int
symbol : str
funding_rate : float
next_funding_time_ts_ms : int
mark_price : float
index_price : float
estimated_settle_price : float
async def update ( self ) - > None :
self . Valkey = await self . Valkey . update ( )
2026-04-25 03:40:47 +00:00
### Markets Info ###
@dataclass ( kw_only = True )
class Market :
symbol : str
min_order_qty : float
@dataclass ( kw_only = True )
class Markets_Details :
Markets : list [ Market ] = field ( default_factory = list )
2026-04-24 07:29:26 +00:00
### Exchanges ###
@dataclass ( kw_only = True )
class Perpetual_Exchange :
Order_Updates : Order_Updates
Position_Updates : Open_Positions
2026-04-25 03:40:47 +00:00
Collateral_Updates : Collateral
2026-04-24 07:29:26 +00:00
Funding_Rate : Funding_Rate
2026-04-25 03:40:47 +00:00
Markets : Markets_Details
2026-04-24 07:29:26 +00:00
mult : int
2026-04-25 03:40:47 +00:00
lh_asset : str
rh_asset : str
symbol_asset_separator : str = ' '
symbol : str
2026-04-24 07:29:26 +00:00
async def update ( self ) :
2026-04-25 03:40:47 +00:00
await self . Collateral_Updates . update ( )
await self . Order_Updates . update ( )
await self . Position_Updates . update ( )
2026-04-24 07:29:26 +00:00
await self . Funding_Rate . update ( )
2026-04-25 03:40:47 +00:00
def __post_init__ ( self ) - > None :
self . symbol = f ' { self . lh_asset . upper ( ) } { self . symbol_asset_separator } { self . rh_asset . upper ( ) } '
2026-04-24 07:29:26 +00:00
@dataclass ( kw_only = True )
class Aster ( Perpetual_Exchange ) :
name : str = ' Aster '
2026-04-25 03:40:47 +00:00
lh_asset : str = ' ETH '
rh_asset : str = ' USDT '
2026-04-24 07:29:26 +00:00
def __post_init__ ( self ) :
2026-04-25 03:40:47 +00:00
super ( ) . __post_init__ ( )
self . Order_Updates = Order_Updates ( Valkey = Valkey_Stream ( channel = ' fr_aster_user_balances ' , none_fills = [ ] ) )
self . Collateral_Updates = Collateral ( Valkey = Valkey_Stream ( channel = ' fr_aster_user_orders ' , none_fills = [ ] ) )
self . Position_Updates = Open_Positions ( Valkey = Valkey_Stream ( channel = ' fr_aster_user_positions ' , none_fills = [ ] ) )
2026-04-24 07:29:26 +00:00
self . Funding_Rate - Funding_Rate ( Valkey = Valkey_Stream ( channel = ' fund_rate_aster ' , none_fills = None ) )
2026-04-25 03:40:47 +00:00
@dataclass ( kw_only = True )
class Extend ( Perpetual_Exchange ) :
name : str = ' Extended '
lh_asset : str = ' ETH '
rh_asset : str = ' USD '
symbol_asset_separator : str = ' - '
2026-04-24 07:29:26 +00:00
2026-04-25 03:40:47 +00:00
def __post_init__ ( self ) :
super ( ) . __post_init__ ( )
self . Order_Updates = Order_Updates ( Valkey = Valkey_Stream ( channel = ' fr_aster_user_balances ' , none_fills = [ ] ) )
self . Collateral_Updates = Collateral ( Valkey = Valkey_Stream ( channel = ' fr_aster_user_orders ' , none_fills = [ ] ) )
self . Position_Updates = Open_Positions ( Valkey = Valkey_Stream ( channel = ' fr_aster_user_positions ' , none_fills = [ ] ) )
self . Funding_Rate - Funding_Rate ( Valkey = Valkey_Stream ( channel = ' fund_rate_aster ' , none_fills = None ) )
2026-04-24 07:29:26 +00:00
2026-04-25 03:40:47 +00:00
# EXCHANGES: list = [ Aster(), Extend() ]
2026-04-24 07:29:26 +00:00
2026-04-23 06:39:51 +00:00
### FLAGS ###
2026-04-24 07:29:26 +00:00
@dataclass ( kw_only = True )
class Flags :
LIQUIDATE_POS_AND_KILL_ALGO_FLAG : bool = False
NET_FUNDING_IS_ZERO : bool = False
Flags = Flags ( )
2026-04-23 06:39:51 +00:00
2026-04-23 17:33:01 +00:00
### UTILS ###
def round_decimal_down ( value , decimal_places ) :
# Construct precision string like '0.01' for 2 places
fmt = f ' 0. { " 0 " * decimal_places } ' if decimal_places > 0 else ' 0 '
precision = Decimal ( fmt )
return Decimal ( str ( value ) ) . quantize ( precision , rounding = ROUND_DOWN )
2026-04-23 16:34:47 +00:00
### OPEN ORDERS ###
async def get_aster_open_orders ( ) :
global ASTER_OPEN_ORDERS
fut_acct_openOrders = {
" url " : " /fapi/v3/openOrders " ,
" method " : " GET " ,
" params " : { }
}
2026-04-24 07:29:26 +00:00
ASTER_OPEN_ORDERS = await aster_auth . post_authenticated_url ( fut_acct_openOrders )
2026-04-23 16:34:47 +00:00
async def get_extend_open_orders ( ) :
global EXTEND_OPEN_ORDERS
EXTEND_OPEN_ORDERS = list ( dict ( await EXTEND_CLIENT . account . get_open_orders ( ) ) . get ( ' data ' , 0 ) )
### WALLLET ###
async def get_aster_collateral ( ) :
global ASTER_AVAIL_COLLATERAL
fut_acct_balances = {
" url " : " /fapi/v3/balance " ,
" method " : " GET " ,
" params " : { }
}
2026-04-24 07:29:26 +00:00
r = await aster_auth . post_authenticated_url ( fut_acct_balances )
2026-04-23 16:34:47 +00:00
ASTER_AVAIL_COLLATERAL = float ( [ d for d in r if d . get ( ' asset ' ) == ASTER_RH_ASSET ] [ 0 ] . get ( ' availableBalance ' ) )
2026-04-24 07:29:26 +00:00
async def get_aster_notional_position ( resp : dict | None = None ) :
2026-04-23 16:34:47 +00:00
global ASTER_NOTIONAL_POSITION
global ASTER_MULT
2026-04-24 07:29:26 +00:00
if not resp :
fut_acct_positionRisk = {
" url " : " /fapi/v3/positionRisk " ,
" method " : " GET " ,
" params " : {
' symbol ' : ASTER_TICKER ,
}
}
resp = await aster_auth . post_authenticated_url ( fut_acct_positionRisk )
d = [ x for x in resp if x . get ( ' symbol ' , None ) == ASTER_TICKER ] [ 0 ]
if len ( d ) < 1 :
logging . info ( f ' BAD NOTIONAL - ASTER CHANGE: Empty d: { d } ; resp: { resp } ' )
kill_algo ( )
2026-04-23 16:34:47 +00:00
2026-04-24 07:29:26 +00:00
aster_unrealized_pnl = float ( d [ ' unrealized_pnl ' ] ) if d . get ( ' unrealized_pnl ' ) is not None else float ( d [ ' unRealizedProfit ' ] )
if d . get ( ' notional ' ) is not None :
notional = float ( d [ ' notional ' ] )
else :
notional = float ( d [ ' position_amount ' ] ) * float ( d [ ' entry_price ' ] )
previous_notional_position = ASTER_NOTIONAL_POSITION
ASTER_NOTIONAL_POSITION = notional - aster_unrealized_pnl
if not resp :
ASTER_MULT = float ( d [ ' leverage ' ] )
if abs ( ASTER_NOTIONAL_POSITION ) > MAX_TARGET_NOTIONAL * 1.01 :
logging . info ( f ' BAD NOTIONAL - ASTER CHANGE: { ASTER_NOTIONAL_POSITION } ; UR PNL: { aster_unrealized_pnl } ; MULT: { ASTER_MULT } ; d: { d } ; resp: { resp } ' )
kill_algo ( )
if ASTER_NOTIONAL_POSITION != previous_notional_position :
logging . info ( f ' ASTER NOTIONAL CHANGE: { ASTER_NOTIONAL_POSITION : .2f } ; UR PNL: { aster_unrealized_pnl : .2f } ; MULT: { ASTER_MULT : .0f } ; resp: { bool ( resp ) } ' )
2026-04-23 16:34:47 +00:00
async def get_extend_collateral ( ) :
global EXTEND_AVAIL_COLLATERAL
get_bals = dict ( dict ( await EXTEND_CLIENT . account . get_balance ( ) ) . get ( ' data ' , { } ) )
EXTEND_AVAIL_COLLATERAL = get_bals . get ( ' available_for_trade ' , 0 ) if get_bals . get ( ' collateral_name ' , None ) == EXTEND_RH_ASSET else 0
2026-04-24 07:29:26 +00:00
async def get_extend_notional ( resp : dict | None = None ) :
2026-04-23 16:34:47 +00:00
global EXTEND_NOTIONAL_POSITION
global EXTEND_MULT
2026-04-24 07:29:26 +00:00
if not resp :
resp = dict ( await EXTEND_CLIENT . account . get_positions ( ) ) . get ( ' data ' , { } )
pos_dict = [ dict ( d ) for d in resp if dict ( d ) . get ( ' market ' ) == EXTEND_TICKER ]
pos_dict = pos_dict [ 0 ]
unrealized_pnl = pos_dict . get ( ' unrealised_pnl ' , 0 )
previous_notional_position = EXTEND_NOTIONAL_POSITION
EXTEND_NOTIONAL_POSITION = float ( pos_dict . get ( ' value ' , 0 ) ) - float ( unrealized_pnl )
EXTEND_MULT = pos_dict . get ( ' leverage ' , EXTEND_MULT )
if EXTEND_NOTIONAL_POSITION != previous_notional_position :
logging . info ( f ' EXTEND NOTIONAL CHANGE: { EXTEND_NOTIONAL_POSITION : .2f } ; UR PNL: { unrealized_pnl : .2f } ; MULT: { EXTEND_MULT : .0f } ; resp: { bool ( resp ) } ' )
2026-04-23 16:34:47 +00:00
### EXCHANGE INFO ###
async def get_aster_exch_info ( ) :
global ASTER_MIN_ORDER_QTY
fut_acct_exchangeInfo = {
" url " : " /fapi/v3/exchangeInfo " ,
" method " : " GET " ,
" params " : { }
}
2026-04-24 07:29:26 +00:00
r = await aster_auth . post_authenticated_url ( fut_acct_exchangeInfo )
2026-04-23 16:34:47 +00:00
s = r [ ' symbols ' ]
d = [ d for d in s if d . get ( ' symbol ' , None ) == ' ETHUSDT ' ] [ 0 ]
f = [ f for f in d [ ' filters ' ] if f . get ( ' filterType ' , None ) == ' LOT_SIZE ' ] [ 0 ]
ASTER_MIN_ORDER_QTY = float ( f [ ' minQty ' ] )
async def get_extend_exch_info ( ) :
global EXTEND_MIN_ORDER_QTY
r = await EXTEND_CLIENT . markets_info . get_markets_dict ( )
EXTEND_MIN_ORDER_QTY = float ( r [ ' ETH-USD ' ] . trading_config . min_order_size )
2026-04-23 17:33:01 +00:00
### CANCEL ORDERS ###
async def aster_cancel_all_orders ( ) :
2026-04-24 07:29:26 +00:00
cancel_all_open_orders = {
" url " : " /fapi/v3/allOpenOrders " ,
" method " : " DELETE " ,
" params " : {
' symbol ' : ' ETHUSDT ' ,
}
}
r = await aster_auth . post_authenticated_url ( cancel_all_open_orders )
logging . info ( f ' ASTER CANCEL ALL OPEN ORDERS RESP: { r } ' )
async def extend_cancel_all_orders ( ) :
r = await EXTEND_CLIENT . orders . mass_cancel ( markets = [ EXTEND_TICKER ] )
logging . info ( f ' EXTEND CANCEL ALL OPEN ORDERS RESP: { r } ' )
2026-04-23 17:33:01 +00:00
2026-04-24 07:29:26 +00:00
### KILL ALGO ###
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 ' )
raise ValueError ( ' KILL FLAG ACTIVATED ' )
2026-04-23 17:33:01 +00:00
2026-04-23 16:34:47 +00:00
### ROUTES ###
2026-04-24 07:29:26 +00:00
# async def aster_remainder_route():
# # Check open orders...cancel replace or new order?
# # Check collateral to confirm you have enough money to trade
# # if CR, what should be the new price? has it changed? maybe no action needed? how long has it been working?
# # if not enough collateral then need to liquidate and kill algo - flip flag
2026-04-23 06:39:51 +00:00
2026-04-24 07:29:26 +00:00
# # if good to order, then create and post order. ADD to LOCAL OPEN ORDERS LIST
2026-04-21 20:22:33 +00:00
2026-04-24 07:29:26 +00:00
# pass
2026-04-23 06:39:51 +00:00
2026-04-24 07:29:26 +00:00
# async def extend_remainder_route():
# pass
2026-04-23 06:39:51 +00:00
2026-04-23 16:34:47 +00:00
### ALGO LOOP ###
2026-04-23 06:39:51 +00:00
async def run_algo ( ) :
2026-04-24 07:29:26 +00:00
2026-04-21 20:22:33 +00:00
try :
while True :
loop_start = time . time ( )
print ( ' __________Start___________ ' )
2026-04-24 07:29:26 +00:00
### Load Data from Feedhandlers ###
2026-04-23 03:11:52 +00:00
ASTER_FUND_RATE_DICT = json . loads ( VAL_KEY . get ( ' fund_rate_aster ' ) )
EXTENDED_FUND_RATE_DICT = json . loads ( VAL_KEY . get ( ' fund_rate_extended ' ) )
2026-04-24 07:29:26 +00:00
2026-04-23 03:11:52 +00:00
ASTER_FUND_RATE = float ( ASTER_FUND_RATE_DICT . get ( ' funding_rate ' , 0 ) )
EXTEND_FUND_RATE = float ( EXTENDED_FUND_RATE_DICT . get ( ' funding_rate ' , 0 ) )
2026-04-23 06:39:51 +00:00
ASTER_FUND_RATE_TIME = float ( ASTER_FUND_RATE_DICT . get ( ' next_funding_time_ts_ms ' , 0 ) )
EXTEND_FUND_RATE_TIME = float ( EXTENDED_FUND_RATE_DICT . get ( ' next_funding_time_ts_ms ' , 0 ) )
2026-04-24 07:29:26 +00:00
ASTER_TICKER_DICT = json . loads ( VAL_KEY . get ( ' fut_ticker_aster ' ) )
EXTENDED_TICKER_DICT = json . loads ( VAL_KEY . get ( ' fut_ticker_extended ' ) )
### Manage Local Collateral Using Updates from WS ###
ASTER_WS_COLLATERAL_UPDATES = VAL_KEY . get ( ' fr_aster_user_positions ' )
ASTER_WS_COLLATERAL_UPDATES = json . loads ( ASTER_WS_COLLATERAL_UPDATES ) if ASTER_WS_COLLATERAL_UPDATES is not None else [ ]
EXTEND_WS_COLLATERAL_UPDATES = VAL_KEY . get ( ' fr_extended_user_positions ' )
EXTEND_WS_COLLATERAL_UPDATES = json . loads ( EXTEND_WS_COLLATERAL_UPDATES ) if EXTEND_WS_COLLATERAL_UPDATES is not None else [ ]
### Manage Local Notionals Using Updates from WS ###
ASTER_WS_POS_UPDATES = VAL_KEY . get ( ' fr_aster_user_positions ' )
ASTER_WS_POS_UPDATES = json . loads ( ASTER_WS_POS_UPDATES ) if ASTER_WS_POS_UPDATES is not None else [ ]
EXTEND_WS_POS_UPDATES = VAL_KEY . get ( ' fr_extended_user_positions ' )
EXTEND_WS_POS_UPDATES = json . loads ( EXTEND_WS_POS_UPDATES ) if EXTEND_WS_POS_UPDATES is not None else [ ]
### Manage Local Orders Using Updates from WS ###
ASTER_WS_ORDER_UPDATES = VAL_KEY . get ( ' fr_aster_user_orders ' )
ASTER_WS_ORDER_UPDATES = json . loads ( ASTER_WS_ORDER_UPDATES ) if ASTER_WS_ORDER_UPDATES is not None else [ ]
EXTEND_WS_ORDER_UPDATES = VAL_KEY . get ( ' fr_extended_user_orders ' )
EXTEND_WS_ORDER_UPDATES = json . loads ( EXTEND_WS_ORDER_UPDATES ) if EXTEND_WS_ORDER_UPDATES is not None else [ ]
# CHECK NO MORE THAN 1 OPEN ORDER ON EITHER EXCHANGE #
if len ( ASTER_OPEN_ORDERS ) > 1 or len ( EXTEND_OPEN_ORDERS ) > 1 :
logging . info ( f ' MORE THAN 1 ORDER OPEN - KILLING ALGO: ASTER_OPEN_ORDERS ( { len ( ASTER_OPEN_ORDERS ) } ): { ASTER_OPEN_ORDERS } ; EXTEND_OPEN_ORDERS ( { len ( EXTEND_OPEN_ORDERS ) } ): { EXTEND_OPEN_ORDERS } ' )
await kill_algo ( )
raise ValueError ( ' NOT HERE: MORE THAN 1 ORDER OPEN - KILLING ALGO: ASTER_OPEN_ORDERS ' )
### CHECK TIME TO FUNDING AND WHETHER TO BE ACTIVE ###
now_ms = round ( datetime . now ( ) . timestamp ( ) * 1000 )
time_to_funding_ms = min ( [ ASTER_FUND_RATE_TIME , EXTEND_FUND_RATE_TIME ] ) - now_ms
if ( time_to_funding_ms > MIN_TIME_TO_FUNDING ) and ( not ASTER_OPEN_ORDERS ) and ( not EXTEND_OPEN_ORDERS ) :
print ( 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 } ' )
time . sleep ( 5 )
continue
if len ( ASTER_WS_POS_UPDATES ) > 0 :
await get_aster_notional_position ( resp = ASTER_WS_POS_UPDATES )
###### *** returned 0 notional even though had a position, need to handle and safety check to not order above max notional.
if len ( EXTEND_WS_POS_UPDATES ) > 0 :
await get_extend_notional ( resp = EXTEND_WS_POS_UPDATES )
if ASTER_WS_ORDER_UPDATES is not None :
for idx , o in enumerate ( ASTER_OPEN_ORDERS ) :
order_id = o . get ( ' order_id ' ) if o . get ( ' order_id ' ) is not None else o . get ( ' orderId ' )
order_orig_status = o [ ' status ' ]
order_update = [ ou for ou in ASTER_WS_ORDER_UPDATES if ou . get ( ' order_id ' , None ) == order_id ]
if len ( order_update ) > 0 :
order_update = order_update [ 0 ]
order_update_status = order_update . get ( ' status ' ) if order_update . get ( ' status ' ) is not None else order_update . get ( ' order_status ' )
order_status_changed = order_orig_status . upper ( ) != order_update_status . upper ( )
if order_status_changed :
logging . info ( f ' ASTER ORDER ( { order_id } ): { order_orig_status } -> { order_update_status } ' )
ASTER_OPEN_ORDERS [ idx ] = order_update
if order_update_status in [ ' CANCELED ' , ' EXPIRED ' ] :
logging . info ( f ' ASTER ORDER CANCELLED or EXPIRED: { order_id } ' )
ASTER_OPEN_ORDERS . pop ( idx )
elif order_update_status in [ ' PARTIALLY_FILLED ' ] :
logging . info ( f ' ASTER ORDER PARTIALLY FILLED: { order_id } ' )
await get_aster_collateral ( )
await get_aster_notional_position ( )
elif order_update_status in [ ' FILLED ' ] :
logging . info ( f ' ASTER ORDER FILLED: { order_id } ' )
ASTER_OPEN_ORDERS . pop ( idx )
await get_aster_collateral ( )
await get_aster_notional_position ( )
else :
logging . critical ( f ' EXTEND ORDER STATUS CHG TO UNEXPECTED VALUE, KILLING... ( { order_id } ): { order_orig_status } -> { order_update_status } ' )
if EXTEND_WS_ORDER_UPDATES is not None :
for idx , o in enumerate ( EXTEND_OPEN_ORDERS ) :
o = dict ( o )
order_id = o . get ( ' order_id ' ) if o . get ( ' order_id ' ) is not None else o . get ( ' id ' )
order_orig_status = o [ ' status ' ]
order_update = [ dict ( ou ) for ou in EXTEND_WS_ORDER_UPDATES if dict ( ou ) . get ( ' order_id ' , None ) == order_id ]
if len ( order_update ) > 0 :
order_update = order_update [ 0 ]
order_update_status = order_update . get ( ' status ' )
order_status_changed = order_orig_status . upper ( ) != order_update_status . upper ( )
if order_status_changed :
logging . info ( f ' EXTEND ORDER ( { order_id } ): { order_orig_status } -> { order_update_status } ' )
EXTEND_OPEN_ORDERS [ idx ] = order_update
if order_update_status in [ ' CANCELLED ' , ' EXPIRED ' , ' REJECTED ' ] :
logging . info ( f ' EXTEND ORDER CANCELLED or EXPIRED: { order_id } ' )
EXTEND_OPEN_ORDERS . pop ( idx )
elif order_update_status in [ ' PARTIALLY_FILLED ' ] :
logging . info ( f ' EXTEND ORDER PARTIALLY FILLED: { order_id } ' )
await get_extend_collateral ( )
await get_extend_notional ( )
elif order_update_status in [ ' FILLED ' ] :
logging . info ( f ' EXTEND ORDER FILLED: { order_id } ' )
EXTEND_OPEN_ORDERS . pop ( idx )
await get_extend_collateral ( )
await get_extend_notional ( )
else :
logging . critical ( f ' EXTEND ORDER STATUS CHG TO UNEXPECTED VALUE, KILLING... ( { order_id } ): { order_orig_status } -> { order_update_status } ' )
2026-04-23 06:39:51 +00:00
ASTER_PAYOUT_DIRECTION_STR = ' LONG PAYS SHORT ' if ASTER_FUND_RATE > 0 else ' SHORT PAYS LONG '
EXTEND_PAYOUT_DIRECTION_STR = ' LONG PAYS SHORT ' if EXTEND_FUND_RATE > 0 else ' SHORT PAYS LONG '
2026-04-23 17:33:01 +00:00
min_between_fundings = round ( ( abs ( ASTER_FUND_RATE_TIME - EXTEND_FUND_RATE_TIME ) / 1000 / 60 ) )
FUNDINGS_AT_SAME_TIME_NEXT_HR = min_between_fundings < 5
# FUNDINGS_AT_SAME_TIME_NEXT_HR = ( (ASTER_FUND_RATE_TIME < 60*60*1000) and (EXTEND_FUND_RATE < 60*60*1000) )
2026-04-23 06:39:51 +00:00
if ( abs ( ASTER_FUND_RATE ) > abs ( EXTEND_FUND_RATE ) ) and FUNDINGS_AT_SAME_TIME_NEXT_HR :
ALPHA_EXCH = ' ASTER '
ALPHA_FUND_RATE = ASTER_FUND_RATE
else :
ALPHA_EXCH = ' EXTEND '
ALPHA_FUND_RATE = EXTEND_FUND_RATE
if ALPHA_FUND_RATE < 0 :
ALPHA_CARRY_SIDE = ' BUY '
ALPHA_TGT_NOTIONAL = MAX_TARGET_NOTIONAL
else :
ALPHA_CARRY_SIDE = ' SELL '
ALPHA_TGT_NOTIONAL = MAX_TARGET_NOTIONAL * - 1
def calc_next_net_fund_rate ( FUNDINGS_AT_SAME_TIME_NEXT_HR : bool ) - > float :
if FUNDINGS_AT_SAME_TIME_NEXT_HR :
return ASTER_FUND_RATE + EXTEND_FUND_RATE
else :
return EXTEND_FUND_RATE
NEXT_NET_FUNDING_RATE = calc_next_net_fund_rate ( FUNDINGS_AT_SAME_TIME_NEXT_HR )
2026-04-24 07:29:26 +00:00
Flags . NET_FUNDING_IS_ZERO = NEXT_NET_FUNDING_RATE == 0.00
if Flags . NET_FUNDING_IS_ZERO :
2026-04-25 03:40:47 +00:00
logging . info ( ' NET FUNDING = 0.00; Cancelling Open Orders; Wait Until Non-Zero. ' )
2026-04-24 07:29:26 +00:00
ALPHA_TGT_NOTIONAL = 0.00
2026-04-23 06:39:51 +00:00
if ALPHA_EXCH == ' EXTEND ' :
ASTER_TGT_NOTIONAL = ALPHA_TGT_NOTIONAL * - 1
EXTEND_TGT_NOTIONAL = ALPHA_TGT_NOTIONAL
2026-04-23 17:33:01 +00:00
if ALPHA_CARRY_SIDE == ' BUY ' :
ASTER_TOB_PX = float ( ASTER_TICKER_DICT [ ' best_ask_px ' ] )
EXTEND_TOB_PX = float ( EXTENDED_TICKER_DICT [ ' best_bid_px ' ] )
else :
ASTER_TOB_PX = float ( ASTER_TICKER_DICT [ ' best_bid_px ' ] )
EXTEND_TOB_PX = float ( EXTENDED_TICKER_DICT [ ' best_ask_px ' ] )
2026-04-23 06:39:51 +00:00
else :
ASTER_TGT_NOTIONAL = ALPHA_TGT_NOTIONAL
EXTEND_TGT_NOTIONAL = ALPHA_TGT_NOTIONAL * - 1
2026-04-23 17:33:01 +00:00
if ALPHA_CARRY_SIDE == ' BUY ' :
ASTER_TOB_PX = float ( ASTER_TICKER_DICT [ ' best_bid_px ' ] )
EXTEND_TOB_PX = float ( EXTENDED_TICKER_DICT [ ' best_ask_px ' ] )
else :
ASTER_TOB_PX = float ( ASTER_TICKER_DICT [ ' best_ask_px ' ] )
EXTEND_TOB_PX = float ( EXTENDED_TICKER_DICT [ ' best_bid_px ' ] )
2026-04-23 06:39:51 +00:00
ASTER_TGT_TAIL = ASTER_TGT_NOTIONAL - ASTER_NOTIONAL_POSITION
EXTEND_TGT_TAIL = EXTEND_TGT_NOTIONAL - EXTEND_NOTIONAL_POSITION
2026-04-24 07:29:26 +00:00
ASTER_TGT_TAIL_BASE_QTY = Decimal ( str ( float ( ASTER_TGT_TAIL ) / float ( ASTER_TOB_PX ) ) ) . quantize ( Decimal ( str ( 0.001 ) ) , rounding = ROUND_DOWN )
EXTEND_TGT_TAIL_BASE_QTY = Decimal ( str ( float ( EXTEND_TGT_TAIL ) / float ( EXTEND_TOB_PX ) ) ) . quantize ( Decimal ( str ( 0.001 ) ) , rounding = ROUND_DOWN )
2026-04-23 17:33:01 +00:00
ASTER_TGT_TAIL_ORDERABLE = abs ( ASTER_TGT_TAIL_BASE_QTY ) > = ASTER_MIN_ORDER_QTY
EXTEND_TGT_TAIL_ORDERABLE = abs ( EXTEND_TGT_TAIL_BASE_QTY ) > = EXTEND_MIN_ORDER_QTY
2026-04-23 03:11:52 +00:00
print ( f '''
2026-04-23 06:39:51 +00:00
{ 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 ( ) ) : } )
2026-04-23 17:33:01 +00:00
ASTER : { ASTER_FUND_RATE : .6 % } [ { ASTER_FUND_RATE * 10_000 : .2 f } bps ] [ { ASTER_FUND_RATE * 1_000_000 : .0 f } pips ] | EXTEND : { EXTEND_FUND_RATE : .6 % } [ { EXTEND_FUND_RATE * 10_000 : .2 f } bps ] [ { EXTEND_FUND_RATE * 1_000_000 : .0 f } pips ]
2026-04-23 06:39:51 +00:00
ASTER : { ASTER_PAYOUT_DIRECTION_STR } | EXTEND : { EXTEND_PAYOUT_DIRECTION_STR }
ASTER : [ Available Collateral : { ASTER_AVAIL_COLLATERAL : .4 f } ] | EXTEND : [ Available Collateral : { EXTEND_AVAIL_COLLATERAL : .4 f } ]
2026-04-24 07:29:26 +00:00
ASTER : [ Notional Position $ : { ASTER_NOTIONAL_POSITION : .4 f } ] | EXTEND : [ Notional Position $ : { EXTEND_NOTIONAL_POSITION : .4 f } ]
2026-04-23 06:39:51 +00:00
2026-04-23 17:33:01 +00:00
SAME TIME ? : { FUNDINGS_AT_SAME_TIME_NEXT_HR } [ Minutes Between Fundings : { min_between_fundings } ]
2026-04-24 07:29:26 +00:00
NET FUNDING : { NEXT_NET_FUNDING_RATE : .6 % } [ { NEXT_NET_FUNDING_RATE * 10_000 : .2 f } bps ] [ { NEXT_NET_FUNDING_RATE * 1_000_000 : .0 f } pips ] ; Is Zero ? : { Flags . NET_FUNDING_IS_ZERO }
2026-04-23 06:39:51 +00:00
ALPHA SIDE : { ALPHA_EXCH } [ { ALPHA_CARRY_SIDE } ]
2026-04-24 07:29:26 +00:00
TGT NOTIONAL : $ { MAX_TARGET_NOTIONAL if not Flags . NET_FUNDING_IS_ZERO else 0.00 }
ASTER : { ASTER_NOTIONAL_POSITION : .4 f } - > { ASTER_TGT_NOTIONAL : .2 f } [ Remain : { ASTER_TGT_TAIL : .4 f } ] | EXTEND : { EXTEND_NOTIONAL_POSITION : .4 f } - > { EXTEND_TGT_NOTIONAL : .2 f } [ Remain : { EXTEND_TGT_TAIL : .4 f } ]
2026-04-25 03:40:47 +00:00
ASTER : { ASTER_TGT_NOTIONAL : .4 f } - { ASTER_NOTIONAL_POSITION : .4 f } = Tail : { ASTER_TGT_TAIL : 4 f } | EXTEND : { EXTEND_TGT_NOTIONAL : .4 f } - { EXTEND_NOTIONAL_POSITION : .4 f } = Tail : { EXTEND_TGT_TAIL : 4 f }
2026-04-24 07:29:26 +00:00
ASTER : { ASTER_TGT_TAIL_BASE_QTY : .4 f } > { ASTER_MIN_ORDER_QTY : .4 f } min [ Order : { ASTER_TGT_TAIL_ORDERABLE } ] | EXTEND : { EXTEND_TGT_TAIL_BASE_QTY : .4 f } > { EXTEND_MIN_ORDER_QTY : .4 f } min [ Order : { EXTEND_TGT_TAIL_ORDERABLE } ]
2026-04-23 06:39:51 +00:00
2026-04-24 07:29:26 +00:00
- - - ASTER OPEN ORDERS - - -
{ ASTER_OPEN_ORDERS }
2026-04-23 06:39:51 +00:00
2026-04-24 07:29:26 +00:00
- - - EXTEND OPEN ORDERS - - -
{ EXTEND_OPEN_ORDERS }
2026-04-23 03:11:52 +00:00
''' )
2026-04-21 20:22:33 +00:00
2026-04-23 06:39:51 +00:00
### ROUTES ###
2026-04-24 07:29:26 +00:00
# ASTER
if ASTER_TGT_TAIL_ORDERABLE and ASTER_ALLOW_ORDERING :
symbol = ASTER_TICKER
side = ' BUY ' if ASTER_TGT_TAIL_BASE_QTY > 0.00 else ' SELL '
qty = str ( abs ( ASTER_TGT_TAIL_BASE_QTY ) )
price = ASTER_TOB_PX - PRICE_WORSENER_ASTER if side == ' BUY ' else ASTER_TOB_PX + PRICE_WORSENER_ASTER
2026-04-25 03:40:47 +00:00
if abs ( abs ( float ( ASTER_TGT_TAIL_BASE_QTY ) ) * float ( price ) ) + abs ( ASTER_NOTIONAL_POSITION ) > MAX_TARGET_NOTIONAL * 1.01 :
pass
logging . info ( f ' TRYING TO ORDER OVER MAX NOTIOANL - ASTER: { ASTER_NOTIONAL_POSITION } + { float ( ASTER_TGT_TAIL_BASE_QTY ) * float ( price ) } (qty: { float ( ASTER_TGT_TAIL_BASE_QTY ) : .2f } ; px: { float ( price ) : .2f } ) ' )
2026-04-24 07:29:26 +00:00
# await aster_remainder_route()
if ASTER_OPEN_ORDERS :
open_order_id = ASTER_OPEN_ORDERS [ 0 ] . get ( ' order_id ' ) if ASTER_OPEN_ORDERS [ 0 ] . get ( ' order_id ' ) is not None else ASTER_OPEN_ORDERS [ 0 ] [ ' orderId ' ]
open_order_px = float ( ASTER_OPEN_ORDERS [ 0 ] . get ( ' price ' ) ) if ASTER_OPEN_ORDERS [ 0 ] . get ( ' price ' ) is not None else float ( ASTER_OPEN_ORDERS [ 0 ] [ ' original_price ' ] )
if round ( open_order_px - float ( price ) , 2 ) == 0.00 :
logging . info ( ' ASTER OPEN ORDER NO PX CHG; SKIPPING ' )
place_order = False
else :
cancel_order = {
" url " : " /fapi/v3/order " ,
" method " : " DELETE " ,
" params " : {
' symbol ' : ASTER_TICKER ,
' orderId ' : open_order_id ,
}
}
cr = await aster_auth . post_authenticated_url ( cancel_order )
if cr . get ( ' status ' , None ) == ' CANCELED ' :
ASTER_OPEN_ORDERS . pop ( 0 )
place_order = True
else :
logging . warning ( f ' ASTER ORDER FAILED TO CANCEL DURING CR ( { open_order_id } ): RESP { cr } ' )
place_order = False
else :
place_order = True
if ASTER_TGT_TAIL_BASE_QTY == 0.00 :
place_order = False
logging . info ( ' ASTER TRYNG TO ORDER 0.00 BASE QTY, SKIPPING ' )
if place_order :
price = Decimal ( str ( price ) ) . quantize ( Decimal ( str ( 0.01 ) ) , rounding = ROUND_DOWN )
post_order = {
" url " : " /fapi/v3/order " ,
" method " : " POST " ,
" params " : {
' symbol ' : symbol ,
' side ' : side ,
' type ' : ' LIMIT ' ,
' timeInForce ' : ' GTC ' ,
' quantity ' : qty ,
' price ' : price ,
}
}
order_resp = await aster_auth . post_authenticated_url ( post_order )
if order_resp . get ( ' orderId ' , None ) is not None :
order_resp [ ' original_price ' ] = price
ASTER_OPEN_ORDERS . append ( order_resp )
2026-04-25 03:40:47 +00:00
utils . send_tg_alert ( f ' FR_ALGO - ASTER Order. Start_$: { ASTER_NOTIONAL_POSITION : .2f } ; Value: { float ( ASTER_TGT_TAIL_BASE_QTY ) * float ( price ) : .2f } ; Price: { float ( price ) : .2f } ' )
2026-04-24 07:29:26 +00:00
logging . info ( f ' ASTER ORDER PLACED SUCCESS: { order_resp } ' )
else :
logging . warning ( ' ASTER PLACE ORDER CHECKS FAILED, SKIPPING ' )
2026-04-23 17:33:01 +00:00
2026-04-24 07:29:26 +00:00
elif not ( ASTER_TGT_TAIL_ORDERABLE ) and ASTER_OPEN_ORDERS :
logging . info ( ' ASTER HAS NO TAIL BUT OPEN ORDERS - CANCELLING OPEN ORDERS ' )
await aster_cancel_all_orders ( )
# EXTEND
if EXTEND_TGT_TAIL_ORDERABLE and EXTEND_ALLOW_ORDERING :
symbol = EXTEND_TICKER
side = OrderSide . BUY if EXTEND_TGT_TAIL_BASE_QTY > 0.00 else OrderSide . SELL
qty = Decimal ( str ( abs ( EXTEND_TGT_TAIL_BASE_QTY ) ) )
price = EXTEND_TOB_PX - PRICE_WORSENER_EXTEND if side == ' BUY ' else EXTEND_TOB_PX + PRICE_WORSENER_EXTEND
2026-04-25 03:40:47 +00:00
if abs ( float ( EXTEND_TGT_TAIL_BASE_QTY ) * float ( price ) ) + abs ( float ( EXTEND_NOTIONAL_POSITION ) ) > MAX_TARGET_NOTIONAL * 1.01 :
logging . info ( f ' TRYING TO ORDER OVER MAX NOTIOANL - EXTEND: { EXTEND_NOTIONAL_POSITION : .2f } + { float ( EXTEND_TGT_TAIL_BASE_QTY ) * float ( price ) : .2f } (qty: { float ( EXTEND_TGT_TAIL_BASE_QTY ) : .2f } ; px: { float ( price ) : .2f } ) ' )
pass
2026-04-24 07:29:26 +00:00
# await extend_remainder_route()
if EXTEND_OPEN_ORDERS :
open_order_dict = dict ( EXTEND_OPEN_ORDERS [ 0 ] )
open_order_id = open_order_dict [ ' external_id ' ]
open_order_px = float ( open_order_dict [ ' price ' ] )
place_order = True
else :
open_order_id = None
open_order_px = 0
place_order = True
if place_order :
price = Decimal ( str ( price ) ) . quantize ( Decimal ( str ( 0.01 ) ) , rounding = ROUND_DOWN )
if round ( open_order_px - float ( price ) , 2 ) == 0.00 :
logging . info ( ' EXTEND OPEN ORDER NO PX CHG; SKIPPING ' )
else :
order_resp = await EXTEND_CLIENT . place_order (
market_name = symbol ,
amount_of_synthetic = qty ,
price = price ,
side = side ,
taker_fee = Decimal ( " 0.00025 " ) ,
previous_order_id = open_order_id ,
)
order_resp_dict = dict ( order_resp )
if order_resp_dict . get ( ' status ' , None ) == ' OK ' :
if EXTEND_OPEN_ORDERS :
EXTEND_OPEN_ORDERS . pop ( 0 )
order_dict = dict ( order_resp_dict [ ' data ' ] )
order_dict [ ' status ' ] = ' NEW '
order_dict [ ' price ' ] = str ( price )
EXTEND_OPEN_ORDERS . append ( order_dict )
2026-04-25 03:40:47 +00:00
utils . send_tg_alert ( f ' FR_ALGO - EXTEND Order. Start_$: { EXTEND_NOTIONAL_POSITION : .2f } ; Value: { float ( EXTEND_TGT_TAIL_BASE_QTY ) * float ( price ) : .2f } ; Price: { float ( price ) : .2f } ' )
2026-04-24 07:29:26 +00:00
logging . info ( f ' EXTEND ORDER PLACED SUCCESS: { order_dict } ' )
else :
logging . warning ( ' EXTEND PLACE ORDER CHECKS FAILED, SKIPPING ' )
elif not ( EXTEND_TGT_TAIL_ORDERABLE ) and EXTEND_OPEN_ORDERS :
logging . info ( ' EXTEND HAS NO TAIL BUT OPEN ORDERS - CANCELLING OPEN ORDERS ' )
await extend_cancel_all_orders ( )
2026-04-23 06:39:51 +00:00
print ( f ' __________ End ___________ (Algo Engine ms: { ( time . time ( ) - loop_start ) * 1000 } ) ' )
2026-04-24 07:29:26 +00:00
time . sleep ( LOOP_SLEEP_SEC )
2026-04-21 20:22:33 +00:00
except KeyboardInterrupt :
2026-04-24 07:29:26 +00:00
logging . info ( ' CANCELLING OPEN ORDERS ' )
await kill_algo ( )
2026-04-21 20:22:33 +00:00
except Exception as e :
logging . error ( traceback . format_exc ( ) )
2026-04-24 07:29:26 +00:00
logging . critical ( f ' *** ALGO ENGINE CRASHED: { e } ' )
logging . info ( ' CANCELLING OPEN ORDERS ' )
utils . send_tg_alert ( f ' FR_ALGO_CRASHED: { str ( e ) } ' )
await kill_algo ( )
2026-04-23 06:39:51 +00:00
2026-04-23 16:34:47 +00:00
### MAIN STARTUP ###
2026-04-21 20:22:33 +00:00
async def main ( ) :
2026-04-23 06:39:51 +00:00
global EXTEND_CLIENT
2026-04-21 20:22:33 +00:00
global VAL_KEY
global CON
2026-04-23 06:39:51 +00:00
_ , EXTEND_CLIENT = await extend_auth . create_auth_account_and_trading_client ( )
2026-04-21 20:22:33 +00:00
VAL_KEY = valkey . Valkey ( host = ' localhost ' , port = 6379 , db = 0 , decode_responses = True )
engine = create_async_engine ( ' mysql+asyncmy://root:pwd@localhost/fund_rate ' )
async with engine . connect ( ) as CON :
2026-04-23 16:34:47 +00:00
### ASTER SETUP ###
2026-04-23 06:39:51 +00:00
await get_aster_collateral ( )
await get_aster_notional_position ( )
2026-04-23 16:34:47 +00:00
await get_aster_exch_info ( )
await get_aster_open_orders ( )
### EXTEND SETUP ###
2026-04-23 06:39:51 +00:00
await get_extend_collateral ( )
await get_extend_notional ( )
2026-04-23 16:34:47 +00:00
await get_extend_exch_info ( )
await get_extend_open_orders ( )
2026-04-23 06:39:51 +00:00
2026-04-21 20:22:33 +00:00
await run_algo ( )
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 ( ) )