Skill: diagnostikon/polymarket-longshot-bias-trader (version 0.0.3)

--- name: polymarket-longshot-bias-trader description: Exploits longshot bias — the most robust anomaly in betting and prediction market research. Systematically fades overpriced low-probability outcomes (buy NO when p ≤ 10%) and backs underpriced near-certainties (buy YES when p ≥ 88%). Domain-agnostic: the bias exists in every market category. Conviction scales with depth of mispricing and narrative salience. metadata: author: Diagnostikon version: "1.0" displayName: Longshot Bias Trader difficulty: intermediate --- # Longshot Bias Trader > **This is a template.** > The default signal is purely price-based — no external API required. The skill discovers liquid markets across all categories, identifies extreme probabilities where behavioral mispricing is strongest, and trades against the crowd. > The skill handles all the plumbing (market discovery, trade execution, safeguards). Your agent provides the alpha. ## Strategy Overview Longshot bias is the single most replicated anomaly in betting market research. First documented by Griffith (1949) in horse racing, it has since been confirmed across sports betting, financial options (the volatility smile is its cousin), and prediction markets. **The core finding:** Participants systematically overweight the excitement of low-probability outcomes and underweight the boring reliability of near-certainties. - A market priced at **6%** is more likely to have a true probability of **2–3%** — retail bets the dream. - A market priced at **91%** is more likely to have a true probability of **95–97%** — retail finds it too boring to back fully. This creates two exploitable edges in opposite directions: 1. **Fade the longshot (buy NO when p is very low):** The crowd overprices rare events. Buying NO when p ≤ 10% gives you the opposite side of that overpriced bet. 2. **Back the near-certainty (buy YES when p is very high):** The crowd underprices likely outcomes. Buying YES when p ≥ 88% gives you the underpriced side. These are not directional bets on events — they are **structural bets against a known behavioral bias** that regenerates in every market, every day. ## The Core Insight: Why Longshot Bias Exists Three behavioral mechanisms drive the bias simultaneously: **1. Probability distortion (Kahneman & Tversky)** Humans do not perceive probabilities linearly. We treat 5% as "might happen" and 95% as "probably won't fail." This distorts prices in predictable ways: low probabilities are overweighted, high probabilities are underweighted. **2. Narrative salience** A 5% longshot has a compelling story. "Imagine if it happened." Retail pays a premium for the story. A 95% near-certainty is boring — retail won't pay full price for something "obviously going to happen anyway." **3. Skewness preference** Small chance of a large gain is psychologically attractive even at negative expected value. This is the same reason people buy lottery tickets. In prediction markets, the NO side of a longshot offers a small chance of a large gain in the *other direction* — but retail doesn't frame it that way. ## Signal Logic ### Default Signal: Conviction-Based Extreme Probability Fading This skill uses **inverted thresholds** compared to standard prediction market traders: | Standard trader | Longshot bias trader | |---|---| | Buy YES when p is low (you think it'll happen) | Buy NO when p is low (market overprices the longshot) | | Buy NO when p is high (you think it won't happen) | Buy YES when p is high (market underprices the certainty) | **Step 1 — Market discovery:** Broad keyword sweep across all high-liquidity categories (politics, crypto, sports, macro, tech, geopolitics). Longshot bias is category-agnostic — it exists wherever retail trades. **Step 2 — Signal gates:** - Spread ≤ MAX_SPREAD (wide spreads eat the edge) - Days to resolution ≥ MIN_DAYS (avoid near-resolution noise where convergence may have already started) **Step 3 — Signal direction:** - `p ≤ LONGSHOT_THRESHOLD` → buy **NO** (fade the overpriced longshot) - `p ≥ CERTAINTY_THRESHOLD` → buy **YES** (back the underpriced near-certainty) - Between thresholds → skip **Step 4 — Conviction sizing:** For NO (fading longshot): ``` conviction = (LONGSHOT_THRESHOLD - p) / LONGSHOT_THRESHOLD ``` At p=0%: conviction=1.0 → MAX_POSITION. At p=LONGSHOT_THRESHOLD: conviction=0.0 → MIN_TRADE floor. For YES (backing near-certainty): ``` conviction = (p - CERTAINTY_THRESHOLD) / (1 - CERTAINTY_THRESHOLD) ``` At p=100%: conviction=1.0 → MAX_POSITION. At p=CERTAINTY_THRESHOLD: conviction=0.0 → MIN_TRADE floor. **Step 5 — Quality multiplier (`_longshot_quality_mult`):** Three factors adjust conviction up or down: | Factor | What it captures | Range | |---|---|---| | Depth of mispricing | Deeper longshots / higher certainties are more mispriced | 1.00 → 1.25x | | Narrative salience | Emotionally charged framing inflates longshot prices further | 1.00 → 1.15x | | Resolution clarity | Objective criteria → cleaner edge; vague criteria → noise | 0.90 → 1.05x | Combined and capped at 1.30x. ### How Sizing Works With defaults (LONGSHOT_THRESHOLD=10%, CERTAINTY_THRESHOLD=88%, MIN_TRADE=$5, MAX_POSITION=$30): **Fading longshots (buy NO):** | Market price p | Conviction | Size | |---|---|---| | 10% (at threshold) | 0% | $5 (floor) | | 7% | 30% | $9 | | 4% | 60% | $18 | | 1% | 90% | $27 | | 0% | 100% | $30 | **Backing near-certainties (buy YES):** | Market price p | Conviction | Size | |---|---|---| | 88% (at threshold) | 0% | $5 (floor) | | 92% | 33% | $10 | | 96% | 67% | $20 | | 99% | 92% | $28 | ### Keywords Monitored ``` president, election, congress, senate, federal reserve, rate cut, rate hike, inflation, recession, gdp, unemployment, bitcoin, ethereum, crypto, btc, eth, championship, world cup, super bowl, nba finals, world series, premier league, champions league, ipo, acquisition, merger, bankruptcy, earnings, agi, artificial intelligence, gpt, clinical trial, fda, ceasefire, sanctions, invasion, treaty, nato ``` ### Remix Signal Ideas - **Calibration data from historical Polymarket markets**: If resolved markets show that markets priced at 5% resolved YES only 1.5% of the time, you have a precise expected-value edge to trade against — replace keyword thresholds with a calibration curve - **Cross-category calibration**: The bias is stronger in some categories (sports, celebrity) than others (macro, crypto) — weight conviction by category-specific historical overpricing rates - **Volume filter on longshots**: Very low-volume longshots may be mispriced for liquidity reasons rather than behavioral reasons — add a minimum volume gate specifically for sub-10% markets to separate behavioral edge from illiquidity risk - **Resolution source quality**: Markets resolving via a single authoritative source (official election results, exchange close price) have cleaner edge than markets resolving via "admin discretion" — apply a resolution clarity multiplier to the conviction - **Temporal decay toward resolution**: As a market approaches resolution, the bias should compress (less time for narrative to accumulate). Wire in a decay function: `mult *= max(0.7, days_to_resolution / 30)` to reduce size as markets near their end date ## 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` | `30` | Max USDC per trade (reached at 100% conviction) | | `SIMMER_MIN_VOLUME` | `10000` | Min market volume filter — higher bar to ensure genuine behavioral pricing, not illiquidity | | `SIMMER_MAX_SPREAD` | `0.06` | Max bid-ask spread (6%) — tight to preserve statistical edge | | `SIMMER_MIN_DAYS` | `3` | Min days until resolution — avoid markets where convergence has already started | | `SIMMER_MAX_POSITIONS` | `10` | Max concurrent open positions | | `SIMMER_MIN_TRADE` | `5` | Floor for any trade (min USDC regardless of conviction) | | `SIMMER_LONGSHOT_THRESHOLD` | `0.10` | Buy NO when market probability ≤ this value | | `SIMMER_CERTAINTY_THRESHOLD` | `0.88` | Buy YES when market probability ≥ this value | ## 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": "\ud83c\udfaf\ud83d\udcc9",
  "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": 30,
      "range": [
        1,
        200
      ],
      "step": 1,
      "label": "Max position size USD"
    },
    {
      "env": "SIMMER_MIN_VOLUME",
      "type": "number",
      "default": 10000,
      "range": [
        0,
        500000
      ],
      "step": 1000,
      "label": "Min market volume USD"
    },
    {
      "env": "SIMMER_MAX_SPREAD",
      "type": "number",
      "default": 0.06,
      "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": 10,
      "range": [
        1,
        20
      ],
      "step": 1,
      "label": "Max open positions"
    },
    {
      "env": "SIMMER_MIN_TRADE",
      "type": "number",
      "default": 5,
      "range": [
        1,
        50
      ],
      "step": 1,
      "label": "Min trade size USD (floor)"
    },
    {
      "env": "SIMMER_LONGSHOT_THRESHOLD",
      "type": "number",
      "default": 0.1,
      "range": [
        0.01,
        0.25
      ],
      "step": 0.01,
      "label": "Longshot threshold \u2014 buy NO when p <= this"
    },
    {
      "env": "SIMMER_CERTAINTY_THRESHOLD",
      "type": "number",
      "default": 0.88,
      "range": [
        0.7,
        0.99
      ],
      "step": 0.01,
      "label": "Certainty threshold \u2014 buy YES when p >= this"
    }
  ],
  "version": "0.0.2"
}

path: trader.py
content: """
polymarket-longshot-bias-trader
Systematically exploits longshot bias — the single most robust anomaly in
betting and prediction market research.

THE EDGE:
  Low-probability outcomes are consistently OVERpriced in prediction markets.
  High-probability outcomes are consistently UNDERpriced.

  Documented across horse racing (Griffith 1949), sports betting, financial
  options (volatility smile), and prediction markets. The mechanism is
  behavioral: retail traders overweight the excitement of longshots and
  underweight the boring near-certainty. We go the opposite direction.

  Fade the longshot (buy NO when p is very low):
    Market says 6% chance → true probability is closer to 2–3% → NO is cheap.

  Back the near-certainty (buy YES when p is very high):
    Market says 91% chance → true probability is closer to 95–97% → YES is cheap.

This is domain-agnostic — the bias exists in ALL categories of prediction
markets. We run broad keyword sweeps rather than targeting a single topic.

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-longshot-bias-trader"
SKILL_SLUG   = "polymarket-longshot-bias-trader"

# Broad keyword sweep — longshot bias is category-agnostic.
# We want high volume, liquid markets where the bias is most exploitable.
KEYWORDS = [
    # High-volume political / macro
    "president", "election", "congress", "senate", "federal reserve", "rate cut",
    "rate hike", "inflation", "recession", "gdp", "unemployment",
    # High-volume crypto
    "bitcoin", "ethereum", "crypto", "btc", "eth",
    # High-volume sports
    "championship", "world cup", "super bowl", "nba finals", "world series",
    "premier league", "champions league",
    # High-volume tech / business
    "ipo", "acquisition", "merger", "bankruptcy", "earnings",
    # AI / science (very active on Polymarket)
    "agi", "artificial intelligence", "gpt", "clinical trial", "fda",
    # Geopolitical
    "ceasefire", "sanctions", "invasion", "treaty", "nato",
]

# Risk parameters
MAX_POSITION  = float(os.environ.get("SIMMER_MAX_POSITION",  "30"))
MIN_VOLUME    = float(os.environ.get("SIMMER_MIN_VOLUME",    "10000"))  # higher bar — we want liquid markets
MAX_SPREAD    = float(os.environ.get("SIMMER_MAX_SPREAD",    "0.06"))
MIN_DAYS      = int(os.environ.get(  "SIMMER_MIN_DAYS",      "3"))      # avoid near-resolution noise
MAX_POSITIONS = int(os.environ.get(  "SIMMER_MAX_POSITIONS", "10"))
MIN_TRADE     = float(os.environ.get("SIMMER_MIN_TRADE",     "5"))

# Longshot bias thresholds — INVERTED from the standard framework.
#
# LONGSHOT_THRESHOLD: buy NO when p <= this value.
#   A market at 7% is a screaming longshot — retail prices the excitement,
#   not the probability. We sell the dream.
#   Literature suggests the bias is strongest below 10%.
#
# CERTAINTY_THRESHOLD: buy YES when p >= this value.
#   A market at 91% is boring to retail but still offers edge.
#   The underpricing of near-certainties is the mirror image of longshot
#   overpricing — the same behavioral mechanism, opposite direction.
#   Literature suggests the bias is strongest above 88%.
LONGSHOT_THRESHOLD  = float(os.environ.get("SIMMER_LONGSHOT_THRESHOLD",  "0.10"))
CERTAINTY_THRESHOLD = float(os.environ.get("SIMMER_CERTAINTY_THRESHOLD", "0.88"))

_client: SimmerClient | None = None


def _longshot_quality_mult(question: str, p: float) -> float:
    """
    Returns a conviction multiplier (0.70–1.30) that adjusts for market
    characteristics known to amplify or dampen the longshot bias.

    1. DEPTH OF LONGSHOT / CERTAINTY (magnitude of mispricing)
       The further from 50%, the more extreme the behavioral distortion.
       p=2% is a more extreme longshot than p=9% — retail excitement peaks
       at tiny probabilities ('imagine if it happened!'). Similarly, p=98%
       is more underpriced than p=89%.

    2. NARRATIVE SALIENCE (does the event have a compelling story?)
       Markets with emotionally charged keywords attract retail dreamers who
       push longshot prices up further. A 'miracle comeback', 'dark horse',
       or 'against all odds' framing inflates longshot prices the most.
       We gain more edge fading narratively salient longshots.

    3. RESOLUTION CLARITY (is the outcome unambiguous?)
       Clear resolution criteria → less 'phantom upside' that retail
       embeds in prices. Vague criteria → wider noise band → edge less clean.
       We prefer markets with precise, objective resolution language.
    """
    q = question.lower()

    # Factor 1: depth of bias — deeper longshot / higher certainty = more edge
    if p <= LONGSHOT_THRESHOLD:
        # Deeper longshots: more overpriced, more edge fading them
        depth = (LONGSHOT_THRESHOLD - p) / LONGSHOT_THRESHOLD   # 0 at boundary, 1 at p=0
        depth_mult = 1.0 + 0.25 * depth                         # 1.00 → 1.25
    else:
        # Deeper certainties: more underpriced, more edge backing them
        depth = (p - CERTAINTY_THRESHOLD) / (1 - CERTAINTY_THRESHOLD)  # 0 at boundary, 1 at p=1
        depth_mult = 1.0 + 0.25 * depth                                 # 1.00 → 1.25

    # Factor 2: narrative salience — emotionally charged longshots are MORE overpriced
    # (retail dreams harder → price is more inflated → our NO is cheaper → more edge)
    narrative_keywords = (
        "miracle", "dark horse", "upset", "comeback", "surprise", "shock",
        "unexpected", "against all odds", "long shot", "longshot", "underdog",
        "wildcard", "wild card", "unlikely", "improbable", "remote chance",
        "outsider", "unprecedented",
    )
    if any(w in q for w in narrative_keywords):
        narrative_mult = 1.15   # narrative hype inflates price → more edge for us
    elif any(w in q for w in ("guarantee", "certain", "inevitable", "lock",
                               "foregone", "obvious", "clear", "definite")):
        narrative_mult = 1.10   # certainty framing but still not priced at 100%
    else:
        narrative_mult = 1.00

    # Factor 3: resolution clarity — objective criteria → cleaner edge
    clarity_good = ("official", "announced", "confirmed", "recorded", "reported",
                    "signed", "published", "voted", "declared", "certified")
    clarity_bad  = ("widely considered", "generally accepted", "consensus",
                    "deemed", "regarded as", "seen as")
    if any(w in q for w in clarity_good):
        clarity_mult = 1.05
    elif any(w in q for w in clarity_bad):
        clarity_mult = 0.90   # ambiguous resolution → noisy edge
    else:
        clarity_mult = 1.00

    return min(1.30, max(0.70, depth_mult * narrative_mult * clarity_mult))


def compute_signal(market) -> tuple[str | None, float, str]:
    """
    Returns (side, size, reasoning) or (None, 0, skip_reason).

    INVERTED from the standard framework:
      - buy NO  when p <= LONGSHOT_THRESHOLD  (fade the overpriced longshot)
      - buy YES when p >= CERTAINTY_THRESHOLD (back the underpriced near-certainty)

    Conviction for NO (fading longshot):
      The deeper the longshot, the more overpriced it is, the more we bet.
      conviction = (LONGSHOT_THRESHOLD - p) / LONGSHOT_THRESHOLD
      At p=0%: conviction=1.0 → MAX_POSITION.
      At p=LONGSHOT_THRESHOLD: conviction=0.0 → MIN_TRADE floor.

    Conviction for YES (backing near-certainty):
      The higher the probability above CERTAINTY_THRESHOLD, the more underpriced.
      conviction = (p - CERTAINTY_THRESHOLD) / (1 - CERTAINTY_THRESHOLD)
      At p=100%: conviction=1.0 → MAX_POSITION.
      At p=CERTAINTY_THRESHOLD: conviction=0.0 → MIN_TRADE floor.
    """
    p = market.current_probability
    q = market.question

    # Spread gate — wide spreads eat into the statistical edge
    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 — avoid near-resolution noise where prices
    # may have already started converging (reducing the bias)
    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}d to resolve — bias may have converged"
        except Exception:
            pass

    mult = _longshot_quality_mult(q, p)

    # FADE THE LONGSHOT — buy NO when market overprices a rare event
    if p <= LONGSHOT_THRESHOLD:
        conviction = min(1.0, (LONGSHOT_THRESHOLD - p) / LONGSHOT_THRESHOLD * mult)
        size = max(MIN_TRADE, round(conviction * MAX_POSITION, 2))
        edge = LONGSHOT_THRESHOLD - p
        return (
            "no", size,
            f"LONGSHOT-FADE NO p={p:.1%} edge={edge:.1%} mult={mult:.2f}x "
            f"size=${size} — {q[:65]}"
        )

    # BACK THE NEAR-CERTAINTY — buy YES when market underprices a likely event
    if p >= CERTAINTY_THRESHOLD:
        conviction = min(1.0, (p - CERTAINTY_THRESHOLD) / (1 - CERTAINTY_THRESHOLD) * mult)
        size = max(MIN_TRADE, round(conviction * MAX_POSITION, 2))
        edge = p - CERTAINTY_THRESHOLD
        return (
            "yes", size,
            f"CERTAINTY-BACK YES p={p:.1%} edge={edge:.1%} mult={mult:.2f}x "
            f"size=${size} — {q[:65]}"
        )

    return None, 0, (
        f"Neutral {p:.1%} — outside longshot(<={LONGSHOT_THRESHOLD:.0%}) "
        f"and certainty(>={CERTAINTY_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 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
    global MAX_POSITION, MIN_VOLUME, MAX_SPREAD, MIN_DAYS, MAX_POSITIONS
    global MIN_TRADE, LONGSHOT_THRESHOLD, CERTAINTY_THRESHOLD
    if _client is None:
        venue = "polymarket" if live else "sim"
        _client = SimmerClient(
            [REDACTED_SECRET]"SIMMER_API_KEY"],
            venue=venue,
        )
        if live:
            _client.live = True
        try:
            _client.apply_skill_config(SKILL_SLUG)
        except AttributeError:
            pass  # apply_skill_config only available in Simmer runtime
        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)))
        MIN_TRADE           = float(os.environ.get("SIMMER_MIN_TRADE",           str(MIN_TRADE)))
        LONGSHOT_THRESHOLD  = float(os.environ.get("SIMMER_LONGSHOT_THRESHOLD",  str(LONGSHOT_THRESHOLD)))
        CERTAINTY_THRESHOLD = float(os.environ.get("SIMMER_CERTAINTY_THRESHOLD", str(CERTAINTY_THRESHOLD)))
    return _client


def find_markets(client: SimmerClient) -> list:
    """Find active markets via broad keyword sweep, 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 run(live: bool = False) -> None:
    mode = "LIVE" if live else "PAPER (sim)"
    print(
        f"[polymarket-longshot-bias-trader] mode={mode} "
        f"longshot<={LONGSHOT_THRESHOLD:.0%} certainty>={CERTAINTY_THRESHOLD:.0%} "
        f"max_pos=${MAX_POSITION}"
    )

    client  = get_client(live=live)
    markets = find_markets(client)
    print(f"[polymarket-longshot-bias-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[:75]}")
            if r.success:
                placed += 1
        except Exception as e:
            print(f"  [error] {m.id}: {e}")

    print(f"[polymarket-longshot-bias-trader] done. {placed} orders placed.")


if __name__ == "__main__":
    ap = argparse.ArgumentParser(
        description=(
            "Exploits longshot bias: fades overpriced low-probability markets (buy NO), "
            "backs underpriced near-certainties (buy YES). "
            "The most documented anomaly in betting research, now systematized."
        )
    )
    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)