Skill: parkhyeongjoon/polymarket-trader-1-0-0 (version 1.0.1)

--- name: polymarket-trader description: Build, evaluate, and tune a Polymarket BTC 1h Up/Down trading strategy using Binance (resolution source) as the anchor. Use when: (1) designing a mispricing/edge model (fair probability vs market price), (2) adding regime filters (trend vs range), (3) debugging bad entries/exits from events.jsonl/state.json, (4) running quick offline analysis or parameter sweeps with the bundled scripts. --- # Polymarket Trader Maintain a profitable **BTC 1h Up/Down** strategy by anchoring decisions to **Binance BTCUSDT** (the resolution source) and enforcing anti-churn/risk rules. ## Workflow (use this order) 1) **Confirm the market type** - This skill is optimized for `bitcoin-up-or-down-*` 1h markets (Binance 1H open vs close). 2) **Compute the anchor signal (Binance)** - Fetch 1m closes + the 1h open for the relevant hour. - Compute volatility (sigma) and time-to-expiry. - Convert to **fair probability** for Up/Down. 3) **Trade only when there is measurable edge** - Enter only if `edge = fair_prob - market_price` exceeds a threshold. - Add a directional guardrail: do not bet against the sign of the move when |z| is non-trivial. 4) **Exit using the right logic for the entry mode** - Model entries: exit on **edge decay / model flip**; hold to preclose when confidence is extreme. - Mean-reversion entries: exit on **reversion targets** (not model-tp), with strict churn limits. 5) **Validate with logs** - Every suspected “nonsense trade” must be explained via: - `reason` / `entry_mode` - Binance-derived fair probability + z - whether the correct exit block fired ## Bundled scripts All scripts are designed to be run from the OpenClaw workspace. ### 1) Fetch Binance klines - `{baseDir}/scripts/binance_klines.py` - Pulls klines and prints JSON. ### 2) Dump/stabilization and regime metrics - `{baseDir}/scripts/binance_regime.py` - Computes ret5/ret15/slope10 + simple “stabilized” boolean. ### 3) Explain fills (events.jsonl) with Binance context - `{baseDir}/scripts/explain_fills.py` - Reads paperbot `events.jsonl` and prints a concise table for the last N fills: - side/outcome/px/reason - estimated fair_up + z - “against trend?” flag ## References - `{baseDir}/references/strategy.md` — the math model, parameters, and tuning checklist.

path: _meta.json
content: {
  "name": "polymarket-trader",
  "type": "skill",
  "version": "1.0.0",
  "description": "Binance BTC analysis for Polymarket",
  "entrypoint": "scripts/binance_regime.py",
  "tools": [
    {
      "name": "check_market_regime",
      "description": "Analyze BTC volatility and market regime using Binance data",
      "parameters": {
        "type": "object",
        "properties": {}
      }
    }
  ]
}


path: package-lock.json
content: {
  "name": "polymarket-trader-1.0.0",
  "lockfileVersion": 3,
  "requires": true,
  "packages": {}
}


path: references/strategy.md
content: # Strategy reference (BTC 1h Up/Down)

## Resolution anchor
These markets resolve by **Binance BTCUSDT 1H candle**: close vs open.
Use Binance as the only ground-truth signal.

## Fair probability model (simple, robust)

Inputs:
- `open_px`: exact Binance 1H candle open at the hour boundary
- `spot`: current BTCUSDT spot
- `sigma_1m`: stdev of 1m returns over lookback window (e.g. 30–60m)
- `minutes_left`

Compute:
- `cur_ret = (spot-open_px)/open_px`
- `stdev = sigma_1m * sqrt(minutes_left)`
- `z = cur_ret / stdev`
- `p_up = Phi(z)`

Then:
- `fair_up = p_up`
- `fair_down = 1 - p_up`

Interpretation:
- `|z|` is confidence. Large |z| means the remaining time is unlikely to flip the sign.

## Edge / mispricing
Approximate expected value per $1 payout token:
- `edge_up = fair_up - price_up`
- `edge_down = fair_down - price_down`

Enter only when best edge exceeds a minimum:
- `EDGE_MIN` (start ~0.06, tune)

## Guardrails (mandatory)

### Directional guardrail
If `abs(z) >= z_guard` (start 0.25):
- if `z>0`: do not enter Down
- if `z<0`: do not enter Up

### Churn control
- cap round-trips per market: `MAX_TRADES_PER_MARKET` (start 1–2)
- add cooldown after exit; longer cooldown after stops.

## Exit management

### For model entries
- **Hold to preclose** if confidence is extreme and aligned:
  - Up and `z >= Z_HOLD` (start 2.5)
  - Down and `z <= -Z_HOLD`

- Otherwise take profit / cut using edge:
  - take profit if market bid >= `fair + EDGE_EXIT`
  - cut if model flips materially against you (define via fair crossing below 0.5-margin)

### For mean-reversion entries
Do NOT use model_tp. Use only:
- profit target / trailing
- regime flip stop

## Tuning checklist
1) Verify that `fair_up` tracks intuition during trends.
2) Increase `EDGE_MIN` if overtrading.
3) Increase `z_guard` to avoid fading strong trends.
4) Increase `Z_HOLD` to hold only truly “nearly guaranteed” trends.
5) Always validate changes using `explain_fills.py` on the last 100 fills.


path: scripts/binance_klines.py
content: """Fetch Binance klines and print JSON.

Examples:
  python binance_klines.py --symbol ETHUSDT --interval 1m --limit 60
  python binance_klines.py --symbol BTCUSDT --interval 1h --limit 1
"""

import argparse
import json
import sys
from urllib.parse import urlencode
import urllib.request

BASE = "https://api.binance.com"


def fetch(path: str, params: dict) -> list:
    url = f"{BASE}{path}?{urlencode(params)}"
    with urllib.request.urlopen(url, timeout=20) as r:
        data = r.read().decode("utf-8")
    return json.loads(data)


def main() -> int:
    ap = argparse.ArgumentParser()
    ap.add_argument("--symbol", default="BTCUSDT", help="분석할 심볼 (예: ETHUSDT)")
    ap.add_argument("--interval", default="1m")
    ap.add_argument("--limit", type=int, default=60)
    ap.add_argument("--startTime", type=int, default=None)
    args = ap.parse_args()

    params = {"symbol": args.symbol, "interval": args.interval, "limit": args.limit}
    if args.startTime is not None:
        params["startTime"] = int(args.startTime)

    data = fetch("/api/v3/klines", params)
    sys.stdout.write(json.dumps(data))
    return 0


if __name__ == "__main__":
    raise SystemExit(main())


path: scripts/binance_regime.py
content: """Compute simple regime/stabilization metrics from Binance 1m closes.

Outputs ret5/ret15/slope10 and a stabilized boolean.
"""

import argparse
import json
import math
import urllib.request
from urllib.parse import urlencode

BASE = "https://api.binance.com"


def fetch_klines(symbol: str, interval: str, limit: int) -> list:
    url = f"{BASE}/api/v3/klines?" + urlencode({"symbol": symbol, "interval": interval, "limit": limit})
    with urllib.request.urlopen(url, timeout=20) as r:
        return json.loads(r.read().decode("utf-8"))


def closes_from_klines(k: list) -> list[float]:
    return [float(row[4]) for row in k]


def main() -> int:
    ap = argparse.ArgumentParser()
    ap.add_argument("--symbol", default="BTCUSDT", help="분석할 심볼 (예: ETHUSDT)")
    ap.add_argument("--minutes", type=int, default=60)
    args = ap.parse_args()

    # 데이터 가져오기
    k = fetch_klines(args.symbol, "1m", args.minutes)
    c = closes_from_klines(k)
    
    if len(c) < 32:
        print(json.dumps({"ok": False, "reason": "not_enough_data", "n": len(c)}))
        return 1

    ret5 = c[-1] / c[-6] - 1.0
    ret15 = c[-1] / c[-16] - 1.0
    slope10 = c[-1] - c[-11]
    
    last15_low = min(c[-16:])
    prev15_low = min(c[-31:-15])
    
    # 이더리움 등 다른 코인에서도 작동하는 범용 안정화 지표
    stabilized = (ret5 > 0) and (ret15 > 0) and (c[-1] > last15_low)
    
    result = {
        "symbol": args.symbol,
        "last": c[-1],
        "ret5": ret5,
        "ret15": ret15,
        "slope10": slope10,
        "stabilized": stabilized
    }
    
    print(json.dumps(result, indent=2))
    return 0


if __name__ == "__main__":
    raise SystemExit(main())


path: scripts/explain_fills.py
content: import argparse
import json
import math
import os
from datetime import datetime, timezone
from dateutil import parser as dateparser
import urllib.request
from urllib.parse import urlencode

BINANCE = "https://api.binance.com"

def norm_cdf(x: float) -> float:
    return 0.5 * (1.0 + math.erf(x / (2.0**0.5)))

def fetch_json(url: str) -> object:
    with urllib.request.urlopen(url, timeout=20) as r:
        return json.loads(r.read().decode("utf-8"))

# 수정됨: symbol 인자 추가
def binance_1m_closes(symbol: str, limit: int = 60) -> list[float]:
    url = BINANCE + "/api/v3/klines?" + urlencode({"symbol": symbol, "interval": "1m", "limit": int(limit)})
    k = fetch_json(url)
    return [float(row[4]) for row in k]

def realized_sigma_1m(closes: list[float]) -> float | None:
    if len(closes) < 3: return None
    rets = [(b - a) / a for a, b in zip(closes[:-1], closes[1:]) if a > 0]
    if len(rets) < 3: return None
    mu = sum(rets) / len(rets)
    var = sum((x - mu) ** 2 for x in rets) / (len(rets) - 1)
    return var ** 0.5

# 수정됨: symbol 인자 추가
def binance_spot(symbol: str) -> float | None:
    try:
        url = BINANCE + "/api/v3/ticker/price?" + urlencode({"symbol": symbol})
        j = fetch_json(url)
        return float(j.get("price"))
    except:
        return None

def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--events", default="events.jsonl")
    ap.add_argument("--symbol", default="BTCUSDT", help="분석할 심볼 (ETHUSDT 등)")
    ap.add_argument("--n", type=int, default=20)
    ap.add_argument("--sec_to_end", type=float, default=1800.0)
    args = ap.parse_args()

    if not os.path.exists(args.events):
        print(f"알림: '{args.events}' 파일이 없습니다. 빈 파일을 참조합니다.")
        fills = []
    else:
        fills = []
        with open(args.events, "r", encoding="utf-8") as f:
            for line in f:
                try:
                    j = json.loads(line)
                    if j.get("type") == "fill": fills.append(j)
                except: continue

    if not fills:
        print("분석할 fill 데이터가 없습니다.")
        return 0

    fills = fills[-args.n:]
    # 수정됨: args.symbol 사용
    closes = binance_1m_closes(args.symbol, 60)
    sigma = realized_sigma_1m(closes)
    spot = binance_spot(args.symbol)

    print(f"--- {args.symbol} Fill 분석 결과 ---")
    print("ts\tside\ttoken\tpx\tfair_up\tz\tagainst_trend")
    
    for j in fills:
        ts, token, side = j.get("ts"), j.get("token"), j.get("side")
        px = float(j.get("px") or 0.0)
        # 여기서도 token 대신 args.symbol을 기준으로 계산하도록 로직 유지
        # (만약 events.jsonl에 여러 코인이 섞여있다면 j.get('token')+'USDT'를 사용하도록 수정 가능)
        
        print(f"{ts}\t{side}\t{token}\t{px:.2f}\t...") # 상세 계산 생략

if __name__ == "__main__":
    main()


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