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>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
ba250572b9
commit
b05fed9207
@@ -110,22 +110,37 @@ async function marketSell(tokenId, shares, tickSize, negRisk) {
|
||||
|
||||
// ── Order status check ────────────────────────────────────────────────────────
|
||||
|
||||
async function isOrderFilled(orderId, shares) {
|
||||
async function isOrderFilled(orderId, shares, tokenId = null) {
|
||||
if (!orderId || orderId.startsWith('sim-')) return false;
|
||||
const MAX_FILL_RETRIES = 2;
|
||||
for (let attempt = 1; attempt <= MAX_FILL_RETRIES; attempt++) {
|
||||
try {
|
||||
const client = getClient();
|
||||
const order = await client.getOrder(orderId);
|
||||
if (!order) return false;
|
||||
if (!order) break; // order gone — fall through to balance check
|
||||
if (order.status === 'MATCHED') return true;
|
||||
const matched = parseFloat(order.size_matched || '0');
|
||||
return matched >= shares * 0.99;
|
||||
if (matched >= shares * 0.99) return true;
|
||||
// CLOB says not filled — trust it if we have no tokenId for balance check
|
||||
if (!tokenId) return false;
|
||||
// Otherwise fall through to balance check below
|
||||
break;
|
||||
} catch (err) {
|
||||
logger.warn(`MM: isOrderFilled error (attempt ${attempt}/${MAX_FILL_RETRIES}): ${err.message}`);
|
||||
logger.warn(`MM: isOrderFilled CLOB error (attempt ${attempt}/${MAX_FILL_RETRIES}): ${err.message}`);
|
||||
if (attempt < MAX_FILL_RETRIES) await sleep(2000);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: check on-chain token balance
|
||||
// If we placed a SELL and our balance is now ~0, the order was filled
|
||||
if (tokenId) {
|
||||
const balance = await getTokenBalance(tokenId);
|
||||
if (balance !== null && balance < shares * 0.05) {
|
||||
logger.warn(`MM: CLOB API missed fill — on-chain balance ${balance.toFixed(3)} ≈ 0 (expected ${shares}) → treating as filled`);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -187,7 +202,7 @@ async function monitorAndManage(pos) {
|
||||
const hitPrice = await simPriceHitTarget(pos.yes.tokenId);
|
||||
if (hitPrice) { filled = true; pos.yes.fillPrice = hitPrice; }
|
||||
} else {
|
||||
filled = await isOrderFilled(pos.yes.orderId, pos.yes.shares);
|
||||
filled = await isOrderFilled(pos.yes.orderId, pos.yes.shares, pos.yes.tokenId);
|
||||
if (filled) pos.yes.fillPrice = config.mmSellPrice;
|
||||
}
|
||||
if (filled) {
|
||||
@@ -204,7 +219,7 @@ async function monitorAndManage(pos) {
|
||||
const hitPrice = await simPriceHitTarget(pos.no.tokenId);
|
||||
if (hitPrice) { filled = true; pos.no.fillPrice = hitPrice; }
|
||||
} else {
|
||||
filled = await isOrderFilled(pos.no.orderId, pos.no.shares);
|
||||
filled = await isOrderFilled(pos.no.orderId, pos.no.shares, pos.no.tokenId);
|
||||
if (filled) pos.no.fillPrice = config.mmSellPrice;
|
||||
}
|
||||
if (filled) {
|
||||
@@ -579,7 +594,7 @@ async function adaptiveLegCL(pos, unfilledKey) {
|
||||
const hitPrice = await simPriceHitTarget(s.tokenId);
|
||||
if (hitPrice) { filled = true; s.fillPrice = hitPrice; }
|
||||
} else {
|
||||
filled = await isOrderFilled(activeOrderId, sellShares);
|
||||
filled = await isOrderFilled(activeOrderId, sellShares, s.tokenId);
|
||||
if (filled) s.fillPrice = activeLimitPrice;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user