Skill: diagnostikon/polymarket-global-elections-trader (version 0.0.3)

--- name: polymarket-global-elections-trader description: Trades Polymarket prediction markets on elections, referendums, and democratic events worldwide. Exploits two structural edges — electoral system type predictability (FPTP amplification vs PR majority impossibility) and regional information lag (non-English polling aggregators update daily; Polymarket prices don't). metadata: author: snetripp version: "1.0" displayName: Global Elections & Democracy Trader difficulty: advanced --- # Global Elections & Democracy Trader > **This is a template.** > The default signal is keyword-based market discovery combined with conviction-based sizing and `election_bias()` — remix it with the data sources listed below. > The skill handles all the plumbing (market discovery, trade execution, safeguards). Your agent provides the alpha. ## Strategy Overview Global election markets on Polymarket have two layered structural edges that compound cleanly without any external API: 1. **Electoral system type** — Polymarket retail trades all elections as if they were US presidential races. They aren't. FPTP systems amplify poll leads into landslides; proportional representation systems make outright majorities near-impossible. Retail systematically misprices both. 2. **Regional information lag** — Polymarket is US-English-speaking. Wahlrecht.de updates German polling daily. DataFolha updates Brazilian tracking daily. Neither is read by the average Polymarket trader. The lag between a 3-point swing in a non-English aggregator and Polymarket repricing is 12–48 hours. ## Signal Logic ### Default Signal: Conviction-Based Sizing with Election Bias 1. Discover active election and referendum markets on Polymarket 2. Compute base conviction from distance to threshold (0% at boundary → 100% at p=0/p=1) 3. Apply `election_bias()` — combines electoral system type with regional information lag 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 ### Election Bias (built-in, no API required) **Factor 1 — Electoral System Type** | System / question type | Multiplier | The structural reality | |---|---|---| | FPTP "will X win" (UK, Australia, Canada) | **1.20x** | A 5% poll lead → ~80-85% win probability; retail anchors to raw margin, not seat translation | | Runoff / second round | **1.15x** | Post-round-1 head-to-head polling has <3% average error; retail treats it like first round | | Referendum (binary yes/no) | **1.10x** | Clean resolution criteria, well-polled in democracies | | Snap election / early dissolution | **0.85x** | No steady polling baseline — volatility dominates | | Coalition formation / who governs | **0.80x** | Multi-party negotiation dynamics genuinely unpredictable from polls alone | | No-confidence / leadership challenge | **0.80x** | Parliamentary arithmetic + defection timing — hard to model | | PR system "will X win outright majority" | **0.70x** | Near-impossible in Germany, Sweden, Netherlands, Spain, Israel — retail massively overprices | **The PR Majority Rule** — In proportional representation systems, a single party winning an outright majority is a statistical anomaly, not a plausible baseline. Germany's Bundestag, Sweden's Riksdag, Israel's Knesset, the Netherlands' Tweede Kamer — single-party majorities happen perhaps once per generation. Yet retail prices these markets at 15–30% because they project the US two-party binary onto a multi-party system. Every such market is a structural NO unless one party is polling above 45%, which essentially never happens in mature PR democracies. **Factor 2 — Regional Information Lag** | Region | Multiplier | Key data sources retail doesn't read | |---|---|---| | Germany / France / Benelux / Scandinavia | **1.20x** | Wahlrecht.de (daily), Politico Europe, Votecompass — 12-36h lag vs Polymarket | | Brazil / Latin America | **1.15x** | DataFolha, Quaest (daily tracking in Portuguese); CIEP, CNC for other countries | | South / Southeast Asia | **1.15x** | India: CSDS, ABP-CVoter; Indonesia: LSI/Indikator; Philippines: Pulse Asia — local exit polls hours ahead of English | | UK / Australia / Canada | **1.05x** | English but underweighted vs US — Electoral Calculus MRP models give structural edge | | Sub-Saharan Africa / fragile democracies | **0.75x** | Low polling quality, coup risk, result disputes — resolution risk is material | ### Combined Examples | Market | System mult | Region mult | Final bias | |---|---|---|---| | "Will SPD win majority in Bundestag?" | 0.70x (PR majority) | 1.20x (Germany lag) | **0.84x** — skeptical | | "Will CDU win German general election?" | 1.20x (FPTP-style win) | 1.20x (Germany lag) | **1.35x cap** | | "Who wins Brazil runoff?" | 1.15x (runoff) | 1.15x (Latin America) | **1.32x** | | "Will UK Labour win majority?" | 1.20x (FPTP) | 1.05x (English) | **1.26x** | | "Will snap election be called?" | 0.85x (snap) | 1.0x | **0.85x** | | "Will Nigerian election resolve cleanly?" | 1.0x | 0.75x (Africa) | **0.75x** | ### Keywords Monitored ``` election, referendum, vote, prime minister, chancellor, president, Germany election, France election, Brazil election, India election, UK election, Japan election, South Korea, Taiwan election, snap election, coalition, majority, parliament, polling, exit poll, incumbent, runoff, second round, Bundestag, Riksdag, Assemblée, Duma, Diet, Congress, Lok Sabha, confidence vote, no confidence, hung parliament, minority government, proportional representation, first past the post ``` ### Remix Signal Ideas - **Wahlrecht.de**: German polling aggregator updated daily — compare to Polymarket for German election markets; the lag is often >24h and actionable - **Politico Europe Poll of Polls**: Aggregated EU-wide polling — covers France, Germany, Italy, Spain, Poland simultaneously - **DataFolha / Quaest**: Brazilian tracking polls in Portuguese — daily updates on presidential and state races - **Electoral Calculus MRP (UK)**: Constituency-level seat projections — translates raw polling to seat counts, far more predictive than national vote share alone ## 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 (USD) — elections need liquidity | | `SIMMER_MAX_SPREAD` | `0.08` | Max bid-ask spread (8%) | | `SIMMER_MIN_DAYS` | `5` | 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\uddf3\ufe0f",
  "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": 500,
      "label": "Min market volume (USD)"
    },
    {
      "env": "SIMMER_MAX_SPREAD",
      "type": "number",
      "default": 0.08,
      "range": [
        0.01,
        0.5
      ],
      "step": 0.01,
      "label": "Max bid-ask spread"
    },
    {
      "env": "SIMMER_MIN_DAYS",
      "type": "number",
      "default": 5,
      "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-global-elections-trader
Trades Polymarket prediction markets on elections, referendums, and democratic events
worldwide — outside the US (which is heavily covered). Focuses on EU, Latin America,
Asia, and Africa.

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-global-elections-trader"
SKILL_SLUG   = "polymarket-global-elections-trader"

KEYWORDS = [
    'election', 'referendum', 'vote', 'prime minister', 'chancellor', 'president',
    'Germany election', 'France election', 'Brazil election', 'India election',
    'UK election', 'Japan election', 'South Korea', 'Taiwan election',
    'snap election', 'coalition', 'majority', 'parliament', 'polling',
    'exit poll', 'incumbent', 'runoff', 'second round', 'Bundestag',
    'Riksdag', 'Assemblée', 'Duma', 'Diet', 'Congress', 'Lok Sabha',
    'confidence vote', 'no confidence', 'hung parliament', 'minority government',
    'proportional representation', 'first past the post',
]

# 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",  "30"))
MIN_VOLUME     = float(os.environ.get("SIMMER_MIN_VOLUME",    "10000"))
MAX_SPREAD     = float(os.environ.get("SIMMER_MAX_SPREAD",    "0.08"))
MIN_DAYS       = int(os.environ.get(  "SIMMER_MIN_DAYS",      "5"))
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 electoral system type and
# regional information lag.
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"))

_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 election_bias(question: str) -> float:
    """
    Returns a conviction multiplier (0.70–1.35) combining two structural edges
    unique to global election markets:

    1. ELECTORAL SYSTEM TYPE — HOW PREDICTABLE IS THIS OUTCOME?
       Electoral systems have radically different outcome distributions. Retail
       on Polymarket trades all elections as if they were US presidential races
       (two candidates, coin flip). They aren't. Understanding the system is
       the core alpha in this domain.

       Two-party / FPTP "will X win" (UK, Australia, Canada)  → 1.20x
         First-past-the-post systems amplify polling leads into landslide
         probabilities. A party with a 5% polling lead has ~80-85% win probability,
         not 55%. Retail systematically underestimates this amplification because
         they anchor to the raw poll margin, not the seat-translation model.
         UK constituency-level polling (Electoral Calculus) makes this trackable.

       Runoff / second-round election (France, Brazil, many) → 1.15x
         After the first round, the runoff polling is dramatically more accurate
         than first-round polling. Head-to-head matchup polling post-round-1
         typically has <3% error. Retail treats runoffs with the same uncertainty
         as first rounds — a significant mispricing. Markets on "who wins the
         runoff" immediately after round-1 results are underpriced on the leading
         candidate.

       Referendum (binary yes/no) (Brexit, IndyRef, etc.)    → 1.10x
         Cleaner resolution criteria than multi-candidate races. Well-polled in
         most democratic countries. Retail overcomplicates these.

       Snap election / early dissolution                      → 0.85x
         Called with little notice — no steady polling baseline, high news-driven
         volatility. The incumbent calling the snap usually has a structural
         advantage that retail underestimates, but the timing uncertainty
         dominates. Trade cautiously.

       Coalition formation / government composition           → 0.80x
         Who forms the coalition after an election is driven by multi-party
         negotiation dynamics that are genuinely unpredictable — party veto
         players, policy distance, personal relationships. Polling tells you
         who won seats, not who governs. Trade with significant caution.

       No-confidence vote / leadership challenge              → 0.80x
         Parliamentary arithmetic matters but so does timing of defections,
         backbench rebellions, and party discipline. Hard to model without
         real-time whip counts.

       PR system "will X win an outright majority"            → 0.70x
         The sharpest dampening in this category. In proportional representation
         systems (Germany, Netherlands, Sweden, Norway, Spain, Israel), a single
         party winning an outright majority of seats is extremely rare — typically
         <5% base rate. Retail prices these markets at 15-30% because they project
         US presidential race logic onto parliamentary systems. It almost never
         happens. Every "will X win a majority in the Bundestag/Riksdag/Knesset"
         question is a structural NO unless polling shows >45% for one party.

    2. REGIONAL INFORMATION LAG — THE POLYMARKET BLINDSPOT
       Polymarket is overwhelmingly US-English-speaking. Non-English-language
       polling updates reach Polymarket traders with a systematic lag of 12–48h.
       Local-language polling aggregators update daily; Polymarket prices don't.
       This is the most exploitable structural gap in election markets.

       German / French / Dutch / Scandinavian elections      → 1.20x
         Wahlrecht.de (Germany), Politico Europe, Votecompass — daily polling
         aggregators in German/French/Dutch that US retail doesn't monitor.
         The lag between a 3-point swing in Wahlrecht and Polymarket repricing
         is 12-36 hours. This is the richest information lag in the category.

       Brazilian / Latin American elections                  → 1.15x
         DataFolha and Quaest (Brazil) publish daily tracking polls in
         Portuguese. CIEP (Mexico), CNC (Argentina) similarly. High-quality
         polling that US retail rarely reads. Runoff races especially.

       South / Southeast Asian elections                     → 1.15x
         India (CSDS, ABP-CVoter), Indonesia (LSI, Indikator), Philippines
         (Pulse Asia) publish local-language tracking. Indian exit polls in
         particular are released in local languages hours before English
         coverage catches up. Enormous audience + lag = large edge.

       UK / Australian / Canadian elections (English)        → 1.05x
         Well-covered in English but less so than US elections. Electoral
         Calculus (UK) and YouGov MRP models give structural edges that
         Polymarket retail underweights.

       Sub-Saharan Africa / fragile democracies              → 0.75x
         Low polling quality (sample sizes, access), high institutional
         volatility. Election outcomes regularly include coup risk, result
         disputes, and logistical delays that make clean resolution uncertain.
         Trade very conservatively — resolution risk is material.

    Combined and capped at 1.35x.
    German FPTP (UK-style): 1.20 × 1.20 = 1.35x cap.
    PR majority question in Germany: 0.70 × 1.20 = 0.84x — correctly skeptical.
    Brazilian runoff: 1.15 × 1.15 = 1.32x — strong, data-backed edge.
    """
    q = question.lower()

    # Factor 1: electoral system type
    if any(w in q for w in ("first past the post", "fptp", "constituency",
                             "seats in parliament", "parliamentary majority",
                             "wins the election", "general election",
                             "wins a majority", "wins the seat")):
        # Disambiguate: FPTP vs PR majority
        if any(w in q for w in ("germany", "bundestag", "netherlands", "sweden",
                                 "riksdag", "norway", "spain", "israel", "knesset",
                                 "proportional", "list seat", "coalition required")):
            system_mult = 0.70  # PR system — majority is near-impossible
        else:
            system_mult = 1.20  # FPTP — polling leads amplify strongly into wins

    elif any(w in q for w in ("runoff", "second round", "second-round",
                               "tour de scrutin", "segundo turno",
                               "ballotage", "deux tours")):
        system_mult = 1.15  # Post-round-1 runoff polling very accurate (<3% error)

    elif any(w in q for w in ("referendum", "plebiscite", "ballot measure",
                               "popular vote on", "vote on independence",
                               "vote on membership", "vote on joining")):
        system_mult = 1.10  # Binary, well-polled, clean resolution criteria

    elif any(w in q for w in ("snap election", "early election", "dissolve parliament",
                               "dissolution", "called early", "called snap")):
        system_mult = 0.85  # No steady polling baseline — volatile

    elif any(w in q for w in ("coalition", "form government", "coalition government",
                               "govern with", "coalition partner", "junior partner",
                               "governing coalition")):
        system_mult = 0.80  # Post-election negotiation — genuinely unpredictable

    elif any(w in q for w in ("no confidence", "confidence vote", "leadership challenge",
                               "leadership contest", "oust", "remove prime minister",
                               "resign", "step down")):
        system_mult = 0.80  # Parliamentary arithmetic + timing of defections

    elif any(w in q for w in ("bundestag majority", "riksdag majority", "knesset majority",
                               "win majority in", "absolute majority", "supermajority",
                               "two-thirds majority")):
        system_mult = 0.70  # PR majority — structurally rare regardless of wording

    else:
        system_mult = 1.0

    # Factor 2: regional information lag
    if any(w in q for w in ("germany", "german", "bundestag", "france", "french",
                             "assemblée", "netherlands", "dutch", "sweden", "swedish",
                             "riksdag", "norway", "denmark", "finland", "austria",
                             "belgium", "switzerland", "spain", "italy", "poland",
                             "chancellor", "président", "premier ministre")):
        region_mult = 1.20  # Wahlrecht.de / Politico Europe — daily lag vs Polymarket

    elif any(w in q for w in ("brazil", "brazilian", "mexico", "mexican",
                               "argentina", "colombia", "chile", "peru",
                               "venezuela", "ecuador", "latin america",
                               "segundo turno", "datafolha", "quaest")):
        region_mult = 1.15  # DataFolha/Quaest/CIEP daily — English lag substantial

    elif any(w in q for w in ("india", "indian", "lok sabha", "indonesia",
                               "indonesian", "philippines", "philippine",
                               "bangladesh", "pakistan", "sri lanka",
                               "south korea", "taiwan", "japan", "japanese")):
        region_mult = 1.15  # Local-language polls + exit polls hours ahead of English

    elif any(w in q for w in ("uk", "united kingdom", "britain", "british",
                               "australia", "australian", "canada", "canadian",
                               "new zealand")):
        region_mult = 1.05  # English-language but less Polymarket attention than US

    elif any(w in q for w in ("africa", "nigeria", "kenya", "ghana", "senegal",
                               "ethiopia", "zimbabwe", "congo", "cameroon",
                               "mali", "burkina", "guinea", "sudan")):
        region_mult = 0.75  # Low polling quality + institutional volatility + coup risk

    else:
        region_mult = 1.0

    return min(1.35, system_mult * region_mult)


def compute_signal(market) -> tuple[str | None, float, str]:
    """
    Returns (side, size, reasoning) or (None, 0, skip_reason).

    Conviction-based sizing with electoral system type and regional lag adjustment:
    - Base conviction scales linearly with distance from threshold
    - election_bias() corrects for two orthogonal edges: system type predictability
      (FPTP amplification 1.20x vs PR majority impossibility 0.70x) and regional
      information lag (German/French/Scandinavian elections lag Polymarket 12-36h)
    - Result capped at 1.0 so size never exceeds MAX_POSITION
    - MIN_TRADE floor prevents trivially small orders near the boundary

    Remix: feed Wahlrecht.de / Politico Europe polling averages into p to trade
    the gap between daily aggregator updates and Polymarket's stale prices directly.
    """
    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 = election_bias(q)

    if p <= YES_THRESHOLD:
        # conviction=0 at threshold boundary, conviction=1 at p=0 — scaled by election 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)"
    print(f"[polymarket-global-elections-trader] mode={mode} max_pos=${MAX_POSITION} min_vol=${MIN_VOLUME} max_spread={MAX_SPREAD:.0%} min_days={MIN_DAYS}")

    client  = get_client(live=live)
    markets = find_markets(client)
    print(f"[polymarket-global-elections-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-global-elections-trader] done. {placed} orders placed.")


if __name__ == "__main__":
    ap = argparse.ArgumentParser(description="Trades Polymarket prediction markets on elections, referendums, and democratic events worldwide.")
    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)