72 Commits

Author SHA1 Message Date
direkturcrypto e4e8d1f62c feat: configurable price polling interval via MAKER_MM_POLL_SEC
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 13:34:55 +07:00
direkturcrypto f376b439a5 fix: patch errorHandling to prevent circular JSON stack overflow
JSON.stringify(err.response.config) in clob-client's errorHandling
includes the httpsAgent (HttpsProxyAgent) object which has circular/deep
refs, causing "Maximum call stack size exceeded" on every API error.

- Fixed live node_modules file to log only status + data (no config)
- Updated patch script to apply this fix idempotently after npm install,
  independent of proxy patch (two separate checks/markers)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 13:32:23 +07:00
direkturcrypto 49de5c60e9 fix: auto-redeem after ghost recovery when holding expensive side
When ghost fill recovery decides to hold the expensive side (not sell),
set pos.holdingSide so the existing waitAndRedeem flow picks it up after
executeMakerRebateStrategy — same path as CANCEL_CHEAP_ON_EXP_FILL.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 13:22:08 +07:00
direkturcrypto 3d2a6e7c4a fix: skip market-sell for expensive side in ghost fill recovery
When expensive side (e.g. YES at 83c) is filled but cheap side (NO at 15c)
is ghost, do NOT market-sell the YES tokens — the cost basis is too high
and a market dump guarantees a loss. Only CL (market-sell) the cheap side
remainder, where the cost basis is low enough to absorb the slippage.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 13:15:25 +07:00
direkturcrypto 9a5073dd20 fix: avoid JSON.stringify on circular response object in placeLimitBuy
When the CLOB client returns a failure response that contains a circular
reference (e.g. axios/fetch internals), JSON.stringify throws
"Maximum call stack size exceeded" which is caught and swallows the real
error. Log only safe primitive fields (errorMsg, error, status) instead.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 00:04:22 +07:00
direkturcrypto 78c66a9b1e feat: cancel cheap side on expensive fill + auto-redeem after resolution
- MAKER_MM_CANCEL_CHEAP_ON_EXP_FILL=true: when the expensive side fills
  first, immediately cancel the cheap side order and hold the token.
  After market ends, polls on-chain payoutDenominator until resolved,
  then calls redeemPositions to recover USDC.
- Default false — existing symmetric maker behavior unchanged.
- Redeem polls every 15s for up to 10 minutes after market close.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 14:19:11 +07:00
direkturcrypto ececad8574 fix: use 2-tick ask buffer to prevent bid crossing ask (taker race)
Between fetching the best ask and placing the GTC order, the ask can
move down 1 tick — turning our bid into a marketable (taker) order,
which hits Polymarket's $1 minimum notional error. Cap bid to
ask - 2*tickSize instead of ask - 1*tickSize to absorb the timing race.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 10:55:19 +07:00
direkturcrypto f2f2d48ada Revert "fix: auto-scale shares to meet $1 minimum notional for cheap-side orders"
This reverts commit fdb613e966.
2026-04-01 10:54:59 +07:00
direkturcrypto fdb613e966 fix: auto-scale shares to meet $1 minimum notional for cheap-side orders
When cheap side price is very low (e.g. NO at 5c), MAKER_MM_TRADE_SIZE=5
produces $0.25 notional which fails Polymarket's $1 minimum for marketable
BUY orders. Auto-scale both sides to ceil($1/price) to meet the minimum,
keeping YES+NO pair counts equal for clean merge.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 10:52:17 +07:00
direkturcrypto 03d3f25d09 fix: auto-detect cheap side for price range filter
MIN/MAX_PRICE was always checked against YES bid, blocking entry when
NO is the cheap side (e.g. YES=96c, NO=4c with range 0.03-0.05).

Now the range filter applies to whichever side has the lower bestBid.
The expensive side is derived from maxCombined - cheapBid as before.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 10:45:42 +07:00
direkturcrypto eec97eb1e5 fix: apply price range filter to YES only, not NO
MIN/MAX_PRICE was filtering both YES and NO bids, blocking valid
skewed-market entries like YES=11c + NO=87c (NO fails MAX_PRICE=0.30).

Now only YES is range-checked. NO only needs basic sanity bounds (0 < noBid < 1).
This enables targeting cheap YES markets while NO bid can be any value.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 14:27:55 +07:00
direkturcrypto 3a4dbf2b02 feat: add MAKER_MM_REENTRY_ENABLED config + fix float combined check
- MAKER_MM_REENTRY_ENABLED=false disables re-entry (one cycle per market)
- Fix floating point bug: 0.1+0.5=0.6000000000000001 caused "combined > max"
  loop when MAKER_MM_MAX_COMBINED=0.60 — now rounds combined to 4dp before compare

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 14:19:25 +07:00
direkturcrypto 0ee19ce48a fix: remove repricing, add ghost fill recovery with onchain verification
Repricing was the root cause of double orders and ghost fills:
- cancel/replace race caused duplicate active orders in CLOB
- cancel API returning OK did not guarantee order was actually gone
- ghost fills (CLOB matched, invalid txhash) went undetected

Changes:
- Remove checkAndReprice entirely — orders placed once and held
- Detect ghost fills via getOpenOrders (reliable) instead of checkOrderStatus
- recoverFromGhostFill: merge paired shares + market-sell remainder
- marketSellToken: verify onchain balance after each attempt, retry up to 3x
  (sells can also be ghost-filled — shares still in wallet despite CLOB saying done)
- Recovery triggers at 30s after detection, not at cut-loss time (prices still fair)
- Update README to reflect no-reprice design

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 10:51:23 +07:00
direkturcrypto 4ea59c8f06 fix: WS fill signal no longer sets pos.filled — prevents partial fill false-positive
WS handler was marking a side as 'filled' for any fill event regardless of
share size. A 2-share partial fill (target: 5) would set pos.no.filled=true,
causing the monitoring loop to enter limbo: both sides 'filled' but onchain
balance below the 0.5x merge threshold, so no merge ever triggered.

WS events now only serve as a wake-up signal for waitForFillOrTimeout.
Onchain balance (getTokenBalance) is the sole source of truth for filled flags.
2026-03-30 23:57:27 +07:00
direkturcrypto 187765f4ac Merge feat/maker-rebate-mm into main
Adds high-frequency maker rebate market-making strategy:
- Simultaneous YES+NO maker limit orders on 15m binary markets
- CTF merge for guaranteed spread capture (market-neutral)
- WebSocket fill detection, combined cap enforcement, one-sided stop
2026-03-30 21:01:19 +07:00
direkturcrypto ef249ad971 feat: maker rebate MM — HFT market-making with spread capture on 15m markets
- Place simultaneous maker limit BUY on YES+NO sides (combined ≈ $0.98)
- Merge filled pairs via CTF contract → capture spread as profit
- WebSocket RTDS real-time fill detection + onchain balance as source of truth
- No aggressive repricing when one side is filled (prevents double exposure)
- Stop re-entry after one-sided stuck cycle (prevents directional accumulation)
- Combined cap always enforced — profitable merge guaranteed
- Auto-queue next market before current closes (zero idle time)
- Remove loss-compensating martingale logic from reprice flow
- Add .env.example with full documentation for all strategies
- Update README with grant-ready project description
2026-03-30 21:00:43 +07:00
direkturcrypto 455f265ed0 feat: WebSocket realtime fill detection + parallel API calls for MM
Replace 10s polling loop with RTDS WebSocket event-driven fill detection
(~100ms latency). Parallelize YES/NO side checks and cancel operations.
Polling kept as 30s safety net fallback.

- Add mmWsFillWatcher.js: subscribes to RTDS activity feed, filters by
  own proxy wallet, emits 'fill' events for watched tokens
- Refactor monitorAndManage(): event-driven loop with parallel checks
- Extract checkSideFill() for parallel-safe per-side fill detection
- Wire up WS watcher lifecycle in mm.js and mm-bot.js

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-28 22:21:50 +07:00
direkturcrypto d48749db40 fix: handle partial fills and defensive/adaptive CL race condition
Bug 1 (partial fill stuck): When an order is partially filled (e.g. 4/20
shares), the bot would get stuck — isOrderFilled returns false, but the
order is dead on the book. Now detect partial fills via getPartialFillInfo,
cancel the old order, and either re-place a limit for the remaining shares
or market-sell if below CLOB minimum.

Bug 2 (defensive overrides fill): When defensive timeout fires, it cancels
both orders — but one side may have actually filled between the last poll
and the cancel. Now re-check on-chain balances after cancellation. If one
side is filled, route to adaptive CL instead of defensive pivot.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-28 22:00:28 +07:00
direkturcrypto e9e5d66ac9 fix: patch CLOB client JSON.stringify to handle circular proxy agent
The CLOB client errorHandling function does JSON.stringify on
err.response.config which includes httpsAgent (HttpsProxyAgent with
circular references). Inject a safe serializer that strips agent
properties before serializing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-28 12:16:46 +07:00
direkturcrypto 0886938694 fix: use axios interceptor instead of inline config injection
The IIFE approach that injected httpsAgent into the request config
caused "Converting circular structure to JSON" when the CLOB client
serialized the config object (HttpsProxyAgent has circular refs).

New approach: register an axios request interceptor after the axios
import. This is simpler (no need to find request config patterns),
works with any code format, and avoids serialization issues since
axios handles httpsAgent internally after interceptors run.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-27 14:36:21 +07:00
direkturcrypto ae523c4f27 fix: breakevenFloor is not defined in adaptive CL
Replace undefined `breakevenFloor` variable with `minAdaptivePrice`
which is the actual breakeven floor calculated from
mmAdaptiveMinCombined - filledLegPrice.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-27 14:08:03 +07:00
direkturcrypto f0295818e2 fix: handle inline axios call pattern in patch script
The VPS fresh install uses `(0, axios_1.default)({ method, url: endpoint, ... })`
instead of a separate `config` variable. Add Format B regex to match this
inline pattern and wrap it in an IIFE that injects the proxy agent.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-27 13:58:27 +07:00
direkturcrypto 85f19d0bd6 fix: use regex matching in patch script + add geoblock IP check
- Rewrite patch-clob-client.cjs to use regex instead of exact string
  matching, so it works regardless of code formatting differences
  between npm versions (fixes "Could not find request config line" on
  fresh VPS install)
- Add multiple fallback patterns (config regex → fallback regex →
  axios call injection) with debug output if all fail
- Add Polymarket geoblock API check (polymarket.com/api/geoblock)
  for both VPS direct IP and proxy IP at startup

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-27 13:55:35 +07:00
direkturcrypto b4d6d648bd feat: auto-patch clob-client proxy support via postinstall script
The proxy patch in node_modules is lost on every fresh npm install.
This postinstall script automatically patches @polymarket/clob-client
http-helpers to inject HttpsProxyAgent for all polymarket.com requests.

Runs automatically after npm install — no manual patching needed on VPS deploy.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-27 13:49:52 +07:00
direkturcrypto d82c5fd14e fix: add axios interceptor for proxy + dual proxy test (fetch + axios)
The CLOB client uses axios internally. Previous setup only set
axios.defaults which doesn't work if the CLOB client or axios
creates its own instance.

Fix: add axios request interceptor that forces proxy agent on every
request to polymarket.com domains.

Also add separate axios-based proxy test alongside fetch test to catch
cases where fetch works but axios doesn't route through proxy.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-27 13:47:22 +07:00
direkturcrypto efe0a077e5 feat: add IP check and geoblock detection at startup
Shows both VPS direct IP and proxy IP so user can verify which IP
Polymarket sees. If CLOB returns 403 geoblock, shows clear error
with link to allowed regions instead of proceeding.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-27 13:42:40 +07:00
direkturcrypto 3078ab9790 fix: tiered floor must start from MM_ADAPTIVE_MIN_COMBINED, not hardcoded $1.00
Was using breakevenFloor (1.00 - filledPrice) which ignored the user's
MM_ADAPTIVE_MIN_COMBINED setting. This caused sells below the configured
minimum (e.g. selling at 28c when min combined was 1.01).

Now: filled @ 70c, MM_ADAPTIVE_MIN_COMBINED=1.01 → floor starts at 31c
  Phase 1 (>180s): 31c
  Phase 2 (90-180s): 21c
  Phase 3 (30-90s): 11c
  Phase 4 (<30s): market sell

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-27 13:36:20 +07:00
direkturcrypto 6e3d484728 merge: defensive pivot, tiered CL, on-chain fill detection, proxy patch for MM 2026-03-27 13:27:50 +07:00
direkturcrypto 565f98571a fix: add proxy-patch import to mm-bot and mm entry points
Both sniper and dumper had proxy-patch.cjs imported but market maker
entry points (mm-bot.js, mm.js) were missing it, causing direct
connections without proxy.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-27 13:26:23 +07:00
direkturcrypto b05fed9207 fix: add on-chain balance fallback for fill detection
CLOB API (client.getOrder) can be stale — order filled on-chain but API
still shows unfilled. This caused the bot to miss fills and incorrectly
enter defensive mode or keep monitoring dead orders.

Now isOrderFilled does:
1. Check CLOB API (existing) with retry
2. Fallback: check on-chain token balance via CTF contract
   If balance < 5% of original shares → treat as filled

Fixes issue where Polymarket UI shows filled but bot doesn't detect it.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-27 13:24:33 +07:00
direkturcrypto ba250572b9 fix: cancel orders immediately when defensive timeout reached
Orders must be cancelled BEFORE the monitoring loop can detect fills,
otherwise the limit sell at 70c can still get filled at minute 3
and trigger adaptive CL instead of defensive pivot.

Now: timeout reached → cancel both orders → enter defensivePivot

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-27 05:25:11 +07:00
direkturcrypto 1da662313d fix: use mid price for initial adaptive CL sell instead of fixed breakeven floor
Check mid price first, place limit at market price (capped at mmSellPrice).
Only falls back to breakeven floor if mid price is below it.

Example: YES filled @ 70c, NO mid = 38c → sell @ 38c (not 30c breakeven)
This gives profit potential instead of always targeting breakeven.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-27 05:15:01 +07:00
direkturcrypto df09403f18 fix: change defensive pivot trigger from 30s to 45s before close
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-27 05:09:02 +07:00
direkturcrypto cc90176861 feat: implement tiered floor + standing order for adaptive CL (5m markets)
Adaptive CL now uses progressive floor lowering for 5-minute markets:

  Phase 1 (>180s left): breakeven floor (e.g. 40c for 60c fill)
  Phase 2 (90-180s):    floor - 10c (accept small loss to escape)
  Phase 3 (30-90s):     floor - 20c + emergency cut if price < 10c
  Phase 4 (<30s):       force market sell

Key improvements:
- Standing order: immediately place limit at breakeven floor so brief
  bounces get caught (don't wait for polling to detect price >= floor)
- Emergency cut: market sell immediately if price < 10c in phase 3
  (market is decisive, bounce unlikely)
- Non-5m markets still use the existing fixed floor (mmAdaptiveMinCombined)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-27 05:01:38 +07:00
direkturcrypto 8a5962d0a5 fix: measure defensive timeout from market open time, not bot entry time
Market start = endTime - 5 minutes, so defensive triggers at exactly
120s after the market opens regardless of when the bot entered.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-27 04:54:42 +07:00
direkturcrypto e19278c665 feat: add defensive pivot mode for 5m markets when neither side fills
When neither limit sell fills within MM_DEFENSIVE_TIMEOUT (default 120s),
the bot enters defensive mode:
- Cancel both limit sells
- Wait until 30s before market close
- If worst side price < MM_DEFENSIVE_WORST_THRESHOLD (default 10c):
  market sell worst side, hold best side for resolution (best ≈ 90c+)
- If worst side ≥ threshold: merge back to USDC (safe $0 P&L)

Only active for 5-minute markets (MM_DURATION=5m).

New config:
  MM_DEFENSIVE_ENABLED (default true)
  MM_DEFENSIVE_TIMEOUT (default 120s)
  MM_DEFENSIVE_WORST_THRESHOLD (default 0.10)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-27 04:43:56 +07:00
direkturcrypto a73cf22aa5 merge: mm-bot improvements - retry fill detection, parallel limit sells, priority safe queue 2026-03-27 04:18:55 +07:00
direkturcrypto 0da77f78fd fix: improve mm-bot reliability - retry fills, parallel sells, smart small remainder handling
- isOrderFilled: retry 2x with logging instead of silent error swallow
- Place YES/NO limit sells in parallel via Promise.all
- Market sell immediately when remaining shares < CLOB minimum (5 shares)
- Re-check on-chain balance before every limit/market sell in adaptive CL
- Separate redeemer Safe queue priority from strategy (split/merge) to reduce cross-market delays
- Cache USDC and exchange approvals in-memory to skip redundant on-chain reads

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-27 04:18:51 +07:00
direkturcrypto d89b8ff54d fix: correct win detection by mapping tokenId→outcome instead of array index
Problem: redeemer used array index to match balances to payoutFractions,
but Data API token order ≠ on-chain outcome index. This caused false wins
(shares on losing side counted as winning).

Fix:
- Store yesTokenId/noTokenId per conditionId in sniperExecutor
- Match tokens by ID (yes=outcome0, no=outcome1) instead of array position
- Only process positions tracked by sniper (skip MM/other positions)
- Inject token lookup via setSniperConditionLookup to avoid circular imports

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 13:59:48 +07:00
direkturcrypto a8f28c961b feat: add time-based multiplier sizing, pause-after-win, and outcome-based win detection
- Time-based bet sizing multiplier (SNIPER_MULTIPLIERS, UTC+8 windows)
- Pause N rounds per asset after win (SNIPER_PAUSE_ROUNDS_AFTER_WIN)
- Win detection via payoutNumerators outcome check instead of redeem value threshold

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 12:53:48 +07:00
direkturcrypto 9e78fb1bfe fix: update sniper logging for 3-tier config
- Fix sniperDetector.js to use sniperTierPrices instead of old config
- Fix sniper-tui.js display for 3-tier pricing
- Remove references to deleted sniperPrice/sniperShares vars

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 14:59:14 +07:00
direkturcrypto c524ed92ef feat: implement 3-tier sniper strategy with weighted sizing
- Replace single price/shares with 3-tier system (3c/2c/1c)
- Tier allocation: 20%/30%/50% (high→low price)
- Min 5 shares per tier enforced
- Update .env.example with new config vars
- Update README.md with strategy documentation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 14:52:37 +07:00
direkturcrypto 00235dc850 fix: inject proxy agent into @polymarket/clob-client
Add proxy support directly to clob-client http-helpers:
- Import https-proxy-agent
- Add getProxyAgent() helper
- Inject agent into axios config for polymarket.com endpoints

This ensures all CLOB API calls route through the proxy.
2026-03-01 22:36:23 +07:00
direkturcrypto f5c777ec0c simplify: use HttpsProxyAgent directly for proxy routing
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 22:33:45 +07:00
direkturcrypto 048d4433c2 fix: implement CONNECT tunnel for proxy routing
Full HTTPS CONNECT tunnel implementation that:
- Routes only Polymarket domains through proxy
- Uses HTTP CONNECT method for HTTPS tunneling
- Leaves all other traffic (RPC, etc.) untouched

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 22:27:18 +07:00
direkturcrypto b770f831db fix: simplify proxy patch to use HttpsProxyAgent globally
Set https.globalAgent and override https.request to use proxy
for all Polymarket domains. Ensures @polymarket/clob-client
axios calls route through proxy.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 22:22:20 +07:00
direkturcrypto 368a3f8da5 fix: ensure proxy is used by @polymarket/clob-client
Add proxy-patch.cjs that patches https.request BEFORE axios is loaded.
This forces all Polymarket API calls through the proxy, including
internal axios instances used by @polymarket/clob-client.

Import patch as very first thing in sniper.js and sniper-tui.js.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 22:18:06 +07:00
direkturcrypto b2f8447d0b perf: cache sniper losses to speed up redeem checks
- Add _skippedLosses Set to cache confirmed losses (skip future checks)
- Add counters for skipped categories (unresolved, losses, no balance)
- Losses only checked once, subsequent cycles skip immediately
- Add summary logging for better visibility

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 22:01:56 +07:00
direkturcrypto c5b169b47e feat: sniper redeem - winners only, high gas, no startup check
- Remove startup redeem check (only runs on interval)
- Only redeem WINNING positions (skip losses for manual cleanup)
- Increase gas fees: 1.5x estimate, min 50 Gwei priority fee
- Add tx hash logging for successful redeems

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 21:42:26 +07:00
direkturcrypto 374e943cc2 feat: redeem logging improvements
- Add transaction hash to redeem success log
- Continue to next position when one fails (don't get stuck)
- Add clearer logging for retry behavior

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 21:30:19 +07:00