-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpolytrader.py
More file actions
281 lines (247 loc) · 9.9 KB
/
polytrader.py
File metadata and controls
281 lines (247 loc) · 9.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
import asyncio
import os
from datetime import datetime
from zoneinfo import ZoneInfo
import requests
from py_clob_client.client import ClobClient
from py_clob_client.clob_types import OrderArgs, OrderType
import nest_asyncio
import json # Added for parsing clobTokenIds string
nest_asyncio.apply()
# ==================== CONFIGURATION ====================
HOST = "https://clob.polymarket.com"
GAMMA_BASE = "https://gamma-api.polymarket.com"
CHAIN_ID = 137 # Polygon
# Load sensitive data from environment variables (recommended)
PRIVATE_KEY = os.getenv("PRIVATE_KEY", "your_private_key_here")
FUNDER_ADDRESS = os.getenv("FUNDER_ADDRESS", "your_funder_address_here")
# Signature type: 0=EOA, 1=POLY_PROXY, 2=POLY_GNOSIS_SAFE (default: 0)
SIGNATURE_TYPE = int(os.getenv("SIGNATURE_TYPE", "0"))
# Trading parameters
BASE_SIZE = 10
MARTINGALE = 2
MAX_SIZE = 100
PRICE_THRESHOLD = 0.0002 # 0.02%
# ==================== CLIENT SETUP ====================
client = ClobClient(
host=HOST,
key=PRIVATE_KEY,
chain_id=CHAIN_ID,
funder=FUNDER_ADDRESS,
signature_type=SIGNATURE_TYPE
)
# Set API credentials (generates or derives from private key)
client.set_api_creds(client.create_or_derive_api_creds())
# ==================== PRICE FUNCTIONS ====================
def get_binance_btc_price():
try:
resp = requests.get("https://api.binance.com/api/v3/ticker/price?symbol=BTCUSDT", timeout=5)
resp.raise_for_status()
return float(resp.json()["price"])
except Exception as e:
print(f"⚠️ Binance price error: {e}")
return None
def get_historical_binance_price(timestamp):
try:
ms = timestamp * 1000
params = {"symbol": "BTCUSDT", "interval": "1m", "startTime": ms, "limit": 1}
resp = requests.get("https://api.binance.com/api/v3/klines", params=params, timeout=5)
resp.raise_for_status()
data = resp.json()
if data:
return float(data[0][4]) # close price
except Exception as e:
print(f"⚠️ Historical Binance price error: {e}")
return None
def get_current_btc_price():
return get_binance_btc_price()
# ==================== MARKET DETECTION ====================
def get_current_5min_slug():
et_now = datetime.now(ZoneInfo("America/New_York"))
minute = et_now.minute
rounded_minute = (minute // 5) * 5
start_dt = et_now.replace(minute=rounded_minute, second=0, microsecond=0)
start_timestamp = int(start_dt.timestamp())
return f"btc-updown-5m-{start_timestamp}", start_dt
def get_market_by_slug(slug):
try:
params = {"slug": slug}
resp = requests.get(f"{GAMMA_BASE}/events", params=params, timeout=10)
resp.raise_for_status()
events = resp.json()
return events[0] if events else None
except Exception as e:
print(f"⚠️ Error fetching event by slug: {e}")
return None
async def get_clob_token_ids(condition_id):
"""
Fetch token IDs from CLOB API using condition ID via a raw HTTP request
with authentication headers from the client.
"""
try:
# No headers needed for this public endpoint—remove to avoid auth issues
url = f"{HOST}/markets"
params = {"condition_id": condition_id}
resp = requests.get(url, params=params, timeout=10) # Removed headers=headers
resp.raise_for_status()
markets = resp.json()
if markets and len(markets) > 0:
tokens = markets[0].get("tokens", [])
if len(tokens) >= 2:
# Usually tokens[0] is "Up", tokens[1] is "Down"
return tokens[0].get("token_id"), tokens[1].get("token_id")
except Exception as e:
print(f"⚠️ Error fetching CLOB market: {e}")
return None, None
async def get_active_btc_market():
slug, _ = get_current_5min_slug()
print(f"🔍 Looking for market with slug: {slug}")
event = get_market_by_slug(slug)
if not event:
print(f"❌ No event found for slug {slug}")
return None
# Verify market is open
now_utc = datetime.now(ZoneInfo("UTC"))
try:
start = datetime.fromisoformat(event["startDate"].replace("Z", "+00:00"))
end = datetime.fromisoformat(event["endDate"].replace("Z", "+00:00"))
except Exception as e:
print(f"⚠️ Date parsing error: {e}")
return None
if not (start <= now_utc <= end):
print(f"⏳ Market {slug} not open (start={start}, end={end}, now={now_utc})")
return None
market_details = event.get("markets", [])
if not market_details:
print("⚠️ Event has no markets")
return None
market = market_details[0]
condition_id = market.get("conditionId")
if not condition_id:
print("⚠️ No conditionId found")
return None
# Fetch token IDs from CLOB (authoritative source)
up_token, down_token = await get_clob_token_ids(condition_id)
if not up_token or not down_token:
# Fallback to Gamma's clobTokenIds (parse as JSON string)
gamma_tokens_str = market.get("clobTokenIds", '["",""]')
try:
gamma_tokens = json.loads(gamma_tokens_str)
up_token = gamma_tokens[0]
down_token = gamma_tokens[1]
print(f"⚠️ Using Gamma token IDs: UP={up_token}, DOWN={down_token}")
except json.JSONDecodeError as e:
print(f"⚠️ Error parsing Gamma clobTokenIds: {e}")
up_token = ""
down_token = ""
else:
print(f"✅ Got token IDs from CLOB: UP={up_token}, DOWN={down_token}")
# Get start price (Binance only for now)
start_timestamp = int(slug.split("-")[-1])
start_price = get_historical_binance_price(start_timestamp)
if start_price is None:
print("⚠️ Could not fetch start price - using 0")
start_price = 0
return {
"id": condition_id,
"slug": slug,
"up_token": up_token,
"down_token": down_token,
"start_price": start_price,
"end_time": end
}
# ==================== TRADING SIGNAL ====================
def get_condition_signal(market, current_price):
if current_price is None:
print("⏸️ No signal: current_price is None")
return None
if market["start_price"] == 0:
print("⏸️ No signal: start_price is 0 (historical fetch failed)")
return None
price_change_pct = (current_price - market["start_price"]) / market["start_price"]
print(f"📊 Price change: {price_change_pct:.4%} (threshold: ±{PRICE_THRESHOLD:.2%})")
if price_change_pct > PRICE_THRESHOLD:
print("🔥 UP signal triggered")
return "UP"
elif price_change_pct < -PRICE_THRESHOLD:
print("🔥 DOWN signal triggered")
return "DOWN"
else:
print("⏸️ No signal: price change below threshold")
return None
# ==================== ORDER PLACEMENT ====================
async def place_trade(token_id, side, size):
if not token_id:
print("❌ Cannot place trade: token_id is empty")
return False
order = OrderArgs(
token_id=token_id,
price=0.5, # Consider using a limit order
size=size,
side=side.upper()
)
try:
signed = client.create_order(order) # Removed 'await' as create_order is synchronous
resp = await client.post_order(signed, OrderType.GTC)
print(f"✅ Order placed: {resp['order_id']} | {side} {size} USDC")
return True
except Exception as e:
print(f"❌ Trade failed: {e}")
return False
def get_last_outcome(market_id):
try:
url = f"https://data-api.polymarket.com/positions?user={FUNDER_ADDRESS}&market={market_id}"
resp = requests.get(url, timeout=10)
if resp.ok:
positions = resp.json()
for pos in positions:
if float(pos.get("payout", 0)) > 0:
return "WIN"
elif float(pos.get("payout", 0)) < 0:
return "LOSS"
except Exception as e:
print(f"⚠️ Could not fetch outcome: {e}")
return "UNKNOWN"
# ==================== MAIN LOOP ====================
async def main():
print("🚀 Starting 5‑min BTC bot (fixed token ID fetch)...")
current_size = BASE_SIZE
last_market_id = None
while True:
try:
market = await get_active_btc_market()
if not market:
print("⏳ No active market. Retrying in 30 seconds...")
await asyncio.sleep(30)
continue
if last_market_id and last_market_id != market["id"]:
outcome = get_last_outcome(last_market_id)
if outcome == "WIN":
current_size = BASE_SIZE
print("🎉 Previous trade won. Size reset.")
elif outcome == "LOSS":
current_size = min(current_size * MARTINGALE, MAX_SIZE)
print(f"😢 Previous trade lost. New size: {current_size}")
last_market_id = market["id"]
price = get_current_btc_price()
if price:
print(f"💰 Current BTC price: ${price:,.2f}")
else:
print("💰 Current BTC price: unavailable")
if market['start_price']:
print(f"🎯 Start price: ${market['start_price']:,.2f}")
else:
print("🎯 Start price: unavailable")
signal = get_condition_signal(market, price)
if signal:
token_id = market["up_token"] if signal == "UP" else market["down_token"]
print(f"⚡ Placing trade: {signal} ${current_size}")
await place_trade(token_id, "BUY", current_size)
else:
print("⏸️ No trade this cycle")
await asyncio.sleep(30)
except Exception as e:
print(f"❌ Unexpected error in main loop: {e}")
await asyncio.sleep(60)
if __name__ == "__main__":
asyncio.run(main())