K4LSH1_OPS: multi-coin LLM bot — DOGE/SOL/ETH, two-tier qwen+ornith, self-adapting

This commit is contained in:
drjones
2026-08-02 19:10:27 -07:00
commit c14bb98f7f
23 changed files with 3418 additions and 0 deletions

279
test_bot.py Normal file
View File

@@ -0,0 +1,279 @@
#!/usr/bin/env python3
"""K4LSH1_OPS unit tests — verify every decision-making component."""
import json, sqlite3, sys, time, tempfile
from pathlib import Path
from unittest.mock import patch, MagicMock
sys.path.insert(0, str(Path(__file__).parent))
from bot import (momentum, decide, rsi, hedge_positions, llm_vote,
Kalshi, load_cfg, learned_min_conf, KRAKEN_PAIRS,
btc_price, intel)
PASS = 0
FAIL = 0
def check(name, condition, detail=""):
global PASS, FAIL
if condition:
PASS += 1
print(f"{name}")
else:
FAIL += 1
print(f"{name}{detail}")
def test_momentum():
print("\n═══ MOMENTUM ═══")
conn = sqlite3.connect(":memory:")
conn.execute("CREATE TABLE ticks (ts REAL, price REAL)")
now = time.time()
# flat market
for i in range(20):
conn.execute("INSERT INTO ticks VALUES (?, 100.0)", (now - 300 + i*15,))
mom, score = momentum(conn)
check("flat market → FLAT", mom == "FLAT", f"got {mom}/{score:.4f}")
check("flat score near 0", score < 0.01, f"score={score:.4f}")
# up market
conn.execute("DELETE FROM ticks")
for i in range(20):
p = 100.0 + i * 0.5
conn.execute("INSERT INTO ticks VALUES (?,?)", (now - 300 + i*15, p))
mom, score = momentum(conn)
check("rising market → UP", mom == "UP", f"got {mom}/{score:.3f}%")
check("up score positive", score > 0.05, f"score={score:.3f}%")
# down market
conn.execute("DELETE FROM ticks")
for i in range(20):
p = 200.0 - i * 0.3
conn.execute("INSERT INTO ticks VALUES (?,?)", (now - 300 + i*15, p))
mom, score = momentum(conn)
check("falling market → DOWN", mom == "DOWN", f"got {mom}/{score:.3f}%")
check("down score positive", score > 0.01, f"score={score:.3f}%")
# too few ticks
conn.execute("DELETE FROM ticks")
conn.execute("INSERT INTO ticks VALUES (?, 100)", (now,))
mom, score = momentum(conn)
check("1 tick → FLAT", mom == "FLAT", f"got {mom}")
check("1 tick score 0", score == 0.0, f"score={score}")
conn.close()
def test_rsi():
print("\n═══ RSI ═══")
conn = sqlite3.connect(":memory:")
conn.execute("CREATE TABLE ticks (ts REAL, price REAL)")
now = time.time()
# oversold: steady decline
for i in range(30):
p = 100.0 - i * 0.5
conn.execute("INSERT INTO ticks VALUES (?,?)", (now - 1800 + i*60, p))
val = rsi(conn)
check("decline → RSI < 30", val is not None and val < 30, f"RSI={val}")
# overbought: steady climb
conn.execute("DELETE FROM ticks")
for i in range(30):
p = 100.0 + i * 0.8
conn.execute("INSERT INTO ticks VALUES (?,?)", (now - 1800 + i*60, p))
val = rsi(conn)
check("climb → RSI > 70", val is not None and val > 70, f"RSI={val}")
conn.close()
def test_learned_min_conf():
print("\n═══ ADAPTIVE CONFIDENCE ═══")
conn = sqlite3.connect(":memory:")
conn.execute("CREATE TABLE orders (id INTEGER PRIMARY KEY, pnl_cents INT, status TEXT)")
conn.execute("CREATE TABLE IF NOT EXISTS kv (k TEXT PRIMARY KEY, v REAL)")
cfg = {"learning": True, "min_conf": 0.55}
# < 8 resolved bets → use default
for i in range(5):
conn.execute("INSERT INTO orders(pnl_cents,status) VALUES (?,?)", (10, "won"))
lc = learned_min_conf(conn, cfg)
check("<8 bets → default", lc == 0.55, f"got {lc}")
# majority wins → relax confidence
for i in range(15):
conn.execute("INSERT INTO orders(pnl_cents,status) VALUES (?,?)", (15 if i < 10 else -10, "won" if i < 10 else "lost"))
lc = learned_min_conf(conn, cfg)
check("high WR → relax", lc < 0.55, f"got {lc}")
# majority losses → tighten (need fresh conn, kv starts empty)
conn.execute("DELETE FROM orders")
conn.execute("DELETE FROM kv")
for i in range(15):
conn.execute("INSERT INTO orders(pnl_cents,status) VALUES (?,?)",
(-10 if i < 10 else 15, "lost" if i < 10 else "won"))
lc = learned_min_conf(conn, {"learning": True, "min_conf": 0.55})
check("low WR → tighten", lc > 0.55, f"got {lc}")
conn.close()
def test_hedge():
print("\n═══ HEDGE LOGIC ═══")
conn = sqlite3.connect(":memory:")
conn.execute("CREATE TABLE orders (id INTEGER PRIMARY KEY, ts REAL, ticker TEXT, "
"side TEXT, count INT, price_cents INT, dry INT, order_id TEXT, status TEXT, "
"settle_ts REAL, pnl_cents INT)")
conn.execute("CREATE TABLE events (ts REAL, level TEXT, msg TEXT)")
cfg = {"dry_run": True, "min_profit_cents": 8}
ticker = "KXBTC15M-TEST"
book = {"yes": [[42]], "no": [[48]]}
# scenario: we bought NO at 45¢, YES ask is 42¢ → profit = 100-45-42=13¢ > 8¢ min
conn.execute("INSERT INTO orders VALUES (1,0,?,?,?,?,0,'sim','posted',NULL,NULL)",
(ticker, "no", 1, 45))
result = hedge_positions(conn, MagicMock(), ticker, cfg, book)
check("NO@45 YES@42 → hedge fires", result == "hedged", f"got {result}")
hedged = conn.execute("SELECT status FROM orders WHERE id=1").fetchone()[0]
check("original marked hedged", hedged == "hedged", f"got {hedged}")
new_orders = conn.execute("SELECT COUNT(*) FROM orders WHERE side='yes'").fetchone()[0]
check("counter-order placed", new_orders == 1)
# scenario: profit too small
conn.execute("DELETE FROM orders")
conn.execute("INSERT INTO orders VALUES (2,0,?,?,?,?,0,'sim','posted',NULL,NULL)",
(ticker, "no", 1, 48))
book2 = {"yes": [[49]], "no": [[50]]}
result2 = hedge_positions(conn, MagicMock(), ticker, cfg, book2)
check("NO@48 YES@49 → no hedge (1¢ profit)", result2 is None)
conn.close()
def test_decision_logic():
print("\n═══ DECISION LOGIC ═══")
conn = sqlite3.connect(":memory:")
conn.execute("CREATE TABLE ticks (ts REAL, price REAL)")
conn.execute("CREATE TABLE decisions (id INTEGER PRIMARY KEY, ts REAL, ticker TEXT, "
"mode TEXT, momentum TEXT, llm_vote TEXT, llm_conf REAL, llm_why TEXT, "
"final TEXT, price REAL, reason TEXT)")
conn.execute("CREATE TABLE orders (id INTEGER PRIMARY KEY, pnl_cents INT, status TEXT)")
conn.execute("CREATE TABLE events (ts REAL, level TEXT, msg TEXT)")
conn.execute("CREATE TABLE IF NOT EXISTS kv (k TEXT PRIMARY KEY, v REAL)")
now = time.time()
for i in range(20):
p = 100.0 + i * 0.3
conn.execute("INSERT INTO ticks VALUES (?,?)", (now - 300 + i*15, p))
cfg = {"swing_threshold_pct": 0.005, "min_conf": 0.50, "learning": False,
"mode": "AUTO", "coin": "BTC", "ollama_url": "http://x", "ollama_model": "x"}
# LLM confirms: UP spike, LLM votes DOWN (fade confirmed)
with patch("bot.intel", return_value={"chg24": 0.85, "fng": 45, "fee_fast": 8}), \
patch("bot.llm_vote", return_value=("DOWN", 0.72, "strong fade signal")):
mom, lv, lc, lw, final, why = decide(cfg, conn, 63400)
check("LLM fade confirm → DOWN", final == "DOWN", f"got {final}")
check("confidence preserved", lc == 0.72, f"got {lc}")
# LLM passes (not enough signal)
with patch("bot.intel", return_value={"chg24": 0.1}), \
patch("bot.llm_vote", return_value=("SKIP", 0.30, "no clear signal")):
mom, lv, lc, lw, final, why = decide(cfg, conn, 63400)
check("LLM skip → SKIP", final == "SKIP", f"got {final}")
# LLM goes against fade but high conf — should follow LLM
with patch("bot.intel", return_value={"chg24": 0.5}), \
patch("bot.llm_vote", return_value=("UP", 0.82, "continuation strong")):
mom, lv, lc, lw, final, why = decide(cfg, conn, 63400)
check("LLM continuation with high conf → trust LLM", final == "UP", f"got {final}/{why}")
# Low confidence → skip
with patch("bot.intel", return_value={"chg24": 0.3}), \
patch("bot.llm_vote", return_value=("DOWN", 0.35, "weak")):
mom, lv, lc, lw, final, why = decide(cfg, conn, 63400)
check("low conf → SKIP", final == "SKIP", f"got {final}")
conn.close()
def test_price_sources():
print("\n═══ PRICE SOURCES ═══")
for coin, pair in [("BTC","XBTUSD"), ("DOGE","XDGUSD"), ("SOL","SOLUSD"), ("ETH","ETHUSD")]:
p = btc_price(coin)
check(f"{coin} price from Kraken", p is not None and p > 0, f"${p}" if p else "None")
# coinbase fallback
with patch("bot.requests.get") as mock_get:
mock_kraken = MagicMock()
mock_kraken.json.side_effect = Exception("kraken down")
mock_cb = MagicMock()
mock_cb.json.return_value = {"data": {"amount": "99999"}}
mock_get.side_effect = [mock_kraken, mock_cb]
p = btc_price("BTC")
check("coinbase fallback works", p == 99999.0, f"got {p}")
def test_llm_vote_parsing():
print("\n═══ LLM VOTE PARSING ═══")
cfg = {"ollama_url": "http://x", "ollama_model": "x", "coin": "BTC"}
# ornith says DOWN with confidence
mock_resp = MagicMock()
mock_resp.text = json.dumps({"response": "DOWN|0.72|RSI oversold, fading up spike"})
with patch("bot.requests.post", return_value=mock_resp):
with patch("bot.intel", return_value={"chg24": 0.5}):
with patch("bot.rsi", return_value=28):
lv, lc, lw = llm_vote(cfg, 63400, "UP", 0.12, sqlite3.connect(":memory:"))
check("parses DOWN", lv == "DOWN", f"got {lv}")
check("parses confidence", lc == 0.72, f"got {lc}")
check("parses why", len(lw) > 5, f"'{lw}'")
# ornith says SKIP
mock_resp.text = json.dumps({"response": "SKIP|0.40|no clear direction"})
with patch("bot.requests.post", return_value=mock_resp):
with patch("bot.intel", return_value={"chg24": 0.1}):
with patch("bot.rsi", return_value=55):
lv, lc, lw = llm_vote(cfg, 63400, "FLAT", 0.001, sqlite3.connect(":memory:"))
check("parses SKIP", lv == "SKIP", f"got {lv}")
# malformed response
mock_resp.text = json.dumps({"response": "garbage noise no pipe"})
with patch("bot.requests.post", return_value=mock_resp):
with patch("bot.intel", return_value={}):
with patch("bot.rsi", return_value=None):
lv, lc, lw = llm_vote(cfg, 63400, "UP", 0.10, sqlite3.connect(":memory:"))
check("malformed → SKIP", lv == "SKIP", f"got {lv}")
check("malformed → 0 conf", lc == 0.0, f"got {lc}")
def test_config():
print("\n═══ CONFIG ═══")
with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
json.dump({"mode": "AUTO", "stake_cents": 15, "coin": "TEST"}, f)
fpath = f.name
try:
from bot import HOME, CFG_PATH, load_cfg
old = str(CFG_PATH)
import bot
bot.CFG_PATH = Path(fpath)
c = load_cfg()
check("loads mode", c["mode"] == "AUTO")
check("loads stake", c["stake_cents"] == 15)
bot.CFG_PATH = Path(old)
finally:
Path(fpath).unlink(missing_ok=True)
def test_kraken_pairs():
print("\n═══ KRAKEN PAIRS ═══")
check("BTC → XBTUSD", KRAKEN_PAIRS["BTC"] == "XBTUSD")
check("DOGE → XDGUSD", KRAKEN_PAIRS["DOGE"] == "XDGUSD")
check("SOL → SOLUSD", KRAKEN_PAIRS["SOL"] == "SOLUSD")
check("ETH → ETHUSD", KRAKEN_PAIRS["ETH"] == "ETHUSD")
check("XRP → XRPUSD", KRAKEN_PAIRS["XRP"] == "XRPUSD")
if __name__ == "__main__":
print("K4LSH1_OPS UNIT TESTS")
print("=" * 50)
tests = [test_momentum, test_rsi, test_learned_min_conf, test_hedge,
test_decision_logic, test_price_sources, test_llm_vote_parsing,
test_config, test_kraken_pairs]
for t in tests:
try:
t()
except Exception as e:
print(f" ✗ CRASH: {e}")
FAIL += 1
print(f"\n{'='*50}")
print(f"PASS: {PASS} FAIL: {FAIL}")
sys.exit(0 if FAIL == 0 else 1)