bug fixes
This commit is contained in:
13
_On_Ice/algo_config_backup.json
Normal file
13
_On_Ice/algo_config_backup.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"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
|
||||
}
|
||||
194
algo.ipynb
194
algo.ipynb
@@ -2,7 +2,7 @@
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"execution_count": 76,
|
||||
"id": "d1eed397",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -18,7 +18,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"execution_count": 77,
|
||||
"id": "c6151613",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -28,7 +28,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 31,
|
||||
"execution_count": null,
|
||||
"id": "d83c61e5",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -38,87 +38,50 @@
|
||||
"1"
|
||||
]
|
||||
},
|
||||
"execution_count": 31,
|
||||
"execution_count": 131,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"config_update = {\n",
|
||||
" 'Min_Time_To_Funding_Minutes': 60,\n",
|
||||
" 'Print_Summary_Each_Loop': True,\n",
|
||||
" 'Allow_Ordering_Aster': True,\n",
|
||||
" 'Allow_Ordering_Extend': True,\n",
|
||||
" 'Loop_Sleep_Sec': 0.0,\n",
|
||||
" 'Flip_Side_For_Testing': False,\n",
|
||||
" 'Price_Worsener_Extend': 0.0,\n",
|
||||
" 'Min_Time_To_Funding_Minutes': 7,\n",
|
||||
" # 'Allow_Ordering_Aster': True,\n",
|
||||
" # 'Allow_Ordering_Extend': True,\n",
|
||||
" 'Loop_Sleep_Sec': 0.00,\n",
|
||||
"# 'Flip_Side_For_Testing': False,\n",
|
||||
"# 'Price_Worsener_Extend': 0.0,\n",
|
||||
" 'Log_Summary_Each_Loop': False,\n",
|
||||
" 'Print_Summary_Each_Loop': False,\n",
|
||||
"}\n",
|
||||
"VAL_KEY.publish('fr_orchestrator_input', json.dumps(config_update))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "d2fdd7d2",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 11,
|
||||
"execution_count": 93,
|
||||
"id": "45fae761",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"Algo_Config(Config_Updated_Timestamp=1777151524162, Allow_Ordering_Aster=True, Allow_Ordering_Extend=True, Loop_Sleep_Sec=1, Max_Target_Notional=0.0, Min_Time_To_Funding_Minutes=60, Price_Worsener_Aster=0.0, Price_Worsener_Extend=0.0, Target_Open_Cash_Position=10)"
|
||||
"5.0"
|
||||
]
|
||||
},
|
||||
"execution_count": 11,
|
||||
"execution_count": 93,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"json.loads(VAL_KEY.get('fr_orchestrator_output'), object_hook=lambda d: structs.Algo_Config(**d))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'Config_Updated_Timestamp': 1777098091913,\n",
|
||||
" 'Allow_Ordering_Aster': False,\n",
|
||||
" 'Allow_Ordering_Extend': False,\n",
|
||||
" 'Loop_Sleep_Sec': 1,\n",
|
||||
" 'Max_Target_Notional': 0.0,\n",
|
||||
" 'Min_Time_To_Funding_Minutes': 60,\n",
|
||||
" 'Price_Worsener_Aster': 0.0,\n",
|
||||
" 'Price_Worsener_Extend': 0.0,\n",
|
||||
" 'Target_Open_Cash_Position': 10,\n",
|
||||
" 'Print_Summary_Each_Loop': False,\n",
|
||||
" 'Flip_Side_For_Testing': False}"
|
||||
]
|
||||
},
|
||||
"execution_count": 9,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"asdict(ALGO_CONFIG)"
|
||||
"json.loads(VAL_KEY.get('fr_orchestrator_output'), object_hook=lambda d: structs.Algo_Config(**d)).Loop_Sleep_Sec"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "d2e26271",
|
||||
"id": "98c500cc",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
@@ -126,9 +89,130 @@
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "f2cf3325",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 31,
|
||||
"id": "a0df43de",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"pos = json.loads(VAL_KEY.get('fr_aster_user_positions'))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 32,
|
||||
"id": "ca526c8a",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"[{'timestamp_arrival': 1777303258987,\n",
|
||||
" 'timestamp_msg': 1777303258979,\n",
|
||||
" 'timestamp_transaction': 1777303258950,\n",
|
||||
" 'event_reason_type': 'ORDER',\n",
|
||||
" 'symbol': 'ETHUSDT',\n",
|
||||
" 'position_amount': 0.226,\n",
|
||||
" 'entry_price': 2284.28,\n",
|
||||
" 'accumulated_realized_pre_fees': 8.24392002,\n",
|
||||
" 'unrealized_pnl': 0.0,\n",
|
||||
" 'margin_type': 'cross',\n",
|
||||
" 'isolated_wallet': 0.0,\n",
|
||||
" 'position_side': 'BOTH'}]"
|
||||
]
|
||||
},
|
||||
"execution_count": 32,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"pos"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 33,
|
||||
"id": "f788b6df",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"Timestamp('2026-04-27 15:20:58.987000')"
|
||||
]
|
||||
},
|
||||
"execution_count": 33,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"import pandas as pd\n",
|
||||
"pd.to_datetime(1777303258987, unit='ms')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 34,
|
||||
"id": "855f980b",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"Timestamp('2026-04-27 15:20:58.979000')"
|
||||
]
|
||||
},
|
||||
"execution_count": 34,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"import pandas as pd\n",
|
||||
"pd.to_datetime(1777303258979, unit='ms')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 35,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"Timestamp('2026-04-27 15:20:58.950000')"
|
||||
]
|
||||
},
|
||||
"execution_count": 35,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"import pandas as pd\n",
|
||||
"pd.to_datetime(1777303258950, unit='ms')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
},
|
||||
{
|
||||
@@ -155,7 +239,7 @@
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.13.12"
|
||||
"version": "3.13.13"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
|
||||
@@ -15,5 +15,5 @@ RUN pip install --no-cache-dir -r requirements.txt
|
||||
COPY . .
|
||||
|
||||
# Finally, run gunicorn.
|
||||
CMD [ "python", "-u" ,"main.py"]
|
||||
CMD [ "python", "main.py"]
|
||||
# CMD [ "gunicorn", "--workers=5", "--threads=1", "-b 0.0.0.0:8000", "app:server"]
|
||||
@@ -1,13 +1,14 @@
|
||||
{
|
||||
"Config_Updated_Timestamp": 1777098091913,
|
||||
"Config_Updated_Timestamp": 1777312620005,
|
||||
"Allow_Ordering_Aster": true,
|
||||
"Allow_Ordering_Extend": true,
|
||||
"Loop_Sleep_Sec": 0.00,
|
||||
"Max_Target_Notional": 0.00,
|
||||
"Min_Time_To_Funding_Minutes": 10,
|
||||
"Loop_Sleep_Sec": 0.0,
|
||||
"Max_Target_Notional": 0.0,
|
||||
"Min_Time_To_Funding_Minutes": 7,
|
||||
"Price_Worsener_Aster": 0.0,
|
||||
"Price_Worsener_Extend": 0.0,
|
||||
"Target_Open_Cash_Position": 10,
|
||||
"Print_Summary_Each_Loop" : false,
|
||||
"Log_Summary_Each_Loop": false,
|
||||
"Print_Summary_Each_Loop": false,
|
||||
"Flip_Side_For_Testing": false
|
||||
}
|
||||
@@ -42,14 +42,19 @@ async def orchestrator() -> None:
|
||||
timestamp = round(datetime.now().timestamp()*1000)
|
||||
data = json.loads(message['data'])
|
||||
# channel = message['channel']
|
||||
|
||||
|
||||
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
|
||||
|
||||
for k, v in data.items():
|
||||
if ALGO_CONFIG.get(k, None) is not None:
|
||||
ALGO_CONFIG[k] = v
|
||||
|
||||
ALGO_CONFIG['Config_Updated_Timestamp'] = timestamp
|
||||
VAL_KEY.set(VK_OUT, json.dumps(ALGO_CONFIG))
|
||||
with open('algo_config.json', 'w', encoding='utf-8') as f:
|
||||
with open('/algo_local_drive/algo_config.json', 'w', encoding='utf-8') as f:
|
||||
# print('SAVING FILE')
|
||||
json.dump(ALGO_CONFIG, f, indent=4)
|
||||
print(f"Algo Config Updated @ {timestamp}; {data}")
|
||||
|
||||
@@ -70,7 +75,7 @@ async def main() -> None:
|
||||
VAL_KEY = valkey.Valkey(host='localhost', port=6379, db=0, decode_responses=True)
|
||||
# engine = create_async_engine('mysql+asyncmy://root:pwd@localhost/fund_rate')
|
||||
|
||||
with open('algo_config.json', 'r', encoding='utf-8') as f:
|
||||
with open('/algo_local_drive/algo_config.json', 'r', encoding='utf-8') as f:
|
||||
# ALGO_CONFIG = json.load(f, object_hook=lambda d: Algo_Config(**d))
|
||||
ALGO_CONFIG = json.load(f)
|
||||
ALGO_CONFIG['Config_Updated_Timestamp'] = round(datetime.now().timestamp()*1000)
|
||||
|
||||
@@ -15,5 +15,5 @@ RUN pip install --no-cache-dir -r requirements.txt
|
||||
COPY . .
|
||||
|
||||
# Finally, run gunicorn.
|
||||
CMD [ "python", "-u" ,"algo_orchestrator.py"]
|
||||
CMD [ "python", "algo_orchestrator.py"]
|
||||
# CMD [ "gunicorn", "--workers=5", "--threads=1", "-b 0.0.0.0:8000", "app:server"]
|
||||
15
docker-compose-algo.yml
Normal file
15
docker-compose-algo.yml
Normal file
@@ -0,0 +1,15 @@
|
||||
# tail -f Fund_Rate_Algo.log
|
||||
# docker compose -f docker-compose-algo.yml up --build
|
||||
|
||||
services:
|
||||
algo:
|
||||
container_name: algo
|
||||
restart: "no"
|
||||
build:
|
||||
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
|
||||
- ./:/algo_local_drive:rw # Read-write access to data
|
||||
network_mode: "host"
|
||||
@@ -1,23 +1,24 @@
|
||||
# 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_User.log
|
||||
# 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
|
||||
|
||||
services:
|
||||
algo:
|
||||
container_name: algo
|
||||
restart: "no"
|
||||
build:
|
||||
context: ./
|
||||
dockerfile: ./algo/Dockerfile
|
||||
depends_on:
|
||||
- algo_orchestrator
|
||||
- ws_aster
|
||||
- ws_aster_user
|
||||
- ws_extended_fund_rate
|
||||
- ws_extended_orderbook
|
||||
- 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
|
||||
network_mode: "host"
|
||||
# algo:
|
||||
# container_name: algo
|
||||
# restart: "no"
|
||||
# build:
|
||||
# context: ./
|
||||
# dockerfile: ./algo/Dockerfile
|
||||
# depends_on:
|
||||
# - algo_orchestrator
|
||||
# - 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
|
||||
# network_mode: "host"
|
||||
algo_orchestrator:
|
||||
container_name: algo_orchestrator
|
||||
restart: "unless-stopped"
|
||||
@@ -27,6 +28,7 @@ services:
|
||||
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
|
||||
- ./:/algo_local_drive:rw # Read-write access to data
|
||||
network_mode: "host"
|
||||
ws_aster:
|
||||
container_name: ws_aster
|
||||
@@ -68,6 +70,16 @@ services:
|
||||
- /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_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"
|
||||
|
||||
0
engine_health.py
Normal file
0
engine_health.py
Normal file
76
main.py
76
main.py
@@ -38,6 +38,7 @@ LOG_FILEPATH: str = os.getenv("LOGS_PATH") + '/Fund_Rate_Algo.log'
|
||||
|
||||
### Algo Config ###
|
||||
ALGO_CONFIG: structs.Algo_Config = None
|
||||
MIN_TIME_TO_FUNDING: int
|
||||
|
||||
### CONSTANTS ###
|
||||
ASTER = structs.Perpetual_Exchange(
|
||||
@@ -64,6 +65,12 @@ EXTEND_AVAIL_COLLATERAL = 0
|
||||
ASTER_NOTIONAL_POSITION = 0
|
||||
EXTEND_NOTIONAL_POSITION = 0
|
||||
|
||||
ASTER_NOTIONAL_OBJ: dict | None = None
|
||||
EXTEND_NOTIONAL_OBJ: dict | None = None
|
||||
|
||||
ASTER_UNREALIZED_PNL = 0
|
||||
EXTEND_UNREALIZED_PNL = 0
|
||||
|
||||
ASTER_OPEN_ORDERS = []
|
||||
EXTEND_OPEN_ORDERS = []
|
||||
|
||||
@@ -111,9 +118,13 @@ async def get_aster_collateral():
|
||||
ASTER_AVAIL_COLLATERAL = float([d for d in r if d.get('asset')==ASTER.rh_asset][0].get('availableBalance'))
|
||||
|
||||
async def get_aster_notional_position(resp: dict | None = None):
|
||||
global ASTER_NOTIONAL_OBJ
|
||||
global ASTER_NOTIONAL_POSITION
|
||||
global ASTER_UNREALIZED_PNL
|
||||
global ASTER_MULT
|
||||
|
||||
previous_notional_obj = ASTER_NOTIONAL_OBJ
|
||||
|
||||
if not resp:
|
||||
fut_acct_positionRisk = {
|
||||
"url": "/fapi/v3/positionRisk",
|
||||
@@ -123,29 +134,38 @@ async def get_aster_notional_position(resp: dict | None = None):
|
||||
}
|
||||
}
|
||||
resp = await aster_auth.post_authenticated_url(fut_acct_positionRisk)
|
||||
d = [x for x in resp if x.get('symbol', None) == ASTER.symbol][0]
|
||||
d['timestamp_arrival'] = round(datetime.now().timestamp()*1000)
|
||||
else:
|
||||
d = [x for x in resp if x.get('symbol', None) == ASTER.symbol][0]
|
||||
|
||||
if previous_notional_obj is not None:
|
||||
if previous_notional_obj['timestamp_arrival'] > d['timestamp_arrival']:
|
||||
# logging.info(f'ASTER NOTIONAL: prev timestamp ({pd.to_datetime(previous_notional_obj['timestamp_arrival'], unit='ms')}) > new timestamp ({pd.to_datetime(d['timestamp_arrival'], unit='ms')}); skipping')
|
||||
return
|
||||
|
||||
d = [x for x in resp if x.get('symbol', None) == ASTER.symbol][0]
|
||||
ASTER_NOTIONAL_OBJ = d
|
||||
|
||||
if len(d) < 1:
|
||||
logging.info(f'BAD NOTIONAL - ASTER CHANGE: Empty d: {d}; resp: {resp}')
|
||||
await kill_algo()
|
||||
|
||||
aster_unrealized_pnl = float(d['unrealized_pnl']) if d.get('unrealized_pnl') is not None else float(d['unRealizedProfit'])
|
||||
ASTER_UNREALIZED_PNL = float(d['unrealized_pnl']) if d.get('unrealized_pnl') is not None else float(d['unRealizedProfit'])
|
||||
|
||||
if d.get('notional') is not None:
|
||||
notional = float(d['notional'])
|
||||
ASTER_NOTIONAL_POSITION = float(d['notional']) - ASTER_UNREALIZED_PNL
|
||||
else:
|
||||
notional = float(d['position_amount'])*float(d['entry_price'])
|
||||
ASTER_NOTIONAL_POSITION = float(d['position_amount'])*float(d['entry_price'])
|
||||
|
||||
previous_notional_position = ASTER_NOTIONAL_POSITION
|
||||
ASTER_NOTIONAL_POSITION = notional - aster_unrealized_pnl
|
||||
if not resp:
|
||||
ASTER_MULT = float(d['leverage'])
|
||||
if abs(ASTER_NOTIONAL_POSITION) > ALGO_CONFIG.Max_Target_Notional*1.01:
|
||||
logging.info(f'BAD NOTIONAL - ASTER CHANGE: {ASTER_NOTIONAL_POSITION}; UR PNL: {aster_unrealized_pnl}; MULT: {ASTER_MULT}; d: {d}; resp: {resp}')
|
||||
# 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}')
|
||||
await kill_algo()
|
||||
if ASTER_NOTIONAL_POSITION != previous_notional_position:
|
||||
logging.info(f'ASTER NOTIONAL CHANGE: {previous_notional_position} -> {ASTER_NOTIONAL_POSITION:.2f}; UR PNL: {aster_unrealized_pnl:.2f}; MULT: {ASTER_MULT:.0f}; resp: {bool(resp)}')
|
||||
# if ASTER_NOTIONAL_POSITION != previous_notional_position:
|
||||
logging.info(f'ASTER NOTIONAL CHANGE: {previous_notional_position} -> {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
|
||||
@@ -155,6 +175,7 @@ async def get_extend_collateral():
|
||||
|
||||
async def get_extend_notional(resp: dict | None = None):
|
||||
global EXTEND_NOTIONAL_POSITION
|
||||
global EXTEND_UNREALIZED_PNL
|
||||
global EXTEND_MULT
|
||||
|
||||
if not resp:
|
||||
@@ -166,7 +187,7 @@ async def get_extend_notional(resp: dict | None = None):
|
||||
logging.info('get_extend_notional - No Positions')
|
||||
else:
|
||||
pos_dict = pos_dict[0]
|
||||
unrealized_pnl = pos_dict.get('unrealised_pnl', 0)
|
||||
EXTEND_UNREALIZED_PNL = pos_dict.get('unrealised_pnl', 0)
|
||||
previous_notional_position = EXTEND_NOTIONAL_POSITION
|
||||
position_side = pos_dict['side'] # LONG or SHORT
|
||||
notional_pos_abs = abs(float(pos_dict['value']))
|
||||
@@ -177,10 +198,13 @@ async def get_extend_notional(resp: dict | None = None):
|
||||
else:
|
||||
logging.info(f'EXTEND BAD SIDE ON POSITION UPDATE: {pos_dict}')
|
||||
|
||||
EXTEND_NOTIONAL_POSITION = notional_pos_sided - float(unrealized_pnl)
|
||||
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}')
|
||||
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: {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():
|
||||
@@ -226,9 +250,11 @@ async def kill_algo():
|
||||
logging.info('ALGO KILL FLAG ACTIVATED; CANCELLING OPEN ORDERS AND SHUTTING DOWN')
|
||||
raise ValueError('KILL FLAG ACTIVATED')
|
||||
|
||||
|
||||
### ALGO LOOP ###
|
||||
async def run_algo():
|
||||
global ALGO_CONFIG
|
||||
global MIN_TIME_TO_FUNDING
|
||||
global ASTER_OPEN_ORDERS
|
||||
global EXTEND_OPEN_ORDERS
|
||||
|
||||
@@ -288,13 +314,15 @@ async def run_algo():
|
||||
now_ms = round(datetime.now().timestamp()*1000)
|
||||
time_to_funding_ms = min([ASTER_FUND_RATE_TIME, EXTEND_FUND_RATE_TIME]) - now_ms
|
||||
if ( time_to_funding_ms > MIN_TIME_TO_FUNDING ) and (not ASTER_OPEN_ORDERS) and (not EXTEND_OPEN_ORDERS):
|
||||
print(f'Outside action window (minutes) and no active order (sleeping for 5 sec): {pd.to_datetime(time_to_funding_ms, unit='ms').minute} > {pd.to_datetime(MIN_TIME_TO_FUNDING, unit='ms').minute}')
|
||||
logging.info(f'Outside action window (minutes) and no active order (sleeping for 5 sec): {pd.to_datetime(time_to_funding_ms, unit='ms').minute} > {pd.to_datetime(MIN_TIME_TO_FUNDING, unit='ms').minute}')
|
||||
time.sleep(5)
|
||||
continue
|
||||
|
||||
if len(ASTER_WS_POS_UPDATES) > 0:
|
||||
# await get_aster_notional_position()
|
||||
await get_aster_notional_position(resp=ASTER_WS_POS_UPDATES)
|
||||
###### *** returned 0 notional even though had a position, need to handle and safety check to not order above max notional.
|
||||
##### NEED TO UPDATE SO IT TAKES THE LATEST MSG, ie drop the WS msg if its older than the exisiting one from the API.
|
||||
|
||||
if len(EXTEND_WS_POS_UPDATES) > 0:
|
||||
await get_extend_notional(resp=EXTEND_WS_POS_UPDATES)
|
||||
@@ -304,7 +332,7 @@ async def run_algo():
|
||||
if ASTER_WS_ORDER_UPDATES is not None:
|
||||
for idx, o in enumerate(ASTER_OPEN_ORDERS):
|
||||
order_id = o.get('order_id') if o.get('order_id') is not None else o['orderId']
|
||||
order_orig_status = o['status'] ### Got a keyerror on this
|
||||
order_orig_status = o.get('status') if o.get('status') is not None else o['order_status']
|
||||
order_update = [ou for ou in ASTER_WS_ORDER_UPDATES if ou.get('order_id', None) == order_id]
|
||||
|
||||
if len(order_update) > 0:
|
||||
@@ -411,8 +439,8 @@ async def run_algo():
|
||||
EXTEND_TOB_PX = float(EXTENDED_TICKER_DICT['best_bid_px'])
|
||||
|
||||
|
||||
ASTER_TGT_TAIL = ASTER_TGT_NOTIONAL - ASTER_NOTIONAL_POSITION
|
||||
EXTEND_TGT_TAIL = EXTEND_TGT_NOTIONAL - EXTEND_NOTIONAL_POSITION
|
||||
ASTER_TGT_TAIL = ASTER_TGT_NOTIONAL - ( float(ASTER_NOTIONAL_POSITION) + float(ASTER_UNREALIZED_PNL) )
|
||||
EXTEND_TGT_TAIL = EXTEND_TGT_NOTIONAL - ( float(EXTEND_NOTIONAL_POSITION) + float(EXTEND_UNREALIZED_PNL) )
|
||||
|
||||
ASTER_TGT_TAIL_BASE_QTY = Decimal(str(float(ASTER_TGT_TAIL) / float(ASTER_TOB_PX))).quantize(Decimal(str(0.001)), rounding=ROUND_DOWN)
|
||||
EXTEND_TGT_TAIL_BASE_QTY = Decimal(str(float(EXTEND_TGT_TAIL) / float(EXTEND_TOB_PX))).quantize(Decimal(str(0.001)), rounding=ROUND_DOWN)
|
||||
@@ -426,9 +454,10 @@ async def run_algo():
|
||||
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}
|
||||
{pd.to_datetime(ASTER_FUND_RATE_TIME, unit='ms')} ({(pd.to_datetime(ASTER_FUND_RATE_TIME, unit='ms')-datetime.now()):}) | {pd.to_datetime(EXTEND_FUND_RATE_TIME, unit='ms')} ({(pd.to_datetime(EXTEND_FUND_RATE_TIME, unit='ms')-datetime.now()):})
|
||||
ASTER: {ASTER_FUND_RATE:.6%} [{ASTER_FUND_RATE*10_000:.2f}bps] [{ASTER_FUND_RATE*1_000_000:.0f}pips] | EXTEND: {EXTEND_FUND_RATE:.6%} [{EXTEND_FUND_RATE*10_000:.2f}bps] [{EXTEND_FUND_RATE*1_000_000:.0f}pips]
|
||||
ASTER: {ASTER_FUND_RATE:.6%} [{ASTER_FUND_RATE*10_000:.2f}bps] [{ASTER_FUND_RATE*1_000_000:.0f}pips] | EXTEND: {EXTEND_FUND_RATE:.6%} [{EXTEND_FUND_RATE*10_000:.2f}bps] [{EXTEND_FUND_RATE*1_000_000:.0f}pips]
|
||||
ASTER: {'LONG PAYS SHORT' if ASTER_FUND_RATE > 0 else 'SHORT PAYS LONG'} | EXTEND: {'LONG PAYS SHORT' if EXTEND_FUND_RATE > 0 else 'SHORT PAYS LONG'}
|
||||
ASTER: [ 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} ]
|
||||
@@ -440,7 +469,7 @@ async def run_algo():
|
||||
TGT NOTIONAL: $ {ALGO_CONFIG.Max_Target_Notional if not Flags.NET_FUNDING_IS_ZERO else 0.00}
|
||||
|
||||
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:.4f} - {ASTER_NOTIONAL_POSITION:.4f} = Tail: {ASTER_TGT_TAIL:4f} | EXTEND: {EXTEND_TGT_NOTIONAL:.4f} - {EXTEND_NOTIONAL_POSITION:.4f} = Tail: {EXTEND_TGT_TAIL:4f}
|
||||
ASTER: {ASTER_TGT_NOTIONAL:.2f} - {ASTER_NOTIONAL_POSITION:.2f} + {ASTER_UNREALIZED_PNL:.2f} = {ASTER_TGT_TAIL:2f} | EXTEND: {EXTEND_TGT_NOTIONAL:.2f} - {EXTEND_NOTIONAL_POSITION:.2f} + {EXTEND_UNREALIZED_PNL:.2f} = {EXTEND_TGT_TAIL:2f}
|
||||
ASTER: {ASTER_TGT_TAIL_BASE_QTY:.4f} > {MAX_MIN_ORDER_QTY:.4f} min [ Order: {ASTER_TGT_TAIL_ORDERABLE} ] | EXTEND: {EXTEND_TGT_TAIL_BASE_QTY:.4f} > {MAX_MIN_ORDER_QTY:.4f} min [ Order: {EXTEND_TGT_TAIL_ORDERABLE} ]
|
||||
|
||||
--- ASTER OPEN ORDERS ---
|
||||
@@ -449,8 +478,10 @@ async def run_algo():
|
||||
--- EXTEND OPEN ORDERS ---
|
||||
{EXTEND_OPEN_ORDERS}
|
||||
''')
|
||||
if ALGO_CONFIG.Log_Summary_Each_Loop:
|
||||
print_summary(use_logging=True)
|
||||
if ALGO_CONFIG.Print_Summary_Each_Loop:
|
||||
print_summary()
|
||||
print_summary(use_logging=False)
|
||||
# print_summary()
|
||||
|
||||
### ROUTES ###
|
||||
@@ -510,6 +541,7 @@ async def run_algo():
|
||||
order_resp = await aster_auth.post_authenticated_url(post_order)
|
||||
if order_resp.get('orderId', None) is not None:
|
||||
order_resp['original_price'] = price
|
||||
order_resp['order_status'] = order_resp['status']
|
||||
ASTER_OPEN_ORDERS.append(order_resp)
|
||||
utils.send_tg_alert(f'FR_ALGO - ASTER Order. 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}')
|
||||
@@ -594,7 +626,7 @@ async def run_algo():
|
||||
logging.info('EXTEND HAS NO TAIL BUT OPEN ORDERS - CANCELLING OPEN ORDERS')
|
||||
await extend_cancel_all_orders()
|
||||
|
||||
print(f'__________ End ___________ (Algo Engine ms: {(time.time() - loop_start)*1000})')
|
||||
# print(f'__________ End ___________ (Algo Engine ms: {(time.time() - loop_start)*1000}); Sleeping for sec: {ALGO_CONFIG.Loop_Sleep_Sec}')
|
||||
|
||||
time.sleep(ALGO_CONFIG.Loop_Sleep_Sec)
|
||||
|
||||
|
||||
@@ -139,9 +139,30 @@ async def create_fr_aster_user_account_pos(
|
||||
else:
|
||||
raise ValueError('Only MySQL engine is implemented')
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### Mkt Trades Table ####
|
||||
async def create_fr_aster_mkt_trades(
|
||||
CON: AsyncContextManager,
|
||||
engine: str = 'mysql', # mysql | duckdb
|
||||
) -> None:
|
||||
if CON is None:
|
||||
logging.info("NO DB CONNECTION, SKIPPING Create Statements")
|
||||
else:
|
||||
if engine == 'mysql':
|
||||
logging.info('Creating Table if Does Not Exist: fr_aster_mkt_trades')
|
||||
await CON.execute(text("""
|
||||
CREATE TABLE IF NOT EXISTS fr_aster_mkt_trades (
|
||||
timestamp_arrival BIGINT,
|
||||
timestamp_msg BIGINT,
|
||||
timestamp_trade BIGINT,
|
||||
symbol VARCHAR(20),
|
||||
aggregate_trade_id VARCHAR(100),
|
||||
price DOUBLE,
|
||||
qty DOUBLE,
|
||||
first_trade_id VARCHAR(100),
|
||||
last_trade_id VARCHAR(100),
|
||||
is_buyer_mkt_maker BOOL
|
||||
);
|
||||
"""))
|
||||
await CON.commit()
|
||||
else:
|
||||
raise ValueError('Only MySQL engine is implemented')
|
||||
|
||||
@@ -150,3 +150,32 @@ async def create_fr_extended_user_position(
|
||||
else:
|
||||
raise ValueError('Only MySQL engine is implemented')
|
||||
|
||||
### Market Trades Table ####
|
||||
async def create_fr_extended_mkt_trades(
|
||||
CON: AsyncContextManager,
|
||||
engine: str = 'mysql', # mysql | duckdb
|
||||
) -> None:
|
||||
if CON is None:
|
||||
logging.info("NO DB CONNECTION, SKIPPING Create Statements")
|
||||
else:
|
||||
if engine == 'mysql':
|
||||
logging.info('Creating Table if Does Not Exist: fr_extended_mkt_trades')
|
||||
await CON.execute(text("""
|
||||
CREATE TABLE IF NOT EXISTS fr_extended_mkt_trades (
|
||||
sequence_id INT,
|
||||
timestamp_arrival BIGINT,
|
||||
timestamp_msg BIGINT,
|
||||
timestamp_trade BIGINT,
|
||||
symbol VARCHAR(20),
|
||||
side_taker VARCHAR(20),
|
||||
trade_type VARCHAR(20),
|
||||
price DOUBLE,
|
||||
qty DOUBLE,
|
||||
trade_id VARCHAR(100),
|
||||
is_buyer_mkt_maker BOOL
|
||||
);
|
||||
"""))
|
||||
await CON.commit()
|
||||
else:
|
||||
raise ValueError('Only MySQL engine is implemented')
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ class Algo_Config:
|
||||
Price_Worsener_Extend: float
|
||||
Target_Open_Cash_Position: int
|
||||
|
||||
Log_Summary_Each_Loop: bool = False
|
||||
Print_Summary_Each_Loop: bool = False
|
||||
Flip_Side_For_Testing: bool = False
|
||||
|
||||
|
||||
272
order_engine.ipynb
Normal file
272
order_engine.ipynb
Normal file
@@ -0,0 +1,272 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 32,
|
||||
"id": "68966247",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import requests\n",
|
||||
"import pandas as pd\n",
|
||||
"import numpy as np\n",
|
||||
"import json\n",
|
||||
"import pandas as pd"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "02cd5305",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"### Extended Trades History ###\n",
|
||||
"candleType = 'trades'\n",
|
||||
"market = 'ETH-USD'\n",
|
||||
"params = {\n",
|
||||
" 'interval': \"1m\",\n",
|
||||
" 'limit': 100,\n",
|
||||
"}\n",
|
||||
"r = requests.get(f'https://api.starknet.extended.exchange/api/v1/info/candles/{market}/{candleType}', params=params)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 33,
|
||||
"id": "5603b04d",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"### Aster Trades History ###\n",
|
||||
"params = {\n",
|
||||
" 'symbol': \"ETHUSDT\",\n",
|
||||
" 'limit': 1000,\n",
|
||||
"}\n",
|
||||
"r = requests.get('https://fapi.asterdex.com/fapi/v3/trades', params=params)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 34,
|
||||
"id": "a3ad1819",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/html": [
|
||||
"<div>\n",
|
||||
"<style scoped>\n",
|
||||
" .dataframe tbody tr th:only-of-type {\n",
|
||||
" vertical-align: middle;\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
" .dataframe tbody tr th {\n",
|
||||
" vertical-align: top;\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
" .dataframe thead th {\n",
|
||||
" text-align: right;\n",
|
||||
" }\n",
|
||||
"</style>\n",
|
||||
"<table border=\"1\" class=\"dataframe\">\n",
|
||||
" <thead>\n",
|
||||
" <tr style=\"text-align: right;\">\n",
|
||||
" <th></th>\n",
|
||||
" <th>id</th>\n",
|
||||
" <th>price</th>\n",
|
||||
" <th>qty</th>\n",
|
||||
" <th>quoteQty</th>\n",
|
||||
" <th>time</th>\n",
|
||||
" <th>isBuyerMaker</th>\n",
|
||||
" </tr>\n",
|
||||
" </thead>\n",
|
||||
" <tbody>\n",
|
||||
" <tr>\n",
|
||||
" <th>0</th>\n",
|
||||
" <td>74506547</td>\n",
|
||||
" <td>2311.02</td>\n",
|
||||
" <td>0.044</td>\n",
|
||||
" <td>101.68</td>\n",
|
||||
" <td>2026-04-27 14:22:45.650</td>\n",
|
||||
" <td>True</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>1</th>\n",
|
||||
" <td>74506548</td>\n",
|
||||
" <td>2311.00</td>\n",
|
||||
" <td>0.004</td>\n",
|
||||
" <td>9.24</td>\n",
|
||||
" <td>2026-04-27 14:22:45.650</td>\n",
|
||||
" <td>True</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>2</th>\n",
|
||||
" <td>74506549</td>\n",
|
||||
" <td>2310.91</td>\n",
|
||||
" <td>0.003</td>\n",
|
||||
" <td>6.93</td>\n",
|
||||
" <td>2026-04-27 14:22:45.650</td>\n",
|
||||
" <td>True</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>3</th>\n",
|
||||
" <td>74506550</td>\n",
|
||||
" <td>2310.90</td>\n",
|
||||
" <td>0.004</td>\n",
|
||||
" <td>9.24</td>\n",
|
||||
" <td>2026-04-27 14:22:45.650</td>\n",
|
||||
" <td>True</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>4</th>\n",
|
||||
" <td>74506551</td>\n",
|
||||
" <td>2310.80</td>\n",
|
||||
" <td>0.004</td>\n",
|
||||
" <td>9.24</td>\n",
|
||||
" <td>2026-04-27 14:22:45.700</td>\n",
|
||||
" <td>True</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>...</th>\n",
|
||||
" <td>...</td>\n",
|
||||
" <td>...</td>\n",
|
||||
" <td>...</td>\n",
|
||||
" <td>...</td>\n",
|
||||
" <td>...</td>\n",
|
||||
" <td>...</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>995</th>\n",
|
||||
" <td>74507542</td>\n",
|
||||
" <td>2312.10</td>\n",
|
||||
" <td>0.004</td>\n",
|
||||
" <td>9.24</td>\n",
|
||||
" <td>2026-04-27 14:34:12.500</td>\n",
|
||||
" <td>True</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>996</th>\n",
|
||||
" <td>74507543</td>\n",
|
||||
" <td>2312.18</td>\n",
|
||||
" <td>2.442</td>\n",
|
||||
" <td>5646.34</td>\n",
|
||||
" <td>2026-04-27 14:34:13.443</td>\n",
|
||||
" <td>True</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>997</th>\n",
|
||||
" <td>74507544</td>\n",
|
||||
" <td>2312.24</td>\n",
|
||||
" <td>10.099</td>\n",
|
||||
" <td>23351.31</td>\n",
|
||||
" <td>2026-04-27 14:34:13.600</td>\n",
|
||||
" <td>True</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>998</th>\n",
|
||||
" <td>74507545</td>\n",
|
||||
" <td>2312.13</td>\n",
|
||||
" <td>3.120</td>\n",
|
||||
" <td>7213.84</td>\n",
|
||||
" <td>2026-04-27 14:34:14.568</td>\n",
|
||||
" <td>True</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>999</th>\n",
|
||||
" <td>74507546</td>\n",
|
||||
" <td>2312.19</td>\n",
|
||||
" <td>6.228</td>\n",
|
||||
" <td>14400.31</td>\n",
|
||||
" <td>2026-04-27 14:34:15.988</td>\n",
|
||||
" <td>True</td>\n",
|
||||
" </tr>\n",
|
||||
" </tbody>\n",
|
||||
"</table>\n",
|
||||
"<p>1000 rows × 6 columns</p>\n",
|
||||
"</div>"
|
||||
],
|
||||
"text/plain": [
|
||||
" id price qty quoteQty time isBuyerMaker\n",
|
||||
"0 74506547 2311.02 0.044 101.68 2026-04-27 14:22:45.650 True\n",
|
||||
"1 74506548 2311.00 0.004 9.24 2026-04-27 14:22:45.650 True\n",
|
||||
"2 74506549 2310.91 0.003 6.93 2026-04-27 14:22:45.650 True\n",
|
||||
"3 74506550 2310.90 0.004 9.24 2026-04-27 14:22:45.650 True\n",
|
||||
"4 74506551 2310.80 0.004 9.24 2026-04-27 14:22:45.700 True\n",
|
||||
".. ... ... ... ... ... ...\n",
|
||||
"995 74507542 2312.10 0.004 9.24 2026-04-27 14:34:12.500 True\n",
|
||||
"996 74507543 2312.18 2.442 5646.34 2026-04-27 14:34:13.443 True\n",
|
||||
"997 74507544 2312.24 10.099 23351.31 2026-04-27 14:34:13.600 True\n",
|
||||
"998 74507545 2312.13 3.120 7213.84 2026-04-27 14:34:14.568 True\n",
|
||||
"999 74507546 2312.19 6.228 14400.31 2026-04-27 14:34:15.988 True\n",
|
||||
"\n",
|
||||
"[1000 rows x 6 columns]"
|
||||
]
|
||||
},
|
||||
"execution_count": 34,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"l = json.loads(r.text)\n",
|
||||
"df = pd.DataFrame(l)\n",
|
||||
"df['time'] = pd.to_datetime(df['time'], unit='ms')\n",
|
||||
"df"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "3c908942",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "60f4608a",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "76624896",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "5ade3c15",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "py_313",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.13.13"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
100
ws_aster.py
100
ws_aster.py
@@ -15,7 +15,8 @@ from sqlalchemy.ext.asyncio import create_async_engine
|
||||
import valkey
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
|
||||
import modules.db as db
|
||||
import modules.aster_db as aster_db
|
||||
|
||||
### Allow only ipv4 ###
|
||||
def allowed_gai_family():
|
||||
@@ -23,10 +24,11 @@ def allowed_gai_family():
|
||||
urllib3_cn.allowed_gai_family = allowed_gai_family
|
||||
|
||||
### Database ###
|
||||
USE_DB: bool = False
|
||||
USE_DB: bool = True
|
||||
USE_VK: bool = True
|
||||
VK_FUND_RATE = 'fund_rate_aster'
|
||||
VK_TICKER = 'fut_ticker_aster'
|
||||
VK_LAST_TRADE = 'fut_last_trade_aster'
|
||||
CON: AsyncContextManager | None = None
|
||||
VAL_KEY = None
|
||||
|
||||
@@ -38,80 +40,10 @@ LOG_FILEPATH: str = os.getenv("LOGS_PATH") + '/Fund_Rate_Aster.log'
|
||||
SYMBOL: str = 'ETHUSDT'
|
||||
STREAM_MARKPRICE: str = f'{SYMBOL.lower()}@markPrice@1s'
|
||||
STREAM_BOOKTICKER: str = f'{SYMBOL.lower()}@bookTicker'
|
||||
STREAM_TRADES: str = f'{SYMBOL.lower()}@aggTrade'
|
||||
|
||||
### Globals ###
|
||||
WSS_URL = f"wss://fstream.asterdex.com/stream?streams={STREAM_MARKPRICE}/{STREAM_BOOKTICKER}"
|
||||
# WSS_URL = f"wss://fstream.asterdex.com/stream?streams={STREAM_MARKPRICE}"
|
||||
|
||||
# HIST_TRADES = np.empty((0, 3))
|
||||
# HIST_TRADES_LOOKBACK_SEC = 6
|
||||
|
||||
# ### Database Funcs ###
|
||||
# async def create_rtds_btcusd_table(
|
||||
# CON: AsyncContextManager,
|
||||
# engine: str = 'mysql', # mysql | duckdb
|
||||
# ) -> None:
|
||||
# if CON is None:
|
||||
# logging.info("NO DB CONNECTION, SKIPPING Create Statements")
|
||||
# else:
|
||||
# if engine == 'mysql':
|
||||
# logging.info('Creating Table if Does Not Exist: binance_btcusd_trades')
|
||||
# await CON.execute(text("""
|
||||
# CREATE TABLE IF NOT EXISTS binance_btcusd_trades (
|
||||
# timestamp_arrival BIGINT,
|
||||
# timestamp_msg BIGINT,
|
||||
# timestamp_value BIGINT,
|
||||
# value DOUBLE,
|
||||
# qty DOUBLE
|
||||
# );
|
||||
# """))
|
||||
# await CON.commit()
|
||||
# else:
|
||||
# raise ValueError('Only MySQL engine is implemented')
|
||||
|
||||
# async def insert_rtds_btcusd_table(
|
||||
# timestamp_arrival: int,
|
||||
# timestamp_msg: int,
|
||||
# timestamp_value: int,
|
||||
# value: float,
|
||||
# qty: float,
|
||||
# CON: AsyncContextManager,
|
||||
# engine: str = 'mysql', # mysql | duckdb
|
||||
# ) -> None:
|
||||
# params={
|
||||
# 'timestamp_arrival': timestamp_arrival,
|
||||
# 'timestamp_msg': timestamp_msg,
|
||||
# 'timestamp_value': timestamp_value,
|
||||
# 'value': value,
|
||||
# 'qty': qty,
|
||||
# }
|
||||
# if CON is None:
|
||||
# logging.info("NO DB CONNECTION, SKIPPING Insert Statements")
|
||||
# else:
|
||||
# if engine == 'mysql':
|
||||
# await CON.execute(text("""
|
||||
# INSERT INTO binance_btcusd_trades
|
||||
# (
|
||||
# timestamp_arrival,
|
||||
# timestamp_msg,
|
||||
# timestamp_value,
|
||||
# value,
|
||||
# qty
|
||||
# )
|
||||
# VALUES
|
||||
# (
|
||||
# :timestamp_arrival,
|
||||
# :timestamp_msg,
|
||||
# :timestamp_value,
|
||||
# :value,
|
||||
# :qty
|
||||
# )
|
||||
# """),
|
||||
# parameters=params
|
||||
# )
|
||||
# await CON.commit()
|
||||
# else:
|
||||
# raise ValueError('Only MySQL engine is implemented')
|
||||
WSS_URL = f"wss://fstream.asterdex.com/stream?streams={STREAM_MARKPRICE}/{STREAM_BOOKTICKER}/{STREAM_TRADES}"
|
||||
|
||||
### Websocket ###
|
||||
async def ws_stream():
|
||||
@@ -156,6 +88,24 @@ async def ws_stream():
|
||||
})
|
||||
VAL_KEY.set(VK_TICKER, VAL_KEY_OBJ)
|
||||
continue
|
||||
case c if c == STREAM_TRADES:
|
||||
# print(f'MKT_TRADE: {data}')
|
||||
trade_obj = {
|
||||
'timestamp_arrival': ts_arrival,
|
||||
'timestamp_msg': data['data']['E'],
|
||||
'timestamp_trade': data['data']['T'],
|
||||
'symbol': data['data']['s'],
|
||||
'aggregate_trade_id': data['data']['a'],
|
||||
'price': float(data['data']['p']),
|
||||
'qty': float(data['data']['q']),
|
||||
'first_trade_id': data['data']['f'],
|
||||
'last_trade_id': data['data']['l'],
|
||||
'is_buyer_mkt_maker': bool(data['data']['m']),
|
||||
}
|
||||
# VAL_KEY.set(VK_LAST_TRADE, json.dumps(trade_obj))
|
||||
if USE_DB:
|
||||
await db.insert_df_to_mysql(table_name='fr_aster_mkt_trades', params=trade_obj, CON=CON)
|
||||
continue
|
||||
case _:
|
||||
logging.warning(f'UNMATCHED OTHER MSG: {data}')
|
||||
else:
|
||||
@@ -188,7 +138,7 @@ async def main():
|
||||
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 aster_db.create_fr_aster_mkt_trades(CON=CON)
|
||||
await ws_stream()
|
||||
else:
|
||||
CON = None
|
||||
|
||||
134
ws_extended_trades.py
Normal file
134
ws_extended_trades.py
Normal file
@@ -0,0 +1,134 @@
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import socket
|
||||
import traceback
|
||||
from datetime import datetime
|
||||
from typing import AsyncContextManager
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import requests.packages.urllib3.util.connection as urllib3_cn # type: ignore
|
||||
import valkey
|
||||
import websockets
|
||||
from dotenv import load_dotenv
|
||||
from sqlalchemy.ext.asyncio import create_async_engine
|
||||
|
||||
import modules.db as db
|
||||
import modules.extended_db as extended_db
|
||||
|
||||
|
||||
### Allow only ipv4 ###
|
||||
def allowed_gai_family():
|
||||
return socket.AF_INET
|
||||
urllib3_cn.allowed_gai_family = allowed_gai_family
|
||||
|
||||
### Database ###
|
||||
USE_DB: bool = True
|
||||
USE_VK: bool = True
|
||||
|
||||
VK_LAST_TRADE = 'fut_last_trade_extended'
|
||||
CON: AsyncContextManager | None = None
|
||||
VAL_KEY = None
|
||||
|
||||
### Logging ###
|
||||
load_dotenv()
|
||||
LOG_FILEPATH: str = os.getenv("LOGS_PATH") + '/Fund_Rate_Extended_Trades.log'
|
||||
|
||||
### CONSTANTS ###
|
||||
WS_SYMBOL: str = 'ETH-USD'
|
||||
|
||||
### Globals ###
|
||||
WSS_URL = f"wss://api.starknet.extended.exchange/stream.extended.exchange/v1/publicTrades/{WS_SYMBOL}"
|
||||
|
||||
### 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:
|
||||
ts_arrival = round(datetime.now().timestamp()*1000)
|
||||
if isinstance(message, str):
|
||||
try:
|
||||
data = json.loads(message)
|
||||
if data.get('data', None) is not None:
|
||||
if data['seq'] == 1: # Skip first msg that has historical trades
|
||||
continue
|
||||
list_for_df = []
|
||||
for t in data['data']:
|
||||
trade_obj = {
|
||||
'sequence_id': data['seq'],
|
||||
'timestamp_arrival': ts_arrival,
|
||||
'timestamp_msg': data['ts'],
|
||||
'timestamp_trade': t['T'],
|
||||
'symbol': t['m'],
|
||||
'side_taker': t['S'],
|
||||
'trade_type': t['tT'],
|
||||
'price': float(t['p']),
|
||||
'qty': float(t['q']),
|
||||
'trade_id': t['i'],
|
||||
'is_buyer_mkt_maker': True if t['S']=='SELL' else False,
|
||||
}
|
||||
list_for_df.append(trade_obj)
|
||||
# VAL_KEY.set(VK_LAST_TRADE, json.dumps(trade_obj))
|
||||
if USE_DB:
|
||||
await db.insert_df_to_mysql(table_name='fr_extended_mkt_trades', params=list_for_df, CON=CON)
|
||||
pass
|
||||
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())
|
||||
|
||||
|
||||
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 extended_db.create_fr_extended_mkt_trades(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")
|
||||
19
ws_extended_trades/Dockerfile
Normal file
19
ws_extended_trades/Dockerfile
Normal file
@@ -0,0 +1,19 @@
|
||||
FROM python:3.13-slim
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y build-essential
|
||||
|
||||
RUN gcc --version
|
||||
RUN rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY requirements.txt .
|
||||
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
COPY . .
|
||||
|
||||
# Finally, run gunicorn.
|
||||
CMD [ "python", "ws_extended_trades.py"]
|
||||
# CMD [ "gunicorn", "--workers=5", "--threads=1", "-b 0.0.0.0:8000", "app:server"]
|
||||
@@ -198,7 +198,6 @@ async def ws_stream():
|
||||
|
||||
VAL_KEY_OBJ = json.dumps(LOCAL_RECENT_POSITIONS)
|
||||
VAL_KEY.set(VK_POSITIONS, VAL_KEY_OBJ)
|
||||
|
||||
await db.insert_df_to_mysql(table_name='fr_extended_user_position', params=list_for_df, CON=CON)
|
||||
continue
|
||||
case _:
|
||||
|
||||
Reference in New Issue
Block a user