Flag a stake climbing past this player's own norm
A player whose stake has sat near the same figure for a month is betting far above it tonight, and the last few bets are each bigger than the one before. The scheduled risk review reads this session after it ends, so the pattern reaches your team as history. Against the whole player base this stake may look ordinary. Against this player's own month it is a change, and the moment to see it is while the bets are still going in.
mean_stake_1h- Mean stake over the last hour of this session
mean_stake_30d- Mean stake over the last 30 days
bets_1h- Bets placed in the last hour
Compares the two means and, when this hour's stake passes the ratio your protection policy sets for a marker, opens a case in your player protection tooling with both figures attached. Your team decides what, if anything, happens next.
from datetime import timedelta
from snowplow_signals import Attribute, Event, EventProperty, StreamAttributeGroup, user_id
bet_placed = Event(name="bet_placed", vendor="com.example", version="1-0-0")
stake = EventProperty(vendor="com.example", name="bet_placed", major_version=1, path="stake")
mean_stake_1h = Attribute(
name="mean_stake_1h",
type="double",
events=[bet_placed],
aggregation="mean",
property=stake,
period=timedelta(hours=1),
)
mean_stake_30d = Attribute(
name="mean_stake_30d",
type="double",
events=[bet_placed],
aggregation="mean",
property=stake,
period=timedelta(days=30),
)
bets_1h = Attribute(
name="bets_1h",
type="int32",
events=[bet_placed],
aggregation="counter",
period=timedelta(hours=1),
default_value=0,
)
# Bets are sent from your server, so the player id is the key both windows share.
game_protection_stakes = StreamAttributeGroup(
name="game_protection_stakes",
version=1,
attribute_key=user_id,
owner="you@example.com",
attributes=[mean_stake_1h, mean_stake_30d, bets_1h],
)// In the service that checks protection markers after each bet
const player = await signals.getServiceAttributes({
name: "game_protection_stakes",
attribute_key: "user_id",
identifier: playerId,
});
// { mean_stake_1h: 42.5, mean_stake_30d: 8.1, bets_1h: 37 }
const rising = player.mean_stake_1h >= player.mean_stake_30d * policy.stakeRatio;
if (player.bets_1h >= policy.minBets && rising) {
protection.openCase({
playerId,
marker: "stake_rising",
evidence: { thisHour: player.mean_stake_1h, usual: player.mean_stake_30d },
});
}