Skip to main content

One post tagged with "leniency"

View All Tags

Quick path to 16 tournaments — leniency and backfill without heavy refactors

· 4 min read
Max Kaido
Architect

Problem statement

We need to launch a full slate of ~16 tournaments daily quickly, even if individual strategies aren’t perfect yet. Candidate scarcity causes portfolio imbalance. This doc proposes a low-complexity approach—two-pass leniency and near‑miss backfill—implemented via small config knobs and generic consumer logic, so we can ship now and refine over time.

Quick path to “good-enough” 16 tournaments

  • Goal: increase candidate supply fast without heavy refactors.
  • Approach: add a two-pass leniency and near‑miss backfill, driven by simple config knobs. Keep “hard no-gos” intact. Pairwise LLM round will clean up remaining noise.

Minimal feature set (1–2 days, low complexity)

  • Two-pass validation
    • Pass 1: current strict rules.
    • Pass 2 (only if below targetCount): re-run with “leniency multipliers” applied to thresholds.
  • Near-miss backfill
    • If still below targetCount, include highest-score near-misses failing by the smallest normalized margin.
  • Score floor
    • Accept on Pass 2/backfill only if totalScore ≥ scoreFloor (e.g., 0.25–0.30).
  • Per-tournament leniency profile
    • profiles: default | loose | aggressive; encoded as multipliers for specific gates.
  • Hard no-gos never relaxed
    • Stablecoins, blacklists, obviously broken data, absurdly low liquidity.
  • Tiny telemetry
    • Count per-gate failures and average “distance-to-threshold”. No dashboards yet; log/CSV is enough.

Example config fields to add

validation: {
targetCandidatesAfterValidation: 160, // ensures supply for top 56
scoreFloorOnLeniency: 0.28,
twoPassEnabled: true,
leniencyProfile: 'loose',
relaxations: { // multipliers applied on Pass 2
volumeRatioMin: 0.85,
adxMin: 0.85,
bbWidthPctMax: 1.25,
diGapTolerance: 1.5, // allows more negative DI gap
slRiskGuardMax: 1.4, // allows larger SL distance
}
}

Where to wire it

  • In your validator helper for each tournament: accept an optional “leniency” object and apply multipliers to thresholds.
  • In processScoreMarketsBatch: after Pass 1, if validCount < target, run Pass 2 with leniency. If still low, backfill near-misses meeting scoreFloor.

Tournament-specific quick levers

  • Momentum Strength Buy (MSB)

    • Volume ratio floor: 1.2 → 1.0 on Pass 2.
    • ADX floor: 25 → 20 on Pass 2.
    • Enable rescue path by default; lower volumeTrend rescue from 0.5 → 0.35.
    • Score floor on Pass 2/backfill: 0.28.
    • Keep EMA alignment required (do not drop structure entirely).
  • Volatility Breakout Buy (VBB)

    • BB width percentile: 20% → 30–35% on Pass 2.
    • Volume ratio: 1.2 → 1.0 on Pass 2.
    • DI gap tolerance: allow −10 instead of −3 on Pass 2; still require no strong bearish bias.
    • Risk guard (SL distance): 5% → 7% max on Pass 2.
    • Volume USD floor: 1.0M → 0.5M on Pass 2.
    • Allow “primed” without full “confirmed” for prefilter inclusion if score ≥ 0.35.

Near-miss backfill (simple rule)

  • Compute normalized distance for each failing gate and take min across gates.
  • Build a “nearMissScore” = −minNormalizedDistance; sort descending.
  • Add near-miss markets (meeting scoreFloor) until targetCandidatesAfterValidation is reached.

Safe bounds

  • Cap Pass 2 increase to 2–3× Pass 1 supply to avoid flooding.
  • Enforce global min liquidity and data-quality checks.
  • Keep a conservative final score floor (≥0.25) for any leniency-induced candidate.

Why this works now

  • Your LLM bracket (step 4–5) is robust and benefits from more diverse, reasonably good candidates.
  • Two-pass + backfill gives you control to always feed enough to reach top 56 without significant engineering.

Phased rollout

  • Day 1: add leniency knobs + two-pass + backfill + tiny telemetry.
  • Day 2: tune MSB/VBB multipliers, set per-tournament targets, watch logs for gate bottlenecks.
  • Week 1: promote more tournaments to this pattern; set consistent targets to reach 16 daily.

Summary

  • Add two-pass leniency + near-miss backfill with a score floor; no heavy DSL needed.
  • Loosen specific MSB/VBB gates as above to quickly lift supply.
  • Keep hard no-gos; rely on LLM rounds to refine quality.
  • Minimal telemetry (counts + distances) is enough to iterate quickly toward 16 tournaments.

Short answer

  • Stick with custom helpers now. Don’t force MSB/VBB into a fully config-only validator/scorer yet.

What I’m recommending

  • Keep the bespoke helper methods for MSB and VBB as the source of truth.
  • Add light, per-tournament config knobs that the helpers read:
    • leniency multipliers (two-pass)
    • target candidate count after validation
    • score floor for lenient/backfill passes
    • near-miss backfill toggle
  • This gives you quick tuning to reach 16 tournaments without heavy refactors.

Later (when you have time)

  • Gradually extend the helper DSL to express gates/state machines and conditional thresholds.
  • Migrate specific pieces of logic to config once expressible, keep escape hatches for bespoke cases.

Summary

  • Use custom helpers + small config levers now to ship 16 “good-enough” tournaments fast.
  • Evolve toward more config-driven definitions incrementally, not as a prerequisite.

Example of how to implement leniency on MSB v3

  • Implemented a generic leniency section on TournamentConfig, applied it to MSB v3 config, and updated the consumer to use these fields without checking tournament type.

What changed

  • libs/core/kaido-types/src/dike/tournament.types.ts
    • Added TournamentLeniencyConfig and optional leniency?: TournamentLeniencyConfig to TournamentConfig.
  • domains/mercury/backend/src/shared/modules/dike/tournaments/configs/momentum-strength-buy/v3/momentum-strength-buy-v3.ts
    • Added:
      • leniency.enabled: true
      • leniency.scoreFloor: 0.28
      • leniency.minVolumeUsd: 500_000
      • leniency.preferValidOverLenient: true
  • domains/mercury/backend/src/shared/modules/dike/tournament.consumer.ts
    • In scoring batch:
      • If strict validation fails, use tournamentConfig.leniency to accept as LENIENT when score >= scoreFloor and volumeUsd >= minVolumeUsd.
      • Mark entries via breakdown.validity = 'VALID'|'LENIENT'.
    • In preselection:
      • If leniency.preferValidOverLenient is true, pick VALID first then LENIENT by score; else purely by score.

How to extend to other tournaments

  • Add the leniency block to a tournament config with desired thresholds. The consumer will automatically:
    • Accept borderline candidates as LENIENT under your score/liquidity floor.
    • Prefer VALID over LENIENT if configured.
    • Still feed only the top 56 into pairwise LLM.

This keeps helpers bespoke, uses small config knobs for tuning, and lets you enable the same approach on any tournament by editing its config only.