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:
33
bot.py
33
bot.py
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user