Full sync - all projects, memory, configs
This commit is contained in:
281
tools/strategy-sentinel.py
Normal file
281
tools/strategy-sentinel.py
Normal file
@ -0,0 +1,281 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Strategy Performance Sentinel — Auto-run strategies, rank winners, spawn variants.
|
||||
|
||||
Tracks trading/prediction strategy performance over time, ranks by ROI,
|
||||
and suggests parameter variations for top performers.
|
||||
|
||||
Usage:
|
||||
python3 strategy-sentinel.py status # Show all strategy performance
|
||||
python3 strategy-sentinel.py evaluate # Run evaluation cycle
|
||||
python3 strategy-sentinel.py add <name> <json> # Register a strategy
|
||||
python3 strategy-sentinel.py rank # Rank strategies by performance
|
||||
python3 strategy-sentinel.py spawn # Auto-spawn variants of winners
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
import random
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
DATA_DIR = Path(__file__).parent.parent / "data" / "strategy-sentinel"
|
||||
DATA_DIR.mkdir(parents=True, exist_ok=True)
|
||||
STRATEGIES_FILE = DATA_DIR / "strategies.json"
|
||||
HISTORY_FILE = DATA_DIR / "history.jsonl"
|
||||
|
||||
# Seed strategies based on Polymarket patterns
|
||||
DEFAULT_STRATEGIES = {
|
||||
"tail-conservative": {
|
||||
"type": "polymarket",
|
||||
"description": "Buy events under 5% probability, min $50k volume",
|
||||
"params": {"threshold": 0.05, "min_volume": 50000, "position_pct": 2},
|
||||
"metrics": {"trades": 0, "wins": 0, "losses": 0, "total_pnl": 0, "roi_pct": 0},
|
||||
"active": True,
|
||||
"created": datetime.now().isoformat(),
|
||||
},
|
||||
"tail-aggressive": {
|
||||
"type": "polymarket",
|
||||
"description": "Buy events under 15% probability, min $10k volume",
|
||||
"params": {"threshold": 0.15, "min_volume": 10000, "position_pct": 5},
|
||||
"metrics": {"trades": 0, "wins": 0, "losses": 0, "total_pnl": 0, "roi_pct": 0},
|
||||
"active": True,
|
||||
"created": datetime.now().isoformat(),
|
||||
},
|
||||
"momentum-high-vol": {
|
||||
"type": "polymarket",
|
||||
"description": "Follow momentum on high-volume markets near 50%",
|
||||
"params": {"price_range": [0.40, 0.60], "min_volume": 100000, "position_pct": 3},
|
||||
"metrics": {"trades": 0, "wins": 0, "losses": 0, "total_pnl": 0, "roi_pct": 0},
|
||||
"active": True,
|
||||
"created": datetime.now().isoformat(),
|
||||
},
|
||||
"crypto-sentiment-bull": {
|
||||
"type": "reddit-intel",
|
||||
"description": "Go long crypto when Reddit sentiment is bullish across 3+ subs",
|
||||
"params": {"min_bullish_subs": 3, "hold_hours": 24},
|
||||
"metrics": {"trades": 0, "wins": 0, "losses": 0, "total_pnl": 0, "roi_pct": 0},
|
||||
"active": True,
|
||||
"created": datetime.now().isoformat(),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def load_strategies():
|
||||
if STRATEGIES_FILE.exists():
|
||||
return json.loads(STRATEGIES_FILE.read_text())
|
||||
save_strategies(DEFAULT_STRATEGIES)
|
||||
return DEFAULT_STRATEGIES
|
||||
|
||||
|
||||
def save_strategies(strategies):
|
||||
STRATEGIES_FILE.write_text(json.dumps(strategies, indent=2))
|
||||
|
||||
|
||||
def log_event(strategy_name, event_type, data):
|
||||
entry = {
|
||||
"ts": datetime.now().isoformat(),
|
||||
"strategy": strategy_name,
|
||||
"event": event_type,
|
||||
"data": data,
|
||||
}
|
||||
with open(HISTORY_FILE, "a") as f:
|
||||
f.write(json.dumps(entry) + "\n")
|
||||
|
||||
|
||||
def record_trade(strategy_name, pnl, details=""):
|
||||
"""Record a trade result for a strategy."""
|
||||
strategies = load_strategies()
|
||||
if strategy_name not in strategies:
|
||||
print(f"❌ Strategy '{strategy_name}' not found")
|
||||
return
|
||||
|
||||
s = strategies[strategy_name]
|
||||
m = s["metrics"]
|
||||
m["trades"] += 1
|
||||
m["total_pnl"] += pnl
|
||||
if pnl > 0:
|
||||
m["wins"] += 1
|
||||
else:
|
||||
m["losses"] += 1
|
||||
m["roi_pct"] = (m["total_pnl"] / max(m["trades"], 1)) * 100 # Simplified
|
||||
m["win_rate"] = m["wins"] / max(m["trades"], 1)
|
||||
m["last_trade"] = datetime.now().isoformat()
|
||||
|
||||
save_strategies(strategies)
|
||||
log_event(strategy_name, "trade", {"pnl": pnl, "details": details})
|
||||
print(f" 📝 Recorded: {strategy_name} → P&L: ${pnl:+.2f}")
|
||||
|
||||
|
||||
def rank_strategies():
|
||||
"""Rank all strategies by performance."""
|
||||
strategies = load_strategies()
|
||||
|
||||
ranked = sorted(
|
||||
[(name, s) for name, s in strategies.items()],
|
||||
key=lambda x: x[1]["metrics"].get("roi_pct", 0),
|
||||
reverse=True
|
||||
)
|
||||
|
||||
print(f"\n🏆 Strategy Rankings — {datetime.now().strftime('%Y-%m-%d %H:%M')}")
|
||||
print("=" * 70)
|
||||
print(f"{'Rank':<5} {'Strategy':<25} {'Trades':<8} {'Win%':<8} {'P&L':<12} {'Status'}")
|
||||
print("-" * 70)
|
||||
|
||||
for i, (name, s) in enumerate(ranked, 1):
|
||||
m = s["metrics"]
|
||||
win_rate = m.get("win_rate", 0)
|
||||
status = "🟢" if s["active"] else "🔴"
|
||||
medal = ["🥇", "🥈", "🥉"][i-1] if i <= 3 and m["trades"] > 0 else f" {i}."
|
||||
|
||||
print(f"{medal:<5} {name:<25} {m['trades']:<8} {win_rate:>5.0%} ${m['total_pnl']:>+9.2f} {status}")
|
||||
|
||||
if not any(s["metrics"]["trades"] > 0 for _, s in ranked):
|
||||
print("\n 📭 No trades recorded yet. Strategies are seeded and ready.")
|
||||
print(" Run polymarket-autopilot.py to generate trade signals.")
|
||||
|
||||
return ranked
|
||||
|
||||
|
||||
def spawn_variants():
|
||||
"""Auto-spawn parameter variants of top-performing strategies."""
|
||||
strategies = load_strategies()
|
||||
|
||||
# Find strategies with trades
|
||||
active_with_trades = {n: s for n, s in strategies.items()
|
||||
if s["active"] and s["metrics"]["trades"] > 0}
|
||||
|
||||
if not active_with_trades:
|
||||
# Spawn variants of seed strategies instead
|
||||
print("📭 No trade data yet. Spawning variants of seed strategies...")
|
||||
active_with_trades = {n: s for n, s in strategies.items() if s["active"]}
|
||||
|
||||
spawned = 0
|
||||
for name, s in list(active_with_trades.items())[:3]:
|
||||
variant_name = f"{name}-v{random.randint(100, 999)}"
|
||||
if variant_name in strategies:
|
||||
continue
|
||||
|
||||
# Mutate parameters
|
||||
new_params = dict(s["params"])
|
||||
for key, val in new_params.items():
|
||||
if isinstance(val, (int, float)):
|
||||
# Vary by ±20%
|
||||
delta = val * 0.2 * random.choice([-1, 1])
|
||||
new_params[key] = round(val + delta, 4)
|
||||
|
||||
strategies[variant_name] = {
|
||||
"type": s["type"],
|
||||
"description": f"Auto-variant of {name}",
|
||||
"params": new_params,
|
||||
"metrics": {"trades": 0, "wins": 0, "losses": 0, "total_pnl": 0, "roi_pct": 0},
|
||||
"active": True,
|
||||
"parent": name,
|
||||
"created": datetime.now().isoformat(),
|
||||
}
|
||||
spawned += 1
|
||||
print(f" 🧬 Spawned: {variant_name}")
|
||||
print(f" Params: {json.dumps(new_params)}")
|
||||
|
||||
save_strategies(strategies)
|
||||
print(f"\n✅ Spawned {spawned} variant(s)")
|
||||
|
||||
|
||||
def evaluate():
|
||||
"""Run evaluation cycle: check Polymarket signals against strategy params."""
|
||||
strategies = load_strategies()
|
||||
polymarket_data = DATA_DIR.parent / "polymarket" / "latest-signals.json"
|
||||
reddit_data = DATA_DIR.parent / "reddit-intel" / "latest.json"
|
||||
|
||||
print(f"\n🔬 Strategy Evaluation — {datetime.now().strftime('%Y-%m-%d %H:%M')}")
|
||||
print("=" * 60)
|
||||
|
||||
# Check Polymarket signals
|
||||
if polymarket_data.exists():
|
||||
signals = json.loads(polymarket_data.read_text())
|
||||
print(f" 📊 Polymarket: {len(signals.get('signals', []))} signals available")
|
||||
|
||||
for name, s in strategies.items():
|
||||
if s["type"] == "polymarket" and s["active"]:
|
||||
matching = []
|
||||
for sig in signals.get("signals", []):
|
||||
params = s["params"]
|
||||
if "threshold" in params and sig.get("price", 1) < params["threshold"]:
|
||||
matching.append(sig)
|
||||
if matching:
|
||||
print(f" 🎯 {name}: {len(matching)} matching signal(s)")
|
||||
log_event(name, "signals_matched", {"count": len(matching)})
|
||||
else:
|
||||
print(" ⚠️ No Polymarket data. Run polymarket-autopilot.py scan first.")
|
||||
|
||||
# Check Reddit sentiment
|
||||
if reddit_data.exists():
|
||||
intel = json.loads(reddit_data.read_text())
|
||||
sentiment = intel.get("overall_market_sentiment", "neutral")
|
||||
print(f" 📰 Reddit sentiment: {sentiment}")
|
||||
|
||||
for name, s in strategies.items():
|
||||
if s["type"] == "reddit-intel" and s["active"]:
|
||||
bullish_subs = sum(1 for v in intel.get("sentiment_by_sub", {}).values() if v == "bullish")
|
||||
threshold = s["params"].get("min_bullish_subs", 3)
|
||||
if bullish_subs >= threshold:
|
||||
print(f" 🎯 {name}: SIGNAL (bullish subs: {bullish_subs}/{threshold})")
|
||||
log_event(name, "signal_triggered", {"bullish_subs": bullish_subs})
|
||||
else:
|
||||
print(" ⚠️ No Reddit data. Run reddit-market-intel.py first.")
|
||||
|
||||
print("\n✅ Evaluation complete")
|
||||
|
||||
|
||||
def show_status():
|
||||
strategies = load_strategies()
|
||||
print(f"\n📡 Strategy Sentinel — {len(strategies)} strategies tracked")
|
||||
print("=" * 60)
|
||||
for name, s in strategies.items():
|
||||
status = "🟢 Active" if s["active"] else "🔴 Paused"
|
||||
print(f"\n {name} [{status}]")
|
||||
print(f" Type: {s['type']} | {s['description']}")
|
||||
print(f" Params: {json.dumps(s['params'])}")
|
||||
m = s["metrics"]
|
||||
if m["trades"] > 0:
|
||||
print(f" Performance: {m['trades']} trades | {m.get('win_rate', 0):.0%} win | ${m['total_pnl']:+.2f} P&L")
|
||||
if s.get("parent"):
|
||||
print(f" Parent: {s['parent']}")
|
||||
|
||||
|
||||
def add_strategy(name, config_json):
|
||||
strategies = load_strategies()
|
||||
try:
|
||||
config = json.loads(config_json)
|
||||
except json.JSONDecodeError:
|
||||
print(f"❌ Invalid JSON: {config_json}")
|
||||
return
|
||||
|
||||
strategies[name] = {
|
||||
"type": config.get("type", "custom"),
|
||||
"description": config.get("description", "Custom strategy"),
|
||||
"params": config.get("params", {}),
|
||||
"metrics": {"trades": 0, "wins": 0, "losses": 0, "total_pnl": 0, "roi_pct": 0},
|
||||
"active": True,
|
||||
"created": datetime.now().isoformat(),
|
||||
}
|
||||
save_strategies(strategies)
|
||||
print(f"✅ Added strategy: {name}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
cmd = sys.argv[1] if len(sys.argv) > 1 else "status"
|
||||
if cmd == "status":
|
||||
show_status()
|
||||
elif cmd == "evaluate":
|
||||
evaluate()
|
||||
elif cmd == "rank":
|
||||
rank_strategies()
|
||||
elif cmd == "spawn":
|
||||
spawn_variants()
|
||||
elif cmd == "add" and len(sys.argv) >= 4:
|
||||
add_strategy(sys.argv[2], sys.argv[3])
|
||||
elif cmd == "record" and len(sys.argv) >= 4:
|
||||
record_trade(sys.argv[2], float(sys.argv[3]), sys.argv[4] if len(sys.argv) > 4 else "")
|
||||
else:
|
||||
print(__doc__)
|
||||
Reference in New Issue
Block a user