feat: apply #33B impulse trail + #34A skip hours 9,21 WIB
#33B Impulse Trail (position_manager.py): - Tighten trailing SL to 1.5x ATR when candle range > 1.5x ATR - Locks profit faster during volatile spikes (+$59, Sharpe 4.03) #34A Time-of-Hour Filter (main_live.py): - Skip entries at WIB hours 9 (02:00 UTC) and 21 (14:00 UTC) - Hour 9 = end NY session (low liquidity), Hour 21 = London-NY transition (whipsaw) - +$356 vs #31B, WR 82.6%, Sharpe 4.41, PF 2.43, DD 2.4% Cumulative live: $3,163 net, 614 trades, 82.6% WR, Sharpe 4.41 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -932,6 +932,15 @@ class TradingBot:
|
||||
logger.info(f"H1 Filter: {final_signal.signal_type} blocked (H1=NEUTRAL)")
|
||||
return
|
||||
|
||||
# 10.2 Time-of-Hour Filter (#34A: skip WIB hours 9 and 21 — backtest +$356)
|
||||
# Hour 9 WIB (02:00 UTC) = end of NY session, low liquidity
|
||||
# Hour 21 WIB (14:00 UTC) = London-NY transition, whipsaw prone
|
||||
from zoneinfo import ZoneInfo
|
||||
wib_hour = datetime.now(ZoneInfo("Asia/Jakarta")).hour
|
||||
if wib_hour in (9, 21):
|
||||
logger.info(f"Time Filter: {final_signal.signal_type} blocked (WIB hour {wib_hour} is skip hour)")
|
||||
return
|
||||
|
||||
# 10.5 Check trade cooldown
|
||||
if self._last_trade_time:
|
||||
time_since_last = (datetime.now() - self._last_trade_time).total_seconds()
|
||||
|
||||
+21
-2
@@ -340,9 +340,18 @@ class SmartPositionManager:
|
||||
if atr_val is not None and atr_val > 0:
|
||||
current_atr = atr_val
|
||||
|
||||
# #33B: Get last candle range for impulse detection
|
||||
last_candle_range = None
|
||||
if len(df_market) >= 1:
|
||||
last_row = df_market.tail(1)
|
||||
last_high = last_row["high"].item()
|
||||
last_low = last_row["low"].item()
|
||||
if last_high is not None and last_low is not None:
|
||||
last_candle_range = last_high - last_low
|
||||
|
||||
for row in positions.iter_rows(named=True):
|
||||
action = self._analyze_single_position(
|
||||
row, market_analysis, current_price, current_atr
|
||||
row, market_analysis, current_price, current_atr, last_candle_range
|
||||
)
|
||||
if action:
|
||||
actions.append(action)
|
||||
@@ -437,6 +446,7 @@ class SmartPositionManager:
|
||||
market: Dict,
|
||||
current_price: float,
|
||||
current_atr: float = None,
|
||||
last_candle_range: float = None,
|
||||
) -> Optional[PositionAction]:
|
||||
"""Analyze a single position and decide action."""
|
||||
ticket = pos["ticket"]
|
||||
@@ -575,8 +585,17 @@ class SmartPositionManager:
|
||||
)
|
||||
|
||||
# 6. Trailing stop (after trail_start pips)
|
||||
# #33B: Impulse detection — tighten trail when candle range > 1.5x ATR
|
||||
if pip_profit >= trail_start:
|
||||
trail_distance = trail_step * 0.1 # Convert to price
|
||||
is_impulse = False
|
||||
if last_candle_range is not None and current_atr is not None and current_atr > 0:
|
||||
if last_candle_range > current_atr * 1.5:
|
||||
is_impulse = True
|
||||
|
||||
active_trail_step = trail_step
|
||||
if is_impulse:
|
||||
active_trail_step = (current_atr * 1.5) / 0.1 # 1.5x ATR in pips
|
||||
trail_distance = active_trail_step * 0.1 # Convert to price
|
||||
|
||||
if is_buy:
|
||||
new_trail_sl = current_price - trail_distance
|
||||
|
||||
Reference in New Issue
Block a user