206 lines
6.5 KiB
Python
206 lines
6.5 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
kch123 Trade Monitor + Game Price Tracker
|
|
Zero AI tokens — pure Python, sends Telegram alerts directly.
|
|
Runs as systemd timer every 5 minutes.
|
|
"""
|
|
import json
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
import urllib.request
|
|
import urllib.parse
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
|
|
WALLET = "0x6a72f61820b26b1fe4d956e17b6dc2a1ea3033ee"
|
|
PROJECT_DIR = Path(__file__).parent
|
|
DATA_DIR = PROJECT_DIR / "data" / "kch123-tracking"
|
|
TRADES_FILE = DATA_DIR / "all-trades.json"
|
|
STATS_FILE = DATA_DIR / "stats.json"
|
|
SIM_FILE = PROJECT_DIR / "data" / "simulations" / "active.json"
|
|
CRED_FILE = Path("/home/wdjones/.openclaw/workspace/.credentials/telegram-bot.env")
|
|
|
|
def load_creds():
|
|
creds = {}
|
|
with open(CRED_FILE) as f:
|
|
for line in f:
|
|
if '=' in line:
|
|
k, v = line.strip().split('=', 1)
|
|
creds[k] = v
|
|
return creds
|
|
|
|
def send_telegram(text, creds):
|
|
url = f"https://api.telegram.org/bot{creds['BOT_TOKEN']}/sendMessage"
|
|
data = urllib.parse.urlencode({
|
|
'chat_id': creds['CHAT_ID'],
|
|
'text': text,
|
|
'parse_mode': 'HTML'
|
|
}).encode()
|
|
try:
|
|
req = urllib.request.Request(url, data=data)
|
|
urllib.request.urlopen(req, timeout=10)
|
|
except Exception as e:
|
|
print(f"Telegram send failed: {e}", file=sys.stderr)
|
|
|
|
def fetch_trades(limit=100):
|
|
url = f"https://data-api.polymarket.com/activity?user={WALLET}&limit={limit}"
|
|
req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
|
|
resp = urllib.request.urlopen(req, timeout=15)
|
|
return json.loads(resp.read())
|
|
|
|
def fetch_positions():
|
|
url = f"https://data-api.polymarket.com/positions?user={WALLET}&sizeThreshold=100&limit=20&sortBy=current&sortOrder=desc"
|
|
req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
|
|
resp = urllib.request.urlopen(req, timeout=15)
|
|
return json.loads(resp.read())
|
|
|
|
def check_new_trades(creds):
|
|
"""Check for new trades and alert"""
|
|
DATA_DIR.mkdir(parents=True, exist_ok=True)
|
|
|
|
known_hashes = set()
|
|
all_trades = []
|
|
if TRADES_FILE.exists():
|
|
with open(TRADES_FILE) as f:
|
|
all_trades = json.load(f)
|
|
known_hashes = {t.get("transactionHash", "") + str(t.get("outcomeIndex", "")) for t in all_trades}
|
|
|
|
recent = fetch_trades(100)
|
|
new_trades = []
|
|
for t in recent:
|
|
key = t.get("transactionHash", "") + str(t.get("outcomeIndex", ""))
|
|
if key not in known_hashes:
|
|
new_trades.append(t)
|
|
known_hashes.add(key)
|
|
all_trades.append(t)
|
|
|
|
if new_trades:
|
|
with open(TRADES_FILE, "w") as f:
|
|
json.dump(all_trades, f)
|
|
|
|
# Format alert
|
|
buys = [t for t in new_trades if t.get("type") == "TRADE" and t.get("side") == "BUY"]
|
|
redeems = [t for t in new_trades if t.get("type") == "REDEEM"]
|
|
|
|
lines = [f"🎯 <b>kch123 New Activity</b> ({len(new_trades)} trades)"]
|
|
|
|
for t in buys[:10]:
|
|
amt = t.get('usdcSize', 0)
|
|
lines.append(f" 📈 BUY ${amt:,.2f} — {t.get('title','')} ({t.get('outcome','')})")
|
|
|
|
for t in redeems[:10]:
|
|
amt = t.get('usdcSize', 0)
|
|
icon = "✅" if amt > 0 else "❌"
|
|
lines.append(f" {icon} REDEEM ${amt:,.2f} — {t.get('title','')}")
|
|
|
|
if len(new_trades) > 20:
|
|
lines.append(f" ... and {len(new_trades) - 20} more")
|
|
|
|
send_telegram("\n".join(lines), creds)
|
|
print(f"Alerted: {len(new_trades)} new trades")
|
|
else:
|
|
print("No new trades")
|
|
|
|
def update_sim_prices():
|
|
"""Update paper trade simulation with current prices"""
|
|
if not SIM_FILE.exists():
|
|
return
|
|
|
|
with open(SIM_FILE) as f:
|
|
sim = json.load(f)
|
|
|
|
try:
|
|
positions_data = fetch_positions()
|
|
except:
|
|
return
|
|
|
|
# Build price lookup by title
|
|
price_map = {}
|
|
for p in positions_data:
|
|
price_map[p.get('title', '')] = {
|
|
'price': p.get('curPrice', 0),
|
|
'value': p.get('currentValue', 0),
|
|
}
|
|
|
|
resolved = False
|
|
for pos in sim.get('positions', []):
|
|
title = pos.get('asset', '')
|
|
if title in price_map:
|
|
new_price = price_map[title]['price']
|
|
pos['current_price'] = new_price
|
|
qty = pos.get('quantity', 0)
|
|
entry = pos.get('entry_price', 0)
|
|
pos['unrealized_pnl'] = round(qty * (new_price - entry), 2)
|
|
pos['unrealized_pnl_pct'] = round((new_price - entry) / entry * 100, 2) if entry else 0
|
|
|
|
if new_price in (0, 1, 0.0, 1.0):
|
|
resolved = True
|
|
|
|
with open(SIM_FILE, 'w') as f:
|
|
json.dump(sim, f, indent=2)
|
|
|
|
return resolved
|
|
|
|
def send_resolution_report(creds):
|
|
"""Send final P&L when game resolves"""
|
|
if not SIM_FILE.exists():
|
|
return
|
|
|
|
with open(SIM_FILE) as f:
|
|
sim = json.load(f)
|
|
|
|
total_pnl = 0
|
|
lines = ["🏈 <b>Super Bowl Resolution — kch123 Copy-Trade</b>\n"]
|
|
|
|
for pos in sim.get('positions', []):
|
|
price = pos.get('current_price', 0)
|
|
entry = pos.get('entry_price', 0)
|
|
size = pos.get('size', 0)
|
|
qty = pos.get('quantity', 0)
|
|
|
|
if price >= 0.95: # Won
|
|
pnl = qty * 1.0 - size
|
|
icon = "✅"
|
|
else: # Lost
|
|
pnl = -size
|
|
icon = "❌"
|
|
|
|
total_pnl += pnl
|
|
lines.append(f"{icon} {pos.get('asset','')}: ${pnl:+,.2f}")
|
|
|
|
lines.append(f"\n<b>Total P&L: ${total_pnl:+,.2f} ({total_pnl/sim.get('bankroll_used', 1000)*100:+.1f}%)</b>")
|
|
lines.append(f"Bankroll: $1,000 → ${1000 + total_pnl:,.2f}")
|
|
|
|
send_telegram("\n".join(lines), creds)
|
|
print(f"Resolution report sent: ${total_pnl:+,.2f}")
|
|
|
|
def main():
|
|
creds = load_creds()
|
|
|
|
# Check for new trades
|
|
try:
|
|
check_new_trades(creds)
|
|
except Exception as e:
|
|
print(f"Trade check error: {e}", file=sys.stderr)
|
|
|
|
# Update sim prices
|
|
try:
|
|
resolved = update_sim_prices()
|
|
if resolved:
|
|
send_resolution_report(creds)
|
|
except Exception as e:
|
|
print(f"Sim update error: {e}", file=sys.stderr)
|
|
|
|
# Update stats
|
|
stats = {}
|
|
if STATS_FILE.exists():
|
|
with open(STATS_FILE) as f:
|
|
stats = json.load(f)
|
|
stats["last_check"] = datetime.now(timezone.utc).isoformat()
|
|
with open(STATS_FILE, "w") as f:
|
|
json.dump(stats, f, indent=2)
|
|
|
|
if __name__ == "__main__":
|
|
main()
|