refactor algo orchestrator and hedge ratio bug fix
This commit is contained in:
@@ -1,13 +1,25 @@
|
||||
{
|
||||
"Config_Updated_Timestamp": 1777098091913,
|
||||
"Allow_Ordering_Aster": false,
|
||||
"Allow_Ordering_Extend": false,
|
||||
"Loop_Sleep_Sec": 5.00,
|
||||
"Max_Target_Notional": 0.00,
|
||||
"Min_Time_To_Funding_Minutes": 10,
|
||||
"Price_Worsener_Aster": 0.0,
|
||||
"Price_Worsener_Extend": 0.0,
|
||||
"Target_Open_Cash_Position": 10,
|
||||
"Print_Summary_Each_Loop" : true,
|
||||
"Flip_Side_For_Testing": false
|
||||
"Updated_Timestamp": 1777433705326,
|
||||
"Config": {
|
||||
"Loop_Sleep_Sec": 0.0,
|
||||
"Max_Order_Over_Notional_Ratio": 1.05,
|
||||
"Max_Target_Notional": 0.0,
|
||||
"Min_Time_To_Funding_Minutes": 60,
|
||||
"Min_Fund_Rate_Pct_To_Trade": 0.0,
|
||||
"Price_Worsener_Aster": 0.0,
|
||||
"Price_Worsener_Extend": -0.1,
|
||||
"Switch_To_Taker_Seconds": 1,
|
||||
"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
|
||||
}
|
||||
|
||||
}
|
||||
37
algo.ipynb
37
algo.ipynb
@@ -2,7 +2,7 @@
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"execution_count": 22,
|
||||
"id": "d1eed397",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -13,12 +13,13 @@
|
||||
"import valkey\n",
|
||||
"\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",
|
||||
"execution_count": 5,
|
||||
"execution_count": 23,
|
||||
"id": "c6151613",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -38,23 +39,31 @@
|
||||
"1"
|
||||
]
|
||||
},
|
||||
"execution_count": 12,
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"config_update = {\n",
|
||||
" # 'Min_Time_To_Funding_Minutes': 60,\n",
|
||||
" # 'Allow_Ordering_Aster': 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",
|
||||
" # 'Price_Worsener_Extend': 0.0,\n",
|
||||
" # 'Log_Summary_Each_Loop': False,\n",
|
||||
" 'Print_Summary_Each_Loop': True,\n",
|
||||
" 'Flatten_Open_Positions': False,\n",
|
||||
" # 'Config': {\n",
|
||||
" # 'Loop_Sleep_Sec': 0.00,\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_Extend': True,\n",
|
||||
" # 'Flip_Side_For_Testing': False,\n",
|
||||
" # 'Flatten_Open_Positions': False,\n",
|
||||
" # },\n",
|
||||
"}\n",
|
||||
"VAL_KEY.publish('fr_orchestrator_input', json.dumps(config_update))"
|
||||
]
|
||||
|
||||
@@ -1,16 +1,24 @@
|
||||
{
|
||||
"Config_Updated_Timestamp": 1777411434591,
|
||||
"Allow_Ordering_Aster": true,
|
||||
"Allow_Ordering_Extend": true,
|
||||
"Loop_Sleep_Sec": 0.0,
|
||||
"Max_Target_Notional": 0.0,
|
||||
"Min_Time_To_Funding_Minutes": 60,
|
||||
"Min_Fund_Rate_Pct_To_Trade": 0.0,
|
||||
"Price_Worsener_Aster": 0.0,
|
||||
"Price_Worsener_Extend": -0.1,
|
||||
"Target_Open_Cash_Position": 10,
|
||||
"Log_Summary_Each_Loop": false,
|
||||
"Print_Summary_Each_Loop": true,
|
||||
"Flatten_Open_Positions": false,
|
||||
"Flip_Side_For_Testing": false
|
||||
"Updated_Timestamp": 1777478176165,
|
||||
"Config": {
|
||||
"Loop_Sleep_Sec": 0.0,
|
||||
"Max_Order_Over_Notional_Ratio": 1.05,
|
||||
"Max_Target_Notional": 0.0,
|
||||
"Min_Time_To_Funding_Minutes": 60,
|
||||
"Min_Fund_Rate_Pct_To_Trade": 0.0,
|
||||
"Price_Worsener_Aster": 0.0,
|
||||
"Price_Worsener_Extend": -0.1,
|
||||
"Switch_To_Taker_Seconds": 1,
|
||||
"Target_Open_Cash_Position": 10
|
||||
},
|
||||
"Logging": {
|
||||
"Log_Summary_Each_Loop": false,
|
||||
"Print_Summary_Each_Loop": true
|
||||
},
|
||||
"Overrides": {
|
||||
"Allow_Ordering_Aster": true,
|
||||
"Allow_Ordering_Extend": true,
|
||||
"Flatten_Open_Positions": false,
|
||||
"Flip_Side_For_Testing": false
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,6 @@ load_dotenv()
|
||||
LOG_FILEPATH: str = os.getenv("LOGS_PATH") + '/Fund_Rate_Algo_Orchestrator.log'
|
||||
|
||||
ALGO_CONFIG: None | dict
|
||||
# ALGO_CONFIG: None | Algo_Config = None
|
||||
|
||||
async def orchestrator() -> None:
|
||||
global ALGO_CONFIG
|
||||
@@ -35,7 +34,7 @@ async def orchestrator() -> None:
|
||||
VK_PUBSUB = VAL_KEY.pubsub()
|
||||
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():
|
||||
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:
|
||||
# ALGO_CONFIG = json.load(f, object_hook=lambda d: Algo_Config(**d))
|
||||
ALGO_CONFIG = json.load(f)
|
||||
ALGO_CONFIG['Config_Updated_Timestamp'] = timestamp
|
||||
ALGO_CONFIG['Updated_Timestamp'] = timestamp
|
||||
|
||||
for k, v in data.items():
|
||||
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))
|
||||
with open('/algo_local_drive/algo_config.json', 'w', encoding='utf-8') as f:
|
||||
# print('SAVING FILE')
|
||||
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:
|
||||
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:
|
||||
logging.info('ORCHESTRATOR SHUTTING DOWN...')
|
||||
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:
|
||||
# ALGO_CONFIG = json.load(f, object_hook=lambda d: Algo_Config(**d))
|
||||
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:
|
||||
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:
|
||||
# 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": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"execution_count": 9,
|
||||
"id": "6c70a8c3",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -27,7 +27,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"execution_count": 12,
|
||||
"id": "ff971ca9",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -42,50 +42,46 @@
|
||||
"CONFIG = MAINNET_CONFIG\n",
|
||||
"\n",
|
||||
"ORDER_MARKET = \"ETH-USD\"\n",
|
||||
"ORDER_SIDE = OrderSide.BUY\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",
|
||||
"execution_count": 3,
|
||||
"execution_count": 13,
|
||||
"id": "fc2c6d2b",
|
||||
"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": [
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Error response from https://api.starknet.extended.exchange/api/v1/user/order: {\"status\":\"ERROR\",\"error\":{\"code\":1125,\"message\":\"Invalid price precision\"}}\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\"}}"
|
||||
"Unclosed client session\n",
|
||||
"client_session: <aiohttp.client.ClientSession object at 0x7b2dc26ba850>\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"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": [
|
||||
"placed_order = await trading_client.place_order(\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",
|
||||
"execution_count": 5,
|
||||
|
||||
176
main.py
176
main.py
@@ -5,7 +5,6 @@ import math
|
||||
import os
|
||||
import time
|
||||
import traceback
|
||||
from dataclasses import asdict, dataclass, field
|
||||
from datetime import datetime, timezone
|
||||
from decimal import ROUND_DOWN, Decimal
|
||||
from typing import AsyncContextManager
|
||||
@@ -37,24 +36,30 @@ load_dotenv()
|
||||
LOG_FILEPATH: str = os.getenv("LOGS_PATH") + '/Fund_Rate_Algo.log'
|
||||
|
||||
### Algo Config ###
|
||||
ALGO_CONFIG: structs.Algo_Config = None
|
||||
ALGO_CONFIG: structs.Algo_Config | None = None
|
||||
MIN_TIME_TO_FUNDING: int
|
||||
|
||||
### CONSTANTS ###
|
||||
### EXCHANGES ###
|
||||
ASTER = structs.Perpetual_Exchange(
|
||||
mult = 150,
|
||||
lh_asset = 'ETH',
|
||||
rh_asset = 'USDT',
|
||||
symbol_asset_separator = '',
|
||||
)
|
||||
|
||||
EXTEND_LH_ASSET: str = 'ETH'
|
||||
EXTEND_RH_ASSET: str = 'USD'
|
||||
EXTEND_TICKER: str = EXTEND_LH_ASSET + '-' + EXTEND_RH_ASSET
|
||||
EXTEND = structs.Perpetual_Exchange(
|
||||
mult = 50,
|
||||
lh_asset = 'ETH',
|
||||
rh_asset = 'USD',
|
||||
symbol_asset_separator = '-',
|
||||
)
|
||||
|
||||
### GLOBALS ###
|
||||
ASTER_MULT = 150
|
||||
EXTEND_MULT = 50
|
||||
Last_Aster_Fill_Time_Ts: float = 0.00
|
||||
Just_Rejected_Or_Expired: bool = False
|
||||
Best_Symbol_by_Exchange: dict = {}
|
||||
|
||||
# ASTER_MULT = 150
|
||||
# EXTEND_MULT = 50
|
||||
|
||||
ASTER_MIN_ORDER_QTY = 0.001
|
||||
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_POSITION
|
||||
global ASTER_UNREALIZED_PNL
|
||||
global ASTER_MULT
|
||||
global ASTER
|
||||
|
||||
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
|
||||
# if not resp: # this can never evaluate
|
||||
# ASTER_MULT = float(d['leverage'])
|
||||
# if abs(ASTER_NOTIONAL_POSITION) > ALGO_CONFIG.Max_Target_Notional*1.01:
|
||||
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}')
|
||||
# ASTER.mult = float(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}')
|
||||
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)}')
|
||||
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
|
||||
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):
|
||||
global EXTEND_NOTIONAL_OBJ
|
||||
global EXTEND_NOTIONAL_POSITION
|
||||
global EXTEND_UNREALIZED_PNL
|
||||
global EXTEND_MULT
|
||||
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_TICKER]
|
||||
pos_dict = [dict(d) for d in resp if dict(d).get('market') == EXTEND.symbol]
|
||||
if pos_dict:
|
||||
pos_dict = pos_dict[0]
|
||||
else:
|
||||
@@ -195,7 +199,7 @@ async def get_extend_notional(resp: dict | None = None):
|
||||
|
||||
pos_dict['timestamp_arrival'] = round(datetime.now().timestamp()*1000)
|
||||
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:
|
||||
pos_dict = pos_dict[0]
|
||||
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}')
|
||||
|
||||
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.Max_Target_Notional*2.01:
|
||||
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}')
|
||||
EXTEND.mult = pos_dict.get('leverage', EXTEND)
|
||||
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} -> {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 ###
|
||||
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}')
|
||||
|
||||
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}')
|
||||
|
||||
### KILL ALGO ###
|
||||
@@ -277,29 +281,39 @@ async def kill_algo():
|
||||
|
||||
### 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'), 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)
|
||||
|
||||
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.Min_Time_To_Funding_Minutes * 60 * 1000
|
||||
MIN_TIME_TO_FUNDING = ALGO_CONFIG.Config.Min_Time_To_Funding_Minutes * 60 * 1000
|
||||
|
||||
### 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'))
|
||||
EXTENDED_FUND_RATE_DICT = json.loads(VAL_KEY.get('fund_rate_extended'))
|
||||
|
||||
ASTER_FUND_RATE = float(ASTER_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
|
||||
EXTEND_FUND_RATE = EXTEND_FUND_RATE * -1
|
||||
|
||||
@@ -370,16 +384,20 @@ async def run_algo():
|
||||
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()
|
||||
# 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()
|
||||
# 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}')
|
||||
@@ -401,6 +419,8 @@ async def run_algo():
|
||||
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()
|
||||
@@ -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}')
|
||||
|
||||
|
||||
# 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))
|
||||
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
|
||||
@@ -429,20 +459,20 @@ async def run_algo():
|
||||
|
||||
if ALPHA_FUND_RATE < 0:
|
||||
ALPHA_CARRY_SIDE = 'BUY'
|
||||
ALPHA_TGT_NOTIONAL = ALGO_CONFIG.Max_Target_Notional
|
||||
ALPHA_TGT_NOTIONAL = ALGO_CONFIG.Config.Max_Target_Notional
|
||||
else:
|
||||
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:
|
||||
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:
|
||||
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.Min_Fund_Rate_Pct_To_Trade*-1) / 100) ) and ( NEXT_NET_FUNDING_RATE <= ( ALGO_CONFIG.Min_Fund_Rate_Pct_To_Trade / 100 ) )
|
||||
if Flags.NET_FUNDING_IS_ZERO or ALGO_CONFIG.Flatten_Open_Positions:
|
||||
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:
|
||||
ALPHA_TGT_NOTIONAL = 0.00
|
||||
# if ASTER_OPEN_ORDERS or EXTEND_OPEN_ORDERS:
|
||||
# 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
|
||||
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
|
||||
|
||||
def print_summary(use_logging: bool = False):
|
||||
OUT: print | logging.info = logging.info if use_logging else print
|
||||
|
||||
OUT(f'''
|
||||
LOOP SLEEP (SEC): {ALGO_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}
|
||||
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: [ 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} [ 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}]
|
||||
|
||||
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_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}
|
||||
''')
|
||||
# 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.Log_Summary_Each_Loop:
|
||||
if ALGO_CONFIG.Logging.Log_Summary_Each_Loop:
|
||||
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()
|
||||
|
||||
|
||||
### ROUTES ###
|
||||
# MIN_EXPECTED_ALPHA_TO_TRADE = 0.0001
|
||||
MIN_EXPECTED_ALPHA_TO_TRADE = -0.000001
|
||||
# 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 ):
|
||||
# logging.info(f'Alpha Ratio too low ({ALPHA_RATIO:.8f}) and no Open Orders...')
|
||||
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) ):
|
||||
if ALGO_CONFIG.Logging.Print_Summary_Each_Loop:
|
||||
print(f'Alpha Ratio too low ({ALPHA_RATIO:.8f}) and no Open Orders...')
|
||||
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 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
|
||||
# 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.Allow_Ordering_Aster:
|
||||
if ASTER_TGT_TAIL_ORDERABLE and 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))
|
||||
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})')
|
||||
await kill_algo()
|
||||
if ASTER_OPEN_ORDERS:
|
||||
@@ -622,6 +655,7 @@ async def run_algo():
|
||||
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:.2f}; Value: {float(ASTER_TGT_TAIL_BASE_QTY)*float(price):.2f}; Price: {float(price):.2f}')
|
||||
logging.info(f'ASTER ORDER PLACED SUCCESS: {order_resp}')
|
||||
print_summary(use_logging=True)
|
||||
@@ -634,13 +668,20 @@ async def run_algo():
|
||||
await aster_cancel_all_orders()
|
||||
|
||||
# EXTEND
|
||||
if EXTEND_TGT_TAIL_ORDERABLE and ALGO_CONFIG.Allow_Ordering_Extend:
|
||||
symbol = EXTEND_TICKER
|
||||
if EXTEND_TGT_TAIL_ORDERABLE and ALGO_CONFIG.Overrides.Allow_Ordering_Extend:
|
||||
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
|
||||
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})')
|
||||
await kill_algo()
|
||||
if EXTEND_OPEN_ORDERS:
|
||||
@@ -667,17 +708,20 @@ async def run_algo():
|
||||
logging.info('EXTEND OPEN ORDER NO PX CHG; SKIPPING')
|
||||
else:
|
||||
try:
|
||||
taker_fee = taker_fee=Decimal("0.00000") if post_only else Decimal("0.00025")
|
||||
order_resp = await EXTEND_CLIENT.place_order(
|
||||
market_name=symbol,
|
||||
amount_of_synthetic=qty,
|
||||
price=price,
|
||||
side=side,
|
||||
taker_fee=Decimal("0.00025"),
|
||||
taker_fee=taker_fee,
|
||||
previous_order_id=open_order_id,
|
||||
post_only=post_only,
|
||||
)
|
||||
except Exception as e:
|
||||
logging.error(f'EXTEND ORDER PLACEMENT FAILED - RESP: {order_resp}')
|
||||
logging.error(f'EXTEND ORDER PLACEMENT FAILED: {e}')
|
||||
logging.error(f'EXTEND ORDER PLACEMENT FAILED - POSTED: market_name:{symbol}, amount_of_synthetic:{qty}, price:{price}, side:{side},taker_fee:{taker_fee}, previous_order_id:{open_order_id}, post_only:{post_only}')
|
||||
logging.error(traceback.format_exc())
|
||||
|
||||
order_resp_dict = dict(order_resp)
|
||||
@@ -694,6 +738,7 @@ async def run_algo():
|
||||
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)
|
||||
@@ -710,8 +755,8 @@ async def run_algo():
|
||||
if ASTER_OPEN_ORDERS or EXTEND_OPEN_ORDERS:
|
||||
continue
|
||||
else:
|
||||
time.sleep(ALGO_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}')
|
||||
time.sleep(ALGO_CONFIG.Config.Loop_Sleep_Sec)
|
||||
# print(f'_____ End No Open Orders _____ (Algo Engine ms: {(time.time() - loop_start)*1000:.2f}); Sleeping for sec: {ALGO_CONFIG.Config.Loop_Sleep_Sec:.0f}')
|
||||
|
||||
except KeyboardInterrupt:
|
||||
logging.info('CANCELLING OPEN ORDERS')
|
||||
@@ -736,10 +781,15 @@ async def main():
|
||||
engine = create_async_engine('mysql+asyncmy://root:pwd@localhost/fund_rate')
|
||||
|
||||
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.Max_Target_Notional = float(min([ASTER_MULT, EXTEND_MULT]) * ALGO_CONFIG.Target_Open_Cash_Position)
|
||||
ALGO_CONFIG = json.load(file)
|
||||
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:
|
||||
### ASTER SETUP ###
|
||||
|
||||
@@ -3,25 +3,39 @@ from dataclasses import dataclass, field
|
||||
from typing import Any
|
||||
|
||||
import valkey
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class Algo_Config:
|
||||
Config_Updated_Timestamp: int
|
||||
# @dataclass(kw_only=True)
|
||||
class Algo_Config_Overrides(BaseModel):
|
||||
Allow_Ordering_Aster: 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
|
||||
Max_Order_Over_Notional_Ratio: float
|
||||
Max_Target_Notional: float
|
||||
Min_Time_To_Funding_Minutes: int
|
||||
Min_Fund_Rate_Pct_To_Trade: float
|
||||
Price_Worsener_Aster: float
|
||||
Price_Worsener_Extend: float
|
||||
Switch_To_Taker_Seconds: int
|
||||
Target_Open_Cash_Position: int
|
||||
|
||||
Log_Summary_Each_Loop: bool = False
|
||||
Print_Summary_Each_Loop: bool = False
|
||||
Flatten_Open_Positions: bool = False
|
||||
Flip_Side_For_Testing: bool = False
|
||||
|
||||
# @dataclass(kw_only=True)
|
||||
class Algo_Config_Logging(BaseModel):
|
||||
Log_Summary_Each_Loop: bool
|
||||
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)
|
||||
class Flags:
|
||||
|
||||
@@ -22,4 +22,5 @@ nicegui
|
||||
# grpcio-tools==1.76.0
|
||||
x10-python-trading-starknet
|
||||
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