# Polymarket: BTC News Trader

Monitors a curated list of top crypto Twitter/X accounts for breaking BTC news, uses an AI model to classify each tweet as bullish/bearish with a confidence score, then auto-bets on the next Polymarket "Bitcoin Up or Down" market when the AI is confident enough. Includes balance check and per-market position dedup so a single news event propagating across multiple accounts only triggers one bet.

### Quick Setup

1\. Review the default 10 curated crypto Twitter accounts. Add or remove accounts if you want a different signal source.

2\. Adjust the AI confidence threshold (default 70). Higher = fewer trades, higher quality.

3\. Pick the Polymarket timeframe — 15-minute, hourly, or daily markets. Hourly is the default and best matches the news-momentum window.

4\. Set your bet amount (default 1 pUSD). Make sure your Polymarket wallet holds at least this much pUSD.

5\. Pick a Polymarket wallet connector and a notification channel.

6\. Test and publish.

### How it works

- Every 5 minutes the workflow searches Twitter for new tweets from the curated accounts (filtered by optional keywords).
- The configured LLM analyzes each tweet for BTC sentiment (bullish / bearish / neutral) and a 0-100 confidence score.
- A dual gate (signal != neutral AND confidence >= threshold) prevents acting on ambiguous news or AI hallucinations.
- Polymarket is searched for the next "Bitcoin Up or Down" market closing within the configured window. The highest-volume match is picked.
- Before betting, the workflow checks that the wallet has enough pUSD for the configured bet and that no position already exists on the target market (dedup against the same news propagating across accounts).
- A market order is placed on the matching outcome (bullish → Up, bearish → Down).
- Every evaluated tweet generates a notification (executed / skipped) for audit.

## Workflow Steps

- **ai-llm-chat** — Classifies the tweet sentiment as bullish, bearish, or neutral for BTC price action in the next 1-2 hours, with a 0-100 confidence score.
- **code-transform** — Compares wallet pUSD balance against the configured bet amount. Returns sufficient=true only when balance >= bet AND bet > 0. Conversion via Number() avoids lexicographic string comparison in the downstream if-gate.
- **if** — Routes to position check only when the wallet has enough pUSD for the configured bet. Skips otherwise so the user is notified to top up instead of hitting an INSUFFICIENT_BALANCE error from Polymarket.
- **if** — Branches on the actual fill outcome. FAK orders return status=''no_fill'' when the best ask moved between book read and matcher arrival; in that case the workflow must release the dedup row so the same market can be retried.
- **if** — Verifies a suitable BTC Up/Down market was found before proceeding to wallet checks and bet placement.
- **polymarket-get-positions** — Looks up the wallet''s existing positions on the target market. The address is sourced from the upstream get-balance result (not from the connector directly) per the convention that read actions take addresses from inputs.
- **if** — Gates downstream actions: bet only when AI signal is bullish/bearish AND confidence is at or above the configured threshold (default 70). AI rejections (neutral or low confidence) terminate silently — most tweets are not actionable, so notifying every one would be noise.
- **db-query** — Atomic per-market claim with 5-minute time-window. Postgres INSERT...ON CONFLICT DO UPDATE WHERE bet_at < now()-5min lets only the first parallel run win (rowsAffected=1) and auto-recovers stuck rows from upstream failures (CLOB outage, place_bet error) after 5 minutes — concurrent runs within the window see rowsAffected=0 and are gated out below. Closes the race window between check_position and place_bet where two tweets in the same poll batch could otherwise place duplicate bets on the same market.
- **if** — Routes to bet placement only when (a) this run claimed the dedup row atomically AND (b) the wallet has no existing position on this market (catches manual bets placed outside the workflow). Else branch terminates silently — already-positioned skips would fire on every tweet within an active position window and create Slack noise.
- **code-transform** — Derives absolute timestamps + expected market duration from the configured timeframe. endDateMin is now+1min (buffer for order placement); expectedDurationMin = 15 for 30-min window, 60 for 90-min window.
- **code-transform** — Filters to BTC Up/Down markets whose duration (parsed from the question title) matches expectedDurationMin: 15 for ''15-minute'' or 60 for ''Hourly''. Excludes 5-min, 4-hour, daily markets so the workflow does not accidentally bet a fast-decay or slow-decay market against a short news-momentum thesis. Markets with unparseable title formats fall through to null and are excluded (safer than a wrong bet). Sorts remaining by volume desc, picks top.
- **code-transform** — Formats a rich execution notification with AI reasoning, market link, order details, and tweet link.
- **code-transform** — Formats a skip notification when the wallet does not have enough pUSD for the configured bet — prompts the user to top up.
- **code-transform** — Formats the no-fill skip notification with the precise reason from place_bet.result.message.
- **code-transform** — Formats a skip notification when no suitable Polymarket market is available in the configured time window.
- **notification** — Sends the trade-executed notification to the configured channel.
- **notification** — Sends the insufficient-balance notification to the configured channel.
- **notification** — Sends the no-fill notification to the configured channel.
- **notification** — Sends the no-market-found notification to the configured channel.
- **code-transform** — Maps the AI signal to the corresponding outcome token by name: bullish -> Up/Yes, bearish -> Down/No. Throws if no outcome matches (signals a malformed or unexpected market).
- **polymarket-place-bet** — Places a market BUY order (Fill-And-Kill) on the AI-picked outcome at the current best ask, sized to the configured fixed amount in pUSD smallest units. Passes conditionId alongside tokenId so the action skips the CLOB /markets-by-token reverse-lookup (an observed transient failure point); see top-of-file NOTE for context.
- **db-query** — Compensates the claim_bet INSERT when place_bet did not fill. Without this, a no_fill leaves a stuck row that blocks every future bullish tweet on this market.
- **polymarket-get-balance** — Retrieves the connected wallet''s pUSD balance and (importantly) exposes walletAddress in the result so downstream actions can look up positions via inputs.address.
- **x-new-tweet** — Polls Twitter every {payload.intervalSeconds} seconds for new tweets from the curated crypto accounts, optionally filtered by keywords. Uses Twitter advanced search syntax (from:(user1 OR user2 OR ...)) for multi-account monitoring.
- **polymarket-search-markets** — Browses Polymarket markets that close inside the configured timeframe using server-side endDate filtering (avoids stale markets that Polymarket reports as active=true). fetchAllPages=true exhaustively pages through results (500 per page, capped at 10k) so high-volume BTC markets ending late in the window are not cut off by limit.

**Tags:** Trading, Polymarket, Twitter, Prediction Market, BTC, Sentiment
