aster auth hell

This commit is contained in:
2026-04-22 05:24:40 +00:00
parent 64266366dd
commit 408a63fe58
14 changed files with 1170 additions and 6 deletions

View File

@@ -0,0 +1,154 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"id": "3a269644",
"metadata": {},
"outputs": [],
"source": [
"import modules.aster_auth_api as aster_auth_api"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "4395fabb",
"metadata": {},
"outputs": [],
"source": [
"listen_key_request = {\n",
" \"url\": \"/fapi/v3/listenKey\",\n",
" \"method\": \"POST\",\n",
" \"params\": {}\n",
"}\n",
"cancel_all_open_orders = {\n",
" \"url\": \"/fapi/v3/allOpenOrders\",\n",
" \"method\": \"DELETE\",\n",
" \"params\": {\n",
" 'symbol': 'ETHUSDT',\n",
" }\n",
"}\n",
"fut_acct_balances = {\n",
" \"url\": \"/fapi/v3/balance\",\n",
" \"method\": \"GET\",\n",
" \"params\": {}\n",
"}\n",
"commission_rate = {\n",
" \"url\": \"/fapi/v3/commissionRate\",\n",
" \"method\": \"GET\",\n",
" \"params\": {\n",
" 'symbol': 'ETHUSDT',\n",
" }\n",
"}\n",
"post_order = {\n",
" \"url\": \"/fapi/v3/order\",\n",
" \"method\": \"POST\",\n",
" \"params\": {\n",
" 'symbol': 'ETHUSDT',\n",
" 'side': 'BUY',\n",
" 'type': 'LIMIT',\n",
" 'timeInForce': 'GTC',\n",
" 'quantity': '0.01',\n",
" 'price': '2100',\n",
" }\n",
"}"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "2122885a",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'orderId': 17340952438,\n",
" 'symbol': 'ETHUSDT',\n",
" 'status': 'NEW',\n",
" 'clientOrderId': '57UkyWpQE4iRP74eAwt7Hf',\n",
" 'price': '2100',\n",
" 'avgPrice': '0.00000',\n",
" 'origQty': '0.010',\n",
" 'executedQty': '0',\n",
" 'cumQty': '0',\n",
" 'cumQuote': '0',\n",
" 'timeInForce': 'GTC',\n",
" 'type': 'LIMIT',\n",
" 'reduceOnly': False,\n",
" 'closePosition': False,\n",
" 'side': 'BUY',\n",
" 'positionSide': 'BOTH',\n",
" 'stopPrice': '0',\n",
" 'workingType': 'CONTRACT_PRICE',\n",
" 'priceProtect': False,\n",
" 'origType': 'LIMIT',\n",
" 'updateTime': 1776835456300,\n",
" 'newChainData': {'hash': '0x12456fb0e14021975aa03e56adb339f8b906710ec3a8604a73a8b697c7ee1225'}}"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"j = aster_auth_api.post_authenticated_url(post_order)\n",
"j"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "45d68bf4",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"id": "fd513311",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"id": "940ceba7",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "py_313",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.13.12"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

53
docker-compose.yml Normal file
View File

@@ -0,0 +1,53 @@
services:
ws_aster:
container_name: ws_aster
restart: "unless-stopped"
build:
context: ./
dockerfile: ./ws_aster/Dockerfile
volumes:
- /home/ubuntu/data:/home/ubuntu/data:rw # Read-write access to data
- /home/ubuntu/logs:/home/ubuntu/logs:rw # Read-write access to data
network_mode: "host"
ws_extended_fund_rate:
container_name: ws_extended_fund_rate
restart: "unless-stopped"
build:
context: ./
dockerfile: ./ws_extended_fund_rate/Dockerfile
volumes:
- /home/ubuntu/data:/home/ubuntu/data:rw # Read-write access to data
- /home/ubuntu/logs:/home/ubuntu/logs:rw # Read-write access to data
network_mode: "host"
ws_extended_orderbook:
container_name: ws_extended_orderbook
restart: "unless-stopped"
build:
context: ./
dockerfile: ./ws_extended_orderbook/Dockerfile
volumes:
- /home/ubuntu/data:/home/ubuntu/data:rw # Read-write access to data
- /home/ubuntu/logs:/home/ubuntu/logs:rw # Read-write access to data
network_mode: "host"
# ws_user:
# container_name: ws_user
# restart: "unless-stopped"
# build:
# context: ./
# dockerfile: ./ws_user/Dockerfile
# volumes:
# - /home/ubuntu/data:/home/ubuntu/data:rw # Read-write access to data
# - /home/ubuntu/logs:/home/ubuntu/logs:rw # Read-write access to data
# network_mode: "host"
# ng:
# container_name: ng
# restart: "unless-stopped"
# build:
# context: ./
# dockerfile: ./ng/Dockerfile
# volumes:
# - /home/ubuntu/data:/home/ubuntu/data:rw # Read-write access to data
# - /home/ubuntu/logs:/home/ubuntu/logs:rw # Read-write access to data
# network_mode: "host"

169
extended.ipynb Normal file
View File

@@ -0,0 +1,169 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 15,
"id": "c9606e56",
"metadata": {},
"outputs": [],
"source": [
"import math\n",
"from datetime import datetime, timezone"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "2b5d7d21",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'type': 'SNAPSHOT',\n",
" 'data': {'t': 'SNAPSHOT',\n",
" 'm': 'ETH-USD',\n",
" 'b': [{'q': '362.381', 'p': '2318.1'}],\n",
" 'a': [{'q': '70.572', 'p': '2318.2'}],\n",
" 'd': '1'},\n",
" 'ts': 1776822518279,\n",
" 'seq': 14}"
]
},
"execution_count": 1,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"{\"type\":\"SNAPSHOT\",\"data\":{\"t\":\"SNAPSHOT\",\"m\":\"ETH-USD\",\"b\":[{\"q\":\"362.381\",\"p\":\"2318.1\"}],\"a\":[{\"q\":\"70.572\",\"p\":\"2318.2\"}],\"d\":\"1\"},\"ts\":1776822518279,\"seq\":14}"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "19f51572",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'data': {'m': 'ETH-USD', 'f': '-0.000001', 'T': 1776823319595},\n",
" 'ts': 1776823319595,\n",
" 'seq': 2}"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"{'data': {'m': 'ETH-USD', 'f': '-0.000001', 'T': 1776823319595}, 'ts': 1776823319595, 'seq': 2}"
]
},
{
"cell_type": "code",
"execution_count": 41,
"id": "68006231",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Timestamp('2026-04-22 03:00:00')"
]
},
"execution_count": 41,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"import pandas as pd\n",
"\n",
"pd.to_datetime(1776826800000, unit='ms')"
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "5561153b",
"metadata": {},
"outputs": [],
"source": [
"def time_round_down(dt, interval_mins=5) -> int: # returns timestamp in seconds\n",
" interval_secs = interval_mins * 60\n",
" seconds = dt.timestamp()\n",
" rounded_seconds = math.floor(seconds / interval_secs) * interval_secs\n",
" \n",
" return rounded_seconds"
]
},
{
"cell_type": "code",
"execution_count": 40,
"id": "bd83b59f",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"1776826800000"
]
},
"execution_count": 40,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(time_round_down(dt=datetime.now(timezone.utc), interval_mins=60)+(60*60))*1000"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"id": "665377af",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"id": "af5c751b",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "py_313",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.13.12"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

15
main.py
View File

@@ -36,12 +36,15 @@ async def run_algo():
loop_start = time.time()
print('__________Start___________')
MEXC_FUND_RATE = json.loads(VAL_KEY.get('fund_rate_mexc'))
MEXC_TICKER = json.loads(VAL_KEY.get('fut_ticker_mexc'))
APEX_TICKER = json.loads(VAL_KEY.get('fut_ticker_apex'))
print(f'MEXC FUND RATE: {MEXC_FUND_RATE}')
print(f'MEXC TICKER: {MEXC_TICKER}')
print(f'APEX TICKER: {APEX_TICKER}')
ASTER_FUND_RATE = json.loads(VAL_KEY.get('fund_rate_aster'))
ASTER_TICKER = 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 = json.loads(VAL_KEY.get('fund_rate_extended'))
EXTENDED_TICKER = json.loads(VAL_KEY.get('fut_ticker_extended'))
print(f'EXTENDED FUND RATE: {EXTENDED_FUND_RATE}')
print(f'EXTENDED TICKER: {EXTENDED_TICKER}')
time.sleep(5)

Binary file not shown.

108
modules/aster_auth_api.py Normal file
View File

@@ -0,0 +1,108 @@
import requests
from dotenv import load_dotenv
import os
import time
import threading
import urllib
from eth_account.messages import encode_typed_data
from eth_account import Account
load_dotenv()
user = os.getenv("RABBY_WALLET")
signer = os.getenv("ASTER_API_WALLET_ADDRESS")
private_key = os.getenv("ASTER_API_PRIVATE_KEY")
_last_ms = 0
_i = 0
def post_authenticated_url(req: dict) -> dict:
typed_data = {
"types": {
"EIP712Domain": [
{"name": "name", "type": "string"},
{"name": "version", "type": "string"},
{"name": "chainId", "type": "uint256"},
{"name": "verifyingContract", "type": "address"}
],
"Message": [
{ "name": "msg", "type": "string" }
]
},
"primaryType": "Message",
"domain": {
"name": "AsterSignTransaction",
"version": "1",
"chainId": 1666,
"verifyingContract": "0x0000000000000000000000000000000000000000"
},
"message": {
"msg": "$msg"
}
}
headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'User-Agent': 'PythonApp/1.0'
}
host = 'https://fapi.asterdex.com'
def get_nonce():
_nonce_lock = threading.Lock()
global _last_ms, _i
with _nonce_lock:
now_ms = int(time.time())
if now_ms == _last_ms:
_i += 1
else:
_last_ms = now_ms
_i = 0
return now_ms * 1_000_000 + _i
def sign_typed_data(data: dict, private_key: str):
"""Sign EIP-712 typed data using encode_typed_data."""
message = encode_typed_data(
domain_data=data["domain"],
message_types={"Message": data["types"]["Message"]},
message_data=data["message"],
)
return Account.sign_message(message, private_key=private_key)
def send_by_url(req):
my_dict = req['params'].copy()
url = host + req['url']
method = req['method']
my_dict['nonce'] = str(get_nonce())
my_dict['user'] = user
my_dict['signer'] = signer
param = urllib.parse.urlencode(my_dict)
typed_data['message']['msg'] = param
signed = sign_typed_data(typed_data, private_key)
full_url = url + '?' + param + '&signature=' + signed.signature.hex()
# print(full_url)
if method == 'GET':
res = requests.get(full_url, headers=headers)
# print(res.status_code, res.text)
return res.json()
elif method == 'POST':
res = requests.post(full_url, headers=headers)
# print(res.status_code, res.text)
return res.json()
elif method == 'PUT':
res = requests.put(full_url, headers=headers)
# print(res.status_code, res.text)
return res.json()
elif method == 'DELETE':
res = requests.delete(full_url, headers=headers)
# print(res.status_code, res.text)
return res.json()
return send_by_url(req=req)

25
requirements.txt Normal file
View File

@@ -0,0 +1,25 @@
pandas
rel
websockets
pyarrow
# plotly
mysql-connector-python
sqlalchemy
requests
pymysql
scipy
asyncmy
cryptography
# TA-Lib
valkey
nicegui
# py_clob_client
# google
# google-api-core==2.30.0
# google-api-python-client==2.190.0
# googleapis-common-protos==1.72.0
# grpcio==1.76.0
# grpcio-tools==1.76.0
x10-python-trading-starknet
eth-keys
eth-account

19
ws_aster/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", "ws_aster.py"]
# CMD [ "gunicorn", "--workers=5", "--threads=1", "-b 0.0.0.0:8000", "app:server"]

200
ws_aster_user.py Normal file
View File

@@ -0,0 +1,200 @@
import asyncio
import json
import logging
import socket
import traceback
from datetime import datetime
from typing import AsyncContextManager
import numpy as np
import pandas as pd
import requests.packages.urllib3.util.connection as urllib3_cn # type: ignore
from sqlalchemy import text
import websockets
from sqlalchemy.ext.asyncio import create_async_engine
import valkey
import os
from dotenv import load_dotenv
### Allow only ipv4 ###
def allowed_gai_family():
return socket.AF_INET
urllib3_cn.allowed_gai_family = allowed_gai_family
### Database ###
USE_DB: bool = False
USE_VK: bool = False
VK_USER = 'fund_rate_aster'
CON: AsyncContextManager | None = None
VAL_KEY = None
### Logging ###
load_dotenv()
LOG_FILEPATH: str = os.getenv("LOGS_PATH") + '/Fund_Rate_Aster_User.log'
### CONSTANTS ###
### Globals ###
WSS_URL = "wss://fstream.asterdex.com"
# HIST_TRADES = np.empty((0, 3))
# HIST_TRADES_LOOKBACK_SEC = 6
# ### Database Funcs ###
# async def create_rtds_btcusd_table(
# CON: AsyncContextManager,
# engine: str = 'mysql', # mysql | duckdb
# ) -> None:
# if CON is None:
# logging.info("NO DB CONNECTION, SKIPPING Create Statements")
# else:
# if engine == 'mysql':
# logging.info('Creating Table if Does Not Exist: binance_btcusd_trades')
# await CON.execute(text("""
# CREATE TABLE IF NOT EXISTS binance_btcusd_trades (
# timestamp_arrival BIGINT,
# timestamp_msg BIGINT,
# timestamp_value BIGINT,
# value DOUBLE,
# qty DOUBLE
# );
# """))
# await CON.commit()
# else:
# raise ValueError('Only MySQL engine is implemented')
# async def insert_rtds_btcusd_table(
# timestamp_arrival: int,
# timestamp_msg: int,
# timestamp_value: int,
# value: float,
# qty: float,
# CON: AsyncContextManager,
# engine: str = 'mysql', # mysql | duckdb
# ) -> None:
# params={
# 'timestamp_arrival': timestamp_arrival,
# 'timestamp_msg': timestamp_msg,
# 'timestamp_value': timestamp_value,
# 'value': value,
# 'qty': qty,
# }
# if CON is None:
# logging.info("NO DB CONNECTION, SKIPPING Insert Statements")
# else:
# if engine == 'mysql':
# await CON.execute(text("""
# INSERT INTO binance_btcusd_trades
# (
# timestamp_arrival,
# timestamp_msg,
# timestamp_value,
# value,
# qty
# )
# VALUES
# (
# :timestamp_arrival,
# :timestamp_msg,
# :timestamp_value,
# :value,
# :qty
# )
# """),
# parameters=params
# )
# await CON.commit()
# else:
# raise ValueError('Only MySQL engine is implemented')
### Websocket ###
async def ws_stream():
async for websocket in websockets.connect(WSS_URL):
logging.info(f"Connected to {WSS_URL}")
try:
async for message in websocket:
ts_arrival = round(datetime.now().timestamp()*1000)
if isinstance(message, str):
try:
data = json.loads(message)
channel = data.get('stream', None)
if channel is not None:
match channel:
case :
continue
case :
# print(f'BT: {data}')
VAL_KEY_OBJ = json.dumps({
'timestamp_arrival': ts_arrival,
'timestamp_msg': data['data']['E'],
'timestamp_transaction': data['data']['T'],
'orderbook_update_id': data['data']['u'],
'symbol': data['data']['s'],
'best_bid_px': data['data']['b'],
'best_bid_qty': data['data']['B'],
'best_ask_px': data['data']['a'],
'best_ask_qty': data['data']['A'],
})
VAL_KEY.set(VK_TICKER, VAL_KEY_OBJ)
continue
case _:
logging.warning(f'UNMATCHED OTHER MSG: {data}')
else:
logging.info(f'Initial or unexpected data struct, skipping: {data}')
continue
except (json.JSONDecodeError, ValueError):
logging.warning(f'Message not in JSON format, skipping: {message}')
continue
else:
raise ValueError(f'Type: {type(data)} not expected: {message}')
except websockets.ConnectionClosed as e:
logging.error(f'Connection closed: {e}')
logging.error(traceback.format_exc())
continue
except Exception as e:
logging.error(f'Connection closed: {e}')
logging.error(traceback.format_exc())
async def main():
global VAL_KEY
global CON
if USE_VK:
VAL_KEY = valkey.Valkey(host='localhost', port=6379, db=0)
else:
VAL_KEY = None
logging.warning("VALKEY NOT BEING USED, NO DATA WILL BE PUBLISHED")
if USE_DB:
engine = create_async_engine('mysql+asyncmy://root:pwd@localhost/polymarket')
async with engine.connect() as CON:
# await create_rtds_btcusd_table(CON=CON)
await ws_stream()
else:
CON = None
logging.warning("DATABASE NOT BEING USED, NO DATA WILL BE RECORDED")
await ws_stream()
if __name__ == '__main__':
START_TIME = round(datetime.now().timestamp()*1000)
logging.info(f'Log FilePath: {LOG_FILEPATH}')
logging.basicConfig(
force=True,
filename=LOG_FILEPATH,
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
filemode='w'
)
logging.info(f"STARTED: {START_TIME}")
try:
asyncio.run(main())
except KeyboardInterrupt:
logging.info("Stream stopped")

View File

203
ws_extended_fund_rate.py Normal file
View File

@@ -0,0 +1,203 @@
import asyncio
import json
import logging
import socket
import traceback
from datetime import datetime, timezone
from typing import AsyncContextManager
import math
import numpy as np
import pandas as pd
import requests.packages.urllib3.util.connection as urllib3_cn # type: ignore
from sqlalchemy import text
import websockets
from sqlalchemy.ext.asyncio import create_async_engine
import valkey
import os
from dotenv import load_dotenv
### Allow only ipv4 ###
def allowed_gai_family():
return socket.AF_INET
urllib3_cn.allowed_gai_family = allowed_gai_family
### Database ###
USE_DB: bool = False
USE_VK: bool = True
VK_FUND_RATE = 'fund_rate_extended'
CON: AsyncContextManager | None = None
VAL_KEY = None
### Logging ###
load_dotenv()
LOG_FILEPATH: str = os.getenv("LOGS_PATH") + '/Fund_Rate_Extended_FR.log'
### CONSTANTS ###
WS_SYMBOL: str = 'ETH-USD'
FUNDING_RATE_INTERVAL_MIN = 60
### Globals ###
WSS_URL = f"wss://api.starknet.extended.exchange/stream.extended.exchange/v1/funding/{WS_SYMBOL}"
# HIST_TRADES = np.empty((0, 3))
# HIST_TRADES_LOOKBACK_SEC = 6
def time_round_down(dt, interval_mins=5) -> int: # returns timestamp in seconds
interval_secs = interval_mins * 60
seconds = dt.timestamp()
rounded_seconds = math.floor(seconds / interval_secs) * interval_secs
return rounded_seconds
# ### Database Funcs ###
# async def create_rtds_btcusd_table(
# CON: AsyncContextManager,
# engine: str = 'mysql', # mysql | duckdb
# ) -> None:
# if CON is None:
# logging.info("NO DB CONNECTION, SKIPPING Create Statements")
# else:
# if engine == 'mysql':
# logging.info('Creating Table if Does Not Exist: binance_btcusd_trades')
# await CON.execute(text("""
# CREATE TABLE IF NOT EXISTS binance_btcusd_trades (
# timestamp_arrival BIGINT,
# timestamp_msg BIGINT,
# timestamp_value BIGINT,
# value DOUBLE,
# qty DOUBLE
# );
# """))
# await CON.commit()
# else:
# raise ValueError('Only MySQL engine is implemented')
# async def insert_rtds_btcusd_table(
# timestamp_arrival: int,
# timestamp_msg: int,
# timestamp_value: int,
# value: float,
# qty: float,
# CON: AsyncContextManager,
# engine: str = 'mysql', # mysql | duckdb
# ) -> None:
# params={
# 'timestamp_arrival': timestamp_arrival,
# 'timestamp_msg': timestamp_msg,
# 'timestamp_value': timestamp_value,
# 'value': value,
# 'qty': qty,
# }
# if CON is None:
# logging.info("NO DB CONNECTION, SKIPPING Insert Statements")
# else:
# if engine == 'mysql':
# await CON.execute(text("""
# INSERT INTO binance_btcusd_trades
# (
# timestamp_arrival,
# timestamp_msg,
# timestamp_value,
# value,
# qty
# )
# VALUES
# (
# :timestamp_arrival,
# :timestamp_msg,
# :timestamp_value,
# :value,
# :qty
# )
# """),
# parameters=params
# )
# await CON.commit()
# else:
# raise ValueError('Only MySQL engine is implemented')
### Websocket ###
async def ws_stream():
async for websocket in websockets.connect(WSS_URL):
logging.info(f"Connected to {WSS_URL}")
try:
async for message in websocket:
ts_arrival = round(datetime.now().timestamp()*1000)
if isinstance(message, str):
try:
data = json.loads(message)
if data.get('data', None) is not None:
print(f'FR: {data}')
fr_next_update_ts = (time_round_down(dt=datetime.now(timezone.utc), interval_mins=60)+(60*60))*1000
VAL_KEY_OBJ = json.dumps({
'sequence_id': data['seq'],
'timestamp_arrival': ts_arrival,
'timestamp_msg': data['ts'],
'symbol': data['data']['m'],
'funding_rate': float(data['data']['f']),
'funding_rate_updated_ts_ms': data['data']['T'],
'next_funding_time_ts_ms': fr_next_update_ts,
})
VAL_KEY.set(VK_FUND_RATE, VAL_KEY_OBJ)
continue
else:
logging.info(f'Initial or unexpected data struct, skipping: {data}')
continue
except (json.JSONDecodeError, ValueError):
logging.warning(f'Message not in JSON format, skipping: {message}')
continue
else:
raise ValueError(f'Type: {type(data)} not expected: {message}')
except websockets.ConnectionClosed as e:
logging.error(f'Connection closed: {e}')
logging.error(traceback.format_exc())
continue
except Exception as e:
logging.error(f'Connection closed: {e}')
logging.error(traceback.format_exc())
async def main():
global VAL_KEY
global CON
if USE_VK:
VAL_KEY = valkey.Valkey(host='localhost', port=6379, db=0)
else:
VAL_KEY = None
logging.warning("VALKEY NOT BEING USED, NO DATA WILL BE PUBLISHED")
if USE_DB:
engine = create_async_engine('mysql+asyncmy://root:pwd@localhost/polymarket')
async with engine.connect() as CON:
# await create_rtds_btcusd_table(CON=CON)
await ws_stream()
else:
CON = None
logging.warning("DATABASE NOT BEING USED, NO DATA WILL BE RECORDED")
await ws_stream()
if __name__ == '__main__':
START_TIME = round(datetime.now().timestamp()*1000)
logging.info(f'Log FilePath: {LOG_FILEPATH}')
logging.basicConfig(
force=True,
filename=LOG_FILEPATH,
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
filemode='w'
)
logging.info(f"STARTED: {START_TIME}")
try:
asyncio.run(main())
except KeyboardInterrupt:
logging.info("Stream stopped")

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", "ws_extended_fund_rate.py"]
# CMD [ "gunicorn", "--workers=5", "--threads=1", "-b 0.0.0.0:8000", "app:server"]

192
ws_extended_orderbook.py Normal file
View File

@@ -0,0 +1,192 @@
import asyncio
import json
import logging
import socket
import traceback
from datetime import datetime
from typing import AsyncContextManager
import numpy as np
import pandas as pd
import requests.packages.urllib3.util.connection as urllib3_cn # type: ignore
from sqlalchemy import text
import websockets
from sqlalchemy.ext.asyncio import create_async_engine
import valkey
import os
from dotenv import load_dotenv
### Allow only ipv4 ###
def allowed_gai_family():
return socket.AF_INET
urllib3_cn.allowed_gai_family = allowed_gai_family
### Database ###
USE_DB: bool = False
USE_VK: bool = True
VK_TICKER = 'fut_ticker_extended'
CON: AsyncContextManager | None = None
VAL_KEY = None
### Logging ###
load_dotenv()
LOG_FILEPATH: str = os.getenv("LOGS_PATH") + '/Fund_Rate_Extended.log'
### CONSTANTS ###
WS_SYMBOL: str = 'ETH-USD'
### Globals ###
WSS_URL = f"wss://api.starknet.extended.exchange/stream.extended.exchange/v1/orderbooks/{WS_SYMBOL}?depth=1"
# HIST_TRADES = np.empty((0, 3))
# HIST_TRADES_LOOKBACK_SEC = 6
# ### Database Funcs ###
# async def create_rtds_btcusd_table(
# CON: AsyncContextManager,
# engine: str = 'mysql', # mysql | duckdb
# ) -> None:
# if CON is None:
# logging.info("NO DB CONNECTION, SKIPPING Create Statements")
# else:
# if engine == 'mysql':
# logging.info('Creating Table if Does Not Exist: binance_btcusd_trades')
# await CON.execute(text("""
# CREATE TABLE IF NOT EXISTS binance_btcusd_trades (
# timestamp_arrival BIGINT,
# timestamp_msg BIGINT,
# timestamp_value BIGINT,
# value DOUBLE,
# qty DOUBLE
# );
# """))
# await CON.commit()
# else:
# raise ValueError('Only MySQL engine is implemented')
# async def insert_rtds_btcusd_table(
# timestamp_arrival: int,
# timestamp_msg: int,
# timestamp_value: int,
# value: float,
# qty: float,
# CON: AsyncContextManager,
# engine: str = 'mysql', # mysql | duckdb
# ) -> None:
# params={
# 'timestamp_arrival': timestamp_arrival,
# 'timestamp_msg': timestamp_msg,
# 'timestamp_value': timestamp_value,
# 'value': value,
# 'qty': qty,
# }
# if CON is None:
# logging.info("NO DB CONNECTION, SKIPPING Insert Statements")
# else:
# if engine == 'mysql':
# await CON.execute(text("""
# INSERT INTO binance_btcusd_trades
# (
# timestamp_arrival,
# timestamp_msg,
# timestamp_value,
# value,
# qty
# )
# VALUES
# (
# :timestamp_arrival,
# :timestamp_msg,
# :timestamp_value,
# :value,
# :qty
# )
# """),
# parameters=params
# )
# await CON.commit()
# else:
# raise ValueError('Only MySQL engine is implemented')
### Websocket ###
async def ws_stream():
async for websocket in websockets.connect(WSS_URL):
logging.info(f"Connected to {WSS_URL}")
try:
async for message in websocket:
ts_arrival = round(datetime.now().timestamp()*1000)
if isinstance(message, str):
try:
data = json.loads(message)
if data.get('type', None) is not None:
# print(f'OB: {data}')
VAL_KEY_OBJ = json.dumps({
'sequence_id': data['seq'],
'timestamp_arrival': ts_arrival,
'timestamp_msg': data['ts'],
'symbol': data['data']['m'],
'best_bid_px': float(data['data']['b'][0]['p']),
'best_bid_qty': float(data['data']['b'][0]['q']),
'best_ask_px': float(data['data']['a'][0]['p']),
'best_ask_qty': float(data['data']['a'][0]['q']),
})
VAL_KEY.set(VK_TICKER, VAL_KEY_OBJ)
continue
else:
logging.info(f'Initial or unexpected data struct, skipping: {data}')
continue
except (json.JSONDecodeError, ValueError):
logging.warning(f'Message not in JSON format, skipping: {message}')
continue
else:
raise ValueError(f'Type: {type(data)} not expected: {message}')
except websockets.ConnectionClosed as e:
logging.error(f'Connection closed: {e}')
logging.error(traceback.format_exc())
continue
except Exception as e:
logging.error(f'Connection closed: {e}')
logging.error(traceback.format_exc())
async def main():
global VAL_KEY
global CON
if USE_VK:
VAL_KEY = valkey.Valkey(host='localhost', port=6379, db=0)
else:
VAL_KEY = None
logging.warning("VALKEY NOT BEING USED, NO DATA WILL BE PUBLISHED")
if USE_DB:
engine = create_async_engine('mysql+asyncmy://root:pwd@localhost/polymarket')
async with engine.connect() as CON:
# await create_rtds_btcusd_table(CON=CON)
await ws_stream()
else:
CON = None
logging.warning("DATABASE NOT BEING USED, NO DATA WILL BE RECORDED")
await ws_stream()
if __name__ == '__main__':
START_TIME = round(datetime.now().timestamp()*1000)
logging.info(f'Log FilePath: {LOG_FILEPATH}')
logging.basicConfig(
force=True,
filename=LOG_FILEPATH,
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
filemode='w'
)
logging.info(f"STARTED: {START_TIME}")
try:
asyncio.run(main())
except KeyboardInterrupt:
logging.info("Stream stopped")

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", "ws_extended_orderbook.py"]
# CMD [ "gunicorn", "--workers=5", "--threads=1", "-b 0.0.0.0:8000", "app:server"]