This commit is contained in:
2026-05-04 18:04:45 +00:00
parent b05f389e49
commit 4eadc32f03
12 changed files with 10709 additions and 2496 deletions

246
main.py
View File

@@ -1,4 +1,4 @@
from orjson import JSONDecodeError
from x10.utils.http import WrappedApiResponse
from x10.perpetual.trading_client.trading_client import PerpetualTradingClient
import asyncio
@@ -9,7 +9,7 @@ import os
import time
import traceback
from datetime import datetime, timezone
from decimal import ROUND_DOWN, ROUND_HALF_UP, Decimal
from decimal import ROUND_DOWN, ROUND_UP, ROUND_HALF_UP, Decimal
from typing import AsyncContextManager
from dataclasses import dataclass, asdict
from typing import Any
@@ -114,10 +114,15 @@ async def get_aster_account_open_symbols() -> list[str]:
"url": "/fapi/v3/positionRisk",
"method": "GET",
"params": {
'symbol': ASTER.symbol,
'symbol':''
}
}
resp: list = await aster_auth.post_authenticated_url(req=fut_acct_positionRisk) # ty:ignore[invalid-assignment]
try:
resp: list = await aster_auth.post_authenticated_url(req=fut_acct_positionRisk) # ty:ignore[invalid-assignment]
except Exception as e:
logging.critical(f'JSONDecodeError trying to get Aster open orders: {e}; resp: {resp}')
await kill_algo()
resp: list = []
ld = [ utils.symbol_to_extend_fmt(x['symbol']) for x in resp if abs(float(x.get('positionAmt', 0))) > 0]
return ld
@@ -144,10 +149,11 @@ async def get_aster_notional_position(resp: list | None = None):
}
try:
resp: list = await aster_auth.post_authenticated_url(req=fut_acct_positionRisk) # ty:ignore[invalid-assignment]
except JSONDecodeError as e:
logging.warning(f'JSONDecodeError trying to get Aster notional: {e}; resp: {resp}')
time.sleep(0.1)
resp: list = await aster_auth.post_authenticated_url(req=fut_acct_positionRisk) # ty:ignore[invalid-assignment]
except Exception as e:
logging.critical(f'JSONDecodeError trying to get Aster notional: {e}; resp: {resp}')
await kill_algo()
resp: list = []
d = [x for x in resp if x.get('symbol', None) == ASTER.symbol][0]
d['timestamp_arrival'] = round(datetime.now().timestamp()*1000)
@@ -168,10 +174,10 @@ async def get_aster_notional_position(resp: list | None = None):
ASTER_NOTIONAL_POSITION = float(d['notional']) - ASTER_UNREALIZED_PNL
else:
ASTER_NOTIONAL_POSITION = float(d['position_amount'])*float(d['entry_price'])
# if not resp: # this can never evaluate
# ASTER.mult = float(d['leverage'])
if d.get('leverage') is not None:
ASTER.mult = int(d['leverage'])
if abs(ASTER_NOTIONAL_POSITION) > ALGO_CONFIG.Config.Max_Target_Notional*ALGO_CONFIG.Config.Max_Order_Over_Notional_Ratio:
logging.info(f'BAD NOTIONAL - ASTER CHANGE: {previous_notional_position} -> {ASTER_NOTIONAL_POSITION}; UR PNL: {ASTER_UNREALIZED_PNL}; MULT: {ASTER.mult}; d: {d}; resp: {resp}')
logging.info(f'BAD NOTIONAL - ASTER CHANGE: {previous_notional_position} -> {ASTER_NOTIONAL_POSITION}; UR PNL: {ASTER_UNREALIZED_PNL}; MULT: {ASTER.mult}; d: {d}; resp: {resp}; max_tgt_notional: {ALGO_CONFIG.Config.Max_Target_Notional}')
await kill_algo()
if ASTER_NOTIONAL_POSITION != previous_notional_position:
logging.info(f'ASTER NOTIONAL CHANGE: {previous_notional_position:.2f} -> {ASTER_NOTIONAL_POSITION:.2f}; UR PNL: {ASTER_UNREALIZED_PNL:.2f}; MULT: {ASTER.mult:.0f}; resp: {bool(resp)}')
@@ -210,13 +216,15 @@ async def get_extend_notional(resp: list | None = None):
pos_dict = [dict(d) for d in resp if dict(d).get('market') == EXTEND.symbol]
if pos_dict:
pos_dict = pos_dict[0]
pos_dict['timestamp_arrival'] = round(datetime.now().timestamp()*1000)
else:
pos_dict = {}
pos_dict['side'] = 'LONG'
pos_dict['value'] = 0.00
pos_dict['unrealised_pnl'] = 0.00
pos_dict['timestamp_arrival'] = round(datetime.now().timestamp()*1000)
logging.info('get_extend_notional - No Positions')
pos_dict['timestamp_arrival'] = round(datetime.now().timestamp()*1000)
else:
pos_dict = [dict(d) for d in resp if dict(d).get('market') == EXTEND.symbol]
if pos_dict:
@@ -225,14 +233,18 @@ async def get_extend_notional(resp: list | None = None):
pos_dict = {}
pos_dict['side'] = 'LONG'
pos_dict['value'] = 0.00
pos_dict['unrealised_pnl'] = 0.00
pos_dict['timestamp_arrival'] = round(datetime.now().timestamp()*1000)
# logging.info('get_extend_notional - No Positions')
pos_dict['timestamp_arrival'] = round(datetime.now().timestamp()*1000)
# pos_dict['timestamp_arrival'] = round(datetime.now().timestamp()*1000)
if previous_notional_obj is not None:
if previous_notional_obj['timestamp_arrival'] > pos_dict['timestamp_arrival']:
# logging.info(f'EXTEND NOTIONAL: prev timestamp ({pd.to_datetime(previous_notional_obj['timestamp_arrival'], unit='ms')}) > new timestamp ({pd.to_datetime(d['timestamp_arrival'], unit='ms')}); skipping')
# logging.info(f'EXTEND NOTIONAL: prev timestamp ({pd.to_datetime(previous_notional_obj['timestamp_arrival'], unit='ms')}) > new timestamp ({pd.to_datetime(pos_dict['timestamp_arrival'], unit='ms')}); skipping')
return
else:
previous_notional_obj = {}
EXTEND_NOTIONAL_OBJ = pos_dict
@@ -252,7 +264,7 @@ async def get_extend_notional(resp: list | None = None):
logging.info(f'BAD NOTIONAL - EXTEND CHANGE: {previous_notional_position} -> {EXTEND_NOTIONAL_POSITION}; UR PNL: {EXTEND_UNREALIZED_PNL}; MULT: {EXTEND.mult}; d: {pos_dict}; resp: {resp}')
await kill_algo()
if EXTEND_NOTIONAL_POSITION != previous_notional_position:
logging.info(f'EXTEND NOTIONAL CHANGE: {previous_notional_position} -> {EXTEND_NOTIONAL_POSITION:.2f}; UR PNL: {EXTEND_UNREALIZED_PNL:.2f}; MULT: {EXTEND.mult}; resp: {bool(resp)}')
logging.info(f'EXTEND NOTIONAL CHANGE: {previous_notional_position} [{previous_notional_obj.get('timestamp_arrival')}] -> {EXTEND_NOTIONAL_POSITION:.2f} [{EXTEND_NOTIONAL_OBJ['timestamp_arrival']}]; UR PNL: {EXTEND_UNREALIZED_PNL:.2f}; MULT: {EXTEND.mult}; resp: {bool(resp)}')
### EXCHANGE INFO ###
async def get_aster_exch_info(symbol_override: str | None = None):
@@ -271,6 +283,7 @@ async def get_aster_exch_info(symbol_override: str | None = None):
d: dict = [d for d in s if d.get('symbol', None) == ASTER.symbol][0]
f: dict = [f for f in d['filters'] if f.get('filterType', None) == 'LOT_SIZE'][0]
q: dict = [f for f in d['filters'] if f.get('filterType', None) == 'PRICE_FILTER'][0]
n: dict = [f for f in d['filters'] if f.get('filterType', None) == 'MIN_NOTIONAL'][0]
min_qty = float(f['minQty'])
min_qty = int(min_qty) if min_qty == int(min_qty) else min_qty
@@ -279,6 +292,7 @@ async def get_aster_exch_info(symbol_override: str | None = None):
min_price = int(min_price) if min_price == int(min_price) else min_price
ASTER.min_order_size = min_qty
ASTER.min_price = min_price
ASTER.min_notional = float(n['notional'])
async def get_extend_exch_info(symbol_override: str | None = None):
global EXTEND
@@ -290,6 +304,7 @@ async def get_extend_exch_info(symbol_override: str | None = None):
EXTEND.min_order_size = float(r[EXTEND.symbol].trading_config.min_order_size)
EXTEND.min_price = float(r[EXTEND.symbol].trading_config.min_price_change)
### CANCEL ORDERS ###
async def aster_cancel_all_orders():
cancel_all_open_orders = {
@@ -367,13 +382,11 @@ async def run_algo():
ASTER_FUND_RATE = ASTER_FUND_RATE * -1
EXTEND_FUND_RATE = EXTEND_FUND_RATE * -1
if ALGO_CONFIG.Overrides.Flatten_Open_Positions or ALGO_CONFIG.Overrides.Flatten_Open_Positions_Opportunistic:
ROUNDING = ROUND_HALF_UP
else:
ROUNDING = ROUND_DOWN
ASTER_FUND_RATE_TIME = float(ASTER_FUND_RATE_DICT.get('next_funding_time_ts_ms', 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
EXTEND_FUND_RATE_TIME = max([float(EXTENDED_FUND_RATE_DICT.get('next_funding_time_ts_ms', 0)), 0])
EXTEND_FUND_RATE_TIME = EXTEND_FUND_RATE_TIME+(60*60*1000) if EXTEND_FUND_RATE_TIME < (datetime.now().timestamp()*1000) else EXTEND_FUND_RATE_TIME
ASTER_TICKER_DICT: Any = VAL_KEY.get('fut_ticker_aster')
ASTER_TICKER_DICT: dict = json.loads(s=ASTER_TICKER_DICT) if ASTER_TICKER_DICT is not None else {}
@@ -510,16 +523,39 @@ async def run_algo():
if abs( ASTER_NOTIONAL_POSITION ) > 0.00 or abs( EXTEND_NOTIONAL_POSITION ) > 0.00:
if ALGO_CONFIG.Logging.Print_Summary_Each_Loop:
print(f'Symbol switch [{ASTER.symbol} > {best_symbol_by_exchange_aster.symbol}] - Flattening Positions')
ALGO_CONFIG.Overrides.Flatten_Open_Positions = True
ALGO_CONFIG.Overrides.Flatten_Open_Positions_Opportunistic = True
else:
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}')
ALGO_CONFIG.Overrides.Flatten_Open_Positions = False
ALGO_CONFIG.Overrides.Flatten_Open_Positions_Opportunistic = False
if Open_Symbols:
logging.info(f'OPEN SYMBOLS TO CLOSE: {Open_Symbols}')
await get_aster_exch_info(symbol_override=Open_Symbols[0])
await get_extend_exch_info(symbol_override=Open_Symbols[0])
master_data = json.loads(s=VAL_KEY.get(name='fr_engine_best_fund_rate_master')) # ty:ignore[invalid-argument-type]
open_symbol_to_work = Open_Symbols[0]
current_pos_master_ast = [d for d in master_data if d.get('symbol_ext') == open_symbol_to_work][0]
ASTER = structs.Perpetual_Exchange(
mult = int(current_pos_master_ast['max_leverage_ast']),
lh_asset = current_pos_master_ast['lh_asset_ast'],
rh_asset = current_pos_master_ast['rh_asset_ast'],
symbol_asset_separator = '',
initial_funding_rate=float(current_pos_master_ast['funding_rate_ast']),
min_price=float(current_pos_master_ast['min_price_ast']),
min_order_size=float(current_pos_master_ast['min_order_size_ast']),
min_lot_size=float(current_pos_master_ast['min_lot_size_ast']),
min_notional=float(current_pos_master_ast['min_notional_ast']),
)
EXTEND = structs.Perpetual_Exchange(
mult = int(current_pos_master_ast['max_leverage_ext']),
lh_asset = current_pos_master_ast['lh_asset_ext'],
rh_asset = current_pos_master_ast['rh_asset_ext'],
symbol_asset_separator = '-',
initial_funding_rate=float(current_pos_master_ast['funding_rate_ext']),
min_price=float(current_pos_master_ast['min_price_ext']),
min_order_size=float(current_pos_master_ast['min_order_size_ext']),
min_lot_size=float(current_pos_master_ast['min_lot_size_ext']),
min_notional=float(current_pos_master_ast['min_notional_ext']),
)
Open_Symbols.pop(0)
await get_aster_notional_position()
await get_extend_notional()
@@ -565,7 +601,13 @@ async def run_algo():
# else:
# logging.info('NET FUNDING = 0.00; NO OPEN ORDERS; Waiting...')
# time.sleep(5)
if ALGO_CONFIG.Overrides.Flatten_Open_Positions or ALGO_CONFIG.Overrides.Flatten_Open_Positions_Opportunistic or ALPHA_TGT_NOTIONAL==0.00:
# ROUNDING = ROUND_UP
ROUNDING = ROUND_HALF_UP
else:
ROUNDING = ROUND_DOWN
if ALPHA_EXCH == 'EXTEND':
ASTER_TGT_NOTIONAL = ALPHA_TGT_NOTIONAL*-1
@@ -603,28 +645,46 @@ async def run_algo():
Expected_Alpha = ( ( ASTER_TOB_PX - EXTEND_TOB_PX ) / (( ASTER_TOB_PX + EXTEND_TOB_PX ) / 2) )
Expected_Alpha_Net_FR = abs(NEXT_NET_FUNDING_RATE) + Expected_Alpha
Expected_Alpha_Net_FR_w_Taker = Expected_Alpha_Net_FR-0.0002
Expected_Alpha_w_Taker = Expected_Alpha-0.0002
Expected_Alpha_Net_FR_w_Taker = Expected_Alpha_Net_FR-0.00025
Expected_Alpha_w_Taker = Expected_Alpha-0.00025
EXTEND_TGT_NOTIONAL = ASTER_NOTIONAL_POSITION * -1
ASTER_TGT_TAIL = ASTER_TGT_NOTIONAL - ( float(ASTER_NOTIONAL_POSITION) + float(ASTER_UNREALIZED_PNL) )
EXTEND_TGT_TAIL = EXTEND_TGT_NOTIONAL - ( float(EXTEND_NOTIONAL_POSITION) + float(EXTEND_UNREALIZED_PNL) )
# EXTEND_TGT_TAIL = EXTEND_TGT_NOTIONAL - ( float(EXTEND_NOTIONAL_POSITION) + float(EXTEND_UNREALIZED_PNL) )
EXTEND_TGT_TAIL = EXTEND_TGT_NOTIONAL - ( float(EXTEND_NOTIONAL_POSITION) )
# EXTEND_TGT_TAIL = float(ASTER_NOTIONAL_POSITION)*-1
min_order_size = ASTER.min_order_size
min_order_size = int(min_order_size) if min_order_size == int(min_order_size) else min_order_size
ASTER_TGT_TAIL_BASE_QTY = Decimal(str(float(ASTER_TGT_TAIL) / float(ASTER_TOB_PX))).quantize(Decimal(str(min_order_size)), rounding=ROUNDING)
if ASTER.min_lot_size:
ASTER_TGT_TAIL_BASE_QTY = float(ASTER_TGT_TAIL_BASE_QTY) - ( float(ASTER_TGT_TAIL_BASE_QTY) % ASTER.min_lot_size )
ASTER_TGT_TAIL_BASE_QTY = Decimal(str(ASTER_TGT_TAIL_BASE_QTY)).quantize(Decimal(str(min_order_size)), rounding=ROUNDING)
min_order_size = EXTEND.min_order_size
min_order_size = int(min_order_size) if min_order_size == int(min_order_size) else min_order_size
EXTEND_TGT_TAIL_BASE_QTY = Decimal(str(float(EXTEND_TGT_TAIL) / float(EXTEND_TOB_PX))).quantize(Decimal(str(min_order_size)), rounding=ROUNDING)
if EXTEND.min_lot_size:
EXTEND_TGT_TAIL_BASE_QTY = float(EXTEND_TGT_TAIL_BASE_QTY) - ( float(EXTEND_TGT_TAIL_BASE_QTY) % EXTEND.min_lot_size )
EXTEND_TGT_TAIL_BASE_QTY = Decimal(str(EXTEND_TGT_TAIL_BASE_QTY)).quantize(Decimal(str(min_order_size)), rounding=ROUNDING)
# MAX_MIN_ORDER_QTY = max([ASTER.min_order_size, EXTEND.min_order_size])
ASTER_TGT_TAIL_ORDERABLE = Decimal(str(abs(ASTER_TGT_TAIL_BASE_QTY))) >= Decimal(str(abs(ASTER.min_order_size)))
EXTEND_TGT_TAIL_ORDERABLE = Decimal(str(abs(EXTEND_TGT_TAIL_BASE_QTY))) >= Decimal(str(abs(EXTEND.min_order_size)))
# ASTER_TGT_TAIL_ORDERABLE = abs(ASTER_TGT_TAIL_BASE_QTY) >= MAX_MIN_ORDER_QTY
# EXTEND_TGT_TAIL_ORDERABLE = abs(EXTEND_TGT_TAIL_BASE_QTY) >= MAX_MIN_ORDER_QTY
ASTER_TGT_TAIL_ORDERABLE = ( Decimal(str(abs(ASTER_TGT_TAIL_BASE_QTY)) ) >= Decimal(str(abs(ASTER.min_order_size))) ) and ( Decimal(str(abs(ASTER_TGT_TAIL))) > Decimal(str(abs(ASTER.min_notional))) )
EXTEND_TGT_TAIL_ORDERABLE = ( Decimal(str(abs(EXTEND_TGT_TAIL_BASE_QTY))) >= Decimal(str(abs(EXTEND.min_order_size))) ) and ( Decimal(str(abs(EXTEND_TGT_TAIL))) > Decimal(str(abs(EXTEND.min_notional))) )
if not ASTER_TGT_TAIL_ORDERABLE:
if abs(ASTER_TGT_TAIL_BASE_QTY) > 0:
if ALGO_CONFIG.Overrides.Flatten_Open_Positions or ALGO_CONFIG.Overrides.Flatten_Open_Positions_Opportunistic or ALPHA_TGT_NOTIONAL == 0.00:
logging.info('* Trying to flatten small Aster balance, was originally not orderable.')
ASTER_TGT_TAIL_ORDERABLE = True
if not EXTEND_TGT_TAIL_ORDERABLE:
if abs(EXTEND_TGT_TAIL_BASE_QTY) > 0:
if ALGO_CONFIG.Overrides.Flatten_Open_Positions or ALGO_CONFIG.Overrides.Flatten_Open_Positions_Opportunistic or ALPHA_TGT_NOTIONAL == 0.00:
logging.info('* Trying to flatten small Extend balance, was originally not orderable.')
EXTEND_TGT_TAIL_ORDERABLE = True
# Hedge_Ratio = abs(( abs( max([abs(float(EXTEND_NOTIONAL_POSITION)), 0.01]) / max([abs(float(ASTER_NOTIONAL_POSITION)), 0.01]) ) - 1 ) * 100)
Hedge_Ratio = abs( ( EXTEND_NOTIONAL_POSITION + ASTER_NOTIONAL_POSITION ) / max([ASTER_NOTIONAL_POSITION, 0.01]) ) * 100
@@ -652,7 +712,7 @@ async def run_algo():
ASTER: {ASTER_TGT_TAIL_BASE_QTY:.4f} > {ASTER.min_order_size:.4f} min [ Order: {ASTER_TGT_TAIL_ORDERABLE} ] | EXTEND: {EXTEND_TGT_TAIL_BASE_QTY:.4f} > {EXTEND.min_order_size:.4f} min [ Order: {EXTEND_TGT_TAIL_ORDERABLE} ]
ALPHA: {ALPHA_RATIO:.8f} ALPHA_RATIO: {Alpha_Nominator:_.6f} / {Alpha_Denominator:_.6f} (Px Diff: {abs(Alpha_Nominator-Alpha_Denominator):.2f}); Expected_Alpha = {Expected_Alpha:.6f} + FR[{NEXT_NET_FUNDING_RATE:.6f}] = * {Expected_Alpha_Net_FR:.6f} *
FEES : TAKER: {0.0002:.2%}; Expected Alpha w Taker = {Expected_Alpha_Net_FR-0.0002:.6f} [w/o FR: {Expected_Alpha_w_Taker:.6f}]
FEES : TAKER: {0.00025:.6%}; Expected Alpha w Taker = {Expected_Alpha_Net_FR_w_Taker:.6f} [w/o FR: {Expected_Alpha_w_Taker:.6f}]
HEDGE: {Hedge_Ratio:.2f}% <= {1:.2f}%: {Currently_Hedged} [{EXTEND_NOTIONAL_POSITION:.2f} / {ASTER_NOTIONAL_POSITION:.2f}]
MKT : Aster: {ASTER.symbol} (best: {best_symbol_by_exchange_aster.symbol}) | Extend: {ASTER.symbol} (best: {best_symbol_by_exchange_extend.symbol})
@@ -674,11 +734,16 @@ async def run_algo():
### ROUTES ###
# Just_Rejected_Or_Expired
# MIN_EXPECTED_ALPHA_TO_TRADE = 0.0001
MIN_EXPECTED_ALPHA_TO_TRADE = abs(NEXT_NET_FUNDING_RATE)*-1
MIN_EXPECTED_ALPHA_TO_TRADE = 0.0000
if ALGO_CONFIG.Overrides.Flatten_Open_Positions_Opportunistic:
exp_alpha = Expected_Alpha_w_Taker
else:
exp_alpha = Expected_Alpha_Net_FR_w_Taker
# MIN_EXPECTED_ALPHA_TO_TRADE = abs(NEXT_NET_FUNDING_RATE)*-1
# MIN_EXPECTED_ALPHA_TO_TRADE = -0.000001
# ALPHA RATIO CHECK
if not( ( Expected_Alpha_Net_FR_w_Taker > MIN_EXPECTED_ALPHA_TO_TRADE ) or ( ASTER_OPEN_ORDERS or EXTEND_OPEN_ORDERS or ALGO_CONFIG.Overrides.Flatten_Open_Positions) ) and Currently_Hedged:
if not( ( exp_alpha > MIN_EXPECTED_ALPHA_TO_TRADE ) or ( ASTER_OPEN_ORDERS or EXTEND_OPEN_ORDERS or ALGO_CONFIG.Overrides.Flatten_Open_Positions) ) and Currently_Hedged:
# if not( ( Expected_Alpha_Net_FR_w_Taker > MIN_EXPECTED_ALPHA_TO_TRADE ) or ( ASTER_OPEN_ORDERS or EXTEND_OPEN_ORDERS or ALGO_CONFIG.Overrides.Flatten_Open_Positions) ) and Currently_Hedged:
if ALGO_CONFIG.Logging.Print_Summary_Each_Loop:
print(f'Alpha Ratio too low ({ALPHA_RATIO:.8f}) and no Open Orders...')
elif ( Expected_Alpha_Net_FR_w_Taker <= MIN_EXPECTED_ALPHA_TO_TRADE ) and ( ASTER_OPEN_ORDERS or EXTEND_OPEN_ORDERS ) and Currently_Hedged and not(ALGO_CONFIG.Overrides.Flatten_Open_Positions):
@@ -732,14 +797,18 @@ async def run_algo():
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:
min_price = ASTER.min_price
min_price = int(min_price) if min_price == int(min_price) else min_price
price: Decimal = Decimal(str(price)).quantize(Decimal(str(min_price)), rounding=ROUND_HALF_UP)
if price == Decimal(str(0.00)).quantize(Decimal(str(min_price)), rounding=ROUND_HALF_UP):
logging.info('ASTER TRYNG TO ORDER with A PRICE OF 0.00, SKIPPING')
continue
if qty >= ASTER.min_order_size:
if qty >= ASTER.min_order_size and (qty*price) > ASTER.min_notional:
reduceOnly = False
else:
reduceOnly = True
@@ -763,7 +832,7 @@ async def run_algo():
order_resp['order_status'] = order_resp['status']
ASTER_OPEN_ORDERS.append(order_resp)
Just_Rejected_Or_Expired = False
utils.send_tg_alert(f'FR_ALGO - ASTER Order ({order_resp['orderId']}). Start_$: {ASTER_NOTIONAL_POSITION:.2f}; Value: {float(ASTER_TGT_TAIL_BASE_QTY)*float(price):.2f}; Price: {float(price):.2f}')
utils.send_tg_alert(f'FR_ALGO - ASTER Order ({order_resp['orderId']}). Start_$: {ASTER_NOTIONAL_POSITION:.4f}; Value: {float(ASTER_TGT_TAIL_BASE_QTY)*float(price):.4f}; Price: {float(price):.4f}')
logging.info(f'ASTER ORDER PLACED SUCCESS: {order_resp}')
print_summary(use_logging=True)
else:
@@ -777,13 +846,22 @@ async def run_algo():
### Add code to flatten small balances
logging.info('ASTER HAS NO TAIL BUT OPEN ORDERS - CANCELLING OPEN ORDERS')
await aster_cancel_all_orders()
time.sleep(0.1)
# if (float(ALPHA_TGT_NOTIONAL) < float(EXTEND_NOTIONAL_POSITION)) and ((float(EXTEND_NOTIONAL_POSITION) + float(EXTEND_TGT_TAIL)) > float(EXTEND_NOTIONAL_POSITION)):
# EXTEND_TGT_TAIL_ORDERABLE= False
# print('ASTER ordering in the wrong directiion - Should be selling, but its buying - skipping')
# elif (float(ALPHA_TGT_NOTIONAL) > float(EXTEND_NOTIONAL_POSITION)) and ((float(EXTEND_NOTIONAL_POSITION) + float(EXTEND_TGT_TAIL)) < float(EXTEND_NOTIONAL_POSITION)):
# EXTEND_TGT_TAIL_ORDERABLE= False
# print('ASTER ordering in the wrong directiion - Should be buying, but its selling - skipping')
# EXTEND
if EXTEND_TGT_TAIL_ORDERABLE and ALGO_CONFIG.Overrides.Allow_Ordering_Extend:
if (EXTEND_TGT_TAIL_ORDERABLE and ALGO_CONFIG.Overrides.Allow_Ordering_Extend):
# if ALGO_CONFIG.Overrides.Allow_Ordering_Extend:
side = OrderSide.BUY if EXTEND_TGT_TAIL_BASE_QTY > 0.00 else OrderSide.SELL
symbol = EXTEND.symbol
qty = Decimal(value=str(abs(EXTEND_TGT_TAIL_BASE_QTY)))
Time_Since_Last_Aster_Fill_ms = ( datetime.now().timestamp()*1000 ) - Last_Aster_Fill_Time_Ts
min_price = EXTEND.min_price
min_price = int(min_price) if min_price == int(min_price) else min_price
@@ -791,10 +869,10 @@ async def run_algo():
post_only = False
price: Decimal = Decimal(value=str(EXTEND_TOB_PX - ( float(min_price)*int(ALGO_CONFIG.Config.Price_Worsener_Extend) ) if side == 'BUY' else EXTEND_TOB_PX + ( float(min_price)*int(ALGO_CONFIG.Config.Price_Worsener_Extend) ) )).quantize(Decimal(str(min_price)), rounding=ROUND_HALF_UP)
else:
post_only = True
# post_only = True
post_only = False
price: Decimal = Decimal(value=str(EXTEND_TOB_PX)).quantize(Decimal(str(min_price)), rounding=ROUND_HALF_UP)
if abs( ( float(EXTEND_TGT_TAIL_BASE_QTY)*float(price) ) + EXTEND_NOTIONAL_POSITION ) > ALGO_CONFIG.Config.Max_Target_Notional*ALGO_CONFIG.Config.Max_Order_Over_Notional_Ratio:
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})')
await kill_algo()
@@ -802,28 +880,19 @@ async def run_algo():
open_order_dict = dict(EXTEND_OPEN_ORDERS[0])
open_order_id = str(open_order_dict['external_id'])
open_order_px = float(open_order_dict['price'])
open_order_filled_qty = float(open_order_dict['filled_qty'])
if qty >= EXTEND.min_order_size:
place_order = True
place_residual_order = False
else:
if int(qty) == 0:
place_order = False
place_residual_order = False
logging.info(f'EXTEND NOT ORDERING DUE TO NOTIONAL QTY == 0; Filled: {float(open_order_filled_qty):.4f}; Residual: {qty:.4f}')
else:
place_order = True
place_residual_order = True
logging.info(f'Ordering RESIDUAL market order for remaining small amount: {qty}')
# if int(qty) == 0:
# place_order = False
# place_residual_order = False
# logging.info(f'EXTEND NOT ORDERING DUE TO NOTIONAL QTY == 0; Filled: {float(open_order_filled_qty):.4f}; Residual: {qty:.4f}')
# else:
# place_order = True
# place_residual_order = False
# logging.info(f'Ordering RESIDUAL market order for remaining small amount: {qty}')
else:
open_order_id = None
open_order_px = 0
place_order = True
if qty >= EXTEND.min_order_size:
place_residual_order = False
else:
place_residual_order = True
if place_order:
price: Decimal = Decimal(str(price)).quantize(Decimal(str(min_price)), rounding=ROUND_HALF_UP)
if round(open_order_px - float(price), len(str(min_price)) - 2 ) == 0.00:
@@ -831,14 +900,13 @@ async def run_algo():
print('EXTEND OPEN ORDER NO PX CHG; SKIPPING')
else:
try:
if place_residual_order:
post_only = False
if abs(float(EXTEND_NOTIONAL_POSITION) + (float(qty)*float(price))) < abs(float(EXTEND_NOTIONAL_POSITION)):
reduce_only = True
else:
# post_only = SEE ABOVE
reduce_only = False
taker_fee = taker_fee=Decimal("0.00000") if post_only else Decimal("0.00025")
# taker_fee = taker_fee=Decimal("0.00000") if post_only else Decimal("0.00025")
taker_fee = Decimal("0.00025")
order_resp: WrappedApiResponse[PlacedOrderModel] = await EXTEND_CLIENT.place_order(
market_name=symbol,
amount_of_synthetic=Decimal(str(qty)),
@@ -846,14 +914,14 @@ async def run_algo():
side=side,
taker_fee=taker_fee,
previous_order_id=open_order_id,
post_only=post_only
# reduce_only=reduce_only
post_only=post_only,
reduce_only=reduce_only
)
except Exception as e:
logging.error(f'EXTEND ORDER PLACEMENT FAILED - RESP: {order_resp}')
logging.error(f'EXTEND ORDER PLACEMENT FAILED: {e}')
logging.error(f'EXTEND ORDER PLACEMENT FAILED - POSTED: market_name:{symbol}, amount_of_synthetic:{qty}, price:{price}, side:{side},taker_fee:{taker_fee}, previous_order_id:{open_order_id}, post_only:{post_only}; reduce_only:{reduce_only}')
logging.error(f'EXTEND ORDER PLACEMENT FAILED - POSTED: market_name:{symbol}, side: {side} amount_of_synthetic:{qty}, price:{price}, side:{side},taker_fee:{taker_fee}, previous_order_id:{open_order_id}, post_only:{post_only}; reduce_only:{reduce_only}')
logging.error(traceback.format_exc())
logging.error(f'EXTEND ORDER PLACEMENT FAILED - RESP: {order_resp}')
order_resp_dict = dict(order_resp)
@@ -888,7 +956,8 @@ async def run_algo():
continue
else:
time.sleep(ALGO_CONFIG.Config.Loop_Sleep_Sec)
# print(f'_____ End No Open Orders _____ (Algo Engine ms: {(time.time() - loop_start)*1000:.2f}); Sleeping for sec: {ALGO_CONFIG.Config.Loop_Sleep_Sec:.0f}')
if ALGO_CONFIG.Logging.Print_Summary_Each_Loop:
print(f'_____ End No Open Orders _____ (Algo Engine ms: {(time.time() - loop_start)*1000:.2f}); Sleeping for sec: {ALGO_CONFIG.Config.Loop_Sleep_Sec:.0f}')
except KeyboardInterrupt:
logging.info('CANCELLING OPEN ORDERS')
@@ -916,23 +985,50 @@ async def main():
VAL_KEY = valkey.Valkey(host='localhost', port=6379, db=0, decode_responses=True)
engine = create_async_engine('mysql+asyncmy://root:pwd@localhost/fund_rate')
best_symbol_by_exchange: dict = json.loads(s=VAL_KEY.get(name='fr_engine_best_fund_rate_output')) # ty:ignore[invalid-argument-type]
ASTER = structs.Perpetual_Exchange(**best_symbol_by_exchange['ASTER'])
EXTEND = structs.Perpetual_Exchange(**best_symbol_by_exchange['EXTEND'])
await set_comb_open_symbols()
best_symbol_by_exchange: dict = json.loads(s=VAL_KEY.get(name='fr_engine_best_fund_rate_output')) # ty:ignore[invalid-argument-type]
if Open_Symbols:
logging.info(f'OPEN SYMBOLS TO CLOSE: {Open_Symbols}')
await get_aster_exch_info(symbol_override=Open_Symbols[0])
await get_extend_exch_info(symbol_override=Open_Symbols[0])
logging.info(f'OPEN SYMBOLS: {Open_Symbols}')
master_data = json.loads(s=VAL_KEY.get(name='fr_engine_best_fund_rate_master')) # ty:ignore[invalid-argument-type]
open_symbol_to_work = Open_Symbols[0]
current_pos_master_ast = [d for d in master_data if d.get('symbol_ext') == open_symbol_to_work][0]
ASTER = structs.Perpetual_Exchange(
mult = int(current_pos_master_ast['max_leverage_ast']),
lh_asset = current_pos_master_ast['lh_asset_ast'],
rh_asset = current_pos_master_ast['rh_asset_ast'],
symbol_asset_separator = '',
initial_funding_rate=float(current_pos_master_ast['funding_rate_ast']),
min_price=float(current_pos_master_ast['min_price_ast']),
min_order_size=float(current_pos_master_ast['min_order_size_ast']),
min_lot_size=float(current_pos_master_ast['min_lot_size_ast']),
min_notional=float(current_pos_master_ast['min_notional_ast']),
)
EXTEND = structs.Perpetual_Exchange(
mult = int(current_pos_master_ast['max_leverage_ext']),
lh_asset = current_pos_master_ast['lh_asset_ext'],
rh_asset = current_pos_master_ast['rh_asset_ext'],
symbol_asset_separator = '-',
initial_funding_rate=float(current_pos_master_ast['funding_rate_ext']),
min_price=float(current_pos_master_ast['min_price_ext']),
min_order_size=float(current_pos_master_ast['min_order_size_ext']),
min_lot_size=float(current_pos_master_ast['min_lot_size_ext']),
min_notional=float(current_pos_master_ast['min_notional_ext']),
)
Open_Symbols.pop(0)
else:
ASTER = structs.Perpetual_Exchange(**best_symbol_by_exchange['ASTER'])
EXTEND = structs.Perpetual_Exchange(**best_symbol_by_exchange['EXTEND'])
# await get_aster_exch_info(symbol_override=Open_Symbols[0])
# await get_extend_exch_info(symbol_override=Open_Symbols[0])
with open('algo_config.json', mode='r', encoding='utf-8') as file:
ALGO_CONFIG = json.load(file)
ALGO_CONFIG = structs.Algo_Config(**ALGO_CONFIG)
ALGO_CONFIG.Config.Max_Target_Notional = float(min([ASTER.mult, EXTEND.mult]) * ALGO_CONFIG.Config.Target_Open_Cash_Position)
# logging.info(f'Initial Algo Config: {ALGO_CONFIG}')
VAL_KEY.set(name='fr_orchestrator_output', value=json.dumps(obj=ALGO_CONFIG.model_dump()))
VAL_KEY.set(name='fr_algo_working_symbol', value=json.dumps(obj={'ASTER': asdict(obj=ASTER), 'EXTEND': asdict(obj=EXTEND)}))