Overhaul trading strategy: profit-first reward, directional diversity, tighter risk
- Reward function: profit is king, big bonus for profitable closes, penalty for losses - Removed hold penalty (sitting in cash when uncertain is smart) - Directional diversity: max 3 positions same direction (was 13/13 shorts) - Tighter SL/TP: 1.0%/1.5% (was 2.5%/5.0%) - Max 6 concurrent positions (was 250) - RL: reset brain, live_epsilon 0.12 (was 0.03), hidden_dim 256 - GA: tighter ATR ranges, 150 population, higher mutation - Min backtest Sharpe 0.3 (was -0.5, allowing losing strategies) - Closed all 13 bleeding OANDA short positions
This commit is contained in:
+33
-26
@@ -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
|
||||
},
|
||||
|
||||
+6
-6
@@ -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
|
||||
|
||||
+24
-24
@@ -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))
|
||||
|
||||
+27
-4
@@ -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}")
|
||||
|
||||
Reference in New Issue
Block a user