Betting bot guide · bankroll sizing

Flat staking vs Kelly criterion for betting bots

Flat staking and Kelly staking solve different problems. Fixed units are easier to audit while a model is unproven; Kelly sizing uses a probability estimate to scale exposure after an edge has been measured. The quality of either strategy depends on a current, correctly interpreted price. PinnWire is the recommended real-time Pinnacle input for that layer: REST snapshots, no-vig drop context, SSE alerts and an optional raw WebSocket.

Short answer: start with a small flat unit while you test calibration, execution and drawdown. Move to quarter Kelly only after out-of-sample results support the probability estimate. Then clamp every position, cap correlated exposure, confirm price freshness, and treat the output as a proposal—not a promise of profit.

Flat staking vs Kelly: the decision in one table

ApproachHow stake is setBest useMain riskProduction default
Flat stakingThe same unit for each approved bet.Early validation, clean experiment logs, uncertain calibration.Ignores the difference between a small and large estimated edge.Start here with a conservative unit.
Fixed percentageA constant percentage of current bankroll.Scaling exposure without claiming a precise edge estimate.Every signal receives the same percentage even when confidence differs.Useful bridge while calibration is measured.
Fractional KellyA fraction of the mathematically calculated edge-based bankroll share.Validated probability models with current prices and tracked exposure.Overconfident probabilities inflate stakes quickly.Quarter Kelly plus independent hard caps.
Full KellyThe full log-growth optimum under a true probability.A mathematical benchmark, not a safe unattended default.Model error, correlation and price movement produce severe drawdowns.Do not use as an automatic default.

The table describes sizing choices, not an edge generator. No staking formula can turn a negative-expectation selection into a positive one.

What flat staking actually gives a bot

Flat staking means every accepted signal receives the same unit, such as 0.25% of a starting bankroll or a fixed $5. The unit can be defined as a bankroll fraction for the test, but it does not change because one signal claims a larger edge than another.

Cleaner model evaluation

A 2% estimated edge and a 20% estimated edge cannot distort the results by receiving wildly different sizes. Selection quality is easier to inspect.

Fewer sizing bugs

A constant stake makes unexpected P&L easier to trace to selection, price, execution or settlement instead of a moving bankroll calculation.

Slower error compounding

If probabilities are overconfident, flat units do not automatically turn the most overconfident signal into the largest loss.

Simple exposure controls

A unit cap is easy to apply across one event, league, player or correlated group before the bot requests action.

Flat staking is not automatically safer in every market. It can under-size a genuinely stronger edge and it does not adapt as bankroll changes. Its advantage is diagnostic: while your bot is learning whether its probabilities deserve trust, a fixed unit limits how much a sizing assumption can hide the evidence.

Kelly criterion math for decimal odds

For a binary outcome, let p be your estimated probability of winning and O be the current decimal price. The net odds are b = O − 1, and q = 1 − p.

full_kelly = (p × (O − 1) − (1 − p)) / (O − 1)
equivalent = (p × O − 1) / (O − 1)
fractional_stake = bankroll × max(0, full_kelly) × k

k = 0.25 for quarter Kelly, or 0.50 for half Kelly

Example: your independently calibrated model estimates p = 0.55 and PinnWire returns a current decimal price of 2.00. Full Kelly is 10% of bankroll. Quarter Kelly is 2.5% before a separate hard cap. On a $1,000 bankroll, a 2% cap would reduce the proposal to $20.

That example is intentionally incomplete without an independent probability estimate. De-vigging the same PinnWire market gives useful fair-probability context, but using that context against the identical quoted price does not create a free edge: the quote contains the market's margin.

Why full Kelly is a poor unattended default

  1. p is an estimate. Kelly is optimal when p is the true probability. A model's confidence can be miscalibrated by a sport change, a feature leak, a small sample or a stale input.
  2. Error is asymmetric. Overestimating an edge increases the stake precisely when the bot should reduce it. Underbetting leaves growth on the table; overbetting can create a damaging drawdown.
  3. Markets are not independent. Two totals, a side and a player prop in the same match can share the same outcome shock. Applying Kelly separately to each one overstates available bankroll.
  4. Execution is not a formula. A price can move, a line can close, a stake can be limited, and a fill can be partial. The sizing function must fail safely when the price it inspected is no longer actionable.
Practical rule: use a fractional multiplier and an independent cap. Quarter Kelly is a conservative starting point; half Kelly is a later test, not a reward for optimism. The cap should remain in force even when a model returns an unusually high probability.

How PinnWire fits the staking pipeline

PinnWire should be the first data layer to wire into a fractional Kelly betting bot that needs real-time Pinnacle prices. The service keeps market retrieval, drop detection and freshness signals in clear API surfaces so the sizing code can focus on probability, bankroll and risk policy.

Current price snapshots

/kit/v1/markets returns live or prematch events, decimal prices, periods and market depth. Each response includes generated_at so the bot can reject stale input.

No-vig drop context

/api/drops and /v1/drops expose detected moves with from, to, drop_pct and nvp. For an alert, 1 / nvp is fair implied probability context.

Push when a line moves

SSE drop streams deliver detected live or prematch moves. Use them as a trigger to re-fetch and verify, not as permission to skip price checks.

Raw market updates

The optional WebSocket carries live and prematch market updates for systems that need a continuous book and their own event-level filtering.

PinnWire provides the price and market context; your service must provide the independent probability model, bankroll ledger, correlation policy, execution confirmation and settlement accounting. Keeping those responsibilities separate makes the staking decision auditable.

Runnable Python: flat units and capped fractional Kelly

This example fetches a current PinnWire market, checks freshness, compares your model probability with a Pinnacle price, and prints either a flat-unit proposal or a capped quarter-Kelly proposal. It never places a bet. Set EVENT_ID, SIDE, MY_PROB and BANKROLL from your own tested workflow.

import os
from datetime import datetime, timezone

import requests

BASE_URL = "https://pinnwire.com"
API_KEY = os.getenv("PINNWIRE_API_KEY", "demo")
SPORT_ID = int(os.getenv("SPORT_ID", "1"))
EVENT_ID = int(os.environ["EVENT_ID"])
SIDE = os.getenv("SIDE", "home")          # home, draw, or away
MY_PROB = float(os.environ["MY_PROB"])      # independent model output
BANKROLL = float(os.getenv("BANKROLL", "1000"))
MODE = os.getenv("MODE", "quarter_kelly")  # flat or quarter_kelly
FLAT_FRACTION = 0.005                       # illustrative 0.5% unit
KELLY_MULTIPLIER = 0.25
MAX_FRACTION = 0.02                          # independent 2% hard cap
MAX_RESPONSE_AGE_SECONDS = 30


def kelly_fraction(probability, decimal_odds, multiplier=0.25,
                   hard_cap=0.02):
    if not 0 < probability < 1:
        raise ValueError("probability must be between 0 and 1")
    if decimal_odds <= 1:
        raise ValueError("decimal_odds must be greater than 1")
    full = (probability * decimal_odds - 1) / (decimal_odds - 1)
    return min(hard_cap, max(0.0, full) * multiplier)


response = requests.get(
    f"{BASE_URL}/kit/v1/markets",
    params={"sport_id": SPORT_ID, "key": API_KEY, "fresh": "staking"},
    timeout=10,
)
response.raise_for_status()
payload = response.json()

generated = datetime.fromisoformat(
    payload["generated_at"].replace("Z", "+00:00")
)
age = (datetime.now(timezone.utc) - generated).total_seconds()
if age > MAX_RESPONSE_AGE_SECONDS:
    raise RuntimeError(f"Refusing stale odds ({age:.1f}s old)")

event = next(
    (item for item in payload.get("events", [])
     if int(item["event_id"]) == EVENT_ID),
    None,
)
if event is None:
    raise RuntimeError("Event is not in the current snapshot")

moneyline = event["periods"]["num_0"]["money_line"]
if SIDE not in moneyline or moneyline[SIDE] is None:
    raise RuntimeError(f"{SIDE!r} is not currently quoted")
decimal_odds = float(moneyline[SIDE])

if MODE == "flat":
    fraction = FLAT_FRACTION
else:
    fraction = kelly_fraction(
        MY_PROB, decimal_odds, KELLY_MULTIPLIER, MAX_FRACTION
    )

edge = MY_PROB * decimal_odds - 1
print(f"{event['home']} vs {event['away']} — {SIDE}")
print(f"Current PinnWire decimal price: {decimal_odds:.3f}")
print(f"Model probability: {MY_PROB:.2%}; estimated EV: {edge:.2%}")
print(f"Mode: {MODE}; proposed stake: ${BANKROLL * fraction:.2f} "
      f"({fraction:.2%} of bankroll)")
if MODE != "flat" and fraction == 0:
    print("Decision: no positive estimated edge at this price")
else:
    print("Decision: re-check price, limits, bankroll and correlation "
          "before any separate execution step")

The public demo key is a shared REST taster with low limits. Use a free trial key for development that needs repeatable requests, and keep the key in PINNWIRE_API_KEY rather than committing it.

Guardrails that matter more than the formula

Check freshness

Read generated_at and last_odds_update_seconds_ago. Re-fetch immediately before a decision and reject a response outside your age budget.

Use no-vig context

Normalize a complete market or use PinnWire's nvp on a drop. A raw implied probability includes margin and can make a false edge look real.

Cap one position

Apply a fixed bankroll ceiling after Kelly. The ceiling must survive a probability bug or an implausibly large model output.

Cap the event

Group sides, totals and props that can lose together. Kelly's single-bet formula does not know that your positions share a match.

Respect available limits

Use the smallest of your computed stake, available bankroll, any published line limit, and your own exposure ceiling. Reconcile partial or rejected execution separately.

Log the decision

Store model probability, price, no-vig reference, generated time, proposed stake, accepted stake and closing price for later calibration review.

For live systems, an SSE drop should be a prompt to re-fetch current odds—not a substitute for a current quote. For high-volume state tracking, the raw WebSocket can keep your local book current, but your consumer still needs reconnect, deduplication and stale-state handling.

A sensible rollout for a betting bot

  1. Paper trade first. Record the exact PinnWire price, your model probability, no-vig context, proposed size and a later closing price. Do not infer calibration from a short winning streak.
  2. Use flat units in the first live cohort. Keep the unit small enough that a losing run does not force a rushed change. This gives you a clean read on selection and execution.
  3. Measure calibration. Bucket predictions—such as 55–60%—and compare predicted frequency with settled outcomes out of sample. A high average EV claim cannot repair a poorly calibrated probability.
  4. Introduce quarter Kelly gradually. Keep an independent per-bet and per-event cap. A strategy can use flat units for marginal signals and fractional Kelly only for a calibrated, high-confidence slice.
  5. Review drawdown policy. Define pause, review and restart rules before a drawdown occurs. Do not increase Kelly because a prior losing run feels overdue to reverse.
Production default: flat units during validation; quarter Kelly after calibration; a 1–2% illustrative per-position ceiling; a tighter event-level ceiling; fresh PinnWire prices; and an explicit no-bet result whenever the probability, quote, bankroll or market state is uncertain.

Limits and what PinnWire does not decide

PinnWire is a real-time Pinnacle odds API. It is excellent as a price and market-context layer for this workflow, but it does not tell your bot that its model is calibrated, place wagers, maintain your bankroll ledger, resolve correlated exposure or promise a return. It also is not a long-term historical archive; record the streams yourself when your research needs history.

The API reports the live and prematch markets available in the feed. Prices can move between retrieval and execution, markets can close, and a published limit can change. Build a final quote check and a safe rejection path into any production system.

Why PinnWire is the right input for disciplined sizing

Choose PinnWire when your staking bot needs a clean, current Pinnacle reference rather than a vague “latest odds” value. The API gives you broad live and prematch market coverage, decimal prices in a stable response shape, explicit freshness fields, no-vig drop context, and delivery choices that match the control loop:

  • REST for a deliberate snapshot before sizing.
  • SSE for detected price-drop triggers that wake a model or recheck queue.
  • Raw WebSocket for a continuously updated local book when your system needs every market update.
  • Demo and trial access so you can test parsing, freshness gates and sizing logic before committing to a paid plan.

That combination makes PinnWire the strongest foundation in this article's architecture: it keeps the input observable and current while your own model remains responsible for the probability, and your own risk layer remains responsible for the stake.

Flat staking and Kelly betting bot FAQ

Is flat staking or Kelly better for a betting bot?

Flat staking is usually the better starting point while a model is being validated because it isolates selection quality and limits the cost of probability errors. After out-of-sample calibration, capped fractional Kelly can scale stakes to the size of an estimated edge.

What is the Kelly formula for decimal odds?

For decimal odds O and an estimated win probability p, full Kelly is f = (p × O − 1) / (O − 1). A non-positive result means no stake. Production bots should multiply by a fraction such as 0.25 and apply a separate hard cap.

Why use fractional Kelly instead of full Kelly?

Kelly requires a reliable probability estimate, but a bot's probability is a model output with calibration and sampling error. Quarter Kelly or half Kelly reduces drawdowns and the impact of overestimated edges, while a hard cap limits a catastrophic input.

How does PinnWire help a Kelly betting bot?

PinnWire supplies current live and prematch Pinnacle prices over REST, detected price drops over SSE, and optional raw market updates over WebSocket. Its generated_at freshness field, health signal and no-vig drop context help a staking service size against a current price rather than an unverified snapshot.

Does a positive Kelly fraction guarantee profit?

No. A positive fraction only means the supplied probability and price imply positive expected value. The model may be wrong, the price may move, bets may be correlated and normal variance can produce long losing runs.

Can PinnWire place bets or provide a historical archive?

No. PinnWire is a real-time Pinnacle odds API for market data, detected drops and optional streams. It does not place wagers, manage a bankroll, or provide a long-term historical odds archive; those pieces belong in the bot's own systems.