refactor valkey into objects with health check

This commit is contained in:
2026-05-15 11:34:25 +09:00
parent f5f43be1a1
commit 1fd922d98f
25 changed files with 6461 additions and 9613 deletions

View File

@@ -47,6 +47,7 @@ async def ws_stream():
try:
data: dict = json.loads(message)
if data:
VAL_KEY.set(name=VK_CHANNEL, value=json.dumps(obj=data))
# print(f'VK_SAVED: {len(data)}')
continue

View File

@@ -29,12 +29,12 @@ 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
CON: AsyncContextManager
VAL_KEY: valkey.Valkey
### Logging ###
load_dotenv()
LOG_FILEPATH: str = os.getenv("LOGS_PATH") + '/Fund_Rate_Extended_FR_ALL.log'
LOG_FILEPATH: str = f'{os.getenv("LOGS_PATH")}/Fund_Rate_Extended_FR_ALL.log'
### Globals ###
WSS_URL = "wss://api.starknet.extended.exchange/stream.extended.exchange/v1/funding/"
@@ -99,8 +99,8 @@ async def main():
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")
raise NotImplementedError('Cannot run without VK')
if USE_DB:
engine = create_async_engine('mysql+asyncmy://root:pwd@localhost/fund_rate')
@@ -108,9 +108,8 @@ async def main():
# 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()
raise NotImplementedError('DB not implemented')
if __name__ == '__main__':

1136
algo.ipynb

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,8 @@
{
"Updated_Timestamp": 1778178429902,
"Updated_Timestamp": 1778798867547,
"Config": {
"Loop_Sleep_Sec": 0.0,
"Max_Order_Over_Notional_Ratio": 1.05,
"Max_Order_Over_Notional_Ratio": 1.5,
"Max_Target_Notional": 0.0,
"Min_Time_To_Funding_Minutes": 57,
"Min_Fund_Rate_Pct_To_Trade": 0.0,

View File

@@ -8,6 +8,9 @@ from datetime import datetime
import valkey
from dotenv import load_dotenv
import modules.utils as utils
import modules.structs as structs
from pathlib import Path
'''
TO DO:
@@ -18,15 +21,24 @@ TO DO:
VK_IN: str = 'fr_orchestrator_input'
VK_OUT: str = 'fr_orchestrator_output'
CONFIG_FILEPATH: str = '/algo_local_drive/algo_config.json'
# CONFIG_FILEPATH: str = 'algo_config.json'
### Logging ###
load_dotenv()
LOG_FILEPATH: str = f'{os.getenv("LOGS_PATH")}/Fund_Rate_Algo_Orchestrator.log'
async def main() -> None:
VAL_KEY: valkey.Valkey = valkey.Valkey(host='localhost', port=6379, db=0, decode_responses=True)
CONFIG_FILEPATH: str = '/algo_local_drive/algo_config.json'
if not Path(CONFIG_FILEPATH).exists():
CONFIG_FILEPATH: str = 'algo_config.json'
# Init Load Config File
with open(file=CONFIG_FILEPATH, mode='r', encoding='utf-8') as f:
Algo_Config: dict = json.load(fp=f)
Algo_Config['Updated_Timestamp'] = round(number=datetime.now().timestamp()*1000)
# vk = structs.VK_Obj(vk_name = 'fr_orchestrator_output', data=Algo_Config)
vk = structs.VK_Orchestrator_Out()
await vk.set(VK_CON=VAL_KEY)
try:
VK_PUBSUB: valkey.client.PubSub = VAL_KEY.pubsub()
@@ -46,11 +58,16 @@ async def main() -> None:
Algo_Config: dict = json.load(fp=f)
Algo_Config['Updated_Timestamp'] = timestamp
if not Algo_Config:
raise ValueError(f'Algo Orchestrator, config is none: {Algo_Config}')
# Update Config w Update Data
Algo_Config: dict = utils.rec_set_dict(orig_dict=Algo_Config, new_dict=data)
# Set VK KV w Updated Config
VAL_KEY.set(name=VK_OUT, value=json.dumps(obj=Algo_Config))
# vk = structs.VK_Obj(vk_name = 'fr_orchestrator_output', data=Algo_Config)
vk = structs.VK_Orchestrator_Out()
await vk.set(VK_CON=VAL_KEY)
# Save Updated Config to File
with open(file=CONFIG_FILEPATH, mode='w', encoding='utf-8') as f:

View File

@@ -2,7 +2,7 @@
"cells": [
{
"cell_type": "code",
"execution_count": 4,
"execution_count": 1,
"id": "3a269644",
"metadata": {},
"outputs": [],
@@ -12,7 +12,7 @@
},
{
"cell_type": "code",
"execution_count": 9,
"execution_count": 2,
"id": "4395fabb",
"metadata": {},
"outputs": [],
@@ -92,7 +92,7 @@
},
{
"cell_type": "code",
"execution_count": 10,
"execution_count": 3,
"id": "2122885a",
"metadata": {},
"outputs": [],
@@ -102,7 +102,7 @@
},
{
"cell_type": "code",
"execution_count": 11,
"execution_count": 4,
"id": "e7341726",
"metadata": {},
"outputs": [
@@ -352,7 +352,7 @@
" {'symbol': 'CHIPUSDT',\n",
" 'positionAmt': '0',\n",
" 'entryPrice': '0.0',\n",
" 'markPrice': '0.00000000',\n",
" 'markPrice': '0.05884243',\n",
" 'unRealizedProfit': '0.00000000',\n",
" 'liquidationPrice': '0',\n",
" 'leverage': '50',\n",
@@ -363,7 +363,7 @@
" 'positionSide': 'BOTH',\n",
" 'notional': '0',\n",
" 'isolatedWallet': '0',\n",
" 'updateTime': 0},\n",
" 'updateTime': 1778212620637},\n",
" {'symbol': '1000BONKUSDT',\n",
" 'positionAmt': '0',\n",
" 'entryPrice': '0.0',\n",
@@ -619,6 +619,21 @@
" 'notional': '0',\n",
" 'isolatedWallet': '0',\n",
" 'updateTime': 0},\n",
" {'symbol': 'MINIMAXUSDT',\n",
" 'positionAmt': '0.0000',\n",
" 'entryPrice': '0.0',\n",
" 'markPrice': '0.00000000',\n",
" 'unRealizedProfit': '0.00000000',\n",
" 'liquidationPrice': '0',\n",
" 'leverage': '2',\n",
" 'maxNotionalValue': '80000',\n",
" 'marginType': 'cross',\n",
" 'isolatedMargin': '0.00000000',\n",
" 'isAutoAddMargin': 'false',\n",
" 'positionSide': 'BOTH',\n",
" 'notional': '0',\n",
" 'isolatedWallet': '0',\n",
" 'updateTime': 0},\n",
" {'symbol': 'AIOTUSDT',\n",
" 'positionAmt': '0',\n",
" 'entryPrice': '0.0',\n",
@@ -1132,18 +1147,18 @@
" {'symbol': 'HYPEUSDT',\n",
" 'positionAmt': '0.00',\n",
" 'entryPrice': '0.0',\n",
" 'markPrice': '0.00000000',\n",
" 'markPrice': '42.14600000',\n",
" 'unRealizedProfit': '0.00000000',\n",
" 'liquidationPrice': '0',\n",
" 'leverage': '300',\n",
" 'maxNotionalValue': '1000',\n",
" 'leverage': '150',\n",
" 'maxNotionalValue': '2000',\n",
" 'marginType': 'cross',\n",
" 'isolatedMargin': '0.00000000',\n",
" 'isAutoAddMargin': 'false',\n",
" 'positionSide': 'BOTH',\n",
" 'notional': '0',\n",
" 'isolatedWallet': '0',\n",
" 'updateTime': 0},\n",
" 'updateTime': 1778174116771},\n",
" {'symbol': 'SYRUPUSDT',\n",
" 'positionAmt': '0',\n",
" 'entryPrice': '0.0',\n",
@@ -1436,7 +1451,7 @@
" 'unRealizedProfit': '0.00000000',\n",
" 'liquidationPrice': '0',\n",
" 'leverage': '2',\n",
" 'maxNotionalValue': '2000000',\n",
" 'maxNotionalValue': '5000000',\n",
" 'marginType': 'cross',\n",
" 'isolatedMargin': '0.00000000',\n",
" 'isAutoAddMargin': 'false',\n",
@@ -1552,7 +1567,7 @@
" {'symbol': 'SOLUSDT',\n",
" 'positionAmt': '0.00',\n",
" 'entryPrice': '0.0',\n",
" 'markPrice': '0.00000000',\n",
" 'markPrice': '88.08336403',\n",
" 'unRealizedProfit': '0.00000000',\n",
" 'liquidationPrice': '0',\n",
" 'leverage': '100',\n",
@@ -2182,7 +2197,7 @@
" {'symbol': 'ETHUSDT',\n",
" 'positionAmt': '0.000',\n",
" 'entryPrice': '0.0',\n",
" 'markPrice': '2327.94576938',\n",
" 'markPrice': '2278.29032657',\n",
" 'unRealizedProfit': '0.00000000',\n",
" 'liquidationPrice': '0',\n",
" 'leverage': '150',\n",
@@ -2193,7 +2208,7 @@
" 'positionSide': 'BOTH',\n",
" 'notional': '0',\n",
" 'isolatedWallet': '0',\n",
" 'updateTime': 1777835550378},\n",
" 'updateTime': 1778127016832},\n",
" {'symbol': 'ZKCUSDT',\n",
" 'positionAmt': '0',\n",
" 'entryPrice': '0.0',\n",
@@ -2332,7 +2347,7 @@
" {'symbol': 'XAGUSDT',\n",
" 'positionAmt': '0.000',\n",
" 'entryPrice': '0.0',\n",
" 'markPrice': '75.87000000',\n",
" 'markPrice': '79.55000000',\n",
" 'unRealizedProfit': '0.00000000',\n",
" 'liquidationPrice': '0',\n",
" 'leverage': '100',\n",
@@ -2351,7 +2366,7 @@
" 'unRealizedProfit': '0.00000000',\n",
" 'liquidationPrice': '0',\n",
" 'leverage': '20',\n",
" 'maxNotionalValue': '5000',\n",
" 'maxNotionalValue': '500000',\n",
" 'marginType': 'cross',\n",
" 'isolatedMargin': '0.00000000',\n",
" 'isAutoAddMargin': 'false',\n",
@@ -2524,6 +2539,21 @@
" 'notional': '0',\n",
" 'isolatedWallet': '0',\n",
" 'updateTime': 0},\n",
" {'symbol': 'NOTUSDT',\n",
" 'positionAmt': '0',\n",
" 'entryPrice': '0.0',\n",
" 'markPrice': '0.00000000',\n",
" 'unRealizedProfit': '0.00000000',\n",
" 'liquidationPrice': '0',\n",
" 'leverage': '3',\n",
" 'maxNotionalValue': '2500000',\n",
" 'marginType': 'cross',\n",
" 'isolatedMargin': '0.00000000',\n",
" 'isAutoAddMargin': 'false',\n",
" 'positionSide': 'BOTH',\n",
" 'notional': '0',\n",
" 'isolatedWallet': '0',\n",
" 'updateTime': 0},\n",
" {'symbol': 'COLLECTUSDT',\n",
" 'positionAmt': '0',\n",
" 'entryPrice': '0.0',\n",
@@ -2539,6 +2569,21 @@
" 'notional': '0',\n",
" 'isolatedWallet': '0',\n",
" 'updateTime': 0},\n",
" {'symbol': 'XIAOMIUSDT',\n",
" 'positionAmt': '0.00',\n",
" 'entryPrice': '0.0',\n",
" 'markPrice': '0.00000000',\n",
" 'unRealizedProfit': '0.00000000',\n",
" 'liquidationPrice': '0',\n",
" 'leverage': '2',\n",
" 'maxNotionalValue': '80000',\n",
" 'marginType': 'cross',\n",
" 'isolatedMargin': '0.00000000',\n",
" 'isAutoAddMargin': 'false',\n",
" 'positionSide': 'BOTH',\n",
" 'notional': '0',\n",
" 'isolatedWallet': '0',\n",
" 'updateTime': 0},\n",
" {'symbol': 'LUNA2USDT',\n",
" 'positionAmt': '0',\n",
" 'entryPrice': '0.0',\n",
@@ -2989,6 +3034,21 @@
" 'notional': '0',\n",
" 'isolatedWallet': '0',\n",
" 'updateTime': 0},\n",
" {'symbol': 'BABYUSDT',\n",
" 'positionAmt': '0',\n",
" 'entryPrice': '0.0',\n",
" 'markPrice': '0.00000000',\n",
" 'unRealizedProfit': '0.00000000',\n",
" 'liquidationPrice': '0',\n",
" 'leverage': '3',\n",
" 'maxNotionalValue': '2500000',\n",
" 'marginType': 'cross',\n",
" 'isolatedMargin': '0.00000000',\n",
" 'isAutoAddMargin': 'false',\n",
" 'positionSide': 'BOTH',\n",
" 'notional': '0',\n",
" 'isolatedWallet': '0',\n",
" 'updateTime': 0},\n",
" {'symbol': 'AVAXUSDT',\n",
" 'positionAmt': '0',\n",
" 'entryPrice': '0.0',\n",
@@ -3154,6 +3214,21 @@
" 'notional': '0',\n",
" 'isolatedWallet': '0',\n",
" 'updateTime': 0},\n",
" {'symbol': 'THETAUSDT',\n",
" 'positionAmt': '0.0',\n",
" 'entryPrice': '0.0',\n",
" 'markPrice': '0.00000000',\n",
" 'unRealizedProfit': '0.00000000',\n",
" 'liquidationPrice': '0',\n",
" 'leverage': '3',\n",
" 'maxNotionalValue': '2500000',\n",
" 'marginType': 'cross',\n",
" 'isolatedMargin': '0.00000000',\n",
" 'isAutoAddMargin': 'false',\n",
" 'positionSide': 'BOTH',\n",
" 'notional': '0',\n",
" 'isolatedWallet': '0',\n",
" 'updateTime': 0},\n",
" {'symbol': 'UNIUSDT',\n",
" 'positionAmt': '0',\n",
" 'entryPrice': '0.0',\n",
@@ -3289,6 +3364,21 @@
" 'notional': '0',\n",
" 'isolatedWallet': '0',\n",
" 'updateTime': 0},\n",
" {'symbol': 'POPMARTUSDT',\n",
" 'positionAmt': '0.0000',\n",
" 'entryPrice': '0.0',\n",
" 'markPrice': '0.00000000',\n",
" 'unRealizedProfit': '0.00000000',\n",
" 'liquidationPrice': '0',\n",
" 'leverage': '2',\n",
" 'maxNotionalValue': '80000',\n",
" 'marginType': 'cross',\n",
" 'isolatedMargin': '0.00000000',\n",
" 'isAutoAddMargin': 'false',\n",
" 'positionSide': 'BOTH',\n",
" 'notional': '0',\n",
" 'isolatedWallet': '0',\n",
" 'updateTime': 0},\n",
" {'symbol': 'ARTXUSDT',\n",
" 'positionAmt': '0',\n",
" 'entryPrice': '0.0',\n",
@@ -3304,6 +3394,21 @@
" 'notional': '0',\n",
" 'isolatedWallet': '0',\n",
" 'updateTime': 0},\n",
" {'symbol': 'CARDSUSDT',\n",
" 'positionAmt': '0',\n",
" 'entryPrice': '0.0',\n",
" 'markPrice': '0.00000000',\n",
" 'unRealizedProfit': '0.00000000',\n",
" 'liquidationPrice': '0',\n",
" 'leverage': '2',\n",
" 'maxNotionalValue': '80000',\n",
" 'marginType': 'cross',\n",
" 'isolatedMargin': '0.00000000',\n",
" 'isAutoAddMargin': 'false',\n",
" 'positionSide': 'BOTH',\n",
" 'notional': '0',\n",
" 'isolatedWallet': '0',\n",
" 'updateTime': 0},\n",
" {'symbol': 'SXPUSDT',\n",
" 'positionAmt': '0',\n",
" 'entryPrice': '0.0',\n",
@@ -3394,6 +3499,21 @@
" 'notional': '0',\n",
" 'isolatedWallet': '0',\n",
" 'updateTime': 0},\n",
" {'symbol': 'AGTUSDT',\n",
" 'positionAmt': '0',\n",
" 'entryPrice': '0.0',\n",
" 'markPrice': '0.00000000',\n",
" 'unRealizedProfit': '0.00000000',\n",
" 'liquidationPrice': '0',\n",
" 'leverage': '3',\n",
" 'maxNotionalValue': '2500000',\n",
" 'marginType': 'cross',\n",
" 'isolatedMargin': '0.00000000',\n",
" 'isAutoAddMargin': 'false',\n",
" 'positionSide': 'BOTH',\n",
" 'notional': '0',\n",
" 'isolatedWallet': '0',\n",
" 'updateTime': 0},\n",
" {'symbol': 'CHZUSDT',\n",
" 'positionAmt': '0',\n",
" 'entryPrice': '0.0',\n",
@@ -3562,7 +3682,7 @@
" {'symbol': 'INITUSDT',\n",
" 'positionAmt': '0',\n",
" 'entryPrice': '0.0',\n",
" 'markPrice': '0.08765944',\n",
" 'markPrice': '0.11143193',\n",
" 'unRealizedProfit': '0.00000000',\n",
" 'liquidationPrice': '0',\n",
" 'leverage': '50',\n",
@@ -3697,7 +3817,7 @@
" {'symbol': 'BNBUSDT',\n",
" 'positionAmt': '0.00',\n",
" 'entryPrice': '0.0',\n",
" 'markPrice': '619.51000000',\n",
" 'markPrice': '639.19000000',\n",
" 'unRealizedProfit': '0.00000000',\n",
" 'liquidationPrice': '0',\n",
" 'leverage': '100',\n",
@@ -3727,7 +3847,7 @@
" {'symbol': 'XMRUSDT',\n",
" 'positionAmt': '0.000',\n",
" 'entryPrice': '0.0',\n",
" 'markPrice': '0.00000000',\n",
" 'markPrice': '390.77183333',\n",
" 'unRealizedProfit': '0.00000000',\n",
" 'liquidationPrice': '0',\n",
" 'leverage': '50',\n",
@@ -3817,7 +3937,7 @@
" {'symbol': 'DOGEUSDT',\n",
" 'positionAmt': '0',\n",
" 'entryPrice': '0.0',\n",
" 'markPrice': '0.00000000',\n",
" 'markPrice': '0.10631546',\n",
" 'unRealizedProfit': '0.00000000',\n",
" 'liquidationPrice': '0',\n",
" 'leverage': '75',\n",
@@ -3828,7 +3948,7 @@
" 'positionSide': 'BOTH',\n",
" 'notional': '0',\n",
" 'isolatedWallet': '0',\n",
" 'updateTime': 0},\n",
" 'updateTime': 1778098357669},\n",
" {'symbol': 'GPSUSDT',\n",
" 'positionAmt': '0',\n",
" 'entryPrice': '0.0',\n",
@@ -3971,7 +4091,7 @@
" 'unRealizedProfit': '0.00000000',\n",
" 'liquidationPrice': '0',\n",
" 'leverage': '2',\n",
" 'maxNotionalValue': '60000',\n",
" 'maxNotionalValue': '5000000',\n",
" 'marginType': 'cross',\n",
" 'isolatedMargin': '0.00000000',\n",
" 'isAutoAddMargin': 'false',\n",
@@ -4039,6 +4159,21 @@
" 'notional': '0',\n",
" 'isolatedWallet': '0',\n",
" 'updateTime': 0},\n",
" {'symbol': 'DOGSUSDT',\n",
" 'positionAmt': '0',\n",
" 'entryPrice': '0.0',\n",
" 'markPrice': '0.00000000',\n",
" 'unRealizedProfit': '0.00000000',\n",
" 'liquidationPrice': '0',\n",
" 'leverage': '3',\n",
" 'maxNotionalValue': '2500000',\n",
" 'marginType': 'cross',\n",
" 'isolatedMargin': '0.00000000',\n",
" 'isAutoAddMargin': 'false',\n",
" 'positionSide': 'BOTH',\n",
" 'notional': '0',\n",
" 'isolatedWallet': '0',\n",
" 'updateTime': 0},\n",
" {'symbol': 'COSUSDT',\n",
" 'positionAmt': '0',\n",
" 'entryPrice': '0.0',\n",
@@ -4174,6 +4309,21 @@
" 'notional': '0',\n",
" 'isolatedWallet': '0',\n",
" 'updateTime': 0},\n",
" {'symbol': 'SOXLUSDT',\n",
" 'positionAmt': '0.0000',\n",
" 'entryPrice': '0.0',\n",
" 'markPrice': '0.00000000',\n",
" 'unRealizedProfit': '0.00000000',\n",
" 'liquidationPrice': '0',\n",
" 'leverage': '2',\n",
" 'maxNotionalValue': '2000000',\n",
" 'marginType': 'cross',\n",
" 'isolatedMargin': '0.00000000',\n",
" 'isAutoAddMargin': 'false',\n",
" 'positionSide': 'BOTH',\n",
" 'notional': '0',\n",
" 'isolatedWallet': '0',\n",
" 'updateTime': 0},\n",
" {'symbol': 'HEMIUSDT',\n",
" 'positionAmt': '0',\n",
" 'entryPrice': '0.0',\n",
@@ -4346,7 +4496,7 @@
" 'unRealizedProfit': '0.00000000',\n",
" 'liquidationPrice': '0',\n",
" 'leverage': '20',\n",
" 'maxNotionalValue': '25000',\n",
" 'maxNotionalValue': '500000',\n",
" 'marginType': 'cross',\n",
" 'isolatedMargin': '0.00000000',\n",
" 'isAutoAddMargin': 'false',\n",
@@ -5047,7 +5197,7 @@
" {'symbol': '4USDT',\n",
" 'positionAmt': '0',\n",
" 'entryPrice': '0.0',\n",
" 'markPrice': '0.00000000',\n",
" 'markPrice': '0.01297365',\n",
" 'unRealizedProfit': '0.00000000',\n",
" 'liquidationPrice': '0',\n",
" 'leverage': '50',\n",
@@ -5058,7 +5208,7 @@
" 'positionSide': 'BOTH',\n",
" 'notional': '0',\n",
" 'isolatedWallet': '0',\n",
" 'updateTime': 0},\n",
" 'updateTime': 1777965261969},\n",
" {'symbol': 'BELUSDT',\n",
" 'positionAmt': '0',\n",
" 'entryPrice': '0.0',\n",
@@ -5330,20 +5480,20 @@
" 'isolatedWallet': '0',\n",
" 'updateTime': 0},\n",
" {'symbol': 'LITUSDT',\n",
" 'positionAmt': '0',\n",
" 'entryPrice': '0.0',\n",
" 'markPrice': '0.93992527',\n",
" 'unRealizedProfit': '0.00000000',\n",
" 'liquidationPrice': '0',\n",
" 'positionAmt': '179',\n",
" 'entryPrice': '0.9766',\n",
" 'markPrice': '0.97480000',\n",
" 'unRealizedProfit': '-0.32220000',\n",
" 'liquidationPrice': '0.68966790',\n",
" 'leverage': '50',\n",
" 'maxNotionalValue': '2500',\n",
" 'marginType': 'cross',\n",
" 'isolatedMargin': '0.00000000',\n",
" 'isAutoAddMargin': 'false',\n",
" 'positionSide': 'BOTH',\n",
" 'notional': '0',\n",
" 'notional': '174.48920000',\n",
" 'isolatedWallet': '0',\n",
" 'updateTime': 1777819835027},\n",
" 'updateTime': 1778213915476},\n",
" {'symbol': 'FFUSDT',\n",
" 'positionAmt': '0',\n",
" 'entryPrice': '0.0',\n",
@@ -5389,6 +5539,21 @@
" 'notional': '0',\n",
" 'isolatedWallet': '0',\n",
" 'updateTime': 0},\n",
" {'symbol': 'STEEMUSDT',\n",
" 'positionAmt': '0',\n",
" 'entryPrice': '0.0',\n",
" 'markPrice': '0.00000000',\n",
" 'unRealizedProfit': '0.00000000',\n",
" 'liquidationPrice': '0',\n",
" 'leverage': '3',\n",
" 'maxNotionalValue': '2500000',\n",
" 'marginType': 'cross',\n",
" 'isolatedMargin': '0.00000000',\n",
" 'isAutoAddMargin': 'false',\n",
" 'positionSide': 'BOTH',\n",
" 'notional': '0',\n",
" 'isolatedWallet': '0',\n",
" 'updateTime': 0},\n",
" {'symbol': 'BMTUSDT',\n",
" 'positionAmt': '0',\n",
" 'entryPrice': '0.0',\n",
@@ -5509,6 +5674,21 @@
" 'notional': '0',\n",
" 'isolatedWallet': '0',\n",
" 'updateTime': 0},\n",
" {'symbol': 'DRAMUSDT',\n",
" 'positionAmt': '0.00',\n",
" 'entryPrice': '0.0',\n",
" 'markPrice': '0.00000000',\n",
" 'unRealizedProfit': '0.00000000',\n",
" 'liquidationPrice': '0',\n",
" 'leverage': '2',\n",
" 'maxNotionalValue': '80000',\n",
" 'marginType': 'cross',\n",
" 'isolatedMargin': '0.00000000',\n",
" 'isAutoAddMargin': 'false',\n",
" 'positionSide': 'BOTH',\n",
" 'notional': '0',\n",
" 'isolatedWallet': '0',\n",
" 'updateTime': 0},\n",
" {'symbol': 'BOBUSDT',\n",
" 'positionAmt': '0',\n",
" 'entryPrice': '0.0',\n",
@@ -5947,7 +6127,7 @@
" {'symbol': 'WLFIUSDT',\n",
" 'positionAmt': '0',\n",
" 'entryPrice': '0.0',\n",
" 'markPrice': '0.05870000',\n",
" 'markPrice': '0.07422827',\n",
" 'unRealizedProfit': '0.00000000',\n",
" 'liquidationPrice': '0',\n",
" 'leverage': '25',\n",
@@ -6154,6 +6334,21 @@
" 'notional': '0',\n",
" 'isolatedWallet': '0',\n",
" 'updateTime': 0},\n",
" {'symbol': 'BILLUSDT',\n",
" 'positionAmt': '0',\n",
" 'entryPrice': '0.0',\n",
" 'markPrice': '0.00000000',\n",
" 'unRealizedProfit': '0.00000000',\n",
" 'liquidationPrice': '0',\n",
" 'leverage': '3',\n",
" 'maxNotionalValue': '2500000',\n",
" 'marginType': 'cross',\n",
" 'isolatedMargin': '0.00000000',\n",
" 'isAutoAddMargin': 'false',\n",
" 'positionSide': 'BOTH',\n",
" 'notional': '0',\n",
" 'isolatedWallet': '0',\n",
" 'updateTime': 0},\n",
" {'symbol': 'CYBERUSDT',\n",
" 'positionAmt': '0',\n",
" 'entryPrice': '0.0',\n",
@@ -6607,7 +6802,7 @@
" {'symbol': 'BTCUSDT',\n",
" 'positionAmt': '0.000',\n",
" 'entryPrice': '0.0',\n",
" 'markPrice': '78659.35912065',\n",
" 'markPrice': '79558.40000000',\n",
" 'unRealizedProfit': '0.00000000',\n",
" 'liquidationPrice': '0',\n",
" 'leverage': '150',\n",
@@ -6664,6 +6859,21 @@
" 'notional': '0',\n",
" 'isolatedWallet': '0',\n",
" 'updateTime': 0},\n",
" {'symbol': 'TENCENTUSDT',\n",
" 'positionAmt': '0.0000',\n",
" 'entryPrice': '0.0',\n",
" 'markPrice': '0.00000000',\n",
" 'unRealizedProfit': '0.00000000',\n",
" 'liquidationPrice': '0',\n",
" 'leverage': '2',\n",
" 'maxNotionalValue': '80000',\n",
" 'marginType': 'cross',\n",
" 'isolatedMargin': '0.00000000',\n",
" 'isAutoAddMargin': 'false',\n",
" 'positionSide': 'BOTH',\n",
" 'notional': '0',\n",
" 'isolatedWallet': '0',\n",
" 'updateTime': 0},\n",
" {'symbol': 'HOODUSDT',\n",
" 'positionAmt': '0.00',\n",
" 'entryPrice': '0.0',\n",
@@ -6876,7 +7086,7 @@
" 'updateTime': 0}]"
]
},
"execution_count": 11,
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}

View File

@@ -9,7 +9,7 @@ services:
context: ./
dockerfile: ./algo/Dockerfile
volumes:
- /home/ubuntu/data:/home/ubuntu/data:rw # Read-write access to data
- /home/ubuntu/logs:/home/ubuntu/logs:rw # Read-write access to data
- /root/data:/root/data:rw # Read-write access to data
- /root/logs:/root/logs:rw # Read-write access to data
- ./:/algo_local_drive:rw # Read-write access to data
network_mode: "host"

View File

@@ -1,4 +1,4 @@
# tail -f Fund_Rate_Aster_FR_ALL.log Fund_Rate_Extended_FR_ALL.log Fund_Rate_Engine_BFR.log 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_User.log
# tail -f Fund_Rate_Algo.log Fund_Rate_Engine_BFR.log Fund_Rate_Algo_Orchestrator.log Fund_Rate_Aster_User.log Fund_Rate_Aster.log Fund_Rate_Extended_FR.log Fund_Rate_Extended_OB.log Fund_Rate_Extended_User.log
services:
# algo:
@@ -9,126 +9,106 @@ services:
# dockerfile: ./algo/Dockerfile
# depends_on:
# - algo_orchestrator
# - engine_best_funding_rate
# - ws_aster
# - ws_aster_user
# - ws_extended_fund_rate
# - ws_extended_orderbook
# - ws_extended_trades
# - ws_extended_user
# volumes:
# - /home/ubuntu/data:/home/ubuntu/data:rw # Read-write access to data
# - /home/ubuntu/logs:/home/ubuntu/logs:rw # Read-write access to data
# - /root/data:/root/data:rw # Read-write access to data
# - /root/logs:/root/logs:rw # Read-write access to data
# network_mode: "host"
algo_orchestrator:
container_name: algo_orchestrator
restart: "unless-stopped"
restart: "no"
build:
context: ./
dockerfile: ./algo_orchestrator/Dockerfile
volumes:
- /home/ubuntu/data:/home/ubuntu/data:rw # Read-write access to data
- /home/ubuntu/logs:/home/ubuntu/logs:rw # Read-write access to data
- /root/data:/root/data:rw # Read-write access to data
- /root/logs:/root/logs:rw # Read-write access to data
- ./:/algo_local_drive:rw # Read-write access to data
network_mode: "host"
engine_best_funding_rate:
container_name: engine_best_funding_rate
restart: "unless-stopped"
restart: "no"
build:
context: ./
dockerfile: ./engine_best_funding_rate/Dockerfile
volumes:
- /home/ubuntu/data:/home/ubuntu/data:rw # Read-write access to data
- /home/ubuntu/logs:/home/ubuntu/logs:rw # Read-write access to data
network_mode: "host"
ws_extended_fund_rate_all:
container_name: ws_extended_fund_rate_all
restart: "unless-stopped"
build:
context: ./
dockerfile: ./ws_extended_fund_rate_all/Dockerfile
volumes:
- /home/ubuntu/data:/home/ubuntu/data:rw # Read-write access to data
- /home/ubuntu/logs:/home/ubuntu/logs:rw # Read-write access to data
network_mode: "host"
ws_aster_fund_rate_all:
container_name: ws_aster_fund_rate_all
restart: "unless-stopped"
build:
context: ./
dockerfile: ./ws_aster_fund_rate_all/Dockerfile
volumes:
- /home/ubuntu/data:/home/ubuntu/data:rw # Read-write access to data
- /home/ubuntu/logs:/home/ubuntu/logs:rw # Read-write access to data
- /root/data:/root/data:rw # Read-write access to data
- /root/logs:/root/logs:rw # Read-write access to data
network_mode: "host"
ws_aster:
container_name: ws_aster
restart: "unless-stopped"
restart: "no"
build:
context: ./
dockerfile: ./ws_aster/Dockerfile
volumes:
- /home/ubuntu/data:/home/ubuntu/data:rw # Read-write access to data
- /home/ubuntu/logs:/home/ubuntu/logs:rw # Read-write access to data
- /root/data:/root/data:rw # Read-write access to data
- /root/logs:/root/logs:rw # Read-write access to data
network_mode: "host"
ws_aster_user:
container_name: ws_aster_user
restart: "unless-stopped"
restart: "no"
build:
context: ./
dockerfile: ./ws_aster_user/Dockerfile
volumes:
- /home/ubuntu/data:/home/ubuntu/data:rw # Read-write access to data
- /home/ubuntu/logs:/home/ubuntu/logs:rw # Read-write access to data
- /root/data:/root/data:rw # Read-write access to data
- /root/logs:/root/logs:rw # Read-write access to data
network_mode: "host"
ws_extended_fund_rate:
container_name: ws_extended_fund_rate
restart: "unless-stopped"
restart: "no"
build:
context: ./
dockerfile: ./ws_extended_fund_rate/Dockerfile
volumes:
- /home/ubuntu/data:/home/ubuntu/data:rw # Read-write access to data
- /home/ubuntu/logs:/home/ubuntu/logs:rw # Read-write access to data
- /root/data:/root/data:rw # Read-write access to data
- /root/logs:/root/logs:rw # Read-write access to data
network_mode: "host"
ws_extended_orderbook:
container_name: ws_extended_orderbook
restart: "unless-stopped"
restart: "no"
build:
context: ./
dockerfile: ./ws_extended_orderbook/Dockerfile
volumes:
- /home/ubuntu/data:/home/ubuntu/data:rw # Read-write access to dataw
- /home/ubuntu/logs:/home/ubuntu/logs:rw # Read-write access to data
- /root/data:/root/data:rw # Read-write access to dataw
- /root/logs:/root/logs:rw # Read-write access to data
network_mode: "host"
# ws_extended_trades:
# container_name: ws_extended_trades
# restart: "unless-stopped"
# build:
# context: ./
# dockerfile: ./ws_extended_trades/Dockerfile
# volumes:
# - /home/ubuntu/data:/home/ubuntu/data:rw # Read-write access to dataw
# - /home/ubuntu/logs:/home/ubuntu/logs:rw # Read-write access to data
# network_mode: "host"
ws_extended_user:
container_name: ws_extended_user
restart: "unless-stopped"
restart: "no"
build:
context: ./
dockerfile: ./ws_extended_user/Dockerfile
volumes:
- /home/ubuntu/data:/home/ubuntu/data:rw # Read-write access to data
- /home/ubuntu/logs:/home/ubuntu/logs:rw # Read-write access to data
- /root/data:/root/data:rw # Read-write access to data
- /root/logs:/root/logs:rw # Read-write access to data
network_mode: "host"
# ws_extended_trades:
# container_name: ws_extended_trades
# restart: "no"
# build:
# context: ./
# dockerfile: ./ws_extended_trades/Dockerfile
# volumes:
# - /root/data:/root/data:rw # Read-write access to dataw
# - /root/logs:/root/logs:rw # Read-write access to data
# network_mode: "host"
# ng:
# container_name: ng
# restart: "unless-stopped"
# restart: "no"
# build:
# context: ./
# dockerfile: ./ng/Dockerfile
# volumes:
# - /home/ubuntu/data:/home/ubuntu/data:rw # Read-write access to data
# - /home/ubuntu/logs:/home/ubuntu/logs:rw # Read-write access to data
# - /root/data:/root/data:rw # Read-write access to data
# - /root/logs:/root/logs:rw # Read-write access to data
# network_mode: "host"

File diff suppressed because one or more lines are too long

View File

@@ -93,7 +93,10 @@ async def get_aster_exch_info() -> pd.DataFrame:
return df
def load_aster_current_fr(df_aster_exch_info: pd.DataFrame) -> pd.DataFrame:
df = pd.DataFrame(data=json.loads(s=VAL_KEY.get(name='fund_rate_aster_all'))) # ty:ignore[invalid-argument-type]
vk_get: str = VAL_KEY.get(name='fund_rate_aster_all') # ty:ignore[invalid-assignment]
if not vk_get:
raise ValueError(f'fund_rate_aster_all is empty: {vk_get}')
df = pd.DataFrame(data=json.loads(vk_get))
df: pd.DataFrame = 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')
@@ -190,9 +193,6 @@ async def loop() -> None:
'net_mult_x_net_fr_abs','net_funding_rate_abs','net_funding_rate','next_funding_at_same_time','last_trade_ts_ast']
].sort_values(by='net_mult_x_net_fr_abs', ascending=False).reset_index(drop=True)
# min_daily_volume = 100_000
# df_best_fr_rate = df_best_fr_rate.loc[ (df_best_fr_rate['daily_volume_ast']>=min_daily_volume) & (df_best_fr_rate['daily_volume_ext']>min_daily_volume) ,:].reset_index(drop=True)
last_trade_max_ts = []
for index, row in df_best_fr_rate.iterrows():
r = json.loads(requests.get(f'https://api.starknet.extended.exchange/api/v1/info/markets/{row['symbol_ext']}/trades').text)
@@ -204,21 +204,41 @@ async def loop() -> None:
df_best_fr_rate['last_trade_ts_dt_ast'] = pd.to_datetime(df_best_fr_rate['last_trade_ts_ast'], unit='ms')
df_best_fr_rate['last_trade_ts_dt_ext'] = pd.to_datetime(df_best_fr_rate['last_trade_ts_ext'], unit='ms')
df_best_fr_rate = df_best_fr_rate.loc[( (datetime.now().timestamp()*1000 )-df_best_fr_rate['last_trade_ts_ast']) < (5*60*1000) ] # Last traded in 3min
# df_best_fr_rate = df_best_fr_rate.loc[( (datetime.now().timestamp()*1000 )-df_best_fr_rate['last_trade_ts_ext']) < (15*60*1000) ] # Last traded in 15min
# print(df_best_fr_rate.columns)
# print(df_best_fr_rate.iloc[0])
candles_ratios = []
for index, row in df_best_fr_rate.iterrows():
df = await get_candles(symbol=row['symbol_ext'])
try:
df = await get_candles(symbol=row['symbol_ext'])
except Exception as e:
logging.warning(f'BFR failed to get candles...sleeping and retrying: {e}')
time.sleep(5)
df = await get_candles(symbol=row['symbol_ext'])
buy_ratio_ext = float(df['med_ratio_aster_over_extend'].median())
buy_ratio_std = float(df['med_ratio_aster_over_extend'].std())
candles_ratios.append({'symbol_ext':row['symbol_ext'], 'buy_ratio_std': buy_ratio_std, 'buy_ratio_ext':buy_ratio_ext,'buy_ratio_ast':buy_ratio_ext*-1})
df_best_fr_rate = df_best_fr_rate.merge(pd.DataFrame(candles_ratios), on='symbol_ext', how='left')
### Set Unfiltered Master Data ###
master_data = df_best_fr_rate[
['symbol_ast','max_leverage_ast','lh_asset_ast','rh_asset_ast','funding_rate_ast','min_price_ast','min_order_size_ast','min_lot_size_ast','min_notional_ast','buy_ratio_ast',
'symbol_ext','max_leverage_ext','lh_asset_ext','rh_asset_ext','funding_rate_ext','min_price_ext','min_order_size_ext','min_lot_size_ext','min_notional_ext','buy_ratio_ext', 'buy_ratio_std','next_funding_at_same_time']
].to_json(orient='records')
VAL_KEY.set(name='fr_engine_best_fund_rate_master', value=str(master_data))
### Filter BFR Data ###
df_best_fr_rate = df_best_fr_rate.loc[( (datetime.now().timestamp()*1000 )-df_best_fr_rate['last_trade_ts_ast']) < (5*60*1000) ] # Last traded in 3min
df_best_fr_rate = df_best_fr_rate.loc[( (datetime.now().timestamp()*1000 )-df_best_fr_rate['last_trade_ts_ext']) < (15*60*1000) ] # Last traded in 15min
min_daily_volume = 100_000
df_best_fr_rate = df_best_fr_rate.loc[ (df_best_fr_rate['daily_volume_ast']>=min_daily_volume) & (df_best_fr_rate['daily_volume_ext']>min_daily_volume) ,:].reset_index(drop=True)
# print(df_best_fr_rate.columns)
# print(df_best_fr_rate.iloc[0])
if len(df_best_fr_rate) < 1:
raise ValueError(f'NO BFR RATE: {df_best_fr_rate}')
@@ -259,12 +279,7 @@ async def loop() -> None:
best_next_funding_pair: dict[str, dict] = {'ASTER': asdict(obj=ASTER), 'EXTEND': asdict(obj=EXTEND)}
VAL_KEY.set(name='fr_engine_best_fund_rate_output', value=json.dumps(obj=best_next_funding_pair))
master_data = df_best_fr_rate[
['symbol_ast','max_leverage_ast','lh_asset_ast','rh_asset_ast','funding_rate_ast','min_price_ast','min_order_size_ast','min_lot_size_ast','min_notional_ast','buy_ratio_ast',
'symbol_ext','max_leverage_ext','lh_asset_ext','rh_asset_ext','funding_rate_ext','min_price_ext','min_order_size_ext','min_lot_size_ext','min_notional_ext','buy_ratio_ext', 'buy_ratio_std','next_funding_at_same_time']
].to_json(orient='records')
VAL_KEY.set(name='fr_engine_best_fund_rate_master', value=str(master_data))
print(df_best_fr_rate[['symbol_ext','max_leverage_ext','buy_ratio_ext','net_funding_rate','daily_volume_ast','buy_ratio_ast']].head(10))
logging.info(f'BFR REFRESHED @ {datetime.now()}')
time.sleep(LOOP_SLEEP_SEC)

View File

@@ -0,0 +1,129 @@
import asyncio
import json
import logging
import os
import traceback
from datetime import datetime
import time
import valkey
from dotenv import load_dotenv
import modules.utils as utils
import modules.structs as structs
from pydantic import BaseModel
import docker
### Database ###
VAL_KEY: valkey.Valkey = valkey.Valkey(host='localhost', port=6379, db=0, decode_responses=True)
DOCKER = docker.from_env()
### Logging ###
load_dotenv()
LOG_FILEPATH: str = f'{os.getenv("LOGS_PATH")}/Fund_Rate_Engine_Health.log'
### CONSTANTS ###
MAX_TIME_SINCE_LAST_UPDATE_MS: int = 1000 * 60 * 3 # 1000 x 60 sec x [minutes]
LOOP_SLEEP_SEC: int = 5
### Globals ###
### Structs ###
class Health_Status(BaseModel):
status: str # ENUM: 'HEALTHY' | 'UNHEALTHY' | 'DEAD'
timestamp: int
vk_objs: list[structs.VK_Obj]
async def get_algo_working_symbol() -> str:
vk_get: str = VAL_KEY.get(name='fr_algo_working_symbol') # ty:ignore[invalid-assignment]
d = json.loads(vk_get)
algo_symbol: str = d.get('EXTEND', {}).get('symbol', '')
return algo_symbol
async def main() -> None:
vk_objs = [
structs.VK_Orchestrator_Output(),
structs.VK_Working_Symbol(),
structs.VK_User_Orders_Extend(),
structs.VK_User_Trades_Extend(),
structs.VK_User_Balances_Aster(),
structs.VK_User_Balances_Extend(),
structs.VK_User_Positions_Aster(),
structs.VK_User_Positions_Extend(),
structs.VK_FR_Aster(),
structs.VK_FR_All_Aster(),
structs.VK_FR_Extend(),
structs.VK_FR_All_Extend(),
structs.VK_Ticker_Aster(),
structs.VK_Ticker_Extend(),
structs.VK_Trade_Aster(),
structs.VK_Trade_Extend(),
]
health_status = Health_Status(
status = 'HEALTHY',
timestamp = round(number=datetime.now().timestamp()*1000),
vk_objs = vk_objs, # ty:ignore[invalid-argument-type]
)
try:
while True:
algo_symbol = await get_algo_working_symbol()
health_status.timestamp = round(number=datetime.now().timestamp()*1000)
for o in health_status.vk_objs:
vk_symbol = o.data.get('symbol') if isinstance(o.data, dict) else None
await o.checks.run_checks(args={
'timestamp': health_status.timestamp,
'algo_symbol': algo_symbol,
'vk_symbol': vk_symbol,
})
vk_statuses = [o.status for o in health_status.vk_objs]
if 'DEAD' in vk_statuses:
health_status.status = 'DEAD'
elif 'UNHEALTHY' in vk_statuses:
health_status.status = 'UNHEALTHY'
else:
health_status.status = 'HEALTHY'
if health_status.status != 'HEALTHY':
all_containers = DOCKER.containers.list(all=True)
for c in all_containers:
if c.status == 'running':
logging.warning(f"stopping: ID: {c.id}, Name: {c.name}, Status: {c.status}")
container = DOCKER.containers.get(c.id)
container.stop(timeout=10)
logging.info('Stopped all containers')
# VAL_KEY.set(name='health_status', value=json.dumps(obj=(health_status)))
logging.info(vk_statuses)
if LOOP_SLEEP_SEC > 0:
time.sleep(LOOP_SLEEP_SEC)
continue
except KeyboardInterrupt:
logging.info(msg='ORCHESTRATOR SHUTTING DOWN...')
except Exception as e:
logging.error(msg=traceback.format_exc())
logging.critical(msg=f'*** ORCHESTRATOR CRASHED: {e}')
if __name__ == '__main__':
START_TIME: int = round(number=datetime.now().timestamp()*1000)
logging.info(msg=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(msg=f"STARTED: {START_TIME}")
asyncio.run(main())

197
main.py
View File

@@ -24,7 +24,7 @@ import math
import os
import time
import traceback
from datetime import datetime, timezone
from datetime import datetime
from decimal import ROUND_DOWN, ROUND_UP, ROUND_HALF_UP, Decimal
from typing import AsyncContextManager
from dataclasses import dataclass, asdict, field
@@ -82,7 +82,7 @@ async def output_algo_status(status: str) -> None:
Algo_Status.last_update_ts_ms = int(round(datetime.now().timestamp()*1000, 2))
Algo_Status.status = status
VAL_KEY.set('algo_status', json.dumps(Algo_Status.model_dump()))
VAL_KEY.set('algo_status', json.dumps(Algo_Status.model_dump(), cls=utils.JSONEncoder_Decimal))
def create_exchange_objs_from_dict(exchanges_dict: dict) -> tuple[structs.Perpetual_Exchange, structs.Perpetual_Exchange]:
Aster = structs.Perpetual_Exchange(
@@ -129,8 +129,8 @@ async def symbol_switch(best_symbol_by_exchange_aster: structs.Perpetual_Exchang
Config.Overrides.Flatten_Open_Positions_Opportunistic = True
else:
logging.info('Balances Flattened - Updating to Trade New Symbols:')
logging.info(f' ASTER.symbol -> {best_symbol_by_exchange_aster.symbol}')
logging.info(f' EXTEND.symbol -> {best_symbol_by_exchange_extend.symbol}')
logging.info(f' {Aster.symbol} -> {best_symbol_by_exchange_aster.symbol}')
logging.info(f' {Extend.symbol} -> {best_symbol_by_exchange_extend.symbol}')
await aster_cancel_all_orders()
await extend_cancel_all_orders()
@@ -152,7 +152,16 @@ async def symbol_switch(best_symbol_by_exchange_aster: structs.Perpetual_Exchang
Aster = best_symbol_by_exchange_aster
Extend = best_symbol_by_exchange_extend
VAL_KEY.set(name='fr_algo_working_symbol', value=json.dumps(obj={'ASTER': asdict(obj=Aster), 'EXTEND': asdict(obj=Extend)}))
if not Aster:
logging.critical(f'Main Algo, Aster is none: {Aster}')
await kill_algo()
elif not Extend:
logging.critical(f'Main Algo, Extend is none: {Extend}')
await kill_algo()
else:
logging.info(f'setting fr_algo_working_symbol: Aster: {Aster}; Extend: {Extend}')
VAL_KEY.set(name='fr_algo_working_symbol', value=json.dumps(obj={'ASTER': asdict(obj=Aster), 'EXTEND': asdict(obj=Extend)}, cls=utils.JSONEncoder_Decimal))
def calc_fr_minutes_remaining_factor(
min_start_procedure: int = 15,
@@ -436,6 +445,12 @@ async def post_aster_order(
# print_summary(use_logging=True)
else:
logging.critical(f'*** Aster Order Response Abnormal: {order_resp}; post_order: {post_order}')
'''
NEED TO HANDLE THE BELOW RESP
*** Aster Order Response Abnormal: {'code': -5018, 'msg': 'Youve reached the maximum notional value limit for this symbol. You can still reduce or close your position to manage your risk.'}; post_order: {'url': '/fapi/v3/order', 'method': 'POST', 'params': {'symbol': 'ENAUSDT', 'side': 'SELL', 'type': 'LIMIT', 'timeInForce': 'GTX', 'quantity': Decimal('1900'), 'price': Decimal('0.1339100'), 'reduceOnly': False}}
*** Aster Order Response Abnormal: {'code': -4226, 'msg': 'Nonce used'}; post_order: {'url': '/fapi/v3/order', 'method': 'POST', 'params': {'symbol': 'BTCUSDT', 'side': 'SELL', 'type': 'LIMIT', 'timeInForce': 'GTX', 'quantity': Decimal('0.006'), 'price': Decimal('80520.0'), 'reduceOnly': False}}
'''
await kill_algo()
async def cancel_extend_order(order_id: str):
@@ -487,6 +502,7 @@ async def post_extend_order(
elif '1142' in str(e): # 'Error response from https://api.starknet.extended.exchange/api/v1/user/order: code 400 - {"status":"ERROR","error":{"code":1142,"message":"Edit order not found"}};'
logging.info('EXTEND EDIT ORDER, NOT FOUND, CANCELLING and continuing')
await extend_cancel_all_orders()
# if Extend_Open_Orders:
# Extend_Open_Orders.pop(0)
# time.sleep(0.1)
@@ -578,11 +594,11 @@ async def handle_order_updates(exch: str, local_open_orders: list[dict], ws_open
logging.info(f'{exch} ORDER PARTIALLY FILLED: {order_id}')
# await get_aster_collateral()
if exch=='ASTER':
await get_aster_notional_position(resp=ws_pos_updates)
await get_aster_notional_position()
Last_Aster_Fill_Time_Ts = datetime.now().timestamp()*1000
Aster.cancel_request_pending = False
else:
await get_extend_notional(resp=ws_pos_updates)
await get_extend_notional()
Extend.cancel_request_pending = False
utils.send_tg_alert(f'FR_ALGO - {exch} PARTIALLY FILLED ({order_id})')
elif order_update_status in ['FILLED']:
@@ -591,7 +607,7 @@ async def handle_order_updates(exch: str, local_open_orders: list[dict], ws_open
# await get_aster_collateral()
if exch=='ASTER':
# await aster_cancel_all_orders()
await get_aster_notional_position(resp=ws_pos_updates)
await get_aster_notional_position()
Last_Aster_Fill_Time_Ts = datetime.now().timestamp()*1000
Aster.cancel_request_pending = False
else:
@@ -683,7 +699,7 @@ async def get_aster_notional_position(resp: list | None = None):
pos_dict['timestamp_arrival'] = round(datetime.now().timestamp()*1000)
if previous_notional_obj:
if previous_notional_obj['timestamp_arrival'] > pos_dict['timestamp_arrival']:
if previous_notional_obj['timestamp_arrival'] >= pos_dict['timestamp_arrival']:
# logging.info(f'ASTER NOTIONAL: prev timestamp ({pd.to_datetime(previous_notional_obj['timestamp_arrival'], unit='ms')}) > new timestamp ({pd.to_datetime(pos_dict['timestamp_arrival'], unit='ms')}); skipping')
return
@@ -700,12 +716,12 @@ async def get_aster_notional_position(resp: list | None = None):
raise ValueError(e)
if pos_dict.get('notional') is not None:
Aster.notional_position = float(pos_dict['notional']) #- Aster.unrealized_pnl
Aster.notional_position = float(pos_dict['notional']) - Aster.unrealized_pnl
else:
Aster.notional_position = float(pos_dict['position_amount'])*float(pos_dict['entry_price'])
if pos_dict.get('leverage') is not None:
Aster.mult = int(pos_dict['leverage'])
if abs(Aster.notional_position) > Config.Config.Max_Target_Notional*Config.Config.Max_Order_Over_Notional_Ratio:
if abs(Decimal(str(Aster.notional_position))) > Config.Config.Max_Target_Notional*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}; pos_dict: {pos_dict}; resp: {resp}; max_tgt_notional: {Config.Config.Max_Target_Notional}')
await kill_algo()
if Aster.notional_position != previous_notional_position:
@@ -778,7 +794,7 @@ async def get_extend_notional(resp: list | None = None):
Extend.notional_position = notional_pos_sided - float(Extend.unrealized_pnl)
Extend.mult = pos_dict.get('leverage', Extend.mult)
if abs(Extend.notional_position) > Config.Config.Max_Target_Notional*Config.Config.Max_Order_Over_Notional_Ratio:
if abs(Decimal(str(Extend.notional_position))) > Config.Config.Max_Target_Notional*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}; pos_dict: {pos_dict}; resp: {resp}')
await kill_algo()
if Extend.notional_position != previous_notional_position:
@@ -843,7 +859,11 @@ async def aster_cancel_all_orders():
logging.info(f'ASTER CANCEL ALL OPEN ORDERS RESP: {r}')
async def extend_cancel_all_orders():
global Extend_Open_Orders
r = await EXTEND_CLIENT.orders.mass_cancel(markets=[Extend.symbol])
if Extend_Open_Orders:
Extend_Open_Orders.pop(0)
logging.info(f'EXTEND CANCEL ALL OPEN ORDERS RESP: {r}')
### KILL ALGO ###
@@ -851,9 +871,10 @@ async def kill_algo():
await aster_cancel_all_orders()
await extend_cancel_all_orders()
logging.info('ALGO KILL FLAG ACTIVATED; CANCELLING OPEN ORDERS AND SHUTTING DOWN')
await output_algo_status('STOPPED')
await output_algo_status('DEAD')
raise ValueError('KILL FLAG ACTIVATED')
### ALGO LOOP ###
async def run_algo():
global Config
@@ -878,14 +899,36 @@ async def run_algo():
# print('__________Start___________')
### Load Algo Config ###
Config = json.loads(VAL_KEY.get('fr_orchestrator_output')) # ty:ignore[invalid-argument-type]
Config = structs.Algo_Config(**Config)
Config.Config.Max_Target_Notional = float(min([Aster.mult, Extend.mult]) * Config.Config.Target_Open_Cash_Position)
fr_orchestrator_output: str = VAL_KEY.get('fr_orchestrator_output') # ty:ignore[invalid-assignment]
if fr_orchestrator_output:
Config = json.loads(fr_orchestrator_output)
Config = structs.Algo_Config(**Config)
Config.Config.Max_Target_Notional = float(min([Aster.mult, Extend.mult]) * Config.Config.Target_Open_Cash_Position)
else:
logging.critical(f'fr_orchestrator_output is empty: {fr_orchestrator_output}; reloading from disk and continuing.')
with open('algo_config.json', mode='r', encoding='utf-8') as file:
Config = json.load(file)
Config = structs.Algo_Config(**Config)
if not Config.model_dump():
logging.critical(f'fr_orchestrator_output is empty - killing: {fr_orchestrator_output};')
Config.Config.Max_Target_Notional = float(min([Aster.mult, Extend.mult]) * Config.Config.Target_Open_Cash_Position)
VAL_KEY.set(name='fr_orchestrator_output', value=json.dumps(obj=Config.model_dump(), cls=utils.JSONEncoder_Decimal))
min_time_to_funding = Config.Config.Min_Time_To_Funding_Minutes * 60 * 1000
### Load Data from Feedhandlers ###
best_symbol_by_exchange: dict = json.loads(s=VAL_KEY.get(name='fr_engine_best_fund_rate_output')) # ty:ignore[invalid-argument-type]
vk_get: str = VAL_KEY.get(name='fr_engine_best_fund_rate_output') # ty:ignore[invalid-assignment]
if vk_get:
best_symbol_by_exchange: dict = json.loads(vk_get)
else:
logging.critical(f'best_symbol_by_exchange is none: {vk_get}')
await kill_algo()
raise ValueError(f'best_symbol_by_exchange is none: {vk_get}')
# best_symbol_by_exchange: dict = json.loads(s=VAL_KEY.get(name='fr_engine_best_fund_rate_output')) # ty:ignore[invalid-argument-type]
best_symbol_by_exchange_aster = structs.Perpetual_Exchange(**best_symbol_by_exchange['ASTER'])
best_symbol_by_exchange_extend = structs.Perpetual_Exchange(**best_symbol_by_exchange['EXTEND'])
@@ -933,10 +976,20 @@ async def run_algo():
fr_best_exch = 'ASTER' if max([abs(fund_rate_ast), abs(fund_rate_ext)]) == abs(fund_rate_ast) else 'EXTEND'
fr_best_rate = fund_rate_ast if max([abs(fund_rate_ast), abs(fund_rate_ext)]) == abs(fund_rate_ast) else fund_rate_ext
fr_best_side = 'BUY' if fr_best_rate < 0 else 'SELL'
if fr_best_side == 'SELL':
fr_best_exch = 'ASTER' if fr_best_exch == 'EXTEND' else 'EXTEND'
fr_best_side = 'BUY'
return net_fr, fr_best_exch, fr_best_side
else:
fr_best_exch = 'EXTEND'
fr_best_side = 'BUY' if fund_rate_ext < 0 else 'SELL'
if fr_best_side == 'SELL':
fr_best_exch = 'ASTER'
fr_best_side = 'BUY'
return fund_rate_ext, fr_best_exch, fr_best_side
next_net_funding_rate, fr_best_exch, fr_best_side = calc_next_net_fund_rate(next_funding_at_same_time, fund_rate_ast=aster_fund_rate, fund_rate_ext=extend_fund_rate)
@@ -947,7 +1000,16 @@ async def run_algo():
aster_ticker_dict: dict = json.loads(s=aster_ticker_dict) if aster_ticker_dict is not None else {}
if ( aster_ticker_dict.get('symbol', None) != Aster.symbol ) and not(Config.Overrides.Flatten_Open_Positions):
logging.warning(f'ASTER Symbol mismatch: {aster_ticker_dict}; expected symbol: {Aster.symbol}')
VAL_KEY.set(name='fr_algo_working_symbol', value=json.dumps(obj={'ASTER': asdict(obj=Aster), 'EXTEND': asdict(obj=Extend)}))
if not Aster:
logging.critical(f'Main Algo, Aster is none: {Aster}')
await kill_algo()
elif not Extend:
logging.critical(f'Main Algo, Extend is none: {Extend}')
await kill_algo()
else:
logging.info(f'setting fr_algo_working_symbol: Aster: {Aster}; Extend: {Extend}')
VAL_KEY.set(name='fr_algo_working_symbol', value=json.dumps(obj={'ASTER': asdict(obj=Aster), 'EXTEND': asdict(obj=Extend)}, cls=utils.JSONEncoder_Decimal))
time.sleep(5)
continue
# raise ValueError(f'ASTER Symbol mismatch: {ASTER_TICKER_DICT}; expected symbol: {ASTER.symbol}')
@@ -956,7 +1018,16 @@ async def run_algo():
extend_ticker_dict: dict = json.loads(s=extend_ticker_dict) if extend_ticker_dict is not None else {}
if ( extend_ticker_dict.get('symbol', None) != Extend.symbol) and not(Config.Overrides.Flatten_Open_Positions):
logging.warning(f'EXTEND Symbol mismatch: {extend_ticker_dict}; expected symbol: {Extend.symbol}')
VAL_KEY.set(name='fr_algo_working_symbol', value=json.dumps(obj={'ASTER': asdict(obj=Aster), 'EXTEND': asdict(obj=Extend)}))
if not Aster:
logging.critical(f'Main Algo, Aster is none: {Aster}')
await kill_algo()
elif not Extend:
logging.critical(f'Main Algo, Extend is none: {Extend}')
await kill_algo()
else:
logging.info(f'setting fr_algo_working_symbol: Aster: {Aster}; Extend: {Extend}')
VAL_KEY.set(name='fr_algo_working_symbol', value=json.dumps(obj={'ASTER': asdict(obj=Aster), 'EXTEND': asdict(obj=Extend)}, cls=utils.JSONEncoder_Decimal))
time.sleep(5)
continue
# raise ValueError(f'EXTEND Symbol mismatch: {EXTENDED_TICKER_DICT}; expected symbol: {EXTEND.symbol}')
@@ -1029,15 +1100,15 @@ async def run_algo():
if Config.Overrides.Flatten_Open_Positions:
aster_tgt = Decimal('0.00')
elif Config.Overrides.Flatten_Open_Positions_Opportunistic:
elif Config.Overrides.Flatten_Open_Positions_Opportunistic or (signal.exchange != fr_best_exch):
if signal.signal:
if signal.exchange == 'EXTEND' and Decimal(str(Aster.notional_position)) > 0:
if signal.exchange == 'EXTEND' and Decimal(str(Aster.notional_position)) >= 0:
aster_tgt = Decimal('0.00')
if signal.exchange == 'EXTEND' and Decimal(str(Aster.notional_position)) < 0:
pass
if signal.exchange == 'ASTER' and Decimal(str(Aster.notional_position)) > 0:
pass
if signal.exchange == 'ASTER' and Decimal(str(Aster.notional_position)) < 0:
if signal.exchange == 'ASTER' and Decimal(str(Aster.notional_position)) <= 0:
aster_tgt = Decimal('0.00')
aster_target = Target(
@@ -1062,7 +1133,7 @@ async def run_algo():
MKT : Aster: {Aster.symbol:<10} (best: {best_symbol_by_exchange_aster.symbol}) | Extend: {Extend.symbol:<10} (best: {best_symbol_by_exchange_extend.symbol})
FR SWITCH : {funding_rate_switch_net:.6%} [{funding_rate_switch_net*10_000:.2f}bps] [{funding_rate_switch_net*1_000_000:.0f}pips]
{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') - pd.to_datetime(datetime.now().timestamp()*1000, unit='ms')) }) | {pd.to_datetime(extend_fund_rate_time, unit='ms')} ({(pd.to_datetime(extend_fund_rate_time, unit='ms')-pd.to_datetime(datetime.now().timestamp()*1000, unit='ms'))})
ASTER: {aster_fund_rate:.6%} [{aster_fund_rate*10_000:.2f}bps] [{aster_fund_rate*1_000_000:.0f}pips] | EXTEND: {extend_fund_rate:.6%} [{extend_fund_rate*10_000:.2f}bps] [{extend_fund_rate*1_000_000:.0f}pips]
ASTER: {'LONG PAYS SHORT' if aster_fund_rate > 0 else 'SHORT PAYS LONG'} | EXTEND: {'LONG PAYS SHORT' if extend_fund_rate > 0 else 'SHORT PAYS LONG'}
ASTER: [ Notional Position $ : {Aster.notional_position:.4f} ] | EXTEND: [ Notional Position $ : {Extend.notional_position:.4f} ]
@@ -1136,7 +1207,7 @@ async def run_algo():
open_order_dict = dict(Aster_Open_Orders[0])
open_order_id = str(open_order_dict['order_id'])
try:
open_order_px = float(open_order_dict['price'])
open_order_px = float(open_order_dict['price']) if open_order_dict.get('price') is not None else float(open_order_dict['last_filled_price'])
except Exception as e:
logging.critical(f'Aster cant find price on order obj: {open_order_dict}; e: {e}')
await kill_algo()
@@ -1226,7 +1297,7 @@ async def run_algo():
)
### Output Algo Status ###
await output_algo_status('WORKING')
await output_algo_status('HEALTHY')
### CHECK TIME TO FUNDING AND WHETHER TO BE ACTIVE ###
if ( time_to_funding_ms > min_time_to_funding ) and (not Aster_Open_Orders) and (not Extend_Open_Orders):
@@ -1246,13 +1317,13 @@ async def run_algo():
time.sleep(Config.Config.Loop_Sleep_Sec)
except KeyboardInterrupt:
logging.info('CANCELLING OPEN ORDERS')
await output_algo_status('STOPPING')
await output_algo_status('UNHEALTHY')
await kill_algo()
except Exception as e:
logging.error(traceback.format_exc())
logging.critical(f'*** ALGO ENGINE CRASHED: {e}')
logging.info('CANCELLING OPEN ORDERS')
await output_algo_status('STOPPING')
await output_algo_status('UNHEALTHY')
utils.send_tg_alert(f'FR_ALGO_CRASHED: {str(e)}')
await kill_algo()
@@ -1275,14 +1346,23 @@ async def main():
await set_comb_open_symbols()
# Open_Symbols = ['HYPE-USD']
best_symbol_by_exchange: dict = json.loads(s=VAL_KEY.get(name='fr_engine_best_fund_rate_output')) # ty:ignore[invalid-argument-type]
vk_get: str = VAL_KEY.get(name='fr_engine_best_fund_rate_output') # ty:ignore[invalid-assignment]
if vk_get:
best_symbol_by_exchange: dict = json.loads(vk_get)
else:
best_symbol_by_exchange = None
raise ValueError('best_symbol_by_exchange is none')
if Open_Symbols:
if Open_Symbols or not(best_symbol_by_exchange):
logging.info(f'OPEN SYMBOLS: {Open_Symbols}')
master_data = json.loads(s=VAL_KEY.get(name='fr_engine_best_fund_rate_master')) # ty:ignore[invalid-argument-type]
open_symbol_to_work = Open_Symbols[0]
current_pos_master_ast = [d for d in master_data if d.get('symbol_ext') == open_symbol_to_work][0]
if not current_pos_master_ast:
logging.critical(f'Open symbol not found in master data, killing algo: symbol {open_symbol_to_work}; md: {current_pos_master_ast}')
await kill_algo()
Aster, Extend = create_exchange_objs_from_dict(exchanges_dict=current_pos_master_ast)
Open_Symbols.pop(0)
@@ -1297,36 +1377,45 @@ async def main():
Config = json.load(file)
Config = structs.Algo_Config(**Config)
Config.Config.Max_Target_Notional = float(min([Aster.mult, Extend.mult]) * Config.Config.Target_Open_Cash_Position)
# logging.info(f'Initial Algo Config: {ALGO_CONFIG}')
VAL_KEY.set(name='fr_orchestrator_output', value=json.dumps(obj=Config.model_dump()))
VAL_KEY.set(name='fr_algo_working_symbol', value=json.dumps(obj={'ASTER': asdict(obj=Aster), 'EXTEND': asdict(obj=Extend)}))
if not Config.model_dump():
raise ValueError(f'Main Algo, initial config is none: {Config}')
elif not Aster:
raise ValueError(f'Main Algo, Aster is none: {Aster}')
elif not Extend:
raise ValueError(f'Main Algo, Extend is none: {Extend}')
else:
Config.Config.Max_Target_Notional = float(min([Aster.mult, Extend.mult]) * Config.Config.Target_Open_Cash_Position)
VAL_KEY.set(name='fr_orchestrator_output', value=json.dumps(obj=Config.model_dump(), cls=utils.JSONEncoder_Decimal))
logging.info(f'setting fr_algo_working_symbol: Aster: {Aster}; Extend: {Extend}')
VAL_KEY.set(name='fr_algo_working_symbol', value=json.dumps(obj={'ASTER': asdict(obj=Aster), 'EXTEND': asdict(obj=Extend)}, cls=utils.JSONEncoder_Decimal))
Funding_Rates_Min_Remaining_Factor_Pcts = calc_fr_minutes_remaining_factor()
Algo_Status = structs.Algo_Status(
last_update_ts_ms = int(round(datetime.now().timestamp()*1000, 2)),
status = 'WORKING',
expected_alpha = 0.00,
model_ratio = 0.00,
current_ratio = 0.00,
)
await output_algo_status('WORKING')
Funding_Rates_Min_Remaining_Factor_Pcts = calc_fr_minutes_remaining_factor()
Algo_Status = structs.Algo_Status(
last_update_ts_ms = int(round(datetime.now().timestamp()*1000, 2)),
status = 'HEALTHY',
expected_alpha = 0.00,
model_ratio = 0.00,
current_ratio = 0.00,
)
await output_algo_status('HEALTHY')
async with engine.connect() as CON:
### ASTER SETUP ###
# await get_aster_collateral()
await get_aster_notional_position()
await get_aster_exch_info()
await get_aster_open_orders()
### EXTEND SETUP ###
# await get_extend_collateral()
await get_extend_notional()
await get_extend_exch_info()
await get_extend_open_orders()
async with engine.connect() as CON:
### ASTER SETUP ###
# await get_aster_collateral()
await get_aster_notional_position()
await get_aster_exch_info()
await get_aster_open_orders()
### EXTEND SETUP ###
# await get_extend_collateral()
await get_extend_notional()
await get_extend_exch_info()
await get_extend_open_orders()
await run_algo()
await run_algo()
if __name__ == '__main__':
START_TIME = round(datetime.now().timestamp()*1000)

View File

@@ -4,13 +4,15 @@ from typing import Any
import valkey
from pydantic import BaseModel
from datetime import datetime
from sqlalchemy.util.typing import Self
from collections.abc import Sequence, Callable
import modules.utils as utils
def ret_true():
return True
class Locked_Value(Sequence):
def __init__(self, initial_value: Any = None, unlock_func: Callable=ret_true):
self._value: Any = initial_value
@@ -88,16 +90,218 @@ class Current_Previous_Value:
self._previous_value = self._value
self._value = v
### Valkey Objects ###
class VK_Check(BaseModel):
status: str = 'HEALTHY' # HEALTHY | UNHEALTHY | DEAD
method: str | None
class VK_Checks(BaseModel):
status: VK_Check = VK_Check(method='check_status')
timestamp: VK_Check = VK_Check(method='check_timestamp')
symbol: VK_Check = VK_Check(method='check_symbol')
async def run_checks(self, args: dict | None = None):
for f in self.model_dump():
method = getattr(getattr(self, f), 'method')
if method is not None:
await getattr(self, method)(args = args)
async def check_status(self, args: dict | None = None) -> None:
# print('checking status')
if self.status.status == '':
self.status.status = 'UNHEALTHY'
else:
self.status.status = self.status.status
async def check_status_nested(self, args: dict | None = None) -> None:
# print('checking status')
if self.status.status == '':
self.status.status = 'UNHEALTHY'
else:
self.status.status = self.status.status
async def check_timestamp(self, args: dict | None = None) -> None:
# print('checking timestamp')
if args is not None:
ts = int(args.get('timestamp', 0))
now = round(datetime.now().timestamp()*1000)
if (now - ts) > 1:
self.timestamp.status = 'UNHEALTHY'
else:
self.timestamp.status = 'HEALTHY'
else:
raise ValueError("Must pass in 'timestamp' arg")
async def check_symbol(self, args: dict | None = None) -> None:
# print('checking symbol')
if args is not None:
symbol = utils.symbol_to_extend_fmt(args.get('algo_symbol', ''))
vk_symbol = utils.symbol_to_extend_fmt(args.get('vk_symbol', ''))
if symbol == vk_symbol:
self.symbol.status = 'HEALTHY'
else:
self.symbol.status = 'UNHEALTHY'
else:
raise ValueError("Must pass in 'algo_symbol' and 'vk_symbol' args")
class VK_Obj(BaseModel):
vk_name: str
timestamp: int = round(datetime.now().timestamp()*1000)
status: str = 'HEALTHY' # HEALTHY | UNHEALTHY | DEAD
checks: VK_Checks = VK_Checks()
data: Any = None
async def get(self, VK_CON: valkey.Valkey) -> None:
print('getting')
vk_get: str = VK_CON.get(self.vk_name) # ty:ignore[invalid-assignment]
vk_dict: dict = json.loads(vk_get)
self.__init__(**vk_dict)
async def set(self, VK_CON: valkey.Valkey, data_override: Any = None) -> None:
print('setting')
if data_override is not None:
self.data = data_override
self.timestamp: int = round(datetime.now().timestamp()*1000)
j: str = self.model_dump_json()
VK_CON.set(self.vk_name, j)
### Valkey Archetypes ###
# Engine - Health
# Engine - Orchestrator
class VK_Orchestrator_Output(VK_Obj):
vk_name: str = 'fr_orchestrator_output'
checks: VK_Checks = VK_Checks(
symbol = VK_Check(method = None)
)
# Algo
class VK_Working_Symbol(VK_Obj):
vk_name: str = 'fr_algo_working_symbol'
checks: VK_Checks = VK_Checks(
symbol = VK_Check(method = None)
)
class VK_Algo_Status(VK_Obj):
vk_name: str = 'algo_status'
checks: VK_Checks = VK_Checks(
symbol = VK_Check(method = None)
)
# User - Orders
class VK_User_Orders_Aster(VK_Obj):
vk_name: str = 'fr_aster_user_orders'
checks: VK_Checks = VK_Checks(
symbol = VK_Check(method = None)
)
class VK_User_Orders_Extend(VK_Obj):
vk_name: str = 'fr_extended_user_orders'
checks: VK_Checks = VK_Checks(
symbol = VK_Check(method = None)
)
# User - Trades
class VK_User_Trades_Extend(VK_Obj):
vk_name: str = 'fr_extended_user_trades'
checks: VK_Checks = VK_Checks(
symbol = VK_Check(method = None)
)
# User - Balances
class VK_User_Balances_Aster(VK_Obj):
vk_name: str = 'fr_aster_user_balances'
checks: VK_Checks = VK_Checks(
symbol = VK_Check(method = None)
)
class VK_User_Balances_Extend(VK_Obj):
vk_name: str = 'fr_extended_user_balances'
checks: VK_Checks = VK_Checks(
symbol = VK_Check(method = None)
)
# User - Positions
class VK_User_Positions_Aster(VK_Obj):
vk_name: str = 'fr_aster_user_positions'
checks: VK_Checks = VK_Checks(
symbol = VK_Check(method = None)
)
class VK_User_Positions_Extend(VK_Obj):
vk_name: str = 'fr_extended_user_positions'
checks: VK_Checks = VK_Checks(
symbol = VK_Check(method = None)
)
# Fund Rates
class VK_FR_Aster(VK_Obj):
vk_name: str = 'fund_rate_aster'
checks: VK_Checks = VK_Checks(
symbol = VK_Check(method = None)
)
class VK_FR_All_Aster(VK_Obj):
vk_name: str = 'fund_rate_aster_all'
checks: VK_Checks = VK_Checks(
symbol = VK_Check(method = None)
)
class VK_FR_Extend(VK_Obj):
vk_name: str = 'fund_rate_extended'
checks: VK_Checks = VK_Checks(
symbol = VK_Check(method = None)
)
class VK_FR_All_Extend(VK_Obj):
vk_name: str = 'fund_rate_extended_all'
checks: VK_Checks = VK_Checks(
symbol = VK_Check(method = None)
)
# Tickers
class VK_Ticker_Aster(VK_Obj):
vk_name: str = 'fut_ticker_aster'
checks: VK_Checks = VK_Checks(
symbol = VK_Check(method = None)
)
class VK_Ticker_Extend(VK_Obj):
vk_name: str = 'fut_ticker_extended'
checks: VK_Checks = VK_Checks(
symbol = VK_Check(method = None)
)
# Trades
class VK_Trade_Aster(VK_Obj):
vk_name: str = 'fut_last_trade_aster'
checks: VK_Checks = VK_Checks(
symbol = VK_Check(method = None)
)
class VK_Trade_Extend(VK_Obj):
vk_name: str = 'fut_last_trade_extended'
checks: VK_Checks = VK_Checks(
symbol = VK_Check(method = None)
)
### Algo Objects ###
class Algo_Status(BaseModel):
last_update_ts_ms: int
status: str # 'WORKING' | 'STOPPED'
status: str # 'WORKING' | 'STOPPED' ///// # ENUM: 'HEALTHY' | 'UNHEALTHY' | 'DEAD'
expected_alpha: float
model_ratio: float
current_ratio: float
# @dataclass(kw_only=True)
class Algo_Config_Overrides(BaseModel):
Allow_Ordering_Aster: bool
Allow_Ordering_Extend: bool
@@ -107,7 +311,6 @@ class Algo_Config_Overrides(BaseModel):
Flip_Side_For_Testing: bool
# @dataclass(kw_only=True)
class Algo_Config_Config(BaseModel):
Loop_Sleep_Sec: int
Max_Order_Over_Notional_Ratio: float
@@ -119,12 +322,12 @@ class Algo_Config_Config(BaseModel):
Switch_To_Taker_Seconds: int
Target_Open_Cash_Position: int
# @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

View File

@@ -2,9 +2,18 @@ import logging
from dotenv import load_dotenv
import requests
import os
import json
from decimal import Decimal
load_dotenv()
class JSONEncoder_Decimal(json.JSONEncoder):
def default(self, o):
if isinstance(o, Decimal):
return float(o)
return super(JSONEncoder_Decimal, self).default(o)
def upsert_list_of_dicts_by_id(list_of_dicts, new_dict, id='id', seq_check_field: str | None = None, reset_seq_id: bool = False) -> list[dict]:
for index, item in enumerate(list_of_dicts):
if item.get(id) == new_dict.get(id):

6
ng.py
View File

@@ -132,6 +132,8 @@ async def get_trades_hist() -> pd.DataFrame:
WHERE timestamp_arrival > {start_ts}
''')
df_aster_orders = pd.read_sql(aster_orders, con=ENGINE)
if len(df_aster_orders) < 1:
return pd.DataFrame()
df_aster_orders['timestamp_dt'] = pd.to_datetime(df_aster_orders['timestamp_transaction'], unit='ms')
df_aster_orders_fill = df_aster_orders.loc[df_aster_orders['execution_type']=='TRADE',:]
df_aster_orders_fill = df_aster_orders_fill[['timestamp_transaction','order_trade_time_ts','timestamp_dt','order_id','trade_id','client_order_id','order_status','side','last_filled_qty','filled_accumulated_qty','commission','last_filled_price','realized_profit']].reset_index(drop=True)
@@ -150,6 +152,8 @@ async def get_trades_hist() -> pd.DataFrame:
WHERE timestamp_arrival > {start_ts}
''')
df_extend_orders = pd.read_sql(extend_orders, con=ENGINE)
if len(df_extend_orders) < 1:
return pd.DataFrame()
df_extend_orders['timestamp_dt'] = pd.to_datetime(df_extend_orders['updated_time_ts'], unit='ms')
df_extend_orders_fill = df_extend_orders.loc[df_extend_orders['status'].isin(['FILLED','PARTIALLY_FILLED']),:]
df_extend_orders_fill = df_extend_orders_fill[['created_time_ts','updated_time_ts','timestamp_dt','order_id','external_id','status','side','qty','filled_qty','payed_fee','price','averagePrice']].reset_index(drop=True)
@@ -351,4 +355,4 @@ async def root():
}).classes('w-full')
ui.run(root, storage_secret="123ABC", reload=True, dark=True, title='Atwater Trading', port=9090)
ui.run(root, storage_secret="123ABC", reload=True, dark=True, title='Atwater Trading', port=8060)

1365
pnl.ipynb

File diff suppressed because it is too large Load Diff

View File

@@ -23,4 +23,6 @@ nicegui
x10-python-trading-starknet
eth-keys
eth-account
pydantic
pydantic
plotly
docker

View File

@@ -17,6 +17,7 @@ import os
from dotenv import load_dotenv
import modules.db as db
import modules.aster_db as aster_db
import sys
### Allow only ipv4 ###
def allowed_gai_family():
@@ -37,9 +38,10 @@ load_dotenv()
LOG_FILEPATH: str = f'{os.getenv(key="LOGS_PATH")}/Fund_Rate_Aster.log'
### CONSTANTS ###
SYMBOL: str = 'ETHUSDT'
SYMBOL: str = 'ENAUSDT'
STREAM_MARKPRICE: str = f'{SYMBOL.lower()}@markPrice@1s'
STREAM_MARKPRICE: str = '!markPrice@arr@1s'
# STREAM_MARKPRICE: str = f'{SYMBOL.lower()}@markPrice@1s'
STREAM_BOOKTICKER: str = f'{SYMBOL.lower()}@bookTicker'
STREAM_TRADES: str = f'{SYMBOL.lower()}@aggTrade'
@@ -83,17 +85,23 @@ async def ws_stream():
async for message in websocket:
### Update Symbol if Algo Outputs Change ###
if ALLOW_SYMBOL_CHG:
best_symbol_by_exchange: dict = json.loads(s=VAL_KEY.get(name='fr_algo_working_symbol')) # ty:ignore[invalid-argument-type]
fr_algo_working_symbol = VAL_KEY.get(name='fr_algo_working_symbol')
if not fr_algo_working_symbol:
logging.critical(f'fr_algo_working_symbol is empty - killing: {fr_algo_working_symbol}')
sys.exit(1)
best_symbol_by_exchange: dict = json.loads(fr_algo_working_symbol) # ty:ignore[invalid-argument-type]
best_symbol: str = best_symbol_by_exchange['ASTER']['symbol']
if best_symbol != SYMBOL:
logging.info(f'Symbol Change: {SYMBOL} -> {best_symbol}')
SYMBOL = best_symbol
await unsubscribe_streams(websocket = websocket, streams=[STREAM_MARKPRICE,STREAM_BOOKTICKER,STREAM_TRADES])
await unsubscribe_streams(websocket = websocket, streams=[STREAM_BOOKTICKER,STREAM_TRADES])
STREAM_MARKPRICE = f'{SYMBOL.lower()}@markPrice@1s'
# STREAM_MARKPRICE = f'{SYMBOL.lower()}@markPrice@1s'
STREAM_BOOKTICKER = f'{SYMBOL.lower()}@bookTicker'
STREAM_TRADES = f'{SYMBOL.lower()}@aggTrade'
await subscribe_streams(websocket = websocket, streams=[STREAM_MARKPRICE,STREAM_BOOKTICKER,STREAM_TRADES])
await subscribe_streams(websocket = websocket, streams=[STREAM_BOOKTICKER,STREAM_TRADES])
continue
ts_arrival = round(datetime.now().timestamp()*1000)
@@ -104,18 +112,25 @@ async def ws_stream():
if channel is not None:
match channel:
case c if c == STREAM_MARKPRICE:
# print(f'MP: {data}')
VAL_KEY_OBJ = json.dumps({
'timestamp_arrival': ts_arrival,
'timestamp_msg': data['data']['E'],
'symbol': data['data']['s'],
'mark_price': data['data']['p'],
'index_price': data['data']['i'],
'estimated_settle_price': data['data']['P'],
'funding_rate': data['data']['r'],
'next_funding_time_ts_ms': data['data']['T'],
})
VAL_KEY.set(VK_FUND_RATE, VAL_KEY_OBJ)
if data.get('data'):
VAL_KEY.set('fund_rate_aster_all', json.dumps(data['data']))
else:
logging.warning(f'Data["data"] is None: {data}')
single_ticker_fr = [d for d in data['data'] if d.get('s')==SYMBOL]
if single_ticker_fr:
d = single_ticker_fr[0]
VAL_KEY_OBJ = json.dumps({
'timestamp_arrival': ts_arrival,
'timestamp_msg': d['E'],
'symbol': d['s'],
'mark_price': d['p'],
'index_price': d['i'],
'estimated_settle_price': d['P'],
'funding_rate': d['r'],
'next_funding_time_ts_ms': d['T'],
})
VAL_KEY.set(VK_FUND_RATE, VAL_KEY_OBJ)
# print(f'MP: {d}')
continue
case c if c == STREAM_BOOKTICKER:
# print(f'BT: {data}')

View File

@@ -16,6 +16,8 @@ from sqlalchemy.ext.asyncio import create_async_engine
import valkey
import os
from dotenv import load_dotenv
import sys
import modules.utils as utils
### Allow only ipv4 ###
@@ -36,10 +38,11 @@ load_dotenv()
LOG_FILEPATH: str = f'{os.getenv("LOGS_PATH")}/Fund_Rate_Extended_FR.log'
### CONSTANTS ###
SYMBOL: str = 'ETH-USD'
SYMBOL: str = 'ENA-USD'
### Globals ###
ALLOW_SYMBOL_CHG: bool = True
LOCAL_FUNDING_RATES = []
def time_round_down(dt, interval_mins=5) -> int: # returns timestamp in seconds
interval_secs = interval_mins * 60
@@ -51,35 +54,40 @@ def time_round_down(dt, interval_mins=5) -> int: # returns timestamp in seconds
### Websocket ###
async def ws_stream():
global SYMBOL
global LOCAL_FUNDING_RATES
while True:
CHANGE_SYMBOL = False
WSS_URL = f"wss://api.starknet.extended.exchange/stream.extended.exchange/v1/funding/{SYMBOL}"
# CHANGE_SYMBOL = False
WSS_URL = "wss://api.starknet.extended.exchange/stream.extended.exchange/v1/funding/"
async for websocket in websockets.connect(WSS_URL):
if CHANGE_SYMBOL:
break
# if CHANGE_SYMBOL:
# break
logging.info(f"Connected to {WSS_URL}")
try:
async for message in websocket:
### Update Symbol if Algo Outputs Change ###
if ALLOW_SYMBOL_CHG:
best_symbol_by_exchange: dict = json.loads(s=VAL_KEY.get(name='fr_algo_working_symbol')) # ty:ignore[invalid-argument-type]
fr_algo_working_symbol = VAL_KEY.get(name='fr_algo_working_symbol')
if not fr_algo_working_symbol:
logging.critical(f'fr_algo_working_symbol is empty - killing: {fr_algo_working_symbol}')
sys.exit(1)
best_symbol_by_exchange: dict = json.loads(fr_algo_working_symbol) # ty:ignore[invalid-argument-type]
best_symbol: str = best_symbol_by_exchange['EXTEND']['symbol']
if best_symbol != SYMBOL:
logging.info(f'Symbol Change: {SYMBOL} -> {best_symbol}')
SYMBOL = best_symbol
CHANGE_SYMBOL = True
await websocket.close()
break
# CHANGE_SYMBOL = True
# await websocket.close()
# break
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}')
print(f'FR: {data}')
fr_next_update_ts = (time_round_down(dt=datetime.now(timezone.utc), interval_mins=60)+(60*60))*1000
VAL_KEY_OBJ = json.dumps({
fr_update = {
'sequence_id': data['seq'],
'timestamp_arrival': ts_arrival,
'timestamp_msg': data['ts'],
@@ -87,8 +95,12 @@ async def ws_stream():
'funding_rate': float(data['data']['f']),
'funding_rate_updated_ts_ms': data['data']['T'],
'next_funding_time_ts_ms': fr_next_update_ts,
})
VAL_KEY.set(VK_FUND_RATE, VAL_KEY_OBJ)
}
if fr_update.get('symbol') == SYMBOL:
VAL_KEY_OBJ = json.dumps(fr_update)
VAL_KEY.set(VK_FUND_RATE, VAL_KEY_OBJ)
LOCAL_FUNDING_RATES = utils.upsert_list_of_dicts_by_id(LOCAL_FUNDING_RATES, fr_update, id='symbol', seq_check_field=None)
VAL_KEY.set('fund_rate_extended_all', json.dumps(LOCAL_FUNDING_RATES))
continue
else:
logging.info(f'Initial or unexpected data struct, skipping: {data}')

View File

@@ -55,14 +55,18 @@ async def ws_stream():
async for message in websocket:
### Update Symbol if Algo Outputs Change ###
if ALLOW_SYMBOL_CHG:
best_symbol_by_exchange: dict = json.loads(s=VAL_KEY.get(name='fr_algo_working_symbol')) # ty:ignore[invalid-argument-type]
best_symbol: str = best_symbol_by_exchange['EXTEND']['symbol']
if best_symbol != SYMBOL:
logging.info(f'Symbol Change: {SYMBOL} -> {best_symbol}')
SYMBOL = best_symbol
CHANGE_SYMBOL = True
await websocket.close()
break
vk_get: str = VAL_KEY.get(name='fr_algo_working_symbol') # ty:ignore[invalid-assignment]
if vk_get:
best_symbol_by_exchange: dict = json.loads(s=vk_get)
best_symbol: str = best_symbol_by_exchange['EXTEND']['symbol']
if best_symbol != SYMBOL:
logging.info(f'Symbol Change: {SYMBOL} -> {best_symbol}')
SYMBOL = best_symbol
CHANGE_SYMBOL = True
await websocket.close()
break
else:
logging.warning('Extend Orderbook WS: "fr_algo_working_symbol" is None; not switching to new symbol...')
ts_arrival = round(datetime.now().timestamp()*1000)
if isinstance(message, str):

View File

@@ -58,14 +58,18 @@ async def ws_stream():
async for message in websocket:
### Update Symbol if Algo Outputs Change ###
if ALLOW_SYMBOL_CHG:
best_symbol_by_exchange: dict = json.loads(s=VAL_KEY.get(name='fr_algo_working_symbol')) # ty:ignore[invalid-argument-type]
best_symbol: str = best_symbol_by_exchange['EXTEND']['symbol']
if best_symbol != SYMBOL:
logging.info(f'Symbol Change: {SYMBOL} -> {best_symbol}')
SYMBOL = best_symbol
CHANGE_SYMBOL = True
await websocket.close()
break
vk_get: str = VAL_KEY.get(name='fr_algo_working_symbol') # ty:ignore[invalid-assignment]
if vk_get:
best_symbol_by_exchange: dict = json.loads(s=vk_get)
best_symbol: str = best_symbol_by_exchange['EXTEND']['symbol']
if best_symbol != SYMBOL:
logging.info(f'Symbol Change: {SYMBOL} -> {best_symbol}')
SYMBOL = best_symbol
CHANGE_SYMBOL = True
await websocket.close()
break
else:
logging.warning('Extend Trades WS: "fr_algo_working_symbol" is None; not switching to new symbol...')
ts_arrival = round(datetime.now().timestamp()*1000)
if isinstance(message, str):