Repo is ready
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
# Polymarket Market Maker Data Updater
|
||||
|
||||
This tool analyzes Polymarket prediction markets to identify profitable market making opportunities. It fetches market data, calculates potential rewards, analyzes price volatility, and updates a Google Spreadsheet with the results.
|
||||
|
||||
## Features
|
||||
|
||||
- Fetches all available Polymarket markets
|
||||
- Calculates rewards based on market maker formulas
|
||||
- Analyzes price volatility over different time windows
|
||||
- Updates Google Sheets with market opportunities sorted by profitability
|
||||
- Automatically runs every 5 minutes
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Python 3.7+
|
||||
- A Google Cloud project with Sheets API enabled
|
||||
- Polymarket account with an API key
|
||||
|
||||
## Setup
|
||||
|
||||
1. Clone the repository
|
||||
2. Install dependencies:
|
||||
```
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
3. Create a `.env` file with your private key (see example below)
|
||||
4. Create a Google service account and download `credentials.json`
|
||||
5. Set up your Google Spreadsheet with the following worksheets:
|
||||
- "All Markets"
|
||||
- "Volatility Markets"
|
||||
- "Full Markets"
|
||||
- "Selected Markets"
|
||||
|
||||
## Running the Tool
|
||||
|
||||
```
|
||||
python update_markets.py
|
||||
```
|
||||
|
||||
This will start the continuous update process, refreshing market data every 5 minutes.
|
||||
|
||||
## Configuration Files
|
||||
|
||||
### .env Example
|
||||
|
||||
```
|
||||
PK=your_polygon_wallet_private_key
|
||||
SPREADSHEET_URL=https://docs.google.com/spreadsheets/d/your_spreadsheet_id/edit
|
||||
```
|
||||
|
||||
### credentials.json Example
|
||||
|
||||
This is a Google service account credentials file. Create one at the [Google Cloud Console](https://console.cloud.google.com/):
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "service_account",
|
||||
"project_id": "your-project-id",
|
||||
"private_key_id": "abc123...",
|
||||
"private_key": "-----BEGIN PRIVATE KEY-----\nYOUR_PRIVATE_KEY_HERE\n-----END PRIVATE KEY-----\n",
|
||||
"client_email": "your-service-account@your-project-id.iam.gserviceaccount.com",
|
||||
"client_id": "123456789",
|
||||
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
||||
"token_uri": "https://oauth2.googleapis.com/token",
|
||||
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
|
||||
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/your-service-account%40your-project-id.iam.gserviceaccount.com"
|
||||
}
|
||||
```
|
||||
|
||||
## Security Notes
|
||||
|
||||
- Keep your private key and credentials.json secure
|
||||
- Never commit sensitive files to public repositories
|
||||
- Use environment variables for sensitive information
|
||||
|
||||
## Disclaimer
|
||||
|
||||
This tool is for educational purposes only. Trading cryptocurrency involves significant risk. This is not financial advice.
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"type": "service_account",
|
||||
"project_id": "your-project-id",
|
||||
"private_key_id": "abc123...",
|
||||
"private_key": "-----BEGIN PRIVATE KEY-----\nYOUR_PRIVATE_KEY_HERE\n-----END PRIVATE KEY-----\n",
|
||||
"client_email": "your-service-account@your-project-id.iam.gserviceaccount.com",
|
||||
"client_id": "123456789",
|
||||
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
||||
"token_uri": "https://oauth2.googleapis.com/token",
|
||||
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
|
||||
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/your-service-account%40your-project-id.iam.gserviceaccount.com"
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
[
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "name",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "guy",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "wad",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "approve",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "totalSupply",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "src",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "dst",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "wad",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "transferFrom",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "wad",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "withdraw",
|
||||
"outputs": [],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "decimals",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint8"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "balanceOf",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "symbol",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "dst",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "wad",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "transfer",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [],
|
||||
"name": "deposit",
|
||||
"outputs": [],
|
||||
"payable": true,
|
||||
"stateMutability": "payable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "allowance",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"payable": true,
|
||||
"stateMutability": "payable",
|
||||
"type": "fallback"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "src",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "guy",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "wad",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Approval",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "src",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "dst",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "wad",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Transfer",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "dst",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "wad",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Deposit",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "src",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "wad",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Withdrawal",
|
||||
"type": "event"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,322 @@
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
import os
|
||||
import requests
|
||||
import time
|
||||
import warnings
|
||||
warnings.filterwarnings("ignore")
|
||||
|
||||
|
||||
if not os.path.exists('data'):
|
||||
os.makedirs('data')
|
||||
|
||||
def get_sel_df(spreadsheet, sheet_name='Selected Markets'):
|
||||
try:
|
||||
wk2 = spreadsheet.worksheet(sheet_name)
|
||||
sel_df = pd.DataFrame(wk2.get_all_records())
|
||||
sel_df = sel_df[sel_df['question'] != ""].reset_index(drop=True)
|
||||
return sel_df
|
||||
except:
|
||||
return pd.DataFrame()
|
||||
|
||||
def get_all_markets(client):
|
||||
cursor = ""
|
||||
all_markets = []
|
||||
|
||||
while True:
|
||||
try:
|
||||
markets = client.get_sampling_markets(next_cursor = cursor)
|
||||
markets_df = pd.DataFrame(markets['data'])
|
||||
|
||||
|
||||
cursor = markets['next_cursor']
|
||||
|
||||
|
||||
|
||||
all_markets.append(markets_df)
|
||||
|
||||
if cursor is None:
|
||||
break
|
||||
except:
|
||||
break
|
||||
|
||||
all_df = pd.concat(all_markets)
|
||||
all_df = all_df.reset_index(drop=True)
|
||||
|
||||
return all_df
|
||||
|
||||
def get_bid_ask_range(ret, TICK_SIZE):
|
||||
bid_from = ret['midpoint'] - ret['max_spread'] / 100
|
||||
bid_to = ret['best_ask'] #Although bid to this high up will change bid_from because of changing midpoint, take optimistic approach
|
||||
|
||||
if bid_to == 0:
|
||||
bid_to = ret['midpoint']
|
||||
|
||||
if bid_to - TICK_SIZE > ret['midpoint']:
|
||||
bid_to = ret['best_bid'] + (TICK_SIZE + 0.1 * TICK_SIZE)
|
||||
|
||||
if bid_from > bid_to:
|
||||
bid_from = bid_to - (TICK_SIZE + 0.1 * TICK_SIZE)
|
||||
|
||||
ask_to = ret['midpoint'] + ret['max_spread'] / 100
|
||||
ask_from = ret['best_bid']
|
||||
|
||||
if ask_from == 0:
|
||||
ask_from = ret['midpoint']
|
||||
|
||||
if ask_from + TICK_SIZE < ret['midpoint']:
|
||||
ask_from = ret['best_ask'] - (TICK_SIZE + 0.1 * TICK_SIZE)
|
||||
|
||||
if ask_from > ask_to:
|
||||
ask_to = ask_from + (TICK_SIZE + 0.1 * TICK_SIZE)
|
||||
|
||||
bid_from = round(bid_from, 3)
|
||||
bid_to = round(bid_to, 3)
|
||||
ask_from = round(ask_from, 3)
|
||||
ask_to = round(ask_to, 3)
|
||||
|
||||
if bid_from < 0:
|
||||
bid_from = 0
|
||||
|
||||
if ask_from < 0:
|
||||
ask_from = 0
|
||||
|
||||
return bid_from, bid_to, ask_from, ask_to
|
||||
|
||||
|
||||
def generate_numbers(start, end, TICK_SIZE):
|
||||
# Calculate the starting point, rounding up to the next hundredth if not an exact multiple of TICK_SIZE
|
||||
rounded_start = (int(start * 100) + 1) / 100 if start * 100 % 1 != 0 else start + TICK_SIZE
|
||||
|
||||
# Calculate the ending point, rounding down to the nearest hundredth
|
||||
rounded_end = int(end * 100) / 100
|
||||
|
||||
# Generate numbers from rounded_start to rounded_end, ensuring they fall strictly within the original bounds
|
||||
numbers = []
|
||||
current = rounded_start
|
||||
while current < end:
|
||||
numbers.append(current)
|
||||
current += TICK_SIZE
|
||||
current = round(current, len(str(TICK_SIZE).split('.')[1])) # Rounding to avoid floating point imprecision
|
||||
|
||||
return numbers
|
||||
|
||||
def add_formula_params(curr_df, midpoint, v, daily_reward):
|
||||
curr_df['s'] = (curr_df['price'] - midpoint).abs()
|
||||
curr_df['S'] = ((v - curr_df['s']) / v) ** 2
|
||||
curr_df['100'] = 1/curr_df['price'] * 100
|
||||
|
||||
curr_df['size'] = curr_df['size'] + curr_df['100']
|
||||
|
||||
curr_df['Q'] = curr_df['S'] * curr_df['size']
|
||||
curr_df['reward_per_100'] = (curr_df['Q'] / curr_df['Q'].sum()) * daily_reward / 2 / curr_df['size'] * curr_df['100']
|
||||
return curr_df
|
||||
|
||||
def process_single_row(row, client):
|
||||
ret = {}
|
||||
ret['question'] = row['question']
|
||||
ret['neg_risk'] = row['neg_risk']
|
||||
|
||||
ret['answer1'] = row['tokens'][0]['outcome']
|
||||
ret['answer2'] = row['tokens'][1]['outcome']
|
||||
|
||||
ret['min_size'] = row['rewards']['min_size']
|
||||
ret['max_spread'] = row['rewards']['max_spread']
|
||||
|
||||
token1 = row['tokens'][0]['token_id']
|
||||
token2 = row['tokens'][1]['token_id']
|
||||
|
||||
rate = 0
|
||||
for rate_info in row['rewards']['rates']:
|
||||
if rate_info['asset_address'].lower() == '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174'.lower():
|
||||
rate = rate_info['rewards_daily_rate']
|
||||
break
|
||||
|
||||
ret['rewards_daily_rate'] = rate
|
||||
book = client.get_order_book(token1)
|
||||
|
||||
bids = pd.DataFrame()
|
||||
asks = pd.DataFrame()
|
||||
|
||||
try:
|
||||
bids = pd.DataFrame(book.bids).astype(float)
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
asks = pd.DataFrame(book.asks).astype(float)
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
try:
|
||||
ret['best_bid'] = bids.iloc[-1]['price']
|
||||
except:
|
||||
ret['best_bid'] = 0
|
||||
|
||||
try:
|
||||
ret['best_ask'] = asks.iloc[-1]['price']
|
||||
except:
|
||||
ret['best_ask'] = 0
|
||||
|
||||
ret['midpoint'] = (ret['best_bid'] + ret['best_ask']) / 2
|
||||
|
||||
TICK_SIZE = row['minimum_tick_size']
|
||||
ret['tick_size'] = TICK_SIZE
|
||||
|
||||
bid_from, bid_to, ask_from, ask_to = get_bid_ask_range(ret, TICK_SIZE)
|
||||
v = round((ret['max_spread'] / 100), 2)
|
||||
|
||||
bids_df = pd.DataFrame()
|
||||
bids_df['price'] = generate_numbers(bid_from, bid_to, TICK_SIZE)
|
||||
|
||||
asks_df = pd.DataFrame()
|
||||
asks_df['price'] = generate_numbers(ask_from, ask_to, TICK_SIZE)
|
||||
|
||||
try:
|
||||
bids_df = bids_df.merge(bids, on='price', how='left').fillna(0)
|
||||
except:
|
||||
bids_df = pd.DataFrame()
|
||||
|
||||
try:
|
||||
asks_df = asks_df.merge(asks, on='price', how='left').fillna(0)
|
||||
except:
|
||||
asks_df = pd.DataFrame()
|
||||
|
||||
best_bid_reward = 0
|
||||
ret_bid = pd.DataFrame()
|
||||
|
||||
try:
|
||||
ret_bid = add_formula_params(bids_df, ret['midpoint'], v, rate)
|
||||
best_bid_reward = round(ret_bid['reward_per_100'].max(), 2)
|
||||
except:
|
||||
pass
|
||||
|
||||
best_ask_reward = 0
|
||||
ret_ask = pd.DataFrame()
|
||||
|
||||
try:
|
||||
ret_ask = add_formula_params(asks_df, ret['midpoint'], v, rate)
|
||||
best_ask_reward = round(ret_ask['reward_per_100'].max(), 2)
|
||||
except:
|
||||
pass
|
||||
|
||||
ret['bid_reward_per_100'] = best_bid_reward
|
||||
ret['ask_reward_per_100'] = best_ask_reward
|
||||
|
||||
ret['sm_reward_per_100'] = round((best_bid_reward + best_ask_reward) / 2, 2)
|
||||
ret['gm_reward_per_100'] = round((best_bid_reward * best_ask_reward) ** 0.5, 2)
|
||||
|
||||
ret['end_date_iso'] = row['end_date_iso']
|
||||
ret['market_slug'] = row['market_slug']
|
||||
ret['token1'] = token1
|
||||
ret['token2'] = token2
|
||||
ret['condition_id'] = row['condition_id']
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def get_all_results(all_df, client):
|
||||
all_results = []
|
||||
|
||||
for idx, row in all_df.iterrows():
|
||||
|
||||
try:
|
||||
if idx % 10 == 0:
|
||||
print(f'{idx} of {len(all_df)}')
|
||||
|
||||
time.sleep(1)
|
||||
result = process_single_row(row, client)
|
||||
all_results.append(result)
|
||||
except:
|
||||
print("error fetching market")
|
||||
|
||||
return all_results
|
||||
|
||||
def get_combined_markets(new_df, new_markets, sel_df):
|
||||
|
||||
if len(sel_df) > 0:
|
||||
old_markets = new_df[new_df['question'].isin(sel_df['question'])]
|
||||
all_markets = pd.concat([old_markets, new_markets])
|
||||
else:
|
||||
all_markets = new_markets
|
||||
|
||||
all_markets = all_markets.drop_duplicates('question')
|
||||
|
||||
all_markets = all_markets.sort_values('gm_reward_per_100', ascending=False)
|
||||
return all_markets
|
||||
|
||||
import concurrent.futures
|
||||
|
||||
def calculate_annualized_volatility(df, hours):
|
||||
end_time = df['t'].max()
|
||||
start_time = end_time - pd.Timedelta(hours=hours)
|
||||
window_df = df[df['t'] >= start_time]
|
||||
volatility = window_df['log_return'].std()
|
||||
annualized_volatility = volatility * np.sqrt(60 * 24 * 252)
|
||||
return round(annualized_volatility, 2)
|
||||
|
||||
def add_volatility(row):
|
||||
res = requests.get(f'https://clob.polymarket.com/prices-history?interval=1m&market={row["token1"]}&fidelity=10')
|
||||
price_df = pd.DataFrame(res.json()['history'])
|
||||
price_df['t'] = pd.to_datetime(price_df['t'], unit='s')
|
||||
price_df['p'] = price_df['p'].round(2)
|
||||
|
||||
price_df.to_csv(f'data/{row["token1"]}.csv', index=False)
|
||||
|
||||
price_df['log_return'] = np.log(price_df['p'] / price_df['p'].shift(1))
|
||||
|
||||
row_dict = row.copy()
|
||||
|
||||
stats = {
|
||||
'1_hour': calculate_annualized_volatility(price_df, 1),
|
||||
'3_hour': calculate_annualized_volatility(price_df, 3),
|
||||
'6_hour': calculate_annualized_volatility(price_df, 6),
|
||||
'12_hour': calculate_annualized_volatility(price_df, 12),
|
||||
'24_hour': calculate_annualized_volatility(price_df, 24),
|
||||
'7_day': calculate_annualized_volatility(price_df, 24 * 7),
|
||||
'14_day': calculate_annualized_volatility(price_df, 24 * 14),
|
||||
'30_day': calculate_annualized_volatility(price_df, 24 * 30),
|
||||
'volatility_price': price_df['p'].iloc[-1]
|
||||
}
|
||||
|
||||
new_dict = {**row_dict, **stats}
|
||||
return new_dict
|
||||
|
||||
def add_volatility_to_df(df):
|
||||
|
||||
results = []
|
||||
df = df.reset_index(drop=True)
|
||||
|
||||
for idx, row in df.iterrows():
|
||||
try:
|
||||
if idx % 10 == 0:
|
||||
print(f'{idx} of {len(df)}')
|
||||
|
||||
ret = add_volatility(row.to_dict())
|
||||
time.sleep(1)
|
||||
results.append(ret)
|
||||
except:
|
||||
print("Error fetching volatility")
|
||||
|
||||
return pd.DataFrame(results)
|
||||
|
||||
|
||||
def get_markets(all_results, sel_df, maker_reward=1):
|
||||
new_df = pd.DataFrame(all_results)
|
||||
new_df['spread'] = abs(new_df['best_ask'] - new_df['best_bid'])
|
||||
new_df = new_df.sort_values('rewards_daily_rate', ascending=False)
|
||||
new_df[' '] = ''
|
||||
|
||||
new_df = new_df[['question', 'answer1', 'answer2', 'neg_risk', 'spread', 'best_bid', 'best_ask', 'rewards_daily_rate', 'bid_reward_per_100', 'ask_reward_per_100', 'gm_reward_per_100', 'sm_reward_per_100', 'min_size', 'max_spread', 'tick_size', 'market_slug', 'token1', 'token2', 'condition_id']]
|
||||
new_df = new_df.replace([np.inf, -np.inf], 0)
|
||||
all_data = new_df.copy()
|
||||
s_df = new_df.copy()
|
||||
|
||||
|
||||
making_markets = s_df[~new_df['question'].isin(sel_df['question'])]
|
||||
making_markets = making_markets.sort_values('gm_reward_per_100', ascending=False)
|
||||
making_markets = making_markets[making_markets['gm_reward_per_100'] >= maker_reward]
|
||||
all_markets = get_combined_markets(new_df, making_markets, sel_df)
|
||||
|
||||
return all_data, all_markets
|
||||
@@ -0,0 +1,13 @@
|
||||
from google.oauth2.service_account import Credentials
|
||||
import gspread
|
||||
import os
|
||||
|
||||
|
||||
def get_spreadsheet():
|
||||
"""Get the main Google Spreadsheet using credentials and URL from environment variables"""
|
||||
scope = ["https://spreadsheets.google.com/feeds", "https://www.googleapis.com/auth/drive"]
|
||||
|
||||
credentials = Credentials.from_service_account_file('credentials.json', scopes=scope)
|
||||
client = gspread.authorize(credentials)
|
||||
spreadsheet = client.open_by_url(os.getenv("SPREADSHEET_URL"))
|
||||
return spreadsheet
|
||||
@@ -0,0 +1,4 @@
|
||||
py-clob-client
|
||||
gspread
|
||||
gspread-dataframe
|
||||
pandas
|
||||
@@ -0,0 +1,136 @@
|
||||
from py_clob_client.constants import POLYGON
|
||||
from py_clob_client.client import ClobClient
|
||||
from py_clob_client.clob_types import OrderArgs, BalanceAllowanceParams, AssetType
|
||||
from py_clob_client.order_builder.constants import BUY
|
||||
|
||||
from web3.constants import MAX_INT
|
||||
from web3 import Web3
|
||||
from web3.middleware import geth_poa_middleware
|
||||
|
||||
import json
|
||||
|
||||
from dotenv import load_dotenv
|
||||
load_dotenv()
|
||||
|
||||
import time
|
||||
|
||||
import os
|
||||
|
||||
def get_clob_client():
|
||||
host = "https://clob.polymarket.com"
|
||||
key = os.getenv("PK")
|
||||
chain_id = POLYGON
|
||||
|
||||
if key is None:
|
||||
print("Environment variable 'PK' cannot be found")
|
||||
return None
|
||||
|
||||
|
||||
try:
|
||||
client = ClobClient(host, key=key, chain_id=chain_id)
|
||||
api_creds = client.create_or_derive_api_creds()
|
||||
client.set_api_creds(api_creds)
|
||||
return client
|
||||
except Exception as ex:
|
||||
print("Error creating clob client")
|
||||
print("________________")
|
||||
print(ex)
|
||||
return None
|
||||
|
||||
|
||||
def approveContracts():
|
||||
web3 = Web3(Web3.HTTPProvider("https://polygon-rpc.com"))
|
||||
web3.middleware_onion.inject(geth_poa_middleware, layer=0)
|
||||
wallet = web3.eth.account.privateKeyToAccount(os.getenv("PK"))
|
||||
|
||||
|
||||
with open('erc20ABI.json', 'r') as file:
|
||||
erc20_abi = json.load(file)
|
||||
|
||||
ctf_address = "0x4D97DCd97eC945f40cF65F87097ACe5EA0476045"
|
||||
erc1155_set_approval = """[{"inputs": [{ "internalType": "address", "name": "operator", "type": "address" },{ "internalType": "bool", "name": "approved", "type": "bool" }],"name": "setApprovalForAll","outputs": [],"stateMutability": "nonpayable","type": "function"}]"""
|
||||
|
||||
usdc_contract = web3.eth.contract(address="0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174", abi=erc20_abi) # usdc.e
|
||||
ctf_contract = web3.eth.contract(address=ctf_address, abi=erc1155_set_approval)
|
||||
|
||||
|
||||
for address in ['0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E', '0xC5d563A36AE78145C45a50134d48A1215220f80a', '0xd91E80cF2E7be2e162c6513ceD06f1dD0dA35296']:
|
||||
usdc_nonce = web3.eth.getTransactionCount( wallet.address )
|
||||
raw_usdc_txn = usdc_contract.functions.approve(address, int(MAX_INT, 0)).build_transaction({
|
||||
"chainId": 137,
|
||||
"from": wallet.address,
|
||||
"nonce": usdc_nonce
|
||||
})
|
||||
signed_usdc_txn = web3.eth.account.sign_transaction(raw_usdc_txn, private_key=os.getenv("PK"))
|
||||
usdc_tx_receipt = web3.eth.wait_for_transaction_receipt(signed_usdc_txn, 600)
|
||||
|
||||
|
||||
print(f'USDC Transaction for {address} returned {usdc_tx_receipt}')
|
||||
time.sleep(1)
|
||||
|
||||
ctf_nonce = web3.eth.getTransactionCount( wallet.address )
|
||||
|
||||
raw_ctf_approval_txn = ctf_contract.functions.setApprovalForAll(address, True).buildTransaction({
|
||||
"chainId": 137,
|
||||
"from": wallet.address,
|
||||
"nonce": ctf_nonce
|
||||
})
|
||||
|
||||
signed_ctf_approval_tx = web3.eth.account.sign_transaction(raw_ctf_approval_txn, private_key=os.getenv("PK"))
|
||||
send_ctf_approval_tx = web3.eth.send_raw_transaction(signed_ctf_approval_tx.rawTransaction)
|
||||
ctf_approval_tx_receipt = web3.eth.wait_for_transaction_receipt(send_ctf_approval_tx, 600)
|
||||
|
||||
print(f'CTF Transaction for {address} returned {ctf_approval_tx_receipt}')
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
|
||||
nonce = web3.eth.getTransactionCount( wallet.address )
|
||||
raw_txn_2 = usdc_contract.functions.approve("0xC5d563A36AE78145C45a50134d48A1215220f80a", int(MAX_INT, 0)).build_transaction({
|
||||
"chainId": 137,
|
||||
"from": wallet.address,
|
||||
"nonce": nonce
|
||||
})
|
||||
signed_txn_2 = web3.eth.account.sign_transaction(raw_txn_2, private_key=os.getenv("PK"))
|
||||
send_txn_2 = web3.eth.send_raw_transaction(signed_txn_2.rawTransaction)
|
||||
|
||||
|
||||
nonce = web3.eth.getTransactionCount( wallet.address )
|
||||
raw_txn_3 = usdc_contract.functions.approve("0xd91E80cF2E7be2e162c6513ceD06f1dD0dA35296", int(MAX_INT, 0)).build_transaction({
|
||||
"chainId": 137,
|
||||
"from": wallet.address,
|
||||
"nonce": nonce
|
||||
})
|
||||
signed_txn_3 = web3.eth.account.sign_transaction(raw_txn_3, private_key=os.getenv("PK"))
|
||||
send_txn_3 = web3.eth.send_raw_transaction(signed_txn_3.rawTransaction)
|
||||
|
||||
|
||||
def market_action( marketId, action, price, size ):
|
||||
order_args = OrderArgs(
|
||||
price=price,
|
||||
size=size,
|
||||
side=action,
|
||||
token_id=marketId,
|
||||
)
|
||||
signed_order = get_clob_client().create_order(order_args)
|
||||
|
||||
try:
|
||||
resp = get_clob_client().post_order(signed_order)
|
||||
print(resp)
|
||||
except Exception as ex:
|
||||
print(ex)
|
||||
pass
|
||||
|
||||
|
||||
def get_position(marketId):
|
||||
client = get_clob_client()
|
||||
position_res = client.get_balance_allowance(
|
||||
BalanceAllowanceParams(
|
||||
asset_type=AssetType.CONDITIONAL,
|
||||
token_id=marketId
|
||||
)
|
||||
)
|
||||
orderBook = client.get_order_book(marketId)
|
||||
price = float(orderBook.bids[-1].price)
|
||||
shares = int(position_res['balance']) / 1e6
|
||||
return shares * price
|
||||
@@ -0,0 +1,130 @@
|
||||
import time
|
||||
import pandas as pd
|
||||
from trading_utils import get_clob_client
|
||||
from google_utils import get_spreadsheet
|
||||
from find_markets import get_sel_df, get_all_markets, get_all_results, get_markets, add_volatility_to_df
|
||||
from gspread_dataframe import set_with_dataframe
|
||||
import traceback
|
||||
|
||||
# Initialize global variables
|
||||
spreadsheet = get_spreadsheet()
|
||||
client = get_clob_client()
|
||||
|
||||
wk_all = spreadsheet.worksheet("All Markets")
|
||||
wk_vol = spreadsheet.worksheet("Volatility Markets")
|
||||
|
||||
sel_df = get_sel_df(spreadsheet, "Selected Markets")
|
||||
|
||||
def update_sheet(data, worksheet):
|
||||
all_values = worksheet.get_all_values()
|
||||
existing_num_rows = len(all_values)
|
||||
existing_num_cols = len(all_values[0]) if all_values else 0
|
||||
|
||||
num_rows, num_cols = data.shape
|
||||
max_rows = max(num_rows, existing_num_rows)
|
||||
max_cols = max(num_cols, existing_num_cols)
|
||||
|
||||
# Create a DataFrame with the maximum size and fill it with empty strings
|
||||
padded_data = pd.DataFrame('', index=range(max_rows), columns=range(max_cols))
|
||||
|
||||
# Update the padded DataFrame with the original data and its columns
|
||||
padded_data.iloc[:num_rows, :num_cols] = data.values
|
||||
padded_data.columns = list(data.columns) + [''] * (max_cols - num_cols)
|
||||
|
||||
# Update the sheet with the padded DataFrame, including column headers
|
||||
set_with_dataframe(worksheet, padded_data, include_index=False, include_column_header=True, resize=True)
|
||||
|
||||
def sort_df(df):
|
||||
# Calculate the mean and standard deviation for each column
|
||||
mean_gm = df['gm_reward_per_100'].mean()
|
||||
std_gm = df['gm_reward_per_100'].std()
|
||||
|
||||
mean_volatility = df['volatility_sum'].mean()
|
||||
std_volatility = df['volatility_sum'].std()
|
||||
|
||||
# Standardize the columns
|
||||
df['std_gm_reward_per_100'] = (df['gm_reward_per_100'] - mean_gm) / std_gm
|
||||
df['std_volatility_sum'] = (df['volatility_sum'] - mean_volatility) / std_volatility
|
||||
|
||||
# Define a custom scoring function for best_bid and best_ask
|
||||
def proximity_score(value):
|
||||
if 0.1 <= value <= 0.25:
|
||||
return (0.25 - value) / 0.15
|
||||
elif 0.75 <= value <= 0.9:
|
||||
return (value - 0.75) / 0.15
|
||||
else:
|
||||
return 0
|
||||
|
||||
df['bid_score'] = df['best_bid'].apply(proximity_score)
|
||||
df['ask_score'] = df['best_ask'].apply(proximity_score)
|
||||
|
||||
# Create a composite score (higher is better for rewards, lower is better for volatility, with proximity scores)
|
||||
df['composite_score'] = (
|
||||
df['std_gm_reward_per_100'] -
|
||||
df['std_volatility_sum'] +
|
||||
df['bid_score'] +
|
||||
df['ask_score']
|
||||
)
|
||||
|
||||
# Sort by the composite score in descending order
|
||||
sorted_df = df.sort_values(by='composite_score', ascending=False)
|
||||
|
||||
# Drop the intermediate columns used for calculation
|
||||
sorted_df = sorted_df.drop(columns=['std_gm_reward_per_100', 'std_volatility_sum', 'bid_score', 'ask_score', 'composite_score'])
|
||||
|
||||
return sorted_df
|
||||
|
||||
def fetch_and_process_data():
|
||||
global spreadsheet, client, wk_all, wk_vol, sel_df
|
||||
|
||||
spreadsheet = get_spreadsheet()
|
||||
client = get_clob_client()
|
||||
|
||||
wk_all = spreadsheet.worksheet("All Markets")
|
||||
wk_vol = spreadsheet.worksheet("Volatility Markets")
|
||||
wk_full = spreadsheet.worksheet("Full Markets")
|
||||
|
||||
sel_df = get_sel_df(spreadsheet, "Selected Markets")
|
||||
|
||||
|
||||
all_df = get_all_markets(client)
|
||||
all_results = get_all_results(all_df, client)
|
||||
m_data, all_markets = get_markets(all_results, sel_df, maker_reward=0.75)
|
||||
|
||||
print(f'{pd.to_datetime("now")}: Fetched all markets data of length {len(all_markets)}.')
|
||||
new_df = add_volatility_to_df(all_markets)
|
||||
new_df['volatility_sum'] = new_df['24_hour'] + new_df['7_day'] + new_df['14_day']
|
||||
|
||||
new_df = new_df.sort_values('volatility_sum', ascending=True)
|
||||
new_df['volatilty/reward'] = ((new_df['gm_reward_per_100'] / new_df['volatility_sum']).round(2)).astype(str)
|
||||
|
||||
new_df = new_df[['question', 'answer1', 'answer2', 'spread', 'rewards_daily_rate', 'gm_reward_per_100', 'sm_reward_per_100', 'bid_reward_per_100', 'ask_reward_per_100', 'volatility_sum', 'volatilty/reward', 'min_size', '1_hour', '3_hour', '6_hour', '12_hour', '24_hour', '7_day', '30_day',
|
||||
'best_bid', 'best_ask', 'volatility_price', 'max_spread', 'tick_size',
|
||||
'neg_risk', 'market_slug', 'token1', 'token2', 'condition_id']]
|
||||
|
||||
|
||||
volatility_df = new_df.copy()
|
||||
volatility_df = volatility_df[new_df['volatility_sum'] < 20]
|
||||
# volatility_df = sort_df(volatility_df)
|
||||
volatility_df = volatility_df.sort_values('gm_reward_per_100', ascending=False)
|
||||
|
||||
new_df = new_df.sort_values('gm_reward_per_100', ascending=False)
|
||||
|
||||
|
||||
print(f'{pd.to_datetime("now")}: Fetched select market of length {len(new_df)}.')
|
||||
|
||||
if len(new_df) > 50:
|
||||
update_sheet(new_df, wk_all)
|
||||
update_sheet(volatility_df, wk_vol)
|
||||
update_sheet(m_data, wk_full)
|
||||
else:
|
||||
print(f'{pd.to_datetime("now")}: Not updating sheet because of length {len(new_df)}.')
|
||||
|
||||
if __name__ == "__main__":
|
||||
while True:
|
||||
try:
|
||||
fetch_and_process_data()
|
||||
time.sleep(60 * 5) # Sleep for 5 minutes (300 seconds)
|
||||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
print(str(e))
|
||||
Reference in New Issue
Block a user