diff --git a/config/auto_config.json b/config/auto_config.json index 9688561..2ec0b83 100644 --- a/config/auto_config.json +++ b/config/auto_config.json @@ -32,7 +32,13 @@ "PG", "XLE", "CVX", - "XOM" + "XOM", + "LMT", + "RTX", + "NOC", + "GD", + "USO", + "GLD" ], "forex_symbols": [ "USD_JPY", @@ -49,54 +55,55 @@ "EUR_GBP", "EUR_CHF" ], - "cycle_interval_seconds": 120, + "cycle_interval_seconds": 60, "initial_capital": 200000, "target_capital": 250000, "commission_rate": 0.0, - "min_trade_value": 100, + "min_trade_value": 50, "require_approval": false, - "max_position_pct": 10, - "max_concurrent_positions": 250, - "stop_loss_pct": 2.5, - "take_profit_pct": 5.0, - "min_backtest_sharpe": -0.5, - "max_correlated_positions": 3 + "max_position_pct": 8, + "max_concurrent_positions": 6, + "stop_loss_pct": 1.0, + "take_profit_pct": 1.5, + "min_backtest_sharpe": 0.3, + "max_correlated_positions": 3, + "max_daily_trades": 50 }, "safety": { "max_position_pct": 10, - "max_concurrent_positions": 250, - "max_daily_trades": 300, + "max_concurrent_positions": 6, + "max_daily_trades": 50, "max_daily_loss_pct": 2, "max_total_loss_pct": 10, "min_trade_value": 100, "initial_capital": 200000 }, "rl": { - "gamma": 0.97, - "epsilon_start": 1.0, - "epsilon_min": 0.05, - "epsilon_decay": 0.999, - "learning_rate": 0.001, + "gamma": 0.95, + "epsilon_start": 0.8, + "epsilon_min": 0.08, + "epsilon_decay": 0.9995, + "learning_rate": 0.0005, "batch_size": 128, - "memory_size": 100000, + "memory_size": 250000, "target_update_freq": 50, - "hidden_dim": 128, - "live_epsilon": 0.05, - "train_interval_hours": 0.25, + "hidden_dim": 256, + "live_epsilon": 0.12, + "train_interval_hours": 0.1, "checkpoint_interval_hours": 1 }, "ga": { - "population_size": 60, + "population_size": 150, "elite_count": 8, - "mutation_rate": 0.3, - "mutation_strength": 0.25, + "mutation_rate": 0.4, + "mutation_strength": 0.3, "crossover_rate": 0.7, "tournament_size": 5, - "evolution_interval_hours": 1, - "generations_per_cycle": 20 + "evolution_interval_hours": 0.5, + "generations_per_cycle": 30 }, "backtest": { - "interval_seconds": 900, + "interval_seconds": 600, "lookback_days": 14, "initial_capital": 200000 }, diff --git a/src/ml/genetic.py b/src/ml/genetic.py index 2da0206..b361aac 100644 --- a/src/ml/genetic.py +++ b/src/ml/genetic.py @@ -29,8 +29,8 @@ GENE_RANGES = { 'macd_slow': (12, 26, True), 'macd_signal': (5, 9, True), 'volume_surge_threshold': (1.1, 2.5, False), - 'stop_loss_atr_mult': (0.5, 2.5, False), - 'take_profit_atr_mult': (0.8, 3.0, False), + 'stop_loss_atr_mult': (0.3, 1.5, False), + 'take_profit_atr_mult': (0.5, 2.0, False), 'max_position_pct': (0.05, 0.15, False), 'min_hold_candles': (1, 6, True), 'max_hold_candles': (3, 36, True), @@ -56,10 +56,10 @@ class StrategyGenome: # Entry thresholds volume_surge_threshold: float = 1.3 - # Risk management (tighter for scalping) - stop_loss_atr_mult: float = 1.2 - take_profit_atr_mult: float = 1.8 - max_position_pct: float = 0.10 + # Risk management (TIGHT for scalping) + stop_loss_atr_mult: float = 0.8 + take_profit_atr_mult: float = 1.2 + max_position_pct: float = 0.08 # Timing (short holds for scalping) min_hold_candles: int = 1 diff --git a/src/ml/rl_environment.py b/src/ml/rl_environment.py index b364edd..0b94d81 100644 --- a/src/ml/rl_environment.py +++ b/src/ml/rl_environment.py @@ -281,43 +281,43 @@ class TradingEnvironment: if prev_equity <= 0: return 0.0 - # Base reward: portfolio return (amplified 2x for scalping sensitivity) - base_reward = (curr_equity - prev_equity) / prev_equity * 2.0 + # PROFIT IS KING. Everything else is noise. + + # Base reward: actual P&L change (amplified for sensitivity) + pnl_change = curr_equity - prev_equity + base_reward = (pnl_change / prev_equity) * 5.0 - # Drawdown penalty + # Drawdown penalty (only kick in at 5%+, don't punish normal swings) drawdown = (self.peak_equity - curr_equity) / self.peak_equity if self.peak_equity > 0 else 0 - dd_penalty = -0.5 * max(0, drawdown - 0.03) + dd_penalty = -1.0 * max(0, drawdown - 0.05) - # Overtrading penalty (reduced for scalping - allow faster re-entry) - overtrade_penalty = 0.0 - if action != self.HOLD and (self.step_count - self.last_action_step) < 2: - overtrade_penalty = -0.0005 - - # Holding penalty (stronger for scalping - don't sit idle) - hold_penalty = 0.0 - if action == self.HOLD and self.position_shares == 0: - hold_penalty = -0.0002 - - # Profitable close bonus (works for both long and short) + # Profitable close bonus — the BIGGEST reward signal close_bonus = 0.0 if action in (self.CLOSE_HALF, self.CLOSE_ALL) and self.position_price > 0: if self.position_shares > 0 and current_price > self.position_price: - # Profitable long close pnl_pct = (current_price - self.position_price) / self.position_price - close_bonus = 0.02 * pnl_pct + close_bonus = 0.1 + pnl_pct * 5.0 # Big bonus for profitable closes elif self.position_shares < 0 and current_price < self.position_price: - # Profitable short close pnl_pct = (self.position_price - current_price) / self.position_price - close_bonus = 0.02 * pnl_pct + close_bonus = 0.1 + pnl_pct * 5.0 + elif self.position_shares > 0 and current_price < self.position_price: + pnl_pct = (self.position_price - current_price) / self.position_price + close_bonus = -0.05 - pnl_pct * 3.0 # Penalty for closing at a loss + elif self.position_shares < 0 and current_price > self.position_price: + pnl_pct = (current_price - self.position_price) / self.position_price + close_bonus = -0.05 - pnl_pct * 3.0 - # Quick scalp bonus: reward fast profitable round trips + # Quick scalp bonus: fast profitable round trips get extra reward scalp_bonus = 0.0 - if action in (self.CLOSE_HALF, self.CLOSE_ALL) and self.position_shares != 0: + if action in (self.CLOSE_HALF, self.CLOSE_ALL) and close_bonus > 0: hold_time = self.step_count - self.entry_step - if hold_time < 12 and close_bonus > 0: # Quick + profitable - scalp_bonus = 0.005 + if hold_time < 12: + scalp_bonus = 0.02 - reward = base_reward + dd_penalty + overtrade_penalty + hold_penalty + close_bonus + scalp_bonus + # NO hold penalty — sitting in cash when uncertain is SMART + # NO overtrading penalty — let the agent trade freely + + reward = base_reward + dd_penalty + close_bonus + scalp_bonus # Clip to [-1, 1] return max(-1.0, min(1.0, reward)) diff --git a/src/trading/executor.py b/src/trading/executor.py index 00e56b3..278924f 100644 --- a/src/trading/executor.py +++ b/src/trading/executor.py @@ -29,7 +29,7 @@ class TradingExecutor: self.commission_rate = config.get('commission_rate', 0.001) def execute_signal(self, symbol: str, action: int, current_price: float, - strategy_params: Dict = None) -> Optional[Dict]: + strategy_params: Dict = None, combined_equity: float = None) -> Optional[Dict]: """ Execute a trading action from the RL agent. @@ -42,6 +42,10 @@ class TradingExecutor: 5: Short 25% of available capital 6: Short 50% of available capital + Args: + combined_equity: Optional total equity across all brokers (for multi-broker setups) + If provided, used for safety checks instead of single-broker equity + Returns: trade record dict, or None if no action taken """ if action == 0: # Hold @@ -61,20 +65,34 @@ class TradingExecutor: logger.error(f"Error getting portfolio: {e}") return None + # Use combined equity for safety checks if provided (multi-broker setup) + safety_equity = combined_equity if combined_equity is not None else equity + # Update safety peak - self.safety.update_peak_equity(equity) + self.safety.update_peak_equity(safety_equity) # Get current positions open_positions = self.store.get_open_positions() open_for_symbol = [p for p in open_positions if p['symbol'] == symbol] num_positions = len(set(p['symbol'] for p in open_positions)) + # DIRECTIONAL DIVERSITY: Don't let all positions be the same direction + all_directions = [p.get('metadata', {}).get('direction', 'long') for p in open_positions] + num_long = sum(1 for d in all_directions if d == 'long') + num_short = sum(1 for d in all_directions if d == 'short') + max_one_direction = max(num_positions - 1, 3) # At least allow 3 of same direction + # BUY LONG actions if action in (1, 2): if open_for_symbol: logger.debug(f"Already have position in {symbol}, skipping buy") return None + # Block if too many longs already + if num_long >= max_one_direction: + logger.debug(f"Too many longs ({num_long}), blocking new long on {symbol}") + return None + pct = 0.25 if action == 1 else 0.50 invest = cash * pct @@ -93,7 +111,7 @@ class TradingExecutor: return None allowed, reason = self.safety.validate_trade( - symbol, 'buy', shares, current_price, equity, num_positions + symbol, 'buy', shares, current_price, safety_equity, num_positions ) if not allowed: logger.debug(f"Trade blocked: {reason}") @@ -220,6 +238,11 @@ class TradingExecutor: logger.debug(f"Already have position in {symbol}, skipping short") return None + # Block if too many shorts already + if num_short >= max_one_direction: + logger.debug(f"Too many shorts ({num_short}), blocking new short on {symbol}") + return None + pct = 0.25 if action == 5 else 0.50 invest = cash * pct @@ -238,7 +261,7 @@ class TradingExecutor: return None allowed, reason = self.safety.validate_trade( - symbol, 'sell', shares, current_price, equity, num_positions + symbol, 'sell', shares, current_price, safety_equity, num_positions ) if not allowed: logger.debug(f"Short blocked: {reason}")