Skill: diagnostikon/polymarket-equity-markets-trader (version 0.0.3)

--- name: polymarket-equity-markets-trader description: Trades Polymarket prediction markets on stock index milestones, major IPOs, earnings surprises, analyst upgrades, and company-specific financial events. Exploits two structural edges — earnings base rate mispricing (S&P companies beat consensus ~73–75% of the time; retail prices it as a coin flip) and macro calendar timing lag (futures market reprices CPI/FOMC/NFP 20–60 min before Polymarket does). metadata: author: snetripp version: "1.0" displayName: Equity Markets & IPO Trader difficulty: advanced --- # Equity Markets & IPO Trader > **This is a template.** > The default signal is keyword-based market discovery combined with conviction-based sizing and `equity_bias()` — two structural edges that work without any external API. > The skill handles all the plumbing (market discovery, trade execution, safeguards). Your agent provides the alpha. ## Strategy Overview Equity prediction markets are unique: they run alongside the deepest, most quantified financial markets in the world. The edge is not in predicting markets better than Wall Street — it is in the **lag** between professional data (earnings base rates, options implied volatility, futures moves) and what Polymarket retail knows. Two structural edges compound cleanly: 1. **Earnings base rate correction** — The most underknown stat in equity markets: S&P 500 companies beat analyst consensus EPS estimates approximately 73–75% of the time, sustained across multiple economic cycles. Large-cap tech (NVDA, AAPL, MSFT, META, GOOG, AMZN) beats ~80–85%. Retail prices earnings beat markets as roughly 50–50. That 20–30 percentage point gap is structural edge that replenishes every earnings season. 2. **Macro calendar timing lag** — S&P 500 futures trade 24/7. When CPI prints, payrolls drop, or FOMC speaks, futures reprice instantly. Polymarket index threshold markets take 20–60 minutes to catch up, especially during pre-market hours. Quarter-start months (Jan, Apr, Jul, Oct) have maximum macro catalyst density — GDP advance estimate, full earnings flood, FOMC decision, and NFP all land within weeks of each other. ## Signal Logic ### Default Signal: Conviction-Based Sizing with Equity Bias 1. Discover active equity and financial markets on Polymarket 2. Compute base conviction from distance to threshold (0% at boundary → 100% at p=0/p=1) 3. Apply `equity_bias()` — market type correction × earnings calendar timing 4. Size = `max(MIN_TRADE, conviction × bias × MAX_POSITION)` — capped at MAX_POSITION 5. Skip markets with spread > MAX_SPREAD or fewer than MIN_DAYS to resolution ### Equity Bias (built-in, no API required) **Factor 1 — Market Type / Earnings Base Rate** | Market type | Multiplier | The structural reality | |---|---|---| | Large-cap tech earnings (NVDA, AAPL, MSFT, META, GOOG, AMZN, TSLA) | **1.25x** | ~80–85% beat rate sustained 2020–2024; retail prices as 50–60%; Nvidia beat consensus 7 consecutive quarters | | General S&P 500 earnings beat | **1.15x** | ~73–75% historical beat rate across multiple cycles; retail treats as coin flip; every "will X beat earnings?" is a structural YES lean | | Index level milestone (S&P 500, Nasdaq, Dow reaching X) | **1.15x** | Futures market is a 24-hour reference; Polymarket lags macro catalysts 20–60 min; pre-market hours especially exploitable | | Dividend / buyback / stock split | **0.90x** | Some insider buying signal but corporate decision timing is genuinely uncertain | | IPO by date / IPO valuation milestone | **0.85x** | Window-driven; S-1 filing to pricing gap is highly variable; banker incentives dominant | | Market crash / correction / bear market by date | **0.75x** | Most reliably overpriced category; crashes are vivid but rare in any specific window; VIX-spike retail anchors to worst case when mean reversion is likelier | | Short squeeze / meme stock milestone | **0.65x** | Pure social coordination; requires specific short float data, borrow rate spikes, coordinated buying — none predictable from question text | **The Crash Overpricing Rule** — "Will the S&P fall 20% by [date]?" is almost always overpriced on Polymarket. Retail's fear-anchoring after every VIX spike causes these markets to trade at 15–30% when the actual base rate for a 20%+ drawdown in any specific 3-month window is far lower. The vivid availability of 2008, 2020, and 2022 makes retail systematically pessimistic on specific-window crash markets. Dampen every crash/bear market question to 0.75x regardless of current probability. **The Beat Rate Rule** — Analyst estimates are deliberately set to be beatable. Companies guide conservatively; analysts shade their models to match guidance; management sandbagging is a documented, multi-decade practice. The 73–75% aggregate beat rate is not a fluke — it is structural. Every "will X beat earnings?" market that prices below 50% is almost certainly mispriced unless the company has a specific documented miss history (e.g., retail, biotech pre-approval). **Factor 2 — Earnings Calendar Timing** | Condition | Multiplier | Why | |---|---|---| | Earnings-type question + peak season (Jan, Feb, Apr, May, Jul, Aug, Oct, Nov) | **1.20x** | Maximum signal density: fresh analyst estimates, current management guidance, live options implied volatility | | Earnings-type question + off-season (Mar, Jun, Sep, Dec) | **0.85x** | Sparse pre-announcements; stale estimates; options vol compressed; base rate edge noisier | | Index/macro question + quarter-start month (Jan, Apr, Jul, Oct) | **1.10x** | GDP advance estimate + full earnings flood + FOMC + NFP all land; maximum macro catalyst density | ### Combined Examples | Market | Type mult | Calendar mult | Final bias | |---|---|---|---| | "Will Nvidia beat Q3 earnings?" — October | 1.25x | 1.20x (peak season) | **1.40x cap** | | "Will S&P 500 reach 6,000 by year end?" — January | 1.15x | 1.10x (quarter-start) | **1.27x** | | "Will Apple beat Q1 earnings?" — March | 1.25x | 0.85x (off-season) | **1.06x** | | "Will there be a market crash by December?" | 0.75x | 1.0x | **0.75x** — always skeptical | | "GameStop short squeeze to $100?" | 0.65x | 1.0x | **0.65x** — near floor | | "Will Amazon IPO a subsidiary this year?" | 0.85x | 1.0x | **0.85x** | ### Keywords Monitored ``` S&P 500, Dow Jones, Nasdaq, stock market, bull market, bear market, market crash, correction, IPO, earnings, revenue miss, beat, Berkshire, Warren Buffett, Apple earnings, Tesla earnings, Nvidia earnings, stock split, buyback, dividend, short squeeze, all-time high, market cap trillion, index rebalance, EPS beat, earnings surprise, guidance raised, revenue beat, beat estimates, CPI, inflation, non-farm payroll, NFP, Fed rate, FOMC, recession, VIX, volatility, circuit breaker ``` ### Remix Signal Ideas - **Yahoo Finance options chain**: Free unofficial API gives implied volatility and options-implied earnings move — compare the options-implied probability band to Polymarket price for earnings beat/miss markets; the gap is consistently 15–25% for mid-cap companies - **CBOE VIX data feed**: VIX level and futures term structure — when VIX spikes, crash markets overprice; when VIX term structure inverts, macro catalyst timing signal for index threshold markets - **SEC EDGAR 8-K filings**: Real-time earnings releases hit EDGAR before press coverage — earnings results are public on EDGAR minutes before Polymarket reprices - **CME FedWatch**: Fed rate probability from federal funds futures — direct input for "will the Fed cut/hike?" markets; far more precise than Polymarket retail pricing ## Safety & Execution Mode **The skill defaults to paper trading (`venue="sim"`). Real trades only with `--live` flag.** | Scenario | Mode | Financial risk | |---|---|---| | `python trader.py` | Paper (sim) | None | | Cron / automaton | Paper (sim) | None | | `python trader.py --live` | Live (polymarket) | Real USDC | `autostart: false` and `cron: null` — nothing runs automatically until you configure it in Simmer UI. ## Required Credentials | Variable | Required | Notes | |---|---|---| | `SIMMER_API_KEY` | Yes | Trading authority. Treat as high-value credential. | ## Tunables (Risk Parameters) All declared as `tunables` in `clawhub.json` and adjustable from the Simmer UI. | Variable | Default | Purpose | |---|---|---| | `SIMMER_MAX_POSITION` | `35` | Max USDC per trade (reached at 100% conviction) | | `SIMMER_MIN_VOLUME` | `15000` | Min market volume filter (USD) — equity markets need liquidity | | `SIMMER_MAX_SPREAD` | `0.07` | Max bid-ask spread (7%) | | `SIMMER_MIN_DAYS` | `3` | Min days until resolution | | `SIMMER_MAX_POSITIONS` | `8` | Max concurrent open positions | | `SIMMER_YES_THRESHOLD` | `0.38` | Buy YES if market price ≤ this value | | `SIMMER_NO_THRESHOLD` | `0.62` | Sell NO if market price ≥ this value | | `SIMMER_MIN_TRADE` | `5` | Floor for any trade (min USDC regardless of conviction) | ## Dependency `simmer-sdk` by Simmer Markets (SpartanLabsXyz) - PyPI: https://pypi.org/project/simmer-sdk/ - GitHub: https://github.com/SpartanLabsXyz/simmer-sdk

path: clawhub.json
content: {
  "emoji": "\ud83d\udcc8",
  "requires": {
    "env": [
      "SIMMER_API_KEY"
    ],
    "pip": [
      "simmer-sdk"
    ]
  },
  "cron": null,
  "autostart": false,
  "automaton": {
    "managed": true,
    "entrypoint": "trader.py"
  },
  "tunables": [
    {
      "env": "SIMMER_MAX_POSITION",
      "type": "number",
      "default": 35,
      "range": [
        1,
        200
      ],
      "step": 1,
      "label": "Max position size (USD)"
    },
    {
      "env": "SIMMER_MIN_VOLUME",
      "type": "number",
      "default": 15000,
      "range": [
        0,
        500000
      ],
      "step": 500,
      "label": "Min market volume (USD)"
    },
    {
      "env": "SIMMER_MAX_SPREAD",
      "type": "number",
      "default": 0.07,
      "range": [
        0.01,
        0.5
      ],
      "step": 0.01,
      "label": "Max bid-ask spread"
    },
    {
      "env": "SIMMER_MIN_DAYS",
      "type": "number",
      "default": 3,
      "range": [
        1,
        180
      ],
      "step": 1,
      "label": "Min days until resolution"
    },
    {
      "env": "SIMMER_MAX_POSITIONS",
      "type": "number",
      "default": 8,
      "range": [
        1,
        20
      ],
      "step": 1,
      "label": "Max open positions"
    }
  ],
  "version": "0.0.2"
}

path: trader.py
content: """
polymarket-equity-markets-trader
Trades Polymarket prediction markets on stock index milestones, major IPOs,
earnings surprises, analyst upgrades, and company-specific financial events.

SAFE BY DEFAULT:
- No --live flag = paper trading (venue="sim"), zero financial risk.
- Cron/automaton runs default to paper mode.
- Real trades only with explicit --live flag.
"""
import os
import argparse
from datetime import datetime, timezone
from simmer_sdk import SimmerClient

TRADE_SOURCE = "sdk:polymarket-equity-markets-trader"
SKILL_SLUG   = "polymarket-equity-markets-trader"

KEYWORDS = [
    'S&P 500', 'Dow Jones', 'Nasdaq', 'stock market', 'bull market', 'bear market',
    'market crash', 'correction', 'IPO', 'earnings', 'revenue miss', 'beat',
    'Berkshire', 'Warren Buffett', 'Apple earnings', 'Tesla earnings',
    'Nvidia earnings', 'stock split', 'buyback', 'dividend', 'short squeeze',
    'all-time high', 'market cap trillion', 'index rebalance', 'EPS beat',
    'earnings surprise', 'guidance raised', 'revenue beat', 'beat estimates',
    'CPI', 'inflation', 'non-farm payroll', 'NFP', 'Fed rate', 'FOMC',
    'recession', 'bear market', 'VIX', 'volatility', 'circuit breaker',
]

# Risk parameters — declared as tunables in clawhub.json, adjustable from Simmer UI.
# Named SIMMER_* so apply_skill_config() can load automaton-managed overrides.
MAX_POSITION   = float(os.environ.get("SIMMER_MAX_POSITION",  "35"))
MIN_VOLUME     = float(os.environ.get("SIMMER_MIN_VOLUME",    "15000"))
MAX_SPREAD     = float(os.environ.get("SIMMER_MAX_SPREAD",    "0.07"))
MIN_DAYS       = int(os.environ.get(  "SIMMER_MIN_DAYS",      "3"))
MAX_POSITIONS  = int(os.environ.get(  "SIMMER_MAX_POSITIONS", "8"))
# Signal thresholds — buy YES below YES_THRESHOLD, sell NO above NO_THRESHOLD.
# Position size scales with conviction, adjusted by event type, earnings base
# rates, and macro calendar timing.
YES_THRESHOLD  = float(os.environ.get("SIMMER_YES_THRESHOLD", "0.38"))
NO_THRESHOLD   = float(os.environ.get("SIMMER_NO_THRESHOLD",  "0.62"))
MIN_TRADE      = float(os.environ.get("SIMMER_MIN_TRADE",     "5"))

# Earnings season: companies report Q1 (Apr–May), Q2 (Jul–Aug),
# Q3 (Oct–Nov), Q4 (Jan–Feb). Signal density peaks in these months.
_EARNINGS_SEASON_MONTHS = {1, 2, 4, 5, 7, 8, 10, 11}

# Quarter-start months (Jan, Apr, Jul, Oct) have maximum macro data density:
# GDP advance estimate, earnings flood, FOMC meetings, and NFP all land.
_HIGH_MACRO_MONTHS = {1, 4, 7, 10}

_client: SimmerClient | None = None


def get_client(live: bool = False) -> SimmerClient:
    """
    live=False → venue="sim"  (paper trades — safe default).
    live=True  → venue="polymarket" (real trades, only with --live flag).
    """
    global _client, MAX_POSITION, MIN_VOLUME, MAX_SPREAD, MIN_DAYS, MAX_POSITIONS, YES_THRESHOLD, NO_THRESHOLD, MIN_TRADE
    if _client is None:
        venue = "polymarket" if live else "sim"
        _client = SimmerClient(
            [REDACTED_SECRET]"SIMMER_API_KEY"],
            venue=venue,
        )
        # Load tunable overrides set via the Simmer UI (SIMMER_* vars only).
        if live:
            _client.live = True
        try:
            _client.apply_skill_config(SKILL_SLUG)
        except AttributeError:
            pass  # apply_skill_config only available in Simmer runtime
        # Re-read params in case apply_skill_config updated os.environ.
        MAX_POSITION   = float(os.environ.get("SIMMER_MAX_POSITION",  str(MAX_POSITION)))
        MIN_VOLUME     = float(os.environ.get("SIMMER_MIN_VOLUME",    str(MIN_VOLUME)))
        MAX_SPREAD     = float(os.environ.get("SIMMER_MAX_SPREAD",    str(MAX_SPREAD)))
        MIN_DAYS       = int(os.environ.get(  "SIMMER_MIN_DAYS",      str(MIN_DAYS)))
        MAX_POSITIONS  = int(os.environ.get(  "SIMMER_MAX_POSITIONS", str(MAX_POSITIONS)))
        YES_THRESHOLD  = float(os.environ.get("SIMMER_YES_THRESHOLD", str(YES_THRESHOLD)))
        NO_THRESHOLD   = float(os.environ.get("SIMMER_NO_THRESHOLD",  str(NO_THRESHOLD)))
        MIN_TRADE      = float(os.environ.get("SIMMER_MIN_TRADE",     str(MIN_TRADE)))
    return _client


def find_markets(client: SimmerClient) -> list:
    """Find active markets matching strategy keywords, deduplicated."""
    seen, unique = set(), []
    for kw in KEYWORDS:
        try:
            for m in client.find_markets(query=kw):
                if m.id not in seen:
                    seen.add(m.id)
                    unique.append(m)
        except Exception as e:
            print(f"[search] {kw!r}: {e}")
    return unique


def equity_bias(question: str) -> float:
    """
    Returns a conviction multiplier (0.65–1.40) combining two structural edges
    unique to equity prediction markets:

    1. MARKET TYPE / EARNINGS BASE RATE CORRECTION
       Equity markets run in parallel with the deepest, most quantified financial
       markets in the world. The edge on Polymarket is the LAG: retail prices
       equity outcomes using headlines and gut feeling while professional data
       (options implied volatility, earnings base rates, futures term structure)
       is sitting in public databases they never check.

       Large-cap tech earnings beat (NVDA, AAPL, MSFT, META, GOOG, AMZN) → 1.25x
         These companies beat Wall Street consensus EPS estimates ~80–85% of the
         time, sustained across multiple years. Nvidia beat consensus 7 consecutive
         quarters 2022–2024. Apple beats ~80%. Meta beats ~85%. Retail prices
         these as ~50–60% likely to beat — a 20–30 percentage point gap that
         is pure edge. Options implied volatility (available free from Yahoo
         Finance) gives a precise probability band for the earnings move; Polymarket
         consistently misprices vs the options-implied range.

       General S&P 500 earnings beat (any large company)           → 1.15x
         The most underknown stat in all of equity markets: S&P 500 companies
         beat analyst consensus EPS estimates approximately 73–75% of the time
         historically. This rate has been consistent across multiple cycles.
         Retail has no idea. They price earnings beats as coin flips. Every
         "will X beat earnings?" market is a structural YES lean unless the
         company has a specific documented miss history.

       Index level milestone (S&P 500, Nasdaq, Dow reaching X)    → 1.15x
         S&P 500 futures market is a 24-hour reference. Polymarket markets on
         index thresholds take 20–60 minutes to reprice after major macro
         catalysts (CPI prints, payrolls, FOMC decisions). This is especially
         large during pre-market hours when futures have already moved but
         Polymarket participants haven't absorbed the move yet.

       Dividend / buyback / stock split announcement               → 0.90x
         Corporate decisions with some information leakage (insider buying
         patterns, SEC pre-disclosure windows) but timing is genuinely
         uncertain. Trade with moderate caution.

       IPO by date / IPO valuation milestone                       → 0.85x
         Banker and market-condition driven. IPO windows are real but timing
         within a window is hard to predict. S-1 filing dates are public but
         the gap from filing to pricing is highly variable.

       Market crash / correction / bear market by date             → 0.75x
         The single most reliably overpriced category in equity prediction
         markets. "Will the S&P fall 20% by X?" Retail dramatically overprices
         crash scenarios for any specific calendar window because crashes are
         vivid and memorable but statistically rare in any given period.
         VIX-spike periods create especially overcrowded crash markets — retail
         fears anchor to the worst case when mean reversion is the more likely
         outcome.

       Short squeeze / meme stock milestone                        → 0.65x
         Pure retail sentiment and social media coordination. GME/AMC-style
         squeezes require specific short float data, borrow rate spikes, and
         coordinated retail buying — all of which are impossible to predict
         from the question text alone. Lowest edge category in equity markets.

    2. EARNINGS CALENDAR TIMING
       Earnings beat/miss markets have dramatically stronger signal during
       earnings season. Outside earnings season, only pre-announcements and
       guidance updates trickle in — the market lacks the data density to
       price these questions accurately, which means the base rate edge
       (73–75% beat rate) is noisier and less actionable.

       Earnings-type question + peak season (Jan, Feb, Apr, May, Jul, Aug, Oct, Nov)
         → 1.20x: Maximum signal density; options implied vol is fresh; analyst
         estimates are current; company guidance is recent.

       Earnings-type question + off-season (Mar, Jun, Sep, Dec)
         → 0.85x: Sparse data; retail prices on stale information from
         last quarter; estimates drift.

       Index/macro question + quarter-start month (Jan, Apr, Jul, Oct)
         → 1.10x: GDP advance estimate + earnings flood + FOMC + NFP all land
         in quarter-start months. Maximum macro catalyst density.

    Combined and capped at 1.40x.
    Large-cap tech earnings in peak season: 1.25 × 1.20 = 1.40x cap — maximum edge.
    Market crash question any time: 0.75x — always trade defensively.
    Short squeeze: 0.65x — near MIN_TRADE floor regardless of probability.
    """
    month = datetime.now(timezone.utc).month
    q = question.lower()

    # Factor 1: market type / earnings base rate
    if any(w in q for w in ("nvidia", "nvda earnings", "nvda beat", "apple earnings",
                             "aapl earnings", "microsoft earnings", "msft earnings",
                             "meta earnings", "google earnings", "alphabet earnings",
                             "amazon earnings", "amzn earnings", "tesla earnings",
                             "tsla earnings")):
        type_mult = 1.25  # ~80-85% beat rate; options market precise; retail prices 50-60%

    elif any(w in q for w in ("earnings beat", "beat estimates", "beat expectations",
                               "eps beat", "revenue beat", "earnings surprise",
                               "beat consensus", "guidance raised", "guidance above",
                               "earnings miss", "miss estimates")):
        type_mult = 1.15  # S&P 500 ~73-75% beat rate — retail has no idea

    elif any(w in q for w in ("s&p 500", "s&p500", "spx ", "nasdaq", "dow jones",
                               "dow ", "nikkei", "ftse", "dax ", "all-time high",
                               "market cap trillion", "index level", "index milestone",
                               "reach 5000", "reach 6000", "reach 7000", "reach 8000",
                               "reach 20000", "reach 30000")):
        type_mult = 1.15  # Futures market reference; Polymarket lags macro catalysts 20-60 min

    elif any(w in q for w in ("dividend", "buyback", "stock split", "share repurchase",
                               "special dividend", "capital return")):
        type_mult = 0.90  # Some leakage signal but timing uncertain

    elif any(w in q for w in ("ipo", "initial public offering", "go public",
                               "direct listing", "spac ", "s-1 filing",
                               "ipo by", "ipo this year")):
        type_mult = 0.85  # Window-driven but precise timing hard

    elif any(w in q for w in ("market crash", "crash by", "stock market crash",
                               "bear market", "recession by", "market collapse",
                               "circuit breaker", "trading halt", "black swan",
                               "20% drop", "30% drop", "market bottom")):
        type_mult = 0.75  # Retail systematically overprices crash for any specific period

    elif any(w in q for w in ("short squeeze", "meme stock", "gamestop", "gme ",
                               "amc ", "wallstreetbets", "wsb ", "roaring kitty",
                               "most shorted", "short interest")):
        type_mult = 0.65  # Social coordination unpredictable; lowest edge category

    else:
        type_mult = 1.0

    # Factor 2: earnings calendar timing
    earnings_keywords = ("earnings", "eps", "revenue beat", "guidance", "quarterly results",
                         "beat estimates", "beat expectations", "earnings surprise",
                         "q1 results", "q2 results", "q3 results", "q4 results")
    is_earnings_type = any(w in q for w in earnings_keywords)

    index_keywords = ("s&p", "nasdaq", "dow", "nikkei", "ftse", "dax", "index",
                      "market cap", "all-time high")
    is_index_type = any(w in q for w in index_keywords)

    if is_earnings_type:
        if month in _EARNINGS_SEASON_MONTHS:
            calendar_mult = 1.20  # Peak season: fresh estimates, current guidance, live options vol
        else:
            calendar_mult = 0.85  # Off-season: stale data, sparse pre-announcements
    elif is_index_type and month in _HIGH_MACRO_MONTHS:
        calendar_mult = 1.10  # Quarter-start: GDP + earnings flood + FOMC maximum density
    else:
        calendar_mult = 1.0

    return min(1.40, type_mult * calendar_mult)


def compute_signal(market) -> tuple[str | None, float, str]:
    """
    Returns (side, size, reasoning) or (None, 0, skip_reason).

    Conviction-based sizing with earnings base rate correction and calendar timing:
    - Base conviction scales linearly with distance from threshold
    - equity_bias() corrects for the most important underknown stat in equity
      markets: S&P 500 companies beat consensus ~73-75% of the time; large-cap
      tech ~80-85%. Retail prices these as coin flips. Every earnings beat market
      is a structural YES lean during earnings season.
    - Calendar multiplier: peak earnings season (1.20x) vs off-season (0.85x)
    - Crash/meme markets dampened (0.65-0.75x) — narrative overpricing
    - Result capped at 1.0 so size never exceeds MAX_POSITION
    - MIN_TRADE floor prevents trivially small orders near the boundary

    Remix: feed options market implied earnings move (from Yahoo Finance options
    chain) into p — the options-implied probability is far more precise than
    Polymarket retail pricing for earnings beat/miss questions.
    """
    p = market.current_probability
    q = market.question

    # Spread gate
    if market.spread_cents is not None and market.spread_cents / 100 > MAX_SPREAD:
        return None, 0, f"Spread {market.spread_cents/100:.1%} > {MAX_SPREAD:.1%}"

    # Days-to-resolution gate
    if market.resolves_at:
        try:
            resolves = datetime.fromisoformat(market.resolves_at.replace("Z", "+00:00"))
            days = (resolves - datetime.now(timezone.utc)).days
            if days < MIN_DAYS:
                return None, 0, f"Only {days} days to resolve"
        except Exception:
            pass

    bias = equity_bias(q)

    if p <= YES_THRESHOLD:
        # conviction=0 at threshold boundary, conviction=1 at p=0 — scaled by equity bias
        conviction = min(1.0, (YES_THRESHOLD - p) / YES_THRESHOLD * bias)
        size = max(MIN_TRADE, round(conviction * MAX_POSITION, 2))
        edge = YES_THRESHOLD - p
        return "yes", size, f"YES {p:.0%} edge={edge:.0%} bias={bias:.2f}x size=${size} — {q[:65]}"

    if p >= NO_THRESHOLD:
        conviction = min(1.0, (p - NO_THRESHOLD) / (1 - NO_THRESHOLD) * bias)
        size = max(MIN_TRADE, round(conviction * MAX_POSITION, 2))
        edge = p - NO_THRESHOLD
        return "no", size, f"NO YES={p:.0%} edge={edge:.0%} bias={bias:.2f}x size=${size} — {q[:65]}"

    return None, 0, f"Neutral at {p:.1%} (outside {YES_THRESHOLD:.0%}/{NO_THRESHOLD:.0%} bands)"


def context_ok(client: SimmerClient, market_id: str) -> tuple[bool, str]:
    """Check flip-flop and slippage safeguards."""
    try:
        ctx = client.get_market_context(market_id)
        if not ctx:
            return True, "no context"
        if ctx.get("discipline", {}).get("is_flip_flop"):
            reason = ctx["discipline"].get("flip_flop_reason", "recent reversal")
            return False, f"Flip-flop: {reason}"
        slip = ctx.get("slippage", {})
        if isinstance(slip, dict) and slip.get("slippage_pct", 0) > 0.15:
            return False, f"Slippage {slip['slippage_pct']:.1%}"
        for w in ctx.get("warnings", []):
            print(f"  [warn] {w}")
    except Exception as e:
        print(f"  [ctx] {market_id}: {e}")
    return True, "ok"


def run(live: bool = False) -> None:
    mode = "LIVE" if live else "PAPER (sim)"
    month = datetime.now(timezone.utc).month
    earnings_season = "PEAK" if month in _EARNINGS_SEASON_MONTHS else "off-season"
    print(f"[polymarket-equity-markets-trader] mode={mode} max_pos=${MAX_POSITION} min_vol=${MIN_VOLUME} max_spread={MAX_SPREAD:.0%} earnings={earnings_season}")

    client  = get_client(live=live)
    markets = find_markets(client)
    print(f"[polymarket-equity-markets-trader] {len(markets)} candidate markets")

    placed = 0
    for m in markets:
        if placed >= MAX_POSITIONS:
            break

        side, size, reasoning = compute_signal(m)
        if not side:
            print(f"  [skip] {reasoning}")
            continue

        ok, why = context_ok(client, m.id)
        if not ok:
            print(f"  [skip] {why}")
            continue

        try:
            r = client.trade(
                market_id=m.id,
                side=side,
                amount=size,
                source=TRADE_SOURCE,
                skill_slug=SKILL_SLUG,
                reasoning=reasoning,
            )
            tag    = "(sim)" if r.simulated else "(live)"
            status = "OK" if r.success else f"FAIL:{r.error}"
            print(f"  [trade] {side.upper()} ${size} {tag} {status} — {reasoning[:70]}")
            if r.success:
                placed += 1
        except Exception as e:
            print(f"  [error] {m.id}: {e}")

    print(f"[polymarket-equity-markets-trader] done. {placed} orders placed.")


if __name__ == "__main__":
    ap = argparse.ArgumentParser(description="Trades Polymarket prediction markets on stock index milestones, major IPOs, earnings surprises, and company-specific financial events.")
    ap.add_argument("--live", action="store_true",
                    help="Real trades on Polymarket. Default is paper (sim) mode.")
    run(live=ap.parse_args().live)


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)