aster auth hell
This commit is contained in:
154
aster.ipynb
154
aster.ipynb
@@ -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
53
docker-compose.yml
Normal 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
169
extended.ipynb
Normal 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
15
main.py
@@ -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)
|
||||
|
||||
|
||||
BIN
modules/__pycache__/aster_auth_api.cpython-313.pyc
Normal file
BIN
modules/__pycache__/aster_auth_api.cpython-313.pyc
Normal file
Binary file not shown.
108
modules/aster_auth_api.py
Normal file
108
modules/aster_auth_api.py
Normal 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
25
requirements.txt
Normal 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
19
ws_aster/Dockerfile
Normal file
@@ -0,0 +1,19 @@
|
||||
FROM python:3.13-slim
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y build-essential
|
||||
|
||||
RUN gcc --version
|
||||
RUN rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY requirements.txt .
|
||||
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
COPY . .
|
||||
|
||||
# Finally, run gunicorn.
|
||||
CMD [ "python", "ws_aster.py"]
|
||||
# CMD [ "gunicorn", "--workers=5", "--threads=1", "-b 0.0.0.0:8000", "app:server"]
|
||||
200
ws_aster_user.py
Normal file
200
ws_aster_user.py
Normal 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")
|
||||
203
ws_extended_fund_rate.py
Normal file
203
ws_extended_fund_rate.py
Normal 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")
|
||||
19
ws_extended_fund_rate/Dockerfile
Normal file
19
ws_extended_fund_rate/Dockerfile
Normal file
@@ -0,0 +1,19 @@
|
||||
FROM python:3.13-slim
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y build-essential
|
||||
|
||||
RUN gcc --version
|
||||
RUN rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY requirements.txt .
|
||||
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
COPY . .
|
||||
|
||||
# Finally, run gunicorn.
|
||||
CMD [ "python", "ws_extended_fund_rate.py"]
|
||||
# CMD [ "gunicorn", "--workers=5", "--threads=1", "-b 0.0.0.0:8000", "app:server"]
|
||||
192
ws_extended_orderbook.py
Normal file
192
ws_extended_orderbook.py
Normal 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")
|
||||
19
ws_extended_orderbook/Dockerfile
Normal file
19
ws_extended_orderbook/Dockerfile
Normal file
@@ -0,0 +1,19 @@
|
||||
FROM python:3.13-slim
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y build-essential
|
||||
|
||||
RUN gcc --version
|
||||
RUN rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY requirements.txt .
|
||||
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
COPY . .
|
||||
|
||||
# Finally, run gunicorn.
|
||||
CMD [ "python", "ws_extended_orderbook.py"]
|
||||
# CMD [ "gunicorn", "--workers=5", "--threads=1", "-b 0.0.0.0:8000", "app:server"]
|
||||
Reference in New Issue
Block a user