Skip to main content

Take Settlement System (Deprecated)

Status: Deprecated — being replaced with new settlement logic Date documented: April 2026


Take Formula

Player Take

T = balancePoints - creditLimit

Agent Take

T = AC(self) + Σ AC(entire recursive downline) - creditLimit

Where:

  • AC(self) = agent user's balancePoints
  • AC(recursive downline) = direct players' balancePoints + sub-agent users' balancePoints + players under sub-agents' balancePoints
  • creditLimit = agent's creditLimit

Sign Convention (Entity Perspective)

  • T > 0 → upline owes entity (entity profited)
  • T < 0 → entity owes upline (entity lost)

Known Issue

Take includes exposure from open bets. When a bet is placed, balancePoints decreases by stake/liability but creditLimit stays the same — so Take drops immediately even though nothing has been won or lost yet. Take should only reflect realized P&L.

Initial:     balance=100, CL=100, exposure=0  → Take = 0    (correct)
Bet placed: balance=90, CL=100, exposure=10 → Take = -10 (wrong — bet hasn't settled)
Bet loses: balance=90, CL=100, exposure=0 → Take = -10 (correct)
Bet wins: balance=110, CL=100, exposure=0 → Take = +10 (correct)

Storage

  • Table: take_balances
  • Unique key: (entityType, entityId)
  • Fields: currentTake (Decimal 18,4), lastCalculatedAt, cumulativePnl, settledAmount, uplineAgentId

When Take Updates

  • Market settlement (bet outcome changes balancePoints)
  • Transfer & Settle (balancePoints adjusted during grace)
  • Does NOT update on: bet placement, credit allocation

Propagation

propagateTakeAfterMarketSettlement(userId) — updates upward 2 hops:

  1. Player's own take
  2. Direct agent's take
  3. Parent agent's take (master agent)

Uses Serializable isolation with exponential backoff retry (max 3 attempts).

Batch Computation

  • batchComputePlayerTakes(userIds[]) — single query, returns Map
  • batchComputeAgentTakes(agentIds[]) — single query, returns Map

Settlement Period Lifecycle

States

open → grace → finalized

Configuration

Stored in PlatformSettings (key: settlement_config):

  • periodDurationHours: default 168 (7 days)
  • gracePeriodMinutes: default 1440 (24 hours)
  • pollIntervalMinutes: default 60 (1 hour)

Open Period

  • Bets settle normally, takes accumulate
  • No settlement actions allowed
  • Period auto-created if none exists

Transition to Grace (Atomic)

  1. Update period status to grace, set graceEndsAt
  2. Snapshot ALL entities' takes into periodSettlementSnapshot table
    • All rows created as Pending, settledAmount = 0
    • Takes negated for viewer perspective: snapshot.take = -entityTake
    • Carryover amounts populated from N-1 finalization
    • dueDate = graceEndsAt
  3. Create overlapping period N+1 in open status

During Grace

  • Transfer & Settle operates on frozen snapshot values (not live takes)
  • Live takes still update from ongoing bets (which belong to N+1)
  • Carryover requests can be submitted and responded to

Finalization (finalizePeriodStatuses())

For each Pending snapshot:

ConditionResolved Status
settledAmount >= |take| (within 0.01 tolerance)Settled
Explicit carryover accepted during graceCarriedOver
take > 0 (entity owes viewer) + no actionDefaulted
take < 0 (viewer owes entity) + no actionCarriedOver

Unsettled amounts (Defaulted/CarriedOver) auto-propagate as accepted carryover requests to N+1.


Transfer & Settle

Constraints

  • Only during grace period
  • Reads from frozen snapshot, not live take
  • Blocked if active carryover request exists (pending or accepted)

Direction Logic

  • Frozen take > 0 (viewer perspective: "entity owes me") → collect → balance adjustment = +amount
  • Frozen take < 0 (viewer perspective: "I owe entity") → pay → balance adjustment = -amount

What Changes (Atomic Transaction)

FieldChange
user.balancePointsIncremented by balance adjustment
takeBalance.currentTakeUpdated to live recalculated take
snapshot.settledAmountIncremented by amount
snapshot.settlementStatusSet to Settled if fully settled
snapshot.finalSettlementDateSet to now if fully settled

Creates transferSettlement audit record with takeBefore, takeAfter, amount, notes.

Partial Settlement

  • Allowed — remainder auto-carries at grace end
  • Each partial creates a separate transferSettlement record

Carryover System

Three Flows

1. Downline Requests Carryover

  • During grace only
  • Creates CarryoverRequest with status pending
  • Upline accepts or rejects

2. Upline Carry Forward

  • During grace only
  • Instantly accepted, no approval needed
  • Creates accepted carryover for period N
  • Auto-propagates to N+1

3. Auto-Carry at Grace End

  • Unactioned entities at finalization
  • Defaulted (entity owes, no action) → auto-carry to N+1
  • CarriedOver (upline owes, no action) → auto-carry to N+1

On Accept

  • Update snapshot to CarriedOver
  • Set carryoverAmount
  • Create auto-accepted CarryoverRequest scoped to N+1

Period Settlement Snapshots

Table: periodSettlementSnapshot

FieldTypePurpose
periodIdstringPeriod this snapshot belongs to
entityTypestring'agent' | 'player'
entityIdstringAgent.id | User.id
entityNamestringDisplay name at snapshot time
accountStatusstringAccount status at snapshot time
takeDecimalFrozen take (negated for viewer perspective)
settlementStatusstringPending | Settled | CarriedOver | Defaulted
settledAmountDecimalCumulative settled during grace
carryoverAmountDecimalAmount carried over from previous period
dueDateDateTimeGrace period end
finalSettlementDateDateTimeWhen status was finalized

Agent Settlement Job

File: agentSettlementJob.ts

Polls every pollIntervalMinutes:

  1. Ensure period exists → create if missing
  2. now >= endDate → transition to grace (snapshot + create N+1)
  3. now >= graceEndsAt → finalize statuses → process agent settlements → finalize period

Manual Trigger

triggerSettlementCalculation(periodId) — admin can force agent settlement processing.


Commission Interaction

  • Commission deducted from balancePoints after market settlement
  • Reduces upline's take (entity owes less or upline owes more)
  • Per-user per-market: only when all of that user's orders in the market are settled
  • Only on exchange markets (betfair-ex), not bookmakers (bifrost, pinnacle)
  • 2% of net positive market P&L

Endpoints

Admin (authenticated)

  • GET /settlements/periods — list periods
  • GET /settlements/periods/current — active period
  • POST /settlements/periods — create (optional custom startDate/endDate)
  • PATCH /settlements/periods/:id/extend — extend open period end date
  • GET /settlements/periods/:id/downline-snapshot — snapshot or live downline

Agent (authenticated)

  • GET /agent/settlements — settlement history
  • GET /agent/settlements/current — current period stats
  • POST /agent/settle-downline — settle downline entity (grace only)
  • POST /agent/carry-forward-downline — upline carries forward (grace only)
  • POST /agent/request-carryover — request carryover from upline (grace only)
  • GET /agent/carryover/pending — pending carryover requests
  • POST /agent/carryover/:id/respond — accept/reject carryover

Frontend

Agent Settlements Page

  • Period selector (current + history)
  • Upline take section
  • Downline table with tabs: All, Pending, CarriedOver, Defaulted
  • Actions: Transfer & Settle modal, Carry Forward, Request Carryover, Respond to carryover
  • Grace period banner when in grace

Admin Settlement Periods Page

  • Current period card with extend button
  • Period list with status filter
  • Configuration panel (duration, grace, poll interval)

TransferSettleModal

  • Shows frozen take (grace) or live take
  • Direction: "Collecting from" or "Paying to"
  • Amount input with remaining calculation
  • Partial settlement warning
  • displayToFp conversion on submit