Repo is ready

This commit is contained in:
warproxxx
2025-03-31 12:16:51 -04:00
commit 8e500d9437
35 changed files with 6044 additions and 0 deletions
+6
View File
@@ -0,0 +1,6 @@
# Polymarket Authentication
PK=your_private_key_here
BROWSER_ADDRESS=your_wallet_address_here
# Google Sheets (for data_updater)
SPREADSHEET_URL=your_google_spreadsheet_url_here
+469
View File
@@ -0,0 +1,469 @@
output.txt
data/
credentials.json
*/credentials.json
.env
.env.*
positions/
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
.pdm.toml
.pdm-python
.pdm-build/
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
.env
env/
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
.pdm.toml
.pdm-python
.pdm-build/
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
+116
View File
@@ -0,0 +1,116 @@
# Poly-Maker
A market making bot for Polymarket prediction markets. This bot automates the process of providing liquidity to markets on Polymarket by maintaining orders on both sides of the book with configurable parameters.
## Overview
Poly-Maker is a comprehensive solution for automated market making on Polymarket. It includes:
- Real-time order book monitoring via WebSockets
- Position management with risk controls
- Customizable trade parameters fetched from Google Sheets
- Automated position merging functionality
- Sophisticated spread and price management
## Structure
The repository consists of several interconnected modules:
- `poly_data`: Core data management and market making logic
- `poly_merger`: Utility for merging positions (based on open-source Polymarket code)
- `poly_stats`: Account statistics tracking
- `poly_utils`: Shared utility functions
- `data_updater`: Separate module for collecting market information
## Requirements
- Python 3.8+
- Node.js (for poly_merger)
- Google Sheets API credentials
- Polymarket account and API credentials
## Installation
1. Clone the repository:
```
git clone https://github.com/yourusername/poly-maker.git
cd poly-maker
```
2. Install Python dependencies:
```
pip install -r requirements.txt
```
3. Install Node.js dependencies for the merger:
```
cd poly_merger
npm install
cd ..
```
4. Set up your environment variables by copying the example file:
```
cp .env.example .env
```
5. Edit the `.env` file with your credentials:
- `PK`: Your private key for Polymarket
- `BROWSER_ADDRESS`: Your wallet address
- `SPREADSHEET_URL`: URL to your Google Sheets configuration
6. Create a Google Service Account and download credentials:
```
cp data_updater/credentials.json.example credentials.json
```
Edit the credentials.json with your Google service account details.
## Configuration
The bot is configured via a Google Spreadsheet with several worksheets:
- **Selected Markets**: Markets you want to trade
- **All Markets**: Database of all markets on Polymarket
- **Hyperparameters**: Configuration parameters for the trading logic
## Usage
### Data Collection
Before running the main bot, you need to gather market data:
```
cd data_updater
python find_markets.py
```
This will populate your Google Sheet with available markets.
### Running the Bot
Start the market maker:
```
python main.py
```
The bot will:
1. Connect to Polymarket API
2. Subscribe to market data via WebSockets
3. Place and manage orders according to your configuration
4. Monitor and merge positions when beneficial
## Notes on poly_merger
The `poly_merger` module is a particularly powerful utility that handles position merging on Polymarket. It's built on open-source Polymarket code and provides a smooth way to consolidate positions, reducing gas fees and improving capital efficiency.
## Important Notes
- This code interacts with real markets and can potentially lose real money
- Test thoroughly with small amounts before deploying with significant capital
- The `data_updater` is technically a separate repository but is included here for convenience
- The `find_markets.py` script in the data_updater is essential for the bot to get market information
## License
MIT
+78
View File
@@ -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.
+12
View File
@@ -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"
}
+279
View File
@@ -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"
}
]
+322
View File
@@ -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
+13
View File
@@ -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
+4
View File
@@ -0,0 +1,4 @@
py-clob-client
gspread
gspread-dataframe
pandas
+136
View File
@@ -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
+130
View File
@@ -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))
+112
View File
@@ -0,0 +1,112 @@
import gc # Garbage collection
import time # Time functions
import asyncio # Asynchronous I/O
import traceback # Exception handling
import threading # Thread management
from poly_data.polymarket_client import PolymarketClient
from poly_data.data_utils import update_markets, update_positions, update_orders
from poly_data.websocket_handlers import connect_market_websocket, connect_user_websocket
import poly_data.global_state as global_state
from poly_data.data_processing import remove_from_performing
def update_once():
"""
Initialize the application state by fetching market data, positions, and orders.
"""
update_markets() # Get market information from Google Sheets
update_positions() # Get current positions from Polymarket
update_orders() # Get current orders from Polymarket
def remove_from_pending():
"""
Clean up stale trades that have been pending for too long (>15 seconds).
This prevents the system from getting stuck on trades that may have failed.
"""
try:
current_time = time.time()
# Iterate through all performing trades
for col in list(global_state.performing.keys()):
for trade_id in list(global_state.performing[col]):
try:
# If trade has been pending for more than 15 seconds, remove it
if current_time - global_state.performing_timestamps[col].get(trade_id, current_time) > 15:
print(f"Removing stale entry {trade_id} from {col} after 15 seconds")
remove_from_performing(col, trade_id)
print("After removing: ", global_state.performing, global_state.performing_timestamps)
except:
print("Error in remove_from_pending")
print(traceback.format_exc())
except:
print("Error in remove_from_pending")
print(traceback.format_exc())
def update_periodically():
"""
Background thread function that periodically updates market data, positions and orders.
- Positions and orders are updated every 5 seconds
- Market data is updated every 30 seconds (every 6 cycles)
- Stale pending trades are removed each cycle
"""
i = 1
while True:
time.sleep(5) # Update every 5 seconds
try:
# Clean up stale trades
remove_from_pending()
# Update positions and orders every cycle
update_positions(avgOnly=True) # Only update average price, not position size
update_orders()
# Update market data every 6th cycle (30 seconds)
if i % 6 == 0:
update_markets()
i = 1
gc.collect() # Force garbage collection to free memory
i += 1
except:
print("Error in update_periodically")
print(traceback.format_exc())
async def main():
"""
Main application entry point. Initializes client, data, and manages websocket connections.
"""
# Initialize client
global_state.client = PolymarketClient()
# Initialize state and fetch initial data
global_state.all_tokens = []
update_once()
print("After initial updates: ", global_state.orders, global_state.positions)
print("\n")
print(f'There are {len(global_state.df)} market, {len(global_state.positions)} positions and {len(global_state.orders)} orders. Starting positions: {global_state.positions}')
# Start background update thread
update_thread = threading.Thread(target=update_periodically, daemon=True)
update_thread.start()
# Main loop - maintain websocket connections
while True:
try:
# Connect to market and user websockets simultaneously
await asyncio.gather(
connect_market_websocket(global_state.all_tokens),
connect_user_websocket()
)
print("Reconnecting to the websocket")
except:
print("Error in main loop")
print(traceback.format_exc())
await asyncio.sleep(1)
gc.collect() # Clean up memory
if __name__ == "__main__":
asyncio.run(main())
+3
View File
@@ -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
View File
File diff suppressed because one or more lines are too long
+144
View File
@@ -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")
+169
View File
@@ -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()
+49
View File
@@ -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 = {}
+320
View File
@@ -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
+148
View File
@@ -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
+34
View File
@@ -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
+98
View File
@@ -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)
+36
View File
@@ -0,0 +1,36 @@
# Poly-Merger
A utility for merging Polymarket positions efficiently. This tool helps in consolidating opposite positions in the same market, allowing you to:
1. Reduce gas costs
2. Free up capital
3. Simplify position management
## How It Works
The merger tool interacts with Polymarket's smart contracts to combine opposite positions in binary markets. When you hold both YES and NO shares in the same market, this tool will merge them to recover your USDC.
## Usage
The merger is invoked through the main Poly-Maker bot when position merging conditions are met, but you can also use it independently:
```
node merge.js [amount_to_merge] [condition_id] [is_neg_risk_market]
```
Example:
```
node merge.js 1000000 1234567 true
```
This would merge 1 USDC worth of opposing positions in market 1234567, which is a negative risk market.
## Prerequisites
- Node.js
- ethers.js v5.x
- A .env file with your Polygon network private key
## Notes
This implementation is based on open-source Polymarket code but has been optimized for automated market making operations.
+136
View File
@@ -0,0 +1,136 @@
/**
* Poly-Merger: Position Merging Utility for Polymarket
*
* This script handles merging of YES and NO positions in Polymarket prediction markets
* to recover collateral. It works with both regular and negative risk markets.
*
* The merger supports Gnosis Safe wallets through the safe-helpers.js utility.
*
* Usage:
* node merge.js [amountToMerge] [conditionId] [isNegRiskMarket]
*
* Example:
* node merge.js 1000000 12345 true
*/
const { ethers } = require('ethers');
const { resolve } = require('path');
const { signAndExecuteSafeTransaction } = require('./safe-helpers');
const { safeAbi } = require('./safeAbi');
// Load environment variables
require('dotenv').config()
// Connect to Polygon network
const provider = new ethers.providers.JsonRpcProvider("https://polygon.llamarpc.com");
const privateKey = process.env.PK;
const wallet = new ethers.Wallet(privateKey, provider);
// Polymarket contract addresses
const addresses = {
// Adapter contract for negative risk markets
neg_risk_adapter: '0xd91E80cF2E7be2e162c6513ceD06f1dD0dA35296',
// USDC token contract on Polygon
collateral: '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174',
// Main conditional tokens contract for prediction markets
conditional_tokens: '0x4D97DCd97eC945f40cF65F87097ACe5EA0476045'
};
// Minimal ABIs for the contracts we interact with
const negRiskAdapterAbi = [
"function mergePositions(bytes32 conditionId, uint256 amount)"
];
const conditionalTokensAbi = [
"function mergePositions(address collateralToken, bytes32 parentCollectionId, bytes32 conditionId, uint256[] partition, uint256 amount)"
];
/**
* Merges YES and NO positions in a Polymarket prediction market to recover USDC collateral.
*
* This function handles both regular and negative risk markets via different contract calls.
* It uses the Gnosis Safe wallet infrastructure for secure transaction execution.
*
* @param {string|number} amountToMerge - Raw amount of tokens to merge (typically expressed in raw units, e.g., 1000000 = 1 USDC)
* @param {string|number} conditionId - The market's condition ID
* @param {boolean} isNegRiskMarket - Whether this is a negative risk market (uses different contract)
* @returns {string} The transaction hash of the merge operation
*/
async function mergePositions(amountToMerge, conditionId, isNegRiskMarket) {
// Log parameters for debugging
console.log(amountToMerge, conditionId, isNegRiskMarket);
// Prepare transaction parameters
const nonce = await provider.getTransactionCount(wallet.address);
const gasPrice = await provider.getGasPrice();
const gasLimit = 10000000; // Set high gas limit to ensure transaction completes
let tx;
// Different contract calls for different market types
if (isNegRiskMarket) {
// For negative risk markets, use the adapter contract
const negRiskAdapter = new ethers.Contract(addresses.neg_risk_adapter, negRiskAdapterAbi, wallet);
tx = await negRiskAdapter.populateTransaction.mergePositions(conditionId, amountToMerge);
} else {
// For regular markets, use the conditional tokens contract directly
const conditionalTokens = new ethers.Contract(addresses.conditional_tokens, conditionalTokensAbi, wallet);
tx = await conditionalTokens.populateTransaction.mergePositions(
addresses.collateral, // USDC contract
ethers.constants.HashZero, // Parent collection ID (0 for top-level markets)
conditionId, // Market ID
[1, 2], // Partition (indexes of outcomes to merge)
amountToMerge // Amount to merge
);
}
// Prepare full transaction object
const transaction = {
...tx,
chainId: 137, // Polygon chain ID
gasPrice: gasPrice,
gasLimit: gasLimit,
nonce: nonce
};
// Get the Safe address from environment variables
const safeAddress = process.env.BROWSER_ADDRESS;
const safe = new ethers.Contract(safeAddress, safeAbi, wallet);
// Execute the transaction through the Safe
console.log("Signing Transaction")
const txResponse = await signAndExecuteSafeTransaction(
wallet,
safe,
transaction.to,
transaction.data,
{
gasPrice: transaction.gasPrice,
gasLimit: transaction.gasLimit
}
);
console.log("Sent transaction. Waiting for response")
const txReceipt = await txResponse.wait();
console.log("merge positions " + txReceipt.transactionHash);
return txReceipt.transactionHash;
}
// Parse command line arguments
const args = process.argv.slice(2);
// Amount of tokens to merge (in raw units, e.g., 1000000 = 1 USDC)
const amountToMerge = args[0];
// The market's condition ID
const conditionId = args[1];
// Whether this is a negative risk market (true/false)
const isNegRiskMarket = args[2] === 'true';
// Execute the merge operation and handle any errors
mergePositions(amountToMerge, conditionId, isNegRiskMarket)
.catch(error => {
console.error("Error merging positions:", error);
process.exit(1);
});
+1339
View File
File diff suppressed because it is too large Load Diff
+15
View File
@@ -0,0 +1,15 @@
{
"name": "poly-merger",
"version": "1.0.0",
"description": "Position merging utility for Polymarket",
"main": "merge.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "MIT",
"dependencies": {
"dotenv": "^16.4.5",
"ethers": "^5.6.3"
}
}
+128
View File
@@ -0,0 +1,128 @@
const { BigNumber, ethers } = require('ethers');
function joinHexData(hexData) {
return `0x${hexData
.map(hex => {
const stripped = hex.replace(/^0x/, "");
return stripped.length % 2 === 0 ? stripped : "0" + stripped;
})
.join("")}`;
}
function abiEncodePacked(...params) {
return joinHexData(
params.map(({ type, value }) => {
const encoded = ethers.utils.defaultAbiCoder.encode([type], [value]);
if (type === "bytes" || type === "string") {
const bytesLength = parseInt(encoded.slice(66, 130), 16);
return encoded.slice(130, 130 + 2 * bytesLength);
}
let typeMatch = type.match(/^(?:u?int\d*|bytes\d+|address)\[\]$/);
if (typeMatch) {
return encoded.slice(130);
}
if (type.startsWith("bytes")) {
const bytesLength = parseInt(type.slice(5));
return encoded.slice(2, 2 + 2 * bytesLength);
}
typeMatch = type.match(/^u?int(\d*)$/);
if (typeMatch) {
if (typeMatch[1] !== "") {
const bytesLength = parseInt(typeMatch[1]) / 8;
return encoded.slice(-2 * bytesLength);
}
return encoded.slice(-64);
}
if (type === "address") {
return encoded.slice(-40);
}
throw new Error(`unsupported type ${type}`);
})
);
}
async function signTransactionHash(signer, message) {
const messageArray = ethers.utils.arrayify(message);
let sig = await signer.signMessage(messageArray);
let sigV = parseInt(sig.slice(-2), 16);
switch (sigV) {
case 0:
case 1:
sigV += 31;
break;
case 27:
case 28:
sigV += 4;
break;
default:
throw new Error("Invalid signature");
}
sig = sig.slice(0, -2) + sigV.toString(16);
return {
r: BigNumber.from("0x" + sig.slice(2, 66)).toString(),
s: BigNumber.from("0x" + sig.slice(66, 130)).toString(),
v: BigNumber.from("0x" + sig.slice(130, 132)).toString(),
};
}
async function signAndExecuteSafeTransaction(signer, safe, to, data, overrides = {}) {
const nonce = await safe.nonce();
console.log("Nonce for safe: ", nonce);
const value = "0";
const safeTxGas = "0";
const baseGas = "0";
const gasPrice = "0";
const gasToken = ethers.constants.AddressZero;
const refundReceiver = ethers.constants.AddressZero;
const operation = 0;
const txHash = await safe.getTransactionHash(
to,
value,
data,
operation,
safeTxGas,
baseGas,
gasPrice,
gasToken,
refundReceiver,
nonce
);
console.log("Transaction hash: ", txHash);
const rsvSignature = await signTransactionHash(signer, txHash);
const packedSig = abiEncodePacked(
{ type: "uint256", value: rsvSignature.r },
{ type: "uint256", value: rsvSignature.s },
{ type: "uint8", value: rsvSignature.v }
);
console.log("Executing transaction");
return safe.execTransaction(
to,
value,
data,
operation,
safeTxGas,
baseGas,
gasPrice,
gasToken,
refundReceiver,
packedSig,
overrides
);
}
module.exports = {
signAndExecuteSafeTransaction,
};
File diff suppressed because it is too large Load Diff
View File
+137
View File
@@ -0,0 +1,137 @@
import pandas as pd
from py_clob_client.headers.headers import create_level_2_headers
from py_clob_client.clob_types import RequestArgs
from poly_utils.google_utils import get_spreadsheet
from gspread_dataframe import set_with_dataframe
import requests
import json
import os
from dotenv import load_dotenv
load_dotenv()
spreadsheet = get_spreadsheet()
def get_markets_df(wk_full):
markets_df = pd.DataFrame(wk_full.get_all_records())
markets_df = markets_df[['question', 'answer1', 'answer2', 'token1', 'token2']]
markets_df['token1'] = markets_df['token1'].astype(str)
markets_df['token2'] = markets_df['token2'].astype(str)
return markets_df
def get_all_orders(client):
orders = client.client.get_orders()
orders_df = pd.DataFrame(orders)
if len(orders_df) > 0:
orders_df['order_size'] = orders_df['original_size'].astype('float') - orders_df['size_matched'].astype('float')
orders_df = orders_df[['asset_id', 'order_size', 'side', 'price']]
orders_df = orders_df.rename(columns={'side': 'order_side', 'price': 'order_price'})
return orders_df
else:
return pd.DataFrame()
def get_all_positions(client):
try:
positions = client.get_all_positions()
positions = positions[['asset', 'size', 'avgPrice', 'curPrice', 'percentPnl']]
positions = positions.rename(columns={'size': 'position_size'})
return positions
except:
return pd.DataFrame()
def combine_dfs(orders_df, positions, markets_df, selected_df):
merged_df = orders_df.merge(positions, left_on=['asset_id'], right_on=['asset'], how='outer')
merged_df['asset_id'] = merged_df['asset_id'].combine_first(merged_df['asset'])
merged_df = merged_df.drop(columns='asset', axis=1)
merge_token1 = merged_df.merge(markets_df, left_on='asset_id', right_on='token1', how='inner')
merge_token1['merged_with'] = 'token1'
# Merge with token2
merge_token2 = merged_df.merge(markets_df, left_on='asset_id', right_on='token2', how='inner')
merge_token2['merged_with'] = 'token2'
# Combine the results
combined_df = pd.concat([merge_token1, merge_token2])
assert len(merged_df) == len(combined_df)
combined_df['answer'] = combined_df.apply(
lambda row: row['answer1'] if row['merged_with'] == 'token1' else row['answer2'], axis=1
)
combined_df = combined_df[['question', 'answer', 'order_size', 'order_side', 'order_price', 'position_size', 'avgPrice', 'curPrice']]
combined_df['order_side'] = combined_df['order_side'].fillna('')
combined_df = combined_df.fillna(0)
combined_df['marketInSelected'] = combined_df['question'].isin(selected_df['question'])
combined_df = combined_df.sort_values('question')
combined_df = combined_df.sort_values('marketInSelected')
return combined_df
def get_earnings(client):
args = RequestArgs(method='GET', request_path='/rewards/user/markets')
l2Headers = create_level_2_headers(client.signer, client.creds, args)
url = "https://polymarket.com/api/rewards/markets"
cursor = ''
markets = []
params = {
"l2Headers": json.dumps(l2Headers),
"orderBy": "earnings",
"position": "DESC",
"makerAddress": os.getenv('BROWSER_WALLET'),
"authenticationType": "eoa",
"nextCursor": cursor,
"requestPath": "/rewards/user/markets"
}
r = requests.get(url, params=params)
results = r.json()
data = pd.DataFrame(results['data'])
data['earnings'] = data['earnings'].apply(lambda x: x[0]['earnings'])
data = data[data['earnings'] > 0].reset_index(drop=True)
data = data[['question', 'earnings', 'earning_percentage']]
return data
def update_stats_once(client):
spreadsheet = get_spreadsheet()
wk_full = spreadsheet.worksheet('Full Markets')
wk_summary = spreadsheet.worksheet('Summary')
wk_sel = spreadsheet.worksheet('Selected Markets')
selected_df = pd.DataFrame(wk_sel.get_all_records())
markets_df = get_markets_df(wk_full)
print("Got spreadsheet...")
orders_df = get_all_orders(client)
print("Got Orders...")
positions = get_all_positions(client)
print("Got Positions...")
if len(positions) > 0 or len(orders_df) > 0:
combined_df = combine_dfs(orders_df, positions, markets_df, selected_df)
earnings = get_earnings(client.client)
print("Got Earnings...")
combined_df = combined_df.merge(earnings, on='question', how='left')
combined_df = combined_df.fillna(0)
combined_df = combined_df.round(2)
combined_df = combined_df.sort_values('earnings', ascending=False)
combined_df = combined_df[['question', 'answer', 'order_size', 'position_size', 'marketInSelected', 'earnings', 'earning_percentage']]
wk_summary.clear()
set_with_dataframe(wk_summary, combined_df, include_index=False, include_column_header=True, resize=True)
else:
print("Position or order is empty")
View File
+16
View File
@@ -0,0 +1,16 @@
from google.oauth2.service_account import Credentials
import gspread
import os
def get_spreadsheet():
scope = ["https://spreadsheets.google.com/feeds", "https://www.googleapis.com/auth/drive"]
creds_file = 'credentials.json' if os.path.exists('credentials.json') else '../credentials.json'
credentials = Credentials.from_service_account_file(creds_file, scopes=scope)
client = gspread.authorize(credentials)
spreadsheet_url = os.getenv("SPREADSHEET_URL")
if not spreadsheet_url:
raise ValueError("SPREADSHEET_URL environment variable is not set")
spreadsheet = client.open_by_url(spreadsheet_url)
return spreadsheet
+12
View File
@@ -0,0 +1,12 @@
py-clob-client
python-dotenv
pandas
gspread
gspread-dataframe
sortedcontainers
websockets==12.0.0
cryptography==42.0.8
web3==5.3.1
asyncio
requests
google-auth
+422
View File
@@ -0,0 +1,422 @@
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
# Cancel existing orders for this token to avoid conflicts
if order['orders']['buy']['size'] > 0 or order['orders']['sell']['size'] > 0:
client.cancel_all_asset(order['token'])
# 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
# Cancel existing orders for this token to avoid conflicts
if order['orders']['buy']['size'] > 0 or order['orders']['sell']['size'] > 0:
client.cancel_all_asset(order['token'])
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)
# 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"\nFor {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}")
# Calculate how much to buy or sell based on our position
buy_amount, sell_amount = get_buy_sell_amount(position, bid_price, row)
# 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
}
print(f"Position: {position}, Trade Size: {row['trade_size']}, "
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 -------
# Only buy if:
# 1. Position is less than 90% of target size
# 2. Position is less than absolute cap (250)
# 3. Buy amount is above minimum size
if position < 0.9 * row['trade_size'] and position < 250 and buy_amount > 0 and buy_amount >= row['min_size']:
# 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)
# 2. Current position + orders is not enough to reach target
elif position + orders['buy']['size'] < 0.95 * row['trade_size']:
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:
print(f"Error performing trade for {market}")
traceback.print_exc()
# Clean up memory and introduce a small delay
gc.collect()
await asyncio.sleep(2)
+18
View File
@@ -0,0 +1,18 @@
from poly_data.polymarket_client import PolymarketClient
from poly_stats.account_stats import update_stats_once
import pandas as pd
import time
import traceback
client = PolymarketClient()
if __name__ == '__main__':
while True:
try:
update_stats_once(client)
except Exception as e:
traceback.print_exc()
print("Now sleeping\n")
time.sleep(60 * 60 * 3) #3 hours