start of refactor to objects
This commit is contained in:
19
algo/Dockerfile
Normal file
19
algo/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", "-u" ,"main.py"]
|
||||||
|
# CMD [ "gunicorn", "--workers=5", "--threads=1", "-b 0.0.0.0:8000", "app:server"]
|
||||||
6980
aster.ipynb
6980
aster.ipynb
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,22 @@
|
|||||||
# tail -f 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_User.log
|
||||||
|
|
||||||
services:
|
services:
|
||||||
|
algo:
|
||||||
|
container_name: algo
|
||||||
|
restart: "no"
|
||||||
|
build:
|
||||||
|
context: ./
|
||||||
|
dockerfile: ./algo/Dockerfile
|
||||||
|
depends_on:
|
||||||
|
- 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"
|
||||||
ws_aster:
|
ws_aster:
|
||||||
container_name: ws_aster
|
container_name: ws_aster
|
||||||
restart: "unless-stopped"
|
restart: "unless-stopped"
|
||||||
|
|||||||
314
extended.ipynb
314
extended.ipynb
@@ -2,7 +2,7 @@
|
|||||||
"cells": [
|
"cells": [
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 4,
|
"execution_count": 2,
|
||||||
"id": "6c70a8c3",
|
"id": "6c70a8c3",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 5,
|
"execution_count": 3,
|
||||||
"id": "ff971ca9",
|
"id": "ff971ca9",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
@@ -41,15 +41,15 @@
|
|||||||
"\n",
|
"\n",
|
||||||
"CONFIG = MAINNET_CONFIG\n",
|
"CONFIG = MAINNET_CONFIG\n",
|
||||||
"\n",
|
"\n",
|
||||||
"ORDER_MARKET = \"BTC-USD\"\n",
|
"ORDER_MARKET = \"ETH-USD\"\n",
|
||||||
"ORDER_SIDE = OrderSide.BUY\n",
|
"ORDER_SIDE = OrderSide.BUY\n",
|
||||||
"ORDER_QTY = Decimal(\"0.001\")\n",
|
"ORDER_QTY = Decimal(\"0.01\")\n",
|
||||||
"ORDER_PRICE = Decimal(\"75000\")"
|
"ORDER_PRICE = Decimal(\"2200\")"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 8,
|
"execution_count": 4,
|
||||||
"id": "fc2c6d2b",
|
"id": "fc2c6d2b",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
@@ -59,18 +59,116 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": null,
|
"execution_count": 41,
|
||||||
"id": "c366706f",
|
"id": "c366706f",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"# placed_order = await trading_client.place_order(\n",
|
"placed_order = await trading_client.place_order(\n",
|
||||||
"# market_name=ORDER_MARKET,\n",
|
" market_name=ORDER_MARKET,\n",
|
||||||
"# amount_of_synthetic=ORDER_QTY,\n",
|
" amount_of_synthetic=ORDER_QTY,\n",
|
||||||
"# price=ORDER_PRICE,\n",
|
" price=ORDER_PRICE,\n",
|
||||||
"# side=ORDER_SIDE,\n",
|
" side=ORDER_SIDE,\n",
|
||||||
"# taker_fee=Decimal(\"0.00025\")\n",
|
" taker_fee=Decimal(\"0.00025\"),\n",
|
||||||
"# )"
|
" previous_order_id='1295034892466447624365619416628580523728221205816494340545831832663414858661'\n",
|
||||||
|
")"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 5,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"text/plain": [
|
||||||
|
"WrappedApiResponse[EmptyModel](status='OK', data=EmptyModel(), error=None, pagination=None)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"execution_count": 5,
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "execute_result"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"c = await trading_client.orders.mass_cancel(markets=['ETH-USD'])\n",
|
||||||
|
"c"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 59,
|
||||||
|
"id": "1e0cc529",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"d = dict(placed_order)\n",
|
||||||
|
"d = d['data']"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 61,
|
||||||
|
"id": "c6e2570b",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"text/plain": [
|
||||||
|
"{'id': 2047411567531950080,\n",
|
||||||
|
" 'external_id': '970778360519119766032805910949664642915982435081116578514332451865869879614'}"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"execution_count": 61,
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "execute_result"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"dict(d)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"id": "3e4cedd1",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"text/plain": [
|
||||||
|
"'OK'"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"execution_count": 58,
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "execute_result"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"dict(placed_order).get('status' \\\n",
|
||||||
|
"'')"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 4,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"text/plain": [
|
||||||
|
"Timestamp('2026-04-23 23:55:13.704000')"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"execution_count": 4,
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "execute_result"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"import pandas as pd\n",
|
||||||
|
"pd.to_datetime(1776988513704, unit='ms')"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -80,28 +178,136 @@
|
|||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": []
|
"source": []
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 8,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"text/plain": [
|
||||||
|
"[PositionModel(id=2047419314696355840, account_id=270571, market='ETH-USD', status='OPENED', side='LONG', leverage=Decimal('50'), size=Decimal('0.215'), value=Decimal('500.247305'), open_price=Decimal('2321.7'), mark_price=Decimal('2326.731653474999'), liquidation_price=Decimal('2222.8'), unrealised_pnl=Decimal('1.081805'), realised_pnl=Decimal('0.065534'), tp_price=None, sl_price=None, adl=2, created_at=1776977778498, updated_at=1776986778819)]"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"execution_count": 8,
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "execute_result"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"d = dict(await trading_client.account.get_positions()).get('data')\n",
|
||||||
|
"d"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 10,
|
||||||
|
"id": "b9ac87f2",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"text/plain": [
|
||||||
|
"{'id': 2047419314696355840,\n",
|
||||||
|
" 'account_id': 270571,\n",
|
||||||
|
" 'market': 'ETH-USD',\n",
|
||||||
|
" 'status': 'OPENED',\n",
|
||||||
|
" 'side': 'LONG',\n",
|
||||||
|
" 'leverage': Decimal('50'),\n",
|
||||||
|
" 'size': Decimal('0.215'),\n",
|
||||||
|
" 'value': Decimal('500.247305'),\n",
|
||||||
|
" 'open_price': Decimal('2321.7'),\n",
|
||||||
|
" 'mark_price': Decimal('2326.731653474999'),\n",
|
||||||
|
" 'liquidation_price': Decimal('2222.8'),\n",
|
||||||
|
" 'unrealised_pnl': Decimal('1.081805'),\n",
|
||||||
|
" 'realised_pnl': Decimal('0.065534'),\n",
|
||||||
|
" 'tp_price': None,\n",
|
||||||
|
" 'sl_price': None,\n",
|
||||||
|
" 'adl': 2,\n",
|
||||||
|
" 'created_at': 1776977778498,\n",
|
||||||
|
" 'updated_at': 1776986778819}"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"execution_count": 10,
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "execute_result"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"dict(d[0])"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": null,
|
"execution_count": null,
|
||||||
|
"id": "62299776",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"ename": "IndexError",
|
||||||
|
"evalue": "list index out of range",
|
||||||
|
"output_type": "error",
|
||||||
|
"traceback": [
|
||||||
|
"\u001b[31m---------------------------------------------------------------------------\u001b[39m",
|
||||||
|
"\u001b[31mIndexError\u001b[39m Traceback (most recent call last)",
|
||||||
|
"\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[40]\u001b[39m\u001b[32m, line 1\u001b[39m\n\u001b[32m----> \u001b[39m\u001b[32m1\u001b[39m [j \u001b[38;5;28;01mfor\u001b[39;00m j \u001b[38;5;28;01min\u001b[39;00m d \u001b[38;5;28;01mif\u001b[39;00m j.get(\u001b[33m'market'\u001b[39m) == \u001b[33m'ETH-USD'\u001b[39m][\u001b[32m0\u001b[39m]\n",
|
||||||
|
"\u001b[31mIndexError\u001b[39m: list index out of range"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"[j for j in d if j.get('market') == 'ETH-USD']"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 31,
|
||||||
"id": "7cd3413d",
|
"id": "7cd3413d",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": []
|
"source": [
|
||||||
|
"balance = dict(dict(await trading_client.account.get_balance()).get('data', {}))"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": null,
|
"execution_count": null,
|
||||||
|
"id": "62632db3",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": []
|
"source": [
|
||||||
|
"r = await trading_client.markets_info.get_pos"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": null,
|
"execution_count": 55,
|
||||||
"id": "a7212988",
|
"id": "e9c3fdc4",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": []
|
"source": [
|
||||||
|
"d = r['ETH-USD'].trading_config"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 58,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"text/plain": [
|
||||||
|
"0.01"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"execution_count": 58,
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "execute_result"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"float(d.min_order_size)"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
@@ -346,17 +552,85 @@
|
|||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": []
|
"source": []
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 11,
|
||||||
|
"id": "b0388dc7",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"import valkey\n",
|
||||||
|
"import json\n",
|
||||||
|
"VAL_KEY = valkey.Valkey(host='localhost', port=6379, db=0, decode_responses=True)"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": null,
|
"execution_count": null,
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"b = json.loads(VAL_KEY.get('fund_rate_aster'))"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 12,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"a = json.loads(VAL_KEY.get('fund_rate_aster'))"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 16,
|
||||||
|
"id": "665377af",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"text/plain": [
|
||||||
|
"{'timestamp_arrival': 1776970338127,\n",
|
||||||
|
" 'timestamp_msg': 1776970338000,\n",
|
||||||
|
" 'symbol': 'ETHUSDT',\n",
|
||||||
|
" 'mark_price': '2310.59000000',\n",
|
||||||
|
" 'index_price': '2311.29372093',\n",
|
||||||
|
" 'estimated_settle_price': '2312.11380907',\n",
|
||||||
|
" 'funding_rate': '-0.00001108',\n",
|
||||||
|
" 'next_funding_time_ts_ms': 1776988800000}"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"execution_count": 16,
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "execute_result"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"json.loads(VAL_KEY.get('fund_rate_aster'))"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 18,
|
||||||
|
"id": "f15dd3c7",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"VAL_KEY.get('fr_aster_user_positions')"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"id": "23f88a3e",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
"source": []
|
"source": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": null,
|
"execution_count": null,
|
||||||
"id": "665377af",
|
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": []
|
"source": []
|
||||||
|
|||||||
511
main.py
511
main.py
@@ -5,18 +5,24 @@ import math
|
|||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
from dataclasses import asdict, dataclass
|
from dataclasses import asdict, dataclass, field
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
|
from decimal import ROUND_DOWN, Decimal
|
||||||
from typing import AsyncContextManager
|
from typing import AsyncContextManager
|
||||||
from dotenv import load_dotenv
|
|
||||||
from decimal import Decimal, ROUND_DOWN
|
from typing import Any
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
# import talib
|
# import talib
|
||||||
import valkey
|
import valkey
|
||||||
|
from dotenv import load_dotenv
|
||||||
from sqlalchemy import text
|
from sqlalchemy import text
|
||||||
from sqlalchemy.ext.asyncio import create_async_engine
|
from sqlalchemy.ext.asyncio import create_async_engine
|
||||||
|
from x10.models.order import OrderSide
|
||||||
|
|
||||||
|
import modules.utils as utils
|
||||||
import modules.aster_auth as aster_auth
|
import modules.aster_auth as aster_auth
|
||||||
import modules.extended_auth as extend_auth
|
import modules.extended_auth as extend_auth
|
||||||
|
|
||||||
@@ -30,6 +36,14 @@ load_dotenv()
|
|||||||
LOG_FILEPATH: str = os.getenv("LOGS_PATH") + '/Fund_Rate_Algo.log'
|
LOG_FILEPATH: str = os.getenv("LOGS_PATH") + '/Fund_Rate_Algo.log'
|
||||||
|
|
||||||
### CONSTANTS ###
|
### CONSTANTS ###
|
||||||
|
ASTER_ALLOW_ORDERING: bool = True
|
||||||
|
EXTEND_ALLOW_ORDERING: bool = True
|
||||||
|
LOOP_SLEEP_SEC = 1
|
||||||
|
PRICE_WORSENER_ASTER = 0.00
|
||||||
|
PRICE_WORSENER_EXTEND = 0.0
|
||||||
|
|
||||||
|
MIN_TIME_TO_FUNDING: int = 1000 * 60 * 7 # 5 minutes.
|
||||||
|
|
||||||
ASTER_LH_ASSET: str = 'ETH'
|
ASTER_LH_ASSET: str = 'ETH'
|
||||||
ASTER_RH_ASSET: str = 'USDT'
|
ASTER_RH_ASSET: str = 'USDT'
|
||||||
ASTER_TICKER: str = ASTER_LH_ASSET + ASTER_RH_ASSET
|
ASTER_TICKER: str = ASTER_LH_ASSET + ASTER_RH_ASSET
|
||||||
@@ -59,9 +73,136 @@ EXTEND_OPEN_ORDERS = []
|
|||||||
# ASTER_OPEN_POSITIONS = []
|
# ASTER_OPEN_POSITIONS = []
|
||||||
# EXTEND_OPEN_POSITIONS = []
|
# EXTEND_OPEN_POSITIONS = []
|
||||||
|
|
||||||
|
@dataclass(kw_only=True)
|
||||||
|
class Valkey_Stream:
|
||||||
|
channel: str
|
||||||
|
data: Any = None
|
||||||
|
none_fill: Any = None
|
||||||
|
|
||||||
|
async def update(self):
|
||||||
|
r = VAL_KEY.get(self.channel)
|
||||||
|
self.data = json.loads(r) if r is not None else self.none_fill
|
||||||
|
|
||||||
|
@dataclass(kw_only=True)
|
||||||
|
class Position:
|
||||||
|
market: str
|
||||||
|
notional: float
|
||||||
|
qty: float
|
||||||
|
|
||||||
|
@dataclass(kw_only=True)
|
||||||
|
class Open_Positions:
|
||||||
|
Valkey: Valkey_Stream
|
||||||
|
Positions: list[Position] = field(default_factory = list)
|
||||||
|
|
||||||
|
async def update(self) -> None:
|
||||||
|
self.Valkey = await self.Valkey.update()
|
||||||
|
|
||||||
|
### Collateral ###
|
||||||
|
@dataclass(kw_only=True)
|
||||||
|
class Asset:
|
||||||
|
symbol: str
|
||||||
|
balance: float
|
||||||
|
|
||||||
|
@dataclass(kw_only=True)
|
||||||
|
class Collateral:
|
||||||
|
Valkey: Valkey_Stream
|
||||||
|
# Last_Updated_Ts_Ms: int
|
||||||
|
# Last_Pulled_Ts_Ms: int
|
||||||
|
Assets: list[Asset] = field(default_factory = list)
|
||||||
|
|
||||||
|
async def update(self) -> None:
|
||||||
|
self.Valkey = await self.Valkey.update()
|
||||||
|
|
||||||
|
### Orders ###
|
||||||
|
@dataclass(kw_only=True)
|
||||||
|
class Order:
|
||||||
|
symbol: str
|
||||||
|
order_id: str
|
||||||
|
client_order_id: str
|
||||||
|
side: str
|
||||||
|
order_type: str
|
||||||
|
original_qty: float
|
||||||
|
original_price: float
|
||||||
|
order_status: str
|
||||||
|
last_filled_qty: float
|
||||||
|
last_filled_price: float
|
||||||
|
commission: float
|
||||||
|
trade_is_maker: bool
|
||||||
|
|
||||||
|
@dataclass(kw_only=True)
|
||||||
|
class Order_Updates:
|
||||||
|
# Last_Updated_Ts_Ms: int
|
||||||
|
# Last_Pulled_Ts_Ms: int
|
||||||
|
Valkey: Valkey_Stream
|
||||||
|
Orders: list[Order] = field(default_factory = list)
|
||||||
|
|
||||||
|
async def update(self) -> None:
|
||||||
|
self.Valkey = await self.Valkey.update()
|
||||||
|
|
||||||
|
### Funding Rate ###
|
||||||
|
@dataclass(kw_only=True)
|
||||||
|
class Funding_Rate:
|
||||||
|
# Last_Updated_Ts_Ms: int
|
||||||
|
# Last_Pulled_Ts_Ms: int
|
||||||
|
Valkey: Valkey_Stream
|
||||||
|
timestamp_arrival: int
|
||||||
|
timestamp_msg: int
|
||||||
|
symbol: str
|
||||||
|
funding_rate: float
|
||||||
|
next_funding_time_ts_ms: int
|
||||||
|
mark_price: float
|
||||||
|
index_price: float
|
||||||
|
estimated_settle_price: float
|
||||||
|
|
||||||
|
async def update(self) -> None:
|
||||||
|
self.Valkey = await self.Valkey.update()
|
||||||
|
|
||||||
|
### Exchanges ###
|
||||||
|
@dataclass(kw_only=True)
|
||||||
|
class Perpetual_Exchange:
|
||||||
|
Order_Updates: Order_Updates
|
||||||
|
Position_Updates: Open_Positions
|
||||||
|
Collateral: Collateral
|
||||||
|
Funding_Rate: Funding_Rate
|
||||||
|
mult: int
|
||||||
|
|
||||||
|
async def update(self):
|
||||||
|
await self.Collateral.update()
|
||||||
|
await self.Orders.update()
|
||||||
|
await self.Positions.update()
|
||||||
|
await self.Funding_Rate.update()
|
||||||
|
|
||||||
|
@dataclass(kw_only=True)
|
||||||
|
class Aster(Perpetual_Exchange):
|
||||||
|
name: str = 'Aster'
|
||||||
|
|
||||||
|
def __post_init__(self):
|
||||||
|
self.Collateral = Order_Updates(Valkey=Valkey_Stream(channel = 'fr_aster_user_balances', none_fills = []))
|
||||||
|
self.Orders = Collateral(Valkey=Valkey_Stream(channel = 'fr_aster_user_orders', none_fills = []))
|
||||||
|
self.Positions = Open_Positions(Valkey=Valkey_Stream(channel = 'fr_aster_user_positions', none_fills = []))
|
||||||
|
self.Funding_Rate - Funding_Rate(Valkey=Valkey_Stream(channel = 'fund_rate_aster', none_fills = None))
|
||||||
|
|
||||||
|
|
||||||
|
# @dataclass(kw_only=True)
|
||||||
|
# class Exchanges:
|
||||||
|
# Aster: Aster
|
||||||
|
# Extend: Perpetual_Exchange
|
||||||
|
|
||||||
|
# async def update(self):
|
||||||
|
# await self.Aster.update()
|
||||||
|
# await self.Extend.update()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### FLAGS ###
|
### FLAGS ###
|
||||||
|
@dataclass(kw_only=True)
|
||||||
|
class Flags:
|
||||||
LIQUIDATE_POS_AND_KILL_ALGO_FLAG: bool = False
|
LIQUIDATE_POS_AND_KILL_ALGO_FLAG: bool = False
|
||||||
NET_FUNDING_IS_ZERO: bool = False
|
NET_FUNDING_IS_ZERO: bool = False
|
||||||
|
# list = field(init=False)
|
||||||
|
Flags = Flags()
|
||||||
|
|
||||||
|
|
||||||
### UTILS ###
|
### UTILS ###
|
||||||
def round_decimal_down(value, decimal_places):
|
def round_decimal_down(value, decimal_places):
|
||||||
@@ -79,7 +220,7 @@ async def get_aster_open_orders():
|
|||||||
"method": "GET",
|
"method": "GET",
|
||||||
"params": {}
|
"params": {}
|
||||||
}
|
}
|
||||||
ASTER_OPEN_ORDERS = aster_auth.post_authenticated_url(fut_acct_openOrders)
|
ASTER_OPEN_ORDERS = await aster_auth.post_authenticated_url(fut_acct_openOrders)
|
||||||
|
|
||||||
async def get_extend_open_orders():
|
async def get_extend_open_orders():
|
||||||
global EXTEND_OPEN_ORDERS
|
global EXTEND_OPEN_ORDERS
|
||||||
@@ -95,23 +236,44 @@ async def get_aster_collateral():
|
|||||||
"method": "GET",
|
"method": "GET",
|
||||||
"params": {}
|
"params": {}
|
||||||
}
|
}
|
||||||
r = aster_auth.post_authenticated_url(fut_acct_balances)
|
r = await aster_auth.post_authenticated_url(fut_acct_balances)
|
||||||
ASTER_AVAIL_COLLATERAL = float([d for d in r if d.get('asset')==ASTER_RH_ASSET][0].get('availableBalance'))
|
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():
|
async def get_aster_notional_position(resp: dict | None = None):
|
||||||
global ASTER_NOTIONAL_POSITION
|
global ASTER_NOTIONAL_POSITION
|
||||||
global ASTER_MULT
|
global ASTER_MULT
|
||||||
|
|
||||||
|
if not resp:
|
||||||
fut_acct_positionRisk = {
|
fut_acct_positionRisk = {
|
||||||
"url": "/fapi/v3/positionRisk",
|
"url": "/fapi/v3/positionRisk",
|
||||||
"method": "GET",
|
"method": "GET",
|
||||||
"params": {}
|
"params": {
|
||||||
|
'symbol': ASTER_TICKER,
|
||||||
}
|
}
|
||||||
r = aster_auth.post_authenticated_url(fut_acct_positionRisk)
|
}
|
||||||
d = [d for d in r if d.get('symbol', None) == ASTER_TICKER][0]
|
resp = await aster_auth.post_authenticated_url(fut_acct_positionRisk)
|
||||||
|
|
||||||
ASTER_NOTIONAL_POSITION = float(d.get('notional' ,0))
|
d = [x for x in resp if x.get('symbol', None) == ASTER_TICKER][0]
|
||||||
ASTER_MULT = float(d.get('leverage', ASTER_MULT))
|
if len(d) < 1:
|
||||||
|
logging.info(f'BAD NOTIONAL - ASTER CHANGE: Empty d: {d}; resp: {resp}')
|
||||||
|
kill_algo()
|
||||||
|
|
||||||
|
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'])
|
||||||
|
else:
|
||||||
|
notional = 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) > 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}')
|
||||||
|
kill_algo()
|
||||||
|
if ASTER_NOTIONAL_POSITION != previous_notional_position:
|
||||||
|
logging.info(f'ASTER NOTIONAL CHANGE: {ASTER_NOTIONAL_POSITION:.2f}; UR PNL: {aster_unrealized_pnl:.2f}; MULT: {ASTER_MULT:.0f}; resp: {bool(resp)}')
|
||||||
|
|
||||||
async def get_extend_collateral():
|
async def get_extend_collateral():
|
||||||
global EXTEND_AVAIL_COLLATERAL
|
global EXTEND_AVAIL_COLLATERAL
|
||||||
@@ -119,18 +281,22 @@ async def get_extend_collateral():
|
|||||||
get_bals = dict(dict(await EXTEND_CLIENT.account.get_balance()).get('data', {}))
|
get_bals = dict(dict(await EXTEND_CLIENT.account.get_balance()).get('data', {}))
|
||||||
EXTEND_AVAIL_COLLATERAL = get_bals.get('available_for_trade', 0) if get_bals.get('collateral_name', None)==EXTEND_RH_ASSET else 0
|
EXTEND_AVAIL_COLLATERAL = get_bals.get('available_for_trade', 0) if get_bals.get('collateral_name', None)==EXTEND_RH_ASSET else 0
|
||||||
|
|
||||||
async def get_extend_notional():
|
async def get_extend_notional(resp: dict | None = None):
|
||||||
global EXTEND_NOTIONAL_POSITION
|
global EXTEND_NOTIONAL_POSITION
|
||||||
global EXTEND_MULT
|
global EXTEND_MULT
|
||||||
|
|
||||||
get_pos = dict(await EXTEND_CLIENT.account.get_positions()).get('data', {})
|
if not resp:
|
||||||
pos_dict = [d for d in get_pos if d.get('market') == EXTEND_TICKER]
|
resp = dict(await EXTEND_CLIENT.account.get_positions()).get('data', {})
|
||||||
if pos_dict:
|
|
||||||
|
pos_dict = [dict(d) for d in resp if dict(d).get('market') == EXTEND_TICKER]
|
||||||
pos_dict = pos_dict[0]
|
pos_dict = pos_dict[0]
|
||||||
EXTEND_NOTIONAL_POSITION = pos_dict.get('value', 0)
|
unrealized_pnl = pos_dict.get('unrealised_pnl', 0)
|
||||||
|
previous_notional_position = EXTEND_NOTIONAL_POSITION
|
||||||
|
EXTEND_NOTIONAL_POSITION = float(pos_dict.get('value', 0)) - float(unrealized_pnl)
|
||||||
EXTEND_MULT = pos_dict.get('leverage', EXTEND_MULT)
|
EXTEND_MULT = pos_dict.get('leverage', EXTEND_MULT)
|
||||||
else:
|
if EXTEND_NOTIONAL_POSITION != previous_notional_position:
|
||||||
EXTEND_NOTIONAL_POSITION = 0
|
logging.info(f'EXTEND NOTIONAL CHANGE: {EXTEND_NOTIONAL_POSITION:.2f}; UR PNL: {unrealized_pnl:.2f}; MULT: {EXTEND_MULT:.0f}; resp: {bool(resp)}')
|
||||||
|
|
||||||
|
|
||||||
### EXCHANGE INFO ###
|
### EXCHANGE INFO ###
|
||||||
async def get_aster_exch_info():
|
async def get_aster_exch_info():
|
||||||
@@ -141,7 +307,7 @@ async def get_aster_exch_info():
|
|||||||
"method": "GET",
|
"method": "GET",
|
||||||
"params": {}
|
"params": {}
|
||||||
}
|
}
|
||||||
r = aster_auth.post_authenticated_url(fut_acct_exchangeInfo)
|
r = await aster_auth.post_authenticated_url(fut_acct_exchangeInfo)
|
||||||
s = r['symbols']
|
s = r['symbols']
|
||||||
d = [d for d in s if d.get('symbol', None) == 'ETHUSDT'][0]
|
d = [d for d in s if d.get('symbol', None) == 'ETHUSDT'][0]
|
||||||
f = [f for f in d['filters'] if f.get('filterType', None) == 'LOT_SIZE'][0]
|
f = [f for f in d['filters'] if f.get('filterType', None) == 'LOT_SIZE'][0]
|
||||||
@@ -153,49 +319,163 @@ async def get_extend_exch_info():
|
|||||||
r = await EXTEND_CLIENT.markets_info.get_markets_dict()
|
r = await EXTEND_CLIENT.markets_info.get_markets_dict()
|
||||||
EXTEND_MIN_ORDER_QTY = float(r['ETH-USD'].trading_config.min_order_size)
|
EXTEND_MIN_ORDER_QTY = float(r['ETH-USD'].trading_config.min_order_size)
|
||||||
|
|
||||||
|
|
||||||
### CANCEL ORDERS ###
|
### CANCEL ORDERS ###
|
||||||
async def aster_cancel_all_orders():
|
async def aster_cancel_all_orders():
|
||||||
|
cancel_all_open_orders = {
|
||||||
|
"url": "/fapi/v3/allOpenOrders",
|
||||||
|
"method": "DELETE",
|
||||||
|
"params": {
|
||||||
|
'symbol': 'ETHUSDT',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r = await aster_auth.post_authenticated_url(cancel_all_open_orders)
|
||||||
|
logging.info(f'ASTER CANCEL ALL OPEN ORDERS RESP: {r}')
|
||||||
|
|
||||||
|
async def extend_cancel_all_orders():
|
||||||
|
r = await EXTEND_CLIENT.orders.mass_cancel(markets=[EXTEND_TICKER])
|
||||||
|
logging.info(f'EXTEND CANCEL ALL OPEN ORDERS RESP: {r}')
|
||||||
|
|
||||||
|
### KILL ALGO ###
|
||||||
|
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')
|
||||||
|
raise ValueError('KILL FLAG ACTIVATED')
|
||||||
|
|
||||||
### ROUTES ###
|
### ROUTES ###
|
||||||
async def aster_remainder_route():
|
# async def aster_remainder_route():
|
||||||
# Check open orders...cancel replace or new order?
|
# # Check open orders...cancel replace or new order?
|
||||||
# Check collateral to confirm you have enough money to trade
|
# # Check collateral to confirm you have enough money to trade
|
||||||
# if CR, what should be the new price? has it changed? maybe no action needed? how long has it been working?
|
# # if CR, what should be the new price? has it changed? maybe no action needed? how long has it been working?
|
||||||
# if not enough collateral then need to liquidate and kill algo - flip flag
|
# # if not enough collateral then need to liquidate and kill algo - flip flag
|
||||||
|
|
||||||
# if good to order, then create and post order. ADD to LOCAL OPEN ORDERS LIST
|
# # if good to order, then create and post order. ADD to LOCAL OPEN ORDERS LIST
|
||||||
|
|
||||||
pass
|
|
||||||
|
|
||||||
async def extend_remainder_route():
|
# pass
|
||||||
pass
|
|
||||||
|
# async def extend_remainder_route():
|
||||||
|
# pass
|
||||||
|
|
||||||
|
|
||||||
### ALGO LOOP ###
|
### ALGO LOOP ###
|
||||||
async def run_algo():
|
async def run_algo():
|
||||||
|
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
loop_start = time.time()
|
loop_start = time.time()
|
||||||
print('__________Start___________')
|
print('__________Start___________')
|
||||||
|
|
||||||
|
### Load Data from Feedhandlers ###
|
||||||
ASTER_FUND_RATE_DICT = json.loads(VAL_KEY.get('fund_rate_aster'))
|
ASTER_FUND_RATE_DICT = json.loads(VAL_KEY.get('fund_rate_aster'))
|
||||||
ASTER_TICKER_DICT = json.loads(VAL_KEY.get('fut_ticker_aster'))
|
|
||||||
# print(f'ASTER FUND RATE: {ASTER_FUND_RATE}')
|
|
||||||
# print(f'ASTER TICKER: {ASTER_TICKER}')
|
|
||||||
|
|
||||||
EXTENDED_FUND_RATE_DICT = json.loads(VAL_KEY.get('fund_rate_extended'))
|
EXTENDED_FUND_RATE_DICT = json.loads(VAL_KEY.get('fund_rate_extended'))
|
||||||
EXTENDED_TICKER_DICT = json.loads(VAL_KEY.get('fut_ticker_extended'))
|
|
||||||
# print(f'EXTENDED FUND RATE: {EXTENDED_FUND_RATE}')
|
|
||||||
# print(f'EXTENDED TICKER: {EXTENDED_TICKER}')
|
|
||||||
|
|
||||||
ASTER_FUND_RATE = float(ASTER_FUND_RATE_DICT.get('funding_rate', 0))
|
ASTER_FUND_RATE = float(ASTER_FUND_RATE_DICT.get('funding_rate', 0))
|
||||||
EXTEND_FUND_RATE = float(EXTENDED_FUND_RATE_DICT.get('funding_rate', 0))
|
EXTEND_FUND_RATE = float(EXTENDED_FUND_RATE_DICT.get('funding_rate', 0))
|
||||||
ASTER_FUND_RATE_TIME = float(ASTER_FUND_RATE_DICT.get('next_funding_time_ts_ms', 0))
|
ASTER_FUND_RATE_TIME = float(ASTER_FUND_RATE_DICT.get('next_funding_time_ts_ms', 0))
|
||||||
EXTEND_FUND_RATE_TIME = float(EXTENDED_FUND_RATE_DICT.get('next_funding_time_ts_ms', 0))
|
EXTEND_FUND_RATE_TIME = float(EXTENDED_FUND_RATE_DICT.get('next_funding_time_ts_ms', 0))
|
||||||
|
|
||||||
|
ASTER_TICKER_DICT = json.loads(VAL_KEY.get('fut_ticker_aster'))
|
||||||
|
EXTENDED_TICKER_DICT = json.loads(VAL_KEY.get('fut_ticker_extended'))
|
||||||
|
|
||||||
|
### Manage Local Collateral Using Updates from WS ###
|
||||||
|
ASTER_WS_COLLATERAL_UPDATES = VAL_KEY.get('fr_aster_user_positions')
|
||||||
|
ASTER_WS_COLLATERAL_UPDATES = json.loads(ASTER_WS_COLLATERAL_UPDATES) if ASTER_WS_COLLATERAL_UPDATES is not None else []
|
||||||
|
EXTEND_WS_COLLATERAL_UPDATES = VAL_KEY.get('fr_extended_user_positions')
|
||||||
|
EXTEND_WS_COLLATERAL_UPDATES = json.loads(EXTEND_WS_COLLATERAL_UPDATES) if EXTEND_WS_COLLATERAL_UPDATES is not None else []
|
||||||
|
|
||||||
|
### Manage Local Notionals Using Updates from WS ###
|
||||||
|
ASTER_WS_POS_UPDATES = VAL_KEY.get('fr_aster_user_positions')
|
||||||
|
ASTER_WS_POS_UPDATES = json.loads(ASTER_WS_POS_UPDATES) if ASTER_WS_POS_UPDATES is not None else []
|
||||||
|
EXTEND_WS_POS_UPDATES = VAL_KEY.get('fr_extended_user_positions')
|
||||||
|
EXTEND_WS_POS_UPDATES = json.loads(EXTEND_WS_POS_UPDATES) if EXTEND_WS_POS_UPDATES is not None else []
|
||||||
|
|
||||||
|
### Manage Local Orders Using Updates from WS ###
|
||||||
|
ASTER_WS_ORDER_UPDATES = VAL_KEY.get('fr_aster_user_orders')
|
||||||
|
ASTER_WS_ORDER_UPDATES = json.loads(ASTER_WS_ORDER_UPDATES) if ASTER_WS_ORDER_UPDATES is not None else []
|
||||||
|
EXTEND_WS_ORDER_UPDATES = VAL_KEY.get('fr_extended_user_orders')
|
||||||
|
EXTEND_WS_ORDER_UPDATES = json.loads(EXTEND_WS_ORDER_UPDATES) if EXTEND_WS_ORDER_UPDATES is not None else []
|
||||||
|
|
||||||
|
# CHECK NO MORE THAN 1 OPEN ORDER ON EITHER EXCHANGE #
|
||||||
|
if len(ASTER_OPEN_ORDERS) > 1 or len(EXTEND_OPEN_ORDERS) > 1:
|
||||||
|
logging.info(f'MORE THAN 1 ORDER OPEN - KILLING ALGO: ASTER_OPEN_ORDERS ({len(ASTER_OPEN_ORDERS)}): {ASTER_OPEN_ORDERS}; EXTEND_OPEN_ORDERS ({len(EXTEND_OPEN_ORDERS)}): {EXTEND_OPEN_ORDERS}')
|
||||||
|
await kill_algo()
|
||||||
|
raise ValueError('NOT HERE: MORE THAN 1 ORDER OPEN - KILLING ALGO: ASTER_OPEN_ORDERS')
|
||||||
|
|
||||||
|
### CHECK TIME TO FUNDING AND WHETHER TO BE ACTIVE ###
|
||||||
|
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}')
|
||||||
|
time.sleep(5)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if len(ASTER_WS_POS_UPDATES) > 0:
|
||||||
|
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.
|
||||||
|
|
||||||
|
if len(EXTEND_WS_POS_UPDATES) > 0:
|
||||||
|
await get_extend_notional(resp=EXTEND_WS_POS_UPDATES)
|
||||||
|
|
||||||
|
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.get('orderId')
|
||||||
|
order_orig_status = o['status']
|
||||||
|
order_update = [ou for ou in ASTER_WS_ORDER_UPDATES if ou.get('order_id', None) == order_id]
|
||||||
|
|
||||||
|
if len(order_update) > 0:
|
||||||
|
order_update = order_update[0]
|
||||||
|
order_update_status = order_update.get('status') if order_update.get('status') is not None else order_update.get('order_status')
|
||||||
|
order_status_changed = order_orig_status.upper() != order_update_status.upper()
|
||||||
|
|
||||||
|
if order_status_changed:
|
||||||
|
logging.info(f'ASTER ORDER ({order_id}): {order_orig_status} -> {order_update_status}')
|
||||||
|
ASTER_OPEN_ORDERS[idx] = order_update
|
||||||
|
if order_update_status in ['CANCELED','EXPIRED']:
|
||||||
|
logging.info(f'ASTER ORDER CANCELLED or EXPIRED: {order_id}')
|
||||||
|
ASTER_OPEN_ORDERS.pop(idx)
|
||||||
|
elif order_update_status in ['PARTIALLY_FILLED']:
|
||||||
|
logging.info(f'ASTER ORDER PARTIALLY FILLED: {order_id}')
|
||||||
|
await get_aster_collateral()
|
||||||
|
await get_aster_notional_position()
|
||||||
|
elif order_update_status in ['FILLED']:
|
||||||
|
logging.info(f'ASTER ORDER FILLED: {order_id}')
|
||||||
|
ASTER_OPEN_ORDERS.pop(idx)
|
||||||
|
await get_aster_collateral()
|
||||||
|
await get_aster_notional_position()
|
||||||
|
else:
|
||||||
|
logging.critical(f'EXTEND ORDER STATUS CHG TO UNEXPECTED VALUE, KILLING... ({order_id}): {order_orig_status} -> {order_update_status}')
|
||||||
|
if EXTEND_WS_ORDER_UPDATES is not None:
|
||||||
|
for idx, o in enumerate(EXTEND_OPEN_ORDERS):
|
||||||
|
o = dict(o)
|
||||||
|
order_id = o.get('order_id') if o.get('order_id') is not None else o.get('id')
|
||||||
|
order_orig_status = o['status']
|
||||||
|
order_update = [dict(ou) for ou in EXTEND_WS_ORDER_UPDATES if dict(ou).get('order_id', None) == order_id]
|
||||||
|
|
||||||
|
if len(order_update) > 0:
|
||||||
|
order_update = order_update[0]
|
||||||
|
order_update_status = order_update.get('status')
|
||||||
|
order_status_changed = order_orig_status.upper() != order_update_status.upper()
|
||||||
|
|
||||||
|
if order_status_changed:
|
||||||
|
logging.info(f'EXTEND ORDER ({order_id}): {order_orig_status} -> {order_update_status}')
|
||||||
|
EXTEND_OPEN_ORDERS[idx] = order_update
|
||||||
|
if order_update_status in ['CANCELLED','EXPIRED','REJECTED']:
|
||||||
|
logging.info(f'EXTEND ORDER CANCELLED or EXPIRED: {order_id}')
|
||||||
|
EXTEND_OPEN_ORDERS.pop(idx)
|
||||||
|
elif order_update_status in ['PARTIALLY_FILLED']:
|
||||||
|
logging.info(f'EXTEND ORDER PARTIALLY FILLED: {order_id}')
|
||||||
|
await get_extend_collateral()
|
||||||
|
await get_extend_notional()
|
||||||
|
elif order_update_status in ['FILLED']:
|
||||||
|
logging.info(f'EXTEND ORDER FILLED: {order_id}')
|
||||||
|
EXTEND_OPEN_ORDERS.pop(idx)
|
||||||
|
await get_extend_collateral()
|
||||||
|
await get_extend_notional()
|
||||||
|
else:
|
||||||
|
logging.critical(f'EXTEND ORDER STATUS CHG TO UNEXPECTED VALUE, KILLING... ({order_id}): {order_orig_status} -> {order_update_status}')
|
||||||
|
|
||||||
|
|
||||||
ASTER_PAYOUT_DIRECTION_STR = 'LONG PAYS SHORT' if ASTER_FUND_RATE > 0 else 'SHORT PAYS LONG'
|
ASTER_PAYOUT_DIRECTION_STR = 'LONG PAYS SHORT' if ASTER_FUND_RATE > 0 else 'SHORT PAYS LONG'
|
||||||
EXTEND_PAYOUT_DIRECTION_STR = 'LONG PAYS SHORT' if EXTEND_FUND_RATE > 0 else 'SHORT PAYS LONG'
|
EXTEND_PAYOUT_DIRECTION_STR = 'LONG PAYS SHORT' if EXTEND_FUND_RATE > 0 else 'SHORT PAYS LONG'
|
||||||
|
|
||||||
@@ -226,7 +506,10 @@ async def run_algo():
|
|||||||
return EXTEND_FUND_RATE
|
return EXTEND_FUND_RATE
|
||||||
|
|
||||||
NEXT_NET_FUNDING_RATE = calc_next_net_fund_rate(FUNDINGS_AT_SAME_TIME_NEXT_HR)
|
NEXT_NET_FUNDING_RATE = calc_next_net_fund_rate(FUNDINGS_AT_SAME_TIME_NEXT_HR)
|
||||||
NET_FUNDING_IS_ZERO = NEXT_NET_FUNDING_RATE == 0.00
|
Flags.NET_FUNDING_IS_ZERO = NEXT_NET_FUNDING_RATE == 0.00
|
||||||
|
if Flags.NET_FUNDING_IS_ZERO:
|
||||||
|
logging.info('NET FUNDING = 0.00; Flattening Open Positions; Wait Until Non-Zero.')
|
||||||
|
ALPHA_TGT_NOTIONAL = 0.00
|
||||||
|
|
||||||
if ALPHA_EXCH == 'EXTEND':
|
if ALPHA_EXCH == 'EXTEND':
|
||||||
ASTER_TGT_NOTIONAL = ALPHA_TGT_NOTIONAL*-1
|
ASTER_TGT_NOTIONAL = ALPHA_TGT_NOTIONAL*-1
|
||||||
@@ -251,8 +534,8 @@ async def run_algo():
|
|||||||
ASTER_TGT_TAIL = ASTER_TGT_NOTIONAL - ASTER_NOTIONAL_POSITION
|
ASTER_TGT_TAIL = ASTER_TGT_NOTIONAL - ASTER_NOTIONAL_POSITION
|
||||||
EXTEND_TGT_TAIL = EXTEND_TGT_NOTIONAL - EXTEND_NOTIONAL_POSITION
|
EXTEND_TGT_TAIL = EXTEND_TGT_NOTIONAL - EXTEND_NOTIONAL_POSITION
|
||||||
|
|
||||||
ASTER_TGT_TAIL_BASE_QTY = Decimal(str(ASTER_TGT_TAIL / ASTER_TOB_PX)).quantize(Decimal(str(0.001)), rounding=ROUND_DOWN)
|
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(EXTEND_TGT_TAIL / EXTEND_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)
|
||||||
|
|
||||||
ASTER_TGT_TAIL_ORDERABLE = abs(ASTER_TGT_TAIL_BASE_QTY) >= ASTER_MIN_ORDER_QTY
|
ASTER_TGT_TAIL_ORDERABLE = abs(ASTER_TGT_TAIL_BASE_QTY) >= ASTER_MIN_ORDER_QTY
|
||||||
EXTEND_TGT_TAIL_ORDERABLE = abs(EXTEND_TGT_TAIL_BASE_QTY) >= EXTEND_MIN_ORDER_QTY
|
EXTEND_TGT_TAIL_ORDERABLE = abs(EXTEND_TGT_TAIL_BASE_QTY) >= EXTEND_MIN_ORDER_QTY
|
||||||
@@ -265,47 +548,153 @@ async def run_algo():
|
|||||||
ASTER: [ Notional Position $ : {ASTER_NOTIONAL_POSITION:.4f} ] | EXTEND: [ Notional Position $ : {EXTEND_NOTIONAL_POSITION:.4f} ]
|
ASTER: [ Notional Position $ : {ASTER_NOTIONAL_POSITION:.4f} ] | EXTEND: [ Notional Position $ : {EXTEND_NOTIONAL_POSITION:.4f} ]
|
||||||
|
|
||||||
SAME TIME? : {FUNDINGS_AT_SAME_TIME_NEXT_HR} [ Minutes Between Fundings: {min_between_fundings} ]
|
SAME TIME? : {FUNDINGS_AT_SAME_TIME_NEXT_HR} [ Minutes Between Fundings: {min_between_fundings} ]
|
||||||
NET FUNDING : {NEXT_NET_FUNDING_RATE:.6%} [{NEXT_NET_FUNDING_RATE*10_000:.2f}bps] [{NEXT_NET_FUNDING_RATE*1_000_000:.0f}pips]; Is Zero?: {NET_FUNDING_IS_ZERO}
|
NET FUNDING : {NEXT_NET_FUNDING_RATE:.6%} [{NEXT_NET_FUNDING_RATE*10_000:.2f}bps] [{NEXT_NET_FUNDING_RATE*1_000_000:.0f}pips]; Is Zero?: {Flags.NET_FUNDING_IS_ZERO}
|
||||||
ALPHA SIDE : {ALPHA_EXCH} [{ALPHA_CARRY_SIDE}]
|
ALPHA SIDE : {ALPHA_EXCH} [{ALPHA_CARRY_SIDE}]
|
||||||
|
|
||||||
TGT NOTIONAL: $ {MAX_TARGET_NOTIONAL}
|
TGT NOTIONAL: $ {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} ]
|
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_TAIL_BASE_QTY:.4f} > {ASTER_MIN_ORDER_QTY:.4f} min [ Order: {ASTER_TGT_TAIL_ORDERABLE} ] | EXTEND: {EXTEND_TGT_TAIL_BASE_QTY:.4f} > {EXTEND_MIN_ORDER_QTY:.4f} min [ Order: {EXTEND_TGT_TAIL_ORDERABLE} ]
|
ASTER: {ASTER_TGT_TAIL_BASE_QTY:.4f} > {ASTER_MIN_ORDER_QTY:.4f} min [ Order: {ASTER_TGT_TAIL_ORDERABLE} ] | EXTEND: {EXTEND_TGT_TAIL_BASE_QTY:.4f} > {EXTEND_MIN_ORDER_QTY:.4f} min [ Order: {EXTEND_TGT_TAIL_ORDERABLE} ]
|
||||||
|
|
||||||
|
--- ASTER OPEN ORDERS ---
|
||||||
|
{ASTER_OPEN_ORDERS}
|
||||||
|
|
||||||
|
--- EXTEND OPEN ORDERS ---
|
||||||
|
{EXTEND_OPEN_ORDERS}
|
||||||
''')
|
''')
|
||||||
|
|
||||||
### SCAN VALKEY USER FEEDS FOR BALANCE UPDATES ###
|
|
||||||
# or just to begin hit the rest API before ordering and update bals then
|
|
||||||
|
|
||||||
|
|
||||||
### SCAN VALKEY USER FEEDS FOR ORDER UPDATES ###
|
|
||||||
|
|
||||||
|
|
||||||
### ROUTES ###
|
### ROUTES ###
|
||||||
if NET_FUNDING_IS_ZERO:
|
# ASTER
|
||||||
logging.info('NET FUNDING = 0.00; Cancelling Open Order and Flattening Open Positions; Wait Until Non-Zero.')
|
if ASTER_TGT_TAIL_ORDERABLE and ASTER_ALLOW_ORDERING:
|
||||||
### ZERO NET FUNDING - CXL OPEN ORDERS, CLOSE POSITIONS, and WAIT
|
symbol = ASTER_TICKER
|
||||||
|
side = 'BUY' if ASTER_TGT_TAIL_BASE_QTY > 0.00 else 'SELL'
|
||||||
|
qty = str(abs(ASTER_TGT_TAIL_BASE_QTY))
|
||||||
|
price = ASTER_TOB_PX - PRICE_WORSENER_ASTER if side == 'BUY' else ASTER_TOB_PX + PRICE_WORSENER_ASTER
|
||||||
|
|
||||||
|
if abs(qty*price) + abs(ASTER_NOTIONAL_POSITION) > MAX_TARGET_NOTIONAL*1.01:
|
||||||
|
logging.info(f'TRYING TO ORDER OVER MAX NOTIOANL - ASTER: {ASTER_NOTIONAL_POSITION} + {qty*price} (qty: {qty}; px: {price})')
|
||||||
|
# await aster_remainder_route()
|
||||||
|
if ASTER_OPEN_ORDERS:
|
||||||
|
open_order_id = ASTER_OPEN_ORDERS[0].get('order_id') if ASTER_OPEN_ORDERS[0].get('order_id') is not None else ASTER_OPEN_ORDERS[0]['orderId']
|
||||||
|
open_order_px = float(ASTER_OPEN_ORDERS[0].get('price')) if ASTER_OPEN_ORDERS[0].get('price') is not None else float(ASTER_OPEN_ORDERS[0]['original_price'])
|
||||||
|
if round(open_order_px - float(price), 2) == 0.00:
|
||||||
|
logging.info('ASTER OPEN ORDER NO PX CHG; SKIPPING')
|
||||||
|
place_order = False
|
||||||
else:
|
else:
|
||||||
if ASTER_TGT_TAIL_ORDERABLE:
|
cancel_order = {
|
||||||
await aster_remainder_route()
|
"url": "/fapi/v3/order",
|
||||||
|
"method": "DELETE",
|
||||||
|
"params": {
|
||||||
|
'symbol': ASTER_TICKER,
|
||||||
|
'orderId': open_order_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cr = await aster_auth.post_authenticated_url(cancel_order)
|
||||||
|
if cr.get('status', None) == 'CANCELED':
|
||||||
|
ASTER_OPEN_ORDERS.pop(0)
|
||||||
|
place_order = True
|
||||||
|
else:
|
||||||
|
logging.warning(f'ASTER ORDER FAILED TO CANCEL DURING CR ({open_order_id}): RESP {cr}')
|
||||||
|
place_order = False
|
||||||
|
else:
|
||||||
|
place_order = True
|
||||||
|
|
||||||
|
if ASTER_TGT_TAIL_BASE_QTY == 0.00:
|
||||||
|
place_order = False
|
||||||
|
logging.info('ASTER TRYNG TO ORDER 0.00 BASE QTY, SKIPPING')
|
||||||
|
|
||||||
|
if place_order:
|
||||||
|
price = Decimal(str(price)).quantize(Decimal(str(0.01)), rounding=ROUND_DOWN)
|
||||||
|
post_order = {
|
||||||
|
"url": "/fapi/v3/order",
|
||||||
|
"method": "POST",
|
||||||
|
"params": {
|
||||||
|
'symbol': symbol,
|
||||||
|
'side': side,
|
||||||
|
'type': 'LIMIT',
|
||||||
|
'timeInForce': 'GTC',
|
||||||
|
'quantity': qty,
|
||||||
|
'price': price,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
order_resp = await aster_auth.post_authenticated_url(post_order)
|
||||||
|
if order_resp.get('orderId', None) is not None:
|
||||||
|
order_resp['original_price'] = price
|
||||||
|
ASTER_OPEN_ORDERS.append(order_resp)
|
||||||
|
utils.send_tg_alert(f'FR_ALGO - ASTER Order. Start_$: {ASTER_NOTIONAL_POSITION:.2f}; Value: {abs(qty*price):.2f}; Price: {str(price):.2f}')
|
||||||
|
logging.info(f'ASTER ORDER PLACED SUCCESS: {order_resp}')
|
||||||
|
else:
|
||||||
|
logging.warning('ASTER PLACE ORDER CHECKS FAILED, SKIPPING')
|
||||||
|
|
||||||
elif not(ASTER_TGT_TAIL_ORDERABLE) and ASTER_OPEN_ORDERS:
|
elif not(ASTER_TGT_TAIL_ORDERABLE) and ASTER_OPEN_ORDERS:
|
||||||
logging.info('ASTER HAS NO TAIL BUT OPEN ORDERS - CANCELLING OPEN ORDERS')
|
logging.info('ASTER HAS NO TAIL BUT OPEN ORDERS - CANCELLING OPEN ORDERS')
|
||||||
|
await aster_cancel_all_orders()
|
||||||
|
|
||||||
pass
|
# EXTEND
|
||||||
if EXTEND_TGT_TAIL_ORDERABLE:
|
if EXTEND_TGT_TAIL_ORDERABLE and EXTEND_ALLOW_ORDERING:
|
||||||
await extend_remainder_route()
|
symbol = EXTEND_TICKER
|
||||||
|
side = OrderSide.BUY if EXTEND_TGT_TAIL_BASE_QTY > 0.00 else OrderSide.SELL
|
||||||
|
qty = Decimal(str(abs(EXTEND_TGT_TAIL_BASE_QTY)))
|
||||||
|
price = EXTEND_TOB_PX - PRICE_WORSENER_EXTEND if side == 'BUY' else EXTEND_TOB_PX + PRICE_WORSENER_EXTEND
|
||||||
|
|
||||||
|
if abs(qty*price) + abs(EXTEND_NOTIONAL_POSITION) > MAX_TARGET_NOTIONAL*1.01:
|
||||||
|
logging.info(f'TRYING TO ORDER OVER MAX NOTIOANL - EXTEND: {EXTEND_NOTIONAL_POSITION} + {qty*price} (qty: {qty}; px: {price})')
|
||||||
|
# await extend_remainder_route()
|
||||||
|
if EXTEND_OPEN_ORDERS:
|
||||||
|
open_order_dict = dict(EXTEND_OPEN_ORDERS[0])
|
||||||
|
open_order_id = open_order_dict['external_id']
|
||||||
|
open_order_px = float(open_order_dict['price'])
|
||||||
|
place_order = True
|
||||||
|
else:
|
||||||
|
open_order_id = None
|
||||||
|
open_order_px = 0
|
||||||
|
place_order = True
|
||||||
|
if place_order:
|
||||||
|
price = Decimal(str(price)).quantize(Decimal(str(0.01)), rounding=ROUND_DOWN)
|
||||||
|
if round(open_order_px - float(price), 2) == 0.00:
|
||||||
|
logging.info('EXTEND OPEN ORDER NO PX CHG; SKIPPING')
|
||||||
|
else:
|
||||||
|
order_resp = await EXTEND_CLIENT.place_order(
|
||||||
|
market_name=symbol,
|
||||||
|
amount_of_synthetic=qty,
|
||||||
|
price=price,
|
||||||
|
side=side,
|
||||||
|
taker_fee=Decimal("0.00025"),
|
||||||
|
previous_order_id=open_order_id,
|
||||||
|
)
|
||||||
|
order_resp_dict = dict(order_resp)
|
||||||
|
if order_resp_dict.get('status', None) == 'OK':
|
||||||
|
if EXTEND_OPEN_ORDERS:
|
||||||
|
EXTEND_OPEN_ORDERS.pop(0)
|
||||||
|
|
||||||
|
order_dict = dict(order_resp_dict['data'])
|
||||||
|
order_dict['status'] = 'NEW'
|
||||||
|
order_dict['price'] = str(price)
|
||||||
|
|
||||||
|
EXTEND_OPEN_ORDERS.append(order_dict)
|
||||||
|
utils.send_tg_alert(f'FR_ALGO - EXTEND Order. Start_$: {EXTEND_NOTIONAL_POSITION:.2f}; Value: {abs(qty*price):.2f}; Price: {str(price):.2f}')
|
||||||
|
logging.info(f'EXTEND ORDER PLACED SUCCESS: {order_dict}')
|
||||||
|
else:
|
||||||
|
logging.warning('EXTEND PLACE ORDER CHECKS FAILED, SKIPPING')
|
||||||
|
|
||||||
|
elif not(EXTEND_TGT_TAIL_ORDERABLE) and EXTEND_OPEN_ORDERS:
|
||||||
|
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})')
|
||||||
time.sleep(5)
|
|
||||||
|
time.sleep(LOOP_SLEEP_SEC)
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print('...algo stopped')
|
logging.info('CANCELLING OPEN ORDERS')
|
||||||
# await cancel_all_orders(CLIENT=CLIENT)
|
await kill_algo()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.critical(f'*** ALGO ENGINE CRASHED: {e}')
|
|
||||||
logging.error(traceback.format_exc())
|
logging.error(traceback.format_exc())
|
||||||
# await cancel_all_orders(CLIENT=CLIENT)
|
logging.critical(f'*** ALGO ENGINE CRASHED: {e}')
|
||||||
|
logging.info('CANCELLING OPEN ORDERS')
|
||||||
|
utils.send_tg_alert(f'FR_ALGO_CRASHED: {str(e)}')
|
||||||
|
await kill_algo()
|
||||||
|
|
||||||
|
|
||||||
### MAIN STARTUP ###
|
### MAIN STARTUP ###
|
||||||
|
|||||||
Binary file not shown.
BIN
modules/__pycache__/utils.cpython-313.pyc
Normal file
BIN
modules/__pycache__/utils.cpython-313.pyc
Normal file
Binary file not shown.
@@ -17,7 +17,7 @@ private_key = os.getenv("ASTER_API_PRIVATE_KEY")
|
|||||||
_last_ms = 0
|
_last_ms = 0
|
||||||
_i = 0
|
_i = 0
|
||||||
|
|
||||||
def post_authenticated_url(req: dict) -> dict:
|
async def post_authenticated_url(req: dict) -> dict:
|
||||||
typed_data = {
|
typed_data = {
|
||||||
"types": {
|
"types": {
|
||||||
"EIP712Domain": [
|
"EIP712Domain": [
|
||||||
@@ -71,7 +71,7 @@ def post_authenticated_url(req: dict) -> dict:
|
|||||||
)
|
)
|
||||||
return Account.sign_message(message, private_key=private_key)
|
return Account.sign_message(message, private_key=private_key)
|
||||||
|
|
||||||
def send_by_url(req):
|
async def send_by_url(req):
|
||||||
my_dict = req['params'].copy()
|
my_dict = req['params'].copy()
|
||||||
url = host + req['url']
|
url = host + req['url']
|
||||||
method = req['method']
|
method = req['method']
|
||||||
@@ -105,4 +105,4 @@ def post_authenticated_url(req: dict) -> dict:
|
|||||||
# print(res.status_code, res.text)
|
# print(res.status_code, res.text)
|
||||||
return res.json()
|
return res.json()
|
||||||
|
|
||||||
return send_by_url(req=req)
|
return await send_by_url(req=req)
|
||||||
|
|||||||
28
modules/utils.py
Normal file
28
modules/utils.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import logging
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
import requests
|
||||||
|
import os
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
def upsert_list_of_dicts_by_id(list_of_dicts, new_dict, id='id', seq_check_field: str | None = None):
|
||||||
|
for index, item in enumerate(list_of_dicts):
|
||||||
|
if item.get(id) == new_dict.get(id):
|
||||||
|
if seq_check_field is not None:
|
||||||
|
if item.get(seq_check_field) > new_dict.get(seq_check_field):
|
||||||
|
logging.info('Skipping out of sequence msg')
|
||||||
|
return list_of_dicts
|
||||||
|
list_of_dicts[index] = new_dict
|
||||||
|
return list_of_dicts
|
||||||
|
|
||||||
|
list_of_dicts.append(new_dict)
|
||||||
|
return list_of_dicts
|
||||||
|
|
||||||
|
def send_tg_alert(msg: str):
|
||||||
|
token = os.getenv("TG_TOKEN")
|
||||||
|
chat_id = os.getenv("TG_ALERTS_CHAT_ID")
|
||||||
|
|
||||||
|
url = f'https://api.telegram.org/bot{token}/sendMessage?chat_id={chat_id}'
|
||||||
|
response = requests.post(url, json={'text': str(str(msg)[:250])}, timeout=10)
|
||||||
|
|
||||||
|
return response.json()
|
||||||
@@ -18,6 +18,7 @@ from dotenv import load_dotenv
|
|||||||
import modules.aster_auth as aster_auth
|
import modules.aster_auth as aster_auth
|
||||||
import modules.aster_db as aster_db
|
import modules.aster_db as aster_db
|
||||||
import modules.db as db
|
import modules.db as db
|
||||||
|
import modules.utils as utils
|
||||||
|
|
||||||
### Allow only ipv4 ###
|
### Allow only ipv4 ###
|
||||||
def allowed_gai_family():
|
def allowed_gai_family():
|
||||||
@@ -53,17 +54,7 @@ LOCAL_RECENT_BALANCES: list = []
|
|||||||
LOCAL_RECENT_POSITIONS: list = []
|
LOCAL_RECENT_POSITIONS: list = []
|
||||||
|
|
||||||
|
|
||||||
def upsert_list_of_dicts_by_id(list_of_dicts, new_dict, id='id'):
|
async def get_new_listen_key() -> str:
|
||||||
for index, item in enumerate(list_of_dicts):
|
|
||||||
if item.get(id) == new_dict.get(id):
|
|
||||||
list_of_dicts[index] = new_dict
|
|
||||||
return list_of_dicts
|
|
||||||
|
|
||||||
list_of_dicts.append(new_dict)
|
|
||||||
return list_of_dicts
|
|
||||||
|
|
||||||
|
|
||||||
def get_new_listen_key() -> str:
|
|
||||||
global LISTEN_KEY_LAST_UPDATE_TS_S
|
global LISTEN_KEY_LAST_UPDATE_TS_S
|
||||||
|
|
||||||
listen_key_request = {
|
listen_key_request = {
|
||||||
@@ -71,7 +62,7 @@ def get_new_listen_key() -> str:
|
|||||||
"method": "POST",
|
"method": "POST",
|
||||||
"params": {}
|
"params": {}
|
||||||
}
|
}
|
||||||
r = aster_auth.post_authenticated_url(listen_key_request)
|
r = await aster_auth.post_authenticated_url(listen_key_request)
|
||||||
listen_key = r.get('listenKey', None)
|
listen_key = r.get('listenKey', None)
|
||||||
print(f'LISTEN KEY: {listen_key}')
|
print(f'LISTEN KEY: {listen_key}')
|
||||||
if listen_key is not None:
|
if listen_key is not None:
|
||||||
@@ -85,7 +76,7 @@ async def listen_key_interval():
|
|||||||
|
|
||||||
while True:
|
while True:
|
||||||
await asyncio.sleep(LISTEN_KEY_PUT_INTERVAL_SEC)
|
await asyncio.sleep(LISTEN_KEY_PUT_INTERVAL_SEC)
|
||||||
LISTEN_KEY = get_new_listen_key()
|
LISTEN_KEY = await get_new_listen_key()
|
||||||
|
|
||||||
### Websocket ###
|
### Websocket ###
|
||||||
async def ws_stream():
|
async def ws_stream():
|
||||||
@@ -95,7 +86,7 @@ async def ws_stream():
|
|||||||
global LOCAL_RECENT_BALANCES
|
global LOCAL_RECENT_BALANCES
|
||||||
global LOCAL_RECENT_POSITIONS
|
global LOCAL_RECENT_POSITIONS
|
||||||
|
|
||||||
LISTEN_KEY = get_new_listen_key()
|
LISTEN_KEY = await get_new_listen_key()
|
||||||
|
|
||||||
async for websocket in websockets.connect(WSS_URL+LISTEN_KEY):
|
async for websocket in websockets.connect(WSS_URL+LISTEN_KEY):
|
||||||
logging.info(f"Connected to {WSS_URL}")
|
logging.info(f"Connected to {WSS_URL}")
|
||||||
@@ -113,7 +104,7 @@ async def ws_stream():
|
|||||||
|
|
||||||
match channel:
|
match channel:
|
||||||
case 'ORDER_TRADE_UPDATE':
|
case 'ORDER_TRADE_UPDATE':
|
||||||
logging.info(f'ORDER_TRADE_UPDATE: {data}')
|
# logging.info(f'ORDER_TRADE_UPDATE: {data}')
|
||||||
new_order_update = {
|
new_order_update = {
|
||||||
'timestamp_arrival': ts_arrival,
|
'timestamp_arrival': ts_arrival,
|
||||||
'timestamp_msg': data['E'],
|
'timestamp_msg': data['E'],
|
||||||
@@ -150,7 +141,7 @@ async def ws_stream():
|
|||||||
'callback_rate': float(data['o'].get("cr", 0)), # :"5.0", // Callback Rate, only puhed with TRAILING_STOP_MARKET order
|
'callback_rate': float(data['o'].get("cr", 0)), # :"5.0", // Callback Rate, only puhed with TRAILING_STOP_MARKET order
|
||||||
'realized_profit': float(data['o']["rp"]), # :"0" // Realized Profit of the trade
|
'realized_profit': float(data['o']["rp"]), # :"0" // Realized Profit of the trade
|
||||||
}
|
}
|
||||||
LOCAL_RECENT_ORDERS = upsert_list_of_dicts_by_id(LOCAL_RECENT_ORDERS, new_order_update, id='client_order_id')
|
LOCAL_RECENT_ORDERS = utils.upsert_list_of_dicts_by_id(LOCAL_RECENT_ORDERS, new_order_update, id='order_id', seq_check_field='timestamp_msg')
|
||||||
LOCAL_RECENT_ORDERS = [t for t in LOCAL_RECENT_ORDERS if t.get('timestamp_arrival', 0) >= LOOKBACK_MIN_TS_MS]
|
LOCAL_RECENT_ORDERS = [t for t in LOCAL_RECENT_ORDERS if t.get('timestamp_arrival', 0) >= LOOKBACK_MIN_TS_MS]
|
||||||
|
|
||||||
VAL_KEY_OBJ = json.dumps(LOCAL_RECENT_ORDERS)
|
VAL_KEY_OBJ = json.dumps(LOCAL_RECENT_ORDERS)
|
||||||
@@ -159,7 +150,7 @@ async def ws_stream():
|
|||||||
await db.insert_df_to_mysql(table_name='fr_aster_user_order_trade', params=new_order_update, CON=CON)
|
await db.insert_df_to_mysql(table_name='fr_aster_user_order_trade', params=new_order_update, CON=CON)
|
||||||
continue
|
continue
|
||||||
case 'MARGIN_CALL':
|
case 'MARGIN_CALL':
|
||||||
logging.info(f'MARGIN_CALL: {data}')
|
# logging.info(f'MARGIN_CALL: {data}')
|
||||||
list_for_df = []
|
list_for_df = []
|
||||||
for p in list(data['p']):
|
for p in list(data['p']):
|
||||||
margin_call_update = {
|
margin_call_update = {
|
||||||
@@ -177,7 +168,7 @@ async def ws_stream():
|
|||||||
'maint_margin_required': float(p["mm"]), # :"1.614445" // Maintenance Margin Required
|
'maint_margin_required': float(p["mm"]), # :"1.614445" // Maintenance Margin Required
|
||||||
}
|
}
|
||||||
list_for_df.append(margin_call_update)
|
list_for_df.append(margin_call_update)
|
||||||
LOCAL_RECENT_MARGIN_CALLS = upsert_list_of_dicts_by_id(LOCAL_RECENT_MARGIN_CALLS, margin_call_update, id='symbol')
|
LOCAL_RECENT_MARGIN_CALLS = utils.upsert_list_of_dicts_by_id(LOCAL_RECENT_MARGIN_CALLS, margin_call_update, id='symbol', seq_check_field='timestamp_msg')
|
||||||
LOCAL_RECENT_MARGIN_CALLS = [t for t in LOCAL_RECENT_MARGIN_CALLS if t.get('timestamp_arrival', 0) >= LOOKBACK_MIN_TS_MS]
|
LOCAL_RECENT_MARGIN_CALLS = [t for t in LOCAL_RECENT_MARGIN_CALLS if t.get('timestamp_arrival', 0) >= LOOKBACK_MIN_TS_MS]
|
||||||
|
|
||||||
VAL_KEY_OBJ = json.dumps(LOCAL_RECENT_MARGIN_CALLS)
|
VAL_KEY_OBJ = json.dumps(LOCAL_RECENT_MARGIN_CALLS)
|
||||||
@@ -186,7 +177,7 @@ async def ws_stream():
|
|||||||
await db.insert_df_to_mysql(table_name='fr_aster_user_margin', params=list_for_df, CON=CON)
|
await db.insert_df_to_mysql(table_name='fr_aster_user_margin', params=list_for_df, CON=CON)
|
||||||
continue
|
continue
|
||||||
case 'ACCOUNT_UPDATE':
|
case 'ACCOUNT_UPDATE':
|
||||||
logging.info(f'ACCOUNT_UPDATE: {data}')
|
# logging.info(f'ACCOUNT_UPDATE: {data}')
|
||||||
list_for_df_bal = []
|
list_for_df_bal = []
|
||||||
list_for_df_pos = []
|
list_for_df_pos = []
|
||||||
### Balance Updates ###
|
### Balance Updates ###
|
||||||
@@ -205,7 +196,7 @@ async def ws_stream():
|
|||||||
'balance_change_excl_pnl_comms': float(b['bc']),
|
'balance_change_excl_pnl_comms': float(b['bc']),
|
||||||
}
|
}
|
||||||
list_for_df_bal.append(balance_update)
|
list_for_df_bal.append(balance_update)
|
||||||
LOCAL_RECENT_BALANCES = upsert_list_of_dicts_by_id(LOCAL_RECENT_BALANCES, balance_update, id='asset')
|
LOCAL_RECENT_BALANCES = utils.upsert_list_of_dicts_by_id(LOCAL_RECENT_BALANCES, balance_update, id='asset', seq_check_field='timestamp_msg')
|
||||||
LOCAL_RECENT_BALANCES = [t for t in LOCAL_RECENT_BALANCES if t.get('timestamp_arrival', 0) >= LOOKBACK_MIN_TS_MS]
|
LOCAL_RECENT_BALANCES = [t for t in LOCAL_RECENT_BALANCES if t.get('timestamp_arrival', 0) >= LOOKBACK_MIN_TS_MS]
|
||||||
VAL_KEY.set(VK_BALANCES, json.dumps(LOCAL_RECENT_BALANCES))
|
VAL_KEY.set(VK_BALANCES, json.dumps(LOCAL_RECENT_BALANCES))
|
||||||
### Position Updates ###
|
### Position Updates ###
|
||||||
@@ -228,13 +219,13 @@ async def ws_stream():
|
|||||||
'position_side': p['ps'],
|
'position_side': p['ps'],
|
||||||
}
|
}
|
||||||
list_for_df_pos.append(position_update)
|
list_for_df_pos.append(position_update)
|
||||||
LOCAL_RECENT_POSITIONS = upsert_list_of_dicts_by_id(LOCAL_RECENT_POSITIONS, position_update, id='symbol')
|
LOCAL_RECENT_POSITIONS = utils.upsert_list_of_dicts_by_id(LOCAL_RECENT_POSITIONS, position_update, id='symbol', seq_check_field='timestamp_msg')
|
||||||
LOCAL_RECENT_POSITIONS = [t for t in LOCAL_RECENT_POSITIONS if t.get('timestamp_arrival', 0) >= LOOKBACK_MIN_TS_MS]
|
LOCAL_RECENT_POSITIONS = [t for t in LOCAL_RECENT_POSITIONS if t.get('timestamp_arrival', 0) >= LOOKBACK_MIN_TS_MS]
|
||||||
VAL_KEY.set(VK_POSITIONS, json.dumps(LOCAL_RECENT_POSITIONS))
|
VAL_KEY.set(VK_POSITIONS, json.dumps(LOCAL_RECENT_POSITIONS))
|
||||||
if balance_update:
|
if balance_update:
|
||||||
await db.insert_df_to_mysql(table_name='fr_aster_user_account_bal', params=list_for_df_bal, CON=CON)
|
await db.insert_df_to_mysql(table_name='fr_aster_user_account_bal', params=list_for_df_bal, CON=CON)
|
||||||
if position_update:
|
if position_update:
|
||||||
await db.insert_df_to_mysql(table_name='fr_aster_user_account_pos', params=list_for_df_bal, CON=CON)
|
await db.insert_df_to_mysql(table_name='fr_aster_user_account_pos', params=list_for_df_pos, CON=CON)
|
||||||
continue
|
continue
|
||||||
case 'listenKeyExpired':
|
case 'listenKeyExpired':
|
||||||
raise('Listen Key Has Expired; Failed to Update Properly. Restarting.')
|
raise('Listen Key Has Expired; Failed to Update Properly. Restarting.')
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import os
|
|||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
import modules.extended_db as extended_db
|
import modules.extended_db as extended_db
|
||||||
import modules.db as db
|
import modules.db as db
|
||||||
|
import modules.utils as utils
|
||||||
|
|
||||||
### Allow only ipv4 ###
|
### Allow only ipv4 ###
|
||||||
def allowed_gai_family():
|
def allowed_gai_family():
|
||||||
@@ -49,19 +50,6 @@ LOCAL_RECENT_TRADES: list = []
|
|||||||
LOCAL_RECENT_BALANCES: list = []
|
LOCAL_RECENT_BALANCES: list = []
|
||||||
LOCAL_RECENT_POSITIONS: list = []
|
LOCAL_RECENT_POSITIONS: list = []
|
||||||
|
|
||||||
def upsert_list_of_dicts_by_id(list_of_dicts, new_dict, id='id', seq_check_field: str | None = None):
|
|
||||||
for index, item in enumerate(list_of_dicts):
|
|
||||||
if item.get(id) == new_dict.get(id):
|
|
||||||
if seq_check_field is not None:
|
|
||||||
if item.get(seq_check_field) < new_dict.get(seq_check_field):
|
|
||||||
logging.info('Skipping out of sequence msg')
|
|
||||||
return list_of_dicts
|
|
||||||
list_of_dicts[index] = new_dict
|
|
||||||
return list_of_dicts
|
|
||||||
|
|
||||||
list_of_dicts.append(new_dict)
|
|
||||||
return list_of_dicts
|
|
||||||
|
|
||||||
### Websocket ###
|
### Websocket ###
|
||||||
async def ws_stream():
|
async def ws_stream():
|
||||||
global LOCAL_RECENT_ORDERS
|
global LOCAL_RECENT_ORDERS
|
||||||
@@ -113,7 +101,7 @@ async def ws_stream():
|
|||||||
'expire_time_ts': o['expireTime'],
|
'expire_time_ts': o['expireTime'],
|
||||||
}
|
}
|
||||||
list_for_df.append(order_update)
|
list_for_df.append(order_update)
|
||||||
LOCAL_RECENT_ORDERS = upsert_list_of_dicts_by_id(LOCAL_RECENT_ORDERS, order_update, id='order_id', seq_check_field='sequence_id')
|
LOCAL_RECENT_ORDERS = utils.upsert_list_of_dicts_by_id(LOCAL_RECENT_ORDERS, order_update, id='order_id', seq_check_field='sequence_id')
|
||||||
LOCAL_RECENT_ORDERS = [t for t in LOCAL_RECENT_ORDERS if t.get('timestamp_arrival', 0) >= LOOKBACK_MIN_TS_MS]
|
LOCAL_RECENT_ORDERS = [t for t in LOCAL_RECENT_ORDERS if t.get('timestamp_arrival', 0) >= LOOKBACK_MIN_TS_MS]
|
||||||
|
|
||||||
VAL_KEY_OBJ = json.dumps(LOCAL_RECENT_ORDERS)
|
VAL_KEY_OBJ = json.dumps(LOCAL_RECENT_ORDERS)
|
||||||
@@ -142,7 +130,7 @@ async def ws_stream():
|
|||||||
'created_time_ts': t['createdTime'],
|
'created_time_ts': t['createdTime'],
|
||||||
'is_taker': t['isTaker'],
|
'is_taker': t['isTaker'],
|
||||||
}
|
}
|
||||||
LOCAL_RECENT_TRADES = upsert_list_of_dicts_by_id(LOCAL_RECENT_TRADES, trade_update, id='trade_id', seq_check_field='sequence_id')
|
LOCAL_RECENT_TRADES = utils.upsert_list_of_dicts_by_id(LOCAL_RECENT_TRADES, trade_update, id='trade_id', seq_check_field='sequence_id')
|
||||||
LOCAL_RECENT_TRADES = [t for t in LOCAL_RECENT_TRADES if t.get('timestamp_arrival', 0) >= LOOKBACK_MIN_TS_MS]
|
LOCAL_RECENT_TRADES = [t for t in LOCAL_RECENT_TRADES if t.get('timestamp_arrival', 0) >= LOOKBACK_MIN_TS_MS]
|
||||||
|
|
||||||
VAL_KEY_OBJ = json.dumps(LOCAL_RECENT_TRADES)
|
VAL_KEY_OBJ = json.dumps(LOCAL_RECENT_TRADES)
|
||||||
@@ -167,7 +155,7 @@ async def ws_stream():
|
|||||||
'exposure': float(data['data']['balance']['exposure']),
|
'exposure': float(data['data']['balance']['exposure']),
|
||||||
'leverage': float(data['data']['balance']['leverage']),
|
'leverage': float(data['data']['balance']['leverage']),
|
||||||
}
|
}
|
||||||
LOCAL_RECENT_BALANCES = upsert_list_of_dicts_by_id(LOCAL_RECENT_BALANCES, balance_update, id='collateral_name', seq_check_field='sequence_id')
|
LOCAL_RECENT_BALANCES = utils.upsert_list_of_dicts_by_id(LOCAL_RECENT_BALANCES, balance_update, id='collateral_name', seq_check_field='sequence_id')
|
||||||
LOCAL_RECENT_BALANCES = [t for t in LOCAL_RECENT_BALANCES if t.get('timestamp_arrival', 0) >= LOOKBACK_MIN_TS_MS]
|
LOCAL_RECENT_BALANCES = [t for t in LOCAL_RECENT_BALANCES if t.get('timestamp_arrival', 0) >= LOOKBACK_MIN_TS_MS]
|
||||||
|
|
||||||
VAL_KEY_OBJ = json.dumps(LOCAL_RECENT_BALANCES)
|
VAL_KEY_OBJ = json.dumps(LOCAL_RECENT_BALANCES)
|
||||||
@@ -203,7 +191,7 @@ async def ws_stream():
|
|||||||
'created_at_ts': p['createdAt'],
|
'created_at_ts': p['createdAt'],
|
||||||
'updated_at_ts': p['updatedAt'],
|
'updated_at_ts': p['updatedAt'],
|
||||||
}
|
}
|
||||||
LOCAL_RECENT_POSITIONS = upsert_list_of_dicts_by_id(LOCAL_RECENT_POSITIONS, position_update, id='position_id', seq_check_field='sequence_id')
|
LOCAL_RECENT_POSITIONS = utils.upsert_list_of_dicts_by_id(LOCAL_RECENT_POSITIONS, position_update, id='position_id', seq_check_field='sequence_id')
|
||||||
LOCAL_RECENT_POSITIONS = [t for t in LOCAL_RECENT_POSITIONS if t.get('timestamp_arrival', 0) >= LOOKBACK_MIN_TS_MS]
|
LOCAL_RECENT_POSITIONS = [t for t in LOCAL_RECENT_POSITIONS if t.get('timestamp_arrival', 0) >= LOOKBACK_MIN_TS_MS]
|
||||||
|
|
||||||
VAL_KEY_OBJ = json.dumps(LOCAL_RECENT_POSITIONS)
|
VAL_KEY_OBJ = json.dumps(LOCAL_RECENT_POSITIONS)
|
||||||
|
|||||||
Reference in New Issue
Block a user