1065 lines
61 KiB
Python
1065 lines
61 KiB
Python
|
|
from x10.utils.http import WrappedApiResponse
|
|
from x10.perpetual.trading_client.trading_client import PerpetualTradingClient
|
|
import asyncio
|
|
import json
|
|
import logging
|
|
import math
|
|
import os
|
|
import time
|
|
import traceback
|
|
from datetime import datetime, timezone
|
|
from decimal import ROUND_DOWN, ROUND_UP, ROUND_HALF_UP, Decimal
|
|
from typing import AsyncContextManager
|
|
from dataclasses import dataclass, asdict
|
|
from typing import Any
|
|
import numpy as np
|
|
import pandas as pd
|
|
import requests
|
|
|
|
# import talib
|
|
import valkey
|
|
from dotenv import load_dotenv
|
|
from sqlalchemy import text
|
|
from sqlalchemy.ext.asyncio import create_async_engine
|
|
from x10.models.order import OrderSide, PlacedOrderModel
|
|
|
|
import modules.utils as utils
|
|
import modules.aster_auth as aster_auth
|
|
import modules.extended_auth as extend_auth
|
|
import modules.structs as structs
|
|
|
|
### Clients ###
|
|
EXTEND_CLIENT: PerpetualTradingClient
|
|
CON: AsyncContextManager
|
|
VAL_KEY: valkey.Valkey
|
|
|
|
### Logging ###
|
|
load_dotenv()
|
|
LOG_FILEPATH: str = f'{os.getenv(key="LOGS_PATH")}/Fund_Rate_Algo.log'
|
|
|
|
### Algo Config ###
|
|
ALGO_CONFIG: structs.Algo_Config
|
|
MIN_TIME_TO_FUNDING: int
|
|
|
|
### EXCHANGES ###
|
|
ASTER: structs.Perpetual_Exchange
|
|
EXTEND: structs.Perpetual_Exchange
|
|
|
|
### GLOBALS ###
|
|
Open_Symbols: list[str] = []
|
|
Last_Aster_Fill_Time_Ts: float = 0.00
|
|
Just_Rejected_Or_Expired: bool = False
|
|
|
|
ASTER_AVAIL_COLLATERAL = 0
|
|
EXTEND_AVAIL_COLLATERAL = 0
|
|
|
|
ASTER_NOTIONAL_POSITION = 0
|
|
EXTEND_NOTIONAL_POSITION = 0
|
|
|
|
ASTER_NOTIONAL_OBJ: dict | None = None
|
|
EXTEND_NOTIONAL_OBJ: dict | None = None
|
|
|
|
ASTER_UNREALIZED_PNL = 0
|
|
EXTEND_UNREALIZED_PNL = 0
|
|
|
|
ASTER_OPEN_ORDERS = []
|
|
EXTEND_OPEN_ORDERS = []
|
|
|
|
# ASTER_OPEN_POSITIONS = []
|
|
# EXTEND_OPEN_POSITIONS = []
|
|
|
|
# EXCHANGES: list = [ Aster(), Extend() ]
|
|
|
|
### FLAGS ###
|
|
Flags = structs.Flags()
|
|
|
|
### 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_HALF_UP)
|
|
|
|
### OPEN ORDERS ###
|
|
async def get_aster_open_orders():
|
|
global ASTER_OPEN_ORDERS
|
|
|
|
fut_acct_openOrders = {
|
|
"url": "/fapi/v3/openOrders",
|
|
"method": "GET",
|
|
"params": {}
|
|
}
|
|
ASTER_OPEN_ORDERS = await aster_auth.post_authenticated_url(fut_acct_openOrders)
|
|
|
|
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": {}
|
|
# }
|
|
# r = await aster_auth.post_authenticated_url(fut_acct_balances)
|
|
# ASTER_AVAIL_COLLATERAL = float([d for d in r if d.get('asset')==ASTER.rh_asset][0].get('availableBalance'))
|
|
|
|
async def get_aster_account_open_symbols() -> list[str]:
|
|
fut_acct_positionRisk: dict = {
|
|
"url": "/fapi/v3/positionRisk",
|
|
"method": "GET",
|
|
"params": {
|
|
'symbol':''
|
|
}
|
|
}
|
|
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
|
|
|
|
async def get_aster_notional_position(resp: list | None = None):
|
|
global ASTER_NOTIONAL_OBJ
|
|
global ASTER_NOTIONAL_POSITION
|
|
global ASTER_UNREALIZED_PNL
|
|
global ASTER
|
|
|
|
previous_notional_obj = ASTER_NOTIONAL_OBJ
|
|
previous_notional_position = ASTER_NOTIONAL_POSITION
|
|
|
|
if resp:
|
|
d = [x for x in resp if x.get('symbol', None) == ASTER.symbol]
|
|
d = d[0] if d else {}
|
|
|
|
if ( not resp ) or ( not d ):
|
|
fut_acct_positionRisk: dict = {
|
|
"url": "/fapi/v3/positionRisk",
|
|
"method": "GET",
|
|
"params": {
|
|
'symbol': ASTER.symbol,
|
|
}
|
|
}
|
|
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 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)
|
|
|
|
if previous_notional_obj is not None:
|
|
if previous_notional_obj['timestamp_arrival'] > d['timestamp_arrival']:
|
|
# logging.info(f'ASTER NOTIONAL: prev timestamp ({pd.to_datetime(previous_notional_obj['timestamp_arrival'], unit='ms')}) > new timestamp ({pd.to_datetime(d['timestamp_arrival'], unit='ms')}); skipping')
|
|
return
|
|
|
|
ASTER_NOTIONAL_OBJ = d
|
|
|
|
if len(d) < 1:
|
|
logging.info(f'BAD NOTIONAL - ASTER CHANGE: Empty d: {d}; resp: {resp}')
|
|
await kill_algo()
|
|
|
|
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:
|
|
ASTER_NOTIONAL_POSITION = float(d['notional']) - ASTER_UNREALIZED_PNL
|
|
else:
|
|
ASTER_NOTIONAL_POSITION = float(d['position_amount'])*float(d['entry_price'])
|
|
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}; 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)}')
|
|
|
|
# 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
|
|
|
|
async def get_extend_account_open_symbols() -> list[str]:
|
|
resp = dict(await EXTEND_CLIENT.account.get_positions()).get('data', [])
|
|
ld = [x.market for x in list(resp) if abs(float(x.size)) > 0]
|
|
return ld
|
|
|
|
async def set_comb_open_symbols() -> None:
|
|
global Open_Symbols
|
|
|
|
open_aster_symbols = await get_aster_account_open_symbols()
|
|
open_extend_symbols = await get_extend_account_open_symbols()
|
|
|
|
Open_Symbols = list(set(open_aster_symbols + open_extend_symbols))
|
|
|
|
|
|
async def get_extend_notional(resp: list | None = None):
|
|
global EXTEND_NOTIONAL_OBJ
|
|
global EXTEND_NOTIONAL_POSITION
|
|
global EXTEND_UNREALIZED_PNL
|
|
global EXTEND
|
|
|
|
previous_notional_obj = EXTEND_NOTIONAL_OBJ
|
|
previous_notional_position = EXTEND_NOTIONAL_POSITION
|
|
|
|
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.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')
|
|
|
|
else:
|
|
pos_dict = [dict(d) for d in resp if dict(d).get('market') == EXTEND.symbol]
|
|
if pos_dict:
|
|
pos_dict = pos_dict[0]
|
|
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)
|
|
|
|
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(pos_dict['timestamp_arrival'], unit='ms')}); skipping')
|
|
return
|
|
else:
|
|
previous_notional_obj = {}
|
|
|
|
EXTEND_NOTIONAL_OBJ = pos_dict
|
|
|
|
EXTEND_UNREALIZED_PNL = pos_dict.get('unrealised_pnl', 0)
|
|
position_side = pos_dict['side'] # LONG or SHORT
|
|
notional_pos_abs = abs(float(pos_dict['value']))
|
|
if position_side == 'LONG':
|
|
notional_pos_sided = notional_pos_abs
|
|
elif position_side == 'SHORT':
|
|
notional_pos_sided = notional_pos_abs * -1
|
|
else:
|
|
logging.info(f'EXTEND BAD SIDE ON POSITION UPDATE: {pos_dict}')
|
|
|
|
EXTEND_NOTIONAL_POSITION = notional_pos_sided - float(EXTEND_UNREALIZED_PNL)
|
|
EXTEND.mult = pos_dict.get('leverage', EXTEND.mult)
|
|
if abs(EXTEND_NOTIONAL_POSITION) > ALGO_CONFIG.Config.Max_Target_Notional*ALGO_CONFIG.Config.Max_Order_Over_Notional_Ratio:
|
|
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} [{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):
|
|
global ASTER
|
|
|
|
if symbol_override:
|
|
ASTER.symbol = utils.symbol_to_aster_fmt(symbol_override)
|
|
|
|
fut_acct_exchangeInfo: dict = {
|
|
"url": "/fapi/v3/exchangeInfo",
|
|
"method": "GET",
|
|
"params": {}
|
|
}
|
|
r: dict = await aster_auth.post_authenticated_url(fut_acct_exchangeInfo) # ty:ignore[invalid-assignment]
|
|
s: list = r['symbols']
|
|
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
|
|
|
|
min_price = float(q['minPrice'])
|
|
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
|
|
|
|
if symbol_override:
|
|
EXTEND.symbol = utils.symbol_to_extend_fmt(symbol_override)
|
|
|
|
r = await EXTEND_CLIENT.markets_info.get_markets_dict()
|
|
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 = {
|
|
"url": "/fapi/v3/allOpenOrders",
|
|
"method": "DELETE",
|
|
"params": {
|
|
'symbol': ASTER.symbol,
|
|
}
|
|
}
|
|
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.symbol])
|
|
logging.info(f'EXTEND CANCEL ALL OPEN ORDERS RESP: {r}')
|
|
|
|
### 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')
|
|
|
|
|
|
### ALGO LOOP ###
|
|
async def run_algo():
|
|
global ASTER
|
|
global EXTEND
|
|
|
|
global ALGO_CONFIG
|
|
global MIN_TIME_TO_FUNDING
|
|
global ASTER_OPEN_ORDERS
|
|
global EXTEND_OPEN_ORDERS
|
|
global Last_Aster_Fill_Time_Ts
|
|
global Just_Rejected_Or_Expired
|
|
# global Best_Symbol_by_Exchange
|
|
|
|
try:
|
|
while True:
|
|
loop_start = time.time()
|
|
# print('__________Start___________')
|
|
### ALGO CONIFG ###
|
|
|
|
ALGO_CONFIG = json.loads(VAL_KEY.get('fr_orchestrator_output')) # ty:ignore[invalid-argument-type]
|
|
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)
|
|
|
|
MIN_TIME_TO_FUNDING = ALGO_CONFIG.Config.Min_Time_To_Funding_Minutes * 60 * 1000
|
|
|
|
### Load Data from Feedhandlers ###
|
|
best_symbol_by_exchange: dict = json.loads(s=VAL_KEY.get(name='fr_engine_best_fund_rate_output')) # ty:ignore[invalid-argument-type]
|
|
best_symbol_by_exchange_aster = structs.Perpetual_Exchange(**best_symbol_by_exchange['ASTER'])
|
|
best_symbol_by_exchange_extend = structs.Perpetual_Exchange(**best_symbol_by_exchange['EXTEND'])
|
|
|
|
|
|
ASTER_FUND_RATE_DICT: Any = VAL_KEY.get('fund_rate_aster')
|
|
ASTER_FUND_RATE_DICT: dict = json.loads(s=ASTER_FUND_RATE_DICT) if ASTER_FUND_RATE_DICT is not None else {}
|
|
if ASTER_FUND_RATE_DICT.get('symbol', None) != ASTER.symbol:
|
|
ASTER_FUND_RATE: float = ASTER.initial_funding_rate
|
|
# logging.info(f'ASTER Symbol mismatch: {ASTER_FUND_RATE_DICT}; expected symbol: {ASTER.symbol}')
|
|
# raise ValueError(f'ASTER Symbol mismatch: {ASTER_FUND_RATE_DICT}; expected symbol: {ASTER.symbol}')
|
|
else:
|
|
ASTER_FUND_RATE: float = float(ASTER_FUND_RATE_DICT.get('funding_rate', 0))
|
|
|
|
EXTENDED_FUND_RATE_DICT: Any = VAL_KEY.get('fund_rate_extended')
|
|
EXTENDED_FUND_RATE_DICT: dict = json.loads(s=EXTENDED_FUND_RATE_DICT) if EXTENDED_FUND_RATE_DICT is not None else {}
|
|
if EXTENDED_FUND_RATE_DICT.get('symbol', None) != EXTEND.symbol:
|
|
EXTEND_FUND_RATE: float = EXTEND.initial_funding_rate
|
|
# logging.info(f'ASTER Symbol mismatch: {EXTENDED_FUND_RATE_DICT}; expected symbol: {EXTEND.symbol}')
|
|
# raise ValueError(f'ASTER Symbol mismatch: {EXTENDED_FUND_RATE_DICT}; expected symbol: {EXTEND.symbol}')
|
|
else:
|
|
EXTEND_FUND_RATE: float = float(EXTENDED_FUND_RATE_DICT.get('funding_rate', 0))
|
|
|
|
if ALGO_CONFIG.Overrides.Flip_Side_For_Testing:
|
|
ASTER_FUND_RATE = ASTER_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 = 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 {}
|
|
if ( ASTER_TICKER_DICT.get('symbol', None) != ASTER.symbol ) and not(ALGO_CONFIG.Overrides.Flatten_Open_Positions):
|
|
logging.warning(f'ASTER Symbol mismatch: {ASTER_TICKER_DICT}; expected symbol: {ASTER.symbol}')
|
|
VAL_KEY.set(name='fr_algo_working_symbol', value=json.dumps(obj={'ASTER': asdict(obj=ASTER), 'EXTEND': asdict(obj=EXTEND)}))
|
|
time.sleep(5)
|
|
continue
|
|
# raise ValueError(f'ASTER Symbol mismatch: {ASTER_TICKER_DICT}; expected symbol: {ASTER.symbol}')
|
|
|
|
EXTENDED_TICKER_DICT: Any = VAL_KEY.get('fut_ticker_extended')
|
|
EXTENDED_TICKER_DICT: dict = json.loads(s=EXTENDED_TICKER_DICT) if EXTENDED_TICKER_DICT is not None else {}
|
|
if ( EXTENDED_TICKER_DICT.get('symbol', None) != EXTEND.symbol) and not(ALGO_CONFIG.Overrides.Flatten_Open_Positions):
|
|
logging.warning(f'EXTEND Symbol mismatch: {EXTENDED_TICKER_DICT}; expected symbol: {EXTEND.symbol}')
|
|
VAL_KEY.set(name='fr_algo_working_symbol', value=json.dumps(obj={'ASTER': asdict(obj=ASTER), 'EXTEND': asdict(obj=EXTEND)}))
|
|
time.sleep(5)
|
|
continue
|
|
# raise ValueError(f'EXTEND Symbol mismatch: {EXTENDED_TICKER_DICT}; expected symbol: {EXTEND.symbol}')
|
|
|
|
### 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 [] # ty:ignore[invalid-argument-type]
|
|
# 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 [] # ty:ignore[invalid-argument-type]
|
|
|
|
### Manage Local Notionals Using Updates from WS ###
|
|
ASTER_WS_POS_UPDATES: Any = VAL_KEY.get(name='fr_aster_user_positions')
|
|
ASTER_WS_POS_UPDATES: list = json.loads(s=ASTER_WS_POS_UPDATES) if ASTER_WS_POS_UPDATES is not None else []
|
|
EXTEND_WS_POS_UPDATES: Any = VAL_KEY.get('fr_extended_user_positions')
|
|
EXTEND_WS_POS_UPDATES: list = 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: Any = VAL_KEY.get('fr_aster_user_orders')
|
|
ASTER_WS_ORDER_UPDATES: list = json.loads(ASTER_WS_ORDER_UPDATES) if ASTER_WS_ORDER_UPDATES is not None else []
|
|
EXTEND_WS_ORDER_UPDATES: Any = VAL_KEY.get('fr_extended_user_orders')
|
|
EXTEND_WS_ORDER_UPDATES: list = 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):
|
|
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}')
|
|
time.sleep(5)
|
|
continue
|
|
|
|
if len(ASTER_WS_POS_UPDATES) > 0:
|
|
# await get_aster_notional_position()
|
|
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.
|
|
##### NEED TO UPDATE SO IT TAKES THE LATEST MSG, ie drop the WS msg if its older than the exisiting one from the API.
|
|
|
|
if len(EXTEND_WS_POS_UPDATES) > 0:
|
|
await get_extend_notional(resp=EXTEND_WS_POS_UPDATES)
|
|
# await get_extend_notional() # ************** NOT USING WEBSOCKET FEED DUE TO ISSUES WITH IT OVERWRITING API DATA ie the WS just statically shows last update and doesnt pull new when you start the algo.
|
|
### Also WS was just stale and caused issues where it sees a fill then gets new API Collateral (correct) and then the next loop would be the old incorrect collateral in the WS, causing bad orders. Do not have issue on ASTER.
|
|
|
|
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['orderId']
|
|
order_orig_status = o.get('status') if o.get('status') is not None else o['order_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)
|
|
Just_Rejected_Or_Expired = True
|
|
utils.send_tg_alert(f'FR_ALGO - ASTER REJECTED ({order_id})')
|
|
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(resp=ASTER_WS_POS_UPDATES)
|
|
Last_Aster_Fill_Time_Ts = datetime.now().timestamp()*1000
|
|
utils.send_tg_alert(f'FR_ALGO - ASTER PARTIALLY FILLED ({order_id})')
|
|
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(resp=ASTER_WS_POS_UPDATES)
|
|
Last_Aster_Fill_Time_Ts = datetime.now().timestamp()*1000
|
|
utils.send_tg_alert(f'FR_ALGO - ASTER FILLED ({order_id})')
|
|
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: dict = order_update[0]
|
|
order_update_status: str = order_update['status']
|
|
order_status_changed: bool = 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, REJECTED or EXPIRED: {order_id}')
|
|
EXTEND_OPEN_ORDERS.pop(idx)
|
|
Just_Rejected_Or_Expired = True
|
|
utils.send_tg_alert(f'FR_ALGO - EXTEND REJECTED ({order_id})')
|
|
elif order_update_status in ['PARTIALLY_FILLED']:
|
|
logging.info(f'EXTEND ORDER PARTIALLY FILLED: {order_id}')
|
|
# await get_extend_collateral()
|
|
await get_extend_notional()
|
|
utils.send_tg_alert(f'FR_ALGO - EXTEND PARTIALLY FILLED ({order_id})')
|
|
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()
|
|
utils.send_tg_alert(f'FR_ALGO - EXTEND FILLED ({order_id})')
|
|
else:
|
|
logging.critical(f'EXTEND ORDER STATUS CHG TO UNEXPECTED VALUE, KILLING... ({order_id}): {order_orig_status} -> {order_update_status}')
|
|
|
|
|
|
if ALGO_CONFIG.Overrides.Allow_Symbol_Change:
|
|
if (best_symbol_by_exchange_aster.symbol != ASTER.symbol) or (best_symbol_by_exchange_extend.symbol != EXTEND.symbol):
|
|
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_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_Opportunistic = False
|
|
if Open_Symbols:
|
|
logging.info(f'OPEN SYMBOLS TO CLOSE: {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)
|
|
await get_aster_notional_position()
|
|
await get_extend_notional()
|
|
else:
|
|
ASTER = best_symbol_by_exchange_aster
|
|
EXTEND = best_symbol_by_exchange_extend
|
|
|
|
VAL_KEY.set(name='fr_algo_working_symbol', value=json.dumps(obj={'ASTER': asdict(obj=ASTER), 'EXTEND': asdict(obj=EXTEND)}))
|
|
|
|
|
|
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
|
|
|
|
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 = ALGO_CONFIG.Config.Max_Target_Notional
|
|
else:
|
|
ALPHA_CARRY_SIDE = 'SELL'
|
|
ALPHA_TGT_NOTIONAL = ALGO_CONFIG.Config.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 max([ASTER_FUND_RATE, EXTEND_FUND_RATE]) - min([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)
|
|
Flags.NET_FUNDING_IS_ZERO = ( NEXT_NET_FUNDING_RATE >= ( (ALGO_CONFIG.Config.Min_Fund_Rate_Pct_To_Trade*-1) / 100) ) and ( NEXT_NET_FUNDING_RATE <= ( ALGO_CONFIG.Config.Min_Fund_Rate_Pct_To_Trade / 100 ) )
|
|
if Flags.NET_FUNDING_IS_ZERO or ALGO_CONFIG.Overrides.Flatten_Open_Positions or ALGO_CONFIG.Overrides.Flatten_Open_Positions_Opportunistic:
|
|
ALPHA_TGT_NOTIONAL = 0.00
|
|
# if ASTER_OPEN_ORDERS or EXTEND_OPEN_ORDERS:
|
|
# logging.info('NET FUNDING = 0.00; Cancelling Open Orders! then Waiting...')
|
|
# aster_cancel_all_orders()
|
|
# extend_cancel_all_orders()
|
|
# time.sleep(5)
|
|
# 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
|
|
EXTEND_TGT_NOTIONAL = ALPHA_TGT_NOTIONAL
|
|
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'])
|
|
Alpha_Nominator = ASTER_TOB_PX
|
|
Alpha_Denominator = EXTEND_TOB_PX
|
|
ALPHA_RATIO = ASTER_TOB_PX / EXTEND_TOB_PX
|
|
Expected_Alpha = ( ( ASTER_TOB_PX - EXTEND_TOB_PX ) / (( ASTER_TOB_PX + EXTEND_TOB_PX ) / 2) )
|
|
else:
|
|
ASTER_TOB_PX = float(ASTER_TICKER_DICT['best_bid_px'])
|
|
EXTEND_TOB_PX = float(EXTENDED_TICKER_DICT['best_ask_px'])
|
|
Alpha_Nominator = EXTEND_TOB_PX
|
|
Alpha_Denominator = ASTER_TOB_PX
|
|
ALPHA_RATIO = EXTEND_TOB_PX / ASTER_TOB_PX
|
|
Expected_Alpha = ( ( EXTEND_TOB_PX - ASTER_TOB_PX ) / (( ASTER_TOB_PX + EXTEND_TOB_PX ) / 2) )
|
|
else:
|
|
ASTER_TGT_NOTIONAL = ALPHA_TGT_NOTIONAL
|
|
EXTEND_TGT_NOTIONAL = ALPHA_TGT_NOTIONAL*-1
|
|
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'])
|
|
Alpha_Nominator = EXTEND_TOB_PX
|
|
Alpha_Denominator = ASTER_TOB_PX
|
|
ALPHA_RATIO = EXTEND_TOB_PX / ASTER_TOB_PX
|
|
Expected_Alpha = ( ( EXTEND_TOB_PX - ASTER_TOB_PX ) / (( EXTEND_TOB_PX + ASTER_TOB_PX ) / 2) )
|
|
else:
|
|
ASTER_TOB_PX = float(ASTER_TICKER_DICT['best_ask_px'])
|
|
EXTEND_TOB_PX = float(EXTENDED_TICKER_DICT['best_bid_px'])
|
|
Alpha_Nominator = ASTER_TOB_PX
|
|
Alpha_Denominator = EXTEND_TOB_PX
|
|
ALPHA_RATIO = ASTER_TOB_PX / EXTEND_TOB_PX
|
|
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.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) )
|
|
# 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))) ) 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
|
|
Currently_Hedged = Hedge_Ratio < 1.00
|
|
|
|
def print_summary(use_logging: bool = False):
|
|
OUT: Any = logging.info if use_logging else print
|
|
|
|
OUT(f'''
|
|
LOOP SLEEP (SEC): {ALGO_CONFIG.Config.Loop_Sleep_Sec}
|
|
FLIP SIDES FOR TESTING?: {ALGO_CONFIG.Overrides.Flip_Side_For_Testing}; ASTER ORDER ENABLED? {ALGO_CONFIG.Overrides.Allow_Ordering_Aster}; EXTEND ORDER ENABLED? {ALGO_CONFIG.Overrides.Allow_Ordering_Extend}
|
|
{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]
|
|
ASTER: {'LONG PAYS SHORT' if ASTER_FUND_RATE > 0 else 'SHORT PAYS LONG'} | EXTEND: {'LONG PAYS SHORT' if EXTEND_FUND_RATE > 0 else 'SHORT PAYS LONG'}
|
|
ASTER: [ Notional Position $ : {ASTER_NOTIONAL_POSITION:.4f} ] | EXTEND: [ Notional Position $ : {EXTEND_NOTIONAL_POSITION:.4f} ]
|
|
|
|
SAME TIME? : {FUNDINGS_AT_SAME_TIME_NEXT_HR} [ Minutes Between Fundings: {min_between_fundings} ]
|
|
NET FUNDING : {NEXT_NET_FUNDING_RATE:.6%} [{NEXT_NET_FUNDING_RATE*10_000:.2f}bps] [{NEXT_NET_FUNDING_RATE*1_000_000:.0f}pips]; Is Zero?: {Flags.NET_FUNDING_IS_ZERO} [Min: {ALGO_CONFIG.Config.Min_Fund_Rate_Pct_To_Trade}]
|
|
ALPHA SIDE : {ALPHA_EXCH} [{ALPHA_CARRY_SIDE}]
|
|
|
|
TGT NOTIONAL: $ {abs(ALPHA_TGT_NOTIONAL):.2f}; Flatten Open Positions Flag? {ALGO_CONFIG.Overrides.Flatten_Open_Positions}; Opportunistic? {ALGO_CONFIG.Overrides.Flatten_Open_Positions_Opportunistic}
|
|
|
|
ASTER: {ASTER_NOTIONAL_POSITION:.4f} -> {ASTER_TGT_NOTIONAL:.2f} [ Remain: {ASTER_TGT_TAIL:.4f} ] | EXTEND: {EXTEND_NOTIONAL_POSITION:.4f} -> {EXTEND_TGT_NOTIONAL:.2f} [ Remain: {EXTEND_TGT_TAIL:.4f} ]
|
|
ASTER: {ASTER_TGT_NOTIONAL:.2f} - {ASTER_NOTIONAL_POSITION:.2f} + {ASTER_UNREALIZED_PNL:.2f} = {ASTER_TGT_TAIL:2f} | EXTEND: {EXTEND_TGT_NOTIONAL:.2f} - {EXTEND_NOTIONAL_POSITION:.2f} + {EXTEND_UNREALIZED_PNL:.2f} = {EXTEND_TGT_TAIL:2f}
|
|
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.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})
|
|
|
|
--- ASTER OPEN ORDERS ---
|
|
{ASTER_OPEN_ORDERS}
|
|
|
|
--- EXTEND OPEN ORDERS ---
|
|
{EXTEND_OPEN_ORDERS}
|
|
''')
|
|
# ASTER: [ Available Collateral: {ASTER_AVAIL_COLLATERAL:.4f} ] | EXTEND: [ Available Collateral: {EXTEND_AVAIL_COLLATERAL:.4f} ]
|
|
# Try Making Hedge Order Contingent on Alpha Order Fills (Basically Hedge has to wait for sig Diff in Balance to order.) would improve when extended is thin (Overnight).
|
|
if ALGO_CONFIG.Logging.Log_Summary_Each_Loop:
|
|
print_summary(use_logging=True)
|
|
if ALGO_CONFIG.Logging.Print_Summary_Each_Loop:
|
|
print_summary(use_logging=False)
|
|
# print_summary()
|
|
|
|
|
|
### ROUTES ###
|
|
# Just_Rejected_Or_Expired
|
|
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( ( 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):
|
|
await aster_cancel_all_orders()
|
|
await extend_cancel_all_orders()
|
|
logging.info('Expected_Alpha went away with open orders...cancelling since we are currently hedged...')
|
|
# time.sleep( (1/1000)*100 ) # 100ms wait for ws cancel response
|
|
else:
|
|
# logging.info(f'*** Alpha Ratio HIT - LETS ORDER: {ALPHA_RATIO:.8f}')
|
|
# ASTER
|
|
if ASTER_TGT_TAIL_ORDERABLE and ALGO_CONFIG.Overrides.Allow_Ordering_Aster:
|
|
# if ALGO_CONFIG.Overrides.Allow_Ordering_Aster:
|
|
symbol = ASTER.symbol
|
|
side = 'BUY' if ASTER_TGT_TAIL_BASE_QTY > 0.00 else 'SELL'
|
|
# qty = str(abs(ASTER_TGT_TAIL_BASE_QTY))
|
|
qty = Decimal(value=str(abs(ASTER_TGT_TAIL_BASE_QTY)))
|
|
price = ASTER_TOB_PX - ( float(ASTER.min_price)*int(ALGO_CONFIG.Config.Price_Worsener_Aster) ) if side == 'BUY' else ASTER_TOB_PX + ( float(ASTER.min_price)*int(ALGO_CONFIG.Config.Price_Worsener_Aster) )
|
|
|
|
if abs( ( float(ASTER_TGT_TAIL_BASE_QTY)*float(price) ) + ASTER_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 - ASTER: {ASTER_NOTIONAL_POSITION} + {float(ASTER_TGT_TAIL_BASE_QTY)*float(price)} (qty: {float(ASTER_TGT_TAIL_BASE_QTY):.2f}; px: {float(price):.2f})')
|
|
await kill_algo()
|
|
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'])
|
|
min_price = ASTER.min_price
|
|
min_price = int(min_price) if min_price == int(min_price) else min_price
|
|
if Decimal(str( float(open_order_px) - float(price) )).quantize(Decimal(str(min_price)), rounding=ROUND_HALF_UP) == 0.00:
|
|
# if round(open_order_px - float(price), len(str(ASTER.min_price)) - 2 ) == 0.00:
|
|
if ALGO_CONFIG.Logging.Print_Summary_Each_Loop:
|
|
print('ASTER OPEN ORDER NO PX CHG; SKIPPING')
|
|
place_order = False
|
|
else:
|
|
cancel_order: dict = {
|
|
"url": "/fapi/v3/order",
|
|
"method": "DELETE",
|
|
"params": {
|
|
'symbol': ASTER.symbol,
|
|
'orderId': open_order_id,
|
|
}
|
|
}
|
|
cr: dict = await aster_auth.post_authenticated_url(cancel_order) # ty:ignore[invalid-assignment]
|
|
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:
|
|
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 and (qty*price) > ASTER.min_notional:
|
|
reduceOnly = False
|
|
else:
|
|
reduceOnly = True
|
|
|
|
post_order = {
|
|
"url": "/fapi/v3/order",
|
|
"method": "POST",
|
|
"params": {
|
|
'symbol': symbol,
|
|
'side': side,
|
|
'type': 'LIMIT',
|
|
'timeInForce': 'GTX',
|
|
'quantity': qty,
|
|
'price': price,
|
|
'reduceOnly': reduceOnly
|
|
}
|
|
}
|
|
order_resp: dict = await aster_auth.post_authenticated_url(post_order) # ty:ignore[invalid-assignment]
|
|
if order_resp.get('orderId', None) is not None:
|
|
order_resp['original_price'] = price
|
|
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:.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:
|
|
logging.critical(f'*** Aster Order Response Abnormal: {order_resp}; post_order: {post_order}')
|
|
await kill_algo()
|
|
else:
|
|
pass
|
|
# logging.warning('ASTER PLACE ORDER CHECKS FAILED, SKIPPING')
|
|
|
|
elif not(ASTER_TGT_TAIL_ORDERABLE) and ASTER_OPEN_ORDERS:
|
|
### 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 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
|
|
if Time_Since_Last_Aster_Fill_ms > ( 1000 * ALGO_CONFIG.Config.Switch_To_Taker_Seconds ): # Change to allow taker orders if its been more than x seconds
|
|
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 = 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()
|
|
if EXTEND_OPEN_ORDERS:
|
|
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'])
|
|
|
|
# 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 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:
|
|
if ALGO_CONFIG.Logging.Print_Summary_Each_Loop:
|
|
print('EXTEND OPEN ORDER NO PX CHG; SKIPPING')
|
|
else:
|
|
try:
|
|
if abs(float(EXTEND_NOTIONAL_POSITION) + (float(qty)*float(price))) < abs(float(EXTEND_NOTIONAL_POSITION)):
|
|
reduce_only = True
|
|
else:
|
|
reduce_only = False
|
|
|
|
# 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)),
|
|
price=Decimal(str(price)),
|
|
side=side,
|
|
taker_fee=taker_fee,
|
|
previous_order_id=open_order_id,
|
|
post_only=post_only,
|
|
reduce_only=reduce_only
|
|
)
|
|
except Exception as e:
|
|
logging.error(f'EXTEND ORDER PLACEMENT FAILED: {e}')
|
|
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)
|
|
|
|
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)
|
|
order_dict['qty'] = str(qty)
|
|
order_dict['filled_qty'] = str(0)
|
|
order_dict['side'] = str(side)
|
|
|
|
EXTEND_OPEN_ORDERS.append(order_dict)
|
|
Just_Rejected_Or_Expired = False
|
|
utils.send_tg_alert(f'FR_ALGO - EXTEND Order ({order_dict.get('id', None)}). Start_$: {EXTEND_NOTIONAL_POSITION:.2f}; Value: {float(EXTEND_TGT_TAIL_BASE_QTY)*float(price):.2f}; Price: {float(price):.2f}')
|
|
logging.info(f'EXTEND ORDER PLACED SUCCESS: {order_dict}')
|
|
print_summary(use_logging=True)
|
|
else:
|
|
logging.critical(f'*** Extend Order Response Abnormal: {order_resp};')
|
|
await kill_algo()
|
|
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()
|
|
|
|
|
|
if ASTER_OPEN_ORDERS or EXTEND_OPEN_ORDERS:
|
|
continue
|
|
else:
|
|
time.sleep(ALGO_CONFIG.Config.Loop_Sleep_Sec)
|
|
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')
|
|
await kill_algo()
|
|
except Exception as e:
|
|
logging.error(traceback.format_exc())
|
|
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()
|
|
|
|
|
|
### MAIN STARTUP ###
|
|
async def main():
|
|
global EXTEND_CLIENT
|
|
global VAL_KEY
|
|
global CON
|
|
global ALGO_CONFIG
|
|
global ASTER
|
|
global EXTEND
|
|
global Open_Symbols
|
|
|
|
|
|
_, 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')
|
|
|
|
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: {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)}))
|
|
|
|
async with engine.connect() as CON:
|
|
### ASTER SETUP ###
|
|
# await get_aster_collateral()
|
|
await get_aster_notional_position()
|
|
await get_aster_exch_info()
|
|
await get_aster_open_orders()
|
|
### EXTEND SETUP ###
|
|
# await get_extend_collateral()
|
|
await get_extend_notional()
|
|
await get_extend_exch_info()
|
|
await get_extend_open_orders()
|
|
|
|
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())
|