Baseline: Ausgangszustand vor Modularisierung
Erster Commit des bestehenden monolithischen WinForms-Copytraders, inklusive der Alt-Backups (*.bak), damit diese dauerhaft in der Historie rekonstruierbar bleiben. Threema-Lib unter libs/ wurde vendored (nested .git entfernt). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
import requests
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import statistics
|
||||
import datetime
|
||||
|
||||
def fetch_activity_3days(wallet):
|
||||
all_trades = []
|
||||
offset = 0
|
||||
now_ts = int(datetime.datetime.now(datetime.timezone.utc).timestamp())
|
||||
three_days = 3 * 24 * 60 * 60
|
||||
|
||||
print(f"Fetching 3 days history for {wallet}...")
|
||||
while True:
|
||||
url = f"https://data-api.polymarket.com/activity?user={wallet}&limit=1000&offset={offset}"
|
||||
try:
|
||||
r = requests.get(url, timeout=15)
|
||||
if r.status_code == 200:
|
||||
data = r.json()
|
||||
items = data if isinstance(data, list) else (data.get("value", data.get("data", [])) if isinstance(data, dict) else [])
|
||||
|
||||
if not items:
|
||||
break
|
||||
|
||||
all_trades.extend(items)
|
||||
|
||||
# Check if we have passed 3 days
|
||||
oldest_ts = items[-1].get("timestamp")
|
||||
if oldest_ts and (now_ts - oldest_ts) >= three_days:
|
||||
break
|
||||
|
||||
offset += 1000
|
||||
else:
|
||||
break
|
||||
except Exception as e:
|
||||
print(f"Error fetching {wallet}: {e}")
|
||||
break
|
||||
|
||||
return all_trades
|
||||
|
||||
def analyze_trader(wallet, display_name):
|
||||
trades = fetch_activity_3days(wallet)
|
||||
if not trades:
|
||||
return None
|
||||
|
||||
# Filter trades and sort ascending (oldest first)
|
||||
valid_trades = [t for t in trades if t.get("type") == "TRADE" and t.get("timestamp") and t.get("asset")]
|
||||
valid_trades.sort(key=lambda x: x["timestamp"])
|
||||
|
||||
# Group by asset
|
||||
from collections import defaultdict
|
||||
by_asset = defaultdict(list)
|
||||
for t in valid_trades:
|
||||
by_asset[t["asset"]].append(t)
|
||||
|
||||
total_evaluated = 0
|
||||
snipes = 0
|
||||
hold_times = []
|
||||
|
||||
for asset, asset_trades in by_asset.items():
|
||||
# Find first BUY
|
||||
buy_ts = None
|
||||
for t in asset_trades:
|
||||
if t["side"] == "BUY":
|
||||
buy_ts = t["timestamp"]
|
||||
break
|
||||
|
||||
if buy_ts is None:
|
||||
continue
|
||||
|
||||
# Find first SELL after BUY (allow same second for immediate script-sells)
|
||||
sell_ts = None
|
||||
for t in asset_trades:
|
||||
if t["side"] == "SELL" and t["timestamp"] >= buy_ts:
|
||||
sell_ts = t["timestamp"]
|
||||
break
|
||||
|
||||
if sell_ts is not None:
|
||||
total_evaluated += 1
|
||||
hold_dur = sell_ts - buy_ts
|
||||
hold_times.append(hold_dur)
|
||||
if hold_dur < 300: # Less than 5 minutes
|
||||
snipes += 1
|
||||
|
||||
if total_evaluated == 0:
|
||||
return {
|
||||
"name": display_name,
|
||||
"wallet": wallet,
|
||||
"evaluated": 0,
|
||||
"snipes": 0,
|
||||
"ratio": 0.0,
|
||||
"median": 0
|
||||
}
|
||||
|
||||
ratio = (snipes / total_evaluated) * 100
|
||||
median_hold = statistics.median(hold_times) if hold_times else 0
|
||||
|
||||
return {
|
||||
"name": display_name,
|
||||
"wallet": wallet,
|
||||
"evaluated": total_evaluated,
|
||||
"snipes": snipes,
|
||||
"ratio": ratio,
|
||||
"median": median_hold
|
||||
}
|
||||
|
||||
def print_result(res):
|
||||
print(f"Trader: {res['name']} ({res['wallet']})")
|
||||
print(f" Evaluated Pairs: {res['evaluated']}")
|
||||
print(f" Snipe Trades (<5m): {res['snipes']}")
|
||||
if res['evaluated'] > 0:
|
||||
print(f" Sniper Ratio: {res['ratio']:.2f}%")
|
||||
print(f" Median Hold: {res['median']:.0f} seconds")
|
||||
print("-" * 40)
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Analyze a trader for Liquidity Sniping.")
|
||||
parser.add_argument("--wallet", type=str, help="Single wallet to analyze")
|
||||
parser.add_argument("--all", action="store_true", help="Analyze all active traders in PolyTraderDB.trackers.json")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.wallet:
|
||||
res = analyze_trader(args.wallet, "CLI_TEST")
|
||||
if res:
|
||||
print_result(res)
|
||||
elif args.all:
|
||||
print("Analyzing all active traders...")
|
||||
db_path = r"bin\Debug\net8.0-windows7.0\Logs\PolyTraderDB.trackers.json"
|
||||
if not os.path.exists(db_path):
|
||||
print(f"Could not find DB at {db_path}")
|
||||
return
|
||||
|
||||
with open(db_path, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
|
||||
active_traders = [t for t in data if t.get("IsActive")]
|
||||
print(f"Found {len(active_traders)} active traders.")
|
||||
|
||||
results = []
|
||||
for t in active_traders:
|
||||
wallet = t.get("WalletAddress")
|
||||
name = t.get("DisplayName")
|
||||
res = analyze_trader(wallet, name)
|
||||
if res:
|
||||
results.append(res)
|
||||
|
||||
# Sort by worst offenders (highest sniper ratio)
|
||||
results.sort(key=lambda x: x["ratio"], reverse=True)
|
||||
|
||||
print("\n=== SNIPING REPORT ===")
|
||||
print(f"{'Trader Name':<20} | {'Evaluated':<10} | {'Snipes':<8} | {'Ratio':<8} | {'Median Hold':<12}")
|
||||
print("-" * 75)
|
||||
for r in results:
|
||||
if r['evaluated'] > 0:
|
||||
print(f"{r['name']:<20} | {r['evaluated']:<10} | {r['snipes']:<8} | {r['ratio']:>5.1f}% | {r['median']:>5.0f} sec")
|
||||
else:
|
||||
print(f"{r['name']:<20} | {r['evaluated']:<10} | {r['snipes']:<8} | {'N/A':<8} | {'N/A':<12}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user