Skip to main content

Fraud Detection System — Rules Reference

Architecture Overview

The fraud detection pipeline evaluates every bet through 26 rules across three tiers:

  1. Dimension Rules (5) — Always run. Produce a score 0–100 that feeds the severity engine.
  2. Deterministic Rules (18) — Conditionally triggered. Return a binary triggered/not-triggered result with severity.
  3. Fraud Signatures (3) — Composite detectors combining multiple signals for high-confidence detection.

Evaluation flow:

Bet placed → TimescaleDB (fraud_events)
→ EvaluationJob picks up bet (60s cycle live, 300s idle)
→ Run 5 dimension rules → DimensionScores
→ Run 18 deterministic rules → triggered RuleResults
→ Run 3 signatures → triggered RuleResults
→ ScoringEngine computes final severity
→ FlaggingService creates case / blocks user / publishes alert

Severity escalation:

ConditionSeverity
Any dimension ≥ 80RED
Two correlated dimensions both ≥ 60RED
Any triggered rule with severity REDRED
Any dimension 60–79ORANGE
Any dimension 40–59YELLOW
All dimensions < 40, no rules triggeredGREEN

Correlated dimension pairs (both ≥ 60 = RED):

  • exchangeVsBookmaker + liquidityExploitation
  • priceMovement + repetition
  • identityLinkage + exchangeVsBookmaker
  • identityLinkage + liquidityExploitation

Post-flag actions:

SeverityAction
REDUser blocked 24h, fraud case created, alert published, 4h SLA
ORANGEFraud case created, alert published, 24h SLA
YELLOWVisible in dashboard only
GREENNo action

Dimension Rules

These run on every bet and produce a continuous score from 0 to 100.

1. Exchange vs Bookmaker (dim_exchange_vs_bookmaker)

File: dimensions/exchangeVsBookmaker.ts

Detects: Price arbitrage — user betting at odds significantly better than the exchange midpoint.

How it works:

  • Compares the user's bet odds against the exchange midpoint price at the time of the bet.
  • Calculates the edge percentage: (betOdds - exchangeMid) / exchangeMid.
  • Applies a staleness penalty — if the bookmaker price is stale (>30s old = score 0, 15–30s = 0.5x, 5–15s = 0.8x).
  • Back bets getting better-than-mid odds are suspicious.
  • Score scales with edge size: small edges score low, large edges score high.

Data used: Exchange ticks (EXCHANGE_TICK events from TimescaleDB), bookmaker ticks, bet odds.


2. Price Movement (dim_price_movement)

File: dimensions/priceMovement.ts

Detects: Courtsiding — betting before a favorable real-world event causes a price shift.

How it works:

  • Compares the exchange midpoint at T-1 (before bet) and T+1 (after bet).
  • If the price moves favorably for the user's position after the bet, this is suspicious.
  • Requires a T+1 event marker (BALL, WICKET, GOAL, etc.) to complete evaluation.
  • Accounts for market suspension timing between T and T+1.
  • Returns null if no T+1 event exists yet (evaluation is deferred).

Data used: Exchange ticks, event markers (BALL, WICKET, GOAL, CARD, MILESTONE, OVER_COMPLETE).


3. Liquidity Exploitation (dim_liquidity_exploitation)

File: dimensions/liquidityExploitation.ts

Detects: Users consistently extracting value from thin or inefficient markets.

How it works:

  • Calculates the ratio of available volume to total market volume at the time of the bet.
  • Scores higher when the user bets into markets with low available liquidity.
  • Timing relative to volume spikes is factored in — betting just before liquidity dries up is suspicious.

Data used: available_volume, total_market_volume from exchange tick events.


4. Repetition (dim_repetition)

File: dimensions/repetition.ts

Detects: Users repeating the same suspicious pattern across multiple bets.

How it works:

  • Analyzes the user's 30-day bet history for consistency in market selection, bet side, and odds range.
  • If >60% of bets follow the same pattern (same market type, same side, similar odds), the score increases.
  • Higher consistency = higher score. Random/varied betting patterns score low.

Data used: User bet history (30 days from TimescaleDB), Prisma queries.


5. Identity Linkage (dim_identity_linkage)

File: dimensions/identityLinkage.ts

Detects: Multi-accounting — multiple accounts controlled by the same person.

How it works:

  • Queries the FraudIdentityCluster table for active clusters that include this user.
  • Scores based on cluster confidence (0–1 scaled to 0–100).
  • Clusters are built from shared device fingerprints, IP addresses, payment methods, and betting pattern similarity.

Data used: FraudIdentityCluster (Prisma), cluster confidence scores.


Deterministic Rules

These run conditionally and return a binary triggered/not-triggered result.

6. Impossible Travel (DET_IMPOSSIBLE_TRAVEL)

File: rules/impossibleTravelRule.ts

Detects: Account compromise or sharing — two logins from geographically distant locations in an impossibly short time.

Logic: 2+ logins from different countries within 1 hour. Uses V3IpAccessLog and USER_LOGIN events.


7. Stake Escalation (DET_STAKE_ESCALATION)

File: rules/stakeEscalationRule.ts

Detects: Sudden increase in bet size suggesting inside information or compromised account.

Logic: Bet stake compared to user's 7-day rolling average. Thresholds: 3x = YELLOW, 5x = ORANGE, 10x = RED. Requires at least 3 historical bets for baseline.


8. Win Rate (DET_WIN_RATE)

File: rules/winRateRule.ts

Detects: Probability-adjusted win rate significantly higher than expected.

Logic: Compares actual win rate against expected win rate given the odds of bets placed. Uses V3ClvEntry data for CLV-adjusted analysis.


9. Opposite Side Same Market (DET_OPPOSITE_SIDE)

File: rules/oppositeSideRule.ts

Detects: Classic arbitrage/hedging — user bets both BACK and LAY on the same selection.

Logic: User places bets on both sides of the same selection within 30 seconds. Queries Prisma for matching bets.


10. Pattern Clone (DET_PATTERN_CLONE)

File: rules/patternCloneRule.ts

Detects: Coordinated betting — one user replicating another user's exact bet pattern.

Logic: Checks if this user's market/odds/stake combination matches another user's recent bet pattern on the same fixture. Queries fraud_events for market-level patterns.


11. Liquidity Dominance (DET_LIQUIDITY_DOMINANCE)

File: rules/liquidityDominanceRule.ts

Detects: Market manipulation — a single user's stake dominating available liquidity.

Logic: User's stake exceeds 30% of total market liquidity. Uses total_market_volume from exchange tick events.


12. Market Concentration (DET_MARKET_CONCENTRATION)

File: rules/marketConcentrationRule.ts

Detects: Unnatural focus on a single market suggesting information advantage.

Logic: Over 70% of user's bet portfolio concentrated on a single market across multiple fixtures.


13. Rapid Cashout (DET_RAPID_CASHOUT)

File: rules/rapidCashoutRule.ts

Detects: Low-risk arbitrage — placing a bet and immediately cashing out for guaranteed profit.

Logic: Cashout event within 5 seconds of bet placement.


14. Suspension Probing (DET_SUSPENSION_PROBING)

File: rules/suspensionProbingRule.ts

Detects: Information leakage — user placing bets just before markets get suspended.

Logic: Bets placed 2–5 seconds before a market suspension event. Suggests the user knows a suspension is coming (e.g., a wicket, goal, or injury is about to happen).


15. Pre-toss Concentration (DET_PRE_TOSS_CONCENTRATION)

File: rules/preTossConcentrationRule.ts

Detects: Insider knowledge of toss outcomes in cricket.

Logic: Heavy betting volume concentrated immediately before a TOSS event marker. Analyzed via TimescaleDB pool queries on fixture timeline.


16. Post-toss Sharp Bet (DET_POST_TOSS_SHARP)

File: rules/postTossSharpBetRule.ts

Detects: Sharp bettor exploiting toss result before market adjusts.

Logic: Large bet placed at high odds immediately after TOSS marker, before odds have fully adjusted.


17. Session Break Timing (DET_SESSION_BREAK_TIMING)

File: rules/sessionBreakTimingRule.ts

Detects: Exploiting predictable market transitions.

Logic: Bets placed exactly at session breaks (over changes, innings starts, session starts). Uses event markers to identify break timing.


18. Reverse Bet (DET_REVERSE_BET)

File: rules/reverseBetRule.ts

Detects: Hedging or bet correction behavior.

Logic: User places a bet on the opposite outcome on the same market shortly after an initial bet.


19. Agent Multiplier Spike (DET_AGENT_MULTIPLIER_SPIKE)

File: rules/agentMultiplierSpikeRule.ts

Detects: Agent-level manipulation — abnormal spike in multiplier requests.

Logic: An individual agent's multiplier requests spike 5x above their baseline within a short time window.


20. Cross-agent Migration (DET_CROSS_AGENT_MIGRATION)

File: rules/crossAgentMigrationRule.ts

Detects: Users hopping between agents to evade detection.

Logic: User moves between 2+ agents within 24 hours. Checks FraudAgentScore entries for migration patterns.


21. Deposit-Bet-Withdraw (DET_DEPOSIT_BET_WITHDRAW)

File: rules/depositBetWithdrawRule.ts

Detects: Money laundering or fraud churn — rapid deposit/bet/withdrawal cycle.

Logic: Complete deposit → bet → withdrawal cycle within 1–2 hours.


22. Persistent CLV Beater (DET_PERSISTENT_CLV_BEATER)

File: rules/persistentClvBeaterRule.ts

Detects: Professional/sharp bettor consistently beating closing line value.

Logic: Average CLV > 3% sustained over 50+ bets in a 30-day window. Uses V3ClvEntry data.


23. Steam Move Exploitation (DET_STEAM_MOVE)

File: rules/steamMoveExploitationRule.ts

Detects: Betting ahead of sharp market movements (steam moves).

Logic: User places bets just before sudden market movement spikes. Requires exchange volume spike detection from tick data.


Fraud Signatures

Composite detectors that combine multiple signals for high-confidence flagging.

24. Courtsiding Signature (SIG_COURTSIDING)

File: signatures/courtsiding.ts

Detects: Confirmed courtsiding pattern using multiple corroborating signals.

Logic:

  • Primary: priceMovement dimension ≥ 60 AND market suspension occurs between T and T+1.
  • Confirming: repetition dimension ≥ 60 OR >60% of user's in-play bets show pre-suspension pattern.
  • Win rate check on in-play bets for statistical confirmation.

25. Multiplier Manipulation Signature (SIG_MULTIPLIER_MANIPULATION)

File: signatures/multiplierManipulation.ts

Detects: Systematic exploitation of the multiplier/odds system.

Logic:

  • Multiplier edge > 5% on average.
  • Consistent positive edge across 10+ bets.
  • User payout ratio > 120% of expected value given the odds they received.

26. Multi-Accounting Signature (SIG_MULTI_ACCOUNTING)

File: signatures/multiAccounting.ts

Detects: Confirmed multi-accounting using multiple corroborating signals.

Logic:

  • Identity linkage cluster confidence > 0.7.
  • Device fingerprint or IP address matches across accounts.
  • Similar betting patterns across linked accounts.
  • Shared payment method detected.

How to Add a New Rule

Step 1: Create the rule file

Create a new file in backend/src/services/fraud/rules/ (for deterministic) or signatures/ (for composite):

// backend/src/services/fraud/rules/yourNewRule.ts

import type {
FraudRule,
EnrichedBet,
BetContext,
DimensionScores,
RuleResult,
} from '../types.js';

const RULE_ID = 'DET_YOUR_RULE_NAME';
const RULE_NAME = 'Your Rule Name';

export class YourNewRule implements FraudRule {
readonly id = RULE_ID;
readonly name = RULE_NAME;
readonly type = 'deterministic' as const;
readonly requiredContext = ['userBetHistory30d'];

// Pass prisma or tsdbPool via constructor if you need DB access
constructor(private prisma?: any) {}

async evaluate(
bet: EnrichedBet,
context: BetContext,
_scores?: DimensionScores,
): Promise<RuleResult> {
const base = { ruleId: this.id, ruleName: this.name };

// ── Your detection logic ──
const suspicious = false;

if (!suspicious) {
return {
...base,
triggered: false,
severity: 'GREEN',
confidence: 0,
details: { reason: 'Not triggered' },
};
}

return {
...base,
triggered: true,
severity: 'RED', // RED | ORANGE | YELLOW
confidence: 0.85, // 0.0 – 1.0
details: {
reason: 'Why this triggered',
// Include any supporting data for the case detail view
},
};
}
}

Step 2: Register in the rule registry

Edit backend/src/services/fraud/rules/ruleRegistry.ts:

// Add import at the top
import { YourNewRule } from './yourNewRule.js';

// Inside createRuleRegistry(), add the registration:
registry.register(new YourNewRule(prisma));

Step 3: Deploy

cd /root/bbook
docker compose -f docker-compose.bbook.yml up -d --build backend

No other changes needed. The evaluation pipeline automatically picks up registered rules.

Available data in evaluate()

ParameterContents
bet.userIdUser who placed the bet
bet.stakeBet amount
bet.oddsBet odds
bet.sideback or lay
bet.fixtureIdFixture identifier
bet.sportIdSport identifier
bet.marketIdMarket identifier
bet.betTimeWhen the bet was placed
bet.orderIdOrder identifier
bet.agentIdAgent who placed the bet (if applicable)
context.userBetHistory30dAll fraud_events for this user (last 30 days)
context.priceTicksExchange/bookmaker price ticks for the fixture
context.eventMarkersTimeline events: BALL, WICKET, GOAL, TOSS, CARD, etc.
context.v3BetFull V3Bet Prisma record
context.auditTrailV3AuditTrail entries for this bet
scoresDimensionScores object (only for deterministic/signature rules)

Rule types

TypePrefixWhen it runsWhat it returns
dimensiondim_Always, on every betScore 0–100 in details.score
deterministicDET_Always, checked conditionallytriggered: true/false with severity
signatureSIG_After dimensions and deterministic rulestriggered: true/false with severity

Tips

  • One rule failure doesn't block others. All rules run inside Promise.allSettled, so exceptions are caught silently.
  • Confidence (0–1) indicates how certain the rule is. Higher confidence = more weight in case priority.
  • Details object is stored in the FraudBetScore metadata and displayed in the admin case detail view.
  • If your rule needs TimescaleDB queries, accept tsdbPool in the constructor (see PreTossConcentrationRule for an example).
  • If your rule needs Prisma models, accept prisma in the constructor (see ImpossibleTravelRule for an example).