polymarket-weather-trader — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited polymarket-weather-trader (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Trade temperature markets on Polymarket using NOAA forecast data.
🚨 Framework, not a production trading system. Read DISCLAIMER.md before connecting to a wallet with real funds.
Template skill. Defaults to dry-run mode (no real money). The --live flag is a deliberate single-command opt-in for real-money execution. Configure tunables (entry/exit thresholds, locations, etc.) via env vars listed below.This skill executes real-money trades on Polymarket only when the --live flag is passed AND the human's wallet is linked to their Simmer account. Trading is bounded by default:
python weather_trader.py (no flag) shows opportunities but executes no trades. The --live flag is required for real-money execution. There is no "auto-graduate" path.TRADING_VENUE=sim to trade Simmer's $SIM virtual currency at real prices — useful for validating the strategy without USDC exposure.SIMMER_WEATHER_MAX_POSITION_USD defaults to $2.00 per trade. Configurable via env var, capped at the user's dashboard-set platform per-trade limit.SIMMER_WEATHER_SLIPPAGE_MAX, default 15%), time-decay (SIMMER_WEATHER_MIN_HOURS_TO_RESOLVE, default 2h), and resolved-market status before every order. Disable only with --no-safeguards (not recommended).SIMMER_WEATHER_EXIT_THRESHOLD (default 0.45), or via client.cancel_order() / a manual sell. ENTRY_THRESHOLD is an upper bound (buy below). If you raise it above the exit default (e.g. entry 0.50 vs exit 0.45), the skill will try to sell the same position on the next cycle — raise EXIT_THRESHOLD too, or own exits yourself.If anything above isn't clear, stop and ask the user before passing --live.
Weather market outcomes are discrete: a temperature bucket ("34-35°F") either matches the actual high on resolution day or it doesn't. The strategy works when the NOAA forecast is more accurate than what the market has priced in.
Test before going live. The $SIM venue gives you a fully virtual sandbox at real market prices — recommended before any --live run.
Risk monitor. Stop-loss and take-profit thresholds are user settings (configurable at simmer.markets/dashboard → Settings → Auto Risk Monitor), shared across all skills under that user account. Per-position overrides via client.set_monitor(market_id, side, stop_loss_pct=..., take_profit_pct=...).
External wallet users: monitors emit alerts via the briefing endpoint — your agent must be running for sells to execute. Managed wallet users: server executes directly.
For wallet setup, see docs.simmer.markets/wallets.
Behaviour changes by version: CHANGELOG.md.
Required environment:
SIMMER_API_KEY — get from simmer.markets/dashboard → SDK tabWALLET_PRIVATE_KEY — Polymarket wallet private key (the SDK signs orders client-side)Then pip install --upgrade simmer-sdk (>=0.13.0) and configure tunables below.
| Setting | Environment Variable | Default | Description |
|---|---|---|---|
| Trading venue | TRADING_VENUE | polymarket | Venue to trade on. Set sim for paper trading. |
| Entry threshold | SIMMER_WEATHER_ENTRY_THRESHOLD | 0.15 | Upper bound — buy when price is below this |
| Min entry price | SIMMER_WEATHER_MIN_ENTRY_PRICE | 0 | Lower bound — skip lottery tickets below this (0 = off), e.g. 0.15 to skip sub-15¢ tickets. Must be below the entry threshold. |
| Min hours to resolve | SIMMER_WEATHER_MIN_HOURS_TO_RESOLVE | 2 | Skip entries if the market resolves in fewer than this many hours. Entry-only; exits keep a fixed 2h floor. Discovery looks ahead max(this, 48h) calendar days — raise it and the scan widens; a 24h floor still sees +1/+2 day markets in a morning heartbeat. |
| Exit threshold | SIMMER_WEATHER_EXIT_THRESHOLD | 0.45 | Sell when price above this. Raise this if you raise entry above 0.45, or the skill will self-exit. |
| Max position | SIMMER_WEATHER_MAX_POSITION_USD | 2.00 | Maximum USD per trade |
| Max trades/run | SIMMER_WEATHER_MAX_TRADES_PER_RUN | 5 | Maximum trades per scan cycle |
| Max buys/market | SIMMER_WEATHER_MAX_BUYS_PER_MARKET | 1 | Cap on buy-fills into the same market (bucket), checked against held positions before entry. 1 = one entry then hold; raise to keep DCA-ing into an underpriced bucket; 0 disables the cap (unbounded DCA). |
| Locations | SIMMER_WEATHER_LOCATIONS | NYC | Comma-separated cities (NYC, Chicago, Seattle, Atlanta, Dallas, Miami, Austin, Houston, Denver, Beijing, Shanghai, Guangzhou, Shenzhen, Chengdu, Chongqing, Wuhan, Qingdao, Zhengzhou, Singapore, Kuala Lumpur, Manila, Busan, Toronto, Buenos Aires, Sao Paulo, Mexico City, Cape Town, Helsinki, Jeddah, Warsaw, Paris, Panama City) |
| Binary only | SIMMER_WEATHER_BINARY_ONLY | false | Skip range-bucket events (e.g., "34-35°F"), only trade binary yes/no markets |
| Smart sizing % | SIMMER_WEATHER_SIZING_PCT | 0.05 | % of balance per trade |
| Slippage max | SIMMER_WEATHER_SLIPPAGE_MAX | 0.15 | Skip trades with slippage above this (0.15 = 15%) |
| Min liquidity | SIMMER_WEATHER_MIN_LIQUIDITY | 0 | Skip markets with liquidity below this USD amount (0 = disabled) |
| Vol targeting | SIMMER_WEATHER_VOL_TARGETING | false | Enable volatility targeting for dynamic position sizing |
| Target vol | SIMMER_WEATHER_TARGET_VOL | 0.20 | Target annualized volatility (0.20 = 20%) |
| Vol max leverage | SIMMER_WEATHER_VOL_MAX_LEVERAGE | 2.0 | Max scale-up multiplier in calm markets |
| Vol min alloc | SIMMER_WEATHER_VOL_MIN_ALLOC | 0.2 | Min allocation floor in volatile markets (0.2 = 20%) |
| Vol EWMA span | SIMMER_WEATHER_VOL_SPAN | 10 | EWMA span for vol calculation (lower = more responsive) |
| Order type | SIMMER_WEATHER_ORDER_TYPE | GTC | GTC (limit, waits for fill) or FAK (cancel if not filled). GTC recommended. |
| Replay forecast archive | SIMMER_REPLAY_FORECASTS | (none) | Replay-only. JSON {station: {YYYY-MM-DD: {high, low}}}. Live NOAA is never used under replay. If unset, loads fixtures/replay_forecasts.json when you add that file (not committed). .sample.json is never auto-loaded. |
Legacy env var aliases (still accepted for backwards compatibility): SIMMER_WEATHER_ENTRY, SIMMER_WEATHER_EXIT, SIMMER_WEATHER_MAX_POSITION, SIMMER_WEATHER_MAX_TRADES
Supported locations (city-name filter applied to market questions): NYC, Chicago, Seattle, Atlanta, Dallas, Miami, Austin, Houston, Denver, Tel Aviv, Munich, London, Tokyo, Seoul, Ankara, Lucknow, Wellington, Madrid, Milan, Amsterdam, Taipei, Beijing, Shanghai, Guangzhou, Shenzhen, Chengdu, Chongqing, Wuhan, Qingdao, Zhengzhou, Singapore, Kuala Lumpur, Manila, Busan, Toronto, Buenos Aires, Sao Paulo, Mexico City, Cape Town, Helsinki, Jeddah, Warsaw, Paris, Panama City. The actual oracle station is parsed per-market from resolution_criteria — see "Resolution-source routing" below.
Polymarket weather markets carry a resolution_criteria field that names the exact station the market resolves on (e.g. "Chicago O'Hare Intl Airport Station" with wunderground.com/.../KORD). v1.21.0+ parses that text per-market and routes to the matching forecast station instead of a city default. If a market names a station the skill doesn't know, the event is skipped with a log line. Add new stations to STATION_ID_TO_NOAA (US) or INTERNATIONAL_STATION_COORDS (international) in weather_trader.py to extend coverage — PRs welcome.
from simmer_sdk import SimmerClient
client = SimmerClient.from_env(venue="polymarket", live=True)from_env() (added in simmer-sdk 0.13.0) reads SIMMER_API_KEY from the environment and raises RuntimeError with a dashboard pointer if unset. If OWS_WALLET is set, it auto-routes through the OpenClaw shared wallet.
# Check account balance and positions
python scripts/status.py
# Detailed position list
python scripts/status.py --positionsAPI Reference:
https://api.simmer.marketsAuthorization: Bearer $SIMMER_API_KEYGET /api/sdk/portfolioGET /api/sdk/positions# Dry run (default — shows opportunities, no trades)
python weather_trader.py
# Execute real trades
python weather_trader.py --live
# With smart position sizing (uses portfolio balance)
python weather_trader.py --live --smart-sizing
# Check positions only
python weather_trader.py --positions
# View config
python weather_trader.py --config
# Disable safeguards (not recommended)
python weather_trader.py --no-safeguards
# Disable trend detection
python weather_trader.py --no-trends
# Enable volatility targeting (dynamic sizing based on market vol)
python weather_trader.py --live --smart-sizing --vol-targeting
# Quiet mode — only output on trades/errors (ideal for high-frequency runs)
python weather_trader.py --live --quiet90-day real-capital P&L on Simmer is the lock. This gate is the fast filter before more real capital. Dogfood's MIN_HOURS=12 canary is a separate seat — do not change it here.
Reuse the existing replay harness. Do not invent a second one.
# Pinned gate (no tape, no network, no API key)
python skills/polymarket-weather-trader/scripts/run_backtest_gate.py
# Same tests, direct
python -m pytest skills/polymarket-weather-trader/tests/test_replay_discovery.py -q
# Full-tape KEEP/KILL — build a window archive, copy it (uncommitted), then replay.
# Auto-load is fixtures/replay_forecasts.json only. .sample.json is shape-only
# and is never loaded.
python skills/polymarket-weather-trader/scripts/build_replay_forecast_archive.py \
--start YYYY-MM-DD --end YYYY-MM-DD --out /tmp/replay_forecasts.json
cp /tmp/replay_forecasts.json \
skills/polymarket-weather-trader/fixtures/replay_forecasts.json
# SIMMER_WEATHER_LOCATIONS defaults to "NYC" — a tape covering other cities
# (Seoul, London, Chicago, ...) will show 0 entries for every one of them
# unless you widen this list to match the tape's city mix (SIM-5484). That
# is expected scoping, not a station/forecast bug — see the note below.
#
# A plain `export SIMMER_WEATHER_LOCATIONS=...` does NOT reach the skill:
# the replay harness builds the bundle subprocess env from a strict
# allowlist (SIM-5067) that this var is not on, so it is silently stripped
# and the run measures NYC regardless. Use --set on the entrypoint instead
# — it writes config.json next to weather_trader.py, which load_config()
# reads before env vars and which survives the per-tick bundle copy.
#
# Name the tape's actual cities, not a generic US list. E.g. for an
# April tape weighted Seoul/Hong Kong/London/Shanghai/NYC/Paris: Seoul,
# London, Paris and Shanghai are mapped (INTERNATIONAL_STATION_COORDS) and
# will enter; Hong Kong has no station mapping anywhere in the skill and
# cannot enter no matter what's in this list.
simmer backtest skills/polymarket-weather-trader \
--entrypoint weather_trader.py --t0 YYYY-MM-DD --t1 YYYY-MM-DD \
--cadence 12h --q temperature --min-volume 0 \
--args "--live --quiet --set locations=NYC,Chicago,Seattle,Atlanta,Miami,Austin,Houston,Denver,Seoul,London,Paris,Shanghai"
# Then read the KEEP/KILL table below, plus the per-city entry count the
# report prints — a KEEP/PIVOT read needs coverage across the mapped
# international cities, not just NYC. Optional in-process / pytest:
# export SIMMER_REPLAY_FORECASTS=/tmp/replay_forecasts.json| Verdict | What the backtest / replay outcome means for the money path |
|---|---|
| FIX | Path is broken or the tape cannot evaluate the skill. Do not add capital. Repair: discovery 422 / fail-open empty listing; wall-clock horizon under replay; yes_price ignored (silent 0.50); every event skipped for missing resolution_criteria; live NOAA/Open-Meteo under replay (look-ahead); preflight WALLET_UNVERIFIED blocking SimState fills; 0 temperature markets on a high-volume slice (--q temperature, lower --min-volume); 0 entries because the forecast archive is missing, empty, or does not cover the tape dates (SIMMER_REPLAY_FORECASTS / fixtures/replay_forecasts.json — missing forecast plane, not "no edge"). |
| not a defect | Every entry lands in one city (default NYC) on a multi-city tape. SIMMER_WEATHER_LOCATIONS defaults to "NYC" — that is the skill honoring its configured scope, not a station/parsing bug. Widen it via --set locations=... (a plain export is stripped by the replay harness allowlist) to the tape's full city mix before judging KEEP/KILL from entry count (SIM-5484). Hong Kong specifically never enters on any tape — the skill has no station mapping for it, not a defect. |
| KILL | Pinned pytest gate fails, or the path is green on a weather-capable tape, evals > 0, and the skill still cannot reach execute_trade when a forecast is injected or loaded from the archive, or after the path works, P&L after costs is clearly ≤ 0 on an honest (not live-NOAA) forecast. Do not add more real capital. |
| KEEP | Full-tape simmer backtest ... --q temperature with evals > 0 and entries > 0 and an honest archive (not live NOAA). Pinned unit tests passing is a path check only — not KEEP. Provisional; the 90-day Simmer P&L lock still decides scale-up. |
A green full-tape run that places 0 trades because NOAA is correctly dark is FIX (forecast plane), not KILL. Copy a real window-covering archive to fixtures/replay_forecasts.json, then re-run. The committed .sample.json is not loaded.
Each cycle the script:
max(MIN_HOURS_TO_RESOLVE, 48h))sdk:weather for trackingWith --smart-sizing, position size is calculated as:
SIMMER_WEATHER_SIZING_PCT)With --vol-targeting, position sizes are dynamically adjusted based on realized market volatility:
position_size = base_size × clamp(target_vol / realized_vol, min_alloc, max_leverage)Before trading, the skill checks:
SIMMER_WEATHER_SLIPPAGE_MAX)SIMMER_WEATHER_MIN_HOURS_TO_RESOLVE hours (default 2). Discovery looks ahead max(this, 48h) so raising the floor does not starve morning heartbeats.SIMMER_WEATHER_MIN_ENTRY_PRICE (default 0 = off) is the other side of the entry-price check, not this list — --no-safeguards does not disable it.
Disable the flip-flop / slippage / time-decay / resolved checks with --no-safeguards (not recommended).
All trades are tagged with source: "sdk:weather". This means:
sdk:weather are excluded from generic copytrade sells."Safeguard blocked: Severe flip-flop warning" — you've been changing direction too much on this market; wait before trading again.
"Slippage too high" — market is illiquid; reduce position size or skip.
"Resolves in Xh - too soon" — market resolving sooner than SIMMER_WEATHER_MIN_HOURS_TO_RESOLVE (default 2h). Raise the env to skip resolve-day entries. Discovery already looks ahead max(MIN_HOURS, 48h), so a morning run with MIN_HOURS=24 can still take +1/+2 day markets. Same-day buckets will always fail a 24h floor before they have 24h left.
"Price $X.XX below min entry" — mid is below SIMMER_WEATHER_MIN_ENTRY_PRICE. Lottery-ticket floor; default is off (0).
"No weather markets found" — weather markets may not be active (seasonal).
`simmer backtest` / "Failed to fetch markets from Simmer API" — replay does not implement tags or status (422 since sdk 0.25.3/0.25.4). v1.23.9+ uses q=temperature under replay. A fetch failure now fails the tick (failed_ticks, not a clean 0-eval). If the fetch succeeds and you still see 0 weather markets, the HF volume slice likely has none — default --min-volume / top-volume selection is a tape follow-up, not a skill bug. Replay listings omit resolution_criteria; v1.23.11+ falls back to the city station table only when criteria is missing (Dallas excluded; present-but-unreadable still skips). Live NOAA is never called under replay.
A full-tape run with 0 entries and NOAA dark is FIX until a real archive covers the tape dates. Build one with scripts/build_replay_forecast_archive.py, then copy it to fixtures/replay_forecasts.json before simmer backtest (harness strips host env; that user file is copied with the bundle). Shape: { "_meta": {…}, "KLGA": { "2026-04-30": { "high": 72, "low": 50, "leads": { "1": {…}, "2": {…}, "3": {…} } } } } — top-level high/low is lead 1; _meta is ignored. Hand-built files may omit leads. fixtures/replay_forecasts.sample.json is that shape only — invented test temps, never auto-loaded. A set-but-missing path fails the tick. Do not treat a 0-entry tape as no-edge. See Backtest gate above.
"External wallet requires a pre-signed order" — WALLET_PRIVATE_KEY is not set. Fix: export WALLET_PRIVATE_KEY=0x<your-polymarket-wallet-private-key>. The SDK signs orders automatically when this env var is present — do not attempt to sign orders manually.
"Balance shows $0 but I have funds on Polygon" — Polymarket V2 (live 2026-04-28) uses pUSD (PolyUSD, 1:1 backed by USDC.e). Migrate at simmer.markets/dashboard (~30s). Full guide: docs.simmer.markets/v2-migration.
"API key invalid" — get a new key from simmer.markets/dashboard → SDK tab.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.