From b793a8114bb98b395f480bbb18953dcdd470c83d Mon Sep 17 00:00:00 2001 From: TPTBusiness Date: Sun, 31 May 2026 17:43:45 +0200 Subject: [PATCH] fix: adaptive exploration boost when SOTA dominated by single indicator - +25% explore when >80% SOTA shares same indicator - Force non-dominant indicator every 100 iterations - Base exploration raised to 40% (effective 30% with 20 SOTA) --- scripts/nexquant_rd_loop.py | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/scripts/nexquant_rd_loop.py b/scripts/nexquant_rd_loop.py index 282837e0..0103ad3f 100644 --- a/scripts/nexquant_rd_loop.py +++ b/scripts/nexquant_rd_loop.py @@ -78,7 +78,7 @@ STRATEGY_TYPES = ["single", "multi_tf", "portfolio", "multi_role"] TREND_TFS = ["30min", "1h", "4h"] # higher TFs for trend filter ENTRY_TFS = ["5min", "15min", "30min"] # lower TFs for entry MIN_SHARPE, MIN_TRADES = 0.5, 20 -EXPLORATION_RATE = 0.30 # 30% explore, 70% exploit +EXPLORATION_RATE = 0.40 # 40% explore, 60% exploit # ═══════════════════════════════════════════════════════════════════════════════ # Evaluation @@ -256,7 +256,39 @@ class ResearchLoop: 'description': f"ML: LightGBM on {len(self.sota)} strategies", 'sota': self.sota[:5]} - if random.random() < self.exploration_rate or not self.sota: + # Every 100 iterations: force non-dominant indicator exploration + if self.iteration % 100 == 0 and len(self.sota) >= 5: + top_ind = self.sota[0]['hypothesis'].get('trend_ind', self.sota[0]['hypothesis'].get('indicator')) + hp = self._random_hypothesis() + # Ensure at least one role uses a different indicator + if hp.get('type') == 'multi_role' and hp['trend_ind'] == top_ind and hp['entry_ind'] == top_ind: + if random.random() < 0.5: + hp['trend_ind'] = random.choice([i for i in INDICATORS_POOL if i != top_ind]) + hp['trend_params'] = self._random_params(hp['trend_ind']) + else: + hp['entry_ind'] = random.choice([i for i in INDICATORS_POOL if i != top_ind]) + hp['entry_params'] = self._random_params(hp['entry_ind']) + hp['description'] = f"{hp['trend_ind']}({hp['trend_tf']})→{hp['entry_ind']}({hp['entry_tf']})" + elif hp.get('type') == 'single' and hp.get('indicator') == top_ind: + hp['indicator'] = random.choice([i for i in INDICATORS_POOL if i != top_ind]) + hp['params'] = self._random_params(hp['indicator']) + hp['description'] = f"{hp['indicator']} on {hp['timeframe']}" + elif hp.get('type') == 'multi_tf' and hp.get('indicator') == top_ind: + hp['indicator'] = random.choice([i for i in INDICATORS_POOL if i != top_ind]) + hp['params'] = self._random_params(hp['indicator']) + hp['description'] = f"{hp['indicator']} on {','.join(hp['timeframes'][:2])}" + hp['generation'] = 'explore' + return hp + + # Boost exploration when SOTA is dominated by one indicator + effective_rate = self.exploration_rate + if len(self.sota) >= 10: + top_ind = self.sota[0]['hypothesis'].get('trend_ind', self.sota[0]['hypothesis'].get('indicator')) + dominated = sum(1 for r in self.sota if r['hypothesis'].get('trend_ind', r['hypothesis'].get('indicator')) == top_ind) + if dominated > len(self.sota) * 0.8: # >80% same indicator + effective_rate += 0.25 # +25% explore boost + + if random.random() < effective_rate or not self.sota: return self._random_hypothesis() else: base = random.choice(self.sota[:5])