Repo is ready
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
# Minimum position size to trigger position merging
|
||||
# Positions smaller than this will be ignored to save on gas costs
|
||||
MIN_MERGE_SIZE = 20
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,144 @@
|
||||
import json
|
||||
from sortedcontainers import SortedDict
|
||||
import poly_data.global_state as global_state
|
||||
import poly_data.CONSTANTS as CONSTANTS
|
||||
|
||||
from trading import perform_trade
|
||||
import time
|
||||
import asyncio
|
||||
from poly_data.data_utils import set_position, set_order, update_positions
|
||||
|
||||
def process_book_data(asset, json_data):
|
||||
global_state.all_data[asset] = {
|
||||
'bids': SortedDict(),
|
||||
'asks': SortedDict()
|
||||
}
|
||||
|
||||
global_state.all_data[asset]['bids'].update({float(entry['price']): float(entry['size']) for entry in json_data['bids']})
|
||||
global_state.all_data[asset]['asks'].update({float(entry['price']): float(entry['size']) for entry in json_data['asks']})
|
||||
|
||||
def process_price_change(asset, side, price_level, new_size):
|
||||
if side == 'bids':
|
||||
book = global_state.all_data[asset]['bids']
|
||||
else:
|
||||
book = global_state.all_data[asset]['asks']
|
||||
|
||||
if new_size == 0:
|
||||
if price_level in book:
|
||||
del book[price_level]
|
||||
else:
|
||||
book[price_level] = new_size
|
||||
|
||||
def process_data(json_data, trade=True):
|
||||
event_type = json_data['event_type']
|
||||
asset = json_data['market']
|
||||
|
||||
if event_type == 'book':
|
||||
process_book_data(asset, json_data)
|
||||
|
||||
if trade:
|
||||
asyncio.create_task(perform_trade(asset))
|
||||
|
||||
elif event_type == 'price_change':
|
||||
side = 'bids' if json_data['side'] == 'BUY' else 'asks'
|
||||
price_level = float(json_data['price'])
|
||||
new_size = float(json_data['size'])
|
||||
process_price_change(asset, side, price_level, new_size)
|
||||
|
||||
if trade:
|
||||
asyncio.create_task(perform_trade(asset))
|
||||
|
||||
|
||||
# pretty_print(f'Received book update for {asset}:', global_state.all_data[asset])
|
||||
|
||||
def add_to_performing(col, id):
|
||||
if col not in global_state.performing:
|
||||
global_state.performing[col] = set()
|
||||
|
||||
if col not in global_state.performing_timestamps:
|
||||
global_state.performing_timestamps[col] = {}
|
||||
|
||||
# Add the trade ID and track its timestamp
|
||||
global_state.performing[col].add(id)
|
||||
global_state.performing_timestamps[col][id] = time.time()
|
||||
|
||||
def remove_from_performing(col, id):
|
||||
if col in global_state.performing:
|
||||
global_state.performing[col].discard(id)
|
||||
|
||||
if col in global_state.performing_timestamps:
|
||||
global_state.performing_timestamps[col].pop(id, None)
|
||||
|
||||
def process_user_data(row):
|
||||
market = row['market']
|
||||
|
||||
side = row['side'].lower()
|
||||
token = row['asset_id']
|
||||
|
||||
if token in global_state.REVERSE_TOKENS:
|
||||
col = token + "_" + side
|
||||
|
||||
if row['event_type'] == 'trade':
|
||||
size = 0
|
||||
price = 0
|
||||
maker_outcome = ""
|
||||
taker_outcome = row['outcome']
|
||||
|
||||
is_user_maker = False
|
||||
for maker_order in row['maker_orders']:
|
||||
if maker_order['maker_address'].lower() == global_state.client.browser_wallet.lower():
|
||||
print("User is maker")
|
||||
size = float(maker_order['matched_amount'])
|
||||
price = float(maker_order['price'])
|
||||
|
||||
is_user_maker = True
|
||||
maker_outcome = maker_order['outcome'] #this is curious
|
||||
|
||||
if maker_outcome == taker_outcome:
|
||||
side = 'buy' if side == 'sell' else 'sell' #need to reverse as we reverse token too
|
||||
else:
|
||||
token = global_state.REVERSE_TOKENS[token]
|
||||
|
||||
if not is_user_maker:
|
||||
size = float(row['size'])
|
||||
price = float(row['price'])
|
||||
print("User is taker")
|
||||
|
||||
print("TRADE EVENT FOR: ", row['market'], "ID: ", row['id'], "STATUS: ", row['status'], " SIDE: ", row['side'], " MAKER OUTCOME: ", maker_outcome, " TAKER OUTCOME: ", taker_outcome, " PROCESSED SIDE: ", side, " SIZE: ", size)
|
||||
|
||||
|
||||
if row['status'] == 'CONFIRMED' or row['status'] == 'FAILED' :
|
||||
if row['status'] == 'FAILED':
|
||||
print(f"Trade failed for {token}, decreasing")
|
||||
asyncio.create_task(asyncio.sleep(2))
|
||||
update_positions()
|
||||
else:
|
||||
remove_from_performing(col, row['id'])
|
||||
print("Confirmed. Performing is ", len(global_state.performing[col]))
|
||||
print("Last trade update is ", global_state.last_trade_update)
|
||||
print("Performing is ", global_state.performing)
|
||||
print("Performing timestamps is ", global_state.performing_timestamps)
|
||||
|
||||
asyncio.create_task(perform_trade(market))
|
||||
|
||||
elif row['status'] == 'MATCHED':
|
||||
add_to_performing(col, row['id'])
|
||||
|
||||
print("Matched. Performing is ", len(global_state.performing[col]))
|
||||
set_position(token, side, size, price)
|
||||
print("Position after matching is ", global_state.positions[str(token)])
|
||||
print("Last trade update is ", global_state.last_trade_update)
|
||||
print("Performing is ", global_state.performing)
|
||||
print("Performing timestamps is ", global_state.performing_timestamps)
|
||||
asyncio.create_task(perform_trade(market))
|
||||
elif row['status'] == 'MINED':
|
||||
remove_from_performing(col, row['id'])
|
||||
|
||||
elif row['event_type'] == 'order':
|
||||
print("ORDER EVENT FOR: ", row['market'], " STATUS: ", row['status'], " TYPE: ", row['type'], " SIDE: ", side, " ORIGINAL SIZE: ", row['original_size'], " SIZE MATCHED: ", row['size_matched'])
|
||||
|
||||
set_order(token, side, float(row['original_size']) - float(row['size_matched']), row['price'])
|
||||
asyncio.create_task(perform_trade(market))
|
||||
|
||||
else:
|
||||
print(f"User date received for {market} but its not in")
|
||||
@@ -0,0 +1,169 @@
|
||||
import poly_data.global_state as global_state
|
||||
from poly_data.utils import get_sheet_df
|
||||
import time
|
||||
import poly_data.global_state as global_state
|
||||
|
||||
#sth here seems to be removing the position
|
||||
def update_positions(avgOnly=False):
|
||||
pos_df = global_state.client.get_all_positions()
|
||||
|
||||
for idx, row in pos_df.iterrows():
|
||||
asset = str(row['asset'])
|
||||
|
||||
if asset in global_state.positions:
|
||||
position = global_state.positions[asset].copy()
|
||||
else:
|
||||
position = {'size': 0, 'avgPrice': 0}
|
||||
|
||||
position['avgPrice'] = row['avgPrice']
|
||||
|
||||
if not avgOnly:
|
||||
position['size'] = row['size']
|
||||
else:
|
||||
|
||||
for col in [f"{asset}_sell", f"{asset}_buy"]:
|
||||
#need to review this
|
||||
if col not in global_state.performing or not isinstance(global_state.performing[col], set) or len(global_state.performing[col]) == 0:
|
||||
try:
|
||||
old_size = position['size']
|
||||
except:
|
||||
old_size = 0
|
||||
|
||||
if asset in global_state.last_trade_update:
|
||||
if time.time() - global_state.last_trade_update[asset] < 5:
|
||||
print(f"Skipping update for {asset} because last trade update was less than 5 seconds ago")
|
||||
continue
|
||||
|
||||
if old_size != row['size']:
|
||||
print(f"No trades are pending. Updating position from {old_size} to {row['size']} and avgPrice to {row['avgPrice']} using API")
|
||||
|
||||
position['size'] = row['size']
|
||||
else:
|
||||
print(f"ALERT: Skipping update for {asset} because there are trades pending for {col} looking like {global_state.performing[col]}")
|
||||
|
||||
global_state.positions[asset] = position
|
||||
|
||||
def get_position(token):
|
||||
token = str(token)
|
||||
if token in global_state.positions:
|
||||
return global_state.positions[token]
|
||||
else:
|
||||
return {'size': 0, 'avgPrice': 0}
|
||||
|
||||
def set_position(token, side, size, price, source='websocket'):
|
||||
token = str(token)
|
||||
size = float(size)
|
||||
price = float(price)
|
||||
|
||||
global_state.last_trade_update[token] = time.time()
|
||||
|
||||
if side.lower() == 'sell':
|
||||
size *= -1
|
||||
|
||||
if token in global_state.positions:
|
||||
|
||||
prev_price = global_state.positions[token]['avgPrice']
|
||||
prev_size = global_state.positions[token]['size']
|
||||
|
||||
|
||||
if size > 0:
|
||||
if prev_size == 0:
|
||||
# Starting a new position
|
||||
avgPrice_new = price
|
||||
else:
|
||||
# Buying more; update average price
|
||||
avgPrice_new = (prev_price * prev_size + price * size) / (prev_size + size)
|
||||
elif size < 0:
|
||||
# Selling; average price remains the same
|
||||
avgPrice_new = prev_price
|
||||
else:
|
||||
# No change in position
|
||||
avgPrice_new = prev_price
|
||||
|
||||
|
||||
global_state.positions[token]['size'] += size
|
||||
global_state.positions[token]['avgPrice'] = avgPrice_new
|
||||
else:
|
||||
global_state.positions[token] = {'size': size, 'avgPrice': price}
|
||||
|
||||
print(f"Updated position from {source}, set to ", global_state.positions[token])
|
||||
|
||||
def update_orders():
|
||||
all_orders = global_state.client.get_all_orders()
|
||||
|
||||
orders = {}
|
||||
|
||||
if len(all_orders) > 0:
|
||||
for token in all_orders['asset_id'].unique():
|
||||
|
||||
if token not in orders:
|
||||
orders[str(token)] = {'buy': {'price': 0, 'size': 0}, 'sell': {'price': 0, 'size': 0}}
|
||||
|
||||
curr_orders = all_orders[all_orders['asset_id'] == str(token)]
|
||||
|
||||
if len(curr_orders) > 0:
|
||||
sel_orders = {}
|
||||
sel_orders['buy'] = curr_orders[curr_orders['side'] == 'BUY']
|
||||
sel_orders['sell'] = curr_orders[curr_orders['side'] == 'SELL']
|
||||
|
||||
for type in ['buy', 'sell']:
|
||||
curr = sel_orders[type]
|
||||
|
||||
if len(curr) > 1:
|
||||
print("Multiple orders found, cancelling")
|
||||
global_state.client.cancel_all_asset(token)
|
||||
orders[str(token)] = {'buy': {'price': 0, 'size': 0}, 'sell': {'price': 0, 'size': 0}}
|
||||
elif len(curr) == 1:
|
||||
orders[str(token)][type]['price'] = float(curr.iloc[0]['price'])
|
||||
orders[str(token)][type]['size'] = float(curr.iloc[0]['original_size'] - curr.iloc[0]['size_matched'])
|
||||
|
||||
global_state.orders = orders
|
||||
|
||||
def get_order(token):
|
||||
token = str(token)
|
||||
if token in global_state.orders:
|
||||
|
||||
if 'buy' not in global_state.orders[token]:
|
||||
global_state.orders[token]['buy'] = {'price': 0, 'size': 0}
|
||||
|
||||
if 'sell' not in global_state.orders[token]:
|
||||
global_state.orders[token]['sell'] = {'price': 0, 'size': 0}
|
||||
|
||||
return global_state.orders[token]
|
||||
else:
|
||||
return {'buy': {'price': 0, 'size': 0}, 'sell': {'price': 0, 'size': 0}}
|
||||
|
||||
def set_order(token, side, size, price):
|
||||
curr = {}
|
||||
curr = {side: {'price': 0, 'size': 0}}
|
||||
|
||||
curr[side]['size'] = float(size)
|
||||
curr[side]['price'] = float(price)
|
||||
|
||||
global_state.orders[str(token)] = curr
|
||||
print("Updated order, set to ", curr)
|
||||
|
||||
|
||||
|
||||
def update_markets():
|
||||
received_df, received_params = get_sheet_df()
|
||||
|
||||
if len(received_df) > 0:
|
||||
global_state.df, global_state.params = received_df.copy(), received_params
|
||||
|
||||
for idx, row in global_state.df.iterrows():
|
||||
for col in ['token1', 'token2']:
|
||||
row[col] = str(row[col])
|
||||
|
||||
if row['token1'] not in global_state.all_tokens:
|
||||
global_state.all_tokens.append(row['token1'])
|
||||
|
||||
if row['token1'] not in global_state.REVERSE_TOKENS:
|
||||
global_state.REVERSE_TOKENS[row['token1']] = row['token2']
|
||||
|
||||
if row['token2'] not in global_state.REVERSE_TOKENS:
|
||||
global_state.REVERSE_TOKENS[row['token2']] = row['token1']
|
||||
|
||||
for col2 in [f"{row['token1']}_buy", f"{row['token1']}_sell", f"{row['token2']}_buy", f"{row['token2']}_sell"]:
|
||||
if col2 not in global_state.performing:
|
||||
global_state.performing[col2] = set()
|
||||
@@ -0,0 +1,49 @@
|
||||
import threading
|
||||
import pandas as pd
|
||||
|
||||
# ============ Market Data ============
|
||||
|
||||
# List of all tokens being tracked
|
||||
all_tokens = []
|
||||
|
||||
# Mapping between tokens in the same market (YES->NO, NO->YES)
|
||||
REVERSE_TOKENS = {}
|
||||
|
||||
# Order book data for all markets
|
||||
all_data = {}
|
||||
|
||||
# Market configuration data from Google Sheets
|
||||
df = None
|
||||
|
||||
# ============ Client & Parameters ============
|
||||
|
||||
# Polymarket client instance
|
||||
client = None
|
||||
|
||||
# Trading parameters from Google Sheets
|
||||
params = {}
|
||||
|
||||
# Lock for thread-safe trading operations
|
||||
lock = threading.Lock()
|
||||
|
||||
# ============ Trading State ============
|
||||
|
||||
# Tracks trades that have been matched but not yet mined
|
||||
# Format: {"token_side": {trade_id1, trade_id2, ...}}
|
||||
performing = {}
|
||||
|
||||
# Timestamps for when trades were added to performing
|
||||
# Used to clear stale trades
|
||||
performing_timestamps = {}
|
||||
|
||||
# Timestamps for when positions were last updated
|
||||
last_trade_update = {}
|
||||
|
||||
# Current open orders for each token
|
||||
# Format: {token_id: {'buy': {price, size}, 'sell': {price, size}}}
|
||||
orders = {}
|
||||
|
||||
# Current positions for each token
|
||||
# Format: {token_id: {'size': float, 'avgPrice': float}}
|
||||
positions = {}
|
||||
|
||||
@@ -0,0 +1,320 @@
|
||||
from dotenv import load_dotenv # Environment variable management
|
||||
import os # Operating system interface
|
||||
|
||||
# Polymarket API client libraries
|
||||
from py_clob_client.client import ClobClient
|
||||
from py_clob_client.clob_types import OrderArgs, BalanceAllowanceParams, AssetType, PartialCreateOrderOptions
|
||||
from py_clob_client.constants import POLYGON
|
||||
|
||||
# Web3 libraries for blockchain interaction
|
||||
from web3 import Web3
|
||||
from web3.middleware import geth_poa_middleware
|
||||
from eth_account import Account
|
||||
|
||||
import requests # HTTP requests
|
||||
import pandas as pd # Data analysis
|
||||
import json # JSON processing
|
||||
import subprocess # For calling external processes
|
||||
|
||||
from py_clob_client.clob_types import OpenOrderParams
|
||||
|
||||
# Smart contract ABIs
|
||||
from poly_data.abis import NegRiskAdapterABI, ConditionalTokenABI, erc20_abi
|
||||
|
||||
# Load environment variables
|
||||
load_dotenv()
|
||||
|
||||
|
||||
class PolymarketClient:
|
||||
"""
|
||||
Client for interacting with Polymarket's API and smart contracts.
|
||||
|
||||
This class provides methods for:
|
||||
- Creating and managing orders
|
||||
- Querying order book data
|
||||
- Checking balances and positions
|
||||
- Merging positions
|
||||
|
||||
The client connects to both the Polymarket API and the Polygon blockchain.
|
||||
"""
|
||||
|
||||
def __init__(self, pk='default') -> None:
|
||||
"""
|
||||
Initialize the Polymarket client with API and blockchain connections.
|
||||
|
||||
Args:
|
||||
pk (str, optional): Private key identifier, defaults to 'default'
|
||||
"""
|
||||
host="https://clob.polymarket.com"
|
||||
|
||||
# Get credentials from environment variables
|
||||
key=os.getenv("PK")
|
||||
browser_address = os.getenv("BROWSER_ADDRESS")
|
||||
|
||||
# Don't print sensitive wallet information
|
||||
print("Initializing Polymarket client...")
|
||||
chain_id=POLYGON
|
||||
self.browser_wallet=Web3.toChecksumAddress(browser_address)
|
||||
|
||||
# Initialize the Polymarket API client
|
||||
self.client = ClobClient(
|
||||
host=host,
|
||||
key=key,
|
||||
chain_id=chain_id,
|
||||
funder=self.browser_wallet,
|
||||
signature_type=2
|
||||
)
|
||||
|
||||
# Set up API credentials
|
||||
self.creds = self.client.create_or_derive_api_creds()
|
||||
self.client.set_api_creds(creds=self.creds)
|
||||
|
||||
# Initialize Web3 connection to Polygon
|
||||
web3 = Web3(Web3.HTTPProvider("https://polygon-rpc.com"))
|
||||
web3.middleware_onion.inject(geth_poa_middleware, layer=0)
|
||||
|
||||
# Set up USDC contract for balance checks
|
||||
self.usdc_contract = web3.eth.contract(
|
||||
address="0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174",
|
||||
abi=erc20_abi
|
||||
)
|
||||
|
||||
# Store key contract addresses
|
||||
self.addresses = {
|
||||
'neg_risk_adapter': '0xd91E80cF2E7be2e162c6513ceD06f1dD0dA35296',
|
||||
'collateral': '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174',
|
||||
'conditional_tokens': '0x4D97DCd97eC945f40cF65F87097ACe5EA0476045'
|
||||
}
|
||||
|
||||
# Initialize contract interfaces
|
||||
self.neg_risk_adapter = web3.eth.contract(
|
||||
address=self.addresses['neg_risk_adapter'],
|
||||
abi=NegRiskAdapterABI
|
||||
)
|
||||
|
||||
self.conditional_tokens = web3.eth.contract(
|
||||
address=self.addresses['conditional_tokens'],
|
||||
abi=ConditionalTokenABI
|
||||
)
|
||||
|
||||
self.web3 = web3
|
||||
|
||||
|
||||
def create_order(self, marketId, action, price, size, neg_risk=False):
|
||||
"""
|
||||
Create and submit a new order to the Polymarket order book.
|
||||
|
||||
Args:
|
||||
marketId (str): ID of the market token to trade
|
||||
action (str): "BUY" or "SELL"
|
||||
price (float): Order price (0-1 range for prediction markets)
|
||||
size (float): Order size in USDC
|
||||
neg_risk (bool, optional): Whether this is a negative risk market. Defaults to False.
|
||||
|
||||
Returns:
|
||||
dict: Response from the API containing order details, or empty dict on error
|
||||
"""
|
||||
# Create order parameters
|
||||
order_args = OrderArgs(
|
||||
token_id=str(marketId),
|
||||
price=price,
|
||||
size=size,
|
||||
side=action
|
||||
)
|
||||
|
||||
signed_order = None
|
||||
|
||||
# Handle regular vs negative risk markets differently
|
||||
if neg_risk == False:
|
||||
signed_order = self.client.create_order(order_args)
|
||||
else:
|
||||
signed_order = self.client.create_order(order_args, options=PartialCreateOrderOptions(neg_risk=True))
|
||||
|
||||
try:
|
||||
# Submit the signed order to the API
|
||||
resp = self.client.post_order(signed_order)
|
||||
return resp
|
||||
except Exception as ex:
|
||||
print(ex)
|
||||
return {}
|
||||
|
||||
def get_order_book(self, market):
|
||||
"""
|
||||
Get the current order book for a specific market.
|
||||
|
||||
Args:
|
||||
market (str): Market ID to query
|
||||
|
||||
Returns:
|
||||
tuple: (bids_df, asks_df) - DataFrames containing bid and ask orders
|
||||
"""
|
||||
orderBook = self.client.get_order_book(market)
|
||||
return pd.DataFrame(orderBook.bids).astype(float), pd.DataFrame(orderBook.asks).astype(float)
|
||||
|
||||
|
||||
def get_usdc_balance(self):
|
||||
"""
|
||||
Get the USDC balance of the connected wallet.
|
||||
|
||||
Returns:
|
||||
float: USDC balance in decimal format
|
||||
"""
|
||||
return self.usdc_contract.functions.balanceOf(self.browser_wallet).call() / 10**6
|
||||
|
||||
def get_pos_balance(self):
|
||||
"""
|
||||
Get the total value of all positions for the connected wallet.
|
||||
|
||||
Returns:
|
||||
float: Total position value in USDC
|
||||
"""
|
||||
res = requests.get(f'https://data-api.polymarket.com/value?user={self.browser_wallet}')
|
||||
return float(res.json()['value'])
|
||||
|
||||
def get_total_balance(self):
|
||||
"""
|
||||
Get the combined value of USDC balance and all positions.
|
||||
|
||||
Returns:
|
||||
float: Total account value in USDC
|
||||
"""
|
||||
return self.get_usdc_balance() + self.get_pos_balance()
|
||||
|
||||
def get_all_positions(self):
|
||||
"""
|
||||
Get all positions for the connected wallet across all markets.
|
||||
|
||||
Returns:
|
||||
DataFrame: All positions with details like market, size, avgPrice
|
||||
"""
|
||||
res = requests.get(f'https://data-api.polymarket.com/positions?user={self.browser_wallet}')
|
||||
return pd.DataFrame(res.json())
|
||||
|
||||
def get_raw_position(self, tokenId):
|
||||
"""
|
||||
Get the raw token balance for a specific market outcome token.
|
||||
|
||||
Args:
|
||||
tokenId (int): Token ID to query
|
||||
|
||||
Returns:
|
||||
int: Raw token amount (before decimal conversion)
|
||||
"""
|
||||
return int(self.conditional_tokens.functions.balanceOf(self.browser_wallet, int(tokenId)).call())
|
||||
|
||||
def get_position(self, tokenId):
|
||||
"""
|
||||
Get both raw and formatted position size for a token.
|
||||
|
||||
Args:
|
||||
tokenId (int): Token ID to query
|
||||
|
||||
Returns:
|
||||
tuple: (raw_position, shares) - Raw token amount and decimal shares
|
||||
Shares less than 1 are treated as 0 to avoid dust amounts
|
||||
"""
|
||||
raw_position = self.get_raw_position(tokenId)
|
||||
shares = float(raw_position / 1e6)
|
||||
|
||||
# Ignore very small positions (dust)
|
||||
if shares < 1:
|
||||
shares = 0
|
||||
|
||||
return raw_position, shares
|
||||
|
||||
def get_all_orders(self):
|
||||
"""
|
||||
Get all open orders for the connected wallet.
|
||||
|
||||
Returns:
|
||||
DataFrame: All open orders with their details
|
||||
"""
|
||||
orders_df = pd.DataFrame(self.client.get_orders())
|
||||
|
||||
# Convert numeric columns to float
|
||||
for col in ['original_size', 'size_matched', 'price']:
|
||||
if col in orders_df.columns:
|
||||
orders_df[col] = orders_df[col].astype(float)
|
||||
|
||||
return orders_df
|
||||
|
||||
def get_market_orders(self, market):
|
||||
"""
|
||||
Get all open orders for a specific market.
|
||||
|
||||
Args:
|
||||
market (str): Market ID to query
|
||||
|
||||
Returns:
|
||||
DataFrame: Open orders for the specified market
|
||||
"""
|
||||
orders_df = pd.DataFrame(self.client.get_orders(OpenOrderParams(
|
||||
market=market,
|
||||
)))
|
||||
|
||||
# Convert numeric columns to float
|
||||
for col in ['original_size', 'size_matched', 'price']:
|
||||
if col in orders_df.columns:
|
||||
orders_df[col] = orders_df[col].astype(float)
|
||||
|
||||
return orders_df
|
||||
|
||||
|
||||
def cancel_all_asset(self, asset_id):
|
||||
"""
|
||||
Cancel all orders for a specific asset token.
|
||||
|
||||
Args:
|
||||
asset_id (str): Asset token ID
|
||||
"""
|
||||
self.client.cancel_market_orders(asset_id=str(asset_id))
|
||||
|
||||
|
||||
|
||||
def cancel_all_market(self, marketId):
|
||||
"""
|
||||
Cancel all orders in a specific market.
|
||||
|
||||
Args:
|
||||
marketId (str): Market ID
|
||||
"""
|
||||
self.client.cancel_market_orders(market=marketId)
|
||||
|
||||
|
||||
def merge_positions(self, amount_to_merge, condition_id, is_neg_risk_market):
|
||||
"""
|
||||
Merge positions in a market to recover collateral.
|
||||
|
||||
This function calls the external poly_merger Node.js script to execute
|
||||
the merge operation on-chain. When you hold both YES and NO positions
|
||||
in the same market, merging them recovers your USDC.
|
||||
|
||||
Args:
|
||||
amount_to_merge (int): Raw token amount to merge (before decimal conversion)
|
||||
condition_id (str): Market condition ID
|
||||
is_neg_risk_market (bool): Whether this is a negative risk market
|
||||
|
||||
Returns:
|
||||
str: Transaction hash or output from the merge script
|
||||
|
||||
Raises:
|
||||
Exception: If the merge operation fails
|
||||
"""
|
||||
amount_to_merge_str = str(amount_to_merge)
|
||||
|
||||
# Prepare the command to run the JavaScript script
|
||||
node_command = f'node poly_merger/merge.js {amount_to_merge_str} {condition_id} {"true" if is_neg_risk_market else "false"}'
|
||||
print(node_command)
|
||||
|
||||
# Run the command and capture the output
|
||||
result = subprocess.run(node_command, shell=True, capture_output=True, text=True)
|
||||
|
||||
# Check if there was an error
|
||||
if result.returncode != 0:
|
||||
print("Error:", result.stderr)
|
||||
raise Exception(f"Error in merging positions: {result.stderr}")
|
||||
|
||||
print("Done merging")
|
||||
|
||||
# Return the transaction hash or output
|
||||
return result.stdout
|
||||
@@ -0,0 +1,148 @@
|
||||
import math
|
||||
from poly_data.data_utils import update_positions
|
||||
import poly_data.global_state as global_state
|
||||
|
||||
# def get_avgPrice(position, assetId):
|
||||
# curr_global = global_state.all_positions[global_state.all_positions['asset'] == str(assetId)]
|
||||
# api_position_size = 0
|
||||
# api_avgPrice = 0
|
||||
|
||||
# if len(curr_global) > 0:
|
||||
# c_row = curr_global.iloc[0]
|
||||
# api_avgPrice = round(c_row['avgPrice'], 2)
|
||||
# api_position_size = c_row['size']
|
||||
|
||||
# if position > 0:
|
||||
# if abs((api_position_size - position)/position * 100) > 5:
|
||||
# print("Updating global positions")
|
||||
# update_positions()
|
||||
|
||||
# try:
|
||||
# c_row = curr_global.iloc[0]
|
||||
# api_avgPrice = round(c_row['avgPrice'], 2)
|
||||
# api_position_size = c_row['size']
|
||||
# except:
|
||||
# return 0
|
||||
# return api_avgPrice
|
||||
|
||||
def get_best_bid_ask_deets(market, name, size, deviation_threshold=0.05):
|
||||
|
||||
best_bid, best_bid_size, second_best_bid, second_best_bid_size, top_bid = find_best_price_with_size(global_state.all_data[market]['bids'], size, reverse=True)
|
||||
best_ask, best_ask_size, second_best_ask, second_best_ask_size, top_ask = find_best_price_with_size(global_state.all_data[market]['asks'], size, reverse=False)
|
||||
|
||||
mid_price = (best_bid + best_ask) / 2
|
||||
|
||||
bid_sum_within_n_percent = sum(size for price, size in global_state.all_data[market]['bids'].items() if best_bid <= price <= mid_price * (1 + deviation_threshold))
|
||||
ask_sum_within_n_percent = sum(size for price, size in global_state.all_data[market]['asks'].items() if mid_price * (1 - deviation_threshold) <= price <= best_ask)
|
||||
|
||||
if name == 'token2':
|
||||
best_bid, second_best_bid, top_bid, best_ask, second_best_ask, top_ask = 1 - best_ask, 1 - second_best_ask, 1 - top_ask, 1 - best_bid, 1 - second_best_bid, 1 - top_bid
|
||||
best_bid_size, second_best_bid_size, best_ask_size, second_best_ask_size = best_ask_size, second_best_ask_size, best_bid_size, second_best_bid_size
|
||||
bid_sum_within_n_percent, ask_sum_within_n_percent = ask_sum_within_n_percent, bid_sum_within_n_percent
|
||||
|
||||
|
||||
|
||||
#return as dictionary
|
||||
return {
|
||||
'best_bid': best_bid,
|
||||
'best_bid_size': best_bid_size,
|
||||
'second_best_bid': second_best_bid,
|
||||
'second_best_bid_size': second_best_bid_size,
|
||||
'top_bid': top_bid,
|
||||
'best_ask': best_ask,
|
||||
'best_ask_size': best_ask_size,
|
||||
'second_best_ask': second_best_ask,
|
||||
'second_best_ask_size': second_best_ask_size,
|
||||
'top_ask': top_ask,
|
||||
'bid_sum_within_n_percent': bid_sum_within_n_percent,
|
||||
'ask_sum_within_n_percent': ask_sum_within_n_percent
|
||||
}
|
||||
|
||||
|
||||
def find_best_price_with_size(price_dict, min_size, reverse=False):
|
||||
lst = list(price_dict.items())
|
||||
|
||||
if reverse:
|
||||
lst.reverse()
|
||||
|
||||
best_price, best_size = None, None
|
||||
second_best_price, second_best_size = None, None
|
||||
top_price = None
|
||||
set_best = False
|
||||
|
||||
for price, size in lst:
|
||||
if top_price is None:
|
||||
top_price = price
|
||||
|
||||
if set_best:
|
||||
second_best_price, second_best_size = price, size
|
||||
break
|
||||
|
||||
if size > min_size:
|
||||
if best_price is None:
|
||||
best_price, best_size = price, size
|
||||
set_best = True
|
||||
|
||||
return best_price, best_size, second_best_price, second_best_size, top_price
|
||||
|
||||
def get_order_prices(best_bid, best_bid_size, top_bid, best_ask, best_ask_size, top_ask, avgPrice, row):
|
||||
|
||||
bid_price = best_bid + row['tick_size']
|
||||
ask_price = best_ask - row['tick_size']
|
||||
|
||||
if best_bid_size < row['min_size'] * 1.5:
|
||||
bid_price = best_bid
|
||||
|
||||
if best_ask_size < 250 * 1.5:
|
||||
ask_price = best_ask
|
||||
|
||||
|
||||
if bid_price >= top_ask:
|
||||
bid_price = top_bid
|
||||
|
||||
if ask_price <= top_bid:
|
||||
ask_price = top_ask
|
||||
|
||||
if bid_price == ask_price:
|
||||
bid_price = top_bid
|
||||
ask_price = top_ask
|
||||
|
||||
# if ask_price <= avgPrice:
|
||||
# if avgPrice - ask_price <= (row['max_spread']*1.7/100):
|
||||
# ask_price = avgPrice
|
||||
|
||||
#temp for sleep
|
||||
if ask_price <= avgPrice and avgPrice > 0:
|
||||
ask_price = avgPrice
|
||||
|
||||
return bid_price, ask_price
|
||||
|
||||
|
||||
|
||||
|
||||
def round_down(number, decimals):
|
||||
factor = 10 ** decimals
|
||||
return math.floor(number * factor) / factor
|
||||
|
||||
def round_up(number, decimals):
|
||||
factor = 10 ** decimals
|
||||
return math.ceil(number * factor) / factor
|
||||
|
||||
def get_buy_sell_amount(position, bid_price, row):
|
||||
buy_amount = 0
|
||||
sell_amount = 0
|
||||
|
||||
sell_amount = position
|
||||
buy_amount = row['trade_size'] - position
|
||||
|
||||
if buy_amount > 0.7 * row['min_size'] and buy_amount < row['min_size']:
|
||||
buy_amount = row['min_size']
|
||||
|
||||
if bid_price < 0.1:
|
||||
|
||||
if row['multiplier'] != '':
|
||||
print(f"Multiplying buy amount by {int(row['multiplier'])}")
|
||||
buy_amount = buy_amount * int(row['multiplier'])
|
||||
|
||||
return buy_amount, sell_amount
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import json
|
||||
from poly_utils.google_utils import get_spreadsheet
|
||||
import pandas as pd
|
||||
|
||||
def pretty_print(txt, dic):
|
||||
print("\n", txt, json.dumps(dic, indent=4))
|
||||
|
||||
def get_sheet_df():
|
||||
|
||||
all = 'All Markets'
|
||||
sel = 'Selected Markets'
|
||||
|
||||
spreadsheet = get_spreadsheet()
|
||||
|
||||
wk = spreadsheet.worksheet(sel)
|
||||
df = pd.DataFrame(wk.get_all_records())
|
||||
df = df[df['question'] != ""].reset_index(drop=True)
|
||||
|
||||
wk2 = spreadsheet.worksheet(all)
|
||||
df2 = pd.DataFrame(wk2.get_all_records())
|
||||
df2 = df2[df2['question'] != ""].reset_index(drop=True)
|
||||
|
||||
|
||||
result = df.merge(df2, on='question', how='inner')
|
||||
|
||||
wk_p = spreadsheet.worksheet('Hyperparameters')
|
||||
records = wk_p.get_all_records()
|
||||
hyperparams, current_type = {}, None
|
||||
|
||||
for r in records:
|
||||
current_type = r['type'] or current_type
|
||||
hyperparams.setdefault(current_type, {})[r['param']] = r['value']
|
||||
|
||||
return result, hyperparams
|
||||
@@ -0,0 +1,98 @@
|
||||
import asyncio # Asynchronous I/O
|
||||
import json # JSON handling
|
||||
import websockets # WebSocket client
|
||||
import traceback # Exception handling
|
||||
|
||||
from poly_data.data_processing import process_data, process_user_data
|
||||
import poly_data.global_state as global_state
|
||||
|
||||
async def connect_market_websocket(chunk):
|
||||
"""
|
||||
Connect to Polymarket's market WebSocket API and process market updates.
|
||||
|
||||
This function:
|
||||
1. Establishes a WebSocket connection to the Polymarket API
|
||||
2. Subscribes to updates for a specified list of market tokens
|
||||
3. Processes incoming order book and price updates
|
||||
|
||||
Args:
|
||||
chunk (list): List of token IDs to subscribe to
|
||||
|
||||
Notes:
|
||||
If the connection is lost, the function will exit and the main loop will
|
||||
attempt to reconnect after a short delay.
|
||||
"""
|
||||
uri = "wss://ws-subscriptions-clob.polymarket.com/ws/market"
|
||||
async with websockets.connect(uri, ping_interval=5, ping_timeout=None) as websocket:
|
||||
# Prepare and send subscription message
|
||||
message = {"assets_ids": chunk}
|
||||
await websocket.send(json.dumps(message))
|
||||
|
||||
print("\n")
|
||||
print(f"Sent market subscription message: {message}")
|
||||
|
||||
try:
|
||||
# Process incoming market data indefinitely
|
||||
while True:
|
||||
message = await websocket.recv()
|
||||
json_data = json.loads(message)
|
||||
# Process order book updates and trigger trading as needed
|
||||
process_data(json_data)
|
||||
except websockets.ConnectionClosed:
|
||||
print("Connection closed in market websocket")
|
||||
print(traceback.format_exc())
|
||||
except Exception as e:
|
||||
print(f"Exception in market websocket: {e}")
|
||||
print(traceback.format_exc())
|
||||
finally:
|
||||
# Brief delay before attempting to reconnect
|
||||
await asyncio.sleep(5)
|
||||
|
||||
async def connect_user_websocket():
|
||||
"""
|
||||
Connect to Polymarket's user WebSocket API and process order/trade updates.
|
||||
|
||||
This function:
|
||||
1. Establishes a WebSocket connection to the Polymarket user API
|
||||
2. Authenticates using API credentials
|
||||
3. Processes incoming order and trade updates for the user
|
||||
|
||||
Notes:
|
||||
If the connection is lost, the function will exit and the main loop will
|
||||
attempt to reconnect after a short delay.
|
||||
"""
|
||||
uri = "wss://ws-subscriptions-clob.polymarket.com/ws/user"
|
||||
|
||||
async with websockets.connect(uri, ping_interval=5, ping_timeout=None) as websocket:
|
||||
# Prepare authentication message with API credentials
|
||||
message = {
|
||||
"type": "user",
|
||||
"auth": {
|
||||
"apiKey": global_state.client.client.creds.api_key,
|
||||
"secret": global_state.client.client.creds.api_secret,
|
||||
"passphrase": global_state.client.client.creds.api_passphrase
|
||||
}
|
||||
}
|
||||
|
||||
# Send authentication message
|
||||
await websocket.send(json.dumps(message))
|
||||
|
||||
print("\n")
|
||||
print(f"Sent user subscription message")
|
||||
|
||||
try:
|
||||
# Process incoming user data indefinitely
|
||||
while True:
|
||||
message = await websocket.recv()
|
||||
json_data = json.loads(message)
|
||||
# Process trade and order updates
|
||||
process_user_data(json_data)
|
||||
except websockets.ConnectionClosed:
|
||||
print("Connection closed in user websocket")
|
||||
print(traceback.format_exc())
|
||||
except Exception as e:
|
||||
print(f"Exception in user websocket: {e}")
|
||||
print(traceback.format_exc())
|
||||
finally:
|
||||
# Brief delay before attempting to reconnect
|
||||
await asyncio.sleep(5)
|
||||
Reference in New Issue
Block a user