diff --git a/docs/research/PROFIT_MOMENTUM_CODE_SNIPPET.py b/docs/research/PROFIT_MOMENTUM_CODE_SNIPPET.py new file mode 100644 index 0000000..372c925 --- /dev/null +++ b/docs/research/PROFIT_MOMENTUM_CODE_SNIPPET.py @@ -0,0 +1,225 @@ +""" +Profit Momentum Tracker - Code Snippets for Integration +======================================================== + +Copy-paste these snippets into main_live.py for integration. +""" + +# ============================================================ +# SNIPPET 1: Import Statement (add to top of main_live.py) +# ============================================================ +from src.profit_momentum_tracker import ProfitMomentumTracker + + +# ============================================================ +# SNIPPET 2: Initialize Tracker (add to TradingBot.__init__) +# ============================================================ +# Initialize Profit Momentum Tracker (NEW) +self.momentum_tracker = ProfitMomentumTracker( + # Velocity thresholds + velocity_reversal_threshold=-0.5, # Exit if velocity < -0.5 $/s + deceleration_threshold=-1.0, # Exit if accel < -1.0 $/sΒ² + stagnation_threshold=0.1, # Velocity < 0.1 $/s = stagnant + stagnation_count_max=8, # Exit after 8 stagnant samples (4s) + + # Drawdown protection + peak_drawdown_threshold=40.0, # Exit if 40% drawdown from peak + min_peak_to_protect=10.0, # Only protect peaks > $10 + + # Anti-early-exit protection + min_profit_for_momentum_exit=5.0, # Don't exit on momentum if profit < $5 + grace_period_seconds=10.0, # Minimum 10s in profit before momentum exit + min_samples_required=6, # Minimum 6 samples (3s) before analyzing + + # Logging + enable_logging=True, +) + +# Pass tracker to Position Manager +self.position_manager = SmartPositionManager( + breakeven_pips=30.0, + trail_start_pips=50.0, + trail_step_pips=30.0, + atr_be_mult=2.0, + atr_trail_start_mult=4.0, + atr_trail_step_mult=3.0, + min_profit_to_protect=5.0, + max_drawdown_from_peak=50.0, + enable_market_close_handler=True, + min_profit_before_close=10.0, + max_loss_to_hold=100.0, + momentum_tracker=self.momentum_tracker, # NEW: Pass tracker + enable_momentum_exit=True, # NEW: Enable momentum exits +) + +# Initialize momentum log throttle +self._last_momentum_log = {} + + +# ============================================================ +# SNIPPET 3: Monitoring Method (add to TradingBot class) +# ============================================================ +async def _monitor_positions_momentum(self): + """ + Monitor open positions momentum every 500ms. + Updates profit tracker for real-time momentum analysis. + """ + logger.info("🎯 Profit momentum monitoring started (500ms interval)") + + while self.running: + try: + # Get open positions + positions_df = self.mt5.get_positions() + + if len(positions_df) > 0: + # Update momentum tracker for each position + for row in positions_df.iter_rows(named=True): + ticket = row["ticket"] + profit = row.get("profit", 0.0) + current_price = row.get("price_current", 0.0) + + # Update tracker + self.momentum_tracker.update(ticket, profit, current_price) + + # Log metrics every 2 seconds per ticket + if self._should_log_momentum(ticket): + summary = self.momentum_tracker.get_position_summary(ticket) + if summary: + logger.debug( + f"#{ticket} | " + f"Profit: ${summary['current_profit']:.2f} | " + f"Peak: ${summary['peak_profit']:.2f} | " + f"Vel: {summary['velocity']:.2f} $/s | " + f"Momentum: {summary['momentum']} | " + f"Drawdown: {summary['drawdown_pct']:.1f}%" + ) + + # Wait 500ms before next update + await asyncio.sleep(0.5) + + except Exception as e: + logger.error(f"Momentum monitoring error: {e}") + await asyncio.sleep(0.5) + + +def _should_log_momentum(self, ticket: int) -> bool: + """ + Throttle momentum logging to every 2 seconds per ticket. + + Args: + ticket: MT5 ticket number + + Returns: + bool: True if should log now + """ + now = time.time() + last_log = self._last_momentum_log.get(ticket, 0) + + if now - last_log >= 2.0: # Log every 2 seconds + self._last_momentum_log[ticket] = now + return True + return False + + +# ============================================================ +# SNIPPET 4: Start Monitoring Task (modify run() method) +# ============================================================ +async def run(self): + """Main trading loop with momentum monitoring.""" + self.running = True + + logger.info("πŸš€ Starting trading bot...") + logger.info(f"Capital Mode: {self.config.capital_mode.value}") + logger.info(f"Risk per Trade: {self.risk_engine.risk_percent}%") + logger.info(f"Symbol: {self.config.symbol}") + + # Start background tasks + tasks = [ + asyncio.create_task(self._trading_loop(), name="trading_loop"), + asyncio.create_task(self._monitor_positions_momentum(), name="momentum_monitor"), # NEW + ] + + try: + # Wait for all tasks + await asyncio.gather(*tasks) + + except KeyboardInterrupt: + logger.warning("⚠️ Shutdown signal received") + self.running = False + + except Exception as e: + logger.error(f"❌ Critical error: {e}", exc_info=True) + self.running = False + + finally: + # Cleanup + logger.info("πŸ›‘ Shutting down...") + for task in tasks: + if not task.done(): + task.cancel() + + # Disconnect MT5 + if not self.simulation: + self.mt5.disconnect() + + logger.success("βœ… Shutdown complete") + + +# ============================================================ +# SNIPPET 5: Optional - Enhanced Position Summary Logging +# ============================================================ +def log_position_summary_with_momentum(self): + """ + Log detailed position summary including momentum metrics. + Call this periodically in trading loop. + """ + positions_df = self.mt5.get_positions() + + if len(positions_df) > 0: + logger.info(f"\n{'='*60}") + logger.info(f"OPEN POSITIONS: {len(positions_df)}") + logger.info(f"{'='*60}") + + for row in positions_df.iter_rows(named=True): + ticket = row["ticket"] + pos_type = row.get("type", "UNKNOWN") + profit = row.get("profit", 0.0) + volume = row.get("volume", 0.0) + + # Get momentum summary + momentum_summary = self.momentum_tracker.get_position_summary(ticket) + + if momentum_summary: + logger.info( + f" #{ticket} | {pos_type} {volume:.2f} lot | " + f"Profit: ${profit:.2f} | " + f"Peak: ${momentum_summary['peak_profit']:.2f} | " + f"Velocity: {momentum_summary['velocity']:.2f} $/s | " + f"Momentum: {momentum_summary['momentum']} | " + f"Samples: {momentum_summary['samples']} | " + f"Time in Profit: {momentum_summary['time_in_profit']:.1f}s" + ) + else: + logger.info( + f" #{ticket} | {pos_type} {volume:.2f} lot | " + f"Profit: ${profit:.2f} (no momentum data yet)" + ) + + logger.info(f"{'='*60}\n") + + +# ============================================================ +# EXAMPLE USAGE IN MAIN +# ============================================================ +if __name__ == "__main__": + # Create bot instance + bot = TradingBot(simulation=False) + + # Run with asyncio + try: + asyncio.run(bot.run()) + except KeyboardInterrupt: + logger.warning("⚠️ Interrupted by user") + except Exception as e: + logger.error(f"❌ Fatal error: {e}", exc_info=True) + sys.exit(1) diff --git a/docs/research/PROFIT_MOMENTUM_INTEGRATION.md b/docs/research/PROFIT_MOMENTUM_INTEGRATION.md new file mode 100644 index 0000000..85e0471 --- /dev/null +++ b/docs/research/PROFIT_MOMENTUM_INTEGRATION.md @@ -0,0 +1,320 @@ +# Profit Momentum Tracker Integration Guide + +## πŸ“‹ Overview + +Profit Momentum Tracker adalah sistem monitoring real-time yang menganalisa pergerakan profit per 500ms untuk mendeteksi timing exit yang optimal. Sistem ini mencegah early exit sambil melindungi profit dari reversal. + +## 🎯 Problem yang Diselesaikan + +1. **Early Cut** - Bot sering exit terlalu cepat ketika profit masih bisa grow +2. **Late Exit** - Bot terlambat exit ketika profit sudah mulai reverse +3. **Tidak Ada Visibility** - Tidak ada tracking real-time profit pattern per ticket +4. **Exit Decision Tidak Optimal** - Exit hanya based on fixed levels (TP/SL), tidak adaptive + +## πŸ”§ How It Works + +### 1. Profit Tracking (500ms interval) +```python +tracker.update(ticket, current_profit, current_price) +``` +- Track profit history dalam deque (max 40 samples = 20 detik) +- Calculate peak profit +- Track time in profit + +### 2. Momentum Metrics Calculation +```python +metrics = tracker.calculate_metrics(ticket) +``` + +Metrics yang dihitung: +- **Velocity** - Rate of profit change ($/s) +- **Acceleration** - Rate of velocity change ($/sΒ²) +- **Peak Profit** - Maximum profit achieved +- **Drawdown from Peak** - % dan $ amount +- **Momentum Direction** - INCREASING/STABLE/DECREASING +- **Stagnation Count** - Consecutive low-velocity samples + +### 3. Exit Conditions + +#### A. Velocity Reversal +``` +Trigger: velocity < -0.5 $/s +Protection: Only if profit >= $5 OR time_in_profit >= 10s +``` +Detect ketika profit mulai turn negative (momentum reversal). + +#### B. Strong Deceleration +``` +Trigger: acceleration < -1.0 $/sΒ² +Protection: Only if profit >= $5 +``` +Detect ketika profit growth slowing down significantly. + +#### C. Peak Drawdown +``` +Trigger: drawdown > 40% from peak +Protection: Only if peak >= $10 +``` +Exit ketika profit pulled back signifikan dari peak. + +#### D. Stagnation +``` +Trigger: 8 consecutive samples with velocity < 0.1 $/s +Protection: Only if profit >= $5 AND time_in_profit >= 10s +``` +Exit ketika profit flat terlalu lama (might reverse soon). + +## πŸš€ Integration Steps + +### Step 1: Import & Initialize in `main_live.py` + +```python +from src.profit_momentum_tracker import ProfitMomentumTracker + +class TradingBot: + def __init__(self, ...): + # ... existing init code ... + + # Initialize Profit Momentum Tracker (NEW) + self.momentum_tracker = ProfitMomentumTracker( + velocity_reversal_threshold=-0.5, # Exit if velocity < -0.5 $/s + deceleration_threshold=-1.0, # Exit if accel < -1.0 $/sΒ² + stagnation_threshold=0.1, # Velocity < 0.1 $/s = stagnant + stagnation_count_max=8, # 8 samples = 4 seconds + peak_drawdown_threshold=40.0, # Exit if 40% drawdown from peak + min_peak_to_protect=10.0, # Protect peaks > $10 + min_profit_for_momentum_exit=5.0, # Don't exit on momentum if < $5 + grace_period_seconds=10.0, # Min 10s in profit before momentum exit + enable_logging=True, + ) + + # Pass tracker to Position Manager + self.position_manager = SmartPositionManager( + # ... existing params ... + momentum_tracker=self.momentum_tracker, # NEW + enable_momentum_exit=True, # NEW + ) +``` + +### Step 2: Add Monitoring Loop in Trading Loop + +Tambahkan async task untuk monitor profit setiap 500ms: + +```python +async def _monitor_positions_momentum(self): + """ + Monitor open positions momentum every 500ms. + Updates profit tracker for real-time analysis. + """ + while self.running: + try: + # Get open positions + positions_df = self.mt5.get_positions() + + if len(positions_df) > 0: + # Update momentum tracker for each position + for row in positions_df.iter_rows(named=True): + ticket = row["ticket"] + profit = row.get("profit", 0.0) + current_price = row.get("price_current", 0.0) + + # Update tracker + self.momentum_tracker.update(ticket, profit, current_price) + + # Optional: Log metrics every 2 seconds + if self._should_log_momentum(ticket): + summary = self.momentum_tracker.get_position_summary(ticket) + if summary: + logger.debug( + f"#{ticket} | Profit: ${summary['current_profit']:.2f} | " + f"Peak: ${summary['peak_profit']:.2f} | " + f"Velocity: {summary['velocity']:.2f} $/s | " + f"Momentum: {summary['momentum']}" + ) + + # Wait 500ms before next update + await asyncio.sleep(0.5) + + except Exception as e: + logger.error(f"Momentum monitoring error: {e}") + await asyncio.sleep(0.5) + + +def _should_log_momentum(self, ticket: int) -> bool: + """Throttle logging to every 2 seconds per ticket.""" + if not hasattr(self, "_last_momentum_log"): + self._last_momentum_log = {} + + now = time.time() + last_log = self._last_momentum_log.get(ticket, 0) + + if now - last_log >= 2.0: # Log every 2 seconds + self._last_momentum_log[ticket] = now + return True + return False +``` + +### Step 3: Start Monitoring Task in Main Loop + +```python +async def run(self): + """Main trading loop.""" + self.running = True + + # Start background tasks + tasks = [ + asyncio.create_task(self._trading_loop()), + asyncio.create_task(self._monitor_positions_momentum()), # NEW + ] + + try: + await asyncio.gather(*tasks) + except Exception as e: + logger.error(f"Trading error: {e}") + finally: + self.running = False +``` + +### Step 4: Cleanup on Position Close + +Already handled automatically in `SmartPositionManager`: + +```python +# In position_manager.py - execute_actions() +if close_result["success"]: + logger.info(f"CLOSED #{action.ticket}: {action.reason}") + self._peak_profits.pop(action.ticket, None) + # Clean up momentum tracker + if self.momentum_tracker: + self.momentum_tracker.cleanup_position(action.ticket) # βœ… Auto cleanup +``` + +## πŸ“Š Usage Examples + +### Example 1: Check Exit Signal Manually +```python +should_exit, reason = tracker.should_exit(ticket, current_profit) +if should_exit: + logger.warning(f"Exit signal for #{ticket}: {reason}") + # Close position +``` + +### Example 2: Get Position Summary +```python +summary = tracker.get_position_summary(ticket) +print(f"Ticket: {summary['ticket']}") +print(f"Current Profit: ${summary['current_profit']:.2f}") +print(f"Peak Profit: ${summary['peak_profit']:.2f}") +print(f"Velocity: {summary['velocity']:.2f} $/s") +print(f"Momentum: {summary['momentum']}") +print(f"Drawdown: {summary['drawdown_pct']:.1f}%") +``` + +### Example 3: Get All Summaries +```python +all_summaries = tracker.get_all_summaries() +for summary in all_summaries: + logger.info( + f"#{summary['ticket']}: ${summary['current_profit']:.2f} | " + f"Peak: ${summary['peak_profit']:.2f} | " + f"Vel: {summary['velocity']:.2f} $/s" + ) +``` + +## πŸ§ͺ Testing + +Run test simulations: + +```bash +python tests/test_profit_momentum.py +``` + +Test scenarios: +1. **Pattern 1**: Steady growth β†’ reversal (should exit at ~90-94% of peak) +2. **Pattern 2**: Quick spike β†’ sharp reversal (should exit fast on velocity reversal) +3. **Pattern 3**: Healthy trend (should NOT exit, maintain position) + +## βš™οΈ Tuning Parameters + +### Conservative (Protect Profit Aggressively) +```python +ProfitMomentumTracker( + velocity_reversal_threshold=-0.3, # Exit sooner + peak_drawdown_threshold=30.0, # Exit on smaller drawdown + grace_period_seconds=5.0, # Shorter grace period +) +``` + +### Aggressive (Let Profit Run) +```python +ProfitMomentumTracker( + velocity_reversal_threshold=-1.0, # Exit later + peak_drawdown_threshold=50.0, # Allow larger drawdown + grace_period_seconds=15.0, # Longer grace period +) +``` + +### Balanced (Default - Recommended) +```python +ProfitMomentumTracker( + velocity_reversal_threshold=-0.5, + deceleration_threshold=-1.0, + peak_drawdown_threshold=40.0, + grace_period_seconds=10.0, +) +``` + +## πŸ“ˆ Expected Benefits + +1. **Better Exit Timing** - Exit based on momentum analysis, not just fixed levels +2. **Avoid Early Cuts** - Grace period & minimum profit protection +3. **Protect from Reversals** - Detect momentum changes before profit turns to loss +4. **Real-time Visibility** - Log profit patterns per ticket +5. **Adaptive Exits** - Respond to actual market movement, not just static TP/SL + +## πŸ” Monitoring & Logging + +Enable detailed logging: +```python +tracker = ProfitMomentumTracker(enable_logging=True) +``` + +Log output examples: +``` +14:32:10 | WARNING | #123456 Momentum reversal detected (velocity: -0.8 $/s, profit: $45.20) +14:32:10 | WARNING | 🚨 EXIT SIGNAL at $45.20: Momentum Exit: Momentum reversal detected +14:32:10 | SUCCESS | βœ… Exit Summary: Peak $50.00 β†’ Exit $45.20 (9.6% from peak) +``` + +## 🎯 Integration Checklist + +- [ ] Import `ProfitMomentumTracker` in `main_live.py` +- [ ] Initialize tracker with tuned parameters +- [ ] Pass tracker to `SmartPositionManager` +- [ ] Add `_monitor_positions_momentum()` method +- [ ] Start monitoring task in `run()` method +- [ ] Test with `test_profit_momentum.py` +- [ ] Monitor logs during live trading +- [ ] Tune parameters based on results + +## πŸ“ Notes + +- Tracker menggunakan **deque with maxlen=40** (20 detik history) +- **Minimal 6 samples** (3 detik) required untuk analisis +- **Grace period** mencegah exit terlalu cepat di awal profit +- **Peak drawdown** hanya aktif jika peak >= threshold +- **Velocity & acceleration** calculated from recent samples untuk responsiveness + +## 🚨 Important Warnings + +1. **Jangan disable grace period** - Bisa cause excessive early exits +2. **Jangan set threshold terlalu ketat** - Bisa exit di normal volatility +3. **Monitor backtest results** - Tune parameters based on historical performance +4. **Test di simulation dulu** - Jangan langsung live trading + +## πŸ“š Related Files + +- `src/profit_momentum_tracker.py` - Main tracker implementation +- `src/position_manager.py` - Integration with exit logic +- `tests/test_profit_momentum.py` - Simulation tests +- `main_live.py` - Main integration point diff --git a/tests/test_profit_momentum.py b/tests/test_profit_momentum.py new file mode 100644 index 0000000..67e8cc4 --- /dev/null +++ b/tests/test_profit_momentum.py @@ -0,0 +1,263 @@ +""" +Test Profit Momentum Tracker +============================= +Demo dan test untuk profit momentum tracking system. +""" + +import sys +import os +import time +import random +from pathlib import Path + +# Add project root to path +project_root = Path(__file__).parent.parent +sys.path.insert(0, str(project_root)) + +from src.profit_momentum_tracker import ProfitMomentumTracker +from loguru import logger + + +def simulate_profit_pattern_1(): + """ + Simulate Pattern 1: Steady Growth then Reversal + - Profit grows steadily + - Peaks at $50 + - Then reverses slowly + + Expected: Should exit around $45-$47 (90-94% of peak) + """ + logger.info("=" * 60) + logger.info("PATTERN 1: Steady Growth β†’ Reversal") + logger.info("=" * 60) + + tracker = ProfitMomentumTracker( + enable_logging=True, + min_profit_for_momentum_exit=5.0, + grace_period_seconds=3.0, + ) + + ticket = 123456 + price = 2650.0 + + # Phase 1: Steady growth (0-10s) + logger.info("\nπŸ“ˆ Phase 1: Steady Growth (0-10s)") + for i in range(20): # 10 seconds at 500ms interval + profit = i * 2.5 # Linear growth to $50 + price += 0.5 + + tracker.update(ticket, profit, price) + time.sleep(0.5) + + if i % 4 == 0: # Log every 2 seconds + metrics = tracker.calculate_metrics(ticket) + if metrics: + logger.info( + f" t={i*0.5:.1f}s | Profit: ${profit:.2f} | " + f"Velocity: {metrics.velocity:.2f} $/s | " + f"Momentum: {metrics.momentum_direction}" + ) + + # Phase 2: Peak stagnation (10-13s) + logger.info("\n⏸️ Phase 2: Peak Stagnation (10-13s)") + for i in range(6): # 3 seconds + profit = 50.0 + random.uniform(-0.5, 0.5) # Stagnant around $50 + price += random.uniform(-0.1, 0.1) + + tracker.update(ticket, profit, price) + should_exit, reason = tracker.should_exit(ticket, profit) + + if should_exit: + logger.warning(f"🚨 EXIT SIGNAL: {reason}") + break + + time.sleep(0.5) + + # Phase 3: Slow reversal (13-20s) + logger.info("\nπŸ“‰ Phase 3: Slow Reversal (13-20s)") + for i in range(14): # 7 seconds + profit = 50.0 - (i * 1.5) # Decline from $50 + price -= 0.3 + + tracker.update(ticket, profit, price) + should_exit, reason = tracker.should_exit(ticket, profit) + + metrics = tracker.calculate_metrics(ticket) + if metrics and i % 2 == 0: + logger.info( + f" t={13+i*0.5:.1f}s | Profit: ${profit:.2f} | " + f"Velocity: {metrics.velocity:.2f} $/s | " + f"Peak Drawdown: {metrics.drawdown_from_peak:.1f}%" + ) + + if should_exit: + logger.warning(f"🚨 EXIT SIGNAL at ${profit:.2f}: {reason}") + summary = tracker.get_position_summary(ticket) + logger.success( + f"βœ… Exit Summary: Peak ${summary['peak_profit']:.2f} β†’ " + f"Exit ${profit:.2f} ({summary['drawdown_pct']:.1f}% from peak)" + ) + break + + time.sleep(0.5) + + +def simulate_profit_pattern_2(): + """ + Simulate Pattern 2: Quick Spike then Sharp Reversal + - Profit spikes quickly to $40 + - Reverses sharply + + Expected: Should exit quickly on velocity reversal + """ + logger.info("\n" + "=" * 60) + logger.info("PATTERN 2: Quick Spike β†’ Sharp Reversal") + logger.info("=" * 60) + + tracker = ProfitMomentumTracker( + enable_logging=True, + velocity_reversal_threshold=-1.0, # More sensitive + min_profit_for_momentum_exit=5.0, + ) + + ticket = 234567 + price = 2650.0 + + # Phase 1: Quick spike (0-4s) + logger.info("\nπŸš€ Phase 1: Quick Spike (0-4s)") + for i in range(8): # 4 seconds + profit = i * 5.0 # Fast growth to $40 + price += 1.0 + + tracker.update(ticket, profit, price) + time.sleep(0.5) + + metrics = tracker.calculate_metrics(ticket) + if metrics and i % 2 == 0: + logger.info( + f" t={i*0.5:.1f}s | Profit: ${profit:.2f} | " + f"Velocity: {metrics.velocity:.2f} $/s" + ) + + # Phase 2: Sharp reversal (4-8s) + logger.info("\nπŸ’₯ Phase 2: Sharp Reversal (4-8s)") + for i in range(8): # 4 seconds + profit = 40.0 - (i * 4.0) # Fast decline + price -= 0.8 + + tracker.update(ticket, profit, price) + should_exit, reason = tracker.should_exit(ticket, profit) + + metrics = tracker.calculate_metrics(ticket) + if metrics: + logger.info( + f" t={4+i*0.5:.1f}s | Profit: ${profit:.2f} | " + f"Velocity: {metrics.velocity:.2f} $/s | " + f"Accel: {metrics.acceleration:.2f} $/sΒ²" + ) + + if should_exit: + logger.warning(f"🚨 EXIT SIGNAL at ${profit:.2f}: {reason}") + summary = tracker.get_position_summary(ticket) + logger.success( + f"βœ… Exit Summary: Peak ${summary['peak_profit']:.2f} β†’ " + f"Exit ${profit:.2f}" + ) + break + + time.sleep(0.5) + + +def simulate_profit_pattern_3(): + """ + Simulate Pattern 3: Healthy Trend (No Exit) + - Profit grows steadily + - Small pullbacks but momentum stays positive + + Expected: Should NOT exit (healthy momentum) + """ + logger.info("\n" + "=" * 60) + logger.info("PATTERN 3: Healthy Trend (No Exit Expected)") + logger.info("=" * 60) + + tracker = ProfitMomentumTracker( + enable_logging=True, + peak_drawdown_threshold=50.0, # Allow larger drawdown + ) + + ticket = 345678 + price = 2650.0 + + # Simulate 15 seconds of healthy growth with small pullbacks + logger.info("\nπŸ“Š Simulating healthy trend with pullbacks...") + for i in range(30): # 15 seconds + # Add some volatility but overall uptrend + base_profit = i * 1.5 + noise = random.uniform(-2.0, 3.0) # Slight upward bias + profit = base_profit + noise + + price += random.uniform(-0.2, 0.5) + + tracker.update(ticket, profit, price) + should_exit, reason = tracker.should_exit(ticket, profit) + + if i % 4 == 0: # Log every 2 seconds + metrics = tracker.calculate_metrics(ticket) + if metrics: + logger.info( + f" t={i*0.5:.1f}s | Profit: ${profit:.2f} | " + f"Peak: ${metrics.peak_profit:.2f} | " + f"Velocity: {metrics.velocity:.2f} $/s | " + f"Status: {metrics.momentum_direction}" + ) + + if should_exit: + logger.warning(f"⚠️ Unexpected exit: {reason}") + break + + time.sleep(0.5) + + if not should_exit: + logger.success("βœ… No exit triggered - Healthy trend maintained!") + summary = tracker.get_position_summary(ticket) + if summary: + logger.info( + f"Final Stats: Peak ${summary['peak_profit']:.2f}, " + f"Current ${summary['current_profit']:.2f}, " + f"Velocity {summary['velocity']:.2f} $/s" + ) + + +def main(): + """Run all simulation patterns.""" + logger.remove() + logger.add( + sys.stdout, + format="{time:HH:mm:ss.SSS} | {message}", + level="INFO", + ) + + logger.info("πŸ§ͺ Profit Momentum Tracker - Simulation Tests") + logger.info("=" * 60) + + try: + # Run pattern simulations + simulate_profit_pattern_1() + time.sleep(2) + + simulate_profit_pattern_2() + time.sleep(2) + + simulate_profit_pattern_3() + + except KeyboardInterrupt: + logger.warning("\n⚠️ Simulation interrupted by user") + except Exception as e: + logger.error(f"❌ Error: {e}", exc_info=True) + + logger.info("\n" + "=" * 60) + logger.success("πŸŽ‰ All simulations completed!") + + +if __name__ == "__main__": + main()