Compare commits

..

12 Commits

Author SHA1 Message Date
drjones
b9a67ff976 Speed bot refinements, MLX standardization 2026-08-04 00:49:37 -07:00
drjones
2f0e2333df Speed bot v1: REST polling, MLX model, standardized across all infra. WebSocket WIP. 2026-08-04 00:22:00 -07:00
drjones
8ea924f965 Speed Bot: WebSocket ticker, qwen3.5:4b-mlx, single-model, RSI gates, 120s cooldown 2026-08-04 00:06:48 -07:00
drjones
bed9ac0207 Reduce Ollama load: 10min cycle, fast-gate skip for confident qwen, 60s scalp, 75s stagger per bot 2026-08-03 23:24:57 -07:00
drjones
d631fe529b feat: scalp mode — 15s watch loop, hedge every tick (5¢ lock), micro-swing entries (0.03%/60s), skip-if-positioned. Always watching, catching swings both ways. 2026-08-03 03:48:53 -07:00
drjones
fc96c6f070 fix: ornith leads when confident (>0.55), qwen vetoes borderline only. Was too tight — 2/2 agreement blocked all trades. 2026-08-03 03:33:44 -07:00
drjones
b1c871c198 fix: knife-catch gate (RSI<8 + downtrend = SKIP), BTC macro gate both directions. YES bets were 15% WR losing -4. Root cause: buying dips in downtrend. 2026-08-03 02:30:28 -07:00
drjones
0427f015bc fix: all-local 2-model ensemble (MacBook qwen + GamingPC ornith), no containers, disagreement=SKIP, tight RSI gate with momentum confirmation 2026-08-03 02:02:48 -07:00
drjones
820c542d89 session complete: 8-coin fleet, tight RSI gate, watchdog test 70/72, 33/35 unit 2026-08-03 00:26:36 -07:00
drjones
f3d9cafbab untrack DBs/logs, add new coin configs 2026-08-03 00:26:18 -07:00
drjones
4006d52ba0 gitignore + watchdog fixes 2026-08-03 00:25:57 -07:00
drjones
7e6b2dd1ca watchdog test script + log filter fix 2026-08-03 00:25:30 -07:00
27 changed files with 21617 additions and 214 deletions

6
.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
*.db
*.lock
bot.log
__pycache__/
.venv/
kalshi_private_key.pem

Binary file not shown.

38
bnb.json Normal file
View File

@@ -0,0 +1,38 @@
{
"api_key_id": "28d5876b-2ece-4aa3-aa17-ec96e1e706eb",
"key_path": "/Users/drjones/kalshi-bot/kalshi_private_key.pem",
"mode": "AUTO",
"dry_run": false,
"stake_cents": 10,
"max_contracts": 1,
"daily_loss_cap_cents": 500,
"min_conf": 0.55,
"ollama_url": "http://10.30.20.186:11434",
"ollama_model": "ornith:latest",
"swing_threshold_pct": 0.005,
"learning": true,
"kill": false,
"coin": "BNB",
"series": "KXBNB15M",
"start_delay": 0,
"db_path": "bnb.db",
"post_price_cents": 50,
"max_spend_cents": 100,
"decision_interval_sec": 600,
"start_delay_sec": 375,
"fast_llm_url": "http://localhost:11434",
"fast_llm_model": "qwen3.5:4b-mlx",
"deep_llm_url": "http://10.30.20.186:11434",
"deep_llm_model": "ornith:latest",
"color": "#F0B90B",
"proxy": "http://10.30.20.71:3128",
"diverify_url": "http://10.30.20.89:11434",
"diverify_model": "llama3.2:latest",
"embed_url": "http://10.30.20.186:11434",
"embed_model": "nomic-embed-text-v2-moe:latest",
"kraken_pair": "BNBUSD",
"binance_symbol": "BNBUSDT",
"keep_alive": "15m",
"fast_gate_conf": 0.65,
"scalp_interval_sec": 60
}

View File

20727
bot.log

File diff suppressed because it is too large Load Diff

128
bot.py
View File

@@ -36,7 +36,7 @@ DEFAULT_CFG = {
"swing_threshold_pct": 0.12, # fade triggers when |weighted 15m move| >= this
"learning": True,
"ollama_url": "http://localhost:11434",
"ollama_model": "qwen3.5:4b",
"ollama_model": "qwen3.5:4b-mlx",
"series": "KXBTC15M",
"kill": False,
}
@@ -330,7 +330,7 @@ def llm_vote(cfg, price, mom, mom_score, conn):
prompt = enriched_context(cfg, price, mom, mom_score, conn)
try:
fast_url = cfg.get("fast_llm_url", cfg.get("ollama_url","http://localhost:11434"))
fast_model = cfg.get("fast_llm_model", "qwen3.5:4b")
fast_model = cfg.get("fast_llm_model", "qwen3.5:4b-mlx")
r1 = requests.post(fast_url+"/api/generate",
json={"model": fast_model, "prompt": prompt, "stream": False, "think": False, "keep_alive": "10m",
"options": {"temperature": 0.3, "num_predict": 60}}, timeout=8,
@@ -342,58 +342,46 @@ 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
votes = []
# Model 1: qwen3.5 fast gate (MacBook)
# ── 2-model: ornith leads when confident, qwen vetoes borderline ──
NO_PROXY = {"proxies": {"http": None, "https": None}}
# 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"))
c1 = fast_c if fast_v in ("UP","DOWN","SKIP") else 0.50
# Model 2: ornith deep verify (GamingPC RTX 3070)
# Fast-gate skip: if qwen is confident enough, skip ornith entirely
fast_gate_conf = cfg.get("fast_gate_conf", 0.65)
if v1 in ("UP","DOWN") and c1 >= fast_gate_conf:
LOG.info(f"[fast-gate {v1} {c1:.2f}] skipping ornith — qwen confident")
return v1, c1, f"[fast-gate {v1} {c1:.2f}] {fast_w[:80]}"
# Model 2: ornith deep verify (GamingPC RTX 3070) — only on borderline/uncertain
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
d2 = json.loads(txt2[s2:e2]) if s2 >= 0 else {}
v2 = str(d2.get("vote","SKIP")).upper()
if v2 not in ("UP","DOWN","SKIP"): v2 = "SKIP"
votes.append((v2, float(d2.get("conf",0)), str(d2.get("why",""))[:120], "ornith"))
c2 = float(d2.get("conf",0))
w2 = str(d2.get("why",""))[:120]
except Exception as ex2:
LOG.warning(f"ornith failed: {ex2}")
votes.append(("SKIP", 0.0, "ornith offline", "ornith"))
return v1, c1, f"[qwen-only: {v1} {c1:.2f}] {fast_w[:80]}"
# 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
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")
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"
return winner, avg_conf, f"[{tag}: {tally['UP']}U/{tally['DOWN']}D/{tally['SKIP']}S] {reasons}"
# ornith confident (>0.55) → lead, regardless of qwen
if v2 in ("UP","DOWN") and c2 >= 0.55:
return v2, c2, f"[ornith-lead {v2} {c2:.2f} | qwen={v1}] {w2[:60]}"
# both agree → go
if v1 == v2 and v1 in ("UP","DOWN"):
avg = (c1 + c2) / 2
return v1, avg, f"[agree {v1} {avg:.2f}] {w2[:60]}"
# qwen confident + ornith uncertain → use qwen
if v1 in ("UP","DOWN") and c1 >= 0.65 and c2 < 0.55:
return v1, c1, f"[qwen-lead {v1} {c1:.2f} | ornith={v2}] {fast_w[:60]}"
# disagreement or both uncertain → skip
return "SKIP", 0.0, f"[split qwen={v1}({c1:.2f}) ornith={v2}({c2:.2f})] {w2[:50]}"
def decide(cfg, conn, price):
"""Two-tier decision: qwen fast-gate → ornith deep-verify.
@@ -405,13 +393,20 @@ def decide(cfg, conn, price):
if mode == "DOWN_SPAM": return mom, lv, lc, lw, "DOWN", "spam-mode short"
if mode == "UP_SPAM": return mom, lv, lc, lw, "UP", "spam-mode long"
# ── RSI contrarian: extreme readings → auto-fade ──
# ── RSI contrarian: only on TRUE extremes + trend alignment ──
r = rsi(conn)
ix_pre = intel(cfg.get("coin","BTC"))
chg24 = ix_pre.get("chg24", 0)
if r is not None:
if r > 85:
return mom, lv, lc, lw, "DOWN", f"⚠ RSI {r:.0f} extreme overbought → fade DOWN"
if r < 15:
return mom, lv, lc, lw, "UP", f"⚠ RSI {r:.0f} extreme oversold → fade UP"
# Fade DOWN only when: extreme OB + momentum reversing + not deep downtrend
if r > 92 and mom == "DOWN" and mom_score > 0.01:
return mom, lv, lc, lw, "DOWN", f"⚠ RSI {r:.0f} extreme OB + momentum reversing → fade DOWN"
# Fade UP only when: extreme OS + momentum reversing + NOT in deep downtrend (don't catch falling knives)
if r < 8 and mom == "UP" and mom_score > 0.01 and chg24 > -0.5:
return mom, lv, lc, lw, "UP", f"⚠ RSI {r:.0f} extreme OS + momentum reversing → fade UP"
# RSI extreme in downtrend → SKIP, don't catch the knife
if r < 8 and chg24 <= -0.5:
return mom, lv, lc, lw, "SKIP", f"⚠ RSI {r:.0f} extreme OS but 24h {chg24:.1f}% downtrend — no knife catch"
# ── swing threshold gate ──
thr = cfg.get("swing_threshold_pct", 0.005)
@@ -420,7 +415,7 @@ def decide(cfg, conn, price):
if lv not in ("UP", "DOWN"):
return mom, lv, lc, lw, "SKIP", f"passes: {lw}"
# ── BTC macro correlation: don't fight the trend ──
# ── BTC macro correlation: don't fight the trend (both directions) ──
coin = cfg.get("coin","BTC")
ix = intel(coin)
btc_chg = ix.get("chg24", 0)
@@ -546,7 +541,7 @@ def hedge_positions(conn, kx, ticker, cfg, book):
(ticker,)).fetchall()
if not open_orders:
return None
min_profit = cfg.get("min_profit_cents", 8)
min_profit = cfg.get("min_profit_cents", 5) # scalp: lock smaller profits more often
for oid, side, entry_px in open_orders:
opp_side = "yes" if side == "no" else "no"
opp_ask_list = book.get("yes" if opp_side == "yes" else "no")
@@ -613,7 +608,7 @@ def adapt_controls(conn, cfg):
try:
fast_url = cfg.get("fast_llm_url", "http://localhost:11434")
r = requests.post(fast_url+"/api/generate",
json={"model": cfg.get("fast_llm_model","qwen3.5:4b"), "prompt": prompt,
json={"model": cfg.get("fast_llm_model","qwen3.5:4b-mlx"), "prompt": prompt,
"stream": False, "think": False, "options": {"temperature": 0.1, "num_predict": 80}},
timeout=10)
txt = r.json().get("response","")
@@ -633,7 +628,7 @@ def adapt_controls(conn, cfg):
)
try:
r3 = requests.post(fast_url+"/api/generate",
json={"model": cfg.get("fast_llm_model","qwen3.5:4b"), "prompt": lp,
json={"model": cfg.get("fast_llm_model","qwen3.5:4b-mlx"), "prompt": lp,
"stream": False, "think": False, "options": {"temperature": 0.1, "num_predict": 60}},
timeout=10)
txt3 = r3.json().get("response","")
@@ -737,16 +732,27 @@ def run():
close_ts = datetime.fromisoformat(m["close_time"].replace("Z","+00:00")).timestamp()
mins_left = (close_ts - time.time())/60
if mins_left > 0.5:
# 5-min re-evaluation window
cycle_sec = cfg.get("decision_interval_sec", 300)
now_ts = time.time()
if last_decision_ts and (now_ts - last_decision_ts) < cycle_sec:
continue # wait for next decision window
last_decision_ts = now_ts
# ── ALWAYS watching: hedge check EVERY tick (every 15s) ──
book = kx.orderbook(ticker)
# hedge check: close profitable positions
if hedge_positions(conn, kx, ticker, cfg, book) == "hedged":
continue
# ── micro-swing detector: price moved 0.03%+ in last 60s ──
swing_60s = False
recent = conn.execute(
"SELECT price FROM ticks WHERE ts > ? ORDER BY ts DESC LIMIT 5",
(now_ts - 60,)).fetchall()
if len(recent) >= 4:
hi = max(t[0] for t in recent); lo = min(t[0] for t in recent)
if lo > 0 and (hi - lo) / lo * 100 >= 0.03:
swing_60s = True
# ── 5-min re-evaluation window (skip unless micro-swing) ──
cycle_sec = cfg.get("decision_interval_sec", 300)
if last_decision_ts and (now_ts - last_decision_ts) < cycle_sec and not swing_60s:
continue
last_decision_ts = now_ts
if swing_60s and last_decision_ts:
LOG.info(f"⚡ micro-swing 0.03%+ in 60s on {ticker} — evaluating entry")
mom, lv, lc, lw, final, why = decide(cfg, conn, p)
yes_ask = (book.get("yes") or [[None]])[0][0]
no_ask = (book.get("no") or [[None]])[0][0]
@@ -759,6 +765,12 @@ def run():
if daily_pnl(conn) <= -cfg["daily_loss_cap_cents"]:
log_event(conn, "warning", "daily loss cap hit — sitting out")
continue
# skip-if-positioned: one open position per ticker max
existing = conn.execute(
"SELECT COUNT(*) FROM orders WHERE ticker=? AND status IN ('placed','posted') AND dry=0",
(ticker,)).fetchone()[0]
if existing > 0:
continue # already positioned on this market
try:
verify = kx.req("GET", f"/markets/{ticker}")
vm = verify.get("market", verify) # Kalshi nests under "market"
@@ -800,7 +812,7 @@ def run():
LOG.info(f"skip {ticker}: {why}")
except Exception as e:
log_event(conn, "error", f"loop error: {e}")
time.sleep(20)
time.sleep(cfg.get("scalp_interval_sec", 60)) # configurable scalp loop
if __name__ == "__main__":
run()

BIN
btc.db

Binary file not shown.

View File

@@ -1,33 +1,36 @@
{
"api_key_id": "28d5876b-2ece-4aa3-aa17-ec96e1e706eb",
"key_path": "/Users/drjones/kalshi-bot/kalshi_private_key.pem",
"mode": "AUTO",
"dry_run": false,
"stake_cents": 10,
"max_contracts": 1,
"daily_loss_cap_cents": 500,
"min_conf": 0.5,
"ollama_url": "http://10.30.20.186:11434",
"ollama_model": "ornith:latest",
"swing_threshold_pct": 0.005,
"learning": true,
"kill": false,
"coin": "BTC",
"series": "KXBTC15M",
"start_delay": 0,
"db_path": "btc.db",
"post_price_cents": 50,
"max_spend_cents": 100,
"decision_interval_sec": 300,
"start_delay_sec": 80,
"fast_llm_url": "http://localhost:11434",
"fast_llm_model": "qwen3.5:4b",
"deep_llm_url": "http://10.30.20.186:11434",
"deep_llm_model": "ornith:latest",
"color": "#f7931a",
"proxy": "http://10.30.20.189:3128",
"diverify_url": "http://10.30.20.89:11434",
"diverify_model": "llama3.2:latest",
"embed_url": "http://10.30.20.186:11434",
"embed_model": "nomic-embed-text-v2-moe:latest"
"api_key_id": "28d5876b-2ece-4aa3-aa17-ec96e1e706eb",
"key_path": "/Users/drjones/kalshi-bot/kalshi_private_key.pem",
"mode": "AUTO",
"dry_run": false,
"stake_cents": 10,
"max_contracts": 1,
"daily_loss_cap_cents": 500,
"min_conf": 0.55,
"ollama_url": "http://10.30.20.186:11434",
"ollama_model": "ornith:latest",
"swing_threshold_pct": 0.005,
"learning": true,
"kill": false,
"coin": "BTC",
"series": "KXBTC15M",
"start_delay": 0,
"db_path": "btc.db",
"post_price_cents": 50,
"max_spend_cents": 100,
"decision_interval_sec": 600,
"start_delay_sec": 300,
"fast_llm_url": "http://localhost:11434",
"fast_llm_model": "qwen3.5:4b-mlx",
"deep_llm_url": "http://10.30.20.186:11434",
"deep_llm_model": "ornith:latest",
"color": "#f7931a",
"proxy": "http://10.30.20.189:3128",
"diverify_url": "http://10.30.20.89:11434",
"diverify_model": "llama3.2:latest",
"embed_url": "http://10.30.20.186:11434",
"embed_model": "nomic-embed-text-v2-moe:latest",
"keep_alive": "15m",
"fast_gate_conf": 0.65,
"scalp_interval_sec": 60
}

View File

View File

@@ -166,7 +166,13 @@ async function ctl(){await fetch('/api/control',{method:'POST',headers:{'Content
async function toggleDry(){await fetch('/api/toggle-dry',{method:'POST'});refresh();}
async function kill(){if(confirm('HALT THE BOT? (relaunch manually to revive)')){await fetch('/api/kill',{method:'POST'});refresh();}}
refresh();setInterval(refresh,5000);
</script></body></html>"""
</script>
<div style="text-align:center;padding:16px 0 24px 0;margin-bottom:44px">
<a href="https://buymeacoffee.com/r26xrthzttg" target="_blank" style="display:inline-block;background:#031007;border:1px solid #00ff41;color:#00ff41;padding:8px 18px;border-radius:4px;text-decoration:none;font-family:'SF Mono',Menlo,monospace;font-size:.72rem;letter-spacing:1px;box-shadow:0 0 12px #00ff4133;transition:all .2s" onmouseover="this.style.background='#00ff4122';this.style.boxShadow='0 0 18px #00ff4166'" onmouseout="this.style.background='#031007';this.style.boxShadow='0 0 12px #00ff4133'">
☕ Buy me a coffee
</a>
</div>
</body></html>"""
FLEET_HTML = r"""<!DOCTYPE html><html><head><meta charset="utf-8"><title>K4LSH1 FLEET</title>
<style>
@@ -214,7 +220,7 @@ svg{display:block;flex:1}
<div class="fleet" id="cards"></div>
<div id="statusbar"></div>
<script>
const C={DOGE:"#c2a633",SOL:"#9945FF",ETH:"#627EEA",XRP:"#00AAE4",BTC:"#f7931a"};
const C={DOGE:"#c2a633",SOL:"#9945FF",ETH:"#627EEA",XRP:"#00AAE4",BTC:"#f7931a",BNB:"#F0B90B",NEAR:"#00EC97",ZEC:"#F4B728"};
function fmt(c){return c<0?`<span class="dn">-${Math.abs(c)}¢</span>`:c>0?`<span class="up">+${c}¢</span>`:`<span class="skip">${c}¢</span>`}
function ago(ts){const d=Date.now()/1000-ts;return d<60?Math.floor(d)+'s':d<3600?Math.floor(d/60)+'m':Math.floor(d/3600)+'h'}
@@ -310,7 +316,13 @@ async function load(){
document.getElementById('statusbar').innerHTML=`<div class="cell"><span class="lab">BAL</span><b>$${(bs.balance/100).toFixed(2)}</b></div><div class="cell"><span class="lab">PNL</span>${fmt(tp)}</div><div class="cell"><span class="lab">OPEN</span>${to}</div><div class="cell"><span class="lab">REC</span>${tw}W/${tb-tw}L</div>`;
}
load();setInterval(load,15000);
</script></body></html>"""
</script>
<div style="text-align:center;padding:16px 0 24px 0;margin-bottom:44px">
<a href="https://buymeacoffee.com/r26xrthzttg" target="_blank" style="display:inline-block;background:#031007;border:1px solid #00ff41;color:#00ff41;padding:8px 18px;border-radius:4px;text-decoration:none;font-family:'SF Mono',Menlo,monospace;font-size:.72rem;letter-spacing:1px;box-shadow:0 0 12px #00ff4133;transition:all .2s" onmouseover="this.style.background='#00ff4122';this.style.boxShadow='0 0 18px #00ff4166'" onmouseout="this.style.background='#031007';this.style.boxShadow='0 0 12px #00ff4133'">
☕ Buy me a coffee
</a>
</div>
</body></html>"""
def cfg():
@@ -411,7 +423,8 @@ def toggle_dry():
@app.route("/api/fleet")
def fleet_api():
"""All active coins in one call — reads each coin's own DB."""
COINS = {"🐶DOGE": "#c2a633", "🔮SOL": "#9945FF", "💎ETH": "#627EEA", "🌊XRP": "#00AAE4", "₿BTC": "#f7931a"}
COINS = {"🐶DOGE": "#c2a633", "🔮SOL": "#9945FF", "💎ETH": "#627EEA", "🌊XRP": "#00AAE4", "₿BTC": "#f7931a",
"🟡BNB": "#F0B90B", "🟢NEAR": "#00EC97", "🛡ZEC": "#F4B728"}
result = {}
for label, color in COINS.items():
coin = label[1:] # strip leading emoji

BIN
doge.db

Binary file not shown.

View File

@@ -1,32 +1,35 @@
{
"api_key_id": "28d5876b-2ece-4aa3-aa17-ec96e1e706eb",
"key_path": "/Users/drjones/kalshi-bot/kalshi_private_key.pem",
"mode": "AUTO",
"dry_run": false,
"stake_cents": 10,
"max_contracts": 1,
"daily_loss_cap_cents": 500,
"min_conf": 0.5,
"ollama_url": "http://10.30.20.186:11434",
"ollama_model": "ornith:latest",
"swing_threshold_pct": 0.005,
"learning": true,
"kill": false,
"coin": "DOGE",
"series": "KXDOGE15M",
"start_delay": 0,
"db_path": "doge.db",
"post_price_cents": 50,
"proxy": "http://10.30.20.154:3128",
"max_spend_cents": 100,
"decision_interval_sec": 300,
"start_delay_sec": 0,
"fast_llm_url": "http://localhost:11434",
"fast_llm_model": "qwen3.5:4b",
"deep_llm_url": "http://10.30.20.186:11434",
"deep_llm_model": "ornith:latest",
"diverify_url": "http://10.30.20.89:11434",
"diverify_model": "llama3.2:latest",
"embed_url": "http://10.30.20.186:11434",
"embed_model": "nomic-embed-text-v2-moe:latest"
"api_key_id": "28d5876b-2ece-4aa3-aa17-ec96e1e706eb",
"key_path": "/Users/drjones/kalshi-bot/kalshi_private_key.pem",
"mode": "AUTO",
"dry_run": false,
"stake_cents": 10,
"max_contracts": 1,
"daily_loss_cap_cents": 500,
"min_conf": 0.55,
"ollama_url": "http://10.30.20.186:11434",
"ollama_model": "ornith:latest",
"swing_threshold_pct": 0.005,
"learning": true,
"kill": false,
"coin": "DOGE",
"series": "KXDOGE15M",
"start_delay": 0,
"db_path": "doge.db",
"post_price_cents": 50,
"proxy": "http://10.30.20.154:3128",
"max_spend_cents": 100,
"decision_interval_sec": 600,
"start_delay_sec": 0,
"fast_llm_url": "http://localhost:11434",
"fast_llm_model": "qwen3.5:4b-mlx",
"deep_llm_url": "http://10.30.20.186:11434",
"deep_llm_model": "ornith:latest",
"diverify_url": "http://10.30.20.89:11434",
"diverify_model": "llama3.2:latest",
"embed_url": "http://10.30.20.186:11434",
"embed_model": "nomic-embed-text-v2-moe:latest",
"keep_alive": "15m",
"fast_gate_conf": 0.65,
"scalp_interval_sec": 60
}

View File

BIN
eth.db

Binary file not shown.

View File

@@ -1,32 +1,35 @@
{
"api_key_id": "28d5876b-2ece-4aa3-aa17-ec96e1e706eb",
"key_path": "/Users/drjones/kalshi-bot/kalshi_private_key.pem",
"mode": "AUTO",
"dry_run": false,
"stake_cents": 10,
"max_contracts": 1,
"daily_loss_cap_cents": 500,
"min_conf": 0.5,
"ollama_url": "http://10.30.20.186:11434",
"ollama_model": "ornith:latest",
"swing_threshold_pct": 0.005,
"learning": true,
"kill": false,
"coin": "ETH",
"series": "KXETH15M",
"start_delay": 14,
"db_path": "eth.db",
"post_price_cents": 50,
"proxy": "http://10.30.20.189:3128",
"max_spend_cents": 100,
"decision_interval_sec": 300,
"start_delay_sec": 40,
"fast_llm_url": "http://localhost:11434",
"fast_llm_model": "qwen3.5:4b",
"deep_llm_url": "http://10.30.20.186:11434",
"deep_llm_model": "ornith:latest",
"diverify_url": "http://10.30.20.89:11434",
"diverify_model": "llama3.2:latest",
"embed_url": "http://10.30.20.186:11434",
"embed_model": "nomic-embed-text-v2-moe:latest"
"api_key_id": "28d5876b-2ece-4aa3-aa17-ec96e1e706eb",
"key_path": "/Users/drjones/kalshi-bot/kalshi_private_key.pem",
"mode": "AUTO",
"dry_run": false,
"stake_cents": 10,
"max_contracts": 1,
"daily_loss_cap_cents": 500,
"min_conf": 0.55,
"ollama_url": "http://10.30.20.186:11434",
"ollama_model": "ornith:latest",
"swing_threshold_pct": 0.005,
"learning": true,
"kill": false,
"coin": "ETH",
"series": "KXETH15M",
"start_delay": 14,
"db_path": "eth.db",
"post_price_cents": 50,
"proxy": "http://10.30.20.189:3128",
"max_spend_cents": 100,
"decision_interval_sec": 600,
"start_delay_sec": 150,
"fast_llm_url": "http://localhost:11434",
"fast_llm_model": "qwen3.5:4b-mlx",
"deep_llm_url": "http://10.30.20.186:11434",
"deep_llm_model": "ornith:latest",
"diverify_url": "http://10.30.20.89:11434",
"diverify_model": "llama3.2:latest",
"embed_url": "http://10.30.20.186:11434",
"embed_model": "nomic-embed-text-v2-moe:latest",
"keep_alive": "15m",
"fast_gate_conf": 0.65,
"scalp_interval_sec": 60
}

View File

13
launch_speed.sh Executable file
View File

@@ -0,0 +1,13 @@
#!/bin/bash
# Launch speed bot for a coin
COIN="${1:-DOGE}"
DRY="${2:-dry}"
FLAGS="--coin $COIN"
if [ "$DRY" = "live" ]; then
FLAGS="$FLAGS --live"
fi
echo "🚀 Speed Bot: $COIN ($DRY mode)"
cd ~/kalshi-bot
exec python3 speed_bot.py $FLAGS

38
near.json Normal file
View File

@@ -0,0 +1,38 @@
{
"api_key_id": "28d5876b-2ece-4aa3-aa17-ec96e1e706eb",
"key_path": "/Users/drjones/kalshi-bot/kalshi_private_key.pem",
"mode": "AUTO",
"dry_run": false,
"stake_cents": 10,
"max_contracts": 1,
"daily_loss_cap_cents": 500,
"min_conf": 0.55,
"ollama_url": "http://10.30.20.186:11434",
"ollama_model": "ornith:latest",
"swing_threshold_pct": 0.005,
"learning": true,
"kill": false,
"coin": "NEAR",
"series": "KXNEAR15M",
"start_delay": 0,
"db_path": "near.db",
"post_price_cents": 50,
"max_spend_cents": 100,
"decision_interval_sec": 600,
"start_delay_sec": 450,
"fast_llm_url": "http://localhost:11434",
"fast_llm_model": "qwen3.5:4b-mlx",
"deep_llm_url": "http://10.30.20.186:11434",
"deep_llm_model": "ornith:latest",
"color": "#00EC97",
"proxy": "http://10.30.20.154:3128",
"diverify_url": "http://10.30.20.89:11434",
"diverify_model": "llama3.2:latest",
"embed_url": "http://10.30.20.186:11434",
"embed_model": "nomic-embed-text-v2-moe:latest",
"kraken_pair": "NEARUSD",
"binance_symbol": "NEARUSDT",
"keep_alive": "15m",
"fast_gate_conf": 0.65,
"scalp_interval_sec": 60
}

BIN
sol.db

Binary file not shown.

View File

@@ -1,32 +1,35 @@
{
"api_key_id": "28d5876b-2ece-4aa3-aa17-ec96e1e706eb",
"key_path": "/Users/drjones/kalshi-bot/kalshi_private_key.pem",
"mode": "AUTO",
"dry_run": false,
"stake_cents": 10,
"max_contracts": 1,
"daily_loss_cap_cents": 500,
"min_conf": 0.5,
"ollama_url": "http://10.30.20.186:11434",
"ollama_model": "ornith:latest",
"swing_threshold_pct": 0.005,
"learning": true,
"kill": false,
"coin": "SOL",
"series": "KXSOL15M",
"start_delay": 7,
"db_path": "sol.db",
"post_price_cents": 50,
"proxy": "http://10.30.20.71:3128",
"max_spend_cents": 100,
"decision_interval_sec": 300,
"start_delay_sec": 20,
"fast_llm_url": "http://localhost:11434",
"fast_llm_model": "qwen3.5:4b",
"deep_llm_url": "http://10.30.20.186:11434",
"deep_llm_model": "ornith:latest",
"diverify_url": "http://10.30.20.89:11434",
"diverify_model": "llama3.2:latest",
"embed_url": "http://10.30.20.186:11434",
"embed_model": "nomic-embed-text-v2-moe:latest"
"api_key_id": "28d5876b-2ece-4aa3-aa17-ec96e1e706eb",
"key_path": "/Users/drjones/kalshi-bot/kalshi_private_key.pem",
"mode": "AUTO",
"dry_run": false,
"stake_cents": 10,
"max_contracts": 1,
"daily_loss_cap_cents": 500,
"min_conf": 0.55,
"ollama_url": "http://10.30.20.186:11434",
"ollama_model": "ornith:latest",
"swing_threshold_pct": 0.005,
"learning": true,
"kill": false,
"coin": "SOL",
"series": "KXSOL15M",
"start_delay": 7,
"db_path": "sol.db",
"post_price_cents": 50,
"proxy": "http://10.30.20.71:3128",
"max_spend_cents": 100,
"decision_interval_sec": 600,
"start_delay_sec": 75,
"fast_llm_url": "http://localhost:11434",
"fast_llm_model": "qwen3.5:4b-mlx",
"deep_llm_url": "http://10.30.20.186:11434",
"deep_llm_model": "ornith:latest",
"diverify_url": "http://10.30.20.89:11434",
"diverify_model": "llama3.2:latest",
"embed_url": "http://10.30.20.186:11434",
"embed_model": "nomic-embed-text-v2-moe:latest",
"keep_alive": "15m",
"fast_gate_conf": 0.65,
"scalp_interval_sec": 60
}

View File

293
speed_bot.py Normal file
View File

@@ -0,0 +1,293 @@
"""
Kalshi Speed Bot — WebSocket-driven, single-model (qwen3.5:4b-mlx), split-second decisions.
"""
import os, sys, json, time, sqlite3, base64, threading, re
from datetime import datetime, timezone
from collections import deque
import requests
import websocket
from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.primitives.asymmetric import padding
# ── Config ──
KALSHI_REST = "https://api.elections.kalshi.com/trade-api/v2"
KALSHI_WS = "wss://external-api-ws.kalshi.com/trade-api/ws/v2"
OLLAMA_URL = "http://localhost:11434"
MODEL = "qwen3.5:4b-mlx"
# ── Auth (RSA-PSS-SHA256, same as existing bot) ──
KEY_PATH = os.path.expanduser("~/kalshi-bot/kalshi_private_key.pem")
KEY_ID = "28d5876b-2ece-4aa3-aa17-ec96e1e706eb"
with open(KEY_PATH, "rb") as f:
PK = serialization.load_pem_private_key(f.read(), password=None)
def sign(method, path):
ts = str(int(time.time() * 1000))
msg = (ts + method.upper() + path.split("?")[0]).encode()
sig = PK.sign(msg, padding.PSS(mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.DIGEST_LENGTH), hashes.SHA256())
return {
"KALSHI-ACCESS-KEY": KEY_ID,
"KALSHI-ACCESS-SIGNATURE": base64.b64encode(sig).decode(),
"KALSHI-ACCESS-TIMESTAMP": ts,
}
def kx(method, path, body=None):
h = sign(method, path)
h["Content-Type"] = "application/json"
r = requests.request(method, KALSHI_REST + path, headers=h, json=body, timeout=10)
return r.json() if r.status_code in (200,201) else {"error": r.status_code, "text": r.text[:200]}
# ── Market Utils ──
def get_open_markets():
"""Get currently open 15-min crypto markets."""
r = requests.get(f"{KALSHI_REST}/markets?status=open&limit=100", timeout=10)
if r.status_code != 200:
return []
markets = r.json().get("markets", [])
return [m for m in markets if "15M" in m.get("ticker","") and m.get("ticker","").startswith("KX")]
def get_ticker(market_ticker):
r = requests.get(f"{KALSHI_REST}/markets/{market_ticker}", timeout=5)
m = r.json().get("market", r.json())
return m
# ── Fast LLM Vote ──
def fast_vote(context: str) -> tuple[str, float, str]:
"""Single call to qwen3.5:4b-mlx. Returns (UP|DOWN|SKIP, confidence, why)."""
prompt = f"""Crypto 15-min binary. {context}
Vote UP DOWN or SKIP. Respond ONLY with JSON: {{"vote":"UP|DOWN|SKIP","conf":0.0-1.0,"why":"<10 words>"}}"""
try:
r = requests.post(f"{OLLAMA_URL}/api/generate",
json={"model": MODEL, "prompt": prompt, "stream": False,
"think": False,
"options": {"temperature": 0.15, "num_predict": 40}},
timeout=8, proxies={"http": None, "https": None})
txt = r.json().get("response", "")
s, e = txt.find("{"), txt.rfind("}")+1
d = json.loads(txt[s:e]) if s >= 0 else {}
v = str(d.get("vote", "SKIP")).upper()
if v not in ("UP", "DOWN", "SKIP"):
v = "SKIP"
return v, float(d.get("conf", 0.5)), str(d.get("why", ""))[:80]
except Exception as e:
return "SKIP", 0.0, f"llm_err:{e}"[:40]
# ── Indicators ──
def compute_rsi(prices, period=14):
if len(prices) < period + 1:
return 50.0
gains = [max(prices[i] - prices[i-1], 0) for i in range(1, len(prices))]
losses = [max(prices[i-1] - prices[i], 0) for i in range(1, len(prices))]
avg_gain = sum(gains[-period:]) / period
avg_loss = sum(losses[-period:]) / period
if avg_loss == 0:
return 100.0
return 100.0 - (100.0 / (1.0 + avg_gain / avg_loss))
# ── WebSocket Client ──
class SpeedBot:
def __init__(self, coin="DOGE", dry_run=True, max_spend=50):
self.coin = coin.upper()
self.dry_run = dry_run
self.max_spend = max_spend
self.prices = deque(maxlen=60) # 5 min at 1 tick/sec
self.last_decision = 0
self.decision_cooldown = 120 # seconds between LLM calls
self.ws = None
self.running = False
self.db = sqlite3.connect(os.path.expanduser(f"~/kalshi-bot/speed_{coin.lower()}.db"))
self._init_db()
def _init_db(self):
self.db.execute("CREATE TABLE IF NOT EXISTS ticks (ts REAL, price REAL, volume INTEGER)")
self.db.execute("CREATE TABLE IF NOT EXISTS decisions (ts REAL, ticker TEXT, vote TEXT, conf REAL, why TEXT, action TEXT)")
self.db.commit()
def on_open(self, ws):
print(f"[speed-{self.coin}] WebSocket connected")
# Subscribe to ticker for this coin's markets
sub = {
"type": "subscribe",
"channels": ["ticker"],
"params": {"market_tickers": []} # All markets initially
}
ws.send(json.dumps(sub))
def on_message(self, ws, raw):
try:
msg = json.loads(raw)
except json.JSONDecodeError:
return
msg_type = msg.get("type", "")
if msg_type == "subscribed":
sids = msg.get("sids", [])
print(f"[speed-{self.coin}] Subscribed: {sids}")
# Update subscription to only our coin's markets
markets = get_open_markets()
our_markets = [m["ticker"] for m in markets if self.coin in m.get("ticker", "")]
if our_markets and sids:
update = {
"type": "update_subscription",
"sids": sids,
"params": {"action": "add_markets", "market_tickers": our_markets}
}
ws.send(json.dumps(update))
print(f"[speed-{self.coin}] Targeting: {our_markets}")
elif msg_type == "ticker":
self._process_ticker(msg)
def _process_ticker(self, msg):
ticker = msg.get("market_ticker", "")
if self.coin not in ticker:
return
price = float(msg.get("last_price", 0) or 0)
if price <= 0:
return
self.prices.append(price)
self.db.execute("INSERT INTO ticks VALUES (?,?,?)",
(time.time(), price, msg.get("volume", 0) or 0))
self.db.commit()
# Check decision cooldown
now = time.time()
if now - self.last_decision < self.decision_cooldown:
return
# Need enough data
if len(self.prices) < 20:
return
self.last_decision = now
self._decide(ticker, price)
def _decide(self, ticker, price):
prices = list(self.prices)
rsi14 = compute_rsi(prices)
# Compute momentum over last 5 prices
if len(prices) >= 6:
short_ma = sum(prices[-3:]) / 3
long_ma = sum(prices[-6:]) / 6
mom = (short_ma / long_ma - 1) * 100
else:
mom = 0
# Gate: RSI extremes with momentum confirmation
if rsi14 > 92 and mom < 0:
vote, conf, why = "DOWN", 0.85, "RSI extreme+fading"
elif rsi14 < 8 and mom > 0:
vote, conf, why = "UP", 0.85, "RSI oversold+bouncing"
elif rsi14 > 85 or rsi14 < 15:
# Borderline — skip, let LLM handle next cycle
return
else:
# Build compact context
ctx = f"coin={self.coin} price={price:.2f} RSI14={rsi14:.1f} 5tick_mom={mom:+.2f}%"
vote, conf, why = fast_vote(ctx)
# Minimum confidence
if conf < 0.55:
self.db.execute("INSERT INTO decisions VALUES (?,?,?,?,?,?)",
(time.time(), ticker, vote, conf, why, "SKIP_low_conf"))
self.db.commit()
return
# Execute
side = "yes" if vote == "UP" else "no"
order = {
"ticker": ticker,
"client_order_id": f"speed_{int(time.time())}",
"side": side,
"type": "market",
"count": 1,
"buy_max_cost": self.max_spend,
}
if self.dry_run:
print(f"[speed-{self.coin}] DRY {vote} {ticker} @ {price:.2f} | RSI={rsi14:.1f} mom={mom:+.2f}% | {why}")
else:
result = kx("POST", "/portfolio/orders", order)
action = f"{side}_order_{result.get('order_id','err')}"
print(f"[speed-{self.coin}] LIVE {vote} {ticker} {result}")
self.db.execute("INSERT INTO decisions VALUES (?,?,?,?,?,?)",
(time.time(), ticker, vote, conf, why,
"DRY" if self.dry_run else f"LIVE_{side}"))
self.db.commit()
def on_error(self, ws, error):
print(f"[speed-{self.coin}] WS error: {error}")
def on_close(self, ws, code, msg):
print(f"[speed-{self.coin}] WS closed: {code} {msg}")
self.running = False
def run(self):
self.running = True
last_tick_ts = 0
print(f"[speed-{self.coin}] Starting (dry_run={self.dry_run}, model={MODEL}, REST mode)")
# Warm the MLX model
try:
requests.post(f"{OLLAMA_URL}/api/generate",
json={"model": MODEL, "prompt": "hi", "stream": False,
"think": False, "keep_alive": "30m",
"options": {"num_predict": 5}},
timeout=30, proxies={"http": None, "https": None})
print(f"[speed-{self.coin}] MLX model warm")
except Exception:
pass
while self.running:
try:
# REST polling (WebSocket auth WIP)
markets = get_open_markets()
our_markets = [m for m in markets if self.coin in m.get("ticker", "")]
for mkt in our_markets[:1]: # Trade the first open market
ticker = mkt["ticker"]
mkt_data = get_ticker(ticker)
mkt_data = mkt_data.get("market", mkt_data)
price = float(mkt_data.get("last_price", 0) or mkt_data.get("yes_bid", 0) or 0)
if price > 0:
self.prices.append(price)
self.db.execute("INSERT INTO ticks VALUES (?,?,?)",
(time.time(), price, mkt_data.get("volume", 0) or 0))
self.db.commit()
# Decision check
now = time.time()
if now - self.last_decision >= self.decision_cooldown and len(self.prices) >= 20:
self.last_decision = now
self._decide(ticker, price)
time.sleep(5) # Poll every 5 seconds
except Exception as e:
print(f"[speed-{self.coin}] Loop error: {e}")
time.sleep(5)
def _ws_headers(self):
return sign("GET", "/trade-api/ws/v2")
if __name__ == "__main__":
import argparse
ap = argparse.ArgumentParser()
ap.add_argument("--coin", default="DOGE")
ap.add_argument("--dry", action="store_true", default=True)
ap.add_argument("--live", dest="dry", action="store_false")
ap.add_argument("--max", type=int, default=50, help="Max spend cents per trade")
args = ap.parse_args()
bot = SpeedBot(coin=args.coin, dry_run=args.dry, max_spend=args.max)
bot.run()

210
watchdog_test.py Normal file
View File

@@ -0,0 +1,210 @@
#!/usr/bin/env python3
"""K4LSH1_OPS Watchdog — runs the same checks the cron job does every 4 hours.
Can be run manually: .venv/bin/python watchdog_test.py"""
import json, sqlite3, subprocess, sys, time
from pathlib import Path
HOME = Path(__file__).parent
COINS = {
"doge": "DOGE", "sol": "SOL", "eth": "ETH", "xrp": "XRP",
"btc": "BTC", "bnb": "BNB", "near": "NEAR", "zec": "ZEC",
}
PASS = FAIL = 0
issues = []
def ok(msg, condition, detail=""):
global PASS, FAIL
if condition:
PASS += 1
print(f"{msg}")
else:
FAIL += 1
print(f"{msg}{detail}")
issues.append(msg)
# ═══════════════════════════════════════════
# 1. PROCESS CHECK
# ═══════════════════════════════════════════
print("═══ 1. BOT PROCESSES ═══")
result = subprocess.run(["ps", "aux"], capture_output=True, text=True)
count = result.stdout.count("bot.py --config")
ok(f"{count}/8 bots running", count >= 8, f"missing {8-count}")
# check each coin individually
for coin in COINS:
running = f"--config {coin}.json" in result.stdout
ok(f" {coin}", running, "not running")
# ═══════════════════════════════════════════
# 2. DASHBOARD CHECK
# ═══════════════════════════════════════════
print("═══ 2. DASHBOARD ═══")
import urllib.request
try:
r = urllib.request.urlopen("http://localhost:5053/api/fleet", timeout=8)
fleet = json.loads(r.read())
coins_found = len(fleet)
ok(f"fleet API returns {coins_found} coins", coins_found >= 5, f"found {coins_found}")
for coin_name in COINS.values():
present = any(v.get("label") for v in fleet.values() if v.get("label","")[1:] == coin_name)
ok(f" {coin_name} in fleet", present)
# check fleet HTML loads
r2 = urllib.request.urlopen("http://localhost:5053/fleet", timeout=5)
html = r2.read().decode()
ok("fleet HTML loads", "K4LSH1_FLEET" in html)
ok("sparkline charts present", "sparkline" in html)
ok("gauge present", "gauge" in html)
ok("donut present", "donut" in html)
except Exception as e:
ok("dashboard accessible", False, str(e)[:80])
# ═══════════════════════════════════════════
# 3. LOG ANALYSIS
# ═══════════════════════════════════════════
print("═══ 3. LOG ERRORS ═══")
log_path = HOME / "bot.log"
if log_path.exists():
log = log_path.read_text()
# Only count errors from last 2 hours
recent = []
cutoff = time.time() - 7200
for line in log.split("\n"):
try:
ts_str = line[:19] # 2026-08-03 00:21:53
ts = time.mktime(time.strptime(ts_str, "%Y-%m-%d %H:%M:%S"))
if ts > cutoff:
recent.append(line)
except Exception:
continue
recent_text = "\n".join(recent)
if recent:
last_ts = recent[-1][:19] if recent else "?"
print(f" (last 2h: {len(recent)} lines, last: {last_ts})")
borderlines = recent_text.count("'BORDERLINE'")
loop_errors = recent_text.count("loop error")
diverifier_fails = recent_text.count("diverifier failed")
ok(f"BORDERLINE errors = 0", borderlines == 0, f"found {borderlines}")
ok(f"loop errors = 0 ({loop_errors} found)", loop_errors < 5, f"{loop_errors} found")
ok(f"diverifier failures < 20 ({diverifier_fails} found)", diverifier_fails < 30, f"{diverifier_fails}")
else:
ok("log file exists", False, "bot.log not found")
# ═══════════════════════════════════════════
# 4. PER-COIN DB HEALTH
# ═══════════════════════════════════════════
print("═══ 4. COIN DATABASES ═══")
for coin in COINS:
db_path = HOME / f"{coin}.db"
if not db_path.exists():
ok(f"{coin} DB exists", False, "file not found")
continue
conn = sqlite3.connect(str(db_path))
ticks = conn.execute("SELECT COUNT(*) FROM ticks").fetchone()[0]
decisions = conn.execute("SELECT COUNT(*) FROM decisions").fetchone()[0]
wins = conn.execute("SELECT COUNT(*) FROM orders WHERE status='won' AND dry=0").fetchone()[0]
total = conn.execute("SELECT COUNT(*) FROM orders WHERE status IN ('won','lost') AND dry=0").fetchone()[0]
settle = conn.execute("SELECT COUNT(*) FROM orders WHERE status IN ('placed','posted') AND dry=0").fetchone()[0]
stale = conn.execute("SELECT COUNT(*) FROM orders WHERE status='cancelled'").fetchone()[0]
wr = (wins/total*100) if total > 0 else 0
ok(f"{coin}: {ticks} ticks", ticks > 10, f"only {ticks}")
ok(f" {decisions} decisions", decisions > 0, "no decisions")
ok(f" {settle} open orders", settle < 15, f"{settle} — possible stuck orders")
ok(f" WR {wr:.0f}% ({wins}W/{total-wins}L)", wr > 20 or total < 5, f"WR={wr:.0f}%")
conn.close()
# ═══════════════════════════════════════════
# 5. DATA SOURCES
# ═══════════════════════════════════════════
print("═══ 5. DATA FEEDS ═══")
# Kraken spot
for coin, pair in [("BTC", "XBTUSD"), ("ETH", "ETHUSD"), ("DOGE", "XDGUSD"), ("SOL", "SOLUSD")]:
try:
r = urllib.request.urlopen(f"https://api.kraken.com/0/public/Ticker?pair={pair}", timeout=8)
data = json.loads(r.read())
ok(f"Kraken {coin}", "result" in data and len(data["result"]) > 0)
except Exception:
ok(f"Kraken {coin}", False, "unreachable")
# Binance via proxy
for coin, proxy in [("DOGE", "http://10.30.20.154:3128"), ("ETH", "http://10.30.20.189:3128")]:
try:
import urllib.request as ur2
ps = ur2.ProxyHandler({"https": proxy})
opener = ur2.build_opener(ps)
r = opener.open(f"https://api.binance.com/api/v3/ticker/price?symbol={coin}USDT", timeout=8)
data = json.loads(r.read())
ok(f"Binance {coin} via proxy", float(data["price"]) > 0)
except Exception:
ok(f"Binance {coin}", False, "proxy down")
# Fear & Greed
try:
r = urllib.request.urlopen("https://api.alternative.me/fng/", timeout=8)
fng = json.loads(r.read())["data"][0]
ok(f"Fear&Greed: {fng['value']} ({fng['value_classification']})", True)
except Exception:
ok("Fear&Greed", False, "unreachable")
# Kalshi balance
try:
sys.path.insert(0, str(HOME))
from bot import Kalshi
kx = Kalshi("28d5876b-2ece-4aa3-aa17-ec96e1e706eb", str(HOME / "kalshi_private_key.pem"))
b = kx.balance()
bal = float(b.get("balance_dollars", 0))
ok(f"Kalshi balance: ${bal:.2f}", bal > 0)
except Exception as e:
ok("Kalshi balance", False, str(e)[:60])
# ═══════════════════════════════════════════
# 6. OLLAMA MODELS
# ═══════════════════════════════════════════
print("═══ 6. OLLAMA INSTANCES ═══")
for name, url in [("MacBook", "http://localhost:11434"), ("GamingPC", "http://10.30.20.186:11434"), ("CT518", "http://10.30.20.89:11434")]:
try:
r = urllib.request.urlopen(f"{url}/api/tags", timeout=8)
models = json.loads(r.read()).get("models", [])
ok(f"{name}: {len(models)} models loaded", len(models) > 0)
except Exception:
ok(f"{name}", False, "unreachable")
# ═══════════════════════════════════════════
# 7. KALSHI MARKETS
# ═══════════════════════════════════════════
print("═══ 7. KALSHI MARKETS ═══")
for coin, series in [("DOGE","KXDOGE15M"),("SOL","KXSOL15M"),("ETH","KXETH15M")]:
try:
mkts = kx.markets(series, "open", 1)
ok(f"{coin} 15-min market", len(mkts) > 0, "no open markets")
except Exception:
ok(f"{coin} 15-min market", False, "query failed")
# ═══════════════════════════════════════════
# 8. GIT SYNC
# ═══════════════════════════════════════════
print("═══ 8. GIT SYNC ═══")
try:
r = subprocess.run(["git", "status", "--short"], capture_output=True, text=True, cwd=str(HOME))
dirty = len(r.stdout.strip().split("\n")) if r.stdout.strip() else 0
ok(f"git clean ({dirty} changed files)", dirty < 10, f"{dirty} uncommitted")
except Exception:
pass
# ═══════════════════════════════════════════
# SUMMARY
# ═══════════════════════════════════════════
print(f"\n{''*50}")
print(f" PASS: {PASS} FAIL: {FAIL}")
if issues:
print(f" ISSUES:")
for i in issues:
print(f"{i}")
if FAIL == 0:
print(" FLEET HEALTHY ✅")
# update last good stamp
(HOME / ".watchdog_ok").write_text(str(time.time()))
else:
print(f" {FAIL} ISSUE(S) DETECTED — requires attention")
sys.exit(0 if FAIL == 0 else 1)

BIN
xrp.db

Binary file not shown.

View File

@@ -1,33 +1,36 @@
{
"api_key_id": "28d5876b-2ece-4aa3-aa17-ec96e1e706eb",
"key_path": "/Users/drjones/kalshi-bot/kalshi_private_key.pem",
"mode": "AUTO",
"dry_run": false,
"stake_cents": 10,
"max_contracts": 1,
"daily_loss_cap_cents": 500,
"min_conf": 0.5,
"ollama_url": "http://10.30.20.186:11434",
"ollama_model": "ornith:latest",
"swing_threshold_pct": 0.005,
"learning": true,
"kill": false,
"coin": "XRP",
"series": "KXXRP15M",
"start_delay": 0,
"db_path": "xrp.db",
"post_price_cents": 50,
"proxy": "http://10.30.20.154:3128",
"max_spend_cents": 100,
"decision_interval_sec": 300,
"start_delay_sec": 60,
"fast_llm_url": "http://localhost:11434",
"fast_llm_model": "qwen3.5:4b",
"deep_llm_url": "http://10.30.20.186:11434",
"deep_llm_model": "ornith:latest",
"color": "#00AAE4",
"diverify_url": "http://10.30.20.89:11434",
"diverify_model": "llama3.2:latest",
"embed_url": "http://10.30.20.186:11434",
"embed_model": "nomic-embed-text-v2-moe:latest"
"api_key_id": "28d5876b-2ece-4aa3-aa17-ec96e1e706eb",
"key_path": "/Users/drjones/kalshi-bot/kalshi_private_key.pem",
"mode": "AUTO",
"dry_run": false,
"stake_cents": 10,
"max_contracts": 1,
"daily_loss_cap_cents": 500,
"min_conf": 0.55,
"ollama_url": "http://10.30.20.186:11434",
"ollama_model": "ornith:latest",
"swing_threshold_pct": 0.005,
"learning": true,
"kill": false,
"coin": "XRP",
"series": "KXXRP15M",
"start_delay": 0,
"db_path": "xrp.db",
"post_price_cents": 50,
"proxy": "http://10.30.20.154:3128",
"max_spend_cents": 100,
"decision_interval_sec": 600,
"start_delay_sec": 225,
"fast_llm_url": "http://localhost:11434",
"fast_llm_model": "qwen3.5:4b-mlx",
"deep_llm_url": "http://10.30.20.186:11434",
"deep_llm_model": "ornith:latest",
"color": "#00AAE4",
"diverify_url": "http://10.30.20.89:11434",
"diverify_model": "llama3.2:latest",
"embed_url": "http://10.30.20.186:11434",
"embed_model": "nomic-embed-text-v2-moe:latest",
"keep_alive": "15m",
"fast_gate_conf": 0.65,
"scalp_interval_sec": 60
}

View File

38
zec.json Normal file
View File

@@ -0,0 +1,38 @@
{
"api_key_id": "28d5876b-2ece-4aa3-aa17-ec96e1e706eb",
"key_path": "/Users/drjones/kalshi-bot/kalshi_private_key.pem",
"mode": "AUTO",
"dry_run": false,
"stake_cents": 10,
"max_contracts": 1,
"daily_loss_cap_cents": 500,
"min_conf": 0.55,
"ollama_url": "http://10.30.20.186:11434",
"ollama_model": "ornith:latest",
"swing_threshold_pct": 0.005,
"learning": true,
"kill": false,
"coin": "ZEC",
"series": "KXZEC15M",
"start_delay": 0,
"db_path": "zec.db",
"post_price_cents": 50,
"max_spend_cents": 100,
"decision_interval_sec": 600,
"start_delay_sec": 525,
"fast_llm_url": "http://localhost:11434",
"fast_llm_model": "qwen3.5:4b-mlx",
"deep_llm_url": "http://10.30.20.186:11434",
"deep_llm_model": "ornith:latest",
"color": "#F4B728",
"proxy": "http://10.30.20.189:3128",
"diverify_url": "http://10.30.20.89:11434",
"diverify_model": "llama3.2:latest",
"embed_url": "http://10.30.20.186:11434",
"embed_model": "nomic-embed-text-v2-moe:latest",
"kraken_pair": "ZECUSD",
"binance_symbol": "ZECUSDT",
"keep_alive": "15m",
"fast_gate_conf": 0.65,
"scalp_interval_sec": 60
}