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.

This commit is contained in:
drjones
2026-08-03 03:48:53 -07:00
parent fc96c6f070
commit d631fe529b
8 changed files with 82 additions and 8 deletions

33
bot.py
View File

@@ -535,7 +535,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")
@@ -726,16 +726,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]
@@ -748,6 +759,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"
@@ -789,7 +806,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(15) # scalp loop: watch constantly
if __name__ == "__main__":
run()