--- fm_mc5_v30.py 2026-10-07 00:11:20 +++ fm_mc5_v32.py 2026-10-07 02:46:41 @@ -259,12 +259,21 @@ val = sell_value(st, p["tok0"], p["fee"], toks) / 1e18 * (1 - c["gmgn_fee"]) - gas m = val / row[2] if row[2] else 0; peak = max(row[3] or 1, m); upd(pool, peak=peak) if c["exit_mode"] in ("stop_time", "trail") and ((row[4] or 0) > 200 or m <= c["stop_x"] * 1.15): - try: # confirm through tick crossings (always when size > $200, else when within 15% of the stop); the quick value only screens - m = (sell_walk(pool, p["tok0"], p["fee"], toks, st) / 1e18 * (1 - c["gmgn_fee"]) - gas) / row[2] - except Exception as e: log(pool, "walk_err", repr(e)[:120]) - if m <= c["stop_x"]: EX.submit(close, pool, "stop_%gx" % c["stop_x"], m); return + if m > c["stop_x"]: # FW v32: confirm through tick crossings only when the quick value is still above the stop + with WLK: + go = pool not in WALKING and time.time() >= WALK_BO.get(pool, 0) + if go: WALKING.add(pool) + if go: + try: m = (sell_walk(pool, p["tok0"], p["fee"], toks, st) / 1e18 * (1 - c["gmgn_fee"]) - gas) / row[2] + except Exception as e: + log(pool, "walk_err", repr(e)[:120]) + if "429" in repr(e): WALK_BO[pool] = time.time() + 5 + finally: + with WLK: WALKING.discard(pool) + if m <= c["stop_x"]: EX.submit(close, pool, "stop_%gx" % c["stop_x"], m, time.time()); return if c["exit_mode"] == "trail" and c["trail_x"] and m <= peak * c["trail_x"] and peak > 1: EX.submit(close, pool, "trail", m) CLOSING = set() +WALKING = set(); WALK_BO = {}; WLK = threading.Lock() # FW v32: one confirm walk per pool, back-off after 429 def wait_tok(tok, b0, secs, up): """Poll the wallet's token balance up to secs. up=True: wait for tokens to arrive (returns gained); else wait until they are gone.""" t_end = time.time() + secs @@ -285,7 +294,8 @@ if r: qm = r[1] / 1e18 * g except Exception: pass return ql, qm -def close(pool, why, trig=None): +def close(pool, why, trig=None, t_trig=None): + t_trig = t_trig or time.time() with lk: if pool in CLOSING: return CLOSING.add(pool) @@ -296,9 +306,12 @@ mode, toks, eth_in, status, wei_in, tries = row[0], int(row[1]), row[2], row[3], row[4], row[5] or 0 gas = c["gas_usd"] / ethusd(); pulled = why == "pull" or p["pulled"] st = state(pool) - try: ql, qm = quotes(pool, p, toks, wei_in, c, st) - except Exception as e: - ql, qm = sell_value(st, p["tok0"], p["fee"], toks) / 1e18 * (1 - c["gmgn_fee"]), None; log(pool, "walk_err", repr(e)[:120]) + fast = mode == "live" and (pulled or why.startswith("stop")) # FW v32: sell first, quote after (log only) + def _q(): + try: return quotes(pool, p, toks, wei_in, c, st) + except Exception as e: + log(pool, "walk_err", repr(e)[:120]); return sell_value(st, p["tok0"], p["fee"], toks) / 1e18 * (1 - c["gmgn_fee"]), None + if not fast: ql, qm = _q() trigv = trig * eth_in if trig is not None else None note = "" if mode == "live": @@ -307,7 +320,9 @@ out = 0.0; note = "no tokens left in the wallet at sell time" else: slip = c["emerg_slip"] if (pulled or why.startswith("stop") or status == "open_unsold" or tries) else c["sell_slip"] - r = swap(p["tok"], ETHA, pct=100, slip=slip); sold = wait_tok(p["tok"], b0, c["fill_wait_s"], False) + r = swap(p["tok"], ETHA, pct=100, slip=slip) + log(pool, "SELL_SENT", "%s %.1f s after trigger, slip %d%%, tx %s" % (why, time.time() - t_trig, slip, txh(r) or "-")) + sold = wait_tok(p["tok"], b0, c["fill_wait_s"], False) if not sold and slip < c["emerg_slip"]: log(pool, "sell_retry", "slip %d%% then %d%% (%s)" % (slip, c["emerg_slip"], str(r)[:120])) r = swap(p["tok"], ETHA, pct=100, slip=c["emerg_slip"]); sold = wait_tok(p["tok"], b0, c["fill_wait_s"], False) @@ -318,6 +333,7 @@ return out = max(ebal() - e0, 0.0); note = "live sell tx %s" % txh(r) if pulled: note += " (pulled)" + if fast: ql, qm = _q() # quote after the sell, for the log only (state snapshot taken before the sell) else: out = 0.0 if pulled else max(ql - gas, 0.0) side = None