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()
|
||||
@@ -0,0 +1,93 @@
|
||||
import sqlite3
|
||||
import json
|
||||
import os
|
||||
|
||||
db_path = r"J:\Softwareprojekte\Polytrader\DBBackup\polytrader.db"
|
||||
conn = sqlite3.connect(db_path)
|
||||
conn.row_factory = sqlite3.Row
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Accounts
|
||||
cursor.execute("SELECT * FROM polymarket_accounts")
|
||||
accounts_rows = cursor.fetchall()
|
||||
accounts_dict = {}
|
||||
for r in accounts_rows:
|
||||
acc = dict(r)
|
||||
# Map to C# AccountState
|
||||
acc_obj = {
|
||||
"AccountId": acc["id"],
|
||||
"Name": acc["name"],
|
||||
"WalletAddress": acc["wallet_address"],
|
||||
"ApiKey": acc["api_key"] or "",
|
||||
"ApiSecret": acc["api_secret"] or "",
|
||||
"ApiPassphrase": acc["api_passphrase"] or "",
|
||||
"PrivateKey": acc["private_key"] or "",
|
||||
"IsDemo": bool(acc["is_demo"]),
|
||||
"IsActive": bool(acc["is_active"]),
|
||||
"CloseOnlyMode": bool(acc["close_only_mode"]),
|
||||
"PayoutAddress": acc["payout_address"] or "",
|
||||
"PayoutLimitUsd": float(acc["payout_limit_usd"] or 0),
|
||||
"PerMarketLimit": float(acc["per_market_limit"] or acc.get("max_trade_percent", 5.0)),
|
||||
"MaxPriceDifference": float(acc["max_price_difference"] or 2.0),
|
||||
"MaxBuyPrice": float(acc["max_buy_price"] or 0.98),
|
||||
"ProfitTarget": float(acc["profit_target"] or 50.0),
|
||||
"LimitUnder6h": float(acc["limit_under_6h"] or 20.0),
|
||||
"LimitUnder24h": float(acc["limit_under_24h"] or 20.0),
|
||||
"LimitUnder72h": float(acc["limit_under_72h"] or 20.0),
|
||||
"LimitOver72h": float(acc["limit_over_72h"] or 40.0),
|
||||
"TotalBalance": 0.0,
|
||||
"AvailableBalance": 0.0,
|
||||
"OpenPositions": {}
|
||||
}
|
||||
accounts_dict[str(acc["id"])] = acc_obj
|
||||
|
||||
# Traders
|
||||
cursor.execute("SELECT * FROM tracked_traders")
|
||||
traders_rows = cursor.fetchall()
|
||||
|
||||
# Links
|
||||
cursor.execute("SELECT * FROM trader_account_links")
|
||||
links_rows = cursor.fetchall()
|
||||
links_map = {}
|
||||
for r in links_rows:
|
||||
t_id = r["trader_id"]
|
||||
a_id = r["account_id"]
|
||||
if t_id not in links_map:
|
||||
links_map[t_id] = []
|
||||
links_map[t_id].append(a_id)
|
||||
|
||||
traders_dict = {}
|
||||
for r in traders_rows:
|
||||
t = dict(r)
|
||||
t_id = t["id"]
|
||||
trader_obj = {
|
||||
"Id": t_id,
|
||||
"WalletAddress": t["wallet_address"],
|
||||
"Category": t["category"] or "",
|
||||
"DisplayName": t["display_name"] or "",
|
||||
"Description": t["description"] or "",
|
||||
"Reasoning": t["reasoning"] or "",
|
||||
"IsActive": bool(t["is_active"]),
|
||||
"IsHidden": bool(t["is_hidden"]),
|
||||
"TotalTrades": int(t["total_trades"] or 0),
|
||||
"WinningTrades": int(t["winning_trades"] or 0),
|
||||
"Winrate30t": float(t["winrate_30t"] or 0.0),
|
||||
"TotalPnl": float(t["total_pnl"] or 0.0),
|
||||
"AssignedAccountIds": links_map.get(t_id, [])
|
||||
}
|
||||
traders_dict[str(t_id)] = trader_obj
|
||||
|
||||
snapshot = {
|
||||
"GlobalTradingPaused": False,
|
||||
"LiveTradingMode": 0,
|
||||
"DemoTradingMode": 0,
|
||||
"Accounts": accounts_dict,
|
||||
"Traders": traders_dict,
|
||||
"TotalCopyTrades": 0,
|
||||
"GlobalPnl": 0.0
|
||||
}
|
||||
|
||||
with open("snapshot.json", "w") as f:
|
||||
json.dump(snapshot, f, indent=4)
|
||||
|
||||
print("Export to snapshot.json complete! File size:", os.path.getsize("snapshot.json"))
|
||||
@@ -0,0 +1,15 @@
|
||||
import json
|
||||
|
||||
log_path = r"J:\Softwareprojekte\PolytraderSharp\PolyTraderSharp\bin\Debug\net8.0-windows7.0\Logs\28-03-2026-Debug.log"
|
||||
|
||||
with open(log_path, 'r', encoding='utf-8', errors='ignore') as f:
|
||||
for line in f:
|
||||
if "14:15:" in line or "14:16:" in line or "14:17:" in line:
|
||||
if "CLOB-PAYLOAD" in line:
|
||||
try:
|
||||
json_str = line.split("->")[1].strip()
|
||||
payload = json.loads(json_str)
|
||||
order = payload.get("order", {})
|
||||
print(f"[{line[:10]}] SIDE: {order.get('side')} | MAKER: {order.get('makerAmount')} | TAKER: {order.get('takerAmount')} | TYPE: {order.get('signatureType')} | TOKEN: {str(order.get('tokenId'))[:10]}...")
|
||||
except Exception as e:
|
||||
pass
|
||||
@@ -0,0 +1,23 @@
|
||||
import json
|
||||
with open(r'j:\Softwareprojekte\PolytraderSharp\PolyTraderSharp\bin\Debug\net8.0-windows7.0\PolyTraderDB\closed_trades.json', 'r', encoding='utf-8') as f:
|
||||
trades = [json.loads(line) for line in f]
|
||||
|
||||
wins = sum(1 for t in trades if t.get('RealizedPnl', 0) > 0)
|
||||
losses = sum(1 for t in trades if t.get('RealizedPnl', 0) < 0)
|
||||
pnl = sum(t.get('RealizedPnl', 0) for t in trades)
|
||||
|
||||
print(f'Total Trades: {len(trades)}')
|
||||
print(f'Wins: {wins}, Losses: {losses}')
|
||||
print(f'Total PnL: {pnl:.2f}')
|
||||
|
||||
reasons = {}
|
||||
for t in trades:
|
||||
r = t.get('ExitReason', 'None')
|
||||
p = t.get('RealizedPnl', 0)
|
||||
if r not in reasons: reasons[r] = {'count': 0, 'pnl': 0}
|
||||
reasons[r]['count'] += 1
|
||||
reasons[r]['pnl'] += p
|
||||
|
||||
print('--- By Reason ---')
|
||||
for r, d in reasons.items():
|
||||
print(r + ': ' + str(d['count']) + ' trades, PnL: ' + str(round(d['pnl'], 2)))
|
||||
Reference in New Issue
Block a user