DEV Community

Cover image for Building fair real-time odds for CS2 stream predictions
Andrei
Andrei

Posted on

Building fair real-time odds for CS2 stream predictions

In my last post, I wrote about stream delay and why realtime on Twitch is secretly a timeline problem.

This time: odds.

number of kills prediction

I am building elo.market, a live prediction system for CS2 streams. Viewers bet virtual elo on things like round winners, bomb plants, and clutches.

At first, dynamic odds sounded easy.

Votes come in -> odds move.

Done, right?

Not really.

Because if odds move too slowly, the market feels fake.

And if they move too fast, one smart whale can make the whole thing feel rigged.

The Problem In One Line

This was the failure mode I kept running into:

small early vote -> juicy odd appears -> whale grabs it -> market feels broken
Enter fullscreen mode Exit fullscreen mode

Or visually:

+------------------+     +--------------------+     +------------------+     +------------------+
| Small early vote | --> | Big price appears  | --> | Whale takes it   | --> | Everyone else    |
| from normal user |     | on the other side  |     | before it settles|     | thinks: "lol ok" |
+------------------+     +--------------------+     +------------------+     +------------------+
Enter fullscreen mode Exit fullscreen mode

That is technically a dynamic market.

It is also terrible product design.

The First Version Was Too Naive

My first version was basically:

  • start from some base probability
  • add a seed so the first bet does not nuke the market
  • recalculate odds from the current pool

Conceptually:

adjustedProb = (amountOnOutcome + baseProb * seed) / (totalPool + seed);
Enter fullscreen mode Exit fullscreen mode

That was fine as a first step.

But it had two problems.

First, some markets should not even start 50/50.

A round_winner market is not actually even if one side is broke and the other has rifles.

So I started feeding real game context into the base probabilities:

  • loss streaks
  • score momentum
  • streamer economy as a team signal

That helped the opening price feel smarter.

But it still did not solve the whale problem.

The Whale Problem

The obvious model is amount-based.

More money on one side means the odds shift harder.

Sounds fair, until one large bet starts overpowering the market.

So I tried blending two signals:

  • how much money was bet
  • how many people bet that side

That was better, but my first attempt still had a bug in the idea.

I used average bet size in the formula.

Which meant a whale did not just affect the amount part. They also inflated the "crowd consensus" part by making average bet size bigger.

That was dumb.

I accidentally gave large bets influence twice.

The Fix That Actually Mattered

The best change was surprisingly small.

I stopped using average bet size for voter influence and switched to a fixed vote unit instead.

So the logic became:

effectiveAmount =
  (1 - voterWeight) * rawAmount +
  voterWeight * voterCount * FIXED_VOTE_UNIT;
Enter fullscreen mode Exit fullscreen mode

That changed the feel of the market a lot.

Now a whale can still move odds because size should matter.

But they cannot also pretend to be "the crowd" just because their bet was huge.

That one change made the system feel much less abusable.

Then I Hit The Next Problem

Even with a better formula, fixed seeds still broke at different economy sizes.

If one streamer has average bets around 50 elo and another has average bets around 1000, the same seed value behaves completely differently.

So I made the seed scale with season behavior instead of hardcoding it.

Conceptually:

dynamicMinSeed = seasonAvgBet * 50;
effectiveSeed = max(dynamicMinSeed, totalPool * seedMultiplier);
Enter fullscreen mode Exit fullscreen mode

That made early odds much harder to snipe across both small and large channels.

Not Every Market Should Behave The Same

Another lesson: a bomb prediction and a map-wide prediction should not react with the same personality.

So I ended up with templates.

  • dynamic for markets that can move more
  • balanced for standard round predictions
  • stable for short live windows
  • anchored for longer markets that should respect the opening line more

That was much better than pretending one formula could fit everything.

Delay Came Back Again

And because Twitch loves making every problem slightly worse, delay showed up here too.

If I delayed both PREDICTION_CREATED and ODDS_UPDATE the same way, viewers saw stale odds.

So the final model became:

  • delay the prediction event
  • send odds updates immediately
  • buffer them on the client until the prediction is actually visible

That way, when the card appears, viewers see current odds instead of ancient history.

Fairness Needed One More Layer

Even after all that, there was still one ugly edge case:

the user clicks at one price, but by the time the vote reaches the backend, the odds are worse.

So I added slippage protection.

If the odds move too far against the user, the vote is rejected and the UI can ask them to accept the new number.

That turned out to matter almost as much as the formula itself.

Because fair pricing is not just math.

It is also about whether users feel tricked.

Where I Landed

The current system is basically this:

game state priors
      +
adaptive seed
      +
voter consensus
      +
slippage protection
      +
delay-aware delivery
      =
dynamic odds that still feel fair
Enter fullscreen mode Exit fullscreen mode
  • smarter base probabilities from real game state
  • adaptive seed so early votes do not create nonsense
  • voter weighting so one whale does not dominate instantly
  • fixed vote units so big bets do not fake consensus
  • different templates for different market types
  • slippage protection for bad fills
  • delay-aware delivery so live odds still look live

The funny part is that dynamic odds sounded like a tiny feature when I started.

In reality, it turned into a product trust problem.

If the odds feel fake, people stop caring.

If the odds feel exploitable, people stop trusting.

The goal was not "perfect market making."

The goal was simpler:

make odds move enough to feel alive, but not enough to become a farming strategy for whales.


If you have built low-liquidity markets, game economies, or live pricing systems, I would love to hear what tradeoffs you made.

Top comments (0)