Compare commits
12 Commits
06ff6e8c76
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b9a67ff976 | ||
|
|
2f0e2333df | ||
|
|
8ea924f965 | ||
|
|
bed9ac0207 | ||
|
|
d631fe529b | ||
|
|
fc96c6f070 | ||
|
|
b1c871c198 | ||
|
|
0427f015bc | ||
|
|
820c542d89 | ||
|
|
f3d9cafbab | ||
|
|
4006d52ba0 | ||
|
|
7e6b2dd1ca |
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
*.db
|
||||||
|
*.lock
|
||||||
|
bot.log
|
||||||
|
__pycache__/
|
||||||
|
.venv/
|
||||||
|
kalshi_private_key.pem
|
||||||
Binary file not shown.
38
bnb.json
Normal file
38
bnb.json
Normal 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
|
||||||
|
}
|
||||||
128
bot.py
128
bot.py
@@ -36,7 +36,7 @@ DEFAULT_CFG = {
|
|||||||
"swing_threshold_pct": 0.12, # fade triggers when |weighted 15m move| >= this
|
"swing_threshold_pct": 0.12, # fade triggers when |weighted 15m move| >= this
|
||||||
"learning": True,
|
"learning": True,
|
||||||
"ollama_url": "http://localhost:11434",
|
"ollama_url": "http://localhost:11434",
|
||||||
"ollama_model": "qwen3.5:4b",
|
"ollama_model": "qwen3.5:4b-mlx",
|
||||||
"series": "KXBTC15M",
|
"series": "KXBTC15M",
|
||||||
"kill": False,
|
"kill": False,
|
||||||
}
|
}
|
||||||
@@ -330,7 +330,7 @@ def llm_vote(cfg, price, mom, mom_score, conn):
|
|||||||
prompt = enriched_context(cfg, price, mom, mom_score, conn)
|
prompt = enriched_context(cfg, price, mom, mom_score, conn)
|
||||||
try:
|
try:
|
||||||
fast_url = cfg.get("fast_llm_url", cfg.get("ollama_url","http://localhost:11434"))
|
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",
|
r1 = requests.post(fast_url+"/api/generate",
|
||||||
json={"model": fast_model, "prompt": prompt, "stream": False, "think": False, "keep_alive": "10m",
|
json={"model": fast_model, "prompt": prompt, "stream": False, "think": False, "keep_alive": "10m",
|
||||||
"options": {"temperature": 0.3, "num_predict": 60}}, timeout=8,
|
"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:
|
except Exception as ex:
|
||||||
fast_v, fast_c, fast_w = "BORDERLINE", 0.50, f"gate: {str(ex)[:40]}"
|
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) ──
|
# ── 2-model: ornith leads when confident, qwen vetoes borderline ──
|
||||||
NO_PROXY = {"proxies": {"http": None, "https": None}} # bypass proxy for LAN Ollama calls
|
NO_PROXY = {"proxies": {"http": None, "https": None}}
|
||||||
votes = []
|
# Model 1: qwen3.5 fast gate (MacBook — already ran)
|
||||||
# Model 1: qwen3.5 fast gate (MacBook)
|
|
||||||
v1 = fast_v if fast_v in ("UP","DOWN","SKIP") else "SKIP"
|
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:
|
try:
|
||||||
deep_url = cfg.get("deep_llm_url", "http://10.30.20.186:11434")
|
r2 = requests.post("http://10.30.20.186:11434/api/generate",
|
||||||
deep_model = cfg.get("deep_llm_model", "ornith:latest")
|
json={"model": "ornith:latest", "prompt": prompt, "stream": False, "think": False, "keep_alive": "10m",
|
||||||
r2 = requests.post(deep_url+"/api/generate",
|
|
||||||
json={"model": deep_model, "prompt": prompt, "stream": False, "think": False, "keep_alive": "10m",
|
|
||||||
"options": {"temperature": 0.2, "num_predict": 90}}, timeout=60, **NO_PROXY)
|
"options": {"temperature": 0.2, "num_predict": 90}}, timeout=60, **NO_PROXY)
|
||||||
txt2 = r2.json().get("response","")
|
txt2 = r2.json().get("response","")
|
||||||
s2, e2 = txt2.find("{"), txt2.rfind("}")+1
|
s2, e2 = txt2.find("{"), txt2.rfind("}")+1
|
||||||
d2 = json.loads(txt2[s2:e2]) if s2 >= 0 else {}
|
d2 = json.loads(txt2[s2:e2]) if s2 >= 0 else {}
|
||||||
v2 = str(d2.get("vote","SKIP")).upper()
|
v2 = str(d2.get("vote","SKIP")).upper()
|
||||||
if v2 not in ("UP","DOWN","SKIP"): v2 = "SKIP"
|
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:
|
except Exception as ex2:
|
||||||
LOG.warning(f"ornith failed: {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
|
# ornith confident (>0.55) → lead, regardless of qwen
|
||||||
fast_vote = votes[0][0]; deep_vote = votes[1][0]
|
if v2 in ("UP","DOWN") and c2 >= 0.55:
|
||||||
if fast_vote != deep_vote or (fast_vote in ("UP","DOWN") and votes[0][1] < 0.55):
|
return v2, c2, f"[ornith-lead {v2} {c2:.2f} | qwen={v1}] {w2[:60]}"
|
||||||
try:
|
# both agree → go
|
||||||
div_url = cfg.get("diverify_url", "http://10.30.20.89:11434")
|
if v1 == v2 and v1 in ("UP","DOWN"):
|
||||||
div_model = cfg.get("diverify_model", "llama3.2:latest")
|
avg = (c1 + c2) / 2
|
||||||
r3 = requests.post(div_url+"/api/generate",
|
return v1, avg, f"[agree {v1} {avg:.2f}] {w2[:60]}"
|
||||||
json={"model": div_model, "prompt": prompt, "stream": False, "keep_alive": "10m",
|
# qwen confident + ornith uncertain → use qwen
|
||||||
"options": {"temperature": 0.3, "num_predict": 60}}, timeout=30, **NO_PROXY)
|
if v1 in ("UP","DOWN") and c1 >= 0.65 and c2 < 0.55:
|
||||||
txt3 = r3.json().get("response","")
|
return v1, c1, f"[qwen-lead {v1} {c1:.2f} | ornith={v2}] {fast_w[:60]}"
|
||||||
s3, e3 = txt3.find("{"), txt3.rfind("}")+1
|
# disagreement or both uncertain → skip
|
||||||
d3 = json.loads(txt3[s3:e3]) if s3 >= 0 else {}
|
return "SKIP", 0.0, f"[split qwen={v1}({c1:.2f}) ornith={v2}({c2:.2f})] {w2[:50]}"
|
||||||
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}"
|
|
||||||
|
|
||||||
def decide(cfg, conn, price):
|
def decide(cfg, conn, price):
|
||||||
"""Two-tier decision: qwen fast-gate → ornith deep-verify.
|
"""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 == "DOWN_SPAM": return mom, lv, lc, lw, "DOWN", "spam-mode short"
|
||||||
if mode == "UP_SPAM": return mom, lv, lc, lw, "UP", "spam-mode long"
|
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)
|
r = rsi(conn)
|
||||||
|
ix_pre = intel(cfg.get("coin","BTC"))
|
||||||
|
chg24 = ix_pre.get("chg24", 0)
|
||||||
if r is not None:
|
if r is not None:
|
||||||
if r > 85:
|
# Fade DOWN only when: extreme OB + momentum reversing + not deep downtrend
|
||||||
return mom, lv, lc, lw, "DOWN", f"⚠ RSI {r:.0f} extreme overbought → fade DOWN"
|
if r > 92 and mom == "DOWN" and mom_score > 0.01:
|
||||||
if r < 15:
|
return mom, lv, lc, lw, "DOWN", f"⚠ RSI {r:.0f} extreme OB + momentum reversing → fade DOWN"
|
||||||
return mom, lv, lc, lw, "UP", f"⚠ RSI {r:.0f} extreme oversold → fade UP"
|
# 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 ──
|
# ── swing threshold gate ──
|
||||||
thr = cfg.get("swing_threshold_pct", 0.005)
|
thr = cfg.get("swing_threshold_pct", 0.005)
|
||||||
@@ -420,7 +415,7 @@ def decide(cfg, conn, price):
|
|||||||
if lv not in ("UP", "DOWN"):
|
if lv not in ("UP", "DOWN"):
|
||||||
return mom, lv, lc, lw, "SKIP", f"passes: {lw}"
|
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")
|
coin = cfg.get("coin","BTC")
|
||||||
ix = intel(coin)
|
ix = intel(coin)
|
||||||
btc_chg = ix.get("chg24", 0)
|
btc_chg = ix.get("chg24", 0)
|
||||||
@@ -546,7 +541,7 @@ def hedge_positions(conn, kx, ticker, cfg, book):
|
|||||||
(ticker,)).fetchall()
|
(ticker,)).fetchall()
|
||||||
if not open_orders:
|
if not open_orders:
|
||||||
return None
|
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:
|
for oid, side, entry_px in open_orders:
|
||||||
opp_side = "yes" if side == "no" else "no"
|
opp_side = "yes" if side == "no" else "no"
|
||||||
opp_ask_list = book.get("yes" if opp_side == "yes" else "no")
|
opp_ask_list = book.get("yes" if opp_side == "yes" else "no")
|
||||||
@@ -613,7 +608,7 @@ def adapt_controls(conn, cfg):
|
|||||||
try:
|
try:
|
||||||
fast_url = cfg.get("fast_llm_url", "http://localhost:11434")
|
fast_url = cfg.get("fast_llm_url", "http://localhost:11434")
|
||||||
r = requests.post(fast_url+"/api/generate",
|
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}},
|
"stream": False, "think": False, "options": {"temperature": 0.1, "num_predict": 80}},
|
||||||
timeout=10)
|
timeout=10)
|
||||||
txt = r.json().get("response","")
|
txt = r.json().get("response","")
|
||||||
@@ -633,7 +628,7 @@ def adapt_controls(conn, cfg):
|
|||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
r3 = requests.post(fast_url+"/api/generate",
|
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}},
|
"stream": False, "think": False, "options": {"temperature": 0.1, "num_predict": 60}},
|
||||||
timeout=10)
|
timeout=10)
|
||||||
txt3 = r3.json().get("response","")
|
txt3 = r3.json().get("response","")
|
||||||
@@ -737,16 +732,27 @@ def run():
|
|||||||
close_ts = datetime.fromisoformat(m["close_time"].replace("Z","+00:00")).timestamp()
|
close_ts = datetime.fromisoformat(m["close_time"].replace("Z","+00:00")).timestamp()
|
||||||
mins_left = (close_ts - time.time())/60
|
mins_left = (close_ts - time.time())/60
|
||||||
if mins_left > 0.5:
|
if mins_left > 0.5:
|
||||||
# 5-min re-evaluation window
|
|
||||||
cycle_sec = cfg.get("decision_interval_sec", 300)
|
|
||||||
now_ts = time.time()
|
now_ts = time.time()
|
||||||
if last_decision_ts and (now_ts - last_decision_ts) < cycle_sec:
|
# ── ALWAYS watching: hedge check EVERY tick (every 15s) ──
|
||||||
continue # wait for next decision window
|
|
||||||
last_decision_ts = now_ts
|
|
||||||
book = kx.orderbook(ticker)
|
book = kx.orderbook(ticker)
|
||||||
# hedge check: close profitable positions
|
|
||||||
if hedge_positions(conn, kx, ticker, cfg, book) == "hedged":
|
if hedge_positions(conn, kx, ticker, cfg, book) == "hedged":
|
||||||
continue
|
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)
|
mom, lv, lc, lw, final, why = decide(cfg, conn, p)
|
||||||
yes_ask = (book.get("yes") or [[None]])[0][0]
|
yes_ask = (book.get("yes") or [[None]])[0][0]
|
||||||
no_ask = (book.get("no") 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"]:
|
if daily_pnl(conn) <= -cfg["daily_loss_cap_cents"]:
|
||||||
log_event(conn, "warning", "daily loss cap hit — sitting out")
|
log_event(conn, "warning", "daily loss cap hit — sitting out")
|
||||||
continue
|
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:
|
try:
|
||||||
verify = kx.req("GET", f"/markets/{ticker}")
|
verify = kx.req("GET", f"/markets/{ticker}")
|
||||||
vm = verify.get("market", verify) # Kalshi nests under "market"
|
vm = verify.get("market", verify) # Kalshi nests under "market"
|
||||||
@@ -800,7 +812,7 @@ def run():
|
|||||||
LOG.info(f"skip {ticker}: {why}")
|
LOG.info(f"skip {ticker}: {why}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log_event(conn, "error", f"loop error: {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__":
|
if __name__ == "__main__":
|
||||||
run()
|
run()
|
||||||
|
|||||||
13
btc.json
13
btc.json
@@ -6,7 +6,7 @@
|
|||||||
"stake_cents": 10,
|
"stake_cents": 10,
|
||||||
"max_contracts": 1,
|
"max_contracts": 1,
|
||||||
"daily_loss_cap_cents": 500,
|
"daily_loss_cap_cents": 500,
|
||||||
"min_conf": 0.5,
|
"min_conf": 0.55,
|
||||||
"ollama_url": "http://10.30.20.186:11434",
|
"ollama_url": "http://10.30.20.186:11434",
|
||||||
"ollama_model": "ornith:latest",
|
"ollama_model": "ornith:latest",
|
||||||
"swing_threshold_pct": 0.005,
|
"swing_threshold_pct": 0.005,
|
||||||
@@ -18,10 +18,10 @@
|
|||||||
"db_path": "btc.db",
|
"db_path": "btc.db",
|
||||||
"post_price_cents": 50,
|
"post_price_cents": 50,
|
||||||
"max_spend_cents": 100,
|
"max_spend_cents": 100,
|
||||||
"decision_interval_sec": 300,
|
"decision_interval_sec": 600,
|
||||||
"start_delay_sec": 80,
|
"start_delay_sec": 300,
|
||||||
"fast_llm_url": "http://localhost:11434",
|
"fast_llm_url": "http://localhost:11434",
|
||||||
"fast_llm_model": "qwen3.5:4b",
|
"fast_llm_model": "qwen3.5:4b-mlx",
|
||||||
"deep_llm_url": "http://10.30.20.186:11434",
|
"deep_llm_url": "http://10.30.20.186:11434",
|
||||||
"deep_llm_model": "ornith:latest",
|
"deep_llm_model": "ornith:latest",
|
||||||
"color": "#f7931a",
|
"color": "#f7931a",
|
||||||
@@ -29,5 +29,8 @@
|
|||||||
"diverify_url": "http://10.30.20.89:11434",
|
"diverify_url": "http://10.30.20.89:11434",
|
||||||
"diverify_model": "llama3.2:latest",
|
"diverify_model": "llama3.2:latest",
|
||||||
"embed_url": "http://10.30.20.186:11434",
|
"embed_url": "http://10.30.20.186:11434",
|
||||||
"embed_model": "nomic-embed-text-v2-moe:latest"
|
"embed_model": "nomic-embed-text-v2-moe:latest",
|
||||||
|
"keep_alive": "15m",
|
||||||
|
"fast_gate_conf": 0.65,
|
||||||
|
"scalp_interval_sec": 60
|
||||||
}
|
}
|
||||||
21
dashboard.py
21
dashboard.py
@@ -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 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();}}
|
async function kill(){if(confirm('HALT THE BOT? (relaunch manually to revive)')){await fetch('/api/kill',{method:'POST'});refresh();}}
|
||||||
refresh();setInterval(refresh,5000);
|
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>
|
FLEET_HTML = r"""<!DOCTYPE html><html><head><meta charset="utf-8"><title>K4LSH1 FLEET</title>
|
||||||
<style>
|
<style>
|
||||||
@@ -214,7 +220,7 @@ svg{display:block;flex:1}
|
|||||||
<div class="fleet" id="cards"></div>
|
<div class="fleet" id="cards"></div>
|
||||||
<div id="statusbar"></div>
|
<div id="statusbar"></div>
|
||||||
<script>
|
<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 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'}
|
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>`;
|
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);
|
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():
|
def cfg():
|
||||||
@@ -411,7 +423,8 @@ def toggle_dry():
|
|||||||
@app.route("/api/fleet")
|
@app.route("/api/fleet")
|
||||||
def fleet_api():
|
def fleet_api():
|
||||||
"""All active coins in one call — reads each coin's own DB."""
|
"""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 = {}
|
result = {}
|
||||||
for label, color in COINS.items():
|
for label, color in COINS.items():
|
||||||
coin = label[1:] # strip leading emoji
|
coin = label[1:] # strip leading emoji
|
||||||
|
|||||||
11
doge.json
11
doge.json
@@ -6,7 +6,7 @@
|
|||||||
"stake_cents": 10,
|
"stake_cents": 10,
|
||||||
"max_contracts": 1,
|
"max_contracts": 1,
|
||||||
"daily_loss_cap_cents": 500,
|
"daily_loss_cap_cents": 500,
|
||||||
"min_conf": 0.5,
|
"min_conf": 0.55,
|
||||||
"ollama_url": "http://10.30.20.186:11434",
|
"ollama_url": "http://10.30.20.186:11434",
|
||||||
"ollama_model": "ornith:latest",
|
"ollama_model": "ornith:latest",
|
||||||
"swing_threshold_pct": 0.005,
|
"swing_threshold_pct": 0.005,
|
||||||
@@ -19,14 +19,17 @@
|
|||||||
"post_price_cents": 50,
|
"post_price_cents": 50,
|
||||||
"proxy": "http://10.30.20.154:3128",
|
"proxy": "http://10.30.20.154:3128",
|
||||||
"max_spend_cents": 100,
|
"max_spend_cents": 100,
|
||||||
"decision_interval_sec": 300,
|
"decision_interval_sec": 600,
|
||||||
"start_delay_sec": 0,
|
"start_delay_sec": 0,
|
||||||
"fast_llm_url": "http://localhost:11434",
|
"fast_llm_url": "http://localhost:11434",
|
||||||
"fast_llm_model": "qwen3.5:4b",
|
"fast_llm_model": "qwen3.5:4b-mlx",
|
||||||
"deep_llm_url": "http://10.30.20.186:11434",
|
"deep_llm_url": "http://10.30.20.186:11434",
|
||||||
"deep_llm_model": "ornith:latest",
|
"deep_llm_model": "ornith:latest",
|
||||||
"diverify_url": "http://10.30.20.89:11434",
|
"diverify_url": "http://10.30.20.89:11434",
|
||||||
"diverify_model": "llama3.2:latest",
|
"diverify_model": "llama3.2:latest",
|
||||||
"embed_url": "http://10.30.20.186:11434",
|
"embed_url": "http://10.30.20.186:11434",
|
||||||
"embed_model": "nomic-embed-text-v2-moe:latest"
|
"embed_model": "nomic-embed-text-v2-moe:latest",
|
||||||
|
"keep_alive": "15m",
|
||||||
|
"fast_gate_conf": 0.65,
|
||||||
|
"scalp_interval_sec": 60
|
||||||
}
|
}
|
||||||
13
eth.json
13
eth.json
@@ -6,7 +6,7 @@
|
|||||||
"stake_cents": 10,
|
"stake_cents": 10,
|
||||||
"max_contracts": 1,
|
"max_contracts": 1,
|
||||||
"daily_loss_cap_cents": 500,
|
"daily_loss_cap_cents": 500,
|
||||||
"min_conf": 0.5,
|
"min_conf": 0.55,
|
||||||
"ollama_url": "http://10.30.20.186:11434",
|
"ollama_url": "http://10.30.20.186:11434",
|
||||||
"ollama_model": "ornith:latest",
|
"ollama_model": "ornith:latest",
|
||||||
"swing_threshold_pct": 0.005,
|
"swing_threshold_pct": 0.005,
|
||||||
@@ -19,14 +19,17 @@
|
|||||||
"post_price_cents": 50,
|
"post_price_cents": 50,
|
||||||
"proxy": "http://10.30.20.189:3128",
|
"proxy": "http://10.30.20.189:3128",
|
||||||
"max_spend_cents": 100,
|
"max_spend_cents": 100,
|
||||||
"decision_interval_sec": 300,
|
"decision_interval_sec": 600,
|
||||||
"start_delay_sec": 40,
|
"start_delay_sec": 150,
|
||||||
"fast_llm_url": "http://localhost:11434",
|
"fast_llm_url": "http://localhost:11434",
|
||||||
"fast_llm_model": "qwen3.5:4b",
|
"fast_llm_model": "qwen3.5:4b-mlx",
|
||||||
"deep_llm_url": "http://10.30.20.186:11434",
|
"deep_llm_url": "http://10.30.20.186:11434",
|
||||||
"deep_llm_model": "ornith:latest",
|
"deep_llm_model": "ornith:latest",
|
||||||
"diverify_url": "http://10.30.20.89:11434",
|
"diverify_url": "http://10.30.20.89:11434",
|
||||||
"diverify_model": "llama3.2:latest",
|
"diverify_model": "llama3.2:latest",
|
||||||
"embed_url": "http://10.30.20.186:11434",
|
"embed_url": "http://10.30.20.186:11434",
|
||||||
"embed_model": "nomic-embed-text-v2-moe:latest"
|
"embed_model": "nomic-embed-text-v2-moe:latest",
|
||||||
|
"keep_alive": "15m",
|
||||||
|
"fast_gate_conf": 0.65,
|
||||||
|
"scalp_interval_sec": 60
|
||||||
}
|
}
|
||||||
13
launch_speed.sh
Executable file
13
launch_speed.sh
Executable 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
38
near.json
Normal 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
|
||||||
|
}
|
||||||
13
sol.json
13
sol.json
@@ -6,7 +6,7 @@
|
|||||||
"stake_cents": 10,
|
"stake_cents": 10,
|
||||||
"max_contracts": 1,
|
"max_contracts": 1,
|
||||||
"daily_loss_cap_cents": 500,
|
"daily_loss_cap_cents": 500,
|
||||||
"min_conf": 0.5,
|
"min_conf": 0.55,
|
||||||
"ollama_url": "http://10.30.20.186:11434",
|
"ollama_url": "http://10.30.20.186:11434",
|
||||||
"ollama_model": "ornith:latest",
|
"ollama_model": "ornith:latest",
|
||||||
"swing_threshold_pct": 0.005,
|
"swing_threshold_pct": 0.005,
|
||||||
@@ -19,14 +19,17 @@
|
|||||||
"post_price_cents": 50,
|
"post_price_cents": 50,
|
||||||
"proxy": "http://10.30.20.71:3128",
|
"proxy": "http://10.30.20.71:3128",
|
||||||
"max_spend_cents": 100,
|
"max_spend_cents": 100,
|
||||||
"decision_interval_sec": 300,
|
"decision_interval_sec": 600,
|
||||||
"start_delay_sec": 20,
|
"start_delay_sec": 75,
|
||||||
"fast_llm_url": "http://localhost:11434",
|
"fast_llm_url": "http://localhost:11434",
|
||||||
"fast_llm_model": "qwen3.5:4b",
|
"fast_llm_model": "qwen3.5:4b-mlx",
|
||||||
"deep_llm_url": "http://10.30.20.186:11434",
|
"deep_llm_url": "http://10.30.20.186:11434",
|
||||||
"deep_llm_model": "ornith:latest",
|
"deep_llm_model": "ornith:latest",
|
||||||
"diverify_url": "http://10.30.20.89:11434",
|
"diverify_url": "http://10.30.20.89:11434",
|
||||||
"diverify_model": "llama3.2:latest",
|
"diverify_model": "llama3.2:latest",
|
||||||
"embed_url": "http://10.30.20.186:11434",
|
"embed_url": "http://10.30.20.186:11434",
|
||||||
"embed_model": "nomic-embed-text-v2-moe:latest"
|
"embed_model": "nomic-embed-text-v2-moe:latest",
|
||||||
|
"keep_alive": "15m",
|
||||||
|
"fast_gate_conf": 0.65,
|
||||||
|
"scalp_interval_sec": 60
|
||||||
}
|
}
|
||||||
293
speed_bot.py
Normal file
293
speed_bot.py
Normal 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
210
watchdog_test.py
Normal 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)
|
||||||
13
xrp.json
13
xrp.json
@@ -6,7 +6,7 @@
|
|||||||
"stake_cents": 10,
|
"stake_cents": 10,
|
||||||
"max_contracts": 1,
|
"max_contracts": 1,
|
||||||
"daily_loss_cap_cents": 500,
|
"daily_loss_cap_cents": 500,
|
||||||
"min_conf": 0.5,
|
"min_conf": 0.55,
|
||||||
"ollama_url": "http://10.30.20.186:11434",
|
"ollama_url": "http://10.30.20.186:11434",
|
||||||
"ollama_model": "ornith:latest",
|
"ollama_model": "ornith:latest",
|
||||||
"swing_threshold_pct": 0.005,
|
"swing_threshold_pct": 0.005,
|
||||||
@@ -19,15 +19,18 @@
|
|||||||
"post_price_cents": 50,
|
"post_price_cents": 50,
|
||||||
"proxy": "http://10.30.20.154:3128",
|
"proxy": "http://10.30.20.154:3128",
|
||||||
"max_spend_cents": 100,
|
"max_spend_cents": 100,
|
||||||
"decision_interval_sec": 300,
|
"decision_interval_sec": 600,
|
||||||
"start_delay_sec": 60,
|
"start_delay_sec": 225,
|
||||||
"fast_llm_url": "http://localhost:11434",
|
"fast_llm_url": "http://localhost:11434",
|
||||||
"fast_llm_model": "qwen3.5:4b",
|
"fast_llm_model": "qwen3.5:4b-mlx",
|
||||||
"deep_llm_url": "http://10.30.20.186:11434",
|
"deep_llm_url": "http://10.30.20.186:11434",
|
||||||
"deep_llm_model": "ornith:latest",
|
"deep_llm_model": "ornith:latest",
|
||||||
"color": "#00AAE4",
|
"color": "#00AAE4",
|
||||||
"diverify_url": "http://10.30.20.89:11434",
|
"diverify_url": "http://10.30.20.89:11434",
|
||||||
"diverify_model": "llama3.2:latest",
|
"diverify_model": "llama3.2:latest",
|
||||||
"embed_url": "http://10.30.20.186:11434",
|
"embed_url": "http://10.30.20.186:11434",
|
||||||
"embed_model": "nomic-embed-text-v2-moe:latest"
|
"embed_model": "nomic-embed-text-v2-moe:latest",
|
||||||
|
"keep_alive": "15m",
|
||||||
|
"fast_gate_conf": 0.65,
|
||||||
|
"scalp_interval_sec": 60
|
||||||
}
|
}
|
||||||
38
zec.json
Normal file
38
zec.json
Normal 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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user