2025-03-31 12:16:51 -04:00
import gc # Garbage collection
import os # Operating system interface
import json # JSON handling
import asyncio # Asynchronous I/O
import traceback # Exception handling
import pandas as pd # Data analysis library
import math # Mathematical functions
import poly_data.global_state as global_state
import poly_data.CONSTANTS as CONSTANTS
# Import utility functions for trading
from poly_data.trading_utils import get_best_bid_ask_deets , get_order_prices , get_buy_sell_amount , round_down , round_up
from poly_data.data_utils import get_position , get_order , set_position
# Create directory for storing position risk information
if not os . path . exists ( 'positions/' ):
os . makedirs ( 'positions/' )
def send_buy_order ( order ):
"""
Create a BUY order for a specific token.
This function:
1. Cancels any existing orders for the token
2. Checks if the order price is within acceptable range
3. Creates a new buy order if conditions are met
Args:
order (dict): Order details including token, price, size, and market parameters
"""
client = global_state . client
2025-06-22 10:28:13 -07:00
# Only cancel existing orders if we need to make significant changes
existing_buy_size = order [ 'orders' ][ 'buy' ][ 'size' ]
existing_buy_price = order [ 'orders' ][ 'buy' ][ 'price' ]
# Cancel orders if price changed significantly or size needs major adjustment
price_diff = abs ( existing_buy_price - order [ 'price' ]) if existing_buy_price > 0 else float ( 'inf' )
size_diff = abs ( existing_buy_size - order [ 'size' ]) if existing_buy_size > 0 else float ( 'inf' )
should_cancel = (
price_diff > 0.005 or # Cancel if price diff > 0.5 cents
size_diff > order [ 'size' ] * 0.1 or # Cancel if size diff > 10%
existing_buy_size == 0 # Cancel if no existing buy order
)
if should_cancel and ( existing_buy_size > 0 or order [ 'orders' ][ 'sell' ][ 'size' ] > 0 ):
print ( f "Cancelling buy orders - price diff: { price_diff : .4f } , size diff: { size_diff : .1f } " )
2025-03-31 12:16:51 -04:00
client . cancel_all_asset ( order [ 'token' ])
2025-06-22 10:28:13 -07:00
elif not should_cancel :
print ( f "Keeping existing buy orders - minor changes: price diff: { price_diff : .4f } , size diff: { size_diff : .1f } " )
return # Don't place new order if existing one is fine
2025-03-31 12:16:51 -04:00
# Calculate minimum acceptable price based on market spread
incentive_start = order [ 'mid_price' ] - order [ 'max_spread' ] / 100
trade = True
# Don't place orders that are below incentive threshold
if order [ 'price' ] < incentive_start :
trade = False
if trade :
# Only place orders with prices between 0.1 and 0.9 to avoid extreme positions
if order [ 'price' ] >= 0.1 and order [ 'price' ] < 0.9 :
print ( f 'Creating new order for { order [ "size" ] } at { order [ "price" ] } ' )
print ( order [ 'token' ], 'BUY' , order [ 'price' ], order [ 'size' ])
client . create_order (
order [ 'token' ],
'BUY' ,
order [ 'price' ],
order [ 'size' ],
True if order [ 'neg_risk' ] == 'TRUE' else False
)
else :
print ( "Not creating buy order because its outside acceptable price range (0.1-0.9)" )
else :
print ( f 'Not creating new order because order price of { order [ "price" ] } is less than incentive start price of { incentive_start } . Mid price is { order [ "mid_price" ] } ' )
def send_sell_order ( order ):
"""
Create a SELL order for a specific token.
This function:
1. Cancels any existing orders for the token
2. Creates a new sell order with the specified parameters
Args:
order (dict): Order details including token, price, size, and market parameters
"""
client = global_state . client
2025-06-22 10:28:13 -07:00
# Only cancel existing orders if we need to make significant changes
existing_sell_size = order [ 'orders' ][ 'sell' ][ 'size' ]
existing_sell_price = order [ 'orders' ][ 'sell' ][ 'price' ]
# Cancel orders if price changed significantly or size needs major adjustment
price_diff = abs ( existing_sell_price - order [ 'price' ]) if existing_sell_price > 0 else float ( 'inf' )
size_diff = abs ( existing_sell_size - order [ 'size' ]) if existing_sell_size > 0 else float ( 'inf' )
should_cancel = (
price_diff > 0.005 or # Cancel if price diff > 0.5 cents
size_diff > order [ 'size' ] * 0.1 or # Cancel if size diff > 10%
existing_sell_size == 0 # Cancel if no existing sell order
)
if should_cancel and ( existing_sell_size > 0 or order [ 'orders' ][ 'buy' ][ 'size' ] > 0 ):
print ( f "Cancelling sell orders - price diff: { price_diff : .4f } , size diff: { size_diff : .1f } " )
2025-03-31 12:16:51 -04:00
client . cancel_all_asset ( order [ 'token' ])
2025-06-22 10:28:13 -07:00
elif not should_cancel :
print ( f "Keeping existing sell orders - minor changes: price diff: { price_diff : .4f } , size diff: { size_diff : .1f } " )
return # Don't place new order if existing one is fine
2025-03-31 12:16:51 -04:00
print ( f 'Creating new order for { order [ "size" ] } at { order [ "price" ] } ' )
client . create_order (
order [ 'token' ],
'SELL' ,
order [ 'price' ],
order [ 'size' ],
True if order [ 'neg_risk' ] == 'TRUE' else False
)
# Dictionary to store locks for each market to prevent concurrent trading on the same market
market_locks = {}
async def perform_trade ( market ):
"""
Main trading function that handles market making for a specific market.
This function:
1. Merges positions when possible to free up capital
2. Analyzes the market to determine optimal bid/ask prices
3. Manages buy and sell orders based on position size and market conditions
4. Implements risk management with stop-loss and take-profit logic
Args:
market (str): The market ID to trade on
"""
# Create a lock for this market if it doesn't exist
if market not in market_locks :
market_locks [ market ] = asyncio . Lock ()
# Use lock to prevent concurrent trading on the same market
async with market_locks [ market ]:
try :
client = global_state . client
# Get market details from the configuration
row = global_state . df [ global_state . df [ 'condition_id' ] == market ] . iloc [ 0 ]
# Determine decimal precision from tick size
round_length = len ( str ( row [ 'tick_size' ]) . split ( "." )[ 1 ])
# Get trading parameters for this market type
params = global_state . params [ row [ 'param_type' ]]
# Create a list with both outcomes for the market
deets = [
{ 'name' : 'token1' , 'token' : row [ 'token1' ], 'answer' : row [ 'answer1' ]},
{ 'name' : 'token2' , 'token' : row [ 'token2' ], 'answer' : row [ 'answer2' ]}
]
print ( f " \n\n { pd . Timestamp . utcnow () . tz_localize ( None ) } : { row [ 'question' ] } " )
# Get current positions for both outcomes
pos_1 = get_position ( row [ 'token1' ])[ 'size' ]
pos_2 = get_position ( row [ 'token2' ])[ 'size' ]
# ------- POSITION MERGING LOGIC -------
# Calculate if we have opposing positions that can be merged
amount_to_merge = min ( pos_1 , pos_2 )
# Only merge if positions are above minimum threshold
if float ( amount_to_merge ) > CONSTANTS . MIN_MERGE_SIZE :
# Get exact position sizes from blockchain for merging
pos_1 = client . get_position ( row [ 'token1' ])[ 0 ]
pos_2 = client . get_position ( row [ 'token2' ])[ 0 ]
amount_to_merge = min ( pos_1 , pos_2 )
scaled_amt = amount_to_merge / 10 ** 6
if scaled_amt > CONSTANTS . MIN_MERGE_SIZE :
print ( f "Position 1 is of size { pos_1 } and Position 2 is of size { pos_2 } . Merging positions" )
# Execute the merge operation
client . merge_positions ( amount_to_merge , market , row [ 'neg_risk' ] == 'TRUE' )
# Update our local position tracking
set_position ( row [ 'token1' ], 'SELL' , scaled_amt , 0 , 'merge' )
set_position ( row [ 'token2' ], 'SELL' , scaled_amt , 0 , 'merge' )
# ------- TRADING LOGIC FOR EACH OUTCOME -------
# Loop through both outcomes in the market (YES and NO)
for detail in deets :
token = int ( detail [ 'token' ])
# Get current orders for this token
orders = get_order ( token )
# Get market depth and price information
deets = get_best_bid_ask_deets ( market , detail [ 'name' ], 100 , 0.1 )
2025-06-22 09:38:27 -07:00
#if deet has None for one these values below, call it with min size of 20
if deets [ 'best_bid' ] is None or deets [ 'best_ask' ] is None or deets [ 'best_bid_size' ] is None or deets [ 'best_ask_size' ] is None :
deets = get_best_bid_ask_deets ( market , detail [ 'name' ], 20 , 0.1 )
2025-03-31 12:16:51 -04:00
# Extract all order book details
best_bid = deets [ 'best_bid' ]
best_bid_size = deets [ 'best_bid_size' ]
second_best_bid = deets [ 'second_best_bid' ]
second_best_bid_size = deets [ 'second_best_bid_size' ]
top_bid = deets [ 'top_bid' ]
best_ask = deets [ 'best_ask' ]
best_ask_size = deets [ 'best_ask_size' ]
second_best_ask = deets [ 'second_best_ask' ]
second_best_ask_size = deets [ 'second_best_ask_size' ]
top_ask = deets [ 'top_ask' ]
# Round prices to appropriate precision
best_bid = round ( best_bid , round_length )
best_ask = round ( best_ask , round_length )
# Calculate ratio of buy vs sell liquidity in the market
try :
overall_ratio = ( deets [ 'bid_sum_within_n_percent' ]) / ( deets [ 'ask_sum_within_n_percent' ])
except :
overall_ratio = 0
try :
second_best_bid = round ( second_best_bid , round_length )
second_best_ask = round ( second_best_ask , round_length )
except :
pass
top_bid = round ( top_bid , round_length )
top_ask = round ( top_ask , round_length )
# Get our current position and average price
pos = get_position ( token )
position = pos [ 'size' ]
avgPrice = pos [ 'avgPrice' ]
position = round_down ( position , 2 )
# Calculate optimal bid and ask prices based on market conditions
bid_price , ask_price = get_order_prices (
best_bid , best_bid_size , top_bid , best_ask ,
best_ask_size , top_ask , avgPrice , row
)
bid_price = round ( bid_price , round_length )
ask_price = round ( ask_price , round_length )
# Calculate mid price for reference
mid_price = ( top_bid + top_ask ) / 2
# Log market conditions for this outcome
print ( f " \n For { detail [ 'answer' ] } . Orders: { orders } Position: { position } , "
f "avgPrice: { avgPrice } , Best Bid: { best_bid } , Best Ask: { best_ask } , "
f "Bid Price: { bid_price } , Ask Price: { ask_price } , Mid Price: { mid_price } " )
2025-06-22 09:38:27 -07:00
# Get position for the opposite token to calculate total exposure
other_token = global_state . REVERSE_TOKENS [ str ( token )]
other_position = get_position ( other_token )[ 'size' ]
2025-03-31 12:16:51 -04:00
# Calculate how much to buy or sell based on our position
2025-06-22 09:38:27 -07:00
buy_amount , sell_amount = get_buy_sell_amount ( position , bid_price , row , other_position )
2025-06-22 10:07:14 -07:00
# Get max_size for logging (same logic as in get_buy_sell_amount)
max_size = row . get ( 'max_size' , row [ 'trade_size' ])
2025-03-31 12:16:51 -04:00
# Prepare order object with all necessary information
order = {
"token" : token ,
"mid_price" : mid_price ,
"neg_risk" : row [ 'neg_risk' ],
"max_spread" : row [ 'max_spread' ],
'orders' : orders ,
'token_name' : detail [ 'name' ],
'row' : row
}
2025-06-22 09:38:27 -07:00
print ( f "Position: { position } , Other Position: { other_position } , "
f "Trade Size: { row [ 'trade_size' ] } , Max Size: { max_size } , "
2025-03-31 12:16:51 -04:00
f "buy_amount: { buy_amount } , sell_amount: { sell_amount } " )
# File to store risk management information for this market
fname = 'positions/' + str ( market ) + '.json'
# ------- SELL ORDER LOGIC -------
if sell_amount > 0 :
# Skip if we have no average price (no real position)
if avgPrice == 0 :
print ( "Avg Price is 0. Skipping" )
continue
order [ 'size' ] = sell_amount
order [ 'price' ] = ask_price
# Get fresh market data for risk assessment
n_deets = get_best_bid_ask_deets ( market , detail [ 'name' ], 100 , 0.1 )
# Calculate current market price and spread
mid_price = round_up (( n_deets [ 'best_bid' ] + n_deets [ 'best_ask' ]) / 2 , round_length )
spread = round ( n_deets [ 'best_ask' ] - n_deets [ 'best_bid' ], 2 )
# Calculate current profit/loss on position
pnl = ( mid_price - avgPrice ) / avgPrice * 100
print ( f "Mid Price: { mid_price } , Spread: { spread } , PnL: { pnl } " )
# Prepare risk details for tracking
risk_details = {
'time' : str ( pd . Timestamp . utcnow () . tz_localize ( None )),
'question' : row [ 'question' ]
}
try :
ratio = ( n_deets [ 'bid_sum_within_n_percent' ]) / ( n_deets [ 'ask_sum_within_n_percent' ])
except :
ratio = 0
pos_to_sell = sell_amount # Amount to sell in risk-off scenario
# ------- STOP-LOSS LOGIC -------
# Trigger stop-loss if either:
# 1. PnL is below threshold and spread is tight enough to exit
# 2. Volatility is too high
if ( pnl < params [ 'stop_loss_threshold' ] and spread <= params [ 'spread_threshold' ]) or row [ '3_hour' ] > params [ 'volatility_threshold' ]:
risk_details [ 'msg' ] = ( f "Selling { pos_to_sell } because spread is { spread } and pnl is { pnl } "
f "and ratio is { ratio } and 3 hour volatility is { row [ '3_hour' ] } " )
print ( "Stop loss Triggered: " , risk_details [ 'msg' ])
# Sell at market best bid to ensure execution
order [ 'size' ] = pos_to_sell
order [ 'price' ] = n_deets [ 'best_bid' ]
# Set period to avoid trading after stop-loss
risk_details [ 'sleep_till' ] = str ( pd . Timestamp . utcnow () . tz_localize ( None ) +
pd . Timedelta ( hours = params [ 'sleep_period' ]))
print ( "Risking off" )
send_sell_order ( order )
client . cancel_all_market ( market )
# Save risk details to file
open ( fname , 'w' ) . write ( json . dumps ( risk_details ))
continue
# ------- BUY ORDER LOGIC -------
2025-06-22 09:38:27 -07:00
# Get max_size, defaulting to trade_size if not specified
max_size = row . get ( 'max_size' , row [ 'trade_size' ])
2025-03-31 12:16:51 -04:00
# Only buy if:
2025-06-22 09:38:27 -07:00
# 1. Position is less than max_size (new logic)
2025-03-31 12:16:51 -04:00
# 2. Position is less than absolute cap (250)
# 3. Buy amount is above minimum size
2025-06-22 09:38:27 -07:00
if position < max_size and position < 250 and buy_amount > 0 and buy_amount >= row [ 'min_size' ]:
2025-03-31 12:16:51 -04:00
# Get reference price from market data
sheet_value = row [ 'best_bid' ]
if detail [ 'name' ] == 'token2' :
sheet_value = 1 - row [ 'best_ask' ]
sheet_value = round ( sheet_value , round_length )
order [ 'size' ] = buy_amount
order [ 'price' ] = bid_price
# Check if price is far from reference
price_change = abs ( order [ 'price' ] - sheet_value )
send_buy = True
# ------- RISK-OFF PERIOD CHECK -------
# If we're in a risk-off period (after stop-loss), don't buy
if os . path . isfile ( fname ):
risk_details = json . load ( open ( fname ))
start_trading_at = pd . to_datetime ( risk_details [ 'sleep_till' ])
current_time = pd . Timestamp . utcnow () . tz_localize ( None )
print ( risk_details , current_time , start_trading_at )
if current_time < start_trading_at :
send_buy = False
print ( f "Not sending a buy order because recently risked off. "
f "Risked off at { risk_details [ 'time' ] } " )
# Only proceed if we're not in risk-off period
if send_buy :
# Don't buy if volatility is high or price is far from reference
if row [ '3_hour' ] > params [ 'volatility_threshold' ] or price_change >= 0.05 :
print ( f '3 Hour Volatility of { row [ "3_hour" ] } is greater than max volatility of '
f ' { params [ "volatility_threshold" ] } or price of { order [ "price" ] } is outside '
f '0.05 of { sheet_value } . Cancelling all orders' )
client . cancel_all_asset ( order [ 'token' ])
else :
# Check for reverse position (holding opposite outcome)
rev_token = global_state . REVERSE_TOKENS [ str ( token )]
rev_pos = get_position ( rev_token )
# If we have significant opposing position, don't buy more
if rev_pos [ 'size' ] > row [ 'min_size' ]:
print ( "Bypassing creation of new buy order because there is a reverse position" )
if orders [ 'buy' ][ 'size' ] > CONSTANTS . MIN_MERGE_SIZE :
print ( "Cancelling buy orders because there is a reverse position" )
client . cancel_all_asset ( order [ 'token' ])
continue
# Check market buy/sell volume ratio
if overall_ratio < 0 :
send_buy = False
print ( f "Not sending a buy order because overall ratio is { overall_ratio } " )
client . cancel_all_asset ( order [ 'token' ])
else :
# Place new buy order if any of these conditions are met:
# 1. We can get a better price than current order
if best_bid > orders [ 'buy' ][ 'price' ]:
print ( f "Sending Buy Order for { token } because better price. "
f "Orders look like this: { orders [ 'buy' ] } . Best Bid: { best_bid } " )
send_buy_order ( order )
2025-06-22 09:38:27 -07:00
# 2. Current position + orders is not enough to reach max_size
elif position + orders [ 'buy' ][ 'size' ] < 0.95 * max_size :
2025-03-31 12:16:51 -04:00
print ( f "Sending Buy Order for { token } because not enough position + size" )
send_buy_order ( order )
# 3. Our current order is too large and needs to be resized
elif orders [ 'buy' ][ 'size' ] > order [ 'size' ] * 1.01 :
print ( f "Resending buy orders because open orders are too large" )
send_buy_order ( order )
# Commented out logic for cancelling orders when market conditions change
# elif best_bid_size < orders['buy']['size'] * 0.98 and abs(best_bid - second_best_bid) > 0.03:
# print(f"Cancelling buy orders because best size is less than 90% of open orders and spread is too large")
# global_state.client.cancel_all_asset(order['token'])
# ------- TAKE PROFIT / SELL ORDER MANAGEMENT -------
elif sell_amount > 0 :
order [ 'size' ] = sell_amount
# Calculate take-profit price based on average cost
tp_price = round_up ( avgPrice + ( avgPrice * params [ 'take_profit_threshold' ] / 100 ), round_length )
order [ 'price' ] = round_up ( tp_price if ask_price < tp_price else ask_price , round_length )
tp_price = float ( tp_price )
order_price = float ( orders [ 'sell' ][ 'price' ])
# Calculate % difference between current order and ideal price
diff = abs ( order_price - tp_price ) / tp_price * 100
# Update sell order if:
# 1. Current order price is significantly different from target
if diff > 2 :
print ( f "Sending Sell Order for { token } because better current order price of "
f " { order_price } is deviant from the tp_price of { tp_price } and diff is { diff } " )
send_sell_order ( order )
# 2. Current order size is too small for our position
elif orders [ 'sell' ][ 'size' ] < position * 0.97 :
print ( f "Sending Sell Order for { token } because not enough sell size. "
f "Position: { position } , Sell Size: { orders [ 'sell' ][ 'size' ] } " )
send_sell_order ( order )
# Commented out additional conditions for updating sell orders
# elif orders['sell']['price'] < ask_price:
# print(f"Updating Sell Order for {token} because its not at the right price")
# send_sell_order(order)
# elif best_ask_size < orders['sell']['size'] * 0.98 and abs(best_ask - second_best_ask) > 0.03...:
# print(f"Cancelling sell orders because best size is less than 90% of open orders...")
# send_sell_order(order)
except Exception as ex :
2025-06-22 10:15:07 -07:00
print ( f "Error performing trade for { market } : { ex } " )
2025-03-31 12:16:51 -04:00
traceback . print_exc ()
# Clean up memory and introduce a small delay
gc . collect ()
await asyncio . sleep ( 2 )