start of refactor to objects

This commit is contained in:
2026-04-24 07:29:26 +00:00
parent ea46b173fa
commit afa2d1fd79
11 changed files with 1003 additions and 6884 deletions

19
algo/Dockerfile Normal file
View 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"]

File diff suppressed because it is too large Load Diff

View File

@@ -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"

View File

@@ -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": []

515
main.py
View File

@@ -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 ###
LIQUIDATE_POS_AND_KILL_ALGO_FLAG: bool = False @dataclass(kw_only=True)
NET_FUNDING_IS_ZERO: bool = False class Flags:
LIQUIDATE_POS_AND_KILL_ALGO_FLAG: 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.

View File

@@ -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
View 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()

View File

@@ -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.')

View File

@@ -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)