CtrlK
BlogDocsLog inGet started
Tessl Logo
SkillPublic

binance-prediction-updown

Scaffold for assembling prediction-market strategies on Binance Agentic Wallet, for recurring BTC Up/Down rounds. Wires round enumeration, market screening, two-step ordering, fill and settlement confirmation and redemption into one runnable pipeline, and exposes every probability judgement as a hook. Ships no strategy of its own. Trigger on: up down prediction, BTC up or down, prediction market bot, hourly prediction rounds, trade up/down automatically, prediction market strategy, prediction calibration, redeem winnings automatically. NOT for: single manual predictions (use binance-agentic-wallet), on-chain token copy trading (binance-onchain-copy-trader), signal queries (binance-trading-signal).

72

Quality

90%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide
SecuritybySnyk

Medium

Suggest reviewing before use

SKILL.md
Quality
Evals
Security

Binance Prediction Up/Down

A scaffold for trading recurring BTC Up/Down rounds: a round scheduler, screening gates, a decision contract that compares your probability against the quote, order and settlement handling, redemption, and two offline evaluation harnesses.

Read this first

Every risk control, stake size and threshold here is a suggestion, not a safeguard.

  • This skill ships no strategy. The bundled hook is an example that makes the pipeline run; the numbers it reads are ones you measure and supply. The reference signal documented under "Strategy hooks" nets roughly +0.65% per round against a ±1.9pp error bar — a worked example of the bar, not a configuration to copy.
  • A hit rate is not an edge. If the market already prices the state your signal keys on, a 58% hit rate earns zero. Only step 3 of the walkthrough can rule that out.
  • Positions can expire worthless; max loss per round is the stake. There is no stop-loss to place — the position resolves itself.

Dependencies

SkillUsed for
binance-agentic-walletprediction *, wallet *

Candles come from the public Binance market-data endpoint (api/v3/klines) — no key, no session.

baw --version
baw cli-check --required-version "$(grep -m1 requiredCliVersion SKILL.md | tr -dc '0-9.')" --json

Not published. This skill lives only where you installed it. binance-agentic-wallet comes from https://github.com/binance/binance-skills-hub; this one does not ship from there yet.

Getting started

Steps 1 and 3 cost nothing and either can kill the idea. Do both before live mode.

1 · Backtest offline — no login, no config, no money.

python3 backtest.py fetch --symbol BTCUSDT --interval 1h
python3 backtest.py eval  --interval 1h --hurdle 0.521

Use the interval of the rounds you will trade (5m/15m/1h/1d) — the same signal measures very differently across them. eval prints a hit_table to paste into the config.

2 · Write the config — the first run writes an empty template and exits.

python3 updown.py                        # writes the template
$EDITOR $UPDOWN_HOME/config.json         # mode: dry-run, expected_slippage_bps: 0

3 · Collect quotes, then test whether the signal is already priced in.

python3 updown.py                        # dry-run: decides and records, places nothing
python3 backtest.py pricedin             # reads it back
streak  dir     n  implied P(up)  realized P(up)     gap   read as
      3    1    64          0.421           0.428  +0.007   PRICED IN — no edge here

A gap near zero where your signal fires means the market already knows — stop there. Needs a few dozen occurrences: at 1h with min_streak 3 that is several days.

4 · Check the plumbing — tail -f $UPDOWN_HOME/updown.log. Expect long stretches of nothing; the HEARTBEAT tally, not the absence of orders, is how you tell working from stuck.

5 · Write your own hook — rounds.jsonl has probability, both asks, overround, breakeven and edge per round.

6 · Go live — minimum stake until the money path proves itself, then set expected_slippage_bps from the ALL-IN lines your fills log.

Capability map

StageCommand
Enumerate roundsprediction market list --l1Category crypto --l2Category up-down
Book / last priceprediction market order-book · market last-trade-price
Price, place, cancelprediction trade quote · place-order · cancel
Positions, settlementprediction position list · settled-history · pnl
Redeemprediction trade redeem
Order statusprediction order history
Guardrail readswallet balance · left-quota · settings

Round lifecycle

Twelve topics are listed at any time — three symbols × four cadences (5 min, 15 min, 1 hour, daily), each with its own book, fee and price feed. symbol and timeframe are required config; one daemon trades exactly one combination.

enumerate → gates → probability hook (cannot see quotes) → compare to quote
  → guards → quote → place → confirm fill → settlement → redeem

Two differences from a spot pipeline:

  • No stop-loss and no exit plan. The position resolves itself; the whole decision is at entry.
  • Winning does not pay you. Payouts sit in PENDING_CLAIM until redeemed. The daemon sweeps on a timer; PENDING_CLAIM should be empty in steady state.

Which feed settles your round

CadenceFeed
5 min, 15 minCHAINLINK
1 hour, dailyBINANCE spot

Published per topic as variantData.priceFeedProvider — read it, do not assume. It decides which series your backtest must use. The topic also publishes startPrice, so an outcome can be checked independently once the round closes.

Decision contract

The quote is the implied probability. Three branches, and the third is the common one:

BranchCondition
Buy UpP(up) − up_ask − cost > edge_threshold
Buy Down(1 − P(up)) − down_ask − cost > edge_threshold
No tradeeverything else

Breakeven hit rate is the quoted price plus costs — buying Up at 0.55 with a 2% fee needs better than 57%. Computed and logged every round by the mechanism layer, not left to the strategy.

Cost = topic feeRateBps + expected_slippage_bps. The order's slippage_bps is a tolerance, not a cost; charging it taxes every round for a worst case that rarely happens.

Screening gates

Three hard gates run in the mechanism layer, before the hook is consulted:

#GateSource
1Overround up_ask + dn_ask − 1market order-book
2Spread per sidesame
3Time to closetopic endDate

Gate 1 does the most work: an observable fact about the book rather than a forecast. Measured live on one BTC 1-hour round: asks 0.55 / 0.46 → overround 1.0%, spread 0.01 — and 3.0% on the same round 22 seconds later. Screen immediately before ordering.

A fourth check runs at sizing rather than screening, because it depends on the stake:

CheckBehaviour
Depth at the best askThe order is refused if the stake exceeds the USDT resting at the top of the book. The screen was done against that price; an order larger than it walks into worse prices and the number the decision was made on stops being the number that fills. $5 at the best ask has been observed — size against the book, not the budget

Fair-value deviation and "breakeven ≤ demonstrated calibration" are your hook's job, not the mechanism layer's: both depend on a model the scaffold does not have. They are listed here so the division is explicit, not because the daemon enforces them.

Thresholds ship unset. A number from someone else's venue describes their book.

Two kinds of market

TypeFair valueConsequence
At-the-moneynaturally ≈0.50No model edge. Only mispricing of that 0.50
Strike or bandcomputable from time and volatilityDeviation is measurable; tails are where crowds err

Strategy hooks

estimate_probability(rnd, ctx) -> float | None   # P(round closes UP); None skips
size_position(rnd, ctx, edge)  -> float          # USD; clamped to stake_usd and budget

ctx carries cfg, policy (verbatim), streak, streak_dir, open, now_ms.

ctx contains no quote, deliberately. An estimator that can see the price anchors on it, and the calibration report then measures something that looks accurate while holding no edge. Enforced at the interface, not left to discipline. Out-of-range or non-numeric returns are rejected with an ALERT, not clamped.

What ships, and what is known about the signal it reads

The hook is where you write your strategy. What ships is an example that makes the pipeline run — every judgement and every number in it should be replaced by what your own analysis shows.

The bundled implementation reads one mechanical feature — how long the current run of same-direction candles is — and looks the answer up in policy.hit_table, which you supply. It ships with no numbers, because a table describes one symbol at one cadence and borrowing one describes nothing. backtest.py eval --interval <iv> prints a table ready to paste.

For reference, the same family measured on the settlement series at every listed cadence:

Candlestreak 2345monotonic?
5m (n≈9.8k)0.5200.5090.5120.514no
15m (n≈9.6k)0.5360.5380.5440.540no
1h (n≈5.8k)0.5390.5440.5520.559yes
1d (n≈1.6k)0.5380.5310.5360.535no
  • A broad reversal tendency of ~+3pp is present at 15m, 1h and 1d. That replicates.
  • "Longer streak, stronger reversal" does not — it appears in one cadence out of four and shifts with the sample window. One in four is what noise looks like.

Net of measured costs, at streak ≥ 3 on an at-the-money round:

hit rate0.544
− fair value−0.500
− overround, one side−0.005
− fee, 200 bps−0.020
− slippage observed on a fill−0.0125
net per round+0.0065

The error bar on the hit rate is ±0.019 — 2.9× the edge. Telling +0.65% from zero needs about 22,700 rounds: 11.5 years at min_streak 3. That is neither "it loses money" nor "it makes money" — it is unverifiable at the rate these rounds occur, and every figure assumes the round is quoted at fair value, which step 3 of the walkthrough exists to check.

Treat the table above as a worked example of the bar, not as a configuration to copy.

What budget_usd does and does not cap

It caps loss, not turnover. A round is allowed only while

realised losses  +  capital currently at risk  +  this stake   ≤   budget_usd

so the worst case — every open round settling to zero — still lands inside the number you acknowledged. Winning stakes return to the pool and the daemon keeps going.

This matters for long runs. An earlier build treated the figure as cumulative turnover and never released it at settlement, which stopped a $50 budget at $1 stakes after 50 rounds whatever the outcome — a halt unrelated to risk, and incompatible with the sample sizes this strategy family needs. If you want to stop after a fixed number of rounds, use max_rounds_per_day.

Mechanism guardrails

Enforced here; hooks cannot bypass them. Values are yours.

GuardBehaviour
budget_usdCap on total loss, enforced as realised losses + capital currently at risk ≤ budget_usd. The stake is released back when a round settles, so the gate tracks risk rather than turnover
stake_usdPer-round cap, after the sizing hook. Floor is 1 USDT (platform minimum)
max_open_roundsConcurrent unsettled rounds
max_rounds_per_dayResets at UTC midnight — the real cost control at this cadence
max_consecutive_lossesHalts new entries, records the reason
Single instanceflock, released by the kernel if the holder dies
Live gateRefuses to start unless the ack file holds the current budget_usd
STOP / KILLStop opening / exit

Configuration

$UPDOWN_HOME/config.json (default ~/.local/updown). First run writes an empty template.

Nothing that touches capital has a default. The one exception is mode — absent means dry-run, so an omission cannot become live trading.

Required (mechanism)

KeyHow to decide it
chainbaw wallet chains --json
symbolBTCUSDT / ETHUSDT / BNBUSDT
timeframe5 min / 15 min / 1 hour / daily — exact strings; a typo is rejected at startup with the live catalogue
budget_usdAn amount whose total loss is acceptable. Enforced literally: a round opens only if losing it, plus everything already lost and everything currently at risk, still fits inside this number. You type it again to go live
stake_usd≥ 1 USDT, and under the size resting at the best ask
max_open_rounds1 until the money path is proven on your account
max_rounds_per_dayA cadence control, independent of the budget. At 2% a round, this is what bounds fees on a quiet strategy — it is not derived from budget_usd
max_consecutive_lossesAt ~55%, four in a row is unremarkable

Required (policy — read by the bundled hook)

KeyHow to decide it
min_streak4+ fires rarely but measures strongest; 2 fires often and measures weakest
edge_thresholdAbove the uncertainty in your own hit-rate estimate, or you trade noise
max_overroundWatch the book for an hour first. Observed 1%–3% minutes apart
max_spreadSame method. Observed 0.01 on liquid BTC rounds
min_seconds_to_close / max_seconds_to_closeMarket orders near settlement have failed outright. Leave room
slippage_bpsOrder tolerance, not a cost
hit_table{"2": 0.53, "3": 0.54, …} for the cadence you trade, from backtest.py eval --interval <iv>. No default — a borrowed table describes nothing
expected_slippage_bpsStart at 0, run the minimum stake, read the ALL-IN … = +N bps each fill logs, subtract the fee, set what you saw. One fill: +325 bps all-in against a 200 bps fee

Optional — kline_interval (defaults to the round's cadence) · anything else your hook reads

Has defaults (plumbing) — mode · poll_sec · settle_poll_sec · kline_refresh_sec · l1_category · l2_category

Runtime files and evaluation

$UPDOWN_HOME holds config.json · state.json · rounds.jsonl (every decision, including traded ones) · orders.jsonl · outcomes.jsonl · quotes.jsonl · updown.log. rounds.jsonl and outcomes.jsonl join on key.

HarnessWhat it answers
backtest.py evalDoes the signal clear the cost hurdle out of sample? Offline, auditable by someone who does not trust the daemon
backtest.py pricedinIs it already in the price? The question a backtest cannot reach

eval enforces a train/test split, prints confidence intervals rather than point estimates, counts hypotheses tested, and checks dose-response on the full sample. Any non-live run writes quotes.jsonl, recording every book it can read, including ones your gates reject — sampling only tradable books biases the priced-in analysis.

Three columns matter in calibration: your probability, a constant 0.50, and the quote. Failing to beat the quote column means there is no strategy, however good the hit rate looks.

Platform behavior notes

A dead session must never look like a quiet market. Every prediction read needs an authenticated session and sessions expire. Pre-flight probes the prediction endpoint itself (wallet status can answer while the prediction session is dead); at runtime any auth failure is logged ALERT and recorded as that round's skip reason.

order-book takes --marketId, not --marketTopicId. Different identifiers; a topic contains markets.

The minimum order is 1 USDT — quote rejects less.

A placed order can still fail. Observed: place-order returns success, the order settles to FAILED ("Failed to execute the market order") with zero filled. Placement is a receipt. The daemon polls to a terminal status and on failure returns the stake to the budget and drops the round — otherwise a failed order consumes budget forever and leaves a phantom open round.

Settled PnL is in realizedPnl; pnl comes back null. Reading the wrong one books every win as zero and destroys the calibration ledger.

Redemption is account-wide, not daemon-scoped: the sweep claims everything claimable in the account, including positions this daemon never opened. Settlement bookkeeping covers only rounds in its own state.json.

Quotes expire between quote and place; the daemon refetches once.

Resting sell orders can co-reserve the same shares and only release at resolution, after which further sells fail for insufficient position while the market is still open. Keep at most one resting order per position.

Pre-flight checks

Read-only — do not probe by placing an order.

baw wallet status --json
baw wallet chains --json
baw wallet balance --binanceChainId <chain> --json
baw prediction category list --json             # confirm crypto / up-down exists
baw prediction position list --limit 1 --json   # confirm the prediction session is alive

Running

cd <skill-dir>/scripts && python3 updown.py                     # foreground
nohup python3 -u updown.py >> runner.log 2>&1 & disown          # long-running
python3 updown.py --collect-quotes                              # records only
ActionCommand
Stop opening new roundstouch $UPDOWN_HOME/STOP
Exit the processtouch $UPDOWN_HOME/KILL
Acknowledge the amount at riskecho <budget_usd> > $UPDOWN_HOME/I_UNDERSTAND_THE_RISK
Restore the live gaterm $UPDOWN_HOME/I_UNDERSTAND_THE_RISK

Going live: set mode to live. The run refuses to start unless the ack file contains the current budget_usd — so it cannot be inherited by copying a runtime directory, and raising budget_usd invalidates it. An agent may write it once the operator has confirmed the number.

Exiting does not close open rounds. They settle on their own and still need redeeming.

Keeping it running

This is a foreground process with an internal loop, not a Unix daemon: no fork, no setsid. nohup … & survives a logout, not a reboot. What the process does and does not guarantee:

Survives a transient errorYes — the loop catches, logs ERROR main loop, and backs off 15s → 300s
Survives a crash or rebootNo — nothing restarts it. That is your supervisor's job
Safe to restartYes — state.json holds budget, open rounds and daily counters; flock prevents two instances
Recovers missed settlementsYes — the sweep re-reads positions and PENDING_CLAIM on each pass
Survives session expiryNo — it keeps running but skips every round, loudly (ALERT). Re-authenticate

launchd (macOS) — ~/Library/LaunchAgents/local.updown.plist:

<plist version="1.0"><dict>
  <key>Label</key><string>local.updown</string>
  <key>ProgramArguments</key>
  <array><string>/usr/bin/python3</string><string>/ABSOLUTE/PATH/scripts/updown.py</string></array>
  <key>EnvironmentVariables</key><dict><key>UPDOWN_HOME</key><string>/Users/you/.local/updown</string></dict>
  <key>RunAtLoad</key><true/>
  <key>KeepAlive</key><true/>
  <key>StandardOutPath</key><string>/Users/you/.local/updown/runner.log</string>
  <key>StandardErrorPath</key><string>/Users/you/.local/updown/runner.log</string>
</dict></plist>

launchctl load ~/Library/LaunchAgents/local.updown.plist

systemd (Linux) — ~/.config/systemd/user/updown.service:

[Service]
Environment=UPDOWN_HOME=%h/.local/updown
ExecStart=/usr/bin/python3 /ABSOLUTE/PATH/scripts/updown.py
Restart=always
RestartSec=30
[Install]
WantedBy=default.target

systemctl --user enable --now updown

The supervisor restarts the process; it does not re-authenticate. A session that expires stops all trading until someone signs in again — plan for that separately, and watch for ALERT lines rather than assuming quiet means healthy.

Known limitations

LimitationDetail
Bundled strategy is unverifiable~+0.65%/round against a ±1.9pp error bar; ~22,700 rounds to confirm. A worked example of the bar your signal must clear, not a signal to fund
Hit rate is not edgeIf the quote reflects the state, a winning hit rate earns zero. Only pricedin rules it out
At-the-money roundsFair value ≈0.50; anything won there is mispricing, not forecasting
Fee dominates200 bps per round, published by the platform
No exit, no stop-lossThe whole decision is at entry
Redemption is requiredWinnings sit unclaimed until swept
Sessions expireMitigated by alerting, not solved. A long run needs re-authentication
Dry-run misses the money pathOrdering, fills, settlement and redemption only run live
Thin depthSingle-digit dollars at the best ask has been observed
No supervisorUse launchd, systemd or a container restart policy

Disclaimer

This is a scaffold, not a trading product.

  • The bundled strategy is a measured reference with no demonstrable positive expectancy net of costs, provided to show that the pipeline runs and the harnesses discriminate.
  • Every risk control and parameter here is a suggestion, not a safeguard. The daemon runs the checks you configure; it does not judge whether they suffice.
  • Nothing here is investment, financial or trading advice.
  • Positions can expire worthless. Automated trading can lose money through market conditions you did not anticipate, platform behavior changes, defects in this software, or defects in the strategy you write.
  • Provided as-is, without warranty. To the maximum extent permitted by law, no liability is accepted for any loss arising from its use.
  • You are solely responsible for what you run, the parameters you choose, compliance with the laws that apply to you, and any funds you commit.

If you are not prepared to lose the full budget_usd, do not set mode to live.

Repository

binance/binance-skills-hub

Path

skills/binance-web3/binance-prediction-updown/SKILL.md

Last updated
First committed

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.