commit adf3e592ebc1be14fc150cf6be00916cf5499dd9 Author: stevekeyharvey Date: Fri Mar 27 12:40:49 2026 -0400 First commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2eea525 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.env \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..4b5a294 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "python-envs.defaultEnvManager": "ms-python.python:conda", + "python-envs.defaultPackageManager": "ms-python.python:conda" +} \ No newline at end of file diff --git a/modules/__pycache__/api.cpython-313.pyc b/modules/__pycache__/api.cpython-313.pyc new file mode 100644 index 0000000..41ce6d9 Binary files /dev/null and b/modules/__pycache__/api.cpython-313.pyc differ diff --git a/modules/api.py b/modules/api.py new file mode 100644 index 0000000..e6cc69f --- /dev/null +++ b/modules/api.py @@ -0,0 +1,42 @@ +import requests +from py_clob_client.client import ClobClient +import os +from dotenv import load_dotenv + +### Functions ### +def check_geoblock() -> None: + response = requests.get("https://polymarket.com/api/geoblock") + geo = response.json() + if geo["blocked"]: + print(f"Trading not available in {geo['country']}") + raise ValueError('GEOBLOCKED - KILLING SCRIPT') + else: + print("Trading available") + +def create_client(): + print('creating client...') + load_dotenv() + private_key = os.getenv("PRIVATE_KEY") + host = "https://clob.polymarket.com" + chain_id = 137 # Polygon mainnet + + temp_client = ClobClient(host, key=private_key, chain_id=chain_id) + api_creds = temp_client.create_or_derive_api_creds() + + client = ClobClient( + host, + key=private_key, + chain_id=chain_id, + creds=api_creds, + signature_type=2, # Rabby + funder=os.getenv("FUNDER") + # funder="0xb2967A7e578E700E27611238B7F762BdADC72CcB" + # 0x2bb5be619b8f348954dd2290abcd267735a9f4a0 + + ) + # View your trade history + trades = client.get_trades() + print(f"You've made {len(trades)} trades") + print('client created successfully!') + + return client \ No newline at end of file diff --git a/order_entry.ipynb b/order_entry.ipynb new file mode 100644 index 0000000..2b6a579 --- /dev/null +++ b/order_entry.ipynb @@ -0,0 +1,681 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 64, + "id": "c0bfb3b5", + "metadata": {}, + "outputs": [], + "source": [ + "import modules.api as api\n", + "import pandas as pd\n", + "from datetime import datetime, timezone, timedelta\n", + "import math\n", + "import requests\n", + "import json\n", + "from dataclasses import dataclass\n", + "\n", + "from py_clob_client.clob_types import OrderArgs, OrderType, PostOrdersArgs, PartialCreateOrderOptions\n", + "from py_clob_client.order_builder.constants import BUY, SELL\n" + ] + }, + { + "cell_type": "code", + "execution_count": 65, + "id": "7d7dc787", + "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\n", + "\n", + "def get_mkt_details_by_slug(slug: str) -> dict[str, str, str]: # {'Up' : 123, 'Down': 456, 'isActive': True, 'MinTickSize': 0.01, 'isNegRisk': False}\n", + " r = requests.get(f\"https://gamma-api.polymarket.com/events/slug/{slug}\")\n", + " market = r.json()['markets'][0]\n", + " token_ids = json.loads(market.get(\"clobTokenIds\", \"[]\"))\n", + " outcomes = json.loads(market.get(\"outcomes\", \"[]\"))\n", + " d = dict(zip(outcomes, token_ids))\n", + " d['isActive'] = market['negRisk']\n", + " d['MinTickSize'] = market['orderPriceMinTickSize']\n", + " d['isNegRisk'] = market['negRisk']\n", + " d['ConditionId'] = market['conditionId']\n", + "\n", + " return d, market" + ] + }, + { + "cell_type": "code", + "execution_count": 66, + "id": "c3e07e21", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Timestamp('2026-03-27 03:15:00')" + ] + }, + "execution_count": 66, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "slug_prefix = 'btc-updown-5m-'\n", + "slug_time_id = time_round_down(dt=datetime.now(timezone.utc))\n", + "slug_full = slug_prefix + str(slug_time_id)\n", + "market_details, market = get_mkt_details_by_slug(slug_full)\n", + "pd.to_datetime(slug_time_id, unit='s')" + ] + }, + { + "cell_type": "code", + "execution_count": 67, + "id": "5ba43ffc", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'Up': '97875487643168796351669326324566509161830383659944117871160601839654217457417',\n", + " 'Down': '96344823573113580705457152659674776966355813491715728702490170635510049560213',\n", + " 'isActive': False,\n", + " 'MinTickSize': 0.01,\n", + " 'isNegRisk': False,\n", + " 'ConditionId': '0x071d8568d3d736502bd450e150ef93481992d1d26df0c094cc119246d8931a23'}" + ] + }, + "execution_count": 67, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "market_details" + ] + }, + { + "cell_type": "code", + "execution_count": 61, + "id": "5d356d3b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "creating client...\n", + "You've made 41 trades\n", + "client created successfully!\n" + ] + } + ], + "source": [ + "client = api.create_client()" + ] + }, + { + "cell_type": "code", + "execution_count": 62, + "id": "bebb53eb", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'price': '0.84', 'side': 'BUY'}\n" + ] + } + ], + "source": [ + "last = client.get_last_trade_price(market_details['Up'])\n", + "print(last)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bae5e6a9", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 63, + "id": "52c0c38a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[{'errorMsg': '', 'orderID': '0x4e8e8b193d91c2d4e7b455d3d654a26697ee3289399a9140b2b1888dda7b9a16', 'takingAmount': '10', 'makingAmount': '1.7', 'status': 'matched', 'transactionsHashes': ['0x4f66978cc001a819d9ba266f708148f0512414627a59ffd9a48d8e4b92e5a716'], 'success': True}, {'errorMsg': '', 'orderID': '0x6c5aad6b231c8a6d7a91c1260fe31955e3715e6557aaa773029bd9ec42f26917', 'takingAmount': '', 'makingAmount': '', 'status': 'live', 'success': True}]\n" + ] + } + ], + "source": [ + "response = client.post_orders([\n", + " PostOrdersArgs(\n", + " order=client.create_order(\n", + " order_args=OrderArgs(\n", + " token_id=market_details['Up'],\n", + " price=0.90,\n", + " size=10,\n", + " side=BUY,\n", + " ),\n", + " options=PartialCreateOrderOptions(\n", + " tick_size=str(market_details['MinTickSize']),\n", + " neg_risk=market_details['isNegRisk']\n", + " ),\n", + " ),\n", + " orderType=OrderType.GTC,\n", + " postOnly=False,\n", + " ),\n", + " PostOrdersArgs(\n", + " order=client.create_order(\n", + " order_args=OrderArgs(\n", + " token_id=market_details['Down'],\n", + " price=0.10,\n", + " size=10,\n", + " side=BUY,\n", + " ),\n", + " options=PartialCreateOrderOptions(\n", + " tick_size=str(market_details['MinTickSize']),\n", + " neg_risk=market_details['isNegRisk']\n", + " ),\n", + " ),\n", + " orderType=OrderType.GTC,\n", + " postOnly=True,\n", + " ),\n", + "])\n", + "\n", + "print(response)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "52a7229b", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fb5f066a", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4b3ccf83", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 68, + "id": "75d45cd8", + "metadata": {}, + "outputs": [], + "source": [ + "d = [{'asset_id': '97987758532314346863331680319607711694838937984814950023901315671390566048932', 'price': '0.37', 'size': '429.41', 'side': 'BUY', 'hash': '999d5b73d83a840c0df7dc2d817ae55a242845d5', 'best_bid': '0.37', 'best_ask': '0.38'}, {'asset_id': '92959981857766705879127008770062050214089835506649207585188324269480756219695', 'price': '0.63', 'size': '429.41', 'side': 'SELL', 'hash': 'ead5af5a55f8b125b1e50f1c80a783c3c2d33187', 'best_bid': '0.62', 'best_ask': '0.63'}]" + ] + }, + { + "cell_type": "code", + "execution_count": 69, + "id": "f608378f", + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.microsoft.datawrangler.viewer.v0+json": { + "columns": [ + { + "name": "index", + "rawType": "int64", + "type": "integer" + }, + { + "name": "asset_id", + "rawType": "object", + "type": "string" + }, + { + "name": "price", + "rawType": "object", + "type": "string" + }, + { + "name": "size", + "rawType": "object", + "type": "string" + }, + { + "name": "side", + "rawType": "object", + "type": "string" + }, + { + "name": "hash", + "rawType": "object", + "type": "string" + }, + { + "name": "best_bid", + "rawType": "object", + "type": "string" + }, + { + "name": "best_ask", + "rawType": "object", + "type": "string" + } + ], + "ref": "310c4763-20db-4051-931a-ef52e1b6513b", + "rows": [ + [ + "0", + "97987758532314346863331680319607711694838937984814950023901315671390566048932", + "0.37", + "429.41", + "BUY", + "999d5b73d83a840c0df7dc2d817ae55a242845d5", + "0.37", + "0.38" + ], + [ + "1", + "92959981857766705879127008770062050214089835506649207585188324269480756219695", + "0.63", + "429.41", + "SELL", + "ead5af5a55f8b125b1e50f1c80a783c3c2d33187", + "0.62", + "0.63" + ] + ], + "shape": { + "columns": 7, + "rows": 2 + } + }, + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
asset_idpricesizesidehashbest_bidbest_ask
09798775853231434686333168031960771169483893798...0.37429.41BUY999d5b73d83a840c0df7dc2d817ae55a242845d50.370.38
19295998185776670587912700877006205021408983550...0.63429.41SELLead5af5a55f8b125b1e50f1c80a783c3c2d331870.620.63
\n", + "
" + ], + "text/plain": [ + " asset_id price size side \\\n", + "0 9798775853231434686333168031960771169483893798... 0.37 429.41 BUY \n", + "1 9295998185776670587912700877006205021408983550... 0.63 429.41 SELL \n", + "\n", + " hash best_bid best_ask \n", + "0 999d5b73d83a840c0df7dc2d817ae55a242845d5 0.37 0.38 \n", + "1 ead5af5a55f8b125b1e50f1c80a783c3c2d33187 0.62 0.63 " + ] + }, + "execution_count": 69, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "pd.DataFrame(d)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2fe32a82", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7603be6c", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 75, + "id": "c099da9a", + "metadata": {}, + "outputs": [], + "source": [ + "start = 1774585800\n", + "end = 1774585800 + 60*5" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b631306d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Timestamp('2026-03-27 04:35:00')" + ] + }, + "execution_count": 76, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "pd.to_datetime(start, unit='s')" + ] + }, + { + "cell_type": "code", + "execution_count": 77, + "id": "685d7dd9", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "300" + ] + }, + "execution_count": 77, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "end - start" + ] + }, + { + "cell_type": "code", + "execution_count": 78, + "id": "bdd681cc", + "metadata": {}, + "outputs": [], + "source": [ + "def format_timestamp(total_seconds):\n", + " minutes, seconds = divmod(total_seconds, 60)\n", + " print(f\"{minutes} minutes and {seconds} seconds\")" + ] + }, + { + "cell_type": "code", + "execution_count": 79, + "id": "7ba83cea", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "5 minutes and 0 seconds\n" + ] + } + ], + "source": [ + "format_timestamp(end - start)" + ] + }, + { + "cell_type": "code", + "execution_count": 88, + "id": "4e211e0d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1774586619" + ] + }, + "execution_count": 88, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "round(datetime.now().timestamp())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5dabe016", + "metadata": {}, + "outputs": [], + "source": [ + "1774585800" + ] + }, + { + "cell_type": "code", + "execution_count": 111, + "id": "8a65ba6f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Timestamp('2026-03-27 03:20:00')" + ] + }, + "execution_count": 111, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "pd.to_datetime(1774581600, unit='s')" + ] + }, + { + "cell_type": "code", + "execution_count": 91, + "id": "83f63c10", + "metadata": {}, + "outputs": [], + "source": [ + "sdt = '2026-03-27T03:20:00Z'" + ] + }, + { + "cell_type": "code", + "execution_count": 110, + "id": "1f907a71", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1774581600" + ] + }, + "execution_count": 110, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "round(datetime.strptime(sdt, '%Y-%m-%dT%H:%M:%SZ').replace(tzinfo=timezone.utc).timestamp())" + ] + }, + { + "cell_type": "code", + "execution_count": 113, + "id": "fe7c0c12", + "metadata": {}, + "outputs": [], + "source": [ + "d = {\"connection_id\":\"a4-i0ecArPECFLQ=\",\"payload\":{\"full_accuracy_value\":\"65779051825748830000000\",\"symbol\":\"btc/usd\",\"timestamp\":1774627572000,\"value\":65779.05182574883},\"timestamp\":1774627573524,\"topic\":\"crypto_prices_chainlink\",\"type\":\"update\"}" + ] + }, + { + "cell_type": "code", + "execution_count": 114, + "id": "a98ace05", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'connection_id': 'a4-i0ecArPECFLQ=',\n", + " 'payload': {'full_accuracy_value': '65779051825748830000000',\n", + " 'symbol': 'btc/usd',\n", + " 'timestamp': 1774627572000,\n", + " 'value': 65779.05182574883},\n", + " 'timestamp': 1774627573524,\n", + " 'topic': 'crypto_prices_chainlink',\n", + " 'type': 'update'}" + ] + }, + "execution_count": 114, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "d" + ] + }, + { + "cell_type": "code", + "execution_count": 120, + "id": "d2278853", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Timestamp('2026-03-27 16:06:13.524000')" + ] + }, + "execution_count": 120, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "pd.to_datetime(d['timestamp'], unit='ms')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "008cb5c9", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f6c647b7", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e1b8d116", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3f1e3a83", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "py313", + "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.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/ws.py b/ws.py new file mode 100644 index 0000000..461e516 --- /dev/null +++ b/ws.py @@ -0,0 +1,150 @@ +import asyncio +import json +import math +import pandas as pd +import os +from datetime import datetime, timezone +import websockets +import numpy as np +import talib +import requests + +WSS_URL = "wss://ws-subscriptions-clob.polymarket.com/ws/market" +SLUG_END_TIME = 0 + +HIST_TRADES = np.empty((0, 2)) + +def format_timestamp(total_seconds) -> str: + minutes, seconds = divmod(total_seconds, 60) + return f"{minutes} minutes and {seconds} seconds" + +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 + +def get_mkt_details_by_slug(slug: str) -> dict[str, str, str]: # {'Up' : 123, 'Down': 456, 'isActive': True, 'MinTickSize': 0.01, 'isNegRisk': False} + r = requests.get(f"https://gamma-api.polymarket.com/events/slug/{slug}") + market = r.json()['markets'][0] + token_ids = json.loads(market.get("clobTokenIds", "[]")) + outcomes = json.loads(market.get("outcomes", "[]")) + d = dict(zip(outcomes, token_ids)) + d['isActive'] = market['negRisk'] + d['MinTickSize'] = market['orderPriceMinTickSize'] + d['isNegRisk'] = market['negRisk'] + d['ConditionId'] = market['conditionId'] + d['EndDateTime'] = market['endDate'] + + return d, market + +def gen_slug(): + slug_prefix = 'btc-updown-5m-' + slug_time_id = time_round_down(dt=datetime.now(timezone.utc)) + return slug_prefix + str(slug_time_id) + + +async def polymarket_stream(): + global SLUG_END_TIME + global HIST_TRADES + + slug_full = gen_slug() + market_details, market = get_mkt_details_by_slug(slug_full) + TARGET_ASSET_ID = market_details['Up'] + SLUG_END_TIME = round(datetime.strptime(market_details['EndDateTime'], '%Y-%m-%dT%H:%M:%SZ').replace(tzinfo=timezone.utc).timestamp()) + print(f'********* NEW MKT - END DATETIME: {pd.to_datetime(SLUG_END_TIME, unit='s')} *********') + + async with websockets.connect(WSS_URL) as websocket: + print(f"Connected to {WSS_URL}") + + subscribe_msg = { + "assets_ids": [TARGET_ASSET_ID], + "type": "market", + "custom_feature_enabled": True + } + + await websocket.send(json.dumps(subscribe_msg)) + print(f"Subscribed to Asset: {TARGET_ASSET_ID}") + + try: + async for message in websocket: + current_ts = round(datetime.now().timestamp()) + sec_remaining = SLUG_END_TIME - current_ts + + if sec_remaining <= 0: + HIST_TRADES = np.empty((0, 2)) + + print('*** Attempting to unsub from past 5min') + update_unsub_msg = { + "operation": 'unsubscribe', + "assets_ids": [TARGET_ASSET_ID], + "custom_feature_enabled": True + } + await websocket.send(json.dumps(update_unsub_msg)) + + print('*** Attempting to SUB to new 5min') + slug_full = gen_slug() + market_details, market = get_mkt_details_by_slug(slug_full) + TARGET_ASSET_ID = market_details['Up'] + SLUG_END_TIME = round(datetime.strptime(market_details['EndDateTime'], '%Y-%m-%dT%H:%M:%SZ').replace(tzinfo=timezone.utc).timestamp()) + + update_sub_msg = { + "operation": 'subscribe', + "assets_ids": [TARGET_ASSET_ID], + "custom_feature_enabled": True + } + await websocket.send(json.dumps(update_sub_msg)) + + + if isinstance(message, str): + data = json.loads(message) + + if isinstance(data, dict): + # print(data.get("event_type", None)) + pass + elif isinstance(data, list): + print('initial book: ') + print(data) + continue + else: + raise ValueError(f'Type: {type(data)} not expected: {message}') + + event_type = data.get("event_type", None) + + if event_type == "price_change": + # print("📈 Price Change") + # print(pd.DataFrame(data['price_changes'])) + pass + elif event_type == "best_bid_ask": + # print(pd.DataFrame([data])) + pass + elif event_type == "last_trade_price": + px = float(data['price']) + qty = float(data['size']) + HIST_TRADES = np.append(HIST_TRADES, np.array([[px, qty]]), axis=0) + SMA = talib.ROC(HIST_TRADES[:,0], timeperiod=10)[-1] + print(f"✨ Last Px: {px:.2f}; ROC: {SMA:.4f}; Qty: {qty:6.2f}; Sec Left: {sec_remaining}") + elif event_type == "book": + pass + elif event_type == "new_market": + print('Received new_market') + elif event_type == "market_resolved": + print(f"Received: {event_type}") + print(data) + elif event_type == "tick_size_change": # may want for CLOB order routing + print(f"Received: {event_type}") + print(data) + else: + print(f"Received: {event_type}") + print(data) + + except websockets.ConnectionClosed: + print("Connection closed by server.") + + +if __name__ == '__main__': + try: + asyncio.run(polymarket_stream()) + except KeyboardInterrupt: + print("Stream stopped.") \ No newline at end of file diff --git a/ws_rtds.py b/ws_rtds.py new file mode 100644 index 0000000..e860944 --- /dev/null +++ b/ws_rtds.py @@ -0,0 +1,61 @@ +import asyncio +import json +import math +import pandas as pd +import os +from datetime import datetime, timezone +import websockets +import numpy as np +import talib +import requests + +WSS_URL = "wss://ws-live-data.polymarket.com" + +HIST_TRADES = np.empty((0, 2)) + + +async def rtds_stream(): + global HIST_TRADES + + async with websockets.connect(WSS_URL) as websocket: + print(f"Connected to {WSS_URL}") + + subscribe_msg = { + "action": "subscribe", + "subscriptions": [ + { + "topic": "crypto_prices_chainlink", + "type": "*", + "filters": "{\"symbol\":\"btc/usd\"}" + } + ] + } + + await websocket.send(json.dumps(subscribe_msg)) + + try: + async for message in websocket: + if isinstance(message, str): + try: + data = json.loads(message) + if data['payload'].get('value', None) is not None: + print(f'🤑 BTC Chainlink Last Px: {data['payload']['value']:_.4f}; TS: {pd.to_datetime(data['timestamp'], unit='ms')}') + else: + print(f'Initial or unexpected data struct, skipping: {data}') + continue + except (json.JSONDecodeError, ValueError): + print(f'Message not in JSON format, skipping: {message}') + continue + else: + raise ValueError(f'Type: {type(data)} not expected: {message}') + + + except websockets.ConnectionClosed: + print("Connection closed by server.") + + +if __name__ == '__main__': + try: + asyncio.run(rtds_stream()) + except KeyboardInterrupt: + print("Stream stopped.") \ No newline at end of file