Skip to main content

Proposal: Simplify Balancing & Limit Enforcement Logic

Problem

The current BalancingService introduces unnecessary complexity that violates the agent's intent when setting exposure limits.

What the agent expects

"My max loss on cricket is 200K. Book bets until that limit. After that, forward everything."

Optionally, with balancing enabled:

"...but if a bet would actually reduce my exposure, let it through even at-limit."

What the code actually does

Three behaviors that contradict the agent's intent:

1. Hedge Bonus — allows booking ABOVE the limit

When balancingEnabled and utilization ≥ 85%, hedge bets get 10% bonus capacity:

maxRetainable = max(limitMax, limitMax × 1.10)

Agent set 200K limit → system books up to 220K for hedges. The agent explicitly said 200K is the max. Exceeding it, even for hedges, violates the agent's stated risk tolerance.

2. Imbalance Penalty — reduces booking BELOW the limit

When utilization ≥ 85% and the bet worsens imbalance:

penalty = 50% × imbalanceRatio × desiredAmount
adjustedRetention = desiredAmount - penalty

Agent still has capacity (say 170K used of 200K limit, 30K remaining). A new bet wants to retain 20K (well within limit). But because the bet "worsens imbalance," the system penalizes it and only retains 12K. The agent has capacity and wants to book — the system overrides that.

3. Hard Reject — refuses booking entirely below the limit

When utilization ≥ 95% and imbalance is high:

adjustedRetention = 0  // full forward

Agent has 190K of 200K used. 10K capacity remains. A non-hedge bet that wants to retain 8K gets fully forwarded because utilization > 95% AND imbalance ratio > threshold. Again, the agent has capacity but the system refuses.

Why this is wrong

All three behaviors modify the agent's limit by side-effect. The agent set a number. The system should respect that number — not second-guess it with heuristics. The limit already IS the risk control. Stacking graduated penalties and bonuses on top creates unpredictable behavior that agents can't reason about.


Proposed Fix

Replace the current BalancingService logic with a simple binary check:

if (exposure < limit)
→ book per matrix rules (cap at remaining capacity)

if (exposure >= limit AND bet INCREASES exposure)
→ forward 100%

if (exposure >= limit AND bet DECREASES exposure AND balancingEnabled)
→ book it (up to the limit, never above)

if (exposure >= limit AND bet DECREASES exposure AND !balancingEnabled)
→ forward 100% (agent wants directional exposure)

What changes

BehaviorCurrentProposed
Hedge at-limit10% bonus (book above limit)Book up to limit, never above
Non-hedge at-limitPenalty/reject based on imbalanceForward 100% (limit is the limit)
Under-limit, any betPenalty if imbalanced near 85%Book normally (capacity exists → use it)
balancingEnabled meaningComplex hedge bonus + imbalance penaltySimple: allow hedges at-limit, or not

What stays the same

  • Limit enforcement — "most restrictive wins" across GLOBAL/SPORT/MARKET scopes
  • Matrix rules — forward % resolution is unchanged
  • Cascade hierarchy — multi-level forwarding unchanged
  • Exposure ledger — tracking retained/forwarded unchanged
  • NNR flag — still set when limit exhausted (for dashboard/alerting)

Concrete Code Changes

1. balancingService.ts — Simplify getBalancingAdjustment()

Delete:

  • HEDGE_BONUS_FACTOR, PENALTY_UTILIZATION, HARD_REJECT_UTILIZATION constants
  • Graduated penalty calculation
  • Hard reject at 95% logic
  • Bonus capacity math

Replace with:

async getBalancingAdjustment(
agentId, eventId, selection, side,
desiredRetention, retainedStake,
limitRemaining, agentConfig
): Promise<BalancingAdjustment> {
if (!agentConfig.balancingEnabled) {
return { adjustedRetention: desiredRetention, reason: 'BALANCING_DISABLED', isHedge: false };
}

const impact = await this.evaluateBetImpact(agentId, eventId, selection, side, desiredRetention, retainedStake);

if (limitRemaining <= 0 && impact.isHedge) {
// At-limit but bet reduces exposure → allow up to the limit (not above)
return {
adjustedRetention: Math.min(desiredRetention, /* recalculated limit capacity after hedge */),
reason: 'HEDGE_ALLOWED_AT_LIMIT',
isHedge: true,
balancingScore: impact.improvementAmount,
worstCaseAfter: impact.worstCaseAfter,
};
}

// Under-limit or non-hedge: no adjustment, let limit enforcement handle it
return { adjustedRetention: desiredRetention, reason: 'NO_ADJUSTMENT', isHedge: impact.isHedge };
}

evaluateBetImpact() and getWorstCaseExposure() stay unchanged — they're clean. Only the decision logic in getBalancingAdjustment() changes.

2. cascadeEngine.ts (lines 407-426) — Stop overriding limits

Delete:

// Current: hedge relaxes limit, non-hedge tightens limit
if (isHedge) {
limitResult.maxRetainable = max(limitMax, adjustedRetention); // ← EXCEEDS LIMIT
} else {
limitResult.maxRetainable = min(limitMax, adjustedRetention); // ← REDUCES BELOW LIMIT
}

Replace with:

if (balancingResult.reason === 'HEDGE_ALLOWED_AT_LIMIT') {
// Hedge at-limit: re-evaluate capacity (exposure will decrease, freeing up room)
limitResult.maxRetainable = balancingResult.adjustedRetention;
limitResult.allPassed = limitResult.maxRetainable >= desiredRetention;
}
// All other cases: limitResult.maxRetainable stays as-is from limit enforcement

3. noNewRisk.ts — Keep dormant (or wire up later)

NNR evaluation (evaluateBetUnderNnr) stays unused for now. It duplicates what the simplified balancing already handles. If we want NNR to block the cascade entry entirely (optimization to skip cascade math), that's a separate follow-up.


Impact

Risk: Low

  • Only changes behavior for agents with balancingEnabled = true (opt-in)
  • Agents without balancing see zero change (limits work exactly as before)
  • Hedge detection math (evaluateBetImpact, worst-case calculation) is untouched

Behavioral changes for balancingEnabled agents:

  • Hedges at-limit: booked up to limit (previously up to 110%)
  • Non-hedge at 85-95%: booked normally up to limit (previously penalized)
  • Non-hedge at 95%+: booked up to remaining capacity (previously hard-rejected)

Testing

  1. Unit test: bet at-limit, hedge, balancing ON → retained up to limit
  2. Unit test: bet at-limit, hedge, balancing OFF → forwarded 100%
  3. Unit test: bet at-limit, non-hedge → forwarded 100%
  4. Unit test: bet under-limit, any type → normal matrix-based booking
  5. Unit test: bet under-limit, near 85%, non-hedge → still booked (no penalty)
  6. Integration: cascade with 2-level hierarchy, limit hit at level 1 → overflow forwards correctly

Summary

The limit is the agent's stated risk tolerance. The system should enforce it exactly — not relax it for hedges, not tighten it with penalties. Balancing should be a simple binary: "when I'm at my limit, do I want exposure-reducing bets to come through, or not?" That's one boolean, one if-statement, zero heuristics.