Daniel Alnawaz commited on
Commit
d8a67a4
·
unverified ·
1 Parent(s): 930df4c
.idea/copilotDiffState.xml CHANGED
The diff for this file is too large to render. See raw diff
 
README.md CHANGED
@@ -119,6 +119,12 @@ Safer learning defaults (already enabled by default):
119
  python adaptive_bot.py --interval 15m --min-model-samples-for-signal 50 --pause-on-negative-expectancy
120
  ```
121
 
 
 
 
 
 
 
122
  Enable periodic ML progress updates to Telegram (good for phone monitoring):
123
 
124
  ```bash
@@ -227,6 +233,7 @@ Environment variables used by the container:
227
  - `BOT_PAUSE_EXPECTANCY_LOOKBACK` (default `30`)
228
  - `BOT_PAUSE_EXPECTANCY_THRESHOLD_USD` (default `-1.0`)
229
  - `BOT_PAUSE_MAX_DRAWDOWN_PCT` (default `12.0`)
 
230
  - `ALLOW_LIVE_TRADING` (set to `1/true/yes` to manually approve live trading)
231
  - `BOT_LIVE_STARTUP_TEST` (set non-empty to add `--live-startup-test`)
232
  - `BOT_LIVE_STARTUP_TEST_COIN` (default `BTC`)
 
119
  python adaptive_bot.py --interval 15m --min-model-samples-for-signal 50 --pause-on-negative-expectancy
120
  ```
121
 
122
+ Auto-expire the safety pause after 3 hours (default) so the bot can retry entries:
123
+
124
+ ```bash
125
+ python adaptive_bot.py --interval 15m --pause-cooldown-minutes 180
126
+ ```
127
+
128
  Enable periodic ML progress updates to Telegram (good for phone monitoring):
129
 
130
  ```bash
 
233
  - `BOT_PAUSE_EXPECTANCY_LOOKBACK` (default `30`)
234
  - `BOT_PAUSE_EXPECTANCY_THRESHOLD_USD` (default `-1.0`)
235
  - `BOT_PAUSE_MAX_DRAWDOWN_PCT` (default `12.0`)
236
+ - `BOT_PAUSE_COOLDOWN_MINUTES` (default `180`, set `0` to disable auto-expiry)
237
  - `ALLOW_LIVE_TRADING` (set to `1/true/yes` to manually approve live trading)
238
  - `BOT_LIVE_STARTUP_TEST` (set non-empty to add `--live-startup-test`)
239
  - `BOT_LIVE_STARTUP_TEST_COIN` (default `BTC`)
__pycache__/adaptive_bot.cpython-310.pyc CHANGED
Binary files a/__pycache__/adaptive_bot.cpython-310.pyc and b/__pycache__/adaptive_bot.cpython-310.pyc differ
 
adaptive_bot.py CHANGED
@@ -535,6 +535,7 @@ class AdaptiveHyperliquidBot:
535
  pause_expectancy_lookback: int,
536
  pause_expectancy_threshold_usd: float,
537
  pause_max_drawdown_pct: float,
 
538
  telegram_ml_report_hours: float,
539
  telegram_ml_report_min_trades: int,
540
  health_host: str,
@@ -567,6 +568,9 @@ class AdaptiveHyperliquidBot:
567
  self.pause_expectancy_lookback = int(max(10, pause_expectancy_lookback))
568
  self.pause_expectancy_threshold_usd = float(pause_expectancy_threshold_usd)
569
  self.pause_max_drawdown_pct = float(max(0.0, pause_max_drawdown_pct))
 
 
 
570
  self.telegram_ml_report_hours = float(max(0.0, telegram_ml_report_hours))
571
  self.telegram_ml_report_min_trades = int(max(0, telegram_ml_report_min_trades))
572
  self.health_host = health_host
@@ -626,6 +630,16 @@ class AdaptiveHyperliquidBot:
626
  def status_payload(self) -> Dict[str, Any]:
627
  metrics = self.learning.global_metrics()
628
  approval = self._live_approval_status_details()
 
 
 
 
 
 
 
 
 
 
629
  return {
630
  "service": "hyperliquid-adaptive-bot",
631
  "status": "ok" if self._running else "stopping",
@@ -656,6 +670,7 @@ class AdaptiveHyperliquidBot:
656
  },
657
  "model_samples": self.learning.model_samples,
658
  "min_model_samples_for_signal": self.min_model_samples_for_signal,
 
659
  }
660
 
661
  def status_overview_payload(self) -> Dict[str, Any]:
@@ -785,23 +800,64 @@ class AdaptiveHyperliquidBot:
785
 
786
  def _entry_pause_reason(self) -> Optional[str]:
787
  if not self.pause_on_negative_expectancy:
 
 
788
  return None
789
  pnl = [float(t.get("pnl_usd", 0.0)) for t in self.learning.closed_trades]
790
  if len(pnl) < self.pause_expectancy_lookback:
 
 
791
  return None
792
  exp_recent = float(np.mean(pnl[-self.pause_expectancy_lookback:]))
793
  if exp_recent >= self.pause_expectancy_threshold_usd:
 
 
 
 
794
  return None
795
 
796
  max_dd = self.learning.ml_progress_metrics().get("max_drawdown_pct")
797
  max_dd_val = float(max_dd) if max_dd is not None else 0.0
798
  if max_dd_val < self.pause_max_drawdown_pct:
 
 
 
 
799
  return None
800
- return (
 
 
 
 
801
  f"safety_pause expectancy_{self.pause_expectancy_lookback}={exp_recent:+.3f} "
802
  f"< {self.pause_expectancy_threshold_usd:+.3f} and max_dd={max_dd_val:.2f}%"
803
  )
804
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
805
  def _live_approval_granted(self) -> bool:
806
  return self._live_approval_status_details()["granted"]
807
 
@@ -1423,6 +1479,12 @@ def build_arg_parser() -> argparse.ArgumentParser:
1423
  default=float(os.environ.get("BOT_PAUSE_MAX_DRAWDOWN_PCT", "12.0")),
1424
  help="Pause only when max drawdown is also at/above this percentage",
1425
  )
 
 
 
 
 
 
1426
  p.add_argument(
1427
  "--telegram-ml-report-hours",
1428
  type=float,
@@ -1466,6 +1528,7 @@ def main() -> None:
1466
  pause_expectancy_lookback=args.pause_expectancy_lookback,
1467
  pause_expectancy_threshold_usd=args.pause_expectancy_threshold_usd,
1468
  pause_max_drawdown_pct=args.pause_max_drawdown_pct,
 
1469
  telegram_ml_report_hours=args.telegram_ml_report_hours,
1470
  telegram_ml_report_min_trades=args.telegram_ml_report_min_trades,
1471
  health_host=args.health_host,
 
535
  pause_expectancy_lookback: int,
536
  pause_expectancy_threshold_usd: float,
537
  pause_max_drawdown_pct: float,
538
+ pause_cooldown_minutes: float,
539
  telegram_ml_report_hours: float,
540
  telegram_ml_report_min_trades: int,
541
  health_host: str,
 
568
  self.pause_expectancy_lookback = int(max(10, pause_expectancy_lookback))
569
  self.pause_expectancy_threshold_usd = float(pause_expectancy_threshold_usd)
570
  self.pause_max_drawdown_pct = float(max(0.0, pause_max_drawdown_pct))
571
+ self.pause_cooldown_minutes = float(max(0.0, pause_cooldown_minutes))
572
+ self._safety_pause_started_ts: Optional[float] = None
573
+ self._safety_pause_wait_recovery: bool = False
574
  self.telegram_ml_report_hours = float(max(0.0, telegram_ml_report_hours))
575
  self.telegram_ml_report_min_trades = int(max(0, telegram_ml_report_min_trades))
576
  self.health_host = health_host
 
630
  def status_payload(self) -> Dict[str, Any]:
631
  metrics = self.learning.global_metrics()
632
  approval = self._live_approval_status_details()
633
+ safety_pause = {
634
+ "enabled": self.pause_on_negative_expectancy,
635
+ "lookback": self.pause_expectancy_lookback,
636
+ "expectancy_threshold_usd": self.pause_expectancy_threshold_usd,
637
+ "max_drawdown_pct": self.pause_max_drawdown_pct,
638
+ "cooldown_minutes": self.pause_cooldown_minutes,
639
+ "active": self._safety_pause_started_ts is not None,
640
+ "active_since_ts": self._safety_pause_started_ts,
641
+ "wait_recovery": self._safety_pause_wait_recovery,
642
+ }
643
  return {
644
  "service": "hyperliquid-adaptive-bot",
645
  "status": "ok" if self._running else "stopping",
 
670
  },
671
  "model_samples": self.learning.model_samples,
672
  "min_model_samples_for_signal": self.min_model_samples_for_signal,
673
+ "safety_pause": safety_pause,
674
  }
675
 
676
  def status_overview_payload(self) -> Dict[str, Any]:
 
800
 
801
  def _entry_pause_reason(self) -> Optional[str]:
802
  if not self.pause_on_negative_expectancy:
803
+ self._safety_pause_started_ts = None
804
+ self._safety_pause_wait_recovery = False
805
  return None
806
  pnl = [float(t.get("pnl_usd", 0.0)) for t in self.learning.closed_trades]
807
  if len(pnl) < self.pause_expectancy_lookback:
808
+ self._safety_pause_started_ts = None
809
+ self._safety_pause_wait_recovery = False
810
  return None
811
  exp_recent = float(np.mean(pnl[-self.pause_expectancy_lookback:]))
812
  if exp_recent >= self.pause_expectancy_threshold_usd:
813
+ if self._safety_pause_wait_recovery:
814
+ logger.info("Safety pause re-armed after expectancy recovered (expectancy_%d=%+.3f).", self.pause_expectancy_lookback, exp_recent)
815
+ self._safety_pause_started_ts = None
816
+ self._safety_pause_wait_recovery = False
817
  return None
818
 
819
  max_dd = self.learning.ml_progress_metrics().get("max_drawdown_pct")
820
  max_dd_val = float(max_dd) if max_dd is not None else 0.0
821
  if max_dd_val < self.pause_max_drawdown_pct:
822
+ if self._safety_pause_wait_recovery:
823
+ logger.info("Safety pause re-armed after drawdown recovered (max_dd=%.2f%%).", max_dd_val)
824
+ self._safety_pause_started_ts = None
825
+ self._safety_pause_wait_recovery = False
826
  return None
827
+
828
+ if self._safety_pause_wait_recovery:
829
+ return None
830
+
831
+ reason = (
832
  f"safety_pause expectancy_{self.pause_expectancy_lookback}={exp_recent:+.3f} "
833
  f"< {self.pause_expectancy_threshold_usd:+.3f} and max_dd={max_dd_val:.2f}%"
834
  )
835
 
836
+ if self.pause_cooldown_minutes <= 0:
837
+ return reason
838
+
839
+ now_ts = time.time()
840
+ if self._safety_pause_started_ts is None:
841
+ self._safety_pause_started_ts = now_ts
842
+
843
+ cooldown_seconds = self.pause_cooldown_minutes * 60.0
844
+ elapsed_seconds = now_ts - self._safety_pause_started_ts
845
+ if elapsed_seconds >= cooldown_seconds:
846
+ self._safety_pause_started_ts = None
847
+ self._safety_pause_wait_recovery = True
848
+ logger.warning(
849
+ "Safety pause cooldown expired after %.1f minutes; allowing entries until metrics recover.",
850
+ self.pause_cooldown_minutes,
851
+ )
852
+ return None
853
+
854
+ remaining_seconds = int(max(0.0, cooldown_seconds - elapsed_seconds))
855
+ remaining_minutes = remaining_seconds // 60
856
+ remaining_remainder = remaining_seconds % 60
857
+ return (
858
+ f"{reason} (cooldown {remaining_minutes:02d}m{remaining_remainder:02d}s remaining)"
859
+ )
860
+
861
  def _live_approval_granted(self) -> bool:
862
  return self._live_approval_status_details()["granted"]
863
 
 
1479
  default=float(os.environ.get("BOT_PAUSE_MAX_DRAWDOWN_PCT", "12.0")),
1480
  help="Pause only when max drawdown is also at/above this percentage",
1481
  )
1482
+ p.add_argument(
1483
+ "--pause-cooldown-minutes",
1484
+ type=float,
1485
+ default=float(os.environ.get("BOT_PAUSE_COOLDOWN_MINUTES", "180")),
1486
+ help="Minutes to keep safety pause active before auto-resuming entries until metrics recover (0 disables auto-expiry)",
1487
+ )
1488
  p.add_argument(
1489
  "--telegram-ml-report-hours",
1490
  type=float,
 
1528
  pause_expectancy_lookback=args.pause_expectancy_lookback,
1529
  pause_expectancy_threshold_usd=args.pause_expectancy_threshold_usd,
1530
  pause_max_drawdown_pct=args.pause_max_drawdown_pct,
1531
+ pause_cooldown_minutes=args.pause_cooldown_minutes,
1532
  telegram_ml_report_hours=args.telegram_ml_report_hours,
1533
  telegram_ml_report_min_trades=args.telegram_ml_report_min_trades,
1534
  health_host=args.health_host,