Files
Funding_Rate/main.py

282 lines
10 KiB
Python
Raw Normal View History

2026-04-21 20:22:33 +00:00
import asyncio
import json
import logging
import math
import os
import time
import traceback
from dataclasses import asdict, dataclass
from datetime import datetime, timezone
from typing import AsyncContextManager
from dotenv import load_dotenv
import numpy as np
import pandas as pd
import requests
# import talib
import valkey
from sqlalchemy import text
from sqlalchemy.ext.asyncio import create_async_engine
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 ###
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
ASTER_NOTIONAL_POSITION = 0
EXTEND_AVAIL_COLLATERAL = 0
EXTEND_NOTIONAL_POSITION = 0
ASTER_OPEN_POSITIONS = []
EXTEND_OPEN_POSITIONS = []
ASTER_OPEN_ORDERS = []
EXTEND_OPEN_ORDERS = []
### FLAGS ###
LIQUIDATE_POS_AND_KILL_ALGO_FLAG: bool = False
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
# 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-23 06:39:51 +00:00
pass
async def extend_remainder_route():
pass
async def run_algo():
2026-04-21 20:22:33 +00:00
try:
while True:
loop_start = time.time()
print('__________Start___________')
2026-04-23 03:11:52 +00:00
ASTER_FUND_RATE_DICT = json.loads(VAL_KEY.get('fund_rate_aster'))
ASTER_TICKER_DICT = json.loads(VAL_KEY.get('fut_ticker_aster'))
# print(f'ASTER FUND RATE: {ASTER_FUND_RATE}')
# print(f'ASTER TICKER: {ASTER_TICKER}')
2026-04-22 05:24:40 +00:00
2026-04-23 03:11:52 +00:00
EXTENDED_FUND_RATE_DICT = json.loads(VAL_KEY.get('fund_rate_extended'))
EXTENDED_TICKER_DICT = json.loads(VAL_KEY.get('fut_ticker_extended'))
# print(f'EXTENDED FUND RATE: {EXTENDED_FUND_RATE}')
# print(f'EXTENDED TICKER: {EXTENDED_TICKER}')
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))
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'
FUNDINGS_AT_SAME_TIME_NEXT_HR = ( (ASTER_FUND_RATE_TIME < 60*60*1000) and (EXTEND_FUND_RATE < 60*60*1000) )
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)
if ALPHA_EXCH == 'EXTEND':
ASTER_TGT_NOTIONAL = ALPHA_TGT_NOTIONAL*-1
EXTEND_TGT_NOTIONAL = ALPHA_TGT_NOTIONAL
else:
ASTER_TGT_NOTIONAL = ALPHA_TGT_NOTIONAL
EXTEND_TGT_NOTIONAL = ALPHA_TGT_NOTIONAL*-1
ASTER_TGT_TAIL = ASTER_TGT_NOTIONAL - ASTER_NOTIONAL_POSITION
EXTEND_TGT_TAIL = EXTEND_TGT_NOTIONAL - EXTEND_NOTIONAL_POSITION
ASTER_TGT_TAIL_ORDERABLE = abs(ASTER_TGT_TAIL) >= ASTER_MIN_ORDER_QTY
EXTEND_TGT_TAIL_ORDERABLE = abs(EXTEND_TGT_TAIL) >= 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()):})
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: {ASTER_PAYOUT_DIRECTION_STR} | EXTEND: {EXTEND_PAYOUT_DIRECTION_STR}
ASTER: [ Available Collateral: {ASTER_AVAIL_COLLATERAL:.4f} ] | EXTEND: [ Available Collateral: {EXTEND_AVAIL_COLLATERAL:.4f} ]
ASTER: [ Notional Position $ : {ASTER_NOTIONAL_POSITION:.4f} ] | EXTEND: [ Notional Position $ : {EXTEND_NOTIONAL_POSITION:.4f} ]
SAME TIME? : {FUNDINGS_AT_SAME_TIME_NEXT_HR}
NET FUNDING : {NEXT_NET_FUNDING_RATE:.6%} [{NEXT_NET_FUNDING_RATE*10_000:.2f}bps] [{NEXT_NET_FUNDING_RATE*1_000_000:.0f}pips]
ALPHA SIDE : {ALPHA_EXCH} [{ALPHA_CARRY_SIDE}]
TGT NOTIONAL: $ {MAX_TARGET_NOTIONAL}
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} ]
ASTER: {ASTER_TGT_TAIL:.4f} > {ASTER_MIN_ORDER_QTY:.4f} min [ Order: {ASTER_TGT_TAIL_ORDERABLE} ] | EXTEND: {EXTEND_TGT_TAIL:.4f} > {EXTEND_MIN_ORDER_QTY:.4f} min [ Order: {EXTEND_TGT_TAIL_ORDERABLE} ]
2026-04-23 03:11:52 +00:00
''')
2026-04-21 20:22:33 +00:00
2026-04-23 06:39:51 +00:00
### SCAN VALKEY USER FEEDS FOR BALANCE UPDATES ###
# or just to begin hit the rest API before ordering and update bals then
### ROUTES ###
if ASTER_TGT_TAIL_ORDERABLE:
await aster_remainder_route()
if EXTEND_TGT_TAIL_ORDERABLE:
await extend_remainder_route()
print(f'__________ End ___________ (Algo Engine ms: {(time.time() - loop_start)*1000})')
2026-04-21 20:22:33 +00:00
time.sleep(5)
except KeyboardInterrupt:
print('...algo stopped')
# await cancel_all_orders(CLIENT=CLIENT)
except Exception as e:
logging.critical(f'*** ALGO ENGINE CRASHED: {e}')
logging.error(traceback.format_exc())
# await cancel_all_orders(CLIENT=CLIENT)
2026-04-23 06:39:51 +00:00
### WALLLET ###
async def get_aster_collateral():
global ASTER_AVAIL_COLLATERAL
fut_acct_balances = {
"url": "/fapi/v3/balance",
"method": "GET",
"params": {}
}
r = 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_notional_position():
global ASTER_NOTIONAL_POSITION
global ASTER_MULT
fut_acct_positionRisk = {
"url": "/fapi/v3/positionRisk",
"method": "GET",
"params": {}
}
r = aster_auth.post_authenticated_url(fut_acct_positionRisk)
d = [d for d in r if d.get('symbol', None) == ASTER_TICKER][0]
ASTER_NOTIONAL_POSITION = float(d.get('notional' ,0))
ASTER_MULT = float(d.get('leverage', ASTER_MULT))
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_notional():
global EXTEND_NOTIONAL_POSITION
global EXTEND_MULT
get_pos = dict(await EXTEND_CLIENT.account.get_positions()).get('data', {})
pos_dict = [d for d in get_pos if d.get('market') == EXTEND_TICKER]
if pos_dict:
pos_dict = pos_dict[0]
EXTEND_NOTIONAL_POSITION = pos_dict.get('value', 0)
EXTEND_MULT = pos_dict.get('leverage', EXTEND_MULT)
else:
EXTEND_NOTIONAL_POSITION = 0
### EXCHANGE INFO ###
async def get_aster_exch_info():
global ASTER_MIN_ORDER_QTY
fut_acct_exchangeInfo = {
"url": "/fapi/v3/exchangeInfo",
"method": "GET",
"params": {}
}
r = aster_auth.post_authenticated_url(fut_acct_exchangeInfo)
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-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:
# await create_executions_orders_table(CON=CON)
2026-04-23 06:39:51 +00:00
await get_aster_collateral()
await get_aster_notional_position()
await get_extend_collateral()
await get_extend_notional()
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())