fix: all-local 2-model ensemble (MacBook qwen + GamingPC ornith), no containers, disagreement=SKIP, tight RSI gate with momentum confirmation

This commit is contained in:
drjones
2026-08-03 02:02:48 -07:00
parent 820c542d89
commit 0427f015bc
8 changed files with 272 additions and 30 deletions

40
bot.py
View File

@@ -342,19 +342,18 @@ def llm_vote(cfg, price, mom, mom_score, conn):
except Exception as ex:
fast_v, fast_c, fast_w = "BORDERLINE", 0.50, f"gate: {str(ex)[:40]}"
# ── 3-model ensemble: qwen3.5 (MacBook) + ornith (GPU) + llama3.2 (CT518 diverifier) ──
NO_PROXY = {"proxies": {"http": None, "https": None}} # bypass proxy for LAN Ollama calls
# ── 2-model ensemble: qwen3.5 (MacBook) + ornith (GamingPC RTX 3070) ──
# Disagreement = SKIP (conservative). No diverifier — avoids VRAM contention.
NO_PROXY = {"proxies": {"http": None, "https": None}}
votes = []
# Model 1: qwen3.5 fast gate (MacBook)
# Model 1: qwen3.5 fast gate (MacBook — already ran)
v1 = fast_v if fast_v in ("UP","DOWN","SKIP") else "SKIP"
votes.append((v1, fast_c if fast_v in ("UP","DOWN","SKIP") else 0.50, fast_w, "qwen3.5"))
# Model 2: ornith deep verify (GamingPC RTX 3070)
# Model 2: ornith deep verify (GamingPC RTX 3070 — always hot)
try:
deep_url = cfg.get("deep_llm_url", "http://10.30.20.186:11434")
deep_model = cfg.get("deep_llm_model", "ornith:latest")
r2 = requests.post(deep_url+"/api/generate",
json={"model": deep_model, "prompt": prompt, "stream": False, "think": False, "keep_alive": "10m",
r2 = requests.post("http://10.30.20.186:11434/api/generate",
json={"model": "ornith:latest", "prompt": prompt, "stream": False, "think": False, "keep_alive": "10m",
"options": {"temperature": 0.2, "num_predict": 90}}, timeout=60, **NO_PROXY)
txt2 = r2.json().get("response","")
s2, e2 = txt2.find("{"), txt2.rfind("}")+1
@@ -366,33 +365,14 @@ def llm_vote(cfg, price, mom, mom_score, conn):
LOG.warning(f"ornith failed: {ex2}")
votes.append(("SKIP", 0.0, "ornith offline", "ornith"))
# Model 3: llama3.2 diverifier (CT518) — runs only on disputed signals
fast_vote = votes[0][0]; deep_vote = votes[1][0]
if fast_vote != deep_vote or (fast_vote in ("UP","DOWN") and votes[0][1] < 0.55):
try:
div_url = cfg.get("diverify_url", "http://10.30.20.89:11434")
div_model = cfg.get("diverify_model", "llama3.2:latest")
r3 = requests.post(div_url+"/api/generate",
json={"model": div_model, "prompt": prompt, "stream": False, "keep_alive": "10m",
"options": {"temperature": 0.3, "num_predict": 60}}, timeout=30, **NO_PROXY)
txt3 = r3.json().get("response","")
s3, e3 = txt3.find("{"), txt3.rfind("}")+1
d3 = json.loads(txt3[s3:e3]) if s3 >= 0 else {}
v3 = str(d3.get("vote","SKIP")).upper()
if v3 not in ("UP","DOWN","SKIP"): v3 = "SKIP"
votes.append((v3, float(d3.get("conf",0)), str(d3.get("why",""))[:120], "llama3.2"))
except Exception as ex3:
LOG.warning(f"diverifier failed: {ex3}")
votes.append(("SKIP", 0.0, "diverifier offline", "llama3.2"))
# Majority vote: UP vs DOWN vs SKIP counts
# Majority: only trade if BOTH agree (2/2). Any disagreement = SKIP.
tally = {"UP": 0, "DOWN": 0, "SKIP": 0}
for v, c, w, src in votes:
tally[v] += 1
winner = "UP" if tally["UP"] >= 2 else ("DOWN" if tally["DOWN"] >= 2 else "SKIP")
winner = "UP" if tally["UP"] == 2 else ("DOWN" if tally["DOWN"] == 2 else "SKIP")
avg_conf = sum(v[1] for v in votes if v[0] == winner) / max(1, tally[winner])
reasons = " | ".join(f"{src}={v}" for v, _, _, src in votes)
tag = "ENSEMBLE" if tally[winner] >= 2 else "SPLIT"
tag = "ENSEMBLE" if winner != "SKIP" else "SPLIT"
return winner, avg_conf, f"[{tag}: {tally['UP']}U/{tally['DOWN']}D/{tally['SKIP']}S] {reasons}"
def decide(cfg, conn, price):