refactor algo orchestrator and hedge ratio bug fix
This commit is contained in:
@@ -1,13 +1,25 @@
|
|||||||
{
|
{
|
||||||
"Config_Updated_Timestamp": 1777098091913,
|
"Updated_Timestamp": 1777433705326,
|
||||||
"Allow_Ordering_Aster": false,
|
"Config": {
|
||||||
"Allow_Ordering_Extend": false,
|
"Loop_Sleep_Sec": 0.0,
|
||||||
"Loop_Sleep_Sec": 5.00,
|
"Max_Order_Over_Notional_Ratio": 1.05,
|
||||||
"Max_Target_Notional": 0.00,
|
"Max_Target_Notional": 0.0,
|
||||||
"Min_Time_To_Funding_Minutes": 10,
|
"Min_Time_To_Funding_Minutes": 60,
|
||||||
|
"Min_Fund_Rate_Pct_To_Trade": 0.0,
|
||||||
"Price_Worsener_Aster": 0.0,
|
"Price_Worsener_Aster": 0.0,
|
||||||
"Price_Worsener_Extend": 0.0,
|
"Price_Worsener_Extend": -0.1,
|
||||||
"Target_Open_Cash_Position": 10,
|
"Switch_To_Taker_Seconds": 1,
|
||||||
"Print_Summary_Each_Loop" : true,
|
"Target_Open_Cash_Position": 10
|
||||||
|
},
|
||||||
|
"Logging": {
|
||||||
|
"Log_Summary_Each_Loop": false,
|
||||||
|
"Print_Summary_Each_Loop": false
|
||||||
|
},
|
||||||
|
"Overrides": {
|
||||||
|
"Allow_Ordering_Aster": true,
|
||||||
|
"Allow_Ordering_Extend": true,
|
||||||
|
"Flatten_Open_Positions": false,
|
||||||
"Flip_Side_For_Testing": false
|
"Flip_Side_For_Testing": false
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
29
algo.ipynb
29
algo.ipynb
@@ -2,7 +2,7 @@
|
|||||||
"cells": [
|
"cells": [
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 4,
|
"execution_count": 22,
|
||||||
"id": "d1eed397",
|
"id": "d1eed397",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
@@ -13,12 +13,13 @@
|
|||||||
"import valkey\n",
|
"import valkey\n",
|
||||||
"\n",
|
"\n",
|
||||||
"with open('algo_config.json', 'r', encoding='utf-8') as file:\n",
|
"with open('algo_config.json', 'r', encoding='utf-8') as file:\n",
|
||||||
" ALGO_CONFIG = json.load(file, object_hook=lambda d: structs.Algo_Config(**d))"
|
" ALGO_CONFIG = json.load(file)\n",
|
||||||
|
" ALGO_CONFIG = structs.Algo_Config(**ALGO_CONFIG)"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 5,
|
"execution_count": 23,
|
||||||
"id": "c6151613",
|
"id": "c6151613",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
@@ -38,23 +39,31 @@
|
|||||||
"1"
|
"1"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"execution_count": 12,
|
"execution_count": 4,
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"output_type": "execute_result"
|
"output_type": "execute_result"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"source": [
|
"source": [
|
||||||
"config_update = {\n",
|
"config_update = {\n",
|
||||||
|
" # 'Config': {\n",
|
||||||
|
" # 'Loop_Sleep_Sec': 0.00,\n",
|
||||||
" # 'Min_Time_To_Funding_Minutes': 60,\n",
|
" # 'Min_Time_To_Funding_Minutes': 60,\n",
|
||||||
|
" # 'Min_Fund_Rate_Pct_To_Trade': 0.001,\n",
|
||||||
|
" # 'Price_Worsener_Extend': 0.0,\n",
|
||||||
|
" # 'Price_Worsener_Aster': 0.0,\n",
|
||||||
|
" # 'Switch_To_Taker_Seconds': 1,\n",
|
||||||
|
" # },\n",
|
||||||
|
" 'Logging': {\n",
|
||||||
|
" 'Log_Summary_Each_Loop': False,\n",
|
||||||
|
" 'Print_Summary_Each_Loop': True,\n",
|
||||||
|
" },\n",
|
||||||
|
" # 'Overrides': {\n",
|
||||||
" # 'Allow_Ordering_Aster': True,\n",
|
" # 'Allow_Ordering_Aster': True,\n",
|
||||||
" # 'Allow_Ordering_Extend': True,\n",
|
" # 'Allow_Ordering_Extend': True,\n",
|
||||||
" # 'Loop_Sleep_Sec': 0.00,\n",
|
|
||||||
" # 'Min_Fund_Rate_Pct_To_Trade': 0.001,\n",
|
|
||||||
" # 'Flip_Side_For_Testing': False,\n",
|
" # 'Flip_Side_For_Testing': False,\n",
|
||||||
" # 'Price_Worsener_Extend': 0.0,\n",
|
" # 'Flatten_Open_Positions': False,\n",
|
||||||
" # 'Log_Summary_Each_Loop': False,\n",
|
" # },\n",
|
||||||
" 'Print_Summary_Each_Loop': True,\n",
|
|
||||||
" 'Flatten_Open_Positions': False,\n",
|
|
||||||
"}\n",
|
"}\n",
|
||||||
"VAL_KEY.publish('fr_orchestrator_input', json.dumps(config_update))"
|
"VAL_KEY.publish('fr_orchestrator_input', json.dumps(config_update))"
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,16 +1,24 @@
|
|||||||
{
|
{
|
||||||
"Config_Updated_Timestamp": 1777411434591,
|
"Updated_Timestamp": 1777478176165,
|
||||||
"Allow_Ordering_Aster": true,
|
"Config": {
|
||||||
"Allow_Ordering_Extend": true,
|
|
||||||
"Loop_Sleep_Sec": 0.0,
|
"Loop_Sleep_Sec": 0.0,
|
||||||
|
"Max_Order_Over_Notional_Ratio": 1.05,
|
||||||
"Max_Target_Notional": 0.0,
|
"Max_Target_Notional": 0.0,
|
||||||
"Min_Time_To_Funding_Minutes": 60,
|
"Min_Time_To_Funding_Minutes": 60,
|
||||||
"Min_Fund_Rate_Pct_To_Trade": 0.0,
|
"Min_Fund_Rate_Pct_To_Trade": 0.0,
|
||||||
"Price_Worsener_Aster": 0.0,
|
"Price_Worsener_Aster": 0.0,
|
||||||
"Price_Worsener_Extend": -0.1,
|
"Price_Worsener_Extend": -0.1,
|
||||||
"Target_Open_Cash_Position": 10,
|
"Switch_To_Taker_Seconds": 1,
|
||||||
|
"Target_Open_Cash_Position": 10
|
||||||
|
},
|
||||||
|
"Logging": {
|
||||||
"Log_Summary_Each_Loop": false,
|
"Log_Summary_Each_Loop": false,
|
||||||
"Print_Summary_Each_Loop": true,
|
"Print_Summary_Each_Loop": true
|
||||||
|
},
|
||||||
|
"Overrides": {
|
||||||
|
"Allow_Ordering_Aster": true,
|
||||||
|
"Allow_Ordering_Extend": true,
|
||||||
"Flatten_Open_Positions": false,
|
"Flatten_Open_Positions": false,
|
||||||
"Flip_Side_For_Testing": false
|
"Flip_Side_For_Testing": false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -26,7 +26,6 @@ load_dotenv()
|
|||||||
LOG_FILEPATH: str = os.getenv("LOGS_PATH") + '/Fund_Rate_Algo_Orchestrator.log'
|
LOG_FILEPATH: str = os.getenv("LOGS_PATH") + '/Fund_Rate_Algo_Orchestrator.log'
|
||||||
|
|
||||||
ALGO_CONFIG: None | dict
|
ALGO_CONFIG: None | dict
|
||||||
# ALGO_CONFIG: None | Algo_Config = None
|
|
||||||
|
|
||||||
async def orchestrator() -> None:
|
async def orchestrator() -> None:
|
||||||
global ALGO_CONFIG
|
global ALGO_CONFIG
|
||||||
@@ -35,7 +34,7 @@ async def orchestrator() -> None:
|
|||||||
VK_PUBSUB = VAL_KEY.pubsub()
|
VK_PUBSUB = VAL_KEY.pubsub()
|
||||||
VK_PUBSUB.subscribe(VK_IN)
|
VK_PUBSUB.subscribe(VK_IN)
|
||||||
|
|
||||||
print(f"Subscribed to '{VK_IN}'. Waiting for messages...")
|
logging.info(f"Subscribed to '{VK_IN}'. Waiting for messages...")
|
||||||
|
|
||||||
for message in VK_PUBSUB.listen():
|
for message in VK_PUBSUB.listen():
|
||||||
if message['type'] == 'message':
|
if message['type'] == 'message':
|
||||||
@@ -46,7 +45,7 @@ async def orchestrator() -> None:
|
|||||||
with open('/algo_local_drive/algo_config.json', 'r', encoding='utf-8') as f:
|
with open('/algo_local_drive/algo_config.json', 'r', encoding='utf-8') as f:
|
||||||
# ALGO_CONFIG = json.load(f, object_hook=lambda d: Algo_Config(**d))
|
# ALGO_CONFIG = json.load(f, object_hook=lambda d: Algo_Config(**d))
|
||||||
ALGO_CONFIG = json.load(f)
|
ALGO_CONFIG = json.load(f)
|
||||||
ALGO_CONFIG['Config_Updated_Timestamp'] = timestamp
|
ALGO_CONFIG['Updated_Timestamp'] = timestamp
|
||||||
|
|
||||||
for k, v in data.items():
|
for k, v in data.items():
|
||||||
if ALGO_CONFIG.get(k, None) is not None:
|
if ALGO_CONFIG.get(k, None) is not None:
|
||||||
@@ -54,12 +53,11 @@ async def orchestrator() -> None:
|
|||||||
|
|
||||||
VAL_KEY.set(VK_OUT, json.dumps(ALGO_CONFIG))
|
VAL_KEY.set(VK_OUT, json.dumps(ALGO_CONFIG))
|
||||||
with open('/algo_local_drive/algo_config.json', 'w', encoding='utf-8') as f:
|
with open('/algo_local_drive/algo_config.json', 'w', encoding='utf-8') as f:
|
||||||
# print('SAVING FILE')
|
|
||||||
json.dump(ALGO_CONFIG, f, indent=4)
|
json.dump(ALGO_CONFIG, f, indent=4)
|
||||||
print(f"Algo Config Updated @ {timestamp}; {data}")
|
logging.info(f"Algo Config Updated @ {timestamp}; {data}")
|
||||||
|
|
||||||
except valkey.exceptions.ConnectionError as e:
|
except valkey.exceptions.ConnectionError as e:
|
||||||
print(f"Could not connect to Valkey. Please check the publish server is up; {e}")
|
logging.info(f"Could not connect to Valkey. Please check the publish server is up; {e}")
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
logging.info('ORCHESTRATOR SHUTTING DOWN...')
|
logging.info('ORCHESTRATOR SHUTTING DOWN...')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -78,7 +76,7 @@ async def main() -> None:
|
|||||||
with open('/algo_local_drive/algo_config.json', 'r', encoding='utf-8') as f:
|
with open('/algo_local_drive/algo_config.json', 'r', encoding='utf-8') as f:
|
||||||
# ALGO_CONFIG = json.load(f, object_hook=lambda d: Algo_Config(**d))
|
# ALGO_CONFIG = json.load(f, object_hook=lambda d: Algo_Config(**d))
|
||||||
ALGO_CONFIG = json.load(f)
|
ALGO_CONFIG = json.load(f)
|
||||||
ALGO_CONFIG['Config_Updated_Timestamp'] = round(datetime.now().timestamp()*1000)
|
ALGO_CONFIG['Updated_Timestamp'] = round(datetime.now().timestamp()*1000)
|
||||||
|
|
||||||
# async with engine.connect() as CON:
|
# async with engine.connect() as CON:
|
||||||
await orchestrator()
|
await orchestrator()
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# tail -f Fund_Rate_Algo.log Fund_Rate_Aster_User.log Fund_Rate_Aster.log Fund_Rate_Extended_FR.log Fund_Rate_Extended_OB.log Fund_Rate_Extended_Trades.log Fund_Rate_Extended_User.log
|
# tail -f Fund_Rate_Algo_Orchestrator.log Fund_Rate_Algo.log Fund_Rate_Aster_User.log Fund_Rate_Aster.log Fund_Rate_Extended_FR.log Fund_Rate_Extended_OB.log Fund_Rate_Extended_Trades.log Fund_Rate_Extended_User.log
|
||||||
|
|
||||||
services:
|
services:
|
||||||
# algo:
|
# algo:
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
172
engine_best_funding_rate.py
Normal file
172
engine_best_funding_rate.py
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
import asyncio
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
import traceback
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import AsyncContextManager
|
||||||
|
|
||||||
|
import pandas as pd
|
||||||
|
import requests
|
||||||
|
import valkey
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
# from sqlalchemy.ext.asyncio import create_async_engine
|
||||||
|
|
||||||
|
### Structs ###
|
||||||
|
@dataclass(kw_only=False)
|
||||||
|
class Asset_Leverage:
|
||||||
|
exchange: str
|
||||||
|
lh_asset: str
|
||||||
|
rh_asset: str
|
||||||
|
max_leverage: int
|
||||||
|
max_notional: float
|
||||||
|
# max_leverage_notional: list = field(default_factory=list)
|
||||||
|
|
||||||
|
### MANUAL LEVERAGE DATA ###
|
||||||
|
LEVERAGE_BY_EXCH: list[Asset_Leverage] = [
|
||||||
|
Asset_Leverage('ASTER', 'BTC' , 'USDT', 150, 300_000), Asset_Leverage('EXTEND', 'BTC' , 'USD', 50, 4_000_000),
|
||||||
|
Asset_Leverage('ASTER', 'ETH' , 'USDT', 150, 300_000), Asset_Leverage('EXTEND', 'ETH' , 'USD', 50, 4_000_000),
|
||||||
|
Asset_Leverage('ASTER', 'LIT' , 'USDT', 50 , 2_500 ), Asset_Leverage('EXTEND', 'LIT' , 'USD', 25, 400_000 ),
|
||||||
|
Asset_Leverage('ASTER', 'CHIP' , 'USDT', 50 , 5_000 ), Asset_Leverage('EXTEND', 'CHIP' , 'USD', 5 , 100_000 ),
|
||||||
|
Asset_Leverage('ASTER', 'XAG' , 'USDT', 100, 50_000 ), Asset_Leverage('EXTEND', 'XAG' , 'USD', 10, 1_000_000),
|
||||||
|
Asset_Leverage('ASTER', '4' , 'USDT', 50 , 5_000 ), Asset_Leverage('EXTEND', '4' , 'USD', 5 , 100_000 ),
|
||||||
|
Asset_Leverage('ASTER', 'XPT' , 'USDT', 3 , 30_000 ), Asset_Leverage('EXTEND', 'XPT' , 'USD', 5 , 1_000_000),
|
||||||
|
Asset_Leverage('ASTER', 'XMR' , 'USDT', 50 , 10_000 ), Asset_Leverage('EXTEND', 'XMR' , 'USD', 25, 400_000 ),
|
||||||
|
Asset_Leverage('ASTER', 'WLFI' , 'USDT', 25 , 104_869), Asset_Leverage('EXTEND', 'WLFI' , 'USD', 10, 250_000 ),
|
||||||
|
Asset_Leverage('ASTER', 'TRUMP', 'USDT', 50 , 5_567 ), Asset_Leverage('EXTEND', 'TRUMP', 'USD', 25, 400_000 ),
|
||||||
|
Asset_Leverage('ASTER', 'INIT' , 'USDT', 50 , 5_000 ), Asset_Leverage('EXTEND', 'INIT' , 'USD', 5 , 100_000 ),
|
||||||
|
Asset_Leverage('ASTER', 'ZORA' , 'USDT', 5 , 100_000), Asset_Leverage('EXTEND', 'ZORA' , 'USD', 5 , 100_000 ),
|
||||||
|
Asset_Leverage('ASTER', 'ZEC' , 'USDT', 75 , 6_250 ), Asset_Leverage('EXTEND', 'ZEC' , 'USD', 10, 250_000 ),
|
||||||
|
]
|
||||||
|
df_leverage_by_exch = pd.DataFrame(LEVERAGE_BY_EXCH)
|
||||||
|
|
||||||
|
### Database ###
|
||||||
|
# CON: AsyncContextManager | None = None
|
||||||
|
VAL_KEY = None
|
||||||
|
VK_OUT = 'fr_engine_best_fund_rate_output'
|
||||||
|
|
||||||
|
### Logging ###
|
||||||
|
load_dotenv()
|
||||||
|
LOG_FILEPATH: str = os.getenv("LOGS_PATH") + '/Fund_Rate_Engine_BFR.log'
|
||||||
|
|
||||||
|
### CONSTANTS ###
|
||||||
|
LOOP_SLEEP_SEC: int = 5
|
||||||
|
REFRESH_MKT_INFO_EVERY_SEC: int = 90
|
||||||
|
REFRESH_MKT_VOLUME_EVERY_SEC: int = 30
|
||||||
|
|
||||||
|
### GLOBALS ###
|
||||||
|
Mkt_Info_Last_Refresh_TS_ms: int
|
||||||
|
Mkt_Volume_Last_Refresh_TS_ms: int
|
||||||
|
|
||||||
|
### Funcs - Load Data ###
|
||||||
|
def get_extended_markets_info() -> pd.DataFrame:
|
||||||
|
global Mkt_Info_Last_Refresh_TS_ms
|
||||||
|
|
||||||
|
r = json.loads(requests.get('https://api.starknet.extended.exchange/api/v1/info/markets').text)
|
||||||
|
|
||||||
|
df = pd.DataFrame(r['data'])
|
||||||
|
df['funding_rate'] = df['marketStats'].apply(lambda x: x.get('fundingRate',{}))
|
||||||
|
df['funding_rate_ts'] = df['marketStats'].apply(lambda x: x.get('nextFundingRate',{}))
|
||||||
|
df['min_order_size'] = df['tradingConfig'].apply(lambda x: x.get('minOrderSize',{}))
|
||||||
|
df['min_price_change'] = df['tradingConfig'].apply(lambda x: x.get('minPriceChange',{}))
|
||||||
|
df['max_leverage'] = df['tradingConfig'].apply(lambda x: x.get('maxLeverage',{}))
|
||||||
|
|
||||||
|
Mkt_Info_Last_Refresh_TS_ms = round(datetime.now().timestamp() * 1000)
|
||||||
|
|
||||||
|
print('Extend markets info refreshed successfully')
|
||||||
|
|
||||||
|
return df
|
||||||
|
|
||||||
|
def load_aster_current_fr() -> pd.DataFrame:
|
||||||
|
df = pd.DataFrame(json.loads(VAL_KEY.get('fund_rate_aster_all')))
|
||||||
|
|
||||||
|
df = df[['s','E','r','T']].rename({'s':'symbol','E':'funding_rate_updated_ts_ms','r':'funding_rate','T':'next_funding_ts'}, axis=1)
|
||||||
|
df['funding_rate_updated_dt'] = pd.to_datetime(df['funding_rate_updated_ts_ms'], unit='ms')
|
||||||
|
df['funding_rate'] = df['funding_rate'].astype(float)
|
||||||
|
df['time_delta_to_next_funding'] = pd.to_datetime(df['next_funding_ts'], unit='ms') - pd.Timestamp.now()
|
||||||
|
|
||||||
|
return df
|
||||||
|
|
||||||
|
def load_extend_current_fr(df_mkt_stats: pd.DataFrame) -> pd.DataFrame:
|
||||||
|
df = pd.DataFrame(json.loads(VAL_KEY.get('fund_rate_extended_all')))
|
||||||
|
|
||||||
|
df = df[['symbol','funding_rate_updated_ts_ms','funding_rate']]
|
||||||
|
df['funding_rate_updated_dt'] = pd.to_datetime(df['funding_rate_updated_ts_ms'], unit='ms')
|
||||||
|
df['funding_rate'] = df['funding_rate'].astype(float)
|
||||||
|
|
||||||
|
df = df.merge(df_mkt_stats[['name','assetName','status', 'funding_rate_ts','max_leverage']].rename({'name':'symbol','funding_rate_ts':'next_funding_ts'}, axis=1), on='symbol', how='left')
|
||||||
|
df = df.loc[df['status']=='ACTIVE',:]
|
||||||
|
df['USDT_Symbol'] = df['assetName'] + 'USDT'
|
||||||
|
|
||||||
|
df['time_delta_to_next_funding'] = pd.to_datetime(df['next_funding_ts'], unit='ms') - pd.Timestamp.now()
|
||||||
|
|
||||||
|
return df
|
||||||
|
|
||||||
|
async def loop() -> None:
|
||||||
|
global Mkt_Info_Last_Refresh_TS_ms
|
||||||
|
|
||||||
|
df_extend_mkt_stats = get_extended_markets_info()
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
ts_arrival = round(datetime.now().timestamp() * 1000)
|
||||||
|
if ( ts_arrival - Mkt_Info_Last_Refresh_TS_ms ) > ( REFRESH_MKT_INFO_EVERY_SEC * 1000 ):
|
||||||
|
df_extend_mkt_stats = get_extended_markets_info()
|
||||||
|
|
||||||
|
df_aster_fr = load_aster_current_fr()
|
||||||
|
df_extend_fr = load_extend_current_fr(df_mkt_stats=df_extend_mkt_stats)
|
||||||
|
|
||||||
|
df_comb_fr = df_extend_fr.merge(df_aster_fr, left_on='USDT_Symbol', right_on='symbol', how='inner', suffixes=('_ext', '_ast'))
|
||||||
|
df_comb_fr['next_funding_at_same_time'] = (abs(df_comb_fr['time_delta_to_next_funding_ext'].dt.total_seconds() - df_comb_fr['time_delta_to_next_funding_ast'].dt.total_seconds()) / 60) < 1
|
||||||
|
df_comb_fr['net_funding_rate'] = (df_comb_fr[['funding_rate_ext', 'funding_rate_ast']].max(axis=1) - df_comb_fr[['funding_rate_ext', 'funding_rate_ast']].min(axis=1)).where(df_comb_fr['next_funding_at_same_time'], df_comb_fr['funding_rate_ext'])
|
||||||
|
df_comb_fr['net_funding_rate_abs'] = df_comb_fr['net_funding_rate'].abs()
|
||||||
|
|
||||||
|
### NET MULT ###
|
||||||
|
df_comb_fr = df_comb_fr.merge(df_leverage_by_exch.loc[df_leverage_by_exch['exchange']=='EXTEND'], left_on='assetName', right_on='lh_asset').merge(df_leverage_by_exch.loc[df_leverage_by_exch['exchange']=='ASTER'], left_on='assetName', right_on='lh_asset', suffixes=('_ext', '_ast'))
|
||||||
|
df_comb_fr['net_mult'] = 1 / ( ( 0.5 / df_comb_fr['max_leverage_ext'] ) + ( 0.5 / df_comb_fr['max_leverage_ast'] ) )
|
||||||
|
df_comb_fr['net_mult'] = df_comb_fr['net_mult'].round(2)
|
||||||
|
df_comb_fr['net_mult_x_net_fr_abs'] = df_comb_fr['net_funding_rate_abs'] * df_comb_fr['net_mult']
|
||||||
|
|
||||||
|
df_best_fr_rate = df_comb_fr[['symbol_ext','symbol_ast','net_mult_x_net_fr_abs','net_funding_rate_abs','net_funding_rate','next_funding_at_same_time']].sort_values(by='net_mult_x_net_fr_abs', ascending=False).reset_index(drop=True)
|
||||||
|
best_next_funding_pair = {'symbol_aster':df_best_fr_rate['symbol_ast'][0],'symbol_extended':df_best_fr_rate['symbol_ext'][0]}
|
||||||
|
|
||||||
|
VAL_KEY.set(VK_OUT, json.dumps(best_next_funding_pair))
|
||||||
|
print(best_next_funding_pair)
|
||||||
|
time.sleep(LOOP_SLEEP_SEC)
|
||||||
|
continue
|
||||||
|
except valkey.exceptions.ConnectionError as e:
|
||||||
|
logging.info(f"Could not connect to Valkey. Please check the publish server is up; {e}")
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
logging.info('ORCHESTRATOR SHUTTING DOWN...')
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(traceback.format_exc())
|
||||||
|
logging.critical(f'*** ORCHESTRATOR CRASHED: {e}')
|
||||||
|
|
||||||
|
|
||||||
|
### STARTUP ###
|
||||||
|
async def main() -> None:
|
||||||
|
global VAL_KEY
|
||||||
|
# global CON
|
||||||
|
|
||||||
|
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 loop()
|
||||||
|
|
||||||
|
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())
|
||||||
File diff suppressed because one or more lines are too long
@@ -2,7 +2,7 @@
|
|||||||
"cells": [
|
"cells": [
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 1,
|
"execution_count": 9,
|
||||||
"id": "6c70a8c3",
|
"id": "6c70a8c3",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 2,
|
"execution_count": 12,
|
||||||
"id": "ff971ca9",
|
"id": "ff971ca9",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
@@ -42,50 +42,46 @@
|
|||||||
"CONFIG = MAINNET_CONFIG\n",
|
"CONFIG = MAINNET_CONFIG\n",
|
||||||
"\n",
|
"\n",
|
||||||
"ORDER_MARKET = \"ETH-USD\"\n",
|
"ORDER_MARKET = \"ETH-USD\"\n",
|
||||||
"ORDER_SIDE = OrderSide.BUY\n",
|
|
||||||
"ORDER_QTY = Decimal(\"0.01\")\n",
|
"ORDER_QTY = Decimal(\"0.01\")\n",
|
||||||
"ORDER_PRICE = Decimal(\"2200.1\")"
|
"ORDER_PRICE = Decimal(\"2300.1\")\n",
|
||||||
|
"ORDER_SIDE = OrderSide.BUY"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 3,
|
"execution_count": 13,
|
||||||
"id": "fc2c6d2b",
|
"id": "fc2c6d2b",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [],
|
|
||||||
"source": [
|
|
||||||
"client, trading_client = await extend_auth.create_auth_account_and_trading_client()"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": null,
|
|
||||||
"id": "c366706f",
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [
|
"outputs": [
|
||||||
{
|
{
|
||||||
"name": "stderr",
|
"name": "stderr",
|
||||||
"output_type": "stream",
|
"output_type": "stream",
|
||||||
"text": [
|
"text": [
|
||||||
"Error response from https://api.starknet.extended.exchange/api/v1/user/order: {\"status\":\"ERROR\",\"error\":{\"code\":1125,\"message\":\"Invalid price precision\"}}\n"
|
"Unclosed client session\n",
|
||||||
]
|
"client_session: <aiohttp.client.ClientSession object at 0x7b2dc26ba850>\n"
|
||||||
},
|
|
||||||
{
|
|
||||||
"ename": "ValueError",
|
|
||||||
"evalue": "Error response from https://api.starknet.extended.exchange/api/v1/user/order: code 400 - {\"status\":\"ERROR\",\"error\":{\"code\":1125,\"message\":\"Invalid price precision\"}}",
|
|
||||||
"output_type": "error",
|
|
||||||
"traceback": [
|
|
||||||
"\u001b[31m---------------------------------------------------------------------------\u001b[39m",
|
|
||||||
"\u001b[31mValueError\u001b[39m Traceback (most recent call last)",
|
|
||||||
"\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[4]\u001b[39m\u001b[32m, line 1\u001b[39m\n\u001b[32m----> \u001b[39m\u001b[32m1\u001b[39m placed_order = await trading_client.place_order(\n\u001b[32m 2\u001b[39m market_name=ORDER_MARKET,\n\u001b[32m 3\u001b[39m amount_of_synthetic=ORDER_QTY,\n\u001b[32m 4\u001b[39m price=ORDER_PRICE,\n",
|
|
||||||
"\u001b[36mFile \u001b[39m\u001b[32m~/miniconda3/envs/py_313/lib/python3.13/site-packages/x10/perpetual/trading_client/trading_client.py:101\u001b[39m, in \u001b[36mPerpetualTradingClient.place_order\u001b[39m\u001b[34m(self, market_name, amount_of_synthetic, price, side, taker_fee, post_only, previous_order_id, expire_time, time_in_force, self_trade_protection_level, external_id, builder_fee, builder_id, reduce_only, tp_sl_type, take_profit, stop_loss)\u001b[39m\n\u001b[32m 78\u001b[39m expire_time = utc_now() + timedelta(hours=\u001b[32m1\u001b[39m)\n\u001b[32m 80\u001b[39m order = create_order_object(\n\u001b[32m 81\u001b[39m account=\u001b[38;5;28mself\u001b[39m.__stark_account,\n\u001b[32m 82\u001b[39m market=market,\n\u001b[32m (...)\u001b[39m\u001b[32m 99\u001b[39m stop_loss=stop_loss,\n\u001b[32m 100\u001b[39m )\n\u001b[32m--> \u001b[39m\u001b[32m101\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;01mawait\u001b[39;00m \u001b[38;5;28mself\u001b[39m.__order_management_module.place_order(order)\n",
|
|
||||||
"\u001b[36mFile \u001b[39m\u001b[32m~/miniconda3/envs/py_313/lib/python3.13/site-packages/x10/perpetual/trading_client/order_management_module.py:31\u001b[39m, in \u001b[36mOrderManagementModule.place_order\u001b[39m\u001b[34m(self, order)\u001b[39m\n\u001b[32m 28\u001b[39m LOGGER.debug(\u001b[33m\"\u001b[39m\u001b[33mPlacing an order: id=\u001b[39m\u001b[38;5;132;01m%s\u001b[39;00m\u001b[33m\"\u001b[39m, order.id)\n\u001b[32m 30\u001b[39m url = \u001b[38;5;28mself\u001b[39m._get_url(\u001b[33m\"\u001b[39m\u001b[33m/user/order\u001b[39m\u001b[33m\"\u001b[39m)\n\u001b[32m---> \u001b[39m\u001b[32m31\u001b[39m response = \u001b[38;5;28;01mawait\u001b[39;00m send_post_request(\n\u001b[32m 32\u001b[39m \u001b[38;5;28;01mawait\u001b[39;00m \u001b[38;5;28mself\u001b[39m.get_session(),\n\u001b[32m 33\u001b[39m url,\n\u001b[32m 34\u001b[39m PlacedOrderModel,\n\u001b[32m 35\u001b[39m json=order.to_api_request_json(exclude_none=\u001b[38;5;28;01mTrue\u001b[39;00m),\n\u001b[32m 36\u001b[39m api_key=\u001b[38;5;28mself\u001b[39m._get_api_key(),\n\u001b[32m 37\u001b[39m )\n\u001b[32m 38\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m response\n",
|
|
||||||
"\u001b[36mFile \u001b[39m\u001b[32m~/miniconda3/envs/py_313/lib/python3.13/site-packages/x10/utils/http.py:173\u001b[39m, in \u001b[36msend_post_request\u001b[39m\u001b[34m(session, url, model_class, json, api_key, request_headers, response_code_to_exception)\u001b[39m\n\u001b[32m 171\u001b[39m \u001b[38;5;28;01masync\u001b[39;00m \u001b[38;5;28;01mwith\u001b[39;00m session.post(url, json=json, headers=headers) \u001b[38;5;28;01mas\u001b[39;00m response:\n\u001b[32m 172\u001b[39m response_text = \u001b[38;5;28;01mawait\u001b[39;00m response.text()\n\u001b[32m--> \u001b[39m\u001b[32m173\u001b[39m \u001b[30;43mhandle_known_errors\u001b[39;49m\u001b[30;43m(\u001b[39;49m\u001b[30;43murl\u001b[39;49m\u001b[30;43m,\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43mresponse_code_to_exception\u001b[39;49m\u001b[30;43m,\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43mresponse\u001b[39;49m\u001b[30;43m,\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43mresponse_text\u001b[39;49m\u001b[30;43m)\u001b[39;49m\n\u001b[32m 174\u001b[39m response_model = parse_response_to_model(response_text, model_class)\n\u001b[32m 176\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m (response_model.status != ResponseStatus.OK) \u001b[38;5;129;01mor\u001b[39;00m (response_model.error \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m):\n",
|
|
||||||
"\u001b[36mFile \u001b[39m\u001b[32m~/miniconda3/envs/py_313/lib/python3.13/site-packages/x10/utils/http.py:243\u001b[39m, in \u001b[36mhandle_known_errors\u001b[39m\u001b[34m(url, response_code_handler, response, response_text)\u001b[39m\n\u001b[32m 241\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m response.status > \u001b[32m299\u001b[39m:\n\u001b[32m 242\u001b[39m LOGGER.error(\u001b[33m\"\u001b[39m\u001b[33mError response from \u001b[39m\u001b[38;5;132;01m%s\u001b[39;00m\u001b[33m: \u001b[39m\u001b[38;5;132;01m%s\u001b[39;00m\u001b[33m\"\u001b[39m, url, response_text)\n\u001b[32m--> \u001b[39m\u001b[32m243\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33mError response from \u001b[39m\u001b[38;5;132;01m{\u001b[39;00murl\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m: code \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mresponse.status\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m - \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mresponse_text\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m\"\u001b[39m)\n",
|
|
||||||
"\u001b[31mValueError\u001b[39m: Error response from https://api.starknet.extended.exchange/api/v1/user/order: code 400 - {\"status\":\"ERROR\",\"error\":{\"code\":1125,\"message\":\"Invalid price precision\"}}"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"source": [
|
||||||
|
"client, trading_client = await extend_auth.create_auth_account_and_trading_client()"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 16,
|
||||||
|
"id": "07887649",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"### Figure out how to flatten small residuals - market order with reduce only?)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 14,
|
||||||
|
"id": "c366706f",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"placed_order = await trading_client.place_order(\n",
|
"placed_order = await trading_client.place_order(\n",
|
||||||
" market_name=ORDER_MARKET,\n",
|
" market_name=ORDER_MARKET,\n",
|
||||||
@@ -98,6 +94,27 @@
|
|||||||
")"
|
")"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 15,
|
||||||
|
"id": "ba85f1e9",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"text/plain": [
|
||||||
|
"WrappedApiResponse[PlacedOrderModel](status='OK', data=PlacedOrderModel(id=2049239831434784768, external_id='359148168147219671570709517544221313286652228166698232409514334035033828578'), error=None, pagination=None)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"execution_count": 15,
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "execute_result"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"placed_order"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 5,
|
"execution_count": 5,
|
||||||
|
|||||||
176
main.py
176
main.py
@@ -5,7 +5,6 @@ import math
|
|||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
from dataclasses import asdict, dataclass, field
|
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from decimal import ROUND_DOWN, Decimal
|
from decimal import ROUND_DOWN, Decimal
|
||||||
from typing import AsyncContextManager
|
from typing import AsyncContextManager
|
||||||
@@ -37,24 +36,30 @@ load_dotenv()
|
|||||||
LOG_FILEPATH: str = os.getenv("LOGS_PATH") + '/Fund_Rate_Algo.log'
|
LOG_FILEPATH: str = os.getenv("LOGS_PATH") + '/Fund_Rate_Algo.log'
|
||||||
|
|
||||||
### Algo Config ###
|
### Algo Config ###
|
||||||
ALGO_CONFIG: structs.Algo_Config = None
|
ALGO_CONFIG: structs.Algo_Config | None = None
|
||||||
MIN_TIME_TO_FUNDING: int
|
MIN_TIME_TO_FUNDING: int
|
||||||
|
|
||||||
### CONSTANTS ###
|
### EXCHANGES ###
|
||||||
ASTER = structs.Perpetual_Exchange(
|
ASTER = structs.Perpetual_Exchange(
|
||||||
mult = 150,
|
mult = 150,
|
||||||
lh_asset = 'ETH',
|
lh_asset = 'ETH',
|
||||||
rh_asset = 'USDT',
|
rh_asset = 'USDT',
|
||||||
symbol_asset_separator = '',
|
symbol_asset_separator = '',
|
||||||
)
|
)
|
||||||
|
EXTEND = structs.Perpetual_Exchange(
|
||||||
EXTEND_LH_ASSET: str = 'ETH'
|
mult = 50,
|
||||||
EXTEND_RH_ASSET: str = 'USD'
|
lh_asset = 'ETH',
|
||||||
EXTEND_TICKER: str = EXTEND_LH_ASSET + '-' + EXTEND_RH_ASSET
|
rh_asset = 'USD',
|
||||||
|
symbol_asset_separator = '-',
|
||||||
|
)
|
||||||
|
|
||||||
### GLOBALS ###
|
### GLOBALS ###
|
||||||
ASTER_MULT = 150
|
Last_Aster_Fill_Time_Ts: float = 0.00
|
||||||
EXTEND_MULT = 50
|
Just_Rejected_Or_Expired: bool = False
|
||||||
|
Best_Symbol_by_Exchange: dict = {}
|
||||||
|
|
||||||
|
# ASTER_MULT = 150
|
||||||
|
# EXTEND_MULT = 50
|
||||||
|
|
||||||
ASTER_MIN_ORDER_QTY = 0.001
|
ASTER_MIN_ORDER_QTY = 0.001
|
||||||
EXTEND_MIN_ORDER_QTY = 0.01
|
EXTEND_MIN_ORDER_QTY = 0.01
|
||||||
@@ -121,7 +126,7 @@ async def get_aster_notional_position(resp: dict | None = None):
|
|||||||
global ASTER_NOTIONAL_OBJ
|
global ASTER_NOTIONAL_OBJ
|
||||||
global ASTER_NOTIONAL_POSITION
|
global ASTER_NOTIONAL_POSITION
|
||||||
global ASTER_UNREALIZED_PNL
|
global ASTER_UNREALIZED_PNL
|
||||||
global ASTER_MULT
|
global ASTER
|
||||||
|
|
||||||
previous_notional_obj = ASTER_NOTIONAL_OBJ
|
previous_notional_obj = ASTER_NOTIONAL_OBJ
|
||||||
|
|
||||||
@@ -159,32 +164,31 @@ async def get_aster_notional_position(resp: dict | None = None):
|
|||||||
|
|
||||||
previous_notional_position = ASTER_NOTIONAL_POSITION
|
previous_notional_position = ASTER_NOTIONAL_POSITION
|
||||||
# if not resp: # this can never evaluate
|
# if not resp: # this can never evaluate
|
||||||
# ASTER_MULT = float(d['leverage'])
|
# ASTER.mult = float(d['leverage'])
|
||||||
# if abs(ASTER_NOTIONAL_POSITION) > ALGO_CONFIG.Max_Target_Notional*1.01:
|
if abs(ASTER_NOTIONAL_POSITION) > ALGO_CONFIG.Config.Max_Target_Notional*ALGO_CONFIG.Config.Max_Order_Over_Notional_Ratio:
|
||||||
if abs(ASTER_NOTIONAL_POSITION) > ALGO_CONFIG.Max_Target_Notional*2.01:
|
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}')
|
|
||||||
await kill_algo()
|
await kill_algo()
|
||||||
# if ASTER_NOTIONAL_POSITION != previous_notional_position:
|
# 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)}')
|
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():
|
async def get_extend_collateral():
|
||||||
global EXTEND_AVAIL_COLLATERAL
|
global EXTEND_AVAIL_COLLATERAL
|
||||||
|
|
||||||
get_bals = dict(dict(await EXTEND_CLIENT.account.get_balance()).get('data', {}))
|
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
|
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(resp: dict | None = None):
|
async def get_extend_notional(resp: dict | None = None):
|
||||||
global EXTEND_NOTIONAL_OBJ
|
global EXTEND_NOTIONAL_OBJ
|
||||||
global EXTEND_NOTIONAL_POSITION
|
global EXTEND_NOTIONAL_POSITION
|
||||||
global EXTEND_UNREALIZED_PNL
|
global EXTEND_UNREALIZED_PNL
|
||||||
global EXTEND_MULT
|
global EXTEND
|
||||||
|
|
||||||
previous_notional_obj = EXTEND_NOTIONAL_OBJ
|
previous_notional_obj = EXTEND_NOTIONAL_OBJ
|
||||||
previous_notional_position = EXTEND_NOTIONAL_POSITION
|
previous_notional_position = EXTEND_NOTIONAL_POSITION
|
||||||
|
|
||||||
if not resp:
|
if not resp:
|
||||||
resp = dict(await EXTEND_CLIENT.account.get_positions()).get('data', [])
|
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 = [dict(d) for d in resp if dict(d).get('market') == EXTEND.symbol]
|
||||||
if pos_dict:
|
if pos_dict:
|
||||||
pos_dict = pos_dict[0]
|
pos_dict = pos_dict[0]
|
||||||
else:
|
else:
|
||||||
@@ -195,7 +199,7 @@ async def get_extend_notional(resp: dict | None = None):
|
|||||||
|
|
||||||
pos_dict['timestamp_arrival'] = round(datetime.now().timestamp()*1000)
|
pos_dict['timestamp_arrival'] = round(datetime.now().timestamp()*1000)
|
||||||
else:
|
else:
|
||||||
pos_dict = [dict(d) for d in resp if dict(d).get('market') == EXTEND_TICKER]
|
pos_dict = [dict(d) for d in resp if dict(d).get('market') == EXTEND.symbol]
|
||||||
if pos_dict:
|
if pos_dict:
|
||||||
pos_dict = pos_dict[0]
|
pos_dict = pos_dict[0]
|
||||||
else:
|
else:
|
||||||
@@ -223,12 +227,12 @@ async def get_extend_notional(resp: dict | None = None):
|
|||||||
logging.info(f'EXTEND BAD SIDE ON POSITION UPDATE: {pos_dict}')
|
logging.info(f'EXTEND BAD SIDE ON POSITION UPDATE: {pos_dict}')
|
||||||
|
|
||||||
EXTEND_NOTIONAL_POSITION = notional_pos_sided - float(EXTEND_UNREALIZED_PNL)
|
EXTEND_NOTIONAL_POSITION = notional_pos_sided - float(EXTEND_UNREALIZED_PNL)
|
||||||
EXTEND_MULT = pos_dict.get('leverage', EXTEND_MULT)
|
EXTEND.mult = pos_dict.get('leverage', EXTEND)
|
||||||
if abs(EXTEND_NOTIONAL_POSITION) > ALGO_CONFIG.Max_Target_Notional*2.01:
|
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}')
|
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()
|
await kill_algo()
|
||||||
if EXTEND_NOTIONAL_POSITION != previous_notional_position:
|
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:.0f}; resp: {bool(resp)}')
|
logging.info(f'EXTEND NOTIONAL CHANGE: {previous_notional_position} -> {EXTEND_NOTIONAL_POSITION:.2f}; UR PNL: {EXTEND_UNREALIZED_PNL:.2f}; MULT: {EXTEND.mult:.0f}; resp: {bool(resp)}')
|
||||||
|
|
||||||
### EXCHANGE INFO ###
|
### EXCHANGE INFO ###
|
||||||
async def get_aster_exch_info():
|
async def get_aster_exch_info():
|
||||||
@@ -264,7 +268,7 @@ async def aster_cancel_all_orders():
|
|||||||
logging.info(f'ASTER CANCEL ALL OPEN ORDERS RESP: {r}')
|
logging.info(f'ASTER CANCEL ALL OPEN ORDERS RESP: {r}')
|
||||||
|
|
||||||
async def extend_cancel_all_orders():
|
async def extend_cancel_all_orders():
|
||||||
r = await EXTEND_CLIENT.orders.mass_cancel(markets=[EXTEND_TICKER])
|
r = await EXTEND_CLIENT.orders.mass_cancel(markets=[EXTEND.symbol])
|
||||||
logging.info(f'EXTEND CANCEL ALL OPEN ORDERS RESP: {r}')
|
logging.info(f'EXTEND CANCEL ALL OPEN ORDERS RESP: {r}')
|
||||||
|
|
||||||
### KILL ALGO ###
|
### KILL ALGO ###
|
||||||
@@ -277,29 +281,39 @@ async def kill_algo():
|
|||||||
|
|
||||||
### ALGO LOOP ###
|
### ALGO LOOP ###
|
||||||
async def run_algo():
|
async def run_algo():
|
||||||
|
global ASTER
|
||||||
|
global EXTEND
|
||||||
|
|
||||||
global ALGO_CONFIG
|
global ALGO_CONFIG
|
||||||
global MIN_TIME_TO_FUNDING
|
global MIN_TIME_TO_FUNDING
|
||||||
global ASTER_OPEN_ORDERS
|
global ASTER_OPEN_ORDERS
|
||||||
global EXTEND_OPEN_ORDERS
|
global EXTEND_OPEN_ORDERS
|
||||||
|
global Last_Aster_Fill_Time_Ts
|
||||||
|
global Just_Rejected_Or_Expired
|
||||||
|
global Best_Symbol_by_Exchange
|
||||||
|
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
loop_start = time.time()
|
loop_start = time.time()
|
||||||
# print('__________Start___________')
|
# print('__________Start___________')
|
||||||
### ALGO CONIFG ###
|
### ALGO CONIFG ###
|
||||||
ALGO_CONFIG = json.loads(VAL_KEY.get('fr_orchestrator_output'), object_hook=lambda d: structs.Algo_Config(**d))
|
|
||||||
ALGO_CONFIG.Max_Target_Notional = float(min([ASTER_MULT, EXTEND_MULT]) * ALGO_CONFIG.Target_Open_Cash_Position)
|
|
||||||
|
|
||||||
MIN_TIME_TO_FUNDING = ALGO_CONFIG.Min_Time_To_Funding_Minutes * 60 * 1000
|
ALGO_CONFIG = json.loads(VAL_KEY.get('fr_orchestrator_output'))
|
||||||
|
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 ###
|
### Load Data from Feedhandlers ###
|
||||||
|
Best_Symbol_by_Exchange = json.loads(VAL_KEY.get('fr_engine_best_fund_rate_output'))
|
||||||
|
|
||||||
ASTER_FUND_RATE_DICT = json.loads(VAL_KEY.get('fund_rate_aster'))
|
ASTER_FUND_RATE_DICT = json.loads(VAL_KEY.get('fund_rate_aster'))
|
||||||
EXTENDED_FUND_RATE_DICT = json.loads(VAL_KEY.get('fund_rate_extended'))
|
EXTENDED_FUND_RATE_DICT = json.loads(VAL_KEY.get('fund_rate_extended'))
|
||||||
|
|
||||||
ASTER_FUND_RATE = float(ASTER_FUND_RATE_DICT.get('funding_rate', 0))
|
ASTER_FUND_RATE = float(ASTER_FUND_RATE_DICT.get('funding_rate', 0))
|
||||||
EXTEND_FUND_RATE = float(EXTENDED_FUND_RATE_DICT.get('funding_rate', 0))
|
EXTEND_FUND_RATE = float(EXTENDED_FUND_RATE_DICT.get('funding_rate', 0))
|
||||||
|
|
||||||
if ALGO_CONFIG.Flip_Side_For_Testing:
|
if ALGO_CONFIG.Overrides.Flip_Side_For_Testing:
|
||||||
ASTER_FUND_RATE = ASTER_FUND_RATE * -1
|
ASTER_FUND_RATE = ASTER_FUND_RATE * -1
|
||||||
EXTEND_FUND_RATE = EXTEND_FUND_RATE * -1
|
EXTEND_FUND_RATE = EXTEND_FUND_RATE * -1
|
||||||
|
|
||||||
@@ -370,16 +384,20 @@ async def run_algo():
|
|||||||
if order_update_status in ['CANCELED','EXPIRED']:
|
if order_update_status in ['CANCELED','EXPIRED']:
|
||||||
logging.info(f'ASTER ORDER CANCELLED or EXPIRED: {order_id}')
|
logging.info(f'ASTER ORDER CANCELLED or EXPIRED: {order_id}')
|
||||||
ASTER_OPEN_ORDERS.pop(idx)
|
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']:
|
elif order_update_status in ['PARTIALLY_FILLED']:
|
||||||
logging.info(f'ASTER ORDER PARTIALLY FILLED: {order_id}')
|
logging.info(f'ASTER ORDER PARTIALLY FILLED: {order_id}')
|
||||||
await get_aster_collateral()
|
# await get_aster_collateral()
|
||||||
await get_aster_notional_position()
|
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})')
|
utils.send_tg_alert(f'FR_ALGO - ASTER PARTIALLY FILLED ({order_id})')
|
||||||
elif order_update_status in ['FILLED']:
|
elif order_update_status in ['FILLED']:
|
||||||
logging.info(f'ASTER ORDER FILLED: {order_id}')
|
logging.info(f'ASTER ORDER FILLED: {order_id}')
|
||||||
ASTER_OPEN_ORDERS.pop(idx)
|
ASTER_OPEN_ORDERS.pop(idx)
|
||||||
await get_aster_collateral()
|
# await get_aster_collateral()
|
||||||
await get_aster_notional_position()
|
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})')
|
utils.send_tg_alert(f'FR_ALGO - ASTER FILLED ({order_id})')
|
||||||
else:
|
else:
|
||||||
logging.critical(f'EXTEND ORDER STATUS CHG TO UNEXPECTED VALUE, KILLING... ({order_id}): {order_orig_status} -> {order_update_status}')
|
logging.critical(f'EXTEND ORDER STATUS CHG TO UNEXPECTED VALUE, KILLING... ({order_id}): {order_orig_status} -> {order_update_status}')
|
||||||
@@ -401,6 +419,8 @@ async def run_algo():
|
|||||||
if order_update_status in ['CANCELLED','EXPIRED','REJECTED']:
|
if order_update_status in ['CANCELLED','EXPIRED','REJECTED']:
|
||||||
logging.info(f'EXTEND ORDER CANCELLED, REJECTED or EXPIRED: {order_id}')
|
logging.info(f'EXTEND ORDER CANCELLED, REJECTED or EXPIRED: {order_id}')
|
||||||
EXTEND_OPEN_ORDERS.pop(idx)
|
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']:
|
elif order_update_status in ['PARTIALLY_FILLED']:
|
||||||
logging.info(f'EXTEND ORDER PARTIALLY FILLED: {order_id}')
|
logging.info(f'EXTEND ORDER PARTIALLY FILLED: {order_id}')
|
||||||
await get_extend_collateral()
|
await get_extend_collateral()
|
||||||
@@ -416,10 +436,20 @@ async def run_algo():
|
|||||||
logging.critical(f'EXTEND ORDER STATUS CHG TO UNEXPECTED VALUE, KILLING... ({order_id}): {order_orig_status} -> {order_update_status}')
|
logging.critical(f'EXTEND ORDER STATUS CHG TO UNEXPECTED VALUE, KILLING... ({order_id}): {order_orig_status} -> {order_update_status}')
|
||||||
|
|
||||||
|
|
||||||
|
# if Best_Symbol_by_Exchange['symbol_aster'] != ASTER.symbol:
|
||||||
|
# if abs( ASTER_NOTIONAL_POSITION ) > 0 or abs( EXTEND_NOTIONAL_POSITION ) > 0:
|
||||||
|
# print('Symbol switch - Flattening Positions')
|
||||||
|
# ALGO_CONFIG.Overrides.Flatten_Open_Positions = True
|
||||||
|
# else:
|
||||||
|
# print(f'Balances Flattened - Updating to Trade New Symbol: ASTER.symbol -> {Best_Symbol_by_Exchange['symbol_aster']}')
|
||||||
|
# # ASTER.symbol = Best_Symbol_by_Exchange['symbol_aster']
|
||||||
|
# # EXTEND.symbol = Best_Symbol_by_Exchange['symbol_extended']
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
min_between_fundings = round((abs(ASTER_FUND_RATE_TIME - EXTEND_FUND_RATE_TIME) / 1000 / 60))
|
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 = min_between_fundings < 5
|
||||||
|
|
||||||
|
|
||||||
if ( abs(ASTER_FUND_RATE) > abs(EXTEND_FUND_RATE) ) and FUNDINGS_AT_SAME_TIME_NEXT_HR:
|
if ( abs(ASTER_FUND_RATE) > abs(EXTEND_FUND_RATE) ) and FUNDINGS_AT_SAME_TIME_NEXT_HR:
|
||||||
ALPHA_EXCH = 'ASTER'
|
ALPHA_EXCH = 'ASTER'
|
||||||
ALPHA_FUND_RATE = ASTER_FUND_RATE
|
ALPHA_FUND_RATE = ASTER_FUND_RATE
|
||||||
@@ -429,20 +459,20 @@ async def run_algo():
|
|||||||
|
|
||||||
if ALPHA_FUND_RATE < 0:
|
if ALPHA_FUND_RATE < 0:
|
||||||
ALPHA_CARRY_SIDE = 'BUY'
|
ALPHA_CARRY_SIDE = 'BUY'
|
||||||
ALPHA_TGT_NOTIONAL = ALGO_CONFIG.Max_Target_Notional
|
ALPHA_TGT_NOTIONAL = ALGO_CONFIG.Config.Max_Target_Notional
|
||||||
else:
|
else:
|
||||||
ALPHA_CARRY_SIDE = 'SELL'
|
ALPHA_CARRY_SIDE = 'SELL'
|
||||||
ALPHA_TGT_NOTIONAL = ALGO_CONFIG.Max_Target_Notional*-1
|
ALPHA_TGT_NOTIONAL = ALGO_CONFIG.Config.Max_Target_Notional*-1
|
||||||
|
|
||||||
def calc_next_net_fund_rate(FUNDINGS_AT_SAME_TIME_NEXT_HR: bool) -> float:
|
def calc_next_net_fund_rate(FUNDINGS_AT_SAME_TIME_NEXT_HR: bool) -> float:
|
||||||
if FUNDINGS_AT_SAME_TIME_NEXT_HR:
|
if FUNDINGS_AT_SAME_TIME_NEXT_HR:
|
||||||
return ASTER_FUND_RATE + EXTEND_FUND_RATE
|
return max([ASTER_FUND_RATE, EXTEND_FUND_RATE]) - min([ASTER_FUND_RATE, EXTEND_FUND_RATE])
|
||||||
else:
|
else:
|
||||||
return EXTEND_FUND_RATE
|
return EXTEND_FUND_RATE
|
||||||
|
|
||||||
NEXT_NET_FUNDING_RATE = calc_next_net_fund_rate(FUNDINGS_AT_SAME_TIME_NEXT_HR)
|
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.Min_Fund_Rate_Pct_To_Trade*-1) / 100) ) and ( NEXT_NET_FUNDING_RATE <= ( ALGO_CONFIG.Min_Fund_Rate_Pct_To_Trade / 100 ) )
|
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.Flatten_Open_Positions:
|
if Flags.NET_FUNDING_IS_ZERO or ALGO_CONFIG.Overrides.Flatten_Open_Positions:
|
||||||
ALPHA_TGT_NOTIONAL = 0.00
|
ALPHA_TGT_NOTIONAL = 0.00
|
||||||
# if ASTER_OPEN_ORDERS or EXTEND_OPEN_ORDERS:
|
# if ASTER_OPEN_ORDERS or EXTEND_OPEN_ORDERS:
|
||||||
# logging.info('NET FUNDING = 0.00; Cancelling Open Orders! then Waiting...')
|
# logging.info('NET FUNDING = 0.00; Cancelling Open Orders! then Waiting...')
|
||||||
@@ -507,26 +537,26 @@ async def run_algo():
|
|||||||
ASTER_TGT_TAIL_ORDERABLE = abs(ASTER_TGT_TAIL_BASE_QTY) >= MAX_MIN_ORDER_QTY
|
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
|
EXTEND_TGT_TAIL_ORDERABLE = abs(EXTEND_TGT_TAIL_BASE_QTY) >= MAX_MIN_ORDER_QTY
|
||||||
|
|
||||||
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(( 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 ) / ASTER_NOTIONAL_POSITION ) * 100
|
||||||
Currently_Hedged = Hedge_Ratio < 1.00
|
Currently_Hedged = Hedge_Ratio < 1.00
|
||||||
|
|
||||||
def print_summary(use_logging: bool = False):
|
def print_summary(use_logging: bool = False):
|
||||||
OUT: print | logging.info = logging.info if use_logging else print
|
OUT: print | logging.info = logging.info if use_logging else print
|
||||||
|
|
||||||
OUT(f'''
|
OUT(f'''
|
||||||
LOOP SLEEP (SEC): {ALGO_CONFIG.Loop_Sleep_Sec}
|
LOOP SLEEP (SEC): {ALGO_CONFIG.Config.Loop_Sleep_Sec}
|
||||||
FLIP SIDES FOR TESTING?: {ALGO_CONFIG.Flip_Side_For_Testing}; ASTER ORDER ENABLED? {ALGO_CONFIG.Allow_Ordering_Aster}; EXTEND ORDER ENABLED? {ALGO_CONFIG.Allow_Ordering_Extend}
|
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()):})
|
{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_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: {'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: [ 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} ]
|
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} ]
|
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.Min_Fund_Rate_Pct_To_Trade}]
|
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}]
|
ALPHA SIDE : {ALPHA_EXCH} [{ALPHA_CARRY_SIDE}]
|
||||||
|
|
||||||
TGT NOTIONAL: $ {abs(ALPHA_TGT_NOTIONAL):.2f}; Flatten Open Positions Flag? {ALGO_CONFIG.Flatten_Open_Positions}
|
TGT NOTIONAL: $ {abs(ALPHA_TGT_NOTIONAL):.2f}; Flatten Open Positions Flag? {ALGO_CONFIG.Overrides.Flatten_Open_Positions}
|
||||||
|
|
||||||
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_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_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}
|
||||||
@@ -543,35 +573,38 @@ async def run_algo():
|
|||||||
--- EXTEND OPEN ORDERS ---
|
--- EXTEND 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).
|
# 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.Log_Summary_Each_Loop:
|
if ALGO_CONFIG.Logging.Log_Summary_Each_Loop:
|
||||||
print_summary(use_logging=True)
|
print_summary(use_logging=True)
|
||||||
if ALGO_CONFIG.Print_Summary_Each_Loop:
|
if ALGO_CONFIG.Logging.Print_Summary_Each_Loop:
|
||||||
print_summary(use_logging=False)
|
print_summary(use_logging=False)
|
||||||
# print_summary()
|
# print_summary()
|
||||||
|
|
||||||
|
|
||||||
### ROUTES ###
|
### ROUTES ###
|
||||||
# MIN_EXPECTED_ALPHA_TO_TRADE = 0.0001
|
# MIN_EXPECTED_ALPHA_TO_TRADE = 0.0001
|
||||||
MIN_EXPECTED_ALPHA_TO_TRADE = -0.000001
|
MIN_EXPECTED_ALPHA_TO_TRADE = -0.000001
|
||||||
# ALPHA RATIO CHECK
|
# ALPHA RATIO CHECK
|
||||||
if not( ( Expected_Alpha_w_Taker > MIN_EXPECTED_ALPHA_TO_TRADE ) or ( ASTER_OPEN_ORDERS or EXTEND_OPEN_ORDERS ) or ALGO_CONFIG.Flatten_Open_Positions ):
|
if not( ( Expected_Alpha_Net_FR_w_Taker > MIN_EXPECTED_ALPHA_TO_TRADE ) or ( ASTER_OPEN_ORDERS or EXTEND_OPEN_ORDERS or Just_Rejected_Or_Expired or ALGO_CONFIG.Overrides.Flatten_Open_Positions) ):
|
||||||
# logging.info(f'Alpha Ratio too low ({ALPHA_RATIO:.8f}) and no Open Orders...')
|
if ALGO_CONFIG.Logging.Print_Summary_Each_Loop:
|
||||||
|
print(f'Alpha Ratio too low ({ALPHA_RATIO:.8f}) and no Open Orders...')
|
||||||
pass
|
pass
|
||||||
elif ( Expected_Alpha_w_Taker <= MIN_EXPECTED_ALPHA_TO_TRADE ) and ( ASTER_OPEN_ORDERS or EXTEND_OPEN_ORDERS ) and Currently_Hedged:
|
elif ( Expected_Alpha_Net_FR_w_Taker <= MIN_EXPECTED_ALPHA_TO_TRADE ) and ( ASTER_OPEN_ORDERS or EXTEND_OPEN_ORDERS ) and Currently_Hedged:
|
||||||
await aster_cancel_all_orders()
|
await aster_cancel_all_orders()
|
||||||
await extend_cancel_all_orders()
|
await extend_cancel_all_orders()
|
||||||
logging.info('Expected_Alpha went away with open orders...cancelling since we are currently hedged...')
|
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
|
# time.sleep( (1/1000)*100 ) # 100ms wait for ws cancel response
|
||||||
else:
|
else:
|
||||||
# logging.info(f'*** Alpha Ratio HIT - LETS ORDER: {ALPHA_RATIO:.8f}')
|
# logging.info(f'*** Alpha Ratio HIT - LETS ORDER: {ALPHA_RATIO:.8f}')
|
||||||
# ASTER
|
# ASTER
|
||||||
if ASTER_TGT_TAIL_ORDERABLE and ALGO_CONFIG.Allow_Ordering_Aster:
|
if ASTER_TGT_TAIL_ORDERABLE and ALGO_CONFIG.Overrides.Allow_Ordering_Aster:
|
||||||
symbol = ASTER.symbol
|
symbol = ASTER.symbol
|
||||||
side = 'BUY' if ASTER_TGT_TAIL_BASE_QTY > 0.00 else 'SELL'
|
side = 'BUY' if ASTER_TGT_TAIL_BASE_QTY > 0.00 else 'SELL'
|
||||||
qty = str(abs(ASTER_TGT_TAIL_BASE_QTY))
|
qty = str(abs(ASTER_TGT_TAIL_BASE_QTY))
|
||||||
price = ASTER_TOB_PX - ALGO_CONFIG.Price_Worsener_Aster if side == 'BUY' else ASTER_TOB_PX + ALGO_CONFIG.Price_Worsener_Aster
|
price = ASTER_TOB_PX - ALGO_CONFIG.Config.Price_Worsener_Aster if side == 'BUY' else ASTER_TOB_PX + ALGO_CONFIG.Config.Price_Worsener_Aster
|
||||||
|
|
||||||
if abs( ( float(ASTER_TGT_TAIL_BASE_QTY)*float(price) ) + ASTER_NOTIONAL_POSITION ) > ALGO_CONFIG.Max_Target_Notional*1.01:
|
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})')
|
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()
|
await kill_algo()
|
||||||
if ASTER_OPEN_ORDERS:
|
if ASTER_OPEN_ORDERS:
|
||||||
@@ -622,6 +655,7 @@ async def run_algo():
|
|||||||
order_resp['original_price'] = price
|
order_resp['original_price'] = price
|
||||||
order_resp['order_status'] = order_resp['status']
|
order_resp['order_status'] = order_resp['status']
|
||||||
ASTER_OPEN_ORDERS.append(order_resp)
|
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:.2f}; Value: {float(ASTER_TGT_TAIL_BASE_QTY)*float(price):.2f}; Price: {float(price):.2f}')
|
||||||
logging.info(f'ASTER ORDER PLACED SUCCESS: {order_resp}')
|
logging.info(f'ASTER ORDER PLACED SUCCESS: {order_resp}')
|
||||||
print_summary(use_logging=True)
|
print_summary(use_logging=True)
|
||||||
@@ -634,13 +668,20 @@ async def run_algo():
|
|||||||
await aster_cancel_all_orders()
|
await aster_cancel_all_orders()
|
||||||
|
|
||||||
# EXTEND
|
# EXTEND
|
||||||
if EXTEND_TGT_TAIL_ORDERABLE and ALGO_CONFIG.Allow_Ordering_Extend:
|
if EXTEND_TGT_TAIL_ORDERABLE and ALGO_CONFIG.Overrides.Allow_Ordering_Extend:
|
||||||
symbol = EXTEND_TICKER
|
Time_Since_Last_Aster_Fill_ms = ( datetime.now().timestamp()*1000 ) - Last_Aster_Fill_Time_Ts
|
||||||
|
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 = EXTEND_TOB_PX - ALGO_CONFIG.Config.Price_Worsener_Extend if side == 'BUY' else EXTEND_TOB_PX + ALGO_CONFIG.Config.Price_Worsener_Extend
|
||||||
|
else:
|
||||||
|
post_only = True
|
||||||
|
price = EXTEND_TOB_PX
|
||||||
|
|
||||||
|
symbol = EXTEND.symbol
|
||||||
side = OrderSide.BUY if EXTEND_TGT_TAIL_BASE_QTY > 0.00 else OrderSide.SELL
|
side = OrderSide.BUY if EXTEND_TGT_TAIL_BASE_QTY > 0.00 else OrderSide.SELL
|
||||||
qty = Decimal(str(abs(EXTEND_TGT_TAIL_BASE_QTY)))
|
qty = Decimal(str(abs(EXTEND_TGT_TAIL_BASE_QTY)))
|
||||||
price = EXTEND_TOB_PX - ALGO_CONFIG.Price_Worsener_Extend if side == 'BUY' else EXTEND_TOB_PX + ALGO_CONFIG.Price_Worsener_Extend
|
|
||||||
|
|
||||||
if abs( ( float(EXTEND_TGT_TAIL_BASE_QTY)*float(price) ) + EXTEND_NOTIONAL_POSITION ) > ALGO_CONFIG.Max_Target_Notional*1.01:
|
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})')
|
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()
|
await kill_algo()
|
||||||
if EXTEND_OPEN_ORDERS:
|
if EXTEND_OPEN_ORDERS:
|
||||||
@@ -667,17 +708,20 @@ async def run_algo():
|
|||||||
logging.info('EXTEND OPEN ORDER NO PX CHG; SKIPPING')
|
logging.info('EXTEND OPEN ORDER NO PX CHG; SKIPPING')
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
|
taker_fee = taker_fee=Decimal("0.00000") if post_only else Decimal("0.00025")
|
||||||
order_resp = await EXTEND_CLIENT.place_order(
|
order_resp = await EXTEND_CLIENT.place_order(
|
||||||
market_name=symbol,
|
market_name=symbol,
|
||||||
amount_of_synthetic=qty,
|
amount_of_synthetic=qty,
|
||||||
price=price,
|
price=price,
|
||||||
side=side,
|
side=side,
|
||||||
taker_fee=Decimal("0.00025"),
|
taker_fee=taker_fee,
|
||||||
previous_order_id=open_order_id,
|
previous_order_id=open_order_id,
|
||||||
|
post_only=post_only,
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f'EXTEND ORDER PLACEMENT FAILED - RESP: {order_resp}')
|
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: {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}')
|
||||||
logging.error(traceback.format_exc())
|
logging.error(traceback.format_exc())
|
||||||
|
|
||||||
order_resp_dict = dict(order_resp)
|
order_resp_dict = dict(order_resp)
|
||||||
@@ -694,6 +738,7 @@ async def run_algo():
|
|||||||
order_dict['side'] = str(side)
|
order_dict['side'] = str(side)
|
||||||
|
|
||||||
EXTEND_OPEN_ORDERS.append(order_dict)
|
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}')
|
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}')
|
logging.info(f'EXTEND ORDER PLACED SUCCESS: {order_dict}')
|
||||||
print_summary(use_logging=True)
|
print_summary(use_logging=True)
|
||||||
@@ -710,8 +755,8 @@ async def run_algo():
|
|||||||
if ASTER_OPEN_ORDERS or EXTEND_OPEN_ORDERS:
|
if ASTER_OPEN_ORDERS or EXTEND_OPEN_ORDERS:
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
time.sleep(ALGO_CONFIG.Loop_Sleep_Sec)
|
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.Loop_Sleep_Sec:.0f}')
|
# 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:
|
except KeyboardInterrupt:
|
||||||
logging.info('CANCELLING OPEN ORDERS')
|
logging.info('CANCELLING OPEN ORDERS')
|
||||||
@@ -736,10 +781,15 @@ async def main():
|
|||||||
engine = create_async_engine('mysql+asyncmy://root:pwd@localhost/fund_rate')
|
engine = create_async_engine('mysql+asyncmy://root:pwd@localhost/fund_rate')
|
||||||
|
|
||||||
with open('algo_config.json', 'r', encoding='utf-8') as file:
|
with open('algo_config.json', 'r', encoding='utf-8') as file:
|
||||||
ALGO_CONFIG = json.load(file, object_hook=lambda d: structs.Algo_Config(**d))
|
ALGO_CONFIG = json.load(file)
|
||||||
ALGO_CONFIG.Max_Target_Notional = float(min([ASTER_MULT, EXTEND_MULT]) * ALGO_CONFIG.Target_Open_Cash_Position)
|
ALGO_CONFIG = structs.Algo_Config(**ALGO_CONFIG)
|
||||||
|
|
||||||
VAL_KEY.set('fr_orchestrator_output', json.dumps(asdict(ALGO_CONFIG)))
|
# with open('algo_config.json', 'r', encoding='utf-8') as file:
|
||||||
|
# ALGO_CONFIG = json.load(file, object_hook=lambda d: structs.Algo_Config(**d))
|
||||||
|
|
||||||
|
ALGO_CONFIG.Config.Max_Target_Notional = float(min([ASTER.mult, EXTEND.mult]) * ALGO_CONFIG.Config.Target_Open_Cash_Position)
|
||||||
|
|
||||||
|
VAL_KEY.set('fr_orchestrator_output', json.dumps(ALGO_CONFIG.model_dump()))
|
||||||
|
|
||||||
async with engine.connect() as CON:
|
async with engine.connect() as CON:
|
||||||
### ASTER SETUP ###
|
### ASTER SETUP ###
|
||||||
|
|||||||
@@ -3,25 +3,39 @@ from dataclasses import dataclass, field
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import valkey
|
import valkey
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
# @dataclass(kw_only=True)
|
||||||
@dataclass(kw_only=True)
|
class Algo_Config_Overrides(BaseModel):
|
||||||
class Algo_Config:
|
|
||||||
Config_Updated_Timestamp: int
|
|
||||||
Allow_Ordering_Aster: bool
|
Allow_Ordering_Aster: bool
|
||||||
Allow_Ordering_Extend: bool
|
Allow_Ordering_Extend: bool
|
||||||
|
Flatten_Open_Positions: bool
|
||||||
|
Flip_Side_For_Testing: bool
|
||||||
|
|
||||||
|
# @dataclass(kw_only=True)
|
||||||
|
class Algo_Config_Config(BaseModel):
|
||||||
Loop_Sleep_Sec: int
|
Loop_Sleep_Sec: int
|
||||||
|
Max_Order_Over_Notional_Ratio: float
|
||||||
Max_Target_Notional: float
|
Max_Target_Notional: float
|
||||||
Min_Time_To_Funding_Minutes: int
|
Min_Time_To_Funding_Minutes: int
|
||||||
Min_Fund_Rate_Pct_To_Trade: float
|
Min_Fund_Rate_Pct_To_Trade: float
|
||||||
Price_Worsener_Aster: float
|
Price_Worsener_Aster: float
|
||||||
Price_Worsener_Extend: float
|
Price_Worsener_Extend: float
|
||||||
|
Switch_To_Taker_Seconds: int
|
||||||
Target_Open_Cash_Position: int
|
Target_Open_Cash_Position: int
|
||||||
|
|
||||||
Log_Summary_Each_Loop: bool = False
|
# @dataclass(kw_only=True)
|
||||||
Print_Summary_Each_Loop: bool = False
|
class Algo_Config_Logging(BaseModel):
|
||||||
Flatten_Open_Positions: bool = False
|
Log_Summary_Each_Loop: bool
|
||||||
Flip_Side_For_Testing: bool = False
|
Print_Summary_Each_Loop: bool
|
||||||
|
|
||||||
|
# @dataclass(kw_only=True)
|
||||||
|
class Algo_Config(BaseModel):
|
||||||
|
Updated_Timestamp: int
|
||||||
|
Config: Algo_Config_Config
|
||||||
|
Logging: Algo_Config_Logging
|
||||||
|
Overrides: Algo_Config_Overrides
|
||||||
|
|
||||||
|
|
||||||
@dataclass(kw_only=True)
|
@dataclass(kw_only=True)
|
||||||
class Flags:
|
class Flags:
|
||||||
|
|||||||
@@ -23,3 +23,4 @@ nicegui
|
|||||||
x10-python-trading-starknet
|
x10-python-trading-starknet
|
||||||
eth-keys
|
eth-keys
|
||||||
eth-account
|
eth-account
|
||||||
|
pydantic
|
||||||
110
ws_aster_fund_rate_all.py
Normal file
110
ws_aster_fund_rate_all.py
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
import asyncio
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import socket
|
||||||
|
import traceback
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
from typing import AsyncContextManager
|
||||||
|
import math
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import pandas as pd
|
||||||
|
import requests.packages.urllib3.util.connection as urllib3_cn # type: ignore
|
||||||
|
from sqlalchemy import text
|
||||||
|
import websockets
|
||||||
|
from sqlalchemy.ext.asyncio import create_async_engine
|
||||||
|
import valkey
|
||||||
|
import os
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
import modules.utils as utils
|
||||||
|
|
||||||
|
### Allow only ipv4 ###
|
||||||
|
def allowed_gai_family():
|
||||||
|
return socket.AF_INET
|
||||||
|
urllib3_cn.allowed_gai_family = allowed_gai_family
|
||||||
|
|
||||||
|
### Database ###
|
||||||
|
USE_DB: bool = False
|
||||||
|
USE_VK: bool = True
|
||||||
|
VK_FUND_RATE_ALL = 'fund_rate_aster_all'
|
||||||
|
|
||||||
|
CON: AsyncContextManager | None = None
|
||||||
|
VAL_KEY = None
|
||||||
|
|
||||||
|
### Logging ###
|
||||||
|
load_dotenv()
|
||||||
|
LOG_FILEPATH: str = os.getenv("LOGS_PATH") + '/Fund_Rate_Aster_FR_ALL.log'
|
||||||
|
|
||||||
|
### Globals ###
|
||||||
|
WSS_URL = "wss://fstream.asterdex.com/ws/!markPrice@arr"
|
||||||
|
|
||||||
|
|
||||||
|
### Websocket ###
|
||||||
|
async def ws_stream():
|
||||||
|
async for websocket in websockets.connect(WSS_URL):
|
||||||
|
logging.info(f"Connected to {WSS_URL}")
|
||||||
|
try:
|
||||||
|
async for message in websocket:
|
||||||
|
if isinstance(message, str):
|
||||||
|
try:
|
||||||
|
data = json.loads(message)
|
||||||
|
if data:
|
||||||
|
VAL_KEY.set(VK_FUND_RATE_ALL, json.dumps(data))
|
||||||
|
# print(f'VK_SAVED: {len(data)}')
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
logging.info(f'Initial or unexpected data struct, skipping: {data}')
|
||||||
|
continue
|
||||||
|
except (json.JSONDecodeError, ValueError):
|
||||||
|
logging.warning(f'Message not in JSON format, skipping: {message}')
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
raise ValueError(f'Type: {type(data)} not expected: {message}')
|
||||||
|
except websockets.ConnectionClosed as e:
|
||||||
|
logging.error(f'Connection closed: {e}')
|
||||||
|
logging.error(traceback.format_exc())
|
||||||
|
continue
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f'Connection closed: {e}')
|
||||||
|
logging.error(traceback.format_exc())
|
||||||
|
|
||||||
|
### Startup ###
|
||||||
|
async def main():
|
||||||
|
global VAL_KEY
|
||||||
|
global CON
|
||||||
|
|
||||||
|
if USE_VK:
|
||||||
|
VAL_KEY = valkey.Valkey(host='localhost', port=6379, db=0)
|
||||||
|
else:
|
||||||
|
VAL_KEY = None
|
||||||
|
logging.warning("VALKEY NOT BEING USED, NO DATA WILL BE PUBLISHED")
|
||||||
|
|
||||||
|
if USE_DB:
|
||||||
|
engine = create_async_engine('mysql+asyncmy://root:pwd@localhost/fund_rate')
|
||||||
|
async with engine.connect() as CON:
|
||||||
|
# await create_rtds_btcusd_table(CON=CON)
|
||||||
|
await ws_stream()
|
||||||
|
else:
|
||||||
|
CON = None
|
||||||
|
logging.warning("DATABASE NOT BEING USED, NO DATA WILL BE RECORDED")
|
||||||
|
await ws_stream()
|
||||||
|
|
||||||
|
|
||||||
|
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}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
asyncio.run(main())
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
logging.info("Stream stopped")
|
||||||
133
ws_extended_fund_rate_all.py
Normal file
133
ws_extended_fund_rate_all.py
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
import asyncio
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import socket
|
||||||
|
import traceback
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
from typing import AsyncContextManager
|
||||||
|
import math
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import pandas as pd
|
||||||
|
import requests.packages.urllib3.util.connection as urllib3_cn # type: ignore
|
||||||
|
from sqlalchemy import text
|
||||||
|
import websockets
|
||||||
|
from sqlalchemy.ext.asyncio import create_async_engine
|
||||||
|
import valkey
|
||||||
|
import os
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
import modules.utils as utils
|
||||||
|
|
||||||
|
### Allow only ipv4 ###
|
||||||
|
def allowed_gai_family():
|
||||||
|
return socket.AF_INET
|
||||||
|
urllib3_cn.allowed_gai_family = allowed_gai_family
|
||||||
|
|
||||||
|
### Database ###
|
||||||
|
USE_DB: bool = False
|
||||||
|
USE_VK: bool = True
|
||||||
|
VK_FUND_RATE = 'fund_rate_extended'
|
||||||
|
VK_FUND_RATE_ALL = 'fund_rate_extended_all'
|
||||||
|
|
||||||
|
CON: AsyncContextManager | None = None
|
||||||
|
VAL_KEY = None
|
||||||
|
|
||||||
|
### Logging ###
|
||||||
|
load_dotenv()
|
||||||
|
LOG_FILEPATH: str = os.getenv("LOGS_PATH") + '/Fund_Rate_Extended_FR_ALL.log'
|
||||||
|
|
||||||
|
### Globals ###
|
||||||
|
WSS_URL = "wss://api.starknet.extended.exchange/stream.extended.exchange/v1/funding/"
|
||||||
|
LOCAL_FUNDING_RATES = []
|
||||||
|
|
||||||
|
def time_round_down(dt, interval_mins=5) -> int: # returns timestamp in seconds
|
||||||
|
interval_secs = interval_mins * 60
|
||||||
|
seconds = dt.timestamp()
|
||||||
|
rounded_seconds = math.floor(seconds / interval_secs) * interval_secs
|
||||||
|
|
||||||
|
return rounded_seconds
|
||||||
|
|
||||||
|
### Websocket ###
|
||||||
|
async def ws_stream():
|
||||||
|
global LOCAL_FUNDING_RATES
|
||||||
|
|
||||||
|
async for websocket in websockets.connect(WSS_URL):
|
||||||
|
logging.info(f"Connected to {WSS_URL}")
|
||||||
|
try:
|
||||||
|
async for message in websocket:
|
||||||
|
ts_arrival = round(datetime.now().timestamp()*1000)
|
||||||
|
if isinstance(message, str):
|
||||||
|
try:
|
||||||
|
data = json.loads(message)
|
||||||
|
if data.get('data', None) is not None:
|
||||||
|
# print(f'FR: {data}')
|
||||||
|
# fr_next_update_ts = (time_round_down(dt=datetime.now(timezone.utc), interval_mins=60)+(60*60))*1000
|
||||||
|
fr_update = {
|
||||||
|
'sequence_id': data['seq'],
|
||||||
|
'timestamp_arrival': ts_arrival,
|
||||||
|
'timestamp_msg': data['ts'],
|
||||||
|
'symbol': data['data']['m'],
|
||||||
|
'funding_rate': float(data['data']['f']),
|
||||||
|
'funding_rate_updated_ts_ms': data['data']['T'],
|
||||||
|
# 'next_funding_time_ts_ms': fr_next_update_ts,
|
||||||
|
}
|
||||||
|
LOCAL_FUNDING_RATES = utils.upsert_list_of_dicts_by_id(LOCAL_FUNDING_RATES, fr_update, id='symbol', seq_check_field='sequence_id')
|
||||||
|
VAL_KEY.set(VK_FUND_RATE_ALL, json.dumps(LOCAL_FUNDING_RATES))
|
||||||
|
# print(f'VK_SAVED: {data}')
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
logging.info(f'Initial or unexpected data struct, skipping: {data}')
|
||||||
|
continue
|
||||||
|
except (json.JSONDecodeError, ValueError):
|
||||||
|
logging.warning(f'Message not in JSON format, skipping: {message}')
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
raise ValueError(f'Type: {type(data)} not expected: {message}')
|
||||||
|
except websockets.ConnectionClosed as e:
|
||||||
|
logging.error(f'Connection closed: {e}')
|
||||||
|
logging.error(traceback.format_exc())
|
||||||
|
continue
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f'Connection closed: {e}')
|
||||||
|
logging.error(traceback.format_exc())
|
||||||
|
|
||||||
|
### Startup ###
|
||||||
|
async def main():
|
||||||
|
global VAL_KEY
|
||||||
|
global CON
|
||||||
|
|
||||||
|
if USE_VK:
|
||||||
|
VAL_KEY = valkey.Valkey(host='localhost', port=6379, db=0)
|
||||||
|
else:
|
||||||
|
VAL_KEY = None
|
||||||
|
logging.warning("VALKEY NOT BEING USED, NO DATA WILL BE PUBLISHED")
|
||||||
|
|
||||||
|
if USE_DB:
|
||||||
|
engine = create_async_engine('mysql+asyncmy://root:pwd@localhost/fund_rate')
|
||||||
|
async with engine.connect() as CON:
|
||||||
|
# await create_rtds_btcusd_table(CON=CON)
|
||||||
|
await ws_stream()
|
||||||
|
else:
|
||||||
|
CON = None
|
||||||
|
logging.warning("DATABASE NOT BEING USED, NO DATA WILL BE RECORDED")
|
||||||
|
await ws_stream()
|
||||||
|
|
||||||
|
|
||||||
|
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}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
asyncio.run(main())
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
logging.info("Stream stopped")
|
||||||
Reference in New Issue
Block a user